diff --git a/examples/dagrt-fusion.py b/examples/dagrt-fusion.py
index d4b977b129de7ef7019d4b237023c73095d91406..fef376c4f94a32725b9e2537d151901a0667f00f 100755
--- a/examples/dagrt-fusion.py
+++ b/examples/dagrt-fusion.py
@@ -460,7 +460,7 @@ def dg_flux(c, tpair):
             np.dot(v.int, normal),
             u.int * normal) - flux_weak
 
-    return sym.interp(tpair.dd, "all_faces")(c*flux_strong)
+    return sym.project(tpair.dd, "all_faces")(c*flux_strong)
 
 
 def get_strong_wave_op_with_discr_direct(cl_ctx, dims=2, order=4):
@@ -499,8 +499,8 @@ def get_strong_wave_op_with_discr_direct(cl_ctx, dims=2, order=4):
 
     rad_normal = sym.normal(BTAG_ALL, dims)
 
-    rad_u = sym.cse(sym.interp("vol", BTAG_ALL)(u))
-    rad_v = sym.cse(sym.interp("vol", BTAG_ALL)(v))
+    rad_u = sym.cse(sym.project("vol", BTAG_ALL)(u))
+    rad_v = sym.cse(sym.project("vol", BTAG_ALL)(v))
 
     rad_bc = sym.cse(join_fields(
             0.5*(rad_u - sign*np.dot(rad_normal, rad_v)),
@@ -627,7 +627,7 @@ class ExecutionMapperWithMemOpCounting(ExecutionMapperWrapper):
             profile_data["bytes_written"] = (
                     profile_data.get("bytes_written", 0) + result.nbytes)
 
-            if op.mapper_method == "map_interpolation":
+            if op.mapper_method == "map_projection":
                 profile_data["interp_bytes_read"] = (
                         profile_data.get("interp_bytes_read", 0) + field.nbytes)
                 profile_data["interp_bytes_written"] = (
@@ -649,7 +649,7 @@ class ExecutionMapperWithMemOpCounting(ExecutionMapperWrapper):
                     expr.op,
                     (
                         # TODO: Not comprehensive.
-                        sym_op.InterpolationOperator,
+                        sym_op.ProjectionOperator,
                         sym_op.RefFaceMassOperator,
                         sym_op.RefInverseMassOperator,
                         sym_op.OppositeInteriorFaceSwap)):
diff --git a/examples/wave/wave-eager.py b/examples/wave/wave-eager.py
index aa9e87ffa0a7885adf9cc8d67d82d1be15a44e93..f060c364b558273d7ab97d678f021ac6e30eabd6 100644
--- a/examples/wave/wave-eager.py
+++ b/examples/wave/wave-eager.py
@@ -38,7 +38,7 @@ from grudge.shortcuts import make_visualizer
 
 
 def interior_trace_pair(discr, vec):
-    i = discr.interp("vol", "int_faces", vec)
+    i = discr.project("vol", "int_faces", vec)
     e = with_object_array_or_scalar(
             lambda el: discr.opposite_face_connection()(el.queue, el),
             i)
@@ -69,15 +69,15 @@ def wave_flux(discr, c, w_tpair):
             0.5*normal_times(v_jump),
             )
 
-    return discr.interp(w_tpair.dd, "all_faces", c*flux_weak)
+    return discr.project(w_tpair.dd, "all_faces", c*flux_weak)
 
 
 def wave_operator(discr, c, w):
     u = w[0]
     v = w[1:]
 
-    dir_u = discr.interp("vol", BTAG_ALL, u)
-    dir_v = discr.interp("vol", BTAG_ALL, v)
+    dir_u = discr.project("vol", BTAG_ALL, u)
+    dir_v = discr.project("vol", BTAG_ALL, v)
     dir_bval = join_fields(dir_u, dir_v)
     dir_bc = join_fields(-dir_u, dir_v)
 
diff --git a/grudge/eager.py b/grudge/eager.py
index e64e2fa8344a848f4d0a218cfd0286ec9f896e2a..27125027ae0594858a777a86b0c1b8fd33203ddd 100644
--- a/grudge/eager.py
+++ b/grudge/eager.py
@@ -46,9 +46,16 @@ def without_queue(ary):
 
 class EagerDGDiscretization(DGDiscretizationWithBoundaries):
     def interp(self, src, tgt, vec):
+        from warnings import warn
+        warn("using 'interp' is deprecated, use 'project' instead.",
++                DeprecationWarning, stacklevel=2)
+
+        return self.project(src, tgt, vec)
+
+    def project(self, src, tgt, vec):
         if is_obj_array(vec):
             return with_object_array_or_scalar(
-                    lambda el: self.interp(src, tgt, el), vec)
+                    lambda el: self.project(src, tgt, el), vec)
 
         return self.connection_from_dds(src, tgt)(vec.queue, vec)
 
