#include <string>
#include <bitset>
#include <iostream>
#include <algorithm>
#include <fstream>
#include <vector>
#include <cctype>
#include <stack>
#include <iomanip>
//#include <boost/filesystem.hpp>

using std::string;
using std::bitset;
using std::max;
using std::ifstream;
using std::cout;
using std::cerr;
using std::vector;

//namespace bfs = boost::filesystem;

enum token_type {EMPTY, IDENTIFIER , KEYWORD,  OPERATOR , PUNCTUATOR , LITERAL, NUMBER};   //token types // glob
const char* token_name[] = {"EMPTY", "IDENTIFIER", "KEYWORD",  "OPERATOR", "PUNCTUATOR", "LITERAL", "NUMBER"};   //token types // glob
const int GAP_SCORE = -8;

 struct alignment

 {

   bitset<3> both;
   bitset<3> gap_x;
   bitset<3> gap_y;

   alignment()
   {
     both = 1;
     gap_x = 2;
     gap_y = 4;
   }

 }align_type;

 struct tokens
 {

    string token;
    token_type type;

 }token;

  class string_align
{
    private:
    int nGap_score;
    int id_matchScore,id_misScore,kw_matchScore,kw_misScore,op_matchScore,op_misScore,lit_matchScore,lit_misScore,num_matchScore,num_misScore;
    int default_misScore;
    std::vector<string> str_x,str_y;
    std::vector<token_type> types_x, types_y;

    public:
    std::stack<string> str_x_aligned,str_y_aligned,str_match;
	std::stack<tokens> token_x_aligned,token_y_aligned;

    public:
    string_align (std::vector<string> x, std::vector<token_type> x_types, std::vector<string> y, std::vector<token_type> y_types)
     {
        nGap_score = GAP_SCORE;
        id_matchScore = 6;
        id_misScore = 4;
        kw_matchScore = 10;       //mis(match) score;
        kw_misScore = -10;
        op_matchScore = 8;
        op_misScore = -10;
        lit_matchScore = 7;
        lit_misScore = 5;
        num_matchScore = 7;
        num_misScore = 0;
        default_misScore = -10;
        str_x = x;
        types_x = x_types;
        str_y = y;
        types_y = y_types;
     }

    private:

    bool has_flag(bitset<3> event, bitset<3> flag)
    {
        bitset<3> test = event & flag;
        if (test.count() != 0) return true;
        return false;
    }

   public:

    int match_score (string x, token_type type_x , string y, token_type type_y)

    {
        if (type_x == type_y)
        {
            if (type_x == IDENTIFIER)
            {
                if (x==y) return id_matchScore;
                return id_misScore;
            }

            if (type_x == KEYWORD)
            {
                if (x==y) return kw_matchScore;
                return kw_misScore;
            }

            if (type_x == OPERATOR || type_x == PUNCTUATOR)
            {
                if (x==y) return op_matchScore;
                return op_misScore;
            }

            if (type_x == LITERAL)
            {
                if (x==y) return lit_matchScore;
                return lit_misScore;
            }

            if (type_x == NUMBER)
            {
                if (x==y) return num_matchScore;
                return num_misScore;
            }
        }
        return default_misScore;
    }

   int align()

    {
      int** scores = new int*[str_x.size()+1];
      for(size_t i = 0; i < str_x.size()+1; ++i)
      scores[i] = new int[str_y.size()+1];

      scores[0][0] = 0;

      bitset<3>** events = new bitset<3>*[str_x.size()+1];
      for(size_t i = 0; i < str_x.size()+1; ++i)
      events[i] = new bitset<3>[str_y.size()+1];

      unsigned int i,j;

      for (i = 1; i <= str_x.size(); i++)
			{
				scores[i][0] = scores[i - 1][0] + nGap_score;
				events[i][0] = align_type.gap_y;
			}

      for (j = 1; j <= str_y.size(); j++)
			{
				scores[0][j] = scores[0][j - 1] + nGap_score;
				events[0][j] = align_type.gap_x;
			}

	  for (i = 1; i <= str_x.size(); i++)
	      for (j = 1; j <= str_y.size(); j++)
	  		{

	          int m = scores[i-1][j-1] + match_score(str_x.at(i-1),types_x.at(i-1),str_y.at(j-1),types_y.at(j-1));
	          int g1 = scores[i-1][j] + nGap_score;
	          int g2 = scores[i][j-1] + nGap_score;
	          scores[i][j] = max(m,max(g1,g2));
	          bitset<3> type = align_type.both;
	          if (scores[i][j] == g1) type |= align_type.gap_y;
	          if (scores[i][j] == g2) type |= align_type.gap_x;
	          if (scores[i][j] > m ) type ^= align_type.both;
	          events[i][j] = type;

            }

       i = str_x.size();
       j = str_y.size();

      while (i > 0 || j > 0)
			{
				if (has_flag(events[i][j],align_type.both) && i > 0 && j > 0)
				{
					str_x_aligned.push(str_x[--i]);
					str_y_aligned.push(str_y[--j]);
				    str_match.push(str_x[i]==str_y[j] ? "=" : "!");
					tokens token_x = { str_x[i],types_x[i] },token_y = { str_y[j],types_y[j] };
					token_x_aligned.push(token_x);
					token_y_aligned.push(token_y);
				}
				else if (has_flag(events[i][j],align_type.gap_y) && i > 0)
				{
					str_x_aligned.push(str_x[--i]);
					str_y_aligned.push(".");
					str_match.push("^");
					tokens token_x = { str_x[i],types_x[i] },token_y = { "",EMPTY };
					token_x_aligned.push(token_x);
					token_y_aligned.push(token_y);
				}
				else if (has_flag(events[i][j],align_type.gap_x) && j > 0)
				{
					str_x_aligned.push(".");
					str_y_aligned.push(str_y[--j]);
					str_match.push("v");
					tokens token_x = { "",EMPTY },token_y = { str_y[j],types_y[j] };
					token_x_aligned.push(token_x);
					token_y_aligned.push(token_y);
				}

            }

     return scores[str_x.size()][str_y.size()];
      }
  };

class parsing
{
    public:
    parsing (std::vector<string> kw)
    {
       kw_list = kw;
    }

    private:
    std::vector<string> kw_list;
    string entire;
    std::string::iterator iter;

    public:
    std::vector<string> get_parsing()
    {
        return tokens;
    }

    public:
    std::vector<token_type> get_types()
    {
        return types;
    }

    public:
    int init (ifstream &f)
          {
            string extracted;
            while (std::getline(f,extracted))
                {
                        for (iter = extracted.begin(); iter != extracted.end(); iter++)    // corr. testing
                    {

                              if (*iter == '"')
                         {
                            iter++;
                            if (iter == extracted.end()) return -1;
                            while (*iter != '"')
                            {
                              iter++;
                              if (iter == extracted.end()) return -1;
                            }
                         }
                           if (*iter == '\'')
                         {
                            iter++;
                            if (iter == extracted.end()) return -1;
                            while (*iter != '\'')
                            {
                              iter++;
                              if (iter == extracted.end()) return -1;
                            }
                         }

                         if (*iter == '/' && *(iter+1) == '/')
                         {
                            extracted.erase(iter, extracted.end());
                            break;
                         }

                    }
                    entire += extracted;
                    extracted.clear();
                }

                for (iter = entire.begin(); iter != entire.end(); iter++)
                {
                    if (*iter == '"')
                    {
                        iter++;
                        while (*iter != '"') iter++;
                    }

                    if (*iter == '\'')
                    {
                        iter++;
                        while (*iter != '\'') iter++;
                    }

                    if (*iter == '/' && *(iter+1) == '*')
                    {
                        std::string::iterator tmp = iter;
                        iter += 2;
                        while ((*iter != '*' || *(iter+1) != '/')  && iter != entire.end())
                        {
                          iter++;
                        }
                        if (iter == entire.end())
                        {
                            entire.erase(tmp,entire.end());
                            break;
                        }
                        iter += 2;
                        iter = entire.erase(tmp,iter);
                    }
                }

            return 0;
          }

    public:
    void str_format ()
    {

       for (iter = entire.begin(); iter != entire.end(); iter++)     //deleting exc. ws
             if (isspace(*iter))
                if ((!is_idensym(*(iter+1)) || !is_idensym(*(iter-1))) && (!is_punsym((iter+1)) || !is_punsym((iter-1)))   )
                {
                    iter = entire.erase(iter) - 1;
                }
       for (iter = entire.begin(); iter != entire.end(); iter++)   //add req.
       {
          if (is_idensym(*iter))
          {

            switch (*(iter-1))
            {
                case '-':
                if (*(iter-2) == '-') iter = entire.insert(iter-2,' ') + 3;
                break;
                case '+':
                if (*(iter-2) == '+') iter = entire.insert(iter-2,' ') + 3;
                break;
            }

            if (*(iter-1) == '!' || *(iter-1) == '~')
             {
                if (*(iter-2) == '+' || *(iter-2) == '-')
                    if (*(iter-3) == '+' || *(iter-3) == '-')
                    iter = entire.insert(iter-3,' ') + 4;
                iter = entire.insert(iter-1,' ') + 2;
             }

             if (*(iter-1) == '*')
             {
                int k = 1;
                iter--;
                while (*iter == '*')
                {
                    iter--;
                    k++;
                }
                iter++;
                if (*(iter-1) == '+' || *(iter-1) == '-')
                    if (*(iter-2) == '+' || *(iter-2) == '-')
                      iter = entire.insert(iter-2,' ') + 3;
                iter = entire.insert(iter,' ') + k;
             }
            if (*(iter-1) == '&')
             {
                int k = 1;
                iter--;
                while (*iter == '&')
                {
                    iter--;
                    k++;
                }
                iter++;
                if (*(iter-1) == '+' || *(iter-1) == '-')
                    if (*(iter-2) == '+' || *(iter-2) == '-')
                      iter = entire.insert(iter-2,' ') + 3;
                iter = entire.insert(iter,' ') + k;
             }

              switch (*(iter+1))
            {
                case '-':
                if (*(iter+2) == '-') iter = entire.insert(iter+3,' ') - 3;
                break;
                case '+':
                if (*(iter+2) == '+') iter = entire.insert(iter+3,' ') - 3;
                break;

            }

           if  (*(iter+1) == '*')
            {
                int k = 1;
                iter++;
                while (*iter == '*')
                {
                    iter++;
                    k++;
                }
                iter = entire.insert(iter,' ') - k;
            }
             if  (*(iter+1) == '&')
            {
                int k = 1;
                iter++;
                while (*iter == '&')
                {
                    iter++;
                    k++;
                }
                iter = entire.insert(iter,' ') - k;
            }
        }
          switch (*iter)
          {
            case '(':case ')': case '[': case ']': case '{': case '}': case '"': case '\'': case '#':
            iter = entire.insert(iter+1,' ') - 1;
            iter = entire.insert(iter,' ') + 1;
            break;
            case '.':
            if (!isdigit(*(iter-1))) iter = entire.insert(iter,' ') + 1;
            break;
          }

       }
    }

    private:      //inspector functions

    bool is_idensym(char c)
    {
        return isalnum(c) || c == '_' || c == '$';
    }

    bool is_punsym(std::string::iterator iter)
    {
      if (*iter == '.' && isdigit(*(iter+1))) return false;
      static string pun_sym = "(){}+-*/=<>.,;:[]%!&|~^#'\"?";
        return pun_sym.find(*iter) != std::string::npos;
    }

    bool is_keyword (string token)

    {
      for (std::vector<string>::iterator iter = kw_list.begin(); iter != kw_list.end(); iter ++)
      {
        if (token == *iter) return true;
      }

      return false;
    }

      bool is_numsym (std::string::iterator iter)

    {
      static string num_spsym = "ex-.ABCDEF";
      if (*iter == '.')
      {
          return isdigit(*(iter + 1)) != 0;
      }
      if (*iter == '-')
      {

          return *(iter - 1) == 'e' || *(iter - 1) == 'E';

      }
        return isdigit(*iter) || num_spsym.find(*iter) != std::string::npos;
    }

    private:
    vector<string> tokens;
    vector<token_type> types;
    string curr_tok;

