Skip to content

Commit a8394db

Browse files
Merge pull request #60 from SSCHAcode/fast_w_update
Now we removed a lot of diagonalization from the code, this should speedup by a significative amount the code.
2 parents b0ead1b + 916404b commit a8394db

6 files changed

Lines changed: 224 additions & 43 deletions

File tree

Modules/Ensemble.py

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ def __init__(self, dyn0, T0, supercell = None, **kwargs):
136136
self.stresses = []
137137
self.xats = []
138138
self.units = UNITS_DEFAULT
139+
self.w_0 = None
140+
self.pols_0 = None
141+
self.current_w = None
142+
self.current_pols = None
139143

140144
self.sscha_energies = []
141145
self.sscha_forces = []
@@ -221,6 +225,12 @@ def __setattr__(self, name, value):
221225
else:
222226
super(Ensemble, self).__setattr__(name, value)
223227

228+
if name == "dyn_0":
229+
self.w_0, self.pols_0 = value.DiagonalizeSupercell()
230+
self.current_dyn = value.Copy()
231+
self.current_w = self.w_0.copy()
232+
self.current_pols = self.pols_0.copy()
233+
224234

225235
def convert_units(self, new_units):
226236
"""
@@ -1297,14 +1307,40 @@ def update_weights(self, new_dynamical_matrix, newT, update_q = False):
12971307

12981308
self.current_T = newT
12991309

1300-
1310+
# Check if the dynamical matrix has changed
1311+
changed_dyn = np.max([np.max(np.abs(self.current_dyn.dynmats[i] - new_dynamical_matrix.dynmats[i])) for i in range(len(self.current_dyn.q_tot))])
1312+
print("DYN CHANGED BY:", changed_dyn)
1313+
changed_dyn = changed_dyn > 1e-30
1314+
1315+
# Prepare the new displacements
1316+
super_struct0 = self.dyn_0.structure.generate_supercell(self.supercell)
1317+
super_structure = new_dynamical_matrix.structure.generate_supercell(self.supercell)
1318+
old_disps = np.zeros(np.shape(self.u_disps), dtype = np.double)
1319+
Nat_sc = super_structure.N_atoms
1320+
1321+
self.u_disps[:,:] = self.xats.reshape((self.N, 3*Nat_sc)) - np.tile(super_structure.coords.ravel(), (self.N,1))
1322+
old_disps[:,:] = self.xats.reshape((self.N, 3*Nat_sc)) - np.tile(super_struct0.coords.ravel(), (self.N,1))
1323+
1324+
if changed_dyn:
1325+
w_new, pols = new_dynamical_matrix.DiagonalizeSupercell()#new_super_dyn.DyagDinQ(0)
1326+
self.current_w = w_new.copy()
1327+
self.current_pols = pols.copy()
1328+
else:
1329+
w_new = self.current_w.copy()
1330+
pols = self.current_pols.copy()
1331+
#w_new, pols = new_dynamical_matrix.DiagonalizeSupercell()#new_super_dyn.DyagDinQ(0)
1332+
#self.current_w = w_new.copy()
1333+
#self.current_pols = pols.copy()
1334+
# Update sscha energies and forces
1335+
self.sscha_energies[:], self.sscha_forces[:,:,:] = new_dynamical_matrix.get_energy_forces(None, displacement = self.u_disps, w_pols = (w_new, pols))
1336+
13011337

13021338
t1 = time.time()
13031339
# Get the frequencies of the original dynamical matrix
1304-
super_struct0 = self.dyn_0.structure.generate_supercell(self.supercell)
13051340
#super_dyn = self.dyn_0.GenerateSupercellDyn(self.supercell)
13061341

1307-
w_original, pols_original = self.dyn_0.DiagonalizeSupercell()#super_dyn.DyagDinQ(0)
1342+
w_original = self.w_0.copy()
1343+
pols_original = self.pols_0.copy()
13081344

13091345
# Exclude translations
13101346
if not self.ignore_small_w:
@@ -1321,10 +1357,8 @@ def update_weights(self, new_dynamical_matrix, newT, update_q = False):
13211357
old_a = SCHAModules.thermodynamic.w_to_a(w, self.T0)
13221358

13231359
# Now do the same for the new dynamical matrix
1324-
super_structure = new_dynamical_matrix.structure.generate_supercell(self.supercell)
13251360
#new_super_dyn = new_dynamical_matrix.GenerateSupercellDyn(self.supercell)
13261361

1327-
w_new, pols = new_dynamical_matrix.DiagonalizeSupercell()#new_super_dyn.DyagDinQ(0)
13281362

13291363
if not self.ignore_small_w:
13301364
trans_mask = CC.Methods.get_translations(pols, super_structure.get_masses_array())
@@ -1341,11 +1375,11 @@ def update_weights(self, new_dynamical_matrix, newT, update_q = False):
13411375
if violating_sum_rule:
13421376
ERR_MSG = """
13431377
ERROR WHILE UPDATING THE WEIGHTS
1344-
1378+
13451379
Error, one dynamical matrix does not satisfy the acoustic sum rule.
1346-
If this problem arises on a sscha run,
1347-
it may be due to a gradient that violates the sum rule.
1348-
Please, be sure you are not using a custom gradient function.
1380+
If this problem arises on a sscha run,
1381+
it may be due to a gradient that violates the sum rule.
1382+
Please, be sure you are not using a custom gradient function.
13491383
13501384
DETAILS OF ERROR:
13511385
Number of translatinal modes in the original dyn = {}
@@ -1360,22 +1394,15 @@ def update_weights(self, new_dynamical_matrix, newT, update_q = False):
13601394
w = np.array(w/2, dtype = np.float64)
13611395
new_a = SCHAModules.thermodynamic.w_to_a(w, newT)
13621396

