From 371d28d8a45c55f54fd6b113e1ef688773002aad Mon Sep 17 00:00:00 2001
From: Andreas Kloeckner <inform@tiker.net>
Date: Wed, 17 Jul 2024 13:37:15 -0500
Subject: [PATCH] Switch to ruff, fix issues

---
 .github/workflows/ci.yml         | 13 +++------
 .gitlab-ci.yml                   |  8 +++---
 benchmarks/bench_translations.py |  4 +--
 examples/expansion-toys.py       |  3 ++-
 pyproject.toml                   | 45 ++++++++++++++++++++++++++++++++
 setup.py                         |  2 +-
 sumpy/assignment_collection.py   |  4 +--
 sumpy/codegen.py                 |  2 +-
 sumpy/cse.py                     |  2 +-
 sumpy/expansion/__init__.py      |  6 ++---
 sumpy/kernel.py                  |  4 +--
 sumpy/p2p.py                     |  2 +-
 sumpy/qbx.py                     |  5 ++--
 sumpy/tools.py                   |  8 +++---
 test/test_misc.py                |  2 +-
 15 files changed, 76 insertions(+), 34 deletions(-)
 create mode 100644 pyproject.toml

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6a2c714a..0a1dcf6f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -8,20 +8,15 @@ on:
         - cron:  '17 3 * * 0'
 
 jobs:
-    flake8:
-        name: Flake8
+    ruff:
+        name: Ruff
         runs-on: ubuntu-latest
         steps:
         -   uses: actions/checkout@v4
-        -
-            uses: actions/setup-python@v5
-            with:
-                # matches compat target in setup.py
-                python-version: '3.8'
         -   name: "Main Script"
             run: |
-                curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/prepare-and-run-flake8.sh
-                . ./prepare-and-run-flake8.sh "$(basename $GITHUB_REPOSITORY)" test examples benchmarks
+                pipx install ruff
+                ruff check
 
     pylint:
         name: Pylint
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 85b7eb07..2326e653 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -118,13 +118,13 @@ Documentation:
   tags:
   - linux
 
-Flake8:
+Ruff:
   stage: test
   script:
-  - curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/prepare-and-run-flake8.sh
-  - . ./prepare-and-run-flake8.sh "$CI_PROJECT_NAME" test examples benchmarks
+  - pix install ruff
+  - ruff check
   tags:
-  - python3
+  - docker-runner
   except:
   - tags
 
diff --git a/benchmarks/bench_translations.py b/benchmarks/bench_translations.py
index f8d718f4..8c31eb8b 100644
--- a/benchmarks/bench_translations.py
+++ b/benchmarks/bench_translations.py
@@ -45,7 +45,7 @@ class TranslationBenchmarkSuite:
 
     def setup(self, param):
         logging.basicConfig(level=logging.INFO)
-        np.random.seed(17)
+        np.random.seed(17)  # noqa: NPY002
         if self.__class__ == TranslationBenchmarkSuite:
             raise NotImplementedError
         mpole_expn_class = self.mpole_expn_class
@@ -78,7 +78,7 @@ class TranslationBenchmarkSuite:
         insns = to_loopy_insns(sac.assignments.items())
         counter = pymbolic.mapper.flop_counter.CSEAwareFlopCounter()
 
-        return sum([counter.rec(insn.expression)+1 for insn in insns])
+        return sum(counter.rec(insn.expression)+1 for insn in insns)
 
     track_m2l_op_count.unit = "ops"
     track_m2l_op_count.timeout = 300.0
diff --git a/examples/expansion-toys.py b/examples/expansion-toys.py
index 48647d0e..6e7d66b3 100644
--- a/examples/expansion-toys.py
+++ b/examples/expansion-toys.py
@@ -29,9 +29,10 @@ def main():
             # HelmholtzKernel(2), extra_kernel_kwargs={"k": 0.3},
             )
 
