Skip to main content

A pyiron module for atomistic simulation workflows

Project description

pyiron_workflow_atomistics

Tests Coverage Status PyPI version Python versions Documentation Status Code style: black License: BSD-3-Clause

Overview

pyiron_workflow_atomistics provides atomistic-simulation workflows for the pyiron ecosystem. It exposes a single generic Engine interface that workflows talk to uniformly, plus topical physics macros (bulk, surface, point defects, grain boundaries) that compose into pyiron workflows.

Layout

pyiron_workflow_atomistics/
├── engine/      # Engine Protocol, EngineOutput, run(), ASEEngine
├── structure/   # Generic builders / transforms / defect-structure generation
├── physics/     # Topical workflow macros — import per-topic
│   ├── bulk.py
│   ├── surface.py
│   ├── point_defect.py
│   └── grain_boundary.py
├── analysis/    # Featurisers, GB-plane finder, derived quantities
└── _internal/   # Private plumbing (not part of the public API)

Users import from the four public subpackages (engine, structure, physics.*, analysis); _internal is intentionally private.

Installation

pip install pyiron_workflow_atomistics
# or
conda install -c conda-forge pyiron_workflow_atomistics

Quick start

Every workflow follows the same pattern: build an Engine, build a structure, and either call run(structure, engine) directly or drop into a topical macro.

from ase.build import bulk
from ase.calculators.emt import EMT

from pyiron_workflow_atomistics.engine import ASEEngine, CalcInputMinimize, run

engine = ASEEngine(
    EngineInput=CalcInputMinimize(force_convergence_tolerance=0.05),
    calculator=EMT(),
    working_directory="./_runs",
)

structure = bulk("Cu", "fcc", a=3.6, cubic=True)
node = run(structure, engine=engine)
node.run()

out = node.outputs.engine_output.value           # EngineOutput dataclass
print(out.final_energy, out.converged)

Engine.with_working_directory("subdir") returns a pickleable copy with the path composed — use it to fan out per-calculation directories.

The Engine layer

from pyiron_workflow_atomistics.engine import (
    Engine,              # the Protocol every backend implements (runtime-checkable)
    EngineOutput,        # @dataclass returned by every engine call
    run,                 # the single workflow node: run(structure, engine)
    subengine,           # @as_function_node wrapper around engine.with_working_directory
    subdir_path,         # @as_function_node returning os.path.join(engine.wd, subdir)
    CalcInputStatic,
    CalcInputMinimize,
    CalcInputMD,
    ASEEngine,
)

The three CalcInput* dataclasses are jargon-free physics-level inputs (force_convergence_tolerance, temperature, thermostat_time_constant, ...) — engines map them to their backend's native parameters.

Topical physics workflows

physics/__init__.py deliberately re-exports nothing — import per-topic so the path tells you what you're using.

Bulk

from pyiron_workflow_atomistics.physics.bulk import eos_volume_scan, optimise_cubic_lattice_parameter
from pyiron_workflow_atomistics.structure import get_bulk

structure = get_bulk.node_function("Cu", crystalstructure="fcc", a=3.6, cubic=True)
wf = eos_volume_scan(base_structure=structure, engine=engine,
                     axes=["a", "b", "c"], strain_range=(-0.05, 0.05), num_points=7)
wf.run()
print(f"v0 = {wf.outputs.v0.value:.3f} Å^3   B = {wf.outputs.B.value:.1f} GPa")

Surface

from ase.build import bulk
from pyiron_workflow_atomistics.physics.surface import calculate_surface_energy

cu_bulk = bulk("Cu", "fcc", a=3.6, cubic=True)
wf = calculate_surface_energy(
    bulk_structure=cu_bulk, engine=engine,
    miller_indices=(1, 1, 1), layers=3, vacuum=10.0,
)
wf.run()
print(wf.outputs.surface_energy.value, "J/m²")

Point defects (vacancy + substitutional)

from pyiron_workflow_atomistics.physics.point_defect import (
    get_vacancy_formation_energy,
    get_substitutional_formation_energy,
)

wf = get_vacancy_formation_energy(
    structure=bulk("Cu", "fcc", a=3.6, cubic=True),
    engine=engine,
    min_dimensions=[12, 12, 12],
)
wf.run()
print(wf.outputs.vacancy_formation_energy.value, "eV")

wf_sub = get_substitutional_formation_energy(
    structure=bulk("Cu", "fcc", a=3.6, cubic=True),
    engine=engine,
    new_symbol="Ni",
    min_dimensions=[12, 12, 12],
)
wf_sub.run()

Grain boundaries

from pyiron_workflow_atomistics.physics.grain_boundary import (
    cleave_gb_structure,
    find_viable_cleavage_planes_around_plane,
    get_GB_energy,
    pure_gb_study,
)

pure_gb_study composes length optimisation, segregation, and cleavage in one macro; the individual functions are available for finer control. See notebooks/pure_grain_boundary_study.ipynb and notebooks/gb_cleavage.ipynb.

Structure manipulation

