Workflows Module¶
The nh3sofc.workflows module provides high-level workflow classes for common computational tasks.
RelaxationWorkflow¶
Geometry optimization workflow.
Constructor¶
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
atoms |
Atoms |
- | Initial structure |
work_dir |
str |
"." |
Working directory |
calculator |
str |
"vasp" |
Calculator: "vasp" or "mace" |
**calc_kwargs |
- | - | Calculator parameters |
Methods¶
setup¶
Set up calculation files.
run¶
Run optimization (MACE) or generate files (VASP).
parse_results¶
Parse optimization results.
Example:
from ase.io import read
from nh3sofc.workflows import RelaxationWorkflow
atoms = read("initial.xyz")
# VASP relaxation
workflow = RelaxationWorkflow(
atoms,
work_dir="./relax",
calculator="vasp",
encut=520,
kspacing=0.03,
hubbard_u={"V": 3.25},
)
workflow.setup()
# After VASP completes...
results = workflow.parse_results()
relaxed = results["atoms"]
print(f"Final energy: {results['energy']:.4f} eV")
DecompositionWorkflow¶
NH3 decomposition pathway workflow.
Constructor¶
DecompositionWorkflow(
nh3_on_slab: Atoms,
work_dir: str = "./decomposition",
n_configs_per_step: int = 5,
calculator: str = "vasp",
**calc_kwargs
)
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
nh3_on_slab |
Atoms |
- | Optimized NH3 on surface |
work_dir |
str |
"./decomposition" |
Working directory |
n_configs_per_step |
int |
5 |
Configurations per step |
calculator |
str |
"vasp" |
Calculator type |
Methods¶
setup¶
Generate all intermediate configurations and calculation files.
Returns: Dictionary mapping step names to list of calculation directories.
get_status¶
Check calculation status for all steps.
parse_results¶
Parse all calculation results.
get_lowest_energies¶
Get lowest energy for each step.
get_energy_profile¶
Get relative energy profile.
get_reaction_energies¶
Get step-by-step reaction energies.
print_summary¶
Print formatted results summary.
Example:
from ase.io import read
from nh3sofc.workflows import DecompositionWorkflow
nh3_on_slab = read("relaxed_nh3_on_surface.traj")
workflow = DecompositionWorkflow(
nh3_on_slab=nh3_on_slab,
work_dir="./decomposition_study",
n_configs_per_step=5,
encut=520,
hubbard_u={"V": 3.25},
vdw="D3BJ",
)
# Setup all calculations
paths = workflow.setup()
for step, dirs in paths.items():
print(f"{step}: {len(dirs)} configurations")
# After calculations complete...
profile = workflow.get_energy_profile()
workflow.print_summary()
Directory Structure¶
decomposition_study/
├── initial_configs/
│ ├── NH3.xyz
│ ├── NH2_H_000.xyz
│ └── ...
├── NH3/
│ └── config_000/
├── NH2_H/
│ ├── config_000/
│ ├── config_001/
│ └── ...
├── NH_2H/
│ └── ...
└── N_3H/
└── ...
NEBWorkflow¶
Nudged Elastic Band workflow for transition states.
Constructor¶
NEBWorkflow(
initial: Atoms,
final: Atoms,
work_dir: str = "./neb",
n_images: int = 5,
calculator: str = "vasp",
**calc_kwargs
)
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
initial |
Atoms |
- | Initial state structure |
final |
Atoms |
- | Final state structure |
work_dir |
str |
"./neb" |
Working directory |
n_images |
int |
5 |
Number of NEB images |
calculator |
str |
"vasp" |
Calculator type |
Methods¶
setup¶
Set up NEB calculation.
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
interpolation |
str |
"idpp" |
Interpolation: "linear" or "idpp" |
climb |
bool |
True |
Use climbing image NEB |
run_mace¶
Run NEB with MACE calculator.
parse_results¶
Parse NEB results.
Returns:
{
"images": List[Atoms],
"energies": List[float],
"barrier_forward": float,
"barrier_reverse": float,
"ts_image": int,
"ts_structure": Atoms,
}
plot_energy_profile¶
Plot NEB energy profile.
Example:
from ase.io import read
from nh3sofc.workflows import NEBWorkflow
initial = read("nh3_on_surface.traj")
final = read("nh2_h_on_surface.traj")
neb = NEBWorkflow(
initial=initial,
final=final,
work_dir="./neb_nh3_dissociation",
n_images=7,
encut=520,
)
# For VASP
neb.setup(interpolation="idpp", climb=True)
# Or run with MACE
neb_images = neb.run_mace(fmax=0.03)
results = neb.parse_results()
print(f"Forward barrier: {results['barrier_forward']:.3f} eV")
print(f"Reverse barrier: {results['barrier_reverse']:.3f} eV")
neb.plot_energy_profile("neb_profile.png")
FrequencyWorkflow¶
Vibrational frequency and thermochemistry workflow.
Constructor¶
FrequencyWorkflow(
atoms: Atoms,
work_dir: str = "./frequency",
adsorbate_indices: List[int] = None,
calculator: str = "vasp",
**calc_kwargs
)
Parameters:
| Name | Type | Description |
|---|---|---|
atoms |
Atoms |
Optimized structure |
work_dir |
str |
Working directory |
adsorbate_indices |
List[int] |
Indices of adsorbate atoms |
calculator |
str |
Calculator type |
Methods¶
setup¶
Set up frequency calculation.
parse_results¶
Parse frequencies and calculate thermochemistry.
Returns:
{
"frequencies": List[float],
"real_frequencies": List[float],
"imaginary": List[float],
"zpe": float,
"thermo": HarmonicThermo,
}
get_thermochemistry¶
Calculate thermodynamic properties.
Returns:
Example:
from nh3sofc.workflows import FrequencyWorkflow
workflow = FrequencyWorkflow(
optimized_atoms,
work_dir="./freq",
adsorbate_indices=[48, 49, 50, 51], # NH3 atom indices
encut=520,
)
workflow.setup()
# After VASP completes...
results = workflow.parse_results()
print(f"Frequencies (cm^-1): {results['real_frequencies']}")
print(f"ZPE: {results['zpe']:.4f} eV")
# Thermochemistry at 673 K
thermo = workflow.get_thermochemistry(temperature=673)
print(f"G(673K): {thermo['G']:.4f} eV")
ScreeningWorkflow¶
High-throughput parameter screening.
Constructor¶
ScreeningWorkflow(
base_structure: Atoms,
parameter_space: Dict[str, List],
work_dir: str = "./screening",
calculator: str = "vasp",
**calc_kwargs
)
Parameters:
| Name | Type | Description |
|---|---|---|
base_structure |
Atoms |
Base structure to modify |
parameter_space |
dict |
Parameters to scan |
work_dir |
str |
Working directory |
Methods¶
setup¶
Generate all calculation directories.
get_status¶
Check status of all calculations.
parse_results¶
Parse all results into DataFrame.
find_optimal¶
Find optimal parameters.
Example:
from nh3sofc.workflows import ScreeningWorkflow
workflow = ScreeningWorkflow(
base_structure=surface,
parameter_space={
"vacancy_concentration": [0.0, 0.05, 0.10, 0.15, 0.20],
"nitrogen_fraction": [0.5, 0.67, 0.75],
},
work_dir="./oxynitride_screening",
encut=520,
)
# Setup all calculations
calc_dirs = workflow.setup()
print(f"Created {len(calc_dirs)} calculations")
# After completion...
results = workflow.parse_results()
optimal = workflow.find_optimal(metric="adsorption_energy")
print(f"Optimal parameters: {optimal}")
CompositionScreening¶
Specialized workflow for composition screening.
Constructor¶
CompositionScreening(
base_structure: Atoms,
elements_to_vary: Dict[str, List[str]],
work_dir: str = "./composition_screening",
**calc_kwargs
)
Example:
workflow = CompositionScreening(
base_structure=surface,
elements_to_vary={
"La": ["La", "Sr", "Ba"],
"V": ["V", "Ti", "Nb"],
},
work_dir="./dopant_screening",
)
workflow.setup()
AdsorbateScreening¶
Screen multiple adsorbates and sites.
Constructor¶
AdsorbateScreening(
surface: Atoms,
adsorbates: List[str],
sites: List[str] = None,
work_dir: str = "./adsorbate_screening",
**calc_kwargs
)
Example:
workflow = AdsorbateScreening(
surface=surface,
adsorbates=["NH3", "NH2", "NH", "N", "H"],
sites=["ontop_La", "ontop_V", "bridge", "hollow"],
work_dir="./adsorbate_study",
)
paths = workflow.setup()
# Creates calculations for all adsorbate-site combinations
Workflow Utilities¶
submit_all¶
from nh3sofc.workflows import submit_all
submit_all(
work_dir: str,
job_system: str = "pbs",
dry_run: bool = False
)
Submit all jobs in a workflow directory.
check_all_complete¶
from nh3sofc.workflows import check_all_complete
complete = check_all_complete(work_dir)
if complete:
print("All calculations finished")
collect_results¶
from nh3sofc.workflows import collect_results
results = collect_results(
work_dir,
output_format="dataframe" # or "dict" or "json"
)
ExsolutionWorkflow¶
Workflow for studying exsolution processes in perovskite materials.
Constructor¶
ExsolutionWorkflow(
atoms: Atoms,
work_dir: str,
metal: str = "Ni",
particle_size: int = 13,
vacancy_fraction: float = 0.1,
calculator: str = "vasp",
**calc_kwargs
)
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
atoms |
Atoms |
- | Perovskite surface structure |
work_dir |
str |
- | Working directory |
metal |
str |
"Ni" |
Exsolution metal (Ni, Co, Fe) |
particle_size |
int |
13 |
Nanoparticle size |
vacancy_fraction |
float |
0.1 |
Oxygen vacancy concentration |
calculator |
str |
"vasp" |
Calculator type |
Methods¶
generate_pathway_structures¶
Generate structures for all exsolution stages: 1. Pristine perovskite 2. Defective (with vacancies) 3. Surface-segregated 4. Exsolved nanoparticle
setup¶
Set up calculations for all stages.
parse_results¶
Parse results and calculate exsolution energetics.
Returns:
{
"pristine": {"energy": ..., "converged": ...},
"defective": {"energy": ..., ...},
"segregated": {"energy": ..., ...},
"exsolved": {"energy": ..., ...},
"exsolution_energy": float, # Calculated driving force
"segregation_energy": float,
"summary": {...}
}
couple_with_decomposition¶
Set up NH3 decomposition study on exsolved particle.
Example:
from nh3sofc.structure import BulkStructure, SurfaceBuilder
from nh3sofc.workflows import ExsolutionWorkflow
# Prepare perovskite surface
bulk = BulkStructure.from_cif("LaSrTiNiO3.cif")
surface = SurfaceBuilder(bulk).create_surface((0,0,1), layers=6, vacuum=15)
# Set up exsolution workflow
wf = ExsolutionWorkflow(
atoms=surface.atoms,
work_dir="./exsolution_study",
metal="Ni",
particle_size=13,
vacancy_fraction=0.1,
hubbard_u={"Ni": 6.2, "Ti": 3.0},
)
wf.generate_pathway_structures()
wf.setup()
# After VASP calculations:
results = wf.parse_results()
print(f"Exsolution energy: {results['exsolution_energy']:.2f} eV")
print(f"Favorable: {results['summary']['favorable']}")
# Continue with NH3 decomposition on exsolved particle
decomp_wf = wf.couple_with_decomposition()
decomp_wf.setup()
ExsolutionScreeningWorkflow¶
High-throughput screening for exsolution parameters.
Constructor¶
ExsolutionScreeningWorkflow(
base_structure: Atoms,
parameter_space: Dict[str, List],
work_dir: str,
calculator: str = "vasp",
n_configs_per_combo: int = 1,
**calc_kwargs
)
Parameters:
| Name | Type | Description |
|---|---|---|
base_structure |
Atoms |
Base perovskite surface |
parameter_space |
dict |
Parameters to screen |
work_dir |
str |
Working directory |
n_configs_per_combo |
int |
Configurations per combination |
Parameter space options:
parameter_space = {
"metal": ["Ni", "Co", "Fe"],
"particle_size": [1, 4, 13],
"vacancy_fraction": [0.0, 0.05, 0.1]
}
Methods¶
generate_all¶
Generate all parameter combinations.
setup_all¶
Set up calculations for all configurations.
parse_all¶
Parse all results.
get_best_result¶
Get best result by specified metric.
Example:
screening = ExsolutionScreeningWorkflow(
base_structure=surface,
parameter_space={
"metal": ["Ni", "Co", "Fe"],
"particle_size": [1, 13],
"vacancy_fraction": [0.05, 0.1],
},
work_dir="./exsolution_screening",
)
screening.generate_all()
screening.setup_all()
# After calculations:
results = screening.parse_all()
best = screening.get_best_result()
print(f"Best: {best['config']}")
run_exsolution_study¶
Convenience function for quick exsolution study setup.