Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ActionBatchCalculate.cxx 15.22 KiB
#include "QFramework/TQFolder.h"
#include "QFramework/TQIterator.h"
#include "QFramework/TQUtils.h"
#include "QFramework/TQPathManager.h"

#include "SFramework/TSStatisticsManager.h"
#include "SFramework/TSStatisticsCalculator.h"
#include "SFramework/TSBreakdownCalculator.h"
#include "SFramework/TSImpactCalculator.h"
#include "SFramework/TSUtils.h"
#include "TFile.h"

/*<cafdoc name=WriteCalculateBreakdown>
  WriteCalculateBreakdown
  ===========================
  
  This is a batch-parallelized version of the `CalculateBreakdown` action.
  Instead of performing all the fits locally, it will write out a
  directory with a copy of the workspaces as well as configs to submit
  jobs fitting the individual points to a batch system.
  
  The unconditional fit will be run first locally, only after that the
  config files will be written to disk - so unless an unconditional
  snapshot exists already, also this version can take some time to
  perform.
  
  Usage:
  ---------------
  ```
  +WriteCalculateBreakdown {
    +Morphing {
      # compute breakdown for the fit to asimov
      +asimov { 
        <outputPath = "./workspaces/run2-comb-$(fitLabel)/Batch/Breakdowns/asm"> # output path for all the configs
        <datasetName="asimovData_SM">
        <invert=false>
      }   
      +asimovinvert {
        <outputPath = "./workspaces/run2-comb-$(fitLabel)/Batch/Breakdowns/asminv"> # output path for all the configs
        <datasetName="asimovData_SM">
        <invert=true>
      } 

      # in case this action has been executed on this sets of inputs
      #  previously, this action will instruct the framework to re-use
      #  the workspace file instead of recomputing the unconditional
      #  fit.
      <allowReuse=true> 

      @ ? {
        # all the options supported for the usual CalculateBreakdown action
      }
    }
  }
  ```

  In order to collect the results, use the `CollectBreakdown` action.

</cafdoc> */

/*<cafdoc name=CollectBreakdown>
  CollectBreakdown
  ===========================
  
  This is the result collection for the batch-parallelized version of the `CalculateBreakdown` action.
  
  Usage:
  ---------------
  ```
  +CollectBreakdown.asimov {
    +Morphing {
        +asimovinvert {
            <inputPath = "./workspaces/run2-comb-$(fitLabel)/Batch/Breakdowns/asimov"> # input path for all the results
        }
    }
  }
  ```

  The results are produced with the `WriteCalculateBreakdown` action.

</cafdoc> */

/*<cafdoc name=WriteCalculateImpacts>
  WriteCalculateImpacts
  ===========================
  
  This is a batch-parallelized version of the `CalculateImpacts` action.
  Instead of performing all the fits locally, it will write out a
  directory with a copy of the workspaces as well as configs to submit
  jobs fitting the individual points to a batch system.
  
  The unconditional fit will be run first locally, only after that the
  config files will be written to disk - so unless an unconditional
  snapshot exists already, also this version can take some time to
  perform.
  
  Usage:
  ---------------
  ```
  +WriteCalculateImpacts {
    +Morphing {
      # compute breakdown for the fit to asimov
      +asimov { 
        <outputPath = "./workspaces/run2-comb-$(fitLabel)/Batch/Impacts/asm"> # output path for all the configs
        <datasetName="asimovData_SM">
        <workspaceFileName="workspace.root"> #optional, defaults to workspace.root
        <invert=false>
      }   
      +asimovinvert {
        <outputPath = "./workspaces/run2-comb-$(fitLabel)/Batch/Impacts/asminv"> # output path for all the configs
        <datasetName="asimovData_SM">
        <invert=true>
      } 

      # in case this action has been executed on this sets of inputs
      #  previously, this action will instruct the framework to re-use
      #  the workspace file instead of recomputing the unconditional
      #  fit.
      <allowReuse=true> 

      @ ? {
        # all the options supported for the usual CalculateImpacts action
      }
    }
  }
  ```

  In order to collect the results, use the `CollectImpacts` action.

</cafdoc> */

