From 6767f278fc1d25629d478698ef2a3dd22b4773d1 Mon Sep 17 00:00:00 2001 From: Rose Yemelyanova Date: Fri, 22 Jul 2022 10:50:03 +0000 Subject: [PATCH 01/13] fixed basic mypy errors --- .gitignore | 5 +++++ src/diffcalc/hkl/geometry.py | 2 +- src/diffcalc/ub/calc.py | 25 +++++++++++++++---------- src/diffcalc/ub/crystal.py | 8 ++++---- src/diffcalc/ub/fitting.py | 16 +++++++++------- src/diffcalc/util.py | 8 +++++--- 6 files changed, 39 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 290a527..8507207 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +# Dev files and environments +.vscode/ +.devcontainer/ +venv2/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/src/diffcalc/hkl/geometry.py b/src/diffcalc/hkl/geometry.py index 438ce7d..b7e3c57 100644 --- a/src/diffcalc/hkl/geometry.py +++ b/src/diffcalc/hkl/geometry.py @@ -392,4 +392,4 @@ def get_q_phi(pos: Position) -> np.ndarray: y = np.array([[0], [1], [0]]) q_lab = (NU @ DELTA - I) @ y # Transform this into the phi frame. - return inv(PHI) @ inv(CHI) @ inv(ETA) @ inv(MU) @ q_lab + return np.array(inv(PHI) @ inv(CHI) @ inv(ETA) @ inv(MU) @ q_lab) diff --git a/src/diffcalc/ub/calc.py b/src/diffcalc/ub/calc.py index fbed45b..7475f96 100644 --- a/src/diffcalc/ub/calc.py +++ b/src/diffcalc/ub/calc.py @@ -10,7 +10,7 @@ from copy import deepcopy from itertools import product from math import acos, asin, cos, degrees, pi, radians, sin -from typing import List, Optional, Sequence, Tuple, Union +from typing import Any, List, Optional, Sequence, Tuple, Union import numpy as np from diffcalc.hkl.geometry import Position, get_q_phi, get_rotation_matrices @@ -99,7 +99,8 @@ def get_array(self, UB: Optional[np.ndarray] = None) -> np.ndarray: n_ref_new = UB @ n_ref_array else: n_ref_new = inv(UB) @ n_ref_array - return n_ref_new / norm(n_ref_new) + + return np.array(n_ref_new / float(norm(n_ref_new))) def set_array(self, n_ref: np.ndarray) -> None: """Set reference vector coordinates from NumPy array. @@ -425,7 +426,7 @@ def set_lattice( """ if not isinstance(name, str): raise TypeError("Invalid crystal name.") - shortform = tuple( + shortform: Tuple[Any, ...] = tuple( val for val in (system, a, b, c, alpha, beta, gamma) if val is not None ) if not shortform: @@ -449,17 +450,17 @@ def set_lattice( raise TypeError( "Invalid number of input parameters to set unit lattice." ) - fullform = (system,) + shortform + fullform: Tuple[Any, ...] = (system,) + shortform + self.crystal = Crystal(name, *fullform) else: if not isinstance(shortform[0], str): raise TypeError("Invalid unit cell parameters specified.") - fullform = shortform + self.crystal = Crystal(name, *shortform) if self.name is None: raise DiffcalcException( "Cannot set lattice until a UBCalcaluation has been started " "with newubcalc" ) - self.crystal = Crystal(name, *fullform) ### Reference vector ### @property @@ -1200,7 +1201,7 @@ def _fit_ub_uncon( a1 = cross3(b2.T, b3.T) * 2 * pi / V a2 = cross3(b3.T, b1.T) * 2 * pi / V a3 = cross3(b1.T, b2.T) * 2 * pi / V - ax, bx, cx = norm(a1), norm(a2), norm(a3) + ax, bx, cx = float(norm(a1)), float(norm(a2)), float(norm(a3)) alpha = acos(dot3(a2, a3) / (bx * cx)) beta = acos(dot3(a1, a3) / (ax * cx)) gamma = acos(dot3(a1, a2) / (ax * bx)) @@ -1231,7 +1232,9 @@ def get_miscut(self) -> Tuple[float, np.ndarray]: rotation_angle = 0.0 else: rotation_axis = rotation_axis / norm(rotation_axis) - cos_rotation_angle = bound(dot3(self.surf_nphi, surf_rot) / norm(surf_rot)) + cos_rotation_angle = bound( + float(dot3(self.surf_nphi, surf_rot) / norm(surf_rot)) + ) rotation_angle = acos(cos_rotation_angle) return rotation_angle, rotation_axis @@ -1263,7 +1266,9 @@ def get_miscut_from_hkl( return None, None axis = axis / norm(axis) try: - miscut = acos(bound(dot3(q_vec, hkl_nphi) / (norm(q_vec) * norm(hkl_nphi)))) + miscut = acos( + bound(float(dot3(q_vec, hkl_nphi) / (norm(q_vec) * norm(hkl_nphi)))) + ) except AssertionError: return 0, (0, 0, 0) return degrees(miscut), (axis[0, 0], axis[1, 0], axis[2, 0]) @@ -1357,7 +1362,7 @@ def _rescale_unit_cell( q_vec = get_q_phi(pos) q_hkl = norm(q_vec) / wavelength d_hkl = self.crystal.get_hkl_plane_distance(hkl) - sc = 1 / (q_hkl * d_hkl) + sc = float(1 / (q_hkl * d_hkl)) name, a1, a2, a3, alpha1, alpha2, alpha3 = self.crystal.get_lattice() if abs(sc - 1.0) < SMALL: return None, None diff --git a/src/diffcalc/ub/crystal.py b/src/diffcalc/ub/crystal.py index 17cd4f0..2bd5c79 100644 --- a/src/diffcalc/ub/crystal.py +++ b/src/diffcalc/ub/crystal.py @@ -377,9 +377,9 @@ def get_hkl_plane_angle( float The angle between the crystal lattice planes. """ - hkl1 = np.array([hkl1]).T - hkl2 = np.array([hkl2]).T - nphi1 = self.B @ hkl1 - nphi2 = self.B @ hkl2 + hkl1_transpose = np.array([hkl1]).T + hkl2_transpose = np.array([hkl2]).T + nphi1 = self.B @ hkl1_transpose + nphi2 = self.B @ hkl2_transpose angle = angle_between_vectors(nphi1, nphi2) return angle diff --git a/src/diffcalc/ub/fitting.py b/src/diffcalc/ub/fitting.py index 19b8d6a..6fbc1d6 100644 --- a/src/diffcalc/ub/fitting.py +++ b/src/diffcalc/ub/fitting.py @@ -27,25 +27,27 @@ def _get_refl_hkl( def _func_crystal( - vals: Sequence[float], uc_system: str, refl_data: Tuple[np.ndarray, Position, float] + vals: Sequence[float], + uc_system: str, + refl_data: List[Tuple[np.ndarray, Position, float]], ) -> float: try: trial_cr = Crystal("trial", uc_system, *vals) except Exception: return 1e6 - res = 0 + res = 0.0 for (hkl_vals, pos_vals, en) in refl_data: wl = 12.3984 / en [_, DELTA, NU, _, _, _] = get_rotation_matrices(pos_vals) q_pos = (NU @ DELTA - I) @ np.array([[0], [2 * pi / wl], [0]]) q_hkl = trial_cr.B @ hkl_vals - res += (norm(q_pos) - norm(q_hkl)) ** 2 + res += (float(norm(q_pos)) - float(norm(q_hkl))) ** 2 return res def _func_orient( - vals, crystal: Crystal, refl_data: Tuple[np.ndarray, Position, float] + vals, crystal: Crystal, refl_data: List[Tuple[np.ndarray, Position, float]] ) -> float: quat = _get_quat_from_u123(*vals) trial_u = _get_rot_matrix(*quat) @@ -67,19 +69,19 @@ def _get_rot_matrix(q0: float, q1: float, q2: float, q3: float) -> np.ndarray: rot = np.array( [ [ - q0 ** 2 + q1 ** 2 - q2 ** 2 - q3 ** 2, + q0**2 + q1**2 - q2**2 - q3**2, 2.0 * (q1 * q2 - q0 * q3), 2.0 * (q1 * q3 + q0 * q2), ], [ 2.0 * (q1 * q2 + q0 * q3), - q0 ** 2 - q1 ** 2 + q2 ** 2 - q3 ** 2, + q0**2 - q1**2 + q2**2 - q3**2, 2.0 * (q2 * q3 - q0 * q1), ], [ 2.0 * (q1 * q3 - q0 * q2), 2.0 * (q2 * q3 + q0 * q1), - q0 ** 2 - q1 ** 2 - q2 ** 2 + q3 ** 2, + q0**2 - q1**2 - q2**2 + q3**2, ], ] ) diff --git a/src/diffcalc/util.py b/src/diffcalc/util.py index 48bc885..0cda4ea 100644 --- a/src/diffcalc/util.py +++ b/src/diffcalc/util.py @@ -74,8 +74,9 @@ def xyz_rotation(axis: Tuple[float, float, float], angle: float) -> np.ndarray: np.ndarray Rotation matrix. """ - rot = Rotation.from_rotvec(angle * np.array(axis) / norm(np.array(axis))) - return rot.as_matrix() + rot: Rotation = Rotation.from_rotvec(angle * np.array(axis) / norm(np.array(axis))) + rot_as_matrix: np.ndarray = rot.as_matrix() + return rot_as_matrix class DiffcalcException(Exception): @@ -298,7 +299,8 @@ def normalised(vector: np.ndarray) -> np.ndarray: ndarray Normalised vector. """ - return vector * (1.0 / norm(vector)) + vector_norm: float = float(norm(vector)) + return vector * (1.0 / vector_norm) def zero_round(num): From 356659f2056628e3f6833dcec2d53fed86eb7884 Mon Sep 17 00:00:00 2001 From: Rose Yemelyanova Date: Fri, 22 Jul 2022 16:19:11 +0000 Subject: [PATCH 02/13] fixed some flake8 errors showing code as too complex --- src/diffcalc/hkl/calc.py | 825 +++++++++++++++++--------------- src/diffcalc/hkl/constraints.py | 72 +-- 2 files changed, 474 insertions(+), 423 deletions(-) diff --git a/src/diffcalc/hkl/calc.py b/src/diffcalc/hkl/calc.py index 7b9babf..d63f61d 100644 --- a/src/diffcalc/hkl/calc.py +++ b/src/diffcalc/hkl/calc.py @@ -6,9 +6,10 @@ from copy import copy from itertools import product from math import acos, asin, atan, atan2, cos, degrees, isnan, pi, sin, sqrt, tan -from typing import Dict, Iterator, List, Optional, Tuple +from typing import Callable, Dict, Iterable, Iterator, List, Optional, Tuple import numpy as np +from diffcalc.hkl.constraints import Constraints from diffcalc.hkl.geometry import ( Position, get_rotation_matrices, @@ -18,6 +19,7 @@ rot_PHI, ) from diffcalc.log import logging +from diffcalc.ub.calc import UBCalculation from diffcalc.util import ( SMALL, DiffcalcException, @@ -120,17 +122,17 @@ def get_virtual_angles( ) # (19) [MU, DELTA, NU, ETA, CHI, PHI] = get_rotation_matrices(pos_in_rad) - Z = MU @ ETA @ CHI @ PHI + z_matrix = MU @ ETA @ CHI @ PHI D = NU @ DELTA # Compute incidence and outgoing angles bin and betaout - surf_nphi = Z @ self.ubcalc.surf_nphi + surf_nphi = z_matrix @ self.ubcalc.surf_nphi kin = np.array([[0], [1], [0]]) kout = D @ np.array([[0], [1], [0]]) betain = angle_between_vectors(kin, surf_nphi) - pi / 2.0 betaout = pi / 2.0 - angle_between_vectors(kout, surf_nphi) - n_lab = Z @ self.ubcalc.n_phi + n_lab = z_matrix @ self.ubcalc.n_phi alpha = asin(bound(-n_lab[1, 0])) naz = atan2(n_lab[0, 0], n_lab[2, 0]) # (20) @@ -211,9 +213,7 @@ def get_position( return results - def _calc_hkl_to_position( - self, h: float, k: float, l: float, wavelength: float - ) -> List[Tuple[Position, Dict[str, float]]]: + def _check_constrained(self): if not self.constraints.is_fully_constrained(): raise DiffcalcException( "Diffcalc is not fully constrained.\n" @@ -226,28 +226,10 @@ def _calc_hkl_to_position( "is not implemented. Type 'help con' for implemented combinations" ) - # constraints are dictionaries - ref_constraint = self.constraints._reference - if ref_constraint: - ref_constraint_name, ref_constraint_value = next( - iter(ref_constraint.items()) - ) - det_constraint = self.constraints._detector - naz_constraint = {"naz": self.constraints.naz} if self.constraints.naz else None - samp_constraints = self.constraints._sample - - assert not ( - det_constraint and naz_constraint - ), "Two 'detector' constraints given" - - h_phi = self.ubcalc.UB @ np.array([[h], [k], [l]]) - theta = ( - self.ubcalc.get_ttheta_from_hkl((h, k, l), 12.39842 / wavelength) / 2.0 - ) # __calc_theta(h_phi, wavelength) - tau = angle_between_vectors(h_phi, self.ubcalc.n_phi) - surf_tau = angle_between_vectors(h_phi, self.ubcalc.surf_nphi) - - if is_small(sin(tau)) and ref_constraint: + def _verify_angles( + self, tau: float, surf_tau: float, ref_constraint_name: str + ) -> None: + if is_small(sin(tau)): if ref_constraint_name == "psi": raise DiffcalcException( "Azimuthal angle 'psi' is undefined as reference and scattering vectors parallel.\n" @@ -258,26 +240,41 @@ def _calc_hkl_to_position( "Reference constraint 'a_eq_b' is redundant as reference and scattering vectors are parallel.\n" "Please constrain one of the sample angles or choose different reference vector orientation." ) - if ( - is_small(sin(surf_tau)) - and ref_constraint - and ref_constraint_name == "bin_eq_bout" - ): + if is_small(sin(surf_tau)) and ref_constraint_name == "bin_eq_bout": raise DiffcalcException( "Reference constraint 'bin_eq_bout' is redundant as scattering vectors is parallel to the surface normal.\n" "Please select another constrain to define sample azimuthal orientation." ) + def _calc_hkl_to_position( + self, h: float, k: float, l: float, wavelength: float + ) -> List[Tuple[Position, Dict[str, float]]]: + self._check_constrained() # is hkl constrained? + + # constraints are dictionaries + ref_constraint = self.constraints.reference + det_constraint = self.constraints.detector + samp_constraints = self.constraints.sample + + # get necessary angles + h_phi = self.ubcalc.UB @ np.array([[h], [k], [l]]) + theta = ( + self.ubcalc.get_ttheta_from_hkl((h, k, l), 12.39842 / wavelength) / 2.0 + ) # __calc_theta(h_phi, wavelength) + tau = angle_between_vectors(h_phi, self.ubcalc.n_phi) + surf_tau = angle_between_vectors(h_phi, self.ubcalc.surf_nphi) + ### Reference constraint column ### n_phi = self.ubcalc.n_phi if ref_constraint: - if {"psi", "a_eq_b", "alpha", "beta"}.issuperset(ref_constraint.keys()): - # An angle for the reference vector (n) is given (Section 5.2) + ref_constraint_name, ref_constraint_value = list(ref_constraint.items())[-1] + self._verify_angles(tau, surf_tau, ref_constraint_name) + if ref_constraint_name in ["psi", "a_eq_b", "alpha", "beta"]: alpha, _ = self._calc_remaining_reference_angles( ref_constraint_name, ref_constraint_value, theta, tau ) - elif {"bin_eq_bout", "betain", "betaout"}.issuperset(ref_constraint.keys()): + else: alpha, _ = self._calc_remaining_reference_angles( ref_constraint_name, ref_constraint_value, theta, surf_tau ) @@ -285,16 +282,16 @@ def _calc_hkl_to_position( n_phi = self.ubcalc.surf_nphi solution_tuples = [] - if det_constraint or naz_constraint: + if det_constraint: - if len(samp_constraints) == 1: + if len(samp_constraints) == 1: # i.e. you have one reference constraint for ( qaz, naz, delta, nu, - ) in self._calc_det_angles_given_det_or_naz_constraint( - det_constraint, naz_constraint, theta, tau, alpha + ) in self._calc_det_angles_given_det_constraint( + det_constraint, theta, tau, alpha ): for ( mu, @@ -307,37 +304,30 @@ def _calc_hkl_to_position( solution_tuples.append((mu, delta, nu, eta, chi, phi)) elif len(samp_constraints) == 2: - if det_constraint: - det_constraint_name, det_constraint_val = next( - iter(det_constraint.items()) - ) - for delta, nu, qaz in self._calc_remaining_detector_angles( - det_constraint_name, det_constraint_val, theta + det_constraint_name, det_constraint_val = next( + iter(det_constraint.items()) + ) + for delta, nu, qaz in self._calc_remaining_detector_angles( + det_constraint_name, det_constraint_val, theta + ): + for ( + mu, + eta, + chi, + phi, + ) in self._calc_sample_angles_given_two_sample_and_detector( + samp_constraints, qaz, theta, h_phi, n_phi ): - for ( - mu, - eta, - chi, - phi, - ) in self._calc_sample_angles_given_two_sample_and_detector( - samp_constraints, qaz, theta, h_phi, n_phi - ): - solution_tuples.append((mu, delta, nu, eta, chi, phi)) - - else: - raise DiffcalcException( - "No code yet to handle this combination of detector and sample constraints!" - ) + solution_tuples.append((mu, delta, nu, eta, chi, phi)) elif len(samp_constraints) == 2: - if ref_constraint_name == "psi": - psi_vals = iter( - [ - ref_constraint_value, - ] - ) - else: - psi_vals = self._calc_psi(alpha, theta, tau) + + psi_vals: Iterator[float] = ( + ref_constraint["psi"] + if "psi" in list(ref_constraint.keys()) + else self._calc_psi(alpha, theta, tau) + ) + for psi in psi_vals: solution_tuples.extend( self._calc_sample_given_two_sample_and_reference( @@ -365,13 +355,6 @@ def _calc_hkl_to_position( for pos in solution_tuples ] - # def _find_duplicate_angles(el): - # idx, tpl = el - # for tmp_tpl in filtered_solutions[idx:]: - # if False not in [abs(x-y) < SMALL for x,y in zip(tmp_tpl, tpl)]: - # return False - # return True - # merged_solution_tuples = filter(_find_duplicate_angles, enumerate(filtered_solutions, 1)) position_pseudo_angles_pairs = self._create_position_pseudo_angles_pairs( tidy_solutions ) @@ -400,8 +383,8 @@ def _create_position_pseudo_angles_pairs( pseudo_angles = self.get_virtual_angles(position, False) try: for constraint in [ - self.constraints._reference, - self.constraints._detector, + self.constraints.reference, + self.constraints.detector, ]: for constraint_name, constraint_value in constraint.items(): if constraint_name == "a_eq_b": @@ -508,8 +491,8 @@ def _calc_psi( else: sin_psi = cos(alpha) * sin(qaz - naz) sgn = sign(sin_tau) - eps = sin_psi ** 2 + cos_psi ** 2 - sigma_ = eps / sin_tau ** 2 - 1 + eps = sin_psi**2 + cos_psi**2 + sigma_ = eps / sin_tau**2 - 1 if not is_small(sigma_): print( "WARNING: Diffcalc could not calculate a unique azimuth " @@ -562,76 +545,189 @@ def _calc_remaining_reference_angles( return alpha, beta - def _calc_det_angles_given_det_or_naz_constraint( + def _calc_det_angles_given_det_constraint( self, det_constraint: Dict[str, Optional[float]], - naz_constraint: Dict[str, Optional[float]], theta: float, tau: float, alpha: float, ) -> Iterator[Tuple[float, float, float, float]]: - - assert det_constraint or naz_constraint try: naz_qaz_angle = self._calc_angle_between_naz_and_qaz(theta, alpha, tau) except AssertionError: return - if det_constraint: - # One of the detector angles is given (Section 5.1) - det_constraint_name, det_constraint_value = next( - iter(det_constraint.items()) + + if "naz" in list(det_constraint.keys()): + naz_value = det_constraint["naz"] + + qaz_angles = ( + [ + naz_value, + ] + if is_small(naz_qaz_angle) + else [ + naz_value - naz_qaz_angle, + naz_value + naz_qaz_angle, + ] ) + + for qaz in qaz_angles: + for delta, nu, _ in self._calc_remaining_detector_angles( + "qaz", qaz, theta + ): + yield qaz, naz_value, delta, nu + else: + constraint_name, constraint_value = list(det_constraint.items())[0] for delta, nu, qaz in self._calc_remaining_detector_angles( - det_constraint_name, det_constraint_value, theta + constraint_name, constraint_value, theta ): - if is_small(naz_qaz_angle): - naz_angles = [ + + naz_angles = ( + [ qaz, ] - else: - naz_angles = [qaz - naz_qaz_angle, qaz + naz_qaz_angle] + if is_small(naz_qaz_angle) + else [qaz - naz_qaz_angle, qaz + naz_qaz_angle] + ) + for naz in naz_angles: yield qaz, naz, delta, nu - elif naz_constraint: # The 'detector' angle naz is given: - naz_name, naz = next(iter(naz_constraint.items())) - assert naz_name == "naz" - if is_small(naz_qaz_angle): - qaz_angles = [ - naz, - ] - else: - qaz_angles = [naz - naz_qaz_angle, naz + naz_qaz_angle] - for qaz in qaz_angles: - for delta, nu, _ in self._calc_remaining_detector_angles( - "qaz", qaz, theta - ): - yield qaz, naz, delta, nu def _calc_remaining_detector_angles( self, constraint_name: str, constraint_value: float, theta: float - ) -> Iterator[Tuple[float, float, float]]: + ) -> Iterable[Tuple[float, float, float]]: """Return delta, nu and qaz given one detector angle.""" - # (section 5.1) - # Find qaz using various derivations of 17 and 18 sin_2theta = sin(2 * theta) cos_2theta = cos(2 * theta) if is_small(sin_2theta): raise DiffcalcException( "No meaningful scattering vector (Q) can be found when " - f"theta is so small {degrees(theta):.4f}." + f"theta is so small: {degrees(theta):.4f} degrees." + ) + + constraint_callable: Dict[ + str, Callable[[float, float, float], Iterable[Tuple[float, float, float]]] + ] = { + "delta": self._calc_remaining_detector_angles_delta_constrained, + "nu": self._calc_remaining_detector_angles_nu_constrained, + "qaz": self._calc_remaining_detector_angles_qaz_constrained, + } + + try: + detector_angles = constraint_callable[constraint_name]( + constraint_value, sin_2theta, cos_2theta ) + except KeyError: + raise DiffcalcException( + constraint_name + " is not an explicit detector angle " + "(naz cannot be handled here)" + ) + + yield from detector_angles + + def _calc_remaining_detector_angles_delta_constrained( + self, delta_value: float, sin_2theta: float, cos_2theta: float + ) -> Iterable[Tuple[float, float, float]]: + + try: + asin_qaz = asin(bound(sin(delta_value) / sin_2theta)) + except AssertionError: + return - if constraint_name == "delta": - delta = constraint_value + cos_delta = cos(delta_value) + if is_small(cos_delta): + print( + ( + "DEGENERATE: with delta=90, %s is degenerate: choosing " + "%s = 0 (allowed because %s is unconstrained)" + ) + % ("nu", "nu", "nu") + ) + acos_nu = 1.0 + else: try: - asin_qaz = asin(bound(sin(delta) / sin_2theta)) # (17 & 18) + acos_nu = acos(bound(cos_2theta / cos_delta)) except AssertionError: return + + qaz_angles = ( + [ + sign(asin_qaz) * pi / 2.0, + ] + if is_small(cos(asin_qaz)) + else [asin_qaz, pi - asin_qaz] + ) + nu_angles = ( + [ + 0.0, + ] + if is_small(acos_nu) + else [acos_nu, -acos_nu] + ) + + for qaz, nu in product(qaz_angles, nu_angles): + sgn_ref = sign(sin_2theta) * sign(cos(qaz)) + sgn_ratio = sign(sin(nu)) * sign(cos_delta) + if sgn_ref == sgn_ratio: + yield delta_value, nu, qaz + + def _calc_remaining_detector_angles_nu_constrained( + self, nu_value: float, sin_2theta: float, cos_2theta: float + ) -> Iterable[Tuple[float, float, float]]: + cos_nu = cos(nu_value) + if is_small(cos_nu): + raise DiffcalcException( + "The %s circle constraint to %.0f degrees is redundant." + "Please change this constraint or use 4-circle mode." + % ("nu", degrees(nu_value)) + ) + cos_delta = cos_2theta / cos(nu_value) + cos_qaz = cos_delta * sin(nu_value) / sin_2theta + + try: + acos_delta = acos(bound(cos_delta)) + acos_qaz = acos(bound(cos_qaz)) + except AssertionError: + return + + qaz_angles = ( + [ + 0.0, + ] + if is_small(acos_qaz) + else [acos_qaz, -acos_qaz] + ) + delta_angles = ( + [ + 0.0, + ] + if is_small(acos_delta) + else [acos_delta, -acos_delta] + ) + + for qaz, delta in product(qaz_angles, delta_angles): + sgn_ref = sign(sin(delta)) + sgn_ratio = sign(sin(qaz)) * sign(sin_2theta) + if sgn_ref == sgn_ratio: + yield delta, nu_value, qaz + + def _calc_remaining_detector_angles_qaz_constrained( + self, qaz_value: float, sin_2theta: float, cos_2theta: float + ) -> Iterable[Tuple[float, float, float]]: + try: + asin_delta = asin(sin(qaz_value) * sin_2theta) + except AssertionError: + return + + if is_small(cos(asin_delta)): + delta_angles = [ + sign(asin_delta) * pi / 2.0, + ] + else: + delta_angles = [asin_delta, pi - asin_delta] + for delta in delta_angles: cos_delta = cos(delta) if is_small(cos_delta): - # raise DiffcalcException( - # 'The %s and %s circles are redundant when delta is constrained to %.0f degrees.' - # 'Please change delta constraint or use 4-circle mode.' % ("nu", 'mu', delta * TODEG)) print( ( "DEGENERATE: with delta=90, %s is degenerate: choosing " @@ -639,98 +735,17 @@ def _calc_remaining_detector_angles( ) % ("nu", "nu", "nu") ) - acos_nu = 1.0 + nu = 0.0 else: + sgn_delta = sign(cos_delta) try: - acos_nu = acos(bound(cos_2theta / cos_delta)) - except AssertionError: - return - if is_small(cos(asin_qaz)): - qaz_angles = [ - sign(asin_qaz) * pi / 2.0, - ] - else: - qaz_angles = [asin_qaz, pi - asin_qaz] - if is_small(acos_nu): - nu_angles = [ - 0.0, - ] - else: - nu_angles = [acos_nu, -acos_nu] - for qaz, nu in product(qaz_angles, nu_angles): - sgn_ref = sign(sin_2theta) * sign(cos(qaz)) - sgn_ratio = sign(sin(nu)) * sign(cos_delta) - if sgn_ref == sgn_ratio: - yield delta, nu, qaz - - elif constraint_name == "nu": - nu = constraint_value - cos_nu = cos(nu) - if is_small(cos_nu): - raise DiffcalcException( - "The %s circle constraint to %.0f degrees is redundant." - "Please change this constraint or use 4-circle mode." - % ("nu", degrees(nu)) - ) - cos_delta = cos_2theta / cos(nu) - cos_qaz = cos_delta * sin(nu) / sin_2theta - try: - acos_delta = acos(bound(cos_delta)) - acos_qaz = acos(bound(cos_qaz)) - except AssertionError: - return - if is_small(acos_qaz): - qaz_angles = [ - 0.0, - ] - else: - qaz_angles = [acos_qaz, -acos_qaz] - if is_small(acos_delta): - delta_angles = [ - 0.0, - ] - else: - delta_angles = [acos_delta, -acos_delta] - for qaz, delta in product(qaz_angles, delta_angles): - sgn_ref = sign(sin(delta)) - sgn_ratio = sign(sin(qaz)) * sign(sin_2theta) - if sgn_ref == sgn_ratio: - yield delta, nu, qaz - - elif constraint_name == "qaz": - qaz = constraint_value - asin_delta = asin(sin(qaz) * sin_2theta) - if is_small(cos(asin_delta)): - delta_angles = [ - sign(asin_delta) * pi / 2.0, - ] - else: - delta_angles = [asin_delta, pi - asin_delta] - for delta in delta_angles: - cos_delta = cos(delta) - if is_small(cos_delta): - print( - ( - "DEGENERATE: with delta=90, %s is degenerate: choosing " - "%s = 0 (allowed because %s is unconstrained)" - ) - % ("nu", "nu", "nu") - ) - # raise DiffcalcException( - # 'The %s circle is redundant when delta is at %.0f degrees.' - # 'Please change detector constraint or use 4-circle mode.' % ("nu", delta * TODEG)) - nu = 0.0 - else: - sgn_delta = sign(cos_delta) nu = atan2( - sgn_delta * sin_2theta * cos(qaz), sgn_delta * cos_2theta + sgn_delta * sin_2theta * cos(qaz_value), sgn_delta * cos_2theta ) - yield delta, nu, qaz - else: - raise DiffcalcException( - constraint_name + " is not an explicit detector angle " - "(naz cannot be handled here)" - ) + except AssertionError: + return + + yield delta, nu, qaz_value def _calc_sample_angles_from_one_sample_constraint( self, @@ -795,135 +810,147 @@ def _calc_remaining_sample_angles( n_phi: np.ndarray, ) -> Iterator[Tuple[float, float, float, float]]: """Return phi, chi, eta and mu, given one of these.""" - # (section 5.3) N_lab = self._calc_N(q_lab, n_lab) N_phi = self._calc_N(q_phi, n_phi) - Z = N_lab @ N_phi.T + z_matrix = N_lab @ N_phi.T + + v_matrices = { + "mu": (lambda mu: inv(rot_MU(mu)) @ N_lab @ N_phi.T), + "phi": (lambda phi: N_lab @ inv(N_phi) @ rot_PHI(phi).T), + } + + constraint_callable = { + "mu": self._calc_remaining_sample_angles_mu( + constraint_value, v_matrices["mu"](constraint_value) + ), + "phi": self._calc_remaining_sample_angles_phi( + constraint_value, v_matrices["phi"](constraint_value) + ), + "eta": self._calc_remaining_sample_angles_eta_or_chi( + constraint_name, constraint_value, z_matrix + ), + "chi": self._calc_remaining_sample_angles_eta_or_chi( + constraint_name, constraint_value, z_matrix + ), + } + + try: + yield from constraint_callable[constraint_name] + except KeyError: + raise DiffcalcException("Given angle must be one of phi, chi, eta or mu") + + def _calc_remaining_sample_angles_mu(self, mu_value: float, v_matrix: np.ndarray): + try: + acos_chi = acos(bound(v_matrix[2, 2])) + except AssertionError: + return + if is_small(sin(acos_chi)): + # chi ~= 0 or 180 and therefor phi || eta The solutions for phi + # and eta here will be valid but will be chosen unpredictably. + # Choose eta=0: + # + # tan(phi+eta)=v12/v11 from docs/extensions_to_yous_paper.wxm + chi = acos_chi + eta = 0.0 + phi = atan2(-v_matrix[1, 0], v_matrix[1, 1]) + logger.debug( + "Eta and phi cannot be chosen uniquely with chi so close " + "to 0 or 180. Returning phi=%.3f and eta=%.3f", + degrees(phi), + degrees(eta), + ) + yield mu_value, eta, chi, phi + else: + for chi in [acos_chi, -acos_chi]: + sgn = sign(sin(chi)) + phi = atan2(-sgn * v_matrix[2, 1], -sgn * v_matrix[2, 0]) + eta = atan2(-sgn * v_matrix[1, 2], sgn * v_matrix[0, 2]) + yield mu_value, eta, chi, phi - if constraint_name == "mu": # (35) - mu = constraint_value - V = inv(rot_MU(mu)) @ N_lab @ N_phi.T + def _calc_remaining_sample_angles_phi(self, phi_value: float, v_matrix: np.ndarray): + try: + asin_eta = asin(bound(v_matrix[0, 1])) + except AssertionError: + return + if is_small(cos(asin_eta)): + raise DiffcalcException( + "Chi and mu cannot be chosen uniquely " "with eta so close to +/-90." + ) + for eta in [asin_eta, pi - asin_eta]: + sgn = sign(cos(eta)) + mu = atan2(sgn * v_matrix[2, 1], sgn * v_matrix[1, 1]) + chi = atan2(sgn * v_matrix[0, 2], sgn * v_matrix[0, 0]) + yield mu, eta, chi, phi_value + + def _calc_remaining_sample_angles_eta_or_chi( + self, constraint_name: str, constraint_value: float, z_matrix: np.ndarray + ): + if constraint_name == "eta": # (39) + eta = constraint_value + cos_eta = cos(eta) + if is_small(cos_eta): + # TODO: Not likely to happen in real world!? + raise DiffcalcException( + "Chi and mu cannot be chosen uniquely with eta " + "constrained so close to +-90." + ) try: - acos_chi = acos(bound(V[2, 2])) + asin_chi = asin(bound(z_matrix[0, 2] / cos_eta)) except AssertionError: return - if is_small(sin(acos_chi)): - # chi ~= 0 or 180 and therefor phi || eta The solutions for phi - # and eta here will be valid but will be chosen unpredictably. - # Choose eta=0: - # - # tan(phi+eta)=v12/v11 from docs/extensions_to_yous_paper.wxm - chi = acos_chi - eta = 0.0 - phi = atan2(-V[1, 0], V[1, 1]) - logger.debug( - "Eta and phi cannot be chosen uniquely with chi so close " - "to 0 or 180. Returning phi=%.3f and eta=%.3f", - degrees(phi), - degrees(eta), - ) - yield mu, eta, chi, phi - else: - for chi in [acos_chi, -acos_chi]: - sgn = sign(sin(chi)) - phi = atan2(-sgn * V[2, 1], -sgn * V[2, 0]) - eta = atan2(-sgn * V[1, 2], sgn * V[0, 2]) - yield mu, eta, chi, phi + all_eta = [ + eta, + ] + all_chi = [asin_chi, pi - asin_chi] - elif constraint_name == "phi": # (37) - phi = constraint_value - V = N_lab @ inv(N_phi) @ rot_PHI(phi).T + else: # constraint_name == 'chi' # (40) + chi = constraint_value + sin_chi = sin(chi) + if is_small(sin_chi): + raise DiffcalcException( + "Eta and phi cannot be chosen uniquely with chi " + "constrained so close to 0. (Please contact developer " + "if this case is useful for you)" + ) try: - asin_eta = asin(bound(V[0, 1])) + acos_eta = acos(bound(z_matrix[0, 2] / sin_chi)) except AssertionError: return - if is_small(cos(asin_eta)): + all_eta = [acos_eta, -acos_eta] + all_chi = [ + chi, + ] + + for chi, eta in product(all_chi, all_eta): + top_for_mu = z_matrix[2, 2] * sin(eta) * sin(chi) + z_matrix[1, 2] * cos( + chi + ) + bot_for_mu = -z_matrix[2, 2] * cos(chi) + z_matrix[1, 2] * sin(eta) * sin( + chi + ) + if is_small(top_for_mu) and is_small(bot_for_mu): raise DiffcalcException( - "Chi and mu cannot be chosen uniquely " - "with eta so close to +/-90." + "Mu cannot be chosen uniquely as mu || phi with chi so close " + "to +/-90 and eta so close 0 or 180.\nPlease choose " + "a different set of constraints." ) - for eta in [asin_eta, pi - asin_eta]: - sgn = sign(cos(eta)) - mu = atan2(sgn * V[2, 1], sgn * V[1, 1]) - chi = atan2(sgn * V[0, 2], sgn * V[0, 0]) - yield mu, eta, chi, phi - - elif constraint_name in ("eta", "chi"): - if constraint_name == "eta": # (39) - eta = constraint_value - cos_eta = cos(eta) - if is_small(cos_eta): - # TODO: Not likely to happen in real world!? - raise DiffcalcException( - "Chi and mu cannot be chosen uniquely with eta " - "constrained so close to +-90." - ) - try: - asin_chi = asin(bound(Z[0, 2] / cos_eta)) - except AssertionError: - return - all_eta = [ - eta, - ] - all_chi = [asin_chi, pi - asin_chi] - - else: # constraint_name == 'chi' # (40) - chi = constraint_value - sin_chi = sin(chi) - if is_small(sin_chi): - raise DiffcalcException( - "Eta and phi cannot be chosen uniquely with chi " - "constrained so close to 0. (Please contact developer " - "if this case is useful for you)" - ) - try: - acos_eta = acos(bound(Z[0, 2] / sin_chi)) - except AssertionError: - return - all_eta = [acos_eta, -acos_eta] - all_chi = [ - chi, - ] + mu = atan2(-top_for_mu, -bot_for_mu) # (41) - for chi, eta in product(all_chi, all_eta): - top_for_mu = Z[2, 2] * sin(eta) * sin(chi) + Z[1, 2] * cos(chi) - bot_for_mu = -Z[2, 2] * cos(chi) + Z[1, 2] * sin(eta) * sin(chi) - if is_small(top_for_mu) and is_small(bot_for_mu): - # chi == +-90, eta == 0/180 and therefore phi || mu cos(chi) == - # 0 and sin(eta) == 0 Experience shows that even though e.g. - # the z[2, 2] and z[1, 2] values used to calculate mu may be - # basically 0 (1e-34) their ratio in every case tested so far - # still remains valid and using them will result in a phi - # solution that is continuous with neighbouring positions. - # - # We cannot test phi minus mu here unfortunately as the final - # phi and mu solutions have not yet been chosen (they may be - # +-x or 180+-x). Otherwise we could choose a sensible solution - # here if the one found was incorrect. - - # tan(phi+eta)=v12/v11 from extensions_to_yous_paper.wxm - # phi_minus_mu = -atan2(Z[2, 0], Z[1, 1]) - raise DiffcalcException( - "Mu cannot be chosen uniquely as mu || phi with chi so close " - "to +/-90 and eta so close 0 or 180.\nPlease choose " - "a different set of constraints." - ) - mu = atan2(-top_for_mu, -bot_for_mu) # (41) - - top_for_phi = Z[0, 1] * cos(eta) * cos(chi) - Z[0, 0] * sin(eta) - bot_for_phi = Z[0, 1] * sin(eta) + Z[0, 0] * cos(eta) * cos(chi) - if is_small(bot_for_phi) and is_small(top_for_phi): - DiffcalcException( - "Phi cannot be chosen uniquely as mu || phi with chi so close " - "to +/-90 and eta so close 0 or 180.\nPlease choose a " - "different set of constraints." - ) - phi = atan2(top_for_phi, bot_for_phi) # (42) - yield mu, eta, chi, phi - - else: - raise DiffcalcException("Given angle must be one of phi, chi, eta or mu") + top_for_phi = z_matrix[0, 1] * cos(eta) * cos(chi) - z_matrix[0, 0] * sin( + eta + ) + bot_for_phi = z_matrix[0, 1] * sin(eta) + z_matrix[0, 0] * cos(eta) * cos( + chi + ) + if is_small(bot_for_phi) and is_small(top_for_phi): + DiffcalcException( + "Phi cannot be chosen uniquely as mu || phi with chi so close " + "to +/-90 and eta so close 0 or 180.\nPlease choose a " + "different set of constraints." + ) + phi = atan2(top_for_phi, bot_for_phi) # (42) + yield mu, eta, chi, phi def _calc_angles_given_three_sample_constraints( self, @@ -937,7 +964,7 @@ def __get_last_sample_angle(A: float, B: float, C: float) -> List[float]: "Sample orientation cannot be chosen uniquely. Please choose a different set of constraints." ) ks = atan2(A, B) - acos_alp = acos(bound(C / sqrt(A ** 2 + B ** 2))) + acos_alp = acos(bound(C / sqrt(A**2 + B**2))) if is_small(acos_alp): alp_list = [ ks, @@ -974,9 +1001,9 @@ def __get_qaz_value(mu: float, eta: float, chi: float, phi: float) -> float: h0, h1, h2 = h_phi_norm[0, 0], h_phi_norm[1, 0], h_phi_norm[2, 0] if "mu" not in samp_constraints: - eta = self.constraints._sample["eta"] - chi = self.constraints._sample["chi"] - phi = self.constraints._sample["phi"] + eta = self.constraints.sample["eta"] + chi = self.constraints.sample["chi"] + phi = self.constraints.sample["phi"] A = h0 * cos(phi) * sin(chi) + h1 * sin(chi) * sin(phi) - h2 * cos(chi) B = ( @@ -1001,9 +1028,9 @@ def __get_qaz_value(mu: float, eta: float, chi: float, phi: float) -> float: yield mu, delta, nu, eta, chi, phi elif "eta" not in samp_constraints: - mu = self.constraints._sample["mu"] - chi = self.constraints._sample["chi"] - phi = self.constraints._sample["phi"] + mu = self.constraints.sample["mu"] + chi = self.constraints.sample["chi"] + phi = self.constraints.sample["phi"] A = ( -h0 * cos(chi) * cos(mu) * cos(phi) @@ -1033,9 +1060,9 @@ def __get_qaz_value(mu: float, eta: float, chi: float, phi: float) -> float: yield mu, delta, nu, eta, chi, phi elif "chi" not in samp_constraints: - mu = self.constraints._sample["mu"] - eta = self.constraints._sample["eta"] - phi = self.constraints._sample["phi"] + mu = self.constraints.sample["mu"] + eta = self.constraints.sample["eta"] + phi = self.constraints.sample["phi"] A = ( -h2 * cos(mu) * sin(eta) @@ -1066,9 +1093,9 @@ def __get_qaz_value(mu: float, eta: float, chi: float, phi: float) -> float: yield mu, delta, nu, eta, chi, phi elif "phi" not in samp_constraints: - mu = self.constraints._sample["mu"] - eta = self.constraints._sample["eta"] - chi = self.constraints._sample["chi"] + mu = self.constraints.sample["mu"] + eta = self.constraints.sample["eta"] + chi = self.constraints.sample["chi"] A = h1 * sin(chi) * sin(mu) - ( h1 * cos(chi) * sin(eta) + h0 * cos(eta) @@ -1120,29 +1147,26 @@ def _calc_sample_angles_given_two_sample_and_reference( def __get_phi_and_qaz(chi: float, eta: float, mu: float) -> Tuple[float, float]: a = sin(chi) * cos(eta) b = sin(chi) * sin(eta) * sin(mu) - cos(chi) * cos(mu) - sin_qaz = V[2, 0] * a - V[2, 2] * b - cos_qaz = -V[2, 2] * a - V[2, 0] * b - # atan2_xi = atan2(V[2, 2] * a + V[2, 0] * b, - # V[2, 0] * a - V[2, 2] * b) # (54) + sin_qaz = v_matrix[2, 0] * a - v_matrix[2, 2] * b + cos_qaz = -v_matrix[2, 2] * a - v_matrix[2, 0] * b + # atan2_xi = atan2(v_matrix[2, 2] * a + v_matrix[2, 0] * b, + # v_matrix[2, 0] * a - v_matrix[2, 2] * b) # (54) qaz = atan2(sin_qaz, cos_qaz) # (54) a = sin(chi) * sin(mu) - cos(mu) * cos(chi) * sin(eta) b = cos(mu) * cos(eta) - phi = atan2(V[1, 1] * a - V[0, 1] * b, V[0, 1] * a + V[1, 1] * b) # (55) - # if is_small(mu+pi/2) and is_small(eta) and False: - # phi_general = phi - # # solved in extensions_to_yous_paper.wxm - # phi = atan2(V[1, 1], V[0, 1]) - # logger.debug("phi = %.3f or %.3f (std)", - # phi*TODEG, phi_general*TODEG ) + phi = atan2( + v_matrix[1, 1] * a - v_matrix[0, 1] * b, + v_matrix[0, 1] * a + v_matrix[1, 1] * b, + ) return qaz, phi def __get_chi_and_qaz(mu: float, eta: float) -> Tuple[float, float]: A = sin(mu) B = -cos(mu) * sin(eta) - sin_chi = A * V[1, 0] + B * V[1, 2] - cos_chi = B * V[1, 0] - A * V[1, 2] + sin_chi = A * v_matrix[1, 0] + B * v_matrix[1, 2] + cos_chi = B * v_matrix[1, 0] - A * v_matrix[1, 2] if is_small(sin_chi) and is_small(cos_chi): raise DiffcalcException( "Chi cannot be chosen uniquely. Please choose a different set of constraints." @@ -1151,8 +1175,8 @@ def __get_chi_and_qaz(mu: float, eta: float) -> Tuple[float, float]: A = sin(eta) B = cos(eta) * sin(mu) - sin_qaz = A * V[0, 1] + B * V[2, 1] - cos_qaz = B * V[0, 1] - A * V[2, 1] + sin_qaz = A * v_matrix[0, 1] + B * v_matrix[2, 1] + cos_qaz = B * v_matrix[0, 1] - A * v_matrix[2, 1] qaz = atan2(sin_qaz, cos_qaz) return qaz, chi @@ -1167,23 +1191,23 @@ def __get_chi_and_qaz(mu: float, eta: float) -> Tuple[float, float]: CHI = rot_CHI(chi) PHI = rot_PHI(phi) - V = CHI @ PHI @ N_phi @ PSI.T @ THETA.T # (46) + v_matrix = CHI @ PHI @ N_phi @ PSI.T @ THETA.T # (46) - # atan2_xi = atan2(-V[2, 0], V[2, 2]) - # atan2_eta = atan2(-V[0, 1], V[1, 1]) - # atan2_mu = atan2(-V[2, 1], sqrt(V[2, 2] ** 2 + V[2, 0] ** 2)) + # atan2_xi = atan2(-v_matrix[2, 0], v_matrix[2, 2]) + # atan2_eta = atan2(-v_matrix[0, 1], v_matrix[1, 1]) + # atan2_mu = atan2(-v_matrix[2, 1], sqrt(v_matrix[2, 2] ** 2 + v_matrix[2, 0] ** 2)) try: - asin_mu = asin(bound(-V[2, 1])) + asin_mu = asin(bound(-v_matrix[2, 1])) except AssertionError: return for mu in [asin_mu, pi - asin_mu]: sgn_cosmu = sign(cos(mu)) - # xi = atan2(-sgn_cosmu * V[2, 0], sgn_cosmu * V[2, 2]) + # xi = atan2(-sgn_cosmu * v_matrix[2, 0], sgn_cosmu * v_matrix[2, 2]) qaz = atan2( - sgn_cosmu * V[2, 2], - sgn_cosmu * V[2, 0], + sgn_cosmu * v_matrix[2, 2], + sgn_cosmu * v_matrix[2, 0], ) - eta = atan2(-sgn_cosmu * V[0, 1], sgn_cosmu * V[1, 1]) + eta = atan2(-sgn_cosmu * v_matrix[0, 1], sgn_cosmu * v_matrix[1, 1]) yield qaz, psi, mu, eta, chi, phi elif "mu" in samp_constraints and "eta" in samp_constraints: @@ -1191,10 +1215,10 @@ def __get_chi_and_qaz(mu: float, eta: float) -> Tuple[float, float]: mu = samp_constraints["mu"] eta = samp_constraints["eta"] - V = N_phi @ PSI.T @ THETA.T # (49) + v_matrix = N_phi @ PSI.T @ THETA.T # (49) try: bot = bound( - -V[2, 1] / sqrt(sin(eta) ** 2 * cos(mu) ** 2 + sin(mu) ** 2) + -v_matrix[2, 1] / sqrt(sin(eta) ** 2 * cos(mu) ** 2 + sin(mu) ** 2) ) except AssertionError: return @@ -1224,10 +1248,11 @@ def __get_chi_and_qaz(mu: float, eta: float) -> Tuple[float, float]: chi = samp_constraints["chi"] eta = samp_constraints["eta"] - V = N_phi @ PSI.T @ THETA.T # (49) + v_matrix = N_phi @ PSI.T @ THETA.T # (49) try: bot = bound( - -V[2, 1] / sqrt(sin(eta) ** 2 * sin(chi) ** 2 + cos(chi) ** 2) + -v_matrix[2, 1] + / sqrt(sin(eta) ** 2 * sin(chi) ** 2 + cos(chi) ** 2) ) except AssertionError: return @@ -1247,11 +1272,11 @@ def __get_chi_and_qaz(mu: float, eta: float) -> Tuple[float, float]: chi = samp_constraints["chi"] mu = samp_constraints["mu"] - V = N_phi @ PSI.T @ THETA.T # (49) + v_matrix = N_phi @ PSI.T @ THETA.T # (49) try: asin_eta = asin( - bound((-V[2, 1] - cos(chi) * sin(mu)) / (sin(chi) * cos(mu))) + bound((-v_matrix[2, 1] - cos(chi) * sin(mu)) / (sin(chi) * cos(mu))) ) except AssertionError: return @@ -1266,14 +1291,14 @@ def __get_chi_and_qaz(mu: float, eta: float) -> Tuple[float, float]: phi = samp_constraints["phi"] PHI = rot_PHI(phi) - V = THETA @ PSI @ inv(N_phi) @ PHI.T + v_matrix = THETA @ PSI @ inv(N_phi) @ PHI.T if is_small(cos(mu)): raise DiffcalcException( "Eta cannot be chosen uniquely. Please choose a different set of constraints." ) try: - acos_eta = acos(bound(V[1, 1] / cos(mu))) + acos_eta = acos(bound(v_matrix[1, 1] / cos(mu))) except AssertionError: return for eta in [acos_eta, -acos_eta]: @@ -1286,14 +1311,14 @@ def __get_chi_and_qaz(mu: float, eta: float) -> Tuple[float, float]: phi = samp_constraints["phi"] PHI = rot_PHI(phi) - V = THETA @ PSI @ inv(N_phi) @ PHI.T + v_matrix = THETA @ PSI @ inv(N_phi) @ PHI.T if is_small(cos(eta)): raise DiffcalcException( "Mu cannot be chosen uniquely. Please choose a different set of constraints." ) try: - acos_mu = acos(bound(V[1, 1] / cos(eta))) + acos_mu = acos(bound(v_matrix[1, 1] / cos(eta))) except AssertionError: return for mu in [acos_mu, -acos_mu]: @@ -1411,7 +1436,7 @@ def _calc_sample_angles_given_two_sample_and_detector( for mu, eta in product(mu_vals, eta_vals): F = y_rotation(qaz - pi / 2.0) THETA = z_rotation(-theta) - V = rot_ETA(eta).T @ rot_MU(mu).T @ F @ THETA # (56) + v_matrix = rot_ETA(eta).T @ rot_MU(mu).T @ F @ THETA # (56) phi_vals = [] try: @@ -1422,7 +1447,9 @@ def _calc_sample_angles_given_two_sample_and_detector( "vector or phi constraints have been set.\nPlease choose a different " "set of constraints." ) - bot = bound(-V[1, 0] / sqrt(N_phi[0, 0] ** 2 + N_phi[1, 0] ** 2)) + bot = bound( + -v_matrix[1, 0] / sqrt(N_phi[0, 0] ** 2 + N_phi[1, 0] ** 2) + ) eps = atan2(N_phi[1, 0], N_phi[0, 0]) phi_vals = [asin(bot) + eps, pi - asin(bot) + eps] # (59) except AssertionError: @@ -1430,8 +1457,8 @@ def _calc_sample_angles_given_two_sample_and_detector( for phi in phi_vals: a = N_phi[0, 0] * cos(phi) + N_phi[1, 0] * sin(phi) chi = atan2( - N_phi[2, 0] * V[0, 0] - a * V[2, 0], - N_phi[2, 0] * V[2, 0] + a * V[0, 0], + N_phi[2, 0] * v_matrix[0, 0] - a * v_matrix[2, 0], + N_phi[2, 0] * v_matrix[2, 0] + a * v_matrix[0, 0], ) # (60) yield mu, eta, chi, phi @@ -1442,11 +1469,12 @@ def _calc_sample_angles_given_two_sample_and_detector( CHI = rot_CHI(chi) PHI = rot_PHI(phi) - V = CHI @ PHI @ N_phi # (62) + v_matrix = CHI @ PHI @ N_phi # (62) try: bot = bound( - V[2, 0] / sqrt(cos(qaz) ** 2 * cos(theta) ** 2 + sin(theta) ** 2) + v_matrix[2, 0] + / sqrt(cos(qaz) ** 2 * cos(theta) ** 2 + sin(theta) ** 2) ) except AssertionError: return @@ -1454,8 +1482,8 @@ def _calc_sample_angles_given_two_sample_and_detector( for mu in [asin(bot) + eps, pi - asin(bot) + eps]: a = cos(theta) * sin(qaz) b = -cos(theta) * sin(mu) * cos(qaz) + cos(mu) * sin(theta) - X = V[1, 0] * a + V[0, 0] * b - Y = V[0, 0] * a - V[1, 0] * b + X = v_matrix[1, 0] * a + v_matrix[0, 0] * b + Y = v_matrix[0, 0] * a - v_matrix[1, 0] * b if is_small(X) and is_small(Y): raise DiffcalcException( "Eta cannot be chosen uniquely as q || eta and no reference " @@ -1466,7 +1494,7 @@ def _calc_sample_angles_given_two_sample_and_detector( # a = -cos(mu) * cos(qaz) * sin(theta) + sin(mu) * cos(theta) # b = cos(mu) * sin(qaz) - # psi = atan2(-V[2, 2] * a - V[2, 1] * b, V[2, 1] * a - V[2, 2] * b) + # psi = atan2(-v_matrix[2, 2] * a - v_matrix[2, 1] * b, v_matrix[2, 1] * a - v_matrix[2, 2] * b) yield mu, eta, chi, phi elif "mu" in samp_constraints and "phi" in samp_constraints: @@ -1476,18 +1504,19 @@ def _calc_sample_angles_given_two_sample_and_detector( F = y_rotation(qaz - pi / 2.0) THETA = z_rotation(-theta) - V = rot_MU(mu).T @ F @ THETA + v_matrix = rot_MU(mu).T @ F @ THETA E = rot_PHI(phi) @ N_phi try: - bot = bound(-V[2, 0] / sqrt(E[0, 0] ** 2 + E[2, 0] ** 2)) + bot = bound(-v_matrix[2, 0] / sqrt(E[0, 0] ** 2 + E[2, 0] ** 2)) except AssertionError: return eps = atan2(E[2, 0], E[0, 0]) for chi in [asin(bot) + eps, pi - asin(bot) + eps]: a = E[0, 0] * cos(chi) + E[2, 0] * sin(chi) eta = atan2( - V[0, 0] * E[1, 0] - V[1, 0] * a, V[0, 0] * a + V[1, 0] * E[1, 0] + v_matrix[0, 0] * E[1, 0] - v_matrix[1, 0] * a, + v_matrix[0, 0] * a + v_matrix[1, 0] * E[1, 0], ) yield mu, eta, chi, phi @@ -1513,7 +1542,7 @@ def _calc_sample_angles_given_two_sample_and_detector( acos_phi = acos( bound( (N_phi[2, 0] * cos(chi) - V20) - / (sin(chi) * sqrt(A ** 2 + B ** 2)) + / (sin(chi) * sqrt(A**2 + B**2)) ) ) except AssertionError: @@ -1556,13 +1585,14 @@ def _calc_sample_angles_given_two_sample_and_detector( "set of constraints." ) - V = (N_phi[1, 0] * cos(phi) - N_phi[0, 0] * sin(phi)) * tan(eta) + v_matrix = (N_phi[1, 0] * cos(phi) - N_phi[0, 0] * sin(phi)) * tan(eta) sgn = sign(cos(eta)) eps = atan2(X * sgn, Y * sgn) try: acos_rhs = acos( bound( - (sin(qaz) * cos(theta) / cos(eta) - V) / sqrt(X ** 2 + Y ** 2) + (sin(qaz) * cos(theta) / cos(eta) - v_matrix) + / sqrt(X**2 + Y**2) ) ) except AssertionError: @@ -1605,7 +1635,7 @@ def _calc_sample_angles_given_two_sample_and_detector( acos_V00 = acos( bound( (cos(theta) * sin(qaz) - N_phi[2, 0] * cos(eta) * sin(chi)) - / sqrt(A ** 2 + B ** 2) + / sqrt(A**2 + B**2) ) ) except AssertionError: @@ -1662,12 +1692,12 @@ def _tidy_degenerate_solutions( self, pos: Position, print_degenerate: bool = False ) -> Position: - detector_like_constraint = self.constraints._detector or self.constraints.naz + detector_like_constraint = self.constraints.detector or self.constraints.naz nu_constrained_to_0 = is_small(pos.nu) and detector_like_constraint - mu_constrained_to_0 = is_small(pos.mu) and "mu" in self.constraints._sample + mu_constrained_to_0 = is_small(pos.mu) and "mu" in self.constraints.sample delta_constrained_to_0 = is_small(pos.delta) and detector_like_constraint - eta_constrained_to_0 = is_small(pos.eta) and "eta" in self.constraints._sample - phi_not_constrained = "phi" not in self.constraints._sample + eta_constrained_to_0 = is_small(pos.eta) and "eta" in self.constraints.sample + phi_not_constrained = "phi" not in self.constraints.sample if ( nu_constrained_to_0 @@ -1785,3 +1815,24 @@ def _verify_virtual_angles( "anglesToVirtualAngles of %f" % virtual_angles_readback[key] ) raise DiffcalcException(s) + + +test = UBCalculation("test") +test.set_lattice(name="test", a=4.913, c=5.405) +test.add_reflection( + hkl=(0, 0, 1), + position=Position(7.31, 0, 10.62, 0, 0, 0), + energy=12.39842, + tag="refl1", +) +test.add_orientation(hkl=(0, 1, 0), xyz=(0, 1, 0), tag="plane") +test.n_hkl = (1.0, 0.0, 0.0) + +test.calc_ub("refl1", "plane") + + +# hklcalc = HklCalculation(test, Constraints({"delta": 1, "chi": 1, "phi": 1})) +hklcalc = HklCalculation(test, Constraints({"qaz": 0, "alpha": 0, "eta": 0})) + + +resultss = hklcalc.get_position(0, 0, 1, 0.1) diff --git a/src/diffcalc/hkl/constraints.py b/src/diffcalc/hkl/constraints.py index ce8c41c..849d48d 100644 --- a/src/diffcalc/hkl/constraints.py +++ b/src/diffcalc/hkl/constraints.py @@ -137,11 +137,11 @@ def __str__(self) -> str: return "\n".join(lines) @property - def _constrained(self): + def constrained(self): return tuple(con for con in self._all if con.active) @property - def _detector(self): + def detector(self): return { con.name: con.value for con in self._all @@ -149,7 +149,7 @@ def _detector(self): } @property - def _reference(self): + def reference(self): return { con.name: con.value for con in self._all @@ -157,7 +157,7 @@ def _reference(self): } @property - def _sample(self): + def sample(self): return { con.name: con.value for con in self._all @@ -210,7 +210,7 @@ def astuple(self) -> Tuple[Union[Tuple[str, float], str], ...]: Tuple with all constrained angle names and values. """ res = [] - for con in self._constrained: + for con in self.constrained: if con._type is _con_type.VALUE: res.append((con.name, getattr(self, con.name))) elif con._type is _con_type.VOID: @@ -289,7 +289,7 @@ def _set_constraint(val: Union[float, bool, None]) -> None: if val is None or val is False: con.value = None return - active_con = {c for c in self._constrained if c._category is con._category} + active_con = {c for c in self.constrained if c._category is con._category} num_active_con = len(active_con) if con in active_con: _set_value(val) @@ -304,14 +304,14 @@ def _set_constraint(val: Union[float, bool, None]) -> None: # We don't know which one we should replace. raise DiffcalcException( f"Cannot set {con.name} constraint. First un-constrain one of the\n" - f"angles {', '.join(sorted(c.name for c in self._constrained))}." + f"angles {', '.join(sorted(c.name for c in self.constrained))}." ) elif num_active_con == 0: # We need to replace a constraint from other category. # We don't know which one to replace raise DiffcalcException( f"Cannot set {con.name} constraint. First un-constrain one of the\n" - f"angles {', '.join(sorted(c.name for c in self._constrained))}." + f"angles {', '.join(sorted(c.name for c in self.constrained))}." ) elif num_active_con == 1: # If we have only one constraint in the requested category. @@ -596,7 +596,7 @@ def _report_constraint(self, con: _Constraint) -> str: def _report_constraints_lines(self) -> List[str]: lines = [] - required = 3 - len(self._constrained) + required = 3 - len(self.constrained) if required == 0: pass elif required == 1: @@ -659,7 +659,7 @@ def is_fully_constrained(self, con: Optional[_Constraint] = None) -> bool: constraint category or no constraints are available. """ if con is None: - return len(self._constrained) >= 3 + return len(self.constrained) >= 3 _max_constrained = { _con_category.DETECTOR: 1, @@ -667,7 +667,7 @@ def is_fully_constrained(self, con: Optional[_Constraint] = None) -> bool: _con_category.SAMPLE: 3, } count_constrained = len( - {c for c in self._constrained if c._category is con._category} + {c for c in self.constrained if c._category is con._category} ) return count_constrained >= _max_constrained[con._category] @@ -685,50 +685,50 @@ def is_current_mode_implemented(self) -> bool: raise ValueError("Three constraints required") count_detector = len( - {c for c in self._constrained if c._category is _con_category.DETECTOR} + {c for c in self.constrained if c._category is _con_category.DETECTOR} ) count_reference = len( - {c for c in self._constrained if c._category is _con_category.REFERENCE} + {c for c in self.constrained if c._category is _con_category.REFERENCE} ) count_sample = len( - {c for c in self._constrained if c._category is _con_category.SAMPLE} + {c for c in self.constrained if c._category is _con_category.SAMPLE} ) if count_sample == 3: if ( - set(self._constrained) == {self._chi, self._phi, self._eta} - or set(self._constrained) == {self._chi, self._phi, self._mu} - or set(self._constrained) == {self._chi, self._eta, self._mu} - or set(self._constrained) == {self._phi, self._eta, self._mu} + set(self.constrained) == {self._chi, self._phi, self._eta} + or set(self.constrained) == {self._chi, self._phi, self._mu} + or set(self.constrained) == {self._chi, self._eta, self._mu} + or set(self.constrained) == {self._phi, self._eta, self._mu} ): return True return False if count_sample == 1: - return self._omega not in set( - self._constrained - ) and self._bisect not in set(self._constrained) + return self._omega not in set(self.constrained) and self._bisect not in set( + self.constrained + ) if count_reference == 1: return ( - {self._chi, self._phi}.issubset(self._constrained) - or {self._chi, self._eta}.issubset(self._constrained) - or {self._chi, self._mu}.issubset(self._constrained) - or {self._mu, self._eta}.issubset(self._constrained) - or {self._mu, self._phi}.issubset(self._constrained) - or {self._eta, self._phi}.issubset(self._constrained) + {self._chi, self._phi}.issubset(self.constrained) + or {self._chi, self._eta}.issubset(self.constrained) + or {self._chi, self._mu}.issubset(self.constrained) + or {self._mu, self._eta}.issubset(self.constrained) + or {self._mu, self._phi}.issubset(self.constrained) + or {self._eta, self._phi}.issubset(self.constrained) ) if count_detector == 1: return ( - {self._chi, self._phi}.issubset(self._constrained) - or {self._mu, self._eta}.issubset(self._constrained) - or {self._mu, self._phi}.issubset(self._constrained) - or {self._mu, self._chi}.issubset(self._constrained) - or {self._eta, self._phi}.issubset(self._constrained) - or {self._eta, self._chi}.issubset(self._constrained) - or {self._mu, self._bisect}.issubset(self._constrained) - or {self._eta, self._bisect}.issubset(self._constrained) - or {self._omega, self._bisect}.issubset(self._constrained) + {self._chi, self._phi}.issubset(self.constrained) + or {self._mu, self._eta}.issubset(self.constrained) + or {self._mu, self._phi}.issubset(self.constrained) + or {self._mu, self._chi}.issubset(self.constrained) + or {self._eta, self._phi}.issubset(self.constrained) + or {self._eta, self._chi}.issubset(self.constrained) + or {self._mu, self._bisect}.issubset(self.constrained) + or {self._eta, self._bisect}.issubset(self.constrained) + or {self._omega, self._bisect}.issubset(self.constrained) ) return False From 18c4dd7fc0ed4de7e0248b27724702821bb0f597 Mon Sep 17 00:00:00 2001 From: Rose Yemelyanova Date: Mon, 25 Jul 2022 15:43:55 +0000 Subject: [PATCH 03/13] fixed some flake8 errors --- src/diffcalc/hkl/calc.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/diffcalc/hkl/calc.py b/src/diffcalc/hkl/calc.py index d63f61d..613efe7 100644 --- a/src/diffcalc/hkl/calc.py +++ b/src/diffcalc/hkl/calc.py @@ -1815,24 +1815,3 @@ def _verify_virtual_angles( "anglesToVirtualAngles of %f" % virtual_angles_readback[key] ) raise DiffcalcException(s) - - -test = UBCalculation("test") -test.set_lattice(name="test", a=4.913, c=5.405) -test.add_reflection( - hkl=(0, 0, 1), - position=Position(7.31, 0, 10.62, 0, 0, 0), - energy=12.39842, - tag="refl1", -) -test.add_orientation(hkl=(0, 1, 0), xyz=(0, 1, 0), tag="plane") -test.n_hkl = (1.0, 0.0, 0.0) - -test.calc_ub("refl1", "plane") - - -# hklcalc = HklCalculation(test, Constraints({"delta": 1, "chi": 1, "phi": 1})) -hklcalc = HklCalculation(test, Constraints({"qaz": 0, "alpha": 0, "eta": 0})) - - -resultss = hklcalc.get_position(0, 0, 1, 0.1) From fbc303ceb93b1d914ffb67be35952d9caa9441cb Mon Sep 17 00:00:00 2001 From: Rose Yemelyanova Date: Mon, 25 Jul 2022 15:45:34 +0000 Subject: [PATCH 04/13] removed unnecessary imports --- src/diffcalc/hkl/calc.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/diffcalc/hkl/calc.py b/src/diffcalc/hkl/calc.py index 613efe7..6c85510 100644 --- a/src/diffcalc/hkl/calc.py +++ b/src/diffcalc/hkl/calc.py @@ -9,7 +9,6 @@ from typing import Callable, Dict, Iterable, Iterator, List, Optional, Tuple import numpy as np -from diffcalc.hkl.constraints import Constraints from diffcalc.hkl.geometry import ( Position, get_rotation_matrices, @@ -19,7 +18,6 @@ rot_PHI, ) from diffcalc.log import logging -from diffcalc.ub.calc import UBCalculation from diffcalc.util import ( SMALL, DiffcalcException, From 1fa65a9b820bf6143a85ae9bb45139decb5e9dcf Mon Sep 17 00:00:00 2001 From: Rose Yemelyanova Date: Mon, 25 Jul 2022 16:11:39 +0000 Subject: [PATCH 05/13] made some changes to error raising and removed some comments --- src/diffcalc/hkl/calc.py | 62 ++++++++++++--------------------- src/diffcalc/hkl/constraints.py | 7 ++-- src/diffcalc/ub/calc.py | 30 +++++++--------- src/diffcalc/ub/crystal.py | 20 ++++++----- src/diffcalc/ub/fitting.py | 2 +- src/diffcalc/ub/reference.py | 40 ++++----------------- 6 files changed, 59 insertions(+), 102 deletions(-) diff --git a/src/diffcalc/hkl/calc.py b/src/diffcalc/hkl/calc.py index 6c85510..3307904 100644 --- a/src/diffcalc/hkl/calc.py +++ b/src/diffcalc/hkl/calc.py @@ -132,19 +132,13 @@ def get_virtual_angles( n_lab = z_matrix @ self.ubcalc.n_phi alpha = asin(bound(-n_lab[1, 0])) - naz = atan2(n_lab[0, 0], n_lab[2, 0]) # (20) + naz = atan2(n_lab[0, 0], n_lab[2, 0]) cos_tau = cos(alpha) * cos(theta) * cos(naz - qaz) + sin(alpha) * sin(theta) - tau = acos(bound(cos_tau)) # (23) - - # Compute Tau using the dot product directly (THIS ALSO WORKS) - # q_lab = ( (NU @ DELTA - I ) @ np.array([[0],[1],[0]]) - # norm = norm(q_lab) - # q_lab = np.array([[1],[0],[0]]) if norm == 0 else q_lab * (1/norm) - # tau_from_dot_product = acos(bound(dot3(q_lab, n_lab))) + tau = acos(bound(cos_tau)) sin_beta = 2 * sin(theta) * cos(tau) - sin(alpha) - beta = asin(bound(sin_beta)) # (24) + beta = asin(bound(sin_beta)) psi = next(self._calc_psi(alpha, theta, tau, qaz, naz)) @@ -212,12 +206,6 @@ def get_position( return results def _check_constrained(self): - if not self.constraints.is_fully_constrained(): - raise DiffcalcException( - "Diffcalc is not fully constrained.\n" - "Type 'help con' for instructions" - ) - if not self.constraints.is_current_mode_implemented(): raise DiffcalcException( "Sorry, the selected constraint combination is valid but " @@ -247,18 +235,14 @@ def _verify_angles( def _calc_hkl_to_position( self, h: float, k: float, l: float, wavelength: float ) -> List[Tuple[Position, Dict[str, float]]]: - self._check_constrained() # is hkl constrained? + self._check_constrained() - # constraints are dictionaries ref_constraint = self.constraints.reference det_constraint = self.constraints.detector samp_constraints = self.constraints.sample - # get necessary angles h_phi = self.ubcalc.UB @ np.array([[h], [k], [l]]) - theta = ( - self.ubcalc.get_ttheta_from_hkl((h, k, l), 12.39842 / wavelength) / 2.0 - ) # __calc_theta(h_phi, wavelength) + theta = self.ubcalc.get_ttheta_from_hkl((h, k, l), 12.39842 / wavelength) / 2.0 tau = angle_between_vectors(h_phi, self.ubcalc.n_phi) surf_tau = angle_between_vectors(h_phi, self.ubcalc.surf_nphi) @@ -282,7 +266,7 @@ def _calc_hkl_to_position( solution_tuples = [] if det_constraint: - if len(samp_constraints) == 1: # i.e. you have one reference constraint + if len(samp_constraints) == 1: for ( qaz, naz, @@ -400,7 +384,7 @@ def _create_position_pseudo_angles_pairs( constraint_value, pseudo_angles[constraint_name] ) position_pseudo_angles_pairs.append((position, pseudo_angles)) - except AssertionError: + except AssertionError: # why? continue return position_pseudo_angles_pairs @@ -409,9 +393,9 @@ def _calc_N(self, Q: np.ndarray, n: np.ndarray) -> np.ndarray: Q = normalised(Q) n = normalised(n) if is_small(angle_between_vectors(Q, n)): - # Replace the reference vector with an alternative vector from Eq.(78) + def __key_func(v): - return v[1] # Workaround for mypy issue #9590 + return v[1] idx_min, _ = min( enumerate([abs(Q[0, 0]), abs(Q[1, 0]), abs(Q[2, 0])]), @@ -441,14 +425,17 @@ def __key_func(v): def _calc_angle_between_naz_and_qaz( self, theta: float, alpha: float, tau: float ) -> float: - # Equation 30: top = cos(tau) - sin(alpha) * sin(theta) bottom = cos(alpha) * cos(theta) if is_small(bottom): if is_small(cos(alpha)): - raise ValueError("cos(alpha) is too small") + raise DiffcalcException( + "cos(alpha) is too small: _calc_angle_between_naz_and_qaz." + ) if is_small(cos(theta)): - raise ValueError("cos(theta) is too small") + raise DiffcalcException( + "cos(theta) is too small: _calc_angle_between_naz_and_qaz." + ) if is_small(sin(tau)): return 0.0 return acos(bound(top / bottom)) @@ -465,13 +452,10 @@ def _calc_psi( sin_tau = sin(tau) cos_theta = cos(theta) if is_small(sin_tau): - # The reference vector is parallel to the scattering vector yield float("nan") elif is_small(cos_theta): - # Reflection is unreachable as theta angle is too close to 90 deg yield float("nan") elif is_small(sin(theta)): - # Reflection is unreachable as |Q| is too small yield float("nan") else: cos_psi = (cos(tau) * sin(theta) - sin(alpha)) / cos_theta # (28) @@ -553,7 +537,7 @@ def _calc_det_angles_given_det_constraint( try: naz_qaz_angle = self._calc_angle_between_naz_and_qaz(theta, alpha, tau) except AssertionError: - return + return # why? if "naz" in list(det_constraint.keys()): naz_value = det_constraint["naz"] @@ -630,7 +614,7 @@ def _calc_remaining_detector_angles_delta_constrained( try: asin_qaz = asin(bound(sin(delta_value) / sin_2theta)) except AssertionError: - return + return #why? cos_delta = cos(delta_value) if is_small(cos_delta): @@ -646,7 +630,7 @@ def _calc_remaining_detector_angles_delta_constrained( try: acos_nu = acos(bound(cos_2theta / cos_delta)) except AssertionError: - return + return #why? qaz_angles = ( [ @@ -686,7 +670,7 @@ def _calc_remaining_detector_angles_nu_constrained( acos_delta = acos(bound(cos_delta)) acos_qaz = acos(bound(cos_qaz)) except AssertionError: - return + return #why? qaz_angles = ( [ @@ -714,8 +698,8 @@ def _calc_remaining_detector_angles_qaz_constrained( ) -> Iterable[Tuple[float, float, float]]: try: asin_delta = asin(sin(qaz_value) * sin_2theta) - except AssertionError: - return + except AssertionError: + return #why? if is_small(cos(asin_delta)): delta_angles = [ @@ -741,7 +725,7 @@ def _calc_remaining_detector_angles_qaz_constrained( sgn_delta * sin_2theta * cos(qaz_value), sgn_delta * cos_2theta ) except AssertionError: - return + return #why? yield delta, nu, qaz_value @@ -842,7 +826,7 @@ def _calc_remaining_sample_angles_mu(self, mu_value: float, v_matrix: np.ndarray try: acos_chi = acos(bound(v_matrix[2, 2])) except AssertionError: - return + return #why? if is_small(sin(acos_chi)): # chi ~= 0 or 180 and therefor phi || eta The solutions for phi # and eta here will be valid but will be chosen unpredictably. diff --git a/src/diffcalc/hkl/constraints.py b/src/diffcalc/hkl/constraints.py index 849d48d..e7df1b5 100644 --- a/src/diffcalc/hkl/constraints.py +++ b/src/diffcalc/hkl/constraints.py @@ -674,7 +674,7 @@ def is_fully_constrained(self, con: Optional[_Constraint] = None) -> bool: def is_current_mode_implemented(self) -> bool: """Check if current constraint set is implemented. - Configuration needs to be fully constraint for this method to work. + Configuration needs to be fully constrained for this method to work. Returns ------- @@ -682,7 +682,10 @@ def is_current_mode_implemented(self) -> bool: True if current constraint set is supported. """ if not self.is_fully_constrained(): - raise ValueError("Three constraints required") + raise DiffcalcException( + "Diffcalc is not fully constrained.\n" + "Type 'help con' for instructions" + ) count_detector = len( {c for c in self.constrained if c._category is _con_category.DETECTOR} diff --git a/src/diffcalc/ub/calc.py b/src/diffcalc/ub/calc.py index 7475f96..6791a85 100644 --- a/src/diffcalc/ub/calc.py +++ b/src/diffcalc/ub/calc.py @@ -425,12 +425,12 @@ def set_lattice( Crystal lattice angle. """ if not isinstance(name, str): - raise TypeError("Invalid crystal name.") + raise DiffcalcException("Invalid crystal name.") shortform: Tuple[Any, ...] = tuple( val for val in (system, a, b, c, alpha, beta, gamma) if val is not None ) if not shortform: - raise TypeError("Please specify unit cell parameters.") + raise DiffcalcException("Please specify unit cell parameters.") elif allnum(shortform): sf = shortform if len(sf) == 1: @@ -608,10 +608,7 @@ def get_number_reflections(self) -> int: int: Number of reference reflections. """ - try: - return len(self.reflist) - except TypeError: - return 0 + return len(self.reflist) def get_tag_refl_num(self, tag: str) -> int: """Get a reference reflection index. @@ -628,8 +625,6 @@ def get_tag_refl_num(self, tag: str) -> int: int: Reference reflection index """ - if tag is None: - raise IndexError("Reflection tag is None") return self.reflist.get_tag_index(tag) + 1 def del_reflection(self, idx: Union[str, int]) -> None: @@ -673,8 +668,6 @@ def add_orientation(self, hkl, xyz, position=None, tag=None): tag : str identifying tag for the reflection """ - if self.orientlist is None: - raise DiffcalcException("No UBCalculation loaded") if position is None: position = Position() self.orientlist.add_orientation(hkl, xyz, position, tag) @@ -730,7 +723,7 @@ def get_number_orientations(self): except TypeError: return 0 - def get_tag_orient_num(self, tag): + def get_tag_orient_num(self, tag: str): """Get a reference orientation index. Get a reference orientation index for the @@ -745,8 +738,6 @@ def get_tag_orient_num(self, tag): int: Reference orientation index """ - if tag is None: - raise IndexError("Orientations tag is None") return self.orientlist.get_tag_index(tag) + 1 def del_orientation(self, idx): @@ -806,7 +797,7 @@ def set_u( """ m = np.array(matrix, dtype=float) if len(m.shape) != 2 or m.shape[0] != 3 or m.shape[1] != 3: - raise TypeError("set_u expects (3, 3) NumPy matrix.") + raise DiffcalcException("set_u expects (3, 3) NumPy matrix.") if self.UB is None: print("Calculating UB matrix.") @@ -847,7 +838,7 @@ def set_ub( """ m = np.array(matrix, dtype=float) if len(m.shape) != 2 or m.shape[0] != 3 or m.shape[1] != 3: - raise TypeError("set_ub expects (3, 3) NumPy matrix.") + raise DiffcalcException("set_ub expects (3, 3) NumPy matrix.") self.UB = m @@ -1318,7 +1309,10 @@ def get_ttheta_from_hkl(self, hkl: Tuple[float, float, float], en: float) -> flo Raises ------ - ValueError + DiffcalcException + If no lattice parameters specified + + DiffcalcException If reflection is unreachable at the provided energy. """ if self.crystal is None: @@ -1328,14 +1322,14 @@ def get_ttheta_from_hkl(self, hkl: Tuple[float, float, float], en: float) -> flo wl = 12.39842 / en d = self.crystal.get_hkl_plane_distance(hkl) if wl > (2 * d): - raise ValueError( + raise DiffcalcException( "Reflection un-reachable as wavelength (%f) is more than twice\n" "the plane distance (%f)" % (wl, d) ) try: return 2.0 * asin(wl / (d * 2)) except ValueError as e: - raise ValueError( + raise DiffcalcException( f"asin(wl / (d * 2) with wl={wl:f} and d={d:f}: " + e.args[0] ) diff --git a/src/diffcalc/ub/crystal.py b/src/diffcalc/ub/crystal.py index 2bd5c79..5755299 100644 --- a/src/diffcalc/ub/crystal.py +++ b/src/diffcalc/ub/crystal.py @@ -7,7 +7,7 @@ from typing import List, Optional, Tuple import numpy as np -from diffcalc.util import allnum, angle_between_vectors, zero_round +from diffcalc.util import DiffcalcException, allnum, angle_between_vectors, zero_round from numpy.linalg import inv @@ -78,7 +78,7 @@ def __init__( ) if allnum(args): if len(args) != 6: - raise ValueError( + raise DiffcalcException( "Crystal definition requires six lattice " "parameters or crystal system name." ) @@ -93,12 +93,14 @@ def __init__( ) else: if not isinstance(args[0], str): - raise ValueError(f"Invalid crystal system name {args[0]}.") + raise DiffcalcException(f"Invalid crystal system name {args[0]}.") self.system = args[0] if allnum(args[1:]): self._set_cell_for_system(system, a, b, c, alpha, beta, gamma) else: - raise ValueError("Crystal lattice parameters must be numeric type.") + raise DiffcalcException( + "Crystal lattice parameters must be numeric type." + ) def __str__(self) -> str: """Represent the crystal lattice information as a string. @@ -255,11 +257,11 @@ def get_lattice_params(self) -> Tuple[str, Tuple[float, ...]]: elif self.system == "Cubic": return self.system, (self.a1,) else: - raise TypeError( + raise DiffcalcException( "Invalid crystal system parameter: %s" % str(self.system) ) except ValueError as e: - raise TypeError from e + raise DiffcalcException from e def _get_cell_for_system( self, system: str @@ -293,7 +295,9 @@ def _get_cell_for_system( elif system == "Cubic": return (self.a1, self.a1, self.a1, pi / 2, pi / 2, pi / 2) else: - raise TypeError("Invalid crystal system parameter: %s" % str(system)) + raise DiffcalcException( + "Invalid crystal system parameter: %s" % str(system) + ) def _set_cell_for_system( self, @@ -329,7 +333,7 @@ def _set_cell_for_system( else: raise TypeError("Invalid crystal system parameter: %s" % str(system)) except ValueError as e: - raise TypeError from e + raise DiffcalcException from e ( self.a1, self.a2, diff --git a/src/diffcalc/ub/fitting.py b/src/diffcalc/ub/fitting.py index 6fbc1d6..11c2ce5 100644 --- a/src/diffcalc/ub/fitting.py +++ b/src/diffcalc/ub/fitting.py @@ -143,7 +143,7 @@ def _get_uc_upper_limits(system: str) -> List[float]: max_unit, ] else: - raise TypeError("Invalid crystal system parameter: %s" % str(system)) + raise DiffcalcException("Invalid crystal system parameter: %s" % str(system)) def fit_crystal(crystal: Crystal, refl_list: List[Reflection]) -> Crystal: diff --git a/src/diffcalc/ub/reference.py b/src/diffcalc/ub/reference.py index 9d7ff00..f88c8ac 100644 --- a/src/diffcalc/ub/reference.py +++ b/src/diffcalc/ub/reference.py @@ -32,17 +32,6 @@ class Reflection: energy: float tag: str - def __post_init__(self): - """Check input argument types. - - Raises - ------ - TypeError - If pos argument has invalid type. - """ - if not isinstance(self.pos, Position): - raise TypeError(f"Invalid position object type {type(self.pos)}.") - @property def astuple( self, @@ -159,10 +148,8 @@ def edit_reflection( num = self.get_tag_index(idx) else: num = idx - 1 - if isinstance(pos, Position): - self.reflections[num] = Reflection(*hkl, pos, energy, tag) - else: - raise TypeError("Invalid position parameter type") + + self.reflections[num] = Reflection(*hkl, pos, energy, tag) def get_reflection(self, idx: Union[str, int]) -> Reflection: """Get a reference reflection. @@ -324,17 +311,6 @@ class Orientation: pos: Position tag: str - def __post_init__(self): - """Check input argument types. - - Raises - ------ - TypeError - If pos argument has invalid type. - """ - if not isinstance(self.pos, Position): - raise TypeError(f"Invalid position object type {type(self.pos)}.") - @property def astuple( self, @@ -418,10 +394,8 @@ def add_orientation( tag : str identifying tag for the orientation. """ - if isinstance(pos, Position): - self.orientations += [Orientation(*hkl, *xyz, pos, tag)] - else: - raise TypeError("Invalid position parameter type") + + self.orientations += [Orientation(*hkl, *xyz, pos, tag)] def edit_orientation( self, @@ -460,10 +434,8 @@ def edit_orientation( num = self.get_tag_index(idx) else: num = idx - 1 - if isinstance(pos, Position): - self.orientations[num] = Orientation(*hkl, *xyz, pos, tag) - else: - raise TypeError(f"Invalid position parameter type {type(pos)}") + + self.orientations[num] = Orientation(*hkl, *xyz, pos, tag) def get_orientation(self, idx: Union[str, int]) -> Orientation: """Get a reference orientation. From 0c5ce3bd906a889170c86973a6fd5de6c743aee7 Mon Sep 17 00:00:00 2001 From: Rose Yemelyanova Date: Tue, 26 Jul 2022 08:04:53 +0000 Subject: [PATCH 06/13] fixed small mypy errors with comments --- src/diffcalc/hkl/calc.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/diffcalc/hkl/calc.py b/src/diffcalc/hkl/calc.py index 3307904..4d5ffaf 100644 --- a/src/diffcalc/hkl/calc.py +++ b/src/diffcalc/hkl/calc.py @@ -614,7 +614,7 @@ def _calc_remaining_detector_angles_delta_constrained( try: asin_qaz = asin(bound(sin(delta_value) / sin_2theta)) except AssertionError: - return #why? + return # why? cos_delta = cos(delta_value) if is_small(cos_delta): @@ -630,7 +630,7 @@ def _calc_remaining_detector_angles_delta_constrained( try: acos_nu = acos(bound(cos_2theta / cos_delta)) except AssertionError: - return #why? + return # why? qaz_angles = ( [ @@ -670,7 +670,7 @@ def _calc_remaining_detector_angles_nu_constrained( acos_delta = acos(bound(cos_delta)) acos_qaz = acos(bound(cos_qaz)) except AssertionError: - return #why? + return # why? qaz_angles = ( [ @@ -698,8 +698,8 @@ def _calc_remaining_detector_angles_qaz_constrained( ) -> Iterable[Tuple[float, float, float]]: try: asin_delta = asin(sin(qaz_value) * sin_2theta) - except AssertionError: - return #why? + except AssertionError: + return # why? if is_small(cos(asin_delta)): delta_angles = [ @@ -725,7 +725,7 @@ def _calc_remaining_detector_angles_qaz_constrained( sgn_delta * sin_2theta * cos(qaz_value), sgn_delta * cos_2theta ) except AssertionError: - return #why? + return # why? yield delta, nu, qaz_value @@ -826,7 +826,7 @@ def _calc_remaining_sample_angles_mu(self, mu_value: float, v_matrix: np.ndarray try: acos_chi = acos(bound(v_matrix[2, 2])) except AssertionError: - return #why? + return # why? if is_small(sin(acos_chi)): # chi ~= 0 or 180 and therefor phi || eta The solutions for phi # and eta here will be valid but will be chosen unpredictably. From 327879c176ed119c85ca09f527ed41d9b0b903f4 Mon Sep 17 00:00:00 2001 From: Rose Yemelyanova Date: Tue, 26 Jul 2022 08:29:51 +0000 Subject: [PATCH 07/13] added fromdict and asdict methods to hkl and nearly all of its child classes to make HklCalculation serializable --- src/diffcalc/hkl/calc.py | 16 ++++++++++- src/diffcalc/ub/calc.py | 40 +++++++++++++++++++++++++- src/diffcalc/ub/crystal.py | 20 ++++++++++++- src/diffcalc/ub/reference.py | 56 +++++++++++++++++++++++++++++++++++- 4 files changed, 128 insertions(+), 4 deletions(-) diff --git a/src/diffcalc/hkl/calc.py b/src/diffcalc/hkl/calc.py index 4d5ffaf..575be1d 100644 --- a/src/diffcalc/hkl/calc.py +++ b/src/diffcalc/hkl/calc.py @@ -6,9 +6,10 @@ from copy import copy from itertools import product from math import acos, asin, atan, atan2, cos, degrees, isnan, pi, sin, sqrt, tan -from typing import Callable, Dict, Iterable, Iterator, List, Optional, Tuple +from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple import numpy as np +from diffcalc.hkl.constraints import Constraints from diffcalc.hkl.geometry import ( Position, get_rotation_matrices, @@ -18,6 +19,7 @@ rot_PHI, ) from diffcalc.log import logging +from diffcalc.ub.calc import UBCalculation from diffcalc.util import ( SMALL, DiffcalcException, @@ -1797,3 +1799,15 @@ def _verify_virtual_angles( "anglesToVirtualAngles of %f" % virtual_angles_readback[key] ) raise DiffcalcException(s) + + @property + def asdict(self) -> Dict[str, Any]: + return {"ubcalc": self.ubcalc.asdict, "constraints": self.constraints.asdict} + + @classmethod + def fromdict(cls, data: Dict[str, Any]) -> "HklCalculation": + constraint_data = data["constraints"] + return HklCalculation( + UBCalculation.fromdict(data["ubcalc"]), + Constraints(constraint_data), + ) diff --git a/src/diffcalc/ub/calc.py b/src/diffcalc/ub/calc.py index 6791a85..f41ef96 100644 --- a/src/diffcalc/ub/calc.py +++ b/src/diffcalc/ub/calc.py @@ -10,7 +10,7 @@ from copy import deepcopy from itertools import product from math import acos, asin, cos, degrees, pi, radians, sin -from typing import Any, List, Optional, Sequence, Tuple, Union +from typing import Any, Dict, List, Optional, Sequence, Tuple, Union import numpy as np from diffcalc.hkl.geometry import Position, get_q_phi, get_rotation_matrices @@ -131,6 +131,14 @@ def set_array(self, n_ref: np.ndarray) -> None: (r1, r2, r3) = tuple(n_ref.T[0].tolist()) self.n_ref = (r1, r2, r3) + @property + def asdict(self) -> Dict[str, Any]: + return self.__dict__.copy() + + @classmethod + def fromdict(cls, data: Dict[str, Any]) -> "ReferenceVector": + return cls(**data) + class UBCalculation: """Class containing information required for for UB matrix calculation. @@ -1374,3 +1382,33 @@ def _rescale_unit_cell( alpha2, alpha3, ) + + @property + def asdict(self) -> Dict[str, Any]: + return { + "name": self.name, + "crystal": self.crystal.asdict if self.crystal is not None else None, + "reflist": self.reflist.asdict, + "orientlist": self.orientlist.asdict, + "reference": self.reference.asdict, + "surface": self.surface.asdict, + "u_matrix": self.U.tolist() if self.U is not None else None, + "ub_matrix": self.UB.tolist() if self.UB is not None else None, + } + + @classmethod + def fromdict(cls, data: Dict[str, Any]) -> "UBCalculation": + # need to return exactly the same object. + ubcalc = cls(data["name"]) + ubcalc.crystal = ( + Crystal.fromdict(data["crystal"]) if data["crystal"] is not None else None + ) + ubcalc.reflist = ReflectionList.fromdict(data["reflist"]) + ubcalc.orientlist = OrientationList.fromdict(data["orientlist"]) + ubcalc.reference = ReferenceVector(**data["reference"]) + ubcalc.surface = ReferenceVector(**data["surface"]) + ubcalc.U = np.array(data["u_matrix"]) if data["u_matrix"] is not None else None + ubcalc.UB = ( + np.array(data["ub_matrix"]) if data["ub_matrix"] is not None else None + ) + return ubcalc diff --git a/src/diffcalc/ub/crystal.py b/src/diffcalc/ub/crystal.py index 5755299..5dfc0c5 100644 --- a/src/diffcalc/ub/crystal.py +++ b/src/diffcalc/ub/crystal.py @@ -4,7 +4,7 @@ crystal plane geometric properties. """ from math import acos, cos, degrees, pi, radians, sin, sqrt -from typing import List, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple import numpy as np from diffcalc.util import DiffcalcException, allnum, angle_between_vectors, zero_round @@ -387,3 +387,21 @@ def get_hkl_plane_angle( nphi2 = self.B @ hkl2_transpose angle = angle_between_vectors(nphi1, nphi2) return angle + + @property + def asdict(self) -> Dict[str, Any]: + """Serialise the crystal into a JSON compatible dictionary""" + return { + "name": self.name, + "system": self.system, + "a": self.a1, + "b": self.a2, + "c": self.a3, + "alpha": self.alpha1, + "beta": self.alpha2, + "gamma": self.alpha3, + } + + @classmethod + def fromdict(cls, data: Dict[str, Any]) -> "Crystal": + return Crystal(**data) diff --git a/src/diffcalc/ub/reference.py b/src/diffcalc/ub/reference.py index f88c8ac..83643a3 100644 --- a/src/diffcalc/ub/reference.py +++ b/src/diffcalc/ub/reference.py @@ -1,6 +1,6 @@ """Module providing objects for working with reference reflections and orientations.""" import dataclasses -from typing import List, Tuple, Union +from typing import Any, Dict, List, Tuple, Union from diffcalc.hkl.geometry import Position @@ -55,6 +55,23 @@ def astuple( h, k, l, pos, en, tag = dataclasses.astuple(self) return (h, k, l), pos.astuple, en, tag + @property + def asdict(self) -> Dict[str, Any]: + class_info = self.__dict__.copy() + class_info["pos"] = self.pos.asdict + return class_info + + @classmethod + def fromdict(cls, data: Dict[str, Any]) -> "Reflection": + return cls( + data["h"], + data["k"], + data["l"], + Position(**data["pos"]), + data["energy"], + data["tag"], + ) + class ReflectionList: """Class containing collection of reference reflections. @@ -277,6 +294,15 @@ def _str_lines(self) -> List[str]: lines.append(fmt % values) return lines + @property + def asdict(self) -> List[Dict[str, Any]]: + return [ref.asdict for ref in self.reflections] + + @classmethod + def fromdict(cls, data: List[Dict[str, Any]]) -> "ReflectionList": + reflections = [Reflection.fromdict(each_ref) for each_ref in data] + return cls(reflections) + @dataclasses.dataclass class Orientation: @@ -334,6 +360,25 @@ def astuple( h, k, l, x, y, z, pos, tag = dataclasses.astuple(self) return (h, k, l), (x, y, z), pos.astuple, tag + @property + def asdict(self) -> Dict[str, Any]: + class_info = self.__dict__.copy() + class_info["pos"] = self.pos.asdict + return class_info + + @classmethod + def fromdict(cls, data: Dict[str, Any]) -> "Orientation": + return cls( + data["h"], + data["k"], + data["l"], + data["x"], + data["y"], + data["z"], + Position(**data["pos"]), + data["tag"], + ) + class OrientationList: """Class containing collection of reference orientations. @@ -567,3 +612,12 @@ def _str_lines(self) -> List[str]: values = (n, h, k, l, x, y, z) + angles + (tag,) lines.append(str_format % values) return lines + + @property + def asdict(self) -> List[Dict[str, Any]]: + return [orient.asdict for orient in self.orientations] + + @classmethod + def fromdict(cls, data: List[Dict[str, Any]]) -> "OrientationList": + orientations = [Orientation.fromdict(each_orient) for each_orient in data] + return cls(orientations) From 2480b7d8beda51dd24b53a24866048243721753e Mon Sep 17 00:00:00 2001 From: Rose Yemelyanova Date: Tue, 26 Jul 2022 11:22:11 +0000 Subject: [PATCH 08/13] restored tests so they pass --- src/diffcalc/hkl/calc.py | 6 ++++-- src/diffcalc/ub/reference.py | 22 +++++++++++++++++++--- tests/diffcalc/hkl/test_constraints.py | 10 +++++----- tests/diffcalc/ub/test_ub.py | 20 ++++++++++---------- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/diffcalc/hkl/calc.py b/src/diffcalc/hkl/calc.py index 575be1d..638e867 100644 --- a/src/diffcalc/hkl/calc.py +++ b/src/diffcalc/hkl/calc.py @@ -306,8 +306,10 @@ def _calc_hkl_to_position( elif len(samp_constraints) == 2: - psi_vals: Iterator[float] = ( - ref_constraint["psi"] + psi_vals: Iterator[float] = iter( + [ + ref_constraint["psi"], + ] if "psi" in list(ref_constraint.keys()) else self._calc_psi(alpha, theta, tau) ) diff --git a/src/diffcalc/ub/reference.py b/src/diffcalc/ub/reference.py index 83643a3..bc3d74a 100644 --- a/src/diffcalc/ub/reference.py +++ b/src/diffcalc/ub/reference.py @@ -3,6 +3,7 @@ from typing import Any, Dict, List, Tuple, Union from diffcalc.hkl.geometry import Position +from diffcalc.util import DiffcalcException @dataclasses.dataclass @@ -127,7 +128,15 @@ def add_reflection( tag : str Identifying tag for the reflection. """ - self.reflections += [Reflection(*hkl, pos, energy, tag)] + msg = "Reflection could not be added due to inproper input parameters." + + if (tag is not None) and (not isinstance(tag, str)): + raise DiffcalcException(msg) + try: + pos.astuple + self.reflections += [Reflection(*hkl, pos, energy, tag)] + except (TypeError, AttributeError): + raise DiffcalcException(msg) def edit_reflection( self, @@ -439,8 +448,15 @@ def add_orientation( tag : str identifying tag for the orientation. """ - - self.orientations += [Orientation(*hkl, *xyz, pos, tag)] + msg = "Orientation could not be added due to inproper input parameters." + + if (tag is not None) and (not isinstance(tag, str)): + raise DiffcalcException(msg) + try: + pos.astuple + self.orientations += [Orientation(*hkl, *xyz, pos, tag)] + except (TypeError, AttributeError): + raise DiffcalcException(msg) def edit_orientation( self, diff --git a/tests/diffcalc/hkl/test_constraints.py b/tests/diffcalc/hkl/test_constraints.py index 3626fa2..d4c0f6a 100644 --- a/tests/diffcalc/hkl/test_constraints.py +++ b/tests/diffcalc/hkl/test_constraints.py @@ -39,9 +39,9 @@ def cm(): def test_init(cm): eq_(cm.asdict, dict()) - eq_(cm._detector, dict()) - eq_(cm._reference, dict()) - eq_(cm._sample, dict()) + eq_(cm.detector, dict()) + eq_(cm.reference, dict()) + eq_(cm.sample, dict()) def test_dict_init(): @@ -203,7 +203,7 @@ def test_unconstrain_okay(cm): eq_(cm.asdict, dict()) cm.delta = 1.0 cm.mu = 2 - eq_(cm._constrained, (cm._delta, cm._mu)) + eq_(cm.constrained, (cm._delta, cm._mu)) eq_(cm.asdict, {"delta": 1.0, "mu": 2}) del cm.delta cm.mu = None @@ -533,7 +533,7 @@ def _constrain(self, *args): cm.constrain(con) -@pytest.mark.xfail(raises=ValueError) +@pytest.mark.xfail(raises=DiffcalcException) def test_is_implemented_invalid(cm): cm.naz = 1 cm.is_current_mode_implemented() diff --git a/tests/diffcalc/ub/test_ub.py b/tests/diffcalc/ub/test_ub.py index 9f71258..7f6efde 100644 --- a/tests/diffcalc/ub/test_ub.py +++ b/tests/diffcalc/ub/test_ub.py @@ -106,11 +106,11 @@ def test_str(self): def test_set_lattice(self): ubcalc = UBCalculation("testing_set_lattice") - with pytest.raises(TypeError): + with pytest.raises(DiffcalcException): ubcalc.set_lattice(1) - with pytest.raises(TypeError): + with pytest.raises(DiffcalcException): ubcalc.set_lattice(1, 2) - with pytest.raises(TypeError): + with pytest.raises(DiffcalcException): ubcalc.set_lattice("HCl") ubcalc.set_lattice("NaCl", 1.1) eq_(("NaCl", 1.1, 1.1, 1.1, 90, 90, 90), ubcalc.crystal.get_lattice()) @@ -120,7 +120,7 @@ def test_set_lattice(self): eq_(("NaCl", 1.1, 2.2, 3.3, 90, 90, 90), ubcalc.crystal.get_lattice()) ubcalc.set_lattice("NaCl", 1.1, 2.2, 3.3, 91) eq_(("NaCl", 1.1, 2.2, 3.3, 90, 91, 90), ubcalc.crystal.get_lattice()) - with pytest.raises(TypeError): + with pytest.raises(DiffcalcException): ubcalc.set_lattice(("NaCl", 1.1, 2.2, 3.3, 91, 92)) ubcalc.set_lattice("NaCl", 1.1, 2.2, 3.3, 91, 92, 93) assert_iterable_almost_equal( @@ -148,7 +148,7 @@ def test_add_reflection(self): result = reflist.get_reflection(2) eq_(result.astuple, ((2.1, 2.2, 2.3), pos2.astuple, 2.10, "atag")) - with pytest.raises(TypeError): + with pytest.raises(DiffcalcException): ubcalc.add_reflection((3.1, 3.2, 3.3), pos3, 3.10) with pytest.raises(IndexError): reflist.get_reflection(3) @@ -219,7 +219,7 @@ def test_add_orientation(self): mneq_(array([[result.x, result.y, result.z]]), trans_orient1) eq_(result.tag, None) - with pytest.raises(TypeError): + with pytest.raises(DiffcalcException): ubcalc.add_orientation(hkl2, orient2, "atag") ubcalc.add_orientation(hkl2, orient2, tag="atag") @@ -288,11 +288,11 @@ def test_setu(self): setu = ubcalc.set_u with pytest.raises(TypeError): setu(1, 2) - with pytest.raises(TypeError): + with pytest.raises(DiffcalcException): setu(1) with pytest.raises(ValueError): setu("a") - with pytest.raises(TypeError): + with pytest.raises(DiffcalcException): setu([1, 2, 3]) with pytest.raises(ValueError): setu([[1.0, 2.0, 3.0], [1.0, 2.0, 3.0], [1.0, 2.0]]) @@ -304,11 +304,11 @@ def test_setub(self): # just test calling this method ubcalc = UBCalculation("test_setub") setub = ubcalc.set_ub - with pytest.raises(TypeError): + with pytest.raises(DiffcalcException): setub(1) with pytest.raises(ValueError): setub("a") - with pytest.raises(TypeError): + with pytest.raises(DiffcalcException): setub([1, 2, 3]) with pytest.raises(ValueError): setub([[1, 2, 3], [1, 2, 3], [1, 2]]) From 433cc37020392e8d3ea38fefa09a1aa9cb4acce5 Mon Sep 17 00:00:00 2001 From: Rose Yemelyanova Date: Wed, 27 Jul 2022 08:36:53 +0000 Subject: [PATCH 09/13] ran black --- tests/diffcalc/ub/test_calculation.py | 15 ++++++--------- tests/diffcalc/ub/test_calculation_you.py | 15 ++++++--------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/tests/diffcalc/ub/test_calculation.py b/tests/diffcalc/ub/test_calculation.py index fb2328f..83cdbf8 100644 --- a/tests/diffcalc/ub/test_calculation.py +++ b/tests/diffcalc/ub/test_calculation.py @@ -25,16 +25,13 @@ from tests.test_tools import eq_ from tests.tools import matrixeq_ -UB1 = ( - array( - ( - (0.9996954135095477, -0.01745240643728364, -0.017449748351250637), - (0.01744974835125045, 0.9998476951563913, -0.0003045864904520898), - (0.017452406437283505, -1.1135499981271473e-16, 0.9998476951563912), - ) +UB1 = array( + ( + (0.9996954135095477, -0.01745240643728364, -0.017449748351250637), + (0.01744974835125045, 0.9998476951563913, -0.0003045864904520898), + (0.017452406437283505, -1.1135499981271473e-16, 0.9998476951563912), ) - * (2 * pi) -) +) * (2 * pi) EN1 = 12.39842 REF1a = PosFromI16sEuler(1, 1, 30, 0, 60, 0) diff --git a/tests/diffcalc/ub/test_calculation_you.py b/tests/diffcalc/ub/test_calculation_you.py index 2c858c9..4b10a23 100644 --- a/tests/diffcalc/ub/test_calculation_you.py +++ b/tests/diffcalc/ub/test_calculation_you.py @@ -39,16 +39,13 @@ # 0.017452406437283505, -1.1135499981271473e-16, 0.9998476951563912]) -UB1 = ( - array( - ( - (0.9996954135095477, -0.01745240643728364, -0.017449748351250637), - (0.01744974835125045, 0.9998476951563913, -0.0003045864904520898), - (0.017452406437283505, -1.1135499981271473e-16, 0.9998476951563912), - ) +UB1 = array( + ( + (0.9996954135095477, -0.01745240643728364, -0.017449748351250637), + (0.01744974835125045, 0.9998476951563913, -0.0003045864904520898), + (0.017452406437283505, -1.1135499981271473e-16, 0.9998476951563912), ) - * (2 * pi) -) +) * (2 * pi) EN1 = 12.39842 REF1a = PosFromI16sEuler(1, 1, 30, 0, 60, 0) From 7d629bd547fec3e199150f06f0ebb8fd3688304f Mon Sep 17 00:00:00 2001 From: Rose Yemelyanova Date: Wed, 27 Jul 2022 10:04:15 +0000 Subject: [PATCH 10/13] added tests for asdict/from dict methods, removed unnecessary fromdicts from crystal and referencevector classes --- src/diffcalc/ub/calc.py | 6 +----- src/diffcalc/ub/crystal.py | 20 +++++++++++--------- tests/diffcalc/hkl/test_calc_methods.py | 11 +++++++++++ tests/diffcalc/hkl/test_constraints.py | 6 ++++++ tests/diffcalc/ub/test_calculation_you.py | 23 +++++++++++++++++++++++ tests/diffcalc/ub/test_crystal.py | 10 ++++++++++ tests/diffcalc/ub/test_orientations.py | 7 +++++++ tests/diffcalc/ub/test_reference.py | 7 +++++++ tests/diffcalc/ub/test_reflections.py | 7 +++++++ 9 files changed, 83 insertions(+), 14 deletions(-) diff --git a/src/diffcalc/ub/calc.py b/src/diffcalc/ub/calc.py index f41ef96..32749b2 100644 --- a/src/diffcalc/ub/calc.py +++ b/src/diffcalc/ub/calc.py @@ -135,10 +135,6 @@ def set_array(self, n_ref: np.ndarray) -> None: def asdict(self) -> Dict[str, Any]: return self.__dict__.copy() - @classmethod - def fromdict(cls, data: Dict[str, Any]) -> "ReferenceVector": - return cls(**data) - class UBCalculation: """Class containing information required for for UB matrix calculation. @@ -1401,7 +1397,7 @@ def fromdict(cls, data: Dict[str, Any]) -> "UBCalculation": # need to return exactly the same object. ubcalc = cls(data["name"]) ubcalc.crystal = ( - Crystal.fromdict(data["crystal"]) if data["crystal"] is not None else None + Crystal(**data["crystal"]) if data["crystal"] is not None else None ) ubcalc.reflist = ReflectionList.fromdict(data["reflist"]) ubcalc.orientlist = OrientationList.fromdict(data["orientlist"]) diff --git a/src/diffcalc/ub/crystal.py b/src/diffcalc/ub/crystal.py index 5dfc0c5..3e527e0 100644 --- a/src/diffcalc/ub/crystal.py +++ b/src/diffcalc/ub/crystal.py @@ -16,7 +16,8 @@ class Crystal: Contains the lattice parameters and calculated B matrix for the crystal under test. Also Calculates the distance between planes at a given hkl - value. + value. All angles are assumed to be given in degrees, and are converted + to radians. Attributes ---------- @@ -390,18 +391,19 @@ def get_hkl_plane_angle( @property def asdict(self) -> Dict[str, Any]: - """Serialise the crystal into a JSON compatible dictionary""" + """Serialise the crystal into a JSON compatible dictionary. + + Note, because the class automatically assumes all angles are + in degrees, the returned angles alpha, beta and gamma are given + in degrees such that the dictionary can be directly unpacked as is. + """ return { "name": self.name, "system": self.system, "a": self.a1, "b": self.a2, "c": self.a3, - "alpha": self.alpha1, - "beta": self.alpha2, - "gamma": self.alpha3, + "alpha": degrees(self.alpha1), + "beta": degrees(self.alpha2), + "gamma": degrees(self.alpha3), } - - @classmethod - def fromdict(cls, data: Dict[str, Any]) -> "Crystal": - return Crystal(**data) diff --git a/tests/diffcalc/hkl/test_calc_methods.py b/tests/diffcalc/hkl/test_calc_methods.py index 0243d62..bd853f1 100644 --- a/tests/diffcalc/hkl/test_calc_methods.py +++ b/tests/diffcalc/hkl/test_calc_methods.py @@ -21,6 +21,7 @@ from unittest.mock import Mock from diffcalc.hkl.calc import HklCalculation +from diffcalc.hkl.constraints import Constraints from diffcalc.hkl.geometry import Position from diffcalc.ub.calc import UBCalculation from diffcalc.util import I @@ -220,3 +221,13 @@ def test_psi7(self): def test_psi8(self): self.check_angle("psi", 88, mu=0, delta=0.001, nu=0, eta=90, chi=-2, phi=0) + + def test_serialisation(self): + hklCalc = HklCalculation( + self.ubcalc, Constraints({"delta": 1, "alpha": 2, "mu": 3}) + ) + hklCalc_json = hklCalc.asdict + + new_hklCalc = HklCalculation.fromdict(hklCalc_json) + + assert new_hklCalc.asdict == hklCalc.asdict diff --git a/tests/diffcalc/hkl/test_constraints.py b/tests/diffcalc/hkl/test_constraints.py index d4c0f6a..2e87eed 100644 --- a/tests/diffcalc/hkl/test_constraints.py +++ b/tests/diffcalc/hkl/test_constraints.py @@ -723,3 +723,9 @@ def test_set_fails(cm): e.args[0], f"Constraint a_eq_b requires boolean value. Found {int} instead.", ) + + +def test_serialisation(cm): + cm.asdict = {"alpha": 1, "mu": 2, "phi": 1, "beta": 2} + cm_json = cm.asdict + assert Constraints(cm_json).asdict == cm.asdict diff --git a/tests/diffcalc/ub/test_calculation_you.py b/tests/diffcalc/ub/test_calculation_you.py index 4b10a23..29a3c5e 100644 --- a/tests/diffcalc/ub/test_calculation_you.py +++ b/tests/diffcalc/ub/test_calculation_you.py @@ -18,6 +18,7 @@ from math import pi +from diffcalc.hkl.geometry import Position from diffcalc.ub.calc import UBCalculation from numpy import array @@ -59,3 +60,25 @@ def testAgainstI16Results(): ubcalc.add_reflection((0, 0, 1), REF1b, EN1, "001") ubcalc.calc_ub() matrixeq_(ubcalc.UB, UB1) + + +def test_serialisation(): + ubcalc = UBCalculation() + ubcalc.set_lattice(name="test", a=4.913, c=5.405) + ubcalc.add_reflection( + hkl=(0, 0, 1), + position=Position(7.31, 0, 10.62, 0, 0, 0), + energy=12.39842, + tag="refl1", + ) + ubcalc.add_orientation(hkl=(0, 1, 0), xyz=(0, 1, 0), tag="plane") + ubcalc.n_hkl = (1, 0, 0) + + ubcalc_json = ubcalc.asdict + new_ubcalc = UBCalculation.fromdict(ubcalc_json) + + ubcalc.calc_ub("refl1", "plane") + new_ubcalc.calc_ub("refl1", "plane") + + assert (new_ubcalc.UB == ubcalc.UB).all() + assert (new_ubcalc.U == ubcalc.U).all() diff --git a/tests/diffcalc/ub/test_crystal.py b/tests/diffcalc/ub/test_crystal.py index 3d3ecd5..57a0212 100644 --- a/tests/diffcalc/ub/test_crystal.py +++ b/tests/diffcalc/ub/test_crystal.py @@ -79,3 +79,13 @@ def test_get_hkl_plane_angle(self): def test__str__(self): cut = Crystal("HCl", 1, 2, 3, 4, 5, 6) print(cut.__str__()) + + def test_serialisation(self): + for sess in scenarios.sessions(): + if sess.bmatrix is None: + continue + crystal = Crystal("tc", *sess.lattice) + cut_json = crystal.asdict + reformed_crystal = Crystal(**cut_json) + + assert (reformed_crystal.B == crystal.B).all() diff --git a/tests/diffcalc/ub/test_orientations.py b/tests/diffcalc/ub/test_orientations.py index 5de2a3b..091ef87 100644 --- a/tests/diffcalc/ub/test_orientations.py +++ b/tests/diffcalc/ub/test_orientations.py @@ -127,3 +127,10 @@ def testSwapOrientation(self): pos.astuple, "orient1", ) + + def test_serialisation(self): + orig_orient_list = self.orientlist + orient_json = orig_orient_list.asdict + reformed_orient_list = OrientationList.fromdict(orient_json) + + assert reformed_orient_list.asdict == orig_orient_list.asdict diff --git a/tests/diffcalc/ub/test_reference.py b/tests/diffcalc/ub/test_reference.py index ba0ac6a..ebb32d4 100644 --- a/tests/diffcalc/ub/test_reference.py +++ b/tests/diffcalc/ub/test_reference.py @@ -67,3 +67,10 @@ def test_n_phi_from_hkl_with_unity_matrix_001(reference): def test_n_phi_from_hkl_with_unity_matrix_010(reference): reference = ReferenceVector((0, 1, 0), True) assert_2darray_almost_equal(reference.get_array(), array([[0], [1], [0]])) + + +def test_serialisation(reference): + reference_json = reference.asdict + reformed_reference = ReferenceVector(**reference_json) + + assert reformed_reference.asdict == reference.asdict diff --git a/tests/diffcalc/ub/test_reflections.py b/tests/diffcalc/ub/test_reflections.py index da71c19..4080e89 100644 --- a/tests/diffcalc/ub/test_reflections.py +++ b/tests/diffcalc/ub/test_reflections.py @@ -121,3 +121,10 @@ def test_swap_reflection(self): 1000, "ref1", ) + + def test_serialisation(self): + orig_reflist = self.reflist + reflist_json = orig_reflist.asdict + reformed_reflist = ReflectionList.fromdict(reflist_json) + + assert reformed_reflist.asdict == orig_reflist.asdict From 7d40c011f696bdb36ce55cc751fc96d6ae5c3d84 Mon Sep 17 00:00:00 2001 From: Rose Yemelyanova Date: Wed, 27 Jul 2022 10:13:52 +0000 Subject: [PATCH 11/13] added extra test to pass code coverage --- tests/diffcalc/hkl/test_calc.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/diffcalc/hkl/test_calc.py b/tests/diffcalc/hkl/test_calc.py index d9fa580..37efdc1 100644 --- a/tests/diffcalc/hkl/test_calc.py +++ b/tests/diffcalc/hkl/test_calc.py @@ -3944,3 +3944,30 @@ def __make_cases_fixture(yrot, zrot): def test_hkl_to_angles_given_UB(self, name, make_cases): case = make_cases(0, 0) self.case_generator(case[name]) + + +class TestConstrainDetRefSamp(_BaseTest): + def setup_method(self): + self.ubcalc = UBCalculation() + self.ubcalc.n_hkl = (1.0, 0.0, 0.0) + + self.ubcalc.set_lattice(name="test", a=4.913, c=5.405) + self.ubcalc.add_reflection( + hkl=(0, 0, 1), + position=Position(7.31, 0, 10.62, 0, 0, 0), + energy=12.39842, + tag="refl1", + ) + self.ubcalc.add_orientation(hkl=(0, 1, 0), xyz=(0, 1, 0), tag="plane") + self.constraints = Constraints({"naz": 3, "alpha": 2, "eta": 1}) + self.hklcalc = HklCalculation(self.ubcalc, self.constraints) + + def _configure_ub(self): + self.ubcalc.calc_ub("refl1", "plane") + + def testGetPosition(self): + self.setup_method() + self._configure_ub() + + results = self.hklcalc.get_position(1, 2, 3, 1) + assert results From ca4c54fdc3ae6818f1d214bb763f80213a69c7f3 Mon Sep 17 00:00:00 2001 From: Rose Yemelyanova Date: Wed, 27 Jul 2022 10:55:05 +0000 Subject: [PATCH 12/13] added github workflow file and fixed some flake8 errors to try to make it pass --- .github/workflows/code.yml | 18 +++++ src/diffcalc/hkl/calc.py | 135 ++++++++++++++++++------------------- src/diffcalc/util.py | 4 +- 3 files changed, 86 insertions(+), 71 deletions(-) create mode 100644 .github/workflows/code.yml diff --git a/.github/workflows/code.yml b/.github/workflows/code.yml new file mode 100644 index 0000000..5b10b4c --- /dev/null +++ b/.github/workflows/code.yml @@ -0,0 +1,18 @@ +name: Code CI + +on: + push: + pull_request: + schedule: + - cron: "0 8 * * MON" + +jobs: + lint: + steps: + - uses: actions/checkout@v2 + - uses: weibullguy/python-lint-plus@master + with: + use-black: true + use-mypy: true + use-flake8: true + extra-flake8-options: "--ignore=C901,W503,E741" \ No newline at end of file diff --git a/src/diffcalc/hkl/calc.py b/src/diffcalc/hkl/calc.py index 638e867..821c5bc 100644 --- a/src/diffcalc/hkl/calc.py +++ b/src/diffcalc/hkl/calc.py @@ -211,7 +211,7 @@ def _check_constrained(self): if not self.constraints.is_current_mode_implemented(): raise DiffcalcException( "Sorry, the selected constraint combination is valid but " - "is not implemented. Type 'help con' for implemented combinations" + + "is not implemented. Type 'help con' for implemented combinations" ) def _verify_angles( @@ -220,18 +220,21 @@ def _verify_angles( if is_small(sin(tau)): if ref_constraint_name == "psi": raise DiffcalcException( - "Azimuthal angle 'psi' is undefined as reference and scattering vectors parallel.\n" - "Please constrain one of the sample angles or choose different reference vector orientation." + "Azimuthal angle 'psi' is undefined as reference and scattering " + + "vectors parallel.\nPlease constrain one of the sample angles or" + + " choose different reference vector orientation." ) elif ref_constraint_name == "a_eq_b": raise DiffcalcException( - "Reference constraint 'a_eq_b' is redundant as reference and scattering vectors are parallel.\n" - "Please constrain one of the sample angles or choose different reference vector orientation." + "Reference constraint 'a_eq_b' is redundant as reference and " + + "scattering vectors are parallel.\nPlease constrain one of the " + + "sample angles or choose different reference vector orientation." ) if is_small(sin(surf_tau)) and ref_constraint_name == "bin_eq_bout": raise DiffcalcException( - "Reference constraint 'bin_eq_bout' is redundant as scattering vectors is parallel to the surface normal.\n" - "Please select another constrain to define sample azimuthal orientation." + "Reference constraint 'bin_eq_bout' is redundant as scattering vectors" + + " is parallel to the surface normal.\nPlease select another" + + " constraint to define sample azimuthal orientation." ) def _calc_hkl_to_position( @@ -248,7 +251,7 @@ def _calc_hkl_to_position( tau = angle_between_vectors(h_phi, self.ubcalc.n_phi) surf_tau = angle_between_vectors(h_phi, self.ubcalc.surf_nphi) - ### Reference constraint column ### + # Reference constraint column n_phi = self.ubcalc.n_phi if ref_constraint: @@ -333,7 +336,7 @@ def _calc_hkl_to_position( if not solution_tuples: raise DiffcalcException( "No solutions were found. " - "Please consider using an alternative set of constraints." + + "Please consider using an alternative set of constraints." ) tidy_solutions = [ @@ -347,7 +350,7 @@ def _calc_hkl_to_position( if not position_pseudo_angles_pairs: raise DiffcalcException( "No solutions were found. Please consider using " - "an alternative pseudo-angle constraints." + + "an alternative pseudo-angle constraints." ) return position_pseudo_angles_pairs @@ -588,7 +591,7 @@ def _calc_remaining_detector_angles( if is_small(sin_2theta): raise DiffcalcException( "No meaningful scattering vector (Q) can be found when " - f"theta is so small: {degrees(theta):.4f} degrees." + + f"theta is so small: {degrees(theta):.4f} degrees." ) constraint_callable: Dict[ @@ -605,8 +608,9 @@ def _calc_remaining_detector_angles( ) except KeyError: raise DiffcalcException( - constraint_name + " is not an explicit detector angle " - "(naz cannot be handled here)" + constraint_name + + " is not an explicit detector angle " + + "(naz cannot be handled here)" ) yield from detector_angles @@ -663,9 +667,8 @@ def _calc_remaining_detector_angles_nu_constrained( cos_nu = cos(nu_value) if is_small(cos_nu): raise DiffcalcException( - "The %s circle constraint to %.0f degrees is redundant." - "Please change this constraint or use 4-circle mode." - % ("nu", degrees(nu_value)) + f"The nu circle constraint to {degrees(nu_value)} degrees is " + + "redundant. Please change this constraint or use 4-circle mode." ) cos_delta = cos_2theta / cos(nu_value) cos_qaz = cos_delta * sin(nu_value) / sin_2theta @@ -842,9 +845,7 @@ def _calc_remaining_sample_angles_mu(self, mu_value: float, v_matrix: np.ndarray phi = atan2(-v_matrix[1, 0], v_matrix[1, 1]) logger.debug( "Eta and phi cannot be chosen uniquely with chi so close " - "to 0 or 180. Returning phi=%.3f and eta=%.3f", - degrees(phi), - degrees(eta), + + f"to 0 or 180. Returning phi={degrees(phi)} and eta={degrees(eta)}" ) yield mu_value, eta, chi, phi else: @@ -861,7 +862,7 @@ def _calc_remaining_sample_angles_phi(self, phi_value: float, v_matrix: np.ndarr return if is_small(cos(asin_eta)): raise DiffcalcException( - "Chi and mu cannot be chosen uniquely " "with eta so close to +/-90." + "Chi and mu cannot be chosen uniquely with eta so close to +/-90." ) for eta in [asin_eta, pi - asin_eta]: sgn = sign(cos(eta)) @@ -879,7 +880,7 @@ def _calc_remaining_sample_angles_eta_or_chi( # TODO: Not likely to happen in real world!? raise DiffcalcException( "Chi and mu cannot be chosen uniquely with eta " - "constrained so close to +-90." + + "constrained so close to +-90." ) try: asin_chi = asin(bound(z_matrix[0, 2] / cos_eta)) @@ -896,8 +897,8 @@ def _calc_remaining_sample_angles_eta_or_chi( if is_small(sin_chi): raise DiffcalcException( "Eta and phi cannot be chosen uniquely with chi " - "constrained so close to 0. (Please contact developer " - "if this case is useful for you)" + + "constrained so close to 0. (Please contact developer " + + "if this case is useful for you)" ) try: acos_eta = acos(bound(z_matrix[0, 2] / sin_chi)) @@ -918,8 +919,8 @@ def _calc_remaining_sample_angles_eta_or_chi( if is_small(top_for_mu) and is_small(bot_for_mu): raise DiffcalcException( "Mu cannot be chosen uniquely as mu || phi with chi so close " - "to +/-90 and eta so close 0 or 180.\nPlease choose " - "a different set of constraints." + + "to +/-90 and eta so close 0 or 180.\nPlease choose " + + "a different set of constraints." ) mu = atan2(-top_for_mu, -bot_for_mu) # (41) @@ -932,8 +933,8 @@ def _calc_remaining_sample_angles_eta_or_chi( if is_small(bot_for_phi) and is_small(top_for_phi): DiffcalcException( "Phi cannot be chosen uniquely as mu || phi with chi so close " - "to +/-90 and eta so close 0 or 180.\nPlease choose a " - "different set of constraints." + + "to +/-90 and eta so close 0 or 180.\nPlease choose a " + + "different set of constraints." ) phi = atan2(top_for_phi, bot_for_phi) # (42) yield mu, eta, chi, phi @@ -947,7 +948,8 @@ def _calc_angles_given_three_sample_constraints( def __get_last_sample_angle(A: float, B: float, C: float) -> List[float]: if is_small(A) and is_small(B): raise DiffcalcException( - "Sample orientation cannot be chosen uniquely. Please choose a different set of constraints." + "Sample orientation cannot be chosen uniquely. Please choose a " + + "different set of constraints." ) ks = atan2(A, B) acos_alp = acos(bound(C / sqrt(A**2 + B**2))) @@ -1135,8 +1137,6 @@ def __get_phi_and_qaz(chi: float, eta: float, mu: float) -> Tuple[float, float]: b = sin(chi) * sin(eta) * sin(mu) - cos(chi) * cos(mu) sin_qaz = v_matrix[2, 0] * a - v_matrix[2, 2] * b cos_qaz = -v_matrix[2, 2] * a - v_matrix[2, 0] * b - # atan2_xi = atan2(v_matrix[2, 2] * a + v_matrix[2, 0] * b, - # v_matrix[2, 0] * a - v_matrix[2, 2] * b) # (54) qaz = atan2(sin_qaz, cos_qaz) # (54) a = sin(chi) * sin(mu) - cos(mu) * cos(chi) * sin(eta) @@ -1155,7 +1155,8 @@ def __get_chi_and_qaz(mu: float, eta: float) -> Tuple[float, float]: cos_chi = B * v_matrix[1, 0] - A * v_matrix[1, 2] if is_small(sin_chi) and is_small(cos_chi): raise DiffcalcException( - "Chi cannot be chosen uniquely. Please choose a different set of constraints." + "Chi cannot be chosen uniquely. Please choose a different set of " + + "constraints." ) chi = atan2(sin_chi, cos_chi) @@ -1178,17 +1179,12 @@ def __get_chi_and_qaz(mu: float, eta: float) -> Tuple[float, float]: CHI = rot_CHI(chi) PHI = rot_PHI(phi) v_matrix = CHI @ PHI @ N_phi @ PSI.T @ THETA.T # (46) - - # atan2_xi = atan2(-v_matrix[2, 0], v_matrix[2, 2]) - # atan2_eta = atan2(-v_matrix[0, 1], v_matrix[1, 1]) - # atan2_mu = atan2(-v_matrix[2, 1], sqrt(v_matrix[2, 2] ** 2 + v_matrix[2, 0] ** 2)) try: asin_mu = asin(bound(-v_matrix[2, 1])) except AssertionError: return for mu in [asin_mu, pi - asin_mu]: sgn_cosmu = sign(cos(mu)) - # xi = atan2(-sgn_cosmu * v_matrix[2, 0], sgn_cosmu * v_matrix[2, 2]) qaz = atan2( sgn_cosmu * v_matrix[2, 2], sgn_cosmu * v_matrix[2, 0], @@ -1215,9 +1211,9 @@ def __get_chi_and_qaz(mu: float, eta: float) -> Tuple[float, float]: eps = atan2(sin(mu), sin(eta) * cos(mu)) chi_vals = [asin(bot) - eps, pi - asin(bot) - eps] # (52) - ## Choose final chi solution here to obtain compatable xi and mu - ## TODO: This temporary solution works only for one case used on i07 - ## Return a list of possible solutions? + # Choose final chi solution here to obtain compatable xi and mu + # TODO: This temporary solution works only for one case used on i07 + # Return a list of possible solutions? # if is_small(eta) and is_small(mu + pi / 2): # for chi in _generate_transformed_values(chi_orig): # if pi / 2 <= chi < pi: @@ -1281,7 +1277,8 @@ def __get_chi_and_qaz(mu: float, eta: float) -> Tuple[float, float]: if is_small(cos(mu)): raise DiffcalcException( - "Eta cannot be chosen uniquely. Please choose a different set of constraints." + "Eta cannot be chosen uniquely. Please choose a different set of " + + "constraints." ) try: acos_eta = acos(bound(v_matrix[1, 1] / cos(mu))) @@ -1301,7 +1298,8 @@ def __get_chi_and_qaz(mu: float, eta: float) -> Tuple[float, float]: if is_small(cos(eta)): raise DiffcalcException( - "Mu cannot be chosen uniquely. Please choose a different set of constraints." + "Mu cannot be chosen uniquely. Please choose a different set " + + "of constraints." ) try: acos_mu = acos(bound(v_matrix[1, 1] / cos(eta))) @@ -1314,7 +1312,8 @@ def __get_chi_and_qaz(mu: float, eta: float) -> Tuple[float, float]: else: raise DiffcalcException( "No code yet to handle this combination of 2 sample " - "constraints and one reference!:" + str(samp_constraints) + + "constraints and one reference!:" + + str(samp_constraints) ) def _calc_sample_angles_given_two_sample_and_detector( @@ -1426,12 +1425,12 @@ def _calc_sample_angles_given_two_sample_and_detector( phi_vals = [] try: - # For the case of (00l) reflection, where N_phi[0,0] = N_phi[1,0] = 0 + # For the case of (00l) reflection, where N_phi[0,0]=N_phi[1,0]=0 if is_small(N_phi[0, 0]) and is_small(N_phi[1, 0]): raise DiffcalcException( - "Phi cannot be chosen uniquely as q || phi and no reference " - "vector or phi constraints have been set.\nPlease choose a different " - "set of constraints." + "Phi cannot be chosen uniquely as q || phi and no " + + "reference vector or phi constraints have been set.\n" + + "Please choose a different set of constraints." ) bot = bound( -v_matrix[1, 0] / sqrt(N_phi[0, 0] ** 2 + N_phi[1, 0] ** 2) @@ -1473,14 +1472,11 @@ def _calc_sample_angles_given_two_sample_and_detector( if is_small(X) and is_small(Y): raise DiffcalcException( "Eta cannot be chosen uniquely as q || eta and no reference " - "vector or eta constraints have been set.\nPlease choose a different " - "set of constraints." + + "vector or eta constraints have been set.\nPlease choose a " + + "different set of constraints." ) eta = atan2(X, Y) - # a = -cos(mu) * cos(qaz) * sin(theta) + sin(mu) * cos(theta) - # b = cos(mu) * sin(qaz) - # psi = atan2(-v_matrix[2, 2] * a - v_matrix[2, 1] * b, v_matrix[2, 1] * a - v_matrix[2, 2] * b) yield mu, eta, chi, phi elif "mu" in samp_constraints and "phi" in samp_constraints: @@ -1516,11 +1512,13 @@ def _calc_sample_angles_given_two_sample_and_detector( B = N_phi[0, 0] if is_small(sin(chi)): raise DiffcalcException( - "Degenerate configuration with phi || eta axes cannot be set uniquely. Please choose a different set of constraints." + "Degenerate configuration with phi || eta axes cannot be set " + + "uniquely. Please choose a different set of constraints." ) if is_small(A) and is_small(B): raise DiffcalcException( - "Phi cannot be chosen uniquely. Please choose a different set of constraints." + "Phi cannot be chosen uniquely. Please choose a different set " + + "of constraints." ) else: ks = atan2(A, B) @@ -1552,7 +1550,8 @@ def _calc_sample_angles_given_two_sample_and_detector( cos_eta = V00 * B00 - V10 * A00 if is_small(A00) and is_small(B00): raise DiffcalcException( - "Eta cannot be chosen uniquely. Please choose a different set of constraints." + "Eta cannot be chosen uniquely. Please choose a different set " + + "of constraints." ) eta = atan2(sin_eta, cos_eta) yield mu, eta, chi, phi @@ -1567,8 +1566,8 @@ def _calc_sample_angles_given_two_sample_and_detector( if is_small(X) and is_small(Y): raise DiffcalcException( "Chi cannot be chosen uniquely as q || chi and no reference " - "vector or chi constraints have been set.\nPlease choose a different " - "set of constraints." + + "vector or chi constraints have been set.\nPlease choose a " + + "different set of constraints." ) v_matrix = (N_phi[1, 0] * cos(phi) - N_phi[0, 0] * sin(phi)) * tan(eta) @@ -1613,7 +1612,8 @@ def _calc_sample_angles_given_two_sample_and_detector( B = N_phi[0, 0] * cos(chi) * cos(eta) + N_phi[1, 0] * sin(eta) if is_small(A) and is_small(B): raise DiffcalcException( - "Phi cannot be chosen uniquely. Please choose a different set of constraints." + "Phi cannot be chosen uniquely. Please choose a different set of " + + "constraints." ) else: ks = atan2(A, B) @@ -1663,7 +1663,8 @@ def _calc_sample_angles_given_two_sample_and_detector( cos_mu = (V10 * A20 - V20 * A10) * sign(B10 * A20 - B20 * A10) if is_small(sin_mu) and is_small(cos_mu): raise DiffcalcException( - "Mu cannot be chosen uniquely. Please choose a different set of constraints." + "Mu cannot be chosen uniquely. Please choose a different set" + + " of constraints." ) mu = atan2(sin_mu, cos_mu) yield mu, eta, chi, phi @@ -1671,7 +1672,8 @@ def _calc_sample_angles_given_two_sample_and_detector( else: raise DiffcalcException( "No code yet to handle this combination of 2 sample " - "constraints and one detector!:" + str(samp_constraints) + + "constraints and one detector!:" + + str(samp_constraints) ) def _tidy_degenerate_solutions( @@ -1758,17 +1760,12 @@ def _verify_pos_map_to_hkl( hkl = self.get_hkl(pos, wavelength) e = 0.001 if (abs(hkl[0] - h) > e) or (abs(hkl[1] - k) > e) or (abs(hkl[2] - l) > e): - s = "ERROR: The angles calculated for hkl=({:f},{:f},{:f}) were {}.\n".format( - h, - k, - l, - str(pos), - ) - s += "Converting these angles back to hkl resulted in hkl=" "(%f,%f,%f)" % ( - hkl[0], - hkl[1], - hkl[2], + s = ( + f"ERROR: The angles calculated for hkl=({h},{k},{l}) were {str(pos)}." + + "\nConverting these angles back to hkl resulted in hkl=" + + f"({hkl[0]},{hkl[1]},{hkl[2]})" ) + raise DiffcalcException(s) def _verify_virtual_angles( diff --git a/src/diffcalc/util.py b/src/diffcalc/util.py index 0cda4ea..e3b4517 100644 --- a/src/diffcalc/util.py +++ b/src/diffcalc/util.py @@ -94,7 +94,7 @@ def __str__(self): return "\n".join(lines) -### Matrices +# Matrices def cross3(x: np.ndarray, y: np.ndarray) -> np.ndarray: @@ -153,7 +153,7 @@ def angle_between_vectors(x: np.ndarray, y: np.ndarray) -> float: return acos(bound(costheta)) -## Math +# Math def bound(x: float) -> float: From 6ab44485928fad595e2ee55ba2a838516363147b Mon Sep 17 00:00:00 2001 From: Rose Yemelyanova Date: Wed, 27 Jul 2022 11:12:37 +0000 Subject: [PATCH 13/13] added wheel and test jobs to CI. Now have, lint, wheel, test --- .github/workflows/code.yml | 64 +++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code.yml b/.github/workflows/code.yml index 5b10b4c..c6207d4 100644 --- a/.github/workflows/code.yml +++ b/.github/workflows/code.yml @@ -15,4 +15,66 @@ jobs: use-black: true use-mypy: true use-flake8: true - extra-flake8-options: "--ignore=C901,W503,E741" \ No newline at end of file + extra-flake8-options: "--ignore=C901,W503,E741" + + wheel: + strategy: + fail-fast: false + matrix: + os: ["ubuntu-latest"] + python: ["3.9"] + + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Create Sdist and Wheel + # Set SOURCE_DATE_EPOCH from git commit for reproducible build + # https://reproducible-builds.org/ + # Set group writable and umask to do the same to match inside DLS + run: | + chmod -R g+w . + umask 0002 + SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) pipx run build --sdist --wheel + - name: Upload Wheel and Sdist as artifacts + uses: actions/upload-artifact@v2 + with: + name: dist + path: dist/* + + - name: Install minimum python version + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + + test: + strategy: + fail-fast: false + matrix: + os: ["ubuntu-latest"] # can add windows-latest, macos-latest + python: ["3.6", "3.7", "3.8", "3.9", "3.10"] + + + include: + # Add an extra Python3.9 runner to use the lockfile + - os: "ubuntu-latest" + python: "3.9" + + runs-on: ${{ matrix.os }} + env: + # https://github.com/pytest-dev/pytest/issues/2042 + PY_IGNORE_IMPORTMISMATCH: "1" + + steps: + - name: Test with pytest + run: pytest --cov tests + with: + python-version: ${{ matrix.python }} + + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v2 + with: + name: ${{ matrix.python }}/${{ matrix.os }} \ No newline at end of file