Page MenuHomeHEPForge

No OneTemporary

diff --git a/Hadronization/ClusterFissioner.cc b/Hadronization/ClusterFissioner.cc
--- a/Hadronization/ClusterFissioner.cc
+++ b/Hadronization/ClusterFissioner.cc
@@ -1,2263 +1,2263 @@
// -*- C++ -*-
//
// ClusterFissioner.cc is a part of Herwig - A multi-purpose Monte Carlo event generator
// Copyright (C) 2002-2019 The Herwig Collaboration
//
// Herwig is licenced under version 3 of the GPL, see COPYING for details.
// Please respect the MCnet academic guidelines, see GUIDELINES for details.
//
//
// Thisk is the implementation of the non-inlined, non-templated member
// functions of the ClusterFissioner class.
//
#include "ClusterFissioner.h"
#include <ThePEG/Interface/ClassDocumentation.h>
#include <ThePEG/Interface/Reference.h>
#include <ThePEG/Interface/Parameter.h>
#include <ThePEG/Interface/Switch.h>
#include <ThePEG/Persistency/PersistentOStream.h>
#include <ThePEG/Persistency/PersistentIStream.h>
#include <ThePEG/PDT/EnumParticles.h>
#include "Herwig/Utilities/Kinematics.h"
#include "Cluster.h"
#include "ThePEG/Repository/UseRandom.h"
#include "ThePEG/Repository/EventGenerator.h"
#include <ThePEG/Utilities/DescribeClass.h>
#include "ThePEG/Interface/ParMap.h"
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/numeric/ublas/lu.hpp>
using namespace Herwig;
DescribeClass<ClusterFissioner,Interfaced>
describeClusterFissioner("Herwig::ClusterFissioner","Herwig.so");
ClusterFissioner::ClusterFissioner() :
_clMaxLight(3.35*GeV),
_clMaxExotic(3.35*GeV),
_clPowLight(2.0),
_clPowExotic(2.0),
_pSplitLight(1.0),
_pSplitExotic(1.0),
_phaseSpaceWeights(false),
_dim(4),
_fissionCluster(0),
_kinematicThresholdChoice(0),
_pwtDIquark(0.0),
_diquarkClusterFission(-1),
_btClM(1.0*GeV),
_iopRem(1),
_kappa(1.0e15*GeV/meter),
_kinematics(0),
_fluxTubeWidth(0.0),
_enhanceSProb(0),
_m0Fission(2.*GeV),
_massMeasure(0),
_probPowFactor(4.0),
_probShift(0.0),
_kinThresholdShift(1.0*sqr(GeV)),
_strictDiquarkKinematics(0),
_covariantBoost(false),
_allowHadronFinalStates(0),
_massSampler(0),
_phaseSpaceSampler(0),
_matrixElement(0)
{}
IBPtr ClusterFissioner::clone() const {
return new_ptr(*this);
}
IBPtr ClusterFissioner::fullclone() const {
return new_ptr(*this);
}
void ClusterFissioner::persistentOutput(PersistentOStream & os) const {
os << ounit(_clMaxLight,GeV)
<< ounit(_clMaxHeavy,GeV) << ounit(_clMaxExotic,GeV) << _clPowLight << _clPowHeavy
<< _clPowExotic << _pSplitLight
<< _pSplitHeavy << _pSplitExotic
<< _fissionCluster << _fissionPwt
<< _pwtDIquark
<< _diquarkClusterFission
<< _kinematics
<< _fluxTubeWidth
<< ounit(_btClM,GeV)
<< _iopRem << ounit(_kappa, GeV/meter)
<< _enhanceSProb << ounit(_m0Fission,GeV) << _massMeasure << _dim << _phaseSpaceWeights
<< _hadronSpectrum
<< _probPowFactor << _probShift << ounit(_kinThresholdShift,sqr(GeV))
<< _strictDiquarkKinematics
<< _covariantBoost
<< _allowHadronFinalStates
<< _massSampler
<< _phaseSpaceSampler
<< _matrixElement;
}
void ClusterFissioner::persistentInput(PersistentIStream & is, int) {
is >> iunit(_clMaxLight,GeV)
>> iunit(_clMaxHeavy,GeV) >> iunit(_clMaxExotic,GeV) >> _clPowLight >> _clPowHeavy
>> _clPowExotic >> _pSplitLight
>> _pSplitHeavy >> _pSplitExotic
>> _fissionCluster >> _fissionPwt
>> _pwtDIquark
>> _diquarkClusterFission
>> _kinematics
>> _fluxTubeWidth
>> iunit(_btClM,GeV)
>> _iopRem >> iunit(_kappa, GeV/meter)
>> _enhanceSProb >> iunit(_m0Fission,GeV) >> _massMeasure >> _dim >> _phaseSpaceWeights
>> _hadronSpectrum
>> _probPowFactor >> _probShift >> iunit(_kinThresholdShift,sqr(GeV))
>> _strictDiquarkKinematics
>> _covariantBoost
>> _allowHadronFinalStates
>> _massSampler
>> _phaseSpaceSampler
>> _matrixElement;
}
void ClusterFissioner::doinit() {
Interfaced::doinit();
for ( const long& id : spectrum()->heavyHadronizingQuarks() ) {
if ( _pSplitHeavy.find(id) == _pSplitHeavy.end() ||
_clPowHeavy.find(id) == _clPowHeavy.end() ||
_clMaxHeavy.find(id) == _clMaxHeavy.end() )
throw InitException() << "not all parameters have been set for heavy quark cluster fission";
}
// for default Pwts not needed to initialize
if (_fissionCluster==0) return;
for ( const long& id : spectrum()->lightHadronizingQuarks() ) {
if ( _fissionPwt.find(id) == _fissionPwt.end() )
// check that all relevant weights are set
throw InitException() << "fission weights for light quarks have not been set";
}
/*
// Not needed since we set Diquark weights from quark weights
for ( const long& id : spectrum()->lightHadronizingDiquarks() ) {
if ( _fissionPwt.find(id) == _fissionPwt.end() )
throw InitException() << "fission weights for light diquarks have not been set";
}*/
double pwtDquark=_fissionPwt.find(ParticleID::d)->second;
double pwtUquark=_fissionPwt.find(ParticleID::u)->second;
double pwtSquark=_fissionPwt.find(ParticleID::s)->second;
// ERROR: TODO makeDiquarkID is protected function ?
// _fissionPwt[spectrum()->makeDiquarkID(ParticleID::d,ParticleID::d,3)] = _pwtDIquark * pwtDquark * pwtDquark;
// _fissionPwt[spectrum()->makeDiquarkID(ParticleID::u,ParticleID::d,1)] = 0.5 * _pwtDIquark * pwtUquark * pwtDquark;
// _fissionPwt[spectrum()->makeDiquarkID(ParticleID::u,ParticleID::u,3)] = _pwtDIquark * pwtUquark * pwtUquark;
// _fissionPwt[spectrum()->makeDiquarkID(ParticleID::s,ParticleID::d,1)] = 0.5 * _pwtDIquark * pwtSquark * pwtDquark;
// _fissionPwt[spectrum()->makeDiquarkID(ParticleID::s,ParticleID::u,1)] = 0.5 * _pwtDIquark * pwtSquark * pwtUquark;
// _fissionPwt[spectrum()->makeDiquarkID(ParticleID::s,ParticleID::s,3)] = _pwtDIquark * pwtSquark * pwtSquark;
// TODO better solution for this magic number alternative
_fissionPwt[1103] = _pwtDIquark * pwtDquark * pwtDquark;
_fissionPwt[2101] = 0.5 * _pwtDIquark * pwtUquark * pwtDquark;
_fissionPwt[2203] = _pwtDIquark * pwtUquark * pwtUquark;
_fissionPwt[3101] = 0.5 * _pwtDIquark * pwtSquark * pwtDquark;
_fissionPwt[3201] = 0.5 * _pwtDIquark * pwtSquark * pwtUquark;
_fissionPwt[3303] = _pwtDIquark * pwtSquark * pwtSquark;
}
void ClusterFissioner::Init() {
static ClassDocumentation<ClusterFissioner> documentation
("Class responsibles for chopping up the clusters");
static Reference<ClusterFissioner,HadronSpectrum> interfaceHadronSpectrum
("HadronSpectrum",
"Set the Hadron spectrum for this cluster fissioner.",
&ClusterFissioner::_hadronSpectrum, false, false, true, false);
// ClMax for light, Bottom, Charm and exotic (e.g. Susy) quarks
static Parameter<ClusterFissioner,Energy>
interfaceClMaxLight ("ClMaxLight","cluster max mass for light quarks (unit [GeV])",
&ClusterFissioner::_clMaxLight, GeV, 3.35*GeV, ZERO, 10.0*GeV,
false,false,false);
static ParMap<ClusterFissioner,Energy> interfaceClMaxHeavy
("ClMaxHeavy",
"ClMax for heavy quarkls",
&ClusterFissioner::_clMaxHeavy, GeV, -1, 3.35*GeV, ZERO, 10.0*GeV,
false, false, Interface::upperlim);
static Parameter<ClusterFissioner,Energy>
interfaceClMaxExotic ("ClMaxExotic","cluster max mass for exotic quarks (unit [GeV])",
&ClusterFissioner::_clMaxExotic, GeV, 3.35*GeV, ZERO, 10.0*GeV,
false,false,false);
// ClPow for light, Bottom, Charm and exotic (e.g. Susy) quarks
static Parameter<ClusterFissioner,double>
interfaceClPowLight ("ClPowLight","cluster mass exponent for light quarks",
&ClusterFissioner::_clPowLight, 0, 2.0, 0.0, 10.0,false,false,false);
static ParMap<ClusterFissioner,double> interfaceClPowHeavy
("ClPowHeavy",
"ClPow for heavy quarks",
&ClusterFissioner::_clPowHeavy, -1, 1.0, 0.0, 10.0,
false, false, Interface::upperlim);
static Parameter<ClusterFissioner,double>
interfaceClPowExotic ("ClPowExotic","cluster mass exponent for exotic quarks",
&ClusterFissioner::_clPowExotic, 0, 2.0, 0.0, 10.0,false,false,false);
// PSplit for light, Bottom, Charm and exotic (e.g. Susy) quarks
static Parameter<ClusterFissioner,double>
interfacePSplitLight ("PSplitLight","cluster mass splitting param for light quarks",
&ClusterFissioner::_pSplitLight, 0, 1.0, 0.0, 10.0,false,false,false);
static ParMap<ClusterFissioner,double> interfacePSplitHeavy
("PSplitHeavy",
"PSplit for heavy quarks",
&ClusterFissioner::_pSplitHeavy, -1, 1.0, 0.0, 10.0,
false, false, Interface::upperlim);
static Parameter<ClusterFissioner,double>
interfacePSplitExotic ("PSplitExotic","cluster mass splitting param for exotic quarks",
&ClusterFissioner::_pSplitExotic, 0, 1.0, 0.0, 10.0,false,false,false);
static Switch<ClusterFissioner,int> interfaceFission
("Fission",
"Option for different Fission options",
&ClusterFissioner::_fissionCluster, 0, false, false);
static SwitchOption interfaceFissionDefault
(interfaceFission,
"Default",
"Normal cluster fission which depends on the hadron selector class.",
0);
static SwitchOption interfaceFissionNew
(interfaceFission,
"New",
"Alternative cluster fission which does not depend on the hadron selector class",
1);
// Switch C->H1,C2 C->H1,H2 on or off
static Switch<ClusterFissioner,int> interfaceAllowHadronFinalStates
("AllowHadronFinalStates",
"Option for allowing hadron final states of cluster fission",
&ClusterFissioner::_allowHadronFinalStates, 0, false, false);
static SwitchOption interfaceAllowHadronFinalStatesAll
(interfaceAllowHadronFinalStates,
"All",
"Option for allowing hadron final states of cluster fission "
"of type C->H1,C2 or C->H1,H2",
0);
static SwitchOption interfaceAllowHadronFinalStatesSemiHadronicOnly
(interfaceAllowHadronFinalStates,
"SemiHadronicOnly",
"Option for allowing hadron final states of cluster fission "
"of type C->H1,C2",
1);
static SwitchOption interfaceAllowHadronFinalStatesNone
(interfaceAllowHadronFinalStates,
"None",
"Option for disabling hadron final states of cluster fission",
2);
// Mass Sampler Switch
static Switch<ClusterFissioner,int> interfaceMassSampler
("MassSampler",
"Option for different Mass sampling options",
&ClusterFissioner::_massSampler, 0, false, false);
static SwitchOption interfaceMassSamplerDefault
(interfaceMassSampler,
"Default",
"Choose H7.2.3 default mass sampling using PSplitX",
0);
static SwitchOption interfaceMassSamplerUniform
(interfaceMassSampler,
"Uniform",
"Choose Uniform Mass sampling in M1,M2 space",
1);
static SwitchOption interfaceMassSamplerFlatPhaseSpace
(interfaceMassSampler,
"FlatPhaseSpace",
"Choose Flat Phase Space sampling of Mass in M1,M2 space",
2);
static SwitchOption interfaceMassSampleSoftMEPheno
(interfaceMassSampler,
"SoftMEPheno",
"Choose skewed Phase Space sampling of Masses in M1,M2 space "
"for greater efficiency in soft matrix element sampling",
3);
// Phase Space Sampler Switch
static Switch<ClusterFissioner,int> interfacePhaseSpaceSampler
("PhaseSpaceSampler",
"Option for different phase space sampling options",
&ClusterFissioner::_phaseSpaceSampler, 0, false, false);
static SwitchOption interfacePhaseSpaceSamplerDefault
(interfacePhaseSpaceSampler,
"FullyAligned",
"Herwig H7.2.3 default Cluster fission of all partons "
"aligned to the relative momentum of the mother cluster",
0);
static SwitchOption interfacePhaseSpaceSamplerAlignedIsotropic
(interfacePhaseSpaceSampler,
"AlignedIsotropic",
"Aligned Clusters but isotropic partons in their respective rest frame",
1);
static SwitchOption interfacePhaseSpaceSamplerFullyIsotropic
(interfacePhaseSpaceSampler,
"FullyIsotropic",
"Isotropic Clusters and isotropic partons in their respective rest frame "
"NOTE: Testing only!!",
2);
// Matrix Element Choice Switch
static Switch<ClusterFissioner,int> interfaceMatrixElement
("MatrixElement",
"Option for different Matrix Element options",
&ClusterFissioner::_matrixElement, 0, false, false);
static SwitchOption interfaceMatrixElementDefault
(interfaceMatrixElement,
"Default",
"Choose trivial matrix element i.e. whatever comes from the mass and "
"phase space sampling",
0);
static SwitchOption interfaceMatrixElementSoftQQbar
(interfaceMatrixElement,
"SoftQQbar",
"Choose Soft q1,q2->q1,q2,g*->q1,q2,q,qbar matrix element",
1);
static Switch<ClusterFissioner,int> interfaceDiquarkClusterFission
("DiquarkClusterFission",
"Allow clusters to fission to 1 or 2 diquark Clusters",
&ClusterFissioner::_diquarkClusterFission, -1, false, false);
static SwitchOption interfaceDiquarkClusterFissionAll
(interfaceDiquarkClusterFission,
"All",
"Allow diquark clusters and baryon clusters to fission to new diquark Clusters",
2);
static SwitchOption interfaceDiquarkClusterFissionOnlyBaryonClusters
(interfaceDiquarkClusterFission,
"OnlyBaryonClusters",
"Allow only baryon clusters to fission to new diquark Clusters",
1);
static SwitchOption interfaceDiquarkClusterFissionNo
(interfaceDiquarkClusterFission,
"No",
"Don't allow clusters to fission to new diquark Clusters",
0);
static SwitchOption interfaceDiquarkClusterFissionNoDiquarks
(interfaceDiquarkClusterFission,
"NoDiquarks",
"Don't allow diquark-antidiquark pairs to pop out of the vacuum",
-1);
static ParMap<ClusterFissioner,double> interfaceFissionPwt
("FissionPwt",
"The weights for quarks in the fission process.",
&ClusterFissioner::_fissionPwt, -1, 1.0, 0.0, 10.0,
false, false, Interface::upperlim);
static Switch<ClusterFissioner,int> interfaceRemnantOption
("RemnantOption",
"Option for the treatment of remnant clusters",
&ClusterFissioner::_iopRem, 1, false, false);
static SwitchOption interfaceRemnantOptionSoft
(interfaceRemnantOption,
"Soft",
"Both clusters produced in the fission of the beam cluster"
" are treated as soft clusters.",
0);
static SwitchOption interfaceRemnantOptionHard
(interfaceRemnantOption,
"Hard",
"Only the cluster containing the remnant is treated as a soft cluster.",
1);
static SwitchOption interfaceRemnantOptionVeryHard
(interfaceRemnantOption,
"VeryHard",
"Even remnant clusters are treated as hard, i.e. all clusters the same",
2);
static Parameter<ClusterFissioner,Energy> interfaceBTCLM
("SoftClusterFactor",
"Parameter for the mass spectrum of remnant clusters",
&ClusterFissioner::_btClM, GeV, 1.*GeV, 0.1*GeV, 10.0*GeV,
false, false, Interface::limited);
static Parameter<ClusterFissioner,Tension> interfaceStringTension
("StringTension",
"String tension used in vertex displacement calculation",
&ClusterFissioner::_kappa, GeV/meter,
1.0e15*GeV/meter, ZERO, ZERO,
false, false, Interface::lowerlim);
static Switch<ClusterFissioner,int> interfaceEnhanceSProb
("EnhanceSProb",
"Option for enhancing strangeness",
&ClusterFissioner::_enhanceSProb, 0, false, false);
static SwitchOption interfaceEnhanceSProbNo
(interfaceEnhanceSProb,
"No",
"No strangeness enhancement.",
0);
static SwitchOption interfaceEnhanceSProbScaled
(interfaceEnhanceSProb,
"Scaled",
"Scaled strangeness enhancement",
1);
static SwitchOption interfaceEnhanceSProbExponential
(interfaceEnhanceSProb,
"Exponential",
"Exponential strangeness enhancement",
2);
static Switch<ClusterFissioner,int> interfaceKinematics
("Kinematics",
"Option for selecting different Kinematics for ClusterFission",
&ClusterFissioner::_kinematics, 0, false, false);
static SwitchOption interfaceKinematicsDefault
(interfaceKinematics,
"Default",
"Fully aligned Cluster Fission along the Original cluster direction",
0);
static SwitchOption interfaceKinematicsIsotropic
(interfaceKinematics,
"Isotropic",
"Fully isotropic two body decay. Not recommended!",
1);
static SwitchOption interfaceKinematicsFluxTube
(interfaceKinematics,
"FluxTube",
"Aligned decay with gaussian pT kick with sigma=ClusterFissioner::FluxTube",
2);
static Switch<ClusterFissioner,int> interfaceMassMeasure
("MassMeasure",
"Option to use different mass measures",
&ClusterFissioner::_massMeasure,0,false,false);
static SwitchOption interfaceMassMeasureMass
(interfaceMassMeasure,
"Mass",
"Mass Measure",
0);
static SwitchOption interfaceMassMeasureLambda
(interfaceMassMeasure,
"Lambda",
"Lambda Measure",
1);
static Parameter<ClusterFissioner,Energy> interfaceFissionMassScale
("FissionMassScale",
"Cluster fission mass scale",
&ClusterFissioner::_m0Fission, GeV, 2.0*GeV, 0.1*GeV, 50.*GeV,
false, false, Interface::limited);
static Parameter<ClusterFissioner,double> interfaceProbPowFactor
("ProbablityPowerFactor",
"Power factor in ClausterFissioner bell probablity function",
&ClusterFissioner::_probPowFactor, 2.0, 1.0, 20.0,
false, false, Interface::limited);
static Parameter<ClusterFissioner,double> interfaceFluxTubeWidth
("FluxTubeWidth",
"sigma of gaussian sampling of pT for FluxTube kinematics",
&ClusterFissioner::_fluxTubeWidth, 0.0, 0.0, 1.0,
false, false, Interface::limited);
static Parameter<ClusterFissioner,double> interfaceProbShift
("ProbablityShift",
"Shifts from the center in ClausterFissioner bell probablity function",
&ClusterFissioner::_probShift, 0.0, -10.0, 10.0,
false, false, Interface::limited);
static Parameter<ClusterFissioner,Energy2> interfaceKineticThresholdShift
("KineticThresholdShift",
"Shifts from the kinetic threshold in ClausterFissioner",
&ClusterFissioner::_kinThresholdShift, sqr(GeV), 0.*sqr(GeV), -10.0*sqr(GeV), 10.0*sqr(GeV),
false, false, Interface::limited);
static Switch<ClusterFissioner,int> interfaceKinematicThreshold
("KinematicThreshold",
"Option for using static or dynamic kinematic thresholds in cluster splittings",
&ClusterFissioner::_kinematicThresholdChoice, 0, false, false);
static SwitchOption interfaceKinematicThresholdStatic
(interfaceKinematicThreshold,
"Static",
"Set static kinematic thresholds for cluster splittings.",
0);
static SwitchOption interfaceKinematicThresholdDynamic
(interfaceKinematicThreshold,
"Dynamic",
"Set dynamic kinematic thresholds for cluster splittings.",
1);
static Switch<ClusterFissioner,bool> interfacePhaseSpaceWeights
("PhaseSpaceWeights",
"Include phase space weights.",
&ClusterFissioner::_phaseSpaceWeights, false, false, false);
static SwitchOption interfacePhaseSpaceWeightsYes
(interfacePhaseSpaceWeights,
"Yes",
"Do include the effect of cluster fission phase space",
true);
static SwitchOption interfacePhaseSpaceWeightsNo
(interfacePhaseSpaceWeights,
"No",
"Do not include the effect of cluster phase space",
false);
static Switch<ClusterFissioner,bool> interfaceCovariantBoost
("CovariantBoost",
"Use single Covariant Boost for Cluster Fission",
&ClusterFissioner::_covariantBoost, false, false, false);
static SwitchOption interfaceCovariantBoostYes
(interfaceCovariantBoost,
"Yes",
"Use Covariant boost",
true);
static SwitchOption interfaceCovariantBoostNo
(interfaceCovariantBoost,
"No",
"Do NOT use Covariant boost",
false);
static Parameter<ClusterFissioner,double>
interfaceDim ("Dimension","Dimension in which phase space weights are calculated",
&ClusterFissioner::_dim, 0, 4.0, 3.0, 10.0,false,false, Interface::limited);
static Switch<ClusterFissioner,int> interfaceStrictDiquarkKinematics
("StrictDiquarkKinematics",
"Option for selecting different selection criterions of diquarks for ClusterFission",
&ClusterFissioner::_strictDiquarkKinematics, 0, false, false);
static SwitchOption interfaceStrictDiquarkKinematicsLoose
(interfaceStrictDiquarkKinematics,
"Loose",
"No kinematic threshold for diquark selection except for Mass bigger than 2 baryons",
0);
static SwitchOption interfaceStrictDiquarkKinematicsStrict
(interfaceStrictDiquarkKinematics,
"Strict",
"Resulting clusters are at least as heavy as 2 lightest baryons",
1);
static Parameter<ClusterFissioner,double> interfacePwtDIquark
("PwtDIquark",
"specific probability for choosing a d diquark",
&ClusterFissioner::_pwtDIquark, 0.0, 0.0, 10.0,
false, false, Interface::limited);
}
tPVector ClusterFissioner::fission(ClusterVector & clusters, bool softUEisOn) {
// return if no clusters
if (clusters.empty()) return tPVector();
/*****************
* Loop over the (input) collection of cluster pointers, and store in
* the vector splitClusters all the clusters that need to be split
* (these are beam clusters, if soft underlying event is off, and
* heavy non-beam clusters).
********************/
stack<ClusterPtr> splitClusters;
for(ClusterVector::iterator it = clusters.begin() ;
it != clusters.end() ; ++it) {
/**************
* Skip 3-component clusters that have been redefined (as 2-component
* clusters) or not available clusters. The latter check is indeed
* redundant now, but it is used for possible future extensions in which,
* for some reasons, some of the clusters found by ClusterFinder are tagged
* straight away as not available.
**************/
if((*it)->isRedefined() || !(*it)->isAvailable()) continue;
// if the cluster is a beam cluster add it to the vector of clusters
// to be split or if it is heavy
// TODO maybe BeamClusters must not necessarily be split since can be very light
// i.e. also lighter than the lightest constituents one can make of those
if((*it)->isBeamCluster() || isHeavy(*it)) splitClusters.push(*it);
}
tPVector finalhadrons;
cut(splitClusters, clusters, finalhadrons, softUEisOn);
return finalhadrons;
}
void ClusterFissioner::cut(stack<ClusterPtr> & clusterStack,
ClusterVector &clusters, tPVector & finalhadrons,
bool softUEisOn) {
/**************************************************
* This method does the splitting of the cluster pointed by cluPtr
* and "recursively" by all of its cluster children, if heavy. All of these
* new children clusters are added (indeed the pointers to them) to the
* collection of cluster pointers collecCluPtr. The method works as follows.
* Initially the vector vecCluPtr contains just the input pointer to the
* cluster to be split. Then it will be filled "recursively" by all
* of the cluster's children that are heavy enough to require, in their turn,
* to be split. In each loop, the last element of the vector vecCluPtr is
* considered (only once because it is then removed from the vector).
* This approach is conceptually recursive, but avoid the overhead of
* a concrete recursive function. Furthermore it requires minimal changes
* in the case that the fission of an heavy cluster could produce more
* than two cluster children as assumed now.
*
* Draw the masses: for normal, non-beam clusters a power-like mass dist
* is used, whereas for beam clusters a fast-decreasing exponential mass
* dist is used instead (to avoid many iterative splitting which could
* produce an unphysical large transverse energy from a supposed soft beam
* remnant process).
****************************************/
// Here we recursively loop over clusters in the stack and cut them
while (!clusterStack.empty()) {
// take the last element of the vector
ClusterPtr iCluster = clusterStack.top(); clusterStack.pop();
// split it
cutType ct = iCluster->numComponents() == 2 ?
cutTwo(iCluster, finalhadrons, softUEisOn) :
cutThree(iCluster, finalhadrons, softUEisOn);
// There are cases when we don't want to split, even if it fails mass test
if(!ct.first.first || !ct.second.first) {
// if an unsplit beam cluster leave if for the underlying event
if(iCluster->isBeamCluster() && softUEisOn)
iCluster->isAvailable(false);
continue;
}
// check if clusters
ClusterPtr one = dynamic_ptr_cast<ClusterPtr>(ct.first.first);
ClusterPtr two = dynamic_ptr_cast<ClusterPtr>(ct.second.first);
// is a beam cluster must be split into two clusters
if(iCluster->isBeamCluster() && (!one||!two) && softUEisOn) {
iCluster->isAvailable(false);
continue;
}
// There should always be a intermediate quark(s) from the splitting
assert(ct.first.second && ct.second.second);
/// \todo sort out motherless quark pairs here. Watch out for 'quark in final state' errors
iCluster->addChild(ct.first.first);
// iCluster->addChild(ct.first.second);
// ct.first.second->addChild(ct.first.first);
iCluster->addChild(ct.second.first);
// iCluster->addChild(ct.second.second);
// ct.second.second->addChild(ct.second.first);
// Sometimes the clusters decay C -> H + C' or C -> H + H' rather then C -> C' + C''
if(one) {
clusters.push_back(one);
if(one->isBeamCluster() && softUEisOn)
one->isAvailable(false);
if(isHeavy(one) && one->isAvailable())
clusterStack.push(one);
}
if(two) {
clusters.push_back(two);
if(two->isBeamCluster() && softUEisOn)
two->isAvailable(false);
if(isHeavy(two) && two->isAvailable())
clusterStack.push(two);
}
}
}
ClusterFissioner::cutType
ClusterFissioner::cutTwo(ClusterPtr & cluster, tPVector & finalhadrons,
bool softUEisOn) {
// need to make sure only 2-cpt clusters get here
assert(cluster->numComponents() == 2);
tPPtr ptrQ1 = cluster->particle(0);
tPPtr ptrQ2 = cluster->particle(1);
Energy Mc = cluster->mass();
// Energy Mcm = cluster->momentum().m();
// if (fabs((Mc-Mcm)/GeV)>0.1 ) {
// std::cout << "Mc = "<<Mc/GeV <<"\tMcm = "<< Mcm/GeV <<"\n";
// exit(2);
// }
assert(ptrQ1);
assert(ptrQ2);
// And check if those particles are from a beam remnant
bool rem1 = cluster->isBeamRemnant(0);
bool rem2 = cluster->isBeamRemnant(1);
// workout which distribution to use
bool soft1(false),soft2(false);
switch (_iopRem) {
case 0:
soft1 = rem1 || rem2;
soft2 = rem2 || rem1;
break;
case 1:
soft1 = rem1;
soft2 = rem2;
break;
}
// Initialization for the exponential ("soft") mass distribution.
static const int max_loop = 1000;
int counter = 0;
Energy Mc1 = ZERO;
Energy Mc2 = ZERO;
Energy m=ZERO;
Energy m1 = ptrQ1->data().constituentMass();
Energy m2 = ptrQ2->data().constituentMass();
tcPDPtr toHadron1, toHadron2;
PPtr newPtr1 = PPtr ();
PPtr newPtr2 = PPtr ();
Lorentz5Momentum pClu1, pClu2, pQ1, pQone, pQtwo, pQ2;
Lorentz5Momentum pClu = cluster->momentum(); // known
Lorentz5Momentum p0Q1 = ptrQ1->momentum(); // known (mom Q1 before fission)
Lorentz5Momentum p0Q2 = ptrQ2->momentum(); // known (mom Q2 before fission)
// where to return to in case of rejected sample
enum returnTo {
FlavourSampling,
MassSampling,
PhaseSpaceSampling,
MatrixElementSampling,
Done
};
// start with FlavourSampling
returnTo retTo=FlavourSampling;
// ### Flavour, Mass, PhaseSpace and MatrixElement Sampling loop until accepted: ###
do
{
counter++;
switch (retTo)
{
case FlavourSampling:
{
// ## Flavour sampling and kinematic constraints ##
drawNewFlavour(newPtr1,newPtr2,cluster);
// get a flavour for the qqbar pair
// check for right ordering
assert (ptrQ2);
assert (newPtr2);
assert (ptrQ2->dataPtr());
assert (newPtr2->dataPtr());
// TODO careful if DiquarkClusters can exist
bool Q1diq = DiquarkMatcher::Check(ptrQ1->id());
bool Q2diq = DiquarkMatcher::Check(ptrQ2->id());
bool newQ1diq = DiquarkMatcher::Check(newPtr1->id());
bool newQ2diq = DiquarkMatcher::Check(newPtr2->id());
bool diqClu1 = Q1diq && newQ1diq;
bool diqClu2 = Q2diq && newQ2diq;
// DEBUG only:
// std::cout << "Make Clusters: ( " << ptrQ1->PDGName() << " " << newPtr1->PDGName() << " ), ( "
// << ptrQ2->PDGName() << " " << newPtr2->PDGName() << " )\n";
// check if Hadron formation is possible
if (!( diqClu1 || diqClu2 )
&& (cantMakeHadron(ptrQ1, newPtr1) || cantMakeHadron(ptrQ2, newPtr2))) {
swap(newPtr1, newPtr2);
// check again
if(cantMakeHadron(ptrQ1, newPtr1) || cantMakeHadron(ptrQ2, newPtr2)) {
throw Exception()
<< "ClusterFissioner cannot split the cluster ("
<< ptrQ1->PDGName() << ' ' << ptrQ2->PDGName()
<< ") into hadrons.\n"
<< "drawn Flavour: "<< newPtr1->PDGName()<<"\n"<< Exception::runerror;
}
}
else if ( diqClu1 || diqClu2 ){
bool swapped=false;
if ( !diqClu1 && cantMakeHadron(ptrQ1,newPtr1) ) {
swap(newPtr1, newPtr2);
swapped=true;
}
if ( !diqClu2 && cantMakeHadron(ptrQ2,newPtr2) ) {
assert(!swapped);
swap(newPtr1, newPtr2);
}
if ( diqClu2 && diqClu1 && ptrQ1->id()*newPtr1->id()>0 ) {
assert(!swapped);
swap(newPtr1, newPtr2);
}
if (!diqClu1) assert(!cantMakeHadron(ptrQ1,newPtr1));
if (!diqClu2) assert(!cantMakeHadron(ptrQ2,newPtr2));
}
// Check that new clusters can produce particles and there is enough
// phase space to choose the drawn flavour
m = newPtr1->data().constituentMass();
// Do not split in the case there is no phase space available
if(Mc < m1 + m + m2 + m) {
retTo = FlavourSampling;
continue;
}
pQ1.setMass(m1);
pQone.setMass(m);
pQtwo.setMass(m);
pQ2.setMass(m2);
// Note: want to fallthrough (in C++17 one could uncomment
// the line below to show that this is intentional)
[[fallthrough]];
}
/*
* MassSampling choices:
* - Default (default)
* - Uniform
* - FlatPhaseSpace
* - SoftMEPheno
* */
case MassSampling:
{
// TODO insert modular function
bool failure = drawNewMasses(Mc, soft1, soft2, pClu1, pClu2,
ptrQ1, pQ1, newPtr1, pQone,
newPtr2, pQtwo, ptrQ2, pQ2);
if(failure) {
retTo = FlavourSampling;
continue;
}
// derive the masses of the children
Mc1 = pClu1.mass();
Mc2 = pClu2.mass();
// TODO include this in the drawNewMasses ? --> not necessary if we include this already in drawNewMasses
// static kinematic threshold
if(_kinematicThresholdChoice == 0) {
if(Mc1 < m1+m || Mc2 < m+m2 || Mc1+Mc2 > Mc){
retTo = FlavourSampling;
continue;
}
// dynamic kinematic threshold
} else if(_kinematicThresholdChoice == 1) {
bool C1 = ( sqr(Mc1) )/( sqr(m1) + sqr(m) + _kinThresholdShift ) < 1.0 ? true : false;
bool C2 = ( sqr(Mc2) )/( sqr(m2) + sqr(m) + _kinThresholdShift ) < 1.0 ? true : false;
bool C3 = ( sqr(Mc1) + sqr(Mc2) )/( sqr(Mc) ) > 1.0 ? true : false;
if( C1 || C2 || C3 ) {
retTo = FlavourSampling;
continue;
}
}
// TODO change this to use the interface _allowHadronFinalStates
// avoid C-> C1,H2 or H1,H2
// if ( Mc1 < spectrum()->massLightestHadronPair(ptrQ1->dataPtr(),newPtr1->dataPtr())) {
// std::cout << "Cluster decays to hadron MC"<< Mc1/GeV << "\t MLHP "<< spectrum()->massLightestHadronPair(ptrQ1->dataPtr(),newPtr1->dataPtr())/GeV<<"\n";
// std::cout << "Flavour " << ptrQ1->PDGName() <<", " << newPtr1->PDGName()<< "\n";
// continue;
// }
// if ( Mc2 < spectrum()->massLightestHadronPair(ptrQ2->dataPtr(),newPtr2->dataPtr())) {
// std::cout << "Cluster decays to hadron MC"<< Mc2/GeV << "\t MLHP "<< spectrum()->massLightestHadronPair(ptrQ2->dataPtr(),newPtr2->dataPtr())/GeV<<"\n";
// std::cout << "Flavour " << ptrQ2->PDGName() <<", " << newPtr2->PDGName()<< "\n";
// continue;
// }
/**************************
* New (not present in Fortran Herwig):
* check whether the fragment masses Mc1 and Mc2 are above the
* threshold for the production of the lightest pair of hadrons with the
* right flavours. If not, then set by hand the mass to the lightest
* single hadron with the right flavours, in order to solve correctly
* the kinematics, and (later in this method) create directly such hadron
* and add it to the children hadrons of the cluster that undergoes the
* fission (i.e. the one pointed by iCluPtr). Notice that in this special
* case, the heavy cluster that undergoes the fission has one single
* cluster child and one single hadron child. We prefer this approach,
* rather than to create a light cluster, with the mass set equal to
* the lightest hadron, and let then the class LightClusterDecayer to do
* the job to decay it to that single hadron, for two reasons:
* First, because the sum of the masses of the two constituents can be,
* in this case, greater than the mass of that hadron, hence it would
* be impossible to solve the kinematics for such two components, and
* therefore we would have a cluster whose components are undefined.
* Second, the algorithm is faster, because it avoids the reshuffling
* procedure that would be necessary if we used LightClusterDecayer
* to decay the light cluster to the lightest hadron.
****************************/
// override chosen masses if needed
toHadron1 = _hadronSpectrum->chooseSingleHadron(ptrQ1->dataPtr(), newPtr1->dataPtr(),Mc1);
if (toHadron1 && _allowHadronFinalStates == 2 ) {
retTo = FlavourSampling;
continue;
}
if(toHadron1) {
Mc1 = toHadron1->mass();
pClu1.setMass(Mc1);
}
toHadron2 = _hadronSpectrum->chooseSingleHadron(ptrQ2->dataPtr(), newPtr2->dataPtr(),Mc2);
if (toHadron2 && _allowHadronFinalStates == 2 ) {
retTo = FlavourSampling;
continue;
}
if(toHadron2) {
Mc2 = toHadron2->mass();
pClu2.setMass(Mc2);
}
if (_allowHadronFinalStates && toHadron1 && toHadron2) {
retTo = FlavourSampling;
continue;
}
// if a beam cluster not allowed to decay to hadrons
if(cluster->isBeamCluster() && (toHadron1||toHadron2) && softUEisOn) {
retTo = FlavourSampling;
continue;
}
// Check if the decay kinematics is still possible: if not then
// force the one-hadron decay for the other cluster as well.
if(Mc1 + Mc2 > Mc) {
// reject if we would need to create a C->H,H process if _allowHadronFinalStates>=1
if (_allowHadronFinalStates>=1) {
retTo = FlavourSampling;
continue;
}
if(!toHadron1) {
toHadron1 = _hadronSpectrum->chooseSingleHadron(ptrQ1->dataPtr(), newPtr1->dataPtr(),Mc-Mc2);
if(toHadron1) {
Mc1 = toHadron1->mass();
pClu1.setMass(Mc1);
}
}
else if(!toHadron2) {
toHadron2 = _hadronSpectrum->chooseSingleHadron(ptrQ2->dataPtr(), newPtr2->dataPtr(),Mc-Mc1);
if(toHadron2) {
Mc2 = toHadron2->mass();
pClu2.setMass(Mc2);
}
}
}
if (Mc <= Mc1+Mc2){
retTo = FlavourSampling;
continue;
}
// Determined the (5-components) momenta (all in the LAB frame)
p0Q1.setMass(ptrQ1->mass()); // known (mom Q1 before fission)
p0Q1.rescaleEnergy(); // TODO check if needed
p0Q2.setMass(ptrQ2->mass()); // known (mom Q2 before fission)
p0Q2.rescaleEnergy();// TODO check if needed
pClu.rescaleMass();
// Note: want to fallthrough (in C++17 one could uncomment
// the line below to show that this is intentional)
[[fallthrough]];
}
/*
* PhaseSpaceSampling choices:
* - FullyAligned (default)
* - AlignedIsotropic
* - FullyIsotropic
* */
case PhaseSpaceSampling:
{
// ### Sample the Phase Space with respect to Matrix Element: ###
// TODO insert here PhaseSpace sampler
bool failure = drawKinematics(pClu,p0Q1,p0Q2,toHadron1,toHadron2,
pClu1,pClu2,pQ1,pQone,pQtwo,pQ2);
if(failure) {
retTo = MassSampling;
continue;
}
// Should be precise i.e. no rejection expected
// Note: want to fallthrough (in C++17 one could uncomment
// the line below to show that this is intentional)
[[fallthrough]];
}
/*
* MatrixElementSampling choices:
* - Default (default)
* - SoftQQbar
* */
case MatrixElementSampling:
{
// TODO insert here partonic Matrix Element rejection
double Pacc = 1.0; // overestimate veto
- std::ofstream out("Pacc.dat", std::ios::app | std::ios::out);
- out << Pacc << "\n";
- out.close();
+ // std::ofstream out("Pacc.dat", std::ios::app | std::ios::out);
+ // out << Pacc << "\n";
+ // out.close();
if (UseRandom::rnd()<Pacc) {
retTo=Done;
break;
}
/*
Energy2 p1Dotp2 = pQ1Hat*pQ2Hat;
Energy2 p1Dotp2Max = Max(pQ1Hat,pQ2Hat);
Energy2 p1Dotq = pQ1Hat*pQ;
Energy2 p1DotqMin = Min(pQ1Hat,pQ);
Energy2 p1DotqMax = Max(pQ1Hat,pQ);
Energy2 p2Dotq = pQ2Hat*pQ;
Energy2 p2DotqMin = Min(pQ2Hat,pQ);
Energy2 p2DotqMax = Max(pQ2Hat,pQ);
Energy2 p1Dotqbar = pQ1Hat*pQbar;
Energy2 p1DotqbarMin = Min(pQ1Hat,pQbar);
Energy2 p1DotqbarMax = Max(pQ1Hat,pQbar);
Energy2 p2Dotqbar = pQ2Hat*pQbar;
Energy2 p2DotqbarMin = Min(pQ2Hat,pQbar);
Energy2 p2DotqbarMax = Max(pQ2Hat,pQbar);
Energy2 qDotqbar = pQ*pQbar;
Energy2 qDotqbarMin = Min(pQ,pQbar);
Energy2 qDotqbarMax = Max(pQ,pQbar);
// double Msquared=-GeV2*GeV2*(p1Dotq*p2Dotqbar+p2Dotq*p1Dotqbar-p1Dotp2*qDotqbar)/(qDotqbar*qDotqbar*(p1Dotq+p1Dotqbar)*(p2Dotq+p2Dotqbar));
double Msquared=GeV2*GeV2*(p1Dotp2*qDotqbar-p1Dotq*p2Dotqbar-p2Dotq*p1Dotqbar)/(sqr(qDotqbar+2*sqr(pQ.mass()))*(p1Dotq+p1Dotqbar)*(p2Dotq+p2Dotqbar));
// if (Msquared<0) {std::cout << "Msq<0\n";continue;}
if (Msquared<0) {continue;}
// double overEstimate=GeV2*GeV2*(p1DotqMax*p2DotqbarMax+p2DotqMax*p1DotqbarMax-p1Dotp2Min*qDotqbarMin)/(qDotqbarMin*qDotqbarMin*(p1DotqMin+p1DotqbarMin)*(p2DotqMin+p2DotqbarMin));
double overEstimate=GeV2*GeV2*(p1Dotp2Max*qDotqbarMin-p1DotqMin*p2DotqbarMin-p2DotqMin*p1DotqbarMin)/(sqr(qDotqbarMin+2*sqr(pQ.mass()))*(p1DotqMin+p1DotqbarMin)*(p2DotqMin+p2DotqbarMin));
assert(overEstimate>0);
ratio=Msquared/overEstimate;
*/
// retTo = FlavourSampling;
retTo = PhaseSpaceSampling;
continue;
}
default:
{
assert(false);
}
}
}
while (retTo!=Done && counter < max_loop);
if(counter >= max_loop) {
// happens if we get at too light cluster to begin with
// TODO exclude this by ensuring that there is always enough phase space for M>m1+m2+2*m and maybe other conditions
// std::cout << "ERROR: Max Looped\n";
static const PPtr null = PPtr();
return cutType(PPair(null,null),PPair(null,null));
}
// ==> full sample generated
/******************
* The previous methods have determined the kinematics and positions
* of C -> C1 + C2.
* In the case that one of the two product is light, that means either
* decayOneHadronClu1 or decayOneHadronClu2 is true, then the momenta
* of the components of that light product have not been determined,
* and a (light) cluster will not be created: the heavy father cluster
* decays, in this case, into a single (not-light) cluster and a
* single hadron. In the other, "normal", cases the father cluster
* decays into two clusters, each of which has well defined components.
* Notice that, in the case of components which point to particles, the
* momenta of the components is properly set to the new values, whereas
* we do not change the momenta of the pointed particles, because we
* want to keep all of the information (that is the new momentum of a
* component after the splitting, which is contained in the _momentum
* member of the Component class, and the (old) momentum of that component
* before the splitting, which is contained in the momentum of the
* pointed particle). Please not make confusion of this only apparent
* inconsistency!
********************/
LorentzPoint posC,pos1,pos2;
posC = cluster->vertex();
calculatePositions(pClu, posC, pClu1, pClu2, pos1, pos2);
cutType rval;
if(toHadron1) {
rval.first = produceHadron(toHadron1, newPtr1, pClu1, pos1);
finalhadrons.push_back(rval.first.first);
}
else {
rval.first = produceCluster(ptrQ1, newPtr1, pClu1, pos1, pQ1, pQone, rem1);
}
if(toHadron2) {
rval.second = produceHadron(toHadron2, newPtr2, pClu2, pos2);
finalhadrons.push_back(rval.second.first);
}
else {
rval.second = produceCluster(ptrQ2, newPtr2, pClu2, pos2, pQ2, pQtwo, rem2);
}
return rval;
}
ClusterFissioner::cutType
ClusterFissioner::cutThree(ClusterPtr & cluster, tPVector & finalhadrons,
bool softUEisOn) {
// need to make sure only 3-cpt clusters get here
assert(cluster->numComponents() == 3);
// extract quarks
tPPtr ptrQ[3] = {cluster->particle(0),cluster->particle(1),cluster->particle(2)};
assert( ptrQ[0] && ptrQ[1] && ptrQ[2] );
// find maximum mass pair
Energy mmax(ZERO);
Lorentz5Momentum pDiQuark;
int iq1(-1),iq2(-1);
Lorentz5Momentum psum;
for(int q1=0;q1<3;++q1) {
psum+= ptrQ[q1]->momentum();
for(int q2=q1+1;q2<3;++q2) {
Lorentz5Momentum ptest = ptrQ[q1]->momentum()+ptrQ[q2]->momentum();
ptest.rescaleMass();
Energy mass = ptest.m();
if(mass>mmax) {
mmax = mass;
pDiQuark = ptest;
iq1 = q1;
iq2 = q2;
}
}
}
// and the spectators
int iother(-1);
for(int ix=0;ix<3;++ix) if(ix!=iq1&&ix!=iq2) iother=ix;
assert(iq1>=0&&iq2>=0&&iother>=0);
// And check if those particles are from a beam remnant
bool rem1 = cluster->isBeamRemnant(iq1);
bool rem2 = cluster->isBeamRemnant(iq2);
// workout which distribution to use
bool soft1(false),soft2(false);
switch (_iopRem) {
case 0:
soft1 = rem1 || rem2;
soft2 = rem2 || rem1;
break;
case 1:
soft1 = rem1;
soft2 = rem2;
break;
}
// Initialization for the exponential ("soft") mass distribution.
static const int max_loop = 1000;
int counter = 0;
Energy Mc1 = ZERO, Mc2 = ZERO, m1=ZERO, m2=ZERO, m=ZERO;
tcPDPtr toHadron;
bool toDiQuark(false);
PPtr newPtr1 = PPtr(),newPtr2 = PPtr();
PDPtr diquark;
bool succeeded = false;
Lorentz5Momentum pClu1, pClu2, pQ1, pQone, pQtwo, pQ2;
do {
succeeded = false;
++counter;
// get a flavour for the qqbar pair
drawNewFlavour(newPtr1,newPtr2,cluster);
// randomly pick which will be (anti)diquark and which a mesonic cluster
if(UseRandom::rndbool()) {
swap(iq1,iq2);
swap(rem1,rem2);
}
// check first order
if(cantMakeHadron(ptrQ[iq1], newPtr1) || !spectrum()->canMakeDiQuark(ptrQ[iq2], newPtr2)) {
swap(newPtr1,newPtr2);
}
// check again
if(cantMakeHadron(ptrQ[iq1], newPtr1) || !spectrum()->canMakeDiQuark(ptrQ[iq2], newPtr2)) {
throw Exception()
<< "ClusterFissioner cannot split the cluster ("
<< ptrQ[iq1]->PDGName() << ' ' << ptrQ[iq2]->PDGName()
<< ") into a hadron and diquark.\n" << Exception::runerror;
}
// Check that new clusters can produce particles and there is enough
// phase space to choose the drawn flavour
m1 = ptrQ[iq1]->data().constituentMass();
m2 = ptrQ[iq2]->data().constituentMass();
m = newPtr1->data().constituentMass();
// Do not split in the case there is no phase space available
if(mmax < m1+m + m2+m) continue;
pQ1.setMass(m1);
pQone.setMass(m);
pQtwo.setMass(m);
pQ2.setMass(m2);
bool failure = drawNewMasses(mmax, soft1, soft2, pClu1, pClu2,
ptrQ[iq1], pQ1, newPtr1, pQone,
newPtr2, pQtwo, ptrQ[iq1], pQ2);
if (failure) continue;
Mc1 = pClu1.mass();
Mc2 = pClu2.mass();
if(Mc1 < m1+m || Mc2 < m+m2 || Mc1+Mc2 > mmax) continue;
if ( _phaseSpaceWeights ) {
if ( phaseSpaceVeto(mmax,Mc1,Mc2,m,m1,m2) )
continue;
}
// check if need to force meson clster to hadron
toHadron = _hadronSpectrum->chooseSingleHadron(ptrQ[iq1]->dataPtr(), newPtr1->dataPtr(),Mc1);
if(toHadron) { Mc1 = toHadron->mass(); pClu1.setMass(Mc1); }
// check if need to force diquark cluster to be on-shell
toDiQuark = false;
diquark = spectrum()->makeDiquark(ptrQ[iq2]->dataPtr(), newPtr2->dataPtr());
if(Mc2 < diquark->constituentMass()) {
Mc2 = diquark->constituentMass(); pClu2.setMass(Mc2);
toDiQuark = true;
}
// if a beam cluster not allowed to decay to hadrons
if(cluster->isBeamCluster() && toHadron && softUEisOn)
continue;
// Check if the decay kinematics is still possible: if not then
// force the one-hadron decay for the other cluster as well.
if(Mc1 + Mc2 > mmax) {
if(!toHadron) {
toHadron = _hadronSpectrum->chooseSingleHadron(ptrQ[iq1]->dataPtr(), newPtr1->dataPtr(),mmax-Mc2);
if(toHadron) { Mc1 = toHadron->mass(); pClu1.setMass(Mc1); }
}
else if(!toDiQuark) {
Mc2 = _hadronSpectrum->massLightestHadron(ptrQ[iq2]->dataPtr(), newPtr2->dataPtr()); pClu2.setMass(Mc2);
toDiQuark = true;
}
}
succeeded = (mmax >= Mc1+Mc2);
}
while (!succeeded && counter < max_loop);
// check no of tries
if(counter >= max_loop) return cutType();
// Determine the (5-components) momenta (all in the LAB frame)
Lorentz5Momentum p0Q1 = ptrQ[iq1]->momentum();
Lorentz5Momentum p0Q2 = ptrQ[iq2]->momentum(); //dummy
p0Q1.setMass(ptrQ[iq1]->momentum().mass());
p0Q2.setMass(ptrQ[iq2]->momentum().mass());
drawKinematics(pDiQuark,p0Q1,p0Q2,toHadron,toDiQuark,
pClu1,pClu2,pQ1,pQone,pQtwo,pQ2);
// positions of the new clusters
LorentzPoint pos1,pos2;
Lorentz5Momentum pBaryon = pClu2+ptrQ[iother]->momentum();
calculatePositions(cluster->momentum(), cluster->vertex(), pClu1, pBaryon, pos1, pos2);
// first the mesonic cluster/meson
cutType rval;
if(toHadron) {
rval.first = produceHadron(toHadron, newPtr1, pClu1, pos1);
finalhadrons.push_back(rval.first.first);
}
else {
rval.first = produceCluster(ptrQ[iq1], newPtr1, pClu1, pos1, pQ1, pQone, rem1);
}
if(toDiQuark) {
rem2 |= cluster->isBeamRemnant(iother);
PPtr newDiQuark = diquark->produceParticle(pClu2);
rval.second = produceCluster(newDiQuark, ptrQ[iother], pBaryon, pos2, pClu2,
ptrQ[iother]->momentum(), rem2);
}
else {
rval.second = produceCluster(ptrQ[iq2], newPtr2, pBaryon, pos2, pQ2, pQtwo, rem2,
ptrQ[iother],cluster->isBeamRemnant(iother));
}
cluster->isAvailable(false);
return rval;
}
ClusterFissioner::PPair
ClusterFissioner::produceHadron(tcPDPtr hadron, tPPtr newPtr, const Lorentz5Momentum &a,
const LorentzPoint &b) const {
PPair rval;
if(hadron->coloured()) {
rval.first = (_hadronSpectrum->lightestHadron(hadron,newPtr->dataPtr()))->produceParticle();
}
else
rval.first = hadron->produceParticle();
rval.second = newPtr;
rval.first->set5Momentum(a);
rval.first->setVertex(b);
return rval;
}
ClusterFissioner::PPair ClusterFissioner::produceCluster(tPPtr ptrQ, tPPtr newPtr,
const Lorentz5Momentum & a,
const LorentzPoint & b,
const Lorentz5Momentum & c,
const Lorentz5Momentum & d,
bool isRem,
tPPtr spect, bool remSpect) const {
PPair rval;
rval.second = newPtr;
ClusterPtr cluster = !spect ? new_ptr(Cluster(ptrQ,rval.second)) : new_ptr(Cluster(ptrQ,rval.second,spect));
rval.first = cluster;
cluster->set5Momentum(a);
cluster->setVertex(b);
assert(cluster->particle(0)->id() == ptrQ->id());
cluster->particle(0)->set5Momentum(c);
cluster->particle(1)->set5Momentum(d);
cluster->setBeamRemnant(0,isRem);
if(remSpect) cluster->setBeamRemnant(2,remSpect);
return rval;
}
/**
* Claculate a veto for the phase space weight
*/
bool ClusterFissioner::phaseSpaceVeto(const Energy Mc, const Energy Mc1, const Energy Mc2,
const Energy m, const Energy m1, const Energy m2) const {
double M_temp = Mc/GeV;
double M1_temp = Mc1/GeV;
double M2_temp = Mc2/GeV;
double m_temp = m/GeV;
double m1_temp = m1/GeV;
double m2_temp = m2/GeV;
double lam1 = sqrt(Kinematics::kaellen(M1_temp, m1_temp, m_temp));
double lam2 = sqrt(Kinematics::kaellen(M2_temp, m2_temp, m_temp));
double lam3 = sqrt(Kinematics::kaellen(M_temp, M1_temp, M2_temp));
double ratio;
double PSweight = pow(lam1*lam2*lam3,_dim-3.)*pow(M1_temp*M2_temp,2.-_dim);
// overestimate only possible for dim>=3.0
assert(_dim>=3.0);
double overEstimate = _dim>=4.0 ? pow(M_temp,4.*_dim-14.):pow(M_temp,2*(_dim-3.0))/pow((m1_temp+m_temp)*(m2_temp+m_temp),4.0-_dim);
ratio = PSweight/overEstimate;
// if (!(ratio>=0)) std::cout << "M "<<M_temp<<" M1 "<<M1_temp<<" M2 "<<M2_temp<<" m1 "<<m1_temp<<" m2 "<<m2_temp<<" m "<<m_temp<<"\n\n";
assert (ratio >= 0);
assert (ratio <= 1);
return (UseRandom::rnd()>ratio);
}
/**
* Calculate the masses and possibly kinematics of the cluster
* fission at hand; if claculateKineamtics is perfomring non-trivial
* steps kinematics calulcated here will be overriden. Currently resorts to the default
*/
bool ClusterFissioner::drawNewMasses(const Energy Mc, bool soft1, bool soft2,
Lorentz5Momentum& pClu1, Lorentz5Momentum& pClu2,
tcPPtr ptrQ1, const Lorentz5Momentum& pQ1,
tcPPtr, const Lorentz5Momentum& pQone,
tcPPtr, const Lorentz5Momentum& pQtwo,
tcPPtr ptrQ2, const Lorentz5Momentum& pQ2) const {
switch (_massSampler)
{
case 0:
return drawNewMassesDefault(Mc, soft1, soft2, pClu1, pClu2, ptrQ1, pQ1, pQone, pQtwo, ptrQ2, pQ2);
break;
case 1:
return drawNewMassesUniform(Mc, pClu1, pClu2, pQ1, pQone, pQtwo, pQ2);
break;
case 2:
return drawNewMassesPhaseSpace(Mc, pClu1, pClu2, pQ1, pQone, pQtwo, pQ2);
break;
case 3:
return drawNewMassesPhaseSpaceExtended(Mc, pClu1, pClu2, pQ1, pQone, pQtwo, pQ2);
break;
default:
assert(false);
}
return true;// failure
}
/**
* Calculate the masses and possibly kinematics of the cluster
* fission at hand; if claculateKineamtics is perfomring non-trivial
* steps kinematics claulcated here will be overriden. Currentl;y resorts to the default
*/
bool ClusterFissioner::drawNewMassesDefault(const Energy Mc, bool soft1, bool soft2,
Lorentz5Momentum& pClu1, Lorentz5Momentum& pClu2,
tcPPtr ptrQ1, const Lorentz5Momentum& pQ1,
const Lorentz5Momentum& pQone,
const Lorentz5Momentum& pQtwo,
tcPPtr ptrQ2, const Lorentz5Momentum& pQ2) const {
// power for splitting
double exp1 = !spectrum()->isExotic(ptrQ1->dataPtr()) ? _pSplitLight : _pSplitExotic;
double exp2 = !spectrum()->isExotic(ptrQ2->dataPtr()) ? _pSplitLight : _pSplitExotic;
for ( const long& id : spectrum()->heavyHadronizingQuarks() ) {
assert(_pSplitHeavy.find(id) != _pSplitHeavy.end());
if ( spectrum()->hasHeavy(id,ptrQ1->dataPtr()) ) exp1 = _pSplitHeavy.find(id)->second;
if ( spectrum()->hasHeavy(id,ptrQ2->dataPtr()) ) exp2 = _pSplitHeavy.find(id)->second;
}
Energy M1 = drawChildMass(Mc,pQ1.mass(),pQ2.mass(),pQone.mass(),exp1,soft1);
Energy M2 = drawChildMass(Mc,pQ2.mass(),pQ1.mass(),pQtwo.mass(),exp2,soft2);
pClu1.setMass(M1);
pClu2.setMass(M2);
return false; // succeeds
}
/**
* Sample the masses for flat phase space
* */
bool ClusterFissioner::drawNewMassesUniform(const Energy Mc,
Lorentz5Momentum& pClu1, Lorentz5Momentum& pClu2,
const Lorentz5Momentum& pQ1,
const Lorentz5Momentum& pQone,
const Lorentz5Momentum& pQtwo,
const Lorentz5Momentum& pQ2) const {
Energy M1,M2;
const Energy m1 = pQ1.mass();
const Energy m2 = pQ2.mass();
const Energy m = pQone.mass();
const Energy M1min = m1 + m;
const Energy M2min = m2 + m;
const Energy M1max = Mc - M2min;
const Energy M2max = Mc - M1min;
assert(M1max-M1min>ZERO);
assert(M2max-M2min>ZERO);
double r1;
double r2;
int counter = 0;
const int max_counter = 100;
while (counter < max_counter) {
r1 = UseRandom::rnd();
r2 = UseRandom::rnd();
M1 = (M1max-M1min)*r1 + M1min;
M2 = (M2max-M2min)*r2 + M2min;
counter++;
if ( Mc > M1 + M2) break;
}
if (counter==max_counter
|| Mc < M1 + M2
|| M1 <= M1min
|| M2 <= M2min ) return true; // failure
pClu1.setMass(M1);
pClu2.setMass(M2);
return false; // succeeds
}
/**
* Sample the masses for flat phase space
* */
bool ClusterFissioner::drawNewMassesPhaseSpace(const Energy Mc,
Lorentz5Momentum& pClu1, Lorentz5Momentum& pClu2,
const Lorentz5Momentum& pQ1,
const Lorentz5Momentum& pQone,
const Lorentz5Momentum& pQtwo,
const Lorentz5Momentum& pQ2) const {
/*
Energy M1,M2;
const Energy m1 = pQ1.mass();
const Energy m2 = pQ2.mass();
const Energy m = pQone.mass();
const Energy M1min = m1 + m;
const Energy M2min = m2 + m;
const Energy M1max = Mc - M2min;
const Energy M2max = Mc - M1min;
assert(M1max-M1min>ZERO);
assert(M2max-M2min>ZERO);
double r1;
double r2;
int counter = 0;
const int max_counter = 100;
while (counter < max_counter) {
r1 = UseRandom::rnd();
r2 = UseRandom::rnd();
M1 = (M1max-M1min)*r1 + M1min;
M2 = (M2max-M2min)*r2 + M2min;
counter++;
if ( Mc <= M1 + M2) continue;
// if ( M1 <= M1min ) continue;
// if ( M2 <= M2min ) continue;
if (!phaseSpaceVeto(Mc,M1,M2,m,m1,m2) ) break; // For FlatPhaseSpace sampling vetoing
}
if (counter==max_counter) return true; // failure
pClu1.setMass(M1);
pClu2.setMass(M2);
return false; // succeeds
*/
Energy M1,M2,MuS;
const Energy m1 = pQ1.mass();
const Energy m2 = pQ2.mass();
const Energy m = pQone.mass();
const Energy M1min = m1 + m;
const Energy M2min = m2 + m;
// const Energy M1max = Mc - M2min;
// const Energy M2max = Mc - M1min;
// assert(M1max-M1min>ZERO);
// assert(M2max-M2min>ZERO);
double r1;
double r2;
int counter = 0;
const int max_counter = 200;
// const Energy MuMminS = M1min + M2min;
// const Energy MuMminD = M1min - M2min;
const Energy MuMax = Mc - (M1min+M2min);
while (counter < max_counter) {
r1 = UseRandom::rnd();
r2 = UseRandom::rnd();
// TODO make this more efficient
// M1 = (M1max-M1min)*r1 + M1min;
// M2 = (M2max-M2min)*r2 + M2min;
MuS = MuMax * sqrt(r1);
// MD = 2*MS*r2 - MS + MuMminD;
M1 = M1min + MuS * r2;
M2 = M2min + MuS * (1.0 - r2);
counter++;
// Automatically satisfied
// if ( Mc <= M1 + M2) std::cout << "Mc "<< Mc/GeV << " M1 "<< M1/GeV <<" M2 " <<M2/GeV << std::endl;;
// if ( M1 <= M1min ) std::cout << "M1 "<< M1/GeV <<" M1min " <<M1min/GeV << std::endl;;
// if ( M2 <= M2min ) std::cout << "M2 "<< M2/GeV <<" M2min " <<M2min/GeV << std::endl;;
// assert( Mc > M1 + M2) ;
// assert( M1 > M1min ) ;
// assert( M2 > M2min ) ;
// if ( Mc <= M1 + M2) continue;
// if ( M1 <= M1min ) continue;
// if ( M2 <= M2min ) continue;
if (!phaseSpaceVeto(Mc,M1,M2,m,m1,m2) ) break; // For FlatPhaseSpace sampling vetoing
}
if (counter==max_counter) return true; // failure
pClu1.setMass(M1);
pClu2.setMass(M2);
return false; // succeeds
}
/**
* Sample the masses for flat phase space with modulation e.g. here
* FlatPhaseSpace*(M1*M2)**alpha
* */
bool ClusterFissioner::drawNewMassesPhaseSpaceExtended(const Energy Mc,
Lorentz5Momentum& pClu1, Lorentz5Momentum& pClu2,
const Lorentz5Momentum& pQ1,
const Lorentz5Momentum& pQone,
const Lorentz5Momentum& pQtwo,
const Lorentz5Momentum& pQ2) const {
Energy M1,M2;
const Energy m1 = pQ1.mass();
const Energy m2 = pQ2.mass();
const Energy m = pQone.mass();
const Energy M1min = m1 + m;
const Energy M2min = m2 + m;
const Energy M1max = Mc - M2min;
const Energy M2max = Mc - M1min;
assert(M1max-M1min>ZERO);
assert(M2max-M2min>ZERO);
double r1;
double r2;
int counter = 0;
const int max_counter = 200;
- const int alpha = 8;
+ const int alpha = 2;
while (counter < max_counter) {
r1 = UseRandom::rnd();
r2 = UseRandom::rnd();
// TODO make this more efficient
// Uniform sampling
// M1 = (M1max-M1min)*r1 + M1min;
// M2 = (M2max-M2min)*r2 + M2min;
// Power sampling giving (M1*M2)**alpha
M1 = M1min * pow( 1.0 - r1 + r1 * pow(M1max/M1min,alpha+1.0) , 1.0/(alpha+1.0));
M2 = M2min * pow( 1.0 - r2 + r2 * pow(M2max/M2min,alpha+1.0) , 1.0/(alpha+1.0));
counter++;
if ( Mc <= M1 + M2) continue;
if ( M1 <= M1min ) continue;
if ( M2 <= M2min ) continue;
if (!phaseSpaceVeto(Mc,M1,M2,m,m1,m2) ) break; // For FlatPhaseSpace sampling vetoing
}
if (counter==max_counter
|| Mc < M1 + M2
|| M1 <= M1min
|| M2 <= M2min ) return true; // failure
pClu1.setMass(M1);
pClu2.setMass(M2);
return false; // succeeds
}
void ClusterFissioner::drawNewFlavourDiquarks(PPtr& newPtrPos,PPtr& newPtrNeg,
const ClusterPtr & clu) const {
// Flavour is assumed to be only u, d, s, with weights
// (which are not normalized probabilities) given
// by the same weights as used in HadronsSelector for
// the decay of clusters into two hadrons.
unsigned hasDiquarks=0;
assert(clu->numComponents()==2);
tcPDPtr pD1=clu->particle(0)->dataPtr();
tcPDPtr pD2=clu->particle(1)->dataPtr();
bool isDiq1=DiquarkMatcher::Check(pD1->id());
if (isDiq1) hasDiquarks++;
bool isDiq2=DiquarkMatcher::Check(pD2->id());
if (isDiq2) hasDiquarks++;
assert(hasDiquarks<=2);
Energy Mc=(clu->momentum().mass());
// if (fabs(clu->momentum().massError() )>1e-14) std::cout << "Mass inconsistency CF : " << std::scientific << clu->momentum().massError() <<"\n";
// Not allow yet Diquark Clusters
// if ( hasDiquarks>=1 || Mc < spectrum()->massLightestBaryonPair(pD1,pD2) )
// return drawNewFlavour(newPtrPos,newPtrNeg);
Energy minMass;
Selector<long> choice;
// int countQ=0;
// int countDiQ=0;
// adding quark-antiquark pairs to the selection list
for ( const long& id : spectrum()->lightHadronizingQuarks() ) {
// TODO uncommenting below gives sometimes 0 selection possibility,
// maybe need to be checked in the LightClusterDecayer and ColourReconnector
// if (Mc < spectrum()->massLightestHadronPair(pD1,pD2)) continue;
// countQ++;
if (_fissionCluster==0) choice.insert(_hadronSpectrum->pwtQuark(id),id);
else if (_fissionCluster==1) choice.insert(_fissionPwt.find(id)->second,id);
else assert(false);
}
// adding diquark-antidiquark pairs to the selection list
switch (hasDiquarks)
{
case 0:
for ( const long& id : spectrum()->lightHadronizingDiquarks() ) {
if (_strictDiquarkKinematics) {
tPDPtr cand = getParticleData(id);
minMass = spectrum()->massLightestHadron(pD2,cand)
+ spectrum()->massLightestHadron(cand,pD1);
}
else minMass = spectrum()->massLightestBaryonPair(pD1,pD2);
if (Mc < minMass) continue;
// countDiQ++;
if (_fissionCluster==0) choice.insert(_hadronSpectrum->pwtQuark(id),id);
else if (_fissionCluster==1) choice.insert(_fissionPwt.find(id)->second,id);
else assert(false);
}
break;
case 1:
if (_diquarkClusterFission<1) break;
for ( const long& id : spectrum()->lightHadronizingDiquarks() ) {
tPDPtr diq = getParticleData(id);
if (isDiq1)
minMass = spectrum()->massLightestHadron(pD2,diq)
+ spectrum()->massLightestBaryonPair(diq,pD1);
else
minMass = spectrum()->massLightestHadron(pD1,diq)
+ spectrum()->massLightestBaryonPair(diq,pD2);
if (Mc < minMass) continue;
// countDiQ++;
if (_fissionCluster==0) choice.insert(_hadronSpectrum->pwtQuark(id),id);
else if (_fissionCluster==1) choice.insert(_fissionPwt.find(id)->second,id);
else assert(false);
}
break;
case 2:
if (_diquarkClusterFission<2) break;
for ( const long& id : spectrum()->lightHadronizingDiquarks() ) {
tPDPtr diq = getParticleData(id);
if (Mc < spectrum()->massLightestBaryonPair(pD1,pD2)) {
throw Exception() << "Found Diquark Cluster:\n" << *clu << "\nwith MassCluster = "
<< ounit(Mc,GeV) <<" GeV MassLightestBaryonPair = "
<< ounit(spectrum()->massLightestBaryonPair(pD1,pD2) ,GeV)
<< " GeV cannot decay" << Exception::eventerror;
}
minMass = spectrum()->massLightestBaryonPair(pD1,diq)
+ spectrum()->massLightestBaryonPair(diq,pD2);
if (Mc < minMass) continue;
// countDiQ++;
if (_fissionCluster==0) choice.insert(_hadronSpectrum->pwtQuark(id),id);
else if (_fissionCluster==1) choice.insert(_fissionPwt.find(id)->second,id);
else assert(false);
}
break;
default:
assert(false);
}
assert(choice.size()>0);
long idNew = choice.select(UseRandom::rnd());
newPtrPos = getParticle(idNew);
newPtrNeg = getParticle(-idNew);
assert(newPtrPos);
assert(newPtrNeg);
assert(newPtrPos->dataPtr());
assert(newPtrNeg->dataPtr());
}
void ClusterFissioner::drawNewFlavourQuarks(PPtr& newPtrPos,PPtr& newPtrNeg) const {
// Flavour is assumed to be only u, d, s, with weights
// (which are not normalized probabilities) given
// by the same weights as used in HadronsSelector for
// the decay of clusters into two hadrons.
Selector<long> choice;
switch(_fissionCluster){
case 0:
for ( const long& id : spectrum()->lightHadronizingQuarks() )
choice.insert(_hadronSpectrum->pwtQuark(id),id);
break;
case 1:
for ( const long& id : spectrum()->lightHadronizingQuarks() )
choice.insert(_fissionPwt.find(id)->second,id);
break;
default :
assert(false);
}
long idNew = choice.select(UseRandom::rnd());
newPtrPos = getParticle(idNew);
newPtrNeg = getParticle(-idNew);
assert (newPtrPos);
assert(newPtrNeg);
assert (newPtrPos->dataPtr());
assert(newPtrNeg->dataPtr());
}
void ClusterFissioner::drawNewFlavourEnhanced(PPtr& newPtrPos,PPtr& newPtrNeg,
Energy2 mass2) const {
if ( spectrum()->gluonId() != ParticleID::g )
throw Exception() << "strange enhancement only working with Standard Model hadronization"
<< Exception::runerror;
// Flavour is assumed to be only u, d, s, with weights
// (which are not normalized probabilities) given
// by the same weights as used in HadronsSelector for
// the decay of clusters into two hadrons.
double prob_d = 0.;
double prob_u = 0.;
double prob_s = 0.;
double scale = abs(double(sqr(_m0Fission)/mass2));
// Choose which splitting weights you wish to use
switch(_fissionCluster){
// 0: ClusterFissioner and ClusterDecayer use the same weights
case 0:
prob_d = _hadronSpectrum->pwtQuark(ParticleID::d);
prob_u = _hadronSpectrum->pwtQuark(ParticleID::u);
/* Strangeness enhancement:
Case 1: probability scaling
Case 2: Exponential scaling
*/
if (_enhanceSProb == 1)
prob_s = (_maxScale < scale) ? 0. : pow(_hadronSpectrum->pwtQuark(ParticleID::s),scale);
else if (_enhanceSProb == 2)
prob_s = (_maxScale < scale) ? 0. : exp(-scale);
break;
/* 1: ClusterFissioner uses its own unique set of weights,
i.e. decoupled from ClusterDecayer */
case 1:
prob_d = _fissionPwt.find(ParticleID::d)->second;
prob_u = _fissionPwt.find(ParticleID::u)->second;
if (_enhanceSProb == 1)
prob_s = (_maxScale < scale) ? 0. : pow(_fissionPwt.find(ParticleID::s)->second,scale);
else if (_enhanceSProb == 2)
prob_s = (_maxScale < scale) ? 0. : exp(-scale);
break;
default:
assert(false);
}
int choice = UseRandom::rnd3(prob_u, prob_d, prob_s);
long idNew = 0;
switch (choice) {
case 0: idNew = ThePEG::ParticleID::u; break;
case 1: idNew = ThePEG::ParticleID::d; break;
case 2: idNew = ThePEG::ParticleID::s; break;
}
newPtrPos = getParticle(idNew);
newPtrNeg = getParticle(-idNew);
assert (newPtrPos);
assert(newPtrNeg);
assert (newPtrPos->dataPtr());
assert(newPtrNeg->dataPtr());
}
Energy2 ClusterFissioner::clustermass(const ClusterPtr & cluster) const {
Lorentz5Momentum pIn = cluster->momentum();
Energy2 endpointmass2 = sqr(cluster->particle(0)->mass() +
cluster->particle(1)->mass());
Energy2 singletm2 = pIn.m2();
// Return either the cluster mass, or the lambda measure
return (_massMeasure == 0) ? singletm2 : singletm2 - endpointmass2;
}
Energy ClusterFissioner::drawChildMass(const Energy M, const Energy m1,
const Energy m2, const Energy m,
const double expt, const bool soft) const {
/***************************
* This method, given in input the cluster mass Mclu of an heavy cluster C,
* made of consituents of masses m1 and m2, draws the masses Mclu1 and Mclu2
* of, respectively, the children cluster C1, made of constituent masses m1
* and m, and cluster C2, of mass Mclu2 and made of constituent masses m2
* and m. The mass is extracted from one of the two following mass
* distributions:
* --- power-like ("normal" distribution)
* d(Prob) / d(M^exponent) = const
* where the exponent can be different from the two children C1 (exp1)
* and C2 (exponent2).
* --- exponential ("soft" distribution)
* d(Prob) / d(M^2) = exp(-b*M)
* where b = 2.0 / average.
* Such distributions are limited below by the masses of
* the constituents quarks, and above from the mass of decaying cluster C.
* The choice of which of the two mass distributions to use for each of the
* two cluster children is dictated by iRemnant (see below).
* If the number of attempts to extract a pair of mass values that are
* kinematically acceptable is above some fixed number (max_loop, see below)
* the method gives up and returns false; otherwise, when it succeeds, it
* returns true.
*
* These distributions have been modified from HERWIG:
* Before these were:
* Mclu1 = m1 + (Mclu - m1 - m2)*pow( rnd(), 1.0/exponent1 );
* The new one coded here is a more efficient version, same density
* but taking into account 'in phase space from' beforehand
***************************/
// hard cluster
if(!soft) {
return pow(UseRandom::rnd(pow((M-m1-m2-m)*UnitRemoval::InvE, expt),
pow(m*UnitRemoval::InvE, expt)), 1./expt
)*UnitRemoval::E + m1;
}
// Otherwise it uses a soft mass distribution
else {
static const InvEnergy b = 2.0 / _btClM;
Energy max = M-m1-m2-2.0*m;
double rmin = b*max;
rmin = ( rmin < 50 ) ? exp(-rmin) : 0.;
double r1;
do {
r1 = UseRandom::rnd(rmin, 1.0) * UseRandom::rnd(rmin, 1.0);
}
while (r1 < rmin);
return m1 + m - log(r1)/b;
}
}
Axis ClusterFissioner::sampleDirectionAligned(const Lorentz5Momentum & pRelCOM) const {
return pRelCOM.vect().unit();
}
Axis ClusterFissioner::sampleDirectionIsotropic() const {
double cosTheta = -1 + 2.0 * UseRandom::rnd();
double sinTheta = sqrt(1.0-cosTheta*cosTheta);
double Phi = 2.0 * Constants::pi * UseRandom::rnd();
Axis uClusterUniform(cos(Phi)*cosTheta, sin(Phi)*cosTheta, sinTheta);
return uClusterUniform.unit();
}
Axis ClusterFissioner::sampleDirectionSemiUniform(const Lorentz5Momentum & pRelCOM) const {
Axis dir=pRelCOM.vect().unit();
Axis res;
do {
res=sampleDirectionIsotropic();
}
while (dir*res<0);
return res;
}
Energy ClusterFissioner::sample2DGaussianPT(const Energy & Pcom) const {
Energy sigmaPT=_fluxTubeWidth*Pcom;
if (_fluxTubeWidth==0) return ZERO;
Energy magnitude;
Energy pTx,pTy;
const int maxcount=100;
int count=0;
do {
if (count>=maxcount) {
if (Pcom>0.5*sigmaPT) throw Exception() << "Could not sample direction in ClusterFissioner::sampleDirectionGaussianPT() "
<< Exception::eventerror;
// Fallback uniform sampling
magnitude=UseRandom::rnd()*Pcom;
break;
}
pTx = UseRandom::rndGauss(sigmaPT);
pTy = UseRandom::rndGauss(sigmaPT);
magnitude=sqrt(sqr(pTx)+sqr(pTy));
count++;
}
while (magnitude>Pcom);
return magnitude;
}
Axis ClusterFissioner::sampleDirectionGaussianPT(const Lorentz5Momentum & pRelCOM,
const Energy Mass, const Energy mass1, const Energy mass2) const {
Energy pstarCOM = Kinematics::pstarTwoBodyDecay(Mass,mass1,mass2);
// sample gaussian pT kick with sigma=pstarCOM*_fluxTubeWidth
// to avoid jacobian factor for ClusterFission matrix element
Energy pT=sample2DGaussianPT(pstarCOM);
Energy2 pT2=pT*pT;
double phi=UseRandom::rnd()*2.0*Constants::pi;
Energy pTx=pT*cos(phi);
Energy pTy=pT*sin(phi);
Axis pRelativeDir=pRelCOM.vect().unit();
Axis TrvX = pRelativeDir.orthogonal().unit();
Axis TrvY = TrvX.cross(pRelativeDir).unit();
Axis pTkick = pTx/pstarCOM*TrvX + pTy/pstarCOM*TrvY;
Axis uClusterFluxTube = (pRelativeDir*sqrt(1.0-pT2/sqr(pstarCOM)) + pTkick);
return uClusterFluxTube.unit();
}
static Energy2 Min(const Lorentz5Momentum p1, const Lorentz5Momentum p2);
static Energy2 Min(const Lorentz5Momentum p1, const Lorentz5Momentum p2){
Energy2 min=p1.e()*p2.e()-p1.vect().mag()*p2.vect().mag();
assert(min>=ZERO);
return min;
}
static Energy2 Max(const Lorentz5Momentum p1, const Lorentz5Momentum p2);
static Energy2 Max(const Lorentz5Momentum p1, const Lorentz5Momentum p2){
Energy2 max=p1.e()*p2.e()+p1.vect().mag()*p2.vect().mag();
assert(max>=ZERO);
return max;
}
bool ClusterFissioner::drawKinematics(const Lorentz5Momentum & pClu,
const Lorentz5Momentum & p0Q1,
const Lorentz5Momentum & p0Q2,
const bool toHadron1,
const bool toHadron2,
Lorentz5Momentum & pClu1,
Lorentz5Momentum & pClu2,
Lorentz5Momentum & pQ1,
Lorentz5Momentum & pQbar,
Lorentz5Momentum & pQ,
Lorentz5Momentum & pQ2bar) const {
/******************
* This method solves the kinematics of the two body cluster decay:
* C (Q1 Q2bar) ---> C1 (Q1 Qbar) + C2 (Q Q2bar)
* In input we receive the momentum of C, pClu, and the momentum
* of the quark Q1 (constituent of C), p0Q1, both in the LAB frame.
* Furthermore, two boolean variables inform whether the two fission
* products (C1, C2) decay immediately into a single hadron (in which
* case the cluster itself is identify with that hadron) and we do
* not have to solve the kinematics of the components (Q1,Qbar) for
* C1 and (Q,Q2bar) for C2.
* The output is given by the following momenta (all 5-components,
* and all in the LAB frame):
* pClu1 , pClu2 respectively of C1 , C2
* pQ1 , pQbar respectively of Q1 , Qbar in C1
* pQ , pQ2bar respectively of Q , Q2 in C2
* The assumption, suggested from the string model, is that, in C frame,
* C1 and its constituents Q1 and Qbar are collinear, and collinear to
* the direction of Q1 in C (that is before cluster decay); similarly,
* (always in the C frame) C2 and its constituents Q and Q2bar are
* collinear (and therefore anti-collinear with C1,Q1,Qbar).
* The solution is then obtained by using Lorentz boosts, as follows.
* The kinematics of C1 and C2 is solved in their parent C frame,
* and then boosted back in the LAB. The kinematics of Q1 and Qbar
* is solved in their parent C1 frame and then boosted back in the LAB;
* similarly, the kinematics of Q and Q2bar is solved in their parent
* C2 frame and then boosted back in the LAB. In each of the three
* "two-body decay"-like cases, we use the fact that the direction
* of the motion of the decay products is known in the rest frame of
* their parent. This is obvious for the first case in which the
* parent rest frame is C; but it is also true in the other two cases
* where the rest frames are C1 and C2. This is because C1 and C2
* are boosted w.r.t. C in the same direction where their components,
* respectively (Q1,Qbar) and (Q,Q2bar) move in C1 and C2 rest frame
* respectively.
* Of course, although the notation used assumed that C = (Q1 Q2bar)
* where Q1 is a quark and Q2bar an antiquark, indeed everything remain
* unchanged also in all following cases:
* Q1 quark, Q2bar antiquark; --> Q quark;
* Q1 antiquark , Q2bar quark; --> Q antiquark;
* Q1 quark, Q2bar diquark; --> Q quark
* Q1 antiquark, Q2bar anti-diquark; --> Q antiquark
* Q1 diquark, Q2bar quark --> Q antiquark
* Q1 anti-diquark, Q2bar antiquark; --> Q quark
**************************/
if (pClu.mass() < pClu1.mass() + pClu2.mass()
|| pClu1.mass()<ZERO
|| pClu2.mass()<ZERO ) {
throw Exception() << "Impossible Kinematics in ClusterFissioner::drawKinematics() (A)"
<< Exception::eventerror;
}
switch (_phaseSpaceSampler)
{
case 0: // FullyAligned [default]
{
// Calculate the unit three-vector, in the Cluster frame,
// using the sampling funtion sampleDirection in the respective
// clusters rest frame
// Calculate the momenta of C1 and C2 in the (parent) C frame first,
// where the direction of C1 is samples according to sampleDirection,
// and then boost back in the LAB frame.
//
// relative momentum in centre of mass of cluster
Lorentz5Momentum p0Q1inCOM(p0Q1);
p0Q1inCOM.setMass(pQ1.mass());
p0Q1inCOM.boost( -pClu.boostVector() ); // boost from LAB to C
Axis uRelCluster = p0Q1inCOM.vect().unit();
Kinematics::twoBodyDecay(pClu, pClu1.mass(), pClu2.mass(),uRelCluster, pClu1, pClu2);
// In the case that cluster1 does not decay immediately into a single hadron,
// calculate the momenta of Q1 (as constituent of C1) and Qbar in the
// (parent) C1 frame first, where the direction of Q1 is u.vect().unit(),
// and then boost back in the LAB frame.
if(!toHadron1) {
if (pClu1.m() < pQ1.mass() + pQbar.mass() ) {
throw Exception() << "Impossible Kinematics in ClusterFissioner::drawKinematics[FluxTube]() (B)"
<< Exception::eventerror;
}
// sample direction (options = Default(aligned), Isotropic
// or FluxTube(gaussian pT kick))
// Axis DirClu1 = sampleDirection(uRelCluster,
// pClu1.mass(), pQ1.mass(), pQbar.mass());
Kinematics::twoBodyDecay(pClu1, pQ1.mass(), pQbar.mass(),
uRelCluster, pQ1, pQbar);
}
// In the case that cluster2 does not decay immediately into a single hadron,
// Calculate the momenta of Q and Q2bar (as constituent of C2) in the
// (parent) C2 frame first, where the direction of Q is u.vect().unit(),
// and then boost back in the LAB frame.
if(!toHadron2) {
if (pClu2.m() < pQ.mass() + pQ2bar.mass() ) {
throw Exception() << "Impossible Kinematics in ClusterFissioner::drawKinematics[FluxTube]() (C)"
<< Exception::eventerror;
}
// sample direction (options = Default(aligned), Isotropic
// or FluxTube(gaussian pT kick))
// Axis DirClu2 = sampleDirection(uRelCluster,
// pClu2.mass(), pQ2bar.mass(), pQ.mass());
Kinematics::twoBodyDecay(pClu2, pQ.mass(), pQ2bar.mass(),
uRelCluster, pQ, pQ2bar);
}
return false; // success
break;
}
case 1: // AlignedIsotropic [alignes the clusters, but draws the constituents isotropically in their rest frame]
{
return false; // success
break;
}
case 2: // FullyIsotropic [clusters and its constituents are drawn completely isotropically]
{
/* NOTE: Only for testing!! No physical meaning
* */
// sample direction (options = Default(aligned), Isotropic
// or FluxTube(gaussian pT kick))
Axis DirClu = sampleDirectionIsotropic();
Axis DirClu1 = sampleDirectionIsotropic();
Axis DirClu2 = sampleDirectionIsotropic();
if (_covariantBoost) {
// const Lorentz5Momentum p0Q1tmp(pQ1.mass(), p0Q1.vect());
// const Lorentz5Momentum p0Q2tmp(pQ2bar.mass(),p0Q2.vect());
const Energy M = pClu.mass();
const Energy M1 = pClu1.mass();
const Energy M2 = pClu2.mass();
const Energy PcomClu=Kinematics::pstarTwoBodyDecay(M,M1,M2);
// Axis DirToClu = sampleDirectionAligned(p0Q1tmp.boost( -pClu.boostVector() ));
Axis DirToClu = DirClu;
Momentum3 pClu1sampVect( PcomClu*DirToClu);
Momentum3 pClu2sampVect(-PcomClu*DirToClu);
pClu1.setMass(M1);
pClu1.setVect(pClu1sampVect);
pClu1.rescaleEnergy();
pClu2.setMass(M2);
pClu2.setVect(pClu2sampVect);
pClu2.rescaleEnergy();
// if (pClu1.m() < pQ1.mass() + pQbar.mass() ) {
// throw Exception() << "Impossible Kinematics in ClusterFissioner::drawKinematics() (B)"
// << Exception::eventerror;
// }
// sample direction (options = Default(aligned), Isotropic
// or FluxTube(gaussian pT kick))
// DirClu1=sampleDirectionIsotropic();
// DirClu1=pClu1.vect().unit();
// if (pClu2.m() < pQ.mass() + pQ2bar.mass() ) {
// throw Exception() << "Impossible Kinematics in ClusterFissioner::drawKinematics[FluxTube]() (C)"
// << Exception::eventerror;
// }
// sample direction (options = Default(aligned), Isotropic
// or FluxTube(gaussian pT kick))
// DirClu2=sampleDirectionIsotropic();
// DirClu2=pClu2.vect().unit();
// Need to boost constituents first into the pClu rest frame
// boost from Cluster1 rest frame to Cluster COM Frame
Kinematics::twoBodyDecay(pClu1, pQ1.mass(), pQbar.mass(), DirClu1, pQ1, pQbar);
// boost from Cluster2 rest frame to Cluster COM Frame
Kinematics::twoBodyDecay(pClu2, pQ.mass(), pQ2bar.mass(), DirClu2, pQ, pQ2bar);
Energy PstarQ12=Kinematics::pstarTwoBodyDecay(M,pQ1.mass(),pQ2bar.mass());
Axis z(0,0,1);
Lorentz5Momentum pQ1Hat(pQ1.mass(),PstarQ12*z);
Lorentz5Momentum pQ2Hat(pQ2bar.mass(),-PstarQ12*z);
// Boost all momenta from the Cluster COM frame to the Lab frame
Kinematics::BoostIntoTwoParticleFrame(pClu.mass(),p0Q1, p0Q2, pClu1, pClu2);
Kinematics::BoostIntoTwoParticleFrame(pClu.mass(),p0Q1, p0Q2, pQ1, pQbar);
Kinematics::BoostIntoTwoParticleFrame(pClu.mass(),p0Q1, p0Q2, pQ, pQ2bar);
}
else {
Kinematics::twoBodyDecay(pClu, pClu1.mass(), pClu2.mass(),DirClu, pClu1, pClu2);
DirClu1=DirClu;
DirClu2=DirClu;
// In the case that cluster1 does not decay immediately into a single hadron,
// calculate the momenta of Q1 (as constituent of C1) and Qbar in the
// (parent) C1 frame first, where the direction of Q1 is u.vect().unit(),
// and then boost back in the LAB frame.
if(!toHadron1) {
if (pClu1.m() < pQ1.mass() + pQbar.mass() ) {
throw Exception() << "Impossible Kinematics in ClusterFissioner::drawKinematics[FluxTube]() (B)"
<< Exception::eventerror;
}
// sample direction (options = Default(aligned), Isotropic
// or FluxTube(gaussian pT kick))
// Axis DirClu1 = sampleDirection(uRelCluster,
// pClu1.mass(), pQ1.mass(), pQbar.mass());
Kinematics::twoBodyDecay(pClu1, pQ1.mass(), pQbar.mass(),
DirClu1, pQ1, pQbar);
}
// In the case that cluster2 does not decay immediately into a single hadron,
// Calculate the momenta of Q and Q2bar (as constituent of C2) in the
// (parent) C2 frame first, where the direction of Q is u.vect().unit(),
// and then boost back in the LAB frame.
if(!toHadron2) {
if (pClu2.m() < pQ.mass() + pQ2bar.mass() ) {
throw Exception() << "Impossible Kinematics in ClusterFissioner::drawKinematics[FluxTube]() (C)"
<< Exception::eventerror;
}
// sample direction (options = Default(aligned), Isotropic
// or FluxTube(gaussian pT kick))
// Axis DirClu2 = sampleDirection(uRelCluster,
// pClu2.mass(), pQ2bar.mass(), pQ.mass());
Kinematics::twoBodyDecay(pClu2, pQ.mass(), pQ2bar.mass(),
DirClu2, pQ, pQ2bar);
}
}
return false; // success
break;
}
default:
assert(false);
}
return true; // failure
}
void ClusterFissioner::calculatePositions(const Lorentz5Momentum & pClu,
const LorentzPoint & positionClu,
const Lorentz5Momentum & pClu1,
const Lorentz5Momentum & pClu2,
LorentzPoint & positionClu1,
LorentzPoint & positionClu2) const {
// Determine positions of cluster children.
// See Marc Smith's thesis, page 127, formulas (4.122) and (4.123).
Energy Mclu = pClu.m();
Energy Mclu1 = pClu1.m();
Energy Mclu2 = pClu2.m();
// Calculate the unit three-vector, in the C frame, along which
// children clusters move.
Lorentz5Momentum u(pClu1);
u.boost( -pClu.boostVector() ); // boost from LAB to C frame
// the unit three-vector is then u.vect().unit()
Energy pstarChild = Kinematics::pstarTwoBodyDecay(Mclu,Mclu1,Mclu2);
// First, determine the relative positions of the children clusters
// in the parent cluster reference frame.
Energy2 mag2 = u.vect().mag2();
InvEnergy fact = mag2>ZERO ? 1./sqrt(mag2) : 1./GeV;
Length x1 = ( 0.25*Mclu + 0.5*( pstarChild + (sqr(Mclu2) - sqr(Mclu1))/(2.0*Mclu)))/_kappa;
Length t1 = Mclu/_kappa - x1;
LorentzDistance distanceClu1( x1 * fact * u.vect(), t1 );
Length x2 = (-0.25*Mclu + 0.5*(-pstarChild + (sqr(Mclu2) - sqr(Mclu1))/(2.0*Mclu)))/_kappa;
Length t2 = Mclu/_kappa + x2;
LorentzDistance distanceClu2( x2 * fact * u.vect(), t2 );
// Then, transform such relative positions from the parent cluster
// reference frame to the Lab frame.
distanceClu1.boost( pClu.boostVector() );
distanceClu2.boost( pClu.boostVector() );
// Finally, determine the absolute positions in the Lab frame.
positionClu1 = positionClu + distanceClu1;
positionClu2 = positionClu + distanceClu2;
}
bool ClusterFissioner::ProbablityFunction(double scale, double threshold) {
double cut = UseRandom::rnd(0.0,1.0);
return 1./(1.+pow(abs((threshold-_probShift)/scale),_probPowFactor)) > cut ? true : false;
}
bool ClusterFissioner::isHeavy(tcClusterPtr clu) {
// particle data for constituents
tcPDPtr cptr[3]={tcPDPtr(),tcPDPtr(),tcPDPtr()};
for(size_t ix=0;ix<min(clu->numComponents(),3);++ix) {
cptr[ix]=clu->particle(ix)->dataPtr();
}
// different parameters for exotic, bottom and charm clusters
double clpow = !spectrum()->isExotic(cptr[0],cptr[1],cptr[1]) ? _clPowLight : _clPowExotic;
Energy clmax = !spectrum()->isExotic(cptr[0],cptr[1],cptr[1]) ? _clMaxLight : _clMaxExotic;
for ( const long& id : spectrum()->heavyHadronizingQuarks() ) {
if ( spectrum()->hasHeavy(id,cptr[0],cptr[1],cptr[1]) ) {
clpow = _clPowHeavy[id];
clmax = _clMaxHeavy[id];
}
}
// required test for SUSY clusters, since aboveCutoff alone
// cannot guarantee (Mc > m1 + m2 + 2*m) in cut()
static const Energy minmass
= getParticleData(ParticleID::d)->constituentMass();
bool aboveCutoff = false, canSplitMinimally = false;
// static kinematic threshold
if(_kinematicThresholdChoice == 0) {
aboveCutoff = (
pow(clu->mass()*UnitRemoval::InvE , clpow)
>
pow(clmax*UnitRemoval::InvE, clpow)
+ pow(clu->sumConstituentMasses()*UnitRemoval::InvE, clpow)
);
canSplitMinimally = clu->mass() > clu->sumConstituentMasses() + 2.0 * minmass;
}
// dynamic kinematic threshold
else if(_kinematicThresholdChoice == 1) {
//some smooth probablity function to create a dynamic thershold
double scale = pow(clu->mass()/GeV , clpow);
double threshold = pow(clmax/GeV, clpow)
+ pow(clu->sumConstituentMasses()/GeV, clpow);
aboveCutoff = ProbablityFunction(scale,threshold);
scale = clu->mass()/GeV;
threshold = clu->sumConstituentMasses()/GeV + 2.0 * minmass/GeV;
canSplitMinimally = ProbablityFunction(scale,threshold);
}
return aboveCutoff && canSplitMinimally;
}
diff --git a/Utilities/Kinematics.cc b/Utilities/Kinematics.cc
--- a/Utilities/Kinematics.cc
+++ b/Utilities/Kinematics.cc
@@ -1,348 +1,342 @@
// -*- C++ -*-
//
// Kinematics.cc is a part of Herwig - A multi-purpose Monte Carlo event generator
// Copyright (C) 2002-2019 The Herwig Collaboration
//
// Herwig is licenced under version 3 of the GPL, see COPYING for details.
// Please respect the MCnet academic guidelines, see GUIDELINES for details.
//
//
// This is the implementation of the non-inlined, non-templated member
// functions of the Kinematics class.
//
#include "Kinematics.h"
#include <ThePEG/Vectors/Lorentz5Vector.h>
#include <ThePEG/Vectors/LorentzVector.h>
#include <ThePEG/Vectors/LorentzRotation.h>
#include <ThePEG/Repository/EventGenerator.h>
#include <ThePEG/Repository/CurrentGenerator.h>
#include <ThePEG/EventRecord/Event.h>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/numeric/ublas/lu.hpp>
using namespace Herwig;
using namespace std;
using namespace ThePEG;
/**
* Boost consistently the Lorentz5Momenta pClu1 and pClu2 (given in their COM frame)
* into the p0Q1 p0Q2 frame.
* */
void Kinematics::BoostIntoTwoParticleFrame(const Energy M, const Lorentz5Momentum & piLab,
const Lorentz5Momentum & pjLab,
Lorentz5Momentum & pClu1,
Lorentz5Momentum & pClu2) {
double cosPhi=piLab.vect().cosTheta(pjLab.vect());
double Phi=acos(cosPhi);
double sinPhi=sin(Phi);
// If Phi==0 use regular 1+1D boost
double epsilon=std::numeric_limits<double>::epsilon();
if (fabs(cosPhi-1.0)<=epsilon || fabs(cosPhi+1.0)<=epsilon) {
Lorentz5Momentum pClu(M,piLab+pjLab);
Boost bv = pClu.boostVector();
pClu1.boost(bv);
pClu2.boost(bv);
return;
}
Energy Ei=piLab.e();
Energy Ej=pjLab.e();
if (std::isnan(Phi) || std::isinf(Phi)) throw Exception() << "NAN in Phi\n"
<< Exception::runerror;
Energy mi=piLab.mass();
Energy mj=pjLab.mass();
Energy2 mi2=mi*mi;
Energy2 mj2=mj*mj;
Energy Pi=piLab.vect().mag();
Energy Pj=pjLab.vect().mag();
assert(Pi>ZERO);
boost::numeric::ublas::vector<double> Pclu1Hat(4);
boost::numeric::ublas::vector<double> Pclu2Hat(4);
const Energy M1 = pClu1.mass();
const Energy M2 = pClu2.mass();
Pclu1Hat(0)=pClu1.e()/GeV;
Pclu1Hat(1)=pClu1.x()/GeV;
Pclu1Hat(2)=pClu1.y()/GeV;
Pclu1Hat(3)=pClu1.z()/GeV;
Pclu2Hat(0)=pClu2.e()/GeV;
Pclu2Hat(1)=pClu2.x()/GeV;
Pclu2Hat(2)=pClu2.y()/GeV;
Pclu2Hat(3)=pClu2.z()/GeV;
// Lorentz Matrix Lambda maps:
// piHat = (Ecomi,0,0, Pcom) to piLab = (Ei, 0,0,Pi )
// pjHat = (Ecomj,0,0,-Pcom) to pjLab = (Ej,Pj*sin(phi),0,Pj*cos(phi))
// and therefore maps also correctly Pclu1/2Hat to Pclu1/2 into the Lab frame
boost::numeric::ublas::matrix<double> Lambda(4,4);
const Energy sqrtS=M;
Energy pstar=Kinematics::pstarTwoBodyDecay(sqrtS,mi,mj);
Energy2 A2=pstar*sqrtS;
Energy2 B2=Pi*Pj*sinPhi;
Energy B2divPi=Pj*sinPhi;
Energy2 Deltaij=Pi*Ej-Ei*Pj*cosPhi;
double delta2=mi2*Pj*Pj*sinPhi*sinPhi/(Deltaij*Deltaij);
double Lambda11=0;
// better numerics
if (delta2<1e-13) Lambda11 = Deltaij>=ZERO ? (1.0-0.5*delta2):-(1.0-0.5*delta2);
else if (Deltaij!=ZERO) Lambda11= Deltaij>=ZERO ? 1.0/sqrt(1.0+delta2):-1.0/sqrt(1.0+delta2);
if (std::isnan(A2/GeV2) || std::isinf(A2/GeV2)) throw Exception() << "NAN in A2/GeV2\n"
<< Exception::runerror;
Lambda(0,0) = (Ei+Ej)/sqrtS;
Lambda(0,1) = B2/A2;
Lambda(0,2) = 0;
Lambda(0,3) = (Ei-Ej)/(2.0*pstar)-((mi2-mj2)*(Ei+Ej))/(2.0*sqrtS*A2);
Lambda(1,0) = B2divPi/sqrtS;
Lambda(1,1) = Lambda11;
Lambda(1,2) = 0;
Lambda(1,3) = -(sqrtS*sqrtS-(mj2-mi2))*B2divPi/(2.0*sqrtS*A2);
Lambda(2,0) = 0;
Lambda(2,1) = 0;
Lambda(2,2) = 1;
Lambda(2,3) = 0;
Lambda(3,0) = (Pi+Pj*cosPhi)/sqrtS;
Lambda(3,1) = Ei*B2divPi/A2;
Lambda(3,2) = 0;
Lambda(3,3) = (A2*A2-0.5*Ei*(Ej*(sqrtS*sqrtS-(mj2-mi2))-Ei*(sqrtS*sqrtS-(mi2-mj2))))/(Pi*sqrtS*A2);
/* Determinant calculation, but this is often far from 1 due to rounding errors
* */
// double det=0;
// det += Lambda(0,0)*Lambda(1,1)*Lambda(3,3);
// det += Lambda(1,0)*Lambda(3,1)*Lambda(0,3);
// det += Lambda(3,0)*Lambda(0,1)*Lambda(1,3);
// det -= Lambda(0,3)*Lambda(1,1)*Lambda(3,0);
// det -= Lambda(1,0)*Lambda(0,1)*Lambda(3,3);
// det -= Lambda(1,3)*Lambda(3,1)*Lambda(0,0);
// if (fabs(det-1.0)>1e-3 || std::isnan(det)) {
// std::cout << "A2 = "<<std::setprecision(18)<< A2/(GeV2) << std::endl;
// std::cout << "B2 = "<< B2/(GeV2)<< std::endl;
// std::cout << "DET-1 = "<< det-1.0 << std::setprecision(3)<< std::endl;
// std::cout << "Lambda:" << std::endl;
// for (int i = 0; i < 4; i++)
// {
// for (int j = 0; j < 4; j++)
// {
// std::cout<< std::setprecision(18) << Lambda(i,j)<< std::setprecision(3) << "\t";
// }
// std::cout << "\n";
// }
// std::cout << "pi,pj in Lab" << std::endl;
// std::cout << "Phi = " << std::setprecision(18) << Phi << std::endl;
// std::cout << "Phi - pi = " << std::setprecision(18) << Phi - M_PI << std::endl;
// }
// doing the Boost into the Lab frame:
boost::numeric::ublas::vector<double> Pclu1=boost::numeric::ublas::prod(Lambda,Pclu1Hat);
boost::numeric::ublas::vector<double> Pclu2=boost::numeric::ublas::prod(Lambda,Pclu2Hat);
// Computing the correct rotation, which maps pi/jRes into pi/jLab
Axis zAxis(0,0,1);
Axis xAxis(1,0,0);
Lorentz5Momentum piRes(mi,Pi*zAxis);
Lorentz5Momentum pjRes(mj,Pj*(xAxis*sinPhi+zAxis*cosPhi));
Lorentz5Momentum pClu1Res(M1,GeV*Axis(Pclu1[1],Pclu1[2],Pclu1[3]));
Lorentz5Momentum pClu2Res(M2,GeV*Axis(Pclu2[1],Pclu2[2],Pclu2[3]));
Axis omega1=piRes.vect().unit().cross(piLab.vect().unit());
double cosAngle1=piRes.vect().unit()*piLab.vect().unit();
double angle1=acos(cosAngle1);
// Rotate piRes into piLab
piRes.rotate(angle1, omega1);
pjRes.rotate(angle1, omega1);
// Correspondingly do the actual rotation on pClu1Res and pClu2Res
pClu1Res.rotate(angle1, omega1);
pClu2Res.rotate(angle1, omega1);
Axis omega2=piRes.vect().unit();
Momentum3 r1dim=(pjLab.vect()-piRes.vect().unit()*(pjLab.vect()*piRes.vect().unit()));
Momentum3 r2dim=(pjRes.vect()-piRes.vect().unit()*(pjRes.vect()*piRes.vect().unit()));
if (r1dim.mag()==ZERO || r2dim.mag()==ZERO || fabs(sinPhi)<1e-14) //trivial rotation so we are done
{
pClu1=pClu1Res;
pClu2=pClu2Res;
return;
}
Axis r1=r1dim.unit();
Axis r2=r2dim.unit();
// signs for 2nd rotation
int signToPi = (piRes.vect()*pjLab.vect())/GeV2 > 0 ? 1:-1;
int signToR1R2 = signToPi*(r2.unit().cross(r1.unit())*piRes.vect())/GeV> 0 ? 1:-1;
double angle2=acos(r1.unit()*r2.unit());
if (signToR1R2<0) angle2=-angle2;
// Rotate pjRes into pjLab
pjRes.rotate(angle2, signToPi*omega2);
// Correspondingly do the actual rotation on pClu1Res and pClu2Res
pClu1Res.rotate(angle2, signToPi*omega2);
pClu2Res.rotate(angle2, signToPi*omega2);
pClu1=pClu1Res;
pClu2=pClu2Res;
}
-inline double Kinematics::kaellen(double x, double y, double z) {
- return (x*x-(y+z)*(y+z))*(x*x-(y-z)*(y-z));
-}
-inline Energy4 Kinematics::kaellenV(Energy x, Energy y, Energy z) {
- return (x*x-(y+z)*(y+z))*(x*x-(y-z)*(y-z));
-}
bool Kinematics::twoBodyDecayNoBoost(const Lorentz5Momentum & p,
const Energy m1, const Energy m2,
const double cosThetaK, const double phiK,
Lorentz5Momentum & p1, Lorentz5Momentum & p2) {
Energy M = p.mass();
Energy2 M2 = M*M;
Energy2 m12 = m1*m1;
Energy2 m22 = m2*m2;
double cph = cos(phiK);
double sph = sin(phiK);
Energy Q = p.vect().mag();
Energy EQ = sqrt(M2+Q*Q);
double B = sqr(M/Q);
double sinThetaK = sqrt(1.-cosThetaK*cosThetaK);
Energy Pcom = sqrt(kaellenV(M,m1,m2))/(2.0*M);
double eta1 = (M2+m12-m22)/(2.0*M2);
double eta2 = (M2-m12+m22)/(2.0*M2);
Energy k = EQ*Pcom/(Q*sqrt(1.0+B-cosThetaK*cosThetaK));
Energy k0 = -EQ*eta1;
Energy E1 = sqrt(k*k+m12+2.0*cosThetaK*Q*k*eta1+sqr(Q*eta1));
k0 += E1;
Momentum3 kvec3(k*cph*sinThetaK,k*sph*sinThetaK,k*cosThetaK);
Lorentz5Momentum kvec5(kvec3,k0);
Axis xAx(1.0,0.0,0.0);
Axis yAx(0.0,1.0,0.0);
Axis zAx(0.0,0.0,1.0);
Lorentz5Momentum pz (Q*zAx,EQ);
p1 = pz*eta1+kvec5;
p2 = pz*eta2-kvec5;
// double Norm = sqrt(B)*(1.0+B)/2.0;
// double PhaseSpaceVol = (1.0+B)*Pcom/(8.0*Q*Norm);
double theta = acos(zAx.cosTheta(p.vect()));
double phi = theta==0 ? 0.0:atan2(p.vect().y()/GeV,p.vect().x()/GeV);
p1.rotate(theta, yAx);
p1.rotate(phi, zAx);
p2.rotate(theta, yAx);
p2.rotate(phi, zAx);
if (fabs((p1.m()-m1)/GeV)>1e-5) std::cout << "p1.m = "<<p1.m()/GeV << " m1 = "<<m1/GeV << std::endl;
if (fabs((p2.m()-m2)/GeV)>1e-5) std::cout << "p2.m = "<<p2.m()/GeV << " m2 = "<<m2/GeV << std::endl;
if (fabs(((p1+p2).m()-M)/GeV)>1e-5) std::cout << "(p1+p2).m = "<<(p1+p2).m()/GeV << " M = "<<M/GeV << std::endl;
return true;
}
bool Kinematics::twoBodyDecay(const Lorentz5Momentum & p,
const Energy m1, const Energy m2,
const Axis & unitDir1,
Lorentz5Momentum & p1, Lorentz5Momentum & p2) {
Energy min=p.mass();
if ( min >= m1 + m2 && m1 >= ZERO && m2 >= ZERO ) {
Momentum3 pstarVector = unitDir1 * Kinematics::pstarTwoBodyDecay(min,m1,m2);
p1 = Lorentz5Momentum(m1, pstarVector);
p2 = Lorentz5Momentum(m2,-pstarVector);
// boost from CM to LAB
Boost bv = p.boostVector();
double gammarest = p.e()/p.mass();
p1.boost( bv, gammarest );
p2.boost( bv, gammarest );
return true;
}
return false;
}
/*****
* This function, as the name implies, performs a three body decay. The decay
* products are distributed uniformly in all three directions.
****/
bool Kinematics::threeBodyDecay(Lorentz5Momentum p0, Lorentz5Momentum &p1,
Lorentz5Momentum &p2, Lorentz5Momentum &p3,
double (*fcn)(Energy2,Energy2,Energy2,InvEnergy4)) {
// Variables needed in calculation...named same as fortran version
Energy a = p0.mass() + p1.mass();
Energy b = p0.mass() - p1.mass();
Energy c = p2.mass() + p3.mass();
if(b < c) {
CurrentGenerator::log()
<< "Kinematics::threeBodyDecay() phase space problem\n"
<< p0.mass()/GeV << " -> "
<< p1.mass()/GeV << ' '
<< p2.mass()/GeV << ' '
<< p3.mass()/GeV << '\n';
return false;
}
Energy d = abs(p2.mass()-p3.mass());
Energy2 aa = sqr(a);
Energy2 bb = sqr(b);
Energy2 cc = sqr(c);
Energy2 dd = sqr(d);
Energy2 ee = (b-c)*(a-d);
Energy2 a1 = 0.5 * (aa+bb);
Energy2 b1 = 0.5 * (cc+dd);
InvEnergy4 c1 = 4./(sqr(a1-b1));
Energy2 ff;
double ww;
Energy4 pp,qq,rr;
// Choose mass of subsystem 23 with prescribed distribution
const unsigned int MAXTRY = 100;
unsigned int ntry=0;
do {
// ff is the mass squared of the 23 subsystem
ff = UseRandom::rnd()*(cc-bb)+bb;
// pp is ((m0+m1)^2 - m23^2)((m0-m1)^2-m23)
pp = (aa-ff)*(bb-ff);
// qq is ((m2+m3)^2 - m23^2)(|m2-m3|^2-m23^2)
qq = (cc-ff)*(dd-ff);
// weight
ww = (fcn != NULL) ? (*fcn)(ff,a1,b1,c1) : 1.0;
ww = sqr(ww);
rr = ee*ff*UseRandom::rnd();
++ntry;
}
while(pp*qq*ww < rr*rr && ntry < MAXTRY );
if(ntry >= MAXTRY) {
CurrentGenerator::log() << "Kinematics::threeBodyDecay can't generate momenta"
<< " after " << MAXTRY << " attempts\n";
return false;
}
// ff is the mass squared of subsystem 23
// do 2 body decays 0->1+23, 23->2+3
double CosAngle, AzmAngle;
Lorentz5Momentum p23;
p23.setMass(sqrt(ff));
generateAngles(CosAngle,AzmAngle);
bool status = twoBodyDecay(p0,p1.mass(),p23.mass(),CosAngle,AzmAngle,p1,p23);
generateAngles(CosAngle,AzmAngle);
status &= twoBodyDecay(p23,p2.mass(),p3.mass(),CosAngle,AzmAngle,p2,p3);
return status;
}
diff --git a/Utilities/Kinematics.h b/Utilities/Kinematics.h
--- a/Utilities/Kinematics.h
+++ b/Utilities/Kinematics.h
@@ -1,143 +1,148 @@
// -*- C++ -*-
//
// Kinematics.h is a part of Herwig - A multi-purpose Monte Carlo event generator
// Copyright (C) 2002-2019 The Herwig Collaboration
//
// Herwig is licenced under version 3 of the GPL, see COPYING for details.
// Please respect the MCnet academic guidelines, see GUIDELINES for details.
//
//
#ifndef HERWIG_Kinematics_H
#define HERWIG_Kinematics_H
// This is the declaration of the Kinematics class.
#include <ThePEG/Config/ThePEG.h>
#include "ThePEG/Vectors/ThreeVector.h"
#include "ThePEG/Vectors/LorentzRotation.h"
#include "ThePEG/Repository/UseRandom.h"
#include <ThePEG/Vectors/Lorentz5Vector.h>
namespace Herwig {
using namespace ThePEG;
/** \ingroup Utilities
* This is a namespace which provides some useful methods
* for kinematics computation, as the two body decays.
*
* NB) For other useful kinematical methods (and probably even those
* implemented in Kinematics class!):
* @see UtilityBase
*/
namespace Kinematics {
/**
* Calculate the momenta for a two body decay
* The return value indicates success or failure.
* @param p The momentum of the decaying particle
* @param m1 The mass of the first decay product
* @param m2 The mass of the second decay product
* @param unitDir1 Direction for the products in the rest frame of
* the decaying particle
* @param p1 The momentum of the first decay product
* @param p2 The momentum of the second decay product
*/
bool twoBodyDecay(const Lorentz5Momentum & p,
const Energy m1, const Energy m2,
const Axis & unitDir1,
Lorentz5Momentum & p1, Lorentz5Momentum & p2);
/**
* Calculate the momenta for a two body decay without performing a boost
* The return value indicates success or failure.
* @param p The momentum of the decaying particle
* @param m1 The mass of the first decay product
* @param m2 The mass of the second decay product
* @param unitDir1 Direction for the products in the rest frame of
* the decaying particle
* @param p1 The momentum of the first decay product
* @param p2 The momentum of the second decay product
*/
bool twoBodyDecayNoBoost(const Lorentz5Momentum & p,
const Energy m1, const Energy m2,
const double cosThetaK, const double phiK,
Lorentz5Momentum & p1, Lorentz5Momentum & p2);
/*
* Kaellen functions
* */
- inline double kaellen(double x, double y, double z);
- inline Energy4 kaellenV(Energy x, Energy y, Energy z);
+ double kaellen(double x, double y, double z){
+ return (x*x-(y+z)*(y+z))*(x*x-(y-z)*(y-z));
+ };
+
+ Energy4 kaellenV(Energy x, Energy y, Energy z){
+ return (x*x-(y+z)*(y+z))*(x*x-(y-z)*(y-z));
+ };
/**
* Boost consistently the Lorentz5Momenta pClu1 and pClu2 (given in their COM frame)
* into the p0Q1 p0Q2 frame.
* */
void BoostIntoTwoParticleFrame(const Energy M, const Lorentz5Momentum & piLab,
const Lorentz5Momentum & pjLab,
Lorentz5Momentum & pClu1,
Lorentz5Momentum & pClu2);
/**
* It returns the unit 3-vector with the given cosTheta and phi.
*/
inline Axis unitDirection(const double cosTheta, const double phi) {
return ( fabs( cosTheta ) <= 1.0 ?
Axis( cos(phi)*sqrt(1.0-cosTheta*cosTheta) ,
sin(phi)*sqrt(1.0-cosTheta*cosTheta) , cosTheta) : Axis() );
}
/**
* Calculate the momenta for a two body decay
* The return value indicates success or failure.
* @param p The momentum of the decaying particle
* @param m1 The mass of the first decay product
* @param m2 The mass of the second decay product
* @param cosThetaStar1 Polar angle in rest frame
* @param phiStar1 Azimuthal angle in rest frame
* @param p1 The momentum of the first decay product
* @param p2 The momentum of the second decay product
*/
inline bool twoBodyDecay(const Lorentz5Momentum & p,
const Energy m1, const Energy m2,
const double cosThetaStar1,
const double phiStar1,
Lorentz5Momentum & p1, Lorentz5Momentum & p2) {
return twoBodyDecay(p,m1,m2,unitDirection(cosThetaStar1,phiStar1),p1,p2);
}
/**
* As the name implies, this takes the momentum p0 and does a flat three
* body decay into p1..p3. The argument fcn is used to add additional
* weights. If it is not used, the default is just flat in phasespace.
* The return value indicates success or failure.
*/
bool threeBodyDecay(Lorentz5Momentum p0, Lorentz5Momentum &p1,
Lorentz5Momentum &p2, Lorentz5Momentum &p3,
double (*fcn)(Energy2,Energy2,Energy2,InvEnergy4) = NULL);
/**
* For the two body decay M -> m1 + m2 it gives the module of the
* 3-momentum of the decay product in the rest frame of M.
*/
inline Energy pstarTwoBodyDecay(const Energy M,
const Energy m1, const Energy m2) {
return ( M > ZERO && m1 >=ZERO && m2 >= ZERO && M > m1+m2 ?
Energy(sqrt(( sqr(M) - sqr(m1+m2) )*( sqr(M) - sqr(m1-m2) ))
/ (2.0*M) ) : ZERO);
}
/**
* This just generates angles. First flat -1..1, second flat 0..2Pi
*/
inline void generateAngles(double & ct, double & az) {
ct = UseRandom::rnd()*2.0 - 1.0; // Flat from -1..1
az = UseRandom::rnd()*Constants::twopi;
}
}
}
#endif /* HERWIG_Kinematics_H */

File Metadata

Mime Type
text/x-diff
Expires
Wed, May 14, 10:03 AM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5111030
Default Alt Text
(101 KB)

Event Timeline