#include <algorithm>

#include <boost/numeric/mtl/mtl.hpp>

#include "DOFMatrix.hpp"
#include "QPsiPhi.hpp"
#include "BasisFunction.hpp"
#include "Boundary.hpp"
#include "DOFAdmin.hpp"
#include "ElInfo.hpp"
#include "FiniteElemSpace.hpp"
#include "Mesh.hpp"
#include "DOFVector.hpp"
#include "Operator.hpp"
#include "BoundaryCondition.hpp"
#include "BoundaryManager.hpp"
#include "Assembler.hpp"

namespace AMDiS
{
  using namespace mtl;

  DOFMatrix::DOFMatrix()
    : rowFeSpace(NULL),
      colFeSpace(NULL),
      elementMatrix(3, 3),
      nRow(0),
      nCol(0),
      nnzPerRow(0),
      inserter(NULL)
  {}

  DOFMatrix::DOFMatrix(const FiniteElemSpace* rowSpace,
                       const FiniteElemSpace* colSpace,
                       std::string n)
    : rowFeSpace(rowSpace),
      colFeSpace(colSpace),
      name(n),
      coupleMatrix(false),
      nnzPerRow(0),
      inserter(NULL)
  {
    FUNCNAME("DOFMatrix::DOFMatrix()");

    TEST_EXIT(rowFeSpace)("No fe space for row!\n");

    if (!colFeSpace)
      colFeSpace = rowFeSpace;
    boundaryManager = new BoundaryManager(rowFeSpace);

    nRow = rowFeSpace->getBasisFcts()->getNumber();
    nCol = colFeSpace->getBasisFcts()->getNumber();
    elementMatrix.change_dim(nRow, nCol);
    rowIndices.resize(nRow);
    colIndices.resize(nCol);
  }

  DOFMatrix::DOFMatrix(const DOFMatrix& rhs)
    : name(rhs.name + "copy")
  {
    FUNCNAME("DOFMatrix::DOFMatrix()");

    *this = rhs;
    TEST_EXIT(rhs.inserter == 0)("Cannot copy during insertion!\n");
    inserter = 0;
  }

  DOFMatrix::~DOFMatrix()
  {
    if (boundaryManager)
      delete boundaryManager;
    if (inserter)
      delete inserter;
  }

  void DOFMatrix::print() const
  {
    if (inserter)
      inserter->print();
  }

  int DOFMatrix::getUsedSize() const
  {
    return rowFeSpace->getAdmin()->getUsedSize();
  }

  DOFMatrix& DOFMatrix::operator=(const DOFMatrix& rhs)
  {
    rowFeSpace = rhs.rowFeSpace;
    colFeSpace = rhs.colFeSpace;
    operators = rhs.operators;
    operatorFactor = rhs.operatorFactor;
    coupleMatrix = rhs.coupleMatrix;

    /// The matrix values may only be copyed, if there is no active insertion.
    if (rhs.inserter == 0 && inserter == 0)
      matrix = rhs.matrix;

    if (rhs.boundaryManager)
      boundaryManager = new BoundaryManager(*rhs.boundaryManager);
    else
      boundaryManager = NULL;

    nRow = rhs.nRow;
    nCol = rhs.nCol;
    elementMatrix.change_dim(nRow, nCol);

    return *this;
  }

  void DOFMatrix::addElementMatrix(const ElementMatrix& elMat,
                                   const BoundaryType* bound,
                                   ElInfo* rowElInfo,
                                   ElInfo* colElInfo)
  {
    FUNCNAME_DBG("DOFMatrix::addElementMatrix()");

    TEST_EXIT_DBG(inserter)("DOFMatrix is not in insertion mode\n");
    TEST_EXIT_DBG(rowFeSpace)("Have now rowFeSpace!\n");

    inserter_type& ins= *inserter;

    // === Get indices mapping from local to global matrix indices. ===

    rowFeSpace->getBasisFcts()->getLocalIndices(rowElInfo->getElement(),
        rowFeSpace->getAdmin(),
        rowIndices);
    if (rowFeSpace == colFeSpace)
    {
      colIndices = rowIndices;
    }
    else
    {
      if (colElInfo)
      {
        colFeSpace->getBasisFcts()->getLocalIndices(colElInfo->getElement(),
            colFeSpace->getAdmin(),
            colIndices);
      }
      else
      {
        // If there is no colElInfo pointer, use rowElInfo the get the indices.
        colFeSpace->getBasisFcts()->getLocalIndices(rowElInfo->getElement(),
            colFeSpace->getAdmin(),
            colIndices);
      }
    }

    for (int i = 0; i < nRow; i++)
    {
      DegreeOfFreedom row = rowIndices[i];

      BoundaryCondition* condition =
        bound ? boundaryManager->getBoundaryCondition(bound[i]) : NULL;

      if (condition && condition->isDirichlet())
      {
        if (condition->applyBoundaryCondition())
          dirichletDofs.insert(row);
      }
      else
      {
        for (int j = 0; j < nCol; j++)
        {
          DegreeOfFreedom col = colIndices[j];
          ins[row][col] += elMat[i][j];
        }
      }
    }
  }

  void DOFMatrix::assembleOperator(Operator& op)
  {
    FUNCNAME("DOFMatrix::assembleOperator()");

    TEST_EXIT(rowFeSpace->getMesh() == colFeSpace->getMesh())
    ("This function does not support for multi mesh procedure!\n");
    TEST_EXIT(op.getRowFeSpace() == rowFeSpace)
    ("Row FE spaces do not fit together!\n");
    TEST_EXIT(op.getColFeSpace() == colFeSpace)
    ("Column FE spaces do not fit together!\n");

    clearOperators();
    addOperator(&op);

    matrix.change_dim(rowFeSpace->getAdmin()->getUsedSize(),
                      colFeSpace->getAdmin()->getUsedSize());

    Mesh* mesh = rowFeSpace->getMesh();
    mesh->dofCompress();
    const BasisFunction* basisFcts = rowFeSpace->getBasisFcts();

    Flag assembleFlag = getAssembleFlag() |
                        Mesh::CALL_LEAF_EL                        |
                        Mesh::FILL_COORDS                         |
                        Mesh::FILL_DET                            |
                        Mesh::FILL_GRD_LAMBDA |
                        Mesh::FILL_NEIGH |
                        Mesh::FILL_BOUND;

    BoundaryType* bound = new BoundaryType[basisFcts->getNumber()];

    calculateNnz();
    if (getBoundaryManager())
      getBoundaryManager()->initMatrix(this);

    startInsertion(getNnz());

    TraverseStack stack;
    ElInfo* elInfo = stack.traverseFirst(mesh, -1, assembleFlag);
    while (elInfo)
    {
      basisFcts->getBound(elInfo, bound);

      assemble(1.0, elInfo, bound);

      if (getBoundaryManager())
        getBoundaryManager()->fillBoundaryConditions(elInfo, this);

      elInfo = stack.traverseNext(elInfo);
    }

    clearDirichletRows();
    finishAssembling();
    finishInsertion();
    getBoundaryManager()->exitMatrix(this);

    delete [] bound;
  }

  void DOFMatrix::assemble(double factor,
                           ElInfo* elInfo,
                           const BoundaryType* bound)
  {
    set_to_zero(elementMatrix);

    std::vector<Operator*>::iterator it = operators.begin();
    std::vector<double*>::iterator factorIt = operatorFactor.begin();
    for (; it != operators.end(); ++it, ++factorIt)
      if ((*it)->getNeedDualTraverse() == false &&
          (*factorIt == NULL ||** factorIt != 0.0))
        (*it)->getElementMatrix(elInfo,	elementMatrix, *factorIt ? **factorIt : 1.0);

    if (factor != 1.0)
      elementMatrix *= factor;

    if (operators.size())
      addElementMatrix(elementMatrix, bound, elInfo, NULL);
  }

  void DOFMatrix::assemble(double factor,
                           ElInfo* elInfo,
                           const BoundaryType* bound,
                           Operator* op)
  {
    FUNCNAME_DBG("DOFMatrix::assemble()");

    TEST_EXIT_DBG(op)("No operator!\n");

    set_to_zero(elementMatrix);
    op->getElementMatrix(elInfo, elementMatrix, factor);

    if (factor != 1.0)
      elementMatrix *= factor;

    addElementMatrix(elementMatrix, bound, elInfo, NULL);
  }

  void DOFMatrix::finishAssembling()
  {
    // call the operators cleanup procedures
    for (std::vector<Operator*>::iterator it = operators.begin();
         it != operators.end(); ++it)
      (*it)->finishAssembling();
  }

  // Should work as before
  Flag DOFMatrix::getAssembleFlag()
  {
    Flag fillFlag(0);
    for (std::vector<Operator*>::iterator op = operators.begin();
         op != operators.end(); ++op)
      fillFlag |= (*op)->getFillFlag();

    return fillFlag;
  }

  void DOFMatrix::addOperator(Operator* op, double* factor, double* estFactor)
  {
    operators.push_back(op);
    operatorFactor.push_back(factor);
    operatorEstFactor.push_back(estFactor);
  }

  void DOFMatrix::clearOperators()
  {
    operators.clear();
    operatorFactor.clear();
    operatorEstFactor.clear();
  }

  void DOFMatrix::copy(const DOFMatrix& rhs)
  {
    matrix = rhs.matrix;
  }

  void DOFMatrix::clearDirichletRows()
  {
    // Do the following only in sequential code. In parallel mode, the specific
    // solver method must care about dirichlet boundary conditions.
    inserter_type& ins = *inserter;
    for (std::set<DegreeOfFreedom>::iterator it = dirichletDofs.begin();
         it != dirichletDofs.end(); ++it)
      ins[(*it)][(*it)] = 1.0;
  }

  size_t DOFMatrix::memsize() const
  {
    return (num_rows(matrix) + matrix.nnz()) * sizeof(base_matrix_type::size_type)
           + matrix.nnz() * sizeof(base_matrix_type::value_type);
  }

  void DOFMatrix::startInsertion(int nnz_per_row)
  {
    if (inserter)
    {
      delete inserter;
      inserter = NULL;
    }

    inserter = new inserter_type(matrix, nnz_per_row);

    dirichletDofs.clear();
  }

} // end namespace AMDiS


