diff --git a/CMakeLists.txt b/CMakeLists.txt
index 44591f5..80e9c6b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,55 +1,66 @@
 cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
 project(CepGen)
 set(PROJECT_VERSION 1)
 
 #----- include external paths
 
 set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/cmake)
 set(CEPGEN_EXTERNAL_CORE_REQS "")
 set(CEPGEN_EXTERNAL_CARDS_REQS "")
 set(CEPGEN_EXTERNAL_IO_REQS "")
 set(CEPGEN_EXTERNAL_HADR_REQS "")
 set(CEPGEN_EXTERNAL_STRF_REQS "")
 include(UseEnvironment)
 #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
 
 set(CEPGEN_SOURCE_DIR ${PROJECT_SOURCE_DIR}/CepGen)
 set(CEPGEN_LIBRARIES CepGenCore CepGenEvent CepGenProcesses CepGenIO CepGenExternalHadronisers CepGenCards)
 
 #----- define all individual modules to be built beforehand
 
 set(CEPGEN_MODULES Processes Event IO Hadronisers Cards)
 
 #----- enable fortran for external libraries linking
 
 enable_language(Fortran)
 
 #----- build all the intermediate objects
 
 include_directories(${PROJECT_SOURCE_DIR})
 add_subdirectory(${CEPGEN_SOURCE_DIR})
 foreach(_module ${CEPGEN_MODULES})
   add_subdirectory(${CEPGEN_SOURCE_DIR}/${_module})
 endforeach()
 
 #----- copy the input cards and other files
 
 file(GLOB_RECURSE input_cards RELATIVE ${PROJECT_SOURCE_DIR} Cards/*)
 foreach(_files ${input_cards})
   configure_file(${_files} ${_files} COPYONLY)
 endforeach()
 configure_file(${CEPGEN_SOURCE_DIR}/README README COPYONLY)
 configure_file(${PROJECT_SOURCE_DIR}/External/mstw_sf_scan_nnlo.dat ${PROJECT_BINARY_DIR}/External/mstw_sf_scan_nnlo.dat COPYONLY)
 
 #----- installation rules
 
 set(MODS "")
 foreach(_module ${CEPGEN_MODULES})
   list(APPEND MODS CepGen/${module})
 endforeach()
 install(DIRECTORY ${MODS} DESTINATION include/CepGen FILES_MATCHING PATTERN "*.h")
 
 #----- set the tests/utils directory
 
 add_subdirectory(test)
 
+#----- documentation
+
+find_package(Doxygen)
+if(DOXYGEN_FOUND)
+  set(DOXYGEN_IN ${CMAKE_SOURCE_DIR}/docs/Doxyfile.in)
+  set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
+  configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)
+  message(STATUS "Doxygen build started")
+  add_custom_target(doc_doxygen COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Generating documentation with Doxygen" VERBATIM)
+endif()
+
diff --git a/Cards/Config/Core.py b/Cards/Config/Core.py
index 10a4ab1..86a0fe1 100644
--- a/Cards/Config/Core.py
+++ b/Cards/Config/Core.py
@@ -1,173 +1,14 @@
 #!/usr/bin/env python
 
-class PrintHelper(object):
-    _indent = 0
-    _indent_size = 4
-    def indent(self):
-        self._indent += self._indent_size
-    def unindent(self):
-        self._indent -= self._indent_size
-    def indentation(self):
-        return ' '*self._indent
+'''@package cepgen
+A collection of tools for Python steering cards definition
+'''
 
-class Parameters(dict):
-    '''A raw list of steering parameters'''
-    __getattr__ = dict.get
-    __setattr__ = dict.__setitem__
-    __delattr__ = dict.__delitem__
-    def __init__(self, *args, **kwargs):
-        self.update(*args, **kwargs)
-        super(Parameters, self).__init__(*args, **kwargs)
-    def __deepcopy__(self, memo):
-        from copy import deepcopy
-        return Parameters([(deepcopy(k, memo), deepcopy(v, memo)) for k, v in self.items()])
-    def dump(self, printer=PrintHelper()):
-        out = self.__class__.__name__+'(\n'
-        for k, v in self.items():
-            printer.indent()
-            out += ('%s%s = ' % (printer.indentation(), k))
-            if v.__class__.__name__ not in ['Parameters', 'Module']:
-                out += v.__repr__()
-            else:
-                out += v.dump(printer)
-            out += ',\n'
-            printer.unindent()
-        out += printer.indentation()+')'
-        return out
-    def __repr__(self):
-        return self.dump()
-    def clone(self, *args, **kwargs):
-        from copy import deepcopy
-        out = deepcopy(self)
-        for k in kwargs:
-            out[k] = kwargs.get(k)
-        return type(self)(out)
-    def load(self, mod):
-        mod = mod.replace('/', '.')
-        module = __import__(mod)
-        self.extend(sys.modules[mod])
+#--- core components includes
+from containers_cfi import Module, Parameters
+from logger_cfi import Logging
 
-class Module(Parameters):
-    '''A named parameters set to steer a generic module'''
-    def __init__(self, name, *args, **kwargs):
-        super(Module, self).__init__(*args, **kwargs)
-        self.mod_name = name
-    def __len__(self):
-        return dict.__len__(self)-1 # discard the name key
-    def dump(self, printer=PrintHelper()):
-        out = self.__class__.__name__+'('+self.mod_name.__repr__()+'\n'
-        mod_repr = self.clone('')
-        mod_repr.pop('mod_name', None)
-        for k, v in mod_repr.items():
-            printer.indent()
-            out += ('%s%s = ' % (printer.indentation(), k))
-            if v.__class__.__name__ not in ['Parameters', 'Module']:
-                out += v.__repr__()
-            else:
-                out += v.dump(printer)
-            out += ',\n'
-            printer.unindent()
-        out += printer.indentation()+')'
-        return out
-    def __repr__(self):
-        return self.dump()
-    def clone(self, name, **kwargs):
-        out = Parameters(self).clone(**kwargs)
-        out.mod_name = name
-        return out
-
-class Logging:
-    '''Logging verbosity'''
-    Nothing         = 0
-    Error           = 1
-    Warning         = 2
-    Information     = 3
-    Debug           = 4
-    DebugInsideLoop = 5
-
-class StructureFunctions:
-    class PDFMode:
-        AllQuarks     = 0
-        ValenceQuarks = 1
-        SeaQuarks     = 2
-    '''Types of structure functions supported'''
-    Electron            = Parameters(id=1)
-    ElasticProton       = Parameters(id=2)
-    SuriYennie          = Parameters(id=11)
-    SzczurekUleshchenko = Parameters(id=12)
-    BlockDurandHa       = Parameters(id=13)
-    FioreBrasse         = Parameters(id=101)
-    ChristyBosted       = Parameters(id=102)
-    CLAS                = Parameters(id=103)
-    ALLM91              = Parameters(id=201)
-    ALLM97              = Parameters(id=202)
-    GD07p               = Parameters(id=203)
-    GD11p               = Parameters(id=204)
-    MSTWgrid = Parameters(
-        id = 205,
-        gridPath = 'External/F2_Luxlike_fit/mstw_f2_scan_nnlo.dat',
-    )
-    LUXlike = Parameters(
-        id = 301,
-        #Q2cut = 10.,
-        #W2limits = (4.,1.),
-        continuumSF = GD11p,
-        resonancesSF = ChristyBosted,
-    )
-    LHAPDF = Parameters(
-        id = 401,
-        pdfSet = 'LUXqed17_plus_PDF4LHC15_nnlo_100',
-        numFlavours = 4,
-        mode = PDFMode.AllQuarks,
-    )
-
-class ProcessMode:
-    '''Types of processes supported'''
-    ElectronProton = 0
-    ElasticElastic = 1
-    ElasticInelastic = 2
-    InelasticElastic = 3
-    InelasticInelastic = 4
-    ProtonElectron = 5
-    ElectronElectron = 6
-
-if __name__ == '__main__':
-    import unittest
-    class TestTypes(unittest.TestCase):
-        def testModules(self):
-            mod = Module('empty')
-            self.assertEqual(len(mod), 0)
-            mod.param1 = 'foo'
-            self.assertEqual(len(mod), 1)
-            # playing with modules clones
-            mod_copy = mod.clone('notEmpty', param1 = 'boo', param2 = 'bar')
-            self.assertEqual(mod.param1, 'foo')
-            self.assertEqual(mod_copy.param1, 'boo')
-            self.assertEqual(mod_copy.param2, 'bar')
-            self.assertEqual(mod.param1+mod_copy.param2, 'foobar')
-        def testParameters(self):
-            params = Parameters(
-                first = 'foo',
-                second = 'bar',
-                third = 42,
-                fourth = (1, 2),
-            )
-            params_copy = params.clone(
-                second = 'bak',
-            )
-            self.assertEqual(len(params), 4)
-            self.assertEqual(params.first, params['first'])
-            self.assertEqual(params['second'], 'bar')
-            self.assertTrue(int(params.third) == params.third)
-            self.assertEqual(len(params.fourth), 2)
-            self.assertEqual(params.second, 'bar')
-            # playing with parameters clones
-            self.assertEqual(params_copy.second, 'bak')
-            # check that the clone does not change value if the origin does
-            # (i.e. we indeed have a deep copy and not a shallow one...)
-            params.third = 43
-            self.assertEqual(params.third, 43)
-            self.assertEqual(params_copy.third, 42)
-
-    unittest.main()
+#--- physics-level includes
+from StructureFunctions_cfi import StructureFunctions
+from ProcessMode_cfi import ProcessMode
 
diff --git a/Cards/Config/gsl_cff.py b/Cards/Config/Gsl_cfi.py
similarity index 83%
rename from Cards/Config/gsl_cff.py
rename to Cards/Config/Gsl_cfi.py
index 6366a11..fc422a5 100644
--- a/Cards/Config/gsl_cff.py
+++ b/Cards/Config/Gsl_cfi.py
@@ -1,8 +1,6 @@
-#import Config.Core as cepgen
-
 class GslRngEngine:
     '''GSL random number generator engine'''
     MT19937         = 0
     Taus            = 1
     GFSR4           = 2
     RanLXS0         = 3
diff --git a/Cards/Config/pdg_cff.py b/Cards/Config/PDG_cfi.py
similarity index 88%
rename from Cards/Config/pdg_cff.py
rename to Cards/Config/PDG_cfi.py
index 3328dd0..dbcabff 100644
--- a/Cards/Config/pdg_cff.py
+++ b/Cards/Config/PDG_cfi.py
@@ -1,16 +1,17 @@
 class PDG:
+    '''Named list of PDG identifiers'''
     down      = 1
     up        = 2
     strange   = 3
     charm     = 4
     bottom    = 5
     top       = 6
     electron  = 11
     muon      = 13
     tau       = 15
     gluon     = 21
     photon    = 22
     Z         = 23
     W         = 24
     proton    = 2212
     neutron   = 2112
diff --git a/Cards/Config/ProcessMode_cfi.py b/Cards/Config/ProcessMode_cfi.py
new file mode 100644
index 0000000..093fc9e
--- /dev/null
+++ b/Cards/Config/ProcessMode_cfi.py
@@ -0,0 +1,10 @@
+class ProcessMode:
+    '''Types of processes supported'''
+    ElectronProton = 0
+    ElasticElastic = 1
+    ElasticInelastic = 2
+    InelasticElastic = 3
+    InelasticInelastic = 4
+    ProtonElectron = 5
+    ElectronElectron = 6
+
diff --git a/Cards/Config/StructureFunctions_cfi.py b/Cards/Config/StructureFunctions_cfi.py
new file mode 100644
index 0000000..ce93fb8
--- /dev/null
+++ b/Cards/Config/StructureFunctions_cfi.py
@@ -0,0 +1,38 @@
+from containers_cfi import Parameters
+
+class StructureFunctions:
+    class PDFMode:
+        AllQuarks     = 0
+        ValenceQuarks = 1
+        SeaQuarks     = 2
+    '''Types of structure functions supported'''
+    Electron            = Parameters(id=1)
+    ElasticProton       = Parameters(id=2)
+    SuriYennie          = Parameters(id=11)
+    SzczurekUleshchenko = Parameters(id=12)
+    BlockDurandHa       = Parameters(id=13)
+    FioreBrasse         = Parameters(id=101)
+    ChristyBosted       = Parameters(id=102)
+    CLAS                = Parameters(id=103)
+    ALLM91              = Parameters(id=201)
+    ALLM97              = Parameters(id=202)
+    GD07p               = Parameters(id=203)
+    GD11p               = Parameters(id=204)
+    MSTWgrid = Parameters(
+        id = 205,
+        gridPath = 'External/F2_Luxlike_fit/mstw_f2_scan_nnlo.dat',
+    )
+    LUXlike = Parameters(
+        id = 301,
+        #Q2cut = 10.,
+        #W2limits = (4.,1.),
+        continuumSF = GD11p,
+        resonancesSF = ChristyBosted,
+    )
+    LHAPDF = Parameters(
+        id = 401,
+        pdfSet = 'LUXqed17_plus_PDF4LHC15_nnlo_100',
+        numFlavours = 4,
+        mode = PDFMode.AllQuarks,
+    )
+
diff --git a/Cards/Config/Core.py b/Cards/Config/containers_cfi.py
similarity index 72%
copy from Cards/Config/Core.py
copy to Cards/Config/containers_cfi.py
index 10a4ab1..c4a377e 100644
--- a/Cards/Config/Core.py
+++ b/Cards/Config/containers_cfi.py
@@ -1,173 +1,134 @@
-#!/usr/bin/env python
-
 class PrintHelper(object):
+    '''Helper class for the pretty-printing of configuration parameters'''
     _indent = 0
     _indent_size = 4
     def indent(self):
+        '''Move to the next indentation block'''
         self._indent += self._indent_size
     def unindent(self):
+        '''Go up to the previous indentation block'''
         self._indent -= self._indent_size
     def indentation(self):
+        '''Current indentation level'''
         return ' '*self._indent
 
 class Parameters(dict):
     '''A raw list of steering parameters'''
     __getattr__ = dict.get
     __setattr__ = dict.__setitem__
     __delattr__ = dict.__delitem__
     def __init__(self, *args, **kwargs):
+        '''Construct from dictionary arguments'''
         self.update(*args, **kwargs)
         super(Parameters, self).__init__(*args, **kwargs)
     def __deepcopy__(self, memo):
+        '''Override the default dict deep copy operator'''
         from copy import deepcopy
         return Parameters([(deepcopy(k, memo), deepcopy(v, memo)) for k, v in self.items()])
     def dump(self, printer=PrintHelper()):
+        '''Human-readable dump of this object'''
         out = self.__class__.__name__+'(\n'
         for k, v in self.items():
             printer.indent()
             out += ('%s%s = ' % (printer.indentation(), k))
             if v.__class__.__name__ not in ['Parameters', 'Module']:
                 out += v.__repr__()
             else:
                 out += v.dump(printer)
             out += ',\n'
             printer.unindent()
         out += printer.indentation()+')'
         return out
     def __repr__(self):
+        '''Human-readable version of this object'''
         return self.dump()
     def clone(self, *args, **kwargs):
+        '''Return a deep copy of this object'''
         from copy import deepcopy
         out = deepcopy(self)
         for k in kwargs:
             out[k] = kwargs.get(k)
         return type(self)(out)
     def load(self, mod):
+        '''Extend this object by an include'''
         mod = mod.replace('/', '.')
         module = __import__(mod)
         self.extend(sys.modules[mod])
 
 class Module(Parameters):
     '''A named parameters set to steer a generic module'''
     def __init__(self, name, *args, **kwargs):
+        '''Construct from dictionary arguments'''
         super(Module, self).__init__(*args, **kwargs)
         self.mod_name = name
     def __len__(self):
+        '''Number of keys handled'''
         return dict.__len__(self)-1 # discard the name key
     def dump(self, printer=PrintHelper()):
+        '''Human-readable dump of this object'''
         out = self.__class__.__name__+'('+self.mod_name.__repr__()+'\n'
         mod_repr = self.clone('')
         mod_repr.pop('mod_name', None)
         for k, v in mod_repr.items():
             printer.indent()
             out += ('%s%s = ' % (printer.indentation(), k))
             if v.__class__.__name__ not in ['Parameters', 'Module']:
                 out += v.__repr__()
             else:
                 out += v.dump(printer)
             out += ',\n'
             printer.unindent()
         out += printer.indentation()+')'
         return out
     def __repr__(self):
+        '''Human-readable version of this object'''
         return self.dump()
     def clone(self, name, **kwargs):
+        '''Return a deep copy of this object'''
         out = Parameters(self).clone(**kwargs)
         out.mod_name = name
         return out
 
-class Logging:
-    '''Logging verbosity'''
-    Nothing         = 0
-    Error           = 1
-    Warning         = 2
-    Information     = 3
-    Debug           = 4
-    DebugInsideLoop = 5
-
-class StructureFunctions:
-    class PDFMode:
-        AllQuarks     = 0
-        ValenceQuarks = 1
-        SeaQuarks     = 2
-    '''Types of structure functions supported'''
-    Electron            = Parameters(id=1)
-    ElasticProton       = Parameters(id=2)
-    SuriYennie          = Parameters(id=11)
-    SzczurekUleshchenko = Parameters(id=12)
-    BlockDurandHa       = Parameters(id=13)
-    FioreBrasse         = Parameters(id=101)
-    ChristyBosted       = Parameters(id=102)
-    CLAS                = Parameters(id=103)
-    ALLM91              = Parameters(id=201)
-    ALLM97              = Parameters(id=202)
-    GD07p               = Parameters(id=203)
-    GD11p               = Parameters(id=204)
-    MSTWgrid = Parameters(
-        id = 205,
-        gridPath = 'External/F2_Luxlike_fit/mstw_f2_scan_nnlo.dat',
-    )
-    LUXlike = Parameters(
-        id = 301,
-        #Q2cut = 10.,
-        #W2limits = (4.,1.),
-        continuumSF = GD11p,
-        resonancesSF = ChristyBosted,
-    )
-    LHAPDF = Parameters(
-        id = 401,
-        pdfSet = 'LUXqed17_plus_PDF4LHC15_nnlo_100',
-        numFlavours = 4,
-        mode = PDFMode.AllQuarks,
-    )
-
-class ProcessMode:
-    '''Types of processes supported'''
-    ElectronProton = 0
-    ElasticElastic = 1
-    ElasticInelastic = 2
-    InelasticElastic = 3
-    InelasticInelastic = 4
-    ProtonElectron = 5
-    ElectronElectron = 6
-
 if __name__ == '__main__':
     import unittest
     class TestTypes(unittest.TestCase):
+        '''A small collection of tests for our new types'''
         def testModules(self):
+            '''Test the Module object'''
             mod = Module('empty')
             self.assertEqual(len(mod), 0)
             mod.param1 = 'foo'
             self.assertEqual(len(mod), 1)
             # playing with modules clones
             mod_copy = mod.clone('notEmpty', param1 = 'boo', param2 = 'bar')
             self.assertEqual(mod.param1, 'foo')
             self.assertEqual(mod_copy.param1, 'boo')
             self.assertEqual(mod_copy.param2, 'bar')
             self.assertEqual(mod.param1+mod_copy.param2, 'foobar')
         def testParameters(self):
+            '''Test the Parameters object'''
             params = Parameters(
                 first = 'foo',
                 second = 'bar',
                 third = 42,
                 fourth = (1, 2),
             )
             params_copy = params.clone(
                 second = 'bak',
             )
             self.assertEqual(len(params), 4)
             self.assertEqual(params.first, params['first'])
             self.assertEqual(params['second'], 'bar')
             self.assertTrue(int(params.third) == params.third)
             self.assertEqual(len(params.fourth), 2)
             self.assertEqual(params.second, 'bar')
             # playing with parameters clones
             self.assertEqual(params_copy.second, 'bak')
             # check that the clone does not change value if the origin does
             # (i.e. we indeed have a deep copy and not a shallow one...)
             params.third = 43
             self.assertEqual(params.third, 43)
             self.assertEqual(params_copy.third, 42)
 
     unittest.main()
 
diff --git a/Cards/Config/integrators_cff.py b/Cards/Config/integrators_cff.py
index d007d66..79c7a24 100644
--- a/Cards/Config/integrators_cff.py
+++ b/Cards/Config/integrators_cff.py
@@ -1,30 +1,30 @@
-import Config.Core as cepgen
-from Config.gsl_cff import GslRngEngine
+from containers_cfi import Module
+from Gsl_cfi import GslRngEngine
 
-plain = cepgen.Module('plain',
+plain = Module('plain',
     numFunctionCalls = 1000000,
     rngEngine = GslRngEngine.MT19937,
 )
 
 class VegasIntegrationMode:
     Stratified = -1
     ImportanceOnly = 0
     Importance = 1
 
 vegas = plain.clone('Vegas',
     numFunctionCalls = 50000,
     chiSqCut = 1.5,
     # VEGAS-specific parameters
     iterations = 10,
     alpha = 1.5,
     mode = VegasIntegrationMode.Importance,
     verbosity = -1,
     loggingOutput = 'cerr',
 )
 
 miser = plain.clone('MISER',
     # MISER-specific parameters
     estimateFraction = 0.1,
     alpha = 2.,
     dither = 0.,
 )
diff --git a/Cards/Config/ktProcess_cfi.py b/Cards/Config/ktProcess_cfi.py
index fd40eb0..8f84bcd 100644
--- a/Cards/Config/ktProcess_cfi.py
+++ b/Cards/Config/ktProcess_cfi.py
@@ -1,22 +1,24 @@
 import Config.Core as cepgen
 from math import pi
 
 class ProtonFlux:
+    '''Type of parton (from proton) flux modelling'''
     PhotonElastic         = 0
     PhotonInelastic       = 1
     PhotonInelasticBudnev = 11
     GluonKMR              = 20
 class HeavyIonFlux:
+    '''Type of parton (from heavy ion) flux modelling'''
     PhotonElastic         = 100
 
 process = cepgen.Module('ktProcess',
     outKinematics = cepgen.Parameters(
         qt = (0., 50.),
         phiqt = (0., 2.*pi),
         #--- cuts on individual particles defining the central system
         rapidity = (-6., 6.),
         #--- cuts on the pt(outgoing system) (hyper-)plane
         ptdiff = (0., 500.),
         phiptdiff = (0., 2.*pi),
     ),
 )
diff --git a/Cards/Config/logger_cfi.py b/Cards/Config/logger_cfi.py
index bedc59f..447ba0d 100644
--- a/Cards/Config/logger_cfi.py
+++ b/Cards/Config/logger_cfi.py
@@ -1,6 +1,15 @@
-import Config.Core as cepgen
+from containers_cfi import Parameters
 
-logger = cepgen.Parameters(
-    level = cepgen.Logging.Information,
+class Logging:
+    '''Logging verbosity'''
+    Nothing         = 0
+    Error           = 1
+    Warning         = 2
+    Information     = 3
+    Debug           = 4
+    DebugInsideLoop = 5
+
+logger = Parameters(
+    level = Logging.Information,
     enabledModules = (),
 )
diff --git a/Cards/Config/pythia8_cff.py b/Cards/Config/pythia8_cff.py
index 24a26b5..ca361da 100644
--- a/Cards/Config/pythia8_cff.py
+++ b/Cards/Config/pythia8_cff.py
@@ -1,33 +1,37 @@
-import Config.Core as cepgen
+from containers_cfi import Module, Parameters
 
-pythia8 = cepgen.Module('pythia8',
-    seed = 1000,
-    maxTrials = 1,
-    pythiaPreConfiguration = (
+pythia8 = Module('pythia8',
+    moduleParameters = Parameters(
+        seed = 1000,
+        maxTrials = 1,
+    ),
+    preConfiguration = (
         # printout properties
         # start by disabling some unnecessary output
         'Next:numberCount = 0',
         # parameterise the fragmentation part
         #'PartonLevel:Remnants = off',
         # disable all Bremsstrahlung/FSR photon production
         'PartonLevel:ISR = off',
         'PartonLevel:FSR = off',
         'PartonLevel:MPI = off',
         'BeamRemnants:primordialKT = off',
     ),
-    pythiaConfiguration = (
+    pythiaDefaults = (
         'ParticleDecays:allowPhotonRadiation = off',
         'Tune:preferLHAPDF = 2',
         #'Beams:setProductionScalesFromLHEF = off',
         'SLHA:keepSM = on',
         'SLHA:minMassSM = 1000.',
         'ParticleDecays:limitTau0 = on',
         'ParticleDecays:tau0Max = 10',
         # CUEP8M1 tuning
         'Tune:pp 14', # Monash 2013 tune by Peter Skands (January 2014)
         'MultipartonInteractions:pT0Ref = 2.4024',
         'MultipartonInteractions:ecmPow = 0.25208',
         'MultipartonInteractions:expPow = 1.6',
     ),
-    pythiaProcessConfiguration = (),
+    processConfiguration = (
+        'pythiaDefaults',
+    ),
 )
diff --git a/Cards/patoccbar_cfg.py b/Cards/patoccbar_cfg.py
index c3d6834..c10a708 100644
--- a/Cards/patoccbar_cfg.py
+++ b/Cards/patoccbar_cfg.py
@@ -1,40 +1,40 @@
 import Config.Core as cepgen
-import Config.ktProcess_cfi as ktfactor
+import Config.ktProcess_cfi as kt
 from Config.integrators_cff import vegas as integrator
-from Config.pdg_cff import PDG
+from Config.PDG_cfi import PDG
 
 from Config.logger_cfi import logger
 logger.enabledModules += ('GenericKTProcess.registerVariable',)
 
-process = ktfactor.process.clone('patoll',
+process = kt.process.clone('patoff',
     processParameters = cepgen.Parameters(
         pair = PDG.charm,
     ),
     inKinematics = cepgen.Parameters(
         pz = (6500., 2562.2),
         structureFunctions = cepgen.StructureFunctions.SuriYennie,
         #structureFunctions = cepgen.StructureFunctions.FioreBrasse,
-        ktFluxes = (ktfactor.ProtonFlux.GluonKMR, ktfactor.HeavyIonFlux.PhotonElastic),
-        #ktFluxes = (ktfactor.ProtonFlux.PhotonElastic, ktfactor.HeavyIonFlux.PhotonElastic),
+        ktFluxes = (kt.ProtonFlux.GluonKMR, kt.HeavyIonFlux.PhotonElastic),
+        #ktFluxes = (kt.ProtonFlux.PhotonElastic, kt.HeavyIonFlux.PhotonElastic),
         heavyIonB = (208, 82),
         kmrGridPath = 'gluon_mmht2014nlo_Watt.dat',
     ),
-    outKinematics = ktfactor.process.outKinematics.clone(
+    outKinematics = kt.process.outKinematics.clone(
         pt = (0.,),
         energy = (0.,),
         rapidity = (-7., 9.),
         #qt = (0.,1000.),
         #eta = (-2.5, 2.5),
         mx = (1.07, 1000.),
         #--- extra cuts on the p1t(l) and p2t(l) plane
         #ptdiff = (0., 2.5),
         #--- distance in rapidity between l^+ and l^-
         #dely = (4., 5.),
     ),
 )
 
 #--- events generation
 from Config.generator_cff import generator
 generator.numEvents = 10000
 generator.numThreads = 1
 
diff --git a/Cards/patoll_cfg.py b/Cards/patoll_cfg.py
index 25b9830..cc33bb7 100644
--- a/Cards/patoll_cfg.py
+++ b/Cards/patoll_cfg.py
@@ -1,35 +1,35 @@
 import Config.Core as cepgen
-import Config.ktProcess_cfi as ktfactor
+import Config.ktProcess_cfi as kt
 from Config.integrators_cff import vegas as integrator
-from Config.pdg_cff import PDG
+from Config.PDG_cfi import PDG
 
-process = ktfactor.process.clone('patoll',
+process = kt.process.clone('patoff',
     processParameters = cepgen.Parameters(
         pair = PDG.muon,
     ),
     inKinematics = cepgen.Parameters(
         pz = (6500., 2562.2),
         #structureFunctions = cepgen.StructureFunctions.SuriYennie,
         #structureFunctions = cepgen.StructureFunctions.FioreBrasse,
-        #structureFunctions = cepgen.StructureFunctions.ALLM91,
+        #structureFunctions = cepgen.StructureFunctions.ALLM97,
         structureFunctions = cepgen.StructureFunctions.LUXlike,
-        ktFluxes = (ktfactor.ProtonFlux.PhotonInelasticBudnev, ktfactor.HeavyIonFlux.PhotonElastic),
+        ktFluxes = (kt.ProtonFlux.PhotonInelasticBudnev, kt.HeavyIonFlux.PhotonElastic),
         heavyIonB = (208, 82),
     ),
-    outKinematics = ktfactor.process.outKinematics.clone(
+    outKinematics = kt.process.outKinematics.clone(
         pt = (4.,),
         energy = (0.,),
         rapidity = (-6., 7.),
         #eta = (-2.5, 2.5),
         mx = (1.07, 1000.),
         #--- extra cuts on the p1t(l) and p2t(l) plane
         #ptdiff = (0., 2.5),
         #--- distance in rapidity between l^+ and l^-
         #dely = (4., 5.),
     ),
 )
 
 #--- events generation
 from Config.generator_cff import generator
 generator.numEvents = 100000
 
diff --git a/Cards/pptoll_cfg.py b/Cards/pptoll_cfg.py
index 998059e..2bf1f9a 100644
--- a/Cards/pptoll_cfg.py
+++ b/Cards/pptoll_cfg.py
@@ -1,31 +1,31 @@
 import Config.Core as cepgen
-import Config.ktProcess_cfi as ktfactor
+import Config.ktProcess_cfi as kt
 from Config.integrators_cff import miser as integrator
-#from Config.pythia8_cff import pythia8 as hadroniser
-from Config.pdg_cff import PDG
+from Config.pythia8_cff import pythia8 as hadroniser
+from Config.PDG_cfi import PDG
 
-process = ktfactor.process.clone('pptoll',
+process = kt.process.clone('pptoll',
     processParameters = cepgen.Parameters(
         mode = cepgen.ProcessMode.ElasticInelastic,
         pair = PDG.muon,
     ),
     inKinematics = cepgen.Parameters(
         pz = (6500., 6500.),
         structureFunctions = cepgen.StructureFunctions.SuriYennie,
         #structureFunctions = cepgen.StructureFunctions.FioreBrasse,
     ),
-    outKinematics = ktfactor.process.outKinematics.clone(
+    outKinematics = kt.process.outKinematics.clone(
         pt = (25.,),
         energy = (0.,),
         eta = (-2.5, 2.5),
         mx = (1.07, 1000.),
         #--- extra cuts on the p1t(l) and p2t(l) plane
         #ptdiff = (0., 2.5),
         #--- distance in rapidity between l^+ and l^-
         #dely = (4., 5.),
     ),
 )
 
 #--- events generation
 from Config.generator_cff import generator
 generator.numEvents = 10000
diff --git a/Cards/pptottbar_cfg.py b/Cards/pptottbar_cfg.py
index 3da665e..6bc823c 100644
--- a/Cards/pptottbar_cfg.py
+++ b/Cards/pptottbar_cfg.py
@@ -1,34 +1,36 @@
 import Config.Core as cepgen
-import Config.ktProcess_cfi as ktfactor
+import Config.ktProcess_cfi as kt
 from Config.integrators_cff import vegas as integrator
 #from Config.pythia8_cff import pythia8 as hadroniser
+from Config.PDG_cfi import PDG
 
 from Config.logger_cfi import logger
 logger.enabledModules += ('PPtoFF.prepare',)
 
-process = ktfactor.process.clone('pptoff',
+process = kt.process.clone('pptoff',
     processParameters = cepgen.Parameters(
         mode = cepgen.ProcessMode.ElasticElastic,
+        pair = PDG.top,
     ),
     inKinematics = cepgen.Parameters(
         pz = (6500., 6500.),
         #structureFunctions = cepgen.StructureFunctions.SuriYennie,
         structureFunctions = cepgen.StructureFunctions.LUXlike,
         #structureFunctions = cepgen.StructureFunctions.FioreBrasse,
     ),
-    outKinematics = ktfactor.process.outKinematics.clone(
+    outKinematics = kt.process.outKinematics.clone(
         pair = 6,
         #eta = (-2.5, 2.5),
         mx = (1.07, 1000.),
         #--- extra cuts on the p1t(t) and p2t(t) plane
         ptdiff = (0., 2000.),
         #--- distance in rapidity between l^+ and l^-
         #dely = (4., 5.),
     ),
 )
 
 #--- events generation
 from Config.generator_cff import generator
 generator.numEvents = 25000
 #generator.treat = True
 
diff --git a/Cards/pptoww_cfg.py b/Cards/pptoww_cfg.py
index 80b5025..cfd5c38 100644
--- a/Cards/pptoww_cfg.py
+++ b/Cards/pptoww_cfg.py
@@ -1,67 +1,64 @@
 import Config.Core as cepgen
-import Config.ktProcess_cfi as ktfactor
 from Config.integrators_cff import vegas as integrator
 from Config.logger_cfi import logger
-from Config.pythia8_cff import pythia8 as hadroniser
-from Config.generator_cff import generator
 
-hadroniser.pythiaProcessConfiguration += (
-    # process-specific
-    '13:onMode = off', # disable muon decays
-    '24:onMode = off', # disable all W decays, but...
-    #'24:onIfAny = 11 13', # enable e-nue + mu-numu final states
-    '24:onPosIfAny = 11', # enable W- -> e- + nu_e decay
-    '24:onNegIfAny = 13', # enable W+ -> mu+ + nu_mu decay
-)
-hadroniser.pythiaPreConfiguration += (
-    #'PartonLevel:MPI = on',
-    #'PartonLevel:ISR = on',
-    #'PartonLevel:FSR = on',
-    'ProcessLevel:resonanceDecays = off', # disable the W decays
+from Config.pythia8_cff import pythia8
+hadroniser = pythia8.clone('pythia8',
+    preConfiguration = pythia8.preConfiguration+(
+        #'PartonLevel:MPI = on',
+        #'PartonLevel:ISR = on',
+        #'PartonLevel:FSR = on',
+        'ProcessLevel:resonanceDecays = off', # disable the W decays
+    ),
+    pythiaConfiguration = (
+        # process-specific
+        '13:onMode = off', # disable muon decays
+        '24:onMode = off', # disable all W decays, but...
+        #'24:onIfAny = 11 13', # enable e-nue + mu-numu final states
+        '24:onPosIfAny = 11', # enable W- -> e- + nu_e decay
+        '24:onNegIfAny = 13', # enable W+ -> mu+ + nu_mu decay
+    ),
+    processConfiguration = pythia8.processConfiguration+('pythiaConfiguration',),
 )
+#from Config.logger_cfi import logger
+#logger.enabledModules += ('Hadroniser.configure',)
 
-process = ktfactor.process.clone('pptoww',
+import Config.ktProcess_cfi as kt
+process = kt.process.clone('pptoww',
     processParameters = cepgen.Parameters(
         mode = cepgen.ProcessMode.ElasticElastic,
         polarisationStates = 0, # full
     ),
     inKinematics = cepgen.Parameters(
         cmEnergy = 13.e3,
         #structureFunctions = cepgen.StructureFunctions.SzczurekUleshchenko,
         #structureFunctions = cepgen.StructureFunctions.ALLM97,
         structureFunctions = cepgen.StructureFunctions.LUXlike,
     ),
-    outKinematics = ktfactor.process.outKinematics.clone(
+    outKinematics = kt.process.outKinematics.clone(
         mx = (1.07, 1000.),
         qt = (0., 1000.),
         #--- extra cuts on the pt(W+) and pt(W-) plane
         ptdiff = (0., 2000.),
         #--- extra cuts on the W+W- system
         invmass = (0.,),
         ptsum = (0.,),
         #--- cuts on single particles' level
         cuts = {
             # cuts on the single W level
-            24: cepgen.Parameters(
-                pt = (0.,), # no pt cut on Ws
-            ),
+            24: cepgen.Parameters(pt = (0.,)), # no pt cut on Ws
             # cuts on the W decay products
             # (mimicking LHC-like experimental cuts)
-            11: cepgen.Parameters(
-                pt = (20.,),
-                eta = (-2.5, 2.5),
-            ),
-            13: cepgen.Parameters(
-                pt = (20.,),
-                eta = (-2.5, 2.5),
-            )
+            11: cepgen.Parameters(pt = (20.,), eta = (-2.5, 2.5)),
+            13: cepgen.Parameters(pt = (20.,), eta = (-2.5, 2.5))
         },
     )
 )
 
-#--- import the default generation parameters
+#--- generation parameters
+from Config.generator_cff import generator
 generator = generator.clone(
     numEvents = 1000,
     printEvery = 100,
     treat = True, # smoothing of the integrand
 )
diff --git a/CepGen/Cards/LpairHandler.cpp b/CepGen/Cards/LpairHandler.cpp
index cfb0b7c..1f4121a 100644
--- a/CepGen/Cards/LpairHandler.cpp
+++ b/CepGen/Cards/LpairHandler.cpp
@@ -1,205 +1,233 @@
 #include "CepGen/Cards/LpairHandler.h"
 #include "CepGen/Core/ParametersList.h"
 #include "CepGen/Core/Exception.h"
 
 #include "CepGen/Physics/PDG.h"
+#include "CepGen/StructureFunctions/StructureFunctionsBuilder.h"
+#include "CepGen/StructureFunctions/LHAPDF.h"
+
 #include "CepGen/Processes/GamGamLL.h"
 #include "CepGen/Processes/PPtoFF.h"
 #include "CepGen/Processes/PPtoWW.h"
+#include "CepGen/Processes/FortranProcesses.h"
 
 #include "CepGen/Hadronisers/Pythia8Hadroniser.h"
 
 #include <fstream>
 
 namespace CepGen
 {
   namespace Cards
   {
     const int LpairHandler::kInvalid = 99999;
 
     //----- specialization for LPAIR input cards
 
     LpairHandler::LpairHandler( const char* file ) :
       proc_params_( new ParametersList ),
-      hi_1_( { 0, 0 } ), hi_2_( { 0, 0 } )
+      str_fun_( 11 ), hi_1_( { 0, 0 } ), hi_2_( { 0, 0 } )
     {
       std::ifstream f( file, std::fstream::in );
       if ( !f.is_open() )
         throw CG_FATAL( "LpairHandler" ) << "Failed to parse file \"" << file << "%s\".";
 
       init( &params_ );
 
+      //--- parse all fields
       std::unordered_map<std::string, std::string> m_params;
       std::string key, value;
       std::ostringstream os;
       while ( f >> key >> value ) {
-        if ( key[0] == '#' ) continue; // FIXME need to ensure there is no extra space before!
+        if ( key[0] == '#' ) // FIXME need to ensure there is no extra space before!
+          continue;
         setParameter( key, value );
         m_params.insert( { key, value } );
         if ( getDescription( key ) != "null" )
           os << "\n>> " << key << " = " << std::setw( 15 ) << getParameter( key )
              << " (" << getDescription( key ) << ")";
       }
       f.close();
 
+      //--- parse the process name
       if ( proc_name_ == "lpair" )
         params_.setProcess( new Process::GamGamLL( *proc_params_ ) );
       else if ( proc_name_ == "pptoll" || proc_name_ == "pptoff" )
         params_.setProcess( new Process::PPtoFF( *proc_params_ ) );
       else if ( proc_name_ == "pptoww" )
         params_.setProcess( new Process::PPtoWW( *proc_params_ ) );
+      else {
+        Process::generateFortranProcesses();
+        for ( auto& proc : Process::FortranProcessesHandler::get().list() )
+          if ( proc_name_ == std::string( proc.name ) )
+            params_.setProcess( new Process::FortranKTProcess( *proc_params_, proc.name, proc.description, proc.method ) );
+        if ( !params_.process() )
+          throw CG_FATAL( "LpairHandler" ) << "Unrecognised process name: " << proc_name_ << "!";
+      }
+
+      //--- parse the structure functions code
+      const unsigned long kLHAPDFCodeDec = 10000000, kLHAPDFPartDec = 1000000;
+      if ( str_fun_ / kLHAPDFCodeDec == 1 ) { // SF from parton
+        params_.kinematics.structure_functions = StructureFunctionsBuilder::get( SF::Type::LHAPDF );
+        auto sf = dynamic_cast<SF::LHAPDF*>( params_.kinematics.structure_functions.get() );
+        const unsigned long icode = str_fun_ % kLHAPDFCodeDec;
+        sf->params.pdf_code = icode % kLHAPDFPartDec;
+        sf->params.mode = (SF::LHAPDF::Parameterisation::Mode)( icode / kLHAPDFPartDec ); // 0, 1, 2
+      }
       else
-        throw CG_FATAL( "LpairHandler" ) << "Unrecognised process name: " << proc_name_ << "!";
+        params_.kinematics.structure_functions = StructureFunctionsBuilder::get( (SF::Type)str_fun_ );
 
+      //--- parse the integration algorithm name
       if ( integr_type_ == "plain" )
         params_.integrator.type = Integrator::Type::plain;
       else if ( integr_type_ == "Vegas" )
         params_.integrator.type = Integrator::Type::Vegas;
       else if ( integr_type_ == "MISER" )
         params_.integrator.type = Integrator::Type::MISER;
       else if ( integr_type_ != "" )
         throw CG_FATAL( "LpairHandler" ) << "Unrecognized integrator type: " << integr_type_ << "!";
 
+      //--- parse the hadronisation algorithm name
       if ( hadr_name_ == "pythia8" )
-        params_.setHadroniser( new Hadroniser::Pythia8Hadroniser( params_ ) );
+        params_.setHadroniser( new Hadroniser::Pythia8Hadroniser( params_, ParametersList() ) );
 
       if ( m_params.count( "IEND" ) )
         setValue<bool>( "IEND", ( std::stoi( m_params["IEND"] ) > 1 ) );
 
+      //--- check if we are dealing with heavy ions for incoming states
       HeavyIon hi1{ hi_1_.first, (Element)hi_1_.second }, hi2{ hi_2_.first, (Element)hi_2_.second };
       if ( hi1 )
         params_.kinematics.incoming_beams.first.pdg = hi1;
       if ( hi2 )
         params_.kinematics.incoming_beams.second.pdg = hi2;
 
       CG_INFO( "LpairHandler" ) << "File '" << file << "' succesfully opened!\n\t"
         << "The following parameters are set:" << os.str();
     }
 
     void
     LpairHandler::init( Parameters* params )
     {
       //-------------------------------------------------------------------------------------------
       // Process/integration/hadronisation parameters
       //-------------------------------------------------------------------------------------------
 
       registerParameter<std::string>( "PROC", "Process name to simulate", &proc_name_ );
       registerParameter<std::string>( "ITYP", "Integration algorithm", &integr_type_ );
       registerParameter<std::string>( "HADR", "Hadronisation algorithm", &hadr_name_ );
       registerParameter<std::string>( "KMRG", "KMR grid interpolation path", &params_.kinematics.kmr_grid_path );
 
       //-------------------------------------------------------------------------------------------
       // General parameters
       //-------------------------------------------------------------------------------------------
 
       registerParameter<bool>( "IEND", "Generation type", &params->generation.enabled );
       registerParameter<bool>( "NTRT", "Smoothen the integrand", &params->generation.treat );
       registerParameter<int>( "DEBG", "Debugging verbosity", (int*)&Logger::get().level );
       registerParameter<int>( "NCVG", "Number of function calls", (int*)&params->integrator.ncvg );
       registerParameter<int>( "ITVG", "Number of integration iterations", (int*)&params->integrator.vegas.iterations );
       registerParameter<int>( "SEED", "Random generator seed", (int*)&params->integrator.rng_seed );
       registerParameter<int>( "NTHR", "Number of threads to use for events generation", (int*)&params->generation.num_threads );
       registerParameter<int>( "MODE", "Subprocess' mode", (int*)&params->kinematics.mode );
       registerParameter<int>( "NCSG", "Number of points to probe", (int*)&params->generation.num_points );
       registerParameter<int>( "NGEN", "Number of events to generate", (int*)&params->generation.maxgen );
       registerParameter<int>( "NPRN", "Number of events before printout", (int*)&params->generation.gen_print_every );
 
       //-------------------------------------------------------------------------------------------
       // Process-specific parameters
       //-------------------------------------------------------------------------------------------
 
       registerParameter<int>( "METH", "Computation method (kT-factorisation)", &proc_params_->operator[]<int>( "method" ) );
       registerParameter<int>( "IPOL", "Polarisation states to consider", &proc_params_->operator[]<int>( "polarisationStates" ) );
 
       //-------------------------------------------------------------------------------------------
       // Process kinematics parameters
       //-------------------------------------------------------------------------------------------
 
-      registerParameter<int>( "PMOD", "Outgoing primary particles' mode", (int*)&params->kinematics.structure_functions );
-      registerParameter<int>( "EMOD", "Outgoing primary particles' mode", (int*)&params->kinematics.structure_functions );
+      registerParameter<int>( "PMOD", "Outgoing primary particles' mode", &str_fun_ );
+      registerParameter<int>( "EMOD", "Outgoing primary particles' mode", &str_fun_ );
       registerParameter<int>( "PAIR", "Outgoing particles' PDG id", (int*)&proc_params_->operator[]<int>( "pair" ) );
 
       registerParameter<int>( "INA1", "Heavy ion atomic weight (1st incoming beam)", (int*)&hi_1_.first );
       registerParameter<int>( "INZ1", "Heavy ion atomic number (1st incoming beam)", (int*)&hi_1_.second );
       registerParameter<int>( "INA2", "Heavy ion atomic weight (1st incoming beam)", (int*)&hi_2_.first );
       registerParameter<int>( "INZ2", "Heavy ion atomic number (1st incoming beam)", (int*)&hi_2_.second );
       registerParameter<double>( "INP1", "Momentum (1st primary particle)", &params->kinematics.incoming_beams.first.pz );
       registerParameter<double>( "INP2", "Momentum (2nd primary particle)", &params->kinematics.incoming_beams.second.pz );
       registerParameter<double>( "INPP", "Momentum (1st primary particle)", &params->kinematics.incoming_beams.first.pz );
       registerParameter<double>( "INPE", "Momentum (2nd primary particle)", &params->kinematics.incoming_beams.second.pz );
       registerParameter<double>( "PTCT", "Minimal transverse momentum (single central outgoing particle)", &params->kinematics.cuts.central.pt_single.min() );
       registerParameter<double>( "MSCT", "Minimal central system mass", &params->kinematics.cuts.central.mass_sum.min() );
       registerParameter<double>( "ECUT", "Minimal energy (single central outgoing particle)", &params->kinematics.cuts.central.energy_single.min() );
       registerParameter<double>( "ETMN", "Minimal pseudo-rapidity (central outgoing particles)", &params->kinematics.cuts.central.eta_single.min() );
       registerParameter<double>( "ETMX", "Maximal pseudo-rapidity (central outgoing particles)", &params->kinematics.cuts.central.eta_single.max() );
       registerParameter<double>( "YMIN", "Minimal rapidity (central outgoing particles)", &params->kinematics.cuts.central.rapidity_single.min() );
       registerParameter<double>( "YMAX", "Maximal rapidity (central outgoing particles)", &params->kinematics.cuts.central.rapidity_single.max() );
       registerParameter<double>( "Q2MN", "Minimal Q² = -q² (exchanged parton)", &params->kinematics.cuts.initial.q2.min() );
       registerParameter<double>( "Q2MX", "Maximal Q² = -q² (exchanged parton)", &params->kinematics.cuts.initial.q2.max() );
       registerParameter<double>( "MXMN", "Minimal invariant mass of proton remnants", &params->kinematics.cuts.remnants.mass_single.min() );
       registerParameter<double>( "MXMX", "Maximal invariant mass of proton remnants", &params->kinematics.cuts.remnants.mass_single.max() );
     }
 
     void
     LpairHandler::store( const char* file )
     {
       std::ofstream f( file, std::fstream::out | std::fstream::trunc );
       if ( !f.is_open() ) {
         CG_ERROR( "LpairHandler" ) << "Failed to open file \"" << file << "%s\" for writing.";
       }
       for ( const auto& it : p_strings_ )
         if ( it.second.value )
           f << it.first << " = " << *it.second.value << "\n";
       for ( const auto& it : p_ints_ )
         if ( it.second.value )
           f << it.first << " = " << *it.second.value << "\n";
       for ( const auto& it : p_doubles_ )
         if ( it.second.value )
           f << it.first << " = " << *it.second.value << "\n";
       for ( const auto& it : p_bools_ )
         if ( it.second.value )
           f << it.first << " = " << *it.second.value << "\n";
       f.close();
     }
 
     void
     LpairHandler::setParameter( const std::string& key, const std::string& value )
     {
       try { setValue<double>( key.c_str(), std::stod( value ) ); } catch ( std::invalid_argument& ) {}
       try { setValue<int>( key.c_str(), std::stoi( value ) ); } catch ( std::invalid_argument& ) {}
       //setValue<bool>( key.c_str(), std::stoi( value ) );
       setValue<std::string>( key.c_str(), value );
     }
 
     std::string
     LpairHandler::getParameter( std::string key ) const
     {
       double dd = getValue<double>( key.c_str() );
       if ( dd != -999. )
         return std::to_string( dd );
 
       int ui = getValue<int>( key.c_str() );
       if ( ui != 999 )
         return std::to_string( ui );
 
       //if ( out = getValue<bool>( key.c_str() )  );
 
       return getValue<std::string>( key.c_str() );
     }
 
     std::string
     LpairHandler::getDescription( std::string key ) const
     {
       if ( p_strings_.count( key ) )
         return p_strings_.find( key )->second.description;
       if ( p_ints_.count( key ) )
         return p_ints_.find( key )->second.description;
       if ( p_doubles_.count( key ) )
         return p_doubles_.find( key )->second.description;
       if ( p_bools_.count( key ) )
         return p_bools_.find( key )->second.description;
       return "null";
     }
   }
 }
 
diff --git a/CepGen/Cards/LpairHandler.h b/CepGen/Cards/LpairHandler.h
index 78cf708..6425106 100644
--- a/CepGen/Cards/LpairHandler.h
+++ b/CepGen/Cards/LpairHandler.h
@@ -1,110 +1,115 @@
 #ifndef CepGen_Cards_LpairReader_h
 #define CepGen_Cards_LpairReader_h
 
 #include "CepGen/Cards/Handler.h"
 #include <unordered_map>
 
 using std::string;
 
 namespace CepGen
 {
   class ParametersList;
   namespace Cards
   {
     /// LPAIR-like steering cards parser and writer
     class LpairHandler : public Handler
     {
       public:
         /// Read a LPAIR steering card
         explicit LpairHandler( const char* file );
 
         /// Store a configuration into a LPAIR steering card
         void store( const char* file );
 
       private:
         template<class T> struct Parameter {
           Parameter( const char* key, const char* descr, T* value ) : key( key ), description( descr ), value( value ) {}
           std::string key, description;
           T* value;
         };
         /// Register a parameter to be steered to a configuration variable
         template<class T> void registerParameter( const char* key, const char* description, T* def ) {}
         /// Set a parameter value
         template<class T> void setValue( const char* key, const T& value ) {}
         /// Retrieve a parameter value
         template<class T> T getValue( const char* key ) const {}
 
         void setParameter( const std::string& key, const std::string& value );
         std::string getParameter( std::string key ) const;
         std::string getDescription( std::string key ) const;
 
         static const int kInvalid;
 
         std::unordered_map<std::string, Parameter<std::string> > p_strings_;
         std::unordered_map<std::string, Parameter<double> > p_doubles_;
         std::unordered_map<std::string, Parameter<int> > p_ints_;
         std::unordered_map<std::string, Parameter<bool> > p_bools_;
 
         void init( Parameters* );
         std::shared_ptr<ParametersList> proc_params_;
+        int str_fun_;
         std::string proc_name_, hadr_name_, integr_type_;
         std::pair<unsigned short,unsigned short> hi_1_, hi_2_;
     };
 
     //----- specialised registerers
 
+    /// Register a string parameter
     template<> inline void LpairHandler::registerParameter<std::string>( const char* key, const char* description, std::string* def ) { p_strings_.insert( std::make_pair( key, Parameter<std::string>( key, description, def ) ) ); }
+    /// Register a double floating point parameter
     template<> inline void LpairHandler::registerParameter<double>( const char* key, const char* description, double* def ) { p_doubles_.insert( std::make_pair( key, Parameter<double>( key, description, def ) ) ); }
+    /// Register an integer parameter
     template<> inline void LpairHandler::registerParameter<int>( const char* key, const char* description, int* def ) { p_ints_.insert( std::make_pair( key, Parameter<int>( key, description, def ) ) ); }
+    /// Register a boolean parameter
     template<> inline void LpairHandler::registerParameter<bool>( const char* key, const char* description, bool* def ) { p_bools_.insert( std::make_pair( key, Parameter<bool>( key, description, def ) ) ); }
 
     //----- specialised setters
 
     template<> inline void LpairHandler::setValue<std::string>( const char* key, const std::string& value ) {
       auto it = p_strings_.find( key );
       if ( it != p_strings_.end() ) *it->second.value = value;
     }
     template<> inline void LpairHandler::setValue<double>( const char* key, const double& value ) {
       auto it = p_doubles_.find( key );
       if ( it != p_doubles_.end() ) *it->second.value = value;
     }
     template<> inline void LpairHandler::setValue<int>( const char* key, const int& value ) {
       auto it = p_ints_.find( key );
       if ( it != p_ints_.end() ) *it->second.value = value;
     }
     template<> inline void LpairHandler::setValue<bool>( const char* key, const bool& value ) {
       auto it = p_bools_.find( key );
       if ( it != p_bools_.end() ) *it->second.value = value;
     }
 
     //----- specialised getters
 
     /// Retrieve a string parameter value
     template<> inline std::string LpairHandler::getValue( const char* key ) const {
       const auto& it = p_strings_.find( key );
       if ( it != p_strings_.end() ) return *it->second.value;
       return "null";
     }
     /// Retrieve a floating point parameter value
     template<> inline double LpairHandler::getValue( const char* key ) const {
       const auto& it = p_doubles_.find( key );
       if ( it != p_doubles_.end() ) return *it->second.value;
       return -999.;
     }
     /// Retrieve an integer parameter value
     template<> inline int LpairHandler::getValue( const char* key ) const {
       const auto& it = p_ints_.find( key );
       if ( it != p_ints_.end() ) return *it->second.value;
       return 999;
     }
     /// Retrieve a boolean parameter value
     template<> inline bool LpairHandler::getValue( const char* key ) const {
       const auto& it = p_bools_.find( key );
       if ( it != p_bools_.end() ) return *it->second.value;
       return true;
     }
   }
 }
 
 #endif
 
diff --git a/CepGen/Cards/PythonHandler.cpp b/CepGen/Cards/PythonHandler.cpp
index f6949c2..2670dc2 100644
--- a/CepGen/Cards/PythonHandler.cpp
+++ b/CepGen/Cards/PythonHandler.cpp
@@ -1,415 +1,420 @@
 #include "CepGen/Cards/PythonHandler.h"
 #include "CepGen/Core/Exception.h"
 
 #ifdef PYTHON
 
 #include "CepGen/Core/TamingFunction.h"
 #include "CepGen/Core/Exception.h"
 #include "CepGen/Core/ParametersList.h"
 
 #include "CepGen/Processes/GamGamLL.h"
 #include "CepGen/Processes/PPtoFF.h"
 #include "CepGen/Processes/PPtoWW.h"
-#include "CepGen/Processes/FortranKTProcess.h"
+#include "CepGen/Processes/FortranProcesses.h"
 
 #include "CepGen/StructureFunctions/StructureFunctionsBuilder.h"
 #include "CepGen/StructureFunctions/LHAPDF.h"
 #include "CepGen/StructureFunctions/MSTWGrid.h"
 #include "CepGen/StructureFunctions/Schaefer.h"
 
 #include "CepGen/Hadronisers/Pythia8Hadroniser.h"
 
 #include <algorithm>
 
-extern "C"
-{
-  extern void nucl_to_ff_( double& );
-}
-
 #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 ) );
 
       //--- list of process-specific parameters
       ParametersList proc_params;
       fillParameter( process, "processParameters", proc_params );
 
       //--- 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 = get<std::string>( pproc_name );
 
       //--- process mode
       params_.kinematics.mode = (KinematicsMode)proc_params.get<int>( "mode", (int)KinematicsMode::invalid );
 
       if ( proc_name == "lpair" )
         params_.setProcess( new Process::GamGamLL( proc_params ) );
       else if ( proc_name == "pptoll" || proc_name == "pptoff" )
         params_.setProcess( new Process::PPtoFF( proc_params ) );
       else if ( proc_name == "pptoww" )
         params_.setProcess( new Process::PPtoWW( proc_params ) );
-      else if ( proc_name == "patoll" )
-        params_.setProcess( new Process::FortranKTProcess( proc_params, "nucltoff", "(p/A)(p/A) ↝ (g/ɣ)ɣ → f⁺f¯", nucl_to_ff_ ) );
-      else throw CG_FATAL( "PythonHandler" ) << "Unrecognised process: " << proc_name << ".";
+      else {
+        Process::generateFortranProcesses();
+        for ( auto& proc : Process::FortranProcessesHandler::get().list() )
+          if ( proc_name == std::string( proc.name ) )
+            params_.setProcess( new Process::FortranKTProcess( proc_params, proc.name, proc.description, proc.method ) );
+        if ( !params_.process() )
+          throw CG_FATAL( "PythonHandler" ) << "Unrecognised process name: " << proc_name << "!";
+      }
 
       //--- 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 );
 
       //--- taming functions
       PyObject* ptam = getElement( process, "tamingFunctions" ); // borrowed
       if ( ptam )
         parseTamingFunctions( ptam );
 
       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 );
       }
 
       //--- finalisation
       Py_CLEAR( cfg );
     }
 
     PythonHandler::~PythonHandler()
     {
       if ( Py_IsInitialized() )
         Py_Finalize();
     }
 
     void
     PythonHandler::parseIncomingKinematics( PyObject* kin )
     {
       //--- retrieve the beams PDG ids
       std::vector<double> beams_pz;
       fillParameter( kin, "pz", beams_pz );
       if ( beams_pz.size() == 2 ) {
         params_.kinematics.incoming_beams.first.pz = beams_pz.at( 0 );
         params_.kinematics.incoming_beams.second.pz = beams_pz.at( 1 );
       }
       //--- retrieve the beams longitudinal momentum
       std::vector<int> beams_pdg;
       fillParameter( kin, "pdgIds", beams_pdg );
       if ( beams_pdg.size() == 2 ) {
         params_.kinematics.incoming_beams.first.pdg = (PDG)beams_pdg.at( 0 );
         params_.kinematics.incoming_beams.second.pdg = (PDG)beams_pdg.at( 1 );
       }
       double sqrt_s = -1.;
       fillParameter( kin, "cmEnergy", sqrt_s );
       fillParameter( kin, "kmrGridPath", params_.kinematics.kmr_grid_path );
       if ( sqrt_s != -1. )
         params_.kinematics.setSqrtS( sqrt_s );
       PyObject* psf = getElement( kin, "structureFunctions" ); // borrowed
       if ( psf )
         parseStructureFunctions( psf, params_.kinematics.structure_functions );
       std::vector<int> kt_fluxes;
       fillParameter( kin, "ktFluxes", kt_fluxes );
       if ( kt_fluxes.size() > 0 )
         params_.kinematics.incoming_beams.first.kt_flux = (KTFlux)kt_fluxes.at( 0 );
       if ( kt_fluxes.size() > 1 )
         params_.kinematics.incoming_beams.second.kt_flux = (KTFlux)kt_fluxes.at( 1 );
       std::vector<int> hi_beam1, hi_beam2;
       fillParameter( kin, "heavyIonA", hi_beam1 );
       if ( hi_beam1.size() == 2 )
         params_.kinematics.incoming_beams.first.pdg = HeavyIon{ (unsigned short)hi_beam1[0], (Element)hi_beam1[1] };
       fillParameter( kin, "heavyIonB", hi_beam2 );
       if ( hi_beam2.size() == 2 )
         params_.kinematics.incoming_beams.second.pdg = HeavyIon{ (unsigned short)hi_beam2[0], (Element)hi_beam2[1] };
     }
 
     void
     PythonHandler::parseStructureFunctions( PyObject* psf, std::shared_ptr<StructureFunctions>& sf_handler )
     {
       int str_fun = 0;
       fillParameter( psf, "id", str_fun );
       sf_handler = StructureFunctionsBuilder::get( (SF::Type)str_fun );
       switch( (SF::Type)str_fun ) {
         case SF::Type::LHAPDF: {
           auto sf = std::dynamic_pointer_cast<SF::LHAPDF>( params_.kinematics.structure_functions );
           fillParameter( psf, "pdfSet", sf->params.pdf_set );
           fillParameter( psf, "numFlavours", (unsigned int&)sf->params.num_flavours );
           fillParameter( psf, "pdfMember", (unsigned int&)sf->params.pdf_member );
           fillParameter( psf, "mode", (unsigned int&)sf->params.mode );
         } break;
         case SF::Type::MSTWgrid: {
           auto sf = std::dynamic_pointer_cast<mstw::Grid>( params_.kinematics.structure_functions );
           fillParameter( psf, "gridPath", sf->params.grid_path );
         } break;
         case SF::Type::Schaefer: {
           auto sf = std::dynamic_pointer_cast<SF::Schaefer>( params_.kinematics.structure_functions );
           fillParameter( psf, "Q2cut", sf->params.q2_cut );
           std::vector<double> w2_lims;
           fillParameter( psf, "W2limits", w2_lims );
           if ( w2_lims.size() != 0 ) {
             if ( w2_lims.size() != 2 )
               throwPythonError( Form( "Invalid size for W2limits attribute: %d != 2!", w2_lims.size() ) );
             else {
               sf->params.w2_lo = *std::min_element( w2_lims.begin(), w2_lims.end() );
               sf->params.w2_hi = *std::max_element( w2_lims.begin(), w2_lims.end() );
             }
           }
           PyObject* pcsf = getElement( psf, "continuumSF" ); // borrowed
           if ( pcsf )
             parseStructureFunctions( pcsf, sf->params.continuum_model );
           PyObject* ppsf = getElement( psf, "perturbativeSF" ); // borrowed
           if ( ppsf )
             parseStructureFunctions( ppsf, sf->params.perturbative_model );
           PyObject* prsf = getElement( psf, "resonancesSF" ); // borrowed
           if ( prsf )
             parseStructureFunctions( prsf, sf->params.resonances_model );
           fillParameter( psf, "higherTwist", (bool&)sf->params.higher_twist );
         } break;
         default: break;
       }
     }
 
     void
     PythonHandler::parseOutgoingKinematics( PyObject* kin )
     {
       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)get<int>( 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)get<int>( 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 = get<std::string>( 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( get<std::string>( pvar ).c_str(), get<std::string>( 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 = get<std::string>( pname );
 
-      fillParameter( hadr, "maxTrials", params_.hadroniser_max_trials );
-      PyObject* pseed = getElement( hadr, "seed" ); // borrowed
-      long long seed = -1ll;
-      if ( pseed && is<int>( pseed ) ) {
-        seed = PyLong_AsLongLong( pseed );
-        CG_DEBUG( "PythonHandler:hadroniser" ) << "Hadroniser seed set to " << seed;
+      //--- list of module-specific parameters
+      ParametersList mod_params;
+      fillParameter( hadr, "moduleParameters", mod_params );
+
+      if ( hadr_name == "pythia8" )
+        params_.setHadroniser( new Hadroniser::Pythia8Hadroniser( params_, mod_params ) );
+      else
+        throwPythonError( Form( "Unrecognised hadronisation algorithm: \"%s\"!", hadr_name.c_str() ) );
+
+      auto h = params_.hadroniser();
+      { //--- before calling the init() method
+        std::vector<std::string> config;
+        fillParameter( hadr, "preConfiguration", config );
+        h->readStrings( config );
       }
-      if ( hadr_name == "pythia8" ) {
-        params_.setHadroniser( new Hadroniser::Pythia8Hadroniser( params_ ) );
+      h->init();
+      { //--- after init() has been called
         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 );
+        fillParameter( hadr, "processConfiguration", config );
+        for ( const auto& block : config ) {
+          std::vector<std::string> config_blk;
+          fillParameter( hadr, block.c_str(), config_blk );
+          h->readStrings( config_blk );
+        }
       }
     }
   }
 }
 
 #endif
 
diff --git a/CepGen/Cards/PythonTypes.cpp b/CepGen/Cards/PythonTypes.cpp
index 0c6b7e8..66361ff 100644
--- a/CepGen/Cards/PythonTypes.cpp
+++ b/CepGen/Cards/PythonTypes.cpp
@@ -1,165 +1,171 @@
 #include "CepGen/Cards/PythonHandler.h"
 #include "CepGen/Core/ParametersList.h"
 #include "CepGen/Core/utils.h"
 
 #ifdef PYTHON
 #if PY_MAJOR_VERSION < 3
 #  define PYTHON2
 #endif
 
 namespace CepGen
 {
   namespace Cards
   {
     //------------------------------------------------------------------
     // typed retrieval helpers
     //------------------------------------------------------------------
 
     template<> bool
     PythonHandler::is<int>( PyObject* obj ) const
     {
 #ifdef PYTHON2
       return ( PyInt_Check( obj ) || PyBool_Check( obj ) );
 #else
       return ( PyLong_Check( obj ) || PyBool_Check( obj ) );
 #endif
     }
 
     template<> int
     PythonHandler::get<int>( PyObject* obj ) const
     {
 #ifdef PYTHON2
       return PyInt_AsLong( obj );
 #else
       return PyLong_AsLong( obj );
 #endif
     }
 
     template<> unsigned long
     PythonHandler::get<unsigned long>( PyObject* obj ) const
     {
 #ifdef PYTHON2
       return PyInt_AsUnsignedLongMask( obj );
 #else
       if ( !PyLong_Check( obj ) )
         throwPythonError( Form( "Object \"%s\" has invalid type %s", key, obj->ob_type->tp_name ) );
       return PyLong_AsUnsignedLong( obj );
 #endif
     }
 
+    template<> long long
+    PythonHandler::get<long long>( PyObject* obj ) const
+    {
+      return PyLong_AsLongLong( obj );
+    }
+
     template<> bool
     PythonHandler::is<double>( PyObject* obj ) const
     {
       return PyFloat_Check( obj );
     }
 
     template<> double
     PythonHandler::get<double>( PyObject* obj ) const
     {
       return PyFloat_AsDouble( obj );
     }
 
     template<> bool
     PythonHandler::is<std::string>( PyObject* obj ) const
     {
 #ifdef PYTHON2
       return PyString_Check( obj );
 #else
       return PyUnicode_Check( obj );
 #endif
     }
 
     template<> std::string
     PythonHandler::get<std::string>( PyObject* obj ) const
     {
       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;
     }
 
     template<> bool
     PythonHandler::is<ParametersList>( PyObject* obj ) const
     {
       return PyDict_Check( obj );
     }
 
     template<> ParametersList
     PythonHandler::get<ParametersList>( PyObject* obj ) const
     {
       ParametersList out;
       PyObject* pkey = nullptr, *pvalue = nullptr;
       Py_ssize_t pos = 0;
       while ( PyDict_Next( obj, &pos, &pkey, &pvalue ) ) {
         const std::string skey = get<std::string>( pkey );
         if ( is<int>( pvalue ) )
           out.set<int>( skey, get<int>( pvalue ) );
         else if ( is<double>( pvalue ) )
           out.set<double>( skey, get<double>( pvalue ) );
         else if ( is<std::string>( pvalue ) )
           out.set<std::string>( skey, get<std::string>( pvalue ) );
         else if ( is<ParametersList>( pvalue ) )
           out.set<ParametersList>( skey, get<ParametersList>( pvalue ) );
         else if ( PyTuple_Check( pvalue ) || PyList_Check( pvalue ) ) { // vector
           PyObject* pfirst = PyTuple_GetItem( pvalue, 0 );
           PyObject* pit = nullptr;
           const bool tuple = PyTuple_Check( pvalue );
           const Py_ssize_t num_entries = ( tuple )
             ? PyTuple_Size( pvalue )
             : PyList_Size( pvalue );
           if ( is<int>( pfirst ) ) {
             std::vector<int> vec;
             for ( Py_ssize_t i = 0; i < num_entries; ++i ) {
               pit = ( tuple ) ? PyTuple_GetItem( pvalue, i ) : PyList_GetItem( pvalue, i );
               if ( pit->ob_type != pfirst->ob_type )
                 throwPythonError( Form( "Mixed types detected in vector '%s'", skey.c_str() ) );
               vec.emplace_back( get<int>( pit ) );
             }
             out.set<std::vector<int> >( skey, vec );
           }
           else if ( is<double>( pfirst ) ) {
             std::vector<double> vec;
             for ( Py_ssize_t i = 0; i < num_entries; ++i ) {
               pit = ( tuple ) ? PyTuple_GetItem( pvalue, i ) : PyList_GetItem( pvalue, i );
               if ( pit->ob_type != pfirst->ob_type )
                 throwPythonError( Form( "Mixed types detected in vector '%s'", skey.c_str() ) );
               vec.emplace_back( get<double>( pit ) );
             }
             out.set<std::vector<double> >( skey, vec );
           }
           else if ( is<std::string>( pfirst ) ) {
             std::vector<std::string> vec;
             for ( Py_ssize_t i = 0; i < num_entries; ++i ) {
               pit = ( tuple ) ? PyTuple_GetItem( pvalue, i ) : PyList_GetItem( pvalue, i );
               if ( pit->ob_type != pfirst->ob_type )
                 throwPythonError( Form( "Mixed types detected in vector '%s'", skey.c_str() ) );
               vec.emplace_back( get<std::string>( pit ) );
             }
             out.set<std::vector<std::string> >( skey, vec );
           }
           else if ( is<ParametersList>( pfirst ) ) {
             std::vector<ParametersList> vec;
             for ( Py_ssize_t i = 0; i < num_entries; ++i ) {
               pit = ( tuple ) ? PyTuple_GetItem( pvalue, i ) : PyList_GetItem( pvalue, i );
               if ( pit->ob_type != pfirst->ob_type )
                 throwPythonError( Form( "Mixed types detected in vector '%s'", skey.c_str() ) );
               vec.emplace_back( get<ParametersList>( pit ) );
             }
             out.set<std::vector<ParametersList> >( skey, vec );
           }
         }
       }
       return out;
     }
   }
 }
 
 #endif
 
diff --git a/CepGen/Cards/PythonUtils.cpp b/CepGen/Cards/PythonUtils.cpp
index 50885c7..b53a57d 100644
--- a/CepGen/Cards/PythonUtils.cpp
+++ b/CepGen/Cards/PythonUtils.cpp
@@ -1,252 +1,251 @@
 #include "CepGen/Cards/PythonHandler.h"
 #include "CepGen/Core/ParametersList.h"
 #include "CepGen/Core/Exception.h"
 #include "CepGen/Core/utils.h"
 
 #ifdef PYTHON
 
 #include <string>
 #include <algorithm>
 #include <frameobject.h>
 
 #if PY_MAJOR_VERSION < 3
 #  define PYTHON2
 #endif
 
 namespace CepGen
 {
   namespace Cards
   {
     //------------------------------------------------------------------
     // 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;
         std::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 = std::string( "  " )+tabul;
             ptraceback = ptraceback->tb_next;
           }
         }
       }
       Py_Finalize();
       throw CG_FATAL( "PythonHandler:error" ) << oss.str();
     }
 
     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 = get<double>( PyTuple_GetItem( pobj, 0 ) );
       lim.min() = min;
       if ( PyTuple_Size( pobj ) > 1 ) {
         double max = get<double>( 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;
       if ( !is<int>( pobj ) )
         throwPythonError( Form( "Object \"%s\" has invalid type %s", key, pobj->ob_type->tp_name ) );
       out = get<int>( pobj );
     }
 
     void
     PythonHandler::fillParameter( PyObject* parent, const char* key, unsigned long& out )
     {
       PyObject* pobj = getElement( parent, key ); // borrowed
       if ( !pobj )
         return;
       if ( !is<int>( pobj ) )
         throwPythonError( Form( "Object \"%s\" has invalid type %s", key, pobj->ob_type->tp_name ) );
       out = get<unsigned long>( pobj );
     }
 
     void
     PythonHandler::fillParameter( PyObject* parent, const char* key, unsigned int& out )
     {
       PyObject* pobj = getElement( parent, key ); // borrowed
       if ( !pobj )
         return;
       if ( !is<int>( pobj ) )
         throwPythonError( Form( "Object \"%s\" has invalid type %s", key, pobj->ob_type->tp_name ) );
       out = get<unsigned long>( pobj );
     }
 
     void
     PythonHandler::fillParameter( PyObject* parent, const char* key, double& out )
     {
       PyObject* pobj = getElement( parent, key ); // borrowed
       if ( !pobj )
         return;
       if ( !is<double>( pobj ) )
         throwPythonError( Form( "Object \"%s\" has invalid type %s", key, pobj->ob_type->tp_name ) );
       out = get<double>( pobj );
     }
 
     void
     PythonHandler::fillParameter( PyObject* parent, const char* key, std::string& out )
     {
       PyObject* pobj = getElement( parent, key ); // borrowed
       if ( !pobj )
         return;
       if ( !is<std::string>( pobj ) )
         throwPythonError( Form( "Object \"%s\" has invalid type %s", key, pobj->ob_type->tp_name ) );
       out = get<std::string>( pobj );
     }
 
     void
     PythonHandler::fillParameter( PyObject* parent, const char* key, std::vector<double>& 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
-        if ( !is<double>( pit ) )
-          continue;
-        out.emplace_back( get<double>( pit ) );
+        if ( is<double>( pit ) )
+          out.emplace_back( get<double>( pit ) );
       }
     }
 
     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( get<std::string>( pit ) );
       }
     }
 
     void
     PythonHandler::fillParameter( PyObject* parent, const char* key, std::vector<int>& out )
     {
       out.clear();
       PyObject* pobj = getElement( parent, key ); // borrowed
       if ( !pobj )
         return;
       if ( !PyTuple_Check( pobj ) )
         throwPythonError( Form( "Object \"%s\" has invalid type", key ) );
       for ( Py_ssize_t i = 0; i < PyTuple_Size( pobj ); ++i ) {
         PyObject* pit = PyTuple_GetItem( pobj, i );
         if ( !is<int>( pit ) )
           throwPythonError( Form( "Object %d has invalid type", i ) );
         out.emplace_back( get<int>( pit ) );
       }
     }
 
     void
     PythonHandler::fillParameter( PyObject* parent, const char* key, ParametersList& out )
     {
       PyObject* pobj = getElement( parent, key ); // borrowed
       if ( !pobj )
         return;
       if ( !PyDict_Check( pobj ) )
         throwPythonError( Form( "Object \"%s\" has invalid type", key ) );
       out += get<ParametersList>( pobj );
     }
   }
 }
 
 #endif
diff --git a/CepGen/Core/Exception.h b/CepGen/Core/Exception.h
index 353f9c9..38e0e0f 100644
--- a/CepGen/Core/Exception.h
+++ b/CepGen/Core/Exception.h
@@ -1,191 +1,194 @@
 #ifndef CepGen_Core_Exception_h
 #define CepGen_Core_Exception_h
 
 #include <sstream>
 #include <stdexcept>
 #include <csignal>
 
 #include "CepGen/Core/Logger.h"
 
 #define CG_EXCEPT_MATCH( str, type ) \
   CepGen::Logger::get().passExceptionRule( str, CepGen::Logger::Level::type )
 
 #define CG_LOG( mod ) \
   ( !CG_EXCEPT_MATCH( mod, information ) ) \
   ? CepGen::NullStream( mod ) \
   : CepGen::Exception( __PRETTY_FUNCTION__, mod, CepGen::Exception::Type::verbatim )
 #define CG_INFO( mod ) \
   ( !CG_EXCEPT_MATCH( mod, information ) ) \
   ? CepGen::NullStream( mod ) \
   : CepGen::Exception( __PRETTY_FUNCTION__, mod, CepGen::Exception::Type::info )
 #define CG_DEBUG( mod ) \
   ( !CG_EXCEPT_MATCH( mod, debug ) ) \
   ? CepGen::NullStream( mod ) \
   : CepGen::Exception( __PRETTY_FUNCTION__, mod, CepGen::Exception::Type::debug )
 #define CG_DEBUG_LOOP( mod ) \
   ( !CG_EXCEPT_MATCH( mod, debugInsideLoop ) ) \
   ? CepGen::NullStream( mod ) \
   : CepGen::Exception( __PRETTY_FUNCTION__, mod, CepGen::Exception::Type::debug )
 #define CG_WARNING( mod ) \
   ( !CG_EXCEPT_MATCH( mod, warning ) ) \
   ? CepGen::NullStream( mod ) \
   : CepGen::Exception( __PRETTY_FUNCTION__, mod, CepGen::Exception::Type::warning )
 #define CG_ERROR( mod ) \
   ( !CG_EXCEPT_MATCH( mod, error ) ) \
   ? CepGen::NullStream( mod ) \
   : CepGen::Exception( __PRETTY_FUNCTION__, mod, CepGen::Exception::Type::warning )
 #define CG_FATAL( mod ) \
   CepGen::Exception( __PRETTY_FUNCTION__, mod, CepGen::Exception::Type::fatal )
 
 namespace CepGen
 {
-  /// A simple exception handler
+  /// \brief A simple exception handler
   /// \author Laurent Forthomme <laurent.forthomme@cern.ch>
   /// \date 24 Mar 2015
   class Exception : public std::exception
   {
     public:
       /// Enumeration of exception severities
       /// \author Laurent Forthomme <laurent.forthomme@cern.ch>
       /// \date 27 Mar 2015
       enum class Type {
         undefined = -1, debug, verbatim, info, warning, error, fatal };
 
       /// Generic constructor
-      /// \param[in] from method invoking the exception
       /// \param[in] module exception classifier
       /// \param[in] type exception type
       /// \param[in] id exception code (useful for logging)
       explicit inline Exception( const char* module = "", Type type = Type::undefined, const int id = 0 ) :
         module_( module ), type_( type ), error_num_( id ) {}
       /// Generic constructor
       /// \param[in] from method invoking the exception
       /// \param[in] module exception classifier
       /// \param[in] type exception type
       /// \param[in] id exception code (useful for logging)
       explicit inline Exception( const char* from, const char* module, Type type = Type::undefined, const int id = 0 ) :
         from_( from ), module_( module ), type_( type ), error_num_( id ) {}
       /// Generic constructor
       /// \param[in] from method invoking the exception
       /// \param[in] module exception classifier
       /// \param[in] type exception type
       /// \param[in] id exception code (useful for logging)
       explicit inline Exception( const char* from, const std::string& module, Type type = Type::undefined, const int id = 0 ) :
         from_( from ), module_( module ), type_( type ), error_num_( id ) {}
       /// Copy constructor
       inline Exception( const Exception& rhs ) :
         from_( rhs.from_ ), module_( rhs.module_ ), message_( rhs.message_.str() ), type_( rhs.type_ ), error_num_( rhs.error_num_ ) {}
       /// Default destructor (potentially killing the process)
       inline ~Exception() noexcept override {
         do { dump(); } while ( 0 );
         // we stop this process' execution on fatal exception
         if ( type_ == Type::fatal )
           if ( raise( SIGINT ) != 0 )
             exit( 0 );
       }
 
       //----- Overloaded stream operators
 
       /// Generic templated message feeder operator
       template<typename T>
       inline friend const Exception& operator<<( const Exception& exc, T var ) {
         Exception& nc_except = const_cast<Exception&>( exc );
         nc_except.message_ << var;
         return exc;
       }
       /// Pipe modifier operator
       inline friend const Exception& operator<<( const Exception& exc, std::ios_base&( *f )( std::ios_base& ) ) {
         Exception& nc_except = const_cast<Exception&>( exc );
         f( nc_except.message_ );
         return exc;
       }
 
       /// Exception message
       /*inline const char* what() const noexcept override {
         return message_.str().c_str();
       }*/
 
       /// Extract the origin of the exception
       inline std::string from() const { return from_; }
       /// Extract the exception code
       inline int errorNumber() const { return error_num_; }
       /// Extract the exception type
       inline Type type() const { return type_; }
       /// Extract a human-readable (and colourified) version of the exception type
       inline std::string typeString() const {
         switch ( type() ) {
           case Type::warning: return "\033[34;1mWarning\033[0m";
           case Type::info: return "\033[32;1mInfo.\033[0m";
           case Type::debug: return "\033[33;1mDebug\033[0m";
           case Type::error: return "\033[31;1mError\033[0m";
           case Type::fatal: return "\033[31;1mFatal\033[0m";
           case Type::undefined: default: return "\33[7;1mUndefined\033[0m";
         }
       }
 
       /// Dump the full exception information in a given output stream
       /// \param[inout] os the output stream where the information is dumped
       inline void dump( std::ostream& os = *Logger::get().output ) const {
         os << fullMessage() << std::endl;
       }
       /// Extract a one-line summary of the exception
       inline std::string shortMessage() const {
         std::ostringstream os;
         os << "[" << typeString() << "]";
         if ( type_ == Type::debug || type_ == Type::warning )
           os << " \033[30;4m" << from_ << "\033[0m\n";
         os << "\t" << message_.str();
         return os.str();
       }
 
     private:
       inline static char* now() {
         static char buffer[10];
         time_t rawtime;
         time( &rawtime );
         struct tm* timeinfo = localtime( &rawtime );
         strftime( buffer, 10, "%H:%M:%S", timeinfo );
         return buffer;
       }
       /// Extract a full exception message
       inline std::string fullMessage() const {
         if ( type_ == Type::info || type_ == Type::debug || type_ == Type::warning )
           return shortMessage();
         if ( type_ == Type::verbatim )
           return message_.str();
         std::ostringstream os;
         os << "============================= Exception detected! =============================" << std::endl
            << " Class:       " << typeString() << std::endl;
         if ( !from_.empty() )
           os << " Raised by:   " << from_ << std::endl;
         os << " Description: \t" << message_.str() << std::endl;
         if ( errorNumber() != 0 )
           os << "-------------------------------------------------------------------------------" << std::endl
              << " Error #" << error_num_ << std::endl;
         os << "===============================================================================";
         return os.str();
       }
       /// Origin of the exception
       std::string from_;
       /// Exception classificator
       std::string module_;
       /// Message to throw
       std::ostringstream message_;
       /// Exception type
       Type type_;
       /// Integer exception number
       int error_num_;
   };
-  /// Placeholder for debugging messages if logging threshold is not reached
+  /// \brief Placeholder for debugging messages if logging threshold is not reached
   /// \date Apr 2018
   struct NullStream
   {
+    /// Construct from a module name
     explicit NullStream( const char* ) {}
+    /// Construct from a module name
     explicit NullStream( const std::string& ) {}
+    /// Copy constructor
     NullStream( const Exception& ) {}
+    /// Stream operator (null and void)
     template<class T> NullStream& operator<<( const T& ) { return *this; }
   };
 }
 
 #endif
 
diff --git a/CepGen/IO/FortranInterface.cpp b/CepGen/Core/FortranInterface.cpp
similarity index 97%
rename from CepGen/IO/FortranInterface.cpp
rename to CepGen/Core/FortranInterface.cpp
index b709844..7f8dca2 100644
--- a/CepGen/IO/FortranInterface.cpp
+++ b/CepGen/Core/FortranInterface.cpp
@@ -1,98 +1,99 @@
 #include "CepGen/StructureFunctions/StructureFunctionsBuilder.h"
 #include "CepGen/StructureFunctions/StructureFunctions.h"
 
 #include "CepGen/Physics/KTFlux.h"
 #include "CepGen/Physics/HeavyIon.h"
 #include "CepGen/Physics/ParticleProperties.h"
 
 #include "CepGen/Core/Exception.h"
 
 #ifdef __cplusplus
 extern "C" {
 #endif
+  /// Expose structure functions calculators to Fortran
   void
   cepgen_structure_functions_( int& sfmode, double& xbj, double& q2, double& f2, double& fl )
   {
     using namespace CepGen;
     SF::Type sf_mode = (SF::Type)sfmode;
 
     CG_DEBUG( "cepgen_structure_functions" ) << sf_mode;
 
     static StructureFunctions& val = StructureFunctionsBuilder::get( sf_mode )->operator()( xbj, q2 );
     f2 = val.F2;
     fl = val.FL;
   }
 
   double
   cepgen_kt_flux_( int& fmode, double& x, double& kt2, int& sfmode, double& mx )
   {
     using namespace CepGen;
     static auto sf = StructureFunctionsBuilder::get( (SF::Type)sfmode );
     return ktFlux(
       (KTFlux)fmode, x, kt2, *sf, mx );
   }
 
   double
   cepgen_kt_flux_hi_( int& fmode, double& x, double& kt2, int& a, int& z )
   {
     using namespace CepGen;
     return ktFlux(
       (KTFlux)fmode, x, kt2, HeavyIon{ (unsigned short)a, (Element)z } );
   }
 
   double
   cepgen_particle_mass_( int& pdg_id )
   {
     try {
       return CepGen::ParticleProperties::mass( (CepGen::PDG)pdg_id );
     } catch ( const CepGen::Exception& e ) {
       e.dump();
       exit( 0 );
     }
   }
 
   double
   cepgen_particle_charge_( int& pdg_id )
   {
     try {
       return CepGen::ParticleProperties::charge( pdg_id );
     } catch ( const CepGen::Exception& e ) {
       e.dump();
       exit( 0 );
     }
   }
 #ifdef __cplusplus
 }
 #endif
 
 namespace CepGen
 {
   namespace Process
   {
     struct FortranKTProcessWrapper : GenericKTProcess
     {
       FortranKTProcessWrapper( const std::string& name, unsigned short parton_pdg, unsigned short outgoing_pdg ) :
         GenericKTProcess( name, std::string( "Fortran wrapped ")+name, 4,
                           { (ParticleCode)parton_pdg, (ParticleCode)parton_pdg },
                           { (ParticleCode)outgoing_pdg, (ParticleCode)outgoing_pdg } ) {}
       ~FortranKTProcessWrapper() {}
       double x[10];
     };
   }
 }
 
 extern "C"
 {
   extern double test_weight_();
   extern struct {
   } test_params_;
 }
 
 #define add_kt_process( name, prefix, parton_pdg, outgoing_pdg ) \
   extern "C" {\
     extern double prefix##_weight_();\
     extern struct {\
     } prefix##_params_;\
   } \
 
 add_kt_process( pptoll_fortran, pptoll, 22, 11 )
diff --git a/CepGen/Core/Generator.cpp b/CepGen/Core/Generator.cpp
index ca646fc..a402891 100644
--- a/CepGen/Core/Generator.cpp
+++ b/CepGen/Core/Generator.cpp
@@ -1,165 +1,171 @@
 #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/GenericProcess.h"
 #include "CepGen/Event/Event.h"
 
 #include <fstream>
 #include <chrono>
 
 namespace CepGen
 {
   volatile int gSignal;
   Generator::Generator() :
-    parameters( std::unique_ptr<Parameters>( new Parameters ) ),
-    cross_section_( -1. ), cross_section_error_( -1. )
+    parameters( std::unique_ptr<Parameters>( new Parameters ) ), result_( -1. ), result_error_( -1. )
   {
     CG_DEBUG( "Generator:init" ) << "Generator initialized";
     try {
       printHeader();
     } catch ( 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 ),
-    cross_section_( -1. ), cross_section_error_( -1. )
+    parameters( ip ), result_( -1. ), result_error_( -1. )
   {}
 
   Generator::~Generator()
   {
     if ( parameters->generation.enabled
       && parameters->process() && parameters->numGeneratedEvents() > 0 ) {
       CG_INFO( "Generator" )
         << "Mean generation time / event: "
         << parameters->totalGenerationTime()*1.e3/parameters->numGeneratedEvents()
         << " ms.";
     }
   }
 
   size_t
   Generator::numDimensions() const
   {
     if ( !parameters->process() )
       return 0;
 
     parameters->process()->addEventContent();
     parameters->process()->setKinematics( parameters->kinematics );
     return parameters->process()->numDimensions();
   }
 
   void
   Generator::clearRun()
   {
     integrator_.reset();
     parameters->process()->first_run = true;
-    cross_section_ = cross_section_error_ = -1.;
+    result_ = result_error_ = -1.;
   }
 
   void
   Generator::setParameters( Parameters& ip )
   {
     parameters = std::unique_ptr<Parameters>( new 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() );
     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"
+      << "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.";
 
+    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*1.e-3 << " +/- " << err*1.e-3 << " nb.";
+    else
+      CG_INFO( "Generator" )
+        << "Total cross section: " << xsec << " +/- " << err << " pb.";
+  }
+
+  void
+  Generator::integrate()
+  {
     // first destroy and recreate the integrator instance
     if ( !integrator_ )
       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() ) );
 
     CG_DEBUG( "Generator:newInstance" )
       << "New integrator instance created\n\t"
       << "Considered topology: " << parameters->kinematics.mode << " case\n\t"
       << "Will proceed with " << numDimensions() << "-dimensional integration.";
 
-    const int res = integrator_->integrate( cross_section_, cross_section_error_ );
+    const int res = integrator_->integrate( result_, result_error_ );
     if ( res != 0 )
-      throw CG_FATAL( "Generator" ) << "Error while computing the cross-section: return value = " << res << ".";
-
-    xsec = cross_section_;
-    err = cross_section_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*1.e-3 << " +/- " << err*1.e-3 << " nb.";
-    else
-      CG_INFO( "Generator" )
-        << "Total cross section: " << xsec << " +/- " << err << " pb.";
+      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;
   }
 
   void
   Generator::generate( std::function<void( const Event&, unsigned long )> callback )
   {
     const Timer tmr;
 
     CG_INFO( "Generator" )
       << parameters->generation.maxgen << " events will be generated.";
 
     integrator_->generate( parameters->generation.maxgen, callback, &tmr );
 
     const double gen_time_s = tmr.elapsed();
     CG_INFO( "Generator" )
       << parameters->generation.ngen << " events generated "
       << "in " << gen_time_s << " s "
       << "(" << gen_time_s/parameters->generation.ngen*1.e3 << " ms/event).";
   }
 }
 
diff --git a/CepGen/Core/GridParameters.h b/CepGen/Core/GridParameters.h
index 9418bf5..098efa9 100644
--- a/CepGen/Core/GridParameters.h
+++ b/CepGen/Core/GridParameters.h
@@ -1,39 +1,40 @@
 #ifndef CepGen_Core_GridParameters_h
 #define CepGen_Core_GridParameters_h
 
 #include <vector>
 #include <map>
 
 namespace CepGen
 {
+  /// A parameters placeholder for the grid integration helper
   class GridParameters
   {
     public:
       /// Maximal number of dimensions handled by this integrator instance
       static const unsigned short max_dimensions_;
       /// Integration grid size parameter
       static const unsigned short mbin_;
       static const double inv_mbin_;
 
       GridParameters();
 
       std::map<unsigned int,std::vector<unsigned short> > n_map;
 
       unsigned int max;
       /// Has the generation been prepared?
       bool gen_prepared;
       double correc;
       double correc2;
       /// Maximal value of the function at one given point
       std::vector<double> f_max;
       /// Maximal value of the function in the considered integration range
       double f_max_global;
       double f_max2;
       double f_max_diff;
       double f_max_old;
       std::vector<unsigned int> num;
   };
 }
 
 #endif
 
diff --git a/CepGen/Core/Hasher.h b/CepGen/Core/Hasher.h
index 149b6ba..6dba163 100644
--- a/CepGen/Core/Hasher.h
+++ b/CepGen/Core/Hasher.h
@@ -1,34 +1,37 @@
 #ifndef CepGen_Core_Hasher_h
 #define CepGen_Core_Hasher_h
 
 #include <cstddef>
 #include <functional>
 
 namespace CepGen
 {
+  /// A hasher table for a given structure
   template<class T,bool>
   struct hasher
   {
     inline size_t operator()( const T& t ) const {
       return std::hash<T>()( t );
     }
   };
+  /// A hasher table for a given structure
   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 ) );
     }
   };
+  /// A hasher table for an enumeration
   template<class T>
   struct EnumHash
   {
     inline size_t operator()( const T& t ) const {
       return hasher<T,std::is_enum<T>::value>()( t );
     }
   };
 }
 
 #endif
 
diff --git a/CepGen/Core/Integrator.h b/CepGen/Core/Integrator.h
index c21af56..630e0c5 100644
--- a/CepGen/Core/Integrator.h
+++ b/CepGen/Core/Integrator.h
@@ -1,99 +1,98 @@
 #ifndef CepGen_Core_Integrator_h
 #define CepGen_Core_Integrator_h
 
 #include <vector>
 #include <memory>
 #include <functional>
 
 #include <string.h>
 
 #include <gsl/gsl_monte.h>
 #include <gsl/gsl_monte_vegas.h>
 #include <gsl/gsl_rng.h>
 
 namespace CepGen
 {
   class Parameters;
   class Event;
   class GridParameters;
   class Timer;
   /// Monte-Carlo integrator instance
   class Integrator
   {
     public:
       enum class Type {
         plain = 0,
-        Vegas = 1, ///< @cite PeterLepage1978192 developed by G.P. Lepage in 1978
+        Vegas = 1, ///< VEGAS algorithm \cite Lepage:1977sw developed by G.P. Lepage
         MISER = 2
       };
       enum class VegasMode { importance = 1, importanceOnly = 0, stratified = -1 };
       /**
        * Book the memory slots and structures for the integrator
        * \note Three integration algorithms are currently supported:
        *  * the plain algorithm randomly sampling points in the phase space
-       *  * the Vegas algorithm developed by P. Lepage, as documented in @cite PeterLepage1978192,
-       *  * the MISER algorithm developed by W.H. Press and G.R. Farrar, as documented in @cite Press:1989vk.
+       *  * the Vegas algorithm developed by P. Lepage, as documented in \cite Lepage:1977sw
+       *  * the MISER algorithm developed by W.H. Press and G.R. Farrar, as documented in \cite Press:1989vk.
        * \param[in] ndim Number of dimensions on which the function will be integrated
        * \param[in] integrand Function to be integrated
        * \param[inout] params Run parameters to define the phase space on which this integration is performed (embedded in an Parameters object)
        */
       Integrator( unsigned int ndim, double integrand(double*,size_t,void*), Parameters* params );
       /// Class destructor
       ~Integrator();
       /**
        * Algorithm to perform the n-dimensional Monte Carlo integration of a given function.
        * \author This C++ implementation: GSL
        * \param[out] result_ The cross section as integrated for the given phase space restrictions
        * \param[out] abserr_ The error associated to the computed cross section
        * \return 0 if the integration was performed successfully
        */
       int integrate( double& result_, double& abserr_ );
       /// Dimensional size of the phase space
       unsigned short dimensions() const;
       void generateOne( std::function<void( const Event&, unsigned long )> callback = nullptr );
       void generate( unsigned long num_events = 0, std::function<void( const Event&, unsigned long )> callback = nullptr, const Timer* tmr = nullptr );
 
     private:
       /**
        * Store the event characterized by its _ndim-dimensional point in the phase
        * space to the output file
        * \brief Store the event in the output file
        * \param[in] x The d-dimensional point in the phase space defining the unique event to store
        * \return A boolean stating whether or not the event could be saved
        */
       bool storeEvent( const std::vector<double>& x, std::function<void( const Event&, unsigned long )> callback = nullptr );
       /// Start the correction cycle on the grid
       /// \param x Point in the phase space considered
       /// \param has_correction Correction cycle started?
       bool correctionCycle( std::vector<double>& x, bool& has_correction );
       int warmupVegas( std::vector<double>& x_low, std::vector<double>& x_up, unsigned int ncall );
       /**
        * Set all the generation mode variables and align them to the integration grid set while computing the cross-section
        * \brief Prepare the class for events generation
        */
       void computeGenerationParameters();
       double uniform() const;
       double eval( const std::vector<double>& x );
       /// Selected bin at which the function will be evaluated
       int ps_bin_;
       /// List of parameters to specify the integration range and the physics determining the phase space
       Parameters* input_params_;
       /// GSL structure storing the function to be integrated by this integrator instance (along with its parameters)
       std::unique_ptr<gsl_monte_function> function_;
       std::unique_ptr<gsl_rng,void(*)( gsl_rng* )> rng_;
       std::unique_ptr<GridParameters> grid_;
       struct gsl_monte_vegas_deleter
       {
         void operator()( gsl_monte_vegas_state* state ) {
           gsl_monte_vegas_free( state );
         }
       };
       std::unique_ptr<gsl_monte_vegas_state,gsl_monte_vegas_deleter> veg_state_;
       double r_boxes_;
   };
   std::ostream& operator<<( std::ostream&, const Integrator::Type& );
   std::ostream& operator<<( std::ostream&, const Integrator::VegasMode& );
 }
 
 #endif
-
diff --git a/CepGen/Core/Parameters.cpp b/CepGen/Core/Parameters.cpp
index 36a3bd3..51cd203 100644
--- a/CepGen/Core/Parameters.cpp
+++ b/CepGen/Core/Parameters.cpp
@@ -1,268 +1,260 @@
 #include "CepGen/Parameters.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 ),
-    hadroniser_max_trials( 5 ),
     taming_functions( new 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 ),
-    hadroniser_max_trials( param.hadroniser_max_trials ),
     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 ),
-    hadroniser_max_trials( param.hadroniser_max_trials ),
     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!
   {}
 
   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::clearRunStatistics()
   {
     total_gen_time_ = 0.;
     num_gen_events_ = 0;
   }
 
   void
   Parameters::addGenerationTime( double gen_time )
   {
     total_gen_time_ += gen_time;
     num_gen_events_++;
   }
 
   Process::GenericProcess*
   Parameters::process()
   {
     return process_.get();
   }
 
   std::string
   Parameters::processName() const
   {
     if ( !process_ )
       return "no process";
     return process_->name();
   }
 
   void
   Parameters::setProcess( Process::GenericProcess* proc )
   {
     if ( !proc )
       throw CG_FATAL( "Parameters" )
         << "Trying to clone an invalid process!";
     process_.reset( proc );
   }
 
   void
   Parameters::cloneProcess( const Process::GenericProcess* proc )
   {
     if ( !proc )
       throw CG_FATAL( "Parameters" )
         << "Trying to clone an invalid process!";
     process_ = std::move( proc->clone() );
   }
 
   Hadroniser::GenericHadroniser*
   Parameters::hadroniser()
   {
     return hadroniser_.get();
   }
 
   std::string
   Parameters::hadroniserName() const
   {
     if ( !hadroniser_ )
       return "";
     return hadroniser_->name();
   }
 
   void
   Parameters::setHadroniser( Hadroniser::GenericHadroniser* hadr )
   {
     hadroniser_.reset( hadr );
   }
 
   std::ostream&
   operator<<( std::ostream& os, const Parameters* p )
   {
     const bool pretty = true;
 
     const int wb = 90, wt = 40;
     os
       << "Parameters dump" << std::left << "\n\n"
       << std::setfill('_') << std::setw( wb+3 ) << "_/¯¯RUN¯INFORMATION¯¯\\_" << std::setfill( ' ' ) << "\n"
       << std::right << std::setw( wb ) << std::left << std::endl
       << std::setw( wt ) << "Process to generate";
     if ( p->process_ ) {
       os << ( pretty ? boldify( p->process_->name().c_str() ) : p->process_->name() ) << "\n"
          << std::setw( wt ) << "" << p->process_->description();
     }
     else
       os << ( pretty ? boldify( "no process!" ) : "no process!" );
     os
       << "\n"
       << 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" << std::boolalpha << p->generation.treat << "\n"
+      << std::setw( wt ) << "Integrand treatment"
+      << ( pretty ? yesno( p->generation.treat ) : std::to_string( p->generation.treat ) ) << "\n"
       << std::setw( wt ) << "Verbosity level " << 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";
     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->type << "\n";
+      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() )
-        continue;
-      os << std::setw( wt ) << lim.first << lim.second << "\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() )
-        continue;
-      os << std::setw( wt ) << lim.first << lim.second << "\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() )
-            continue;
-          os << "   - " << std::setw( wt-5 ) << lim.first << lim.second << "\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( Integrator::Type::Vegas ), ncvg( 500000 ),
     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 );
     }
     {
       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( false ), ngen( 0 ), gen_print_every( 10000 ),
     num_threads( 2 ), num_points( 100 )
   {}
 }
diff --git a/CepGen/Core/ParametersList.cpp b/CepGen/Core/ParametersList.cpp
index f15c953..f94cc67 100644
--- a/CepGen/Core/ParametersList.cpp
+++ b/CepGen/Core/ParametersList.cpp
@@ -1,298 +1,298 @@
 #include "CepGen/Core/ParametersList.h"
 #include "CepGen/Core/Exception.h"
 
 namespace CepGen
 {
   ParametersList&
   ParametersList::operator+=( const ParametersList& oth )
   {
     param_values_.insert( oth.param_values_.begin(), oth.param_values_.end() );
     int_values_.insert( oth.int_values_.begin(), oth.int_values_.end() );
     dbl_values_.insert( oth.dbl_values_.begin(), oth.dbl_values_.end() );
     str_values_.insert( oth.str_values_.begin(), oth.str_values_.end() );
     vec_param_values_.insert( oth.vec_param_values_.begin(), oth.vec_param_values_.end() );
     vec_int_values_.insert( oth.vec_int_values_.begin(), oth.vec_int_values_.end() );
     vec_dbl_values_.insert( oth.vec_dbl_values_.begin(), oth.vec_dbl_values_.end() );
     vec_str_values_.insert( oth.vec_str_values_.begin(), oth.vec_str_values_.end() );
     return *this;
   }
 
   std::ostream&
   operator<<( std::ostream& os, const ParametersList& params )
   {
     for ( const auto& kv : params.int_values_ )
       os << "\n" << kv.first << ": int(" << kv.second << ")";
     for ( const auto& kv : params.dbl_values_ )
       os << "\n" << kv.first << ": double(" << kv.second << ")";
     for ( const auto& kv : params.str_values_ )
       os << "\n" << kv.first << ": string(" << kv.second << ")";
     for ( const auto& kv : params.param_values_ )
       os << "\n" << kv.first << ": param({" << kv.second << "\n})";
     for ( const auto& kv : params.vec_int_values_ ) {
       os << "\n" << kv.first << ": vint(";
       bool first = true;
       for ( const auto& v : kv.second ) {
         os << ( first ? "" : ", " ) << v;
         first = false;
       }
       os << ")";
     }
     for ( const auto& kv : params.vec_dbl_values_ ) {
       os << "\n" << kv.first << ": vdouble(";
       bool first = true;
       for ( const auto& v : kv.second ) {
         os << ( first ? "" : ", " ) << v;
         first = false;
       }
       os << ")";
     }
     for ( const auto& kv : params.vec_str_values_ ) {
       os << "\n" << kv.first << ": vstring(";
       bool first = true;
       for ( const auto& v : kv.second ) {
         os << ( first ? "" : ", " ) << v;
         first = false;
       }
       os << ")";
     }
     return os;
   }
 
   //------------------------------------------------------------------
   // default template (placeholders)
   //------------------------------------------------------------------
 
   template<typename T> T
   ParametersList::get( std::string key, T def ) const
   {
     throw CG_FATAL( "ParametersList" ) << "Invalid type retrieved for key=" << key << "!";
   }
 
   template<typename T> T&
   ParametersList::operator[]( std::string key )
   {
     throw CG_FATAL( "ParametersList" ) << "Invalid type retrieved for key=" << key << "!";
   }
 
   template<typename T> void
   ParametersList::set( std::string key, const T& value )
   {
     throw CG_FATAL( "ParametersList" ) << "Invalid type to be set for key=" << key << "!";
   }
 
   //------------------------------------------------------------------
   // sub-parameters-type attributes
   //------------------------------------------------------------------
 
   template<> ParametersList
   ParametersList::get<ParametersList>( std::string key, ParametersList def ) const
   {
     for ( const auto& kv : param_values_ )
       if ( kv.first.compare( key ) == 0 )
         return kv.second;
     CG_DEBUG( "ParametersList" ) << "Failed to retrieve parameter with key=" << key << ".";
     return def;
   }
 
   template<> ParametersList&
   ParametersList::operator[]<ParametersList>( std::string key )
   {
     for ( auto& kv : param_values_ )
       if ( kv.first.compare( key ) == 0 )
         return kv.second;
-    throw CG_FATAL( "ParametersList" ) << "Failed to retrieve parameter with key=" << key << ".";
+    return param_values_[key];
   }
 
   template<> void
   ParametersList::set<ParametersList>( std::string key, const ParametersList& value )
   {
     param_values_[key] = value;
   }
 
   template<> std::vector<ParametersList>
   ParametersList::get<std::vector<ParametersList> >( std::string key, std::vector<ParametersList> def ) const
   {
     for ( const auto& kv : vec_param_values_ )
       if ( kv.first.compare( key ) == 0 )
         return kv.second;
     CG_DEBUG( "ParametersList" ) << "Failed to retrieve parameter with key=" << key << ".";
     return def;
   }
 
   template<> std::vector<ParametersList>&
   ParametersList::operator[]<std::vector<ParametersList> >( std::string key )
   {
     for ( auto& kv : vec_param_values_ )
       if ( kv.first.compare( key ) == 0 )
         return kv.second;
-    throw CG_FATAL( "ParametersList" ) << "Failed to retrieve parameter with key=" << key << ".";
+    return vec_param_values_[key];
   }
 
   template<> void
   ParametersList::set<std::vector<ParametersList> >( std::string key, const std::vector<ParametersList>& value )
   {
     vec_param_values_[key] = value;
   }
 
   //------------------------------------------------------------------
   // integer-type attributes
   //------------------------------------------------------------------
 
   template<> int
   ParametersList::get<int>( std::string key, int def ) const
   {
     for ( const auto& kv : int_values_ )
       if ( kv.first.compare( key ) == 0 )
         return kv.second;
     CG_DEBUG( "ParametersList" ) << "Failed to retrieve parameter with key=" << key << ".";
     return def;
   }
 
   template<> int&
   ParametersList::operator[]<int>( std::string key )
   {
     for ( auto& kv : int_values_ )
       if ( kv.first.compare( key ) == 0 )
         return kv.second;
-    throw CG_FATAL( "ParametersList" ) << "Failed to retrieve parameter with key=" << key << ".";
+    return int_values_[key];
   }
 
   template<> void
   ParametersList::set<int>( std::string key, const int& value )
   {
     int_values_[key] = value;
   }
 
   template<> std::vector<int>
   ParametersList::get<std::vector<int> >( std::string key, std::vector<int> def ) const
   {
     for ( const auto& kv : vec_int_values_ )
       if ( kv.first.compare( key ) == 0 )
         return kv.second;
     CG_DEBUG( "ParametersList" ) << "Failed to retrieve parameter with key=" << key << ".";
     return def;
   }
 
   template<> std::vector<int>&
   ParametersList::operator[]<std::vector<int> >( std::string key )
   {
     for ( auto& kv : vec_int_values_ )
       if ( kv.first.compare( key ) == 0 )
         return kv.second;
-    throw CG_FATAL( "ParametersList" ) << "Failed to retrieve parameter with key=" << key << ".";
+    return vec_int_values_[key];
   }
 
   template<> void
   ParametersList::set<std::vector<int> >( std::string key, const std::vector<int>& value )
   {
     vec_int_values_[key] = value;
   }
 
   //------------------------------------------------------------------
   // floating point-type attributes
   //------------------------------------------------------------------
 
   template<> double
   ParametersList::get<double>( std::string key, double def ) const
   {
     for ( const auto& kv : dbl_values_ )
       if ( kv.first.compare( key ) == 0 )
         return kv.second;
     CG_DEBUG( "ParametersList" ) << "Failed to retrieve parameter with key=" << key << ".";
     return def;
   }
 
   template<> double&
   ParametersList::operator[]<double>( std::string key )
   {
     for ( auto& kv : dbl_values_ )
       if ( kv.first.compare( key ) == 0 )
         return kv.second;
-    throw CG_FATAL( "ParametersList" ) << "Failed to retrieve parameter with key=" << key << ".";
+    return dbl_values_[key];
   }
 
   template<> void
   ParametersList::set<double>( std::string key, const double& value )
   {
     dbl_values_[key] = value;
   }
 
   template<> std::vector<double>
   ParametersList::get<std::vector<double> >( std::string key, std::vector<double> def ) const
   {
     for ( const auto& kv : vec_dbl_values_ )
       if ( kv.first.compare( key ) == 0 )
         return kv.second;
     CG_DEBUG( "ParametersList" ) << "Failed to retrieve parameter with key=" << key << ".";
     return def;
   }
 
   template<> std::vector<double>&
   ParametersList::operator[]<std::vector<double> >( std::string key )
   {
     for ( auto& kv : vec_dbl_values_ )
       if ( kv.first.compare( key ) == 0 )
         return kv.second;
-    throw CG_FATAL( "ParametersList" ) << "Failed to retrieve parameter with key=" << key << ".";
+    return vec_dbl_values_[key];
   }
 
   template<> void
   ParametersList::set<std::vector<double> >( std::string key, const std::vector<double>& value )
   {
     vec_dbl_values_[key] = value;
   }
 
   //------------------------------------------------------------------
   // string-type attributes
   //------------------------------------------------------------------
 
   template<> std::string
   ParametersList::get<std::string>( std::string key, std::string def ) const
   {
     for ( const auto& kv : str_values_ )
       if ( kv.first.compare( key ) == 0 )
         return kv.second;
     CG_DEBUG( "ParametersList" ) << "Failed to retrieve parameter with key=" << key << ".";
     return def;
   }
 
   template<> std::string&
   ParametersList::operator[]<std::string>( std::string key )
   {
     for ( auto& kv : str_values_ )
       if ( kv.first.compare( key ) == 0 )
         return kv.second;
-    throw CG_FATAL( "ParametersList" ) << "Failed to retrieve parameter with key=" << key << ".";
+    return str_values_[key];
   }
 
   template<> void
   ParametersList::set<std::string>( std::string key, const std::string& value )
   {
     str_values_[key] = value;
   }
 
   template<> std::vector<std::string>
   ParametersList::get<std::vector<std::string> >( std::string key, std::vector<std::string> def ) const
   {
     for ( const auto& kv : vec_str_values_ )
       if ( kv.first.compare( key ) == 0 )
         return kv.second;
     CG_DEBUG( "ParametersList" ) << "Failed to retrieve parameter with key=" << key << ".";
     return def;
   }
 
   template<> std::vector<std::string>&
   ParametersList::operator[]<std::vector<std::string> >( std::string key )
   {
     for ( auto& kv : vec_str_values_ )
       if ( kv.first.compare( key ) == 0 )
         return kv.second;
-    throw CG_FATAL( "ParametersList" ) << "Failed to retrieve parameter with key=" << key << ".";
+    return vec_str_values_[key];
   }
 
   template<> void
   ParametersList::set<std::vector<std::string> >( std::string key, const std::vector<std::string>& value )
   {
     vec_str_values_[key] = value;
   }
 }
diff --git a/CepGen/Core/ParametersList.h b/CepGen/Core/ParametersList.h
index 01f46ad..5abb45e 100644
--- a/CepGen/Core/ParametersList.h
+++ b/CepGen/Core/ParametersList.h
@@ -1,68 +1,98 @@
 #ifndef CepGen_Core_ParametersList_h
 #define CepGen_Core_ParametersList_h
 
 #include <vector>
 #include <map>
 #include <unordered_map>
 #include <string>
 
 namespace CepGen
 {
+  /// Parameters container
   class ParametersList
   {
     private:
       template<typename T> struct default_arg
       {
         static T get() { return T(); }
       };
 
     public:
       ParametersList() = default;
       ~ParametersList() = default; // required for unique_ptr initialisation!
+      /// Get a parameter value
       template<typename T> T get( std::string key, T def = default_arg<T>::get() ) const;
+      /// Reference to a parameter value
       template<typename T> T& operator[]( std::string key );
+      /// Set a parameter value
       template<typename T> void set( std::string key, const T& value );
+      /// Concatenate two parameters containers
       ParametersList& operator+=( const ParametersList& oth );
 
+      /// Human-readable version of a parameters container
       friend std::ostream& operator<<( std::ostream& os, const ParametersList& );
 
     private:
       std::map<std::string,ParametersList> param_values_;
       std::unordered_map<std::string,int> int_values_;
       std::unordered_map<std::string,double> dbl_values_;
       std::unordered_map<std::string,std::string> str_values_;
       std::unordered_map<std::string,std::vector<ParametersList> > vec_param_values_;
       std::unordered_map<std::string,std::vector<int> > vec_int_values_;
       std::unordered_map<std::string,std::vector<double> > vec_dbl_values_;
       std::unordered_map<std::string,std::vector<std::string> > vec_str_values_;
   };
+  /// Get an integer parameter value
   template<> int ParametersList::get<int>( std::string key, int def ) const;
+  /// Reference to an integer parameter value
   template<> int& ParametersList::operator[]<int>( std::string key );
+  /// Set an integer parameter value
   template<> void ParametersList::set<int>( std::string key, const int& value );
+  /// Get a vector of integers parameter value
   template<> std::vector<int> ParametersList::get<std::vector<int> >( std::string key, std::vector<int> def ) const;
+  /// Reference to a vector of integers parameter value
   template<> std::vector<int>& ParametersList::operator[]<std::vector<int> >( std::string key );
+  /// Set a vector of integers parameter value
   template<> void ParametersList::set<std::vector<int> >( std::string key, const std::vector<int>& value );
 
+  /// Get a double floating point parameter value
   template<> double ParametersList::get<double>( std::string key, double def ) const;
+  /// Reference to a double floating point parameter value
   template<> double& ParametersList::operator[]<double>( std::string key );
+  /// Set a double floating point parameter value
   template<> void ParametersList::set<double>( std::string key, const double& value );
+  /// Get a vector of double floating point parameter value
   template<> std::vector<double> ParametersList::get<std::vector<double> >( std::string key, std::vector<double> def ) const;
+  /// Reference to a vector of double floating point parameter value
   template<> std::vector<double>& ParametersList::operator[]<std::vector<double> >( std::string key );
+  /// Set a vector of double floating point parameter value
   template<> void ParametersList::set<std::vector<double> >( std::string key, const std::vector<double>& value );
 
+  /// Get a string parameter value
   template<> std::string ParametersList::get<std::string>( std::string key, std::string def ) const;
+  /// Reference to a string parameter value
   template<> std::string& ParametersList::operator[]<std::string>( std::string key );
+  /// Set a string parameter value
   template<> void ParametersList::set<std::string>( std::string key, const std::string& value );
+  /// Get a vector of strings parameter value
   template<> std::vector<std::string> ParametersList::get<std::vector<std::string> >( std::string key, std::vector<std::string> def ) const;
+  /// Reference to a vector of strings parameter value
   template<> std::vector<std::string>& ParametersList::operator[]<std::vector<std::string> >( std::string key );
+  /// Set a vector of strings parameter value
   template<> void ParametersList::set<std::vector<std::string> >( std::string key, const std::vector<std::string>& value );
 
+  /// Get a parameters list parameter value
   template<> ParametersList ParametersList::get<ParametersList>( std::string key, ParametersList def ) const;
+  /// Reference to a parameters list parameter value
   template<> ParametersList& ParametersList::operator[]<ParametersList>( std::string key );
+  /// Set a parameters list parameter value
   template<> void ParametersList::set<ParametersList>( std::string key, const ParametersList& value );
+  /// Get a vector of parameters list parameter value
   template<> std::vector<ParametersList> ParametersList::get<std::vector<ParametersList> >( std::string key, std::vector<ParametersList> def ) const;
+  /// Reference to a vector of parameters list parameter value
   template<> std::vector<ParametersList>& ParametersList::operator[]<std::vector<ParametersList> >( std::string key );
+  /// Set a vector of parameters list parameter value
   template<> void ParametersList::set<std::vector<ParametersList> >( std::string key, const std::vector<ParametersList>& value );
 }
 
 #endif
diff --git a/CepGen/Core/utils.h b/CepGen/Core/utils.h
index 3a3bd46..dd7678d 100644
--- a/CepGen/Core/utils.h
+++ b/CepGen/Core/utils.h
@@ -1,31 +1,38 @@
 #ifndef CepGen_Core_utils_h
 #define CepGen_Core_utils_h
 
 #include <string>
 
 namespace CepGen
 {
+  /// Add a closing "s" when needed
   inline const char* s( unsigned short num ) { return ( num > 1 ) ? "s" : ""; }
-
   /// Format a string using a printf style format descriptor.
   std::string Form( const std::string fmt, ... );
-
+  /// Human-readable boolean printout
   inline const char* yesno( const bool& test ) { return ( test ) ? "\033[32;1myes\033[0m" : "\033[31;1mno\033[0m"; }
   //inline const char* boldify( const char* str ) { const std::string out = std::string( "\033[33;1m" ) + std::string( str ) + std::string( "\033[0m" ); return out.c_str(); }
+  /// Boldify a string for TTY-type output streams
   inline std::string boldify( const std::string& str ) { return Form( "\033[1m%s\033[0m", str.c_str() ); }
+  /// Boldify a string for TTY-type output streams
   inline std::string boldify( const char* str ) { return boldify( std::string( str ) ); }
+  /// Boldify a double floating point number for TTY-type output streams
   inline std::string boldify( const double& dbl ) { return boldify( Form("%.2f", dbl ) ); }
+  /// Boldify an integer for TTY-type output streams
   inline std::string boldify( const int& i ) { return boldify( Form("% d", i ) ); }
+  /// Boldify an unsigned integer for TTY-type output streams
   inline std::string boldify( const unsigned int& ui ) { return boldify( Form("%d", ui ) ); }
+  /// Boldify an unsigned long integer for TTY-type output streams
   inline std::string boldify( const unsigned long& ui ) { return boldify( Form("%lu", ui ) ); }
+  /// TTY-type enumeration of colours
   enum class Colour { gray = 30, red = 31, green = 32, yellow = 33, blue = 34, purple = 35 };
+  /// Colourise a string for TTY-type output streams
   inline std::string colourise( const std::string& str, const Colour& col ) { return Form( "\033[%d%s\033[0m", (int)col, str.c_str() ); }
-
+  /// Replace all occurences of a text by another
   size_t replace_all( std::string& str, const std::string& from, const std::string& to );
 }
 
 /// Provide a random number generated along a uniform distribution between 0 and 1
-//inline double drand() { srand (time(nullptr)); return static_cast<double>(rand())/RAND_MAX; }
 #define drand() static_cast<double>( rand()/RAND_MAX )
 
 #endif
diff --git a/CepGen/Event/Event.cpp b/CepGen/Event/Event.cpp
index eda5a35..bce4c2a 100644
--- a/CepGen/Event/Event.cpp
+++ b/CepGen/Event/Event.cpp
@@ -1,341 +1,339 @@
 #include "CepGen/Event/Event.h"
 #include "CepGen/Physics/PDG.h"
 #include "CepGen/Physics/HeavyIon.h"
 #include "CepGen/Core/Exception.h"
 #include "CepGen/Core/utils.h"
 
 #include <algorithm>
 #include <math.h>
 
 namespace CepGen
 {
   Event::Event() :
     num_hadronisation_trials( 0 ),
     time_generation( -1. ), time_total( -1. )
   {}
 
   Event::Event( const Event& rhs ) :
     num_hadronisation_trials( rhs.num_hadronisation_trials ),
     time_generation( rhs.time_generation ), time_total( rhs.time_total ),
     particles_( rhs.particles_ ),
     evtcontent_( rhs.evtcontent_ )
   {}
 
   void
   Event::clear()
   {
     particles_.clear();
     time_generation = -1.;
     time_total = -1.;
   }
 
   void
   Event::freeze()
   {
     //--- store a snapshot of the primordial event block
     if ( particles_.count( Particle::CentralSystem ) > 0 )
       evtcontent_.cs = particles_[Particle::CentralSystem].size();
     if ( particles_.count( Particle::OutgoingBeam1 ) > 0 )
       evtcontent_.op1 = particles_[Particle::OutgoingBeam1].size();
     if ( particles_.count( Particle::OutgoingBeam2 ) > 0 )
       evtcontent_.op2 = particles_[Particle::OutgoingBeam2].size();
   }
 
   void
   Event::restore()
   {
     //--- remove all particles after the primordial event block
     if ( particles_.count( Particle::CentralSystem ) > 0 )
       particles_[Particle::CentralSystem].resize( evtcontent_.cs );
     if ( particles_.count( Particle::OutgoingBeam1 ) > 0 )
       particles_[Particle::OutgoingBeam1].resize( evtcontent_.op1 );
     if ( particles_.count( Particle::OutgoingBeam2 ) > 0 )
       particles_[Particle::OutgoingBeam2].resize( evtcontent_.op2 );
   }
 
   double
   Event::cmEnergy() const
   {
     return CMEnergy( getOneByRole( Particle::IncomingBeam1 ), getOneByRole( Particle::IncomingBeam2 ) );
   }
 
   Particles&
   Event::getByRole( Particle::Role role )
   {
     //--- retrieve all particles with a given role
     return particles_[role];
   }
 
   const Particles&
   Event::getByRole( Particle::Role role ) const
   {
     if ( particles_.count( role ) == 0 )
       throw CG_FATAL( "Event" ) << "Failed to retrieve a particle with " << role << " role.";
     //--- retrieve all particles with a given role
     return particles_.at( role );
   }
 
   ParticlesIds
   Event::getIdsByRole( Particle::Role role ) const
   {
     ParticlesIds out;
     //--- retrieve all particles ids with a given role
     if ( particles_.count( role ) == 0 )
       return out;
 
     for ( const auto& part : particles_.at( role ) )
       out.insert( part.id() );
 
     return out;
   }
 
   Particle&
   Event::getOneByRole( Particle::Role role )
   {
     //--- retrieve the first particle a the given role
     Particles& parts_by_role = getByRole( role );
     if ( parts_by_role.size() == 0 )
       throw CG_FATAL( "Event" ) << "No particle retrieved with " << role << " role.";
     if ( parts_by_role.size() > 1 )
       throw CG_FATAL( "Event" ) << "More than one particle with " << role << " role: "
         << parts_by_role.size() << " particles.";
     return *parts_by_role.begin();
   }
 
   const Particle&
   Event::getOneByRole( Particle::Role role ) const
   {
     if ( particles_.count( role ) == 0 )
       throw CG_FATAL( "Event" ) << "Failed to retrieve a particle with " << role << " role.";
     //--- retrieve the first particle a the given role
     const Particles& parts_by_role = particles_.at( role );
     if ( parts_by_role.size() == 0 )
       throw CG_FATAL( "Event" ) << "No particle retrieved with " << role << " role.";
     if ( parts_by_role.size() > 1 )
       throw CG_FATAL( "Event" ) << "More than one particle with " << role << " role: "
         << parts_by_role.size() << " particles";
     return *parts_by_role.begin();
   }
 
   Particle&
   Event::operator[]( int id )
   {
     for ( auto& role_part : particles_ )
       for ( auto& part : role_part.second )
         if ( part.id() == id )
           return part;
 
     throw CG_FATAL( "Event" ) << "Failed to retrieve the particle with id=" << id << ".";
   }
 
   const Particle&
-  Event::getConstById( int id ) const
+  Event::at( int id ) const
   {
     for ( const auto& role_part : particles_ )
       for ( const auto& part : role_part.second )
         if ( part.id() == id )
           return part;
 
     throw CG_FATAL( "Event" ) << "Failed to retrieve the particle with id=" << id << ".";
   }
 
   Particles
   Event::getByIds( const ParticlesIds& ids ) const
   {
     Particles out;
     for ( const auto& id : ids )
-      out.emplace_back( getConstById( id ) );
+      out.emplace_back( at( id ) );
 
     return out;
   }
 
   Particles
   Event::mothers( const Particle& part )
   {
     return getByIds( part.mothers() );
   }
 
   Particles
   Event::daughters( const Particle& part )
   {
     return getByIds( part.daughters() );
   }
 
   ParticleRoles
   Event::roles() const
   {
     ParticleRoles out;
     ParticlesMap::const_iterator it, end = particles_.end();
     for ( it = particles_.begin(); it != end; it = particles_.upper_bound( it->first ) ) {
       out.emplace_back( it->first );
     }
     return out;
   }
 
   Particle&
   Event::addParticle( Particle& part, bool replace )
   {
     CG_DEBUG_LOOP( "Event" ) << "Particle with PDGid = " << part.integerPdgId() << " has role " << part.role();
     if ( part.role() <= 0 )
       throw CG_FATAL( "Event" ) << "Trying to add a particle with role=" << (int)part.role() << ".";
 
     //--- retrieve the list of particles with the same role
     Particles& part_with_same_role = getByRole( part.role() );
 
     //--- specify the id
     if ( part_with_same_role.empty() && part.id() < 0 ) part.setId( numParticles() ); // set the id if previously invalid/inexistent
     if ( !part_with_same_role.empty() ) {
       if ( replace ) part.setId( part_with_same_role[0].id() ); // set the previous id if replacing a particle
       else part.setId( numParticles() );
     }
 
     //--- add the particle to the collection
     if ( replace ) part_with_same_role = Particles( 1, part ); // generate a vector containing only this particle
     else part_with_same_role.emplace_back( part );
 
     return part_with_same_role.back();
   }
 
   Particle&
   Event::addParticle( Particle::Role role, bool replace )
   {
     Particle np( role, PDG::invalid );
     return addParticle( np, replace );
   }
 
   size_t
   Event::numParticles() const
   {
     size_t out = 0;
     for ( const auto& role_part : particles_ )
       out += role_part.second.size();
     return out;
   }
 
   const Particles
   Event::particles() const
   {
     Particles out;
     for ( const auto& role_part : particles_ )
       out.insert( out.end(), role_part.second.begin(), role_part.second.end() );
 
     std::sort( out.begin(), out.end() );
     return out;
   }
 
   const Particles
   Event::stableParticles() const
   {
     Particles out;
     for ( const auto& role_part : particles_ )
       for ( const auto& part : role_part.second )
         if ( (short)part.status() > 0 )
           out.emplace_back( part );
 
     std::sort( out.begin(), out.end() );
     return out;
   }
 
   void
   Event::checkKinematics() const
   {
     // check the kinematics through parentage
     for ( const auto& part : particles() ) {
       ParticlesIds daughters = part.daughters();
       if ( daughters.empty() )
         continue;
       Particle::Momentum ptot;
       for ( const auto& daugh : daughters ) {
-        const Particle& d = getConstById( daugh );
+        const Particle& d = at( daugh );
         const ParticlesIds mothers = d.mothers();
         ptot += d.momentum();
         if ( mothers.size() < 2 )
           continue;
-        for ( const auto& moth : mothers ) {
-          if ( moth == part.id() )
-            continue;
-          ptot -= getConstById( moth ).momentum();
-        }
+        for ( const auto& moth : mothers )
+          if ( moth != part.id() )
+            ptot -= at( moth ).momentum();
       }
       const double mass_diff = ( ptot-part.momentum() ).mass();
       if ( fabs( mass_diff ) > minimal_precision_ ) {
         dump();
         throw CG_FATAL( "Event" ) << "Error in momentum balance for particle " << part.id() << ": mdiff = " << mass_diff << ".";
       }
     }
   }
 
   void
   Event::dump( std::ostream& out, bool stable ) const
   {
     const Particles parts = ( stable ) ? stableParticles() : particles();
 
     std::ostringstream os;
 
     Particle::Momentum p_total;
     for ( const auto& part : parts ) {
       const ParticlesIds mothers = part.mothers();
       {
         std::ostringstream oss_pdg;
         if ( part.pdgId() == PDG::invalid && mothers.size() > 0 ) {
           for ( unsigned short i = 0; i < mothers.size(); ++i )
-            oss_pdg << ( i > 0 ? "/" : "" ) << getConstById( *std::next( mothers.begin(), i ) ).pdgId();
+            oss_pdg << ( i > 0 ? "/" : "" ) << at( *std::next( mothers.begin(), i ) ).pdgId();
           os << Form( "\n %2d\t\t%-10s", part.id(), oss_pdg.str().c_str() );
         }
         else {
           if ( (HeavyIon)part.pdgId() )
             oss_pdg << (HeavyIon)part.pdgId();
           else
             oss_pdg << part.pdgId();
           os << Form( "\n %2d\t%-+7d %-10s", part.id(), part.integerPdgId(), oss_pdg.str().c_str() );
         }
       }
       os << "\t";
       if ( part.charge() != 999. )
         os << Form( "%-.2f\t", part.charge() );
       else
         os << "\t";
       { std::ostringstream oss; oss << part.role(); os << Form( "%-8s %6d\t", oss.str().c_str(), part.status() ); }
       if ( !mothers.empty() ) {
         std::ostringstream oss;
         unsigned short i = 0;
         for ( const auto& moth : mothers ) {
           oss << ( i > 0 ? "+" : "" ) << moth;
           ++i;
         }
         os << Form( "%6s ", oss.str().c_str() );
       }
       else os << "       ";
       const Particle::Momentum mom = part.momentum();
       os << Form( "% 9.6e % 9.6e % 9.6e % 9.6e % 12.5f", mom.px(), mom.py(), mom.pz(), part.energy(), part.mass() );
 
       // discard non-primary, decayed particles
       if ( part.status() >= Particle::Status::Undefined ) {
         const int sign = ( part.status() == Particle::Status::Undefined )
           ? -1
           : +1;
         p_total += sign*mom;
       }
     }
     //--- set a threshold to the computation precision
     p_total.truncate();
     //
     CG_INFO( "Event" )
      << Form( "Dump of event content:\n"
               " Id\tPDG id\tName\t\tCharge\tRole\t Status\tMother\tpx            py            pz            E      \t M         \n"
               " --\t------\t----\t\t------\t----\t ------\t------\t----GeV/c---  ----GeV/c---  ----GeV/c---  ----GeV/c---\t --GeV/c²--"
               "%s\n"
               " ----------------------------------------------------------------------------------------------------------------------------------\n"
               "\t\t\t\t\t\t\tBalance% 9.6e % 9.6e % 9.6e % 9.6e", os.str().c_str(), p_total.px(), p_total.py(), p_total.pz(), p_total.energy() );
   }
 
   //------------------------------------------------------------------------------------------------
 
   Event::NumParticles::NumParticles() :
     cs( 0 ), op1( 0 ), op2( 0 )
   {}
 
   Event::NumParticles::NumParticles( const NumParticles& np ) :
     cs( np.cs ), op1( np.op1 ), op2( np.op2 )
   {}
 }
diff --git a/CepGen/Event/Event.h b/CepGen/Event/Event.h
index d5193bf..599b592 100644
--- a/CepGen/Event/Event.h
+++ b/CepGen/Event/Event.h
@@ -1,124 +1,119 @@
 #ifndef CepGen_Event_Event_h
 #define CepGen_Event_Event_h
 
 #include "CepGen/Event/Particle.h"
 #include "CepGen/Core/Logger.h"
 
 namespace CepGen
 {
   /**
    * Class containing all the information on the in- and outgoing particles' kinematics
    * \brief Kinematic information on the particles in the event
    */
   class Event {
     public:
       Event();
       Event( const Event& );
       /// Empty the whole event content
       void clear();
       /// Initialize an "empty" event collection
       void freeze();
       /// Restore the event to its "empty" state
       void restore();
 
       /// Dump all the known information on every Particle object contained in this Event container in the output stream
       /// \param[out] os Output stream where to dump the information
       /// \param[in] stable_ Do we only show the stable particles in this event?
       void dump( std::ostream& os = *Logger::get().output, bool stable_ = false ) const;
-
+      /// Incoming beams centre-of-mass energy, in GeV
       double cmEnergy() const;
 
       //----- particles adders
 
-      /// Set the information on one particle in the process
+      /// \brief Set the information on one particle in the process
       /// \param[in] part The Particle object to insert or modify in the event
       /// \param[in] replace Do we replace the particle if already present in the event or do we append another particle with the same role ?
       Particle& addParticle( Particle& part, bool replace = false );
       /// \brief Create a new particle in the event, with no kinematic information but the role it has to play in the process
       /// \param[in] role The role the particle will play in the process
       /// \param[in] replace Do we replace the particle if already present in the event or do we append another particle with the same role ?
       Particle& addParticle( Particle::Role role, bool replace = false );
 
       //----- particles retrievers
 
       /// Number of particles in the event
       size_t numParticles() const;
-      /// \brief Vector of all particles in the event
+      /// Vector of all particles in the event
       const Particles particles() const;
-      /// \brief Vector of all stable particles in the event
+      /// Vector of all stable particles in the event
       const Particles stableParticles() const;
-      /**
-       * Returns the list of Particle objects corresponding to a certain role in the process kinematics
-       * \brief Gets a list of particles by their role in the event
+      /** Get a list of Particle objects corresponding to a certain role in the process kinematics
        * \param[in] role The role the particles have to play in the process
        * \return A vector of references to the requested Particle objects
        */
       Particles& getByRole( Particle::Role role );
+      /// Get a list of constant Particle objects corresponding to a certain role in the process kinematics
       const Particles& getByRole( Particle::Role role ) const;
+      /// Get a list of particle identifiers in Event corresponding to a certain role in the process kinematics
       ParticlesIds getIdsByRole( Particle::Role role ) const;
-      /**
-       * Returns the first Particle object in the particles list whose role corresponds to the given argument
+      /** \brief Get the first Particle object in the particles list whose role corresponds to the given argument
        * \param[in] role The role the particle has to play in the event
        * \return A Particle object corresponding to the first particle with the role
        */
       Particle& getOneByRole( Particle::Role role );
       const Particle& getOneByRole( Particle::Role role ) const;
-      /**
-       * Returns the reference to the Particle object corresponding to a unique identifier in the event
-       * \brief Gets one particle by its unique identifier in the event
-       * \param[in] id_ The unique identifier to this particle in the event
+      /** \brief Get the reference to the Particle object corresponding to a unique identifier in the event
+       * \param[in] id The unique identifier to this particle in the event
        * \return A reference to the requested Particle object
        */
-      Particle& operator[]( int id_ );
-      /// Get a const Particle object using its unique identifier
-      /// \param[in] id_ Unique identifier of the particle in the event
-      /// \return Constant object to be retrieved
-      const Particle& getConstById( int id_ ) const;
-      /**
-       * Returns the references to the Particle objects corresponding to the unique identifiers in the event
-       * \brief Gets a vector of particles by their unique identifier in the event
+      Particle& operator[]( int id );
+      /** \brief Get a const Particle object using its unique identifier
+       * \param[in] id Unique identifier of the particle in the event
+       * \return Constant object to be retrieved
+       */
+      const Particle& at( int id ) const;
+      /** \brief Get references to the Particle objects corresponding to the unique identifiers in the event
        * \param[in] ids_ The unique identifiers to the particles to be selected in the event
        * \return A vector of references to the requested Particle objects
        */
       Particles getByIds( const ParticlesIds& ids_ ) const;
-      /**
-       * Returns the list of mother particles of any given Particle object in this event
+      /** \brief Get the list of mother particles of any given Particle object in this event
        * \param[in] part The reference to the Particle object from which we want to extract the mother particles
        * \return A list of parenting Particle object
        */
 
       //----- general particles information retriever
 
       Particles mothers( const Particle& part );
       /// Get a vector containing all the daughters from a particle
       /// \param[in] part The particle for which the daughter particles have to be retrieved
       /// \return Vector of Particle objects containing all the daughters' kinematic information
       Particles daughters( const Particle& part );
       /// Get a list of roles for the given event (really process-dependant for the central system)
       /// \return Vector of integers corresponding to all the roles the particles can play in the event
       ParticleRoles roles() const;
 
       /// Number of trials before the event was "correctly" hadronised
       unsigned short num_hadronisation_trials;
       /// Time needed to generate the event at parton level (in seconds)
       float time_generation;
       /// Time needed to generate the hadronised (if needed) event (in seconds)
       float time_total;
 
     private:
       static constexpr double minimal_precision_ = 1.e-10;
       /// Check if the event kinematics is properly defined
       void checkKinematics() const;
       /// List of particles in the event, mapped to their role in the process
       ParticlesMap particles_;
       /// Last particle in an "empty" event
       struct NumParticles {
         NumParticles();
         NumParticles( const NumParticles& np );
         unsigned short cs, op1, op2;
       };
       NumParticles evtcontent_;
   };
 }
 
 #endif
diff --git a/CepGen/Event/Particle.cpp b/CepGen/Event/Particle.cpp
index 0362063..40a8306 100644
--- a/CepGen/Event/Particle.cpp
+++ b/CepGen/Event/Particle.cpp
@@ -1,259 +1,264 @@
 #include "CepGen/Event/Particle.h"
 #include "CepGen/Physics/PDG.h"
 #include "CepGen/Core/Exception.h"
 #include "CepGen/Core/utils.h"
 #include "CepGen/Physics/Constants.h"
 
 namespace CepGen
 {
   Particle::Particle() :
     id_( -1 ), charge_sign_( 1 ),
     mass_( -1. ), helicity_( 0. ),
     role_( UnknownRole ), status_( Status::Undefined ), pdg_id_( PDG::invalid )
   {}
 
   Particle::Particle( Role role, PDG pdgId, Status st ) :
     id_( -1 ), charge_sign_( 1 ),
     mass_( -1. ), helicity_( 0. ),
     role_( role ), status_( st ), pdg_id_( pdgId )
   {
-    if ( pdg_id_ != PDG::invalid ) {
+    if ( pdg_id_ != PDG::invalid )
       computeMass();
-    }
   }
 
   Particle::Particle( const Particle& part ) :
     id_( part.id_ ), charge_sign_( part.charge_sign_ ),
     momentum_( part.momentum_ ), mass_( part.mass_ ), helicity_( part.helicity_ ),
     role_( part.role_ ), status_( part.status_ ),
     mothers_( part.mothers_ ), daughters_( part.daughters_ ),
     pdg_id_( part.pdg_id_ )
   {}
 
   bool
   Particle::operator<( const Particle& rhs ) const
   {
-    return ( id_ >= 0 && rhs.id_ > 0 && id_ < rhs.id_ );
+    return id_ >= 0
+        && rhs.id_ > 0
+        && id_ < rhs.id_;
   }
 
   double
   Particle::thetaToEta( double theta )
   {
-    return -log( tan( 0.5 * theta * M_PI/180. ) );
+    return -log( tan( 0.5*theta*M_PI/180. ) );
   }
 
   double
   Particle::etaToTheta( double eta )
   {
-    return 2.*atan( exp( -eta ) )*180. / M_PI;
+    return 2.*atan( exp( -eta ) )*180.*M_1_PI;
   }
 
   bool
   Particle::valid()
   {
-    if ( pdg_id_ == PDG::invalid ) return false;
-    if ( momentum_.p() == 0. && mass() == 0. ) return false;
+    if ( pdg_id_ == PDG::invalid )
+      return false;
+    if ( momentum_.p() == 0. && mass_ == 0. )
+      return false;
     return true;
   }
 
   void
   Particle::computeMass( bool off_shell )
   {
     if ( !off_shell && pdg_id_ != PDG::invalid ) { // retrieve the mass from the on-shell particle's properties
       mass_ = ParticleProperties::mass( pdg_id_ );
     }
     else if ( momentum_.energy() >= 0. ) {
       mass_ = sqrt( energy2() - momentum_.p2() );
     }
 
     //--- finish by setting the energy accordingly
     if ( momentum_.energy() < 0. ) { // invalid energy
       momentum_.setEnergy( sqrt( momentum_.p2() + mass2() ) );
     }
   }
 
   void
   Particle::setMass( double m )
   {
-    if ( m >= 0. ) mass_ = m;
-    else computeMass();
+    if ( m >= 0. )
+      mass_ = m;
+    else
+      computeMass();
   }
 
   void
   Particle::addMother( Particle& part )
   {
     mothers_.insert( part.id() );
 
     CG_DEBUG_LOOP( "Particle" )
       <<  "Particle " << id() << " (pdgId=" << part.integerPdgId() << ") "
       << "is the new mother of " << id_ << " (pdgId=" << (int)pdg_id_ << ").";
 
     part.addDaughter( *this );
   }
 
   void
   Particle::addDaughter( Particle& part )
   {
     const auto ret = daughters_.insert( part.id() );
 
     if ( CG_EXCEPT_MATCH( "Particle", debugInsideLoop ) ) {
       std::ostringstream os;
       for ( const auto& daugh : daughters_ )
         os << Form( "\n\t * id=%d", daugh );
       CG_DEBUG_LOOP( "Particle" )
         << "Particle " << role_ << " (pdgId=" << (int)pdg_id_ << ") "
         << "has now " << daughters_.size() << " daughter(s):"
         << os.str();
     }
 
     if ( ret.second ) {
       CG_DEBUG_LOOP( "Particle" )
         << "Particle " << part.role() << " (pdgId=" << part.integerPdgId() << ") "
         << "is a new daughter of " << role_ << " (pdgId=" << (int)pdg_id_ << "%4d).";
 
       if ( part.mothers().find( id_ ) == part.mothers().end() )
         part.addMother( *this );
     }
   }
 
   void
   Particle::setMomentum( const Momentum& mom, bool offshell )
   {
     momentum_ = mom;
-    if ( !offshell && mom.mass() > 0. ) mass_ = momentum_.mass();
-    else computeMass();
-  }
-
-  void
-  Particle::setMomentum( double px, double py, double pz )
-  {
-    momentum_.setP( px, py, pz );
-    setEnergy();
+    if ( !offshell && mom.mass() > 0. )
+      mass_ = momentum_.mass();
+    else
+      computeMass();
   }
 
   void
   Particle::setMomentum( double px, double py, double pz, double e )
   {
-    setMomentum( px, py, pz );
-    if ( fabs( e-momentum_.energy() )>1.e-6 ) { // more than 1 eV difference
+    momentum_.setP( px, py, pz );
+    setEnergy( e );
+    if ( fabs( e-momentum_.energy() ) > 1.e-6 ) // more than 1 eV difference
       CG_ERROR( Form( "Energy difference: %.5e", e-momentum_.energy() ) );
-      return;
-    }
   }
 
   double
   Particle::energy() const
   {
-    return ( momentum_.energy() < 0. ) ? sqrt( mass2()+momentum_.p2() ) : momentum_.energy();
+    return ( momentum_.energy() < 0.
+      ? std::hypot( mass_, momentum_.p() )
+      : momentum_.energy() );
   }
 
   void
   Particle::setEnergy( double e )
   {
-    if ( e < 0. && mass_ >= 0. ) e = sqrt( mass2()+momentum_.p2() );
+    if ( e < 0. && mass_ >= 0. )
+      e = std::hypot( mass_, momentum_.p() );
     momentum_.setEnergy( e );
   }
 
   void
   Particle::setPdgId( short pdg )
   {
     pdg_id_ = (PDG)abs( pdg );
     switch ( pdg_id_ ) {
       case PDG::electron: case PDG::muon: case PDG::tau:
         charge_sign_ = -pdg/abs( pdg ); break;
       default:
         charge_sign_ = pdg/abs( pdg ); break;
     }
   }
 
   void
   Particle::setPdgId( const PDG& pdg, short ch )
   {
     pdg_id_ = pdg;
     switch ( pdg_id_ ) {
       case PDG::electron: case PDG::muon: case PDG::tau:
         charge_sign_ = -ch; break;
       default:
         charge_sign_ = ch; break;
     }
   }
 
   int
   Particle::integerPdgId() const
   {
     const float ch = ParticleProperties::charge( pdg_id_ );
-    if ( ch == 0 ) return static_cast<int>( pdg_id_ );
+    if ( ch == 0 )
+      return static_cast<int>( pdg_id_ );
     return static_cast<int>( pdg_id_ ) * charge_sign_ * ( ch/fabs( ch ) );
   }
 
   void
   Particle::dump() const
   {
     std::ostringstream osm, osd;
     if ( !primary() ) {
       osm << ": mother(s): ";
       unsigned short i = 0;
       for ( const auto& moth : mothers_ ) {
         osm << ( i > 0 ? ", " : "" ) << moth;
         ++i;
       }
     }
     const ParticlesIds daughters_list = daughters();
     if ( daughters_list.size() > 0 ) {
       osd << ": id = ";
       unsigned short i = 0;
       for ( const auto& daugh : daughters_list ) {
         osm << ( i > 0 ? ", " : "" ) << daugh;
         ++i;
       }
     }
     CG_INFO( "Particle" )
       << "Dumping a particle with id=" << id_ << ", role=" << role_ << ", status=" << (int)status_ << "\n\t"
-      << "Particle id: " << integerPdgId() << " (" << pdg_id_ << "), mass = " << mass() << " GeV\n\t"
+      << "Particle id: " << integerPdgId() << " (" << pdg_id_ << "), mass = " << mass_ << " GeV\n\t"
       << "Momentum: " << momentum_ << " GeV\t" << "(|P| = p = " << momentum_.p() << " GeV)\n\t"
       << " p⟂ = " << momentum_.pt() << " GeV, eta = " << momentum_.eta() << ", phi = " << momentum_.phi() << "\n\t"
       << "Primary? " << yesno( primary() ) << osm.str() << "\n\t"
       << numDaughters() << " daughter(s)" << osd.str();
   }
 
   double
   Particle::etaToY( double eta_, double m_, double pt_ )
   {
-    const double mt = m_*m_ + pt_*pt_;
-    return asinh( sqrt( ( ( ( mt*mt-m_*m_ )*cosh( 2.*eta_ ) + m_*m_ )/ mt*mt - 1. ) / 2. ) );
+    const double m2 = m_*m_, mt = std::hypot( m_, pt_ );
+    return asinh( sqrt( ( ( mt*mt-m2 )*cosh( 2.*eta_ )+m2 )/ mt*mt - 1. )*M_SQRT1_2 );
   }
 
   std::ostream&
   operator<<( std::ostream& os, const Particle::Role& rl )
   {
     switch ( rl ) {
       case Particle::UnknownRole:   return os << "unknown";
-      case Particle::IncomingBeam1: return os << "in.b.1";
-      case Particle::IncomingBeam2: return os << "in.b.2";
-      case Particle::OutgoingBeam1: return os << "out.b.1";
-      case Particle::OutgoingBeam2: return os << "out.b.2";
-      case Particle::Parton1:       return os << "parton1";
-      case Particle::Parton2:       return os << "parton2";
-      case Particle::Parton3:       return os << "parton3";
-      case Particle::Intermediate:  return os << "hard.pr.";
+      case Particle::IncomingBeam1: return os << "i.beam 1";
+      case Particle::IncomingBeam2: return os << "i.beam 2";
+      case Particle::OutgoingBeam1: return os << "o.beam 1";
+      case Particle::OutgoingBeam2: return os << "o.beam 2";
+      case Particle::Parton1:       return os << "parton 1";
+      case Particle::Parton2:       return os << "parton 2";
+      case Particle::Parton3:       return os << "parton 3";
+      case Particle::Intermediate:  return os << "hard pr.";
       case Particle::CentralSystem: return os << "central";
     }
     return os;
   }
 
   double
   CMEnergy( const Particle& p1, const Particle& p2 )
   {
-    if ( p1.mass()*p2.mass() < 0. ) return 0.;
-    if ( p1.energy()*p2.energy() < 0. ) return 0.;
+    if ( p1.mass()*p2.mass() < 0.
+      || p1.energy()*p2.energy() < 0. )
+      return 0.;
     return sqrt( p1.mass2()+p2.mass2() + 2.*p1.energy()*p2.energy() - 2.*( p1.momentum()*p2.momentum() ) );
   }
 
   double
   CMEnergy( const Particle::Momentum& m1, const Particle::Momentum& m2 )
   {
-    if ( m1.mass()*m2.mass() < 0. ) return 0.;
-    if ( m1.energy()*m2.energy() < 0. ) return 0.;
+    if ( m1.mass()*m2.mass() < 0.
+      || m1.energy()*m2.energy() < 0. )
+      return 0.;
     return sqrt( m1.mass2()+m2.mass2() + 2.*m1.energy()*m2.energy() - 2.*( m1*m2 ) );
   }
 }
diff --git a/CepGen/Event/Particle.h b/CepGen/Event/Particle.h
index 7de41a8..d76ec6b 100644
--- a/CepGen/Event/Particle.h
+++ b/CepGen/Event/Particle.h
@@ -1,368 +1,346 @@
 #ifndef CepGen_Event_Particle_h
 #define CepGen_Event_Particle_h
 
 #include "CepGen/Physics/ParticleProperties.h"
 
 #include <set>
 #include <map>
 #include <vector>
 
 namespace CepGen
 {
 
   /// A set of integer-type particle identifiers
   typedef std::set<int> ParticlesIds;
 
   /// Kinematic information for one particle
   class Particle {
     public:
       /// Internal status code for a particle
       enum class Status {
         PrimordialIncoming = -9,
         DebugResonance = -5,
         Resonance = -4,
         Fragmented = -3,
         Propagator = -2,
         Incoming = -1,
         Undefined = 0,
         FinalState = 1,
         Undecayed = 2, Unfragmented = 3
       };
       /// Role of the particle in the process
       enum Role {
         UnknownRole = -1,
         IncomingBeam1 = 1, IncomingBeam2 = 2,
         OutgoingBeam1 = 3, OutgoingBeam2 = 5,
         CentralSystem = 6,
         Intermediate = 4,
         Parton1 = 41, Parton2 = 42, Parton3 = 43
       };
       /**
        * Container for a particle's 4-momentum, along with useful methods to ease the development of any matrix element level generator
        * \brief 4-momentum for a particle
        * \date Dec 2015
        * \author Laurent Forthomme <laurent.forthomme@cern.ch>
        */
       class Momentum {
         public:
           /// Build a 4-momentum at rest with an invalid energy (no mass information known)
           Momentum();
           /// Build a 4-momentum using its 3-momentum coordinates and its energy
           Momentum( double x, double y, double z, double t = -1. );
           /// Build a 4-momentum using its 3-momentum coordinates and its energy
           Momentum( double* p );
 
           // --- static definitions
 
           /// Build a 3-momentum from its three pseudo-cylindric coordinates
           static Momentum fromPtEtaPhi( double pt, double eta, double phi, double e = -1. );
           /// Build a 4-momentum from its scalar momentum, and its polar and azimuthal angles
           static Momentum fromPThetaPhi( double p, double theta, double phi, double e = -1. );
           /// Build a 4-momentum from its four momentum and energy coordinates
           static Momentum fromPxPyPzE( double px, double py, double pz, double e );
+          /// Build a 4-momentum from its transverse momentum, rapidity and mass
           static Momentum fromPxPyYM( double px, double py, double rap, double m );
 
           // --- vector and scalar operators
 
           /// Scalar product of the 3-momentum with another 3-momentum
           double threeProduct( const Momentum& ) const;
           /// Scalar product of the 4-momentum with another 4-momentum
           double fourProduct( const Momentum& ) const;
           /// Vector product of the 3-momentum with another 3-momentum
           double crossProduct( const Momentum& ) const;
           /// Add a 4-momentum through a 4-vector sum
           Momentum& operator+=( const Momentum& );
           /// Subtract a 4-momentum through a 4-vector sum
           Momentum& operator-=( const Momentum& );
           /// Scalar product of the 3-momentum with another 3-momentum
           double operator*=( const Momentum& );
           /// Multiply all 4-momentum coordinates by a scalar
           Momentum& operator*=( double c );
           /// Equality operator
           bool operator==( const Momentum& ) const;
           /// Human-readable format for a particle's momentum
           friend std::ostream& operator<<( std::ostream& os, const Particle::Momentum& mom );
 
           Momentum& betaGammaBoost( double gamma, double betagamma );
           /// Forward Lorentz boost
           Momentum& lorentzBoost( const Particle::Momentum& p );
 
           // --- setters and getters
 
           /// Set all the components of the 4-momentum (in GeV)
           void setP( double px, double py, double pz, double e );
           /// Set all the components of the 3-momentum (in GeV)
           void setP( double px, double py, double pz );
           /// Set the energy (in GeV)
           inline void setEnergy( double e ) { energy_ = e; }
           /// Compute the energy from the mass
           inline void setMass( double m ) { setMass2( m*m ); }
           /// Compute the energy from the mass
           void setMass2( double m2 );
           /// Get one component of the 4-momentum (in GeV)
           double operator[]( const unsigned int i ) const;
           /// Get one component of the 4-momentum (in GeV)
           double& operator[]( const unsigned int i );
           /// Momentum along the \f$x\f$-axis (in GeV)
           inline double px() const { return px_; }
           /// Momentum along the \f$y\f$-axis (in GeV)
           inline double py() const { return py_; }
           /// Longitudinal momentum (in GeV)
           inline double pz() const { return pz_; }
           /// Transverse momentum (in GeV)
           double pt() const;
-          /// Squared transverse momentum (in GeV\f$^\textrm{2}\f$)
+          /// Squared transverse momentum (in GeV\f${}^2\f$)
           double pt2() const;
           /// 4-vector of double precision floats (in GeV)
           const std::vector<double> pVector() const;
           /// 3-momentum norm (in GeV)
           inline double p() const { return p_; }
-          /// Squared 3-momentum norm (in GeV\f$^\textrm{2}\f$)
+          /// Squared 3-momentum norm (in GeV\f${}^2\f$)
           inline double p2() const { return p_*p_; }
           /// Energy (in GeV)
           inline double energy() const { return energy_; }
-          /// Squared energy (in GeV^2)
+          /// Squared energy (in GeV\f${}^2\f$)
           inline double energy2() const { return energy_*energy_; }
-          /// Squared mass (in GeV^2) as computed from its energy and momentum
+          /// Squared mass (in GeV\f${}^2\f$) as computed from its energy and momentum
           inline double mass2() const { return energy2()-p2(); }
           /// Mass (in GeV) as computed from its energy and momentum
           /// \note Returns \f$-\sqrt{|E^2-\mathbf{p}^2|}<0\f$ if \f$\mathbf{p}^2>E^2\f$
           double mass() const;
           /// Polar angle (angle with respect to the longitudinal direction)
           double theta() const;
           /// Azimutal angle (angle in the transverse plane)
           double phi() const;
           /// Pseudo-rapidity
           double eta() const;
           /// Rapidity
           double rapidity() const;
           void truncate( double tolerance = 1.e-10 );
           /// Rotate the transverse components by an angle phi (and reflect the y coordinate)
           Momentum& rotatePhi( double phi, double sign );
           /// Rotate the particle's momentum by a polar/azimuthal angle
           Momentum& rotateThetaPhi( double theta_, double phi_ );
           /// Apply a \f$ z\rightarrow -z\f$ transformation
           inline Momentum& mirrorZ() { pz_ = -pz_; return *this; }
         private:
           /// Compute the 3-momentum's norm
           void computeP();
           /// Momentum along the \f$x\f$-axis
           double px_;
           /// Momentum along the \f$y\f$-axis
           double py_;
           /// Momentum along the \f$z\f$-axis
           double pz_;
           /// 3-momentum's norm (in GeV/c)
           double p_;
           /// Energy (in GeV)
           double energy_;
       };
       /// Human-readable format for a particle's PDG code
       friend std::ostream& operator<<( std::ostream& os, const PDG& pc );
       /// Human-readable format for a particle's role in the event
       friend std::ostream& operator<<( std::ostream& os, const Particle::Role& rl );
       /// Compute the 4-vector sum of two 4-momenta
       friend Particle::Momentum operator+( const Particle::Momentum& mom1, const Particle::Momentum& mom2 );
       /// Compute the 4-vector difference of two 4-momenta
       friend Particle::Momentum operator-( const Particle::Momentum& mom1, const Particle::Momentum& mom2 );
       /// Compute the inverse per-coordinate 4-vector
       friend Particle::Momentum operator-( const Particle::Momentum& mom );
       /// Scalar product of two 3-momenta
       friend double operator*( const Particle::Momentum& mom1, const Particle::Momentum& mom2 );
       /// Multiply all components of a 4-momentum by a scalar
       friend Particle::Momentum operator*( const Particle::Momentum& mom, double c );
       /// Multiply all components of a 4-momentum by a scalar
       friend Particle::Momentum operator*( double c, const Particle::Momentum& mom );
 
       //----- static getters
 
       /// Convert a polar angle to a pseudo-rapidity
       static double thetaToEta( double theta );
       /// Convert a pseudo-rapidity to a polar angle
       static double etaToTheta( double eta );
       /// Convert a pseudo-rapidity to a rapidity
       static double etaToY( double eta_, double m_, double pt_ );
 
       Particle();
       /// Build using the role of the particle in the process and its PDG id
       /// \param[in] pdgId PDG identifier
       /// \param[in] role Role of the particle in the process
       /// \param[in] st Current status
       Particle( Role role, PDG pdgId, Status st = Status::Undefined );
       /// Copy constructor
       Particle( const Particle& );
       inline ~Particle() {}
       /// Comparison operator (from unique identifier)
       bool operator<( const Particle& rhs ) const;
       /// Comparison operator (from their reference's unique identifier)
       //bool operator<( Particle *rhs ) const { return ( id < rhs->id ); }
 
       // --- general particle properties
 
       /// Unique identifier (in a Event object context)
       int id() const { return id_; }
       //void setId( int id ) { id_ = id; }
       /// Set the particle unique identifier in an event
       void setId( int id ) { id_ = id; }
       /// Electric charge (given as a float number, for the quarks and bound states)
       float charge() const { return charge_sign_ * ParticleProperties::charge( pdg_id_ ); }
       /// Set the electric charge sign (+-1 for charged or 0 for neutral particles)
       void setChargeSign( int sign ) { charge_sign_ = sign; }
       /// Role in the considered process
       Role role() const { return role_; }
       /// Set the particle role in the process
       void setRole( const Role& role ) { role_ = role; }
       /**
        * Codes 1-10 correspond to currently existing partons/particles, and larger codes contain partons/particles which no longer exist, or other kinds of event information
        * \brief Particle status
        */
       Status status() const { return status_; }
       /// Set the particle decay/stability status
       void setStatus( Status status ) { status_ = status; }
 
       /// Set the PDG identifier (along with the particle's electric charge)
       /// \param[in] pdg PDG identifier
       /// \param[in] ch Electric charge (0, 1, or -1)
       void setPdgId( const PDG& pdg, short ch = 0 );
       /// Set the PDG identifier (along with the particle's electric charge)
       /// \param[in] pdg_id PDG identifier (incl. electric charge in e)
       void setPdgId( short pdg_id );
       /// Retrieve the objectified PDG identifier
       inline PDG pdgId() const { return pdg_id_; }
       /// Retrieve the integer value of the PDG identifier
       int integerPdgId() const;
       /// Particle's helicity
       float helicity() const { return helicity_; }
       /// Set the helicity of the particle
       void setHelicity( float heli ) { helicity_ = heli; }
-      /**
-       * Gets the particle's mass in \f$\textrm{GeV}/c^{2}\f$.
-       * \brief Gets the particle's mass
-       * \return The particle's mass
-       */
+      /// Particle mass in GeV/c\f${}^2\f$
+      /// \return Particle's mass
       inline double mass() const { return mass_; };
-      /**
-       * Set the mass of the particle in \f$\textrm{GeV}/c^{2}\f$ while ensuring that the kinematics is properly set (the mass is set according to the energy and the momentum in priority)
-       * \brief Compute the particle's mass in \f$\textrm{GeV}/c^{2}\f$
-       */
+      /// Compute the particle mass
+      /// \param[in] off_shell Allow the particle to be produced off-shell?
+      /// \note This method ensures that the kinematics is properly set (the mass is set according to the energy and the momentum in priority)
       void computeMass( bool off_shell = false );
-      /**
-       * Set the mass of the particle in \f$\textrm{GeV}/c^{2}\f$ according to a value given as an argument. This method ensures that the kinematics is properly set (the mass is set according to the energy and the momentum in priority)
-       * \param m The mass in \f$\textrm{GeV}/c^{2}\f$ to set
-       * \brief Set the particle's mass in \f$\textrm{GeV}/c^{2}\f$
-       */
+      /// Set the particle mass, in GeV/c\f${}^2\f$
+      /// \param m Mass in GeV/c\f${}^2\f$
+      /// \note This method ensures that the kinematics is properly set (the mass is set according to the energy and the momentum in priority)
       void setMass( double m = -1. );
-      /// Get the particle's squared mass (in \f$\textrm{GeV}^\textrm{2}\f$)
+      /// Particle squared mass, in GeV\f${}^2\f$/c\f${}^4\f$
       inline double mass2() const { return mass_*mass_; };
       /// Retrieve the momentum object associated with this particle
       inline Momentum& momentum() { return momentum_; }
       /// Retrieve the momentum object associated with this particle
       inline Momentum momentum() const { return momentum_; }
       /// Associate a momentum object to this particle
       void setMomentum( const Momentum& mom, bool offshell = false );
       /**
-       * \brief Set the 3-momentum associated to the particle
-       * \param[in] px Momentum along the \f$x\f$-axis, in \f$\textrm{GeV}/c\f$
-       * \param[in] py Momentum along the \f$y\f$-axis, in \f$\textrm{GeV}/c\f$
-       * \param[in] pz Momentum along the \f$z\f$-axis, in \f$\textrm{GeV}/c\f$
-       */
-      void setMomentum( double px, double py, double pz );
-      /**
-       * \brief Set the 4-momentum associated to the particle
-       * \param[in] px Momentum along the \f$x\f$-axis, in \f$\textrm{GeV}/c\f$
-       * \param[in] py Momentum along the \f$y\f$-axis, in \f$\textrm{GeV}/c\f$
-       * \param[in] pz Momentum along the \f$z\f$-axis, in \f$\textrm{GeV}/c\f$
+       * \brief Set the 3- or 4-momentum associated to the particle
+       * \param[in] px Momentum along the \f$x\f$-axis, in GeV/c
+       * \param[in] py Momentum along the \f$y\f$-axis, in GeV/c
+       * \param[in] pz Momentum along the \f$z\f$-axis, in GeV/c
        * \param[in] e Energy, in GeV
        */
-      void setMomentum( double px, double py, double pz, double e );
-      /**
-       * \brief Set the 4-momentum associated to the particle
-       * \param[in] p 4-momentum
-       */
+      void setMomentum( double px, double py, double pz, double e = -1. );
+      /// Set the 4-momentum associated to the particle
+      /// \param[in] p 4-momentum
       inline void setMomentum( double p[4] ) { setMomentum( p[0], p[1], p[2], p[3] ); }
-      /**
-       * \brief Set the particle's energy
-       * \param[in] e Energy, in GeV
-       */
-      void setEnergy( double e=-1. );
-      /// Get the particle's energy (in GeV)
+      /// Set the particle's energy
+      /// \param[in] e Energy, in GeV
+      void setEnergy( double e = -1. );
+      /// Get the particle's energy, in GeV
       double energy() const;
-      /// Get the particle's squared energy (in \f$\textrm{GeV}^\textrm{2}\f$)
+      /// Get the particle's squared energy, in GeV\f${}^2\f$
       inline double energy2() const { return energy()*energy(); };
       /// Is this particle a valid particle which can be used for kinematic computations?
       bool valid();
 
       // --- particle relations
 
       /// Is this particle a primary particle?
       inline bool primary() const { return mothers_.empty(); }
-      /**
-       * \brief Set the mother particle
-       * \param[in] part A Particle object containing all the information on the mother particle
-       */
+      /// Set the mother particle
+      /// \param[in] part A Particle object containing all the information on the mother particle
       void addMother( Particle& part );
-      /**
-       * \brief Gets the unique identifier to the mother particle from which this particle arises
-       * \return An integer representing the unique identifier to the mother of this particle in the event
-       */
+      /// Get the unique identifier to the mother particle from which this particle arises
+      /// \return An integer representing the unique identifier to the mother of this particle in the event
       inline ParticlesIds mothers() const { return mothers_; }
       /**
        * \brief Add a decay product
        * \param[in] part The Particle object in which this particle will desintegrate or convert
        * \return A boolean stating if the particle has been added to the daughters list or if it was already present before
        */
       void addDaughter( Particle& part );
       /// Gets the number of daughter particles
       inline unsigned int numDaughters() const { return daughters_.size(); };
-      /**
-       * \brief Get an identifiers list all daughter particles
-       * \return An integer vector containing all the daughters' unique identifier in the event
-       */
+      /// Get an identifiers list all daughter particles
+      /// \return An integer vector containing all the daughters' unique identifier in the event
       inline ParticlesIds daughters() const { return daughters_; }
 
       // --- global particle information extraction
 
       /// Dump all the information on this particle into the standard output stream
       void dump() const;
 
     private:
       /// Unique identifier in an event
       int id_;
       /// Electric charge (+-1 or 0)
       short charge_sign_;
       /// Momentum properties handler
       Momentum momentum_;
-      /// Mass in \f$\textrm{GeV}/c^2\f$
+      /// Mass, in GeV/c\f${}^2\f$
       double mass_;
       /// Helicity
       float helicity_;
       /// Role in the process
       Role role_;
       /// Decay/stability status
       Status status_;
       /// List of mother particles
       ParticlesIds mothers_;
       /// List of daughter particles
       ParticlesIds daughters_;
       /// PDG id
       PDG pdg_id_;
   };
 
   /// Compute the centre of mass energy of two particles (incoming or outgoing states)
   double CMEnergy( const Particle& p1, const Particle& p2 );
   /// Compute the centre of mass energy of two particles (incoming or outgoing states)
   double CMEnergy( const Particle::Momentum& m1, const Particle::Momentum& m2 );
 
   //bool operator<( const Particle& a, const Particle& b ) { return a.id<b.id; }
 
   // --- particle containers
 
   /// List of Particle objects
   typedef std::vector<Particle> Particles;
   /// List of particles' roles
   typedef std::vector<Particle::Role> ParticleRoles;
   /// Map between a particle's role and its associated Particle object
   typedef std::map<Particle::Role,Particles> ParticlesMap;
 }
 
 #endif
diff --git a/CepGen/Generator.h b/CepGen/Generator.h
index 764ba13..3520ac3 100644
--- a/CepGen/Generator.h
+++ b/CepGen/Generator.h
@@ -1,122 +1,122 @@
 #ifndef CepGen_Generator_h
 #define CepGen_Generator_h
 
 #include <sstream>
 #include <memory>
 #include <functional>
 
 ////////////////////////////////////////////////////////////////////////////////
 
 /**
- * \image latex cepgen_logo.pdf
  * \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 Vermaseren1983347. This latter allows to
+ * 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) by the GSL implementation of the *Vegas* algorithm, a
- * numerical technique for importance sampling integration developed in 1972 by G. P. Lepage\cite PeterLepage1978192.
+ * 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$\textbf{x}\f$ in the
+   * 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$\textbf{x}\f$ are computed in the \a f-function defined
+   * 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();
       /// Feed the generator with a Parameters object
       void setParameters( 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 cross_section_; }
+      double crossSection() const { return result_; }
       /// Last error on the cross section computed by the generator
-      double crossSectionError() const { return cross_section_error_; }
+      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 = {} );
       /// 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:
       /// Vegas instance which will integrate the function
       std::unique_ptr<Integrator> integrator_;
       /// Cross section value computed at the last integration
-      double cross_section_;
+      double result_;
       /// Error on the cross section as computed in the last integration
-      double cross_section_error_;
+      double result_error_;
   };
 }
 
 #endif
diff --git a/CepGen/Hadronisers/GenericHadroniser.cpp b/CepGen/Hadronisers/GenericHadroniser.cpp
index ac2d150..b2049da 100644
--- a/CepGen/Hadronisers/GenericHadroniser.cpp
+++ b/CepGen/Hadronisers/GenericHadroniser.cpp
@@ -1,45 +1,54 @@
 #include "CepGen/Hadronisers/GenericHadroniser.h"
+
 #include "CepGen/Core/Exception.h"
+#include "CepGen/Core/ParametersList.h"
 
 namespace CepGen
 {
   namespace Hadroniser
   {
-    GenericHadroniser::GenericHadroniser( const char* name ) :
-      name_( name )
-    {}
+    GenericHadroniser::GenericHadroniser( const char* name, const ParametersList& plist ) :
+      name_( name ),
+      seed_      ( plist.get<int>( "seed", -1ll ) ),
+      max_trials_( plist.get<int>( "maxTrials", 1 ) )
+    {
+      CG_DEBUG( "Hadroniser:init" )
+        << "\"" << name_ << "\"-type hadroniser built with:\n\t"
+        << "* seed = " << seed_ << "\n\t"
+        << "* maximum trials: " << max_trials_;
+    }
 
     void
     GenericHadroniser::readStrings( const std::vector<std::string>& params ) {
       if ( params.empty() )
         return;
       std::ostringstream os;
       for ( const auto& p : params ) {
         readString( p );
         os << "\n\t  '" << p << "'";
       }
       CG_DEBUG( "Hadroniser:configure" )
         << "Feeding \"" << name_ << "\" hadroniser with:"
         << os.str();
     }
 
     std::string
     GenericHadroniser::name() const
     {
       return name_;
     }
   }
 
   std::ostream&
   operator<<( std::ostream& os, const Hadroniser::GenericHadroniser& hadr )
   {
     return os << hadr.name().c_str();
   }
 
   std::ostream&
   operator<<( std::ostream& os, const Hadroniser::GenericHadroniser* hadr )
   {
     return os << hadr->name().c_str();
   }
 }
 
diff --git a/CepGen/Hadronisers/GenericHadroniser.h b/CepGen/Hadronisers/GenericHadroniser.h
index da08557..b7b7440 100644
--- a/CepGen/Hadronisers/GenericHadroniser.h
+++ b/CepGen/Hadronisers/GenericHadroniser.h
@@ -1,56 +1,70 @@
 #ifndef CepGen_Hadronisers_GenericHadroniser_h
 #define CepGen_Hadronisers_GenericHadroniser_h
 
 #include <vector>
 #include <memory>
 #include <iostream>
 
 namespace CepGen
 {
   class Event;
   class Particle;
   class Parameters;
+  class ParametersList;
   /// Location for all hadronisers to be run downstream to the events generation
   namespace Hadroniser
   {
     /**
-     * Class template to define any hadroniser as a general object with defined methods
+     * \brief Class template to define any hadroniser as a general object with defined methods
      * \author Laurent Forthomme <laurent.forthomme@cern.ch>
      * \date January 2014
      */
     class GenericHadroniser
     {
       public:
+        /// Write out all hadroniser attributes in output stream
         friend std::ostream& operator<<( std::ostream& os, const GenericHadroniser& hadr );
+        /// Write out all hadroniser attributes in output stream
         friend std::ostream& operator<<( std::ostream& os, const GenericHadroniser* hadr );
 
         /// Default constructor for an undefined hadroniser
-        explicit GenericHadroniser( const char* name = "unnamed_hadroniser" );
+        explicit GenericHadroniser( const char* name, const ParametersList& );
         virtual ~GenericHadroniser() {}
 
-        /// Hadronise a full event
-        /// \param[inout] ev Event to hadronise
-        /// \param[inout] weight Event weight after hadronisation
-        /// \param[in] full Perform the full state hadronisation (incl. remnants fragmentation)
-        /// \return Boolean stating whether or not the hadronisation occured successfully
-        virtual bool run( Event& ev, double& weight, bool full ) = 0;
-        /// Specify a random numbers generator seed for the hadroniser
-        /// \param[in] seed A RNG seed
-        virtual void setSeed( long long seed ) = 0;
-        virtual void setCrossSection( double xsec, double xsec_err ) {}
-
+        /// Parse a configuration string
         virtual void readString( const char* ) {}
+        /// Parse a configuration string
         virtual void readString( const std::string& param ) { readString( param.c_str() ); }
+        /// Parse a list of configuration strings
         virtual void readStrings( const std::vector<std::string>& params );
+        /// Initialise the event hadroniser before its running
+        virtual void init() = 0;
+        /** \brief Hadronise a full event
+         * \param[inout] ev Event to hadronise
+         * \param[inout] weight Event weight after hadronisation
+         * \param[in] full Perform the full state hadronisation (incl. remnants fragmentation)
+         * \return Boolean stating whether or not the hadronisation occured successfully
+         */
+        virtual bool run( Event& ev, double& weight, bool full ) = 0;
+        /// Specify the process cross section, in pb
+        virtual void setCrossSection( double xsec, double xsec_err ) {}
+
+        /// \brief Specify a random numbers generator seed for the hadroniser
+        /// \param[in] seed A RNG seed
+        void setSeed( long long seed ) { seed_ = seed; }
 
         /// Return a human-readable name for this hadroniser
         std::string name() const;
 
       protected:
         /// Name of the hadroniser
         std::string name_;
+        /// Random numbers generator seed for the hadroniser
+        long long seed_;
+        /// Maximal number of trials for the hadronisation of the proton(s) remnants
+        unsigned short max_trials_;
     };
   }
 }
 
 #endif
diff --git a/CepGen/Hadronisers/Pythia8Hadroniser.cpp b/CepGen/Hadronisers/Pythia8Hadroniser.cpp
index b8a0669..85964dd 100644
--- a/CepGen/Hadronisers/Pythia8Hadroniser.cpp
+++ b/CepGen/Hadronisers/Pythia8Hadroniser.cpp
@@ -1,475 +1,464 @@
 #include "CepGen/Hadronisers/Pythia8Hadroniser.h"
 
 #include "CepGen/Parameters.h"
 #include "CepGen/Physics/Kinematics.h"
 #include "CepGen/Physics/Constants.h"
 #include "CepGen/Physics/PDG.h"
 
+#include "CepGen/Core/ParametersList.h"
 #include "CepGen/Core/Exception.h"
 #include "CepGen/Core/utils.h"
 
 #include "CepGen/Event/Event.h"
 #include "CepGen/Event/Particle.h"
 
 #include "CepGen/Version.h"
 
 namespace CepGen
 {
   namespace Hadroniser
   {
 #ifdef PYTHIA8
     Pythia8::Vec4
     momToVec4( const Particle::Momentum& mom )
     {
       return Pythia8::Vec4( mom.px(), mom.py(), mom.pz(), mom.energy() );
     }
 #endif
 
-    Pythia8Hadroniser::Pythia8Hadroniser( const Parameters& params ) :
-      GenericHadroniser( "pythia8" ), max_attempts_( params.hadroniser_max_trials ),
+    Pythia8Hadroniser::Pythia8Hadroniser( const Parameters& params, const ParametersList& plist ) :
+      GenericHadroniser( "pythia8", plist ),
 #ifdef PYTHIA8
       pythia_( new Pythia8::Pythia ), lhaevt_( new LHAEvent( &params ) ),
 #endif
       full_evt_( false ), offset_( 0 ), first_evt_( true ), params_( &params )
     {
 #ifdef PYTHIA8
       pythia_->setLHAupPtr( (Pythia8::LHAup*)lhaevt_.get() );
       pythia_->settings.parm( "Beams:idA", (short)params.kinematics.incoming_beams.first.pdg );
       pythia_->settings.parm( "Beams:idB", (short)params.kinematics.incoming_beams.second.pdg );
       // specify we will be using a LHA input
       pythia_->settings.mode( "Beams:frameType", 5 );
       pythia_->settings.parm( "Beams:eCM", params.kinematics.sqrtS() );
 #endif
       for ( const auto& pdgid : params.kinematics.minimum_final_state )
         min_ids_.emplace_back( (unsigned short)pdgid );
     }
 
     Pythia8Hadroniser::~Pythia8Hadroniser()
     {
 #ifdef PYTHIA8
-      pythia_->settings.writeFile( "last_pythia_config.cmd", true );
+      pythia_->settings.writeFile( "last_pythia_config.cmd", false );
 #endif
     }
 
-    bool
+    void
+    Pythia8Hadroniser::readString( const char* param )
+    {
+#ifdef PYTHIA8
+      if ( !pythia_->readString( param ) )
+        throw CG_FATAL( "Pythia8Hadroniser" ) << "The Pythia8 core failed to parse the following setting:\n\t" << param;
+#endif
+    }
+
+    void
     Pythia8Hadroniser::init()
     {
 #ifdef PYTHIA8
       if ( pythia_->settings.flag( "ProcessLevel:all" ) != full_evt_ )
         pythia_->settings.flag( "ProcessLevel:all", full_evt_ );
 
+      if ( seed_ == -1ll )
+        pythia_->settings.flag( "Random:setSeed", false );
+      else {
+        pythia_->settings.flag( "Random:setSeed", true );
+        pythia_->settings.mode( "Random:seed", seed_ );
+      }
+
       switch ( params_->kinematics.mode ) {
-        case KinematicsMode::ElasticElastic:
+        case KinematicsMode::ElasticElastic: {
           pythia_->settings.mode( "BeamRemnants:unresolvedHadron", 3 );
-          break;
-        case KinematicsMode::InelasticElastic:
+        } break;
+        case KinematicsMode::InelasticElastic: {
           pythia_->settings.mode( "BeamRemnants:unresolvedHadron", 2 );
-          break;
-        case KinematicsMode::ElasticInelastic:
+        } break;
+        case KinematicsMode::ElasticInelastic: {
           pythia_->settings.mode( "BeamRemnants:unresolvedHadron", 1 );
-          break;
-        case KinematicsMode::InelasticInelastic: default:
+        } break;
+        case KinematicsMode::InelasticInelastic: default: {
           pythia_->settings.mode( "BeamRemnants:unresolvedHadron", 0 );
-          break;
+        } break;
       }
-//      pythia_->settings.mode( "BeamRemnants:remnantMode", 1 );
 
       if ( !pythia_->init() )
         throw CG_FATAL( "Pythia8Hadroniser" )
           << "Failed to initialise the Pythia8 core!\n\t"
           << "See the message above for more details.";
 #else
       throw CG_FATAL( "Pythia8Hadroniser" )
-        << "Pythia8 is not linked to thin instance!";
-#endif
-      return true;
-    }
-
-    void
-    Pythia8Hadroniser::readString( const char* param )
-    {
-#ifdef PYTHIA8
-      if ( !pythia_->readString( param ) )
-        throw CG_FATAL( "Pythia8Hadroniser" ) << "The Pythia8 core failed to parse the following setting:\n\t" << param;
-#endif
-    }
-
-    void
-    Pythia8Hadroniser::setSeed( long long seed )
-    {
-#ifdef PYTHIA8
-      if ( seed == -1ll )
-        pythia_->settings.flag( "Random:setSeed", false );
-      else {
-        pythia_->settings.flag( "Random:setSeed", true );
-        pythia_->settings.mode( "Random:seed", seed );
-      }
+        << "Pythia8 is not linked to this instance!";
 #endif
     }
 
     void
     Pythia8Hadroniser::setCrossSection( double xsec, double xsec_err )
     {
 #ifdef PYTHIA8
       lhaevt_->setCrossSection( 0, xsec, xsec_err );
 #endif
     }
 
     bool
     Pythia8Hadroniser::run( Event& ev, double& weight, bool full )
     {
       //--- initialise the event weight before running any decay algorithm
       weight = 1.;
 
-#ifndef PYTHIA8
-      throw CG_FATAL( "Pythia8Hadroniser" ) << "Pythia8 is not linked to this instance!";
-#else
+#ifdef PYTHIA8
       if ( !full && !pythia_->settings.flag( "ProcessLevel:resonanceDecays" ) )
         return true;
 
       //--- switch full <-> partial event
       if ( full != full_evt_ ) {
         full_evt_ = full;
         init();
       }
 
       //===========================================================================================
       // convert our event into a custom LHA format
       //===========================================================================================
 
       lhaevt_->feedEvent( ev, full, params_->kinematics.mode );
       //if ( full ) lhaevt_->listEvent();
 
       //===========================================================================================
       // launch the hadronisation / resonances decays, and update the event accordingly
       //===========================================================================================
 
       ev.num_hadronisation_trials = 0;
       while ( true ) {
-        ev.num_hadronisation_trials++; // start at 1
-        if ( ev.num_hadronisation_trials > max_attempts_ )
+        if ( ev.num_hadronisation_trials++ > max_trials_ )
           return false;
         //--- run the hadronisation/fragmentation algorithm
-        if ( !pythia_->next() )
-          continue;
-        //--- hadronisation successful
-        if ( first_evt_ && full ) {
-          offset_ = 0;
-          for ( unsigned short i = 1; i < pythia_->event.size(); ++i )
-            if ( pythia_->event[i].status() == -12 ) // skip the incoming particles
-              offset_++;
-          first_evt_ = false;
+        if ( pythia_->next() ) {
+          //--- hadronisation successful
+          if ( first_evt_ && full ) {
+            offset_ = 0;
+            for ( unsigned short i = 1; i < pythia_->event.size(); ++i )
+              if ( pythia_->event[i].status() == -12 ) // skip the incoming particles
+                offset_++;
+            first_evt_ = false;
+          }
+          break;
         }
-        break;
       }
 
       //===========================================================================================
       // update the event content with Pythia's output
       //===========================================================================================
 
       updateEvent( ev, weight, full );
-
-#endif
       return true;
+#else
+      throw CG_FATAL( "Pythia8Hadroniser" ) << "Pythia8 is not linked to this instance!";
+#endif
     }
 
 #ifdef PYTHIA8
     Particle&
     Pythia8Hadroniser::addParticle( Event& ev, const Pythia8::Particle& py_part, const Pythia8::Vec4& mom, unsigned short role ) const
     {
       Particle& op = ev.addParticle( (Particle::Role)role );
       op.setPdgId( static_cast<PDG>( abs( py_part.id() ) ), py_part.charge() );
       op.setStatus( py_part.isFinal()
         ? Particle::Status::FinalState
         : Particle::Status::Propagator );
       op.setMomentum( Particle::Momentum( mom.px(), mom.py(), mom.pz(), mom.e() ) );
       op.setMass( mom.mCalc() );
       lhaevt_->addCorresp( py_part.index()-offset_, op.id() );
       return op;
     }
 
     void
     Pythia8Hadroniser::updateEvent( Event& ev, double& weight, bool full ) const
     {
       for ( unsigned short i = 1+offset_; i < pythia_->event.size(); ++i ) {
         const Pythia8::Particle& p = pythia_->event[i];
-        const unsigned short cg_id = lhaevt_->cgPart( i-offset_ );
+        const unsigned short cg_id = lhaevt_->cepgenId( i-offset_ );
         if ( cg_id != LHAEvent::invalid_id ) {
           //----- particle already in the event
           Particle& cg_part = ev[cg_id];
           //--- fragmentation result
           if ( cg_part.role() == Particle::OutgoingBeam1
             || cg_part.role() == Particle::OutgoingBeam2 ) {
             cg_part.setStatus( Particle::Status::Fragmented );
             continue;
           }
           //--- particle is not what we expect
-          if ( abs( p.id() ) != abs( cg_part.integerPdgId() ) ) {
+          if ( p.idAbs() != abs( cg_part.integerPdgId() ) ) {
             CG_INFO( "Pythia8Hadroniser:update" ) << "LHAEVT event content:";
             lhaevt_->listEvent();
             CG_INFO( "Pythia8Hadroniser:update" ) << "Pythia event content:";
             pythia_->event.list();
             CG_INFO( "Pythia8Hadroniser:update" ) << "CepGen event content:";
             ev.dump();
             CG_INFO( "Pythia8Hadroniser:update" ) << "Correspondence:";
             lhaevt_->dumpCorresp();
 
             throw CG_FATAL( "Pythia8Hadroniser:update" )
               << "Event list corruption detected for (Pythia/CepGen) particle " << i << "/" << cg_id << ":\n\t"
               << "should be " << abs( p.id() ) << ", "
               << "got " << cg_part.integerPdgId() << "!";
           }
-          //--- no decay for this particle
-          if ( p.particleDataEntry().sizeChannels() == 0 )
-            continue;
           //--- resonance decayed; apply branching ratio for this decay
-          weight *= p.particleDataEntry().pickChannel().bRatio();
-          cg_part.setStatus( Particle::Status::Resonance );
+          if ( p.particleDataEntry().sizeChannels() > 0 ) {
+            weight *= p.particleDataEntry().pickChannel().bRatio();
+            cg_part.setStatus( Particle::Status::Resonance );
+          }
         }
         else {
           //----- new particle to be added
-          Particle::Role role = (Particle::Role)findRole( ev, p );
-          Pythia8::Vec4 mom = p.p();
-          switch ( role ) {
+          const unsigned short role = findRole( ev, p );
+          switch ( (Particle::Role)role ) {
             default: break;
-            case Particle::OutgoingBeam1: // no break!
+            case Particle::OutgoingBeam1: {
               ev.getByRole( Particle::OutgoingBeam1 )[0].setStatus( Particle::Status::Fragmented );
               if ( abs( p.status() ) != 61 )
                 break;
-            case Particle::OutgoingBeam2: // no break!
+            } // no break!
+            case Particle::OutgoingBeam2: {
               ev.getByRole( Particle::OutgoingBeam2 )[0].setStatus( Particle::Status::Fragmented );
               if ( abs( p.status() ) != 61 )
                 break;
+            } // no break!
           }
           // found the role ; now we can add the particle
-          Particle& cg_part = addParticle( ev, p, mom, (unsigned short)role );
+          Particle& cg_part = addParticle( ev, p, p.p(), role );
           for ( const auto& moth_id : p.motherList() ) {
             if ( moth_id <= offset_ )
               continue;
-            const unsigned short moth_cg_id = lhaevt_->cgPart( moth_id-offset_ );
+            const unsigned short moth_cg_id = lhaevt_->cepgenId( moth_id-offset_ );
             if ( moth_cg_id != LHAEvent::invalid_id )
               cg_part.addMother( ev[moth_cg_id] );
             else
-              cg_part.addMother( addParticle( ev, pythia_->event[moth_id], mom, (unsigned short)role ) );
+              cg_part.addMother( addParticle( ev, pythia_->event[moth_id], p.p(), role ) );
             if ( !p.isFinal() ) {
               if ( p.isResonance() || p.daughterList().size() > 0 )
                 cg_part.setStatus( Particle::Status::Resonance );
               else
                 cg_part.setStatus( Particle::Status::Undefined );
             }
           }
         }
       }
     }
 
     unsigned short
     Pythia8Hadroniser::findRole( const Event& ev, const Pythia8::Particle& p ) const
     {
       for ( const auto& par_id : p.motherList() ) {
         if ( par_id == 1 && offset_ > 0 )
           return (unsigned short)Particle::OutgoingBeam1;
         if ( par_id == 2 && offset_ > 0 )
           return (unsigned short)Particle::OutgoingBeam2;
-        const unsigned short par_cg_id = lhaevt_->cgPart( par_id-offset_ );
+        const unsigned short par_cg_id = lhaevt_->cepgenId( par_id-offset_ );
         if ( par_cg_id != LHAEvent::invalid_id )
-          return (unsigned short)ev.getConstById( par_cg_id ).role();
+          return (unsigned short)ev.at( par_cg_id ).role();
         return findRole( ev, pythia_->event[par_id] );
       }
       return (unsigned short)Particle::UnknownRole;
     }
 #endif
   }
 
   //================================================================================================
   // Custom LHA event definition
   //================================================================================================
 
 #ifdef PYTHIA8
   const double LHAEvent::mp_ = ParticleProperties::mass( PDG::proton );
   const double LHAEvent::mp2_ = LHAEvent::mp_*LHAEvent::mp_;
 
   LHAEvent::LHAEvent( const Parameters* params ) :
     LHAup( 3 ), params_( params )
   {
     addProcess( 0, 1., 1., 1.e3 );
     if ( params_ ) {
       setBeamA( (short)params_->kinematics.incoming_beams.first.pdg, params_->kinematics.incoming_beams.first.pz );
       setBeamB( (short)params_->kinematics.incoming_beams.second.pdg, params_->kinematics.incoming_beams.second.pz );
     }
   }
 
   void
   LHAEvent::setCrossSection( int id, double xsec, double xsec_err )
   {
     setXSec( id, xsec );
     setXErr( id, xsec_err );
     //listInit();
   }
 
   void
   LHAEvent::feedEvent( const Event& ev, bool full, const KinematicsMode& mode )
   {
     const double scale = ev.getOneByRole( Particle::Intermediate ).mass();
     setProcess( 0, 1., scale, Constants::alphaEM, Constants::alphaQCD );
 
     const Particle& part1 = ev.getOneByRole( Particle::Parton1 ), &part2 = ev.getOneByRole( Particle::Parton2 );
     const Particle& op1 = ev.getOneByRole( Particle::OutgoingBeam1 ), &op2 = ev.getOneByRole( Particle::OutgoingBeam2 );
     const double q2_1 = -part1.momentum().mass2(), q2_2 = -part2.momentum().mass2();
     const double x1 = q2_1/( q2_1+op1.mass2()-mp2_ ), x2 = q2_2/( q2_2+op2.mass2()-mp2_ );
 
     unsigned short quark1_id = 0, quark2_id = 0;
     unsigned short quark1_pdgid = part1.integerPdgId(), quark2_pdgid = part2.integerPdgId();
 
     const Pythia8::Vec4 mom_part1( Hadroniser::momToVec4( part1.momentum() ) ), mom_part2( Hadroniser::momToVec4( part2.momentum() ) );
 
     if ( !full ) {
       //=============================================================================================
       // incoming partons
       //=============================================================================================
 
       addCorresp( sizePart(), part1.id() );
       addParticle( quark1_pdgid, -2, quark1_id, 0, 0, 0, mom_part1.px(), mom_part1.py(), mom_part1.pz(), mom_part1.e(), mom_part1.mCalc(), 0., 0. );
 
       addCorresp( sizePart(), part2.id() );
       addParticle( quark2_pdgid, -2, quark2_id, 0, 0, 0, mom_part2.px(), mom_part2.py(), mom_part2.pz(), mom_part2.e(), mom_part2.mCalc(), 0., 0. );
     }
     else { // full event content (with collinear partons)
       const bool inel1 = ( mode == KinematicsMode::InelasticElastic || mode == KinematicsMode::InelasticInelastic );
       const bool inel2 = ( mode == KinematicsMode::ElasticInelastic || mode == KinematicsMode::InelasticInelastic );
 
-      unsigned short quark1_colour = 0, quark2_colour = 0;
       Pythia8::Vec4 mom_iq1 = mom_part1, mom_iq2 = mom_part2;
+      unsigned short colour_index = 501, quark1_colour = 0, quark2_colour = 0;
       //FIXME select quark flavours accordingly
       if ( inel1 ) {
         quark1_pdgid = 2;
-        quark1_colour = 501;
-        const Particle& ip1 = ev.getOneByRole( Particle::IncomingBeam1 );
-        mom_iq1 = Pythia8::Vec4( 0., 0., x1*ip1.momentum().pz(), x1*ip1.energy() );
+        quark1_colour = colour_index++;
+        mom_iq1 = Hadroniser::momToVec4( x1*ev.getOneByRole( Particle::IncomingBeam1 ).momentum() );
       }
       if ( inel2 ) {
         quark2_pdgid = 2;
-        quark2_colour = 502;
-        const Particle& ip2 = ev.getOneByRole( Particle::IncomingBeam2 );
-        mom_iq2 = Pythia8::Vec4( 0., 0., x2*ip2.momentum().pz(), x2*ip2.energy() );
+        quark2_colour = colour_index++;
+        mom_iq2 = Hadroniser::momToVec4( x2*ev.getOneByRole( Particle::IncomingBeam2 ).momentum() );
       }
 
       //--- flavour / x value of hard-process initiators
       setIdX( part1.integerPdgId(), part2.integerPdgId(), x1, x2 );
 
       //===========================================================================================
       // incoming valence quarks
       //===========================================================================================
 
       quark1_id = sizePart();
-      addCorresp( sizePart(), op1.id() );
+      addCorresp( quark1_id, op1.id() );
       addParticle( quark1_pdgid, -1, 0, 0, quark1_colour, 0, mom_iq1.px(), mom_iq1.py(), mom_iq1.pz(), mom_iq1.e(), mom_iq1.mCalc(), 0., 1. );
 
       quark2_id = sizePart();
-      addCorresp( sizePart(), op2.id() );
+      addCorresp( quark2_id, op2.id() );
       addParticle( quark2_pdgid, -1, 0, 0, quark2_colour, 0, mom_iq2.px(), mom_iq2.py(), mom_iq2.pz(), mom_iq2.e(), mom_iq2.mCalc(), 0., 1. );
 
       //===========================================================================================
       // outgoing valence quarks
       //===========================================================================================
 
       if ( inel1 ) {
         const Pythia8::Vec4 mom_oq1 = mom_iq1-mom_part1;
         addParticle( quark1_pdgid, 1, quark1_id, quark2_id, quark1_colour, 0, mom_oq1.px(), mom_oq1.py(), mom_oq1.pz(), mom_oq1.e(), mom_oq1.mCalc(), 0., 1. );
       }
       if ( inel2 ) {
         const Pythia8::Vec4 mom_oq2 = mom_iq2-mom_part2;
         addParticle( quark2_pdgid, 1, quark1_id, quark2_id, quark2_colour, 0, mom_oq2.px(), mom_oq2.py(), mom_oq2.pz(), mom_oq2.e(), mom_oq2.mCalc(), 0., 1. );
       }
     }
 
     //=============================================================================================
     // central system
     //=============================================================================================
 
     for ( const auto& p : ev.getByRole( Particle::CentralSystem ) ) {
       const auto mothers = p.mothers();
       unsigned short moth1_id = 1, moth2_id = 2;
       if ( !full ) {
         moth1_id = moth2_id = 0;
         if ( mothers.size() > 0 ) {
           const unsigned short moth1_cg_id = *mothers.begin();
-          moth1_id = pyPart( moth1_cg_id );
+          moth1_id = pythiaId( moth1_cg_id );
           if ( moth1_id == invalid_id ) {
-            const Particle& moth = ev.getConstById( moth1_cg_id );
+            const Particle& moth = ev.at( moth1_cg_id );
             if ( moth.mothers().size() > 0 )
-              moth1_id = pyPart( *moth.mothers().begin() );
+              moth1_id = pythiaId( *moth.mothers().begin() );
             if ( moth.mothers().size() > 1 )
-              moth2_id = pyPart( *moth.mothers().rbegin() );
+              moth2_id = pythiaId( *moth.mothers().rbegin() );
           }
           if ( mothers.size() > 1 ) {
             const unsigned short moth2_cg_id = *mothers.rbegin();
-            moth2_id = pyPart( moth2_cg_id );
+            moth2_id = pythiaId( moth2_cg_id );
             if ( moth2_id == invalid_id ) {
-              const Particle& moth = ev.getConstById( moth2_cg_id );
+              const Particle& moth = ev.at( moth2_cg_id );
               moth.dump();
-              moth2_id = pyPart( *moth.mothers().rbegin() );
+              moth2_id = pythiaId( *moth.mothers().rbegin() );
             }
           }
         }
       }
       const Pythia8::Vec4 mom_part( p.momentum().px(), p.momentum().py(), p.momentum().pz(), p.momentum().energy() );
       addCorresp( sizePart(), p.id() );
       addParticle( p.integerPdgId(), 1, moth1_id, moth2_id, 0, 0, mom_part.px(), mom_part.py(), mom_part.pz(), mom_part.e(), mom_part.mCalc(), 0., 0., 0. );
     }
     setPdf( quark1_pdgid, quark2_pdgid, x1, x2, scale, 0., 0., false );
   }
 
   bool
   LHAEvent::setInit()
   {
     return true;
   }
 
   bool
   LHAEvent::setEvent( int )
   {
     return true;
   }
 
   void
   LHAEvent::setProcess( int id, double xsec, double q2_scale, double alpha_qed, double alpha_qcd )
   {
     LHAup::setProcess( id, xsec, q2_scale, alpha_qed, alpha_qcd );
     py_cg_corresp_.clear();
   }
 
   unsigned short
-  LHAEvent::cgPart( unsigned short py_id ) const
+  LHAEvent::cepgenId( unsigned short py_id ) const
   {
     for ( const auto& py_cg : py_cg_corresp_ )
       if ( py_cg.first == py_id )
         return py_cg.second;
     return invalid_id;
   }
 
   unsigned short
-  LHAEvent::pyPart( unsigned short cg_id ) const
+  LHAEvent::pythiaId( unsigned short cg_id ) const
   {
     for ( const auto& py_cg : py_cg_corresp_ )
       if ( py_cg.second == cg_id )
         return py_cg.first;
     return invalid_id;
   }
 
   void
   LHAEvent::addCorresp( unsigned short py_id, unsigned short cg_id )
   {
     py_cg_corresp_.emplace_back( py_id, cg_id );
   }
 
   void
   LHAEvent::dumpCorresp() const
   {
     std::ostringstream oss;
     oss << "List of Pythia <-> CepGen particle ids correspondance";
     for ( const auto& py_cg : py_cg_corresp_ )
       oss << "\n\t" << py_cg.first << " <-> " << py_cg.second;
     CG_INFO( "LHAEvent:dump" ) << oss.str();
   }
 #endif
 }
diff --git a/CepGen/Hadronisers/Pythia8Hadroniser.h b/CepGen/Hadronisers/Pythia8Hadroniser.h
index 84e1f20..dfdbcbc 100644
--- a/CepGen/Hadronisers/Pythia8Hadroniser.h
+++ b/CepGen/Hadronisers/Pythia8Hadroniser.h
@@ -1,86 +1,84 @@
 #ifndef CepGen_Hadronisers_Pythia8Hadroniser_h
 #define CepGen_Hadronisers_Pythia8Hadroniser_h
 
 #include "CepGen/Hadronisers/GenericHadroniser.h"
 
 #ifdef PYTHIA8
 #include <Pythia8/Pythia.h>
 #include <memory>
 #endif
 
 #include <unordered_map>
 #include <vector>
 
 namespace CepGen
 {
   class Particle;
+  class ParametersList;
   enum class KinematicsMode;
 #ifdef PYTHIA8
   class LHAEvent : public Pythia8::LHAup
   {
     public:
       explicit LHAEvent( const Parameters* );
       void feedEvent( const Event& ev, bool full, const KinematicsMode& );
       bool setInit() override;
       bool setEvent( int ) override;
       void setCrossSection( int id, double xsec, double xsec_err );
       void setProcess( int id, double xsec, double q2_scale, double alpha_qed, double alpha_qcd );
 
-      unsigned short cgPart( unsigned short py_id ) const;
-      unsigned short pyPart( unsigned short cg_id ) const;
+      unsigned short cepgenId( unsigned short py_id ) const;
+      unsigned short pythiaId( unsigned short cg_id ) const;
       void addCorresp( unsigned short py_id, unsigned short cg_id );
       void dumpCorresp() const;
 
       static constexpr unsigned short invalid_id = 999;
     private:
       static const double mp_, mp2_;
       std::vector<std::pair<unsigned short, unsigned short> > py_cg_corresp_;
       const Parameters* params_;
   };
 #endif
 
   namespace Hadroniser
   {
     /**
      * Full interface to the Pythia8 hadronisation algorithm. It can be used in a single particle decay mode as well as a full event hadronisation using the string model, as in Jetset.
      * \brief Pythia8 hadronisation algorithm
      */
     class Pythia8Hadroniser : public GenericHadroniser
     {
       public:
-        explicit Pythia8Hadroniser( const Parameters& );
+        explicit Pythia8Hadroniser( const Parameters&, const ParametersList& );
         ~Pythia8Hadroniser();
 
+        void readString( const char* param ) override;
+        void init() override;
         bool run( Event& ev, double& weight, bool full ) override;
-        void setSeed( long long seed ) override;
+
         void setCrossSection( double xsec, double xsec_err ) override;
 
         bool fullEvent() const { return full_evt_; }
         void setFullEvent( bool full = true ) { full_evt_ = full; }
 
-        bool init();
-        void readString( const char* param ) override;
-
       private:
         static constexpr unsigned short invalid_idx_ = 999;
-        unsigned short max_attempts_;
         std::vector<unsigned short> min_ids_;
         std::unordered_map<short,short> py_cg_corresp_;
 #ifdef PYTHIA8
         unsigned short findRole( const Event& ev, const Pythia8::Particle& p ) const;
         void updateEvent( Event& ev, double& weight, bool full ) const;
         Particle& addParticle( Event& ev, const Pythia8::Particle&, const Pythia8::Vec4& mom, unsigned short ) const;
         /// A Pythia8 core to be wrapped
         std::unique_ptr<Pythia8::Pythia> pythia_;
-        std::shared_ptr<LHAEvent> lhaevt_;
+        std::unique_ptr<LHAEvent> lhaevt_;
 #endif
         bool full_evt_;
         unsigned short offset_;
         bool first_evt_;
         const Parameters* params_; // not owning
     };
   }
 }
 
 #endif
-
diff --git a/CepGen/IO/ExportHandler.h b/CepGen/IO/ExportHandler.h
index 741a32e..dcf77a8 100644
--- a/CepGen/IO/ExportHandler.h
+++ b/CepGen/IO/ExportHandler.h
@@ -1,57 +1,59 @@
 #ifndef CepGen_Export_ExportHandler_h
 #define CepGen_Export_ExportHandler_h
 
 #include <iostream>
 
 namespace CepGen
 {
   class Event;
   class Parameters;
   /// Location for all output generators
   namespace OutputHandler
   {
     /**
      * \brief Output format handler for events export
      * \author Laurent Forthomme <laurent.forthomme@cern.ch>
      * \date Sep 2016
      */
     class ExportHandler
     {
       public:
         /// All types of output available for export
         enum OutputType {
           HepMC, ///< HepMC ASCII format
           LHE ///< LHEF format
         };
+        /// Human-readable output name
         friend std::ostream& operator<<( std::ostream& os, const OutputType& type ) {
           switch ( type ) {
             case HepMC: return os << "HepMC ASCII";
             case LHE: return os << "LHEF";
           }
           return os;
         }
 
       public:
-        /// Class constructor
+        /// \brief Class constructor
         /// \param[in] type Requested output type
         explicit ExportHandler( const OutputType& type ) :
           type_( type ), event_num_( 0. ) {}
         virtual ~ExportHandler() {}
+        /// Initialise the handler and its inner parameterisation
         virtual void initialise( const Parameters& ) = 0;
         /// Set the process cross section and its associated error
         virtual void setCrossSection( double xsec, double err_xsec ) {}
         /// Set the event number
         void setEventNumber( const unsigned int& ev_id ) { event_num_ = ev_id; }
         /// Writer operator
         virtual void operator<<( const Event& ) = 0;
 
       protected:
         /// Type of output requested
         OutputType type_;
         /// Event index
         unsigned int event_num_;
     };
   }
 }
 
 #endif
diff --git a/CepGen/IO/GridHandler.h b/CepGen/IO/GridHandler.h
index 18ba207..68e1556 100644
--- a/CepGen/IO/GridHandler.h
+++ b/CepGen/IO/GridHandler.h
@@ -1,303 +1,314 @@
 #ifndef CepGen_IO_GridHandler_h
 #define CepGen_IO_GridHandler_h
 
 #include <gsl/gsl_version.h>
 #ifdef GSL_MAJOR_VERSION
 #  if GSL_MAJOR_VERSION > 2 || ( GSL_MAJOR_VERSION == 2 && GSL_MINOR_VERSION >= 1 )
 #    include <gsl/gsl_interp2d.h>
 #    include <gsl/gsl_spline2d.h>
 #    define GOOD_GSL 1
 #  endif
 #endif
 #include <gsl/gsl_interp.h>
 #include <gsl/gsl_spline.h>
 #include <gsl/gsl_errno.h>
 #include <gsl/gsl_math.h>
 
 #include "CepGen/Core/Exception.h"
 #include <vector>
 #include <map>
 
 namespace CepGen
 {
   class StructureFunctions;
   enum struct GridType
   {
     linear = 0,
     logarithmic = 1,
     square = 2
   };
-  /// A generic class for D-dimensional grid interpolation
+  /// \brief A generic class for D-dimensional grid interpolation
   /// \param N Number of values handled per point
   template <size_t D,size_t N=1>
   class GridHandler
   {
     public:
       typedef std::vector<double> coord_t;
       typedef std::array<double,N> values_t;
 
     public:
       explicit GridHandler( const GridType& grid_type ) :
         grid_type_( grid_type ), accel_{}
       {
         for ( size_t i = 0; i < D; ++i )
           accel_.emplace_back( gsl_interp_accel_alloc(), gsl_interp_accel_free );
       }
       ~GridHandler() {}
 
       /// Interpolate a point to a given coordinate
       values_t eval( coord_t in_coords ) const {
         values_t out;
         coord_t coord = in_coords;
         switch ( grid_type_ ) {
           case GridType::logarithmic: {
             for ( auto& c : coord )
               c = log10( c );
           } break;
           case GridType::square: {
             for ( auto& c : coord )
               c *= c;
           } break;
           default: break;
         }
         //--- dimension of the vector space coordinate to evaluate
         switch ( D ) {
           case 1: {
             for ( size_t i = 0; i < N; ++i ) {
               int res = gsl_spline_eval_e( splines_1d_.at( i ).get(), coord.at( 0 ), accel_.at( 0 ).get(), &out[i] );
               if ( res != GSL_SUCCESS ) {
                 out[i] = 0.;
                 CG_WARNING( "GridHandler" )
                   << "Failed to evaluate the grid value (N=" << i << ") "
                   << "for x = " << in_coords.at( 0 ) << ". "
                   << "GSL error: " << gsl_strerror( res );
               }
             }
           } break;
           case 2: {
 #ifdef GOOD_GSL
             const double x = coord.at( 0 ), y = coord.at( 1 );
             for ( size_t i = 0; i < N; ++i ) {
               int res = gsl_spline2d_eval_e( splines_2d_.at( i ).get(), x, y, accel_.at( 0 ).get(), accel_.at( 1 ).get(), &out[i] );
               if ( res != GSL_SUCCESS ) {
                 out[i] = 0.;
                 CG_WARNING( "GridHandler" )
                   << "Failed to evaluate the grid value (N=" << i << ") "
                   << "for x = " << in_coords.at( 0 ) << " / y = " << in_coords.at( 1 ) << ". "
                   << "GSL error: " << gsl_strerror( res );
               }
             }
 #else
             //--- retrieve the indices of the bin in the set
             coord_t before, after;
             findIndices( coord, before, after );
             //--- find boundaries values
             const gridpoint_t& ext_11 = values_raw_.at( { before[0], before[1] } ),
                               &ext_12 = values_raw_.at( { before[0],  after[1] } ),
                               &ext_21 = values_raw_.at( {  after[0], before[1] } ),
                               &ext_22 = values_raw_.at( {  after[0],  after[1] } );
             //--- now that we have the boundaries, we may interpolate
             coord_t c_d( D );
             for ( size_t i = 0; i < D; ++i )
               c_d[i] = ( after[i] != before[i] )
                 ? ( coord.at( i )-before[i] )/( after[i]-before[i] )
                 : 0.;
             const gridpoint_t ext_1 = ext_11*( 1.-c_d[0] ) + ext_21*c_d[0];
             const gridpoint_t ext_2 = ext_12*( 1.-c_d[0] ) + ext_22*c_d[0];
             out = ext_1*( 1.-c_d[1] )+ext_2*c_d[1];
 #endif
           } break;
           case 3: {
             //--- retrieve the indices of the bin in the set
             coord_t before, after;
             findIndices( coord, before, after );
             //--- find boundaries values
             const gridpoint_t& ext_111 = values_raw_.at( { before[0], before[1], before[2] } ),
                               &ext_112 = values_raw_.at( { before[0], before[1],  after[2] } ),
                               &ext_121 = values_raw_.at( { before[0],  after[1], before[2] } ),
                               &ext_122 = values_raw_.at( { before[0],  after[1],  after[2] } ),
                               &ext_211 = values_raw_.at( {  after[0], before[1], before[2] } ),
                               &ext_212 = values_raw_.at( {  after[0], before[1],  after[2] } ),
                               &ext_221 = values_raw_.at( {  after[0],  after[1], before[2] } ),
                               &ext_222 = values_raw_.at( {  after[0],  after[1],  after[2] } );
             //--- now that we have the boundaries, we may interpolate
             coord_t c_d( D );
             for ( size_t i = 0; i < D; ++i )
               c_d[i] = ( after[i] != before[i] )
                 ? ( coord.at( i )-before[i] )/( after[i]-before[i] )
                 : 0.;
             const gridpoint_t ext_11 = ext_111*( 1.-c_d[0] ) + ext_211*c_d[0];
             const gridpoint_t ext_12 = ext_112*( 1.-c_d[0] ) + ext_212*c_d[0];
             const gridpoint_t ext_21 = ext_121*( 1.-c_d[0] ) + ext_221*c_d[0];
             const gridpoint_t ext_22 = ext_122*( 1.-c_d[0] ) + ext_222*c_d[0];
             const gridpoint_t ext_1 = ext_11*( 1.-c_d[1] ) + ext_21*c_d[1];
             const gridpoint_t ext_2 = ext_12*( 1.-c_d[1] ) + ext_22*c_d[1];
             out = ext_1*( 1.-c_d[2] )+ext_2*c_d[2];
           } break;
           default:
             throw CG_FATAL( "GridHandler" ) << "Unsupported number of dimensions: " << N << ".\n\t"
               << "Please contact the developers to add such a new feature.";
         }
         return out;
       }
 
       /// Insert a new value in the grid
       void insert( coord_t coord, values_t value ) {
         auto mod_coord = coord;
         if ( grid_type_ != GridType::linear )
           for ( auto& c : mod_coord )
             switch ( grid_type_ ) {
               case GridType::logarithmic:
                 c = log10( c ); break;
               case GridType::square:
                 c *= c; break;
               default: break;
             }
         values_raw_[mod_coord] = value;
       }
       /// Return the list of values handled in the grid
       std::map<coord_t,values_t> values() const { return values_raw_; }
 
       /// Initialise the grid and all useful interpolators/accelerators
       void init() {
         if ( values_raw_.empty() )
           CG_ERROR( "GridHandler" ) << "Empty grid.";
         gsl_set_error_handler_off();
         //--- start by building grid coordinates from raw values
         for ( auto& c : coords_ )
           c.clear();
         for ( const auto& val : values_raw_ ) {
           unsigned short i = 0;
           for ( const auto& c : val.first ) {
             if ( std::find( coords_.at( i ).begin(), coords_.at( i ).end(), c ) == coords_.at( i ).end() )
               coords_.at( i ).emplace_back( c );
             ++i;
           }
         }
         for ( auto& c : coords_ )
           std::sort( c.begin(), c.end() );
         { //--- debugging of the grid coordinates
           std::ostringstream os;
           unsigned short i = 0;
           for ( const auto& cs : coords_ ) {
             os << "\n>> coordinate " << (i++) << " has " << cs.size() << " member" << ( cs.size() > 1 ? "s" : "" ) << ":";
             unsigned short j = 0;
             for ( const auto& val : cs )
               os << ( j++ % 20 == 0 ? "\n  " : " " ) << val;
           }
           CG_DEBUG( "GridHandler" ) << "Grid dump:" << os.str();
         }
         //--- particularise by dimension
         switch ( D ) {
           case 1: { //--- x |-> (f1,...)
             const gsl_interp_type* type = gsl_interp_cspline;
             //const gsl_interp_type* type = gsl_interp_steffen;
 #ifdef GOOD_GSL
             const unsigned short min_size = gsl_interp_type_min_size( type );
 #else
             const unsigned short min_size = type->min_size;
 #endif
             if ( min_size >= values_raw_.size() )
               throw CG_FATAL( "GridHandler" ) << "Not enough points for \"" << type->name << "\" type of interpolation.\n\t"
                 << "Minimum required: " << min_size << ", got " << values_raw_.size() << "!";
             for ( size_t i = 0; i < N; ++i ) {
               values_[i].reset( new double[values_raw_.size()] );
               splines_1d_.emplace_back( gsl_spline_alloc( type, values_raw_.size() ), gsl_spline_free );
             }
             std::vector<double> x_vec;
             unsigned short i = 0;
             for ( const auto& vals : values_raw_ ) {
               x_vec.emplace_back( vals.first.at( 0 ) );
               unsigned short j = 0;
               for ( const auto& val : vals.second )
                 values_[j++].get()[i++] = val;
             }
             for ( unsigned short i = 0; i < splines_1d_.size(); ++i )
               gsl_spline_init( splines_1d_.at( i ).get(), &x_vec[0], values_[i].get(), values_raw_.size() );
           } break;
           case 2: { //--- (x,y) |-> (f1,...)
 #ifdef GOOD_GSL
             const gsl_interp2d_type* type = gsl_interp2d_bilinear;
             splines_2d_.clear();
             for ( size_t i = 0; i < N; ++i ) {
               values_[i].reset( new double[coords_.at( 0 ).size() * coords_.at( 1 ).size()] );
               splines_2d_.emplace_back( gsl_spline2d_alloc( type, coords_.at( 0 ).size(), coords_.at( 1 ).size() ), gsl_spline2d_free );
             }
 
             // second loop over all points to populate the grid
             for ( const auto& val : values_raw_ ) {
               double val_x = val.first.at( 0 ), val_y = val.first.at( 1 );
               // retrieve the index of the bin in the set
               const unsigned short id_x = std::distance( coords_.at( 0 ).begin(), std::lower_bound( coords_.at( 0 ).begin(), coords_.at( 0 ).end(), val_x ) );
               const unsigned short id_y = std::distance( coords_.at( 1 ).begin(), std::lower_bound( coords_.at( 1 ).begin(), coords_.at( 1 ).end(), val_y ) );
               for ( unsigned short i = 0; i < splines_2d_.size(); ++i )
                 gsl_spline2d_set( splines_2d_.at( i ).get(), values_[i].get(), id_x, id_y, val.second[i] );
             }
 
             // initialise splines objects
             const coord_t& x_vec = coords_.at( 0 ), &y_vec = coords_.at( 1 );
             for ( unsigned short i = 0; i < splines_2d_.size(); ++i )
               gsl_spline2d_init( splines_2d_.at( i ).get(), &x_vec[0], &y_vec[0], values_[i].get(), x_vec.size(), y_vec.size() );
 #else
             CG_WARNING( "GridHandler" )
               << "GSL version ≥ 2.1 is required for spline bilinear interpolation.\n\t"
               << "Version " << GSL_VERSION << " is installed on this system!\n\t"
               << "Will use a simple bilinear approximation instead.";
 #endif
           } break;
         }
       }
+      std::array<std::pair<double,double>,D> boundaries() const {
+        std::array<std::pair<double,double>,D> out;
+        unsigned short i = 0;
+        for ( const auto& c : coords_ ) {
+          const auto& min = std::min_element( c.begin(), c.end() ), max = std::max_element( c.begin(), c.end() );
+          out[i++] = {
+            ( min != c.end() ) ? *min : std::numeric_limits<double>::infinity(),
+            ( max != c.end() ) ? *max : std::numeric_limits<double>::infinity() };
+        }
+        return out;
+      }
 
     protected:
       GridType grid_type_;
       /// List of coordinates and associated value(s) in the grid
       std::map<coord_t,values_t> values_raw_;
 
       std::vector<std::unique_ptr<gsl_interp_accel,void(*)( gsl_interp_accel* )> > accel_;
       std::vector<std::unique_ptr<gsl_spline,void(*)( gsl_spline* )> > splines_1d_;
 #ifdef GOOD_GSL
       std::vector<std::unique_ptr<gsl_spline2d,void(*)( gsl_spline2d* )> > splines_2d_;
 #endif
       std::array<coord_t,D> coords_;
       std::array<std::unique_ptr<double[]>,N> values_;
 
     private:
       void findIndices( const coord_t& coord, coord_t& min, coord_t& max ) const {
         min.reserve( D );
         max.reserve( D );
         for ( size_t i = 0; i < D; ++i ) {
           const auto& c = coords_.at( i );
           if ( coord.at( i ) < c.front() )
             min[i] = max[i] = c.front();
           else if ( coord.at( i ) > c.back() )
             min[i] = max[i] = c.back();
           else {
             auto it_coord = std::lower_bound( c.begin(), c.end(), coord.at( i ) );
             min[i] = *it_coord;
             max[i] = ( it_coord != c.end() ) ? *( it_coord++ ) : *it_coord;
           }
         }
       }
 
       struct gridpoint_t : values_t
       {
         gridpoint_t() : values_t() {}
         gridpoint_t( const values_t& arr ) : values_t( arr ) {}
         gridpoint_t operator*( double c ) const {
           gridpoint_t out = *this;
           for ( auto& a : out )
             a *= c;
           return out;
         }
         gridpoint_t operator+( const gridpoint_t& rhs ) const {
           gridpoint_t out = *this;
           for ( size_t i = 0; i < out.size(); ++i )
             out[i] += rhs[i];
           return out;
         }
       };
   };
 }
 
 #endif
 
diff --git a/CepGen/IO/HepMCHandler.cpp b/CepGen/IO/HepMCHandler.cpp
index ad0a216..e8b1948 100644
--- a/CepGen/IO/HepMCHandler.cpp
+++ b/CepGen/IO/HepMCHandler.cpp
@@ -1,136 +1,134 @@
 #include "CepGen/IO/HepMCHandler.h"
 
 #include "CepGen/Parameters.h"
 #include "CepGen/Core/Exception.h"
 
 #include "CepGen/Event/Event.h"
 #include "CepGen/Physics/Constants.h"
 
 #ifdef LIBHEPMC
 #  include "HepMC/GenVertex.h"
 #  include "HepMC/GenParticle.h"
 #endif
 
 namespace CepGen
 {
   namespace OutputHandler
   {
     HepMCHandler::HepMCHandler( const char* filename, const ExportHandler::OutputType& type ) :
       ExportHandler( type )
 #ifdef LIBHEPMC
 #  ifdef HEPMC_VERSION3
       , output_( new HepMC::WriterAscii( filename ) ),
 #  else
       , output_( new HepMC::IO_GenEvent( filename ) ),
 #  endif
       event_( new HepMC::GenEvent() )
 #endif
     {}
 
     void
     HepMCHandler::operator<<( const Event& evt )
     {
       fillEvent( evt );
 #ifdef LIBHEPMC
 #  ifdef HEPMC_VERSION3
       output_->write_event( *event_ );
 #  else
       output_->write_event( event_.get() );
 #  endif
 #endif
     }
 
     void
     HepMCHandler::setCrossSection( double xsect, double xsect_err )
     {
 #ifdef LIBHEPMC
 #  ifdef HEPMC_VERSION3
       xs_->set_cross_section( xsect, xsect_err );
       event_->add_attribute( "AlphaQCD", HepMC::make_shared<HepMC::DoubleAttribute>( Constants::alphaQCD ) );
       event_->add_attribute( "AlphaEM", HepMC::make_shared<HepMC::DoubleAttribute>( Constants::alphaEM ) );
 #  else
       xs_.set_cross_section( xsect, xsect_err );
       event_->set_alphaQCD( Constants::alphaQCD );
       event_->set_alphaQED( Constants::alphaEM );
 #  endif
 #endif
     }
 
     void
     HepMCHandler::fillEvent( const Event& evt )
     {
 #ifdef LIBHEPMC
       event_->clear();
 
       // general information
       event_->set_cross_section( xs_ );
 
       event_->set_event_number( event_num_ );
       event_->weights().push_back( 1. ); // unweighted events
 
       // filling the particles content
       const HepMC::FourVector origin( 0., 0., 0., 0. );
       Particles part_vec = evt.particles();
 
       int cm_id = 0, idx = 1;
 
 #  ifdef HEPMC_VERSION3
       HepMC::GenVertexPtr v1 = HepMC::make_shared<HepMC::GenVertex>( origin ),
                           v2 = HepMC::make_shared<HepMC::GenVertex>( origin ),
                           vcm = HepMC::make_shared<HepMC::GenVertex>( origin );
 #  else
       HepMC::GenVertex* v1 = new HepMC::GenVertex( origin ),
                        *v2 = new HepMC::GenVertex( origin ),
                        *vcm = new HepMC::GenVertex( origin );
 #  endif
       for ( unsigned int i = 0; i < part_vec.size(); ++i ) {
         const Particle part_orig = part_vec.at( i );
         HepMC::FourVector pmom( part_orig.momentum().px(),
                                 part_orig.momentum().py(),
                                 part_orig.momentum().pz(),
                                 part_orig.energy() );
 #  ifdef HEPMC_VERSION3
         HepMC::GenParticlePtr part = HepMC::make_shared<HepMC::GenParticle>( pmom, part_orig.integerPdgId(), (int)part_orig.status() );
 #  else
         HepMC::GenParticle* part = new HepMC::GenParticle( pmom, part_orig.integerPdgId(), (int)part_orig.status() );
         part->suggest_barcode( idx++ );
 #  endif
         const ParticlesIds moth = part_orig.mothers();
 
         switch ( part_orig.role() ) {
           case Particle::IncomingBeam1: { v1->add_particle_in( part ); } break;
           case Particle::IncomingBeam2: { v2->add_particle_in( part ); } break;
           case Particle::OutgoingBeam1: { v1->add_particle_out( part ); } break;
           case Particle::OutgoingBeam2: { v2->add_particle_out( part ); } break;
           case Particle::Parton1:       { v1->add_particle_out( part ); vcm->add_particle_in( part ); } break;
           case Particle::Parton2:       { v2->add_particle_out( part ); vcm->add_particle_in( part ); } break;
           case Particle::Parton3:       { v2->add_particle_out( part ); vcm->add_particle_in( part ); } break;
           case Particle::Intermediate:  { cm_id = i; continue; } break;
           case Particle::CentralSystem:
           default: {
             if ( moth.size() == 0 ) continue;
             if ( *moth.begin() == cm_id ) vcm->add_particle_out( part );
-            else {
-              std::cout << "other particle!!" << std::endl;
-              continue;
-              //FIXME secondary products... to be implemented!
-            }
+            else
+              throw CG_FATAL( "HepMCHandler:fillEvent" )
+                << "Other particle requested! Not yet implemented!";
           } break;
         }
         idx++;
       }
       event_->add_vertex( v1 );
       event_->add_vertex( v2 );
       event_->add_vertex( vcm );
 
 #  ifndef HEPMC_VERSION3
       event_->set_beam_particles( *v1->particles_in_const_begin(), *v2->particles_in_const_begin() );
       event_->set_signal_process_vertex( *v1->vertices_begin() );
       event_->set_beam_particles( *v1->particles_in_const_begin(), *v2->particles_in_const_end() );
 #  endif
 #endif
       event_num_++;
     }
   }
 }
 
diff --git a/CepGen/IO/LHEFHandler.cpp b/CepGen/IO/LHEFHandler.cpp
index e87cc2e..7429b69 100644
--- a/CepGen/IO/LHEFHandler.cpp
+++ b/CepGen/IO/LHEFHandler.cpp
@@ -1,252 +1,252 @@
 #include "CepGen/IO/LHEFHandler.h"
 
 #include "CepGen/StructureFunctions/StructureFunctions.h"
 #include "CepGen/Event/Event.h"
 #include "CepGen/Physics/Constants.h"
 
 #include "CepGen/Parameters.h"
 #include "CepGen/Version.h"
 
 namespace CepGen
 {
   namespace OutputHandler
   {
     LHEFHandler::LHEFHandler( const char* filename ) :
       ExportHandler( ExportHandler::LHE )
 #if defined ( HEPMC_LHEF )
       , lhe_output_( new LHEF::Writer( filename ) )
 #elif defined ( PYTHIA_LHEF )
       , pythia_( new Pythia8::Pythia ), lhaevt_( new LHAevent )
 #endif
     {
 #if defined ( PYTHIA_LHEF )
       lhaevt_->openLHEF( filename );
 #endif
     }
 
     LHEFHandler::~LHEFHandler()
     {
 #if defined ( PYTHIA_LHEF )
       if ( lhaevt_ )
         lhaevt_->closeLHEF( false ); // we do not want to rewrite the init block
 #endif
     }
 
     void
     LHEFHandler::initialise( const Parameters& params )
     {
       std::ostringstream oss_init;
       oss_init
         << "<!--\n"
         << "  ***** Sample generated with CepGen v" << version() << " *****\n"
         << "  * process: " << params.processName() << " (" << params.kinematics.mode << ")\n";
       if ( params.kinematics.mode != KinematicsMode::ElasticElastic ) {
         oss_init
           << "  * structure functions: " << params.kinematics.structure_functions->type << "\n";
         if ( !params.hadroniserName().empty() )
           oss_init
             << "  * hadroniser: " << params.hadroniserName() << "\n";
       }
       oss_init
         << "  *--- incoming state\n";
       if ( params.kinematics.cuts.initial.q2.valid() )
         oss_init
           << "  * Q2 range (GeV2): "
           << params.kinematics.cuts.initial.q2.min() << ", "
           << params.kinematics.cuts.initial.q2.max() << "\n";
       if ( params.kinematics.mode != KinematicsMode::ElasticElastic
         && params.kinematics.cuts.remnants.mass_single.valid() )
         oss_init
           << "  * remnants mass range (GeV/c2): "
           << params.kinematics.cuts.remnants.mass_single.min() << ", "
           << params.kinematics.cuts.remnants.mass_single.max() << "\n";
       oss_init
         << "  *--- central system\n";
       if ( params.kinematics.cuts.central.pt_single.valid() )
         oss_init
           << "  * single particle pt (GeV/c): "
           << params.kinematics.cuts.central.pt_single.min() << ", "
           << params.kinematics.cuts.central.pt_single.max() << "\n";
       if ( params.kinematics.cuts.central.energy_single.valid() )
         oss_init
           << "  * single particle energy (GeV): "
           << params.kinematics.cuts.central.energy_single.min() << ", "
           << params.kinematics.cuts.central.energy_single.max() << "\n";
       if ( params.kinematics.cuts.central.eta_single.valid() )
         oss_init
           << "  * single particle eta: "
           << params.kinematics.cuts.central.eta_single.min() << ", "
           << params.kinematics.cuts.central.eta_single.max() << "\n";
       if ( params.kinematics.cuts.central.pt_sum.valid() )
         oss_init
           << "  * total pt (GeV/c): "
           << params.kinematics.cuts.central.mass_sum.min() << ", "
           << params.kinematics.cuts.central.mass_sum.max() << "\n";
       if ( params.kinematics.cuts.central.mass_sum.valid() )
         oss_init
           << "  * total invariant mass (GeV/c2): "
           << params.kinematics.cuts.central.mass_sum.min() << ", "
           << params.kinematics.cuts.central.mass_sum.max() << "\n";
       oss_init
         << "  **************************************************\n"
         << "-->";
 #if defined ( HEPMC_LHEF )
       lhe_output_->headerBlock() << oss_init.str();
       //params.dump( lhe_output_->initComments(), false );
       LHEF::HEPRUP run = lhe_output_->heprup;
       run.IDBMUP = { (int)params.kinematics.incoming_beams.first.pdg, (int)params.kinematics.incoming_beams.second.pdg };
       run.EBMUP = { (double)params.kinematics.incoming_beams.first.pz, (double)params.kinematics.incoming_beams.second.pz };
       run.NPRUP = 1;
       run.resize();
       run.XSECUP[0] = params.integrator.result;
       run.XERRUP[0] = params.integrator.err_result;
       run.XMAXUP[0] = 1.;
       run.LPRUP[0] = 1;
       lhe_output_->heprup = run;
       lhe_output_->init();
 #elif defined ( PYTHIA_LHEF )
       oss_init << std::endl; // LHEF is usually not beautifully parsed as a standard XML...
       lhaevt_->addComments( oss_init.str() );
       lhaevt_->initialise( params );
       pythia_->settings.mode( "Beams:frameType", 5 );
       pythia_->settings.mode( "Next:numberCount", 0 ); // remove some of the Pythia output
       pythia_->settings.flag( "ProcessLevel:all", false ); // we do not want Pythia to interfere...
       pythia_->setLHAupPtr( lhaevt_.get() );
       pythia_->init();
       lhaevt_->initLHEF();
 #endif
     }
 
     void
     LHEFHandler::operator<<( const Event& ev )
     {
 #if defined ( HEPMC_LHEF )
       LHEF::HEPEUP out;
       out.heprup = &lhe_output_->heprup;
       out.XWGTUP = 1.;
       out.XPDWUP = std::pair<double,double>( 0., 0. );
       out.SCALUP = 0.;
       out.AQEDUP = Constants::alphaEM;
       out.AQCDUP = Constants::alphaQCD;
       out.NUP = ev.numParticles();
       out.resize();
       for ( unsigned short ip = 0; ip < ev.numParticles(); ++ip ) {
-        const Particle part = ev.getConstById( ip );
+        const Particle part = ev.at( ip );
         out.IDUP[ip] = part.integerPdgId(); // PDG id
         out.ISTUP[ip] = (short)part.status(); // status code
         out.MOTHUP[ip] = std::pair<int,int>( ( part.mothers().size() > 0 ) ? *part.mothers().begin()+1 : 0, ( part.mothers().size() > 1 ) ? *part.mothers().rbegin()+1 : 0 ); // mothers
         out.ICOLUP[ip] = std::pair<int,int>( 0, 0 );
         out.PUP[ip] = std::vector<double>( { { part.momentum().px(), part.momentum().py(), part.momentum().pz(), part.energy(), part.mass() } } ); // momentum
         out.VTIMUP[ip] = 0.; // invariant lifetime
         out.SPINUP[ip] = 0.;
       }
       lhe_output_->eventComments() << "haha";
       lhe_output_->hepeup = out;
       lhe_output_->writeEvent();
 #elif defined ( PYTHIA_LHEF )
       lhaevt_->feedEvent( 0, ev );
       pythia_->next();
       lhaevt_->eventLHEF();
 #endif
     }
 
     void
     LHEFHandler::setCrossSection( double xsect, double xsect_err )
     {
 #if defined ( PYTHIA_LHEF )
       lhaevt_->setCrossSection( 0, xsect, xsect_err );
 #endif
     }
 
     //---------------------------------------------------------------------------------------------
     // Define LHA event record if one uses Pythia to store the LHE
     //---------------------------------------------------------------------------------------------
 
 #if defined ( PYTHIA_LHEF )
     LHEFHandler::LHAevent::LHAevent() : LHAup( 3 )
     {}
 
     void
     LHEFHandler::LHAevent::initialise( const Parameters& params )
     {
       setBeamA( (short)params.kinematics.incoming_beams.first.pdg, params.kinematics.incoming_beams.first.pz );
       setBeamB( (short)params.kinematics.incoming_beams.second.pdg, params.kinematics.incoming_beams.second.pz );
       addProcess( 0, params.integrator.result, params.integrator.err_result, 100. );
     }
 
     void
     LHEFHandler::LHAevent::addComments( const std::string& comments )
     {
       osLHEF << comments;
     }
 
     void
     LHEFHandler::LHAevent::setCrossSection( unsigned short proc_id, double xsect, double xsect_err )
     {
       setXSec( proc_id, xsect );
       setXErr( proc_id, xsect_err );
     }
 
     void
     LHEFHandler::LHAevent::feedEvent( unsigned short proc_id, const Event& ev, bool full_event )
     {
       const double scale = ev.getOneByRole( Particle::Intermediate ).mass();
       setProcess( proc_id, 1., scale, Constants::alphaEM, Constants::alphaQCD );
 
       const Particle& ip1 = ev.getOneByRole( Particle::IncomingBeam1 ), &ip2 = ev.getOneByRole( Particle::IncomingBeam2 );
       const Particles& op1 = ev.getByRole( Particle::OutgoingBeam1 ), &op2 = ev.getByRole( Particle::OutgoingBeam2 );
       const double q2_1 = -( ip1.momentum()-op1[0].momentum() ).mass2(), q2_2 = -( ip2.momentum()-op2[0].momentum() ).mass2();
       const double x1 = q2_1/( q2_1+op1[0].mass2()-ip1.mass2() ), x2 = q2_2/( q2_2+op2[0].mass2()-ip2.mass2() );
       setIdX( ip1.integerPdgId(), ip2.integerPdgId(), x1, x2 );
 
       short parton1_pdgid = 0, parton2_pdgid = 0;
       for ( const auto& part : ev.particles() ) {
         short pdg_id = part.integerPdgId(), status = 0, moth1 = 0, moth2 = 0;
         switch ( part.role() ) {
           case Particle::Parton1:
           case Particle::Parton2: {
             if ( part.role() == Particle::Parton1 )
               parton1_pdgid = part.integerPdgId();
             if ( part.role() == Particle::Parton2 )
               parton2_pdgid = part.integerPdgId();
             if ( !full_event )
               continue;
             status = -2; // conserving xbj/Q2
           } break;
           case Particle::Intermediate: {
             if ( !full_event )
               continue;
             status = 2;
             if ( pdg_id == 0 )
-              pdg_id = ev.getConstById( *part.mothers().begin() ).integerPdgId();
+              pdg_id = ev.at( *part.mothers().begin() ).integerPdgId();
           } break;
           case Particle::IncomingBeam1:
           case Particle::IncomingBeam2: {
             if ( !full_event )
               continue;
             status = -9;
           } break;
           case Particle::OutgoingBeam1:
           case Particle::OutgoingBeam2:
           case Particle::CentralSystem: {
             status = (short)part.status();
             if ( status != 1 )
               continue;
           } break;
           default: break;
         }
         if ( full_event ) {
           const auto& mothers = part.mothers();
           if ( mothers.size() > 0 )
             moth1 = *mothers.begin()+1;
           if ( mothers.size() > 1 )
             moth2 = *mothers.rbegin()+1;
         }
         const Particle::Momentum& mom = part.momentum();
         addParticle( pdg_id, status, moth1, moth2, 0, 0, mom.px(), mom.py(), mom.pz(), mom.energy(), mom.mass(), 0. ,0., 0. );
       }
       setPdf( parton1_pdgid, parton2_pdgid, x1, x2, scale, 0., 0., true );
     }
 #endif
   }
 }
diff --git a/CepGen/Parameters.h b/CepGen/Parameters.h
index fb21f43..2b76c79 100644
--- a/CepGen/Parameters.h
+++ b/CepGen/Parameters.h
@@ -1,148 +1,146 @@
 #ifndef CepGen_Parameters_h
 #define CepGen_Parameters_h
 
 #include "CepGen/Core/Integrator.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 TamingFunctionsCollection;
   class ParametersList;
   namespace Process { class GenericProcess; }
   namespace Hadroniser { class GenericHadroniser; }
   /// 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!
       /// 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
       Process::GenericProcess* process();
       /// Name of the process considered
       std::string processName() const;
       /// Set the process to study
       void setProcess( Process::GenericProcess* proc );
       void cloneProcess( const Process::GenericProcess* proc );
 
       //----- events kinematics
 
       /// Events kinematics for phase space definition
       Kinematics kinematics;
 
       //----- VEGAS
 
       /// Collection of integrator parameters
       struct Integration
       {
         Integration();
         Integration( const Integration& );
         ~Integration();
         Integrator::Type 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
       Hadroniser::GenericHadroniser* hadroniser();
       /// Name of the hadroniser (if applicable)
       std::string hadroniserName() const;
       /// Set the hadronisation algorithm
       void setHadroniser( Hadroniser::GenericHadroniser* hadr );
-      /// Maximal number of trials for the hadronisation of the proton(s) remnants
-      unsigned int hadroniser_max_trials;
 
       //----- taming functions
 
       /// Functionals to be used to account for rescattering corrections (implemented within the process)
       std::shared_ptr<TamingFunctionsCollection> taming_functions;
 
       //----- run statistics
 
       /// Reset the total generation time and the number of events generated for this run
       void clearRunStatistics();
       /// 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<Process::GenericProcess> process_;
       std::unique_ptr<Hadroniser::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/Physics/BreitWigner.h b/CepGen/Physics/BreitWigner.h
index 65c3276..f3f0302 100644
--- a/CepGen/Physics/BreitWigner.h
+++ b/CepGen/Physics/BreitWigner.h
@@ -1,37 +1,44 @@
 #ifndef CepGen_Physics_BreitWigner_h
 #define CepGen_Physics_BreitWigner_h
 
 #include <math.h>
 
 namespace CepGen
 {
+  /// A Breit-Wigner/Cauchy distribution generator
   class BreitWigner
   {
     public:
       BreitWigner( double mean = 0., double gamma = 0., double emin = -1., double emax = -1. ) :
         mean_( mean ), gamma_( gamma ), emin_( emin ), emax_( emax ) {}
+      /// Copy constructor
       BreitWigner( const BreitWigner& oth ) :
         mean_( oth.mean_ ), gamma_( oth.gamma_ ), emin_( oth.emin_ ), emax_( oth.emax_ ) {}
 
+      /// Minimal energy to consider
       double min() const { return emin_; }
+      /// Maximal energy to consider
       double max() const { return emax_; }
+      /// Shoot a value according to parameterisation
       inline double operator()( double x ) const {
         const double val = mean_+0.5*gamma_*tan( ( 2.*x-1. )*M_PI_2 );
-        if ( ( emin_ >= 0. && val < emin_ ) || ( emax_ >= 0. && val > emax_ ) )
+        if ( emin_ >= 0. && val < emin_ )
+          return -1.;
+        if ( emax_ >= 0. && val > emax_ )
           return -1.;
         return val;
       }
 
     private:
       /// Mean of distribution
       double mean_;
       /// Width of distribution
       double gamma_;
       /// Minimal value
       double emin_;
       /// Maximal value
       double emax_;
   };
 }
 
 #endif
diff --git a/CepGen/Physics/Constants.h b/CepGen/Physics/Constants.h
index 3828b8a..0c971c6 100644
--- a/CepGen/Physics/Constants.h
+++ b/CepGen/Physics/Constants.h
@@ -1,22 +1,22 @@
 #ifndef CepGen_Physics_Constants_h
 #define CepGen_Physics_Constants_h
 
 #include <math.h>
 
 namespace CepGen
 {
   /// List of physical constants useful that may be used for the matrix element definition
   namespace Constants
   {
-    /// Electromagnetic coupling constant \f$\alpha_\textrm{em}=\frac{e^2}{4\pi\epsilon_0\hbar c}\f$
+    /// Electromagnetic coupling constant \f$\alpha_{\rm em}=\frac{e^2}{4\pi\epsilon_0\hbar c}\f$
     constexpr double alphaEM = 1./137.035;
-    /// Strong coupling constant \f$\alpha_\textrm{QCD}\f$
+    /// Strong coupling constant \f$\alpha_{\rm QCD}\f$
     constexpr double alphaQCD = 0.1184; // at the Z pole
-    /// Conversion factor between GeV^2 and barn
+    /// Conversion factor between GeV\f${}^2\f$ and barn
     constexpr double GeV2toBarn = 0.389351824e9; // 1.e4*(197.3271**2);
     constexpr double sconstb = 2.1868465e10; // 1.1868465e10;
   }
 }
 
 #endif
 
diff --git a/CepGen/Physics/Cuts.h b/CepGen/Physics/Cuts.h
index afd3147..6c8e1e7 100644
--- a/CepGen/Physics/Cuts.h
+++ b/CepGen/Physics/Cuts.h
@@ -1,31 +1,32 @@
 #ifndef CepGen_Physics_Cuts_h
 #define CepGen_Physics_Cuts_h
 
 #include "CepGen/Physics/Limits.h"
 #include <vector>
 
 namespace CepGen
 {
   /// Constraints to be applied on the events kinematics
   struct Cuts
   {
     Limits pt_single;       ///< single particle transverse momentum
     Limits eta_single;      ///< single particle pseudo-rapidity
     Limits rapidity_single; ///< single particle rapidity
     Limits energy_single;   ///< single particle energy
     Limits mass_single;     ///< single particle mass
     Limits pt_sum;          ///< multiparticle system transverse momentum
     Limits eta_sum;         ///< multiparticle system pseudo-rapidity
     Limits energy_sum;      ///< multiparticle system energy
     Limits mass_sum;        ///< multiparticle system invariant mass
     Limits pt_diff;         ///< transverse momentum balance between the central particles
     Limits phi_pt_diff;     ///< azimuthal angles difference between the central particles
     Limits rapidity_diff;   ///< rapidity balance between the central particles
     Limits q2;              ///< parton virtuality
     Limits qt;              ///< parton transverse virtuality
     Limits phi_qt;          ///< parton azimuthal angle difference
+    /// A collection of name -> limits
     std::vector<std::pair<std::string,Limits> > list() const;
   };
 }
 
 #endif
diff --git a/CepGen/Physics/GluonGrid.h b/CepGen/Physics/GluonGrid.h
index 5580ae5..588b140 100644
--- a/CepGen/Physics/GluonGrid.h
+++ b/CepGen/Physics/GluonGrid.h
@@ -1,40 +1,42 @@
 #ifndef CepGen_Physics_GluonGrid_h
 #define CepGen_Physics_GluonGrid_h
 
 #include "CepGen/IO/GridHandler.h"
 
 #define DEFAULT_KMR_GRID_PATH "gluon_mmht2014nlo_Watt.dat"
 
 /// Kimber-Martin-Ryskin unintegrated gluon densities
 namespace kmr
 {
   /// A KMR unintegrated gluon densities grid interpolator
   class GluonGrid : private CepGen::GridHandler<3,1>
   {
     public:
       struct Parameterisation {
         Parameterisation() : grid_path( DEFAULT_KMR_GRID_PATH ) {}
+        /// Location of the grid to be interpolated
         std::string grid_path;
       };
 
     public:
       /// Retrieve the grid interpolator (singleton)
       static GluonGrid& get( const char* path = DEFAULT_KMR_GRID_PATH );
 
       /// Compute the gluon flux
       double operator()( double x, double kt2, double mu2 ) const;
+      /// Grid parameterisation object
       Parameterisation params;
 
     public:
       GluonGrid( const GluonGrid& ) = delete;
       void operator=( const GridHandler& ) = delete;
 
     private:
       explicit GluonGrid( const Parameterisation& = Parameterisation() );
   };
 }
 
 #undef DEFAULT_KMR_GRID_PATH
 
 #endif
 
diff --git a/CepGen/Physics/KTFlux.h b/CepGen/Physics/KTFlux.h
index db8df10..5e16ba5 100644
--- a/CepGen/Physics/KTFlux.h
+++ b/CepGen/Physics/KTFlux.h
@@ -1,35 +1,45 @@
 #ifndef CepGen_Physics_KTFlux_h
 #define CepGen_Physics_KTFlux_h
 
 #include "CepGen/Physics/PDG.h"
 #include <ostream>
 
 namespace CepGen
 {
   class StructureFunctions;
   class HeavyIon;
+  /// Collection of fundamental constants for kT fluxes definition
   struct KTFluxParameters
   {
-    static const double kMinKTFlux, kMP, kMP2;
+    static const double kMinKTFlux; ///< Minimal value taken for a kT-factorised flux
+    static const double kMP; ///< Proton mass, un GeV/c\f${}^2\f$
+    static const double kMP2; ///< Squared proton mass
   };
   /// Type of incoming partons fluxes
   enum class KTFlux
   {
     invalid = -1,
     P_Photon_Elastic = 0,
     P_Photon_Inelastic = 1,
     P_Photon_Inelastic_Budnev = 11,
     P_Gluon_KMR = 20,
     HI_Photon_Elastic = 100
   };
+  /// Human version of the flux name
   std::ostream& operator<<( std::ostream&, const KTFlux& );
-  /// Get the flux at a given parton x/kT
+  /// \brief Compute the flux for a given parton x/kT
+  /// \param[in] type Flux modelling
   /// \param[in] x Parton momentum fraction
   /// \param[in] kt2 Transverse 2-momentum \f$\mathbf{q}_{\mathrm{T}}^2\f$ of the incoming parton
   /// \param[in] sf Structure functions evaluator
   /// \param[in] mx Outgoing diffractive proton mass
   double ktFlux( const KTFlux& type, double x, double kt2, StructureFunctions& sf, double mx = KTFluxParameters::kMP );
+  /// \brief Compute the flux (from heavy ion) for a given parton x/kT
+  /// \param[in] type Flux modelling
+  /// \param[in] x Parton momentum fraction
+  /// \param[in] kt2 Transverse 2-momentum \f$\mathbf{q}_{\mathrm{T}}^2\f$ of the incoming parton
+  /// \param[in] hi Heavy ion properties
   double ktFlux( const KTFlux& type, double x, double kt2, const HeavyIon& hi );
 }
 
 #endif
diff --git a/CepGen/Physics/Kinematics.h b/CepGen/Physics/Kinematics.h
index bafb8b8..1acddef 100644
--- a/CepGen/Physics/Kinematics.h
+++ b/CepGen/Physics/Kinematics.h
@@ -1,79 +1,80 @@
 #ifndef CepGen_Physics_Kinematics_h
 #define CepGen_Physics_Kinematics_h
 
 #include "CepGen/Core/Hasher.h"
 
 #include "CepGen/Physics/Cuts.h"
 #include "CepGen/Physics/HeavyIon.h"
 
 #include <ostream>
 #include <vector>
 #include <unordered_map>
 #include <memory>
 
 namespace CepGen
 {
   enum class PDG;
   enum class KTFlux;
   class StructureFunctions;
   /// Type of kinematics to consider for the process
   enum class KinematicsMode
   {
     invalid = -1,
     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)
   std::ostream& operator<<( std::ostream&, const KinematicsMode& );
   /// List of kinematic constraints to apply on the process phase space.
   class Kinematics
   {
     public:
       Kinematics();
       ~Kinematics();
 
+      /// Incoming beams characteristics
       struct Beam
       {
-        /// Incoming particle's momentum (in \f$\text{GeV}/c\f$)
-        double pz;
-        PDG pdg;
-        KTFlux kt_flux;
+        double pz; ///< Incoming particle momentum, in GeV/c
+        PDG pdg; ///< PDG identifier for the beam
+        KTFlux kt_flux; ///< Type of kT-factorised flux to be considered (if any)
       };
       friend std::ostream& operator<<( std::ostream&, const Beam& );
       /// Beam/primary particle's kinematics
       std::pair<Beam,Beam> incoming_beams;
       /// Set the incoming particles' momenta (if the collision is symmetric)
       void setSqrtS( double sqrts );
       /// Process centre of mass energy
       double sqrtS() const;
       /// Minimum list of central particles required
       std::vector<PDG> minimum_final_state;
 
       /// Type of kinematics to consider for the phase space
       KinematicsMode mode;
       /// Type of structure functions to consider
       std::shared_ptr<StructureFunctions> structure_functions;
 
+      /// A collection of cuts to apply on the physical phase space
       struct CutsList
       {
         CutsList();
         /// 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;
       std::string kmr_grid_path;
   };
 }
 
 #endif
 
diff --git a/CepGen/Physics/ParticleProperties.h b/CepGen/Physics/ParticleProperties.h
index e3d747a..dff3d94 100644
--- a/CepGen/Physics/ParticleProperties.h
+++ b/CepGen/Physics/ParticleProperties.h
@@ -1,27 +1,33 @@
 #ifndef CepGen_Physics_ParticleProperties_h
 #define CepGen_Physics_ParticleProperties_h
 
 namespace CepGen
 {
   enum class PDG;
   namespace ParticleProperties
   {
-    /// Mass (in GeV) of a particle
-    /// \param pdg_id PDG identifier
-    /// \return Mass of the particle in \f$\textrm{GeV}/c^2\f$
+    /** \brief Mass of a particle, in GeV/c\f${}^2\f$
+     * \param pdg_id PDG identifier
+     */
     double mass( const PDG& pdg_id );
-    /// Electric charge of a particle, in \f$e\f$
-    /// \param[in] pdg_id PDG id
+    /** \brief Electric charge of a particle, in \f$e\f$
+     * \param[in] pdg_id PDG id
+     */
     double charge( const PDG& pdg_id );
-    /// Electric charge of a particle, in \f$e\f$
-    /// \param[in] id integer PDG id
+    /** \brief Electric charge of a particle, in \f$e\f$
+     * \param[in] id integer PDG id
+     */
     double charge( int id );
+    /** \brief Colour factor for a given particle
+     * \param[in] id integer PDG id
+     */
     unsigned short colours( const PDG& pdg_id );
-    /// Total decay width of an unstable particle, in GeV
-    /// \param[in] pdg_id PDG (PDG ID)
+    /** \brief Total decay width of an unstable particle, in GeV
+     * \param[in] pdg_id PDG (PDG ID)
+     */
     double width( const PDG& pdg_id );
   }
 }
 
 #endif
 
diff --git a/CepGen/Processes/CMakeLists.txt b/CepGen/Processes/CMakeLists.txt
index 8050ba9..7489f0c 100644
--- a/CepGen/Processes/CMakeLists.txt
+++ b/CepGen/Processes/CMakeLists.txt
@@ -1,7 +1,14 @@
 file(GLOB sources *.cpp Fortran/*.f)
 
 include_directories(${PROJECT_SOURCE_DIR})
+include_directories(Fortran)
+
+set(F77_PROC_PATH ${PROJECT_SOURCE_DIR}/External/Processes)
+file(GLOB oth_proc_sources ${F77_PROC_PATH}/*.f ${F77_PROC_PATH}/CepGenWrapper.cpp)
+if(oth_proc_sources)
+  list(APPEND sources ${oth_proc_sources})
+endif()
 
 add_library(CepGenProcesses SHARED ${sources})
 
 install(TARGETS CepGenProcesses DESTINATION lib)
diff --git a/CepGen/Processes/Fortran/cepgen_blocks.inc b/CepGen/Processes/Fortran/cepgen_blocks.inc
index ff15b10..3df9e0e 100644
--- a/CepGen/Processes/Fortran/cepgen_blocks.inc
+++ b/CepGen/Processes/Fortran/cepgen_blocks.inc
@@ -1,61 +1,61 @@
+      !> \file cepgen_blocks.inc
 c     ================================================================
 c     F77 process-to-CepGen helper
 c     > this header file defines a set of common blocks useful for
 c     > the interfacing of any kt-factorised matrix element computation
 c     > to a mother CepGen instance
 c     ================================================================
 
 c     =================================================================
-c     collection of fundamental physics constants
-c     =================================================================
+      !> collection of fundamental physics constants
       common/constants/am_p,units,pi,alpha_em
       double precision am_p,units,pi,alpha_em
 
 c     =================================================================
 c     information on the full process
 c       inp1 = proton energy in lab frame
 c       inp2 = nucleus energy **per nucleon** in LAB frame
 c       Collision is along z-axis
 c     =================================================================
       common/params/icontri,iflux1,iflux2,imethod,sfmod,pdg_l,
      &     a_nuc1,z_nuc1,a_nuc2,z_nuc2,
      &     inp1,inp2
       integer icontri,iflux1,iflux2,imethod,sfmod,pdg_l,
      &     a_nuc1,z_nuc1,a_nuc2,z_nuc2
       double precision inp1,inp2
 
 c     =================================================================
 c     kt-factorisation kinematics
 c     =================================================================
       common/ktkin/q1t,q2t,phiq1t,phiq2t,y1,y2,ptdiff,phiptdiff,
      &     am_x,am_y
       double precision q1t,q2t,phiq1t,phiq2t,y1,y2,ptdiff,phiptdiff,
      &     am_x,am_y
 
 c     =================================================================
 c     phase space cuts
 c     =================================================================
       common/kincuts/ipt,iene,ieta,iinvm,iptsum,idely,
      &     pt_min,pt_max,ene_min,ene_max,eta_min,eta_max,
      &     invm_min,invm_max,ptsum_min,ptsum_max,
      &     dely_min,dely_max
       integer ipt,iene,ieta,iinvm,iptsum,idely
       double precision pt_min,pt_max,ene_min,ene_max,eta_min,eta_max,
      &     invm_min,invm_max,ptsum_min,ptsum_max,
      &     dely_min,dely_max
 
 c     =================================================================
 c     generated event kinematics
 c     =================================================================
       common/evtkin/px,py,nout,idum,ipdg,pc
       integer nout,idum,ipdg(4)
       double precision px(4),py(4),pc(4,4)
 
 c     =================================================================
 c     helpers for the evaluation of unintegrated parton fluxes
 c     =================================================================
       external CepGen_kT_flux,CepGen_kT_flux_HI
       double precision CepGen_kT_flux,CepGen_kT_flux_HI
       external CepGen_particle_charge,CepGen_particle_mass
       double precision CepGen_particle_charge,CepGen_particle_mass
 
diff --git a/CepGen/Processes/Fortran/cepgen_print.f b/CepGen/Processes/Fortran/cepgen_print.f
index 38954f6..6c11061 100644
--- a/CepGen/Processes/Fortran/cepgen_print.f
+++ b/CepGen/Processes/Fortran/cepgen_print.f
@@ -1,40 +1,42 @@
+      !> \file cepgen_print.f
       subroutine cepgen_print
+      !> Print useful run information in standard stream
       implicit none
       include 'cepgen_blocks.inc'
       logical params_shown
       data params_shown/.false./
       save params_shown
 
       if(params_shown) return
 
       print *,'========================================================'
       print *,'Parameter                                  value(s)'
       print *,'--------------------------------------------------------'
       print 101,'Process mode:',icontri
       print 101,'Computation method:',imethod
       print 101,'Structure functions:',sfmod
       print 101,'Central system PDG:',pdg_l
       print 103,'Beams momenta:',inp1,inp2
       print 102,'Fluxes modes:',iflux1,iflux2
       print 104,'Beams (A,Z):',a_nuc1,z_nuc1,a_nuc2,z_nuc2
       print *,'========================================================'
       print *,'Cut                        enabled   minimum     maximum'
       print *,'--------------------------------------------------------'
       print 100,'pt(single)',ipt,pt_min,pt_max
       print 100,'energy(single)',iene,ene_min,ene_max
       print 100,'eta(single)',ieta,eta_min,eta_max
       print 100,'m(sum)',iinvm,invm_min,invm_max
       print 100,'pt(sum)',iptsum,ptsum_min,ptsum_max
       print 100,'delta(y)',idely,dely_min,dely_max
       print *,'========================================================'
 
       params_shown=.true.
 
 100   format(A26,'     ',L2,f12.4,f12.4)
 101   format(A33,I12)
 102   format(A33,I12,I12)
 103   format(A33,f12.2,f12.2)
 104   format(A33,'   (',I3,',',I3,'),  (',I3,',',I3,')')
 
       end
 
diff --git a/CepGen/Processes/FortranKTProcess.cpp b/CepGen/Processes/FortranKTProcess.cpp
index c5c6f01..8ec897a 100644
--- a/CepGen/Processes/FortranKTProcess.cpp
+++ b/CepGen/Processes/FortranKTProcess.cpp
@@ -1,187 +1,231 @@
 #include "CepGen/Processes/FortranKTProcess.h"
 #include "CepGen/Core/ParametersList.h"
 
 #include "CepGen/StructureFunctions/StructureFunctions.h"
 #include "CepGen/Event/Event.h"
 
 #include "CepGen/Physics/KTFlux.h"
 #include "CepGen/Physics/Constants.h"
 #include "CepGen/Physics/PDG.h"
 
 extern "C"
 {
+  /// General physics constants
   struct Constants {
-    double m_p, units, pi, alpha_em;
+    double m_p; ///< Proton mass
+    double units; ///< Conversion factor GeV\f${}^2\to\f$ barn
+    double pi; ///< \f$\pi\f$
+    double alpha_em; ///< Electromagnetic coupling constant
   };
+  /// Generic run parameters
   struct Parameters {
-    int icontri, iflux1, iflux2, imethod, sfmod, pdg_l, a_nuc1, z_nuc1, a_nuc2, z_nuc2;
-    double inp1, inp2;
+    int icontri; ///< Kinematics mode
+    int iflux1; ///< Type of kT-factorised flux for first incoming parton
+    int iflux2; ///< Type of kT-factorised flux for second incoming parton
+    int imethod; ///< Computation method for matrix element
+    int sfmod; ///< Structure functions modelling
+    int pdg_l; ///< Central system PDG id
+    int a_nuc1; ///< First beam mass number
+    int z_nuc1; ///< First beam atomic number
+    int a_nuc2; ///< Second beam mass number
+    int z_nuc2; ///< Second beam atomic number
+    double inp1; ///< First beam momentum, in GeV/c
+    double inp2; ///< Second beam momentum, in GeV/c
   };
+  /// Kinematics properties of the kT-factorised process
   struct KtKinematics {
-   double q1t, q2t, phiq1t, phiq2t, y1, y2, ptdiff, phiptdiff, m_x, m_y;
+   double q1t; ///< Transverse momentum of the first incoming parton
+   double q2t; ///< Transverse momentum of the second incoming parton
+   double phiq1t; ///< Azimutal angle of the first incoming parton
+   double phiq2t; ///< Azimutal angle of the second incoming parton
+   double y1; ///< First incoming parton rapidity
+   double y2; ///< Second incoming parton rapidity
+   double ptdiff; ///< Central system pT balance
+   double phiptdiff; ///< Central system azimutal angle difference
+   double m_x; ///< Invariant mass for the first diffractive state
+   double m_y; ///< Invariant mass for the second diffractive state
   };
+  /// Phase space cuts for event kinematics
   struct KinematicsCuts {
-    int ipt, iene, ieta, iinvm, iptsum, idely;
-    double pt_min, pt_max, ene_min, ene_max, eta_min, eta_max;
-    double invm_min, invm_max, ptsum_min, ptsum_max;
-    double dely_min, dely_max;
+    int ipt; ///< Switch for cut on single particle transverse momentum
+    int iene; ///< Switch for cut on single particle energy
+    int ieta; ///< Switch for cut on single particle pseudo-rapidity
+    int iinvm; ///< Switch for cut on central system invariant mass
+    int iptsum; ///< Switch for cut on central system transverse momentum
+    int idely; ///< Switch for cut on rapididty difference
+    double pt_min; ///< Minimal single particle transverse momentum
+    double pt_max; ///< Maximal single particle transverse momentum
+    double ene_min; ///< Minimal single particle energy
+    double ene_max; ///< Maximal single particle energy
+    double eta_min; ///< Minimal single particle pseudo-rapidity
+    double eta_max; ///< Maximal single particle pseudo-rapidity
+    double invm_min; ///< Minimal central system invariant mass
+    double invm_max; ///< Maximal central system invariant mass
+    double ptsum_min; ///< Minimal central system transverse momentum
+    double ptsum_max; ///< Maximal central system transverse momentum
+    double dely_min; ///< Minimal rapidity difference for central system
+    double dely_max; ///< Maximal rapidity difference for central system
   };
+  /// Single event kinematics
   struct EventKinematics {
-    double px[4], py[4];
-    int nout, idum, pdg[4];
-    double pc[4][4];
+    double px[4]; ///< 4-momentum of first outgoing proton state
+    double py[4]; ///< 4-momentum of second outgoing proton state
+    int nout; ///< Number of particles in central system
+    int idum; ///< Placeholder for blocks alignment
+    int pdg[4]; ///< PDG ids of all particles in central system
+    double pc[4][4]; ///< 4-momenta of all particles in central system
   };
 
   extern Constants constants_;
   extern Parameters params_;
   extern KtKinematics ktkin_;
   extern KinematicsCuts kincuts_;
   extern EventKinematics evtkin_;
 }
 
 namespace CepGen
 {
   namespace Process
   {
     FortranKTProcess::FortranKTProcess( const ParametersList& params, const char* name, const char* descr, std::function<void( double& )> func ) :
       GenericKTProcess( params, name, descr, { { PDG::photon, PDG::photon } }, { PDG::muon, PDG::muon } ),
       pair_( params.get<int>( "pair", 13 ) ),
       method_( params.get<int>( "method", 1 ) ),
       func_( func )
     {
-      constants_.m_p = ParticleProperties::mass( PDG::proton );
+      constants_.m_p = GenericProcess::mp_;
       constants_.units = Constants::GeV2toBarn;
       constants_.pi = M_PI;
       constants_.alpha_em = Constants::alphaEM;
     }
 
     void
     FortranKTProcess::preparePhaseSpace()
     {
       mom_ip1_ = event_->getOneByRole( Particle::IncomingBeam1 ).momentum();
       mom_ip2_ = event_->getOneByRole( Particle::IncomingBeam2 ).momentum();
 
       registerVariable( y1_, Mapping::linear, cuts_.cuts.central.rapidity_single, { -6., 6. }, "First central particle rapidity" );
       registerVariable( y2_, Mapping::linear, cuts_.cuts.central.rapidity_single, { -6., 6. }, "Second central particle rapidity" );
       registerVariable( pt_diff_, Mapping::linear, cuts_.cuts.central.pt_diff, { 0., 50. }, "Transverse momentum difference between central particles" );
       registerVariable( phi_pt_diff_, Mapping::linear, cuts_.cuts.central.phi_pt_diff, { 0., 2.*M_PI }, "Central particles azimuthal angle difference" );
 
       //===========================================================================================
       // feed phase space cuts to the common block
       //===========================================================================================
 
       cuts_.cuts.central.pt_single.save( (bool&)kincuts_.ipt, kincuts_.pt_min, kincuts_.pt_max );
       cuts_.cuts.central.energy_single.save( (bool&)kincuts_.iene, kincuts_.ene_min, kincuts_.ene_max );
       cuts_.cuts.central.eta_single.save( (bool&)kincuts_.ieta, kincuts_.eta_min, kincuts_.eta_max );
       cuts_.cuts.central.mass_sum.save( (bool&)kincuts_.iinvm, kincuts_.invm_min, kincuts_.invm_max );
       cuts_.cuts.central.pt_sum.save( (bool&)kincuts_.iptsum, kincuts_.ptsum_min, kincuts_.ptsum_max );
       cuts_.cuts.central.rapidity_diff.save( (bool&)kincuts_.idely, kincuts_.dely_min, kincuts_.dely_max );
 
       //===========================================================================================
       // feed run parameters to the common block
       //===========================================================================================
 
       params_.icontri = (int)cuts_.mode;
       params_.imethod = method_;
       params_.sfmod = (int)cuts_.structure_functions->type;
       params_.pdg_l = pair_;
 
       //-------------------------------------------------------------------------------------------
       // incoming beams information
       //-------------------------------------------------------------------------------------------
 
       params_.inp1 = cuts_.incoming_beams.first.pz;
       params_.inp2 = cuts_.incoming_beams.second.pz;
       const HeavyIon in1 = (HeavyIon)cuts_.incoming_beams.first.pdg;
       if ( in1 ) {
         params_.a_nuc1 = in1.A;
         params_.z_nuc1 = (unsigned short)in1.Z;
         if ( params_.z_nuc1 > 1 ) {
           event_->getOneByRole( Particle::IncomingBeam1 ).setPdgId( (PDG)in1 );
           event_->getOneByRole( Particle::OutgoingBeam1 ).setPdgId( (PDG)in1 );
         }
       }
       else
         params_.a_nuc1 = params_.z_nuc1 = 1;
 
       const HeavyIon in2 = (HeavyIon)cuts_.incoming_beams.second.pdg;
       if ( in2 ) {
         params_.a_nuc2 = in2.A;
         params_.z_nuc2 = (unsigned short)in2.Z;
         if ( params_.z_nuc2 > 1 ) {
           event_->getOneByRole( Particle::IncomingBeam2 ).setPdgId( (PDG)in2 );
           event_->getOneByRole( Particle::OutgoingBeam2 ).setPdgId( (PDG)in2 );
         }
       }
       else
         params_.a_nuc2 = params_.z_nuc2 = 1;
 
       //-------------------------------------------------------------------------------------------
       // intermediate partons information
       //-------------------------------------------------------------------------------------------
 
       params_.iflux1 = (int)cuts_.incoming_beams.first.kt_flux;
       params_.iflux2 = (int)cuts_.incoming_beams.second.kt_flux;
       if ( (KTFlux)params_.iflux1 == KTFlux::P_Gluon_KMR )
         event_->getOneByRole( Particle::Parton1 ).setPdgId( PDG::gluon );
       if ( (KTFlux)params_.iflux2 == KTFlux::P_Gluon_KMR )
         event_->getOneByRole( Particle::Parton2 ).setPdgId( PDG::gluon );
     }
 
     double
     FortranKTProcess::computeKTFactorisedMatrixElement()
     {
       ktkin_.q1t = qt1_;
       ktkin_.q2t = qt2_;
       ktkin_.phiq1t = phi_qt1_;
       ktkin_.phiq2t = phi_qt2_;
       ktkin_.y1 = y1_;
       ktkin_.y2 = y2_;
       ktkin_.ptdiff = pt_diff_;
       ktkin_.phiptdiff = phi_pt_diff_;
       ktkin_.m_x = MX_;
       ktkin_.m_y = MY_;
+
+      //--- compute the event weight
       double weight = 0.;
       func_( weight );
       return weight;
     }
 
     void
     FortranKTProcess::fillCentralParticlesKinematics()
     {
       //===========================================================================================
       // outgoing beam remnants
       //===========================================================================================
 
       PX_ = Particle::Momentum( evtkin_.px );
       PY_ = Particle::Momentum( evtkin_.py );
       // express these momenta per nucleon
       PX_ *= 1./params_.a_nuc1;
       PY_ *= 1./params_.a_nuc2;
 
-//std::cout << PX_ << PY_ << std::endl;
-
       //===========================================================================================
       // intermediate partons
       //===========================================================================================
 
       const Particle::Momentum mom_par1 = mom_ip1_-PX_, mom_par2 = mom_ip2_-PY_;
       event_->getOneByRole( Particle::Parton1 ).setMomentum( mom_par1 );
       event_->getOneByRole( Particle::Parton2 ).setMomentum( mom_par2 );
       event_->getOneByRole( Particle::Intermediate ).setMomentum( mom_par1+mom_par2 );
 
       //===========================================================================================
       // central system
       //===========================================================================================
 
       Particles& oc = event_->getByRole( Particle::CentralSystem );
       for ( int i = 0; i < evtkin_.nout; ++i ) {
-        auto& p = oc[i];
+        Particle& p = oc[i];
         p.setPdgId( evtkin_.pdg[i] );
         p.setStatus( Particle::Status::FinalState );
         p.setMomentum( Particle::Momentum( evtkin_.pc[i] ) );
       }
     }
   }
 }
 
diff --git a/CepGen/Processes/FortranKTProcess.h b/CepGen/Processes/FortranKTProcess.h
index af15df0..7a726c0 100644
--- a/CepGen/Processes/FortranKTProcess.h
+++ b/CepGen/Processes/FortranKTProcess.h
@@ -1,35 +1,38 @@
 #ifndef CepGen_Processes_FortranKTProcess_h
 #define CepGen_Processes_FortranKTProcess_h
 
 #include "GenericKTProcess.h"
 #include <functional>
 
 namespace CepGen
 {
   namespace Process
   {
     /// Compute the matrix element for a generic \f$k_T\f$-factorised process defined in a Fortran subroutine
     class FortranKTProcess : public GenericKTProcess
     {
       public:
         FortranKTProcess( const ParametersList& params, const char* name, const char* descr, std::function<void(double&)> func );
         ProcessPtr clone() const override { return ProcessPtr( new FortranKTProcess( *this ) ); }
 
       private:
         void preparePhaseSpace() override;
         double computeKTFactorisedMatrixElement() override;
         void fillCentralParticlesKinematics() override;
 
-        int pair_;
-        int method_;
-        /// Subroutine to be called for weight computation
-        std::function<void(double&)> func_;
-        double y1_, y2_, pt_diff_, phi_pt_diff_;
+        int pair_; ///< Outgoing particles type
+        int method_; ///< Computation method for the process
+        std::function<void(double&)> func_; ///< Subroutine to be called for weight computation
+        double y1_; ///< First outgoing particle rapidity
+        double y2_; ///< Second outgoing particle rapidity
+        double pt_diff_; ///< Transverse momentum balance between outgoing particles
+        double phi_pt_diff_; ///< Azimutal angle difference between outgoing particles
 
-        Particle::Momentum mom_ip1_, mom_ip2_;
+        Particle::Momentum mom_ip1_; ///< First incoming beam momentum
+        Particle::Momentum mom_ip2_; ///< Second incoming beam momentum
     };
   }
 }
 
 #endif
 
diff --git a/CepGen/Processes/FortranProcesses.h b/CepGen/Processes/FortranProcesses.h
new file mode 100644
index 0000000..e07e32b
--- /dev/null
+++ b/CepGen/Processes/FortranProcesses.h
@@ -0,0 +1,52 @@
+#ifndef CepGen_Processes_FortranProcesses_h
+#define CepGen_Processes_FortranProcesses_h
+
+#include "CepGen/Processes/FortranKTProcess.h"
+
+namespace CepGen
+{
+  namespace Process
+  {
+    /// A Fortran process handler
+    struct FortranProcess
+    {
+      const char* name; ///< CepGen-readable process name
+      void ( *method )( double& ); ///< Pointer to the weight computation functional
+      const char* description; ///< Human-readable process description
+    };
+    /// Fortran processes collector
+    class FortranProcessesHandler
+    {
+      public:
+        /// Static collector retrieval method
+        static FortranProcessesHandler& get() {
+          static FortranProcessesHandler fph;
+          return fph;
+        }
+        /// Register a Fortran process into the collector
+        void add( const FortranProcess& proc ) {
+          processes_.emplace_back( proc );
+        }
+        /// Get a list of processes handled by this collector
+        const std::vector<FortranProcess>& list() const { return processes_; }
+        /// Generic copy-constructor
+        FortranProcessesHandler( const FortranProcessesHandler& ) = delete;
+
+      private:
+        explicit FortranProcessesHandler() {}
+        std::vector<FortranProcess> processes_;
+    };
+
+    void generateFortranProcesses();
+  }
+}
+#define DECLARE_FORTRAN_SUBROUTINE( method ) \
+  extern "C" { extern void method ## _( double& ); }
+#define BEGIN_FORTRAN_PROCESSES_ENUM \
+  namespace CepGen { namespace Process { void generateFortranProcesses() {
+#define REGISTER_FORTRAN_PROCESS( name, method, description ) \
+  CepGen::Process::FortranProcessesHandler::get().add( CepGen::Process::FortranProcess{ name, method ## _, description } );
+#define END_FORTRAN_PROCESSES_ENUM }}}
+
+#endif
+
diff --git a/CepGen/Processes/GamGamLL.h b/CepGen/Processes/GamGamLL.h
index cdc3528..d7f46f2 100644
--- a/CepGen/Processes/GamGamLL.h
+++ b/CepGen/Processes/GamGamLL.h
@@ -1,229 +1,221 @@
 #ifndef CepGen_Processes_GamGamLL_h
 #define CepGen_Processes_GamGamLL_h
 
 #include "CepGen/Processes/GenericProcess.h"
 #include "CepGen/Core/ParametersList.h"
 
 namespace CepGen
 {
   namespace Process
   {
     /**
      * Full class of methods and objects to compute the full analytic matrix element
-     * \cite Vermaseren1983347 for the \f$\gamma\gamma\to\ell^{+}\ell^{-}\f$ process
+     * \cite Vermaseren:1982cz for the \f$\gamma\gamma\to\ell^{+}\ell^{-}\f$ process
      * according to a set of kinematic constraints provided for the incoming and
      * outgoing particles (the Kinematics object).
-     * The particle roles in this process are defined as following: \n
-     * \image latex lpair_kinematics.pdf Detailed particle roles in the two-photon process as defined by the @a GamGamLL object. The incoming protons/electrons are denoted by a role 1, and 2, as the outgoing protons/protons remnants/ electrons carry the indices 3 and 5. The two outgoing leptons have the roles 6 and 7, while the lepton/antilepton distinction is done randomly (thus, the arrow convention is irrelevant here).
-     *
      * The \a f function created by this Process child has its \a _ndim -dimensional
      * coordinates mapped as :
      * - 0 = \f$t_1\f$, first incoming photon's virtuality
      * - 1 = \f$t_2\f$, second incoming photon's virtuality
      * - 2 = \f$s_2\f$ mapping
      * - 3 = yy4 = \f$\cos\left(\pi x_3\right)\f$ definition
      * - 4 = \f$w_4\f$, the two-photon system's invariant mass
-     * - 5 = xx6 = \f$\frac{1}{2}\left(1-\cos\theta^\text{CM}_6\right)\f$ definition (3D rotation of the first outgoing lepton with respect to the two-photon centre-of-mass system). If the @a nm_ optimisation flag is set this angle coefficient value becomes
-     *   \f[\frac{1}{2}\left(\frac{a_\text{map}}{b_\text{map}}\frac{\beta-1}{\beta+1}+1\right)\f]
-     *   with \f$a_\text{map}=\frac{1}{2}\left(w_4-t_1-t_2\right)\f$, \f$b_\text{map}=\frac{1}{2}\sqrt{\left(\left(w_4-t_1-t_2\right)^2-4t_1t_2\right)\left(1-4\frac{w_6}{w_4}\right)}\f$, and \f$\beta=\left(\frac{a_\text{map}+b_\text{map}}{a_\text{map}-b_\text{map}}\right)^{2x_5-1}\f$
-     *   and the \a fJacobian element is scaled by a factor \f$\frac{1}{2}\frac{\left(a_\text{map}^2-b_\text{map}^2\cos^2\theta^\text{CM}_6\right)}{a_\text{map}b_\text{map}}\log\left(\frac{a_\text{map}+b_\text{map}}{a_\text{map}-b_\text{map}}\right)\f$
-     * - 6 = _phicm6_, or \f$\phi_6^\text{CM}\f$ the rotation angle of the dilepton system in the centre-of-mass
+     * - 5 = xx6 = \f$\frac{1}{2}\left(1-\cos\theta^{\rm CM}_6\right)\f$ definition (3D rotation of the first outgoing lepton with respect to the two-photon centre-of-mass system). If the \a nm_ optimisation flag is set this angle coefficient value becomes
+     *   \f[\frac{1}{2}\left(\frac{a_{\rm map}}{b_{\rm map}}\frac{\beta-1}{\beta+1}+1\right)\f]
+     *   with \f$a_{\rm map}=\frac{1}{2}\left(w_4-t_1-t_2\right)\f$, \f$b_{\rm map}=\frac{1}{2}\sqrt{\left(\left(w_4-t_1-t_2\right)^2-4t_1t_2\right)\left(1-4\frac{w_6}{w_4}\right)}\f$, and \f$\beta=\left(\frac{a_{\rm map}+b_{\rm map}}{a_{\rm map}-b_{\rm map}}\right)^{2x_5-1}\f$
+     *   and the Jacobian element is scaled by a factor \f$\frac{1}{2}\frac{\left(a_{\rm map}^2-b_{\rm map}^2\cos^2\theta^{\rm CM}_6\right)}{a_{\rm map}b_{\rm map}}\log\left(\frac{a_{\rm map}+b_{\rm map}}{a_{\rm map}-b_{\rm map}}\right)\f$
+     * - 6 = _phicm6_, or \f$\phi_6^{\rm CM}\f$ the rotation angle of the dilepton system in the centre-of-mass
      *   system
      * - 7 = \f$x_q\f$, \f$w_X\f$ mappings, as used in the single- and double-dissociative
      *   cases only
      * \brief Compute the matrix element for a CE \f$\gamma\gamma\to\ell^{+}\ell^{-}\f$
      *  process
      */
     class GamGamLL : public GenericProcess
     {
       public:
-        /// Class constructor ; set the mandatory parameters before integration and events generation
-        /// \param[in] nopt Optimisation (legacy from LPAIR)
+        /// \brief Class constructor: set the mandatory parameters before integration and events generation
+        /// \param[in] params General process parameters (nopt = Optimisation, legacy from LPAIR)
         explicit GamGamLL( const ParametersList& params = ParametersList() );
         ProcessPtr clone() const override { return ProcessPtr( new GamGamLL( *this ) ); }
 
         void addEventContent() override;
         void beforeComputeWeight() override;
-        /// Compute the process' weight for the given point
-        /// \return \f$\mathrm d\sigma(\mathbf x)(\gamma\gamma\to\ell^{+}\ell^{-})\f$,
-        ///   the differential cross-section for the given point in the phase space.
         double computeWeight() override;
         unsigned int numDimensions() const override;
         void setKinematics( const Kinematics& cuts ) override;
         void fillKinematics( bool ) override;
         /// Compute the ougoing proton remnant mass
         /// \param[in] x A random number (between 0 and 1)
         /// \param[in] outmass The maximal outgoing particles' invariant mass
         /// \param[in] lepmass The outgoing leptons' mass
         /// \param[out] dw The size of the integration bin
         /// \return Mass of the outgoing proton remnant
         double computeOutgoingPrimaryParticlesMasses( double x, double outmass, double lepmass, double& dw );
         /// Set all the kinematic variables for the outgoing proton remnants, and prepare the hadronisation
-        /// \param[in] part_ Particle to "prepare" for the hadronisation to be performed
-        void prepareHadronisation( Particle *part_ );
+        /// \param[in] part Particle to "prepare" for the hadronisation to be performed
+        void prepareHadronisation( Particle *part );
 
       private:
         /**
          * Calculate energies and momenta of the
          *  1st, 2nd (resp. the "proton-like" and the "electron-like" incoming particles),
          *  3rd (the "proton-like" outgoing particle),
          *  4th (the two-photons central system), and
          *  5th (the "electron-like" outgoing particle) particles in the overall centre-of-mass frame.
          * \brief Energies/momenta computation for the various particles, in the CM system
          * \return Success state of the operation
          */
         bool orient();
         /**
          * Compute the expression of the matrix element squared for the \f$\gamma\gamma\rightarrow\ell^{+}\ell^{-}\f$ process.
          * It returns the value of the convolution of the form factor or structure functions with the central two-photons matrix element squared.
          * \brief Computes the matrix element squared for the requested process
          * \return Full matrix element for the two-photon production of a pair of spin\f$-\frac{1}{2}-\f$point particles.
          *  It is noted as \f[
          *  M = \frac{1}{4bt_1 t_2}\sum_{i=1}^2\sum_{j=1}^2 u_i v_j t_{ij} = \frac{1}{4}\frac{u_1 v_1 t_{11}+u_2 v_1 t_{21}+u_1 v_2 t_{12}+u_2 v_2 t_{22}}{t_1 t_2 b}
          * \f] where \f$b\f$ = \a bb_ is defined in \a ComputeWeight as : \f[
-         *  b = t_1 t_2+\left(w_{\gamma\gamma}\sin^2{\theta^\text{CM}_6}+4m_\ell\cos^2{\theta^\text{CM}_6}\right) p_g^2
+         *  b = t_1 t_2+\left(w_{\gamma\gamma}\sin^2{\theta^{\rm CM}_6}+4m_\ell\cos^2{\theta^{\rm CM}_6}\right) p_g^2
          * \f]
          */
         double periPP( int, int );
         /**
          * Describe the kinematics of the process \f$p_1+p_2\to p_3+p_4+p_5\f$ in terms of Lorentz-invariant variables.
          * These variables (along with others) will then be fed into the \a PeriPP method (thus are essential for the evaluation of the full matrix element).
          * \return Success state of the operation
          */
         bool pickin();
 
         /// Internal switch for the optimised code version (LPAIR legacy ; unimplemented here)
         int n_opt_;
         int pair_;
 
         Limits w_limits_;
         Limits q2_limits_;
         Limits mx_limits_;
         struct Masses
         {
           Masses();
           /// squared mass of the first proton-like outgoing particle
           double MX2_;
           /// squared mass of the second proton-like outgoing particle
           double MY2_;
           /// squared mass of the outgoing leptons
           double Ml2_;
           /// \f$\delta_2=m_1^2-m_2^2\f$ as defined in Vermaseren's paper
-          /// \cite Vermaseren1983347 for the full definition of this quantity
+          /// \cite Vermaseren:1982cz for the full definition of this quantity
           double w12_;
 
           /// \f$\delta_1=m_3^2-m_1^2\f$ as defined in Vermaseren's paper
-          /// \cite Vermaseren1983347 for the full definition of this quantity
+          /// \cite Vermaseren:1982cz for the full definition of this quantity
           double w31_;
           double dw31_;
           /// \f$\delta_4=m_5^2-m_2^2\f$ as defined in Vermaseren's paper
-          /// \cite Vermaseren1983347 for the full definition of this quantity
+          /// \cite Vermaseren:1982cz for the full definition of this quantity
           double w52_;
           double dw52_;
         };
         Masses masses_;
 
         /// energy of the first proton-like incoming particle
         double ep1_;
         /// energy of the second proton-like incoming particle
         double ep2_;
         double p_cm_;
 
         /// energy of the two-photon central system
         double ec4_;
         /// 3-momentum norm of the two-photon central system
         double pc4_;
         /// mass of the two-photon central system
         double mc4_;
         /// squared mass of the two-photon central system
         double w4_;
 
         /// \f$p_{12} = \frac{1}{2}\left(s-m_{p_1}^2-m_{p_2}^2\right)\f$
         double p12_;
         double p1k2_, p2k1_;
         /// \f$p_{13} = -\frac{1}{2}\left(t_1-m_{p_1}^2-m_{p_3}^2\right)\f$
         double p13_;
         double p14_, p25_;
 
         double q1dq_, q1dq2_;
 
         double s1_, s2_;
 
         double epsi_;
         double g5_, g6_;
         double a5_, a6_;
         double bb_;
 
         double gram_;
         double dd1_, dd2_, dd3_;
         /// \f$\delta_5=m_4^2-t_1\f$ as defined in Vermaseren's paper
-        /// \cite Vermaseren1983347 for the full definition of this quantity
+        /// \cite Vermaseren:1982cz for the full definition of this quantity
         double dd4_;
         double dd5_;
         /**
          * Invariant used to tame divergences in the matrix element computation. It is defined as
          * \f[\Delta = \left(p_1\cdot p_2\right)\left(q_1\cdot q_2\right)-\left(p_1\cdot q_2\right)\left(p_2\cdot q_1\right)\f]
          * with \f$p_i, q_i\f$ the 4-momenta associated to the incoming proton-like particle and to the photon emitted from it.
          */
         double delta_;
         double g4_;
         double sa1_, sa2_;
 
         double sl1_;
 
         /// cosine of the polar angle for the two-photons centre-of-mass system
         double cos_theta4_;
         /// sine of the polar angle for the two-photons centre-of-mass system
         double sin_theta4_;
 
         double al4_;
         double be4_;
         double de3_, de5_;
         double pt4_;
 
         /// Kinematics of the first incoming proton
         Particle::Momentum p1_lab_;
         /// Kinematics of the second incoming proton
         Particle::Momentum p2_lab_;
         /// Kinematics of the first outgoing proton
         Particle::Momentum p3_lab_;
         /// Kinematics of the two-photon system (in the two-proton CM)
         Particle::Momentum p4_lab_;
         /// Kinematics of the second outgoing proton
         Particle::Momentum p5_lab_;
         /// Kinematics of the first outgoing lepton (in the two-proton CM)
         Particle::Momentum p6_cm_;
         /// Kinematics of the second outgoing lepton (in the two-proton CM)
         Particle::Momentum p7_cm_;
         double jacobian_;
 
       private:
         /**
-         * Define modified variables of integration to avoid peaks integrations (see @cite Vermaseren1983347 for details)
+         * Define modified variables of integration to avoid peaks integrations (see \cite Vermaseren:1982cz for details)
          * Return a set of two modified variables of integration to maintain the stability of the integrant. These two new variables are :
          * - \f$y_{out} = x_{min}\left(\frac{x_{max}}{x_{min}}\right)^{exp}\f$ the new variable
          * - \f$\mathrm dy_{out} = x_{min}\left(\frac{x_{max}}{x_{min}}\right)^{exp}\log\frac{x_{min}}{x_{max}}\f$, the new variable's differential form
-         * @brief Redefine the variables of integration in order to avoid the strong peaking of the integrant.
-         * @param[in] expo Exponant
-         * @param[in] xmin Minimal value of the variable
-         * @param[in] xmax Maximal value of the variable
-         * @param[out] out The new variable definition
-         * @param[out] dout The differential variant of the new variable definition
-         * @param[in] var_name The variable name
-         * @note This method overrides the set of `mapxx` subroutines in ILPAIR, with a slight difference according to the sign of the
+         * \brief Redefine the variables of integration in order to avoid the strong peaking of the integrant
+         * \param[in] expo Exponant
+         * \param[in] lim Min/maximal value of the variable
+         * \param[out] out The new variable definition
+         * \param[out] dout The bin width the new variable definition
+         * \param[in] var_name The variable name
+         * \note This method overrides the set of `mapxx` subroutines in ILPAIR, with a slight difference according to the sign of the
          *  \f$\mathrm dy_{out}\f$ parameter :
          *  - left unchanged :
          * > `mapw2`, `mapxq`, `mapwx`, `maps2`
          *  - opposite sign :
          * > `mapt1`, `mapt2`
          */
-        void map( double expo, const Limits& lim, double& out, double& dout, const std::string& var_name="" );
+        void map( double expo, const Limits& lim, double& out, double& dout, const std::string& var_name = "" );
         void mapla( double y, double z, int u, double xm, double xp, double& x, double& d );
         /// Compute the electric/magnetic form factors for the two considered \f$Q^{2}\f$ momenta transfers
         void formFactors( double q1, double q2, FormFactors& fp1, FormFactors& fp2 ) const;
     };
   }
 }
 
 #endif
-
diff --git a/CepGen/Processes/GenericKTProcess.h b/CepGen/Processes/GenericKTProcess.h
index e5ce598..a1fd04c 100644
--- a/CepGen/Processes/GenericKTProcess.h
+++ b/CepGen/Processes/GenericKTProcess.h
@@ -1,143 +1,143 @@
 #ifndef CepGen_Processes_GenericKTProcess_h
 #define CepGen_Processes_GenericKTProcess_h
 
 #include "GenericProcess.h"
 
 namespace CepGen
 {
   class ParametersList;
   namespace Process
   {
     /**
      * A generic kT-factorisation process.
      * \note
      * - First 4 dimensions of the phase space are required for the
      *    incoming partons' virtualities (radial and azimuthal coordinates).
      * - Last 0-2 dimensions may be used for the scattered diffractive
      *    system(s)' invariant mass definition.
      * \brief Class template to define any kT-factorisation process
      * \author Laurent Forthomme <laurent.forthomme@cern.ch>
      * \date Apr 2016
      */
     class GenericKTProcess : public GenericProcess
     {
       public:
         /// Class constructor
         /// \param[in] params Parameters list
         /// \param[in] name Generic process name
         /// \param[in] description Human-readable kT-factorised process name
         /// \param[in] partons First and second incoming parton
         /// \param[in] output Produced final state particles
         GenericKTProcess( const ParametersList& params,
                           const std::string& name,
                           const std::string& description,
                           const std::array<PDG,2>& partons,
                           const std::vector<PDG>& output );
 
         /// Populate the event content with the generated process' topology
         void addEventContent() override;
         /// Retrieve the total number of dimensions on which the integration is being performet
         unsigned int numDimensions() const override;
         /// Retrieve the event weight in the phase space
         double computeWeight() override;
         /// Populate the event content with the generated process' kinematics
         void fillKinematics( bool ) override;
-
+        /// List all variables handled by this generic process
         void dumpVariables() const;
 
       protected:
         /// Set the kinematics associated to the phase space definition
         void setKinematics( const Kinematics& kin ) override;
         /// Set the kinematics of the central system before any point computation
         virtual void setExtraContent() {}
         /// Prepare the central part of the Jacobian (only done once, as soon as the kinematics is set)
         virtual void preparePhaseSpace() = 0;
         /// kT-factorised matrix element (event weight)
         /// \return Weight of the point in the phase space to the integral
         virtual double computeKTFactorisedMatrixElement() = 0;
         /// Compute the unintegrated photon fluxes (for inelastic distributions, interpolation on double logarithmic grid)
         std::pair<double,double> incomingFluxes( double, double, double, double ) const;
         /// Set the kinematics of the incoming and outgoing protons (or remnants)
         void fillPrimaryParticlesKinematics();
         /// Set the kinematics of the outgoing central system
         virtual void fillCentralParticlesKinematics() = 0;
 
         /// Type of mapping to apply on the variable
         enum class Mapping
         {
-          /// a linear \f$\textrm dx\f$ mapping
+          /// a linear \f${\rm d}x\f$ mapping
           linear = 0,
-          /// a logarithmic \f$\frac{\textrm dx}{x} = \textrm d(\log x)\f$ mapping
+          /// a logarithmic \f$\frac{{\rm d}x}{x} = {\rm d}(\log x)\f$ mapping
           logarithmic,
-          /// a square \f$\textrm dx^2=2x\cdot\textrm dx\f$ mapping
+          /// a square \f${\rm d}x^2=2x\cdot{\rm d}x\f$ mapping
           square
         };
         friend std::ostream& operator<<( std::ostream&, const Mapping& );
         /// Register a variable to be handled and populated whenever
         ///  a new phase space point weight is to be calculated.
         /// \note To be run once per generation (before any point computation)
         /// \param[out] out Reference to the variable to be mapped
         /// \param[in] type Type of mapping to apply
         /// \param[in] in Integration limits
         /// \param[in] default_limits Limits to apply if none retrieved from the user configuration
         /// \param[in] description Human-readable description of the variable
         void registerVariable( double& out, const Mapping& type, const Limits& in, Limits default_limits, const char* description );
         /// Generate and initialise all variables handled by this process
         /// \return Phase space point-dependent component of the Jacobian weight of the point in the phase space for integration
         /// \note To be run at each point computation (therefore, to be optimised!)
         double generateVariables() const;
-
+        /// Number of dimensions on which to perform the integration
         unsigned short num_dimensions_;
 
         /// Phase space point-independant component of the Jacobian weight of the point in the phase space for integration
         double kt_jacobian_;
 
         /// Log-virtuality range of the intermediate parton
         Limits log_qt_limits_;
         /// Intermediate azimuthal angle range
         Limits phi_qt_limits_;
         /// Invariant mass range for the scattered excited system
         Limits mx_limits_;
 
         /// Virtuality of the first intermediate parton (photon, pomeron, ...)
         double qt1_;
         /// Azimuthal rotation of the first intermediate parton's transverse virtuality
         double phi_qt1_;
         /// Virtuality of the second intermediate parton (photon, pomeron, ...)
         double qt2_;
         /// Azimuthal rotation of the second intermediate parton's transverse virtuality
         double phi_qt2_;
 
         /// First outgoing proton
         Particle::Momentum PX_;
         /// Second outgoing proton
         Particle::Momentum PY_;
 
         /// Handler to a variable mapped by this process
         struct MappingVariable
         {
           /// Human-readable description of the variable
           std::string description;
           /// Kinematic limits to apply on the variable
           Limits limits;
           /// Reference to the process variable to generate/map
           double& variable;
           /// Interpolation type
           Mapping type;
           /// Corresponding integration variable
           unsigned short index;
         };
         /// Collection of variables to be mapped at the weight generation stage
         std::vector<MappingVariable> mapped_variables_;
 
       private:
         /// First and second intermediate parton (photon, pomeron, ...)
         std::array<PDG,2> kIntermediateParts;
         /// Type of particles produced in the final state
         std::vector<PDG> kProducedParts;
     };
   }
 }
 
 #endif
 
diff --git a/CepGen/Processes/GenericProcess.h b/CepGen/Processes/GenericProcess.h
index 4cd3cae..f52bc95 100644
--- a/CepGen/Processes/GenericProcess.h
+++ b/CepGen/Processes/GenericProcess.h
@@ -1,165 +1,166 @@
 #ifndef CepGen_Processes_GenericProcess_h
 #define CepGen_Processes_GenericProcess_h
 
 #include "CepGen/Event/Particle.h"
 #include "CepGen/Physics/Kinematics.h"
 
 #include <vector>
 #include <memory>
 
 namespace CepGen
 {
   class Event;
   class FormFactors;
   /// Location for all physics processes to be generated
   namespace Process
   {
-    /// Class template to define any process to compute using this MC integrator/events generator
+    /// \brief Class template to define any process to compute using this MC integrator/events generator
     /// \author Laurent Forthomme <laurent.forthomme@cern.ch>
     /// \date Jan 2014
     class GenericProcess
     {
       public:
         /// Default constructor for an undefined process
         /// \param[in] name Process name
         /// \param[in] description Human-readable description of the process
         /// \param[in] has_event Do we generate the associated event structure?
         GenericProcess( const std::string& name, const std::string& description = "<invalid process>", bool has_event = true );
         /// Copy constructor for a user process
         GenericProcess( const GenericProcess& );
         virtual ~GenericProcess() = default;
 
         /// Assignment operator
         GenericProcess& operator=( const GenericProcess& );
 
         /// Human-readable format dump of a GenericProcess object
         friend std::ostream& operator<<( std::ostream& os, const GenericProcess& proc );
         /// Human-readable format dump of a pointer to a GenericProcess object
         friend std::ostream& operator<<( std::ostream& os, const GenericProcess* proc );
 
         /// Generic map of particles with their role in the process
         typedef std::map<Particle::Role,PDG> ParticlesRoleMap;
         /// Pair of particle with their associated role in the process
         typedef std::pair<Particle::Role,PDG> ParticleWithRole;
         /// Map of all incoming state particles in the process
         typedef ParticlesRoleMap IncomingState;
         /// Map of all outgoing particles in the process
         typedef std::map<Particle::Role,std::vector<PDG> > OutgoingState;
 
-        /// Copy all process' attributes into a new object
+        /// Copy all process attributes into a new object
         virtual std::unique_ptr<GenericProcess> clone() const = 0;
 
         /// Restore the Event object to its initial state
         void clearEvent();
         /// Set the kinematics of the incoming state particles
         void setIncomingKinematics( const Particle::Momentum& p1, const Particle::Momentum& p2 );
         /// Compute the incoming state kinematics
         void prepareKinematics();
 
       public:
         /// Set the incoming and outgoing state to be expected in the process
         inline virtual void addEventContent() {}
         /// Set the list of kinematic cuts to apply on the outgoing particles' final state
         /// \param[in] cuts The Cuts object containing the kinematic parameters
         virtual void setKinematics( const Kinematics& cuts );
         /// Return the number of dimensions on which the integration has to be performed
         /// \return Number of dimensions on which to integrate
         virtual unsigned int numDimensions() const = 0;
 
         /// Prepare the process for its integration over the whole phase space
         inline virtual void beforeComputeWeight() {}
         /// Compute the weight for this point in the phase-space
         virtual double computeWeight() = 0;
         /// Fill the Event object with the particles' kinematics
         /// \param[in] symmetrise Symmetrise the event? (randomise the production of positively- and negatively-charged outgoing central particles)
         virtual void fillKinematics( bool symmetrise = false ) = 0;
 
       public:
         /**
          * Sets the phase space point to compute the weight associated to it.
          * \brief Sets the phase space point to compute
          * \param[in] ndim The number of dimensions of the point in the phase space
          * \param[in] x[] The (\a ndim_)-dimensional point in the phase space on which the kinematics and the cross-section are computed
          */
         void setPoint( const unsigned int ndim, double* x );
         /// Dump the evaluated point's coordinates in the standard output stream
         void dumpPoint() const;
         /// Complete list of Particle with their role in the process for the point considered in the phase space, returned as an Event object.
         /// \return Event object containing all the generated Particle objects
         inline std::shared_ptr<Event> event() const { return event_; }
 
         ///Get the number of dimensions on which the integration is performed
         inline const unsigned int ndim() const { return x_.size(); }
         /// Get the value of a component of the d-dimensional point considered
         double x( unsigned int idx ) const;
         /// Name of the process considered
         inline const std::string& name() const { return name_; }
         /// Human-readable description of the process
         inline const std::string& description() const { return description_; }
 
         /// Does the process contain (and hold) an event?
         bool hasEvent() const { return has_event_; }
         /// Pointer to the last event produced in this run
         std::shared_ptr<Event> last_event;
 
       protected:
-        static const double mp_, mp2_;
+        static const double mp_; ///< Proton mass, in GeV/c\f${}^2\f$
+        static const double mp2_; ///< Squared proton mass, in GeV\f${}^2\f$/c\f${}^4\f$
 
         /// Set the incoming and outgoing states to be defined in this process (and prepare the Event object accordingly)
         void setEventContent( const IncomingState& ini, const OutgoingState& fin );
 
         // ---
 
         /// Name of the process
         std::string name_;
         /// Process human-readable description
         std::string description_;
 
       public:
         /// Is it the first time the process is computed?
         bool first_run;
 
       protected:
         /// Array of double precision floats representing the point on which the weight in the cross-section is computed
         std::vector<double> x_;
         /// \f$s\f$, squared centre of mass energy of the incoming particles' system, in \f$\mathrm{GeV}^2\f$
         double s_;
         /// \f$\sqrt s\f$, centre of mass energy of the incoming particles' system (in GeV)
         double sqs_;
         /// Invariant mass of the first proton-like outgoing particle (or remnant)
         double MX_;
         /// Invariant mass of the second proton-like outgoing particle (or remnant)
         double MY_;
         /// \f$m_1^2\f$, squared mass of the first proton-like incoming particle
         double w1_;
         /// \f$m_2^2\f$, squared mass of the second proton-like incoming particle
         double w2_;
         /// Virtuality of the first incoming photon
         double t1_;
         /// Virtuality of the second incoming photon
         double t2_;
 
         /// Set of cuts to apply on the final phase space
         Kinematics cuts_;
         /// Does the process contain (and hold) an event?
         bool has_event_;
         /// Event object containing all the information on the in- and outgoing particles
         std::shared_ptr<Event> event_;
         /// Is the phase space point set?
         bool is_point_set_;
 
       private:
         /**
          * Is the system's kinematics well defined and compatible with the process ?
          * This check is mandatory to perform the d-dimensional point's cross-section computation.
          * \brief Is the system's kinematics well defined?
          * \return A boolean stating if the input kinematics and the final states are well-defined
          */
         bool isKinematicsDefined();
     };
   }
   /// Helper typedef for a Process unique pointer
   typedef std::unique_ptr<Process::GenericProcess> ProcessPtr;
 }
 
 #endif
diff --git a/CepGen/Processes/PPtoFF.cpp b/CepGen/Processes/PPtoFF.cpp
index 4c6a69b..8e218cf 100644
--- a/CepGen/Processes/PPtoFF.cpp
+++ b/CepGen/Processes/PPtoFF.cpp
@@ -1,372 +1,373 @@
 #include "CepGen/Processes/PPtoFF.h"
 
 #include "CepGen/Event/Event.h"
 
 #include "CepGen/Physics/Constants.h"
 #include "CepGen/Physics/FormFactors.h"
 #include "CepGen/Physics/PDG.h"
 
 #include "CepGen/Core/Exception.h"
+#include <iomanip>
 
 namespace CepGen
 {
   namespace Process
   {
     PPtoFF::PPtoFF( const ParametersList& params ) :
       GenericKTProcess( params, "pptoff", "ɣɣ → f⁺f¯", { { PDG::photon, PDG::photon } }, { PDG::muon, PDG::muon } ),
       pair_( params.get<int>( "pair", 0 ) ),
       method_( params.get<int>( "method", 1 ) ),
       y1_( 0. ), y2_( 0. ), pt_diff_( 0. ), phi_pt_diff_( 0. )
     {}
 
     void
     PPtoFF::preparePhaseSpace()
     {
       registerVariable( y1_, Mapping::linear, cuts_.cuts.central.rapidity_single, { -6., 6. }, "First outgoing fermion rapidity" );
       registerVariable( y2_, Mapping::linear, cuts_.cuts.central.rapidity_single, { -6., 6. }, "Second outgoing fermion rapidity" );
       registerVariable( pt_diff_, Mapping::linear, cuts_.cuts.central.pt_diff, { 0., 50. }, "Fermions transverse momentum difference" );
       registerVariable( phi_pt_diff_, Mapping::linear, cuts_.cuts.central.phi_pt_diff, { 0., 2.*M_PI }, "Fermions azimuthal angle difference" );
 
       if ( (PDG)pair_ == PDG::invalid )
         throw CG_FATAL( "PPtoFF:prepare" )
           << "Invalid fermion pair selected: " << pair_ << "!";
 
       const PDG pdg_f = (PDG)pair_;
       mf_ = ParticleProperties::mass( pdg_f );
       mf2_ = mf_*mf_;
       qf_ = ParticleProperties::charge( pdg_f );
       colf_ = ParticleProperties::colours( pdg_f );
       CG_DEBUG( "PPtoFF:prepare" )
         << "Produced particles (" << pdg_f << ") "
         << "with mass = " << mf_ << " GeV, "
-        << "and charge = " << qf_ << " e";
+        << "and charge = " << std::setprecision( 2 ) << qf_ << " e";
       CG_DEBUG( "PPtoFF:mode" )
         << "matrix element computation method: " << method_ << ".";
     }
 
     double
     PPtoFF::computeKTFactorisedMatrixElement()
     {
       //=================================================================
       //     matrix element computation
       //=================================================================
 
       //--- central partons
       const Particle::Momentum q1t( qt1_*cos( phi_qt1_ ), qt1_*sin( phi_qt1_ ), 0. );
       const Particle::Momentum q2t( qt2_*cos( phi_qt2_ ), qt2_*sin( phi_qt2_ ), 0. );
 
       CG_DEBUG_LOOP( "PPtoFF" ) << "q(1/2)t = " << q1t << ", " << q2t << ".";
 
       //--- two-parton system
       const Particle::Momentum ptsum = q1t+q2t;
       const Particle::Momentum ptdiff( pt_diff_*cos( phi_pt_diff_ ), pt_diff_*sin( phi_pt_diff_ ), 0. );
 
       //--- outgoing fermions
       const Particle::Momentum p1_cm = 0.5*( ptsum+ptdiff ), p2_cm = 0.5*( ptsum-ptdiff );
 
       //=================================================================
       //     a window in single particle transverse momentum
       //=================================================================
 
       const Limits& pt_limits = cuts_.cuts.central.pt_single;
       if ( !pt_limits.passes( p1_cm.pt() ) || !pt_limits.passes( p2_cm.pt() ) )
         return 0.;
 
       //=================================================================
       //     a window in transverse momentum difference
       //=================================================================
 
       if ( !cuts_.cuts.central.pt_diff.passes( fabs( p1_cm.pt()-p2_cm.pt() ) ) )
         return 0.;
 
       //=================================================================
       //     a window in rapidity distance
       //=================================================================
 
       if ( !cuts_.cuts.central.rapidity_diff.passes( fabs( y1_-y2_ ) ) )
         return 0.;
 
       //=================================================================
       //     auxiliary quantities
       //=================================================================
 
       // transverse mass for the two fermions
       const double amt1 = std::hypot( p1_cm.pt(), mf_ ), amt2 = std::hypot( p2_cm.pt(), mf_ );
       const double alpha1 = amt1/sqs_*exp( y1_ ), beta1  = amt1/sqs_*exp( -y1_ ),
                    alpha2 = amt2/sqs_*exp( y2_ ), beta2  = amt2/sqs_*exp( -y2_ );
 
       CG_DEBUG_LOOP( "PPtoFF" )
         << "Sudakov parameters:\n\t"
         << "  alpha(1/2) = " << alpha1 << ", " << alpha2 << "\n\t"
         << "   beta(1/2) = " << beta1 << ", " << beta2 << ".";
 
       const double x1 = alpha1+alpha2, x2 = beta1+beta2;
       if ( x1 <= 0. || x1 > 1. || x2 <= 0. || x2 > 1. )
         return 0.; // sanity check
 
       //=================================================================
       //     additional conditions for energy-momentum conservation
       //=================================================================
 
       const double s1_eff = x1*s_-qt1_*qt1_, s2_eff = x2*s_-qt2_*qt2_;
       const double invm = sqrt( amt1*amt1 + amt2*amt2 + 2.*amt1*amt2*cosh(y1_-y2_) - ptsum.pt2() );
       CG_DEBUG_LOOP( "PPtoFF" )
         << "s(1/2)eff = " << s1_eff << ", " << s2_eff << " GeV²\n\t"
         << "central system's invariant mass = " << invm << " GeV.";
 
       if ( ( cuts_.mode == KinematicsMode::ElasticInelastic
           || cuts_.mode == KinematicsMode::InelasticInelastic )
         && ( sqrt( s1_eff ) <= ( MY_+invm ) ) )
         return 0.;
       if ( ( cuts_.mode == KinematicsMode::InelasticElastic
           || cuts_.mode == KinematicsMode::InelasticInelastic )
         && ( sqrt( s2_eff ) <= ( MX_+invm ) ) )
         return 0.;
 
       //=================================================================
       //     four-momenta of the outgoing protons (or remnants)
       //=================================================================
 
       const Particle::Momentum& ak1 = event_->getOneByRole( Particle::IncomingBeam1 ).momentum(),
                                &ak2 = event_->getOneByRole( Particle::IncomingBeam2 ).momentum();
       CG_DEBUG_LOOP( "PPtoFF" )
         << "incoming particles: p(1/2) = " << ak1 << ", " << ak2 << ".";
 
       const double px_plus  = ( 1.-x1 )*M_SQRT2*ak1.p(),
                    px_minus = ( MX_*MX_ + q1t.pt2() )*0.5/px_plus;
 
       const double py_minus = ( 1.-x2 )*M_SQRT2*ak2.p(),
                    py_plus  = ( MY_*MY_ + q2t.pt2() )*0.5/py_minus;
 
       CG_DEBUG_LOOP( "PPtoFF" )
         << "px± = " << px_plus << ", " << px_minus << "\n\t"
         << "py± = " << py_plus << ", " << py_minus << ".";
 
       PX_ = Particle::Momentum( -q1t.px(), -q1t.py(), ( px_plus-px_minus )*M_SQRT1_2, ( px_plus+px_minus )*M_SQRT1_2 );
       PY_ = Particle::Momentum( -q2t.px(), -q2t.py(), ( py_plus-py_minus )*M_SQRT1_2, ( py_plus+py_minus )*M_SQRT1_2 );
 
       CG_DEBUG_LOOP( "PPtoFF" )
         << "First remnant:  " << PX_ << ", mass = " << PX_.mass() << "\n\t"
         << "Second remnant: " << PY_ << ", mass = " << PY_.mass() << ".";
 
       if ( fabs( PX_.mass()-MX_ ) > 1.e-6 )
         throw CG_FATAL( "PPtoFF" ) << "Invalid X system mass: " << PX_.mass() << "/" << MX_ << ".";
       if ( fabs( PY_.mass()-MY_ ) > 1.e-6 )
         throw CG_FATAL( "PPtoFF" ) << "Invalid Y system mass: " << PY_.mass() << "/" << MY_ << ".";
 
       //=================================================================
       //     four-momenta of the outgoing l^+ and l^-
       //=================================================================
 
       const Particle::Momentum p1 = p1_cm + alpha1*ak1 + beta1*ak2;
       const Particle::Momentum p2 = p2_cm + alpha2*ak1 + beta2*ak2;
       CG_DEBUG_LOOP( "PPtoFF" )
         << "unboosted first fermion:  " << p1 << ", mass = " << p1.mass() << "\n\t"
         << "          second fermion: " << p2 << ", mass = " << p2.mass() << ".";
 
       p_f1_ = Particle::Momentum::fromPxPyYM( p1_cm.px(), p1_cm.py(), y2_, mf_ );
       p_f2_ = Particle::Momentum::fromPxPyYM( p2_cm.px(), p2_cm.py(), y1_, mf_ );
 
       CG_DEBUG_LOOP( "PPtoFF" )
         << "First fermion:  " << p_f1_ << ", mass = " << p_f1_.mass() << "\n\t"
         << "Second fermion: " << p_f2_ << ", mass = " << p_f2_.mass() << ".";
 
       if ( fabs( p_f1_.mass()-mf_ ) > 1.e-4 )
         throw CG_FATAL( "PPtoFF" ) << "Invalid fermion 1 mass: " << p_f1_.mass() << "/" << mf_ << ".";
       if ( fabs( p_f2_.mass()-mf_ ) > 1.e-4 )
         throw CG_FATAL( "PPtoFF" ) << "Invalid fermion 2 mass: " << p_f2_.mass() << "/" << mf_ << ".";
 
       //=================================================================
       //     matrix elements
       //=================================================================
       double amat2 = 0.;
 
       //=================================================================
       //     How matrix element is calculated
       //=================================================================
 
       if ( method_ == 0 ) {
         //===============================================================
         //     Mendelstam variables
         //===============================================================
 
         //const double shat = s_*x1*x2; // approximation
         const double shat = ( q1t+q2t ).mass2(); // exact formula
         //const double mll = sqrt( shat );
         const double that1 = ( q1t-p1 ).mass2(), that2 = ( q2t-p2 ).mass2();
         const double uhat1 = ( q1t-p2 ).mass2(), uhat2 = ( q2t-p1 ).mass2();
         const double that = 0.5*( that1+that2 ), uhat = 0.5*( uhat1+uhat2 );
 
         amat2 = onShellME( shat, that, uhat );
 
         CG_DEBUG_LOOP( "PPtoFF:onShell" )
           << "that(1/2) = " << that1 << " / " << that2 << "\n\t"
           << "uhat(1/2) = " << uhat1 << " / " << uhat2 << "\n\t"
           << "squared matrix element: " << amat2 << ".";
       }
 
       else if ( method_ == 1 ) {
         const double t1abs = ( q1t.pt2() + x1*( MX_*MX_-mp2_ )+x1*x1*mp2_ )/( 1.-x1 ),
                      t2abs = ( q2t.pt2() + x2*( MY_*MY_-mp2_ )+x2*x2*mp2_ )/( 1.-x2 );
         const double z1p = alpha1/x1, z1m = alpha2/x1,
                      z2p = beta1 /x2, z2m = beta2 /x2;
         CG_DEBUG_LOOP( "PPtoFF:offShell" )
           << "z(1/2)p = " << z1p << ", " << z2p << "\n\t"
           << "z(1/2)m = " << z1m << ", " << z2m << ".";
         amat2 = offShellME( t1abs, t2abs, z1m, z1p, z2m, z2p, q1t, q2t ) * pow( x1*x2*s_, 2 );
       }
 
       //============================================
       //     unintegrated photon distributions
       //============================================
 
       const std::pair<double,double> fluxes
         = GenericKTProcess::incomingFluxes( x1, q1t.pt2(), x2, q2t.pt2() );
 
       CG_DEBUG_LOOP( "PPtoFF" )
         << "Incoming photon fluxes for (x/kt2) = "
         << "(" << x1 << "/" << q1t.pt2() << "), "
         << "(" << x2 << "/" << q2t.pt2() << "):\n\t"
         << fluxes.first << ", " << fluxes.second << ".";
 
       //=================================================================
       //     factor 2.*pi from integration over phi_sum
       //     factor 1/4 from jacobian of transformations
       //     factors 1/pi and 1/pi due to integration over
       //       d^2 kappa_1 d^2 kappa_2 instead d kappa_1^2 d kappa_2^2
       //=================================================================
 
       const double g_em = 4.*M_PI*Constants::alphaEM*qf_*qf_;
       const double aintegral = amat2 * colf_ * ( g_em*g_em )
                              * 1. / pow( 4.*M_PI*( x1*x2*s_ ), 2 )
                              * fluxes.first*M_1_PI * fluxes.second*M_1_PI * 0.25
                              * Constants::GeV2toBarn;
 
       //=================================================================
       return aintegral*qt1_*qt2_*pt_diff_;
       //=================================================================
     }
 
     void
     PPtoFF::fillCentralParticlesKinematics()
     {
       // randomise the charge of the outgoing fermions
       short sign = ( drand() > 0.5 ) ? +1 : -1;
 
       //=================================================================
       //     first outgoing fermion
       //=================================================================
       Particle& of1 = event_->getByRole( Particle::CentralSystem )[0];
       of1.setPdgId( of1.pdgId(), sign );
       of1.setStatus( Particle::Status::FinalState );
       of1.setMomentum( p_f1_ );
 
       //=================================================================
       //     second outgoing fermion
       //=================================================================
       Particle& of2 = event_->getByRole( Particle::CentralSystem )[1];
       of2.setPdgId( of2.pdgId(), -sign );
       of2.setStatus( Particle::Status::FinalState );
       of2.setMomentum( p_f2_ );
     }
 
     double
     PPtoFF::onShellME( double shat, double that, double uhat ) const
     {
       CG_DEBUG_LOOP( "PPtoFF:onShell" )
         << "shat: " << shat << ", that: " << that << ", uhat: " << uhat << ".";
 
       //=================================================================
       //     on-shell formula for M^2
       //=================================================================
       const double ml4 = mf2_*mf2_, ml8 = ml4*ml4;
 
       const double term1  =  6. *ml8,
                    term2  = -3. *ml4 *that*that,
                    term3  = -14.*ml4 *that*uhat,
                    term4  = -3. *ml4 *uhat*uhat,
                    term5  =      mf2_*that*that*that,
                    term6  =  7.* mf2_*that*that*uhat,
                    term7  =  7.* mf2_*that*uhat*uhat,
                    term8  =      mf2_*uhat*uhat*uhat,
                    term9  =          -that*that*that*uhat,
                    term10 =          -that*uhat*uhat*uhat;
 
       return -2.*( term1+term2+term3+term4+term5
                   +term6+term7+term8+term9+term10 )/( pow( ( mf2_-that )*( mf2_-uhat ), 2) );
     }
 
     double
     PPtoFF::offShellME( double t1abs, double t2abs, double z1m, double z1p, double z2m, double z2p, const Particle::Momentum& q1, const Particle::Momentum& q2 ) const
     {
       //=================================================================
       //     Wolfgang's formulae
       //=================================================================
 
       const double z1 = z1p*z1m, z2 = z2p*z2m;
       const double eps12 = mf2_+z1*t1abs, eps22 = mf2_+z2*t2abs;
 
       const Particle::Momentum ak1 = ( z1m*p_f1_-z1p*p_f2_ ), ak2 = ( z2m*p_f1_-z2p*p_f2_ );
       const Particle::Momentum ph_p1 = ak1+z1p*q2, ph_m1 = ak1-z1m*q2;
       const Particle::Momentum ph_p2 = ak2+z2p*q1, ph_m2 = ak2-z2m*q1;
 
       const Particle::Momentum phi1(
         ph_p1.px()/( ph_p1.pt2()+eps12 )-ph_m1.px()/( ph_m1.pt2()+eps12 ),
         ph_p1.py()/( ph_p1.pt2()+eps12 )-ph_m1.py()/( ph_m1.pt2()+eps12 ),
         0.,
         1./( ph_p1.pt2()+eps12 )-1./( ph_m1.pt2()+eps12 )
       );
 
       const Particle::Momentum phi2(
         ph_p2.px()/( ph_p2.pt2()+eps22 )-ph_m2.px()/( ph_m2.pt2()+eps22 ),
         ph_p2.py()/( ph_p2.pt2()+eps22 )-ph_m2.py()/( ph_m2.pt2()+eps22 ),
         0.,
         1./( ph_p2.pt2()+eps22 )-1./( ph_m2.pt2()+eps22 )
       );
 
       const double dot1 = phi1.threeProduct( q1 )/qt1_, cross1 = phi1.crossProduct( q1 )/qt1_;
       const double dot2 = phi2.threeProduct( q2 )/qt2_, cross2 = phi2.crossProduct( q2 )/qt2_;
       CG_DEBUG_LOOP( "PPtoFF:offShell" )
         << "phi1 = " << phi1 << "\n\t"
         << "phi2 = " << phi2 << "\n\t"
         << "(dot):   " << dot1 << " / " << dot2 << "\n\t"
         << "(cross): " << cross1 << " / " << cross2 << ".";
 
       //=================================================================
       //     six terms in Wolfgang's formula for
       //     off-shell gamma gamma --> l^+ l^-
       //=================================================================
 
       const unsigned short imat1 = 1, imat2 = 1;
       const unsigned short itermLL = 1, itermTT = 1, itermLT = 1, itermtt = 1;
 
       const double aux2_1 = itermLL * ( mf2_ + 4.*z1*z1*t1abs ) * phi1.energy2()
                            +itermTT * ( ( z1p*z1p + z1m*z1m )*( dot1*dot1 + cross1*cross1 ) )
                            +itermtt * ( cross1*cross1 - dot1*dot1 )
                            -itermLT * 4.*z1*( z1p-z1m ) * phi1.energy() * q1.threeProduct( phi1 );
 
       const double aux2_2 = itermLL * ( mf2_ + 4.*z2*z2*t2abs ) * phi2.energy2()
                            +itermTT * ( ( z2p*z2p + z2m*z2m )*( dot2*dot2 + cross2*cross2 ) )
                            +itermtt * ( cross2*cross2 - dot2*dot2 )
                            -itermLT * 4.*z2*( z2p-z2m ) * phi2.energy() * q2.threeProduct( phi2 );
 
       //=================================================================
       //     convention of matrix element as in our kt-factorization
       //     for heavy flavours
       //=================================================================
 
       const double amat2_1 = aux2_1*2.*z1*q1.pt2()/( q1.pt2()*q2.pt2() ),
                    amat2_2 = aux2_2*2.*z2*q2.pt2()/( q1.pt2()*q2.pt2() );
 
       //=================================================================
       //     symmetrization
       //=================================================================
 
       CG_DEBUG_LOOP( "PPtoFF:offShell" )
         << "aux2(1/2) = " << aux2_1 << " / " << aux2_2 << "\n\t"
         << "amat2(1/2), amat2 = " << amat2_1 << " / " << amat2_2 << " / " << ( 0.5*( imat1*amat2_1 + imat2*amat2_2 ) ) << ".";
 
       return 0.5*( imat1*amat2_1 + imat2*amat2_2 );
     }
   }
 }
diff --git a/CepGen/Processes/PPtoWW.h b/CepGen/Processes/PPtoWW.h
index 541f86f..01971af 100644
--- a/CepGen/Processes/PPtoWW.h
+++ b/CepGen/Processes/PPtoWW.h
@@ -1,58 +1,59 @@
 #ifndef CepGen_Processes_PPtoWW_h
 #define CepGen_Processes_PPtoWW_h
 
 #include "CepGen/Processes/GenericKTProcess.h"
 #include "CepGen/Core/ParametersList.h"
 
 namespace CepGen
 {
   namespace Process
   {
-    /// Compute the matrix element for a CE \f$\gamma\gamma\rightarrow W^+W^-\f$ process using \f$k_T\f$-factorization approach
+    /// \brief Compute the matrix element for a CE \f$\gamma\gamma\rightarrow W^+W^-\f$ process using \f$k_T\f$-factorization approach
+    /// \note The full theoretical description of this process definition may be found in \cite Luszczak:2018ntp.
     class PPtoWW : public GenericKTProcess
     {
       public:
         PPtoWW( const ParametersList& params = ParametersList() );
         ProcessPtr clone() const override { return ProcessPtr( new PPtoWW( *this ) ); }
         enum class Polarisation { full = 0, LL = 1, LT = 2, TL = 3, TT = 4 };
 
       private:
         static const double mw_, mw2_;
 
         void preparePhaseSpace() override;
         double computeKTFactorisedMatrixElement() override;
         void fillCentralParticlesKinematics() override;
 
         double amplitudeWW( double shat, double that, double uhat, short lam1, short lam2, short lam3, short lam4 );
         double onShellME( double shat, double that, double uhat );
         double offShellME( double shat, double that, double uhat, double phi_sum, double phi_diff );
 
         int method_;
         Polarisation pol_state_;
         std::vector<short> pol_w1_, pol_w2_;
 
         /// Rapidity range for the outgoing W bosons
         Limits rap_limits_;
         /// Rapidity of the first outgoing W boson
         double y1_;
         /// Rapidity of the first outgoing W boson
         double y2_;
 
         Limits ptdiff_limits_;
         /// Transverse momentum difference for the two outgoing W bosons
         double pt_diff_;
 
         Limits phi_pt_diff_limits_;
         /// Azimuthal angle difference for the two outgoing W bosons
         double phi_pt_diff_;
 
         /// First outgoing W boson's momentum
         Particle::Momentum p_w1_;
         /// Second outgoing W boson's momentum
         Particle::Momentum p_w2_;
     };
   }
 }
 
 #endif
 
diff --git a/CepGen/StructureFunctions/ALLM.h b/CepGen/StructureFunctions/ALLM.h
index 77a22c8..aaf10cb 100644
--- a/CepGen/StructureFunctions/ALLM.h
+++ b/CepGen/StructureFunctions/ALLM.h
@@ -1,61 +1,62 @@
 #ifndef CepGen_StructureFunctions_ALLM_h
 #define CepGen_StructureFunctions_ALLM_h
 
 #include "StructureFunctions.h"
 #include "SigmaRatio.h"
 
 #include <vector>
 
 namespace CepGen
 {
   namespace SF
   {
+    /// \f$F_{2/L}\f$ parameterisation by Abramowicz, Levin, Levy, and Maor \cite Abramowicz:1991xz\cite Abramowicz:1997ms
     class ALLM : public StructureFunctions
     {
       public:
         class Parameterisation
         {
           private:
             struct Parameters {
               Parameters() :
                 a( { 0., 0., 0. } ), b( { 0., 0., 0. } ), c( { 0., 0., 0. } ) {}
               Parameters( const std::vector<double>& c, const std::vector<double>& a, const std::vector<double>& b ) :
                 a( a ), b( b ), c( c ) {}
               std::vector<double> a, b, c;
             };
 
           public:
             Parameterisation() :
               m02( 0. ), mp2( 0. ), mr2( 0. ), q02( 0. ), lambda2( 0. ) {}
             /// Pre-HERA data fit (694 data points)
             static Parameterisation allm91();
             /// Fixed target and HERA photoproduction total cross sections (1356 points)
             static Parameterisation allm97();
             static Parameterisation hht_allm();
             static Parameterisation hht_allm_ft();
             static Parameterisation gd07p();
             static Parameterisation gd11p();
 
             Parameters pomeron, reggeon;
             /// Effective photon squared mass
             double m02;
             /// Effective pomeron squared mass
             double mp2;
             /// Effective reggeon squared mass
             double mr2;
             double q02;
             /// Squared QCD scale
             double lambda2;
             Type type;
         };
 
         explicit ALLM( const ALLM::Parameterisation& param = ALLM::Parameterisation::allm97() );
         ALLM& operator()( double xbj, double q2 ) override;
 
       private:
         Parameterisation params_;
     };
   }
 }
 
 #endif
diff --git a/CepGen/StructureFunctions/BlockDurandHa.h b/CepGen/StructureFunctions/BlockDurandHa.h
index efe0ec7..a1c5c95 100644
--- a/CepGen/StructureFunctions/BlockDurandHa.h
+++ b/CepGen/StructureFunctions/BlockDurandHa.h
@@ -1,36 +1,37 @@
 #ifndef CepGen_StructureFunctions_BlockDurandHa_h
 #define CepGen_StructureFunctions_BlockDurandHa_h
 
 #include "StructureFunctions.h"
 #include <array>
 
 namespace CepGen
 {
   namespace SF
   {
+    /// \f$F_2\f$ parameterisation from Block, Durand, and Ha \cite Block:2014kza
     class BlockDurandHa : public StructureFunctions
     {
       public:
         struct Parameterisation
         {
           std::array<double,3> a, b;
           std::array<double,2> c;
           double n;
           /// Effective mass spread parameter
           double lambda;
           /// Asymptotic log-behaviour transition scale factor
           double mu2;
           /// Squared effective mass (~VM mass)
           double m2;
           static Parameterisation standard();
         };
         explicit BlockDurandHa( const BlockDurandHa::Parameterisation& params = BlockDurandHa::Parameterisation::standard() );
         BlockDurandHa& operator()( double xbj, double q2 ) override;
 
       private:
         Parameterisation params_;
     };
   }
 }
 
 #endif
diff --git a/CepGen/StructureFunctions/CLAS.cpp b/CepGen/StructureFunctions/CLAS.cpp
index b35c5b4..032a3e1 100644
--- a/CepGen/StructureFunctions/CLAS.cpp
+++ b/CepGen/StructureFunctions/CLAS.cpp
@@ -1,217 +1,220 @@
 #include "CepGen/StructureFunctions/CLAS.h"
 
 #include "CepGen/Physics/PDG.h"
 #include "CepGen/Physics/Constants.h"
 
 #include "CepGen/Event/Particle.h"
 #include "CepGen/Core/Exception.h"
 
 namespace CepGen
 {
   namespace SF
   {
     CLAS::Parameterisation
     CLAS::Parameterisation::standard_proton()
     {
       Parameterisation params;
       params.mode = Parameterisation::proton;
       params.mp = mp_;
       params.mpi0 = ParticleProperties::mass( PDG::piZero );
       // SLAC fit parameters
       params.c_slac = { { 0.25615, 2.1785, 0.89784, -6.7162, 3.7557, 1.6421, 0.37636 } };
       // CLAS parameterisation
       params.x = { { -0.599937, 4.76158, 0.411676 } };
       params.b = { { 0.755311, 3.35065, 3.51024, 1.74470 } };
       params.alpha = -0.174985;
       params.beta = 0.00967019;
       params.mu = -0.0352567;
       params.mup = 3.51852;
 
       Parameterisation::Resonance r0;
       r0.amplitude = 1.04;
       r0.mass = 1.22991;
       r0.width = 0.106254;
       r0.angular_momentum = 1;
       params.resonances.emplace_back( r0 );
 
       Parameterisation::Resonance r1;
       r1.amplitude = 0.481327;
       r1.mass = 1.51015;
       r1.width = 0.0816620;
       r1.angular_momentum = 2;
       params.resonances.emplace_back( r1 );
 
       Parameterisation::Resonance r2;
       r2.amplitude = 0.655872;
       r2.mass = 1.71762;
       r2.width = 0.125520;
       r2.angular_momentum = 3;
       params.resonances.emplace_back( r2 );
 
       Parameterisation::Resonance r3;
       r3.amplitude = 0.747338;
       r3.mass = 1.95381;
       r3.width = 0.198915;
       r3.angular_momentum = 2;
       params.resonances.emplace_back( r3 );
 
       return params;
     }
 
     CLAS::Parameterisation
     CLAS::Parameterisation::standard_neutron()
     {
       Parameterisation params = standard_proton();
       params.mode = Parameterisation::neutron;
       params.c_slac = { { 0.0640, 0.2250, 4.1060, -7.0790, 3.0550, 1.6421, 0.37636 } };
       return params;
     }
 
     CLAS::Parameterisation
     CLAS::Parameterisation::standard_deuteron()
     {
       Parameterisation params = standard_proton();
       params.mode = Parameterisation::deuteron;
       params.c_slac = { { 0.47709, 2.1602, 3.6274, -10.470, 4.9272, 1.5121, 0.35115 } };
       params.x = { { -0.21262, 6.9690, 0.40314 } };
       params.b = { { 0.76111, 4.1470, 3.7119, 1.4218 } };
       params.alpha = -0.24480;
       params.beta = 0.014503;
 
       params.resonances.clear();
 
       Parameterisation::Resonance r0;
       r0.amplitude = 0.74847;
       r0.mass = 1.2400;
       r0.width = 0.12115;
       r0.angular_momentum = 1;
       params.resonances.emplace_back( r0 );
 
       Parameterisation::Resonance r1;
       r1.amplitude = 0.011500;
       r1.mass = 1.4772;
       r1.width = 0.0069580;
       r1.angular_momentum = 2;
       params.resonances.emplace_back( r1 );
 
       Parameterisation::Resonance r2;
       r2.amplitude = 0.12662;
       r2.mass = 1.5233;
       r2.width = 0.084095;
       r2.angular_momentum = 3;
       params.resonances.emplace_back( r2 );
 
       Parameterisation::Resonance r3;
       r3.amplitude = 0.747338;
       r3.mass = 1.95381;
       r3.width = 0.198915;
       r3.angular_momentum = 2;
       params.resonances.emplace_back( r3 );
 
       return params;
     }
 
     CLAS::CLAS( const CLAS::Parameterisation& params ) :
       StructureFunctions( Type::CLAS ), params_( params )
     {}
 
     CLAS&
     CLAS::operator()( double xbj, double q2 )
     {
       std::pair<double,double> nv = { xbj, q2 };
       if ( nv == old_vals_ )
         return *this;
       old_vals_ = nv;
 
       const double mp2 = params_.mp*params_.mp;
       const double w2 = mp2 + q2*( 1.-xbj )/xbj;
       const double w_min = params_.mp+params_.mpi0;
 
       if ( sqrt( w2 ) < w_min ) {
         F2 = 0.;
         return *this;
       }
 
       F2 = f2slac( xbj, q2 );
       std::pair<double,double> rb = resbkg( q2, sqrt( w2 ) );
 
       F2 *= ( rb.first+rb.second );
       return *this;
     }
 
     double
     CLAS::f2slac( double xbj, double q2 ) const
     {
       if ( xbj >= 1. )
         return 0.;
 
       const double xsxb = ( q2+params_.c_slac[6] )/( q2+params_.c_slac[5]*xbj );
       const double xs = xbj*xsxb;
 
       double f2 = 0.;
       for ( unsigned short i = 0; i < 5; ++i )
         f2 += params_.c_slac[i]*pow( 1.-xs, i );
 
       if ( params_.mode == Parameterisation::deuteron && xbj > 0. )
         f2 /= ( 1.-exp( -7.70*( 1./xbj-1.+params_.mp*params_.mp/q2 ) ) );
 
       return f2 * pow( 1.-xs, 3 ) / xsxb;
     }
 
     std::pair<double,double>
     CLAS::resbkg( double q2, double w ) const
     {
       const double mp2 = params_.mp*params_.mp, mpi02 = params_.mpi0*params_.mpi0;
       const double coef = 6.08974;
 
       double wth = params_.mp+params_.mpi0;
-      if ( w < wth ) return std::make_pair( 0., 0. );
-      if ( w > 4. ) return std::make_pair( 1., 0. );
+      if ( w < wth )
+        return std::make_pair( 0., 0. );
+      if ( w > 4. )
+        return std::make_pair( 1., 0. );
 
       const double w2 = w*w;
 
       double qs = pow( w2+mp2-mpi02, 2 )-4.*mp2*w2;
       if ( qs <= 0. ) return std::make_pair( 1., 0. );
       qs = 0.5 * sqrt( qs )/w;
 
       const double omega = 0.5*( w2+q2-mp2 )/params_.mp;
       const double xn = 0.5*q2/( params_.mp*omega );
 
       const double bkg2 = ( w > params_.b[3] )
         ? exp( -params_.b[2]*( w2-params_.b[3]*params_.b[3] ) )
         : 1.;
 
       double f2bkg = (    params_.b[0] )*( 1.-exp( -params_.b[1]*( w-wth ) ) )
                    + ( 1.-params_.b[0] )*( 1.-bkg2 );
       f2bkg *= ( 1.+( 1.-f2bkg )*( params_.x[0]+params_.x[1]*pow( xn-params_.x[2], 2 ) ) );
 
       double etab = 1., etad = 1.;
       if ( params_.mode != Parameterisation::deuteron && q2 <= 2. && w <= 2.5 ) {
         etab = 1.-2.5*q2*exp( -12.5*q2*q2-50.*( w-1.325 )*( w-1.325 ) );
         etad = 1.+2.5*q2*exp( -12.5*q2*q2 );
       }
       f2bkg *= etab;
 
       double f2resn = 0.;
 
       for ( unsigned short i = 0; i < params_.resonances.size(); ++i ) {
         const Parameterisation::Resonance& res = params_.resonances[i];
         const double ai = ( i == 0 )
           ? etad * ( res.amplitude + q2*std::min( 0., params_.alpha+params_.beta*q2 ) )
           : res.amplitude;
         const double dmi = ( i == 2 )
           ? res.mass * ( 1.+params_.mu/( 1.+params_.mup*q2 ) )
           : res.mass;
         double qs0 = pow( dmi*dmi+mp2-mpi02, 2 )-4.*mp2*dmi*dmi;
-        if ( qs0 <= 0. ) break;
+        if ( qs0 <= 0. )
+          break;
         qs0 = 0.5*sqrt( qs0 )/dmi;
         int ji = 2*res.angular_momentum;
         const double dg = 0.5*res.width*pow( qs/qs0, ji+1 )*( 1.+pow( coef*qs0, ji ) )/( 1.+pow( coef*qs, ji ) );
         f2resn += ai*dg/( ( w-dmi )*( w-dmi )+dg*dg );
       }
-      f2resn *= 0.5*( 1.-params_.b[0] )*bkg2/( params_.mp*M_PI );
+      f2resn *= 0.5*( 1.-params_.b[0] )*bkg2/params_.mp*M_1_PI;
 
       return std::make_pair( f2bkg, f2resn );
     }
   }
 }
diff --git a/CepGen/StructureFunctions/CLAS.h b/CepGen/StructureFunctions/CLAS.h
index d00fa3e..5444cc3 100644
--- a/CepGen/StructureFunctions/CLAS.h
+++ b/CepGen/StructureFunctions/CLAS.h
@@ -1,65 +1,69 @@
 #ifndef CepGen_StructureFunctions_CLAS_h
 #define CepGen_StructureFunctions_CLAS_h
 
 #include "CepGen/StructureFunctions/StructureFunctions.h"
 
 #include <array>
 #include <vector>
 
 namespace CepGen
 {
   namespace SF
   {
-    /// CLAS parameterisation developed to describe nucleon data at \f$Q^2 > 0.5\f$ GeV² and \f$x_{Bj} > 0.15\f$.
-    /// \note This code was provided on 2016-04-13 by Silvano Simula and
-    ///  reflects the parametrisation used in hep-ph/0301204 (CLAS) and
-    ///  described in hep-ph/9901360.
+    /// \brief CLAS parameterisation for nucleon data at \f$Q^2\f$ > 0.5 GeV\f${}^2\f$ and \f$x_{\rm Bj}\f$ > 0.15
+    /// \note This code was provided on 2016-04-13 by Silvano Simula and reflects the parameterisation used in \cite Osipenko:2003bu (CLAS) and described in \cite Ricco:1998yr.
     class CLAS : public StructureFunctions
     {
       public:
+        /// List of steering parameters for a physics case
         struct Parameterisation
         {
+          /// Standard parameterisation of a parton-from-neutron emission
           static Parameterisation standard_neutron();
+          /// Standard parameterisation of a parton-from-proton emission
           static Parameterisation standard_proton();
+          /// Standard parameterisation of a parton-from-deuteron emission
           static Parameterisation standard_deuteron();
 
           /// Physical properties associated to a resonance
           struct Resonance
           {
             double amplitude, mass, width;
             short angular_momentum;
           };
 
-          enum { neutron = 0, proton = 1, deuteron = 2 } mode;
-          double mp, mpi0;
+          enum { neutron = 0, proton = 1, deuteron = 2 } mode; ///< Nucleon type
+          double mp; ///< Proton mass
+          double mpi0; ///< Neutral pion mass
           // SLAC fit parameters
           std::array<double,7> c_slac;
           // CLAS parameterisation
           double alpha, beta, mu, mup;
           std::array<double,3> x;
           std::array<double,4> b;
           std::vector<Resonance> resonances;
           std::array<unsigned short,4> lr;
         };
 
+        /// Standard parameterisation interpolator constructor (photon from proton)
         explicit CLAS( const CLAS::Parameterisation& params = CLAS::Parameterisation::standard_proton() );
 
         CLAS& operator()( double xbj, double q2 ) override;
 
       private:
         /// \brief Method to evaluate the background/resonance terms of
         ///  the modulating function for the nucleon
         /// \note SLAC parameterisation
         std::pair<double,double> resbkg( double q2, double w ) const;
         /// \brief Method to evaluate the deep inelastic structure function
         /// \f$F_{2}^{N}\f$ using the SLAC parameterisation
-        /// \param[in] q2 squared four-momentum transfer in GeV²
+        /// \param[in] q2 squared four-momentum transfer in GeV\f${}^2\f$
         /// \param[in] xbj Bjorken scaling variable
         /// \return \f$F_{2}^{N}\f$
         double f2slac( double xbj, double q2 ) const;
         Parameterisation params_;
     };
   }
 }
 
 #endif
diff --git a/CepGen/StructureFunctions/ChristyBosted.h b/CepGen/StructureFunctions/ChristyBosted.h
index 173534f..97b97ce 100644
--- a/CepGen/StructureFunctions/ChristyBosted.h
+++ b/CepGen/StructureFunctions/ChristyBosted.h
@@ -1,83 +1,84 @@
 #ifndef CepGen_StructureFunctions_ChristyBosted_h
 #define CepGen_StructureFunctions_ChristyBosted_h
 
 #include "StructureFunctions.h"
 #include "CepGen/Physics/Constants.h"
 #include <array>
 #include <vector>
 
 namespace CepGen
 {
   namespace SF
   {
+    /// \f$F_{2/L}\f$ parameterisation by Christy and Bosted \cite Bosted:2007xd
     class ChristyBosted : public StructureFunctions
     {
       public:
         struct Parameterisation
         {
           static Parameterisation standard();
 
           struct ResonanceParameters
           {
             struct BranchingRatios
             {
               BranchingRatios() : singlepi( 0. ), doublepi( 0. ), eta( 0. ) {}
               BranchingRatios( double singlepi, double doublepi, double eta ) : singlepi( singlepi ), doublepi( doublepi ), eta( eta ) {}
               bool valid() const { return ( singlepi+doublepi+eta == 1. ); }
               /// single pion branching ratio
               double singlepi;
               /// double pion branching ratio
               double doublepi;
               /// eta meson branching ratio
               double eta;
             };
             ResonanceParameters() : angular_momentum( 0. ), x0( 0. ), mass( 0. ), width( 0. ), A0_T( 0. ), A0_L( 0. ) {}
             double kr() const;
             double ecmr( double m2 ) const;
             double kcmr() const { return ecmr( 0. ); }
             double pcmr( double m2 ) const { return sqrt( std::max( 0., ecmr( m2 )*ecmr( m2 )-m2 ) ); }
             BranchingRatios br;
             /// meson angular momentum
             double angular_momentum;
             /// damping parameter
             double x0;
             /// mass, in GeV/c2
             double mass;
             /// full width, in GeV
             double width;
             double A0_T;
             double A0_L;
             std::array<double,5> fit_parameters;
           };
           struct ContinuumParameters
           {
             struct DirectionParameters
             {
               DirectionParameters() : sig0( 0. ) {}
               DirectionParameters( double sig0, const std::vector<double>& params ) : sig0( sig0 ), fit_parameters( params ) {}
               double sig0;
               std::vector<double> fit_parameters;
             };
             std::array<DirectionParameters,2> transverse;
             std::array<DirectionParameters,1> longitudinal;
           };
           double m0;
           std::vector<ResonanceParameters> resonances;
           ContinuumParameters continuum;
         };
 
         explicit ChristyBosted( const ChristyBosted::Parameterisation& params = ChristyBosted::Parameterisation::standard() );
         ChristyBosted& operator()( double xbj, double q2 ) override;
 
         //--- already computed internally during F2 computation
         void computeFL( double xbj, double q2, const CepGen::SF::SigmaRatio& ) override {}
         void computeFL( double xbj, double q2, double r ) override {}
 
       private:
         double resmod507( char sf, double w2, double q2 ) const;
         Parameterisation params_;
     };
   }
 }
 
 #endif
diff --git a/CepGen/StructureFunctions/FioreBrasse.h b/CepGen/StructureFunctions/FioreBrasse.h
index 0625c17..d1f5e2c 100644
--- a/CepGen/StructureFunctions/FioreBrasse.h
+++ b/CepGen/StructureFunctions/FioreBrasse.h
@@ -1,45 +1,43 @@
 #ifndef CepGen_StructureFunctions_FioreBrasse_h
 #define CepGen_StructureFunctions_FioreBrasse_h
 
 #include "CepGen/StructureFunctions/StructureFunctions.h"
 
 #include <vector>
 
 namespace CepGen
 {
   namespace SF
   {
+    ///\f${\cal W}_{1,2}\f$ structure functions parameterisation by Fiore et al \cite Fiore:2002re and Brasse et al \cite Brasse:1976bf
     class FioreBrasse : public StructureFunctions
     {
       public:
         struct Parameterisation
         {
           static Parameterisation standard();
           static Parameterisation alternative();
 
           struct ResonanceParameters {
 //            ResonanceParameters( double a0, double a1, double a2, double a, double q02, float spin ) :
 //              alpha0( a0 ), alpha1( a1 ), alpha2( a2 ), a( a ), q02( q02 ), spin( spin ) {}
             double alpha0, alpha1, alpha2, a, q02;
             float spin;
           };
 
           std::vector<ResonanceParameters> resonances;
           double s0, norm;
         };
-        /// Fiore-Brasse proton structure functions (F.W Brasse et al., DESY 76/11 (1976),
-        /// http://dx.doi.org/10.1016/0550-3213(76)90231-5)
+        /// Fiore \cite Fiore:2002re and Brasse \cite Brasse:1976bf proton structure functions
         explicit FioreBrasse( const FioreBrasse::Parameterisation& params = FioreBrasse::Parameterisation::standard() );
-        /// \param[in] q2 Squared 4-momentum transfer
-        /// \param[in] xbj Bjorken's x
-        /// \cite Brasse1976413
         FioreBrasse& operator()( double xbj, double q2 ) override;
+        /// Old implementation from LPAIR
         FioreBrasse& operator()( double xbj, double q2, bool old );
 
         double W1, W2;
         Parameterisation params;
     };
   }
 }
 
 #endif
diff --git a/CepGen/StructureFunctions/LHAPDF.cpp b/CepGen/StructureFunctions/LHAPDF.cpp
index 9c983e4..c251204 100644
--- a/CepGen/StructureFunctions/LHAPDF.cpp
+++ b/CepGen/StructureFunctions/LHAPDF.cpp
@@ -1,150 +1,159 @@
 #include "CepGen/StructureFunctions/LHAPDF.h"
 #include "CepGen/Core/Exception.h"
 #include "CepGen/Core/utils.h"
 
 namespace CepGen
 {
   namespace SF
   {
     constexpr std::array<short,6> LHAPDF::qtimes3_, LHAPDF::pdgid_;
 
     LHAPDF::Parameterisation::Parameterisation() :
-      num_flavours( 4 ), pdf_set( "cteq6" ), pdf_member( 0 ), mode( Mode::full )
+      num_flavours( 4 ), pdf_set( "cteq6" ), pdf_code( 0l ), pdf_member( 0 ), mode( Mode::full )
     {}
 
-    LHAPDF::Parameterisation
-    LHAPDF::Parameterisation::cteq6()
-    {
-      Parameterisation p;
-      p.num_flavours = 4;
-      p.pdf_set = "cteq6";
-      p.pdf_member = 0;
-      p.mode = Mode::full;
-      return p;
-    }
-
     LHAPDF::LHAPDF( const Parameterisation& param ) :
       StructureFunctions( Type::LHAPDF ), params( param ), initialised_( false )
     {}
 
     LHAPDF::LHAPDF( const char* set, unsigned short member, const Parameterisation::Mode& mode ) :
       StructureFunctions( Type::LHAPDF ), initialised_( false )
     {
       params.pdf_set = set;
       params.pdf_member = member;
       params.mode = mode;
     }
 
+    std::string
+    LHAPDF::description() const
+    {
+      std::ostringstream os;
+      os << "LHAPDF{" << params.pdf_set << ",m=" << params.pdf_member << ",mode=" << params.mode << "}";
+      return os.str();
+    }
+
     void
     LHAPDF::initialise()
     {
       if ( initialised_ )
         return;
 #ifdef LIBLHAPDF
       std::string lhapdf_version, pdf_description, pdf_type;
 #  if defined LHAPDF_MAJOR_VERSION && LHAPDF_MAJOR_VERSION == 6
       try {
+        //--- check if PDF code is set
+        if ( params.pdf_code != 0l ) {
+          auto pdf = ::LHAPDF::lookupPDF( params.pdf_code );
+          if ( pdf.second != 0 )
+            throw CG_FATAL( "LHAPDF" ) << "Failed to retrieve PDFset with id=" << params.pdf_code << "!";
+          if ( !params.pdf_set.empty() && params.pdf_set != pdf.first )
+            CG_WARNING( "LHAPDF" ) << "PDF set name changed from \"" << params.pdf_set << "\" to \"" << pdf.first << "\".";
+          params.pdf_set = pdf.first;
+        }
         pdf_set_ = ::LHAPDF::PDFSet( params.pdf_set );
         pdf_set_.mkPDFs<std::unique_ptr<::LHAPDF::PDF> >( pdfs_ );
         lhapdf_version = ::LHAPDF::version();
         pdf_description = pdf_set_.description();
         pdf_type = pdfs_[params.pdf_member]->type();
       } catch ( const ::LHAPDF::Exception& e ) {
         throw CG_FATAL( "LHAPDF" )
           << "Caught LHAPDF exception:\n\t"
           << e.what();
       }
 #  else
-      ::LHAPDF::initPDFSet( params.pdf_set, ::LHAPDF::LHGRID, params.pdf_member );
+      if ( params.pdf_code != 0l )
+        ::LHAPDF::initPDFSet( (int)params.pdf_code, params.pdf_member );
+      else
+        ::LHAPDF::initPDFSet( params.pdf_set, ::LHAPDF::LHGRID, params.pdf_member );
       lhapdf_version = ::LHAPDF::getVersion();
       pdf_description = ::LHAPDF::getDescription();
 #  endif
       replace_all( pdf_description, ". ", ".\n  " );
       CG_INFO( "LHAPDF" ) << "LHAPDF structure functions evaluator successfully built.\n"
         << " * LHAPDF version: " << lhapdf_version << "\n"
         << " * number of flavours: " << params.num_flavours << "\n"
         << " * PDF set: " << params.pdf_set << "\n"
         << ( pdf_description.empty() ? "" : "  "+pdf_description+"\n" )
         << " * PDF member: " << params.pdf_member << ( pdf_type.empty() ? "" : " ("+pdf_type+")" ) << "\n"
         << " * quarks mode: " << params.mode;
       initialised_ = true;
 #else
       throw CG_FATAL( "LHAPDF" ) << "LHAPDF is not liked to this instance!";
 #endif
     }
 
     LHAPDF&
     LHAPDF::operator()( double xbj, double q2 )
     {
 #ifdef LIBLHAPDF
       std::pair<double,double> nv = { xbj, q2 };
       if ( nv == old_vals_ )
         return *this;
       old_vals_ = nv;
 
       F2 = 0.;
       if ( params.num_flavours == 0 || params.num_flavours > 6 )
         return *this;
 
       if ( !initialised_ )
         initialise();
 #  if defined LHAPDF_MAJOR_VERSION && LHAPDF_MAJOR_VERSION >= 6
       auto& member = *pdfs_[params.pdf_member];
       if ( !member.inPhysicalRangeXQ2( xbj, q2 ) ) {
         CG_WARNING( "LHAPDF" ) << "(x=" << xbj << ", Q²=" << q2 << " GeV²) "
           << "not in physical range for PDF member " << params.pdf_member << ":\n\t"
           << "  min: (x=" << member.xMin() << ", Q²=" << member.q2Min() << "),\n\t"
           << "  max: (x=" << member.xMax() << ", Q²=" << member.q2Max() << ").";
         return *this;
       }
 #  else
       if ( q2 < ::LHAPDF::getQ2min( params.pdf_member ) || q2 > ::LHAPDF::getQ2max( params.pdf_member )
         || xbj < ::LHAPDF::getXmin( params.pdf_member ) || xbj > ::LHAPDF::getXmax( params.pdf_member ) ) {
         CG_WARNING( "LHAPDF" ) << "(x=" << xbj << "/Q²=" << q2 << " GeV²) "
           << "not in physical range for PDF member " << params.pdf_member << ":\n"
           << "  min: (x=" << ::LHAPDF::getXmin( params.pdf_member ) << "/Q²=" << ::LHAPDF::getQ2min( params.pdf_member ) << "),\n"
           << "  max: (x=" << ::LHAPDF::getXmax( params.pdf_member ) << "/Q²=" << ::LHAPDF::getQ2max( params.pdf_member ) << ").";
         return *this;
       }
       const double q = sqrt( q2 );
 #  endif
 
       for ( int i = 0; i < params.num_flavours; ++i ) {
         const double prefactor = 1./9.*qtimes3_[i]*qtimes3_[i];
 #  if defined LHAPDF_MAJOR_VERSION && LHAPDF_MAJOR_VERSION >= 6
         if ( !pdfs_[params.pdf_member]->hasFlavor( pdgid_[i] ) )
           throw CG_FATAL( "LHAPDF" ) << "Flavour " << pdgid_[i] << " is unsupported!";
         const double xq = member.xfxQ2( pdgid_[i], xbj, q2 );
         const double xqbar = member.xfxQ2( -pdgid_[i], xbj, q2 );
 #  else
         const double xq = ::LHAPDF::xfx( xbj, q, pdgid_[i] );
         const double xqbar = ::LHAPDF::xfx( xbj, q, -pdgid_[i] );
 #  endif
         switch ( params.mode ) {
           case Parameterisation::Mode::full:
             F2 += prefactor*( xq+xqbar ); break;
           case Parameterisation::Mode::valence:
             F2 += prefactor*( xq-xqbar ); break;
           case Parameterisation::Mode::sea:
             F2 += prefactor*( 2.*xqbar ); break;
         }
       }
 #else
       throw CG_FATAL( "LHAPDF" ) << "LHAPDF is not liked to this instance!";
 #endif
 
       return *this;
     }
   }
 
   std::ostream&
   operator<<( std::ostream& os, const SF::LHAPDF::Parameterisation::Mode& mode )
   {
     switch ( mode ) {
       case SF::LHAPDF::Parameterisation::Mode::full: return os << "all quarks";
       case SF::LHAPDF::Parameterisation::Mode::valence: return os << "valence quarks";
       case SF::LHAPDF::Parameterisation::Mode::sea: return os << "sea quarks";
     }
     return os;
   }
 }
diff --git a/CepGen/StructureFunctions/LHAPDF.h b/CepGen/StructureFunctions/LHAPDF.h
index a4e264e..09f8aa1 100644
--- a/CepGen/StructureFunctions/LHAPDF.h
+++ b/CepGen/StructureFunctions/LHAPDF.h
@@ -1,58 +1,68 @@
 #ifndef CepGen_StructureFunctions_LHAPDF_h
 #define CepGen_StructureFunctions_LHAPDF_h
 
 #include "StructureFunctions.h"
 
 #ifdef LIBLHAPDF
 #include "LHAPDF/LHAPDF.h"
 #endif
 
 #include <array>
 
 namespace CepGen
 {
   namespace SF
   {
-    /// Generic, tree-level import of structure functions from an external PDFs grid
+    /// Generic partonic level perturbative structure functions built from an external PDFs grid
     class LHAPDF : public StructureFunctions
     {
       public:
+        /// Model parameterisation
         struct Parameterisation
         {
+          /// Standard (usual CTEQ6) constructor
           Parameterisation();
-          static Parameterisation cteq6();
+          /// Number of quark flavours considered in the SF building
           unsigned short num_flavours;
+          /// String-type PDF identifier (default)
           std::string pdf_set;
+          /// Integer-type PDF identifier (if no string version is provided)
+          unsigned long pdf_code;
+          /// PDF set used
           unsigned short pdf_member;
+          /// Quarks types
           enum class Mode { full = 0, valence = 1, sea = 2 };
+          /// Quarks types considered in the SF building
           Mode mode;
         };
-
-        explicit LHAPDF( const Parameterisation& param = Parameterisation::cteq6() );
+        /// Build a calculator from its Parameterisation object
+        explicit LHAPDF( const Parameterisation& param = Parameterisation() );
+        /// Build a calculator from a set, its member, and the contributing quarks
         explicit LHAPDF( const char* set, unsigned short member = 0, const Parameterisation::Mode& mode = Parameterisation::Mode::full );
         LHAPDF& operator()( double xbj, double q2 ) override;
-
+        /// Parameterisation used in this SFs calculator
         Parameterisation params;
 
       private:
+        std::string description() const override;
         void initialise();
         bool initialised_;
 
 #ifdef LIBLHAPDF
 #  if defined LHAPDF_MAJOR_VERSION && LHAPDF_MAJOR_VERSION >= 6
         ::LHAPDF::PDFSet pdf_set_;
         std::vector<std::unique_ptr<::LHAPDF::PDF> > pdfs_;
 #  endif
 #endif
         static constexpr std::array<short,6> pdgid_ = { { 1, 2, 3, 4, 5, 6 } };
         static constexpr std::array<short,6> qtimes3_ = { {
           -1 /*d*/, 2 /*u*/,
           -1 /*s*/, 2 /*c*/,
           -1 /*b*/, 2 /*t*/
         } };
     };
   }
   std::ostream& operator<<( std::ostream& os, const SF::LHAPDF::Parameterisation::Mode& mode );
 }
 
 #endif
diff --git a/CepGen/StructureFunctions/MSTWGrid.cpp b/CepGen/StructureFunctions/MSTWGrid.cpp
index 8d20cac..41e56ec 100644
--- a/CepGen/StructureFunctions/MSTWGrid.cpp
+++ b/CepGen/StructureFunctions/MSTWGrid.cpp
@@ -1,113 +1,116 @@
 #include "CepGen/StructureFunctions/MSTWGrid.h"
 #include "CepGen/Core/Exception.h"
 #include "CepGen/Core/utils.h"
 #include "CepGen/StructureFunctions/StructureFunctions.h"
 
 #include <fstream>
-#include <set>
 
 namespace mstw
 {
   const unsigned int Grid::good_magic = 0x5754534d; // MSTW in ASCII
 
   Grid&
   Grid::get( const char* filename )
   {
     Parameterisation p;
     p.grid_path = filename;
     static Grid instance( p );
     return instance;
   }
 
   Grid::Grid( const Parameterisation& param ) :
     CepGen::StructureFunctions( CepGen::SF::Type::MSTWgrid ),
     CepGen::GridHandler<2,2>( CepGen::GridType::logarithmic ),
     params( param )
   {
-    std::set<double> q2_vals, xbj_vals;
-
     { // file readout part
       std::ifstream file( params.grid_path, std::ios::binary | std::ios::in );
       if ( !file.is_open() )
         throw CG_FATAL( "Grid" ) << "Impossible to load grid file \"" << params.grid_path << "\"!";
 
       file.read( reinterpret_cast<char*>( &header_ ), sizeof( header_t ) );
 
       // first checks on the file header
 
       if ( header_.magic != good_magic )
         throw CG_FATAL( "Grid" ) << "Wrong magic number retrieved: " << header_.magic << ", expecting " << good_magic << ".";
 
       if ( header_.nucleon != header_t::proton )
         throw CG_FATAL( "Grid" ) << "Only proton structure function grids can be retrieved for this purpose!";
 
       // retrieve all points and evaluate grid boundaries
 
       sfval_t val;
-      while ( file.read( reinterpret_cast<char*>( &val ), sizeof( sfval_t ) ) ) {
-        q2_vals.insert( val.q2 );
-        xbj_vals.insert( val.xbj );
+      while ( file.read( reinterpret_cast<char*>( &val ), sizeof( sfval_t ) ) )
         insert( { val.xbj, val.q2 }, { val.f2, val.fl } );
-      }
       file.close();
     }
 
-    if ( q2_vals.size() < 2 || xbj_vals.size() < 2 )
-      throw CG_FATAL( "Grid" ) << "Invalid grid retrieved!";
-
     init();
 
+    const auto& bounds = boundaries();
     CG_INFO( "Grid" )
       << "MSTW@" << header_.order << " grid evaluator built "
       << "for " << header_.nucleon << " structure functions (" << header_.cl << ")\n\t"
-      << "xBj in range [" << pow( 10., *xbj_vals.begin() ) << ":" << pow( 10., *xbj_vals.rbegin() ) << "]\n\t"
-      << " Q² in range [" << pow( 10., *q2_vals.begin() ) << ":" << pow( 10., *q2_vals.rbegin() ) << "].";
+      << "xBj in range [" << pow( 10., bounds[0].first ) << ":" << pow( 10., bounds[0].second ) << "]\n\t"
+      << " Q² in range [" << pow( 10., bounds[1].first ) << ":" << pow( 10., bounds[1].second ) << "].";
+  }
+
+  std::string
+  Grid::description() const
+  {
+    std::ostringstream os;
+    const auto& bounds = boundaries();
+    os << "MSTW grid{"
+       << pow( 10., bounds[0].first ) << "<xbj<" << pow( 10., bounds[0].second ) << ","
+       << pow( 10., bounds[1].first ) << "<Q²/GeV²<" << pow( 10., bounds[1].second ) << "}";
+    return os.str();
   }
 
   Grid&
   Grid::operator()( double xbj, double q2 )
   {
     const std::array<double,2> val = CepGen::GridHandler<2,2>::eval( { xbj, q2 } );
     F2 = val[0];
     FL = val[1];
     return *this;
   }
 
   std::ostream&
   operator<<( std::ostream& os, const Grid::sfval_t& val )
   {
     return os << CepGen::Form( "xbj = %.4f\tQ² = %.5e GeV²\tF₂ = % .6e\tFL = % .6e", val.xbj, val.q2, val.f2, val.fl );
   }
 
   std::ostream&
   operator<<( std::ostream& os, const Grid::header_t::order_t& order )
   {
     switch ( order ) {
       case Grid::header_t::lo: return os << "LO";
       case Grid::header_t::nlo: return os << "nLO";
       case Grid::header_t::nnlo: return os << "nnLO";
     }
     return os;
   }
 
   std::ostream&
   operator<<( std::ostream& os, const Grid::header_t::cl_t& cl )
   {
     switch ( cl ) {
       case Grid::header_t::cl68: return os << "68% C.L.";
       case Grid::header_t::cl95: return os << "95% C.L.";
     }
     return os;
   }
 
   std::ostream&
   operator<<( std::ostream& os, const Grid::header_t::nucleon_t& nucl )
   {
     switch ( nucl ) {
       case Grid::header_t::proton: return os << "proton";
       case Grid::header_t::neutron: return os << "neutron";
     }
     return os;
   }
 }
 
diff --git a/CepGen/StructureFunctions/MSTWGrid.h b/CepGen/StructureFunctions/MSTWGrid.h
index 68f31e1..42962d7 100644
--- a/CepGen/StructureFunctions/MSTWGrid.h
+++ b/CepGen/StructureFunctions/MSTWGrid.h
@@ -1,72 +1,82 @@
 #ifndef CepGen_StructureFunctions_MSTWGrid_h
 #define CepGen_StructureFunctions_MSTWGrid_h
 
 #include "CepGen/IO/GridHandler.h"
 #include "CepGen/StructureFunctions/StructureFunctions.h"
 
 #define DEFAULT_MSTW_GRID_PATH "External/mstw_sf_scan_nnlo.dat"
 
 /// Martin-Stirling-Thorne-Watt PDFs structure functions
 namespace mstw
 {
   /// A \f$F_{2,L}\f$ grid interpolator
   class Grid : public CepGen::StructureFunctions, private CepGen::GridHandler<2,2>
   {
     public:
       /// Grid header information as parsed from the file
       struct header_t
       {
+        /// Interpolation order
         enum order_t : unsigned short { lo = 0, nlo = 1, nnlo = 2 };
+        /// Confidence level
         enum cl_t : unsigned short { cl68 = 0, cl95 = 1 };
+        /// Type of nucleon interpolated
         enum nucleon_t : unsigned short { proton = 1, neutron = 2 };
-        unsigned int magic;
-        order_t order;
-        cl_t cl;
-        nucleon_t nucleon;
+        unsigned int magic; ///< Grid file magic number
+        order_t order; ///< Interpolation order
+        cl_t cl; ///< Confidence level
+        nucleon_t nucleon; ///< Type of nucleon interpolated
       };
+      /// Structure functions value at a given \f$Q^2/x_{\rm Bj}\f$ coordinate
       struct sfval_t
       {
-        float q2, xbj;
-        double f2, fl;
+        float q2; ///< four-momentum transfer, in GeV\f${}^2\f$
+        float xbj; ///< Bjorken's scaling variable
+        double f2; ///< Transverse structure function value
+        double fl; ///< Longitudinal structure function value
       };
+      /// List of parameters for this grid definition
       struct Parameterisation {
         Parameterisation() : grid_path( DEFAULT_MSTW_GRID_PATH ) {}
+        /// Location of the grid to be interpolated
         std::string grid_path;
       };
 
     public:
       /// Retrieve the grid interpolator (singleton)
       static Grid& get( const char* path = DEFAULT_MSTW_GRID_PATH );
 
       /// Compute the structure functions at a given \f$Q^2/x_{\rm Bj}\f$
       Grid& operator()( double xbj, double q2 ) override;
       /// Retrieve the grid's header information
       header_t header() const { return header_; }
+      /// Grid parameterisation object
       Parameterisation params;
 
         //--- already retrieved from grid, so no need to recompute it
       void computeFL( double xbj, double q2, const CepGen::SF::SigmaRatio& ) override {}
       void computeFL( double xbj, double q2, double r ) override {}
 
     public:
       Grid( const Grid& ) = delete;
       void operator=( const GridHandler& ) = delete;
 
     private:
       explicit Grid( const Parameterisation& = Parameterisation() );
+      std::string description() const override;
       static const unsigned int good_magic;
       static std::shared_ptr<Grid> singl_;
 
       header_t header_;
   };
 
-  std::ostream& operator<<( std::ostream&, const Grid::sfval_t& );
-  std::ostream& operator<<( std::ostream&, const Grid::header_t::order_t& );
-  std::ostream& operator<<( std::ostream&, const Grid::header_t::cl_t& );
-  std::ostream& operator<<( std::ostream&, const Grid::header_t::nucleon_t& );
+  std::ostream& operator<<( std::ostream&, const Grid::sfval_t& ); ///< Human-readable description of a values point
+  std::ostream& operator<<( std::ostream&, const Grid::header_t::order_t& ); ///< Human-readable description of an interpolation order
+  std::ostream& operator<<( std::ostream&, const Grid::header_t::cl_t& ); ///< Human-readable description of a confidence level
+  std::ostream& operator<<( std::ostream&, const Grid::header_t::nucleon_t& ); ///< Human-readable description of a nucleon type
 }
 
 #undef DEFAULT_MSTW_GRID_PATH
 
 #endif
 
diff --git a/CepGen/StructureFunctions/Schaefer.cpp b/CepGen/StructureFunctions/Schaefer.cpp
index a56e8b3..95f943a 100644
--- a/CepGen/StructureFunctions/Schaefer.cpp
+++ b/CepGen/StructureFunctions/Schaefer.cpp
@@ -1,137 +1,151 @@
 #include "CepGen/StructureFunctions/Schaefer.h"
 #include "CepGen/StructureFunctions/StructureFunctionsBuilder.h"
 #include "CepGen/StructureFunctions/LHAPDF.h"
 
 #include "CepGen/Core/Exception.h"
 
 #include "CepGen/Physics/Constants.h"
 #include "CepGen/Physics/ParticleProperties.h"
 
 namespace CepGen
 {
   namespace SF
   {
     Schaefer::Parameterisation
     Schaefer::Parameterisation::mstwGrid()
     {
       Parameterisation par;
       par.q2_cut = 9.;
       par.w2_hi = 4.;
       par.w2_lo = 3.;
       par.resonances_model = StructureFunctionsBuilder::get( SF::Type::ChristyBosted );
       par.perturbative_model = StructureFunctionsBuilder::get( SF::Type::MSTWgrid );
       par.continuum_model = StructureFunctionsBuilder::get( SF::Type::GD11p );
       par.higher_twist = 0;
       return par;
     }
 
     Schaefer::Parameterisation
     Schaefer::Parameterisation::mstwParton()
     {
       Parameterisation par;
       par.q2_cut = 9.;
       par.w2_hi = 4.;
       par.w2_lo = 3.;
       par.resonances_model = StructureFunctionsBuilder::get( SF::Type::ChristyBosted );
       par.perturbative_model = std::make_shared<SF::LHAPDF>( "MSTW2008nnlo90cl" );
       par.continuum_model = StructureFunctionsBuilder::get( SF::Type::GD11p );
       par.higher_twist = 1;
       return par;
     }
 
     Schaefer::Parameterisation
     Schaefer::Parameterisation::cteq()
     {
       Parameterisation par;
       par.q2_cut = 9.;
       par.w2_hi = 4.;
       par.w2_lo = 3.;
       par.resonances_model = StructureFunctionsBuilder::get( SF::Type::ChristyBosted );
       par.perturbative_model = std::make_shared<SF::LHAPDF>( "cteq6l1" );
       par.continuum_model = StructureFunctionsBuilder::get( SF::Type::GD11p );
       par.higher_twist = 0;
       return par;
     }
 
     Schaefer::Schaefer( const Schaefer::Parameterisation& params ) :
       StructureFunctions( Type::Schaefer ),
       params( params ), initialised_( false ), inv_omega_range_( -1. )
     {}
 
+    std::string
+    Schaefer::description() const
+    {
+      std::ostringstream os;
+      os << "LUXlike{"
+         << "r=" << *params.resonances_model << ","
+         << "p=" << *params.perturbative_model << ","
+         << "c=" << *params.continuum_model;
+      if ( params.higher_twist )
+        os << ",HT";
+      os << "}";
+      return os.str();
+    }
+
     void
     Schaefer::initialise()
     {
       CG_INFO( "LUXlike" ) << "LUXlike structure functions evaluator successfully initialised.\n"
         << " * Q² cut:             " << params.q2_cut << " GeV²\n"
         << " * W² ranges:          " << params.w2_lo << " GeV² / " << params.w2_hi << " GeV²\n"
-        << " * resonance model:    " << params.resonances_model->type << "\n"
-        << " * perturbative model: " << params.perturbative_model->type << "\n"
-        << " * continuum model:    " << params.continuum_model->type << "\n"
+        << " * resonance model:    " << *params.resonances_model << "\n"
+        << " * perturbative model: " << *params.perturbative_model << "\n"
+        << " * continuum model:    " << *params.continuum_model << "\n"
         << " * higher-twist?       " << std::boolalpha << params.higher_twist;
       inv_omega_range_ = 1./( params.w2_hi-params.w2_lo );
       initialised_ = true;
     }
 
     Schaefer&
     Schaefer::operator()( double xbj, double q2 )
     {
       if ( !initialised_ )
         initialise();
 
       std::pair<double,double> nv = { xbj, q2 };
       if ( nv == old_vals_ )
         return *this;
       old_vals_ = nv;
 
       const double w2 = mp2_+q2*( 1.-xbj )/xbj;
 
       StructureFunctions sel_sf;
       if ( q2 < params.q2_cut ) {
         if ( w2 < params.w2_lo )
           sel_sf = ( *params.resonances_model )( xbj, q2 );
         else if ( w2 < params.w2_hi ) {
           auto sf_r = ( *params.resonances_model )( xbj, q2 );
           auto sf_c = ( *params.continuum_model )( xbj, q2 );
           sf_r.computeFL( xbj, q2 );
           sf_c.computeFL( xbj, q2 );
           const double r = rho( w2 );
           F2 = r*sf_c.F2 + ( 1.-r )*sf_r.F2;
           FL = r*sf_c.FL + ( 1.-r )*sf_r.FL;
           return *this;
         }
         else
           sel_sf = ( *params.continuum_model )( xbj, q2 );
       }
       else {
         if ( w2 < params.w2_hi )
           sel_sf = ( *params.continuum_model )( xbj, q2 );
         else {
           auto sf_p = ( *params.perturbative_model )( xbj, q2 );
           F2 = sf_p.F2;
           sf_p.computeFL( xbj, q2 );
           FL = sel_sf.FL;
           if ( params.higher_twist )
             F2 *= ( 1.+5.5/q2 );
           return *this;
         }
       }
 
       F2 = sel_sf( xbj, q2 ).F2;
       sel_sf.computeFL( xbj, q2 );
       FL = sel_sf.FL;
 
       return *this;
     }
 
     double
     Schaefer::rho( double w2 ) const
     {
       if ( inv_omega_range_ <= 0. )
         throw CG_FATAL( "LUXlike" ) << "Invalid W² limits: "
           << params.w2_lo << " / " << params.w2_hi << " GeV²!";
       const double omega = ( w2-params.w2_lo )*inv_omega_range_;
       const double omega2 = omega*omega;
       return 2.*omega2-omega*omega;
     }
   }
 }
diff --git a/CepGen/StructureFunctions/Schaefer.h b/CepGen/StructureFunctions/Schaefer.h
index 8ae678b..34bbf6b 100644
--- a/CepGen/StructureFunctions/Schaefer.h
+++ b/CepGen/StructureFunctions/Schaefer.h
@@ -1,37 +1,44 @@
 #ifndef CepGen_StructureFunctions_Schaefer_h
 #define CepGen_StructureFunctions_Schaefer_h
 
-#include "StructureFunctions.h"
+#include "CepGen/StructureFunctions/StructureFunctions.h"
 #include <memory>
 
 namespace CepGen
 {
   namespace SF
   {
+    /// LUX-like hybrid modelling of \f$F_{2/L}\f$ structure functions
     class Schaefer : public StructureFunctions
     {
       public:
+        /// Standard parameterisation for this SF set
         struct Parameterisation
         {
-          static Parameterisation mstwGrid();
-          static Parameterisation mstwParton();
-          static Parameterisation cteq();
-          double q2_cut, w2_lo, w2_hi;
-          std::shared_ptr<StructureFunctions> resonances_model, perturbative_model, continuum_model;
-          bool higher_twist;
+          static Parameterisation mstwGrid(); ///< "Standard" parameterisation with MSTW grid NNLO perturbative model
+          static Parameterisation mstwParton(); ///< "Standard" parameterisation with partonic MSTW perturbative model
+          static Parameterisation cteq(); ///< "Standard" parameterisation with partonic CTEQ perturbative model
+          double q2_cut; ///< Transition \f$Q^2\f$ before reaching the continuum/perturbative regions
+          double w2_lo; ///< Transition \f$W^2\f$ between resonances and hybrid continuum/resonances low-\f$Q^2\f$ regions
+          double w2_hi; ///< Transition \f$W^2\f$ between hybrid continuum/resonances and continuum low-\f$Q^2\f$ regions, or continuum and perturbative high-\f$Q^2\f$ regions
+          std::shared_ptr<StructureFunctions> resonances_model; ///< Resonances-dominated region (low-\f$Q^2/W^2\f$) modelling
+          std::shared_ptr<StructureFunctions> perturbative_model; ///< Perturbative region (high-\f$Q^2/W^2\f$) modelling
+          std::shared_ptr<StructureFunctions> continuum_model; ///< Continuum regions modelling
+          bool higher_twist; ///< Enable/disable the HT correction
         };
         Schaefer( const Parameterisation& param = Parameterisation::mstwGrid() );
         Schaefer& operator()( double xbj, double q2 ) override;
 
         Parameterisation params;
 
       private:
+        std::string description() const override;
         double rho( double w2 ) const;
         void initialise();
         bool initialised_;
         double inv_omega_range_;
     };
   }
 }
 
 #endif
diff --git a/CepGen/StructureFunctions/SigmaRatio.cpp b/CepGen/StructureFunctions/SigmaRatio.cpp
index f1f7fb3..eb874e1 100644
--- a/CepGen/StructureFunctions/SigmaRatio.cpp
+++ b/CepGen/StructureFunctions/SigmaRatio.cpp
@@ -1,103 +1,104 @@
 #include "CepGen/StructureFunctions/SigmaRatio.h"
 #include "CepGen/Physics/PDG.h"
 #include "CepGen/Physics/ParticleProperties.h"
 
 #include <math.h>
 #include <iostream>
 
 namespace CepGen
 {
   namespace SF
   {
     const double SigmaRatio::mp_ = ParticleProperties::mass( PDG::proton );
     const double SigmaRatio::mp2_ = SigmaRatio::mp_*SigmaRatio::mp_;
 
     double
     SigmaRatio::theta( double xbj, double q2 ) const
     {
       return 1.+12.*( q2/( q2+1. ) )*( 0.125*0.125/( 0.125*0.125+xbj*xbj ) );
     }
 
     E143Ratio::Parameterisation
     E143Ratio::Parameterisation::standard()
     {
       Parameterisation out;
       out.q2_b = 0.34;
       out.lambda2 = 0.2*0.2;
       out.a = { { 0.0485, 0.5470,  2.0621, -0.3804,   0.5090, -0.0285 } };
       out.b = { { 0.0481, 0.6114, -0.3509, -0.4611,   0.7172, -0.0317 } };
       out.c = { { 0.0577, 0.4644,  1.8288, 12.3708, -43.1043, 41.7415 } };
       return out;
     }
 
     E143Ratio::E143Ratio( const Parameterisation& param ) :
       params_( param )
     {}
 
     double
     E143Ratio::operator()( double xbj, double q2, double& err ) const
     {
       const double u = q2/params_.q2_b;
       const double inv_xl = 1./log( q2/params_.lambda2 );
       const double pa = ( 1.+params_.a[3]*xbj+params_.a[4]*xbj*xbj )*pow( xbj, params_.a[5] );
       const double pb = ( 1.+params_.b[3]*xbj+params_.b[4]*xbj*xbj )*pow( xbj, params_.b[5] );
       const double theta = SigmaRatio::theta( xbj, q2 );
       const double q2_thr = params_.c[3]*xbj + params_.c[4]*xbj*xbj+params_.c[5]*xbj*xbj*xbj;
       // here come the three fits
       const double ra = params_.a[0]*inv_xl*theta + params_.a[1]/pow( pow( q2, 4 )+pow( params_.a[2], 4 ), 0.25 )*pa,
                    rb = params_.b[0]*inv_xl*theta + ( params_.b[1]/q2+params_.b[2]/( q2*q2+0.3*0.3 ) )*pb,
                    rc = params_.c[0]*inv_xl*theta + params_.c[1]*pow( pow( q2-q2_thr, 2 )+pow( params_.c[2], 2 ), -0.5 );
 
       const double r = ( ra+rb+rc ) / 3.; // R is set to be the average of the three fits
       // numerical safety for low-Q²
       err = 0.0078-0.013*xbj+( 0.070-0.39*xbj+0.70*xbj*xbj )/( 1.7+q2 );
       if ( q2 > params_.q2_b )
         return r;
       return r * 0.5 * ( 3.*u-u*u*u );
     }
 
     R1990Ratio::Parameterisation
     R1990Ratio::Parameterisation::standard()
     {
       Parameterisation out;
       out.lambda2 = 0.2*0.2;
       out.b = { { 0.0635, 0.5747, -0.3534 } };
       return out;
     }
 
     R1990Ratio::R1990Ratio( const Parameterisation& param ) :
       params_( param )
     {}
 
     double
     R1990Ratio::operator()( double xbj, double q2, double& err ) const
     {
       err = 0.;
       return ( params_.b[0]+SigmaRatio::theta( xbj, q2 )/log( q2/params_.lambda2 )
              + params_.b[1]/q2
              + params_.b[2]/( q2*q2+0.09 ) );
     }
 
     double
     CLASRatio::operator()( double xbj, double q2, double& err ) const
     {
       // 2 kinematic regions:
       //  - resonances ( w < 2.5 )
       //  - DIS ( w > 2.5 )
       const double w2 = mp2_ + q2*( 1.-xbj )/xbj, w = sqrt( w2 );
       const double xth = q2/( q2+2.5*2.5-mp2_ ); // xth = x( W = 2.5 GeV )
       const double zeta = log( 25.*q2 );
       const double xitmp = ( w < 2.5 ) ? theta( xth, q2 ) : theta( xbj, q2 );
       const double tmp = 0.041*xitmp/zeta + 0.592/q2 - 0.331/( 0.09+q2*q2 );
-      if ( w < 2.5 ) return tmp * pow( ( 1.-xbj )/( 1.-xth ), 3 );
+      if ( w < 2.5 )
+        return tmp * pow( ( 1.-xbj )/( 1.-xth ), 3 );
       return tmp;
     }
 
     double
     SBRatio::operator()( double xbj, double q2, double& err ) const
     {
       err = 0.;
       return 0.014*q2*( exp( -0.07*q2 )+41.*exp( -0.8*q2 ) );
     }
   }
 }
diff --git a/CepGen/StructureFunctions/SigmaRatio.h b/CepGen/StructureFunctions/SigmaRatio.h
index 22ee12f..45af95d 100644
--- a/CepGen/StructureFunctions/SigmaRatio.h
+++ b/CepGen/StructureFunctions/SigmaRatio.h
@@ -1,76 +1,79 @@
 #ifndef CepGen_StructureFunctions_SigmaRatio_h
 #define CepGen_StructureFunctions_SigmaRatio_h
 
 #include <array>
 
 namespace CepGen
 {
   namespace SF
   {
+    /// A generic modelling of the \f$R=\sigma_L/\sigma_T\f$ ratio
     class SigmaRatio
     {
       public:
         SigmaRatio() {}
-        /// Extract the longitudinal/transverse cross section ratio and associated error for a given Q²/\f$x_{\textrm{Bj}}\f$ couple.
+        /// Extract the longitudinal/transverse cross section ratio and associated error for a given \f$(x_{\rm Bj},Q^2)\f$ couple.
         virtual double operator()( double xbj, double q2, double& err ) const = 0;
 
       protected:
-        /// \f$x_{\textrm{Bj}}\f$ dependence for QCD-matching of R at high-Q²
+        /// \f$x_{\rm Bj}\f$ dependence for QCD-matching of R at high-\f$Q^2\f$
         double theta( double xbj, double q2 ) const;
-        static const double mp_, mp2_;
+        static const double mp_; ///< Proton mass, in GeV/c\f${}^2\f$
+        static const double mp2_; ///< Squared proton mass, in GeV\f${}^2\f$/c\f${}^4\f$
     };
 
-    // Reference: arXiv:hep-ex/9808028
+    /// E143 experimental R measurement \cite Abe:1998ym
     class E143Ratio : public SigmaRatio
     {
       public:
         struct Parameterisation
         {
           double q2_b, lambda2;
           std::array<double,6> a, b, c;
           static Parameterisation standard();
         };
         explicit E143Ratio( const Parameterisation& param = Parameterisation::standard() );
         double operator()( double xbj, double q2, double& err ) const override;
 
       private:
         Parameterisation params_;
     };
 
-    /// \warning valid for Q² > 0.3 GeV²
-    // Reference: Phys.Lett. B 250 (1990) 193-198 (https://inspirehep.net/record/296980)
+    /** \brief SLAC experimental R measurement \cite Whitlow:1990gk
+     * \warning valid for \f$Q^2\f$ > 0.3 GeV\f${}^2\f$
+     */
     class R1990Ratio: public SigmaRatio
     {
       public:
         struct Parameterisation
         {
           double lambda2;
           std::array<double,3> b;
           static Parameterisation standard();
         };
         explicit R1990Ratio( const Parameterisation& param = Parameterisation::standard() );
         double operator()( double xbj, double q2, double& err ) const override;
 
       private:
         Parameterisation params_;
     };
 
+    /// CLAS experimental R measurement
     class CLASRatio : public SigmaRatio
     {
       public:
         CLASRatio() {}
         double operator()( double xbj, double q2, double& err ) const override;
     };
 
-    /// Sibirtsev & Blunden parameterisation of the R ratio
-    // Reference: Phys.Rev. C 88,065202 (2013)
+    /// Sibirtsev & Blunden parameterisation of the R ratio \cite Sibirtsev:2013cga
     class SBRatio : public SigmaRatio
     {
       public:
         SBRatio() {}
         double operator()( double xbj, double q2, double& err ) const override;
     };
   }
 }
 
 #endif
diff --git a/CepGen/StructureFunctions/StructureFunctions.cpp b/CepGen/StructureFunctions/StructureFunctions.cpp
index f98c2ed..6d6c31c 100644
--- a/CepGen/StructureFunctions/StructureFunctions.cpp
+++ b/CepGen/StructureFunctions/StructureFunctions.cpp
@@ -1,75 +1,88 @@
 #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 xbj, double q2 ) 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 xbj, double q2, const SF::SigmaRatio& ratio )
   {
     double r_error = 0.;
     computeFL( xbj, q2, ratio( xbj, q2, r_error ) );
   }
 
   void
   StructureFunctions::computeFL( double xbj, double q2, double r )
   {
     const double tau = 4.*xbj*xbj*mp2_/q2;
     FL = F2 * ( 1.+tau ) * ( r/( 1.+r ) );
   }
 
+  std::string
+  StructureFunctions::description() const
+  {
+    std::ostringstream os;
+    os << type;
+    return os.str();
+  }
+
   /// 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;
+    os << sf.description();
+    if ( sf.old_vals_ != std::pair<double,double>() )
+      os << " at (" << sf.old_vals_.first << ", " << sf.old_vals_.second << "): "
+         << "F2 = " << sf.F2 << ", FL = " << sf.FL;
+    return os;
   }
+
   /// 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 << "ALLM91";
       case SF::Type::ALLM97:              return os << "ALLM97";
       case SF::Type::GD07p:               return os << "GD07p";
       case SF::Type::GD11p:               return os << "GD11p";
       case SF::Type::Schaefer:            return os << "LUXlike";
       case SF::Type::MSTWgrid:            return os << "MSTW (grid)";
       case SF::Type::LHAPDF:              return os << "LHAPDF";
     }
     return os;
   }
 }
diff --git a/CepGen/StructureFunctions/StructureFunctions.h b/CepGen/StructureFunctions/StructureFunctions.h
index 0d16122..cebbddf 100644
--- a/CepGen/StructureFunctions/StructureFunctions.h
+++ b/CepGen/StructureFunctions/StructureFunctions.h
@@ -1,71 +1,86 @@
 #ifndef CepGen_StructureFunctions_StructureFunctions_h
 #define CepGen_StructureFunctions_StructureFunctions_h
 
 #include "CepGen/StructureFunctions/SigmaRatio.h"
 
 #include <iostream>
 #include <map>
 
 namespace CepGen
 {
+  /// Structure functions modelling scope
   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,
       LHAPDF              = 401,
     };
   }
   std::ostream& operator<<( std::ostream&, const SF::Type& );
 
   class StructureFunctionsFactory;
+  /// Generic placeholder for the parameterisation of nucleon structure functions
   class StructureFunctions
   {
     public:
+      /// Copy constructor
       StructureFunctions( const StructureFunctions& sf ) :
         type( sf.type ), F2( sf.F2 ), FL( sf.FL ), old_vals_( sf.old_vals_ ) {}
+      /// Standard SF parameterisation constructor
       StructureFunctions( const SF::Type& type = SF::Type::Invalid, double f2 = 0., double fl = 0. ) :
         type( type ), F2( f2 ), FL( fl ), old_vals_({ 0., 0. }) {}
       ~StructureFunctions() {}
 
+      /// Human-readable description of this SF parameterisation
+      friend std::ostream& operator<<( std::ostream&, const StructureFunctions& );
+      /// Assign from another SF parameterisation object
       StructureFunctions& operator=( const StructureFunctions& sf ) {
         type = sf.type, F2 = sf.F2, FL = sf.FL, old_vals_ = sf.old_vals_;
         return *this;
       }
 
+      /// Build a SF parameterisation for a given type
       static StructureFunctions builder( const SF::Type& );
 
+      /// Compute all relevant structure functions for a given \f$(x_{\rm Bj},Q^2)\f$ couple
       virtual StructureFunctions& operator()( double xbj, double q2 ) { return *this; }
+      /// Compute the longitudinal structure function for a given point
       virtual void computeFL( double xbj, double q2, const SF::SigmaRatio& ratio = SF::E143Ratio() );
+      /// Compute the longitudinal structure function for a given point
       virtual void computeFL( double xbj, double q2, double r );
+      /// Compute the \f$F_1\f$ structure function for a given point
       double F1( double xbj, double q2 ) const;
 
+      /// Interpolation type of structure functions
       SF::Type type;
-      double F2, FL;
+      double F2; ///< Last computed transverse structure function value
+      double FL; ///< Last computed longitudinal structure function value
 
     protected:
-      static const double mp_, mp2_;
-      std::pair<double,double> old_vals_;
+      virtual std::string description() const; ///< Human-readable description of this SF set
+      static const double mp_; ///< Proton mass, in GeV/c\f${}^2\f$
+      static const double mp2_; ///< Squared proton mass, in GeV\f${}^2\f$/c\f${}^4\f$
+      std::pair<double,double> old_vals_; ///< Last \f$(x_{\rm Bj},Q^2)\f$ couple computed
 
     private:
       std::string name_;
   };
-  std::ostream& operator<<( std::ostream&, const StructureFunctions& );
 }
 
 #endif
diff --git a/CepGen/StructureFunctions/SuriYennie.h b/CepGen/StructureFunctions/SuriYennie.h
index df29643..c2c5036 100644
--- a/CepGen/StructureFunctions/SuriYennie.h
+++ b/CepGen/StructureFunctions/SuriYennie.h
@@ -1,31 +1,35 @@
 #ifndef CepGen_StructureFunctions_SuriYennie_h
 #define CepGen_StructureFunctions_SuriYennie_h
 
 #include "StructureFunctions.h"
 
 namespace CepGen
 {
   namespace SF
   {
+    /// \f$F_{1/2/E/M}\f$ modelling by Suri and Yennie \cite Suri:1971yx
     class SuriYennie : public StructureFunctions
     {
       public:
+        /// Collection of parameterisation-dependent couplings
         struct Parameterisation {
           // values extracted from experimental fits
           static Parameterisation standard();
           static Parameterisation alternative();
 
           double C1, C2, D1, rho2, Cp, Bp;
         };
 
         explicit SuriYennie( const SuriYennie::Parameterisation& param = SuriYennie::Parameterisation::standard() );
         SuriYennie& operator()( double xbj, double q2 ) override;
 
-        double F1, FE, FM;
+        double F1;
+        double FE; ///< Electric proton form factor
+        double FM; ///< Magnetic proton form factor
       private:
         Parameterisation params_;
     };
   }
 }
 
 #endif
diff --git a/CepGen/StructureFunctions/SzczurekUleshchenko.h b/CepGen/StructureFunctions/SzczurekUleshchenko.h
index 38e08ec..ba6b032 100644
--- a/CepGen/StructureFunctions/SzczurekUleshchenko.h
+++ b/CepGen/StructureFunctions/SzczurekUleshchenko.h
@@ -1,26 +1,27 @@
 #ifndef CepGen_StructureFunctions_SzczurekUleshchenko_h
 #define CepGen_StructureFunctions_SzczurekUleshchenko_h
 
 #include "CepGen/StructureFunctions/StructureFunctions.h"
 
 extern "C"
 {
   extern void grv95lo_( float&, float&, float&, float&, float&, float&, float&, float& );
 }
 
 namespace CepGen
 {
   namespace SF
   {
+    /// Szcurek and Uleshchenko modelling of \f$F_{1/2}\f$ \cite Szczurek:1999wp
     class SzczurekUleshchenko : public StructureFunctions
     {
       public:
         SzczurekUleshchenko();
         SzczurekUleshchenko& operator()( double xbj, double q2 ) override;
 
         double F1;
     };
   }
 }
 
 #endif
diff --git a/CepGen/Version.h b/CepGen/Version.h
index 1c81b6c..64fd762 100644
--- a/CepGen/Version.h
+++ b/CepGen/Version.h
@@ -1,13 +1,18 @@
 #ifndef CepGen_Version_h
 #define CepGen_Version_h
 
 #include <string>
 
 namespace CepGen
 {
+  /// CepGen version
+  /// \note Format: 0xMMmmff, with
+  ///  - MM = major version
+  ///  - mm = minor version
+  ///  - ff = feature(s) release
   const unsigned int cepgen_version = 0x000900;
-
+  /// Human-readable version number
   const std::string version();
 }
 
 #endif
diff --git a/External/Processes/CepGenWrapper.cpp b/External/Processes/CepGenWrapper.cpp
new file mode 100644
index 0000000..023699c
--- /dev/null
+++ b/External/Processes/CepGenWrapper.cpp
@@ -0,0 +1,32 @@
+#include "CepGen/Processes/FortranProcesses.h"
+#include "CepGen/Core/Exception.h"
+
+//=================================================================================================
+// START BY LISTING ALL FORTRAN SUBROUTINES                                                      //
+// usage:                                                                                        //
+//  DECLARE_FORTRAN_SUBROUTINE( subroutine_name )                                                //
+// with the Fortran subroutine name written in lowercase (no trailing '_' necessary)             //
+//=================================================================================================
+
+DECLARE_FORTRAN_SUBROUTINE( nucl_to_ff )
+
+//=================================================================================================
+BEGIN_FORTRAN_PROCESSES_ENUM                                                                     //
+// START THE MAPPING name -> Fortran SUBROUTINE                                                  //
+// usage:                                                                                        //
+//  REGISTER_FORTRAN_PROCESS( "name", subroutine_name, "description )                            //
+//=================================================================================================
+
+REGISTER_FORTRAN_PROCESS( "patoff", nucl_to_ff, "pA ↝ (g/ɣ)ɣ → f⁺f¯" )
+REGISTER_FORTRAN_PROCESS( "aptoff", nucl_to_ff, "Ap ↝ ɣ(g/ɣ) → f⁺f¯" )
+REGISTER_FORTRAN_PROCESS( "aatoff", nucl_to_ff, "AA ↝ ɣɣ → f⁺f¯" )
+
+//=================================================================================================
+// DO NOT EDIT BELOW THIS LINE                                                                   //
+std::ostringstream os;                                                                           //
+for ( auto& proc : Process::FortranProcessesHandler::get().list() )                              //
+  os << "\n *) " << proc.name << ": " << proc.description;                                       //
+CG_DEBUG( "FortranProcesses" ) << "List of Fortran processes registered:" << os.str();           //
+END_FORTRAN_PROCESSES_ENUM                                                                       //
+//=================================================================================================
+
diff --git a/CepGen/Processes/Fortran/nucl_to_ff.f b/External/Processes/nucl_to_ff.f
similarity index 100%
rename from CepGen/Processes/Fortran/nucl_to_ff.f
rename to External/Processes/nucl_to_ff.f
diff --git a/cmake/UseEnvironment.cmake b/cmake/UseEnvironment.cmake
index 5fa5738..8432e25 100644
--- a/cmake/UseEnvironment.cmake
+++ b/cmake/UseEnvironment.cmake
@@ -1,104 +1,104 @@
 set(LXPLUS_GCC_ENV "source /afs/cern.ch/sw/lcg/external/gcc/4.9.3/x86_64-slc6-gcc49-opt/setup.sh")
 set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -Wall -cpp")
 if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic-errors -g")
 else()
   if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.7)
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic-errors -g")
   else()
     message(STATUS "gcc version >= 4.7 is required")
     if($ENV{HOSTNAME} MATCHES "^lxplus[0-9]+.cern.ch")
       message(STATUS "Compiling on LXPLUS. Did you properly source the environment variables?")
       message(STATUS "e.g. `${LXPLUS_GCC_ENV}`")
     endif()
     message(FATAL_ERROR "Please clean up this build environment and try again...")
   endif()
 endif()
 if(NOT CMAKE_VERSION VERSION_LESS 3.1)
   set(CMAKE_CXX_STANDARD 11)
 else()
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
 endif()
 
 if($ENV{HOSTNAME} MATCHES "^lxplus[0-9]+.cern.ch")
   set(BASE_DIR "/afs/cern.ch/sw/lcg/external")
   list(APPEND CMAKE_PREFIX_PATH "${BASE_DIR}/CMake/2.8.9/Linux-i386/share/cmake-2.8/Modules")
   set(GSL_DIR "${BASE_DIR}/GSL/2.2.1/x86_64-slc6-gcc48-opt")
   set(HEPMC_DIR "${BASE_DIR}/HepMC/2.06.08/x86_64-slc6-gcc48-opt")
   #set(LHAPDF_DIR "${BASE_DIR}/MCGenerators/lhapdf/5.8.9/x86_64-slc6-gcc46-opt")
   set(LHAPDF_DIR "${BASE_DIR}/MCGenerators_lcgcmt67c/lhapdf/6.2.0/x86_64-slc6-gcc48-opt")
   set(PYTHIA8_DIR "${BASE_DIR}/MCGenerators_lcgcmt67c/pythia8/230/x86_64-slc6-gcc48-opt")
   set(PYTHON_DIR "${BASE_DIR}/Python/2.7.4/x86_64-slc6-gcc48-opt")
   set(PYTHON_LIBRARY "${PYTHON_DIR}/lib/libpython2.7.so")
   set(PYTHON_EXECUTABLE "${PYTHON_DIR}/bin/python")
   set(PYTHON_INCLUDE_DIR "${PYTHON_DIR}/include/python2.7")
   set(ROOTSYS "${BASE_DIR}/../app/releases/ROOT/6.06.08/x86_64-slc6-gcc49-opt/root")
 
   message(STATUS "Compiling on LXPLUS. Do not forget to source the environment variables!")
   message(STATUS "e.g. `${LXPLUS_GCC_ENV}`")
   #--- searching for GSL
   find_library(GSL_LIB gsl HINTS ${GSL_DIR} PATH_SUFFIXES lib REQUIRED)
   find_library(GSL_CBLAS_LIB gslcblas HINTS ${GSL_DIR} PATH_SUFFIXES lib)
   #--- searching for LHAPDF
   find_library(LHAPDF LHAPDF HINTS ${LHAPDF_DIR} PATH_SUFFIXES lib)
   find_path(LHAPDF_INCLUDE LHAPDF HINTS ${LHAPDF_DIR} PATH_SUFFIXES include)
   #--- searching for HepMC
   find_library(HEPMC_LIB HepMC HINTS ${HEPMC_DIR} PATH_SUFFIXES lib)
   find_path(HEPMC_INCLUDE HepMC HINTS ${HEPMC_DIR} PATH_SUFFIXES include)
 else()
   find_library(GSL_LIB gsl REQUIRED)
   find_library(GSL_CBLAS_LIB gslcblas)
   find_library(LHAPDF LHAPDF)
   find_path(LHAPDF_INCLUDE LHAPDF)
   find_library(HEPMC_LIB HepMC)
   find_path(HEPMC_INCLUDE HepMC)
 endif()
 #--- searching for Pythia 8
 set(PYTHIA8_DIRS $ENV{PYTHIA8_DIR} ${PYTHIA8_DIR} /usr /usr/local /opt/pythia8)
 find_library(PYTHIA8 pythia8 HINTS ${PYTHIA8_DIRS} PATH_SUFFIXES lib)
 find_path(PYTHIA8_INCLUDE Pythia8 HINTS ${PYTHIA8_DIRS} PATH_SUFFIXES include include/Pythia8 include/pythia8)
 
 message(STATUS "GSL found in ${GSL_LIB}")
 list(APPEND CEPGEN_EXTERNAL_IO_REQS ${GSL_LIB} ${GSL_CBLAS_LIB})
 list(APPEND CEPGEN_EXTERNAL_CORE_REQS ${GSL_LIB} ${GSL_CBLAS_LIB})
 list(APPEND CEPGEN_EXTERNAL_STRF_REQS ${GSL_LIB} ${GSL_CBLAS_LIB})
 
 if(LHAPDF)
   message(STATUS "LHAPDF found in ${LHAPDF}")
   list(APPEND CEPGEN_EXTERNAL_STRF_REQS ${LHAPDF})
   add_definitions(-DLIBLHAPDF)
   include_directories(${LHAPDF_INCLUDE})
 endif()
 find_package(PythonLibs 2.7)
 if(PYTHONLIBS_FOUND)
   list(APPEND CEPGEN_EXTERNAL_CARDS_REQS ${PYTHON_LIBRARIES})
   add_definitions(-DPYTHON)
   message(STATUS "Python v${PYTHONLIBS_VERSION_STRING} found")
   include_directories(${PYTHON_INCLUDE_DIRS})
 endif()
 if(PYTHIA8)
-  message(STATUS "Pythia8 found in ${PYTHIA8}")
+  message(STATUS "Pythia 8 found in ${PYTHIA8}")
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-misleading-indentation")
   list(APPEND CEPGEN_EXTERNAL_HADR_REQS ${PYTHIA8} dl)
   add_definitions(-DPYTHIA8)
   include_directories(${PYTHIA8_INCLUDE})
 endif()
 if(HEPMC_LIB)
   message(STATUS "HepMC found in ${HEPMC_INCLUDE}")
   list(APPEND CEPGEN_EXTERNAL_IO_REQS ${HEPMC_LIB})
   add_definitions(-DLIBHEPMC)
   include_directories(${HEPMC_INCLUDE})
 endif()
 find_library(MUPARSER muparser)
 if(MUPARSER)
   message(STATUS "muParser found in ${MUPARSER}")
   list(APPEND CEPGEN_EXTERNAL_CORE_REQS ${MUPARSER})
   add_definitions(-DMUPARSER)
 endif()
 set(ALPHAS_PATH ${PROJECT_SOURCE_DIR}/External)
 file(GLOB alphas_sources ${ALPHAS_PATH}/alphaS.f)
 if(alphas_sources)
   message(STATUS "alpha(s) evolution found in ${alphas_sources}")
   add_definitions(-DALPHA_S)
 endif()
 
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 0000000..cd0ecdb
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,2 @@
+html*
+latex/*
diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in
new file mode 100644
index 0000000..3bae15f
--- /dev/null
+++ b/docs/Doxyfile.in
@@ -0,0 +1,2483 @@
+# Doxyfile 1.8.14
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = "CepGen"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         = 0.9
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "A generic central exclusive processes event generator"
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO           = @CMAKE_CURRENT_SOURCE_DIR@/docs/small-cepgen-logo.png
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = @CMAKE_CURRENT_SOURCE_DIR@/docs/
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES    = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES        = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines (in the resulting output). You can put ^^ in the value part of an
+# alias to insert a newline as if a physical newline was in the original file.
+
+ALIASES                =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST              =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C  = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT       = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 0.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS   = 0
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC         = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO, these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES, upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC  = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = YES
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES        = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES         = @CMAKE_CURRENT_SOURCE_DIR@/docs/bibliography.bib
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET                  = YES
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS               = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC       = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR          = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT                  = @CMAKE_CURRENT_SOURCE_DIR@
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
+# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
+
+FILE_PATTERNS          = *.c \
+                         *.cc \
+                         *.cxx \
+                         *.cpp \
+                         *.c++ \
+                         *.java \
+                         *.ii \
+                         *.ixx \
+                         *.ipp \
+                         *.i++ \
+                         *.inl \
+                         *.idl \
+                         *.ddl \
+                         *.odl \
+                         *.h \
+                         *.hh \
+                         *.hxx \
+                         *.hpp \
+                         *.h++ \
+                         *.cs \
+                         *.d \
+                         *.php \
+                         *.php4 \
+                         *.php5 \
+                         *.phtml \
+                         *.inc \
+                         *.m \
+                         *.markdown \
+                         *.md \
+                         *.mm \
+                         *.dox \
+                         *.py \
+                         *.pyw \
+                         *.f90 \
+                         *.f95 \
+                         *.f03 \
+                         *.f08 \
+                         *.f \
+                         *.for \
+                         *.tcl \
+                         *.vhd \
+                         *.vhdl \
+                         *.ucf \
+                         *.qsf
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                = @CMAKE_CURRENT_BINARY_DIR@
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS       = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER         = YES
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS        = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see https://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX     = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET  =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP         = NO
+
+# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
+# documentation will contain a main index with vertical navigation menus that
+# are dynamically created via Javascript. If disabled, the navigation index will
+# consists of multiple levels of tabs that are statically embedded in every HTML
+# page. Disable this option to support browsers that do not have Javascript,
+# like the Qt help browser.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_MENUS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS  = YES
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: https://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See https://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET        = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP      = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE               =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION           =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the master .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI           = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING     =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://doc.qt.io/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://doc.qt.io/qt-4.8/qthelpproject.html#virtual-folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://doc.qt.io/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW      = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH         = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# https://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX            = YES
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT         = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from https://www.mathjax.org before deployment.
+# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH        = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS     =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE       =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH    = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: https://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH        = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: https://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL       =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE        = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID     =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX         = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER           =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES      =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE        = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES     = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE        = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP        = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE    =
+
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE        = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION          = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR             =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT             = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK       = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT         = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
+# the structure of the code including all documentation. Note that this feature
+# is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED             = LIBLHAPDF=1 PYTHON=1 \
+                         PYTHIA8=1
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS        = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES         = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH               =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT               = YES
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS        = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS     = YES
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH          = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT       = svg
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG        = YES
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS           =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS           =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH      =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE      =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH  =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP            = YES
diff --git a/docs/bibliography.bib b/docs/bibliography.bib
new file mode 100644
index 0000000..616ab5d
--- /dev/null
+++ b/docs/bibliography.bib
@@ -0,0 +1,276 @@
+@article{Lepage:1977sw,
+      author         = "Lepage, G. Peter",
+      title          = "{A New Algorithm for Adaptive Multidimensional
+                        Integration}",
+      journal        = "J. Comput. Phys.",
+      volume         = "27",
+      year           = "1978",
+      pages          = "192",
+      doi            = "10.1016/0021-9991(78)90004-9",
+      reportNumber   = "SLAC-PUB-1839-REV, SLAC-PUB-1839",
+      SLACcitation   = "%%CITATION = JCTPA,27,192;%%"
+}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Physics processes definitions
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+@inproceedings{Baranov:1991yq,
+      author         = "Baranov, S. P. and Duenger, O. and Shooshtari, H. and
+                        Vermaseren, J. A. M.",
+      title          = "{LPAIR: A generator for lepton pair production}",
+      booktitle      = "{Workshop on Physics at HERA Hamburg, Germany, October
+                        29-30, 1991}",
+      year           = "1991",
+      pages          = "1478-1482",
+      SLACcitation   = "%%CITATION = INSPIRE-326934;%%"
+}
+
+@article{Luszczak:2018ntp,
+      author         = "{\L{}}uszczak, Marta and Sch{\"{a}}fer, Wolfgang and Szczurek,
+                        Antoni",
+      title          = "{Production of $W^+ W^-$ pairs via $\gamma^*\gamma^* \to
+                        W^+ W^-$ subprocess with photon transverse momenta}",
+      journal        = "JHEP",
+      volume         = "05",
+      year           = "2018",
+      pages          = "064",
+      doi            = "10.1007/JHEP05(2018)064",
+      eprint         = "1802.03244",
+      archivePrefix  = "arXiv",
+      primaryClass   = "hep-ph",
+      SLACcitation   = "%%CITATION = ARXIV:1802.03244;%%"
+}
+
+@article{Vermaseren:1982cz,
+      author         = "Vermaseren, J. A. M.",
+      title          = "{Two Photon Processes at Very High-Energies}",
+      journal        = "Nucl. Phys.",
+      volume         = "B229",
+      year           = "1983",
+      pages          = "347-371",
+      doi            = "10.1016/0550-3213(83)90336-X",
+      reportNumber   = "NIKHEF-H/82-15",
+      SLACcitation   = "%%CITATION = NUPHA,B229,347;%%"
+}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Structure functions definitions
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+@article{Abramowicz:1991xz,
+      author         = "Abramowicz, H. and Levin, E. M. and Levy, A. and Maor,
+                        U.",
+      title          = "{A Parametrization of $\sigma_T(\gamma^* p)$ above the
+                        resonance region $Q^2 \gtrapprox 0$}",
+      journal        = "Phys. Lett. B",
+      volume         = "269",
+      year           = "1991",
+      pages          = "465-476",
+      doi            = "10.1016/0370-2693(91)90202-2",
+      reportNumber   = "DESY-91-068",
+      SLACcitation   = "%%CITATION = PHLTA,B269,465;%%"
+}
+
+@article{Abramowicz:1997ms,
+      author         = "Abramowicz, H. and Levy, A.",
+      title          = "{The ALLM parameterization of $\sigma_{\rm tot}(\gamma^* p)$: An
+                        Update}",
+      year           = "1997",
+      eprint         = "hep-ph/9712415",
+      archivePrefix  = "arXiv",
+      primaryClass   = "hep-ph",
+      reportNumber   = "DESY-97-251",
+      SLACcitation   = "%%CITATION = HEP-PH/9712415;%%"
+}
+
+@article{Block:2014kza,
+      author         = "Block, Martin M. and Durand, Loyal and Ha, Phuoc",
+      title          = "{Connection of the virtual $\gamma^*p$ cross section of
+                        ep deep inelastic scattering to real $\gamma p$ scattering, and
+                        the implications for $\nu N$ and $ep$ total cross sections}",
+      journal        = "Phys. Rev.",
+      volume         = "D89",
+      year           = "2014",
+      number         = "9",
+      pages          = "094027",
+      doi            = "10.1103/PhysRevD.89.094027",
+      eprint         = "1404.4530",
+      archivePrefix  = "arXiv",
+      primaryClass   = "hep-ph",
+      SLACcitation   = "%%CITATION = ARXIV:1404.4530;%%"
+}
+
+@article{Bosted:2007xd,
+      author         = "Bosted, P. E. and Christy, M. E.",
+      title          = "{Empirical fit to inelastic electron-deuteron and
+                        electron-neutron resonance region transverse
+                        cross-sections}",
+      journal        = "Phys. Rev. C",
+      volume         = "77",
+      year           = "2008",
+      pages          = "065206",
+      doi            = "10.1103/PhysRevC.77.065206",
+      eprint         = "0711.0159",
+      archivePrefix  = "arXiv",
+      primaryClass   = "hep-ph",
+      reportNumber   = "JLAB-PHY-07-744",
+      SLACcitation   = "%%CITATION = ARXIV:0711.0159;%%"
+}
+
+@article{Brasse:1976bf,
+      author         = "Brasse, F. W. and Flauger, W. and Gayler, Joerg and Goel,
+                        S. P. and Haidan, R. and Merkwitz, M. and Wriedt, H.",
+      title          = "{Parametrization of the $q^2$ dependence of $\gamma_V p$
+                        total cross sections in the resonance region}",
+      journal        = "Nucl. Phys.",
+      volume         = "B110",
+      year           = "1976",
+      pages          = "413-433",
+      doi            = "10.1016/0550-3213(76)90231-5",
+      reportNumber   = "DESY-76-11",
+      SLACcitation   = "%%CITATION = NUPHA,B110,413;%%"
+}
+
+@article{Fiore:2002re,
+      author         = "Fiore, R. and Flachi, A. and Jenkovszky, Laszlo L. and
+                        Lengyel, A. I. and Magas, V. K.",
+      title          = "{Explicit model realizing parton hadron duality}",
+      journal        = "Eur. Phys. J. A",
+      volume         = "15",
+      year           = "2002",
+      pages          = "505-515",
+      doi            = "10.1140/epja/i2002-10047-3",
+      eprint         = "hep-ph/0206027",
+      archivePrefix  = "arXiv",
+      primaryClass   = "hep-ph",
+      reportNumber   = "UNICAL-TH-06-4, UAB-FT-529",
+      SLACcitation   = "%%CITATION = HEP-PH/0206027;%%"
+}
+
+@article{Osipenko:2003bu,
+      author         = "Osipenko, M. and others",
+      title          = "{A Kinematically complete measurement of the proton
+                        structure function F(2) in the resonance region and
+                        evaluation of its moments}",
+      collaboration  = "CLAS",
+      journal        = "Phys. Rev.",
+      volume         = "D67",
+      year           = "2003",
+      pages          = "092001",
+      doi            = "10.1103/PhysRevD.67.092001",
+      eprint         = "hep-ph/0301204",
+      archivePrefix  = "arXiv",
+      primaryClass   = "hep-ph",
+      reportNumber   = "JLAB-PHY-03-46",
+      SLACcitation   = "%%CITATION = HEP-PH/0301204;%%"
+}
+
+@article{Ricco:1998yr,
+      author         = "Ricco, G. and Simula, S. and Battaglieri, M.",
+      title          = "{Power corrections in the longitudinal and transverse
+                        structure functions of proton and deuteron}",
+      journal        = "Nucl. Phys.",
+      volume         = "B555",
+      year           = "1999",
+      pages          = "306-334",
+      doi            = "10.1016/S0550-3213(99)00302-8",
+      eprint         = "hep-ph/9901360",
+      archivePrefix  = "arXiv",
+      primaryClass   = "hep-ph",
+      reportNumber   = "INFN-RM3-98-2",
+      SLACcitation   = "%%CITATION = HEP-PH/9901360;%%"
+}
+
+@article{Suri:1971yx,
+      author         = "Suri, Ashok and Yennie, Donald R.",
+      title          = "{THE SPACE-TIME PHENOMENOLOGY OF PHOTON ABSORPTION AND
+                        INELASTIC ELECTRON SCATTERING}",
+      journal        = "Annals Phys.",
+      volume         = "72",
+      year           = "1972",
+      pages          = "243",
+      doi            = "10.1016/0003-4916(72)90242-4",
+      reportNumber   = "SLAC-PUB-0954",
+      SLACcitation   = "%%CITATION = APNYA,72,243;%%"
+}
+
+@article{Szczurek:1999wp,
+      author         = "Szczurek, A. and Uleshchenko, V.",
+      title          = "{On the range of validity of the QCD improved parton
+                        model}",
+      journal        = "Phys. Lett.",
+      volume         = "B475",
+      year           = "2000",
+      pages          = "120-126",
+      doi            = "10.1016/S0370-2693(00)00066-6",
+      eprint         = "hep-ph/9911467",
+      archivePrefix  = "arXiv",
+      primaryClass   = "hep-ph",
+      SLACcitation   = "%%CITATION = HEP-PH/9911467;%%"
+}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% R modellings definitions
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+@article{Abe:1998ym,
+      author         = "Abe, K. and others",
+      title          = "{Measurements of R = $\sigma$(L) / $\sigma$(T) for 0.03 <
+                        x < 0.1 and fit to world data}",
+      collaboration  = "E143",
+      journal        = "Phys. Lett.",
+      volume         = "B452",
+      year           = "1999",
+      pages          = "194-200",
+      doi            = "10.1016/S0370-2693(99)00244-0",
+      eprint         = "hep-ex/9808028",
+      archivePrefix  = "arXiv",
+      primaryClass   = "hep-ex",
+      reportNumber   = "SLAC-PUB-7927",
+      SLACcitation   = "%%CITATION = HEP-EX/9808028;%%"
+}
+
+@article{Whitlow:1990gk,
+      author         = "Whitlow, L. W. and Rock, Stephen and Bodek, A. and
+                        Riordan, E. M. and Dasu, S.",
+      title          = "{A Precise extraction of R = sigma-L / sigma-T from a
+                        global analysis of the SLAC deep inelastic e p and e d
+                        scattering cross-sections}",
+      journal        = "Phys. Lett.",
+      volume         = "B250",
+      year           = "1990",
+      pages          = "193-198",
+      doi            = "10.1016/0370-2693(90)91176-C",
+      reportNumber   = "SLAC-PUB-5284, UR-1102",
+      SLACcitation   = "%%CITATION = PHLTA,B250,193;%%"
+}
+
+@article{Sibirtsev:2013cga,
+      author         = "Sibirtsev, A. and Blunden, P. G.",
+      title          = "{$Q^2$ evolution of the electric and magnetic
+                        polarizabilities of the proton}",
+      journal        = "Phys. Rev.",
+      volume         = "C88",
+      year           = "2013",
+      number         = "6",
+      pages          = "065202",
+      doi            = "10.1103/PhysRevC.88.065202",
+      eprint         = "1311.6482",
+      archivePrefix  = "arXiv",
+      primaryClass   = "nucl-th",
+      SLACcitation   = "%%CITATION = ARXIV:1311.6482;%%"
+}
+
+@article{Beringer:1900zz,
+      author         = "Beringer, J. and others",
+      title          = "{Review of Particle Physics (RPP)}",
+      collaboration  = "Particle Data Group",
+      journal        = "Phys. Rev.",
+      volume         = "D86",
+      year           = "2012",
+      pages          = "010001",
+      doi            = "10.1103/PhysRevD.86.010001",
+      reportNumber   = "SLAC-REPRINT-2014-001",
+      SLACcitation   = "%%CITATION = PHRVA,D86,010001;%%"
+}
diff --git a/docs/small-cepgen-logo.png b/docs/small-cepgen-logo.png
new file mode 100644
index 0000000..a34c8e6
Binary files /dev/null and b/docs/small-cepgen-logo.png differ
diff --git a/CepGen/Processes/TestProcess.h b/test/TestProcess.h
similarity index 93%
rename from CepGen/Processes/TestProcess.h
rename to test/TestProcess.h
index 24c758f..f846601 100644
--- a/CepGen/Processes/TestProcess.h
+++ b/test/TestProcess.h
@@ -1,44 +1,44 @@
 #ifndef CepGen_Processes_TestProcess_h
 #define CepGen_Processes_TestProcess_h
 
 #include "CepGen/Processes/GenericProcess.h"
 #include "CepGen/Core/Functional.h"
 
 namespace CepGen
 {
   namespace Process
   {
     /// Generic process to test the Vegas instance
     template<size_t N>
     class TestProcess : public GenericProcess
     {
       public:
         TestProcess() :
           GenericProcess( "test", ".oO TEST PROCESS Oo.", false ),
           funct_( "1./(1.-cos(x*_pi)*cos(y*_pi)*cos(z*_pi))", { { "x", "y", "z" } } ) {}
         TestProcess( const char* formula, std::array<std::string,N> args ) :
           GenericProcess( "test", Form( ".oO TEST PROCESS (%s) Oo.", formula ), false ),
           funct_( formula, args ) {}
 
         ProcessPtr clone() const override { return ProcessPtr( new TestProcess<N>( *this ) ); }
 
         void addEventContent() override {}
         /// Number of dimensions on which to perform the integration
-        unsigned int numDimensions( const Kinematics::Mode& ) const override { return N; }
+        unsigned int numDimensions() const override { return N; }
         /// Generic formula to compute a weight out of a point in the phase space
         double computeWeight() override {
           std::array<double,N> args;
           std::copy_n( x_.begin(), N, args.begin() );
           return funct_.eval( args );
         }
         /// Dummy function to be called on events generation
         void fillKinematics( bool ) override { return; }
 
       private:
         Functional<N> funct_;
     };
   }
 }
 
 #endif
 
diff --git a/test/TreeInfo.h b/test/TreeInfo.h
index 2b83775..060c9fe 100644
--- a/test/TreeInfo.h
+++ b/test/TreeInfo.h
@@ -1,154 +1,181 @@
 #ifndef Test_TreeInfo_h
 #define Test_TreeInfo_h
 
 #include "TFile.h"
 #include "TTree.h"
 #include "Math/Vector3D.h"
 #include "Math/Vector4D.h"
 
 #include <string>
 
 namespace CepGen
 {
+  /// All useful information about a generation run
   struct TreeRun
   {
-    double sqrt_s;
-    double xsect, errxsect;
-    unsigned int num_events, litigious_events;
+    double sqrt_s; ///< Centre of mass energy for beam particles
+    double xsect; ///< Process cross section, in pb
+    double errxsect; ///< Uncertainty on process cross section, in pb
+    unsigned int num_events; ///< Number of events generated in run
+    unsigned int litigious_events; ///< Number of litigious events in run
+    /// ROOT tree used for storage/retrieval of this run information
     TTree* tree;
 
     TreeRun() : tree( NULL ) { clear(); }
+    /// Reinitialise the run tree
     void clear() {
       sqrt_s = -1.;
       xsect = errxsect = -1.;
       num_events = litigious_events = 0;
     }
+    /// Populate the run tree
     void create() {
       tree = new TTree( "run", "a tree containing information on the previous run" );
       if ( !tree ) return;
       tree->Branch( "xsect", &xsect, "xsect/D" );
       tree->Branch( "errxsect", &errxsect, "errxsect/D" );
       tree->Branch( "num_events", &num_events, "num_events/i" );
       tree->Branch( "litigious_events", &litigious_events, "litigious_events/i" );
       tree->Branch( "sqrt_s", &sqrt_s, "sqrt_s/D" );
     }
+    /// Fill the run tree
     void fill() {
       tree->Fill();
     }
+    /// Attach the run tree reader to a given file
     void attach( const char* filename, const char* run_tree = "run" ) {
       attach( TFile::Open( filename ), run_tree );
     }
+    /// Attach the run tree reader to a given tree
     void attach( TFile* file, const char* run_tree = "run" ) {
       tree = dynamic_cast<TTree*>( file->Get( run_tree ) );
       if ( !tree ) return;
       tree->SetBranchAddress( "xsect", &xsect );
       tree->SetBranchAddress( "errxsect", &errxsect );
       tree->SetBranchAddress( "num_events", &num_events );
       tree->SetBranchAddress( "litigious_events", &litigious_events );
       tree->SetBranchAddress( "sqrt_s", &sqrt_s );
       if ( tree->GetEntriesFast() > 1 )
         std::cerr << "The run tree has more than one entry." << std::endl;
       tree->GetEntry( 0 );
     }
   };
 
+  /// All useful information about a generated event
   struct TreeEvent
   {
     // book a sufficienly large number to allow the large multiplicity
     // of excited proton fragmentation products
-    static const unsigned short maxpart = 5000;
-
+    static const unsigned short maxpart = 5000; ///< Maximal number of particles in event
+    /// Tree for which the event is booked
     TTree* tree;
+    /// A pointer to the file opened for storage/retrieval
     std::unique_ptr<TFile> file;
 
-    float gen_time, tot_time;
-    int nremn_ch[2], nremn_nt[2], np;
+    float gen_time; ///< Event generation time
+    float tot_time; ///< Total event generation time
+    int nremn_ch[2], nremn_nt[2];
+    int np; ///< Number of particles in the event
     std::vector<ROOT::Math::XYZTVector> momentum, *pMom;
-    double pt[maxpart], eta[maxpart], phi[maxpart], rapidity[maxpart];
-    double E[maxpart], m[maxpart], charge[maxpart];
-    int pdg_id[maxpart], parent1[maxpart], parent2[maxpart];
-    int stable[maxpart], role[maxpart], status[maxpart];
+    double pt[maxpart]; ///< Particles transverse momentum
+    double eta[maxpart]; ///< Particles pseudo-rapidity
+    double phi[maxpart]; ///< Particles azimutal angle
+    double rapidity[maxpart]; ///< Particles rapidity
+    double E[maxpart]; ///< Particles energy, in GeV
+    double m[maxpart]; ///< Particles mass, in GeV/c\f${}^2\f$
+    double charge[maxpart]; ///< Particles charges, in e
+    int pdg_id[maxpart]; ///< Integer particles PDG id
+    int parent1[maxpart]; ///< First particles mother
+    int parent2[maxpart]; ///< Last particles mother
+    int stable[maxpart]; ///< Whether the particle must decay or not
+    int role[maxpart]; ///< Particles role in the event
+    int status[maxpart]; ///< Integer status code
 
     TreeEvent() : tree( nullptr ), pMom( nullptr ) {
       clear();
     }
-
+    /// Reinitialise the event content
     void clear() {
       gen_time = tot_time = 0.;
       for ( unsigned short i = 0; i < 2; ++i )
         nremn_ch[i] = nremn_nt[i] = 0;
       np = 0;
       momentum.clear();
       for ( unsigned short i = 0; i < maxpart; ++i ) {
         pt[i] = eta[i] = phi[i] = rapidity[i] = E[i] = m[i] = charge[i] = 0.;
         pdg_id[i] = parent1[i] = parent2[i] = stable[i] = role[i] = status[i] = 0;
       }
     }
+    /// Fill the tree with a new event
     void fill() {
       if ( !tree )
         throw std::runtime_error( "TreeEvent: Trying to fill a non-existent tree!" );
 
       tree->Fill();
       clear();
     }
+    /// Populate the tree and all associated branches
     void create( TTree* t ) {
       tree = t;
       if ( !tree ) return;
       tree->Branch( "npart", &np, "npart/I" );
       tree->Branch( "nremn_charged", nremn_ch, "nremn_charged[2]/I" );
       tree->Branch( "nremn_neutral", nremn_nt, "nremn_neutral[2]/I" );
       tree->Branch( "role", role, "role[npart]/I" );
       pMom = &momentum;
       tree->Branch( "momentum", "std::vector<ROOT::Math::LorentzVector<ROOT::Math::PxPyPzE4D<double> > >", &pMom );
       tree->Branch( "pt", pt, "pt[npart]/D" );
       tree->Branch( "eta", eta, "eta[npart]/D" );
       tree->Branch( "phi", phi, "phi[npart]/D" );
       tree->Branch( "rapidity", rapidity, "rapidity[npart]/D" );
       tree->Branch( "E", E, "E[npart]/D" );
       tree->Branch( "m", m, "m[npart]/D" );
       tree->Branch( "charge", charge, "charge[npart]/D" );
       tree->Branch( "pdg_id", pdg_id, "pdg_id[npart]/I" );
       tree->Branch( "parent1", parent1, "parent1[npart]/I" );
       tree->Branch( "parent2", parent2, "parent2[npart]/I" );
       tree->Branch( "stable", stable, "stable[npart]/I" );
       tree->Branch( "status", status, "status[npart]/I" );
       tree->Branch( "generation_time", &gen_time, "generation_time/F" );
       tree->Branch( "total_time", &tot_time, "total_time/F" );
     }
+    /// Attach the event tree reader to a given file
     void attach( const char* filename, const char* events_tree = "events" ) {
       file.reset( TFile::Open( filename ) );
       attach( file.get(), events_tree );
     }
+    /// Attach the event tree reader to a given ROOT file
     void attach( TFile* f, const char* events_tree = "events" ) {
       tree = dynamic_cast<TTree*>( f->Get( events_tree ) );
       attach( tree );
     }
+    /// Attach the event tree reader to a given tree
     void attach( TTree* t ) {
       tree = t;
       if ( !tree ) return;
       tree->SetBranchAddress( "npart", &np );
       tree->SetBranchAddress( "nremn_charged", nremn_ch );
       tree->SetBranchAddress( "nremn_neutral", nremn_ch );
       tree->SetBranchAddress( "role", role );
       tree->SetBranchAddress( "momentum", &pMom );
       tree->SetBranchAddress( "pt", pt );
       tree->SetBranchAddress( "eta", eta );
       tree->SetBranchAddress( "phi", phi );
       tree->SetBranchAddress( "rapidity", rapidity );
       tree->SetBranchAddress( "E", E );
       tree->SetBranchAddress( "m", m );
       tree->SetBranchAddress( "charge", charge );
       tree->SetBranchAddress( "pdg_id", pdg_id );
       tree->SetBranchAddress( "parent1", parent1 );
       tree->SetBranchAddress( "parent2", parent2 );
       tree->SetBranchAddress( "stable", stable );
       tree->SetBranchAddress( "status", status );
       tree->SetBranchAddress( "generation_time", &gen_time );
       tree->SetBranchAddress( "total_time", &tot_time );
     }
   };
 }
 
 #endif
 
 
diff --git a/test/abort.h b/test/abort.h
index 9f53a4d..5270b9e 100644
--- a/test/abort.h
+++ b/test/abort.h
@@ -1,52 +1,55 @@
 #ifndef CepGen_Tests_abort_h
 #define CepGen_Tests_abort_h
 
 #include "CepGen/Core/Exception.h"
 #include "CepGen/Core/utils.h"
 #include <csignal>
 
 namespace CepGen
 {
   extern volatile int gSignal;
+  /// Exception raised when the user terminates the process
   struct RunAbortedException : Exception
   {
     using Exception::Exception;
     ~RunAbortedException() override {}
   };
 }
 
+/// Object handling an user-driven process abortion
 class AbortHandler
 {
   public:
     AbortHandler( int flags = SA_SIGINFO ) {
       action_.sa_sigaction = handle_ctrl_c;
       sigemptyset( &action_.sa_mask );
       action_.sa_flags = flags;
       init();
     }
+    /// Switch on/off multithreading capabilities
     void setMT( bool mt_on = true ) {
       if ( mt_on )
         action_.sa_sigaction = handle_ctrl_c_mt;
       else
         action_.sa_sigaction = handle_ctrl_c;
       init();
     }
 
   private:
     static void handle_ctrl_c_mt( int signal, siginfo_t*, void* ) {
       CepGen::gSignal = signal;
     }
     static void handle_ctrl_c( int signal, siginfo_t*, void* ) {
       CepGen::gSignal = signal;
       throw CepGen::RunAbortedException( __PRETTY_FUNCTION__, CepGen::Exception::Type::info )
         << "Run aborted.";
     }
     void init() {
       if ( sigaction( SIGINT, &action_, nullptr ) != 0
         || sigaction( SIGTERM, &action_, nullptr ) != 0 )
         throw CG_FATAL( "AbortHandler" ) << "Failed to initialise the C-c handler!";
     }
     struct sigaction action_;
 };
 
 #endif
diff --git a/test/test_integrator.cpp b/test/test_integrator.cpp
index 419dc6b..ba5f523 100644
--- a/test/test_integrator.cpp
+++ b/test/test_integrator.cpp
@@ -1,53 +1,53 @@
 #include "CepGen/Generator.h"
 #include "CepGen/Parameters.h"
-#include "CepGen/Processes/TestProcess.h"
+#include "TestProcess.h"
 
 #include <iostream>
 
 using namespace std;
 
 int
 main( int argc, char* argv[] )
 {
   if ( argc < 3 || string( argv[2] ) != "debug" )
     CepGen::Logger::get().level = CepGen::Logger::Level::nothing;
 
   const double max_sigma = 3.0;
 
   CepGen::Generator mg;
   if ( argc > 1 && string( argv[1] ) == "plain" )
     mg.parameters->integrator.type = CepGen::Integrator::Type::plain;
   if ( argc > 1 && string( argv[1] ) == "vegas" )
     mg.parameters->integrator.type = CepGen::Integrator::Type::Vegas;
   if ( argc > 1 && string( argv[1] ) == "miser" )
     mg.parameters->integrator.type = CepGen::Integrator::Type::MISER;
 
   double result, error;
 
   { // test 1
     const double exact = 1.3932039296856768591842462603255;
     mg.parameters->setProcess( new CepGen::Process::TestProcess<3> );
     mg.computeXsection( result, error );
     if ( fabs( exact - result ) > max_sigma * error )
       throw CG_FATAL( "main" ) << "pull = " << fabs( exact-result )/error << ".";
     cout << "Test 1 passed!" << endl;
   }
   { // test 2
     const double exact = 2./3.;
     mg.parameters->setProcess( new CepGen::Process::TestProcess<2>( "x^2+y^2", { { "x", "y" } } ) );
     mg.computeXsection( result, error );
     if ( fabs( exact - result ) > max_sigma * error )
       throw CG_FATAL( "main" ) << "pull = " << fabs( exact-result )/error << ".";
     cout << "Test 2 passed!" << endl;
   }
   { // test 3
     const double exact = 13./12.;
     mg.parameters->setProcess( new CepGen::Process::TestProcess<3>( "x+y^2+z^3", { { "x", "y", "z" } } ) );
     mg.computeXsection( result, error );
     if ( fabs( exact - result ) > max_sigma * error )
       throw CG_FATAL( "main" ) << "pull = " << fabs( exact-result )/error << ".";
     cout << "Test 3 passed!" << endl;
   }
 
   return 0;
 }
diff --git a/test/test_physics_processes.cpp b/test/test_physics_processes.cpp
index 3bbae77..bf7d458 100644
--- a/test/test_physics_processes.cpp
+++ b/test/test_physics_processes.cpp
@@ -1,211 +1,211 @@
 #include "CepGen/Generator.h"
 #include "CepGen/Parameters.h"
 #include "CepGen/Core/ParametersList.h"
 #include "CepGen/Core/Timer.h"
 
 #include "CepGen/Processes/GamGamLL.h"
 #include "CepGen/Processes/PPtoFF.h"
 #include "CepGen/Processes/PPtoWW.h"
 
 #include "CepGen/StructureFunctions/StructureFunctionsBuilder.h"
 #include "CepGen/StructureFunctions/StructureFunctions.h"
 #include "CepGen/Physics/PDG.h"
 
 #include "abort.h"
 
 #include <unordered_map>
 #include <assert.h>
 #include <string.h>
 #include <math.h>
 
 using namespace std;
 
 int
 main( int argc, char* argv[] )
 {
   typedef vector<pair<const char*,pair<double,double> > > KinematicsMap;
   typedef vector<pair<float, KinematicsMap> > ValuesAtCutMap;
 
   AbortHandler ctrl_c;
 
   // values defined at pt(single lepton)>15 GeV, |eta(single lepton)|<2.5, mX<1000 GeV
   // process -> { pt cut -> { kinematics -> ( sigma, delta(sigma) ) } }
   vector<pair<const char*,ValuesAtCutMap> > values_map = {
     //--- LPAIR values at sqrt(s) = 13 TeV
     { "lpair", {
       { 3.0, { // pt cut
         { "elastic",    { 2.0871703e1, 3.542e-2 } },
         { "singlediss", { 1.5042536e1, 3.256e-2 } },
         { "doublediss", { 1.38835e1, 4.03018e-2 } }
       } },
       { 15.0, { // pt cut
         { "elastic",    { 4.1994803e-1, 8.328e-4 } },
         { "singlediss", { 4.8504819e-1, 1.171e-3 } },
         { "doublediss", { 6.35650e-1, 1.93968e-3 } }
       } },
     } },
     //--- PPtoLL values
     { "pptoll", {
       { 3.0, { // pt cut
         { "elastic",       { 2.0936541e1, 1.4096e-2 } },
         { "singlediss_su", { 1.4844881e1, 2.0723e-2 } }, // SU, qt<50
         { "doublediss_su", { 1.3580637e1, 2.2497e-2 } }, // SU, qt<50
       } },
       { 15.0, { // pt cut
         { "elastic",       { 4.2515888e-1, 3.0351e-4 } },
         { "singlediss_su", { 4.4903253e-1, 5.8970e-4 } }, // SU, qt<50
         { "doublediss_su", { 5.1923819e-1, 9.6549e-4 } }, // SU, qt<50
         /*{ "2_singlediss", { 4.6710287e-1, 6.4842e-4 } }, // SU, qt<500
         { "3_doublediss", { 5.6316353e-1, 1.1829e-3 } }, // SU, qt<500*/
       } },
     } },
     //--- PPtoWW values
     { "pptoww", {
       { 0.0, { // pt cut
         { "elastic",         { 0.273, 0.01 } },
         { "elastic",         { 0.273, 0.01 } }, // FIXME
         { "singlediss_lux",  { 0.409, 0.01 } },
         { "doublediss_lux",  { 1.090, 0.01 } },
         { "singlediss_allm", { 0.318, 0.01 } },
         { "doublediss_allm", { 0.701, 0.01 } }
       } }
     } },
   };
 
   const double num_sigma = 3.0;
 
   if ( argc < 3 || strcmp( argv[2], "debug" ) != 0 )
     CepGen::Logger::get().level = CepGen::Logger::Level::nothing;
 
   CepGen::Timer tmr;
   CepGen::Generator mg;
 
   if ( argc > 1 && strcmp( argv[1], "plain" ) == 0 )
     mg.parameters->integrator.type = CepGen::Integrator::Type::plain;
   if ( argc > 1 && strcmp( argv[1], "vegas" ) == 0 )
     mg.parameters->integrator.type = CepGen::Integrator::Type::Vegas;
   if ( argc > 1 && strcmp( argv[1], "miser" ) == 0 )
     mg.parameters->integrator.type = CepGen::Integrator::Type::MISER;
 
   { cout << "Testing with " << mg.parameters->integrator.type << " integrator" << endl; }
 
   mg.parameters->kinematics.setSqrtS( 13.e3 );
   mg.parameters->kinematics.cuts.central.eta_single.in( -2.5, 2.5 );
   mg.parameters->kinematics.cuts.remnants.mass_single.max() = 1000.;
   //mg.parameters->integrator.ncvg = 50000;
 
   CG_INFO( "main" ) << "Initial configuration time: " << tmr.elapsed()*1.e3 << " ms.";
   tmr.reset();
 
   unsigned short num_tests = 0, num_tests_passed = 0;
   vector<string> failed_tests, passed_tests;
 
   try {
     for ( const auto& values_vs_generator : values_map ) { // loop over all generators
       CepGen::ParametersList param;
       const string generator = values_vs_generator.first;
       if ( generator == "lpair"  ) {
         param.set<int>( "pair", 13 );
         mg.parameters->setProcess( new CepGen::Process::GamGamLL( param ) );
       }
       else if ( generator == "pptoll" ) {
         param.set<int>( "pair", 13 );
         mg.parameters->setProcess( new CepGen::Process::PPtoFF( param ) );
         mg.parameters->kinematics.cuts.initial.qt = { 0., 50. };
       }
       else if ( generator == "pptoww" ) {
         mg.parameters->setProcess( new CepGen::Process::PPtoWW );
         mg.parameters->kinematics.setSqrtS( 13.e3 );
         //mg.parameters->kinematics.cuts.initial.qt = { 0., 50. };
       }
       else {
         CG_ERROR( "main" ) << "Unrecognized generator mode: " << values_vs_generator.first << ".";
         break;
       }
 
       for ( const auto& values_vs_cut : values_vs_generator.second ) { // loop over the single lepton pT cut
         mg.parameters->kinematics.cuts.central.pt_single.min() = values_vs_cut.first;
         for ( const auto& values_vs_kin : values_vs_cut.second ) { // loop over all possible kinematics
           const string kin_mode = values_vs_kin.first;
 
           if ( kin_mode.find( "elastic"    ) != string::npos )
-            mg.parameters->kinematics.mode = CepGen::Kinematics::Mode::ElasticElastic;
+            mg.parameters->kinematics.mode = CepGen::KinematicsMode::ElasticElastic;
           else if ( kin_mode.find( "singlediss" ) != string::npos )
-            mg.parameters->kinematics.mode = CepGen::Kinematics::Mode::InelasticElastic;
+            mg.parameters->kinematics.mode = CepGen::KinematicsMode::InelasticElastic;
           else if ( kin_mode.find( "doublediss" ) != string::npos )
-            mg.parameters->kinematics.mode = CepGen::Kinematics::Mode::InelasticInelastic;
+            mg.parameters->kinematics.mode = CepGen::KinematicsMode::InelasticInelastic;
           else {
             CG_ERROR( "main" ) << "Unrecognized kinematics mode: " << values_vs_kin.first << ".";
             break;
           }
 
           if ( kin_mode.find( "_su" ) != string::npos )
             mg.parameters->kinematics.structure_functions = CepGen::StructureFunctionsBuilder::get( CepGen::SF::Type::SzczurekUleshchenko );
           else if ( kin_mode.find( "_lux" ) != string::npos )
             mg.parameters->kinematics.structure_functions = CepGen::StructureFunctionsBuilder::get( CepGen::SF::Type::Schaefer );
           else if ( kin_mode.find( "_allm" ) != string::npos )
             mg.parameters->kinematics.structure_functions = CepGen::StructureFunctionsBuilder::get( CepGen::SF::Type::ALLM97 );
           else
             mg.parameters->kinematics.structure_functions = CepGen::StructureFunctionsBuilder::get( CepGen::SF::Type::SuriYennie );
 
           //CG_INFO( "main" ) << mg.parameters.get();
           CG_INFO( "main" )
             << "Process: "<< values_vs_generator.first << "/" << values_vs_kin.first << "\n\t"
             << "Configuration time: " << tmr.elapsed()*1.e3 << " ms.";
           tmr.reset();
 
           mg.clearRun();
           const double xsec_ref = values_vs_kin.second.first;
           const double err_xsec_ref = values_vs_kin.second.second;
           double xsec_cepgen, err_xsec_cepgen;
           mg.computeXsection( xsec_cepgen, err_xsec_cepgen );
 
           const double sigma = fabs( xsec_ref-xsec_cepgen ) / std::hypot( err_xsec_cepgen, err_xsec_ref );
 
           CG_INFO( "main" )
             << "Computed cross section:\n\t"
             << "Ref.   = " << xsec_ref << " +/- " << err_xsec_ref << "\n\t"
             << "CepGen = " << xsec_cepgen << " +/- " << err_xsec_cepgen << "\n\t"
             << "Pull: " << sigma << ".";
 
           CG_INFO( "main" ) << "Computation time: " << tmr.elapsed()*1.e3 << " ms.";
           tmr.reset();
 
           ostringstream oss; oss << values_vs_kin.first;
           string test_res = CepGen::Form( "%-10s", values_vs_generator.first )+"\t"+
                             CepGen::Form( "pt-gt-%.1f", values_vs_cut.first )+"\t"+
                             CepGen::Form( "%-16s", oss.str().c_str() )+"\t"
                             "ref="+CepGen::Form( "%g", xsec_ref )+"\t"
                             "got="+CepGen::Form( "%g", xsec_cepgen )+"\t"
                             "pull="+CepGen::Form( "%+g", sigma );
           if ( fabs( sigma ) < num_sigma ) {
             passed_tests.emplace_back( test_res );
             num_tests_passed++;
           }
           else
             failed_tests.emplace_back( test_res );
 
           num_tests++;
           cout << "Test " << num_tests_passed << "/"
                           << num_tests << " passed!" << endl;
         }
       }
     }
   } catch ( CepGen::Exception& e ) {}
   if ( failed_tests.size() != 0 ) {
     ostringstream os_failed, os_passed;
     for ( const auto& fail : failed_tests )
       os_failed << " (*) " << fail << endl;
     for ( const auto& pass : passed_tests )
       os_passed << " (*) " << pass << endl;
     throw CG_FATAL( "main" )
       << "Some tests failed!\n"
       << os_failed.str() << "\n"
       << "Passed tests:\n"
       << os_passed.str() << ".";
   }
 
   CG_INFO( "main" ) << "ALL TESTS PASSED!";
 
   return 0;
 }