diff --git a/benchmarks/bench_translations.py b/benchmarks/bench_translations.py
index a6a920d08ac8070daa050b5f9fe639dc4eb592dd..f8d718f416ab817d70b262e2ad47b5e0e7d184a2 100644
--- a/benchmarks/bench_translations.py
+++ b/benchmarks/bench_translations.py
@@ -57,8 +57,9 @@ class TranslationBenchmarkSuite:
         m_expn = self.mpole_expn_class(knl, order=param.order)
         l_expn = self.local_expn_class(knl, order=param.order)
 
-        src_coeff_exprs = [sym.Symbol("src_coeff%d" % i)
-                for i in range(len(m_expn))]
+        src_coeff_exprs = [
+            sym.Symbol(f"src_coeff{i}")
+            for i in range(len(m_expn))]
         dvec = sym.make_sym_vector("d", knl.dim)
         src_rscale = sym.Symbol("src_rscale")
         tgt_rscale = sym.Symbol("tgt_rscale")
@@ -72,7 +73,7 @@ class TranslationBenchmarkSuite:
             result = l_expn.translate_from(m_expn, src_coeff_exprs, src_rscale,
                                        dvec, tgt_rscale)
         for i, expr in enumerate(result):
-            sac.assign_unique("coeff%d" % i, expr)
+            sac.assign_unique(f"coeff{i}", expr)
         sac.run_global_cse()
         insns = to_loopy_insns(sac.assignments.items())
         counter = pymbolic.mapper.flop_counter.CSEAwareFlopCounter()
diff --git a/examples/curve-pot.py b/examples/curve-pot.py
index 134d7d34a3555480cc60f2869d7cd57269524902..810990ad10164d8227b7dcb6f137dbaf556a7d87 100644
--- a/examples/curve-pot.py
+++ b/examples/curve-pot.py
@@ -32,7 +32,7 @@ def process_kernel(knl, what_operator):
     #     from sumpy.kernel import DirectionalTargetDerivative
     #     knl = DirectionalTargetDerivative(knl)
     else:
-        raise RuntimeError("unrecognized operator '%s'" % what_operator)
+        raise RuntimeError(f"unrecognized operator '{what_operator}'")
 
     return source_knl, target_knl
 
diff --git a/setup.py b/setup.py
index 6e49b1c8c6801ecd7183fb0d70371a214a6271c6..835f6507f47d695b82f1bfc1c5bb0beb4d58beb6 100644
--- a/setup.py
+++ b/setup.py
@@ -33,9 +33,7 @@ def find_git_revision(tree_root):
               cwd=tree_root)
     (git_rev, _) = p.communicate()
 
-    import sys
     git_rev = git_rev.decode()
-
     git_rev = git_rev.rstrip()
 
     retcode = p.returncode
@@ -54,7 +52,7 @@ def write_git_revision(package_name):
     git_rev = find_git_revision(dn)
 
     with open(join(dn, package_name, "_git_rev.py"), "w") as outf:
-        outf.write('GIT_REVISION = "%s"\n' % git_rev)
+        outf.write(f'GIT_REVISION = "{git_rev}"\n')
 
 
 write_git_revision("sumpy")
diff --git a/sumpy/codegen.py b/sumpy/codegen.py
index ffcf445f8d4bda2b3cbe7a8e3e0fffba7e4bc387..83f4fce79293e7b0def035f6e97d357134d3644a 100644
--- a/sumpy/codegen.py
+++ b/sumpy/codegen.py
@@ -274,7 +274,7 @@ class BesselDerivativeReplacer(CSECachingIdentityMapper, CallExternalRecMapper):
                     2**(-k)*sum(
                         (-1)**idx*int(sym.binomial(k, idx)) * function(i, arg)
                         for idx, i in enumerate(range(order-k, order+k+1, 2))),
-                    "d%d_%s_%s" % (n_derivs, function.name, order_str))
+                    f"d{n_derivs}_{function.name}_{order_str}")
         else:
             return CSECachingIdentityMapper.map_call(
                     rec_self or self, expr, rec_self, *args)
@@ -309,8 +309,8 @@ class BesselSubstitutor(CSECachingIdentityMapper):
 
     @memoize_method
     def bessel_jv_two(self, order, arg):
-        name_om1 = self.name_gen("bessel_%d" % (order-1))
-        name_o = self.name_gen("bessel_%d" % order)
+        name_om1 = self.name_gen(f"bessel_{order - 1}")
+        name_o = self.name_gen(f"bessel_{order}")
         self.assignments.append(
                 make_assignment(
                     (prim.Variable(name_om1), prim.Variable(name_o),),
@@ -329,16 +329,15 @@ class BesselSubstitutor(CSECachingIdentityMapper):
         elif order < 0:
             return self.wrap_in_cse(
                     (-1)**order*self.bessel_j(-order, arg),
-                    "bessel_j_neg%d" % -order)
+                    f"bessel_j_neg{-order}")
         else:
             assert abs(order) < top_order
 
             # AS (9.1.27)
             nu = order+1
             return self.wrap_in_cse(
-                    2*nu/arg*self.bessel_j(nu, arg)
-                    - self.bessel_j(nu+1, arg),
-                    "bessel_j_%d" % order)
+                    2*nu/arg*self.bessel_j(nu, arg) - self.bessel_j(nu+1, arg),
+                    f"bessel_j_{order}")
 
     # }}}
 
@@ -366,14 +365,13 @@ class BesselSubstitutor(CSECachingIdentityMapper):
             nu = -order
             return self.wrap_in_cse(
                     (-1) ** nu * self.hankel_1(nu, arg),
-                    "hank1_neg%d" % nu)
+                    f"hank1_neg{nu}")
         elif order > 1:
             # AS (9.1.27)
             nu = order-1
             return self.wrap_in_cse(
-                    2*nu/arg*self.hankel_1(nu, arg)
-                    - self.hankel_1(nu-1, arg),
-                    "hank1_%d" % order)
+                    2*nu/arg*self.hankel_1(nu, arg) - self.hankel_1(nu-1, arg),
+                    f"hank1_{order}")
         else:
             raise AssertionError()
 
@@ -463,8 +461,8 @@ class BigIntegerKiller(CSECachingIdentityMapper, CallExternalRecMapper):
             expr_as_float = self.float_type(expr)
             if int(expr_as_float) != int(expr):
                 from warnings import warn