/**
 * Copyright (c) 2020, Román Cárdenas Rodríguez
 * ARSLab - Carleton University
 * GreenLSI - Polytechnic University of Madrid
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef CADMIUM_CELLDEVS_GRID_UTILS_HPP
#define CADMIUM_CELLDEVS_GRID_UTILS_HPP

#include <utility>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <utility>
#include <cassert>
#include <exception>
#include <cmath>
#include <boost/functional/hash.hpp>
#include <cadmium/celldevs/utils/utils.hpp>

namespace cadmium::celldevs {

    using cell_position = std::vector<int>;  /// Alias to refer to a cell.

    /**
     * Alias to refer to an unordered map with cell positions as keys.
     * @tparam X type of the values stored in the unordered map.
     */
    template<typename X>
    using cell_unordered = std::unordered_map<cell_position, X>;

    /**
     * Cell configuration structure.
     * @tparam S type used to represent cell states.
     * @tparam V type used to represent vicinities between cells.
     */
    template <typename S, typename V>
    using grid_cell_config = cell_config<cell_position, S, V>;

    /**
     * Auxiliary class with useful functions for grid cells.
     * @tparam S type used to represent cell states.
     * @tparam V type used to represent vicinities between cells.
     */
    template<typename S, typename V=int>
    class cell_map {
    public:
        cell_position shape;                /// Shape of the scenario
        cell_position location;             /// Location of the cell
        S state;                            /// Initial state of the cell
        cell_unordered<V> neighborhood;     /// Indicates the vicinities type of neighbor cells
        bool wrapped;                       /// It indicates whether the scenario is wrapped or not

        cell_map() { throw std::exception(); }

        cell_map(cell_position shape, cell_position location, S const &state, cell_unordered<V> const &neighborhood, bool wrapped);

        [[maybe_unused]] [[nodiscard]] int manhattan_distance(cell_position const &a) const;

        [[maybe_unused]] [[nodiscard]] int chebyshev_distance(cell_position const &a) const;

        [[maybe_unused]] [[nodiscard]] double n_norm_distance(cell_position const &a, unsigned int n) const;

        [[maybe_unused]] [[nodiscard]] double euclidean_distance(cell_position const &a) const;

        [[maybe_unused]] [[nodiscard]] cell_position neighbor(cell_position const &relative) const;

        [[maybe_unused]] [[nodiscard]] cell_position relative(cell_position const &neighbor) const;
    };

    /**
     * Class used by grid_coupled to set the scenario.
     * @tparam S type used to represent cell states.
     * @tparam V type used to represent vicinities between cells.
     */
    template<typename S, typename V>
    class grid_scenario {
    public:
        cell_position shape;                             /// Shape of the scenario.
        unsigned int dimension;                          /// Dimension of the grid of the scenario.
        cell_unordered<grid_cell_config<S, V>> configs;  /// Configuration of cells in the grid.
        bool wrapped;                                    /// It indicates whether the scenario is wrapped or not.

        void set_initial_config(const grid_cell_config<S, V> &config) {
            configs = cell_unordered<grid_cell_config<S, V>>();
            cell_position current = cell_position();
            for (int i = 0; i < dimension; i++)
                current.push_back(0);
            while (true) {
                try {
                    set_initial_config(current, config);
                    current = next_cell(current, 0);
                } catch (std::overflow_error &e) {
                    break;
                }
            }
        }

        void set_initial_config(const cell_position &cell, const grid_cell_config<S, V> config) {
            assert(cell_in_scenario(cell));
            configs[cell] = config;
        }

        grid_scenario(const cell_position &shape, const grid_cell_config<S, V> &config, bool wrapped):
        shape(shape), dimension(shape.size()), configs(), wrapped(wrapped) {
            // Assert that the shape of the scenario is well-defined
            set_initial_config(config);
            for (auto const &d: shape)
                assert(d > 0);
            for (auto const &cell: configs) {
                assert(cell.first.size() == dimension);
            }
        }

        /***************************************************/
        /***************** STATIC METHODS ******************/
        /***************************************************/

        /*************** distance functions ****************/
        // Auxiliary function for obtaining the distance vector between two cell
        static cell_position distance_vector(const cell_position &origin, const cell_position &destination,
                                             const cell_position &shape, bool wrapped) {
            assert(cell_in_scenario(origin, shape) && cell_in_scenario(destination, shape));
            cell_position res = cell_position();
            for (int i = 0; i < origin.size(); i++) {
                auto diff = destination[i] - origin[i];
                if (wrapped && std::abs(diff) > shape[i] / 2)
                    diff = (diff < 0) ? diff + shape[i] : diff - shape[i];
                res.push_back(diff);
            }
            return res;
        }

        // Auxiliary function for obtaining the destination cell from the origin cell and the distance vector
        static cell_position destination_cell(const cell_position &origin, const cell_position &distance,
                                              const cell_position &shape, bool wrapped) {
            assert(cell_in_scenario(origin, shape) && distance.size() == shape.size());
            for (int i = 0; i < shape.size(); i++)
                assert(std::abs(distance[i]) < shape[i]);
            cell_position res = cell_position();
            for (int i = 0; i < origin.size(); i++) {
                auto dest = origin[i] + distance[i];
                if (wrapped)
                    dest = (dest + shape[i]) % shape[i];
                res.push_back(dest);
            }
            if (!cell_in_scenario(res, shape))
                throw std::overflow_error("Destination cell is not in scenario");
            return res;
        }

        // Auxiliary function for obtaining the Manhattan distance between two cell of the grid
        [[maybe_unused]] static int manhattan_distance(const cell_position &a, const cell_position &b,
                                                       const cell_position &shape, bool wrapped) {
            int res = 0;
            for (auto const &d: distance_vector(a, b, shape, wrapped))
                res += std::abs(d);
            return res;
        }

        // Auxiliary function for obtaining the Chebyshev distance between two cell of the grid
        [[maybe_unused]] static int chebyshev_distance(const cell_position &a, const cell_position &b,
                                                       const cell_position &shape, bool wrapped) {
            int res = 0;
            for (auto const &d: distance_vector(a, b, shape, wrapped)) {
                auto d_abs = std::abs(d);
                res = (d_abs > res) ? d_abs : res;
            }
            return res;
        }

        // Auxiliary function for obtaining the N-norm distance between two cell of the grid
        [[maybe_unused]] static double n_norm_distance(const cell_position &a, const cell_position &b, unsigned int n,
                                                       const cell_position &shape, bool wrapped) {
            assert(n > 0);
            double res = 0;
            for (auto const &d: distance_vector(a, b, shape, wrapped))
                res += std::pow((float) std::abs(d), n);
            return std::pow(res, 1.0 / n);
        }

        // Auxiliary function for obtaining the Euclidean distance between two cell of the grid
        [[maybe_unused]] static double euclidean_distance(const cell_position &a, const cell_position &b,
                                                          const cell_position &shape, bool wrapped) {
            return n_norm_distance(a, b, 2, shape, wrapped);
        }

        /************* Neighborhoods functions *************/
        // Auxiliary function for generating biassed Moore neighborhoods (i.e., center cell is not (0,0...)
        static std::vector<cell_position> biassed_moore_neighborhood(unsigned int dimension, unsigned int range) {
            std::vector<cell_position> res = std::vector<cell_position>();
            cell_position scenario_shape = cell_position();
            cell_position current = cell_position();
            for (int i = 0; i < dimension; i++) {
                scenario_shape.push_back(2 * range + 1);
                current.push_back(0);
            }
            while (true) {
                res.push_back(current);
                try {
                    current = next_cell(current, scenario_shape, 0);
                }
                catch (std::overflow_error &e) {
                    break;
                }
            }
            return res;
        }

        // Auxiliary function for unbiassing a neighborhood as a function of a middle cell
        static void unbias_neighborhood(std::vector<cell_position> &biassed_neighborhood, const cell_position &middle) {
            for (auto &cell: biassed_neighborhood) {
                int dimension = cell.size();
                for (int i = 0; i < dimension; i++) {
                    cell[i] -= middle[i];
                }
            }
        }

        // Auxiliary function for generating von Neumann neighborhoods
        static std::vector<cell_position> biassed_von_neumann_neighborhood(unsigned int dimension, unsigned int range) {
            std::vector<cell_position> res = std::vector<cell_position>();
            std::vector<cell_position> moore = biassed_moore_neighborhood(dimension, range);
            cell_position middle = cell_position();
            cell_position shape = cell_position();
            for (int i = 0; i < dimension; i++) {
                shape.push_back(2 * range + 1);
                middle.push_back(range);
            }
            for (auto const &neighbor: moore) {
                if (manhattan_distance(middle, neighbor, shape, false) <= range)
                    res.push_back(neighbor);
            }
            return res;
        }

        // Auxiliary function for generating Moore neighborhoods
        static std::vector<cell_position> moore_neighborhood(unsigned int dimension, unsigned int range) {
            std::vector<cell_position> res = biassed_moore_neighborhood(dimension, range);
            cell_position middle = cell_position();
            for (int i = 0; i < dimension; i++) {
                middle.push_back(range);
            }
            unbias_neighborhood(res, middle);
            return res;
        }

        // Auxiliary function for generating von Neumann neighborhoods
        static std::vector<cell_position> von_neumann_neighborhood(unsigned int dimension, unsigned int range) {
            std::vector<cell_position> res = biassed_von_neumann_neighborhood(dimension, range);
            cell_position middle = cell_position();
            for (int i = 0; i < dimension; i++) {
                middle.push_back(range);
            }
            unbias_neighborhood(res, middle);
            return res;
        }
        /*************** Cell space-related **************/
        // Auxiliary function for iterating over cell of a scenario
        static cell_position next_cell(cell_position last_cell, cell_position const &scenario_shape, int d) {
            // If the dimension being explored has not reached the maximum, we just sum 1 to this dimension
            if (last_cell[d] < scenario_shape[d] - 1) {
                last_cell[d]++;
                return last_cell;
                // If the d is not the maximum dimension, we trigger a recursive call for incrementing the next dimension
            } else if (d < last_cell.size() - 1) {
                last_cell[d] = 0;
                return next_cell(last_cell, scenario_shape, d + 1);
                // Otherwise, we have reached the limit of our scenario. Throw overflow error.
            } else {
                throw std::overflow_error("Reached the last cell of the scenario");
            }
        }

        // Returns true if cell is within the boundaries of the scenario
        static bool cell_in_scenario(cell_position const &cell, cell_position const &shape) {
            assert(cell.size() == shape.size());
            for (int i = 0; i < shape.size(); i++) {
                if (cell[i] < 0 || cell[i] >= shape[i])
                    return false;
            }
            return true;
        }

        /***************************************************/
        /*************** NON-STATIC METHODS ****************/
        /***************************************************/

        /*************** Distance functions ****************/
        [[maybe_unused]] cell_position distance_vector(cell_position const &origin, cell_position const &destination) {
            return distance_vector(origin, destination, shape, wrapped);
        }

        [[maybe_unused]] cell_position destination_cell(cell_position const &origin, cell_position const &distance) {
            return destination_cell(origin, distance, shape, wrapped);
        }

        [[maybe_unused]] int manhattan_distance(cell_position const &a, cell_position const &b) {
            return manhattan_distance(a, b, shape, wrapped);
        }

        [[maybe_unused]] int chebyshev_distance(cell_position const &a, cell_position const &b) {
            return chebyshev_distance(a, b, shape, wrapped);
        }

        [[maybe_unused]] double n_norm_distance(cell_position const &a, cell_position const &b, unsigned int n) {
            return n_norm_distance(a, b, n, shape, wrapped);
        }

        [[maybe_unused]] double euclidean_distance(cell_position const &a, cell_position const &b) {
            return n_norm_distance(a, b, 2);
        }
        /*************** Cell space-related **************/
        // Returns true if cell is within the boundaries of the scenario
        bool cell_in_scenario(cell_position const &cell) {
            return cell_in_scenario(cell, shape);
        }

        cell_position next_cell(cell_position last_cell, int d) {
            return next_cell(std::move(last_cell), shape, d);
        }

        // Returns unordered map {relative_neighbor: absolute_neighbor} for a given cell
        cell_map<S, V> get_cell_map(const cell_position &cell) {
            assert(cell_in_scenario(cell));
            auto config = configs[cell];
            cell_unordered<V> neighborhood = cell_unordered<V>();
            for (auto const &neighbor: config.neighborhood) {
                cell_position relative = neighbor.first;
                V vicinity = neighbor.second;
                try {
                    cell_position absolute = destination_cell(cell, relative);
                    neighborhood.insert({absolute, vicinity});
                } catch (std::overflow_error &e) {  // Only if neighbor is valid will it be added to the map
                    continue;
                }
            }
            return cell_map<S, V>(shape, cell, config.state, neighborhood, wrapped);
        }
    };

    template<typename S, typename V>
    cell_map<S, V>::cell_map(cell_position shape, cell_position location, const S &state, const cell_unordered<V> &neighborhood, bool wrapped) :
        shape(std::move(shape)), location(std::move(location)), state(state), neighborhood(neighborhood), wrapped(wrapped) {}

    template<typename S, typename V>
    int cell_map<S, V>::manhattan_distance(const cell_position &a) const {
        return grid_scenario<S, V>::manhattan_distance(location, a, shape, wrapped);
    }

    template<typename S, typename V>
    int cell_map<S, V>::chebyshev_distance(const cell_position &a) const {
        return grid_scenario<S, V>::chebyshev_distance(location, a, shape, wrapped);
    }

    template<typename S, typename V>
    double cell_map<S, V>::n_norm_distance(const cell_position &a, unsigned int n) const {
        return grid_scenario<S, V>::n_norm_distance(location, a, n, shape, wrapped);
    }

    template<typename S, typename V>
    [[maybe_unused]] double cell_map<S, V>::euclidean_distance(const cell_position &a) const {
        return n_norm_distance(a, 2);
    }

    template<typename S, typename V>
    [[maybe_unused]] cell_position cell_map<S, V>::neighbor(const cell_position &relative) const {
        return grid_scenario<S, V>::destination_cell(location, relative, shape, wrapped);
    }

    template<typename S, typename V>
    [[maybe_unused]] cell_position cell_map<S, V>::relative(const cell_position &neighbor) const {
        return grid_scenario<S, V>::distance_vector(location, neighbor, shape, wrapped);
    }
} //namespace cadmium::celldevs
#endif //CADMIUM_CELLDEVS_GRID_UTILS_HPP


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#ifndef MTL_MAP_VIEW_INCLUDE
#define MTL_MAP_VIEW_INCLUDE

