Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
TSModelFactory.cxx 27.47 KiB
#include <iostream>
#include <fstream>
#include <sstream>
#include <cstdlib>
#include <vector>
#include <iterator>
#include <algorithm>

#include "SFramework/TSModelFactory.h"
#include "RooStats/HistFactory/PiecewiseInterpolation.h"
#include "RooStats/HistFactory/FlexibleInterpVar.h"
#include "RooStats/ModelConfig.h"
#include "RooRealVar.h"
#include "RooRealSumPdf.h"
#include "QFramework/TQStringUtils.h"
#include "QFramework/TQTaggable.h"
#include "QFramework/TQIterator.h"
#include "QFramework/TQPathManager.h"
#include "QFramework/TQUtils.h"
#include "TSystem.h"
#include "TMath.h"
#include "TFile.h"

#ifdef __cpp_generic_lambdas
#if __cpp_generic_lambdas >= 201304
#define HAS_GENERIC_LAMBDAS
#endif
#endif

#include "SFramework/TSIterator.h"

using namespace RooStats::HistFactory;

ClassImp(TSModelFactory)

#ifdef HAS_GENERIC_LAMBDAS    
namespace {
  namespace SFINAE {
    // SFINAE test
    // https://stackoverflow.com/questions/257288/is-it-possible-to-write-a-template-to-check-for-a-functions-existence
    
    class true_val    {char dummy[1];};
    class false_val   {char dummy[2];};

    template <typename T>
    class hasStackLabel {
    public:
      template <typename C> static true_val    test(decltype(&C::SetStackLabel));
      template <typename C> static false_val   test(...);    

      enum { value = sizeof(test<T>(0)) == sizeof(true_val) };
    };

    // static_if
    //https://stackoverflow.com/questions/37617677/implementing-a-compile-time-static-if-logic-for-different-string-types-in-a-co
    template <typename T, typename F>          auto static_if(std::true_type, T t, F /*f*/) { return t; }
    template <typename T, typename F>          auto static_if(std::false_type, T /*t*/, F f) { return f; }
    template <bool B, typename T, typename F>  auto static_if(T t, F f) { return static_if(std::integral_constant<bool, B>{}, t, f); }
    template <bool B, typename T>              auto static_if(T t) { return static_if(std::integral_constant<bool, B>{}, t, [](auto&&...){}); }
    
    template <class C> inline void trySetStackLabel(C& staterr, const char* stackLabel){
      static_if<hasStackLabel<C>::value>
        ([&](auto& s) {
           s.SetStackLabel(stackLabel);
         })
        (staterr);
    }
  }
}
#endif

//__________________________________________________________________________________|___________

TSModelFactory::TSModelFactory() {

  fShowInfo  = false;
  fShowError  = true;
  fShowWarn  = false;
}


//__________________________________________________________________________________|___________

TSModelFactory::~TSModelFactory() {
}


//__________________________________________________________________________________|___________

void TSModelFactory::info(TString message) {
  if (fShowInfo) {
    std::cout << "SFramework/TSModelFactory: " << message.Data() << std::endl;
  }
}


//__________________________________________________________________________________|___________

void TSModelFactory::error(TString message) {
  if (fShowError) {
    std::cout << "SFramework/TSModelFactory: " << TQStringUtils::makeBoldRed(TString("ERROR: ") + message) << std::endl;
  }
}


//__________________________________________________________________________________|___________

void TSModelFactory::warn(TString message) {
  if (fShowWarn) {
    std::cout << "SFramework/TSModelFactory: " << TQStringUtils::makeBoldYellow(TString("WARNING: ") + message) << std::endl;
  }
}


//__________________________________________________________________________________|___________

Bool_t TSModelFactory::createXML(TQFolder * model, const TString& xmlDir) {

  /* create model */
  RooStats::HistFactory::Measurement * ms = createMeasurement(model);

  /* write XML */
  if (ms) {
    Bool_t success = createXML(ms, xmlDir, model->getTagStringDefault(".HistogramFile", TQFolder::concatPaths(xmlDir,"histograms.root")));
    delete ms;
    return success;
  } else {
    return false;
  }
}

//__________________________________________________________________________________|___________