1363-
Nat_sc = super_structure.N_atoms
13641397

13651398
# Get the new displacements in the supercell
13661399
t3 = time.time()
1367-
old_disps = np.zeros(np.shape(self.u_disps), dtype = np.double)
1368-
1369-
self.u_disps[:,:] = self.xats.reshape((self.N, 3*Nat_sc)) - np.tile(super_structure.coords.ravel(), (self.N,1))
1370-
old_disps[:,:] = self.xats.reshape((self.N, 3*Nat_sc)) - np.tile(super_struct0.coords.ravel(), (self.N,1))
1371-
13721400
# for i in range(self.N):
13731401
# self.u_disps[i, :] = (self.xats[i, :, :] - super_structure.coords).reshape( 3*Nat_sc )
13741402

13751403
# old_disps[i,:] = (self.xats[i, :, :] - super_dyn.structure.coords).reshape( 3*Nat_sc )
13761404

13771405
# # TODO: this method recomputes the displacements, it is useless since we already have them in self.u_disps
1378-
self.sscha_energies[:], self.sscha_forces[:,:,:] = new_dynamical_matrix.get_energy_forces(None, displacement = self.u_disps, w_pols = (w_new, pols))
13791406

13801407
t4 = time.time()
13811408

@@ -1422,6 +1449,8 @@ def update_weights(self, new_dynamical_matrix, newT, update_q = False):
14221449
rho_tmp[i] *= np.exp(-0.5 * (v_new - v_old) )
14231450
# Lets try to use this one
14241451
self.rho = rho_tmp
1452+
1453+
14251454

14261455
#print("\n".join(["%8d) %16.8f" % (i+1, r) for i, r in enumerate(self.rho)]))
14271456

@@ -1620,7 +1649,7 @@ def get_free_energy(self, return_error = False):
16201649
The free energy in the current dynamical matrix and at the ensemble temperature
16211650
"""
16221651

1623-
free_energy = self.current_dyn.GetHarmonicFreeEnergy(self.current_T)
1652+
free_energy = self.current_dyn.GetHarmonicFreeEnergy(self.current_T, w_pols = (self.current_w, self.current_pols))
16241653

16251654
# We got the F_0
16261655
# Now we can compute the free energy difference

Modules/Minimizer.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,9 @@ def run_step(self, gradient, kl_new):
240240
self.step *= self.decrement_step
241241

242242
if self.verbose:
243-
print("Step too large, reducing to {}".format(self.step))
243+
print("Step too large (scalar = {} | kl_ratio = {}), reducing to {}".format(scalar, kl_ratio, self.step))
244+
#print("Direction: ", self.direction)
245+
#print("Gradient: ", gradient)
244246
else:
245247
# The step is good, therefore next step perform a new direction
246248
self.new_direction = True

Modules/SchaMinimizer.py

Lines changed: 76 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
It is possible to use it to perform the anharmonic minimization
2525
"""
2626

27+
from inspect import signature
28+
2729
#import Ensemble
2830
import numpy as np
2931
import difflib
@@ -415,16 +417,19 @@ def minimization_step(self, custom_function_gradient = None):
415417
struct_grad, struct_grad_err = self.ensemble.get_average_forces(True)
416418
#print "SHAPE:", np.shape(struct_grad)
417419
struct_grad_reshaped = - struct_grad.reshape( (3 * self.dyn.structure.N_atoms))
418-
t2 = time.time()
419-
420-
print ("Time elapsed to compute the structure gradient:", t2 - t1, "s")
421420

