Skip to content

Commit 58db27d

Browse files
authored
Merge pull request deepmodeling#59 from ahxbcn/work_function
Work function
2 parents 19b28f0 + 3be7761 commit 58db27d

6 files changed

Lines changed: 125 additions & 9 deletions

File tree

src/abacusagent/constant.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
RY_TO_EV = 13.60569253
2+
THZ_TO_K = 47.9924

src/abacusagent/modules/submodules/cube.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
from abacustest.lib_prepare.abacus import AbacusStru, ReadInput, WriteInput
1313
from abacustest.lib_model.comm import check_abacus_inputs
1414

15-
from abacusagent.modules.util.comm import run_abacus, generate_work_path, link_abacusjob
16-
from abacusagent.modules.util.cube_manipulator import read_gaussian_cube, axpy, write_gaussian_cube
15+
from abacusagent.modules.util.comm import run_abacus, generate_work_path, link_abacusjob, collect_metrics
16+
from abacusagent.modules.util.cube_manipulator import read_gaussian_cube, axpy, write_gaussian_cube, profile1d
1717

1818
def abacus_cal_elf(abacus_inputs_dir: Path):
1919
"""

src/abacusagent/modules/submodules/phonon.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@
1010
from abacustest.lib_model.comm import check_abacus_inputs
1111

1212
from abacusagent.init_mcp import mcp
13+
from abacusagent.constant import THZ_TO_K
1314
from abacusagent.modules.util.comm import run_abacus, generate_work_path, link_abacusjob, collect_metrics
1415

15-
THz_TO_K = 47.9924
16-
1716

1817
def abacus_phonon_dispersion(
1918
abacus_inputs_dir: Path,
@@ -147,7 +146,7 @@ def abacus_phonon_dispersion(
147146
"free_energy": float(thermal['free_energy'][0]),
148147
"heat_capacity": float(thermal['heat_capacity'][0]),
149148
"max_frequency_THz": float(np.max(freqs)),
150-
"max_frequency_K": float(np.max(freqs) * THz_TO_K),
149+
"max_frequency_K": float(np.max(freqs) * THZ_TO_K),
151150
}
152151
except Exception as e:
153152
return {"message": f"Calculating phonon spectrum failed: {e}"}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import os
2+
from pathlib import Path
3+
from typing import Literal, Optional, Dict, Any
4+
5+
from abacustest.lib_prepare.abacus import ReadInput, WriteInput
6+
from abacustest.lib_model.comm import check_abacus_inputs
7+
8+
from abacusagent.constant import RY_TO_EV
9+
from abacusagent.modules.util.comm import run_abacus, generate_work_path, link_abacusjob, collect_metrics
10+
from abacusagent.modules.util.cube_manipulator import read_gaussian_cube, profile1d
11+
12+
def plot_averaged_elecstat_pot(
13+
averaged_elecstat_data,
14+
work_path: Path,
15+
axis: Literal['x', 'y', 'z'] = 'z',
16+
plot_filename: Optional[str] = "elecstat_pot_profile.png"
17+
) -> Dict[str, Any]:
18+
import matplotlib.pyplot as plt
19+
plt.plot(averaged_elecstat_data['data'][:, 0], averaged_elecstat_data['data'][:, 1], label='Electrostatic Potential')
20+
plt.xlim(0, 1)
21+
plt.xlabel("Fractional Coordinate along " + axis)
22+
plt.ylabel("Electrostatic Potential (eV)")
23+
plot_path = os.path.join(work_path, plot_filename)
24+
plt.savefig(plot_path, dpi=300)
25+
26+
return plot_path
27+
28+
def abacus_cal_work_function(
29+
abacus_inputs_dir: Path,
30+
vacuum_direction: Literal['x', 'y', 'z'] = 'z',
31+
) -> Dict[str, Any]:
32+
"""
33+
Calculate the electrostatic potential and work function using ABACUS.
34+
35+
Args:
36+
abacus_inputs_dir (Path): Path to the ABACUS input files, which contains the INPUT, STRU, KPT, and pseudopotential or orbital files.
37+
vacuum_direction (Literal['x', 'y', 'z']): The direction of the vacuum.
38+
39+
Returns:
40+
A dictionary containing:
41+
- elecstat_pot_work_function_work_path (Path): Path to the ABACUS job directory calculating electrostatic potential and work function.
42+
- elecstat_pot_file (Path): Path to the cube file containing the electrostatic potential.
43+
- averaged_elecstat_pot_plot (Path): Path to the plot of the averaged electrostatic potential.
44+
- work_function (float): The calculated work function in eV.
45+
"""
46+
try:
47+
is_valid, msg = check_abacus_inputs(abacus_inputs_dir)
48+
if not is_valid:
49+
raise RuntimeError(f"Invalid ABACUS input files: {msg}")
50+
51+
work_path = Path(generate_work_path()).absolute()
52+
link_abacusjob(src=abacus_inputs_dir,dst=work_path,copy_files=["INPUT", "STRU"], exclude_directories=True)
53+
input_params = ReadInput(os.path.join(work_path, 'INPUT'))
54+
if input_params.get('nspin', 1) not in [1, 2]:
55+
raise ValueError('Only non spin-polarized and collinear spin-polarized calculation are supported for calculating electrostatic potential and work function')
56+
57+
input_params['calculation'] = 'scf'
58+
input_params['out_pot'] = 2
59+
WriteInput(input_params, os.path.join(work_path, 'INPUT'))
60+
61+
run_abacus(work_path)
62+
63+
metrics = collect_metrics(work_path, metrics_names=['normal_end', 'converge', 'efermi'])
64+
if metrics['normal_end'] is not True or metrics['converge'] is not True:
65+
raise RuntimeError('ABACUS calculation didn\'t end normally or didn\'t reached SCF convergence')
66+
67+
pot_file = os.path.join(work_path, f"OUT.{input_params.get('suffix', 'ABACUS')}/ElecStaticPot.cube")
68+
pot = read_gaussian_cube(pot_file)
69+
70+
profile_result = profile1d(pot, axis=vacuum_direction, average=True)
71+
profile_result['data'][:, 1] *= RY_TO_EV # Convert from Rydberg to eV
72+
v_vacuum = max(profile_result['data'][:, 1])
73+
work_function = v_vacuum - metrics['efermi']
74+
75+
# Plot the averaged electrostatic potential
76+
plot_path = plot_averaged_elecstat_pot(profile_result, work_path, axis=vacuum_direction)
77+
78+
return {'elecstat_pot_work_function_work_path': Path(work_path).absolute(),
79+
'elecstat_pot_file': Path(pot_file).absolute(),
80+
'averaged_elecstat_pot_plot': Path(plot_path).absolute(),
81+
'work_function': work_function}
82+
except Exception as e:
83+
return {'message': f"Calculating electrostatic potential and work function failed: {e}"}

src/abacusagent/modules/util/cube_manipulator.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def axpy(x, y=None, alpha=1.0, beta=1.0):
121121
else:
122122
return alpha * x + beta * y
123123

124-
def profile1d(data: dict, axis: str):
124+
def profile1d(data: dict, axis: str, average: bool = False):
125125
"""integrate the 3D cube data to 2D plane.
126126
Args:
127127
data (dict): the dictionary containing the cube data.
@@ -133,12 +133,18 @@ def profile1d(data: dict, axis: str):
133133
import numpy as np
134134

