Skip to content
Open
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
1 change: 1 addition & 0 deletions src/quacc/recipes/aims/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Recipes for FHI-Aims."""
117 changes: 117 additions & 0 deletions src/quacc/recipes/aims/_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""Base jobs for FHI-aims."""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

from ase.calculators.aims import Aims, AimsProfile

from quacc import get_settings
from quacc.runners.ase import Runner
from quacc.schemas.ase import Summarize
from quacc.utils.dicts import recursive_dict_merge
from quacc.utils.kpts import kspacing_to_kpts

if TYPE_CHECKING:
from typing import Any

from ase.atoms import Atoms

from quacc.types import Filenames, RunSchema, SourceDirectory


def run_and_summarize(
atoms: Atoms,
calc_defaults: dict[str, Any] | None = None,
calc_swaps: dict[str, Any] | None = None,
additional_fields: dict[str, Any] | None = None,
copy_files: SourceDirectory | dict[SourceDirectory, Filenames] | None = None,
) -> RunSchema:
"""
Base function to carry out FHI-aims recipes.

Parameters
----------
atoms
Atoms object
calc_defaults
The default calculator parameters.
calc_swaps
Custom kwargs for the FHI-aims calculator. Set a value to
`quacc.Remove` to remove a pre-existing key entirely. For a list of available
keys, refer to the [ase.calculators.aims.Aims][] calculator.
additional_fields
Any additional fields to supply to the summarizer.
copy_files
Files to copy (and decompress) from source to the runtime directory.

Returns
-------
RunSchema
Dictionary of results from [quacc.schemas.ase.Summarize.run][]
"""
calc = prep_calculator(atoms, calc_defaults=calc_defaults, calc_swaps=calc_swaps)
final_atoms = Runner(atoms, calc, copy_files=copy_files).run_calc()

return Summarize(move_magmoms=True, additional_fields=additional_fields).run(
final_atoms, atoms
)


def prep_calculator(
atoms: Atoms,
calc_defaults: dict[str, Any] | None = None,
calc_swaps: dict[str, Any] | None = None,
) -> Aims:
"""
Prepare the FHI-aims calculator.

Parameters
----------
atoms
Atoms object
calc_defaults
The default calculator parameters.
calc_swaps
Custom kwargs for the FHI-aims calculator. Set a value to
`quacc.Remove` to remove a pre-existing key entirely. For a list of available
keys, refer to the [ase.calculators.aims.Aims][] calculator.

Returns
-------
Aims
The FHI-aims calculator.
"""
calc_flags = recursive_dict_merge(calc_defaults or {}, calc_swaps or {})
settings = get_settings()
species_dir = calc_flags.pop("species_dir", None)

if not any(atoms.pbc):
for key in ["kspacing", "k_grid", "k_grid_density"]:
if key in calc_flags:
calc_flags.pop(key)
elif (
"kspacing" in calc_flags
and "k_grid" not in calc_flags
and "k_grid_density" not in calc_flags
):
kspacing = calc_flags.pop("kspacing")
calc_flags["k_grid"] = kspacing_to_kpts(atoms, kspacing)

if "spin" not in calc_flags and hasattr(atoms, "get_initial_magnetic_moments"):
magmoms = atoms.get_initial_magnetic_moments()
if magmoms is not None and any(abs(m) > 1e-6 for m in magmoms):
calc_flags["spin"] = "collinear"

aims_cmd = f"{settings.AIMS_PARALLEL_CMD} {settings.AIMS_BIN}"
species_path = settings.AIMS_SPECIES_DEFAULTS
if species_dir:
species_path = Path(species_path) / species_dir

return Aims(
profile=AimsProfile(
command=aims_cmd.strip(), default_species_directory=str(species_path)
),
**calc_flags,
)
219 changes: 219 additions & 0 deletions src/quacc/recipes/aims/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
"""Core recipes for FHI-aims."""

from __future__ import annotations

from typing import TYPE_CHECKING, Literal

from quacc import job
from quacc.atoms.core import check_is_metal
from quacc.recipes.aims._base import run_and_summarize

if TYPE_CHECKING:
from typing import Any

from ase.atoms import Atoms

from quacc.types import Filenames, RunSchema, SourceDirectory


BASE_SET_METAL = {
"occupation_type": "cold 0.1",
"relativistic": "atomic_zora scalar",
"charge_mix_param": 0.05,
"mixer": "pulay",
"n_max_pulay": 14,
"xc": "pbe",
"output_level": "normal",
}

BASE_SET_AGNOSTIC = {
"relativistic": "atomic_zora scalar",
"mixer": "pulay",
"xc": "pbe",
"output_level": "normal",
}

BASE_SET_NON_METAL = {
"occupation_type": "gaussian 0.01",
"relativistic": "atomic_zora scalar",
"xc": "pbe",
"charge_mix_param": 0.20,
"mixer": "pulay",
"output_level": "normal",
}

KSPACING_METAL = 0.033
KSPACING_AGNOSTIC = 0.033
KSPACING_NON_METAL = 0.045


