diff --git a/src/language/modules/MeshModule.cpp b/src/language/modules/MeshModule.cpp
index 86c9a0c106e9fe6eece190c93365fc42a47bf864..61767078aeaa0f8362393196a01ce9cde167a064 100644
--- a/src/language/modules/MeshModule.cpp
+++ b/src/language/modules/MeshModule.cpp
@@ -12,52 +12,12 @@
 #include <mesh/DiamondDualMeshManager.hpp>
 #include <mesh/GmshReader.hpp>
 #include <mesh/Mesh.hpp>
+#include <mesh/MeshInterpoler.hpp>
+#include <mesh/MeshTransformer.hpp>
 #include <utils/Exceptions.hpp>
 
 #include <Kokkos_Core.hpp>
 
-template <typename T>
-class MeshTransformation;
-template <typename OutputType, typename InputType>
-class MeshTransformation<OutputType(InputType)> : public PugsFunctionAdapter<OutputType(InputType)>
-{
-  static constexpr size_t Dimension = OutputType::Dimension;
-  using Adapter                     = PugsFunctionAdapter<OutputType(InputType)>;
-
- public:
-  static inline std::shared_ptr<Mesh<Connectivity<Dimension>>>
-  transform(const FunctionSymbolId& function_symbol_id, std::shared_ptr<const IMesh> p_mesh)
-  {
-    using MeshType             = Mesh<Connectivity<Dimension>>;
-    const MeshType& given_mesh = dynamic_cast<const MeshType&>(*p_mesh);
-
-    auto& expression    = Adapter::getFunctionExpression(function_symbol_id);
-    auto convert_result = Adapter::getResultConverter(expression.m_data_type);
-
-    Array<ExecutionPolicy> context_list = Adapter::getContextList(expression);
-
-    NodeValue<const InputType> given_xr = given_mesh.xr();
-    NodeValue<OutputType> xr(given_mesh.connectivity());
-
-    using execution_space = typename Kokkos::DefaultExecutionSpace::execution_space;
-    Kokkos::Experimental::UniqueToken<execution_space, Kokkos::Experimental::UniqueTokenScope::Global> tokens;
-
-    parallel_for(given_mesh.numberOfNodes(), [=, &expression, &tokens](NodeId r) {
-      const int32_t t = tokens.acquire();
-
-      auto& execution_policy = context_list[t];
-
-      Adapter::convertArgs(execution_policy.currentContext(), given_xr[r]);
-      auto result = expression.execute(execution_policy);
-      xr[r]       = convert_result(std::move(result));
-
-      tokens.release(t);
-    });
-
-    return std::make_shared<MeshType>(given_mesh.shared_connectivity(), xr);
-  }
-};
-
 MeshModule::MeshModule()
 {
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IMesh>>);
@@ -78,23 +38,20 @@ MeshModule::MeshModule()
 
                               [](std::shared_ptr<const IMesh> p_mesh,
                                  const FunctionSymbolId& function_id) -> std::shared_ptr<const IMesh> {
-                                switch (p_mesh->dimension()) {
-                                case 1: {
-                                  using TransformT = TinyVector<1>(TinyVector<1>);
-                                  return MeshTransformation<TransformT>::transform(function_id, p_mesh);
-                                }
-                                case 2: {
-                                  using TransformT = TinyVector<2>(TinyVector<2>);
-                                  return MeshTransformation<TransformT>::transform(function_id, p_mesh);
-                                }
-                                case 3: {
-                                  using TransformT = TinyVector<3>(TinyVector<3>);
-                                  return MeshTransformation<TransformT>::transform(function_id, p_mesh);
-                                }
-                                default: {
-                                  throw UnexpectedError("invalid mesh dimension");
-                                }
-                                }
+                                return MeshTransformer{}.transform(function_id, p_mesh);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("interpolate",
+                            std::make_shared<BuiltinFunctionEmbedder<
+                              std::shared_ptr<const IMesh>(const std::shared_ptr<const IMesh>&,
+                                                           const std::shared_ptr<const IMesh>&, const double&)>>(
+
+                              [](const std::shared_ptr<const IMesh>& source_mesh,
+                                 const std::shared_ptr<const IMesh>& destination_mesh,
+                                 const double& theta) -> std::shared_ptr<const IMesh> {
+                                return MeshInterpoler{}.interpolate(source_mesh, destination_mesh, theta);
                               }
 
                               ));
diff --git a/src/mesh/CMakeLists.txt b/src/mesh/CMakeLists.txt
index c315ced7e506310c08c414d78b4df77aab646d4b..f536bae7afe8b6d1bc979e4abd9feccaf7c86a7d 100644
--- a/src/mesh/CMakeLists.txt
+++ b/src/mesh/CMakeLists.txt
@@ -20,9 +20,11 @@ add_library(
   MeshFaceBoundary.cpp
   MeshFlatFaceBoundary.cpp
   MeshFlatNodeBoundary.cpp
+  MeshInterpoler.cpp
   MeshLineNodeBoundary.cpp
   MeshNodeBoundary.cpp
   MeshRandomizer.cpp
+  MeshTransformer.cpp
   SynchronizerManager.cpp)
 
 # Additional dependencies
diff --git a/src/mesh/MeshInterpoler.cpp b/src/mesh/MeshInterpoler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..26cba4bff5eeca4b75690b47e30126bac070646b
--- /dev/null
+++ b/src/mesh/MeshInterpoler.cpp
@@ -0,0 +1,59 @@
+#include <mesh/MeshInterpoler.hpp>
+
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+
+template <typename ConnectivityType>
+std::shared_ptr<const Mesh<ConnectivityType>>
+MeshInterpoler::_interpolate(const Mesh<ConnectivityType>& source_mesh,
+                             const Mesh<ConnectivityType>& destination_mesh,
+                             const double& theta) const
+{
+  if (source_mesh.shared_connectivity() == destination_mesh.shared_connectivity()) {
+    const ConnectivityType& connectivity = source_mesh.connectivity();
+    NodeValue<TinyVector<ConnectivityType::Dimension>> theta_xr{connectivity};
+    const NodeValue<const TinyVector<ConnectivityType::Dimension>> source_xr      = source_mesh.xr();
+    const NodeValue<const TinyVector<ConnectivityType::Dimension>> destination_xr = destination_mesh.xr();
+
+    const double one_minus_theta = 1 - theta;
+    parallel_for(
+      connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) {
+        theta_xr[node_id] = one_minus_theta * source_xr[node_id] + theta * destination_xr[node_id];
+      });
+
+    return std::make_shared<Mesh<ConnectivityType>>(source_mesh.shared_connectivity(), theta_xr);
+  } else {
+    throw NormalError("interpolated meshes must share the same connectivity");
+  }
+}
+
+std::shared_ptr<const IMesh>
+MeshInterpoler::interpolate(const std::shared_ptr<const IMesh>& p_source_mesh,
+                            const std::shared_ptr<const IMesh>& p_destination_mesh,
+                            const double& theta) const
+{
+  if (p_source_mesh->dimension() != p_destination_mesh->dimension()) {
+    throw NormalError("incompatible mesh dimensions");
+  } else {
+    switch (p_source_mesh->dimension()) {
+    case 1: {
+      using MeshType = Mesh<Connectivity<1>>;
+      return this->_interpolate(dynamic_cast<const MeshType&>(*p_source_mesh),
+                                dynamic_cast<const MeshType&>(*p_destination_mesh), theta);
+    }
+    case 2: {
+      using MeshType = Mesh<Connectivity<2>>;
+      return this->_interpolate(dynamic_cast<const MeshType&>(*p_source_mesh),
+                                dynamic_cast<const MeshType&>(*p_destination_mesh), theta);
+    }
+    case 3: {
+      using MeshType = Mesh<Connectivity<3>>;
+      return this->_interpolate(dynamic_cast<const MeshType&>(*p_source_mesh),
+                                dynamic_cast<const MeshType&>(*p_destination_mesh), theta);
+    }
+    default: {
+      throw UnexpectedError("invalid mesh dimension");
+    }
+    }
+  }
+}
diff --git a/src/mesh/MeshInterpoler.hpp b/src/mesh/MeshInterpoler.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f8a79eea4918bf71b6b7586683a14f2a67194df9
--- /dev/null
+++ b/src/mesh/MeshInterpoler.hpp
@@ -0,0 +1,27 @@
+#ifndef MESH_INTERPOLER_HPP
+#define MESH_INTERPOLER_HPP
+
+class IMesh;
+
+template <typename ConnectivityType>
+class Mesh;
+
+#include <memory>
+
+class MeshInterpoler
+{
+ private:
+  template <typename ConnectivityType>
+  std::shared_ptr<const Mesh<ConnectivityType>> _interpolate(const Mesh<ConnectivityType>& source_mesh,
+                                                             const Mesh<ConnectivityType>& destination_mesh,
+                                                             const double& theta) const;
+
+ public:
+  std::shared_ptr<const IMesh> interpolate(const std::shared_ptr<const IMesh>& p_source_mesh,
+                                           const std::shared_ptr<const IMesh>& p_destination_mesh,
+                                           const double& theta) const;
+  MeshInterpoler()  = default;
+  ~MeshInterpoler() = default;
+};
+
+#endif   // MESH_INTERPOLER_HPP
diff --git a/src/mesh/MeshTransformer.cpp b/src/mesh/MeshTransformer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..67b3688926dcfb765c44799cff4674955515eb8b
--- /dev/null
+++ b/src/mesh/MeshTransformer.cpp
@@ -0,0 +1,71 @@
+#include <mesh/MeshTransformer.hpp>
+
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+
+#include <language/utils/FunctionTable.hpp>
+#include <language/utils/PugsFunctionAdapter.hpp>
+#include <language/utils/SymbolTable.hpp>
+
+template <typename OutputType, typename InputType>
+class MeshTransformer::MeshTransformation<OutputType(InputType)> : public PugsFunctionAdapter<OutputType(InputType)>
+{
+  static constexpr size_t Dimension = OutputType::Dimension;
+  using Adapter                     = PugsFunctionAdapter<OutputType(InputType)>;
+
+ public:
+  static inline std::shared_ptr<Mesh<Connectivity<Dimension>>>
+  transform(const FunctionSymbolId& function_symbol_id, std::shared_ptr<const IMesh> p_mesh)
+  {
+    using MeshType             = Mesh<Connectivity<Dimension>>;
+    const MeshType& given_mesh = dynamic_cast<const MeshType&>(*p_mesh);
+
+    auto& expression    = Adapter::getFunctionExpression(function_symbol_id);
+    auto convert_result = Adapter::getResultConverter(expression.m_data_type);
+
+    Array<ExecutionPolicy> context_list = Adapter::getContextList(expression);
+
+    NodeValue<const InputType> given_xr = given_mesh.xr();
+    NodeValue<OutputType> xr(given_mesh.connectivity());
+
+    using execution_space = typename Kokkos::DefaultExecutionSpace::execution_space;
+    Kokkos::Experimental::UniqueToken<execution_space, Kokkos::Experimental::UniqueTokenScope::Global> tokens;
+
+    parallel_for(given_mesh.numberOfNodes(), [=, &expression, &tokens](NodeId r) {
+      const int32_t t = tokens.acquire();
+
+      auto& execution_policy = context_list[t];
+
+      Adapter::convertArgs(execution_policy.currentContext(), given_xr[r]);
+      auto result = expression.execute(execution_policy);
+      xr[r]       = convert_result(std::move(result));
+
+      tokens.release(t);
+    });
+
+    return std::make_shared<MeshType>(given_mesh.shared_connectivity(), xr);
+  }
+};
+
+std::shared_ptr<const IMesh>
+MeshTransformer::transform(const FunctionSymbolId& function_id, std::shared_ptr<const IMesh> p_mesh)
+
+{
+  switch (p_mesh->dimension()) {
+  case 1: {
+    using TransformT = TinyVector<1>(TinyVector<1>);
+    return MeshTransformation<TransformT>::transform(function_id, p_mesh);
+  }
+  case 2: {
+    using TransformT = TinyVector<2>(TinyVector<2>);
+    return MeshTransformation<TransformT>::transform(function_id, p_mesh);
+  }
+  case 3: {
+    using TransformT = TinyVector<3>(TinyVector<3>);
+    return MeshTransformation<TransformT>::transform(function_id, p_mesh);
+  }
+  default: {
+    throw UnexpectedError("invalid mesh dimension");
+  }
+  }
+}
diff --git a/src/mesh/MeshTransformer.hpp b/src/mesh/MeshTransformer.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ce0a4d47eab0557004234d22ea5331a0d2b0f793
--- /dev/null
+++ b/src/mesh/MeshTransformer.hpp
@@ -0,0 +1,26 @@
+#ifndef MESH_TRANSFORMER_HPP
+#define MESH_TRANSFORMER_HPP
+
+class IMesh;
+
+template <typename ConnectivityType>
+class Mesh;
+
+class FunctionSymbolId;
+
+#include <memory>
+
+class MeshTransformer
+{
+  template <typename T>
+  class MeshTransformation;
+
+ public:
+  std::shared_ptr<const IMesh> transform(const FunctionSymbolId& function_symbol_id,
+                                         std::shared_ptr<const IMesh> p_mesh);
+
+  MeshTransformer()  = default;
+  ~MeshTransformer() = default;
+};
+
+#endif   // MESH_TRANSFORMER_HPP