@@ -291,3 +291,119 @@ def find_near_minimum_settings(
291291 results .sort (key = lambda x : sum (x ['cell' ][:3 ]))
292292
293293 return results
294+
295+
296+ def find_near_minimum_settings_multiples (
297+ cell ,
298+ length_tolerance = 0.03 ,
299+ angle_tolerance = 3.0 ,
300+ max_search_index = 10 ,
301+ ):
302+ """
303+ Find nearly-reduced settings across order-2 super- and subcells.
304+
305+ This function extends find_near_minimum_settings by searching not only the
306+ input cell, but also 7 order-2 supercells and 7 order-2 subcells (14 total
307+ additional cells). This helps find alternative settings that might be missed
308+ due to reduction boundaries when the true cell might be a multiple or
309+ submultiple of the measured cell.
310+
311+ Parameters
312+ ----------
313+ cell : tuple or unit_cell
314+ (a, b, c, alpha, beta, gamma) - axes in Angstrom, angles in degrees
315+ length_tolerance : float
316+ Fractional tolerance for length perturbations (e.g., 0.03 = 3%)
317+ angle_tolerance : float
318+ Tolerance for angle perturbations in degrees
319+ max_search_index : int
320+ Search lattice vectors up to +/-max_search_index
321+
322+ Returns
323+ -------
324+ list of dict
325+ Each dict contains:
326+ - 'P': transformation matrix (3x3 integer or float array) including
327+ the super/subcell transformation
328+ - 'cell': transformed cell parameters (tuple of 6 floats)
329+ - 'G': transformed metric tensor (3x3 array)
330+ """
331+ # Handle cctbx unit_cell objects
332+ if hasattr (cell , 'parameters' ):
333+ cell = cell .parameters ()
334+
335+ # Define 7 standard order-2 supercell transformations (det = 2)
336+ supercell_transforms = [
337+ np .array ([[2 , 0 , 0 ], [0 , 1 , 0 ], [0 , 0 , 1 ]]), # double a
338+ np .array ([[1 , 0 , 0 ], [0 , 2 , 0 ], [0 , 0 , 1 ]]), # double b
339+ np .array ([[1 , 0 , 0 ], [0 , 1 , 0 ], [0 , 0 , 2 ]]), # double c
340+ np .array ([[1 , 1 , 0 ], [- 1 , 1 , 0 ], [0 , 0 , 1 ]]), # C-face diagonal
341+ np .array ([[1 , 0 , 1 ], [0 , 1 , 0 ], [- 1 , 0 , 1 ]]), # B-face diagonal
342+ np .array ([[1 , 0 , 0 ], [0 , 1 , 1 ], [0 , - 1 , 1 ]]), # A-face diagonal
343+ np .array ([[1 , 1 , 1 ], [- 1 , 1 , 0 ], [- 1 , 0 , 1 ]]), # body diagonal
344+ ]
345+
346+ # Subcell transforms are the inverses of supercell transforms
347+ # (det = 1/2, map from original to smaller cell)
348+ subcell_transforms = [np .linalg .inv (S ) for S in supercell_transforms ]
349+
350+ all_results = []
351+
352+ # Process original cell
353+ original_results = find_near_minimum_settings (
354+ cell , length_tolerance , angle_tolerance , max_search_index
355+ )
356+ all_results .extend (original_results )
357+
358+ G_original = cell_to_metric_tensor (cell )
359+
360+ # Process supercells
361+ for S in supercell_transforms :
362+ # Transform to supercell: G_super = S^T @ G @ S
363+ G_super = S .T @ G_original @ S
364+ cell_super = metric_tensor_to_cell (G_super )
365+
366+ # Find near-minimum settings in the supercell
367+ super_results = find_near_minimum_settings (
368+ cell_super , length_tolerance , angle_tolerance , max_search_index
369+ )
370+
371+ # Compose transformations: P_total = S @ P_super
372+ for result in super_results :
373+ P_total = S @ result ['P' ]
374+ G_total = P_total .T @ G_original @ P_total
375+ cell_total = metric_tensor_to_cell (G_total )
376+
377+ all_results .append ({
378+ 'P' : P_total ,
379+ 'cell' : cell_total ,
380+ 'G' : G_total ,
381+ })
382+
383+ # Process subcells
384+ for T in subcell_transforms :
385+ # Transform to subcell: G_sub = T^T @ G @ T
386+ G_sub = T .T @ G_original @ T
387+ cell_sub = metric_tensor_to_cell (G_sub )
388+
389+ # Find near-minimum settings in the subcell
390+ sub_results = find_near_minimum_settings (
391+ cell_sub , length_tolerance , angle_tolerance , max_search_index
392+ )
393+
394+ # Compose transformations: P_total = T @ P_sub
395+ for result in sub_results :
396+ P_total = T @ result ['P' ]
397+ G_total = P_total .T @ G_original @ P_total
398+ cell_total = metric_tensor_to_cell (G_total )
399+
400+ all_results .append ({
401+ 'P' : P_total ,
402+ 'cell' : cell_total ,
403+ 'G' : G_total ,
404+ })
405+
406+ # Sort by sum of basis vector lengths
407+ all_results .sort (key = lambda x : sum (x ['cell' ][:3 ]))
408+
409+ return all_results
0 commit comments