Bool_t TSModelFactory::writeHistograms(TQFolder* model, const TString& hfname){
  TQFolder * histos = model->getFolder(".Histograms");
  if (!histos) return false;
  /* replace parameter in filenames */
  TString histosFilename = model->replaceInText(hfname);

  histosFilename = TQPathManager::getPathManager()->getTargetPath(histosFilename);
  /* make sure directory exists */
  if (!TQUtils::ensureDirectoryForFile(histosFilename)) return false;

  TFile * histosFile = TFile::Open(histosFilename, "RECREATE", histosFilename.Data());
  if (!histosFile || !histosFile->IsOpen()) {
    if (histosFile) {
      delete histosFile;
    }
    return false;
  }
    
  TString targetName = TString::Format("%s_histograms", model->GetName());
  
  model->setTagString(".HistogramFile", histosFilename);
  model->setTagString(".HistogramPathPrefix", targetName);
  
  // delete existing object in file with same name
  TDirectory* target = histosFile->mkdir(targetName);
  TQFolder* nominal = histos->getFolder("Nominal");
  if(nominal){
    nominal->writeDirectory(target);
  } else {
    return false;
  }
  TQFolder* shapesysts = histos->getFolder("Systematics");
  if(shapesysts){
    shapesysts->writeDirectory(target);
  }
  histosFile->Write();
  histosFile->Close();
  delete histosFile;
  return true;
}

//__________________________________________________________________________________|___________

Bool_t TSModelFactory::createXML(RooStats::HistFactory::Measurement * measurement, const TString& xmlDir, const TString& histosFile) {

  /* write XML */
  if (measurement) {
    measurement->PrintXML(xmlDir.Data());

    // now, fix some stuff inplace because HistFactory is buggy
    char* rootsys = std::getenv("ROOTSYS");
    TString s = "cp ";
    s.Append(TQFolder::concatPaths(rootsys,"etc","HistFactorySchema.dtd"));
    s.Append(" ");
    s.Append(xmlDir);
    s.Append(+"/");
    info(s);
    TList* l = TQUtils::execute(s);
    delete l;
    l = TQUtils::execute ("sed -i -e 's|<Data[ ]*HistoName=\"\"[ ]*InputFile=\"\"[ ]*HistoPath=\"\"[ ]*/>||' "+xmlDir+"/*.xml");
    delete l;
    l = TQUtils::execute ("sed -i -e 's|InputFile=\"[^\"]*\"|InputFile=\""+histosFile+"\"|' "+xmlDir+"/*.xml");
    delete l;
    TString cwd = gSystem->pwd();
    TQStringUtils::ensureTrailingText(cwd,"/");
    l = TQUtils::execute ("sed -i -e 's|<Input>./"+cwd+"|<Input>|' "+xmlDir+"/*.xml");
    delete l;
    return true;
  } else {
    return false;
  }
}


//__________________________________________________________________________________|___________

