diff --git a/CepGen/IO/FortranInterface.cpp b/CepGen/IO/FortranInterface.cpp
index 8fd67b2..b71aff5 100644
--- a/CepGen/IO/FortranInterface.cpp
+++ b/CepGen/IO/FortranInterface.cpp
@@ -1,20 +1,20 @@
 #include "CepGen/StructureFunctions/StructureFunctionsBuilder.h"
-#include "CepGen/IO/MSTWGridHandler.h"
+#include "CepGen/StructureFunctions/StructureFunctions.h"
 #include "CepGen/Core/Exception.h"
 
 extern "C"
 {
   void
   cepgen_structure_functions_( int& sfmode, double& q2, double& xbj, double& f2, double& fl )
   {
     using namespace CepGen;
     SF::Type sf_mode = (SF::Type)sfmode;
 
     CG_DEBUG( "cepgen_structure_functions" ) << sf_mode;
 
     StructureFunctions& val = ( *StructureFunctionsBuilder::get( sf_mode ) )( q2, xbj );
     f2 = val.F2;
     fl = val.FL;
   }
 }
 
diff --git a/CepGen/IO/GridHandler.h b/CepGen/IO/GridHandler.h
new file mode 100644
index 0000000..3f217c5
--- /dev/null
+++ b/CepGen/IO/GridHandler.h
@@ -0,0 +1,182 @@
+#ifndef CepGen_IO_GridHandler_h
+#define CepGen_IO_GridHandler_h
+
+#include <gsl/gsl_version.h>
+#ifdef GSL_MAJOR_VERSION
+#  if GSL_MAJOR_VERSION > 2 || ( GSL_MAJOR_VERSION == 2 && GSL_MINOR_VERSION >= 1 )
+#    include <gsl/gsl_interp2d.h>
+#    include <gsl/gsl_spline2d.h>
+#    define GOOD_GSL 1
+#  endif
+#endif
+#include <gsl/gsl_errno.h>
+#include <gsl/gsl_math.h>
+
+#include "CepGen/Core/Exception.h"
+#include <set>
+
+namespace CepGen
+{
+  class StructureFunctions;
+  enum struct GridType
+  {
+    kLinear = 0,
+    kLogarithmic = 1,
+    kSquare = 2
+  };
+  /// A generic class for 2-dimensional grid interpolation
+  /// \param N Number of values handled per point
+  template <size_t N=1>
+  class GridHandler
+  {
+    public:
+      /// A point and its associated value(s)
+      struct grid_t
+      {
+        /// First coordinate
+        float x;
+        /// Second coordinate
+        float y;
+        /// (list of) value(s) for this point
+        double value[N];
+      };
+
+    public:
+      explicit GridHandler( const GridType& grid_type ) :
+#ifdef GOOD_GSL
+        xacc_( gsl_interp_accel_alloc(), gsl_interp_accel_free ),
+        yacc_( gsl_interp_accel_alloc(), gsl_interp_accel_free ),
+#endif
+        grid_type_( grid_type )
+      {}
+      ~GridHandler() {
+#ifdef GOOD_GSL
+        for ( unsigned short i = 0; i < splines_.size(); ++i ) {
+          gsl_spline2d_free( splines_[i] );
+          if ( values_[i] )
+            delete[] values_[i];
+        }
+#endif
+      }
+
+      /// Interpolate a point to a given coordinate
+      grid_t eval( double x, double y ) const {
+        grid_t ev;
+        if ( grid_type_ == GridType::kLogarithmic ) {
+          x = log10( x );
+          y = log10( y );
+        }
+        else if ( grid_type_ == GridType::kSquare ) {
+          x *= x;
+          y *= y;
+        }
+#ifdef GOOD_GSL
+        unsigned short i = 0;
+        for ( auto& sp : splines_ )
+          if ( gsl_spline2d_eval_e( sp, x, y, xacc_.get(), yacc_.get(), &ev.value[i++] ) != GSL_SUCCESS )
+            CG_WARNING( "GridHandler" )
+              << "Failed to evaluate the grid value "
+              << "for x = " << x << " / y = " << y << ".";
+#else
+        double x_1 = -1., x_2 = -1., y_1 = -1., y_2 = -1.;
+        std::array<double,N> ext_11, ext_12, ext_21, ext_22;
+        for ( unsigned short i = 0; i < x_vals_.size()-1; ++i ) {
+          if ( x >= x_vals_.at( i ) && x < x_vals_.at( i+1 ) ) {
+            x_1 = x_vals_.at( i );
+            x_2 = x_vals_.at( i+1 );
+            break;
+          }
+        }
+        for ( unsigned int i = 0; i < y_vals_.size()-1; ++i ) {
+          if ( y >= y_vals_.at( i ) && y < y_vals_.at( i+1 ) ) {
+            y_1 = y_vals_.at( i );
+            y_2 = y_vals_.at( i+1 );
+            break;
+          }
+        }
+        for ( const auto& val : values_raw_ ) {
+          if ( x_1 == val.x && y_1 == val.y )
+            for ( unsigned short i = 0; i < ext_11.size(); ++i )
+              ext_11[i] = val.value[i];
+          if ( x_2 == val.x && y_1 == val.y )
+            for ( unsigned short i = 0; i < ext_12.size(); ++i )
+              ext_12[i] = val.value[i];
+          if ( x_1 == val.x && y_2 == val.y )
+            for ( unsigned short i = 0; i < ext_21.size(); ++i )
+              ext_21[i] = val.value[i];
+          if ( x_2 == val.x && y_2 == val.y )
+            for ( unsigned short i = 0; i < ext_22.size(); ++i )
+              ext_22[i] = val.value[i];
+        }
+        // now that we have the boundaries, we may interpolate
+        const double x2x1 = x_2-x_1;
+        const double y2y1 = y_2-y_1;
+        const double x2x = x_2-x;
+        const double y2y = y_2-y;
+        const double xx1 = x-x_1;
+        const double yy1 = y-y_1;
+        for ( unsigned short i = 0; i < ext_11.size(); ++i )
+          ev.value[i] = 1.0 / ( x2x1*y2y1 ) * ( ext_11[i]*x2x*y2y + ext_21[i]*xx1*y2y + ext_12[i]*x2x*yy1 + ext_22[i]*xx1*yy1 );
+#endif
+        return ev;
+      }
+
+      /// Return the list of values handled in the grid
+      std::vector<grid_t> values() const { return values_raw_; }
+
+    protected:
+      /// Initialise the grid and all useful interpolators/accelerators
+      void initGSL( const std::set<double>& x_vals, const std::set<double>& y_vals ) {
+#ifdef GOOD_GSL
+        gsl_set_error_handler_off();
+        const gsl_interp2d_type* type = gsl_interp2d_bilinear;
+
+        for ( unsigned short i = 0; i < splines_.size(); ++i ) {
+          values_[i] = new double[x_vals.size() * y_vals.size()];
+          splines_[i] = gsl_spline2d_alloc( type, x_vals.size(), y_vals.size() );
+        }
+
+        // second loop over all points to populate the grid
+        for ( const auto& val : values_raw_ ) {
+          // retrieve the index of the Q2/xbj bin in the set
+          const unsigned short id_x = std::distance( x_vals.begin(), x_vals.lower_bound( log10( val.x  ) ) ),
+                               id_y = std::distance( y_vals.begin(), y_vals.lower_bound( log10( val.y ) ) );
+          unsigned short i = 0;
+          for ( auto& sp : splines_ ) {
+            gsl_spline2d_set( sp, values_[i], id_x, id_y, val.value[i] );
+            ++i;
+          }
+        }
+
+        // initialise splines objects
+        std::vector<double> q2_vec( x_vals.begin(), x_vals.end() ), xbj_vec( y_vals.begin(), y_vals.end() );
+        for ( unsigned short i = 0; i < splines_.size(); ++i )
+          gsl_spline2d_init( splines_[i], &q2_vec[0], &xbj_vec[0], values_[i], x_vals.size(), y_vals.size() );
+#else
+        CG_WARNING( "GridHandler" )
+          << "GSL version ≥ 2.1 is required for spline bilinear interpolation.\n\t"
+          << "Version " << GSL_VERSION << " is installed on this system!\n\t"
+          << "Will use a linear approximation instead.\n\t"
+          << "You may check the numerical validity of this approach...";
+#endif
+      }
+
+      /// List of coordinates and associated value(s) in the grid
+      std::vector<grid_t> values_raw_;
+#ifdef GOOD_GSL
+      std::array<gsl_spline2d*,N> splines_;
+      std::unique_ptr<gsl_interp_accel,void(*)( gsl_interp_accel* )> xacc_, yacc_;
+      std::array<double*,N> values_;
+#else
+      /// List of coordinates handled in the grid
+      std::vector<double> x_vals_;
+      /// List of coordinates handled in the grid
+      std::vector<double> y_vals_;
+#endif
+      GridType grid_type_;
+
+  };
+}
+
+#endif
+
diff --git a/CepGen/IO/MSTWGridHandler.cpp b/CepGen/IO/MSTWGridHandler.cpp
deleted file mode 100644
index 37be5a1..0000000
--- a/CepGen/IO/MSTWGridHandler.cpp
+++ /dev/null
@@ -1,222 +0,0 @@
-#include "MSTWGridHandler.h"
-#include "CepGen/Core/Exception.h"
-#include "CepGen/Core/utils.h"
-#include "CepGen/StructureFunctions/StructureFunctions.h"
-
-#include <fstream>
-
-#include <gsl/gsl_errno.h>
-#include <gsl/gsl_math.h>
-
-namespace MSTW
-{
-  const unsigned int GridHandler::good_magic = 0x5754534d; // MSTW in ASCII
-
-  GridHandler&
-  GridHandler::get( const char* filename )
-  {
-    static GridHandler instance( filename );
-    return instance;
-  }
-
-  GridHandler::GridHandler( const char* filename )
-#ifdef GOOD_GSL
-    : xacc_( 0 ), yacc_( 0 ), values_( { { 0, 0 } } )
-#endif
-  {
-    std::set<double> q2_vals, xbj_vals;
-
-    { // file readout part
-      std::ifstream file( filename, std::ios::binary | std::ios::in );
-      if ( !file.is_open() )
-        throw CG_FATAL( "GridHandler" ) << "Impossible to load grid file \"" << filename << "%s\"!";
-
-      file.read( reinterpret_cast<char*>( &header_ ), sizeof( header_t ) );
-
-      // first checks on the file header
-
-      if ( header_.magic != good_magic )
-        throw CG_FATAL( "GridHandler" ) << "Wrong magic number retrieved: " << header_.magic << ", expecting " << good_magic << ".";
-
-      if ( header_.nucleon != header_t::proton )
-        throw CG_FATAL( "GridHandler" ) << "Only proton structure function grids can be retrieved for this purpose!";
-
-      // retrieve all points and evaluate grid boundaries
-
-      sfval_t val;
-      while ( file.read( reinterpret_cast<char*>( &val ), sizeof( sfval_t ) ) ) {
-        q2_vals.insert( log10( val.q2 ) );
-        xbj_vals.insert( log10( val.xbj ) );
-#ifndef GOOD_GSL
-        q2_vals_.emplace_back( val.q2 );
-        xbj_vals_.emplace_back( val.xbj );
-#endif
-        values_raw_.emplace_back( val );
-      }
-      file.close();
-    }
-
-    if ( q2_vals.size() < 2 || xbj_vals.size() < 2 )
-      throw CG_FATAL( "GridHandler" ) << "Invalid grid retrieved!";
-
-    initGSL( q2_vals, xbj_vals );
-
-    {
-      std::ostringstream ss_cl, ss_ord, ss_nucl;
-      ss_cl << header_.cl;
-      ss_ord << header_.order;
-      ss_nucl << header_.nucleon;
-      CG_INFO( "GridHandler" )
-        << "MSTW@" << header_.order << " grid evaluator built "
-        << "for " << header_.nucleon << " structure functions (" << header_.cl << ")\n\t"
-        << " Q² in range [" << pow( 10.,  *q2_vals.begin() ) << ":" << pow( 10.,  *q2_vals.rbegin() ) << "]\n\t"
-        << "xBj in range [" << pow( 10., *xbj_vals.begin() ) << ":" << pow( 10., *xbj_vals.rbegin() ) << "].";
-    }
-  }
-
-  GridHandler::~GridHandler()
-  {
-#ifdef GOOD_GSL
-    for ( unsigned short i = 0; i < num_functions_; ++i ) {
-      gsl_spline2d_free( splines_[i] );
-      if ( values_[i] ) delete[] values_[i];
-    }
-    if ( xacc_ ) gsl_interp_accel_free( xacc_ );
-    if ( yacc_ ) gsl_interp_accel_free( yacc_ );
-#endif
-  }
-
-  void
-  GridHandler::initGSL( const std::set<double>& q2_vals, const std::set<double>& xbj_vals )
-  {
-#ifdef GOOD_GSL
-    gsl_set_error_handler_off();
-    const gsl_interp2d_type* T = gsl_interp2d_bilinear;
-
-    for ( unsigned short i = 0; i < num_functions_; ++i ) {
-      values_[i] = new double[q2_vals.size() * xbj_vals.size()];
-      splines_[i] = gsl_spline2d_alloc( T, q2_vals.size(), xbj_vals.size() );
-    }
-    xacc_ = gsl_interp_accel_alloc();
-    yacc_ = gsl_interp_accel_alloc();
-
-    // second loop over all points to populate the grid
-    for ( const auto& val : values_raw_ ) {
-      // retrieve the index of the Q2/xbj bin in the set
-      const unsigned short id_q2  = std::distance(  q2_vals.begin(),  q2_vals.lower_bound( log10( val.q2  ) ) ),
-                           id_xbj = std::distance( xbj_vals.begin(), xbj_vals.lower_bound( log10( val.xbj ) ) );
-      gsl_spline2d_set( splines_[F2], values_[F2], id_q2, id_xbj, val.f2 );
-      gsl_spline2d_set( splines_[FL], values_[FL], id_q2, id_xbj, val.fl );
-    }
-
-    // initialise splines objects
-    std::vector<double> q2_vec( q2_vals.begin(), q2_vals.end() ), xbj_vec( xbj_vals.begin(), xbj_vals.end() );
-    for ( unsigned short i = 0; i < num_functions_; ++i ) {
-      gsl_spline2d_init( splines_[i], &q2_vec[0], &xbj_vec[0], values_[i], q2_vals.size(), xbj_vals.size() );
-    }
-#else
-    CG_WARNING( "GridHandler" )
-      << "GSL version ≥ 2.1 is required for spline bilinear interpolation.\n\t"
-      << "Version " << GSL_VERSION << " is installed on this system!\n\t"
-      << "Will use a linear approximation instead.\n\t"
-      << "You may check the numerical validity of this approach...";
-#endif
-  }
-
-  CepGen::StructureFunctions
-  GridHandler::eval( double q2, double xbj ) const
-  {
-    CepGen::StructureFunctions ev;
-#ifdef GOOD_GSL
-    if ( gsl_spline2d_eval_e( splines_[F2], log10( q2 ), log10( xbj ), xacc_, yacc_, &ev.F2 ) != GSL_SUCCESS
-      || gsl_spline2d_eval_e( splines_[FL], log10( q2 ), log10( xbj ), xacc_, yacc_, &ev.FL ) != GSL_SUCCESS ) {
-      CG_WARNING( "GridHandler" )
-        << "Failed to evaluate the structure functions "
-        << "for Q² = " << q2 << " GeV² / xbj = " << xbj << ".";
-      return ev;
-    }
-#else
-    double q2_1 = -1., q2_2 = -1., xbj_1 = -1., xbj_2 = -1.;
-    double f2_11 = 0., f2_12 = 0., f2_21 = 0., f2_22 = 0.;
-    double fl_11 = 0., fl_12 = 0., fl_21 = 0., fl_22 = 0.;
-    for ( unsigned int i = 0; i < q2_vals_.size()-1; ++i ) {
-      if ( q2 >= q2_vals_.at( i ) && q2 < q2_vals_.at( i+1 ) ) {
-        q2_1 = q2_vals_.at( i );
-        q2_2 = q2_vals_.at( i+1 );
-        break;
-      }
-    }
-    for ( unsigned int i = 0; i < xbj_vals_.size()-1; ++i ) {
-      if ( xbj >= xbj_vals_.at( i ) && xbj < xbj_vals_.at( i+1 ) ) {
-        xbj_1 = xbj_vals_.at( i );
-        xbj_2 = xbj_vals_.at( i+1 );
-        break;
-      }
-    }
-    for ( const auto& val : values_raw_ ) {
-      if ( q2_1 == val.q2 && xbj_1 == val.xbj ) {
-        f2_11 = val.f2;
-        fl_11 = val.fl;
-      }
-      if ( q2_2 == val.q2 && xbj_1 == val.xbj ) {
-        f2_12 = val.f2;
-        fl_12 = val.fl;
-      }
-      if ( q2_1 == val.q2 && xbj_2 == val.xbj ) {
-        f2_21 = val.f2;
-        fl_21 = val.fl;
-      }
-      if ( q2_2 == val.q2 && xbj_2 == val.xbj ) {
-        f2_22 = val.f2;
-        fl_22 = val.fl;
-      }
-    }
-    const double x2x1 = xbj_2-xbj_1;
-    const double y2y1 = q2_2-q2_1;
-    const double x2x = xbj_2-xbj;
-    const double y2y = q2_2-q2;
-    const double yy1 = q2-q2_1;
-    const double xx1 = xbj-xbj_1;
-    ev.F2 = 1.0 / (x2x1 * y2y1) * ( f2_11*x2x*y2y + f2_21*xx1*y2y + f2_12*x2x*yy1 + f2_22*xx1*yy1 );
-    ev.FL = 1.0 / (x2x1 * y2y1) * ( fl_11*x2x*y2y + fl_21*xx1*y2y + fl_12*x2x*yy1 + fl_22*xx1*yy1 );
-#endif
-    return ev;
-  }
-
-  std::ostream&
-  operator<<( std::ostream& os, const GridHandler::sfval_t& val )
-  {
-    return os << Form( "Q² = %.5e GeV²\txbj = %.4f\tF₂ = % .6e\tFL = % .6e", val.q2, val.xbj, val.f2, val.fl );
-  }
-
-  std::ostream&
-  operator<<( std::ostream& os, const GridHandler::header_t::order_t& order )
-  {
-    switch ( order ) {
-      case GridHandler::header_t::lo: return os << "LO";
-      case GridHandler::header_t::nlo: return os << "nLO";
-      case GridHandler::header_t::nnlo: return os << "nnLO";
-    }
-    return os;
-  }
-
-  std::ostream&
-  operator<<( std::ostream& os, const GridHandler::header_t::cl_t& cl )
-  {
-    switch ( cl ) {
-      case GridHandler::header_t::cl68: return os << "68% C.L.";
-      case GridHandler::header_t::cl95: return os << "95% C.L.";
-    }
-    return os;
-  }
-
-  std::ostream&
-  operator<<( std::ostream& os, const GridHandler::header_t::nucleon_t& nucl )
-  {
-    switch ( nucl ) {
-      case GridHandler::header_t::proton: return os << "proton";
-      case GridHandler::header_t::neutron: return os << "neutron";
-    }
-    return os;
-  }
-}
diff --git a/CepGen/IO/MSTWGridHandler.h b/CepGen/IO/MSTWGridHandler.h
deleted file mode 100644
index 33fcd8c..0000000
--- a/CepGen/IO/MSTWGridHandler.h
+++ /dev/null
@@ -1,75 +0,0 @@
-#ifndef CepGen_IO_MSTWGridHandler_h
-#define CepGen_IO_MSTWGridHandler_h
-
-#include <gsl/gsl_version.h>
-
-#ifdef GSL_MAJOR_VERSION
-#if GSL_MAJOR_VERSION > 2 || ( GSL_MAJOR_VERSION == 2 && GSL_MINOR_VERSION >= 1 )
-#include <gsl/gsl_interp2d.h>
-#include <gsl/gsl_spline2d.h>
-#define GOOD_GSL 1
-#endif
-#endif
-
-#include <array>
-#include <vector>
-#include <set>
-
-namespace CepGen { class StructureFunctions; }
-namespace MSTW
-{
-  class GridHandler
-  {
-    public:
-      struct sfval_t {
-        float q2, xbj;
-        double f2, fl;
-      };
-      struct header_t {
-        enum order_t : unsigned short { lo = 0, nlo = 1, nnlo = 2 };
-        enum cl_t : unsigned short { cl68 = 0, cl95 = 1 };
-        enum nucleon_t : unsigned short { proton = 1, neutron = 2 };
-        unsigned int magic;
-        order_t order;
-        cl_t cl;
-        nucleon_t nucleon;
-      };
-
-    public:
-      static GridHandler& get( const char* filename = "External/F2_Luxlike_fit/mstw_f2_scan_nnlo.dat" );
-      ~GridHandler();
-
-      CepGen::StructureFunctions eval( double q2, double xbj ) const;
-
-      header_t header() const { return header_; }
-      std::vector<sfval_t> values() const { return values_raw_; }
-
-    private:
-      explicit GridHandler( const char* );
-      void initGSL( const std::set<double>& q2_vals, const std::set<double>& xbj_vals );
-
-      enum spline_type { F2 = 0, FL = 1, num_functions_ };
-      static const unsigned int good_magic;
-
-      header_t header_;
-      std::vector<sfval_t> values_raw_;
-#ifdef GOOD_GSL
-      std::array<gsl_spline2d*,2> splines_;
-      gsl_interp_accel* xacc_, *yacc_;
-      std::array<double*,2> values_;
-#else
-      std::vector<double> xbj_vals_, q2_vals_;
-#endif
-
-    public:
-      GridHandler( const GridHandler& ) = delete;
-      void operator=( const GridHandler& ) = delete;
-  };
-  std::ostream& operator<<( std::ostream&, const GridHandler::sfval_t& );
-  std::ostream& operator<<( std::ostream&, const GridHandler::header_t::order_t& );
-  std::ostream& operator<<( std::ostream&, const GridHandler::header_t::cl_t& );
-  std::ostream& operator<<( std::ostream&, const GridHandler::header_t::nucleon_t& );
-}
-
-#endif
-
diff --git a/CepGen/StructureFunctions/MSTWGrid.cpp b/CepGen/StructureFunctions/MSTWGrid.cpp
new file mode 100644
index 0000000..1d7292a
--- /dev/null
+++ b/CepGen/StructureFunctions/MSTWGrid.cpp
@@ -0,0 +1,119 @@
+#include "CepGen/StructureFunctions/MSTWGrid.h"
+#include "CepGen/Core/Exception.h"
+#include "CepGen/Core/utils.h"
+#include "CepGen/StructureFunctions/StructureFunctions.h"
+
+#include <fstream>
+#include <atomic>
+
+#include <gsl/gsl_errno.h>
+#include <gsl/gsl_math.h>
+
+namespace MSTW
+{
+  const unsigned int Grid::good_magic = 0x5754534d; // MSTW in ASCII
+
+  Grid&
+  Grid::get( const char* filename )
+  {
+    Parameterisation p;
+    p.grid_path = filename;
+    static Grid instance( p );
+    return instance;
+  }
+
+  Grid::Grid( const Parameterisation& param ) :
+    CepGen::StructureFunctions( CepGen::SF::Type::MSTWgrid ),
+    CepGen::GridHandler<2>( CepGen::GridType::kLogarithmic ),
+    params( param )
+  {
+    std::set<double> q2_vals, xbj_vals;
+
+    { // file readout part
+      std::ifstream file( params.grid_path, std::ios::binary | std::ios::in );
+      if ( !file.is_open() )
+        throw CG_FATAL( "Grid" ) << "Impossible to load grid file \"" << params.grid_path << "\"!";
+
+      file.read( reinterpret_cast<char*>( &header_ ), sizeof( header_t ) );
+
+      // first checks on the file header
+
+      if ( header_.magic != good_magic )
+        throw CG_FATAL( "Grid" ) << "Wrong magic number retrieved: " << header_.magic << ", expecting " << good_magic << ".";
+
+      if ( header_.nucleon != header_t::proton )
+        throw CG_FATAL( "Grid" ) << "Only proton structure function grids can be retrieved for this purpose!";
+
+      // retrieve all points and evaluate grid boundaries
+
+      sfval_t val;
+      while ( file.read( reinterpret_cast<char*>( &val ), sizeof( sfval_t ) ) ) {
+        q2_vals.insert( log10( val.x ) );
+        xbj_vals.insert( log10( val.y ) );
+#ifndef GOOD_GSL
+        x_vals_.emplace_back( val.x );
+        y_vals_.emplace_back( val.y );
+#endif
+        values_raw_.emplace_back( val );
+      }
+      file.close();
+    }
+
+    if ( q2_vals.size() < 2 || xbj_vals.size() < 2 )
+      throw CG_FATAL( "Grid" ) << "Invalid grid retrieved!";
+
+    initGSL( q2_vals, xbj_vals );
+
+    CG_INFO( "Grid" )
+      << "MSTW@" << header_.order << " grid evaluator built "
+      << "for " << header_.nucleon << " structure functions (" << header_.cl << ")\n\t"
+      << " Q² in range [" << pow( 10., *q2_vals.begin() ) << ":" << pow( 10., *q2_vals.rbegin() ) << "]\n\t"
+      << "xBj in range [" << pow( 10., *xbj_vals.begin() ) << ":" << pow( 10., *xbj_vals.rbegin() ) << "].";
+  }
+
+  Grid&
+  Grid::operator()( double q2, double xbj )
+  {
+    const sfval_t val = CepGen::GridHandler<2>::eval( q2, xbj );
+    F2 = val.value[0];
+    FL = val.value[1];
+    return *this;
+  }
+
+  std::ostream&
+  operator<<( std::ostream& os, const sfval_t& val )
+  {
+    return os << Form( "Q² = %.5e GeV²\txbj = %.4f\tF₂ = % .6e\tFL = % .6e", val.x, val.y, val.value[0], val.value[1] );
+  }
+
+  std::ostream&
+  operator<<( std::ostream& os, const Grid::header_t::order_t& order )
+  {
+    switch ( order ) {
+      case Grid::header_t::lo: return os << "LO";
+      case Grid::header_t::nlo: return os << "nLO";
+      case Grid::header_t::nnlo: return os << "nnLO";
+    }
+    return os;
+  }
+
+  std::ostream&
+  operator<<( std::ostream& os, const Grid::header_t::cl_t& cl )
+  {
+    switch ( cl ) {
+      case Grid::header_t::cl68: return os << "68% C.L.";
+      case Grid::header_t::cl95: return os << "95% C.L.";
+    }
+    return os;
+  }
+
+  std::ostream&
+  operator<<( std::ostream& os, const Grid::header_t::nucleon_t& nucl )
+  {
+    switch ( nucl ) {
+      case Grid::header_t::proton: return os << "proton";
+      case Grid::header_t::neutron: return os << "neutron";
+    }
+    return os;
+  }
+}
diff --git a/CepGen/StructureFunctions/MSTWGrid.h b/CepGen/StructureFunctions/MSTWGrid.h
new file mode 100644
index 0000000..b2f5484
--- /dev/null
+++ b/CepGen/StructureFunctions/MSTWGrid.h
@@ -0,0 +1,61 @@
+#ifndef CepGen_StructureFunctions_MSTWGrid_h
+#define CepGen_StructureFunctions_MSTWGrid_h
+
+#include "CepGen/IO/GridHandler.h"
+#include "CepGen/StructureFunctions/StructureFunctions.h"
+
+/// Martin-Stirling-Thorne-Watt PDFs structure functions
+namespace MSTW
+{
+  /// \note x/y = Q2/xbj given by the parent
+  typedef CepGen::GridHandler<2>::grid_t sfval_t;
+  std::ostream& operator<<( std::ostream&, const sfval_t& );
+
+  /// A \f$F_{2,L}\f$ grid interpolator
+  class Grid : public CepGen::StructureFunctions, private CepGen::GridHandler<2>
+  {
+    public:
+      /// Grid header information as parsed from the file
+      struct header_t {
+        enum order_t : unsigned short { lo = 0, nlo = 1, nnlo = 2 };
+        enum cl_t : unsigned short { cl68 = 0, cl95 = 1 };
+        enum nucleon_t : unsigned short { proton = 1, neutron = 2 };
+        unsigned int magic;
+        order_t order;
+        cl_t cl;
+        nucleon_t nucleon;
+      };
+      struct Parameterisation {
+        Parameterisation() : grid_path( "External/F2_Luxlike_fit/mstw_f2_scan_nnlo.dat" ) {}
+        std::string grid_path;
+      };
+
+    public:
+      /// Retrieve the grid interpolator (singleton)
+      static Grid& get( const char* path = "External/F2_Luxlike_fit/mstw_f2_scan_nnlo.dat" );
+
+      /// Compute the structure functions at a given \f$Q^2/x_{\rm Bj}\f$
+      Grid& operator()( double q2, double xbj ) override;
+      /// Retrieve the grid's header information
+      header_t header() const { return header_; }
+      Parameterisation params;
+
+    public:
+      Grid( const Grid& ) = delete;
+      void operator=( const GridHandler& ) = delete;
+
+    private:
+      explicit Grid( const Parameterisation& = Parameterisation() );
+      static const unsigned int good_magic;
+      static std::shared_ptr<Grid> singl_;
+
+      header_t header_;
+  };
+
+  std::ostream& operator<<( std::ostream&, const Grid::header_t::order_t& );
+  std::ostream& operator<<( std::ostream&, const Grid::header_t::cl_t& );
+  std::ostream& operator<<( std::ostream&, const Grid::header_t::nucleon_t& );
+}
+
+#endif
+
diff --git a/CepGen/StructureFunctions/StructureFunctionsBuilder.cpp b/CepGen/StructureFunctions/StructureFunctionsBuilder.cpp
index b0d2b52..1de7159 100644
--- a/CepGen/StructureFunctions/StructureFunctionsBuilder.cpp
+++ b/CepGen/StructureFunctions/StructureFunctionsBuilder.cpp
@@ -1,40 +1,40 @@
 #include "CepGen/StructureFunctions/StructureFunctionsBuilder.h"
 
 #include "CepGen/StructureFunctions/ALLM.h"
 #include "CepGen/StructureFunctions/BlockDurandHa.h"
 #include "CepGen/StructureFunctions/FioreBrasse.h"
 #include "CepGen/StructureFunctions/ChristyBosted.h"
 #include "CepGen/StructureFunctions/CLAS.h"
 #include "CepGen/StructureFunctions/GenericLHAPDF.h"
 #include "CepGen/StructureFunctions/SuriYennie.h"
 #include "CepGen/StructureFunctions/SzczurekUleshchenko.h"
 #include "CepGen/StructureFunctions/Schaefer.h"
