From 7ed62d7ff819b31b2217bf65be5995cbb3bef42d Mon Sep 17 00:00:00 2001
From: Peter Sherwood <peter.sherwood@cern.ch>
Date: Sat, 5 Mar 2022 18:22:13 +0100
Subject: [PATCH 1/4] Jet hypo change the algorithm to calculate jet
 combinations

Before this commit - take all combinations sfter unrolling
The jets associated with a RepeatedCondition with repeat value > 1.
For such Conditions, now use an NchooseK index generator. This reduces,
sometimes drastically the number of combaintions due to duplicate jet indices,
or due to regenerating the same cmbination many times.

The new system uses a link list of jet indices selectors (JetStreams). The
chain is driben ba a JetStreamer instance. The elements of the list are
of type SimpleJetStream if the Condition repeat value is 1, and a CombinationsJetStreamer
otherwise.

New streamer code:
       new file:   TrigHLTJetHypo/src/CombinationsGenerator.cxx
       new file:   TrigHLTJetHypo/src/CombinationsGenerator.h
       new file:   TrigHLTJetHypo/src/CombinationsJetStream.cxx
       new file:   TrigHLTJetHypo/src/CombinationsJetStream.h
       new file:   TrigHLTJetHypo/src/IJetStream.h
       new file:   TrigHLTJetHypo/src/JetStreamer.cxx
       new file:   TrigHLTJetHypo/src/JetStreamer.h
       new file:   TrigHLTJetHypo/src/SimpleJetStream.cxx
       new file:   TrigHLTJetHypo/src/SimpleJetStream.h
       new file:   TrigHLTJetHypo/src/make_jetstream.h

Modified Jet hypo code:

       modified:   TrigHLTJetHypo/src/JetGroupProduct.cxx
       modified:   TrigHLTJetHypo/src/JetGroupProduct.h

New unit testing code

       modified:   TrigHLTJetHypoUnitTests/CMakeLists.txt
       deleted:    TrigHLTJetHypoUnitTests/tests/CombinationsGenTest.cxx
       new file:   TrigHLTJetHypoUnitTests/tests/CombinationsGeneratorTest.cxx
       modified:   TrigHLTJetHypoUnitTests/tests/JetGroupProductTest.cxx
       new file:   TrigHLTJetHypoUnitTests/tests/JetStreamerTest.cxx
       new file:   TrigHLTJetHypoUnitTests/tests/make_jetstreamTest.cxx

Updated/fixed Unit testing code
       modified:   TrigHLTJetHypoUnitTests/tests/JetGroupProductTest.cxx
       deleted:    TrigHLTJetHypoUnitTests/tests/CombinationsGenTest.cxx
---
 .../python/TrigJetHypoToolConfig.py           |   2 +-
 .../src/CombinationsGenerator.cxx             |  24 ++
 .../src/CombinationsGenerator.h               |  87 +++++
 .../src/CombinationsJetStream.cxx             |  22 ++
 .../src/CombinationsJetStream.h               | 151 ++++++++
 .../TrigHLTJetHypo/src/IJetStream.h           |  40 +++
 .../TrigHLTJetHypo/src/JetGroupProduct.cxx    |  87 +++--
 .../TrigHLTJetHypo/src/JetGroupProduct.h      |  10 +-
 .../TrigHLTJetHypo/src/JetStreamer.cxx        |  13 +
 .../TrigHLTJetHypo/src/JetStreamer.h          |  64 ++++
 .../TrigHLTJetHypo/src/SimpleJetStream.cxx    |  26 ++
 .../TrigHLTJetHypo/src/SimpleJetStream.h      | 126 +++++++
 .../TrigHLTJetHypo/src/make_jetstream.h       |  72 ++++
 .../TrigHLTJetHypoUnitTests/CMakeLists.txt    |   4 +-
 .../tests/CombinationsGenTest.cxx             |  48 ---
 .../tests/CombinationsGeneratorTest.cxx       |  76 ++++
 .../tests/JetGroupProductTest.cxx             |   2 +-
 .../tests/JetStreamerTest.cxx                 | 337 ++++++++++++++++++
 .../tests/make_jetstreamTest.cxx              | 100 ++++++
 19 files changed, 1219 insertions(+), 72 deletions(-)
 create mode 100644 Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsGenerator.cxx
 create mode 100644 Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsGenerator.h
 create mode 100644 Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.cxx
 create mode 100644 Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.h
 create mode 100644 Trigger/TrigHypothesis/TrigHLTJetHypo/src/IJetStream.h
 create mode 100644 Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetStreamer.cxx
 create mode 100644 Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetStreamer.h
 create mode 100644 Trigger/TrigHypothesis/TrigHLTJetHypo/src/SimpleJetStream.cxx
 create mode 100644 Trigger/TrigHypothesis/TrigHLTJetHypo/src/SimpleJetStream.h
 create mode 100644 Trigger/TrigHypothesis/TrigHLTJetHypo/src/make_jetstream.h
 delete mode 100644 Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/CombinationsGenTest.cxx
 create mode 100644 Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/CombinationsGeneratorTest.cxx
 create mode 100644 Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/JetStreamerTest.cxx
 create mode 100644 Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/make_jetstreamTest.cxx

diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/python/TrigJetHypoToolConfig.py b/Trigger/TrigHypothesis/TrigHLTJetHypo/python/TrigJetHypoToolConfig.py
index ffb1c6be9b63..fd9133baf121 100644
--- a/Trigger/TrigHypothesis/TrigHLTJetHypo/python/TrigJetHypoToolConfig.py
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/python/TrigJetHypoToolConfig.py
@@ -10,7 +10,7 @@ from AthenaCommon.Logging import logging
 logger = logging.getLogger(__name__)
 
 
