advisor-scattering 0.5.2__py3-none-any.whl → 0.5.3__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
@@ -145,24 +142,34 @@ class BrillouinCalculator:
145
142
  ):
146
143
  """Calculate scattering angles from HKL indices.
147
144
 
148
- CURRENTLY THE CHI IS FIXED TO 0, TO BE EXTENDED
145
+ Returns up to two solutions if they exist.
149
146
 
150
147
  Args:
151
- h, k, l (float): HKL indices
148
+ H, K, L (float): HKL indices
149
+ fixed_angle (float): Value of the fixed angle in degrees
150
+ fixed_angle_name (str): Name of the fixed angle ('chi' or 'phi')
152
151
 
153
152
  Returns:
154
- dict: Dictionary containing scattering angles and minimum energy
153
+ dict: Dictionary containing:
154
+ - tth (list): Scattering angles in degrees
155
+ - theta (list): Sample theta rotation values in degrees
156
+ - phi (list): Sample phi rotation values in degrees
157
+ - chi (list): Sample chi rotation values in degrees
158
+ - H, K, L (float): Input HKL indices
159
+ - number_of_solutions (int): Number of distinct solutions found
160
+ - feasible (list): Boolean list indicating if each solution is feasible
161
+ - success (bool): Whether calculation succeeded
162
+ - error (str or None): Error message if any
155
163
  """
156
164
 
157
165
  if not self.is_initialized():
158
166
  raise ValueError("Calculator not initialized")
159
167
 
160
-
161
168
  calculate_angles = _calculate_angles_factory(fixed_angle_name)
162
169
  a, b, c, alpha, beta, gamma = self.lab.get_lattice_parameters()
163
170
  roll, pitch, yaw = self.lab.get_lattice_angles()
164
171
  try:
