diff --git a/src/language/modules/MeshModule.cpp b/src/language/modules/MeshModule.cpp
index 3d097d30761fda305ea6490d74da0fd19438a972..b0f1a2313a6db1a5164ea46862d4fd84d0ddd6a1 100644
--- a/src/language/modules/MeshModule.cpp
+++ b/src/language/modules/MeshModule.cpp
@@ -2,8 +2,11 @@
 
 #include <algebra/TinyVector.hpp>
 #include <language/node_processor/ExecutionPolicy.hpp>
+#include <language/utils/BinaryOperatorProcessorBuilder.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 #include <language/utils/FunctionTable.hpp>
+#include <language/utils/OStream.hpp>
+#include <language/utils/OperatorRepository.hpp>
 #include <language/utils/SymbolTable.hpp>
 #include <language/utils/TypeDescriptor.hpp>
 #include <mesh/CartesianMeshBuilder.hpp>
@@ -250,4 +253,10 @@ MeshModule::MeshModule()
 
 void
 MeshModule::registerOperators() const
-{}
+{
+  OperatorRepository& repository = OperatorRepository::instance();
+
+  repository.addBinaryOperator<language::shift_left_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::shift_left_op, std::shared_ptr<const OStream>,
+                                                    std::shared_ptr<const OStream>, std::shared_ptr<const IMesh>>>());
+}
diff --git a/src/mesh/Connectivity.cpp b/src/mesh/Connectivity.cpp
index a819af495a1dbb358a2705231e3ff30f2bcbed02..7b81eeb0326e7cd5bc5df2b9c6d45b642dcb5e56 100644
--- a/src/mesh/Connectivity.cpp
+++ b/src/mesh/Connectivity.cpp
@@ -241,6 +241,63 @@ Connectivity<Dimension>::_buildIsBoundaryNode() const
   }
 }
 
+template <ItemType item_type, size_t Dimension>
+inline void
+_printReference(std::ostream& os, const Connectivity<Dimension>& connectivity)
+{
+  auto count_all_items = [](const auto& item_is_owned) -> size_t {
+    using ItemId  = typename std::decay_t<decltype(item_is_owned)>::index_type;
+    size_t number = 0;
+    for (ItemId item_id = 0; item_id < item_is_owned.numberOfItems(); ++item_id) {
+      number += item_is_owned[item_id];
+    }
+    return parallel::allReduceSum(number);
+  };
+
+  auto count_zone_items = [](const auto& item_is_owned, const auto& item_list) -> size_t {
+    size_t number = 0;
+    for (size_t i_item = 0; i_item < item_list.size(); ++i_item) {
+      number += item_is_owned[item_list[i_item]];
+    }
+    return parallel::allReduceSum(number);
+  };
+
+  os << "- number of " << itemName(item_type) << "s: " << rang::fgB::yellow
+     << count_all_items(connectivity.template isOwned<item_type>()) << rang::style::reset << '\n';
+  os << "  " << rang::fgB::yellow << connectivity.template numberOfRefItemList<item_type>() << rang::style::reset
+     << " references\n";
+  if (connectivity.template numberOfRefItemList<item_type>() > 0) {
+    for (size_t i_ref_item = 0; i_ref_item < connectivity.template numberOfRefItemList<item_type>(); ++i_ref_item) {
+      auto ref_item_list = connectivity.template refItemList<item_type>(i_ref_item);
+      os << "  - " << rang::fgB::green << ref_item_list.refId().tagName() << rang::style::reset << " ("
+         << rang::fgB::green << ref_item_list.refId().tagNumber() << rang::style::reset << ") number "
+         << rang::fgB::yellow << count_zone_items(connectivity.template isOwned<item_type>(), ref_item_list.list())
+         << rang::style::reset << '\n';
+    }
+  }
+}
+
+template <size_t Dimension>
+std::ostream&
+Connectivity<Dimension>::_write(std::ostream& os) const
+{
+  os << "connectivity of dimension " << Dimension << '\n';
+  _printReference<ItemType::cell>(os, *this);
+  if constexpr (Dimension > 1) {
+    _printReference<ItemType::face>(os, *this);
+  }
+  if constexpr (Dimension > 2) {
+    _printReference<ItemType::edge>(os, *this);
+  }
+  _printReference<ItemType::node>(os, *this);
+
+  return os;
+}
+
+template std::ostream& Connectivity<1>::_write(std::ostream&) const;
+template std::ostream& Connectivity<2>::_write(std::ostream&) const;
+template std::ostream& Connectivity<3>::_write(std::ostream&) const;
+
 template void Connectivity<1>::_buildIsBoundaryFace() const;
 template void Connectivity<2>::_buildIsBoundaryFace() const;
 template void Connectivity<3>::_buildIsBoundaryFace() const;
