Skip to content

Commit bdba5b8

Browse files
authored
Merge pull request #65 from ahxbcn/fix_vibration
Fix bug in vibration.py
2 parents 79b23e4 + d75ad1e commit bdba5b8

3 files changed

Lines changed: 102 additions & 58 deletions

File tree

src/abacusagent/modules/submodules/vibration.py

Lines changed: 65 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -93,17 +93,65 @@ def collect_force(abacusjob_dir):
9393

9494
return metrics['force']
9595

96-
def filter_force(forces, selected_atoms):
96+
def prepare_abacus_vibration_analysis(input_params: Dict[str, Any],
97+
stru: AbacusStru,
98+
work_path: Path,
99+
abacus_inputs_dir: Path,
100+
selected_atoms: Optional[List[int]] = None,
101+
stepsize: float = 0.01,
102+
vib_cache_dir: str = "vib"):
97103
"""
98-
Select forces belong to selected atoms
104+
Prepare ABACUS input files for vibrational analysis.
99105
"""
100-
selected_atoms_force_idx = []
101-
for selected_atom in selected_atoms:
102-
selected_atoms_force_idx += [selected_atom*3, selected_atom*3+1, selected_atom*3+2]
106+
stru_file = input_params.get('stru_file', "STRU")
107+
displaced_stru = copy.deepcopy(stru)
108+
original_stru_coord = np.array(stru.get_coord(bohr=False, direct=False))
109+
110+
if selected_atoms is None:
111+
selected_atoms = [i for i in range(stru.get_natoms())]
112+
113+
selected_atoms.sort()
103114

104-
filtered_force = np.array(forces)[selected_atoms_force_idx]
115+
DIRECTION_MAP = ['x', 'y', 'z']
116+
STEP_MAP = {'+': 1, '-': -1}
117+
disped_stru_job_paths = []
118+
disped_stru_cache_labels = []
119+
120+
# Prepare ABACUS input files for the given structure
121+
abacus_scf_work_path = os.path.join(work_path, "SCF")
122+
original_stru_job_path = os.path.join(abacus_scf_work_path, "eq")
123+
os.makedirs(original_stru_job_path)
105124

106-
return filtered_force
125+
link_abacusjob(src=abacus_inputs_dir,
126+
dst=original_stru_job_path,
127+
copy_files=['INPUT', 'STRU', 'KPT', 'abacus.log'],
128+
exclude_directories=True)
129+
WriteInput(input_params, os.path.join(original_stru_job_path, "INPUT"))
130+
disped_stru_job_paths.append(original_stru_job_path)
131+
disped_stru_cache_labels.append(os.path.join(vib_cache_dir, 'cache.eq.json'))
132+
133+
# Prepare ABACUS input files for each displaced structure. nfree is assumed to be 2.
134+
for selected_atom in selected_atoms:
135+
for direction in range(3): # x, y and z directions
136+
displaced_stru_coord = copy.deepcopy(original_stru_coord)
137+
for step in STEP_MAP.keys(): # Two steps along one direction
138+
disped_stru_job_path = os.path.join(abacus_scf_work_path, f"disp-{selected_atom}-{DIRECTION_MAP[direction]}{step}")
139+
os.makedirs(disped_stru_job_path)
140+
141+
link_abacusjob(src=abacus_inputs_dir,
142+
dst=disped_stru_job_path,
143+
copy_files=['INPUT', 'STRU', 'KPT', 'abacus.log'],
144+
exclude_directories=True)
145+
146+
displaced_stru_coord[selected_atom][direction] = original_stru_coord[selected_atom][direction] + stepsize * STEP_MAP[step]
147+
displaced_stru.set_coord(displaced_stru_coord, bohr=False, direct=False)
148+
WriteInput(input_params, os.path.join(disped_stru_job_path, "INPUT"))
149+
displaced_stru.write(os.path.join(disped_stru_job_path, stru_file))
150+
151+
disped_stru_job_paths.append(disped_stru_job_path)
152+
disped_stru_cache_labels.append(os.path.join(vib_cache_dir, f"cache.{selected_atom}{DIRECTION_MAP[direction]}{step}.json"))
153+
154+
return disped_stru_job_paths, disped_stru_cache_labels
107155