diff --git a/grudge/execution.py b/grudge/execution.py
index a95b15e75e43917688f2c3766651e3018ff9d526..7c3fbb07846880b64f01cab250379a2afb8e33db 100644
--- a/grudge/execution.py
+++ b/grudge/execution.py
@@ -250,7 +250,7 @@ class ExecutionMapper(mappers.Evaluator,
 
         return result
 
-    def map_interpolation(self, op, field_expr):
+    def map_projection(self, op, field_expr):
         conn = self.discrwb.connection_from_dds(op.dd_in, op.dd_out)
         return conn(self.queue, self.rec(field_expr)).with_queue(self.queue)
 
diff --git a/grudge/models/advection.py b/grudge/models/advection.py
index 36ce5c155bde62a7314a3ec6f0d828e8968946a6..e160643d5232ca3e6d7c196afe39a6e96b9cf410 100644
--- a/grudge/models/advection.py
+++ b/grudge/models/advection.py
@@ -94,7 +94,7 @@ class StrongAdvectionOperator(AdvectionOperatorBase):
         u = sym.var("u")
 
         def flux(pair):
-            return sym.interp(pair.dd, "all_faces")(
+            return sym.project(pair.dd, "all_faces")(
                     self.flux(pair))
 
         return (
@@ -118,11 +118,11 @@ class WeakAdvectionOperator(AdvectionOperatorBase):
         u = sym.var("u")
 
         def flux(pair):
-            return sym.interp(pair.dd, "all_faces")(
+            return sym.project(pair.dd, "all_faces")(
                     self.flux(pair))
 
         bc_in = self.inflow_u
-        # bc_out = sym.interp("vol", self.outflow_tag)(u)
+        # bc_out = sym.project(sym.DD_VOLUME, self.outflow_tag)(u)
 
         return sym.InverseMassOperator()(
                 np.dot(
@@ -149,20 +149,20 @@ class VariableCoefficientAdvectionOperator(AdvectionOperatorBase):
         self.quad_tag = quad_tag
 
     def flux(self, u):
-        surf_v = sym.interp(sym.DD_VOLUME, u.dd)(self.v)
+        surf_v = sym.project(sym.DD_VOLUME, u.dd)(self.v)
         return advection_weak_flux(self.flux_type, u, surf_v)
 
     def sym_operator(self):
         u = sym.var("u")
 
         def flux(pair):
-            return sym.interp(pair.dd, face_dd)(self.flux(pair))
+            return sym.project(pair.dd, face_dd)(self.flux(pair))
 
         face_dd = sym.DOFDesc(sym.FACE_RESTR_ALL, self.quad_tag)
         boundary_dd = sym.DOFDesc(sym.BTAG_ALL, self.quad_tag)
         quad_dd = sym.DOFDesc(sym.DTAG_VOLUME_ALL, self.quad_tag)
 
-        to_quad = sym.interp(sym.DD_VOLUME, quad_dd)
+        to_quad = sym.project(sym.DD_VOLUME, quad_dd)
         stiff_t_op = sym.stiffness_t(self.ambient_dim,
                 dd_in=quad_dd, dd_out=sym.DD_VOLUME)
 
diff --git a/grudge/models/em.py b/grudge/models/em.py
index 225b3b4a411beae96359df0548ff5366f00ea675..f564196711a1b2749a1987d93cb6f2eeaf85eb74 100644
--- a/grudge/models/em.py
+++ b/grudge/models/em.py
@@ -191,8 +191,8 @@ class MaxwellOperator(HyperbolicOperator):
         "Construct part of the flux operator template for PEC boundary conditions"
         e, h = self.split_eh(w)
 
-        pec_e = sym.cse(sym.interp("vol", self.pec_tag)(e))
-        pec_h = sym.cse(sym.interp("vol", self.pec_tag)(h))
+        pec_e = sym.cse(sym.project("vol", self.pec_tag)(e))
+        pec_h = sym.cse(sym.project("vol", self.pec_tag)(h))
 
         return join_fields(-pec_e, pec_h)
 
@@ -200,8 +200,8 @@ class MaxwellOperator(HyperbolicOperator):
         "Construct part of the flux operator template for PMC boundary conditions"
         e, h = self.split_eh(w)
 
-        pmc_e = sym.cse(sym.interp("vol", self.pmc_tag)(e))
-        pmc_h = sym.cse(sym.interp("vol", self.pmc_tag)(h))
+        pmc_e = sym.cse(sym.project("vol", self.pmc_tag)(e))
+        pmc_h = sym.cse(sym.project("vol", self.pmc_tag)(h))
 
         return join_fields(pmc_e, -pmc_h)
 
@@ -221,8 +221,8 @@ class MaxwellOperator(HyperbolicOperator):
         absorb_Z = (mu/epsilon)**0.5  # noqa: N806
         absorb_Y = 1/absorb_Z  # noqa: N806
 
-        absorb_e = sym.cse(sym.interp("vol", self.absorb_tag)(e))
-        absorb_h = sym.cse(sym.interp("vol", self.absorb_tag)(h))
+        absorb_e = sym.cse(sym.project("vol", self.absorb_tag)(e))
+        absorb_h = sym.cse(sym.project("vol", self.absorb_tag)(h))
 
         bc = join_fields(
                 absorb_e + 1/2*(self.space_cross_h(absorb_normal, self.space_cross_e(
@@ -278,7 +278,7 @@ class MaxwellOperator(HyperbolicOperator):
                 ]
 
         def flux(pair):
-            return sym.interp(pair.dd, "all_faces")(self.flux(pair))
+            return sym.project(pair.dd, "all_faces")(self.flux(pair))
 
         return (
                 - self.local_derivatives(w)
diff --git a/grudge/models/wave.py b/grudge/models/wave.py
index 6defafc878f856fe83449b12dc055cfdf0b1f432..116cd329b0d648722b2537bb99545d783301b7b0 100644
--- a/grudge/models/wave.py
+++ b/grudge/models/wave.py
@@ -113,8 +113,8 @@ class StrongWaveOperator(HyperbolicOperator):
         # boundary conditions -------------------------------------------------
 
         # dirichlet BCs -------------------------------------------------------
-        dir_u = sym.cse(sym.interp("vol", self.dirichlet_tag)(u))
-        dir_v = sym.cse(sym.interp("vol", self.dirichlet_tag)(v))
+        dir_u = sym.cse(sym.project("vol", self.dirichlet_tag)(u))
+        dir_v = sym.cse(sym.project("vol", self.dirichlet_tag)(v))
         if self.dirichlet_bc_f:
             # FIXME
             from warnings import warn
@@ -129,15 +129,15 @@ class StrongWaveOperator(HyperbolicOperator):
         dir_bc = sym.cse(dir_bc, "dir_bc")
 
         # neumann BCs ---------------------------------------------------------
-        neu_u = sym.cse(sym.interp("vol", self.neumann_tag)(u))
-        neu_v = sym.cse(sym.interp("vol", self.neumann_tag)(v))
+        neu_u = sym.cse(sym.project("vol", self.neumann_tag)(u))
+        neu_v = sym.cse(sym.project("vol", self.neumann_tag)(v))
         neu_bc = sym.cse(join_fields(neu_u, -neu_v), "neu_bc")
 
         # radiation BCs -------------------------------------------------------
         rad_normal = sym.normal(self.radiation_tag, d)
 
-        rad_u = sym.cse(sym.interp("vol", self.radiation_tag)(u))
-        rad_v = sym.cse(sym.interp("vol", self.radiation_tag)(v))
+        rad_u = sym.cse(sym.project("vol", self.radiation_tag)(u))
+        rad_v = sym.cse(sym.project("vol", self.radiation_tag)(v))
 
         rad_bc = sym.cse(join_fields(
                 0.5*(rad_u - self.sign*np.dot(rad_normal, rad_v)),
@@ -148,7 +148,7 @@ class StrongWaveOperator(HyperbolicOperator):
         nabla = sym.nabla(d)
 
         def flux(pair):
-            return sym.interp(pair.dd, "all_faces")(
+            return sym.project(pair.dd, "all_faces")(
                     self.flux(pair))
 
         result = (
@@ -253,8 +253,8 @@ class WeakWaveOperator(HyperbolicOperator):
         # boundary conditions -------------------------------------------------
 
         # dirichlet BCs -------------------------------------------------------
-        dir_u = sym.cse(sym.interp("vol", self.dirichlet_tag)(u))
-        dir_v = sym.cse(sym.interp("vol", self.dirichlet_tag)(v))
+        dir_u = sym.cse(sym.project("vol", self.dirichlet_tag)(u))
+        dir_v = sym.cse(sym.project("vol", self.dirichlet_tag)(v))
         if self.dirichlet_bc_f:
             # FIXME
             from warnings import warn
@@ -269,15 +269,15 @@ class WeakWaveOperator(HyperbolicOperator):
         dir_bc = sym.cse(dir_bc, "dir_bc")
 
         # neumann BCs ---------------------------------------------------------
-        neu_u = sym.cse(sym.interp("vol", self.neumann_tag)(u))
-        neu_v = sym.cse(sym.interp("vol", self.neumann_tag)(v))
+        neu_u = sym.cse(sym.project("vol", self.neumann_tag)(u))
+        neu_v = sym.cse(sym.project("vol", self.neumann_tag)(v))
         neu_bc = sym.cse(join_fields(neu_u, -neu_v), "neu_bc")
 
         # radiation BCs -------------------------------------------------------
         rad_normal = sym.normal(self.radiation_tag, d)
 
-        rad_u = sym.cse(sym.interp("vol", self.radiation_tag)(u))
-        rad_v = sym.cse(sym.interp("vol", self.radiation_tag)(v))
+        rad_u = sym.cse(sym.project("vol", self.radiation_tag)(u))
+        rad_v = sym.cse(sym.project("vol", self.radiation_tag)(v))
 
         rad_bc = sym.cse(join_fields(
                 0.5*(rad_u - self.sign*np.dot(rad_normal, rad_v)),
@@ -286,7 +286,7 @@ class WeakWaveOperator(HyperbolicOperator):
 
         # entire operator -----------------------------------------------------
         def flux(pair):
-            return sym.interp(pair.dd, "all_faces")(self.flux(pair))
+            return sym.project(pair.dd, "all_faces")(self.flux(pair))
 
         result = sym.InverseMassOperator()(
                 join_fields(
@@ -395,9 +395,9 @@ class VariableCoefficientWeakWaveOperator(HyperbolicOperator):
         # boundary conditions -------------------------------------------------
 
         # dirichlet BCs -------------------------------------------------------
-        dir_c = sym.cse(sym.interp("vol", self.dirichlet_tag)(self.c))
-        dir_u = sym.cse(sym.interp("vol", self.dirichlet_tag)(u))
-        dir_v = sym.cse(sym.interp("vol", self.dirichlet_tag)(v))
+        dir_c = sym.cse(sym.project("vol", self.dirichlet_tag)(self.c))
+        dir_u = sym.cse(sym.project("vol", self.dirichlet_tag)(u))
+        dir_v = sym.cse(sym.project("vol", self.dirichlet_tag)(v))
         if self.dirichlet_bc_f:
             # FIXME
             from warnings import warn
@@ -412,28 +412,28 @@ class VariableCoefficientWeakWaveOperator(HyperbolicOperator):
         dir_bc = sym.cse(dir_bc, "dir_bc")
 
         # neumann BCs ---------------------------------------------------------
-        neu_c = sym.cse(sym.interp("vol", self.neumann_tag)(self.c))
-        neu_u = sym.cse(sym.interp("vol", self.neumann_tag)(u))
-        neu_v = sym.cse(sym.interp("vol", self.neumann_tag)(v))
+        neu_c = sym.cse(sym.project("vol", self.neumann_tag)(self.c))
+        neu_u = sym.cse(sym.project("vol", self.neumann_tag)(u))
+        neu_v = sym.cse(sym.project("vol", self.neumann_tag)(v))
         neu_bc = sym.cse(join_fields(neu_c, neu_u, -neu_v), "neu_bc")
 
         # radiation BCs -------------------------------------------------------
         rad_normal = sym.normal(self.radiation_tag, d)
 
-        rad_c = sym.cse(sym.interp("vol", self.radiation_tag)(self.c))
-        rad_u = sym.cse(sym.interp("vol", self.radiation_tag)(u))
-        rad_v = sym.cse(sym.interp("vol", self.radiation_tag)(v))
+        rad_c = sym.cse(sym.project("vol", self.radiation_tag)(self.c))
+        rad_u = sym.cse(sym.project("vol", self.radiation_tag)(u))
+        rad_v = sym.cse(sym.project("vol", self.radiation_tag)(v))
 
         rad_bc = sym.cse(join_fields(rad_c,
-                0.5*(rad_u - sym.interp("vol", self.radiation_tag)(self.sign)
+                0.5*(rad_u - sym.project("vol", self.radiation_tag)(self.sign)
                     * np.dot(rad_normal, rad_v)),
                 0.5*rad_normal*(np.dot(rad_normal, rad_v)
-                    - sym.interp("vol", self.radiation_tag)(self.sign)*rad_u)
+                    - sym.project("vol", self.radiation_tag)(self.sign)*rad_u)
                 ), "rad_bc")
 
         # entire operator -----------------------------------------------------
         def flux(pair):
-            return sym.interp(pair.dd, "all_faces")(self.flux(pair))
+            return sym.project(pair.dd, "all_faces")(self.flux(pair))
 
         result = sym.InverseMassOperator()(
                 join_fields(
diff --git a/grudge/symbolic/mappers/__init__.py b/grudge/symbolic/mappers/__init__.py
index 36b57d50aaf333fbd3303fd72f1425f5de085086..6d060e4691256404e12b600f9dfa6236f1216e7d 100644
--- a/grudge/symbolic/mappers/__init__.py
+++ b/grudge/symbolic/mappers/__init__.py
@@ -131,7 +131,7 @@ class OperatorReducerMixin(LocalOpReducerMixin, FluxOpReducerMixin):
 
     map_elementwise_linear = _map_op_base
 
-    map_interpolation = _map_op_base
+    map_projection = _map_op_base
 
     map_elementwise_sum = _map_op_base
     map_elementwise_min = _map_op_base
@@ -184,7 +184,7 @@ class IdentityMapperMixin(LocalOpReducerMixin, FluxOpReducerMixin):
         # it's a leaf--no changing children
         return expr
 
-    map_interpolation = map_elementwise_linear
+    map_projection = map_elementwise_linear
 
     map_elementwise_sum = map_elementwise_linear
     map_elementwise_min = map_elementwise_linear
@@ -358,9 +358,9 @@ class DOFDescReplacer(IdentityMapper):
             field = self.rec(expr.field)
             return op.OppositePartitionFaceSwap(dd_in=self.new_dd,
                                                 dd_out=self.new_dd)(field)
-        elif (isinstance(expr.op, op.InterpolationOperator)
+        elif (isinstance(expr.op, op.ProjectionOperator)
                     and expr.op.dd_out == self.prev_dd):
-            return op.InterpolationOperator(dd_in=expr.op.dd_in,
+            return op.ProjectionOperator(dd_in=expr.op.dd_in,
                                             dd_out=self.new_dd)(expr.field)
         elif (isinstance(expr.op, op.RefDiffOperatorBase)
                     and expr.op.dd_out == self.prev_dd
@@ -420,14 +420,14 @@ class DistributedMapper(CSECachingMapperMixin, IdentityMapper):
         from meshmode.mesh import BTAG_PARTITION
         from meshmode.discretization.connection import (FACE_RESTR_ALL,
                                                         FACE_RESTR_INTERIOR)
-        if (isinstance(expr.op, op.InterpolationOperator)
+        if (isinstance(expr.op, op.ProjectionOperator)
                 and expr.op.dd_in.domain_tag is FACE_RESTR_INTERIOR
                 and expr.op.dd_out.domain_tag is FACE_RESTR_ALL):
             distributed_work = 0
             for i_remote_part in self.connected_parts:
                 mapped_field = RankGeometryChanger(i_remote_part)(expr.field)
                 btag_part = BTAG_PARTITION(i_remote_part)
-                distributed_work += op.InterpolationOperator(dd_in=btag_part,
+                distributed_work += op.ProjectionOperator(dd_in=btag_part,
                                              dd_out=expr.op.dd_out)(mapped_field)
             return expr + distributed_work
         else:
@@ -458,9 +458,9 @@ class RankGeometryChanger(CSECachingMapperMixin, IdentityMapper):
                     dd_in=self.new_dd,
                     dd_out=self.new_dd,
                     unique_id=expr.op.unique_id)(field)
-        elif (isinstance(expr.op, op.InterpolationOperator)
+        elif (isinstance(expr.op, op.ProjectionOperator)
                     and expr.op.dd_out == self.prev_dd):
-            return op.InterpolationOperator(dd_in=expr.op.dd_in,
+            return op.ProjectionOperator(dd_in=expr.op.dd_in,
                                             dd_out=self.new_dd)(expr.field)
         elif (isinstance(expr.op, op.RefDiffOperator)
                     and expr.op.dd_out == self.prev_dd
@@ -824,8 +824,8 @@ class StringifyMapper(pymbolic.mapper.stringifier.StringifyMapper):
     def map_function_symbol(self, expr, enclosing_prec):
         return expr.name
 
-    def map_interpolation(self, expr, enclosing_prec):
-        return "Interp" + self._format_op_dd(expr)
+    def map_projection(self, expr, enclosing_prec):
+        return "Project" + self._format_op_dd(expr)
 
 
 class PrettyStringifyMapper(
@@ -882,14 +882,14 @@ class QuadratureCheckerAndRemover(CSECachingMapperMixin, IdentityMapper):
         import grudge.symbolic.operators as op
         # changed
 
-        if dd_in == dd_out and isinstance(expr.op, op.InterpolationOperator):
+        if dd_in == dd_out and isinstance(expr.op, op.ProjectionOperator):
             # This used to be to-quad interpolation and has become a no-op.
             # Remove it.
             return self.rec(expr.field)
 
         if isinstance(expr.op, op.StiffnessTOperator):
             new_op = type(expr.op)(expr.op.xyz_axis, dd_in, dd_out)
-        elif isinstance(expr.op, (op.FaceMassOperator, op.InterpolationOperator)):
+        elif isinstance(expr.op, (op.FaceMassOperator, op.ProjectionOperator)):
             new_op = type(expr.op)(dd_in, dd_out)
         else:
             raise NotImplementedError("do not know how to modify dd_in and dd_out "
@@ -952,7 +952,7 @@ class EmptyFluxKiller(CSECachingMapperMixin, IdentityMapper):
 
     def map_operator_binding(self, expr):
         from meshmode.mesh import is_boundary_tag_empty
-        if (isinstance(expr.op, sym.InterpolationOperator)
+        if (isinstance(expr.op, sym.ProjectionOperator)
                 and expr.op.dd_out.is_boundary_or_partition_interface()):
             domain_tag = expr.op.dd_out.domain_tag
             assert isinstance(domain_tag, sym.DTAG_BOUNDARY)
diff --git a/grudge/symbolic/operators.py b/grudge/symbolic/operators.py
index c4afc0ec32d0d2d1b8a1ecb79003cf919bd5ca18..b57f22fd3b9fdb412c13fd01df5adc4045e0ea6c 100644
--- a/grudge/symbolic/operators.py
+++ b/grudge/symbolic/operators.py
@@ -36,9 +36,9 @@ Basic Operators
 
 .. autoclass:: Operator
 .. autoclass:: ElementwiseLinearOperator
-.. autoclass:: InterpolationOperator
+.. autoclass:: ProjectionOperator
 
-.. data:: interp
+.. data:: project
 
 Reductions
 ^^^^^^^^^^
@@ -146,17 +146,17 @@ class ElementwiseLinearOperator(Operator):
     mapper_method = intern("map_elementwise_linear")
 
 
-class InterpolationOperator(Operator):
+class ProjectionOperator(Operator):
     def __init__(self, dd_in, dd_out):
-        super(InterpolationOperator, self).__init__(dd_in, dd_out)
+        super(ProjectionOperator, self).__init__(dd_in, dd_out)
 
     def __call__(self, expr):
         from pytools.obj_array import with_object_array_or_scalar
 
-        def interp_one(subexpr):
+        def project_one(subexpr):
             from pymbolic.primitives import is_constant
             if self.dd_in == self.dd_out:
-                # no-op interpolation, go away
+                # no-op projection, go away
                 return subexpr
             elif is_constant(subexpr):
                 return subexpr
@@ -164,12 +164,30 @@ class InterpolationOperator(Operator):
                 from grudge.symbolic.primitives import OperatorBinding
                 return OperatorBinding(self, subexpr)
 
-        return with_object_array_or_scalar(interp_one, expr)
+        return with_object_array_or_scalar(project_one, expr)
+
+    mapper_method = intern("map_projection")
+
+
+project = ProjectionOperator
+
+
+class InterpolationOperator(ProjectionOperator):
+    def __init__(self, dd_in, dd_out):
+        from warnings import warn
+        warn("'InterpolationOperator' is deprecated, "
+                "use 'ProjectionOperator' instead.",
+                DeprecationWarning, stacklevel=2)
+
+        super(InterpolationOperator, self).__init__(dd_in, dd_out)
 
-    mapper_method = intern("map_interpolation")
 
+def interp(dd_in, dd_out):
+    from warnings import warn
+    warn("using 'interp' is deprecated, use 'project' instead.",
+            DeprecationWarning, stacklevel=2)
 
-interp = InterpolationOperator
+    return ProjectionOperator(dd_in, dd_out)
 
 
 # {{{ element reduction: sum, min, max
diff --git a/grudge/symbolic/primitives.py b/grudge/symbolic/primitives.py
index d2f1a01f1c5415e7c2877625156bc0087ab104a6..b1f121d209b6fa74170877170512a98681bb3a01 100644
--- a/grudge/symbolic/primitives.py
+++ b/grudge/symbolic/primitives.py
@@ -513,8 +513,8 @@ def forward_metric_derivative(xyz_axis, rst_axis, dd=None):
     result = diff_op(NodeCoordinateComponent(xyz_axis, inner_dd))
 
     if dd.uses_quadrature():
-        from grudge.symbolic.operators import interp
-        result = interp(inner_dd, dd)(result)
+        from grudge.symbolic.operators import project
+        result = project(inner_dd, dd)(result)
 
     return cse(
             result,
@@ -649,15 +649,15 @@ def mv_normal(dd, ambient_dim, dim=None):
     if dim == 0:
         # NOTE: when the mesh is 0D, we do not have a clear notion of
         # `exterior normal`, so we just take the tangent of the 1D element,
-        # interpolate it to the element faces and make it signed
+        # project it to the element faces and make it signed
         tangent = parametrization_derivative(
                 ambient_dim, dim=1, dd=DD_VOLUME)
         tangent = tangent / sqrt(tangent.norm_squared())
 
-        from grudge.symbolic.operators import interp
-        interp = interp(DD_VOLUME, dd)
+        from grudge.symbolic.operators import project
+        project = project(DD_VOLUME, dd)
         mv = MultiVector(np.array([
-            mv.as_scalar() * interp(t) for t in tangent.as_vector()
+            mv.as_scalar() * project(t) for t in tangent.as_vector()
             ]))
 
     return cse(mv, "normal", cse_scope.DISCRETIZATION)
@@ -721,15 +721,15 @@ class TracePair:
 
 
 def int_tpair(expression, qtag=None):
-    from grudge.symbolic.operators import interp, OppositeInteriorFaceSwap
+    from grudge.symbolic.operators import project, OppositeInteriorFaceSwap
 
-    i = interp("vol", "int_faces")(expression)
+    i = project("vol", "int_faces")(expression)
     e = cse(OppositeInteriorFaceSwap()(i))
 
     if qtag is not None and qtag != QTAG_NONE:
         q_dd = DOFDesc("int_faces", qtag)
-        i = cse(interp("int_faces", q_dd)(i))
-        e = cse(interp("int_faces", q_dd)(e))
+        i = cse(project("int_faces", q_dd)(i))
+        e = cse(project("int_faces", q_dd)(e))
     else:
         q_dd = "int_faces"
 
@@ -757,8 +757,8 @@ def bv_tpair(dd, interior, exterior):
         representing the exterior value to be used
         for the flux.
     """
-    from grudge.symbolic.operators import interp
-    interior = cse(interp("vol", dd)(interior))
+    from grudge.symbolic.operators import project
+    interior = cse(project("vol", dd)(interior))
     return TracePair(dd, interior, exterior)
 
 # }}}
diff --git a/test/test_grudge.py b/test/test_grudge.py
index fc20811f9e126dea877fa61d4111b3b336db606f..50cb3d0b2cba0c60e1a8241c900afd113bfc4208 100644
--- a/test/test_grudge.py
+++ b/test/test_grudge.py
@@ -231,7 +231,7 @@ def test_2d_gauss_theorem(ctx_factory):
                 ).sum())
             -  # noqa: W504
             sym.integral(
-                sym.interp("vol", sym.BTAG_ALL)(f(sym.nodes(2)))
+                sym.project("vol", sym.BTAG_ALL)(f(sym.nodes(2)))
                 .dot(sym.normal(sym.BTAG_ALL, 2)),
                 dd=sym.BTAG_ALL)
             )(queue)
diff --git a/test/test_mpi_communication.py b/test/test_mpi_communication.py
index f9b12d3b4ec8922204cb7201c715adbb51f037b4..0b5ae492798b2783564f4afc75ec3051a43c3461 100644
--- a/test/test_mpi_communication.py
+++ b/test/test_mpi_communication.py
@@ -70,15 +70,15 @@ def simple_mpi_communication_entrypoint():
     myfunc = bind(vol_discr, myfunc_symb)(queue)
 
     sym_all_faces_func = sym.cse(
-        sym.interp("vol", "all_faces")(sym.var("myfunc")))
+        sym.project("vol", "all_faces")(sym.var("myfunc")))
     sym_int_faces_func = sym.cse(
-        sym.interp("vol", "int_faces")(sym.var("myfunc")))
+        sym.project("vol", "int_faces")(sym.var("myfunc")))
     sym_bdry_faces_func = sym.cse(
-        sym.interp(sym.BTAG_ALL, "all_faces")(
-            sym.interp("vol", sym.BTAG_ALL)(sym.var("myfunc"))))
+        sym.project(sym.BTAG_ALL, "all_faces")(
+            sym.project("vol", sym.BTAG_ALL)(sym.var("myfunc"))))
 
     bound_face_swap = bind(vol_discr,
-        sym.interp("int_faces", "all_faces")(
+        sym.project("int_faces", "all_faces")(
             sym.OppositeInteriorFaceSwap("int_faces")(
                 sym_int_faces_func)
             ) - (sym_all_faces_func - sym_bdry_faces_func)