Skip to content

Commit e01f58b

Browse files
Merge branch 'master' into Diegom_sobol
2 parents a2fe438 + 2abf5a2 commit e01f58b

7 files changed

Lines changed: 382 additions & 14 deletions

File tree

Modules/AdvancedCalculations.py

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
import cellconstructor as CC
2+
import sscha, sscha.Cluster
3+
import threading, copy
4+
5+
import numpy as np
6+
import scipy, scipy.interpolate
7+
8+
import sys, os
9+
10+
MODULE_DESCRIPTION = '''
11+
This module contains useful functions to do post-processing analysis of the SSCHA.
12+
13+
For example, it implements custom cluster calculators to compute the electron-phonon effect on
14+
absorbption and the bandgap.
15+
16+
'''
17+
18+
19+
class OpticalQECluster(sscha.Cluster.Cluster):
20+
'''
21+
This class does the same as the sscha.Cluster to submit the calculation of an ensemble
22+
but it also computes the spectral properties.
23+
'''
24+
25+
def __init__(self, new_k_grid = None, random_offset = True, epsilon_data = None,
26+
epsilon_binary = 'epsilon.x -npool NPOOL -i PREFIX.pwi > PREFIX.pwo',
27+
**kwargs):
28+
'''
29+
Initialize the cluster object.
30+
31+
Parameters
32+
----------
33+
34+
new_k_grid : list
35+
The dimension of the new k mesh to run the nscf calculation
36+
random_offset : bool
37+
If True, a random offset is added to the calculation
38+
epsilon_data : dict
39+
The dictionary with the information of the namespace for the
40+
epsilon.x file
41+
epsilon_binary : string
42+
The path to the epsilon.x binary inside the cluster.
43+
**kwargs :
44+
All other arguments to be passed to the cluster.
45+
46+
'''
47+
self.new_k_grid = new_k_grid
48+
49+
if random_offset:
50+
self.random_offset = np.random.uniform(size = 3)
51+
else:
52+
self.random_offset = np.zeros(3)
53+
54+
self.kpts = None
55+
self.epsilon_binary = epsilon_binary
56+
57+
58+
self.read_sigma = False
59+
60+
self.epsilon_data = {
61+
'inputpp' : {
62+
'calculation' : 'sigma',
63+
'prefix' : 'AHAH'
64+
},
65+
'energy_grid' : {
66+
'wmin' : 0,
67+
'wmax' : 40,
68+
'nw' : 10000,
69+
'temperature' : 300
70+
}
71+
}
72+
if epsilon_data is not None:
73+
self.epsilon_data = epsilon_data
74+
75+
super().__init__(**kwargs)
76+
77+
78+
def __setattr__(self, __name, __value):
79+
super().__setattr__(__name, __value)
80+
81+
# Always regenerate the kpts
82+
if __name == 'new_k_grid' and not __value is None:
83+
assert len(__value) == 3, 'Error, new_k_grid must be a tuple with 3 elements'
84+
self.generate_kpts()
85+
86+
87+
def generate_kpts(self):
88+
'''
89+
Generate the kpts for the non self-consistent calculation
90+
'''
91+
92+
nkpts = int(np.prod(self.new_k_grid))
93+
94+
self.kpts = np.zeros((nkpts, 3), dtype = np.double)
95+
96+
for i in range(nkpts):
97+
x = float(i // (self.new_k_grid[1] * self.new_k_grid[2]))
98+
res = i % (self.new_k_grid[1] * self.new_k_grid[2])
99+
y = float(res // self.new_k_grid[2])
100+
z = float(res % self.new_k_grid[2])
101+
102+
x /= self.new_k_grid[0]
103+
y /= self.new_k_grid[1]
104+
z /= self.new_k_grid[2]
105+
106+
107+
x = (x + self.random_offset[0]) % 1
108+
y = (y + self.random_offset[1]) % 1
109+
z = (z + self.random_offset[2]) % 1
110+
111+
self.kpts[i, :] = [x, y, z]
112+
113+
114+
def get_execution_command(self, label):
115+
'''
116+
Get the command to execute the two pw.x and epsilon.x
117+
calculations
118+
'''
119+
120+
# Get the MPI command replacing NPROC
121+
new_mpicmd = self.mpi_cmd.replace("NPROC", str(self.n_cpu))
122+
123+
# Replace the NPOOL variable and the PREFIX in the binary
124+
binary = self.binary.replace("NPOOL", str(self.n_pool)).replace("PREFIX", label)
125+
binary_nscf = self.binary.replace("NPOOL", str(self.n_pool)).replace("PREFIX", label + '_nscf')
126+
binary_eps = self.epsilon_binary.replace("NPOOL", str(self.n_pool)).replace("PREFIX", label + '_eps')
127+
128+
tmt_str = ""
129+
if self.use_timeout:
130+
tmt_str = "timeout %d " % self.timeout
131+
132+
submission_txt = '''
133+
{0} {1} {2}
134+
{0} {1} {3}
135+
{0} {1} {4}
136+
'''.format(tmt_str, new_mpicmd, binary, binary_nscf, binary_eps)
137+
138+
# Remove the wavefunction and other heavy data
139+
submission_txt += '''
140+
rm -rf {0}.wfc* {0}.save
141+
142+
'''.format(label)
143+
144+
return submission_txt
145+
146+
147+
def read_results(self, calc, label):
148+
'''
149+
Get the results
150+
'''
151+
152+
results = super().read_results(calc, label)
153+
154+
print('THREAD ID {} START READING THE EPSILON'.format(threading.get_native_id()))
155+
156+
# Add the additional information related to the epsilon
157+
prefix = label
158+
epsilon_data = None
159+
eps_real = np.loadtxt(os.path.join(self.local_workdir, 'epsr_{}.dat'.format(prefix)))
160+
eps_imag = np.loadtxt(os.path.join(self.local_workdir, 'epsi_{}.dat'.format(prefix)))
161+
162+
nw = eps_real.shape[0]
163+
epsilon_data = np.zeros((nw, 3), dtype = np.double)
164+
165+
epsilon_data[:,0] = eps_real[:,0]
166+
epsilon_data[:, 1] = np.mean(eps_real[:, 1:], axis = 1)
167+
epsilon_data[:, 2] = np.mean(eps_imag[:, 2:], axis = 1)
168+
169+
results['epsilon'] = epsilon_data
170+
print('THREAD ID {} READED ALSO THE EPSILON!'.format(threading.get_native_id()))
171+
172+
return results
173+
174+
175+
176+
def prepare_input_file(self, structures, calc, labels):
177+
'''
178+
Prepare the input files for the cluster
179+
'''
180+
181+
182+
# Prepare the input file
183+
list_of_inputs = []
184+
list_of_outputs = []
185+
for i, (label, structure) in enumerate(zip(labels, structures)):
186+
# Avoid thread conflict
187+
self.lock.acquire()
188+
189+
try:
190+
calc.set_directory(self.local_workdir)
191+
PREFIX = label
192+
old_input = copy.deepcopy(calc.input_data)
193+
old_kpts = copy.deepcopy(calc.kpts)
194+
195+
196+
calc.input_data['control'].update({'prefix' : PREFIX})
197+
calc.set_label(label)
198+
calc.write_input(structure)
199+
200+
print("[THREAD {}] LBL: {} | PREFIX: {}".format(threading.get_native_id(), label, calc.input_data["control"]["prefix"]))
201+
202+
203+
# prepare the first scf calculation
204+
input_file = '{}.pwi'.format(label)
205+
output_file = '{}.pwo'.format(label)
206+
207+
list_of_inputs.append(input_file)
208+
list_of_outputs.append(output_file)
209+
210+
# prepare the nscf calculation
211+
calc.input_data['control'].update({'calculation' : 'nscf'})
212+
calc.input_data['system'].update({'nosym' : True})
213+
calc.kpts = self.kpts.copy()
214+
215+
# Generate the input file
216+
new_label = '{}_nscf'.format(label)
217+
calc.set_label(new_label, override_prefix = False)
218+
calc.write_input(structure)
219+
input_file = '{}.pwi'.format(new_label)
220+
output_file = '{}.pwo'.format(new_label)
221+
list_of_inputs.append(input_file)
222+
list_of_outputs.append(output_file)
223+
224+
225+
# Prepare the epsilon calculation
226+
eps_namelist = copy.deepcopy(self.epsilon_data)
227+
eps_namelist['inputpp'].update({'prefix' : calc.input_data['control']['prefix']})
228+
eps_lines = CC.Methods.write_namelist(eps_namelist)
229+
230+
231+
new_label = '{}_eps'.format(label)
232+
input_file = '{}.pwi'.format(new_label)
233+
output_file = '{}.pwo'.format(new_label)
234+
235+
# Write the epsilon input file
236+
eps_in_filename = os.path.join(self.local_workdir, input_file)
237+
with open(eps_in_filename, 'w') as fp:
238+
fp.writelines(eps_lines)
239+
print('fname: {} prepared'.format(eps_in_filename))
240+
241+
242+
243+
list_of_inputs.append(input_file)
244+
list_of_outputs.append(output_file)
245+
246+
# Append also the imaginary and real part of epsilon and sigma
247+
outputs = ['epsi_{}.dat', 'epsr_{}.dat', 'sigmai_{}.dat', 'sigmar_{}.dat']
248+
list_of_outputs += [x.format(PREFIX) for x in outputs]
249+
250+
251+
# Reset the calculator with the old data
252+
calc.input_data = old_input
253+
calc.kpts = old_kpts
254+
255+
256+
257+
except Exception as e:
258+
MSG = '''
259+
Error while writing input file {}.
260+
'''.format(label)
261+
print(MSG)
262+
print('ERROR MSG:')
263+
print(e)
264+
265+
# Release the lock on the threads
266+
self.lock.release()
267+
268+
print('THREAD: {} inputs: {} outputs: {}'.format(threading.get_native_id(), list_of_inputs, list_of_outputs))
269+
270+
271+
return list_of_inputs, list_of_outputs
272+
273+
274+
275+
def get_optical_spectrum(ensemble, w_array = None):
276+
"""
277+
COMPUTE THE OPTICAL SPECTRUM
278+
============================
279+
280+
By averaging the dielectric properties of phonon-displaced configurations,
281+
we can get the phonon-renormalized optical spectrum.
282+
283+
284+
Parameters
285+
----------
286+
ensemble : sscha.Ensemble.Ensemble
287+
The ensemble. It should contain the optical data.
288+
To do it, use the cluster calculator OpticalQECluster of
289+
the AdvancedCalculation module.
290+
w_array : ndarray, Optional
291+
The frequencies at which to interpolate the results.
292+
If not passed, the full dataset of frequencies will be returned.
293+
294+
Results
295+
-------
296+
w : ndarray, dtype = float
297+
The frequency (eV)
298+
n : ndarray, dtype = complex
299+
The (complex) refractive index
300+
"""
301+
302+
w_data = None
303+
for i in range(ensemble.N):
304+
if not 'epsilon' in ensemble.all_properties[i]:
305+
ERR = """
306+
Error, the configuration {} has no 'epsilon' data.
307+
""".format(i)
308+
raise ValueError(ERR)
309+
310+
data = ensemble.all_properties[i]['epsilon']
311+
312+
if w_data is None:
313+
w_data = data[:,0]
314+
eps_real = np.zeros_like(w_data)
315+
eps_imag = np.zeros_like(w_data)
316+
317+
318+
eps_real += data[:,1]
319+
eps_imag += data[:,2]
320+
321+
eps_real /= ensemble.N
322+
eps_imag /= ensemble.N
323+
324+
# Interpolate the data if requested
325+
if w_array is not None:
326+
f_real = scipy.interpolate.interp1d(w_data, eps_real,
327+
kind = 'cubic', bounds_error = False, fill_value = 'extrapolate')
328+
eps_real = f_real(w_array)
329+
f_imag = scipy.interpolate.interp1d(w_data, eps_imag,
330+
kind = 'cubic', bounds_error = False, fill_value = 'extrapolate')
331+
eps_imag = f_imag(w_array)
332+
w_data = w_array
333+
334+
# Build the complex epsilon
335+
epsilon = eps_real + 1j * eps_imag
336+
337+
# Build the refractive index
338+
n = np.sqrt(epsilon)
339+
340+
return w_data, n
341+
342+

Modules/Cluster.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,7 +1451,7 @@ def compute_ensemble_batch(self, ensemble, cellconstructor_calc, get_stress = Tr
14511451

14521452
# Setup if the ensemble has the stress
14531453
ensemble.has_stress = get_stress
1454-
ensemble.all_properties = [None] * ensemble.N
1454+
#ensemble.all_properties = [None] * ensemble.N
14551455

14561456
# Check if the working directory exists
14571457
if not os.path.isdir(self.local_workdir):
@@ -1510,7 +1510,8 @@ def compute_single_jobarray(jobs_id, calc):
15101510
if not is_success:
15111511
continue
15121512

1513-
ensemble.all_properties[num] = copy.deepcopy(res)
1513+
res_only_extra = {x : res[x] for x in res if x not in ["energy", "forces", "stress", "structure"]}
1514+
ensemble.all_properties[num].update(res_only_extra)
15141515
ensemble.energies[num] = res["energy"] / units["Ry"]
15151516
ensemble.forces[num, :, :] = res["forces"] / units["Ry"]
15161517
if get_stress:
@@ -1570,6 +1571,8 @@ def compute_single_jobarray(jobs_id, calc):
15701571
print ("Expected batch ordinary resubmissions:", num_batch_offset)
15711572
raise ValueError("Error, resubmissions exceeded the maximum number of %d" % self.max_recalc)
15721573
break
1574+
1575+
print("CALCULATION ENDED: all properties: {}".format(ensemble.all_properties))
15731576

15741577

15751578

@@ -1585,10 +1588,11 @@ def compute_ensemble(self, ensemble, ase_calc, get_stress = True, timeout=None):
15851588
"""
15861589

15871590
# Check if the compute_ensemble batch must be done
1588-
if self.job_number != 1:
1589-
self.compute_ensemble_batch(ensemble, ase_calc, get_stress, timeout)
1590-
return
1591+
#if self.job_number != 1:
1592+
self.compute_ensemble_batch(ensemble, ase_calc, get_stress, timeout)
1593+
return
15911594

1595+
"""
15921596
# Track the remaining configurations
15931597
success = [False] * ensemble.N
15941598
@@ -1657,4 +1661,5 @@ def compute_single(num, calc):
16571661
print ("Expected batch ordinary resubmissions:", num_batch_offset)
16581662
raise ValueError("Error, resubmissions exceeded the maximum number of %d" % self.max_recalc)
16591663
break
1664+
"""
16601665

0 commit comments

Comments
 (0)