108156
def abacus_vibration_analysis(abacus_inputs_dir: Path,
109157
selected_atoms: Optional[List[int]] = None,
@@ -142,7 +190,7 @@ def abacus_vibration_analysis(abacus_inputs_dir: Path,
142190
raise ValueError("stepsize should be positive.")
143191

144192
work_path = Path(generate_work_path()).absolute()
145-
193+
146194
input_params = ReadInput(os.path.join(abacus_inputs_dir, "INPUT"))
147195
stru_file = input_params.get('stru_file', "STRU")
148196
stru = AbacusStru.ReadStru(os.path.join(abacus_inputs_dir, stru_file))
@@ -157,66 +205,26 @@ def abacus_vibration_analysis(abacus_inputs_dir: Path,
157205
indices=selected_atoms,
158206
delta=stepsize,
159207
nfree=nfree)
160-
161-
displaced_stru = copy.deepcopy(stru)
162-
original_stru_coord = np.array(stru.get_coord(bohr=False, direct=False))
163-
164-
if selected_atoms is None:
165-
selected_atoms = [i for i in range(stru.get_natoms())]
166-
167-
selected_atoms.sort()
168-
169-
DIRECTION_MAP = ['x', 'y', 'z']
170-
STEP_MAP = {'+': 1, '-': -1}
171-
disped_stru_job_paths = []
172-
disped_stru_cache_labels = []
173-
174-
# Prepare ABACUS input files for the given structure
175-
abacus_scf_work_path = os.path.join(work_path, "SCF")
176-
original_stru_job_path = os.path.join(abacus_scf_work_path, "eq")
177-
os.makedirs(original_stru_job_path)
178-
179-
link_abacusjob(src=abacus_inputs_dir,
180-
dst=original_stru_job_path,
181-
copy_files=['INPUT', 'STRU', 'KPT', 'abacus.log'],
182-
exclude_directories=True)
183-
WriteInput(input_params, os.path.join(original_stru_job_path, "INPUT"))
184-
disped_stru_job_paths.append(original_stru_job_path)
185-
disped_stru_cache_labels.append(os.path.join(vib_cache_dir, 'cache.eq.json'))
186-
187-
# Prepare ABACUS input files for each displaced structure. nfree is assumed to be 2.
188-
for selected_atom in selected_atoms:
189-
for direction in range(3): # x, y and z directions
190-
displaced_stru_coord = copy.deepcopy(original_stru_coord)
191-
for step in STEP_MAP.keys(): # Two steps along one direction
192-
disped_stru_job_path = os.path.join(abacus_scf_work_path, f"disp-{selected_atom}-{DIRECTION_MAP[direction]}{step}")
193-
os.makedirs(disped_stru_job_path)
194-
195-
link_abacusjob(src=abacus_inputs_dir,
196-
dst=disped_stru_job_path,
197-
copy_files=['INPUT', 'STRU', 'KPT', 'abacus.log'],
198-
exclude_directories=True)
199-
200-
displaced_stru_coord[selected_atom][direction] = original_stru_coord[selected_atom][direction] + stepsize * STEP_MAP[step]
201-
displaced_stru.set_coord(displaced_stru_coord, bohr=False, direct=False)
202-
WriteInput(input_params, os.path.join(disped_stru_job_path, "INPUT"))
203-
displaced_stru.write(os.path.join(disped_stru_job_path, stru_file))
204208

205-
disped_stru_job_paths.append(disped_stru_job_path)
206-
disped_stru_cache_labels.append(os.path.join(vib_cache_dir, f"cache.{selected_atom}{DIRECTION_MAP[direction]}{step}.json"))
209+
disped_stru_job_paths, disped_stru_cache_labels = prepare_abacus_vibration_analysis(input_params,
210+
stru,
211+
work_path=work_path,
212+
abacus_inputs_dir=abacus_inputs_dir,
213+
selected_atoms=selected_atoms,
214+
stepsize=stepsize,
215+
vib_cache_dir=vib_cache_dir)
207216

208217
# Run ABACUS calculation for all prepared jobs
209218
run_abacus(disped_stru_job_paths)
210219

211220
# Collect needed forces for each ABACUS calculation and dump to cache directory used by ase.vibrations
212221
cache_forces_json = {"forces": {
213-
"__ndarray__": [[len(selected_atoms), 3],
222+
"__ndarray__": [[stru.get_natoms(), 3],
214223
"float64",
215224
[]]
216225
}}
217226
for disped_stru_job_path, disped_stru_cache_label in list(zip(disped_stru_job_paths, disped_stru_cache_labels)):
218-
force = collect_force(disped_stru_job_path)
219-
cache_forces_json["forces"]["__ndarray__"][2] = list(filter_force(force, selected_atoms))
227+
cache_forces_json["forces"]["__ndarray__"][2] = collect_force(disped_stru_job_path)
220228
with open(os.path.join(vib_cache_dir, disped_stru_cache_label), "w") as fin:
221229
json.dump(cache_forces_json, fin)
222230

@@ -253,6 +261,5 @@ def abacus_vibration_analysis(abacus_inputs_dir: Path,
253261
'vib_free_energy': float(free_energy),
254262
'vib_analysis_work_dir': Path(work_path).absolute()}
255263
except Exception as e:
256-
print(e)
257264
return {'message': f"Doing vibration analysis failed: {e}"}
258265

tests/integrate_test/data/ref_results.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,15 @@
194194
"vib_free_energy": 0.1834753687243439
195195
}
196196
},
197+
"test_abacus_vibration_analysis_h2_selected":
198+
{
199+
"result": {
200+
"frequencies": [58.98644787490194, 58.98644787490194, 3035.9634063376648],
201+
"zero_point_energy": 0.19551913052685363,
202+
"vib_entropy": 0.0004398879187182639,
203+
"vib_free_energy": 0.08145444835006974
204+
}
205+
},
197206
"test_abacus_run_md_nve":
198207
{
199208
"result": {}

tests/integrate_test/test_vibration.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,31 @@ def test_abacus_vibration_analysis_h2(self):
4949
self.assertAlmostEqual(outputs['zero_point_energy'], ref_results['zero_point_energy'], places=4)
5050
self.assertAlmostEqual(outputs['vib_entropy'], ref_results['vib_entropy'], places=4)
5151
self.assertAlmostEqual(outputs['vib_free_energy'], ref_results['vib_free_energy'], places=4)
52+
53+
def test_abacus_vibration_analysis_h2_selected(self):
54+
"""
55+
Test the abacus_vibration_analysis function for relaxed H2 molecule.
56+
Only 1 atom are selected for vibration analysis.
57+
"""
58+
test_func_name = inspect.currentframe().f_code.co_name
59+
ref_results = load_test_ref_result(test_func_name)
60+
61+
test_work_dir = self.test_path / test_func_name
62+
shutil.copytree(self.abacus_inputs_dir_h2, test_work_dir)
63+
shutil.copy2(self.stru_h2_relaxed, test_work_dir / "STRU")
64+
65+
outputs = abacus_vibration_analysis(test_work_dir,
66+
selected_atoms = [1],
67+
stepsize = 0.01,
68+
nfree = 2,
69+
temperature=400)
70+
print(outputs)
71+
72+
self.assertIsInstance(outputs['vib_analysis_work_dir'], get_path_type())
73+
74+
for freq_output, freq_ref in zip(outputs['frequencies'], ref_results['frequencies']):
75+
self.assertAlmostEqual(freq_output, freq_ref, places=2)
76+
77+
self.assertAlmostEqual(outputs['zero_point_energy'], ref_results['zero_point_energy'], places=4)
78+
self.assertAlmostEqual(outputs['vib_entropy'], ref_results['vib_entropy'], places=4)
79+
self.assertAlmostEqual(outputs['vib_free_energy'], ref_results['vib_free_energy'], places=4)

0 commit comments

Comments
 (0)