#include <utility>
#include <boost/utility/enable_if.hpp>
#include <boost/mpl/if.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/type_traits/is_integral.hpp>
#include <boost/type_traits/add_reference.hpp>
#include <boost/shared_ptr.hpp>

#include <boost/numeric/mtl/concept/collection.hpp>
#include <boost/numeric/mtl/utility/category.hpp>
#include <boost/numeric/mtl/utility/range_generator.hpp>
#include <boost/numeric/mtl/utility/property_map.hpp>
#include <boost/numeric/mtl/utility/is_multi_vector_expr.hpp>
#include <boost/numeric/mtl/utility/auto_type.hpp>
#include <boost/numeric/mtl/utility/auto_or_const_ref_type.hpp>
#include <boost/numeric/mtl/matrix/crtp_base_matrix.hpp>
#include <boost/numeric/mtl/operation/sub_matrix.hpp>
#include <boost/numeric/mtl/operation/sfunctor.hpp>
#include <boost/numeric/mtl/operation/tfunctor.hpp>
#include <boost/numeric/mtl/operation/conj.hpp>
#include <boost/numeric/mtl/operation/imag.hpp>
#include <boost/numeric/mtl/operation/real.hpp>

#include <boost/numeric/mtl/matrix/mat_expr.hpp>
#include <boost/numeric/mtl/vector/map_view.hpp>

namespace mtl { namespace mat { namespace detail {
    // Forward declaration for friend declaration
    template <typename, typename> struct map_value;

    template <typename Functor, typename Matrix> struct map_vector {}; // not defined in general
    template <typename Functor, typename Vector>
    struct map_vector<Functor, mtl::mat::multi_vector<Vector> >
    {
	typedef mtl::vec::map_view<Functor, Vector> type;
    };

}}}

namespace mtl { namespace mat {

// View for elementwise mapping of a matrix
template <typename Functor, typename Matrix>
struct map_view
  : public const_crtp_base_matrix< map_view<Functor, Matrix>,
				   typename Functor::result_type, typename Matrix::size_type >,
    public mat_expr< map_view<Functor, Matrix> >
{
    typedef map_view                                                    self;
    typedef mat_expr< self >                                            expr_base;
    typedef typename mtl::traits::auto_type<Matrix>::type               other_type; // other as container type
    typedef typename mtl::traits::auto_or_const_ref_type<Matrix>::type  other;      // container or Matrix&
//     typedef typename boost::add_reference<other>::type const        const_ref_type;
//     typedef Matrix                                     other;
//     typedef const Matrix&                              const_ref_type;
    typedef typename OrientedCollection<other_type>::orientation        orientation;

    typedef typename Functor::result_type                               value_type;
    typedef typename Functor::result_type                               const_reference;

    typedef typename other_type::key_type                               key_type;
    typedef typename other_type::size_type                              size_type;
    typedef typename other_type::index_type                             index_type;
    typedef typename other_type::dim_type                               dim_type;

    struct dummy { typedef void type; };
    // Maybe other_type instead of Matrix for more generality
    typedef typename boost::mpl::eval_if<mtl::traits::is_multi_vector_expr<Matrix>, detail::map_vector<Functor, Matrix>, dummy>::type vector_type;

    map_view (const Functor& functor, const Matrix& ref) : functor(functor), ref(ref) {}

    map_view (const Functor& functor, boost::shared_ptr<Matrix> p)
      : functor(functor), my_copy(p), ref(*p) {}

#ifdef MTL_WITH_MOVE
  map_view (self&& that) : my_copy(std::move(that.my_copy)), functor(that.functor), ref(that.ref) {}
#endif

    map_view (const self& that) : functor(that.functor), ref(that.ref) { assert(that.my_copy.use_count() == 0); }

    value_type operator() (size_type r, size_type c) const
    {
        return functor(ref(r, c));
    }
    // for multi_vector, needs enable_if since only defined for multi_vector
    template <typename S>
    typename boost::lazy_enable_if<boost::is_integral<S>, detail::map_vector<Functor, Matrix> >::type
    vector(S c) const
    {
	return typename detail::map_vector<Functor, Matrix>::type(functor, ref.vector(c));
    }

    size_type dim1() const { return ref.dim1(); }
    size_type dim2() const { return ref.dim2(); }
    dim_type dimensions() const { return ref.dimensions(); }

    size_type begin_row() const { return ref.begin_row(); }
    size_type end_row() const { return ref.end_row(); }
    size_type begin_col() const { return ref.begin_col(); }
    size_type end_col() const {	return ref.end_col(); }

    size_type nnz() const { return ref.nnz(); }

    friend size_type inline num_rows(const self& A)
    { 	using mtl::mat::num_rows; return num_rows(A.ref);     }
    friend size_type inline num_cols(const self& A)
    { 	using mtl::mat::num_cols; return num_cols(A.ref);     }
    template <typename, typename> friend struct detail::map_value;

  protected:
    boost::shared_ptr<Matrix>           my_copy;
  public:
    Functor           functor;
    other             ref;
};

template <typename Functor, typename Matrix>
inline std::size_t size(const map_view<Functor, Matrix>& A)
{     return num_rows(A) * num_rows(A); }

// ==========
// Sub matrix
// ==========

template <typename Functor, typename Matrix>
struct sub_matrix_t< mtl::mat::map_view<Functor, Matrix> >
{
    typedef mtl::mat::map_view<Functor, Matrix>                                     view_type;

    // Mapping of sub-matrix type
    typedef typename sub_matrix_t<Matrix>::const_sub_matrix_type                       ref_sub_type;
    typedef mtl::mat::map_view<Functor, ref_sub_type>                               const_sub_matrix_type;
    typedef typename view_type::size_type                                              size_type;

    const_sub_matrix_type operator()(view_type const& view, size_type begin_r, size_type end_r,
				     size_type begin_c, size_type end_c)
    {
	typedef boost::shared_ptr<ref_sub_type>                        pointer_type;

	// Submatrix of referred matrix (or view)
	// Create a submatrix, whos address will be kept by map_view
	// Functor is copied from view
	pointer_type p(new ref_sub_type(sub_matrix(view.ref, begin_r, end_r, begin_c, end_c)));
	return const_sub_matrix_type(view.functor, p);
    }
};

}} // namespace mtl::matrix

namespace mtl { namespace traits {

    namespace detail {

	template <typename Functor, typename Matrix>
	struct map_value
	{
	    typedef typename Matrix::key_type                      key_type;
	    typedef typename mtl::mat::map_view<Functor, Matrix>::value_type value_type;
	    typedef typename mtl::mat::map_view<Functor, Matrix>::other_type other_type;

	    map_value(mtl::mat::map_view<Functor, Matrix> const& map_matrix)
	      : map_matrix(map_matrix), its_value(map_matrix.ref)
	    {}

	    value_type operator() (key_type const& key) const
	    {
		return map_matrix.functor(its_value(key));
	    }

	  protected:
	    mtl::mat::map_view<Functor, Matrix> const&        map_matrix;
	    typename mtl::traits::const_value<other_type>::type its_value;
        };

	template <typename Functor, typename Matrix>
	struct mapped_type_helper
	{
	    static const bool is_id= boost::is_same<Functor, sfunctor::identity<typename Matrix::value_type> >::value;
	    typedef typename boost::mpl::if_c<
		is_id,
		Matrix,
		typename mtl::mat::map_view<Functor, Matrix>::other_type
	    >::type other_type;
	    typedef typename other_type::key_type   key_type;
	    typedef typename other_type::size_type  size_type;

	    typedef typename boost::mpl::if_c<is_id,
					      mat::banded_view<Matrix>,
					      mat::map_view<Functor, Matrix> >::type arg_type;
	};

	template <typename Functor, typename Matrix>
	struct mapped_row
	  : mapped_type_helper<Functor, Matrix>
	{
	    typedef mapped_type_helper<Functor, Matrix> base;
	    explicit mapped_row(typename base::arg_type const& view) : its_row(view.ref) {}

	    typename base::size_type operator() (typename base::key_type const& key) const
	    {	return its_row(key);	    }

	  protected:
	    typename row<typename base::other_type>::type  its_row;
        };

        template <typename Functor, typename Matrix>
        struct mapped_col
	  : mapped_type_helper<Functor, Matrix>
        {
	    typedef mapped_type_helper<Functor, Matrix> base;
	    mapped_col(typename base::arg_type const& view) : its_col(view.ref) {}

	    typename base::size_type operator() (typename base::key_type const& key) const
	    {	return its_col(key);    }

          protected:
	    typename col<typename base::other_type>::type  its_col;
        };

    } // namespace detail

    template <typename Functor, typename Matrix>
    struct row<mtl::mat::map_view<Functor, Matrix> >
    {
	typedef detail::mapped_row<Functor, Matrix>   type;
    };

