diff --git a/src/LauDecayTimePdf.cc b/src/LauDecayTimePdf.cc index 850b164..f7dc0dd 100644 --- a/src/LauDecayTimePdf.cc +++ b/src/LauDecayTimePdf.cc @@ -1,613 +1,614 @@ /* Copyright 2006 University of Warwick Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /* Laura++ package authors: John Back Paul Harrison Thomas Latham */ /*! \file LauDecayTimePdf.cc \brief File containing implementation of LauDecayTimePdf class. */ #include #include #include #include #include #include #include "TMath.h" #include "TRandom.h" #include "TSystem.h" #include "TH1.h" #include "RooMath.h" #include "Lau1DCubicSpline.hh" #include "Lau1DHistPdf.hh" #include "LauConstants.hh" #include "LauComplex.hh" #include "LauDecayTimePdf.hh" #include "LauFitDataTree.hh" #include "LauParameter.hh" #include "LauParamFixed.hh" #include "LauRandom.hh" #include "LauNonSmearedDecayTimeCalculator.hh" #include "LauNonSmearedUniformEfficiencyDecayTimeIntegrator.hh" #include "LauNonSmearedBinnedEfficiencyDecayTimeIntegrator.hh" #include "LauNonSmearedSplineEfficiencyDecayTimeIntegrator.hh" #include "LauSmearedDecayTimeCalculator.hh" #include "LauSmearedUniformEfficiencyDecayTimeIntegrator.hh" #include "LauSmearedBinnedEfficiencyDecayTimeIntegrator.hh" #include "LauSmearedSplineEfficiencyDecayTimeIntegrator.hh" ClassImp(LauDecayTimePdf) LauDecayTimePdf::LauDecayTimePdf(const TString& theVarName, const Double_t minAbscissaVal, const Double_t maxAbscissaVal, const TH1* dtHist, const LauDecayTime::TimeMeasurementMethod method) : type_{LauDecayTime::FuncType::Hist}, method_{method}, varName_{theVarName}, minAbscissa_{minAbscissaVal}, maxAbscissa_{maxAbscissaVal}, pdfHist_{ (dtHist==nullptr) ? nullptr : std::make_unique( varName_, dtHist, minAbscissa_, maxAbscissa_ ) } // TODO - check this, once we've consolidated all the data members { } LauDecayTimePdf::LauDecayTimePdf(const TString& theVarName, const Double_t minAbscissaVal, const Double_t maxAbscissaVal, const TString& theVarErrName, const Double_t minAbscissaErr, const Double_t maxAbscissaErr, const TH1* dtHist, const TH1* dtErrHist, const LauDecayTime::TimeMeasurementMethod method) : type_{LauDecayTime::FuncType::Hist}, method_{method}, varName_{theVarName}, varErrName_{theVarErrName}, minAbscissa_{minAbscissaVal}, maxAbscissa_{maxAbscissaVal}, minAbscissaError_{minAbscissaErr}, maxAbscissaError_{maxAbscissaErr}, pdfHist_{ (dtHist==nullptr) ? nullptr : std::make_unique( varName_, dtHist, minAbscissa_, maxAbscissa_ ) }, errHist_{ (dtErrHist==nullptr) ? nullptr : std::make_unique( varErrName_, dtErrHist, minAbscissaError_, maxAbscissaError_ ) } // TODO - check this, once we've consolidated all the data members { } LauDecayTimePdf::LauDecayTimePdf(const TString& theVarName, const Double_t minAbscissaVal, const Double_t maxAbscissaVal, std::unique_ptr physicsModel, const LauDecayTime::TimeMeasurementMethod method) : type_{physicsModel->getFunctionType()}, method_{method}, varName_{theVarName}, minAbscissa_{minAbscissaVal}, maxAbscissa_{maxAbscissaVal}, physicsModel_{std::move(physicsModel)} // TODO - check this, once we've consolidated all the data members { auto effModel = std::make_unique(1.0); calculator_ = std::make_unique( minAbscissa_, maxAbscissa_, *physicsModel_ ); integrator_ = std::make_unique( minAbscissa_, maxAbscissa_, *physicsModel_, *effModel ); efficiencyModel_ = std::move( effModel ); } LauDecayTimePdf::LauDecayTimePdf(const TString& theVarName, const Double_t minAbscissaVal, const Double_t maxAbscissaVal, std::unique_ptr physicsModel, std::unique_ptr resolutionModel, const LauDecayTime::TimeMeasurementMethod method) : type_{physicsModel->getFunctionType()}, method_{method}, varName_{theVarName}, minAbscissa_{minAbscissaVal}, maxAbscissa_{maxAbscissaVal}, physicsModel_{std::move(physicsModel)}, resolutionModel_{std::move(resolutionModel)}, smear_{true} // TODO - check this, once we've consolidated all the data members { auto effModel = std::make_unique(1.0); calculator_ = std::make_unique( minAbscissa_, maxAbscissa_, *physicsModel_, *resolutionModel_ ); integrator_ = std::make_unique( minAbscissa_, maxAbscissa_, *physicsModel_, *effModel, *resolutionModel_ ); efficiencyModel_ = std::move( effModel ); } LauDecayTimePdf::LauDecayTimePdf(const TString& theVarName, const Double_t minAbscissaVal, const Double_t maxAbscissaVal, const TString& theVarErrName, const Double_t minAbscissaErr, const Double_t maxAbscissaErr, std::unique_ptr physicsModel, std::unique_ptr resolutionModel, const TH1* dtErrHist, const LauDecayTime::TimeMeasurementMethod method) : type_{physicsModel->getFunctionType()}, method_{method}, varName_{theVarName}, varErrName_{theVarErrName}, minAbscissa_{minAbscissaVal}, maxAbscissa_{maxAbscissaVal}, minAbscissaError_{minAbscissaErr}, maxAbscissaError_{maxAbscissaErr}, physicsModel_{std::move(physicsModel)}, resolutionModel_{std::move(resolutionModel)}, smear_{true}, scaleWithPerEventError_{true}, errHist_{ (dtErrHist==nullptr) ? nullptr : std::make_unique( varErrName_, dtErrHist, minAbscissaError_, maxAbscissaError_ ) } // TODO - check this, once we've consolidated all the data members { auto effModel = std::make_unique(1.0); calculator_ = std::make_unique( minAbscissa_, maxAbscissa_, *physicsModel_, *resolutionModel_ ); integrator_ = std::make_unique( minAbscissa_, maxAbscissa_, *physicsModel_, *effModel, *resolutionModel_ ); efficiencyModel_ = std::move( effModel ); } void LauDecayTimePdf::initialise() { if (type_ == LauDecayTime::FuncType::Hist) { if ( not pdfHist_ ) { std::cerr << "ERROR in LauDecayTimePdf::initialise : Hist type should have a histogram PDF supplied" << std::endl; gSystem->Exit(EXIT_FAILURE); } + return; } if ( smear_ and scaleWithPerEventError_ ) { if ( not errHist_ ) { std::cerr << "ERROR in LauDecayTimePdf::initialise : scaling with per-event error but no error distribution supplied" << std::endl; gSystem->Exit(EXIT_FAILURE); } } // TODO other consistency checks? // Initialise the physics model physicsModel_->initialise(); // Initialise the resolution model if ( resolutionModel_ ) { resolutionModel_->initialise(); } // Initialise the efficiency model efficiencyModel_->initialise(); // Get all the parameters and consolidate them so we can pass them to the fit model std::vector physicsPars { physicsModel_->getParameters() }; physicsParFloating_ = this->anyParFloating( physicsPars ); this->addParams( physicsPars ); if ( resolutionModel_ ) { std::vector resoPars { resolutionModel_->getParameters() }; resoParFloating_ = this->anyParFloating( resoPars ); this->addParams( resoPars ); } std::vector effiPars { efficiencyModel_->getParameters() }; effiParFloating_ = this->anyParFloating( effiPars ); this->addParams( effiPars ); anythingFloating_ = physicsParFloating_ or resoParFloating_ or effiParFloating_; } void LauDecayTimePdf::cacheInfo(const LauFitDataTree& inputData) { const std::size_t nEvents { inputData.nEvents() }; // If we're a histogram form then there's not so much to do if ( type_ == LauDecayTime::FuncType::Hist ) { // Pass the data to the decay-time PDF for caching pdfHist_->cacheInfo(inputData); // Pass the data to the decay-time error PDF for caching if ( errHist_ ) { errHist_->cacheInfo(inputData); } // Make cache of effiTerms // Efficiency will always be 1 by definition effiTerms_.assign(nEvents,1.0); return; } // Otherwise... // Check that the input data contains the decay time variable bool hasBranch { inputData.haveBranch( varName_ ) }; if (!hasBranch) { std::cerr << "ERROR in LauDecayTimePdf::cacheInfo : Input data does not contain variable \"" << varName_ << "\"" << std::endl; gSystem->Exit(EXIT_FAILURE); } // If we're scaling by the per-event error, also check that the input data contains the decay time error variable const bool needPerEventNormTerms { smear_ and scaleWithPerEventError_ }; if ( needPerEventNormTerms ) { hasBranch = inputData.haveBranch( varErrName_ ); if (!hasBranch) { std::cerr << "ERROR in LauDecayTimePdf::cacheInfo : Input data does not contain variable \"" << varErrName_ << "\"" << std::endl; gSystem->Exit(EXIT_FAILURE); } // Pass the data to the decay-time error PDF for caching errHist_->cacheInfo(inputData); } // Clear the vectors and reserve enough space in the caches of the terms abscissas_.clear(); abscissas_.resize(nEvents); abscissaErrors_.clear(); abscissaErrors_.resize(nEvents); termsStore_.clear(); termsStore_.resize(nEvents); effiTerms_.clear(); effiTerms_.resize(nEvents); // Correctly size the normalisation cache elements // depending on whether we're doing per-event resolution normTermsStore_.clear(); if ( needPerEventNormTerms ) { normTermsStore_.resize(nEvents); } else { normTermsStore_.resize(1); } // Determine the abscissa and abscissa error values for each event for ( std::size_t iEvt {0}; iEvt < nEvents; iEvt++ ) { const LauFitData& dataValues { inputData.getData(iEvt) }; const Double_t abscissa { dataValues.at( varName_ ) }; if ( abscissa > maxAbscissa_ or abscissa < minAbscissa_ ) { std::cerr << "ERROR in LauDecayTimePdf::cacheInfo : Given value of the decay time: " << abscissa << " is outside allowed range: [" << minAbscissa_ << "," << maxAbscissa_ << "]" << std::endl; gSystem->Exit(EXIT_FAILURE); } abscissas_[iEvt] = abscissa; const Double_t abscissaErr { needPerEventNormTerms ? dataValues.at( varErrName_ ) : 0.0 }; if ( errHist_ and ( abscissaErr > maxAbscissaError_ or abscissaErr < minAbscissaError_ ) ) { std::cerr << "ERROR in LauDecayTimePdf::cacheInfo : Given value of the decay-time error: " << abscissaErr << " is outside allowed range: [" << minAbscissaError_ << "," << maxAbscissaError_ << "]." << std::endl; gSystem->Exit(EXIT_FAILURE); } abscissaErrors_[iEvt] = abscissaErr; } // Cache the numerator terms calculator_->cacheInfo( abscissas_, abscissaErrors_ ); // Cache the normalisations integrator_->cacheInfo( abscissaErrors_ ); // Force storing of all info this first time around physicsParChanged_ = true; resoParChanged_ = true; effiParChanged_ = true; this->updateCache(); } bool LauDecayTimePdf::anyParFloating( const std::vector& pars ) const { LauParamFixed isFixed; return not std::all_of( pars.begin(), pars.end(), isFixed ); } void LauDecayTimePdf::addParams( std::vector& pars ) { for ( auto& par : pars ) { params_.push_back( par ); } } void LauDecayTimePdf::updateCache() { // Calculate the values of all terms for each event const std::size_t nEvents { abscissas_.size() }; // If any of the physics or resolution parameters have changed we need // to update all numerator terms if ( physicsParChanged_ or resoParChanged_ ) { for (std::size_t iEvt {0}; iEvt < nEvents; iEvt++) { termsStore_[iEvt] = calculator_->getTerms( iEvt ); } } // If any of the efficiency parameters have changed we need to // recalculate the efficiency if ( effiParChanged_ ) { for (std::size_t iEvt {0}; iEvt < nEvents; iEvt++) { effiTerms_[iEvt] = this->calcEffiTerm( abscissas_[iEvt] ); } } // Calculate the normalisation terms // If we're not doing per-event scaling, // we only need to calculate the normalisations once const std::array nNormEntries { 1, nEvents }; const bool needPerEventNormTerms { smear_ and scaleWithPerEventError_ }; for ( std::size_t iEvt{0}; iEvt < nNormEntries[needPerEventNormTerms]; ++iEvt ) { normTermsStore_[iEvt] = integrator_->getNormTerms( iEvt ); } } void LauDecayTimePdf::calcLikelihoodInfo(const std::size_t iEvt) { // Extract all the terms and their normalisations if (type_ == LauDecayTime::FuncType::Hist) { pdfHist_->calcLikelihoodInfo(iEvt); histTerm_ = pdfHist_->getLikelihood(); } else { terms_ = termsStore_[iEvt]; const bool needPerEventNormTerms { smear_ and scaleWithPerEventError_ }; const std::size_t normTermElement { needPerEventNormTerms * iEvt }; normTerms_ = normTermsStore_[normTermElement]; } // Extract the decay time error PDF value if ( errHist_ ) { errHist_->calcLikelihoodInfo(iEvt); errTerm_ = errHist_->getLikelihood(); } else { errTerm_ = 1.0; } // Extract the decay time efficiency effiTerm_ = effiTerms_[iEvt]; } void LauDecayTimePdf::calcLikelihoodInfo( const Double_t abscissa ) { // Check whether any of the gaussians should be scaled - if any of them should we need the per-event error if ( smear_ and scaleWithPerEventError_ ) { std::cerr << "ERROR in LauDecayTimePdf::calcLikelihoodInfo : Per-event error on decay time not provided, cannot calculate anything." << std::endl; return; } this->calcLikelihoodInfo(abscissa, 0.0); } Double_t LauDecayTimePdf::calcEffiTerm( const Double_t abscissa ) const { Double_t effiTerm { efficiencyModel_->getEfficiency( abscissa ) }; // TODO print warning messages for these, but not every time // - do we even want the upper limit imposed? if ( effiTerm > 1.0 ) { effiTerm = 1.0; } // else if ( effiTerm < 0.0 ) { effiTerm = 0.0; } else if ( effiTerm <= 0.0 ) { effiTerm = 1e-20; } return effiTerm; } void LauDecayTimePdf::calcLikelihoodInfo(const Double_t abscissa, const Double_t abscissaErr) { // Check that the decay time and the decay time error are in valid ranges if ( abscissa > maxAbscissa_ or abscissa < minAbscissa_ ) { std::cerr << "ERROR in LauDecayTimePdf::calcLikelihoodInfo : Given value of the decay time: " << abscissa << " is outside allowed range: [" << minAbscissa_ << "," << maxAbscissa_ << "]" << std::endl; gSystem->Exit(EXIT_FAILURE); } if ( errHist_ and ( abscissaErr > maxAbscissaError_ or abscissaErr < minAbscissaError_ ) ) { std::cerr << "ERROR in LauDecayTimePdf::calcLikelihoodInfo : Given value of the decay-time error: " << abscissaErr << " is outside allowed range: [" << minAbscissaError_ << "," << maxAbscissaError_ << "]." << std::endl; gSystem->Exit(EXIT_FAILURE); } // Calculate the decay time error PDF value if ( errHist_ ) { const std::vector absErrVec {abscissaErr}; errHist_->calcLikelihoodInfo(absErrVec); errTerm_ = errHist_->getLikelihood(); } else { errTerm_ = 1.0; } // For the histogram PDF just calculate that term and return if ( type_ == LauDecayTime::FuncType::Hist ){ pdfHist_->calcLikelihoodInfo(abscissa); histTerm_ = pdfHist_->getLikelihood(); effiTerm_ = 1.0; return; } // Determine the decay time efficiency effiTerm_ = this->calcEffiTerm( abscissa ); // Determine the other terms terms_ = calculator_->calcTerms( abscissa, {1.0, abscissaErr} ); // TODO - do we need to do this? //if ( smear_ and scaleWithPerEventError_ ) { //normTerms_ = integrator_->calcNormTerms( {1.0, abscissaErr} ); //} } Double_t LauDecayTimePdf::generateError( bool forceNew ) { if ( errHist_ && ( forceNew or not abscissaErrorGenerated_ ) ) { LauFitData errData { errHist_->generate(nullptr) }; abscissaError_ = errData.at( varErrName_ ); abscissaErrorGenerated_ = true; } return abscissaError_; } Double_t LauDecayTimePdf::generate( const LauKinematics* kinematics ) { // generateError SHOULD have been called before this // function but will call it here just to make sure // (has no effect if has already been called) abscissaError_ = this->generateError(); Double_t abscissa{0.0}; switch ( type_ ) { case LauDecayTime::FuncType::ExpTrig : case LauDecayTime::FuncType::ExpHypTrig : std::cerr << "ERROR in LauDecayTimePdf::generate : direct generation does not make sense for ExpTrig and ExpHypTrig types" << std::endl; return abscissa; case LauDecayTime::FuncType::Hist : { const LauFitData genAbscissa { pdfHist_ -> generate( kinematics ) }; abscissa = genAbscissa.at(this->varName()); break; } case LauDecayTime::FuncType::Exp : { do { abscissa = LauRandom::randomFun()->Uniform(minAbscissa_,maxAbscissa_); this->calcLikelihoodInfo( abscissa, abscissaError_ ); const Double_t pdfVal { this->getExpTerm() * this->getEffiTerm() }; const Double_t maxVal { this->getMaxHeight(kinematics) }; if ( pdfVal > maxVal ) { std::cerr << "WARNING in LauDecayTimePdf::generate : PDF value = " << pdfVal << " is larger than the maximum PDF height " << maxVal << "\n"; std::cerr << " : This occurs for the abscissa = " << abscissa << "\n"; std::cerr << " : This really should not happen!!" << std::endl; } if ( LauRandom::randomFun()->Rndm() <= pdfVal/maxVal ) { break; } } while ( true ); break; } case LauDecayTime::FuncType::Delta : { // TODO std::cerr << "WARNING in LauDecayTimePdf::generate : generation of Delta case not currently implemented" << std::endl; break; } case LauDecayTime::FuncType::DeltaExp : { // TODO std::cerr << "WARNING in LauDecayTimePdf::generate : generation of DeltaExp case not currently implemented" << std::endl; break; } } // mark that we need a new error to be generated next time abscissaErrorGenerated_ = false; return abscissa; } Double_t LauDecayTimePdf::getMaxHeight(const LauKinematics* /*kinematics*/) { if ( heightUpToDate_ ) { return maxHeight_; } // TODO this is brute force - can we do this in a more refined way? // Scan in small increments across the space to find the maximum height const std::size_t nPoints { 1000 }; const Double_t range { maxAbscissa_ - minAbscissa_ }; const Double_t delta { range / nPoints }; maxHeight_ = 0.0; for ( Double_t point {minAbscissa_}; point <= maxAbscissa_; point += delta ) { this->calcLikelihoodInfo(point, abscissaError_); Double_t heightAtPoint { 0.0 }; if ( type_ == LauDecayTime::FuncType::Exp ) { heightAtPoint = this->getExpTerm(); } else { // TODO - implement the Delta and ExpDelta cases std::cerr << "WARNING in LauDecayTimePdf::getMaxHeight : only Exp case currently implemented" << std::endl; } heightAtPoint *= this->getEffiTerm(); if ( heightAtPoint > maxHeight_ ) { maxHeight_ = heightAtPoint; } } // Mutliply by 120% to be on the safe side maxHeight_ *= 1.2; // Mark the height as being up to date // (unless we're scaling by per-event error) heightUpToDate_ = not ( smear_ and scaleWithPerEventError_ ); return maxHeight_; } void LauDecayTimePdf::setBinnedEfficiency( std::unique_ptr effModel ) { if ( not effModel ) { std::cerr << "WARNING in LauDecayTimePdf::setBinnedEfficiency : supplied binned efficiency model pointer is null" << std::endl; return; } // Create the approptiate integrator // NB need to use effModel here since it needs to be of concrete type if ( smear_ ) { integrator_ = std::make_unique( minAbscissa_, maxAbscissa_, *physicsModel_, *effModel, *resolutionModel_ ); } else { integrator_ = std::make_unique( minAbscissa_, maxAbscissa_, *physicsModel_, *effModel ); } // Store the efficiency model (as a pointer to base) efficiencyModel_ = std::move(effModel); } void LauDecayTimePdf::updatePulls() { for ( std::vector::iterator iter = params_.begin(); iter != params_.end(); ++iter ) { std::vector params = (*iter)->getPars(); for (std::vector::iterator params_iter = params.begin(); params_iter != params.end(); ++params_iter ) { if (!(*iter)->fixed()) { (*params_iter)->updatePull(); } } } } void LauDecayTimePdf::propagateParUpdates() { // If none of the parameters are floating there's nothing to do if ( not anythingFloating_ ) { return; } physicsModel_->propagateParUpdates(); physicsParChanged_ = physicsModel_->anythingChanged(); if ( resolutionModel_ ) { resolutionModel_->propagateParUpdates(); resoParChanged_ = resolutionModel_->anythingChanged(); } efficiencyModel_->propagateParUpdates(); effiParChanged_ = efficiencyModel_->anythingChanged(); // If nothing has changed, there's nothing to do anythingChanged_ = physicsParChanged_ or resoParChanged_ or effiParChanged_; if ( not anythingChanged_ ) { return; } calculator_->propagateParUpdates(); integrator_->propagateParUpdates(); // Otherwise we need to update the cache this->updateCache(); }