diff --git a/analyses/pluginMC/MC_Cent_pPb_Eta.plot b/analyses/pluginMC/MC_Cent_pPb_Eta.plot --- a/analyses/pluginMC/MC_Cent_pPb_Eta.plot +++ b/analyses/pluginMC/MC_Cent_pPb_Eta.plot @@ -1,55 +1,55 @@ -# BEGIN PLOT /MC_Centrality/d02-x01-y01 +# BEGIN PLOT /MC_Cent_pPb_Eta/d02-x01-y01 Title=Charged particle density. Centrality 60-90\% YLabel=$(1/N_{evt}) \; dN_{ch}/d\eta$ LogY=0 YMax=85 # END PLOT -# BEGIN PLOT /MC_Centrality/d02-x01-y02 +# BEGIN PLOT /MC_Cent_pPb_Eta/d02-x01-y02 Title=Charged particle density. Centrality 40-60\% YLabel=$(1/N_{evt}) \; dN_{ch}/d\eta$ XLabel=$\eta$ LogY=0 YMax=85 # END PLOT -# BEGIN PLOT /MC_Centrality/d02-x01-y03 +# BEGIN PLOT /MC_Cent_pPb_Eta/d02-x01-y03 Title=Charged particle density. Centrality 30-40\% YLabel=$(1/N_{evt}) \; dN_{ch}/d\eta$ XLabel=$\eta$ LogY=0 YMax=85 # END PLOT -# BEGIN PLOT /MC_Centrality/d02-x01-y04 +# BEGIN PLOT /MC_Cent_pPb_Eta/d02-x01-y04 Title=Charged particle density. Centrality 20-30\% YLabel=$(1/N_{evt}) \; dN_{ch}/d\eta$ XLabel=$\eta$ LogY=0 YMax=85 # END PLOT -# BEGIN PLOT /MC_Centrality/d02-x01-y05 +# BEGIN PLOT /MC_Cent_pPb_Eta/d02-x01-y05 Title=Charged particle density. Centrality 10-20\% YLabel=$(1/N_{evt}) \; dN_{ch}/d\eta$ XLabel=$\eta$ LogY=0 YMax=85 # END PLOT -# BEGIN PLOT /MC_Centrality/d02-x01-y06 +# BEGIN PLOT /MC_Cent_pPb_Eta/d02-x01-y06 Title=Charged particle density. Centrality 5-10\% YLabel=$(1/N_{evt}) \; dN_{ch}/d\eta$ XLabel=$\eta$ LogY=0 YMax=85 # END PLOT -# BEGIN PLOT /MC_Centrality/d02-x01-y07 +# BEGIN PLOT /MC_Cent_pPb_Eta/d02-x01-y07 Title=Charged particle density. Centrality 1-5\% YLabel=$(1/N_{evt}) \; dN_{ch}/d\eta$ XLabel=$\eta$ LogY=0 YMax=85 # END PLOT -# BEGIN PLOT /MC_Centrality/d02-x01-y08 +# BEGIN PLOT /MC_Cent_pPb_Eta/d02-x01-y08 Title=Charged particle density. Centrality 0-1\% XLabel=$\eta$ YLabel=$(1/N_{evt}) \; dN_{ch}/d\eta$ LogY=0 YMax=85 # END PLOT diff --git a/src/Core/AnalysisHandler.cc b/src/Core/AnalysisHandler.cc --- a/src/Core/AnalysisHandler.cc +++ b/src/Core/AnalysisHandler.cc @@ -1,408 +1,556 @@ // -*- C++ -*- #include "Rivet/Config/RivetCommon.hh" #include "Rivet/AnalysisHandler.hh" #include "Rivet/Analysis.hh" #include "Rivet/Tools/ParticleName.hh" #include "Rivet/Tools/BeamConstraint.hh" #include "Rivet/Tools/Logging.hh" #include "Rivet/Projections/Beam.hh" #include "YODA/IO.h" namespace Rivet { AnalysisHandler::AnalysisHandler(const string& runname) : _runname(runname), _eventcounter("/_EVTCOUNT"), _xs(NAN), _xserr(NAN), - _initialised(false), _ignoreBeams(false) + _initialised(false), _ignoreBeams(false), _dumpPeriod(0), _dumping(false) { } AnalysisHandler::~AnalysisHandler() { } Log& AnalysisHandler::getLog() const { return Log::getLog("Rivet.Analysis.Handler"); } void AnalysisHandler::init(const GenEvent& ge) { if (_initialised) throw UserError("AnalysisHandler::init has already been called: cannot re-initialize!"); setRunBeams(Rivet::beams(ge)); MSG_DEBUG("Initialising the analysis handler"); _eventcounter.reset(); // Check that analyses are beam-compatible, and remove those that aren't const size_t num_anas_requested = analysisNames().size(); vector anamestodelete; for (const AnaHandle a : _analyses) { if (!_ignoreBeams && !a->isCompatible(beams())) { //MSG_DEBUG(a->name() << " requires beams " << a->requiredBeams() << " @ " << a->requiredEnergies() << " GeV"); anamestodelete.push_back(a->name()); } } for (const string& aname : anamestodelete) { MSG_WARNING("Analysis '" << aname << "' is incompatible with the provided beams: removing"); removeAnalysis(aname); } if (num_anas_requested > 0 && analysisNames().empty()) { cerr << "All analyses were incompatible with the first event's beams\n" << "Exiting, since this probably wasn't intentional!" << endl; exit(1); } // Warn if any analysis' status is not unblemished for (const AnaHandle a : analyses()) { if (toUpper(a->status()) == "PRELIMINARY") { MSG_WARNING("Analysis '" << a->name() << "' is preliminary: be careful, it may change and/or be renamed!"); } else if (toUpper(a->status()) == "OBSOLETE") { MSG_WARNING("Analysis '" << a->name() << "' is obsolete: please update!"); } else if (toUpper(a->status()).find("UNVALIDATED") != string::npos) { MSG_WARNING("Analysis '" << a->name() << "' is unvalidated: be careful, it may be broken!"); } } // Initialize the remaining analyses for (AnaHandle a : _analyses) { MSG_DEBUG("Initialising analysis: " << a->name()); try { // Allow projection registration in the init phase onwards a->_allowProjReg = true; a->init(); //MSG_DEBUG("Checking consistency of analysis: " << a->name()); //a->checkConsistency(); } catch (const Error& err) { cerr << "Error in " << a->name() << "::init method: " << err.what() << endl; exit(1); } MSG_DEBUG("Done initialising analysis: " << a->name()); } _initialised = true; MSG_DEBUG("Analysis handler initialised"); } void AnalysisHandler::analyze(const GenEvent& ge) { // Call init with event as template if not already initialised if (!_initialised) init(ge); assert(_initialised); // Ensure that beam details match those from the first event (if we're checking beams) if ( !_ignoreBeams ) { const PdgIdPair beams = Rivet::beamIds(ge); const double sqrts = Rivet::sqrtS(ge); if (!compatible(beams, _beams) || !fuzzyEquals(sqrts, sqrtS())) { cerr << "Event beams mismatch: " << PID::toBeamsString(beams) << " @ " << sqrts/GeV << " GeV" << " vs. first beams " << this->beams() << " @ " << this->sqrtS()/GeV << " GeV" << endl; exit(1); } } // Create the Rivet event wrapper /// @todo Filter/normalize the event here Event event(ge); // Weights /// @todo Drop this / just report first weight when we support multiweight events _eventcounter.fill(event.weight()); MSG_DEBUG("Event #" << _eventcounter.numEntries() << " weight = " << event.weight()); // Cross-section #ifdef HEPMC_HAS_CROSS_SECTION if (ge.cross_section()) { _xs = ge.cross_section()->cross_section(); _xserr = ge.cross_section()->cross_section_error(); } #endif // Run the analyses for (AnaHandle a : _analyses) { MSG_TRACE("About to run analysis " << a->name()); try { a->analyze(event); } catch (const Error& err) { cerr << "Error in " << a->name() << "::analyze method: " << err.what() << endl; exit(1); } MSG_TRACE("Finished running analysis " << a->name()); } + + if ( _dumpPeriod > 0 && numEvents()%_dumpPeriod == 0 ) { + MSG_INFO("Dumping intermediate results to " << _dumpFile << "."); + _dumping = true; + finalize(); + _dumping = false; + writeData(_dumpFile); + } + } void AnalysisHandler::analyze(const GenEvent* ge) { if (ge == nullptr) { MSG_ERROR("AnalysisHandler received null pointer to GenEvent"); //throw Error("AnalysisHandler received null pointer to GenEvent"); } analyze(*ge); } void AnalysisHandler::finalize() { if (!_initialised) return; + + // First we make copies of all analysis objects. + map backupAOs; + for (auto ao : getData(false, true) ) + backupAOs[ao->path()] = AnalysisObjectPtr(ao->newclone()); + + // Now we run the (re-entrant) finalize() functions for all analyses. MSG_INFO("Finalising analyses"); for (AnaHandle a : _analyses) { a->setCrossSection(_xs); try { - a->finalize(); + if ( !_dumping || a->info().reentrant() ) a->finalize(); + else if ( _dumping ) + MSG_INFO("Skipping periodic dump of " << a->name() + << " as it is not declared reentrant."); } catch (const Error& err) { cerr << "Error in " << a->name() << "::finalize method: " << err.what() << endl; exit(1); } } + // Now we copy all analysis objects to the list of finalized + // ones, and restore the value to their original ones. + _finalizedAOs.clear(); + for ( auto ao : getData() ) + _finalizedAOs.push_back(AnalysisObjectPtr(ao->newclone())); + for ( auto ao : getData(false, true) ) { + auto aoit = backupAOs.find(ao->path()); + if ( aoit == backupAOs.end() ) { + AnaHandle ana = analysis(split(ao->path(), "/")[0]); + if ( ana ) ana->removeAnalysisObject(ao->path()); + } else + copyao(aoit->second, ao); + } + // Print out number of events processed const int nevts = _eventcounter.numEntries(); MSG_INFO("Processed " << nevts << " event" << (nevts != 1 ? "s" : "")); // // Delete analyses // MSG_DEBUG("Deleting analyses"); // _analyses.clear(); // Print out MCnet boilerplate cout << endl; cout << "The MCnet usage guidelines apply to Rivet: see http://www.montecarlonet.org/GUIDELINES" << endl; cout << "Please acknowledge plots made with Rivet analyses, and cite arXiv:1003.0694 (http://arxiv.org/abs/1003.0694)" << endl; } AnalysisHandler& AnalysisHandler::addAnalysis(const string& analysisname, std::map pars) { // Make an option handle. std::string parHandle = ""; for (map::iterator par = pars.begin(); par != pars.end(); ++par) { parHandle +=":"; parHandle += par->first + "=" + par->second; } return addAnalysis(analysisname + parHandle); } AnalysisHandler& AnalysisHandler::addAnalysis(const string& analysisname) { // Check for a duplicate analysis /// @todo Might we want to be able to run an analysis twice, with different params? /// Requires avoiding histo tree clashes, i.e. storing the histos on the analysis objects. string ananame = analysisname; vector anaopt = split(analysisname, ":"); if ( anaopt.size() > 1 ) ananame = anaopt[0]; AnaHandle analysis( AnalysisLoader::getAnalysis(ananame) ); if (analysis.get() != 0) { // < Check for null analysis. MSG_DEBUG("Adding analysis '" << analysisname << "'"); map opts; for ( int i = 1, N = anaopt.size(); i < N; ++i ) { vector opt = split(anaopt[i], "="); if ( opt.size() != 2 ) { MSG_WARNING("Error in option specification. Skipping analysis " << analysisname); return *this; } if ( !analysis->info().validOption(opt[0], opt[1]) ) { MSG_WARNING("Cannot set option '" << opt[0] << "' to '" << opt[1] << "'. Skipping analysis " << analysisname); return *this; } opts[opt[0]] = opt[1]; } for ( auto opt: opts) { analysis->_options[opt.first] = opt.second; analysis->_optstring += ":" + opt.first + "=" + opt.second; } for (const AnaHandle& a : _analyses) { if (a->name() == analysis->name() ) { MSG_WARNING("Analysis '" << analysisname << "' already registered: skipping duplicate"); return *this; } } analysis->_analysishandler = this; _analyses.insert(analysis); } else { MSG_WARNING("Analysis '" << analysisname << "' not found."); } // MSG_WARNING(_analyses.size()); // for (const AnaHandle& a : _analyses) MSG_WARNING(a->name()); return *this; } AnalysisHandler& AnalysisHandler::removeAnalysis(const string& analysisname) { std::shared_ptr toremove; for (const AnaHandle a : _analyses) { if (a->name() == analysisname) { toremove = a; break; } } if (toremove.get() != 0) { MSG_DEBUG("Removing analysis '" << analysisname << "'"); _analyses.erase(toremove); } return *this; } ///////////////////////////// -/* Old addData which did not add orphaned preloads. + void AnalysisHandler::addData(const std::vector& aos) { for (const AnalysisObjectPtr ao : aos) { - const string path = ao->path(); + string path = ao->path(); + if ( path.substr(0, 5) != "/RAW/" ) { + _orphanedPreloads.push_back(ao); + continue; + } + + path = path.substr(4); + ao->setPath(path); if (path.size() > 1) { // path > "/" try { const string ananame = split(path, "/")[0]; AnaHandle a = analysis(ananame); a->addAnalysisObject(ao); /// @todo Need to statistically merge... } catch (const Error& e) { - MSG_WARNING(e.what()); + MSG_TRACE("Adding analysis object " << path << + " to the list of orphans."); + _orphanedPreloads.push_back(ao); } } } } -*/ - void AnalysisHandler::addData(const std::vector& aos) { - for (const AnalysisObjectPtr ao : aos) { - const string path = ao->path(); - if (path.size() > 1) { // path > "/" - try { - const string ananame = split(path, "/")[0]; - MSG_TRACE("Preloading analysis object " << path); - AnaHandle a = analysis(ananame); - a->addAnalysisObject(ao); /// @todo Need to statistically merge... - } catch (const Error& e) { - MSG_TRACE("Adding analysis object " << path << - " to the list of orphans."); - _orphanedPreloads.push_back(ao); + + void AnalysisHandler:: + mergeYodas(const vector & aofiles, bool equiv) { + vector< vector > aosv; + vector xsecs; + vector xsecerrs; + vector sows; + set ananames; + + // First scan all files and extract analysis objects and add the + // corresponding anayses.. + for ( auto file : aofiles ) { + Scatter1DPtr xsec; + CounterPtr sow; + + // For each file make sure that cross section and sum-of-weights + // objects are present and stor all RAW ones in a vector; + vector aos; + _eventcounter.reset(); + try { + /// @todo Use new YODA SFINAE to fill the smart ptr vector directly + vector aos_raw; + YODA::read(file, aos_raw); + for (AnalysisObject* aor : aos_raw) { + AnalysisObjectPtr ao = AnalysisObjectPtr(aor); + if ( ao->path().substr(0, 5) != "/RAW/" ) continue; + ao->setPath(ao->path().substr(4)); + if ( ao->path() == "/_XSEC" ) + xsec = dynamic_pointer_cast(ao); + else if ( ao->path() == "/_EVTCOUNT" ) + sow = dynamic_pointer_cast(ao); + else { + string ananame = split(ao->path(), "/")[0]; + if ( ananames.insert(ananame).second ) addAnalysis(ananame); + aos.push_back(ao); + } } + if ( !xsec || !sow ) { + MSG_ERROR( "Error in AnalysisHandler::mergeYodas: The file " << file + << " did not contain weights and cross section info."); + exit(1); + } + xsecs.push_back(xsec->point(0).x()); + xsecerrs.push_back(sqr(xsec->point(0).xErrAvg())); + _eventcounter += *sow; + aosv.push_back(aos); + } catch (...) { //< YODA::ReadError& + throw UserError("Unexpected error in reading file: " + file); } } + + // Now calculate the scale to be applied for all bins in a file + // and get the common cross section and sum of weights. + _xs = _xserr = 0.0; + for ( int i = 0, N = sows.size(); i < N; ++i ) { + double effnent = sows[i]->effNumEntries(); + _xs += (equiv? effnent: 1.0)*xsecs[i]; + _xserr += (equiv? sqr(effnent): 1.0)*xsecerrs[i]; + } + + vector scales(sows.size(), 1.0); + if ( equiv ) { + _xs /= _eventcounter.effNumEntries(); + _xserr = sqrt(_xserr)/_eventcounter.effNumEntries(); + } else { + _xserr = sqrt(_xserr); + for ( int i = 0, N = sows.size(); i < N; ++i ) + scales[i] = (_eventcounter.sumW()/sows[i]->sumW())*(xsecs[i]/_xs); + } + + // Initialize the analyses allowing them to book analysis objects. + for (AnaHandle a : _analyses) { + MSG_DEBUG("Initialising analysis: " << a->name()); + if ( !a->info().reentrant() ) + MSG_WARNING("Analysis " << a->name() << " has not been validated to have " + << "a reentrant finalize method. The result is unpredictable."); + try { + // Allow projection registration in the init phase onwards + a->_allowProjReg = true; + a->init(); + //MSG_DEBUG("Checking consistency of analysis: " << a->name()); + //a->checkConsistency(); + } catch (const Error& err) { + cerr << "Error in " << a->name() << "::init method: " << err.what() << endl; + exit(1); + } + MSG_DEBUG("Done initialising analysis: " << a->name()); + } + _initialised = true; + + // Get a list of all anaysis objects to handle. + map current; + for ( auto ao : getData(false, true) ) current[ao->path()] = ao; + + // Go through all objects to be merged and add them to current + // after appropriate scaling. + for ( int i = 0, N = aosv.size(); i < N; ++i) + for ( auto ao : aosv[i] ) { + if ( ao->path() == "/_XSEC" || ao->path() == "_EVTCOUNT" ) continue; + auto aoit = current.find(ao->path()); + if ( aoit == current.end() ) { + MSG_WARNING("" << ao->path() << " was not properly booked."); + continue; + } + if ( !addaos(aoit->second, ao, scales[i]) ) + MSG_WARNING("Cannot merge objects with path " << ao->path() + <<" of type " << ao->annotation("Type") ); + } + + // Now we can simply finalize() the analysis, leaving the + // controlling program to write it out some yoda-file. + finalize(); + } + void AnalysisHandler::readData(const string& filename) { vector aos; try { /// @todo Use new YODA SFINAE to fill the smart ptr vector directly vector aos_raw; YODA::read(filename, aos_raw); for (AnalysisObject* aor : aos_raw) aos.push_back(AnalysisObjectPtr(aor)); } catch (...) { //< YODA::ReadError& throw UserError("Unexpected error in reading file: " + filename); } if (!aos.empty()) addData(aos); } - vector AnalysisHandler::getData(bool includeorphans) const { + vector AnalysisHandler:: + getData(bool includeorphans, bool includetmps) const { vector rtn; // Event counter rtn.push_back( make_shared(_eventcounter) ); // Cross-section + err as scatter YODA::Scatter1D::Points pts; pts.insert(YODA::Point1D(_xs, _xserr)); rtn.push_back( make_shared(pts, "/_XSEC") ); // Analysis histograms for (const AnaHandle a : analyses()) { vector aos = a->analysisObjects(); // MSG_WARNING(a->name() << " " << aos.size()); for (const AnalysisObjectPtr ao : aos) { // Exclude paths from final write-out if they contain a "TMP" layer (i.e. matching "/TMP/") /// @todo This needs to be much more nuanced for re-entrant histogramming - if (ao->path().find("/TMP/") != string::npos) continue; + if ( !includetmps && ao->path().find("/TMP/" ) != string::npos) continue; rtn.push_back(ao); } } // Sort histograms alphanumerically by path before write-out sort(rtn.begin(), rtn.end(), [](AnalysisObjectPtr a, AnalysisObjectPtr b) {return a->path() < b->path();}); if ( includeorphans ) rtn.insert(rtn.end(), _orphanedPreloads.begin(), _orphanedPreloads.end()); return rtn; } void AnalysisHandler::writeData(const string& filename) const { - const vector aos = getData(); + vector out = _finalizedAOs; + out.reserve(2*out.size()); + vector aos = getData(); + for ( auto ao : aos ) { + ao = AnalysisObjectPtr(ao->newclone()); + ao->setPath("/RAW" + ao->path()); + out.push_back(ao); + } + try { - YODA::write(filename, aos.begin(), aos.end()); + YODA::write(filename, out.begin(), out.end()); } catch (...) { //< YODA::WriteError& throw UserError("Unexpected error in writing file: " + filename); } } std::vector AnalysisHandler::analysisNames() const { std::vector rtn; for (AnaHandle a : _analyses) { rtn.push_back(a->name()); } return rtn; } const AnaHandle AnalysisHandler::analysis(const std::string& analysisname) const { for (const AnaHandle a : analyses()) if (a->name() == analysisname) return a; throw Error("No analysis named '" + analysisname + "' registered in AnalysisHandler"); } AnalysisHandler& AnalysisHandler::addAnalyses(const std::vector& analysisnames) { for (const string& aname : analysisnames) { //MSG_DEBUG("Adding analysis '" << aname << "'"); addAnalysis(aname); } return *this; } AnalysisHandler& AnalysisHandler::removeAnalyses(const std::vector& analysisnames) { for (const string& aname : analysisnames) { removeAnalysis(aname); } return *this; } bool AnalysisHandler::needCrossSection() const { bool rtn = false; for (const AnaHandle a : _analyses) { if (!rtn) rtn = a->needsCrossSection(); if (rtn) break; } return rtn; } AnalysisHandler& AnalysisHandler::setCrossSection(double xs) { _xs = xs; return *this; } bool AnalysisHandler::hasCrossSection() const { return (!std::isnan(crossSection())); } AnalysisHandler& AnalysisHandler::addAnalysis(Analysis* analysis) { analysis->_analysishandler = this; _analyses.insert(AnaHandle(analysis)); return *this; } PdgIdPair AnalysisHandler::beamIds() const { return Rivet::beamIds(beams()); } double AnalysisHandler::sqrtS() const { return Rivet::sqrtS(beams()); } void AnalysisHandler::setIgnoreBeams(bool ignore) { _ignoreBeams=ignore; } }