advisor-scattering 0.5.2__py3-none-any.whl → 0.9.1__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.
@@ -5,11 +5,9 @@ from scipy.optimize import fsolve
5
5
 
6
6
  from advisor.domain import angle_to_matrix
7
7
  from advisor.domain.core import Lab
8
- from .core import (
9
- _calculate_angles_factory,
10
- _calculate_angles_tth_fixed,
11
- _calculate_hkl,
12
- )
8
+
9
+ from .core import (_calculate_angles_factory, _calculate_angles_tth_fixed,
10
+ _calculate_hkl)
13
11
 
14
12
 
15
13
  def is_feasible(theta, tth):
@@ -34,6 +32,7 @@ class BrillouinCalculator:
34
32
  self.hPlanck = 6.62607015e-34 # Planck's constant [J·s]
35
33
  self.c_light = 299792458 # Speed of light [m/s]
36
34
  self.e = 1.602176634e-19 # Elementary charge [C]
35
+ self.ev_to_lambda = 12398.42 # eV to Angstrom
37
36
 
38
37
  # Initialize sample
39
38
  self.lab = Lab()
@@ -97,9 +96,7 @@ class BrillouinCalculator:
97
96
  chi,
98
97
  )
99
98
  # Calculate wavelength and wavevector
100
- self.lambda_A = (
101
- (self.hPlanck * self.c_light) / (self.energy * self.e) * 1e10
102
- )
99
+ self.lambda_A = self.ev_to_lambda / self.energy
103
100
  self.k_in = 2 * np.pi / self.lambda_A
104
101
 
105
102
  self._initialized = True
@@ -108,6 +105,18 @@ class BrillouinCalculator:
108
105
  print(f"Error initializing calculator: {str(e)}")
109
106
  return False
110
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
+
111
120
  def _sample_to_lab_conversion(self, a_vec, b_vec, c_vec):
112
121
  """Convert vectors from sample coordinate system to lab coordinate system."""
113
122
  # For now, just return the same vectors
@@ -145,24 +154,34 @@ class BrillouinCalculator:
145
154
  ):
146
155
  """Calculate scattering angles from HKL indices.
147
156
 
148
- CURRENTLY THE CHI IS FIXED TO 0, TO BE EXTENDED
157
+ Returns up to two solutions if they exist.
149
158
 
150
159
  Args:
151
- h, k, l (float): HKL indices
160
+ H, K, L (float): HKL indices
161
+ fixed_angle (float): Value of the fixed angle in degrees
162
+ fixed_angle_name (str): Name of the fixed angle ('chi' or 'phi')
152
163
 
153
164
  Returns:
154
- dict: Dictionary containing scattering angles and minimum energy
165
+ dict: Dictionary containing:
166
+ - tth (list): Scattering angles in degrees
167
+ - theta (list): Sample theta rotation values in degrees
168
+ - phi (list): Sample phi rotation values in degrees
169
+ - chi (list): Sample chi rotation values in degrees
170
+ - H, K, L (float): Input HKL indices
171
+ - number_of_solutions (int): Number of distinct solutions found
172
+ - feasible (list): Boolean list indicating if each solution is feasible
173
+ - success (bool): Whether calculation succeeded
174
+ - error (str or None): Error message if any
155
175
  """
156
176
 
157
177
  if not self.is_initialized():
158
178
  raise ValueError("Calculator not initialized")
159
179
 
160
-
161
180
  calculate_angles = _calculate_angles_factory(fixed_angle_name)
162
181
  a, b, c, alpha, beta, gamma = self.lab.get_lattice_parameters()
163
182
  roll, pitch, yaw = self.lab.get_lattice_angles()
164
183
  try:
