diff --git a/CepGen/Core/Generator.cpp b/CepGen/Core/Generator.cpp
index 92c9e56..22adf79 100644
--- a/CepGen/Core/Generator.cpp
+++ b/CepGen/Core/Generator.cpp
@@ -1,185 +1,185 @@
 #include "CepGen/Generator.h"
 #include "CepGen/Parameters.h"
 #include "CepGen/Version.h"
 
 #include "CepGen/Core/Integrator.h"
 #include "CepGen/Core/Exception.h"
 #include "CepGen/Core/Timer.h"
 
 #include "CepGen/Physics/PDG.h"
 #include "CepGen/Processes/ProcessesHandler.h"
 #include "CepGen/Event/Event.h"
 
 #include <fstream>
 #include <chrono>
 #include <atomic>
 
 namespace cepgen
 {
   namespace utils { std::atomic<int> gSignal; }
   Generator::Generator() :
-    parameters( std::unique_ptr<Parameters>( new Parameters ) ), result_( -1. ), result_error_( -1. )
+    parameters_( new Parameters ), result_( -1. ), result_error_( -1. )
   {
     CG_DEBUG( "Generator:init" ) << "Generator initialized";
     try {
       printHeader();
     } catch ( const Exception& e ) {
       e.dump();
     }
     // Random number initialization
     std::chrono::system_clock::time_point time = std::chrono::system_clock::now();
     srandom( time.time_since_epoch().count() );
   }
 
   Generator::Generator( Parameters* ip ) :
-    parameters( ip ), result_( -1. ), result_error_( -1. )
+    parameters_( ip ), result_( -1. ), result_error_( -1. )
   {}
 
   Generator::~Generator()
   {
-    if ( parameters->generation.enabled
-      && parameters->process()
-      && parameters->numGeneratedEvents() > 0 )
+    if ( parameters_->generation.enabled
+      && parameters_->process()
+      && parameters_->numGeneratedEvents() > 0 )
       CG_INFO( "Generator" )
         << "Mean generation time / event: "
-        << parameters->totalGenerationTime()*1.e3/parameters->numGeneratedEvents()
+        << parameters_->totalGenerationTime()*1.e3/parameters_->numGeneratedEvents()
         << " ms.";
   }
 
   size_t
   Generator::numDimensions() const
   {
-    if ( !parameters->process() )
+    if ( !parameters_->process() )
      return 0;
-    return parameters->process()->numDimensions();
+    return parameters_->process()->numDimensions();
   }
 
   void
   Generator::clearRun()
   {
-    integrator_.reset();
-    if ( parameters->process() ) {
-      parameters->process()->first_run = true;
-      parameters->process()->addEventContent();
-      parameters->process()->setKinematics( parameters->kinematics );
+    if ( parameters_->process() ) {
+      parameters_->process()->first_run = true;
+      parameters_->process()->addEventContent();
+      parameters_->process()->setKinematics( parameters_->kinematics );
     }
     result_ = result_error_ = -1.;
     {
       std::ostringstream os;
       for ( const auto& pr : cepgen::proc::ProcessesHandler::get().modules() )
         os << " " << pr;
       CG_DEBUG( "Generator:clearRun" ) << "Processes handled:" << os.str() << ".";
     }
   }
 
   void
   Generator::setParameters( const Parameters& ip )
   {
-    parameters.reset( new Parameters( (Parameters&)ip ) ); // copy constructor
+    parameters_.reset( new Parameters( (Parameters&)ip ) ); // copy constructor
   }
 
   void
   Generator::printHeader()
   {
     std::string tmp;
     std::ostringstream os; os << "version " << version() << std::endl;
     std::ifstream hf( "README" );
     if ( !hf.good() )
       throw CG_WARNING( "Generator" ) << "Failed to open README file.";
     while ( true ) {
       if ( !hf.good() ) break;
       getline( hf, tmp );
       os << "\n " << tmp;
     }
     hf.close();
     CG_INFO( "Generator" ) << os.str();
   }
 
   double
   Generator::computePoint( double* x )
   {
-    double res = integrand::eval( x, numDimensions(), (void*)parameters.get() );
+    double res = integrand::eval( x, numDimensions(), (void*)parameters_.get() );
     std::ostringstream os;
     for ( unsigned int i = 0; i < numDimensions(); ++i )
       os << x[i] << " ";
     CG_DEBUG( "Generator:computePoint" )
       << "Result for x[" << numDimensions() << "] = { " << os.str() << "}:\n\t"
       << res << ".";
     return res;
   }
 
   void
   Generator::computeXsection( double& xsec, double& err )
   {
     CG_INFO( "Generator" ) << "Starting the computation of the process cross-section.";
 
-    clearRun();
     integrate();
 
     xsec = result_;
     err = result_error_;
 
     if ( xsec < 1.e-2 )
       CG_INFO( "Generator" )
         << "Total cross section: " << xsec*1.e3 << " +/- " << err*1.e3 << " fb.";
     else if ( xsec < 5.e2 )
       CG_INFO( "Generator" )
         << "Total cross section: " << xsec << " +/- " << err << " pb.";
     else if ( xsec < 5.e5 )
       CG_INFO( "Generator" )
         << "Total cross section: " << xsec*1.e-3 << " +/- " << err*1.e-3 << " nb.";
     else if ( xsec < 5.e8 )
       CG_INFO( "Generator" )
         << "Total cross section: " << xsec*1.e-6 << " +/- " << err*1.e-6 << " µb.";
     else
       CG_INFO( "Generator" )
         << "Total cross section: " << xsec*1.e-9 << " +/- " << err*1.e-9 << " mb.";
   }
 
   void
   Generator::integrate()
   {
+    clearRun();
+
     // first destroy and recreate the integrator instance
     if ( !integrator_ )
-      integrator_ = std::unique_ptr<Integrator>( new Integrator( numDimensions(), integrand::eval, parameters.get() ) );
+      integrator_ = std::unique_ptr<Integrator>( new Integrator( numDimensions(), integrand::eval, parameters_.get() ) );
     else if ( integrator_->dimensions() != numDimensions() )
-      integrator_.reset( new Integrator( numDimensions(), integrand::eval, parameters.get() ) );
+      integrator_.reset( new Integrator( numDimensions(), integrand::eval, parameters_.get() ) );
 
     CG_DEBUG( "Generator:newInstance" )
       << "New integrator instance created\n\t"
-      << "Considered topology: " << parameters->kinematics.mode << " case\n\t"
+      << "Considered topology: " << parameters_->kinematics.mode << " case\n\t"
       << "Will proceed with " << numDimensions() << "-dimensional integration.";
 
     const int res = integrator_->integrate( result_, result_error_ );
     if ( res != 0 )
       throw CG_FATAL( "Generator" )
         << "Error while computing the cross-section!\n\t"
         << "GSL error: " << gsl_strerror( res ) << ".";
   }
 
   std::shared_ptr<Event>
   Generator::generateOneEvent()
   {
     integrator_->generateOne();
 
-    parameters->addGenerationTime( parameters->process()->last_event->time_total );
-    return parameters->process()->last_event;
+    parameters_->addGenerationTime( parameters_->process()->last_event->time_total );
+    return parameters_->process()->last_event;
   }
 
   void
   Generator::generate( std::function<void( const Event&, unsigned long )> callback )
   {
     const utils::Timer tmr;
 
     CG_INFO( "Generator" )
-      << parameters->generation.maxgen << " events will be generated.";
+      << parameters_->generation.maxgen << " events will be generated.";
 
-    integrator_->generate( parameters->generation.maxgen, callback );
+    integrator_->generate( parameters_->generation.maxgen, callback );
 
     const double gen_time_s = tmr.elapsed();
     CG_INFO( "Generator" )
-      << parameters->generation.ngen << " events generated "
+      << parameters_->generation.ngen << " events generated "
       << "in " << gen_time_s << " s "
-      << "(" << gen_time_s/parameters->generation.ngen*1.e3 << " ms/event).";
+      << "(" << gen_time_s/parameters_->generation.ngen*1.e3 << " ms/event).";
   }
 }
diff --git a/CepGen/Core/Parameters.cpp b/CepGen/Core/Parameters.cpp
index f61b999..88358bb 100644
--- a/CepGen/Core/Parameters.cpp
+++ b/CepGen/Core/Parameters.cpp
@@ -1,281 +1,296 @@
 #include "CepGen/Parameters.h"
 
 #include "CepGen/Core/Integrator.h"
 #include "CepGen/Core/ParametersList.h"
 #include "CepGen/Core/Exception.h"
 #include "CepGen/Core/TamingFunction.h"
 
 #include "CepGen/Physics/PDG.h"
 #include "CepGen/Processes/GenericProcess.h"
 #include "CepGen/Hadronisers/GenericHadroniser.h"
 
 #include "CepGen/StructureFunctions/StructureFunctions.h"
 
 #include <iomanip>
 
 namespace cepgen
 {
   Parameters::Parameters() :
     general( new ParametersList ),
     taming_functions( new utils::TamingFunctionsCollection ),
     store_( false ), total_gen_time_( 0. ), num_gen_events_( 0 )
   {}
 
   Parameters::Parameters( Parameters& param ) :
     general( param.general ),
     kinematics( param.kinematics ), integrator( param.integrator ), generation( param.generation ),
     taming_functions( param.taming_functions ),
     process_( std::move( param.process_ ) ),
     hadroniser_( std::move( param.hadroniser_ ) ),
     store_( false ), total_gen_time_( param.total_gen_time_ ), num_gen_events_( param.num_gen_events_ )
   {}
 
   Parameters::Parameters( const Parameters& param ) :
     general( param.general ),
     kinematics( param.kinematics ), integrator( param.integrator ), generation( param.generation ),
     taming_functions( param.taming_functions ),
     store_( false ), total_gen_time_( param.total_gen_time_ ), num_gen_events_( param.num_gen_events_ )
   {}
 
   Parameters::~Parameters() // required for unique_ptr initialisation!
   {}
 
+  Parameters&
+  Parameters::operator=( Parameters param )
+  {
+    general = param.general;
+    kinematics = param.kinematics;
+    integrator = param.integrator;
+    generation = param.generation;
+    taming_functions = param.taming_functions;
+    process_ = std::move( param.process_ );
+    hadroniser_ = std::move( param.hadroniser_ );
+    total_gen_time_ = param.total_gen_time_;
+    num_gen_events_ = param.num_gen_events_;
+    return *this;
+  }
+
   void
   Parameters::setThetaRange( float thetamin, float thetamax )
   {
     kinematics.cuts.central.eta_single = {
       Particle::thetaToEta( thetamax ),
       Particle::thetaToEta( thetamin )
     };
 
     CG_DEBUG( "Parameters" )
       << "eta in range: " << kinematics.cuts.central.eta_single
       << " => theta(min) = " << thetamin << ", theta(max) = " << thetamax << ".";
   }
 
   void
   Parameters::prepareRun()
   {
     //--- first-run preparation
     if ( process_ && process_->first_run ) {
       CG_DEBUG( "Parameters" )
         << "Run started for " << process_->name() << " process "
         << "0x" << std::hex << process_.get() << std::dec << ".\n\t"
         << "Process mode considered: " << kinematics.mode << "\n\t"
         << "   first beam: " << kinematics.incoming_beams.first << "\n\t"
         << "  second beam: " << kinematics.incoming_beams.second << "\n\t"
         << "  structure functions: " << kinematics.structure_functions;
       if ( process_->hasEvent() )
         process_->clearEvent();
       process_->first_run = false;
     }
     //--- clear the run statistics
     total_gen_time_ = 0.;
     num_gen_events_ = 0;
   }
 
   void
   Parameters::addGenerationTime( double gen_time )
   {
     total_gen_time_ += gen_time;
     num_gen_events_++;
   }
 
   proc::GenericProcess*
   Parameters::process()
   {
     return process_.get();
   }
 
   std::string
   Parameters::processName() const
   {
     if ( !process_ )
       return "no process";
     return process_->name();
   }
 
   void
   Parameters::setProcess( std::unique_ptr<proc::GenericProcess> proc )
   {
     process_ = std::move( proc );
   }
 
   void
   Parameters::setProcess( proc::GenericProcess* proc )
   {
     if ( !proc )
       throw CG_FATAL( "Parameters" )
         << "Trying to clone an invalid process!";
     process_.reset( proc );
   }
 
   hadr::GenericHadroniser*
   Parameters::hadroniser()
   {
     return hadroniser_.get();
   }
 
   std::string
   Parameters::hadroniserName() const
   {
     if ( !hadroniser_ )
       return "";
     return hadroniser_->name();
   }
 
   void
   Parameters::setHadroniser( std::unique_ptr<hadr::GenericHadroniser> hadr )
   {
     hadroniser_ = std::move( hadr );
   }
 
   void
   Parameters::setHadroniser( hadr::GenericHadroniser* hadr )
   {
     hadroniser_.reset( hadr );
   }
 
   std::ostream&
   operator<<( std::ostream& os, const Parameters* p )
   {
     const bool pretty = true;
 
     const int wb = 90, wt = 33;
     os << std::left << "\n";
     if ( p->process_ ) {
       os
         << std::setfill('_') << std::setw( wb+3 ) << "_/¯ PROCESS INFORMATION ¯\\_" << std::setfill( ' ' ) << "\n"
         << std::right << std::setw( wb ) << std::left << std::endl
         << std::setw( wt ) << "Process to generate"
         << ( pretty ? boldify( p->process_->name().c_str() ) : p->process_->name() ) << "\n"
         << std::setw( wt ) << "" << p->process_->description() << "\n";
         for ( const auto& par : p->process_->parameters().keys() )
           os << "    " << par << ": " << p->process_->parameters().getString( par ) << "\n";
     }
     os
       << "\n"
       << std::setfill('_') << std::setw( wb+3 ) << "_/¯ RUN INFORMATION ¯\\_" << std::setfill( ' ' ) << "\n"
       << std::right << std::setw( wb ) << std::left << std::endl
       << std::setw( wt ) << "Events generation? "
       << ( pretty ? yesno( p->generation.enabled ) : std::to_string( p->generation.enabled ) ) << "\n"
       << std::setw( wt ) << "Number of events to generate"
       << ( pretty ? boldify( p->generation.maxgen ) : std::to_string( p->generation.maxgen ) ) << "\n";
     if ( p->generation.num_threads > 1 )
       os
         << std::setw( wt ) << "Number of threads" << p->generation.num_threads << "\n";
     os
       << std::setw( wt ) << "Number of points to try per bin" << p->generation.num_points << "\n"
       << std::setw( wt ) << "Integrand treatment"
       << ( pretty ? yesno( p->generation.treat ) : std::to_string( p->generation.treat ) ) << "\n"
       << std::setw( wt ) << "Verbosity level " << utils::Logger::get().level << "\n";
     if ( p->hadroniser_ ) {
       os
         << "\n"
         << std::setfill( '-' ) << std::setw( wb+6 )
         << ( pretty ? boldify( " Hadronisation algorithm " ) : "Hadronisation algorithm" ) << std::setfill( ' ' ) << "\n\n"
         << std::setw( wt ) << "Name"
         << ( pretty ? boldify( p->hadroniser_->name().c_str() ) : p->hadroniser_->name() ) << "\n";
     }
     os
       << "\n"
       << std::setfill( '-' ) << std::setw( wb+6 )
       << ( pretty ? boldify( " Integration parameters " ) : "Integration parameters" ) << std::setfill( ' ' ) << "\n\n";
     std::ostringstream int_algo; int_algo << p->integrator.type;
     os
       << std::setw( wt ) << "Integration algorithm"
       << ( pretty ? boldify( int_algo.str().c_str() ) : int_algo.str() ) << "\n"
       << std::setw( wt ) << "Number of function calls" << p->integrator.ncvg << "\n"
       << std::setw( wt ) << "Random number generator seed" << p->integrator.rng_seed << "\n";
     if ( p->integrator.rng_engine )
       os
         << std::setw( wt ) << "Random number generator engine"
         << p->integrator.rng_engine->name << "\n";
     std::ostringstream proc_mode; proc_mode << p->kinematics.mode;
     os
       << "\n"
       << std::setfill('_') << std::setw( wb+3 ) << "_/¯ EVENTS KINEMATICS ¯\\_" << std::setfill( ' ' ) << "\n\n"
       << std::setw( wt ) << "Incoming particles"
       << p->kinematics.incoming_beams.first << ",\n" << std::setw( wt ) << ""
       << p->kinematics.incoming_beams.second << "\n"
       << std::setw( wt ) << "C.m. energy (GeV)" << p->kinematics.sqrtS() << "\n";
     if ( p->kinematics.mode != KinematicsMode::invalid )
       os << std::setw( wt ) << "Subprocess mode" << ( pretty ? boldify( proc_mode.str().c_str() ) : proc_mode.str() ) << "\n";
     if ( p->kinematics.mode != KinematicsMode::ElasticElastic )
       os << std::setw( wt ) << "Structure functions" << *p->kinematics.structure_functions << "\n";
     os
       << "\n"
       << std::setfill( '-' ) << std::setw( wb+6 ) << ( pretty ? boldify( " Incoming partons " ) : "Incoming partons" ) << std::setfill( ' ' ) << "\n\n";
     for ( const auto& lim : p->kinematics.cuts.initial.list() ) // map(particles class, limits)
       if ( lim.second.valid() )
         os << std::setw( wt ) << lim.first << lim.second << "\n";
     os
       << "\n"
       << std::setfill( '-' ) << std::setw( wb+6 ) << ( pretty ? boldify( " Outgoing central system " ) : "Outgoing central system" ) << std::setfill( ' ' ) << "\n\n";
     for ( const auto& lim : p->kinematics.cuts.central.list() )
       if ( lim.second.valid() )
         os << std::setw( wt ) << lim.first << lim.second << "\n";
     if ( p->kinematics.cuts.central_particles.size() > 0 ) {
       os << std::setw( wt ) << ( pretty ? boldify( ">>> per-particle cuts:" ) : ">>> per-particle cuts:" ) << "\n";
       for ( const auto& part_per_lim : p->kinematics.cuts.central_particles ) {
         os << " * all single " << std::setw( wt-3 ) << part_per_lim.first << "\n";
         for ( const auto& lim : part_per_lim.second.list() )
           if ( lim.second.valid() )
             os << "   - " << std::setw( wt-5 ) << lim.first << lim.second << "\n";
       }
     }
     os << "\n";
     os << std::setfill( '-' ) << std::setw( wb+6 ) << ( pretty ? boldify( " Proton / remnants " ) : "Proton / remnants" ) << std::setfill( ' ' ) << "\n\n";
     for ( const auto& lim : p->kinematics.cuts.remnants.list() )
       os << std::setw( wt ) << lim.first << lim.second << "\n";
     return os;
   }
 
   std::ostream&
   operator<<( std::ostream& os, const Parameters& p )
   {
     return os << &p;
   }
 
   //-----------------------------------------------------------------------------------------------
 
   Parameters::Integration::Integration() :
     type( IntegratorType::Vegas ), ncvg( 50000 ),
     rng_seed( 0 ), rng_engine( (gsl_rng_type*)gsl_rng_mt19937 ),
     vegas_chisq_cut( 1.5 ),
     result( -1. ), err_result( -1. )
   {
     const size_t ndof = 10;
     {
       std::shared_ptr<gsl_monte_vegas_state> tmp_state( gsl_monte_vegas_alloc( ndof ), gsl_monte_vegas_free );
       gsl_monte_vegas_params_get( tmp_state.get(), &vegas );
       vegas.iterations = 10;
     }
     {
       std::shared_ptr<gsl_monte_miser_state> tmp_state( gsl_monte_miser_alloc( ndof ), gsl_monte_miser_free );
       gsl_monte_miser_params_get( tmp_state.get(), &miser );
     }
   }
 
   Parameters::Integration::Integration( const Integration& rhs ) :
     type( rhs.type ), ncvg( rhs.ncvg ),
     rng_seed( rhs.rng_seed ), rng_engine( rhs.rng_engine ),
     vegas( rhs.vegas ), vegas_chisq_cut( rhs.vegas_chisq_cut ),
     miser( rhs.miser ),
     result( -1. ), err_result( -1. )
   {}
 
   Parameters::Integration::~Integration()
   {
     //if ( vegas.ostream && vegas.ostream != stdout && vegas.ostream != stderr )
     //  fclose( vegas.ostream );
   }
 
   //-----------------------------------------------------------------------------------------------
 
   Parameters::Generation::Generation() :
     enabled( false ), maxgen( 0 ),
     symmetrise( false ), treat( true ), ngen( 0 ), gen_print_every( 10000 ),
     num_threads( 2 ), num_points( 100 )
   {}
 }
diff --git a/CepGen/Generator.h b/CepGen/Generator.h
index a383a58..5118332 100644
--- a/CepGen/Generator.h
+++ b/CepGen/Generator.h
@@ -1,122 +1,126 @@
 #ifndef CepGen_Generator_h
 #define CepGen_Generator_h
 
 #include <sstream>
 #include <memory>
 #include <functional>
 
 ////////////////////////////////////////////////////////////////////////////////
 
 /**
  * \mainpage Foreword
  * This Monte Carlo generator was developed as a modern version of the LPAIR code introduced
  * in the early 1990s by J. Vermaseren *et al*\cite Baranov:1991yq\cite Vermaseren:1982cz. This latter allows to
  * compute the cross-section and to generate events for the \f$\gamma\gamma\to\ell^{+}\ell^{-}\f$
  * process in the scope of high energy physics.
  *
  * Soon after the integration of its matrix element, it was extended as a tool to compute and
  * generate events for any generic 2\f$\rightarrow\f$ 3 central exclusive process.
  * To do so, the main operation performed here is the integration of the matrix element (given as a
  * subset of a GenericProcess object) over the full available phase space.
  *
  */
 
 ////////////////////////////////////////////////////////////////////////////////
 
 /// Common namespace for this Monte Carlo generator
 namespace cepgen
 {
   namespace integrand
   {
     /**
      * Function to be integrated. It returns the value of the weight for one point
      * of the full phase space (or "event"). This weights includes the matrix element
      * of the process considered, along with all the kinematic factors, and the cut
      * restrictions imposed on this phase space. \f$x\f$ is therefore an array of random
      * numbers defined inside its boundaries (as normalised so that \f$\forall i<\mathrm{ndim}\f$,
      * \f$0<x_i<1\f$.
      */
     double eval( double*, size_t, void* );
   }
 
   class Event;
   class Integrator;
   class Parameters;
 
   ////////////////////////////////////////////////////////////////////////////////
 
   /**
    * This object represents the core of this Monte Carlo generator, with its
    * capability to generate the events (using the embedded Vegas object) and to
    * study the phase space in term of the variation of resulting cross section
    * while scanning the various parameters (point \f${\bf x}\f$ in the
    * multi-dimensional phase space).
    *
    * The phase space is constrained using the Parameters object given as an
    * argument to the constructor, and the differential cross-sections for each
    * value of the array \f${\bf x}\f$ are computed in the \a f-function defined
    * outside (but populated inside) this object.
    *
    * This f-function embeds a GenericProcess-inherited object which defines all the
    * methods to compute this differential cross-section as well as the in- and outgoing
    * kinematics associated to each particle.
    *
    * \author Laurent Forthomme <laurent.forthomme@cern.ch>
    * \date Feb 2013
    * \brief Core of the Monte-Carlo generator
    *
    */
   class Generator {
     public:
       /// Core of the Monte Carlo integrator and events generator
       Generator();
       /// Core of the Monte Carlo integrator and events generator
       /// \param[in] ip List of input parameters defining the phase space on which to perform the integration
       Generator( Parameters *ip );
       ~Generator();
+
       /// Dump this program's header into the standard output stream
       void printHeader();
+
+      const Parameters& parameters() const { return *parameters_; }
       /// Feed the generator with a Parameters object
       void setParameters( const Parameters& ip );
       /// Remove all references to a previous generation/run
       void clearRun();
       /**
        * Compute the cross section for the run parameters defined by this object.
        * This returns the cross section as well as the absolute error computed along.
        * \brief Compute the cross-section for the given process
        * \param[out] xsec The computed cross-section, in pb
        * \param[out] err The absolute integration error on the computed cross-section, in pb
        */
       void computeXsection( double& xsec, double& err );
-      /// Integrate the functional over the whole phase space
-      void integrate();
       /// Last cross section computed by the generator
       double crossSection() const { return result_; }
       /// Last error on the cross section computed by the generator
       double crossSectionError() const { return result_error_; }
 
       //void terminate();
       /// Generate one single event given the phase space computed by Vegas in the integration step
       /// \return A pointer to the Event object generated in this run
       std::shared_ptr<Event> generateOneEvent();
       /// Launch the generation of events
       void generate( std::function<void( const Event&, unsigned long )> callback = nullptr );
       /// Number of dimensions on which the integration is performed
       size_t numDimensions() const;
       /// Compute one single point from the total phase space
       /// \param[in] x the n-dimensional point to compute
       /// \return the function value for the given point
       double computePoint( double* x );
-      /// Physical Parameters used in the events generation and cross-section computation
-      std::unique_ptr<Parameters> parameters;
+
    private:
+      /// Integrate the functional over the whole phase space
+      void integrate();
+      /// Physical Parameters used in the events generation and cross-section computation
+      std::unique_ptr<Parameters> parameters_;
       /// Vegas instance which will integrate the function
       std::unique_ptr<Integrator> integrator_;
       /// Cross section value computed at the last integration
       double result_;
       /// Error on the cross section as computed in the last integration
       double result_error_;
   };
 }
 
 #endif
diff --git a/CepGen/Parameters.h b/CepGen/Parameters.h
index 2fe6e5e..0260b0d 100644
--- a/CepGen/Parameters.h
+++ b/CepGen/Parameters.h
@@ -1,149 +1,153 @@
 #ifndef CepGen_Parameters_h
 #define CepGen_Parameters_h
 
 #include "CepGen/Physics/Kinematics.h"
 
 #include <memory>
 
 #include <gsl/gsl_monte_vegas.h>
 #include <gsl/gsl_monte_miser.h>
 
 namespace cepgen
 {
   class Event;
   class ParametersList;
   namespace proc { class GenericProcess; }
   namespace hadr { class GenericHadroniser; }
   namespace utils { class TamingFunctionsCollection; }
   enum class IntegratorType;
   /// List of parameters used to start and run the simulation job
   class Parameters
   {
     public:
       Parameters();
       /// Copy constructor (transfers ownership to the process/hadroniser!)
       Parameters( Parameters& );
       /// Const copy constructor (all but the process and the hadroniser)
       Parameters( const Parameters& );
       ~Parameters(); // required for unique_ptr initialisation!
+
+      /// Assignment operator
+      Parameters& operator=( Parameters );
+
       /// Set the polar angle range for the produced leptons
       /// \param[in] thetamin The minimal value of \f$\theta\f$ for the outgoing leptons
       /// \param[in] thetamax The maximal value of \f$\theta\f$ for the outgoing leptons
       void setThetaRange( float thetamin, float thetamax );
       /// Dump the input parameters in the terminal
       friend std::ostream& operator<<( std::ostream& os, const Parameters* );
       friend std::ostream& operator<<( std::ostream& os, const Parameters& );
 
       std::shared_ptr<ParametersList> general;
 
       //----- process to compute
 
       /// Process for which the cross-section will be computed and the events will be generated
       proc::GenericProcess* process();
       /// Name of the process considered
       std::string processName() const;
       /// Set the process to study
       void setProcess( std::unique_ptr<proc::GenericProcess> proc );
       /// Set the process to study
       void setProcess( proc::GenericProcess* proc );
 
       //----- events kinematics
 
       /// Events kinematics for phase space definition
       Kinematics kinematics;
 
       //----- VEGAS
 
       /// Collection of integrator parameters
       struct Integration
       {
         Integration();
         Integration( const Integration& );
         ~Integration();
         IntegratorType type;
         /// Number of function calls to be computed for each point
         unsigned int ncvg; // ??
         /// Random number generator seed
         long rng_seed;
         /// Random number generator engine
         gsl_rng_type* rng_engine;
         gsl_monte_vegas_params vegas;
         double vegas_chisq_cut;
         gsl_monte_miser_params miser;
         double result, err_result;
       };
       /// Integrator parameters
       Integration integrator;
 
       //----- events generation
 
       /// Collection of events generation parameters
       struct Generation
       {
         Generation();
         /// Are we generating events ? (true) or are we only computing the cross-section ? (false)
         bool enabled;
         /// Maximal number of events to generate in this run
         unsigned int maxgen;
         /// Do we want the events to be symmetrised with respect to the \f$z\f$-axis ?
         bool symmetrise;
         /// Is the integrand to be smoothed for events generation?
         bool treat;
         /// Number of events already generated in this run
         unsigned int ngen;
         /// Frequency at which the events are displayed to the end-user
         unsigned int gen_print_every;
         /// Number of threads to perform the integration
         unsigned int num_threads;
         /// Number of points to "shoot" in each integration bin by the algorithm
         unsigned int num_points;
       };
       /// Events generation parameters
       Generation generation;
 
       /// Specify if the generated events are to be stored
       void setStorage( bool store ) { store_ = store; }
       /// Are the events generated in this run to be stored in the output file ?
       bool storage() const { return store_; }
 
       //----- hadronisation algorithm
 
       /// Hadronisation algorithm to use for the proton(s) fragmentation
       hadr::GenericHadroniser* hadroniser();
       /// Name of the hadroniser (if applicable)
       std::string hadroniserName() const;
       /// Set the hadronisation algorithm
       void setHadroniser( std::unique_ptr<hadr::GenericHadroniser> hadr );
       /// Set the hadronisation algorithm
       void setHadroniser( hadr::GenericHadroniser* hadr );
 
       //----- taming functions
 
       /// Functionals to be used to account for rescattering corrections (implemented within the process)
       std::shared_ptr<utils::TamingFunctionsCollection> taming_functions;
 
       //----- run operations
 
       /// Reset the total generation time and the number of events generated for this run, prepare kinematics
       void prepareRun();
       /// Add a new timing into the total generation time
       /// \param[in] gen_time Time to add (in seconds)
       void addGenerationTime( double gen_time );
       /// Return the total generation time for this run (in seconds)
       inline double totalGenerationTime() const { return total_gen_time_; }
       /// Total number of events already generated in this run
       inline unsigned int numGeneratedEvents() const { return num_gen_events_; }
 
     private:
       std::unique_ptr<proc::GenericProcess> process_;
       std::unique_ptr<hadr::GenericHadroniser> hadroniser_;
 
       bool store_;
       /// Total generation time (in seconds)
       double total_gen_time_;
       /// Number of events already generated
       unsigned int num_gen_events_;
   };
 }
 
 #endif
diff --git a/CepGen/Processes/GenericKTProcess.cpp b/CepGen/Processes/GenericKTProcess.cpp
index f84fc04..da287eb 100644
--- a/CepGen/Processes/GenericKTProcess.cpp
+++ b/CepGen/Processes/GenericKTProcess.cpp
@@ -1,378 +1,380 @@
 #include "CepGen/Processes/GenericKTProcess.h"
 
 #include "CepGen/Core/Exception.h"
 
 #include "CepGen/Event/Event.h"
 
 #include "CepGen/StructureFunctions/StructureFunctions.h"
 #include "CepGen/StructureFunctions/SigmaRatio.h"
 
 #include "CepGen/Physics/Constants.h"
 #include "CepGen/Physics/KTFlux.h"
 #include "CepGen/Physics/FormFactors.h"
 #include "CepGen/Physics/PDG.h"
 
 #include <iomanip>
 
 namespace cepgen
 {
   namespace proc
   {
     GenericKTProcess::GenericKTProcess( const ParametersList& params,
                                         const std::string& name,
                                         const std::string& description,
                                         const std::array<PDG,2>& partons,
                                         const std::vector<PDG>& central ) :
       GenericProcess( params, name, description+" (kT-factorisation approach)" ),
       num_dimensions_( 0 ), kt_jacobian_( 0. ),
       qt1_( 0. ), phi_qt1_( 0. ), qt2_( 0. ), phi_qt2_( 0. ),
       kIntermediateParts( partons ), kProducedParts( central )
     {}
 
     void
     GenericKTProcess::addEventContent()
     {
       GenericProcess::setEventContent(
         { // incoming state
           { Particle::IncomingBeam1, PDG::proton },
           { Particle::IncomingBeam2, PDG::proton },
           { Particle::Parton1, kIntermediateParts[0] },
           { Particle::Parton2, kIntermediateParts[1] }
         },
         { // outgoing state
           { Particle::OutgoingBeam1, { PDG::proton } },
           { Particle::OutgoingBeam2, { PDG::proton } },
           { Particle::CentralSystem, kProducedParts }
         }
       );
       setExtraContent();
     }
 
     unsigned int
     GenericKTProcess::numDimensions() const
     {
       return num_dimensions_;
     }
 
     void
     GenericKTProcess::setKinematics( const Kinematics& kin )
     {
       kin_ = kin;
 
       const KTFlux flux1 = (KTFlux)kin_.incoming_beams.first.kt_flux,
                    flux2 = (KTFlux)kin_.incoming_beams.second.kt_flux;
 
       if ( kin_.mode == KinematicsMode::invalid ) {
         //--- try to extrapolate kinematics mode from unintegrated fluxes
         bool el1 = ( flux1 == KTFlux::P_Photon_Elastic
                   || flux1 == KTFlux::HI_Photon_Elastic
                   || flux1 == KTFlux::P_Gluon_KMR );
         bool el2 = ( flux2 == KTFlux::P_Photon_Elastic
                   || flux2 == KTFlux::HI_Photon_Elastic
                   || flux2 == KTFlux::P_Gluon_KMR );
         if ( el1 && el2 )
           kin_.mode = KinematicsMode::ElasticElastic;
         else if ( el1 )
           kin_.mode = KinematicsMode::ElasticInelastic;
         else if ( el2 )
           kin_.mode = KinematicsMode::InelasticElastic;
         else
           kin_.mode = KinematicsMode::InelasticInelastic;
       }
       else {
         //--- try to extrapolate unintegrated fluxes from kinematics mode
         const HeavyIon hi1( kin_.incoming_beams.first.pdg ), hi2( kin_.incoming_beams.second.pdg );
         //==========================================================================================
         // ensure the first incoming flux is compatible with the kinematics mode
         //==========================================================================================
         if ( ( kin_.mode == KinematicsMode::ElasticElastic
             || kin_.mode == KinematicsMode::ElasticInelastic )
           && ( flux1 != KTFlux::P_Photon_Elastic ) ) {
           kin_.incoming_beams.first.kt_flux = hi1
             ? KTFlux::HI_Photon_Elastic
             : KTFlux::P_Photon_Elastic;
           CG_DEBUG( "GenericKTProcess:kinematics" )
             << "Set the kt flux for first incoming photon to \""
             << kin_.incoming_beams.first.kt_flux << "\".";
         }
         else if ( flux1 != KTFlux::P_Photon_Inelastic
                && flux1 != KTFlux::P_Photon_Inelastic_Budnev ) {
           if ( hi1 )
             throw CG_FATAL( "GenericKTProcess:kinematics" )
               << "Inelastic photon emission from HI not yet supported!";
           kin_.incoming_beams.first.kt_flux = KTFlux::P_Photon_Inelastic_Budnev;
           CG_DEBUG( "GenericKTProcess:kinematics" )
             << "Set the kt flux for first incoming photon to \""
             << kin_.incoming_beams.first.kt_flux << "\".";
         }
         //==========================================================================================
         // ensure the second incoming flux is compatible with the kinematics mode
         //==========================================================================================
         if ( ( kin_.mode == KinematicsMode::ElasticElastic
             || kin_.mode == KinematicsMode::InelasticElastic )
           && ( flux2 != KTFlux::P_Photon_Elastic ) ) {
           kin_.incoming_beams.second.kt_flux = hi2
             ? KTFlux::HI_Photon_Elastic
             : KTFlux::P_Photon_Elastic;
           CG_DEBUG( "GenericKTProcess:kinematics" )
             << "Set the kt flux for second incoming photon to \""
             << kin_.incoming_beams.second.kt_flux << "\".";
         }
         else if ( flux2 != KTFlux::P_Photon_Inelastic
                && flux2 != KTFlux::P_Photon_Inelastic_Budnev ) {
           if ( hi2 )
             throw CG_FATAL( "GenericKTProcess:kinematics" )
               << "Inelastic photon emission from HI not yet supported!";
           kin_.incoming_beams.second.kt_flux = KTFlux::P_Photon_Inelastic_Budnev;
           CG_DEBUG( "GenericKTProcess:kinematics" )
             << "Set the kt flux for second incoming photon to \""
             << kin_.incoming_beams.second.kt_flux << "\".";
         }
       }
 
+std::cout << kin_.incoming_beams.first << "|" << kin_.incoming_beams.second << std::endl;
+
       //============================================================================================
       // initialise the "constant" (wrt x) part of the Jacobian
       //============================================================================================
 
       kt_jacobian_ = 1.;
       num_dimensions_ = 0;
       mapped_variables_.clear();
 
       //============================================================================================
       // register the incoming partons' variables
       //============================================================================================
 
       registerVariable( qt1_, Mapping::logarithmic, kin_.cuts.initial.qt, { 1.e-10, 500. }, "First incoming parton virtuality" );
       registerVariable( qt2_, Mapping::logarithmic, kin_.cuts.initial.qt, { 1.e-10, 500. }, "Second incoming parton virtuality" );
       registerVariable( phi_qt1_, Mapping::linear, kin_.cuts.initial.phi_qt, { 0., 2.*M_PI }, "First incoming parton azimuthal angle" );
       registerVariable( phi_qt2_, Mapping::linear, kin_.cuts.initial.phi_qt, { 0., 2.*M_PI }, "Second incoming parton azimuthal angle" );
 
       //============================================================================================
       // register all process-dependent variables
       //============================================================================================
 
       preparePhaseSpace();
 
       //============================================================================================
       // register the outgoing remnants' variables
       //============================================================================================
 
       MX_ = event_->getOneByRole( Particle::IncomingBeam1 ).mass();
       MY_ = event_->getOneByRole( Particle::IncomingBeam2 ).mass();
       if ( kin_.mode == KinematicsMode::InelasticElastic || kin_.mode == KinematicsMode::InelasticInelastic )
         registerVariable( MX_, Mapping::square, kin_.cuts.remnants.mass_single, { 1.07, 1000. }, "Positive z proton remnant mass" );
       if ( kin_.mode == KinematicsMode::ElasticInelastic || kin_.mode == KinematicsMode::InelasticInelastic )
         registerVariable( MY_, Mapping::square, kin_.cuts.remnants.mass_single, { 1.07, 1000. }, "Negative z proton remnant mass" );
 
       prepareKinematics();
     }
 
     double
     GenericKTProcess::computeWeight()
     {
       if ( mapped_variables_.size() == 0 )
         throw CG_FATAL( "GenericKTProcess:weight" )
           << "No variables are mapped with this process!";
       if ( kt_jacobian_ == 0. )
         throw CG_FATAL( "GenericKTProcess:weight" )
           << "Point-independant component of the Jacobian for this "
           << "kt-factorised process is null.\n\t"
           << "Please check the validity of the phase space!";
 
       //============================================================================================
       // generate and initialise all variables, and auxiliary (x-dependent) part of the Jacobian
       // for this phase space point.
       //============================================================================================
 
       const double aux_jacobian = generateVariables();
       if ( aux_jacobian <= 0. )
         return 0.;
 
       //============================================================================================
       // compute the integrand and combine together into a single weight for the phase space point.
       //============================================================================================
 
       const double integrand = computeKTFactorisedMatrixElement();
       if ( integrand <= 0. )
         return 0.;
 
       const double weight = ( kt_jacobian_*aux_jacobian ) * integrand;
 
       CG_DEBUG_LOOP( "GenericKTProcess:weight" )
         << "Jacobian: " << kt_jacobian_ << " * " << aux_jacobian
         << " = " << ( kt_jacobian_*aux_jacobian ) << ".\n\t"
         << "Integrand = " << integrand << "\n\t"
         << "dW = " << weight << ".";
 
       return weight;
     }
 
     std::pair<double,double>
     GenericKTProcess::incomingFluxes( double x1, double q1t2, double x2, double q2t2 ) const
     {
       const HeavyIon hi1( kin_.incoming_beams.first.pdg ), hi2( kin_.incoming_beams.second.pdg );
       //--- compute fluxes according to modelling specified in parameters card
       std::pair<double,double> fluxes = {
         hi1
           ? ktFlux( (KTFlux)kin_.incoming_beams.first.kt_flux, x1, q1t2, hi1 )
           : ktFlux( (KTFlux)kin_.incoming_beams.first.kt_flux, x1, q1t2, *kin_.structure_functions, MX_ ),
         hi2
           ? ktFlux( (KTFlux)kin_.incoming_beams.second.kt_flux, x2, q2t2, hi2 )
           : ktFlux( (KTFlux)kin_.incoming_beams.second.kt_flux, x2, q2t2, *kin_.structure_functions, MY_ )
       };
 
       CG_DEBUG_LOOP( "GenericKTProcess:fluxes" )
         << "KT fluxes: " << fluxes.first << " / " << fluxes.second << ".";
       return fluxes;
     }
 
     void
     GenericKTProcess::registerVariable( double& out, const Mapping& type,
                                         const Limits& in, Limits default_limits,
                                         const char* description )
     {
       Limits lim = in;
       out = 0.; // reset the variable
       if ( !in.valid() ) {
         CG_DEBUG( "GenericKTProcess:registerVariable" )
           << description << " could not be retrieved from the user configuration!\n\t"
           << "Setting it to the default value: " << default_limits << ".";
         lim = default_limits;
       }
       if ( type == Mapping::logarithmic )
         lim = {
           std::max( log( lim.min() ), -10. ),
           std::min( log( lim.max() ), +10. )
         };
       mapped_variables_.emplace_back( MappingVariable{ description, lim, out, type, num_dimensions_++ } );
       switch ( type ) {
         case Mapping::square:
           kt_jacobian_ *= 2.*lim.range();
           break;
         default:
           kt_jacobian_ *= lim.range();
           break;
       }
       CG_DEBUG( "GenericKTProcess:registerVariable" )
         << description << " has been mapped to variable " << num_dimensions_ << ".\n\t"
         << "Allowed range for integration: " << lim << ".\n\t"
         << "Variable integration mode: " << type << ".";
     }
 
     void
     GenericKTProcess::dumpVariables() const
     {
       std::ostringstream os;
       for ( const auto& var : mapped_variables_ )
         os << "\n\t(" << var.index << ") " << var.type << " mapping (" << var.description << ") in range " << var.limits;
       CG_INFO( "GenericKTProcess:dumpVariables" )
         << "List of variables handled by this kt-factorised process:"
         << os.str();
     }
 
     double
     GenericKTProcess::generateVariables() const
     {
       double jacobian = 1.;
       for ( const auto& cut : mapped_variables_ ) {
         if ( !cut.limits.valid() )
           continue;
         const double xv = x( cut.index ); // between 0 and 1
         switch ( cut.type ) {
           case Mapping::linear: {
             cut.variable = cut.limits.x( xv );
           } break;
           case Mapping::logarithmic: {
             cut.variable = exp( cut.limits.x( xv ) );
             jacobian *= cut.variable;
           } break;
           case Mapping::square: {
             cut.variable = cut.limits.x( xv );
             jacobian *= cut.variable;
           } break;
         }
       }
       if ( CG_EXCEPT_MATCH( "KtProcess:vars", debugInsideLoop ) ) {
         std::ostringstream oss;
         for ( const auto& cut : mapped_variables_ ) {
           oss << "variable " << cut.index
               << " in range " << std::left << std::setw( 20 ) << cut.limits << std::right
               << " has value " << cut.variable << "\n\t";
         }
         CG_DEBUG_LOOP( "KtProcess:vars" ) << oss.str();
       }
       return jacobian;
     }
 
     void
     GenericKTProcess::fillKinematics( bool )
     {
       fillCentralParticlesKinematics(); // process-dependent!
       fillPrimaryParticlesKinematics();
     }
 
     void
     GenericKTProcess::fillPrimaryParticlesKinematics()
     {
       //============================================================================================
       //     outgoing protons
       //============================================================================================
 
       Particle& op1 = event_->getOneByRole( Particle::OutgoingBeam1 ),
                &op2 = event_->getOneByRole( Particle::OutgoingBeam2 );
 
       op1.setMomentum( PX_ );
       op2.setMomentum( PY_ );
 
       switch ( kin_.mode ) {
         case KinematicsMode::ElasticElastic:
           op1.setStatus( Particle::Status::FinalState );
           op2.setStatus( Particle::Status::FinalState );
           break;
         case KinematicsMode::ElasticInelastic:
           op1.setStatus( Particle::Status::FinalState );
           op2.setStatus( Particle::Status::Unfragmented ); op2.setMass( MY_ );
           break;
         case KinematicsMode::InelasticElastic:
           op1.setStatus( Particle::Status::Unfragmented ); op1.setMass( MX_ );
           op2.setStatus( Particle::Status::FinalState );
           break;
         case KinematicsMode::InelasticInelastic:
           op1.setStatus( Particle::Status::Unfragmented ); op1.setMass( MX_ );
           op2.setStatus( Particle::Status::Unfragmented ); op2.setMass( MY_ );
           break;
         default: {
           throw CG_FATAL( "GenericKTProcess" )
             << "This kT factorisation process is intended for p-on-p collisions! Aborting.";
         } break;
       }
 
       //============================================================================================
       //     incoming partons (photons, pomerons, ...)
       //============================================================================================
 
       Particle& g1 = event_->getOneByRole( Particle::Parton1 );
       g1.setMomentum( event_->getOneByRole( Particle::IncomingBeam1 ).momentum()-PX_, true );
 
       Particle& g2 = event_->getOneByRole( Particle::Parton2 );
       g2.setMomentum( event_->getOneByRole( Particle::IncomingBeam2 ).momentum()-PY_, true );
 
       //============================================================================================
       //     two-parton system
       //============================================================================================
 
       event_->getOneByRole( Particle::Intermediate ).setMomentum( g1.momentum()+g2.momentum() );
     }
 
     std::ostream&
     operator<<( std::ostream& os, const GenericKTProcess::Mapping& type )
     {
       switch ( type ) {
         case GenericKTProcess::Mapping::linear: return os << "linear";
         case GenericKTProcess::Mapping::logarithmic: return os << "logarithmic";
         case GenericKTProcess::Mapping::square: return os << "squared";
       }
       return os;
     }
   }
 }
diff --git a/test/cepgen.cpp b/test/cepgen.cpp
index 25dc453..c58f1ca 100644
--- a/test/cepgen.cpp
+++ b/test/cepgen.cpp
@@ -1,66 +1,70 @@
 //--- steering cards
 #include "CepGen/Cards/Handler.h"
 
 #include "CepGen/Generator.h"
 #include "CepGen/Core/Exception.h"
 
 //--- necessary include to build the default run
 #include "CepGen/Physics/PDG.h"
 #include "CepGen/Processes/ProcessesHandler.h"
 
 #include "AbortHandler.h"
 
 #include <iostream>
 
 using namespace std;
 
 /**
  * Main caller for this MC generator.
  *  * loads the configuration files' variables if passed as an argument,
  *    or a default LPAIR-like configuration,
  *  * launches the cross-section computation and the events generation.
  * \author Laurent Forthomme <laurent.forthomme@cern.ch>
  */
 int main( int argc, char* argv[] )
 {
   //--- first start by defining the generator object
   cepgen::Generator gen;
 
   if ( argc < 2 ) {
     CG_INFO( "main" ) << "No config file provided. Setting the default parameters.";
 
     //--- default run: LPAIR elastic ɣɣ → µ⁺µ¯ at 13 TeV
     cepgen::ParametersList pgen;
     pgen.set<int>( "pair", (int)cepgen::PDG::muon );
-    gen.parameters->setProcess( cepgen::proc::ProcessesHandler::get().build( "lpair", pgen ) );
-    gen.parameters->kinematics.mode = cepgen::KinematicsMode::ElasticElastic;
-    gen.parameters->kinematics.cuts.central.pt_single.min() = 15.;
-    gen.parameters->kinematics.cuts.central.eta_single = { -2.5, 2.5 };
-    gen.parameters->generation.enabled = true;
-    gen.parameters->generation.maxgen = 1e3;
+
+    cepgen::Parameters params;
+    params.setProcess( cepgen::proc::ProcessesHandler::get().build( "lpair", pgen ) );
+    params.kinematics.mode = cepgen::KinematicsMode::ElasticElastic;
+    params.kinematics.cuts.central.pt_single.min() = 15.;
+    params.kinematics.cuts.central.eta_single = { -2.5, 2.5 };
+    params.generation.enabled = true;
+    params.generation.maxgen = 1e3;
+    gen.setParameters( params );
   }
   else
     gen.setParameters( cepgen::card::Handler::parse( argv[1] ) );
 
+
   //--- list all parameters
-  CG_LOG( "main" ) << gen.parameters.get();
+  CG_LOG( "main" ) << gen.parameters();
 
   cepgen::utils::AbortHandler ctrl_c;
 
   try {
     //--- let there be a cross-section...
     double xsec = 0., err = 0.;
     gen.computeXsection( xsec, err );
 
-    if ( gen.parameters->generation.enabled )
+    if ( gen.parameters().generation.enabled )
       //--- events generation starts here
       // (one may use a callback function)
       gen.generate();
   } catch ( const cepgen::utils::RunAbortedException& e ) {
     CG_INFO( "main" ) << "Run aborted!";
   } catch ( const cepgen::Exception& e ) {
     e.dump();
   }
 
   return 0;
 }