+    rng = np.random.default_rng()
     pt_src = t.PointSources(
             tctx,
-            np.random.rand(2, 50) - 0.5,
+            rng.uniform(size=(2, 50)) - 0.5,
             np.ones(50))
 
     fp = FieldPlotter([3, 0], extent=8)
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..11cf370c
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,45 @@
+[tool.ruff]
+target-version = "py38"
+preview = true
+
+[tool.ruff.lint]
+extend-select = [
+    "B",   # flake8-bugbear
+    "C",   # flake8-comprehensions
+    "E",   # pycodestyle
+    "F",   # pyflakes
+    "G",   # flake8-logging-format
+    # "I",   # flake8-isort
+    "N",   # pep8-naming
+    "NPY", # numpy
+    "Q",   # flake8-quotes
+    # "UP",  # pyupgrade
+    # "RUF", # ruff
+    # "W",   # pycodestyle
+]
+extend-ignore = [
+    "C90",  # McCabe complexity
+    "E221", # multiple spaces before operator
+    "E226", # missing whitespace around arithmetic operator
+    "E402", # module-level import not at top of file
+    "UP006", # updated annotations due to __future__ import
+    "UP007", # updated annotations due to __future__ import
+    "UP031", # use f-strings instead of %
+    "UP032", # use f-strings instead of .format
+
+    # TODO
+    "E265",  # comment format
+    "F841",  # unused local
+]
+
+[tool.ruff.lint.flake8-quotes]
+docstring-quotes = "double"
+inline-quotes = "double"
+multiline-quotes = "double"
+
+[tool.ruff.lint.isort]
+combine-as-imports = true
+known-local-folder = [
+    "pytools",
+]
+lines-after-imports = 2
diff --git a/setup.py b/setup.py
index 359a3ea5..c6fb7427 100644
--- a/setup.py
+++ b/setup.py
@@ -50,7 +50,7 @@ def find_git_revision(tree_root):
     if retcode != 0:
         from warnings import warn
 
-        warn("unable to find git revision")
+        warn("unable to find git revision", stacklevel=1)
         return None
 
     return git_rev
diff --git a/sumpy/assignment_collection.py b/sumpy/assignment_collection.py
index 81bb066f..5115714d 100644
--- a/sumpy/assignment_collection.py
+++ b/sumpy/assignment_collection.py
@@ -216,8 +216,8 @@ class SymbolicAssignmentCollection:
             del self.assignments[name]
             self.assignments[name] = new_expr
 
-        logger.info("common subexpression elimination: done after {dur:.2f} s"
-                    .format(dur=time.time() - start_time))
+        logger.info("common subexpression elimination: done after %.2f s",
+                    time.time() - start_time)
         return new_extra_exprs
 
 # }}}
diff --git a/sumpy/codegen.py b/sumpy/codegen.py
index 670d0729..5c51cd9c 100644
--- a/sumpy/codegen.py
+++ b/sumpy/codegen.py
@@ -481,7 +481,7 @@ class BigIntegerKiller(CSECachingIdentityMapper, CallExternalRecMapper):
             if int(expr_as_float) != int(expr):
                 from warnings import warn
                 warn(f"Converting '{expr}' to "
-                     f"'{self.float_type.__name__}' loses digits")
+                     f"'{self.float_type.__name__}' loses digits", stacklevel=1)
 
             # Suppress further warnings.
             self.warn = False
diff --git a/sumpy/cse.py b/sumpy/cse.py
index f7023aa1..635c26f1 100644
--- a/sumpy/cse.py
+++ b/sumpy/cse.py
@@ -527,7 +527,7 @@ def tree_cse(exprs, symbols, opt_subs=None):
             try:
                 sym = next(symbols)
             except StopIteration:
-                raise ValueError("Symbols iterator ran out of symbols.")
+                raise ValueError("Symbols iterator ran out of symbols.") from None
 
             subs[orig_expr] = sym
             replacements.append((sym, new_expr))
diff --git a/sumpy/expansion/__init__.py b/sumpy/expansion/__init__.py
index dc3ce3ba..493ff710 100644
--- a/sumpy/expansion/__init__.py
+++ b/sumpy/expansion/__init__.py
@@ -734,9 +734,9 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler):
 
         plog.done()
 