165
- tth_result, theta_result, phi_result, chi_result = calculate_angles(
184
+ result = calculate_angles(
166
185
  self.k_in,
167
186
  H,
168
187
  K,
@@ -181,19 +200,31 @@ class BrillouinCalculator:
181
200
  except Exception as e:
182
201
  return {
183
202
  "success": False,
184
- "error": "No solution found; The Q point is possible not reachable at this energy and/or scattering angle tth. System message:" + str(e),
203
+ "error": "No solution found; The Q point is possibly not reachable at this energy and/or scattering angle tth. System message:" + str(e),
185
204
  }
205
+
206
+ # Extract lists from result
207
+ tth_list = result["tth"]
208
+ theta_list = result["theta"]
209
+ phi_list = result["phi"]
210
+ chi_list = result["chi"]
211
+ num_solutions = result["number_of_solutions"]
212
+
213
+ # Calculate feasibility for each solution
214
+ feasible_list = [is_feasible(theta_list[i], tth_list[i]) for i in range(len(theta_list))]
215
+
186
216
  return {
187
- "tth": tth_result,
188
- "theta": theta_result,
189
- "phi": phi_result,
190
- "chi": chi_result,
217
+ "tth": tth_list,
218
+ "theta": theta_list,
219
+ "phi": phi_list,
220
+ "chi": chi_list,
191
221
  "H": H,
192
222
  "K": K,
193
223
  "L": L,
224
+ "number_of_solutions": num_solutions,
194
225
  "success": True,
195
226
  "error": None,
196
- "feasible": is_feasible(theta_result, tth_result),
227
+ "feasible": feasible_list,
197
228
  }
198
229
 
199
230
  def calculate_angles_tth_fixed(
@@ -205,52 +236,77 @@ class BrillouinCalculator:
205
236
  fixed_angle_name="chi",
206
237
  fixed_angle=0.0,
207
238
  ):
239
+ """Calculate scattering angles with fixed tth.
240
+
241
+ Returns up to two solutions if they exist.
242
+
243
+ Args:
244
+ tth (float): Fixed scattering angle in degrees
245
+ H, K, L (float): HKL indices (one should be None to be solved)
246
+ fixed_angle_name (str): Name of the fixed angle ('chi' or 'phi')
247
+ fixed_angle (float): Value of the fixed angle in degrees
248
+
249
+ Returns:
250
+ dict: Dictionary containing lists of solutions and metadata
251
+ """
208
252
  a, b, c, alpha, beta, gamma = self.lab.get_lattice_parameters()
209
253
  roll, pitch, yaw = self.lab.get_lattice_angles()
210
254
 
211
255
  try:
212
- tth_result, theta_result, phi_result, chi_result, momentum = (
213
- _calculate_angles_tth_fixed(
214
- self.k_in,
215
- tth,
216
- a,
217
- b,
218
- c,
219
- alpha,
220
- beta,
221
- gamma,
222
- roll,
223
- pitch,
224
- yaw,
225
- H,
226
- K,
227
- L,
228
- fixed_angle_name,
229
- fixed_angle,
230
- )
256
+ result = _calculate_angles_tth_fixed(
257
+ self.k_in,
258
+ tth,
259
+ a,
260
+ b,
261
+ c,
262
+ alpha,
263
+ beta,
264
+ gamma,
265
+ roll,
266
+ pitch,
267
+ yaw,
268
+ H,
269
+ K,
270
+ L,
271
+ fixed_angle_name,
272
+ fixed_angle,
231
273
  )
232
- H = momentum if H is None else H
233
- K = momentum if K is None else K
234
- L = momentum if L is None else L
274
+
275
+ # Extract data from result
276
+ tth_list = result["tth"]
277
+ theta_list = result["theta"]
278
+ phi_list = result["phi"]
279
+ chi_list = result["chi"]
280
+ momentum = result["momentum"]
281
+ num_solutions = result["number_of_solutions"]
282
+
283
+ # Update the solved HKL component
284
+ H_result = momentum if H is None else H
285
+ K_result = momentum if K is None else K
286
+ L_result = momentum if L is None else L
287
+
235
288
  except Exception as e:
236
289
  return {
237
290
  "success": False,
238
- "error": "No solution found; The Q point is possible not reachable at this energy and/or scattering angle tth. System message:" + str(e),
291
+ "error": "No solution found; The Q point is possibly not reachable at this energy and/or scattering angle tth. System message:" + str(e),
239
292
  }
240
293
 
241
- result = {
242
- "tth": tth_result,
243
- "theta": theta_result,
244
- "phi": phi_result,
245
- "chi": chi_result,
246
- "H": H,
247
- "K": K,
248
- "L": L,
294
+ # Calculate feasibility for each solution
295
+ feasible_list = [is_feasible(theta_list[i], tth_list[i]) for i in range(len(theta_list))]
296
+
297
+ return {
298
+ "tth": tth_list,
299
+ "theta": theta_list,
300
+ "phi": phi_list,
301
+ "chi": chi_list,
302
+ "H": H_result,
303
+ "K": K_result,
304
+ "L": L_result,
305
+ "number_of_solutions": num_solutions,
249
306
  "success": True,
250
307
  "error": None,
251
- "feasible": is_feasible(theta_result, tth_result),
308
+ "feasible": feasible_list,
252
309
  }
253
- return result
254
310
 
255
311
  def calculate_angles_tth_fixed_scan(
256
312
  self,
@@ -264,6 +320,9 @@ class BrillouinCalculator:
264
320
  ):
265
321
  """Calculate scattering angles for a range of HKL values with fixed tth.
266
322
 
323
+ Each HKL point may have up to 2 solutions. Results are flattened into lists
324
+ with solution_group indicating which original HKL point each solution belongs to.
325
+
267
326
  Args:
268
327
  tth (float): Fixed scattering angle in degrees
269
328
  start_points (tuple): Starting HKL values (the deactivated index will be ignored)
@@ -315,7 +374,7 @@ class BrillouinCalculator:
315
374
  else [None] * num_points
316
375
  )
317
376
 
318
- # Initialize result lists
377
+ # Initialize result lists - flattened to include all solutions
319
378
  all_tth = []
320
379
  all_theta = []
321
380
  all_phi = []
@@ -323,6 +382,9 @@ class BrillouinCalculator:
323
382
  all_h = []
324
383
  all_k = []
325
384
  all_l = []
385
+ all_feasible = []
386
+ all_solution_group = [] # Track which HKL point each solution belongs to
387
+ all_solution_index = [] # Track if it's solution 1 or 2 within the group
326
388
 
327
389
  # Calculate for each point
328
390
  for i in range(num_points):
@@ -344,14 +406,20 @@ class BrillouinCalculator:
344
406
  # Skip points that fail to calculate but don't fail completely
345
407
  continue
346
408
 
347
- # Each calculation can return multiple solutions
348
- all_tth.append(result["tth"])
349
- all_theta.append(result["theta"])
350
- all_phi.append(result["phi"])
351
- all_chi.append(result["chi"])
352
- all_h.append(result["H"])
353
- all_k.append(result["K"])
354
- all_l.append(result["L"])
409
+ # Each calculation returns lists of solutions
410
+ num_solutions = result.get("number_of_solutions", 1)
411
+ for sol_idx in range(num_solutions):
412
+ all_tth.append(result["tth"][sol_idx])
413
+ all_theta.append(result["theta"][sol_idx])
414
+ all_phi.append(result["phi"][sol_idx])
415
+ all_chi.append(result["chi"][sol_idx])
416
+ all_h.append(result["H"])
417
+ all_k.append(result["K"])
418
+ all_l.append(result["L"])
419
+ all_feasible.append(result["feasible"][sol_idx])
420
+ all_solution_group.append(i) # Which HKL point
421
+ all_solution_index.append(sol_idx + 1) # Solution 1 or 2
422
+
355
423
  except Exception as e:
356
424
  # Log the error but continue with other points
357
425
  print(f"Error calculating point {(h, k, l)}: {str(e)}")
@@ -371,10 +439,12 @@ class BrillouinCalculator:
371
439
  "H": all_h,
372
440
  "K": all_k,
373
441
  "L": all_l,
374
- "deactivated_index": deactivated_index, # Store which index was deactivated
442
+ "deactivated_index": deactivated_index,
443
+ "solution_group": all_solution_group,
444
+ "solution_index": all_solution_index,
375
445
  "success": True,
376
446
  "error": None,
377
- "feasible": is_feasible(all_theta, all_tth),
447
+ "feasible": all_feasible,
378
448
  }
379
449
 
380
450
  def is_initialized(self):