RooStats::HistFactory::Measurement * TSModelFactory::createMeasurement(TQFolder * model) {

  /* stop if model definition is missing */
  if (!model) {
    error("createMeasurement(): No model definition provided");
    return 0;
  }

  fModel = model;

  info("createMeasurement(...): Creating Measurement object...");

  /* create a new Measurement object */
  RooStats::HistFactory::Measurement * measurement = new RooStats::HistFactory::Measurement(model->GetName(), model->getTagStringDefault("Title", model->GetTitle()));
  
  /* set configuration of Measurement object */
  TString outputFilePrefix = model->getTagStringDefault("OutputFilePrefix", model->GetName());
  measurement->SetOutputFilePrefix(outputFilePrefix.Data());
  Bool_t exportOnly = true;
  // @tag:[ExportOnly] If true, no plots or tables are produced but the model is just saved
  if (model->getTagBool("ExportOnly", exportOnly))
    measurement->SetExportOnly(exportOnly);
  auto pois = model->getTagVString("POI");
  std::sort(pois.begin(),pois.end());
  for(auto poi:pois){
    measurement->AddPOI(poi.Data());
  }
  Double_t lumi = 1.;
  if (model->getTagDouble("Lumi", lumi))
    measurement->SetLumi(lumi);
  Double_t lumiRelErr = 0.;
  if (model->getTagDouble("LumiRelErr", lumiRelErr))
    measurement->SetLumiRelErr(lumiRelErr);


  /* loop over and add channels */
  TQIterator itrCh(model->getListOfFolders("?"), "!.*", true);
  while (itrCh.hasNext()) {
    addChannel((TQFolder*)itrCh.readNext(), measurement);
  }

  /* set global parameter settings */
  TQIterator itrParam(model->getListOfFolders("ParamSettings/?"), "!.*", true);
  while (itrParam.hasNext()) {
    TQFolder * folder = (TQFolder*)itrParam.readNext();
    TQTaggable tagsParam(folder);
    
    // constant parameter?
    Bool_t isConst = false;
    if (tagsParam.getTagBool("Const", isConst) && isConst) {
      measurement->AddConstantParam(folder->GetName());
    }

    // constant parameter?
    if (tagsParam.getTagBoolDefault("POI", false)) {
      measurement->AddPOI(folder->GetName());
    }
    
    // constraint term?
    TString constraint;
    if (tagsParam.getTagString("ConstraintTerm", constraint)) {
      if (constraint.CompareTo("Uniform", TString::kIgnoreCase) == 0) {
        measurement->AddUniformSyst(folder->GetName());
      } else if (constraint.CompareTo("Gamma", TString::kIgnoreCase) == 0) {
        measurement->AddGammaSyst(folder->GetName(), 1.);
      } else if (constraint.CompareTo("LogNorm", TString::kIgnoreCase) == 0) {
        measurement->AddLogNormSyst(folder->GetName(), 1.);
      } else if (constraint.CompareTo("Gaussian", TString::kIgnoreCase) != 0) {
        warn(TString::Format("createMeasurement(): Unknown constraint term '%s' "
                             "for parameter '%s'", constraint.Data(), folder->GetName()));
      }
    }
  }

  fModel = 0;

  /* return measurement object */
  return measurement;
}


//__________________________________________________________________________________|___________

Bool_t TSModelFactory::reviseWorkspace(RooWorkspace * workspace, TQFolder * model) {
  /* stop if workspace is missing */
  if (!workspace) {
    error("reviseWorkspace(): No valid workspace provided");
    return false;
  }

  /* stop if model definition is missing */
  if (!model) {
    error("reviseWorkspace(): No model definition provided");
    return false;
  }

  // stop if model config is missing
  RooStats::ModelConfig* mc = dynamic_cast<RooStats::ModelConfig*>(workspace->obj("ModelConfig"));
  if (!mc) {
    error("reviseWorkspace(): No ModelConfig available");
    return false;
  }  

  // set title
  workspace->SetTitle(model->getTagStringDefault("Title", model->GetTitle()));

  // use parameter settings
  TQFolder* paramSettings = model->getFolder("ParamSettings");  
  
  /* check NPs */
  RooArgSet thenps;
  const RooAbsCollection* nps = mc->GetNuisanceParameters();
  if(!nps || nps->getSize() < 1){
    warn("no nuisance parameters set, setting manually");
    RooArgList allVars(workspace->allVars());
    for(auto* item : allVars){
      auto* var = dynamic_cast<RooRealVar*>(item);
      if(!var) continue;
      bool isNP = TQStringUtils::matches(var->GetName(),"alpha_*") || TQStringUtils::matches(var->GetName(),"gamma_stat_*");
      if(paramSettings){
	TQFolder* p = paramSettings->getFolder(var->GetName());
	if(p){
	  p->getTagBool("isNP",isNP);
	}
      }
      if(isNP){
	thenps.add(*var);
      }
    }
  } else {
    thenps.add(*nps);
  }

  /* check POIs */
  auto pois = model->getTagVString("POI");
  RooArgSet thepois;
  if(mc->GetParametersOfInterest()){
    thepois.add(*mc->GetParametersOfInterest());
  }
  for(auto poi:pois){
    for(const auto& var:workspace->allVars()){
      if(TQStringUtils::matches(var->GetName(),poi)){
	thepois.add(*var);
      }
    }
  }

  // set everything
  RooArgSet finalnps;
  for(const auto& np:thenps){
    if(!thepois.find(np->GetName())) finalnps.add(*np);
  }
  mc->SetParametersOfInterest(thepois);
  mc->SetNuisanceParameters(finalnps);
  
  /* set global parameter settings */
  if(paramSettings){
    TQIterator itrParam(paramSettings->getListOfFolders("?"), "!.*", true);
    while (itrParam.hasNext()) {
      TQFolder * folder = (TQFolder*)itrParam.readNext();
      TString name = folder->GetName();
      
      // corresponding variable from workspace
      RooRealVar * var = workspace->var(name.Data());
      if (!var) {
	warn(TString::Format("reviseWorkspace(): Failed to find variable '%s' "
			     "in workspace. Skipping ...", name.Data()));
	continue;
      }
      
      TQTaggable tagsParam(folder);
      
      // title of variable
      TString title;
      if (tagsParam.getTagString("Title", title)) {
	var->SetTitle(title.Data());
      }
      
      // interpolation code?
      Int_t interpCode = 4;
      if (tagsParam.getTagInteger("InterpCode", interpCode)) {
        for(TObject* obj : var->clients()){
	  if (obj->InheritsFrom(FlexibleInterpVar::Class())) {
	    ((FlexibleInterpVar*)obj)->setInterpCode(*var, interpCode);
	  } else if (obj->InheritsFrom(PiecewiseInterpolation::Class())) {
	    ((PiecewiseInterpolation*)obj)->setInterpCode(*var, interpCode);
	  } else {
	    error(TString::Format("reviseWorkspace(): Failed to set interpolation code "
				  "on variable '%s'. Skipping ...", var->GetName()));
	  }
	}
      }

      // description?
      TString desc;
      if(tagsParam.getTagString("Description",desc)){
	var->setAttribute(desc.Data(),true);
      }
      
      std::vector<TString> attributes = tagsParam.getTagVString("Attributes");
      for(const auto& attr:attributes){
	var->setAttribute(attr.Data());
      }
    }
  }
  
  if(model->getTagBoolDefault("isBinned",true)){
    for(RooAbsArg* arg : workspace->components()){
      if (arg->IsA() == RooRealSumPdf::Class()) {
        arg->setAttribute("BinnedLikelihood");
      }
    }
  }

  // check for Lumi parameter
  RooRealVar* lumi = workspace->var("Lumi");
  if(lumi){
    lumi->setConstant(true);
  }
  
  // Save model in workspace: export as text listing ...
  TList * lConfig = model->exportToText(false);
  // ... and put it into workspace (keep workspace independent of analysis code library)
  workspace->import(*lConfig, ".Model", true);

  return true;
}