    template <typename Functor, typename Matrix>
    struct col<mtl::mat::map_view<Functor, Matrix> >
    {
	typedef detail::mapped_col<Functor, Matrix>   type;
    };

    template <typename Functor, typename Matrix>
    struct const_value<mtl::mat::map_view<Functor, Matrix> >
    {
	typedef detail::map_value<Functor, Matrix>  type;
    };

    // ================
    // Range generators
    // ================

    // Use range_generator of original matrix
    template <typename Tag, typename Functor, typename Matrix>
    struct range_generator<Tag, mtl::mat::map_view<Functor, Matrix> >
	: public detail::referred_range_generator<mtl::mat::map_view<Functor, Matrix>,
	      range_generator<Tag, typename mtl::mat::map_view<Functor, Matrix>::other_type> >
    {};

    // To disambigue
    template <typename Functor, typename Matrix>
    struct range_generator<tag::major, mtl::mat::map_view<Functor, Matrix> >
	: public detail::referred_range_generator<mtl::mat::map_view<Functor, Matrix>,
	      range_generator<tag::major, typename mtl::mat::map_view<Functor, Matrix>::other_type> >
    {};

}} // mtl::traits

namespace mtl { namespace mat {

template <typename Scaling, typename Matrix>
struct scaled_view
  : public map_view<tfunctor::scale<Scaling, typename Matrix::value_type>, Matrix>
{
    typedef tfunctor::scale<Scaling, typename Matrix::value_type>  functor_type;
    typedef map_view<functor_type, Matrix>                         base;
    typedef scaled_view                                            self;

    scaled_view(const Scaling& scaling, const Matrix& matrix)
      : base(functor_type(scaling), matrix)
    {}

    scaled_view(const Scaling& scaling, boost::shared_ptr<Matrix> p)
      : base(functor_type(scaling), p)
    {}

#ifdef MTL_WITH_MOVE
    scaled_view (self&& that) : base(that) {}
    scaled_view (const self& that) : base(that) {}
#endif
};

// rscaled_view -- added by Hui Li
template <typename Matrix, typename RScaling>
struct rscaled_view
  : public map_view<tfunctor::rscale<typename Matrix::value_type,RScaling>, Matrix>
{
    typedef tfunctor::rscale<typename Matrix::value_type, RScaling>  functor_type;
    typedef map_view<functor_type, Matrix>                          base;
    typedef rscaled_view                                            self;

    rscaled_view(const Matrix& matrix, const RScaling& rscaling)
      : base(functor_type(rscaling),matrix)
    {}

    rscaled_view(boost::shared_ptr<Matrix> p, const RScaling& rscaling)
      : base(functor_type(rscaling), p)
    {}

#ifdef MTL_WITH_MOVE
    rscaled_view (self&& that) : base(that) {}
    rscaled_view (const self& that) : base(that) {}
#endif
};

// divide_by_view -- added by Hui Li
template <typename Matrix, typename Divisor>
struct divide_by_view
  : public map_view<tfunctor::divide_by<typename Matrix::value_type,Divisor>, Matrix>
{
    typedef tfunctor::divide_by<typename Matrix::value_type, Divisor>  functor_type;
    typedef map_view<functor_type, Matrix>                             base;
    typedef divide_by_view                                             self;

    divide_by_view(const Matrix& matrix,const Divisor& div)
      : base(functor_type(div), matrix)
    {}

    divide_by_view(boost::shared_ptr<Matrix> p, const Divisor& div)
      : base(functor_type(div), p)
    {}

#ifdef MTL_WITH_MOVE
    divide_by_view (self&& that) : base(that) {}
    divide_by_view (const self& that) : base(that) {}
#endif
};

template <typename Matrix>
struct conj_view
  : public map_view<mtl::sfunctor::conj<typename Matrix::value_type>, Matrix>
{
    typedef mtl::sfunctor::conj<typename Matrix::value_type>            functor_type;
    typedef map_view<functor_type, Matrix>                              base;
    typedef conj_view                                                   self;

    conj_view(const Matrix& matrix) : base(functor_type(), matrix) {}
    conj_view(boost::shared_ptr<Matrix> p) : base(functor_type(), p) {}

#ifdef MTL_WITH_MOVE
    conj_view (self&& that) : base(that) {}
    conj_view (const self& that) : base(that) {}
#endif
};

template <typename Matrix>
struct imag_view
  : public map_view<mtl::sfunctor::imag<typename Matrix::value_type>, Matrix>
{
    typedef mtl::sfunctor::imag<typename Matrix::value_type>            functor_type;
    typedef map_view<functor_type, Matrix>                              base;
    typedef imag_view                                                   self;

    imag_view(const Matrix& matrix) : base(functor_type(), matrix) {}
    imag_view(boost::shared_ptr<Matrix> p) : base(functor_type(), p) {}

#ifdef MTL_WITH_MOVE
    imag_view (self&& that) : base(that) {}
    imag_view (const self& that) : base(that) {}
#endif
};

template <typename Matrix>
struct negate_view
  : public map_view<mtl::sfunctor::negate<typename Matrix::value_type>, Matrix>
{
    typedef mtl::sfunctor::negate<typename Matrix::value_type>            functor_type;
    typedef map_view<functor_type, Matrix>                              base;
    typedef negate_view                                                   self;

    negate_view(const Matrix& matrix) : base(functor_type(), matrix) {}
    negate_view(boost::shared_ptr<Matrix> p) : base(functor_type(), p) {}

#ifdef MTL_WITH_MOVE
    negate_view (self&& that) : base(that) {}
    negate_view (const self& that) : base(that) {}
#endif
};

template <typename Matrix>
struct real_view
  : public map_view<mtl::sfunctor::real<typename Matrix::value_type>, Matrix>
{
    typedef mtl::sfunctor::real<typename Matrix::value_type>            functor_type;
    typedef map_view<functor_type, Matrix>                              base;
    typedef real_view                                                   self;

    real_view(const Matrix& matrix) : base(functor_type(), matrix) {}
    real_view(boost::shared_ptr<Matrix> p) : base(functor_type(), p) {}

#ifdef MTL_WITH_MOVE
    real_view (self&& that) : base(that) {}
    real_view (const self& that) : base(that) {}
#endif
};

template <typename Matrix>
struct exp_view
  : public map_view<mtl::sfunctor::exp<typename Matrix::value_type>, Matrix>
{
    typedef mtl::sfunctor::exp<typename Matrix::value_type>            functor_type;
    typedef map_view<functor_type, Matrix>                              base;
    typedef exp_view                                                   self;

    exp_view(const Matrix& matrix) : base(functor_type(), matrix) {}
    exp_view(boost::shared_ptr<Matrix> p) : base(functor_type(), p) {}

#ifdef MTL_WITH_MOVE
    exp_view (self&& that) : base(that) {}
    exp_view (const self& that) : base(that) {}
#endif
};

template <typename Scaling, typename Matrix>
struct sub_matrix_t< mtl::mat::scaled_view<Scaling, Matrix> >
  : public sub_matrix_t< mtl::mat::map_view<tfunctor::scale<Scaling, typename Matrix::value_type>,
					       Matrix> >
{};

template <typename Matrix>
struct sub_matrix_t< mtl::mat::conj_view<Matrix> >
  : public sub_matrix_t< mtl::mat::map_view<sfunctor::conj<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix, typename RScaling>
struct sub_matrix_t< mtl::mat::rscaled_view<Matrix, RScaling> >
  : public sub_matrix_t< mtl::mat::map_view<tfunctor::rscale<typename Matrix::value_type, RScaling>,
					       Matrix> >
{};

template <typename Matrix, typename Divisor>
struct sub_matrix_t< mtl::mat::divide_by_view<Matrix, Divisor> >
  : public sub_matrix_t< mtl::mat::map_view<tfunctor::divide_by<typename Matrix::value_type, Divisor>,
					       Matrix> >
{};

template <typename Matrix>
struct sub_matrix_t< mtl::mat::imag_view<Matrix> >
  : public sub_matrix_t< mtl::mat::map_view<mtl::sfunctor::imag<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct sub_matrix_t< mtl::mat::negate_view<Matrix> >
  : public sub_matrix_t< mtl::mat::map_view<mtl::sfunctor::negate<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct sub_matrix_t< mtl::mat::real_view<Matrix> >
  : public sub_matrix_t< mtl::mat::map_view<mtl::sfunctor::real<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct sub_matrix_t< mtl::mat::exp_view<Matrix> >
  : public sub_matrix_t< mtl::mat::map_view<mtl::sfunctor::exp<typename Matrix::value_type>, Matrix> >
{};

}} // namespace mtl::matrix

namespace mtl { namespace sfunctor {

    template <typename Matrix>
    struct conj_aux<Matrix, tag::matrix>
    {
	typedef mat::conj_view<Matrix> result_type;

	static inline result_type apply(const Matrix& matrix)
	{
	    return result_type(matrix);
	}

	result_type operator() (const Matrix& matrix) const
	{
	    return apply(matrix);
	}
    };

}} // namespace mtl::sfunctor