    public:
    void dismember()
    {

      for (iter = entire.begin();iter != entire.end();)
      {

        if (isalpha(*iter) || (*iter) == '_' || (*iter == '$'))
        {

          while (is_idensym(*iter))
           {
                curr_tok += *iter;
                iter++;
           }

          tokens.push_back(curr_tok);

          if (is_keyword(curr_tok))
          {
           types.push_back(KEYWORD);
          }
          else
          {
            types.push_back(IDENTIFIER);
          }
          curr_tok.clear();
        } else
       if (is_punsym(iter))
       {
         int flag = 0;
             if (*iter == '"')
             {
                curr_tok += *iter++;
                while (*iter != '"')
                {
                    curr_tok += *iter++;
                }
                curr_tok += *iter++;
                tokens.push_back(curr_tok);
                types.push_back(LITERAL);
                curr_tok.clear();
                flag = 1;
             }
         if (!flag)
         {
             if (*iter == '\'')
             {
                curr_tok += *iter++;
                while (*iter != '\'')
                {
                    curr_tok += *iter++;
                }
                curr_tok += *iter++;
                tokens.push_back(curr_tok);
                types.push_back(LITERAL);
                curr_tok.clear();
                flag = 1;
             }
         }
         if (!flag)
         {
             while (is_punsym(iter))
             {
               curr_tok += *iter;
               iter++;
             }
             tokens.push_back(curr_tok);
             if (curr_tok == "#" || curr_tok == ";") types.push_back(PUNCTUATOR); else types.push_back(OPERATOR);
             curr_tok.clear();
         }
        } else

       if (isdigit(*iter) || *iter == '.')
       {
         while (is_numsym(iter))
         {
            curr_tok += *iter;
            iter++;
         }
         tokens.push_back(curr_tok);
         types.push_back(NUMBER);
         curr_tok.clear();
       } else
         iter++;
     }
   }

  };

int main (int argc, const char **argv)
{
ifstream kw_list;
string extracted;
kw_list.open("keywords_reserved.txt");
std::vector<string> kw;
while (std::getline(kw_list,extracted)) kw.push_back(extracted);

if (argc < 3)
    {
        cerr << "File isn't specified";
        return -1;
    }

std::vector<string> file1_parsed,file2_parsed;
std::vector<token_type> file1_types,file2_types;
ifstream f1,f2;
f1.open(argv[1]);
f2.open(argv[2]);

parsing file1 (kw);
parsing file2 (kw);
if (file1.init(f1) || file2.init(f2))
{
    cerr << "Some of files are unsafe for parsing";
    return -1;
}

file1.str_format();
file2.str_format();

file1.dismember();
file2.dismember();
file1_parsed = file1.get_parsing();
file2_parsed = file2.get_parsing();
file1_types = file1.get_types();
file2_types = file2.get_types();
string_align alignment(file1_parsed,file1_types,file2_parsed, file2_types);
int scores = alignment.align();

int amount = 0;

//std::stack<string> *xa = &alignment.str_x_aligned,*ya = &alignment.str_y_aligned;
std::stack<tokens> *xa = &alignment.token_x_aligned,*ya = &alignment.token_y_aligned;

while(!xa->empty() && !ya->empty())
{
//if(!xa->empty())
//{
	cout << xa->top().token << '\t' << ya->top().token << '\t'
		<< (xa->top().type != EMPTY && ya->top().type != EMPTY
			? alignment.match_score(xa->top().token,xa->top().type,ya->top().token,ya->top().type)
			: GAP_SCORE)
		<< '/' << token_name[xa->top().type] << '/' << token_name[ya->top().type]
		<< std::endl;

	xa->pop();
	ya->pop();
	amount++;
}

cout << "Score: " << scores << std::endl << "Amount: " << amount << std::endl;
double plag = ((double)scores) / (6*amount); // - 0.25;    //empirically
if (plag < 0) plag = 0;
if (plag > 1) plag = 1;
double uniqueness = 1-plag;
cout << "Uniqueness:" << uniqueness;

}


// Copyright (C) 2011-2012 by the BEM++ Authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "piecewise_polynomial_continuous_scalar_space.hpp"

#include "piecewise_polynomial_discontinuous_scalar_space.hpp"
#include "space_helper.hpp"

#include "../assembly/discrete_sparse_boundary_operator.hpp"
#include "../common/acc.hpp"
#include "../common/boost_make_shared_fwd.hpp"
#include "../common/bounding_box_helpers.hpp"
#include "../fiber/explicit_instantiation.hpp"
#include "../fiber/lagrange_scalar_shapeset.hpp"
#include "../grid/entity.hpp"
#include "../grid/entity_iterator.hpp"
#include "../grid/geometry.hpp"
#include "../grid/grid.hpp"
#include "../grid/grid_segment.hpp"
#include "../grid/grid_view.hpp"
#include "../grid/mapper.hpp"
#include "../grid/vtk_writer.hpp"

#include <stdexcept>
#include <iostream>

#include <boost/array.hpp>