//__________________________________________________________________________________|___________

Bool_t TSModelFactory::addChannel(TQFolder * definition, RooStats::HistFactory::Measurement * measurement) {

  /* extract channel name */
  TString channelName = definition->GetName();
  if (!TQStringUtils::removeLeadingText(channelName, "Channel."))
    return false;
  
  TString histosfile = definition->getTagStringDefault("~.HistogramFile", "");
  TString histospath = TQFolder::concatPaths(definition->getTagStringDefault("~.HistogramPathPrefix", ""),"Nominal",channelName.Data());
  
  /* create Channel object */
  RooStats::HistFactory::Channel * channel = new Channel(channelName.Data(),histospath.Data());

  /* set statistical error configuration */
  channel->SetStatErrorConfig(
                              definition->getTagDoubleDefault("StatRelErrorThreshold", 0.05),
                              definition->getTagStringDefault("StatConstraintType", "Poisson").Data());

  /* loop over subfolder */
  TQIterator itr(definition->getListOfFolders("?"), "!.*", true);
  while (itr.hasNext()) {
    TQFolder * folder = (TQFolder*)itr.readNext();

    if (addSample(folder, channel)) {
      info(TString::Format("Added as Sample '%s'", folder->GetName()));
    }
  }

  /* add Channel object to Measurement object */
  measurement->AddChannel(*channel); //FIXME: this looks like a memory leak. AddChannel does not take a reference but is called by value. However, we never delete our local "channel" object

  return true;
}


//__________________________________________________________________________________|___________

