diff --git a/CepGen/Cards/PythonHandler.cpp b/CepGen/Cards/PythonHandler.cpp
index 7c6a963..1182d1c 100644
--- a/CepGen/Cards/PythonHandler.cpp
+++ b/CepGen/Cards/PythonHandler.cpp
@@ -1,595 +1,596 @@
 #include "PythonHandler.h"
 #include "CepGen/Core/Exception.h"
 
 #ifdef PYTHON
 
 #include "CepGen/Core/TamingFunction.h"
 #include "CepGen/Core/Exception.h"
 
 #include "CepGen/Processes/GamGamLL.h"
 #include "CepGen/Processes/PPtoFF.h"
 #include "CepGen/Processes/PPtoWW.h"
 
+#include "CepGen/StructureFunctions/StructureFunctionsBuilder.h"
 #include "CepGen/Hadronisers/Pythia8Hadroniser.h"
 
 #include <algorithm>
 #include <frameobject.h>
 
 #if PY_MAJOR_VERSION < 3
 #  define PYTHON2
 #endif
 
 namespace CepGen
 {
   namespace Cards
   {
     //----- specialization for CepGen input cards
     PythonHandler::PythonHandler( const char* file )
     {
       setenv( "PYTHONPATH", ".:..:Cards", 1 );
       std::string filename = getPythonPath( file );
       const size_t fn_len = filename.length()+1;
 
       //Py_DebugFlag = 1;
       //Py_VerboseFlag = 1;
 
 #ifdef PYTHON2
       char* sfilename = new char[fn_len];
       snprintf( sfilename, fn_len, "%s", filename.c_str() );
 #else
       wchar_t* sfilename = new wchar_t[fn_len];
       swprintf( sfilename, fn_len, L"%s", filename.c_str() );
 #endif
       if ( sfilename )
         Py_SetProgramName( sfilename );
 
       Py_InitializeEx( 1 );
 
       if ( sfilename )
         delete [] sfilename;
       if ( !Py_IsInitialized() )
         throw CG_FATAL( "PythonHandler" ) << "Failed to initialise the Python cards parser!";
 
       CG_INFO( "PythonHandler" )
         << "Initialised the Python cards parser\n\t"
         << "Python version: " << Py_GetVersion() << "\n\t"
         << "Platform: " << Py_GetPlatform() << ".";
 
       PyObject* cfg = PyImport_ImportModule( filename.c_str() ); // new
       if ( !cfg )
         throwPythonError( Form( "Failed to parse the configuration card %s", file ) );
 
       PyObject* process = PyObject_GetAttrString( cfg, PROCESS_NAME ); // new
       if ( !process )
         throwPythonError( Form( "Failed to extract a \"%s\" keyword from the configuration card %s", PROCESS_NAME, file ) );
 
       //--- type of process to consider
       PyObject* pproc_name = getElement( process, MODULE_NAME ); // borrowed
       if ( !pproc_name )
         throwPythonError( Form( "Failed to extract the process name from the configuration card %s", file ) );
       const std::string proc_name = decode( pproc_name );
 
       if ( proc_name == "lpair" )
         params_.setProcess( new Process::GamGamLL );
       else if ( proc_name == "pptoll" || proc_name == "pptoff" )
         params_.setProcess( new Process::PPtoFF );
       else if ( proc_name == "pptoww" )
         params_.setProcess( new Process::PPtoWW );
       else throw CG_FATAL( "PythonHandler" ) << "Unrecognised process: " << proc_name << ".";
 
       //--- process mode
       fillParameter( process, "mode", (int&)params_.kinematics.mode );
 
       //--- process kinematics
       PyObject* pin_kinematics = getElement( process, "inKinematics" ); // borrowed
       if ( pin_kinematics )
         parseIncomingKinematics( pin_kinematics );
 
       PyObject* pout_kinematics = getElement( process, "outKinematics" ); // borrowed
       if ( pout_kinematics )
         parseOutgoingKinematics( pout_kinematics );
 
       Py_CLEAR( process );
 
       PyObject* plog = PyObject_GetAttrString( cfg, "logger" ); // new
       if ( plog ) {
         parseLogging( plog );
         Py_CLEAR( plog );
       }
 
       //--- hadroniser parameters
       PyObject* phad = PyObject_GetAttrString( cfg, "hadroniser" ); // new
       if ( phad ) {
         parseHadroniser( phad );
         Py_CLEAR( phad );
       }
 
       //--- generation parameters
       PyObject* pint = PyObject_GetAttrString( cfg, "integrator" ); // new
       if ( pint ) {
         parseIntegrator( pint );
         Py_CLEAR( pint );
       }
 
       PyObject* pgen = PyObject_GetAttrString( cfg, "generator" ); // new
       if ( pgen ) {
         parseGenerator( pgen );
         Py_CLEAR( pgen );
       }
 
       //--- taming functions
       PyObject* ptam = PyObject_GetAttrString( cfg, "tamingFunctions" ); // new
       if ( ptam ) {
         parseTamingFunctions( ptam );
         Py_CLEAR( ptam );
       }
 
       //--- finalisation
       Py_CLEAR( cfg );
     }
 
     PythonHandler::~PythonHandler()
     {
       if ( Py_IsInitialized() )
         Py_Finalize();
     }
 
     void
     PythonHandler::parseIncomingKinematics( PyObject* kin )
     {
       PyObject* ppz = getElement( kin, "pz" ); // borrowed
       if ( ppz && PyTuple_Check( ppz ) && PyTuple_Size( ppz ) == 2 ) {
         double pz0 = PyFloat_AsDouble( PyTuple_GetItem( ppz, 0 ) );
         double pz1 = PyFloat_AsDouble( PyTuple_GetItem( ppz, 1 ) );
         params_.kinematics.inp = { pz0, pz1 };
       }
       double sqrt_s = -1.;
       fillParameter( kin, "cmEnergy", sqrt_s );
       if ( sqrt_s != -1. )
         params_.kinematics.setSqrtS( sqrt_s );
       int str_fun = 0;
       fillParameter( kin, "structureFunctions", str_fun );
-      //params_.kinematics.structure_functions = ;
+      params_.kinematics.structure_functions = StructureFunctionsBuilder::get( (SF::Type)str_fun );
     }
 
     void
     PythonHandler::parseOutgoingKinematics( PyObject* kin )
     {
       PyObject* ppair = getElement( kin, "pair" ); // borrowed
       if ( ppair ) {
         if ( isInteger( ppair ) ) {
           PDG pair = (PDG)asInteger( ppair );
           params_.kinematics.central_system = { pair, pair };
         }
         else if ( PyTuple_Check( ppair ) ) {
           if ( PyTuple_Size( ppair ) != 2 )
             throw CG_FATAL( "PythonHandler" ) << "Invalid value for in_kinematics.pair!";
           PDG pair1 = (PDG)asInteger( PyTuple_GetItem( ppair, 0 ) );
           PDG pair2 = (PDG)asInteger( PyTuple_GetItem( ppair, 1 ) );
           params_.kinematics.central_system = { pair1, pair2 };
         }
       }
 
       PyObject* pparts = getElement( kin, "minFinalState" ); // borrowed
       if ( pparts && PyTuple_Check( pparts ) )
         for ( unsigned short i = 0; i < PyTuple_Size( pparts ); ++i )
           params_.kinematics.minimum_final_state.emplace_back( (PDG)asInteger( PyTuple_GetItem( pparts, i ) ) );
 
       PyObject* pcuts = getElement( kin, "cuts" ); // borrowed
       if ( pcuts )
         parseParticlesCuts( pcuts );
 
       // for LPAIR/collinear matrix elements
       fillLimits( kin, "q2", params_.kinematics.cuts.initial.q2 );
 
       // for the kT factorised matrix elements
       fillLimits( kin, "qt", params_.kinematics.cuts.initial.qt );
       fillLimits( kin, "phiqt", params_.kinematics.cuts.initial.phi_qt );
       fillLimits( kin, "ptdiff", params_.kinematics.cuts.central.pt_diff );
       fillLimits( kin, "phiptdiff", params_.kinematics.cuts.central.phi_pt_diff );
       fillLimits( kin, "rapiditydiff", params_.kinematics.cuts.central.rapidity_diff );
 
       // generic phase space limits
       fillLimits( kin, "rapidity", params_.kinematics.cuts.central.rapidity_single );
       fillLimits( kin, "eta", params_.kinematics.cuts.central.eta_single );
       fillLimits( kin, "pt", params_.kinematics.cuts.central.pt_single );
 
       fillLimits( kin, "ptsum", params_.kinematics.cuts.central.pt_sum );
       fillLimits( kin, "invmass", params_.kinematics.cuts.central.mass_sum );
 
       fillLimits( kin, "mx", params_.kinematics.cuts.remnants.mass_single );
     }
 
     void
     PythonHandler::parseParticlesCuts( PyObject* cuts )
     {
       if ( !PyDict_Check( cuts ) )
         throwPythonError( "Particle cuts object should be a dictionary!" );
       PyObject* pkey = nullptr, *pvalue = nullptr;
       Py_ssize_t pos = 0;
       while ( PyDict_Next( cuts, &pos, &pkey, &pvalue ) ) {
         const PDG pdg = (PDG)asInteger( pkey );
         fillLimits( pvalue, "pt", params_.kinematics.cuts.central_particles[pdg].pt_single );
         fillLimits( pvalue, "energy", params_.kinematics.cuts.central_particles[pdg].energy_single );
         fillLimits( pvalue, "eta", params_.kinematics.cuts.central_particles[pdg].eta_single );
         fillLimits( pvalue, "rapidity", params_.kinematics.cuts.central_particles[pdg].rapidity_single );
       }
     }
 
     void
     PythonHandler::parseLogging( PyObject* log )
     {
       fillParameter( log, "level", (int&)Logger::get().level );
       std::vector<std::string> enabled_modules;
       fillParameter( log, "enabledModules", enabled_modules );
       for ( const auto& mod : enabled_modules )
         Logger::get().addExceptionRule( mod );
     }
 
     void
     PythonHandler::parseIntegrator( PyObject* integr )
     {
       if ( !PyDict_Check( integr ) )
         throwPythonError( "Integrator object should be a dictionary!" );
       PyObject* palgo = getElement( integr, MODULE_NAME ); // borrowed
       if ( !palgo )
         throwPythonError( "Failed to retrieve the integration algorithm name!" );
       std::string algo = decode( palgo );
       if ( algo == "plain" )
         params_.integrator.type = Integrator::Type::plain;
       else if ( algo == "Vegas" ) {
         params_.integrator.type = Integrator::Type::Vegas;
         fillParameter( integr, "alpha", (double&)params_.integrator.vegas.alpha );
         fillParameter( integr, "iterations", params_.integrator.vegas.iterations );
         fillParameter( integr, "mode", (int&)params_.integrator.vegas.mode );
         fillParameter( integr, "verbosity", (int&)params_.integrator.vegas.verbose );
         std::string vegas_logging_output = "cerr";
         fillParameter( integr, "loggingOutput", vegas_logging_output );
         if ( vegas_logging_output == "cerr" )
           // redirect all debugging information to the error stream
           params_.integrator.vegas.ostream = stderr;
         else if ( vegas_logging_output == "cout" )
           // redirect all debugging information to the standard stream
           params_.integrator.vegas.ostream = stdout;
         else
           params_.integrator.vegas.ostream = fopen( vegas_logging_output.c_str(), "w" );
       }
       else if ( algo == "MISER" ) {
         params_.integrator.type = Integrator::Type::MISER;
         fillParameter( integr, "estimateFraction", (double&)params_.integrator.miser.estimate_frac );
         fillParameter( integr, "minCalls", params_.integrator.miser.min_calls );
         fillParameter( integr, "minCallsPerBisection", params_.integrator.miser.min_calls_per_bisection );
         fillParameter( integr, "alpha", (double&)params_.integrator.miser.alpha );
         fillParameter( integr, "dither", (double&)params_.integrator.miser.dither );
       }
       else
         throwPythonError( Form( "Invalid integration algorithm: %s", algo.c_str() ) );
 
       fillParameter( integr, "numFunctionCalls", params_.integrator.ncvg );
       fillParameter( integr, "seed", (unsigned long&)params_.integrator.rng_seed );
       unsigned int rng_engine;
       fillParameter( integr, "rngEngine", rng_engine );
       switch ( rng_engine ) {
         case 0: default: params_.integrator.rng_engine = (gsl_rng_type*)gsl_rng_mt19937; break;
         case 1: params_.integrator.rng_engine = (gsl_rng_type*)gsl_rng_taus2; break;
         case 2: params_.integrator.rng_engine = (gsl_rng_type*)gsl_rng_gfsr4; break;
         case 3: params_.integrator.rng_engine = (gsl_rng_type*)gsl_rng_ranlxs0; break;
       }
       fillParameter( integr, "chiSqCut", params_.integrator.vegas_chisq_cut );
     }
 
     void
     PythonHandler::parseGenerator( PyObject* gen )
     {
       if ( !PyDict_Check( gen ) )
         throwPythonError( "Generation information object should be a dictionary!" );
       params_.generation.enabled = true;
       fillParameter( gen, "treat", params_.generation.treat );
       fillParameter( gen, "numEvents", params_.generation.maxgen );
       fillParameter( gen, "printEvery", params_.generation.gen_print_every );
       fillParameter( gen, "numThreads", params_.generation.num_threads );
       fillParameter( gen, "numPoints", params_.generation.num_points );
     }
 
     void
     PythonHandler::parseTamingFunctions( PyObject* tf )
     {
       if ( !PyList_Check( tf ) )
         throwPythonError( "Taming functions list should be a list!" );
 
       for ( Py_ssize_t i = 0; i < PyList_Size( tf ); ++i ) {
         PyObject* pit = PyList_GetItem( tf, i ); // borrowed
         if ( !pit )
           continue;
         if ( !PyDict_Check( pit ) )
           throwPythonError( Form( "Item %d has invalid type %s", i, pit->ob_type->tp_name ) );
         PyObject* pvar = getElement( pit, "variable" ), *pexpr = getElement( pit, "expression" ); // borrowed
         params_.taming_functions->add( decode( pvar ).c_str(), decode( pexpr ).c_str() );
       }
     }
 
     void
     PythonHandler::parseHadroniser( PyObject* hadr )
     {
       if ( !PyDict_Check( hadr ) )
         throwPythonError( "Hadroniser object should be a dictionary!" );
 
       PyObject* pname = getElement( hadr, MODULE_NAME ); // borrowed
       if ( !pname )
         throwPythonError( "Hadroniser name is required!" );
       std::string hadr_name = decode( pname );
 
       fillParameter( hadr, "maxTrials", params_.hadroniser_max_trials );
       PyObject* pseed = getElement( hadr, "seed" ); // borrowed
       long long seed = -1ll;
       if ( pseed && isInteger( pseed ) ) {
         seed = PyLong_AsLongLong( pseed );
         CG_DEBUG( "PythonHandler:hadroniser" ) << "Hadroniser seed set to " << seed;
       }
       if ( hadr_name == "pythia8" ) {
         params_.setHadroniser( new Hadroniser::Pythia8Hadroniser( params_ ) );
         std::vector<std::string> config;
         auto pythia8 = dynamic_cast<Hadroniser::Pythia8Hadroniser*>( params_.hadroniser() );
         pythia8->setSeed( seed );
         fillParameter( hadr, "pythiaPreConfiguration", config );
         pythia8->readStrings( config );
         pythia8->init();
         fillParameter( hadr, "pythiaConfiguration", config );
         pythia8->readStrings( config );
         fillParameter( hadr, "pythiaProcessConfiguration", config );
         pythia8->readStrings( config );
       }
     }
 
     //------------------------------------------------------------------
     // Python API helpers
     //------------------------------------------------------------------
 
     std::string
     PythonHandler::getPythonPath( const char* file )
     {
       std::string s_filename = file;
       s_filename = s_filename.substr( 0, s_filename.find_last_of( "." ) ); // remove the extension
       std::replace( s_filename.begin(), s_filename.end(), '/', '.' ); // replace all '/' by '.'
       return s_filename;
     }
 
     void
     PythonHandler::throwPythonError( const std::string& message )
     {
       PyObject* ptype = nullptr, *pvalue = nullptr, *ptraceback_obj = nullptr;
       // retrieve error indicator and clear it to handle ourself the error
       PyErr_Fetch( &ptype, &pvalue, &ptraceback_obj );
       PyErr_Clear();
       // ensure the objects retrieved are properly normalised and point to compatible objects
       PyErr_NormalizeException( &ptype, &pvalue, &ptraceback_obj );
       std::ostringstream oss; oss << message;
       if ( ptype != nullptr ) { // we can start the traceback
         oss << "\n\tError: "
 #ifdef PYTHON2
             << PyString_AsString( PyObject_Str( pvalue ) ); // deprecated in python v3+
 #else
             << PyUnicode_AsUTF8( PyObject_Str( pvalue ) );
 #endif
         PyTracebackObject* ptraceback = (PyTracebackObject*)ptraceback_obj;
         string tabul = "↪ ";
         if ( ptraceback != nullptr ) {
           while ( ptraceback->tb_next != nullptr ) {
             PyFrameObject* pframe = ptraceback->tb_frame;
             if ( pframe != nullptr ) {
               int line = PyCode_Addr2Line( pframe->f_code, pframe->f_lasti );
 #ifdef PYTHON2
               const char* filename = PyString_AsString( pframe->f_code->co_filename );
               const char* funcname = PyString_AsString( pframe->f_code->co_name );
 #else
               const char* filename = PyUnicode_AsUTF8( pframe->f_code->co_filename );
               const char* funcname = PyUnicode_AsUTF8( pframe->f_code->co_name );
 #endif
               oss << Form( "\n\t%s%s on %s (line %d)", tabul.c_str(), boldify( funcname ).c_str(), filename, line );
             }
             else
               oss << Form( "\n\t%s issue in line %d", tabul.c_str(), ptraceback->tb_lineno );
             tabul = string( "  " )+tabul;
             ptraceback = ptraceback->tb_next;
           }
         }
       }
       Py_Finalize();
       throw CG_FATAL( "PythonHandler:error" ) << oss.str();
     }
 
     std::string
     PythonHandler::decode( PyObject* obj )
     {
       std::string out;
 #ifdef PYTHON2
       out = PyString_AsString( obj ); // deprecated in python v3+
 #else
       PyObject* pstr = PyUnicode_AsEncodedString( obj, "utf-8", "strict" ); // new
       if ( !pstr )
         throwPythonError( "Failed to decode a Python object!" );
       out = PyBytes_AS_STRING( pstr );
       Py_CLEAR( pstr );
 #endif
       return out;
     }
 
     PyObject*
     PythonHandler::encode( const char* str )
     {
       PyObject* obj = PyUnicode_FromString( str ); // new
       if ( !obj )
         throwPythonError( Form( "Failed to encode the following string:\n\t%s", str ) );
       return obj;
     }
 
     PyObject*
     PythonHandler::getElement( PyObject* obj, const char* key )
     {
       PyObject* pout = nullptr, *nink = encode( key );
       if ( !nink )
         return pout;
       pout = PyDict_GetItem( obj, nink ); // borrowed
       Py_CLEAR( nink );
       if ( pout )
         CG_DEBUG( "PythonHandler:getElement" )
           << "retrieved " << pout->ob_type->tp_name << " element \"" << key << "\" "
           << "from " << obj->ob_type->tp_name << " object\n\t"
           << "new reference count: " << pout->ob_refcnt;
       else
         CG_DEBUG( "PythonHandler:getElement" )
           << "did not retrieve a valid element \"" << key << "\"";
       return pout;
     }
 
     void
     PythonHandler::fillLimits( PyObject* obj, const char* key, Limits& lim )
     {
       PyObject* pobj = getElement( obj, key ); // borrowed
       if ( !pobj )
         return;
       if ( !PyTuple_Check( pobj ) )
         throw CG_FATAL( "PythonHandler:fillLimits" ) << "Invalid value retrieved for " << key << ".";
       if ( PyTuple_Size( pobj ) < 1 )
         throw CG_FATAL( "PythonHandler:fillLimits" ) << "Invalid number of values unpacked for " << key << "!";
       double min = PyFloat_AsDouble( PyTuple_GetItem( pobj, 0 ) );
       lim.min() = min;
       if ( PyTuple_Size( pobj ) > 1 ) {
         double max = PyFloat_AsDouble( PyTuple_GetItem( pobj, 1 ) );
         if ( max != -1 )
           lim.max() = max;
       }
     }
 
     void
     PythonHandler::fillParameter( PyObject* parent, const char* key, bool& out )
     {
       PyObject* pobj = getElement( parent, key ); // borrowed
       if ( !pobj )
         return;
       if ( !PyBool_Check( pobj ) )
         throwPythonError( Form( "Object \"%s\" has invalid type %s", key, pobj->ob_type->tp_name ) );
       fillParameter( parent, key, (int&)out );
     }
 
     void
     PythonHandler::fillParameter( PyObject* parent, const char* key, int& out )
     {
       PyObject* pobj = getElement( parent, key ); // borrowed
       if ( !pobj )
         return;
 #ifdef PYTHON2
       if ( !PyInt_Check( pobj ) && !PyBool_Check( pobj ) )
         throwPythonError( Form( "Object \"%s\" has invalid type %s", key, pobj->ob_type->tp_name ) );
       out = PyInt_AsLong( pobj );
 #else
       if ( !PyLong_Check( pobj ) && !PyBool_Check( pobj ) )
         throwPythonError( Form( "Object \"%s\" has invalid type %s", key, pobj->ob_type->tp_name ) );
       out = PyLong_AsLong( pobj );
 #endif
     }
 
     void
     PythonHandler::fillParameter( PyObject* parent, const char* key, unsigned long& out )
     {
       PyObject* pobj = getElement( parent, key ); // borrowed
       if ( !pobj )
         return;
       if ( !PyLong_Check( pobj )
 #ifdef PYTHON2
         && !PyInt_Check( pobj )
 #endif
       )
         throwPythonError( Form( "Object \"%s\" has invalid type %s", key, pobj->ob_type->tp_name ) );
       if ( PyLong_Check( pobj ) )
         out = PyLong_AsUnsignedLong( pobj );
 #ifdef PYTHON2
       else if ( PyInt_Check( pobj ) )
         out = PyInt_AsUnsignedLongMask( pobj );
 #endif
     }
 
     void
     PythonHandler::fillParameter( PyObject* parent, const char* key, unsigned int& out )
     {
       PyObject* pobj = getElement( parent, key ); // borrowed
       if ( !pobj )
         return;
 #ifdef PYTHON2
       if ( !PyInt_Check( pobj ) )
         throwPythonError( Form( "Object \"%s\" has invalid type %s", key, pobj->ob_type->tp_name ) );
       out = PyInt_AsUnsignedLongMask( pobj );
 #else
       if ( !PyLong_Check( pobj ) )
         throwPythonError( Form( "Object \"%s\" has invalid type %s", key, pobj->ob_type->tp_name ) );
       out = PyLong_AsUnsignedLong( pobj );
 #endif
     }
 
     void
     PythonHandler::fillParameter( PyObject* parent, const char* key, double& out )
     {
       PyObject* pobj = getElement( parent, key ); // borrowed
       if ( !pobj )
         return;
       if ( !PyFloat_Check( pobj ) )
         throwPythonError( Form( "Object \"%s\" has invalid type %s", key, pobj->ob_type->tp_name ) );
       out = PyFloat_AsDouble( pobj );
     }
 
     void
     PythonHandler::fillParameter( PyObject* parent, const char* key, std::string& out )
     {
       PyObject* pobj = getElement( parent, key ); // borrowed
       if ( !pobj )
         return;
       if ( !
 #ifdef PYTHON2
         PyString_Check( pobj )
 #else
         PyUnicode_Check( pobj )
 #endif
       )
         throwPythonError( Form( "Object \"%s\" has invalid type %s", key, pobj->ob_type->tp_name ) );
       out = decode( pobj );
     }
 
     void
     PythonHandler::fillParameter( PyObject* parent, const char* key, std::vector<std::string>& out )
     {
       out.clear();
       PyObject* pobj = getElement( parent, key ); // borrowed
       if ( !pobj )
         return;
       if ( !PyTuple_Check( pobj ) )
         throwPythonError( Form( "Object \"%s\" has invalid type %s", key, pobj->ob_type->tp_name ) );
       for ( Py_ssize_t i = 0; i < PyTuple_Size( pobj ); ++i ) {
         PyObject* pit = PyTuple_GetItem( pobj, i ); // borrowed
         out.emplace_back( decode( pit ) );
       }
     }
 
     bool
     PythonHandler::isInteger( PyObject* obj )
     {
 #ifdef PYTHON2
       return PyInt_Check( obj );
 #else
       return PyLong_Check( obj );
 #endif
     }
 
     int
     PythonHandler::asInteger( PyObject* obj )
     {
 #ifdef PYTHON2
       return PyInt_AsLong( obj );
 #else
       return PyLong_AsLong( obj );
 #endif
     }
   }
 }
 
 #endif
 
