Skip to content

Commit cdad44d

Browse files
Merge pull request #63 from SSCHAcode/new_cluster
A new cluster interface. The new cluster interface waits for the queue without remaining pending and uses tar archives to move files between the server and the client, minimizing I/O operations.
2 parents 0ec3ee5 + 682fa5f commit cdad44d

11 files changed

Lines changed: 915 additions & 234 deletions

File tree

Modules/Cluster.py

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

Modules/Ensemble.py

Lines changed: 68 additions & 5 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
"""
@@ -2680,7 +2736,7 @@ def get_dynamical_bubble(self, q, w, smearing = 1e-5):
26802736

26812737

26822738
def get_free_energy_hessian(self, include_v4 = False, get_full_hessian = True, verbose = False, \
2683-
use_symmetries = True):
2739+
use_symmetries = True, return_d3 = False):
26842740
"""
26852741
GET THE FREE ENERGY ODD CORRECTION
26862742
==================================
@@ -2705,11 +2761,16 @@ def get_free_energy_hessian(self, include_v4 = False, get_full_hessian = True, v
27052761
use_symmetries : bool
27062762
If true, the d3 and d4 are symmetrized in real space.
27072763
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.
27082766
27092767
Returns
27102768
-------
27112769
phi_sc : Phonons()
27122770
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.
27132774
"""
27142775
# For now the v4 is not implemented
27152776
# if include_v4:
@@ -2845,6 +2906,8 @@ def get_free_energy_hessian(self, include_v4 = False, get_full_hessian = True, v
28452906
dyn_hessian.dynmats[iq] = dynq_odd[iq, :, :]
28462907

28472908

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

28502913

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,

Modules/SchaMinimizer.py

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ def __init__(self, ensemble = None, root_representation = "normal",
231231
self.__gw__ = []
232232
self.__gw_err__ = []
233233
self.__KL__ = []
234+
self.__good_kasteps__ = []
234235

235236

236237

@@ -554,6 +555,10 @@ def minimization_step(self, custom_function_gradient = None):
554555

555556
# Update the previous gradient
556557
self.prev_grad = dyn_grad
558+
559+
# If the step is good, pass chose it
560+
if self.minimizer.new_direction:
561+
self.__good_kasteps__.append(len(self.__fe__) - 1)
557562

558563
def setup_from_namelist(self, input_file):
559564
"""
@@ -1232,7 +1237,25 @@ def finalize(self, verbose = 1):
12321237
self.__gw_err__[-1] * __RyTomev__))
12331238
print ("Kong-Liu effective sample size = ", self.ensemble.get_effective_sample_size())
12341239
print ()
1235-
1240+
1241+
# Print the total force on the structure
1242+
print("Total force on the centroids [eV/A]:")
1243+
forces, err = self.ensemble.get_average_forces(True)
1244+
# Apply the symmetries
1245+
if not self.neglect_symmetries:
1246+
qe_sym = CC.symmetries.QE_Symmetry(self.dyn.structure)
1247+
if self.use_spglib:
1248+
qe_sym.SetupFromSPGLIB()
1249+
else:
1250+
qe_sym.SetupQPoint()
1251+
qe_sym.SymmetrizeVector(forces)
1252+
qe_sym.SymmetrizeVector(err)
1253+
err /= np.sqrt(qe_sym.QE_nsym)
1254+
1255+
for i in range(self.dyn.structure.N_atoms):
1256+
print("{:4d}) {:14.6f}{:14.6f}{:14.6f} +- {:14.6f}{:14.6f}{:14.6f}".format(*list([i] + list(forces[i, :] * CC.Units.RY_TO_EV) + list(err[i,:] * CC.Units.RY_TO_EV))))
1257+
print()
1258+
12361259
if self.ensemble.has_stress and verbose >= 1:
12371260
print ()
12381261
print (" ==== STRESS TENSOR [GPa] ==== ")
@@ -1387,9 +1410,9 @@ def check_stop(self):
13871410
print ()
13881411
print ("The gw gradient satisfy the convergence condition.")
13891412

1390-
if self.gradi_op == "gc":
1413+
if self.gradi_op == "gc" or not self.minim_struct:
13911414
total_cond = gc_cond
1392-
elif self.gradi_op == "gw":
1415+
elif self.gradi_op == "gw" or not self.minim_dyn:
13931416
total_cond = gw_cond
13941417
elif self.gradi_op == "all":
13951418
total_cond = gc_cond and gw_cond
@@ -1465,18 +1488,18 @@ def plot_results(self, save_filename = None, plot = True):
14651488

14661489
self.__gc__.append(self.__gc__[-1])
14671490
self.__gc_err__.append(self.__gc_err__[-1])
1491+
1492+
fe = np.array(self.__fe__)[self.__good_kasteps__] * __RyTomev__
1493+
fe_err = np.array(self.__fe_err__)[self.__good_kasteps__] * __RyTomev__
14681494

1469-
# Convert the data in numpy arrays
1470-
fe = np.array(self.__fe__) * __RyTomev__
1471-
fe_err = np.array(self.__fe_err__) * __RyTomev__
14721495

1473-
gc = np.array(self.__gc__) * __RyTomev__
1474-
gc_err = np.array(self.__gc_err__) * __RyTomev__
1496+
gc = np.array(self.__gc__)[self.__good_kasteps__] * __RyTomev__
1497+
gc_err = np.array(self.__gc_err__)[self.__good_kasteps__] * __RyTomev__
14751498

1476-
gw = np.array(self.__gw__) * __RyTomev__
1477-
gw_err = np.array(self.__gw_err__) * __RyTomev__
1499+
gw = np.array(self.__gw__)[self.__good_kasteps__] * __RyTomev__
1500+
gw_err = np.array(self.__gw_err__)[self.__good_kasteps__] * __RyTomev__
14781501

1479-
kl = np.array(self.__KL__, dtype = np.float64)
1502+
kl = np.array(self.__KL__, dtype = np.float64)[self.__good_kasteps__]
14801503

14811504
steps = np.arange(len(fe))
14821505

@@ -1489,6 +1512,7 @@ def plot_results(self, save_filename = None, plot = True):
14891512
np.savetxt(save_filename, np.real(np.transpose(save_data)),
14901513
header = "Steps; Free energy +- error [meV]; FC gradient +- error [bohr^2]; Structure gradient +- error [meV / A]; Kong-Liu N_eff")
14911514
print ("Minimization data saved in %s." % save_filename)
1515+
print('Good steps at {}'.format(self.__good_kasteps__))
14921516

14931517

14941518
# Plot

Modules/Tools.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727
import scipy, scipy.linalg
2828
import numpy as np
2929
import os
30+
import json
31+
class NumpyEncoder(json.JSONEncoder):
32+
def default(self, obj):
33+
if isinstance(obj, np.ndarray):
34+
return obj.tolist()
35+
return json.JSONEncoder.default(self, obj)
3036

3137

3238
# Here some usefull functions to solve linear systems

0 commit comments

Comments
 (0)