Bool_t TSModelFactory::addSample(TQFolder * definition, RooStats::HistFactory::Channel * channel) {

  // extract sample name by removing the leading
  // "Sample." (stop if this prefix is not present)
  TString sampleName = definition->GetName();
  if (!TQStringUtils::removeLeadingText(sampleName, "Sample.")) {
    // missing prefix "Sample."
    return false;
  }
  TString sampleType = definition->getTagStringDefault("Type","B");

  /* extract histogram location */
  TString histogramLocation;
  if(!definition->getTagString("Histo",histogramLocation)){
    error(TString::Format("unable to find histogram for '%s'",definition->getPath().Data()));
    return false;
  }
  TString histogramFile = definition->getTagStringDefault("~.HistogramFile","histFactor_tmp.root");
  TString histogramPathPrefix = definition->getTagStringDefault("~.HistogramPathPrefix","");
  
  /* split histogram location */
  TString histoPath = TQFolder::concatPaths(histogramPathPrefix, histogramLocation);
  TString histoName = TQFolder::getPathTail(histoPath);    
  TQStringUtils::ensureTrailingText(histoPath,"/");

  TString histoFilePath = TQPathManager::getPathManager()->getTargetPath(histogramFile).c_str();
  
  if (sampleType != "D"){
    
    /* create Sample object */
    Sample * sample = new Sample(sampleName.Data(),histoName.Data(), histoFilePath.Data(), histoPath.Data());
  
    /* NormalizeByTheory */
    sample->SetNormalizeByTheory(definition->getTagBoolDefault("NormalizeByTheory", true));

    /* StatError ? */
    if (definition->getTagBoolDefault("ActivateStatError", false)) {
      sample->GetStatError().Activate(true);

      #ifdef HAS_GENERIC_LAMBDAS    
      TString stackLabel;
      if(definition->getTagString("StatErrorStackLabel",stackLabel)){
        ::SFINAE::trySetStackLabel(sample->GetStatError(),stackLabel.Data());
      }
      #endif
      
      if (definition->hasTag("StatErrorHisto")) {
        TString statErrorHistoFile;
        TString statErrorHistoName;
        TString statErrorHistoPath;
        splitHistoLocation(definition->getTagStringDefault("StatErrorHisto"),statErrorHistoFile, statErrorHistoName, statErrorHistoPath);
        TString statErrorHistoFilePath = TQPathManager::getPathManager()->getTargetPath(statErrorHistoFile).c_str();
        sample->GetStatError().SetUseHisto(true);
        sample->GetStatError().SetHistoName(statErrorHistoName.Data());
        sample->GetStatError().SetHistoName(statErrorHistoFilePath.Data());
        sample->GetStatError().SetHistoName(statErrorHistoPath.Data());
      } 
    } else {
      sample->GetStatError().Activate(false);
    }
    
    /* loop over subfolders */
    TQIterator itr(definition->getListOfFolders("?"), "!.*", true);
    while (itr.hasNext()) {
      TQFolder * folder = (TQFolder*)itr.readNext();
      TString name = folder->GetName();

      if (addNormFactor(folder, sample)) {
        info(TString::Format("Added as NormFactor '%s'", folder->GetName()));
      } else if (addOverallSys(folder, sample)) {
        info(TString::Format("Added as OverallSys '%s'", folder->GetName()));
      } else if (addHistoSys(folder, sample)) {
        info(TString::Format("Added as HistoSys '%s'", folder->GetName()));
      } else if (addShapeSys(folder, sample)) {
        info(TString::Format("Added as ShapeSys '%s'", folder->GetName()));
      } else if (addShapeFactor(folder, sample)) {
        info(TString::Format("Added as ShapeFactor '%s'", folder->GetName()));
      } else if (addHistoFactor(folder, sample)) {
        info(TString::Format("Added as HistFactor '%s'", folder->GetName()));	
      } else {
        error(TString::Format("Unrecognized element '%s' "
                              "in sample '%s'", name.Data(), sampleName.Data()));
      }
    }

    /* add the Sample object to the Channel object */
    channel->AddSample(*sample);
  } else {
    /* add data */
    TString dataName = definition->getTagStringDefault("dataset", sampleName == "Data" ? "obsData" : sampleName.Data());
    if(dataName == "obsData"){
      channel->SetData(histoName.Data(), histoFilePath.Data(), histoPath.Data());
    } else {
      auto data = RooStats::HistFactory::Data(histoName.Data(), histoFilePath.Data(), histoPath.Data());
      data.SetName(dataName.Data());
      channel->AddAdditionalData(data);
    }
  }

  return true;
}


