diff --git a/hkl/configuration.py b/hkl/configuration.py index 5e4d3e2f..163553b2 100644 --- a/hkl/configuration.py +++ b/hkl/configuration.py @@ -53,6 +53,8 @@ DEFAULT_WAVELENGTH = 1.54 # angstrom EXPORT_FORMATS = "dict json yaml".split() +EQUAL_TOLERANCE = 1.0e-7 + # standard value checks, raise exception(s) when appropriate def _check_key(key, biblio, intro): @@ -254,12 +256,12 @@ def validate(self, dc_obj): def write(self, diffractometer): """Write sample details to diffractometer.""" - s = diffractometer.calc._samples.get(self.name) + sample = diffractometer.calc._samples.get(self.name) lattice_parameters = list(self.lattice.values) - if s is None: - s = diffractometer.calc.new_sample(self.name, lattice=lattice_parameters) + if sample is None: + sample = diffractometer.calc.new_sample(self.name, lattice=lattice_parameters) else: - s.lattice = lattice_parameters + sample.lattice = lattice_parameters reflection_list = [] for reflection in self.reflections: @@ -275,18 +277,18 @@ def write(self, diffractometer): w1 = rdict["wavelength"] try: diffractometer.calc.wavelength = w1 - r = diffractometer.calc.sample.add_reflection(*args) + r = sample.add_reflection(*args) + if rdict["orientation_reflection"]: + reflection_list.append(r) except RuntimeError as exc: raise RuntimeError(f"could not add reflection({args}, wavelength={w1})") from exc finally: diffractometer.calc.wavelength = w0 - - if rdict["orientation_reflection"]: - reflection_list.append(r) + # Remaining code will not be executed if exception was raised. if len(reflection_list) > 1: r1, r2 = reflection_list[0], reflection_list[1] - diffractometer.calc.sample.compute_UB(r1, r2) + sample.compute_UB(r1, r2) @dataclass @@ -543,6 +545,11 @@ def restore(self, data, clear=True, restore_constraints=True): If ``True`` (default), remove any previous configuration of the diffractometer and reset it to default values before restoring the configuration. + + If ``False``, sample reflections will be append with all reflections + included in the configuration data for that sample. Existing + reflections will not be changed. The user may need to edit the + list of reflections after ``restore(clear=False)``. restore_constraints *bool*: If ``True`` (default), restore any constraints provided. diff --git a/hkl/sample.py b/hkl/sample.py index 1c52a535..e67a0f69 100644 --- a/hkl/sample.py +++ b/hkl/sample.py @@ -1,3 +1,5 @@ +"""Sample on the diffractometer.""" + import logging import numpy as np @@ -88,21 +90,19 @@ class HklSample(object): .. autosummary:: + ~name ~add_reflection - ~affine + ~remove_reflection + ~swap_orientation_reflections ~clear_reflections + ~affine ~compute_UB - ~hkl_calc - ~hkl_sample ~lattice - ~name ~reciprocal ~reflection_measured_angles ~reflection_theoretical_angles ~reflections ~reflections_details - ~remove_reflection - ~swap_orientation_reflections ~U ~UB ~ux @@ -113,6 +113,8 @@ class HklSample(object): .. autosummary:: + ~hkl_calc + ~hkl_sample ~__repr__ ~__str__ ~_create_reflection diff --git a/hkl/tests/__init__.py b/hkl/tests/__init__.py index bff05675..e69de29b 100644 --- a/hkl/tests/__init__.py +++ b/hkl/tests/__init__.py @@ -1,32 +0,0 @@ -import logging - -import numpy - -from ..util import new_lattice - -logger = logging.getLogger("ophyd_session_test") - -TARDIS_TEST_MODE = "lifting_detector_mu" -TWO_PI = 2 * numpy.pi - - -def new_sample(diffractometer, name, lattice): - diffractometer.calc.new_sample(name, lattice=lattice) - - -def sample_kryptonite(diffractometer): - triclinic = new_lattice(4, 5, 6, 75, 85, 95) - new_sample(diffractometer, "kryptonite", lattice=triclinic) - - -def sample_silicon(diffractometer): - from .. import SI_LATTICE_PARAMETER - - cubic = new_lattice(SI_LATTICE_PARAMETER) - new_sample(diffractometer, "silicon", lattice=cubic) - - -def sample_vibranium(diffractometer): - a0 = TWO_PI - cubic = new_lattice(a0) - new_sample(diffractometer, "vibranium", lattice=cubic) diff --git a/hkl/tests/test_configuration.py b/hkl/tests/test_configuration.py index 328d4281..e04f8f0f 100644 --- a/hkl/tests/test_configuration.py +++ b/hkl/tests/test_configuration.py @@ -15,7 +15,7 @@ from ..configuration import DCSample from ..util import Constraint from ..util import new_lattice -from . import TWO_PI +from .tools import TWO_PI TEST_CONFIG_FILE = "data/e4c-config.json" diff --git a/hkl/tests/test_restore_reflections.py b/hkl/tests/test_restore_reflections.py new file mode 100644 index 00000000..7d4bd103 --- /dev/null +++ b/hkl/tests/test_restore_reflections.py @@ -0,0 +1,61 @@ +""" +Test that reflections from one sample are not restored to other samples. +""" + +from .. import DiffractometerConfiguration +from .tools import sample_kryptonite +from .tools import sample_silicon +from .tools import sample_vibranium + + +def test_issue289(e4cv): + assert e4cv is not None + + # setup four samples with reflections, all of them different + main = e4cv.calc.sample # the default sample + assert len(main.reflections) == 0 + m_100 = main.add_reflection(1, 0, 0, (-45, 0, 0, 0)) + m_010 = main.add_reflection(0, 1, 0, (45, 0, 0, 0)) + main.compute_UB(m_100, m_010) + + kryptonite = sample_kryptonite(e4cv) + assert len(kryptonite.reflections) == 0 + k_200 = kryptonite.add_reflection(2, 0, 0, (30, 0, 10, 60)) + k_020 = kryptonite.add_reflection(0, 2, 0, (30, 90, 10, 60)) + kryptonite.compute_UB(k_200, k_020) + + vibranium = sample_vibranium(e4cv) + assert len(vibranium.reflections) == 0 + v_003 = vibranium.add_reflection(0, 0, 3, (20.33, 4.33, 8.33, 40.33)) + v_033 = vibranium.add_reflection(0, 3, 3, (20.33, -94.33, 8.33, 40.33)) + vibranium.compute_UB(v_003, v_033) + + silicon = sample_silicon(e4cv) + assert len(silicon.reflections) == 0 + s_440 = silicon.add_reflection(4, 4, 0, (34, 44, 54, 64)) + s_444 = silicon.add_reflection(4, 4, 4, (34, 134, 54, 64)) + silicon.compute_UB(s_440, s_444) + + n_saved_reflections = 2 + assert len(main.reflections) == n_saved_reflections + assert len(kryptonite.reflections) == n_saved_reflections + assert len(silicon.reflections) == n_saved_reflections + assert len(vibranium.reflections) == n_saved_reflections + # same test, using diffractometer sample dictionary now. + for sample in e4cv.calc._samples.values(): + assert len(sample.reflections) == n_saved_reflections, f"{sample.name=}" + assert len(e4cv.calc._samples) == 4 + + agent = DiffractometerConfiguration(e4cv) + assert agent is not None + config = agent.export() + assert isinstance(config, str) + + # Restore the configuration without clearing the diffractometer first. + # This should not change the number of samples or the number of + # reflections for any of the samples. + agent.restore(config, clear=False) + + assert len(e4cv.calc._samples) == 4 + for sample in e4cv.calc._samples.values(): + assert len(sample.reflections) == 2 * n_saved_reflections diff --git a/hkl/tests/test_tardis.py b/hkl/tests/test_tardis.py index 6cd826e4..85cdf625 100644 --- a/hkl/tests/test_tardis.py +++ b/hkl/tests/test_tardis.py @@ -9,7 +9,7 @@ @pytest.fixture(scope="function") def kcf_sample(tardis): - from . import new_sample + from .tools import new_sample # note: orientation matrix (below) was pre-computed with this wavelength # wavelength units must match lattice unit cell length units @@ -90,7 +90,7 @@ def test_params(tardis): """ Make sure the parameters are set correctly """ - from . import TARDIS_TEST_MODE + from .tools import TARDIS_TEST_MODE calc = tardis.calc assert calc.pseudo_axis_names == "h k l".split() @@ -319,7 +319,7 @@ def test_sample1(sample1, tardis): def test_sample1_calc_only(): """Comparisons start with the Tardis' calc support (no Diffractometer object).""" - from . import TARDIS_TEST_MODE + from .tools import TARDIS_TEST_MODE tardis_calc = hkl_calc.CalcE6C() diff --git a/hkl/tests/tools.py b/hkl/tests/tools.py new file mode 100644 index 00000000..7e0f6e24 --- /dev/null +++ b/hkl/tests/tools.py @@ -0,0 +1,38 @@ +""" +Common code, setups, constants, ... for these tests. + +Avoids direct imports of __init__.py. +""" + +import logging + +import numpy + +from ..util import new_lattice + +logger = logging.getLogger("ophyd_session_test") + +TARDIS_TEST_MODE = "lifting_detector_mu" +TWO_PI = 2 * numpy.pi + + +def new_sample(diffractometer, name, lattice): + return diffractometer.calc.new_sample(name, lattice=lattice) + + +def sample_kryptonite(diffractometer): + triclinic = new_lattice(4, 5, 6, 75, 85, 95) + return new_sample(diffractometer, "kryptonite", lattice=triclinic) + + +def sample_silicon(diffractometer): + from .. import SI_LATTICE_PARAMETER + + cubic = new_lattice(SI_LATTICE_PARAMETER) + return new_sample(diffractometer, "silicon", lattice=cubic) + + +def sample_vibranium(diffractometer): + a0 = TWO_PI + cubic = new_lattice(a0) + return new_sample(diffractometer, "vibranium", lattice=cubic)