Skip to content

Commit

Permalink
bump ruff and fix errors
Browse files Browse the repository at this point in the history
  • Loading branch information
janosh committed Dec 22, 2023
1 parent 5b7e266 commit acc89dc
Show file tree
Hide file tree
Showing 11 changed files with 65 additions and 74 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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

Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
<h1 align="center">
<img src="https://github.com/materialsvirtuallab/matcalc/assets/30958850/89486f2f-73fb-40fb-803a-dfafe510eb6d" width="150" height="150" alt="MatCalc logo" style="vertical-align: middle;" />
<img src="https://github.com/materialsvirtuallab/matcalc/assets/30958850/89486f2f-73fb-40fb-803a-dfafe510eb6d" width="100" alt="MatCalc logo" style="vertical-align: middle;" /><br>
MatCalc
</h1>

<h4 align="center">

[![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)
[![codecov](https://codecov.io/gh/materialsvirtuallab/matcalc/branch/main/graph/badge.svg?token=OR7Z9WWRRC)](https://codecov.io/gh/materialsvirtuallab/matcalc)
[![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)

</h4>

## 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.
Expand Down
4 changes: 2 additions & 2 deletions matcalc/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
15 changes: 8 additions & 7 deletions matcalc/elasticity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down
12 changes: 6 additions & 6 deletions matcalc/neb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions matcalc/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
45 changes: 14 additions & 31 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
10 changes: 4 additions & 6 deletions tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand All @@ -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)


Expand All @@ -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")
11 changes: 8 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,35 @@
"""
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).
"""
return PymatgenTest.get_structure("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)
11 changes: 6 additions & 5 deletions tests/test_elasticity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
9 changes: 4 additions & 5 deletions tests/test_neb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

0 comments on commit acc89dc

Please sign in to comment.