Repository: pyxem/diffsims (language: python)

Issue:
Remove the deprecated reciprocal lattice point class
#### Description of the change
Removes the deprecated `diffsims.crystallography.ReciprocalLatticePoint` class, which was deprecated in June 2022. Closes #189.

Three methods in the public API for generating sets of {hkl} were not deprecated. These have improved replacements in the "new" `ReciprocalLatticeVector` class:
* `get_hkl()` -> `from_highest_hkl()`
* `get_highest_hkl()` -> `from_min_dspacing()`
* `get_equivalent_hkl()` -> `symmetrise()`

These functions should be deprecated and the user "new" methods. This can be done with the deprecation tools from #199, which should be merged before this PR.

#### Progress of the PR
- [x] [Docstrings for all functions](https://github.com/numpy/numpy/blob/master/doc/example.py)
- [x] Unit tests with pytest for all lines
- [x] Clean code style by [running black](https://diffsims.readthedocs.io/en/latest/contributing.html#get-the-style-right)
- [x] Deprecate old methods to get sets of reciprocal lattice vectors

#### For reviewers
<!-- Don't remove the checklist below. -->
- [ ] The PR title is short, concise, and will make sense 1 year later.
- [ ] New functions are imported in corresponding `__init__.py`.
- [ ] New features, API changes, and deprecations are mentioned in the
      unreleased section in `CHANGELOG.rst`.
- [ ] Contributor(s) are listed correctly in `credits` in `diffsims/release_info.py` and
      in `.zenodo.json`.


Discussion / hints:
I thought I'd resolved merge conflicts locally, but alas. I believe the issue relates to a small change you made in the  *reciprocal_lattice_point.py* file, @CSSFrancis (https://github.com/pyxem/diffsims/commit/6a7ac0b0090eb4f403a051edf3c8d1793b8cde0a), which I here delete. I'll make a new branch and PR.

Fix patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 11e21eca..e27a0964 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -28,6 +28,8 @@ Removed
 -------
 - Removed support for Python 3.6 and Python 3.7, leaving 3.8 as the oldest supported
   version.
+- ``ReciprocalLatticePoint`` class; Use the ``ReciprocalLatticeVector`` class instead,
+  which is an improved replacement.
 
 Fixed
 -----
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 8ad2eb98..30f45633 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -12,6 +12,7 @@ We have a `Code of Conduct
 <https://github.com/pyxem/diffsims/blob/master/.github/CODE_OF_CONDUCT.md>`_ that must
 be honoured by contributors.
 
+
 Start using diffsims
 ====================
 
@@ -42,6 +43,7 @@ install of diffsims!
 PS: If you choose to develop in Windows/Mac you may find the `Github Desktop
 <https://desktop.github.com>`_ useful.
 
+
 Questions?
 ==========
 
@@ -56,6 +58,7 @@ scary but it ensures that issues are identified and logged until dealt with. Thi
 also a good place to make a proposal for some new feature or tool that you want to work
 on.
 
+
 Good coding practice
 ====================
 
@@ -89,7 +92,6 @@ your newly added and modified files prior to each PR.
 If this doesn't work for you, you can also use the Pre-commit CI to reformat your code
 on github by commenting "pre-commit autofix" on your PR.
 
-
 Run and write tests
 -------------------
 
@@ -128,7 +130,6 @@ Useful hints on testing:
   error-prone. See `pytest documentation for more details
   <https://doc.pytest.org/en/latest/how-to/parametrize.html>`_.
 
-
 Deprecations
 ------------
 We attempt to adhere to semantic versioning as best we can. This means that as little,
@@ -138,16 +139,20 @@ so that users get a heads-up one (minor) release before something is removed or
 with a possible alternative to be used.
 
 
-A deprecation decorator should be placed right above the object signature to be deprecated.
+A deprecation decorator should be placed right above the object signature to be deprecated::
 
-.. code-block:: python
     from diffsims.utils._deprecated import deprecated
+
     @deprecated(since=0.8, removal=0.9, alternative="bar")
-    def foo(self, n):
-        return n + 1
-    @property
-    @deprecated(since=0.9, removal=0.10, alternative="another", is_function=True)
+    def foo(self, n): ...
 
+    @property
+    @deprecated(
+        since="0.9",
+        removal="0.10",
+        alternative="another",
+        alternative_is_function=True
+    ): ...
 
 Build and write documentation
 -----------------------------
@@ -173,6 +178,7 @@ in the `reStructuredText (reST)
 plaintext markup language. They should be accessible in the browser by typing
 ``file:///your-absolute/path/to/diffsims/doc/build/html/index.html`` in the address bar.
 
+
 Continuous integration (CI)
 ===========================
 
@@ -181,6 +187,7 @@ diffsims can be installed on Windows, macOS and Linux. After a successful instal
 the CI server runs the tests. After the tests return no errors, code coverage is
 reported to `Coveralls <https://coveralls.io/github/pyxem/diffsims?branch=master>`_.
 
+
 Learn more
 ==========
 
diff --git a/MANIFEST.in b/MANIFEST.in
index c7068aa3..2153cf7e 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,7 @@
+# Info on valid syntax for controlling files in the distribution in this file:
+# * https://setuptools.pypa.io/en/latest/userguide/miscellaneous.html
+# * https://setuptools.pypa.io/en/latest/userguide/datafiles.html
+
 include CHANGELOG.rst
 include CONTRIBUTING.rst
 include LICENSE
@@ -6,5 +10,6 @@ include README.rst
 include readthedocs.yaml
 include setup.cfg
 include setup.py
+include diffsims/tests/**/*.npy
 
-recursive-include doc Makefile make.bat *.rst *.py *.png
+recursive-include doc Makefile make.bat *.rst *.py *.png
\ No newline at end of file
diff --git a/diffsims/crystallography/__init__.py b/diffsims/crystallography/__init__.py
index 736a8e3d..564ac510 100644
--- a/diffsims/crystallography/__init__.py
+++ b/diffsims/crystallography/__init__.py
@@ -20,8 +20,7 @@
 g, hkl) for a crystal structure.
 """
 
-from diffsims.crystallography.reciprocal_lattice_point import (
-    ReciprocalLatticePoint,
+from diffsims.crystallography.get_hkl import (
     get_equivalent_hkl,
     get_highest_hkl,
     get_hkl,
@@ -32,6 +31,5 @@
     "get_equivalent_hkl",
     "get_highest_hkl",
     "get_hkl",
-    "ReciprocalLatticePoint",
     "ReciprocalLatticeVector",
 ]
diff --git a/diffsims/crystallography/get_hkl.py b/diffsims/crystallography/get_hkl.py
new file mode 100644
index 00000000..8d54135c
--- /dev/null
+++ b/diffsims/crystallography/get_hkl.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+# Copyright 2017-2023 The diffsims developers
+#
+# This file is part of diffsims.
+#
+# diffsims is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# diffsims is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with diffsims.  If not, see <http://www.gnu.org/licenses/>.
+
+from itertools import product
+
+import numpy as np
+from orix.vector import Vector3d
+
+from diffsims.utils._deprecated import deprecated
+
+
+@deprecated(
+    since="0.6",
+    alternative="diffsims.crystallography.ReciprocalLatticeVector.from_min_dspacing",
+    removal="0.7",
+)
+def get_highest_hkl(lattice, min_dspacing=0.5):
+    """Return the highest Miller indices hkl of the plane with a direct
+    space interplanar spacing (d-spacing) greater than but closest to
+    *min_dspacing*.
+
+    Parameters
+    ----------
+    lattice : diffpy.structure.Lattice
+        Crystal lattice.
+    min_dspacing : float, optional
+        Smallest interplanar spacing to consider. Default is 0.5 Å.
+
+    Returns
+    -------
+    highest_hkl : np.ndarray
+        Highest Miller indices.
+    """
+    highest_hkl = np.ones(3, dtype=int)
+    for i in range(3):
+        hkl = np.zeros(3)
+        d = min_dspacing + 1
+        while d > min_dspacing:
+            hkl[i] += 1
+            d = 1 / lattice.rnorm(hkl)
+        highest_hkl[i] = hkl[i]
+    return highest_hkl
+
+
+@deprecated(
+    since="0.6",
+    alternative="diffsims.crystallography.ReciprocalLatticeVector.from_highest_hkl",
+    removal="0.7",
+)
+def get_hkl(highest_hkl):
+    """Return a list of planes from a set of highest Miller indices.
+
+    Parameters
+    ----------
+    highest_hkl : orix.vector.Vector3d, np.ndarray, list, or tuple of int
+        Highest Miller indices to consider.
+
+    Returns
+    -------
+    hkl : np.ndarray
+        An array of Miller indices.
+    """
+    index_ranges = [np.arange(-i, i + 1) for i in highest_hkl]
+    return np.asarray(list(product(*index_ranges)))
+
+
+@deprecated(
+    since="0.6",
+    alternative="diffsims.crystallography.ReciprocalLatticeVector.symmetrise",
+    removal="0.7",
+)
+def get_equivalent_hkl(hkl, operations, unique=False, return_multiplicity=False):
+    """Return symmetrically equivalent Miller indices.
+
+    Parameters
+    ----------
+    hkl : orix.vector.Vector3d, np.ndarray, list or tuple of int
+        Miller indices.
+    operations : orix.quaternion.symmetry.Symmetry
+        Point group describing allowed symmetry operations.
+    unique : bool, optional
+        Whether to return only unique Miller indices. Default is False.
+    return_multiplicity : bool, optional
+        Whether to return the multiplicity of the input indices. Default
+        is False.
+
+    Returns
+    -------
+    new_hkl : orix.vector.Vector3d
+        The symmetrically equivalent Miller indices.
+    multiplicity : np.ndarray
+        Number of symmetrically equivalent indices. Only returned if
+        `return_multiplicity` is True.
+    """
+    new_hkl = operations.outer(Vector3d(hkl))
+    new_hkl = new_hkl.flatten().reshape(*new_hkl.shape[::-1])
+
+    multiplicity = None
+    if unique:
+        n_families = new_hkl.shape[0]
+        multiplicity = np.zeros(n_families, dtype=int)
+        temp_hkl = new_hkl[0].unique().data
+        multiplicity[0] = temp_hkl.shape[0]
+        if n_families > 1:
+            for i, hkl in enumerate(new_hkl[1:]):
+                temp_hkl2 = hkl.unique()
+                multiplicity[i + 1] = temp_hkl2.size
+                temp_hkl = np.append(temp_hkl, temp_hkl2.data, axis=0)
+        new_hkl = Vector3d(temp_hkl[: multiplicity.sum()])
+
+    # Remove 1-dimensions
+    new_hkl = new_hkl.squeeze()
+
+    if unique and return_multiplicity:
+        return new_hkl, multiplicity
+    else:
+        return new_hkl
diff --git a/diffsims/crystallography/reciprocal_lattice_point.py b/diffsims/crystallography/reciprocal_lattice_point.py
deleted file mode 100644
index c9a9f658..00000000
--- a/diffsims/crystallography/reciprocal_lattice_point.py
+++ /dev/null
@@ -1,484 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2017-2023 The diffsims developers
-#
-# This file is part of diffsims.
-#
-# diffsims is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# diffsims is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with diffsims.  If not, see <http://www.gnu.org/licenses/>.
-
-from collections import defaultdict
-from itertools import product
-from warnings import warn
-
-import numpy as np
-from orix.vector import Vector3d
-
-from diffsims.structure_factor.structure_factor import (
-    get_kinematical_structure_factor,
-    get_doyleturner_structure_factor,
-    get_refraction_corrected_wavelength,
-)
-
-_FLOAT_EPS = np.finfo(float).eps  # Used to round values below 1e-16 to zero
-
-
-class ReciprocalLatticePoint:
-    """*[Deprecated]* Reciprocal lattice point (or crystal plane,
-    reflector, g, etc.) with Miller indices, length of the reciprocal
-    lattice vectors and other relevant structure_factor parameters.
-
-    Notes
-    -----
-    Deprecated since version 0.5: Class ``ReciprocalLatticePoint`` is
-    deprecated and will be removed in version 0.6. Use
-    :class:`~diffsims.crystallography.ReciprocalLatticeVector` instead.
-    """
-
-    def __init__(self, phase, hkl):
-        """A container for Miller indices, structure factors and related
-        parameters for crystal planes (reciprocal lattice points,
-        reflectors, g, etc.).
-
-        Parameters
-        ----------
-        phase : orix.crystal_map.phase_list.Phase
-            A phase container with a crystal structure and a space and
-            point group describing the allowed symmetry operations.
-        hkl : orix.vector.Vector3d, np.ndarray, list, or tuple
-            Miller indices.
-
-        """
-
-        warn(
-            message=(
-                "Class `ReciprocalLatticePoint` is deprecated and will be removed in "
-                "version 0.6. Use `ReciprocalLatticeVector` instead."
-            ),
-            category=np.VisibleDeprecationWarning,
-        )
-
-        self.phase = phase
-        self._raise_if_no_point_group()
-        self._hkl = Vector3d(hkl)
-        self._structure_factor = [None] * self.size
-        self._theta = [None] * self.size
-
-    def __repr__(self):
-        return (
-            f"{self.__class__.__name__} {self.hkl.shape}\n"
-            f"Phase: {self.phase.name} ({self.phase.point_group.name})\n"
-            f"{np.array_str(self.hkl.data, precision=4, suppress_small=True)}"
-        )
-
-    def __getitem__(self, key):
-        new_rlp = self.__class__(self.phase, self.hkl[key])
-        if self.structure_factor[0] is None:
-            new_rlp._structure_factor = [None] * new_rlp.size
-        else:
-            new_rlp._structure_factor = self.structure_factor[key]
-        if self.theta[0] is None:
-            new_rlp._theta = [None] * new_rlp.size
-        else:
-            new_rlp._theta = self.theta[key]
-        return new_rlp
-
-    @property
-    def hkl(self):
-        """Return :class:`~orix.vector.Vector3d` of Miller indices."""
-        return Vector3d(self._hkl.data.astype(int))
-
-    @property
-    def h(self):
-        """Return :class:`np.ndarray` of Miller index h."""
-        return self.hkl.data[..., 0]
-
-    @property
-    def k(self):
-        """Return :class:`np.ndarray` of Miller index k."""
-        return self.hkl.data[..., 1]
-
-    @property
-    def l(self):
-        """Return :class:`np.ndarray` of Miller index l."""
-        return self.hkl.data[..., 2]
-
-    @property
-    def size(self):
-        """Return `int`."""
-        return self.hkl.size
-
-    @property
-    def shape(self):
-        """Return `tuple`."""
-        return self.hkl.data.shape
-
-    @property
-    def multiplicity(self):
-        """Return either `int` or :class:`np.ndarray` of `int`."""
-        return self.symmetrise(antipodal=True, return_multiplicity=True)[1]
-
-    @property
-    def gspacing(self):
-        """Return :class:`np.ndarray` of reciprocal lattice point
-        spacings.
-        """
-        return self.phase.structure.lattice.rnorm(self.hkl.data)
-
-    @property
-    def dspacing(self):
-        """Return :class:`np.ndarray` of direct lattice interplanar
-        spacings.
-        """
-        return 1 / self.gspacing
-
-    @property
-    def scattering_parameter(self):
-        """Return :class:`np.ndarray` of scattering parameters s."""
-        return 0.5 * self.gspacing
-
-    @property
-    def structure_factor(self):
-        """Return :class:`np.ndarray` of structure factors F or None."""
-        return self._structure_factor
-
-    @property
-    def allowed(self):
-        """Return whether planes diffract according to structure_factor
-        selection rules assuming kinematical scattering theory.
-        """
-        self._raise_if_no_space_group()
-
-        # Translational symmetry
-        centering = self.phase.space_group.short_name[0]
-
-        if centering == "P":  # Primitive
-            if self.phase.space_group.crystal_system == "HEXAGONAL":
-                # TODO: See rules in e.g.
-                #  https://mcl1.ncifcrf.gov/dauter_pubs/284.pdf, Table 4
-                #  http://xrayweb.chem.ou.edu/notes/symmetry.html, Systematic Absences
-                raise NotImplementedError
-            else:  # Any hkl
-                return np.ones(self.size, dtype=bool)
-        elif centering == "F":  # Face-centred, hkl all odd/even
-            selection = np.sum(np.mod(self.hkl.data, 2), axis=1)
-            return np.array([i not in [1, 2] for i in selection], dtype=bool)
-        elif centering == "I":  # Body-centred, h + k + l = 2n (even)
-            return np.mod(np.sum(self.hkl.data, axis=1), 2) == 0
-        elif centering == "A":  # Centred on A faces only
-            return np.mod(self.k + self.l, 2) == 0
-        elif centering == "B":  # Centred on B faces only
-            return np.mod(self.h + self.l, 2) == 0
-        elif centering == "C":  # Centred on C faces only
-            return np.mod(self.h + self.k, 2) == 0
-        elif centering in ["R", "H"]:  # Rhombohedral
-            return np.mod(-self.h + self.k + self.l, 3) == 0
-
-    @property
-    def theta(self):
-        """Return :class:`np.ndarray` of twice the Bragg angle."""
-        return self._theta
-
-    @classmethod
-    def from_min_dspacing(cls, phase, min_dspacing=0.5):
-        """Create a CrystalPlane object populated by unique Miller indices
-        with a direct space interplanar spacing greater than a lower
-        threshold.
-
-        Parameters
-        ----------
-        phase : orix.crystal_map.phase_list.Phase
-            A phase container with a crystal structure and a space and
-            point group describing the allowed symmetry operations.
-        min_dspacing : float, optional
-            Smallest interplanar spacing to consider. Default is 0.5 Å.
-        """
-        highest_hkl = get_highest_hkl(
-            lattice=phase.structure.lattice, min_dspacing=min_dspacing
-        )
-        hkl = get_hkl(highest_hkl=highest_hkl)
-        return cls(phase=phase, hkl=hkl).unique()
-
-    @classmethod
-    def from_highest_hkl(cls, phase, highest_hkl):
-        """Create a CrystalPlane object populated by unique Miller indices
-        below, but including, a set of higher indices.
-
-        Parameters
-        ----------
-        phase : orix.crystal_map.phase_list.Phase
-            A phase container with a crystal structure and a space and
-            point group describing the allowed symmetry operations.
-        highest_hkl : np.ndarray, list, or tuple of int
-            Highest Miller indices to consider (including).
-        """
-        hkl = get_hkl(highest_hkl=highest_hkl)
-        return cls(phase=phase, hkl=hkl).unique()
-
-    def unique(self, use_symmetry=True):
-        """Return planes with unique Miller indices.
-
-        Parameters
-        ----------
-        use_symmetry : bool, optional
-            Whether to use symmetry to remove the planes with indices
-            symmetrically equivalent to another set of indices.
-
-        Returns
-        -------
-        ReciprocalLatticePoint
-        """
-        if use_symmetry:
-            all_hkl = self.hkl.data
-            # Remove [0, 0, 0] points
-            all_hkl = all_hkl[~np.all(np.isclose(all_hkl, 0), axis=1)]
-
-            families = defaultdict(list)
-            for this_hkl in all_hkl.tolist():
-                for that_hkl in families.keys():
-                    if _is_equivalent(this_hkl, that_hkl):
-                        families[tuple(that_hkl)].append(this_hkl)
-                        break
-                else:
-                    families[tuple(this_hkl)].append(this_hkl)
-
-            n_families = len(families)
-            unique_hkl = np.zeros((n_families, 3))
-            for i, all_hkl_in_family in enumerate(families.values()):
-                unique_hkl[i] = sorted(all_hkl_in_family)[-1]
-        else:
-            unique_hkl = self.hkl.unique()
-        # TODO: Enable inheriting classes pass on their properties in this new object
-        return self.__class__(phase=self.phase, hkl=unique_hkl)
-
-    def symmetrise(
-        self,
-        antipodal=True,
-        unique=True,
-        return_multiplicity=False,
-    ):
-        """Return planes with symmetrically equivalent Miller indices.
-
-        Parameters
-        ----------
-        antipodal : bool, optional
-            Whether to include antipodal symmetry operations. Default is
-            True.
-        unique : bool, optional
-            Whether to return only distinct indices. Default is True.
-            If True, zero-entries, which are assumed to be degenerate, are
-            removed.
-        return_multiplicity : bool, optional
-            Whether to return the multiplicity of indices. This option is
-            only available if `unique` is True. Default is False.
-
-        Returns
-        -------
-        ReciprocalLatticePoint
-            Planes with Miller indices symmetrically equivalent to the
-            original planes.
-        multiplicity : np.ndarray
-            Multiplicity of the original Miller indices. Only returned if
-            `return_multiplicity` is True.
-
-        Notes
-        -----
-        Should be the same as EMsoft's CalcFamily in their symmetry.f90
-        module, although not entirely sure. Use with care.
-        """
-        # Get symmetry operations
-        pg = self.phase.point_group
-        operations = pg if antipodal else pg[~pg.improper]
-
-        out = get_equivalent_hkl(
-            hkl=self.hkl,
-            operations=operations,
-            unique=unique,
-            return_multiplicity=return_multiplicity,
-        )
-
-        # TODO: Enable inheriting classes pass on their properties in this new object
-        # Format output and return
-        if unique and return_multiplicity:
-            multiplicity = out[1]
-            if multiplicity.size == 1:
-                multiplicity = multiplicity[0]
-            return self.__class__(phase=self.phase, hkl=out[0]), multiplicity
-        else:
-            return self.__class__(phase=self.phase, hkl=out)
-
-    def calculate_structure_factor(self, method=None, voltage=None):
-        """Populate `self.structure_factor` with the structure factor F
-        for each plane.
-
-        Parameters
-        ----------
-        method : str, optional
-            Either "kinematical" for kinematical X-ray structure factors
-            or "doyleturner" for structure factors using Doyle-Turner
-            atomic scattering factors. If None (default), kinematical
-            structure factors are calculated.
-        voltage : float, optional
-            Beam energy in V used when `method=doyleturner`.
-        """
-        if method is None:
-            method = "kinematical"
-        methods = ["kinematical", "doyleturner"]
-        if method not in methods:
-            raise ValueError(f"method={method} must be among {methods}")
-        elif method == "doyleturner" and voltage is None:
-            raise ValueError(
-                "'voltage' parameter must be set when method='doyleturner'"
-            )
-
-        # TODO: Find a better way to call different methods in the loop
-        structure_factors = np.zeros(self.size)
-        for i, (hkl, s) in enumerate(zip(self.hkl.data, self.scattering_parameter)):
-            if method == "kinematical":
-                structure_factors[i] = get_kinematical_structure_factor(
-                    phase=self.phase,
-                    hkl=hkl,
-                    scattering_parameter=s,
-                )
-            else:
-                structure_factors[i] = get_doyleturner_structure_factor(
-                    phase=self.phase,
-                    hkl=hkl,
-                    scattering_parameter=s,
-                    voltage=voltage,
-                )
-        self._structure_factor = np.where(
-            structure_factors < _FLOAT_EPS, 0, structure_factors
-        )
-
-    def calculate_theta(self, voltage):
-        """Populate `self.theta` with the Bragg angle :math:`theta_B` for
-        each plane.
-
-        Parameters
-        ----------
-        voltage : float
-            Beam energy in V.
-        """
-        wavelength = get_refraction_corrected_wavelength(self.phase, voltage)
-        self._theta = np.arcsin(0.5 * wavelength * self.gspacing)
-
-    def _raise_if_no_point_group(self):
-        """Raise ValueError if the phase attribute has no point group
-        set.
-        """
-        if self.phase.point_group is None:
-            raise ValueError(f"The phase {self.phase} must have a point group set")
-
-    def _raise_if_no_space_group(self):
-        """Raise ValueError if the phase attribute has no space group
-        set.
-        """
-        if self.phase.space_group is None:
-            raise ValueError(f"The phase {self.phase} must have a space group set")
-
-
-def get_highest_hkl(lattice, min_dspacing=0.5):
-    """Return the highest Miller indices hkl of the plane with a direct
-    space interplanar spacing greater than but closest to a lower
-    threshold.
-
-    Parameters
-    ----------
-    lattice : diffpy.structure.Lattice
-        Crystal lattice.
-    min_dspacing : float, optional
-        Smallest interplanar spacing to consider. Default is 0.5 Å.
-
-    Returns
-    -------
-    highest_hkl : np.ndarray
-        Highest Miller indices.
-    """
-    highest_hkl = np.ones(3, dtype=int)
-    for i in range(3):
-        hkl = np.zeros(3)
-        d = min_dspacing + 1
-        while d > min_dspacing:
-            hkl[i] += 1
-            d = 1 / lattice.rnorm(hkl)
-        highest_hkl[i] = hkl[i]
-    return highest_hkl
-
-
-def get_hkl(highest_hkl):
-    """Return a list of planes from a set of highest Miller indices.
-
-    Parameters
-    ----------
-    highest_hkl : orix.vector.Vector3d, np.ndarray, list, or tuple of int
-        Highest Miller indices to consider.
-
-    Returns
-    -------
-    hkl : np.ndarray
-        An array of Miller indices.
-    """
-    index_ranges = [np.arange(-i, i + 1) for i in highest_hkl]
-    return np.asarray(list(product(*index_ranges)))
-
-
-def get_equivalent_hkl(hkl, operations, unique=False, return_multiplicity=False):
-    """Return symmetrically equivalent Miller indices.
-
-    Parameters
-    ----------
-    hkl : orix.vector.Vector3d, np.ndarray, list or tuple of int
-        Miller indices.
-    operations : orix.quaternion.symmetry.Symmetry
-        Point group describing allowed symmetry operations.
-    unique : bool, optional
-        Whether to return only unique Miller indices. Default is False.
-    return_multiplicity : bool, optional
-        Whether to return the multiplicity of the input indices. Default
-        is False.
-
-    Returns
-    -------
-    new_hkl : orix.vector.Vector3d
-        The symmetrically equivalent Miller indices.
-    multiplicity : np.ndarray
-        Number of symmetrically equivalent indices. Only returned if
-        `return_multiplicity` is True.
-    """
-    new_hkl = operations.outer(Vector3d(hkl))
-    new_hkl = new_hkl.flatten().reshape(*new_hkl.shape[::-1])
-
-    multiplicity = None
-    if unique:
-        n_families = new_hkl.shape[0]
-        multiplicity = np.zeros(n_families, dtype=int)
-        temp_hkl = new_hkl[0].unique().data
-        multiplicity[0] = temp_hkl.shape[0]
-        if n_families > 1:
-            for i, hkl in enumerate(new_hkl[1:]):
-                temp_hkl2 = hkl.unique()
-                multiplicity[i + 1] = temp_hkl2.size
-                temp_hkl = np.append(temp_hkl, temp_hkl2.data, axis=0)
-        new_hkl = Vector3d(temp_hkl[: multiplicity.sum()])
-
-    # Remove 1-dimensions
-    new_hkl = new_hkl.squeeze()
-
-    if unique and return_multiplicity:
-        return new_hkl, multiplicity
-    else:
-        return new_hkl
-
-
-def _is_equivalent(this_hkl: list, that_hkl: list) -> bool:
-    return sorted(np.abs(this_hkl)) == sorted(np.abs(that_hkl))
diff --git a/doc/user/installation.rst b/doc/user/installation.rst
index 2f170779..32caa725 100644
--- a/doc/user/installation.rst
+++ b/doc/user/installation.rst
@@ -4,7 +4,8 @@ Installation
 
 diffsims can be installed from `Anaconda <https://anaconda.org/conda-forge/diffsims>`_, the
 `Python Package Index <https://pypi.org/project/diffsims>`_ (``pip``), or from source,
-and supports Python >= 3.6.
+and supports Python >= 3.8.
+
 
 With pip
 ========
@@ -22,6 +23,7 @@ To install a specific version of diffsims (say version 0.5.1)::
 
     pip install diffsims==0.5.1
 
+
 With Anaconda
 =============
 
@@ -48,6 +50,7 @@ To install a specific version of diffsims (say version 0.5.1)::
 
     conda install diffsims==0.5.1 -c conda-forge
 
+
 .. _install-from-source:
 
 From source
diff --git a/setup.cfg b/setup.cfg
index b804aa93..304edb0a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -18,15 +18,15 @@ relative_files = True
 [coverage:report]
 precision = 2
 
+# https://github.com/vidartf/manifix
+# https://github.com/vidartf/globmatch
 [manifix]
 known_excludes =
+     doc/build/**
+     htmlcov/**
      .*
      .*/**
      **/*.nbi
      **/*.nbc
      **/*.pyc
-     .git/**
-     doc/build/**
-     htmlcov/**
      *.code-workspace
-     **/*.npy
\ No newline at end of file


Test patch:
diff --git a/diffsims/tests/crystallography/test_get_hkl.py b/diffsims/tests/crystallography/test_get_hkl.py
new file mode 100644
index 00000000..8021af28
--- /dev/null
+++ b/diffsims/tests/crystallography/test_get_hkl.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+# Copyright 2017-2023 The diffsims developers
+#
+# This file is part of diffsims.
+#
+# diffsims is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# diffsims is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with diffsims.  If not, see <http://www.gnu.org/licenses/>.
+
+import numpy as np
+from orix.crystal_map import Phase
+from orix.vector import Vector3d
+import pytest
+
+from diffsims.crystallography import (
+    get_equivalent_hkl,
+    get_highest_hkl,
+    get_hkl,
+    ReciprocalLatticeVector,
+)
+
+
+class TestGetHKL:
+    @pytest.mark.parametrize("highest_hkl, n", [([1, 2, 3], 105), ([1, 1, 1], 27)])
+    def test_get_hkl(self, highest_hkl, n):
+        highest_hkl = np.array(highest_hkl)
+        with pytest.warns(np.VisibleDeprecationWarning):
+            hkl = get_hkl(highest_hkl)
+        assert np.allclose(hkl.max(axis=0), highest_hkl)
+        assert np.allclose(hkl.min(axis=0), -highest_hkl)
+        assert hkl.shape[0] == n
+        assert np.allclose(abs(hkl).min(axis=0), [0, 0, 0])  # Contains zero-vector
+
+        # Slightly different implementation (from orix)
+        phase = Phase(point_group="1")
+        g = ReciprocalLatticeVector.from_highest_hkl(phase, highest_hkl)
+        assert np.allclose(g.data.max(axis=0), highest_hkl)
+        assert np.allclose(g.data.min(axis=0), -highest_hkl)
+        assert g.size == n - 1
+        assert np.allclose(abs(g.data).min(axis=0), [0, 0, 0])
+
+    @pytest.mark.parametrize(
+        "d, hkl", [(0.5, [6, 6, 21]), (1, [3, 3, 11]), (1.5, [2, 2, 7])]
+    )
+    def test_get_highest_hkl(self, silicon_carbide_phase, d, hkl):
+        with pytest.warns(np.VisibleDeprecationWarning):
+            hkl_out = get_highest_hkl(
+                silicon_carbide_phase.structure.lattice, min_dspacing=d
+            )
+        assert np.allclose(hkl_out, hkl)
+
+        # Slightly different implementation (from orix)
+        g = ReciprocalLatticeVector.from_min_dspacing(silicon_carbide_phase, d)
+        assert np.allclose(g.hkl.max(axis=0) + 1, hkl_out)
+
+    def test_get_equivalent_hkl(self):
+        phase_225 = Phase(space_group=225)
+        with pytest.warns(np.VisibleDeprecationWarning):
+            hkl1 = get_equivalent_hkl(
+                [1, 1, 1], operations=phase_225.point_group, unique=True
+            )
+        # fmt: off
+        assert np.allclose(
+            hkl1.data,
+            [
+                [ 1,  1,  1],
+                [-1,  1,  1],
+                [-1, -1,  1],
+                [ 1, -1,  1],
+                [ 1, -1, -1],
+                [ 1,  1, -1],
+                [-1,  1, -1],
+                [-1, -1, -1],
+            ]
+        )
+        # fmt: on
+        g1 = ReciprocalLatticeVector(phase_225, hkl=[1, 1, 1])
+        assert np.allclose(g1.symmetrise().hkl, hkl1.data)
+
+        with pytest.warns(np.VisibleDeprecationWarning):
+            hkl2 = get_equivalent_hkl([1, 1, 1], operations=phase_225.point_group)
+        assert hkl2.shape[0] == g1.symmetrise().size * 6 == 48
+
+        phase_186 = Phase(space_group=186)
+        with pytest.warns(np.VisibleDeprecationWarning):
+            hkl3, mult3 = get_equivalent_hkl(
+                [2, 2, 0],
+                operations=phase_186.point_group,
+                return_multiplicity=True,
+                unique=True,
+            )
+        g3 = ReciprocalLatticeVector(phase_186, hkl=[2, 2, 0])
+        assert mult3 == g3.symmetrise().size == 12
+        assert np.allclose(hkl3.data[:2], [[2, 2, 0], [-2.7321, 0.7321, 0]], atol=1e-4)
+
+        with pytest.warns(np.VisibleDeprecationWarning):
+            hkl4, mult4 = get_equivalent_hkl(
+                Vector3d([[2, 2, 0], [4, 0, 0]]),
+                phase_186.point_group,
+                unique=True,
+                return_multiplicity=True,
+            )
+        g4 = ReciprocalLatticeVector(phase_186, hkl=[[2, 2, 0], [4, 0, 0]])
+        g4_sym, mult4_2 = g4.symmetrise(return_multiplicity=True)
+        assert np.allclose(mult4, [12, 6])
+        assert np.allclose(mult4_2, [12, 6])
+        assert hkl4.shape[0] == g4_sym.size == 18
diff --git a/diffsims/tests/crystallography/test_reciprocal_lattice_point.py b/diffsims/tests/crystallography/test_reciprocal_lattice_point.py
deleted file mode 100644
index d8c82fdb..00000000
--- a/diffsims/tests/crystallography/test_reciprocal_lattice_point.py
+++ /dev/null
@@ -1,329 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2017-2023 The diffsims developers
-#
-# This file is part of diffsims.
-#
-# diffsims is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# diffsims is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with diffsims.  If not, see <http://www.gnu.org/licenses/>.
-
-import numpy as np
-from orix.crystal_map import Phase
-from orix.vector import Vector3d
-import pytest
-
-from diffsims.crystallography import ReciprocalLatticePoint
-
-
-class TestReciprocalLatticePoint:
-    @pytest.mark.parametrize(
-        "hkl", [[[1, 1, 1], [2, 0, 0]], np.array([[1, 1, 1], [2, 0, 0]])]
-    )
-    def test_init_rlp(self, nickel_phase, hkl):
-        with pytest.warns(np.VisibleDeprecationWarning):
-            rlp = ReciprocalLatticePoint(phase=nickel_phase, hkl=hkl)
-        assert rlp.phase.name == nickel_phase.name
-        assert isinstance(rlp.hkl, Vector3d)
-        assert rlp.structure_factor[0] is None
-        assert rlp.theta[0] is None
-        assert rlp.size == 2
-        assert rlp.shape == (2, 3)
-        assert rlp.hkl[0].shape == (1,)
-        assert rlp.hkl.data[0].shape == (3,)
-        assert np.issubdtype(rlp.hkl.data.dtype, int)
-
-    @pytest.mark.parametrize("min_dspacing, desired_size", [(2, 9), (1, 19), (0.5, 83)])
-    def test_init_from_min_dspacing(self, ferrite_phase, min_dspacing, desired_size):
-        with pytest.warns(np.VisibleDeprecationWarning):
-            assert (
-                ReciprocalLatticePoint.from_min_dspacing(
-                    phase=ferrite_phase, min_dspacing=min_dspacing
-                ).size
-                == desired_size
-            )
-
-    @pytest.mark.parametrize(
-        "highest_hkl, desired_highest_hkl, desired_lowest_hkl, desired_size",
-        [
-            ([3, 3, 3], [3, 3, 3], [1, 0, 0], 19),
-            ([3, 4, 0], [3, 4, 0], [0, 4, 0], 13),
-            ([4, 3, 0], [4, 3, 0], [1, 0, 0], 13),
-        ],
-    )
-    def test_init_from_highest_hkl(
-        self,
-        silicon_carbide_phase,
-        highest_hkl,
-        desired_highest_hkl,
-        desired_lowest_hkl,
-        desired_size,
-    ):
-        with pytest.warns(np.VisibleDeprecationWarning):
-            rlp = ReciprocalLatticePoint.from_highest_hkl(
-                phase=silicon_carbide_phase, highest_hkl=highest_hkl
-            )
-        assert np.allclose(rlp[0].hkl.data, desired_highest_hkl)
-        assert np.allclose(rlp[-1].hkl.data, desired_lowest_hkl)
-        assert rlp.size == desired_size
-
-    def test_repr(self, ferrite_phase):
-        with pytest.warns(np.VisibleDeprecationWarning):
-            rlp = ReciprocalLatticePoint.from_min_dspacing(
-                ferrite_phase, min_dspacing=2
-            )
-        assert repr(rlp) == (
-            f"ReciprocalLatticePoint (9,)\n"
-            f"Phase: ferrite (m-3m)\n"
-            "[[2 2 2]\n [2 2 1]\n [2 2 0]\n [2 1 1]\n [2 1 0]\n [2 0 0]\n [1 1 1]\n"
-            " [1 1 0]\n [1 0 0]]"
-        )
-
-    def test_get_item(self, ferrite_phase):
-        with pytest.warns(np.VisibleDeprecationWarning):
-            rlp = ReciprocalLatticePoint.from_min_dspacing(
-                phase=ferrite_phase, min_dspacing=1.5
-            )
-        rlp.calculate_structure_factor()
-        rlp.calculate_theta(voltage=20e3)
-
-        assert rlp[0].size == 1
-        assert rlp[:2].size == 2
-        assert np.allclose(rlp[5:7].hkl.data, rlp.hkl[5:7].data)
-
-        assert np.allclose(rlp[10:13].structure_factor, rlp.structure_factor[10:13])
-        assert np.allclose(rlp[20:23].theta, rlp.theta[20:23])
-
-        assert rlp.phase.space_group.number == rlp[0].phase.space_group.number
-        assert rlp.phase.point_group.name == rlp[10:15].phase.point_group.name
-        assert np.allclose(
-            rlp.phase.structure.lattice.abcABG(),
-            rlp[20:23].phase.structure.lattice.abcABG(),
-        )
-
-    def test_get_hkl(self, silicon_carbide_phase):
-        with pytest.warns(np.VisibleDeprecationWarning):
-            rlp = ReciprocalLatticePoint.from_min_dspacing(
-                silicon_carbide_phase, min_dspacing=3
-            )
-        assert np.allclose(rlp.h, [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0])
-        assert np.allclose(rlp.k, [1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0])
-        assert np.allclose(rlp.l, [4, 3, 2, 1, 0, 4, 3, 2, 0, 4, 3, 2])
-
-    def test_multiplicity(self, nickel_phase, silicon_carbide_phase):
-        with pytest.warns(np.VisibleDeprecationWarning):
-            assert np.allclose(
-                ReciprocalLatticePoint.from_min_dspacing(
-                    phase=nickel_phase, min_dspacing=1
-                ).multiplicity,
-                # fmt: off
-                np.array([
-                    8, 24, 24, 24, 12, 24, 48, 48, 24, 24, 48, 24, 24, 24, 6, 8, 24,
-                    24, 12, 24, 48, 24, 24, 24,  6,  8, 24, 12, 24, 24,  6,  8, 12, 6
-                ])
-                # fmt: on
-            )
-        with pytest.warns(np.VisibleDeprecationWarning):
-            assert np.allclose(
-                ReciprocalLatticePoint.from_min_dspacing(
-                    phase=silicon_carbide_phase, min_dspacing=1
-                ).multiplicity,
-                # fmt: off
-                np.array([
-                    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
-                    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 6,
-                    6, 6, 6, 6, 6, 6, 6, 6, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
-                    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6,
-                    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6,
-                    1, 1, 1, 1, 1, 1, 1, 1
-                ])
-                # fmt: on
-            )
-
-    def test_gspacing_dspacing_scattering_parameter(self, ferrite_phase):
-        with pytest.warns(np.VisibleDeprecationWarning):
-            rlp = ReciprocalLatticePoint.from_min_dspacing(
-                phase=ferrite_phase, min_dspacing=2
-            )
-        # fmt: off
-        assert np.allclose(
-            rlp.gspacing,
-            np.array([
-                1.2084778, 1.04657248, 0.98671799, 0.85452285, 0.78006907, 0.69771498,
-                0.604238, 0.493359, 0.34885749
-            ])
-        )
-        assert np.allclose(
-            rlp.dspacing,
-            np.array([
-                0.82748727, 0.9555, 1.01346079, 1.17024372, 1.28193777, 1.43325,
-                1.65497455, 2.02692159, 2.8665
-            ])
-        )
-        assert np.allclose(
-            rlp.scattering_parameter,
-            np.array([
-                0.6042389, 0.52328624, 0.493359, 0.42726142, 0.39003453, 0.34885749,
-                0.30211945, 0.2466795, 0.17442875
-            ])
-        )
-        # fmt: on
-
-    @pytest.mark.parametrize(
-        "space_group, hkl, centering, desired_allowed",
-        [
-            (224, [[1, 1, -1], [-2, 2, 2], [3, 1, 0]], "P", [True, True, True]),
-            (230, [[1, 1, -1], [-2, 2, 2], [3, 1, 0]], "I", [False, True, True]),
-            (225, [[1, 1, 1], [2, 2, 1], [-3, 3, 3]], "F", [True, False, True]),
-            (167, [[1, 2, 3], [2, 2, 3], [-3, 2, 4]], "H", [False, True, True]),  # R
-            (68, [[1, 2, 3], [2, 2, 3], [-2, 2, 4]], "C", [False, True, True]),
-            (41, [[1, 2, 2], [1, 1, -1], [1, 1, 2]], "A", [True, True, False]),
-        ],
-    )
-    def test_allowed(self, space_group, hkl, centering, desired_allowed):
-        with pytest.warns(np.VisibleDeprecationWarning):
-            rlp = ReciprocalLatticePoint(phase=Phase(space_group=space_group), hkl=hkl)
-        assert rlp.phase.space_group.short_name[0] == centering
-        assert np.allclose(rlp.allowed, desired_allowed)
-
-    def test_allowed_b_centering(self):
-        """B centering will never fire since no diffpy.structure space group has
-        'B' first in the name.
-        """
-        phase = Phase(space_group=15)
-        phase.space_group.short_name = "B"
-        with pytest.warns(np.VisibleDeprecationWarning):
-            rlp = ReciprocalLatticePoint(
-                phase=phase, hkl=[[1, 2, 2], [1, 1, -1], [1, 1, 2]]
-            )
-        assert np.allclose(rlp.allowed, [False, True, False])
-
-    def test_allowed_raises(self, silicon_carbide_phase):
-        with pytest.raises(NotImplementedError):
-            with pytest.warns(np.VisibleDeprecationWarning):
-                _ = ReciprocalLatticePoint.from_min_dspacing(
-                    phase=silicon_carbide_phase, min_dspacing=1
-                ).allowed
-
-    def test_unique(self, ferrite_phase):
-        hkl = [[-1, -1, -1], [1, 1, 1], [1, 0, 0], [0, 0, 1]]
-        with pytest.warns(np.VisibleDeprecationWarning):
-            rlp = ReciprocalLatticePoint(phase=ferrite_phase, hkl=hkl)
-        assert isinstance(rlp.unique(), ReciprocalLatticePoint)
-        assert np.allclose(rlp.unique(use_symmetry=False).hkl.data, hkl)
-        assert np.allclose(rlp.unique().hkl.data, [[1, 1, 1], [1, 0, 0]])
-
-    def test_symmetrise(self):
-        with pytest.warns(np.VisibleDeprecationWarning):
-            assert np.allclose(
-                ReciprocalLatticePoint(phase=Phase(space_group=225), hkl=[1, 1, 1])
-                .symmetrise()
-                .hkl.data,
-                np.array(
-                    [
-                        [1, 1, 1],
-                        [-1, 1, 1],
-                        [-1, -1, 1],
-                        [1, -1, 1],
-                        [1, -1, -1],
-                        [1, 1, -1],
-                        [-1, 1, -1],
-                        [-1, -1, -1],
-                    ]
-                ),
-            )
-
-        with pytest.warns(np.VisibleDeprecationWarning):
-            rlp2, multiplicity = ReciprocalLatticePoint(
-                phase=Phase(space_group=186), hkl=[2, 2, 0]
-            ).symmetrise(return_multiplicity=True)
-        assert multiplicity == 12
-        assert np.allclose(
-            rlp2.hkl.data,
-            [
-                [2, 2, 0],
-                [-2, 0, 0],
-                [0, -2, 0],
-                [-2, -2, 0],
-                [2, 0, 0],
-                [0, 2, 0],
-                [-2, 2, 0],
-                [0, -2, 0],
-                [2, 0, 0],
-                [2, -2, 0],
-                [0, 2, 0],
-                [-2, 0, 0],
-            ],
-        )
-
-        with pytest.warns(np.VisibleDeprecationWarning):
-            rlp3 = ReciprocalLatticePoint(
-                phase=Phase(space_group=186), hkl=[2, 2, 0]
-            ).symmetrise(antipodal=False)
-        assert np.allclose(
-            rlp3.hkl.data,
-            [[2, 2, 0], [-2, 0, 0], [0, -2, 0], [-2, -2, 0], [2, 0, 0], [0, 2, 0]],
-        )
-
-    @pytest.mark.parametrize(
-        "method, voltage, hkl, desired_factor",
-        [
-            ("kinematical", None, [1, 1, 0], 35.783295),
-            (None, None, [1, 1, 0], 35.783295),
-            ("doyleturner", 20e3, [[2, 0, 0], [1, 1, 0]], [5.581302, 8.096651]),
-        ],
-    )
-    def test_calculate_structure_factor(
-        self, ferrite_phase, method, voltage, hkl, desired_factor
-    ):
-        with pytest.warns(np.VisibleDeprecationWarning):
-            rlp = ReciprocalLatticePoint(phase=ferrite_phase, hkl=hkl)
-        rlp.calculate_structure_factor(method=method, voltage=voltage)
-        assert np.allclose(rlp.structure_factor, desired_factor)
-
-    def test_calculate_structure_factor_raises(self, ferrite_phase):
-        with pytest.warns(np.VisibleDeprecationWarning):
-            rlp = ReciprocalLatticePoint(phase=ferrite_phase, hkl=[1, 0, 0])
-        with pytest.raises(ValueError, match="method=man must be among"):
-            rlp.calculate_structure_factor(method="man")
-        with pytest.raises(ValueError, match="'voltage' parameter must be set when"):
-            rlp.calculate_structure_factor(method="doyleturner")
-
-    @pytest.mark.parametrize(
-        "voltage, hkl, desired_theta",
-        [(20e3, [1, 1, 1], 0.00259284), (200e3, [2, 0, 0], 0.00087484)],
-    )
-    def test_calculate_theta(self, ferrite_phase, voltage, hkl, desired_theta):
-        with pytest.warns(np.VisibleDeprecationWarning):
-            rlp = ReciprocalLatticePoint(phase=ferrite_phase, hkl=hkl)
-        rlp.calculate_theta(voltage=voltage)
-        assert np.allclose(rlp.theta, desired_theta)
-
-    def test_one_point(self, ferrite_phase):
-        with pytest.warns(np.VisibleDeprecationWarning):
-            rlp = ReciprocalLatticePoint(phase=ferrite_phase, hkl=[1, 1, 0])
-
-        assert rlp.size == 1
-        assert np.allclose(rlp.allowed, True)
-
-    def test_init_without_point_group_raises(self):
-        phase = Phase()
-        with pytest.raises(ValueError, match=f"The phase {phase} must have a"):
-            with pytest.warns(np.VisibleDeprecationWarning):
-                _ = ReciprocalLatticePoint(phase=phase, hkl=[1, 1, 1])
-
-    def test_get_allowed_without_space_group_raises(self):
-        phase = Phase(point_group="432")
-        with pytest.warns(np.VisibleDeprecationWarning):
-            rlp = ReciprocalLatticePoint(phase=phase, hkl=[1, 1, 1])
-
-        with pytest.raises(ValueError, match=f"The phase {phase} must have a"):
-            _ = rlp.allowed