// Begin_Html
/*
This is a generalization of the above Likelihood methods to <i>N</i><sub>var</sub>
dimensions, where <i>N</i><sub>var</sub> is the number of input variables
used in the MVA. If the multi-dimensional probability density functions
(PDFs) for signal and background were known, this method contains the entire
physical information, and is therefore optimal. Usually, kernel estimation
methods are used to approximate the PDFs using the events from the
training sample. <br><p></p>
A very simple probability density estimator (PDE) has been suggested
in <a href="http://arxiv.org/abs/hep-ex/0211019">hep-ex/0211019</a>. The
PDE for a given test event is obtained from counting the (normalized)
number of signal and background (training) events that occur in the
"vicinity" of the test event. The volume that describes "vicinity" is
user-defined. A <a href="http://arxiv.org/abs/hep-ex/0211019">search
method based on binary-trees</a> is used to effectively reduce the
selection time for the range search. Three different volume definitions
are optional: <br><p></p>
<ul>
<li><u>MinMax:</u>
the volume is defined in each dimension with respect
to the full variable range found in the training sample. </li>
<li><u>RMS:</u>
the volume is defined in each dimensions with respect
to the RMS estimated from the training sample. </li>
<li><u>Adaptive:</u>
a volume element is defined in each dimensions with
respect to the RMS estimated from the training sample. The overall
scale of the volume element is then determined for each event so
that the total number of events confined in the volume be within
a user-defined range.</li>
</ul><p></p>
The adaptive range search is used by default.
// End_Html
*/
#include "TMVA/MethodPDERS.h"
#include "TMVA/Tools.h"
#include "TMVA/RootFinder.h"
#include "TFile.h"
#include "TObjString.h"
#include "TMath.h"
#include <stdexcept>
#define TMVA_MethodPDERS__countByHand__Debug__
#undef TMVA_MethodPDERS__countByHand__Debug__
namespace TMVA {
const Bool_t MethodPDERS_UseFindRoot = kTRUE;
}
using std::vector;
ClassImp(TMVA::MethodPDERS)
;
TMVA::MethodPDERS::MethodPDERS( TString jobName, TString methodTitle, DataSet& theData,
TString theOption, TDirectory* theTargetDir )
: TMVA::MethodBase( jobName, methodTitle, theData, theOption, theTargetDir )
{
InitPDERS();
DeclareOptions();
ParseOptions();
ProcessOptions();
}
TMVA::MethodPDERS::MethodPDERS( DataSet& theData,
TString theWeightFile,
TDirectory* theTargetDir )
: TMVA::MethodBase( theData, theWeightFile, theTargetDir )
{
InitPDERS();
DeclareOptions();
}
void TMVA::MethodPDERS::InitPDERS( void )
{
SetMethodName( "PDERS" );
SetMethodType( TMVA::Types::kPDERS );
SetTestvarName();
fBinaryTreeS = fBinaryTreeB = NULL;
fReferenceTree = NULL;
UpdateThis();
fDeltaFrac = 3.0;
fVRangeMode = kAdaptive;
fKernelEstimator = kBox;
fNEventsMin = 100;
fNEventsMax = 200;
fMaxVIterations = 50;
fInitialScale = 0.99;
fGaussSigma = 0.2;
fInitializedVolumeEle = kFALSE;
}
TMVA::MethodPDERS::~MethodPDERS( void )
{
if (NULL != fBinaryTreeS) delete fBinaryTreeS;
if (NULL != fBinaryTreeB) delete fBinaryTreeB;
if (NULL != fFin) { fFin->Close(); delete fFin; }
}
void TMVA::MethodPDERS::DeclareOptions()
{
DeclareOptionRef(fVolumeRange="MinMax", "VolumeRangeMode", "Method to determine volume range");
AddPreDefVal(TString("Unscaled"));
AddPreDefVal(TString("MinMax"));
AddPreDefVal(TString("RMS"));
AddPreDefVal(TString("Adaptive"));
DeclareOptionRef(fKernelString="Box", "KernelEstimator", "Kernel estimation function");
AddPreDefVal(TString("Box"));
AddPreDefVal(TString("Sphere"));
AddPreDefVal(TString("Teepee"));
AddPreDefVal(TString("Gauss"));
AddPreDefVal(TString("Sinc3"));
AddPreDefVal(TString("Sinc5"));
AddPreDefVal(TString("Sinc7"));
AddPreDefVal(TString("Sinc9"));
AddPreDefVal(TString("Sinc11"));
AddPreDefVal(TString("Lanczos2"));
AddPreDefVal(TString("Lanczos3"));
AddPreDefVal(TString("Lanczos5"));
AddPreDefVal(TString("Lanczos8"));
DeclareOptionRef(fDeltaFrac , "DeltaFrac", "nEventsMin/Max for minmax and rms volume range");
DeclareOptionRef(fNEventsMin , "NEventsMin", "nEventsMin for adaptive volume range");
DeclareOptionRef(fNEventsMax , "NEventsMax", "nEventsMax for adaptive volume range");
DeclareOptionRef(fMaxVIterations, "MaxVIterations", "MaxVIterations for adaptive volume range");
DeclareOptionRef(fInitialScale , "InitialScale", "InitialScale for adaptive volume range");
DeclareOptionRef(fGaussSigma , "GaussSigma", "Width (wrt volume size) of Gaussian kernel estimator");
}
void TMVA::MethodPDERS::ProcessOptions()
{
MethodBase::ProcessOptions();
fVRangeMode = TMVA::MethodPDERS::kUnsupported;
if (fVolumeRange == "MinMax" ) fVRangeMode = TMVA::MethodPDERS::kMinMax;
else if (fVolumeRange == "RMS" ) fVRangeMode = TMVA::MethodPDERS::kRMS;
else if (fVolumeRange == "Adaptive" ) fVRangeMode = TMVA::MethodPDERS::kAdaptive;
else if (fVolumeRange == "Unscaled" ) fVRangeMode = TMVA::MethodPDERS::kUnscaled;
else {
fLogger << kFATAL << "VolumeRangeMode parameter '" << fVolumeRange << "' unknown" << Endl;
}
if (fKernelString == "Box" ) fKernelEstimator = TMVA::MethodPDERS::kBox;
else if (fKernelString == "Sphere" ) fKernelEstimator = TMVA::MethodPDERS::kSphere;
else if (fKernelString == "Teepee" ) fKernelEstimator = TMVA::MethodPDERS::kTeepee;
else if (fKernelString == "Gauss" ) fKernelEstimator = TMVA::MethodPDERS::kGauss;
else if (fKernelString == "Sinc3" ) fKernelEstimator = TMVA::MethodPDERS::kSinc3;
else if (fKernelString == "Sinc5" ) fKernelEstimator = TMVA::MethodPDERS::kSinc5;
else if (fKernelString == "Sinc7" ) fKernelEstimator = TMVA::MethodPDERS::kSinc7;
else if (fKernelString == "Sinc9" ) fKernelEstimator = TMVA::MethodPDERS::kSinc9;
else if (fKernelString == "Sinc11" ) fKernelEstimator = TMVA::MethodPDERS::kSinc11;
else if (fKernelString == "Lanczos2" ) fKernelEstimator = TMVA::MethodPDERS::kLanczos2;
else if (fKernelString == "Lanczos3" ) fKernelEstimator = TMVA::MethodPDERS::kLanczos3;
else if (fKernelString == "Lanczos5" ) fKernelEstimator = TMVA::MethodPDERS::kLanczos5;
else if (fKernelString == "Lanczos8" ) fKernelEstimator = TMVA::MethodPDERS::kLanczos8;
else {
fLogger << kFATAL << "KernelEstimator parameter '" << fKernelString << "' unknown" << Endl;
}
fLogger << kVERBOSE << "interpreted option string: vRangeMethod: '"
<< (const char*)((fVRangeMode == kMinMax) ? "MinMax" :
(fVRangeMode == kUnscaled) ? "Unscaled" :
(fVRangeMode == kRMS ) ? "RMS" : "Adaptive") << "'" << Endl;
if (fVRangeMode == kMinMax || fVRangeMode == kRMS)
fLogger << kVERBOSE << "deltaFrac: " << fDeltaFrac << Endl;
else
fLogger << kVERBOSE << "nEventsMin/Max, maxVIterations, initialScale: "
<< fNEventsMin << " " << fNEventsMax
<< " " << fMaxVIterations << " " << fInitialScale << Endl;
fLogger << kVERBOSE << "KernelEstimator = " << fKernelString << Endl;
}
void TMVA::MethodPDERS::Train( void )
{
if (!CheckSanity()) fLogger << kFATAL << "<Train> sanity check failed" << Endl;
Data().GetTrainingTree()->ResetBranchAddresses();
Data().ResetCurrentTree();
TTree* refTree = (TTree*)Data().GetTrainingTree()->CloneTree(0);
refTree->SetName("referenceTree");
Data().Event().SetBranchAddresses( refTree );
for (Int_t i=0; i<Data().GetNEvtTrain(); i++) {
ReadTrainingEvent(i);
refTree->Fill();
}
SetReferenceTree( refTree );
}
Double_t TMVA::MethodPDERS::GetMvaValue()
{
if (fInitializedVolumeEle == kFALSE) {
fInitializedVolumeEle = kTRUE;
SetVolumeElement();
Int_t nS = 0, nB = 0;
fBinaryTreeS = new TMVA::BinarySearchTree();
nS = fBinaryTreeS->Fill( Data(), GetReferenceTree(), 1, Types::kNone );
fBinaryTreeB = new TMVA::BinarySearchTree();
nB = fBinaryTreeB->Fill( Data(), GetReferenceTree(), 0, Types::kNone );
if (NULL == fBinaryTreeS || NULL == fBinaryTreeB) {
fLogger << kFATAL << "<Train> Create(BinaryTree) returned zero "
<< "binaryTree pointer(s): " << fBinaryTreeS << " " << fBinaryTreeB << Endl;
}
fScaleS = 1.0/Float_t(nS);
fScaleB = 1.0/Float_t(nB);
fLogger << kVERBOSE << "signal and background scales: " << fScaleS << " " << fScaleB << Endl;
}
return this->RScalc( Data().Event() );
}
void TMVA::MethodPDERS::SetVolumeElement( void )
{
fDelta = (GetNvar() > 0) ? new vector<Float_t>( GetNvar() ) : 0;
fShift = (GetNvar() > 0) ? new vector<Float_t>( GetNvar() ) : 0;
if (fDelta != 0) {
for (Int_t ivar=0; ivar<GetNvar(); ivar++) {
switch (fVRangeMode) {
case kRMS:
case kAdaptive:
Double_t meanS, meanB, rmsS, rmsB, xmin, xmax;
TMVA::Tools::ComputeStat( GetReferenceTree(), (*fInputVars)[ivar],
meanS, meanB, rmsS, rmsB, xmin, xmax );
(*fDelta)[ivar] = (rmsS + rmsB)*0.5*fDeltaFrac;
fLogger << kVERBOSE << "delta of var[" << (*fInputVars)[ivar]
<< "\t]: " << (rmsS + rmsB)*0.5
<< "\t | comp with d|norm|: " << (GetXmax( ivar ) - GetXmin( ivar ))
<< Endl;
break;
case kMinMax:
(*fDelta)[ivar] = (GetXmax( ivar ) - GetXmin( ivar ))*fDeltaFrac;
break;
case kUnscaled:
(*fDelta)[ivar] = fDeltaFrac;
break;
default:
fLogger << kFATAL << "<SetVolumeElement> unknown range-set mode: "
<< fVRangeMode << Endl;
}
(*fShift)[ivar] = 0.5;
}
}
else {
fLogger << kFATAL << "GetNvar() <= 0: " << GetNvar() << Endl;
}
}
TMVA::MethodPDERS* TMVA::MethodPDERS::fgThisPDERS = NULL;
Double_t TMVA::MethodPDERS::IGetVolumeContentForRoot( Double_t scale )
{
return ThisPDERS()->GetVolumeContentForRoot( scale );
}
Double_t TMVA::MethodPDERS::GetVolumeContentForRoot( Double_t scale )
{
Volume v( *fHelpVolume );
v.ScaleInterval( scale );
Double_t cS = GetBinaryTreeSig()->SearchVolume( &v );
Double_t cB = GetBinaryTreeBkg()->SearchVolume( &v );
v.Delete();
return cS + cB;
}
Float_t TMVA::MethodPDERS::RScalc( const TMVA::Event& e )
{
vector<Double_t> *lb = new vector<Double_t>( GetNvar() );
for (Int_t ivar=0; ivar<GetNvar(); ivar++) (*lb)[ivar] = e.GetVal(ivar);
vector<Double_t> *ub = new vector<Double_t>( *lb );
for (Int_t ivar=0; ivar<GetNvar(); ivar++) {
(*lb)[ivar] -= (*fDelta)[ivar]*(1.0 - (*fShift)[ivar]);
(*ub)[ivar] += (*fDelta)[ivar]*(*fShift)[ivar];
}
TMVA::Volume* volume = new TMVA::Volume( lb, ub );
Float_t countS = 0;
Float_t countB = 0;
#ifdef TMVA_MethodPDERS__countByHand__Debug__
countS = fBinaryTreeS->SearchVolume( volume );
countB = fBinaryTreeB->SearchVolume( volume );
Int_t iS = 0, iB = 0;
for (Int_t ievt_=0; ievt_<Data().GetNEvtTrain(); ievt_++) {
Data().ReadTrainEvent(ievt_);
Bool_t inV;
for (Int_t ivar=0; ivar<GetNvar(); ivar++) {
Float_t x = Data().Event().GetVal(ivar);
inV = (x > (*volume->Lower)[ivar] && x <= (*volume->Upper)[ivar]);
if (!inV) break;
}
if (inV) {
if (Data().Event().IsSignal())
iS++;
else
iB++;
}
}
fLogger << kVERBOSE << "debug: my test: S/B: " << iS << " " << iB << Endl;
fLogger << kVERBOSE << "debug: binTree: S/B: " << countS << " " << countB << Endl << Endl;
#endif
if (fVRangeMode == kRMS || fVRangeMode == kUnscaled) {
vector<Double_t> *lb = new vector<Double_t>( GetNvar() );
for (Int_t ivar=0; ivar<GetNvar(); ivar++) (*lb)[ivar] = e.GetVal(ivar);
vector<Double_t> *ub = new vector<Double_t>( *lb );
for (Int_t ivar=0; ivar<GetNvar(); ivar++) {
(*lb)[ivar] -= (*fDelta)[ivar]*(1.0 - (*fShift)[ivar]);
(*ub)[ivar] += (*fDelta)[ivar]*(*fShift)[ivar];
}
TMVA::Volume* volume = new TMVA::Volume( lb, ub );
TMVA::Volume v( *volume );
std::vector<TMVA::Event*> eventsS;
std::vector<TMVA::Event*> eventsB;
fBinaryTreeS->SearchVolume( &v, &eventsS );
fBinaryTreeB->SearchVolume( &v, &eventsB );
countS = KernelEstimate( e, eventsS, v );
countB = KernelEstimate( e, eventsB, v );
delete lb;
delete ub;
}
else if (fVRangeMode == kAdaptive) {
if (TMVA::MethodPDERS_UseFindRoot) {
fHelpVolume = new TMVA::Volume( *volume );
UpdateThis();
TMVA::RootFinder rootFinder( &IGetVolumeContentForRoot, 0.01, 50, 50, 10 );
Double_t scale = rootFinder.Root( (fNEventsMin + fNEventsMax)/2.0 );
TMVA::Volume v( *volume );
v.ScaleInterval( scale );
std::vector<TMVA::Event*> eventsS;
std::vector<TMVA::Event*> eventsB;
fBinaryTreeS->SearchVolume( &v, &eventsS );
fBinaryTreeB->SearchVolume( &v, &eventsB );
countS = KernelEstimate( e, eventsS, v );
countB = KernelEstimate( e, eventsB, v );
v.Delete();
fHelpVolume->Delete();
delete fHelpVolume; fHelpVolume = NULL;
}
else {
countS = fBinaryTreeS->SearchVolume( volume );
countB = fBinaryTreeB->SearchVolume( volume );
Float_t nEventsO = countS + countB;
Int_t i_=0;
while (nEventsO < fNEventsMin) {
volume->ScaleInterval( 1.15 );
countS = fBinaryTreeS->SearchVolume( volume );
countB = fBinaryTreeB->SearchVolume( volume );
nEventsO = countS + countB;
i_++;
}
if (i_ > 50) fLogger << kWARNING << "warning in event: " << e
<< ": adaptive volume pre-adjustment reached "
<< ">50 iterations in while loop (" << i_ << ")" << Endl;
Float_t nEventsN = nEventsO;
Float_t nEventsE = 0.5*(fNEventsMin + fNEventsMax);
Float_t scaleO = 1.0;
Float_t scaleN = fInitialScale;
Float_t scale = scaleN;
Float_t cS = countS;
Float_t cB = countB;
for (Int_t ic=1; ic<fMaxVIterations; ic++) {
if (nEventsN < fNEventsMin || nEventsN > fNEventsMax) {
TMVA::Volume* v = new TMVA::Volume( *volume );
v->ScaleInterval( scale );
cS = fBinaryTreeS->SearchVolume( v );
cB = fBinaryTreeB->SearchVolume( v );
nEventsN = cS + cB;
if (nEventsN > 1 && nEventsN - nEventsO != 0)
if (scaleN - scaleO != 0)
scale += (scaleN - scaleO)/(nEventsN - nEventsO)*(nEventsE - nEventsN);
else
scale += (-0.01);
else
scale += 0.5;
scaleN = scale;
if (TMath::Abs(cS + cB - nEventsE) < TMath::Abs(countS + countB - nEventsE) &&
(cS + cB >= fNEventsMin || countS + countB < cS + cB)) {
countS = cS; countB = cB;
}
v->Delete();
delete v;
}
else break;
}
nEventsN = countS + countB;
if (nEventsN < fNEventsMin-1 || nEventsN > fNEventsMax+1)
fLogger << kWARNING << "warning in event " << e
<< ": adaptive volume adjustment reached "
<< "max. #iterations (" << fMaxVIterations << ")"
<< "[ nEvents: " << nEventsN << " " << fNEventsMin << " " << fNEventsMax << "]"
<< Endl;
}
}
volume->Delete();
delete volume;
if (countS < 1e-20 && countB < 1e-20) return 0.5;
if (countB < 1e-20) return 1.0;
if (countS < 1e-20) return 0.0;
Float_t r = countB*fScaleB/(countS*fScaleS);
return 1.0/(r + 1.0);
}
Double_t TMVA::MethodPDERS::KernelEstimate( const TMVA::Event & event,
vector<TMVA::Event*>& events, TMVA::Volume& v )
{
Double_t pdfSum = 0;
Double_t *dim_normalization = new Double_t[GetNvar()];
for (Int_t ivar=0; ivar<GetNvar(); ivar++)
dim_normalization [ivar] = 2 / ((*v.fUpper)[ivar] - (*v.fLower)[ivar]);
for (vector<TMVA::Event*>::iterator iev = events.begin(); iev != events.end(); iev++) {
Double_t normalized_distance = GetNormalizedDistance (event, *(*iev), dim_normalization);
if (normalized_distance > 1 && fKernelEstimator != kBox) continue;
pdfSum += ApplyKernelFunction (normalized_distance) * (*iev)->GetWeight();
}
return KernelNormalization( pdfSum < 0. ? 0. : pdfSum );
}
Double_t TMVA::MethodPDERS::ApplyKernelFunction (Double_t normalized_distance)
{
switch (fKernelEstimator) {
case kBox:
case kSphere:
return 1;
break;
case kTeepee:
return (1 - normalized_distance);
break;
case kGauss:
return TMath::Gaus( normalized_distance, 0, fGaussSigma, kFALSE);
break;
case kSinc3:
case kSinc5:
case kSinc7:
case kSinc9:
case kSinc11: {
Double_t side_crossings = 2 + ((int) fKernelEstimator) - ((int) kSinc3);
return NormSinc (side_crossings * normalized_distance);
}
break;
case kLanczos2:
return LanczosFilter (2, normalized_distance);
break;
case kLanczos3:
return LanczosFilter (3, normalized_distance);
break;
case kLanczos5:
return LanczosFilter (5, normalized_distance);
break;
case kLanczos8:
return LanczosFilter (8, normalized_distance);
break;
default:
fLogger << kFATAL << "Kernel estimation function unsupported. Enumerator is " << fKernelEstimator << Endl;
break;
}
return 0;
}
Double_t TMVA::MethodPDERS::KernelNormalization (Double_t pdf)
{
static Double_t ret = 1.;
if (ret != 0.)
return ret*pdf;
switch (fKernelEstimator) {
case kBox:
case kSphere:
ret = 1.;
break;
case kTeepee:
ret = (GetNvar() * (GetNvar() + 1) * TMath::Gamma (((Double_t) GetNvar()) / 2.)) /
( TMath::Power (2., (Double_t) GetNvar() + 1) * TMath::Power (TMath::Pi(), ((Double_t) GetNvar()) / 2.));
break;
case kGauss:
ret = 1. / TMath::Power ( 2 * TMath::Pi() * fGaussSigma * fGaussSigma, ((Double_t) GetNvar()) / 2.);
break;
case kSinc3:
case kSinc5:
case kSinc7:
case kSinc9:
case kSinc11:
case kLanczos2:
case kLanczos3:
case kLanczos5:
case kLanczos8:
ret = 1 / TMath::Power ( 2., (Double_t) GetNvar() );
break;
default:
fLogger << kFATAL << "Kernel estimation function unsupported. Enumerator is " << fKernelEstimator << Endl;
}
ret *= ( TMath::Power (2., GetNvar()) * TMath::Gamma (1 + (((Double_t) GetNvar()) / 2.)) ) /
TMath::Power (TMath::Pi(), ((Double_t) GetNvar()) / 2.);
return ret*pdf;
}
Double_t TMVA::MethodPDERS::GetNormalizedDistance ( const TMVA::Event &base_event,
const TMVA::Event &sample_event,
Double_t *dim_normalization) {
Double_t ret=0;
for (Int_t ivar=0; ivar<GetNvar(); ivar++) {
Double_t dist = dim_normalization[ivar] * (sample_event.GetVal(ivar) - base_event.GetVal(ivar));
ret += dist*dist;
}
return TMath::Sqrt (ret);
}
Double_t TMVA::MethodPDERS::NormSinc (Double_t x)
{
if (x < 10e-10 && x > -10e-10) {
return 1;
}
Double_t pix = TMath::Pi() * x;
Double_t sinc = TMath::Sin(pix) / pix;
Double_t ret;
if (GetNvar() % 2)
ret = TMath::Power (sinc, GetNvar());
else
ret = TMath::Abs (sinc) * TMath::Power (sinc, GetNvar() - 1);
return ret;
}
Double_t TMVA::MethodPDERS::LanczosFilter (Int_t level, Double_t x)
{
if (x < 10e-10 && x > -10e-10) {
return 1;
}
Double_t pix = TMath::Pi() * x;
Double_t pixtimesn = pix * ((Double_t) level);
Double_t lanczos = (TMath::Sin(pix) / pix) * (TMath::Sin(pixtimesn) / pixtimesn);
Double_t ret;
if (GetNvar() % 2)
ret = TMath::Power (lanczos, GetNvar());
else
ret = TMath::Abs (lanczos) * TMath::Power (lanczos, GetNvar() - 1);
return ret;
}
Float_t TMVA::MethodPDERS::GetError( Float_t countS, Float_t countB,
Float_t sumW2S, Float_t sumW2B ) const
{
Float_t c = fScaleB/fScaleS;
Float_t d = countS + c*countB; d *= d;
if (d < 1e-10) return 1;
Float_t f = c*c/d/d;
Float_t err = f*countB*countB*sumW2S + f*countS*countS*sumW2B;
if (err < 1e-10) return 1;
return sqrt(err);
}
void TMVA::MethodPDERS::WriteWeightsToStream( ostream& o ) const
{
TString fname = GetWeightFileName() + ".root";
fLogger << kINFO << "creating weight file: " << fname << Endl;
TFile *fout = new TFile( fname, "RECREATE" );
o << "# weights stored in root i/o file: " << fname << endl;
GetReferenceTree()->Write();
fout->Close();
delete fout;
}
void TMVA::MethodPDERS::ReadWeightsFromStream( istream& istr )
{
if (istr.eof());
TString fname = GetWeightFileName();
if (!fname.EndsWith( ".root" )) fname += ".root";
fLogger << kINFO << "reading weight file: " << fname << Endl;
fFin = new TFile( fname );
TTree* tree = (TTree*)fFin->Get( "referenceTree" );
SetReferenceTree( tree );
if (NULL == GetReferenceTree()) {
fLogger << kFATAL << "error while reading 'referenceTree': zero pointer " << Endl;
}
}
ROOT page - Class index - Class Hierarchy - Top of the page
This page has been automatically generated. If you have any comments or suggestions about the page layout send a mail to ROOT support, or contact the developers with any questions or problems regarding ROOT.