diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 1aec2e5..e39eecd 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -6,7 +6,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.1.7
+ rev: v0.1.9
hooks:
- id: ruff
args: [--fix]
@@ -21,7 +21,7 @@ repos:
- id: trailing-whitespace
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v1.7.1
+ rev: v1.8.0
hooks:
- id: mypy
diff --git a/README.md b/README.md
index cd07120..78b0b63 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,10 @@
-
+
MatCalc
+
+
[![GitHub license](https://img.shields.io/github/license/materialsvirtuallab/matcalc)](https://github.com/materialsvirtuallab/matcalc/blob/main/LICENSE)
[![Linting](https://github.com/materialsvirtuallab/matcalc/workflows/Linting/badge.svg)](https://github.com/materialsvirtuallab/matcalc/workflows/Linting/badge.svg)
[![Testing](https://github.com/materialsvirtuallab/matcalc/workflows/Testing/badge.svg)](https://github.com/materialsvirtuallab/matcalc/workflows/Testing/badge.svg)
@@ -10,23 +12,25 @@
[![Requires Python 3.8+](https://img.shields.io/badge/Python-3.8+-blue.svg?logo=python&logoColor=white)](https://python.org/downloads)
[![PyPI](https://img.shields.io/pypi/v/matcalc?logo=pypi&logoColor=white)](https://pypi.org/project/matcalc?logo=pypi&logoColor=white)
+
+
## Docs
[materialsvirtuallab.github.io/matcalc](https://materialsvirtuallab.github.io/matcalc)
## Introduction
-MatCalc is a Python library for calculating materials properties from the potential energy surface (PES). The
-PES can be from DFT or, more commonly, from machine learning interatomic potentials (MLIPs).
+MatCalc is a Python library for calculating material properties from the potential energy surface (PES). The
+PES can come from DFT or, more commonly, from machine learning interatomic potentials (MLIPs).
-Calculating various materials properties can require relatively involved setup of various simulation codes. The
+Calculating material properties often requires involved setups of various simulation codes. The
goal of MatCalc is to provide a simplified, consistent interface to access these properties with any
parameterization of the PES.
## Outline
The main base class in MatCalc is `PropCalc` (property calculator). [All `PropCalc` subclasses](https://github.com/search?q=repo%3Amaterialsvirtuallab%2Fmatcalc%20%22(PropCalc)%22) should implement a
-`calc(pymatgen.Structure) -> dict` method that returns a dict of properties.
+`calc(pymatgen.Structure) -> dict` method that returns a dictionary of properties.
In general, `PropCalc` should be initialized with an ML model or ASE calculator, which is then used by either ASE,
LAMMPS or some other simulation code to perform calculations of properties.
diff --git a/matcalc/base.py b/matcalc/base.py
index 623951c..49e0a5e 100644
--- a/matcalc/base.py
+++ b/matcalc/base.py
@@ -2,7 +2,7 @@
from __future__ import annotations
import abc
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Any
from joblib import Parallel, delayed
@@ -29,7 +29,7 @@ def calc(self, structure: Structure) -> dict:
"""
def calc_many(
- self, structures: Sequence[Structure], n_jobs: None | int = None, **kwargs
+ self, structures: Sequence[Structure], n_jobs: None | int = None, **kwargs: Any
) -> Generator[dict, None, None]:
"""
Performs calc on many structures. The return type is a generator given that the calc method can potentially be
diff --git a/matcalc/elasticity.py b/matcalc/elasticity.py
index cd07ff2..5d1c60e 100644
--- a/matcalc/elasticity.py
+++ b/matcalc/elasticity.py
@@ -15,6 +15,7 @@
from collections.abc import Sequence
from ase.calculators.calculator import Calculator
+ from numpy.typing import ArrayLike
from pymatgen.core import Structure
@@ -48,11 +49,11 @@ def __init__(
self.norm_strains = tuple(np.array([1]) * np.asarray(norm_strains))
self.shear_strains = tuple(np.array([1]) * np.asarray(shear_strains))
if len(self.norm_strains) == 0:
- raise ValueError("norm_strains must be nonempty")
+ raise ValueError("norm_strains is empty")
if len(self.shear_strains) == 0:
- raise ValueError("shear_strains must be nonempty")
+ raise ValueError("shear_strains is empty")
if 0 in self.norm_strains or 0 in self.shear_strains:
- raise ValueError("Strains must be nonzero")
+ raise ValueError("strains must be non-zero")
self.relax_structure = relax_structure
self.fmax = fmax
if len(self.norm_strains) > 1 and len(self.shear_strains) > 1:
@@ -112,11 +113,11 @@ def calc(self, structure: Structure) -> dict[str, Any]:
def _elastic_tensor_from_strains(
self,
- strains,
- stresses,
- eq_stress=None,
+ strains: ArrayLike,
+ stresses: ArrayLike,
+ eq_stress: ArrayLike = None,
tol: float = 1e-7,
- ):
+ ) -> tuple[ElasticTensor, float]:
"""
Slightly modified version of Pymatgen function
pymatgen.analysis.elasticity.elastic.ElasticTensor.from_independent_strains;
diff --git a/matcalc/neb.py b/matcalc/neb.py
index 3681658..e069166 100644
--- a/matcalc/neb.py
+++ b/matcalc/neb.py
@@ -2,7 +2,7 @@
from __future__ import annotations
import os
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Any
from ase.io import Trajectory
from ase.neb import NEB, NEBTools
@@ -27,8 +27,8 @@ def __init__(
traj_folder: str | None = None,
interval: int = 1,
climb: bool = True,
- **kwargs,
- ):
+ **kwargs: Any,
+ ) -> None:
"""
Args:
images(list): A list of pymatgen structures as NEB image structures.
@@ -65,8 +65,8 @@ def from_end_images(
n_images: int = 7,
interpolate_lattices: bool = False,
autosort_tol: float = 0.5,
- **kwargs,
- ):
+ **kwargs: Any,
+ ) -> NEBCalc:
"""
Initialize a NEBCalc from end images.
@@ -94,7 +94,7 @@ def from_end_images(
def calc( # type: ignore[override]
self, fmax: float = 0.1, max_steps: int = 1000
- ) -> float:
+ ) -> tuple[float, float]:
"""
Perform NEB calculation.
diff --git a/matcalc/utils.py b/matcalc/utils.py
index 726de2d..4822c46 100644
--- a/matcalc/utils.py
+++ b/matcalc/utils.py
@@ -4,7 +4,7 @@
import functools
from inspect import isclass
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Any
import ase.optimize
from ase.optimize.optimize import Optimizer
@@ -23,7 +23,7 @@
@functools.lru_cache
-def get_universal_calculator(name: str | Calculator, **kwargs) -> Calculator:
+def get_universal_calculator(name: str | Calculator, **kwargs: Any) -> Calculator:
"""
Helper method to get some well-known **universal** calculators.
Imports should be inside if statements to ensure that all models are optional dependencies.
diff --git a/pyproject.toml b/pyproject.toml
index b11b9cc..87f3a8c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -51,51 +51,34 @@ packages = ["matcalc"]
[tool.ruff]
target-version = "py39"
line-length = 120
-select = [
- "B", # flake8-bugbear
- "C4", # flake8-comprehensions
- "D", # pydocstyle
- "E", # pycodestyle error
- "EXE", # flake8-executable
- "F", # pyflakes
- "I", # isort
- "ICN", # flake8-import-conventions
- "ISC", # flake8-implicit-str-concat
- "PD", # pandas-vet
- "PERF", # perflint
- "PIE", # flake8-pie
- "PL", # pylint
- "PT", # flake8-pytest-style
- "PYI", # flakes8-pyi
- "Q", # flake8-quotes
- "RET", # flake8-return
- "RSE", # flake8-raise
- "RUF", # Ruff-specific rules
- "SIM", # flake8-simplify
- "SLOT", # flake8-slots
- "TCH", # flake8-type-checking
- "TID", # tidy imports
- "TID", # flake8-tidy-imports
- "UP", # pyupgrade
- "W", # pycodestyle warning
- "YTT", # flake8-2020
-]
+select = ["ALL"]
ignore = [
+ "ANN101",
+ "ANN102",
+ "ANN401",
"B019", # functools.lru_cache on methods can lead to memory leaks
+ "COM812", # trailing comma missing
"D105", # Missing docstring in magic method
"D205", # 1 blank line required between summary line and description
"D212", # Multi-line docstring summary should start at the first line
+ "EM101",
+ "EM102",
+ "FBT001",
+ "FBT002",
"PLR", # pylint refactor
"PLW0603", # Using the global statement to update variables is discouraged
+ "PTH", # prefer Path to os.path
"SIM105", # Use contextlib.suppress(OSError) instead of try-except-pass
+ "TRY003",
]
+exclude = ["docs/conf.py"]
pydocstyle.convention = "google"
isort.required-imports = ["from __future__ import annotations"]
[tool.ruff.per-file-ignores]
"__init__.py" = ["F401"]
-"tasks.py" = ["D"]
-"tests/*" = ["D"]
+"tasks.py" = ["ANN", "D", "T203"]
+"tests/*" = ["D", "INP001", "N802", "N803", "S101"]
[tool.pytest.ini_options]
addopts = "--durations=30 --quiet -rXs --color=yes -p no:warnings"
diff --git a/tasks.py b/tasks.py
index 9f308be..57bd4bc 100644
--- a/tasks.py
+++ b/tasks.py
@@ -67,8 +67,6 @@ def make_docs(ctx):
ctx.run("cp ../README.md index.md", warn=True)
ctx.run("rm matcalc.*.rst", warn=True)
ctx.run("sphinx-apidoc -P -M -d 6 -o . -f ../matcalc")
- # ctx.run("rm matcalc*.html", warn=True)
- # ctx.run("sphinx-build -b html . ../docs") # HTML building.
ctx.run("cp modules.rst index.rst")
ctx.run("sphinx-build -M markdown . .")
ctx.run("rm *.rst", warn=True)
@@ -110,7 +108,7 @@ def publish(ctx):
@task
-def release_github(ctx):
+def release_github(ctx): # noqa: ARG001
desc = get_changelog()
payload = {
"tag_name": "v" + NEW_VER,
@@ -124,16 +122,16 @@ def release_github(ctx):
"https://api.github.com/repos/materialsvirtuallab/matcalc/releases",
data=json.dumps(payload),
headers={"Authorization": "token " + os.environ["GITHUB_RELEASES_TOKEN"]},
+ timeout=10,
)
pprint(response.json())
@task
-def release(ctx, notest=False):
+def release(ctx, notest: bool = False) -> None:
ctx.run("rm -r dist build matcalc.egg-info", warn=True)
if not notest:
ctx.run("pytest tests")
- # publish(ctx)
release_github(ctx)
@@ -145,6 +143,6 @@ def get_changelog():
@task
-def view_docs(ctx):
+def view_docs(ctx) -> None:
with cd("docs"):
ctx.run("bundle exec jekyll serve")
diff --git a/tests/conftest.py b/tests/conftest.py
index 9164dba..0e9b7c6 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -10,16 +10,21 @@
"""
from __future__ import annotations
+from typing import TYPE_CHECKING
+
import matgl
import pytest
from matgl.ext.ase import M3GNetCalculator
from pymatgen.util.testing import PymatgenTest
+if TYPE_CHECKING:
+ from pymatgen.core import Structure
+
matgl.clear_cache(confirm=False)
@pytest.fixture(scope="session")
-def LiFePO4():
+def LiFePO4() -> Structure:
"""LiFePO4 structure as session-scoped fixture (don't modify in-place,
will affect other tests).
"""
@@ -27,13 +32,13 @@ def LiFePO4():
@pytest.fixture(scope="session")
-def Li2O():
+def Li2O() -> Structure:
"""Li2O structure as session-scoped fixture."""
return PymatgenTest.get_structure("Li2O")
@pytest.fixture(scope="session")
-def M3GNetCalc():
+def M3GNetCalc() -> M3GNetCalculator:
"""M3GNet calculator as session-scoped fixture."""
potential = matgl.load_model("M3GNet-MP-2021.2.8-PES")
return M3GNetCalculator(potential=potential, stress_weight=0.01)
diff --git a/tests/test_elasticity.py b/tests/test_elasticity.py
index 087d87b..f27420b 100644
--- a/tests/test_elasticity.py
+++ b/tests/test_elasticity.py
@@ -59,12 +59,13 @@ def test_elastic_calc(Li2O: Structure, M3GNetCalc: M3GNetCalculator) -> None:
assert results["bulk_modulus_vrh"] == pytest.approx(0.6631894154825593, rel=1e-3)
-def test_elastic_calc_invalid_states(M3GNetCalc: M3GNetCalculator):
- with pytest.raises(ValueError, match="shear_strains must be nonempty"):
+def test_elastic_calc_invalid_states(M3GNetCalc: M3GNetCalculator) -> None:
+ with pytest.raises(ValueError, match="shear_strains is empty"):
ElasticityCalc(M3GNetCalc, shear_strains=[])
- with pytest.raises(ValueError, match="norm_strains must be nonempty"):
+ with pytest.raises(ValueError, match="norm_strains is empty"):
ElasticityCalc(M3GNetCalc, norm_strains=[])
- with pytest.raises(ValueError, match="Strains must be nonzero"):
+
+ with pytest.raises(ValueError, match="strains must be non-zero"):
ElasticityCalc(M3GNetCalc, norm_strains=[0.0, 0.1])
- with pytest.raises(ValueError, match="Strains must be nonzero"):
+ with pytest.raises(ValueError, match="strains must be non-zero"):
ElasticityCalc(M3GNetCalc, shear_strains=[0.0, 0.1])
diff --git a/tests/test_neb.py b/tests/test_neb.py
index 3a400a2..96cde00 100644
--- a/tests/test_neb.py
+++ b/tests/test_neb.py
@@ -19,12 +19,11 @@ def test_neb_calc(LiFePO4: Structure, M3GNetCalc: M3GNetCalculator, tmp_path: Pa
image_start.remove_sites([2])
image_end = LiFePO4.copy()
image_end.remove_sites([3])
- NEBcalc = NEBCalc.from_end_images(image_start, image_end, M3GNetCalc, n_images=5, traj_folder=tmp_path)
- barriers = NEBcalc.calc(fmax=0.5)
- print(barriers)
- assert len(NEBcalc.neb.images) == 7
+ neb_calc = NEBCalc.from_end_images(image_start, image_end, M3GNetCalc, n_images=5, traj_folder=tmp_path)
+ barriers = neb_calc.calc(fmax=0.5)
+ assert len(neb_calc.neb.images) == 7
assert barriers[0] == pytest.approx(0.0184783935546875, rel=0.002)
assert barriers[1] == pytest.approx(0.0018920898, rel=0.002)
with pytest.raises(ValueError, match="Unknown optimizer='invalid', must be one of "):
- NEBcalc.from_end_images(image_start, image_end, M3GNetCalc, optimizer="invalid")
+ neb_calc.from_end_images(image_start, image_end, M3GNetCalc, optimizer="invalid")