diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 381303b5..47ed7557 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: timeout-minutes: 30 strategy: matrix: - python-version: ['3.9','3.10','3.11','3.12'] + python-version: ["3.10","3.11","3.12"] steps: - uses: actions/checkout@v4 @@ -27,9 +27,7 @@ jobs: env: # show timings of tests PYTEST_ADDOPTS: "--durations=0" - run: | - unset CI - uv run pytest --run-extra-mlips --cov janus_core --cov-append . + run: uv run pytest --run-extra-mlips --cov janus_core --cov-append . - name: Report coverage to Coveralls uses: coverallsapp/github-action@v2 diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index f296f151..3e754e8c 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -10,7 +10,7 @@ jobs: timeout-minutes: 30 strategy: matrix: - python-version: ['3.9','3.10','3.11','3.12'] + python-version: ["3.10","3.11","3.12"] steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index 1cca1073..2f29d2cf 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Tools for machine learnt interatomic potentials `janus-core` dependencies currently include: -- Python >= 3.9 +- Python >= 3.10 - ASE >= 3.23 - mace-torch = 0.3.8 - chgnet = 0.3.8 (optional) diff --git a/docs/source/getting_started/getting_started.rst b/docs/source/getting_started/getting_started.rst index 564e29e5..5efc7ca0 100644 --- a/docs/source/getting_started/getting_started.rst +++ b/docs/source/getting_started/getting_started.rst @@ -7,7 +7,7 @@ Dependencies ``janus-core`` dependencies currently include: -- Python >= 3.9 +- Python >= 3.10 - ASE >= 3.23 - mace-torch = 0.3.8 - chgnet = 0.3.8 (optional) diff --git a/janus_core/calculations/eos.py b/janus_core/calculations/eos.py index c2423e00..cb80b152 100644 --- a/janus_core/calculations/eos.py +++ b/janus_core/calculations/eos.py @@ -322,7 +322,9 @@ def run(self) -> EoSResults: if self.write_results: with open(f"{self.file_prefix}-eos-raw.dat", "w", encoding="utf8") as out: print("#Lattice Scalar | Energy [eV] | Volume [Å^3] ", file=out) - for eos_data in zip(self.lattice_scalars, self.energies, self.volumes): + for eos_data in zip( + self.lattice_scalars, self.energies, self.volumes, strict=True + ): print(*eos_data, file=out) eos = EquationOfState(self.volumes, self.energies, self.eos_type) diff --git a/janus_core/calculations/geom_opt.py b/janus_core/calculations/geom_opt.py index 46c4f2ba..e3714e7c 100644 --- a/janus_core/calculations/geom_opt.py +++ b/janus_core/calculations/geom_opt.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Any, Callable +from collections.abc import Callable +from typing import Any import warnings from ase import Atoms, filters, units diff --git a/janus_core/cli/descriptors.py b/janus_core/cli/descriptors.py index eccedc36..a7f9defd 100644 --- a/janus_core/cli/descriptors.py +++ b/janus_core/cli/descriptors.py @@ -1,12 +1,9 @@ -# ruff: noqa: I002, FA100 """Set up MLIP descriptors commandline interface.""" -# Issues with future annotations and typer -# c.f. https://github.com/maxb2/typer-config/issues/295 -# from __future__ import annotations +from __future__ import annotations from pathlib import Path -from typing import Annotated, Optional +from typing import Annotated from typer import Context, Option, Typer from typer_config import use_config @@ -49,7 +46,7 @@ def descriptors( device: Device = "cpu", model_path: ModelPath = None, out: Annotated[ - Optional[Path], + Path | None, Option( help=( "Path to save structure with calculated descriptors. Default is " diff --git a/janus_core/cli/eos.py b/janus_core/cli/eos.py index 047c7398..96841243 100644 --- a/janus_core/cli/eos.py +++ b/janus_core/cli/eos.py @@ -1,12 +1,9 @@ -# ruff: noqa: I002, FA100 """Set up eos commandline interface.""" -# Issues with future annotations and typer -# c.f. https://github.com/maxb2/typer-config/issues/295 -# from __future__ import annotations +from __future__ import annotations from pathlib import Path -from typing import Annotated, Optional, get_args +from typing import Annotated, get_args from typer import Context, Option, Typer from typer_config import use_config @@ -66,7 +63,7 @@ def eos( read_kwargs: ReadKwargsLast = None, calc_kwargs: CalcKwargs = None, file_prefix: Annotated[ - Optional[Path], + Path | None, Option( help=( """ diff --git a/janus_core/cli/geomopt.py b/janus_core/cli/geomopt.py index 5a504b4f..5a2a6aee 100644 --- a/janus_core/cli/geomopt.py +++ b/janus_core/cli/geomopt.py @@ -1,12 +1,9 @@ -# ruff: noqa: I002, FA100 """Set up geomopt commandline interface.""" -# Issues with future annotations and typer -# c.f. https://github.com/maxb2/typer-config/issues/295 -# from __future__ import annotations +from __future__ import annotations from pathlib import Path -from typing import Annotated, Any, Optional +from typing import Annotated, Any from typer import Context, Option, Typer from typer_config import use_config @@ -30,7 +27,7 @@ def _set_minimize_kwargs( minimize_kwargs: dict[str, Any], - traj: Optional[str], + traj: str | None, opt_cell_lengths: bool, pressure: float, ) -> None: @@ -91,7 +88,7 @@ def geomopt( ctx: Context, struct: StructPath, optimizer: Annotated[ - Optional[str], + str | None, Option(help="Name of ASE optimizer function to use."), ] = "LBFGS", fmax: Annotated[ @@ -112,7 +109,7 @@ def geomopt( ), ] = False, filter_func: Annotated[ - Optional[str], + str | None, Option( help=( "Name of ASE filter/constraint function to use. If using " @@ -134,7 +131,7 @@ def geomopt( ), ] = 0.001, out: Annotated[ - Optional[Path], + Path | None, Option( help=( "Path to save optimized structure. Default is inferred from name " diff --git a/janus_core/cli/janus.py b/janus_core/cli/janus.py index 1a4f3732..66a61943 100644 --- a/janus_core/cli/janus.py +++ b/janus_core/cli/janus.py @@ -1,9 +1,6 @@ -# ruff: noqa: I002, FA100 """Set up commandline interface.""" -# Issues with future annotations and typer -# c.f. https://github.com/maxb2/typer-config/issues/295 -# from __future__ import annotations +from __future__ import annotations from typing import Annotated @@ -39,7 +36,7 @@ def print_version( version: Annotated[ bool, Option("--version", help="Print janus version and exit.") ] = None, -) -> False: +) -> None: """ Print current janus-core version and exit. diff --git a/janus_core/cli/md.py b/janus_core/cli/md.py index 515d86fd..eb435ace 100644 --- a/janus_core/cli/md.py +++ b/janus_core/cli/md.py @@ -1,12 +1,9 @@ -# ruff: noqa: I002, FA100 """Set up md commandline interface.""" -# Issues with future annotations and typer -# c.f. https://github.com/maxb2/typer-config/issues/295 -# from __future__ import annotations +from __future__ import annotations from pathlib import Path -from typing import Annotated, Optional, get_args +from typing import Annotated, get_args from typer import Context, Option, Typer from typer_config import use_config @@ -101,7 +98,7 @@ def md( int, Option(help="Frequency to rescale velocities during equilibration.") ] = 10, file_prefix: Annotated[ - Optional[Path], + Path | None, Option( help=( """ @@ -116,7 +113,7 @@ def md( bool, Option(help="Whether to infer restart file if restarting dynamics.") ] = True, restart_stem: Annotated[ - Optional[Path], + Path | None, Option(help="Stem for restart file name. Default inferred from `file_prefix`."), ] = None, restart_every: Annotated[ @@ -129,7 +126,7 @@ def md( int, Option(help="Restart files to keep if rotating.") ] = 4, final_file: Annotated[ - Optional[Path], + Path | None, Option( help=( """ @@ -140,7 +137,7 @@ def md( ), ] = None, stats_file: Annotated[ - Optional[Path], + Path | None, Option( help=( """ @@ -152,7 +149,7 @@ def md( ] = None, stats_every: Annotated[int, Option(help="Frequency to output statistics.")] = 100, traj_file: Annotated[ - Optional[Path], + Path | None, Option(help="File to save trajectory. Default inferred from `file_prefix`."), ] = None, traj_append: Annotated[bool, Option(help="Whether to append trajectory.")] = False, @@ -161,23 +158,23 @@ def md( int, Option(help="Frequency of steps to save trajectory.") ] = 100, temp_start: Annotated[ - Optional[float], + float | None, Option(help="Temperature to start heating, in K."), ] = None, temp_end: Annotated[ - Optional[float], + float | None, Option(help="Maximum temperature for heating, in K."), ] = None, temp_step: Annotated[ - Optional[float], Option(help="Size of temperature steps when heating, in K.") + float | None, Option(help="Size of temperature steps when heating, in K.") ] = None, temp_time: Annotated[ - Optional[float], Option(help="Time between heating steps, in fs.") + float | None, Option(help="Time between heating steps, in fs.") ] = None, write_kwargs: WriteKwargs = None, post_process_kwargs: PostProcessKwargs = None, seed: Annotated[ - Optional[int], + int | None, Option(help="Random seed for numpy.random and random functions."), ] = None, log: LogPath = None, diff --git a/janus_core/cli/phonons.py b/janus_core/cli/phonons.py index 85724fb8..02387395 100644 --- a/janus_core/cli/phonons.py +++ b/janus_core/cli/phonons.py @@ -1,12 +1,9 @@ -# ruff: noqa: I002, FA100 """Set up phonons commandline interface.""" -# Issues with future annotations and typer -# c.f. https://github.com/maxb2/typer-config/issues/295 -# from __future__ import annotations +from __future__ import annotations from pathlib import Path -from typing import Annotated, Optional +from typing import Annotated from typer import Context, Option, Typer from typer_config import use_config @@ -67,7 +64,7 @@ def phonons( ), ] = 51, qpoint_file: Annotated[ - Optional[Path], + Path | None, Option( help=( "Path to yaml file with info to generate a path of q-points for band " @@ -125,7 +122,7 @@ def phonons( read_kwargs: ReadKwargsLast = None, calc_kwargs: CalcKwargs = None, file_prefix: Annotated[ - Optional[Path], + Path | None, Option( help=( """ diff --git a/janus_core/cli/preprocess.py b/janus_core/cli/preprocess.py index be1b4ca8..614ffe1f 100644 --- a/janus_core/cli/preprocess.py +++ b/janus_core/cli/preprocess.py @@ -1,9 +1,6 @@ -# noqa: I002, FA102 """Set up MLIP preprocessing commandline interface.""" -# Issues with future annotations and typer -# c.f. https://github.com/maxb2/typer-config/issues/295 -# from __future__ import annotations +from __future__ import annotations from pathlib import Path from typing import Annotated diff --git a/janus_core/cli/singlepoint.py b/janus_core/cli/singlepoint.py index 059edd59..0635ffa6 100644 --- a/janus_core/cli/singlepoint.py +++ b/janus_core/cli/singlepoint.py @@ -1,12 +1,9 @@ -# ruff: noqa: I002, FA100 """Set up singlepoint commandline interface.""" -# Issues with future annotations and typer -# c.f. https://github.com/maxb2/typer-config/issues/295 -# from __future__ import annotations +from __future__ import annotations from pathlib import Path -from typing import Annotated, Optional +from typing import Annotated from typer import Context, Option, Typer from typer_config import use_config @@ -37,7 +34,7 @@ def singlepoint( device: Device = "cpu", model_path: ModelPath = None, properties: Annotated[ - Optional[list[str]], + list[str] | None, Option( help=( "Properties to calculate. If not specified, 'energy', 'forces' " @@ -46,7 +43,7 @@ def singlepoint( ), ] = None, out: Annotated[ - Optional[Path], + Path | None, Option( help=( "Path to save structure with calculated results. Default is inferred " diff --git a/janus_core/cli/train.py b/janus_core/cli/train.py index 197ca272..de687f93 100644 --- a/janus_core/cli/train.py +++ b/janus_core/cli/train.py @@ -1,9 +1,6 @@ -# noqa: I002, FA102 """Set up MLIP training commandline interface.""" -# Issues with future annotations and typer -# c.f. https://github.com/maxb2/typer-config/issues/295 -# from __future__ import annotations +from __future__ import annotations from pathlib import Path from typing import Annotated diff --git a/janus_core/cli/types.py b/janus_core/cli/types.py index b37e8a70..c8e73842 100644 --- a/janus_core/cli/types.py +++ b/janus_core/cli/types.py @@ -4,7 +4,7 @@ import ast from pathlib import Path -from typing import TYPE_CHECKING, Annotated, Optional +from typing import TYPE_CHECKING, Annotated from typer import Option @@ -67,13 +67,13 @@ def __str__(self) -> str: StructPath = Annotated[Path, Option(help="Path of structure to simulate.")] Architecture = Annotated[ - Optional[str], Option(help="MLIP architecture to use for calculations.") + str | None, Option(help="MLIP architecture to use for calculations.") ] -Device = Annotated[Optional[str], Option(help="Device to run calculations on.")] -ModelPath = Annotated[Optional[str], Option(help="Path to MLIP model.")] +Device = Annotated[str | None, Option(help="Device to run calculations on.")] +ModelPath = Annotated[str | None, Option(help="Path to MLIP model.")] ReadKwargsAll = Annotated[ - Optional[TyperDict], + TyperDict | None, Option( parser=parse_dict_class, help=( @@ -88,7 +88,7 @@ def __str__(self) -> str: ] ReadKwargsLast = Annotated[ - Optional[TyperDict], + TyperDict | None, Option( parser=parse_dict_class, help=( @@ -103,7 +103,7 @@ def __str__(self) -> str: ] CalcKwargs = Annotated[ - Optional[TyperDict], + TyperDict | None, Option( parser=parse_dict_class, help=( @@ -118,7 +118,7 @@ def __str__(self) -> str: ] WriteKwargs = Annotated[ - Optional[TyperDict], + TyperDict | None, Option( parser=parse_dict_class, help=( @@ -132,7 +132,7 @@ def __str__(self) -> str: ] OptKwargs = Annotated[ - Optional[TyperDict], + TyperDict | None, Option( parser=parse_dict_class, help=( @@ -146,7 +146,7 @@ def __str__(self) -> str: ] MinimizeKwargs = Annotated[ - Optional[TyperDict], + TyperDict | None, Option( parser=parse_dict_class, help=( @@ -160,7 +160,7 @@ def __str__(self) -> str: ] DoSKwargs = Annotated[ - Optional[TyperDict], + TyperDict | None, Option( parser=parse_dict_class, help=( @@ -174,7 +174,7 @@ def __str__(self) -> str: ] PDoSKwargs = Annotated[ - Optional[TyperDict], + TyperDict | None, Option( parser=parse_dict_class, help=( @@ -188,7 +188,7 @@ def __str__(self) -> str: ] EnsembleKwargs = Annotated[ - Optional[TyperDict], + TyperDict | None, Option( parser=parse_dict_class, help=( @@ -202,7 +202,7 @@ def __str__(self) -> str: ] DisplacementKwargs = Annotated[ - Optional[TyperDict], + TyperDict | None, Option( parser=parse_dict_class, help=( @@ -216,7 +216,7 @@ def __str__(self) -> str: ] PostProcessKwargs = Annotated[ - Optional[TyperDict], + TyperDict | None, Option( parser=parse_dict_class, help=( @@ -230,7 +230,7 @@ def __str__(self) -> str: ] LogPath = Annotated[ - Optional[Path], + Path | None, Option( help=( "Path to save logs to. Default is inferred from the name of the structure " @@ -240,7 +240,7 @@ def __str__(self) -> str: ] Summary = Annotated[ - Optional[Path], + Path | None, Option( help=( "Path to save summary of inputs, start/end time, and carbon emissions. " diff --git a/janus_core/helpers/janus_types.py b/janus_core/helpers/janus_types.py index 4533f4f5..f31a4b8b 100644 --- a/janus_core/helpers/janus_types.py +++ b/janus_core/helpers/janus_types.py @@ -6,7 +6,7 @@ from enum import Enum import logging from pathlib import Path, PurePath -from typing import IO, TYPE_CHECKING, Literal, Optional, TypedDict, TypeVar, Union +from typing import IO, TYPE_CHECKING, Literal, TypedDict, TypeVar from ase import Atoms from ase.eos import EquationOfState @@ -19,11 +19,11 @@ # General T = TypeVar("T") -MaybeList = Union[T, list[T]] -MaybeSequence = Union[T, Sequence[T]] -PathLike = Union[str, Path] -StartStopStep = tuple[Optional[int], Optional[int], int] -SliceLike = Union[slice, range, int, StartStopStep] +MaybeList = T | list[T] +MaybeSequence = T | Sequence[T] +PathLike = str | Path +StartStopStep = tuple[int | None, int | None, int] +SliceLike = slice | range | int | StartStopStep # ASE Arg types diff --git a/janus_core/helpers/mlip_calculators.py b/janus_core/helpers/mlip_calculators.py index 537dc027..6fd2401a 100644 --- a/janus_core/helpers/mlip_calculators.py +++ b/janus_core/helpers/mlip_calculators.py @@ -64,7 +64,7 @@ def _set_model_path( model_path = kwargs.pop(present.pop()) # Convert to path if file/directory exists - if isinstance(model_path, (Path, str)) and Path(model_path).expanduser().exists(): + if isinstance(model_path, Path | str) and Path(model_path).expanduser().exists(): return Path(model_path).expanduser() return model_path diff --git a/janus_core/helpers/stats.py b/janus_core/helpers/stats.py index f0f5fbb4..7ee1cdcf 100644 --- a/janus_core/helpers/stats.py +++ b/janus_core/helpers/stats.py @@ -205,7 +205,7 @@ def data_tags(self) -> Iterator[tuple[str, str]]: Iterator[tuple[str, str]] Zipped labels and units. """ - return zip(self.labels, self.units) + return zip(self.labels, self.units, strict=True) def read(self) -> None: """Read MD stats and store them in `data`.""" diff --git a/janus_core/helpers/struct_io.py b/janus_core/helpers/struct_io.py index 5d6144fd..2d3c0c16 100644 --- a/janus_core/helpers/struct_io.py +++ b/janus_core/helpers/struct_io.py @@ -183,7 +183,7 @@ def input_structs( struct = struct[0] # Check struct is valid type - if not isinstance(struct, (Atoms, Sequence)) or isinstance(struct, str): + if not isinstance(struct, Atoms | Sequence) or isinstance(struct, str): raise ValueError("`struct` must be an ASE Atoms object or sequence of Atoms") # Check struct is valid length diff --git a/janus_core/helpers/utils.py b/janus_core/helpers/utils.py index b1976c32..0c71f4b9 100644 --- a/janus_core/helpers/utils.py +++ b/janus_core/helpers/utils.py @@ -339,7 +339,7 @@ def _dump_ascii( if header: print(f"# {' | '.join(header)}", file=file) - for cols in zip(*columns.values()): + for cols in zip(*columns.values(), strict=True): print(*map(format, cols, formats), file=file) @@ -371,7 +371,7 @@ def _dump_csv( if header: print(",".join(header), file=file) - for cols in zip(*columns.values()): + for cols in zip(*columns.values(), strict=True): print(",".join(map(format, cols, formats)), file=file) @@ -453,7 +453,7 @@ def validate_slicelike(maybe_slicelike: SliceLike) -> None: ValueError If maybe_slicelike is not SliceLike. """ - if isinstance(maybe_slicelike, (slice, range, int)): + if isinstance(maybe_slicelike, slice | range | int): return if isinstance(maybe_slicelike, tuple) and len(maybe_slicelike) == 3: start, stop, step = maybe_slicelike @@ -487,7 +487,7 @@ def slicelike_to_startstopstep(index: SliceLike) -> StartStopStep: return (index, None, 1) return (index, index + 1, 1) - if isinstance(index, (slice, range)): + if isinstance(index, slice | range): return (index.start, index.stop, index.step) return index diff --git a/janus_core/processing/correlator.py b/janus_core/processing/correlator.py index 4328d20d..6461c4c8 100644 --- a/janus_core/processing/correlator.py +++ b/janus_core/processing/correlator.py @@ -326,7 +326,9 @@ def update(self, atoms: Atoms) -> None: Atoms object to observe values from. """ # All pairs of data to be correlated. - value_pairs = zip(self._get_a(atoms).flatten(), self._get_b(atoms).flatten()) + value_pairs = zip( + self._get_a(atoms).flatten(), self._get_b(atoms).flatten(), strict=True + ) if self._correlators is None: # Initialise correlators automatically. self._correlators = [ @@ -335,7 +337,7 @@ def update(self, atoms: Atoms) -> None: ) for _ in range(len(self._get_a(atoms).flatten())) ] - for corr, values in zip(self._correlators, value_pairs): + for corr, values in zip(self._correlators, value_pairs, strict=True): corr.update(*values) def get(self) -> tuple[Iterable[float], Iterable[float]]: diff --git a/janus_core/processing/post_process.py b/janus_core/processing/post_process.py index 2c9a66ff..3e46a92a 100644 --- a/janus_core/processing/post_process.py +++ b/janus_core/processing/post_process.py @@ -122,9 +122,9 @@ def compute_rdf( f"to number of samples ({len(rdf)})" ) - for (dists, rdfs), out_path in zip(rdf.values(), filenames): + for (dists, rdfs), out_path in zip(rdf.values(), filenames, strict=True): with open(out_path, "w", encoding="utf-8") as out_file: - for dist, rdf_i in zip(dists, rdfs): + for dist, rdf_i in zip(dists, rdfs, strict=True): print(dist, rdf_i, file=out_file) else: @@ -152,7 +152,7 @@ def compute_rdf( filenames = filenames[0] with open(filenames, "w", encoding="utf-8") as out_file: - for dist, rdf_i in zip(*rdf): + for dist, rdf_i in zip(*rdf, strict=True): print(dist, rdf_i, file=out_file) return rdf @@ -287,9 +287,9 @@ def compute_vaf( ) if filenames: - for vaf, filename in zip(vafs[1], filenames): + for vaf, filename in zip(vafs[1], filenames, strict=True): with open(filename, "w", encoding="utf-8") as out_file: - for lag, dat in zip(lags, vaf): + for lag, dat in zip(lags, vaf, strict=True): print(lag, dat, file=out_file) return vafs diff --git a/pyproject.toml b/pyproject.toml index 38854ebf..c1e53f36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ authors = [ { name = "Jacob Wilkins" }, { name = "Alin M. Elena" }, ] -requires-python = "<3.13,>=3.9" +requires-python = ">=3.10" classifiers = [ "Programming Language :: Python", "Intended Audience :: Science/Research", @@ -34,7 +34,7 @@ dependencies = [ "torch<=2.2,>=2.1", "torch-dftd==0.4.0", "typer<1.0.0,>=0.12.5", - "typer-config<2.0.0,>=1.4.0", + "typer-config<2.0.0,>=1.4.2", ] [project.optional-dependencies] @@ -107,7 +107,7 @@ source=["janus_core"] [tool.ruff] exclude = ["conf.py"] -target-version = "py39" +target-version = "py310" [tool.ruff.lint] # Ignore complexity diff --git a/tests/test_md_cli.py b/tests/test_md_cli.py index 16c05bf9..970c84cc 100644 --- a/tests/test_md_cli.py +++ b/tests/test_md_cli.py @@ -257,7 +257,9 @@ def test_seed(tmp_path): final_stats_2 = lines[2].split() - for i, (stats_1, stats_2) in enumerate(zip(final_stats_1, final_stats_2)): + for i, (stats_1, stats_2) in enumerate( + zip(final_stats_1, final_stats_2, strict=True) + ): if i != 1: assert stats_1 == stats_2 diff --git a/tox.ini b/tox.ini index 4057dbf3..925e8f5e 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ envlist = py311 usedevelop=True uv_sync_flags=--python={env_python} -[testenv:py{39,310,311,312}] +[testenv:py{310,311,312}] runner = uv-venv-lock-runner extras = all with_dev = True