Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ testpaths = ["tests"]
addopts = "-v"

[project.optional-dependencies]
dev = ["mcp>=1.9.0",
"abacustest>=0.4.37",
dev = ["mcp>=1.10.0",
"abacustest>=0.4.49",
"science-agent-sdk"
]
7 changes: 5 additions & 2 deletions src/abacusagent/modules/bader.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ def abacus_badercharge_run(

Returns:
dict: A dictionary containing:
- bader_charges: List of Bader charge for each atom.
- net_bader_charges: List of net Bader charge for each atom. Core charge is included.
- number_of_electrons: List of number of electrons around each atom. Core charge is not included.
- core_charges: List of core charge for each atom.
- atom_labels: Labels of atoms in the structure.
- abacus_workpath: Absolute path to the ABACUS work directory.
- badercharge_run_workpath: Absolute path to the Bader analysis work directory.
- bader_result_csv: Absolute path to the CSV file containing detailed Bader charge results
"""
return _abacus_badercharge_run(abacus_inputs_dir)

Expand All @@ -40,7 +43,7 @@ def calculate_bader_charge_from_cube(

Returns:
dict: A dictionary containing:
- net_charges: List of net charge for each atom. Core charge is included.
- net_bader_charges: List of net charge for each atom. Core charge is included.
- number_of_electrons: List of number of electrons around each atom. Core charge is not included.
- core_charges: List of core charge for each atom.
- work_path: Absolute path to the work directory.
Expand Down
40 changes: 31 additions & 9 deletions src/abacusagent/modules/submodules/bader.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
import glob
import shutil
import json
import csv
import unittest
from typing import List, Dict, Optional, Any

from pathlib import Path

import numpy as np
import unittest
from pymatgen.core.periodic_table import Element

from abacustest.lib_prepare.abacus import ReadInput, WriteInput, AbacusStru
from abacustest.lib_model.comm import check_abacus_inputs
Expand Down Expand Up @@ -204,7 +205,14 @@ def calculate_bader_charge_from_cube(
fcube (list|str): List of cube files or a single cube file path.

Returns:
list: A list of Bader charges.
dict: A dictionary containing:
- atom_labels: List of atom labels.
- net_bader_charges: List of net charge for each atom. Core charge is included.
- number_of_electrons: List of number of electrons around each atom. Core charge is not included.
- core_charge: List of core charge for each atom.
- work_path: Absolute path to the work directory.
- cube_file: Absolute path to the cube file used in this tool.
- charge_results_json: Absolute path to the JSON file containing detailed Bader charge results
"""
if not isinstance(fcube, (list, tuple)):
fcube = [fcube]
Expand All @@ -231,19 +239,22 @@ def calculate_bader_charge_from_cube(
bader_charges = read_bader_acf(Path(work_path) / 'ACF.dat')
cube_data = read_gaussian_cube(merged_cube_file)
net_bader_charges = (np.array(cube_data["chg"]) - np.array(bader_charges)).tolist()
atom_labels = [Element.from_Z(i).symbol for i in cube_data['atomz']]

bader_charge_json = Path("./bader_charge_results.json").absolute()
with open(bader_charge_json, "w") as fout:
json.dump({
"number_of_electrons": bader_charges,
"core_charge": cube_data["chg"],
"net_charges": net_bader_charges
"net_bader_charges": net_bader_charges,
"atom_labels": atom_labels
}, fout)

return {
"atom_labels": atom_labels,
"net_bader_charges": net_bader_charges,
"number_of_electrons": bader_charges,
"core_charge": cube_data["chg"],
"net_charges": net_bader_charges,
"work_path": Path(work_path).absolute(),
"cube_file": Path(merged_cube_file).absolute(),
"charge_results_json": bader_charge_json.absolute(),
Expand All @@ -264,11 +275,12 @@ def abacus_badercharge_run(
Returns:
dict: A dictionary containing:
- net_bader_charges: List of net Bader charge for each atom. Core charge is included.
- 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.
- atom_core_charges: List of core charge for each atom.
- number_of_electrons: List of number of electrons around each atom. Core charge is not included.
- core_charges: List of core charge for each atom.
- atom_labels: Labels of atoms in the structure.
- abacus_workpath: Absolute path to the ABACUS work directory.
- badercharge_run_workpath: Absolute path to the Bader analysis work directory.
- bader_result_csv: Absolute path to the CSV file containing detailed Bader charge results
"""
try:
is_valid, msg = check_abacus_inputs(abacus_inputs_dir)
Expand All @@ -289,13 +301,23 @@ def abacus_badercharge_run(
# Postprocess the charge density to obtain Bader charges
bader_results = calculate_bader_charge_from_cube(fcube)

# Write necessary results to csv file
bader_result_csv = Path("./bader_charge_results.csv").absolute()

with open(bader_result_csv, "w") as fout:
writer = csv.writer(fout)
writer.writerow(['atom_label', 'net_bader_charge'])
for label, charge in zip(bader_results["atom_labels"], bader_results["net_bader_charges"]):
writer.writerow([label, f"{charge: .6f}"])

return {
"net_charges": bader_results["net_charges"],
"net_bader_charges": bader_results["net_bader_charges"],
"number_of_electrons": bader_results["number_of_electrons"],
"core_charges": bader_results["core_charge"],
"atom_labels": atom_labels,
"atom_labels": atom_labels, # Use atom labels from ABACUS STRU file, not from cube file
"abacus_workpath": Path(abacus_jobpath).absolute(),
"badercharge_run_workpath": Path(bader_results["work_path"]).absolute(),
"bader_result_csv": Path(bader_result_csv).absolute()
}
except Exception as e:
return {"message": f"Calculating Bader charge failed: {e}"}
Expand Down
4 changes: 2 additions & 2 deletions tests/integrate_test/data/ref_results.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@
"test_abacus_badercharge_run_nspin1":
{
"result": {
"net_charges": [0.930, -0.930],
"net_bader_charges": [0.930, -0.930],
"atom_labels": ["Na", "Cl"]
}
},
"test_abacus_badercharge_run_nspin2":
{
"result": {
"net_charges": [0.930, -0.930],
"net_bader_charges": [0.930, -0.930],
"atom_labels": ["Na", "Cl"]
}
},
Expand Down
4 changes: 2 additions & 2 deletions tests/integrate_test/test_bader.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def test_abacus_badercharge_run_nspin1(self):
badercharge_run_workpath = outputs['badercharge_run_workpath']
self.assertIsInstance(abacus_workpath, get_path_type())
self.assertIsInstance(badercharge_run_workpath, get_path_type())
for act, ref in zip(outputs['net_charges'], ref_results['net_charges']):
for act, ref in zip(outputs['net_bader_charges'], ref_results['net_bader_charges']):
self.assertAlmostEqual(act, ref, places=3)
for act, ref in zip(outputs['atom_labels'], ref_results['atom_labels']):
self.assertEqual(act, ref)
Expand All @@ -68,7 +68,7 @@ def test_abacus_badercharge_run_nspin2(self):
badercharge_run_workpath = outputs['badercharge_run_workpath']
self.assertIsInstance(abacus_workpath, get_path_type())
self.assertIsInstance(badercharge_run_workpath, get_path_type())
for act, ref in zip(outputs['net_charges'], ref_results['net_charges']):
for act, ref in zip(outputs['net_bader_charges'], ref_results['net_bader_charges']):
self.assertAlmostEqual(act, ref, places=3)
for act, ref in zip(outputs['atom_labels'], ref_results['atom_labels']):
self.assertEqual(act, ref)
3 changes: 2 additions & 1 deletion tests/integrate_test/test_tool_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,11 @@ def test_run_abacus_calculation_bader_charge(self):
fixed_axes=None)
print(outputs)

self.assertIsInstance(outputs['bader_result_csv'], get_path_type())
for act, ref in zip(outputs['net_bader_charges'], ref_results['net_bader_charges']):
self.assertAlmostEqual(act, ref, delta=1e-3)
for act, ref in zip(outputs['atom_labels'], ref_results['atom_labels']):
self.assertEqual(act, ref)
self.assertEqual(act, ref)

def test_run_abacus_calculation_band(self):
"""
Expand Down