/*<cafdoc name=CollectImpacts>
  CollectImpacts
  ===========================
  
  This is the result collection for the batch-parallelized version of the `CalculateImpacts` action.
  
  Usage:
  ---------------
  ```
  +CollectImpacts.asimov {
    +Morphing {
        +asimovinvert {
            <inputPath = "./workspaces/run2-comb-$(fitLabel)/Batch/Breakdowns/asimov"> # input path for all the results
        }
    }
  }
  ```

  The results are produced with the `WriteCalculateImpacts` action.

</cafdoc> */

namespace TSBaseActions {

  template<class BaseCalculator> class WriteImpactBreakdownConfig : public TSStatisticsManager::Action {
    const TString actionname;
    const TString component;
    const TString subcomponent;

  public:
    WriteImpactBreakdownConfig(const TString& aname, const TString& comp, const TString& subcomp) :
      actionname(aname),
      component(comp),
      subcomponent(subcomp)
    {
      // do nothing
    }
		
    bool execute(TQFolder * config) const override {
      RooWorkspace * workspace = dynamic_cast<RooWorkspace*>(workspaces()->getObject(config->GetName()));

      if(!workspace){
        manager->error(TString::Format("no such workspace available: '%s'",config->GetName()));
        return false;
      }
      
      TQFolder * result = results()->getFolder(TString::Format("%s+",config->GetName()));

      int nOK = 0;
      TQFolderIterator itr(config->getListOfFolders("?"));
      while(itr.hasNext()){
        TQFolder* cfg = itr.readNext();
        manager->info(TString::Format("Calculating breakdown '%s' on workspace '%s'",cfg->GetName(),config->GetName()));
        nOK += writeConfigs(workspace,cfg,result);
      }
      return nOK>0;
    }

			
    bool writeConfigs(RooWorkspace* ws, TQFolder* config, TQFolder* result) const {
      BaseCalculator calc(ws,result->getFolder("Snapshots+"));
      const RooArgSet * nuis	= calc.getNuisanceParameters();

      RooArgSet listOfPOIs	= calc.getPOIs(config);
      if (listOfPOIs.getSize() == 0) {
        manager->error("no POIs found!");
        return false;
      }

      TString output = TQFolder::concatPaths("batch",actionname);
      config->getTagString("~outputPath",output);
      output = TQPathManager::getPathManager()->getTargetPath(output);
      TQUtils::ensureDirectory(TQFolder::concatPaths(output,config->GetName(),"fitlogs"));
      TQUtils::ensureDirectory(TQFolder::concatPaths(output,config->GetName(),"configs"));
      TQUtils::ensureDirectory(TQFolder::concatPaths(output,config->GetName(),"results"));
      manager->info(TString::Format("creating configuration for '%s' in '%s'",config->GetName(),output.Data()));
      
      TString workspaceFileName = "workspace.root";
      config->getTagString("~workspaceFileName",workspaceFileName);
      TString snapshotfile = TQFolder::concatPaths(output,config->GetName(),"snapshots.txt");			
      TString filename = TQFolder::concatPaths(output,config->GetName(),workspaceFileName);

      TQFolder* resulttarget = result->getFolder(TQFolder::concatPaths(component,config->GetName(),calc.fFolderName)+"+");
      TQFolder* snapshots = result->getFolder("Snapshots+");

      if(!config->getTagBoolDefault("~allowReuse",false) || !TQUtils::fileExists(snapshotfile) || !TQUtils::fileExists(snapshotfile)){
        calc.runPreFit(config,listOfPOIs,resulttarget);
        
        snapshots->detachFromBase();
        TQFolder* tmp = new TQFolder("uncondresult");
        tmp->addFolder(snapshots);
        tmp->exportToTextFile(TQPathManager::getPathManager()->getTargetPath(snapshotfile).c_str());
        snapshots->detachFromBase();
        result->addFolder(snapshots);
        delete tmp;

        ws->writeToFile(TQPathManager::getPathManager()->getTargetPath(filename).c_str());
      } else {
        manager->warn(TString::Format("reusing files '%s' and '%s' from previous run!",filename.Data(),snapshotfile.Data()));
      }  

      TQFolder* outconfig = new TQFolder("config");
      outconfig->setTagString("expectedInput",filename);      
      TQFolder* import = outconfig->getFolder(TQFolder::concatPaths("ImportWorkspaces",ws->GetName())+"+");
      import->setTagString("inputFile",filename+":"+ws->GetName());
      import->setTagBool("infile",true);
				
      TQFolder* action = outconfig->getFolder(TQFolder::concatPaths(this->actionname,ws->GetName(),config->GetName())+"+");

      TQFolder* exprt = outconfig->getFolder(TQFolder::concatPaths("ExportResults",ws->GetName())+"+");
			
      TQTaggable options(config);
      TSUtils::expandKeys(nuis,config);
			
      TQIterator itrGroups(config->getListOfKeys("group.*"), true);
      while (itrGroups.hasNext()) {
        action->clearTags();
        exprt->clearTags();
        TString name = itrGroups.readNext()->GetName();
        action->importTags(options);
        //cleanup of tags
        action->removeTags("config.group.*"); //remove all group/singles specific options
        action->removeTags("config.singles.*");
        options.exportTags(action,"","config."+name+".*");//re-add only the applicable ones
        //action->importTagsWithPrefix(options,"config."+name); //re-import only the applicable ones //note: this method doesn't work as one might expect... it imports everything but prepends the prefix to all keys...
        //remove all group and singles definitions, will re-add only the currently treated one afterwards.
        action->removeTags("group.*");
        action->removeTags("singles.*");
        //"move" the config.* ones to their proper namings (the filtering for suitable ones was already done before when exporting the tags from 'options'
        //action->importTagsWithoutPrefix(action,"config.");
        
        //action->setTagString("fit.logToFile",TQFolder::concatPaths(output,config->GetName(),"fitlogs",name)+".log");
	action->setTagString("fit.logToFile",TQFolder::concatPaths(output,config->GetName(),"fitlogs","$(id)")+".log");        

        TString filter = config->getTagStringDefault(name, "!*");
        action->setTagString(name,filter);//re-add the group/single that we're interested in
        
        TString resultfile = TQFolder::concatPaths(output,config->GetName(),"results",name)+".root";
        exprt->setTagString("outputFile",resultfile);
        outconfig->setTagString("expectedOutput",resultfile);
        TString outconfigfile = TQFolder::concatPaths(output,config->GetName(),"configs",name)+".txt";
        outconfig->setTagString("identifier",TString::Format("%s_%s_%s",actionname.Data(),config->GetName(),name.Data()));        
        if(outconfig->exportToTextFile(TQPathManager::getPathManager()->getTargetPath(outconfigfile).c_str())){
          manager->info(TString::Format("wrote %s",outconfigfile.Data()));
        } else {
          manager->warn(TString::Format("error writing %s",outconfigfile.Data()));
        }
      }
      delete outconfig;
      return true;
    }
  };