namespace Bempp {

template <typename BasisFunctionType>
PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::
    PiecewisePolynomialContinuousScalarSpace(const shared_ptr<const Grid> &grid,
                                             int polynomialOrder)
    : ScalarSpace<BasisFunctionType>(grid), m_polynomialOrder(polynomialOrder),
      m_segment(GridSegment::wholeGrid(*grid)), m_strictlyOnSegment(false),
      m_flatLocalDofCount(0) {
  initialize();
}

template <typename BasisFunctionType>
bool
PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::spaceIsCompatible(
    const Space<BasisFunctionType> &other) const {

  typedef PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>
  thisSpaceType;

  if (other.grid().get() != this->grid().get())
    return false;

  if (other.spaceIdentifier() == this->spaceIdentifier()) {
    // Try to typecast the other space down.
    const thisSpaceType &temp = dynamic_cast<const thisSpaceType &>(other);
    if (this->m_polynomialOrder == temp.m_polynomialOrder)
      return true;
    else
      return false;
  } else
    return false;
}

template <typename BasisFunctionType>
PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::
    PiecewisePolynomialContinuousScalarSpace(const shared_ptr<const Grid> &grid,
                                             int polynomialOrder,
                                             const GridSegment &segment,
                                             bool strictlyOnSegment)
    : ScalarSpace<BasisFunctionType>(grid), m_polynomialOrder(polynomialOrder),
      m_segment(segment), m_strictlyOnSegment(strictlyOnSegment),
      m_flatLocalDofCount(0) {
  initialize();
}

template <typename BasisFunctionType>
void PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::initialize() {
  const int gridDim = this->grid()->dim();
  if (gridDim != 2)
    throw std::invalid_argument("PiecewisePolynomialContinuousScalarSpace::"
                                "PiecewisePolynomialContinuousScalarSpace(): "
                                "2-dimensional grids are supported");
  m_view = this->grid()->leafView();
  if (m_polynomialOrder == 1)
    m_triangleShapeset.reset(
        new Fiber::LagrangeScalarShapeset<3, BasisFunctionType, 1>());
  else if (m_polynomialOrder == 2)
    m_triangleShapeset.reset(
        new Fiber::LagrangeScalarShapeset<3, BasisFunctionType, 2>());
  else if (m_polynomialOrder == 3)
    m_triangleShapeset.reset(
        new Fiber::LagrangeScalarShapeset<3, BasisFunctionType, 3>());
  else if (m_polynomialOrder == 4)
    m_triangleShapeset.reset(
        new Fiber::LagrangeScalarShapeset<3, BasisFunctionType, 4>());
  else if (m_polynomialOrder == 5)
    m_triangleShapeset.reset(
        new Fiber::LagrangeScalarShapeset<3, BasisFunctionType, 5>());
  else if (m_polynomialOrder == 6)
    m_triangleShapeset.reset(
        new Fiber::LagrangeScalarShapeset<3, BasisFunctionType, 6>());
  else if (m_polynomialOrder == 7)
    m_triangleShapeset.reset(
        new Fiber::LagrangeScalarShapeset<3, BasisFunctionType, 7>());
  else if (m_polynomialOrder == 8)
    m_triangleShapeset.reset(
        new Fiber::LagrangeScalarShapeset<3, BasisFunctionType, 8>());
  else if (m_polynomialOrder == 9)
    m_triangleShapeset.reset(
        new Fiber::LagrangeScalarShapeset<3, BasisFunctionType, 9>());
  else if (m_polynomialOrder == 10)
    m_triangleShapeset.reset(
        new Fiber::LagrangeScalarShapeset<3, BasisFunctionType, 10>());
  else
    throw std::invalid_argument("PiecewisePolynomialContinuousScalarSpace::"
                                "PiecewisePolynomialContinuousScalarSpace(): "
                                "polynomialOrder must be >= 1 and <= 10");
  assignDofsImpl();
}

template <typename BasisFunctionType>
PiecewisePolynomialContinuousScalarSpace<
    BasisFunctionType>::~PiecewisePolynomialContinuousScalarSpace() {}

template <typename BasisFunctionType>
int
PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::domainDimension()
    const {
  return this->grid()->dim();
}

template <typename BasisFunctionType>
int
PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::codomainDimension()
    const {
  return 1;
}

template <typename BasisFunctionType>
const Fiber::Shapeset<BasisFunctionType> &
PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::shapeset(
    const Entity<0> &element) const {
  if (elementVariant(element) == 3)
    return *m_triangleShapeset;
  throw std::logic_error(
      "PiecewisePolynomialContinuousScalarSpace::shapeset(): "
      "invalid element variant, this shouldn't happen!");
}

template <typename BasisFunctionType>
ElementVariant
PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::elementVariant(
    const Entity<0> &element) const {
  GeometryType type = element.type();
  if (type.isLine())
    return 2;
  else if (type.isTriangle())
    return 3;
  else if (type.isQuadrilateral())
    return 4;
  else
    throw std::runtime_error("PiecewisePolynomialContinuousScalarSpace::"
                             "elementVariant(): invalid geometry type, "
                             "this shouldn't happen!");
}

template <typename BasisFunctionType>
void
PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::setElementVariant(
    const Entity<0> &element, ElementVariant variant) {
  if (variant != elementVariant(element))
    // for this space, the element variants are unmodifiable,
    throw std::runtime_error("PiecewisePolynomialContinuousScalarSpace::"
                             "setElementVariant(): invalid variant");
}

template <typename BasisFunctionType>
shared_ptr<const Space<BasisFunctionType>>
PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::discontinuousSpace(
    const shared_ptr<const Space<BasisFunctionType>> &self) const {
  if (!m_discontinuousSpace) {
    tbb::mutex::scoped_lock lock(m_discontinuousSpaceMutex);
    typedef PiecewisePolynomialDiscontinuousScalarSpace<BasisFunctionType>
    DiscontinuousSpace;
    if (!m_discontinuousSpace)
      m_discontinuousSpace.reset(
          new DiscontinuousSpace(this->grid(), m_polynomialOrder, m_segment));
  }
  return m_discontinuousSpace;
}

template <typename BasisFunctionType>
bool
PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::isDiscontinuous()
    const {
  return false;
}

template <typename BasisFunctionType>
void
PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::assignDofsImpl() {
  // TODO: refactor this function, it's way too long!

  // In addition to DOF assignment, this function also precalculates bounding
  // boxes of global DOFs

  const int elementCount = m_view->entityCount(0);
  if (elementCount == 0)
    return;
  const int gridDim = this->domainDimension();
  if (gridDim != 2)
    throw std::runtime_error("PiecewisePolynomialContinuousScalarSpace::"
                             "assignDofsImpl(): only 2-dimensional grids "
                             "are supported at present");
  const int vertexCodim = gridDim;
  const int edgeCodim = vertexCodim - 1;

  // const Mapper& elementMapper = m_view->elementMapper();
  const IndexSet &indexSet = m_view->indexSet();

  // Map vertices to global dofs
  const int vertexCount = m_view->entityCount(2);
  // At first, the elements of this vector will be set to the number of
  // DOFs corresponding to a given vertex or to -1 if that vertex is to be
  // ignored
  std::vector<GlobalDofIndex> vertexGlobalDofs(vertexCount);
  for (int i = 0; i < vertexCount; ++i)
    if (m_segment.contains(gridDim, i))
      acc(vertexGlobalDofs, (size_t)i) = 1;
    else
      acc(vertexGlobalDofs, (size_t)i) = -1;

  // Map edges to global dofs
  const int edgeCount = m_view->entityCount(1);
  const int internalDofCountPerEdge = m_polynomialOrder - 1;
  // At first, the elements of this vector will be set to the number of
  // DOFs corresponding to a given edge or to -1 if that edge is to be
  // ignored
  std::vector<GlobalDofIndex> edgeStartingGlobalDofs(edgeCount);
  for (int i = 0; i < edgeCount; ++i)
    if (m_segment.contains(gridDim - 1, i))
      acc(edgeStartingGlobalDofs, i) = internalDofCountPerEdge;
    else
      acc(edgeStartingGlobalDofs, i) = -1;

  // Map element interiors to global dofs
  // and, if striclyOnSegment is set, detect vertices and edges not belonging
  // to any element on segment
  const int bubbleDofCountPerTriangle =
      std::max(0, (m_polynomialOrder - 1) * (m_polynomialOrder - 2) / 2);
  const int bubbleDofCountPerQuad =
      std::max(0, (m_polynomialOrder - 1) * (m_polynomialOrder - 1));
  // At first, the elements of this vector will be set to the number of
  // DOFs corresponding to a given element or to -1 if that element is to be
  // ignored
  std::vector<GlobalDofIndex> bubbleStartingGlobalDofs(elementCount);
  std::vector<bool> noElementAdjacentToVertexIsOnSegment(vertexCount, true);
  std::vector<bool> noElementAdjacentToEdgeIsOnSegment(edgeCount, true);
  std::unique_ptr<EntityIterator<0>> it = m_view->entityIterator<0>();
  while (!it->finished()) {
    const Entity<0> &element = it->entity();
    EntityIndex elementIndex = indexSet.entityIndex(element);
    int vertexCount = element.template subEntityCount<2>();
    if (vertexCount != 3 && vertexCount != 4)
      throw std::runtime_error("PiecewisePolynomialContinuousScalarSpace::"
                               "assignDofsImpl(): elements must be "
                               "triangular or quadrilateral");
    if (m_segment.contains(0, elementIndex)) {
      acc(bubbleStartingGlobalDofs, elementIndex) =
          vertexCount == 3 ? bubbleDofCountPerTriangle : bubbleDofCountPerQuad;
      if (m_strictlyOnSegment)
        for (int i = 0; i < vertexCount; ++i) {
          int index = indexSet.subEntityIndex(element, i, gridDim);
          acc(noElementAdjacentToVertexIsOnSegment, index) = false;
          index = indexSet.subEntityIndex(element, i, gridDim - 1);
          acc(noElementAdjacentToEdgeIsOnSegment, index) = false;
        }
    } else
      acc(bubbleStartingGlobalDofs, elementIndex) = -1;
    it->next();
  }

  // If strictlyOnSegment is set, deactivate vertices and edges not adjacent
  // to any element in segment
  if (m_strictlyOnSegment) {
    for (int i = 0; i < vertexCount; ++i)
      if (acc(noElementAdjacentToVertexIsOnSegment, i))
        acc(vertexGlobalDofs, i) = -1;

    for (int i = 0; i < edgeCount; ++i)
      if (acc(noElementAdjacentToEdgeIsOnSegment, i))
        acc(edgeStartingGlobalDofs, i) = -1;
  }

  // Assign global dofs to entities
  int globalDofCount_ = 0;
  for (int i = 0; i < vertexCount; ++i)
    if (acc(vertexGlobalDofs, i) == 1)
      acc(vertexGlobalDofs, i) = globalDofCount_++;
  for (int i = 0; i < edgeCount; ++i) {
    int dofCount = acc(edgeStartingGlobalDofs, i);
    if (dofCount > 0) {
      acc(edgeStartingGlobalDofs, i) = globalDofCount_;
      globalDofCount_ += dofCount;
    }
  }
  for (int i = 0; i < elementCount; ++i) {
    int dofCount = acc(bubbleStartingGlobalDofs, i);
    if (dofCount > 0) {
      acc(bubbleStartingGlobalDofs, i) = globalDofCount_;
      globalDofCount_ += dofCount;
    }
  }

  // Initialise DOF maps
  const int localDofCountPerTriangle =
      (m_polynomialOrder + 1) * (m_polynomialOrder + 2) / 2;
  m_local2globalDofs.clear();
  std::vector<GlobalDofIndex> prototypeGlobalDofs;
  prototypeGlobalDofs.reserve(localDofCountPerTriangle);
  m_local2globalDofs.resize(elementCount, prototypeGlobalDofs);
  m_global2localDofs.clear();
  // std::vector<LocalDof> prototypeLocalDofs;
  // prototypeLocalDofs.reserve(localDofCountPerTriangle);
  m_global2localDofs.resize(globalDofCount_ /*, prototypeLocalDofs*/);

  // Initialise bounding-box caches
  BoundingBox<CoordinateType> model;
  model.lbound.x = std::numeric_limits<CoordinateType>::max();
  model.lbound.y = std::numeric_limits<CoordinateType>::max();
  model.lbound.z = std::numeric_limits<CoordinateType>::max();
  model.ubound.x = -std::numeric_limits<CoordinateType>::max();
  model.ubound.y = -std::numeric_limits<CoordinateType>::max();
  model.ubound.z = -std::numeric_limits<CoordinateType>::max();
  m_globalDofBoundingBoxes.resize(globalDofCount_, model);

  // Iterate over elements
  it = m_view->entityIterator<0>();
  arma::Mat<CoordinateType> vertices;
  arma::Col<CoordinateType> dofPosition;
  m_flatLocalDofCount = 0;
  std::vector<int> gdofAccessCounts(globalDofCount_, 0);
  while (!it->finished()) {
    const Entity<0> &element = it->entity();
    const Geometry &geo = element.geometry();
    EntityIndex elementIndex = indexSet.entityIndex(element);
    bool elementContained =
        !m_strictlyOnSegment || m_segment.contains(0, elementIndex);

    geo.getCorners(vertices);
    int vertexCount = vertices.n_cols;

    // List of global DOF indices corresponding to the local DOFs of the
    // current element
    std::vector<GlobalDofIndex> &globalDofs =
        acc(m_local2globalDofs, elementIndex);
    if (vertexCount == 3) {
      std::vector<int> ldofAccessCounts(localDofCountPerTriangle, 0);
      boost::array<int, 3> vertexIndices;
      for (int i = 0; i < 3; ++i)
        acc(vertexIndices, i) =
            indexSet.subEntityIndex(element, i, vertexCodim);
      globalDofs.resize(localDofCountPerTriangle);
      // vertex dofs
      {
        int ldof, gdof;

        ldof = 0;
        if (elementContained)
          gdof = vertexGlobalDofs[acc(vertexIndices, 0)];
        else
          gdof = -1;
        if (gdof >= 0) {
          acc(globalDofs, ldof) = gdof;
          acc(m_global2localDofs, gdof).push_back(LocalDof(elementIndex, ldof));
          ++acc(gdofAccessCounts, gdof);
          extendBoundingBox(acc(m_globalDofBoundingBoxes, gdof), vertices);
          setBoundingBoxReference<CoordinateType>(
              acc(m_globalDofBoundingBoxes, gdof), vertices.col(0));
          ++m_flatLocalDofCount;
        } else
          acc(globalDofs, ldof) = -1;
        ++acc(ldofAccessCounts, ldof);

        ldof = m_polynomialOrder;
        if (elementContained)
          gdof = vertexGlobalDofs[acc(vertexIndices, 1)];
        else
          gdof = -1;
        if (gdof >= 0) {
          acc(globalDofs, ldof) = gdof;
          acc(m_global2localDofs, gdof).push_back(LocalDof(elementIndex, ldof));
          ++acc(gdofAccessCounts, gdof);
          extendBoundingBox(acc(m_globalDofBoundingBoxes, gdof), vertices);
          setBoundingBoxReference<CoordinateType>(
              acc(m_globalDofBoundingBoxes, gdof), vertices.col(1));
          ++m_flatLocalDofCount;
        } else
          acc(globalDofs, ldof) = -1;
        ++acc(ldofAccessCounts, ldof);

        ldof = localDofCountPerTriangle - 1;
        if (elementContained)
          gdof = vertexGlobalDofs[acc(vertexIndices, 2)];
        else
          gdof = -1;
        if (gdof >= 0) {
          acc(globalDofs, ldof) = gdof;
          acc(m_global2localDofs, gdof).push_back(LocalDof(elementIndex, ldof));
          ++acc(gdofAccessCounts, gdof);
          extendBoundingBox(acc(m_globalDofBoundingBoxes, gdof), vertices);
          setBoundingBoxReference<CoordinateType>(
              acc(m_globalDofBoundingBoxes, gdof), vertices.col(2));
          ++m_flatLocalDofCount;
        } else
          acc(globalDofs, ldof) = -1;
        ++acc(ldofAccessCounts, ldof);
      }

      // edge dofs
      if (m_polynomialOrder >= 2) {
        int start, end, step;
        int edgeIndex;

        edgeIndex = indexSet.subEntityIndex(element, 0, edgeCodim);
        dofPosition = 0.5 * (vertices.col(0) + vertices.col(1));
        if (acc(edgeStartingGlobalDofs, edgeIndex) >= 0 && elementContained) {
          if (acc(vertexIndices, 0) < acc(vertexIndices, 1)) {
            start = acc(edgeStartingGlobalDofs, edgeIndex);
            end = start + internalDofCountPerEdge;
            step = 1;
          } else {
            end = acc(edgeStartingGlobalDofs, edgeIndex) - 1;
            start = end + internalDofCountPerEdge;
            step = -1;
          }
          for (int ldof = 1, gdof = start; gdof != end; ++ldof, gdof += step) {
            acc(globalDofs, ldof) = gdof;
            acc(m_global2localDofs, gdof)
                .push_back(LocalDof(elementIndex, ldof));
            ++acc(ldofAccessCounts, ldof);
            ++acc(gdofAccessCounts, gdof);
            extendBoundingBox(acc(m_globalDofBoundingBoxes, gdof), vertices);
            setBoundingBoxReference<CoordinateType>(
                acc(m_globalDofBoundingBoxes, gdof), dofPosition);
            ++m_flatLocalDofCount;
          }
        } else
          for (int ldof = 1; ldof <= internalDofCountPerEdge; ++ldof) {
            acc(globalDofs, ldof) = -1;
            ++acc(ldofAccessCounts, ldof);
          }

        edgeIndex = indexSet.subEntityIndex(element, 1, edgeCodim);
        dofPosition = 0.5 * (vertices.col(0) + vertices.col(2));
        if (acc(edgeStartingGlobalDofs, edgeIndex) >= 0 && elementContained) {
          if (acc(vertexIndices, 0) < acc(vertexIndices, 2)) {
            start = acc(edgeStartingGlobalDofs, edgeIndex);
            end = start + internalDofCountPerEdge;
            step = 1;
          } else {
            end = acc(edgeStartingGlobalDofs, edgeIndex) - 1;
            start = end + internalDofCountPerEdge;
            step = -1;
          }
          for (int ldofy = 1, gdof = start; gdof != end;
               ++ldofy, gdof += step) {
            int ldof =
                ldofy * (m_polynomialOrder + 1) - ldofy * (ldofy - 1) / 2;
            acc(globalDofs, ldof) = gdof;
            acc(m_global2localDofs, gdof)
                .push_back(LocalDof(elementIndex, ldof));
            ++acc(ldofAccessCounts, ldof);
            ++acc(gdofAccessCounts, gdof);
            extendBoundingBox(acc(m_globalDofBoundingBoxes, gdof), vertices);
            setBoundingBoxReference<CoordinateType>(
                acc(m_globalDofBoundingBoxes, gdof), dofPosition);
            ++m_flatLocalDofCount;
          }
        } else
          for (int ldofy = 1; ldofy <= internalDofCountPerEdge; ++ldofy) {
            int ldof =
                ldofy * (m_polynomialOrder + 1) - ldofy * (ldofy - 1) / 2;
            acc(globalDofs, ldof) = -1;
            ++acc(ldofAccessCounts, ldof);
          }

        edgeIndex = indexSet.subEntityIndex(element, 2, edgeCodim);
        dofPosition = 0.5 * (vertices.col(1) + vertices.col(2));
        if (acc(edgeStartingGlobalDofs, edgeIndex) >= 0 && elementContained) {
          if (acc(vertexIndices, 1) < acc(vertexIndices, 2)) {
            start = acc(edgeStartingGlobalDofs, edgeIndex);
            end = start + internalDofCountPerEdge;
            step = 1;
          } else {
            end = acc(edgeStartingGlobalDofs, edgeIndex) - 1;
            start = end + internalDofCountPerEdge;
            step = -1;
          }
          for (int ldofy = 1, gdof = start; gdof != end;
               ++ldofy, gdof += step) {
            int ldof = ldofy * (m_polynomialOrder + 1) -
                       ldofy * (ldofy - 1) / 2 + (m_polynomialOrder - ldofy);
            acc(globalDofs, ldof) = gdof;
            acc(m_global2localDofs, gdof)
                .push_back(LocalDof(elementIndex, ldof));
            ++acc(ldofAccessCounts, ldof);
            ++acc(gdofAccessCounts, gdof);
            extendBoundingBox(acc(m_globalDofBoundingBoxes, gdof), vertices);
            setBoundingBoxReference<CoordinateType>(
                acc(m_globalDofBoundingBoxes, gdof), dofPosition);
            ++m_flatLocalDofCount;
          }
        } else
          for (int ldofy = 1; ldofy <= internalDofCountPerEdge; ++ldofy) {
            int ldof = ldofy * (m_polynomialOrder + 1) -
                       ldofy * (ldofy - 1) / 2 + (m_polynomialOrder - ldofy);
            acc(globalDofs, ldof) = -1;
            ++acc(ldofAccessCounts, ldof);
          }
      }

      // bubble dofs
      if (m_polynomialOrder >= 3) {
        dofPosition =
            (vertices.col(0) + vertices.col(1) + vertices.col(2)) / 3.;
        bool useDofs = acc(bubbleStartingGlobalDofs, elementIndex) >= 0;
        for (int ldofy = 1, gdof = acc(bubbleStartingGlobalDofs, elementIndex);
             ldofy < m_polynomialOrder; ++ldofy)
          for (int ldofx = 1; ldofx + ldofy < m_polynomialOrder;
               ++ldofx, ++gdof) {
            int ldof = ldofy * (m_polynomialOrder + 1) -
                       ldofy * (ldofy - 1) / 2 + ldofx;
            if (useDofs) {
              acc(globalDofs, ldof) = gdof;
              acc(m_global2localDofs, gdof)
                  .push_back(LocalDof(elementIndex, ldof));
              ++acc(gdofAccessCounts, gdof);
              extendBoundingBox(acc(m_globalDofBoundingBoxes, gdof), vertices);
              setBoundingBoxReference<CoordinateType>(
                  acc(m_globalDofBoundingBoxes, gdof), dofPosition);
              ++m_flatLocalDofCount;
            } else
              acc(globalDofs, ldof) = -1;
            ++acc(ldofAccessCounts, ldof);
          }
      }
      for (size_t i = 0; i < ldofAccessCounts.size(); ++i)
        assert(acc(ldofAccessCounts, i) == 1);
    } else
      throw std::runtime_error("PiecewisePolynomialContinuousScalarSpace::"
                               "assignDofsImpl(): quadrilateral elements "
                               "are not supported yet");

    it->next();
  }
// for (size_t i = 0; i < gdofAccessCounts.size(); ++i)
//     std::cout << i << " " << acc(gdofAccessCounts, i) << "\n";

#ifndef NDEBUG
  for (size_t i = 0; i < globalDofCount_; ++i) {
    const BoundingBox<CoordinateType> &bbox = acc(m_globalDofBoundingBoxes, i);

    assert(bbox.reference.x >= bbox.lbound.x);
    assert(bbox.reference.y >= bbox.lbound.y);
    assert(bbox.reference.z >= bbox.lbound.z);
    assert(bbox.reference.x <= bbox.ubound.x);
    assert(bbox.reference.y <= bbox.ubound.y);
    assert(bbox.reference.z <= bbox.ubound.z);
  }
#endif // NDEBUG

  // Initialize the container mapping the flat local dof indices to
  // local dof indices
  SpaceHelper<BasisFunctionType>::initializeLocal2FlatLocalDofMap(
      m_flatLocalDofCount, m_local2globalDofs, m_flatLocal2localDofs);
}

template <typename BasisFunctionType>
size_t
PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::globalDofCount()
    const {
  return m_global2localDofs.size();
}

template <typename BasisFunctionType>
size_t
PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::flatLocalDofCount()
    const {
  return m_flatLocalDofCount;
}

template <typename BasisFunctionType>
void PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::getGlobalDofs(
    const Entity<0> &element, std::vector<GlobalDofIndex> &dofs) const {
  const Mapper &mapper = m_view->elementMapper();
  EntityIndex index = mapper.entityIndex(element);
  dofs = m_local2globalDofs[index];
}

template <typename BasisFunctionType>
void
PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::global2localDofs(
    const std::vector<GlobalDofIndex> &globalDofs,
    std::vector<std::vector<LocalDof>> &localDofs) const {
  localDofs.resize(globalDofs.size());
  for (size_t i = 0; i < globalDofs.size(); ++i)
    localDofs[i] = m_global2localDofs[globalDofs[i]];
}

template <typename BasisFunctionType>
void PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::
    flatLocal2localDofs(const std::vector<FlatLocalDofIndex> &flatLocalDofs,
                        std::vector<LocalDof> &localDofs) const {
  localDofs.resize(flatLocalDofs.size());
  for (size_t i = 0; i < flatLocalDofs.size(); ++i)
    localDofs[i] = m_flatLocal2localDofs[flatLocalDofs[i]];
}

template <typename BasisFunctionType>
void PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::
    getGlobalDofPositions(std::vector<Point3D<CoordinateType>> &positions)
    const {
  positions.resize(m_globalDofBoundingBoxes.size());
  for (size_t i = 0; i < m_globalDofBoundingBoxes.size(); ++i)
    acc(positions, i) = acc(m_globalDofBoundingBoxes, i).reference;
}

template <typename BasisFunctionType>
void PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::
    getFlatLocalDofPositions(std::vector<Point3D<CoordinateType>> &positions)
    const {
  throw std::runtime_error("PiecewisePolynomialContinuousScalarSpace::"
                           "getFlatLocalDofPositions(): not implemented yet");
}

template <typename BasisFunctionType>
void PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::
    getGlobalDofBoundingBoxes(std::vector<BoundingBox<CoordinateType>> &bboxes)
    const {
  bboxes = m_globalDofBoundingBoxes;
}

template <typename BasisFunctionType>
void PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::
    getFlatLocalDofBoundingBoxes(
        std::vector<BoundingBox<CoordinateType>> &bboxes) const {
  throw std::runtime_error(
      "PiecewisePolynomialContinuousScalarSpace::"
      "getFlatLocalDofBoundingBoxes(): not implemented yet");
}

template <typename BasisFunctionType>
void PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::
    getGlobalDofNormals(std::vector<Point3D<CoordinateType>> &normals) const {
  SpaceHelper<BasisFunctionType>::getGlobalDofNormals_defaultImplementation(
      *m_view, m_global2localDofs, normals);
}

template <typename BasisFunctionType>
void PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::
    getFlatLocalDofNormals(std::vector<Point3D<CoordinateType>> &normals)
    const {
  throw std::runtime_error("PiecewisePolynomialContinuousScalarSpace::"
                           "getFlatLocalDofNormals(): not implemented yet");
}

template <typename BasisFunctionType>
void
PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::dumpClusterIds(
    const char *fileName,
    const std::vector<unsigned int> &clusterIdsOfDofs) const {
  dumpClusterIdsEx(fileName, clusterIdsOfDofs, GLOBAL_DOFS);
}

template <typename BasisFunctionType>
void
PiecewisePolynomialContinuousScalarSpace<BasisFunctionType>::dumpClusterIdsEx(
    const char *fileName, const std::vector<unsigned int> &clusterIdsOfDofs,
    DofType dofType) const {
  throw std::runtime_error("PiecewisePolynomialContinuousScalarSpace::"
                           "dumpClusterIdsEx(): not implemented yet");
}

FIBER_INSTANTIATE_CLASS_TEMPLATED_ON_BASIS(
    PiecewisePolynomialContinuousScalarSpace);

} // namespace Bempp


