Repository: TRAIS-Lab/dattri (language: python)

Issue:
switch {pycodestlye, pydocstyle, pylint} to ruff
[pycodestyle](https://github.com/PyCQA/pycodestyle) was archived and suggested switching to [ruff](https://github.com/astral-sh/ruff), which integrates various linters. This PR removes `pycodestlye`, `pydocstyle`, and `pylint` and adds `ruff` in github action workflow. New lint errors in existing python files are also fixed. 

**Note:** We could selectively disable some unnecessarily strict lint rules as we further develop this project. To disable lint rules for all python files, add corresponding codes to `ignore` under `[tool.ruff.lint]` in `pyproject.toml`. To disable lint rules for test files only, add corresponding codes to `"**/test/**/*.py"` under `[tool.ruff.lint.extend-per-file-ignores]`.


Discussion / hints:
@TheaperDeng sorry this PR will cause some conflicts with #5. 

Fix patch:
diff --git a/.github/workflows/darglint.yml b/.github/workflows/darglint.yml
new file mode 100644
index 00000000..e16bb2e0
--- /dev/null
+++ b/.github/workflows/darglint.yml
@@ -0,0 +1,23 @@
+name: Darglint
+
+on: [pull_request, workflow_dispatch]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: ["3.10"]
+    steps:
+    - uses: actions/checkout@v3
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v3
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install darglint
+    - name: Analysing the code with darglint
+      run: |
+        darglint $(git ls-files 'dattri/*.py')
diff --git a/dattri/func/ihvp.py b/dattri/func/ihvp.py
index dadc8185..704e04a6 100644
--- a/dattri/func/ihvp.py
+++ b/dattri/func/ihvp.py
@@ -1,34 +1,60 @@
-"""ihvp (inverse hessian-vector product) calculation for an arbitrary function.
+"""IHVP (inverse hessian-vector product) calculation.
 
 This module contains:
-- `ihvp_direct`: Direct algorithm for ihvp.
+- `ihvp_explicit`: IHVP via explicit Hessian calculation.
 """
+from __future__ import annotations
 
-from collections.abc import Callable
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from collections.abc import Callable
+    from typing import Tuple, Union
 
 import torch
 from torch import Tensor
 from torch.func import hessian
 
 
-def ihvp_direct(func: Callable, *args, argnums: int = 0) -> Callable:
-    """Direct ihvp algorithm function.
-
-    Standing for the inverse-hessian-vector product, returns a function that,
-    when given vectors, computes the product of inverse-hessian and vector.
-
-    Direct algorithm calcualte the hessian matrix explicitly and then use
-    `torch.linagl.solve` for each vector production.
-
-    :param func: A Python function that takes one or more arguments.
-           Must return a single-element Tensor. The hessian will
-           be calculated on this function.
-    :param argnums: An integer default to 0. Specifies arguments to compute
-           gradients with respect to.
+def ihvp_explicit(func: Callable,
+                  *args,
+                  argnums: Union[int, Tuple[int, ...]] = 0) -> Callable:
+    """IHVP via explicit Hessian calculation.
+
+    IHVP stands for inverse-hessian-vector product. For a given function
+    `func`, this method first calculates the Hessian matrix explicitly
+    and then wraps the Hessian in a function that uses `torch.linalg.solve` to
+    calculate the IHVP for any given vector.
+
+    Args:
+        func (Callable): A function taking one or more arguments and returning
+            a single-element Tensor. The Hessian will be calculated based on
+            this function.
+        *args: List of arguments for `func`.
+        argnums (int or Tuple[int], optional): An integer or a tuple of integers
+            deciding which arguments in `*args` to get the Hessian with respect
+            to. Default: 0.
+
+    Returns:
+        A function that takes a vector `vec` and returns the IHVP of the Hessian
+        of `func` and `vec`.
+
+    Note:
+        This method stores the Hessian matrix explicitly and is not computationally
+        efficient.
     """
     hessian_tensor = hessian(func, argnums=argnums)(*args)
 
     def _ihvp_direct_func(vec: Tensor) -> Tensor:
+        """The IHVP function based on `hessian_tensor`.
+
+        Args:
+            vec (Tensor): A vector with the same dimension as the first dim of
+                `hessian_tensor`.
+
+        Returns:
+            The IHVP value, i.e., inverse of `hessian_tensor` times `vec`.
+        """
         return torch.linalg.solve(hessian_tensor, vec.T).T
 
     return _ihvp_direct_func
diff --git a/pyproject.toml b/pyproject.toml
index 923cbf7e..ca7bf4d8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,10 +1,10 @@
 [project]
 name = "dattri"
-requires-python = ">=3.10"
+requires-python = ">=3.8"
 
 [tool.ruff.lint]
 select = ["ALL"]
-ignore = ["ANN002", "D203", "D213", "D407"]  # ignores: ANN002 (unnecessarily strict rule), D203 (conflict with D211), D213 (conflict with D212), D407 (supporting Google style docstrings)
+ignore = ["ANN002", "D203", "D213", "D407", "UP"]  # ignores: ANN002 (unnecessarily strict rule), D203 (conflict with D211), D213 (conflict with D212), D407 (supporting Google style docstrings), UP (compatibility with Python 3.8)
 
 [tool.ruff.lint.extend-per-file-ignores]
 "__init__.py" = ["E401", "E402"]


Test patch:
diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
index ade9b04a..38c759c3 100644
--- a/.github/workflows/pytest.yml
+++ b/.github/workflows/pytest.yml
@@ -23,6 +23,6 @@ jobs:
 
         python setup.py sdist bdist_wheel
         pip install dist/*.whl
-    - name: Analysing the code with pylint
+    - name: Analysing the code with pytest
       run: |
         pytest -v test
diff --git a/test/dattri/func/test_ihvp.py b/test/dattri/func/test_ihvp.py
index 05f9a90c..2913fcfd 100644
--- a/test/dattri/func/test_ihvp.py
+++ b/test/dattri/func/test_ihvp.py
@@ -1,28 +1,27 @@
 """Unit test for ihvp calculator."""
-# ruff: noqa: S101
 import torch
 
-from dattri.func.ihvp import ihvp_direct
+from dattri.func.ihvp import ihvp_explicit
 
 
 class TestIHVP:
     """Test ihvp functions."""
 
-    def test_ihvp_direct(self):
-        """Test ihvp_direct."""
+    def test_ihvp_explicit(self):
+        """Test ihvp_explicit."""
 
         def _target(x):
             return torch.sin(x).sum()
 
         x = torch.randn(2)
         vec = torch.randn(5, 2)
-        ihvp = ihvp_direct(_target, x, argnums=0)
+        ihvp = ihvp_explicit(_target, x, argnums=0)
 
         assert torch.allclose(ihvp(vec), (torch.diag(-1 / x.sin()) @ vec.T).T)
         assert ihvp(vec).shape == (5, 2)
 
-    def test_ihvp_direct_argnum(self):
-        """Test argnum of ihvp_direct."""
+    def test_ihvp_explicit_argnums(self):
+        """Test argnums of ihvp_explicit."""
 
         def _target(x, y):
             return torch.sin(x + y).sum()
@@ -30,7 +29,7 @@ def _target(x, y):
         x = 2
         y = torch.randn(2)
         vec = torch.randn(5, 2)
-        ihvp = ihvp_direct(_target, x, y, argnums=1)
+        ihvp = ihvp_explicit(_target, x, y, argnums=1)
 
         assert torch.allclose(ihvp(vec), (torch.diag(-1 / (2+y).sin()) @ vec.T).T)
         assert ihvp(vec).shape == (5, 2)