@@ -793,6 +793,28 @@ def generate_possible_hkls(self, h_max, k_max=None, l_max=None, max_square=12):
793793 # Generate all possible hkls and filter by extinction rules
794794 possible_hkls = []
795795 canonical_seen = set () # Track canonical forms to avoid duplicates
796+ symmetry_seen = set () # Track symmetry-equivalent hkls
797+
798+ # Build reciprocal-space rotation operators from the general Wyckoff position
799+ reciprocal_ops = []
800+ op_seen = set ()
801+ if len (self .wyckoffs ) > 0 and len (self .wyckoffs [0 ]) > 0 :
802+ for op in self .wyckoffs [0 ]:
803+ try :
804+ matrix = np .rint (np .linalg .inv (op .rotation_matrix ).T ).astype (int )
805+ except np .linalg .LinAlgError :
806+ continue
807+ key = tuple (matrix .flatten ().tolist ())
808+ if key not in op_seen :
809+ op_seen .add (key )
810+ reciprocal_ops .append (matrix )
811+ if len (reciprocal_ops ) == 0 :
812+ reciprocal_ops = [np .eye (3 , dtype = int )]
813+
814+ def get_symmetry_key (hkl ):
815+ vec = np .array (hkl , dtype = int )
816+ orbit = [tuple ((matrix @ vec ).tolist ()) for matrix in reciprocal_ops ]
817+ return max (orbit )
796818
797819 for h in range (0 , h_max + 1 ):
798820 # add permutation
@@ -821,7 +843,11 @@ def generate_possible_hkls(self, h_max, k_max=None, l_max=None, max_square=12):
821843 #print('AAAAAAAAAAAAAAAAAAA', h, k, l, sh, sk, sl)
822844 if valid_hkls :
823845 canonical_seen .add (canonical )
824- possible_hkls .extend (valid_hkls )
846+ for hkl in valid_hkls :
847+ symmetry_key = get_symmetry_key (hkl )
848+ if symmetry_key not in symmetry_seen :
849+ symmetry_seen .add (symmetry_key )
850+ possible_hkls .append (hkl )
825851
826852 # Sort by h²+k²+l² in ascending order
827853 possible_hkls .sort (key = lambda hkl : hkl [0 ]** 2 + hkl [1 ]** 2 + hkl [2 ]** 2 )
@@ -961,14 +987,14 @@ def reduce_hkl_guesses(self, hkls):
961987 # must follow the ordering constraints
962988 mask1 = np .all (hkls [:,0 ,:] >= hkls [:,1 ,:], axis = 1 ) # (h1, k1, l1) >= (h2, k2, l2)
963989 hkls = hkls [~ mask1 ]
964- print ("Reducing order" , len (hkls ), "hkl guesses for space group" , self .number )
990+ # print("Reducing order", len(hkls), "hkl guesses for space group", self.number)
965991
966992 # must be non-coplanar
967993 B = np .zeros ([len (hkls ), 2 , 2 ])
968994 B [:,:,0 ] = hkls [:,:,0 ] ** 2 + hkls [:,:,1 ] ** 2
969995 B [:,:,1 ] = hkls [:,:,2 ] ** 2
970996 hkls = hkls [np .linalg .det (B ) != 0 ]
971- print ("Reducing colinear" , len (hkls ), "hkl guesses for space group" , self .number )
997+ # print("Reducing colinear", len(hkls), "hkl guesses for space group", self.number)
972998
973999 elif 15 < self .number < 75 :
9741000 # must follow the ordering constraints
@@ -5017,13 +5043,33 @@ def get_canonical_hkl(h, k, l, spg):
50175043 """
50185044 hkl = [abs (h ), abs (k ), abs (l )] # Take absolute values first
50195045
5046+ def canonical_hex_hkl (h , k , l ):
5047+ """Canonicalize hkl for hexagonal systems using 6-fold in-plane symmetry."""
5048+ candidates = [
5049+ (h , k , l ),
5050+ (- k , h + k , l ),
5051+ (- h - k , h , l ),
5052+ (- h , - k , l ),
5053+ (k , - h - k , l ),
5054+ (h + k , - h , l ),
5055+ ]
5056+ candidates = [tuple (abs (x ) for x in c ) for c in candidates ]
5057+ return max (candidates )
5058+
50205059 if spg >= 195 : # cubic
50215060 # For cubic: sort in descending order
50225061 # (2,2,0), (2,0,2), (0,2,2) all become (2,2,0)
50235062 hkl .sort (reverse = True )
50245063 return tuple (hkl )
50255064
5026- elif spg >= 75 : # tetragonal/hexagonal
5065+ elif spg >= 168 : # hexagonal
5066+ return canonical_hex_hkl (h , k , abs (l ))
5067+
5068+ elif spg >= 143 : # trigonal
5069+ h_sorted = sorted ([hkl [0 ], hkl [1 ]], reverse = True )
5070+ return tuple ([h_sorted [0 ], h_sorted [1 ], hkl [2 ]])
5071+
5072+ elif spg >= 75 : # tetragonal
50275073 # For tetragonal: h and k are equivalent, l is unique
50285074 # (2,1,3) and (1,2,3) are equivalent -> (2,1,3)
50295075 h_sorted = sorted ([hkl [0 ], hkl [1 ]], reverse = True )
@@ -5048,6 +5094,19 @@ def get_canonical_hkl_series(hkl_series, spg):
50485094 """
50495095 from itertools import permutations
50505096
5097+ def canonical_hex_hkl (h , k , l ):
5098+ """Canonicalize hkl for hexagonal systems using 6-fold in-plane symmetry."""
5099+ candidates = [
5100+ (h , k , l ),
5101+ (- k , h + k , l ),
5102+ (- h - k , h , l ),
5103+ (- h , - k , l ),
5104+ (k , - h - k , l ),
5105+ (h + k , - h , l ),
5106+ ]
5107+ candidates = [tuple (abs (x ) for x in c ) for c in candidates ]
5108+ return max (candidates )
5109+
50515110 def apply_permutation_to_series (hkl_series , perm ):
50525111 """Apply the same permutation to all hkls in the series"""
50535112 return [tuple (abs (hkl [i ]) for i in perm ) for hkl in hkl_series ]
@@ -5074,7 +5133,38 @@ def apply_permutation_to_series(hkl_series, perm):
50745133
50755134 return tuple (best_canonical )
50765135
5077- elif spg >= 75 : # tetragonal/hexagonal - h and k equivalent, l unique
5136+ elif spg >= 168 : # hexagonal - 6-fold in-plane symmetry, l unique
5137+ canonical_series = [canonical_hex_hkl (h , k , l ) for h , k , l in hkl_series ]
5138+ return tuple (canonical_series )
5139+
5140+ elif spg >= 143 : # trigonal - h and k equivalent, l unique
5141+ best_canonical = None
5142+ best_score = None
5143+
5144+ # Try permutations that maintain crystallographic meaning
5145+ perms_to_try = [(0 , 1 , 2 ), (1 , 0 , 2 )] # Keep l in position, swap h,k
5146+
5147+ for perm in perms_to_try :
5148+ # Apply the same permutation to the entire series
5149+ canonical_series = apply_permutation_to_series (hkl_series , perm )
5150+
5151+ # For trigonal: sort h,k but keep l separate
5152+ canonical_series = [
5153+ tuple ([* sorted ([hkl [0 ], hkl [1 ]], reverse = True ), hkl [2 ]])
5154+ for hkl in canonical_series
5155+ ]
5156+
5157+ # Score the result
5158+ score = sum (sum (h * (10 ** (3 - i )) for i , h in enumerate (hkl ))
5159+ for hkl in canonical_series )
5160+
5161+ if best_score is None or score > best_score :
5162+ best_score = score
5163+ best_canonical = canonical_series
5164+
5165+ return tuple (best_canonical )
5166+
5167+ elif spg >= 75 : # tetragonal - h and k equivalent, l unique
50785168 best_canonical = None
50795169 best_score = None
50805170
@@ -5296,7 +5386,56 @@ def generate_possible_hkls(bravais, h_max=50, k_max=50, l_max=50):
52965386 hkls_with_signs = np .column_stack ([sh , sk , sl ])
52975387 all_hkls .append (hkls_with_signs )
52985388
5299- return np .vstack (all_hkls )
5389+ all_hkls = np .vstack (all_hkls )
5390+
5391+ # Remove symmetry-equivalent hkls using representative space groups
5392+ bravais_to_spg = {
5393+ 1 : 1 , # triclinic P
5394+ 2 : 3 , # monoclinic P
5395+ 3 : 5 , # monoclinic C
5396+ 4 : 16 , # orthorhombic P
5397+ 5 : 16 , # orthorhombic A
5398+ 6 : 16 , # orthorhombic C
5399+ 7 : 16 , # orthorhombic I
5400+ 8 : 16 , # orthorhombic F
5401+ 9 : 75 , # tetragonal P
5402+ 10 : 79 , # tetragonal I
5403+ 11 : 168 , # hexagonal P
5404+ 12 : 143 , # rhombohedral/trigonal R
5405+ 13 : 195 , # cubic P
5406+ 14 : 197 , # cubic I
5407+ 15 : 196 , # cubic F
5408+ }
5409+ spg = bravais_to_spg [bravais ]
5410+
5411+ # Build reciprocal-space rotation operators from a representative space group
5412+ reciprocal_ops = []
5413+ op_seen = set ()
5414+ group = Group (spg )
5415+ if len (group .wyckoffs ) > 0 and len (group .wyckoffs [0 ]) > 0 :
5416+ for op in group .wyckoffs [0 ]:
5417+ try :
5418+ matrix = np .rint (np .linalg .inv (op .rotation_matrix ).T ).astype (int )
5419+ except np .linalg .LinAlgError :
5420+ continue
5421+ key = tuple (matrix .flatten ().tolist ())
5422+ if key not in op_seen :
5423+ op_seen .add (key )
5424+ reciprocal_ops .append (matrix )
5425+ if len (reciprocal_ops ) == 0 :
5426+ reciprocal_ops = [np .eye (3 , dtype = int )]
5427+
5428+ unique_hkls = []
5429+ seen = set ()
5430+ for h , k , l in all_hkls :
5431+ vec = np .array ([int (h ), int (k ), int (l )], dtype = int )
5432+ orbit = [tuple ((matrix @ vec ).tolist ()) for matrix in reciprocal_ops ]
5433+ symmetry_key = max (orbit )
5434+ if symmetry_key not in seen :
5435+ seen .add (symmetry_key )
5436+ unique_hkls .append (tuple (vec .tolist ()))
5437+
5438+ return np .array (unique_hkls , dtype = int )
53005439
53015440
53025441if __name__ == "__main__" :
0 commit comments