// Distributed under the MIT License.
// See LICENSE.txt for details.

#pragma once

#include <algorithm>
#include <array>
#include <boost/functional/hash.hpp>
#include <cstddef>
#include <functional>
#include <tuple>
#include <type_traits>
#include <utility>  // IWYU pragma: keep // for std::forward

#include "DataStructures/DataBox/DataBoxTag.hpp"
#include "DataStructures/DataBox/PrefixHelpers.hpp"
#include "DataStructures/DataBox/Prefixes.hpp"
#include "DataStructures/Matrix.hpp"
#include "DataStructures/Variables.hpp"
#include "Domain/Direction.hpp"
#include "Domain/ElementId.hpp"
#include "Domain/Mesh.hpp"
#include "ErrorHandling/Assert.hpp"
#include "NumericalAlgorithms/DiscontinuousGalerkin/LiftFlux.hpp"
#include "NumericalAlgorithms/LinearOperators/ApplyMatrices.hpp"
#include "NumericalAlgorithms/Spectral/Projection.hpp"
#include "Utilities/Algorithm.hpp"
#include "Utilities/Gsl.hpp"
#include "Utilities/MakeArray.hpp"
#include "Utilities/TMPL.hpp"
/// \cond
template <size_t VolumeDim>
class ElementId;
template <size_t VolumeDim>
class OrientationMap;
// IWYU pragma: no_forward_declare Variables
/// \endcond

