Skip to content

Commit 604800f

Browse files
committed
Add the RPI workflow
1 parent 91d9a85 commit 604800f

File tree

2 files changed

+313
-1
lines changed

2 files changed

+313
-1
lines changed

src/libra_py/workflows/nbra/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@
2525
"step2_many_body",
2626
"step3",
2727
"step3_many_body",
28-
"step4"
28+
"step4",
29+
"rpi"
2930
]

src/libra_py/workflows/nbra/rpi.py

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
import libra_py.units as units
2+
from libra_py import data_conv
3+
import libra_py.data_read as data_read
4+
import util.libutil as comn
5+
import matplotlib.pyplot as plt
6+
import sys
7+
import cmath
8+
import math
9+
import os
10+
import multiprocessing as mp
11+
import time
12+
import numpy as np
13+
import h5py
14+
import scipy.sparse as sp
15+
import multiprocessing as mp
16+
17+
if sys.platform == "cygwin":
18+
from cyglibra_core import *
19+
elif sys.platform == "linux" or sys.platform == "linux2":
20+
from liblibra_core import *
21+
22+
def run_patch_rpi(rpi_params):
23+
"""
24+
This function distributes the jobs to perform patch dynamics calculations in the restricted path integral (RPI) method.
25+
26+
Args:
27+
28+
rpi_params ( dictionary ): parameters controlling the execution of the RPI dynamics
29+
Can contain:
30+
31+
* **rpi_params["run_slurm"]** ( bool ): Whether to use the slurm environment to submit the jobs using the submit_template file.
32+
If it is set to False, it will run the calculations on the active session but multiple jobs will be run on the current active session.
33+
34+
* **rpi_params["submit_template"]** ( string ): The path of a template slurm submit file.
35+
36+
* **rpi_params["run_python_file"]** ( string ): The path of a template running script.
37+
38+
* **rpi_params["path_to_save_Hvibs"]** ( string ): The path of the vibronic Hamiltonian files.
39+
40+
* **rpi_params["submission_exe"]** ( string ): The submission executable
41+
42+
* **rpi_params["iread"]** ( int ): The initial step to read the vibronic Hamiltonian and time overlap
43+
44+
* **rpi_params["fread"]** ( int ): The final step to read the vibronic Hamiltonian and time overlap
45+
46+
* **rpi_params["iconds"]** ( list of ints ): The list of initial step indices from the trajectory segment from iread to fread.
47+
Each initial condition characterizes each batch.
48+
49+
* **rpi_params["nsteps"]** ( int ): The total number of RPI simulation steps
50+
51+
* **rpi_params["npatches"]** ( int ): The number of patches. The time duration of each patch dynamics will be int(nsteps/npatches) + 1.
52+
The additional single step is because the tsh recipe (see libra_py.dynamics.tsh.compute for details) used for the patch dynamics
53+
saves the dynamics information before the electron-nuclear propagation in each MD loop.
54+
55+
* **rpi_params["nstates"]** ( int ): The number of electronic states.
56+
57+
* **rpi_params["dt"]** ( double ): the time step in the atomic unit.
58+
59+
* **rpi_params["path_to_save_patch"]** ( string ): The path of the output patch dynamics
60+
61+
Return:
62+
None: but performs the action
63+
"""
64+
65+
out_dir = rpi_params["path_to_save_patch"]
66+
if not os.path.exists(out_dir):
67+
os.mkdir(out_dir)
68+
69+
file = open(rpi_params["run_python_file"], 'r')
70+
lines = file.readlines()
71+
file.close()
72+
73+
os.chdir(out_dir)
74+
75+
for ibatch, icond in enumerate(rpi_params["iconds"]):
76+
for ipatch in range(rpi_params["npatches"]):
77+
for istate in range(rpi_params["nstates"]):
78+
print(F"Submitting patch dynamics job of icond = {ibatch}, ipatch = {ipatch}, istate = {istate}")
79+
80+
# Compute the istep and fstep here, for each patch
81+
istep = rpi_params['iread'] + icond + ipatch*int(rpi_params['nsteps']/rpi_params['npatches'])
82+
fstep = fstep = istep + int(rpi_params['nsteps']/rpi_params['npatches']) + 1
83+
84+
dir_patch = F"job_{ibatch}_{ipatch}_{istate}"
85+
if os.path.exists(dir_patch):
86+
os.system('rm -rf ' + dir_patch)
87+
os.mkdir(dir_patch)
88+
os.chdir(dir_patch)
89+
file = open('run.py', 'w')
90+
91+
for i in range(len(lines)):
92+
if "params['path_to_save_Hvibs'] =" in lines[i]:
93+
file.write("""params['path_to_save_Hvibs'] = "%s"\n""" % rpi_params["path_to_save_Hvibs"])
94+
elif "params['istep'] =" in lines[i]:
95+
file.write("params['istep'] = %d\n" % istep)
96+
elif "params['fstep'] =" in lines[i]:
97+
file.write("params['fstep'] = %d\n" % fstep)
98+
elif "params['nsteps'] =" in lines[i]:
99+
file.write("params['nsteps'] = %d\n" % rpi_params["nsteps"])
100+
elif "params['npatches'] =" in lines[i]:
101+
file.write("params['npatches'] = %d\n" % rpi_params["npatches"])
102+
elif "params['istate'] =" in lines[i]:
103+
file.write("params['istate'] = %d\n" % istate)
104+
elif "params['dt'] =" in lines[i]:
105+
file.write("params['dt'] = %f\n" % rpi_params["dt"])
106+
else:
107+
file.write(lines[i])
108+
file.close()
109+
110+
if rpi_params["run_slurm"]:
111+
os.system('cp ../../%s %s' % (rpi_params["submit_template"], rpi_params["submit_template"]))
112+
os.system('%s %s' % (rpi_params["submission_exe"], rpi_params["submit_template"]))
113+
else:
114+
# Just in case you want to use a bash file and not submitting
115+
os.system("python run.py > log")
116+
os.chdir('../')
117+
118+
os.chdir('../')
119+
120+
def print_pop(outfile, time, pops):
121+
"""
122+
This function prints the RPI population
123+
"""
124+
with open(outfile, "w") as f:
125+
for istep in range(time.shape[0]):
126+
line = f"{time[istep]:13.8f} " + " ".join(f"{pops[istep, ist]:13.8f}" for ist in range(pops.shape[1])) + "\n"
127+
f.write(line)
128+
129+
def process_batch(args):
130+
ibatch, icond, rpi_params = args
131+
nsteps, dt, istate = rpi_params["nsteps"], rpi_params["dt"], rpi_params["istate"]
132+
npatches, nstates = rpi_params["npatches"], rpi_params["nstates"]
133+
path_to_save_patch = rpi_params["path_to_save_patch"]
134+
135+
nstep_patch = int(nsteps / npatches)
136+
pops = np.zeros((npatches * nstep_patch + 1, nstates))
137+
138+
P_temp = np.zeros(nstates)
139+
P_temp[istate] = 1.0
140+
pops[0, :] = P_temp
141+
142+
for ipatch in range(npatches):
143+
print(F"Summing ipatch = {ipatch}, ibatch = {ibatch}, icond = {icond}")
144+
145+
# Compute the transition probability from a patch dynamics
146+
T = np.zeros((nstep_patch, nstates, nstates)) # (timestep in a patch, init, dest)
147+
for ist in range(nstates):
148+
with h5py.File(F"{path_to_save_patch}/job_{ibatch}_{ipatch}_{ist}/out/mem_data.hdf", 'r') as f:
149+
pop_adi_data = np.array(f["se_pop_adi/data"])
150+
T[:, ist, :] = pop_adi_data[1:,:]
151+
T[:, ist, ist] = 0.0 # zero diagonal explicitly
152+
153+
# Fill diagonal by the norm conservation
154+
T[:, np.arange(nstates), np.arange(nstates)] = 1.0 - np.sum(T, axis=2)
155+
156+
for j in range(nstep_patch):
157+
glob_time = ipatch * nstep_patch + j + 1
158+
pops[glob_time, :] = P_temp @ T[j]
159+
if j == nstep_patch - 1:
160+
P_temp = pops[glob_time]
161+
162+
# Per-batch output
163+
time = np.array([x for x in range(npatches * nstep_patch + 1)]) * dt
164+
print(F"Print the population from ibatch, icond = {ibatch}, {icond}")
165+
print_pop(rpi_params["prefix"] + F"_ibatch{ibatch}.dat", time, pops)
166+
167+
return pops
168+
169+
def run_sum_rpi(rpi_params):
170+
"""
171+
This function conducts the RPI patch summation to yield the population dynamics in the whole time domain.
172+
173+
Args:
174+
175+
rpi_params ( dictionary ): parameters controlling the execution of the RPI dynamics
176+
Can contain:
177+
178+
* **rpi_params["nprocs"]** ( int ): The number of processors to be used.
179+
180+
* **rpi_params["nsteps"]** ( int ): The total number of RPI simulation steps
181+
182+
* **rpi_params["dt"]** ( double ): the time step in the atomic unit.
183+
184+
* **rpi_params["istate"]** ( int ): The initial state
185+
186+
* **rpi_params["iconds"]** ( list of ints ): The list of initial step indices from the trajectory segment from iread to fread.
187+
Each initial condition characterizes each batch.
188+
189+
* **rpi_params["npatches"]** ( int ): The number of patches.
190+
191+
* **rpi_params["nstates"]** ( int ): The number of electronic states.
192+
193+
* **rpi_params["path_to_save_patch"]** ( string ): The path of the precomputed patch dynamics
194+
195+
* **rpi_params["prefix"]** ( string ): The prefix for the population dynamics output
196+
197+
Return:
198+
None: but performs the action
199+
"""
200+
nprocs = rpi_params["nprocs"]
201+
202+
nsteps, dt = rpi_params["nsteps"], rpi_params["dt"]
203+
npatches, nstates = rpi_params["npatches"], rpi_params["nstates"]
204+
iconds = rpi_params["iconds"]
205+
206+
nstep_patch = int(nsteps / npatches)
207+
time = np.array([x for x in range(npatches * nstep_patch + 1)]) * dt
208+
pops_avg = np.zeros((npatches * nstep_patch + 1, nstates))
209+
210+
with mp.Pool(processes=nprocs) as pool:
211+
results = pool.map(process_batch, [(ibatch, icond, rpi_params) for ibatch, icond in enumerate(iconds)])
212+
213+
for pops in results:
214+
pops_avg += pops
215+
216+
pops_avg /= len(iconds)
217+
218+
print("Print the final population from all batches")
219+
print_pop(rpi_params["prefix"] + "_all.dat", time, pops_avg)
220+
221+
222+
def run_sum_rpi_crude(rpi_params):
223+
"""
224+
This function conducts the RPI patch summation to yield the population dynamics in the whole time domain.
225+
226+
Args:
227+
228+
rpi_params ( dictionary ): parameters controlling the execution of the RPI dynamics
229+
Can contain:
230+
231+
* **rpi_params["nsteps"]** ( int ): The total number of RPI simulation steps
232+
233+
* **rpi_params["dt"]** ( double ): the time step in the atomic unit.
234+
235+
* **rpi_params["istate"]** ( int ): The initial state
236+
237+
* **rpi_params["iconds"]** ( list of ints ): The list of initial step indices from the trajectory segment from iread to fread.
238+
Each initial condition characterizes each batch.
239+
240+
* **rpi_params["npatches"]** ( int ): The number of patches.
241+
242+
* **rpi_params["nstates"]** ( int ): The number of electronic states.
243+
244+
* **rpi_params["path_to_save_patch"]** ( string ): The path of the precomputed patch dynamics
245+
246+
* **rpi_params["prefix"]** ( string ): The prefix for the population dynamics output
247+
248+
Return:
249+
None: but performs the action
250+
"""
251+
252+
nsteps, dt, istate = rpi_params["nsteps"], rpi_params["dt"], rpi_params["istate"]
253+
254+
iconds, npatches, nstates = rpi_params["iconds"], rpi_params["npatches"], rpi_params["nstates"]
255+
path_to_save_patch = rpi_params["path_to_save_patch"]
256+
257+
nstep_patch = int(nsteps / npatches)
258+
259+
time = np.array([x for x in range(npatches * nstep_patch + 1)]) * dt
260+
261+
# The total population across all batches
262+
pops_avg = np.zeros((npatches * nstep_patch + 1, nstates))
263+
264+
for ibatch, icond in enumerate(iconds):
265+
# The global population on a batch
266+
pops = np.zeros((npatches * nstep_patch + 1, nstates))
267+
268+
P_temp = np.zeros(nstates)
269+
P_temp[istate] = 1.0 # Set the initial population
270+
pops[0, :] = P_temp
271+
272+
pop_patch = np.zeros((nstep_patch, nstates, nstates)) # A temporary array to read population of a patch dynamics
273+
T = np.zeros((nstep_patch, nstates, nstates)) # The transition probability from a patch dynamics
274+
for ipatch in range(npatches):
275+
print(F"Summing ipatch = {ipatch}, ibatch = {ibatch}, icond = {icond}")
276+
pop_patch.fill(0.0)
277+
T.fill(0.0)
278+
for ist in range(nstates):
279+
with h5py.File(F"{path_to_save_patch}/job_{ibatch}_{ipatch}_{ist}/out/mem_data.hdf", 'r') as f:
280+
pop_adi_data = np.array(f["se_pop_adi/data"])
281+
282+
for istep in range(nstep_patch):
283+
for jst in range(nstates):
284+
pop_patch[istep, jst, ist] = pop_adi_data[1 + istep, jst]
285+
286+
for ist in range(nstates):
287+
for jst in range(nstates):
288+
if ist == jst:
289+
continue
290+
T[:, ist, jst] = pop_patch[:, jst, ist]
291+
292+
for istep in range(nstep_patch):
293+
for ist in range(nstates):
294+
T[istep, ist, ist] = 1. - np.sum(T[istep, ist, :])
295+
296+
for j in range(nstep_patch):
297+
glob_time = ipatch * nstep_patch + j + 1
298+
for jst in range(nstates):
299+
pops[glob_time, jst] = np.sum(P_temp * T[j, :, jst])
300+
if j == nstep_patch - 1:
301+
P_temp = pops[glob_time]
302+
pops_avg += pops
303+
304+
print(F"Print the population from ibatch, icond = {ibatch}, {icond}")
305+
print_pop(rpi_params["prefix"] + F"_ibatch{ibatch}.dat", time, pops)
306+
307+
pops_avg /= len(iconds)
308+
309+
print("Print the final population from all batches")
310+
print_pop(rpi_params["prefix"] + "_all.dat", time, pops_avg)
311+

0 commit comments

Comments
 (0)