diff --git a/CepGen/IO/FortranInterface.cpp b/CepGen/IO/FortranInterface.cpp
index 89028c0..38151c6 100644
--- a/CepGen/IO/FortranInterface.cpp
+++ b/CepGen/IO/FortranInterface.cpp
@@ -1,50 +1,22 @@
-#include "CepGen/StructureFunctions/SzczurekUleshchenko.h"
-#include "CepGen/StructureFunctions/SuriYennie.h"
-#include "CepGen/StructureFunctions/FioreBrasse.h"
-#include "CepGen/StructureFunctions/ChristyBosted.h"
-#include "CepGen/StructureFunctions/CLAS.h"
-#include "CepGen/StructureFunctions/BlockDurandHa.h"
-#include "CepGen/StructureFunctions/ALLM.h"
-#include "CepGen/StructureFunctions/Schaefer.h"
-#include "CepGen/StructureFunctions/GenericLHAPDF.h"
+#include "CepGen/StructureFunctions/StructureFunctionsBuilder.h"
 #include "CepGen/IO/MSTWGridHandler.h"
 
 extern "C"
 {
   void
   cepgen_structure_functions_( int& sfmode, double& q2, double& xbj, double& f2, double& fl )
   {
     using namespace CepGen;
-    StructureFunctions sf;
-    switch ( (SF::Type)sfmode ) {
-      case SF::Type::Electron:
-      case SF::Type::ElasticProton:
-      case SF::Type::Invalid: {
-        f2 = fl = 0.;
-        return;
-      }
-      case SF::Type::SzczurekUleshchenko: sf = SF::SzczurekUleshchenko(); break;
-      case SF::Type::SuriYennie:          sf = SF::SuriYennie(); break;
-      case SF::Type::FioreBrasse:         sf = SF::FioreBrasse(); break;
-      case SF::Type::ChristyBosted:       sf = SF::ChristyBosted(); break;
-      case SF::Type::CLAS:                sf = SF::CLAS(); break;
-      case SF::Type::BlockDurandHa:       sf = SF::BlockDurandHa(); break;
-      case SF::Type::ALLM91:              sf = SF::ALLM( SF::ALLM::Parameterisation::allm91() ); break;
-      case SF::Type::ALLM97:              sf = SF::ALLM( SF::ALLM::Parameterisation::allm97() ); break;
-      case SF::Type::GD07p:               sf = SF::ALLM( SF::ALLM::Parameterisation::gd07p() ); break;
-      case SF::Type::GD11p:               sf = SF::ALLM( SF::ALLM::Parameterisation::gd11p() ); break;
-      case SF::Type::Schaefer:            sf = SF::Schaefer(); break;
-      case SF::Type::GenericLHAPDF:       sf = SF::GenericLHAPDF(); break;
-      case SF::Type::MSTWgrid: {
-        sf = MSTW::GridHandler::get().eval( q2, xbj );
-        f2 = sf.F2;
-        fl = sf.FL;
-        return;
-      }
+    SF::Type sf_mode = (SF::Type)sfmode;
+    if ( sf_mode == SF::Type::MSTWgrid ) {
+      StructureFunctions sf = MSTW::GridHandler::get().eval( q2, xbj );
+      f2 = sf.F2;
+      fl = sf.FL;
+      return;
     }
-    sf = sf( q2, xbj );
+    StructureFunctions sf = StructureFunctionsBuilder::get( sf_mode )( q2, xbj );
     f2 = sf.F2;
     fl = sf.FL;
   }
 }
 
diff --git a/CepGen/Physics/Kinematics.cpp b/CepGen/Physics/Kinematics.cpp
index 896b16a..b7d61f0 100644
--- a/CepGen/Physics/Kinematics.cpp
+++ b/CepGen/Physics/Kinematics.cpp
@@ -1,70 +1,75 @@
 #include "CepGen/Physics/Kinematics.h"
 #include "CepGen/Physics/PDG.h"
 
 #include "CepGen/Core/Exception.h"
 #include "CepGen/Core/utils.h"
 
 #include "CepGen/StructureFunctions/SuriYennie.h"
 #include "CepGen/Event/Particle.h"
 
 namespace CepGen
 {
   Kinematics::Kinematics() :
     inp( { 6500., 6500. } ), inpdg( { PDG::Proton, PDG::Proton } ),
     mode( Mode::ElasticElastic ), structure_functions( SF::SuriYennie() )
   {}
 
+  Kinematics::Kinematics( const Kinematics& kin ) :
+    inp( kin.inp ), inpdg( kin.inpdg ), mode( kin.mode ),
+    structure_functions( kin.structure_functions )
+  {}
+
   Kinematics::~Kinematics()
   {}
 
   void
   Kinematics::setSqrtS( double sqrts )
   {
     const double pin = 0.5 * sqrts;
     inp = { pin, pin };
   }
 
   double
   Kinematics::sqrtS() const
   {
     return inp.first + inp.second;
   }
 
   //-----------------------------------------------------------------------------------------------
   // User-friendly displayers
   //-----------------------------------------------------------------------------------------------
 
   std::ostream&
   operator<<( std::ostream& os, const Kinematics::Mode& pm )
   {
     switch ( pm ) {
       case Kinematics::Mode::ElectronElectron:
         return os << "electron/electron";
       case Kinematics::Mode::ElectronProton:
         return os << "electron/proton";
       case Kinematics::Mode::ProtonElectron:
         return os << "proton/electron";
       case Kinematics::Mode::ElasticElastic:
         return os << "elastic/elastic";
       case Kinematics::Mode::InelasticElastic:
         return os << "inelastic/elastic";
       case Kinematics::Mode::ElasticInelastic:
         return os << "elastic/inelastic";
       case Kinematics::Mode::InelasticInelastic:
         return os << "inelastic/inelastic";
     }
     return os;
   }
 
   //------------------------------------------------------------------------------------------------
   // List of kinematics limits
   //------------------------------------------------------------------------------------------------
 
   Kinematics::CutsList::CutsList()
   {
     initial.q2 = { 0., 1.e5 };
     central.pt_single.min() = 0.;
     remnants.mass_single = { 1.07, 320. };
   }
 }
 
diff --git a/CepGen/Physics/Kinematics.h b/CepGen/Physics/Kinematics.h
index 4f40228..6edd99d 100644
--- a/CepGen/Physics/Kinematics.h
+++ b/CepGen/Physics/Kinematics.h
@@ -1,96 +1,97 @@
 #ifndef CepGen_Physics_Kinematics_h
 #define CepGen_Physics_Kinematics_h
 
 #include <iomanip>
 #include <algorithm>
 
 #include "CepGen/Core/Logger.h"
 #include "CepGen/StructureFunctions/StructureFunctions.h"
 
 #include "CepGen/Physics/ParticleProperties.h"
 #include "CepGen/Physics/Cuts.h"
 #include "CepGen/Physics/Limits.h"
 
 #include <vector>
 #include <unordered_map>
 
 namespace CepGen
 {
   enum class PDG;
   /// List of kinematic constraints to apply on the process phase space.
   class Kinematics
   {
     public:
       Kinematics();
+      Kinematics( const Kinematics& );
       ~Kinematics();
 
       /// Type of kinematics to consider for the process
       enum class Mode {
         ElectronProton = 0,     ///< electron-proton elastic case
         ElasticElastic = 1,     ///< proton-proton elastic case
         ElasticInelastic = 2,   ///< proton-proton single-dissociative (or inelastic-elastic) case
         InelasticElastic = 3,   ///< proton-proton single-dissociative (or elastic-inelastic) case
         InelasticInelastic = 4, ///< proton-proton double-dissociative case
         ProtonElectron,
         ElectronElectron
       };
       /// Human-readable format of a process mode (elastic/dissociative parts)
       friend std::ostream& operator<<( std::ostream&, const Mode& );
 
       /// Incoming particles' momentum (in \f$\text{GeV}/c\f$)
       std::pair<double,double> inp;
       /// Set the incoming particles' momenta (if the collision is symmetric)
       void setSqrtS( double sqrts );
       /// Process centre of mass energy
       double sqrtS() const;
       /// Beam/primary particle's PDG identifier
       std::pair<PDG,PDG> inpdg;
       /// PDG id of the outgoing central particles
       std::vector<PDG> central_system;
       /// Minimum list of central particles required
       std::vector<PDG> minimum_final_state;
 
       /// Type of kinematics to consider for the phase space
       Mode mode;
       /// Type of structure functions to consider
       StructureFunctions structure_functions;
 
       struct CutsList
       {
         CutsList();
         template<class T,bool>
         struct hasher
         {
           inline size_t operator()( const T& t ) const {
             return std::hash<T>()( t );
           }
         };
         template<class T>
         struct hasher<T, true>
         {
           inline size_t operator() ( const T& t ) {
             typedef typename std::underlying_type<T>::type enumType;
             return std::hash<enumType>()( static_cast<enumType>( t ) );
           }
         };
         template<class T>
         struct EnumHash
         {
           inline size_t operator()( const T& t ) const {
             return hasher<T,std::is_enum<T>::value>()( t );
           }
         };
         /// Cuts on the initial particles kinematics
         Cuts initial;
         /// Cuts on the central system produced
         Cuts central;
         std::unordered_map<PDG,Cuts,EnumHash<PDG> > central_particles;
         /// Cuts on the beam remnants system
         Cuts remnants;
       };
       CutsList cuts;
   };
 }
 
 #endif
 
diff --git a/CepGen/StructureFunctions/ALLM.cpp b/CepGen/StructureFunctions/ALLM.cpp
index 227218e..6866a51 100644
--- a/CepGen/StructureFunctions/ALLM.cpp
+++ b/CepGen/StructureFunctions/ALLM.cpp
@@ -1,167 +1,168 @@
 #include "ALLM.h"
 #include "CepGen/Physics/ParticleProperties.h"
 #include <cmath>
 
 namespace CepGen
 {
   namespace SF
   {
     ALLM::Parameterisation
     ALLM::Parameterisation::allm91()
     {
       Parameterisation p;
       p.pomeron = Parameters( {
         {  0.26550,  0.04856,  1.04682 },
         { -0.04503, -0.36407,  8.17091 },
         {  0.49222,  0.52116,  3.5515  } } );
       p.reggeon = Parameters( {
         {  0.67639,  0.49027,  2.66275 },
         {  0.60408,  0.17353,  1.61812 },
         {  1.26066,  1.83624,  0.81141 } } );
       p.m02 = 0.30508;
       p.mp2 = 10.676;
       p.mr2 = 0.20623;
       p.q02 = 0.27799;
       p.lambda2 = 0.06527;
       p.type = Type::ALLM91;
       return p;
     }
 
     ALLM::Parameterisation
     ALLM::Parameterisation::allm97()
     {
       Parameterisation p;
       p.pomeron = Parameters( {
         {  0.28067,  0.22291,  2.1979 },
         { -0.0808,  -0.44812,  1.1709 },
         {  0.36292,  1.8917,   1.8439 } } );
       p.reggeon = Parameters( {
         {  0.80107,  0.97307,  3.4924  },
         {  0.58400,  0.37888,  2.6063  },
         {  0.01147,  3.7582,   0.49338 } } );
       p.m02 = 0.31985;
       p.mp2 = 49.457;
       p.mr2 = 0.15052;
       p.q02 = 0.52544;
       p.lambda2 = 0.06526;
       p.type = Type::ALLM97;
       return p;
     }
 
     ALLM::Parameterisation
     ALLM::Parameterisation::hht_allm()
     {
       Parameterisation p;
       p.pomeron = Parameters( {
         {  0.412,  0.164,  17.7  },
         { -0.835, -0.446,  10.6  },
         { -45.8,    55.7, -0.031 } } );
       p.reggeon = Parameters( {
         { -1.04,   2.97,   0.163 },
         {  0.706,  0.185, -16.4  },
         { -1.29,   4.51,   1.16  } } );
       p.m02 = 0.446;
       p.mp2 = 74.2;
       p.mr2 = 29.3;
       p.q02 = 4.74e-5;
       p.lambda2 = 2.2e-8;
       p.type = Type::Invalid;
       return p;
     }
 
     ALLM::Parameterisation
     ALLM::Parameterisation::hht_allm_ft()
     {
       Parameterisation p;
       p.pomeron = Parameters( {
         {  0.356,  0.171, 18.6  },
         { -0.075, -0.470, 9.2   },
         { -0.477,  54.0,  0.073 } } );
       p.reggeon = Parameters( {
         { -0.636, 3.37,  -0.660 },
         {  0.882, 0.082, -8.5   },
         {  0.339, 3.38,   1.07  } } );
       p.m02 = 0.388;
       p.mp2 = 50.8;
       p.mr2 = 0.838;
       p.q02 = 1.87e-5;
       p.lambda2 = 4.4e-9;
       p.type = Type::Invalid;
       return p;
     }
 
     ALLM::Parameterisation
     ALLM::Parameterisation::gd07p()
     {
       Parameterisation p;
       p.pomeron = Parameters( {
         {  0.339,  0.127, 1.16  },
         { -0.105, -0.495, 1.29  },
         { -1.42,   4.51,  0.551 } } );
       p.reggeon = Parameters( {
         { 0.838, 2.36,  1.77  },
         { 0.374, 0.998, 0.775 },
         { 2.71,  1.83,  1.26  } } );
       p.m02 = 0.454;
       p.mp2 = 30.7;
       p.mr2 = 0.117;
       p.q02 = 1.15;
       p.lambda2 = 0.06527;
       p.type = Type::GD07p;
       return p;
     }
 
     ALLM::Parameterisation
     ALLM::Parameterisation::gd11p()
     {
       Parameterisation p;
       p.pomeron = Parameters( {
         {  0.3638,   0.1211, 1.166 }, // c
         { -0.11895, -0.4783, 1.353 }, // a
         {  1.0833,   2.656,  1.771 } } ); // b
       p.reggeon = Parameters( {
         {   1.3633,  2.256,  2.209   },
         {   0.3425,  1.0603, 0.5164  },
         { -10.408,  14.857,  0.07739 } } );
       p.m02 = 0.5063;
       p.mp2 = 34.75;
       p.mr2 = 0.03190;
       p.q02 = 1.374;
       p.lambda2 = 0.06527;
       p.type = Type::GD11p;
       return p;
     }
 
     ALLM::ALLM( const ALLM::Parameterisation& param ) :
       StructureFunctions( param.type ), params_( param )
     {}
 
     ALLM
     ALLM::operator()( double q2, double xbj, const SigmaRatio& rcomp ) const
     {
       const double W2_eff = q2*( 1.-xbj )/xbj;
       const double xp = ( q2+params_.mp2 )/( q2+W2_eff+params_.mp2 ),
                    xr = ( q2+params_.mr2 )/( q2+W2_eff+params_.mr2 );
 
       const double xlog1 = log( ( q2+params_.q02 )/ params_.lambda2 ), xlog2 = log( params_.q02/params_.lambda2 );
       const double t = log( xlog1/xlog2 );
 
       const double apom = params_.pomeron.a[0] + ( params_.pomeron.a[0]-params_.pomeron.a[1] )*( 1./( 1.+pow( t, params_.pomeron.a[2] ) ) - 1. );
       const double bpom = params_.pomeron.b[0] + params_.pomeron.b[1]*pow( t, params_.pomeron.b[2] );
       const double cpom = params_.pomeron.c[0] + ( params_.pomeron.c[0]-params_.pomeron.c[1] )*( 1./( 1.+pow( t, params_.pomeron.c[2] ) ) - 1. );
 
       const double areg = params_.reggeon.a[0] + params_.reggeon.a[1]*pow( t, params_.reggeon.a[2] );
       const double breg = params_.reggeon.b[0] + params_.reggeon.b[1]*pow( t, params_.reggeon.b[2] );
       const double creg = params_.reggeon.c[0] + params_.reggeon.c[1]*pow( t, params_.reggeon.c[2] );
 
       const double F2_Pom = cpom*pow( xp, apom )*pow( 1.-xbj, bpom ),
                    F2_Reg = creg*pow( xr, areg )*pow( 1.-xbj, breg );
 
       ALLM allm;
       allm.F2 = q2/( q2+params_.m02 ) * ( F2_Pom + F2_Reg );
       allm.computeFL( q2, xbj, rcomp );
 
       return allm;
     }
   }
 }
+
diff --git a/CepGen/StructureFunctions/StructureFunctions.cpp b/CepGen/StructureFunctions/StructureFunctions.cpp
index edfd54f..05ef043 100644
--- a/CepGen/StructureFunctions/StructureFunctions.cpp
+++ b/CepGen/StructureFunctions/StructureFunctions.cpp
@@ -1,74 +1,75 @@
 #include "CepGen/StructureFunctions/StructureFunctions.h"
 
 #include "CepGen/Physics/PDG.h"
 #include "CepGen/Physics/ParticleProperties.h"
 
 #include "CepGen/Core/Exception.h"
 #include "CepGen/Core/utils.h"
 
 #include <iostream>
 
 namespace CepGen
 {
   const double StructureFunctions::mp_ = ParticleProperties::mass( PDG::Proton );
   const double StructureFunctions::mp2_ = StructureFunctions::mp_*StructureFunctions::mp_;
 
   double
   StructureFunctions::F1( double q2, double xbj ) const
   {
     if ( xbj == 0. || q2 == 0. ) {
       CG_ERROR( "StructureFunctions:F1" )
         << "Invalid range for Q² = " << q2 << " or xBj = " << xbj << ".";
       return 0.;
     }
     const double F1 = 0.5*( ( 1+4.*xbj*xbj*mp2_/q2 )*F2 - FL )/xbj;
     CG_DEBUG_LOOP( "StructureFunctions:F1" )
       << "F1 for Q² = " << q2 << ", xBj = " << xbj << ": " << F1 << "\n\t"
       << "(F2 = " << F2 << ", FL = " << FL << ").";
     return F1;
   }
 
   void
   StructureFunctions::computeFL( double q2, double xbj, const SF::SigmaRatio& ratio )
   {
     double r_error = 0.;
     computeFL( q2, xbj, ratio( q2, xbj, r_error ) );
   }
 
   void
   StructureFunctions::computeFL( double q2, double xbj, double r )
   {
     const double tau = 4.*xbj*xbj*mp2_/q2;
     FL = F2 * ( 1.+tau ) * ( r/( 1.+r ) );
   }
 
   /// Human-readable format of a structure function object
   std::ostream&
   operator<<( std::ostream& os, const StructureFunctions& sf )
   {
     return os << "F2 = " << sf.F2 << ", FL = " << sf.FL;
   }
   /// Human-readable format of a structure function type
   std::ostream&
   operator<<( std::ostream& os, const SF::Type& sf )
   {
     switch ( sf ) {
       case SF::Type::Invalid:             return os << "[INVALID]";
       case SF::Type::Electron:            return os << "electron";
       case SF::Type::ElasticProton:       return os << "elastic proton";
       case SF::Type::SuriYennie:          return os << "Suri-Yennie";
       case SF::Type::SzczurekUleshchenko: return os << "Szczurek-Uleshchenko";
       case SF::Type::FioreBrasse:         return os << "Fiore-Brasse";
       case SF::Type::ChristyBosted:       return os << "Christy-Bosted";
       case SF::Type::CLAS:                return os << "CLAS";
       case SF::Type::BlockDurandHa:       return os << "BDH";
       case SF::Type::ALLM91:              return os << "ALLM;91";
       case SF::Type::ALLM97:              return os << "ALLM;97";
       case SF::Type::GD07p:               return os << "ALLM;GD07p";
       case SF::Type::GD11p:               return os << "ALLM;GD11p";
       case SF::Type::Schaefer:            return os << "Schaefer";
       case SF::Type::MSTWgrid:            return os << "MSTW (grid)";
+      case SF::Type::GenericLHAPDF:       return os << "LHAPDF (generic)";
     }
     return os;
   }
 }
diff --git a/CepGen/StructureFunctions/StructureFunctions.h b/CepGen/StructureFunctions/StructureFunctions.h
index b314deb..3b62fb3 100644
--- a/CepGen/StructureFunctions/StructureFunctions.h
+++ b/CepGen/StructureFunctions/StructureFunctions.h
@@ -1,58 +1,65 @@
 #ifndef CepGen_StructureFunctions_StructureFunctions_h
 #define CepGen_StructureFunctions_StructureFunctions_h
 
+#include "CepGen/StructureFunctions/SigmaRatio.h"
+
 #include <iostream>
-#include "SigmaRatio.h"
+#include <map>
 
 namespace CepGen
 {
   namespace SF
   {
     /// Proton structure function to be used in the outgoing state description
     /// \note Values correspond to the LPAIR legacy steering card values
     enum struct Type {
       Invalid             = 0,
       Electron            = 1,
       ElasticProton       = 2,
       SuriYennie          = 11,
       SzczurekUleshchenko = 12,
       BlockDurandHa       = 13,
       FioreBrasse         = 101,
       ChristyBosted       = 102,
       CLAS                = 103,
       ALLM91              = 201,
       ALLM97              = 202,
       GD07p               = 203,
       GD11p               = 204,
       MSTWgrid            = 205,
       Schaefer            = 301,
       GenericLHAPDF       = 401,
     };
   }
   std::ostream& operator<<( std::ostream&, const SF::Type& );
 
+  class StructureFunctionsFactory;
   class StructureFunctions
   {
     public:
+      StructureFunctions( const StructureFunctions& sf ) :
+        type( sf.type ), F2( sf.F2 ), FL( sf.FL ), old_vals_( sf.old_vals_ ) {}
       StructureFunctions( const SF::Type& type = SF::Type::Invalid, double f2 = 0., double fl = 0. ) :
         type( type ), F2( f2 ), FL( fl ), old_vals_({ 0., 0. }) {}
 
+      static StructureFunctions builder( const SF::Type& );
+
       virtual StructureFunctions& operator()( double q2, double xbj ) { return *this; }
       void computeFL( double q2, double xbj, const SF::SigmaRatio& ratio = SF::E143Ratio() );
       void computeFL( double q2, double xbj, double r );
       double F1( double q2, double xbj ) const;
 
       SF::Type type;
       double F2, FL;
 
     protected:
       static const double mp_, mp2_;
       std::pair<double,double> old_vals_;
 
     private:
       std::string name_;
   };
   std::ostream& operator<<( std::ostream&, const StructureFunctions& );
 }
 
 #endif
diff --git a/CepGen/StructureFunctions/StructureFunctionsBuilder.cpp b/CepGen/StructureFunctions/StructureFunctionsBuilder.cpp
new file mode 100644
index 0000000..f876f99
--- /dev/null
+++ b/CepGen/StructureFunctions/StructureFunctionsBuilder.cpp
@@ -0,0 +1,38 @@
+#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"
+
+namespace CepGen
+{
+  StructureFunctions
+  StructureFunctionsBuilder::get( const SF::Type& sf_type )
+  {
+    std::cout << sf_type << std::endl;
+    switch ( sf_type ) {
+      case SF::Type::Electron:
+      case SF::Type::MSTWgrid: //FIXME
+      default:
+      case SF::Type::ElasticProton:       return StructureFunctions();
+      case SF::Type::SzczurekUleshchenko: return SF::SzczurekUleshchenko();
+      case SF::Type::SuriYennie:          return SF::SuriYennie();
+      case SF::Type::FioreBrasse:         return SF::FioreBrasse();
+      case SF::Type::ChristyBosted:       return SF::ChristyBosted();
+      case SF::Type::CLAS:                return SF::CLAS();
+      case SF::Type::BlockDurandHa:       return SF::BlockDurandHa();
+      case SF::Type::ALLM91:              return SF::ALLM( SF::ALLM::Parameterisation::allm91() );
+      case SF::Type::ALLM97:              return SF::ALLM( SF::ALLM::Parameterisation::allm97() );
+      case SF::Type::GD07p:               return SF::ALLM( SF::ALLM::Parameterisation::gd07p() );
+      case SF::Type::GD11p:               return SF::ALLM( SF::ALLM::Parameterisation::gd11p() );
+      case SF::Type::Schaefer:            return SF::Schaefer();
+    }
+  }
+}
diff --git a/CepGen/StructureFunctions/StructureFunctionsBuilder.h b/CepGen/StructureFunctions/StructureFunctionsBuilder.h
new file mode 100644
index 0000000..8a58944
--- /dev/null
+++ b/CepGen/StructureFunctions/StructureFunctionsBuilder.h
@@ -0,0 +1,22 @@
+#ifndef CepGen_StructureFunctions_StructureFunctionsBuilder_h
+#define CepGen_StructureFunctions_StructureFunctionsBuilder_h
+
+#include "CepGen/StructureFunctions/StructureFunctions.h"
+
+namespace CepGen
+{
+  /// Helper class to generate any supported set of structure functions
+  class StructureFunctionsBuilder
+  {
+    public:
+      StructureFunctionsBuilder() {}
+      ~StructureFunctionsBuilder() {}
+
+      /// Build structure functions from the modelling type
+      static StructureFunctions get( const SF::Type& );
+      /// Build structure functions from the modelling name
+      static StructureFunctions get( const char* );
+  };
+}
+
+#endif
diff --git a/CepGen/StructureFunctions/SuriYennie.cpp b/CepGen/StructureFunctions/SuriYennie.cpp
index 850ff99..dc60836 100644
--- a/CepGen/StructureFunctions/SuriYennie.cpp
+++ b/CepGen/StructureFunctions/SuriYennie.cpp
@@ -1,65 +1,67 @@
 #include "SuriYennie.h"
 #include "CepGen/Physics/ParticleProperties.h"
+#include "CepGen/Core/Exception.h"
 #include <math.h>
 
 namespace CepGen
 {
   namespace SF
   {
     SuriYennie::Parameterisation
     SuriYennie::Parameterisation::standard()
     {
       Parameterisation p;
       p.C1 = 0.86926;
       p.C2 = 2.23422;
       p.D1 = 0.12549;
       p.rho2 = 0.585;
       p.Cp = 0.96;
       p.Bp = 0.63;
       return p;
     }
 
     SuriYennie::Parameterisation
     SuriYennie::Parameterisation::alternative()
     {
       Parameterisation p;
       p.C1 = 0.6303;
       p.C2 = 2.3049;
       p.D1 = 0.04681;
       p.rho2 = 1.05;
       p.Cp = 1.23;
       p.Bp = 0.61;
       return p;
     }
 
     SuriYennie::SuriYennie( const SuriYennie::Parameterisation& param ) :
       F1( 0. ), FE( 0. ), FM( 0. ), params_( param )
     {}
 
     SuriYennie&
     SuriYennie::operator()( double q2, double xbj )
     {
+      CG_INFO( "SY" );
       std::pair<double,double> nv = { q2, xbj };
       if ( nv == old_vals_ )
         return *this;
       old_vals_ = nv;
 
       const double inv_q2 = 1./q2;
       const double dm2 = q2*( 1.-xbj )/xbj;
       const double mx2 = mp2_ + dm2; // [GeV^2]
       const double en = dm2+q2, en2 = en*en; // [GeV^2]
       const double nu = 0.5 * en / mp_, Xpr = q2/( q2+mx2 ), tau = 0.25 * q2/mp2_;
       const double mq = params_.rho2+q2;
 
       FM = ( params_.C1*dm2*pow( params_.rho2/mq, 2 )
             + ( params_.C2*mp2_*pow( 1.-Xpr, 4 ) ) / ( 1.+Xpr*( Xpr*params_.Cp-2.*params_.Bp ) ) ) * inv_q2;
       FE = ( tau*FM + params_.D1*dm2*q2*params_.rho2/mp2_*pow( dm2/mq, 2 )/en2 ) / ( 1.+0.25*en2/mp2_*inv_q2 );
 
       //const double w2 = 2.*mp*FE;
 
       F1 = 0.5*FM*q2/mp_;
       F2 = 2.*nu*FE;
       return *this;
     }
   }
 }