from pyiron_workflow_atomistics.structure import (
    get_bulk, create_surface_slab,
    add_vacuum, create_supercell, create_supercell_with_min_dimensions, rattle,
    create_vacancy, substitutional_swap,
)

Analysis

from pyiron_workflow_atomistics.analysis import (
    voronoi_site_featuriser,
    distance_matrix_site_featuriser,
    soap_site_featuriser,
    find_gb_plane, plot_gb_plane,
    get_per_atom_quantity,
)

Implementing a custom engine

Engine is a typing.Protocol — any class that satisfies the contract works. There is no base class to inherit from.

import os
from dataclasses import dataclass, field, replace
from typing import Any, Callable
from ase import Atoms

from pyiron_workflow_atomistics.engine import (
    CalcInputStatic, CalcInputMinimize, CalcInputMD, EngineOutput,
)


@dataclass
class MyCustomEngine:
    """Drop-in replacement for ASEEngine targeting your backend."""

    EngineInput: CalcInputStatic | CalcInputMinimize | CalcInputMD
    backend_config: dict[str, Any] = field(default_factory=dict)
    working_directory: str = field(default_factory=os.getcwd)

    def get_calculate_fn(self, structure: Atoms) -> tuple[Callable[..., EngineOutput], dict[str, Any]]:
        """Return ``(callable, kwargs)``. The callable is invoked as
        ``callable(structure=structure, **kwargs)`` and must return an
        :class:`EngineOutput`. ``structure`` must NOT be in kwargs."""

        from my_backend import run_calculation

        kwargs = {
            "working_directory": self.working_directory,
            "config": self.backend_config,
            # map self.EngineInput.* into your backend's native parameters
        }
        return run_calculation, kwargs

    def with_working_directory(self, subdir: str) -> "MyCustomEngine":
        """Pure copy with the working directory composed — never mutate self."""
        return replace(
            self, working_directory=os.path.join(self.working_directory, subdir)
        )

The contract:

  • Pickleable — workflows checkpoint to disk and may resubmit to SLURM.
  • with_working_directory is pure — return a copy, do not mutate self. The recommended idiom is dataclasses.replace.
  • get_calculate_fn(structure) returns (callable, kwargs); the callable returns an EngineOutput.

Use subengine(engine=engine, subdir="foo") inside @pwf.as_macro_node bodies — calling engine.with_working_directory(...) directly on a channel input crashes pyiron_workflow's readiness checks.

EngineOutput

@dataclass
class EngineOutput:
    final_structure: Atoms
    final_energy: float
    converged: bool

    final_forces:        np.ndarray | None = None
    final_stress:        np.ndarray | None = None   # (3, 3)
    final_stress_voigt:  np.ndarray | None = None   # (6,)
    final_volume:        float       | None = None
    final_magmoms:       np.ndarray  | None = None

    energies:      list[float]        | None = None
    forces:        list[np.ndarray]   | None = None
    stresses:      list[np.ndarray]   | None = None
    structures:    list[Atoms]        | None = None
    n_ionic_steps: int                | None = None

EngineOutput.to_dict() returns a dataclasses.asdict view, with ASE objects preserved by reference.

Notebooks

Worked examples covering every public workflow live in notebooks/. Each notebook supplies its own calculator (EMT for the Cu / Ni / Pd / Ag / Pt / Au / Al demos; EAM with the bundled Al-Fe.eam.fs for the Fe ones) so it executes self-contained.

Documentation

For full API documentation: ReadTheDocs.

Contributing

We welcome contributions — see CONTRIBUTING.rst for the development workflow.

License

BSD 3-Clause — see LICENSE.

Citation

@software{pyiron_workflow_atomistics,
  author = {pyiron team},
  title  = {pyiron_workflow_atomistics},
  year   = {2024},
  url    = {https://github.com/pyiron/pyiron_workflow_atomistics}
}

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyiron_workflow_atomistics-0.0.5.tar.gz (84.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyiron_workflow_atomistics-0.0.5-py3-none-any.whl (78.9 kB view details)

Uploaded Python 3

File details

Details for the file pyiron_workflow_atomistics-0.0.5.tar.gz.

File metadata

File hashes

Hashes for pyiron_workflow_atomistics-0.0.5.tar.gz
Algorithm Hash digest
SHA256 91847e2cac849692b85004de98a9989ae1a6934d44d434b273c3374304594c23
MD5 b72de1e4639398afe8952181bbe6c25d
BLAKE2b-256 76ed6d2b1af4fac3f05fd4a91d1b628131021af11632570268ccd8118fef9403

See more details on using hashes here.

File details

Details for the file pyiron_workflow_atomistics-0.0.5-py3-none-any.whl.

File metadata

File hashes

Hashes for pyiron_workflow_atomistics-0.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 2f4a6b990ed53ea23918e19e247e09645734f5c93da4b9c7708446bebf4478eb
MD5 c82b2ad30a4b2f3aca7846304bd3f8af
BLAKE2b-256 a1f70d0ab2cbf74b0525d4fb5256ae00d3c4ab58335678c51e263d6f4d3f948d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page