From 055cae61b60df5abcbbf456a719017c317641839 Mon Sep 17 00:00:00 2001
From: Peter Onyisi <ponyisi@utexas.edu>
Date: Tue, 11 Feb 2020 09:44:52 +0000
Subject: [PATCH] Allow specification of merge methods for histograms & publish
 to offline provider

---
 .../python/ExampleMonitorAlgorithm.py         |  3 ++
 .../AthenaMonitoringKernel/HistogramDef.h     |  2 +
 .../python/GenericMonitoringTool.py           | 11 ++++-
 .../share/GenericMon.txt                      | 24 +++++-----
 .../src/HistogramDef.cxx                      |  2 +
 .../OfflineHistogramProvider.h                |  8 ++--
 .../test/GenericMonParsing_test.cxx           |  1 +
 .../test/test_defineHistogram.py              | 45 ++++++++++++-------
 8 files changed, 63 insertions(+), 33 deletions(-)

diff --git a/Control/AthenaMonitoring/python/ExampleMonitorAlgorithm.py b/Control/AthenaMonitoring/python/ExampleMonitorAlgorithm.py
index ffa4588bde2..16022c70537 100644
--- a/Control/AthenaMonitoring/python/ExampleMonitorAlgorithm.py
+++ b/Control/AthenaMonitoring/python/ExampleMonitorAlgorithm.py
@@ -85,6 +85,9 @@ def ExampleMonitoringConfig(inputFlags):
                             xbins=[0,.1,.2,.4,.8,1.6])
     myGroup.defineHistogram('random,pT', type='TH2F', title='title;x;y',path='ToBringThemAll',
                             xbins=[0,.1,.2,.4,.8,1.6],ybins=[0,10,30,40,60,70,90])
+    # specify a merge method
+    myGroup.defineHistogram('lumiPerBCID;lumiPerBCID_merge',title='Luminosity,WithCommaInTitle;L/BCID;Events',
+                            path='ToRuleThemAll',xbins=40,xmin=0.0,xmax=80.0, merge='weightedAverage')
     # TEfficiencies
     myGroup.defineHistogram('pT_passed,pT', type='TEfficiency', title='Test TEfficiency;x;Eff',
                             path='AndInTheDarkness', xbins=100, xmin=0.0, xmax=50.0)
diff --git a/Control/AthenaMonitoringKernel/AthenaMonitoringKernel/HistogramDef.h b/Control/AthenaMonitoringKernel/AthenaMonitoringKernel/HistogramDef.h
index d391dcc2682..d3e457bc508 100644
--- a/Control/AthenaMonitoringKernel/AthenaMonitoringKernel/HistogramDef.h
+++ b/Control/AthenaMonitoringKernel/AthenaMonitoringKernel/HistogramDef.h
@@ -44,6 +44,8 @@ namespace Monitored {
     float zmax; //!< z axis maximum
     std::vector<std::string> zlabels; //!< labels for z axis
 
+    std::string merge;
+
     std::string treeDef;
 
     bool ok{false}; //!< good declaration
diff --git a/Control/AthenaMonitoringKernel/python/GenericMonitoringTool.py b/Control/AthenaMonitoringKernel/python/GenericMonitoringTool.py
index 60e7581fbf3..51087c2d706 100644
--- a/Control/AthenaMonitoringKernel/python/GenericMonitoringTool.py
+++ b/Control/AthenaMonitoringKernel/python/GenericMonitoringTool.py
@@ -119,6 +119,8 @@ class GenericMonitoringArray:
 #  @param xlabels  List of x bin labels.
 #  @param ylabels  List of y bin labels.
 #  @param zlabels  List of x bin labels.
+#  @param merge    Merge method to use for object, if not default. Possible algorithms for offline DQM
+#                  are given in https://twiki.cern.ch/twiki/bin/view/Atlas/DQMergeAlgs
 
 def defineHistogram(varname, type='TH1F', path=None,
                     title=None, weight=None, alias=None,
@@ -126,11 +128,11 @@ def defineHistogram(varname, type='TH1F', path=None,
                     ybins=None, ymin=None, ymax=None, ylabels=None,
                     zmin=None, zmax=None, zlabels=None, 
                     opt='', treedef=None, labels=None, convention=None,
-                    cutmask=None):
+                    cutmask=None, merge=None):
 
     # All of these fields default to an empty string
     stringSettingsKeys = ['xvar', 'yvar', 'zvar', 'type', 'path', 'title', 'weight',
-    'cutMask', 'opt', 'convention', 'alias', 'treeDef'] 
+    'cutMask', 'opt', 'convention', 'alias', 'treeDef', 'merge'] 
     # All of these fileds default to 0
     numberSettingsKeys = ['xbins', 'xmin', 'xmax', 'ybins', 'ymin', 'ymax', 'zbins',
     'zmin', 'zmax']