135135
mat3d = data["data"].reshape(int(data["nx"]), int(data["ny"]), int(data["nz"]))
136+
137+
func = np.mean if average else np.sum
136138
if axis == "x":
137-
val = np.sum(mat3d, axis=2).sum(axis=1)
139+
val = func(mat3d, axis=2)
140+
val = func(val, axis=1)
138141
elif axis == "y":
139-
val = np.sum(mat3d, axis=0).sum(axis=1)
142+
val = func(mat3d, axis=0)
143+
val = func(val, axis=1)
140144
elif axis == "z":
141-
val = np.sum(mat3d, axis=0).sum(axis=0)
145+
val = func(mat3d, axis=0)
146+
val = func(val, axis=0)
147+
142148
# remember to write the axis data
143149
ngrid = data["nx"] if axis == "x" else data["ny"] if axis == "y" else data["nz"]
144150
var = np.linspace(0, 1, int(ngrid))
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from pathlib import Path
2+
from typing import List, Dict, Any, Literal
3+
4+
from abacusagent.init_mcp import mcp
5+
from abacusagent.modules.submodules.work_function import abacus_cal_work_function as _abacus_cal_work_function
6+
7+
@mcp.tool()
8+
def abacus_cal_work_function(
9+
abacus_inputs_dir: Path,
10+
vacuum_direction: Literal['x', 'y', 'z'] = 'z',
11+
) -> Dict[str, Any]:
12+
"""
13+
Calculate the electrostatic potential and work function using ABACUS.
14+
15+
Args:
16+
abacus_inputs_dir (Path): Path to the ABACUS input files, which contains the INPUT, STRU, KPT, and pseudopotential or orbital files.
17+
vacuum_direction (Literal['x', 'y', 'z']): The direction of the vacuum.
18+
19+
Returns:
20+
A dictionary containing:
21+
- elecstat_pot_work_function_work_path (Path): Path to the ABACUS job directory calculating electrostatic potential and work function.
22+
- elecstat_pot_file (Path): Path to the cube file containing the electrostatic potential.
23+
- averaged_elecstat_pot_plot (Path): Path to the plot of the averaged electrostatic potential.
24+
- work_function (float): The calculated work function in eV.
25+
"""
26+
return _abacus_cal_work_function(abacus_inputs_dir, vacuum_direction)

0 commit comments

Comments
 (0)