165
- tth_result, theta_result, phi_result, chi_result = calculate_angles(
172
+ result = calculate_angles(
166
173
  self.k_in,
167
174
  H,
168
175
  K,
@@ -181,19 +188,31 @@ class BrillouinCalculator:
181
188
  except Exception as e:
182
189
  return {
183
190
  "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),
191
+ "error": "No solution found; The Q point is possibly not reachable at this energy and/or scattering angle tth. System message:" + str(e),
185
192
  }
193
+
194
+ # Extract lists from result
195
+ tth_list = result["tth"]
196
+ theta_list = result["theta"]
197
+ phi_list = result["phi"]
198
+ chi_list = result["chi"]
199
+ num_solutions = result["number_of_solutions"]
200
+
201
+ # Calculate feasibility for each solution
202
+ feasible_list = [is_feasible(theta_list[i], tth_list[i]) for i in range(len(theta_list))]
203
+
186
204
  return {
187
- "tth": tth_result,
188
- "theta": theta_result,
189
- "phi": phi_result,
190
- "chi": chi_result,
205
+ "tth": tth_list,
206
+ "theta": theta_list,
207
+ "phi": phi_list,
208
+ "chi": chi_list,
191
209
  "H": H,
192
210
  "K": K,
193
211
  "L": L,
212
+ "number_of_solutions": num_solutions,
194
213
  "success": True,
195
214
  "error": None,
196
- "feasible": is_feasible(theta_result, tth_result),
215
+ "feasible": feasible_list,
197
216
  }
198
217
 
199
218
  def calculate_angles_tth_fixed(
@@ -205,52 +224,77 @@ class BrillouinCalculator:
205
224
  fixed_angle_name="chi",
206
225
  fixed_angle=0.0,
207
226
  ):
227
+ """Calculate scattering angles with fixed tth.
228
+
229
+ Returns up to two solutions if they exist.
230
+
231
+ Args:
232
+ tth (float): Fixed scattering angle in degrees
233
+ H, K, L (float): HKL indices (one should be None to be solved)
234
+ fixed_angle_name (str): Name of the fixed angle ('chi' or 'phi')
235
+ fixed_angle (float): Value of the fixed angle in degrees
236
+
237
+ Returns:
238
+ dict: Dictionary containing lists of solutions and metadata
239
+ """
208
240
  a, b, c, alpha, beta, gamma = self.lab.get_lattice_parameters()
209
241
  roll, pitch, yaw = self.lab.get_lattice_angles()
210
242
 
211
243
  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
- )
244
+ result = _calculate_angles_tth_fixed(
245
+ self.k_in,
246
+ tth,
247
+ a,
248
+ b,
249
+ c,
250
+ alpha,
251
+ beta,
252
+ gamma,
253
+ roll,
254
+ pitch,
255
+ yaw,
256
+ H,
257
+ K,
258
+ L,
259
+ fixed_angle_name,
260
+ fixed_angle,
231
261
  )
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
262
+
263
+ # Extract data from result
264
+ tth_list = result["tth"]
265
+ theta_list = result["theta"]
266
+ phi_list = result["phi"]
267
+ chi_list = result["chi"]
268
+ momentum = result["momentum"]
269
+ num_solutions = result["number_of_solutions"]
270
+
271
+ # Update the solved HKL component
272
+ H_result = momentum if H is None else H
273
+ K_result = momentum if K is None else K
274
+ L_result = momentum if L is None else L
275
+
235
276
  except Exception as e:
236
277
  return {
237
278
  "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),
279
+ "error": "No solution found; The Q point is possibly not reachable at this energy and/or scattering angle tth. System message:" + str(e),
239
280
  }
240
281
 
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,
282
+ # Calculate feasibility for each solution
283
+ feasible_list = [is_feasible(theta_list[i], tth_list[i]) for i in range(len(theta_list))]
284
+
285
+ return {
286
+ "tth": tth_list,
287
+ "theta": theta_list,
288
+ "phi": phi_list,
289
+ "chi": chi_list,
290
+ "H": H_result,
291
+ "K": K_result,
292
+ "L": L_result,
293
+ "number_of_solutions": num_solutions,
249
294
  "success": True,
250
295
  "error": None,
251
- "feasible": is_feasible(theta_result, tth_result),
296
+ "feasible": feasible_list,
252
297
  }
253
- return result
254
298
 
255
299
  def calculate_angles_tth_fixed_scan(
256
300
  self,
@@ -264,6 +308,9 @@ class BrillouinCalculator:
264
308
  ):
265
309
  """Calculate scattering angles for a range of HKL values with fixed tth.
266
310
 
311
+ Each HKL point may have up to 2 solutions. Results are flattened into lists
312
+ with solution_group indicating which original HKL point each solution belongs to.
313
+
267
314
  Args:
268
315
  tth (float): Fixed scattering angle in degrees
269
316
  start_points (tuple): Starting HKL values (the deactivated index will be ignored)
@@ -315,7 +362,7 @@ class BrillouinCalculator:
315
362
  else [None] * num_points
316
363
  )
317
364
 
318
- # Initialize result lists
365
+ # Initialize result lists - flattened to include all solutions
319
366
  all_tth = []
320
367
  all_theta = []
321
368
  all_phi = []
@@ -323,6 +370,9 @@ class BrillouinCalculator:
323
370
  all_h = []
324
371
  all_k = []
325
372
  all_l = []
373
+ all_feasible = []
374
+ all_solution_group = [] # Track which HKL point each solution belongs to
375
+ all_solution_index = [] # Track if it's solution 1 or 2 within the group
326
376
 
327
377
  # Calculate for each point
328
378
  for i in range(num_points):
@@ -344,14 +394,20 @@ class BrillouinCalculator:
344
394
  # Skip points that fail to calculate but don't fail completely
345
395
  continue
346
396
 
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"])
397
+ # Each calculation returns lists of solutions
398
+ num_solutions = result.get("number_of_solutions", 1)
399
+ for sol_idx in range(num_solutions):
400
+ all_tth.append(result["tth"][sol_idx])
401
+ all_theta.append(result["theta"][sol_idx])
402
+ all_phi.append(result["phi"][sol_idx])
403
+ all_chi.append(result["chi"][sol_idx])
404
+ all_h.append(result["H"])
405
+ all_k.append(result["K"])
406
+ all_l.append(result["L"])
407
+ all_feasible.append(result["feasible"][sol_idx])
408
+ all_solution_group.append(i) # Which HKL point
409
+ all_solution_index.append(sol_idx + 1) # Solution 1 or 2
410
+
355
411
  except Exception as e:
356
412
  # Log the error but continue with other points
357
413
  print(f"Error calculating point {(h, k, l)}: {str(e)}")
@@ -371,10 +427,12 @@ class BrillouinCalculator:
371
427
  "H": all_h,
372
428
  "K": all_k,
373
429
  "L": all_l,
374
- "deactivated_index": deactivated_index, # Store which index was deactivated
430
+ "deactivated_index": deactivated_index,
431
+ "solution_group": all_solution_group,
432
+ "solution_index": all_solution_index,
375
433
  "success": True,
376
434
  "error": None,
377
- "feasible": is_feasible(all_theta, all_tth),
435
+ "feasible": all_feasible,
378
436
  }
379
437
 
380
438
  def is_initialized(self):