// Traits for specific views
namespace mtl { namespace traits {

template <typename Scaling, typename Matrix>
struct row< mtl::mat::scaled_view<Scaling, Matrix> >
  : public row< mtl::mat::map_view<tfunctor::scale<Scaling, typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix, typename RScaling>
struct row< mtl::mat::rscaled_view<Matrix, RScaling> >
  : public row< mtl::mat::map_view<tfunctor::rscale<typename Matrix::value_type, RScaling>, Matrix> >
{};

template <typename Matrix, typename Divisor>
struct row< mtl::mat::divide_by_view<Matrix, Divisor> >
  : public row< mtl::mat::map_view<tfunctor::divide_by<typename Matrix::value_type, Divisor>, Matrix> >
{};

template <typename Matrix>
struct row< mtl::mat::conj_view<Matrix> >
  : public row< mtl::mat::map_view<mtl::sfunctor::conj<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct row< mtl::mat::imag_view<Matrix> >
  : public row< mtl::mat::map_view<mtl::sfunctor::imag<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct row< mtl::mat::negate_view<Matrix> >
  : public row< mtl::mat::map_view<mtl::sfunctor::negate<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct row< mtl::mat::real_view<Matrix> >
  : public row< mtl::mat::map_view<mtl::sfunctor::real<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct row< mtl::mat::exp_view<Matrix> >
  : row< mtl::mat::map_view<mtl::sfunctor::exp<typename Matrix::value_type>, Matrix> >
{};

template <typename Scaling, typename Matrix>
struct col< mtl::mat::scaled_view<Scaling, Matrix> >
  : public col< mtl::mat::map_view<tfunctor::scale<Scaling, typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix, typename RScaling>
struct col< mtl::mat::rscaled_view<Matrix, RScaling> >
  : public col< mtl::mat::map_view<tfunctor::rscale<typename Matrix::value_type, RScaling>, Matrix> >
{};

template <typename Matrix, typename Divisor>
struct col< mtl::mat::divide_by_view<Matrix, Divisor> >
  : public col< mtl::mat::map_view<tfunctor::divide_by<typename Matrix::value_type, Divisor>, Matrix> >
{};

template <typename Matrix>
struct col< mtl::mat::conj_view<Matrix> >
  : public col< mtl::mat::map_view<sfunctor::conj<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct col< mtl::mat::imag_view<Matrix> >
  : public col< mtl::mat::map_view<mtl::sfunctor::imag<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct col< mtl::mat::negate_view<Matrix> >
  : public col< mtl::mat::map_view<mtl::sfunctor::negate<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct col< mtl::mat::real_view<Matrix> >
  : public col< mtl::mat::map_view<mtl::sfunctor::real<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct col< mtl::mat::exp_view<Matrix> >
  : col< mtl::mat::map_view<mtl::sfunctor::exp<typename Matrix::value_type>, Matrix> >
{};

template <typename Scaling, typename Matrix>
struct const_value< mtl::mat::scaled_view<Scaling, Matrix> >
  : public const_value< mtl::mat::map_view<tfunctor::scale<Scaling, typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix, typename RScaling>
struct const_value< mtl::mat::rscaled_view<Matrix, RScaling> >
  : public const_value< mtl::mat::map_view<tfunctor::rscale<typename Matrix::value_type, RScaling>, Matrix> >
{};

template <typename Matrix, typename Divisor>
struct const_value< mtl::mat::divide_by_view<Matrix, Divisor> >
  : public const_value< mtl::mat::map_view<tfunctor::divide_by<typename Matrix::value_type, Divisor>, Matrix> >
{};

template <typename Matrix>
struct const_value< mtl::mat::conj_view<Matrix> >
  : public const_value< mtl::mat::map_view<sfunctor::conj<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct const_value< mtl::mat::imag_view<Matrix> >
  : public const_value< mtl::mat::map_view<mtl::sfunctor::imag<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct const_value< mtl::mat::negate_view<Matrix> >
  : public const_value< mtl::mat::map_view<mtl::sfunctor::negate<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct const_value< mtl::mat::real_view<Matrix> >
  : public const_value< mtl::mat::map_view<mtl::sfunctor::real<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct const_value< mtl::mat::exp_view<Matrix> >
  : public const_value< mtl::mat::map_view<mtl::sfunctor::exp<typename Matrix::value_type>, Matrix> >
{};

template <typename Tag, typename Scaling, typename Matrix>
struct range_generator< Tag, mtl::mat::scaled_view<Scaling, Matrix> >
    : public range_generator< Tag, mtl::mat::map_view<tfunctor::scale<Scaling, typename Matrix::value_type>,
						    Matrix> >
{};

template <typename Tag, typename Matrix>
struct range_generator< Tag, mtl::mat::conj_view<Matrix> >
    : public range_generator< Tag, mtl::mat::map_view<sfunctor::conj<typename Matrix::value_type>, Matrix> >
{};

template <typename Tag, typename Matrix, typename RScaling>
struct range_generator< Tag, mtl::mat::rscaled_view<Matrix, RScaling> >
    : public range_generator< Tag, mtl::mat::map_view<tfunctor::rscale<typename Matrix::value_type, RScaling>,
						    Matrix> >
{};

template <typename Tag, typename Matrix, typename Divisor>
struct range_generator< Tag, mtl::mat::divide_by_view<Matrix, Divisor> >
    : public range_generator< Tag, mtl::mat::map_view<tfunctor::divide_by<typename Matrix::value_type, Divisor>,
						    Matrix> >
{};

template <typename Tag, typename Matrix>
struct range_generator< Tag, mtl::mat::imag_view<Matrix> >
    : public range_generator< Tag, mtl::mat::map_view<sfunctor::imag<typename Matrix::value_type>, Matrix> >
{};

template <typename Tag, typename Matrix>
struct range_generator< Tag, mtl::mat::negate_view<Matrix> >
    : public range_generator< Tag, mtl::mat::map_view<sfunctor::negate<typename Matrix::value_type>, Matrix> >
{};

template <typename Tag, typename Matrix>
struct range_generator< Tag, mtl::mat::real_view<Matrix> >
    : public range_generator< Tag, mtl::mat::map_view<sfunctor::real<typename Matrix::value_type>, Matrix> >
{};

template <typename Tag, typename Matrix>
struct range_generator< Tag, mtl::mat::exp_view<Matrix> >
  : range_generator< Tag, mtl::mat::map_view<mtl::sfunctor::exp<typename Matrix::value_type>, Matrix> >
{};

// Disambiguation

template <typename Scaling, typename Matrix>
struct range_generator< tag::major, mtl::mat::scaled_view<Scaling, Matrix> >
    : public range_generator< tag::major, mtl::mat::map_view<tfunctor::scale<Scaling, typename Matrix::value_type>,
						    Matrix> >
{};

template <typename Matrix>
struct range_generator< tag::major, mtl::mat::conj_view<Matrix> >
    : public range_generator< tag::major, mtl::mat::map_view<sfunctor::conj<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix, typename RScaling>
struct range_generator< tag::major, mtl::mat::rscaled_view<Matrix, RScaling> >
    : public range_generator< tag::major, mtl::mat::map_view<tfunctor::rscale<typename Matrix::value_type, RScaling>,
						    Matrix> >
{};

template <typename Matrix, typename Divisor>
struct range_generator< tag::major, mtl::mat::divide_by_view<Matrix, Divisor> >
    : public range_generator< tag::major, mtl::mat::map_view<tfunctor::divide_by<typename Matrix::value_type, Divisor>,
						    Matrix> >
{};

template <typename Matrix>
struct range_generator< tag::major, mtl::mat::imag_view<Matrix> >
    : public range_generator< tag::major, mtl::mat::map_view<sfunctor::imag<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct range_generator< tag::major, mtl::mat::negate_view<Matrix> >
    : public range_generator< tag::major, mtl::mat::map_view<sfunctor::negate<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct range_generator< tag::major, mtl::mat::real_view<Matrix> >
    : public range_generator< tag::major, mtl::mat::map_view<sfunctor::real<typename Matrix::value_type>, Matrix> >
{};

template <typename Matrix>
struct range_generator< tag::major, mtl::mat::exp_view<Matrix> >
  : range_generator< tag::major, mtl::mat::map_view<mtl::sfunctor::exp<typename Matrix::value_type>, Matrix> >
{};

}} // mtl::traits

#endif // MTL_MAP_VIEW_INCLUDE


// 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.

#ifndef bempp_identity_operator_hpp
#define bempp_identity_operator_hpp

#include "../common/common.hpp"

#include "elementary_abstract_boundary_operator.hpp"

#include "abstract_boundary_operator_id.hpp"
#include <boost/scoped_ptr.hpp>

namespace Fiber
{

/** \cond FORWARD_DECL */
template <typename ResultType> class LocalAssemblerForOperators;
/** \endcond */

} // namespace Fiber

namespace Bempp
{

/** \cond FORWARD_DECL */
template <typename BasisFunctionType, typename ResultType> class BoundaryOperator;
template <typename BasisFunctionType, typename ResultType> class IdentityOperator;
/** \endcond */

template <typename BasisFunctionType, typename ResultType>
class BEMPP_DEPRECATED IdentityOperatorId : public AbstractBoundaryOperatorId
{
public:
    IdentityOperatorId(const IdentityOperator<BasisFunctionType, ResultType>& op);
    virtual size_t hash() const;
    virtual bool isEqual(const AbstractBoundaryOperatorId &other) const;

private:
    const Space<BasisFunctionType>* m_domain;
    const Space<BasisFunctionType>* m_range;
    const Space<BasisFunctionType>* m_dualToRange;
};

/** \ingroup identity
 *  \brief Identity operator.
 *
 *  Let \f$X\f$ and \f$Y\f$ be two function spaces defined on the same grid. If
 *  \f$X \supset Y\f$, an instance of IdentityOperator with domain \f$X\f$
 *  and range \f$Y\f$ represents the orthogonal projection operator from
 *  \f$X\f$ to \f$Y\f$. If \f$X \subset Y\f$, it represents the inclusion
 *  operator from \f$X\f$ to \f$Y\f$. In the special case of \f$X = Y\f$, we
 *  have the standard identity operator. In BEM++ we (ab)use the term "identity
 *  operator" to refer to all these three cases.
 *
 *  See AbstractBoundaryOperator for the documentation of the template
 *  parameters.
 *
 *  Use identityOperator() to create a BoundaryOperator object wrapping
 *  an identity operator.
 */
template <typename BasisFunctionType_, typename ResultType_>
class IdentityOperator :
        public ElementaryAbstractBoundaryOperator<BasisFunctionType_, ResultType_>
{
    typedef ElementaryAbstractBoundaryOperator<BasisFunctionType_, ResultType_> Base;
public:
    /** \copydoc ElementaryAbstractBoundaryOperator::BasisFunctionType */
    typedef typename Base::BasisFunctionType BasisFunctionType;
    /** \copydoc ElementaryAbstractBoundaryOperator::ResultType */
    typedef typename Base::ResultType ResultType;
    /** \copydoc ElementaryAbstractBoundaryOperator::CoordinateType */
    typedef typename Base::CoordinateType CoordinateType;
    /** \copydoc ElementaryAbstractBoundaryOperator::QuadratureStrategy */
    typedef typename Base::QuadratureStrategy QuadratureStrategy;
    /** \copydoc ElementaryAbstractBoundaryOperator::LocalAssembler */
    typedef typename Base::LocalAssembler LocalAssembler;

    /** \brief Constructor.
     *
     *  \param[in] domain
     *    Function space being the domain of the operator.
     *  \param[in] range
     *    Function space being the range of the operator.
     *  \param[in] dualToRange
     *    Function space dual to the the range of the operator.
     *  \param[in] label
     *    Textual label of the operator. If empty, a unique label is generated
     *    automatically.
     *  \param[in] symmetry
     *    Symmetry of the weak form of the operator. Can be any combination of
     *    the flags defined in the enumeration type Symmetry.
     *    If set to AUTO_SYMMETRY (default), the symmetry is determined
     *    automatically by checking whether its domain and space dual to its
     *    range are equal. If so, the operator is marked as Hermitian,
     *    and if the basis functions are real-valued, also as symmetric.
     *
     *  All the three spaces must be defined on the same grid. */
    IdentityOperator(const shared_ptr<const Space<BasisFunctionType> >& domain,
                     const shared_ptr<const Space<BasisFunctionType> >& range,
                     const shared_ptr<const Space<BasisFunctionType> >& dualToRange,
                     const std::string& label = "",
                     int symmetry = AUTO_SYMMETRY);
    IdentityOperator(const IdentityOperator& other);
    virtual ~IdentityOperator();
    IdentityOperator& operator=(const IdentityOperator& rhs);

    /** \brief Return the identifier of this operator.
     *
     *  Identity operators are treated as equivalent if they have the same domain,
     *  range and dual to range.
     *
     *  \deprecated This function is deprecated and will be removed in a future
     *  version of BEM++.
     */
    BEMPP_DEPRECATED virtual shared_ptr<const AbstractBoundaryOperatorId> id() const;

    /** \brief Return true. */
    virtual bool isLocal() const;

protected:
    virtual shared_ptr<DiscreteBoundaryOperator<ResultType_> >
    assembleWeakFormImpl(
            const Context<BasisFunctionType, ResultType>& context) const;

private:
    virtual std::auto_ptr<LocalAssembler> makeAssemblerImpl(
            const QuadratureStrategy& quadStrategy,
            const shared_ptr<const GeometryFactory>& testGeometryFactory,
            const shared_ptr<const GeometryFactory>& trialGeometryFactory,
            const shared_ptr<const Fiber::RawGridGeometry<CoordinateType> >& testRawGeometry,
            const shared_ptr<const Fiber::RawGridGeometry<CoordinateType> >& trialRawGeometry,
            const shared_ptr<const std::vector<const Fiber::Basis<BasisFunctionType>*> >& testBases,
            const shared_ptr<const std::vector<const Fiber::Basis<BasisFunctionType>*> >& trialBases,
            const shared_ptr<const Fiber::OpenClHandler>& openClHandler,
            const ParallelizationOptions& parallelizationOptions,
            VerbosityLevel::Level verbosityLevel,
            bool cacheSingularIntegrals) const;

    virtual shared_ptr<DiscreteBoundaryOperator<ResultType_> >
    assembleWeakFormInternalImpl(
            LocalAssembler& assembler,
            const AssemblyOptions& options) const;

    std::auto_ptr<DiscreteBoundaryOperator<ResultType_> >
    assembleWeakFormInDenseMode(
            LocalAssembler& assembler,
            const AssemblyOptions& options) const;

    std::auto_ptr<DiscreteBoundaryOperator<ResultType_> >
    assembleWeakFormInSparseMode(
            LocalAssembler& assembler,
            const AssemblyOptions& options) const;

private:
    struct Impl;
    boost::scoped_ptr<Impl> m_impl;
    shared_ptr<const AbstractBoundaryOperatorId> m_id;
};

/** \relates IdentityOperator
 *  \brief Construct a BoundaryOperator object wrapping an IdentityOperator.
 *
 *  This convenience function constructs an abstract identity operator and wraps
 *  it in a BoundaryOperator object.
 *
 *  \param[in] context
 *    A Context object that will be used to build the weak form of the
 *    identity operator when necessary.
 *  \param[in] domain
 *    Function space being the domain of the identity operator.
 *  \param[in] range
 *    Function space being the range of the identity operator.
 *  \param[in] dualToRange
 *    Function space dual to the the range of the identity operator.
 *  \param[in] label
 *    Textual label of the identity operator (optional, used for debugging).
 *  \param[in] symmetry
 *    Symmetry of the weak form of the operator. Can be any combination of
 *    the flags defined in the enumeration type Symmetry.
 *    If set to AUTO_SYMMETRY (default), the symmetry is determined
 *    automatically by checking whether its domain and space dual to its
 *    range are equal. If so, the operator is marked as Hermitian,
 *    and if the basis functions are real-valued, also as symmetric.
 *
 *  All the three spaces must be defined on the same grid. */
template <typename BasisFunctionType, typename ResultType>
BoundaryOperator<BasisFunctionType, ResultType>
identityOperator(const shared_ptr<const Context<BasisFunctionType, ResultType> >& context,
                 const shared_ptr<const Space<BasisFunctionType> >& domain,
                 const shared_ptr<const Space<BasisFunctionType> >& range,
                 const shared_ptr<const Space<BasisFunctionType> >& dualToRange,
                 const std::string& label = "",
                 int symmetry = AUTO_SYMMETRY);

} // namespace Bempp

