@@ -134,6 +134,32 @@ def __init__(self, structure, threshold = 1e-5):
134134 # After the translation, which vector is transformed in which one?
135135 # This info is stored here as ndarray( size = (N_atoms, N_trans), dtype = np.intc, order = "F")
136136 self .QE_translations_irt = []
137+
138+ # Cached transformation matrices for cartesian/crystal conversion
139+ self ._comp_matrix = None
140+ self ._comp_matrix_inv = None
141+
142+ def _ensure_transformation_matrices (self ):
143+ """
144+ Compute and cache the transformation matrices between cartesian and crystal coordinates.
145+ Follows the same logic as Methods.convert_matrix_cart_cryst.
146+ """
147+ if self ._comp_matrix is not None :
148+ return
149+
150+ unit_cell = self .structure .unit_cell
151+ # Compute metric tensor
152+ metric_tensor = np .zeros ((3 ,3 ))
153+ for i in range (3 ):
154+ for j in range (i , 3 ):
155+ metric_tensor [i , j ] = metric_tensor [j , i ] = unit_cell [i , :].dot (unit_cell [j , :])
156+
157+ # comp_matrix = inv(metric_tensor) @ unit_cell
158+ comp_matrix = np .linalg .inv (metric_tensor ).dot (unit_cell )
159+ comp_matrix_inv = np .linalg .inv (comp_matrix )
160+
161+ self ._comp_matrix = comp_matrix
162+ self ._comp_matrix_inv = comp_matrix_inv
137163
138164 def ForceSymmetry (self , structure ):
139165 """
@@ -793,7 +819,7 @@ def ApplySymmetryToTensor4(self, v4, initialize_symmetries = True):
793819 # Apply all the symmetries at gamma
794820 symph .sym_v4 (v4 , self .QE_at , self .QE_s , self .QE_irt , self .QE_nsymq )
795821
796- def ApplyQStar (self , fcq , q_point_group ):
822+ def ApplyQStar (self , fcq , q_point_group , timer = None ):
797823 """
798824 APPLY THE Q STAR SYMMETRY
799825 =========================
@@ -910,11 +936,18 @@ def ApplyQStar(self, fcq, q_point_group):
910936 #print sorting_q
911937
912938
913- # Copy the matrix in the new one
914- for xq in range (nq ):
915- for xat in range (self .QE_nat ):
916- for yat in range (self .QE_nat ):
917- final_fc [xq , 3 * xat : 3 * xat + 3 , 3 * yat : 3 * yat + 3 ] += dyn_star [sorting_q [xq ], :,:, xat , yat ]
939+ # Copy the matrix in the new one - VECTORIZED VERSION
940+ # dyn_star has shape (nq, 3, 3, nat, nat)
941+ # Reorder according to sorting_q
942+ dyn_star_reordered = dyn_star [sorting_q , :, :, :, :] # shape (nq, 3, 3, nat, nat)
943+ # Reshape to (nq, 3*nat, 3*nat)
944+ # First transpose to (nq, nat, 3, nat, 3) to group xat with first 3, yat with second 3
945+ # Original indices: 0=nq, 1=cart_row, 2=cart_col, 3=xat, 4=yat
946+ # Target order: (nq, xat, cart_row, yat, cart_col) -> transpose(0,3,1,4,2)
947+ dyn_star_reshaped = dyn_star_reordered .transpose (0 , 3 , 1 , 4 , 2 )
948+ # Now reshape to (nq, 3*nat, 3*nat)
949+ dyn_star_reshaped = dyn_star_reshaped .reshape (nq , 3 * self .QE_nat , 3 * self .QE_nat )
950+ final_fc += dyn_star_reshaped
918951
919952
920953 # Now divide the matrix per the xq value
@@ -967,7 +1000,7 @@ def ApplySymmetryToMatrix(self, matrix, err = None):
9671000
9681001
9691002
970- def SymmetrizeFCQ (self , fcq , q_stars , verbose = False , asr = "custom" ):
1003+ def SymmetrizeFCQ (self , fcq , q_stars , verbose = False , asr = "custom" , timer = None ):
9711004 """
9721005 Use the current structure to impose symmetries on a complete dynamical matrix
9731006 in q space. Also the simple sum rule at Gamma is imposed
@@ -980,6 +1013,7 @@ def SymmetrizeFCQ(self, fcq, q_stars, verbose = False, asr = "custom"):
9801013 The list of q points divided by stars, the fcq must follow the order
9811014 of the q points in the q_stars array
9821015 """
1016+
9831017 nqirr = len (q_stars )
9841018 nq = np .sum ([len (x ) for x in q_stars ])
9851019
@@ -999,11 +1033,10 @@ def SymmetrizeFCQ(self, fcq, q_stars, verbose = False, asr = "custom"):
9991033 # Prepare the symmetrization
10001034 if verbose :
10011035 print ("Symmetries in q = " , q_points [iq , :])
1002- t1 = time .time ()
1003- self .SetupQPoint (q_points [iq ,:], verbose )
1004- t2 = time .time ()
1005- if verbose :
1006- print (" [SYMMETRIZEFCQ] Time to setup the q point %d" % iq , t2 - t1 , "s" )
1036+ if timer is not None :
1037+ timer .execute_timed_function (self .SetupQPoint , q_points [iq ,:], verbose )
1038+ else :
1039+ self .SetupQPoint (q_points [iq ,:], verbose )
10071040
10081041 # Proceed with the sum rule if we are at Gamma
10091042
@@ -1012,27 +1045,30 @@ def SymmetrizeFCQ(self, fcq, q_stars, verbose = False, asr = "custom"):
10121045 if verbose :
10131046 print ("q_point:" , q_points [iq ,:])
10141047 print ("Applying sum rule" )
1015- self .ImposeSumRule (fcq [iq ,:,:], asr )
1048+ if timer is not None :
1049+ timer .execute_timed_function (self .ImposeSumRule , fcq [iq ,:,:], asr )
1050+ else :
1051+ self .ImposeSumRule (fcq [iq ,:,:], asr )
10161052 elif asr == "crystal" :
1017- self .ImposeSumRule (fcq [iq , :,:], asr = asr )
1053+ if timer is not None :
1054+ timer .execute_timed_function (self .ImposeSumRule , fcq [iq , :,:], asr = asr )
1055+ else :
1056+ self .ImposeSumRule (fcq [iq , :,:], asr = asr )
10181057 elif asr == "no" :
10191058 pass
10201059 else :
10211060 raise ValueError ("Error, only 'simple', 'crystal', 'custom' or 'no' asr are supported, given %s" % asr )
10221061
1023- t1 = time .time ()
1024- if verbose :
1025- print (" [SYMMETRIZEFCQ] Time to apply the sum rule:" , t1 - t2 , "s" )
1026-
10271062 # # Symmetrize the matrix
10281063 if verbose :
10291064 old_fcq = fcq [iq , :,:].copy ()
10301065 w_old = np .linalg .eigvals (fcq [iq , :, :])
10311066 print ("FREQ BEFORE SYM:" , w_old )
1032- self .SymmetrizeDynQ (fcq [iq , :,:], q_points [iq ,:])
1033- t2 = time .time ()
1067+ if timer is not None :
1068+ timer .execute_timed_function (self .SymmetrizeDynQ , fcq [iq , :,:], q_points [iq ,:])
1069+ else :
1070+ self .SymmetrizeDynQ (fcq [iq , :,:], q_points [iq ,:])
10341071 if verbose :
1035- print (" [SYMMETRIZEFCQ] Time to symmetrize the %d dynamical matrix:" % iq , t2 - t1 , "s" )
10361072 print (" [SYMMETRIZEFCQ] Difference before the symmetrization:" , np .sqrt (np .sum (np .abs (old_fcq - fcq [iq , :,:])** 2 )))
10371073 w_new = np .linalg .eigvals (fcq [iq , :, :])
10381074 print ("FREQ AFTER SYM:" , w_new )
@@ -1041,14 +1077,13 @@ def SymmetrizeFCQ(self, fcq, q_stars, verbose = False, asr = "custom"):
10411077 q0_index = 0
10421078 for i in range (nqirr ):
10431079 q_len = len (q_stars [i ])
1044- t1 = time .time ()
10451080 if verbose :
10461081 print ("Applying the q star symmetrization on:" )
10471082 print (np .array (q_stars [i ]))
1048- self . ApplyQStar ( fcq [ q0_index : q0_index + q_len , :,:], np . array ( q_stars [ i ]))
1049- t2 = time . time ( )
1050- if verbose :
1051- print ( " [SYMMETRIZEFCQ] Time to apply the star q_irr = %d:" % i , t2 - t1 , "s" )
1083+ if timer is not None :
1084+ timer . execute_timed_function ( self . ApplyQStar , fcq [ q0_index : q0_index + q_len , :,:], np . array ( q_stars [ i ]) )
1085+ else :
1086+ self . ApplyQStar ( fcq [ q0_index : q0_index + q_len , :,:], np . array ( q_stars [ i ]) )
10521087 q0_index += q_len
10531088
10541089
@@ -1060,7 +1095,7 @@ def ChangeThreshold(self, threshold):
10601095 symph .symm_base .set_accep_threshold (self .threshold )
10611096
10621097
1063- def ImposeSumRule (self , force_constant , asr = "custom" , axis = 1 , zeu = None ):
1098+ def ImposeSumRule (self , force_constant , asr = "custom" , axis = 1 , zeu = None , timer = None ):
10641099 """
10651100 QE SUM RULE
10661101 ===========
@@ -1129,7 +1164,7 @@ def ImposeSumRule(self, force_constant, asr = "custom", axis = 1, zeu = None):
11291164
11301165
11311166
1132- def SetupQPoint (self , q_point = np .zeros (3 ), verbose = False ):
1167+ def SetupQPoint (self , q_point = np .zeros (3 ), verbose = False , timer = None ):
11331168 """
11341169 Get symmetries of the small group of q
11351170
@@ -1505,7 +1540,7 @@ def SymmetrizeVector(self, vector):
15051540 vector [i , :] = tmp_vector [:,i ]
15061541
15071542
1508- def SymmetrizeDynQ (self , dyn_matrix , q_point ):
1543+ def SymmetrizeDynQ (self , dyn_matrix , q_point , timer = None ):
15091544 """
15101545 DYNAMICAL MATRIX SYMMETRIZATION
15111546 ===============================
@@ -1531,10 +1566,10 @@ def SymmetrizeDynQ(self, dyn_matrix, q_point):
15311566 QE_dyn = np .zeros ( (3 , 3 , self .QE_nat , self .QE_nat ), dtype = np .complex128 , order = "F" )
15321567
15331568 # Get the crystal coordinates for the matrix
1534- for na in range ( self . QE_nat ) :
1535- for nb in range (self .QE_nat ):
1536- fc = dyn_matrix [ 3 * na : 3 * na + 3 , 3 * nb : 3 * nb + 3 ]
1537- QE_dyn [:, :, na , nb ] = Methods . convert_matrix_cart_cryst ( fc , self . structure . unit_cell , False )
1569+ if timer is not None :
1570+ timer . execute_timed_function (self ._convert_cart_to_cryst , dyn_matrix , QE_dyn , override_name = "ConvertCartToCryst" )
1571+ else :
1572+ self . _convert_cart_to_cryst ( dyn_matrix , QE_dyn )
15381573
15391574 # Prepare the xq variable
15401575 #xq = np.ones(3, dtype = np.float64)
@@ -1561,17 +1596,57 @@ def SymmetrizeDynQ(self, dyn_matrix, q_point):
15611596
15621597
15631598 # USE THE QE library to perform the symmetrization
1564- symph .symdynph_gq_new ( xq , QE_dyn , self .QE_s , self .QE_invs , self .QE_rtau ,
1565- self .QE_irt , self .QE_irotmq , self .QE_minus_q , self .QE_nsymq , self .QE_nat )
1599+ if timer is not None :
1600+ t1 = time .time ()
1601+ symph .symdynph_gq_new ( xq , QE_dyn , self .QE_s , self .QE_invs , self .QE_rtau ,
1602+ self .QE_irt , self .QE_irotmq , self .QE_minus_q , self .QE_nsymq , self .QE_nat )
1603+ t2 = time .time ()
1604+ timer .add_timer ("FortranSymdynph" , t2 - t1 )
1605+ else :
1606+ symph .symdynph_gq_new ( xq , QE_dyn , self .QE_s , self .QE_invs , self .QE_rtau ,
1607+ self .QE_irt , self .QE_irotmq , self .QE_minus_q , self .QE_nsymq , self .QE_nat )
15661608
15671609 # TODO: Error while applying the symmetry
15681610
15691611 # Return to cartesian coordinates
1570- for na in range ( self . QE_nat ) :
1571- for nb in range (self .QE_nat ):
1572- fc = QE_dyn [:, :, na , nb ]
1573- dyn_matrix [ 3 * na : 3 * na + 3 , 3 * nb : 3 * nb + 3 ] = Methods . convert_matrix_cart_cryst ( fc , self . structure . unit_cell , True )
1612+ if timer is not None :
1613+ timer . execute_timed_function (self ._convert_cryst_to_cart , dyn_matrix , QE_dyn , override_name = "ConvertCrystToCart" )
1614+ else :
1615+ self . _convert_cryst_to_cart ( dyn_matrix , QE_dyn )
15741616
1617+ def _convert_cart_to_cryst (self , dyn_matrix , QE_dyn ):
1618+ """
1619+ Helper: convert dynamical matrix from cartesian to crystal coordinates.
1620+ Vectorized version using cached transformation matrices.
1621+ """
1622+ self ._ensure_transformation_matrices ()
1623+ nat = self .QE_nat
1624+ A = self ._comp_matrix_inv # transformation from cart to cryst
1625+
1626+ # Reshape dyn_matrix (3*nat, 3*nat) -> (nat, 3, nat, 3)
1627+ dyn_blocks = dyn_matrix .reshape (nat , 3 , nat , 3 )
1628+ # Apply transformation: fc_cryst = A.T @ fc_cart @ A
1629+ tmp = np .einsum ('ki,a i b j -> a k b j' , A .T , dyn_blocks )
1630+ fc_cryst = np .einsum ('a k b j, j l -> a k b l' , tmp , A )
1631+ # Convert to QE_dyn shape (3,3,nat,nat): fc_cryst[na,k,nb,l] -> QE_dyn[k,l,na,nb]
1632+ QE_dyn [:,:,:,:] = fc_cryst .transpose (1 ,3 ,0 ,2 )
1633+
1634+ def _convert_cryst_to_cart (self , dyn_matrix , QE_dyn ):
1635+ """
1636+ Helper: convert dynamical matrix from crystal to cartesian coordinates.
1637+ Vectorized version using cached transformation matrices.
1638+ """
1639+ self ._ensure_transformation_matrices ()
1640+ nat = self .QE_nat
1641+ B = self ._comp_matrix # transformation from cryst to cart
1642+
1643+ # Convert QE_dyn (3,3,nat,nat) to (nat,3,nat,3): QE_dyn[k,l,na,nb] -> fc_cryst[na,k,nb,l]
1644+ fc_cryst = QE_dyn .transpose (2 ,0 ,3 ,1 )
1645+ # Apply transformation: fc_cart = B.T @ fc_cryst @ B
1646+ tmp = np .einsum ('ki,a i b j -> a k b j' , B .T , fc_cryst )
1647+ fc_cart = np .einsum ('a k b j, j l -> a k b l' , tmp , B )
1648+ # Reshape to (3*nat, 3*nat) and assign to dyn_matrix
1649+ dyn_matrix [:,:] = fc_cart .transpose (0 ,1 ,2 ,3 ).reshape (3 * nat , 3 * nat )
15751650 def GetQStar (self , q_vector ):
15761651 """
15771652 GET THE Q STAR
0 commit comments