advisor-scattering 0.5.3__py3-none-any.whl → 0.9.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. advisor/controllers/app_controller.py +2 -1
  2. advisor/domain/__init__.py +4 -0
  3. advisor/domain/core/lab.py +9 -2
  4. advisor/domain/core/lattice.py +2 -5
  5. advisor/domain/core/sample.py +9 -2
  6. advisor/domain/orientation.py +219 -0
  7. advisor/domain/orientation_calculator.py +173 -0
  8. advisor/features/__init__.py +7 -4
  9. advisor/features/scattering_geometry/domain/brillouin_calculator.py +43 -1
  10. advisor/features/scattering_geometry/domain/core.py +4 -4
  11. advisor/features/scattering_geometry/ui/components/angles_to_hkl_components.py +10 -18
  12. advisor/features/scattering_geometry/ui/components/hk_angles_components.py +16 -29
  13. advisor/features/scattering_geometry/ui/components/hkl_scan_components.py +14 -25
  14. advisor/features/scattering_geometry/ui/components/hkl_to_angles_components.py +18 -29
  15. advisor/features/scattering_geometry/ui/scattering_geometry_tab.py +9 -1
  16. advisor/ui/dialogs/__init__.py +7 -0
  17. advisor/ui/dialogs/diffraction_test_dialog.py +287 -0
  18. advisor/ui/init_window.py +39 -15
  19. advisor/ui/visualizers/HKLScan2DVisualizer.py +37 -2
  20. {advisor_scattering-0.5.3.dist-info → advisor_scattering-0.9.5.dist-info}/METADATA +4 -2
  21. {advisor_scattering-0.5.3.dist-info → advisor_scattering-0.9.5.dist-info}/RECORD +24 -20
  22. {advisor_scattering-0.5.3.dist-info → advisor_scattering-0.9.5.dist-info}/WHEEL +0 -0
  23. {advisor_scattering-0.5.3.dist-info → advisor_scattering-0.9.5.dist-info}/entry_points.txt +0 -0
  24. {advisor_scattering-0.5.3.dist-info → advisor_scattering-0.9.5.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,8 @@ from typing import Optional
6
6
 
7
7
  from PyQt5.QtWidgets import QApplication
8
8
 
9
- from advisor.features import ScatteringGeometryController, StructureFactorController
9
+ from advisor.features.scattering_geometry.controllers import ScatteringGeometryController
10
+ from advisor.features.structure_factor.controllers import StructureFactorController
10
11
  from advisor.ui.init_window import InitWindow
11
12
  from advisor.ui.main_window import MainWindow
12
13
 
@@ -10,6 +10,8 @@ from .geometry import (
10
10
  lab_to_sample_conversion,
11
11
  )
12
12
  from .unit_converter import UnitConverter
13
+ from .orientation import fit_orientation_from_diffraction_tests
14
+ from .orientation_calculator import OrientationCalculator
13
15
 
14
16
  __all__ = [
15
17
  "get_real_space_vectors",
@@ -20,4 +22,6 @@ __all__ = [
20
22
  "sample_to_lab_conversion",
21
23
  "lab_to_sample_conversion",
22
24
  "UnitConverter",
25
+ "fit_orientation_from_diffraction_tests",
26
+ "OrientationCalculator",
23
27
  ]
@@ -3,8 +3,8 @@
3
3
  import numpy as np
4
4
 
5
5
  from advisor.domain import angle_to_matrix
6
- from .sample import Sample
7
6
 
7
+ from .sample import Sample
8
8
 
9
9
 
10
10
  class Lab:
@@ -113,9 +113,16 @@ class Lab:
113
113
  return ex_lattice_in_lab, ey_lattice_in_lab, ez_lattice_in_lab
114
114
 
115
115
  def rotate(self, theta, phi, chi):
116
- """Rotate the lab."""
116
+ """Rotate the lab / goniometer"""
117
117
  self.theta = theta
118
118
  self.phi = phi
119
119
  self.chi = chi
120
120
  self.calculate_real_space_vectors()
121
121
  self.calculate_reciprocal_space_vectors()
122
+
123
+ def reorient(self,roll, pitch, yaw):
124
+ """reorient the sample with respect to the lab"""
125
+ self.sample.reorient(roll, pitch, yaw)
126
+ self.calculate_real_space_vectors()
127
+ self.calculate_reciprocal_space_vectors()
128
+
@@ -2,10 +2,7 @@
2
2
 
3
3
  import numpy as np
4
4
 
5
- from advisor.domain import (
6
- get_real_space_vectors,
7
- get_reciprocal_space_vectors,
8
- )
5
+ from advisor.domain import get_real_space_vectors, get_reciprocal_space_vectors
9
6
 
10
7
 
11
8
  class Lattice:
@@ -31,7 +28,7 @@ class Lattice:
31
28
  self.c_star_vec_lattice = None
32
29
 
33
30
  def initialize(self, a, b, c, alpha, beta, gamma):
34
- """Initialize the sample.
31
+ """Initialize by calculating the real and reciprocal space vectors.
35
32
 
36
33
  Args:
37
34
  a, b, c (float): Lattice constants in Angstroms
@@ -3,8 +3,10 @@
3
3
  import numpy as np
4
4
 
5
5
  from advisor.domain import euler_to_matrix
6
+
6
7
  from .lattice import Lattice
7
8
 
9
+
8
10
  class Sample:
9
11
  """This is a class for the sample."""
10
12
 
@@ -97,5 +99,10 @@ class Sample:
97
99
  self.b_star_vec_sample = rotation_matrix @ b_star_vec_lattice
98
100
  self.c_star_vec_sample = rotation_matrix @ c_star_vec_lattice
99
101
 
100
- def rotate(self, theta, phi, chi):
101
- """Rotate the sample."""
102
+ def reorient(self,roll, pitch, yaw):
103
+ """reorient the sample"""
104
+ self.roll = roll
105
+ self.pitch = pitch
106
+ self.yaw = yaw
107
+ self.calculate_real_space_vectors()
108
+ self.calculate_reciprocal_space_vectors()
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Orientation fitting from diffraction tests.
4
+
5
+ This module provides functions to determine the optimal Euler angles (roll, pitch, yaw)
6
+ that align a crystal lattice orientation with observed diffraction data.
7
+ """
8
+
9
+ import numpy as np
10
+ from scipy.optimize import minimize
11
+
12
+ from .orientation_calculator import OrientationCalculator
13
+
14
+ # Default number of random restarts for optimization
15
+ DEFAULT_N_RESTARTS = 20
16
+ # Target residual error - stop early if achieved
17
+ TARGET_RESIDUAL = 1e-10
18
+
19
+
20
+ def fit_orientation_from_diffraction_tests(
21
+ lattice_params: dict,
22
+ diffraction_tests: list,
23
+ initial_guess: tuple = (0.0, 0.0, 0.0),
24
+ n_restarts: int = DEFAULT_N_RESTARTS,
25
+ ) -> dict:
26
+ """Fit crystal orientation from diffraction test data.
27
+
28
+ Given lattice parameters and a list of diffraction tests (each containing
29
+ known HKL values and measured angles), find the Euler angles (roll, pitch, yaw)
30
+ that best explain the observations.
31
+
32
+ Uses multiple random restarts to avoid local minima.
33
+
34
+ Args:
35
+ lattice_params: Dictionary containing lattice parameters:
36
+ - a, b, c (float): Lattice constants in Angstroms
37
+ - alpha, beta, gamma (float): Lattice angles in degrees
38
+ diffraction_tests: List of dictionaries, each containing:
39
+ - H, K, L (float): Expected Miller indices
40
+ - energy (float): X-ray energy in eV
41
+ - tth (float): Scattering angle 2θ in degrees
42
+ - theta (float): Sample theta rotation in degrees
43
+ - phi (float): Sample phi rotation in degrees
44
+ - chi (float): Sample chi rotation in degrees
45
+ initial_guess: Initial guess for (roll, pitch, yaw) in degrees
46
+ n_restarts: Number of random restarts to try (default: 20)
47
+
48
+ Returns:
49
+ dict: Dictionary containing:
50
+ - roll, pitch, yaw (float): Optimized Euler angles in degrees
51
+ - residual_error (float): Final residual error (sum of squared HKL differences)
52
+ - individual_errors (list): Per-test HKL errors
53
+ - success (bool): Whether optimization converged
54
+ - message (str): Status message
55
+ """
56
+
57
+ if not diffraction_tests:
58
+ return {
59
+ "success": False,
60
+ "message": "No diffraction tests provided",
61
+ "roll": 0.0,
62
+ "pitch": 0.0,
63
+ "yaw": 0.0,
64
+ }
65
+
66
+ # Validate diffraction tests
67
+ required_keys = ["H", "K", "L", "energy", "tth", "theta", "phi", "chi"]
68
+ for i, test in enumerate(diffraction_tests):
69
+ missing = [k for k in required_keys if k not in test]
70
+ if missing:
71
+ return {
72
+ "success": False,
73
+ "message": f"Test {i+1} is missing required keys: {missing}",
74
+ "roll": 0.0,
75
+ "pitch": 0.0,
76
+ "yaw": 0.0,
77
+ }
78
+
79
+ # Initialize calculator with lattice parameters (using initial roll, pitch, yaw = 0)
80
+ calculator = OrientationCalculator()
81
+ init_params = {
82
+ "a": lattice_params["a"],
83
+ "b": lattice_params["b"],
84
+ "c": lattice_params["c"],
85
+ "alpha": lattice_params["alpha"],
86
+ "beta": lattice_params["beta"],
87
+ "gamma": lattice_params["gamma"],
88
+ "energy": diffraction_tests[0]["energy"], # Will be updated per test
89
+ "roll": 0.0,
90
+ "pitch": 0.0,
91
+ "yaw": 0.0,
92
+ }
93
+
94
+ if not calculator.initialize(init_params):
95
+ return {
96
+ "success": False,
97
+ "message": "Failed to initialize calculator with given lattice parameters",
98
+ "roll": 0.0,
99
+ "pitch": 0.0,
100
+ "yaw": 0.0,
101
+ }
102
+
103
+ def objective(params):
104
+ """Objective function: sum of squared HKL errors."""
105
+ roll, pitch, yaw = params
106
+ calculator.reorient_sample(roll, pitch, yaw)
107
+
108
+ total_error = 0.0
109
+ for test in diffraction_tests:
110
+ # Update energy for this test
111
+ calculator.change_energy(test["energy"])
112
+
113
+ # Calculate HKL from angles
114
+ result = calculator.calculate_hkl(
115
+ test["tth"], test["theta"], test["phi"], test["chi"]
116
+ )
117
+
118
+ # Compute squared error
119
+ dH = result["H"] - test["H"]
120
+ dK = result["K"] - test["K"]
121
+ dL = result["L"] - test["L"]
122
+ total_error += dH**2 + dK**2 + dL**2
123
+
124
+ return total_error
125
+
126
+ def run_optimization(start_point):
127
+ """Run a single optimization from a starting point."""
128
+ return minimize(
129
+ objective,
130
+ start_point,
131
+ method="L-BFGS-B",
132
+ bounds=[(-180, 180), (-180, 180), (-180, 180)],
133
+ options={"ftol": 1e-12, "gtol": 1e-10, "maxiter": 1000},
134
+ )
135
+
136
+ # Multiple random restarts to find global minimum
137
+ best_result = None
138
+ best_error = float("inf")
139
+
140
+ # First try the user-provided initial guess
141
+ initial_points = [initial_guess]
142
+
143
+ # Add random starting points
144
+ rng = np.random.default_rng(seed=42) # Reproducible results
145
+ for _ in range(n_restarts - 1):
146
+ # Random angles in [-180, 180]
147
+ random_point = tuple(rng.uniform(-180, 180, 3))
148
+ initial_points.append(random_point)
149
+
150
+ # Also add some structured starting points
151
+ structured_points = [
152
+ (0, 0, 0),
153
+ (90, 0, 0), (-90, 0, 0),
154
+ (0, 90, 0), (0, -90, 0),
155
+ (0, 0, 90), (0, 0, -90),
156
+ ]
157
+ for pt in structured_points:
158
+ if pt not in initial_points:
159
+ initial_points.append(pt)
160
+
161
+ # Run optimization from each starting point
162
+ for start_point in initial_points:
163
+ try:
164
+ result = run_optimization(start_point)
165
+ if result.fun < best_error:
166
+ best_error = result.fun
167
+ best_result = result
168
+
169
+ # Early stopping if we found a very good solution
170
+ if best_error < TARGET_RESIDUAL:
171
+ break
172
+ except Exception:
173
+ # Skip failed optimizations
174
+ continue
175
+
176
+ if best_result is None:
177
+ return {
178
+ "success": False,
179
+ "message": "All optimization attempts failed",
180
+ "roll": 0.0,
181
+ "pitch": 0.0,
182
+ "yaw": 0.0,
183
+ }
184
+
185
+ # Extract optimized parameters
186
+ roll_opt, pitch_opt, yaw_opt = best_result.x
187
+
188
+ # Calculate individual errors at optimal orientation
189
+ calculator.reorient_sample(roll_opt, pitch_opt, yaw_opt)
190
+ individual_errors = []
191
+ for test in diffraction_tests:
192
+ calculator.change_energy(test["energy"])
193
+ calc_result = calculator.calculate_hkl(
194
+ test["tth"], test["theta"], test["phi"], test["chi"]
195
+ )
196
+ error = {
197
+ "H_expected": test["H"],
198
+ "K_expected": test["K"],
199
+ "L_expected": test["L"],
200
+ "H_calculated": calc_result["H"],
201
+ "K_calculated": calc_result["K"],
202
+ "L_calculated": calc_result["L"],
203
+ "H_error": calc_result["H"] - test["H"],
204
+ "K_error": calc_result["K"] - test["K"],
205
+ "L_error": calc_result["L"] - test["L"],
206
+ }
207
+ individual_errors.append(error)
208
+
209
+ return {
210
+ "success": best_result.success,
211
+ "message": best_result.message if hasattr(best_result, "message") else "Optimization completed",
212
+ "roll": float(roll_opt),
213
+ "pitch": float(pitch_opt),
214
+ "yaw": float(yaw_opt),
215
+ "residual_error": float(best_result.fun),
216
+ "individual_errors": individual_errors,
217
+ "n_iterations": best_result.nit if hasattr(best_result, "nit") else None,
218
+ "n_restarts_used": len(initial_points),
219
+ }
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Lightweight calculator for orientation fitting.
4
+
5
+ This module provides a minimal calculator class that can compute HKL values
6
+ from scattering angles, used specifically for orientation fitting.
7
+ It avoids importing from feature modules to prevent circular dependencies.
8
+ """
9
+
10
+ import numpy as np
11
+
12
+ from advisor.domain import angle_to_matrix
13
+ from advisor.domain.core import Lab
14
+
15
+
16
+ class OrientationCalculator:
17
+ """Lightweight version of Brillouincalculator for orientation fitting (Set UB Matrix calculation).
18
+
19
+ This class provides only the methods needed for fitting crystal orientation (UB matrix)
20
+ from diffraction data.
21
+ """
22
+
23
+ # Physical constants
24
+ EV_TO_LAMBDA = 12398.42 # eV to Angstrom conversion
25
+
26
+ def __init__(self):
27
+ """Initialize the calculator."""
28
+ self._initialized = False
29
+ self.lab = Lab()
30
+ self.energy = None
31
+ self.lambda_A = None
32
+ self.k_in = None
33
+
34
+ def initialize(self, params: dict) -> bool:
35
+ """Initialize with lattice parameters.
36
+
37
+ Args:
38
+ params: Dictionary containing:
39
+ - a, b, c (float): Lattice constants in Angstroms
40
+ - alpha, beta, gamma (float): Lattice angles in degrees
41
+ - energy (float): X-ray energy in eV
42
+ - roll, pitch, yaw (float, optional): Euler angles in degrees
43
+
44
+ Returns:
45
+ bool: True if initialization was successful
46
+ """
47
+ try:
48
+ a = params.get("a", 4.0)
49
+ b = params.get("b", 4.0)
50
+ c = params.get("c", 12.0)
51
+ alpha = params.get("alpha", 90.0)
52
+ beta = params.get("beta", 90.0)
53
+ gamma = params.get("gamma", 90.0)
54
+ roll = params.get("roll", 0.0)
55
+ pitch = params.get("pitch", 0.0)
56
+ yaw = params.get("yaw", 0.0)
57
+ self.energy = params["energy"]
58
+
59
+ # Initialize lab with default sample rotation (0, 0, 0)
60
+ self.lab.initialize(a, b, c, alpha, beta, gamma, roll, pitch, yaw, 0, 0, 0)
61
+
62
+ # Calculate wavelength and wavevector
63
+ self.lambda_A = self.EV_TO_LAMBDA / self.energy
64
+ self.k_in = 2 * np.pi / self.lambda_A
65
+
66
+ self._initialized = True
67
+ return True
68
+ except Exception as e:
69
+ print(f"Error initializing OrientationCalculator: {e}")
70
+ return False
71
+
72
+ def change_energy(self, energy: float) -> bool:
73
+ """Change the X-ray energy.
74
+
75
+ Args:
76
+ energy: X-ray energy in eV
77
+
78
+ Returns:
79
+ bool: True if successful
80
+ """
81
+ self.energy = energy
82
+ self.lambda_A = self.EV_TO_LAMBDA / self.energy
83
+ self.k_in = 2 * np.pi / self.lambda_A
84
+ return True
85
+
86
+ def reorient_sample(self, roll: float, pitch: float, yaw: float) -> bool:
87
+ """Reorient the sample (change Euler angles).
88
+
89
+ Args:
90
+ roll, pitch, yaw: Euler angles in degrees
91
+
92
+ Returns:
93
+ bool: True if successful
94
+ """
95
+ self.lab.reorient(roll, pitch, yaw)
96
+ return True
97
+
98
+ def calculate_hkl(
99
+ self, tth: float, theta: float, phi: float, chi: float
100
+ ) -> dict:
101
+ """Calculate HKL from scattering angles.
102
+
103
+ Args:
104
+ tth: Scattering angle 2θ in degrees
105
+ theta: Sample theta rotation in degrees
106
+ phi: Sample phi rotation in degrees
107
+ chi: Sample chi rotation in degrees
108
+
109
+ Returns:
110
+ dict: Dictionary containing:
111
+ - H, K, L (float): Miller indices
112
+ - tth, theta, phi, chi (float): Input angles
113
+ - success (bool): Whether calculation succeeded
114
+ - error (str or None): Error message if any
115
+ """
116
+ if not self._initialized:
117
+ return {
118
+ "H": None, "K": None, "L": None,
119
+ "tth": tth, "theta": theta, "phi": phi, "chi": chi,
120
+ "success": False,
121
+ "error": "Calculator not initialized",
122
+ }
123
+
124
+ try:
125
+ # Get real space vectors in lab frame
126
+ a_vec_lab, b_vec_lab, c_vec_lab = self.lab.get_real_space_vectors()
127
+
128
+ # Calculate momentum transfer magnitude
129
+ k_magnitude = 2.0 * self.k_in * np.sin(np.radians(tth / 2.0))
130
+
131
+ # Calculate delta angle
132
+ delta = 90 - (tth / 2.0)
133
+ sin_delta = np.sin(np.radians(delta))
134
+ cos_delta = np.cos(np.radians(delta))
135
+
136
+ # Momentum transfer at theta, phi, chi = 0
137
+ k_vec_initial = np.array(
138
+ [-k_magnitude * sin_delta, -k_magnitude * cos_delta, 0.0]
139
+ )
140
+
141
+ # Rotation of the beam is the reverse rotation of the sample
142
+ rotation_matrix = angle_to_matrix(theta, phi, chi).T
143
+
144
+ # Momentum transfer at the given angles
145
+ k_vec_lab = rotation_matrix @ k_vec_initial
146
+
147
+ # Calculate HKL by projecting onto real space vectors
148
+ H = np.dot(k_vec_lab, a_vec_lab) / (2 * np.pi)
149
+ K = np.dot(k_vec_lab, b_vec_lab) / (2 * np.pi)
150
+ L = np.dot(k_vec_lab, c_vec_lab) / (2 * np.pi)
151
+
152
+ return {
153
+ "H": H,
154
+ "K": K,
155
+ "L": L,
156
+ "tth": tth,
157
+ "theta": theta,
158
+ "phi": phi,
159
+ "chi": chi,
160
+ "success": True,
161
+ "error": None,
162
+ }
163
+ except Exception as e:
164
+ return {
165
+ "H": None, "K": None, "L": None,
166
+ "tth": tth, "theta": theta, "phi": phi, "chi": chi,
167
+ "success": False,
168
+ "error": str(e),
169
+ }
170
+
171
+ def is_initialized(self) -> bool:
172
+ """Check if the calculator is initialized."""
173
+ return self._initialized
@@ -1,6 +1,9 @@
1
- """Feature packages."""
1
+ """Feature packages.
2
2
 
3
- from advisor.features.scattering_geometry.controllers import ScatteringGeometryController
4
- from advisor.features.structure_factor.controllers import StructureFactorController
3
+ Note: Controllers are not exported here to avoid circular imports.
4
+ Import them directly from their modules:
5
+ from advisor.features.scattering_geometry.controllers import ScatteringGeometryController
6
+ from advisor.features.structure_factor.controllers import StructureFactorController
7
+ """
5
8
 
6
- __all__ = ["ScatteringGeometryController", "StructureFactorController"]
9
+ __all__ = []
@@ -105,6 +105,18 @@ class BrillouinCalculator:
105
105
  print(f"Error initializing calculator: {str(e)}")
106
106
  return False
107
107
 
108
+ def change_energy(self, energy):
109
+ """Change the energy of the X-ray source, in eV"""
110
+ self.energy = energy
111
+ self.lambda_A = self.ev_to_lambda / self.energy
112
+ self.k_in = 2 * np.pi / self.lambda_A
113
+ return True
114
+
115
+ def reorient_sample(self, roll, pitch, yaw):
116
+ """Reorient the sample with respect to the lab"""
117
+ self.lab.reorient(roll, pitch, yaw)
118
+ return True
119
+
108
120
  def _sample_to_lab_conversion(self, a_vec, b_vec, c_vec):
109
121
  """Convert vectors from sample coordinate system to lab coordinate system."""
110
122
  # For now, just return the same vectors
@@ -434,7 +446,8 @@ class BrillouinCalculator:
434
446
  "error": None,
435
447
  "feasible": all_feasible,
436
448
  }
437
-
449
+
450
+
438
451
  def is_initialized(self):
439
452
  """Check if the calculator is initialized.
440
453
 
@@ -459,6 +472,35 @@ class BrillouinCalculator:
459
472
  "gamma": gamma,
460
473
  }
461
474
 
475
+
476
+ def get_max_hkl_values(self, tth):
477
+ """Get the maximum HKL values, valid only for orthorhombic crystals.
478
+
479
+ Returns:
480
+ tuple: Maximum HKL values
481
+ """
482
+ if not self.is_initialized():
483
+ raise ValueError("Calculator not initialized")
484
+ a, b, c, alpha, beta, gamma = self.lab.get_lattice_parameters()
485
+ k_magnitude = self.get_k_magnitude(tth)
486
+ h_max = k_magnitude / (2 * np.pi / a)
487
+ k_max = k_magnitude / (2 * np.pi / b)
488
+ l_max = k_magnitude / (2 * np.pi / c)
489
+ if alpha != 90.0 or beta != 90.0 or gamma != 90.0:
490
+ success = False
491
+ message = "The crystal is not orthorhombic,\nthe accesible area is just approximate."
492
+ else:
493
+ success = True
494
+ message = None
495
+ return {
496
+ "h_max": h_max,
497
+ "k_max": k_max,
498
+ "l_max": l_max,
499
+ "success": success,
500
+ "message": message,
501
+ }
502
+
503
+
462
504
  def get_real_space_vectors(self):
463
505
  """Get the real space vectors.
464
506
 
@@ -253,7 +253,7 @@ def _calculate_angles_chi_fixed(
253
253
  yaw,
254
254
  chi_fixed,
255
255
  target_objective=1e-10,
256
- max_restarts=20,
256
+ max_restarts=40,
257
257
  ):
258
258
  """Calculate scattering angles with chi angle (in degrees) fixed.
259
259
 
@@ -268,7 +268,7 @@ def _calculate_angles_chi_fixed(
268
268
  roll, pitch, yaw (float): Lattice rotation Euler angles in degrees. We use ZYX convention.
269
269
  chi_fixed (float): Fixed chi angle in degrees
270
270
  target_objective (float, optional): Convergence tolerance for fsolve. Defaults to 1e-10.
271
- max_restarts (int, optional): Maximum number of random restarts. Defaults to 20.
271
+ max_restarts (int, optional): Maximum number of random restarts. Defaults to 40.
272
272
 
273
273
  Returns:
274
274
  dict: Dictionary containing:
@@ -384,7 +384,7 @@ def _calculate_angles_phi_fixed(
384
384
  yaw,
385
385
  phi_fixed,
386
386
  target_objective=1e-10,
387
- max_restarts=20,
387
+ max_restarts=40,
388
388
  ):
389
389
  """Calculate scattering angles with phi angle fixed.
390
390
 
@@ -399,7 +399,7 @@ def _calculate_angles_phi_fixed(
399
399
  roll, pitch, yaw (float): Lattice rotation Euler angles in degrees. We use ZYX convention.
400
400
  phi_fixed (float): Fixed phi angle in degrees
401
401
  target_objective (float, optional): Convergence tolerance for fsolve. Defaults to 1e-10.
402
- max_restarts (int, optional): Maximum number of random restarts. Defaults to 20.
402
+ max_restarts (int, optional): Maximum number of random restarts. Defaults to 40.
403
403
 
404
404
  Returns:
405
405
  dict: Dictionary containing:
@@ -1,17 +1,9 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
3
  # pylint: disable=no-name-in-module, import-error
4
- from PyQt5.QtWidgets import (
5
- QWidget,
6
- QVBoxLayout,
7
- QFormLayout,
8
- QGroupBox,
9
- QLabel,
10
- QPushButton,
11
- QDoubleSpinBox,
12
- QLineEdit,
13
- )
14
4
  from PyQt5.QtCore import pyqtSignal
5
+ from PyQt5.QtWidgets import (QDoubleSpinBox, QFormLayout, QGroupBox, QLabel,
6
+ QLineEdit, QPushButton, QVBoxLayout, QWidget)
15
7
 
16
8
 
17
9
  class AnglesToHKLControls(QWidget):
@@ -48,14 +40,6 @@ class AnglesToHKLControls(QWidget):
48
40
  self.theta_input.valueChanged.connect(self.anglesChanged.emit)
49
41
  form_layout.addRow("θ:", self.theta_input)
50
42
 
51
- # phi input
52
- self.phi_input = QDoubleSpinBox()
53
- self.phi_input.setRange(-180.0, 180.0)
54
- self.phi_input.setValue(0.0)
55
- self.phi_input.setSuffix(" °")
56
- self.phi_input.valueChanged.connect(self.anglesChanged.emit)
57
- form_layout.addRow("φ:", self.phi_input)
58
-
59
43
  # chi input
60
44
  self.chi_input = QDoubleSpinBox()
61
45
  self.chi_input.setRange(-180.0, 180.0)
@@ -64,6 +48,14 @@ class AnglesToHKLControls(QWidget):
64
48
  self.chi_input.valueChanged.connect(self.anglesChanged.emit)
65
49
  form_layout.addRow("χ:", self.chi_input)
66
50
 
51
+ # phi input
52
+ self.phi_input = QDoubleSpinBox()
53
+ self.phi_input.setRange(-180.0, 180.0)
54
+ self.phi_input.setValue(0.0)
55
+ self.phi_input.setSuffix(" °")
56
+ self.phi_input.valueChanged.connect(self.anglesChanged.emit)
57
+ form_layout.addRow("φ:", self.phi_input)
58
+
67
59
  main_layout.addWidget(form_group)
68
60
 
69
61
  # Calculate button