Skip to content

Commit 334945e

Browse files
Merge pull request #54 from SSCHAcode/fast_wpols
In this branch the diagonalization is performed only once
2 parents 751d238 + e357cc6 commit 334945e

3 files changed

Lines changed: 161 additions & 74 deletions

File tree

Modules/Ensemble.py

Lines changed: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import sscha.Parallel as Parallel
3535
from sscha.Parallel import pprint as print
3636

37+
import difflib
3738

3839
import SCHAModules
3940
#import sscha_HP_odd
@@ -98,7 +99,7 @@
9899
class 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

Modules/Relax.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
class SSCHA(object):
4848

4949
def __init__(self, minimizer = None, ase_calculator=None, N_configs=1, max_pop = 20,
50-
save_ensemble = False, cluster = None):
50+
save_ensemble = False, cluster = None, **kwargs):
5151
"""
5252
This module initialize the relaxer. It may perform
5353
constant volume or pressure relaxation using fully anharmonic potentials.
@@ -69,6 +69,7 @@ def __init__(self, minimizer = None, ase_calculator=None, N_configs=1, max_pop =
6969
cluster : Cluster.Cluster, optional
7070
If different from None, the ensemble force and energy calculations
7171
will be runned in the provided cluster.
72+
**kwargs : any other keyword that matches an object of this structure
7273
"""
7374

7475
if minimizer == None:
@@ -85,7 +86,7 @@ def __init__(self, minimizer = None, ase_calculator=None, N_configs=1, max_pop =
8586
# If the ensemble must be saved at each iteration.
8687
#
8788
self.save_ensemble = save_ensemble
88-
self.data_dir = ""
89+
self.data_dir = "data"
8990

9091

9192

@@ -109,6 +110,11 @@ def __init__(self, minimizer = None, ase_calculator=None, N_configs=1, max_pop =
109110
self.__total_attributes__ = [item for item in self.__dict__.keys()]
110111
self.fixed_attributes = True # This must be the last attribute to be setted
111112

113+
114+
# Setup any other keyword given in input (raising the error if not already defined)
115+
for key in kwargs:
116+
self.__setattr__(key, kwargs[key])
117+
112118
def __setattr__(self, name, value):
113119
"""
114120
This method is used to set an attribute.
@@ -271,8 +277,7 @@ def relax(self, restart_from_ens = False, get_stress = False,
271277
with some ase calculator)
272278
ensemble_loc : string
273279
Where the ensemble of each population is saved on the disk. If none, it will
274-
use the content of self.data_dir. If also self.data_dir is None,
275-
the ensemble will not not be saved (useful to avoid disk I/O for force fields)
280+
use the content of self.data_dir. It is just a way to override the variable self.data_dir
276281
start_pop : int, optional
277282
The starting index for the population, used only for saving the ensemble and the dynamical
278283
matrix. If None, the content of self.start_pop will be used.
@@ -286,6 +291,27 @@ def relax(self, restart_from_ens = False, get_stress = False,
286291

287292
if ensemble_loc is None:
288293
ensemble_loc = self.data_dir
294+
295+
if (not ensemble_loc) and self.save_ensemble:
296+
ERR_MSG = """
297+
Error, you must specify where to save the ensembles.
298+
this can be done either passing ensemble_loc = "path/to/dir"
299+
for the ensemble, or by setting the data_dir attribute of this object.
300+
"""
301+
raise IOError(ERR_MSG)
302+
303+
if self.save_ensemble:
304+
if not os.path.exists(ensemble_loc):
305+
os.makedirs(ensemble_loc)
306+
else:
307+
if not os.isdir(ensemble_loc):
308+
ERR_MSG = """
309+
Error, the specified location to save the ensemble:
310+
'{}'
311+
already exists and it is not a directory.
312+
""".format(ensemble_loc)
313+
raise IOError(ERR_MSG)
314+
289315

290316
if start_pop is None:
291317
start_pop = self.start_pop

0 commit comments

Comments
 (0)