namespace dg {

template <size_t VolumeDim>
using MortarId = std::pair<::Direction<VolumeDim>, ElementId<VolumeDim>>;
template <size_t MortarDim>
using MortarSize = std::array<Spectral::MortarSize, MortarDim>;
template <size_t VolumeDim, typename ValueType>
using MortarMap = std::unordered_map<MortarId<VolumeDim>, ValueType,
                                     boost::hash<MortarId<VolumeDim>>>;

/// \ingroup DiscontinuousGalerkinGroup
/// Find a mesh for a mortar capable of representing data from either
/// of two faces.
template <size_t Dim>
Mesh<Dim> mortar_mesh(const Mesh<Dim>& face_mesh1,
                      const Mesh<Dim>& face_mesh2) noexcept;

/// \ingroup DiscontinuousGalerkinGroup
/// Determine the size of the mortar (i.e., the part of the face it
/// covers) for communicating with a neighbor.  This is the size
/// relative to the size of \p self, and will not generally agree with
/// that determined by \p neighbor.
template <size_t Dim>
MortarSize<Dim - 1> mortar_size(
    const ElementId<Dim>& self, const ElementId<Dim>& neighbor,
    size_t dimension, const OrientationMap<Dim>& orientation) noexcept;

/// \ingroup DiscontinuousGalerkinGroup
/// Determine whether data on an element face needs to be projected to a mortar.
/// If no projection is necessary the data may be used on the mortar as-is.
template <size_t Dim>
bool needs_projection(const Mesh<Dim>& face_mesh, const Mesh<Dim>& mortar_mesh,
                      const MortarSize<Dim>& mortar_size) noexcept {
  return mortar_mesh != face_mesh or
      alg::any_of(mortar_size, [](const Spectral::MortarSize& size) noexcept {
        return size != Spectral::MortarSize::Full;
      });
}

/// \ingroup DiscontinuousGalerkinGroup
/// Project variables from a face to a mortar.
template <typename Tags, size_t Dim>
Variables<Tags> project_to_mortar(const Variables<Tags>& vars,
                                  const Mesh<Dim>& face_mesh,
                                  const Mesh<Dim>& mortar_mesh,
                                  const MortarSize<Dim>& mortar_size) noexcept {
  const Matrix identity{};
  auto projection_matrices = make_array<Dim>(std::cref(identity));

  const auto face_slice_meshes = face_mesh.slices();
  const auto mortar_slice_meshes = mortar_mesh.slices();
  for (size_t i = 0; i < Dim; ++i) {
    const auto& face_slice_mesh = gsl::at(face_slice_meshes, i);
    const auto& mortar_slice_mesh = gsl::at(mortar_slice_meshes, i);
    const auto& slice_size = gsl::at(mortar_size, i);
    if (slice_size != Spectral::MortarSize::Full or
        face_slice_mesh != mortar_slice_mesh) {
      gsl::at(projection_matrices, i) = projection_matrix_element_to_mortar(
          slice_size, mortar_slice_mesh, face_slice_mesh);
    }
  }
  return apply_matrices(projection_matrices, vars, face_mesh.extents());
}

/// \ingroup DiscontinuousGalerkinGroup
/// Project variables from a mortar to a face.
template <typename Tags, size_t Dim>
Variables<Tags> project_from_mortar(
    const Variables<Tags>& vars, const Mesh<Dim>& face_mesh,
    const Mesh<Dim>& mortar_mesh, const MortarSize<Dim>& mortar_size) noexcept {
  ASSERT(face_mesh != mortar_mesh or
             alg::any_of(mortar_size,
                         [](const Spectral::MortarSize& size) noexcept {
                           return size != Spectral::MortarSize::Full;
                         }),
         "project_from_mortar should not be called if the interface mesh and "
         "mortar mesh are identical. Please elide the copy instead.");

  const Matrix identity{};
  auto projection_matrices = make_array<Dim>(std::cref(identity));

  const auto face_slice_meshes = face_mesh.slices();
  const auto mortar_slice_meshes = mortar_mesh.slices();
  for (size_t i = 0; i < Dim; ++i) {
    const auto& face_slice_mesh = gsl::at(face_slice_meshes, i);
    const auto& mortar_slice_mesh = gsl::at(mortar_slice_meshes, i);
    const auto& slice_size = gsl::at(mortar_size, i);
    if (slice_size != Spectral::MortarSize::Full or
        face_slice_mesh != mortar_slice_mesh) {
      gsl::at(projection_matrices, i) = projection_matrix_mortar_to_element(
          slice_size, face_slice_mesh, mortar_slice_mesh);
    }
  }
  return apply_matrices(projection_matrices, vars, mortar_mesh.extents());
}

namespace MortarHelpers_detail {
template <typename NormalDotNumericalFluxComputer,
          typename... NumericalFluxTags, typename... SelfTags,
          typename... PackagedTags>
void apply_normal_dot_numerical_flux(
    const gsl::not_null<Variables<tmpl::list<NumericalFluxTags...>>*>
        numerical_fluxes,
    const NormalDotNumericalFluxComputer& normal_dot_numerical_flux_computer,
    const Variables<tmpl::list<SelfTags...>>& self_packaged_data,
    const Variables<tmpl::list<PackagedTags...>>&
        neighbor_packaged_data) noexcept {
  normal_dot_numerical_flux_computer(
      make_not_null(&get<NumericalFluxTags>(*numerical_fluxes))...,
      get<PackagedTags>(self_packaged_data)...,
      get<PackagedTags>(neighbor_packaged_data)...);
}
}  // namespace MortarHelpers_detail

/// \ingroup DiscontinuousGalerkinGroup
/// Compute the lifted data resulting from computing the numerical flux.
///
/// \details
/// This applies the numerical flux, projects the result to the face
/// mesh if necessary, and then lifts it to the volume (still
/// presented only on the face mesh as all other points are zero).
///
/// Projection must happen after the numerical flux calculation so
/// that the elements on either side of the mortar calculate the same
/// result.  Projection must happen before flux lifting because we
/// want the factor of the magnitude of the unit normal added during
/// the lift to cancel the Jacobian factor in integrals to preserve
/// conservation; this only happens if the two operations are done on
/// the same grid.
template <typename FluxCommTypes, typename NormalDotNumericalFluxComputer,
          size_t Dim, typename LocalData>
auto compute_boundary_flux_contribution(
    const NormalDotNumericalFluxComputer& normal_dot_numerical_flux_computer,
    LocalData&& local_data,
    const typename FluxCommTypes::PackagedData& remote_data,
    const Mesh<Dim>& face_mesh, const Mesh<Dim>& mortar_mesh,
    const size_t extent_perpendicular_to_boundary,
    const MortarSize<Dim>& mortar_size) noexcept
    -> db::const_item_type<
        db::remove_tag_prefix<typename FluxCommTypes::normal_dot_fluxes_tag>> {
  static_assert(std::is_same_v<std::decay_t<LocalData>,
                               typename FluxCommTypes::LocalData>,
                "Second argument must be a FluxCommTypes::LocalData");
  using variables_tag =
      db::remove_tag_prefix<typename FluxCommTypes::normal_dot_fluxes_tag>;
  db::const_item_type<db::add_tag_prefix<Tags::NormalDotNumericalFlux,
                                         variables_tag>>
      normal_dot_numerical_fluxes(mortar_mesh.number_of_grid_points(), 0.0);
  MortarHelpers_detail::apply_normal_dot_numerical_flux(
      make_not_null(&normal_dot_numerical_fluxes),
      normal_dot_numerical_flux_computer, local_data.mortar_data, remote_data);

  tmpl::for_each<db::get_variables_tags_list<variables_tag>>(
      [&normal_dot_numerical_fluxes, &local_data](const auto tag) noexcept {
        using Tag = tmpl::type_from<decltype(tag)>;
        auto& numerical_flux =
            get<Tags::NormalDotNumericalFlux<Tag>>(normal_dot_numerical_fluxes);
        const auto& local_flux =
            get<Tags::NormalDotFlux<Tag>>(local_data.mortar_data);
        for (size_t i = 0; i < numerical_flux.size(); ++i) {
          numerical_flux[i] -= local_flux[i];
        }
      });

  const bool refining =
      face_mesh != mortar_mesh or
      std::any_of(mortar_size.begin(), mortar_size.end(),
                  [](const Spectral::MortarSize s) noexcept {
                    return s != Spectral::MortarSize::Full;
                  });

  return dg::lift_flux(
      refining ? project_from_mortar(normal_dot_numerical_fluxes, face_mesh,
                                     mortar_mesh, mortar_size)
               : std::move(normal_dot_numerical_fluxes),
      extent_perpendicular_to_boundary,
      std::forward<LocalData>(local_data).magnitude_of_face_normal);
}
}  // namespace dg


/************************************************************************
 *
 *
 *  Copyright 2015  Arun Das (University of Waterloo)
 *                      [adas@uwaterloo.ca]
 *                  James Servos (University of Waterloo)
 *                      [jdservos@uwaterloo.ca]
 *
 *
 *************************************************************************/
#include <Eigen/Eigenvalues>
#include <wave/matching/groundSegmentation.hpp>
#include <wave/matching/PointcloudXYZGD.hpp>

