Page MenuHomeHEPForge

No OneTemporary

diff --git a/EvtGenBase/EvtMTRandomEngine.hh b/EvtGenBase/EvtMTRandomEngine.hh
index 4d713e4..32ce16b 100644
--- a/EvtGenBase/EvtMTRandomEngine.hh
+++ b/EvtGenBase/EvtMTRandomEngine.hh
@@ -1,43 +1,44 @@
/***********************************************************************
* Copyright 1998-2020 CERN for the benefit of the EvtGen authors *
* *
* This file is part of EvtGen. *
* *
* EvtGen is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* EvtGen is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with EvtGen. If not, see <https://www.gnu.org/licenses/>. *
***********************************************************************/
#ifndef EVTMTRANDOMENGINE_HH
#define EVTMTRANDOMENGINE_HH
#include "EvtGenBase/EvtRandomEngine.hh"
#include <random>
class EvtMTRandomEngine : public EvtRandomEngine {
public:
- EvtMTRandomEngine( unsigned int seed = 1430957218 );
+ EvtMTRandomEngine( unsigned long int seed = 1430957218 );
- virtual double random() override;
+ double random() override;
- virtual void setSeed( unsigned int seed ) override;
+ void setSeed( unsigned long int seed ) override;
+
+ unsigned long int lastSeed() const override { return m_lastSeed; }
private:
std::mt19937 m_engine;
-
- typedef std::uniform_real_distribution<double> URDist;
- URDist m_distribution;
+ std::uniform_real_distribution<double> m_distribution;
+ unsigned long int m_lastSeed;
};
#endif
diff --git a/EvtGenBase/EvtRandom.hh b/EvtGenBase/EvtRandom.hh
index 17582f9..12228b3 100644
--- a/EvtGenBase/EvtRandom.hh
+++ b/EvtGenBase/EvtRandom.hh
@@ -1,48 +1,50 @@
/***********************************************************************
* Copyright 1998-2020 CERN for the benefit of the EvtGen authors *
* *
* This file is part of EvtGen. *
* *
* EvtGen is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* EvtGen is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with EvtGen. If not, see <https://www.gnu.org/licenses/>. *
***********************************************************************/
#ifndef EVTRANDOM_HH
#define EVTRANDOM_HH
class EvtRandomEngine;
class EvtRandom {
public:
static double Flat();
static double Flat( double max );
static double Flat( double min, double max );
//generate unit Gaussian
static double Gaussian();
static double random();
- static void setSeed( unsigned int seed );
+ static void setSeed( unsigned long int seed );
+
+ static unsigned long int lastSeed();
//This class does not take ownership of the random engine;
//the caller needs to make sure that the engine is not
//destroyed.
static void setRandomEngine( EvtRandomEngine* randomEngine );
private:
static thread_local EvtRandomEngine* m_randomEngine;
};
#endif
diff --git a/EvtGenBase/EvtRandomEngine.hh b/EvtGenBase/EvtRandomEngine.hh
index b4edb89..cbe7c41 100644
--- a/EvtGenBase/EvtRandomEngine.hh
+++ b/EvtGenBase/EvtRandomEngine.hh
@@ -1,39 +1,41 @@
/***********************************************************************
* Copyright 1998-2020 CERN for the benefit of the EvtGen authors *
* *
* This file is part of EvtGen. *
* *
* EvtGen is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* EvtGen is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with EvtGen. If not, see <https://www.gnu.org/licenses/>. *
***********************************************************************/
#ifndef EVTRANDOMENGINE_HH
#define EVTRANDOMENGINE_HH
// Description:Class to generate random numbers. Single member
// function random is expected to return a random
// number in the range ]0..1[.
class EvtRandomEngine {
public:
virtual ~EvtRandomEngine(){};
virtual double random() = 0;
- virtual void setSeed( unsigned int seed ) = 0;
+ virtual void setSeed( unsigned long int seed ) = 0;
+
+ virtual unsigned long int lastSeed() const = 0;
private:
};
#endif
diff --git a/EvtGenBase/EvtSimpleRandomEngine.hh b/EvtGenBase/EvtSimpleRandomEngine.hh
index e9fb9bc..0730b8a 100644
--- a/EvtGenBase/EvtSimpleRandomEngine.hh
+++ b/EvtGenBase/EvtSimpleRandomEngine.hh
@@ -1,40 +1,42 @@
/***********************************************************************
* Copyright 1998-2020 CERN for the benefit of the EvtGen authors *
* *
* This file is part of EvtGen. *
* *
* EvtGen is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* EvtGen is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with EvtGen. If not, see <https://www.gnu.org/licenses/>. *
***********************************************************************/
#ifndef EVTSIMPLERANDOMENGINE_HH
#define EVTSIMPLERANDOMENGINE_HH
#include "EvtGenBase/EvtRandomEngine.hh"
class EvtSimpleRandomEngine : public EvtRandomEngine {
public:
EvtSimpleRandomEngine() { m_next = 1; }
void reset() { m_next = 1; }
double random() override;
- void setSeed( unsigned int seed ) override;
+ void setSeed( unsigned long int seed ) override;
+
+ unsigned long int lastSeed() const override { return m_next; }
private:
unsigned long int m_next;
};
#endif
diff --git a/EvtGenExternal/EvtExternalGenFactory.hh b/EvtGenExternal/EvtExternalGenFactory.hh
index 4bc19f4..eb6ce21 100644
--- a/EvtGenExternal/EvtExternalGenFactory.hh
+++ b/EvtGenExternal/EvtExternalGenFactory.hh
@@ -1,69 +1,70 @@
/***********************************************************************
* Copyright 1998-2020 CERN for the benefit of the EvtGen authors *
* *
* This file is part of EvtGen. *
* *
* EvtGen is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* EvtGen is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with EvtGen. If not, see <https://www.gnu.org/licenses/>. *
***********************************************************************/
#ifndef EVTEXTERNALGENFACTORY_HH
#define EVTEXTERNALGENFACTORY_HH
#include "EvtGenModels/EvtAbsExternalGen.hh"
#include <map>
#include <memory>
// Description: A factory type method to create engines for external physics
// generators like Pythia.
class EvtExternalGenFactory final {
public:
enum class GenId
{
PythiaGenId = 0,
TauolaGenId
};
static EvtExternalGenFactory& getInstance();
EvtAbsExternalGen* getGenerator( const GenId genId );
void initialiseAllGenerators();
void definePythiaGenerator( std::string xmlDir, bool convertPhysCodes,
bool useEvtGenRandom = true );
- void defineTauolaGenerator( bool useEvtGenRandom = true );
+ void defineTauolaGenerator( bool useEvtGenRandom = true,
+ bool seedTauolaFortran = true );
//methods to add configuration commands to the pythia generators
//void addPythiaCommand( std::string generator, std::string module, std::string param, std::string value);
//void addPythia6Command(std::string generator, std::string module, std::string param, std::string value);
private:
EvtExternalGenFactory() = default;
~EvtExternalGenFactory() = default;
EvtExternalGenFactory( const EvtExternalGenFactory& ) = delete;
EvtExternalGenFactory( EvtExternalGenFactory&& ) = delete;
EvtExternalGenFactory& operator=( const EvtExternalGenFactory& ) = delete;
EvtExternalGenFactory& operator=( EvtExternalGenFactory&& ) = delete;
typedef std::map<GenId, std::unique_ptr<EvtAbsExternalGen>> ExtGenMap;
//typedef std::map<GenId, std::map<std::string, std::vector<std::string>>> ExtGenCommandMap;
ExtGenMap m_extGenMap;
//ExtGenCommandMap m_extGenCommandMap;
};
#endif
diff --git a/EvtGenExternal/EvtExternalGenList.hh b/EvtGenExternal/EvtExternalGenList.hh
index 6fbd63e..b85182b 100644
--- a/EvtGenExternal/EvtExternalGenList.hh
+++ b/EvtGenExternal/EvtExternalGenList.hh
@@ -1,57 +1,58 @@
/***********************************************************************
* Copyright 1998-2020 CERN for the benefit of the EvtGen authors *
* *
* This file is part of EvtGen. *
* *
* EvtGen is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* EvtGen is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with EvtGen. If not, see <https://www.gnu.org/licenses/>. *
***********************************************************************/
#ifndef EVTEXTERNALGENLIST_HH
#define EVTEXTERNALGENLIST_HH
#include "EvtGenBase/EvtAbsRadCorr.hh"
#include "EvtGenBase/EvtDecayBase.hh"
#include <list>
// Description: A factory type method to create engines for external physics
// generators like Pythia.
class EvtExternalGenList {
public:
EvtExternalGenList( bool convertPythiaCodes = false,
std::string pythiaXmlDir = "",
std::string photonType = "gamma",
- bool useEvtGenRandom = true );
+ bool useEvtGenRandom = true,
+ bool seedTauolaFortran = true );
virtual ~EvtExternalGenList();
std::list<EvtDecayBase*> getListOfModels();
EvtAbsRadCorr* getPhotosModel( const double infraredCutOff = 1.0e-7,
const double maxWtInterference = 64.0 );
EvtAbsRadCorr* getSherpaPhotonsModel( const double infraredCutOff = 1.0e-7,
const int mode = 2,
const int useME = 0 );
protected:
private:
std::string m_photonType;
bool m_useEvtGenRandom;
};
#endif
diff --git a/EvtGenExternal/EvtTauolaEngine.hh b/EvtGenExternal/EvtTauolaEngine.hh
index d86d1f2..8eedbdc 100644
--- a/EvtGenExternal/EvtTauolaEngine.hh
+++ b/EvtGenExternal/EvtTauolaEngine.hh
@@ -1,86 +1,87 @@
/***********************************************************************
* Copyright 1998-2020 CERN for the benefit of the EvtGen authors *
* *
* This file is part of EvtGen. *
* *
* EvtGen is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* EvtGen is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with EvtGen. If not, see <https://www.gnu.org/licenses/>. *
***********************************************************************/
#ifdef EVTGEN_TAUOLA
#ifndef EVTTAUOLAENGINE_HH
#define EVTTAUOLAENGINE_HH
#include "EvtGenBase/EvtDecayBase.hh"
#include "EvtGenBase/EvtId.hh"
#include "EvtGenBase/EvtParticle.hh"
#include "EvtGenBase/EvtVector4R.hh"
#include "EvtGenModels/EvtAbsExternalGen.hh"
#ifdef EVTGEN_HEPMC3
#include "HepMC3/Relatives.h"
#include "HepMC3/Units.h"
#include "Tauola/TauolaHepMC3Event.h"
#include "Tauola/TauolaHepMC3Particle.h"
#else
#include "Tauola/TauolaHepMCEvent.h"
#include "Tauola/TauolaHepMCParticle.h"
#include "Tauola/TauolaParticle.h"
#endif
#include "EvtGenBase/EvtHepMCEvent.hh"
#include <mutex>
// Description: Interface to the TAUOLA external generator
class EvtTauolaEngine : public EvtAbsExternalGen {
public:
- EvtTauolaEngine( bool useEvtGenRandom = true );
+ EvtTauolaEngine( bool useEvtGenRandom = true, bool seedTauolaFortran = true );
bool doDecay( EvtParticle* theMother ) override;
void initialise() override;
protected:
private:
GenParticlePtr createGenParticle( const EvtParticle* theParticle ) const;
void setUpPossibleTauModes();
void setOtherParameters();
int getModeInt( EvtDecayBase* decayModel ) const;
void decayTauEvent( EvtParticle* tauParticle );
bool m_useEvtGenRandom{ true };
+ bool m_seedTauolaFortran{ true };
// PDG standard code integer ID for tau particle
static constexpr int m_tauPDG{ 15 };
// Number of possible decay modes in Tauola
static constexpr int m_nTauolaModes{ 22 };
// Neutral and charged spin propagator choices
static int m_neutPropType;
static int m_posPropType;
static int m_negPropType;
static bool m_initialised;
static std::mutex m_tauola_mutex;
};
#endif
#endif
diff --git a/src/EvtGenBase/EvtMTRandomEngine.cpp b/src/EvtGenBase/EvtMTRandomEngine.cpp
index 9433c51..6c176e2 100644
--- a/src/EvtGenBase/EvtMTRandomEngine.cpp
+++ b/src/EvtGenBase/EvtMTRandomEngine.cpp
@@ -1,43 +1,44 @@
/***********************************************************************
* Copyright 1998-2020 CERN for the benefit of the EvtGen authors *
* *
* This file is part of EvtGen. *
* *
* EvtGen is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* EvtGen is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with EvtGen. If not, see <https://www.gnu.org/licenses/>. *
***********************************************************************/
#include "EvtGenBase/EvtMTRandomEngine.hh"
#include "EvtGenBase/EvtReport.hh"
#include <iostream>
-EvtMTRandomEngine::EvtMTRandomEngine( unsigned int seed ) :
- m_engine( seed ), m_distribution( URDist( 0.0, 1.0 ) )
+EvtMTRandomEngine::EvtMTRandomEngine( unsigned long int seed ) :
+ m_engine{ seed }, m_distribution{ 0.0, 1.0 }, m_lastSeed{ seed }
{
EvtGenReport( EVTGEN_INFO, "EvtMTRandomEngine" )
<< "Mersenne-Twister random number generator with seed = " << seed
<< std::endl;
}
double EvtMTRandomEngine::random()
{
return m_distribution( m_engine );
}
-void EvtMTRandomEngine::setSeed( unsigned int seed )
+void EvtMTRandomEngine::setSeed( unsigned long int seed )
{
m_engine.seed( seed );
+ m_lastSeed = seed;
}
diff --git a/src/EvtGenBase/EvtRandom.cpp b/src/EvtGenBase/EvtRandom.cpp
index 5d9e8da..c6725b6 100644
--- a/src/EvtGenBase/EvtRandom.cpp
+++ b/src/EvtGenBase/EvtRandom.cpp
@@ -1,94 +1,106 @@
/***********************************************************************
* Copyright 1998-2020 CERN for the benefit of the EvtGen authors *
* *
* This file is part of EvtGen. *
* *
* EvtGen is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* EvtGen is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with EvtGen. If not, see <https://www.gnu.org/licenses/>. *
***********************************************************************/
#include "EvtGenBase/EvtRandom.hh"
#include "EvtGenBase/EvtConst.hh"
#include "EvtGenBase/EvtRandomEngine.hh"
#include "EvtGenBase/EvtReport.hh"
#include <iostream>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
using std::endl;
thread_local EvtRandomEngine* EvtRandom::m_randomEngine = nullptr;
void EvtRandom::setRandomEngine( EvtRandomEngine* randomEngine )
{
m_randomEngine = randomEngine;
}
double EvtRandom::random()
{
if ( m_randomEngine == nullptr ) {
EvtGenReport( EVTGEN_ERROR, "EvtGen" )
<< "No random engine available in "
<< "EvtRandom::random()." << endl;
::abort();
}
return m_randomEngine->random();
}
-void EvtRandom::setSeed( unsigned int seed )
+void EvtRandom::setSeed( unsigned long int seed )
{
if ( m_randomEngine == nullptr ) {
EvtGenReport( EVTGEN_ERROR, "EvtGen" )
<< "No random engine available in "
<< "EvtRandom::random()." << endl;
::abort();
}
m_randomEngine->setSeed( seed );
}
+unsigned long int EvtRandom::lastSeed()
+{
+ if ( m_randomEngine == nullptr ) {
+ EvtGenReport( EVTGEN_ERROR, "EvtGen" )
+ << "No random engine available in "
+ << "EvtRandom::random()." << endl;
+ ::abort();
+ }
+
+ return m_randomEngine->lastSeed();
+}
+
// Random number routine to generate numbers between
// min and max. By djl on July 27, 1995.
double EvtRandom::Flat( double min, double max )
{
if ( min > max ) {
EvtGenReport( EVTGEN_ERROR, "EvtGen" )
<< "min>max in EvtRandom::Flat(" << min << "," << max << ")" << endl;
::abort();
}
return EvtRandom::random() * ( max - min ) + min;
}
double EvtRandom::Flat( double max )
{
return max * EvtRandom::random();
}
double EvtRandom::Flat()
{
return EvtRandom::random();
}
double EvtRandom::Gaussian()
{
double x = EvtRandom::random();
double y = EvtRandom::random();
return cos( x * EvtConst::twoPi ) * sqrt( -2.0 * log( 1 - y ) );
}
diff --git a/src/EvtGenBase/EvtSimpleRandomEngine.cpp b/src/EvtGenBase/EvtSimpleRandomEngine.cpp
index 3fcecec..51e665a 100644
--- a/src/EvtGenBase/EvtSimpleRandomEngine.cpp
+++ b/src/EvtGenBase/EvtSimpleRandomEngine.cpp
@@ -1,38 +1,38 @@
/***********************************************************************
* Copyright 1998-2020 CERN for the benefit of the EvtGen authors *
* *
* This file is part of EvtGen. *
* *
* EvtGen is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* EvtGen is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with EvtGen. If not, see <https://www.gnu.org/licenses/>. *
***********************************************************************/
#include "EvtGenBase/EvtSimpleRandomEngine.hh"
#include <iostream>
#include <math.h>
#include <stdio.h>
double EvtSimpleRandomEngine::random()
{
m_next = m_next * 1103515245 + 123345;
unsigned temp = (unsigned)( m_next / 65536 ) % 32768;
return ( temp + 1.0 ) / 32769.0;
}
-void EvtSimpleRandomEngine::setSeed( unsigned int seed )
+void EvtSimpleRandomEngine::setSeed( unsigned long int seed )
{
m_next = seed;
}
diff --git a/src/EvtGenExternal/EvtExternalGenFactory.cpp b/src/EvtGenExternal/EvtExternalGenFactory.cpp
index 9d11ad4..6b94d76 100644
--- a/src/EvtGenExternal/EvtExternalGenFactory.cpp
+++ b/src/EvtGenExternal/EvtExternalGenFactory.cpp
@@ -1,112 +1,113 @@
/***********************************************************************
* Copyright 1998-2020 CERN for the benefit of the EvtGen authors *
* *
* This file is part of EvtGen. *
* *
* EvtGen is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* EvtGen is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with EvtGen. If not, see <https://www.gnu.org/licenses/>. *
***********************************************************************/
#include "EvtGenExternal/EvtExternalGenFactory.hh"
#include "EvtGenBase/EvtReport.hh"
#ifdef EVTGEN_PYTHIA
#include "EvtGenExternal/EvtPythiaEngine.hh"
#endif
#ifdef EVTGEN_TAUOLA
#include "EvtGenExternal/EvtTauolaEngine.hh"
#endif
#include <iostream>
using std::endl;
EvtExternalGenFactory& EvtExternalGenFactory::getInstance()
{
static thread_local EvtExternalGenFactory theFactory;
return theFactory;
}
// Only define the generator if we have the external ifdef variable set
#ifdef EVTGEN_PYTHIA
void EvtExternalGenFactory::definePythiaGenerator( std::string xmlDir,
bool convertPhysCodes,
bool useEvtGenRandom )
{
EvtGenReport( EVTGEN_INFO, "EvtGen" )
<< "Defining EvtPythiaEngine: data tables defined in " << xmlDir << endl;
if ( convertPhysCodes == true ) {
EvtGenReport( EVTGEN_INFO, "EvtGen" )
<< "Pythia 6 codes in decay files will be converted to Pythia 8 codes"
<< endl;
} else {
EvtGenReport( EVTGEN_INFO, "EvtGen" )
<< "Pythia 8 codes need to be used in decay files" << endl;
}
if ( useEvtGenRandom == true ) {
EvtGenReport( EVTGEN_INFO, "EvtGen" )
<< "Using EvtGen random engine for Pythia 8 as well" << endl;
}
m_extGenMap[GenId::PythiaGenId] = std::make_unique<EvtPythiaEngine>(
xmlDir, convertPhysCodes, useEvtGenRandom );
}
#else
void EvtExternalGenFactory::definePythiaGenerator( std::string, bool, bool )
{
}
#endif
#ifdef EVTGEN_TAUOLA
-void EvtExternalGenFactory::defineTauolaGenerator( bool useEvtGenRandom )
+void EvtExternalGenFactory::defineTauolaGenerator( bool useEvtGenRandom,
+ bool seedTauolaFortran )
{
EvtGenReport( EVTGEN_INFO, "EvtGen" ) << "Defining EvtTauolaEngine." << endl;
- m_extGenMap[GenId::TauolaGenId] = std::make_unique<EvtTauolaEngine>(
- useEvtGenRandom );
+ m_extGenMap[GenId::TauolaGenId] =
+ std::make_unique<EvtTauolaEngine>( useEvtGenRandom, seedTauolaFortran );
}
#else
-void EvtExternalGenFactory::defineTauolaGenerator( bool )
+void EvtExternalGenFactory::defineTauolaGenerator( bool, bool )
{
}
#endif
EvtAbsExternalGen* EvtExternalGenFactory::getGenerator( const GenId genId )
{
ExtGenMap::iterator iter = m_extGenMap.find( genId );
if ( iter == m_extGenMap.end() ) {
EvtGenReport( EVTGEN_INFO, "EvtGen" )
<< "EvtAbsExternalGen::getGenerator: could not find generator for genId = "
//FIXME C++23 use std::to_underlying
<< static_cast<std::underlying_type_t<GenId>>( genId ) << endl;
return nullptr;
}
// Retrieve the external generator engine
auto& theGenerator = iter->second;
return theGenerator.get();
}
void EvtExternalGenFactory::initialiseAllGenerators()
{
for ( auto& [id, theGenerator] : m_extGenMap ) {
if ( theGenerator ) {
theGenerator->initialise();
}
}
}
diff --git a/src/EvtGenExternal/EvtExternalGenList.cpp b/src/EvtGenExternal/EvtExternalGenList.cpp
index cc7c093..27c4e31 100644
--- a/src/EvtGenExternal/EvtExternalGenList.cpp
+++ b/src/EvtGenExternal/EvtExternalGenList.cpp
@@ -1,120 +1,121 @@
/***********************************************************************
* Copyright 1998-2020 CERN for the benefit of the EvtGen authors *
* *
* This file is part of EvtGen. *
* *
* EvtGen is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* EvtGen is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with EvtGen. If not, see <https://www.gnu.org/licenses/>. *
***********************************************************************/
#include "EvtGenExternal/EvtExternalGenList.hh"
#include "EvtGenModels/EvtNoRadCorr.hh"
#include "EvtGenExternal/EvtExternalGenFactory.hh"
#include "EvtGenExternal/EvtPHOTOS.hh"
#include "EvtGenExternal/EvtPythia.hh"
#include "EvtGenExternal/EvtSherpaPhotons.hh"
#include "EvtGenExternal/EvtTauola.hh"
EvtExternalGenList::EvtExternalGenList( bool convertPythiaCodes,
std::string pythiaXmlDir,
std::string photonType,
- bool useEvtGenRandom ) :
+ bool useEvtGenRandom,
+ bool seedTauolaFortran ) :
m_photonType{ photonType }, m_useEvtGenRandom{ useEvtGenRandom }
{
// Instantiate the external generator factory
EvtExternalGenFactory& extFactory = EvtExternalGenFactory::getInstance();
if ( pythiaXmlDir.size() < 1 ) {
// If we have no string defined, check the value of the
// PYTHIA8DATA environment variable which should be set to the
// xmldoc Pythia directory
char* pythiaDataDir = getenv( "PYTHIA8DATA" );
if ( pythiaDataDir != nullptr ) {
pythiaXmlDir = pythiaDataDir;
}
}
extFactory.definePythiaGenerator( pythiaXmlDir, convertPythiaCodes,
useEvtGenRandom );
- extFactory.defineTauolaGenerator( useEvtGenRandom );
+ extFactory.defineTauolaGenerator( useEvtGenRandom, seedTauolaFortran );
}
EvtExternalGenList::~EvtExternalGenList()
{
}
#ifdef EVTGEN_PHOTOS
EvtAbsRadCorr* EvtExternalGenList::getPhotosModel( const double infraredCutOff,
const double maxWtInterference )
{
// Define the Photos model, which uses the EvtPHOTOS class.
EvtPHOTOS* photosModel = new EvtPHOTOS( m_photonType, m_useEvtGenRandom,
infraredCutOff, maxWtInterference );
return photosModel;
}
#else
EvtAbsRadCorr* EvtExternalGenList::getPhotosModel(
const double /*infraredCutOff*/, const double /*maxWtInterference*/ )
{
EvtGenReport( EVTGEN_ERROR, "EvtGen" )
<< " PHOTOS generator has been called for FSR simulation, but it was not switched on during compilation."
<< std::endl;
EvtGenReport( EVTGEN_ERROR, "EvtGen" )
<< " The simulation will be generated without FSR." << std::endl;
return new EvtNoRadCorr{};
}
#endif
#ifdef EVTGEN_SHERPA
EvtAbsRadCorr* EvtExternalGenList::getSherpaPhotonsModel(
const double infraredCutOff, const int mode, const int useME )
{
// Define the Photos model, which uses the EvtSherpaPhotonsEngine class.
EvtSherpaPhotons* sherpaPhotonsModel =
new EvtSherpaPhotons( m_useEvtGenRandom, infraredCutOff, mode, useME );
return sherpaPhotonsModel;
}
#else
EvtAbsRadCorr* EvtExternalGenList::getSherpaPhotonsModel(
const double /*infraredCutOff*/, const int /*mode*/, const int /*useME*/ )
{
EvtGenReport( EVTGEN_ERROR, "EvtGen" )
<< " Sherpa's PHOTONS++ generator has been called for FSR simulation, but Sherpa was not switched on during compilation."
<< std::endl;
EvtGenReport( EVTGEN_ERROR, "EvtGen" )
<< " The simulation will be generated without FSR." << std::endl;
return new EvtNoRadCorr{};
}
#endif
std::list<EvtDecayBase*> EvtExternalGenList::getListOfModels()
{
// Create the Pythia and Tauola models, which use their own engine classes.
EvtPythia* pythiaModel = new EvtPythia();
EvtTauola* tauolaModel = new EvtTauola();
std::list<EvtDecayBase*> extraModels;
extraModels.push_back( pythiaModel );
extraModels.push_back( tauolaModel );
// Return the list of models
return extraModels;
}
diff --git a/src/EvtGenExternal/EvtTauolaEngine.cpp b/src/EvtGenExternal/EvtTauolaEngine.cpp
index b444289..ecc7e87 100644
--- a/src/EvtGenExternal/EvtTauolaEngine.cpp
+++ b/src/EvtGenExternal/EvtTauolaEngine.cpp
@@ -1,591 +1,601 @@
/***********************************************************************
* Copyright 1998-2020 CERN for the benefit of the EvtGen authors *
* *
* This file is part of EvtGen. *
* *
* EvtGen is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* EvtGen is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with EvtGen. If not, see <https://www.gnu.org/licenses/>. *
***********************************************************************/
#ifdef EVTGEN_TAUOLA
#include "EvtGenExternal/EvtTauolaEngine.hh"
#include "EvtGenBase/EvtDecayTable.hh"
#include "EvtGenBase/EvtPDL.hh"
#include "EvtGenBase/EvtRandom.hh"
#include "EvtGenBase/EvtReport.hh"
#include "EvtGenBase/EvtSymTable.hh"
#include "EvtGenBase/EvtVector4R.hh"
#include "Tauola/Log.h"
#include "Tauola/Tauola.h"
#include <array>
#include <cmath>
#include <iostream>
+#include <limits>
#include <memory>
#include <sstream>
#include <string>
using std::endl;
// Mutex Tauola as it is not thread safe.
int EvtTauolaEngine::m_neutPropType = 0;
int EvtTauolaEngine::m_posPropType = 0;
int EvtTauolaEngine::m_negPropType = 0;
bool EvtTauolaEngine::m_initialised = false;
std::mutex EvtTauolaEngine::m_tauola_mutex;
-EvtTauolaEngine::EvtTauolaEngine( bool useEvtGenRandom ) :
- m_useEvtGenRandom{ useEvtGenRandom }
+EvtTauolaEngine::EvtTauolaEngine( bool useEvtGenRandom, bool seedTauolaFortran ) :
+ m_useEvtGenRandom{ useEvtGenRandom }, m_seedTauolaFortran{ seedTauolaFortran }
{
}
void EvtTauolaEngine::initialise()
{
const std::lock_guard<std::mutex> lock( m_tauola_mutex );
// Set up all possible tau decay modes.
// This should be done just before the first doDecay() call,
// since we want to make sure that any decay.dec files are processed
// first to get lists of particle modes and their alias definitions
// (for creating EvtParticles with the right history information).
if ( m_initialised ) {
return;
}
EvtGenReport( EVTGEN_INFO, "EvtGen" ) << "Setting up TAUOLA." << endl;
// These three lines are not really necessary since they are the default.
// But they are here so that we know what the initial conditions are.
// tau PDG code
Tauolapp::Tauola::setDecayingParticle( m_tauPDG );
// all modes allowed
Tauolapp::Tauola::setSameParticleDecayMode( Tauolapp::Tauola::All );
// all modes allowed
Tauolapp::Tauola::setOppositeParticleDecayMode( Tauolapp::Tauola::All );
// Limit the number of warnings printed out.
// Can't choose zero here, unfortunately.
Tauolapp::Log::SetWarningLimit( 1 );
// Initial the Tauola external generator
if ( m_useEvtGenRandom ) {
EvtGenReport( EVTGEN_INFO, "EvtGen" )
<< "Using EvtGen random number engine also for Tauola++" << endl;
Tauolapp::Tauola::setRandomGenerator( EvtRandom::Flat );
}
// Use the BaBar-tuned chiral current calculations by default. Can be changed using the
// TauolaCurrentOption keyword in decay files
Tauolapp::Tauola::setNewCurrents( 1 );
Tauolapp::Tauola::initialize();
// Set-up possible decay modes _after_ we have read the (user) decay file
this->setUpPossibleTauModes();
this->setOtherParameters();
m_initialised = true;
}
void EvtTauolaEngine::setUpPossibleTauModes()
{
// Get the decay table list defined by the decay.dec files.
// Only look for the first tau particle decay mode definitions with the Tauola name,
// since that generator only allows the same BFs for both tau+ and tau- decays.
// We can not choose a specific tau decay event-by-event, since this is
// only possible before we call Tauola::initialize().
// Otherwise, we could have selected a random mode ourselves for tau- and tau+
// separately (via selecting a random number and comparing it to be less than
// the cumulative BF) for each event.
const int nPDL = EvtPDL::entries();
bool gotAnyTauolaModes( false );
for ( int iPDL = 0; iPDL < nPDL; iPDL++ ) {
const EvtId particleId = EvtPDL::getEntry( iPDL );
const int PDGId = EvtPDL::getStdHep( particleId );
if ( abs( PDGId ) == m_tauPDG && gotAnyTauolaModes == false ) {
const int aliasInt = particleId.getAlias();
// Get the list of decay modes for this tau particle (alias)
const int nModes = EvtDecayTable::getInstance().getNModes( aliasInt );
// Vector to store tau mode branching fractions.
// The size of this vector equals the total number of possible
// Tauola decay modes. Initialise all BFs to zero.
std::vector<double> tauolaModeBFs;
tauolaModeBFs.assign( m_nTauolaModes, 0.0 );
double totalTauModeBF( 0.0 );
int nNonTauolaModes( 0 );
// Loop through each decay mode
for ( int iMode = 0; iMode < nModes; iMode++ ) {
EvtDecayBase* decayModel =
EvtDecayTable::getInstance().findDecayModel( aliasInt, iMode );
if ( decayModel ) {
// Check that the decay model name matches TAUOLA
std::string modelName = decayModel->getName();
if ( modelName == "TAUOLA" ) {
gotAnyTauolaModes = true;
// Extract the decay mode integer type and branching fraction
double BF = decayModel->getBranchingFraction();
int modeArrayInt = this->getModeInt( decayModel ) - 1;
if ( modeArrayInt >= 0 && modeArrayInt < m_nTauolaModes ) {
tauolaModeBFs[modeArrayInt] = BF;
totalTauModeBF += BF;
}
} else {
nNonTauolaModes++;
}
} // Decay mode exists
} // Loop over decay models
if ( gotAnyTauolaModes == true && nNonTauolaModes > 0 ) {
EvtGenReport( EVTGEN_ERROR, "EvtGen" )
<< "Please remove all non-TAUOLA decay modes for particle "
<< EvtPDL::name( particleId ) << endl;
::abort();
}
// Normalise all (non-zero) tau mode BFs to sum up to 1.0, and
// let Tauola know about these normalised branching fractions
if ( totalTauModeBF > 0.0 ) {
EvtGenReport( EVTGEN_INFO, "EvtGen" )
<< "Setting TAUOLA BF modes using the definitions for the particle "
<< EvtPDL::name( particleId ) << endl;
for ( int iTauMode = 0; iTauMode < m_nTauolaModes; iTauMode++ ) {
tauolaModeBFs[iTauMode] /= totalTauModeBF;
double modeBF = tauolaModeBFs[iTauMode];
EvtGenReport( EVTGEN_INFO, "EvtGen" )
<< "Setting TAUOLA BF for mode " << iTauMode + 1
<< " = " << modeBF << endl;
Tauolapp::Tauola::setTauBr( iTauMode + 1, modeBF );
}
EvtGenReport( EVTGEN_INFO, "EvtGen" )
<< "Any other TAUOLA BF modes for other tau particle decay mode definitions will be ignored!"
<< endl;
}
} // Got tau particle and have yet to get a TAUOLA mode
} // Loop through PDL entries
}
int EvtTauolaEngine::getModeInt( EvtDecayBase* decayModel ) const
{
int modeInt( 0 );
if ( decayModel ) {
int nVars = decayModel->getNArg();
if ( nVars > 0 ) {
modeInt = static_cast<int>( decayModel->getArg( 0 ) );
}
}
return modeInt;
}
void EvtTauolaEngine::setOtherParameters()
{
// Set other Tauola parameters using the "Defined" keyword in the decay file. If any of
// these are not found in the decay file, then default values are assumed/kept
// 1) TauolaNeutralProp: Specify the neutral propagator type used for spin matrix calculations
// "Z" (default), "Gamma", "Higgs" (H0), "PseudoHiggs" (A0), "MixedHiggs" (A0/H0)
int iErr( 0 );
std::string neutPropName = EvtSymTable::get( "TauolaNeutralProp", iErr );
if ( neutPropName == "Z0" || neutPropName == "Z" ) {
m_neutPropType = Tauolapp::TauolaParticle::Z0;
} else if ( neutPropName == "Gamma" ) {
m_neutPropType = Tauolapp::TauolaParticle::GAMMA;
} else if ( neutPropName == "Higgs" ) {
m_neutPropType = Tauolapp::TauolaParticle::HIGGS;
} else if ( neutPropName == "PseudoHiggs" ) {
m_neutPropType = Tauolapp::TauolaParticle::HIGGS_A;
} else if ( neutPropName == "MixedHiggs" ) {
m_neutPropType = Tauolapp::Tauola::getHiggsScalarPseudoscalarPDG();
}
if ( m_neutPropType != 0 ) {
EvtGenReport( EVTGEN_INFO, "EvtGen" )
<< "TAUOLA neutral spin propagator PDG id set to " << m_neutPropType
<< endl;
}
// 2) TauolaChargedProp: Specify the charged propagator type used for spin matrix calculations
// "W" (default), "Higgs" (H+/H-)
std::string chargedPropName = EvtSymTable::get( "TauolaChargedProp", iErr );
if ( chargedPropName == "W" ) {
m_negPropType = Tauolapp::TauolaParticle::W_MINUS;
m_posPropType = Tauolapp::TauolaParticle::W_PLUS;
} else if ( chargedPropName == "Higgs" ) {
m_negPropType = Tauolapp::TauolaParticle::HIGGS_MINUS;
m_posPropType = Tauolapp::TauolaParticle::HIGGS_PLUS;
}
if ( m_negPropType != 0 ) {
EvtGenReport( EVTGEN_INFO, "EvtGen" )
<< "TAUOLA negative charge spin propagator PDG id set to "
<< m_negPropType << endl;
}
if ( m_posPropType != 0 ) {
EvtGenReport( EVTGEN_INFO, "EvtGen" )
<< "TAUOLA positive charge spin propagator PDG id set to "
<< m_posPropType << endl;
}
// 3) TauolaHiggsMixingAngle: Specify the mixing angle between the neutral scalar & pseudoscalar Higgs
// A0/H0; the default mixing angle is pi/4 radians
std::string mixString = EvtSymTable::get( "TauolaHiggsMixingAngle", iErr );
// If the definition name is not found, get() just returns the first argument string
if ( mixString != "TauolaHiggsMixingAngle" ) {
double mixAngle = std::atof( mixString.c_str() );
EvtGenReport( EVTGEN_INFO, "EvtGen" )
<< "TAUOLA Higgs mixing angle set to " << mixAngle << " radians"
<< endl;
Tauolapp::Tauola::setHiggsScalarPseudoscalarMixingAngle( mixAngle );
}
// 4) TauolaBRi, where i = 1,2,3,4: Redefine sub-channel branching fractions using the setTaukle
// function, after initialized() has been called. Default values = 0.5, 0.5, 0.5 and 0.6667
std::array<double, 4> BRVect{ 0.5, 0.5, 0.5, 0.6667 };
for ( int j = 0; j < 4; j++ ) {
std::ostringstream o;
o << j + 1;
std::string BRName = "TauolaBR" + o.str();
std::string stringBR = EvtSymTable::get( BRName, iErr );
// If the definition name is not found, get() just returns the first argument string
if ( stringBR != BRName ) {
BRVect[j] = std::atof( stringBR.c_str() );
}
}
EvtGenReport( EVTGEN_INFO, "EvtGen" )
<< "TAUOLA::setTaukle values are " << BRVect[0] << ", " << BRVect[1]
<< ", " << BRVect[2] << ", " << BRVect[3] << endl;
Tauolapp::Tauola::setTaukle( BRVect[0], BRVect[1], BRVect[2], BRVect[3] );
// 5) Specify the hadronic current option, e.g. orig CLEO = 0, BaBar-tuned = 1 (default), ...
// No check is made by EvtGen on valid integer options - its just passed to Tauola
std::string currentOption = EvtSymTable::get( "TauolaCurrentOption", iErr );
// If the definition name is not found, get() just returns the first argument string
if ( currentOption != "TauolaCurrentOption" ) {
int currentOpt = std::atoi( currentOption.c_str() );
EvtGenReport( EVTGEN_INFO, "EvtGen" )
<< "TAUOLA current option = " << currentOpt << endl;
Tauolapp::Tauola::setNewCurrents( currentOpt );
}
}
bool EvtTauolaEngine::doDecay( EvtParticle* tauParticle )
{
if ( !m_initialised ) {
this->initialise();
}
if ( !tauParticle ) {
return false;
}
// Check that we have a tau particle.
EvtId partId = tauParticle->getId();
if ( abs( EvtPDL::getStdHep( partId ) ) != m_tauPDG ) {
return false;
}
int nTauDaug = tauParticle->getNDaug();
// If the number of tau daughters is not zero, then we have already decayed
// it using Tauola/another decay algorithm.
if ( nTauDaug > 0 ) {
return true;
}
this->decayTauEvent( tauParticle );
return true;
}
void EvtTauolaEngine::decayTauEvent( EvtParticle* tauParticle )
{
// Either we have a tau particle within a decay chain, or a single particle.
// Create a dummy HepMC event & vertex for the parent particle, containing the tau as
// one of the outgoing particles. If we have a decay chain, the parent will be the
// incoming particle, while the daughters, including the tau, are outgoing particles.
// For the single particle case, the incoming particle is null, while the single tau
// is the only outgoing particle.
// We can then pass this event to Tauola which should then decay the tau particle.
// We also consider all other tau particles from the parent decay in the logic below.
// Create the dummy event.
auto theEvent = std::make_unique<GenEvent>( Units::GEV, Units::MM );
// Create the decay "vertex".
GenVertexPtr theVertex = newGenVertexPtr();
theEvent->add_vertex( theVertex );
// Get the parent of this tau particle
EvtParticle* theParent = tauParticle->getParent();
GenParticlePtr hepMCParent( nullptr );
// Assign the parent particle as the incoming particle to the vertex.
if ( theParent ) {
hepMCParent = this->createGenParticle( theParent );
theVertex->add_particle_in( hepMCParent );
} else {
// The tau particle has no parent. Set "itself" as the incoming particle for the first vertex.
// This is needed, otherwise Tauola warns of momentum non-conservation for this (1st) vertex.
GenParticlePtr tauGenInit = this->createGenParticle( tauParticle );
theVertex->add_particle_in( tauGenInit );
}
// Find all daughter particles and assign them as outgoing particles to the vertex.
// This will include the tau particle we are currently processing.
// If the parent decay has more than one tau particle, we need to include them as well.
// This is important since Tauola needs the correct physics correlations: we do not
// want Tauola to decay each particle separately if they are from tau pair combinations.
// Tauola will process the event, and we will create EvtParticles from all tau decay
// products, i.e. the tau particle we currently have and any other tau particles.
// EvtGen will try to decay the other tau particle(s) by calling EvtTauola and therefore
// this function. However, we check to see if the tau candidate has any daughters already.
// If it does, then we have already set the tau decay products from Tauola.
// Map to store (HepMC,EvtParticle) pairs for each tau candidate from the parent
// decay. This is needed to find out what EvtParticle corresponds to a given tau HepMC
// candidate: we do not want to recreate existing EvtParticle pointers.
std::map<GenParticlePtr, EvtParticle*> tauMap;
// Keep track of the original EvtId of the parent particle, since we may need to set
// the equivalent HepMCParticle has a gauge boson to let Tauola calculate spin effects
EvtId origParentId( -1, -1 );
if ( theParent ) {
// Original parent id
origParentId = EvtPDL::getId( theParent->getName() );
// Find all tau particles in the decay tree and store them in the map.
// Keep track of how many tau daughters this parent particle has
int nTaus( 0 );
int nDaug( theParent->getNDaug() );
int iDaug( 0 );
for ( iDaug = 0; iDaug < nDaug; iDaug++ ) {
EvtParticle* theDaughter = theParent->getDaug( iDaug );
if ( theDaughter ) {
GenParticlePtr hepMCDaughter = this->createGenParticle(
theDaughter );
theVertex->add_particle_out( hepMCDaughter );
EvtId theId = theDaughter->getId();
int PDGInt = EvtPDL::getStdHep( theId );
if ( abs( PDGInt ) == m_tauPDG ) {
// Delete any siblings for the tau particle
if ( theDaughter->getNDaug() > 0 ) {
theDaughter->deleteDaughters( false );
}
tauMap[hepMCDaughter] = theDaughter;
nTaus++;
} else {
// Treat all other particles as "stable"
hepMCDaughter->set_status( Tauolapp::TauolaParticle::STABLE );
}
} // theDaughter != 0
} // Loop over daughters
// For the parent particle, artifically set the PDG to a boson with the same 4-momentum
// so that spin correlations are calculated inside Tauola.
// This leaves the original parent m_EvtParticle_ unchanged
if ( nTaus > 0 && hepMCParent ) {
int parCharge = EvtPDL::chg3( origParentId ) /
3; // (3*particle charge)/3 = particle charge
if ( parCharge == 0 && m_neutPropType != 0 ) {
hepMCParent->set_pdg_id( m_neutPropType );
} else if ( parCharge == -1 && m_negPropType != 0 ) {
hepMCParent->set_pdg_id( m_negPropType );
} else if ( parCharge == 1 && m_posPropType != 0 ) {
hepMCParent->set_pdg_id( m_posPropType );
}
}
} else {
// We only have the one tau particle. Store only this in the map.
GenParticlePtr singleTau = this->createGenParticle( tauParticle );
theVertex->add_particle_out( singleTau );
tauMap[singleTau] = tauParticle;
}
{
const std::lock_guard<std::mutex> lock( m_tauola_mutex );
+ if ( m_useEvtGenRandom && m_seedTauolaFortran ) {
+ static thread_local auto lastSeed{
+ std::numeric_limits<unsigned long int>::max() };
+ if ( lastSeed != EvtRandom::lastSeed() ) {
+ lastSeed = EvtRandom::lastSeed();
+ Tauolapp::Tauola::setSeed( lastSeed, 0, 0 );
+ }
+ }
+
// Now pass the event to Tauola for processing
// Create a Tauola event object
#ifdef EVTGEN_HEPMC3
Tauolapp::TauolaHepMC3Event tauolaEvent( theEvent.get() );
#else
Tauolapp::TauolaHepMCEvent tauolaEvent( theEvent.get() );
#endif
// Run the Tauola algorithm
tauolaEvent.decayTaus();
}
// Loop over all tau particles in the HepMC event and create their EvtParticle daughters.
// Store all final "stable" descendent particles as the tau daughters, i.e.
// let Tauola decay any resonances such as a_1 or rho.
// If there is more than one tau particle in the event, then also create the
// corresponding EvtParticles for their daughters as well. They will not be
// re-decayed since we check at the start of this function if the tau particle has
// any daughters before running Tauola decayTaus().
#ifdef EVTGEN_HEPMC3
for ( auto aParticle : theEvent->particles() ) {
#else
HepMC::GenEvent::particle_iterator eventIter;
for ( eventIter = theEvent->particles_begin();
eventIter != theEvent->particles_end(); ++eventIter ) {
// Check to see if we have a tau particle
HepMC::GenParticle* aParticle = ( *eventIter );
#endif
if ( aParticle && abs( aParticle->pdg_id() ) == m_tauPDG ) {
// Find out what EvtParticle corresponds to the HepMC particle.
// We need this to create and attach EvtParticle daughters.
EvtParticle* tauEvtParticle = tauMap[aParticle];
if ( tauEvtParticle ) {
// Get the tau 4-momentum in the lab (first mother) frame. We need to boost
// all the tau daughters to this frame, such that daug.getP4() is in the tau restframe.
EvtVector4R tauP4CM = tauEvtParticle->getP4Lab();
tauP4CM.set( tauP4CM.get( 0 ), -tauP4CM.get( 1 ),
-tauP4CM.get( 2 ), -tauP4CM.get( 3 ) );
// Get the decay vertex for the tau particle
GenVertexPtr endVertex = aParticle->end_vertex();
std::vector<EvtId> daugIdVect;
std::vector<EvtVector4R> daugP4Vect;
// Loop through all descendants
#ifdef EVTGEN_HEPMC3
for ( auto tauDaug :
HepMC3::Relatives::DESCENDANTS( endVertex ) ) {
#else
HepMC::GenVertex::particle_iterator tauIter;
// Loop through all descendants
for ( tauIter = endVertex->particles_begin( HepMC::descendants );
tauIter != endVertex->particles_end( HepMC::descendants );
++tauIter ) {
HepMC::GenParticle* tauDaug = ( *tauIter );
#endif
// Check to see if this descendant has its own decay vertex, e.g. rho resonance.
// If so, skip this daughter and continue looping through the descendant list
// until we reach the final "stable" products (e.g. pi pi from rho -> pi pi).
GenVertexPtr daugDecayVtx = tauDaug->end_vertex();
if ( daugDecayVtx ) {
continue;
}
// Store the particle id and 4-momentum
int tauDaugPDG = tauDaug->pdg_id();
EvtId daugId = EvtPDL::evtIdFromStdHep( tauDaugPDG );
daugIdVect.push_back( daugId );
FourVector tauDaugP4 = tauDaug->momentum();
double tauDaug_px = tauDaugP4.px();
double tauDaug_py = tauDaugP4.py();
double tauDaug_pz = tauDaugP4.pz();
double tauDaug_E = tauDaugP4.e();
EvtVector4R daugP4( tauDaug_E, tauDaug_px, tauDaug_py,
tauDaug_pz );
daugP4Vect.push_back( daugP4 );
} // Loop over HepMC tau daughters
// Create the tau EvtParticle daughters and assign their ids and 4-mtm
int nDaug = daugIdVect.size();
tauEvtParticle->makeDaughters( nDaug, daugIdVect );
int iDaug( 0 );
for ( iDaug = 0; iDaug < nDaug; iDaug++ ) {
EvtParticle* theDaugPart = tauEvtParticle->getDaug( iDaug );
if ( theDaugPart ) {
EvtId theDaugId = daugIdVect[iDaug];
EvtVector4R theDaugP4 = daugP4Vect[iDaug];
theDaugP4.applyBoostTo(
tauP4CM ); // Boost the 4-mtm to the tau rest frame
theDaugPart->init( theDaugId, theDaugP4 );
}
} // Loop over tau daughters
}
} // We have a tau HepMC particle in the event
}
theEvent->clear();
}
GenParticlePtr EvtTauolaEngine::createGenParticle( const EvtParticle* theParticle ) const
{
// Method to create an HepMC::GenParticle version of the given EvtParticle.
if ( theParticle == nullptr ) {
return nullptr;
}
// Get the 4-momentum (E, px, py, pz) for the EvtParticle
EvtVector4R p4 = theParticle->getP4Lab();
// Convert this to the HepMC 4-momentum
double E = p4.get( 0 );
double px = p4.get( 1 );
double py = p4.get( 2 );
double pz = p4.get( 3 );
FourVector hepMC_p4( px, py, pz, E );
int PDGInt = EvtPDL::getStdHep( theParticle->getId() );
// Set the status flag for the particle.
int status = Tauolapp::TauolaParticle::HISTORY;
GenParticlePtr genParticle = newGenParticlePtr( hepMC_p4, PDGInt, status );
return genParticle;
}
#endif
diff --git a/test/testDecayModel.cc b/test/testDecayModel.cc
index 41d50be..933ae7f 100644
--- a/test/testDecayModel.cc
+++ b/test/testDecayModel.cc
@@ -1,1828 +1,1829 @@
/***********************************************************************
* Copyright 1998-2020 CERN for the benefit of the EvtGen authors *
* *
* This file is part of EvtGen. *
* *
* EvtGen is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* EvtGen is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with EvtGen. If not, see <https://www.gnu.org/licenses/>. *
***********************************************************************/
#include "testDecayModel.hh"
#include "EvtGen/EvtGen.hh"
#include "EvtGenBase/EvtAbsRadCorr.hh"
#include "EvtGenBase/EvtConst.hh"
#include "EvtGenBase/EvtDecayBase.hh"
#include "EvtGenBase/EvtId.hh"
#include "EvtGenBase/EvtKine.hh"
#include "EvtGenBase/EvtMTRandomEngine.hh"
#include "EvtGenBase/EvtPDL.hh"
#include "EvtGenBase/EvtParticle.hh"
#include "EvtGenBase/EvtParticleFactory.hh"
#include "EvtGenBase/EvtRandom.hh"
#include "EvtGenBase/EvtVector4R.hh"
#ifdef EVTGEN_EXTERNAL
#include "EvtGenExternal/EvtExternalGenList.hh"
#endif
#include "TROOT.h"
#include "tbb/tbb.h"
#include <chrono>
#include <fstream>
#include <future>
#include <iostream>
#include <list>
#include <memory>
using nlohmann::json;
std::once_flag TestDecayModel::m_createDecFile_threadlock;
TestHistos::TestHistos( const std::string& parentName, const json& config )
{
// Histogram information
const std::size_t nHistos{ config.size() };
m_1DhistVect.reserve( nHistos );
m_2DhistVect.reserve( nHistos );
for ( const auto& hInfo : config ) {
const auto varTitle{ hInfo.at( "title" ).get<std::string>() };
const auto varName{ hInfo.at( "variable" ).get<std::string>() };
// Integer values that define what particles need to be used
// for invariant mass combinations or helicity angles etc
const auto d1{ hInfo.at( "d1" ).get<int>() };
const auto d2{ hInfo.at( "d2" ).get<int>() };
const auto nBins{ hInfo.at( "nbins" ).get<int>() };
const auto xmin{ hInfo.at( "xmin" ).get<double>() };
const auto xmax{ hInfo.at( "xmax" ).get<double>() };
std::string histName( varName.c_str() );
if ( d1 != 0 ) {
histName += "_";
histName += std::to_string( d1 );
}
if ( d2 != 0 ) {
histName += "_";
histName += std::to_string( d2 );
}
if ( !hInfo.contains( "variableY" ) ) {
auto hist = std::make_unique<TH1D>( histName.c_str(),
varTitle.c_str(), nBins, xmin,
xmax );
m_1DhistVect.emplace_back(
std::make_pair( HistInfo{ varName, d1, d2 }, std::move( hist ) ) );
} else {
const auto varNameY{ hInfo.at( "variableY" ).get<std::string>() };
const auto d1Y{ hInfo.at( "d1Y" ).get<int>() };
const auto d2Y{ hInfo.at( "d2Y" ).get<int>() };
const auto nBinsY{ hInfo.at( "nbinsY" ).get<int>() };
const auto ymin{ hInfo.at( "ymin" ).get<double>() };
const auto ymax{ hInfo.at( "ymax" ).get<double>() };
histName += "_";
histName += varNameY;
if ( d1Y != 0 ) {
histName += "_";
histName += std::to_string( d1Y );
}
if ( d2Y != 0 ) {
histName += "_";
histName += std::to_string( d2Y );
}
auto hist = std::make_unique<TH2D>( histName.c_str(),
varTitle.c_str(), nBins, xmin,
xmax, nBinsY, ymin, ymax );
m_2DhistVect.emplace_back(
std::make_pair( HistInfo{ varName, d1, d2, varNameY, d1Y, d2Y },
std::move( hist ) ) );
}
}
// Add a mixed/unmixed histogram
// Useful for the case where the parent is a neutral K, D or B
const std::array<std::string, 14> parentsThatMix{
"B_s0", "anti-B_s0", "B_s0L", "B_s0H", "B0", "anti-B0", "B0L",
"B0H", "D0", "anti-D0", "K0", "anti-K0", "K_S0", "K_L0" };
if ( std::find( parentsThatMix.begin(), parentsThatMix.end(), parentName ) !=
parentsThatMix.end() ) {
const std::string varTitle{ parentName + " mixed" };
m_mixedHist = std::make_unique<TH1D>( "mixed", varTitle.c_str(), 2, 0.0,
2.0 );
// TODO maybe set bin labels?
}
}
TestHistos::TestHistos( const TestHistos& rhs )
{
m_1DhistVect.reserve( rhs.m_1DhistVect.size() );
for ( auto& [info, hist] : rhs.m_1DhistVect ) {
auto newHist = std::unique_ptr<TH1>{ static_cast<TH1*>( hist->Clone() ) };
m_1DhistVect.push_back( std::make_pair( info, std::move( newHist ) ) );
}
m_2DhistVect.reserve( rhs.m_2DhistVect.size() );
for ( auto& [info, hist] : rhs.m_2DhistVect ) {
auto newHist = std::unique_ptr<TH2>{ static_cast<TH2*>( hist->Clone() ) };
m_2DhistVect.push_back( std::make_pair( info, std::move( newHist ) ) );
}
if ( rhs.m_mixedHist ) {
m_mixedHist.reset( static_cast<TH1*>( rhs.m_mixedHist->Clone() ) );
}
}
TestHistos::TestHistos( TestHistos&& rhs ) noexcept
{
this->swap( rhs );
}
TestHistos& TestHistos::operator=( const TestHistos& rhs )
{
TestHistos tmp{ rhs };
this->swap( tmp );
return *this;
}
TestHistos& TestHistos::operator=( TestHistos&& rhs ) noexcept
{
this->swap( rhs );
return *this;
}
void TestHistos::swap( TestHistos& rhs ) noexcept
{
m_1DhistVect.swap( rhs.m_1DhistVect );
m_2DhistVect.swap( rhs.m_2DhistVect );
std::swap( m_mixedHist, rhs.m_mixedHist );
}
void TestHistos::add( const TestHistos& rhs )
{
// handle the special case where we have been default constructed and the rhs has not
if ( m_1DhistVect.empty() && m_2DhistVect.empty() && !m_mixedHist ) {
( *this ) = rhs;
return;
}
// TODO - should really check that the sets of histograms are the same between left and right
const std::size_t n1DHists{ rhs.m_1DhistVect.size() };
for ( std::size_t i{ 0 }; i < n1DHists; ++i ) {
m_1DhistVect[i].second->Add( rhs.m_1DhistVect[i].second.get() );
}
const std::size_t n2DHists{ rhs.m_2DhistVect.size() };
for ( std::size_t i{ 0 }; i < n2DHists; ++i ) {
m_2DhistVect[i].second->Add( rhs.m_2DhistVect[i].second.get() );
}
if ( m_mixedHist && rhs.m_mixedHist ) {
m_mixedHist->Add( rhs.m_mixedHist.get() );
}
}
void TestHistos::normalise()
{
for ( auto& [_, hist] : m_1DhistVect ) {
const double area{ hist->Integral() };
if ( area > 0.0 ) {
hist->Scale( 1.0 / area );
}
}
for ( auto& [_, hist] : m_2DhistVect ) {
const double area{ hist->Integral() };
if ( area > 0.0 ) {
hist->Scale( 1.0 / area );
}
}
if ( m_mixedHist ) {
const double area{ m_mixedHist->Integral() };
if ( area > 0.0 ) {
m_mixedHist->Scale( 1.0 / area );
}
}
}
void TestHistos::save( TFile* outputFile )
{
outputFile->cd();
for ( auto& [info, hist] : m_1DhistVect ) {
hist->SetDirectory( outputFile );
hist->Write();
hist.release();
}
for ( auto& [info, hist] : m_2DhistVect ) {
hist->SetDirectory( outputFile );
hist->Write();
hist.release();
}
if ( m_mixedHist ) {
m_mixedHist->SetDirectory( outputFile );
m_mixedHist->Write();
m_mixedHist.release();
}
}
TestDecayModel::TestDecayModel( const json& config ) :
m_config{ checkMandatoryFields( config )
? readConfig( config )
: throw std::runtime_error{
"ERROR : json does not contain all required fields" } }
{
}
bool TestDecayModel::checkMandatoryFields( const json& config )
{
const std::array<std::string, 7> mandatoryFields{ "parent", "daughters",
"models", "parameters",
"outfile", "events",
"histograms" };
const std::array<std::string, 7> mandatoryHistoFields{
"title", "variable", "d1", "d2", "nbins", "xmin", "xmax" };
const std::array<std::string, 6> extra2DHistoFields{ "variableY", "d1Y",
"d2Y", "nbinsY",
"ymin", "ymax" };
bool allMandatoryFields{ true };
for ( const auto& field : mandatoryFields ) {
if ( !config.contains( field ) ) {
std::cerr << "ERROR : json does not contain required field: " << field
<< std::endl;
allMandatoryFields = false;
continue;
}
if ( field == "histograms" ) {
const json& jHistos{ config.at( "histograms" ) };
for ( const auto& hInfo : jHistos ) {
for ( const auto& hField : mandatoryHistoFields ) {
if ( !hInfo.contains( hField ) ) {
std::cerr
<< "ERROR : json does not contain required field for histogram definition: "
<< hField << std::endl;
allMandatoryFields = false;
}
}
if ( hInfo.contains( extra2DHistoFields[0] ) ) {
for ( const auto& hField : extra2DHistoFields ) {
if ( !hInfo.contains( hField ) ) {
std::cerr
<< "ERROR : json does not contain required field for 2D histogram definition: "
<< hField << std::endl;
allMandatoryFields = false;
}
}
}
}
}
}
return allMandatoryFields;
}
TestConfig TestDecayModel::readConfig( const json& config )
{
TestConfig cfg;
// Get all the mandatory fields first
cfg.parentName = config.at( "parent" ).get<std::string>();
cfg.daughterNames = config.at( "daughters" ).get<std::vector<std::string>>();
cfg.modelNames = config.at( "models" ).get<std::vector<std::string>>();
cfg.modelParameters =
config.at( "parameters" ).get<std::vector<std::vector<std::string>>>();
cfg.nEvents = config.at( "events" ).get<std::size_t>();
// Histogram information
cfg.testHistograms = TestHistos{ cfg.parentName, config.at( "histograms" ) };
// Then check for optional fields, setting default values if not present
if ( config.contains( "grand_daughters" ) &&
config.at( "grand_daughters" ).is_array() ) {
cfg.grandDaughterNames = config.at( "grand_daughters" )
.get<std::vector<std::vector<std::string>>>();
}
if ( config.contains( "extras" ) && config.at( "extras" ).is_array() ) {
cfg.extraCommands = config.at( "extras" ).get<std::vector<std::string>>();
}
// Set the number of threads to use, 1 by default
cfg.nThreads = 1;
if ( config.contains( "threads" ) ) {
cfg.nThreads = config.at( "threads" ).get<std::size_t>();
}
// Set the FSR generator. Use PHOTOS by default.
cfg.fsrGenerator = FSRGenerator::PHOTOS;
if ( config.contains( "fsr_generator" ) ) {
cfg.fsrGenerator = config.at( "fsr_generator" ).get<FSRGenerator>();
}
// Set the type of threading to use, stdlib by default
cfg.threadModel = ThreadModel::StdLib;
if ( config.contains( "thread_model" ) ) {
cfg.threadModel = config.at( "thread_model" ).get<ThreadModel>();
}
// Set the RNG seed base (defaults to zero), to which the event number is added
cfg.rngSeed = 0;
if ( config.contains( "rng_seed" ) ) {
cfg.rngSeed = config.at( "rng_seed" ).get<std::size_t>();
}
// Set reference and output file names
// Insert fsrGenerator name if FSR simulation is not deactivated
const bool noFSR = std::find( cfg.extraCommands.begin(),
cfg.extraCommands.end(),
"noFSR" ) != cfg.extraCommands.end();
const std::string fileNameEnd =
noFSR ? ".root" : "_" + to_string( cfg.fsrGenerator ) + ".root";
const auto outFileStrSize = config.at( "outfile" ).get<std::string>().size() -
5;
cfg.outFileName =
config.at( "outfile" ).get<std::string>().substr( 0, outFileStrSize ) +
fileNameEnd;
cfg.refFileName = "Ref/" + cfg.outFileName;
cfg.decFileName = cfg.outFileName.substr( 0, cfg.outFileName.size() - 5 ) +
".dec";
cfg.debugFlag = ( config.contains( "debug_flag" ) &&
config.at( "debug_flag" ).is_boolean() )
? config.at( "debug_flag" ).get<bool>()
: false;
if ( config.contains( "do_conjugate_decay" ) &&
config.at( "do_conjugate_decay" ).is_array() ) {
cfg.doConjDecay =
config.at( "do_conjugate_decay" ).get<std::vector<bool>>();
}
if ( cfg.doConjDecay.size() != cfg.modelNames.size() ) {
cfg.doConjDecay.resize( cfg.modelNames.size(), false );
}
return cfg;
}
void TestDecayModel::run()
{
TestHistos theHistos;
const auto start{ std::chrono::steady_clock::now() };
if ( m_config.nThreads > 1 ) {
// Run multi-threaded using the specified thread model
switch ( m_config.threadModel ) {
case ThreadModel::StdLib:
theHistos = runStdThreads();
break;
case ThreadModel::TBB:
theHistos = runTBBThreads();
break;
}
} else {
// Run in the main thread
theHistos = runDecayBody( 0, m_config.nEvents );
}
const auto end{ std::chrono::steady_clock::now() };
const std::chrono::duration<double, std::milli> elapsed_ms{ ( end - start ) };
const std::chrono::duration<double, std::milli> elapsed_ms_per_event{
elapsed_ms / m_config.nEvents };
std::cout << "Took " << elapsed_ms.count() << " ms to generate "
<< m_config.nEvents << " events using " << m_config.nThreads
<< " " << to_string( m_config.threadModel ) << " threads ("
<< elapsed_ms_per_event.count() << " ms per event)" << std::endl;
// Normalise histograms.
theHistos.normalise();
// Compare with reference histograms
compareHistos( theHistos, m_config.refFileName );
// Create the root output file and write the histograms to it
// Only save the mixed/unmixed histogram for neutral K, D, B
std::unique_ptr<TFile> outFile{
TFile::Open( m_config.outFileName.c_str(), "recreate" ) };
theHistos.save( outFile.get() );
outFile->Close();
std::cout << "Created output file: " << m_config.outFileName.c_str()
<< std::endl;
}
TestHistos TestDecayModel::runStdThreads() const
{
// Determine the number of threads and the number of events per thread
const std::size_t nThreads{ std::min( m_config.nThreads, m_config.nEvents ) };
const std::size_t nEventsPerThread{ ( m_config.nEvents % nThreads )
? m_config.nEvents / nThreads + 1
: m_config.nEvents / nThreads };
// Create the store for the results from each thread
std::vector<std::future<TestHistos>> allHistos;
allHistos.reserve( nThreads );
// Launch the threads
std::size_t firstEvent{ 0 };
std::size_t nEvents{ nEventsPerThread };
for ( std::size_t iThread{ 0 }; iThread < nThreads; ++iThread ) {
// The last thread may need to generate slightly fewer events to get the required total
if ( ( firstEvent + nEvents ) >= m_config.nEvents ) {
nEvents = m_config.nEvents - firstEvent;
}
std::cout << "Thread " << iThread << " will generate " << nEvents
<< " events" << std::endl;
allHistos.emplace_back(
std::async( std::launch::async, [this, firstEvent, nEvents]() {
return runDecayBody( firstEvent, nEvents );
} ) );
firstEvent += nEvents;
}
// Now wait for each thread to finish
bool complete{ false };
do {
// Set the flag to completed and set it back if we find incomplete threads
complete = true;
for ( auto& future : allHistos ) {
auto status = future.wait_for( std::chrono::seconds( 10 ) );
if ( status != std::future_status::ready ) {
complete = false;
}
}
} while ( !complete );
// Accumulate the histograms from all threads
TestHistos theHistos{ allHistos[0].get() };
for ( std::size_t iThread{ 1 }; iThread < nThreads; ++iThread ) {
theHistos.add( allHistos[iThread].get() );
}
return theHistos;
}
TestHistos TestDecayModel::runTBBThreads() const
{
tbb::global_control gc{ tbb::global_control::parameter::max_allowed_parallelism,
m_config.nThreads };
TestHistos init;
return tbb::parallel_reduce(
tbb::blocked_range<std::size_t>( 0, m_config.nEvents ), init,
[this]( const tbb::blocked_range<std::size_t>& range,
const TestHistos& init ) -> TestHistos {
std::cout << "Thread "
<< tbb::this_task_arena::current_thread_index()
<< " will generate " << range.size()
<< " events: " << range.begin() << " - " << range.end()
<< std::endl;
TestHistos tmp{ init };
tmp.add( runDecayBody( range.begin(), range.size() ) );
return tmp;
},
[]( const TestHistos& lhs, const TestHistos& rhs ) -> TestHistos {
TestHistos tmp{ lhs };
tmp.add( rhs );
return tmp;
} );
}
TestHistos TestDecayModel::runDecayBody( const std::size_t firstEvent,
const std::size_t nEvents ) const
{
// Initialise the EvtGen object and hence the EvtPDL tables
// The EvtGen object is used by generateEvents, while the
// latter are also used within createDecFile
// Define the random number generator
static thread_local auto randomEngine{ std::make_unique<EvtMTRandomEngine>() };
// TODO - need to streamline the extra models stuff
static thread_local EvtAbsRadCorr* radCorrEngine{ nullptr };
std::list<EvtDecayBase*> extraModels;
static thread_local bool initialised{ false };
#ifdef EVTGEN_EXTERNAL
if ( !initialised ) {
bool convertPythiaCodes( false );
bool useEvtGenRandom( true );
+ bool seedTauolaFortran( true );
EvtExternalGenList genList( convertPythiaCodes, "", "gamma",
- useEvtGenRandom );
+ useEvtGenRandom, seedTauolaFortran );
switch ( m_config.fsrGenerator ) {
case FSRGenerator::PHOTOS:
radCorrEngine = genList.getPhotosModel();
break;
case FSRGenerator::SherpaPhotons1:
radCorrEngine = genList.getSherpaPhotonsModel( 1e-7, 1, 0 );
break;
case FSRGenerator::SherpaPhotons20:
radCorrEngine = genList.getSherpaPhotonsModel( 1e-7, 2, 0 );
break;
case FSRGenerator::SherpaPhotons21:
radCorrEngine = genList.getSherpaPhotonsModel( 1e-7, 2, 1 );
break;
}
extraModels = genList.getListOfModels();
}
#endif
static thread_local EvtGen theGen{ "../DECAY.DEC", "../evt.pdl",
randomEngine.get(), radCorrEngine,
&extraModels };
// Creates a decay file based on json file input
// We don't want this to be called by every thread!
std::call_once( m_createDecFile_threadlock, [this]() { createDecFile(); } );
// Read the decay file
if ( !initialised ) {
theGen.readUDecay( m_config.decFileName.c_str() );
initialised = true;
}
// Define the histograms to be saved
TestHistos theHistos{ m_config.testHistograms };
// Generate events and fill histograms
generateEvents( theGen, theHistos, firstEvent, nEvents );
return theHistos;
}
void TestDecayModel::createDecFile() const
{
// Create (or overwrite) the decay file
std::ofstream decFile{ m_config.decFileName };
// Create daughter aliases if needed
std::vector<std::string> aliasPrefix;
for ( std::size_t daughter_index{ 0 };
daughter_index < m_config.daughterNames.size(); daughter_index++ ) {
if ( !m_config.grandDaughterNames.empty() &&
!m_config.grandDaughterNames[daughter_index].empty() ) {
decFile << "Alias My" << m_config.daughterNames[daughter_index] << " "
<< m_config.daughterNames[daughter_index] << std::endl;
if ( m_config.doConjDecay[daughter_index + 1] ) {
const EvtId daugID{
EvtPDL::getId( m_config.daughterNames[daughter_index] ) };
const EvtId daugConjID{ EvtPDL::chargeConj( daugID ) };
const std::string conjName{ daugConjID.getName() };
std::string conjName_Alias{ daugConjID.getName() };
if ( std::find( std::begin( m_config.daughterNames ),
std::end( m_config.daughterNames ),
daugConjID.getName() ) !=
std::end( m_config.daughterNames ) ) {
conjName_Alias = conjName_Alias + "_" + daughter_index;
}
decFile << "Alias My" << conjName_Alias << " " << conjName
<< std::endl;
decFile << "ChargeConj My"
<< m_config.daughterNames[daughter_index] << " My"
<< conjName_Alias << std::endl;
} else if ( m_config.doConjDecay[0] ) {
decFile << "ChargeConj My"
<< m_config.daughterNames[daughter_index] << " My"
<< m_config.daughterNames[daughter_index] << std::endl;
}
aliasPrefix.push_back( "My" );
} else {
aliasPrefix.push_back( "" );
}
}
for ( const auto& iExtra : m_config.extraCommands ) {
decFile << iExtra << std::endl;
}
// Parent decay
decFile << "Decay " << m_config.parentName << std::endl;
decFile << "1.0";
for ( std::size_t daughter_index{ 0 };
daughter_index < m_config.daughterNames.size(); daughter_index++ ) {
decFile << " " << aliasPrefix[daughter_index]
<< m_config.daughterNames[daughter_index];
}
decFile << " " << m_config.modelNames[0];
for ( const auto& par : m_config.modelParameters[0] ) {
decFile << " " << par;
}
decFile << ";" << std::endl;
decFile << "Enddecay" << std::endl;
if ( m_config.doConjDecay[0] ) {
EvtId parID{ EvtPDL::getId( m_config.parentName ) };
EvtId parConjID{ EvtPDL::chargeConj( parID ) };
decFile << "CDecay " << parConjID.getName() << std::endl;
}
// Daughter decays into granddaughters
for ( std::size_t daughter_index{ 0 };
daughter_index < m_config.grandDaughterNames.size(); daughter_index++ ) {
if ( m_config.grandDaughterNames[daughter_index].empty() )
continue;
decFile << "Decay " << aliasPrefix[daughter_index]
<< m_config.daughterNames[daughter_index] << std::endl;
decFile << "1.0";
for ( std::size_t grandDaughter_index{ 0 };
grandDaughter_index <
m_config.grandDaughterNames[daughter_index].size();
grandDaughter_index++ ) {
decFile << " "
<< m_config.grandDaughterNames[daughter_index][grandDaughter_index];
}
decFile << " " << m_config.modelNames[daughter_index + 1];
for ( const auto& par : m_config.modelParameters[daughter_index + 1] ) {
decFile << " " << par;
}
decFile << ";" << std::endl;
decFile << "Enddecay" << std::endl;
if ( m_config.doConjDecay[daughter_index + 1] ) {
EvtId daugID{
EvtPDL::getId( m_config.daughterNames[daughter_index] ) };
EvtId daugConjID{ EvtPDL::chargeConj( daugID ) };
std::string conjName_Alias{ daugConjID.getName() };
if ( std::find( std::begin( m_config.daughterNames ),
std::end( m_config.daughterNames ),
daugConjID.getName() ) !=
std::end( m_config.daughterNames ) ) {
conjName_Alias = conjName_Alias + "_" + daughter_index;
}
decFile << "CDecay " << aliasPrefix[daughter_index]
<< conjName_Alias << std::endl;
}
}
decFile << "End" << std::endl;
decFile.close();
}
void TestDecayModel::generateEvents( EvtGen& theGen, TestHistos& theHistos,
const std::size_t firstEvent,
const std::size_t nEvents ) const
{
// Generate the decays
const EvtId parId{ EvtPDL::getId( m_config.parentName.c_str() ) };
const EvtId conjId{ m_config.doConjDecay[0] ? EvtPDL::chargeConj( parId )
: parId };
for ( std::size_t i{ firstEvent }; i < ( firstEvent + nEvents ); i++ ) {
if ( i % 1000 == 0 ) {
std::cout << "Event " << firstEvent + nEvents - i << std::endl;
}
// seed the RNG based on the event number
EvtRandom::setSeed( m_config.rngSeed + i );
// Initial 4-momentum and particle
EvtVector4R pInit( EvtPDL::getMass( parId ), 0.0, 0.0, 0.0 );
EvtParticle* parent{ nullptr };
if ( EvtRandom::Flat() < 0.5 ) {
parent = EvtParticleFactory::particleFactory( parId, pInit );
} else {
parent = EvtParticleFactory::particleFactory( conjId, pInit );
}
theGen.generateDecay( parent );
// Check for mixing (and fill histogram)
EvtParticle* prodParent{ nullptr };
TH1* mixedHist{ theHistos.getMixedHist() };
if ( mixedHist ) {
if ( parent->getNDaug() == 1 ) {
prodParent = parent;
parent = prodParent->getDaug( 0 );
mixedHist->Fill( 1 );
} else {
mixedHist->Fill( 0 );
}
}
// To debug
if ( m_config.debugFlag ) {
std::cout << "Parent PDG code: " << parent->getPDGId()
<< " has daughters " << parent->getNDaug() << std::endl;
for ( std::size_t iDaughter{ 0 }; iDaughter < parent->getNDaug();
iDaughter++ ) {
std::cout << "Parent PDG code of daughter " << iDaughter
<< " : " << parent->getDaug( iDaughter )->getPDGId()
<< " has daughters "
<< parent->getDaug( iDaughter )->getNDaug()
<< std::endl;
for ( std::size_t iGrandDaughter{ 0 };
iGrandDaughter < parent->getDaug( iDaughter )->getNDaug();
iGrandDaughter++ ) {
std::cout << "Parent PDG code of grand daughter "
<< iGrandDaughter << " : "
<< parent->getDaug( iDaughter )
->getDaug( iGrandDaughter )
->getPDGId()
<< " has daughters "
<< parent->getDaug( iDaughter )
->getDaug( iGrandDaughter )
->getNDaug()
<< std::endl;
}
}
}
int leadingChargedDaughter = -1;
const std::string fsrStr = "_FSRPhotons";
const std::string ewStr = "_EnergyWeight";
const std::string perDaughter = "_perDaughter";
// Store information
for ( auto& [info, hist] : theHistos.get1DHistos() ) {
if ( !hist ) {
continue;
}
const std::string varName = info.getName();
std::string reducedVarName = varName;
const std::string::size_type findFSRstr = varName.find( fsrStr );
const std::string::size_type findPerDaugStr = varName.find(
perDaughter );
// If the variable name does not have the substrings '_perDaughter' and '_FSRPhotons', then add an entry per event and continue
if ( findFSRstr == std::string::npos &&
findPerDaugStr == std::string::npos ) {
const double value{
getValue( parent, varName, info.getd1(), info.getd2() ) };
hist->Fill( value );
continue;
}
// If the variable name has the substring '_perDaughter' and does not have the substring '_FSRPhotons', then add an entry per daughter and continue
else if ( findFSRstr == std::string::npos ) {
reducedVarName.erase( findPerDaugStr, perDaughter.length() );
// Figure out whether the variable indicates to be saved for a requested particle type
const std::string::size_type findIsStr = reducedVarName.find(
"_is" );
const std::string requestedType = findIsStr == std::string::npos
? ""
: reducedVarName.substr(
findIsStr + 3 );
if ( !requestedType.empty() ) {
reducedVarName.erase( findIsStr,
reducedVarName.length() - findIsStr );
}
for ( int iDaug{ 0 }; iDaug < (int)parent->getNDaug(); iDaug++ ) {
const double value{
getValue( parent, reducedVarName, iDaug + 1, 0 ) };
const int partGroup = getPartGroup(
parent->getDaug( iDaug )->getPDGId() );
if ( requestedType.empty() ||
isPartType( partGroup, requestedType ) ) {
hist->Fill( value );
}
}
continue;
}
// If otherwise the variable contains the substring '_FSRPhotons', then add an entry per photon
if ( leadingChargedDaughter == -1 ) {
leadingChargedDaughter = findChargedDaughterWithMaxE( parent );
}
reducedVarName.erase( findFSRstr, fsrStr.length() );
const std::string::size_type findEwStr = reducedVarName.find( ewStr );
// If variable name has substring '_EnergyWeight' then add an entry weighted by the photon energy
bool energyWeight = false;
if ( findEwStr != std::string::npos ) {
reducedVarName.erase( findEwStr, ewStr.length() );
energyWeight = true;
}
for ( int iDaug{ 0 }; iDaug < (int)parent->getNDaug(); iDaug++ ) {
const EvtParticle* iDaughter = parent->getDaug( iDaug );
if ( iDaughter->getAttribute( "FSR" ) == 1 ) {
const double value{ getValue( parent, reducedVarName,
iDaug + 1,
leadingChargedDaughter + 1 ) };
if ( energyWeight ) {
const double photonEnergy = iDaughter->getP4().get( 0 );
hist->Fill( value, photonEnergy );
} else {
hist->Fill( value );
}
}
}
}
for ( auto& [info, hist] : theHistos.get2DHistos() ) {
if ( !hist ) {
continue;
}
const double valueX{ getValue( parent,
info.getName( HistInfo::Axis::X ),
info.getd1( HistInfo::Axis::X ),
info.getd2( HistInfo::Axis::X ) ) };
const double valueY{ getValue( parent,
info.getName( HistInfo::Axis::Y ),
info.getd1( HistInfo::Axis::Y ),
info.getd2( HistInfo::Axis::Y ) ) };
hist->Fill( valueX, valueY );
}
if ( m_config.debugFlag ) {
if ( prodParent ) {
prodParent->printTree();
} else {
parent->printTree();
}
}
// Cleanup
if ( prodParent ) {
prodParent->deleteTree();
} else {
parent->deleteTree();
}
}
}
int TestDecayModel::findChargedDaughterWithMaxE( const EvtParticle* parent ) const
{
/* This function returns the index of the charged daughter with the highest energy
* following the sign convention below. */
double max_E = 0;
double max_index = 0;
const int parentCh3 = EvtPDL::chg3( parent->getId() );
for ( int iDaug{ 0 }; iDaug < (int)parent->getNDaug(); iDaug++ ) {
const EvtParticle* iDaughter = parent->getDaug( iDaug );
const int dauCh3 = EvtPDL::chg3( iDaughter->getId() );
const double daugE = iDaughter->getP4LabBeforeFSR().get( 0 );
// Sign convention: take negative daughter if mother is neutral,
// otherwise the one with the same sign as the mother.
if ( ( ( parentCh3 == 0 && dauCh3 < 0 ) || ( parentCh3 * dauCh3 > 0 ) ) &&
daugE > max_E ) {
max_E = daugE;
max_index = iDaug;
}
}
return max_index;
}
double TestDecayModel::getValue( const EvtParticle* parent,
const std::string& varName, const int d1,
const int d2 ) const
{
double value{ std::numeric_limits<double>::quiet_NaN() };
if ( !parent ) {
return value;
}
const int NDaugMax( parent->getNDaug() );
// If variable name contains "_daugX", we are interested in daughters of daughter X
// Else we are interested in daughters
const EvtParticle* selectedParent{ parent };
std::string selectedVarName{ varName };
if ( varName.find( "_daug" ) != std::string::npos ) {
// Get daughter index from last character in string
const int iDaughter{ varName.back() - '0' };
selectedVarName = varName.substr( 0, varName.size() - 6 );
if ( iDaughter > 0 && iDaughter <= NDaugMax ) {
selectedParent = parent->getDaug( iDaughter - 1 );
} else {
return value;
}
}
const int sel_NDaugMax( selectedParent->getNDaug() );
const EvtParticle* par1{ d1 > 0 && d1 <= sel_NDaugMax
? selectedParent->getDaug( d1 - 1 )
: nullptr };
const EvtParticle* par2{ d2 > 0 && d2 <= sel_NDaugMax
? selectedParent->getDaug( d2 - 1 )
: nullptr };
const EvtParticle* par3{ sel_NDaugMax > 0
? selectedParent->getDaug( sel_NDaugMax - 1 )
: nullptr };
// 4-momenta in parent rest frame
const EvtVector4R p1{ par1 != nullptr ? par1->getP4() : EvtVector4R() };
const EvtVector4R p2{ par2 != nullptr ? par2->getP4() : EvtVector4R() };
const EvtVector4R p3{ par3 != nullptr ? par3->getP4() : EvtVector4R() };
// 4-momenta in lab frame (1st parent in decay tree)
const EvtVector4R p1_lab{ par1 != nullptr ? par1->getP4Lab() : EvtVector4R() };
const EvtVector4R p2_lab{ par2 != nullptr ? par2->getP4Lab() : EvtVector4R() };
const EvtVector4R p3_lab{ par3 != nullptr ? par3->getP4Lab() : EvtVector4R() };
if ( !selectedVarName.compare( "id" ) ) {
// StdHep ID of one of the daughters (controlled by d1) or the parent
if ( par1 ) {
value = par1->getPDGId();
} else {
value = selectedParent->getPDGId();
}
} else if ( !selectedVarName.compare( "particleType" ) ) {
if ( par1 ) {
value = getPartGroup( par1->getPDGId() );
} else {
value = getPartGroup( selectedParent->getPDGId() );
}
} else if ( !selectedVarName.compare( "nDaug" ) ) {
// Number of daughters
value = sel_NDaugMax;
} else if ( !selectedVarName.compare( "parMass" ) ) {
// Parent invariant mass
value = selectedParent->mass();
} else if ( !selectedVarName.compare( "mass" ) ) {
// Invariant mass
if ( d2 != 0 ) {
// Invariant 4-mass combination of particles d1 and d2
value = ( p1 + p2 ).mass();
} else {
// Invariant mass of particle d1 only
value = p1.mass();
}
} else if ( !selectedVarName.compare( "massSq" ) ) {
// Invariant mass
if ( d2 != 0 ) {
// Invariant 4-mass combination of particles d1 and d2
value = ( p1 + p2 ).mass2();
} else {
// Invariant mass of particle d1 only
value = p1.mass2();
}
} else if ( !selectedVarName.compare( "mPrime" ) ) {
// pick up first unused particle rather than last one
if ( sel_NDaugMax != 3 ) {
return -1;
}
int unused{ 0 };
for ( int ii{ 1 }; ii <= sel_NDaugMax; ++ii ) {
if ( ii != d1 && ii != d2 ) {
unused = ii;
break;
}
}
if ( unused == 0 ) {
unused = sel_NDaugMax;
}
const auto parL{ selectedParent->getDaug( unused - 1 ) };
// const auto& pL = parL->getP4();
const double mB{ selectedParent->mass() };
const double m1{ par1->mass() };
const double m2{ par2->mass() };
const double m3{ parL->mass() };
const double m12{ ( p1 + p2 ).mass() };
const double m12norm{
2 * ( ( m12 - ( m1 + m2 ) ) / ( mB - ( m1 + m2 + m3 ) ) ) - 1 };
value = acos( m12norm ) / EvtConst::pi;
} else if ( !selectedVarName.compare( "thetaPrime" ) ) {
// pick up first unused particle rather than last one
if ( sel_NDaugMax != 3 ) {
return -1;
}
int unused{ 0 };
for ( int ii{ 1 }; ii <= sel_NDaugMax; ++ii ) {
if ( ii != d1 && ii != d2 ) {
unused = ii;
break;
}
}
if ( unused == 0 ) {
unused = sel_NDaugMax;
}
const auto parL{ selectedParent->getDaug( unused - 1 ) };
const auto& pL{ parL->getP4() };
const double mB{ selectedParent->mass() };
const double m1{ p1.mass() };
const double m2{ p2.mass() };
const double m3{ pL.mass() };
double mBSq{ mB * mB };
double m1Sq{ m1 * m1 };
double m2Sq{ m2 * m2 };
double m3Sq{ m3 * m3 };
const double m12{ ( p1 + p2 ).mass() };
const double m13{ ( p1 + p3 ).mass() };
const double m12Sq{ m12 * m12 };
const double m13Sq{ m13 * m13 };
double en1{ ( m12Sq - m2Sq + m1Sq ) / ( 2.0 * m12 ) };
double en3{ ( mBSq - m12Sq - m3Sq ) / ( 2.0 * m12 ) };
double p1_12{ std::sqrt( en1 * en1 - m1Sq ) };
double p3_12{ std::sqrt( en3 * en3 - m3Sq ) };
double cosTheta{ ( -m13Sq + m1Sq + m3Sq + 2. * en1 * en3 ) /
( 2. * p1_12 * p3_12 ) };
value = acos( cosTheta ) / EvtConst::pi;
} else if ( !selectedVarName.compare( "pSumSq" ) ) {
// Invariant momentum sum squared
value = ( p1 + p2 ).mass2();
} else if ( !selectedVarName.compare( "pDiffSq" ) ) {
// Invariant momentum difference squared
value = ( p1 - p2 ).mass2();
} else if ( !selectedVarName.compare( "mass3" ) ) {
// Invariant mass of 3 daughters
value = ( p1 + p2 + p3 ).mass();
} else if ( !selectedVarName.compare( "mass3_specified" ) ) {
// Invariant mass of 3 daughters, d1 is first daughter
// second daughter is d1 + 1, d2 is last daughter
const EvtParticle* daug2{ selectedParent->getDaug( d1 ) };
const EvtParticle* daug3{ selectedParent->getDaug( d2 - 1 ) };
const EvtVector4R p2_specified{ daug2 != nullptr ? daug2->getP4Lab()
: EvtVector4R() };
const EvtVector4R p3_specified{ daug3 != nullptr ? daug3->getP4Lab()
: EvtVector4R() };
value = ( p1 + p2_specified + p3_specified ).mass();
} else if ( !selectedVarName.compare( "cosTheta3" ) ) {
// Cosine of the polar angle of the momentum of d1 + d2 + d3
const EvtVector4R p123{ p1 + p2 + p3 };
if ( p123.d3mag() > 0.0 ) {
value = p123.get( 3 ) / p123.d3mag();
}
} else if ( !selectedVarName.compare( "pLab" ) ) {
// Momentum of particle d1 in lab frame
value = p1_lab.d3mag();
} else if ( !selectedVarName.compare( "p" ) ) {
// Momentum of particle d1 in parent rest frame
value = p1.d3mag();
} else if ( !selectedVarName.compare( "pLabSq" ) ) {
// Momentum squared of particle d1 (in lab frame)
const double p1_lab_x{ p1_lab.get( 1 ) };
const double p1_lab_y{ p1_lab.get( 2 ) };
const double p1_lab_z{ p1_lab.get( 3 ) };
value = p1_lab_x * p1_lab_x + p1_lab_y * p1_lab_y + p1_lab_z * p1_lab_z;
} else if ( !selectedVarName.compare( "pSq" ) ) {
// Momentum squared of particle d1 (in lab frame)
const double p1_x{ p1.get( 1 ) };
const double p1_y{ p1.get( 2 ) };
const double p1_z{ p1.get( 3 ) };
value = p1_x * p1_x + p1_y * p1_y + p1_z * p1_z;
} else if ( !selectedVarName.compare( "pxLab" ) ) {
// x momentum of particle d1 in lab frame
value = p1_lab.get( 1 );
} else if ( !selectedVarName.compare( "px" ) ) {
// x momentum of particle d1 in parent frame
value = p1.get( 1 );
} else if ( !selectedVarName.compare( "pyLab" ) ) {
// y momentum of particle d1 in lab frame
value = p1_lab.get( 2 );
} else if ( !selectedVarName.compare( "py" ) ) {
// y momentum of particle d1 in parent frame
value = p1.get( 2 );
} else if ( !selectedVarName.compare( "pzLab" ) ) {
// z momentum of particle d1 in lab frame
value = p1_lab.get( 3 );
} else if ( !selectedVarName.compare( "pz" ) ) {
// z momentum of particle d1 in parent frame
value = p1.get( 3 );
} else if ( !selectedVarName.compare( "cosHel" ) ||
!selectedVarName.compare( "absCosHel" ) ||
!selectedVarName.compare( "cosHelParent" ) ) {
// Cosine of helicity angle
EvtVector4R p12;
EvtVector4R p1Res;
if ( !selectedVarName.compare( "cosHelParent" ) ) {
p12 = selectedParent->getP4Lab();
const EvtVector4R boost{ p12.get( 0 ), -p12.get( 1 ), -p12.get( 2 ),
-p12.get( 3 ) };
// Momentum of particle d1 in resonance frame, p1Res
p1Res = boostTo( p1_lab, boost );
} else if ( d2 != 0 ) {
// Resonance center-of-mass system (d1 and d2)
p12 = p1_lab + p2_lab;
// Boost vector
const EvtVector4R boost{ p12.get( 0 ), -p12.get( 1 ), -p12.get( 2 ),
-p12.get( 3 ) };
// Momentum of particle d1 in resonance frame, p1Res
p1Res = boostTo( p1_lab, boost );
} else {
// The resonance is d1
p12 = p1;
// Find its first daughter
const EvtParticle* gpar{ par1 != nullptr ? par1->getDaug( 0 )
: nullptr };
p1Res = gpar != nullptr ? gpar->getP4() : EvtVector4R();
}
// Cosine of angle between p1Res and momentum of resonance in parent frame
const double p1ResMag{ p1Res.d3mag() };
const double p12Mag{ p12.d3mag() };
if ( p1ResMag > 0.0 && p12Mag > 0.0 ) {
value = -p1Res.dot( p12 ) / ( p1ResMag * p12Mag );
}
if ( !selectedVarName.compare( "absCosHel" ) ) {
value = std::abs( value );
}
} else if ( !selectedVarName.compare( "cosHelTau" ) ) {
// Works only for B -> X nu_1 tau -> pi nu_2.
// Cosine of helicity angle between pi momentum and opposite W momentum -(nu_1 + tau)
// in the tau rest frame. Index d1 must match with tau.
// p3 (momentum of last daughter) is taken as the neutrino momentum
// W momentum
const EvtVector4R p_W{ p1_lab + p3_lab };
// Index d2 must match the index of the pion (daughter of tau)
const EvtParticle* pion{
selectedParent->getDaug( d1 - 1 )->getDaug( d2 - 1 ) };
const EvtVector4R p_pion{ pion != nullptr ? pion->getP4Lab()
: EvtVector4R() };
// Boost vector to tau frame
const EvtVector4R boost{ p1_lab.get( 0 ), -p1_lab.get( 1 ),
-p1_lab.get( 2 ), -p1_lab.get( 3 ) };
// Boost both momenta to tau frame
const EvtVector4R p_W_boosted{ boostTo( p_W, boost ) };
const EvtVector4R p_pion_boosted{ boostTo( p_pion, boost ) };
// Cosine of angle between opposite W momentum and pion momentum in tau frame
const double p_W_boostedMag{ p_W_boosted.d3mag() };
const double p_pion_boostedMag{ p_pion_boosted.d3mag() };
if ( p_W_boostedMag > 0.0 && p_pion_boostedMag > 0.0 ) {
value = -p_W_boosted.dot( p_pion_boosted ) /
( p_W_boostedMag * p_pion_boostedMag );
}
} else if ( !selectedVarName.compare( "cosHelDiTau" ) ||
!selectedVarName.compare( "cosHelDiTau_over05" ) ||
!selectedVarName.compare( "cosHelDiTau_under05" ) ) {
// Works for B -> tau (pi nu) tau -> (pi nu), B -> phi (-> KK) l l decays,
// B -> 4 pions, or similar decays..
// Cosine of helicity angle between pi momentum and opposite B momentum in tau rest frame.
// Index d1 must match with tau
if ( sel_NDaugMax < 2 || sel_NDaugMax > 4 ) {
return value;
}
// B momentum
const EvtVector4R p_B{ selectedParent->getP4Lab() };
// Index d2 must match the index of the pion (daughter of tau)
const EvtParticle* pion_1{
sel_NDaugMax <= 3
? selectedParent->getDaug( d1 - 1 )->getDaug( d2 - 1 )
: selectedParent->getDaug( d1 - 1 ) };
const EvtVector4R p_pion_1{ pion_1 != nullptr ? pion_1->getP4Lab()
: EvtVector4R() };
const EvtVector4R p_first_daughter{
sel_NDaugMax <= 3 ? selectedParent->getDaug( d1 - 1 )->getP4Lab()
: selectedParent->getDaug( d1 - 1 )->getP4Lab() +
selectedParent->getDaug( d1 )->getP4Lab() };
// Boost vector to tau frame
const EvtVector4R boost_1{ p_first_daughter.get( 0 ),
-p_first_daughter.get( 1 ),
-p_first_daughter.get( 2 ),
-p_first_daughter.get( 3 ) };
// Boost both momenta to tau frame
const EvtVector4R p_B_boosted_1{ boostTo( p_B, boost_1 ) };
const EvtVector4R p_pion_boosted_1{ boostTo( p_pion_1, boost_1 ) };
// Cosine of angle between opposite W momentum and pion momentum in tau frame
const double p_B_boosted_1_Mag{ p_B_boosted_1.d3mag() };
const double p_pion_boosted_1_Mag{ p_pion_boosted_1.d3mag() };
double hel1{ std::numeric_limits<double>::quiet_NaN() };
double hel{ std::numeric_limits<double>::quiet_NaN() };
if ( p_B_boosted_1_Mag > 0.0 && p_pion_boosted_1_Mag > 0.0 ) {
hel1 = -p_B_boosted_1.dot( p_pion_boosted_1 ) /
( p_B_boosted_1_Mag * p_pion_boosted_1_Mag );
}
if ( ( !selectedVarName.compare( "cosHelDiTau_over05" ) ) ||
( !selectedVarName.compare( "cosHelDiTau_under05" ) ) ) {
// Works for B -> tau (pi nu) tau -> (pi nu) or similar decays.
// Cosine of helicity angle between pi momentum and opposite B momentum in tau rest frame
// Index d1 must match with tau; cosHelicity above +0.5 or below -0.5
// Index d2 must match the index of the pion (daughter of tau)
const EvtParticle* pion{
sel_NDaugMax <= 3 ? selectedParent->getDaug( 0 )->getDaug( d2 - 1 )
: selectedParent->getDaug( 0 ) };
const EvtVector4R p_pion{ pion != nullptr ? pion->getP4Lab()
: EvtVector4R() };
// Boost vector to tau frame
const EvtVector4R p_second_daughter{
sel_NDaugMax == 2
? selectedParent->getDaug( 0 )->getP4Lab()
: selectedParent->getDaug( 0 )->getP4Lab() +
selectedParent->getDaug( 1 )->getP4Lab() };
const EvtVector4R boost{ p_second_daughter.get( 0 ),
-p_second_daughter.get( 1 ),
-p_second_daughter.get( 2 ),
-p_second_daughter.get( 3 ) };
// Boost both momenta to tau frame
const EvtVector4R p_B_boosted{ boostTo( p_B, boost ) };
const EvtVector4R p_pion_boosted{ boostTo( p_pion, boost ) };
// Cosine of angle between opposite W momentum and pion momentum in tau frame
const double p_B_boostedMag{ p_B_boosted.d3mag() };
const double p_pion_boostedMag{ p_pion_boosted.d3mag() };
if ( p_B_boostedMag > 0.0 && p_pion_boostedMag > 0.0 ) {
hel = -p_B_boosted.dot( p_pion_boosted ) /
( p_B_boostedMag * p_pion_boostedMag );
}
}
if ( !selectedVarName.compare( "cosHelDiTau" ) ||
( hel > 0.5 && !selectedVarName.compare( "cosHelDiTau_over05" ) ) ||
( hel < -0.5 && !selectedVarName.compare( "cosHelDiTau_under05" ) ) ) {
value = hel1;
}
} else if ( !selectedVarName.compare( "cosAcoplanarityAngle" ) ||
!selectedVarName.compare( "acoplanarityAngle" ) ) {
// Acoplanarity angle or cosine for B -> tau (pi nu) tau -> (pi nu),
// B -> phi (-> KK) l l decays, B -> 4 pions, or similar decays
value = getCosAcoplanarityAngle( selectedParent, sel_NDaugMax, d1, d2 );
if ( !selectedVarName.compare( "acoplanarityAngle" ) ) {
value = std::acos( value ) * 180.0 / EvtConst::pi; // degrees
}
} else if ( !selectedVarName.compare( "cosTheta" ) ) {
// Cosine of polar angle of first daughter in lab frame
const double p1_lab_mag{ p1_lab.d3mag() };
if ( p1_lab_mag > 0.0 ) {
value = p1_lab.get( 3 ) / p1_lab_mag;
}
} else if ( !selectedVarName.compare( "phi" ) ) {
// Azimuthal angle of first daughter in lab frame (degrees)
const double p1_lab_mag{ p1_lab.d3mag() };
if ( p1_lab_mag > 0.0 ) {
value = atan2( p1_lab.get( 1 ), p1_lab.get( 2 ) ) * 180.0 /
EvtConst::pi;
}
} else if ( !selectedVarName.compare( "openingAngle" ) ) {
// Polar angle between first and second daughters in parent's frame
const double cost{ p1.dot( p2 ) / ( p1.d3mag() * p2.d3mag() ) };
value = acos( cost ) * 180.0 / EvtConst::pi;
} else if ( !selectedVarName.compare( "decayangle" ) ) {
// Polar angle between first and second daughters in lab frame
const EvtVector4R p{ selectedParent->getP4() };
const EvtVector4R q{ p1 + p2 };
const EvtVector4R d{ p1 };
const double cost{ EvtDecayAngle( p, q, d ) };
value = acos( cost ) * 180.0 / EvtConst::pi;
} else if ( !selectedVarName.compare( "decayangle3" ) ) {
// Polar angle between combined first and second daughters
// with the third daughter in lab frame
const EvtVector4R p{ selectedParent->getP4() };
const EvtVector4R q{ p1 + p2 + p3 };
const EvtVector4R d{ p1 + p2 };
const double cost{ EvtDecayAngle( p, q, d ) };
value = acos( cost ) * 180.0 / EvtConst::pi;
} else if ( !selectedVarName.compare( "decayangle_BTO4PI" ) ) {
// Polar angle between first and second daughters in lab frame.
// Used in PIPIPI (BTO4PI_CP) model
const EvtVector4R p{ p1 + p2 + p3 };
const EvtVector4R q{ p1 + p2 };
const EvtVector4R d{ p1 };
const double cost{ EvtDecayAngle( p, q, d ) };
value = acos( cost ) * 180.0 / EvtConst::pi;
} else if ( !selectedVarName.compare( "chi" ) ) {
// Chi angle (degrees) using all 4 daughters and parent 4 mom
const EvtParticle* daug1{ selectedParent->getDaug( d1 - 1 ) };
const EvtParticle* daug2{ selectedParent->getDaug( d1 ) };
const EvtParticle* daug3{ selectedParent->getDaug( d1 + 1 ) };
const EvtParticle* daug4{ selectedParent->getDaug( d1 + 2 ) };
const EvtVector4R p_parent{ selectedParent->getP4() };
const EvtVector4R p_daug1{ daug1 != nullptr ? daug1->getP4()
: EvtVector4R() };
const EvtVector4R p_daug2{ daug2 != nullptr ? daug2->getP4()
: EvtVector4R() };
const EvtVector4R p_daug3{ daug3 != nullptr ? daug3->getP4()
: EvtVector4R() };
const EvtVector4R p_daug4{ daug4 != nullptr ? daug4->getP4()
: EvtVector4R() };
const double chi{
EvtDecayAngleChi( p_parent, p_daug1, p_daug2, p_daug3, p_daug4 ) };
value = chi * 180.0 / EvtConst::pi;
} else if ( !selectedVarName.compare( "cosThetaResNorm" ) ) {
// P -> R p4 -> (p1 p2 p3) p4, where the resonance R decays to p1 p2 p3.
// Theta is the angle between the normal of the plane containing p1, p2 & p3
// and the bachelor particle p4 in the rest frame of resonance R.
// The normal vector is given by the cross product p3 x p1
if ( sel_NDaugMax > 1 ) {
const EvtParticle* res{ selectedParent->getDaug( 0 ) };
const EvtParticle* bac{ selectedParent->getDaug( 1 ) };
// Check resonance has 3 daughters
if ( res != nullptr && res->getNDaug() == 3 ) {
const EvtParticle* daug1 = res->getDaug( 0 );
const EvtParticle* daug3 = res->getDaug( 2 );
// 4-momenta in base parent P lab frame
const EvtVector4R p4_Res{ res->getP4Lab() };
const EvtVector4R p4_p4{ bac != nullptr ? bac->getP4Lab()
: EvtVector4R() };
const EvtVector4R p4_p1{ daug1 != nullptr ? daug1->getP4Lab()
: EvtVector4R() };
const EvtVector4R p4_p3{ daug3 != nullptr ? daug3->getP4Lab()
: EvtVector4R() };
// Boost 4-vector for resonance frame
const EvtVector4R boost{ p4_Res.get( 0 ), -p4_Res.get( 1 ),
-p4_Res.get( 2 ), -p4_Res.get( 3 ) };
// Momentum of p1 and p3 in resonance frame
const EvtVector4R p1Res{ boostTo( p4_p1, boost ) };
const EvtVector4R p3Res{ boostTo( p4_p3, boost ) };
// Plane normal vector (just uses 3-momentum components)
const EvtVector4R norm{ p3Res.cross( p1Res ) };
// Momentum of p4 in resonance frame
const EvtVector4R p4Res{ boostTo( p4_p4, boost ) };
// Cosine of the angle between the normal and p4 in the resonance frame
const double normMag{ norm.d3mag() };
const double p4ResMag{ p4Res.d3mag() };
if ( normMag > 0.0 && p4ResMag > 0.0 ) {
value = norm.dot( p4Res ) / ( normMag * p4ResMag );
}
}
}
} else if ( !selectedVarName.compare( "cosBetaRes" ) ) {
// For resonance P (parent) -> p1 p2 p3, beta is the
// angle between p1 & p3 in the (p1 + p2) rest frame
if ( sel_NDaugMax > 2 ) {
const EvtParticle* daug1 = selectedParent->getDaug( 0 );
const EvtParticle* daug2 = selectedParent->getDaug( 1 );
const EvtParticle* daug3 = selectedParent->getDaug( 2 );
// 4-momenta in base parent frame
const EvtVector4R p4_p1{ daug1 != nullptr ? daug1->getP4Lab()
: EvtVector4R() };
const EvtVector4R p4_p2{ daug2 != nullptr ? daug2->getP4Lab()
: EvtVector4R() };
const EvtVector4R p4_p3{ daug3 != nullptr ? daug3->getP4Lab()
: EvtVector4R() };
// p1 + p2
const EvtVector4R p12{ p4_p1 + p4_p2 };
// Boost 4-vector for p12 frame
const EvtVector4R boost{ p12.get( 0 ), -p12.get( 1 ), -p12.get( 2 ),
-p12.get( 3 ) };
// Momentum of p1 & p3 in p12 frame
const EvtVector4R p1_12{ boostTo( p4_p1, boost ) };
const EvtVector4R p3_12{ boostTo( p4_p3, boost ) };
// Cosine of angle between p1 & p3 in p12 frame
const double p1_12Mag{ p1_12.d3mag() };
const double p3_12Mag{ p3_12.d3mag() };
if ( p1_12Mag > 0.0 && p3_12Mag > 0.0 ) {
value = p1_12.dot( p3_12 ) / ( p1_12Mag * p3_12Mag );
}
}
} else if ( !selectedVarName.compare( "E" ) ) {
// Energy of first daughter in lab frame
value = p1_lab.get( 0 );
} else if ( !selectedVarName.compare( "E_over_Eparent" ) ) {
// Energy of first daughter w.r.t parent energy (lab frame)
value = p1_lab.get( 0 ) / selectedParent->getP4Lab().get( 0 );
} else if ( !selectedVarName.compare( "E_over_Eparent_over05" ) ) {
// First daughter E_over_Eparent (lab frame) if d2 granddaughter E ratio > 0.5
const double E_over_Eparent_2{
parent->getDaug( 0 )->getDaug( d2 - 1 )->getP4Lab().get( 0 ) /
parent->getDaug( 0 )->getP4Lab().get( 0 ) };
if ( E_over_Eparent_2 > 0.5 ) {
value = p1_lab.get( 0 ) / selectedParent->getP4Lab().get( 0 );
}
} else if ( !selectedVarName.compare( "totE_over_Mparent" ) ) {
// Energy of given daughters w.r.t parent mass (lab frame)
value = ( p1_lab.get( 0 ) + p2_lab.get( 0 ) ) / selectedParent->mass();
} else if ( !selectedVarName.compare( "prob" ) ) {
// Decay probability
const double* dProb{ selectedParent->decayProb() };
if ( dProb ) {
value = *dProb;
}
} else if ( !selectedVarName.compare( "lifetime" ) ) {
// Lifetime of particle d1 (ps)
if ( d1 ) {
value = par1 != nullptr ? par1->getLifetime() * 1e12 / EvtConst::c
: 0.0;
} else {
value = parent != nullptr
? parent->getLifetime() * 1e12 / EvtConst::c
: 0.0;
}
} else if ( !selectedVarName.compare( "deltaT" ) ) {
// Lifetime difference between particles d1 and d2
const double t1{
par1 != nullptr ? par1->getLifetime() * 1e12 / EvtConst::c : 0.0 };
const double t2{
par2 != nullptr ? par2->getLifetime() * 1e12 / EvtConst::c : 0.0 };
value = t1 - t2;
} else if ( !selectedVarName.compare( "decTime" ) ) {
// Decay flight time of particle d1 in picoseconds.
// Decay vertex = position of (1st) decay particle of d1
const EvtParticle* gpar{ par1 != nullptr ? par1->getDaug( 0 ) : nullptr };
const EvtVector4R vtxPos{ gpar != nullptr ? gpar->get4Pos()
: EvtVector4R() };
const double p{ p1_lab.d3mag() };
value = p > 0.0
? 1e12 * vtxPos.d3mag() * p1_lab.mass() / ( p * EvtConst::c )
: 0.0;
} else if ( !selectedVarName.compare( "nFSRPhotons" ) ) {
// Loop over all daughters and get number of FSR photons
double nFSRPhotons{ 0 };
for ( std::size_t iDaughter{ 0 };
iDaughter < selectedParent->getNDaug(); iDaughter++ ) {
const EvtParticle* iDaug = selectedParent->getDaug( iDaughter );
if ( iDaug->getAttribute( "FSR" ) == 1 )
nFSRPhotons += 1.0;
}
value = nFSRPhotons;
} else if ( !selectedVarName.compare( "totalFSREnergy" ) ) {
// Loop over all daughters and get number of FSR photons
double totalFSREnergy{ 0 };
for ( std::size_t iDaughter{ 0 };
iDaughter < selectedParent->getNDaug(); iDaughter++ ) {
const EvtParticle* iDaug = selectedParent->getDaug( iDaughter );
if ( iDaug->getAttribute( "FSR" ) == 1 )
totalFSREnergy += iDaug->getP4Lab().get( 0 );
}
value = totalFSREnergy;
} else {
std::cerr << "Warning: Did not recognise variable name "
<< selectedVarName << std::endl;
}
return value;
}
void TestDecayModel::compareHistos( const TestHistos& theHistos,
const std::string& refFileName ) const
{
// Compare histograms with the same name, calculating the chi-squared
std::unique_ptr<TFile> refFile{ TFile::Open( refFileName.c_str(), "read" ) };
if ( !refFile ) {
std::cerr << "Could not open reference file " << refFileName << std::endl;
return;
}
// TODO - should we plot the (signed) chisq histogram? and save it as pdf/png?
// TODO - add comparison of mixedHist
for ( auto& [_, hist] : theHistos.get1DHistos() ) {
const std::string histName{ hist->GetName() };
// Get equivalent reference histogram
const TH1* refHist{
dynamic_cast<TH1*>( refFile->Get( histName.c_str() ) ) };
if ( refHist ) {
double chiSq{ 0.0 };
int nDof{ 0 };
int iGood{ 0 };
const double pValue{
refHist->Chi2TestX( hist.get(), chiSq, nDof, iGood, "WW" ) };
const double integral{ refHist->Integral() };
std::cout << "Histogram " << histName << " chiSq/nDof = " << chiSq
<< "/" << nDof << ", pValue = " << pValue
<< ", integral = " << integral << std::endl;
} else {
std::cerr << "Could not find reference histogram " << histName
<< std::endl;
}
}
for ( auto& [_, hist] : theHistos.get2DHistos() ) {
const std::string histName{ hist->GetName() };
// Get equivalent reference histogram
const TH2* refHist{
dynamic_cast<TH2*>( refFile->Get( histName.c_str() ) ) };
if ( refHist ) {
double chiSq{ 0.0 };
int nDof{ 0 };
int iGood{ 0 };
const double pValue{
refHist->Chi2TestX( hist.get(), chiSq, nDof, iGood, "WW" ) };
const double integral{ refHist->Integral() };
std::cout << "Histogram " << histName << " chiSq/nDof = " << chiSq
<< "/" << nDof << ", pValue = " << pValue
<< ", integral = " << integral << std::endl;
} else {
std::cerr << "Could not find reference histogram " << histName
<< std::endl;
}
}
refFile->Close();
}
int TestDecayModel::getPartGroup( const int PDGId ) const
{
int group( -1 );
const int absPDGId = std::abs( PDGId );
if ( absPDGId >= 11 && absPDGId <= 16 ) {
group = 0; // leptons
} else if ( absPDGId == 22 ) {
group = 1; // photon
} else if ( absPDGId == 211 ) {
group = 2; // pi+-
} else if ( absPDGId == 111 ) {
group = 3; // pi0
} else if ( absPDGId == 321 ) {
group = 4; // K+-
} else if ( absPDGId == 311 || absPDGId == 130 || absPDGId == 310 ) {
group = 5; // K0
} else if ( absPDGId == 411 ) {
group = 6; // D+-
} else if ( absPDGId == 421 ) {
group = 7; // D0
} else if ( absPDGId == 2212 || absPDGId == 2112 || absPDGId == 2224 ||
absPDGId == 2214 || absPDGId == 2114 || absPDGId == 1114 ) {
group = 8; // light baryons
} else if ( absPDGId >= 3112 && absPDGId <= 3334 ) {
group = 9; // strange baryons
} else if ( absPDGId != 0 ) {
group = 10; // other particles
}
return group;
}
bool TestDecayModel::isPartType( const int group,
const std::string& particleType ) const
{
if ( ( !particleType.compare( "LeptonOrPhoton" ) &&
( group == 0 || group == 1 ) ) ||
( !particleType.compare( "Pion" ) && ( group == 2 || group == 3 ) ) ||
( !particleType.compare( "Kaon" ) && ( group == 4 || group == 5 ) ) ||
( !particleType.compare( "Dmeson" ) && ( group == 6 || group == 7 ) ) ||
( !particleType.compare( "Baryon" ) && ( group == 8 || group == 9 ) ) ||
( !particleType.compare( "Other" ) && group == 10 ) ) {
return true;
} else {
return false;
}
}
double TestDecayModel::getCosAcoplanarityAngle( const EvtParticle* selectedParent,
const int sel_NDaugMax,
const int d1, const int d2 ) const
{
// Given a two-body decay, the acoplanarity angle is defined as the angle between the
// two decay planes (normal vectors) in the reference frame of the mother.
// Each normal vector is the cross product of the momentum of one daughter (in the frame of the mother)
// and the momentum of one of the granddaughters (in the reference frame of the daughter).
// In case of 3 daughters, we build an intermediate daughter (2+3) from the last 2 daughters
// (which are then treated as grand daughters), and in case of 4 daughters, we build
// 2 intermediate daughters (1+2) and (3+4)
double cosAco{ 0.0 };
if ( sel_NDaugMax < 2 || sel_NDaugMax > 4 ) {
return cosAco;
}
const EvtParticle* daughter1{ selectedParent->getDaug( 0 ) };
const EvtParticle* daughter2{ selectedParent->getDaug( 1 ) };
const EvtParticle* daughter3{ selectedParent->getDaug( 2 ) };
const EvtParticle* daughter4{ selectedParent->getDaug( 3 ) };
const EvtParticle* grandDaughter1{ daughter1->getDaug( d1 - 1 ) };
const EvtParticle* grandDaughter2{ daughter2->getDaug( d2 - 1 ) };
const EvtVector4R parent4Vector{ selectedParent->getP4Lab() };
const EvtVector4R daughter4Vector1{
sel_NDaugMax <= 3 ? daughter1->getP4Lab()
: daughter1->getP4Lab() + daughter2->getP4Lab() };
const EvtVector4R daughter4Vector2{
sel_NDaugMax == 2 ? daughter2->getP4Lab()
: sel_NDaugMax == 3 ? daughter2->getP4Lab() + daughter3->getP4Lab()
: daughter3->getP4Lab() + daughter4->getP4Lab() };
const EvtVector4R grandDaughter4Vector1{
sel_NDaugMax <= 3 ? grandDaughter1->getP4Lab() : daughter1->getP4Lab() };
const EvtVector4R grandDaughter4Vector2{
sel_NDaugMax == 2 ? grandDaughter2->getP4Lab()
: sel_NDaugMax == 3 ? daughter2->getP4Lab()
: daughter3->getP4Lab() };
const EvtVector4R parentBoost{ parent4Vector.get( 0 ),
-parent4Vector.get( 1 ),
-parent4Vector.get( 2 ),
-parent4Vector.get( 3 ) };
const EvtVector4R daughter1Boost{ daughter4Vector1.get( 0 ),
-daughter4Vector1.get( 1 ),
-daughter4Vector1.get( 2 ),
-daughter4Vector1.get( 3 ) };
const EvtVector4R daughter2Boost{ daughter4Vector2.get( 0 ),
-daughter4Vector2.get( 1 ),
-daughter4Vector2.get( 2 ),
-daughter4Vector2.get( 3 ) };
// Boosting daughters to reference frame of the mother
const EvtVector4R daughter4Vector1_boosted{
boostTo( daughter4Vector1, parentBoost ) };
const EvtVector4R daughter4Vector2_boosted{
boostTo( daughter4Vector2, parentBoost ) };
// Boosting each granddaughter to reference frame of its mother
const EvtVector4R grandDaughter4Vector1_boosted{
boostTo( grandDaughter4Vector1, daughter1Boost ) };
const EvtVector4R grandDaughter4Vector2_boosted{
boostTo( grandDaughter4Vector2, daughter2Boost ) };
// We calculate the normal vectors of the decay two planes
const EvtVector4R normalVector1{
daughter4Vector1_boosted.cross( grandDaughter4Vector1_boosted ) };
const EvtVector4R normalVector2{
daughter4Vector2_boosted.cross( grandDaughter4Vector2_boosted ) };
const double normalVector1Mag{ normalVector1.d3mag() };
const double normalVector2Mag{ normalVector2.d3mag() };
if ( normalVector1Mag > 0.0 && normalVector2Mag > 0.0 ) {
cosAco = normalVector1.dot( normalVector2 ) /
( normalVector1Mag * normalVector2Mag );
}
return cosAco;
}
int main( int argc, char* argv[] )
{
if ( argc < 2 || argc > 3 ) {
std::cerr << "Expecting at least one argument: test configuration json file"
<< "\nAdditional argument supported for: general configuration json file"
<< std::endl;
return 1;
}
// Tweak ROOT behaviour
ROOT::EnableThreadSafety();
TH1::AddDirectory( kFALSE );
// Load input file in json format
const std::string testConfigFileName{ argv[1] };
const std::string generalConfigFileName{
( argc > 2 ) ? argv[2] : "jsonFiles/config/default.json" };
json generalConfig;
json testConfig;
{
std::ifstream inputStr{ generalConfigFileName };
inputStr >> generalConfig;
}
{
std::ifstream inputStr{ testConfigFileName };
inputStr >> testConfig;
}
if ( testConfig.is_array() ) {
for ( auto& cc : testConfig ) {
cc.merge_patch( generalConfig );
TestDecayModel test{ cc };
test.run();
}
} else {
testConfig.merge_patch( generalConfig );
TestDecayModel test{ testConfig };
test.run();
}
return 0;
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Feb 23, 2:01 PM (2 h, 17 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4486435
Default Alt Text
(134 KB)

Event Timeline