@job
def static_job(
atoms: Atoms,
species_defaults: Literal[
"light", "intermediate", "tight", "really_tight"
] = "intermediate",
kspacing: float | None = None,
spin: Literal["none", "collinear", "non-collinear"] | None = None,
copy_files: SourceDirectory | dict[SourceDirectory, Filenames] | None = None,
additional_fields: dict[str, Any] | None = None,
agnostic_params: bool = False,
**calc_kwargs,
) -> RunSchema:
"""
Function to carry out a basic SCF calculation with FHI-aims.

Parameters
----------
atoms
The Atoms object.
species_defaults
The level of accuracy for the basis set and integration grids.
Options: "light", "intermediate", "tight", "really_tight".
Default is "intermediate" which is suitable for production SCF calculations.
kspacing
The kpoint spacing in Å^-1. If not provided, defaults to
KSPACING_METAL (0.033) for metals and KSPACING_NON_METAL (0.045) for non-metals.
Ignored for aperiodic systems.
spin
Spin treatment. Options are "none", "collinear", or "non-collinear".
Default is None, which will automatically set to "collinear" if magnetic
moments are detected in the atoms object.
copy_files
Files to copy (and decompress) from source to the runtime directory.
additional_fields
Additional fields to add to the results dictionary.
agnostic_params
If True, uses minimal settings (no occupation_type or charge_mix_param)
letting FHI-aims determine these automatically. If False (default),
auto-detects whether the system is metallic or not.
**calc_kwargs
Custom kwargs for the FHI-aims calculator. For a list of available
keys, refer to the [ase.calculators.aims.Aims][] calculator.

Returns
-------
RunSchema
Dictionary of results, specified in [quacc.schemas.ase.Summarize.run][].
See the type-hint for the data structure.
"""
if agnostic_params:
calc_defaults = BASE_SET_AGNOSTIC.copy()
default_kspacing = KSPACING_AGNOSTIC
else:
calc_defaults = (
BASE_SET_METAL.copy()
if check_is_metal(atoms)
else BASE_SET_NON_METAL.copy()
)
default_kspacing = (
KSPACING_METAL if check_is_metal(atoms) else KSPACING_NON_METAL
)

calc_defaults["species_dir"] = species_defaults

if kspacing is not None:
calc_defaults["kspacing"] = kspacing
else:
calc_defaults["kspacing"] = default_kspacing

if spin is not None:
calc_defaults["spin"] = spin

return run_and_summarize(
atoms,
calc_defaults=calc_defaults,
calc_swaps=calc_kwargs,
additional_fields={"name": "FHI-aims Static"} | (additional_fields or {}),
copy_files=copy_files,
)


@job
def relax_job(
atoms: Atoms,
species_defaults: Literal[
"light", "intermediate", "tight", "really_tight"
] = "light",
kspacing: float | None = None,
spin: Literal["none", "collinear", "non-collinear"] | None = None,
relax_cell: bool = False,
copy_files: SourceDirectory | dict[SourceDirectory, Filenames] | None = None,
additional_fields: dict[str, Any] | None = None,
agnostic_params: bool = False,
**calc_kwargs,
) -> RunSchema:
"""
Function to carry out a structure relaxation with FHI-aims internal optimizer.

Parameters
----------
atoms
The Atoms object.
species_defaults
The level of accuracy for the basis set and integration grids.
Options: "light", "intermediate", "tight", "really_tight".
Default is "light" which is suitable for geometry optimizations.
kspacing
The kpoint spacing in Å^-1. If not provided, defaults to
KSPACING_METAL (0.033) for metals and KSPACING_NON_METAL (0.045) for non-metals.
Ignored for aperiodic systems.
spin
Spin treatment. Options are "none", "collinear", or "non-collinear".
Default is None, which will automatically set to "collinear" if magnetic
moments are detected in the atoms object.
relax_cell
Whether to relax the cell or not.
copy_files
Files to copy (and decompress) from source to the runtime directory.
additional_fields
Additional fields to add to the results dictionary.
agnostic_params
If True, uses minimal settings (no occupation_type or charge_mix_param)
letting FHI-aims determine these automatically. If False (default),
auto-detects whether the system is metallic or not.
**calc_kwargs
Custom kwargs for the FHI-aims calculator. For a list of available
keys, refer to the [ase.calculators.aims.Aims][] calculator.

Returns
-------
RunSchema
Dictionary of results from [quacc.schemas.ase.Summarize.run][].
See the type-hint for the data structure.
"""
if agnostic_params:
calc_defaults = BASE_SET_AGNOSTIC.copy()
default_kspacing = KSPACING_AGNOSTIC
else:
calc_defaults = (
BASE_SET_METAL.copy()
if check_is_metal(atoms)
else BASE_SET_NON_METAL.copy()
)
default_kspacing = (
KSPACING_METAL if check_is_metal(atoms) else KSPACING_NON_METAL
)

calc_defaults["species_dir"] = species_defaults

if kspacing is not None:
calc_defaults["kspacing"] = kspacing
else:
calc_defaults["kspacing"] = default_kspacing

if spin is not None:
calc_defaults["spin"] = spin

calc_defaults["relax_geometry"] = "bfgs 1E-2"

if relax_cell:
calc_defaults["relax_unit_cell"] = "full"

return run_and_summarize(
atoms,
calc_defaults=calc_defaults,
calc_swaps=calc_kwargs,
additional_fields={"name": "FHI-aims Relax"} | (additional_fields or {}),
copy_files=copy_files,
)
27 changes: 27 additions & 0 deletions src/quacc/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,33 @@ class QuaccSettings(BaseSettings):
),
)

# ---------------------------
# FHI-aims Settings
# ---------------------------
AIMS_BIN: Path = Field(
Path("aims.x"), description="Path to the FHI-aims executable."
)

AIMS_PARALLEL_CMD: str = Field(
"",
description=(
"""
Parallelization command to run FHI-aims. For example: 'mpirun -np 4'.
Note that this does not include the executable name.
"""
),
)

AIMS_SPECIES_DEFAULTS: Path = Field(
Path("defaults_2020"),
description=(
"""
Path to the species_defaults directory containing the FHI-aims basis sets.
This should point to a specific species defaults set (e.g., defaults_2020),
which contains subdirectories like 'light', 'intermediate', 'tight', etc.
"""
),
)
# ---------------------------
# Q-Chem Settings
# ---------------------------
Expand Down
Loading