-                warn("Converting '%d' to '%s' loses digits"
-                        % (expr, self.float_type.__name__))
+                warn(f"Converting '{expr}' to "
+                     f"'{self.float_type.__name__}' loses digits")
 
             # Suppress further warnings.
             self.warn = False
@@ -684,7 +682,7 @@ def to_loopy_insns(assignments, vector_names=frozenset(), pymbolic_expr_maps=(),
             return expr
 
     def convert_expr(name, expr):
-        logger.debug("generate expression for: %s" % name)
+        logger.debug("generate expression for: %s", name)
         expr = cmb_mapper(expr)
         for m in pymbolic_expr_maps:
             expr = m(expr)
diff --git a/sumpy/e2e.py b/sumpy/e2e.py
index dac3e174074e8075f753ad70d44ef1311d311fa4..8bcd346e833c273f819db400c3b9c2c50a3c733e 100644
--- a/sumpy/e2e.py
+++ b/sumpy/e2e.py
@@ -97,8 +97,9 @@ class E2EBase(KernelCacheWrapper):
         from sumpy.symbolic import make_sym_vector
         dvec = make_sym_vector("d", self.dim)
 
-        src_coeff_exprs = [sym.Symbol("src_coeff%d" % i)
-                for i in range(len(self.src_expansion))]
+        src_coeff_exprs = [
+            sym.Symbol(f"src_coeff{i}")
+            for i in range(len(self.src_expansion))]
         src_rscale = sym.Symbol("src_rscale")
 
         tgt_rscale = sym.Symbol("tgt_rscale")
@@ -106,7 +107,7 @@ class E2EBase(KernelCacheWrapper):
         from sumpy.assignment_collection import SymbolicAssignmentCollection
         sac = SymbolicAssignmentCollection()
         tgt_coeff_names = [
-                sac.assign_unique("coeff%d" % i, coeff_i)
+                sac.assign_unique(f"coeff{i}", coeff_i)
                 for i, coeff_i in enumerate(
                     self.tgt_expansion.translate_from(
                         self.src_expansion, src_coeff_exprs, src_rscale,
@@ -158,18 +159,15 @@ class E2EFromCSR(E2EBase):
         dvec = make_sym_vector("d", self.dim)
 
         src_rscale = sym.Symbol("src_rscale")
-
         tgt_rscale = sym.Symbol("tgt_rscale")
 
         ncoeff_src = len(self.src_expansion)
-
-        src_coeff_exprs = [sym.Symbol("src_coeff%d" % i)
-                for i in range(ncoeff_src)]
+        src_coeff_exprs = [sym.Symbol(f"src_coeff{i}") for i in range(ncoeff_src)]
 
         from sumpy.assignment_collection import SymbolicAssignmentCollection
         sac = SymbolicAssignmentCollection()
         tgt_coeff_names = [
-                sac.assign_unique("coeff%d" % i, coeff_i)
+                sac.assign_unique(f"coeff{i}", coeff_i)
                 for i, coeff_i in enumerate(
                     self.tgt_expansion.translate_from(
                         self.src_expansion, src_coeff_exprs, src_rscale,
@@ -308,22 +306,21 @@ class M2LUsingTranslationClassesDependentData(E2EFromCSR):
         src_rscale = sym.Symbol("src_rscale")
         tgt_rscale = sym.Symbol("tgt_rscale")
 
-        m2l_translation_classes_dependent_ndata = \
+        m2l_translation_classes_dependent_ndata = (
                 self.tgt_expansion.m2l_translation_classes_dependent_ndata(
-                        self.src_expansion)
+                        self.src_expansion))
         m2l_translation_classes_dependent_data = \
-                    [sym.Symbol("data%d" % i)
+                    [sym.Symbol(f"data{i}")
                 for i in range(m2l_translation_classes_dependent_ndata)]
 
         ncoeff_src = len(self.src_expansion)
 
-        src_coeff_exprs = [sym.Symbol("src_coeffs%d" % i)
-                for i in range(ncoeff_src)]
+        src_coeff_exprs = [sym.Symbol(f"src_coeffs{i}") for i in range(ncoeff_src)]
 
         from sumpy.assignment_collection import SymbolicAssignmentCollection
         sac = SymbolicAssignmentCollection()
         tgt_coeff_names = [
-                sac.assign_unique("tgt_coeff%d" % i, coeff_i)
+                sac.assign_unique(f"tgt_coeff{i}", coeff_i)
                 for i, coeff_i in enumerate(
                     self.tgt_expansion.translate_from(
                         self.src_expansion, src_coeff_exprs, src_rscale,
@@ -538,8 +535,8 @@ class M2LGenerateTranslationClassesDependentData(E2EBase):
         from sumpy.assignment_collection import SymbolicAssignmentCollection
         sac = SymbolicAssignmentCollection()
         tgt_coeff_names = [
-                sac.assign_unique("m2l_translation_classes_dependent_expr%d" % i,
-                    coeff_i)
+                sac.assign_unique(
+                    f"m2l_translation_classes_dependent_expr{i}", coeff_i)
                 for i, coeff_i in enumerate(
                     self.tgt_expansion.m2l_translation_classes_dependent_data(
                         self.src_expansion, src_rscale,
@@ -651,8 +648,9 @@ class M2LPreprocessMultipole(E2EBase):
     default_name = "m2l_preprocess_multipole"
 
     def get_loopy_insns(self, result_dtype):
-        src_coeff_exprs = [sym.Symbol("src_coeff%d" % i)
-                for i in range(len(self.src_expansion))]
+        src_coeff_exprs = [
+            sym.Symbol(f"src_coeff{i}")
+            for i in range(len(self.src_expansion))]
 
         src_rscale = sym.Symbol("src_rscale")
 
@@ -660,7 +658,7 @@ class M2LPreprocessMultipole(E2EBase):
         sac = SymbolicAssignmentCollection()
 
         preprocessed_src_coeff_names = [
-                sac.assign_unique("preprocessed_src_coeff%d" % i, coeff_i)
+                sac.assign_unique(f"preprocessed_src_coeff{i}", coeff_i)
                 for i, coeff_i in enumerate(
                     self.tgt_expansion.m2l_preprocess_multipole_exprs(
                         self.src_expansion, src_coeff_exprs,
@@ -753,7 +751,7 @@ class M2LPostprocessLocal(E2EBase):
             self.tgt_expansion.m2l_postprocess_local_nexprs(self.tgt_expansion)
 
         tgt_coeff_exprs_before_postprocessing = [
-            sym.Symbol("tgt_coeff_before_postprocessing%d" % i)
+            sym.Symbol(f"tgt_coeff_before_postprocessing{i}")
             for i in range(ncoeffs_before_postprocessing)]
 
         src_rscale = sym.Symbol("src_rscale")
@@ -772,7 +770,7 @@ class M2LPostprocessLocal(E2EBase):
                     tgt_coeff_exprs]
 
         tgt_coeff_names = [
-            sac.assign_unique("tgt_coeff%d" % i, coeff_i)
+            sac.assign_unique(f"tgt_coeff{i}", coeff_i)
             for i, coeff_i in enumerate(tgt_coeff_exprs)]
 
         sac.run_global_cse()
@@ -860,9 +858,9 @@ class E2EFromChildren(E2EBase):
 
     def get_kernel(self):
         if self.src_expansion is not self.tgt_expansion:
-            raise RuntimeError("%s requires that the source "
-                   "and target expansion are the same object"
-                   % type(self).__name__)
+            raise RuntimeError(
+                f"{type(self).__name__} requires that the source "
+                "and target expansion are the same object")
 
         ncoeffs_src = len(self.src_expansion)
         ncoeffs_tgt = len(self.tgt_expansion)
@@ -982,9 +980,9 @@ class E2EFromParent(E2EBase):
 
     def get_kernel(self):
         if self.src_expansion is not self.tgt_expansion:
-            raise RuntimeError("%s requires that the source "
-                    "and target expansion are the same object"
-                    % self.default_name)
+            raise RuntimeError(
+                f"{self.default_name} requires that the source "
+                "and target expansion are the same object")
 
         ncoeffs = len(self.src_expansion)
 
diff --git a/sumpy/e2p.py b/sumpy/e2p.py
index 25e03242b4507f28c71cd3ca3a97d6b1b9af0889..421547d360da58f325f0d1cba4cabd3b570ca922 100644
--- a/sumpy/e2p.py
+++ b/sumpy/e2p.py
@@ -85,11 +85,12 @@ class E2PBase(KernelCacheWrapper):
         from sumpy.assignment_collection import SymbolicAssignmentCollection
         sac = SymbolicAssignmentCollection()
 
-        coeff_exprs = [sym.Symbol("coeff%d" % i)
+        coeff_exprs = [
+                sym.Symbol(f"coeff{i}")
                 for i in range(len(self.expansion.get_coefficient_identifiers()))]
 
         result_names = [
-            sac.assign_unique("result_%d_p" % i,
+            sac.assign_unique(f"result_{i}_p",
                 self.expansion.evaluate(knl, coeff_exprs, bvec, rscale, sac=sac))
             for i, knl in enumerate(self.kernels)
             ]
diff --git a/sumpy/expansion/__init__.py b/sumpy/expansion/__init__.py
index 7a145ede4d5446d7d3e40e7918e58cec95dfac6a..26c0d00a852c867fe89a23da876d5f87b98cee8b 100644
--- a/sumpy/expansion/__init__.py
+++ b/sumpy/expansion/__init__.py
@@ -179,8 +179,8 @@ class ExpansionBase:
             new_kwargs[name] = kwargs.pop(name, getattr(self, name))
 
         if kwargs:
-            raise TypeError("unexpected keyword arguments '%s'"
-                % ", ".join(kwargs))
+            raise TypeError(
+                "unexpected keyword arguments '{}'".format(", ".join(kwargs)))
 
         return type(self)(**new_kwargs)
 
@@ -236,8 +236,8 @@ class ExpansionTermsWrangler:
             new_kwargs[name] = kwargs.pop(name, getattr(self, name))
 
         if kwargs:
-            raise TypeError("unexpected keyword arguments '%s'"
-                % ", ".join(kwargs))
+            raise TypeError(
+                "unexpected keyword arguments '{}'".format(", ".join(kwargs)))
 
         return type(self)(**new_kwargs)
 
diff --git a/sumpy/expansion/level_to_order.py b/sumpy/expansion/level_to_order.py
index 7b50c499065374eca099223b5199c63aad7451b5..cc8440fd6989edd33e14169e4e8c066ed794e1ed 100644
--- a/sumpy/expansion/level_to_order.py
+++ b/sumpy/expansion/level_to_order.py
@@ -57,12 +57,12 @@ class FMMLibExpansionOrderFinder:
             if tree.dimensions == 2:
                 nterms, ier = pyfmmlib.l2dterms(self.tol)
                 if ier:
-                    raise RuntimeError("l2dterms returned error code '%d'" % ier)
+                    raise RuntimeError(f"l2dterms returned error code '{ier}'")
 
             elif tree.dimensions == 3:
                 nterms, ier = pyfmmlib.l3dterms(self.tol)
                 if ier:
-                    raise RuntimeError("l3dterms returned error code '%d'" % ier)
+                    raise RuntimeError(f"l3dterms returned error code '{ier}'")
 
         elif isinstance(kernel, HelmholtzKernel):
             helmholtz_k = dict(kernel_args)[kernel.helmholtz_k_name]
@@ -71,12 +71,12 @@ class FMMLibExpansionOrderFinder:
             if tree.dimensions == 2:
                 nterms, ier = pyfmmlib.h2dterms(size, helmholtz_k, self.tol)
                 if ier:
-                    raise RuntimeError("h2dterms returned error code '%d'" % ier)
+                    raise RuntimeError(f"h2dterms returned error code '{ier}'")
 
             elif tree.dimensions == 3:
                 nterms, ier = pyfmmlib.h3dterms(size, helmholtz_k, self.tol)
                 if ier:
-                    raise RuntimeError("h3dterms returned error code '%d'" % ier)
+                    raise RuntimeError(f"h3dterms returned error code '{ier}'")
 
         return nterms + self.extra_order
 
diff --git a/sumpy/expansion/local.py b/sumpy/expansion/local.py
index 145a043b8302cf217254cb2c3898acf9a65315fd..f760998dbd98284fd082453ade3e36d17ae9bfb1 100644
--- a/sumpy/expansion/local.py
+++ b/sumpy/expansion/local.py
@@ -997,9 +997,9 @@ class _FourierBesselLocalExpansion(LocalExpansionBase):
 
             return translated_coeffs
 
-        raise RuntimeError("do not know how to translate %s to %s"
-                           % (type(src_expansion).__name__,
-                               type(self).__name__))
+        raise RuntimeError(
+            "do not know how to translate "
+            f"{type(src_expansion).__name__} to {type(self).__name__}")
 
     def loopy_translate_from(self, src_expansion):
         if isinstance(src_expansion, self.mpole_expn_class):
diff --git a/sumpy/expansion/multipole.py b/sumpy/expansion/multipole.py
index 112c719013a2189bca1d8635aab499746b37745c..4af5cec332e925cdd05a8d8bfc2331fe35a0b7db 100644
--- a/sumpy/expansion/multipole.py
+++ b/sumpy/expansion/multipole.py
@@ -109,20 +109,20 @@ class VolumeTaylorMultipoleExpansionBase(MultipoleExpansionBase):
     def translate_from(self, src_expansion, src_coeff_exprs, src_rscale,
             dvec, tgt_rscale, sac=None, _fast_version=True):
         if not isinstance(src_expansion, type(self)):
-            raise RuntimeError("do not know how to translate %s to "
-                    "Taylor multipole expansion"
-                               % type(src_expansion).__name__)
+            raise RuntimeError(
+                f"do not know how to translate {type(src_expansion).__name__} to "
+                "a Taylor multipole expansion")
 
         if not self.use_rscale:
             src_rscale = 1
             tgt_rscale = 1
 
-        logger.info("building translation operator for %s: %s(%d) -> %s(%d): start"
-                % (src_expansion.kernel,
+        logger.info("building translation operator for %s: %s(%d) -> %s(%d): start",
+                    src_expansion.kernel,
                     type(src_expansion).__name__,
                     src_expansion.order,
                     type(self).__name__,
-                    self.order))
+                    self.order)
 
         from sumpy.tools import mi_factorial
 
@@ -447,9 +447,9 @@ class _HankelBased2DMultipoleExpansion(MultipoleExpansionBase):
     def translate_from(self, src_expansion, src_coeff_exprs, src_rscale,
             dvec, tgt_rscale, sac=None):
         if not isinstance(src_expansion, type(self)):
-            raise RuntimeError("do not know how to translate %s to %s"
-                               % (type(src_expansion).__name__,
-                                   type(self).__name__))
+            raise RuntimeError(
+                "do not know how to translate "
+                f"{type(src_expansion).__name__} to {type(self).__name__}")
 
         if not self.use_rscale:
             src_rscale = 1
diff --git a/sumpy/kernel.py b/sumpy/kernel.py
index a2b6baa8798ca373bfd56ebdd9993a0188c1fc1a..d307e6aa78aab81799019464d62549a954f0133f 100644
--- a/sumpy/kernel.py
+++ b/sumpy/kernel.py
@@ -321,7 +321,7 @@ class ExpressionKernel(Kernel):
                 self.is_complex_valued)
 
     def __repr__(self):
-        return "ExprKnl%dD" % self.dim
+        return f"ExprKnl{self.dim}D"
 
     def get_expression(self, scaled_dist_vec):
         from sumpy.symbolic import PymbolicToSympyMapperWithSymbols
@@ -332,7 +332,7 @@ class ExpressionKernel(Kernel):
 
         from sumpy.symbolic import Symbol
         expr = expr.xreplace({
-            Symbol("d%d" % i): dist_vec_i
+            Symbol(f"d{i}"): dist_vec_i
             for i, dist_vec_i in enumerate(scaled_dist_vec)
             })
 
@@ -413,7 +413,7 @@ class LaplaceKernel(ExpressionKernel):
         return (self.dim,)
 
     def __repr__(self):
-        return "LapKnl%dD" % self.dim
+        return f"LapKnl{self.dim}D"
 
     mapper_method = "map_laplace_kernel"
 
@@ -461,7 +461,7 @@ class BiharmonicKernel(ExpressionKernel):
         return (self.dim,)
 
     def __repr__(self):
-        return "BiharmKnl%dD" % self.dim
+        return f"BiharmKnl{self.dim}D"
 
     mapper_method = "map_biharmonic_kernel"
 
@@ -523,8 +523,7 @@ class HelmholtzKernel(ExpressionKernel):
             self.allow_evanescent))
 
     def __repr__(self):
-        return "HelmKnl%dD(%s)" % (
-                self.dim, self.helmholtz_k_name)
+        return f"HelmKnl{self.dim}D({self.helmholtz_k_name})"
 
     def prepare_loopy_kernel(self, loopy_knl):
         from sumpy.codegen import register_bessel_callables
@@ -611,8 +610,7 @@ class YukawaKernel(ExpressionKernel):
         key_builder.rec(key_hash, (self.dim, self.yukawa_lambda_name))
 
     def __repr__(self):
-        return "YukKnl%dD(%s)" % (
-                self.dim, self.yukawa_lambda_name)
+        return f"YukKnl{self.dim}D({self.yukawa_lambda_name})"
 
     def prepare_loopy_kernel(self, loopy_knl):
         from sumpy.codegen import register_bessel_callables
@@ -721,7 +719,7 @@ class ElasticityKernel(ExpressionKernel):
         mapper(self.poisson_ratio)
 
     def __repr__(self):
-        return "ElasticityKnl%dD_%d%d" % (self.dim, self.icomp, self.jcomp)
+        return f"ElasticityKnl{self.dim}D_{self.icomp}{self.jcomp}"
 
     @memoize_method
     def get_args(self):
@@ -757,7 +755,7 @@ class StokesletKernel(ElasticityKernel):
         super().__init__(dim, icomp, jcomp, viscosity_mu, poisson_ratio)
 
     def __repr__(self):
-        return "StokesletKnl%dD_%d%d" % (self.dim, self.icomp, self.jcomp)
+        return f"StokesletKnl{self.dim}D_{self.icomp}{self.jcomp}"
 
 
 class StressletKernel(ExpressionKernel):
@@ -817,8 +815,7 @@ class StressletKernel(ExpressionKernel):
         mapper(self.viscosity_mu)
 
     def __repr__(self):
-        return "StressletKnl%dD_%d%d%d" % (self.dim, self.icomp, self.jcomp,
-                self.kcomp)
+        return f"StressletKnl{self.dim}D_{self.icomp}{self.jcomp}{self.kcomp}"
 
     @memoize_method
     def get_args(self):
@@ -896,7 +893,7 @@ class LineOfCompressionKernel(ExpressionKernel):
         mapper(self.poisson_ratio)
 
     def __repr__(self):
-        return "LineOfCompressionKnl%dD_%d" % (self.dim, self.axis)
+        return f"LineOfCompressionKnl{self.dim}D_{self.axis}"
 
     @memoize_method
     def get_args(self):
@@ -984,10 +981,10 @@ class AxisSourceDerivative(DerivativeBase):
         return (self.axis, self.inner_kernel)
 
     def __str__(self):
-        return "d/dy%d %s" % (self.axis, self.inner_kernel)
+        return f"d/dy{self.axis} {self.inner_kernel}"
 
     def __repr__(self):
-        return "AxisSourceDerivative(%d, %r)" % (self.axis, self.inner_kernel)
+        return f"AxisSourceDerivative({self.axis}, {self.inner_kernel!r})"
 
     def get_derivative_coeff_dict_at_source(self, expr_dict):
         expr_dict = self.inner_kernel.get_derivative_coeff_dict_at_source(
@@ -1021,10 +1018,10 @@ class AxisTargetDerivative(DerivativeBase):
         return (self.axis, self.inner_kernel)
 
     def __str__(self):
-        return "d/dx%d %s" % (self.axis, self.inner_kernel)
+        return f"d/dx{self.axis} {self.inner_kernel}"
 
     def __repr__(self):
-        return "AxisTargetDerivative(%d, %r)" % (self.axis, self.inner_kernel)
+        return f"AxisTargetDerivative({self.axis}, {self.inner_kernel!r})"
 
     def postprocess_at_target(self, expr, bvec):
         from sumpy.tools import (DifferentiatedExprDerivativeTaker,
@@ -1076,7 +1073,7 @@ class DirectionalDerivative(DerivativeBase):
 
     def __init__(self, inner_kernel, dir_vec_name=None):
         if dir_vec_name is None:
-            dir_vec_name = self.directional_kind + "_derivative_dir"
+            dir_vec_name = f"{self.directional_kind}_derivative_dir"
 
         KernelWrapper.__init__(self, inner_kernel)
         self.dir_vec_name = dir_vec_name
@@ -1228,10 +1225,10 @@ class TargetPointMultiplier(KernelWrapper):
         return (self.axis, self.inner_kernel)
 
     def __str__(self):
-        return "x%d %s" % (self.axis, self.inner_kernel)
+        return f"x{self.axis} {self.inner_kernel}"
 
     def __repr__(self):
-        return "TargetPointMultiplier(%d, %r)" % (self.axis, self.inner_kernel)
+        return f"TargetPointMultiplier({self.axis}, {self.inner_kernel!r})"
 
     def replace_base_kernel(self, new_base_kernel):
         return type(self)(self.axis,
diff --git a/sumpy/p2p.py b/sumpy/p2p.py
index 21dde61efe9ca0553dfea2eb8e3c62b5f903065e..94b9362a19c8643c4874a1140c01ac33a19c9218 100644
--- a/sumpy/p2p.py
+++ b/sumpy/p2p.py
@@ -275,9 +275,10 @@ class P2PMatrixGenerator(P2PBase):
         loopy_insns, result_names = self.get_loopy_insns_and_result_names()
         arguments = (
             self.get_default_src_tgt_arguments()
-            + [lp.GlobalArg("result_%d" % i, dtype,
-                shape="ntargets,nsources")
-             for i, dtype in enumerate(self.value_dtypes)])
+            + [
+                lp.GlobalArg(f"result_{i}", dtype, shape="ntargets,nsources")
+                for i, dtype in enumerate(self.value_dtypes)
+            ])
 
         loopy_knl = lp.make_kernel(["""
             {[itgt, isrc, idim]: \
@@ -344,8 +345,10 @@ class P2PMatrixSubsetGenerator(P2PBase):
                 lp.GlobalArg("tgtindices", None, shape="nresult"),
                 lp.ValueArg("nresult", None)
             ]
-            + [lp.GlobalArg("result_%d" % i, dtype, shape="nresult")
-             for i, dtype in enumerate(self.value_dtypes)])
+            + [
+                lp.GlobalArg(f"result_{i}", dtype, shape="nresult")
+                for i, dtype in enumerate(self.value_dtypes)
+            ])
 
         loopy_knl = lp.make_kernel(
             "{[imat, idim]: 0 <= imat < nresult and 0 <= idim < dim}",
diff --git a/sumpy/point_calculus.py b/sumpy/point_calculus.py
index 1756f6e1cb7264c958c507d084d2fe9c8a40fd5d..b80ab51f06adda4c62fbfdd48b478794a45ed39e 100644
--- a/sumpy/point_calculus.py
+++ b/sumpy/point_calculus.py
@@ -78,7 +78,7 @@ class CalculusPatch:
             weights_1d = weights_1d * (h/2)
 
         else:
-            raise ValueError("invalid node set: %s" % nodes)
+            raise ValueError(f"invalid node set: {nodes}")
 
         self.h = h
         self.npoints = npoints
diff --git a/sumpy/qbx.py b/sumpy/qbx.py
index 5193c3b7736235d9c9204a2360b6b0ee60c3b558..b479e1db31b59d691a2c602080f5f7d3ef80055f 100644
--- a/sumpy/qbx.py
+++ b/sumpy/qbx.py
@@ -57,7 +57,7 @@ def stringify_expn_index(i):
     else:
         assert isinstance(i, int)
         if i < 0:
-            return "m%d" % (-i)
+            return f"m{-i}"
         else:
             return str(i)
 
@@ -110,12 +110,13 @@ class LayerPotentialBase(KernelComputation, KernelCacheWrapper):
 
         assigned_coeffs = [
             sym.Symbol(
-                sac.assign_unique("expn%dcoeff%s" % (
-                    expansion_nr, stringify_expn_index(i)),
-                        coefficients[self.expansion.get_storage_index(i)]))
+                sac.assign_unique(
+                    f"expn{expansion_nr}coeff{stringify_expn_index(i)}",
+                    coefficients[self.expansion.get_storage_index(i)])
+                )
             for i in self.expansion.get_coefficient_identifiers()]
 
-        return sac.assign_unique("expn%d_result" % expansion_nr,
+        return sac.assign_unique(f"expn{expansion_nr}_result",
             self.expansion.evaluate(tgt_knl, assigned_coeffs, bvec, rscale))
 
     def get_loopy_insns_and_result_names(self):
@@ -154,13 +155,14 @@ class LayerPotentialBase(KernelComputation, KernelCacheWrapper):
         return loopy_insns, result_names
 
     def get_strength_or_not(self, isrc, kernel_idx):
-        return var("strength_%d_isrc" % self.strength_usage[kernel_idx])
+        return var(f"strength_{self.strength_usage[kernel_idx]}_isrc")
 
     def get_kernel_exprs(self, result_names):
         exprs = [var(name) for i, name in enumerate(result_names)]
 
         return [lp.Assignment(id=None,
-                    assignee="pair_result_%d" % i, expression=expr,
+                    assignee=f"pair_result_{i}",
+                    expression=expr,
                     temp_var_type=lp.Optional(None))
                 for i, expr in enumerate(exprs)]
 
@@ -208,7 +210,7 @@ class LayerPotentialBase(KernelComputation, KernelCacheWrapper):
                     ["isrc_outer", f"{itgt_name}_inner"])
         else:
             from warnings import warn
-            warn("don't know how to tune layer potential computation for '%s'" % dev)
+            warn(f"don't know how to tune layer potential computation for '{dev}'")
             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)
 
@@ -233,12 +235,14 @@ class LayerPotential(LayerPotentialBase):
         kernel_exprs = self.get_kernel_exprs(result_names)
         arguments = (
             self.get_default_src_tgt_arguments()
-            + [lp.GlobalArg("strength_%d" % i,
-                None, shape="nsources", order="C")
-            for i in range(self.strength_count)]
-            + [lp.GlobalArg("result_%d" % i,
-                None, shape="ntargets", order="C")
-            for i in range(len(self.target_kernels))])
+            + [
+                lp.GlobalArg(f"strength_{i}", None, shape="nsources", order="C")
+                for i in range(self.strength_count)
+            ]
+            + [
+                lp.GlobalArg(f"result_{i}", None, shape="ntargets", order="C")
+                for i in range(len(self.target_kernels))
+            ])
 
         loopy_knl = lp.make_kernel(["""
             {[itgt, isrc, idim]: \
@@ -288,7 +292,7 @@ class LayerPotential(LayerPotentialBase):
                 centers_is_obj_array=is_obj_array_like(centers))
 
         for i, dens in enumerate(strengths):
-            kwargs["strength_%d" % i] = dens
+            kwargs[f"strength_{i}"] = dens
 
         return knl(queue, sources=sources, targets=targets, center=centers,
                 expansion_radii=expansion_radii, **kwargs)
@@ -312,9 +316,11 @@ class LayerPotentialMatrixGenerator(LayerPotentialBase):
         kernel_exprs = self.get_kernel_exprs(result_names)
         arguments = (
             self.get_default_src_tgt_arguments()
-            + [lp.GlobalArg("result_%d" % i,
-                dtype, shape="ntargets, nsources", order="C")
-             for i, dtype in enumerate(self.value_dtypes)])
+            + [
+                lp.GlobalArg(f"result_{i}",
+                             dtype, shape="ntargets, nsources", order="C")
+                for i, dtype in enumerate(self.value_dtypes)
+            ])
 
         loopy_knl = lp.make_kernel(["""
             {[itgt, isrc, idim]: \
@@ -384,8 +390,10 @@ class LayerPotentialMatrixSubsetGenerator(LayerPotentialBase):
                 lp.GlobalArg("tgtindices", None, shape="nresult"),
                 lp.ValueArg("nresult", None)
             ]
-            + [lp.GlobalArg("result_%d" % i, dtype, shape="nresult")
-             for i, dtype in enumerate(self.value_dtypes)])
+            + [
+                lp.GlobalArg(f"result_{i}", dtype, shape="nresult")
+                for i, dtype in enumerate(self.value_dtypes)
+            ])
 
         loopy_knl = lp.make_kernel([
             "{[imat, idim]: 0 <= imat < nresult and 0 <= idim < dim}"
@@ -510,8 +518,8 @@ def find_jump_term(kernel, arg_provider):
             src_derivatives.append(kernel.dir_vec_name)
             kernel = kernel.kernel
         else:
-            raise RuntimeError("derivative type '%s' not understood"
-                    % type(kernel))
+            raise RuntimeError(
+                f"derivative type '{type(kernel).__name__}' not understood")
 
     tgt_count = len(tgt_derivatives)
     src_count = len(src_derivatives)
@@ -568,8 +576,9 @@ def find_jump_term(kernel, arg_provider):
                     * info.tangent[i]
                     * info.density_prime)
 
-    raise ValueError("don't know jump term for %d "
-            "target and %d source derivatives" % (tgt_count, src_count))
+    raise ValueError(
+        f"Do not know jump term for {tgt_count} target "
+        f"and {src_count} source derivatives")
 
 
 # {{{ symbolic argument provider
@@ -596,77 +605,77 @@ class _JumpTermSymbolicArgumentProvider:
         self.arguments[self.density_var_name] = \
                 lp.GlobalArg(self.density_var_name, self.density_dtype,
                         shape="ntargets", order="C")
-        return parse("%s[itgt]" % self.density_var_name)
+        return parse(f"{self.density_var_name}[itgt]")
 
     @property
     @memoize_method
     def density_prime(self):
-        prime_var_name = self.density_var_name+"_prime"
-        self.arguments[prime_var_name] = \
+        prime_var_name = f"{self.density_var_name}_prime"
+        self.arguments[prime_var_name] = (
                 lp.GlobalArg(prime_var_name, self.density_dtype,
-                        shape="ntargets", order="C")
-        return parse("%s[itgt]" % prime_var_name)
+                             shape="ntargets", order="C"))
+        return parse(f"{prime_var_name}[itgt]")
 
     @property
     @memoize_method
     def side(self):
-        self.arguments["side"] = \
-                lp.GlobalArg("side", self.geometry_dtype, shape="ntargets")
+        self.arguments["side"] = (
+                lp.GlobalArg("side", self.geometry_dtype, shape="ntargets"))
         return parse("side[itgt]")
 
     @property
     @memoize_method
     def normal(self):
-        self.arguments["normal"] = \
+        self.arguments["normal"] = (
                 lp.GlobalArg("normal", self.geometry_dtype,
-                        shape=("ntargets", self.dim), order="C")
+                             shape=("ntargets", self.dim), order="C"))
         from pytools.obj_array import make_obj_array
         return make_obj_array([
-            parse("normal[itgt, %d]" % i)
+            parse(f"normal[itgt, {i}]")
             for i in range(self.dim)])
 
     @property
     @memoize_method
     def tangent(self):
-        self.arguments["tangent"] = \
+        self.arguments["tangent"] = (
                 lp.GlobalArg("tangent", self.geometry_dtype,
-                        shape=("ntargets", self.dim), order="C")
+                             shape=("ntargets", self.dim), order="C"))
         from pytools.obj_array import make_obj_array
         return make_obj_array([
-            parse("tangent[itgt, %d]" % i)
+            parse(f"tangent[itgt, {i}]")
             for i in range(self.dim)])
 
     @property
     @memoize_method
     def mean_curvature(self):
-        self.arguments["mean_curvature"] = \
+        self.arguments["mean_curvature"] = (
                 lp.GlobalArg("mean_curvature",
-                        self.geometry_dtype, shape="ntargets",
-                        order="C")
+                             self.geometry_dtype, shape="ntargets",
+                             order="C"))
         return parse("mean_curvature[itgt]")
 
     @property
     @memoize_method
     def src_derivative_dir(self):
-        self.arguments["src_derivative_dir"] = \
+        self.arguments["src_derivative_dir"] = (
                 lp.GlobalArg("src_derivative_dir",
-                        self.geometry_dtype, shape=("ntargets", self.dim),
-                        order="C")
+                             self.geometry_dtype, shape=("ntargets", self.dim),
+                             order="C"))
         from pytools.obj_array import make_obj_array
         return make_obj_array([
-            parse("src_derivative_dir[itgt, %d]" % i)
+            parse(f"src_derivative_dir[itgt, {i}]")
             for i in range(self.dim)])
 
     @property
     @memoize_method
     def tgt_derivative_dir(self):
-        self.arguments["tgt_derivative_dir"] = \
+        self.arguments["tgt_derivative_dir"] = (
                 lp.GlobalArg("tgt_derivative_dir",
-                        self.geometry_dtype, shape=("ntargets", self.dim),
-                        order="C")
+                             self.geometry_dtype, shape=("ntargets", self.dim),
+                             order="C"))
         from pytools.obj_array import make_obj_array
         return make_obj_array([
-            parse("tgt_derivative_dir[itgt, %d]" % i)
+            parse(f"tgt_derivative_dir[itgt, {i}]")
             for i in range(self.dim)])
 
 # }}}
diff --git a/sumpy/symbolic.py b/sumpy/symbolic.py
index 3b927f0ae4a50e761f59751df19d7ca2add0ea42..53f73e3c00ff63c48bd115a0b3d723b90aaeb0cb 100644
--- a/sumpy/symbolic.py
+++ b/sumpy/symbolic.py
@@ -49,14 +49,13 @@ def _find_symbolic_backend():
     if backend is not None:
         if backend not in ALLOWED_BACKENDS:
             raise RuntimeError(
-                "%s value is unrecognized: '%s' "
-                "(allowed values are %s)" % (
-                    BACKEND_ENV_VAR,
-                    backend,
-                    ", ".join("'%s'" % val for val in ALLOWED_BACKENDS)))
+                f"{BACKEND_ENV_VAR} value is unrecognized: '{backend}' "
+                "(allowed values are {})".format(
+                    ", ".join(f"'{val}'" for val in ALLOWED_BACKENDS))
+                )
 
         if backend == "symengine" and not symengine_found:
-            raise RuntimeError("could not find SymEngine: %s" % symengine_error)
+            raise RuntimeError(f"could not find SymEngine: {symengine_error}")
 
         USE_SYMENGINE = (backend == "symengine")
     else:
@@ -136,7 +135,7 @@ else:
 class _DerivativeKiller(IdentityMapperBase):
     def map_derivative(self, expr):
         from pymbolic import var
-        return var("d_"+"_".join(expr.variables))(expr.child)
+        return var("d_{}".format("_".join(expr.variables)))(expr.child)
 
     def map_substitution(self, expr):
         return self.rec(expr.child)
@@ -185,10 +184,10 @@ def checked_cse(exprs, symbols=None):
     new_assignments, new_exprs = sym.cse(exprs, **kwargs)
 
     max_old = _get_assignments_in_maxima({
-            "old_expr%d" % i: expr
+            f"old_expr{i}": expr
             for i, expr in enumerate(exprs)})
     new_ass_dict = {
-            "new_expr%d" % i: expr
+            f"new_expr{i}": expr
             for i, expr in enumerate(new_exprs)}
     for name, val in new_assignments:
         new_ass_dict[name.name] = val
@@ -196,11 +195,11 @@ def checked_cse(exprs, symbols=None):
 
     with open("check.mac", "w") as outf:
         outf.write("ratprint:false;\n")
-        outf.write("%s\n\n" % max_old)
-        outf.write("%s\n" % max_new)
+        outf.write(f"{max_old}\n\n")
+        outf.write(f"{max_new}\n")
         for i in range(len(exprs)):
-            outf.write('print("diff in expr %d:\n");\n' % i)
-            outf.write("print(ratsimp(old_expr%d - new_expr%d));\n" % (i, i))
+            outf.write(f'print("diff in expr {i}:\n");\n')
+            outf.write(f"print(ratsimp(old_expr{i} - new_expr{i}));\n")
 
     from subprocess import check_call
     check_call(["maxima", "--very-quiet", "-r", 'load("check.mac");'])
@@ -220,8 +219,7 @@ def pymbolic_real_norm_2(x):
 
 
 def make_sym_vector(name, components):
-    return sym.Matrix(
-            [sym.Symbol("%s%d" % (name, i)) for i in range(components)])
+    return sym.Matrix([sym.Symbol(f"{name}{i}") for i in range(components)])
 
 
 def vector_xreplace(expr, from_vec, to_vec):
@@ -254,7 +252,7 @@ class PymbolicToSympyMapperWithSymbols(PymbolicToSympyMapper):
 
     def map_subscript(self, expr):
         if isinstance(expr.aggregate, prim.Variable) and isinstance(expr.index, int):
-            return sym.Symbol("%s%d" % (expr.aggregate.name, expr.index))
+            return sym.Symbol(f"{expr.aggregate.name}{expr.index}")
         else:
             self.raise_conversion_error(expr)
 
diff --git a/sumpy/tools.py b/sumpy/tools.py
index b43dedbf2717b7a57ceeecbc38ccc20b83d696ad..84b97927a153d24387aa2965a539080dba7a0180 100644
--- a/sumpy/tools.py
+++ b/sumpy/tools.py
@@ -629,7 +629,7 @@ class KernelComputation:
         import loopy as lp
         return [
                 lp.Assignment(id=None,
-                    assignee="knl_%d_scaling" % i,
+                    assignee=f"knl_{i}_scaling",
                     expression=sympy_conv(kernel.get_global_scaling_const()),
                     temp_var_type=lp.Optional(dtype),
                     tags=frozenset([ScalingAssignmentTag()]))
@@ -735,7 +735,7 @@ class KernelCacheWrapper:
             except KeyError:
                 pass
 
-        logger.info("%s: kernel cache miss" % self.name)
+        logger.info("%s: kernel cache miss", self.name)
         if CACHING_ENABLED:
             logger.info("{}: kernel cache miss [key={}]".format(
                 self.name, cache_key))
diff --git a/sumpy/toys.py b/sumpy/toys.py
index 25596f90274fe513f888371bd679542eee3a07b3..3b8876dbc5612b03a7bf7de9cfb0815c76ac5377 100644
--- a/sumpy/toys.py
+++ b/sumpy/toys.py
@@ -512,8 +512,7 @@ def multipole_expand(psource, center, order=None, rscale=1, **expn_kwargs):
                 MultipoleExpansion, expn_kwargs)
 
     else:
-        raise TypeError("do not know how to expand '%s'"
-                % type(psource).__name__)
+        raise TypeError(f"do not know how to expand '{type(psource).__name__}'")
 
 
 def local_expand(psource, center, order=None, rscale=1, **expn_kwargs):
@@ -541,8 +540,7 @@ def local_expand(psource, center, order=None, rscale=1, **expn_kwargs):
                 LocalExpansion, expn_kwargs)
 
     else:
-        raise TypeError("do not know how to expand '%s'"
-                % type(psource).__name__)
+        raise TypeError(f"do not know how to expand '{type(psource).__name__}'")
 
 
 def logplot(fp, psource, **kwargs):
@@ -691,7 +689,7 @@ class SchematicVisitor:
             elif expn_style == "circle":
                 draw_circle(psource.center, psource.radius, fill=None)
             else:
-                raise ValueError("unknown expn_style: %s" % self.expn_style)
+                raise ValueError(f"unknown expn_style: {self.expn_style}")
 
         if psource.derived_from is None:
             return
diff --git a/sumpy/visualization.py b/sumpy/visualization.py
index 1447718dcaa7cc35d0a246e7f5e5187fd8192e37..c5ce571095c9bfcd4e05e72ad127f02ecb424059 100644
--- a/sumpy/visualization.py
+++ b/sumpy/visualization.py
@@ -44,8 +44,8 @@ def separate_by_real_and_imag(data, real_only):
         if real_only or entry_dtype.kind != "c":
             yield (name, obj_array_real_copy(field))
         else:
-            yield (name + "_r", obj_array_real_copy(field))
-            yield (name + "_i", obj_array_imag_copy(field))
+            yield (f"{name}_r", obj_array_real_copy(field))
+            yield (f"{name}_i", obj_array_imag_copy(field))
 
 
 def make_field_plotter_from_bbox(bbox, h, extend_factor=0):
diff --git a/test/test_fmm.py b/test/test_fmm.py
index d1fd9690f7f2b934ed152769f4ade7b99646c53a..5e505c4b65801a4c27d5bf3de6163697f25857d6 100644
--- a/test/test_fmm.py
+++ b/test/test_fmm.py
@@ -214,7 +214,7 @@ def test_sumpy_fmm(ctx_factory, knl, local_expn_class, mpole_expn_class,
         ref_pot = ref_pot.get()
 
         rel_err = la.norm(pot - ref_pot, np.inf) / la.norm(ref_pot, np.inf)
-        logger.info("order %d -> relative l2 error: %g" % (order, rel_err))
+        logger.info("order %d -> relative l2 error: %g", order, rel_err)
 
         pconv_verifier.add_data_point(order, rel_err)
 
@@ -437,7 +437,7 @@ def test_sumpy_fmm_exclude_self(ctx_factory):
     ref_pot = ref_pot.get()
 
     rel_err = la.norm(pot - ref_pot) / la.norm(ref_pot)
-    logger.info("order %d -> relative l2 error: %g" % (order, rel_err))
+    logger.info("order %d -> relative l2 error: %g", order, rel_err)
 
     assert np.isclose(rel_err, 0, atol=1e-7)
 
@@ -505,7 +505,7 @@ def test_sumpy_axis_source_derivative(ctx_factory):
         pots.append(pot.get())
 
     rel_err = la.norm(pots[0] + pots[1]) / la.norm(pots[0])
-    logger.info("order %d -> relative l2 error: %g" % (order, rel_err))
+    logger.info("order %d -> relative l2 error: %g", order, rel_err)
 
     assert np.isclose(rel_err, 0, atol=1e-5)
 
@@ -579,7 +579,7 @@ def test_sumpy_target_point_multiplier(ctx_factory, deriv_axes):
         ref_pot = pot1 * sources[0].get()
 
     rel_err = la.norm(pot0 - ref_pot) / la.norm(ref_pot)
-    logger.info("order %d -> relative l2 error: %g" % (order, rel_err))
+    logger.info("order %d -> relative l2 error: %g", order, rel_err)
 
     assert np.isclose(rel_err, 0, atol=1e-5)
 
diff --git a/test/test_kernels.py b/test/test_kernels.py
index cfc62ea7c40ad48e5940fbde48f998a00d2bbbf0..24eecd609e5218a37d0847c2f0d1f2e561403f52 100644
--- a/test/test_kernels.py
+++ b/test/test_kernels.py
@@ -768,7 +768,7 @@ def test_m2m_and_l2l_exprs_simpler(base_knl, local_expn_class, mpole_expn_class,
 
     from sumpy.symbolic import make_sym_vector, Symbol, USE_SYMENGINE
     dvec = make_sym_vector("d", knl.dim)
-    src_coeff_exprs = [Symbol("src_coeff%d" % i) for i in range(len(mpole_expn))]
+    src_coeff_exprs = [Symbol(f"src_coeff{i}") for i in range(len(mpole_expn))]
 
     src_rscale = 3
     tgt_rscale = 2
diff --git a/test/test_misc.py b/test/test_misc.py
index d10278b1d0f2e2b5170abbc1467afdc0f51cbc65..42956bc6eab80e9cfe325b71f2d5c9042a67bba9 100644
--- a/test/test_misc.py
+++ b/test/test_misc.py
@@ -265,7 +265,7 @@ def test_toy_p2e2e2p(ctx_factory, case):
 
     if not 0 <= case_conv_factor <= 1:
         raise ValueError(
-                "convergence factor not in valid range: %e" % case.conv_factor)
+            f"convergence factor not in valid range: {case_conv_factor}")
 
     from sumpy.expansion.local import VolumeTaylorLocalExpansion
     from sumpy.expansion.multipole import VolumeTaylorMultipoleExpansion