namespace wave {

bool compareSignalPoints(const signalPoint &a, const signalPoint &b) {
    return a.height < b.height;
}

groundSegmentation::groundSegmentation(GroundSegmentationParams config) {
    this->params = config;
    this->pBG = new polarBinGrid;
    initializePolarBinGrid();
}

void groundSegmentation::initializePolarBinGrid(void) {
    this->pBG->aCell.resize(this->params.num_bins_a);
    for (int i = 0; i < this->params.num_bins_a; i++) {
        this->pBG->aCell[i].sigPoints.clear();
        this->pBG->aCell[i].lCell.resize(this->params.num_bins_l);
        this->pBG->aCell[i].sigPoints.resize(this->params.num_bins_l);
        this->pBG->aCell[i].rangeHeightSignal.resize(this->params.num_bins_l);
        std::vector<signalPoint>().swap(pBG->aCell[i].sigPoints);
        for (int j = 0; j < this->params.num_bins_l; j++) {
            pBG->aCell[i].lCell[j].binPoints.clear();
            pBG->aCell[i].lCell[j].obsPoints.clear();
            pBG->aCell[i].lCell[j].drvPoints.clear();
            pBG->aCell[i].lCell[j].groundPoints.clear();
            pBG->aCell[i].lCell[j].prototypePoint = PointXYZGD();
            pBG->aCell[i].lCell[j].prototypePoint.x = NAN;
            pBG->aCell[i].lCell[j].prototypePoint.y = NAN;
            pBG->aCell[i].lCell[j].prototypePoint.z = NAN;
            pBG->aCell[i].rangeHeightSignal[j].x = NAN;
            pBG->aCell[i].rangeHeightSignal[j].y = NAN;
            pBG->aCell[i].lCell[j].cAssigned = -1;

            // force memory deletion of std::vectors
            std::vector<PointXYZGD>().swap(pBG->aCell[i].lCell[j].binPoints);
            std::vector<PointXYZGD>().swap(pBG->aCell[i].lCell[j].obsPoints);
            std::vector<PointXYZGD>().swap(pBG->aCell[i].lCell[j].drvPoints);
            std::vector<PointXYZGD>().swap(pBG->aCell[i].lCell[j].groundPoints);
        }
    }
}

void groundSegmentation::setupGroundSegmentation(
  pcl::PointCloud<pcl::PointXYZ>::Ptr inputCloud,
  pcl::PointCloud<PointXYZGD>::Ptr groundCloud,
  pcl::PointCloud<PointXYZGD>::Ptr obsCloud,
  pcl::PointCloud<PointXYZGD>::Ptr drvCloud) {
    groundCloud->clear();
    obsCloud->clear();

    refCloud = inputCloud;  // set the cloud datastructure;
    oCloud = obsCloud;      // set obs cloud
    gCloud = groundCloud;   // set ground cloud
    dCloud = drvCloud;

    oCloud->clear();
    gCloud->clear();
    dCloud->clear();

    genPolarBinGrid(refCloud);
}

void groundSegmentation::genPolarBinGrid(
  pcl::PointCloud<pcl::PointXYZ>::Ptr inputCloud) {
    initializePolarBinGrid();

    size_t nPts = inputCloud->size();
    double bsize_rad = (double) ((360.0) / this->params.num_bins_a);
    double bsize_lin = (double) this->params.rmax / this->params.num_bins_l;
    for (size_t i = 0; i < nPts; i++) {
        PointXYZGD curPt;
        float px = curPt.x = inputCloud->points[i].x;
        float py = curPt.y = inputCloud->points[i].y;
        float pz = curPt.z = inputCloud->points[i].z;

        if (sqrt(px * px + py * py + pz * pz) < this->params.rmax) {
            double ph = (atan2(py, px)) * (180 / M_PI);  // in degrees
            if (ph < 0)
                ph = 360.0 + ph;

            // bin into sector

            unsigned int bind_rad =
              static_cast<unsigned int>(ph / bsize_rad);  // got the radial bin

            if (bind_rad == this->params.num_bins_a) {
                bind_rad = 0;
            }

            // get the linear bin
            float xyDist = sqrt(px * px + py * py);

            unsigned int bind_lin = static_cast<unsigned int>(
              xyDist / bsize_lin);  // got the radial bin

            pBG->aCell[bind_rad].lCell[bind_lin].binPoints.push_back(curPt);
            // add the point to the bin
            // check the protoype point

            if (isnanf(pBG->aCell[bind_rad].lCell[bind_lin].prototypePoint.z) ||
                pz < pBG->aCell[bind_rad]
                       .lCell[bind_lin]
                       .prototypePoint.z)  // smallest by z
            {
                pBG->aCell[bind_rad].lCell[bind_lin].prototypePoint = curPt;
                pBG->aCell[bind_rad].rangeHeightSignal[bind_lin].x = xyDist;
                pBG->aCell[bind_rad].rangeHeightSignal[bind_lin].y = pz;
            }
        }
    }
}

MatX groundSegmentation::genGPModel(std::vector<signalPoint> &ps1,
                                    std::vector<signalPoint> &ps2,
                                    float sig_f,
                                    float p_l) {
    size_t nP1 = ps1.size();
    size_t nP2 = ps2.size();

    MatX CMAT;
    CMAT.resize(nP1, nP2);
    float coeff = (-1 / (2 * p_l * p_l));

    for (size_t i = 0; i < nP1; i++) {
        for (size_t j = 0; j < nP2; j++) {
            double diff = (ps1[i].range - ps2[j].range);
            CMAT(i, j) = sig_f * exp(coeff * (diff * diff));
        }
    }
    return CMAT;
}

void groundSegmentation::segmentGround() {
    for (int i = 0; i < this->params.num_bins_a; i++) {
        sectorINSAC(i);
    }
}

void groundSegmentation::sectorINSAC(int sectorIndex) {
    if (sectorIndex >= this->params.num_bins_a) {
        return;
    }
    int numFilled = 0;

    // pull out the valid points from the sector
    std::vector<signalPoint> &sigPtr = pBG->aCell[sectorIndex].sigPoints;
    sigPtr.clear();
    for (int i = 0; i < this->params.num_bins_l; i++) {
        if (!std::isnan(pBG->aCell[sectorIndex].rangeHeightSignal[i].x) &&
            pBG->aCell[sectorIndex].lCell[i].binPoints.size() > 5) {
            // bin has a valid point, and enough points to make a good
            // guess for a protopoint
            signalPoint newPoint;
            newPoint.range = pBG->aCell[sectorIndex].rangeHeightSignal[i].x;
            newPoint.height = pBG->aCell[sectorIndex].rangeHeightSignal[i].y;
            newPoint.idx = i;
            sigPtr.push_back(newPoint);
            numFilled++;
        }
    }
    // get the seed points.  Select the 3 lowest points.  Sort based on height
    // values
    sort(sigPtr.begin(), sigPtr.end(), compareSignalPoints);

    // now that the z points are sorted by height, take the
    // this->params.num_seed_points worth
    // as the seed
    size_t npt =
      sigPtr.size() < static_cast<size_t>(this->params.num_seed_points)
        ? sigPtr.size()
        : static_cast<size_t>(this->params.num_seed_points);
    std::vector<signalPoint> currentModel;
    int ptCtr = 0;
    int currIdx = 0;
    bool keepGoing = true;
    bool sufficientModel = true;

    while (true) {
        if (static_cast<size_t>(currIdx) >= sigPtr.size())  // overflow
        {
            break;
        }

        if (sigPtr[currIdx].range < this->params.max_seed_range &&
            fabs(sigPtr[currIdx].height) < this->params.max_seed_height) {
            // close enough to
            // robot and height
            // makese sense in
            // robot locality

            sigPtr[currIdx].isGround = true;
            currentModel.push_back(sigPtr[currIdx]);
            sigPtr.erase(sigPtr.begin() + currIdx);
            ptCtr++;

        } else {
            currIdx++;
        }

        if (static_cast<size_t>(ptCtr) >= npt)  // done
        {
            break;
        }
    }

    // check size
    if (currentModel.size() < 2)  // not enough for model, all obs pts
    {
        keepGoing = false;
        sufficientModel = false;
    }

    // got the seedpoints, start theINSAC process
    // cov matrices
    MatX C_XsX;
    MatX C_XX;
    MatX C_XsXs;
    MatX C_XXs;

    if (sigPtr.size() == 0)
        // no points to insac, put the seed points in as ground
        keepGoing = false;

    MatX temp;
    MatX f_s;
    MatX Vf_s;
    while (keepGoing) {
        // generate the covariance matrices

        C_XsX =
          genGPModel(sigPtr, currentModel, this->params.p_sf, this->params.p_l);
        C_XX = genGPModel(
          currentModel, currentModel, this->params.p_sf, this->params.p_l);
        C_XsXs =
          genGPModel(sigPtr, sigPtr, this->params.p_sf, this->params.p_l);
        C_XXs = C_XsX.transpose();

        // temporary calc
        MatX tCalc1 =
          C_XX + (this->params.p_sn * MatX::Identity(C_XX.rows(), C_XX.cols()));
        MatX tCalc2 = C_XsX * tCalc1.inverse();

        // test the points against the current model

        MatX modelZ(currentModel.size(), 1);
        for (unsigned int i = 0; i < currentModel.size(); i++) {
            modelZ(i, 0) = currentModel[i].height;
        }

        f_s = tCalc2 * modelZ;
        Vf_s = C_XsXs - tCalc2 * C_XXs;

        if (Vf_s.rows() == 0) {
            keepGoing = false;
            LOG_INFO("WARNING BREAKING LOOP: VF_s does not exist");
            continue;
        }

        bool searchCandidatePoints = true;
        unsigned int k = 0;
        // test for inliers using INSAC algorithm
        int startSize = currentModel.size();  // beginning size of the model set
        while (searchCandidatePoints) {
            double vf = Vf_s(k, k);
            double met =
              (sigPtr[k].height - f_s(k)) / (sqrt(this->params.p_sn + vf * vf));

            if (vf < this->params.p_tmodel &&
                abs(met) < this->params.p_tdata) {  // we have an inlier!
                // add to model set
                currentModel.push_back(sigPtr[k]);
                // remove from sample set
                sigPtr.erase(sigPtr.begin() + k);

                // delete row from f_s
                temp = f_s;
                f_s.resize(f_s.rows() - 1, f_s.cols());
                f_s.topRows(k) = temp.topRows(k);
                f_s.bottomRows(temp.rows() - k - 1) =
                  temp.bottomRows(temp.rows() - k - 1);

                // delete row from Vf_s
                temp = Vf_s;
                Vf_s.resize(Vf_s.rows() - 1, Vf_s.cols());
                Vf_s.topRows(k) = temp.topRows(k);
                Vf_s.bottomRows(temp.rows() - k - 1) =
                  temp.bottomRows(temp.rows() - k - 1);

                // delete col from Vf_s
                temp = Vf_s;
                Vf_s.resize(Vf_s.rows(), Vf_s.cols() - 1);
                Vf_s.leftCols(k) = temp.leftCols(k);
                Vf_s.rightCols(temp.cols() - k - 1) =
                  temp.rightCols(temp.cols() - k - 1);

            } else {
                k++;
            }

            if (sigPtr.size() == k) {
                searchCandidatePoints = false;
            }
        }

        int endSize = currentModel.size();  // end size of the model set
        if (startSize == endSize || sigPtr.size() == 0) {
            keepGoing = false;
        }
    }  // end INSAC

    // fill in the ground and obs pointclouds

    double numObs = 0;
    Vec3 obsSum(0, 0, 0);

    for (int i = 0; i < (int) currentModel.size(); i++) {
        int currIdx = currentModel[i].idx;
        linCell *curCell = &pBG->aCell[sectorIndex].lCell[currIdx];

        // go through all the points in this cell and assign to ground/not
        // ground
        for (unsigned int j = 0; j < curCell->binPoints.size(); j++) {
            float h = abs(currentModel[i].height - curCell->binPoints[j].z);
            if (h < this->params.p_tg)  // z heights are close
            {
                gCloud->push_back(
                  curCell->binPoints[j]);  // add the point to ground
                curCell->groundPoints.push_back(curCell->binPoints[j]);
            } else {
                // check drivability
                if (h > this->params.robot_height) {
                    // drivable
                    curCell->binPoints[j].drivable = 1;
                } else {
                    curCell->binPoints[j].drivable = 0;
                    dCloud->push_back(curCell->binPoints[j]);  // add to obs
                    curCell->drvPoints.push_back(curCell->binPoints[j]);
                }
                oCloud->push_back(curCell->binPoints[j]);  // add to obs
                curCell->obsPoints.push_back(curCell->binPoints[j]);
                obsSum += Vec3(curCell->binPoints[j].x,
                               curCell->binPoints[j].y,
                               curCell->binPoints[j].z);
                numObs++;
            }
        }
        // mean of obs points
        curCell->obsMean = obsSum / numObs;
    }

    // FIXME: WHY IS F_S < SIGPTR SOMETIMES?
    int i;
    if (sufficientModel) {
        // add all the obs points from the non ground classified pts
        for (i = 0; i < (int) sigPtr.size(); i++) {
            linCell *curCell = &pBG->aCell[sectorIndex].lCell[sigPtr[i].idx];

            for (int j = 0; j < (int) curCell->binPoints.size(); j++) {
                float h = abs(curCell->binPoints[j].z - f_s(i));
                // check drivability
                if (h > this->params.robot_height) {
                    // drivable
                    curCell->binPoints[j].drivable = 1;
                } else {
                    curCell->binPoints[j].drivable = 0;
                    dCloud->push_back(curCell->binPoints[j]);  // add to obs
                    curCell->drvPoints.push_back(curCell->binPoints[j]);
                }
                oCloud->push_back(curCell->binPoints[j]);  // add to obs
                curCell->obsPoints.push_back(curCell->binPoints[j]);
                obsSum += Vec3(curCell->binPoints[j].x,
                               curCell->binPoints[j].y,
                               curCell->binPoints[j].z);
                numObs++;
            }
            // mean of obs points
            curCell->obsMean = obsSum / numObs;
        }
    } else {
        LOG_INFO("WARNING:Insufficnent Model for angular slice");
    }
}

}  // namespace wave


#include "it/Entropy.hpp"
#include "binomial.hpp"
#include <algorithm>
#include <map>
#include <set>

#if BOOST_PYTHON_EXTENSIONS
#include <boost/python/extract.hpp>
#endif

#include "algorithm/TupleSpace.hpp"
#include "it/EntropyCalculator.hpp"

using namespace mist;
using namespace mist::algorithm;

TupleSpace::TupleSpace()
  : tuple_size(0){};
TupleSpace::~TupleSpace(){};

// default space for N variables in tuples size d
TupleSpace::TupleSpace(int N, int d)
  : tuple_size(d)
{
  if (N == 0 || d == 0) {
    throw TupleSpaceException("TupleSpace",
                              "Number of variables and dimension cannot be zero.");
  }
  tuple_t vars(N);
  for (int ii = 0; ii < N; ii++) {
    vars[ii] = ii;
  }
  this->addVariableGroup("default", vars);
  tuple_t groupTuple(d, 0);
  this->addVariableGroupTuple(groupTuple);
};

std::vector<std::string>
TupleSpace::names() const
{
  return variableNames;
}

void
TupleSpace::set_names(std::vector<std::string> const& names)
{
  variableNames = names;
}

int
TupleSpace::tupleSize() const
{
  return tuple_size;
}

void
TupleSpace::addVariableGroupTuple(std::vector<std::string> const& groupNames)
{
  TupleSpace::tuple_t groupIndexes;
  for (auto& name : groupNames) {
    try {
      groupIndexes.push_back(variableGroupNames[name]);
    } catch (std::out_of_range& e) {
      throw TupleSpaceException("addVariableGroupTuple",
                                "group named " + name + " does not exist.");
    }
  }
  addVariableGroupTuple(groupIndexes);
}

void
TupleSpace::addVariableGroupTuple(TupleSpace::tuple_t const& groupIndexes)
{
  // validate tuple size
  if (!tuple_size) {
    tuple_size = groupIndexes.size();
  } else if (tuple_size != groupIndexes.size()) {
    throw TupleSpaceException(
      "addVariableGroupTuple",
      "Could not add group tuple, all tuples must be the same size");
  }
  // validate group indexes
  for (auto group : groupIndexes) {
    if (group >= variableGroups.size()) {
      throw TupleSpaceException("addVariableGroupTuple",
                                "variable group index " +
                                  std::to_string(group) + " out of range.");
    }
  }
  variableGroupTuples.push_back(groupIndexes);
}

int
TupleSpace::addVariableGroup(std::string const& name,
                             TupleSpace::tuple_t const& vars)
{
  std::set<int> unique_vars;
  tuple_t group;
  for (auto var : vars) {
    // ignore duplicates within variable group
    if (unique_vars.find(var) == unique_vars.end()) {
      unique_vars.insert(var);
      group.push_back(var);
      // check for overlap with other variable groups
      if (seen_vars.find(var) != seen_vars.end()) {
        throw TupleSpaceException(
          "addVariableGroup",
          "WARNING: variable " + std::to_string(var) +
            " listed twice in variable group definitions. This exception can "
            "be caught if overlapping variable groups are desired (unusual).");
      }
      seen_vars.emplace(var);
    }
  }
  std::sort(group.begin(), group.end());
  variableGroups.push_back(group);
  variableGroupSizes.push_back(group.size());
  int index = variableGroups.size() - 1;
  variableGroupNames.emplace(name, index);
  return index;
}

TupleSpace::tuple_t const&
TupleSpace::getVariableGroup(int index) const
{
  try {
    return variableGroups.at(index);
  } catch (std::out_of_range& e) {
    throw TupleSpaceException("getVariableGroup",
                              "group index " + std::to_string(index) +
                                " out of range.");
  }
}

TupleSpace::tuple_t const&
TupleSpace::getVariableGroup(std::string const& name) const
{
  try {
    return variableGroups.at(variableGroupNames.at(name));
  } catch (std::out_of_range& e) {
    throw TupleSpaceException("getVariableGroup",
                              "group named " + name + " does not exist.");
  }
}

std::vector<std::size_t> const&
TupleSpace::getVariableGroupSizes() const
{
    return variableGroupSizes;
}

std::vector<TupleSpace::tuple_t> const&
TupleSpace::getVariableGroups() const
{
  return variableGroups;
}

std::vector<TupleSpace::tuple_t> const&
TupleSpace::getVariableGroupTuples() const
{
  return variableGroupTuples;
}