//__________________________________________________________________________________|___________

Bool_t TSModelFactory::addNormFactor(TQFolder * definition, Sample * sample) {

  /* extract norm factor name */
  TString normFactorName = definition->GetName();
  if (!TQStringUtils::removeLeadingText(normFactorName, "NormFactor.")) {
    // missing prefix "NormFactor."
    return false;
  }

  /* add norm factor */
  sample->AddNormFactor(normFactorName.Data(),
                        definition->getTagDoubleDefault("Val", 1.),
                        definition->getTagDoubleDefault("Low", 1.),
                        definition->getTagDoubleDefault("High", 1.));

  TQFolder * paramSettings = fModel->getFolder(TQFolder::concatPaths("ParamSettings", normFactorName) + "+");
  
  /* propagate constant attribute of parameter */
  Bool_t isConst = false;
  if (definition->getTagBool("Const", isConst)) {
    TQTaggable tagsParam(paramSettings);
    if (tagsParam.getTagBoolDefault("Const", isConst) == isConst) {
      paramSettings->setTagBool("Const", isConst);
    } else {
      paramSettings->setTagBool(".Const.Recessive", isConst);
    }
  }

  if(definition->getTagBoolDefault("POI",false)){
    paramSettings->setTagBool("POI",true);
  }
  
  return true;
}


//__________________________________________________________________________________|___________

Bool_t TSModelFactory::addShapeFactor(TQFolder * definition, Sample * sample) {

  /* extract shape factor name */
  TString shapeFactorName = definition->GetName();
  if (!TQStringUtils::removeLeadingText(shapeFactorName, "ShapeFactor.")) {
    // missing prefix "ShapeFactor."
    return false;
  }

  /* add shape factor */
  sample->AddShapeFactor(shapeFactorName.Data());

  TQFolder * paramSettings = fModel->getFolder(TQFolder::concatPaths("ParamSettings", shapeFactorName) + "+");  
  if(definition->getTagBoolDefault("POI",false)){
    paramSettings->setTagBool("POI",true);
  }
  
  return true;
}

//__________________________________________________________________________________|___________

Bool_t TSModelFactory::addHistoFactor(TQFolder * definition, Sample * sample) {

  /* extract shape factor name */
  TString histoFactorName = definition->GetName();
  if (!TQStringUtils::removeLeadingText(histoFactorName, "HistoFactor.")) {
    // missing prefix "HistFactor."
    return false;
  }

  /* extract histogram locations */
  TString histogramLocationHigh,histogramLocationLow;
  if(!definition->getTagString("HistoHigh",histogramLocationHigh) || !definition->getTagString("HistoLow",histogramLocationLow)){
    error(TString::Format("no histogram assigned to '%s'",definition->getPath().Data()));
    return false;
  }
  
  /* split histogram locations */
  /* extract histogram location */
  
  TString histogramFile = definition->getTagStringDefault("~.HistogramFile","histFactor_tmp.root");
  TString histogramPathPrefix = definition->getTagStringDefault("~.HistogramPathPrefix","");
  
  /* split histogram location */
  TString histoPathLow = TQFolder::concatPaths(histogramPathPrefix, histogramLocationLow);
  TString histoPathHigh = TQFolder::concatPaths(histogramPathPrefix, histogramLocationHigh);
  TString histoNameLow = TQFolder::getPathTail(histoPathLow);    
  TString histoNameHigh = TQFolder::getPathTail(histoPathHigh);    
  TQStringUtils::ensureTrailingText(histoPathLow,"/");
  TQStringUtils::ensureTrailingText(histoPathHigh,"/");
  
  TString histoFilePath = TQPathManager::getPathManager()->getTargetPath(histogramFile).c_str();
  
  /* add norm factor */
  sample->AddHistoFactor(histoFactorName.Data(), 
			 histoNameLow.Data(),  histoFilePath.Data(), histoPathLow.Data(),
			 histoNameHigh.Data(), histoFilePath.Data(), histoPathHigh.Data());

  return true;
}

//__________________________________________________________________________________|___________