#endif


/*
 * Copyright 2009-2011 The VOTCA Development Team (http://www.votca.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include <iostream>
#include <fstream>
#include <iomanip>
#include <boost/lexical_cast.hpp>
#include <votca/tools/rangeparser.h>
#include <votca/csg/beadlist.h>
#include <votca/csg/nblistgrid.h>
#include "csg_stat_imc.h"
#include <votca/csg/imcio.h>

namespace votca { namespace csg {

Imc::Imc()
   : _block_length(0), _do_imc(false), _processed_some_frames(false)
{
}

Imc::~Imc()
{
}

// begin the coarse graining process
// here the data structures are prepared to handle all the data
void Imc::Initialize()
{
    // do some output
    if(_do_imc)
	    cout << "begin to calculate inverse monte carlo parameters\n";
    else
	    cout << "begin to calculate distribution functions\n";
    cout << "# of bonded interactions: " << _bonded.size() << endl;
    cout << "# of non-bonded interactions: " << _nonbonded.size() << endl;

    if ( _bonded.size()+_nonbonded.size() == 0 )
            throw std::runtime_error("No interactions defined in options xml-file - nothing to be done");

   // initialize non-bonded structures
   for (list<Property*>::iterator iter = _nonbonded.begin();
            iter != _nonbonded.end(); ++iter) {
        interaction_t *i = AddInteraction(*iter);
        i->_is_bonded = false;
    }

    // initialize non-bonded structures
   for (list<Property*>::iterator iter = _bonded.begin();
        iter != _bonded.end(); ++iter) {
            interaction_t *i = AddInteraction(*iter);
            i->_is_bonded = true;
   }

   // initialize the group structures
    if(_do_imc)
        InitializeGroups();
};

void Imc::BeginEvaluate(Topology *top, Topology *top_atom)
{
  // we didn't process any frames so far
    _nframes = 0;
    _nblock = 0;
    _processed_some_frames=false;

    // initialize non-bonded structures
   for (list<Property*>::iterator iter = _nonbonded.begin();
            iter != _nonbonded.end(); ++iter) {
        string name = (*iter)->get("name").value();

        interaction_t &i = *_interactions[name];

        // generate the bead lists
        BeadList beads1, beads2;

        beads1.Generate(*top, (*iter)->get("type1").value());
        beads2.Generate(*top, (*iter)->get("type2").value());

        if(beads1.size() == 0)
            throw std::runtime_error("Topology does not have beads of type \"" + (*iter)->get("type1").value() + "\"\n"
                    "This was specified in type1 of interaction \"" + name+ "\"");
        if(beads2.size() == 0)
            throw std::runtime_error("Topology does not have beads of type \"" + (*iter)->get("type2").value() + "\"\n"
                    "This was specified in type2 of interaction \"" + name + "\"");
        // calculate normalization factor for rdf

        if ((*iter)->get("type1").value() == (*iter)->get("type2").value())
            i._norm = 1. / (beads1.size()*(beads2.size()) / 2.);
        else
            i._norm = 1. / (beads1.size() * beads2.size());
    }

    for (list<Property*>::iterator iter = _bonded.begin();
            iter != _bonded.end(); ++iter) {
        string name = (*iter)->get("name").value();

        std::list<Interaction *> list = top->InteractionsInGroup(name);
        if (list.empty() )
            throw std::runtime_error("Bonded interaction '" + name + "' defined in options xml-file, but not in topology - check name definition in the mapping file again");
    }
}

// create an entry for interactions
Imc::interaction_t *Imc::AddInteraction(Property *p)
{
    string name = p->get("name").value();
    string group;
    if(_do_imc)
        group = p->get("inverse.imc.group").value();
    else
        group = "none";

    interaction_t *i = new interaction_t;
    i->_index = _interactions.size();
    _interactions[name] = i;
    getGroup(group)->_interactions.push_back(i);

    i->_step = p->get("step").as<double>();
    i->_min = p->get("min").as<double>();
    i->_max = p->get("max").as<double>();
    i->_norm = 1.0;
    i->_p=p;

    // initialize the current and average histogram
    int n = (int)((i->_max - i->_min) / i->_step + 1.000000001);

    i->_average.Initialize(i->_min, i->_max+i->_step, n);

    return i;
}

// end of trajectory, post processing data
void Imc::EndEvaluate()
{
    if(_nframes > 0) {
        if(_block_length == 0) {
	    string suffix = string(".") + _extension;
            WriteDist(suffix);
            if(_do_imc)
                WriteIMCData();
        }
    }
    // clear interactions and groups
    _interactions.clear();
    _groups.clear();
    if(!_processed_some_frames)
        throw std::runtime_error("no frames were processed. Please check your input");
}

// load options from xml file
void Imc::LoadOptions(const string &file) {
    load_property_from_xml(_options, file);
    _bonded = _options.Select("cg.bonded");
    _nonbonded = _options.Select("cg.non-bonded");

}

// evaluate current conformation
void Imc::Worker::EvalConfiguration(Topology *top, Topology *top_atom) {

    _cur_vol = top->BoxVolume();
    // process non-bonded interactions
    DoNonbonded(top);
    // process bonded interactions
    DoBonded(top);
}

void Imc::ClearAverages()
{
    map<string, interaction_t *>::iterator ic_iter;
    map<string, group_t *>::iterator group_iter;

    _nframes = 0;
    for (ic_iter = _interactions.begin(); ic_iter != _interactions.end(); ++ic_iter)
        ic_iter->second->_average.Clear();

    for (group_iter = _groups.begin(); group_iter != _groups.end(); ++group_iter)
        group_iter->second->_corr.clear();
}

class IMCNBSearchHandler {
public:
    IMCNBSearchHandler(HistogramNew *hist)
            : _hist(hist) {}

    HistogramNew *_hist;

    bool FoundPair(Bead *b1, Bead *b2, const vec &r, const double dist) {
        _hist->Process(dist);
        return false;
    }
};

// process non-bonded interactions for current frame
void Imc::Worker::DoNonbonded(Topology *top)
{
    for (list<Property*>::iterator iter = _imc->_nonbonded.begin();
            iter != _imc->_nonbonded.end(); ++iter) {
        string name = (*iter)->get("name").value();

        interaction_t &i = *_imc->_interactions[name];

        // generate the bead lists
        BeadList beads1, beads2;

        beads1.Generate(*top, (*iter)->get("type1").value());
        beads2.Generate(*top, (*iter)->get("type2").value());

        // generate the neighbour list
        NBList *nb;

        bool gridsearch=true;

        if(_imc->_options.exists("cg.nbsearch")) {
            if(_imc->_options.get("cg.nbsearch").as<string>() == "grid")
                gridsearch=true;
            else if(_imc->_options.get("cg.nbsearch").as<string>() == "simple")
                gridsearch=false;
            else throw std::runtime_error("cg.nbsearch invalid, can be grid or simple");
        }
        if(gridsearch)
            nb = new NBListGrid();
        else
            nb = new NBList();

        nb->setCutoff(i._max + i._step);

        // clear the current histogram
        _current_hists[i._index].Clear();

        IMCNBSearchHandler h(&(_current_hists[i._index]));

        nb->SetMatchFunction(&h, &IMCNBSearchHandler::FoundPair);

        // is it same types or different types?
        if((*iter)->get("type1").value() == (*iter)->get("type2").value())
            nb->Generate(beads1);
        else
            nb->Generate(beads1, beads2);

        // process all pairs
        /*NBList::iterator pair_iter;
        for(pair_iter = nb->begin(); pair_iter!=nb->end();++pair_iter) {
                _current_hists[i._index].Process((*pair_iter)->dist());
        }*/

        delete nb;
    }
}

// process non-bonded interactions for current frame
void Imc::Worker::DoBonded(Topology *top)
{
    for (list<Property*>::iterator iter = _imc->_bonded.begin();
            iter != _imc->_bonded.end(); ++iter) {
        string name = (*iter)->get("name").value();

        interaction_t &i = *_imc->_interactions[name];

        // clear the current histogram
        _current_hists[i._index].Clear();

        // now fill with new data
        std::list<Interaction *> list = top->InteractionsInGroup(name);

        std::list<Interaction *>::iterator ic_iter;
        for(ic_iter=list.begin(); ic_iter!=list.end(); ++ic_iter) {
            Interaction *ic = *ic_iter;
            double v = ic->EvaluateVar(*top);
            _current_hists[i._index].Process(v);
        }
    }
}

