diff --git a/src/LauDecayTimeResolution.cc b/src/LauDecayTimeResolution.cc index 7ac1887..b8396b3 100644 --- a/src/LauDecayTimeResolution.cc +++ b/src/LauDecayTimeResolution.cc @@ -1,372 +1,374 @@ /* Copyright 2021 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 LauDecayTimeResolution.cc \brief File containing implementation of LauDecayTimeResolution class. */ #include #include #include #include #include "TString.h" #include "TSystem.h" #include "LauAbsRValue.hh" #include "LauDecayTimeResolution.hh" ClassImp(LauDecayTimeResolution); LauDecayTimeResolution::LauDecayTimeResolution( const std::size_t nGauss, std::vector> resolutionParams, const bool scale ) : nGauss_{nGauss}, scaleWithPerEventError_{scale}, scaleMeans_(nGauss_,scale), scaleWidths_(nGauss_,scale), scalingType_{scaleWithPerEventError_ ? ScalingType::Global : ScalingType::None}, paramsOwned_{std::move(resolutionParams)} { this->checkSetup(); } LauDecayTimeResolution::LauDecayTimeResolution( const std::size_t nGauss, std::vector> resolutionParams, const std::vector& scale ) : nGauss_{nGauss}, scaleWithPerEventError_( std::accumulate( scale.begin(), scale.end(), false, std::logical_or() ) ), scaleMeans_{scale}, scaleWidths_{scale}, scalingType_{scaleWithPerEventError_ ? ScalingType::PerGaussian : ScalingType::None}, paramsOwned_{std::move(resolutionParams)} { this->checkSetup(); } LauDecayTimeResolution::LauDecayTimeResolution( const std::size_t nGauss, std::vector> resolutionParams, const std::vector& scaleMeans, const std::vector& scaleWidths ) : nGauss_{nGauss}, scaleWithPerEventError_( std::accumulate( scaleMeans.begin(), scaleMeans.end(), false, std::logical_or() ) || std::accumulate( scaleWidths.begin(), scaleWidths.end(), false, std::logical_or() ) ), scaleMeans_{scaleMeans}, scaleWidths_{scaleWidths}, scalingType_{scaleWithPerEventError_ ? ScalingType::PerParameter : ScalingType::None}, paramsOwned_{std::move(resolutionParams)} { this->checkSetup(); } void LauDecayTimeResolution::resizeVectors() { params_.resize( paramsOwned_.size() ); std::transform( paramsOwned_.begin(), paramsOwned_.end(), params_.begin(), [](std::unique_ptr& par){return par.get();} ); fractions_.assign( nGauss_-1, nullptr ); means_.assign( nGauss_, nullptr ); widths_.assign( nGauss_, nullptr ); fractionVals_.assign( nGauss_, 0.0 ); meanVals_.assign( nGauss_, 0.0 ); widthVals_.assign( nGauss_, 0.0 ); } void LauDecayTimeResolution::checkSetup() { if ( nGauss_ == 0 ) { std::cerr << "ERROR in LauDecayTimeResolution::checkSetup : number of Gaussians is zero!" << std::endl; gSystem->Exit(EXIT_FAILURE); } if ( scaleWithPerEventError_ ) { // if we're scaling by the per-event error, check that the scale vectors are the right length if ( scaleMeans_.size() != nGauss_ || scaleWidths_.size() != nGauss_ ) { std::cerr << "ERROR in LauDecayTimeResolution::checkSetup : number of Gaussians = " << nGauss_ << " but scale vectors are of length " << scaleMeans_.size() << " and " << scaleWidths_.size() << ", cannot continue!" << std::endl; gSystem->Exit(EXIT_FAILURE); } } this->resizeVectors(); const TString meanNameBase{"mean_"}; const TString sigmaNameBase{"sigma_"}; const TString fracNameBase{"frac_"}; bool foundParams{true}; for (std::size_t i{0}; i < nGauss_; ++i) { TString tempName{meanNameBase}; tempName += i; TString tempName2{sigmaNameBase}; tempName2 += i; TString tempName3{fracNameBase}; tempName3 += i; means_[i] = this->findParameter(tempName); foundParams &= (means_[i] != nullptr); widths_[i] = this->findParameter(tempName2); foundParams &= (widths_[i] != nullptr); if (i!=0) { fractions_[i-1] = this->findParameter(tempName3); foundParams &= (fractions_[i-1] != nullptr); } } if ( ! foundParams ) { std::cerr << "ERROR in LauDecayTimeResolution::checkSetup : decay time resolution function with " << nGauss_ << " Gaussians requires:\n"; std::cerr << " - 2 parameters per Gaussian (i): \"mean_i\" and \"sigma_i\"\n"; std::cerr << " - " << nGauss_-1 << " fractions: \"frac_i\", where i!=0" << std::endl; gSystem->Exit(EXIT_FAILURE); } } LauAbsRValue* LauDecayTimeResolution::findParameter(const TString& parName) { for ( LauAbsRValue* par : params_ ) { if ( par && par->name().Contains(parName) ) { return par; } } std::cerr << "ERROR in LauDecayTimeResolution::findParameter : Parameter \"" << parName << "\" not found." << std::endl; return nullptr; } const LauAbsRValue* LauDecayTimeResolution::findParameter(const TString& parName) const { for ( const LauAbsRValue* par : params_ ) { if ( par && par->name().Contains(parName) ) { return par; } } std::cerr << "ERROR in LauDecayTimeResolution::findParameter : Parameter \"" << parName << "\" not found." << std::endl; return nullptr; } void LauDecayTimeResolution::initialise() { anythingFloating_ = false; for ( std::size_t i{0}; i < nGauss_; ++i ) { const bool meanFloating { not means_[i]->fixed() }; const bool widthFloating { not widths_[i]->fixed() }; anythingFloating_ |= (meanFloating or widthFloating); std::cout << "INFO in LauDecayTimeResolution::initialise : mean[" << i << "] floating set to: " << (meanFloating ? "True" : "False") << std::endl; std::cout << "INFO in LauDecayTimeResolution::initialise : width[" << i << "] floating set to: " << (widthFloating ? "True" : "False") << std::endl; if ( i < (nGauss_ - 1) ) { const bool fracFloating { not fractions_[i]->fixed() }; anythingFloating_ |= fracFloating; std::cout << "INFO in LauDecayTimeResolution::initialise : fraction[" << i << "] floating set to: " << (fracFloating ? "True" : "False") << std::endl; } } this->updateParameterCache(); } void LauDecayTimeResolution::updateParameterCache() { static auto assignValue = [](const LauAbsRValue* par){return par->unblindValue();}; std::transform( means_.begin(), means_.end(), meanVals_.begin(), assignValue ); std::transform( widths_.begin(), widths_.end(), widthVals_.begin(), assignValue ); std::transform( fractions_.begin(), fractions_.end(), fractionVals_.begin()+1, assignValue ); fractionVals_[0] = std::accumulate( fractionVals_.begin()+1, fractionVals_.end(), 1.0, std::minus{} ); } void LauDecayTimeResolution::propagateParUpdates() { // If none of the parameters are floating there's nothing to do if ( not anythingFloating_ ) { return; } // Otherwise, determine whether any of the floating parameters have changed and act accordingly static auto checkEquality = [](const LauAbsRValue* par, const Double_t cacheVal){return par->unblindValue() == cacheVal;}; anythingChanged_ = false; anythingChanged_ |= not std::equal( means_.begin(), means_.end(), meanVals_.begin(), checkEquality ); anythingChanged_ |= not std::equal( widths_.begin(), widths_.end(), widthVals_.begin(), checkEquality ); anythingChanged_ |= not std::equal( fractions_.begin(), fractions_.end(), fractionVals_.begin()+1, checkEquality ); if ( anythingChanged_ ) { this->updateParameterCache(); } } std::map> LauDecayTimeResolution::readFromJson( const TString& fileName, const TString& elementName) { // NB deliberately not using uniform initialisation here because of this issue: // https://json.nlohmann.me/home/faq/#brace-initialization-yields-arrays const nlohmann::json j = LauJsonTools::readJsonFile( fileName.Data(), elementName.Data(), LauJsonTools::JsonType::Object ); if ( j.is_null() ) { if ( elementName != "" ) { std::cerr << "ERROR in LauDecayTimeResolution::readFromJson : unable to retrieve JSON object from element \"" << elementName << "\" in file \"" << fileName << "\"" << std::endl; } else { std::cerr << "ERROR in LauDecayTimeResolution::readFromJson : unable to retrieve JSON object from root element of file \"" << fileName << "\"" << std::endl; } return {}; } return readFromJson( j ); } std::map> LauDecayTimeResolution::readFromJson( const nlohmann::json& j ) { using JsonType = LauJsonTools::JsonType; // the object should have the following elements: // - an array of parameter objects // - an object for the signal model settings // - an array of background model settings const std::vector mandatoryElements { std::make_pair("parameters", JsonType::Array), std::make_pair("signalModel", JsonType::Object), std::make_pair("backgroundModels", JsonType::Array) }; if ( ! LauJsonTools::checkObjectElements( j, mandatoryElements ) ) { std::cerr << "ERROR in LauDecayTimeResolution::readFromJson : JSON object does not contain required elements: \"parameters\" (array), \"signalModel\" (object) and \"backgroundModels\" (array)" << std::endl; return {}; } auto parameters { LauAbsRValue::readFromJson( j.at("parameters") ) }; auto sigModel { readResolutionModelFromJson( j.at("signalModel"), parameters ) }; if ( ! sigModel ) { return {}; } auto backgroundModels { readBackgroundModelsFromJson( j.at("backgroundModels"), parameters ) }; if ( backgroundModels.size() != j.at("backgroundModels").size() ) { return {}; } backgroundModels.insert( std::make_pair( "signal", std::move(sigModel) ) ); return backgroundModels; } std::unique_ptr LauDecayTimeResolution::readResolutionModelFromJson( const nlohmann::json& j, std::vector>& parameters ) { using JsonType = LauJsonTools::JsonType; // the object should have the following elements: // - an unsigned integer specifying the number of Gaussians in the model // - a string representing the LauDecayTimeResolution::ScalingType function type // - an array of strings specifying the parameters we need const std::vector mandatoryElements { std::make_pair("nGauss", JsonType::Number_Unsigned), std::make_pair("scalingType", JsonType::String), std::make_pair("parameters", JsonType::Array) }; if ( ! LauJsonTools::checkObjectElements( j, mandatoryElements ) ) { std::cerr << "ERROR in LauDecayTimeResolution::readResolutionModelFromJson : JSON object from \"signalModel\" element does not contain required elements: \"nGauss\" (unsigned), \"scalingType\" (string) and \"parameters\" (array)" << std::endl; return {}; } auto nGauss { LauJsonTools::getValue( j, "nGauss" ) }; auto scalingType { LauJsonTools::getValue( j, "scalingType" ) }; auto parNames { LauJsonTools::getValue>( j, "parameters" ) }; std::vector> params; params.reserve( parNames.size() ); for ( const auto& name : parNames ) { auto iter { std::find_if( parameters.begin(), parameters.end(), [&name]( std::unique_ptr& par ){ return par->name() == name; } ) }; if ( iter == parameters.end() ) { const TString errMsg {"ERROR in LauDecayTimeResolution::readResolutionModelFromJson : Cannot locate parameter \"" + name + "\""}; throw LauJsonTools::InvalidJson{errMsg.Data()}; } params.emplace_back( std::move(*iter) ); parameters.erase(iter); } switch ( scalingType ) { case ScalingType::None : return std::make_unique( nGauss, std::move(params), false ); case ScalingType::Global : return std::make_unique( nGauss, std::move(params), true ); case ScalingType::PerGaussian : return std::make_unique( nGauss, std::move(params), LauJsonTools::getValue>( j, "scale" ) ); case ScalingType::PerParameter : return std::make_unique( nGauss, std::move(params), LauJsonTools::getValue>( j, "scaleMeans" ), LauJsonTools::getValue>( j, "scaleWidths" ) ); } // we shouldn't ever reach here return {}; } std::map> LauDecayTimeResolution::readBackgroundModelsFromJson( const nlohmann::json& j, std::vector>& parameters ) { using LauJsonTools::JsonType; using LauJsonTools::checkObjectElements; using LauJsonTools::getValue; // each object in the array should have the following elements: // - a string for the name of the background component // - an object defining the model const std::vector mandatoryElements { std::make_pair("name", JsonType::String), std::make_pair("model", JsonType::Object) }; for ( const auto& comp : j ) { if ( ! checkObjectElements( comp, mandatoryElements ) ) { std::cerr << "ERROR in LauDecayTimeResolution::readBackgroundModelsFromJson : JSON object in \"backgroundModels\" array does not contain required elements: \"name\" (string) and \"model\" (object)" << std::endl; return {}; } } std::map> models; for ( const auto& comp : j ) { auto name { getValue( comp, "name" ) }; models[name] = readResolutionModelFromJson( comp.at("model"), parameters ); } return models; } void LauDecayTimeResolution::serialiseToJson( nlohmann::json& model, nlohmann::json& parameters ) const { using nlohmann::json; model["nGauss"] = nGauss_; model["scalingType"] = scalingType_; json& parNames = model["parameters"] = json::array(); for ( auto& par : params_ ) { parNames.push_back( par->name() ); parameters.push_back( *par ); } switch ( scalingType_ ) { case ScalingType::None : case ScalingType::Global : break; case ScalingType::PerGaussian : model["scale"] = scaleMeans_; + break; case ScalingType::PerParameter : model["scaleMeans"] = scaleMeans_; model["scaleWidths"] = scaleWidths_; + break; } }