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

name

(string attribute) Name of this solver.

version

(string attribute) Version of this solver.

addReflection(reflection)

Add an observed diffraction reflection.

calculate_UB(r1, r2)

Calculate the UB matrix with two reflections.

extra_axis_names

Returns list of any extra axes in the current mode.

forward(pseudos)

Compute list of solutions(reals) from pseudos.

geometries

@classmethod [3] : Returns list of all geometries support by this solver.

inverse(reals)

Compute pseudos from reals.

modes

Returns list of all modes support by this geometry.

pseudo_axis_names

Returns list of all pseudos support by this geometry.

real_axis_names

Returns list of all reals support by this geometry.

refineLattice(reflections)

Return refined lattice parameters given reflections.

removeAllReflections()

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

HklSolver

Full-featured (Linux x86_64 only)

NoOpSolver

No-operation (demonstration & testing)

ThTthSolver

Minimal pure-Python (demonstration)

TrivialSolver() [7]

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 SolverBase which enforces a consistent interface.

  • The Core class handles unit conversion between diffractometer and solver units.

  • Solvers can be platform-specific (such as HklSolver which is C code compiled only for Linux x86_64 architectures).

  • Consider using NoOpSolver or TrivialSolver() [7] as starting references for testing infrastructure.

Footnotes#