// returns a group, creates it if doesn't exist
Imc::group_t *Imc::getGroup(const string &name)
{
    map<string, group_t *>::iterator iter;
    iter = _groups.find(name);
    if(iter == _groups.end()) {
        return _groups[name] = new group_t;
    }
    return (*iter).second;
}

// initialize the groups after interactions are added
void Imc::InitializeGroups()
{
    if(!_do_imc) return;
    map<string, group_t *>::iterator group_iter;

    // clear all the pairs

    // iterator over all groups
    for (group_iter = _groups.begin(); group_iter != _groups.end(); ++group_iter) {
        group_t *grp = (*group_iter).second;
        grp->_pairs.clear();

        int n = 0;
        // count number of bins needed in matrix
        for (list<interaction_t*>::iterator i1 = grp->_interactions.begin();
                i1 != grp->_interactions.end(); ++i1)
            n+=(*i1)->_average.getNBins();

        // handy access to matrix
        group_matrix &M = grp->_corr;

        // initialize matrix with zeroes
        M.resize(n,n);
        M = ub::zero_matrix<double>(n, n);

        // now create references to the sub matrices
        int i, j;
        i=0;j=0;
        // iterate over all possible compinations of pairs
        for (list<interaction_t*>::iterator i1 = grp->_interactions.begin();
                i1 != grp->_interactions.end(); ++i1) {
            int n1 = (*i1)->_average.getNBins();
            j = i;
            for (list<interaction_t*>::iterator i2 = i1;
                    i2 != grp->_interactions.end(); ++i2) {
                int n2 = (*i2)->_average.getNBins();

                // create matrix proxy with sub-matrix
                pair_matrix corr(M, ub::range(i, i+n1), ub::range(j, j+n2));
                // add the pair
                grp->_pairs.push_back(pair_t(*i1, *i2, i, j, corr));
                j+=n2;
            }
            i+=n1;
        }
    }
}

// update the correlation matrix
void Imc::DoCorrelations(Imc::Worker *worker) {
    if(!_do_imc) return;
    vector<pair_t>::iterator pair;
    map<string, group_t *>::iterator group_iter;

     for (group_iter = _groups.begin(); group_iter != _groups.end(); ++group_iter) {
        group_t *grp = (*group_iter).second;
        // update correlation for all pairs
        for (pair = grp->_pairs.begin(); pair != grp->_pairs.end(); ++pair) {
            ub::vector<double> &a = worker->_current_hists[pair->_i1->_index].data().y();
            ub::vector<double> &b = worker->_current_hists[pair->_i2->_index].data().y();
            pair_matrix &M = pair->_corr;

            // M_ij += a_i*b_j
            //for(int i=0; i<M.size1(); ++i)
            //    for(int j=i; j<M.size2(); ++j)
            //        M(i,j) = ((((double)_nframes-1.0)*M(i,j)) + (a(i)*b(j)))/(double)_nframes;
            M = ((((double)_nframes-1.0)*M) + ub::outer_prod(a, b))/(double)_nframes;
        }
    }
}

// write the distribution function
void Imc::WriteDist(const string &suffix)
{
    map<string, interaction_t *>::iterator iter;

    // for all interactions
    for (iter = _interactions.begin(); iter != _interactions.end(); ++iter) {
        // calculate the rdf
        Table &t = iter->second->_average.data();
        Table dist(t);
        if(!iter->second->_is_bonded) {
            // normalization is calculated using exact shell volume (difference of spheres)
            for(unsigned int i=0; i<dist.y().size(); ++i) {
                double x1 = dist.x()[i] - 0.5*iter->second->_step;
                double x2 = x1 + iter->second->_step;
                if(x1<0) {
                    dist.y()[i]=0;
                }
                else {
                    dist.y()[i] = _avg_vol.getAvg()*iter->second->_norm *
                        dist.y()[i]/(4./3.*M_PI*(x2*x2*x2 - x1*x1*x1));
                }
            }
        }
        else {
	    // \TODO normalize bond and angle differently....
            double norm=ub::norm_1(dist.y());
            if ( norm > 0 ) {
              dist.y() = iter->second->_norm * dist.y() / ( norm * iter->second->_step );
            }
        }

        dist.Save((iter->first) + suffix);
        cout << "written " << (iter->first) + suffix << "\n";
    }
}

/**
 *  Here the inverse monte carlo matrix is calculated and written out
 *
 *  steps:
 *      - calculate th
 */
void Imc::WriteIMCData(const string &suffix) {
    if(!_do_imc) return;
    //map<string, interaction_t *>::iterator ic_iter;
    map<string, group_t *>::iterator group_iter;

    // iterate over all groups
    for(group_iter = _groups.begin(); group_iter!=_groups.end(); ++group_iter) {
        group_t *grp = (*group_iter).second;
        string grp_name = (*group_iter).first;
        list<interaction_t *>::iterator iter;

        // number of total bins for all interactions in group is matrix dimension
        int n=grp->_corr.size1();

        // build full set of equations + copy some data to make
        // code better to read
        group_matrix gmc(grp->_corr);
        ub::vector<double> dS(n);
        ub::vector<double> r(n);
        // the next two variables are to later extract the individual parts
        // from the whole data after solving equations
        vector<RangeParser> ranges; // sizes of the individual interactions
        vector<string> names; // names of the interactions

        // copy all averages+r of group to one vector
        n=0;
        int begin=1;
        for(iter=grp->_interactions.begin(); iter != grp->_interactions.end(); ++iter) {
            interaction_t *ic = *iter;

            // sub vector for dS
            ub::vector_range< ub::vector<double> > sub_dS(dS,
                    ub::range(n, n + ic->_average.getNBins()));

            // sub vector for r
            ub::vector_range< ub::vector<double> > sub_r(r,
                    ub::range(n, n + ic->_average.getNBins()));

            // read in target and calculate dS
            CalcDeltaS(ic, sub_dS);

            // copy r
            sub_r = ic->_average.data().x();

            // save size

            RangeParser rp;
            int end = begin  + ic->_average.getNBins() -1;
            rp.Add(begin, end);
            ranges.push_back(rp);
            begin = end+1;
            // save name
            names.push_back(ic->_p->get("name").as<string>());

            // shift subrange by size of current
            n+=ic->_average.getNBins();
        }

        // now we need to calculate the
        // A_ij = <S_i*S_j> - <S_i>*<S_j>
        vector<pair_t>::iterator pair;
        for (pair = grp->_pairs.begin(); pair != grp->_pairs.end(); ++pair) {
            interaction_t *i1 = pair->_i1;
            interaction_t *i2 = pair->_i2;

            // make reference to <S_i>
            ub::vector<double> &a = i1->_average.data().y();
            // make reference to <S_j>
            ub::vector<double> &b = i2->_average.data().y();

            int i=pair->_offset_i;
            int j=pair->_offset_j;
            int n1=i1->_average.getNBins();
            int n2=i2->_average.getNBins();

            // sub matrix for these two interactions
            // we only need to take care about one sub-matrix and not the mirrored
            // one since ublas makes sure the matrix is symmetric
            pair_matrix M(gmc, ub::range(i, i+n1),
                               ub::range(j, j+n2));
            // A_ij = -(<a_i*a_j>  - <a_i>*<b_j>)
            //for(i=0; i<M.size1(); ++i)
            //    for(j=i; j<M.size2(); ++j)
            //        M(i,j) = -(M(i,j) - a(i)*b(j));
            M = -(M - ub::outer_prod(a, b));
        }

        imcio_write_dS(grp_name + suffix + ".imc", r, dS);
        imcio_write_matrix(grp_name + suffix + ".gmc", gmc);
        imcio_write_index(grp_name + suffix + ".idx", names, ranges);

    }
}

// calculate deviation from target vectors
void Imc::CalcDeltaS(interaction_t *interaction, ub::vector_range< ub::vector<double> > &dS)
{
    const string &name = interaction->_p->get("name").as<string>();

    Table target;
    target.Load(name + ".dist.tgt");

    if(!interaction->_is_bonded) {
        for(unsigned int i=0; i<target.y().size(); ++i) {
            double x1 = target.x()[i] - 0.5*interaction->_step;
            double x2 = x1 + interaction->_step;
            if(x1<0)
                x1=x2=0;
            target.y()[i] = 1./(_avg_vol.getAvg()*interaction->_norm) *
                target.y()[i] * (4./3.*M_PI*(x2*x2*x2 - x1*x1*x1));
        }
    }
    else {
            target.y() = (1.0 / interaction->_norm)*target.y();
    }
    if(target.y().size() !=  interaction->_average.data().y().size())
        throw std::runtime_error("number of grid points in target does not match the grid");

    dS = interaction->_average.data().y() - target.y();
}

void Imc::WriteIMCBlock(const string &suffix)
{
    if(!_do_imc) return;
    //map<string, interaction_t *>::iterator ic_iter;
    map<string, group_t *>::iterator group_iter;

    // iterate over all groups
    for(group_iter = _groups.begin(); group_iter!=_groups.end(); ++group_iter) {
        group_t *grp = (*group_iter).second;
        string grp_name = (*group_iter).first;
        list<interaction_t *>::iterator iter;

        // number of total bins for all interactions in group is matrix dimension
        int n=grp->_corr.size1();

        // build full set of equations + copy some data to make
        // code better to read
        group_matrix gmc(grp->_corr);
        ub::vector<double> dS(n);
        ub::vector<double> r(n);
        // the next two variables are to later extract the individual parts
        // from the whole data after solving equations
        vector<int> sizes; // sizes of the individual interactions
        vector<string> names; // names of the interactions

        // copy all averages+r of group to one vector
        n=0;
        for(iter=grp->_interactions.begin(); iter != grp->_interactions.end(); ++iter) {
            interaction_t *ic = *iter;

            // sub vector for dS
            ub::vector_range< ub::vector<double> > sub_dS(dS,
                    ub::range(n, n + ic->_average.getNBins()));

            // sub vector for r
            ub::vector_range< ub::vector<double> > sub_r(r,
                    ub::range(n, n + ic->_average.getNBins()));

            // read in target and calculate dS
            sub_dS = ic->_average.data().y();
            // copy r
            sub_r = ic->_average.data().x();
            // save size
            sizes.push_back(ic->_average.getNBins());
            // save name
            names.push_back(ic->_p->get("name").as<string>());

            // shift subrange by size of current
            n+=ic->_average.getNBins();
        }

        // write the dS
        ofstream out_dS;
        string name_dS = grp_name + suffix + ".S";
        out_dS.open(name_dS.c_str());
        out_dS << setprecision(8);
        if(!out_dS)
            throw runtime_error(string("error, cannot open file ") + name_dS);

        for(size_t i=0; i<dS.size(); ++i) {
            out_dS << r[i] << " " << dS[i] << endl;
        }

        out_dS.close();
        cout << "written " << name_dS << endl;

        // write the correlations
        ofstream out_cor;
        string name_cor = grp_name + suffix + ".cor";
        out_cor.open(name_cor.c_str());
        out_cor << setprecision(8);

        if(!out_cor)
            throw runtime_error(string("error, cannot open file ") + name_cor);

        for(group_matrix::size_type i=0; i<grp->_corr.size1(); ++i) {
            for(group_matrix::size_type j=0; j<grp->_corr.size2(); ++j) {
                out_cor << grp->_corr(i, j) << " ";
            }
            out_cor << endl;
        }
        out_cor.close();
        cout << "written " << name_cor << endl;
    }
}