#if BOOST_PYTHON_EXTENSIONS
int
TupleSpace::pyAddVariableGroup(std::string const& name, p::list const& list)
{
  int n = p::len(list);
  // copy list in
  algorithm::TupleSpace::tuple_t vars(n);
  for (int ii = 0; ii < n; ii++) {
    p::extract<int> var(list[ii]);
    if (var.check()) {
      vars[ii] = var;
    } else {
      throw TupleSpaceException("pyAddVariableGroup",
                                "Expected list with elements type int");
    }
  }
  return addVariableGroup(name, vars);
}

void
TupleSpace::pyAddVariableGroupTuple(p::list const& list)
{
  int n = p::len(list); // Doesn't give the right answer ...
  algorithm::TupleSpace::tuple_t groups(n);
  for (int ii = 0; ii < n; ii++) {
    p::extract<int> gint(list[ii]); //TODO segaults
    p::extract<std::string> gname(list[ii]);
    if (gint.check()) {
      groups[ii] = p::extract<int>(list[ii]);
    } else if (gname.check()) {
      std::string name(gname);
      try {
        groups[ii] = variableGroupNames.at(name);
      } catch (std::exception& e) {
        throw TupleSpaceException("pyAddVariableGroupTuple",
                                  "group named " + name + " does not exist.");
      }
    } else {
      throw TupleSpaceException("pyAddVariableGroupTuple",
                                "Expected list with elements type int or str");
    }
  }
  addVariableGroupTuple(groups);
}
#endif

static std::vector<std::size_t>
groupSizes(std::vector<TupleSpace::tuple_t> const& groups)
{
  std::vector<std::size_t> group_sizes;
  for (auto& group : groups) {
    group_sizes.push_back(group.size());
  }
  return group_sizes;
}

static TupleSpace::tuple_t
groupAppearances(int d, TupleSpace::tuple_t const& group_tuple, int pos)
{
  // count group appearances following the current index
  TupleSpace::tuple_t app(d, 0);
  for (int gg = pos; gg < group_tuple.size(); gg++) {
    app[group_tuple[gg]]++;
  }
  return app;
}

static TupleSpace::tuple_t
groupAppearances(int d, TupleSpace::tuple_t const& group_tuple)
{
  return groupAppearances(d, group_tuple, 0);
}

TupleSpace::count_t
TupleSpace::count_tuples_group_tuple(tuple_t const& group_tuple) const
{
  count_t total = 1;
  auto const& N = this->variableGroupSizes;
  auto d = N.size();
  auto a = groupAppearances(d, group_tuple);
  for (int ii = 0; ii < d; ii++) {
    total *= binomial(N[ii], a[ii]);
  }
  return total;
}

TupleSpace::count_t
TupleSpace::count_tuples() const
{
  count_t total = 0;
  for (auto const& group_tuple : this->variableGroupTuples) {
    total += count_tuples_group_tuple(group_tuple);
  }
  return total;
}

// Fast-forward to the group containig the tuple at the target position.
static int
find_group(TupleSpace::count_t* count, TupleSpace::count_t target, TupleSpace const& ts)
{
  auto const& group_tuples = ts.getVariableGroupTuples();
  int gg = 0;
  for (auto const& group_tuple : group_tuples) {
    TupleSpace::count_t skip = ts.count_tuples_group_tuple(group_tuple);
    if ((skip + *count) > target) {
      break;
    }
    *count += skip;
    gg++;
  }
  return gg;
}

// Fast-forward the index at position pos so that the target tuple is found by
// incrementing indexes in the following positions. E.g. if the target 3-tuple
// is (1,4,8), then
//
//      find_index(..., pos=0, ...) returns 1.
//      find_index(..., pos=1, ...) returns 4.
//      find_index(..., pos=2, ...) returns 8.
//
static int
find_index(TupleSpace::tuple_t const& group_tuple,
           int pos,
           TupleSpace::count_t* count,
           TupleSpace::count_t target,
           std::vector<std::size_t> const& N,
           TupleSpace::tuple_t& starts)
{
  unsigned ii = 0;                // index
  unsigned gi = group_tuple[pos]; // group corresponding to index
  auto app = groupAppearances(N.size(), group_tuple, pos + 1);

  for (ii = starts[gi]; ii < N[gi]; ii++) {
    starts[gi] = ii + 1;
    // count the number of tuples skipped over by incrementing index
    // for last index skip = 1 (could be optimized with a special case)
    TupleSpace::count_t skip = 1;
    for (unsigned gg = 0; gg < N.size(); gg++) {
      // binomial combination reduces to linear when appearances == 1
      TupleSpace::count_t f = 1;
      for (unsigned kk = 0; kk < app[gg]; kk++) {
        skip = skip * (N[gg] - starts[gg] - kk);
        f *= (kk + 1);
      }
      skip /= f;
    }
    if ((skip + *count) > target) {
      break;
    }
    *count += skip;
  }
  if (ii >= N[gi]) {
    throw TupleSpaceException("find_index", "Failed to FFW index, out of range");
  }
  return ii;
}

TupleSpace::tuple_t
TupleSpace::find_tuple(count_t target) const
{
  // The fast-forward algorithm maintains a skipped tuple count so that when
  // the count equals the target count we have found the target tuple.
  count_t count = 0;

  auto const& N = this->variableGroupSizes;
  auto const& group_tuples = this->variableGroupTuples;

  // scan ahead to the group tuple that generates the target tuple
  unsigned gg = find_group(&count, target, *this);
  auto tuple_size = group_tuples[gg].size();
  tuple_t ret(tuple_size + 1);
  ret[0] = gg;

  // scan to the target tuple
  tuple_t starts(N.size(), 0);
  for (unsigned ii = 0; ii < tuple_size; ii++) {
    ret[ii + 1] = find_index(group_tuples[gg], ii, &count, target, N, starts);
  }

  return ret;
}

static void
traverse_d1(TupleSpace const& ts, TupleSpace::count_t start, TupleSpace::count_t stop, TupleSpaceTraverser& traverser)
{
  auto const& groups = ts.getVariableGroups();
  auto const& group_tuples = ts.getVariableGroupTuples();
  auto const& N = ts.getVariableGroupSizes();
  unsigned ngroups = groups.size();
  unsigned ngtuples = group_tuples.size();

  // tuple generation state
  bool init = true;
  bool work = true;
  auto count = start;
  TupleSpace::tuple_t starts(ngroups);
  starts.assign(ngroups,0);

  // fast-forward to starting group and tuple
  auto ffw = ts.find_tuple(start);

  // sub tuples on the stack
  TupleSpace::tuple_t t0(1);

  for (unsigned gg = ffw[0]; gg < ngtuples && work; gg++) {
    unsigned g0 = group_tuples[gg][0];

    // loop through all tuples generated by this group_tuple
    for (unsigned i0 = (init) ? ffw[1] : starts[g0]; i0 < N[g0] && work; i0++) {
      starts[g0] = i0 + 1;
      auto v0 = groups[g0][i0];
      t0[0] = v0;
      traverser.process_tuple(count, t0);
      count++;
      init = false;
      work = count < stop;
    }
    starts[g0] = 0;
  }
}

static void
traverse_d2(TupleSpace const& ts, TupleSpace::count_t start, TupleSpace::count_t stop, TupleSpaceTraverser& traverser)
{
  auto const& groups = ts.getVariableGroups();
  auto const& group_tuples = ts.getVariableGroupTuples();
  auto const& N = ts.getVariableGroupSizes();
  unsigned ngroups = groups.size();
  unsigned ngtuples = group_tuples.size();

  // tuple generation state
  bool init = true;
  bool work = true;
  auto count = start;
  TupleSpace::tuple_t starts(ngroups);
  starts.assign(ngroups,0);

  // fast-forward to starting group and tuple
  auto ffw = ts.find_tuple(start);

  // sub tuples on the stack
  TupleSpace::tuple_t t01(2);

  for (unsigned gg = ffw[0]; gg < ngtuples && work; gg++) {
    unsigned g0 = group_tuples[gg][0];
    unsigned g1 = group_tuples[gg][1];

    // loop through all tuples generated by this group_tuple
    for (unsigned i0 = (init) ? ffw[1] : starts[g0]; i0 < N[g0] && work; i0++) {
      starts[g0] = i0 + 1;
      auto v0 = groups[g0][i0];
      t01[0] = v0;
      for (unsigned i1 = (init) ? ffw[2] : starts[g1]; i1 < N[g1] && work; i1++) {
        starts[g1] = i1 + 1;
        auto v1 = groups[g1][i1];
        t01[1] = v1;
        traverser.process_tuple(count, t01);
        count++;
        init = false;
        work = count < stop;
      }
      starts[g1] = 0;
    }
    starts[g0] = 0;
  }
}

static void
traverse_d2_entropy(TupleSpace const& ts, TupleSpace::count_t start, TupleSpace::count_t stop, TupleSpaceTraverser& traverser, it::EntropyCalculator & ecalc)
{
  auto const& groups = ts.getVariableGroups();
  auto const& group_tuples = ts.getVariableGroupTuples();
  auto const& N = ts.getVariableGroupSizes();
  unsigned ngroups = groups.size();
  unsigned ngtuples = group_tuples.size();

  // tuple generation state
  bool init = true;
  bool work = true;
  auto count = start;
  TupleSpace::tuple_t starts(ngroups);
  starts.assign(ngroups,0);
  it::Entropy entropy((unsigned)it::d2::size);

  // fast-forward to starting group and tuple
  auto ffw = ts.find_tuple(start);

  // sub tuples on the stack
  TupleSpace::tuple_t t0(1);
  TupleSpace::tuple_t t1(1);
  TupleSpace::tuple_t t01(2);

  for (unsigned gg = ffw[0]; gg < ngtuples && work; gg++) {
    unsigned g0 = group_tuples[gg][0];
    unsigned g1 = group_tuples[gg][1];

    // loop through all tuples generated by this group_tuple
    for (unsigned i0 = (init) ? ffw[1] : starts[g0]; i0 < N[g0] && work; i0++) {
      starts[g0] = i0 + 1;
      auto v0 = groups[g0][i0];
      t0[0] = v0;
      t01[0] = v0;
      entropy[(unsigned)it::d2::e0] = ecalc.entropy(t0);
      for (unsigned i1 = (init) ? ffw[2] : starts[g1]; i1 < N[g1] && work; i1++) {
        starts[g1] = i1 + 1;
        auto v1 = groups[g1][i1];
        t1[0] = v1;
        t01[1] = v1;
        entropy[(unsigned)it::d2::e1]  = ecalc.entropy(t1);
        entropy[(unsigned)it::d2::e01] = ecalc.entropy(t01);
        traverser.process_tuple_entropy(count, t01, entropy);
        count++;
        init = false;
        work = count < stop;
      }
      starts[g1] = 0;
    }
    starts[g0] = 0;
  }
}

static void
traverse_d3(TupleSpace const& ts, TupleSpace::count_t start, TupleSpace::count_t stop, TupleSpaceTraverser& traverser)
{
  auto const& groups = ts.getVariableGroups();
  auto const& group_tuples = ts.getVariableGroupTuples();
  auto const& N = ts.getVariableGroupSizes();
  unsigned ngroups = groups.size();
  unsigned ngtuples = group_tuples.size();

  // tuple generation state
  bool init = true;
  bool work = true;
  auto count = start;
  TupleSpace::tuple_t starts(ngroups);
  starts.assign(ngroups,0);

  // fast-forward to starting group and tuple
  auto ffw = ts.find_tuple(start);

  // sub tuples on the stack
  TupleSpace::tuple_t t012(3);

  for (unsigned gg = ffw[0]; gg < ngtuples && work; gg++) {
    unsigned g0 = group_tuples[gg][0];
    unsigned g1 = group_tuples[gg][1];
    unsigned g2 = group_tuples[gg][2];

    // loop through all tuples generated by this group_tuple
    for (unsigned i0 = (init) ? ffw[1] : starts[g0]; i0 < N[g0] && work; i0++) {
      starts[g0] = i0 + 1;
      auto v0 = groups[g0][i0];
      t012[0] = v0;
      for (unsigned i1 = (init) ? ffw[2] : starts[g1]; i1 < N[g1] && work; i1++) {
        starts[g1] = i1 + 1;
        auto v1 = groups[g1][i1];
        t012[1] = v1;
        for (unsigned i2 = (init) ? ffw[3] : starts[g2]; i2 < N[g2] && work; i2++) {
          starts[g2] = i2 + 1;
          auto v2 = groups[g2][i2];
          t012[2] = v2;
          traverser.process_tuple(count, t012);
          count++;
          init = false;
          work = count < stop;
        }
        starts[g2] = 0;
      }
      starts[g1] = 0;
    }
    starts[g0] = 0;
  }
}

