diff --git a/.vscode/settings.json b/.vscode/settings.json index 9b38853..d7338ad 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,5 @@ "tests" ], "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, } \ No newline at end of file diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index bb624f7..e9bb26a 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -12,7 +12,7 @@ {% set build_number = 0 %} package: - name: mypackagename + name: tavi version: {{ version_number }} source: diff --git a/pyproject.toml b/pyproject.toml index cebdbec..10d9b29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,14 +2,14 @@ dependencies = [ # list all runtime dependencies here ] -description = "Example Python repo for neutrons" +description = "Triple-Axis data Visualization" dynamic = ["version"] license = {text = "LGPL-3.0"} name = "examplepyapp" requires-python = ">=3.10" [project.urls] -homepage = "https://github.com/neutrons/python_project_template/" # if no homepage, use repo url +homepage = "https://github.com/neutrons/TAVI" [build-system] build-backend = "setuptools.build_meta" @@ -36,7 +36,7 @@ distance = "{next_version}.dev{distance}" distance-dirty = "{next_version}.dev{distance}+d{build_date:%Y%m%d%H%M}" [tool.versioningit.write] -file = "src/packagenamepy/_version.py" +file = "src/tavi/_version.py" [tool.setuptools.packages.find] exclude = ["tests*", "scripts*", "docs*", "notebooks*"] @@ -45,11 +45,11 @@ where = ["src"] [tool.setuptools.package-data] "*" = ["*.yml", "*.yaml", "*.ini"] -[project.scripts] -packagename-cli = "packagenamepy.packagename:main" +# [project.scripts] +# packagename-cli = "packagenamepy.packagename:main" -[project.gui-scripts] -packagenamepy = "packagenamepy.packagename:gui" +# [project.gui-scripts] +# packagenamepy = "packagenamepy.packagename:gui" [tool.pytest.ini_options] markers = [ diff --git a/src/tavi/instrument/tas.py b/src/tavi/instrument/tas.py index 0397bb7..d469e2d 100644 --- a/src/tavi/instrument/tas.py +++ b/src/tavi/instrument/tas.py @@ -1,6 +1,7 @@ import json import numpy as np + from tavi.instrument.tas_cmponents import * from tavi.sample.powder import Powder from tavi.sample.sample import Sample @@ -74,11 +75,13 @@ def load_sample_from_json(self, path_to_json): with open(path_to_json, "r", encoding="utf-8") as file: sample_params = json.load(file) - if sample_params["type"] == "xtal": - sample = Xtal.from_json(sample_params) - elif sample_params["type"] == "powder": - sample = Powder.from_json(sample_params) - else: + try: # is type xtal ot powder? + sample_type = sample_params["type"] + if sample_type == "xtal": + sample = Xtal.from_json(sample_params) + elif sample_type == "powder": + sample = Powder.from_json(sample_params) + except KeyError: # sample type is not given sample = Sample.from_json(sample_params) self.load_sample(sample) diff --git a/src/tavi/sample/sample.py b/src/tavi/sample/sample.py index c381451..2ca2644 100755 --- a/src/tavi/sample/sample.py +++ b/src/tavi/sample/sample.py @@ -1,8 +1,7 @@ +# -*- coding: utf-8 -*- import numpy as np -from tavi.utilities import * - -np.set_printoptions(floatmode="fixed", precision=4) +# from tavi.utilities import * class Sample(object): @@ -19,7 +18,6 @@ class Sample(object): a, b, c lattice constants in Angstrom alpha, beta, gamma angles in degrees a_vec, b_vec, c_vec real sapce lattice vector - _v_abg V_alpha_beta_gamma = unit_cell_volume/(abc) a_star, b_star, c_star lattice constants in inverse Angstrom alpha_star, beta_star, gamma_star reciprocal angles in degrees a_star_vec, b_star_vec, c_star_vec reciprocal lattice vector @@ -42,16 +40,20 @@ class Sample(object): """ - def __init__(self, lattice_params=(1, 1, 1, 90, 90, 90)): + def __init__( + self, + lattice_params: tuple[float] = (1, 1, 1, 90, 90, 90), + ) -> None: + assert len(lattice_params) == 6, "Incomplete lattice parameters." + for length in lattice_params[:3]: + assert length > 0, "Lattice parameters smaller than zero." + for angle in lattice_params[3:]: + assert 0.0 < angle < 180.0, "Lattice angles out of range." + + self.update_lattice_parameters(lattice_params) # parameters for resolution calculation - self.shape = "cuboid" - self.width = 1.0 # * cm2angstrom - self.height = 1.0 # * cm2angstrom - self.depth = 1.0 # * cm2angstrom - self.mosaic = 30 # * min2rad # horizontal mosaic - self.mosaic_v = 30 # * min2rad # vertical mosaic - - self.update_lattice(lattice_params) + self.set_mosaic() # with defalt values + self.set_shape() # with defalt values @classmethod def from_json(cls, sample_params): @@ -64,24 +66,34 @@ def from_json(cls, sample_params): sample_params["beta"], sample_params["gamma"], ) - - sample = cls(lattice_params=lattice_params) - - param_dict = ("shape", "width", "height", "depth", "mosaic", "mosaic_v") - - for key, val in sample_params.items(): - match key: - case "height" | "width" | "depth": - setattr(sample, key, val * cm2angstrom) - # case "mosaic" | "mosaic_v": - # setattr(sample, key, val * min2rad) - case _: - if key in param_dict: - setattr(sample, key, val) - sample.update_lattice(lattice_params) - return sample - - def update_lattice(self, lattice_params=(1, 1, 1, 90, 90, 90)): + return cls(lattice_params=lattice_params) + + def set_shape( + self, + shape: str = "cuboid", + width: float = 1.0, # in cm + height: float = 1.0, # in cm + depth: float = 1.0, # in cm + ) -> None: + """set sample shape""" + self.shape = shape + self.width = width + self.height = height + self.depth = depth + + def set_mosaic( + self, + mosaic: float = 30, # horizontal mosaic + mosaic_v: float = 30, # vertical mosaic + ) -> None: + """Set horizontal and vertical mosaic in units of minitues of arc""" + self.mosaic_h = mosaic # * min2rad + self.mosaic_v = mosaic_v # * min2rad + + def update_lattice_parameters( + self, + lattice_params: tuple[float] = (1, 1, 1, 90, 90, 90), + ): """update real and reciprocal space lattice parameters and vectors""" a, b, c, alpha, beta, gamma = lattice_params @@ -92,8 +104,11 @@ def update_lattice(self, lattice_params=(1, 1, 1, 90, 90, 90)): self.beta = beta self.gamma = gamma - self._v_abg = Sample.v_alpha_beta_gamma_calc(alpha, beta, gamma) - self.a_vec, self.b_vec, self.c_vec = self.real_vec_cart() + ( + self.a_vec, + self.b_vec, + self.c_vec, + ) = self._real_space_vectors() ( self.a_star, self.b_star, @@ -103,30 +118,34 @@ def update_lattice(self, lattice_params=(1, 1, 1, 90, 90, 90)): self.gamma_star, ) = self.reciprocal_latt_params() - self.a_star_vec, self.b_star_vec, self.c_star_vec = self.reciprocal_vec_cart() + ( + self.a_star_vec, + self.b_star_vec, + self.c_star_vec, + ) = self._reciprocal_space_vectors() @staticmethod - def v_alpha_beta_gamma_calc(alpha, beta, gamma): + def v_alpha_beta_gamma_calc(alpha, beta, gamma) -> float: """ Calculate V_alpha_bet_gamma = Volume/(abc) Volume = a * (b x c) """ - cos_alpha = np.cos(alpha / 180 * np.pi) - cos_beta = np.cos(beta / 180 * np.pi) - cos_gamma = np.cos(gamma / 180 * np.pi) + cos_alpha = np.cos(np.deg2rad(alpha)) + cos_beta = np.cos(np.deg2rad(beta)) + cos_gamma = np.cos(np.deg2rad(gamma)) v_alpha_beta_gamma = np.sqrt( 1 - cos_alpha**2 - cos_beta**2 - cos_gamma**2 + 2 * cos_alpha * cos_beta * cos_gamma ) return v_alpha_beta_gamma - def real_vec_cart(self): + def _real_space_vectors(self) -> tuple[np.ndarray]: """ Calculate the real space lattice vectors in Cartesian coordiantes """ - cos_alpha = np.cos(self.alpha / 180 * np.pi) - cos_beta = np.cos(self.beta / 180 * np.pi) - cos_gamma = np.cos(self.gamma / 180 * np.pi) - sin_gamma = np.sin(self.gamma / 180 * np.pi) + cos_alpha = np.cos(np.deg2rad(self.alpha)) + cos_beta = np.cos(np.deg2rad(self.beta)) + cos_gamma = np.cos(np.deg2rad(self.gamma)) + sin_gamma = np.sin(np.deg2rad(self.gamma)) ac = np.array([self.a, 0, 0]) bc = np.array( @@ -136,48 +155,45 @@ def real_vec_cart(self): 0, ] ) - + v_abg = Sample.v_alpha_beta_gamma_calc(self.alpha, self.beta, self.gamma) cc = np.array( [ self.c * cos_beta, self.c * (cos_alpha - cos_gamma * cos_beta) / sin_gamma, - self.c * self._v_abg / sin_gamma, + self.c * v_abg / sin_gamma, ] ) - # ac = np.round(ac, 8) - # bc = np.round(bc, 8) - # cc = np.round(cc, 8) return (ac, bc, cc) def reciprocal_latt_params(self): - """Calculate the reciprocal lattice parameters and angles""" - sin_alpha = np.sin(self.alpha / 180 * np.pi) - cos_alpha = np.cos(self.alpha / 180 * np.pi) - sin_beta = np.sin(self.beta / 180 * np.pi) - cos_beta = np.cos(self.beta / 180 * np.pi) - cos_gamma = np.cos(self.gamma / 180 * np.pi) - sin_gamma = np.sin(self.gamma / 180 * np.pi) - - a_star = sin_alpha / self.a / self._v_abg * np.pi * 2 - b_star = sin_beta / self.b / self._v_abg * np.pi * 2 - c_star = sin_gamma / self.c / self._v_abg * np.pi * 2 - alpha_star = np.arccos((cos_beta * cos_gamma - cos_alpha) / sin_beta / sin_gamma) / np.pi * 180 - beta_star = np.arccos((cos_gamma * cos_alpha - cos_beta) / sin_alpha / sin_gamma) / np.pi * 180 - gamma_star = np.arccos((cos_alpha * cos_beta - cos_gamma) / sin_beta / sin_alpha) / np.pi * 180 - # a_star = np.round(a_star, 8) - # b_star = np.round(b_star, 8) - # c_star = np.round(c_star, 8) - # alpha_star = np.round(alpha_star, 8) - # beta_star = np.round(beta_star, 8) - # gamma_star = np.round(gamma_star, 8) + """Calculate the reciprocal lattice parameter lengths and angles""" + sin_alpha = np.sin(np.deg2rad(self.alpha)) + cos_alpha = np.cos(np.deg2rad(self.alpha)) + sin_beta = np.sin(np.deg2rad(self.beta)) + cos_beta = np.cos(np.deg2rad(self.beta)) + cos_gamma = np.cos(np.deg2rad(self.gamma)) + sin_gamma = np.sin(np.deg2rad(self.gamma)) + + v_abg = Sample.v_alpha_beta_gamma_calc(self.alpha, self.beta, self.gamma) + + a_star = sin_alpha / self.a / v_abg * np.pi * 2 + b_star = sin_beta / self.b / v_abg * np.pi * 2 + c_star = sin_gamma / self.c / v_abg * np.pi * 2 + alpha_star = np.arccos((cos_beta * cos_gamma - cos_alpha) / sin_beta / sin_gamma) + beta_star = np.arccos((cos_gamma * cos_alpha - cos_beta) / sin_alpha / sin_gamma) + gamma_star = np.arccos((cos_alpha * cos_beta - cos_gamma) / sin_beta / sin_alpha) + alpha_star = np.rad2deg(alpha_star) + beta_star = np.rad2deg(beta_star) + gamma_star = np.rad2deg(gamma_star) return (a_star, b_star, c_star, alpha_star, beta_star, gamma_star) - def reciprocal_vec_cart(self): + def _reciprocal_space_vectors(self) -> tuple[np.ndarray]: """ Calculate the reciprocal space lattice vectors in the Cartesian coordinates """ - v = self._v_abg * self.a * self.b * self.c + v_abg = Sample.v_alpha_beta_gamma_calc(self.alpha, self.beta, self.gamma) + v = v_abg * self.a * self.b * self.c prefactor = 2 * np.pi / v a_star_vec = np.cross(self.b_vec, self.c_vec) * prefactor b_star_vec = np.cross(self.c_vec, self.a_vec) * prefactor @@ -185,40 +201,48 @@ def reciprocal_vec_cart(self): return (a_star_vec, b_star_vec, c_star_vec) - def hkl2q(self, hkl): + def hkl2q(self, hkl: tuple[float]) -> float: """Convert (h,k,l) to q, in units of inverse Angstrom""" - (h, k, l) = hkl - q = np.linalg.norm(h * self.a_star_vec + k * self.b_star_vec + l * self.c_star_vec) - return q + assert len(hkl) == 3, "Length of (h,k,l) is not 3." + (qh, qk, ql) = hkl + q_vec = qh * self.a_star_vec + qk * self.b_star_vec + ql * self.c_star_vec + q_norm = np.linalg.norm(q_vec) + return q_norm - def b_mat(self): + def b_mat(self) -> np.ndarray: """ Calculate the B matrix B * (h,k,l) gives Q in terms of i_star, j_star, k_star """ + alpha_star_deg = np.deg2rad(self.alpha_star) + beta_star_deg = np.deg2rad(self.beta_star) + gamma_star_deg = np.deg2rad(self.gamma_star) b_mat = np.array( [ [ self.a_star, - self.b_star * np.cos(self.gamma_star / 180 * np.pi), - self.c_star * np.cos(self.beta_star / 180 * np.pi), + self.b_star * np.cos(gamma_star_deg), + self.c_star * np.cos(beta_star_deg), ], [ 0, - self.b_star * np.sin(self.gamma_star / 180 * np.pi), - -self.c_star * np.sin(self.beta_star / 180 * np.pi) * np.cos(self.alpha / 180 * np.pi), + self.b_star * np.sin(gamma_star_deg), + -self.c_star * np.sin(beta_star_deg) * np.cos(alpha_star_deg), ], [0, 0, 2 * np.pi / self.c], ] ) - b_mat = b_mat / 2 / np.pi - b_mat = np.round(b_mat, 8) + b_mat = b_mat / (2 * np.pi) + # b_mat = np.round(b_mat, 8) return b_mat - def reciprocal_basis(self): + def _reciprocal_basis(self) -> tuple[np.ndarray]: """Calculate the reciprocal basis vectors i_star, j_star, k_star""" i_star = self.a_star_vec / np.linalg.norm(self.a_star_vec) - a_star_perp = np.cross(np.cross(self.a_star_vec, self.b_star_vec), self.a_star_vec) + a_star_perp = np.cross( + np.cross(self.a_star_vec, self.b_star_vec), + self.a_star_vec, + ) j_star = a_star_perp / np.linalg.norm(a_star_perp) k_star = np.cross(i_star, j_star) return (i_star, j_star, k_star) diff --git a/src/tavi/sample/xtal.py b/src/tavi/sample/xtal.py index 100bec1..7b4658c 100755 --- a/src/tavi/sample/xtal.py +++ b/src/tavi/sample/xtal.py @@ -31,7 +31,7 @@ def __init__(self, lattice_params=(1, 1, 1, 90, 90, 90)): self.plane_normal = None self.in_plane_ref = None - self.i_star, self.j_star, self.k_star = self.reciprocal_basis() + self.i_star, self.j_star, self.k_star = self._reciprocal_basis() @classmethod def from_json(cls, sample_params): @@ -61,7 +61,7 @@ def from_json(cls, sample_params): case _: if key in param_dict: setattr(sample, key, val) - sample.update_lattice(lattice_params) + sample.update_lattice_parameters(lattice_params) return sample @property diff --git a/test_data/test_samples/nitio3.json b/test_data/test_samples/nitio3.json index c894d8e..ec02a8c 100644 --- a/test_data/test_samples/nitio3.json +++ b/test_data/test_samples/nitio3.json @@ -10,7 +10,7 @@ "width": 1.0, "height": 1.0, "depth": 1.0, - "mosaic": 30, + "mosaic_h": 30, "mosaic_v": 30, "ub_matrix": [ -0.016965, diff --git a/test_data/test_samples/python_samples/nitio3.py b/test_data/test_samples/python_samples/nitio3.py index fa8cc35..4d0f1b6 100644 --- a/test_data/test_samples/python_samples/nitio3.py +++ b/test_data/test_samples/python_samples/nitio3.py @@ -1,6 +1,7 @@ import numpy as np -from tavi.utilities import * + from tavi.sample.xtal import Xtal +from tavi.utilities import * # test_xtal = Xtal(lattice_params=(5.3995, 5.64, 11.75, 90, 90, 90)) nitio3 = Xtal(lattice_params=(5.034785, 5.034785, 13.812004, 90, 90, 120)) @@ -10,7 +11,7 @@ nitio3.width = 1.0 * cm2angstrom nitio3.height = 1.0 * cm2angstrom nitio3.depth = 1.0 * cm2angstrom -nitio3.mosaic = 30 * min2rad # horizontal mosaic +nitio3.mosaic_h = 30 * min2rad # horizontal mosaic nitio3.mosaic_v = 30 * min2rad # vertical mosaic ub_matrix_spice = np.array( diff --git a/test_data/test_samples/python_samples/sample_test.py b/test_data/test_samples/python_samples/sample_test.py index 549c4a7..dd56a9a 100644 --- a/test_data/test_samples/python_samples/sample_test.py +++ b/test_data/test_samples/python_samples/sample_test.py @@ -1,6 +1,7 @@ import numpy as np -from tavi.utilities import * + from tavi.sample.xtal import Xtal +from tavi.utilities import * # test_xtal = Xtal(lattice_params=(5.3995, 5.64, 11.75, 90, 90, 90)) test_xtal = Xtal(lattice_params=(3.574924, 3.574924, 5.663212, 90, 90, 120)) @@ -10,7 +11,7 @@ test_xtal.width = 1.0 # * cm2angstrom test_xtal.height = 1.0 # * cm2angstrom test_xtal.depth = 1.0 # * cm2angstrom -test_xtal.mosaic = 30 # * min2rad # horizontal mosaic +test_xtal.mosaic_h = 30 # * min2rad # horizontal mosaic test_xtal.mosaic_v = 30 # * min2rad # vertical mosaic ub_matrix_spice = np.array( diff --git a/test_data/test_samples/sample.json b/test_data/test_samples/sample.json new file mode 100644 index 0000000..2b4f9a7 --- /dev/null +++ b/test_data/test_samples/sample.json @@ -0,0 +1,8 @@ +{ + "a": 5.034785, + "b": 5.034785, + "c": 13.812004, + "alpha": 90, + "beta": 90, + "gamma": 120 +} \ No newline at end of file diff --git a/test_data/test_samples/sample_test.json b/test_data/test_samples/sample_test.json index 9753e8c..3db2ab2 100644 --- a/test_data/test_samples/sample_test.json +++ b/test_data/test_samples/sample_test.json @@ -10,7 +10,7 @@ "width": 1.0, "height": 1.0, "depth": 1.0, - "mosaic": 30, + "mosaic_h": 30, "mosaic_v": 30, "ub_matrix": [ 0.053821, diff --git a/tests/test_sample.py b/tests/test_sample.py index 9ff94f1..d1391d6 100644 --- a/tests/test_sample.py +++ b/tests/test_sample.py @@ -1,8 +1,11 @@ import numpy as np import pytest +from tavi.instrument.tas import TAS from tavi.sample.xtal import Xtal +np.set_printoptions(floatmode="fixed", precision=4) + @pytest.fixture def xtal_info(): @@ -64,3 +67,9 @@ def test_uv_to_ub_matrix(xtal_info): ub_matrix_calc = xtal.uv_to_ub_matrix(u, v) assert np.allclose(ub_matrix_calc, ub_matrix, atol=1e-2) + + +def test_load_sample_from_json(): + sample_json_path = "./test_data/test_samples/sample.json" + tas = TAS() + tas.load_sample_from_json(sample_json_path)