3434import sscha .Parallel as Parallel
3535from sscha .Parallel import pprint as print
3636
37+ import difflib
3738
3839import SCHAModules
3940#import sscha_HP_odd
9899class Ensemble :
99100 __debug_index__ = 0
100101
101- def __init__ (self , dyn0 , T0 , supercell = None ):
102+ def __init__ (self , dyn0 , T0 , supercell = None , ** kwargs ):
102103 """
103104 PREPARE THE ENSEMBLE
104105 ====================
@@ -115,6 +116,7 @@ def __init__(self, dyn0, T0, supercell = None):
115116 The temperature used to generate the ensemble.
116117 supercell: optional, list of int
117118 The supercell dimension. If not provided, it will be determined by dyn0
119+ **kwargs : any other attribute of the ensemble
118120 """
119121
120122 # N is the number of element in the ensemble
@@ -178,6 +180,39 @@ def __init__(self, dyn0, T0, supercell = None):
178180 # Get the extra quantities
179181 self .extra_quantities = {}
180182
183+
184+ # Setup the attribute control
185+ self .__total_attributes__ = [item for item in self .__dict__ .keys ()]
186+ self .fixed_attributes = True # This must be the last attribute to be setted
187+
188+
189+ # Setup any other keyword given in input (raising the error if not already defined)
190+ for key in kwargs :
191+ self .__setattr__ (key , kwargs [key ])
192+
193+
194+ def __setattr__ (self , name , value ):
195+ """
196+ This method is used to set an attribute.
197+ It will raise an exception if the attribute does not exists (with a suggestion of similar entries)
198+ """
199+
200+
201+ if "fixed_attributes" in self .__dict__ :
202+ if name in self .__total_attributes__ :
203+ super (Ensemble , self ).__setattr__ (name , value )
204+ elif self .fixed_attributes :
205+ similar_objects = str ( difflib .get_close_matches (name , self .__total_attributes__ ))
206+ ERROR_MSG = """
207+ Error, the attribute '{}' is not a member of '{}'.
208+ Suggested similar attributes: {} ?
209+ """ .format (name , type (self ).__name__ , similar_objects )
210+
211+ raise AttributeError (ERROR_MSG )
212+ else :
213+ super (Ensemble , self ).__setattr__ (name , value )
214+
215+
181216 def convert_units (self , new_units ):
182217 """
183218 CONVERT ALL THE VARIABLE IN A COHERENT UNIT OF MEASUREMENTS
@@ -1178,15 +1213,15 @@ def update_weights(self, new_dynamical_matrix, newT, update_q = False):
11781213 super_struct0 = self .dyn_0 .structure .generate_supercell (self .supercell )
11791214 #super_dyn = self.dyn_0.GenerateSupercellDyn(self.supercell)
11801215
1181- w , pols = self .dyn_0 .DiagonalizeSupercell ()#super_dyn.DyagDinQ(0)
1216+ w_original , pols_original = self .dyn_0 .DiagonalizeSupercell ()#super_dyn.DyagDinQ(0)
11821217
11831218 # Exclude translations
11841219 if not self .ignore_small_w :
1185- trans_original = CC .Methods .get_translations (pols , super_struct0 .get_masses_array ())
1220+ trans_original = CC .Methods .get_translations (pols_original , super_struct0 .get_masses_array ())
11861221 else :
1187- trans_original = np .abs (w ) < CC .Phonons .__EPSILON_W__
1222+ trans_original = np .abs (w_original ) < CC .Phonons .__EPSILON_W__
11881223
1189- w = w [~ trans_original ]
1224+ w = w_original [~ trans_original ]
11901225
11911226 # Convert from Ry to Ha and in fortran double precision
11921227 w = np .array (w / 2 , dtype = np .float64 )
@@ -1198,12 +1233,12 @@ def update_weights(self, new_dynamical_matrix, newT, update_q = False):
11981233 super_structure = new_dynamical_matrix .structure .generate_supercell (self .supercell )
11991234 #new_super_dyn = new_dynamical_matrix.GenerateSupercellDyn(self.supercell)
12001235
1201- w , pols = new_dynamical_matrix .DiagonalizeSupercell ()#new_super_dyn.DyagDinQ(0)
1236+ w_new , pols = new_dynamical_matrix .DiagonalizeSupercell ()#new_super_dyn.DyagDinQ(0)
12021237
12031238 if not self .ignore_small_w :
12041239 trans_mask = CC .Methods .get_translations (pols , super_structure .get_masses_array ())
12051240 else :
1206- trans_mask = np .abs (w ) < CC .Phonons .__EPSILON_W__
1241+ trans_mask = np .abs (w_new ) < CC .Phonons .__EPSILON_W__
12071242
12081243
12091244 # Check if the new dynamical matrix satisfies the sum rule
@@ -1230,7 +1265,7 @@ def update_weights(self, new_dynamical_matrix, newT, update_q = False):
12301265 print (ERR_MSG )
12311266 raise ValueError (ERR_MSG )
12321267
1233- w = w [~ trans_mask ]
1268+ w = w_new [~ trans_mask ]
12341269 w = np .array (w / 2 , dtype = np .float64 )
12351270 new_a = SCHAModules .thermodynamic .w_to_a (w , newT )
12361271
@@ -1249,7 +1284,7 @@ def update_weights(self, new_dynamical_matrix, newT, update_q = False):
12491284 # old_disps[i,:] = (self.xats[i, :, :] - super_dyn.structure.coords).reshape( 3*Nat_sc )
12501285
12511286 # # TODO: this method recomputes the displacements, it is useless since we already have them in self.u_disps
1252- self .sscha_energies [:], self .sscha_forces [:,:,:] = new_dynamical_matrix .get_energy_forces (None , displacement = self .u_disps )
1287+ self .sscha_energies [:], self .sscha_forces [:,:,:] = new_dynamical_matrix .get_energy_forces (None , displacement = self .u_disps , w_pols = ( w_new , pols ) )
12531288
12541289 t4 = time .time ()
12551290
@@ -1271,8 +1306,8 @@ def update_weights(self, new_dynamical_matrix, newT, update_q = False):
12711306
12721307
12731308 # Get the covariance matrices of the ensemble
1274- ups_new = np .real (new_dynamical_matrix .GetUpsilonMatrix (self .current_T ))
1275- ups_old = np .real (self .dyn_0 .GetUpsilonMatrix (self .T0 ))
1309+ ups_new = np .real (new_dynamical_matrix .GetUpsilonMatrix (self .current_T , w_pols = ( w_new , pols ) ))
1310+ ups_old = np .real (self .dyn_0 .GetUpsilonMatrix (self .T0 , w_pols = ( w_original , pols_original ) ))
12761311
12771312 # Get the normalization ratio
12781313 #norm = np.sqrt(np.abs(np.linalg.det(ups_new) / np.linalg.det(ups_old)))
@@ -1755,11 +1790,22 @@ def get_preconditioned_gradient(self, subtract_sscha = True, return_error = Fals
17551790 if verbose :
17561791 print (" [GRADIENT] Time to call the fortran code:" , t2 - t1 , "s" )
17571792
1758- # Perform the fourier transform
1759- q_grad = CC .Phonons .GetDynQFromFCSupercell (grad , np .array (self .current_dyn .q_tot ),
1760- self .current_dyn .structure , supercell_dyn .structure )
1761- q_grad_err = CC .Phonons .GetDynQFromFCSupercell (grad_err , np .array (self .current_dyn .q_tot ),
1762- self .current_dyn .structure , supercell_dyn .structure )
1793+ # If we are at gamma, we can skip this part
1794+ # Which makes the code faster
1795+ if np .prod (self .dyn_0 .GetSupercell ()) > 1 :
1796+
1797+ # Perform the fourier transform
1798+ q_grad = CC .Phonons .GetDynQFromFCSupercell (grad , np .array (self .current_dyn .q_tot ),
1799+ self .current_dyn .structure , supercell_dyn .structure )
1800+ q_grad_err = CC .Phonons .GetDynQFromFCSupercell (grad_err , np .array (self .current_dyn .q_tot ),
1801+ self .current_dyn .structure , supercell_dyn .structure )
1802+ else :
1803+ nat3 , _ = grad .shape
1804+ q_grad = np .zeros ( (1 , nat3 , nat3 ), dtype = np .double )
1805+ q_grad_err = np .zeros_like (q_grad )
1806+ q_grad [0 , :, :] = grad
1807+ q_grad_err [0 , :, :] = grad_err
1808+
17631809 t1 = time .time ()
17641810 if verbose :
17651811 print (" [GRADIENT] Time to get back in fourier space:" , t1 - t2 , "s" )
@@ -3014,7 +3060,7 @@ def get_energy_forces(self, ase_calculator, compute_stress = True, stress_numeri
30143060
30153061 # Print the status
30163062 if Parallel .am_i_the_master () and verbose :
3017- print ("Computing configuration %d out of %d" % (i + 1 , stop ))
3063+ print ("Computing configuration %d out of %d (nat = %d) " % (i + 1 , stop , struct . N_atoms ))
30183064 sys .stdout .flush ()
30193065
30203066 # Avoid for errors
@@ -3023,27 +3069,28 @@ def get_energy_forces(self, ase_calculator, compute_stress = True, stress_numeri
30233069 while run :
30243070 try :
30253071 energy = atms .get_total_energy () / Rydberg # eV => Ry
3072+ # Get energy, forces (and stress)
3073+ energy = atms .get_total_energy () / Rydberg # eV => Ry
3074+ forces_ = atms .get_forces () / Rydberg # eV / A => Ry / A
3075+ if compute_stress :
3076+ if not stress_numerical :
3077+ stress [9 * i0 : 9 * i0 + 9 ] = - atms .get_stress (False ).reshape (9 ) * Bohr ** 3 / Rydberg # ev/A^3 => Ry/bohr
3078+ else :
3079+ stress [9 * i0 : 9 * i0 + 9 ] = - ase_calculator .calculate_numerical_stress (atms , voigt = False ).ravel ()* Bohr ** 3 / Rydberg
3080+
3081+ # Copy into the ensemble array
3082+ energies [i0 ] = energy
3083+ forces [nat3 * i0 : nat3 * i0 + nat3 ] = forces_ .reshape ( nat3 )
30263084 run = False
30273085 except :
30283086 print ("Rerun the job %d" % i )
30293087 count_fails += 1
30303088 if count_fails >= 5 :
30313089 run = False
3032- sys .stderr .write ("Error in the ASE calculator for more than 5 times\n " )
3090+ struct .save_scf ("error_struct.scf" )
3091+ sys .stderr .write ("Error in the ASE calculator for more than 5 times\n while computing 'error_struct.scf'" )
30333092 raise
30343093
3035- # Get energy, forces (and stress)
3036- energy = atms .get_total_energy () / Rydberg # eV => Ry
3037- forces_ = atms .get_forces () / Rydberg # eV / A => Ry / A
3038- if compute_stress :
3039- if not stress_numerical :
3040- stress [9 * i0 : 9 * i0 + 9 ] = - atms .get_stress (False ).reshape (9 ) * Bohr ** 3 / Rydberg # ev/A^3 => Ry/bohr
3041- else :
3042- stress [9 * i0 : 9 * i0 + 9 ] = - ase_calculator .calculate_numerical_stress (atms , voigt = False ).ravel ()* Bohr ** 3 / Rydberg
3043-
3044- # Copy into the ensemble array
3045- energies [i0 ] = energy
3046- forces [nat3 * i0 : nat3 * i0 + nat3 ] = forces_ .reshape ( nat3 )
30473094
30483095
30493096 i0 += 1
0 commit comments