-debug = False  # SET TO FALSE  WHEN COMMITTING
+debug = True  # SET TO FALSE  WHEN COMMITTING
 if debug:
     from AthenaCommon.Constants import DEBUG
     logger.setLevel(DEBUG)
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsGenerator.cxx b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsGenerator.cxx
new file mode 100644
index 000000000000..d833eaf7ef19
--- /dev/null
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsGenerator.cxx
@@ -0,0 +1,24 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "./CombinationsGenerator.h"
+
+std::ostream& operator << (std::ostream& os, const CombinationsGenerator& cg){
+  os << "CombinationsGenerator m_invalid " <<std::boolalpha << cg.m_invalid
+     << " bitmask len " << cg.m_bitmask.size() 
+       << " m_bitmask: ";
+  for (const auto& c : cg.m_bitmask) {
+    if (c == 0) {
+      os << 0 << " ";
+    } else if (c == 1) {
+      os << 1 << " ";
+    } else {
+      os << '?' << " ";
+    }
+  }
+  os << " m_N " << cg.m_N << " m_K " << cg.m_K;
+
+  os << '\n';
+  return os;
+}
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsGenerator.h b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsGenerator.h
new file mode 100644
index 000000000000..70977e13c40a
--- /dev/null
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsGenerator.h
@@ -0,0 +1,87 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef TRIG_HLTJETHYPO_COMBINATIONSGENERATOR_H
+#define TRIG_HLTJETHYPO_COMBINATIONSGENERATOR_H
+
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <sstream>
+
+/* 
+Combinations generator. Given n, k > 0, n>= k, bump() calculate sets 
+a bit mask of length n with k set values and n-k unset values.
+
+get() uses the bitmask to return the postions of the k set values.
+
+When all combinations havebeen exhausted by succesive calls to bump
+the process cycles. At the point of recycling a bool flag is set.
+This flag is unset on the next call to bump().
+*/
+
+class CombinationsGenerator {
+ public:
+
+  friend std::ostream& operator << (std::ostream&, const CombinationsGenerator&);
+  
+  CombinationsGenerator(std::size_t n, std::size_t k):
+    m_invalid{false}, m_N{n}, m_K(k){
+    // m_cycled{false}, m_invalid{false}{
+
+       // if n==k, std::prev_permutations never returns false,
+    // so treat as a special case
+    if (m_N == 0 or m_K > m_N) {
+      m_invalid = true;
+    } else if (m_N==m_K) {
+      m_NequalsKvec.reserve(m_K);
+      for(std::size_t i = 0u; i != m_K; ++i){
+	m_NequalsKvec.push_back(i);
+      }
+    } else if (m_K < m_N){
+      m_bitmask = std::string(m_K, 1);
+      m_bitmask.resize(m_N, 0);
+    } else {
+      m_invalid = true;
+    }
+  }
+
+    
+  std::vector<std::size_t> get() const {
+
+    if (m_K < m_N and m_K > 0) {
+      std::vector<std::size_t> comb;
+      for(std::size_t  i = 0; i < m_bitmask.size(); ++i){
+	if(m_bitmask[i]){comb.push_back(i);}
+      }
+      return comb;
+    }
+    
+    if(m_K == m_N) {return m_NequalsKvec;}
+
+    return std::vector<std::size_t>();
+  }
+
+  bool bump() {
+    // returns true if have cycled
+
+    if (m_K < m_N and m_K > 0) {
+      return  ! std::prev_permutation(m_bitmask.begin(), m_bitmask.end());
+    }
+    return true;
+  }
+
+private:
+
+  bool m_invalid;
+  std::string m_bitmask;
+
+  std::size_t m_N;
+  std::size_t m_K;
+  std::vector<std::size_t> m_NequalsKvec; 
+};
+
+std::ostream& operator << (std::ostream& os, const CombinationsGenerator& cg);
+
+#endif
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.cxx b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.cxx
new file mode 100644
index 000000000000..ca64fa05da4e
--- /dev/null
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.cxx
@@ -0,0 +1,22 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+*/
+
+
+#include "CombinationsJetStream.h"
+
+std::ostream& operator << (std::ostream& os, const CombinationsJetStream& str){
+  os << "CombinationsJetStream m_combgen " << str.m_id << '\n' 
+     << *str.m_combgen << '\n';
+  return os;
+}
+
+std::stringstream& operator << (std::stringstream& os, const CombinationsJetStream& str){
+  os << "CombinationsJetStream id: " << str.m_id
+     << " m_combgen: " << *str.m_combgen
+     <<" data: ";
+  for (const auto& d : str.m_data){os << d << " ";}
+  os <<'\n';
+  
+  return os;
+}
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.h b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.h
new file mode 100644
index 000000000000..41920b92fed7
--- /dev/null
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.h
@@ -0,0 +1,151 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef TRIGHLTJETHYPO_COMBINATIONSJETSTREAM_H
+#define TRIGHLTJETHYPO_COMBINATIONSJETSTREAM_H
+
+/*
+ * CompoundJetStream is an implementation of IJetStream.
+ * on every call to bump(), it makes available a vector of k
+ * indices chosen from a vector of n indices.
+ *
+ * The positions in the k chosen jets in the jet indices list
+ * is claculated by an instance of CombinationsGenerator, held
+ * as attribute of this class.
+ *
+ * On each call to bump(), the right neighbor is checked for
+ * having cycled. If this is rthe case, or if there is no such neighbor,
+ * the instance will bump itself by
+ * asking its  CombinationsGenerator which positions to 
+ * use, and makes these values availble for collection.
+ * When all combinations have been exhausted by succesive calls to 
+ * bump, the bump() returns true, and the cycle is restarted.
+ * 
+ */
+
+
+#include "IJetStream.h"
+#include "CombinationsGenerator.h"
+#include <vector>
+#include <sstream>
+#include <memory>
+#include <string>
+
+class CombinationsJetStream: public IJetStream {
+
+ public:
+  friend std::ostream& operator << (std::ostream&,
+				    const CombinationsJetStream&);
+  friend std::stringstream& operator << (std::stringstream&,
+					 const CombinationsJetStream&);
+
+  CombinationsJetStream(const std::vector<std::size_t>& jets,
+		  std::unique_ptr<IJetStream> neigh,
+			std::size_t k,
+			std::size_t id):
+    m_jets(jets),
+    m_neigh(std::move(neigh)),
+    m_k(k),
+    m_id{id}
+  {
+    auto n = m_jets.size();
+
+    m_valid = k <= n and !jets.empty();
+    if (m_valid) {
+      m_combgen.reset(new CombinationsGenerator(n, k));
+      auto indices = m_combgen->get();
+      m_data.clear();
+      for (const auto i : indices) {m_data.push_back(m_jets.at(i));}
+    }
+  }
+  
+  virtual std::vector<std::size_t> get() override {
+
+
+    auto result = m_neigh ? m_neigh->get() : std::vector<std::size_t>();
+    result.insert(result.end(), m_data.begin(), m_data.end());
+    return result;
+  }
+
+  
+  virtual bool bump() override {
+
+    // if there is a neighor, bump it. If it returns
+    // true, it has cycled, in which case try bumping this stream
+
+    bool cycled{false};
+    if (m_neigh) {
+      bool neigh_cycled = m_neigh->bump();
+      if (!neigh_cycled) {return false;}
+
+      cycled = m_combgen->bump();
+      // cycled = m_combgen->cycled();
+
+      // if (cycled) {
+      //	m_combgen->reset();
+      // }
+      auto indices = m_combgen->get();
+      m_data.clear();
+      for (const auto i : indices) {m_data.push_back(m_jets.at(i));}
+      return cycled;
+    } else {
+      // no neighbor
+      cycled = m_combgen->bump();
+      // cycled = m_combgen->cycled();
+      // if (cycled) {
+      // m_combgen->reset();
+      // }
+
+      auto indices = m_combgen->get();
+      m_data.clear();
+      for (const auto& i : indices) {
+	m_data.push_back(m_jets.at(i));
+      }
+      return cycled;
+    }
+  }
+
+  
+  virtual bool valid() const override {
+    if (!m_valid){return false;}
+    
+    if (m_neigh) {return m_neigh->valid();}
+    return true;
+  }
+
+  virtual std::string dump() const override {
+
+    auto result = m_neigh ? m_neigh->dump() : "";
+
+    std::stringstream ss;
+    ss << *this;
+    result += ss.str();
+    
+    return result;
+  }
+
+private:
+  std::vector<std::size_t> m_jets;
+  std::unique_ptr<IJetStream> m_neigh{nullptr};
+  std::size_t m_k;  // n choose k...
+  std::size_t m_id;
+  std::vector<std::size_t> m_data;
+  std::unique_ptr<CombinationsGenerator> m_combgen{nullptr};
+  bool m_valid{false};
+   
+ 
+  
+  bool empty() const {
+    return m_jets.empty();
+  }
+
+  };
+
+std::ostream& operator << (std::ostream&,
+			   const CombinationsJetStream&);
+
+std::stringstream& operator << (std::stringstream&,
+				const CombinationsJetStream&);
+
+ #endif
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/IJetStream.h b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/IJetStream.h
new file mode 100644
index 000000000000..2e49923d6786
--- /dev/null
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/IJetStream.h
@@ -0,0 +1,40 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef TRIGHLTJETHYPO_IJETSTREAM_H
+#define TRIGHLTJETHYPO_IJETSTREAM_H
+
+/*
+ * PABC Interface to trigger jet hypo JetStream classes
+ * In this context, jets are represented by integer indices.
+ * a jet stream steps through the available indices in a manner
+ * that is implemnented in the concrete classes. The selected
+ * indices are returned in a vector.
+ *
+ * Checks on the validity of the stream can be made after construction
+ *
+ * Concrete implementations  a pointer to a right neighbor
+ * of typeIJetStream object, and so can be an element  of a linked list.
+ * 
+ * Cycling is used by the left neighbor to decide whether the left
+ * neigbor should be bumped.
+ * 
+ * When the state of all elements of the list is cycled, the caller is
+ * informed, and will normally stop the iteration,
+ *
+ */
+
+#include <vector>
+#include <string>
+
+class IJetStream {
+ public:
+  virtual ~IJetStream(){}
+  virtual std::vector<std::size_t> get() = 0;
+  virtual bool valid() const = 0;
+  virtual bool bump() = 0;
+  virtual std::string dump() const= 0;
+};
+
+#endif
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetGroupProduct.cxx b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetGroupProduct.cxx
index 6d6f8ec79ee7..b301218327a6 100644
--- a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetGroupProduct.cxx
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetGroupProduct.cxx
@@ -3,6 +3,9 @@
 */
 
 #include "./JetGroupProduct.h"
