diff --git a/PhysicsAnalysis/Interfaces/TriggerAnalysisInterfaces/TriggerAnalysisInterfaces/ITrigGlobalEfficiencyCorrectionTool.h b/PhysicsAnalysis/Interfaces/TriggerAnalysisInterfaces/TriggerAnalysisInterfaces/ITrigGlobalEfficiencyCorrectionTool.h
index 7063cd6bf7ec103001e4e3a2189ca1e405ea30d8..4b72be7f5a3040996ffdf52eb9d8e7661f3b12a2 100644
--- a/PhysicsAnalysis/Interfaces/TriggerAnalysisInterfaces/TriggerAnalysisInterfaces/ITrigGlobalEfficiencyCorrectionTool.h
+++ b/PhysicsAnalysis/Interfaces/TriggerAnalysisInterfaces/TriggerAnalysisInterfaces/ITrigGlobalEfficiencyCorrectionTool.h
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
 */
 
 // contact: jmaurer@cern.ch
@@ -7,29 +7,109 @@
 #ifndef TRIGGERANALYSISINTERFACES_ITRIGGLOBALEFFICIENCYCORRECTIONTOOL_H
 #define TRIGGERANALYSISINTERFACES_ITRIGGLOBALEFFICIENCYCORRECTIONTOOL_H 1
 
-#include "AsgTools/IAsgTool.h"
-#include "xAODEgamma/ElectronFwd.h"
-#include "xAODMuon/Muon.h"
+#include "PATInterfaces/ISystematicsTool.h"
 #include "PATInterfaces/CorrectionCode.h"
+#include "xAODEgamma/Electron.h"
+#include "xAODMuon/Muon.h"
+#include "xAODEgamma/Photon.h"
+
+#include <vector>
+#include <type_traits>
 
-class ITrigGlobalEfficiencyCorrectionTool : public virtual asg::IAsgTool
+class ITrigGlobalEfficiencyCorrectionTool : public virtual CP::ISystematicsTool
 {
 public:
 	ASG_TOOL_INTERFACE(ITrigGlobalEfficiencyCorrectionTool)
+	
+	template<typename Arg> static constexpr bool validArgs(unsigned nTrailingDoubles);
+	template<typename Arg1, typename Arg2, typename... OtherArgs> static constexpr bool validArgs(unsigned nTrailingDoubles);
 
 	virtual CP::CorrectionCode getEfficiencyScaleFactor(const std::vector<const xAOD::IParticle*>& particles, double& efficiencyScaleFactor) = 0;
-	virtual CP::CorrectionCode getEfficiencyScaleFactor(unsigned runNumber, const std::vector<const xAOD::IParticle*>& particles, double& efficiencyScaleFactor) = 0;
 	virtual CP::CorrectionCode getEfficiency(const std::vector<const xAOD::IParticle*>& particles, double& efficiencyData, double& efficiencyMc) = 0;
-	virtual CP::CorrectionCode getEfficiency(unsigned runNumber, const std::vector<const xAOD::IParticle*>& particles, double& efficiencyData, double& efficiencyMc) = 0;		
+	virtual CP::CorrectionCode checkTriggerMatching(bool& matched, const std::vector<const xAOD::IParticle*>& particles) = 0;
+	
+	/// Alternatively, the list of particles can be supplied via one or several vectors of xAOD::Electron*/Muon*/Photon*
+	/// The generic signature is getEfficiencyScaleFactor((const) vector<(const)Type1*>&, ..., (const) vector<(const)TypeN*>&, double& efficiencyScaleFactor)
+	/// e.g. getEfficiencyScaleFactor(electrons, muons, sf);
+	///      getEfficiencyScaleFactor(photons, sf);
+	///      getEfficiencyScaleFactor(muons, electrons, photons, sf);
+	/// don't forget the last argument(s)! (scale factor for getEfficiencyScaleFactor(), data and MC efficiencies for getEfficiency())
+	template<typename... Args>
+	auto getEfficiencyScaleFactor(Args&... args) -> std::enable_if_t<validArgs<Args...>(1), CP::CorrectionCode>;
+	template<typename... Args>
+	auto getEfficiency(Args&... args) -> std::enable_if_t<validArgs<Args...>(2), CP::CorrectionCode>;
+	template<typename... Args>
+	auto checkTriggerMatching(bool& matched, Args&... args) -> std::enable_if_t<validArgs<Args...>(0), CP::CorrectionCode>;
+	
+	/// This will fill the 'triggers' argument with the names of the triggers relevant for the current run number, among those specified in the tool configuration
+	virtual CP::CorrectionCode getRelevantTriggers(std::vector<std::string>& triggers) = 0;
+	
+	/// This utility function provides the number of legs for the specified trigger
+	virtual CP::CorrectionCode countTriggerLegs(const std::string& trigger, std::size_t& numberOfLegs) = 0;
+	
+	/// These should in principle not be used (except by unit tests), as the CP tools require the EventInfo decoration "RandomRunNumber" to be present 
+	virtual CP::CorrectionCode getEfficiencyScaleFactor(unsigned runNumber, const std::vector<const xAOD::IParticle*>& particles, double& efficiencyScaleFactor) = 0;
+	virtual CP::CorrectionCode getEfficiency(unsigned runNumber, const std::vector<const xAOD::IParticle*>& particles, double& efficiencyData, double& efficiencyMc) = 0;	
 	
-	virtual CP::CorrectionCode getEfficiencyScaleFactor(const std::vector<const xAOD::Electron*>& electrons,
-			const std::vector<const xAOD::Muon*>& muons, double& efficiencyScaleFactor) = 0;
-	virtual CP::CorrectionCode getEfficiencyScaleFactor(unsigned runNumber, const std::vector<const xAOD::Electron*>& electrons,
-			const std::vector<const xAOD::Muon*>& muons, double& efficiencyScaleFactor) = 0;
-	virtual CP::CorrectionCode getEfficiency(const std::vector<const xAOD::Electron*>& electrons,
-			const std::vector<const xAOD::Muon*>& muons, double& efficiencyData, double& efficiencyMc) = 0;
-	virtual CP::CorrectionCode getEfficiency(unsigned runNumber, const std::vector<const xAOD::Electron*>& electrons,
-			const std::vector<const xAOD::Muon*>& muons, double& efficiencyData, double& efficiencyMc) = 0;	
+protected:
+	double* handleArg(double& arg, std::vector<const xAOD::IParticle*>&) { return &arg; }
+	template<typename P>  double* handleArg(const std::vector<P>& arg, std::vector<const xAOD::IParticle*>& particles)
+	{
+		for(auto ptr : arg) particles.push_back(static_cast<const xAOD::IParticle*>(ptr));
+		return nullptr;
+	}
 };
 
-#endif //> !TRIGGERANALYSISINTERFACES_ITRIGGLOBALEFFICIENCYCORRECTIONTOOL_H
\ No newline at end of file
+template<typename Arg>
+constexpr bool ITrigGlobalEfficiencyCorrectionTool::validArgs(unsigned nTrailingDoubles)
+{
+	if(std::is_same<double, Arg>::value) return (nTrailingDoubles==1);
+	using P = std::remove_cv_t<Arg>;
+	return std::is_same<P, std::vector<xAOD::Electron*>>::value
+		|| std::is_same<P, std::vector<const xAOD::Electron*>>::value
+		|| std::is_same<P, std::vector<xAOD::Muon*>>::value
+		|| std::is_same<P, std::vector<const xAOD::Muon*>>::value
+		|| std::is_same<P, std::vector<xAOD::Photon*>>::value
+		|| std::is_same<P, std::vector<const xAOD::Photon*>>::value;
+}
+
+template<typename Arg1, typename Arg2, typename... OtherArgs>
+constexpr bool ITrigGlobalEfficiencyCorrectionTool::validArgs(unsigned nTrailingDoubles)
+{
+	bool xs [] = { std::is_same<OtherArgs, double>::value..., true };
+	for(bool x : xs) if(!x) return validArgs<Arg1>(0) && validArgs<Arg2, OtherArgs...>(nTrailingDoubles);
+	unsigned nTD = sizeof...(OtherArgs);
+	if(nTD == nTrailingDoubles) return validArgs<Arg1>(0) && validArgs<Arg2>(0);
+	if(nTD == nTrailingDoubles-1) return validArgs<Arg1>(0) && std::is_same<Arg2, double>::value;
+	if(nTD == nTrailingDoubles-2) return std::is_same<Arg1, double>::value && std::is_same<Arg2, double>::value;
+	return false;
+}
+
+template<typename... Args>
+auto ITrigGlobalEfficiencyCorrectionTool::getEfficiencyScaleFactor(Args&... args)
+	-> std::enable_if_t<validArgs<Args...>(1), CP::CorrectionCode>
+{
+	std::vector<const xAOD::IParticle*> particles;
+	double* sf[] = { nullptr, handleArg(args, particles)... };
+	return getEfficiencyScaleFactor(particles, *sf[sizeof...(Args)]);
+}
+
+template<typename... Args>
+auto ITrigGlobalEfficiencyCorrectionTool::getEfficiency(Args&... args)
+	-> std::enable_if_t<validArgs<Args...>(2), CP::CorrectionCode>
+{
+	std::vector<const xAOD::IParticle*> particles;
+	double* eff[] = { nullptr, handleArg(args, particles)... };
+	return getEfficiency(particles, *eff[sizeof...(Args)-1], *eff[sizeof...(Args)]);
+}
+
+template<typename... Args>
+auto ITrigGlobalEfficiencyCorrectionTool::checkTriggerMatching(bool& matched, Args&... args)
+	-> std::enable_if_t<validArgs<Args...>(0), CP::CorrectionCode>
+{
+	std::vector<const xAOD::IParticle*> particles;
+	double* eff[] __attribute__((unused)) = { nullptr, handleArg(args, particles)... };
+	return checkTriggerMatching(matched, particles);
+}
+
+#endif //> !TRIGGERANALYSISINTERFACES_ITRIGGLOBALEFFICIENCYCORRECTIONTOOL_H
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/CMakeLists.txt b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..46b2549a26376d64ba92f07a8a2c15ce7b2f43e1
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/CMakeLists.txt
@@ -0,0 +1,67 @@
+################################################################################
+# Package: TrigGlobalEfficiencyCorrection
+################################################################################
+
+atlas_subdir( TrigGlobalEfficiencyCorrection )
+
+set( athena_subdirs )
+if( NOT XAOD_STANDALONE )
+  set( athena_subdirs GaudiKernel PhysicsAnalysis/POOLRootAccess Control/AthAnalysisBaseComps )
+endif()
+
+find_package( Boost )
+find_package( ROOT COMPONENTS RIO )
+
+atlas_add_library( TrigGlobalEfficiencyCorrectionLib
+   TrigGlobalEfficiencyCorrection/*.h Root/*.cxx
+   #PUBLIC_HEADERS TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrectionTool.h
+   PUBLIC_HEADERS TrigGlobalEfficiencyCorrection
+   PRIVATE_INCLUDE_DIRS ${BOOST_INCLUDE_DIRS} TriggerAnalysisInterfaces
+   LINK_LIBRARIES AsgTools xAODEgamma xAODMuon TriggerAnalysisInterfaces AthAnalysisBaseCompsLib EgammaAnalysisInterfacesLib MuonAnalysisInterfacesLib xAODEventInfo PathResolver TriggerMatchingToolLib
+   PRIVATE_LINK_LIBRARIES ${BOOST_LIBRARIES}
+)
+
+if( NOT XAOD_STANDALONE )
+   atlas_add_component( TrigGlobalEfficiencyCorrection
+      src/*.h src/*.cxx src/components/*.cxx
+      LINK_LIBRARIES xAODEgamma xAODMuon EgammaAnalysisInterfacesLib MuonAnalysisInterfacesLib xAODEventInfo GaudiKernel TrigGlobalEfficiencyCorrectionLib
+   )
+endif()
+
+atlas_add_dictionary( TrigGlobalEfficiencyCorrectionDict
+   TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrectionDict.h
+   TrigGlobalEfficiencyCorrection/selection.xml
+   LINK_LIBRARIES TrigGlobalEfficiencyCorrectionLib
+)
+
+set( pool_lib )
+if( NOT XAOD_STANDALONE )
+  set( pool_lib POOLRootAccessLib )
+endif()
+
+foreach(example TrigGlobEffCorrExample0 TrigGlobEffCorrExample1
+TrigGlobEffCorrExample3a TrigGlobEffCorrExample3b TrigGlobEffCorrExample3c 
+TrigGlobEffCorrExample3d TrigGlobEffCorrExample3e
+TrigGlobEffCorrExample4 TrigGlobEffCorrExample5a TrigGlobEffCorrExample5b)
+   atlas_add_executable( ${example}
+      SOURCES examples/${example}.cxx
+	  INCLUDE_DIRS ${ROOT_INCLUDE_DIRS}
+	  LINK_LIBRARIES TrigGlobalEfficiencyCorrectionLib ${pool_lib}
+   )
+endforeach()
+
+atlas_add_executable( TrigGlobEffCorrExample6
+   SOURCES examples/TrigGlobEffCorrExample6.cxx
+   INCLUDE_DIRS ${ROOT_INCLUDE_DIRS}
+   LINK_LIBRARIES TrigGlobalEfficiencyCorrectionLib ${pool_lib}
+)
+
+atlas_add_executable( TrigGlobEffCorrValidation
+   SOURCES util/TrigGlobEffCorrValidation.cxx
+   INCLUDE_DIRS ${ROOT_INCLUDE_DIRS}
+   LINK_LIBRARIES TrigGlobalEfficiencyCorrectionLib xAODRootAccess
+)
+
+atlas_add_test(UnitTests SCRIPT util/unit_tests.sh)
+
+atlas_install_data( data/*.cfg )
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/README b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/README
new file mode 100644
index 0000000000000000000000000000000000000000..4678583910e3ff45a398cfc6b2dff6ab33dcf79d
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/README
@@ -0,0 +1,28 @@
+The tool interface ITrigGlobalEfficiencyCorrectionTool.h is hosted in another package, 
+PhysicsAnalysis/Interfaces/TriggerAnalysisInterfaces. 
+
+
+Documentation:
+--------------
+o) https://twiki.cern.ch/twiki/bin/viewauth/Atlas/TrigGlobalEfficiencyCorrectionTool
+
+
+Formulas for the combinatorics:
+-------------------------------
+o) doc/formulas.pdf
+
+
+Examples of configuration of the tool:
+--------------------------------------
+o) Source files util/TrigGlobEffCorrExample*.cxx (dual-use (Ath)AnalysisBase executables)
+o) Usage: TrigGlobEffCorrExample0 [--debug] <input DxAOD file>.root
+o) Contents:
+ -- Example 0: minimal configuration
+ -- Example 1: singe+dilepton triggers combination
+ -- Example 2: [removed]
+ -- Examples 3a-3e: usage of lepton selection tags
+ -- Example 4: usage of the static helper method suggestElectronMapKeys()
+ -- Example 5a: photon triggers, simplest example (symmetric diphoton trigger)
+ -- Example 5b: photon triggers, more complex (asymmetric diphoton trigger)
+ -- Example 06: trigger matching
+o) More details in the comments of each source file
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/README.developers b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/README.developers
new file mode 100644
index 0000000000000000000000000000000000000000..3e3921c323b512a57ec539f4f42cacb1ee1f697a
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/README.developers
@@ -0,0 +1,3 @@
+The examples 0-3 should not be edited directly: the source files are produced 
+by running the script examples/generator/generateExamples.py.
+Modifications should therefore be made to the templates in the generator directory. 
\ No newline at end of file
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/Root/Calculator.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/Root/Calculator.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..5a6c6392425ec322412487c232f114f821e8ed7b
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/Root/Calculator.cxx
@@ -0,0 +1,1404 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+// contact: jmaurer@cern.ch
+
+#include "TrigGlobalEfficiencyCorrection/Calculator.h"
+#include "TrigGlobalEfficiencyCorrection/ImportData.h"
+#include "TrigGlobalEfficiencyCorrection/Lepton.h"
+#include "TrigGlobalEfficiencyCorrection/Trigger.h"
+
+#include <random>
+#include <iterator>
+
+using namespace TrigGlobEffCorr;
+using std::placeholders::_1;
+using std::placeholders::_2;
+using std::placeholders::_3;
+using std::placeholders::_4;
+
+Calculator::Calculator(TrigGlobalEfficiencyCorrectionTool& parent, unsigned nPeriodsToReserve) :
+	asg::AsgMessaging(&parent),
+	m_parent(&parent)
+{
+	msg().setLevel(parent.msg().level());
+	m_periods.reserve(nPeriodsToReserve);
+}
+		
+bool Calculator::addPeriod(ImportData& data, const std::pair<unsigned,unsigned>& boundaries, const std::string& combination, 
+	bool useToys, std::size_t& uniqueElectronLeg, std::size_t& uniquePhotonLeg)
+{
+	bool success = true;
+	m_parent = data.getParent();
+	
+	auto triggers = data.parseTriggerString(combination, success);
+	if(!success) return false;
+	if(!triggers.size())
+	{
+		ATH_MSG_ERROR("The trigger combination is empty!");
+		return false;
+	}
+	
+	if(!findUniqueLeg(xAOD::Type::Electron, uniqueElectronLeg, triggers)) return false;
+	if(!findUniqueLeg(xAOD::Type::Photon, uniquePhotonLeg, triggers)) return false;
+	
+	/// Choose the appropriate function to compute efficiencies for this particular trigger combination
+	Helper helper(triggers);
+	if(helper.duplicates())
+	{
+		ATH_MSG_ERROR("The following combination of triggers contains duplicates: " << combination);
+		return false;
+	}
+	if(!useToys)
+	{	
+		success = helper.findAndBindFunction();
+		if(!helper.m_formula)
+		{
+			ATH_MSG_ERROR("This trigger combination is currently not supported with an explicit formula (you may use toys instead, slower): " << combination);
+			return false;
+		}
+	}
+	else
+	{
+		helper.m_formula = std::bind(&Calculator::globalEfficiency_Toys, ::_1, ::_2, ::_3, triggers, ::_4);
+	}
+	if(success)
+	{
+		if(data.adaptTriggerListForTriggerMatching(triggers))
+		{
+			m_periods.emplace_back(boundaries, std::move(helper.m_formula), std::move(triggers));
+		}
+		else
+		{
+			if(m_parent->m_validTrigMatchTool) return false;
+			m_periods.emplace_back(boundaries, std::move(helper.m_formula));
+		}
+	}
+	else
+	{
+		ATH_MSG_ERROR("Unspecified error occurred while trying to find the formula for the trigger combination " << combination);
+	}
+	return success;
+}
+
+bool Calculator::findUniqueLeg(xAOD::Type::ObjectType obj, std::size_t& uniqueLeg, const std::vector<TrigDef>& defs)
+{
+	if(uniqueLeg) return true; /// initial non-zero value means that ListOfLegsPerTool is filled
+	for(auto& def : defs)
+	{
+		TriggerProperties tp(def);
+		for(auto itr=tp.cbegin(obj);itr!=tp.cend(obj);++itr)
+		{
+			if(uniqueLeg && uniqueLeg!=*itr)
+			{
+				ATH_MSG_ERROR("The property 'ListOfLegsPerTool' needs to be filled as the specified trigger combination involves several electron (or photon) trigger legs");
+				return false;
+			}
+			uniqueLeg = *itr;
+		}
+	}
+	return true;
+}
+
+const Calculator::Period* Calculator::getPeriod(unsigned runNumber) const
+{
+	auto period = std::find_if(m_periods.cbegin(), m_periods.cend(),
+		[=](const Period& p) { return runNumber>=p.m_boundaries.first && runNumber<=p.m_boundaries.second; });
+	if(period == m_periods.end())
+	{
+		ATH_MSG_ERROR("No trigger combination has been specified for run " << runNumber);
+		return nullptr;
+	}
+	return &*period;
+}
+
+bool Calculator::compute(TrigGlobalEfficiencyCorrectionTool& parent, const LeptonList& leptons, unsigned runNumber, Efficiencies& efficiencies)
+{
+	m_parent = &parent;
+	auto period = getPeriod(runNumber);
+	if(!period) return false;
+	m_cachedEfficiencies.clear();
+	return period->m_formula && period->m_formula(this, leptons, runNumber, efficiencies);
+}
+
+bool Calculator::checkTriggerMatching(TrigGlobalEfficiencyCorrectionTool& parent, bool& matched, const LeptonList& leptons, unsigned runNumber)
+{
+	matched = false;
+	m_parent = &parent;
+	auto period = getPeriod(runNumber);
+	if(!period) return false;
+	if(!period->m_triggers.size())
+	{
+		ATH_MSG_ERROR("Empty list of triggers for run number " << runNumber);
+		return false;
+	}
+	auto& trigMatchTool = m_parent->m_trigMatchTool;
+	
+	/// First, for each lepton, list the trigger leg(s) it is allowed to fire (depends on pT and selection tags)
+	const unsigned nLep = leptons.size();
+	std::vector<flat_set<std::size_t> > validLegs(leptons.size());
+	for(unsigned i=0;i<nLep;++i)
+	{
+		if(!fillListOfLegsFor(leptons[i], period->m_triggers, validLegs[i])) return false;
+	}
+	
+	/// Then for each trigger, call trigger matching tool for all possible (valid) lepton combinations
+	std::vector<flat_set<std::size_t> > firedLegs;
+	std::vector<const xAOD::IParticle*> trigLeptons;
+	const std::size_t magicWordHLT = 0xf7b8b87ef2917d66;
+
+	for(auto& trig : period->m_triggers)
+	{
+		/// Get trigger chain name with a "HLT_" prefix
+		auto itr = m_parent->m_dictionary.find(trig.name ^ magicWordHLT);
+		if(itr == m_parent->m_dictionary.end())
+		{
+			itr = m_parent->m_dictionary.emplace(trig.name ^ magicWordHLT, "HLT_" + m_parent->m_dictionary.at(trig.name)).first;
+		}
+		const std::string& chain = itr->second;
+		
+		unsigned nLegs = 0;
+		if(trig.type & TT_SINGLELEPTON_FLAG) nLegs = 1;
+		else if(trig.type & TT_DILEPTON_FLAG) nLegs = 2;
+		else if(trig.type & TT_TRILEPTON_FLAG) nLegs = 3;
+		else
+		{
+			ATH_MSG_ERROR("Unsupported trigger (type = " << std::hex << trig.type << std::dec << ") " << chain );
+			return false;
+		}
+		firedLegs.resize(nLegs);
+		trigLeptons.resize(nLegs);
+		for(unsigned i0=0;i0<nLep;++i0)
+		{
+			firedLegs[0].swap(validLegs[i0]); /// borrow the set of legs that can be fired by that lepton
+			trigLeptons[0] = leptons[i0].particle();
+			if(nLegs == 1)
+			{
+				if(canTriggerBeFired(trig, firedLegs) /// enough lepton(s) on trigger plateau?
+					&& trigMatchTool->match(trigLeptons, chain)) return (matched = true);
+			}
+			else for(unsigned i1=i0+1;i1<nLep;++i1)
+			{
+				firedLegs[1].swap(validLegs[i1]);
+				trigLeptons[1] = leptons[i1].particle();
+				if(nLegs == 2)
+				{
+					if(canTriggerBeFired(trig, firedLegs)
+						&& trigMatchTool->match(trigLeptons, chain)) return (matched = true);
+				}
+				else for(unsigned i2=i1+1;i2<nLep;++i2)
+				{
+					firedLegs[2].swap(validLegs[i2]);
+					trigLeptons[2] = leptons[i2].particle();
+					if(canTriggerBeFired(trig, firedLegs)
+						&& trigMatchTool->match(trigLeptons, chain)) return (matched = true);
+					firedLegs[2].swap(validLegs[i2]);
+				}
+				firedLegs[1].swap(validLegs[i1]);
+			}
+			firedLegs[0].swap(validLegs[i0]); /// return the set of legs back to the main container
+		}
+	}
+	return true;
+}
+
+bool Calculator::getRelevantTriggersForUser(TrigGlobalEfficiencyCorrectionTool& parent, std::vector<std::string>& triggers, unsigned runNumber)
+{
+	triggers.clear();
+	m_parent = &parent;
+	auto period = getPeriod(runNumber);
+	if(!period) return false;
+	if(!period->m_triggers.size())
+	{
+		ATH_MSG_ERROR("Empty list of triggers for run number " << runNumber << " (was there a configuration issue? please check for warnings during initialization)");
+		return false;
+	}
+	bool success = true;
+	auto notfound = parent.m_dictionary.end();
+	for(auto& trig : period->m_triggers)
+	{
+		auto itr = parent.m_dictionary.find(trig.name);
+		if(itr == notfound)
+		{
+			ATH_MSG_ERROR("can't retrieve name of trigger with hash " << trig.name << " (shouldn't happen; contact tool developers!)");
+			success = false;
+		}
+		else triggers.push_back(itr->second);
+	}
+	if(!success) triggers.clear();
+	return success;
+}
+
+Efficiencies Calculator::getCachedTriggerLegEfficiencies(const Lepton& lepton, unsigned runNumber, std::size_t leg, bool& success)
+{
+	auto insertion = m_cachedEfficiencies.emplace(std::make_pair(&lepton, leg), Efficiencies());
+	Efficiencies& efficiencies = insertion.first->second;
+	if(insertion.second)
+	{
+		bool cpSuccess = false;
+		switch(lepton.type())
+		{
+		case xAOD::Type::Electron:
+			cpSuccess = m_parent->getTriggerLegEfficiencies(lepton.electron(), runNumber, leg, lepton.tag(), efficiencies);
+			break;
+		case xAOD::Type::Muon:
+			cpSuccess = m_parent->getTriggerLegEfficiencies(lepton.muon(), leg, lepton.tag(), efficiencies);
+			break;
+		case xAOD::Type::Photon:
+			cpSuccess = m_parent->getTriggerLegEfficiencies(lepton.photon(), runNumber, leg, lepton.tag(), efficiencies);
+			break;
+		default: ATH_MSG_ERROR("Unsupported particle type");
+		}
+		if(!cpSuccess)
+		{
+			efficiencies.data() = -777.;
+			efficiencies.mc() = -777.;
+			success = false;
+		}
+	}
+	if(efficiencies.mc()==-777.) success = false;
+	return efficiencies;
+}
+
+///
+/// One single-lepton trigger
+///
+template<typename Trig1L>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber, const Trig1L trig, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig1L::is1L(), bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_One1L() at line " << __LINE__);
+	if(!trig)
+	{
+		globalEfficiencies = {0.};
+		return true;
+	}
+	globalEfficiencies = {1.};
+	bool success = true;
+	for(auto& lepton : leptons)
+	{
+		if(trig.irrelevantFor(lepton) || !aboveThreshold(lepton, trig())) continue;
+		auto efficiencies = getCachedTriggerLegEfficiencies(lepton, runNumber, trig(), success);
+		globalEfficiencies *= ~efficiencies;
+	}
+	globalEfficiencies = ~globalEfficiencies;
+	return success;
+}
+
+///
+/// Two single-lepton triggers, two object types
+///
+template<typename Trig1L_obj1, typename Trig1L_obj2>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber, const Trig1L_obj1 trig1, const Trig1L_obj2 trig2, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig1L_obj1::is1L()
+						&& Trig1L_obj2::is1L()
+						&& Trig1L_obj1::object() != Trig1L_obj2::object(),
+		bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_Two1L() at line " << __LINE__);
+	if(!trig1) return globalEfficiency(leptons, runNumber, trig2, globalEfficiencies);
+	if(!trig2) return globalEfficiency(leptons, runNumber, trig1, globalEfficiencies);
+	globalEfficiencies = {1.};
+	bool success = true;
+	for(auto& lepton : leptons)
+	{
+		std::size_t leg;
+		if(trig1.relevantFor(lepton)) leg = trig1();
+		else if(trig2.relevantFor(lepton)) leg = trig2();
+		else continue;
+		if(!aboveThreshold(lepton, leg)) continue;
+		auto efficiencies = getCachedTriggerLegEfficiencies(lepton, runNumber, leg, success);
+		globalEfficiencies *= ~efficiencies;
+	}
+	globalEfficiencies = ~globalEfficiencies;
+	return success;
+}
+
+///
+/// Several single-lepton triggers, one object type
+///
+template<typename Trig1L>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber, const flat_set<Trig1L>& trigs, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig1L::is1L(), bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_Several1L() at line " << __LINE__);
+	if(trigs.size() == 1) return globalEfficiency(leptons, runNumber, *trigs.cbegin(), globalEfficiencies);
+	if(!trigs.size())
+	{
+		globalEfficiencies = {0.};
+		return true;
+	}
+	globalEfficiencies = {1.};
+	bool success = true;
+	for(auto& lepton : leptons)
+	{
+		if(Trig1L::irrelevantFor(lepton)) continue;
+		std::size_t loosestLeg = getLoosestLegAboveThreshold(lepton, trigs, success);
+		if(loosestLeg && success)
+		{
+			auto efficiencies = getCachedTriggerLegEfficiencies(lepton, runNumber, loosestLeg, success);
+			globalEfficiencies *= ~efficiencies;
+		}
+	}
+	globalEfficiencies = ~globalEfficiencies;
+	return success;
+}
+
+///
+/// Several single-lepton triggers, two object types
+///
+template<typename Trig1L_obj1, typename Trig1L_obj2>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber,
+			const flat_set<Trig1L_obj1>& trigs1, const flat_set<Trig1L_obj2>& trigs2, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig1L_obj1::is1L()
+						&& Trig1L_obj2::is1L()
+						&& Trig1L_obj1::object() != Trig1L_obj2::object(),
+		bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_Several1L() at line " << __LINE__);
+	if(trigs1.size()==1 && trigs2.size()==1)
+	{
+		return globalEfficiency(leptons, runNumber, *trigs1.cbegin(), *trigs2.cbegin(), globalEfficiencies);
+	}
+	if(!trigs1.size()) return globalEfficiency(leptons, runNumber, trigs2, globalEfficiencies);
+	if(!trigs2.size()) return globalEfficiency(leptons, runNumber, trigs1, globalEfficiencies);
+	globalEfficiencies = {1.};
+	bool success = true;
+	for(auto& lepton : leptons)
+	{
+		std::size_t loosestLeg;
+		if(Trig1L_obj1::relevantFor(lepton)) loosestLeg = getLoosestLegAboveThreshold(lepton, trigs1, success);
+		else if(Trig1L_obj2::relevantFor(lepton)) loosestLeg = getLoosestLegAboveThreshold(lepton, trigs2, success);
+		else continue;
+		if(loosestLeg && success)
+		{
+			auto efficiencies = getCachedTriggerLegEfficiencies(lepton, runNumber, loosestLeg, success);
+			globalEfficiencies *= ~efficiencies;
+		}
+	}
+	globalEfficiencies = ~globalEfficiencies;
+	return success;
+}
+
+///
+/// One mixed-flavour dilepton trigger
+///
+template<typename Trig2Lmix>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber, const Trig2Lmix trig, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig2Lmix::is2Lmix(), bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_One2L() at line " << __LINE__);
+	Efficiencies efficiencies[2];
+	bool success = globalEfficiency(leptons, runNumber, trig.side1(), efficiencies[0])
+		&& globalEfficiency(leptons, runNumber, trig.side2(), efficiencies[1]);
+	if(success) globalEfficiencies = efficiencies[0] * efficiencies[1];
+	else globalEfficiencies = {0.};
+	return success;
+}
+
+///
+/// One symmetric dilepton trigger
+///
+template<typename Trig2Lsym>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber, const Trig2Lsym trig , Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig2Lsym::is2Lsym(), bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_One2L() at line " << __LINE__);
+	globalEfficiencies = {0.};
+	if(!trig) return true;
+	Efficiencies singleInefficiencies(1.);
+	bool success = true;
+	int nt = 0;
+	for(auto& lepton : leptons)
+	{
+		if(trig.irrelevantFor(lepton) || !aboveThreshold(lepton, trig())) continue;
+		++nt;
+		auto efficiencies = getCachedTriggerLegEfficiencies(lepton, runNumber, trig(), success);
+		globalEfficiencies = ~efficiencies*globalEfficiencies + efficiencies*~singleInefficiencies;
+		singleInefficiencies *= ~efficiencies;
+	}
+	return success;
+}
+
+///
+/// One asymmetric dilepton trigger
+///
+template<typename Trig2Lasym>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber, const Trig2Lasym trig, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig2Lasym::is2Lasym(), bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_One2L() at line " << __LINE__);
+	if(trig.symmetric()) return globalEfficiency(leptons, runNumber, trig.to_symmetric(), globalEfficiencies);
+	globalEfficiencies = {0.};
+	if(!trig) return true;
+	Efficiencies singleInefficiencies[2] = {{1.},{1.}}, twoSingleInefficiencies = {1.};
+	bool success = true;
+	for(auto& lepton : leptons)
+	{
+		if(trig.irrelevantFor(lepton)) continue;
+		Efficiencies efficiencies[2] = {{0.}, {0.}};
+		int loosest = 0;
+		if(aboveThreshold(lepton, trig(0)))
+		{
+			efficiencies[0] = getCachedTriggerLegEfficiencies(lepton, runNumber, trig(0), success);
+			if(aboveThreshold(lepton, trig(1)))
+			{
+				efficiencies[1] = getCachedTriggerLegEfficiencies(lepton, runNumber, trig(1), success);
+				loosest = m_parent->getLoosestLeg(lepton, trig(0), trig(1), success)==trig(1);
+			}
+		}
+		else if(aboveThreshold(lepton, trig(1)))
+		{
+			efficiencies[1] = getCachedTriggerLegEfficiencies(lepton, runNumber, trig(1), success);
+			loosest = 1;
+		}
+		else continue;
+		const int tightest = 1 - loosest;
+		globalEfficiencies = ~efficiencies[loosest]*globalEfficiencies 
+			+ (efficiencies[loosest]-efficiencies[tightest])*~singleInefficiencies[tightest]
+			+ efficiencies[tightest]*~twoSingleInefficiencies;
+		twoSingleInefficiencies *= ~efficiencies[loosest];
+		for(int i=0;i<2;++i) singleInefficiencies[i] *= ~efficiencies[i];
+	}
+	return success;
+}
+
+///
+/// One mixed-flavour dilepton trigger + single-lepton triggers
+///
+template<typename Trig2Lmix, typename Trig1L_obj1, typename Trig1L_obj2>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber,
+		const Trig2Lmix trig2Lmix, const flat_set<Trig1L_obj1>& trigs1L1, const flat_set<Trig1L_obj2>& trigs1L2, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig2Lmix::is2Lmix()
+						&& Trig1L_obj1::is1L() && Trig2Lmix::object1()==Trig1L_obj1::object()
+						&& Trig1L_obj2::is1L() && Trig2Lmix::object2()==Trig1L_obj2::object(),
+		bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_One2LSeveral1L() at line " << __LINE__);
+	if(!(trigs1L1.size() + trigs1L2.size()))
+		return globalEfficiency(leptons, runNumber, trig2Lmix, globalEfficiencies);
+	if(trig2Lmix.hiddenBy(trigs1L1) || trig2Lmix.hiddenBy(trigs1L2))
+		return globalEfficiency(leptons, runNumber, trigs1L1, trigs1L2, globalEfficiencies);
+	Efficiencies efficiencies[4];
+	bool success = globalEfficiency(leptons, runNumber, trigs1L1, efficiencies[0])
+		&& globalEfficiency(leptons, runNumber, trigs1L2, efficiencies[1])
+		&& globalEfficiency(leptons, runNumber, trig2Lmix.addTo(trigs1L1), efficiencies[2])
+		&& globalEfficiency(leptons, runNumber, trig2Lmix.addTo(trigs1L2), efficiencies[3]);
+	if(success)
+	{
+		globalEfficiencies = Efficiencies(1.) - ~efficiencies[0]*~efficiencies[1]
+			+ (efficiencies[2]-efficiencies[0])*(efficiencies[3]-efficiencies[1]);
+	}
+	else globalEfficiencies = {0.};
+	return success;
+}
+
+///
+/// One dilepton trigger + one single-lepton trigger
+///
+template<typename Trig2L, typename Trig1L>
+inline auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber,
+			const Trig2L trig2L, const Trig1L trig1L, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig2L::is2Lnomix() 
+						&& Trig1L::is1L()
+						&& Trig2L::object()==Trig1L::object(),
+		bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_One2LSeveral1L() at line " << __LINE__);
+	return globalEfficiency(leptons, runNumber, trig2L, flat_set<Trig1L>{trig1L}, globalEfficiencies);
+}
+
+///
+/// One symmetric dilepton trigger + several single-lepton triggers
+///
+template<typename Trig2Lsym, typename Trig1L>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber,
+			const Trig2Lsym trig2L, const flat_set<Trig1L>& trigs1L, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig2Lsym::is2Lsym() 
+						&& Trig1L::is1L() 
+						&& Trig1L::object() == Trig2Lsym::object(), 
+		bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_One2LSeveral1L() at line " << __LINE__);
+	if(!trigs1L.size()) return globalEfficiency(leptons, runNumber, trig2L, globalEfficiencies);
+	if(!trig2L || trig2L.hiddenBy(trigs1L)) return globalEfficiency(leptons, runNumber, trigs1L, globalEfficiencies);
+	globalEfficiencies = {0.};
+	Efficiencies twoSingleInefficiencies = {1.};
+	bool success = true;
+	for(auto& lepton : leptons)
+	{
+		if(trig2L.irrelevantFor(lepton)) continue;
+		Efficiencies efficiencies1L(0.), efficiencies2L(0.);
+		const Efficiencies* loosestLegEfficiency;
+		std::size_t loosest1lepLeg = getLoosestLegAboveThreshold(lepton, trigs1L, success);
+		if(loosest1lepLeg)
+		{
+			efficiencies1L = getCachedTriggerLegEfficiencies(lepton, runNumber, loosest1lepLeg, success);
+			if(aboveThreshold(lepton, trig2L()))
+			{
+				efficiencies2L = getCachedTriggerLegEfficiencies(lepton, runNumber, trig2L(), success);
+				loosestLegEfficiency = (m_parent->getLoosestLeg(lepton, trig2L(), loosest1lepLeg , success)==trig2L())? &efficiencies2L : &efficiencies1L;
+			}
+			else loosestLegEfficiency = &efficiencies1L;
+		}
+		else if(aboveThreshold(lepton, trig2L()))
+		{
+			efficiencies2L = getCachedTriggerLegEfficiencies(lepton, runNumber, trig2L(), success);
+			loosestLegEfficiency = &efficiencies2L;
+		}
+		else continue;
+		globalEfficiencies = ~(*loosestLegEfficiency)*globalEfficiencies + efficiencies1L;
+		if(loosestLegEfficiency==&efficiencies2L) globalEfficiencies += ~twoSingleInefficiencies*(efficiencies2L - efficiencies1L);			
+		twoSingleInefficiencies *= ~(*loosestLegEfficiency);
+	}
+	return success;
+}
+
+///
+/// One asymmetric dilepton trigger + several single-lepton triggers
+///
+template<typename Trig2Lasym, typename Trig1L>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber,
+			const Trig2Lasym trig2L, const flat_set<Trig1L>& trigs1L, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig2Lasym::is2Lasym() 
+						&& Trig1L::is1L() 
+						&& Trig1L::object() == Trig2Lasym::object(), 
+		bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_One2LSeveral1L() at line " << __LINE__);
+	if(trig2L.symmetric()) return globalEfficiency(leptons, runNumber, trig2L.to_symmetric(), trigs1L, globalEfficiencies);
+	if(!trigs1L.size()) return globalEfficiency(leptons, runNumber, trig2L, globalEfficiencies);
+	if(!trig2L || trig2L.hiddenBy(trigs1L)) return globalEfficiency(leptons, runNumber, trigs1L, globalEfficiencies);
+	globalEfficiencies = {0.};
+	Efficiencies twoSingleInefficiencies[2] = {{1.}, {1.}}, threeSingleInefficiencies = {1.};
+	bool success = true;
+	for(auto& lepton : leptons)
+	{
+		if(trig2L.irrelevantFor(lepton)) continue;
+		flat_set<std::size_t> validLegs;
+		for(std::size_t leg : trig2L.legs) if(aboveThreshold(lepton, leg)) validLegs.insert(leg);
+		Efficiencies efficiencies1L = {0.};
+		std::size_t loosest1lepLeg = getLoosestLegAboveThreshold(lepton, trigs1L, success);
+		if(loosest1lepLeg)
+		{
+			efficiencies1L = getCachedTriggerLegEfficiencies(lepton, runNumber, loosest1lepLeg, success);
+			validLegs.insert(loosest1lepLeg);
+		}
+		if(!validLegs.size()) continue;
+		auto looseLegs = m_parent->getTwoLoosestLegs(lepton,validLegs,success);
+		auto efficienciesLoose = (looseLegs.first==loosest1lepLeg)? efficiencies1L : getCachedTriggerLegEfficiencies(lepton, runNumber, looseLegs.first, success);
+		Efficiencies efficienciesMedium = {0.};
+		if(validLegs.size()>=2)
+			efficienciesMedium = (looseLegs.second==loosest1lepLeg)? efficiencies1L : getCachedTriggerLegEfficiencies(lepton, runNumber, looseLegs.second, success);
+		globalEfficiencies = ~efficienciesLoose*globalEfficiencies + efficiencies1L;
+		if(loosest1lepLeg!=looseLegs.first)
+		{
+			globalEfficiencies += (efficienciesLoose-efficienciesMedium)*~twoSingleInefficiencies[looseLegs.first==trig2L.legs[0]];
+			if(loosest1lepLeg!=looseLegs.second) globalEfficiencies += (efficienciesMedium-efficiencies1L)*~threeSingleInefficiencies;
+		}
+		threeSingleInefficiencies *= ~efficienciesLoose;
+		twoSingleInefficiencies[0] *= (looseLegs.first!=trig2L.legs[1])?~efficienciesLoose:~efficienciesMedium; /// S1 v S3
+		twoSingleInefficiencies[1] *= (looseLegs.first!=trig2L.legs[0])?~efficienciesLoose:~efficienciesMedium; /// S2 v S3
+	}
+	return success;
+}
+
+///
+/// Two symmetric dilepton triggers + several single-lepton triggers
+///
+template<typename Trig2Lsym, typename Trig1L> 
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber,
+			const Trig2Lsym trig2L1, const Trig2Lsym trig2L2, const flat_set<Trig1L>& trigs1L, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig2Lsym::is2Lsym() 
+						&& Trig1L::is1L()
+						&& Trig1L::object() == Trig2Lsym::object(), 
+		bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_Two2LSeveral1L() at line " << __LINE__);
+	if(!trig2L1 || trig2L1==trig2L2 || trig2L1.hiddenBy(trigs1L)) return globalEfficiency(leptons, runNumber, trig2L2, trigs1L, globalEfficiencies);
+	if(!trig2L2 || trig2L2.hiddenBy(trigs1L)) return globalEfficiency(leptons, runNumber, trig2L1, trigs1L, globalEfficiencies);
+	globalEfficiencies = {0.};
+	Efficiencies singleInefficiencies{1.};
+	Efficiencies efficiencies2Lsym[2] = {{0.},{0.}};
+	
+	bool success = true;
+	for(auto& lepton : leptons)
+	{
+		if(trig2L1.irrelevantFor(lepton)) continue;
+		flat_set<std::size_t> validLegs;
+		std::map<std::size_t, Efficiencies> efficiencies{{0,0.}};
+		std::size_t loosest1lepLeg = getLoosestLegAboveThreshold(lepton, trigs1L, success);
+		for(std::size_t leg : {loosest1lepLeg, trig2L1(), trig2L2()})
+		{
+			if(leg && aboveThreshold(lepton, leg))
+			{
+				validLegs.insert(leg);
+				efficiencies.emplace(leg, getCachedTriggerLegEfficiencies(lepton, runNumber, leg, success));
+			}
+			else efficiencies.emplace(leg, 0.);
+		}
+		if(!validLegs.size()) continue;
+		auto looseLegs = m_parent->getTwoLoosestLegs(lepton, validLegs, success);
+		std::size_t lambda13 = (looseLegs.first!=trig2L2())? looseLegs.first : looseLegs.second;
+		std::size_t lambda23 = (looseLegs.first!=trig2L1())? looseLegs.first : looseLegs.second;
+		globalEfficiencies = globalEfficiencies*~efficiencies[looseLegs.first] + efficiencies[loosest1lepLeg];
+		if(looseLegs.first==trig2L1()) globalEfficiencies += efficiencies2Lsym[1]*(efficiencies[trig2L1()] - efficiencies[looseLegs.second]);
+		if(looseLegs.first==trig2L2()) globalEfficiencies += efficiencies2Lsym[0]*(efficiencies[trig2L2()] - efficiencies[looseLegs.second]);
+		if(looseLegs.first!=loosest1lepLeg) globalEfficiencies += ~singleInefficiencies*(efficiencies[looseLegs.second] - efficiencies[loosest1lepLeg]);
+		efficiencies2Lsym[0] = ~efficiencies[looseLegs.first]*efficiencies2Lsym[0] + efficiencies[lambda23];
+		efficiencies2Lsym[1] = ~efficiencies[looseLegs.first]*efficiencies2Lsym[1] + efficiencies[lambda13];
+		if(looseLegs.first==trig2L1()) efficiencies2Lsym[0] += (efficiencies[trig2L1()]-efficiencies[lambda23])*~singleInefficiencies;
+		if(looseLegs.first==trig2L2()) efficiencies2Lsym[1] += (efficiencies[trig2L2()]-efficiencies[lambda13])*~singleInefficiencies;
+		singleInefficiencies *= ~efficiencies[looseLegs.first];
+	}
+	return success;
+}
+
+///
+/// Two dilepton triggers (one asym., one sym.) + several single-lepton triggers
+///
+template<typename Trig2Lasym, typename Trig2Lsym, typename Trig1L>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber,
+			const Trig2Lasym trig2Lasym, const Trig2Lsym trig2Lsym, const flat_set<Trig1L>& trigs1L, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig2Lasym::is2Lasym() 
+						&& Trig2Lsym::is2Lsym() && Trig2Lsym::object()==Trig2Lasym::object()
+						&& Trig1L::is1L() && Trig1L::object()==Trig2Lasym::object(), 
+		bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_Two2LSeveral1L() at line " << __LINE__);
+	if(!trig2Lasym || trig2Lasym.hiddenBy(trigs1L)) return globalEfficiency(leptons, runNumber, trig2Lsym, trigs1L, globalEfficiencies);
+	if(!trig2Lsym || trig2Lsym.hiddenBy(trigs1L)) return globalEfficiency(leptons, runNumber, trig2Lasym, trigs1L, globalEfficiencies);
+	if(trig2Lasym(0)==trig2Lsym() || trig2Lasym(1)==trig2Lsym())
+	{
+		ATH_MSG_ERROR("implementation -- does this function work properly when the two 2L triggers have one leg in common? Must be checked");
+		return false;
+	}
+	if(trig2Lasym.symmetric()) return globalEfficiency(leptons, runNumber, trig2Lasym.to_symmetric(), trig2Lsym, trigs1L, globalEfficiencies);
+	globalEfficiencies = {0.};
+	Efficiencies singleInefficiencies[3] = {{1.}, {1.}, {1.}};
+	Efficiencies efficiencies2Lasym {0.}, efficiencies2Lsym[3] = {{0.},{0.},{0.}};
+	
+	bool success = true;
+	for(auto& lepton : leptons)
+	{
+		if(trig2Lasym.irrelevantFor(lepton)) continue;
+		flat_set<std::size_t> validLegs;
+		std::map<std::size_t, Efficiencies> efficiencies{{0,0.}};
+		std::size_t loosest1lepLeg = getLoosestLegAboveThreshold(lepton, trigs1L, success);
+		for(std::size_t leg : {trig2Lasym(0), trig2Lasym(1), trig2Lsym(), loosest1lepLeg})
+		{
+			if(leg && aboveThreshold(lepton, leg))
+			{
+				validLegs.insert(leg);
+				efficiencies.emplace(leg, getCachedTriggerLegEfficiencies(lepton, runNumber, leg, success));
+			}
+			else efficiencies.emplace(leg, 0.);
+		}
+		if(!validLegs.size()) continue;
+		
+		const auto sortedLegs = m_parent->getSortedLegs(lepton, validLegs, success);
+		if(!success) return false;
+		std::size_t loosestLeg = sortedLegs[0];
+		std::size_t secondLoosestLeg = validLegs.size()>=2 ? sortedLegs[1] : 0;
+		std::size_t secondTightestLeg = validLegs.size()>=3 ? sortedLegs[2] : 0;
+		std::size_t tightestLeg = validLegs.size()>=4 ? sortedLegs[3]: 0;
+		std::size_t lambda124 = (loosestLeg!=trig2Lasym(1))? loosestLeg : secondLoosestLeg;
+		std::size_t lambda134 = (loosestLeg!=trig2Lasym(0))? loosestLeg : secondLoosestLeg;
+		std::size_t lambda234 = (loosestLeg!=trig2Lsym())? loosestLeg : secondLoosestLeg;
+		std::size_t lambda14 = (lambda124!=trig2Lasym(0))? lambda124 : (lambda134!=trig2Lasym(1))? lambda134 : secondTightestLeg;
+		std::size_t lambda24 = (lambda124!=trig2Lsym())? lambda124 : (lambda234!=trig2Lasym(1))? lambda234 : secondTightestLeg;
+		std::size_t lambda34 = (lambda134!=trig2Lsym())? lambda134 : (lambda234!=trig2Lasym(0))? lambda234 : secondTightestLeg;
+		std::size_t tau13=0, tau12=0, tau23=0;
+		if(loosestLeg==trig2Lsym() || loosestLeg==trig2Lasym(0)) tau12 = (loosestLeg==trig2Lsym())? trig2Lasym(0) : trig2Lsym();
+		else if(secondLoosestLeg==trig2Lsym() || secondLoosestLeg==trig2Lasym(0)) tau12 = (secondLoosestLeg==trig2Lsym())? trig2Lasym(0) : trig2Lsym();
+		else if(secondTightestLeg==trig2Lsym() || secondTightestLeg==trig2Lasym(0)) tau12 = (secondTightestLeg==trig2Lsym())? trig2Lasym(0) : trig2Lsym();
+		else if(tightestLeg==trig2Lsym() || tightestLeg==trig2Lasym(0)) tau12 = (tightestLeg==trig2Lsym())? trig2Lasym(0) : trig2Lsym();
+		if(loosestLeg==trig2Lsym() || loosestLeg==trig2Lasym(1)) tau13 = (loosestLeg==trig2Lsym())? trig2Lasym(1) : trig2Lsym();
+		else if(secondLoosestLeg==trig2Lsym() || secondLoosestLeg==trig2Lasym(1)) tau13 = (secondLoosestLeg==trig2Lsym())? trig2Lasym(1) : trig2Lsym();
+		else if(secondTightestLeg==trig2Lsym() || secondTightestLeg==trig2Lasym(1)) tau13 = (secondTightestLeg==trig2Lsym())? trig2Lasym(1) : trig2Lsym();
+		else if(tightestLeg==trig2Lsym() || tightestLeg==trig2Lasym(1)) tau13 = (tightestLeg==trig2Lsym())? trig2Lasym(1) : trig2Lsym();
+		if(loosestLeg==trig2Lasym(0) || loosestLeg==trig2Lasym(1)) tau23 = (loosestLeg==trig2Lasym(0))? trig2Lasym(1) : trig2Lasym(0);
+		else if(secondLoosestLeg==trig2Lasym(0) || secondLoosestLeg==trig2Lasym(1)) tau23 = (secondLoosestLeg==trig2Lasym(0))? trig2Lasym(1) : trig2Lasym(0);
+		else if(secondTightestLeg==trig2Lasym(0) || secondTightestLeg==trig2Lasym(1)) tau23 = (secondTightestLeg==trig2Lasym(0))? trig2Lasym(1) : trig2Lasym(0);
+		else if(tightestLeg==trig2Lasym(0) || tightestLeg==trig2Lasym(1)) tau23 = (tightestLeg==trig2Lasym(0))? trig2Lasym(1) : trig2Lasym(0);
+
+		/// can't use tightestLeg==trig2Lsym because it might also be 0
+		globalEfficiencies = globalEfficiencies*~efficiencies[loosestLeg] + efficiencies[loosest1lepLeg]
+			+ (efficiencies[tau13] - efficiencies[secondTightestLeg])*~singleInefficiencies[0]
+			+ (efficiencies[tau12] - efficiencies[secondTightestLeg])*~singleInefficiencies[1]
+			+ (efficiencies[tau23] - efficiencies[secondTightestLeg])*efficiencies2Lsym[2];
+		if(loosestLeg==trig2Lsym()) globalEfficiencies += (efficiencies[trig2Lsym()]-efficiencies[secondLoosestLeg])*efficiencies2Lasym;
+		else if(loosestLeg==trig2Lasym(1)) globalEfficiencies += (efficiencies[trig2Lasym(1)]-efficiencies[secondLoosestLeg])*efficiencies2Lsym[0];
+		else if(loosestLeg==trig2Lasym(0)) globalEfficiencies += (efficiencies[trig2Lasym(0)]-efficiencies[secondLoosestLeg])*efficiencies2Lsym[1];
+		if(secondTightestLeg && tightestLeg==loosest1lepLeg) /// this works because loosest1lepLeg is 0 if not on plateau...
+			globalEfficiencies += (efficiencies[secondTightestLeg]-efficiencies[tightestLeg])*~singleInefficiencies[2];
+
+		efficiencies2Lasym = ~efficiencies[loosestLeg]*efficiencies2Lasym + efficiencies[lambda14];
+		if(loosestLeg==trig2Lasym(0) || loosestLeg==trig2Lasym(1))
+		{
+			/// note: secondLoosestLeg is valid because the loosest leg is either trig2Lasym(0) or trig2Lasym(1)
+			efficiencies2Lasym += (efficiencies[loosestLeg]-efficiencies[secondLoosestLeg])*~singleInefficiencies[loosestLeg==trig2Lasym(0)]
+				+ (efficiencies[secondLoosestLeg]-efficiencies[lambda14])*~singleInefficiencies[2];
+		}
+		efficiencies2Lsym[0] = ~efficiencies[lambda124]*efficiencies2Lsym[0] + efficiencies[lambda24];
+		efficiencies2Lsym[1] = ~efficiencies[lambda134]*efficiencies2Lsym[1] + efficiencies[lambda34];
+		efficiencies2Lsym[2] = ~efficiencies[loosestLeg]*efficiencies2Lsym[2] + efficiencies[lambda234];
+		if(lambda124==trig2Lsym()) efficiencies2Lsym[0] += (efficiencies[trig2Lsym()]-efficiencies[lambda24])*~singleInefficiencies[0];
+		if(lambda134==trig2Lsym()) efficiencies2Lsym[1] += (efficiencies[trig2Lsym()]-efficiencies[lambda34])*~singleInefficiencies[1];
+		if(loosestLeg==trig2Lsym()) efficiencies2Lsym[2] += (efficiencies[trig2Lsym()]-efficiencies[lambda234])*~singleInefficiencies[2];
+		singleInefficiencies[0] *= ~efficiencies[lambda124];
+		singleInefficiencies[1] *= ~efficiencies[lambda134];
+		singleInefficiencies[2] *= ~efficiencies[loosestLeg];
+	}
+	return success;
+}
+
+///
+/// One symmetric trilepton trigger
+///
+template<typename Trig3Lsym>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber, const Trig3Lsym trig, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig3Lsym::is3Lsym(), bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_One3L() at line " << __LINE__);
+	globalEfficiencies = {0.};
+	Efficiencies singleInefficiencies{1.}, efficiencies2L{0.};
+	bool success = true;
+	for(auto& lepton : leptons)
+	{
+		if(trig.irrelevantFor(lepton) || !aboveThreshold(lepton, trig())) continue;
+		auto efficiencies = getCachedTriggerLegEfficiencies(lepton, runNumber, trig(), success);
+		globalEfficiencies = ~efficiencies*globalEfficiencies + efficiencies*efficiencies2L;
+		efficiencies2L = ~efficiencies*efficiencies2L + efficiencies*~singleInefficiencies;
+		singleInefficiencies *= ~efficiencies;
+	}
+	return success;
+}
+
+///
+/// One half-symmetric trilepton trigger
+///
+template<typename Trig3Lhalfsym>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber, const Trig3Lhalfsym trig, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig3Lhalfsym::is3Lhalfsym(), bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_One3L() at line " << __LINE__);
+	if(trig.symmetric()) return globalEfficiency(leptons, runNumber, trig.to_symmetric(), globalEfficiencies);
+	globalEfficiencies = {0.};
+	Efficiencies singleInefficiencies[2] = {{1.}, {1.}}, twoSingleInefficiencies{1.};
+	Efficiencies efficiencies2Lsym{0.}, efficiencies2Lasym{0.}, efficiencies2L2L{0.};
+	bool success = true;
+	for(auto& lepton : leptons)
+	{
+		if(trig.irrelevantFor(lepton)) continue;
+		Efficiencies efficiencies[2] = {{0.}, {0.}};
+		const int asym = 0, sym = 1;
+		int loosestLeg;
+		if(aboveThreshold(lepton, trig.asymLeg()))
+		{
+			efficiencies[asym] = getCachedTriggerLegEfficiencies(lepton, runNumber, trig.asymLeg(), success);
+			if(aboveThreshold(lepton, trig.symLeg()))
+			{
+				efficiencies[sym] = getCachedTriggerLegEfficiencies(lepton, runNumber, trig.symLeg(), success);
+				loosestLeg = m_parent->getLoosestLeg(lepton, trig.asymLeg(), trig.symLeg(), success)==trig.asymLeg()? asym : sym;
+			}
+			else loosestLeg = asym;
+		}
+		else if(aboveThreshold(lepton, trig.symLeg()))
+		{
+			efficiencies[sym] = getCachedTriggerLegEfficiencies(lepton, runNumber, trig.symLeg(), success);
+			loosestLeg = sym;
+		}
+		else continue;
+		Efficiencies delta = efficiencies[asym] - efficiencies[sym];
+		if(loosestLeg == asym)
+		{
+			globalEfficiencies = ~efficiencies[asym]*globalEfficiencies + efficiencies[sym]*efficiencies2L2L + delta*efficiencies2Lsym;
+			efficiencies2L2L = ~efficiencies[asym]*efficiencies2L2L + efficiencies[sym]*~twoSingleInefficiencies + delta*~singleInefficiencies[sym];
+			efficiencies2Lasym = ~efficiencies[asym]*efficiencies2Lasym + efficiencies[sym]*~twoSingleInefficiencies + delta*~singleInefficiencies[sym];
+		}
+		else
+		{
+			globalEfficiencies = ~efficiencies[sym]*globalEfficiencies + efficiencies[asym]*efficiencies2L2L - delta*efficiencies2Lasym;
+			efficiencies2L2L = ~efficiencies[sym]*efficiencies2L2L + efficiencies[sym]*~twoSingleInefficiencies;
+			efficiencies2Lasym = ~efficiencies[sym]*efficiencies2Lasym + efficiencies[asym]*~twoSingleInefficiencies - delta*~singleInefficiencies[asym];
+		}
+		efficiencies2Lsym = ~efficiencies[sym]*efficiencies2Lsym + efficiencies[sym]*~singleInefficiencies[sym];
+		twoSingleInefficiencies *= ~efficiencies[loosestLeg];
+		singleInefficiencies[sym] *= ~efficiencies[sym];
+		singleInefficiencies[asym] *= ~efficiencies[asym];
+	}
+	return success;
+}
+
+///
+/// One dilepton trigger + one mixed-flavour dilepton trigger
+///
+template<typename Trig2L, typename Trig2Lmix>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber, const Trig2L trig2L, const Trig2Lmix trig2Lmix, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig2L::is2Lnomix()
+						&& Trig2Lmix::is2Lmix()
+						&& (Trig2Lmix::object1()==Trig2L::object() || Trig2Lmix::object2()==Trig2L::object()),
+		bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_Two2L() at line " << __LINE__);
+	Efficiencies efficiencies1L, efficiencies2L, efficiencies2Lor1L;
+	bool success = globalEfficiency(leptons, runNumber, trig2Lmix.template antiside<Trig2L>(), efficiencies1L);
+	success = success && globalEfficiency(leptons, runNumber, trig2L, efficiencies2L);
+	success = success && globalEfficiency(leptons, runNumber, trig2L, trig2Lmix.template side<Trig2L>(), efficiencies2Lor1L);
+	globalEfficiencies = efficiencies2L*~efficiencies1L + efficiencies1L*efficiencies2Lor1L;
+	return success;
+}
+
+///
+/// Combinaisons with 3 dilepton triggers including one mixed-flavour, and one sym./asym. dilepton for each flavour
+///
+template<typename Trig2L_obj1, typename Trig2L_obj2, typename Trig2Lmix>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber,
+		const Trig2L_obj1 trig2L_obj1, const Trig2L_obj2 trig2L_obj2, const Trig2Lmix trig2Lmix, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig2Lmix::is2Lmix()
+						&& Trig2L_obj1::is2Lnomix() && Trig2L_obj1::object() == Trig2Lmix::object1()
+						&& Trig2L_obj2::is2Lnomix() && Trig2L_obj2::object() == Trig2Lmix::object2(),
+						
+		bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_Three2L() at line " << __LINE__);
+	Efficiencies efficiencies2L[2] = {{0.}, {0.}}, efficiencies2Lor1L[2] = {{0.}, {0.}};
+	bool success = true;
+	if(trig2L_obj1)
+	{
+		success = success && globalEfficiency(leptons, runNumber, trig2L_obj1, efficiencies2L[0]);
+		if(trig2Lmix) success = success && globalEfficiency(leptons, runNumber, trig2L_obj1, trig2Lmix.template side<Trig2L_obj1>(), efficiencies2Lor1L[0]);
+		else efficiencies2Lor1L[0] = efficiencies2L[0];
+	}
+	else if(trig2Lmix) success = success && globalEfficiency(leptons, runNumber, trig2Lmix.template side<Trig2L_obj1>(), efficiencies2Lor1L[0]);
+	if(trig2L_obj2)
+	{
+		success = success && globalEfficiency(leptons, runNumber, trig2L_obj2, efficiencies2L[1]);
+		if(trig2Lmix) success = success && globalEfficiency(leptons, runNumber, trig2L_obj2, trig2Lmix.template side<Trig2L_obj2>(), efficiencies2Lor1L[1]);
+		else efficiencies2Lor1L[1] = efficiencies2L[1];
+	}
+	else if(trig2Lmix) success = success && globalEfficiency(leptons, runNumber, trig2Lmix.template side<Trig2L_obj2>(), efficiencies2Lor1L[1]);
+	globalEfficiencies = efficiencies2L[0]*~efficiencies2Lor1L[1] +  efficiencies2L[1]*~efficiencies2Lor1L[0] + efficiencies2Lor1L[0]*efficiencies2Lor1L[1];
+	return success;
+}
+
+///
+/// Same combinaisons with 3 dilepton triggers, + single-lepton triggers
+///
+template<typename Trig2L_obj1, typename Trig2L_obj2, typename Trig2Lmix, typename Trig1L_obj1, typename Trig1L_obj2>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber, const Trig2L_obj1 trig2L_obj1, const Trig2L_obj2 trig2L_obj2,
+		const Trig2Lmix trig2Lmix, const flat_set<Trig1L_obj1>& trigs1L_obj1, const flat_set<Trig1L_obj2>& trigs1L_obj2, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig2Lmix::is2Lmix()
+						&& Trig2L_obj1::is2Lnomix() && Trig2L_obj1::object()==Trig2Lmix::object1()
+						&& Trig2L_obj2::is2Lnomix() && Trig2L_obj2::object()==Trig2Lmix::object2()
+						&& Trig1L_obj1::is1L() && Trig1L_obj1::object()==Trig2Lmix::object1()
+						&& Trig1L_obj2::is1L() && Trig1L_obj2::object()==Trig2Lmix::object2(),
+						
+		bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_Three2LSeveral1L() at line " << __LINE__);
+	Efficiencies efficiencies[4];
+	bool success = true;
+	if(trig2L_obj1) success = success && globalEfficiency(leptons, runNumber, trig2L_obj1, trigs1L_obj1, efficiencies[0]);
+	else success = success && globalEfficiency(leptons, runNumber, trigs1L_obj1, efficiencies[0]);
+	if(trig2L_obj2) success = success && globalEfficiency(leptons, runNumber, trig2L_obj2, trigs1L_obj2, efficiencies[1]);
+	else success = success && globalEfficiency(leptons, runNumber, trigs1L_obj2, efficiencies[1]);
+	if(trig2Lmix && !trig2Lmix.hiddenBy(trigs1L_obj1))
+	{
+		auto t = trig2Lmix.addTo(trigs1L_obj1);
+		if(trig2L_obj1) success = success && globalEfficiency(leptons, runNumber, trig2L_obj1, t, efficiencies[2]);
+		else success = success && globalEfficiency(leptons, runNumber, t, efficiencies[2]);
+	}
+	else efficiencies[2] = efficiencies[0];
+	if(trig2Lmix && !trig2Lmix.hiddenBy(trigs1L_obj2))
+	{
+		auto t = trig2Lmix.addTo(trigs1L_obj2);
+		if(trig2L_obj2) success = success && globalEfficiency(leptons, runNumber, trig2L_obj2, t, efficiencies[3]);
+		else success = success && globalEfficiency(leptons, runNumber, t, efficiencies[3]);
+	}
+	else efficiencies[3] = efficiencies[1];
+	globalEfficiencies = Efficiencies(1.) - ~efficiencies[0]*~efficiencies[1] + (efficiencies[2]-efficiencies[0])*(efficiencies[3]-efficiencies[1]);
+	return success;
+}
+
+///
+/// Six dilepton triggers (two mixed-flavour, two sym., two asym./sym.) for two object types
+///
+template<typename Trig2L_obj1, typename Trig2Lsym_obj1, typename Trig2L_obj2, typename Trig2Lsym_obj2,
+	typename Trig2Lmix, typename Trig1L_obj1, typename Trig1L_obj2>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber,
+		const Trig2L_obj1 trig2L_obj1, const Trig2Lsym_obj1 trig2Lsym_obj1, const Trig2L_obj2 trig2L_obj2, const Trig2Lsym_obj2 trig2Lsym_obj2,
+		const Trig2Lmix trig2Lmix1, const Trig2Lmix trig2Lmix2, 
+		const flat_set<Trig1L_obj1>& trigs1L_obj1, const flat_set<Trig1L_obj2>& trigs1L_obj2, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig2Lmix::is2Lmix()
+						&& Trig2L_obj1::is2Lnomix() && Trig2L_obj1::object()==Trig2Lmix::object1()
+						&& Trig2L_obj2::is2Lnomix() && Trig2L_obj2::object()==Trig2Lmix::object2()
+						&& Trig2Lsym_obj1::is2Lsym() && Trig2Lsym_obj1::object()==Trig2Lmix::object1()
+						&& Trig2Lsym_obj2::is2Lsym() && Trig2Lsym_obj2::object()==Trig2Lmix::object2()
+						&& Trig1L_obj1::is1L() && Trig1L_obj1::object()==Trig2Lmix::object1()
+						&& Trig1L_obj2::is1L() && Trig1L_obj2::object()==Trig2Lmix::object2(),
+		bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_Six2LSeveral1L() at line " << __LINE__);
+	
+	auto singleObjectFactor = [=](auto trig2L, auto trig2Lsym, auto& trigs1L, Efficiencies (&efficiencies)[4]) -> bool
+	{
+		auto eval_for = [=](const auto& trigs1L_extended, Efficiencies& eff) -> bool
+		{
+			if(trig2L && trig2Lsym) return this->globalEfficiency(leptons, runNumber, trig2L, trig2Lsym, trigs1L_extended, eff);
+			else if(trig2L) return this->globalEfficiency(leptons, runNumber, trig2L, trigs1L_extended, eff);
+			else if(trig2Lsym) return this->globalEfficiency(leptons, runNumber, trig2Lsym, trigs1L_extended, eff);
+			else return this->globalEfficiency(leptons, runNumber, trigs1L_extended, eff);
+		};
+	
+		bool success = eval_for(trigs1L, efficiencies[0]);
+		if(trig2Lmix1) success = success && eval_for(trig2Lmix1.addTo(trigs1L), efficiencies[1]);
+		else efficiencies[1] = efficiencies[0];
+		if(trig2Lmix2)
+		{
+			auto t = trig2Lmix2.addTo(trigs1L);
+			success = success && eval_for(t, efficiencies[2]);
+			if(trig2Lmix1) success && eval_for(trig2Lmix1.addTo(t), efficiencies[3]);
+			else efficiencies[3] = efficiencies[2];
+		}
+		else
+		{
+			efficiencies[2] = efficiencies[0];
+			efficiencies[3] = efficiencies[1];
+		}
+		return success;
+	};
+	
+	Efficiencies efficiencies1[4], efficiencies2[4];
+	bool success = singleObjectFactor(trig2L_obj1, trig2Lsym_obj1, trigs1L_obj1, efficiencies1)
+		&& singleObjectFactor(trig2L_obj2, trig2Lsym_obj2, trigs1L_obj2, efficiencies2);
+	globalEfficiencies = Efficiencies(1.) - ~efficiencies1[0]*~efficiencies2[0] + (efficiencies1[1]-efficiencies1[0])*(efficiencies2[1]-efficiencies2[0])
+		+ (efficiencies1[2]-efficiencies1[0])*(efficiencies2[2]-efficiencies2[0]) 
+		- (efficiencies1[0]-efficiencies1[1]-efficiencies1[2]+efficiencies1[3])*(efficiencies2[0]-efficiencies2[1]-efficiencies2[2]+efficiencies2[3]);
+	return success;
+}
+
+///
+/// One mixed-flavour trilepton trigger
+///
+template<typename Trig3Lmix>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber, const Trig3Lmix trig, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig3Lmix::is3Lmix(), bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_One3L() at line " << __LINE__);
+	Efficiencies efficiencies[2] = {{0.}, {0.}};
+	bool success = globalEfficiency(leptons, runNumber, trig.template side<Trig3Lmix::object1()>(), efficiencies[0])
+		&& globalEfficiency(leptons, runNumber, trig.template side<Trig3Lmix::object2()>(), efficiencies[1]);
+	globalEfficiencies = efficiencies[0]*efficiencies[1];
+	return success;
+}
+
+///
+/// Two complementary mixed-flavour trilepton triggers
+///
+template<typename Trig3Lmix1, typename Trig3Lmix2>
+auto Calculator::globalEfficiency(const LeptonList& leptons, unsigned runNumber, const Trig3Lmix1 trig1, const Trig3Lmix2 trig2, Efficiencies& globalEfficiencies)
+	-> std::enable_if_t<Trig3Lmix1::is3Lmix() 
+					&& Trig3Lmix2::is3Lmix() 
+					&& Trig3Lmix1::object1() == Trig3Lmix2::object2()
+					&& Trig3Lmix1::object2() == Trig3Lmix2::object1(), 
+		bool>
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_Two3L() at line " << __LINE__);
+	Efficiencies efficiencies[6] = {{0.}, {0.}, {0.}, {0.}, {0.}, {0.}};
+	auto trig2La = trig1.template side<Trig3Lmix1::object1()>();
+	auto trig1La = trig2.template side<Trig3Lmix2::object2()>();
+	bool success = globalEfficiency(leptons, runNumber, trig1La, efficiencies[0])
+		&& globalEfficiency(leptons, runNumber, trig2La, efficiencies[1]);
+	if(!trig2La.hiddenBy(trig1La)) success = success && globalEfficiency(leptons, runNumber, trig2La, trig1La, efficiencies[2]);
+	else efficiencies[2] = efficiencies[0];
+	auto trig2Lb = trig2.template side<Trig3Lmix2::object1()>();
+	auto trig1Lb = trig1.template side<Trig3Lmix1::object2()>();
+	success = success && globalEfficiency(leptons, runNumber, trig1Lb, efficiencies[3])
+		&& globalEfficiency(leptons, runNumber, trig2Lb, efficiencies[4]);
+	if(!trig2Lb.hiddenBy(trig1Lb)) success = success && globalEfficiency(leptons, runNumber, trig2Lb, trig1Lb, efficiencies[5]);
+	else efficiencies[5] = efficiencies[3];
+	globalEfficiencies = efficiencies[0]*efficiencies[4] + efficiencies[3]*efficiencies[1] 
+		+ (efficiencies[2]-efficiencies[0]-efficiencies[1])*(efficiencies[3]+efficiencies[4]-efficiencies[5]);
+	return success;
+}
+
+bool Calculator::globalEfficiency_Factorized2(const LeptonList& leptons, unsigned runNumber, GlobEffFunc func1, GlobEffFunc func2, Efficiencies& globalEfficiencies)
+{
+	Efficiencies efficiencies[2];
+	if(!func1(this, leptons, runNumber, efficiencies[0])) return false;
+	if(!func2(this, leptons, runNumber, efficiencies[1])) return false;
+	globalEfficiencies = ~(~efficiencies[0] * ~efficiencies[1]);
+	return true;
+}
+
+bool Calculator::globalEfficiency_Factorized3(const LeptonList& leptons, unsigned runNumber, GlobEffFunc func1, GlobEffFunc func2, GlobEffFunc func3, Efficiencies& globalEfficiencies)
+{
+	Efficiencies efficiencies[3];
+	if(!func1(this, leptons, runNumber, efficiencies[0])) return false;
+	if(!func2(this, leptons, runNumber, efficiencies[1])) return false;
+	if(!func3(this, leptons, runNumber, efficiencies[2])) return false;
+	globalEfficiencies = ~(~efficiencies[0] * ~efficiencies[1]* ~efficiencies[2]);
+	return true;
+}
+
+bool Calculator::fillListOfLegsFor(const Lepton& lepton, const std::vector<TrigDef>& triggers, flat_set<std::size_t>& validLegs) const
+{
+	validLegs.clear();
+	for(auto& trig : triggers)
+	{
+		TriggerProperties tp(trig);
+		if(!tp.valid())
+		{
+			ATH_MSG_ERROR("Unrecognized trigger type " << trig.type);
+			return false;
+		}
+		auto end = tp.cend(lepton.type());
+		for(auto itr=tp.cbegin(lepton.type()); itr!=end; ++itr)if(aboveThreshold(lepton, *itr)) validLegs.emplace(*itr);
+	}
+	return true;
+}
+
+bool Calculator::canTriggerBeFired(const TrigDef& trig, const std::vector<flat_set<std::size_t> >& firedLegs) const
+{
+	static_assert(std::tuple_size<decltype(trig.leg)>::value == 3, "extend Calculator::canTriggerBeFired() implementation to support triggers with >= 4 legs");
+	int n0=0, n1=0, n0min = 1 + (trig.leg[1]!=0)*(trig.leg[0]!=trig.leg[1]?-5:1) + (trig.leg[2]!=0)*(trig.leg[0]!=trig.leg[2]?-9:1);
+	if(n0min>0 || !trig.leg[2])
+	{
+		for(auto& legs : firedLegs)
+		{
+			bool fire0 = legs.count(trig.leg[0]);
+			if(n0min <= 0) /// Asymmetric dilepton triggers
+			{
+				if(n1 && fire0) return true;
+				if(legs.count(trig.leg[1]))
+				{
+					if(n0) return true;
+					++n1;
+				}
+				if(fire0) ++n0;
+			}
+			else if(fire0 && ++n0>=n0min) return true; /// Single-lepton and symmetric di/trilepton triggers 
+		}
+	}
+	else /// Trilepton triggers (except fully-symmetric ones that are addressed above)
+	{
+		auto end = firedLegs.end();
+		for(auto legs0=firedLegs.begin();legs0!=end;++legs0)
+		{
+			for(int i=0;i<3;++i)
+			{
+				if(!legs0->count(trig.leg[i])) continue;
+				for(auto legs1=legs0+1;legs1!=end;++legs1)
+				{
+					for(int j=1;j<3;++j)
+					{
+						if(!legs1->count(trig.leg[(i+j)%3])) continue;
+						for(auto legs2=legs1+1;legs2!=end;++legs2)
+						{
+							if(legs2->count(trig.leg[(i+3-j)%3])) return true;
+						}
+					}
+				}
+			}
+		}
+	}
+	return false;
+}
+
+bool Calculator::globalEfficiency_Toys(const LeptonList& leptons, unsigned runNumber,
+	const std::vector<TrigDef>& triggers, Efficiencies& globalEfficiencies)
+{
+	ATH_MSG_DEBUG("Entered Calculator::globalEfficiency_Toys() at line " << __LINE__);
+	globalEfficiencies = {0.};
+	if(m_parent->m_numberOfToys <= 0)
+	{
+		ATH_MSG_ERROR("The specified number of toys is <= 0");
+		return false;
+	}
+	std::map<const Lepton*, std::vector<std::pair<std::size_t, Efficiencies> > > leptonEfficiencies;
+	for(auto& lepton : leptons)
+	{
+		flat_set<std::size_t> validLegs;
+		if(!fillListOfLegsFor(lepton, triggers, validLegs)) return false;
+		auto& efficiencies = leptonEfficiencies[&lepton];
+		const int nLegs = validLegs.size();
+		if(nLegs)
+		{
+			bool success = true;
+			for(std::size_t leg : m_parent->getSortedLegs(lepton, validLegs, success))
+			{
+				efficiencies.emplace_back(leg, getCachedTriggerLegEfficiencies(lepton, runNumber, leg, success));
+			}
+			if(!success) return false;
+		}
+	}
+	unsigned long seed;
+	if(!m_parent->m_useInternalSeed)
+	{
+		if(!m_parent->retrieveEventNumber(seed))
+		{
+			ATH_MSG_WARNING("Will use internal seed instead of event number");
+			seed = m_parent->m_seed++;
+		}
+	}
+	else seed = m_parent->m_seed++;
+	std::mt19937_64 randomEngine(seed);
+	std::uniform_real_distribution<double> uniformPdf(0., 1.);
+	std::vector<flat_set<std::size_t> > firedLegs(leptonEfficiencies.size());
+	unsigned long nPassed[2] = {0, 0};
+	for(unsigned long toy=0;toy<m_parent->m_numberOfToys;++toy)
+	{
+		for(int step=0;step<2;++step) /// 0 = data, 1 = MC
+		{
+			auto legs = firedLegs.begin();
+			for(auto& kv : leptonEfficiencies)
+			{
+				legs->clear();
+				double x = uniformPdf(randomEngine);
+				for(auto& p : kv.second)
+				{
+					if(x < (step? p.second.mc(): p.second.data())) legs->emplace(p.first);
+				}
+				++legs;
+			}
+			for(auto& trig : triggers)
+			{
+				if(!canTriggerBeFired(trig, firedLegs)) continue;
+				++nPassed[step];
+				break;
+			}
+		}
+	}
+	globalEfficiencies.data() = double(nPassed[0]) / double(m_parent->m_numberOfToys);
+	globalEfficiencies.mc() = double(nPassed[1]) / double(m_parent->m_numberOfToys);
+	return true;
+}
+
+Calculator::Helper::Helper(const std::vector<TrigDef>& defs) :
+	m_formula(nullptr), m_defs(defs)
+{
+}
+
+bool Calculator::Helper::duplicates() const
+{
+	for(auto itr1=m_defs.cbegin(); itr1!=m_defs.cend(); ++itr1)
+		for(auto itr2=itr1+1; itr2!=m_defs.cend(); ++itr2)
+			if(itr1->type==itr2->type && itr1->leg==itr2->leg) return true;
+	return false;
+}
+
+namespace TrigGlobEffCorr /// the template specializations below must be enclosed in this namespace
+{
+	template<typename T>
+	struct TrigGlobEffCorr::Calculator::Helper::BindPackedParam
+	{
+		using TrigType = T;
+		using ArgType = std::add_const_t<T>;
+		static constexpr bool multiple() { return false; }
+		static constexpr bool optional() { return false; }
+		static void add(T& arg, ImportData::TrigDef& def) { arg.setDefinition(def); }
+		static constexpr bool valid(const T& arg) { return bool(arg); }
+	};
+
+	template<typename T>
+	struct TrigGlobEffCorr::Calculator::Helper::BindPackedParam<flat_set<T>>
+	{
+		using TrigType = T;
+		using ArgType = const flat_set<T>&;
+		static constexpr bool multiple() { return true; }
+		static constexpr bool optional() { return false; }
+		static void add(flat_set<T>& arg, ImportData::TrigDef& def) { arg.emplace().first->setDefinition(def); }
+		static constexpr bool valid(const flat_set<T>& arg) { return arg.size(); }
+	};
+
+	template<typename T>
+	struct TrigGlobEffCorr::Calculator::Helper::BindPackedParam<Calculator::Helper::Optional<T>>
+	{
+		using TrigType = typename BindPackedParam<T>::TrigType;
+		using ArgType = typename BindPackedParam<T>::ArgType;
+		static constexpr bool multiple() { return BindPackedParam<T>::multiple(); }
+		static constexpr bool optional() { return true; }
+		static void add(std::remove_cv_t<std::remove_reference_t<ArgType>>& arg, ImportData::TrigDef def) { BindPackedParam<T>::add(arg, def); }
+		static constexpr bool valid(ArgType& arg) { return BindPackedParam<T>::valid(arg); }
+	};
+}
+
+template<typename Param>
+auto Calculator::Helper::extract() 
+{
+	std::remove_cv_t<std::remove_reference_t<typename Param::ArgType>> trigs;
+	for(auto& def : m_defs)
+	{
+		if(def.used || def.type!=Param::TrigType::type()) continue;
+		def.used = true;
+		Param::add(trigs, def);
+		if(!Param::multiple()) break;
+	}
+	if(!Param::optional() && !Param::valid(trigs)) throw NoSuchTrigger();
+	return trigs;
+}
+
+template<typename... Trigs>
+bool Calculator::Helper::bindFunction()
+{
+	for(auto& def : m_defs) def.used = false;
+	using fnptr = bool(Calculator::*)(const LeptonList&, unsigned, typename BindPackedParam<Trigs>::ArgType..., Efficiencies&);
+	try
+	{
+		m_formula = std::bind<fnptr>(&Calculator::globalEfficiency, ::_1, ::_2, ::_3, extract<BindPackedParam<Trigs>>()..., ::_4);
+		if(std::all_of(m_defs.cbegin(), m_defs.cend(), [](auto& def){return def.used;})) return true;
+	}
+	catch(NoSuchTrigger){}
+	m_formula = nullptr;
+	return false;
+}
+
+template<TriggerType object_flag>
+bool Calculator::Helper::findAndBindFunction() /// for combinations with a single flavour present
+{
+	using A = TriggerClass<object_flag>;
+	using A1L = flat_set<typename A::T_1>;
+	using A_2sym = typename A::T_2sym;
+	using A_2asym = typename A::T_2asym;
+	if(!m_n2L && !m_n3L)
+	{
+		return bindFunction<typename A::T_1>() || bindFunction<A1L>();
+	}
+	else if(m_n2L==1 && !m_n3L)
+	{
+		return bindFunction<A_2sym>() || bindFunction<A_2asym>()
+			|| bindFunction<A_2sym, A1L>() || bindFunction<A_2asym, A1L>();
+	}
+	else if(m_n2L==2 && !m_n3L)
+	{
+		return bindFunction<A_2sym, A_2sym, Optional<A1L>>() || bindFunction<A_2asym, A_2sym, Optional<A1L>>();
+	}
+	else if(m_n3L==1 && !m_n1L && !m_n2L)
+	{
+		return bindFunction<typename A::T_3sym>() || bindFunction<typename A::T_3halfsym>();
+	}
+	return false;
+}
+
+template<TriggerType object_flag1, TriggerType object_flag2>
+bool Calculator::Helper::findAndBindFunction() /// for combinations with two flavours present
+{
+	/// this only deals with the presence of mixed triggers
+	using A = TriggerClass<object_flag1>;
+	using B = TriggerClass<object_flag2>;
+	using AB = TriggerClass<object_flag1, object_flag2>;
+	using A_1 = typename A::T_1;
+	using B_1 = typename B::T_1;		
+	using OA1L = Optional<flat_set<A_1>>;
+	using OB1L = Optional<flat_set<B_1>>;
+	using A_2sym = typename A::T_2sym;
+	using B_2sym = typename B::T_2sym;
+	using A_2asym = typename A::T_2asym;
+	using B_2asym = typename B::T_2asym;
+	using AB_1_1 = typename AB::T_1_1;
+	
+	/// checked if triggers can be factorized = no mixed trigger in the combination.
+	if(m_n1L>0 && !m_n2L && !m_n3L)
+	{
+		return bindFunction<A_1, B_1>() || bindFunction<flat_set<A_1>, flat_set<B_1>>();
+	}
+	else if(m_n2L==1 && !m_n3L) /// one dilepton trigger (+ single-lepton triggers)
+	{
+		return bindFunction<AB_1_1>() || bindFunction<AB_1_1, flat_set<A_1>, flat_set<B_1>>();
+	}
+	else if(m_n2L>=2 && m_n2L<=6 && !m_n3L) /// several dilepton triggers (+ single-lepton triggers)
+	{
+		return 
+			/// 2 dilepton triggers
+			   bindFunction<A_2sym, AB_1_1>() || bindFunction<A_2asym, AB_1_1>()
+			|| bindFunction<B_2sym, AB_1_1>() || bindFunction<B_2asym, AB_1_1>()
+			/// 3 dilepton triggers
+			|| bindFunction<Optional<A_2sym>, Optional<B_2sym>, Optional<AB_1_1>, OA1L, OB1L>()
+			|| bindFunction<Optional<A_2asym>, Optional<B_2sym>, Optional<AB_1_1>, OA1L, OB1L>()
+			|| bindFunction<Optional<A_2sym>, Optional<B_2asym>, Optional<AB_1_1>, OA1L, OB1L>()
+			|| bindFunction<Optional<A_2asym>, Optional<B_2asym>, Optional<AB_1_1>, OA1L, OB1L>()
+			/// 6 dilepton triggers
+			|| bindFunction<Optional<A_2sym>, Optional<A_2sym>, Optional<B_2sym>, Optional<B_2sym>, Optional<AB_1_1>, Optional<AB_1_1>, OA1L, OB1L>()
+			|| bindFunction<Optional<A_2asym>, Optional<A_2sym>, Optional<B_2sym>, Optional<B_2sym>, Optional<AB_1_1>, Optional<AB_1_1>, OA1L, OB1L>()
+			|| bindFunction<Optional<A_2sym>, Optional<A_2sym>, Optional<B_2asym>, Optional<B_2sym>, Optional<AB_1_1>, Optional<AB_1_1>, OA1L, OB1L>()
+			|| bindFunction<Optional<A_2asym>, Optional<A_2sym>, Optional<B_2asym>, Optional<B_2sym>, Optional<AB_1_1>, Optional<AB_1_1>, OA1L, OB1L>();
+	}
+	else if(m_n3L==1 && !m_n2L && !m_n1L) /// one mixed trilepton trigger
+	{
+		return bindFunction<typename AB::T_2sym_1>() || bindFunction<typename AB::T_1_2sym>()
+			||  bindFunction<typename AB::T_2asym_1>() || bindFunction<typename AB::T_1_2asym>();
+	}
+	else if(m_n3L==2 && !m_n2L && !m_n1L) /// two mixed trilepton triggers
+	{
+		return bindFunction<typename AB::T_2sym_1, typename AB::T_1_2sym>() 
+			|| bindFunction<typename AB::T_2asym_1, typename AB::T_1_2sym>()
+			|| bindFunction<typename AB::T_2sym_1, typename AB::T_1_2asym>() 
+			|| bindFunction<typename AB::T_2asym_1, typename AB::T_1_2asym>();
+	}
+	return false;
+}
+
+bool Calculator::Helper::findAndBindFunction() /// top-level function
+{
+	auto countTriggers = [&](auto nlep_flag) { return std::count_if(m_defs.cbegin(), m_defs.cend(), [=](auto& def){return def.type&nlep_flag;}); };
+	m_n1L = countTriggers(TT_SINGLELEPTON_FLAG);
+	m_n2L = countTriggers(TT_DILEPTON_FLAG);
+	m_n3L = countTriggers(TT_TRILEPTON_FLAG);
+	auto exclusively = [&](auto obj_flags) { return std::none_of(m_defs.cbegin(), m_defs.cend(), [=](auto& def){return def.type&TT_MASK_FLAVOUR&~obj_flags;}); };
+
+	/// First check if the trigger combination refers to a single object type
+	if(exclusively(TT_ELECTRON_FLAG)) return findAndBindFunction<TT_ELECTRON_FLAG>();
+	if(exclusively(TT_MUON_FLAG)) return findAndBindFunction<TT_MUON_FLAG>();
+	if(exclusively(TT_PHOTON_FLAG)) return findAndBindFunction<TT_PHOTON_FLAG>();
+
+	/// Then try to rely on available formulas for combinations with two object types
+	bool success = false;
+	if(exclusively(TT_ELECTRON_FLAG|TT_MUON_FLAG)) success = findAndBindFunction<TT_ELECTRON_FLAG,TT_MUON_FLAG>();
+	else if(exclusively(TT_ELECTRON_FLAG|TT_PHOTON_FLAG)) success = findAndBindFunction<TT_ELECTRON_FLAG,TT_PHOTON_FLAG>();
+	else if(exclusively(TT_MUON_FLAG|TT_PHOTON_FLAG)) success = findAndBindFunction<TT_MUON_FLAG,TT_PHOTON_FLAG>();
+	if(success) return true;
+	
+	/// As a last resort, maybe factorizing helps
+	std::vector<Helper> helpers;
+	for(auto obj_flag : {TT_ELECTRON_FLAG, TT_MUON_FLAG, TT_PHOTON_FLAG})
+	{
+		if(std::any_of(m_defs.cbegin(), m_defs.cend(), /// check there's no mixed-flavour trigger involving 'obj_flag'
+			[&](auto& def){ return (def.type&obj_flag) && TriggerProperties(def.type).mixed();})) continue;
+		std::vector<ImportData::TrigDef> trigs1, trigs2;
+		std::partition_copy(m_defs.begin(), m_defs.end(), std::back_inserter(trigs1), std::back_inserter(trigs2), [&](auto& def){ return (def.type&obj_flag); });
+		m_defs.swap(trigs2);
+		if(!trigs1.size()) continue;
+		helpers.emplace_back(trigs1);
+		if(!helpers.back().findAndBindFunction()) return false;
+	}
+	if(helpers.size())
+	{
+		if(m_defs.size())
+		{
+			if(!findAndBindFunction()) return false;
+			if(helpers.size() == 1) m_formula = std::bind(&Calculator::globalEfficiency_Factorized2, ::_1, ::_2, ::_3, 
+				std::move(m_formula), std::move(helpers[0].m_formula), ::_4);
+			else if(helpers.size()==2) m_formula = std::bind(&Calculator::globalEfficiency_Factorized3, ::_1, ::_2, ::_3, 
+				std::move(m_formula), std::move(helpers[0].m_formula), std::move(helpers[1].m_formula), ::_4);
+			else
+			{
+				m_formula = nullptr;
+				return false;
+			}
+		}
+		else	
+		{
+			if(helpers.size() == 2) m_formula = std::bind(&Calculator::globalEfficiency_Factorized2, ::_1, ::_2, ::_3, 
+				std::move(helpers[0].m_formula), std::move(helpers[1].m_formula), ::_4);
+			else if(helpers.size() == 3) m_formula = std::bind(&Calculator::globalEfficiency_Factorized3, ::_1, ::_2, ::_3, 
+				std::move(helpers[0].m_formula), std::move(helpers[1].m_formula), std::move(helpers[2].m_formula), ::_4);
+			else return false;
+		}
+		return true;
+	}
+
+	return false;
+}
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/Root/CheckConfig.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/Root/CheckConfig.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..64750ee19ea87d41a13008719589252df513647a
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/Root/CheckConfig.cxx
@@ -0,0 +1,294 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+// contact: jmaurer@cern.ch
+
+#include "TrigGlobalEfficiencyCorrection/CheckConfig.h"
+#include "TrigGlobalEfficiencyCorrection/ImportData.h"
+#include "TrigGlobalEfficiencyCorrection/Calculator.h"
+#include <boost/container/flat_set.hpp>
+template<typename Key> using flat_set = boost::container::flat_set<Key>;
+
+#include <cctype>
+
+using CheckConfig = TrigGlobEffCorr::CheckConfig;
+using ImportData = TrigGlobEffCorr::ImportData;
+
+
+CheckConfig::CheckConfig(TrigGlobalEfficiencyCorrectionTool& parent) :
+	asg::AsgMessaging(&parent), m_parent(parent)
+{
+	msg().setLevel(parent.msg().level());
+}
+
+
+template<class CPTool>
+ToolHandle<CPTool>* CheckConfig::findToolByName(ToolHandleArray<CPTool>& suppliedTools, const std::string& name)
+{
+	for(auto& tool : suppliedTools)
+	{
+		if(tool.name()==name || tool->name()==name) // athena: not always the same
+		{
+			return &tool;
+		}
+	}
+	return nullptr;
+}
+
+bool CheckConfig::basicConfigChecks()
+{
+	bool success = true;
+	
+	if(m_parent.m_suppliedElectronEfficiencyTools.size() != m_parent.m_suppliedElectronScaleFactorTools.size())
+	{
+		ATH_MSG_ERROR("The numbers of electron tools supplied via the 'ElectronEfficiencyTools' and 'ElectronScaleFactorTools' properties should be identical");
+		return false;
+	}
+	
+	if(m_parent.m_suppliedPhotonEfficiencyTools.size() != m_parent.m_suppliedPhotonScaleFactorTools.size())
+	{
+		ATH_MSG_ERROR("The numbers of photon tools supplied via the 'PhotonEfficiencyTools' and 'PhotonScaleFactorTools' properties should be identical");
+		return false;
+	}
+	
+	/// All tools mentioned in 'ListOfLegsPerTool' must be in 'ElectronEfficiencyTools' or 'ElectronScaleFactorTools' (or equivalent photon tools)
+	for(auto& kv : m_parent.m_legsPerTool)
+	{
+		auto& name = kv.first;
+		if(findToolByName(m_parent.m_suppliedElectronEfficiencyTools, name)
+			|| findToolByName(m_parent.m_suppliedElectronScaleFactorTools, name)
+			|| findToolByName(m_parent.m_suppliedPhotonEfficiencyTools, name)
+			|| findToolByName(m_parent.m_suppliedPhotonScaleFactorTools, name)
+			) continue;
+		success = false;
+		if(findToolByName(m_parent.m_suppliedMuonTools, name))
+		{
+			ATH_MSG_ERROR("Muon tool " << name << " mentioned in property 'ListOfLegsPerTool', which is only aimed at electron and photon tools");
+		}
+		else
+		{
+			std::string known_tools = "; the known tools are";
+			for(auto& tool : m_parent.m_suppliedElectronEfficiencyTools) known_tools += " " + tool.name();
+			for(auto& tool : m_parent.m_suppliedElectronScaleFactorTools) known_tools += " " + tool.name();
+			for(auto& tool : m_parent.m_suppliedPhotonEfficiencyTools) known_tools += " " + tool.name();
+			for(auto& tool : m_parent.m_suppliedPhotonScaleFactorTools) known_tools += " " + tool.name();
+			ATH_MSG_ERROR("Unknown tool " << name << " mentioned in property 'ListOfLegsPerTool'" << known_tools);
+		}
+	}
+	if(!success) return false;
+	
+	/// All electron/photon tools must be associated to trigger legs (except when there's only one tool)
+	auto toolsHaveLegInfo = [this](auto& effTools, auto& sfTools, const char* type)
+	{
+		if(effTools.size() < 2) return true;
+		bool success = true;
+		for(int i=0;i<2;++i)
+		{
+			for(auto& tool : (i? effTools: sfTools))
+			{
+				const std::string& name = tool.name();
+				if(m_parent.m_legsPerTool.find(name) == m_parent.m_legsPerTool.end())
+				{
+					ATH_MSG_ERROR(type << " tool " << name << " associated trigger legs are not indicated in 'ListOfLegsPerTool', "
+						"doing so is mandatory when several tools are used");
+					success = false;
+				}
+			}
+		}
+		return success;
+	};
+	success &= toolsHaveLegInfo(m_parent.m_suppliedElectronEfficiencyTools, m_parent.m_suppliedElectronScaleFactorTools, "Electron");
+	success &= toolsHaveLegInfo(m_parent.m_suppliedPhotonEfficiencyTools, m_parent.m_suppliedPhotonScaleFactorTools, "Photon");
+	if(!success) return false;
+	
+	/// 
+	/// Additional checks when tags are used
+	///
+	
+	if(m_parent.m_leptonTagDecorations == "")
+	{
+		if(m_parent.m_muonLegsPerTag.size() || m_parent.m_electronLegsPerTag.size()
+			|| m_parent.m_legsPerTag.size() || m_parent.m_tagsPerTool.size())
+		{
+			ATH_MSG_ERROR("the property 'LeptonTagDecorations' must be filled when any of 'ListOfTagsPerTool'"
+				" / 'ListOfLegsPerTag' / 'MuonLegsPerTag' / 'ElectronLegsPerTag' is.");
+			return false;
+		}
+		return true;
+	}
+	
+	/// All tools mentioned in 'ListOfTagsPerTool' must be known
+	unsigned nElectronToolsWithTags = 0, nMuonToolsWithTags = 0, nPhotonToolsWithTags = 0;
+	for(auto& kv : m_parent.m_tagsPerTool)
+	{
+		auto& name = kv.first;
+		if(findToolByName(m_parent.m_suppliedElectronEfficiencyTools, name)
+			|| findToolByName(m_parent.m_suppliedElectronScaleFactorTools, name)) ++nElectronToolsWithTags;
+		else if(findToolByName(m_parent.m_suppliedMuonTools, name)) ++nMuonToolsWithTags;
+		else if(findToolByName(m_parent.m_suppliedPhotonEfficiencyTools, name)
+			|| findToolByName(m_parent.m_suppliedPhotonScaleFactorTools, name)) ++nPhotonToolsWithTags;
+		else
+		{
+			success = false;
+			std::string all_tools = "; the known tools are";
+			for(auto& tool : m_parent.m_suppliedElectronEfficiencyTools) all_tools += " " + tool.name();
+			for(auto& tool : m_parent.m_suppliedElectronScaleFactorTools) all_tools += " " + tool.name();
+			for(auto& tool : m_parent.m_suppliedPhotonEfficiencyTools) all_tools += " " + tool.name();
+			for(auto& tool : m_parent.m_suppliedPhotonScaleFactorTools) all_tools += " " + tool.name();
+			ATH_MSG_ERROR("Unknown tool " << name << " mentioned in property 'ListOfTagsPerTool'");
+		}
+	}
+	/// Either all muon tools are associated to tags, either none
+	if(nMuonToolsWithTags && (nMuonToolsWithTags != m_parent.m_suppliedMuonTools.size()))
+	{
+		ATH_MSG_ERROR("Not all muon tools have been associated with tags in the 'ListOfTagsPerTool' property");
+		success = false;
+	}
+	/// Either all electron tools are associated to tags, either none
+	unsigned nSupplied = m_parent.m_suppliedElectronEfficiencyTools.size() + m_parent.m_suppliedElectronScaleFactorTools.size();
+	if(nElectronToolsWithTags && (nElectronToolsWithTags!=nSupplied))
+	{
+		ATH_MSG_ERROR("Not all electron tools have been associated with tags in the 'ListOfTagsPerTool' property");
+		success = false;
+	}
+	if(!success) return false;
+	/// Either all photon tools are associated to tags, either none
+	nSupplied = m_parent.m_suppliedPhotonEfficiencyTools.size() + m_parent.m_suppliedPhotonScaleFactorTools.size();
+	if(nPhotonToolsWithTags && (nPhotonToolsWithTags!=nSupplied))
+	{
+		ATH_MSG_ERROR("Not all photon tools have been associated with tags in the 'ListOfTagsPerTool' property");
+		success = false;
+	}
+	if(!success) return false;
+
+	/*
+	 *  More checks that are done in other places (or still need to be implemented!):
+	 *
+	 * - [advancedConfigChecks()] for each entry in ListOfLegsPerTag there must be a suitable tool for that tag and leg(s)
+	 * - [enumerateTools()] no two electron/photon tools share the same {leg,tag} combination
+	 * - [enumerateTools()] no two muon tools share the same tag
+	 * - [advancedConfigChecks()] electron efficiency and scale factors have identical configurations: for each eff. tool leg/tag we must find a SF tool, and the other leg/tag pairs associated to those tools must be identical.
+	 * - [UNCHECKED] if tags are used with electron (resp. muon, photon) tools, then all electron (muon, photon) tools must have an entry in ListOfTagsPerTool. Done partially in this function, but the case where no tools are tagged (yet tags are used, according to ListOfLegsPerTag or LeptonTagDecorations) escapes detection. 
+	 * - [loadTagsConfiguration()] each entry of ListOfLegsPerTag can be matched to a suitable tool
+	 * - [UNCHECKED] suffixed tag read from a lepton must correspond to a know tag
+	 * - [enumerateTools()] list of legs associated to each tool contains only known legs
+	 * - [UNCHECKED TrigGlobEffCorrImportData import* functions] various consistency checks of the configuration files
+	 * - [advancedConfigChecks()] user-specified periods are orthogonal
+	 * - [ImportData::parseTriggerString()] no duplicated triggers in the combination
+	 * - [UNCHECKED] for each configured electron/photon tool there is at least one associated tag^leg pair in 'ListOfLegsPerTag' (unless no electron/photon tags used)
+	 * - [UNCHECKED] for each configured muon tool there is at least one associated tag in 'MuonLegsPerTag' (unless empty)
+	 * - [enumerateTools()] electron tools can't be associated to photon legs; and reciprocally
+	 */
+	
+	return success;
+}
+
+bool CheckConfig::advancedConfigChecks()
+{
+	/// This method requires all (most) of TrigGlobalEfficiencyCorrectionTool internal variables to have been properly initialized already
+	/// -> to be called as the last step of TrigGlobalEfficiencyCorrectionTool::initialize()
+	bool success = true;
+
+	using ToolKey = TrigGlobalEfficiencyCorrectionTool::ToolKey;
+	
+	/// Check that for each electron/photon efficiency tool there is an associated scale factor tool
+	/// with the same associated legs and tags. And vice versa. 
+	auto checkConsistency = [this](auto& effToolIndex, auto& sfToolIndex, const char* type)
+	{
+		bool mismatch = (effToolIndex.size() != sfToolIndex.size());
+		if(!mismatch)
+		{
+			for(auto& kv : sfToolIndex)
+			{
+				auto itr = effToolIndex.find(kv.first);
+				if(itr != effToolIndex.end())
+				{
+					std::size_t index1 = kv.second, index2 = itr->second;
+					flat_set<ToolKey> pairs1, pairs2;
+					for(auto& kv : sfToolIndex) if(kv.second==index1) pairs1.insert(kv.first);
+					for(auto& kv : effToolIndex) if(kv.second==index2) pairs2.insert(kv.first);
+					if(pairs1 != pairs2) mismatch = true;
+				}
+				else mismatch = true;
+			}
+		}
+		if(mismatch)
+		{
+			ATH_MSG_ERROR("There must be a one-to-one correspondence between the " << type << " efficiency and scale factor tools "
+				"(including their associated trigger legs and selection tags)");
+			return false;
+		}
+		return true;
+	};
+	if(!checkConsistency(m_parent.m_electronEffToolIndex, m_parent.m_electronSfToolIndex, "electron")) return false;
+	if(!checkConsistency(m_parent.m_photonEffToolIndex, m_parent.m_photonSfToolIndex, "photon")) return false;
+	
+	/// A suitable CP tool must be available for each entry of 'ListOfLegsPerTag'
+	for(auto& kv : m_parent.m_legsPerTag)
+	{
+		std::size_t tag = (kv.first!="*")? m_parent.m_hasher(kv.first) : 0;
+		for(std::size_t leg : m_parent.listNonOrderedCSValues(kv.second,success))
+		{
+			auto type = ImportData::associatedLeptonFlavour(m_parent.m_dictionary[leg],success);
+			if(type == xAOD::Type::Electron)
+			{
+				if(m_parent.m_electronEffToolIndex.find(ToolKey(leg, tag)) == m_parent.m_electronEffToolIndex.end())
+				{
+					ATH_MSG_ERROR("No electron tool provided for the combination of trigger leg '" << m_parent.m_dictionary[leg]
+						<< "' and selection tag '" << kv.first << "' mentioned in the property 'ListOfLegsPerTag'");
+					success = false;
+				}
+			}
+			else if(type == xAOD::Type::Muon)
+			{
+				if(m_parent.m_muonToolIndex.find(ToolKey(0, tag)) == m_parent.m_muonToolIndex.end())
+				{
+					ATH_MSG_ERROR("No muon tool provided for the combination of trigger leg '" << m_parent.m_dictionary[leg]
+						<< "' and selection tag '" << kv.first << "' mentioned in the property 'ListOfLegsPerTag'");
+					success = false;
+				}
+			}
+			else if(type == xAOD::Type::Photon)
+			{
+				if(m_parent.m_photonEffToolIndex.find(ToolKey(leg, tag)) == m_parent.m_photonEffToolIndex.end())
+				{
+					ATH_MSG_ERROR("No photon tool provided for the combination of trigger leg '" << m_parent.m_dictionary[leg]
+						<< "' and selection tag '" << kv.first << "' mentioned in the property 'ListOfLegsPerTag'");
+					success = false;
+				}
+			}
+			else
+			{					
+				ATH_MSG_ERROR("Unable to determine which lepton flavour is associated to the trigger leg '" << m_parent.m_dictionary[leg] << "' in the property 'ListOfLegsPerTag'");
+				success = false;
+			}
+		}
+	}
+	if(!success) return false;
+	
+	/// Periods don't overlap
+	auto periods = m_parent.m_calculator->m_periods;	
+	const auto periods_end =  periods.end();
+	for(auto itr1=periods.begin(); itr1!=periods_end; ++itr1)
+	{
+		auto& x = itr1->m_boundaries;
+		if(x.second < x.first)
+		{
+			ATH_MSG_ERROR("One of the periods specified in TriggerCombination has runMin (" << x.first << ") > runMax ("  << x.second << ")");
+			success = false;
+		}
+		for(auto itr2=itr1+1; itr2!=periods_end; ++itr2)
+		{
+			auto& y = itr2->m_boundaries;
+			if((x.first>=y.first && x.first<=y.second) || (x.second>=y.first && x.second<=y.second))
+			{
+				ATH_MSG_ERROR("The periods specified in TriggerCombination overlap");
+				success = false;
+			}
+		}
+	}
+	if(!success) return false;
+	
+	return success;
+}
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/Root/ImportData.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/Root/ImportData.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..4b4e6378b5ca0dd302cbfa8cf4efc2c7eb286646
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/Root/ImportData.cxx
@@ -0,0 +1,727 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+// contact: jmaurer@cern.ch
+
+#include "TrigGlobalEfficiencyCorrection/ImportData.h"
+#include "PathResolver/PathResolver.h"
+
+#include <fstream>
+#include <functional>
+#include <algorithm>
+
+using ImportData = TrigGlobEffCorr::ImportData;
+
+ImportData::ImportData() :
+	/// the following two variables are owned by this tool and released in the destructor
+	asg::AsgMessaging("TrigGlobalEfficiencyCorrectionTool"),
+	m_parent(nullptr),
+	m_dictionary(*new std::map<std::size_t,std::string>),
+	m_hasher(*new std::hash<std::string>)
+{
+}
+
+ImportData::ImportData(TrigGlobalEfficiencyCorrectionTool& parent) :
+	asg::AsgMessaging(&parent),
+	m_parent(&parent),
+	m_dictionary(parent.m_dictionary),
+	m_hasher(parent.m_hasher)
+{
+	msg().setLevel(parent.msg().level());
+}
+
+ImportData::~ImportData()
+{
+	if(!m_parent)
+	{
+		delete &m_dictionary;
+		delete &m_hasher;
+	}
+}
+
+bool ImportData::importAll(const std::map<std::string, std::string>& overridenThresholds)
+{
+	return importPeriods()
+		&& importThresholds(overridenThresholds)
+		&& importTriggers()
+		&& importHierarchies();
+}
+
+bool ImportData::readDataFile(const char* filename, std::vector<std::string>& contents)
+{
+	contents.clear();
+	bool success = true;
+	auto name = PathResolverFindCalibFile(filename);
+	if(name.length())
+	{
+		std::ifstream f(name.c_str(),std::ios_base::in);
+		if(f.is_open())
+		{
+			std::string line;
+			while(f.good())
+			{
+				if(std::getline(f,line))
+				{
+					auto i = line.find('#');
+					if(i != std::string::npos) line = line.substr(0,i);
+					if(line.length() >= 3) contents.emplace_back(std::move(line));
+				}
+			}
+			if(!f.eof())
+			{
+				ATH_MSG_ERROR("Issue encountered while reading configuration file " << filename);
+				success = false;
+			}
+			f.close();
+			return true;
+		}
+		else
+		{
+		ATH_MSG_ERROR("Unable to open configuration file " << filename);
+			success = false;
+		}
+	}
+	else
+	{
+		ATH_MSG_ERROR("Unable to find configuration file " << filename);
+		success = false;
+	}
+	return success;
+}
+
+void ImportData::setNonMixed3LType(TrigDef& def, TriggerType flavourFlag)
+{
+	if(def.leg[0]==def.leg[1] && def.leg[1]==def.leg[2]) def.type = static_cast<TriggerType>(TT_TRILEPTON_SYM | flavourFlag);
+	else if(def.leg[0]!=def.leg[1] && def.leg[1]!=def.leg[2] && def.leg[0]!=def.leg[2]) def.type = static_cast<TriggerType>(TT_TRILEPTON_ASYM | flavourFlag);
+	else /// swap legs so that the last two are identical, for later convenience
+	{
+		if(def.leg[1]!=def.leg[0] && def.leg[1]!=def.leg[2]) std::swap(def.leg[0],def.leg[1]);
+		else if(def.leg[2]!=def.leg[0] && def.leg[2]!=def.leg[1]) std::swap(def.leg[0],def.leg[2]);
+		def.type = static_cast<TriggerType>(TT_TRILEPTON_HALFSYM | flavourFlag);
+	}
+}
+
+bool ImportData::importTriggers()
+{
+	if(!m_triggerThresholds.size() && !importThresholds()) return false;
+	m_triggerDefs.clear();
+	std::vector<std::string> config;
+	if(!readDataFile("TrigGlobalEfficiencyCorrection/Triggers.cfg", config)) return false;
+	std::stringstream ss;
+	std::string triggerName, token;
+	bool success = true;
+	for(auto& line : config)
+	{
+                ATH_MSG_DEBUG(line);
+		ss.clear();
+		ss.str(line);
+		ss >> triggerName;
+		std::size_t h = m_hasher(triggerName);
+		m_dictionary[h] = triggerName;
+		ATH_MSG_DEBUG(std::to_string(h) << " " << triggerName );
+		auto& def = m_triggerDefs[h];
+		def.name = h;
+		for(std::size_t& leg : def.leg)
+		{
+			if(!(ss >> token)) break;
+			h = m_hasher(token);
+			m_dictionary.emplace(h,token);
+			leg = h;
+			if(m_triggerThresholds.find(h) == m_triggerThresholds.end())
+			{
+				ATH_MSG_ERROR("Unknown trigger leg '" << token << "' found in Triggers.cfg");
+				success = false;
+			}
+		}
+		if(!def.leg[0])
+		{
+			def.leg[0] = h; /// default: assume the leg's name is the same as the full trigger name
+			if(m_triggerThresholds.find(h) == m_triggerThresholds.end())
+			{
+				ATH_MSG_ERROR("Unknown trigger leg '" << triggerName << "' (inferred from trigger name) found in Triggers.cfg");
+				success = false;
+			}
+		}
+		if(!success) continue;
+		
+		/// Classify trigger and re-arrange legs (if needed) so that all electron legs come before muon legs, and muon legs before photon legs
+		def.type = TT_UNKNOWN;		
+		auto flavour0 = associatedLeptonFlavour(def.leg[0], success);
+		int ne = (flavour0 == xAOD::Type::Electron)*1;
+		int nm = (flavour0 == xAOD::Type::Muon)*1;
+		if(def.leg[1])
+		{
+			auto flavour1 = associatedLeptonFlavour(def.leg[1], success);
+			if(flavour1 == xAOD::Type::Electron)
+			{
+				if(!ne) std::swap(def.leg[0],def.leg[1]);
+				++ne;
+			}
+			else if(flavour1 == xAOD::Type::Muon)
+			{
+				if(!(ne+nm)) std::swap(def.leg[0],def.leg[1]);
+				++nm;
+			}
+			else if(flavour1 != xAOD::Type::Photon) success = false;
+			if(def.leg[2])
+			{
+				auto flavour2 = associatedLeptonFlavour(def.leg[2], success);
+				if(flavour2 == xAOD::Type::Electron)
+				{
+					if(!ne) std::swap(def.leg[0], def.leg[2]);
+					else if(ne==1) std::swap(def.leg[1], def.leg[2]);
+					++ne;
+				}
+				else if(flavour2 == xAOD::Type::Muon)
+				{
+					if(!(ne+nm)) std::swap(def.leg[0], def.leg[2]);
+					else if((ne+nm)==1) std::swap(def.leg[1], def.leg[2]);
+					++nm;
+				}
+				else if(flavour2 != xAOD::Type::Photon) success = false;
+				if(ne+nm==0 || ne==3 || nm==3) /// single-flavour trilepton triggers
+				{
+					setNonMixed3LType(def, (ne? TT_ELECTRON_FLAG : nm? TT_MUON_FLAG : TT_PHOTON_FLAG));
+				}
+				else /// mixed-flavour trilepton triggers
+				{
+					bool sym = (def.leg[0]==def.leg[1] || def.leg[1]==def.leg[2]);
+					if(ne==2) def.type = nm? (sym? TT_2E_MU_SYM : TT_2E_MU_ASYM) : (sym? TT_2E_G_SYM : TT_2E_G_ASYM);
+					else if(nm==2) def.type = ne? (sym? TT_E_2MU_SYM : TT_E_2MU_ASYM) : (sym? TT_2MU_G_SYM : TT_2MU_G_ASYM);
+					else if(ne+nm==1) def.type = ne? (sym? TT_E_2G_SYM : TT_E_2G_ASYM) : (sym? TT_MU_2G_SYM : TT_MU_2G_ASYM);
+					else success = false;
+				}
+			}
+			else /// dilepton triggers
+			{
+				if(ne==2) def.type = (def.leg[0]==def.leg[1])? TT_2E_SYM : TT_2E_ASYM;
+				else if(nm==2) def.type = (def.leg[0]==def.leg[1])? TT_2MU_SYM : TT_2MU_ASYM;
+				else if(ne+nm==0) def.type = (def.leg[0]==def.leg[1])? TT_2G_SYM : TT_2G_ASYM;
+				else if(ne==1 && nm==1) def.type = TT_E_MU;
+				else if(ne==1) def.type = TT_E_G;
+				else if(nm==1) def.type = TT_MU_G;
+			}
+		}
+		else /// single lepton triggers
+		{
+			def.type = ne? TT_SINGLE_E : nm? TT_SINGLE_MU : TT_SINGLE_G;
+		}
+		if(!success || def.type==TT_UNKNOWN)
+		{
+			success = false;
+			// ATH_MSG_ERROR("Configuration issue for trigger " << triggerName);
+		}
+	}
+	return success;
+}
+
+bool ImportData::importThresholds(const std::map<std::string, std::string>& overridenThresholds)
+{
+	m_triggerThresholds.clear();
+	std::vector<std::string> config;
+	if(!readDataFile("TrigGlobalEfficiencyCorrection/Thresholds.cfg", config)) return false;
+	std::stringstream ss;
+	std::string leg, unit;
+	float pt;
+	bool success = true;
+	for(auto& line : config)
+	{
+		ss.clear();
+		ss.str(line);
+		if(ss >> leg >> pt >> unit)
+		{
+			std::size_t h = m_hasher(leg);
+			m_dictionary.emplace(h,leg);
+			if(unit == "GeV") pt *= 1e3f;
+			else if(unit != "MeV")
+			{
+				ATH_MSG_ERROR("Unable to import pT threshold for leg \"" << leg << "\" (missing unit)");
+				success = false;
+			}
+			m_triggerThresholds[h] = pt;
+		}
+		else
+		{
+			ATH_MSG_ERROR("Unable to import pT threshold for leg \"" << leg << '"');
+			success = false;
+		}
+	}
+	if(!success)
+	{
+		m_triggerThresholds.clear();
+		return false;
+	}
+
+	/// Override thresholds if requested
+	bool belowRecommended = false;
+	for(auto& kv : overridenThresholds)
+	{
+		auto itr = m_triggerThresholds.find(m_hasher(kv.first));
+		if(itr != m_triggerThresholds.end())
+		{
+			float pt = 0.f;
+			try { pt = std::stof(kv.second); }
+			catch(...)
+			{
+				ATH_MSG_ERROR("Unable to convert threshold argument \""<<kv.second<<"\" to floating-point value");
+				success = false;
+				continue;
+			}
+			if(pt<1e3f)
+			{
+				ATH_MSG_WARNING("Suspiciously low threshold (" << pt << " MeV) set for trigger leg " << kv.first
+					<< ", please make sure you provided the threshold in MeV and not in GeV!");
+			}
+			if(pt < itr->second) belowRecommended = true;
+			itr->second = pt;
+		}
+		else
+		{
+			ATH_MSG_ERROR("Can't override threshold for unknown trigger leg " << kv.first);
+			success = false;
+		}
+	}
+	if(belowRecommended)
+	{
+		ATH_MSG_WARNING("Tool configured to use trigger thresholds below those recommended!");
+	}
+	return success;
+}
+
+bool ImportData::importPeriods()
+{
+	m_dataPeriods.clear();
+	std::vector<std::string> config;
+	if(!readDataFile("TrigGlobalEfficiencyCorrection/DataPeriods.cfg", config)) return false;
+	std::stringstream ss;
+	std::string key;
+	std::pair<unsigned,unsigned> runs;
+	for(auto& line : config)
+	{
+		ss.clear();
+		ss.str(line);
+		ss >> key >> runs.first >> runs.second;
+		if(ss.fail())
+		{
+			m_dataPeriods.clear();
+			return false;
+		}
+		m_dataPeriods.emplace(key,runs);
+	}
+	return true;
+}
+
+bool ImportData::importHierarchies()
+{
+	if(!m_triggerThresholds.size() && !importThresholds()) return false;
+	m_hierarchyMeta.clear();
+	m_hierarchyData.clear();
+	std::vector<std::string> config;
+	if(!readDataFile("TrigGlobalEfficiencyCorrection/Hierarchies.cfg", config)) return false;
+	std::stringstream ss;
+	std::string token, unit;
+	std::map<std::size_t, std::vector<std::size_t> > aliases;
+	for(auto& line : config)
+	{
+		bool success = true;
+		ss.clear();
+		ss.str(line);
+		if(line[0]=='[')
+		{
+			auto& meta = *m_hierarchyMeta.emplace(m_hierarchyMeta.end(), Hierarchy{(short)m_hierarchyData.size(),0,0.f,1e12f});
+			if(line[1]=='-' && line[2]==']') ss.ignore(3);
+			else
+			{
+				char sep = '-';
+				if(line[1]=='>') ss.ignore(2) >> meta.minPt >> unit;
+				else if(line[1]=='<') ss.ignore(2) >> meta.maxPt >> unit;
+				else ss.ignore(1) >> meta.minPt >> sep >> meta.maxPt >> unit;
+				if(!ss || sep!='-' || (unit!="GeV]" && unit!="MeV]"))
+				{
+					ATH_MSG_ERROR("Unable to parse pT restrictions in Hierarchies.cfg");
+					success = false;
+				}
+				if(unit == "GeV]")
+				{
+					meta.minPt *= 1e3f;
+					if(meta.maxPt < 1e12f) meta.maxPt *= 1e3f;
+				}
+			}
+			while(ss >> token)
+			{
+				std::size_t h = m_hasher(token);
+				auto itr = aliases.find(h);
+				if(itr == aliases.end())
+				{
+					if(m_triggerThresholds.find(h) == m_triggerThresholds.end())
+					{
+						ATH_MSG_ERROR("Unknown trigger leg '" << token << "' found in Hierarchies.cfg");
+						success = false;
+					}
+					m_dictionary.emplace(h,token);
+					m_hierarchyData.push_back(h);
+				}
+				else m_hierarchyData.insert(m_hierarchyData.end(),itr->second.begin(),itr->second.end());
+				if(ss >> token && token!='>') success = false;
+			}
+			meta.nLegs = m_hierarchyData.size() - meta.offset;
+			success = success && meta.nLegs;
+		}
+		else
+		{
+			ss >> token;
+			auto& legs = aliases[m_hasher(token)];
+			if(ss >> token && token==":=")
+			{
+				legs.clear();
+				while(ss >> token)
+				{
+					std::size_t h = m_hasher(token);
+					m_dictionary.emplace(h,token);
+					legs.push_back(h);
+					if(m_triggerThresholds.find(h) == m_triggerThresholds.end())
+					{
+						ATH_MSG_ERROR("Unknown trigger leg '" << token << "' found in Hierarchies.cfg");
+						success = false;
+					}
+					if(ss >> token && token!='>') success = false;
+				}
+				success = success && legs.size();
+			}
+			else success = false;
+		}
+		if(!success)
+		{
+			ATH_MSG_ERROR("Failed parsing line from Hierarchies.cfg:\n" << line);
+			m_hierarchyMeta.clear();
+			m_hierarchyData.clear();
+			return false;
+		}
+	}
+	return true;
+}
+
+bool ImportData::importMapKeys(const std::string& version, std::map<std::size_t,std::map<std::size_t,int> >& keysPerLeg)
+{
+	keysPerLeg.clear();
+	std::vector<std::string> config;
+	if(!readDataFile("TrigGlobalEfficiencyCorrection/MapKeys.cfg", config)) return false;
+	std::stringstream ss;
+	std::string token;
+	bool reading = false;
+	for(auto& line : config)
+	{
+		std::size_t pos = line.find("[VERSION]");
+		if(pos != std::string::npos)
+		{
+			reading = false;
+			ss.clear();
+			ss.str(TrigGlobEffCorr::removeWhitespaces(line.substr(pos+9)));
+			while(std::getline(ss, token, ','))
+			{
+				if(token == version)
+				{
+					reading = true;
+					break;
+				}
+			}
+			continue;
+		}
+		if(!reading) continue;
+		ss.clear();
+		ss.str(line);
+		int year;
+		ss >> year >> token;
+		year = 1<<(year-2015);
+		std::size_t leg = m_hasher(token);
+		auto& keys = keysPerLeg[leg];
+		while(ss >> token)
+		{
+			std::size_t h = m_hasher(token);
+			auto insertion = keys.emplace(h, year);
+			if(insertion.second) m_dictionary.emplace(h, token);
+			else insertion.first->second |= year;
+		}
+	}
+	if(!keysPerLeg.size())
+	{
+		ATH_MSG_ERROR("Unable to import the available map keys for the version " << version);
+		return false;
+	}
+	return true;
+}
+
+bool ImportData::getPeriodBoundaries(const std::string& period, std::pair<unsigned,unsigned>& boundaries)
+{
+	// First possibility: a defined period keyword
+	auto itr = m_dataPeriods.find(period);
+	if(itr!=m_dataPeriods.end())
+	{
+		boundaries = itr->second;
+		return true;
+	}
+	// Otherwise it's a '-'-separated range
+	auto sep = period.find_first_of('-');
+	if(sep!=std::string::npos)
+	{
+		std::string kwMin = period.substr(0,sep);
+		std::string kwMax = period.substr(sep+1);
+		// Second possibility: a range of defined period keywords
+		auto itrMin = m_dataPeriods.find(kwMin);
+		auto itrMax = m_dataPeriods.find(kwMax);
+		if(itrMin!=m_dataPeriods.end() && itrMax!=m_dataPeriods.end())
+		{
+			boundaries = std::minmax({itrMin->second.first, itrMax->second.first, itrMin->second.second, itrMax->second.second});
+			return true;
+		}
+		// Third possibility: a range of run numbers
+		try
+		{
+			boundaries = std::minmax(std::stoi(kwMin), std::stoi(kwMax));
+			return true;
+		}
+		catch(...) {}
+	}
+	ATH_MSG_ERROR("Unable to understand the period/range " << period);
+	return false;
+}
+
+xAOD::Type::ObjectType ImportData::associatedLeptonFlavour(const std::string& leg, bool& success)
+{
+	// note: 'success' is not set to 'true', only downgraded to 'false' if needed
+	if(leg.length()>=2 && leg[0]=='e' && leg[1]>='1' && leg[1]<='9') return xAOD::Type::Electron;
+	else if(leg.length()>=3 && leg[0]=='m' && leg[1]=='u' && leg[2]>='1' && leg[2]<='9') return xAOD::Type::Muon;
+	else if(leg.length()>=3 && leg[0]=='g' && leg[1]>='1' && leg[1]<='9') return xAOD::Type::Photon;
+	success = false;
+	return xAOD::Type::Other;
+}
+
+xAOD::Type::ObjectType ImportData::associatedLeptonFlavour(std::size_t leg, bool& success)
+{
+	// note: 'success' is not set to 'true', only downgraded to 'false' if needed
+	auto itr = m_dictionary.find(leg);
+	if(itr != m_dictionary.end())
+	{
+		return associatedLeptonFlavour(itr->second,success);
+	}
+	success = false;
+	return xAOD::Type::Other;
+}
+
+std::vector<ImportData::TrigDef> ImportData::parseTriggerString(const std::string& triggerString, bool& success)
+{
+	std::string s = TrigGlobEffCorr::removeWhitespaces(triggerString);
+	if(s.find("|||") != std::string::npos)
+	{
+		ATH_MSG_ERROR("Invalid format for the trigger combination '" << triggerString <<"'");
+		success = false;
+		return {};
+	}
+	/// Replace all || by |
+	while(true)
+	{
+		auto i = s.find("||");
+		if(i == std::string::npos) break;
+		s.replace(i, 1, "");
+	}
+	if(s=="" || s=="|")
+	{
+		ATH_MSG_ERROR("Invalid format for the trigger combination '" << triggerString <<"'");
+		success = false;
+		return {};
+	}
+	std::vector<TrigDef> triggers;
+	std::set<std::size_t> hashes;
+	std::stringstream ss(s);
+	while(std::getline(ss,s,'|'))
+	{
+		std::size_t trig = m_hasher(s);
+		ATH_MSG_DEBUG(std::to_string(trig) << " --> " << s );
+		auto itr = m_triggerDefs.find(trig);
+		if(itr == m_triggerDefs.end())
+		{
+			ATH_MSG_ERROR("Unknown trigger '" << s << "' found while parsing trigger combination");
+			success = false;
+			return {};	
+		}
+		if(!hashes.insert(trig).second)
+		{
+			ATH_MSG_ERROR("The trigger '" << s << "' is present more than once in the combination");
+			success = false;
+			return {};
+		}
+		triggers.push_back(itr->second);
+	}
+	success = success && triggers.size();
+	return triggers;
+}
+
+bool ImportData::suggestElectronMapKeys(const std::map<std::string,std::string>& triggerCombination,
+		const std::string& version, std::map<std::string,std::string>& legsPerKey)
+{
+	legsPerKey.clear();
+	if(!importAll()) return false;
+	
+	bool success = true;
+	std::map<std::size_t,int> legs;
+	
+	for(auto& kv : triggerCombination)
+	{
+		auto itrPeriod = m_dataPeriods.find(kv.first);
+		if(itrPeriod == m_dataPeriods.end())
+		{
+			ATH_MSG_ERROR("Unknown period " << kv.first);
+			success = false;
+			continue;
+		}
+		int years = 0;
+		for(int k=0;k<32;++k)
+		{
+			auto itr = m_dataPeriods.find(std::to_string(2015+k));
+			if(itr != m_dataPeriods.end() && itrPeriod->second.first <= itr->second.second 
+				&& itrPeriod->second.second >= itr->second.first)
+			{
+				years |= (1<<k);
+			}
+		}
+		auto triggers = parseTriggerString(kv.second,success);
+		if(!success) continue;
+		for(auto& trig : triggers)
+		{
+			for(std::size_t leg : trig.leg)
+			{
+				if(leg && associatedLeptonFlavour(leg, success)==xAOD::Type::Electron)
+				{
+					auto insertion = legs.emplace(leg,years);
+					if(!insertion.second) insertion.first->second |= years;
+				}
+			}
+		}
+	}
+	if(!success) return false;
+
+	if(version != "")
+	{
+		std::map<std::size_t,std::map<std::size_t,int> > allKeys;
+		if(!importMapKeys(version, allKeys)) return false;
+		std::map<std::size_t,std::vector<std::size_t> > allLegsPerKey;
+		std::set<std::size_t> legsWithMultipleKeys;
+		bool sameKeyForAllyears = true;
+		while(legs.size())
+		{
+			allLegsPerKey.clear();
+			for(auto& kvLegs : legs) // loop on remaining legs
+			{
+				std::size_t leg = kvLegs.first;
+				int years = kvLegs.second;
+				auto itrKeys = allKeys.find(leg); // list of keys for that leg
+				if(itrKeys != allKeys.end())
+				{
+					for(auto& kvKeys : itrKeys->second) // loop on those keys
+					{
+						auto y = (kvKeys.second & years);
+						if((y==years) || (!sameKeyForAllyears && y!=0)) // key must support all years needed for that leg -- until no longer possible
+						{
+							auto insertion = allLegsPerKey.emplace(kvKeys.first,std::vector<std::size_t>{leg});
+							if(!insertion.second) insertion.first->second.push_back(leg); 
+						}
+					}
+				}
+				else
+				{
+					ATH_MSG_ERROR("Sorry, no idea what the map key should be for the trigger leg '" 
+							<< m_dictionary.at(leg) << "', manual configuration is needed");
+					success = false;
+				}
+			}
+			if(!success) break;
+
+			if(!allLegsPerKey.size())
+			{
+				if(sameKeyForAllyears)
+				{
+					sameKeyForAllyears = false;
+					continue;
+				}
+				success = false;
+				break;
+			}
+			
+			using T = decltype(allLegsPerKey)::value_type;
+			auto itrKey = std::max_element(allLegsPerKey.begin(), allLegsPerKey.end(),
+				[](T& x,T& y){return x.second.size()<y.second.size();});
+			std::string& strLegs = legsPerKey[m_dictionary.at(itrKey->first)];
+			for(std::size_t leg : itrKey->second)
+			{
+				int& wantedYears = legs.at(leg);
+				int supportedYears = (allKeys.at(leg).at(itrKey->first)) & wantedYears;
+				if(supportedYears!=wantedYears || legsWithMultipleKeys.count(leg))
+				{
+					legsWithMultipleKeys.insert(leg);
+					for(int i=0;i<32;++i)
+					{
+						if(supportedYears & (1<<i))
+						{
+							if(strLegs.length() && strLegs.back()!=',') strLegs += ',';
+							strLegs += m_dictionary.at(leg) + "[" + std::to_string(2015 + i) + "]";
+						}
+					}
+				}
+				else
+				{
+					if(strLegs.length() && strLegs.back()!=',') strLegs += ',';
+					strLegs += m_dictionary.at(leg);
+				}
+				if(supportedYears == wantedYears) legs.erase(leg);
+				else wantedYears &= ~supportedYears;
+			}
+		}
+	}
+	else
+	{
+		/// If no version is specified, the list of trigger legs is returned
+		for(auto& kv : legs)
+		{
+			legsPerKey.emplace(std::to_string(legsPerKey.size()), m_dictionary.at(kv.first));
+		}
+	}
+	
+	if(!success) legsPerKey.clear();
+	return success;
+}
+
+bool ImportData::adaptTriggerListForTriggerMatching(std::vector<ImportData::TrigDef>& triggers)
+{
+	/// This essentially splits _OR_ single lepton triggers into independent items
+	std::set<std::size_t> extraItems; /// to prevent duplicates
+	std::vector<ImportData::TrigDef> updatedTriggers;
+	for(auto& trig : triggers)
+	{
+		auto& name = m_dictionary.at(trig.name);
+		std::size_t pos = 0, len = name.find("_OR_");
+		if(len == std::string::npos)
+		{
+			updatedTriggers.emplace_back(trig);
+			continue;
+		}
+		while(true)
+		{
+			std::string item = name.substr(pos, len);
+			auto h = m_hasher(item);
+			auto def = m_triggerDefs.find(h);
+			if(def == m_triggerDefs.end())
+			{
+				ATH_MSG_ERROR("while listing triggers for trigger matching; trigger \"" << item << "\" extracted from \"" << name << "\" is not recognized");
+				return false;
+			}
+			if(extraItems.emplace(h).second) updatedTriggers.emplace_back(def->second);
+			if(len == std::string::npos) break;
+			pos += len + 4;
+			len = name.find("_OR_", pos);
+			if(len != std::string::npos) len -= pos;
+		}
+	}
+	triggers.swap(updatedTriggers);
+	return true;
+}
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/Root/TrigGlobalEfficiencyCorrectionTool.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/Root/TrigGlobalEfficiencyCorrectionTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..d76de401b996c69f90fbfab47c690c9234cfd475
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/Root/TrigGlobalEfficiencyCorrectionTool.cxx
@@ -0,0 +1,1074 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+// contact: jmaurer@cern.ch
+
+#include "TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrectionTool.h"
+#include "TrigGlobalEfficiencyCorrection/ImportData.h"
+#include "TrigGlobalEfficiencyCorrection/CheckConfig.h"
+#include "TrigGlobalEfficiencyCorrection/Calculator.h"
+#include "TrigGlobalEfficiencyCorrection/Efficiencies.h"
+#include "TrigGlobalEfficiencyCorrection/Lepton.h"
+#include "xAODEventInfo/EventInfo.h"
+#include "xAODEgamma/Electron.h"
+#include "xAODEgamma/Photon.h"
+
+#include <array>
+#include <boost/container/flat_set.hpp>
+#include <sstream>
+#include <algorithm>
+#include <cctype>
+#include <iterator>
+#include <type_traits>
+#include <limits>
+#include <regex>
+
+using CheckConfig = TrigGlobEffCorr::CheckConfig;
+using Calculator = TrigGlobEffCorr::Calculator;
+using ImportData = TrigGlobEffCorr::ImportData;
+using Efficiencies = TrigGlobEffCorr::Efficiencies;
+using Lepton = TrigGlobEffCorr::Lepton;
+
+
+TrigGlobalEfficiencyCorrectionTool::TrigGlobalEfficiencyCorrectionTool(const std::string& name)
+	: asg::AsgTool(name), m_trigMatchTool("", nullptr), m_checkElectronLegTag(false), m_checkMuonLegTag(false), m_seed(1), m_validTrigMatchTool(false),
+	m_runNumberDecorator("RandomRunNumber"), m_calculator()
+{
+	declareProperty("ElectronEfficiencyTools", m_suppliedElectronEfficiencyTools, "electron MC efficiency tools (one for each kind of electron trigger leg)");
+	declareProperty("ElectronScaleFactorTools", m_suppliedElectronScaleFactorTools, "electron scale factor tools (one for each kind of electron trigger leg)");
+	declareProperty("PhotonEfficiencyTools", m_suppliedPhotonEfficiencyTools, "photon MC efficiency tools (one for each kind of photon trigger leg)");
+	declareProperty("PhotonScaleFactorTools", m_suppliedPhotonScaleFactorTools, "photon scale factor tools (one for each kind of photon trigger leg)");	
+	declareProperty("MuonTools", m_suppliedMuonTools, "muon efficiency/scale factor tool (one per year)");
+	declareProperty("ListOfLegsPerTool", m_legsPerTool, "comma-separated list of trigger legs supported by each electron or photon tool");
+	declareProperty("TriggerCombination", m_triggerCb, "map of trigger combination per period and/or range of runs");
+	declareProperty("TriggerCombination2015", m_triggerCbPerYear["2015"] = "", "trigger combination \"trigger1 || trigger2 || ...\"");
+	declareProperty("TriggerCombination2016", m_triggerCbPerYear["2016"] = "", "trigger combination \"trigger1 || trigger2 || ...\"");
+	declareProperty("TriggerCombination2017", m_triggerCbPerYear["2017"] = "", "trigger combination \"trigger1 || trigger2 || ...\"");
+	declareProperty("TriggerCombination2018", m_triggerCbPerYear["2018"] = "", "trigger combination \"trigger1 || trigger2 || ...\"");
+	declareProperty("LeptonTagDecorations", m_leptonTagDecorations = "", 
+		"comma-separated list of decorations for the lepton selection tags, ordered by increasing tightness. "
+		"If a name ends with =, the tag is the decorated value, otherwise it is the decoration name");
+	declareProperty("ListOfTagsPerTool", m_tagsPerTool, "comma-separated list of lepton selection tags associated to each tool");
+	declareProperty("ElectronLegsPerTag", m_electronLegsPerTag, "DEPRECATED, use ListOfLegsPerTag instead");
+	declareProperty("MuonLegsPerTag", m_muonLegsPerTag, "DEPRECATED, use ListOfLegsPerTag instead");
+	declareProperty("ListOfLegsPerTag", m_legsPerTag, "map of allowed trigger legs for each tag");
+	declareProperty("NumberOfToys", m_numberOfToys = 0, "if different from 0, use toy experiments instead of explicit formulas");
+	declareProperty("OverrideThresholds", m_overrideThresholds, "new thresholds (in MeV) for the plateaux of the indicated trigger legs -- use at your own risk!");
+	declareProperty("UseInternalSeed", m_useInternalSeed = false, "do not use event number as random number generation seed");
+	declareProperty("TriggerMatchingTool", m_trigMatchTool, "handle to an IMatchingTool instance");
+	
+	m_cpCode.ignore();
+}
+
+TrigGlobalEfficiencyCorrectionTool::~TrigGlobalEfficiencyCorrectionTool()
+{
+	m_cpCode.ignore();
+}
+
+StatusCode TrigGlobalEfficiencyCorrectionTool::initialize()
+{
+	if(m_initialized)
+	{
+		ATH_MSG_ERROR("Tool has already been initialized");
+		return StatusCode::FAILURE;
+	}
+	ATH_MSG_INFO ("Initializing " << name() << "...");
+	
+	m_dictionary.emplace(0, "");
+	
+	if(!processDeprecatedProperties()) return StatusCode::FAILURE;
+	
+	ATH_MSG_DEBUG("Importing data from configuration files");
+	ImportData data(*this);
+	if(!data.importAll(m_overrideThresholds)) return StatusCode::FAILURE;
+	/// Copy the following variables from data as the latter is short-lived
+	m_hierarchyMeta = data.getHierarchyMeta();
+	m_hierarchyData = data.getHierarchyData();
+	m_thresholds = data.getTriggerThresholds();
+	
+	ATH_MSG_DEBUG("Retrieving tools"); 
+	if(m_suppliedElectronEfficiencyTools.retrieve() != StatusCode::SUCCESS
+		|| m_suppliedElectronScaleFactorTools.retrieve() != StatusCode::SUCCESS
+		|| m_suppliedMuonTools.retrieve() != StatusCode::SUCCESS
+		|| m_suppliedPhotonEfficiencyTools.retrieve() != StatusCode::SUCCESS
+		|| m_suppliedPhotonScaleFactorTools.retrieve() != StatusCode::SUCCESS)
+	{
+		ATH_MSG_ERROR("Unable to retrieve CP tools");
+		return StatusCode::FAILURE;
+	}
+	
+	ATH_MSG_DEBUG("Retrieving trigger matching tool (if provided)");
+	m_validTrigMatchTool = false;
+	if(m_trigMatchTool.name() != "")
+	{
+		if(m_trigMatchTool.retrieve() != StatusCode::SUCCESS)
+		{
+			ATH_MSG_ERROR("Unable to retrieve trigger matching tool");
+			return StatusCode::FAILURE;
+		}
+		ATH_MSG_DEBUG("Trigger matching support enabled");
+		m_validTrigMatchTool = true;
+	}
+	
+	ATH_MSG_DEBUG("Basic checks");
+	CheckConfig checks(*this);
+	if(!checks.basicConfigChecks()) return StatusCode::FAILURE;
+
+	ATH_MSG_DEBUG("Enumerating tools");
+	flat_set<std::size_t> collectedElectronTags, collectedMuonTags, collectedPhotonTags;
+	if(!enumerateTools(data, m_suppliedElectronEfficiencyTools, m_electronEffToolIndex, collectedElectronTags)
+		|| !enumerateTools(data, m_suppliedElectronScaleFactorTools, m_electronSfToolIndex, collectedElectronTags)
+		|| !enumerateTools(data, m_suppliedMuonTools, m_muonToolIndex, collectedMuonTags)
+		|| !enumerateTools(data, m_suppliedPhotonEfficiencyTools, m_photonEffToolIndex, collectedPhotonTags)
+		|| !enumerateTools(data, m_suppliedPhotonScaleFactorTools, m_photonSfToolIndex, collectedPhotonTags))
+	{
+		return StatusCode::FAILURE;
+	}
+	
+	ATH_MSG_DEBUG("Loading user-defined trigger combination"); 
+	bool useDefaultElectronTools = (m_suppliedElectronEfficiencyTools.size()==1) && (m_suppliedElectronScaleFactorTools.size()==1) && (m_legsPerTool.size()==0);
+	bool useDefaultPhotonTools = (m_suppliedPhotonEfficiencyTools.size()==1) && (m_suppliedPhotonScaleFactorTools.size()==1) && (m_legsPerTool.size()==0);
+	if(!loadTriggerCombination(data, useDefaultElectronTools, useDefaultPhotonTools)) return StatusCode::FAILURE;
+	
+	ATH_MSG_DEBUG("Loading lepton selection tags decorators"); 
+	if(!loadTagDecorators(collectedElectronTags, collectedMuonTags, collectedPhotonTags)) return StatusCode::FAILURE;
+
+	ATH_MSG_DEBUG("Loading list of legs allowed for each tag"); 
+	if(!loadListOfLegsPerTag(collectedElectronTags, collectedMuonTags, collectedPhotonTags)) return StatusCode::FAILURE;
+	
+	ATH_MSG_DEBUG("Advanced checks");
+	if(!checks.advancedConfigChecks()) return StatusCode::FAILURE;
+	
+	ATH_MSG_INFO("Initialization successful"); 
+	m_initialized = true;
+	return StatusCode::SUCCESS;
+}
+
+bool TrigGlobalEfficiencyCorrectionTool::processDeprecatedProperties()
+{
+	if(m_electronLegsPerTag.size())
+	{
+		ATH_MSG_WARNING("The property 'ElectronLegsPerTag' is deprecated, please eventually use 'ListOfLegsPerTag' instead");
+		for(auto& kv : m_electronLegsPerTag)
+		{
+			auto insert = m_legsPerTag.insert(kv);
+			if(!insert.second) insert.first->second += "," + kv.second;
+		}
+	}
+	if(m_muonLegsPerTag.size())
+	{
+		ATH_MSG_WARNING("The property 'MuonLegsPerTag' is deprecated, please eventually use 'ListOfLegsPerTag' instead");
+		for(auto& kv : m_muonLegsPerTag)
+		{
+			auto insert = m_legsPerTag.insert(kv);
+			if(!insert.second) insert.first->second += "," + kv.second;
+		}
+	}
+	return true;
+}
+
+bool TrigGlobalEfficiencyCorrectionTool::parseTagString(const std::string& tagstring, flat_set<std::size_t>& allTags)
+{
+	bool success = true;
+	const std::size_t star = m_hasher("*");
+	for(std::size_t tag : listNonOrderedCSValues(tagstring,success))
+	{
+		allTags.insert((tag!=star)? tag : 0);
+	}
+	if(!success)
+	{
+		ATH_MSG_ERROR("List of tags \"" << tagstring << "\" is not provided in a valid format");
+	}
+	return success;
+}
+
+template<class CPTool>
+bool TrigGlobalEfficiencyCorrectionTool::enumerateTools(ImportData& data, ToolHandleArray<CPTool>& suppliedTools, 
+	std::map<ToolKey, std::size_t>& toolIndex, flat_set<std::size_t>& collectedTags)
+{
+	bool success = true;
+	for(unsigned index=0;index<suppliedTools.size();++index)
+	{
+		auto& handle = suppliedTools[index];
+		const std::string& name = handle.name(), altname = handle->name(); // athena: not always the same
+		flat_set<ToolKey> listOfLegs;
+		/// Find the legs associated to this tool ("ListOfLegsPerTool" property)
+		if(suppliedTools.size()!=1 || m_legsPerTool.size()!=0)
+		{
+			auto itrLegs = m_legsPerTool.find(name);
+			if(itrLegs == m_legsPerTool.end()) itrLegs = m_legsPerTool.find(altname);
+			if(itrLegs != m_legsPerTool.end())
+			{
+				listOfLegs = parseListOfLegs(data, itrLegs->second, success);
+				if(!success)
+				{
+					ATH_MSG_ERROR("The 'ListOfLegsPerTool' property has an invalid entry for the tool'" << name << "'");
+					continue;
+				}
+			}
+			else if(std::is_same<CPTool,IAsgElectronEfficiencyCorrectionTool>::value 
+				|| std::is_same<CPTool,IAsgPhotonEfficiencyCorrectionTool>::value)
+			{
+				ATH_MSG_ERROR("Property 'ListOfLegsPerTool' empty for tool '" << name << "'");
+				success = false;
+				continue;
+			}
+			else
+			{
+				listOfLegs.emplace();
+			}
+		}
+		else listOfLegs.emplace();
+		/// Find the tags associated to this tool ("ListOfTagsPerTool" property)
+		flat_set<std::size_t> tags;
+		auto itrTags = m_tagsPerTool.find(name);
+		if(itrTags == m_tagsPerTool.end()) itrTags = m_tagsPerTool.find(altname);
+		if(itrTags != m_tagsPerTool.end())
+		{
+			success = success && parseTagString(itrTags->second, tags);
+			collectedTags.insert(tags.begin(), tags.end());
+		}
+		else tags.emplace(0);
+
+		/// Store
+		unsigned short nUncheckedLegs = 0;
+		for(auto& key : listOfLegs)
+		{
+			std::size_t leg = key.hash;
+			if(leg)
+			{
+				auto flavour = data.associatedLeptonFlavour(leg, success);
+				if(!(flavour==xAOD::Type::Electron && std::is_same<CPTool,IAsgElectronEfficiencyCorrectionTool>::value)
+					&& !(flavour==xAOD::Type::Photon && std::is_same<CPTool,IAsgPhotonEfficiencyCorrectionTool>::value))
+				{
+					ATH_MSG_ERROR("Unexpected association of trigger leg '" << m_dictionary[leg] << "' to tool '" << name << "'");
+					success = false;
+				}
+			}
+			for(std::size_t tag : tags)
+			{
+				if(!toolIndex.emplace(ToolKey(leg, tag, key.boundaries), index).second)
+				{
+					if(leg && tag) ATH_MSG_ERROR("Multiple tools associated to the selection tag '" <<  m_dictionary[tag] << "' for the trigger leg '" << m_dictionary[leg] << "'");
+					else if(leg) ATH_MSG_ERROR("Multiple tools associated to the trigger leg '" <<  m_dictionary[leg] << "'");
+					else if(tag) ATH_MSG_ERROR("Multiple tools associated to the selection tag '" <<  m_dictionary[tag] << "'");
+					else ATH_MSG_ERROR("Multiple tools not associated to any trigger leg / selection tag");
+					success  = false;
+				}
+			}
+			++nUncheckedLegs;
+		}
+		if(!nUncheckedLegs)
+		{
+			ATH_MSG_ERROR("Tool " << name << " hasn't been associated to any trigger leg");
+			success = false;
+		}
+	}
+	return success;
+}
+
+auto TrigGlobalEfficiencyCorrectionTool::parseListOfLegs(ImportData& data, const std::string& inputList, bool& success) -> flat_set<ToolKey> 
+{
+	if(!inputList.length()) return {};
+	std::regex rx("\\s*([[:alnum:]_]+)\\s*(?:\\[\\s*([^,\\[\\]]+)\\s*\\]\\s*)?(?:,|$)");
+	flat_set<ToolKey> keys;
+	std::smatch sm;
+	auto itr=inputList.cbegin();
+	do
+	{
+		if(sm.ready()) itr += sm.length();
+		if(!std::regex_search(itr, inputList.cend(), sm ,rx) || sm.prefix().length())
+		{
+			ATH_MSG_ERROR("Invalid format for the property \"ListOfLegsPerTool\"");
+			success = false;
+			break;
+		}
+		std::size_t leg = m_hasher(sm[1].str());
+		if(m_thresholds.find(leg) == m_thresholds.end())
+		{
+			ATH_MSG_ERROR("Unknown trigger leg '" << sm[1].str() << "' found in 'ListOfLegsPerTool'");
+			success = false;
+			continue;
+		}
+		ToolKey key(leg, 0);
+		if(sm.length(2))
+		{
+			if(!data.getPeriodBoundaries(sm[2].str(), key.boundaries))
+			{
+				ATH_MSG_ERROR("Invalid period \"" << sm[2].str() << "\"found in the property \"ListOfLegsPerTool\"");
+				success = false;
+				continue;
+			}
+		}
+		if(!keys.emplace(key).second)
+		{
+			ATH_MSG_ERROR("Trigger leg '" << sm[1].str() << "' mentioned several times with overlapping time periods in the property 'ListOfLegsPerTool'");
+			success = false;
+			continue;
+		}
+	} while(sm.suffix().length());
+	return keys;
+}
+
+bool TrigGlobalEfficiencyCorrectionTool::loadTriggerCombination(ImportData& data, bool useDefaultElectronTools, bool useDefaultPhotonTools)
+{
+	bool success = true, mustBeEmpty = m_triggerCb.size();
+	for(auto& kv : m_triggerCbPerYear)
+	{
+		if(!kv.second.size()) continue;
+		if(mustBeEmpty)
+		{
+			ATH_MSG_ERROR ("You're not allowed to use simultaneously the 'TriggerCombination' and 'TriggerCombination" << kv.first << "' properties.");
+			return false;
+		}
+		m_triggerCb.insert(kv);
+	}
+	
+	m_calculator = std::unique_ptr<Calculator>(new Calculator(*this, m_triggerCb.size()));
+	std::set<std::size_t> allUniqueElectronLegs, allUniquePhotonLegs;
+	for(auto& kv : m_triggerCb)
+	{
+		std::pair<unsigned, unsigned> boundaries;
+		if(!data.getPeriodBoundaries(kv.first, boundaries))
+		{
+			success = false;
+			continue;
+		}
+		std::size_t uniqueElectronLeg = !useDefaultElectronTools, uniquePhotonLeg = !useDefaultPhotonTools;
+		if(!m_calculator->addPeriod(data, boundaries, kv.second, m_numberOfToys, uniqueElectronLeg, uniquePhotonLeg))
+		{
+			success = false;
+			continue;
+		}
+		if(uniqueElectronLeg && useDefaultElectronTools) allUniqueElectronLegs.insert(uniqueElectronLeg);
+		if(uniquePhotonLeg && useDefaultPhotonTools) allUniquePhotonLegs.insert(uniquePhotonLeg);
+	}
+	if(!success) return false;
+	
+	auto remapTools = [](auto& toolIndex, auto& allUniqueLegs) {
+		typename std::remove_reference<decltype(toolIndex)>::type remappedToolIndex;
+		for(std::size_t leg : allUniqueLegs)
+		{
+			if(!leg) continue;
+			for(auto& kv : toolIndex)
+			{
+				/// tools have not been associated with legs so far so the key hash is only the tag
+				const ToolKey& key = kv.first;
+				remappedToolIndex.emplace(ToolKey(leg, key.hash, key.boundaries), kv.second);
+			}
+		}
+		toolIndex.swap(remappedToolIndex);
+	};
+	
+	if(useDefaultElectronTools && allUniqueElectronLegs.size())
+	{
+		remapTools(m_electronSfToolIndex, allUniqueElectronLegs);
+		remapTools(m_electronEffToolIndex, allUniqueElectronLegs);
+	}
+	if(useDefaultPhotonTools && allUniquePhotonLegs.size())
+	{
+		remapTools(m_photonSfToolIndex, allUniquePhotonLegs);
+		remapTools(m_photonEffToolIndex, allUniquePhotonLegs);
+	}
+	return success;
+}
+
+bool TrigGlobalEfficiencyCorrectionTool::loadTagDecorators(const flat_set<std::size_t>& collectedElectronTags,
+	const flat_set<std::size_t>& collectedMuonTags, const flat_set<std::size_t>& collectedPhotonTags)
+{
+	bool success = true;
+	
+	flat_set<std::size_t> collectedTags(collectedElectronTags);
+	collectedTags.insert(collectedMuonTags.begin(), collectedMuonTags.end());
+	collectedTags.insert(collectedPhotonTags.begin(), collectedPhotonTags.end());
+	flat_set<std::size_t> allTags = collectedTags;
+	
+	/// Initialize decorators
+	/// Note: can't use listNonOrderedCSValues() as the order in which decorations are listed must be preserved
+	std::stringstream ss(TrigGlobEffCorr::removeWhitespaces(m_leptonTagDecorations));
+	std::string decoration;
+	flat_set<std::size_t> allDecorations;
+	while(std::getline(ss,decoration,','))
+	{
+		if(!decoration.length() || decoration=="?")
+		{
+			ATH_MSG_ERROR("Found an empty string in the 'LeptonTagDecorations' property");
+			success = false;
+			break;
+		}
+		
+		bool found = false, suffixed = false;
+		std::size_t h;
+		if(decoration.back() != '?')
+		{
+			/// tag = decorator name (considered tagged if decorated value != 0)
+			h = m_hasher(decoration);
+			found = (allTags.find(h) != allTags.end());
+		}
+		else
+		{
+			/// tag = decorator name (without '?') + decorated value
+			decoration.pop_back();
+			suffixed = true;
+			for(std::size_t tag : allTags) /// find a tag of the form <name><integer suffix>
+			{
+				auto& s = m_dictionary[tag];
+				if(s.find(decoration) != 0) continue;
+				if(std::all_of(s.begin() + decoration.length(),s.end(),[](char c){ return std::isdigit(c); }))
+				{
+					found = true;
+					break;
+				}
+			}
+			h = m_hasher(decoration);
+		}
+		if(!allDecorations.insert(h).second)	
+		{
+			ATH_MSG_ERROR("The selection tag '" << decoration << "' is listed twice in the property 'LeptonTagDecorations'");
+			success = false;
+			continue;
+		}
+		if(!found)
+		{
+			ATH_MSG_ERROR("the selection tag '" << decoration << "' is only referred to in the property 'LeptonTagDecorations'");
+			success = false;
+			continue;
+		}
+		m_leptonTagDecorators.emplace_back(decoration,h,suffixed);
+		m_dictionary.emplace(h,decoration);
+	}
+	std::reverse(m_leptonTagDecorators.begin(),m_leptonTagDecorators.end());
+	if(!success) return false;
+	
+	/// Check there's a suitable decorator for all tags associated to CP tools via ListOfTagsPerTool
+	for(std::size_t tag : collectedTags)
+	{
+		if(!tag) continue;
+		auto itr = std::find_if(m_leptonTagDecorators.begin(),m_leptonTagDecorators.end(),
+			[tag](const TagDecorator& ltd){ return (!ltd.suffixed) && (ltd.hash==tag); });
+		if(itr != m_leptonTagDecorators.end()) continue; /// ok, there's a direct match
+		bool found = false;
+		auto& s= m_dictionary[tag];
+		for(auto& ltd : m_leptonTagDecorators)
+		{
+			auto& name = m_dictionary[ltd.hash];
+			if(s.find(name)!=0) continue;
+			if(std::all_of(s.begin()+name.length(),s.end(),[](char c){ return std::isdigit(c); }))
+			{
+				found = true;
+				break;
+			}
+		}
+		if(!found)
+		{
+			ATH_MSG_ERROR("the selection tag '" << m_dictionary[tag] << "' hasn't been found in in 'LeptonTagDecorations'");
+			success = false;
+			continue;
+		}
+	}
+	if(!success) return false;
+
+	return success;
+}
+
+bool TrigGlobalEfficiencyCorrectionTool::loadListOfLegsPerTag(const flat_set<std::size_t>& /*collectedElectronTags*/, const flat_set<std::size_t>& /*collectedMuonTags*/, const flat_set<std::size_t>& /*collectedPhotonTags*/)
+{
+	bool success = true;
+	
+	/// For safety...
+	/*
+	if(!m_muonLegsPerTag.size() && m_leptonTagDecorators.size())
+	{
+		ATH_MSG_INFO("Lepton selection tag decorators have been configured, but the property 'MuonLegsPerTag' is empty. "
+			"Muons will be considered suitable for any trigger leg regardless of their tag.");
+	}
+	if(!m_electronLegsPerTag.size() && m_leptonTagDecorators.size())
+	{
+		ATH_MSG_INFO("Lepton selection tag decorators have been configured, but the property 'ElectronLegsPerTag' is empty. "
+			"Electrons will be considered suitable for any trigger leg that has an associated SF tool for that tag.");
+	}
+	*/
+	
+	/// Create the hash list of legs allowed for each tag; check that a CP tool is available for each combination
+	for(auto& kv : m_legsPerTag)
+	{
+		std::size_t tag;
+		if(!kv.first.size() || kv.first=="*") tag = 0;
+		else tag = m_hasher(kv.first);
+		auto listOfLegs = listNonOrderedCSValues(kv.second,success);
+		if(!success)
+		{
+			ATH_MSG_ERROR("The property 'ListOfLegsPerTag' has an invalid entry for the tag '" << kv.first << "'");
+			break;
+		}
+		for(std::size_t leg : listOfLegs)
+		{
+			auto type = ImportData::associatedLeptonFlavour(m_dictionary[leg], success);
+			if(type==xAOD::Type::Electron && m_electronSfToolIndex.find(ToolKey(leg, tag))==m_electronSfToolIndex.end())
+			{
+				ATH_MSG_ERROR("No electron tool has been provided for trigger leg '" << m_dictionary[leg] << "' and selection tag " << kv.first
+					<< " mentioned in the property 'ListOfLegsPerTag'");
+				success = false;
+			}
+			else if(type==xAOD::Type::Muon && m_muonToolIndex.find(ToolKey(0, tag))==m_muonToolIndex.end())
+			{
+				ATH_MSG_ERROR("No muon tool has been provided for selection tag " << kv.first
+					<< " mentioned in the property 'ListOfLegsPerTag'");
+				success = false;
+			}
+			else if(type==xAOD::Type::Photon && m_photonSfToolIndex.find(ToolKey(leg, tag))==m_photonSfToolIndex.end())
+			{
+				ATH_MSG_ERROR("No photon tool has been provided for trigger leg '" << m_dictionary[leg] << "' and selection tag " << kv.first
+					<< " mentioned in the property 'ListOfLegsPerTag'");
+				success = false;
+			}
+			if(m_validLegTagPairs.insert(leg ^ tag).second)
+			{
+				if(type==xAOD::Type::Electron) m_checkElectronLegTag = true;
+				if(type==xAOD::Type::Muon) m_checkMuonLegTag = true;
+				if(type==xAOD::Type::Photon) m_checkPhotonLegTag = true;
+			}
+			else
+			{
+				ATH_MSG_ERROR("The combination of trigger leg '" << m_dictionary[leg] << "' and selection tag " << kv.first
+					<< " is mentioned more than once in the property 'ListOfLegsPerTag'");
+				success = false;
+			}
+		}
+	}
+	if(!success) return false;
+	
+	return success;
+}
+
+inline bool TrigGlobalEfficiencyCorrectionTool::checkAndRecord(CP::CorrectionCode&& cc)
+{
+   cc.setChecked();
+   if(cc > m_cpCode) m_cpCode = cc;
+   return cc==CP::CorrectionCode::Ok;
+}
+
+/// Parses the input string (comma-separated list) and returns a set of hashes
+/// Watch out: since a flat_set is returned, the order of the entries in the input list is not preserved, as hinted by the name of the function
+auto TrigGlobalEfficiencyCorrectionTool::listNonOrderedCSValues(const std::string& s, bool& success) -> flat_set<std::size_t>
+{
+	std::stringstream ss(TrigGlobEffCorr::removeWhitespaces(s));
+	flat_set<std::size_t> hashes;
+	std::string token;
+	while(std::getline(ss, token, ','))
+	{
+		if(token.length())
+		{
+			std::size_t h = m_hasher(token);
+			if(hashes.insert(h).second)
+			{
+				m_dictionary.emplace(h, std::move(token));
+			}
+			else
+			{
+				success = false;
+				ATH_MSG_ERROR("Found duplicate entry while parsing comma-separated list '" << s << "'");
+			}
+		}
+		else
+		{
+			success = false;
+			ATH_MSG_ERROR("Found null-length entry while parsing comma-separated list '" << s << "'");
+		}
+	}
+	if(!success) hashes.clear();
+	return hashes;
+}
+
+template<class ParticleType>
+bool TrigGlobalEfficiencyCorrectionTool::getEgammaTriggerLegEfficiencies(const ParticleType* p, unsigned runNumber, std::size_t leg, std::size_t tag, Efficiencies& efficiencies)
+{
+	/// Common implementation for electrons and photons
+	auto ptype = []() { return std::is_same<ParticleType, xAOD::Electron>::value? "electron" : std::is_same<ParticleType, xAOD::Photon>::value? "photon" : "<unknown type>"; };
+	auto pp = static_cast<const xAOD::IParticle*>(p);
+	ATH_MSG_DEBUG("Retrieving efficiencies for " << ptype() << ' ' << p << " (pt=" << pp->pt() << ", eta=" << pp->eta() 
+		<< ", tag='" << m_dictionary[tag] << "') for trigger leg " << m_dictionary[leg]);
+	auto itrSf = GetScaleFactorToolIndex(p).find(ToolKey(leg, tag, runNumber));
+	auto itrEff = GetEfficiencyToolIndex(p).find(ToolKey(leg, tag, runNumber));
+	if(itrSf==GetScaleFactorToolIndex(p).end() || itrEff==GetEfficiencyToolIndex(p).end())
+	{
+		if(!tag) ATH_MSG_ERROR("Unable to find " << ptype() << " tools needed for trigger leg " << m_dictionary[leg] << " (run number = " << runNumber << ")");
+		else ATH_MSG_ERROR("Unable to find " << ptype() << " tools needed for trigger leg " << m_dictionary[leg] << " and selection tag " << m_dictionary[tag] 
+			<< " (run number = " << runNumber << ")");
+		return false;
+	}
+	double sf;
+	bool success = checkAndRecord(GetScaleFactorTool(p, itrSf->second).getEfficiencyScaleFactor(*p, sf))
+		&& checkAndRecord(GetEfficiencyTool(p, itrEff->second).getEfficiencyScaleFactor(*p, efficiencies.mc()));
+	efficiencies.data() = sf * efficiencies.mc();
+	ATH_MSG_DEBUG("found for that " << ptype() << " eff(data) = " << efficiencies.data()<<" and eff(MC) = "<<efficiencies.mc());
+	return success;
+}
+
+bool TrigGlobalEfficiencyCorrectionTool::getTriggerLegEfficiencies(const xAOD::Electron* p, unsigned runNumber, std::size_t leg, std::size_t tag, Efficiencies& efficiencies)
+{
+	return getEgammaTriggerLegEfficiencies(p, runNumber, leg, tag, efficiencies);
+}
+
+bool TrigGlobalEfficiencyCorrectionTool::getTriggerLegEfficiencies(const xAOD::Photon* p, unsigned runNumber, std::size_t leg, std::size_t tag, Efficiencies& efficiencies)
+{
+	return getEgammaTriggerLegEfficiencies(p, runNumber, leg, tag, efficiencies);
+}
+
+bool TrigGlobalEfficiencyCorrectionTool::getTriggerLegEfficiencies(const xAOD::Muon* p, std::size_t leg, std::size_t tag, Efficiencies& efficiencies)
+{
+	ATH_MSG_DEBUG("Retrieving efficiencies for muon " <<p << " (pt=" << p->pt() << ", eta=" << p->eta() 
+		<< ", tag='" << m_dictionary[tag] << "') for trigger leg " << m_dictionary[leg]);
+	auto itr = m_muonToolIndex.find(ToolKey(0, tag, 0));
+	if(itr==m_muonToolIndex.end())
+	{
+		if(!tag) ATH_MSG_ERROR("Unable to find muon tool");
+		else ATH_MSG_ERROR("Unable to find muon tool needed for selection tag " << m_dictionary[tag]);
+		m_cpCode = CP::CorrectionCode::Error;
+		return false;
+	}
+	auto& tool = *m_suppliedMuonTools[itr->second];
+	auto& hltTrig = m_dictionary[leg ^ 0xB0DDD56fF8E3250D];
+	if(!hltTrig.size())
+	{
+		hltTrig = "HLT_" + m_dictionary[leg];
+		while(true)
+		{
+			std::size_t i = hltTrig.find("_OR_m");
+			if(i == std::string::npos) break;
+			hltTrig.insert(i+4, "HLT_", 4);
+		}
+	}
+	bool success = checkAndRecord(tool.getTriggerEfficiency(*p, efficiencies.mc(), hltTrig, kFALSE))
+		&& checkAndRecord(tool.getTriggerEfficiency(*p, efficiencies.data(), hltTrig, kTRUE));
+	ATH_MSG_DEBUG("found for that muon eff(data) = " << efficiencies.data()<<" and eff(MC) = "<<efficiencies.mc());
+	return success;
+}
+
+bool TrigGlobalEfficiencyCorrectionTool::retrieveRunNumber(unsigned& runNumber)
+{
+	runNumber = 0;
+	auto eventInfo = evtStore()->retrieve<const xAOD::EventInfo>("EventInfo");
+	if(!eventInfo)
+	{
+		ATH_MSG_ERROR("Can't retrieve 'EventInfo' from evtStore()");
+		return false;
+	}
+	if(eventInfo->eventType(xAOD::EventInfo::IS_SIMULATION))
+	{
+		if(!m_runNumberDecorator.isAvailable(*eventInfo))
+		{
+			ATH_MSG_ERROR("Can't retrieve 'RandomRunNumber' from EventInfo");
+			return false;
+		}
+		runNumber = m_runNumberDecorator(*eventInfo);
+	}
+	else runNumber = eventInfo->runNumber();
+	return true;
+}
+
+bool TrigGlobalEfficiencyCorrectionTool::retrieveEventNumber(unsigned long& eventNumber)
+{
+	auto eventInfo = evtStore()->retrieve<const xAOD::EventInfo>("EventInfo");
+	if(!eventInfo)
+	{
+		ATH_MSG_WARNING("Can't retrieve event number from evtStore()");
+		eventNumber = 0;
+		return false;
+	}
+	eventNumber = eventInfo->eventNumber();
+	return true;
+}
+
+template<class Particle>
+bool TrigGlobalEfficiencyCorrectionTool::updateLeptonList(LeptonList& leptons, const std::vector<const Particle*>& particles)
+{
+	for(auto lep : particles)
+	{
+		std::size_t tag = 0;
+		for(auto& ltd: m_leptonTagDecorators)
+		{
+			if(ltd.decorator.isAvailable(*lep))
+			{
+				char v = ltd.decorator(*lep);
+				if(v)
+				{
+					if(ltd.suffixed) /// tag = <name>+<value>
+					{
+						std::string s = m_dictionary.at(ltd.hash) + std::to_string(v); 
+						tag = m_hasher(s);
+						m_dictionary.emplace(tag, s);
+					}
+					else tag = ltd.hash;
+				}
+			}
+		}
+		leptons.emplace_back(lep, tag);
+	}
+	return true;
+}
+
+CP::CorrectionCode TrigGlobalEfficiencyCorrectionTool::getEfficiencyScaleFactor(const std::vector<const xAOD::IParticle*>& leptons, double& efficiencyScaleFactor)
+{
+	unsigned runNumber;
+	if(!retrieveRunNumber(runNumber)) return CP::CorrectionCode::Error;
+	return getEfficiencyScaleFactor(runNumber, leptons, efficiencyScaleFactor);
+}
+
+CP::CorrectionCode TrigGlobalEfficiencyCorrectionTool::getEfficiency(const std::vector<const xAOD::IParticle*>& leptons, double& efficiencyData, double& efficiencyMc)
+{
+	unsigned runNumber;
+	if(!retrieveRunNumber(runNumber)) return CP::CorrectionCode::Error;
+	return getEfficiency(runNumber, leptons, efficiencyData, efficiencyMc);
+}
+
+CP::CorrectionCode TrigGlobalEfficiencyCorrectionTool::getEfficiencyScaleFactor(unsigned runNumber, const std::vector<const xAOD::IParticle*>& particles, double& efficiencyScaleFactor)
+{
+	m_cpCode = CP::CorrectionCode::Ok; /// ignores silently previous state + clears "checked" flag
+	efficiencyScaleFactor = 1.;
+	
+	Efficiencies efficiencies;
+	auto cc = getEfficiency(runNumber, particles, efficiencies.data(), efficiencies.mc());
+	if(cc == CP::CorrectionCode::Ok)
+	{
+		if(efficiencies.data()>0. && efficiencies.mc()>0.)
+		{
+			efficiencyScaleFactor = efficiencies.data() / efficiencies.mc();
+			return CP::CorrectionCode::Ok;
+		}
+		else
+		{
+			efficiencyScaleFactor = 1.;
+			ATH_MSG_WARNING("Efficiencies do not seem valid, forcing the scale factor to 1.");
+			return CP::CorrectionCode::OutOfValidityRange;
+		}
+	}
+	efficiencyScaleFactor = 1.;
+	return cc;
+}
+
+CP::CorrectionCode TrigGlobalEfficiencyCorrectionTool::getEfficiency(unsigned runNumber, const std::vector<const xAOD::IParticle*>& particles, double& efficiencyData, double& efficiencyMc)
+{
+	m_cpCode = CP::CorrectionCode::Ok; /// ignores silently previous state + clears "checked" flag
+	efficiencyData = 0.;
+	efficiencyMc = 0.;
+	
+	ATH_MSG_DEBUG("Computing global trigger efficiency for this event with  " << particles.size() << " lepton(s) as input; run number = " << runNumber);
+	
+	if(runNumber<266904 || runNumber>364292)
+	{
+		ATH_MSG_WARNING("Suspicious run number provided " << runNumber << ", continuing anyway");
+	}
+	
+	#ifdef XAOD_STANDALONE
+	if(!m_initialized)
+	{
+		ATH_MSG_WARNING("Tool hasn't been initialized, trying now");
+		if(initialize() != StatusCode::SUCCESS)
+		{
+			ATH_MSG_ERROR("Giving up.");
+			m_cpCode.ignore();
+			return CP::CorrectionCode::Error;
+		}
+	}
+	#endif
+	
+	LeptonList leptons;
+	updateLeptonList(leptons, particles);
+	
+	Efficiencies efficiencies;
+	if(m_calculator->compute(*this, leptons, runNumber, efficiencies))
+	{
+		efficiencyData = efficiencies.data();
+		efficiencyMc = efficiencies.mc();
+		if(efficiencies.data()<=0. || efficiencies.mc()<=0.)
+		{
+			ATH_MSG_WARNING("Efficiencies do not seem valid");
+			m_cpCode.ignore();
+			m_cpCode = CP::CorrectionCode::OutOfValidityRange;
+		}
+	}
+	else
+	{
+		m_cpCode.ignore();
+		m_cpCode = CP::CorrectionCode::Error;
+	}
+	return m_cpCode;
+}
+
+CP::CorrectionCode TrigGlobalEfficiencyCorrectionTool::checkTriggerMatching(bool& matched, const std::vector<const xAOD::IParticle*>& particles)
+{
+	unsigned runNumber;
+	if(!retrieveRunNumber(runNumber))
+	{
+		ATH_MSG_ERROR("Unable to retrieve run number, aborting checkTriggerMatching()");
+		return CP::CorrectionCode::Error;
+	}
+	if(!m_validTrigMatchTool)
+	{
+		ATH_MSG_ERROR("A valid IMatchingTool instance should be provided via the property 'TriggerMatchingTool'");
+		return CP::CorrectionCode::Error;
+	}
+	LeptonList leptons;
+	updateLeptonList(leptons, particles);
+	return m_calculator->checkTriggerMatching(*this, matched, leptons, runNumber)? CP::CorrectionCode::Ok : CP::CorrectionCode::Error;
+}
+
+CP::CorrectionCode TrigGlobalEfficiencyCorrectionTool::getRelevantTriggers(std::vector<std::string>& triggers)
+{
+	unsigned runNumber;
+	if(!retrieveRunNumber(runNumber))
+	{
+		ATH_MSG_ERROR("Unable to retrieve run number, aborting getRelevantTriggers()");
+		return CP::CorrectionCode::Error;
+	}
+	return m_calculator->getRelevantTriggersForUser(*this, triggers, runNumber)? CP::CorrectionCode::Ok : CP::CorrectionCode::Error;
+}
+
+CP::CorrectionCode TrigGlobalEfficiencyCorrectionTool::countTriggerLegs(const std::string& trigger, std::size_t& numberOfLegs)
+{
+	numberOfLegs = 0;
+	/// this abuses a little the role of the dictionary to act as a cache: 
+	/// for the key chosen as the trigger name hash XORed with the magic number,
+	/// the mapped value is not the name of the trigger, but a meaningless string whose size equals the number of legs
+	constexpr std::size_t magic = 0xa3bad03e613527c9;
+	const std::size_t name = m_hasher(trigger), key = name^magic;
+	std::string& value = m_dictionary[key];
+	if(!value.length())
+	{
+		ImportData data;
+		if(!data.importTriggers()) return CP::CorrectionCode::Error;
+		auto itr = data.getTriggerDefs().find(name);
+		if(itr == data.getTriggerDefs().end())
+		{
+			m_dictionary.erase(key);
+			ATH_MSG_ERROR("The trigger " << trigger << " is not recognized");
+			return CP::CorrectionCode::Error;
+		}
+		auto type = itr->second.type;
+		using TrigGlobEffCorr::TriggerType;
+		if(type & TriggerType::TT_SINGLELEPTON_FLAG) value = "~";
+		else if(type & TriggerType::TT_DILEPTON_FLAG) value = "~~";
+		else if(type & TriggerType::TT_TRILEPTON_FLAG) value = "~~~";
+		else
+		{
+			m_dictionary.erase(key);
+			ATH_MSG_ERROR("Unrecognized trigger type, implementation must be fixed!");
+			return CP::CorrectionCode::Error;
+		}
+	}
+	numberOfLegs = value.length();
+	return CP::CorrectionCode::Ok;
+}
+
+bool TrigGlobalEfficiencyCorrectionTool::aboveThreshold(const Lepton& lepton, std::size_t leg) const
+{
+	bool decision = (lepton.pt() >= m_thresholds.at(leg));
+	if(m_validLegTagPairs.size())
+	{
+		if((lepton.type()==xAOD::Type::Electron && m_checkElectronLegTag)
+			|| (lepton.type()==xAOD::Type::Muon && m_checkMuonLegTag)
+			|| (lepton.type()==xAOD::Type::Photon && m_checkPhotonLegTag))
+		{
+			decision = decision && (m_validLegTagPairs.find(leg ^ lepton.tag()) != m_validLegTagPairs.end());
+		}
+	}
+	ATH_MSG_DEBUG("Lepton " << lepton.particle() << " (pt=" << lepton.pt() << ") is " 
+		<< (decision?"":"not ") << "considered suitable for firing trigger leg " << m_dictionary.at(leg));
+	return decision;
+}
+
+std::size_t TrigGlobalEfficiencyCorrectionTool::getCombinedHash(const flat_set<std::size_t>& legs)
+{
+	if(legs.size() < 2) return 0;
+	std::size_t combinedHash = 0;
+	for(auto& leg : legs) combinedHash ^= leg;
+	return combinedHash;
+}
+
+std::size_t TrigGlobalEfficiencyCorrectionTool::getCombinedHash(std::size_t leg1, std::size_t leg2)
+{
+	return leg1 ^ leg2; /// returns 0 if leg1 == leg2, which is the correct behaviour
+}
+
+inline constexpr auto TrigGlobalEfficiencyCorrectionTool::forwardLegs(const flat_set<std::size_t>& legs) -> const flat_set<std::size_t>&
+{
+	return legs;
+}
+
+inline constexpr std::array<std::size_t,2> TrigGlobalEfficiencyCorrectionTool::forwardLegs(std::size_t leg1, std::size_t leg2)
+{
+	if(leg1<leg2) return {leg1, leg2};
+	else return {leg2, leg1};
+}
+
+template<class... ListOfLegs>
+unsigned long TrigGlobalEfficiencyCorrectionTool::getCachedTriggerLegsRanking(const Lepton& lepton, ListOfLegs... legs)
+{
+	auto combinedHash = getCombinedHash(legs...);
+	if(!combinedHash) return 0xFEDCBA9876543210; // only one (distinct) leg
+	auto cachedRankings = m_cachedLegRankings.equal_range(combinedHash);
+	auto rankingItr = cachedRankings.first;
+	float pt = lepton.pt();
+	while(rankingItr!=cachedRankings.second && (pt<rankingItr->second.minPt || pt>=rankingItr->second.maxPt)) ++rankingItr;
+	if(rankingItr == cachedRankings.second)
+	{
+		CachedRanking r = rankTriggerLegs(pt, forwardLegs(legs...));
+		if(r) m_cachedLegRankings.emplace(combinedHash, r);
+		return r.ranking;
+	}
+	return rankingItr->second.ranking;
+}
+
+template<class Container>
+auto TrigGlobalEfficiencyCorrectionTool::rankTriggerLegs(float pt, const Container& legs) -> CachedRanking
+{
+	const std::size_t nLegs = legs.size();
+	CachedRanking r;
+	r.ranking = std::numeric_limits<decltype(r.ranking)>::max();
+	r.minPt = 0.f;
+	r.maxPt = 1e12f;
+	if(nLegs >= 2*sizeof(r.ranking))
+	{
+		ATH_MSG_ERROR("Implementation currently doesn't support ranking of more than " << 2*sizeof(r.ranking) << " trigger legs");
+		return r;
+	}
+	std::vector<uint8_t> counts(nLegs);
+	
+	/// need not only to sort, but also to verify consistency for all pairs of legs (in case of configuration issue)
+	/// for that, use a O(n^2) algorithm and count for each leg the number of legs tighter than itself
+	auto legI = legs.begin();
+	for(unsigned i=0;i<nLegs;++i)
+	{
+		auto legJ = legI;
+		for(unsigned j=i+1;j<nLegs;++j)
+		{
+			++legJ;
+			bool found = false;
+			for(auto& meta : m_hierarchyMeta)
+			{
+				if(pt<meta.minPt || pt>=meta.maxPt) continue;
+				auto data = m_hierarchyData.begin() + meta.offset;
+				auto end = data + meta.nLegs;
+				auto a = std::find(data,end,*legI);
+				if(a==end) continue;
+				auto b = std::find(data,end,*legJ);
+				if(b==end) continue;
+				r.minPt = std::max(r.minPt, meta.minPt);
+				r.maxPt = std::min(r.maxPt, meta.maxPt);
+				++(counts[(a>b)? i : j]);
+				found = true;
+				break;
+			}
+			if(!found)
+			{
+				/// future: might search recursively whether some order can be found via transitivity through an intermediate leg
+				ATH_MSG_ERROR("Unable to rank trigger legs " << m_dictionary[*legI] << " and " << m_dictionary[*legJ]);
+				return r;
+			}
+		}
+		++legI;
+	}
+	decltype(r.ranking) ranking = 0;
+	for(unsigned i=0;i<nLegs;++i)
+	{
+		unsigned char index = std::find(counts.begin(), counts.end(), i) - counts.begin();
+		if(index >= nLegs)
+		{
+			ATH_MSG_ERROR("Inconsistency found while trying to rank " << nLegs << " trigger legs");
+			return r;
+		}
+		ranking = (ranking<<4 | index);
+	}
+	r.ranking = ranking;
+	return r;
+}
+
+std::size_t TrigGlobalEfficiencyCorrectionTool::getLoosestLeg(const Lepton& lepton, std::size_t leg1, std::size_t leg2, bool& success)
+{
+	auto ranking = getCachedTriggerLegsRanking(lepton, leg1, leg2);
+	if(CachedRanking::invalid(ranking))
+	{
+		success = false;
+		return -1;
+	}
+	return forwardLegs(leg1, leg2)[ranking&0xF];
+}
+
+std::pair<std::size_t,std::size_t> TrigGlobalEfficiencyCorrectionTool::getTwoLoosestLegs(const Lepton& lepton, const flat_set<std::size_t>& legs, bool& success)
+{
+	auto ranking = getCachedTriggerLegsRanking(lepton, legs);
+	if(CachedRanking::invalid(ranking))
+	{
+		success = false;
+		return {0, 0};
+	}
+	std::pair<std::size_t,std::size_t> looseLegs{0, 0};
+	looseLegs.first = *legs.nth(ranking&0xF);
+	if(legs.size() >= 2) looseLegs.second = *legs.nth((ranking>>4)&0xF);
+	return looseLegs;
+}
+
+std::size_t TrigGlobalEfficiencyCorrectionTool::getLoosestLegAboveThreshold(const Lepton& lepton, const flat_set<std::size_t>& legs, bool& success)
+{
+	flat_set<std::size_t> validLegs;
+	for(auto leg : legs)
+		if(aboveThreshold(lepton,leg)) validLegs.insert(leg);
+	if(validLegs.size()==0) return 0;
+	if(validLegs.size()==1) return *validLegs.begin();
+	auto ranking = getCachedTriggerLegsRanking(lepton, legs);
+	if(CachedRanking::invalid(ranking))
+	{
+		success = false;
+		return 0;
+	}
+	return *legs.nth(ranking&0xF);
+}
+
+std::vector<std::size_t> TrigGlobalEfficiencyCorrectionTool::getSortedLegs(const Lepton& lepton, const flat_set<std::size_t>& legs, bool& success)
+{
+	const int nLegs = legs.size();
+	unsigned long ranking = getCachedTriggerLegsRanking(lepton, legs);
+	if(CachedRanking::invalid(ranking))
+	{
+		success = false;
+		ranking = 0xFEDCBA9876543210;
+	}
+	std::vector<std::size_t> sorted_legs(nLegs);
+	for(int i=0; i<nLegs; ++i)
+	{
+		sorted_legs[i] = *legs.nth(ranking&0xF);
+		ranking >>= 4;
+	}
+	return sorted_legs;
+}
+
+CP::CorrectionCode TrigGlobalEfficiencyCorrectionTool::suggestElectronMapKeys(const std::map<std::string,std::string>& triggerCombination, 
+	const std::string& version, std::map<std::string,std::string>& legsPerKey)
+{
+	ImportData data;
+	bool success = data.suggestElectronMapKeys(triggerCombination, version, legsPerKey);
+	return success? CP::CorrectionCode::Ok : CP::CorrectionCode::Error;
+}
+
+bool TrigGlobalEfficiencyCorrectionTool::isAffectedBySystematic(const CP::SystematicVariation& systematic) const
+{
+	auto sys = affectingSystematics();
+	return !systematic.empty() && sys.find(systematic)!=sys.end();
+}
+
+CP::SystematicSet TrigGlobalEfficiencyCorrectionTool::affectingSystematics() const
+{
+	return CP::SystematicSet();
+}
+
+CP::SystematicSet TrigGlobalEfficiencyCorrectionTool::recommendedSystematics() const
+{
+	return {};
+}
+
+StatusCode TrigGlobalEfficiencyCorrectionTool::applySystematicVariation(const CP::SystematicSet&)
+{
+	return StatusCode::SUCCESS;
+}
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/Calculator.h b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/Calculator.h
new file mode 100644
index 0000000000000000000000000000000000000000..df75a5562711721ca93728dfb7bf74214f7f886a
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/Calculator.h
@@ -0,0 +1,235 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+// contact: jmaurer@cern.ch
+
+#ifndef TRIGGLOBALEFFICIENCYCORRECTION_CALCULATOR_H
+#define TRIGGLOBALEFFICIENCYCORRECTION_CALCULATOR_H 1
+
+#include "TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrectionTool.h"
+#include "TrigGlobalEfficiencyCorrection/ImportData.h"
+#include "TrigGlobalEfficiencyCorrection/Efficiencies.h"
+
+#include <map>
+#include <algorithm>
+#include <functional>
+#include <boost/container/flat_set.hpp>
+template<typename Key> using flat_set = boost::container::flat_set<Key>;
+
+namespace TrigGlobEffCorr
+{
+
+class Lepton;
+
+class Calculator : public asg::AsgMessaging
+{
+	using LeptonList = TrigGlobalEfficiencyCorrectionTool::LeptonList;
+	using TrigDef = TrigGlobEffCorr::ImportData::TrigDef;
+	using GlobEffFunc = std::function<bool(Calculator*,const LeptonList&,unsigned,Efficiencies&)>;
+
+public:
+	Calculator(TrigGlobalEfficiencyCorrectionTool& parent, unsigned nPeriodsToReserve);
+	bool addPeriod(ImportData& data, const std::pair<unsigned,unsigned>& boundaries, const std::string& combination, 
+			bool useToys, std::size_t& uniqueElectronLeg, std::size_t& uniquePhotonLeg);
+	bool compute(TrigGlobalEfficiencyCorrectionTool& parent, const LeptonList& leptons, unsigned runNumber, Efficiencies& efficiencies);
+	bool checkTriggerMatching(TrigGlobalEfficiencyCorrectionTool& parent, bool& matched, const LeptonList& leptons, unsigned runNumber);
+	bool getRelevantTriggersForUser(TrigGlobalEfficiencyCorrectionTool& parent, std::vector<std::string>& triggers, unsigned runNumber);
+	
+	struct Period
+	{
+		const std::pair<unsigned,unsigned> m_boundaries;
+		GlobEffFunc m_formula;
+		std::vector<TrigDef> m_triggers; /// only used for trigger matching; not filled otherwise. Also, single-lepton _OR_ triggers are split!
+		Period(const decltype(m_boundaries)& b, decltype(m_formula)&& f, decltype(m_triggers)&& t = {}) : m_boundaries(b), m_formula(f), m_triggers(t) {}
+	};
+	
+private:
+	TrigGlobalEfficiencyCorrectionTool* m_parent; /// pointer updated at each call to compute() because the parent tool might have been moved in-between
+	
+	std::vector<Period> m_periods;
+	std::map<std::pair<const Lepton*, std::size_t>, Efficiencies> m_cachedEfficiencies;
+	
+	bool aboveThreshold(const Lepton& p,std::size_t leg) const { return m_parent->aboveThreshold(p, leg); }
+	template<typename Trig1L> auto getLoosestLegAboveThreshold(const Lepton& lepton, const flat_set<Trig1L>& trigs, bool& success)
+			-> std::enable_if_t<Trig1L::is1L(), std::size_t>
+		{ return m_parent->getLoosestLegAboveThreshold(lepton, Trig1L::anonymize(trigs), success); }
+	Efficiencies getCachedTriggerLegEfficiencies(const Lepton& lepton, unsigned runNumber, std::size_t leg, bool& success);
+	bool fillListOfLegsFor(const Lepton& lepton, const std::vector<TrigDef>& triggers, flat_set<std::size_t>& validLegs) const;
+	bool canTriggerBeFired(const TrigDef& trig, const std::vector<flat_set<std::size_t> >& firedLegs) const;
+	const Period* getPeriod(unsigned runNumber) const;
+	bool findUniqueLeg(xAOD::Type::ObjectType obj, std::size_t& uniqueLeg, const std::vector<TrigDef>& defs);
+	
+	/// One single-lepton trigger
+	template<typename Trig1L>
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig1L, Efficiencies&)
+		-> std::enable_if_t<Trig1L::is1L(), bool>;
+	/// Two single-lepton triggers, two object types
+	template<typename Trig1L_obj1, typename Trig1L_obj2>
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig1L_obj1 trig1, const Trig1L_obj2 trig2, Efficiencies&)
+		-> std::enable_if_t<Trig1L_obj1::is1L()
+							&& Trig1L_obj2::is1L()
+							&& Trig1L_obj1::object() != Trig1L_obj2::object(),
+			bool>;
+	/// Several single-lepton triggers, one object type
+	template<typename Trig1L>
+	auto globalEfficiency(const LeptonList&, unsigned, const flat_set<Trig1L>&, Efficiencies&)
+		-> std::enable_if_t<Trig1L::is1L(), bool>;
+	/// Several single-lepton triggers, two object types
+	template<typename Trig1L_obj1, typename Trig1L_obj2>
+	auto globalEfficiency(const LeptonList&, unsigned, const flat_set<Trig1L_obj1>& trigs1, const flat_set<Trig1L_obj2>& trigs2, Efficiencies&)
+		-> std::enable_if_t<Trig1L_obj1::is1L()
+							&& Trig1L_obj2::is1L()
+							&& Trig1L_obj1::object() != Trig1L_obj2::object(),
+			bool>;
+	/// One mixed-flavour dilepton trigger
+	template<typename Trig2Lmix>
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig2Lmix, Efficiencies&)
+		-> std::enable_if_t<Trig2Lmix::is2Lmix(), bool>;
+	/// One symmetric dilepton trigger
+	template<typename Trig2Lsym>
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig2Lsym , Efficiencies&)
+		-> std::enable_if_t<Trig2Lsym::is2Lsym(), bool>;
+	/// One asymmetric dilepton trigger
+	template<typename Trig2Lasym>
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig2Lasym, Efficiencies&) 
+		-> std::enable_if_t<Trig2Lasym::is2Lasym(), bool>;
+	/// One mixed-flavour dilepton trigger + single-lepton triggers
+	template<typename Trig2Lmix, typename Trig1L_obj1, typename Trig1L_obj2>
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig2Lmix, const flat_set<Trig1L_obj1>&, const flat_set<Trig1L_obj2>&, Efficiencies&)
+		-> std::enable_if_t<Trig2Lmix::is2Lmix()
+							&& Trig1L_obj1::is1L() && Trig2Lmix::object1()==Trig1L_obj1::object()
+							&& Trig1L_obj2::is1L() && Trig2Lmix::object2()==Trig1L_obj2::object(),
+			bool>;
+	/// One dilepton trigger + one single-lepton trigger
+	template<typename Trig2L, typename Trig1L>
+	inline auto globalEfficiency(const LeptonList&, unsigned, const Trig2L, const Trig1L, Efficiencies&)
+		-> std::enable_if_t<Trig2L::is2Lnomix() 
+							&& Trig1L::is1L()
+							&& Trig2L::object()==Trig1L::object(),
+			bool>;
+	/// One symmetric dilepton trigger + several single-lepton triggers
+	template<typename Trig2Lsym, typename Trig1L>
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig2Lsym, const flat_set<Trig1L>&, Efficiencies&)
+		-> std::enable_if_t<Trig2Lsym::is2Lsym() 
+							&& Trig1L::is1L() 
+							&& Trig1L::object() == Trig2Lsym::object(), 
+			bool>;
+	/// One asymmetric dilepton trigger + several single-lepton triggers
+	template<typename Trig2Lasym, typename Trig1L>
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig2Lasym, const flat_set<Trig1L>&, Efficiencies&)
+		-> std::enable_if_t<Trig2Lasym::is2Lasym() 
+							&& Trig1L::is1L() 
+							&& Trig1L::object() == Trig2Lasym::object(), 
+			bool>;
+	/// Two symmetric dilepton triggers + several single-lepton triggers
+	template<typename Trig2Lsym, typename Trig1L> 
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig2Lsym, const Trig2Lsym, const flat_set<Trig1L>&, Efficiencies&)
+		-> std::enable_if_t<Trig2Lsym::is2Lsym() 
+							&& Trig1L::is1L()
+							&& Trig1L::object() == Trig2Lsym::object(), 
+			bool>;
+	/// Two dilepton triggers (one asym., one sym.) + several single-lepton triggers
+	template<typename Trig2Lasym, typename Trig2Lsym, typename Trig1L>
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig2Lasym, const Trig2Lsym, const flat_set<Trig1L>&, Efficiencies&)
+		-> std::enable_if_t<Trig2Lasym::is2Lasym() 
+							&& Trig2Lsym::is2Lsym() && Trig2Lsym::object()==Trig2Lasym::object()
+							&& Trig1L::is1L() && Trig1L::object()==Trig2Lasym::object(), 
+			bool>;
+	/// One symmetric trilepton trigger
+	template<typename Trig3Lsym>
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig3Lsym, Efficiencies&)
+		-> std::enable_if_t<Trig3Lsym::is3Lsym(), bool>;
+	/// One half-symmetric trilepton trigger
+	template<typename Trig3Lhalfsym>
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig3Lhalfsym, Efficiencies&)
+		-> std::enable_if_t<Trig3Lhalfsym::is3Lhalfsym(), bool>;
+	/// One dilepton trigger + one mixed-flavour dilepton trigger
+	template<typename Trig2L, typename Trig2Lmix>
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig2L, const Trig2Lmix, Efficiencies&)
+		-> std::enable_if_t<Trig2L::is2Lnomix()
+							&& Trig2Lmix::is2Lmix()
+							&& (Trig2Lmix::object1()==Trig2L::object() || Trig2Lmix::object2()==Trig2L::object()),
+			bool>;
+	/// Combinaisons with 3 dilepton triggers including one mixed-flavour, and one sym./asym. dilepton for each flavour
+	template<typename Trig2L_obj1, typename Trig2L_obj2, typename Trig2Lmix>
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig2L_obj1, const Trig2L_obj2, const Trig2Lmix, Efficiencies&)
+		-> std::enable_if_t<Trig2Lmix::is2Lmix()
+							&& Trig2L_obj1::is2Lnomix() && Trig2L_obj1::object() == Trig2Lmix::object1()
+							&& Trig2L_obj2::is2Lnomix() && Trig2L_obj2::object() == Trig2Lmix::object2(),
+							
+			bool>;
+	/// Same combinaisons with 3 dilepton triggers, + single-lepton triggers
+	template<typename Trig2L_obj1, typename Trig2L_obj2, typename Trig2Lmix, typename Trig1L_obj1, typename Trig1L_obj2>
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig2L_obj1, const Trig2L_obj2, const Trig2Lmix, 
+			const flat_set<Trig1L_obj1>&, const flat_set<Trig1L_obj2>&, Efficiencies&)
+		-> std::enable_if_t<Trig2Lmix::is2Lmix()
+							&& Trig2L_obj1::is2Lnomix() && Trig2L_obj1::object()==Trig2Lmix::object1()
+							&& Trig2L_obj2::is2Lnomix() && Trig2L_obj2::object()==Trig2Lmix::object2()
+							&& Trig1L_obj1::is1L() && Trig1L_obj1::object()==Trig2Lmix::object1()
+							&& Trig1L_obj2::is1L() && Trig1L_obj2::object()==Trig2Lmix::object2(),
+							
+			bool>;
+	/// Six dilepton triggers (two mixed-flavour, two sym., two asym./sym.) for two object types
+	template<typename Trig2L_obj1, typename Trig2Lsym_obj1, typename Trig2L_obj2, typename Trig2Lsym_obj2,
+		typename Trig2Lmix, typename Trig1L_obj1, typename Trig1L_obj2>
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig2L_obj1, const Trig2Lsym_obj1, const Trig2L_obj2, const Trig2Lsym_obj2,
+			const Trig2Lmix, const Trig2Lmix, const flat_set<Trig1L_obj1>&, const flat_set<Trig1L_obj2>&, Efficiencies&)
+		-> std::enable_if_t<Trig2Lmix::is2Lmix()
+							&& Trig2L_obj1::is2Lnomix() && Trig2L_obj1::object()==Trig2Lmix::object1()
+							&& Trig2L_obj2::is2Lnomix() && Trig2L_obj2::object()==Trig2Lmix::object2()
+							&& Trig2Lsym_obj1::is2Lsym() && Trig2Lsym_obj1::object()==Trig2Lmix::object1()
+							&& Trig2Lsym_obj2::is2Lsym() && Trig2Lsym_obj2::object()==Trig2Lmix::object2()
+							&& Trig1L_obj1::is1L() && Trig1L_obj1::object()==Trig2Lmix::object1()
+							&& Trig1L_obj2::is1L() && Trig1L_obj2::object()==Trig2Lmix::object2(),
+			bool>;
+	/// One mixed-flavour trilepton trigger
+	template<typename Trig3Lmix>
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig3Lmix, Efficiencies&)
+		-> std::enable_if_t<Trig3Lmix::is3Lmix(), bool>;
+	/// Two complementary mixed-flavour trilepton triggers
+	template<typename Trig3Lmix1, typename Trig3Lmix2>
+	auto globalEfficiency(const LeptonList&, unsigned, const Trig3Lmix1, const Trig3Lmix2, Efficiencies&)
+		-> std::enable_if_t<Trig3Lmix1::is3Lmix() 
+						&& Trig3Lmix2::is3Lmix() 
+						&& Trig3Lmix1::object1() == Trig3Lmix2::object2()
+						&& Trig3Lmix1::object2() == Trig3Lmix2::object1(), 
+			bool>;
+	
+	bool globalEfficiency_Factorized2(const LeptonList& leptons, unsigned runNumber, GlobEffFunc func1, GlobEffFunc func2, Efficiencies& globalEfficiencies);
+	bool globalEfficiency_Factorized3(const LeptonList& leptons, unsigned runNumber, GlobEffFunc func1, GlobEffFunc func2, GlobEffFunc func3, Efficiencies& globalEfficiencies);
+
+	bool globalEfficiency_Toys(const LeptonList&, unsigned, const std::vector<TrigDef>& triggers, Efficiencies&);
+	
+private:
+	class Helper
+	{
+	public:
+		Helper(const std::vector<TrigDef>& defs);
+		Helper(Helper&&) = default;
+
+		bool duplicates() const;
+		std::function<bool(Calculator*,const LeptonList&,unsigned,Efficiencies&)> m_formula;
+		
+		bool findAndBindFunction();
+
+	protected:
+		std::vector<TrigDef> m_defs;
+		unsigned m_n1L = 0, m_n2L = 0, m_n3L = 0;
+	
+		template<TriggerType object_flag> bool findAndBindFunction();
+		template<TriggerType object1_flag, TriggerType object2_flag> bool findAndBindFunction();
+		template<typename... Trigs> bool bindFunction();
+		
+		template<typename Param> auto extract();
+		struct NoSuchTrigger {};
+		template<typename T> struct Optional{}; /// to decorate the parameters of the findAndBind() function(s)
+		template<typename T> struct BindPackedParam;
+
+	};
+
+	friend class CheckConfig;
+};
+
+}
+#endif
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/CheckConfig.h b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/CheckConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..75c96514871e75eb1f458754cb68c33357b21f02
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/CheckConfig.h
@@ -0,0 +1,35 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+// contact: jmaurer@cern.ch
+
+#ifndef TRIGGLOBALEFFICIENCYCORRECTION_CHECKCONFIG_H
+#define TRIGGLOBALEFFICIENCYCORRECTION_CHECKCONFIG_H 1
+
+#include "TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrectionTool.h"
+#include "AsgMessaging/AsgMessaging.h"
+
+#include <functional>
+#include <vector>
+
+namespace TrigGlobEffCorr
+{
+	
+class CheckConfig : public asg::AsgMessaging
+{	
+public:
+	CheckConfig(TrigGlobalEfficiencyCorrectionTool& parent);
+	
+	bool basicConfigChecks();
+	bool advancedConfigChecks();
+	
+private:
+	TrigGlobalEfficiencyCorrectionTool& m_parent;
+
+	template<class CPTool> static ToolHandle<CPTool>* findToolByName(ToolHandleArray<CPTool>& suppliedTools, const std::string& name);
+};
+
+}
+
+#endif
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/Efficiencies.h b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/Efficiencies.h
new file mode 100644
index 0000000000000000000000000000000000000000..237159493a06292097c17fdcd4e8b76589d629e2
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/Efficiencies.h
@@ -0,0 +1,42 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+// contact: jmaurer@cern.ch
+
+#ifndef TRIGGLOBALEFFICIENCYCORRECTION_EFFICIENCIES_H
+#define TRIGGLOBALEFFICIENCYCORRECTION_EFFICIENCIES_H 1
+
+#include <utility>
+
+namespace TrigGlobEffCorr
+{
+
+class Efficiencies
+{
+public:
+	Efficiencies() = default;
+	Efficiencies(double e) : m_effs(e,e) {}
+	Efficiencies(const Efficiencies&) = default;
+	Efficiencies(Efficiencies&&) = default;
+	Efficiencies& operator=(const Efficiencies&) = default;
+	Efficiencies& operator=(Efficiencies&&) = default;
+	~Efficiencies() = default;
+	double& data() { return m_effs.first; }
+	double& mc() { return m_effs.second; }
+	double data() const { return m_effs.first; }
+	double mc() const { return m_effs.second; }	
+	Efficiencies operator~() const { return Efficiencies(1. - m_effs.first, 1. - m_effs.second); }
+	Efficiencies operator+(const Efficiencies& rhs) const { return Efficiencies(m_effs.first+rhs.m_effs.first, m_effs.second+rhs.m_effs.second); }
+	Efficiencies operator-(const Efficiencies& rhs) const { return Efficiencies(m_effs.first-rhs.m_effs.first, m_effs.second-rhs.m_effs.second); }
+	Efficiencies operator*(const Efficiencies& rhs) const { return Efficiencies(m_effs.first*rhs.m_effs.first, m_effs.second*rhs.m_effs.second); }
+	Efficiencies& operator+=(const Efficiencies& rhs) { m_effs.first+=rhs.m_effs.first; m_effs.second+=rhs.m_effs.second; return *this; }
+	Efficiencies& operator*=(const Efficiencies& rhs) { m_effs.first*=rhs.m_effs.first; m_effs.second*=rhs.m_effs.second; return *this; }
+protected:
+	Efficiencies(double vd, double vm) : m_effs(vd,vm) {}
+	std::pair<double,double> m_effs;
+};
+
+}
+
+#endif
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/ImportData.h b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/ImportData.h
new file mode 100644
index 0000000000000000000000000000000000000000..73e8bb084acf408fdc53003918be18764beeb4f8
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/ImportData.h
@@ -0,0 +1,148 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+// contact: jmaurer@cern.ch
+
+#ifndef TRIGGLOBALEFFICIENCYCORRECTION_IMPORTDATA_H
+#define TRIGGLOBALEFFICIENCYCORRECTION_IMPORTDATA_H 1
+
+#include "TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrectionTool.h"
+#include "AsgMessaging/AsgMessaging.h"
+#include <functional>
+#include <algorithm>
+#include <cctype>
+#include <vector>
+#include <map>
+#include <array>
+
+namespace TrigGlobEffCorr
+{
+
+enum TriggerType
+{
+	TT_UNKNOWN = 0x0,
+	TT_X2Y_FLAG = 0x1, // used to distinguish X_2Y from 2X_Y triggers
+	TT_ELECTRON_FLAG = 0x10,
+	TT_MUON_FLAG = 0x20,
+	TT_PHOTON_FLAG = 0x40,
+	TT_MASK_FLAVOUR = TT_ELECTRON_FLAG | TT_MUON_FLAG | TT_PHOTON_FLAG,
+	TT_SYM = 0x100,
+	TT_HALFSYM = 0x200,
+	TT_ASYM = 0x300,
+	TT_MASK_SYMMETRY = TT_SYM | TT_HALFSYM | TT_ASYM,
+	TT_MASK_TYPE = ~TT_MASK_FLAVOUR,
+	// single lepton triggers
+	TT_SINGLELEPTON_FLAG = 0x1000,
+	TT_SINGLE_E = TT_SINGLELEPTON_FLAG| TT_ELECTRON_FLAG,
+	TT_SINGLE_MU = TT_SINGLELEPTON_FLAG | TT_MUON_FLAG,
+	TT_SINGLE_G = TT_SINGLELEPTON_FLAG | TT_PHOTON_FLAG,
+	// dilepton triggers
+	TT_DILEPTON_FLAG = 0x2000,
+	TT_DILEPTON_SYM = TT_DILEPTON_FLAG | TT_SYM,
+	TT_DILEPTON_ASYM = TT_DILEPTON_FLAG | TT_ASYM,
+	TT_2E_SYM = TT_DILEPTON_SYM | TT_ELECTRON_FLAG,
+	TT_2E_ASYM = TT_DILEPTON_ASYM | TT_ELECTRON_FLAG,
+	TT_2MU_SYM = TT_DILEPTON_SYM | TT_MUON_FLAG,
+	TT_2MU_ASYM = TT_DILEPTON_ASYM | TT_MUON_FLAG,
+	TT_E_MU = TT_DILEPTON_FLAG | TT_ELECTRON_FLAG | TT_MUON_FLAG,
+	TT_2G_SYM = TT_DILEPTON_SYM | TT_PHOTON_FLAG,
+	TT_2G_ASYM = TT_DILEPTON_ASYM | TT_PHOTON_FLAG,
+	TT_E_G = TT_DILEPTON_FLAG | TT_ELECTRON_FLAG | TT_PHOTON_FLAG,
+	TT_MU_G = TT_DILEPTON_FLAG | TT_MUON_FLAG | TT_PHOTON_FLAG,
+	// trilepton triggers
+	TT_TRILEPTON_FLAG = 0x4000,
+	TT_TRILEPTON_SYM = TT_TRILEPTON_FLAG | TT_SYM,
+	TT_TRILEPTON_HALFSYM = TT_TRILEPTON_FLAG | TT_HALFSYM,
+	TT_TRILEPTON_ASYM = TT_TRILEPTON_FLAG | TT_ASYM,
+	TT_3E_SYM = TT_TRILEPTON_SYM | TT_ELECTRON_FLAG,
+	TT_3E_HALFSYM = TT_TRILEPTON_HALFSYM | TT_ELECTRON_FLAG,
+	TT_3E_ASYM = TT_TRILEPTON_ASYM | TT_ELECTRON_FLAG,
+	TT_3MU_SYM = TT_TRILEPTON_SYM | TT_MUON_FLAG,
+	TT_3MU_HALFSYM = TT_TRILEPTON_HALFSYM | TT_MUON_FLAG,
+	TT_3MU_ASYM = TT_TRILEPTON_ASYM | TT_MUON_FLAG,
+	TT_2E_MU_SYM = TT_TRILEPTON_SYM | TT_ELECTRON_FLAG | TT_MUON_FLAG,
+	TT_E_2MU_SYM = TT_2E_MU_SYM | TT_X2Y_FLAG,
+	TT_2E_MU_ASYM = TT_TRILEPTON_ASYM | TT_ELECTRON_FLAG | TT_MUON_FLAG,
+	TT_E_2MU_ASYM = TT_2E_MU_ASYM | TT_X2Y_FLAG,
+	TT_3G_SYM = TT_TRILEPTON_SYM | TT_PHOTON_FLAG,
+	TT_3G_HALFSYM = TT_TRILEPTON_HALFSYM | TT_PHOTON_FLAG,
+	TT_3G_ASYM = TT_TRILEPTON_ASYM | TT_PHOTON_FLAG,
+	TT_2E_G_SYM = TT_TRILEPTON_SYM | TT_ELECTRON_FLAG | TT_PHOTON_FLAG,
+	TT_E_2G_SYM = TT_2E_G_SYM | TT_X2Y_FLAG,
+	TT_2E_G_ASYM = TT_TRILEPTON_ASYM | TT_ELECTRON_FLAG | TT_PHOTON_FLAG,
+	TT_E_2G_ASYM = TT_2E_G_ASYM | TT_X2Y_FLAG,
+	TT_2MU_G_SYM = TT_TRILEPTON_SYM | TT_MUON_FLAG | TT_PHOTON_FLAG,
+	TT_MU_2G_SYM = TT_2MU_G_SYM | TT_X2Y_FLAG,
+	TT_2MU_G_ASYM = TT_TRILEPTON_ASYM | TT_MUON_FLAG | TT_PHOTON_FLAG,
+	TT_MU_2G_ASYM = TT_2MU_G_ASYM | TT_X2Y_FLAG,
+};
+
+class ImportData : public asg::AsgMessaging
+{
+	using Hierarchy = TrigGlobalEfficiencyCorrectionTool::Hierarchy;
+	
+public:
+	struct TrigDef
+	{ 
+		TriggerType type;
+		std::size_t name;
+		std::array<std::size_t,3> leg;
+		bool used = false; /// auxiliary variable used by Calculator::Helper::extra() and bindFunction()
+		TrigDef(TriggerType type=TT_UNKNOWN, std::size_t name=0, std::size_t leg0=0, std::size_t leg1=0, std::size_t leg2=0) : type(type), name(name), leg{leg0,leg1,leg2} {}
+	};	
+
+	ImportData();
+	ImportData(TrigGlobalEfficiencyCorrectionTool& parent);
+	~ImportData();
+	
+	bool importHierarchies();
+	bool importTriggers();
+	bool importThresholds(const std::map<std::string, std::string>& overridenThresholds = {});
+	bool importPeriods();
+	bool importMapKeys(const std::string& tag, std::map<std::size_t,std::map<std::size_t,int> >& keysPerLeg);
+	// NB: the following function calls all import*() functions except importMapKeys()
+	bool importAll(const std::map<std::string, std::string>& overridenThresholds = {});
+	
+	bool getPeriodBoundaries(const std::string& period, std::pair<unsigned,unsigned>& boundaries);
+	bool suggestElectronMapKeys(const std::map<std::string,std::string>& triggerCombination,
+		const std::string& version, std::map<std::string,std::string>& legsPerKey);
+		
+	const std::map<std::size_t,TrigDef>& getTriggerDefs() const { return m_triggerDefs; }
+	const std::map<std::size_t,float>& getTriggerThresholds() const { return m_triggerThresholds; }
+	const std::map<std::string, std::pair<unsigned,unsigned>>& getDataPeriods() const { return m_dataPeriods; }
+	const std::vector<Hierarchy> getHierarchyMeta() const { return m_hierarchyMeta; }
+	const std::vector<std::size_t> getHierarchyData() const { return m_hierarchyData; }
+	const std::map<std::size_t,std::string>& getDictionary() const { return m_dictionary; }
+	xAOD::Type::ObjectType associatedLeptonFlavour(std::size_t leg, bool& success);
+	static xAOD::Type::ObjectType associatedLeptonFlavour(const std::string& leg, bool& success);
+	std::vector<TrigDef> parseTriggerString(const std::string& triggerString, bool& success);
+	TrigGlobalEfficiencyCorrectionTool* getParent() { return m_parent; }
+	bool adaptTriggerListForTriggerMatching(std::vector<ImportData::TrigDef>& triggers);
+	
+protected:
+	bool readDataFile(const char* filename, std::vector<std::string>& contents);
+	void setNonMixed3LType(TrigDef& def, TriggerType flavourFlag);
+	
+	TrigGlobalEfficiencyCorrectionTool* m_parent;
+	std::map<std::size_t,std::string>& m_dictionary;
+	std::hash<std::string>& m_hasher;
+	
+	std::map<std::size_t,TrigDef> m_triggerDefs;
+	std::map<std::size_t,float> m_triggerThresholds;
+	std::map<std::string, std::pair<unsigned,unsigned>> m_dataPeriods;
+	std::vector<Hierarchy> m_hierarchyMeta;
+	std::vector<std::size_t> m_hierarchyData;
+};
+
+inline std::string removeWhitespaces(const std::string& s)
+{
+	std::string t(s);
+	t.erase(std::remove_if(t.begin(), t.end(),
+			[](char c){ return std::isspace(c); }), t.end());
+	return t;
+}
+
+}
+
+#endif
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/Lepton.h b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/Lepton.h
new file mode 100644
index 0000000000000000000000000000000000000000..bf974c2174ee450ba87da0c817ba1e214a36d47f
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/Lepton.h
@@ -0,0 +1,42 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+// contact: jmaurer@cern.ch
+
+#ifndef TRIGGLOBALEFFICIENCYCORRECTION_LEPTON_H
+#define TRIGGLOBALEFFICIENCYCORRECTION_LEPTON_H 1
+
+#include "xAODEgamma/Electron.h"
+#include "xAODEgamma/Photon.h"
+
+namespace TrigGlobEffCorr
+{
+
+class Lepton
+{
+public:
+	Lepton(const xAOD::IParticle* ptr, std::size_t tag=0) : m_obj(ptr), m_tag(tag) {}
+	Lepton(const xAOD::Electron* ptr, std::size_t tag=0) : m_obj(ptr), m_tag(tag) {}
+	Lepton(const xAOD::Photon* ptr, std::size_t tag=0) : m_obj(ptr), m_tag(tag) {}
+	Lepton(const Lepton&) = default;
+	Lepton& operator=(const Lepton&) = default;
+	Lepton(Lepton&&) = default;
+	Lepton& operator=(Lepton&&) = default;
+	float pt() const { return m_obj->pt(); }
+	xAOD::Type::ObjectType type() const { return m_obj->type(); }
+	std::size_t tag() const { return m_tag; }
+	const xAOD::Electron* electron() const { return static_cast<const xAOD::Electron*>(m_obj); }
+	const xAOD::Muon* muon() const { return static_cast<const xAOD::Muon*>(m_obj); }
+	const xAOD::Photon* photon() const { return static_cast<const xAOD::Photon*>(m_obj); }
+	const xAOD::IParticle* particle() const { return m_obj; }
+	bool operator<(const Lepton& rhs) const { return m_obj<rhs.m_obj; }
+protected:
+	const xAOD::IParticle* m_obj = nullptr;
+	mutable std::size_t m_tag = 0; // 0 = no tag
+};
+
+}
+
+
+#endif
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrectionDict.h b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrectionDict.h
new file mode 100644
index 0000000000000000000000000000000000000000..f422d67bbf510b420aa5a941c9ecdf118ba9f3a3
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrectionDict.h
@@ -0,0 +1,10 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef TOOLPROTOTYPES_TRIGGLOBALEFFICIENCYCORRECTIONDICT_H
+#define TOOLPROTOTYPES_TRIGGLOBALEFFICIENCYCORRECTIONDICT_H
+
+#include "TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrectionTool.h"
+
+#endif
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrectionTool.h b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrectionTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..0bd10db0c889686f8bd4583d04607fa685b8add9
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrectionTool.h
@@ -0,0 +1,199 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+// contact: jmaurer@cern.ch
+
+#ifndef TRIGGLOBALEFFICIENCYCORRECTION_TRIGGLOBALEFFICIENCYCORRECTIONTOOL_H
+#define TRIGGLOBALEFFICIENCYCORRECTION_TRIGGLOBALEFFICIENCYCORRECTIONTOOL_H 1
+
+#include "TriggerAnalysisInterfaces/ITrigGlobalEfficiencyCorrectionTool.h"
+
+#include "AsgTools/ToolHandleArray.h"
+#include "AsgTools/AsgTool.h"
+#include "EgammaAnalysisInterfaces/IAsgElectronEfficiencyCorrectionTool.h"
+#include "MuonAnalysisInterfaces/IMuonTriggerScaleFactors.h"
+#include "EgammaAnalysisInterfaces/IAsgPhotonEfficiencyCorrectionTool.h"
+#include "AthContainers/AuxElement.h"
+#include "TriggerMatchingTool/IMatchingTool.h"
+#include "xAODEgamma/PhotonFwd.h"
+
+#include <string>
+#include <vector>
+#include <map>
+#include <set>
+#include <boost/container/container_fwd.hpp>
+#include <memory>
+
+
+namespace TrigGlobEffCorr
+{
+	class ImportData;
+	class Calculator;
+	class CheckConfig;
+	class Efficiencies;
+	class Lepton;
+}
+
+class TrigGlobalEfficiencyCorrectionTool: public asg::AsgTool, public virtual ITrigGlobalEfficiencyCorrectionTool
+{
+public: 
+	ASG_TOOL_CLASS(TrigGlobalEfficiencyCorrectionTool,ITrigGlobalEfficiencyCorrectionTool)
+
+	TrigGlobalEfficiencyCorrectionTool(const std::string& name);
+	virtual ~TrigGlobalEfficiencyCorrectionTool();
+
+	virtual StatusCode  initialize() override;
+
+	virtual CP::CorrectionCode getEfficiencyScaleFactor(const std::vector<const xAOD::IParticle*>& particles, double& efficiencyScaleFactor) override;
+	virtual CP::CorrectionCode getEfficiencyScaleFactor(unsigned runNumber, const std::vector<const xAOD::IParticle*>& particles, double& efficiencyScaleFactor) override;
+	virtual CP::CorrectionCode getEfficiency(const std::vector<const xAOD::IParticle*>& particles, double& efficiencyData, double& efficiencyMc) override;
+	virtual CP::CorrectionCode getEfficiency(unsigned runNumber, const std::vector<const xAOD::IParticle*>& particles, double& efficiencyData, double& efficiencyMc) override;	
+	
+	virtual bool isAffectedBySystematic(const CP::SystematicVariation& systematic) const override;
+	virtual CP::SystematicSet affectingSystematics() const override;
+	virtual CP::SystematicSet recommendedSystematics() const override;
+	virtual StatusCode applySystematicVariation(const CP::SystematicSet& systConfig) override;
+
+	virtual CP::CorrectionCode checkTriggerMatching(bool& matched, const std::vector<const xAOD::IParticle*>& particles) override;
+	virtual CP::CorrectionCode getRelevantTriggers(std::vector<std::string>& triggers) override;
+	virtual CP::CorrectionCode countTriggerLegs(const std::string& trigger, std::size_t& numberOfLegs) override;
+	
+	static CP::CorrectionCode suggestElectronMapKeys(const std::map<std::string,std::string>& triggerCombination,
+		const std::string& version, std::map<std::string,std::string>& legsPerKey);
+	
+private: 
+
+	struct Hierarchy
+	{
+		short offset, nLegs;
+		float minPt, maxPt;
+	};
+	struct TagDecorator
+	{
+		SG::AuxElement::ConstAccessor<char> decorator;
+		std::size_t hash;
+		bool suffixed;
+		TagDecorator(const std::string& name, std::size_t hash, bool suffixed) : decorator(name), hash(hash), suffixed(suffixed) {}
+	};
+	struct CachedRanking
+	{
+		float minPt, maxPt;
+		unsigned long ranking;
+		operator bool() const { return ranking+1; }
+		static bool invalid(unsigned long ranking) { return !(ranking+1); }
+	};
+	struct ToolKey
+	{
+		std::size_t hash;
+		std::pair<unsigned,unsigned> boundaries;
+		bool operator<(const ToolKey& rhs) const { return hash<rhs.hash || (hash==rhs.hash && boundaries.second<rhs.boundaries.first); }
+		bool operator==(const ToolKey& rhs) const { return hash==rhs.hash && boundaries.second>=rhs.boundaries.first && rhs.boundaries.second>=boundaries.first; }
+		ToolKey(std::size_t leg, std::size_t tag, unsigned runNumber) : hash(leg^tag), boundaries(runNumber, runNumber) {}
+		ToolKey(std::size_t leg, std::size_t tag, std::pair<unsigned,unsigned> bounds) : hash(leg^tag), boundaries(bounds) {}
+		ToolKey(std::size_t leg = 0, std::size_t tag = 0) : hash(leg^tag), boundaries(0, 999999) {}
+	};
+	using LeptonList = std::vector<TrigGlobEffCorr::Lepton>;
+	
+	/// Properties:
+	ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> m_suppliedElectronEfficiencyTools;
+	ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> m_suppliedElectronScaleFactorTools;
+	ToolHandleArray<IAsgPhotonEfficiencyCorrectionTool> m_suppliedPhotonEfficiencyTools;
+	ToolHandleArray<IAsgPhotonEfficiencyCorrectionTool> m_suppliedPhotonScaleFactorTools;
+	ToolHandleArray<CP::IMuonTriggerScaleFactors> m_suppliedMuonTools;
+	std::map<std::string, std::string> m_legsPerTool;
+	std::map<std::string, std::string> m_triggerCb;
+	std::map<std::string, std::string> m_triggerCbPerYear;
+	std::string m_leptonTagDecorations;
+	std::map<std::string, std::string> m_tagsPerTool;
+	std::map<std::string, std::string> m_electronLegsPerTag; /// deprecated
+	std::map<std::string, std::string> m_muonLegsPerTag; /// deprecated
+	std::map<std::string, std::string> m_legsPerTag;
+	std::map<std::string, std::string> m_overrideThresholds;
+	unsigned long m_numberOfToys;
+	bool m_useInternalSeed;
+	ToolHandle<Trig::IMatchingTool> m_trigMatchTool;
+
+	std::hash<std::string> m_hasher; //!
+	std::map<std::size_t,float > m_thresholds; //!
+	std::multimap<std::size_t, CachedRanking> m_cachedLegRankings; //!
+	std::map<ToolKey, std::size_t > m_electronSfToolIndex; //!
+	std::map<ToolKey, std::size_t > m_electronEffToolIndex; //!
+	std::map<ToolKey, std::size_t > m_photonSfToolIndex; //!
+	std::map<ToolKey, std::size_t > m_photonEffToolIndex; //!
+	std::map<ToolKey, std::size_t > m_muonToolIndex; //!
+	std::set<std::size_t> m_validLegTagPairs; //!
+	bool m_checkElectronLegTag; //!
+	bool m_checkMuonLegTag; //!
+	bool m_checkPhotonLegTag; //!
+	std::map<std::size_t, std::string> m_dictionary; //!
+	
+	std::vector<Hierarchy> m_hierarchyMeta; //!
+	std::vector<std::size_t> m_hierarchyData; //!
+	
+	bool m_initialized = false; //!
+	CP::CorrectionCode m_cpCode = CP::CorrectionCode::Ok; //!
+	unsigned long m_seed;
+	bool m_validTrigMatchTool; //!
+	
+	std::vector<TagDecorator> m_leptonTagDecorators; //!
+	SG::AuxElement::ConstAccessor<unsigned int> m_runNumberDecorator; //!
+	std::unique_ptr<TrigGlobEffCorr::Calculator> m_calculator; //!
+	
+	template<typename Key> using flat_set = boost::container::flat_set<Key>;
+
+	/// Internal methods (I) -- initialization of the tool
+	bool loadHierarchies();
+	template<class CPTool> bool enumerateTools(TrigGlobEffCorr::ImportData& data, ToolHandleArray<CPTool>& suppliedTools,
+		std::map<ToolKey, std::size_t>& toolIndex, flat_set<std::size_t>& collectedTags);
+	flat_set<ToolKey> parseListOfLegs(TrigGlobEffCorr::ImportData& data, const std::string& inputList, bool& success);
+	bool parseTagString(const std::string& tagstring, flat_set<std::size_t>& tags);
+	bool loadTriggerCombination(TrigGlobEffCorr::ImportData& data, bool useDefaultElectronTools, bool useDefaultPhotonTools);
+	bool loadTagDecorators(const flat_set<std::size_t>& collectedElectronTags, const flat_set<std::size_t>& collectedMuonTags, const flat_set<std::size_t>& collectedPhotonTags);
+	bool loadListOfLegsPerTag(const flat_set<std::size_t>& collectedElectronTags, const flat_set<std::size_t>& collectedMuonTags, const flat_set<std::size_t>& collectedPhotonTags);
+	bool processDeprecatedProperties();
+	
+	/// Internal methods (II) -- core task
+	bool retrieveRunNumber(unsigned& runNumber);
+	bool retrieveEventNumber(unsigned long& eventNumber);
+	bool aboveThreshold(const TrigGlobEffCorr::Lepton& p,std::size_t leg) const;
+	template<class... ListOfLegs> unsigned long getCachedTriggerLegsRanking(const TrigGlobEffCorr::Lepton& lepton, ListOfLegs... legs);
+	std::size_t getLoosestLegAboveThreshold(const TrigGlobEffCorr::Lepton& lepton, const flat_set<std::size_t>& legs, bool& success);
+	std::size_t getLoosestLeg(const TrigGlobEffCorr::Lepton& lepton, std::size_t leg1, std::size_t leg2, bool& success);
+	std::pair<std::size_t,std::size_t> getTwoLoosestLegs(const TrigGlobEffCorr::Lepton& lepton, const flat_set<std::size_t>& legs, bool& success);
+	std::vector<std::size_t> getSortedLegs(const TrigGlobEffCorr::Lepton& lepton, const flat_set<std::size_t>& legs, bool& success);
+	template<class Container> CachedRanking rankTriggerLegs(float pt, const Container& legs);
+	template<class Particle> bool updateLeptonList(LeptonList& leptons, const std::vector<const Particle*>& particles);
+	void updateMuonTriggerNames(std::size_t leg, const std::string& name);
+	bool getTriggerLegEfficiencies(const xAOD::Electron* p, unsigned runNumber, std::size_t leg, std::size_t tag, TrigGlobEffCorr::Efficiencies& efficiencies);
+	bool getTriggerLegEfficiencies(const xAOD::Muon* p, std::size_t leg, std::size_t tag, TrigGlobEffCorr::Efficiencies& efficiencies);
+	bool getTriggerLegEfficiencies(const xAOD::Photon* p, unsigned runNumber, std::size_t leg, std::size_t tag, TrigGlobEffCorr::Efficiencies& efficiencies);
+	template<class ParticleType>
+	bool getEgammaTriggerLegEfficiencies(const ParticleType* p, unsigned runNumber, std::size_t leg, std::size_t tag, TrigGlobEffCorr::Efficiencies& efficiencies);
+	decltype(m_electronSfToolIndex)& GetScaleFactorToolIndex(const xAOD::Electron*) { return m_electronSfToolIndex; }
+	decltype(m_photonSfToolIndex)& GetScaleFactorToolIndex(const xAOD::Photon*) { return m_photonSfToolIndex; }
+	decltype(m_electronEffToolIndex)& GetEfficiencyToolIndex(const xAOD::Electron*) { return m_electronEffToolIndex; }
+	decltype(m_photonEffToolIndex)& GetEfficiencyToolIndex(const xAOD::Photon*) { return m_photonEffToolIndex; }
+	IAsgElectronEfficiencyCorrectionTool& GetScaleFactorTool(const xAOD::Electron*, std::size_t index)
+		{ return *m_suppliedElectronScaleFactorTools[index]; }
+	IAsgPhotonEfficiencyCorrectionTool& GetScaleFactorTool(const xAOD::Photon*, std::size_t index)
+		{ return *m_suppliedPhotonScaleFactorTools[index]; }
+	IAsgElectronEfficiencyCorrectionTool& GetEfficiencyTool(const xAOD::Electron*, std::size_t index)
+		{ return *m_suppliedElectronEfficiencyTools[index]; }
+	IAsgPhotonEfficiencyCorrectionTool& GetEfficiencyTool(const xAOD::Photon*, std::size_t index)
+		{ return *m_suppliedPhotonEfficiencyTools[index]; }
+	std::size_t getCombinedHash(const flat_set<std::size_t>& legs);
+	std::size_t getCombinedHash(std::size_t leg1, std::size_t leg2);
+	static inline constexpr const flat_set<std::size_t>& forwardLegs(const flat_set<std::size_t>& legs);
+	static inline constexpr std::array<std::size_t, 2> forwardLegs(std::size_t leg1, std::size_t leg2);
+	
+	/// Internal methods (III) -- misc. helpers
+	inline bool checkAndRecord(CP::CorrectionCode&& cc);
+	flat_set<std::size_t> listNonOrderedCSValues(const std::string& s, bool& success);
+
+	friend class TrigGlobEffCorr::ImportData;
+	friend class TrigGlobEffCorr::CheckConfig;
+	friend class TrigGlobEffCorr::Calculator;
+};
+
+#endif
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/Trigger.h b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/Trigger.h
new file mode 100644
index 0000000000000000000000000000000000000000..9e80931ef55763ce624d4debb23b93e086b76f53
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/Trigger.h
@@ -0,0 +1,346 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+// contact: jmaurer@cern.ch
+
+#ifndef TRIGGLOBALEFFICIENCYCORRECTION_TRIGGER_H
+#define TRIGGLOBALEFFICIENCYCORRECTION_TRIGGER_H 1
+
+#include "TrigGlobalEfficiencyCorrection/ImportData.h"
+#include "xAODBase/ObjectType.h"
+
+#include <array>
+#include <algorithm>
+#include <type_traits>
+#include <boost/container/flat_set.hpp>
+template<typename Key> using flat_set = boost::container::flat_set<Key>;
+
+namespace TrigGlobEffCorr
+{
+
+class UnusedArg { public: static constexpr xAOD::Type::ObjectType object() { return xAOD::Type::Other; } };
+
+class TriggerProperties
+{
+public:
+	explicit constexpr TriggerProperties(TriggerType tt) : m_type(tt), m_legs{} {}
+	explicit TriggerProperties(const ImportData::TrigDef& def) : m_type(def.type) { loadLegs(def, m_legs); }
+	constexpr TriggerType type() const { return m_type; }
+	constexpr bool valid() const
+	{
+		return !((m_type&TT_MASK_FLAVOUR)&~(TT_ELECTRON_FLAG|TT_MUON_FLAG|TT_PHOTON_FLAG)); /// validity of mixed() function
+	}
+	constexpr bool mixed() const
+	{
+		auto x = m_type&TT_MASK_FLAVOUR;
+		return (x!=TT_ELECTRON_FLAG) && (x!=TT_MUON_FLAG) && (x!=TT_PHOTON_FLAG);
+	}
+	constexpr unsigned nDistinctLegs() const
+	{
+		auto x = m_type&TT_MASK_SYMMETRY;
+		if(m_type&TT_SINGLELEPTON_FLAG) return 1;
+		else if(m_type&TT_DILEPTON_FLAG) return 2 - 1*(x==TT_SYM);
+		else if(m_type&TT_TRILEPTON_FLAG) return (x==TT_ASYM)? 3 : 1 + 1*(mixed()||(x!=TT_SYM));
+		return 0;
+	}
+	constexpr unsigned nDistinctLegs(xAOD::Type::ObjectType obj) const
+	{
+		bool firstPos = true;
+		switch(obj)
+		{
+			case xAOD::Type::Electron:
+				if(!(m_type&TT_ELECTRON_FLAG)) return 0;
+				break;
+			case xAOD::Type::Muon:
+				if(!(m_type&TT_MUON_FLAG)) return 0;
+				firstPos = (m_type&TT_PHOTON_FLAG);
+				break;
+			case xAOD::Type::Photon:
+				if(!(m_type&TT_PHOTON_FLAG)) return 0;
+				firstPos = false;
+				break;
+			default: return 0;
+		}
+		if(!mixed()) return nDistinctLegs();
+		return (firstPos == (m_type&TT_X2Y_FLAG))? 1 : nDistinctLegs()-1;
+	}
+	template<typename Array>
+	void loadLegs(const ImportData::TrigDef& src, Array& dest)
+	{
+		if(src.type != m_type) throw; /// can't be thrown due to bad user action -- only in case of a bug in the Calculator class
+		std::fill(dest.begin(), dest.end(), 0);
+		if(m_type==TT_2E_MU_SYM || m_type==TT_2E_G_SYM || m_type==TT_2MU_G_SYM) /// special case needed to skip the duplicated leg for 2X_Y triggers
+		{
+			dest[0] = src.leg[0];
+			dest[1] = src.leg[2];
+		}
+		else /// Works as well for non-mixed trilepton triggers since the asymmetric leg is always stored first
+		{
+			std::copy_n(src.leg.cbegin(), nDistinctLegs(), dest.begin());
+		}
+	}
+	
+	constexpr int cbegin_offset(xAOD::Type::ObjectType obj) const
+	{
+		return (obj!=xAOD::Type::Electron) *(nDistinctLegs(xAOD::Type::Electron)  
+			+ (obj!=xAOD::Type::Muon)*nDistinctLegs(xAOD::Type::Muon));
+	}
+	
+	auto cbegin(xAOD::Type::ObjectType obj) const
+	{
+		return m_legs.cbegin() + cbegin_offset(obj);
+	}
+	
+	constexpr int cend_offset(xAOD::Type::ObjectType obj) const
+	{
+		return -((obj!=xAOD::Type::Photon) *(nDistinctLegs(xAOD::Type::Photon)  
+			+ (obj!=xAOD::Type::Muon)*nDistinctLegs(xAOD::Type::Muon)));
+	}
+	
+	auto cend(xAOD::Type::ObjectType obj) const
+	{
+		return m_legs.cbegin() + nDistinctLegs() + cend_offset(obj);
+	}
+	
+protected:
+	TriggerType m_type;
+	std::array<std::size_t, 3> m_legs;
+};
+
+template<TriggerType tt, typename CastType1 = UnusedArg, typename CastType2 = UnusedArg>
+class Trigger
+{
+	static_assert(TriggerProperties(tt).valid(), "trigger type not supported");
+private:
+	static constexpr bool extraCheck(xAOD::Type::ObjectType obj) { return (object()==obj || obj==xAOD::Type::Other); }
+	template<typename T> struct Optional {};
+public:
+	
+	static constexpr TriggerType type() { return tt; }
+	
+	static constexpr bool mixed()
+	{
+		return TriggerProperties(tt).mixed();
+	}
+	
+	static constexpr unsigned nDistinctLegs() { return TriggerProperties(tt).nDistinctLegs(); }
+	
+	static constexpr unsigned nDistinctLegs(xAOD::Type::ObjectType obj) { return TriggerProperties(tt).nDistinctLegs(obj); }
+
+	static constexpr xAOD::Type::ObjectType object1()
+	{
+		if(!is3Lmix())
+		{
+			if(tt&TT_ELECTRON_FLAG) return xAOD::Type::Electron;
+			if(tt&TT_MUON_FLAG) return xAOD::Type::Muon;
+			if(tt&TT_PHOTON_FLAG) return xAOD::Type::Photon;
+			return xAOD::Type::Other;
+		}
+		if((tt&TT_ELECTRON_FLAG) && !(tt&TT_X2Y_FLAG)) return xAOD::Type::Electron;
+		if((tt&TT_PHOTON_FLAG) && (tt&TT_X2Y_FLAG)) return xAOD::Type::Photon;
+		return (tt&TT_MUON_FLAG)? xAOD::Type::Muon : xAOD::Type::Other;
+	}
+	
+	static constexpr xAOD::Type::ObjectType object2()
+	{
+		if(is2Lmix())
+		{
+			if((tt&TT_ELECTRON_FLAG) && (tt&TT_MUON_FLAG)) return xAOD::Type::Muon;
+			if((tt&(TT_ELECTRON_FLAG|TT_MUON_FLAG)) && (tt&TT_PHOTON_FLAG)) return xAOD::Type::Photon;
+			return xAOD::Type::Other;
+		}
+		else if(!is3Lmix()) return xAOD::Type::Other;	
+		if((tt&TT_ELECTRON_FLAG) && (tt&TT_X2Y_FLAG)) return xAOD::Type::Electron;
+		if((tt&TT_PHOTON_FLAG) && !(tt&TT_X2Y_FLAG)) return xAOD::Type::Photon;
+		return (tt&TT_MUON_FLAG)? xAOD::Type::Muon : xAOD::Type::Other;
+	}
+	
+	static constexpr xAOD::Type::ObjectType object()
+	{
+		if(mixed()) return xAOD::Type::Other;
+		return object1();
+	}
+	
+	static bool relevantFor(const Lepton& lepton) { return lepton.type()==object(); }
+	static bool irrelevantFor(const Lepton& lepton) { return !relevantFor(lepton); }
+	
+	static constexpr bool is1L() { return (tt&TT_SINGLELEPTON_FLAG); }
+	static constexpr bool is2Lnomix() { return (tt&TT_DILEPTON_FLAG) && !mixed(); }
+	static constexpr bool is2Lasym() { return ((tt&TT_MASK_TYPE)==TT_DILEPTON_ASYM); }
+	static constexpr bool is2Lsym() { return ((tt&TT_MASK_TYPE)==TT_DILEPTON_SYM); }
+	static constexpr bool is3Lsym() { return ((tt&TT_MASK_TYPE)==TT_TRILEPTON_SYM) && !mixed(); }
+	static constexpr bool is3Lhalfsym() { return ((tt&TT_MASK_TYPE)==TT_TRILEPTON_HALFSYM) && !mixed(); }
+	static constexpr bool is2Lmix() { return (tt&TT_DILEPTON_FLAG) && mixed(); }
+	static constexpr bool is3Lmix() { return (tt&TT_TRILEPTON_FLAG) && mixed(); }
+
+	
+	std::array<std::size_t, nDistinctLegs()> legs;
+	
+	explicit Trigger()
+	{
+		std::fill(legs.begin(), legs.end(), 0);
+	}
+	
+	void setDefinition(const ImportData::TrigDef& def)
+	{
+		TriggerProperties(tt).loadLegs(def, legs);
+	}
+	
+	template<bool=true> std::size_t operator()(void) const
+	{
+		static_assert(nDistinctLegs()==1, "this function is not meaningful for this type of trigger, hence should not be used.");
+		return legs[0];
+	}
+	
+	std::size_t operator()(unsigned index) const
+	{
+		return legs[index];
+	}
+	
+	template<bool=true> std::size_t operator<(const Trigger& rhs) const
+	{
+		static_assert(is1L(), "this function is not meaningful for this type of trigger, hence should not be used.");
+		return legs[0] < rhs.legs[0];
+	}
+	
+	explicit operator bool() const
+	{
+		return std::all_of(legs.cbegin(), legs.cend(), [](std::size_t x)->bool{ return x; });
+	}
+	
+	bool operator==(const Trigger& rhs) const
+	{
+		return legs == rhs.legs;
+	}
+	
+	template<xAOD::Type::ObjectType obj = object()> auto cbegin() const
+	{
+		return legs.cbegin() + TriggerProperties(tt).cbegin_offset(obj);
+	}
+	
+	template<xAOD::Type::ObjectType obj = object()> auto cend() const
+	{
+		return legs.cend() + TriggerProperties(tt).cend_offset(obj);
+	}
+
+	template<typename Trig1L> auto hiddenBy(const Trig1L trig) const
+		-> typename std::enable_if<Trig1L::is1L(), bool>::type
+	{
+		static_assert(Trig1L::is1L(), "this function is not meaningful for this type of trigger, hence should not be used.");
+		constexpr auto obj = Trig1L::object();
+		return std::find(cbegin<obj>(), cend<obj>(), trig()) != cend<obj>();
+	}
+	
+	template<typename Trig1L> auto hiddenBy(const flat_set<Trig1L>& trigs) const
+		-> typename std::enable_if<Trig1L::is1L(), bool>::type
+	{
+		static_assert(Trig1L::is1L(), "this function is not meaningful for this type of trigger, hence should not be used.");
+		return std::any_of(trigs.cbegin(), trigs.cend(), [&](Trig1L t)->bool{ return hiddenBy(t); });
+	}
+	
+	/// Returns a pseudo trigger built only from the legs of flavour 'obj'
+	/// If anti==true, uses instead only legs with flavours other than 'obj'
+	template<xAOD::Type::ObjectType obj, bool anti=false> auto side() const
+		-> std::conditional_t<anti ^ (CastType1::object()==obj), CastType1, CastType2>
+	{
+		static_assert(mixed(), "this function is not meaningful for this type of trigger, hence should not be used.");
+		static_assert(obj != xAOD::Type::Other, "implementation incomplete");
+		using CastType = decltype(side<obj, anti>());
+		CastType trig;
+		std::copy(this->cbegin<CastType::object()>(), this->cend<CastType::object()>(), trig.legs.begin());
+		return trig;
+	}
+	/// Returns a pseudo trigger built only from the legs of the same flavour as the trigger 'TrigX'
+	template<typename TrigX> auto side() const -> decltype(side<TrigX::object()>()) { return side<TrigX::object()>(); }
+	/// Complement to the previous function
+	template<typename TrigX> auto antiside() const -> decltype(side<TrigX::object(),true>()) { return side<TrigX::object(),true>(); }
+	/// Returns a pseudo trigger of type CastType1/2
+	CastType1 side1() const { return side<CastType1::object()>(); }
+	CastType2 side2() const { return side<CastType2::object()>(); }
+
+	template<typename Trig1L> auto addTo(const flat_set<Trig1L>& trigs1L) const
+		-> std::enable_if_t<Trig1L::is1L() && nDistinctLegs(Trig1L::object())==1, flat_set<Trig1L>> 
+	{
+		static_assert(mixed(), "this function is not meaningful for this type of trigger, hence should not be used.");
+		flat_set<Trig1L> trigs(trigs1L);
+		trigs.insert(side<Trig1L>());
+		return trigs;
+	}
+	
+	template<typename Trig1L> static auto anonymize(const flat_set<Trig1L>& triggers)
+		-> std::enable_if_t<is1L() && tt==Trig1L::type(), const flat_set<std::size_t>&>
+	{
+		static_assert(sizeof(Trig1L)==sizeof(std::size_t), "invalid cast if the key sizes differ");
+		return reinterpret_cast<const flat_set<std::size_t>&>(triggers);
+	}
+	
+	template<bool=true> bool symmetric() const
+	{
+		static_assert(!std::is_same<CastType1, UnusedArg>::value, "this function is not meaningful for this type of trigger, hence should not be used.");
+		return std::all_of(legs.cbegin()+1, legs.cend(), [&](std::size_t l)->bool{ return l==legs[0]; });
+	}
+
+	template<bool=true> CastType1 to_symmetric() const
+	{
+		static_assert(!std::is_same<CastType1, UnusedArg>::value, "this function is not meaningful for this type of trigger, hence should not be used.");
+		CastType1 trig;
+		trig.legs[0] = this->legs[0];
+		return trig;
+	}
+	
+	template<bool=true> std::size_t asymLeg() const
+	{
+		static_assert((tt&TT_MASK_SYMMETRY)==TT_HALFSYM, "this function is not meaningful for this type of trigger, hence should not be used.");
+		return legs[0];
+	}
+	
+	template<bool=true> std::size_t symLeg() const
+	{
+		static_assert((tt&TT_MASK_SYMMETRY)==TT_HALFSYM, "this function is not meaningful for this type of trigger, hence should not be used.");
+		return legs[1];
+	}
+};
+
+template<TriggerType, TriggerType=TT_UNKNOWN> struct TriggerClass;
+
+template<TriggerType object_flag>
+struct TriggerClass<object_flag, TT_UNKNOWN>
+{
+	static constexpr auto addObjFlag(int tt) { return static_cast<TriggerType>(tt|object_flag); }
+	
+	/// single-lepton trigger (e24_lhmedium_L1EM20VH, mu24_imedium_OR_mu40...)
+	struct T_1 : public Trigger<addObjFlag(TT_SINGLELEPTON_FLAG)> {};
+	/// symmetric dilepton trigger (2e17_lhvloose_nod0, 2mu14...)
+	struct T_2sym : public Trigger<addObjFlag(TT_DILEPTON_SYM)> {};
+	/// asymmetric dilepton trigger (mu18_mu8noL1, g35_loose_g25_loose, ...):
+	struct T_2asym : public Trigger<addObjFlag(TT_DILEPTON_ASYM), T_2sym> {};
+	/// symmetric trilepton trigger (3mu6, ...):
+	struct T_3sym : public Trigger<addObjFlag(TT_TRILEPTON_SYM)> {};
+	/// half-symmetric trilepton trigger (e17_lhloose_2e9_lhloose, mu6_2mu4, ...):
+	struct T_3halfsym : public Trigger<addObjFlag(TT_TRILEPTON_HALFSYM), T_3sym> {};
+};
+
+template<TriggerType object1_flag, TriggerType object2_flag>
+struct TriggerClass
+{
+	using A = TriggerClass<object1_flag>;
+	using B = TriggerClass<object2_flag>;
+	static constexpr auto addObjFlags(int tt) { return static_cast<TriggerType>(tt|object1_flag|object2_flag); }
+	
+	/// mixed-flavour dilepton trigger (e17_lhloose_nod0_mu14, e7_lhmedium_mu24, ...):
+	struct T_1_1 : public Trigger<addObjFlags(TT_DILEPTON_FLAG), typename A::T_1, typename B::T_1> {};
+	/// mixed-flavour trilepton trigger type 2x_y (2e12_lhloose_mu10, ...)
+	struct T_2sym_1 : public Trigger<addObjFlags(TT_TRILEPTON_SYM), typename A::T_2sym, typename B::T_1> {};
+	/// mixed-flavour trilepton trigger type x_x_y
+	struct T_2asym_1: public Trigger<addObjFlags(TT_TRILEPTON_ASYM), typename A::T_2asym, typename B::T_1> {};
+	/// mixed-flavour trilepton trigger type x_2y (e12_lhloose_2mu10, ...)
+	struct T_1_2sym: public Trigger<addObjFlags(TT_TRILEPTON_SYM|TT_X2Y_FLAG), typename A::T_1, typename B::T_2sym> {};
+	/// mixed-flavour trilepton trigger type x_y_y
+	struct T_1_2asym: public Trigger<addObjFlags(TT_TRILEPTON_SYM|TT_X2Y_FLAG), typename A::T_1, typename B::T_2asym> {};
+};
+
+}
+
+
+#endif
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/selection.xml b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/selection.xml
new file mode 100644
index 0000000000000000000000000000000000000000..109952f5d697812daa83a32887efa970d06ea8f0
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrection/selection.xml
@@ -0,0 +1,4 @@
+
+<lcgdict>
+   <class name="TrigGlobalEfficiencyCorrectionTool" />
+</lcgdict>
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/data/DataPeriods.cfg b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/data/DataPeriods.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..716bbdede6296d30f961502b4755927a030960a3
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/data/DataPeriods.cfg
@@ -0,0 +1,55 @@
+###
+# List of data periods
+# contact: jmaurer@cern.ch
+###
+
+all		0		999999
+2015	266904	284484
+2015:A	266904	267639
+2015:B	267358	267599
+2015:C	270441	272531
+2015:D	276073	276954
+2015:E	278727	279928
+2015:F	279932	280422
+2015:G	280423	281075
+2015:H	281130	281411
+2015:I	281662	282482
+2015:J	282625	284484
+2016	296939	311481
+2016:A	296939	300287
+2016:B	300345	300908
+2016:C	301912	302393
+2016:D	302737	303560
+2016:E	303638	303892
+2016:F	303943	304494
+2016:G	305291	306714
+2016:I	307124	308084
+2016:K	309311	309759
+2016:L	310015	311481
+2017	325713	341649
+2017:B	325713	328393
+2017:C	329385	330470
+2017:D	330857	332304
+2017:E	332720	334779
+2017:F	334842	335290
+2017:H	336497	336782
+2017:I	336832	337833
+2017:K	338183	340453
+2017:N	341257	341649
+2018	348197	364292
+2018:A	348197	348836
+2018:B	348885	349533
+2018:C	349534	350220
+2018:D	350310	352107
+2018:E	352123	352137
+2018:F	352274	352514
+2018:G	354107	354494
+2018:H	354826	355224
+2018:I	355261	355273
+2018:J	355331	355468
+2018:K	355529	356259
+2018:L	357050	359171
+2018:M	359191	360414
+2018:N	361635	361696
+2018:O	361738	363400
+2018:Q	363664	364292
\ No newline at end of file
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/data/Hierarchies.cfg b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/data/Hierarchies.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..165207a5198c892b2bd31a5db1868258bdcef057
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/data/Hierarchies.cfg
@@ -0,0 +1,46 @@
+#-------- Format ------------------------------------------------------------------------
+# First column = pT interval of validity;  omitted numerical value -> 0/infinity assumed
+# Trigger legs must be separated with ' > ' tokens (including spaces)
+# Aliases can be (re)defined with ':='
+# Comments indicated by '#'
+# pT in MeV or GeV (explicit mention required)
+#---------------------------------------------------------------------------------------
+
+## muon triggers
+multi := mu24 > mu22 > mu20 > mu18 > mu14 > mu10 > mu6 > mu4 > mu8noL1
+[-]   mu50 > mu40 > multi
+# only single-muon triggers have been using isolation so far,
+# so we assume there's no need of being able to compare two legs with isolation since in principle
+# only the lowest-unprescaled chain is used
+[-] mu20_iloose_L1MU15 > mu20_iloose_L1MU15_OR_mu50 > mu50 > mu20_iloose_L1MU15_OR_mu40 > mu40 > multi
+[-] mu26_imedium > mu26_imedium_OR_mu50 > mu50 > mu26_imedium_OR_mu40 > mu40 > multi
+[-] mu26_ivarmedium > mu26_ivarmedium_OR_mu50 > mu50 > mu26_ivarmedium_OR_mu40 > mu40 > multi
+[-] mu24_iloose_L1MU15 > mu24_iloose_L1MU15_OR_mu50 > mu50 > mu24_iloose_L1MU15_OR_mu40 > mu40 > multi
+[-] mu24_iloose_L1MU15_OR_mu24_iloose > mu24_iloose_L1MU15_OR_mu24_iloose_OR_mu50 > mu50 > mu24_iloose_L1MU15_OR_mu24_iloose_OR_mu40 > mu40 > multi
+[-] mu24_imedium > mu24_imedium_OR_mu50 > mu50 > mu24_imedium_OR_mu40 > mu40 > multi
+[-] mu24_ivarmedium > mu24_ivarmedium_OR_mu50 > mu50 > mu24_ivarmedium_OR_mu40 > mu40 > multi
+
+## 2015 electron trigger legs (no '_nod0' suffix)
+loose1 := e12_lhloose_L1EM10VH > e120_lhloose
+loose2 := e17_lhloose > e12_lhloose > e9_lhloose
+[-]          e24_lhmedium_L1EM20VHI > e24_lhmedium_L1EM20VH > e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose
+[-]          e24_lhmedium_L1EM20VHI > e24_lhmedium_L1EM20VH > e60_lhmedium > e24_lhmedium_L1EM15VH > e20_lhmedium > e7_lhmedium > loose1 > loose2
+[<61 GeV]    e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose > e60_lhmedium > e24_lhmedium_L1EM15VH > e20_lhmedium > e7_lhmedium > loose1 > loose2
+[61-121 GeV] e60_lhmedium > e24_lhmedium_L1EM15VH > e20_lhmedium > e7_lhmedium > e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose > loose1 > loose2
+[>121 GeV]   e60_lhmedium > e24_lhmedium_L1EM15VH > e20_lhmedium > e7_lhmedium > loose1 > e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose > loose2
+
+## 2016+2017 electron trigger legs (always '_nod0' suffix)
+medium := e26_lhmedium_nod0_L1EM22VHI > e24_lhmedium_nod0_L1EM20VHI > e26_lhmedium_nod0 > e24_lhmedium_nod0_L1EM20VH > e60_lhmedium_nod0 > e24_lhmedium_nod0_L1EM15VH > e20_lhmedium_nod0 > e7_lhmedium_nod0
+loose1 := e17_lhloose_nod0_L1EM15VH > e10_lhloose_nod0_L1EM8VH  > e140_lhloose_nod0
+loose2 := e17_lhloose_nod0 > e12_lhloose_nod0 > e9_lhloose_nod0 > e17_lhvloose_nod0_L1EM15VHI > e24_lhvloose_nod0_L1EM20VH > e15_lhvloose_nod0_L1EM13VH > e12_lhvloose_nod0_L1EM10VH > e17_lhvloose_nod0
+[-]          e26_lhtight_nod0_ivarloose > e24_lhtight_nod0_ivarloose > medium > loose1 > loose2
+[<61 GeV]    e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0 > e24_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0 > medium > loose1 > loose2
+[61-141 GeV] medium > e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0 > e24_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0 > loose1 > loose2
+[>141 GeV]   medium > loose1 > e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0 > e24_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0 > loose2
+
+## photon triggers
+[-] g35_tight_icalotight_L1EM24VHI > g20_tight_icalovloose_L1EM15VHI > g22_tight_L1EM15VHI > g22_tight > g20_tight > g35_medium_L1EM20VH > g25_medium_L1EM20VH > g35_loose_L1EM24VHI > g35_loose_L1EM22VHI > g50_loose_L1EM20VH > g140_loose > g120_loose > g35_loose > g25_loose > g20_loose > g15_loose > g12_loose > g10_loose > g35_loose_L1EM15 > g25_loose_L1EM15
+
+
+
+
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/data/MapKeys.cfg b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/data/MapKeys.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..b56288131dc666bb4c0c4727060fb3a7443708e7
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/data/MapKeys.cfg
@@ -0,0 +1,101 @@
+###
+# List of trigger legs supported by each map key; only used by suggestElectronMapKeys()
+# contact: jmaurer@cern.ch
+###
+
+
+###
+[VERSION] 2015_2016/rel20.7/Moriond_February2017_v2, 2015_2016/rel20.7/Moriond_February2017_v3
+###
+2015  e12_lhloose_L1EM10VH  2015_e12_lhloose_L1EM10VH   DI_E_2015_e12_lhloose_L1EM10VH_2016_e15_lhvloose_nod0_L1EM13VH  DI_E_2015_e12_lhloose_L1EM10VH_2016_e17_lhvloose_nod0
+2015  e15_lhloose_L1EM13VH  2015_e15_lhloose_L1EM13VH
+2015  e17_lhloose_L1EM15  2015_e17_lhloose_L1EM15
+2015  e17_lhloose  2015_e17_lhloose     DI_E_2015_e17_lhloose_2016_e17_lhloose  MULTI_L_2015_e17_lhloose_2016_e17_lhloose_nod0  TRI_E_2015_e17_lhloose_2016_e17_lhloose_nod0  TRI_E_2015_e17_lhloose_2016_e17_lhmedium_nod0
+2015  e17_lhmedium  2015_e17_lhmedium
+2015  e20_lhmedium  2015_e20_lhmedium
+2015  e24_lhmedium_L1EM15VH  2015_e24_lhmedium_L1EM15VH
+2015  e24_lhmedium_L1EM20VHI  2015_e24_lhmedium_L1EM20VHI       MULTI_L_2015_e24_lhmedium_L1EM20VHI_2016_e24_lhmedium_nod0_L1EM20VHI    MULTI_L_2015_e24_lhmedium_L1EM20VHI_2016_e26_lhmedium_nod0_L1EM22VHI
+2015  e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose  2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose SINGLE_E_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_e24_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0 SINGLE_E_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0
+2015  e24_medium_L1EM20VHI  2015_e24_medium_L1EM20VHI
+2015  e26_lhtight_nod0_iloose  2015_e26_lhtight_nod0_iloose
+2015  e7_lhmedium  2015_e7_lhmedium     MULTI_L_2015_e7_lhmedium_2016_e7_lhmedium_nod0
+2015  e9_lhloose  2015_e9_lhloose       TRI_E_2015_e9_lhloose_2016_e9_lhloose_nod0      TRI_E_2015_e9_lhloose_2016_e9_lhmedium_nod0
+2015  e17_lhloose  2015_e17_lhloose     DI_E_2015_e17_lhloose_2016_e17_lhloose  MULTI_L_2015_e17_lhloose_2016_e17_lhloose_nod0  TRI_E_2015_e17_lhloose_2016_e17_lhloose_nod0  TRI_E_2015_e17_lhloose_2016_e17_lhmedium_nod0
+2016  e17_lhloose  2016_e17_lhloose     DI_E_2015_e17_lhloose_2016_e17_lhloose  MULTI_L_2015_e17_lhloose_2016_e17_lhloose_nod0  TRI_E_2015_e17_lhloose_2016_e17_lhloose_nod0
+2016  e7_lhmedium  MULTI_L_2015_e7_lhmedium_2016_e7_lhmedium_nod0
+2016  e9_lhloose  TRI_E_2015_e9_lhloose_2016_e9_lhloose_nod0
+2016  e12_lhloose_nod0  2016_e12_lhloose_nod0   MULTI_L_2015_e12_lhloose_2016_e12_lhloose_nod0
+2016  e15_lhvloose_nod0_L1EM13VH  2016_e15_lhvloose_nod0_L1EM13VH       DI_E_2015_e12_lhloose_L1EM10VH_2016_e15_lhvloose_nod0_L1EM13VH
+2016  e17_lhloose  2016_e17_lhloose     DI_E_2015_e17_lhloose_2016_e17_lhloose  MULTI_L_2015_e17_lhloose_2016_e17_lhloose_nod0  TRI_E_2015_e17_lhloose_2016_e17_lhloose_nod0
+2016  e17_lhloose_nod0  2016_e17_lhloose_nod0   MULTI_L_2015_e17_lhloose_2016_e17_lhloose_nod0  TRI_E_2015_e17_lhloose_2016_e17_lhloose_nod0
+2016  e17_lhmedium_nod0_L1EM15HI  2016_e17_lhmedium_nod0_L1EM15HI
+2016  e17_lhmedium_nod0  2016_e17_lhmedium_nod0 TRI_E_2015_e17_lhloose_2016_e17_lhmedium_nod0
+2016  e17_lhmedium_nod0_ivarloose_L1EM15HI  2016_e17_lhmedium_nod0_ivarloose_L1EM15HI
+2016  e17_lhvloose_nod0  2016_e17_lhvloose_nod0 DI_E_2015_e12_lhloose_L1EM10VH_2016_e17_lhvloose_nod0
+2016  e20_lhmedium_nod0  2016_e20_lhmedium_nod0
+2016  e24_lhmedium_nod0_L1EM15VH  2016_e24_lhmedium_nod0_L1EM15VH
+2016  e24_lhmedium_nod0_L1EM20VHI  2016_e24_lhmedium_nod0_L1EM20VHI     MULTI_L_2015_e24_lhmedium_L1EM20VHI_2016_e24_lhmedium_nod0_L1EM20VHI
+2016  e24_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0  2016_e24_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0   SINGLE_E_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_e24_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0
+2016  e26_lhmedium_nod0_L1EM22VHI  2016_e26_lhmedium_nod0_L1EM22VHI     MULTI_L_2015_e24_lhmedium_L1EM20VHI_2016_e26_lhmedium_nod0_L1EM22VHI
+2016  e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0  2016_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0   SINGLE_E_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0
+2016  e7_lhmedium_nod0  2016_e7_lhmedium_nod0   MULTI_L_2015_e7_lhmedium_2016_e7_lhmedium_nod0
+2016  e9_lhloose_nod0  2016_e9_lhloose_nod0     TRI_E_2015_e9_lhloose_2016_e9_lhloose_nod0
+2016  e9_lhmedium_nod0  2016_e9_lhmedium_nod0   TRI_E_2015_e9_lhloose_2016_e9_lhmedium_nod0
+
+###
+[VERSION] 2015_2017/rel21.2/Moriond_February2018_v2
+###
+2015    e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose   2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose      MULTI_L_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_e26_lhmedium_nod0_L1EM22VHI_2017_e26_lhmedium_nod0      SINGLE_E_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_2017_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0
+2015    e7_lhmedium     2015_e7_lhmedium        MULTI_L_2015_e7_lhmedium_2016_e7_lhmedium_nod0_2017_e7_lhmedium_nod0
+2015    e17_lhloose     2015_e17_lhloose        MULTI_L_2015_e17_lhloose_2016_e17_lhloose_nod0_2017_e17_lhloose_nod0
+TRI_E_2015_e17_lhloose_2016_e17_lhloose_nod0_2017_e24_lhvloose_nod0_L1EM20VH
+2015    e12_lhloose     MULTI_L_2015_e12_lhloose_2016_e12_lhloose_nod0_2017_e12_lhloose_nod0
+2015    e12_lhloose_L1EM10VH    2015_e12_lhloose_L1EM10VH       DI_E_2015_e12_lhloose_L1EM10VH_2016_e17_lhvloose_nod0_2017_e24_lhvloose_nod0_L1EM20VH
+2015    e9_lhloose      2015_e9_lhloose TRI_E_2015_e9_lhloose_2016_e9_lhloose_nod0_2017_e12_lhvloose_nod0_L1EM10VH
+2016    e12_lhloose_nod0        2016_e12_lhloose_nod0   MULTI_L_2015_e12_lhloose_2016_e12_lhloose_nod0_2017_e12_lhloose_nod0
+2016    e17_lhloose_nod0        2016_e17_lhloose_nod0   MULTI_L_2015_e17_lhloose_2016_e17_lhloose_nod0_2017_e17_lhloose_nod0    TRI_E_2015_e17_lhloose_2016_e17_lhloose_nod0_2017_e24_lhvloose_nod0_L1EM20VH
+2016    e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0    2016_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0       SINGLE_E_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_2017_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0
+2016    e7_lhmedium_nod0        2016_e7_lhmedium_nod0   MULTI_L_2015_e7_lhmedium_2016_e7_lhmedium_nod0_2017_e7_lhmedium_nod0
+2016    e26_lhmedium_nod0_L1EM22VHI     2016_e26_lhmedium_nod0_L1EM22VHI        MULTI_L_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_e26_lhmedium_nod0_L1EM22VHI_2017_e26_lhmedium_nod0
+2016    e17_lhvloose_nod0       2016_e17_lhvloose_nod0  DI_E_2015_e12_lhloose_L1EM10VH_2016_e17_lhvloose_nod0_2017_e24_lhvloose_nod0_L1EM20VH
+2016    e9_lhloose_nod0 2016_e9_lhloose_nod0    TRI_E_2015_e9_lhloose_2016_e9_lhloose_nod0_2017_e12_lhvloose_nod0_L1EM10VH
+2017    e12_lhloose_nod0        2017_e12_lhloose_nod0   MULTI_L_2015_e12_lhloose_2016_e12_lhloose_nod0_2017_e12_lhloose_nod0
+2017    e17_lhloose_nod0        2017_e17_lhloose_nod0   MULTI_L_2015_e17_lhloose_2016_e17_lhloose_nod0_2017_e17_lhloose_nod0
+2017    e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0    2017_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0       SINGLE_E_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_2017_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0
+2017    e7_lhmedium_nod0        2017_e7_lhmedium_nod0   MULTI_L_2015_e7_lhmedium_2016_e7_lhmedium_nod0_2017_e7_lhmedium_nod0
+2017    e26_lhmedium_nod0       2017_e26_lhmedium_nod0  MULTI_L_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_e26_lhmedium_nod0_L1EM22VHI_2017_e26_lhmedium_nod0
+2017    e12_lhvloose_nod0_L1EM10VH      2017_e12_lhvloose_nod0_L1EM10VH TRI_E_2015_e9_lhloose_2016_e9_lhloose_nod0_2017_e12_lhvloose_nod0_L1EM10VH
+2017    e24_lhvloose_nod0_L1EM20VH      2017_e24_lhvloose_nod0_L1EM20VH DI_E_2015_e12_lhloose_L1EM10VH_2016_e17_lhvloose_nod0_2017_e24_lhvloose_nod0_L1EM20VH   TRI_E_2015_e17_lhloose_2016_e17_lhloose_nod0_2017_e24_lhvloose_nod0_L1EM20VH
+
+###
+[VERSION] 2015_2017/rel21.2/Consolidation_September2018_v1
+###
+2015  e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose  2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose  SINGLE_E_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_2018_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0  MULTI_L_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_e26_lhmedium_nod0_L1EM22VHI_2017_2018_e26_lhmedium_nod0
+2015  e7_lhmedium  2015_e7_lhmedium  MULTI_L_2015_e7_lhmedium_2016_2018_e7_lhmedium_nod0
+2015  e17_lhloose  2015_e17_lhloose  MULTI_L_2015_e17_lhloose_2016_2018_e17_lhloose_nod0  TRI_E_2015_e17_lhloose_2016_e17_lhloose_nod0_2017_2018_e24_lhvloose_nod0_L1EM20VH
+2015  e12_lhloose  MULTI_L_2015_e12_lhloose_2016_2018_e12_lhloose_nod0
+2015  e12_lhloose_L1EM10VH  2015_e12_lhloose_L1EM10VH  DI_E_2015_e12_lhloose_L1EM10VH_2016_e17_lhvloose_nod0_2017_2018_e24_lhvloose_nod0_L1EM20VH  DI_E_2015_e12_lhloose_L1EM10VH_2016_e17_lhvloose_nod0_2017_2018_e17_lhvloose_nod0_L1EM15VHI
+2015  e9_lhloose  2015_e9_lhloose  TRI_E_2015_e9_lhloose_2016_e9_lhloose_nod0_2017_2018_e12_lhvloose_nod0_L1EM10VH
+2016  e12_lhloose_nod0  2016_e12_lhloose_nod0  MULTI_L_2015_e12_lhloose_2016_2018_e12_lhloose_nod0
+2016  e17_lhloose_nod0  2016_e17_lhloose_nod0  MULTI_L_2015_e17_lhloose_2016_2018_e17_lhloose_nod0  TRI_E_2015_e17_lhloose_2016_e17_lhloose_nod0_2017_2018_e24_lhvloose_nod0_L1EM20VH
+2016  e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0  2016_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0  SINGLE_E_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_2018_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0
+2016  e7_lhmedium_nod0  2016_e7_lhmedium_nod0  MULTI_L_2015_e7_lhmedium_2016_2018_e7_lhmedium_nod0
+2016  e26_lhmedium_nod0_L1EM22VHI  2016_e26_lhmedium_nod0_L1EM22VHI  MULTI_L_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_e26_lhmedium_nod0_L1EM22VHI_2017_2018_e26_lhmedium_nod0
+2016  e17_lhvloose_nod0  2016_e17_lhvloose_nod0  DI_E_2015_e12_lhloose_L1EM10VH_2016_e17_lhvloose_nod0_2017_2018_e24_lhvloose_nod0_L1EM20VH  DI_E_2015_e12_lhloose_L1EM10VH_2016_e17_lhvloose_nod0_2017_2018_e17_lhvloose_nod0_L1EM15VHI
+2016  e9_lhloose_nod0  2016_e9_lhloose_nod0  TRI_E_2015_e9_lhloose_2016_e9_lhloose_nod0_2017_2018_e12_lhvloose_nod0_L1EM10VH
+2017  e26_lhmedium_nod0  2017_e26_lhmedium_nod0  MULTI_L_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_e26_lhmedium_nod0_L1EM22VHI_2017_2018_e26_lhmedium_nod0
+2017  e12_lhloose_nod0  2017_e12_lhloose_nod0  MULTI_L_2015_e12_lhloose_2016_2018_e12_lhloose_nod0
+2017  e17_lhloose_nod0  2017_e17_lhloose_nod0  MULTI_L_2015_e17_lhloose_2016_2018_e17_lhloose_nod0
+2017  e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0  2017_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0  SINGLE_E_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_2018_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0
+2017  e7_lhmedium_nod0  2017_e7_lhmedium_nod0  MULTI_L_2015_e7_lhmedium_2016_2018_e7_lhmedium_nod0
+2017  e17_lhvloose_nod0_L1EM15VHI  2017_e17_lhvloose_nod0_L1EM15VHI  DI_E_2015_e12_lhloose_L1EM10VH_2016_e17_lhvloose_nod0_2017_2018_e17_lhvloose_nod0_L1EM15VHI
+2017  e24_lhvloose_nod0_L1EM20VH  2017_e24_lhvloose_nod0_L1EM20VH  DI_E_2015_e12_lhloose_L1EM10VH_2016_e17_lhvloose_nod0_2017_2018_e24_lhvloose_nod0_L1EM20VH  TRI_E_2015_e17_lhloose_2016_e17_lhloose_nod0_2017_2018_e24_lhvloose_nod0_L1EM20VH
+2017  e12_lhvloose_nod0_L1EM10VH  2017_e12_lhvloose_nod0_L1EM10VH  TRI_E_2015_e9_lhloose_2016_e9_lhloose_nod0_2017_2018_e12_lhvloose_nod0_L1EM10VH
+2018  e26_lhmedium_nod0  2018_e26_lhmedium_nod0  MULTI_L_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_e26_lhmedium_nod0_L1EM22VHI_2017_2018_e26_lhmedium_nod0
+2018  e12_lhloose_nod0  2018_e12_lhloose_nod0  MULTI_L_2015_e12_lhloose_2016_2018_e12_lhloose_nod0
+2018  e17_lhloose_nod0  2018_e17_lhloose_nod0  MULTI_L_2015_e17_lhloose_2016_2018_e17_lhloose_nod0
+2018  e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0  2018_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0  SINGLE_E_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_2018_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0
+2018  e7_lhmedium_nod0  2018_e7_lhmedium_nod0  MULTI_L_2015_e7_lhmedium_2016_2018_e7_lhmedium_nod0
+2018  e17_lhvloose_nod0_L1EM15VHI  2018_e17_lhvloose_nod0_L1EM15VHI  DI_E_2015_e12_lhloose_L1EM10VH_2016_e17_lhvloose_nod0_2017_2018_e17_lhvloose_nod0_L1EM15VHI
+2018  e24_lhvloose_nod0_L1EM20VH  2018_e24_lhvloose_nod0_L1EM20VH  DI_E_2015_e12_lhloose_L1EM10VH_2016_e17_lhvloose_nod0_2017_2018_e24_lhvloose_nod0_L1EM20VH  TRI_E_2015_e17_lhloose_2016_e17_lhloose_nod0_2017_2018_e24_lhvloose_nod0_L1EM20VH
+2018  e12_lhvloose_nod0_L1EM10VH  2018_e12_lhvloose_nod0_L1EM10VH  TRI_E_2015_e9_lhloose_2016_e9_lhloose_nod0_2017_2018_e12_lhvloose_nod0_L1EM10VH
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/data/Thresholds.cfg b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/data/Thresholds.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..5772af543277a438baf161fb1eecefaf92df0459
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/data/Thresholds.cfg
@@ -0,0 +1,101 @@
+###
+# List of pT thresholds for the various trigger legs
+# pT in MeV or GeV (explicit mention required)
+# contact: jmaurer@cern.ch
+###
+
+mu50	52500 MeV
+mu40	42000 MeV
+mu26_imedium			27300 MeV
+mu26_imedium_OR_mu40	27300 MeV
+mu26_imedium_OR_mu50	27300 MeV
+mu26_ivarmedium			27300 MeV
+mu26_ivarmedium_OR_mu40	27300 MeV
+mu26_ivarmedium_OR_mu50	27300 MeV
+mu24_iloose_L1MU15			25200 MeV
+mu24_iloose_L1MU15_OR_mu40	25200 MeV
+mu24_iloose_L1MU15_OR_mu50	25200 MeV
+mu24_iloose_L1MU15_OR_mu24_iloose			25200 MeV
+mu24_iloose_L1MU15_OR_mu24_iloose_OR_mu40	25200 MeV
+mu24_iloose_L1MU15_OR_mu24_iloose_OR_mu50	25200 MeV
+mu24_imedium			25200 MeV
+mu24_imedium_OR_mu40	25200 MeV
+mu24_imedium_OR_mu50	25200 MeV
+mu24_ivarmedium			25200 MeV
+mu24_ivarmedium_OR_mu40	25200 MeV
+mu24_ivarmedium_OR_mu50	25200 MeV
+mu26		27000 MeV
+mu24		25000 MeV
+mu22		23000 MeV
+mu20_iloose_L1MU15_OR_mu50		21000 MeV
+mu20_iloose_L1MU15_OR_mu40		21000 MeV
+mu20_iloose_L1MU15				21000 MeV
+mu20		21000 MeV
+mu18		19000 MeV
+mu18noL1	18000 MeV
+mu15		15000 MeV
+mu15noL1	15000 MeV
+mu14		15000 MeV
+mu10		11000 MeV
+mu8noL1		10000 MeV
+mu6			7000 MeV
+mu4			5000 MeV
+mu4noL1		4000 MeV
+mu2noL1		2000 MeV
+
+e140_lhloose_nod0			141 GeV
+e120_lhloose				121 GeV
+e60_lhmedium				61 GeV
+e60_lhmedium_nod0			61 GeV
+e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0	27 GeV
+e26_lhtight_nod0_ivarloose	27 GeV
+e26_lhmedium_nod0			27 GeV
+e26_lhmedium_nod0_L1EM22VHI	27 GeV
+e24_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0	25 GeV
+e24_lhtight_nod0_ivarloose 	25 GeV
+e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose	25 GeV
+e24_lhmedium_L1EM20VH		25 GeV
+e24_lhmedium_nod0_L1EM20VH	25 GeV
+e24_lhmedium_L1EM15VH		25 GeV
+e24_lhmedium_L1EM20VHI		25 GeV
+e24_lhmedium_nod0_L1EM20VHI	25 GeV
+e24_lhmedium_nod0_L1EM15VH	25 GeV
+e24_lhvloose_nod0_L1EM20VH	25 GeV
+e20_lhmedium_nod0			21 GeV
+e20_lhmedium				21 GeV
+e17_lhloose					18 GeV
+e17_lhloose_nod0			18 GeV
+e17_lhvloose_nod0			18 GeV
+e17_lhvloose_nod0_L1EM15VHI	18 GeV
+e17_lhloose_nod0_L1EM15VH	18 GeV
+e15_lhvloose_nod0_L1EM13VH	16 GeV
+e12_lhloose					13 GeV
+e12_lhloose_nod0			13 GeV
+e12_lhloose_L1EM10VH		13 GeV
+e12_lhvloose_nod0_L1EM10VH	13 GeV
+e10_lhloose_nod0_L1EM8VH	11 GeV
+e9_lhloose					10 GeV
+e9_lhloose_nod0				10 GeV
+e7_lhmedium_nod0			8 GeV
+e7_lhmedium					8 GeV
+
+g140_loose					141 GeV
+g120_loose					121 GeV
+g50_loose_L1EM20VH			51 GeV
+g35_tight_icalotight_L1EM24VHI	36 GeV
+g35_medium_L1EM20VH			36 GeV
+g35_loose_L1EM24VHI			36 GeV
+g35_loose_L1EM22VHI			36 GeV
+g35_loose					36 GeV
+g35_loose_L1EM15			36 GeV
+g25_medium_L1EM20VH			26 GeV
+g25_loose					26 GeV
+g25_loose_L1EM15			26 GeV
+g22_tight_L1EM15VHI			23 GeV
+g22_tight					23 GeV
+g20_tight_icalovloose_L1EM15VHI	21 GeV
+g20_tight		21 GeV
+g20_loose		21 GeV
+g15_loose		16 GeV
+g12_loose		13 GeV
+g10_loose		11 GeV
\ No newline at end of file
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/data/Triggers.cfg b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/data/Triggers.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..0d6d1483660e582bd88dd82668f114bafbc824f5
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/data/Triggers.cfg
@@ -0,0 +1,124 @@
+###
+# List of triggers and associated legs
+# If no leg is specified, the assumption is that it is a single-lepton trigger and that the name of the leg is the same
+# contact: jmaurer@cern.ch
+###
+
+## Single muon triggers
+mu20_iloose_L1MU15
+mu20_iloose_L1MU15_OR_mu40
+mu20_iloose_L1MU15_OR_mu50
+mu24_iloose_L1MU15
+mu24_iloose_L1MU15_OR_mu40
+mu24_iloose_L1MU15_OR_mu50
+mu24_iloose_L1MU15_OR_mu24_iloose
+mu24_iloose_L1MU15_OR_mu24_iloose_OR_mu40
+mu24_iloose_L1MU15_OR_mu24_iloose_OR_mu50
+mu24_imedium
+mu24_imedium_OR_mu40
+mu24_imedium_OR_mu50
+mu24_ivarmedium
+mu24_ivarmedium_OR_mu40
+mu24_ivarmedium_OR_mu50
+mu26_imedium
+mu26_imedium_OR_mu40
+mu26_imedium_OR_mu50
+mu26_ivarmedium
+mu26_ivarmedium_OR_mu40
+mu26_ivarmedium_OR_mu50
+mu40
+mu50
+
+## Dimuon triggers
+2mu10			mu10	mu10
+2mu14			mu14	mu14
+mu18_mu8noL1	mu18	mu8noL1
+mu20_mu8noL1	mu20	mu8noL1
+mu22_mu8noL1	mu22	mu8noL1
+	
+## Trimuon triggers
+3mu4		mu4		mu4		mu4
+3mu6		mu6		mu6		mu6
+mu6_2mu4	mu6		mu4		mu4
+mu18_2mu4noL1	mu18	mu4noL1		mu4noL1
+mu20_2mu4noL1	mu20	mu4noL1		mu4noL1
+
+## Single electron triggers
+e15_lhvloose_nod0_L1EM13VH
+e24_lhmedium_L1EM20VH
+e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose
+e24_lhtight_nod0_ivarloose
+e24_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0
+e26_lhtight_nod0_ivarloose
+e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0
+e120_lhloose
+e60_lhmedium
+e60_lhmedium_nod0
+e140_lhloose_nod0
+
+## Dielectron triggers
+2e12_lhloose_L12EM10VH			e12_lhloose_L1EM10VH			e12_lhloose_L1EM10VH
+2e15_lhvloose_nod0_L12EM13VH	e15_lhvloose_nod0_L1EM13VH		e15_lhvloose_nod0_L1EM13VH
+2e17_lhvloose_nod0				e17_lhvloose_nod0				e17_lhvloose_nod0
+2e17_lhvloose_nod0_L12EM15VHI	e17_lhvloose_nod0_L1EM15VHI		e17_lhvloose_nod0_L1EM15VHI
+2e24_lhvloose_nod0				e24_lhvloose_nod0_L1EM20VH		e24_lhvloose_nod0_L1EM20VH
+
+## Trielectron triggers
+e17_lhloose_2e9_lhloose				e17_lhloose			e9_lhloose			e9_lhloose
+e17_lhloose_nod0_2e9_lhloose_nod0	e17_lhloose_nod0	e9_lhloose_nod0		e9_lhloose_nod0
+e17_lhloose_nod0_2e10_lhloose_nod0_L1EM15VH_3EM8VH		e17_lhloose_nod0_L1EM15VH	e10_lhloose_nod0_L1EM8VH	e10_lhloose_nod0_L1EM8VH
+e24_lhvloose_nod0_2e12_lhvloose_nod0_L1EM20VH_3EM10VH	e24_lhvloose_nod0_L1EM20VH	e12_lhvloose_nod0_L1EM10VH	e12_lhvloose_nod0_L1EM10VH
+
+## Electron-muon triggers
+e17_lhloose_nod0_mu14	e17_lhloose_nod0	mu14
+e7_lhmedium_nod0_mu24	e7_lhmedium_nod0	mu24
+e17_lhloose_mu14		e17_lhloose			mu14
+e7_lhmedium_mu24		e7_lhmedium			mu24
+e24_lhmedium_L1EM20VHI_mu8noL1			e24_lhmedium_L1EM20VHI			mu8noL1
+e24_lhmedium_nod0_L1EM20VHI_mu8noL1		e24_lhmedium_nod0_L1EM20VHI		mu8noL1
+e26_lhmedium_nod0_mu8noL1				e26_lhmedium_nod0				mu8noL1
+e26_lhmedium_nod0_L1EM22VHI_mu8noL1		e26_lhmedium_nod0_L1EM22VHI		mu8noL1
+e12_lhloose_2mu10		e12_lhloose			mu10				mu10
+e12_lhloose_nod0_2mu10	e12_lhloose_nod0	mu10				mu10
+2e12_lhloose_mu10		e12_lhloose			e12_lhloose			mu10
+2e12_lhloose_nod0_mu10	e12_lhloose_nod0	e12_lhloose_nod0	mu10
+
+## Single photon triggers
+g140_loose
+g120_loose
+
+## Diphoton triggers
+2g50_loose_L12EM20VH				g50_loose_L1EM20VH					g50_loose_L1EM20VH
+g35_medium_g25_medium_L12EM20VH		g35_medium_L1EM20VH					g25_medium_L1EM20VH
+g35_loose_g25_loose					g35_loose							g25_loose
+g35_loose_L1EM15_g25_loose_L1EM15	g35_loose_L1EM15					g25_loose_L1EM15
+2g20_tight_icalovloose_L12EM15VHI	g20_tight_icalovloose_L1EM15VHI		g20_tight_icalovloose_L1EM15VHI
+2g22_tight_L12EM15VHI				g22_tight_L1EM15VHI					g22_tight_L1EM15VHI
+2g22_tight							g22_tight							g22_tight
+2g20_tight							g20_tight							g20_tight
+
+## Triphoton triggers
+2g25_loose_g15_loose	g25_loose	g25_loose	g15_loose
+2g20_loose_g15_loose	g20_loose	g20_loose	g15_loose
+3g20_loose				g20_loose	g20_loose	g20_loose
+3g15_loose				g15_loose	g15_loose	g15_loose
+
+## Electron-photon triggers
+e24_lhmedium_nod0_L1EM20VH_g25_medium	e24_lhmedium_nod0_L1EM20VH		g25_medium_L1EM20VH
+e24_lhmedium_nod0_L1EM15VH_g25_medium	e24_lhmedium_nod0_L1EM15VH		g25_medium_L1EM20VH
+e24_lhmedium_L1EM15VH_g25_medium		e24_lhmedium_L1EM15VH			g25_medium_L1EM20VH
+e20_lhmedium_nod0_g35_loose		e20_lhmedium_nod0		g35_loose
+e20_lhmedium_g35_loose			e20_lhmedium			g35_loose
+e24_lhmedium_nod0_2g12_loose	e24_lhmedium_nod0_L1EM20VH		g12_loose		g12_loose
+e20_lhmedium_nod0_2g10_loose	e24_lhmedium_nod0_L1EM20VH		g10_loose		g10_loose
+e20_lhmedium_2g10_loose			e20_lhmedium					g10_loose		g10_loose
+
+## Muon-photon triggers
+g35_tight_icalotight_L1EM24VHI_mu18noL1		g35_tight_icalotight_L1EM24VHI		mu18noL1
+g35_loose_L1EM22VHI_mu18noL1				g35_loose_L1EM22VHI					mu18noL1
+g35_loose_L1EM24VHI_mu18					g35_loose_L1EM24VHI					mu18
+g25_medium_mu24								g25_medium_L1EM20VH					mu24
+g35_tight_icalotight_L1EM24VHI_mu15noL1_mu2noL1 	g35_tight_icalotight_L1EM24VHI		mu15noL1		mu2noL1
+g35_loose_L1EM24VHI_mu15_mu2noL1					g35_loose_L1EM24VHI					mu15			mu2noL1	
+g35_loose_L1EM22VHI_mu15noL1_mu2noL1				g35_loose_L1EM22VHI					mu15noL1		mu2noL1
+2g10_loose_mu20										g10_loose							g10_loose		mu20
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/doc/formulas.pdf b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/doc/formulas.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..dbaae17f399d512767a3536b11308c9bf22bae6d
Binary files /dev/null and b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/doc/formulas.pdf differ
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/doc/formulas.tex b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/doc/formulas.tex
new file mode 100644
index 0000000000000000000000000000000000000000..78b8cf1e9fd973be5fe5c58ac3add69d2407498c
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/doc/formulas.tex
@@ -0,0 +1,519 @@
+\documentclass{article}
+\usepackage{mathtools}
+\usepackage{amsmath}
+\usepackage{amssymb}
+\usepackage{subfigure}
+\usepackage{graphicx}
+\usepackage{color}
+\usepackage[labelformat=empty]{caption}
+%\usepackage{url}
+\usepackage{hyperref}
+\newcommand{\link}[2]{\textcolor{blue}{\underline{\smash{\href{#1}{#2}}}}}
+\newcommand{\pt}{\ensuremath{p_\mathrm{T}}}
+\newcommand{\met}{\ensuremath{E_\mathrm{T}^\mathrm{miss}}}
+\newcommand{\meff}{\ensuremath{m_\mathrm{eff}}}
+\newcommand{\amu}{\ensuremath{\langle\mu\rangle}}
+\newcommand{\BYes}{\textcolor{green}{$\bullet$}}
+\newcommand{\BNeutr}{\textcolor{orange}{$\bullet$}}
+\newcommand{\BNo}{\textcolor{red}{$\bullet$}}
+%\newcommand{\pro}{\ensuremath{\mathbb{P}}}
+\newcommand{\pro}{\ensuremath{\mathbb{P}}}
+
+\usepackage{pbox}
+\usepackage{multirow}
+
+\definecolor{col0}{RGB}{0,0,0}
+
+
+\begin{document}
+
+\section*{Definitions}
+
+Making generous use of the formulas to expand probabilities of unions or intersections of $n$ events 
+(RHS sums have $2^n-1$ terms in total):
+\begin{align*}
+\pro(A_1\vee \ldots \vee A_n)  &= \pro(A_1) + \ldots - \pro(A_1\wedge A_2) - \ldots + (-1)^{n+1} \pro(A_1\wedge\ldots\wedge A_n)\\
+\pro(A_1\wedge \ldots \wedge A_n)  &= \pro(A_1) + \ldots - \pro(A_1\vee A_2) - \ldots + (-1)^{n+1} \pro(A_1\vee\ldots\vee A_n)
+\end{align*}
+
+\tableofcontents
+
+\section{Single lepton triggers}
+\subsection{One $e/\mu$ trigger, $n$ leptons}
+\begin{align}
+\pro(T_n) & =\pro(\ell_1\vee\ldots\vee\ell_n)\notag\\
+&= 1 - \pro(\bar\ell_1\wedge\ldots\wedge\bar\ell_n)\notag\\
+&= 1- \prod_{1\leq k\leq n} (1-\pro(\ell_k))
+\label{e:1}
+\end{align}
+
+This is the formula implemented in the muon trigger scale factor tool. 
+
+\subsection{One $e$ + one $\mu$ trigger, $n_e+n_\mu$ leptons}
+\label{s:eOm}
+
+\begin{align}
+\pro(T_{n_e+n_\mu})&=\pro(E_{n_e}\vee M_{n_\mu})\notag\\
+& =1 - \pro(\bar E_{n_e})\pro(\bar M_{n_\mu})\notag\\
+&= 1- \prod_{1\leq k\leq n_e} (1-\pro(e_k))\prod_{1\leq k\leq n_\mu} (1-\pro(\mu_k))
+\label{e:eOm}
+\end{align}
+
+This is the same formula as the previous case. Note however that the total efficiency can't be factorized into separate contributions for electrons and muons (because of this leading ``1 -'' term breaking linearity), so the scale factor has to be evaluated at once considering both lepton flavours. 
+
+\subsection{Several $e/\mu$ triggers, $n$ leptons}
+\label{s:x1e}
+
+If there are $k$ triggers in the combination: 
+
+\begin{align}
+\pro(T_n)&=\pro(T^{1}_n\vee\ldots\vee T^{k}_n) \notag\\
+&= 1 - \pro(\bar T^1_n\wedge\ldots\wedge\bar T_n^k) \notag\\
+&= \pro(\bar \ell_1^1\vee\ldots\bar\ell_1^k)\times\ldots\times\pro(\bar \ell_n^1\vee\ldots\bar\ell_n^k)\notag\\
+& = 1- \prod_{1\leq j\leq n} (1-\pro(\ell_j^{\lambda_j}))
+\end{align}
+
+This is, conveniently, the same expression as for a single trigger~\ref{e:1}, except that for each lepton 
+one needs to consider the probability of triggering the loosest trigger leg according to the established hierarchy, 
+The index of this loosest leg is indicated by $\lambda_j\in [0,k-1]$. 
+As indicated by the subscript $j$, this index may vary from one lepton 
+to the other since a given lepton might not be on plateau for all $k$ legs; 
+also, the hierarchy itself is sometimes $p_\mathrm{T}$-dependent. 
+
+\subsection{Several $e$ + $\mu$ triggers, $n_e+n_\mu$ leptons}
+\label{s:x1l}
+
+Straightforward combination of sections~\ref{s:eOm} and~\ref{s:x1e}:
+\begin{align}
+\pro(T_n)&=
+1- \prod_{1\leq j\leq n_e} (1-\pro(e_j^{\lambda_j}))\prod_{1\leq j\leq n_\mu} (1-\pro(\mu_j^{\lambda_j}))
+\label{e:x1l}
+\end{align}
+
+
+\section{Dilepton triggers, simple cases}
+
+\subsection{One $e\mu$ trigger, $n_e+n_\mu$ leptons}
+
+Need to fire both the electron and the muon legs, things factorize nicely: 
+\begin{align}
+\pro(T_{n_e+n_\mu})&=\pro(E_{n_e}\wedge M_{n_\mu}) \notag\\
+&= \pro(E_{n_e})\times\pro(M_{n_\mu})\notag\\
+&=\left(1- \prod_{1\leq k\leq n_e} (1-\pro(e_k))\right)\times\left(1- \prod_{1\leq k\leq n_\mu} (1-\pro(\mu_k))\right)
+\end{align}
+
+\subsection{One symmetric $ee/\mu\mu$ trigger, $n$ leptons}
+
+For example \textrm{2e12\_lhloose}. 
+The efficiency is computed by induction over the number of leptons $n$. 
+Let's define $S_{n-1}=\ell_1\vee\ldots\vee\ell_{n-1}$, i.e. when at least one leg was fired by one of the first $n-1$ leptons. 
+Then: 
+
+\begin{align}
+\pro(T_n) &= \pro(T_{n-1}  \vee (\ell_n\wedge S_{n-1}))\notag\\
+&= \pro(T_{n-1}) + \pro(\ell_n\wedge S_{n-1}) - \pro(T_{n-1}\wedge \ell_n\wedge S_{n-1})\notag\\
+&= (1-\pro(\ell_n))\pro(T_{n-1})+\pro(\ell_n)\pro(S_{n-1})\quad\text{since }T_{n-1}\wedge S_{n-1}=T_{n-1}\notag\\
+&= (1-\pro(\ell_n))\pro(T_{n-1})+\pro(\ell_n)\left[1-\!\!\prod_{1\leq k\leq n-1}\!\!\!(1-\pro(\ell_k))\right]
+%&=\pro(((S_{n-1}\vee \bar S_{n-1})\wedge T_{n-1})  \vee (\ell_n\wedge S_{n-1}))\notag\\
+\label{e:2s}
+\end{align}
+This is straightforward to implement: 
+
+\begin{verbatim}
+p[0] = 0
+s[0] = 1
+for k = 1 to n:
+    p[k] = (1-e[k])*p[k-1] + e[k]*(1-s[k-1])
+    s[k] *= (1-e[k])
+return p[n]
+\end{verbatim}
+
+\subsection{One asymmetric $\ell\ell$ trigger, $n$ leptons}
+
+For example \textrm{mu18\_mu8noL1}. The two legs are differentiated by superscripts ($\ell_k^1$, $\ell_k^2$), 
+with the leg(1) having a higher \pt\ threshold than the leg (2). 
+Using induction again: 
+
+\begin{align}
+\pro(T_n) &= 
+\pro(T_{n-1}\vee (\ell_n^1 \wedge S_{n-1}^2) \vee (\ell_n^2 \wedge S_{n-1}^1))\notag\\
+&=(1-\pro(\ell_n^{\lambda_n}))\pro(T_{n-1})
++\pro(\ell_n^1)\pro(S_{n-1}^2) + \pro(\ell_n^2)\pro(S_{n-1}^1)\notag\\
+&\qquad- \pro(\ell_n^{\tau_n})\pro(S_{n-1}^1\wedge S_{n-1}^2)\notag\\
+&=(1-\pro(\ell_n^{\lambda_n}))\pro(T_{n-1})
++(\pro(\ell_n^{\lambda_n})-\pro(\ell_n^{\tau_n}))\pro(S_{n-1}^{\tau_n})\notag\\
+&\qquad + \pro(\ell_n^{\tau_n}) \pro(S_{n-1}^1\vee S_{n-1}^2)
+\label{e:2a}
+\end{align}
+where $\lambda_k$ (resp. $\tau_k$) is the index of the trigger leg that is the loosest 
+(resp. tightest) according to the established hierarchy at the lepton $k$'s \pt. 
+
+The expressions for $\pro(S_{n-1}^1)$ and $\pro(S_{n-1}^2)$ are given by~\ref{e:1}, 
+and $\pro(S_{n}^1\vee S_{n}^2)$ is simply the probability 
+for a logical ``or'' of two same-flavour two single-lepton triggers, hence is given by~\ref{e:x1l}. 
+
+\section{Combination of dilepton and single lepton triggers, only one lepton flavour}
+
+\subsection{One symmetric $\ell\ell$ + one or more same-flavour $\ell$ triggers, $n$ leptons}
+
+First addressing the case of one single-lepton trigger, then generalizing. 
+Superscripts 1 and 2 will respectively correspond to the single lepton trigger leg, and the dilepton trigger leg. 
+\begin{align}
+\pro(T_n) &= \pro(T_{n-1} \vee \ell_n^1 \vee (\ell_n^2 \wedge S_{n-1}^2))\notag\\
+&= \pro(T_{n-1})[1-\pro(\ell_n^1)] + \pro(\ell_n^1)
++ [\pro(\ell_n^2)-\pro(\ell_n^{\tau_n})][\pro(S_{n-1}^2) - \pro(T_{n-1}\wedge S_{n-1}^2)] \notag\\
+&= \pro(T_{n-1})[1-\pro(\ell_n^1)] + \pro(\ell_n^1)
++ [\pro(\ell_n^2)-\pro(\ell_n^{\tau_n})][\pro(T_{n-1}\vee S_{n-1}^2) - \pro(T_{n-1})]\notag\\
+&= \pro(T_{n-1})[1-\pro(\ell_n^1)] + \pro(\ell_n^1)
++ [\pro(\ell_n^2)-\pro(\ell_n^{\tau_n})][\pro(S_{n-1}^1\vee S_{n-1}^2) - \pro(T_{n-1})]\notag\\
+&= \pro(T_{n-1})[1-\pro(\ell_n^{\lambda_n})] + \pro(\ell_n^1)
++ \pro(S_{n-1}^1\vee S_{n-1}^2)[\pro(\ell_n^2)-\pro(\ell_n^{\tau_n})]\notag\\
+&= \pro(T_{n-1})[1-\pro(\ell_n^{\lambda_n})] + \pro(\ell_n^1)
++ \delta_2^{\lambda_n}\pro(S_{n-1}^1\vee S_{n-1}^2)[\pro(\ell_n^2)-\pro(\ell_n^1)]\notag
+\end{align}
+
+The expression for $\pro(S_{n-1}^1\vee S_{n-1}^2)$ is given by~\ref{e:1O1}. 
+
+For more than one single-lepton trigger, we denote $Z_n:=S_n^{1,1}\vee\ldots\vee S_n^{1,k}$ the union of the 
+$k$ single-lepton triggers. We also denote by the superscript $1$ the loosest single-lepton trigger for the lepton $n$.  
+Then: 
+\begin{align}
+\pro(T_n) 
+&= \pro(T_{n-1} \vee (\ell_n^{1,1}\vee\ldots\vee\ell_n^{1,k}) \vee (\ell_n^2 \wedge S_{n-1}^2))\notag\\
+&= \pro(T_{n-1} \vee \ell_n^{1} \vee (\ell_n^2 \wedge S_{n-1}^2))\notag\\
+&= \pro(T_{n-1})[1-\pro(\ell_n^{\lambda_n})] + \pro(\ell_n^{1})
++ \delta_2^{\lambda_n}
+\pro(Z_{n-1}\vee S_{n-1}^2)[\pro(\ell_n^2)-\pro(\ell_n^{1})]
+\label{e:2sO1}
+\end{align}
+
+
+\subsection{One asymmetric $\ell\ell$ + one or more same-flavour $\ell$ trigger, $n$ leptons}
+
+Uperscripts $2$ and $3$ indicate the two legs of the dilepton trigger, while $1$ indicates the loosest of the single-lepton trigger legs 
+for the lepton $n$ (i.e. equivalent to $\lambda_n^1$ in the previous section). However, $S_{n-1}^1$ still represents the event of triggering with any of the single-lepton triggers for one of the first $n-1$ leptons. 
+ 
+\begin{align}
+\pro(T_n) &= \pro(T_{n-1} \
+\vee (\ell_n^{1} \wedge S_{n-1}^{2}) \vee (\ell_n^{2} \wedge S_{n-1}^{1}) \vee \ell_n^3) \notag\\
+%
+&=[1-\pro(\ell_n^3)]\pro(T_{n-1}) + \pro(\ell_n^3)
++ [\pro(\ell_n^{1}) - \pro(\ell_n^{\tau_n^{1,3}})][\pro(S_{n-1}^{2}) - \pro(T_{n-1}\wedge S_{n-1}^2) ]\notag\\
+&\qquad + [\pro(\ell_n^{2}) - \pro(\ell_n^{\tau_n^{2,3}})][\pro(S_{n-1}^{1})-\pro(T_{n-1}\wedge S_{n-1}^1)]\notag\\
+&\qquad +  [\pro(\ell_n^{\tau_{n}}) - \pro(\ell_n^{\tau_{n}^{1,2}}) ]
+[\pro(S_{n-1}^{1}\wedge S_{n-1}^{2}) - \pro(T_{n-1}\wedge S_{n-1}^{1}\wedge S_{n-1}^{2})]\notag\\
+%
+&=[1-\pro(\ell_n^3)]\pro(T_{n-1}) + \pro(\ell_n^3)
+ + [\pro(\ell_n^{1}) - \pro(\ell_n^{\tau_n^{1,3}})][\pro(T_{n-1}\vee  S_{n-1}^2) - \pro(T_{n-1})]\notag\\
+&\qquad + [\pro(\ell_n^{2}) - \pro(\ell_n^{\tau_n^{2,3}})][\pro(T_{n-1}\vee  S_{n-1}^1) - \pro(T_{n-1})]\notag\\
+&\qquad +  [\pro(\ell_n^{\tau_{n}}) - \pro(\ell_n^{\tau_{n}^{1,2}}) ]
+[\pro(T_{n-1} \vee S_{n-1}^{1})+\pro(T_{n-1}\vee S_{n-1}^{2}) 
+- \pro(T_{n-1})-\pro(T_{n-1} \vee S_{n-1}^{1} \vee S_{n-1}^{2})]\notag\\
+%
+&= [1-\pro(\ell_n^{\lambda_n})]\pro(T_{n-1}) + \pro(\ell_n^3)
++ (1-\delta_3^{\lambda_n})[\pro(\ell_n^{\lambda_n}) - \pro(\ell_n^{m_n})]\pro(T_{n-1}\vee  S_{n-1}^{\tau_n^{1,2}})\notag\\
+&\qquad +  \delta_3^{\tau_n}[\pro(\ell_n^{m_n}) - \pro(\ell_n^3) ]
+\pro(T_{n-1} \vee (S_{n-1}^{1}\wedge S_{n-1}^{2}))\notag\\
+%
+&= [1-\pro(\ell_n^{\lambda_n})]\pro(T_{n-1}) + \pro(\ell_n^3)
++ (1-\delta_3^{\lambda_n})[\pro(\ell_n^{\lambda_n}) -\pro(\ell_n^{m_n})]\pro(S_{n-1}^3\vee  S_{n-1}^{\tau^{1,2}_n})\notag\\
+&\qquad + \delta_3^{\tau_n} [\pro(\ell_n^{m_n}) - \pro(\ell_n^3) ]
+\pro(S_{n-1}^1 \vee S_{n-1}^{2}\vee S_{n-1}^{3})
+\label{e:2aO1}
+\end{align}
+where $m_n$ stands for the ``medium'' leg (neither the tightest, nor the loosest) according to the hierarchy for the lepton $n$. 
+The different terms can be evaluated with~\ref{e:x1l}, and using induction. 
+
+
+%\subsection{One sym. $\ell\ell$ + one asym. $\ell\ell$, $n$ leptons}
+%
+%Notation: $D_n$ for the symmetric trigger, $A_n$ for the asymmetric trigger, $S_n^1$ for a single leg of the 
+%symmetric trigger, $S_n^2$ and $S_n^3$ similarly for the two different legs of the asymmetric trigger. 
+%\begin{align}
+%\pro(T_n)
+%&= \pro(T_{n-1}\vee (\ell_n^1\wedge S_{n-1}^1)
+%\vee  (\ell_n^2\wedge S_{n-1}^3) \vee (\ell_n^3\wedge S_{n-1}^2))\notag\\
+%%
+%&= \pro(T_{n-1}) 
+%+\pro(\ell_n^1)[\pro(S_{n-1}^1) - \pro(T_{n-1}\wedge S_{n-1}^1)]
+%+\pro(\ell_n^2)[\pro(S_{n-1}^3) - \pro(T_{n-1}\wedge S_{n-1}^3)]\notag\\
+%&\qquad +\pro(\ell_n^3)[\pro(S_{n-1}^2) - \pro(T_{n-1}\wedge S_{n-1}^2)]
+%+\pro(\ell_n^{\tau_n^{1,2}})[\pro(T_{n-1}\wedge S_{n-1}^1\wedge S_{n-1}^3) - \pro( S_{n-1}^1\wedge S_{n-1}^3)]\notag\\
+%&\qquad+\pro(\ell_n^{\tau_n^{1,3}})[\pro(T_{n-1}\wedge S_{n-1}^1\wedge S_{n-1}^2) - \pro( S_{n-1}^1\wedge S_{n-1}^2)]\notag\\
+%&\qquad+\pro(\ell_n^{\tau_n^{2,3}})[\pro(T_{n-1}\wedge S_{n-1}^2\wedge S_{n-1}^3) - \pro( S_{n-1}^2\wedge S_{n-1}^3)]\notag\\
+%&\qquad+ \pro(\ell_n^{\tau_n^{1,2,3}})[\pro(S_{n-1}^1 \wedge S_{n-1}^2\wedge S_{n-1}^3) - 
+%\pro(T_{n-1}\wedge S_{n-1}^1\wedge S_{n-1}^2\wedge S_{n-1}^3)]\notag\\
+%%
+%&= \pro(T_{n-1}) 
+%+\pro(\ell_n^1)[\pro(T_{n-1}\vee S_{n-1}^1) - \pro(T_{n-1})]
+%+\pro(\ell_n^2)[\pro(T_{n-1}\vee S_{n-1}^3) - \pro(T_{n-1})]\notag\\
+%&\qquad +\pro(\ell_n^3)[\pro(T_{n-1}\vee S_{n-1}^2) - \pro(T_{n-1})]
+%+ \pro(\ell_n^{\tau_n^{1,2}})X_{1,3}+\pro(\ell_n^{\tau_n^{1,3}})X_{1,2}
+%+\pro(\ell_n^{\tau_n^{2,3}})X_{2,3}\notag\\
+%&\qquad+ \pro(\ell_n^{\tau_n^{1,2,3}})[\pro(T_{n-1}\vee S_{n-1}^1\vee S_{n-1}^2\vee S_{n-1}^3)
+%- X_{1,2} - X_{1,3} - X_{2,3}\notag\\
+%&\qquad\qquad+ 2\pro(T_{n-1}) - \pro(T_{n-1}\vee S_{n-1}^1) - \pro(T_{n-1}\vee S_{n-1}^2) - \pro(T_{n-1}\vee S_{n-1}^3)]\notag\\
+%\text{with }&X_{j,k}:=
+%\pro(T_{n-1}\wedge S_{n-1}^j\wedge S_{n-1}^k) - \pro( S_{n-1}^j\wedge S_{n-1}^k)\notag\\
+%&\qquad=\pro(T_{n-1}\vee S_{n-1}^j\vee S_{n-1}^k) 
+%- \pro(T_{n-1}\vee S_{n-1}^j) - \pro(T_{n-1}\vee S_{n-1}^k) + \pro( T_{n-1})\notag
+%\end{align}
+%
+%Therefore: 
+%\begin{align}
+%\pro(T_n)
+%&= [1 - \pro(\ell_n^1) - \pro(\ell_n^2) - \pro(\ell_n^3) 
+%+ \pro(\ell_n^{\tau_n^{1,2}}) + \pro(\ell_n^{\tau_n^{1,3}}) + \pro(\ell_n^{\tau_n^{2,3}})
+%- \pro(\ell_n^{\tau_n^{1,2,3}})]\pro(T_{n-1})\notag\\
+%&\qquad+\pro(\ell_n^1)\pro(T_{n-1}\vee S_{n-1}^1)
+%+\pro(\ell_n^2)\pro(T_{n-1}\vee S_{n-1}^3)
+%+\pro(\ell_n^3)\pro(T_{n-1}\vee S_{n-1}^2)
+%\notag\\
+%&\qquad + [\pro(\ell_n^{\tau_n^{1,2}}) - \pro(\ell_n^{\tau_n^{1,2,3}})]\pro(T_{n-1}\vee S_{n-1}^1\vee S_{n-1}^3)
+%+ [\pro(\ell_n^{\tau_n^{1,3}}) - \pro(\ell_n^{\tau_n^{1,2,3}})]\pro(T_{n-1}\vee S_{n-1}^1\vee S_{n-1}^2)\notag\\
+%&\qquad+ [\pro(\ell_n^{\tau_n^{2,3}}) - \pro(\ell_n^{\tau_n^{1,2,3}})]\pro(T_{n-1}\vee S_{n-1}^2\vee S_{n-1}^3)
+%+ \pro(\ell_n^{\tau_n^{1,2,3}})\pro(T_{n-1}\vee S_{n-1}^1\vee S_{n-1}^2\vee S_{n-1}^3)\notag\\
+%&\qquad- [\pro(\ell_n^{\tau_n^{1,2}}) + \pro(\ell_n^{\tau_n^{1,3}}) - \pro(\ell_n^{\tau_n^{1,2,3}})]
+%\pro(T_{n-1}\vee S_{n-1}^1)\notag\\
+%&\qquad- [\pro(\ell_n^{\tau_n^{1,3}}) + \pro(\ell_n^{\tau_n^{2,3}}) - \pro(\ell_n^{\tau_n^{1,2,3}})]
+%\pro(T_{n-1}\vee S_{n-1}^2)\notag\\
+%&\qquad- [\pro(\ell_n^{\tau_n^{1,2}}) + \pro(\ell_n^{\tau_n^{2,3}}) - \pro(\ell_n^{\tau_n^{1,2,3}})]
+%\pro(T_{n-1}\vee S_{n-1}^3)\notag\\
+%%
+%&= [1 - \pro(\ell_n^{\lambda_n})]\pro(T_{n-1}) 
+%+ \pro(\ell_n^{\tau_n^{1,2,3}})\pro(S_{n-1}^1\vee S_{n-1}^2\vee S_{n-1}^3)\notag\\
+%&\qquad
+%-\delta_1^{\lambda_n}\pro(\ell_n^{m_n})\pro(A_{n-1}\vee S_{n-1}^1)
+%-\delta_2^{\lambda_n}\pro(\ell_n^{m_n})\pro(D_{n-1}\vee S_{n-1}^3)
+%-\delta_3^{\lambda_n}\pro(\ell_n^{m_n})\pro(D_{n-1}\vee S_{n-1}^2)
+%\notag\\
+%&\qquad + \delta_3^{\tau_n}[\pro(\ell_n^{m_n}) - \pro(\ell_n^3)]\pro(S_{n-1}^1\vee S_{n-1}^3)
+%+ \delta_2^{\tau_n}[\pro(\ell_n^{m_n}) - \pro(\ell_n^2)]\pro(S_{n-1}^1\vee S_{n-1}^2)\notag\\
+%&\qquad+  \delta_1^{\tau_n}[\pro(\ell_n^{m_n}) - \pro(\ell_n^1)]\pro(D_{n-1}\vee S_{n-1}^2\vee S_{n-1}^3)
+%%  + \delta_2^{\tau_n}[\pro(\ell_n^{m_n}) - \pro(\ell_n^2)]\pro(T_{n-1}\vee S_{n-1}^1\vee S_{n-1}^3)\notag\\
+%% &\qquad + \delta_3^{\tau_n}[\pro(\ell_n^{m_n}) - \pro(\ell_n^3)]\pro(T_{n-1}\vee S_{n-1}^1\vee S_{n-1}^2)
+%\end{align}
+
+\subsection{Two sym. $\ell\ell$ + several $\ell$ triggers, $n$ leptons}
+
+Superscripts $1$ and $2$ stand for the symmetric triggers, and superscript $3$ for the loosest single lepton trigger for the lepton $n$. $S_{n-1}^j$ represents the event of triggering the leg $j$ with any of the first $n-1$ leptons 
+(for $j=3$, it should be interpreted as ``any single lepton trigger''). 
+$m_n$ stands for the sub-tightest leg for the lepton $n$. 
+
+\begin{align*}
+\pro(T_n)&=
+\pro(T_{n-1}\vee (\ell_n^1\wedge S_{n-1}^1) \vee (\ell_n^2\wedge S_{n-1}^2) \vee \ell_n^3)
+\end{align*}
+
+This happens to be the same starting expression as eq.\ref{e:2sO1}, except that the reduction of the $(T_{n-1}\vee S_{n-1}\ldots)$ 
+terms in the last step is different; here the remaining terms resolve to:
+\begin{itemize}
+\item $T_{n-1}\vee S_{n-1}^{1,2} = D_{n-1}^{2,1}\vee Z_{n-1}\vee S_{n-1}^{1,2}$, then evaluated with eq.~\ref{e:2sO1}
+\item $T_{n-1}\vee S_{n-1}^{1}\vee S_{n-1}^{2} = Z_{n-1} \vee S_{n-1}^{1}\vee S_{n-1}^2$, with eq.~\ref{e:eOm}
+\end{itemize}
+where $D^{1,2}$ stand for the symmetric triggers and $Z$ for the combination of single-lepton triggers. 
+
+\subsection{One sym. $\ell\ell$ + one asym. $\ell\ell$ + several $\ell$ triggers, $n$ leptons}
+
+Superscript $1$ stands for the symmetric trigger, superscripts $2$ and $3$ for the two legs of the asymmetric trigger, 
+and superscript $4$ for the loosest single lepton trigger for the lepton $n$. $S_{n-1}^j$ represents the event of 
+triggering the leg $j$ with any of the first $n-1$ leptons (for $j=4$, it should be interpreted as ``any single lepton trigger''). 
+$m_n$ stands for the second tightest leg for the lepton $n$, $\nu_n$ for the third. 
+\begin{align}
+\pro(T_n)&=
+\pro(T_{n-1}\vee (\ell_n^1\wedge S_{n-1}^1) \vee (\ell_n^2\wedge S_{n-1}^3) 
+\vee (\ell_n^3\wedge S_{n-1}^2) \vee \ell_n^4)\notag\\
+%
+&=\pro(\ell_n^4) + \pro(T_{n-1})[1-\pro(\ell_n^4)]
++ [\pro(S_{n-1}^1) - \pro(T_{n-1}\wedge S_{n-1}^1)][\pro(\ell_n^1)-\pro(\ell_n^{\tau_n^{1,4}})])\notag\\
+&\qquad+[\pro(S_{n-1}^2) - \pro(T_{n-1}\wedge S_{n-1}^2)][\pro(\ell_n^3)-\pro(\ell_n^{\tau_n^{3,4}})]
++ [\pro(S_{n-1}^3) - \pro(T_{n-1}\wedge S_{n-1}^3)][\pro(\ell_n^2)-\pro(\ell_n^{\tau_n^{2,4}})])\notag\\
+&\qquad + [\pro(S_{n-1}^1\wedge S_{n-1}^2) - \pro(T_{n-1}\wedge S_{n-1}^1\wedge S_{n-1}^2)]
+[\pro(\ell_n^{\tau_n^{1,3,4}})-\pro(\ell_n^{\tau_n^{1,3}})])\notag\\
+&\qquad+ [\pro(S_{n-1}^1\wedge S_{n-1}^3) - \pro(T_{n-1}\wedge S_{n-1}^1\wedge S_{n-1}^3)]
+[\pro(\ell_n^{\tau_n^{1,2,4}})-\pro(\ell_n^{\tau_n^{1,2}})])\notag\\
+&\qquad+ [\pro(S_{n-1}^2\wedge S_{n-1}^3) - \pro(T_{n-1}\wedge S_{n-1}^2\wedge S_{n-1}^3)]
+[\pro(\ell_n^{\tau_n^{2,3,4}})-\pro(\ell_n^{\tau_n^{2,3}})])\notag\\
+&\qquad+ [\pro(S_{n-1}^1\wedge S_{n-1}^2\wedge S_{n-1}^3) 
+- \pro(T_{n-1}\wedge S_{n-1}^1\wedge S_{n-1}^2\wedge S_{n-1}^3)]
+[\pro(\ell_n^{\tau_n^{1,2,3}})-\pro(\ell_n^{\tau_n^{1,2,3,4}})]\notag\\
+%
+&= \pro(\ell_n^4) + \pro(T_{n-1})[1 - \pro(\ell_n^{\lambda_n})]
++ \delta_1^{\lambda_n}\pro(T_{n-1}\vee S_{n-1}^1)
+[\pro(\ell_n^1)-\pro(\ell_n^{\nu_n})])\notag\\
+&\qquad+\delta_3^{\lambda_n}\pro(T_{n-1}\vee S_{n-1}^2)
+[\pro(\ell_n^3)-\pro(\ell_n^{\nu_n})]
++ \delta_2^{\lambda_n}\pro(T_{n-1}\vee S_{n-1}^3)
+[\pro(\ell_n^2)-\pro(\ell_n^{\nu_n})])\notag\\
+&\qquad -\pro(T_{n-1}\vee S_{n-1}^1\vee S_{n-1}^2)
+[\pro(\ell_n^{m_n})-\pro(\ell_n^{\tau_n^{1,3}})]
+-\pro(T_{n-1}\vee S_{n-1}^1\vee S_{n-1}^3)[\pro(\ell_n^{m_n})-\pro(\ell_n^{\tau_n^{1,2}})])\notag\\
+&\qquad -\pro(T_{n-1}\vee S_{n-1}^2\vee S_{n-1}^3)[\pro(\ell_n^{m_n})-\pro(\ell_n^{\tau_n^{2,3}})]
++ \delta_4^{\tau_n}\pro(T_{n-1}\vee S_{n-1}^1\vee S_{n-1}^2\vee S_{n-1}^3)]
+[\pro(\ell_n^{m_n})-\pro(\ell_n^{4})]
+\label{e:2s2a}
+%
+\end{align}
+
+The different terms forming this expression can be evaluated with already-established formulas for simpler combinations of triggers, 
+by using that: 
+\begin{itemize}
+\item $T_{n-1}\vee S_{n-1}^1 = A_{n-1} \vee Z_{n-1} \vee S_{n-1}^1$, then evaluated with eq.~\ref{e:2aO1}
+\item $T_{n-1}\vee S_{n-1}^{2,3} = D_{n-1} \vee Z_{n-1} \vee S_{n-1}^{2,3}$, with eq.~\ref{e:2sO1}
+\item $T_{n-1}\vee S_{n-1}^1\vee S_{n-1}^{2,3} = Z_{n-1} \vee S_{n-1}^1\vee S_{n-1}^{2,3}$, with eq.~\ref{e:eOm}
+\item $T_{n-1}\vee S_{n-1}^2\vee S_{n-1}^3= D_{n-1}\vee Z_{n-1} \vee S_{n-1}^2\vee S_{n-1}^3$, with eq.~\ref{e:2sO1}
+\item $T_{n-1}\vee S_{n-1}^1\vee S_{n-1}^2\vee S_{n-1}^3= Z_{n-1} \vee S_{n-1}^1\vee S_{n-1}^2\vee S_{n-1}^3$, with eq.~\ref{e:eOm}
+\end{itemize}
+where $D$ stands for the symmetric trigger, $A$ for the asymmetric trigger, and $Z$ for the combination of single-lepton triggers. 
+
+
+
+
+
+\section{Combinations of dilepton and single lepton triggers, mixing lepton flavours}
+
+\subsection{One $e\mu$ + several single-$e/\mu$ triggers, $n_e+n_\mu$ leptons}
+\label{s:emO1}
+
+We denote by $Z_e$ and $Z_\mu$ the unions of the single-electron (resp. single-muon) triggers, 
+and by $S_e^2/S_\mu^2$ the legs of the $e\mu$ trigger. Then: 
+
+\begin{align}
+\pro(T_{n_e+n_\mu}) & = \pro((S_e^2\wedge S_\mu^2)\vee Z_e \vee Z_\mu)\notag\\
+&= \pro(Z_e\vee Z_\mu) + [\pro(S_e^2) - \pro(S_e^2\wedge Z_e)][\pro(S_\mu^2) - \pro(S_\mu^2\wedge Z_\mu)]\notag\\
+&= \pro(Z_e\vee Z_\mu)+ [\pro(Z_e\vee S_e^2) - \pro(Z_e)][\pro(Z_\mu\vee S_\mu^2) - \pro(Z_\mu) ]\notag\\
+&= 1 - [1-\pro(Z_e)][1-\pro(Z_\mu)]+ [\pro(Z_e\vee S_e^2) - \pro(Z_e)][\pro(Z_\mu\vee S_\mu^2) - \pro(Z_\mu) ]
+\end{align}
+
+\subsection{One $ee/\mu\mu$ + one $e\mu$ trigger, $n_e+n_\mu$ leptons}
+
+\begin{align}
+\pro(T_{n_e+n_\mu}) &= \pro(D_e \vee (S_e\wedge S_\mu))\notag\\
+&= \pro(D_e)(1-\pro(S_\mu)) + \pro(S_\mu)\pro(D_e\vee S_e) 
+\end{align}
+
+the different terms can be evaluated with~\ref{e:1},~\ref{e:2s} (\ref{e:2a}) or~\ref{e:2sO1}, 
+depending whether the $ee/\mu\mu$ trigger is symmetric or not. 
+
+\subsection{One $ee$ + one $\mu\mu$ + one $e\mu$ trigger, $n_e+n_\mu$ leptons}
+
+\begin{align}
+\pro(T_{n_e+n_\mu}) &= \pro(D_e \vee (S_e\wedge S_\mu) \vee D_\mu)\notag\\
+&= \pro(D_e)[1-\pro(D_\mu\vee S_\mu)] + \pro(D_\mu)[1-\pro(D_e\vee S_e)]\notag\\
+&\qquad\qquad +  \pro(D_e\vee S_e) \pro(D_\mu\vee S_\mu) 
+\end{align}
+
+the different terms can be evaluated with~\ref{e:1},~\ref{e:2s} or~\ref{e:2sO1}. 
+
+
+%\subsection{One $ee$ + one $\mu\mu$ + one $e\mu$ trigger + one or more same-flavour $e/\mu$ trigger, $n_e+n_\mu$ leptons}
+%
+%We denote $Z_e:=S_e^{1a}\vee\ldots\vee S_e^{1k}$ the union of the $k$ single-lepton triggers. Then: 
+%\begin{align}
+%&\quad\,\,\pro(D_e \vee (S_e^2\wedge S_\mu^2) \vee D_\mu \vee Z_e)\notag\\
+%&=
+%\pro(D_\mu)
+%+ [\pro(D_e)+\pro(Z_e) - \pro(D_e \wedge Z_e)][1 - \pro(D_\mu)]\notag\\
+%&\quad+ [\pro(S_e^2) - \pro(D_e \wedge S_e^2) - \pro(Z_e\wedge S_e^2) + \pro(D_e \wedge Z_e\wedge S_e^2) ]
+%[\pro(S_\mu^2) - \pro( D_\mu\wedge S_\mu^2)]\notag\\
+%&= \pro(D_\mu) + \pro(D_e \vee Z_e)[1 - \pro(D_\mu)]\notag\\
+%&\quad+ [\pro(D_e \vee Z_e\vee S_e^2) - \pro(D_e\vee Z_e)]
+%[\pro( D_\mu\vee S_\mu^2)-\pro(D_\mu)]
+%\end{align}
+
+\subsection{One $ee$ + one $\mu\mu$ + one $e\mu$ trigger + several $e/\mu$ triggers, $n_e+n_\mu$ leptons}
+
+We denote $Z_e:=S_e^{1a}\vee\ldots\vee S_e^{1{k_e}}$ the union of the $k_e$ single-electron triggers, 
+and similarly $Z_\mu$ for the $k_\mu$ single-muon triggers. Then: 
+\begin{align}
+&\quad\,\,\pro(D_e \vee (S_e^2\wedge S_\mu^2) \vee D_\mu \vee Z_e \vee Z_\mu)\notag\\
+&=
+\pro(D_\mu \vee Z_\mu)
++ [1-\pro(D_\mu \vee Z_\mu)]\pro(D_e \vee Z_e)\notag\\
+&\qquad+ [\pro(S_\mu^2) - \pro(S_\mu^2\wedge D_\mu) 
+- \pro(S_\mu^2\wedge Z_\mu)+ \pro(S_\mu^2\wedge D_\mu\wedge Z_\mu)]\notag\\
+&\qquad\qquad\times[\pro(S_e^2) - \pro(S_e^2\wedge D_e) - \pro(S_e^2\wedge Z_e) + \pro(S_e^2\wedge D_e\wedge Z_e)]\notag\\
+&=
+1
+- [1-\pro(D_\mu \vee Z_\mu)][1-\pro(D_e \vee Z_e)]\notag\\
+&\qquad+[\pro(D_\mu\vee Z_\mu\vee S_\mu^2) - \pro(D_\mu\vee Z_\mu)]
+[\pro(D_e\vee Z_e \vee S_e^2) - \pro(D_e\vee Z_e)]
+\end{align}
+
+the evaluation of the different terms (one dilepton trigger + several single-lepton triggers) can be performed 
+with~\ref{e:2sO1} and~\ref{e:2aO1}. 
+
+
+\subsection{Two $ee$ + two $\mu\mu$ + two $e\mu$ + several $e/\mu$ triggers, $n_e+n_\mu$ leptons}
+
+Notation: $E=$ all dielectron or single electron triggers, $M=$ all dimuon or single muon triggers, 
+$S_e^k$ (resp. $S_\mu^k$) the electron leg (resp. muon leg) of the $k$-th $e\mu$ trigger. 
+
+\begin{align}
+\pro(T_n)&=\pro(E\vee (S_e^1\wedge S_\mu^1)\vee(S_e^1\wedge S_\mu^1)\vee M)\notag\\
+&= 1 - (1-\pro(E))(1-\pro(M)) + [\pro(E\vee S_e^1)-\pro(E)][\pro(M\vee S_\mu^1)-\pro(M)]\notag\\
+&\qquad + [\pro(E\vee S_e^2)-\pro(E)][\pro(M\vee S_\mu^2)-\pro(M)]\notag\\
+&\qquad + [\pro(E\vee S_e^1)+\pro(E\vee S_e^2)-\pro(E)-\pro(E\vee S_e^1\vee S_e^2)]\times\notag\\
+&\qquad\qquad\times[-\pro(M\vee S_\mu^1)-\pro(M\vee S_\mu^2)+\pro(M)+\pro(M\vee S_\mu^1\vee S_\mu^2)]
+\end{align}
+
+\section{Trilepton triggers}
+
+\subsection{Fully symmetric $3e/3\mu$ trigger, $n$ leptons}
+By induction:
+\begin{align}
+\pro(T_n) &= \pro(T_{n-1} \vee (D_{n-1}\wedge \ell_n))\notag\\
+&= \pro(T_{n-1})(1-\pro(\ell_n)) + \pro(\ell_n)\pro(D_{n-1})
+\end{align}
+with $\pro(D_{n-1})$ given by~\ref{e:2s}. 
+
+\subsection{Mixed $2e\_\mu/2\mu\_e/e\_e\_\mu/\mu\_\mu\_e$ trigger, $n_e+n_\mu$ leptons}
+
+
+\begin{align}
+\pro(T_{n_e+n_\mu}) &= \pro(E_{n_e})\pro(M_{n_\mu})
+\end{align}
+with $\pro(M_{n_\mu})$ given by~\ref{e:1} and $\pro(E_{n_e})$ by either~\ref{e:2s} or~\ref{e:2a} 
+depending whether the two electrons legs are identical or not. 
+
+
+
+
+\subsection{Half-symmetric $e\_2e/\mu\_2\mu$ trigger, $n$ leptons}
+Superscript 1 indicates the leg of the symmetric part, and 2 the other leg; $D_{n-1}$ 
+and $A_{n-1}$ stand for pseudo dilepton triggers built respectively with legs $1+1$ and $1+2$. 
+By induction: 
+\begin{align}
+\pro(T_n) &= \pro(T_{n-1} \vee (A_{n-1}\wedge \ell_n^1) \vee (D_{n-1}\wedge \ell_n^2))\notag\\
+&= [1-\pro(\ell_n^{\lambda_n})]\pro(T_{n-1}) +  [\pro(\ell_n^1) - \pro(\ell_n^{\tau_n})]\pro(A_{n-1})\notag\\
+&\qquad+ [\pro(\ell_n^2) - \pro(\ell_n^{\tau_n})]\pro(D_{n-1})
++ \pro(\ell_n^{\tau_n})\pro(D_{n-1}\vee A_{n-1})
+\end{align}
+
+with $\pro(D_{n-1})$ given by~\ref{e:2s}, $\pro(A_{n-1})$ by~\ref{e:2a}, 
+ and $\pro(D_{n-1}\vee A_{n-1})$ by~\ref{e:2s2a}; 
+the latter's expression is however greatly simplified since there is no single-lepton trigger involved 
+and the two ``dilepton'' triggers have a leg in common. 
+Its expression is therefore: 
+\begin{align}
+\pro(D_{n}\vee A_{n}) = [1-\pro(\ell_n^{\lambda_n})]\pro(D_{n-1}\vee A_{n-1}) + [\pro(\ell_n^{\lambda_n})-\pro(\ell_n^1)]\pro(S_{n-1}^1)
++\pro(\ell_n^1)\pro(S_{n-1}^1\vee S_{n-1}^2) 
+\end{align}
+
+
+\subsection{Two complementary mixed $2e\_\mu/2\mu\_e/e\_e\_\mu/\mu\_\mu\_e$ triggers, $n_e+n_\mu$ leptons}
+
+Complementary = two electrons+1 muon for one trigger, and two muons+1 electron for the other. 
+
+\begin{align}
+\pro(T_{n_e+n_\mu}) &= \pro((S_e\wedge D_\mu) \vee (S_\mu\wedge D_e))\notag\\
+&= \pro(S_e)\pro(D_\mu) + \pro(S_\mu)\pro(D_e) 
++ [\pro(D_e\vee S_e) - \pro(D_e)-\pro(S_e)]
+[\pro(D_\mu)+\pro(S_\mu)-\pro(D_\mu\vee S_\mu)]
+\end{align}
+with $\pro(S)$ given by~\ref{e:1}, $\pro(D)$ by either~\ref{e:2s} or~\ref{e:2a} 
+and $\pro(D\vee S)$ by~\ref{e:2sO1} or~\ref{e:2aO1}.
+
+
+
+\end{document}
\ No newline at end of file
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample0.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample0.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..12075a2d260b44f6a1b3370b733f6e0ba8ac1804
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample0.cxx
@@ -0,0 +1,269 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// Based on CPToolTester.cxx (A. Kraznahorkay) 
+/// and ut_ath_checkTrigger_test.cxx (W. Buttinger)
+/// Contact: jmaurer@cern.ch
+/*
+ *    Simple example: single electron or single muon trigger, using different 
+ * triggers for 2015 and 2016-2018.
+ * 
+ */
+
+// ROOT include(s):
+#include <TFile.h>
+#include <TError.h>
+
+// Infrastructure include(s):
+#ifdef XAOD_STANDALONE
+    #include "xAODRootAccess/Init.h"
+    #include "xAODRootAccess/TEvent.h"
+    #include "xAODRootAccess/TStore.h"
+#else
+    #include "AthAnalysisBaseComps/AthAnalysisHelper.h"
+    #include "POOLRootAccess/TEvent.h"
+#endif
+#include "AsgMessaging/MessageCheck.h"
+// EDM include(s):
+#include "AsgTools/AnaToolHandle.h"
+#include "EgammaAnalysisInterfaces/IAsgElectronEfficiencyCorrectionTool.h"
+#include "MuonAnalysisInterfaces/IMuonTriggerScaleFactors.h"
+#include "TriggerAnalysisInterfaces/ITrigGlobalEfficiencyCorrectionTool.h"
+#include "xAODEventInfo/EventInfo.h"
+#include "xAODEgamma/ElectronContainer.h"
+#include "xAODMuon/MuonContainer.h"
+#include "PATCore/PATCoreEnums.h"
+#include "AthContainers/AuxElement.h"
+
+// stdlib include(s):
+#include <random>
+#include <vector>
+#include <array>
+using std::vector;
+using std::string;
+
+#define MSGSOURCE "Example 0"
+
+ANA_MSG_HEADER(Test)
+ANA_MSG_SOURCE(Test, MSGSOURCE)
+using namespace Test;
+int main(int argc, char* argv[])
+{
+    ANA_CHECK_SET_TYPE(bool)
+    const char* filename = nullptr;
+    bool debug = false, cmdline_error = false, toys = false;
+    for(int i=1;i<argc;++i)
+    {
+        if(string(argv[i]) == "--debug") debug = true;
+        else if(string(argv[i]) == "--toys") toys = true;
+        else if(!filename && *argv[i]!='-') filename = argv[i];
+        else cmdline_error = true;
+    }
+    if(!filename || cmdline_error)
+    {
+        Error(MSGSOURCE, "No file name received!");
+        Error(MSGSOURCE, "  Usage: %s [--debug] [--toys] [DxAOD file name]", argv[0]);
+        return 1;
+    }
+    #ifdef XAOD_STANDALONE
+        xAOD::Init(MSGSOURCE).ignore();
+        TFile* file = TFile::Open(filename, "READ");
+        if(!file)
+        {
+            Error(MSGSOURCE, "Unable to open file!");
+            return 2;
+        }
+        xAOD::TEvent event(xAOD::TEvent::kClassAccess);
+        xAOD::TStore store;
+        StatusCode::enableFailure();
+    #else
+       IAppMgrUI* app = POOL::Init();
+       POOL::TEvent event(POOL::TEvent::kClassAccess);
+       TString file(filename);
+    #endif
+    event.readFrom(file).ignore();
+    Long64_t entries = event.getEntries();
+    Info(MSGSOURCE, "Number of events in the file: %lli", entries);
+
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the electron CP tools");
+    /// For property 'ElectronEfficiencyTools':
+    ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronEffTools;
+    /// For property 'ElectronScaleFactorTools':
+    ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronSFTools; 
+    /// RAII on-the-fly creation of electron CP tools:
+    vector<asg::AnaToolHandle<IAsgElectronEfficiencyCorrectionTool>> factory;
+    const char* mapPath = "ElectronEfficiencyCorrection/2015_2017/"
+            "rel21.2/Consolidation_September2018_v1/map1.txt";
+    for(int j=0;j<2;++j) /// two instances: 0 -> MC efficiencies, 1 -> SFs
+    {
+        string name = "AsgElectronEfficiencyCorrectionTool/"
+                + ((j? "ElTrigEff_" : "ElTrigSF_")
+                + std::to_string(factory.size()/2));
+        auto t = factory.emplace(factory.end(), name);
+        t->setProperty("MapFilePath", mapPath).ignore();
+        t->setProperty("TriggerKey", string(j?"":"Eff_")
+            + "SINGLE_E_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_2018_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0").ignore();
+        t->setProperty("IdKey", "Tight").ignore();
+        t->setProperty("IsoKey", "FCTight").ignore();
+        t->setProperty("CorrelationModel", "TOTAL").ignore();
+        t->setProperty("ForceDataType", (int)PATCore::ParticleDataType::Full).ignore();
+        if(t->initialize() != StatusCode::SUCCESS)
+        {
+            Error(MSGSOURCE, "Unable to initialize the electron CP tool <%s>!",
+                    t->name().c_str());
+            return 3;
+        }
+        auto& handles = (j? electronSFTools : electronEffTools);
+        handles.push_back(t->getHandle());
+    }
+
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the muon CP tools");
+    /// For property 'MuonTools':
+    ToolHandleArray<CP::IMuonTriggerScaleFactors> muonTools;
+    asg::AnaToolHandle<CP::IMuonTriggerScaleFactors> muonTool("CP::MuonTriggerScaleFactors/MuonTrigEff");
+    muonTool.setProperty("MuonQuality", "Tight").ignore();
+    muonTool.setProperty("useRel207", false).ignore();
+    if(muonTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the muon CP tool!");
+        return 3;
+    }
+    muonTools.push_back(muonTool.getHandle());
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the global trigger SF tool");
+    asg::AnaToolHandle<ITrigGlobalEfficiencyCorrectionTool> myTool("TrigGlobalEfficiencyCorrectionTool/TrigGlobal");
+    myTool.setProperty("ElectronEfficiencyTools", electronEffTools).ignore();
+    myTool.setProperty("ElectronScaleFactorTools", electronSFTools).ignore();
+    myTool.setProperty("MuonTools", muonTools).ignore();
+    const char* triggers2015 = 
+        "mu20_iloose_L1MU15_OR_mu50"
+        "|| e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose";
+    myTool.setProperty("TriggerCombination2015", triggers2015).ignore();
+    const char* triggers2016to2018 = 
+        "mu26_ivarmedium_OR_mu50"
+        "|| e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0";
+    myTool.setProperty("TriggerCombination2016", triggers2016to2018).ignore();
+    myTool.setProperty("TriggerCombination2017", triggers2016to2018).ignore();
+    myTool.setProperty("TriggerCombination2018", triggers2016to2018).ignore();
+
+    if(debug) myTool.setProperty("OutputLevel", MSG::DEBUG).ignore();
+    if(toys) myTool.setProperty("NumberOfToys", 1000).ignore();
+    if(myTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the TrigGlob tool!");
+        return 3;
+    }
+    
+    /// Uniform random run number generation spanning the target dataset.
+    /// In real life, use the PileupReweightingTool instead!
+    const unsigned periodRuns[] = {
+        /// 2015 periods D-H, J
+        276073, 278727, 279932, 280423, 281130, 282625,
+        /// 2016 periods A-L
+        296939, 300345, 301912, 302737, 303638, 303943, 305291, 307124, 
+        305359, 309311, 310015,
+        /// 2017 periods B-K
+        325713, 329385, 330857, 332720, 334842, 336497, 336832, 338183,
+        /// 2018 periods B-Q
+        348885, 349534, 350310, 352274, 354107, 354826, 355261, 355331,
+        355529, 357050, 359191, 361635, 361738, 363664
+    };
+    std::uniform_int_distribution<unsigned> uniformPdf(0,
+            sizeof(periodRuns)/sizeof(*periodRuns) - 1);
+    std::default_random_engine randomEngine;
+    
+    SG::AuxElement::ConstAccessor<int> truthType("truthType");
+    SG::AuxElement::ConstAccessor<int> truthOrigin("truthOrigin");
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Starting the event loop");
+    unsigned errors = 0;
+    double nSuitableEvents = 0., sumW = 0.;
+    for(Long64_t entry = 0; entry < entries; ++entry)
+    {
+        event.getEntry(entry);
+        
+        /// Get a random run number, and decorate the event info
+        const xAOD::EventInfo* eventInfo = nullptr;
+        event.retrieve(eventInfo,"EventInfo").ignore();
+        unsigned runNumber = periodRuns[uniformPdf(randomEngine)];
+        eventInfo->auxdecor<unsigned>("RandomRunNumber") = runNumber;
+        vector<const xAOD::Electron*> myTriggeringElectrons;
+        const xAOD::ElectronContainer* electrons = nullptr;
+        event.retrieve(electrons,"Electrons").ignore();
+        for(auto electron : *electrons)
+        {
+            if(!electron->caloCluster()) continue;
+            float eta = fabs(electron->caloCluster()->etaBE(2));
+            float pt = electron->pt();
+            if(pt<10e3f || eta>=2.47) continue;
+            if(!truthType.isAvailable(*electron)) continue;
+            if(!truthOrigin.isAvailable(*electron)) continue;
+            int t = truthType(*electron), o = truthOrigin(*electron);
+            if(t!=2 || !(o==10 || (o>=12 && o<=22) || o==43)) continue;
+            /// lepton must be above trigger threshold:
+            if(pt < (runNumber>290000? 27e3f : 25e3f)) continue;
+
+            myTriggeringElectrons.push_back(electron);
+        }
+
+        vector<const xAOD::Muon*> myTriggeringMuons;
+        const xAOD::MuonContainer* muons = nullptr;
+        event.retrieve(muons,"Muons").ignore();
+        for(auto muon : *muons)
+        {
+            float pt = muon->pt();
+            if(pt<10e3f || fabs(muon->eta())>=2.5) continue;
+            auto mt = muon->muonType();
+            if(mt!=xAOD::Muon::Combined && mt!=xAOD::Muon::MuonStandAlone) continue;
+            auto& mtp = *(muon->primaryTrackParticle());
+            if(!truthType.isAvailable(mtp)) continue;
+            if(!truthOrigin.isAvailable(mtp)) continue;
+            int t = truthType(mtp), o = truthOrigin(mtp);
+            if(t!=6 || !(o==10 || (o>=12 && o<=22) || o==43)) continue;
+            /// lepton must be above trigger threshold:
+            if(pt < (runNumber>290000? 27.3e3f : 21e3f)) continue;
+
+            myTriggeringMuons.push_back(muon);
+        }
+
+        /// Events must contain at least one lepton above trigger threshold
+        if(myTriggeringElectrons.size()+myTriggeringMuons.size() < 1) continue;
+
+
+        /// Finally retrieve the global trigger scale factor
+        double sf = 1.;
+        auto cc = myTool->getEfficiencyScaleFactor(myTriggeringElectrons,
+            myTriggeringMuons, sf);
+        if(cc==CP::CorrectionCode::Ok)
+        {
+            nSuitableEvents += 1;
+            sumW += sf;
+        }
+        else
+        {
+            Warning(MSGSOURCE, "Scale factor evaluation failed");
+            ++errors;
+        }
+        if(errors>10)
+        {
+            Error(MSGSOURCE, "Too many errors reported!");
+            break;
+        }
+    }
+    Info(MSGSOURCE, "Average scale factor: %f (over %ld events)",
+            sumW / nSuitableEvents, long(nSuitableEvents));
+    #ifndef XAOD_STANDALONE
+		ANA_CHECK(app->finalize())
+    #endif
+    return errors? 4 : 0;
+}
+
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample1.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample1.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..4e97ff780f9b2e6ddb960972f26d321b0ef4bf89
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample1.cxx
@@ -0,0 +1,321 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// Based on CPToolTester.cxx (A. Kraznahorkay) 
+/// and ut_ath_checkTrigger_test.cxx (W. Buttinger)
+/// Contact: jmaurer@cern.ch
+/*
+ *    Another example: combination of single-lepton, dielectron and dimuon
+ * triggers, using different configurations for each year, and illustrating
+ * how to fill the property 'ListOfLegsPerTool'.
+ * 
+ */
+
+// ROOT include(s):
+#include <TFile.h>
+#include <TError.h>
+
+// Infrastructure include(s):
+#ifdef XAOD_STANDALONE
+    #include "xAODRootAccess/Init.h"
+    #include "xAODRootAccess/TEvent.h"
+    #include "xAODRootAccess/TStore.h"
+#else
+    #include "AthAnalysisBaseComps/AthAnalysisHelper.h"
+    #include "POOLRootAccess/TEvent.h"
+#endif
+
+#include "AsgMessaging/MessageCheck.h"
+// EDM include(s):
+#include "AsgTools/AnaToolHandle.h"
+#include "EgammaAnalysisInterfaces/IAsgElectronEfficiencyCorrectionTool.h"
+#include "MuonAnalysisInterfaces/IMuonTriggerScaleFactors.h"
+#include "TriggerAnalysisInterfaces/ITrigGlobalEfficiencyCorrectionTool.h"
+#include "xAODEventInfo/EventInfo.h"
+#include "xAODEgamma/ElectronContainer.h"
+#include "xAODMuon/MuonContainer.h"
+#include "PATCore/PATCoreEnums.h"
+#include "AthContainers/AuxElement.h"
+
+// stdlib include(s):
+#include <random>
+#include <vector>
+#include <array>
+using std::vector;
+using std::string;
+
+#define MSGSOURCE "Example 1"
+
+ANA_MSG_HEADER(Test)
+ANA_MSG_SOURCE(Test, MSGSOURCE)
+using namespace Test;
+int main(int argc, char* argv[])
+{
+    ANA_CHECK_SET_TYPE(bool)
+    const char* filename = nullptr;
+    bool debug = false, cmdline_error = false, toys = false;
+    for(int i=1;i<argc;++i)
+    {
+        if(string(argv[i]) == "--debug") debug = true;
+        else if(string(argv[i]) == "--toys") toys = true;
+        else if(!filename && *argv[i]!='-') filename = argv[i];
+        else cmdline_error = true;
+    }
+    if(!filename || cmdline_error)
+    {
+        Error(MSGSOURCE, "No file name received!");
+        Error(MSGSOURCE, "  Usage: %s [--debug] [--toys] [DxAOD file name]", argv[0]);
+        return 1;
+    }
+    #ifdef XAOD_STANDALONE
+        xAOD::Init(MSGSOURCE).ignore();
+        TFile* file = TFile::Open(filename, "READ");
+        if(!file)
+        {
+            Error(MSGSOURCE, "Unable to open file!");
+            return 2;
+        }
+        xAOD::TEvent event(xAOD::TEvent::kClassAccess);
+        xAOD::TStore store;
+        StatusCode::enableFailure();
+    #else
+       IAppMgrUI* app = POOL::Init();
+       POOL::TEvent event(POOL::TEvent::kClassAccess);
+       TString file(filename);
+    #endif
+    event.readFrom(file).ignore();
+    Long64_t entries = event.getEntries();
+    Info(MSGSOURCE, "Number of events in the file: %lli", entries);
+
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the electron CP tools");
+    /// For property 'ElectronEfficiencyTools':
+    ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronEffTools;
+    /// For property 'ElectronScaleFactorTools':
+    ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronSFTools; 
+        /// For property 'ListOfLegsPerTool':
+    std::map<string,string> legsPerTool;
+
+    /// RAII on-the-fly creation of electron CP tools:
+    vector<asg::AnaToolHandle<IAsgElectronEfficiencyCorrectionTool>> factory;
+    enum{ cLEGS, cKEY };
+    vector<std::array<string,2>> toolConfigs = {
+         /// {<list of trigger legs>, <key in map file>}
+         /// Single-electron trigger (same tool instance for 2015-2018):
+        {"e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose, e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0",
+            "SINGLE_E_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_2018_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0"}, 
+        /// Dielectron trigger (same tool instance for 2015-2018):
+        {"e12_lhloose_L1EM10VH, e17_lhvloose_nod0, e24_lhvloose_nod0_L1EM20VH", 
+            "DI_E_2015_e12_lhloose_L1EM10VH_2016_e17_lhvloose_nod0_2017_2018_e24_lhvloose_nod0_L1EM20VH"},
+        /// Complementary 2e17_lhvloose_nod0_L12EM15VHI trigger
+        {"e17_lhvloose_nod0_L1EM15VHI", 
+            "DI_E_2015_e12_lhloose_L1EM10VH_2016_e17_lhvloose_nod0_2017_2018_e17_lhvloose_nod0_L1EM15VHI"}
+     };
+
+    const char* mapPath = "ElectronEfficiencyCorrection/2015_2017/"
+            "rel21.2/Consolidation_September2018_v1/map2.txt";
+    for(auto& cfg : toolConfigs) /// one instance per trigger leg x working point
+    for(int j=0;j<2;++j) /// two instances: 0 -> MC efficiencies, 1 -> SFs
+    {
+        string name = "AsgElectronEfficiencyCorrectionTool/"
+                + ((j? "ElTrigEff_" : "ElTrigSF_")
+                + std::to_string(factory.size()/2));
+        auto t = factory.emplace(factory.end(), name);
+        t->setProperty("MapFilePath", mapPath).ignore();
+        t->setProperty("TriggerKey", string(j?"":"Eff_") + cfg[cKEY]).ignore();
+        t->setProperty("IdKey", "Tight").ignore();
+        t->setProperty("IsoKey", "FCTight").ignore();
+        t->setProperty("CorrelationModel", "TOTAL").ignore();
+        t->setProperty("ForceDataType", (int)PATCore::ParticleDataType::Full).ignore();
+        if(t->initialize() != StatusCode::SUCCESS)
+        {
+            Error(MSGSOURCE, "Unable to initialize the electron CP tool <%s>!",
+                    t->name().c_str());
+            return 3;
+        }
+        auto& handles = (j? electronSFTools : electronEffTools);
+        handles.push_back(t->getHandle());
+        /// Safer to retrieve the name from the final ToolHandle, it might be
+        /// prefixed (by the parent tool name) when the handle is copied
+        name = handles[handles.size()-1].name();
+        legsPerTool[name] = cfg[cLEGS];
+
+    }
+
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the muon CP tools");
+    /// For property 'MuonTools':
+    ToolHandleArray<CP::IMuonTriggerScaleFactors> muonTools;
+    asg::AnaToolHandle<CP::IMuonTriggerScaleFactors> muonTool("CP::MuonTriggerScaleFactors/MuonTrigEff");
+    muonTool.setProperty("MuonQuality", "Tight").ignore();
+    muonTool.setProperty("useRel207", false).ignore();
+    if(muonTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the muon CP tool!");
+        return 3;
+    }
+    muonTools.push_back(muonTool.getHandle());
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the global trigger SF tool");
+    asg::AnaToolHandle<ITrigGlobalEfficiencyCorrectionTool> myTool("TrigGlobalEfficiencyCorrectionTool/TrigGlobal");
+    myTool.setProperty("ElectronEfficiencyTools", electronEffTools).ignore();
+    myTool.setProperty("ElectronScaleFactorTools", electronSFTools).ignore();
+    myTool.setProperty("MuonTools", muonTools).ignore();
+    std::map<std::string, std::string> triggers;
+    triggers["2015"] = 
+        "mu20_iloose_L1MU15_OR_mu50"
+        "|| mu18_mu8noL1"
+        "|| e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose"
+        "|| 2e12_lhloose_L12EM10VH";
+    triggers["2016"] = 
+        "mu26_ivarmedium_OR_mu50"
+        "|| mu22_mu8noL1"
+        "|| e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0"
+        "|| 2e17_lhvloose_nod0";
+    std::string only2e24 = 
+        "mu26_ivarmedium_OR_mu50"
+        "|| mu22_mu8noL1"
+        "|| e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0"
+        "|| 2e24_lhvloose_nod0";
+    std::string nominal = only2e24 + "|| 2e17_lhvloose_nod0_L12EM15VHI";
+    triggers["324320-326695"] = nominal; /// 2017 before accidental prescale of L12EM15VHI
+    triggers["326834-328393"] = only2e24; /// 2017 during accidental prescale
+    triggers["329385-364292"] = nominal; /// 2017 after accidental prescale + 2018
+    myTool.setProperty("TriggerCombination", triggers).ignore();
+    myTool.setProperty("ListOfLegsPerTool", legsPerTool).ignore();
+
+    if(debug) myTool.setProperty("OutputLevel", MSG::DEBUG).ignore();
+    if(toys) myTool.setProperty("NumberOfToys", 1000).ignore();
+    if(myTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the TrigGlob tool!");
+        return 3;
+    }
+    
+    /// Uniform random run number generation spanning the target dataset.
+    /// In real life, use the PileupReweightingTool instead!
+    const unsigned periodRuns[] = {
+        /// 2015 periods D-H, J
+        276073, 278727, 279932, 280423, 281130, 282625,
+        /// 2016 periods A-L
+        296939, 300345, 301912, 302737, 303638, 303943, 305291, 307124, 
+        305359, 309311, 310015,
+        /// 2017 periods B-K
+        325713, 329385, 330857, 332720, 334842, 336497, 336832, 338183,
+        /// 2018 periods B-Q
+        348885, 349534, 350310, 352274, 354107, 354826, 355261, 355331,
+        355529, 357050, 359191, 361635, 361738, 363664
+    };
+    std::uniform_int_distribution<unsigned> uniformPdf(0,
+            sizeof(periodRuns)/sizeof(*periodRuns) - 1);
+    std::default_random_engine randomEngine;
+    
+    SG::AuxElement::ConstAccessor<int> truthType("truthType");
+    SG::AuxElement::ConstAccessor<int> truthOrigin("truthOrigin");
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Starting the event loop");
+    unsigned errors = 0;
+    double nSuitableEvents = 0., sumW = 0.;
+    for(Long64_t entry = 0; entry < entries; ++entry)
+    {
+        event.getEntry(entry);
+        
+        /// Get a random run number, and decorate the event info
+        const xAOD::EventInfo* eventInfo = nullptr;
+        event.retrieve(eventInfo,"EventInfo").ignore();
+        unsigned runNumber = periodRuns[uniformPdf(randomEngine)];
+        eventInfo->auxdecor<unsigned>("RandomRunNumber") = runNumber;
+
+        unsigned nTrig1L = 0, nTrig2mu = 0;
+        
+        vector<const xAOD::Electron*> myTriggeringElectrons;
+        const xAOD::ElectronContainer* electrons = nullptr;
+        event.retrieve(electrons,"Electrons").ignore();
+        for(auto electron : *electrons)
+        {
+            if(!electron->caloCluster()) continue;
+            float eta = fabs(electron->caloCluster()->etaBE(2));
+            float pt = electron->pt();
+            if(pt<10e3f || eta>=2.47) continue;
+            if(!truthType.isAvailable(*electron)) continue;
+            if(!truthOrigin.isAvailable(*electron)) continue;
+            int t = truthType(*electron), o = truthOrigin(*electron);
+            if(t!=2 || !(o==10 || (o>=12 && o<=22) || o==43)) continue;
+            /// lepton must be above softest trigger threshold:
+            if((runNumber>=326834 && runNumber<=328393 && pt<25e3f) /// 2017 during accidental prescale: 2e24
+                || (runNumber>290000 && pt<18e3f) /// 2016-2018: 2e17
+                || (pt<13e3f)) continue; /// 2015: 2e12
+            /// also count leptons above single-lepton trigger threshold
+            if(pt >= (runNumber>290000? 27e3f : 25e3f)) ++nTrig1L;
+
+            myTriggeringElectrons.push_back(electron);
+        }
+
+        vector<const xAOD::Muon*> myTriggeringMuons;
+        const xAOD::MuonContainer* muons = nullptr;
+        event.retrieve(muons,"Muons").ignore();
+        for(auto muon : *muons)
+        {
+            float pt = muon->pt();
+            if(pt<10e3f || fabs(muon->eta())>=2.5) continue;
+            auto mt = muon->muonType();
+            if(mt!=xAOD::Muon::Combined && mt!=xAOD::Muon::MuonStandAlone) continue;
+            auto& mtp = *(muon->primaryTrackParticle());
+            if(!truthType.isAvailable(mtp)) continue;
+            if(!truthOrigin.isAvailable(mtp)) continue;
+            int t = truthType(mtp), o = truthOrigin(mtp);
+            if(t!=6 || !(o==10 || (o>=12 && o<=22) || o==43)) continue;
+            /// lepton must be above softest trigger threshold (mu8noL1 here):
+            if(pt < 10e3f) continue;
+            /// also count leptons above single-lepton trigger threshold
+            if(pt >= (runNumber>290000? 27.3e3f : 21e3f)) ++nTrig1L;
+            /// and count muons suitable for the hard leg of the dimuon trigger
+            if(pt >= (runNumber>290000? 23e3f : 19e3f)) ++nTrig2mu;
+
+            myTriggeringMuons.push_back(muon);
+        }
+
+        /// Events must contain enough leptons to trigger
+        if(nTrig1L==0 /// single-lepton trigger
+            && myTriggeringElectrons.size()<2 /// dielectron
+            && (nTrig2mu==0 || myTriggeringMuons.size()<2)) /// dimuon
+        {
+            continue;
+        }
+
+
+        /// Finally retrieve the global trigger scale factor
+        double sf = 1.;
+        auto cc = myTool->getEfficiencyScaleFactor(myTriggeringElectrons,
+            myTriggeringMuons, sf);
+        if(cc==CP::CorrectionCode::Ok)
+        {
+            nSuitableEvents += 1;
+            sumW += sf;
+        }
+        else
+        {
+            Warning(MSGSOURCE, "Scale factor evaluation failed");
+            ++errors;
+        }
+        if(errors>10)
+        {
+            Error(MSGSOURCE, "Too many errors reported!");
+            break;
+        }
+    }
+    Info(MSGSOURCE, "Average scale factor: %f (over %ld events)",
+            sumW / nSuitableEvents, long(nSuitableEvents));
+    #ifndef XAOD_STANDALONE
+		ANA_CHECK(app->finalize())
+    #endif
+    return errors? 4 : 0;
+}
+
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample3a.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample3a.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..580331847dd660a8f0966402a49a30ae0c0c79f0
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample3a.cxx
@@ -0,0 +1,335 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// Based on CPToolTester.cxx (A. Kraznahorkay) 
+/// and ut_ath_checkTrigger_test.cxx (W. Buttinger)
+/// Contact: jmaurer@cern.ch
+/*
+ *    The set of examples 3a - 3e illustrates the use of lepton selection tags
+ * for various scenarios:
+ * 
+ * - Example 3a: trigger = 2e12_lhloose_L12EM10VH, selecting events containing
+ *               >=2 loose electrons, the leading-pT electron always satisfying
+ *               in addition tight PID+isolation requirements.
+ * 
+ * - Example 3b: trigger = e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose
+ *                         || 2e12_lhloose_L12EM10VH
+ *               selecting events with >=2 loose electrons where the leading-pT
+ *               electron also satisfies medium PID requirements.
+ *               Only the latter is allowed to fire the single-electron trigger.
+ * 
+ * - Example 3c: trigger = e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose
+ *                         || 2e12_lhloose_L12EM10VH
+ *               selecting events with >=2 loose electrons. Any electron also
+ *               satisfying medium PID requirements is allowed to fire the
+ *               single-electron trigger.
+ * 
+ * - Example 3d: trigger = 2e17_lhvloose_nod0 || e7_lhmedium_nod0_mu24
+ *       || e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0
+ *               same type of selection as example 3c but with 3 PID working
+ *               points, using two distinct decorations.
+ * 
+ * - Example 3e: same scenario as example 3d, but using an alternative
+ *               implementation that requires only one decoration.
+ * 
+ */
+/*
+ *    For this example (3a), one needs to set up two versions of the electron
+ * trigger tools: one configured for offline tight PID+isolation, to be used for
+ * the leading electron, and another configured for offline loose PID, to be
+ * used for subleading electrons.
+ * 
+ *    The configuration of the TrigGlobalEfficiencyCorrectionTool then involves 
+ * filling two additional properties, 'ListOfTagsPerTool' and
+ * 'LeptonTagDecorations'. We also fill a third one, 'ElectronLegsPerTag';
+ * it isn't required but still advised as the tool can then perform further
+ * consistency checks of its configuration.",  
+ * 
+ *    The leading electron will always be tagged with a 'Signal' decoration
+ * (decorated value set to 1), while subleading electrons are never tagged 
+ * (-> decorated value set to 0).
+ *    Since efficiencies provided by CP tools are inclusive, one should NEVER
+ *  tag the subleading leptons as 'Signal', even if they satisfy the tight PID
+ * + isolation requirements; doing so would bias the results.
+ *    See example 3c that deals with a more complicate situation requiring
+ * handling multiple PID levels for all leptons.
+ * 
+ */
+
+// ROOT include(s):
+#include <TFile.h>
+#include <TError.h>
+
+// Infrastructure include(s):
+#ifdef XAOD_STANDALONE
+    #include "xAODRootAccess/Init.h"
+    #include "xAODRootAccess/TEvent.h"
+    #include "xAODRootAccess/TStore.h"
+#else
+    #include "AthAnalysisBaseComps/AthAnalysisHelper.h"
+    #include "POOLRootAccess/TEvent.h"
+#endif
+
+#include "AsgMessaging/MessageCheck.h"
+// EDM include(s):
+#include "AsgTools/AnaToolHandle.h"
+#include "EgammaAnalysisInterfaces/IAsgElectronEfficiencyCorrectionTool.h"
+#include "MuonAnalysisInterfaces/IMuonTriggerScaleFactors.h"
+#include "TriggerAnalysisInterfaces/ITrigGlobalEfficiencyCorrectionTool.h"
+#include "xAODEventInfo/EventInfo.h"
+#include "xAODEgamma/ElectronContainer.h"
+#include "xAODMuon/MuonContainer.h"
+#include "PATCore/PATCoreEnums.h"
+#include "AthContainers/AuxElement.h"
+
+// stdlib include(s):
+#include <sstream>
+#include <random>
+#include <vector>
+#include <array>
+using std::vector;
+using std::string;
+
+/// Helper function to split comma-delimited strings
+namespace { vector<string> split_comma_delimited(const std::string&); }
+
+#define MSGSOURCE "Example 3a"
+
+ANA_MSG_HEADER(Test)
+ANA_MSG_SOURCE(Test, MSGSOURCE)
+using namespace Test;
+int main(int argc, char* argv[])
+{
+    ANA_CHECK_SET_TYPE(bool)
+    const char* filename = nullptr;
+    bool debug = false, cmdline_error = false, toys = false;
+    for(int i=1;i<argc;++i)
+    {
+        if(string(argv[i]) == "--debug") debug = true;
+        else if(string(argv[i]) == "--toys") toys = true;
+        else if(!filename && *argv[i]!='-') filename = argv[i];
+        else cmdline_error = true;
+    }
+    if(!filename || cmdline_error)
+    {
+        Error(MSGSOURCE, "No file name received!");
+        Error(MSGSOURCE, "  Usage: %s [--debug] [--toys] [DxAOD file name]", argv[0]);
+        return 1;
+    }
+    #ifdef XAOD_STANDALONE
+        xAOD::Init(MSGSOURCE).ignore();
+        TFile* file = TFile::Open(filename, "READ");
+        if(!file)
+        {
+            Error(MSGSOURCE, "Unable to open file!");
+            return 2;
+        }
+        xAOD::TEvent event(xAOD::TEvent::kClassAccess);
+        xAOD::TStore store;
+        StatusCode::enableFailure();
+    #else
+       IAppMgrUI* app = POOL::Init();
+       POOL::TEvent event(POOL::TEvent::kClassAccess);
+       TString file(filename);
+    #endif
+    event.readFrom(file).ignore();
+    Long64_t entries = event.getEntries();
+    Info(MSGSOURCE, "Number of events in the file: %lli", entries);
+
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the electron CP tools");
+    /// For property 'ElectronEfficiencyTools':
+    ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronEffTools;
+    /// For property 'ElectronScaleFactorTools':
+    ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronSFTools; 
+        /// For property 'ListOfLegsPerTool':
+    std::map<std::string,std::string> legsPerTool;
+    /// For property 'ListOfTagsPerTool':
+    std::map<std::string,std::string> tagsPerTool;
+    /// For property 'ElectronLegsPerTag':
+    std::map<std::string,std::string> legsPerTag;
+    /// To tag the leading electron as 'Signal'
+    SG::AuxElement::Decorator<char> dec_signal("Signal");
+
+    /// RAII on-the-fly creation of electron CP tools:
+    vector<asg::AnaToolHandle<IAsgElectronEfficiencyCorrectionTool>> factory;
+    enum{ cLEGS, cTAG, cKEY, cPID, cISO };
+    std::vector<std::array<std::string,5> > toolConfigs = {
+        /// <list of legs>, <list of tags>, <key in map file>, <PID WP>, <iso WP>
+        /// For leading electron:
+        {"e12_lhloose_L1EM10VH", "Signal", "2015_e12_lhloose_L1EM10VH", "Tight", "FixedCutTightTrackOnly"},
+        /// For subleading electron(s):
+        {"e12_lhloose_L1EM10VH", "*", "2015_e12_lhloose_L1EM10VH", "LooseBLayer", ""}
+    };
+
+    const char* mapPath = "ElectronEfficiencyCorrection/2015_2017/"
+            "rel21.2/Moriond_February2018_v2/map6.txt";
+    for(auto& cfg : toolConfigs) /// one instance per trigger leg x working point
+    for(int j=0;j<2;++j) /// two instances: 0 -> MC efficiencies, 1 -> SFs
+    {
+        string name = "AsgElectronEfficiencyCorrectionTool/"
+                + ((j? "ElTrigEff_" : "ElTrigSF_")
+                + std::to_string(factory.size()/2));
+        auto t = factory.emplace(factory.end(), name);
+        t->setProperty("MapFilePath", mapPath).ignore();
+        t->setProperty("TriggerKey", string(j?"":"Eff_") + cfg[cKEY]).ignore();
+        t->setProperty("IdKey", cfg[cPID]).ignore();
+        t->setProperty("IsoKey", cfg[cISO]).ignore();
+
+        t->setProperty("CorrelationModel", "TOTAL").ignore();
+        t->setProperty("ForceDataType", (int)PATCore::ParticleDataType::Full).ignore();
+        if(t->initialize() != StatusCode::SUCCESS)
+        {
+            Error(MSGSOURCE, "Unable to initialize the electron CP tool <%s>!",
+                    t->name().c_str());
+            return 3;
+        }
+        auto& handles = (j? electronSFTools : electronEffTools);
+        handles.push_back(t->getHandle());
+        /// Safer to retrieve the name from the final ToolHandle, it might be
+        /// prefixed (by the parent tool name) when the handle is copied    
+        name = handles[handles.size()-1].name();
+        legsPerTool[name] = cfg[cLEGS];
+        tagsPerTool[name] = cfg[cTAG];
+        if(!j)
+        {
+            for(auto& tag : ::split_comma_delimited(cfg[cTAG]))
+            {
+                if(legsPerTag[tag]=="") legsPerTag[tag] = cfg[cLEGS];
+                else legsPerTag[tag] += "," + cfg[cLEGS];
+            }
+        }
+
+    }
+
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the global trigger SF tool");
+    asg::AnaToolHandle<ITrigGlobalEfficiencyCorrectionTool> myTool("TrigGlobalEfficiencyCorrectionTool/TrigGlobal");
+    myTool.setProperty("ElectronEfficiencyTools", electronEffTools).ignore();
+    myTool.setProperty("ElectronScaleFactorTools", electronSFTools).ignore();
+    const char* triggers2015 = "2e12_lhloose_L12EM10VH";
+    myTool.setProperty("TriggerCombination2015", triggers2015).ignore();
+    myTool.setProperty("LeptonTagDecorations", "Signal").ignore();
+    myTool.setProperty("ListOfLegsPerTool", legsPerTool).ignore();
+    myTool.setProperty("ListOfTagsPerTool", tagsPerTool).ignore();
+    myTool.setProperty("ListOfLegsPerTag", legsPerTag).ignore();
+
+    if(debug) myTool.setProperty("OutputLevel", MSG::DEBUG).ignore();
+    if(toys) myTool.setProperty("NumberOfToys", 1000).ignore();
+    if(myTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the TrigGlob tool!");
+        return 3;
+    }
+    
+    /// Uniform random run number generation spanning the target dataset.
+    /// In real life, use the PileupReweightingTool instead!
+    const unsigned periodRuns[] = {
+        /// 2015 periods D-H, J
+        276073, 278727, 279932, 280423, 281130, 282625
+    };
+    std::uniform_int_distribution<unsigned> uniformPdf(0,
+            sizeof(periodRuns)/sizeof(*periodRuns) - 1);
+    std::default_random_engine randomEngine;
+    
+    SG::AuxElement::ConstAccessor<int> truthType("truthType");
+    SG::AuxElement::ConstAccessor<int> truthOrigin("truthOrigin");
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Starting the event loop");
+    unsigned errors = 0;
+    double nSuitableEvents = 0., sumW = 0.;
+    for(Long64_t entry = 0; entry < entries; ++entry)
+    {
+        event.getEntry(entry);
+        
+        /// Get a random run number, and decorate the event info
+        const xAOD::EventInfo* eventInfo = nullptr;
+        event.retrieve(eventInfo,"EventInfo").ignore();
+        unsigned runNumber = periodRuns[uniformPdf(randomEngine)];
+        eventInfo->auxdecor<unsigned>("RandomRunNumber") = runNumber;
+        vector<const xAOD::Electron*> myTriggeringElectrons;
+        const xAOD::ElectronContainer* electrons = nullptr;
+        event.retrieve(electrons,"Electrons").ignore();
+        for(auto electron : *electrons)
+        {
+            if(!electron->caloCluster()) continue;
+            float eta = fabs(electron->caloCluster()->etaBE(2));
+            float pt = electron->pt();
+            if(pt<10e3f || eta>=2.47) continue;
+            if(!truthType.isAvailable(*electron)) continue;
+            if(!truthOrigin.isAvailable(*electron)) continue;
+            int t = truthType(*electron), o = truthOrigin(*electron);
+            if(t!=2 || !(o==10 || (o>=12 && o<=22) || o==43)) continue;
+            /// electron must be above softest trigger threshold (e12 here)
+            if(pt < 13e3f) continue;
+
+            myTriggeringElectrons.push_back(electron);
+        }
+
+        vector<const xAOD::Muon*> myTriggeringMuons;
+
+        /// Events must contain at least two lepton above trigger threshold
+        if(myTriggeringElectrons.size() < 2) continue;
+
+        /// Let's pretend that the leading electron passes TightLH+isolation,
+        /// thus the event passes the selection; now we tag the electrons:
+        auto compareByPt = [](const xAOD::Electron*e1, const xAOD::Electron*e2)
+            { return e1->pt() < e2->pt(); };
+        auto leadingElectron = *std::max_element(myTriggeringElectrons.begin(),
+            myTriggeringElectrons.end(), compareByPt);
+        for(auto electron : myTriggeringElectrons)
+        {
+            /// Leading electron tagged as 'Signal' -> decorated value set to 1
+            /// Subleading electron(s) not tagged -> decorated value set to 0
+            dec_signal(*electron) = (electron==leadingElectron)? 1 : 0;
+        }
+
+
+        /// Finally retrieve the global trigger scale factor
+        double sf = 1.;
+        auto cc = myTool->getEfficiencyScaleFactor(myTriggeringElectrons,
+            myTriggeringMuons, sf);
+        if(cc==CP::CorrectionCode::Ok)
+        {
+            nSuitableEvents += 1;
+            sumW += sf;
+        }
+        else
+        {
+            Warning(MSGSOURCE, "Scale factor evaluation failed");
+            ++errors;
+        }
+        if(errors>10)
+        {
+            Error(MSGSOURCE, "Too many errors reported!");
+            break;
+        }
+    }
+    Info(MSGSOURCE, "Average scale factor: %f (over %ld events)",
+            sumW / nSuitableEvents, long(nSuitableEvents));
+    #ifndef XAOD_STANDALONE
+		ANA_CHECK(app->finalize())
+    #endif
+    return errors? 4 : 0;
+}
+
+/// Split comma-delimited string
+namespace
+{
+    inline vector<string> split_comma_delimited(const string& s)
+    {
+        std::stringstream ss(s);
+        std::vector<std::string> tokens;
+        std::string token;
+        while(std::getline(ss, token, ','))
+        {
+            if(token.length()) tokens.push_back(token);
+        }
+        return tokens;
+    }
+}
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample3b.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample3b.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..4478a185cbc261f16fe09992aa04d48e39aa8971
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample3b.cxx
@@ -0,0 +1,340 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// Based on CPToolTester.cxx (A. Kraznahorkay) 
+/// and ut_ath_checkTrigger_test.cxx (W. Buttinger)
+/// Contact: jmaurer@cern.ch
+/*
+ *    The set of examples 3a - 3e illustrates the use of lepton selection tags
+ * for various scenarios:
+ * 
+ * - Example 3a: trigger = 2e12_lhloose_L12EM10VH, selecting events containing
+ *               >=2 loose electrons, the leading-pT electron always satisfying
+ *               in addition tight PID+isolation requirements.
+ * 
+ * - Example 3b: trigger = e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose
+ *                         || 2e12_lhloose_L12EM10VH
+ *               selecting events with >=2 loose electrons where the leading-pT
+ *               electron also satisfies medium PID requirements.
+ *               Only the latter is allowed to fire the single-electron trigger.
+ * 
+ * - Example 3c: trigger = e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose
+ *                         || 2e12_lhloose_L12EM10VH
+ *               selecting events with >=2 loose electrons. Any electron also
+ *               satisfying medium PID requirements is allowed to fire the
+ *               single-electron trigger.
+ * 
+ * - Example 3d: trigger = 2e17_lhvloose_nod0 || e7_lhmedium_nod0_mu24
+ *       || e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0
+ *               same type of selection as example 3c but with 3 PID working
+ *               points, using two distinct decorations.
+ * 
+ * - Example 3e: same scenario as example 3d, but using an alternative
+ *               implementation that requires only one decoration.
+ * 
+ */
+/*
+ *    For this example (3b), one needs to set up three versions of the electron
+ * trigger tools: two configured for offline tight PID+isolation, to be used for
+ * the leading electron (one for the trigger leg e24_xxx, another for the leg
+ * e12_xxx), and another configured for offline loose PID (for the leg e12_xxx)
+ * to be used for subleading electrons.
+ * 
+ *    The configuration of the TrigGlobalEfficiencyCorrectionTool is very similar
+ * to the example 3a (e.g. the decoration of electrons is identical), please
+ * refer to that example for more details.
+ *    Here, in addition, one needs to let the tool know, via the property
+ * 'ListOfLegsPerTag', that only 'Signal'-tagged electrons are allowed to fire
+ * the e24_xxx leg.
+ * 
+ */
+
+// ROOT include(s):
+#include <TFile.h>
+#include <TError.h>
+
+// Infrastructure include(s):
+#ifdef XAOD_STANDALONE
+    #include "xAODRootAccess/Init.h"
+    #include "xAODRootAccess/TEvent.h"
+    #include "xAODRootAccess/TStore.h"
+#else
+    #include "AthAnalysisBaseComps/AthAnalysisHelper.h"
+    #include "POOLRootAccess/TEvent.h"
+#endif
+
+#include "AsgMessaging/MessageCheck.h"
+// EDM include(s):
+#include "AsgTools/AnaToolHandle.h"
+#include "EgammaAnalysisInterfaces/IAsgElectronEfficiencyCorrectionTool.h"
+#include "MuonAnalysisInterfaces/IMuonTriggerScaleFactors.h"
+#include "TriggerAnalysisInterfaces/ITrigGlobalEfficiencyCorrectionTool.h"
+#include "xAODEventInfo/EventInfo.h"
+#include "xAODEgamma/ElectronContainer.h"
+#include "xAODMuon/MuonContainer.h"
+#include "PATCore/PATCoreEnums.h"
+#include "AthContainers/AuxElement.h"
+
+// stdlib include(s):
+#include <sstream>
+#include <random>
+#include <vector>
+#include <array>
+using std::vector;
+using std::string;
+
+/// Helper function to split comma-delimited strings
+namespace { vector<string> split_comma_delimited(const std::string&); }
+
+#define MSGSOURCE "Example 3b"
+
+ANA_MSG_HEADER(Test)
+ANA_MSG_SOURCE(Test, MSGSOURCE)
+using namespace Test;
+int main(int argc, char* argv[])
+{
+    ANA_CHECK_SET_TYPE(bool)
+    const char* filename = nullptr;
+    bool debug = false, cmdline_error = false, toys = false;
+    for(int i=1;i<argc;++i)
+    {
+        if(string(argv[i]) == "--debug") debug = true;
+        else if(string(argv[i]) == "--toys") toys = true;
+        else if(!filename && *argv[i]!='-') filename = argv[i];
+        else cmdline_error = true;
+    }
+    if(!filename || cmdline_error)
+    {
+        Error(MSGSOURCE, "No file name received!");
+        Error(MSGSOURCE, "  Usage: %s [--debug] [--toys] [DxAOD file name]", argv[0]);
+        return 1;
+    }
+    #ifdef XAOD_STANDALONE
+        xAOD::Init(MSGSOURCE).ignore();
+        TFile* file = TFile::Open(filename, "READ");
+        if(!file)
+        {
+            Error(MSGSOURCE, "Unable to open file!");
+            return 2;
+        }
+        xAOD::TEvent event(xAOD::TEvent::kClassAccess);
+        xAOD::TStore store;
+        StatusCode::enableFailure();
+    #else
+       IAppMgrUI* app = POOL::Init();
+       POOL::TEvent event(POOL::TEvent::kClassAccess);
+       TString file(filename);
+    #endif
+    event.readFrom(file).ignore();
+    Long64_t entries = event.getEntries();
+    Info(MSGSOURCE, "Number of events in the file: %lli", entries);
+
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the electron CP tools");
+    /// For property 'ElectronEfficiencyTools':
+    ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronEffTools;
+    /// For property 'ElectronScaleFactorTools':
+    ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronSFTools; 
+        /// For property 'ListOfLegsPerTool':
+    std::map<std::string,std::string> legsPerTool;
+    /// For property 'ListOfTagsPerTool':
+    std::map<std::string,std::string> tagsPerTool;
+    /// For property 'ElectronLegsPerTag':
+    std::map<std::string,std::string> legsPerTag;
+    /// To tag electron(s) as 'Signal'
+    SG::AuxElement::Decorator<char> dec_signal("Signal");
+    /// To emulate PID selection (90% loose-to-medium efficiency)
+    std::bernoulli_distribution bernoulliPdf(0.9);
+
+    /// RAII on-the-fly creation of electron CP tools:
+    vector<asg::AnaToolHandle<IAsgElectronEfficiencyCorrectionTool>> factory;
+    enum{ cLEGS, cTAG, cKEY, cPID, cISO };
+    std::vector<std::array<std::string,5> > toolConfigs = {
+        /// <list of legs>, <list of tags>, <key in map file>, <PID WP>, <iso WP>
+        /// Single electron trigger, only for the leading electron ("Signal")
+        {"e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose", "Signal", "2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose", "Tight", "FixedCutTightTrackOnly"}, 
+        /// Dielectron trigger, for the leading electron ("Signal")
+        {"e12_lhloose_L1EM10VH", "Signal", "2015_e12_lhloose_L1EM10VH", "Tight", "FixedCutTightTrackOnly"}, 
+        /// Dielectron trigger, for subleading electron(s) (not tagged => "*")
+        {"e12_lhloose_L1EM10VH", "*", "2015_e12_lhloose_L1EM10VH", "LooseBLayer", ""}
+     };
+
+    const char* mapPath = "ElectronEfficiencyCorrection/2015_2017/"
+            "rel21.2/Moriond_February2018_v2/map6.txt";
+    for(auto& cfg : toolConfigs) /// one instance per trigger leg x working point
+    for(int j=0;j<2;++j) /// two instances: 0 -> MC efficiencies, 1 -> SFs
+    {
+        string name = "AsgElectronEfficiencyCorrectionTool/"
+                + ((j? "ElTrigEff_" : "ElTrigSF_")
+                + std::to_string(factory.size()/2));
+        auto t = factory.emplace(factory.end(), name);
+        t->setProperty("MapFilePath", mapPath).ignore();
+        t->setProperty("TriggerKey", string(j?"":"Eff_") + cfg[cKEY]).ignore();
+        t->setProperty("IdKey", cfg[cPID]).ignore();
+        t->setProperty("IsoKey", cfg[cISO]).ignore();
+
+        t->setProperty("CorrelationModel", "TOTAL").ignore();
+        t->setProperty("ForceDataType", (int)PATCore::ParticleDataType::Full).ignore();
+        if(t->initialize() != StatusCode::SUCCESS)
+        {
+            Error(MSGSOURCE, "Unable to initialize the electron CP tool <%s>!",
+                    t->name().c_str());
+            return 3;
+        }
+        auto& handles = (j? electronSFTools : electronEffTools);
+        handles.push_back(t->getHandle());
+        /// Safer to retrieve the name from the final ToolHandle, it might be
+        /// prefixed (by the parent tool name) when the handle is copied    
+        name = handles[handles.size()-1].name();
+        legsPerTool[name] = cfg[cLEGS];
+        tagsPerTool[name] = cfg[cTAG];
+        if(!j)
+        {
+            for(auto& tag : ::split_comma_delimited(cfg[cTAG]))
+            {
+                if(legsPerTag[tag]=="") legsPerTag[tag] = cfg[cLEGS];
+                else legsPerTag[tag] += "," + cfg[cLEGS];
+            }
+        }
+
+    }
+
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the global trigger SF tool");
+    asg::AnaToolHandle<ITrigGlobalEfficiencyCorrectionTool> myTool("TrigGlobalEfficiencyCorrectionTool/TrigGlobal");
+    myTool.setProperty("ElectronEfficiencyTools", electronEffTools).ignore();
+    myTool.setProperty("ElectronScaleFactorTools", electronSFTools).ignore();
+    const char* triggers2015 = 
+        "e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose"
+        "|| 2e12_lhloose_L12EM10VH";
+    myTool.setProperty("TriggerCombination2015", triggers2015).ignore();
+    myTool.setProperty("LeptonTagDecorations", "Signal").ignore();
+    myTool.setProperty("ListOfLegsPerTool", legsPerTool).ignore();
+    myTool.setProperty("ListOfTagsPerTool", tagsPerTool).ignore();
+    myTool.setProperty("ListOfLegsPerTag", legsPerTag).ignore();
+
+    if(debug) myTool.setProperty("OutputLevel", MSG::DEBUG).ignore();
+    if(toys) myTool.setProperty("NumberOfToys", 1000).ignore();
+    if(myTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the TrigGlob tool!");
+        return 3;
+    }
+    
+    /// Uniform random run number generation spanning the target dataset.
+    /// In real life, use the PileupReweightingTool instead!
+    const unsigned periodRuns[] = {
+        /// 2015 periods D-H, J
+        276073, 278727, 279932, 280423, 281130, 282625
+    };
+    std::uniform_int_distribution<unsigned> uniformPdf(0,
+            sizeof(periodRuns)/sizeof(*periodRuns) - 1);
+    std::default_random_engine randomEngine;
+    
+    SG::AuxElement::ConstAccessor<int> truthType("truthType");
+    SG::AuxElement::ConstAccessor<int> truthOrigin("truthOrigin");
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Starting the event loop");
+    unsigned errors = 0;
+    double nSuitableEvents = 0., sumW = 0.;
+    for(Long64_t entry = 0; entry < entries; ++entry)
+    {
+        event.getEntry(entry);
+        
+        /// Get a random run number, and decorate the event info
+        const xAOD::EventInfo* eventInfo = nullptr;
+        event.retrieve(eventInfo,"EventInfo").ignore();
+        unsigned runNumber = periodRuns[uniformPdf(randomEngine)];
+        eventInfo->auxdecor<unsigned>("RandomRunNumber") = runNumber;
+        vector<const xAOD::Electron*> myTriggeringElectrons;
+        const xAOD::ElectronContainer* electrons = nullptr;
+        event.retrieve(electrons,"Electrons").ignore();
+        for(auto electron : *electrons)
+        {
+            if(!electron->caloCluster()) continue;
+            float eta = fabs(electron->caloCluster()->etaBE(2));
+            float pt = electron->pt();
+            if(pt<10e3f || eta>=2.47) continue;
+            if(!truthType.isAvailable(*electron)) continue;
+            if(!truthOrigin.isAvailable(*electron)) continue;
+            int t = truthType(*electron), o = truthOrigin(*electron);
+            if(t!=2 || !(o==10 || (o>=12 && o<=22) || o==43)) continue;
+            /// electron must be above softest trigger threshold (e12 here)
+            if(pt < 13e3f) continue;
+
+            myTriggeringElectrons.push_back(electron);
+        }
+
+        vector<const xAOD::Muon*> myTriggeringMuons;
+
+        if(myTriggeringElectrons.size() < 1) continue;
+        
+        /// Let's pretend that the leading electron passes TightLH+isolation,
+        /// thus the event passes the selection; now we tag the electrons:
+        auto compareByPt = [](const xAOD::Electron*e1, const xAOD::Electron*e2)
+            { return e1->pt() < e2->pt(); };
+        auto leadingElectron = *std::max_element(myTriggeringElectrons.begin(),
+            myTriggeringElectrons.end(), compareByPt);
+        for(auto electron : myTriggeringElectrons)
+        {
+            /// Leading electron tagged as 'Signal' -> decorated value set to 1
+            /// Subleading electron(s) not tagged -> decorated value set to 0
+            dec_signal(*electron) = (electron==leadingElectron)? 1 : 0;
+        }
+        
+        /// Events must contain enough leptons to trigger
+        if(leadingElectron->pt() < 25e3f /// single-electron trigger
+            && myTriggeringElectrons.size() < 2) /// dielectron
+        {
+            continue;
+        }
+
+
+        /// Finally retrieve the global trigger scale factor
+        double sf = 1.;
+        auto cc = myTool->getEfficiencyScaleFactor(myTriggeringElectrons,
+            myTriggeringMuons, sf);
+        if(cc==CP::CorrectionCode::Ok)
+        {
+            nSuitableEvents += 1;
+            sumW += sf;
+        }
+        else
+        {
+            Warning(MSGSOURCE, "Scale factor evaluation failed");
+            ++errors;
+        }
+        if(errors>10)
+        {
+            Error(MSGSOURCE, "Too many errors reported!");
+            break;
+        }
+    }
+    Info(MSGSOURCE, "Average scale factor: %f (over %ld events)",
+            sumW / nSuitableEvents, long(nSuitableEvents));
+    #ifndef XAOD_STANDALONE
+		ANA_CHECK(app->finalize())
+    #endif
+    return errors? 4 : 0;
+}
+
+/// Split comma-delimited string
+namespace
+{
+    inline vector<string> split_comma_delimited(const string& s)
+    {
+        std::stringstream ss(s);
+        std::vector<std::string> tokens;
+        std::string token;
+        while(std::getline(ss, token, ','))
+        {
+            if(token.length()) tokens.push_back(token);
+        }
+        return tokens;
+    }
+}
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample3c.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample3c.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..0273ed4671a13856705d7a7e0b676ec837fe93a2
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample3c.cxx
@@ -0,0 +1,335 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// Based on CPToolTester.cxx (A. Kraznahorkay) 
+/// and ut_ath_checkTrigger_test.cxx (W. Buttinger)
+/// Contact: jmaurer@cern.ch
+/*
+ *    The set of examples 3a - 3e illustrates the use of lepton selection tags
+ * for various scenarios:
+ * 
+ * - Example 3a: trigger = 2e12_lhloose_L12EM10VH, selecting events containing
+ *               >=2 loose electrons, the leading-pT electron always satisfying
+ *               in addition tight PID+isolation requirements.
+ * 
+ * - Example 3b: trigger = e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose
+ *                         || 2e12_lhloose_L12EM10VH
+ *               selecting events with >=2 loose electrons where the leading-pT
+ *               electron also satisfies medium PID requirements.
+ *               Only the latter is allowed to fire the single-electron trigger.
+ * 
+ * - Example 3c: trigger = e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose
+ *                         || 2e12_lhloose_L12EM10VH
+ *               selecting events with >=2 loose electrons. Any electron also
+ *               satisfying medium PID requirements is allowed to fire the
+ *               single-electron trigger.
+ * 
+ * - Example 3d: trigger = 2e17_lhvloose_nod0 || e7_lhmedium_nod0_mu24
+ *       || e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0
+ *               same type of selection as example 3c but with 3 PID working
+ *               points, using two distinct decorations.
+ * 
+ * - Example 3e: same scenario as example 3d, but using an alternative
+ *               implementation that requires only one decoration.
+ * 
+ */
+/*
+ *    For this example (3c), any electron might be tagged as 'Signal' if it
+ * passes MediumLH selection (here emulated with a random number). Electron tools
+ * providing the efficiency/SF for the e24_xxx leg are configured with the
+ * MediumLH working point. For the tools providing efficiency/SF for the e12_xxx
+ * leg however, one SHOULD NOT set up one version for 'Medium'-tagged electrons
+ * and another version for untagged electrons. This is because for the latter
+ * one would need to configure the tools for a LooseLH-but-not-MediumLH working
+ * point, which is not provided by egamma. Instead, we use a single version,
+ * configured with the LooseLH working point, and instruct the TrigGlob tool to
+ * use that version for both 'Medium'-tagged and untagged electrons. In this way,
+ * no bias is introduced. ",
+ *    The configuration of the TrigGlobalEfficiencyCorrectionTool is otherwise
+ * essentially the same as for the example 3b, please refer to that example for
+ * more details.
+ *     
+ */
+
+// ROOT include(s):
+#include <TFile.h>
+#include <TError.h>
+
+// Infrastructure include(s):
+#ifdef XAOD_STANDALONE
+    #include "xAODRootAccess/Init.h"
+    #include "xAODRootAccess/TEvent.h"
+    #include "xAODRootAccess/TStore.h"
+#else
+    #include "AthAnalysisBaseComps/AthAnalysisHelper.h"
+    #include "POOLRootAccess/TEvent.h"
+#endif
+
+#include "AsgMessaging/MessageCheck.h"
+// EDM include(s):
+#include "AsgTools/AnaToolHandle.h"
+#include "EgammaAnalysisInterfaces/IAsgElectronEfficiencyCorrectionTool.h"
+#include "MuonAnalysisInterfaces/IMuonTriggerScaleFactors.h"
+#include "TriggerAnalysisInterfaces/ITrigGlobalEfficiencyCorrectionTool.h"
+#include "xAODEventInfo/EventInfo.h"
+#include "xAODEgamma/ElectronContainer.h"
+#include "xAODMuon/MuonContainer.h"
+#include "PATCore/PATCoreEnums.h"
+#include "AthContainers/AuxElement.h"
+
+// stdlib include(s):
+#include <sstream>
+#include <random>
+#include <vector>
+#include <array>
+using std::vector;
+using std::string;
+
+/// Helper function to split comma-delimited strings
+namespace { vector<string> split_comma_delimited(const std::string&); }
+
+#define MSGSOURCE "Example 3c"
+
+ANA_MSG_HEADER(Test)
+ANA_MSG_SOURCE(Test, MSGSOURCE)
+using namespace Test;
+int main(int argc, char* argv[])
+{
+    ANA_CHECK_SET_TYPE(bool)
+    const char* filename = nullptr;
+    bool debug = false, cmdline_error = false, toys = false;
+    for(int i=1;i<argc;++i)
+    {
+        if(string(argv[i]) == "--debug") debug = true;
+        else if(string(argv[i]) == "--toys") toys = true;
+        else if(!filename && *argv[i]!='-') filename = argv[i];
+        else cmdline_error = true;
+    }
+    if(!filename || cmdline_error)
+    {
+        Error(MSGSOURCE, "No file name received!");
+        Error(MSGSOURCE, "  Usage: %s [--debug] [--toys] [DxAOD file name]", argv[0]);
+        return 1;
+    }
+    #ifdef XAOD_STANDALONE
+        xAOD::Init(MSGSOURCE).ignore();
+        TFile* file = TFile::Open(filename, "READ");
+        if(!file)
+        {
+            Error(MSGSOURCE, "Unable to open file!");
+            return 2;
+        }
+        xAOD::TEvent event(xAOD::TEvent::kClassAccess);
+        xAOD::TStore store;
+        StatusCode::enableFailure();
+    #else
+       IAppMgrUI* app = POOL::Init();
+       POOL::TEvent event(POOL::TEvent::kClassAccess);
+       TString file(filename);
+    #endif
+    event.readFrom(file).ignore();
+    Long64_t entries = event.getEntries();
+    Info(MSGSOURCE, "Number of events in the file: %lli", entries);
+
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the electron CP tools");
+    /// For property 'ElectronEfficiencyTools':
+    ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronEffTools;
+    /// For property 'ElectronScaleFactorTools':
+    ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronSFTools; 
+        /// For property 'ListOfLegsPerTool':
+    std::map<std::string,std::string> legsPerTool;
+    /// For property 'ListOfTagsPerTool':
+    std::map<std::string,std::string> tagsPerTool;
+    /// For property 'ElectronLegsPerTag':
+    std::map<std::string,std::string> legsPerTag;
+    /// To tag electron(s) as 'Signal'
+    SG::AuxElement::Decorator<char> dec_signal("Signal");
+    /// To emulate PID selection (90% loose-to-medium efficiency)
+    std::bernoulli_distribution bernoulliPdf(0.9);
+
+    /// RAII on-the-fly creation of electron CP tools:
+    vector<asg::AnaToolHandle<IAsgElectronEfficiencyCorrectionTool>> factory;
+    enum{ cLEGS, cTAG, cKEY, cPID, cISO };
+    std::vector<std::array<std::string,5> > toolConfigs = {
+        /// <list of legs>, <list of tags>, <key in map file>, <PID WP>, <iso WP>
+        /// Single electron trigger: electrons tagged 'Signal'
+        {"e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose", "Signal", "2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose", "Medium", ""}, 
+        /// Dielectron trigger: all electrons (tagged or not)
+        {"e12_lhloose_L1EM10VH", "*,Signal", "2015_e12_lhloose_L1EM10VH", "LooseBLayer", ""}
+     };
+
+    const char* mapPath = "ElectronEfficiencyCorrection/2015_2017/"
+            "rel21.2/Moriond_February2018_v2/map6.txt";
+    for(auto& cfg : toolConfigs) /// one instance per trigger leg x working point
+    for(int j=0;j<2;++j) /// two instances: 0 -> MC efficiencies, 1 -> SFs
+    {
+        string name = "AsgElectronEfficiencyCorrectionTool/"
+                + ((j? "ElTrigEff_" : "ElTrigSF_")
+                + std::to_string(factory.size()/2));
+        auto t = factory.emplace(factory.end(), name);
+        t->setProperty("MapFilePath", mapPath).ignore();
+        t->setProperty("TriggerKey", string(j?"":"Eff_") + cfg[cKEY]).ignore();
+        t->setProperty("IdKey", cfg[cPID]).ignore();
+        t->setProperty("IsoKey", cfg[cISO]).ignore();
+
+        t->setProperty("CorrelationModel", "TOTAL").ignore();
+        t->setProperty("ForceDataType", (int)PATCore::ParticleDataType::Full).ignore();
+        if(t->initialize() != StatusCode::SUCCESS)
+        {
+            Error(MSGSOURCE, "Unable to initialize the electron CP tool <%s>!",
+                    t->name().c_str());
+            return 3;
+        }
+        auto& handles = (j? electronSFTools : electronEffTools);
+        handles.push_back(t->getHandle());
+        /// Safer to retrieve the name from the final ToolHandle, it might be
+        /// prefixed (by the parent tool name) when the handle is copied    
+        name = handles[handles.size()-1].name();
+        legsPerTool[name] = cfg[cLEGS];
+        tagsPerTool[name] = cfg[cTAG];
+        if(!j)
+        {
+            for(auto& tag : ::split_comma_delimited(cfg[cTAG]))
+            {
+                if(legsPerTag[tag]=="") legsPerTag[tag] = cfg[cLEGS];
+                else legsPerTag[tag] += "," + cfg[cLEGS];
+            }
+        }
+
+    }
+
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the global trigger SF tool");
+    asg::AnaToolHandle<ITrigGlobalEfficiencyCorrectionTool> myTool("TrigGlobalEfficiencyCorrectionTool/TrigGlobal");
+    myTool.setProperty("ElectronEfficiencyTools", electronEffTools).ignore();
+    myTool.setProperty("ElectronScaleFactorTools", electronSFTools).ignore();
+    const char* triggers2015 = 
+        "e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose"
+        "|| 2e12_lhloose_L12EM10VH";
+    myTool.setProperty("TriggerCombination2015", triggers2015).ignore();
+    myTool.setProperty("LeptonTagDecorations", "Signal").ignore();
+    myTool.setProperty("ListOfLegsPerTool", legsPerTool).ignore();
+    myTool.setProperty("ListOfTagsPerTool", tagsPerTool).ignore();
+    myTool.setProperty("ListOfLegsPerTag", legsPerTag).ignore();
+
+    if(debug) myTool.setProperty("OutputLevel", MSG::DEBUG).ignore();
+    if(toys) myTool.setProperty("NumberOfToys", 1000).ignore();
+    if(myTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the TrigGlob tool!");
+        return 3;
+    }
+    
+    /// Uniform random run number generation spanning the target dataset.
+    /// In real life, use the PileupReweightingTool instead!
+    const unsigned periodRuns[] = {
+        /// 2015 periods D-H, J
+        276073, 278727, 279932, 280423, 281130, 282625
+    };
+    std::uniform_int_distribution<unsigned> uniformPdf(0,
+            sizeof(periodRuns)/sizeof(*periodRuns) - 1);
+    std::default_random_engine randomEngine;
+    
+    SG::AuxElement::ConstAccessor<int> truthType("truthType");
+    SG::AuxElement::ConstAccessor<int> truthOrigin("truthOrigin");
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Starting the event loop");
+    unsigned errors = 0;
+    double nSuitableEvents = 0., sumW = 0.;
+    for(Long64_t entry = 0; entry < entries; ++entry)
+    {
+        event.getEntry(entry);
+        
+        /// Get a random run number, and decorate the event info
+        const xAOD::EventInfo* eventInfo = nullptr;
+        event.retrieve(eventInfo,"EventInfo").ignore();
+        unsigned runNumber = periodRuns[uniformPdf(randomEngine)];
+        eventInfo->auxdecor<unsigned>("RandomRunNumber") = runNumber;
+        vector<const xAOD::Electron*> myTriggeringElectrons;
+        const xAOD::ElectronContainer* electrons = nullptr;
+        event.retrieve(electrons,"Electrons").ignore();
+        for(auto electron : *electrons)
+        {
+            if(!electron->caloCluster()) continue;
+            float eta = fabs(electron->caloCluster()->etaBE(2));
+            float pt = electron->pt();
+            if(pt<10e3f || eta>=2.47) continue;
+            if(!truthType.isAvailable(*electron)) continue;
+            if(!truthOrigin.isAvailable(*electron)) continue;
+            int t = truthType(*electron), o = truthOrigin(*electron);
+            if(t!=2 || !(o==10 || (o>=12 && o<=22) || o==43)) continue;
+            /// electron must be above softest trigger threshold (e12 here)
+            if(pt < 13e3f) continue;
+
+            myTriggeringElectrons.push_back(electron);
+        }
+
+        vector<const xAOD::Muon*> myTriggeringMuons;
+
+        /// Add 'Signal' decorations to random electrons
+        /// also count 'Signal' electrons above e24_xxx threshold
+        unsigned nTrig1L = 0;
+        for(auto electron : myTriggeringElectrons)
+        {
+            bool signal = bernoulliPdf(randomEngine);
+            dec_signal(*electron) = signal? 1 : 0;
+            if(signal && electron->pt()>25e3f) ++nTrig1L;
+        }
+        
+        /// Events must contain enough leptons to trigger
+        if(nTrig1L < 1 /// single-electron trigger
+            && myTriggeringElectrons.size() < 2) /// dielectron
+        {
+            continue;
+        }
+
+
+        /// Finally retrieve the global trigger scale factor
+        double sf = 1.;
+        auto cc = myTool->getEfficiencyScaleFactor(myTriggeringElectrons,
+            myTriggeringMuons, sf);
+        if(cc==CP::CorrectionCode::Ok)
+        {
+            nSuitableEvents += 1;
+            sumW += sf;
+        }
+        else
+        {
+            Warning(MSGSOURCE, "Scale factor evaluation failed");
+            ++errors;
+        }
+        if(errors>10)
+        {
+            Error(MSGSOURCE, "Too many errors reported!");
+            break;
+        }
+    }
+    Info(MSGSOURCE, "Average scale factor: %f (over %ld events)",
+            sumW / nSuitableEvents, long(nSuitableEvents));
+    #ifndef XAOD_STANDALONE
+		ANA_CHECK(app->finalize())
+    #endif
+    return errors? 4 : 0;
+}
+
+/// Split comma-delimited string
+namespace
+{
+    inline vector<string> split_comma_delimited(const string& s)
+    {
+        std::stringstream ss(s);
+        std::vector<std::string> tokens;
+        std::string token;
+        while(std::getline(ss, token, ','))
+        {
+            if(token.length()) tokens.push_back(token);
+        }
+        return tokens;
+    }
+}
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample3d.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample3d.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..87eed517ba35fb8de09d0b92563e2d141a621cac
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample3d.cxx
@@ -0,0 +1,378 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// Based on CPToolTester.cxx (A. Kraznahorkay) 
+/// and ut_ath_checkTrigger_test.cxx (W. Buttinger)
+/// Contact: jmaurer@cern.ch
+/*
+ *    The set of examples 3a - 3e illustrates the use of lepton selection tags
+ * for various scenarios:
+ * 
+ * - Example 3a: trigger = 2e12_lhloose_L12EM10VH, selecting events containing
+ *               >=2 loose electrons, the leading-pT electron always satisfying
+ *               in addition tight PID+isolation requirements.
+ * 
+ * - Example 3b: trigger = e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose
+ *                         || 2e12_lhloose_L12EM10VH
+ *               selecting events with >=2 loose electrons where the leading-pT
+ *               electron also satisfies medium PID requirements.
+ *               Only the latter is allowed to fire the single-electron trigger.
+ * 
+ * - Example 3c: trigger = e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose
+ *                         || 2e12_lhloose_L12EM10VH
+ *               selecting events with >=2 loose electrons. Any electron also
+ *               satisfying medium PID requirements is allowed to fire the
+ *               single-electron trigger.
+ * 
+ * - Example 3d: trigger = 2e17_lhvloose_nod0 || e7_lhmedium_nod0_mu24
+ *       || e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0
+ *               same type of selection as example 3c but with 3 PID working
+ *               points, using two distinct decorations.
+ * 
+ * - Example 3e: same scenario as example 3d, but using an alternative
+ *               implementation that requires only one decoration.
+ * 
+ */
+/*
+ *    We use two decorations, 'MyMedium' and 'MyTight'
+ * Note that the TrigGlob tool considers only one single tag per electron. When
+ * an electron has >=2 non-zero decorations (e.g. 'MyMedium' + 'MyTight'),
+ * the tag associated to the electron by the tool is chosen as the first one
+ * appearing in the list of decorations provided in the 'LeptonTagDecorations'
+ * property.
+ *     
+ */
+
+// ROOT include(s):
+#include <TFile.h>
+#include <TError.h>
+
+// Infrastructure include(s):
+#ifdef XAOD_STANDALONE
+    #include "xAODRootAccess/Init.h"
+    #include "xAODRootAccess/TEvent.h"
+    #include "xAODRootAccess/TStore.h"
+#else
+    #include "AthAnalysisBaseComps/AthAnalysisHelper.h"
+    #include "POOLRootAccess/TEvent.h"
+#endif
+
+#include "AsgMessaging/MessageCheck.h"
+// EDM include(s):
+#include "AsgTools/AnaToolHandle.h"
+#include "EgammaAnalysisInterfaces/IAsgElectronEfficiencyCorrectionTool.h"
+#include "MuonAnalysisInterfaces/IMuonTriggerScaleFactors.h"
+#include "TriggerAnalysisInterfaces/ITrigGlobalEfficiencyCorrectionTool.h"
+#include "xAODEventInfo/EventInfo.h"
+#include "xAODEgamma/ElectronContainer.h"
+#include "xAODMuon/MuonContainer.h"
+#include "PATCore/PATCoreEnums.h"
+#include "AthContainers/AuxElement.h"
+
+// stdlib include(s):
+#include <sstream>
+#include <random>
+#include <vector>
+#include <array>
+using std::vector;
+using std::string;
+
+/// Helper function to split comma-delimited strings
+namespace { vector<string> split_comma_delimited(const std::string&); }
+
+#define MSGSOURCE "Example 3d"
+
+ANA_MSG_HEADER(Test)
+ANA_MSG_SOURCE(Test, MSGSOURCE)
+using namespace Test;
+int main(int argc, char* argv[])
+{
+    ANA_CHECK_SET_TYPE(bool)
+    const char* filename = nullptr;
+    bool debug = false, cmdline_error = false, toys = false;
+    for(int i=1;i<argc;++i)
+    {
+        if(string(argv[i]) == "--debug") debug = true;
+        else if(string(argv[i]) == "--toys") toys = true;
+        else if(!filename && *argv[i]!='-') filename = argv[i];
+        else cmdline_error = true;
+    }
+    if(!filename || cmdline_error)
+    {
+        Error(MSGSOURCE, "No file name received!");
+        Error(MSGSOURCE, "  Usage: %s [--debug] [--toys] [DxAOD file name]", argv[0]);
+        return 1;
+    }
+    #ifdef XAOD_STANDALONE
+        xAOD::Init(MSGSOURCE).ignore();
+        TFile* file = TFile::Open(filename, "READ");
+        if(!file)
+        {
+            Error(MSGSOURCE, "Unable to open file!");
+            return 2;
+        }
+        xAOD::TEvent event(xAOD::TEvent::kClassAccess);
+        xAOD::TStore store;
+        StatusCode::enableFailure();
+    #else
+       IAppMgrUI* app = POOL::Init();
+       POOL::TEvent event(POOL::TEvent::kClassAccess);
+       TString file(filename);
+    #endif
+    event.readFrom(file).ignore();
+    Long64_t entries = event.getEntries();
+    Info(MSGSOURCE, "Number of events in the file: %lli", entries);
+
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the electron CP tools");
+    /// For property 'ElectronEfficiencyTools':
+    ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronEffTools;
+    /// For property 'ElectronScaleFactorTools':
+    ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronSFTools; 
+        /// For property 'ListOfLegsPerTool':
+    std::map<std::string,std::string> legsPerTool;
+    /// For property 'ListOfTagsPerTool':
+    std::map<std::string,std::string> tagsPerTool;
+    /// For property 'ElectronLegsPerTag':
+    std::map<std::string,std::string> legsPerTag;
+    /// To tag electron(s) as 'MyMedium' and 'MyTight'
+    SG::AuxElement::Decorator<char> dec_medium("MyMedium");
+    SG::AuxElement::Decorator<char> dec_tight("MyTight");
+    /// To emulate PID selection (90% loose-to-medium/medium-to-tight eff.)
+    std::bernoulli_distribution bernoulliPdf(0.9);
+
+    /// RAII on-the-fly creation of electron CP tools:
+    vector<asg::AnaToolHandle<IAsgElectronEfficiencyCorrectionTool>> factory;
+    enum{ cLEGS, cTAG, cKEY, cPID, cISO };
+    std::vector<std::array<std::string,5> > toolConfigs = {
+        /// <list of legs>, <list of tags>, <key in map file>, <PID WP>, <iso WP>
+        /// Single-electron trigger: electrons tagged 'MyTight'
+        {"e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0", "MyTight", 
+            "2016_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0", "Tight", "GradientLoose"},
+        /// Electron-muon trigger: electrons tagged 'MyTight' or 'MyMedium'
+        {"e7_lhmedium_nod0", "MyMedium,MyTight", "2016_e7_lhmedium_nod0", "Medium", ""},
+        /// Dielectron trigger: all electrons (tagged or not)
+        {"e17_lhvloose_nod0", "*,MyMedium,MyTight", "2016_e17_lhvloose_nod0", "LooseBLayer", ""},
+     };
+
+    const char* mapPath = "ElectronEfficiencyCorrection/2015_2017/"
+            "rel21.2/Moriond_February2018_v2/map6.txt";
+    for(auto& cfg : toolConfigs) /// one instance per trigger leg x working point
+    for(int j=0;j<2;++j) /// two instances: 0 -> MC efficiencies, 1 -> SFs
+    {
+        string name = "AsgElectronEfficiencyCorrectionTool/"
+                + ((j? "ElTrigEff_" : "ElTrigSF_")
+                + std::to_string(factory.size()/2));
+        auto t = factory.emplace(factory.end(), name);
+        t->setProperty("MapFilePath", mapPath).ignore();
+        t->setProperty("TriggerKey", string(j?"":"Eff_") + cfg[cKEY]).ignore();
+        t->setProperty("IdKey", cfg[cPID]).ignore();
+        t->setProperty("IsoKey", cfg[cISO]).ignore();
+
+        t->setProperty("CorrelationModel", "TOTAL").ignore();
+        t->setProperty("ForceDataType", (int)PATCore::ParticleDataType::Full).ignore();
+        if(t->initialize() != StatusCode::SUCCESS)
+        {
+            Error(MSGSOURCE, "Unable to initialize the electron CP tool <%s>!",
+                    t->name().c_str());
+            return 3;
+        }
+        auto& handles = (j? electronSFTools : electronEffTools);
+        handles.push_back(t->getHandle());
+        /// Safer to retrieve the name from the final ToolHandle, it might be
+        /// prefixed (by the parent tool name) when the handle is copied    
+        name = handles[handles.size()-1].name();
+        legsPerTool[name] = cfg[cLEGS];
+        tagsPerTool[name] = cfg[cTAG];
+        if(!j)
+        {
+            for(auto& tag : ::split_comma_delimited(cfg[cTAG]))
+            {
+                if(legsPerTag[tag]=="") legsPerTag[tag] = cfg[cLEGS];
+                else legsPerTag[tag] += "," + cfg[cLEGS];
+            }
+        }
+
+    }
+
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the muon CP tools");
+    /// For property 'MuonTools':
+    ToolHandleArray<CP::IMuonTriggerScaleFactors> muonTools;
+    asg::AnaToolHandle<CP::IMuonTriggerScaleFactors> muonTool("CP::MuonTriggerScaleFactors/MuonTrigEff");
+    muonTool.setProperty("MuonQuality", "Tight").ignore();
+    muonTool.setProperty("useRel207", false).ignore();
+    if(muonTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the muon CP tool!");
+        return 3;
+    }
+    muonTools.push_back(muonTool.getHandle());
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the global trigger SF tool");
+    asg::AnaToolHandle<ITrigGlobalEfficiencyCorrectionTool> myTool("TrigGlobalEfficiencyCorrectionTool/TrigGlobal");
+    myTool.setProperty("ElectronEfficiencyTools", electronEffTools).ignore();
+    myTool.setProperty("ElectronScaleFactorTools", electronSFTools).ignore();
+    myTool.setProperty("MuonTools", muonTools).ignore();
+    const char* triggers2016 = 
+        "e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0"
+        "|| e7_lhmedium_nod0_mu24"
+        "|| 2e17_lhvloose_nod0";
+    myTool.setProperty("TriggerCombination2016", triggers2016).ignore();
+    /// Listing 'Tight' first as it has higher priority (an electron with both
+    /// non-zero 'Tight'+'Medium' decorations will then be tagged as 'Tight')
+    myTool.setProperty("LeptonTagDecorations", "MyTight,MyMedium").ignore();
+    myTool.setProperty("ListOfLegsPerTool", legsPerTool).ignore();
+    myTool.setProperty("ListOfTagsPerTool", tagsPerTool).ignore();
+    myTool.setProperty("ListOfLegsPerTag", legsPerTag).ignore();
+
+    if(debug) myTool.setProperty("OutputLevel", MSG::DEBUG).ignore();
+    if(toys) myTool.setProperty("NumberOfToys", 1000).ignore();
+    if(myTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the TrigGlob tool!");
+        return 3;
+    }
+    
+    /// Uniform random run number generation spanning the target dataset.
+    /// In real life, use the PileupReweightingTool instead!
+    const unsigned periodRuns[] = {
+        /// 2016 periods A-L
+        296939, 300345, 301912, 302737, 303638, 303943, 305291, 307124, 
+        305359, 309311, 310015
+    };
+    std::uniform_int_distribution<unsigned> uniformPdf(0,
+            sizeof(periodRuns)/sizeof(*periodRuns) - 1);
+    std::default_random_engine randomEngine;
+    
+    SG::AuxElement::ConstAccessor<int> truthType("truthType");
+    SG::AuxElement::ConstAccessor<int> truthOrigin("truthOrigin");
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Starting the event loop");
+    unsigned errors = 0;
+    double nSuitableEvents = 0., sumW = 0.;
+    for(Long64_t entry = 0; entry < entries; ++entry)
+    {
+        event.getEntry(entry);
+        
+        /// Get a random run number, and decorate the event info
+        const xAOD::EventInfo* eventInfo = nullptr;
+        event.retrieve(eventInfo,"EventInfo").ignore();
+        unsigned runNumber = periodRuns[uniformPdf(randomEngine)];
+        eventInfo->auxdecor<unsigned>("RandomRunNumber") = runNumber;
+
+        unsigned nTrig_e26 = 0, nTrig_e7 = 0, nTrig_e17 = 0;
+        
+        vector<const xAOD::Electron*> myTriggeringElectrons;
+        const xAOD::ElectronContainer* electrons = nullptr;
+        event.retrieve(electrons,"Electrons").ignore();
+        for(auto electron : *electrons)
+        {
+            if(!electron->caloCluster()) continue;
+            float eta = fabs(electron->caloCluster()->etaBE(2));
+            float pt = electron->pt();
+            if(pt<10e3f || eta>=2.47) continue;
+            if(!truthType.isAvailable(*electron)) continue;
+            if(!truthOrigin.isAvailable(*electron)) continue;
+            int t = truthType(*electron), o = truthOrigin(*electron);
+            if(t!=2 || !(o==10 || (o>=12 && o<=22) || o==43)) continue;
+            /// electron must be above softest trigger threshold (e7 here
+            if(pt < 7e3f) continue;
+            if(pt >= 18e3f) ++nTrig_e17;
+
+            myTriggeringElectrons.push_back(electron);
+        }
+
+        vector<const xAOD::Muon*> myTriggeringMuons;
+        const xAOD::MuonContainer* muons = nullptr;
+        event.retrieve(muons,"Muons").ignore();
+        for(auto muon : *muons)
+        {
+            if(runNumber >= 324320) break; // delete line once all SFs available for 2017
+            float pt = muon->pt();
+            if(pt<10e3f || fabs(muon->eta())>=2.5) continue;
+            auto mt = muon->muonType();
+            if(mt!=xAOD::Muon::Combined && mt!=xAOD::Muon::MuonStandAlone) continue;
+            auto& mtp = *(muon->primaryTrackParticle());
+            if(!truthType.isAvailable(mtp)) continue;
+            if(!truthOrigin.isAvailable(mtp)) continue;
+            int t = truthType(mtp), o = truthOrigin(mtp);
+            if(t!=6 || !(o==10 || (o>=12 && o<=22) || o==43)) continue;
+            /// muon must be above softest trigger threshold (mu24 here)
+            if(pt < 25.2e3f) continue;
+
+            myTriggeringMuons.push_back(muon);
+        }
+
+        /// Add 'MyMedium' & 'MyTight' decorations to random electrons
+        /// also count tight electrons above e26_xxx threshold
+        /// and medium electrons above e7_xxx threshold
+        for(auto electron : myTriggeringElectrons)
+        {
+            bool medium = bernoulliPdf(randomEngine);
+            dec_medium(*electron) = medium? 1 : 0;
+            if(medium && electron->pt()>8e3f) ++nTrig_e7;
+            bool tight = medium && bernoulliPdf(randomEngine);
+            dec_tight(*electron) = tight? 1 : 0;
+            if(tight && electron->pt()>27e3f) ++nTrig_e26;
+        }
+        
+        /// Events must contain enough leptons to trigger
+        if(nTrig_e26 < 1 /// single-electron trigger
+            && (nTrig_e7==0 || myTriggeringMuons.size()==0) /// electron-muon
+            && nTrig_e17 < 2) /// dielectron
+        {
+            continue;
+        }
+
+
+        /// Finally retrieve the global trigger scale factor
+        double sf = 1.;
+        auto cc = myTool->getEfficiencyScaleFactor(myTriggeringElectrons,
+            myTriggeringMuons, sf);
+        if(cc==CP::CorrectionCode::Ok)
+        {
+            nSuitableEvents += 1;
+            sumW += sf;
+        }
+        else
+        {
+            Warning(MSGSOURCE, "Scale factor evaluation failed");
+            ++errors;
+        }
+        if(errors>10)
+        {
+            Error(MSGSOURCE, "Too many errors reported!");
+            break;
+        }
+    }
+    Info(MSGSOURCE, "Average scale factor: %f (over %ld events)",
+            sumW / nSuitableEvents, long(nSuitableEvents));
+    #ifndef XAOD_STANDALONE
+		ANA_CHECK(app->finalize())
+    #endif
+    return errors? 4 : 0;
+}
+
+/// Split comma-delimited string
+namespace
+{
+    inline vector<string> split_comma_delimited(const string& s)
+    {
+        std::stringstream ss(s);
+        std::vector<std::string> tokens;
+        std::string token;
+        while(std::getline(ss, token, ','))
+        {
+            if(token.length()) tokens.push_back(token);
+        }
+        return tokens;
+    }
+}
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample3e.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample3e.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..624d98f15d48a01c447e05d73f33ba6f65c9794e
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample3e.cxx
@@ -0,0 +1,375 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// Based on CPToolTester.cxx (A. Kraznahorkay) 
+/// and ut_ath_checkTrigger_test.cxx (W. Buttinger)
+/// Contact: jmaurer@cern.ch
+/*
+ *    The set of examples 3a - 3e illustrates the use of lepton selection tags
+ * for various scenarios:
+ * 
+ * - Example 3a: trigger = 2e12_lhloose_L12EM10VH, selecting events containing
+ *               >=2 loose electrons, the leading-pT electron always satisfying
+ *               in addition tight PID+isolation requirements.
+ * 
+ * - Example 3b: trigger = e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose
+ *                         || 2e12_lhloose_L12EM10VH
+ *               selecting events with >=2 loose electrons where the leading-pT
+ *               electron also satisfies medium PID requirements.
+ *               Only the latter is allowed to fire the single-electron trigger.
+ * 
+ * - Example 3c: trigger = e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose
+ *                         || 2e12_lhloose_L12EM10VH
+ *               selecting events with >=2 loose electrons. Any electron also
+ *               satisfying medium PID requirements is allowed to fire the
+ *               single-electron trigger.
+ * 
+ * - Example 3d: trigger = 2e17_lhvloose_nod0 || e7_lhmedium_nod0_mu24
+ *       || e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0
+ *               same type of selection as example 3c but with 3 PID working
+ *               points, using two distinct decorations.
+ * 
+ * - Example 3e: same scenario as example 3d, but using an alternative
+ *               implementation that requires only one decoration.
+ * 
+ */
+/*
+ *    We use a single decoration named 'PID', and rely on the decorated value to
+ * indicate whether the electron passes TightLH PID + isolation (=> value = 2)
+ * or only MediumLH PID (=> value = 1). The TrigGlob tool then forms tags
+ * by suffixing the decorated value to the decoration name, i.e. 'PID1' and
+ * 'PID2'. One must then use the latter tags in the properties 'ListOfLegsPerTag'
+ * and 'ListOfTagsPerTool'. */
+
+// ROOT include(s):
+#include <TFile.h>
+#include <TError.h>
+
+// Infrastructure include(s):
+#ifdef XAOD_STANDALONE
+    #include "xAODRootAccess/Init.h"
+    #include "xAODRootAccess/TEvent.h"
+    #include "xAODRootAccess/TStore.h"
+#else
+    #include "AthAnalysisBaseComps/AthAnalysisHelper.h"
+    #include "POOLRootAccess/TEvent.h"
+#endif
+
+#include "AsgMessaging/MessageCheck.h"
+// EDM include(s):
+#include "AsgTools/AnaToolHandle.h"
+#include "EgammaAnalysisInterfaces/IAsgElectronEfficiencyCorrectionTool.h"
+#include "MuonAnalysisInterfaces/IMuonTriggerScaleFactors.h"
+#include "TriggerAnalysisInterfaces/ITrigGlobalEfficiencyCorrectionTool.h"
+#include "xAODEventInfo/EventInfo.h"
+#include "xAODEgamma/ElectronContainer.h"
+#include "xAODMuon/MuonContainer.h"
+#include "PATCore/PATCoreEnums.h"
+#include "AthContainers/AuxElement.h"
+
+// stdlib include(s):
+#include <sstream>
+#include <random>
+#include <vector>
+#include <array>
+using std::vector;
+using std::string;
+
+/// Helper function to split comma-delimited strings
+namespace { vector<string> split_comma_delimited(const std::string&); }
+
+#define MSGSOURCE "Example 3e"
+
+ANA_MSG_HEADER(Test)
+ANA_MSG_SOURCE(Test, MSGSOURCE)
+using namespace Test;
+int main(int argc, char* argv[])
+{
+    ANA_CHECK_SET_TYPE(bool)
+    const char* filename = nullptr;
+    bool debug = false, cmdline_error = false, toys = false;
+    for(int i=1;i<argc;++i)
+    {
+        if(string(argv[i]) == "--debug") debug = true;
+        else if(string(argv[i]) == "--toys") toys = true;
+        else if(!filename && *argv[i]!='-') filename = argv[i];
+        else cmdline_error = true;
+    }
+    if(!filename || cmdline_error)
+    {
+        Error(MSGSOURCE, "No file name received!");
+        Error(MSGSOURCE, "  Usage: %s [--debug] [--toys] [DxAOD file name]", argv[0]);
+        return 1;
+    }
+    #ifdef XAOD_STANDALONE
+        xAOD::Init(MSGSOURCE).ignore();
+        TFile* file = TFile::Open(filename, "READ");
+        if(!file)
+        {
+            Error(MSGSOURCE, "Unable to open file!");
+            return 2;
+        }
+        xAOD::TEvent event(xAOD::TEvent::kClassAccess);
+        xAOD::TStore store;
+        StatusCode::enableFailure();
+    #else
+       IAppMgrUI* app = POOL::Init();
+       POOL::TEvent event(POOL::TEvent::kClassAccess);
+       TString file(filename);
+    #endif
+    event.readFrom(file).ignore();
+    Long64_t entries = event.getEntries();
+    Info(MSGSOURCE, "Number of events in the file: %lli", entries);
+
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the electron CP tools");
+    /// For property 'ElectronEfficiencyTools':
+    ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronEffTools;
+    /// For property 'ElectronScaleFactorTools':
+    ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronSFTools; 
+        /// For property 'ListOfLegsPerTool':
+    std::map<std::string,std::string> legsPerTool;
+    /// For property 'ListOfTagsPerTool':
+    std::map<std::string,std::string> tagsPerTool;
+    /// For property 'ElectronLegsPerTag':
+    std::map<std::string,std::string> legsPerTag;
+    /// To tag electrons according to the PID criteria they fulfil
+    SG::AuxElement::Decorator<char> dec_pid("PID");
+    /// To emulate PID selection (90% loose-to-medium/medium-to-tight eff.)
+    std::bernoulli_distribution bernoulliPdf(0.9);
+
+    /// RAII on-the-fly creation of electron CP tools:
+    vector<asg::AnaToolHandle<IAsgElectronEfficiencyCorrectionTool>> factory;
+    enum{ cLEGS, cTAG, cKEY, cPID, cISO };
+    std::vector<std::array<std::string,5> > toolConfigs = {
+        /// <list of legs>, <list of tags>, <key in map file>, <PID WP>, <iso WP>
+        /// Single-electron trigger: only electrons tagged 'PID2' (TightLH+iso) 
+        {"e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0", "PID2", 
+            "2016_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0", "Tight", "GradientLoose"},
+        /// Electron-muon trigger: electrons tagged 'PID2' or 'PID1' (MediumLH)
+        {"e7_lhmedium_nod0", "PID1,PID2", "2016_e7_lhmedium_nod0", "Medium", ""},
+        /// Dielectron trigger: all electrons (tagged or not)
+        {"e17_lhvloose_nod0", "*,PID1,PID2", "2016_e17_lhvloose_nod0", "LooseBLayer", ""},
+     };
+
+    const char* mapPath = "ElectronEfficiencyCorrection/2015_2017/"
+            "rel21.2/Moriond_February2018_v2/map6.txt";
+    for(auto& cfg : toolConfigs) /// one instance per trigger leg x working point
+    for(int j=0;j<2;++j) /// two instances: 0 -> MC efficiencies, 1 -> SFs
+    {
+        string name = "AsgElectronEfficiencyCorrectionTool/"
+                + ((j? "ElTrigEff_" : "ElTrigSF_")
+                + std::to_string(factory.size()/2));
+        auto t = factory.emplace(factory.end(), name);
+        t->setProperty("MapFilePath", mapPath).ignore();
+        t->setProperty("TriggerKey", string(j?"":"Eff_") + cfg[cKEY]).ignore();
+        t->setProperty("IdKey", cfg[cPID]).ignore();
+        t->setProperty("IsoKey", cfg[cISO]).ignore();
+
+        t->setProperty("CorrelationModel", "TOTAL").ignore();
+        t->setProperty("ForceDataType", (int)PATCore::ParticleDataType::Full).ignore();
+        if(t->initialize() != StatusCode::SUCCESS)
+        {
+            Error(MSGSOURCE, "Unable to initialize the electron CP tool <%s>!",
+                    t->name().c_str());
+            return 3;
+        }
+        auto& handles = (j? electronSFTools : electronEffTools);
+        handles.push_back(t->getHandle());
+        /// Safer to retrieve the name from the final ToolHandle, it might be
+        /// prefixed (by the parent tool name) when the handle is copied    
+        name = handles[handles.size()-1].name();
+        legsPerTool[name] = cfg[cLEGS];
+        tagsPerTool[name] = cfg[cTAG];
+        if(!j)
+        {
+            for(auto& tag : ::split_comma_delimited(cfg[cTAG]))
+            {
+                if(legsPerTag[tag]=="") legsPerTag[tag] = cfg[cLEGS];
+                else legsPerTag[tag] += "," + cfg[cLEGS];
+            }
+        }
+
+    }
+
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the muon CP tools");
+    /// For property 'MuonTools':
+    ToolHandleArray<CP::IMuonTriggerScaleFactors> muonTools;
+    asg::AnaToolHandle<CP::IMuonTriggerScaleFactors> muonTool("CP::MuonTriggerScaleFactors/MuonTrigEff");
+    muonTool.setProperty("MuonQuality", "Tight").ignore();
+    muonTool.setProperty("useRel207", false).ignore();
+    if(muonTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the muon CP tool!");
+        return 3;
+    }
+    muonTools.push_back(muonTool.getHandle());
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the global trigger SF tool");
+    asg::AnaToolHandle<ITrigGlobalEfficiencyCorrectionTool> myTool("TrigGlobalEfficiencyCorrectionTool/TrigGlobal");
+    myTool.setProperty("ElectronEfficiencyTools", electronEffTools).ignore();
+    myTool.setProperty("ElectronScaleFactorTools", electronSFTools).ignore();
+    myTool.setProperty("MuonTools", muonTools).ignore();
+    const char* triggers2016 = 
+        "e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0"
+        "|| e7_lhmedium_nod0_mu24"
+        "|| 2e17_lhvloose_nod0";
+    myTool.setProperty("TriggerCombination2016", triggers2016).ignore();
+    /// Special character '?' indicates that the decorated value is to be 
+    /// suffixed to the name (=> 'PID1' for medium, 'PID2' for tight)
+    myTool.setProperty("LeptonTagDecorations", "PID?").ignore();
+    myTool.setProperty("ListOfLegsPerTool", legsPerTool).ignore();
+    myTool.setProperty("ListOfTagsPerTool", tagsPerTool).ignore();
+    myTool.setProperty("ListOfLegsPerTag", legsPerTag).ignore();
+
+    if(debug) myTool.setProperty("OutputLevel", MSG::DEBUG).ignore();
+    if(toys) myTool.setProperty("NumberOfToys", 1000).ignore();
+    if(myTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the TrigGlob tool!");
+        return 3;
+    }
+    
+    /// Uniform random run number generation spanning the target dataset.
+    /// In real life, use the PileupReweightingTool instead!
+    const unsigned periodRuns[] = {
+        /// 2016 periods A-L
+        296939, 300345, 301912, 302737, 303638, 303943, 305291, 307124, 
+        305359, 309311, 310015
+    };
+    std::uniform_int_distribution<unsigned> uniformPdf(0,
+            sizeof(periodRuns)/sizeof(*periodRuns) - 1);
+    std::default_random_engine randomEngine;
+    
+    SG::AuxElement::ConstAccessor<int> truthType("truthType");
+    SG::AuxElement::ConstAccessor<int> truthOrigin("truthOrigin");
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Starting the event loop");
+    unsigned errors = 0;
+    double nSuitableEvents = 0., sumW = 0.;
+    for(Long64_t entry = 0; entry < entries; ++entry)
+    {
+        event.getEntry(entry);
+        
+        /// Get a random run number, and decorate the event info
+        const xAOD::EventInfo* eventInfo = nullptr;
+        event.retrieve(eventInfo,"EventInfo").ignore();
+        unsigned runNumber = periodRuns[uniformPdf(randomEngine)];
+        eventInfo->auxdecor<unsigned>("RandomRunNumber") = runNumber;
+
+        unsigned nTrig_e26 = 0, nTrig_e7 = 0, nTrig_e17 = 0;
+        
+        vector<const xAOD::Electron*> myTriggeringElectrons;
+        const xAOD::ElectronContainer* electrons = nullptr;
+        event.retrieve(electrons,"Electrons").ignore();
+        for(auto electron : *electrons)
+        {
+            if(!electron->caloCluster()) continue;
+            float eta = fabs(electron->caloCluster()->etaBE(2));
+            float pt = electron->pt();
+            if(pt<10e3f || eta>=2.47) continue;
+            if(!truthType.isAvailable(*electron)) continue;
+            if(!truthOrigin.isAvailable(*electron)) continue;
+            int t = truthType(*electron), o = truthOrigin(*electron);
+            if(t!=2 || !(o==10 || (o>=12 && o<=22) || o==43)) continue;
+            /// electron must be above softest trigger threshold (e7 here
+            if(pt < 7e3f) continue;
+            if(pt >= 18e3f) ++nTrig_e17;
+
+            myTriggeringElectrons.push_back(electron);
+        }
+
+        vector<const xAOD::Muon*> myTriggeringMuons;
+        const xAOD::MuonContainer* muons = nullptr;
+        event.retrieve(muons,"Muons").ignore();
+        for(auto muon : *muons)
+        {
+            if(runNumber >= 324320) break; // delete line once all SFs available for 2017
+            float pt = muon->pt();
+            if(pt<10e3f || fabs(muon->eta())>=2.5) continue;
+            auto mt = muon->muonType();
+            if(mt!=xAOD::Muon::Combined && mt!=xAOD::Muon::MuonStandAlone) continue;
+            auto& mtp = *(muon->primaryTrackParticle());
+            if(!truthType.isAvailable(mtp)) continue;
+            if(!truthOrigin.isAvailable(mtp)) continue;
+            int t = truthType(mtp), o = truthOrigin(mtp);
+            if(t!=6 || !(o==10 || (o>=12 && o<=22) || o==43)) continue;
+            /// muon must be above softest trigger threshold (mu24 here)
+            if(pt < 25.2e3f) continue;
+
+            myTriggeringMuons.push_back(muon);
+        }
+
+        /// Add 'PID' decorations to random electrons
+        /// also count 'Tight' electrons above e26_xxx threshold
+        /// and 'Medium' electrons above e7_xxx threshold
+        for(auto electron : myTriggeringElectrons)
+        {
+            bool medium = bernoulliPdf(randomEngine);
+            bool tight = medium && bernoulliPdf(randomEngine);
+            /// Set decorated value to 2 for TightLH+iso or 1 for MediumLH
+            dec_pid(*electron) = tight? 2 : medium? 1 : 0;
+            if(medium && electron->pt()>8e3f) ++nTrig_e7;
+            if(tight && electron->pt()>27e3f) ++nTrig_e26;
+        }
+        
+        /// Events must contain enough leptons to trigger
+        if(nTrig_e26 < 1 /// single-electron trigger
+            && (nTrig_e7==0 || myTriggeringMuons.size()==0) /// electron-muon
+            && nTrig_e17 < 2) /// dielectron
+        {
+            continue;
+        }
+
+
+        /// Finally retrieve the global trigger scale factor
+        double sf = 1.;
+        auto cc = myTool->getEfficiencyScaleFactor(myTriggeringElectrons,
+            myTriggeringMuons, sf);
+        if(cc==CP::CorrectionCode::Ok)
+        {
+            nSuitableEvents += 1;
+            sumW += sf;
+        }
+        else
+        {
+            Warning(MSGSOURCE, "Scale factor evaluation failed");
+            ++errors;
+        }
+        if(errors>10)
+        {
+            Error(MSGSOURCE, "Too many errors reported!");
+            break;
+        }
+    }
+    Info(MSGSOURCE, "Average scale factor: %f (over %ld events)",
+            sumW / nSuitableEvents, long(nSuitableEvents));
+    #ifndef XAOD_STANDALONE
+		ANA_CHECK(app->finalize())
+    #endif
+    return errors? 4 : 0;
+}
+
+/// Split comma-delimited string
+namespace
+{
+    inline vector<string> split_comma_delimited(const string& s)
+    {
+        std::stringstream ss(s);
+        std::vector<std::string> tokens;
+        std::string token;
+        while(std::getline(ss, token, ','))
+        {
+            if(token.length()) tokens.push_back(token);
+        }
+        return tokens;
+    }
+}
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample4.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample4.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..77b9cac090bcb70923840adc72b7e9dcc508a4eb
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample4.cxx
@@ -0,0 +1,173 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+// Based on CPToolTester.cxx (A. Kraznahorkay) and ut_ath_checkTrigger_test.cxx (W. Buttinger)
+// Contact: jmaurer@cern.ch
+
+/*
+ *   Examples of "automatic" configuration using TrigGlobalEfficiencyCorrectionTool::suggestElectronMapKeys()
+ *
+ */
+
+// ROOT include(s):
+#include <TFile.h>
+#include <TError.h>
+
+// Infrastructure include(s):
+#ifdef XAOD_STANDALONE
+	#include "xAODRootAccess/Init.h"
+	#include "xAODRootAccess/TEvent.h"
+	#include "xAODRootAccess/TStore.h"
+#else
+	#include "AthAnalysisBaseComps/AthAnalysisHelper.h"
+	#include "POOLRootAccess/TEvent.h"
+#endif
+
+#include "AsgMessaging/MessageCheck.h"
+// EDM include(s):
+#include "xAODEventInfo/EventInfo.h"
+#include "xAODEgamma/ElectronContainer.h"
+#include "PATCore/PATCoreEnums.h"
+#include "AsgTools/AnaToolHandle.h"
+#include "EgammaAnalysisInterfaces/IAsgElectronEfficiencyCorrectionTool.h"
+// The interface header is not sufficient here as this example makes use of a static method of TrigGlobalEfficiencyCorrectionTool
+#include "TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrectionTool.h"
+
+// stdlib include(s):
+#include <random>
+
+bool quiet = false;
+
+#define MSGSOURCE "Example 4"
+ANA_MSG_HEADER(Test)
+ANA_MSG_SOURCE(Test, MSGSOURCE)
+using namespace Test;
+int main(int argc, char* argv[])
+{
+    ANA_CHECK_SET_TYPE(bool)
+	const std::string flagQuiet("--quiet");
+	for(int i=1;i<argc;++i)
+	{
+		if(argv[i] == flagQuiet) quiet = true;
+	}
+	
+	const char* APP_NAME = argv[0];
+	#ifdef XAOD_STANDALONE
+		xAOD::Init(APP_NAME).ignore();
+		StatusCode::enableFailure();
+	#else
+	   IAppMgrUI* app = POOL::Init();
+	#endif
+	
+	/// Retrieve the list of electron map keys for the chosen trigger combination
+	std::map<std::string,std::string> triggerCombination;
+	triggerCombination["2015"] = "2e12_lhloose_L12EM10VH || e17_lhloose_mu14  || mu18_mu8noL1"
+		"|| e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose || mu20_iloose_L1MU15";
+	triggerCombination["2016"] = "2e17_lhvloose_nod0 || e17_lhloose_nod0_mu14  || mu22_mu8noL1 "
+		"|| e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0 || mu26_ivarmedium";
+	triggerCombination["2017"] = "2e17_lhvloose_nod0_L12EM15VHI || 2e24_lhvloose_nod0 || e17_lhloose_nod0_mu14  || mu22_mu8noL1 "
+		"|| e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0 || mu26_ivarmedium";
+	triggerCombination["2018"] = triggerCombination["2017"];
+	std::map<std::string,std::string> legsPerKey, legsAlone;
+	auto cc = TrigGlobalEfficiencyCorrectionTool::suggestElectronMapKeys(triggerCombination, "", legsAlone);
+	if(cc == CP::CorrectionCode::Ok)
+	{
+		if(!quiet)
+		{
+			std::string msg = "List of trigger legs for this combination of triggers:\n";
+			for(auto& kv : legsAlone)
+			{
+				msg += "   - " + kv.second + '\n';
+			}
+			Info(APP_NAME, "%s", msg.c_str());
+		}
+	}
+	else
+	{
+		Error(APP_NAME, "Unable to find list of trigger legs!");
+		return 1;
+	}
+	
+	cc = TrigGlobalEfficiencyCorrectionTool::suggestElectronMapKeys(triggerCombination, "2015_2017/rel21.2/Consolidation_September2018_v1", legsPerKey);
+	if(cc == CP::CorrectionCode::Ok)
+	{
+		if(!quiet)
+		{
+			std::string msg = "List of map keys necessary for this combination of triggers:\n";
+			for(auto& kv : legsPerKey)
+			{
+				msg += "   - tool with key \"" + kv.first + "\" chosen for legs " + kv.second + '\n';
+			}
+			Info(APP_NAME, "%s", msg.c_str());
+		}
+	}
+	else
+	{
+		Error(APP_NAME, "Unable to find list of map keys!");
+		return 1;
+	}
+	
+	/// Then create all the needed electron tools and initialize the TrigGlob tool
+	/// using the information returned by suggestElectronMapKeys()
+
+	/// Trigger efficiency/scale factor CP tools for electrons and muons
+	ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronEffTools, electronSFTools;
+	std::vector<asg::AnaToolHandle<IAsgElectronEfficiencyCorrectionTool> > electronToolsFactory; // for RAII
+	std::map<std::string,std::string> legsPerTool;
+	int nTools = 0;
+	for(auto& kv : legsPerKey)
+	{
+		const std::string& trigKey = kv.first;
+		for(int j=0;j<2;++j) /// one tool instance for efficiencies, another for scale factors
+		{
+			auto t = electronToolsFactory.emplace(electronToolsFactory.end(), "AsgElectronEfficiencyCorrectionTool/ElTrigEff_"+std::to_string(nTools++));
+			t->setProperty("MapFilePath", "ElectronEfficiencyCorrection/2015_2017/rel21.2/Consolidation_September2018_v1/map3.txt").ignore();
+			t->setProperty("TriggerKey", (j? trigKey : "Eff_"+trigKey)).ignore();
+			t->setProperty("IdKey","Tight").ignore();
+			t->setProperty("IsoKey","FCTight").ignore();
+			t->setProperty("CorrelationModel","TOTAL").ignore();
+			t->setProperty("ForceDataType", (int)PATCore::ParticleDataType::Full).ignore();
+			t->setProperty("OutputLevel", MSG::ERROR).ignore();
+			if(t->initialize() != StatusCode::SUCCESS)
+			{
+				Error(APP_NAME, "Unable to initialize CP tool <%s>!", t->name().c_str());
+				return 2;
+			}
+			auto& handles = j? electronSFTools : electronEffTools;
+			handles.push_back(t->getHandle());
+			/// Safer to retrieve the name from the final ToolHandle, it might be prefixed (by the parent tool name) when the handle is copied
+			std::string name = handles[handles.size()-1].name();
+			legsPerTool[name] = legsPerKey[trigKey];
+		}
+	}
+	
+    ToolHandleArray<CP::IMuonTriggerScaleFactors> muonTools;
+    asg::AnaToolHandle<CP::IMuonTriggerScaleFactors> muonTool("CP::MuonTriggerScaleFactors/MuonTrigEff");
+    muonTool.setProperty("MuonQuality", "Tight").ignore();
+	muonTool.setProperty("OutputLevel", MSG::ERROR).ignore();
+    if(muonTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(APP_NAME, "Unable to initialize the muon CP tool!");
+        return 3;
+    }
+    muonTools.push_back(muonTool.getHandle());
+	
+	asg::AnaToolHandle<ITrigGlobalEfficiencyCorrectionTool> myTool("TrigGlobalEfficiencyCorrectionTool/MyTool");
+	myTool.setProperty("ElectronEfficiencyTools",electronEffTools).ignore();
+	myTool.setProperty("ElectronScaleFactorTools",electronSFTools).ignore();
+	myTool.setProperty("MuonTools",muonTools).ignore();
+	myTool.setProperty("TriggerCombination", triggerCombination).ignore();
+	myTool.setProperty("ListOfLegsPerTool", legsPerTool).ignore();
+	myTool.setProperty("OutputLevel", quiet? MSG::WARNING : MSG::INFO).ignore();
+	if(myTool.initialize() != StatusCode::SUCCESS)
+	{
+		Error( APP_NAME, "Unable to initialize tool!" );
+		return 4;
+	}
+	
+	#ifndef XAOD_STANDALONE
+		ANA_CHECK(app->finalize())
+	#endif
+	return 0;
+}
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample5a.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample5a.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..489e198352dea8a9da6420a099a4c3d298b3b8bb
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample5a.cxx
@@ -0,0 +1,215 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// Based on CPToolTester.cxx (A. Kraznahorkay) 
+/// and ut_ath_checkTrigger_test.cxx (W. Buttinger)
+/// Contact: jmaurer@cern.ch
+/*
+ *    This example shows how to configure the tool for photon triggers. 
+ *    Here, the minimal configuration is shown (one symmetric diphoton trigger)
+ */
+
+// ROOT include(s):
+#include <TFile.h>
+#include <TError.h>
+
+// Infrastructure include(s):
+#ifdef XAOD_STANDALONE
+    #include "xAODRootAccess/Init.h"
+    #include "xAODRootAccess/TEvent.h"
+    #include "xAODRootAccess/TStore.h"
+#else
+    #include "AthAnalysisBaseComps/AthAnalysisHelper.h"
+    #include "POOLRootAccess/TEvent.h"
+#endif
+
+#include "AsgMessaging/MessageCheck.h"
+// EDM include(s):
+#include "AsgTools/AnaToolHandle.h"
+#include "EgammaAnalysisInterfaces/IAsgPhotonEfficiencyCorrectionTool.h"
+#include "TriggerAnalysisInterfaces/ITrigGlobalEfficiencyCorrectionTool.h"
+#include "xAODEventInfo/EventInfo.h"
+#include "xAODEgamma/PhotonContainer.h"
+#include "PATCore/PATCoreEnums.h"
+
+// stdlib include(s):
+#include <random>
+#include <vector>
+#include <array>
+using std::vector;
+using std::string;
+
+#define MSGSOURCE "Example 5a"
+
+ANA_MSG_HEADER(Test)
+ANA_MSG_SOURCE(Test, MSGSOURCE)
+using namespace Test;
+int main(int argc, char* argv[])
+{
+    ANA_CHECK_SET_TYPE(bool)
+    const char* filename = nullptr;
+    bool debug = false, cmdline_error = false, toys = false;
+    for(int i=1;i<argc;++i)
+    {
+        if(string(argv[i]) == "--debug") debug = true;
+        else if(string(argv[i]) == "--toys") toys = true;
+        else if(!filename && *argv[i]!='-') filename = argv[i];
+        else cmdline_error = true;
+    }
+    if(!filename || cmdline_error)
+    {
+        Error(MSGSOURCE, "No file name received!");
+        Error(MSGSOURCE, "  Usage: %s [--debug] [--toys] [DxAOD file name]", argv[0]);
+        return 1;
+    }
+    #ifdef XAOD_STANDALONE
+        xAOD::Init(MSGSOURCE).ignore();
+        TFile* file = TFile::Open(filename, "READ");
+        if(!file)
+        {
+            Error(MSGSOURCE, "Unable to open file!");
+            return 2;
+        }
+        xAOD::TEvent event(xAOD::TEvent::kClassAccess);
+        xAOD::TStore store;
+        StatusCode::enableFailure();
+    #else
+       IAppMgrUI* app = POOL::Init();
+       POOL::TEvent event(POOL::TEvent::kClassAccess);
+       TString file(filename);
+    #endif
+    event.readFrom(file).ignore();
+    Long64_t entries = event.getEntries();
+    Info(MSGSOURCE, "Number of events in the file: %lli", entries);
+
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the photon CP tools");
+    /// For property 'PhotonEfficiencyTools':
+    ToolHandleArray<IAsgPhotonEfficiencyCorrectionTool> photonEffTools;
+    /// For property 'PhotonScaleFactorTools':
+    ToolHandleArray<IAsgPhotonEfficiencyCorrectionTool> photonSFTools; 
+
+    /// RAII on-the-fly creation of photon CP tools:
+    vector<asg::AnaToolHandle<IAsgPhotonEfficiencyCorrectionTool>> factory;
+
+    const char* mapPath = "PhotonEfficiencyCorrection/2015_2018/"
+            "rel21.2/Summer2018_Rec_v1/map1.txt";
+    for(int j=0;j<2;++j) /// two instances: 0 -> MC efficiencies, 1 -> SFs
+    {
+        string name = "AsgPhotonEfficiencyCorrectionTool/" + std::string(j? "PhTrigEff" : "PhTrigSF");
+        auto t = factory.emplace(factory.end(), name);
+        t->setProperty("MapFilePath", mapPath).ignore();
+        t->setProperty("TriggerKey", string(j?"":"Eff_") + "DI_PH_2015_2016_g20_tight_2016_g22_tight_2017_2018_g20_tight_icalovloose_L1EM15VHI").ignore();
+        t->setProperty("IsoKey", "Loose").ignore();
+        t->setProperty("ForceDataType", (int)PATCore::ParticleDataType::Full).ignore();
+        if(t->initialize() != StatusCode::SUCCESS)
+        {
+            Error(MSGSOURCE, "Unable to initialize the photon CP tool <%s>!",
+                    t->name().c_str());
+            return 3;
+        }
+        auto& handles = (j? photonSFTools : photonEffTools);
+        handles.push_back(t->getHandle());
+    }
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the global trigger SF tool");
+    asg::AnaToolHandle<ITrigGlobalEfficiencyCorrectionTool> myTool("TrigGlobalEfficiencyCorrectionTool/TrigGlobal");
+    myTool.setProperty("PhotonEfficiencyTools", photonEffTools).ignore();
+    myTool.setProperty("PhotonScaleFactorTools", photonSFTools).ignore();
+    std::map<std::string, std::string> triggers;
+    triggers["266904-302872"] = "2g20_tight"; /// 2015 + 2016 periods A-D3
+    triggers["302919-311481"] = "2g22_tight"; /// 2016 periods D4-L
+    triggers["2017"] = "2g22_tight_L12EM15VHI";
+    triggers["2018"] = "2g22_tight_L12EM15VHI";
+    myTool.setProperty("TriggerCombination", triggers).ignore();
+
+    if(debug) myTool.setProperty("OutputLevel", MSG::DEBUG).ignore();
+    if(toys) myTool.setProperty("NumberOfToys", 1000).ignore();
+    if(myTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the TrigGlob tool!");
+        return 3;
+    }
+    
+    /// Uniform random run number generation spanning the target dataset.
+    /// In real life, use the PileupReweightingTool instead!
+    const unsigned periodRuns[] = {
+        /// 2015 periods D3-H, J
+        276262, 278727, 279932, 280423, 281130, 282625,
+        /// 2016 periods A3-L
+        297730, 300345, 301912, 302737, 303638, 303943, 305291, 307124, 
+        305359, 309311, 310015,
+        /// 2017 periods B-K
+        325713, 329385, 330857, 332720, 334842, 336497, 336832, 338183,
+        /// 2018 periods B-M
+        348885, 349534, 350310, 352274, 354107, 354826, 355261, 355331,
+        355529, 357050, 359191
+    };
+    std::uniform_int_distribution<unsigned> uniformPdf(0,
+            sizeof(periodRuns)/sizeof(*periodRuns) - 1);
+    std::default_random_engine randomEngine;
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Starting the event loop");
+    unsigned errors = 0;
+    double nSuitableEvents = 0., sumW = 0.;
+    for(Long64_t entry = 0; entry < entries; ++entry)
+    {
+        event.getEntry(entry);
+        
+        /// Get a random run number, and decorate the event info
+        const xAOD::EventInfo* eventInfo = nullptr;
+        event.retrieve(eventInfo,"EventInfo").ignore();
+        unsigned runNumber = periodRuns[uniformPdf(randomEngine)];
+        eventInfo->auxdecor<unsigned>("RandomRunNumber") = runNumber;
+
+        vector<const xAOD::Photon*> myTriggeringPhotons;
+        const xAOD::PhotonContainer* photons = nullptr;
+        event.retrieve(photons,"Photons").ignore();
+        for(auto photon : *photons)
+        {
+            if(!photon->caloCluster()) continue;
+            float eta = fabs(photon->caloCluster()->etaBE(2));
+            float pt = photon->pt();
+            if(pt<10e3f || eta>=2.37 || (eta>1.37 && eta<1.52)) continue;
+            int t = photon->auxdata<int>("truthType");
+            if(t!=14) continue;
+            /// photon must be above trigger threshold:
+            if(pt < (runNumber>=302919? 23e3f : 21e3f)) continue;
+            myTriggeringPhotons.push_back(photon);
+        }
+
+        /// Events must contain enough photons to trigger
+        if(myTriggeringPhotons.size() < 2) continue;
+
+        /// Finally retrieve the global trigger scale factor
+        double sf = 1.;
+        auto cc = myTool->getEfficiencyScaleFactor(myTriggeringPhotons, sf);
+        if(cc==CP::CorrectionCode::Ok)
+        {
+            nSuitableEvents += 1;
+            sumW += sf;
+        }
+        else
+        {
+            Warning(MSGSOURCE, "Scale factor evaluation failed");
+            ++errors;
+        }
+        if(errors>10)
+        {
+            Error(MSGSOURCE, "Too many errors reported!");
+            break;
+        }
+    }
+    Info(MSGSOURCE, "Average scale factor: %f (over %ld events)",
+            sumW / nSuitableEvents, long(nSuitableEvents));
+    #ifndef XAOD_STANDALONE
+		ANA_CHECK(app->finalize())
+    #endif
+    return errors? 4 : 0;
+}
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample5b.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample5b.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..c57df606d95289b2521caa502b31602a2b459e8e
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample5b.cxx
@@ -0,0 +1,233 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// Based on CPToolTester.cxx (A. Kraznahorkay) 
+/// and ut_ath_checkTrigger_test.cxx (W. Buttinger)
+/// Contact: jmaurer@cern.ch
+/*
+ *    Another example showing how to configure the tool for photon triggers. 
+ *    This time for a more complex configuration (asymmetric diphoton trigger);
+ *    see also the explanations in the example 1 as the configuration for photon triggers 
+ *    is very similar to that for electron triggers. 
+ */
+
+// ROOT include(s):
+#include <TFile.h>
+#include <TError.h>
+
+// Infrastructure include(s):
+#ifdef XAOD_STANDALONE
+    #include "xAODRootAccess/Init.h"
+    #include "xAODRootAccess/TEvent.h"
+    #include "xAODRootAccess/TStore.h"
+#else
+    #include "AthAnalysisBaseComps/AthAnalysisHelper.h"
+    #include "POOLRootAccess/TEvent.h"
+#endif
+
+#include "AsgMessaging/MessageCheck.h"
+// EDM include(s):
+#include "AsgTools/AnaToolHandle.h"
+#include "EgammaAnalysisInterfaces/IAsgPhotonEfficiencyCorrectionTool.h"
+#include "TriggerAnalysisInterfaces/ITrigGlobalEfficiencyCorrectionTool.h"
+#include "xAODEventInfo/EventInfo.h"
+#include "xAODEgamma/PhotonContainer.h"
+#include "PATCore/PATCoreEnums.h"
+
+// stdlib include(s):
+#include <random>
+#include <vector>
+#include <array>
+using std::vector;
+using std::string;
+
+#define MSGSOURCE "Example 5b"
+
+ANA_MSG_HEADER(Test)
+ANA_MSG_SOURCE(Test, MSGSOURCE)
+using namespace Test;
+int main(int argc, char* argv[])
+{
+    ANA_CHECK_SET_TYPE(bool)
+    const char* filename = nullptr;
+    bool debug = false, cmdline_error = false, toys = false;
+    for(int i=1;i<argc;++i)
+    {
+        if(string(argv[i]) == "--debug") debug = true;
+        else if(string(argv[i]) == "--toys") toys = true;
+        else if(!filename && *argv[i]!='-') filename = argv[i];
+        else cmdline_error = true;
+    }
+    if(!filename || cmdline_error)
+    {
+        Error(MSGSOURCE, "No file name received!");
+        Error(MSGSOURCE, "  Usage: %s [--debug] [--toys] [DxAOD file name]", argv[0]);
+        return 1;
+    }
+    #ifdef XAOD_STANDALONE
+        xAOD::Init(MSGSOURCE).ignore();
+        TFile* file = TFile::Open(filename, "READ");
+        if(!file)
+        {
+            Error(MSGSOURCE, "Unable to open file!");
+            return 2;
+        }
+        xAOD::TEvent event(xAOD::TEvent::kClassAccess);
+        xAOD::TStore store;
+        StatusCode::enableFailure();
+    #else
+       IAppMgrUI* app = POOL::Init();
+       POOL::TEvent event(POOL::TEvent::kClassAccess);
+       TString file(filename);
+    #endif
+    event.readFrom(file).ignore();
+    Long64_t entries = event.getEntries();
+    Info(MSGSOURCE, "Number of events in the file: %lli", entries);
+
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the photon CP tools");
+    /// For property 'PhotonEfficiencyTools':
+    ToolHandleArray<IAsgPhotonEfficiencyCorrectionTool> photonEffTools;
+    /// For property 'PhotonScaleFactorTools':
+    ToolHandleArray<IAsgPhotonEfficiencyCorrectionTool> photonSFTools; 
+    /// For property 'ListOfLegsPerTool':
+    std::map<string,string> legsPerTool;
+
+    /// RAII on-the-fly creation of photon CP tools:
+    vector<asg::AnaToolHandle<IAsgPhotonEfficiencyCorrectionTool>> factory;
+    enum{ cLEGS, cKEY };
+    vector<std::array<string,2>> toolConfigs = {
+         /// {<list of trigger legs>, <key in map file>}
+        {"g35_loose, g35_medium_L1EM20VH", "DI_PH_2015_g35_loose_2016_g35_loose_2017_g35_medium_L1EM20VH_2018_g35_medium_L1EM20VH"},
+        {"g25_loose, g25_medium_L1EM20VH", "DI_PH_2015_g25_loose_2016_g25_loose_2017_g25_medium_L1EM20VH_2018_g25_medium_L1EM20VH"}
+     };
+
+    const char* mapPath = "PhotonEfficiencyCorrection/2015_2018/"
+            "rel21.2/Summer2018_Rec_v1/map1.txt";
+    for(auto& cfg : toolConfigs) /// one instance per trigger leg x working point
+    for(int j=0;j<2;++j) /// two instances: 0 -> MC efficiencies, 1 -> SFs
+    {
+        string name = "AsgPhotonEfficiencyCorrectionTool/"
+                + ((j? "PhTrigEff_" : "PhTrigSF_")
+                + std::to_string(factory.size()/2));
+        auto t = factory.emplace(factory.end(), name);
+        t->setProperty("MapFilePath", mapPath).ignore();
+        t->setProperty("TriggerKey", string(j?"":"Eff_") + cfg[cKEY]).ignore();
+        t->setProperty("IsoKey", "Loose").ignore();
+        t->setProperty("ForceDataType", (int)PATCore::ParticleDataType::Full).ignore();
+        if(t->initialize() != StatusCode::SUCCESS)
+        {
+            Error(MSGSOURCE, "Unable to initialize the photon CP tool <%s>!",
+                    t->name().c_str());
+            return 3;
+        }
+        auto& handles = (j? photonSFTools : photonEffTools);
+        handles.push_back(t->getHandle());
+        /// Safer to retrieve the name from the final ToolHandle, it might be
+        /// prefixed (by the parent tool name) when the handle is copied
+        name = handles[handles.size()-1].name();
+        legsPerTool[name] = cfg[cLEGS];
+    }
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the global trigger SF tool");
+    asg::AnaToolHandle<ITrigGlobalEfficiencyCorrectionTool> myTool("TrigGlobalEfficiencyCorrectionTool/TrigGlobal");
+    myTool.setProperty("PhotonEfficiencyTools", photonEffTools).ignore();
+    myTool.setProperty("PhotonScaleFactorTools", photonSFTools).ignore();
+    myTool.setProperty("TriggerCombination2015", "g35_loose_g25_loose").ignore();
+    myTool.setProperty("TriggerCombination2016", "g35_loose_g25_loose").ignore();
+    myTool.setProperty("TriggerCombination2017", "g35_medium_g25_medium_L12EM20VH").ignore();
+    myTool.setProperty("TriggerCombination2018", "g35_medium_g25_medium_L12EM20VH").ignore();
+    myTool.setProperty("ListOfLegsPerTool", legsPerTool).ignore();
+
+    if(debug) myTool.setProperty("OutputLevel", MSG::DEBUG).ignore();
+    if(toys) myTool.setProperty("NumberOfToys", 1000).ignore();
+    if(myTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the TrigGlob tool!");
+        return 3;
+    }
+
+    /// Uniform random run number generation spanning the target dataset.
+    /// In real life, use the PileupReweightingTool instead!
+    const unsigned periodRuns[] = {
+        /// 2015 periods D3-H, J
+        276262, 278727, 279932, 280423, 281130, 282625,
+        /// 2016 periods A3-L
+        297730, 300345, 301912, 302737, 303638, 303943, 305291, 307124, 
+        305359, 309311, 310015,
+        /// 2017 periods B-K
+        325713, 329385, 330857, 332720, 334842, 336497, 336832, 338183,
+        /// 2018 periods B-M
+        348885, 349534, 350310, 352274, 354107, 354826, 355261, 355331,
+        355529, 357050, 359191
+    };
+    std::uniform_int_distribution<unsigned> uniformPdf(0,
+            sizeof(periodRuns)/sizeof(*periodRuns) - 1);
+    std::default_random_engine randomEngine;
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Starting the event loop");
+    unsigned errors = 0;
+    double nSuitableEvents = 0., sumW = 0.;
+    for(Long64_t entry = 0; entry < entries; ++entry)
+    {
+        event.getEntry(entry);
+        
+        /// Get a random run number, and decorate the event info
+        const xAOD::EventInfo* eventInfo = nullptr;
+        event.retrieve(eventInfo,"EventInfo").ignore();
+        unsigned runNumber = periodRuns[uniformPdf(randomEngine)];
+        eventInfo->auxdecor<unsigned>("RandomRunNumber") = runNumber;
+
+        vector<const xAOD::Photon*> myTriggeringPhotons;
+        const xAOD::PhotonContainer* photons = nullptr;
+        event.retrieve(photons,"Photons").ignore();
+        unsigned n36 = 0;
+        for(auto photon : *photons)
+        {
+            if(!photon->caloCluster()) continue;
+            float eta = fabs(photon->caloCluster()->etaBE(2));
+            float pt = photon->pt();
+            if(pt<10e3f || eta>=2.37 || (eta>1.37 && eta<1.52)) continue;
+            int t = photon->auxdata<int>("truthType");
+            if(t!=14) continue;
+            /// photon must be above trigger threshold for the softest leg:
+            if(pt < 26e3f) continue;
+            myTriggeringPhotons.push_back(photon);
+			if(pt > 36e3f) ++n36; /// also counting those suitable for the highest-pT leg
+        }
+
+        /// Events must contain enough photons to trigger
+        if(myTriggeringPhotons.size()<2 || n36<1) continue;
+
+        /// Finally retrieve the global trigger scale factor
+        double sf = 1.;
+        auto cc = myTool->getEfficiencyScaleFactor(myTriggeringPhotons, sf);
+        if(cc==CP::CorrectionCode::Ok)
+        {
+            nSuitableEvents += 1;
+            sumW += sf;
+        }
+        else
+        {
+            Warning(MSGSOURCE, "Scale factor evaluation failed");
+            ++errors;
+        }
+        if(errors>10)
+        {
+            Error(MSGSOURCE, "Too many errors reported!");
+            break;
+        }
+    }
+    Info(MSGSOURCE, "Average scale factor: %f (over %ld events)",
+            sumW / nSuitableEvents, long(nSuitableEvents));
+    #ifndef XAOD_STANDALONE
+		ANA_CHECK(app->finalize())
+    #endif
+    return errors? 4 : 0;
+}
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample6.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample6.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..e6e3b36851bf37f98b472e13aca59d5cb11ff781
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/TrigGlobEffCorrExample6.cxx
@@ -0,0 +1,220 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// Based on CPToolTester.cxx (A. Kraznahorkay) 
+/// and ut_ath_checkTrigger_test.cxx (W. Buttinger)
+/// Contact: jmaurer@cern.ch
+/*
+ *    Trigger matching example: one can use the same configuration as in other examples;
+ *    the only addition is the (mandatory) creation of a Trig::MatchingTool instance 
+ *    for the "TriggerMatchingTool" property.
+ */
+
+// ROOT include(s):
+#include <TFile.h>
+#include <TError.h>
+
+// Infrastructure include(s):
+#ifdef XAOD_STANDALONE
+    #include "xAODRootAccess/Init.h"
+    #include "xAODRootAccess/TEvent.h"
+    #include "xAODRootAccess/TStore.h"
+#else
+    #include "AthAnalysisBaseComps/AthAnalysisHelper.h"
+    #include "POOLRootAccess/TEvent.h"
+#endif
+
+#include "AsgMessaging/MessageCheck.h"
+// EDM include(s):
+#include "AsgTools/AnaToolHandle.h"
+#include "TriggerAnalysisInterfaces/ITrigGlobalEfficiencyCorrectionTool.h"
+#include "TrigConfInterfaces/ITrigConfigTool.h"
+#include "TrigDecisionTool/TrigDecisionTool.h"
+#include "TriggerMatchingTool/IMatchingTool.h"
+#include "xAODEventInfo/EventInfo.h"
+#include "xAODEgamma/ElectronContainer.h"
+#include "xAODMuon/MuonContainer.h"
+#include "PATCore/PATCoreEnums.h"
+
+// stdlib include(s):
+#include <random>
+#include <vector>
+#include <array>
+using std::vector;
+using std::string;
+
+#define MSGSOURCE "Example 6"
+// messaging
+ANA_MSG_HEADER(Test)
+ANA_MSG_SOURCE(Test, MSGSOURCE)
+using namespace Test;
+int main(int argc, char* argv[])
+{
+    ANA_CHECK_SET_TYPE(bool)
+    const char* filename = nullptr;
+    bool debug = false, fast = false, cmdline_error = false;
+    for(int i=1;i<argc;++i)
+    {
+        if(string(argv[i]) == "--debug") debug = true;
+        if(string(argv[i]) == "--fast") fast = true;
+        else if(!filename && *argv[i]!='-') filename = argv[i];
+        else cmdline_error = true;
+    }
+    if(!filename || cmdline_error)
+    {
+        Error(MSGSOURCE, "No file name received!");
+        Error(MSGSOURCE, "  Usage: %s [--debug] [--fast] [DxAOD file name]", argv[0]);
+        return 1;
+    }
+    #ifdef XAOD_STANDALONE
+        xAOD::Init(MSGSOURCE).ignore();
+        TFile* file = TFile::Open(filename, "READ");
+        if(!file)
+        {
+            Error(MSGSOURCE, "Unable to open file!");
+            return 2;
+        }
+        xAOD::TEvent event(xAOD::TEvent::kClassAccess);
+        xAOD::TStore store;
+        StatusCode::enableFailure();
+    #else
+       IAppMgrUI* app = POOL::Init();
+       POOL::TEvent event(POOL::TEvent::kClassAccess);
+       TString file(filename);
+    #endif
+    event.readFrom(file).ignore();
+    Long64_t entries = event.getEntries();
+    Info(MSGSOURCE, "Number of events in the file: %lli", entries);
+    if(fast) entries = std::min(entries, 1000LL);
+
+    /* ********************************************************************** */
+    
+    asg::AnaToolHandle<TrigConf::ITrigConfigTool> trigConfTool("TrigConf::xAODConfigTool/TrigConfig");
+    if(trigConfTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the trigger config tool!");
+        return 3;
+    }
+    asg::AnaToolHandle<Trig::TrigDecisionTool> trigDecTool("Trig::TrigDecisionTool/TrigDecision");
+    trigDecTool.setProperty("ConfigTool", trigConfTool.getHandle()).ignore();
+    trigDecTool.setProperty("TrigDecisionKey", "xTrigDecision").ignore();
+    if(trigDecTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the trigger matching tool!");
+        return 3;
+    }
+    asg::AnaToolHandle<Trig::IMatchingTool> trigMatchTool("Trig::MatchingTool/TrigMatch");
+    trigMatchTool.setProperty("TrigDecisionTool", trigDecTool.getHandle()).ignore();
+    if(trigMatchTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the trigger matching tool!");
+        return 3;
+    }
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the global trigger SF tool");
+    asg::AnaToolHandle<ITrigGlobalEfficiencyCorrectionTool> myTool("TrigGlobalEfficiencyCorrectionTool/TrigGlobal");
+    const char* triggers2015 = 
+        "mu20_iloose_L1MU15_OR_mu50"
+        "|| mu18_mu8noL1"
+        "|| e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose"
+        "|| 2e12_lhloose_L12EM10VH";
+    myTool.setProperty("TriggerCombination2015", triggers2015).ignore();
+    const char* triggers2016 = 
+        "mu26_ivarmedium_OR_mu50"
+        "|| mu22_mu8noL1"
+        "|| e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0"
+        "|| 2e17_lhvloose_nod0";
+    myTool.setProperty("TriggerCombination2016", triggers2016).ignore();
+    myTool.setProperty("TriggerMatchingTool", trigMatchTool.getHandle()).ignore();
+    if(debug) myTool.setProperty("OutputLevel", MSG::DEBUG).ignore();
+    if(myTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the TrigGlob tool!");
+        return 3;
+    }
+    
+    /// Uniform random run number generation spanning the target dataset.
+    /// In real life, use the PileupReweightingTool instead!
+    const unsigned periodRuns[] = {
+        276073, 278727, 279932, 280423, 281130, 282625, /// 2015 periods D-H, J
+        296939, 300345, 301912, 302737, 303638, 303943, 305291, 307124, 
+        305359, 309311, 310015 /// 2016 periods A-L
+    };
+    std::uniform_int_distribution<unsigned> uniformPdf(0,
+            sizeof(periodRuns)/sizeof(*periodRuns) - 1);
+    std::default_random_engine randomEngine;
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Starting the event loop");
+    unsigned errors = 0;
+    double nSuitableEvents = 0., nMatched = 0.;
+    for(Long64_t entry = 0; entry < entries; ++entry)
+    {
+        event.getEntry(entry);
+        
+        /// Get a random run number, and decorate the event info
+        const xAOD::EventInfo* eventInfo = nullptr;
+        event.retrieve(eventInfo,"EventInfo").ignore();
+        unsigned runNumber = periodRuns[uniformPdf(randomEngine)];
+        eventInfo->auxdecor<unsigned>("RandomRunNumber") = runNumber;
+        vector<const xAOD::Electron*> myTriggeringElectrons;
+        const xAOD::ElectronContainer* electrons = nullptr;
+        event.retrieve(electrons,"Electrons").ignore();
+        for(auto electron : *electrons)
+        {
+            if(!electron->caloCluster()) continue;
+            float eta = fabs(electron->caloCluster()->etaBE(2));
+            float pt = electron->pt();
+            if(pt<10e3f || eta>=2.47) continue;
+            int t = electron->auxdata<int>("truthType");
+            int o = electron->auxdata<int>("truthOrigin");
+            if(t!=2 || !(o==10 || (o>=12 && o<=22) || o==43)) continue;
+            myTriggeringElectrons.push_back(electron);
+        }
+
+        vector<const xAOD::Muon*> myTriggeringMuons;
+        const xAOD::MuonContainer* muons = nullptr;
+        event.retrieve(muons,"Muons").ignore();
+        for(auto muon : *muons)
+        {
+            float pt = muon->pt();
+            if(pt<10e3f || fabs(muon->eta())>=2.5) continue;
+            auto mt = muon->muonType();
+            if(mt!=xAOD::Muon::Combined && mt!=xAOD::Muon::MuonStandAlone) continue;
+            int t = muon->primaryTrackParticle()->auxdata<int>("truthType");
+            int o = muon->primaryTrackParticle()->auxdata<int>("truthOrigin");
+            if(t!=6 || !(o==10 || (o>=12 && o<=22) || o==43)) continue;
+            myTriggeringMuons.push_back(muon);
+        }
+
+        /// Events must contain at least one lepton above trigger threshold
+        if(myTriggeringElectrons.size()+myTriggeringMuons.size() < 1) continue;
+        nSuitableEvents += 1;
+        
+        bool matched = false;
+        if(myTool->checkTriggerMatching(matched, myTriggeringElectrons, myTriggeringMuons) != CP::CorrectionCode::Ok)
+        {
+            Error(MSGSOURCE, "trigger matching could not be checked, interrupting execution");
+            ++errors;
+            break;
+        }
+            
+        if(matched) nMatched += 1;
+    }
+    
+    if(errors < nSuitableEvents)
+    {
+        Info(MSGSOURCE, "Fraction of trigger-matched events: %f (over %ld events)",
+            nMatched / nSuitableEvents, long(nSuitableEvents));
+    }
+    
+    #ifndef XAOD_STANDALONE
+		ANA_CHECK(app->finalize())
+    #endif
+    return errors? 4 : 0;
+}
+
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/comments.txt b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/comments.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e769012cf6630b174491dfa8935db392f801abe8
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/comments.txt
@@ -0,0 +1,108 @@
+[example0] ###################################################################
+   Simple example: single electron or single muon trigger, using different 
+configurations for 2015 and 2016.
+
+[example1] ###################################################################
+   Another example: combination of single-lepton, dielectron and dimuon
+triggers, using different configurations for 2015 and 2016, and illustrating
+how to fill the property 'ListOfLegsPerTool'.
+
+[example2] ###################################################################
+   Similar to example 1 (combination of single-lepton, dielectron and dimuon
+triggers), but showing how to use different triggers in different data-taking
+periods, and how to modify some of the trigger pT thresholds.
+
+[example3com] ################################################################
+   The set of examples 3a - 3e illustrates the use of lepton selection tags
+for various scenarios:
+
+- Example 3a: trigger = 2e12_lhloose_L12EM10VH, selecting events containing
+              >=2 loose electrons, the leading-pT electron always satisfying
+              in addition tight PID+isolation requirements.
+
+- Example 3b: trigger = e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose
+                        || 2e12_lhloose_L12EM10VH
+              selecting events with >=2 loose electrons where the leading-pT
+              electron also satisfies medium PID requirements.
+              Only the latter is allowed to fire the single-electron trigger.
+
+- Example 3c: trigger = e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose
+                        || 2e12_lhloose_L12EM10VH
+              selecting events with >=2 loose electrons. Any electron also
+              satisfying medium PID requirements is allowed to fire the
+              single-electron trigger.
+
+- Example 3d: trigger = 2e17_lhvloose_nod0 || e7_lhmedium_nod0_mu24
+      || e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0
+              same type of selection as example 3c but with 3 PID working
+              points, using two distinct decorations.
+
+- Example 3e: same scenario as example 3d, but using an alternative
+              implementation that requires only one decoration.
+
+[example3a] ##################################################################
+   For this example (3a), one needs to set up two versions of the electron
+trigger tools: one configured for offline tight PID+isolation, to be used for
+the leading electron, and another configured for offline loose PID, to be
+used for subleading electrons.
+
+   The configuration of the TrigGlobalEfficiencyCorrectionTool then involves 
+filling two additional properties, 'ListOfTagsPerTool' and
+'LeptonTagDecorations'. We also fill a third one, 'ElectronLegsPerTag';
+it isn't required but still advised as the tool can then perform further
+consistency checks of its configuration.",  
+
+   The leading electron will always be tagged with a 'Signal' decoration
+(decorated value set to 1), while subleading electrons are never tagged 
+(-> decorated value set to 0).
+   Since efficiencies provided by CP tools are inclusive, one should NEVER
+ tag the subleading leptons as 'Signal', even if they satisfy the tight PID
++ isolation requirements; doing so would bias the results.
+   See example 3c that deals with a more complicate situation requiring
+handling multiple PID levels for all leptons.
+
+[example3b] ##################################################################
+   For this example (3b), one needs to set up three versions of the electron
+trigger tools: two configured for offline tight PID+isolation, to be used for
+the leading electron (one for the trigger leg e24_xxx, another for the leg
+e12_xxx), and another configured for offline loose PID (for the leg e12_xxx)
+to be used for subleading electrons.
+
+   The configuration of the TrigGlobalEfficiencyCorrectionTool is very similar
+to the example 3a (e.g. the decoration of electrons is identical), please
+refer to that example for more details.
+   Here, in addition, one needs to let the tool know, via the property
+'ListOfLegsPerTag', that only 'Signal'-tagged electrons are allowed to fire
+the e24_xxx leg.
+
+[example3c] ##################################################################
+   For this example (3c), any electron might be tagged as 'Signal' if it
+passes MediumLH selection (here emulated with a random number). Electron tools
+providing the efficiency/SF for the e24_xxx leg are configured with the
+MediumLH working point. For the tools providing efficiency/SF for the e12_xxx
+leg however, one SHOULD NOT set up one version for 'Medium'-tagged electrons
+and another version for untagged electrons. This is because for the latter
+one would need to configure the tools for a LooseLH-but-not-MediumLH working
+point, which is not provided by egamma. Instead, we use a single version,
+configured with the LooseLH working point, and instruct the TrigGlob tool to
+use that version for both 'Medium'-tagged and untagged electrons. In this way,
+no bias is introduced. ",
+   The configuration of the TrigGlobalEfficiencyCorrectionTool is otherwise
+essentially the same as for the example 3b, please refer to that example for
+more details.
+    
+[example3d] ##################################################################
+   We use two decorations, 'MyMedium' and 'MyTight'
+Note that the TrigGlob tool considers only one single tag per electron. When
+an electron has >=2 non-zero decorations (e.g. 'MyMedium' + 'MyTight'),
+the tag associated to the electron by the tool is chosen as the first one
+appearing in the list of decorations provided in the 'LeptonTagDecorations'
+property.
+    
+[example3e] ##################################################################
+   We use a single decoration named 'PID', and rely on the decorated value to
+indicate whether the electron passes TightLH PID + isolation (=> value = 2)
+or only MediumLH PID (=> value = 1). The TrigGlob tool then forms tags
+by suffixing the decorated value to the decoration name, i.e. 'PID1' and
+'PID2'. One must then use the latter tags in the properties 'ListOfLegsPerTag'
+and 'ListOfTagsPerTool'.
\ No newline at end of file
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/cptools_config.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/cptools_config.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..456911fd5465d4ac9f31a378d3cb130e2b011854
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/cptools_config.cxx
@@ -0,0 +1,160 @@
+[example0:electron_properties_set] #############################################
+        t->setProperty("TriggerKey", string(j?"":"Eff_")
+			+ "SINGLE_E_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_2017_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0").ignore();
+        t->setProperty("IdKey", "Tight").ignore();
+        t->setProperty("IsoKey", "FixedCutTightTrackOnly").ignore();
+	
+[example1and2:electron_extraproperties_declare] ################################
+    /// For property 'ListOfLegsPerTool':
+    std::map<string,string> legsPerTool;
+
+[example1:electron_toolconfigs] ############################################
+    enum{ cLEGS, cKEY };
+    vector<std::array<string,2>> toolConfigs = {
+         /// {<list of trigger legs>, <key in map file>}
+         /// Single-electron trigger (same tool instance for 2015 and 2016):
+        {"e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose, e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0",
+            "SINGLE_E_2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose_2016_2017_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0"}, 
+        /// Dielectron trigger (same tool instance for 2015-2017):
+        {"e12_lhloose_L1EM10VH, e17_lhvloose_nod0, e24_lhvloose_nod0_L1EM20VH", 
+            "DI_E_2015_e12_lhloose_L1EM10VH_2016_e17_lhvloose_nod0_2017_e24_lhvloose_nod0_L1EM20VH"}
+     };
+
+[example2:electron_toolconfigs] ############################################
+    enum{ cLEGS, cKEY };
+    vector<std::array<string,2>> toolConfigs = {
+         /// {<list of trigger legs>, <key in map file>}
+         /// Single-electron trigger (different instances for 2015-2017):
+        {"e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose", "2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose"}, 
+        {"e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0 [2016]", "2016_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0"}, 
+        {"e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0 [2017]", "2017_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0"}, 
+        /// the above splitting per year is just for illustration -- better
+        ///  to use a single instance for 2015-2017, as in the Example 1
+        /// Dielectron trigger (same tool instance for 2015-2017):
+        {"e12_lhloose_L1EM10VH, e17_lhvloose_nod0, e24_lhvloose_nod0_L1EM20VH", 
+            "DI_E_2015_e12_lhloose_L1EM10VH_2016_e17_lhvloose_nod0_2017_e24_lhvloose_nod0_L1EM20VH"}
+     };
+
+[example1and2:electron_properties_set] #########################################
+        t->setProperty("TriggerKey", string(j?"":"Eff_") + cfg[cKEY]).ignore();
+        t->setProperty("IdKey", "Tight").ignore();
+        t->setProperty("IsoKey", "FixedCutTightTrackOnly").ignore();
+
+[example1and2:electron_extraproperties_fill] ###################################
+        /// Safer to retrieve the name from the final ToolHandle, it might be
+        /// prefixed (by the parent tool name) when the handle is copied
+		name = handles[handles.size()-1].name();
+		legsPerTool[name] = cfg[cLEGS];
+
+[example3com:electron_extraproperties_declare] #################################
+    /// For property 'ListOfLegsPerTool':
+	std::map<std::string,std::string> legsPerTool;
+    /// For property 'ListOfTagsPerTool':
+	std::map<std::string,std::string> tagsPerTool;
+    /// For property 'ElectronLegsPerTag':
+	std::map<std::string,std::string> legsPerTag;
+  
+[example3a3b:electron_extraproperties_declare] #################################
++[example3com:electron_extraproperties_declare]
+    /// To tag the leading electron as 'Signal'
+    SG::AuxElement::Decorator<char> dec_signal("Signal");
+
+[example3c:electron_extraproperties_declare] ###################################
++[example3com:electron_extraproperties_declare]
+    /// To tag electron(s) as 'Signal'
+    SG::AuxElement::Decorator<char> dec_signal("Signal");
+    /// To emulate PID selection (90% loose-to-medium efficiency)
+    std::bernoulli_distribution bernoulliPdf(0.9);
+
+[example3d:electron_extraproperties_declare] ###################################
++[example3com:electron_extraproperties_declare]
+    /// To tag electron(s) as 'MyMedium' and 'MyTight'
+    SG::AuxElement::Decorator<char> dec_medium("MyMedium");
+    SG::AuxElement::Decorator<char> dec_tight("MyTight");
+    /// To emulate PID selection (90% loose-to-medium/medium-to-tight eff.)
+    std::bernoulli_distribution bernoulliPdf(0.9);
+ 
+[example3e:electron_extraproperties_declare] ###################################
++[example3com:electron_extraproperties_declare]
+    /// To tag electrons according to the PID criteria they fulfil
+    SG::AuxElement::Decorator<char> dec_pid("PID");
+    /// To emulate PID selection (90% loose-to-medium/medium-to-tight eff.)
+    std::bernoulli_distribution bernoulliPdf(0.9);
+ 
+[example3a:electron_toolconfigs] ###############################################
+    enum{ cLEGS, cTAG, cKEY, cPID, cISO };
+    std::vector<std::array<std::string,5> > toolConfigs = {
+        /// <list of legs>, <list of tags>, <key in map file>, <PID WP>, <iso WP>
+        /// For leading electron:
+        {"e12_lhloose_L1EM10VH", "Signal", "2015_e12_lhloose_L1EM10VH", "Tight", "FixedCutTightTrackOnly"},
+        /// For subleading electron(s):
+        {"e12_lhloose_L1EM10VH", "*", "2015_e12_lhloose_L1EM10VH", "LooseBLayer", ""}
+    };
+        
+[example3b:electron_toolconfigs] ###############################################
+    enum{ cLEGS, cTAG, cKEY, cPID, cISO };
+    std::vector<std::array<std::string,5> > toolConfigs = {
+		/// <list of legs>, <list of tags>, <key in map file>, <PID WP>, <iso WP>
+        /// Single electron trigger, only for the leading electron ("Signal")
+		{"e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose", "Signal", "2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose", "Tight", "FixedCutTightTrackOnly"}, 
+		/// Dielectron trigger, for the leading electron ("Signal")
+		{"e12_lhloose_L1EM10VH", "Signal", "2015_e12_lhloose_L1EM10VH", "Tight", "FixedCutTightTrackOnly"}, 
+		/// Dielectron trigger, for subleading electron(s) (not tagged => "*")
+		{"e12_lhloose_L1EM10VH", "*", "2015_e12_lhloose_L1EM10VH", "LooseBLayer", ""}
+     };
+        
+[example3c:electron_toolconfigs] ###############################################
+    enum{ cLEGS, cTAG, cKEY, cPID, cISO };
+    std::vector<std::array<std::string,5> > toolConfigs = {
+		/// <list of legs>, <list of tags>, <key in map file>, <PID WP>, <iso WP>
+        /// Single electron trigger: electrons tagged 'Signal'
+		{"e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose", "Signal", "2015_e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose", "Medium", ""}, 
+		/// Dielectron trigger: all electrons (tagged or not)
+		{"e12_lhloose_L1EM10VH", "*,Signal", "2015_e12_lhloose_L1EM10VH", "LooseBLayer", ""}
+     };
+        
+[example3d:electron_toolconfigs] ###############################################
+    enum{ cLEGS, cTAG, cKEY, cPID, cISO };
+    std::vector<std::array<std::string,5> > toolConfigs = {
+		/// <list of legs>, <list of tags>, <key in map file>, <PID WP>, <iso WP>
+		/// Single-electron trigger: electrons tagged 'MyTight'
+		{"e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0", "MyTight", 
+			"2016_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0", "Tight", "GradientLoose"},
+		/// Electron-muon trigger: electrons tagged 'MyTight' or 'MyMedium'
+		{"e7_lhmedium_nod0", "MyMedium,MyTight", "2016_e7_lhmedium_nod0", "Medium", ""},
+		/// Dielectron trigger: all electrons (tagged or not)
+		{"e17_lhvloose_nod0", "*,MyMedium,MyTight", "2016_e17_lhvloose_nod0", "LooseBLayer", ""},
+     };
+        
+[example3e:electron_toolconfigs] ###############################################
+    enum{ cLEGS, cTAG, cKEY, cPID, cISO };
+    std::vector<std::array<std::string,5> > toolConfigs = {
+		/// <list of legs>, <list of tags>, <key in map file>, <PID WP>, <iso WP>
+		/// Single-electron trigger: only electrons tagged 'PID2' (TightLH+iso) 
+		{"e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0", "PID2", 
+			"2016_e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0", "Tight", "GradientLoose"},
+		/// Electron-muon trigger: electrons tagged 'PID2' or 'PID1' (MediumLH)
+		{"e7_lhmedium_nod0", "PID1,PID2", "2016_e7_lhmedium_nod0", "Medium", ""},
+		/// Dielectron trigger: all electrons (tagged or not)
+		{"e17_lhvloose_nod0", "*,PID1,PID2", "2016_e17_lhvloose_nod0", "LooseBLayer", ""},
+     };
+        
+[example3:electron_properties_set] #############################################
+        t->setProperty("TriggerKey", string(j?"":"Eff_") + cfg[cKEY]).ignore();
+        t->setProperty("IdKey", cfg[cPID]).ignore();
+        t->setProperty("IsoKey", cfg[cISO]).ignore();
+        
+[example3:electron_extraproperties_fill] #######################################
+        /// Safer to retrieve the name from the final ToolHandle, it might be
+        /// prefixed (by the parent tool name) when the handle is copied    
+        name = handles[handles.size()-1].name();
+        legsPerTool[name] = cfg[cLEGS];
+        tagsPerTool[name] = cfg[cTAG];
+        if(!j)
+        {
+            for(auto& tag : ::split_comma_delimited(cfg[cTAG]))
+            {
+                if(legsPerTag[tag]=="") legsPerTag[tag] = cfg[cLEGS];
+                else legsPerTag[tag] += "," + cfg[cLEGS];
+            }
+        }
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/eventloop.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/eventloop.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..56865211a6ab2130ed2d91fb9acadbb235189b83
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/eventloop.cxx
@@ -0,0 +1,166 @@
+[example0:electron_selection] ##################################################
+            /// lepton must be above trigger threshold:
+            if(pt < (runNumber>290000? 27e3f : 25e3f)) continue;
+
+[example0:muon_selection] ######################################################
+            /// lepton must be above trigger threshold:
+            if(pt < (runNumber>290000? 27.3e3f : 21e3f)) continue;
+
+[example0:trigger_matching] ####################################################
+        /// Events must contain at least one lepton above trigger threshold
+        if(myTriggeringElectrons.size()+myTriggeringMuons.size() < 1) continue;
+
+[example1and2:trigcounters] ####################################################
+        unsigned nTrig1L = 0, nTrig2mu = 0;
+
+[example1and2:electron_selection] ##############################################
+            /// lepton must be above softest trigger threshold:
+            if((runNumber>320000 && pt<25e3f) /// 2017: 2e24
+				|| (runNumber>290000 && pt<18e3f) /// 2016: 2e17
+				|| (pt<13e3f)) continue; /// 2015: 2e12
+            /// also count leptons above single-lepton trigger threshold
+            if(pt >= (runNumber>290000? 27e3f : 25e3f)) ++nTrig1L;
+
+[example1:muon_selection] ######################################################
+            /// lepton must be above softest trigger threshold (mu8noL1 here):
+            if(pt < 10e3f) continue;
+            /// also count leptons above single-lepton trigger threshold
+            if(pt >= (runNumber>290000? 27.3e3f : 21e3f)) ++nTrig1L;
+            // and count muons suitable for the hard leg of the dimuon trigger
+            if(pt >= (runNumber>290000? 23e3f : 19e3f)) ++nTrig2mu;
+        
+[example2:muon_selection] ######################################################
+            /// lepton must be above softest trigger threshold (mu8noL1 here):
+            if(pt < 10e3f) continue;
+            /// also count leptons above single-lepton trigger threshold
+            if(pt >= (runNumber>290000? 27.3e3f : 21e3f)) ++nTrig1L;
+            /// and count muons suitable for the hard leg of the dimuon trigger
+            if(pt >= 25e3f) ++nTrig2mu;
+
+[example1:trigger_matching] ####################################################
+        /// Events must contain enough leptons to trigger
+        if(nTrig1L==0 /// single-lepton trigger
+            && myTriggeringElectrons.size()<2 /// dielectron
+            && (nTrig2mu==0 || myTriggeringMuons.size()<2)) /// dimuon
+        {
+            continue;
+        }
+
+[example2:trigger_matching] ####################################################
+        /// Events must contain enough leptons to trigger
+        if((nTrig1L==0 || (runNumber>=303638 && runNumber<320000)) /// single-lepton trigger (when used)
+            && myTriggeringElectrons.size()<2 /// dielectron
+            && (nTrig2mu==0 || myTriggeringMuons.size()<2)) /// dimuon
+        {
+            continue;
+        }
+
+[example3d3e:trigcounters] #####################################################
+        unsigned nTrig_e26 = 0, nTrig_e7 = 0, nTrig_e17 = 0;
+
+[example3a3b3c:electron_selection] #############################################
+            /// electron must be above softest trigger threshold (e12 here)
+            if(pt < 13e3f) continue;
+        
+[example3d3e:electron_selection] ###############################################
+            /// electron must be above softest trigger threshold (e7 here
+            if(pt < 7e3f) continue;
+            if(pt >= 18e3f) ++nTrig_e17;
+        
+[example3d3e:muon_selection] ###################################################
+            /// muon must be above softest trigger threshold (mu24 here)
+            if(pt < 25.2e3f) continue;
+        
+[example3a3b:tagging] ##########################################################
+        /// Let's pretend that the leading electron passes TightLH+isolation,
+		/// thus the event passes the selection; now we tag the electrons:
+        auto compareByPt = [](const xAOD::Electron*e1, const xAOD::Electron*e2)
+            { return e1->pt() < e2->pt(); };
+        auto leadingElectron = *std::max_element(myTriggeringElectrons.begin(),
+            myTriggeringElectrons.end(), compareByPt);
+		for(auto electron : myTriggeringElectrons)
+        {
+            /// Leading electron tagged as 'Signal' -> decorated value set to 1
+            /// Subleading electron(s) not tagged -> decorated value set to 0
+            dec_signal(*electron) = (electron==leadingElectron)? 1 : 0;
+        }
+        
+[example3a:trigger_matching] ###################################################
+        /// Events must contain at least two lepton above trigger threshold
+        if(myTriggeringElectrons.size() < 2) continue;
+
++[example3a3b:tagging]
+        
+[example3b:trigger_matching] ###################################################
+        if(myTriggeringElectrons.size() < 1) continue;
+        
++[example3a3b:tagging]
+        
+        /// Events must contain enough leptons to trigger
+        if(leadingElectron->pt() < 25e3f /// single-electron trigger
+            && myTriggeringElectrons.size() < 2) /// dielectron
+        {
+            continue;
+        }
+        
+[example3c:trigger_matching] ###################################################
+        /// Add 'Signal' decorations to random electrons
+        /// also count 'Signal' electrons above e24_xxx threshold
+        unsigned nTrig1L = 0;
+		for(auto electron : myTriggeringElectrons)
+        {
+            bool signal = bernoulliPdf(randomEngine);
+            dec_signal(*electron) = signal? 1 : 0;
+            if(signal && electron->pt()>25e3f) ++nTrig1L;
+        }
+        
+        /// Events must contain enough leptons to trigger
+        if(nTrig1L < 1 /// single-electron trigger
+            && myTriggeringElectrons.size() < 2) /// dielectron
+        {
+            continue;
+        }
+        
+[example3d:trigger_matching] ###################################################
+        /// Add 'MyMedium' & 'MyTight' decorations to random electrons
+        /// also count tight electrons above e26_xxx threshold
+        /// and medium electrons above e7_xxx threshold
+		for(auto electron : myTriggeringElectrons)
+        {
+            bool medium = bernoulliPdf(randomEngine);
+            dec_medium(*electron) = medium? 1 : 0;
+            if(medium && electron->pt()>8e3f) ++nTrig_e7;
+            bool tight = medium && bernoulliPdf(randomEngine);
+            dec_tight(*electron) = tight? 1 : 0;
+            if(tight && electron->pt()>27e3f) ++nTrig_e26;
+        }
+        
+        /// Events must contain enough leptons to trigger
+        if(nTrig_e26 < 1 /// single-electron trigger
+            && (nTrig_e7==0 || myTriggeringMuons.size()==0) /// electron-muon
+            && nTrig_e17 < 2) /// dielectron
+        {
+            continue;
+        }
+        
+[example3e:trigger_matching] ###################################################
+        /// Add 'PID' decorations to random electrons
+        /// also count 'Tight' electrons above e26_xxx threshold
+        /// and 'Medium' electrons above e7_xxx threshold
+		for(auto electron : myTriggeringElectrons)
+        {
+            bool medium = bernoulliPdf(randomEngine);
+            bool tight = medium && bernoulliPdf(randomEngine);
+            /// Set decorated value to 2 for TightLH+iso or 1 for MediumLH
+            dec_pid(*electron) = tight? 2 : medium? 1 : 0;
+            if(medium && electron->pt()>8e3f) ++nTrig_e7;
+            if(tight && electron->pt()>27e3f) ++nTrig_e26;
+        }
+        
+        /// Events must contain enough leptons to trigger
+        if(nTrig_e26 < 1 /// single-electron trigger
+            && (nTrig_e7==0 || myTriggeringMuons.size()==0) /// electron-muon
+            && nTrig_e17 < 2) /// dielectron
+        {
+            continue;
+        }
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/generateExamples.py b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/generateExamples.py
new file mode 100755
index 0000000000000000000000000000000000000000..76d5ea619ae66f1a52b3a7727a0f6b2a9d2b691b
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/generateExamples.py
@@ -0,0 +1,221 @@
+#!/usr/bin/env python
+import copy, re, os, sys
+
+wdir = sys.argv[0].replace('generateExamples.py', '')
+if not os.path.isabs(wdir): wdir = os.getcwd() + '/' + wdir
+
+def import_skeleton(discarded_blocks = []):
+    filename = 'skeleton.cxx'
+    discard = False
+    first_discarded_line = 0
+    blocks = []
+    buffer = ''
+    with open(wdir + filename,'r') as f:
+        linenumber = 0
+        for line in f:
+            linenumber += 1
+            if line.startswith('['):
+                opening = (line[1] != '/')
+                if ']' not in line:
+                    print "Parsing error on %s line %d: character ']' not found"%(filename,linenumber)
+                    exit(-1)
+                token = line.split(']')[0][(1 if opening else 2):]
+                if opening:
+                    if token in blocks:
+                        print "Parsing error on %s line %d: request for opening block [%s] which is already open"%(filename,linenumber,token)
+                        exit(-1)
+                    blocks.append(token)
+                else:
+                    if len(blocks)==0 or blocks[-1] != token:
+                        print "Parsing error on %s line %d: request for closing block [%s] which either isn't open or isn't the last one opened"%(filename,linenumber,token)
+                        exit(-1)
+                    blocks.pop()
+                will_discard = any(b in discarded_blocks for b in blocks)
+                if discard and not will_discard:
+                    print '   discarded lines %d - %d from block [%s]'%(first_discarded_line,linenumber,token)
+                elif not discard and will_discard:
+                    first_discarded_line = linenumber
+                discard = will_discard
+                continue
+            if discard: continue
+            if 'DATA2015' in blocks: comma = ',' if ('DATA2016' not in discarded_blocks or 'DATA2017' not in discarded_blocks) else ''
+            elif 'DATA2016' in blocks: comma = ',' if ('DATA2017' not in discarded_blocks) else ''
+            elif 'DATA2017' in blocks: comma = ''
+            else: comma = None
+            if comma is not None: line = line.replace(' MAYBE_COMMA', comma)
+            buffer += line
+    return buffer
+
+def import_stuffing(src, tag, comment_out = False):
+	lines = None
+	with open(wdir + src, 'r') as f:
+		for line in f:
+			if line.startswith('[example%s]'%(tag)): lines = ['/*\n'] if comment_out else []
+			elif lines is not None:
+				if line.startswith('[example'): break
+				if line.startswith('+[example') and not comment_out: lines.append(import_stuffing(src, line[9:].split(']')[0], False))
+				else: lines.append((' * ' if comment_out else '') + line)
+	if lines is None or len(lines)==0:
+		print 'ERROR: unable to import example' + tag + ' when parsing ' + src
+		exit(-1)
+	if comment_out: lines.append(' */')
+	elif lines[-1].isspace(): del lines[-1]
+	return ''.join(lines)
+	
+def import_comments(tag):
+	return import_stuffing('comments.txt', tag, True)
+	
+def import_trigglob_config(tag):
+	return import_stuffing('trigglob_config.cxx', tag, False)
+
+def import_cptools_config(tag):
+	return import_stuffing('cptools_config.cxx', tag, False)
+
+def import_eventloop(tag):
+	return import_stuffing('eventloop.cxx', tag, False)
+	
+def safe_format(txt, replacements):
+    newtxt = txt.replace('{','{{').replace('}','}}')
+    for token in replacements:
+        newtxt = newtxt.replace('{{'+token+'}}','{'+token+'}')
+    newtxt = newtxt.format(**replacements).replace('\t','    ')
+    newtxt = re.sub(r'\n\s*?DELETE\-THIS\-LINE\s*?\n','\n',newtxt)
+    if newtxt.count('DELETE-THIS-LINE') != 0:
+        print 'ERROR: not all DELETE-THIS-LINE symbols were replaced'
+    return newtxt
+    
+replacements = {
+    'head_comments': 'DELETE-THIS-LINE', 
+    'example_number': 'DELETE-THIS-LINE',
+    'electron_extraproperties_declare': 'DELETE-THIS-LINE', 
+    'electron_toolconfigs': 'DELETE-THIS-LINE', 
+    'electron_tools_properties_set': 'DELETE-THIS-LINE',
+    'electron_extraproperties_fill': 'DELETE-THIS-LINE',
+    'muon_extraproperties_fill': 'DELETE-THIS-LINE',
+    'trigglob_properties_set': 'DELETE-THIS-LINE',
+    'eventloop_trigcounters': 'DELETE-THIS-LINE',
+    'eventloop_electron_selection': 'DELETE-THIS-LINE',
+    'eventloop_muon_selection': 'DELETE-THIS-LINE',
+    'trigger_matching_requirements': 'DELETE-THIS-LINE',
+}
+
+def write_example(subs,tokens=[]):
+    path = wdir + '/../'
+    txt = import_skeleton(tokens) # 'ELECTRONS', 'MUONS', 'MULTITRIGGERS', '2016_RUNS'
+    txt = safe_format(txt,r)
+    filename = 'TrigGlobEffCorrExample%s.cxx'%(subs['example_number'])
+    dest = path + filename
+    writeCXX = True
+    if os.path.exists(dest):
+        with open(dest,'r') as f:
+            oldtxt = f.read()
+            if txt==oldtxt:
+                print "INFO: %s didn't change, it won't be overwritten"%(filename)
+                writeCXX = False
+    if writeCXX:
+        with open(dest,'w') as f:
+            f.write(txt)
+
+r = copy.deepcopy(replacements)
+r['head_comments'] = import_comments('0')
+r['example_number'] = '0'
+r['electron_tools_properties_set'] = import_cptools_config('0:electron_properties_set')
+r['trigglob_properties_set'] = import_trigglob_config('0')
+r['eventloop_electron_selection'] = import_eventloop('0:electron_selection')
+r['eventloop_muon_selection'] = import_eventloop('0:muon_selection')
+r['trigger_matching_requirements'] = import_eventloop('0:trigger_matching')
+write_example(r, ['MULTITRIGGERS','SPLITFUNC'])
+
+
+r = copy.deepcopy(replacements)
+r['head_comments'] = import_comments('1')
+r['example_number'] = '1'
+r['electron_extraproperties_declare'] = import_cptools_config('1and2:electron_extraproperties_declare')
+r['electron_toolconfigs'] = import_cptools_config('1:electron_toolconfigs')
+r['electron_tools_properties_set'] = import_cptools_config('1and2:electron_properties_set')
+r['electron_extraproperties_fill'] = import_cptools_config('1and2:electron_extraproperties_fill')
+r['trigglob_properties_set'] = import_trigglob_config('1')
+r['eventloop_trigcounters'] = import_eventloop('1and2:trigcounters')
+r['eventloop_electron_selection'] = import_eventloop('1and2:electron_selection')
+r['eventloop_muon_selection'] = import_eventloop('1:muon_selection')
+r['trigger_matching_requirements'] = import_eventloop('1:trigger_matching')
+write_example(r,['SPLITFUNC'])
+
+
+r = copy.deepcopy(replacements)
+r['head_comments'] = import_comments('2')
+r['example_number'] = '2'
+r['electron_extraproperties_declare'] = import_cptools_config('1and2:electron_extraproperties_declare')
+r['electron_toolconfigs'] = import_cptools_config('2:electron_toolconfigs')
+r['electron_tools_properties_set'] = import_cptools_config('1and2:electron_properties_set')
+r['electron_extraproperties_fill'] = import_cptools_config('1and2:electron_extraproperties_fill')
+r['trigglob_properties_set'] = import_trigglob_config('2')
+r['eventloop_trigcounters'] = import_eventloop('1and2:trigcounters')
+r['eventloop_electron_selection'] = import_eventloop('1and2:electron_selection')
+r['eventloop_muon_selection'] = import_eventloop('2:muon_selection')
+r['trigger_matching_requirements'] = import_eventloop('2:trigger_matching')
+write_example(r,['SPLITFUNC'])
+
+r = copy.deepcopy(replacements)
+r['head_comments'] = import_comments('3com') + '\n' + import_comments('3a')
+r['example_number'] = '3a'
+r['electron_extraproperties_declare'] = import_cptools_config('3a3b:electron_extraproperties_declare')
+r['electron_toolconfigs'] = import_cptools_config('3a:electron_toolconfigs')
+r['electron_tools_properties_set'] = import_cptools_config('3:electron_properties_set')
+r['electron_extraproperties_fill'] = import_cptools_config('3:electron_extraproperties_fill')
+r['trigglob_properties_set'] = import_trigglob_config('3a')
+r['eventloop_electron_selection'] = import_eventloop('3a3b3c:electron_selection')
+r['trigger_matching_requirements'] = import_eventloop('3a:trigger_matching')
+write_example(r,['MUONS','DATA2016','DATA2017'])
+
+r = copy.deepcopy(replacements)
+r['head_comments'] = import_comments('3com') + '\n' + import_comments('3b')
+r['example_number'] = '3b'
+r['electron_extraproperties_declare'] = import_cptools_config('3c:electron_extraproperties_declare')
+r['electron_toolconfigs'] = import_cptools_config('3b:electron_toolconfigs')
+r['electron_tools_properties_set'] = import_cptools_config('3:electron_properties_set')
+r['electron_extraproperties_fill'] = import_cptools_config('3:electron_extraproperties_fill')
+r['trigglob_properties_set'] = import_trigglob_config('3b3c')
+r['eventloop_electron_selection'] = import_eventloop('3a3b3c:electron_selection')
+r['trigger_matching_requirements'] = import_eventloop('3b:trigger_matching')
+write_example(r,['MUONS','DATA2016','DATA2017'])
+
+r = copy.deepcopy(replacements)
+r['head_comments'] = import_comments('3com') + '\n' + import_comments('3c')
+r['example_number'] = '3c'
+r['electron_extraproperties_declare'] = import_cptools_config('3c:electron_extraproperties_declare')
+r['electron_toolconfigs'] = import_cptools_config('3c:electron_toolconfigs')
+r['electron_tools_properties_set'] = import_cptools_config('3:electron_properties_set')
+r['electron_extraproperties_fill'] = import_cptools_config('3:electron_extraproperties_fill')
+r['trigglob_properties_set'] = import_trigglob_config('3b3c')
+r['eventloop_electron_selection'] = import_eventloop('3a3b3c:electron_selection')
+r['trigger_matching_requirements'] = import_eventloop('3c:trigger_matching')
+write_example(r,['MUONS','DATA2016','DATA2017'])
+
+r = copy.deepcopy(replacements)
+r['head_comments'] = import_comments('3com') + '\n' + import_comments('3d')
+r['example_number'] = '3d'
+r['electron_extraproperties_declare'] = import_cptools_config('3d:electron_extraproperties_declare')
+r['electron_toolconfigs'] = import_cptools_config('3d:electron_toolconfigs')
+r['electron_tools_properties_set'] = import_cptools_config('3:electron_properties_set')
+r['electron_extraproperties_fill'] = import_cptools_config('3:electron_extraproperties_fill')
+r['trigglob_properties_set'] = import_trigglob_config('3d')
+r['eventloop_trigcounters'] = import_eventloop('3d3e:trigcounters')
+r['eventloop_electron_selection'] = import_eventloop('3d3e:electron_selection')
+r['eventloop_muon_selection'] = import_eventloop('3d3e:muon_selection')
+r['trigger_matching_requirements'] = import_eventloop('3d:trigger_matching')
+write_example(r,['DATA2015','DATA2017'])
+
+r = copy.deepcopy(replacements)
+r['head_comments'] = import_comments('3com') + '\n' + import_comments('3e')
+r['example_number'] = '3e'
+r['electron_extraproperties_declare'] = import_cptools_config('3e:electron_extraproperties_declare')
+r['electron_toolconfigs'] = import_cptools_config('3e:electron_toolconfigs')
+r['electron_tools_properties_set'] = import_cptools_config('3:electron_properties_set')
+r['electron_extraproperties_fill'] = import_cptools_config('3:electron_extraproperties_fill')
+r['trigglob_properties_set'] = import_trigglob_config('3e')
+r['eventloop_trigcounters'] = import_eventloop('3d3e:trigcounters')
+r['eventloop_electron_selection'] = import_eventloop('3d3e:electron_selection')
+r['eventloop_muon_selection'] = import_eventloop('3d3e:muon_selection')
+r['trigger_matching_requirements'] = import_eventloop('3e:trigger_matching')
+write_example(r,['DATA2015','DATA2017'])
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/skeleton.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/skeleton.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..3a420c681589f163d708437c2da50f453e688bdd
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/skeleton.cxx
@@ -0,0 +1,296 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// Based on CPToolTester.cxx (A. Kraznahorkay) 
+/// and ut_ath_checkTrigger_test.cxx (W. Buttinger)
+/// Contact: jmaurer@cern.ch
+{head_comments}
+
+// ROOT include(s):
+#include <TFile.h>
+#include <TError.h>
+
+// Infrastructure include(s):
+#ifdef XAOD_STANDALONE
+    #include "xAODRootAccess/Init.h"
+    #include "xAODRootAccess/TEvent.h"
+    #include "xAODRootAccess/TStore.h"
+#else
+    #include "AthAnalysisBaseComps/AthAnalysisHelper.h"
+    #include "POOLRootAccess/TEvent.h"
+#endif
+
+// EDM include(s):
+#include "AsgTools/AnaToolHandle.h"
+#include "EgammaAnalysisInterfaces/IAsgElectronEfficiencyCorrectionTool.h"
+#include "MuonAnalysisInterfaces/IMuonTriggerScaleFactors.h"
+#include "TriggerAnalysisInterfaces/ITrigGlobalEfficiencyCorrectionTool.h"
+#include "xAODEventInfo/EventInfo.h"
+#include "xAODEgamma/ElectronContainer.h"
+#include "xAODMuon/MuonContainer.h"
+#include "PATCore/PATCoreEnums.h"
+#include "AthContainers/AuxElement.h"
+
+// stdlib include(s):
+[SPLITFUNC]
+#include <sstream>
+[/SPLITFUNC]
+#include <random>
+#include <vector>
+#include <array>
+using std::vector;
+using std::string;
+
+[SPLITFUNC]
+/// Helper function to split comma-delimited strings
+namespace { vector<string> split_comma_delimited(const std::string&); }
+
+[/SPLITFUNC]
+#define MSGSOURCE "Example {example_number}"
+
+int main(int argc, char* argv[])
+{
+	const char* filename = nullptr;
+	bool debug = false, cmdline_error = false, toys = false;
+	for(int i=1;i<argc;++i)
+	{
+		if(string(argv[i]) == "--debug") debug = true;
+		else if(string(argv[i]) == "--toys") toys = true;
+		else if(!filename && *argv[i]!='-') filename = argv[i];
+		else cmdline_error = true;
+	}
+    if(!filename || cmdline_error)
+    {
+        Error(MSGSOURCE, "No file name received!");
+        Error(MSGSOURCE, "  Usage: %s [--debug] [--toys] [DxAOD file name]", argv[0]);
+        return 1;
+    }
+    #ifdef XAOD_STANDALONE
+        xAOD::Init(MSGSOURCE).ignore();
+        TFile* file = TFile::Open(filename, "READ");
+        if(!file)
+        {
+            Error(MSGSOURCE, "Unable to open file!");
+            return 2;
+        }
+        xAOD::TEvent event(xAOD::TEvent::kClassAccess);
+        xAOD::TStore store;
+        StatusCode::enableFailure();
+    #else
+       IAppMgrUI* app = POOL::Init();
+       POOL::TEvent event(POOL::TEvent::kClassAccess);
+       TString file(filename);
+    #endif
+    event.readFrom(file).ignore();
+    Long64_t entries = event.getEntries();
+    Info(MSGSOURCE, "Number of events in the file: %lli", entries);
+
+[ELECTRONS]
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the electron CP tools");
+    /// For property 'ElectronEfficiencyTools':
+    ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronEffTools;
+    /// For property 'ElectronScaleFactorTools':
+    ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronSFTools; 
+    {electron_extraproperties_declare}
+    /// RAII on-the-fly creation of electron CP tools:
+    vector<asg::AnaToolHandle<IAsgElectronEfficiencyCorrectionTool>> factory;
+[MULTITRIGGERS]
+{electron_toolconfigs}
+[/MULTITRIGGERS]
+    const char* mapPath = "ElectronEfficiencyCorrection/2015_2017/"
+            "rel21.2/Moriond_February2018_v2/map6.txt";
+[MULTITRIGGERS]
+    for(auto& cfg : toolConfigs) /// one instance per trigger leg x working point
+[/MULTITRIGGERS]
+    for(int j=0;j<2;++j) /// two instances: 0 -> MC efficiencies, 1 -> SFs
+    {
+        string name = "AsgElectronEfficiencyCorrectionTool/"
+                + ((j? "ElTrigEff_" : "ElTrigSF_")
+                + std::to_string(factory.size()/2));
+        auto t = factory.emplace(factory.end(), name);
+        t->setProperty("MapFilePath", mapPath).ignore();
+{electron_tools_properties_set}
+        t->setProperty("CorrelationModel", "TOTAL").ignore();
+        t->setProperty("ForceDataType", (int)PATCore::ParticleDataType::Full).ignore();
+        if(t->initialize() != StatusCode::SUCCESS)
+        {
+            Error(MSGSOURCE, "Unable to initialize the electron CP tool <%s>!",
+                    t->name().c_str());
+            return 3;
+        }
+        auto& handles = (j? electronSFTools : electronEffTools);
+        handles.push_back(t->getHandle());
+{electron_extraproperties_fill}
+    }
+
+[/ELECTRONS]
+[MUONS]
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the muon CP tools");
+    /// For property 'MuonTools':
+    ToolHandleArray<CP::IMuonTriggerScaleFactors> muonTools;
+    asg::AnaToolHandle<CP::IMuonTriggerScaleFactors> muonTool("CP::MuonTriggerScaleFactors/MuonTrigEff");
+    muonTool.setProperty("CalibrationRelease", "180905_TriggerUpdate").ignore();
+    muonTool.setProperty("MuonQuality", "Tight").ignore();
+    muonTool.setProperty("useRel207", false).ignore();
+    if(muonTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the muon CP tool!");
+        return 3;
+    }
+    muonTools.push_back(muonTool.getHandle());
+{muon_extraproperties_fill}
+    
+[/MUONS]
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Configuring the global trigger SF tool");
+    asg::AnaToolHandle<ITrigGlobalEfficiencyCorrectionTool> myTool("TrigGlobalEfficiencyCorrectionTool/TrigGlobal");
+[ELECTRONS]
+    myTool.setProperty("ElectronEfficiencyTools", electronEffTools).ignore();
+    myTool.setProperty("ElectronScaleFactorTools", electronSFTools).ignore();
+[/ELECTRONS]
+[MUONS]
+    myTool.setProperty("MuonTools", muonTools).ignore();
+[/MUONS]
+{trigglob_properties_set}
+    if(debug) myTool.setProperty("OutputLevel", MSG::DEBUG).ignore();
+    if(toys) myTool.setProperty("NumberOfToys", 1000).ignore();
+    if(myTool.initialize() != StatusCode::SUCCESS)
+    {
+        Error(MSGSOURCE, "Unable to initialize the TrigGlob tool!");
+        return 3;
+    }
+    
+    /// Uniform random run number generation spanning the target dataset.
+    /// In real life, use the PileupReweightingTool instead!
+    const unsigned periodRuns[] = {
+[DATA2015]
+        /// 2015 periods D-H, J
+        276073, 278727, 279932, 280423, 281130, 282625 MAYBE_COMMA
+[/DATA2015]
+[DATA2016]
+        /// 2016 periods A-L
+        296939, 300345, 301912, 302737, 303638, 303943, 305291, 307124, 
+        305359, 309311, 310015 MAYBE_COMMA
+[/DATA2016]
+[DATA2017]
+        /// 2017 periods B-K
+        325713, 329385, 330857, 332720, 334842, 335302, 336497, 336832, 
+        338183 MAYBE_COMMA
+[/DATA2017]
+    };
+    std::uniform_int_distribution<unsigned> uniformPdf(0,
+            sizeof(periodRuns)/sizeof(*periodRuns) - 1);
+    std::default_random_engine randomEngine;
+    
+    SG::AuxElement::ConstAccessor<int> truthType("truthType");
+    SG::AuxElement::ConstAccessor<int> truthOrigin("truthOrigin");
+    
+    /* ********************************************************************** */
+    
+    Info(MSGSOURCE, "Starting the event loop");
+    unsigned errors = 0;
+    double nSuitableEvents = 0., sumW = 0.;
+    for(Long64_t entry = 0; entry < entries; ++entry)
+    {
+        event.getEntry(entry);
+        
+        /// Get a random run number, and decorate the event info
+        const xAOD::EventInfo* eventInfo = nullptr;
+        event.retrieve(eventInfo,"EventInfo").ignore();
+        unsigned runNumber = periodRuns[uniformPdf(randomEngine)];
+        eventInfo->auxdecor<unsigned>("RandomRunNumber") = runNumber;
+
+{eventloop_trigcounters}        
+        vector<const xAOD::Electron*> myTriggeringElectrons;
+[ELECTRONS]
+        const xAOD::ElectronContainer* electrons = nullptr;
+        event.retrieve(electrons,"Electrons").ignore();
+        for(auto electron : *electrons)
+        {
+            if(!electron->caloCluster()) continue;
+            float eta = fabs(electron->caloCluster()->etaBE(2));
+            float pt = electron->pt();
+            if(pt<10e3f || eta>=2.47) continue;
+            if(!truthType.isAvailable(*electron)) continue;
+            if(!truthOrigin.isAvailable(*electron)) continue;
+            int t = truthType(*electron), o = truthOrigin(*electron);
+            if(t!=2 || !(o==10 || (o>=12 && o<=22) || o==43)) continue;
+{eventloop_electron_selection}
+            myTriggeringElectrons.push_back(electron);
+        }
+
+[/ELECTRONS]
+        vector<const xAOD::Muon*> myTriggeringMuons;
+[MUONS]
+        const xAOD::MuonContainer* muons = nullptr;
+        event.retrieve(muons,"Muons").ignore();
+        for(auto muon : *muons)
+        {
+            if(runNumber >= 324320) break; // delete line once all SFs available for 2017
+            float pt = muon->pt();
+            if(pt<10e3f || fabs(muon->eta())>=2.5) continue;
+            auto mt = muon->muonType();
+            if(mt!=xAOD::Muon::Combined && mt!=xAOD::Muon::MuonStandAlone) continue;
+            auto& mtp = *(muon->primaryTrackParticle());
+            if(!truthType.isAvailable(mtp)) continue;
+            if(!truthOrigin.isAvailable(mtp)) continue;
+            int t = truthType(mtp), o = truthOrigin(mtp);
+            if(t!=6 || !(o==10 || (o>=12 && o<=22) || o==43)) continue;
+{eventloop_muon_selection}
+            myTriggeringMuons.push_back(muon);
+        }
+[/MUONS]
+
+{trigger_matching_requirements}
+
+        /// Finally retrieve the global trigger scale factor
+        double sf = 1.;
+        auto cc = myTool->getEfficiencyScaleFactor(myTriggeringElectrons,
+			myTriggeringMuons, sf);
+        if(cc==CP::CorrectionCode::Ok)
+        {
+            nSuitableEvents += 1;
+            sumW += sf;
+        }
+        else
+        {
+            Warning(MSGSOURCE, "Scale factor evaluation failed");
+            ++errors;
+        }
+        if(errors>10)
+        {
+            Error(MSGSOURCE, "Too many errors reported!");
+            break;
+        }
+    }
+    Info(MSGSOURCE, "Average scale factor: %f (over %ld events)",
+            sumW / nSuitableEvents, long(nSuitableEvents));
+    #ifndef XAOD_STANDALONE
+        app->finalize();
+    #endif
+    return errors? 4 : 0;
+}
+
+[SPLITFUNC]
+/// Split comma-delimited string
+namespace
+{
+	inline vector<string> split_comma_delimited(const string& s)
+	{
+		std::stringstream ss(s);
+		std::vector<std::string> tokens;
+		std::string token;
+		while(std::getline(ss, token, ','))
+		{
+			if(token.length()) tokens.push_back(token);
+		}
+		return tokens;
+	}
+}
+[/SPLITFUNC]
\ No newline at end of file
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/trigglob_config.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/trigglob_config.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..fba54f2cf896b0839c7d20b62b31200cb17026dd
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/examples/generator/trigglob_config.cxx
@@ -0,0 +1,101 @@
+[example0] #####################################################################
+    const char* triggers2015 = 
+        "mu20_iloose_L1MU15_OR_mu50"
+        "|| e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose";
+    myTool.setProperty("TriggerCombination2015", triggers2015).ignore();
+    const char* triggers2016and2017 = 
+        "mu26_ivarmedium_OR_mu50"
+        "|| e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0";
+    myTool.setProperty("TriggerCombination2016", triggers2016and2017).ignore();
+    myTool.setProperty("TriggerCombination2017", triggers2016and2017).ignore();
+
+[example1] #####################################################################
+    const char* triggers2015 = 
+        "mu20_iloose_L1MU15_OR_mu50"
+        "|| mu18_mu8noL1"
+        "|| e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose"
+        "|| 2e12_lhloose_L12EM10VH";
+    myTool.setProperty("TriggerCombination2015", triggers2015).ignore();
+    const char* triggers2016 = 
+        "mu26_ivarmedium_OR_mu50"
+        "|| mu22_mu8noL1"
+        "|| e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0"
+        "|| 2e17_lhvloose_nod0";
+    myTool.setProperty("TriggerCombination2016", triggers2016).ignore();
+    const char* triggers2017 = 
+        "mu26_ivarmedium_OR_mu50"
+        "|| mu22_mu8noL1"
+        "|| e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0"
+        "|| 2e24_lhvloose_nod0";
+    myTool.setProperty("TriggerCombination2017", triggers2017).ignore();
+    myTool.setProperty("ListOfLegsPerTool", legsPerTool).ignore();
+        
+[example2] #####################################################################
+    /// Fill a map with the trigger combinations
+    std::map<string, string> triggers;
+    triggers["2015"] = 
+        "mu20_iloose_L1MU15_OR_mu50"
+        "|| mu18_mu8noL1"
+        "|| e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose"
+        "|| 2e12_lhloose_L12EM10VH";
+    triggers["2016:A-2016:D"] = 
+        "mu26_ivarmedium_OR_mu50"
+        "|| mu22_mu8noL1"
+        "|| e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0"
+        "|| 2e17_lhvloose_nod0";
+    triggers["303638-311481"] = /// (change triggers for fun in 2016 periods E-L)
+        "mu22_mu8noL1"
+        "|| 2e17_lhvloose_nod0";
+    triggers["2017"] = 
+        "mu26_ivarmedium_OR_mu50"
+        "|| mu22_mu8noL1"
+        "|| e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0"
+        "|| 2e24_lhvloose_nod0";
+    myTool.setProperty("TriggerCombination", triggers).ignore();
+    myTool.setProperty("ListOfLegsPerTool", legsPerTool).ignore();
+    /// Change for fun the pT thresholds used by the tool for mu18 and mu22
+    std::map<string, string> thresholds;
+    thresholds["mu18"] = "25e3";
+    thresholds["mu22"] = "25e3";
+    myTool.setProperty("OverrideThresholds", thresholds).ignore();
+
+[example3com] ##################################################################
+    myTool.setProperty("ListOfLegsPerTool", legsPerTool).ignore();
+    myTool.setProperty("ListOfTagsPerTool", tagsPerTool).ignore();
+    myTool.setProperty("ListOfLegsPerTag", legsPerTag).ignore();
+        
+[example3a] ####################################################################
+    const char* triggers2015 = "2e12_lhloose_L12EM10VH";
+    myTool.setProperty("TriggerCombination2015", triggers2015).ignore();
+    myTool.setProperty("LeptonTagDecorations", "Signal").ignore();
++[example3com]
+        
+[example3b3c] ##################################################################
+    const char* triggers2015 = 
+        "e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose"
+        "|| 2e12_lhloose_L12EM10VH";
+    myTool.setProperty("TriggerCombination2015", triggers2015).ignore();
+    myTool.setProperty("LeptonTagDecorations", "Signal").ignore();
++[example3com]
+ 
+[example3d] ####################################################################
+    const char* triggers2016 = 
+        "e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0"
+        "|| e7_lhmedium_nod0_mu24"
+        "|| 2e17_lhvloose_nod0";
+    myTool.setProperty("TriggerCombination2016", triggers2016).ignore();
+    /// Listing 'Tight' first as it has higher priority (an electron with both
+    /// non-zero 'Tight'+'Medium' decorations will then be tagged as 'Tight')
+    myTool.setProperty("LeptonTagDecorations", "MyTight,MyMedium").ignore();
++[example3com]
+        
+[example3e] ####################################################################
+    const char* triggers2016 = 
+        "e26_lhtight_nod0_ivarloose_OR_e60_lhmedium_nod0_OR_e140_lhloose_nod0"
+        "|| e7_lhmedium_nod0_mu24"
+        "|| 2e17_lhvloose_nod0";
+    myTool.setProperty("TriggerCombination2016", triggers2016).ignore();
+    /// Special character '?' indicates that the decorated value is to be 
+    /// suffixed to the name (=> 'PID1' for medium, 'PID2' for tight)
+    myTool.setProperty("LeptonTagDecorations", "PID?").ignore();
++[example3com]
\ No newline at end of file
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/src/TrigGlobalEfficiencyCorrection_entries.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/src/TrigGlobalEfficiencyCorrection_entries.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..0043eb6da2abbf3b56376fdbebc2f30fbde83440
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/src/TrigGlobalEfficiencyCorrection_entries.cxx
@@ -0,0 +1,3 @@
+#include "TrigGlobalEfficiencyCorrection/TrigGlobalEfficiencyCorrectionTool.h"
+
+DECLARE_COMPONENT( TrigGlobalEfficiencyCorrectionTool )
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/util/TrigGlobEffCorrValidation.cxx b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/util/TrigGlobEffCorrValidation.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..d91f8f9ca135fe5da82181d578347d06972c4cc2
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/util/TrigGlobEffCorrValidation.cxx
@@ -0,0 +1,654 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+// contact: jmaurer@cern.ch
+
+#include "xAODRootAccess/Init.h"
+#include "xAODRootAccess/TStore.h"
+#include "AsgTools/AnaToolHandle.h"
+#include "TriggerAnalysisInterfaces/ITrigGlobalEfficiencyCorrectionTool.h"
+#include "EgammaAnalysisInterfaces/IAsgElectronEfficiencyCorrectionTool.h"
+#include "EgammaAnalysisInterfaces/IAsgPhotonEfficiencyCorrectionTool.h"
+#include "MuonAnalysisInterfaces/IMuonTriggerScaleFactors.h"
+#include "xAODEgamma/Electron.h"
+#include "xAODEgamma/ElectronContainer.h"
+#include "xAODEgamma/ElectronAuxContainer.h"
+#include "xAODMuon/MuonContainer.h"
+#include "xAODMuon/MuonAuxContainer.h"
+#include "PATInterfaces/ISystematicsTool.h"
+#include "xAODEgamma/Photon.h"
+#include "xAODEgamma/PhotonContainer.h"
+#include "xAODEgamma/PhotonAuxContainer.h"
+
+#include <random>
+#include <functional>
+#include <algorithm>
+#include <initializer_list>
+
+#define MSGSOURCE "TrigGlobEffCorrValidation"
+
+class SimpleElectronEfficiencyCorrectionTool : virtual public IAsgElectronEfficiencyCorrectionTool, public asg::AsgTool
+{
+	ASG_TOOL_CLASS(SimpleElectronEfficiencyCorrectionTool, IAsgElectronEfficiencyCorrectionTool)
+	std::function<double(float)> m_func;
+public:
+	SimpleElectronEfficiencyCorrectionTool(const std::string& name, double eff = 0.8) : AsgTool(name), m_func([=](float){return eff;}) {}
+	SimpleElectronEfficiencyCorrectionTool(const std::string& name, const std::function<double(float)>& func) : AsgTool(name), m_func(func) {}
+	virtual ~SimpleElectronEfficiencyCorrectionTool() {}
+	virtual CP::CorrectionCode getEfficiencyScaleFactor(const xAOD::Electron& electron, double& efficiencyScaleFactor) const override
+	{
+		efficiencyScaleFactor = m_func(electron.pt());
+		return CP::CorrectionCode::Ok;
+	}
+	virtual CP::CorrectionCode applyEfficiencyScaleFactor(const xAOD::Electron&) const override { return CP::CorrectionCode::Error; }
+	virtual bool isAffectedBySystematic(const CP::SystematicVariation&) const override { return false; }
+	virtual CP::SystematicSet affectingSystematics() const override { return CP::SystematicSet(); }
+	virtual CP::SystematicSet recommendedSystematics() const override { return CP::SystematicSet(); }
+	virtual StatusCode applySystematicVariation(const CP::SystematicSet&) override { return StatusCode::SUCCESS; }
+	virtual int systUncorrVariationIndex( const xAOD::Electron &) const override { return 0; }
+};
+
+class SimplePhotonEfficiencyCorrectionTool : virtual public IAsgPhotonEfficiencyCorrectionTool, public asg::AsgTool
+{
+	ASG_TOOL_CLASS(SimplePhotonEfficiencyCorrectionTool, IAsgPhotonEfficiencyCorrectionTool)
+	std::function<double(float)> m_func;
+public:
+	SimplePhotonEfficiencyCorrectionTool(const std::string& name, double eff = 0.8) : AsgTool(name), m_func([=](float){return eff;}) {}
+	SimplePhotonEfficiencyCorrectionTool(const std::string& name, const std::function<double(float)>& func) : AsgTool(name), m_func(func) {}
+	virtual ~SimplePhotonEfficiencyCorrectionTool() {}
+	virtual CP::CorrectionCode getEfficiencyScaleFactor(const xAOD::Egamma& photon, double& efficiencyScaleFactor) const override
+	{
+		efficiencyScaleFactor = m_func(photon.pt());
+		return CP::CorrectionCode::Ok;
+	}
+	virtual CP::CorrectionCode getEfficiencyScaleFactorError(const xAOD::Egamma&, double&) const override { return CP::CorrectionCode::Error; }
+	virtual CP::CorrectionCode applyEfficiencyScaleFactor(xAOD::Egamma&) const override { return CP::CorrectionCode::Error; }
+	virtual bool isAffectedBySystematic(const CP::SystematicVariation&) const override { return false; }
+	virtual CP::SystematicSet affectingSystematics() const override { return CP::SystematicSet(); }
+	virtual CP::SystematicSet recommendedSystematics() const override { return CP::SystematicSet(); }
+	virtual StatusCode applySystematicVariation(const CP::SystematicSet&) override { return StatusCode::SUCCESS; }
+};
+
+class SimpleMuonTriggerScaleFactors : public CP::IMuonTriggerScaleFactors, public asg::AsgTool 
+{
+	ASG_TOOL_CLASS(SimpleMuonTriggerScaleFactors, CP::IMuonTriggerScaleFactors)
+	std::map<std::string, std::function<double(float)>> m_efficiencies;
+public:
+	SimpleMuonTriggerScaleFactors(const std::string& name, const std::map<std::string,std::function<double(float)>>& efficiencies = {}) : AsgTool(name)
+	{
+		for(auto& kv : efficiencies)
+		{
+			if(kv.first[0] != 'm') continue;
+			std::string leg;
+			for(char c : kv.first)
+			{
+				if(c=='u' && leg.back()=='m') leg.replace(leg.end()-1, leg.end(), "HLT_mu");
+				else leg.push_back(c);
+			}
+			m_efficiencies.emplace(leg, kv.second);
+		}
+	}
+	virtual ~SimpleMuonTriggerScaleFactors() {}
+	virtual StatusCode initialize(void) override { return StatusCode::SUCCESS; }
+	virtual CP::CorrectionCode getTriggerScaleFactor(const xAOD::MuonContainer&, Double_t&, const std::string&) const override { return CP::CorrectionCode::Ok; }
+	virtual CP::CorrectionCode getTriggerScaleFactor(const xAOD::Muon&, Double_t&, const std::string&) const override { return CP::CorrectionCode::Ok; }
+	virtual CP::CorrectionCode getTriggerEfficiency(const xAOD::Muon& muon, Double_t& efficiency, const std::string& trig, Bool_t) const override
+	{
+		auto itr = m_efficiencies.find(trig);
+		if(itr != m_efficiencies.end())
+		{
+			efficiency = itr->second(muon.pt());
+			return CP::CorrectionCode::Ok;
+		}
+		return CP::CorrectionCode::Error;
+	}
+	virtual int getBinNumber(const xAOD::Muon&, const std::string&) const override { return 0; };
+    virtual bool isAffectedBySystematic(const CP::SystematicVariation&) const override { return false; }
+	virtual CP::SystematicSet affectingSystematics() const override { return CP::SystematicSet(); }
+	virtual CP::SystematicSet recommendedSystematics() const override { return CP::SystematicSet(); }
+	virtual StatusCode applySystematicVariation(const CP::SystematicSet&) override { return StatusCode::SUCCESS; }
+	virtual bool isTriggerSupported(const std::string& ) const override {return false;}
+};
+
+struct Config
+{
+	std::string testName;
+	std::string triggers = "";
+	unsigned minLeptons = 1;
+	unsigned maxLeptons = 5;
+	std::vector<float> leptonPtValues;
+	std::map<std::string, std::function<double(float)>> efficiencies;
+	std::function<bool(const std::vector<const xAOD::Electron*>&,const std::vector<const xAOD::Muon*>&,const std::vector<const xAOD::Photon*>&)> eventSelection = nullptr;
+	double expectedEfficiency = -1.;
+	double expectedEfficiencyTolerance = 1e-6;
+	bool debug = false;
+	
+	Config(const char* name) : testName(name) { leptonPtValues = {30e4f}; }
+	Config& setTriggers(const std::string& t) { triggers = t; return *this; }
+	Config& setLeptonPDF(unsigned nmin, unsigned nmax, std::initializer_list<float> ptvalues)
+	{
+		minLeptons = nmin;
+		maxLeptons = nmax;
+		leptonPtValues = ptvalues;
+		return *this;
+	}
+	Config& setEfficiency(const std::string& leg, double eff)
+	{
+		efficiencies.emplace(leg, [=](float){return eff;});
+		return *this;
+	}
+	Config& setEfficiency(const std::string& leg, std::function<double(float)>&& eff)
+	{
+		efficiencies.emplace(leg, eff);
+		return *this;
+	}
+	Config& setEventSelection(const decltype(eventSelection)& sel) { eventSelection = sel; return *this; }
+	Config& setDebug() { debug = true; return *this; };
+	Config& setExpectedEfficiency(double eff, double tolerance)
+	{
+		expectedEfficiency = eff;
+		expectedEfficiencyTolerance = tolerance;
+		return *this;
+	}
+};
+
+xAOD::ElectronContainer* electronContainer = nullptr;
+xAOD::MuonContainer* muonContainer = nullptr;
+xAOD::PhotonContainer* photonContainer = nullptr;
+bool quiet = false, fast = false, skip = false;
+
+template<class Lepton>
+inline unsigned count(const std::vector<const Lepton*>& leptons, float ptmin, float ptmax = 1e12f)
+{
+	return std::count_if(leptons.cbegin(), leptons.cend(), [=](const Lepton* l){return l->pt()>=ptmin && l->pt()<ptmax;});
+}
+
+bool run_test(const Config& cfg, int toy_to_debug = -1);
+
+using namespace asg::msgUserCode;
+
+int main(int argc, char* argv[])
+{	
+	ANA_CHECK_SET_TYPE(bool);
+	const std::string flagQuiet("--quiet"), flagFast("--fast"), flagSkip("--skip-if-athena");
+	for(int i=1;i<argc;++i)
+	{
+		if(argv[i] == flagQuiet) quiet = true;
+		else if(argv[i] == flagFast) fast = true;
+	#ifndef XAOD_STANDALONE
+		else if(argv[i] == flagSkip) skip = true;
+	#endif
+	}
+	if(skip) return 0;
+	
+#ifndef XAOD_STANDALONE
+	Warning(MSGSOURCE, "This test doesn't work with athena for the moment.");
+	// the problem is that one can't initialize a ToolHandle from a pointer
+	// and the Simple* tools are not known to the athena framework.
+#endif
+	
+#ifdef XAOD_STANDALONE
+	StatusCode::enableFailure();
+	ANA_CHECK (xAOD::Init());
+#endif
+	xAOD::TStore store;
+	CP::CorrectionCode::enableFailure();
+	
+	electronContainer = new xAOD::ElectronContainer();
+	ANA_CHECK(store.record(electronContainer, "Electrons"));
+	auto electronContainerAux = new xAOD::ElectronAuxContainer();
+	ANA_CHECK(store.record(electronContainerAux, "ElectronsAux"));
+	electronContainer->setStore(electronContainerAux);
+	for(int i=0;i<10;++i)
+	{
+		auto el = new xAOD::Electron();
+		electronContainer->push_back(el);
+	}
+	
+	muonContainer = new xAOD::MuonContainer();
+	ANA_CHECK(store.record(muonContainer, "Muons"));
+	auto muonContainerAux = new xAOD::MuonAuxContainer();
+	ANA_CHECK(store.record(muonContainerAux, "MuonsAux"));
+	muonContainer->setStore(muonContainerAux);
+	for(int i=0;i<10;++i)
+	{
+		auto mu = new xAOD::Muon();
+		muonContainer->push_back(mu);
+	}
+	
+	photonContainer = new xAOD::PhotonContainer();
+	ANA_CHECK(store.record(photonContainer, "Photons"));
+	auto photonContainerAux = new xAOD::PhotonAuxContainer();
+	ANA_CHECK(store.record(photonContainerAux, "PhotonsAux"));
+	photonContainer->setStore(photonContainerAux);
+	for(int i=0;i<10;++i)
+	{
+		auto ph = new xAOD::Photon();
+		photonContainer->push_back(ph);
+	}
+	
+	using VE = const std::vector<const xAOD::Electron*>&;
+	using VM = const std::vector<const xAOD::Muon*>&;
+	using VP = const std::vector<const xAOD::Photon*>&;
+	
+	ANA_CHECK(run_test(Config("1L (1 lepton)")
+		.setTriggers("e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose || mu20_iloose_L1MU15_OR_mu50")
+		.setLeptonPDF(1, 1, {30e3f})
+		.setEfficiency("e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose", 0.60)
+		.setEfficiency("mu20_iloose_L1MU15_OR_mu50", 0.60)
+		.setExpectedEfficiency(0.60, 1e-6)
+	));
+	
+	ANA_CHECK(run_test(Config("1L (2 flavours, 1-4 leptons)")
+		.setTriggers("e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose || mu20_iloose_L1MU15_OR_mu50")
+		.setLeptonPDF(1, 4, {30e3f})
+		.setEfficiency("e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose", 0.40)
+	));
+	
+	ANA_CHECK(run_test(Config("2e (2 leptons)")
+		.setTriggers("2e12_lhloose_L12EM10VH")
+		.setLeptonPDF(2, 2, {30e3f})
+		.setEfficiency("e12_lhloose_L1EM10VH", 0.50)
+		.setExpectedEfficiency(0.25, 1e-6)
+	));
+
+	ANA_CHECK(run_test(Config("2e (2-3 leptons)")
+		.setTriggers("2e12_lhloose_L12EM10VH")
+		.setLeptonPDF(2, 3, {30e3f})
+		.setEfficiency("e12_lhloose_L1EM10VH", 0.50)
+	));
+	
+	ANA_CHECK(run_test(Config("emu (2 leptons)")
+		.setTriggers("e17_lhloose_mu14")
+		.setLeptonPDF(2, 2, {20e3f})
+		.setEfficiency("e17_lhloose", 0.60)
+		.setEfficiency("mu14", 0.60)
+		.setExpectedEfficiency(0.36, 1e-6)
+		.setEventSelection([](VE ve, VM vm, VP) { return count(ve,18e3f)>0&&count(vm,15e3f)>0; })
+	));
+	
+	ANA_CHECK(run_test(Config("emu (2-3 leptons)")
+		.setTriggers("e17_lhloose_mu14")
+		.setLeptonPDF(2, 3, {16e3f, 20e3f})
+		.setEfficiency("e17_lhloose", 0.64)
+		.setEfficiency("mu14", 0.56)
+		.setEventSelection([](VE ve, VM vm, VP) { return count(ve,18e3f)>0&&count(vm,15e3f)>0; })
+	));
+	
+	ANA_CHECK(run_test(Config("2mu (asym) (2 leptons)")
+		.setTriggers("mu18_mu8noL1")
+		.setLeptonPDF(2, 2, {30e3f})
+		.setEfficiency("mu18", 0.70)
+		.setEfficiency("mu8noL1", 0.70)
+		.setExpectedEfficiency(0.49, 1e-6)
+	));
+	
+	ANA_CHECK(run_test(Config("2mu (asym) (2-3 leptons)")
+		.setTriggers("mu18_mu8noL1")
+		.setLeptonPDF(2, 3, {11e3f, 21e3f})
+		.setEfficiency("mu18", 0.60)
+		.setEfficiency("mu8noL1", 0.70)
+		.setEventSelection([](VE, VM vm, VP) { return count(vm,19e3f)>0&&count(vm,10e3f)>1; })
+	));
+	
+	ANA_CHECK(run_test(Config("2e||emu||2mu")
+		.setTriggers("2e12_lhloose_L12EM10VH || e17_lhloose_mu14 || mu18_mu8noL1")
+		.setLeptonPDF(2, 4, {10e3f, 16e3f, 20e3f})
+		.setEfficiency("e12_lhloose_L1EM10VH", 0.50)
+		.setEfficiency("e17_lhloose", 0.60)
+		.setEfficiency("mu18", 0.60)
+		.setEfficiency("mu14", 0.60)
+		.setEfficiency("mu8noL1", 0.70)
+		.setEventSelection([](VE ve, VM vm, VP) { return count(ve,13e3f)>1 
+			|| (count(ve,18e3f)>0&&count(vm,15e3f)>0) || (count(vm,19e3f)>0&&count(vm,10e3f)>1); })
+	));
+	
+	ANA_CHECK(run_test(Config("2e||emu||2mu||1e||1mu")
+		.setTriggers("2e12_lhloose_L12EM10VH || e17_lhloose_mu14 || mu18_mu8noL1 || e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose || mu20_iloose_L1MU15_OR_mu50")
+		.setLeptonPDF(1, 4, {10e3f, 16e3f, 20e3f, 30e3f})
+		.setEfficiency("e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose", 0.40)
+		.setEfficiency("mu20_iloose_L1MU15_OR_mu50", 0.40)
+		.setEfficiency("e12_lhloose_L1EM10VH", 0.50)
+		.setEfficiency("e17_lhloose", 0.60)
+		.setEfficiency("mu18", 0.60)
+		.setEfficiency("mu14", 0.60)
+		.setEfficiency("mu8noL1", 0.70)
+		.setEventSelection([](VE ve, VM vm, VP) { return count(ve,13e3f)>1 
+			|| (count(ve,18e3f)>0&&count(vm,15e3f)>0) || (count(vm,19e3f)>0&&count(vm,10e3f)>1)
+			|| count(ve,25e3f)>0 || count(vm,21.5e3f)>0; })
+	));
+	
+	ANA_CHECK(run_test(Config("e_2e (3 leptons)")
+		.setTriggers("e17_lhloose_2e9_lhloose")
+		.setLeptonPDF(3, 3, {20e3f})
+		.setEfficiency("e17_lhloose", 0.90)
+		.setEfficiency("e9_lhloose", 0.90)
+		.setExpectedEfficiency(0.729, 1e-6)
+	));
+	
+	ANA_CHECK(run_test(Config("e_2e (3-5 leptons)")
+		.setTriggers("e17_lhloose_2e9_lhloose")
+		.setLeptonPDF(3, 5, {12e3f, 20e3f})
+		.setEfficiency("e17_lhloose", 0.60)
+		.setEfficiency("e9_lhloose", 0.70)
+		.setEventSelection([](VE ve, VM, VP) { return (count(ve,18e3f)>0&&count(ve,10e3f)>2); })
+	));
+	
+	ANA_CHECK(run_test(Config("3mu (3 leptons)")
+		.setTriggers("3mu6")
+		.setLeptonPDF(3, 3, {10e3f})
+		.setEfficiency("mu6", 0.90)
+		.setExpectedEfficiency(0.729, 1e-6)
+	));
+	
+	ANA_CHECK(run_test(Config("3mu (3-5 leptons)")
+		.setTriggers("3mu6")
+		.setLeptonPDF(3, 5, {10e3f})
+		.setEfficiency("mu6", 0.70)
+		.setEventSelection([](VE, VM vm, VP) { return count(vm,7e3f)>2; })
+	));
+	
+	ANA_CHECK(run_test(Config("2e_mu||e_2mu")
+		.setTriggers("e12_lhloose_2mu10 || 2e12_lhloose_mu10")
+		.setLeptonPDF(3, 5, {14e3f})
+		.setEfficiency("e12_lhloose", 0.50)
+		.setEfficiency("mu10", 0.60)
+		.setEventSelection([](VE ve, VM vm, VP) { return (count(ve,13e3f)>1&&count(vm,11e3f)>0) || (count(ve,13e3f)>0&&count(vm,11e3f)>1); })
+	));	
+	
+	// not a full test of the function since 2mu10 completely shadows 2mu14, but it validates at least part of the logic. Full test done with photons (see below).
+	ANA_CHECK(run_test(Config("2mu||2mu||mu")
+		.setTriggers("2mu10 || 2mu14 || mu24_iloose_L1MU15")
+		.setLeptonPDF(1, 4, {13e3f, 16e3f, 30e3f})
+		.setEfficiency("mu10", 0.70)
+		.setEfficiency("mu14", 0.60)
+		.setEfficiency("mu24_iloose_L1MU15", 0.30)
+		.setEventSelection([](VE, VM vm, VP) { return (count(vm,11e3f)>1) || (count(vm,25.5e3f)>1) ; })
+	));
+	
+	ANA_CHECK(run_test(Config("mu_mu||2mu||mu")
+		.setTriggers("mu18_mu8noL1 || 2mu14 || mu24_iloose_L1MU15")
+		.setLeptonPDF(1, 4, {11e3f, 16e3f, 20e3f, 30e3f})
+		.setEfficiency("mu8noL1", 0.80)
+		.setEfficiency("mu14", 0.70)
+		.setEfficiency("mu18", 0.60)
+		.setEfficiency("mu24_iloose_L1MU15", 0.30)
+		.setEventSelection([](VE, VM vm, VP) { return (count(vm,25.5e3f)>0) || (count(vm,10e3f)>1&&count(vm,19e3f)>0) || (count(vm,15e3f)>1) ; })
+	));
+	
+	ANA_CHECK(run_test(Config("2e||mu_mu||2mu||emu||emu||e||mu")
+		.setTriggers("2e12_lhloose_L12EM10VH || mu18_mu8noL1 || 2mu14 || e17_lhloose_mu14 || e7_lhmedium_mu24 || e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose || mu20_iloose_L1MU15")
+		.setLeptonPDF(2, 2, {11e3f, 13e3f, 16e3f, 20e3f, 30e3f})
+		.setEfficiency("mu8noL1", 0.80)
+		.setEfficiency("mu14", 0.70)
+		.setEfficiency("mu18", 0.60)
+		.setEfficiency("mu20_iloose_L1MU15", 0.30)
+		.setEfficiency("mu24", 0.37)
+		.setEfficiency("e7_lhmedium", 0.53)
+		.setEfficiency("e12_lhloose_L1EM10VH", 0.73)
+		.setEfficiency("e17_lhloose", 0.67)
+		.setEfficiency("e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose", 0.42)
+		.setEventSelection([](VE ve, VM vm, VP) { return (count(vm,25.5e3f)>0) || (count(ve,25e3f)>0) || (count(ve,13e3f)>1) || (count(vm,15e3f)>1)
+			|| (count(vm,10e3f)>1&&count(vm,19e3f)>0) ||  (count(ve,18e3f)>0&&count(vm,15e3f)>0) || (count(ve,8e3f)>0&&count(vm,25.5e3f)>0); })
+	));
+	
+	ANA_CHECK(run_test(Config("2g (sym)")
+		.setTriggers("2g22_tight_L12EM15VHI")
+		.setLeptonPDF(2, 3, {30e3f})
+		.setEfficiency("g22_tight_L1EM15VHI", 0.60)
+		.setEventSelection([](VE, VM, VP vp) { return count(vp,23e3f)>1; })
+	));
+	
+	ANA_CHECK(run_test(Config("2g (sym) || 2g (sym) || 1g")
+		.setTriggers("2g50_loose_L12EM20VH || 2g22_tight_L12EM15VHI || g120_loose")
+		.setLeptonPDF(2, 3, {25e3f, 60e3f, 150e3f})
+		.setEfficiency("g22_tight_L1EM15VHI", 0.43)
+		.setEfficiency("g50_loose_L1EM20VH", 0.74)
+		.setEfficiency("g120_loose", 0.82)
+		.setEventSelection([](VE, VM, VP vp) { return count(vp,23e3f)>1 || count(vp,121e3f)>0; })
+	));
+	
+	ANA_CHECK(run_test(Config("2g (sym) || 2g (asym)")
+		.setTriggers("2g22_tight_L12EM15VHI || g35_medium_g25_medium_L12EM20VH")
+		.setLeptonPDF(2, 4, {24e3f, 30e3f, 40e3f})
+		.setEfficiency("g22_tight_L1EM15VHI", 0.43)
+		.setEfficiency("g35_medium_L1EM20VH", 0.74)
+		.setEfficiency("g25_medium_L1EM20VH", 0.82)
+		.setEventSelection([](VE, VM, VP vp) { return count(vp,23e3f)>1; })
+	));
+
+	ANA_CHECK(run_test(Config("2g_g (3 photons)")
+		.setTriggers("2g25_loose_g15_loose")
+		.setLeptonPDF(3, 3, {30e3f})
+		.setEfficiency("g25_loose", 0.8)
+		.setEfficiency("g15_loose", 0.9)
+		.setExpectedEfficiency(0.704, 1e-6)
+	));
+	
+	ANA_CHECK(run_test(Config("2g_g (3-5 photons)")
+		.setTriggers("2g25_loose_g15_loose")
+		.setLeptonPDF(3, 5, {20e3f, 30e3f})
+		.setEfficiency("g25_loose", 0.63)
+		.setEfficiency("g15_loose", 0.88)
+		.setEventSelection([](VE, VM, VP vp) { return count(vp,26e3f)>1 && count(vp,16e3f)>2; })
+	));
+	
+	ANA_CHECK(run_test(Config("e || mu || g (factorized)")
+		.setTriggers("e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose || mu20_iloose_L1MU15_OR_mu50 || g120_loose")
+		.setLeptonPDF(1, 2, {150e3f})
+		.setEfficiency("e24_lhmedium_L1EM20VH_OR_e60_lhmedium_OR_e120_lhloose", 0.55)
+		.setEfficiency("mu20_iloose_L1MU15_OR_mu50", 0.66)
+		.setEfficiency("g120_loose", 0.77)
+	));
+
+	ANA_CHECK(run_test(Config("3e || 3mu || 3g (factorized)")
+		.setTriggers("e17_lhloose_2e9_lhloose || 3mu6 || 3g20_loose")
+		.setLeptonPDF(4, 6, {30e3f})
+		.setEfficiency("e9_lhloose", 0.68)
+		.setEfficiency("e17_lhloose", 0.54)
+		.setEfficiency("mu6", 0.77)
+		.setEfficiency("g20_loose", 0.81)
+		.setEventSelection([](VE ve, VM vm, VP vp) { return count(ve,18e3f)>2 || count(vm,10e3f)>2 || count(vp,21e3f)>2; })
+	));
+
+	ANA_CHECK(run_test(Config("e_2mu || 2g || 1g (factorized)")
+		.setTriggers("g120_loose || 2e12_lhloose_mu10 || 2g22_tight_L12EM15VHI")
+		.setLeptonPDF(3, 6, {150e3f})
+		.setEfficiency("e12_lhloose", 0.48)
+		.setEfficiency("mu10", 0.62)
+		.setEfficiency("g22_tight_L1EM15VHI", 0.35)
+		.setEfficiency("g120_loose", 0.56)
+		.setEventSelection([](VE ve, VM vm, VP vp) { return (count(ve,13e3f)>1  && count(vm,11e3f)>0) || count(vp,21e3f)>0; })
+	));	
+	
+	//Info(MSGSOURCE, "Boost version: %i", BOOST_VERSION);
+	return 0;
+}
+
+
+bool configure(asg::AnaToolHandle<ITrigGlobalEfficiencyCorrectionTool>& tool, 
+	ToolHandleArray<IAsgElectronEfficiencyCorrectionTool>& electronEffToolsHandles, ToolHandleArray<IAsgElectronEfficiencyCorrectionTool>& electronSFToolsHandles, 
+	ToolHandleArray<CP::IMuonTriggerScaleFactors>& muonToolsHandles,
+	ToolHandleArray<IAsgPhotonEfficiencyCorrectionTool>& photonEffToolsHandles, ToolHandleArray<IAsgPhotonEfficiencyCorrectionTool>& photonSFToolsHandles, 
+	const std::string& triggers, const std::map<std::string, std::string>& legsPerTool, unsigned long nToys, bool debug)
+{
+	ANA_CHECK_SET_TYPE(bool);
+	ANA_CHECK(tool.setProperty("ElectronEfficiencyTools", electronEffToolsHandles));
+	ANA_CHECK(tool.setProperty("ElectronScaleFactorTools", electronSFToolsHandles));
+	ANA_CHECK(tool.setProperty("PhotonEfficiencyTools", photonEffToolsHandles));
+	ANA_CHECK(tool.setProperty("PhotonScaleFactorTools", photonSFToolsHandles));
+	ANA_CHECK(tool.setProperty("ListOfLegsPerTool", legsPerTool));
+	ANA_CHECK(tool.setProperty("MuonTools", muonToolsHandles));
+	ANA_CHECK(tool.setProperty("TriggerCombination2015", triggers));
+	ANA_CHECK(tool.setProperty("NumberOfToys", nToys));
+	ANA_CHECK(tool.setProperty("UseInternalSeed", true));
+	ANA_CHECK(tool.setProperty("OutputLevel", debug? MSG::DEBUG : quiet? MSG::WARNING : MSG::INFO));
+	ANA_CHECK(tool.initialize());
+	return true;
+}
+
+bool run_test(const Config& cfg, int toy_to_debug)
+{
+	ANA_CHECK_SET_TYPE(bool);
+	if(!quiet) Info(MSGSOURCE, "Running test %s", cfg.testName.c_str());
+	const int nToysPerEvent = (fast? 250 : 500), nToysPerTest = (fast? 200 : 2000), nToySamples = 10;
+	std::vector<SimpleElectronEfficiencyCorrectionTool*> electronTools;
+	ToolHandleArray<IAsgElectronEfficiencyCorrectionTool> electronEffToolsHandles, electronSFToolsHandles;
+	std::vector<SimplePhotonEfficiencyCorrectionTool*> photonTools;
+	ToolHandleArray<IAsgPhotonEfficiencyCorrectionTool> photonEffToolsHandles, photonSFToolsHandles;
+	std::map<std::string,std::string> legsPerTool;
+	bool generateElectrons = false, generateMuons = false, generatePhotons = false;
+	for(auto& kv : cfg.efficiencies)
+	{
+		if(kv.first[0]=='e')
+		{
+			electronTools.emplace_back(new SimpleElectronEfficiencyCorrectionTool("EFF-"+kv.first, kv.second));
+			legsPerTool.emplace(electronTools.back()->name(), kv.first);
+			#ifdef XAOD_STANDALONE
+				electronEffToolsHandles.push_back(electronTools.back());
+			#endif
+			electronTools.emplace_back(new SimpleElectronEfficiencyCorrectionTool("SF-"+kv.first, 1.));
+			legsPerTool.emplace(electronTools.back()->name(), kv.first);
+			#ifdef XAOD_STANDALONE
+				electronSFToolsHandles.push_back(electronTools.back());
+			#endif
+			generateElectrons  =true;
+		}
+		else if(kv.first[0]=='g')
+		{
+			photonTools.emplace_back(new SimplePhotonEfficiencyCorrectionTool("EFF-"+kv.first, kv.second));
+			legsPerTool.emplace(photonTools.back()->name(), kv.first);
+			#ifdef XAOD_STANDALONE
+				photonEffToolsHandles.push_back(photonTools.back());
+			#endif
+			photonTools.emplace_back(new SimplePhotonEfficiencyCorrectionTool("SF-"+kv.first, 1.));
+			legsPerTool.emplace(photonTools.back()->name(), kv.first);
+			#ifdef XAOD_STANDALONE
+				photonSFToolsHandles.push_back(photonTools.back());
+			#endif
+			generatePhotons = true;
+		}
+		else if(kv.first[0]=='m') generateMuons = true;
+	}
+	std::vector<SimpleMuonTriggerScaleFactors*> muonTools;
+	muonTools.emplace_back(new SimpleMuonTriggerScaleFactors("EFF-muons", cfg.efficiencies));
+	ToolHandleArray<CP::IMuonTriggerScaleFactors> muonToolsHandles;
+	#ifdef XAOD_STANDALONE
+		muonToolsHandles.push_back(muonTools.back());
+	#endif
+	std::string suffix = ((toy_to_debug>=0)? "_debug" : "");
+	asg::AnaToolHandle<ITrigGlobalEfficiencyCorrectionTool> trigGlobTool("TrigGlobalEfficiencyCorrectionTool/trigGlobTool" + suffix);
+	asg::AnaToolHandle<ITrigGlobalEfficiencyCorrectionTool> trigGlobTool_toys("TrigGlobalEfficiencyCorrectionTool/trigGlobTool_toys" + suffix);
+	bool debug = cfg.debug || (toy_to_debug>=0);
+	ANA_CHECK(configure(trigGlobTool, electronEffToolsHandles, electronSFToolsHandles, muonToolsHandles, photonEffToolsHandles, photonSFToolsHandles, cfg.triggers, legsPerTool, 0, debug));
+	ANA_CHECK(configure(trigGlobTool_toys, electronEffToolsHandles, electronSFToolsHandles, muonToolsHandles, photonEffToolsHandles, photonSFToolsHandles, cfg.triggers, legsPerTool, nToysPerEvent, debug));
+	std::default_random_engine rdm;
+	std::uniform_int_distribution<unsigned> nleptonsPdf(cfg.minLeptons, cfg.maxLeptons);
+	std::discrete_distribution<> flavourPdf({1.*generateElectrons, 1.*generateMuons, 1.*generatePhotons});
+	std::uniform_int_distribution<unsigned> ptIndexPdf(0, cfg.leptonPtValues.size()-1);
+	std::vector<const xAOD::Electron*> electrons;
+	std::vector<const xAOD::Muon*> muons;
+	std::vector<const xAOD::Photon*> photons;
+	std::vector<const xAOD::IParticle*> particles;
+	double sum_eff = 0., sum_eff_toys[nToySamples];
+	std::fill(std::begin(sum_eff_toys), std::end(sum_eff_toys), 0.);
+	for(int toy=0; toy<nToysPerTest; ++toy)
+	{
+		do
+		{
+			electrons.clear();
+			muons.clear();
+			photons.clear();
+			particles.clear();
+			const unsigned nLeptons = nleptonsPdf(rdm);
+			for(unsigned index=0;index<nLeptons;++index)
+			{
+				float pt = cfg.leptonPtValues[ptIndexPdf(rdm)];
+				switch(flavourPdf(rdm))
+				{
+					case 0:
+					{
+						auto electron = electronContainer->at(electrons.size());
+						particles.push_back(electron);
+						electrons.push_back(electron);
+						electron->setP4(pt, 0.f, 0.f, 0.511f);
+						break;
+					}
+					case 1:
+					{
+						auto muon = muonContainer->at(muons.size());
+						particles.push_back(muon);
+						muons.push_back(muon);
+						muon->setP4(pt, 0.f, 0.f);
+						break;
+					}
+					case 2:
+					{
+						auto photon = photonContainer->at(photons.size());
+						particles.push_back(photon);
+						photons.push_back(photon);
+						photon->setP4(pt, 0.f, 0.f, 0.f);
+						break;
+					}
+				}
+			}
+		}
+		while(cfg.eventSelection && !cfg.eventSelection(electrons, muons, photons));
+		if(toy_to_debug>=0 && toy!=toy_to_debug) continue;
+		
+		double dummy, eff = 0., eff_toys;
+		const int runNumber = 281130;
+		if(trigGlobTool->getEfficiency(runNumber, particles, eff, dummy) != CP::CorrectionCode::Ok)
+		{
+			if(toy_to_debug < 0)
+			{
+				Info(MSGSOURCE, "Running %s test again for this toy event, with the debug flag set", cfg.testName.c_str());
+				run_test(cfg, toy);
+			}
+			return false;
+		}
+		sum_eff += eff;
+		for(int spl=0;spl<nToySamples;++spl)
+		{
+			eff_toys = 0.;
+			if(trigGlobTool_toys->getEfficiency(runNumber, particles, eff_toys, dummy) != CP::CorrectionCode::Ok)
+			{
+				if(toy_to_debug < 0)
+				{
+					Info(MSGSOURCE, "Running %s test again for this toy event, with the debug flag set", cfg.testName.c_str());
+					run_test(cfg, toy);
+					return false;
+				}
+				return false;
+			}
+			sum_eff_toys[spl] += eff_toys;
+		}
+		if(toy == toy_to_debug) return true;
+	}
+	if(cfg.expectedEfficiency > 0.)
+	{
+		double eff = sum_eff / nToysPerTest;
+		if(fabs(eff - cfg.expectedEfficiency) > cfg.expectedEfficiencyTolerance)
+		{
+			return false;
+		}
+	}
+	double eff = sum_eff/nToysPerTest, eff_toys = 0., toys_rms = 0.;
+	for(double sum : sum_eff_toys) eff_toys += sum / nToysPerTest;
+	eff_toys /= nToySamples;
+	for(double sum : sum_eff_toys) toys_rms += pow(sum/nToysPerTest - eff_toys, 2);
+	toys_rms = sqrt(toys_rms / (nToySamples-1));
+	double  sigma = fabs(eff-eff_toys) / toys_rms;
+	if(!quiet) Info(MSGSOURCE, "Efficiency: %f, toys: %f (signif. = %.1f sigma)", eff, eff_toys, sigma);
+	if(sigma >= 3.)
+	{
+		Error(MSGSOURCE, "The difference is too large");
+		return false;
+	}
+	else if(sigma >= 2.) Warning(MSGSOURCE, "The difference isn't small");
+	for(auto tool : electronTools) delete tool;
+	for(auto tool : muonTools) delete tool;
+	for(auto tool : photonTools) delete tool;
+	return true;
+}
diff --git a/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/util/unit_tests.sh b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/util/unit_tests.sh
new file mode 100755
index 0000000000000000000000000000000000000000..b9eeaf0a43b098f3284a8e581cbf79a59fdb394e
--- /dev/null
+++ b/Trigger/TrigAnalysis/TrigGlobalEfficiencyCorrection/util/unit_tests.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+set -e
+echo "Testing TrigGlobalEfficiencyCorrectionTool internal logic (standalone only)"
+TrigGlobEffCorrValidation --quiet --fast --skip-if-athena
+echo "Testing TrigGlobalEfficiencyCorrectionTool interactions with CP tools"
+TrigGlobEffCorrExample4 --quiet
+echo "Test successful."
+exit 0