-        logger.debug("number of Taylor coefficients was reduced from {orig} to {red}"
-                     .format(orig=len(self.get_full_coefficient_identifiers()),
-                             red=len(stored_identifiers)))
+        logger.debug("number of Taylor coefficients was reduced from %d to %d",
+                     len(self.get_full_coefficient_identifiers()),
+                     len(stored_identifiers))
 
         shape = (len(mis), len(stored_identifiers))
         op = CSEMatVecOperator(from_input_coeffs_by_row,
diff --git a/sumpy/kernel.py b/sumpy/kernel.py
index 76027809..d3edefa0 100644
--- a/sumpy/kernel.py
+++ b/sumpy/kernel.py
@@ -1313,9 +1313,9 @@ class KernelMapper:
     def rec(self, kernel):
         try:
             method = getattr(self, kernel.mapper_method)
-        except AttributeError:
+        except AttributeError as err:
             raise RuntimeError("{} cannot handle {}".format(
-                type(self), type(kernel)))
+                type(self), type(kernel))) from err
         else:
             return method(kernel)
 
diff --git a/sumpy/p2p.py b/sumpy/p2p.py
index c5acdd9d..6babc237 100644
--- a/sumpy/p2p.py
+++ b/sumpy/p2p.py
@@ -704,7 +704,7 @@ class P2PFromCSR(P2PBase):
             for i, (array_name, array_size, array_dtype) in \
                     enumerate(zip(local_arrays, local_array_sizes,
                                   local_array_dtypes)):
-                if array_dtype not in [np.float, np.double]:
+                if issubclass(array_dtype.type, np.complexfloating):
                     # pyopencl does not support complex data type vectors
                     continue
                 if array_size in [2, 3, 4, 8, 16]:
diff --git a/sumpy/qbx.py b/sumpy/qbx.py
index 9c903ab1..19ff0f7f 100644
--- a/sumpy/qbx.py
+++ b/sumpy/qbx.py
@@ -210,7 +210,8 @@ class LayerPotentialBase(KernelCacheMixin, KernelComputation):
                     ["isrc_outer", f"{itgt_name}_inner"])
         else:
             from warnings import warn
-            warn(f"don't know how to tune layer potential computation for '{dev}'")
+            warn(f"don't know how to tune layer potential computation for '{dev}'",
+                 stacklevel=1)
             loopy_knl = lp.split_iname(loopy_knl, itgt_name, 128, outer_tag="g.0")
         loopy_knl = self._allow_redundant_execution_of_knl_scaling(loopy_knl)
 
@@ -574,7 +575,7 @@ def find_jump_term(kernel, arg_provider):
         elif tgt_count == 1:
             from warnings import warn
             warn("jump terms for mixed derivatives (1 src+1 tgt) only available "
-                    "for the double-layer potential")
+                    "for the double-layer potential", stacklevel=1)
             i, = tgt_derivatives
             assert isinstance(i, int)
             return (
diff --git a/sumpy/tools.py b/sumpy/tools.py
index 13acbbab..8c666b61 100644
--- a/sumpy/tools.py
+++ b/sumpy/tools.py
@@ -473,8 +473,8 @@ class KernelCacheMixin:
         logger.info("%s: kernel cache miss", self.name)
         if CACHING_ENABLED and not (
                 NO_CACHE_KERNELS and self.name in NO_CACHE_KERNELS):
-            logger.info("{}: kernel cache miss [key={}]".format(
-                self.name, cache_key))
+            logger.info("%s: kernel cache miss [key=%d]",
+                self.name, cache_key)
 
         from pytools import MinRecursionLimit
         with MinRecursionLimit(3000):
@@ -666,8 +666,8 @@ def to_complex_dtype(dtype):
     np_type = np.dtype(dtype).type
     try:
         return to_complex_type_dict[np_type]
-    except KeyError:
-        raise RuntimeError(f"Unknown dtype: {dtype}")
+    except KeyError as err:
+        raise RuntimeError(f"Unknown dtype: {dtype}") from err
 
 
 @dataclass(frozen=True)
diff --git a/test/test_misc.py b/test/test_misc.py
index 9cee476c..02842570 100644
--- a/test/test_misc.py
+++ b/test/test_misc.py
@@ -426,7 +426,7 @@ def test_as_scalar_pde_maxwell():
     mu, epsilon = symbols("mu, epsilon")
     t = (0, 0, 0, 1)
 
-    pde = concat(curl(E) + diff(B, t),  curl(B) - mu*epsilon*diff(E, t),
+    pde = concat(curl(E) + diff(B, t), curl(B) - mu*epsilon*diff(E, t),
                  divergence(E), divergence(B))
     as_scalar_pde(pde, 3)
 
-- 
GitLab