Bool_t TSModelFactory::addOverallSys(TQFolder * definition, Sample * sample) {

  /* extract systematic name */
  TString overallSysName = definition->GetName();
  if (!TQStringUtils::removeLeadingText(overallSysName, "OverallSys.")) {
    // missing prefix "OverallSys."
    return false;
  }

  /* add norm factor */
  sample->AddOverallSys(overallSysName.Data(),
                        definition->getTagDoubleDefault("Low", 1.),
                        definition->getTagDoubleDefault("High", 1.));

  return true;
}


//__________________________________________________________________________________|___________

Bool_t TSModelFactory::addHistoSys(TQFolder * definition, Sample * sample) {

  /* extract systematic name */
  TString histoSysName = definition->GetName();
  if (!TQStringUtils::removeLeadingText(histoSysName, "HistoSys.")) {
    // missing prefix "HistoSys."
    return false;
  }
  
  /* extract histogram locations */
  TString histogramLocationHigh,histogramLocationLow;
  if(!definition->getTagString("HistoHigh",histogramLocationHigh) || !definition->getTagString("HistoLow",histogramLocationLow)){
    error(TString::Format("no histogram assigned to '%s'",definition->getPath().Data()));
    return false;
  }
  
  /* split histogram locations */
  /* extract histogram location */
  
  TString histogramFile = definition->getTagStringDefault("~.HistogramFile","histFactor_tmp.root");
  TString histogramPathPrefix = definition->getTagStringDefault("~.HistogramPathPrefix","");
  
  /* split histogram location */
  TString histoPathLow = TQFolder::concatPaths(histogramPathPrefix, histogramLocationLow);
  TString histoPathHigh = TQFolder::concatPaths(histogramPathPrefix, histogramLocationHigh);
  TString histoNameLow = TQFolder::getPathTail(histoPathLow);    
  TString histoNameHigh = TQFolder::getPathTail(histoPathHigh);    
  TQStringUtils::ensureTrailingText(histoPathLow,"/");
  TQStringUtils::ensureTrailingText(histoPathHigh,"/");
  
  TString histoFilePath = TQPathManager::getPathManager()->getTargetPath(histogramFile).c_str();
  
  /* add norm factor */
  sample->AddHistoSys(histoSysName.Data(), 
                      histoNameLow.Data(),  histoFilePath.Data(), histoPathLow.Data(),
                      histoNameHigh.Data(), histoFilePath.Data(), histoPathHigh.Data());
  
  return true;
}

//__________________________________________________________________________________|___________

Bool_t TSModelFactory::addShapeSys(TQFolder * definition, Sample * sample) {

  /* extract systematic name */
  TString shapeSysName = definition->GetName();
  if (!TQStringUtils::removeLeadingText(shapeSysName, "ShapeSys.")) {
    // missing prefix "ShapeSys."
    return false;
  }

  /* extract histogram locations */
  TString histogramLocation;
  if(!definition->getTagString("Histo",histogramLocation)){
    error(TString::Format("unable to find histogram for '%s'",definition->getPath().Data()));
    return false;
  }  
  TString histogramFile;
  TString histogramPathPrefix;
  definition->getTagString("~.HistogramFile", histogramFile, true);
  definition->getTagString("~.HistogramPathPrefix", histogramPathPrefix, true);
  
  bool usePoisson = definition->getTagBoolDefault("usePoisson",false);
  
  histogramLocation = histogramFile + ":"
    + TQFolder::concatPaths(histogramPathPrefix, histogramLocation);

  /* split histogram locations */
  TString histoFile;
  TString histoName;
  TString histoPath;
  splitHistoLocation(histogramLocation, histoFile, histoName, histoPath);
  
  /* add norm factor */
  sample->AddShapeSys(shapeSysName.Data(), ( usePoisson ? RooStats::HistFactory::Constraint::Type::Poisson : RooStats::HistFactory::Constraint::Type::Gaussian ) , histoName.Data(), TQPathManager::getPathManager()->getTargetPath(histoFile).c_str(), histoPath.Data());

  return true;
}


//__________________________________________________________________________________|___________

void TSModelFactory::splitHistoLocation(TString input,
                                        TString &histoFile, TString &histoName, TString &histoPath) {
  /* extract histogram location */
  histoPath = input;
  histoFile = TQStringUtils::readPrefix(histoPath, ":");
  histoName = TQFolder::getPathTail(histoPath);

  if (!histoPath.IsNull())
    histoPath.Append("/");
}