diff --git a/src/mesh/Connectivity.hpp b/src/mesh/Connectivity.hpp
index 86b6606c7f24c2271569f07cbb5fe9d10a8b8fe3..b0279507ee41e6f6d3433bda2b5dd7beaf406265 100644
--- a/src/mesh/Connectivity.hpp
+++ b/src/mesh/Connectivity.hpp
@@ -29,6 +29,9 @@ class ConnectivityDescriptor;
 template <size_t Dim>
 class Connectivity final : public IConnectivity
 {
+ private:
+  std::ostream& _write(std::ostream&) const final;
+
  public:
   PUGS_INLINE
   static std::shared_ptr<Connectivity<Dim>> build(const ConnectivityDescriptor&);
diff --git a/src/mesh/IConnectivity.hpp b/src/mesh/IConnectivity.hpp
index def3475c386a3e3b321ea640cdf378456f39fac6..3ea34ab6e4fcf6935b456ed0c9cec1b65b79d44f 100644
--- a/src/mesh/IConnectivity.hpp
+++ b/src/mesh/IConnectivity.hpp
@@ -9,6 +9,9 @@
 
 class IConnectivity : public std::enable_shared_from_this<IConnectivity>
 {
+ protected:
+  virtual std::ostream& _write(std::ostream&) const = 0;
+
  public:
   template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
   friend class SubItemValuePerItem;
@@ -16,6 +19,12 @@ class IConnectivity : public std::enable_shared_from_this<IConnectivity>
   template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
   friend class SubItemArrayPerItem;
 
+  friend std::ostream&
+  operator<<(std::ostream& os, const IConnectivity& connectivity)
+  {
+    return connectivity._write(os);
+  }
+
   virtual const ConnectivityMatrix& getMatrix(const ItemType& item_type_0, const ItemType& item_type_1) const = 0;
 
   virtual size_t dimension() const = 0;
diff --git a/src/mesh/IMesh.hpp b/src/mesh/IMesh.hpp
index cb524849aaf0a7f994d8fb34fe2e97508c5e4f8a..350f0f2e660df2ae7070e3435b25eb53d19f61b5 100644
--- a/src/mesh/IMesh.hpp
+++ b/src/mesh/IMesh.hpp
@@ -2,12 +2,22 @@
 #define I_MESH_HPP
 
 #include <cstddef>
+#include <iostream>
 
 class IMesh
 {
+ protected:
+  virtual std::ostream& _write(std::ostream&) const = 0;
+
  public:
   virtual size_t dimension() const = 0;
 
+  friend std::ostream&
+  operator<<(std::ostream& os, const IMesh& mesh)
+  {
+    return mesh._write(os);
+  }
+
   IMesh(const IMesh&) = delete;
   IMesh(IMesh&&)      = delete;
 
diff --git a/src/mesh/Mesh.hpp b/src/mesh/Mesh.hpp
index e5817a2a9394b96bea04e78d061e166d3f0fbc02..52cdd8bbfb834c98aaf6242cefa6b0d726e06ef2 100644
--- a/src/mesh/Mesh.hpp
+++ b/src/mesh/Mesh.hpp
@@ -20,6 +20,12 @@ class Mesh final : public IMesh
   const std::shared_ptr<const Connectivity> m_connectivity;
   const NodeValue<const Rd> m_xr;
 
+  std::ostream&
+  _write(std::ostream& os) const final
+  {
+    return os << *m_connectivity;
+  }
+
  public:
   PUGS_INLINE
   size_t