How to write a new Solver#
An hklpy2 Solver is an adapter [2] for a backend diffractometer computation library.
Steps#
To write a new solver for hklpy2, you need to create a Python class
that inherits from SolverBase and
register it as an entry point.
Tip
Create a new project [6] for this work.
Here are the essential steps:
Step 1. Create a Solver Class#
Create a Python class that inherits from
SolverBase and implement all required abstract
methods (methods marked with decorator @abstractmethod
[1]):
1from hklpy2.backends.base import SolverBase
2from hklpy2.misc import IDENTITY_MATRIX_3X3
3from hklpy2.misc import KeyValueMap
4from hklpy2.misc import Matrix3x3
5from hklpy2.misc import NamedFloatDict
6
7class MySolver(SolverBase):
8 name = "my_solver"
9 version = "1.0.0"
10
11 def __init__(self, geometry: str, **kwargs):
12 super().__init__(geometry, **kwargs)
13
14 # Required abstract methods
15 def addReflection(self, reflection: KeyValueMap) -> None:
16 """Add an observed diffraction reflection."""
17 pass # TODO: send to your library
18
19 def calculate_UB(self, r1: KeyValueMap, r2: KeyValueMap) -> Matrix3x3:
20 """Calculate the UB matrix with two reflections."""
21 return IDENTITY_MATRIX_3X3 # TODO: calculate with your library
22
23 def forward(self, pseudos: NamedFloatDict) -> list[NamedFloatDict]:
24 """Compute list of solutions(reals) from pseudos."""
25 return [{}] # TODO: calculate with your library
26
27 def inverse(self, reals: NamedFloatDict) -> NamedFloatDict:
28 """Compute pseudos from reals."""
29 return {} # TODO: calculate with your library
30
31 def refineLattice(self, reflections: list[KeyValueMap]) -> NamedFloatDict:
32 """Refine lattice parameters from reflections."""
33 return {} # TODO: calculate with your library
34
35 def removeAllReflections(self) -> None:
36 """Remove all reflections."""
37 pass # TODO: use your library
38
39 # Required properties
40 @property
41 def extra_axis_names(self) -> list[str]:
42 """Ordered list of extra axis names."""
43 return []
44
45 @classmethod
46 def geometries(cls) -> list[str]:
47 """Supported diffractometer geometries."""
48 return ["MY_GEOMETRY"]
49
50 @property
51 def modes(self) -> list[str]:
52 """Available operating modes."""
53 return []
54
55 @property
56 def pseudo_axis_names(self) -> list[str]:
57 """Ordered list of pseudo axis names (h, k, l)."""
58 return []
59
60 @property
61 def real_axis_names(self) -> list[str]:
62 """Ordered list of real axis names (omega, chi, phi, tth)."""
63 return []
Step 2. Register as Entry Point#
Create a [project.entry-points."hklpy2.solver"] section in your project’s
pyproject.toml file and declare your solver. Here’s an example:
1[project.entry-points."hklpy2.solver"]
2my_solver = "my_package.my_solver:MySolver"
Step 3. Install and Test#
Install your package to make the solver discoverable. Then test that it loads correctly:
1import hklpy2
2
3# List available solvers
4print(hklpy2.solvers())
5
6# Load your solver class
7SolverClass = hklpy2.get_solver("my_solver")
8
9# Create an instance
10solver = SolverClass("MY_GEOMETRY")
Use the creator() factory to create a diffractometer
with your solver and test it. Here’s a suggested start:
1import hklpy2
2
3sim = hklpy2.creator(name="sim", solver="my_solver")
4sim.wh()
Key Implementation Details#
Required Methods Contract#
All solvers must implement these attributes, methods, and properties:
method (or property) |
description |
|---|---|
|
(string attribute) Name of this solver. |
|
(string attribute) Version of this solver. |
|
Add an observed diffraction reflection. |
|
Calculate the UB matrix with two reflections. |
|
Returns list of any extra axes in the current mode. |
|
Compute list of solutions(reals) from pseudos. |
|
|
|
Compute pseudos from reals. |
|
Returns list of all modes support by this geometry. |
|
Returns list of all pseudos support by this geometry. |
|
Returns list of all reals support by this geometry. |
|
Return refined lattice parameters given reflections. |
|
Clears sample of all stored reflections. |
Engineering Units System#
Define your solver’s internal units via class constants:
ANGLE_UNITS: Default “degrees”LENGTH_UNITS: Default “angstrom”
Example Reference#
Compare with these Solver classes:
class |
description |
|---|---|
Full-featured (Linux x86_64 only) |
|
No-operation (demonstration & testing) |
|
Minimal pure-Python (demonstration) |
|
|
Minimal requirements, non-functional (internal testing) |
Notes#
hklpy2 identifies a Solver [4] as a plugin using Python’s entry point [5] support.
All solvers must inherit from
SolverBasewhich enforces a consistent interface.The
Coreclass handles unit conversion between diffractometer and solver units.Solvers can be platform-specific (such as
HklSolverwhich is C code compiled only for Linux x86_64 architectures).Consider using
NoOpSolverorTrivialSolver()[7] as starting references for testing infrastructure.