CsgApplication::Worker *Imc::ForkWorker()
{
    Imc::Worker *worker;
    worker = new Imc::Worker;
    map<string, interaction_t *>::iterator ic_iter;

    worker->_current_hists.resize(_interactions.size());
    worker->_imc = this;

    for (ic_iter = _interactions.begin(); ic_iter != _interactions.end(); ++ic_iter) {
        interaction_t *i = ic_iter->second;
        worker->_current_hists[i->_index].Initialize(
        i->_average.getMin(),
        i->_average.getMax(),
        i->_average.getNBins());
    }
    return worker;
}

void Imc::MergeWorker(CsgApplication::Worker* worker_)
{
    _processed_some_frames = true;
    Imc::Worker *worker = dynamic_cast<Imc::Worker *>(worker_);
        // update the average
    map<string, interaction_t *>::iterator ic_iter;
    //map<string, group_t *>::iterator group_iter;

    ++_nframes;
    _avg_vol.Process(worker->_cur_vol);
    for (ic_iter = _interactions.begin(); ic_iter != _interactions.end(); ++ic_iter) {
        interaction_t *i=ic_iter->second;
        i->_average.data().y() = (((double)_nframes-1.0)*i->_average.data().y()
            + worker->_current_hists[i->_index].data().y())/(double)_nframes;
    }

        // update correlation matrices
    if(_do_imc)
        DoCorrelations(worker);

    if(_block_length != 0) {
        if((_nframes % _block_length)==0) {
            _nblock++;
            string suffix = string("_") + boost::lexical_cast<string>(_nblock) + string(".") + _extension;
            WriteDist(suffix);
            WriteIMCData(suffix);
            WriteIMCBlock(suffix);
            ClearAverages();
        }
    }

}

}}


#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;

#include <fmt/format.h>
#include <fmt/format.cc>
#include <fmt/string.h>
#include <fmt/ostream.h>

#include <vector>
#include <cmath>
#include <random>
#include <math.h>

#include <Eigen/Dense>
using namespace Eigen;

// --------------------------------------------------
// #include "definitions.h"
const double pi = M_PI;
typedef std::array<double, 3> vec;

using std::sin;
using std::cos;
using std::exp;
using std::acos;
using std::asin;
using std::pow;
using std::sqrt;
using std::log;

// --------------------------------------------------
namespace el_ph {

    class photon {

        public:
            std::array<double, 8> data;
            photon( double, double, double, double, double, double, double, double );

            /// photon four-velocity components
            const double hv(){ return data[0]; };
            const double vx(){ return data[1]; };
            const double vy(){ return data[2]; };
            const double vz(){ return data[3]; };
            const double x(){ return data[4]; };
            const double y(){ return data[5]; };
            const double z(){ return data[6]; };
            const double w(){ return data[7]; };

            /// Location
            // const double x(){ return data[4]; };
            // const double y(){ return data[5]; };
            // const double z(){ return data[6]; };

    };

    photon::photon(double E, double vx, double vy, double vz, double x, double y, double z, double w) {
        this->data = {{E, vx, vy, vz, x, y, z, w}};
    };

    // --------------------------------------------------
    class photonBucket {

        /// size of the bucket (in terms of photons)
        size_t nPhotons = 0;

        /// Photon container
        std::vector<std::array<double, 8>> bucket;

        public:
            void push_back( photon ph );

            //const size_t size( ) { return this->nPhotons; };
            const size_t size( ) { return bucket.size(); };

            void replace( const size_t indx, photon ph );

            photon get( const size_t indx );

            std::array<double, 8> get_data( const size_t indx );

            void resize(const size_t N);

            void swap(const size_t i1, const size_t i2);

            std::vector<double> vecE();
            std::vector<double> vecZ();
    };

    /// Append photon into bucket
    void photonBucket::push_back( photon ph ) {
        bucket.push_back( ph.data );
        nPhotons++;
    };

    /// Replace i:th photon with the given one
    void photonBucket::replace( const size_t indx, photon ph ) {
        bucket[indx] = ph.data;
    };

    /// Swap i1 and i2 photons in the bucket
    void photonBucket::swap(const size_t i1, const size_t i2) {
        std::swap( bucket[i1], bucket[i2] );
    };

    /// return i:th photon data
    photon photonBucket::get( const size_t indx ) {
        std::array<double, 8> data = bucket[indx];
        photon ph(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);

        return ph;
    };

    /// return i:th photon raw data
    std::array<double, 8> photonBucket::get_data( const size_t indx ) {
        return bucket[indx];
    };

    /// Resize bucket
    void photonBucket::resize(const size_t N) {
        // TODO error check if N < N
        bucket.resize(N);
        nPhotons = N;

        return;
    };

    /// collect energy components of velocity
    std::vector<double> photonBucket::vecE() {
        std::vector<double> ve;
        ve.resize( size() );

        for (size_t i=0; i<size(); i++) {
            auto d = bucket[i];
            ve[i] = d[0];
        }
        return ve;
    };

    /// collect z components of velocity
    std::vector<double> photonBucket::vecZ() {
        std::vector<double> vz;
        vz.resize( size() );

        for (size_t i=0; i<size(); i++) {
            auto d = bucket[i];
            vz[i] = d[3];
        }
        return vz;
    };

    /*
    class electron {

        public:
            std::array<double, 4> data;
            electron( double, double, double, double );

            // four-velocity of electron
            const double v0(){ return data[0]; };
            const double vx(){ return data[1]; };
            const double vy(){ return data[2]; };
            const double vz(){ return data[3]; };

            /// 3-velocity in units of c, i.e. v/c
            const double v() {
                return std::sqrt(
                          pow(vx(), 2.0)
                        + pow(vy(), 2.0)
                        + pow(vz(), 2.0)
                                ); };

            /// gamma factor
            const double gamma() { return 1.0 / std::sqrt( 1.0 - std::pow( v(), 2.0 ) ); };

            /// proper momentum
            const double p() { return gamma() * v(); };

    };

    electron::electron(double v0, double vx, double vy, double vz) {
        this->data = {{v0, vx, vy, vz}};
    };
    */

    /// Electron with all velocity transformations build-in
    // Uses Eigen vectors internally for performance
    class electron {

        public:

        /// spatial components of the four-velocity
        Vector3d fvel;

        /// load from vector
        void loadFvel(Vector3d u) { fvel = u; };

        /// Load from components
        void loadFvelComponents(double ux, double uy, double uz) {
                fvel(0) = ux;
                fvel(1) = uy;
                fvel(2) = uz;
        };

        /// Load from velocity
        // u = v*gamma = v/sqrt(1-v^2)
        void loadVel(Vector3d v) {
            fvel = v / sqrt(1.0 - v.dot(v));
        };

        void loadVelComponents(double vx, double vy, double vz) {
            Vector3d v(vx, vy, vz);
            loadVel(v);
        };

/*        // return 3d components of the electron 4-velocity
        const double vx(){ return fvel(0); };
        const double vy(){ return fvel(1); };
        const double vz(){ return fvel(2); };
*/

        /// gamma factor
        const double gamma() { return sqrt(1.0 + fvel.dot(fvel) ); };

        /// beta = v/c = sqrt(1-1/gamma^2)
        const double beta() { return sqrt(1.0 - 1.0/(gamma()*gamma())); };

        /// p = sqrt(gamma^2-1)
        const double pel() { return sqrt(gamma()*gamma()-1.0); };

        /// coordinate velocity vector v = u/gamma
        Vector3d vel() { return fvel/gamma(); };

        /// vmod = sqrt(vx^2 + vy^2 + vz^2)
        const double vmod() { return sqrt(vel().dot(vel())); };

        // unit vector in the electron direction
        Vector3d beta0() { return vel()/beta(); };

    };

    // --------------------------------------------------
    class electronBucket {

        /// size of the bucket (number of electrons)
        size_t nElectrons = 0;

        /// Electron container
//        std::vector<std::array<double, 3>> bucket;
        std::vector<Vector3d> bucket;

        public:
            void push_back( electron e );
            const size_t size( ) { return this->nElectrons; };

            void replace( size_t indx, electron e );

            electron get( size_t indx);

    };

    void electronBucket::push_back( electron e ) {
        bucket.push_back( e.vel() );
        nElectrons++;
    };

    void electronBucket::replace( size_t indx, electron e ) {
        bucket[indx] = e.vel();
    };

    electron electronBucket::get( size_t indx ) {
        //std::array<double, 3> data = bucket[indx];
        electron e;
        Vector3d vel = bucket[indx];
        e.loadVel(vel);

        return e;
    };

}

// --------------------------------------------------
PYBIND11_MODULE(el_ph, m) {

    py::class_<el_ph::photon>(m, "photon" )
        .def(py::init<double, double, double, double, double, double, double, double >())
        .def_readwrite("data",      &el_ph::photon::data)
        .def("hv",  &el_ph::photon::hv)
        .def("vx",  &el_ph::photon::vx)
        .def("vy",  &el_ph::photon::vy)
        .def("vz",  &el_ph::photon::vz)
        .def("x",  &el_ph::photon::x)
        .def("y",  &el_ph::photon::y)
        .def("z",  &el_ph::photon::z)
        .def("w",  &el_ph::photon::w);

    py::class_<el_ph::electron>(m, "electron" )
        .def(py::init<>())
        .def("vx",   [](el_ph::electron &e){return e.vel()(0);})
        .def("vy",   [](el_ph::electron &e){return e.vel()(1);})
        .def("vz",   [](el_ph::electron &e){return e.vel()(2);})
        .def("loadVelComponents",  &el_ph::electron::loadVelComponents)
        .def("loadFvelComponents", &el_ph::electron::loadFvelComponents)
        .def("beta",  &el_ph::electron::beta)
        .def("gamma", &el_ph::electron::gamma)
        .def("vmod", &el_ph::electron::vmod)
        .def("beta0", &el_ph::electron::beta0);

    py::class_<el_ph::electronBucket>(m, "electronBucket" )
        .def(py::init<>())
        .def("size",      &el_ph::electronBucket::size)
        .def("replace",   &el_ph::electronBucket::replace)
        .def("get",       &el_ph::electronBucket::get)
        .def("push_back", &el_ph::electronBucket::push_back);

    py::class_<el_ph::photonBucket>(m, "photonBucket" )
        .def(py::init<>())
        .def("size",      &el_ph::photonBucket::size)
        .def("replace",   &el_ph::photonBucket::replace)
        .def("get",       &el_ph::photonBucket::get)
        .def("vecE",      &el_ph::photonBucket::vecE)
        .def("vecZ",      &el_ph::photonBucket::vecZ)
        .def("push_back", &el_ph::photonBucket::push_back);

}