@@ -264,6 +266,11 @@ def defineHistogram(varname, type='TH1F', path=None,
         assert isinstance(zlabels, (list, tuple)),'zlabels must be list or tuple'
         settings['zlabels'] = zlabels
 
+    # merge method
+    if merge is not None:
+        assert type not in ['TEfficiency', 'TTree', 'TGraph'],'only default merge defined for non-histogram objects'
+        settings['merge'] = merge
+
     # Tree branches
     if treedef is not None:
         assert type=='TTree','cannot define tree branches for a non-TTree object'
diff --git a/Control/AthenaMonitoringKernel/share/GenericMon.txt b/Control/AthenaMonitoringKernel/share/GenericMon.txt
index 767e42e4c31..2d7ffae7915 100644
--- a/Control/AthenaMonitoringKernel/share/GenericMon.txt
+++ b/Control/AthenaMonitoringKernel/share/GenericMon.txt
@@ -15,15 +15,15 @@ THistSvc.OutputLevel = 0;
 THistSvc.Output= {"EXPERT DATAFILE='expert-monitoring.root' OPT='RECREATE'" };
 ToolSvc.MonTool.OutputLevel = 0;
 ToolSvc.MonTool.HistPath="TestGroup";
-ToolSvc.MonTool.Histograms =  {'{"alias": "Eta", "allvars": ["Eta"], "convention": "", "opt": "", "path": "EXPERT", "title": "#eta of Clusters; #eta; number of RoIs", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 2, "xlabels": [], "xmax": 2.50, "xmin": -2.50, "xvar": "Eta", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": ""}'};
-ToolSvc.MonTool.Histograms += {'{"alias": "Eta_CutMask", "allvars": ["Eta"], "convention": "", "opt": "", "path": "EXPERT", "title": "#eta of Clusters, with cut; #eta; number of RoIs", "type": "TH1F", "weight": "", "cutMask": "CutMask", "xarray": [], "xbins": 2, "xlabels": [], "xmax": 2.50, "xmin": -2.50, "xvar": "Eta", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": ""}'};
-ToolSvc.MonTool.Histograms += {'{"alias": "Phi", "allvars": ["Phi"], "convention": "", "opt": "", "path": "EXPERT", "title": "#phi of Clusters; #phi; number of RoIs", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 2, "xlabels": [], "xmax": 3.15, "xmin": -3.15, "xvar": "Phi", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": ""}'};
-ToolSvc.MonTool.Histograms += {'{"alias": "Phi_vs_Eta", "allvars": ["Eta", "Phi"], "convention": "", "opt": "", "path": "EXPERT", "title": "#phi vs #eta of Clusters; #eta; #phi; number of RoIs", "type": "TH2F", "weight": "", "cutMask": "", "xarray": [], "xbins": 2, "xlabels": [], "xmax": 2.5, "xmin": -2.5, "xvar": "Eta", "yarray": [], "ybins": 2, "ylabels": [], "ymax": 3.15, "ymin": -3.15, "yvar": "Phi", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": ""}'};
-ToolSvc.MonTool.Histograms += {'{"alias": "Phi_vs_Eta_Tree", "allvars": ["Eta", "Phi"], "convention": "", "opt": "", "path": "EXPERT", "title": "Test Tree", "type": "TTree", "weight": "", "cutMask": "", "xarray": [], "xbins": 2, "xlabels": [], "xmax": 2.5, "xmin": -2.5, "xvar": "Eta", "yarray": [], "ybins": 2, "ylabels": [], "ymax": 3.15, "ymin": -3.15, "yvar": "Phi", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": "Eta/F:Phi/vector<float>"}'};
-ToolSvc.MonTool.Histograms += {'{"alias": "TIME_t1", "allvars": ["TIME_t1"], "convention": "", "opt": "", "path": "EXPERT", "title": "Timing of tool 1", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 200, "xlabels": [], "xmax": 20000.0, "xmin": 0.0, "xvar": "TIME_t1", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": ""}'};
-ToolSvc.MonTool.Histograms += {'{"alias": "TIME_t2", "allvars": ["TIME_t2"], "convention": "", "opt": "", "path": "EXPERT", "title": "Timing of tool 2", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 100.0, "xmin": 0.0, "xvar": "TIME_t2", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": ""}'};
-ToolSvc.MonTool.Histograms += {'{"alias": "TIME_t3", "allvars": ["TIME_t3"], "convention": "", "opt": "", "path": "EXPERT", "title": "Timing of tool 3", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 100.0, "xmin": 0.0, "xvar": "TIME_t3", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": ""}'};
-ToolSvc.MonTool.Histograms += {'{"alias": "DetID", "allvars": ["DetID"], "convention": "", "opt": "", "path": "EXPERT", "title": "Test of text filling", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 10, "xlabels": [], "xmax": 10.0, "xmin": 0.0, "xvar": "DetID", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": ""}'};
-ToolSvc.MonTool.Histograms += {'{"alias": "DetID_vs_DetCalo", "allvars": ["DetCalo", "DetID"], "convention": "", "opt": "", "path": "EXPERT", "title": "Fill 2D with strings", "type": "TH2I", "weight": "", "cutMask": "", "xarray": [], "xbins": 10, "xlabels": [], "xmax": 10.0, "xmin": 0.0, "xvar": "DetCalo", "yarray": [], "ybins": 10, "ylabels": [], "ymax": 10.0, "ymin": 0.0, "yvar": "DetID", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": ""}'};
-ToolSvc.MonTool.Histograms += {'{"alias": "y_vs_DetCalo", "allvars": ["DetCalo", "y"], "convention": "", "opt": "", "path": "EXPERT", "title": "Fill 2D with string & double", "type": "TH2I", "weight": "", "cutMask": "", "xarray": [], "xbins": 10, "xlabels": [], "xmax": 10.0, "xmin": 0.0, "xvar": "DetCalo", "yarray": [], "ybins": 10, "ylabels": [], "ymax": 10.0, "ymin": 0.0, "yvar": "y", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": ""}'};
-ToolSvc.MonTool.Histograms += {'{"alias": "DetCalo_vs_x", "allvars": ["x", "DetCalo"], "convention": "", "opt": "", "path": "EXPERT", "title": "Fill 2D with double & string", "type": "TH2I", "weight": "", "cutMask": "", "xarray": [], "xbins": 10, "xlabels": [], "xmax": 10.0, "xmin": 0.0, "xvar": "x", "yarray": [], "ybins": 10, "ylabels": [], "ymax": 10.0, "ymin": 0.0, "yvar": "DetCalo", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": ""}'};
+ToolSvc.MonTool.Histograms =  {'{"alias": "Eta", "allvars": ["Eta"], "convention": "", "opt": "", "path": "EXPERT", "title": "#eta of Clusters; #eta; number of RoIs", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 2, "xlabels": [], "xmax": 2.50, "xmin": -2.50, "xvar": "Eta", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": "", "merge": ""}'};
+ToolSvc.MonTool.Histograms += {'{"alias": "Eta_CutMask", "allvars": ["Eta"], "convention": "", "opt": "", "path": "EXPERT", "title": "#eta of Clusters, with cut; #eta; number of RoIs", "type": "TH1F", "weight": "", "cutMask": "CutMask", "xarray": [], "xbins": 2, "xlabels": [], "xmax": 2.50, "xmin": -2.50, "xvar": "Eta", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": "", "merge": ""}'};
+ToolSvc.MonTool.Histograms += {'{"alias": "Phi", "allvars": ["Phi"], "convention": "", "opt": "", "path": "EXPERT", "title": "#phi of Clusters; #phi; number of RoIs", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 2, "xlabels": [], "xmax": 3.15, "xmin": -3.15, "xvar": "Phi", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": "", "merge": ""}'};
+ToolSvc.MonTool.Histograms += {'{"alias": "Phi_vs_Eta", "allvars": ["Eta", "Phi"], "convention": "", "opt": "", "path": "EXPERT", "title": "#phi vs #eta of Clusters; #eta; #phi; number of RoIs", "type": "TH2F", "weight": "", "cutMask": "", "xarray": [], "xbins": 2, "xlabels": [], "xmax": 2.5, "xmin": -2.5, "xvar": "Eta", "yarray": [], "ybins": 2, "ylabels": [], "ymax": 3.15, "ymin": -3.15, "yvar": "Phi", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": "", "merge": ""}'};
+ToolSvc.MonTool.Histograms += {'{"alias": "Phi_vs_Eta_Tree", "allvars": ["Eta", "Phi"], "convention": "", "opt": "", "path": "EXPERT", "title": "Test Tree", "type": "TTree", "weight": "", "cutMask": "", "xarray": [], "xbins": 2, "xlabels": [], "xmax": 2.5, "xmin": -2.5, "xvar": "Eta", "yarray": [], "ybins": 2, "ylabels": [], "ymax": 3.15, "ymin": -3.15, "yvar": "Phi", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": "Eta/F:Phi/vector<float>", "merge": ""}'};
+ToolSvc.MonTool.Histograms += {'{"alias": "TIME_t1", "allvars": ["TIME_t1"], "convention": "", "opt": "", "path": "EXPERT", "title": "Timing of tool 1", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 200, "xlabels": [], "xmax": 20000.0, "xmin": 0.0, "xvar": "TIME_t1", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": "", "merge": ""}'};
+ToolSvc.MonTool.Histograms += {'{"alias": "TIME_t2", "allvars": ["TIME_t2"], "convention": "", "opt": "", "path": "EXPERT", "title": "Timing of tool 2", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 100.0, "xmin": 0.0, "xvar": "TIME_t2", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": "", "merge": ""}'};
+ToolSvc.MonTool.Histograms += {'{"alias": "TIME_t3", "allvars": ["TIME_t3"], "convention": "", "opt": "", "path": "EXPERT", "title": "Timing of tool 3", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 100.0, "xmin": 0.0, "xvar": "TIME_t3", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": "", "merge": ""}'};
+ToolSvc.MonTool.Histograms += {'{"alias": "DetID", "allvars": ["DetID"], "convention": "", "opt": "", "path": "EXPERT", "title": "Test of text filling", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 10, "xlabels": [], "xmax": 10.0, "xmin": 0.0, "xvar": "DetID", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": "", "merge": ""}'};
+ToolSvc.MonTool.Histograms += {'{"alias": "DetID_vs_DetCalo", "allvars": ["DetCalo", "DetID"], "convention": "", "opt": "", "path": "EXPERT", "title": "Fill 2D with strings", "type": "TH2I", "weight": "", "cutMask": "", "xarray": [], "xbins": 10, "xlabels": [], "xmax": 10.0, "xmin": 0.0, "xvar": "DetCalo", "yarray": [], "ybins": 10, "ylabels": [], "ymax": 10.0, "ymin": 0.0, "yvar": "DetID", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": "", "merge": ""}'};
+ToolSvc.MonTool.Histograms += {'{"alias": "y_vs_DetCalo", "allvars": ["DetCalo", "y"], "convention": "", "opt": "", "path": "EXPERT", "title": "Fill 2D with string & double", "type": "TH2I", "weight": "", "cutMask": "", "xarray": [], "xbins": 10, "xlabels": [], "xmax": 10.0, "xmin": 0.0, "xvar": "DetCalo", "yarray": [], "ybins": 10, "ylabels": [], "ymax": 10.0, "ymin": 0.0, "yvar": "y", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": "", "merge": ""}'};
+ToolSvc.MonTool.Histograms += {'{"alias": "DetCalo_vs_x", "allvars": ["x", "DetCalo"], "convention": "", "opt": "", "path": "EXPERT", "title": "Fill 2D with double & string", "type": "TH2I", "weight": "", "cutMask": "", "xarray": [], "xbins": 10, "xlabels": [], "xmax": 10.0, "xmin": 0.0, "xvar": "x", "yarray": [], "ybins": 10, "ylabels": [], "ymax": 10.0, "ymin": 0.0, "yvar": "DetCalo", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": "", "treeDef": "", "merge": ""}'};
diff --git a/Control/AthenaMonitoringKernel/src/HistogramDef.cxx b/Control/AthenaMonitoringKernel/src/HistogramDef.cxx
index 0c358c1107d..e68d252d7fc 100644
--- a/Control/AthenaMonitoringKernel/src/HistogramDef.cxx
+++ b/Control/AthenaMonitoringKernel/src/HistogramDef.cxx
@@ -42,6 +42,8 @@ const HistogramDef HistogramDef::parse(const std::string& histogramDefinition) {
   result.zmax = setting["zmax"];
   result.zlabels = setting["zlabels"].get<std::vector<std::string>>();
 
+  result.merge = setting["merge"];
+
   result.treeDef = setting["treeDef"];
 
   result.ok = true;
diff --git a/Control/AthenaMonitoringKernel/src/HistogramFiller/OfflineHistogramProvider.h b/Control/AthenaMonitoringKernel/src/HistogramFiller/OfflineHistogramProvider.h
index 2cca3b1f45e..7f2ede7d97d 100644
--- a/Control/AthenaMonitoringKernel/src/HistogramFiller/OfflineHistogramProvider.h
+++ b/Control/AthenaMonitoringKernel/src/HistogramFiller/OfflineHistogramProvider.h
@@ -124,7 +124,9 @@ namespace Monitored {
         std::string interval;
         std::string conv = m_histDef->convention;
         char triggerData[] = "<none>";
-        char mergeData[] = "<default>";
+        const std::string mergeDataStr = m_histDef->merge == "" ? "<default>" : m_histDef->merge;
+        std::vector<char> mergeData{mergeDataStr.begin(), mergeDataStr.end()};
+        mergeData.push_back('\0');
         if (conv.find("run") != std::string::npos) {
           interval = "run";
         } else if (conv.find("lowStat") != std::string::npos) {
@@ -138,7 +140,7 @@ namespace Monitored {
           tree->Branch("Name", &(splitPath.second[0]), "Name/C");
           tree->Branch("Interval", &(interval[0]), "Interval/C");
           tree->Branch("TriggerChain", triggerData, "TriggerChain/C");
-          tree->Branch("MergeMethod", mergeData, "MergeMethod/C");
+          tree->Branch("MergeMethod", mergeData.data(), "MergeMethod/C");
           tree->Fill();
 
           if (!histSvc->regTree(treePath, std::move(tree))) {
@@ -152,7 +154,7 @@ namespace Monitored {
             tree->SetBranchAddress("Name", &(splitPath.second[0]));
             tree->SetBranchAddress("Interval", &(interval[0]));
             tree->SetBranchAddress("TriggerChain", triggerData);
-            tree->SetBranchAddress("MergeMethod", mergeData);
+            tree->SetBranchAddress("MergeMethod", mergeData.data());
             tree->Fill();
           } else {
             MsgStream log(Athena::getMessageSvc(), "OfflineHistogramProvider");
diff --git a/Control/AthenaMonitoringKernel/test/GenericMonParsing_test.cxx b/Control/AthenaMonitoringKernel/test/GenericMonParsing_test.cxx
index 7e4beba45de..12b32244262 100644
--- a/Control/AthenaMonitoringKernel/test/GenericMonParsing_test.cxx
+++ b/Control/AthenaMonitoringKernel/test/GenericMonParsing_test.cxx
@@ -41,6 +41,7 @@ json defaultJson() {
   j["zmax"] = 0.0;
   j["zmin"] = 0.0;
   j["zvar"] = "";
+  j["merge"] = "";
   j["treeDef"] = "";
   return j;
 }
diff --git a/Control/AthenaMonitoringKernel/test/test_defineHistogram.py b/Control/AthenaMonitoringKernel/test/test_defineHistogram.py
index b4f23ea2be0..1f0f6bc5126 100644
--- a/Control/AthenaMonitoringKernel/test/test_defineHistogram.py
+++ b/Control/AthenaMonitoringKernel/test/test_defineHistogram.py
@@ -11,83 +11,88 @@ from AthenaMonitoringKernel.GenericMonitoringTool import defineHistogram, define
 class Test( unittest.TestCase ):
    def test_1D( self ):
       check = defineHistogram('var', 'TH1F', 'EXPERT', 'title', '', '', 10, 0.0, 10.0)
-      true = '{"alias": "var", "allvars": ["var"], "convention": "", "opt": "", "path": "EXPERT", "title": "title", "treeDef": "", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 10, "xlabels": [], "xmax": 10.0, "xmin": 0.0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
+      true = '{"alias": "var", "allvars": ["var"], "convention": "", "merge": "", "opt": "", "path": "EXPERT", "title": "title", "treeDef": "", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 10, "xlabels": [], "xmax": 10.0, "xmin": 0.0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
       self.assertEqual(json.loads(check), json.loads(true))
 
    def test_1D_opt( self ):
       check = defineHistogram('var', opt='myopt')
-      true = '{"alias": "var", "allvars": ["var"], "convention": "", "opt": "myopt", "path": "", "title": "var", "treeDef": "", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
+      true = '{"alias": "var", "allvars": ["var"], "convention": "", "merge": "", "opt": "myopt", "path": "", "title": "var", "treeDef": "", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
       self.assertEqual(json.loads(check), json.loads(true))
 
    def test_1D_weight( self ):
       check = defineHistogram('var', weight='myweight')
-      true = '{"alias": "var", "allvars": ["var"], "convention": "", "opt": "", "path": "", "title": "var", "treeDef": "", "type": "TH1F", "weight": "myweight", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
+      true = '{"alias": "var", "allvars": ["var"], "convention": "", "merge": "", "opt": "", "path": "", "title": "var", "treeDef": "", "type": "TH1F", "weight": "myweight", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
       self.assertEqual(json.loads(check), json.loads(true))
 
    def test_1D_cutmask( self ):
       check = defineHistogram('var', cutmask='mycutmask')
-      true = '{"alias": "var", "allvars": ["var"], "convention": "", "opt": "", "path": "", "title": "var", "treeDef": "", "type": "TH1F", "weight": "", "cutMask": "mycutmask", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
+      true = '{"alias": "var", "allvars": ["var"], "convention": "", "merge": "", "opt": "", "path": "", "title": "var", "treeDef": "", "type": "TH1F", "weight": "", "cutMask": "mycutmask", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
       self.assertEqual(json.loads(check), json.loads(true))
 
    def test_1D_array( self ):
       check = defineHistogram('var', xbins=[0, 1, 2, 4, 8])
-      true = '{"alias": "var", "allvars": ["var"], "convention": "", "opt": "", "path": "", "title": "var", "treeDef": "", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [0, 1, 2, 4, 8], "xbins": 4, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
+      true = '{"alias": "var", "allvars": ["var"], "convention": "", "merge": "", "opt": "", "path": "", "title": "var", "treeDef": "", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [0, 1, 2, 4, 8], "xbins": 4, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
       self.assertEqual(json.loads(check), json.loads(true))
 
    def test_1D_title( self ):
       check = defineHistogram('var', title='mytitle')
-      true = '{"alias": "var", "allvars": ["var"], "convention": "", "opt": "", "path": "", "title": "mytitle", "treeDef": "", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
+      true = '{"alias": "var", "allvars": ["var"], "convention": "", "merge": "", "opt": "", "path": "", "title": "mytitle", "treeDef": "", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
       self.assertEqual(json.loads(check), json.loads(true))
 
    def test_1D_labelsX( self ):
       check = defineHistogram('var', xlabels=["bin0", "bin1"])
-      true = '{"alias": "var", "allvars": ["var"], "convention": "", "opt": "", "path": "", "title": "var", "treeDef": "", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 2, "xlabels": ["bin0", "bin1"], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
+      true = '{"alias": "var", "allvars": ["var"], "convention": "", "merge": "", "opt": "", "path": "", "title": "var", "treeDef": "", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 2, "xlabels": ["bin0", "bin1"], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
       self.assertEqual(json.loads(check), json.loads(true))
 
    def test_1D_labelsY( self ):
       check = defineHistogram('var', ylabels=["bin0", "bin1"])
-      true = '{"alias": "var", "allvars": ["var"], "convention": "", "opt": "", "path": "", "title": "var", "treeDef": "", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 2, "ylabels": ["bin0", "bin1"], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
+      true = '{"alias": "var", "allvars": ["var"], "convention": "", "merge": "", "opt": "", "path": "", "title": "var", "treeDef": "", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 2, "ylabels": ["bin0", "bin1"], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
       self.assertEqual(json.loads(check), json.loads(true))
 
    def test_2D( self ):
       check = defineHistogram('varX,varY', type='TH2F', xbins=10, xmin=0.0, xmax=10.0, ybins=40, ymin=0.0, ymax=20.0)
-      true = '{"alias": "varY_vs_varX", "allvars": ["varX", "varY"], "convention": "", "opt": "", "path": "", "title": "varX,varY", "treeDef": "", "type": "TH2F", "weight": "", "cutMask": "", "xarray": [], "xbins": 10, "xlabels": [], "xmax": 10.0, "xmin": 0.0, "xvar": "varX", "yarray": [], "ybins": 40, "ylabels": [], "ymax": 20.0, "ymin": 0.0, "yvar": "varY", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
+      true = '{"alias": "varY_vs_varX", "allvars": ["varX", "varY"], "convention": "", "merge": "", "opt": "", "path": "", "title": "varX,varY", "treeDef": "", "type": "TH2F", "weight": "", "cutMask": "", "xarray": [], "xbins": 10, "xlabels": [], "xmax": 10.0, "xmin": 0.0, "xvar": "varX", "yarray": [], "ybins": 40, "ylabels": [], "ymax": 20.0, "ymin": 0.0, "yvar": "varY", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
       self.assertEqual(json.loads(check), json.loads(true))
 
    def test_2D_space( self ):
       check = defineHistogram(' varX , varY ', type='TH2F', xbins=10, xmin=0.0, xmax=10.0, ybins=40, ymin=0.0, ymax=20.0)
-      true = '{"alias": "varY_vs_varX", "allvars": ["varX", "varY"], "convention": "", "opt": "", "path": "", "title": " varX , varY ", "treeDef": "", "type": "TH2F", "weight": "", "cutMask": "", "xarray": [], "xbins": 10, "xlabels": [], "xmax": 10.0, "xmin": 0.0, "xvar": "varX", "yarray": [], "ybins": 40, "ylabels": [], "ymax": 20.0, "ymin": 0.0, "yvar": "varY", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
+      true = '{"alias": "varY_vs_varX", "allvars": ["varX", "varY"], "convention": "", "merge": "", "opt": "", "path": "", "title": " varX , varY ", "treeDef": "", "type": "TH2F", "weight": "", "cutMask": "", "xarray": [], "xbins": 10, "xlabels": [], "xmax": 10.0, "xmin": 0.0, "xvar": "varX", "yarray": [], "ybins": 40, "ylabels": [], "ymax": 20.0, "ymin": 0.0, "yvar": "varY", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
       self.assertEqual(json.loads(check), json.loads(true))
 
    def test_2D_array( self ):
       check = defineHistogram('varX,varY', 'TH2F', xbins=[0,1,2], ybins=[1,2,3,7])
-      true = '{"alias": "varY_vs_varX", "allvars": ["varX", "varY"], "convention": "", "opt": "", "path": "", "title": "varX,varY", "treeDef": "", "type": "TH2F", "weight": "", "cutMask": "", "xarray": [0, 1, 2], "xbins": 2, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "varX", "yarray": [1, 2, 3, 7], "ybins": 3, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "varY", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
+      true = '{"alias": "varY_vs_varX", "allvars": ["varX", "varY"], "convention": "", "merge": "", "opt": "", "path": "", "title": "varX,varY", "treeDef": "", "type": "TH2F", "weight": "", "cutMask": "", "xarray": [0, 1, 2], "xbins": 2, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "varX", "yarray": [1, 2, 3, 7], "ybins": 3, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "varY", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
       self.assertEqual(json.loads(check), json.loads(true))
 
    def test_2D_labelsXY( self ):
       check = defineHistogram('varX,varY', 'TH2F', xlabels=["bin0", "bin1"], ylabels=["bin0", "bin1", "bin2"])
-      true = '{"alias": "varY_vs_varX", "allvars": ["varX", "varY"], "convention": "", "opt": "", "path": "", "title": "varX,varY", "treeDef": "", "type": "TH2F", "weight": "", "cutMask": "", "xarray": [], "xbins": 2, "xlabels": ["bin0", "bin1"], "xmax": 1, "xmin": 0, "xvar": "varX", "yarray": [], "ybins": 3, "ylabels": ["bin0", "bin1", "bin2"], "ymax": 0.0, "ymin": 0.0, "yvar": "varY", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
+      true = '{"alias": "varY_vs_varX", "allvars": ["varX", "varY"], "convention": "", "merge": "", "opt": "", "path": "", "title": "varX,varY", "treeDef": "", "type": "TH2F", "weight": "", "cutMask": "", "xarray": [], "xbins": 2, "xlabels": ["bin0", "bin1"], "xmax": 1, "xmin": 0, "xvar": "varX", "yarray": [], "ybins": 3, "ylabels": ["bin0", "bin1", "bin2"], "ymax": 0.0, "ymin": 0.0, "yvar": "varY", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
       self.assertEqual(json.loads(check), json.loads(true))
 
    def test_3D( self ):
       check = defineHistogram('varX,varY,varZ', 'TProfile2D',
          xbins=10, xmin=0.0, xmax=10.0, ybins=40, ymin=0.0, ymax=20.0, zmin=-1.0, zmax=1.0)
-      true = '{"alias": "varZ_vs_varY_vs_varX", "allvars": ["varX", "varY", "varZ"], "convention": "", "opt": "", "path": "", "title": "varX,varY,varZ", "treeDef": "", "type": "TProfile2D", "weight": "", "cutMask": "", "xarray": [], "xbins": 10, "xlabels": [], "xmax": 10.0, "xmin": 0.0, "xvar": "varX", "yarray": [], "ybins": 40, "ylabels": [], "ymax": 20.0, "ymin": 0.0, "yvar": "varY", "zbins": 0, "zlabels": [], "zmax": 1.0, "zmin": -1.0, "zvar": "varZ"}'
+      true = '{"alias": "varZ_vs_varY_vs_varX", "allvars": ["varX", "varY", "varZ"], "convention": "", "merge": "", "opt": "", "path": "", "title": "varX,varY,varZ", "treeDef": "", "type": "TProfile2D", "weight": "", "cutMask": "", "xarray": [], "xbins": 10, "xlabels": [], "xmax": 10.0, "xmin": 0.0, "xvar": "varX", "yarray": [], "ybins": 40, "ylabels": [], "ymax": 20.0, "ymin": 0.0, "yvar": "varY", "zbins": 0, "zlabels": [], "zmax": 1.0, "zmin": -1.0, "zvar": "varZ"}'
       self.assertEqual(json.loads(check), json.loads(true))
 
    def test_efficiency( self ):
       check = defineHistogram('var,pass', type='TEfficiency')
-      true = '{"alias": "pass_vs_var", "allvars": ["var", "pass"], "convention": "", "opt": "", "path": "", "title": "var,pass", "treeDef": "", "type": "TEfficiency", "weight": "", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "pass", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
+      true = '{"alias": "pass_vs_var", "allvars": ["var", "pass"], "convention": "", "merge": "", "opt": "", "path": "", "title": "var,pass", "treeDef": "", "type": "TEfficiency", "weight": "", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "pass", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
       self.assertEqual(json.loads(check), json.loads(true))
 
    def test_tree( self ):
       check = defineTree('var,pass', treedef='var/F:pass/I')
-      true = '{"alias": "pass_vs_var", "allvars": ["var", "pass"], "convention": "", "opt": "", "path": "", "title": "var,pass", "treeDef": "var/F:pass/I", "type": "TTree", "weight": "", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "pass", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
+      true = '{"alias": "pass_vs_var", "allvars": ["var", "pass"], "convention": "", "merge": "", "opt": "", "path": "", "title": "var,pass", "treeDef": "var/F:pass/I", "type": "TTree", "weight": "", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "pass", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
       self.assertEqual(json.loads(check), json.loads(true))
 
    def test_offlineNamingConvention( self ):
       check = defineHistogram('var', path='EXPERT', convention='OFFLINE:lowStat')
-      true = '{"alias": "var", "allvars": ["var"], "convention": "OFFLINE:lowStat", "opt": "", "path": "EXPERT", "title": "var", "treeDef": "", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
+      true = '{"alias": "var", "allvars": ["var"], "convention": "OFFLINE:lowStat", "merge": "", "opt": "", "path": "EXPERT", "title": "var", "treeDef": "", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 100, "xlabels": [], "xmax": 1, "xmin": 0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
+      self.assertEqual(json.loads(check), json.loads(true))
+
+   def test_merge( self ):
+      check = defineHistogram('var', 'TH1F', 'EXPERT', 'title', '', '', 10, 0.0, 10.0, merge='weightedAverage')
+      true = '{"alias": "var", "allvars": ["var"], "convention": "", "merge": "weightedAverage", "opt": "", "path": "EXPERT", "title": "title", "treeDef": "", "type": "TH1F", "weight": "", "cutMask": "", "xarray": [], "xbins": 10, "xlabels": [], "xmax": 10.0, "xmin": 0.0, "xvar": "var", "yarray": [], "ybins": 0.0, "ylabels": [], "ymax": 0.0, "ymin": 0.0, "yvar": "", "zbins": 0, "zlabels": [], "zmax": 0.0, "zmin": 0.0, "zvar": ""}'
       self.assertEqual(json.loads(check), json.loads(true))
 
    def test_enforcePath( self ):
@@ -106,5 +111,13 @@ class Test( unittest.TestCase ):
       self.assertIs(check, '')
       athenaCommonFlags.isOnline = False
 
+   def test_enforceMergeTypesTest( self ):
+      from AthenaCommon.AthenaCommonFlags import athenaCommonFlags
+      with self.assertRaises(AssertionError):
+         check = defineHistogram('var,pass', type='TEfficiency', merge='weightedAverage')
+      with self.assertRaises(AssertionError):
+         check = defineHistogram('var,pass', type='TTree', merge='weightedAverage')
+     
+
 if __name__ == '__main__':
    unittest.main()
-- 
GitLab