static void
traverse_d3_entropy(TupleSpace const& ts, TupleSpace::count_t start, TupleSpace::count_t stop, TupleSpaceTraverser& traverser, it::EntropyCalculator & ecalc)
{
  auto const& groups = ts.getVariableGroups();
  auto const& group_tuples = ts.getVariableGroupTuples();
  auto const& N = ts.getVariableGroupSizes();
  unsigned ngroups = groups.size();
  unsigned ngtuples = group_tuples.size();

  // tuple generation state
  bool init = true;
  bool work = true;
  auto count = start;
  TupleSpace::tuple_t starts(ngroups);
  starts.assign(ngroups,0);
  it::Entropy entropy((unsigned)it::d3::size);

  // sub tuples on the stack
  TupleSpace::tuple_t t0(1);
  TupleSpace::tuple_t t1(1);
  TupleSpace::tuple_t t2(1);
  TupleSpace::tuple_t t01(2);
  TupleSpace::tuple_t t02(2);
  TupleSpace::tuple_t t12(2);
  TupleSpace::tuple_t t012(3);

  // fast-forward to starting group and tuple
  auto ffw = ts.find_tuple(start);

  for (unsigned gg = ffw[0]; gg < ngtuples && work; gg++) {
    unsigned g0 = group_tuples[gg][0];
    unsigned g1 = group_tuples[gg][1];
    unsigned g2 = group_tuples[gg][2];

    // loop through all tuples generated by this group_tuple
    for (unsigned i0 = (init) ? ffw[1] : starts[g0]; i0 < N[g0] && work; i0++) {
      starts[g0] = i0 + 1;
      auto v0 = groups[g0][i0];
      t0[0] = v0;
      t02[0] = v0;
      t01[0] = v0;
      t012[0] = v0;
      entropy[(unsigned)it::d3::e0] = ecalc.entropy(t0);
      for (unsigned i1 = (init) ? ffw[2] : starts[g1]; i1 < N[g1] && work; i1++) {
        starts[g1] = i1 + 1;
        auto v1 = groups[g1][i1];
        t1[0] = v1;
        t01[1] = v1;
        t12[0] = v1;
        t012[1] = v1;
        entropy[(unsigned)it::d3::e1]  = ecalc.entropy(t1);
        entropy[(unsigned)it::d3::e01] = ecalc.entropy(t01);
        for (unsigned i2 = (init) ? ffw[3] : starts[g2]; i2 < N[g2] && work; i2++) {
          starts[g2] = i2 + 1;
          auto v2 = groups[g2][i2];
          t2[0] = v2;
          t02[1] = v2;
          t12[1] = v2;
          t012[2] = v2;
          entropy[(unsigned)it::d3::e2]   = ecalc.entropy(t2);
          entropy[(unsigned)it::d3::e02]  = ecalc.entropy(t02);
          entropy[(unsigned)it::d3::e12]  = ecalc.entropy(t12);
          entropy[(unsigned)it::d3::e012] = ecalc.entropy(t012);
          traverser.process_tuple_entropy(count, t012, entropy);
          count++;
          init = false;
          work = count < stop;
        }
        starts[g2] = 0;
      }
      starts[g1] = 0;
    }
    starts[g0] = 0;
  }
}

static void
traverse_d4(TupleSpace const& ts, TupleSpace::count_t start, TupleSpace::count_t stop, TupleSpaceTraverser& traverser)
{
  auto const& groups = ts.getVariableGroups();
  auto const& group_tuples = ts.getVariableGroupTuples();
  auto const& N = ts.getVariableGroupSizes();
  unsigned ngroups = groups.size();
  unsigned ngtuples = group_tuples.size();

  // tuple generation state
  bool init = true;
  bool work = true;
  auto count = start;
  TupleSpace::tuple_t starts(ngroups);
  starts.assign(ngroups,0);

  // fast-forward to starting group and tuple
  auto ffw = ts.find_tuple(start);

  // sub tuples on the stack
  TupleSpace::tuple_t t0123(4);

  for (unsigned gg = ffw[0]; gg < ngtuples && work; gg++) {
    unsigned g0 = group_tuples[gg][0];
    unsigned g1 = group_tuples[gg][1];
    unsigned g2 = group_tuples[gg][2];
    unsigned g3 = group_tuples[gg][3];

    // loop through all tuples generated by this group_tuple
    for (unsigned i0 = (init) ? ffw[1] : starts[g0]; i0 < N[g0] && work; i0++) {
      starts[g0] = i0 + 1;
      auto v0 = groups[g0][i0];
      t0123[0] = v0;
      for (unsigned i1 = (init) ? ffw[2] : starts[g1]; i1 < N[g1] && work; i1++) {
        starts[g1] = i1 + 1;
        auto v1 = groups[g1][i1];
        t0123[1] = v1;
        for (unsigned i2 = (init) ? ffw[3] : starts[g2]; i2 < N[g2] && work; i2++) {
          starts[g2] = i2 + 1;
          auto v2 = groups[g2][i2];
          t0123[2] = v2;
          for (unsigned i3 = (init) ? ffw[4] : starts[g3]; i3 < N[g3] && work; i3++) {
            starts[g3] = i3 + 1;
            auto v3 = groups[g3][i3];
            t0123[3] = v3;
            traverser.process_tuple(count, t0123);
            count++;
            init = false;
            work = count < stop;
          }
          starts[g3] = 0;
        }
        starts[g2] = 0;
      }
      starts[g1] = 0;
    }
    starts[g0] = 0;
  }
}

static void
traverse_d4_entropy(TupleSpace const& ts, TupleSpace::count_t start, TupleSpace::count_t stop, TupleSpaceTraverser& traverser, it::EntropyCalculator & ecalc)
{
  auto const& groups = ts.getVariableGroups();
  auto const& group_tuples = ts.getVariableGroupTuples();
  auto const& N = ts.getVariableGroupSizes();
  unsigned ngroups = groups.size();
  unsigned ngtuples = group_tuples.size();

  // tuple generation state
  bool init = true;
  bool work = true;
  auto count = start;
  TupleSpace::tuple_t starts(ngroups);
  starts.assign(ngroups,0);
  it::Entropy entropy((unsigned)it::d4::size);

  // sub tuples on the stack
  TupleSpace::tuple_t t0(1);
  TupleSpace::tuple_t t1(1);
  TupleSpace::tuple_t t2(1);
  TupleSpace::tuple_t t3(1);
  TupleSpace::tuple_t t01(2);
  TupleSpace::tuple_t t02(2);
  TupleSpace::tuple_t t03(2);
  TupleSpace::tuple_t t12(2);
  TupleSpace::tuple_t t13(2);
  TupleSpace::tuple_t t23(2);
  TupleSpace::tuple_t t012(3);
  TupleSpace::tuple_t t013(3);
  TupleSpace::tuple_t t023(3);
  TupleSpace::tuple_t t123(3);
  TupleSpace::tuple_t t0123(4);

  // fast-forward to starting group and tuple
  auto ffw = ts.find_tuple(start);

  for (unsigned gg = ffw[0]; gg < ngtuples && work; gg++) {
    unsigned g0 = group_tuples[gg][0];
    unsigned g1 = group_tuples[gg][1];
    unsigned g2 = group_tuples[gg][2];
    unsigned g3 = group_tuples[gg][3];

    // loop through all tuples generated by this group_tuple
    for (unsigned i0 = (init) ? ffw[1] : starts[g0]; i0 < N[g0] && work; i0++) {
      starts[g0] = i0 + 1;
      auto v0 = groups[g0][i0];
      t0[0] = v0;
      t01[0] = v0;
      t02[0] = v0;
      t03[0] = v0;
      t012[0] = v0;
      t013[0] = v0;
      t023[0] = v0;
      t0123[0] = v0;
      entropy[(unsigned)it::d4::e0] = ecalc.entropy(t0);
      for (unsigned i1 = (init) ? ffw[2] : starts[g1]; i1 < N[g1] && work; i1++) {
        starts[g1] = i1 + 1;
        auto v1 = groups[g1][i1];
        t1[0] = v1;
        t01[1] = v1;
        t12[0] = v1;
        t13[0] = v1;
        t012[1] = v1;
        t013[1] = v1;
        t123[0] = v1;
        t0123[1] = v1;
        entropy[(unsigned)it::d4::e1]  = ecalc.entropy(t1);
        entropy[(unsigned)it::d4::e01] = ecalc.entropy(t01);
        for (unsigned i2 = (init) ? ffw[3] : starts[g2]; i2 < N[g2] && work; i2++) {
          starts[g2] = i2 + 1;
          auto v2 = groups[g2][i2];
          t2[0] = v2;
          t02[1] = v2;
          t12[1] = v2;
          t23[0] = v2;
          t012[2] = v2;
          t023[1] = v2;
          t123[1] = v2;
          t0123[2] = v2;
          entropy[(unsigned)it::d4::e2]   = ecalc.entropy(t2);
          entropy[(unsigned)it::d4::e02]  = ecalc.entropy(t02);
          entropy[(unsigned)it::d4::e12]  = ecalc.entropy(t12);
          entropy[(unsigned)it::d4::e012] = ecalc.entropy(t012);
          for (unsigned i3 = (init) ? ffw[4] : starts[g3]; i3 < N[g3] && work; i3++) {
            starts[g3] = i3 + 1;
            auto v3 = groups[g3][i3];
            t3[0] = v3;
            t03[1] = v3;
            t13[1] = v3;
            t23[1] = v3;
            t013[2] = v3;
            t023[2] = v3;
            t123[2] = v3;
            t0123[3] = v3;
            entropy[(unsigned)it::d4::e3]    = ecalc.entropy(t3);
            entropy[(unsigned)it::d4::e03]   = ecalc.entropy(t03);
            entropy[(unsigned)it::d4::e13]   = ecalc.entropy(t13);
            entropy[(unsigned)it::d4::e23]   = ecalc.entropy(t23);
            entropy[(unsigned)it::d4::e013]  = ecalc.entropy(t013);
            entropy[(unsigned)it::d4::e023]  = ecalc.entropy(t023);
            entropy[(unsigned)it::d4::e123]  = ecalc.entropy(t123);
            entropy[(unsigned)it::d4::e0123] = ecalc.entropy(t0123);
            traverser.process_tuple_entropy(count, t0123, entropy);
            count++;
            init = false;
            work = count < stop;
          }
          starts[g3] = 0;
        }
        starts[g2] = 0;
      }
      starts[g1] = 0;
    }
    starts[g0] = 0;
  }
}

void
TupleSpace::traverse(TupleSpaceTraverser& traverser) const
{
  traverse(0, -1, traverser);
}

void
TupleSpace::traverse_entropy(it::EntropyCalculator &ecalc, TupleSpaceTraverser& traverser) const
{
  traverse_entropy(0, -1, ecalc, traverser);
}

void
TupleSpace::traverse(count_t start, count_t stop, TupleSpaceTraverser& traverser) const
{
  switch(tuple_size) {
    case 1: traverse_d1(*this, start, stop, traverser); break;
    case 2: traverse_d2(*this, start, stop, traverser); break;
    case 3: traverse_d3(*this, start, stop, traverser); break;
    case 4: traverse_d4(*this, start, stop, traverser); break;
    default:
      throw TupleSpaceException("traverse", "Tuple size greater than 4 unsupported.");
  }
}

void
TupleSpace::traverse_entropy(count_t start, count_t stop, it::EntropyCalculator &ecalc, TupleSpaceTraverser& traverser) const
{
  switch(tuple_size) {
    case 1:
      throw TupleSpaceException("traverse_entropy", "Tuple size 1 unsupported.");
    case 2: traverse_d2_entropy(*this, start, stop, traverser, ecalc); break;
    case 3: traverse_d3_entropy(*this, start, stop, traverser, ecalc); break;
    case 4: traverse_d4_entropy(*this, start, stop, traverser, ecalc); break;
    default:
      throw TupleSpaceException("traverse", "Tuple size greater than 4 unsupported.");
  }
}