422421

423422
# Preconditionate the gradient for the wyckoff minimization
424423
if self.precond_wyck:
425-
struct_precond = GetStructPrecond(self.ensemble.current_dyn, ignore_small_w = self.ensemble.ignore_small_w)
424+
w_pols = None
425+
if len(self.dyn.q_tot) == 1:
426+
w_pols = (self.ensemble.current_w, self.ensemble.current_pols)
427+
struct_precond = GetStructPrecond(self.ensemble.current_dyn, ignore_small_w = self.ensemble.ignore_small_w, w_pols = w_pols)
426428
struct_grad_precond = struct_precond.dot(struct_grad_reshaped)
427429
struct_grad = struct_grad_precond.reshape( (self.dyn.structure.N_atoms, 3))
430+
t2 = time.time()
431+
432+
print ("Time elapsed to compute the structure gradient:", t2 - t1, "s")
428433

429434

430435
# Apply the symmetries to the forces
@@ -448,7 +453,35 @@ def minimization_step(self, custom_function_gradient = None):
448453

449454
# Perform the gradient restriction
450455
if custom_function_gradient is not None:
451-
custom_function_gradient(dyn_grad, struct_grad)
456+
457+
# Check the number of parameters
458+
try:
459+
sig = signature(custom_function_gradient).parameters
460+
except Exception as e:
461+
print(e)
462+
463+
MSG = '''
464+
While inspecting the custom_function_gradient an error was rised.
465+
Maybe you did not pass the minimizer a valid function?
466+
'''
467+
raise ValueError(MSG)
468+
469+
470+
if len(sig) not in [2,3]:
471+
MSG = '''
472+
Error, the custom_function_gradient must have either 2 or 3 arguments:
473+
- dynamical_matrix_gradient
474+
- structure gradient
475+
- [Optional] The minimizer (self) object
476+
477+
The function you provided accepts {} arguments instead.
478+
'''.format(len(sig))
479+
raise ValueError(MSG)
480+
481+
if len(sig) == 3:
482+
custom_function_gradient(dyn_grad, struct_grad, self)
483+
else:
484+
custom_function_gradient(dyn_grad, struct_grad)
452485

453486

454487
# Append the gradient modulus to the minimization info
@@ -500,22 +533,23 @@ def minimization_step(self, custom_function_gradient = None):
500533

501534

502535
# If we have imaginary frequencies, force the kl ratio to zero
536+
537+
# Update the ensemble
538+
try:
539+
self.update()
540+
except np.linalg.LinAlgError as error:
541+
print("Diagonalization error:")
542+
print(error)
543+
print("Reducing the minimization step...")
544+
new_kl_ratio = 0 # Force step reduction
545+
is_diag_ok = False
546+
diag_error_counter += 1
547+
503548
if self.check_imaginary_frequencies():
504549
print("Immaginary frequencies found! Redoing the step.")
505550
new_kl_ratio = 0
506551
is_diag_ok = False
507552
imag_freq_counter += 1
508-
else:
509-
# Update the ensemble
510-
try:
511-
self.update()
512-
except np.linalg.LinAlgError as error:
513-
print("Diagonalization error:")
514-
print(error)
515-
print("Reducing the minimization step...")
516-
new_kl_ratio = 0 # Force step reduction
517-
is_diag_ok = False
518-
diag_error_counter += 1
519553

