Skip to content

Commit 0b72076

Browse files
authored
Merge pull request #79 from ahxbcn/bader
Update bader tools
2 parents 0b03840 + 33a41f1 commit 0b72076

File tree

6 files changed

+44
-18
lines changed

6 files changed

+44
-18
lines changed

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ testpaths = ["tests"]
3939
addopts = "-v"
4040

4141
[project.optional-dependencies]
42-
dev = ["mcp>=1.9.0",
43-
"abacustest>=0.4.37",
42+
dev = ["mcp>=1.10.0",
43+
"abacustest>=0.4.49",
4444
"science-agent-sdk"
4545
]

src/abacusagent/modules/bader.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ def abacus_badercharge_run(
1919
2020
Returns:
2121
dict: A dictionary containing:
22-
- bader_charges: List of Bader charge for each atom.
22+
- net_bader_charges: List of net Bader charge for each atom. Core charge is included.
23+
- number_of_electrons: List of number of electrons around each atom. Core charge is not included.
24+
- core_charges: List of core charge for each atom.
2325
- atom_labels: Labels of atoms in the structure.
2426
- abacus_workpath: Absolute path to the ABACUS work directory.
2527
- badercharge_run_workpath: Absolute path to the Bader analysis work directory.
28+
- bader_result_csv: Absolute path to the CSV file containing detailed Bader charge results
2629
"""
2730
return _abacus_badercharge_run(abacus_inputs_dir)
2831

@@ -40,7 +43,7 @@ def calculate_bader_charge_from_cube(
4043
4144
Returns:
4245
dict: A dictionary containing:
43-
- net_charges: List of net charge for each atom. Core charge is included.
46+
- net_bader_charges: List of net charge for each atom. Core charge is included.
4447
- number_of_electrons: List of number of electrons around each atom. Core charge is not included.
4548
- core_charges: List of core charge for each atom.
4649
- work_path: Absolute path to the work directory.

src/abacusagent/modules/submodules/bader.py

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
import glob
77
import shutil
88
import json
9+
import csv
10+
import unittest
911
from typing import List, Dict, Optional, Any
10-
1112
from pathlib import Path
1213

1314
import numpy as np
14-
import unittest
15+
from pymatgen.core.periodic_table import Element
1516

1617
from abacustest.lib_prepare.abacus import ReadInput, WriteInput, AbacusStru
1718
from abacustest.lib_model.comm import check_abacus_inputs
@@ -204,7 +205,14 @@ def calculate_bader_charge_from_cube(
204205
fcube (list|str): List of cube files or a single cube file path.
205206
206207
Returns:
207-
list: A list of Bader charges.
208+
dict: A dictionary containing:
209+
- atom_labels: List of atom labels.
210+
- net_bader_charges: List of net charge for each atom. Core charge is included.
211+
- number_of_electrons: List of number of electrons around each atom. Core charge is not included.
212+
- core_charge: List of core charge for each atom.
213+
- work_path: Absolute path to the work directory.
214+
- cube_file: Absolute path to the cube file used in this tool.
215+
- charge_results_json: Absolute path to the JSON file containing detailed Bader charge results
208216
"""
209217
if not isinstance(fcube, (list, tuple)):
210218
fcube = [fcube]
@@ -231,19 +239,22 @@ def calculate_bader_charge_from_cube(
231239
bader_charges = read_bader_acf(Path(work_path) / 'ACF.dat')
232240
cube_data = read_gaussian_cube(merged_cube_file)
233241
net_bader_charges = (np.array(cube_data["chg"]) - np.array(bader_charges)).tolist()
242+
atom_labels = [Element.from_Z(i).symbol for i in cube_data['atomz']]
234243

235244
bader_charge_json = Path("./bader_charge_results.json").absolute()
236245
with open(bader_charge_json, "w") as fout:
237246
json.dump({
238247
"number_of_electrons": bader_charges,
239248
"core_charge": cube_data["chg"],
240-
"net_charges": net_bader_charges
249+
"net_bader_charges": net_bader_charges,
250+
"atom_labels": atom_labels
241251
}, fout)
242252

243253
return {
254+
"atom_labels": atom_labels,
255+
"net_bader_charges": net_bader_charges,
244256
"number_of_electrons": bader_charges,
245257
"core_charge": cube_data["chg"],
246-
"net_charges": net_bader_charges,
247258
"work_path": Path(work_path).absolute(),
248259
"cube_file": Path(merged_cube_file).absolute(),
249260
"charge_results_json": bader_charge_json.absolute(),
@@ -264,11 +275,12 @@ def abacus_badercharge_run(
264275
Returns:
265276
dict: A dictionary containing:
266277
- net_bader_charges: List of net Bader charge for each atom. Core charge is included.
267-
- bader_charges: List of Bader charge for each atom. The value represents the number of valence electrons for each atom, and core charge is not included.
268-
- atom_core_charges: List of core charge for each atom.
278+
- number_of_electrons: List of number of electrons around each atom. Core charge is not included.
279+
- core_charges: List of core charge for each atom.
269280
- atom_labels: Labels of atoms in the structure.
270281
- abacus_workpath: Absolute path to the ABACUS work directory.
271282
- badercharge_run_workpath: Absolute path to the Bader analysis work directory.
283+
- bader_result_csv: Absolute path to the CSV file containing detailed Bader charge results
272284
"""
273285
try:
274286
is_valid, msg = check_abacus_inputs(abacus_inputs_dir)
@@ -289,13 +301,23 @@ def abacus_badercharge_run(
289301
# Postprocess the charge density to obtain Bader charges
290302
bader_results = calculate_bader_charge_from_cube(fcube)
291303

304+
# Write necessary results to csv file
305+
bader_result_csv = Path("./bader_charge_results.csv").absolute()
306+
307+
with open(bader_result_csv, "w") as fout:
308+
writer = csv.writer(fout)
309+
writer.writerow(['atom_label', 'net_bader_charge'])
310+
for label, charge in zip(bader_results["atom_labels"], bader_results["net_bader_charges"]):
311+
writer.writerow([label, f"{charge: .6f}"])
312+
292313
return {
293-
"net_charges": bader_results["net_charges"],
314+
"net_bader_charges": bader_results["net_bader_charges"],
294315
"number_of_electrons": bader_results["number_of_electrons"],
295316
"core_charges": bader_results["core_charge"],
296-
"atom_labels": atom_labels,
317+
"atom_labels": atom_labels, # Use atom labels from ABACUS STRU file, not from cube file
297318
"abacus_workpath": Path(abacus_jobpath).absolute(),
298319
"badercharge_run_workpath": Path(bader_results["work_path"]).absolute(),
320+
"bader_result_csv": Path(bader_result_csv).absolute()
299321
}
300322
except Exception as e:
301323
return {"message": f"Calculating Bader charge failed: {e}"}

tests/integrate_test/data/ref_results.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@
2727
"test_abacus_badercharge_run_nspin1":
2828
{
2929
"result": {
30-
"net_charges": [0.930, -0.930],
30+
"net_bader_charges": [0.930, -0.930],
3131
"atom_labels": ["Na", "Cl"]
3232
}
3333
},
3434
"test_abacus_badercharge_run_nspin2":
3535
{
3636
"result": {
37-
"net_charges": [0.930, -0.930],
37+
"net_bader_charges": [0.930, -0.930],
3838
"atom_labels": ["Na", "Cl"]
3939
}
4040
},

tests/integrate_test/test_bader.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def test_abacus_badercharge_run_nspin1(self):
4343
badercharge_run_workpath = outputs['badercharge_run_workpath']
4444
self.assertIsInstance(abacus_workpath, get_path_type())
4545
self.assertIsInstance(badercharge_run_workpath, get_path_type())
46-
for act, ref in zip(outputs['net_charges'], ref_results['net_charges']):
46+
for act, ref in zip(outputs['net_bader_charges'], ref_results['net_bader_charges']):
4747
self.assertAlmostEqual(act, ref, places=3)
4848
for act, ref in zip(outputs['atom_labels'], ref_results['atom_labels']):
4949
self.assertEqual(act, ref)
@@ -68,7 +68,7 @@ def test_abacus_badercharge_run_nspin2(self):
6868
badercharge_run_workpath = outputs['badercharge_run_workpath']
6969
self.assertIsInstance(abacus_workpath, get_path_type())
7070
self.assertIsInstance(badercharge_run_workpath, get_path_type())
71-
for act, ref in zip(outputs['net_charges'], ref_results['net_charges']):
71+
for act, ref in zip(outputs['net_bader_charges'], ref_results['net_bader_charges']):
7272
self.assertAlmostEqual(act, ref, places=3)
7373
for act, ref in zip(outputs['atom_labels'], ref_results['atom_labels']):
7474
self.assertEqual(act, ref)

tests/integrate_test/test_tool_wrapper.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,10 +220,11 @@ def test_run_abacus_calculation_bader_charge(self):
220220
fixed_axes=None)
221221
print(outputs)
222222

223+
self.assertIsInstance(outputs['bader_result_csv'], get_path_type())
223224
for act, ref in zip(outputs['net_bader_charges'], ref_results['net_bader_charges']):
224225
self.assertAlmostEqual(act, ref, delta=1e-3)
225226
for act, ref in zip(outputs['atom_labels'], ref_results['atom_labels']):
226-
self.assertEqual(act, ref)
227+
self.assertEqual(act, ref)
227228

228229
def test_run_abacus_calculation_band(self):
229230
"""

0 commit comments

Comments
 (0)