-#include "CepGen/IO/MSTWGridHandler.h"
+#include "CepGen/StructureFunctions/MSTWGrid.h"
 
 namespace CepGen
 {
   std::shared_ptr<StructureFunctions>
   StructureFunctionsBuilder::get( const SF::Type& sf_type )
   {
     switch ( sf_type ) {
       case SF::Type::Electron:
       case SF::Type::ElasticProton:
       default:                            return std::make_shared<StructureFunctions>();
       case SF::Type::SzczurekUleshchenko: return std::make_shared<SF::SzczurekUleshchenko>();
       case SF::Type::SuriYennie:          return std::make_shared<SF::SuriYennie>();
       case SF::Type::FioreBrasse:         return std::make_shared<SF::FioreBrasse>();
       case SF::Type::ChristyBosted:       return std::make_shared<SF::ChristyBosted>();
       case SF::Type::CLAS:                return std::make_shared<SF::CLAS>();
       case SF::Type::BlockDurandHa:       return std::make_shared<SF::BlockDurandHa>();
       case SF::Type::ALLM91:              return std::make_shared<SF::ALLM>( SF::ALLM::Parameterisation::allm91() );
       case SF::Type::ALLM97:              return std::make_shared<SF::ALLM>( SF::ALLM::Parameterisation::allm97() );
       case SF::Type::GD07p:               return std::make_shared<SF::ALLM>( SF::ALLM::Parameterisation::gd07p() );
       case SF::Type::GD11p:               return std::make_shared<SF::ALLM>( SF::ALLM::Parameterisation::gd11p() );
       case SF::Type::Schaefer:            return std::make_shared<SF::Schaefer>();
       case SF::Type::GenericLHAPDF:       return std::make_shared<SF::GenericLHAPDF>();
       //--- particular case for the MSTW grid as we are dealing
       //--- with a singleton ; hence, no deleter is needed!
       case SF::Type::MSTWgrid:            return std::shared_ptr<MSTW::Grid>( &MSTW::Grid::get(), [=]( MSTW::Grid* ){} );
     }
   }
 }