520554
if diag_error_counter >= self.max_diag_error_counter:
521555
ERROR_MSG = """
@@ -1134,7 +1168,7 @@ def run(self, verbose = 1, custom_function_pre = None, custom_function_post = No
11341168
self.__fe__.append(np.real(fe))
11351169
self.__fe_err__.append(np.real(err))
11361170

1137-
harm_fe = self.dyn.GetHarmonicFreeEnergy(self.ensemble.current_T) / np.prod(self.ensemble.supercell)
1171+
harm_fe = self.dyn.GetHarmonicFreeEnergy(self.ensemble.current_T, w_pols = (self.ensemble.current_w, self.ensemble.current_pols)) / np.prod(self.ensemble.supercell)
11381172
anharm_fe = np.real(fe - harm_fe)
11391173

11401174
# Compute the KL ratio
@@ -1295,26 +1329,35 @@ def finalize(self, verbose = 1):
12951329
self.dyn.structure.coords[i,2]))
12961330
print ()
12971331
print (" ==== FINAL FREQUENCIES [cm-1] ==== ")
1298-
super_dyn = self.dyn.GenerateSupercellDyn(self.ensemble.supercell)
1299-
w, pols = super_dyn.DyagDinQ(0)
1300-
trans = CC.Methods.get_translations(pols, super_dyn.structure.get_masses_array())
1332+
#super_dyn = self.dyn.GenerateSupercellDyn(self.ensemble.supercell)
1333+
super_struct = self.dyn.structure.generate_supercell(self.dyn.GetSupercell())
1334+
w = self.ensemble.current_w.copy()
1335+
pols = self.ensemble.current_pols.copy()
1336+
1337+
#w, pols = super_dyn.DyagDinQ(0)
1338+
trans = CC.Methods.get_translations(pols, super_struct.get_masses_array())
13011339

13021340
for i in range(len(w)):
13031341
print ("Mode %5d: freq %16.8f cm-1 | is translation? " % (i+1, w[i] * __RyToCm__), trans[i])
13041342

13051343
print ()
13061344

13071345

1308-
13091346
def check_imaginary_frequencies(self):
13101347
"""
13111348
The following subroutine check if the current matrix has imaginary frequency. In this case
13121349
the minimization is stopped.
13131350
"""
13141351

1352+
# Avoid the check if the dynamical matrix has been computed.
1353+
if not self.minim_dyn:
1354+
return False
1355+
13151356
# Get the frequencies
13161357
#superdyn = self.dyn.GenerateSupercellDyn(self.ensemble.supercell)
1317-
w, pols = self.dyn.DiagonalizeSupercell()#.DyagDinQ(0)
1358+
w = self.ensemble.current_w.copy()
1359+
pols = self.ensemble.current_pols.copy()
1360+
#w, pols = self.dyn.DiagonalizeSupercell()#.DyagDinQ(0)
13181361
ss = self.dyn.structure.generate_supercell(self.dyn.GetSupercell())
13191362

13201363
# Get translations
@@ -1333,7 +1376,9 @@ def check_imaginary_frequencies(self):
13331376
if w[0] < 0:
13341377
print ("Total frequencies (excluding translations):")
13351378
#superdyn0 = self.ensemble.dyn_0.GenerateSupercellDyn(self.ensemble.supercell)
1336-
wold, pold = self.ensemble.dyn_0.DiagonalizeSupercell()# superdyn0.DyagDinQ(0)
1379+
wold = self.ensemble.w_0.copy()
1380+
pold = self.ensemble.pols_0.copy()
1381+
#wold, pold = self.ensemble.dyn_0.DiagonalizeSupercell()# superdyn0.DyagDinQ(0)
13371382

13381383
ss0 = self.ensemble.dyn_0.structure.generate_supercell(self.dyn.GetSupercell())
13391384

@@ -1824,7 +1869,7 @@ def ApplyFCPrecond(current_dyn, matrix, T = 0):
18241869

18251870

18261871

1827-
def GetStructPrecond(current_dyn, ignore_small_w = False):
1872+
def GetStructPrecond(current_dyn, ignore_small_w = False, w_pols = None):
18281873
r"""
18291874
GET THE PRECONDITIONER FOR THE STRUCTURE MINIMIZATION
18301875
=====================================================
@@ -1845,6 +1890,8 @@ def GetStructPrecond(current_dyn, ignore_small_w = False):
18451890
----------
18461891
current_dyn : Phonons()
18471892
The current dynamical matrix
1893+
w_pols :
1894+
If given, do not diagonalize the gradient
18481895
18491896
Returns
18501897
-------
@@ -1854,7 +1901,11 @@ def GetStructPrecond(current_dyn, ignore_small_w = False):
18541901
"""
18551902

18561903
# Dyagonalize the current dynamical matrix
1857-
w, pols = current_dyn.DyagDinQ(0)
1904+
if w_pols:
1905+
w = w_pols[0].copy()
1906+
pols = w_pols[1].copy()
1907+
else:
1908+
w, pols = current_dyn.DyagDinQ(0)
18581909

18591910
# Get some usefull array
18601911
mass = current_dyn.structure.get_masses_array()

Modules/Utilities.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,7 @@ def get_fix_rotations_CFG(dyn):
627627
assert np.max(np.abs(dyn.q_tot[0])) < 1e-5, "Error, the dynamical matrix must be at Gamma"
628628

629629
nat = dyn.structure.N_atoms
630+
raise NotImplementedError("Error, not yet implemented")
630631
# TODO: To be ended
631632

632633

File renamed without changes.

0 commit comments

Comments
 (0)