diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsGenerator.cxx b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsGenerator.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..d833eaf7ef1970c0c9801b179d1ea0145fc3cf89
--- /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 0000000000000000000000000000000000000000..b7ea1d09048943c510d158ba3c7ab42377e96e53
--- /dev/null
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsGenerator.h
@@ -0,0 +1,86 @@
+/*
+  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){
+
+    // 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 0000000000000000000000000000000000000000..ca64fa05da4ef2f08fb90baa6b042a12dbf06ece
--- /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 0000000000000000000000000000000000000000..b8fbba68c0627049666cb4ca0c3e76a7f040d426
--- /dev/null
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypo/src/CombinationsJetStream.h
@@ -0,0 +1,143 @@
+/*
+  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();
+
+      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();
+
+      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 0000000000000000000000000000000000000000..2e49923d678620e09d62a500275be4530ca7bc80
--- /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 6d6f8ec79ee7df5e63a3ec0647122c855e77207b..4d0672e88c86182caa75c1c6f49b754e2b2e3360 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,24 +46,15 @@ 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));
     }
   }
 
-  // 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);
+  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 +69,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 +80,25 @@ 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
-    }
 
-    // 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 2347b1ec06b27e5d4ac6f405b6dbdf87ec10a5bc..35f8fbeb03fec05da19ed9a481f675692b1fa787 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 0000000000000000000000000000000000000000..6672d8a466151cb5a41e01b4229c3893f7b792ca
--- /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 0000000000000000000000000000000000000000..7c84da64ca1c75a46ca664ed74b00dd5b366808d
--- /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 0000000000000000000000000000000000000000..99bfe5e0410913e5729cc096c27d749dc9823b81
--- /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 0000000000000000000000000000000000000000..352661ab4a9962e7bf21e2fe4f9daa20334d7f14
--- /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 0000000000000000000000000000000000000000..ec46fec8a166a4dc7f5c5554de91e7c55d9e5807
--- /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 7a86a159f64f0e97cfd695024676c9caa9164b97..4618282a5b57717406df07de6de54ac8f47df874 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 d4effc409ba3023936b99251f07303cdc9d10412..0000000000000000000000000000000000000000
--- 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 0000000000000000000000000000000000000000..c472146adfdc133b8b7ff548c98772da7d9ac19d
--- /dev/null
+++ b/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/CombinationsGeneratorTest.cxx
@@ -0,0 +1,57 @@
+/*
+  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());
+}
+
+
+
diff --git a/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/JetGroupProductTest.cxx b/Trigger/TrigHypothesis/TrigHLTJetHypoUnitTests/tests/JetGroupProductTest.cxx
index 03b8d76f59fd69503832805617fec5ee084c7a6c..3d099f22b032a72940e29c1a1277d524a663e258 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 0000000000000000000000000000000000000000..517e1d6c677b27e77be81d70dae9df6d57f7e7bc
--- /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 0000000000000000000000000000000000000000..2b5c5c4b4f9c6bf6b1b8708a7e141bff475c107c
--- /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());
+}
+