  class CollectImpactBreakdownResults : public TSStatisticsManager::Action {

    const TString component;
    const TString subcomponent;

  public:
    CollectImpactBreakdownResults(const TString& comp, const TString& subcomp) :
      component(comp),
      subcomponent(subcomp)
    {
      // do nothing
    };
		
    bool execute(TQFolder * config) const override {
			
      TString configname(config->GetName());
      TQFolder* resultset = results()->getFolder(configname+"+");
      if(!resultset){
        manager->error(TString::Format("no such result available: '%s'",config->GetName()));
        return false;
      }
      resultset->detachFromBase();
			
      TQFolderIterator versions(config->getListOfFolders("?"),true);
      while(versions.hasNext()){
        TQFolder* version = versions.readNext();
        if(!version) continue;
				
        TString path;
        if(!version->getTagString("inputPath",path)){
          manager->error(TString::Format("need to provide input path for '%s', skipping!",version->GetName()));
          continue;
        }
        
        manager->info(TString::Format("reading input from '%s'",path.Data()));
      
        TString resultfiles = TQFolder::concatPaths(TQPathManager::getPathManager()->getTargetPath(path),"results");
	TString resultfiles_txt = resultfiles;
        TQStringUtils::ensureTrailingText(resultfiles_txt,"/*.txt");
        TCollection* list_txt = TQUtils::ls(resultfiles_txt);
	TString resultfiles_root = resultfiles;
        TQStringUtils::ensureTrailingText(resultfiles_root,"/*.root");		
        TCollection* list_root = TQUtils::ls(resultfiles_root);	

	TList* list = new TList();
	list->SetOwner(true);	  	
	if(list_txt){
	  list->AddAll(list_txt);
	  list_txt->SetOwner(false);
	  delete list_txt;
	}
	if(list_root){
	  list->AddAll(list_root);
	  list_root->SetOwner(false);	  
	  delete list_root;
	}	
	if(list->GetEntries() == 0){
          manager->error(TString::Format("no files found under '%s'!",resultfiles.Data()));
          continue;
        }
        TQIterator itr(list,true);
        TQFolder* snapshots = resultset->getFolder(TQFolder::concatPaths(this->component,version->GetName(),"FitResults")+"+");
        TString err;
        if(!snapshots->importFromTextFile(TQFolder::concatPaths(TQPathManager::getPathManager()->getTargetPath(path),"snapshots.txt",err))){
          manager->warn("unable to read snapshot file!");
        }
	
        while(itr.hasNext()){
          err.Clear();
          TString fname(itr.readNext()->GetName());
	  TQFolder* component = NULL;
	  if(fname.EndsWith(".txt")){
	    component = TQFolder::loadFromTextFile(fname,err);
	  } else {
	    component = TQFolder::loadFolder(fname,false);
	  }
	  if(!component){
	    manager->error(TString::Format("unable to load file '%s': %s",fname.Data(),err.Data()));
            continue;
          } else {
            manager->info(TString::Format("loaded file '%s'",fname.Data()));
          }
          TString path(TQFolder::concatPaths(this->component,version->GetName(),this->subcomponent,"?/?"));
          TQFolderIterator folders(component->getListOfFolders(path),true);
          while(folders.hasNext()){
            TQFolder* f = folders.readNext();
            if(!f) continue;
            TString fpath(f->getPath());
            TQFolder::getPathTail(fpath);
            TQFolder* target = resultset->getFolder(fpath+"+");
            if(!target->getFolder(f->GetName())){
              f->detachFromBase();
              target->addObject(f);
            } else {
              manager->warn(TString::Format("skip collecting '%s', already present in '%s'",f->GetName(),target->getPath().Data()));
            }
          }
        }
      }
      results()->addObject(resultset);
      return true;
    }
  };
  namespace {
    bool writeBreakdown   = TSStatisticsManager::registerAction(new WriteImpactBreakdownConfig<TSBreakdownCalculator>   ("CalculateBreakdown","Breakdowns","Breakdown"),"WriteCalculateBreakdown");
    bool writeImpacts     = TSStatisticsManager::registerAction(new WriteImpactBreakdownConfig<TSImpactCalculator>      ("CalculateImpacts",  "Impacts",   "Impacts"  ),"WriteCalculateImpacts");
    bool collectBreakdown = TSStatisticsManager::registerAction(new CollectImpactBreakdownResults("Breakdowns",        "Breakdown"),"CollectBreakdown");
    bool collectImpacts   = TSStatisticsManager::registerAction(new CollectImpactBreakdownResults("Impacts",           "Impacts"  ),"CollectImpacts");
  }
}