+#include "./JetStreamer.h"
+#include "./make_jetstream.h"
+
 #include <set>
 #include <string>
 
@@ -10,11 +13,29 @@ JetGroupProduct::JetGroupProduct(const std::vector<std::size_t>& siblings,
 				 const CondInd2JetGroupsInds& satisfiedBy,
 				 const std::vector<std::size_t>& condMult) {
 
-  // copy the parts od satisfiedBy corresponding to the sibling indices
+  m_valid = !siblings.empty() or satisfiedBy.size() != condMult.size();
+  if (m_valid) {init(siblings, satisfiedBy, condMult);}
+}
+
+void JetGroupProduct::init(const std::vector<std::size_t>& siblings,
+		      const CondInd2JetGroupsInds& satisfiedBy,
+		      const std::vector<std::size_t>& condMult) {
+
+  // copy the parts of satisfiedBy corresponding to the sibling indices
   // into m_condIndices. The number of copies made per sibling is the
   // given by the sibling multiplicity.
+
+
+  
+  std::vector<std::vector<std::size_t>> condIndices;
+  condIndices.reserve(siblings.size());
+  std::vector<std::size_t> repeats;
+  condIndices.reserve(siblings.size());
+  
   for(const auto& isib : siblings){
     auto mult = condMult[isib];  // jet groups indices of satisying jgs.
+    repeats.push_back(mult);
+    condIndices.push_back(satisfiedBy.at(isib));
 
     // find the greatest jet index we will deal with
     const auto& sibjets = satisfiedBy.at(isib);
@@ -25,6 +46,7 @@ JetGroupProduct::JetGroupProduct(const std::vector<std::size_t>& siblings,
     m_jetMask.reserve(m_jetEnd);
     
     // no of copies = multiplicity of the Condition
+    
     for (std::size_t im = 0; im != mult; ++im){
       m_condIndices.push_back(satisfiedBy.at(isib));
     }
@@ -32,17 +54,23 @@ JetGroupProduct::JetGroupProduct(const std::vector<std::size_t>& siblings,
 
   // find the size for the satisfying jet group vectors.
   // these values will be used ot generate indexes into m_condIndices.
-  std::vector<std::size_t> ends;
-  ends.reserve(m_condIndices.size());
-  for(const auto& s : m_condIndices){
-    ends.push_back(s.size());
-  }
+  // std::vector<std::size_t> ends;
+  // ends.reserve(m_condIndices.size());
+  // for(const auto& s : m_condIndices){
+  //   ends.push_back(s.size());
+  //}
 
   // ProductGen is a device for calculating a tuple of indices
   // into a vector of vectors of indices. The length of the tuple
   // is the length of m_condIndices. The values of the tuple
   // are indices into the inner vectors.
-  m_productGen = ProductGen(ends);
+  // m_productGen = ProductGen(ends);
+
+  //std::vector<std::size_t> repeats(m_condIndices.size(), 1);
+
+
+  auto stream = make_jetstream(condIndices, repeats, 0);
+  m_jetstreamer.reset(new JetStreamer(std::move(stream)));
 }
   
 std::vector<std::size_t> JetGroupProduct::next(const Collector& collector){
@@ -57,6 +85,8 @@ std::vector<std::size_t> JetGroupProduct::next(const Collector& collector){
   // already been generated. If this is the first time the sequece has
   // been generated, it will be added to m_seenJetIndices, and then requrned
   // to the caller. If it has already been seen, it will be abandoned.
+
+  if (!m_valid) {return std::vector<std::size_t>();}
   
   unsigned int ipass{0};
   std::vector<std::size_t> jg_indices;
@@ -66,26 +96,43 @@ std::vector<std::size_t> JetGroupProduct::next(const Collector& collector){
     
     if(collector){
       collector->collect("JetGroupProduct::next()",
-                         "loop start pass" + std::to_string(ipass++));
+                         "loop start pass " + std::to_string(ipass++));
     }
       
-    auto indices = m_productGen.next();
-    if(indices.empty()){
-      return indices;  //an empty vector of size_t ints
-    }
+    // auto indices = m_productGen.next();
+    // if(indices.empty()){
+    //   return indices;  //an empty vector of size_t ints
+    // }
+    //
+    // // select indices from the child jet group indicies. Form a vector
+    // // of indices.
+    // bool blocked{false};
+    // for(std::size_t i = 0; i < indices.size(); ++i){
+    //   auto idx = (m_condIndices.at(i)).at(indices[i]);
+    //   if (m_jetMask[idx]) {
+    // 	blocked = true;
+    //	break;
+    //  }
+    //
+    //
+    // m_jetMask[idx] = true;
+    // }
 
-    // select indices from the child jet group indicies. Form a vector
-    // of indices.
     bool blocked{false};
-    for(std::size_t i = 0; i < indices.size(); ++i){
-      auto idx = (m_condIndices.at(i)).at(indices[i]);
-      if (m_jetMask[idx]) {
+    auto jet_indices = m_jetstreamer->next();
+    if (jet_indices.empty()) {
+      if(collector){
+      collector->collect("JetGroupProduct::next()",
+                         "end of iteration ");
+      }
+      return jet_indices;
+    }
+    for (const auto& ind : jet_indices) {
+      if (m_jetMask[ind]) {
 	blocked = true;
 	break;
       }
- 
-      
-      m_jetMask[idx] = true;
+      m_jetMask[ind] = true;
     }
 
     if (blocked){continue;}
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetGroupProduct.h b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetGroupProduct.h
index 2347b1ec06b2..35f8fbeb03fe 100644
--- a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetGroupProduct.h
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetGroupProduct.h
@@ -7,6 +7,7 @@
 
 #include "./IJetGroupProduct.h"
 #include "./ProductGen.h"
+#include "./JetStreamer.h"
 #include "./DebugInfoCollector.h"
 #include "TrigHLTJetHypo/TrigHLTJetHypoUtils/HypoJetDefs.h"
 #include <vector>
@@ -42,8 +43,15 @@ class JetGroupProduct: public IJetGroupProduct{
   std::vector<std::vector<std::size_t>>  m_condIndices;
   std::vector<bool>  m_jetMask;
   std::size_t  m_jetEnd{0};
-  ProductGen m_productGen;
+  // ProductGen m_productGen;
   std::vector<std::vector<std::size_t>> m_seenIndices;
+  std::unique_ptr<JetStreamer> m_jetstreamer{nullptr};
+  bool m_valid{false};
+
+  void init(const std::vector<std::size_t>& siblings,
+	    const CondInd2JetGroupsInds& satisfiedBy,
+	    const std::vector<std::size_t>& condMult
+	    );
 
 };
 
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetStreamer.cxx b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetStreamer.cxx
new file mode 100644
index 000000000000..6672d8a46615
--- /dev/null
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetStreamer.cxx
@@ -0,0 +1,13 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+*/
+
+
+#include "./JetStreamer.h"
+
+
+std::ostream& operator << (std::ostream& os, const JetStreamer& js){
+  os << "JetStreamer\n" <<  js.m_stream->dump() << '\n';
+  return os;
+}
+
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetStreamer.h b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetStreamer.h
new file mode 100644
index 000000000000..7c84da64ca1c
--- /dev/null
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetStreamer.h
@@ -0,0 +1,64 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef TRIGHLTJETHYPO_JETSTEAMER_H
+#define TRIGHLTJETHYPO_JETSTEAMER_H
+
+/*
+ * JetStreamer owns a linked list  of IJetStream objects.
+ * These provide vectors of indices according to the conrete types.
+ * The stream objects cycle, which allows for stepping through
+ * all possible combinations of jets. On each call to bump an element 
+ * in the list informs its left neigbor if the fact. The
+ * the cycle state is relayed back to JetStreamer: when all elements
+ * in the list have cycled, the iteration stops.
+ */
+
+#include "./IJetStream.h"
+#include "./SimpleJetStream.h"
+#include "./CombinationsJetStream.h"
+
+#include <sstream>
+#include <ostream>
+
+class JetStreamer {
+
+ public:
+
+  friend std::ostream& operator << (std::ostream&, const JetStreamer&);
+  friend std::ostream& operator << (std::stringstream&, const JetStreamer&);
+
+  
+ JetStreamer(std::unique_ptr<IJetStream>&& stream) :
+   m_stream(std::move(stream))
+  {
+    m_valid  = m_stream != nullptr and  m_stream->valid();
+  }
+  
+  
+  std::vector<std::size_t> next() {
+
+    if (!m_valid) {
+      return std::vector<std::size_t>();
+    }
+
+    if (m_done) {return  std::vector<std::size_t>();}
+    auto result = m_stream->get(); // stream always as legal data
+
+    m_done = m_stream->bump();
+    
+    return result;
+  }
+
+  bool isValid() const {return m_valid;}
+  
+ private:
+  std::unique_ptr<IJetStream> m_stream;
+  bool m_done{false};
+  bool m_valid{false};
+};
+
+std::ostream& operator << (std::ostream&, const JetStreamer&);
+
+#endif
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/SimpleJetStream.cxx b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/SimpleJetStream.cxx
new file mode 100644
index 000000000000..99bfe5e04109
--- /dev/null
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/SimpleJetStream.cxx
@@ -0,0 +1,26 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "./SimpleJetStream.h"
+
+std::ostream& operator << (std::ostream& os ,
+			   const SimpleJetStream& js) {
+
+  os << "SimpleJetStream id " << js.m_id
+     << " m_valid "  << std::boolalpha << js.m_valid
+     << " no of jets: " << js.m_jets.size()
+     << " m_ind "  << js.m_ind;
+  return os;
+}
+
+std::stringstream& operator << (std::stringstream& os ,
+				const SimpleJetStream& js) {
+
+  os << "SimpleJetStream id " << js.m_id
+     << " m_valid "  << std::boolalpha << js.m_valid
+     << " no of jets: " << js.m_jets.size()
+     << " m_ind "  << js.m_ind;
+  return os;
+}
+
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/SimpleJetStream.h b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/SimpleJetStream.h
new file mode 100644
index 000000000000..352661ab4a99
--- /dev/null
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/SimpleJetStream.h
@@ -0,0 +1,126 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef TRIGHLTJETHYPO_SIMPLEJETSTREAM_H
+#define TRIGHLTJETHYPO_SIMPLEJETSTREAM_H
+
+
+/*
+ * SimpleJetStream is an implementation of IJetStream.
+
+ * On each call to bump(), it steps through its container of jet indices,
+ * making the current value
+ * available for collection. When it reaches the end, and moves
+ * back to the begining of its list. bump() returns the true when it has
+ * cycled, otherwise it returns false
+ * 
+ */
+
+#include "IJetStream.h"
+#include <vector>
+#include <sstream>
+#include <memory>
+#include <string>
+
+class SimpleJetStream: public IJetStream {
+
+ public:
+
+
+  friend std::ostream& operator << (std::ostream&,
+				    const SimpleJetStream&);
+  friend std::stringstream& operator << (std::stringstream&,
+					 const SimpleJetStream&);
+
+  SimpleJetStream(const std::vector<std::size_t>& jets,
+		  std::unique_ptr<IJetStream> neigh,
+		  std::size_t id):
+    m_jets(jets),
+    m_neigh(std::move(neigh)),
+    m_id{id}
+  {
+    m_valid = !jets.empty();
+    if(m_valid) {
+      m_data = m_jets.at(0);
+      m_ind = 0;
+    }
+  }
+  
+  
+  virtual std::vector<std::size_t> get() override {
+
+    auto result = m_neigh ? m_neigh->get() : std::vector<std::size_t>();
+    result.push_back(m_data);
+
+    return result;
+  }
+  
+  virtual bool bump() override {
+    // if there is a neighbor, try bumping it.
+    bool cycled{false};
+    if (m_neigh) {
+      bool neigh_cycled = m_neigh->bump();
+
+      if (!neigh_cycled) {return false;}
+      
+      // neighbor has cycled as a result of bumping.
+	++m_ind;
+	cycled = m_ind == m_jets.size();
+	
+	if (cycled) {m_ind = 0;}
+	
+	m_data = m_jets.at(m_ind);
+	return cycled;
+    }  else {
+    
+      // no neighbor
+
+      ++m_ind;
+
+      cycled = m_ind == m_jets.size();
+  
+      if(cycled) {
+	m_ind = 0;
+      }
+      m_data = m_jets.at(m_ind);
+      return cycled;
+    }
+  }
+
+
+
+  virtual bool valid() const override {
+    if (!m_valid) {return false;}
+    
+    if (m_neigh) {return m_neigh->valid();}
+    return true;
+  }
+
+  virtual std::string dump() const override {
+    std::stringstream ss;
+
+    auto result = m_neigh ? m_neigh->dump() : "";
+
+    ss<< *this << '\n';
+    result += ss.str();
+    
+    return result;
+  }
+
+private:
+  std::vector<std::size_t> m_jets;
+  std::size_t m_ind{0};
+  std::unique_ptr<IJetStream> m_neigh{nullptr};
+  std::size_t m_id;
+  std::size_t m_data;
+  bool m_valid{false};
+
+};
+
+std::ostream& operator << (std::ostream& os ,
+			   const SimpleJetStream& js);
+std::stringstream& operator << (std::stringstream& os ,
+				const SimpleJetStream& js);
+
+#endif
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/make_jetstream.h b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/make_jetstream.h
new file mode 100644
index 000000000000..ec46fec8a166
--- /dev/null
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/make_jetstream.h
@@ -0,0 +1,72 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef TRIGHLTJETHYPO_MAKE_JETSTREAM_H
+#define TRIGHLTJETHYPO_MAKE_JETSTREAM_H
+
+/*
+ * make_jetstream creates a linled list of IJetStreams. 
+ * The first arguement of type  vector<vector<std::size>> contains 
+ * jet indices that pass a RepeatedCondition. There is one entry in the outer
+ * vector per Condition being considered.
+ *
+ * The vector<std:size_t> contains the repeat values of the RepeatedConditions.
+ *
+ * The concrete types in the list
+ * will be a SimpleJetStream if the repeat value for the corresponding
+ * Condition is 1, and a CombinationsJetStream otherwise.
+ */
+
+#include "SimpleJetStream.h"
+#include "CombinationsJetStream.h"
+#include "JetStreamer.h"
+#include <memory>
+#include <vector>
+
+std::unique_ptr<IJetStream>
+make_jetstream(std::vector<std::vector<std::size_t>> indices,
+	    std::vector<std::size_t> repeats,
+	    std::size_t sid) {
+  if (indices.size()==1) {
+    auto null_stream  = std::unique_ptr<IJetStream>{nullptr};
+    auto base_case = std::unique_ptr<IJetStream>(nullptr);
+    auto repeat = repeats.at(0);
+    if (repeat == 1) {
+      base_case.reset(new SimpleJetStream(indices.at(0),
+					  std::move(null_stream),
+					  sid));
+    } else {
+      base_case.reset(new CombinationsJetStream(indices.at(0),
+						std::move(null_stream),
+						repeat,
+						sid));
+    }
+    return base_case;
+		    
+  }
+
+  auto inds = indices.back();
+  indices.pop_back();
+
+  auto repeat = repeats.back();
+  repeats.pop_back();
+  
+  auto n_sid = sid;
+  auto right_stream =  make_jetstream(indices, repeats, ++n_sid);
+  auto stream = std::unique_ptr<IJetStream>(nullptr);
+  if (repeat == 1) {
+    stream.reset(new SimpleJetStream(inds,
+				     std::move(right_stream),
+				     sid));
+  } else {
+        stream.reset(new CombinationsJetStream(inds,
+					       std::move(right_stream),
+					       repeat,
+					       sid));
+  }
+  
+  return stream;
+}
+					     
+#endif
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/CMakeLists.txt b/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/CMakeLists.txt
index 7a86a159f64f..4618282a5b57 100644
--- a/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/CMakeLists.txt
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/CMakeLists.txt
@@ -23,7 +23,7 @@ atlas_add_component( TrigHLTJetHypoUnitTests
 # Test(s) in the package:
 atlas_add_test( TrigHLTJetHypoUnitTests
    SOURCES tests/all_tests.cxx
-   tests/CombinationsGenTest.cxx
+   tests/CombinationsGeneratorTest.cxx
    tests/ProductGenTest.cxx
    tests/JetGroupProductTest.cxx
    tests/JetGroupUnionTest.cxx
@@ -35,6 +35,8 @@ atlas_add_test( TrigHLTJetHypoUnitTests
    tests/PassThroughFilterTest.cxx
    tests/ConditionFilterTest.cxx
    tests/MultiFilterTest.cxx
+   tests/JetStreamerTest.cxx
+   tests/make_jetstreamTest.cxx
    INCLUDE_DIRS ${GMOCK_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS} ${ROOT_INCLUDE_DIRS}
    LINK_LIBRARIES ${GMOCK_LIBRARIES} ${GTEST_LIBRARIES} ${ROOT_LIBRARIES} TrigHLTJetHypoLib TrigHLTJetHypoUnitTestsLib xAODJet
    POST_EXEC_SCRIPT nopost.sh )
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/CombinationsGenTest.cxx b/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/CombinationsGenTest.cxx
deleted file mode 100644
index d4effc409ba3..000000000000
--- a/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/CombinationsGenTest.cxx
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
-*/
-
-#include "gtest/gtest.h"
-#include "TrigHLTJetHypo/TrigHLTJetHypoUtils/CombinationsGen.h"
-#include <vector>
-
-using res = std::pair<std::vector<unsigned int>, bool>;
-using vec = std::vector<unsigned int>;
-
-TEST(CombinationsGenTester, n3k1) { 
-  CombinationsGen gen(3,1);
-  EXPECT_EQ (res(vec{0}, true), gen.next());
-  EXPECT_EQ (res(vec{1}, true), gen.next());
-  EXPECT_EQ (res(vec{2}, true), gen.next());
-  res r =  gen.next();
-  EXPECT_EQ (r.second, false);
-}
-
-TEST(CombinationsGenTester, n3k2) { 
-  CombinationsGen gen(3,2);
-  EXPECT_EQ (res(vec{0,1}, true), gen.next());
-  EXPECT_EQ (res(vec{0,2}, true), gen.next());
-  EXPECT_EQ (res(vec{1,2}, true), gen.next());
-  res r =  gen.next();
-  EXPECT_EQ (r.second, false);
-}
-
-
-TEST(CombinationsGenTester, n3k3) { 
-  CombinationsGen gen(3,3);
-  EXPECT_EQ (res(vec{0,1, 2}, true), gen.next());
-  res r =  gen.next();
-  EXPECT_EQ (r.second, false);
-}
-
-TEST(CombinationsGenTester, n3k0) { 
-  CombinationsGen gen(3,0);
-  EXPECT_EQ (res(vec{}, true), gen.next());
-  res r =  gen.next();
-  EXPECT_EQ (r.second, false);
-}
-
-TEST(CombinationsGenTester, n3kgtn) { 
-  CombinationsGen gen(3,4);
-  EXPECT_FALSE(gen.next().second);
-}
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/CombinationsGeneratorTest.cxx b/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/CombinationsGeneratorTest.cxx
new file mode 100644
index 000000000000..ac959e1ae02b
--- /dev/null
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/CombinationsGeneratorTest.cxx
@@ -0,0 +1,76 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "gtest/gtest.h"
+#include "TrigHLTJetHypo/../src/CombinationsGenerator.h"
+#include <vector>
+
+using vec = std::vector<size_t>;
+
+TEST(CombinationsGeneratorTester, n3k1) { 
+  CombinationsGenerator gen(3,1);
+  // return value from bump() says whether the generator has cycled.
+  EXPECT_EQ (vec{0}, gen.get());
+  EXPECT_EQ (false, gen.bump());
+  EXPECT_EQ (vec{1}, gen.get());
+  EXPECT_EQ (false, gen.bump());
+  EXPECT_EQ (vec{2}, gen.get());
+  EXPECT_EQ (true, gen.bump());
+}
+
+TEST(CombinationsGeneratorTester, n3k2) { 
+  CombinationsGenerator gen(3,2);
+  vec v0 {0,1};
+  vec v1 {0,2};
+  vec v2 {1,2};
+  EXPECT_EQ (v0, gen.get());
+  EXPECT_EQ (false, gen.bump());
+  EXPECT_EQ (v1, gen.get());
+  EXPECT_EQ (false, gen.bump());
+  EXPECT_EQ (v2, gen.get());
+  EXPECT_EQ (true, gen.bump());
+}
+
+TEST(CombinationsGeneratorTester, n3k3) { 
+  CombinationsGenerator gen(3,3);
+  vec v0 {0,1,2};
+  EXPECT_EQ (v0, gen.get());
+  EXPECT_EQ (true, gen.bump());
+}
+
+TEST(CombinationsGeneratorTester, n3k0) { 
+  CombinationsGenerator gen(3,0);
+  vec v0 {};
+  EXPECT_EQ (v0, gen.get());
+  EXPECT_EQ (true, gen.bump());
+}
+
+TEST(CombinationsGeneratorTester, n3k4) { 
+  CombinationsGenerator gen(3,4);
+  vec v0 {};
+  EXPECT_EQ (v0, gen.get());
+  EXPECT_EQ (true, gen.bump());
+}
+
+
+/*
+TEST(CombinationsGeneratorTester, n3k3) { 
+  CombinationsGenerator gen(3,3);
+  EXPECT_EQ (res(vec{0,1, 2}, true), gen.next());
+  res r =  gen.next();
+  EXPECT_EQ (r.second, false);
+cd}
+
+TEST(CombinationsGeneratorTester, n3k0) { 
+  CombinationsGenerator gen(3,0);
+  EXPECT_EQ (res(vec{}, true), gen.next());
+  res r =  gen.next();
+  EXPECT_EQ (r.second, false);
+}
+
+TEST(CombinationsGeneratorTester, n3kgtn) { 
+  CombinationsGenerator gen(3,4);
+  EXPECT_FALSE(gen.next().second);
+}
+*/
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/JetGroupProductTest.cxx b/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/JetGroupProductTest.cxx
index 03b8d76f59fd..3d099f22b032 100644
--- a/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/JetGroupProductTest.cxx
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/JetGroupProductTest.cxx
@@ -24,7 +24,7 @@ TEST(JetGroupProductTester, empty) {
 
 
 
-TEST(JetGroupProductTester, onecond) {
+TEST(JetGroupProductTester, onecondition) {
   std::vector<std::size_t> siblings{0};
 
   CondInd2JetGroupsInds satisfiedBy;
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/JetStreamerTest.cxx b/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/JetStreamerTest.cxx
new file mode 100644
index 000000000000..517e1d6c677b
--- /dev/null
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/JetStreamerTest.cxx
@@ -0,0 +1,337 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "gtest/gtest.h"
+#include "TrigHLTJetHypo/../src/JetStreamer.h"
+#include "TrigHLTJetHypo/../src/IJetStream.h"
+#include <vector>
+#include <memory>
+
+using vec = std::vector<std::size_t>;
+
+std::unique_ptr<JetStreamer> make_streamer(int test){
+  
+  auto null_stream  = std::unique_ptr<IJetStream>{nullptr};
+
+  auto streamer = std::unique_ptr<JetStreamer> (nullptr);
+
+  if (test == 0) {
+    std::vector<std::size_t> jets0 {1, 2};
+
+    std::cerr <<"test0\n";
+   
+    auto stream0 = std::make_unique<SimpleJetStream>(jets0,
+						     std::move(null_stream),
+						     0
+						     );
+    streamer = std::make_unique<JetStreamer>(std::move(stream0));
+    
+    return streamer;
+  } else if (test == 1) {
+
+    std::vector<std::size_t> jets_cs {5, 6, 7};
+    std::size_t k2{2};
+
+    auto stream0 =
+      std::make_unique<CombinationsJetStream>(jets_cs,
+					      std::move(null_stream),
+					      k2,
+					      0
+					      );
+    streamer.reset(new JetStreamer(std::move(stream0)));
+    return streamer;
+  } else if (test == 2) {
+
+    std::vector<std::size_t> jets0 {1, 2};
+    std::vector<std::size_t> jets1 {3, 4};
+    auto stream1 =
+      std::make_unique<SimpleJetStream>(jets1,
+					std::move(null_stream),
+					1
+					);
+
+          
+    auto stream0 =
+      std::make_unique<SimpleJetStream>(jets0,
+					std::move(stream1),
+					0
+					);
+    
+    streamer.reset(new JetStreamer(std::move(stream0)));
+    return streamer;
+  } else if (test == 3) {
+    
+    std::vector<std::size_t> jets_cs {5, 6, 7};
+    std::size_t k2{2};
+    std::vector<std::size_t> jets0 {1, 2};
+
+
+      
+    auto stream1 =
+      std::make_unique<CombinationsJetStream>(jets_cs,
+					      std::move(null_stream),
+					      k2,
+					      1
+					      );
+
+          
+    auto stream0 =
+      std::make_unique<SimpleJetStream>(jets0,
+					std::move(stream1),
+					0
+					);
+    
+    streamer.reset(new JetStreamer(std::move(stream0)));
+    return streamer;
+    } else if (test == 4) {
+        
+    std::vector<std::size_t> jets_cs {5, 6, 7};
+    std::size_t k2{2};
+    std::vector<std::size_t> jets0 {1, 2};
+
+    
+    auto stream1 =
+      std::make_unique<SimpleJetStream>(jets0,
+					std::move(null_stream),
+					1
+					);
+    
+    
+    auto stream0 =
+      std::make_unique<CombinationsJetStream>(jets_cs,
+					      std::move(stream1),
+					      k2,
+					      0
+					      );
+    
+    streamer.reset(new JetStreamer(std::move(stream0)));
+    return streamer;
+  } else if (test == 5) {
+
+            
+    std::vector<std::size_t> jets_cs {5, 6, 7};
+    std::size_t k2{2};
+    std::vector<std::size_t> jets0 {1, 2};
+    std::vector<std::size_t> jets1 {3, 4};
+
+    auto stream2 =
+      std::make_unique<SimpleJetStream>(jets1,
+					std::move(null_stream),
+					2
+					);
+      
+    auto stream1 =
+      std::make_unique<CombinationsJetStream>(jets_cs,
+					      std::move(stream2),
+					      k2,
+					      1
+					      );
+
+          
+    auto stream0 =
+      std::make_unique<SimpleJetStream>(jets0,
+					std::move(stream1),
+					0
+					);
+    
+    streamer.reset(new JetStreamer(std::move(stream0)));
+  
+  return streamer;
+   } else if (test == 6) {
+
+            
+    std::vector<std::size_t> jets_cs {};
+    std::size_t k2{2};
+    std::vector<std::size_t> jets0 {1, 2};
+    std::vector<std::size_t> jets1 {3, 4};
+
+    auto stream2 =
+      std::make_unique<SimpleJetStream>(jets1,
+					std::move(null_stream),
+					2
+					);
+      
+    auto stream1 =
+      std::make_unique<CombinationsJetStream>(jets_cs,
+					      std::move(stream2),
+					      k2,
+					      1
+					      );
+
+          
+    auto stream0 =
+      std::make_unique<SimpleJetStream>(jets0,
+					std::move(stream1),
+					0
+					);
+    
+    streamer.reset(new JetStreamer(std::move(stream0)));
+  
+    return streamer;
+ } else if (test == 7) {
+
+            
+    std::vector<std::size_t> jets_cs {5, 6, 7};
+    std::size_t k4{4};
+    std::vector<std::size_t> jets0 {1, 2};
+    std::vector<std::size_t> jets1 {3, 4};
+
+    auto stream2 =
+      std::make_unique<SimpleJetStream>(jets1,
+					std::move(null_stream),
+					2
+					);
+      
+    auto stream1 =
+      std::make_unique<CombinationsJetStream>(jets_cs,
+					      std::move(stream2),
+					      k4,
+					      1
+					      );
+
+          
+    auto stream0 =
+      std::make_unique<SimpleJetStream>(jets0,
+					std::move(stream1),
+					0
+					);
+    
+    streamer.reset(new JetStreamer(std::move(stream0)));
+  
+    return streamer;
+    } else if (test == 8) {
+
+    auto stream0 = std::unique_ptr<IJetStream>(nullptr);
+    streamer.reset(new JetStreamer(std::move(stream0)));
+    return streamer;
+  } else {
+  std::cerr << "unknown test << " << test << '\n';
+  return streamer;
+  }
+}
+
+TEST(JetStreamerTester, oneSimpleJetStream) {
+  // Cycle around the input vector
+  auto streamer = make_streamer(0);
+  EXPECT_EQ (vec{1}, streamer->next());
+  EXPECT_EQ (vec{2}, streamer->next());
+  EXPECT_EQ (vec{}, streamer->next());
+}
+
+
+TEST(JetStreamerTester, oneCombinationsJetStream) {
+  // Cycle around the input vector
+  auto streamer = make_streamer(1);
+  vec v0{5, 6};
+  vec v1{5, 7};
+  vec v2{6, 7};
+  EXPECT_EQ (v0, streamer->next());
+  EXPECT_EQ (v1, streamer->next());
+  EXPECT_EQ (v2, streamer->next());
+  EXPECT_EQ (vec{}, streamer->next());
+}
+
+
+TEST(JetStreamerTester, twoSimpleJetStreams) {
+  // Cycle around the input vector
+  auto streamer = make_streamer(2);
+  vec v0{3, 1};
+  vec v1{4, 1};
+  vec v2{3, 2};
+  vec v3{4, 2};
+
+  EXPECT_EQ (v0, streamer->next());
+  EXPECT_EQ (v1, streamer->next());
+  EXPECT_EQ (v2, streamer->next());
+  EXPECT_EQ (v3, streamer->next());
+  EXPECT_EQ (vec{}, streamer->next());
+}
+
+TEST(JetStreamerTester, SimpleThenCombinationsJetStreams) {
+  // Cycle around the input vector
+  auto streamer = make_streamer(3);
+  vec v0{5, 6, 1};
+  vec v1{5, 7, 1};
+  vec v2{6, 7, 1};
+  vec v3{5, 6, 2};
+  vec v4{5, 7, 2};
+  vec v5{6, 7, 2};
+
+  EXPECT_EQ (v0, streamer->next());
+  EXPECT_EQ (v1, streamer->next());
+  EXPECT_EQ (v2, streamer->next());
+  EXPECT_EQ (v3, streamer->next());
+  EXPECT_EQ (v4, streamer->next());
+  EXPECT_EQ (v5, streamer->next());
+  EXPECT_EQ (vec{}, streamer->next());
+}
+
+TEST(JetStreamerTester, CombinationsThenSimpleJetStreams) {
+  // Cycle around the input vector
+  auto streamer = make_streamer(4);
+  vec v0{1, 5, 6};
+  vec v1{2, 5, 6};
+  vec v2{1, 5, 7};
+  vec v3{2, 5, 7};
+  vec v4{1, 6, 7};
+  vec v5{2, 6, 7};
+
+  EXPECT_EQ (v0, streamer->next());
+  EXPECT_EQ (v1, streamer->next());
+  EXPECT_EQ (v2, streamer->next());
+  EXPECT_EQ (v3, streamer->next());
+  EXPECT_EQ (v4, streamer->next());
+  EXPECT_EQ (v5, streamer->next());
+  EXPECT_EQ (vec{}, streamer->next());
+}
+
+TEST(JetStreamerTester, SimmpleThenCombinationsThenSimpleJetStreams) {
+  // Cycle around the input vector
+  auto streamer = make_streamer(5);
+  vec v0{3, 5, 6, 1};
+  vec v1{4, 5, 6, 1};
+  vec v2{3, 5, 7, 1};
+  vec v3{4, 5, 7, 1};
+  vec v4{3, 6, 7, 1};
+  vec v5{4, 6, 7, 1};
+  vec v6{3, 5, 6, 2};
+  vec v7{4, 5, 6, 2};
+  vec v8{3, 5, 7, 2};
+  vec v9{4, 5, 7, 2};
+  vec v10{3, 6, 7, 2};
+  vec v11{4, 6, 7, 2};
+
+  EXPECT_EQ (v0, streamer->next());
+  EXPECT_EQ (v1, streamer->next());
+  EXPECT_EQ (v2, streamer->next());
+  EXPECT_EQ (v3, streamer->next());
+  EXPECT_EQ (v4, streamer->next());
+  EXPECT_EQ (v5, streamer->next());
+  EXPECT_EQ (v6, streamer->next());
+  EXPECT_EQ (v7, streamer->next());
+  EXPECT_EQ (v8, streamer->next());
+  EXPECT_EQ (v9, streamer->next());
+  EXPECT_EQ (v10, streamer->next());
+  EXPECT_EQ (v11, streamer->next());
+  EXPECT_EQ (vec{}, streamer->next());
+}
+
+TEST(JetStreamerTester, InvalidJetIndices) {
+  // Cycle around the input vector
+  auto streamer = make_streamer(6);
+  EXPECT_EQ (vec{}, streamer->next());
+}
+
+
+TEST(JetStreamerTester, InvalidNchooseK) {
+  // Cycle around the input vector
+  auto streamer = make_streamer(7);
+  EXPECT_EQ (vec{}, streamer->next());
+}
+
+TEST(JetStreamerTester, InvalidJetStream) {
+  // Cycle around the input vector
+  auto streamer = make_streamer(8);
+  EXPECT_EQ (vec{}, streamer->next());
+}
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/make_jetstreamTest.cxx b/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/make_jetstreamTest.cxx
new file mode 100644
index 000000000000..2b5c5c4b4f9c
--- /dev/null
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/make_jetstreamTest.cxx
@@ -0,0 +1,100 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "gtest/gtest.h"
+#include "TrigHLTJetHypo/../src/make_jetstream.h"
+#include <vector>
+#include <memory>
+
+using vec = std::vector<std::size_t>;
+
+TEST(make_jetstreamTester, SimpleSimpleSimpleChain) {
+
+  std::vector<std::vector<std::size_t>> v;
+  v.push_back(std::vector<std::size_t>{1,2});
+  v.push_back(std::vector<std::size_t>{3,4, 5});
+  v.push_back(std::vector<std::size_t>{6,7});
+
+  std::vector<std::size_t> repeats{1,1,1};
+
+  std::size_t sid{0};
+
+  auto stream = make_jetstream(v, repeats, sid);
+
+  JetStreamer streamer(std::move(stream));
+
+  vec v0{1, 3, 6}; 
+  vec v1{2, 3, 6}; 
+  vec v2{1, 4, 6}; 
+  vec v3{2, 4, 6}; 
+  vec v4{1, 5, 6}; 
+  vec v5{2, 5, 6}; 
+  vec v6{1, 3, 7}; 
+  vec v7{2, 3, 7}; 
+  vec v8{1, 4, 7}; 
+  vec v9{2, 4, 7}; 
+  vec v10{1, 5, 7}; 
+  vec v11{2, 5, 7}; 
+
+
+  EXPECT_EQ (v0, streamer.next());
+  EXPECT_EQ (v1, streamer.next());
+  EXPECT_EQ (v2, streamer.next());
+  EXPECT_EQ (v3, streamer.next());
+  EXPECT_EQ (v4, streamer.next());
+  EXPECT_EQ (v5, streamer.next());
+  EXPECT_EQ (v6, streamer.next());
+  EXPECT_EQ (v7, streamer.next());
+  EXPECT_EQ (v8, streamer.next());
+  EXPECT_EQ (v9, streamer.next());
+  EXPECT_EQ (v10, streamer.next());
+  EXPECT_EQ (v11, streamer.next());
+  
+  EXPECT_EQ (vec{}, streamer.next());
+}
+
+
+TEST(make_jetstreamTester, SimpleCompoundSimpleChain) {
+ std::vector<std::vector<std::size_t>> v;
+  v.push_back(std::vector<std::size_t>{1,2});
+  v.push_back(std::vector<std::size_t>{3,4, 5});
+  v.push_back(std::vector<std::size_t>{6,7});
+
+  std::vector<std::size_t> repeats{1,2,1};
+
+  std::size_t sid{0};
+
+  auto stream = make_jetstream(v, repeats, sid);
+
+  JetStreamer streamer(std::move(stream));
+
+  vec v0{1, 3, 4, 6}; 
+  vec v1{2, 3, 4, 6}; 
+  vec v2{1, 3, 5, 6}; 
+  vec v3{2, 3, 5, 6}; 
+  vec v4{1, 4, 5, 6}; 
+  vec v5{2, 4, 5, 6}; 
+  vec v6{1, 3, 4, 7}; 
+  vec v7{2, 3, 4, 7}; 
+  vec v8{1, 3, 5, 7}; 
+  vec v9{2, 3, 5, 7}; 
+  vec v10{1, 4, 5, 7}; 
+  vec v11{2, 4, 5, 7}; 
+
+  EXPECT_EQ (v0, streamer.next());
+  EXPECT_EQ (v1, streamer.next());
+  EXPECT_EQ (v2, streamer.next());
+  EXPECT_EQ (v3, streamer.next());
+  EXPECT_EQ (v4, streamer.next());
+  EXPECT_EQ (v5, streamer.next());
+  EXPECT_EQ (v6, streamer.next());
+  EXPECT_EQ (v7, streamer.next());
+  EXPECT_EQ (v8, streamer.next());
+  EXPECT_EQ (v9, streamer.next());
+  EXPECT_EQ (v10, streamer.next());
+  EXPECT_EQ (v11, streamer.next());
+  
+  EXPECT_EQ (vec{}, streamer.next());
+}
+
-- 
GitLab


From f91b24f76e04a435b1f00c2bf7454c1452d1a75a Mon Sep 17 00:00:00 2001
From: Peter Sherwood <peter.sherwood@cern.ch>
Date: Sat, 5 Mar 2022 21:52:12 +0100
Subject: [PATCH 2/4] set the jet hypo debug flag to False

---
 .../TrigHLTJetHypo/python/TrigJetHypoToolConfig.py              | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/python/TrigJetHypoToolConfig.py b/Trigger/TrigHypothesis/TrigHLTJetHypo/python/TrigJetHypoToolConfig.py
index fd9133baf121..ffb1c6be9b63 100644
--- a/Trigger/TrigHypothesis/TrigHLTJetHypo/python/TrigJetHypoToolConfig.py
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/python/TrigJetHypoToolConfig.py
@@ -10,7 +10,7 @@ from AthenaCommon.Logging import logging
 logger = logging.getLogger(__name__)
 
 
-debug = True  # SET TO FALSE  WHEN COMMITTING
+debug = False  # SET TO FALSE  WHEN COMMITTING
 if debug:
     from AthenaCommon.Constants import DEBUG
     logger.setLevel(DEBUG)
-- 
GitLab


From b608f45813fef1bf77627066667acfdcd80e6caa Mon Sep 17 00:00:00 2001
From: Peter Sherwood <peter.sherwood@cern.ch>
Date: Mon, 7 Mar 2022 15:55:05 +0100
Subject: [PATCH 3/4] jet hypo - remove commented-out code

---
 .../src/CombinationsGenerator.h               |  3 +-
 .../src/CombinationsJetStream.h               |  4 ---
 .../TrigHLTJetHypo/src/JetGroupProduct.cxx    | 34 -------------------
 .../tests/CombinationsGeneratorTest.cxx       | 19 -----------
 4 files changed, 1 insertion(+), 59 deletions(-)

diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsGenerator.h b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsGenerator.h
index 70977e13c40a..b7ea1d090489 100644
--- a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsGenerator.h
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsGenerator.h
@@ -28,9 +28,8 @@ class CombinationsGenerator {
   
   CombinationsGenerator(std::size_t n, std::size_t k):
     m_invalid{false}, m_N{n}, m_K(k){
-    // m_cycled{false}, m_invalid{false}{
 
-       // if n==k, std::prev_permutations never returns false,
+    // if n==k, std::prev_permutations never returns false,
     // so treat as a special case
     if (m_N == 0 or m_K > m_N) {
       m_invalid = true;
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.h b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.h
index 41920b92fed7..163b82fe01aa 100644
--- a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.h
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.h
@@ -80,11 +80,7 @@ class CombinationsJetStream: public IJetStream {
       if (!neigh_cycled) {return false;}
 
       cycled = m_combgen->bump();
-      // cycled = m_combgen->cycled();
 
-      // if (cycled) {
-      //	m_combgen->reset();
-      // }
       auto indices = m_combgen->get();
       m_data.clear();
       for (const auto i : indices) {m_data.push_back(m_jets.at(i));}
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetGroupProduct.cxx b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetGroupProduct.cxx
index b301218327a6..4d0672e88c86 100644
--- a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetGroupProduct.cxx
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/JetGroupProduct.cxx
@@ -52,22 +52,6 @@ void JetGroupProduct::init(const std::vector<std::size_t>& siblings,
     }
   }
 
-  // find the size for the satisfying jet group vectors.
-  // these values will be used ot generate indexes into m_condIndices.
-  // std::vector<std::size_t> ends;
-  // ends.reserve(m_condIndices.size());
-  // for(const auto& s : m_condIndices){
-  //   ends.push_back(s.size());
-  //}
-
-  // ProductGen is a device for calculating a tuple of indices
-  // into a vector of vectors of indices. The length of the tuple
-  // is the length of m_condIndices. The values of the tuple
-  // are indices into the inner vectors.
-  // m_productGen = ProductGen(ends);
-
-  //std::vector<std::size_t> repeats(m_condIndices.size(), 1);
-
 
   auto stream = make_jetstream(condIndices, repeats, 0);
   m_jetstreamer.reset(new JetStreamer(std::move(stream)));
@@ -99,24 +83,6 @@ std::vector<std::size_t> JetGroupProduct::next(const Collector& collector){
                          "loop start pass " + std::to_string(ipass++));
     }
       
-    // auto indices = m_productGen.next();
-    // if(indices.empty()){
-    //   return indices;  //an empty vector of size_t ints
-    // }
-    //
-    // // select indices from the child jet group indicies. Form a vector
-    // // of indices.
-    // bool blocked{false};
-    // for(std::size_t i = 0; i < indices.size(); ++i){
-    //   auto idx = (m_condIndices.at(i)).at(indices[i]);
-    //   if (m_jetMask[idx]) {
-    // 	blocked = true;
-    //	break;
-    //  }
-    //
-    //
-    // m_jetMask[idx] = true;
-    // }
 
     bool blocked{false};
     auto jet_indices = m_jetstreamer->next();
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/CombinationsGeneratorTest.cxx b/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/CombinationsGeneratorTest.cxx
index ac959e1ae02b..c472146adfdc 100644
--- a/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/CombinationsGeneratorTest.cxx
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/CombinationsGeneratorTest.cxx
@@ -54,23 +54,4 @@ TEST(CombinationsGeneratorTester, n3k4) {
 }
 
 
-/*
-TEST(CombinationsGeneratorTester, n3k3) { 
-  CombinationsGenerator gen(3,3);
-  EXPECT_EQ (res(vec{0,1, 2}, true), gen.next());
-  res r =  gen.next();
-  EXPECT_EQ (r.second, false);
-cd}
-
-TEST(CombinationsGeneratorTester, n3k0) { 
-  CombinationsGenerator gen(3,0);
-  EXPECT_EQ (res(vec{}, true), gen.next());
-  res r =  gen.next();
-  EXPECT_EQ (r.second, false);
-}
 
-TEST(CombinationsGeneratorTester, n3kgtn) { 
-  CombinationsGenerator gen(3,4);
-  EXPECT_FALSE(gen.next().second);
-}
-*/
-- 
GitLab


From 65115514b7aa12ae7b94f2d146e9e80167ab8def Mon Sep 17 00:00:00 2001
From: Peter Sherwood <peter.sherwood@cern.ch>
Date: Mon, 7 Mar 2022 16:03:08 +0100
Subject: [PATCH 4/4] jet hypo commit file overlooked in the last commit (dead
 code removal)

---
 .../TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.h | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.h b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.h
index 163b82fe01aa..b8fbba68c062 100644
--- a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.h
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.h
@@ -88,10 +88,6 @@ class CombinationsJetStream: public IJetStream {
     } else {
       // no neighbor
       cycled = m_combgen->bump();
-      // cycled = m_combgen->cycled();
-      // if (cycled) {
-      // m_combgen->reset();
-      // }
 
       auto indices = m_combgen->get();
       m_data.clear();
-- 
GitLab