diff --git a/src/language/modules/SchemeModule.cpp b/src/language/modules/SchemeModule.cpp
index b4022d660475cf93eb9f9e1cb291622de22dba22..9ba22459f1884535c176f11325ed4e447a8fbf71 100644
--- a/src/language/modules/SchemeModule.cpp
+++ b/src/language/modules/SchemeModule.cpp
@@ -10,6 +10,7 @@
 #include <mesh/Mesh.hpp>
 #include <mesh/MeshData.hpp>
 #include <mesh/MeshDataManager.hpp>
+#include <mesh/MeshRandomizer.hpp>
 #include <scheme/AcousticSolver.hpp>
 #include <scheme/DirichletBoundaryConditionDescriptor.hpp>
 #include <scheme/DiscreteFunctionDescriptorP0.hpp>
@@ -92,6 +93,44 @@ SchemeModule::SchemeModule()
 
       ));
 
+  this->_addBuiltinFunction("randomizeMesh",
+                            std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<
+                              const IMesh>(std::shared_ptr<const IMesh>,
+                                           const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&)>>(
+
+                              [](std::shared_ptr<const IMesh> p_mesh,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list) -> std::shared_ptr<const IMesh> {
+                                switch (p_mesh->dimension()) {
+                                case 1: {
+                                  constexpr size_t Dimension = 1;
+                                  using MeshType             = Mesh<Connectivity<Dimension>>;
+                                  const MeshType& mesh       = dynamic_cast<const MeshType&>(*p_mesh);
+                                  MeshRandomizer randomizer(mesh, bc_descriptor_list);
+                                  return randomizer.getRandomizedMesh();
+                                }
+                                case 2: {
+                                  constexpr size_t Dimension = 2;
+                                  using MeshType             = Mesh<Connectivity<Dimension>>;
+                                  const MeshType& mesh       = dynamic_cast<const MeshType&>(*p_mesh);
+                                  MeshRandomizer randomizer(mesh, bc_descriptor_list);
+                                  return randomizer.getRandomizedMesh();
+                                }
+                                case 3: {
+                                  constexpr size_t Dimension = 3;
+                                  using MeshType             = Mesh<Connectivity<Dimension>>;
+                                  const MeshType& mesh       = dynamic_cast<const MeshType&>(*p_mesh);
+                                  MeshRandomizer randomizer(mesh, bc_descriptor_list);
+                                  return randomizer.getRandomizedMesh();
+                                }
+                                default: {
+                                  throw UnexpectedError("invalid mesh dimension");
+                                }
+                                }
+                              }
+
+                              ));
+
   this->_addBuiltinFunction("boundaryName",
                             std::make_shared<
                               BuiltinFunctionEmbedder<std::shared_ptr<const IBoundaryDescriptor>(const std::string&)>>(
diff --git a/src/mesh/MeshRandomizer.hpp b/src/mesh/MeshRandomizer.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7c77b7328c28cbf3d64756ecd59b1d9d28c787ff
--- /dev/null
+++ b/src/mesh/MeshRandomizer.hpp
@@ -0,0 +1,291 @@
+#ifndef MESH_RANDOMIZER_HPP
+#define MESH_RANDOMIZER_HPP
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/ItemValueUtils.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshNodeBoundary.hpp>
+#include <scheme/FixedBoundaryConditionDescriptor.hpp>
+#include <scheme/IBoundaryConditionDescriptor.hpp>
+#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
+#include <utils/RandomEngine.hpp>
+
+#include <variant>
+#include <vector>
+
+template <size_t Dimension>
+class MeshRandomizer
+{
+ private:
+  using Rd               = TinyVector<Dimension>;
+  using Rdxd             = TinyMatrix<Dimension>;
+  using ConnectivityType = Connectivity<Dimension>;
+  using MeshType         = Mesh<ConnectivityType>;
+
+  const MeshType& m_given_mesh;
+
+  class SymmetryBoundaryCondition;
+  class FixedBoundaryCondition;
+
+  using BoundaryCondition = std::variant<FixedBoundaryCondition, SymmetryBoundaryCondition>;
+
+  using BoundaryConditionList = std::vector<BoundaryCondition>;
+  BoundaryConditionList m_boundary_condition_list;
+
+  BoundaryConditionList
+  _getBCList(const MeshType& mesh,
+             const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
+  {
+    BoundaryConditionList bc_list;
+
+    constexpr ItemType FaceType = [] {
+      if constexpr (Dimension > 1) {
+        return ItemType::face;
+      } else {
+        return ItemType::node;
+      }
+    }();
+
+    for (const auto& bc_descriptor : bc_descriptor_list) {
+      bool is_valid_boundary_condition = true;
+
+      switch (bc_descriptor->type()) {
+      case IBoundaryConditionDescriptor::Type::symmetry: {
+        const SymmetryBoundaryConditionDescriptor& sym_bc_descriptor =
+          dynamic_cast<const SymmetryBoundaryConditionDescriptor&>(*bc_descriptor);
+        for (size_t i_ref_face_list = 0; i_ref_face_list < mesh.connectivity().template numberOfRefItemList<FaceType>();
+             ++i_ref_face_list) {
+          const auto& ref_face_list = mesh.connectivity().template refItemList<FaceType>(i_ref_face_list);
+          const RefId& ref          = ref_face_list.refId();
+          if (ref == sym_bc_descriptor.boundaryDescriptor()) {
+            SymmetryBoundaryCondition{MeshFlatNodeBoundary<Dimension>(mesh, ref_face_list)};
+            bc_list.push_back(SymmetryBoundaryCondition{MeshFlatNodeBoundary<Dimension>(mesh, ref_face_list)});
+          }
+        }
+        is_valid_boundary_condition = true;
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::fixed: {
+        const FixedBoundaryConditionDescriptor& fixed_bc_descriptor =
+          dynamic_cast<const FixedBoundaryConditionDescriptor&>(*bc_descriptor);
+        for (size_t i_ref_face_list = 0; i_ref_face_list < mesh.connectivity().template numberOfRefItemList<FaceType>();
+             ++i_ref_face_list) {
+          const auto& ref_face_list = mesh.connectivity().template refItemList<FaceType>(i_ref_face_list);
+          const RefId& ref          = ref_face_list.refId();
+          if (ref == fixed_bc_descriptor.boundaryDescriptor()) {
+            MeshNodeBoundary<Dimension> mesh_node_boundary{mesh, ref_face_list};
+
+            const auto& node_list = mesh_node_boundary.nodeList();
+
+            bc_list.push_back(FixedBoundaryCondition{node_list});
+          }
+        }
+        break;
+      }
+      default: {
+        is_valid_boundary_condition = false;
+      }
+      }
+      if (not is_valid_boundary_condition) {
+        std::ostringstream error_msg;
+        error_msg << *bc_descriptor << " is an invalid boundary condition for mesh randomizer";
+        throw NormalError(error_msg.str());
+      }
+    }
+
+    return bc_list;
+  }
+
+  void
+  _applyBC(NodeValue<Rd>& shift) const
+  {
+    std::cout << "applying " << m_boundary_condition_list.size() << " boundary conditions\n";
+    for (auto&& boundary_condition : m_boundary_condition_list) {
+      std::visit(
+        [&](auto&& bc) {
+          using BCType = std::decay_t<decltype(bc)>;
+          if constexpr (std::is_same_v<BCType, SymmetryBoundaryCondition>) {
+            const Rd& n = bc.outgoingNormal();
+
+            const Rdxd I   = identity;
+            const Rdxd nxn = tensorProduct(n, n);
+            const Rdxd P   = I - nxn;
+
+            const Array<const NodeId>& node_list = bc.nodeList();
+            parallel_for(
+              node_list.size(), PUGS_LAMBDA(const size_t i_node) {
+                const NodeId node_id = node_list[i_node];
+
+                shift[node_id] = P * shift[node_id];
+              });
+
+          } else if constexpr (std::is_same_v<BCType, FixedBoundaryCondition>) {
+            const Array<const NodeId>& node_list = bc.nodeList();
+            parallel_for(
+              node_list.size(), PUGS_LAMBDA(const size_t i_node) {
+                const NodeId node_id = node_list[i_node];
+                shift[node_id]       = zero;
+              });
+
+          } else {
+            throw UnexpectedError("invalid boundary condition type");
+          }
+        },
+        boundary_condition);
+    }
+  }
+
+ public:
+  std::shared_ptr<const MeshType>
+  getRandomizedMesh() const
+  {
+    const ConnectivityType& connectivity = m_given_mesh.connectivity();
+    NodeValue<const Rd> given_xr         = m_given_mesh.xr();
+
+    auto edge_to_node_matrix = connectivity.edgeToNodeMatrix();
+    EdgeValue<double> edge_length{connectivity};
+    parallel_for(
+      connectivity.numberOfEdges(), PUGS_LAMBDA(const EdgeId edge_id) {
+        const auto& edge_nodes = edge_to_node_matrix[edge_id];
+        const NodeId node_0    = edge_nodes[0];
+        const NodeId node_1    = edge_nodes[1];
+        const Rd delta_x       = given_xr[node_0] - given_xr[node_1];
+
+        edge_length[edge_id] = std::sqrt(dot(delta_x, delta_x));
+      });
+
+    auto node_to_edge_matrix = connectivity.nodeToEdgeMatrix();
+    NodeValue<double> max_delta_xr{connectivity};
+    parallel_for(
+      connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) {
+        const auto& node_edges = node_to_edge_matrix[node_id];
+        double max_delta       = edge_length[node_edges[0]];
+
+        for (size_t i_edge = 1; i_edge < node_edges.size(); ++i_edge) {
+          const EdgeId edge_id = node_edges[i_edge];
+          max_delta            = std::min(max_delta, edge_length[edge_id]);
+        }
+        max_delta_xr[node_id] = max_delta;
+      });
+
+    synchronize(max_delta_xr);
+
+    std::uniform_real_distribution<> dis(-0.3, 0.3);
+
+    NodeValue<const int> node_numbers = connectivity.nodeNumber();
+    using IdCorrespondance            = std::pair<int, NodeId>;
+    Array<IdCorrespondance> node_numbers_to_node_id{node_numbers.numberOfItems()};
+    parallel_for(
+      node_numbers.numberOfItems(), PUGS_LAMBDA(const NodeId node_id) {
+        node_numbers_to_node_id[node_id] = std::make_pair(node_numbers[node_id], node_id);
+      });
+
+    std::sort(&node_numbers_to_node_id[0], &node_numbers_to_node_id[0] + node_numbers_to_node_id.size(),
+              [](IdCorrespondance& a, IdCorrespondance& b) { return a.first < b.first; });
+
+    RandomEngine& random_engine = RandomEngine::instance();
+
+    NodeValue<Rd> xr{connectivity};
+
+    int i_node_number = 0;
+    for (size_t i = 0; i < node_numbers_to_node_id.size(); ++i) {
+      const auto [node_number, node_id] = node_numbers_to_node_id[i];
+      for (; i_node_number < node_number; ++i_node_number) {
+        for (size_t j = 0; j < Dimension; ++j) {
+          dis(random_engine.engine());
+        }
+      }
+
+      double max_delta = max_delta_xr[node_id];
+
+      Rd shift;
+      for (size_t i_component = 0; i_component < Dimension; ++i_component) {
+        shift[i_component] = max_delta * dis(random_engine.engine());
+      }
+
+      xr[node_id] = shift;
+
+      ++i_node_number;
+    }
+
+    const int max_node_number =
+      parallel::allReduceMax(node_numbers_to_node_id[node_numbers_to_node_id.size() - 1].first);
+
+    // Advances random engine to preserve CPU random number generators synchronization
+    for (; i_node_number < max_node_number; ++i_node_number) {
+      for (size_t j = 0; j < Dimension; ++j) {
+        dis(random_engine.engine());
+      }
+    }
+
+    this->_applyBC(xr);
+
+    parallel_for(
+      connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) { xr[node_id] += given_xr[node_id]; });
+
+    return std::make_shared<MeshType>(m_given_mesh.shared_connectivity(), xr);
+  }
+
+  MeshRandomizer(const MeshRandomizer&) = delete;
+  MeshRandomizer(MeshRandomizer&&)      = delete;
+
+  MeshRandomizer(const MeshType& given_mesh,
+                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
+    : m_given_mesh(given_mesh), m_boundary_condition_list(this->_getBCList(given_mesh, bc_descriptor_list))
+  {}
+
+  ~MeshRandomizer() = default;
+};
+
+template <size_t Dimension>
+class MeshRandomizer<Dimension>::SymmetryBoundaryCondition
+{
+ public:
+  using Rd = TinyVector<Dimension, double>;
+
+ private:
+  const MeshFlatNodeBoundary<Dimension> m_mesh_flat_node_boundary;
+
+ public:
+  const Rd&
+  outgoingNormal() const
+  {
+    return m_mesh_flat_node_boundary.outgoingNormal();
+  }
+
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_flat_node_boundary.nodeList();
+  }
+
+  SymmetryBoundaryCondition(const MeshFlatNodeBoundary<Dimension>& mesh_flat_node_boundary)
+    : m_mesh_flat_node_boundary(mesh_flat_node_boundary)
+  {
+    ;
+  }
+
+  ~SymmetryBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class MeshRandomizer<Dimension>::FixedBoundaryCondition
+{
+ private:
+  const Array<const NodeId> m_node_list;
+
+ public:
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_node_list;
+  }
+
+  FixedBoundaryCondition(const Array<const NodeId>& node_list) : m_node_list{node_list} {}
+
+  ~FixedBoundaryCondition() = default;
+};
+
+#endif   // MESH_RANDOMIZER_HPP