diff --git a/conftest.py b/conftest.py index 16c68c85..fd5b288d 100644 --- a/conftest.py +++ b/conftest.py @@ -3,6 +3,8 @@ Based on https://docs.pytest.org/en/latest/example/simple.html. """ +from __future__ import annotations + import pytest diff --git a/janus_core/__init__.py b/janus_core/__init__.py index efaa8049..fd316051 100644 --- a/janus_core/__init__.py +++ b/janus_core/__init__.py @@ -1,5 +1,7 @@ """Tools for machine learnt interatomic potentials.""" +from __future__ import annotations + from importlib.metadata import version __version__ = version("janus-core") diff --git a/janus_core/calculations/base.py b/janus_core/calculations/base.py index bc3fccdf..7146ac5e 100644 --- a/janus_core/calculations/base.py +++ b/janus_core/calculations/base.py @@ -1,6 +1,8 @@ """Prepare structures for MLIP calculations.""" -from typing import Any, Optional +from __future__ import annotations + +from typing import Any from ase import Atoms @@ -71,22 +73,22 @@ def __init__( self, *, calc_name: str = "base", - struct: Optional[MaybeSequence[Atoms]] = None, - struct_path: Optional[PathLike] = None, + struct: MaybeSequence[Atoms] | None = None, + struct_path: PathLike | None = None, arch: Architectures = "mace_mp", device: Devices = "cpu", - model_path: Optional[PathLike] = None, - read_kwargs: Optional[ASEReadArgs] = None, + model_path: PathLike | None = None, + read_kwargs: ASEReadArgs | None = None, sequence_allowed: bool = True, - calc_kwargs: Optional[dict[str, Any]] = None, - set_calc: Optional[bool] = None, + calc_kwargs: dict[str, Any] | None = None, + set_calc: bool | None = None, attach_logger: bool = False, - log_kwargs: Optional[dict[str, Any]] = None, + log_kwargs: dict[str, Any] | None = None, track_carbon: bool = True, - tracker_kwargs: Optional[dict[str, Any]] = None, - file_prefix: Optional[PathLike] = None, - additional_prefix: Optional[str] = None, - param_prefix: Optional[str] = None, + tracker_kwargs: dict[str, Any] | None = None, + file_prefix: PathLike | None = None, + additional_prefix: str | None = None, + param_prefix: str | None = None, ) -> None: """ Read the structure being simulated and attach an MLIP calculator. diff --git a/janus_core/calculations/descriptors.py b/janus_core/calculations/descriptors.py index f8d4dd28..95425d15 100644 --- a/janus_core/calculations/descriptors.py +++ b/janus_core/calculations/descriptors.py @@ -1,7 +1,9 @@ """Calculate MLIP descriptors for structures.""" +from __future__ import annotations + from collections.abc import Sequence -from typing import Any, Optional +from typing import Any from ase import Atoms import numpy as np @@ -73,23 +75,23 @@ class Descriptors(BaseCalculation): def __init__( self, - struct: Optional[MaybeSequence[Atoms]] = None, - struct_path: Optional[PathLike] = None, + struct: MaybeSequence[Atoms] | None = None, + struct_path: PathLike | None = None, arch: Architectures = "mace_mp", device: Devices = "cpu", - model_path: Optional[PathLike] = None, - read_kwargs: Optional[ASEReadArgs] = None, - calc_kwargs: Optional[dict[str, Any]] = None, - set_calc: Optional[bool] = None, + model_path: PathLike | None = None, + read_kwargs: ASEReadArgs | None = None, + calc_kwargs: dict[str, Any] | None = None, + set_calc: bool | None = None, attach_logger: bool = False, - log_kwargs: Optional[dict[str, Any]] = None, + log_kwargs: dict[str, Any] | None = None, track_carbon: bool = True, - tracker_kwargs: Optional[dict[str, Any]] = None, + tracker_kwargs: dict[str, Any] | None = None, invariants_only: bool = True, calc_per_element: bool = False, calc_per_atom: bool = False, write_results: bool = False, - write_kwargs: Optional[ASEWriteArgs] = None, + write_kwargs: ASEWriteArgs | None = None, ) -> None: """ Initialise class. diff --git a/janus_core/calculations/eos.py b/janus_core/calculations/eos.py index fee3df43..1ddd3ee7 100644 --- a/janus_core/calculations/eos.py +++ b/janus_core/calculations/eos.py @@ -1,7 +1,9 @@ """Equation of State.""" +from __future__ import annotations + from copy import copy -from typing import Any, Optional +from typing import Any from ase import Atoms from ase.eos import EquationOfState @@ -105,31 +107,31 @@ class EoS(BaseCalculation): def __init__( self, - struct: Optional[Atoms] = None, - struct_path: Optional[PathLike] = None, + struct: Atoms | None = None, + struct_path: PathLike | None = None, arch: Architectures = "mace_mp", device: Devices = "cpu", - model_path: Optional[PathLike] = None, - read_kwargs: Optional[ASEReadArgs] = None, - calc_kwargs: Optional[dict[str, Any]] = None, - set_calc: Optional[bool] = None, + model_path: PathLike | None = None, + read_kwargs: ASEReadArgs | None = None, + calc_kwargs: dict[str, Any] | None = None, + set_calc: bool | None = None, attach_logger: bool = False, - log_kwargs: Optional[dict[str, Any]] = None, + log_kwargs: dict[str, Any] | None = None, track_carbon: bool = True, - tracker_kwargs: Optional[dict[str, Any]] = None, + tracker_kwargs: dict[str, Any] | None = None, min_volume: float = 0.95, max_volume: float = 1.05, n_volumes: int = 7, eos_type: EoSNames = "birchmurnaghan", minimize: bool = True, minimize_all: bool = False, - minimize_kwargs: Optional[dict[str, Any]] = None, + minimize_kwargs: dict[str, Any] | None = None, write_results: bool = True, write_structures: bool = False, - write_kwargs: Optional[OutputKwargs] = None, + write_kwargs: OutputKwargs | None = None, plot_to_file: bool = False, - plot_kwargs: Optional[dict[str, Any]] = None, - file_prefix: Optional[PathLike] = None, + plot_kwargs: dict[str, Any] | None = None, + file_prefix: PathLike | None = None, ) -> None: """ Initialise class. diff --git a/janus_core/calculations/geom_opt.py b/janus_core/calculations/geom_opt.py index 09b18d01..3f4c155e 100644 --- a/janus_core/calculations/geom_opt.py +++ b/janus_core/calculations/geom_opt.py @@ -1,6 +1,8 @@ """Prepare and run geometry optimization.""" -from typing import Any, Callable, Optional, Union +from __future__ import annotations + +from typing import Any, Callable import warnings from ase import Atoms, filters, units @@ -97,30 +99,30 @@ class GeomOpt(BaseCalculation): def __init__( self, - struct: Optional[Atoms] = None, - struct_path: Optional[PathLike] = None, + struct: Atoms | None = None, + struct_path: PathLike | None = None, arch: Architectures = "mace_mp", device: Devices = "cpu", - model_path: Optional[PathLike] = None, - read_kwargs: Optional[ASEReadArgs] = None, - calc_kwargs: Optional[dict[str, Any]] = None, - set_calc: Optional[bool] = None, + model_path: PathLike | None = None, + read_kwargs: ASEReadArgs | None = None, + calc_kwargs: dict[str, Any] | None = None, + set_calc: bool | None = None, attach_logger: bool = False, - log_kwargs: Optional[dict[str, Any]] = None, + log_kwargs: dict[str, Any] | None = None, track_carbon: bool = True, - tracker_kwargs: Optional[dict[str, Any]] = None, + tracker_kwargs: dict[str, Any] | None = None, fmax: float = 0.1, steps: int = 1000, symmetrize: bool = False, symmetry_tolerance: float = 0.001, angle_tolerance: float = -1.0, - filter_func: Optional[Union[Callable, str]] = FrechetCellFilter, - filter_kwargs: Optional[dict[str, Any]] = None, - optimizer: Union[Callable, str] = LBFGS, - opt_kwargs: Optional[ASEOptArgs] = None, + filter_func: Callable | str | None = FrechetCellFilter, + filter_kwargs: dict[str, Any] | None = None, + optimizer: Callable | str = LBFGS, + opt_kwargs: ASEOptArgs | None = None, write_results: bool = False, - write_kwargs: Optional[OutputKwargs] = None, - traj_kwargs: Optional[OutputKwargs] = None, + write_kwargs: OutputKwargs | None = None, + traj_kwargs: OutputKwargs | None = None, ) -> None: """ Initialise GeomOpt class. diff --git a/janus_core/calculations/md.py b/janus_core/calculations/md.py index 0096ad17..4f17d883 100644 --- a/janus_core/calculations/md.py +++ b/janus_core/calculations/md.py @@ -1,5 +1,7 @@ """Run molecular dynamics simulations.""" +from __future__ import annotations + import datetime from functools import partial from itertools import combinations_with_replacement @@ -7,7 +9,7 @@ from os.path import getmtime from pathlib import Path import random -from typing import Any, Optional, Union +from typing import Any from warnings import warn from ase import Atoms, units @@ -175,51 +177,51 @@ class MolecularDynamics(BaseCalculation): def __init__( self, - struct: Optional[Atoms] = None, - struct_path: Optional[PathLike] = None, + struct: Atoms | None = None, + struct_path: PathLike | None = None, arch: Architectures = "mace_mp", device: Devices = "cpu", - model_path: Optional[PathLike] = None, - read_kwargs: Optional[ASEReadArgs] = None, - calc_kwargs: Optional[dict[str, Any]] = None, - set_calc: Optional[bool] = None, + model_path: PathLike | None = None, + read_kwargs: ASEReadArgs | None = None, + calc_kwargs: dict[str, Any] | None = None, + set_calc: bool | None = None, attach_logger: bool = False, - log_kwargs: Optional[dict[str, Any]] = None, + log_kwargs: dict[str, Any] | None = None, track_carbon: bool = True, - tracker_kwargs: Optional[dict[str, Any]] = None, - ensemble: Optional[Ensembles] = None, + tracker_kwargs: dict[str, Any] | None = None, + ensemble: Ensembles | None = None, steps: int = 0, timestep: float = 1.0, temp: float = 300, equil_steps: int = 0, minimize: bool = False, minimize_every: int = -1, - minimize_kwargs: Optional[dict[str, Any]] = None, + minimize_kwargs: dict[str, Any] | None = None, rescale_velocities: bool = False, remove_rot: bool = False, rescale_every: int = 10, - file_prefix: Optional[PathLike] = None, + file_prefix: PathLike | None = None, restart: bool = False, restart_auto: bool = True, - restart_stem: Optional[PathLike] = None, + restart_stem: PathLike | None = None, restart_every: int = 1000, rotate_restart: bool = False, restarts_to_keep: int = 4, - final_file: Optional[PathLike] = None, - stats_file: Optional[PathLike] = None, + final_file: PathLike | None = None, + stats_file: PathLike | None = None, stats_every: int = 100, - traj_file: Optional[PathLike] = None, + traj_file: PathLike | None = None, traj_append: bool = False, traj_start: int = 0, traj_every: int = 100, - temp_start: Optional[float] = None, - temp_end: Optional[float] = None, - temp_step: Optional[float] = None, - temp_time: Optional[float] = None, - write_kwargs: Optional[OutputKwargs] = None, - post_process_kwargs: Optional[PostProcessKwargs] = None, - correlation_kwargs: Optional[list[CorrelationKwargs]] = None, - seed: Optional[int] = None, + temp_start: float | None = None, + temp_end: float | None = None, + temp_step: float | None = None, + temp_time: float | None = None, + write_kwargs: OutputKwargs | None = None, + post_process_kwargs: PostProcessKwargs | None = None, + correlation_kwargs: list[CorrelationKwargs] | None = None, + seed: int | None = None, ) -> None: """ Initialise molecular dynamics simulation configuration. @@ -493,7 +495,7 @@ def __init__( "filemode": "a", } - self.dyn: Union[Langevin, VelocityVerlet, ASE_NPT] + self.dyn: Langevin | VelocityVerlet | ASE_NPT self.n_atoms = len(self.struct) self.offset = 0 @@ -633,7 +635,7 @@ def _optimize_structure(self) -> None: optimizer = GeomOpt(self.struct, **self.minimize_kwargs) optimizer.run() - def _set_param_prefix(self, file_prefix: Optional[PathLike] = None) -> str: + def _set_param_prefix(self, file_prefix: PathLike | None = None) -> str: """ Set ensemble parameters for output files. @@ -1156,8 +1158,8 @@ def __init__( bulk_modulus: float = 2.0, pressure: float = 0.0, ensemble: Ensembles = "npt", - file_prefix: Optional[PathLike] = None, - ensemble_kwargs: Optional[dict[str, Any]] = None, + file_prefix: PathLike | None = None, + ensemble_kwargs: dict[str, Any] | None = None, **kwargs, ) -> None: """ @@ -1211,7 +1213,7 @@ def __init__( **ensemble_kwargs, ) - def _set_param_prefix(self, file_prefix: Optional[PathLike] = None) -> str: + def _set_param_prefix(self, file_prefix: PathLike | None = None) -> str: """ Set ensemble parameters for output files. @@ -1297,7 +1299,7 @@ def __init__( *args, friction: float = 0.005, ensemble: Ensembles = "nvt", - ensemble_kwargs: Optional[dict[str, Any]] = None, + ensemble_kwargs: dict[str, Any] | None = None, **kwargs, ) -> None: """ @@ -1391,7 +1393,7 @@ def __init__( self, *args, ensemble: Ensembles = "nve", - ensemble_kwargs: Optional[dict[str, Any]] = None, + ensemble_kwargs: dict[str, Any] | None = None, **kwargs, ) -> None: """ @@ -1442,7 +1444,7 @@ def __init__( *args, thermostat_time: float = 50.0, ensemble: Ensembles = "nvt-nh", - ensemble_kwargs: Optional[dict[str, Any]] = None, + ensemble_kwargs: dict[str, Any] | None = None, **kwargs, ) -> None: """ @@ -1546,8 +1548,8 @@ def __init__( bulk_modulus: float = 2.0, pressure: float = 0.0, ensemble: Ensembles = "nph", - file_prefix: Optional[PathLike] = None, - ensemble_kwargs: Optional[dict[str, Any]] = None, + file_prefix: PathLike | None = None, + ensemble_kwargs: dict[str, Any] | None = None, **kwargs, ) -> None: """ diff --git a/janus_core/calculations/phonons.py b/janus_core/calculations/phonons.py index 19fd6ce1..ffc3cfc5 100644 --- a/janus_core/calculations/phonons.py +++ b/janus_core/calculations/phonons.py @@ -1,7 +1,9 @@ """Phonon calculations.""" +from __future__ import annotations + from collections.abc import Sequence -from typing import Any, Optional, get_args +from typing import Any, get_args from ase import Atoms from numpy import ndarray @@ -137,25 +139,25 @@ class Phonons(BaseCalculation): def __init__( self, - struct: Optional[Atoms] = None, - struct_path: Optional[PathLike] = None, + struct: Atoms | None = None, + struct_path: PathLike | None = None, arch: Architectures = "mace_mp", device: Devices = "cpu", - model_path: Optional[PathLike] = None, - read_kwargs: Optional[ASEReadArgs] = None, - calc_kwargs: Optional[dict[str, Any]] = None, - set_calc: Optional[bool] = None, + model_path: PathLike | None = None, + read_kwargs: ASEReadArgs | None = None, + calc_kwargs: dict[str, Any] | None = None, + set_calc: bool | None = None, attach_logger: bool = False, - log_kwargs: Optional[dict[str, Any]] = None, + log_kwargs: dict[str, Any] | None = None, track_carbon: bool = True, - tracker_kwargs: Optional[dict[str, Any]] = None, + tracker_kwargs: dict[str, Any] | None = None, calcs: MaybeSequence[PhononCalcs] = (), supercell: MaybeList[int] = 2, displacement: float = 0.01, mesh: tuple[int, int, int] = (10, 10, 10), symmetrize: bool = False, minimize: bool = False, - minimize_kwargs: Optional[dict[str, Any]] = None, + minimize_kwargs: dict[str, Any] | None = None, temp_min: float = 0.0, temp_max: float = 1000.0, temp_step: float = 50.0, @@ -163,7 +165,7 @@ def __init__( plot_to_file: bool = False, write_results: bool = True, write_full: bool = True, - file_prefix: Optional[PathLike] = None, + file_prefix: PathLike | None = None, enable_progress_bar: bool = False, ) -> None: """ @@ -364,7 +366,7 @@ def calcs(self, value: MaybeSequence[PhononCalcs]) -> None: self._calcs = value def calc_force_constants( - self, write_force_consts: Optional[bool] = None, **kwargs + self, write_force_consts: bool | None = None, **kwargs ) -> None: """ Calculate force constants and optionally write results. @@ -437,9 +439,9 @@ def calc_force_constants( def write_force_constants( self, *, - phonopy_file: Optional[PathLike] = None, - force_consts_to_hdf5: Optional[bool] = None, - force_consts_file: Optional[PathLike] = None, + phonopy_file: PathLike | None = None, + force_consts_to_hdf5: bool | None = None, + force_consts_file: PathLike | None = None, ) -> None: """ Write results of force constants calculations. @@ -480,7 +482,7 @@ def write_force_constants( phonon.force_constants, filename=force_consts_file ) - def calc_bands(self, write_bands: Optional[bool] = None, **kwargs) -> None: + def calc_bands(self, write_bands: bool | None = None, **kwargs) -> None: """ Calculate band structure and optionally write and plot results. @@ -505,9 +507,9 @@ def calc_bands(self, write_bands: Optional[bool] = None, **kwargs) -> None: def write_bands( self, *, - bands_file: Optional[PathLike] = None, - save_plots: Optional[bool] = None, - plot_file: Optional[PathLike] = None, + bands_file: PathLike | None = None, + save_plots: bool | None = None, + plot_file: PathLike | None = None, ) -> None: """ Write results of band structure calculations. @@ -547,8 +549,8 @@ def write_bands( def calc_thermal_props( self, - mesh: Optional[tuple[int, int, int]] = None, - write_thermal: Optional[bool] = None, + mesh: tuple[int, int, int] | None = None, + write_thermal: bool | None = None, **kwargs, ) -> None: """ @@ -598,7 +600,7 @@ def calc_thermal_props( if write_thermal: self.write_thermal_props(**kwargs) - def write_thermal_props(self, thermal_file: Optional[PathLike] = None) -> None: + def write_thermal_props(self, thermal_file: PathLike | None = None) -> None: """ Write results of thermal properties calculations. @@ -629,8 +631,8 @@ def write_thermal_props(self, thermal_file: Optional[PathLike] = None) -> None: def calc_dos( self, *, - mesh: Optional[tuple[int, int, int]] = None, - write_dos: Optional[bool] = None, + mesh: tuple[int, int, int] | None = None, + write_dos: bool | None = None, **kwargs, ) -> None: """ @@ -677,11 +679,11 @@ def calc_dos( def write_dos( self, *, - dos_file: Optional[PathLike] = None, - plot_to_file: Optional[bool] = None, - plot_file: Optional[PathLike] = None, + dos_file: PathLike | None = None, + plot_to_file: bool | None = None, + plot_file: PathLike | None = None, plot_bands: bool = False, - plot_bands_file: Optional[PathLike] = None, + plot_bands_file: PathLike | None = None, ) -> None: """ Write results of DOS calculation. @@ -736,8 +738,8 @@ def write_dos( def calc_pdos( self, *, - mesh: Optional[tuple[int, int, int]] = None, - write_pdos: Optional[bool] = None, + mesh: tuple[int, int, int] | None = None, + write_pdos: bool | None = None, **kwargs, ) -> None: """ @@ -786,9 +788,9 @@ def calc_pdos( def write_pdos( self, *, - pdos_file: Optional[PathLike] = None, - plot_to_file: Optional[bool] = None, - plot_file: Optional[PathLike] = None, + pdos_file: PathLike | None = None, + plot_to_file: bool | None = None, + plot_file: PathLike | None = None, ) -> None: """ Write results of PDOS calculation. diff --git a/janus_core/calculations/single_point.py b/janus_core/calculations/single_point.py index a4171f54..a182e530 100644 --- a/janus_core/calculations/single_point.py +++ b/janus_core/calculations/single_point.py @@ -1,7 +1,9 @@ """Prepare and perform single point calculations.""" +from __future__ import annotations + from collections.abc import Sequence -from typing import Any, Optional, get_args +from typing import Any, get_args from ase import Atoms from numpy import ndarray @@ -80,21 +82,21 @@ class SinglePoint(BaseCalculation): def __init__( self, *, - struct: Optional[MaybeSequence[Atoms]] = None, - struct_path: Optional[PathLike] = None, + struct: MaybeSequence[Atoms] | None = None, + struct_path: PathLike | None = None, arch: Architectures = "mace_mp", device: Devices = "cpu", - model_path: Optional[PathLike] = None, - read_kwargs: Optional[ASEReadArgs] = None, - calc_kwargs: Optional[dict[str, Any]] = None, - set_calc: Optional[bool] = None, + model_path: PathLike | None = None, + read_kwargs: ASEReadArgs | None = None, + calc_kwargs: dict[str, Any] | None = None, + set_calc: bool | None = None, attach_logger: bool = False, - log_kwargs: Optional[dict[str, Any]] = None, + log_kwargs: dict[str, Any] | None = None, track_carbon: bool = True, - tracker_kwargs: Optional[dict[str, Any]] = None, + tracker_kwargs: dict[str, Any] | None = None, properties: MaybeSequence[Properties] = (), write_results: bool = False, - write_kwargs: Optional[OutputKwargs] = None, + write_kwargs: OutputKwargs | None = None, ) -> None: """ Read the structure being simulated and attach an MLIP calculator. diff --git a/janus_core/cli/descriptors.py b/janus_core/cli/descriptors.py index a81a3f25..dbebd3ce 100644 --- a/janus_core/cli/descriptors.py +++ b/janus_core/cli/descriptors.py @@ -1,5 +1,10 @@ +# ruff: noqa: I002, FA100 """Set up MLIP descriptors commandline interface.""" +# Issues with future annotations and typer +# c.f. https://github.com/fastapi/typer/discussions/802 +# from __future__ import annotations + from pathlib import Path from typing import Annotated, Optional diff --git a/janus_core/cli/eos.py b/janus_core/cli/eos.py index c55be86e..33d7387f 100644 --- a/janus_core/cli/eos.py +++ b/janus_core/cli/eos.py @@ -1,5 +1,10 @@ +# ruff: noqa: I002, FA100 """Set up eos commandline interface.""" +# Issues with future annotations and typer +# c.f. https://github.com/fastapi/typer/discussions/802 +# from __future__ import annotations + from pathlib import Path from typing import Annotated, Optional, get_args diff --git a/janus_core/cli/geomopt.py b/janus_core/cli/geomopt.py index 5b76a529..de218804 100644 --- a/janus_core/cli/geomopt.py +++ b/janus_core/cli/geomopt.py @@ -1,5 +1,10 @@ +# ruff: noqa: I002, FA100 """Set up geomopt commandline interface.""" +# Issues with future annotations and typer +# c.f. https://github.com/fastapi/typer/discussions/802 +# from __future__ import annotations + from pathlib import Path from typing import Annotated, Any, Optional diff --git a/janus_core/cli/janus.py b/janus_core/cli/janus.py index ecb3dcd1..9d33da80 100644 --- a/janus_core/cli/janus.py +++ b/janus_core/cli/janus.py @@ -1,5 +1,10 @@ +# ruff: noqa: I002, FA100 """Set up commandline interface.""" +# Issues with future annotations and typer +# c.f. https://github.com/fastapi/typer/discussions/802 +# from __future__ import annotations + from typing import Annotated from typer import Exit, Option, Typer diff --git a/janus_core/cli/md.py b/janus_core/cli/md.py index 5bab22ef..74845d26 100644 --- a/janus_core/cli/md.py +++ b/janus_core/cli/md.py @@ -1,5 +1,10 @@ +# ruff: noqa: I002, FA100 """Set up md commandline interface.""" +# Issues with future annotations and typer +# c.f. https://github.com/fastapi/typer/discussions/802 +# from __future__ import annotations + from pathlib import Path from typing import Annotated, Optional, get_args diff --git a/janus_core/cli/phonons.py b/janus_core/cli/phonons.py index 03dbf309..64a362df 100644 --- a/janus_core/cli/phonons.py +++ b/janus_core/cli/phonons.py @@ -1,5 +1,10 @@ +# ruff: noqa: I002, FA100 """Set up phonons commandline interface.""" +# Issues with future annotations and typer +# c.f. https://github.com/fastapi/typer/discussions/802 +# from __future__ import annotations + from pathlib import Path from typing import Annotated, Optional diff --git a/janus_core/cli/singlepoint.py b/janus_core/cli/singlepoint.py index ead77ed7..602f5adf 100644 --- a/janus_core/cli/singlepoint.py +++ b/janus_core/cli/singlepoint.py @@ -1,5 +1,10 @@ +# ruff: noqa: I002, FA100 """Set up singlepoint commandline interface.""" +# Issues with future annotations and typer +# c.f. https://github.com/fastapi/typer/discussions/802 +# from __future__ import annotations + from pathlib import Path from typing import Annotated, Optional diff --git a/janus_core/cli/train.py b/janus_core/cli/train.py index 6098e63c..1608c89b 100644 --- a/janus_core/cli/train.py +++ b/janus_core/cli/train.py @@ -1,5 +1,10 @@ +# noqa: I002, FA102 """Set up MLIP training commandline interface.""" +# Issues with future annotations and typer +# c.f. https://github.com/fastapi/typer/discussions/802 +# from __future__ import annotations + from pathlib import Path from typing import Annotated diff --git a/janus_core/helpers/janus_types.py b/janus_core/helpers/janus_types.py index 8adc3400..708c7845 100644 --- a/janus_core/helpers/janus_types.py +++ b/janus_core/helpers/janus_types.py @@ -1,5 +1,7 @@ """Module containing types used in Janus-Core.""" +from __future__ import annotations + from collections.abc import Collection, Sequence from enum import Enum import logging @@ -35,9 +37,9 @@ class ASEReadArgs(TypedDict, total=False): """Main arguments for ase.io.read.""" - filename: Union[str, PurePath, IO] - index: Union[int, slice, str] - format: Optional[str] + filename: str | PurePath | IO + index: int | slice | str + format: str | None parallel: bool do_not_split_by_at_sign: bool @@ -45,9 +47,9 @@ class ASEReadArgs(TypedDict, total=False): class ASEWriteArgs(TypedDict, total=False): """Main arguments for ase.io.write.""" - filename: Union[str, PurePath, IO] + filename: str | PurePath | IO images: MaybeSequence[Atoms] - format: Optional[str] + format: str | None parallel: bool append: bool @@ -55,9 +57,9 @@ class ASEWriteArgs(TypedDict, total=False): class ASEOptArgs(TypedDict, total=False): """Main arguments for ase optimisers.""" - restart: Optional[bool] - logfile: Optional[PathLike] - trajectory: Optional[str] + restart: bool | None + logfile: PathLike | None + trajectory: str | None class PostProcessKwargs(TypedDict, total=False): @@ -67,21 +69,21 @@ class PostProcessKwargs(TypedDict, total=False): rdf_compute: bool rdf_rmax: float rdf_nbins: int - rdf_elements: MaybeSequence[Union[str, int]] + rdf_elements: MaybeSequence[str | int] rdf_by_elements: bool rdf_start: int - rdf_stop: Optional[int] + rdf_stop: int | None rdf_step: int - rdf_output_file: Optional[str] + rdf_output_file: str | None # VAF vaf_compute: bool vaf_velocities: bool vaf_fft: bool vaf_atoms: Sequence[Sequence[int]] vaf_start: int - vaf_stop: Optional[int] + vaf_stop: int | None vaf_step: int - vaf_output_file: Optional[PathLike] + vaf_output_file: PathLike | None @runtime_checkable @@ -107,9 +109,9 @@ class CorrelationKwargs(TypedDict, total=True): """Arguments for on-the-fly correlations .""" #: observable a in , with optional args and kwargs - a: Union[Observable, tuple[Observable, tuple, dict]] + a: Observable | tuple[Observable, tuple, dict] #: observable b in , with optional args and kwargs - b: Union[Observable, tuple[Observable, tuple, dict]] + b: Observable | tuple[Observable, tuple, dict] #: name used for correlation in output name: str #: blocks used in multi-tau algorithm diff --git a/janus_core/helpers/log.py b/janus_core/helpers/log.py index c61ee35e..cfa9845d 100644 --- a/janus_core/helpers/log.py +++ b/janus_core/helpers/log.py @@ -1,8 +1,10 @@ """Configure logger with yaml-styled format.""" +from __future__ import annotations + import json import logging -from typing import Literal, Optional +from typing import Literal from codecarbon import OfflineEmissionsTracker from codecarbon.output import LoggerOutput @@ -98,12 +100,12 @@ def format(self, record: logging.LogRecord) -> str: def config_logger( name: str, - filename: Optional[str] = None, + filename: str | None = None, level: LogLevel = logging.INFO, capture_warnings: bool = True, filemode: Literal["r", "w", "a", "x", "r+", "w+", "a+", "x+"] = "w", force: bool = True, -) -> Optional[logging.Logger]: +) -> logging.Logger | None: """ Configure logger with yaml-styled format. @@ -152,13 +154,13 @@ def config_logger( def config_tracker( - janus_logger: Optional[logging.Logger], + janus_logger: logging.Logger | None, track_carbon: bool = True, *, country_iso_code: str = "GBR", save_to_file: bool = False, log_level: Literal["debug", "info", "warning", "error", "critical"] = "critical", -) -> Optional[OfflineEmissionsTracker]: +) -> OfflineEmissionsTracker | None: """ Configure codecarbon tracker to log outputs. diff --git a/janus_core/helpers/stats.py b/janus_core/helpers/stats.py index 14ed89cc..f0f5fbb4 100644 --- a/janus_core/helpers/stats.py +++ b/janus_core/helpers/stats.py @@ -1,5 +1,7 @@ """Module that reads the md stats output timeseries.""" +from __future__ import annotations + from collections.abc import Iterator from functools import singledispatchmethod import re diff --git a/janus_core/helpers/struct_io.py b/janus_core/helpers/struct_io.py index 87e4049f..bf877610 100644 --- a/janus_core/helpers/struct_io.py +++ b/janus_core/helpers/struct_io.py @@ -1,10 +1,12 @@ """Module for functions for input and output of structures.""" +from __future__ import annotations + from collections.abc import Collection, Sequence from copy import copy import logging from pathlib import Path -from typing import Any, Optional, get_args +from typing import Any, get_args from ase import Atoms from ase.io import read, write @@ -67,8 +69,8 @@ def attach_calculator( *, arch: Architectures = "mace_mp", device: Devices = "cpu", - model_path: Optional[PathLike] = None, - calc_kwargs: Optional[dict[str, Any]] = None, + model_path: PathLike | None = None, + calc_kwargs: dict[str, Any] | None = None, ) -> None: """ Configure calculator and attach to structure(s). @@ -103,17 +105,17 @@ def attach_calculator( def input_structs( - struct: Optional[MaybeSequence[Atoms]] = None, + struct: MaybeSequence[Atoms] | None = None, *, - struct_path: Optional[PathLike] = None, - read_kwargs: Optional[ASEReadArgs] = None, + struct_path: PathLike | None = None, + read_kwargs: ASEReadArgs | None = None, sequence_allowed: bool = True, arch: Architectures = "mace_mp", device: Devices = "cpu", - model_path: Optional[PathLike] = None, - calc_kwargs: Optional[dict[str, Any]] = None, - set_calc: Optional[bool] = None, - logger: Optional[logging.Logger] = None, + model_path: PathLike | None = None, + calc_kwargs: dict[str, Any] | None = None, + set_calc: bool | None = None, + logger: logging.Logger | None = None, ) -> MaybeSequence[Atoms]: """ Read input structures and/or attach MLIP calculators. @@ -216,12 +218,12 @@ def input_structs( def output_structs( images: MaybeSequence[Atoms], *, - struct_path: Optional[PathLike] = None, + struct_path: PathLike | None = None, set_info: bool = True, write_results: bool = False, properties: Collection[Properties] = (), invalidate_calc: bool = False, - write_kwargs: Optional[ASEWriteArgs] = None, + write_kwargs: ASEWriteArgs | None = None, ) -> None: """ Copy or move calculated results to Atoms.info dict and/or write structures to file. diff --git a/janus_core/helpers/utils.py b/janus_core/helpers/utils.py index 08557257..46f140a1 100644 --- a/janus_core/helpers/utils.py +++ b/janus_core/helpers/utils.py @@ -1,10 +1,12 @@ """Utility functions for janus_core.""" +from __future__ import annotations + from abc import ABC from collections.abc import Generator, Iterable, Sequence from io import StringIO from pathlib import Path -from typing import Any, Literal, Optional, TextIO, Union +from typing import Any, Literal, TextIO from ase import Atoms from rich.progress import ( @@ -28,9 +30,9 @@ class FileNameMixin(ABC): # noqa: B024 (abstract-base-class-without-abstract-me struct : MaybeSequence[Atoms] Structure from which to derive the default name. If `struct` is a sequence, the first structure will be used. - struct_path : Optional[PathLike] + struct_path : PathLike | None Path to file containing structures. - file_prefix : Optional[PathLike] + file_prefix : PathLike | None Default prefix to use. *additional Components to add to default file_prefix (joined by hyphens). @@ -47,8 +49,8 @@ class FileNameMixin(ABC): # noqa: B024 (abstract-base-class-without-abstract-me def __init__( self, struct: MaybeSequence[Atoms], - struct_path: Optional[PathLike], - file_prefix: Optional[PathLike], + struct_path: PathLike | None, + file_prefix: PathLike | None, *additional, ) -> None: """ @@ -59,9 +61,9 @@ def __init__( struct : MaybeSequence[Atoms] Structure(s) from which to derive the default name. If `struct` is a sequence, the first structure will be used. - struct_path : Optional[PathLike] + struct_path : PathLike | None Path to file structures were read from. Used as default prefix is not None. - file_prefix : Optional[PathLike] + file_prefix : PathLike | None Default prefix to use. *additional Components to add to default file_prefix (joined by hyphens). @@ -72,9 +74,9 @@ def __init__( @staticmethod def _get_default_prefix( - file_prefix: Optional[PathLike], + file_prefix: PathLike | None, struct: MaybeSequence[Atoms], - struct_path: Optional[PathLike], + struct_path: PathLike | None, *additional, ) -> str: """ @@ -82,12 +84,12 @@ def _get_default_prefix( Parameters ---------- - file_prefix : Optional[PathLike] + file_prefix : PathLike | None Given file_prefix. struct : MaybeSequence[Atoms] Structure(s) from which to derive the default name. If `struct` is a sequence, the first structure will be used. - struct_path : Optional[PathLike] + struct_path : PathLike | None Path to file containing structures. *additional Components to add to default file_prefix (joined by hyphens). @@ -114,8 +116,8 @@ def _build_filename( self, suffix: str, *additional, - filename: Optional[PathLike] = None, - prefix_override: Optional[str] = None, + filename: PathLike | None = None, + prefix_override: str | None = None, ) -> Path: """ Set filename using the file prefix and suffix if not specified otherwise. @@ -126,7 +128,7 @@ def _build_filename( Default suffix to use if `filename` is not specified. *additional Extra components to add to suffix (joined with hyphens). - filename : Optional[PathLike] + filename : PathLike | None Filename to use, if specified. Default is None. prefix_override : Optional[str] Replace file_prefix if not None. @@ -149,7 +151,7 @@ def _build_filename( return built_filename -def none_to_dict(dictionaries: Sequence[Optional[dict]]) -> Generator[dict, None, None]: +def none_to_dict(dictionaries: Sequence[dict | None]) -> Generator[dict, None, None]: """ Ensure dictionaries that may be None are dictionaries. @@ -168,13 +170,13 @@ def none_to_dict(dictionaries: Sequence[Optional[dict]]) -> Generator[dict, None def write_table( fmt: Literal["ascii", "csv"], - file: Optional[TextIO] = None, - units: Optional[dict[str, str]] = None, - formats: Optional[dict[str, str]] = None, + file: TextIO | None = None, + units: dict[str, str] | None = None, + formats: dict[str, str] | None = None, *, print_header: bool = True, **columns, -) -> Optional[StringIO]: +) -> StringIO | None: """ Dump a table in a standard format. @@ -368,7 +370,7 @@ def _dump_csv( print(",".join(map(format, cols, formats)), file=file) -def track_progress(sequence: Union[Sequence, Iterable], description: str) -> Iterable: +def track_progress(sequence: Sequence | Iterable, description: str) -> Iterable: """ Track the progress of iterating over a sequence. diff --git a/janus_core/processing/correlator.py b/janus_core/processing/correlator.py index f38e1241..39752a82 100644 --- a/janus_core/processing/correlator.py +++ b/janus_core/processing/correlator.py @@ -1,7 +1,8 @@ """Module to correlate scalar data on-the-fly.""" +from __future__ import annotations + from collections.abc import Iterable -from typing import Union from ase import Atoms import numpy as np @@ -196,8 +197,8 @@ class Correlation: def __init__( self, - a: Union[Observable, tuple[Observable, tuple, dict]], - b: Union[Observable, tuple[Observable, tuple, dict]], + a: Observable | tuple[Observable, tuple, dict], + b: Observable | tuple[Observable, tuple, dict], name: str, blocks: int, points: int, diff --git a/janus_core/processing/observables.py b/janus_core/processing/observables.py index daec7840..32eee246 100644 --- a/janus_core/processing/observables.py +++ b/janus_core/processing/observables.py @@ -1,5 +1,7 @@ """Module for built-in correlation observables.""" +from __future__ import annotations + from ase import Atoms, units diff --git a/janus_core/processing/post_process.py b/janus_core/processing/post_process.py index 9fe7edf0..08af0e8d 100644 --- a/janus_core/processing/post_process.py +++ b/janus_core/processing/post_process.py @@ -1,8 +1,9 @@ """Module for post-processing trajectories.""" +from __future__ import annotations + from collections.abc import Sequence from itertools import combinations_with_replacement -from typing import Optional, Union from ase import Atoms from ase.geometry.analysis import Analysis @@ -45,17 +46,17 @@ def _process_index(index: SliceLike) -> StartStopStep: def compute_rdf( data: MaybeSequence[Atoms], - ana: Optional[Analysis] = None, + ana: Analysis | None = None, /, *, - filenames: Optional[MaybeSequence[PathLike]] = None, + filenames: MaybeSequence[PathLike] | None = None, by_elements: bool = False, rmax: float = 2.5, nbins: int = 50, - elements: Optional[MaybeSequence[Union[int, str]]] = None, + elements: MaybeSequence[int | str] | None = None, index: SliceLike = (0, None, 1), - volume: Optional[float] = None, -) -> Union[NDArray[float64], dict[tuple[str, str], NDArray[float64]]]: + volume: float | None = None, +) -> NDArray[float64] | dict[tuple[str, str] | NDArray[float64]]: """ Compute the rdf of data. @@ -184,12 +185,12 @@ def compute_rdf( def compute_vaf( data: Sequence[Atoms], - filenames: Optional[MaybeSequence[PathLike]] = None, + filenames: MaybeSequence[PathLike] | None = None, *, use_velocities: bool = False, fft: bool = False, index: SliceLike = (0, None, 1), - filter_atoms: MaybeSequence[MaybeSequence[Optional[int]]] = ((None),), + filter_atoms: MaybeSequence[MaybeSequence[int | None]] = ((None),), time_step: float = 1.0, ) -> tuple[NDArray[float64], list[NDArray[float64]]]: """ diff --git a/janus_core/processing/symmetry.py b/janus_core/processing/symmetry.py index 058e2d38..1adb332b 100644 --- a/janus_core/processing/symmetry.py +++ b/janus_core/processing/symmetry.py @@ -1,5 +1,7 @@ """Module for functions operating on structure symmetry.""" +from __future__ import annotations + from ase import Atoms from ase.spacegroup.symmetrize import refine_symmetry from spglib import get_spacegroup diff --git a/janus_core/training/train.py b/janus_core/training/train.py index 6f9f1d20..6962fce6 100644 --- a/janus_core/training/train.py +++ b/janus_core/training/train.py @@ -1,7 +1,9 @@ """Train MLIP.""" +from __future__ import annotations + from pathlib import Path -from typing import Any, Optional +from typing import Any try: from mace.cli.run_train import run as run_train @@ -40,11 +42,11 @@ def check_files_exist(config: dict, req_file_keys: list[PathLike]) -> None: def train( mlip_config: PathLike, - req_file_keys: Optional[list[PathLike]] = None, + req_file_keys: list[PathLike] | None = None, attach_logger: bool = False, - log_kwargs: Optional[dict[str, Any]] = None, + log_kwargs: dict[str, Any] | None = None, track_carbon: bool = True, - tracker_kwargs: Optional[dict[str, Any]] = None, + tracker_kwargs: dict[str, Any] | None = None, ) -> None: """ Run training for MLIP by passing a configuration file to the MLIP's CLI. diff --git a/pyproject.toml b/pyproject.toml index 2f5451c5..5beba1b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -115,7 +115,7 @@ select = [ # pycodestyle "E", "W", # Pyflakes - "F", + "F", "FA", # pyupgrade "I", # pep8-naming @@ -126,6 +126,7 @@ select = [ [tool.ruff.lint.isort] force-sort-within-sections = true +required-imports = ["from __future__ import annotations"] [tool.ruff.lint.pydocstyle] convention = "numpy" diff --git a/tests/__init__.py b/tests/__init__.py index 431588a3..647c1f33 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,5 +1,7 @@ """Tests for janus_core.""" +from __future__ import annotations + from pathlib import Path TEST_DIR = Path(__file__).resolve().parent diff --git a/tests/test_cli.py b/tests/test_cli.py index 9330d586..d8fe5ce1 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,5 +1,7 @@ """Test commandline interface with no sub-commands.""" +from __future__ import annotations + from typer.testing import CliRunner from janus_core.cli.janus import app diff --git a/tests/test_correlator.py b/tests/test_correlator.py index 39bdeaf5..91b390e5 100644 --- a/tests/test_correlator.py +++ b/tests/test_correlator.py @@ -1,5 +1,7 @@ """Test the Correlator.""" +from __future__ import annotations + from collections.abc import Iterable from pathlib import Path diff --git a/tests/test_descriptors.py b/tests/test_descriptors.py index c1e1ddc6..2006b30d 100644 --- a/tests/test_descriptors.py +++ b/tests/test_descriptors.py @@ -1,5 +1,7 @@ """Test MLIP desriptors calculations.""" +from __future__ import annotations + from pathlib import Path from ase import Atoms diff --git a/tests/test_descriptors_cli.py b/tests/test_descriptors_cli.py index 3215770e..cbec3b31 100644 --- a/tests/test_descriptors_cli.py +++ b/tests/test_descriptors_cli.py @@ -1,5 +1,7 @@ """Test descriptors commandline interface.""" +from __future__ import annotations + from pathlib import Path from ase.io import read diff --git a/tests/test_eos.py b/tests/test_eos.py index 804d4949..d02cd658 100644 --- a/tests/test_eos.py +++ b/tests/test_eos.py @@ -1,5 +1,7 @@ """Test equation of state calculations.""" +from __future__ import annotations + from pathlib import Path from ase.eos import EquationOfState diff --git a/tests/test_eos_cli.py b/tests/test_eos_cli.py index 6e059100..2a21387d 100644 --- a/tests/test_eos_cli.py +++ b/tests/test_eos_cli.py @@ -1,5 +1,7 @@ """Test eos commandline interface.""" +from __future__ import annotations + from pathlib import Path from ase.io import read diff --git a/tests/test_filenamemixin.py b/tests/test_filenamemixin.py index 11b15f2e..2de75d91 100644 --- a/tests/test_filenamemixin.py +++ b/tests/test_filenamemixin.py @@ -1,5 +1,7 @@ """Test FileNameMixin functions.""" +from __future__ import annotations + from pathlib import Path from ase.io import read diff --git a/tests/test_geom_opt.py b/tests/test_geom_opt.py index c42a7641..a37621f3 100644 --- a/tests/test_geom_opt.py +++ b/tests/test_geom_opt.py @@ -1,5 +1,7 @@ """Test geometry optimization.""" +from __future__ import annotations + from pathlib import Path from ase.filters import FrechetCellFilter, UnitCellFilter diff --git a/tests/test_geomopt_cli.py b/tests/test_geomopt_cli.py index 9b25a265..dd2769f6 100644 --- a/tests/test_geomopt_cli.py +++ b/tests/test_geomopt_cli.py @@ -1,5 +1,7 @@ """Test geomopt commandline interface.""" +from __future__ import annotations + from pathlib import Path from ase import Atoms diff --git a/tests/test_janus.py b/tests/test_janus.py index 7172f147..ae54e012 100644 --- a/tests/test_janus.py +++ b/tests/test_janus.py @@ -1,5 +1,7 @@ """Test importing janus_core.""" +from __future__ import annotations + import janus_core diff --git a/tests/test_log.py b/tests/test_log.py index 5a176272..63a9593c 100644 --- a/tests/test_log.py +++ b/tests/test_log.py @@ -1,5 +1,7 @@ """Test logging module.""" +from __future__ import annotations + import yaml from janus_core.helpers.log import config_logger diff --git a/tests/test_md.py b/tests/test_md.py index ef815767..2373c52f 100644 --- a/tests/test_md.py +++ b/tests/test_md.py @@ -1,5 +1,7 @@ """Test molecular dynamics.""" +from __future__ import annotations + from pathlib import Path from ase import Atoms diff --git a/tests/test_md_cli.py b/tests/test_md_cli.py index 5c6d441a..ef6bfe20 100644 --- a/tests/test_md_cli.py +++ b/tests/test_md_cli.py @@ -1,5 +1,7 @@ """Test md commandline interface.""" +from __future__ import annotations + from pathlib import Path from ase import Atoms diff --git a/tests/test_mlip_calculators.py b/tests/test_mlip_calculators.py index 6d1c7754..b7c21d23 100644 --- a/tests/test_mlip_calculators.py +++ b/tests/test_mlip_calculators.py @@ -1,5 +1,7 @@ """Test configuration of MLIP calculators.""" +from __future__ import annotations + from pathlib import Path from zipfile import BadZipFile diff --git a/tests/test_phonons.py b/tests/test_phonons.py index 9644ce01..f36bc625 100644 --- a/tests/test_phonons.py +++ b/tests/test_phonons.py @@ -1,5 +1,7 @@ """Test phonons calculations.""" +from __future__ import annotations + from pathlib import Path from ase.io import read diff --git a/tests/test_phonons_cli.py b/tests/test_phonons_cli.py index 4764ebbb..e15dd66a 100644 --- a/tests/test_phonons_cli.py +++ b/tests/test_phonons_cli.py @@ -1,5 +1,7 @@ """Test phonons commandline interface.""" +from __future__ import annotations + from pathlib import Path import pytest diff --git a/tests/test_post_process.py b/tests/test_post_process.py index b1a3a600..5858627a 100644 --- a/tests/test_post_process.py +++ b/tests/test_post_process.py @@ -1,5 +1,7 @@ """Test post processing.""" +from __future__ import annotations + from pathlib import Path from ase.io import read diff --git a/tests/test_single_point.py b/tests/test_single_point.py index cf998d8d..c2bc538d 100644 --- a/tests/test_single_point.py +++ b/tests/test_single_point.py @@ -1,5 +1,7 @@ """Test configuration of MLIP calculators.""" +from __future__ import annotations + from pathlib import Path from ase.io import read diff --git a/tests/test_singlepoint_cli.py b/tests/test_singlepoint_cli.py index b5f89958..f3efde63 100644 --- a/tests/test_singlepoint_cli.py +++ b/tests/test_singlepoint_cli.py @@ -1,5 +1,7 @@ """Test singlepoint commandline interface.""" +from __future__ import annotations + from pathlib import Path from ase import Atoms diff --git a/tests/test_stats.py b/tests/test_stats.py index 443a180d..c11b1f09 100644 --- a/tests/test_stats.py +++ b/tests/test_stats.py @@ -1,5 +1,7 @@ """Test stats reader.""" +from __future__ import annotations + from pathlib import Path import pytest diff --git a/tests/test_train_cli.py b/tests/test_train_cli.py index ddde4d20..1db86763 100644 --- a/tests/test_train_cli.py +++ b/tests/test_train_cli.py @@ -1,5 +1,7 @@ """Test train commandline interface.""" +from __future__ import annotations + import logging from pathlib import Path import shutil diff --git a/tests/test_utils.py b/tests/test_utils.py index a4130834..c000b6af 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,5 +1,7 @@ """Test utility functions.""" +from __future__ import annotations + from pathlib import Path from ase import Atoms diff --git a/tests/utils.py b/tests/utils.py index fd26eaee..acd0188b 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,9 +1,10 @@ """Utility functions for tests.""" +from __future__ import annotations + import logging from pathlib import Path import re -from typing import Union from ase import Atoms from ase.io import read @@ -12,7 +13,7 @@ from janus_core.helpers.janus_types import MaybeSequence, PathLike -def read_atoms(path: Path) -> Union[Atoms, None]: +def read_atoms(path: Path) -> Atoms | None: """ Read Atoms structure file, and delete file regardless of success.