agent-materials-science 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_materials_science/__init__.py +26 -0
- agent_materials_science/__main__.py +12 -0
- agent_materials_science/agent.py +384 -0
- agent_materials_science/cli.py +377 -0
- agent_materials_science/config.py +318 -0
- agent_materials_science/core/__init__.py +15 -0
- agent_materials_science/core/adsorption.py +752 -0
- agent_materials_science/core/surface.py +383 -0
- agent_materials_science/core/workflow.py +662 -0
- agent_materials_science/py.typed +0 -0
- agent_materials_science/tools/__init__.py +29 -0
- agent_materials_science/tools/ase_tools.py +347 -0
- agent_materials_science/tools/converters.py +365 -0
- agent_materials_science/tools/fairchem_calc.py +536 -0
- agent_materials_science/tools/materials_project.py +275 -0
- agent_materials_science-0.2.1.dist-info/METADATA +266 -0
- agent_materials_science-0.2.1.dist-info/RECORD +21 -0
- agent_materials_science-0.2.1.dist-info/WHEEL +5 -0
- agent_materials_science-0.2.1.dist-info/entry_points.txt +2 -0
- agent_materials_science-0.2.1.dist-info/licenses/LICENSE +21 -0
- agent_materials_science-0.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Materials Science Adsorption Agent
|
|
3
|
+
|
|
4
|
+
A Python agent for automated materials science workflows, focusing on
|
|
5
|
+
surface adsorption analysis using Materials Project data, ASE/pymatgen
|
|
6
|
+
tools, and FairChem UMA machine learning potentials.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .config import AgentConfig
|
|
10
|
+
from .agent import (
|
|
11
|
+
MaterialsScienceAgent,
|
|
12
|
+
AgentResult,
|
|
13
|
+
InteractiveAgent,
|
|
14
|
+
run_analysis,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__version__ = "0.2.1"
|
|
18
|
+
__author__ = "Materials Science Agent"
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"MaterialsScienceAgent",
|
|
22
|
+
"AgentConfig",
|
|
23
|
+
"AgentResult",
|
|
24
|
+
"InteractiveAgent",
|
|
25
|
+
"run_analysis",
|
|
26
|
+
]
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Materials Science Adsorption Agent
|
|
3
|
+
|
|
4
|
+
This module provides the main agent class that orchestrates the complete
|
|
5
|
+
workflow for surface adsorption analysis.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Optional, Tuple, List, Dict, Any
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
from .config import AgentConfig, get_available_adsorbates, get_available_models
|
|
12
|
+
from .core.workflow import AdsorptionWorkflow, WorkflowResult
|
|
13
|
+
from .core.surface import SurfaceBuilder, TerminationInfo
|
|
14
|
+
from .core.adsorption import AdsorptionSiteFinder, AdsorptionSite
|
|
15
|
+
from .tools.materials_project import MaterialsProjectTool
|
|
16
|
+
from .tools.ase_tools import structure_to_atoms, get_slab_info
|
|
17
|
+
from .tools.converters import create_adsorbate, list_available_adsorbates
|
|
18
|
+
from .tools.fairchem_calc import FAIRCHEM_AVAILABLE, check_fairchem_installation
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Re-export WorkflowResult as AgentResult for cleaner API
|
|
22
|
+
AgentResult = WorkflowResult
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MaterialsScienceAgent:
|
|
26
|
+
"""
|
|
27
|
+
Main agent for materials science adsorption analysis.
|
|
28
|
+
|
|
29
|
+
This agent provides a complete workflow for:
|
|
30
|
+
1. Fetching materials from Materials Project
|
|
31
|
+
2. Creating surface slabs with specific Miller indices and terminations
|
|
32
|
+
3. Identifying adsorption sites on surfaces
|
|
33
|
+
4. Calculating adsorption energies using ML potentials (FairChem)
|
|
34
|
+
5. Ranking sites and generating reports
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
```python
|
|
38
|
+
from agent_materials_science import MaterialsScienceAgent, AgentConfig
|
|
39
|
+
|
|
40
|
+
config = AgentConfig(
|
|
41
|
+
material="Si",
|
|
42
|
+
miller_indices=(1, 1, 1),
|
|
43
|
+
adsorbate="H",
|
|
44
|
+
calculate_energies=True,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
agent = MaterialsScienceAgent(config)
|
|
48
|
+
result = agent.run()
|
|
49
|
+
|
|
50
|
+
print(result.summary())
|
|
51
|
+
```
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, config: AgentConfig):
|
|
55
|
+
"""
|
|
56
|
+
Initialize the agent.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
config: Agent configuration specifying material, surface,
|
|
60
|
+
adsorbate, and calculation parameters.
|
|
61
|
+
"""
|
|
62
|
+
self.config = config
|
|
63
|
+
self._workflow = None
|
|
64
|
+
self._result = None
|
|
65
|
+
|
|
66
|
+
def run(self) -> AgentResult:
|
|
67
|
+
"""
|
|
68
|
+
Execute the complete adsorption analysis workflow.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
AgentResult containing all analysis data, including:
|
|
72
|
+
- Material and slab information
|
|
73
|
+
- Identified adsorption sites
|
|
74
|
+
- Calculated energies (if requested)
|
|
75
|
+
- Output file paths
|
|
76
|
+
"""
|
|
77
|
+
self._workflow = AdsorptionWorkflow(self.config)
|
|
78
|
+
self._result = self._workflow.run()
|
|
79
|
+
return self._result
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def result(self) -> Optional[AgentResult]:
|
|
83
|
+
"""Get the last workflow result, if available."""
|
|
84
|
+
return self._result
|
|
85
|
+
|
|
86
|
+
def get_summary(self) -> str:
|
|
87
|
+
"""
|
|
88
|
+
Get a human-readable summary of the results.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Formatted summary string
|
|
92
|
+
"""
|
|
93
|
+
if self._result is None:
|
|
94
|
+
return "No results available. Run the agent first."
|
|
95
|
+
return self._result.summary()
|
|
96
|
+
|
|
97
|
+
def get_best_site(self) -> Optional[Dict[str, Any]]:
|
|
98
|
+
"""
|
|
99
|
+
Get the best adsorption site from the last run.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Dictionary with site information, or None
|
|
103
|
+
"""
|
|
104
|
+
if self._result is None:
|
|
105
|
+
return None
|
|
106
|
+
return self._result.best_site
|
|
107
|
+
|
|
108
|
+
def get_output_files(self) -> List[str]:
|
|
109
|
+
"""
|
|
110
|
+
Get list of output files from the last run.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
List of file paths
|
|
114
|
+
"""
|
|
115
|
+
if self._result is None:
|
|
116
|
+
return []
|
|
117
|
+
return self._result.output_files
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def check_installation() -> Dict[str, Any]:
|
|
121
|
+
"""
|
|
122
|
+
Check the installation status of required dependencies.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Dictionary with installation status
|
|
126
|
+
"""
|
|
127
|
+
status = {
|
|
128
|
+
"fairchem": check_fairchem_installation(),
|
|
129
|
+
"available_adsorbates": get_available_adsorbates(),
|
|
130
|
+
"available_models": get_available_models(),
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Check Materials Project API
|
|
134
|
+
try:
|
|
135
|
+
from mp_api.client import MPRester
|
|
136
|
+
status["mp_api"] = True
|
|
137
|
+
except ImportError:
|
|
138
|
+
status["mp_api"] = False
|
|
139
|
+
|
|
140
|
+
# Check pymatgen
|
|
141
|
+
try:
|
|
142
|
+
from pymatgen.core import Structure
|
|
143
|
+
status["pymatgen"] = True
|
|
144
|
+
except ImportError:
|
|
145
|
+
status["pymatgen"] = False
|
|
146
|
+
|
|
147
|
+
# Check ASE
|
|
148
|
+
try:
|
|
149
|
+
from ase.atoms import Atoms
|
|
150
|
+
status["ase"] = True
|
|
151
|
+
except ImportError:
|
|
152
|
+
status["ase"] = False
|
|
153
|
+
|
|
154
|
+
return status
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def list_adsorbates() -> List[str]:
|
|
158
|
+
"""
|
|
159
|
+
List available adsorbate species.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
List of adsorbate names
|
|
163
|
+
"""
|
|
164
|
+
return list_available_adsorbates()
|
|
165
|
+
|
|
166
|
+
@staticmethod
|
|
167
|
+
def list_models() -> List[str]:
|
|
168
|
+
"""
|
|
169
|
+
List available FairChem models.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
List of model names
|
|
173
|
+
"""
|
|
174
|
+
return get_available_models()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class InteractiveAgent:
|
|
178
|
+
"""
|
|
179
|
+
Interactive version of the agent with step-by-step control.
|
|
180
|
+
|
|
181
|
+
This class allows users to execute individual steps of the workflow
|
|
182
|
+
and inspect intermediate results.
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
def __init__(self, api_key: Optional[str] = None):
|
|
186
|
+
"""
|
|
187
|
+
Initialize the interactive agent.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
api_key: Materials Project API key (optional, uses env var if not provided)
|
|
191
|
+
"""
|
|
192
|
+
self.mp_tool = MaterialsProjectTool(api_key=api_key)
|
|
193
|
+
self._bulk_structure = None
|
|
194
|
+
self._material_id = None
|
|
195
|
+
self._slab = None
|
|
196
|
+
self._terminations = None
|
|
197
|
+
self._sites = None
|
|
198
|
+
self._adsorbate = None
|
|
199
|
+
|
|
200
|
+
def fetch_material(self, material: str) -> Dict[str, Any]:
|
|
201
|
+
"""
|
|
202
|
+
Fetch a material from Materials Project.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
material: Formula (e.g., 'Si') or MP ID (e.g., 'mp-149')
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Material properties dictionary
|
|
209
|
+
"""
|
|
210
|
+
if material.startswith("mp-"):
|
|
211
|
+
self._bulk_structure = self.mp_tool.get_structure_by_mp_id(material)
|
|
212
|
+
self._material_id = material
|
|
213
|
+
else:
|
|
214
|
+
self._bulk_structure, self._material_id = self.mp_tool.get_structure_by_formula(material)
|
|
215
|
+
|
|
216
|
+
props = self.mp_tool.get_material_properties(self._material_id)
|
|
217
|
+
return props
|
|
218
|
+
|
|
219
|
+
def list_terminations(
|
|
220
|
+
self,
|
|
221
|
+
miller: Tuple[int, int, int] = (1, 1, 1),
|
|
222
|
+
min_slab_size: float = 10.0,
|
|
223
|
+
vacuum: float = 15.0,
|
|
224
|
+
) -> List[Dict[str, Any]]:
|
|
225
|
+
"""
|
|
226
|
+
List available surface terminations.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
miller: Miller indices
|
|
230
|
+
min_slab_size: Minimum slab thickness (Å)
|
|
231
|
+
vacuum: Vacuum thickness (Å)
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
List of termination info dictionaries
|
|
235
|
+
"""
|
|
236
|
+
if self._bulk_structure is None:
|
|
237
|
+
raise RuntimeError("Fetch a material first with fetch_material()")
|
|
238
|
+
|
|
239
|
+
builder = SurfaceBuilder(self._bulk_structure, miller)
|
|
240
|
+
self._terminations = builder.get_available_terminations(min_slab_size, vacuum)
|
|
241
|
+
|
|
242
|
+
return [t.to_dict() for t in self._terminations]
|
|
243
|
+
|
|
244
|
+
def create_slab(
|
|
245
|
+
self,
|
|
246
|
+
miller: Tuple[int, int, int] = (1, 1, 1),
|
|
247
|
+
termination: int = 0,
|
|
248
|
+
min_slab_size: float = 10.0,
|
|
249
|
+
vacuum: float = 15.0,
|
|
250
|
+
supercell: Tuple[int, int] = (1, 1),
|
|
251
|
+
) -> Dict[str, Any]:
|
|
252
|
+
"""
|
|
253
|
+
Create a surface slab.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
miller: Miller indices
|
|
257
|
+
termination: Termination index (use list_terminations to see options)
|
|
258
|
+
min_slab_size: Minimum slab thickness (Å)
|
|
259
|
+
vacuum: Vacuum thickness (Å)
|
|
260
|
+
supercell: In-plane supercell (nx, ny)
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Slab info dictionary
|
|
264
|
+
"""
|
|
265
|
+
if self._bulk_structure is None:
|
|
266
|
+
raise RuntimeError("Fetch a material first with fetch_material()")
|
|
267
|
+
|
|
268
|
+
builder = SurfaceBuilder(self._bulk_structure, miller)
|
|
269
|
+
self._slab = builder.build_slab(
|
|
270
|
+
termination=termination,
|
|
271
|
+
min_slab_size=min_slab_size,
|
|
272
|
+
min_vacuum_size=vacuum,
|
|
273
|
+
supercell=supercell,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return get_slab_info(self._slab)
|
|
277
|
+
|
|
278
|
+
def find_sites(
|
|
279
|
+
self,
|
|
280
|
+
height_offset: float = 2.0,
|
|
281
|
+
site_types: Optional[List[str]] = None,
|
|
282
|
+
) -> List[Dict[str, Any]]:
|
|
283
|
+
"""
|
|
284
|
+
Find adsorption sites on the slab.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
height_offset: Height above surface for adsorbate (Å)
|
|
288
|
+
site_types: Types to include (default: all)
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
List of site dictionaries
|
|
292
|
+
"""
|
|
293
|
+
if self._slab is None:
|
|
294
|
+
raise RuntimeError("Create a slab first with create_slab()")
|
|
295
|
+
|
|
296
|
+
finder = AdsorptionSiteFinder(self._slab, height_offset=height_offset)
|
|
297
|
+
sites = finder.find_all_sites()
|
|
298
|
+
|
|
299
|
+
if site_types:
|
|
300
|
+
sites = finder.filter_by_type(sites, site_types)
|
|
301
|
+
|
|
302
|
+
sites = finder.remove_duplicates(sites)
|
|
303
|
+
self._sites = sites
|
|
304
|
+
|
|
305
|
+
return [s.to_dict() for s in sites]
|
|
306
|
+
|
|
307
|
+
def select_adsorbate(self, species: str) -> Dict[str, Any]:
|
|
308
|
+
"""
|
|
309
|
+
Select an adsorbate species.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
species: Adsorbate name (e.g., 'H', 'CO', 'OH')
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
Adsorbate info dictionary
|
|
316
|
+
"""
|
|
317
|
+
self._adsorbate = create_adsorbate(species, height=0)
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
"species": species,
|
|
321
|
+
"n_atoms": len(self._adsorbate),
|
|
322
|
+
"elements": list(set(self._adsorbate.get_chemical_symbols())),
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
def get_slab(self):
|
|
326
|
+
"""Get the current slab (ASE Atoms object)."""
|
|
327
|
+
return self._slab
|
|
328
|
+
|
|
329
|
+
def get_sites(self) -> List[AdsorptionSite]:
|
|
330
|
+
"""Get the current sites (AdsorptionSite objects)."""
|
|
331
|
+
return self._sites or []
|
|
332
|
+
|
|
333
|
+
def get_adsorbate(self):
|
|
334
|
+
"""Get the current adsorbate (ASE Atoms object)."""
|
|
335
|
+
return self._adsorbate
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def run_analysis(
|
|
339
|
+
material: str,
|
|
340
|
+
miller: Tuple[int, int, int] = (1, 1, 1),
|
|
341
|
+
adsorbate: str = "H",
|
|
342
|
+
output_dir: str = "outputs",
|
|
343
|
+
calculate_energies: bool = False,
|
|
344
|
+
**kwargs
|
|
345
|
+
) -> AgentResult:
|
|
346
|
+
"""
|
|
347
|
+
Convenience function to run a complete analysis.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
material: Material formula or MP ID
|
|
351
|
+
miller: Miller indices
|
|
352
|
+
adsorbate: Adsorbate species
|
|
353
|
+
output_dir: Output directory
|
|
354
|
+
calculate_energies: Whether to calculate adsorption energies
|
|
355
|
+
**kwargs: Additional configuration options
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
AgentResult with analysis data
|
|
359
|
+
|
|
360
|
+
Example:
|
|
361
|
+
```python
|
|
362
|
+
from agent_materials_science import run_analysis
|
|
363
|
+
|
|
364
|
+
result = run_analysis(
|
|
365
|
+
material="Pt",
|
|
366
|
+
miller=(1, 1, 1),
|
|
367
|
+
adsorbate="CO",
|
|
368
|
+
calculate_energies=True,
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
print(f"Best site: {result.best_site}")
|
|
372
|
+
```
|
|
373
|
+
"""
|
|
374
|
+
config = AgentConfig(
|
|
375
|
+
material=material,
|
|
376
|
+
miller_indices=miller,
|
|
377
|
+
adsorbate=adsorbate,
|
|
378
|
+
output_dir=output_dir,
|
|
379
|
+
calculate_energies=calculate_energies,
|
|
380
|
+
**kwargs
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
agent = MaterialsScienceAgent(config)
|
|
384
|
+
return agent.run()
|