Skip to content

Commit 26d2613

Browse files
committed
Merge branch 'master' of github.com:SSCHAcode/python-sscha
2 parents 172ef45 + 25e4822 commit 26d2613

14 files changed

Lines changed: 1757 additions & 245 deletions

LICENSE.txt

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

Modules/Cluster.py

Lines changed: 556 additions & 170 deletions
Large diffs are not rendered by default.

Modules/Ensemble.py

Lines changed: 85 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22
from __future__ import print_function
33
import sys, os
4+
import warnings
45
import numpy as np
56
import time
67

@@ -33,6 +34,9 @@
3334

3435
import sscha.Parallel as Parallel
3536
from sscha.Parallel import pprint as print
37+
from sscha.Tools import NumpyEncoder
38+
39+
import json
3640

3741
import difflib
3842

@@ -95,7 +99,11 @@
9599
UNITS_DEFAULT = "default"
96100
UNITS_HARTREE = "hartree"
97101
SUPPORTED_UNITS = [UNITS_DEFAULT, UNITS_HARTREE]
98-
102+
class NumpyEncoder(json.JSONEncoder):
103+
def default(self, obj):
104+
if isinstance(obj, np.ndarray):
105+
return obj.tolist()
106+
return json.JSONEncoder.default(self, obj)
99107

100108
class Ensemble:
101109
__debug_index__ = 0
@@ -179,7 +187,7 @@ def __init__(self, dyn0, T0, supercell = None, **kwargs):
179187
self.stress_computed = None
180188

181189
# Get the extra quantities
182-
self.extra_quantities = {}
190+
self.all_properties = []
183191

184192

185193
# Setup the attribute control
@@ -610,7 +618,7 @@ def load_from_calculator_output(self, directory, out_ext = ".pwo"):
610618

611619
# Get the stress if any
612620
try:
613-
stress = ase_struct.get_stress(voigt=False)
621+
stress = - ase_struct.get_stress(voigt=False)
614622
# eV/A^3 -> Ry/bohr^3
615623
stress /= Rydberg / Bohr**3
616624
count_stress += 1
@@ -762,6 +770,10 @@ def save_bin(self, data_dir, population_id = 1):
762770
np.save("%s/stresses_pop%d.npy" % (data_dir, population_id), self.stresses)
763771

764772
self.dyn_0.save_qe("%s/dyn_gen_pop%d_" % (data_dir, population_id))
773+
774+
if len(self.all_properties):
775+
with open(os.path.join(data_dir, "all_properties_pop%d.json" % population_id), "w") as fp:
776+
json.dump({"properties" : self.all_properties}, fp, cls=NumpyEncoder)
765777

766778
def save_enhanced_xyz(self, filename, append_mode = True, stress_key = "virial", forces_key = "force", energy_key = "energy"):
767779
"""
@@ -861,7 +873,8 @@ def save_raw(self, root_directory, type_dict = None):
861873
np.savetxt(os.path.join(root_directory, "coord.raw"), self.xats.reshape((self.N, 3 * nat)))
862874

863875
# Save the box
864-
np.savetxt(os.path.join(root_directory, "box.raw"), np.tile(self.current_dyn.structure.unit_cell.ravel(), (self.N, 1)))
876+
#Prepare an array with all the unit cells
877+
np.savetxt(os.path.join(root_directory, "box.raw"), np.array([s.unit_cell.ravel() for s in self.structures]))
865878

866879
# Save the forces
867880
np.savetxt(os.path.join(root_directory, "force.raw"), self.forces.reshape((self.N, 3*nat)) * Rydberg)
@@ -949,6 +962,16 @@ def load_bin(self, data_dir, population_id = 1, avoid_loading_dyn = False):
949962
# Setup that both forces and stresses are not computed
950963
self.stress_computed = np.ones(self.N, dtype = bool)
951964
self.force_computed = np.ones(self.N, dtype = bool)
965+
966+
all_prop_fname = os.path.join(data_dir, "all_properties_pop%d.json" % population_id)
967+
if os.path.exists(all_prop_fname):
968+
with open(os.path.join(data_dir, "all_properties_pop%d.json" % population_id), "w") as fp:
969+
props= json.load(fp)
970+
if "properties" in props:
971+
self.all_properties = props["properties"]
972+
else:
973+
warnings.warn("WARNING: found file {} but not able to load the properties keyword.".format(all_prop_fname))
974+
952975

953976

954977
def init_from_structures(self, structures):
@@ -1616,6 +1639,39 @@ def get_free_energy(self, return_error = False):
16161639
return free_energy, error
16171640
return free_energy
16181641

1642+
def get_entropy(self, return_error = False):
1643+
r"""
1644+
GET THE ENTROPY
1645+
===============
1646+
1647+
Get the total anharmonic entropy.
1648+
1649+
The equation implemented is the analytical derivative of the free energy,
1650+
and assumes that the SSCHA free energy is minimized.
1651+
1652+
.. math::
1653+
1654+
S = - \frac{dF}{dT}
1655+
1656+
S = S_{harm} - \left<V - {\mathcal V}\right>\sum_\mu \frac{1}{1 + 2n_\mu} \frac{dn_\mu}{dT}
1657+
1658+
1659+
where :math:`S_{harm}` is the 'harmonic' entropy computed from the dynamucal matrix,
1660+
plus a correction accounting for the ensemble anharmonicity.
1661+
1662+
1663+
Parameters
1664+
----------
1665+
return_error : bool
1666+
If true, returns also the error
1667+
1668+
Results
1669+
-------
1670+
entropy, error : float
1671+
Returns the entropy and [optionally] the stochastic error.
1672+
"""
1673+
raise NotImplementedError("Error, to be implemented")
1674+
16191675

16201676
def get_free_energy_interpolating(self, target_supercell, support_dyn_coarse = None, support_dyn_fine = None, error_on_imaginary_frequency = True, return_error = False):
16211677
"""
@@ -1625,6 +1681,9 @@ def get_free_energy_interpolating(self, target_supercell, support_dyn_coarse = N
16251681
This is a trick to interpolate the free energy in the
16261682
infinite volume limit.
16271683
1684+
Note, this function report the free eenrgy in the primitive cell, while the method get_free_energy
1685+
returns the energy in the supercell.
1686+
16281687
Parameters
16291688
----------
16301689
target_supercell : list (N, N, N)
@@ -1659,12 +1718,14 @@ def get_free_energy_interpolating(self, target_supercell, support_dyn_coarse = N
16591718

16601719

16611720
# Interpolate the dynamical matrix
1662-
new_dyn = self.current_dyn.Interpolate( self.current_dyn.GetSupercell(),
1663-
target_supercell,
1664-
support_dyn_coarse,
1665-
support_dyn_fine)
1721+
if support_dyn_fine is not None:
1722+
new_dyn = self.current_dyn.Interpolate( self.current_dyn.GetSupercell(),
1723+
target_supercell,
1724+
support_dyn_coarse,
1725+
support_dyn_fine)
1726+
else:
1727+
new_dyn = self.current_dyn.InterpolateMesh(target_supercell)
16661728

1667-
# TODO: Allow double interpolation in case of support dyn
16681729

16691730
# Get the new harmonic free energy
16701731
harm_fe = new_dyn.GetHarmonicFreeEnergy(self.current_T,
@@ -1863,10 +1924,14 @@ def get_preconditioned_gradient(self, subtract_sscha = True, return_error = Fals
18631924
if np.prod(self.dyn_0.GetSupercell()) > 1:
18641925

18651926
# Perform the fourier transform
1866-
q_grad = CC.Phonons.GetDynQFromFCSupercell(grad, np.array(self.current_dyn.q_tot),
1927+
if return_error:
1928+
q_grad,q_grad_err = CC.Phonons.GetDynQFromFCSupercell(grad, np.array(self.current_dyn.q_tot),
1929+
self.current_dyn.structure, supercell_dyn.structure,fc2=grad_err)
1930+
else:
1931+
q_grad = CC.Phonons.GetDynQFromFCSupercell(grad, np.array(self.current_dyn.q_tot),
18671932
self.current_dyn.structure, supercell_dyn.structure)
1868-
q_grad_err = CC.Phonons.GetDynQFromFCSupercell(grad_err, np.array(self.current_dyn.q_tot),
1869-
self.current_dyn.structure, supercell_dyn.structure)
1933+
#q_grad_err = CC.Phonons.GetDynQFromFCSupercell(grad_err, np.array(self.current_dyn.q_tot),
1934+
# self.current_dyn.structure, supercell_dyn.structure)
18701935
else:
18711936
nat3, _ = grad.shape
18721937
q_grad = np.zeros( (1, nat3, nat3), dtype = np.double)
@@ -2671,7 +2736,7 @@ def get_dynamical_bubble(self, q, w, smearing = 1e-5):
26712736

26722737

26732738
def get_free_energy_hessian(self, include_v4 = False, get_full_hessian = True, verbose = False, \
2674-
use_symmetries = True):
2739+
use_symmetries = True, return_d3 = False):
26752740
"""
26762741
GET THE FREE ENERGY ODD CORRECTION
26772742
==================================
@@ -2696,11 +2761,16 @@ def get_free_energy_hessian(self, include_v4 = False, get_full_hessian = True, v
26962761
use_symmetries : bool
26972762
If true, the d3 and d4 are symmetrized in real space.
26982763
It requires that spglib is installed to detect symmetries in the supercell correctly.
2764+
return_d3 : bool
2765+
If true, returns also the tensor of three phonon scattering.
26992766
27002767
Returns
27012768
-------
27022769
phi_sc : Phonons()
27032770
The dynamical matrix of the free energy hessian in (Ry/bohr^2)
2771+
d3 : ndarray (size = (3*nat_sc, 3*nat_sc, 3*nat_sc), Optional
2772+
Return the three-phonon-scattering tensor (in Ry atomic units).
2773+
Only if return_d3 is True.
27042774
"""
27052775
# For now the v4 is not implemented
27062776
# if include_v4:
@@ -2836,6 +2906,8 @@ def get_free_energy_hessian(self, include_v4 = False, get_full_hessian = True, v
28362906
dyn_hessian.dynmats[iq] = dynq_odd[iq, :, :]
28372907

28382908

2909+
if return_d3:
2910+
return dyn_hessian, d3* 2.0 # Ha to Ry
28392911
return dyn_hessian
28402912

28412913

Modules/Optimizer.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ def __init__(self, starting_unit_cell):
4646
self.uc_0_inv = np.linalg.inv(self.uc_0)
4747
self.x_start = None
4848
self.reset_strain = False
49+
50+
# A control parameter for the line minimization (avoid failures)
51+
self.last_factor = 0
4952

5053
self.uc_old = None
5154

@@ -105,7 +108,8 @@ def get_line_step(self, grad):
105108
sys.stdout.flush()
106109

107110
print("[CELL] GRADIENT DOT DIRECTION = {}".format(factor))
108-
111+
failure_lmin = False
112+
109113
sys.stdout.flush()
110114
# Regularization (avoid run away)
111115
if factor > 2:
@@ -119,20 +123,36 @@ def get_line_step(self, grad):
119123
print(" incrementing by a 15 %")
120124

121125
elif factor < self.min_alpha_factor:
126+
# Check if the last time was good
127+
if self.last_factor != 0:
128+
if factor < self.last_factor:
129+
print("[CELL] Warning: reduced step and got worse; restarting line minimization with a good step.")
130+
failure_lmin = True
131+
self.last_factor = factor
122132
factor = self.min_alpha_factor
123133
good_step = False
124134

125135
if np.isnan(factor):
136+
self.last_factor = 0
126137
factor = self.min_alpha_factor
127138
good_step = False
128-
129-
self.alpha *= factor
139+
140+
# Check if the line minimization got contradditing result
141+
# In this case stop the line minimization and proceed with a standard gradient descend.
142+
if failure_lmin:
143+
good_step = True
144+
else:
145+
self.alpha *= factor
130146

131147

132148
#if self.alpha < self.min_alpha_step * self.alpha0:
133149
# self.alpha = self.min_alpha_step * self.alpha0
134150
#self.alpha *= factor
135151

152+
# Reset the check on the line minimization if this is a good step
153+
if good_step:
154+
self.last_factor = 0
155+
136156
return good_step
137157

138158
def reset(self, unit_cell):
@@ -271,7 +291,7 @@ def UpdateCell(self, unit_cell, stress_tensor, fix_volume = False, verbose = Tru
271291

272292

273293

274-
# Get the strain tensor up to know
294+
# Get the strain tensor up to now
275295
strain = np.transpose(self.uc_0_inv.dot(unit_cell) - I)
276296

277297
# Get the gradient with respect to the strain

Modules/Relax.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,33 @@ def vc_relax(self, target_press = 0, static_bulk_modulus = 100,
441441
True if the minimization converged, False if the maximum number of
442442
populations has been reached.
443443
"""
444+
445+
# Prepare the saving directory
446+
if ensemble_loc is None:
447+
ensemble_loc = self.data_dir
448+
449+
if (not ensemble_loc) and self.save_ensemble:
450+
ERR_MSG = """
451+
Error, you must specify where to save the ensembles.
452+
this can be done either passing ensemble_loc = "path/to/dir"
453+
for the ensemble, or by setting the data_dir attribute of this object.
454+
"""
455+
raise IOError(ERR_MSG)
456+
457+
if self.save_ensemble:
458+
if not os.path.exists(ensemble_loc):
459+
os.makedirs(ensemble_loc)
460+
else:
461+
if not os.path.isdir(ensemble_loc):
462+
ERR_MSG = """
463+
Error, the specified location to save the ensemble:
464+
'{}'
465+
already exists and it is not a directory.
466+
""".format(ensemble_loc)
467+
raise IOError(ERR_MSG)
468+
469+
470+
444471
# Rescale the target pressure in eV / A^3
445472
target_press_evA3 = target_press / sscha.SchaMinimizer.__evA3_to_GPa__
446473
I = np.eye(3, dtype = np.float64)
@@ -509,6 +536,10 @@ def vc_relax(self, target_press = 0, static_bulk_modulus = 100,
509536
self.minim.ensemble.dyn_0 = self.minim.dyn.Copy()
510537
if pop != start_pop or not restart_from_ens:
511538
self.minim.ensemble.generate(self.N_configs)
539+
540+
# Save also the generation
541+
#if ensemble_loc is not None and self.save_ensemble:
542+
# self.minim.ensemble.save_bin(ensemble_loc, pop)
512543

513544
# Compute energies and forces
514545
self.minim.ensemble.compute_ensemble(self.calc, True, stress_numerical,

0 commit comments

Comments
 (0)