#ifndef REGISTRATION_ROPS_CUSTOM_LRF_HPP
#define REGISTRATION_ROPS_CUSTOM_LRF_HPP

#include <array>
#include <numeric> // for accumulate
#include <Eigen/Eigenvalues> // for EigenSolver

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT>
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::ROPSEstimationWithLocalReferenceFrames () :
        number_of_bins_ (5),
        number_of_rotations_ (3),
        support_radius_ (1.0f),
        sqr_support_radius_ (1.0f),
        step_ (22.5f),
        triangles_ (0),
        triangles_of_the_point_ (0)
{
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT>
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::~ROPSEstimationWithLocalReferenceFrames ()
{
    triangles_.clear ();
    triangles_of_the_point_.clear ();
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT> void
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::setNumberOfPartitionBins (unsigned int number_of_bins)
{
    if (number_of_bins != 0)
        number_of_bins_ = number_of_bins;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT> unsigned int
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::getNumberOfPartitionBins () const
{
    return (number_of_bins_);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT> void
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::setNumberOfRotations (unsigned int number_of_rotations)
{
    if (number_of_rotations != 0)
    {
        number_of_rotations_ = number_of_rotations;
        step_ = 90.0f / static_cast <float> (number_of_rotations_ + 1);
    }
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT> unsigned int
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::getNumberOfRotations () const
{
    return (number_of_rotations_);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT> void
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::setSupportRadius (float support_radius)
{
    if (support_radius > 0.0f)
    {
        support_radius_ = support_radius;
        sqr_support_radius_ = support_radius * support_radius;
    }
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT> float
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::getSupportRadius () const
{
    return (support_radius_);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT> void
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::setTriangles (const std::vector <pcl::Vertices>& triangles)
{
    triangles_ = triangles;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT> void
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::getTriangles (std::vector <pcl::Vertices>& triangles) const
{
    triangles = triangles_;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT> void
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::computeFeature (PointCloudOut &output)
{
    if (this->frames_never_defined_ && this->triangles_.empty()) {
        PCL_ERROR("Either local reference frames or triangle must be provided for RoPS estimation!");
    }
    bool use_custom_frames = true;
    if (this->frames_never_defined_) {
        this->buildListOfPointsTriangles();
        use_custom_frames = false;
    }

    //feature size = number_of_rotations * number_of_axis_to_rotate_around * number_of_projections * number_of_central_moments
    unsigned int feature_size = number_of_rotations_ * 3 * 3 * 5;
    const auto number_of_points = indices_->size ();
    output.clear ();
    output.reserve (number_of_points);

    for (const auto& idx: *indices_)
    {
        pcl::Indices local_points;
        Eigen::Matrix3f lrf_matrix;
        if (use_custom_frames) {
            const PointRF &frame = this->frames_->points[idx];
            for (int d = 0; d < 3; ++d) {
                lrf_matrix.row(0)[d] = frame.x_axis[d];
                lrf_matrix.row(1)[d] = frame.y_axis[d];
                lrf_matrix.row(2)[d] = frame.z_axis[d];
            }
            std::vector <float> distances;
            this->tree_->radiusSearch((*this->input_)[idx], this->support_radius_, local_points, distances);
        } else {
            std::set<unsigned int> local_triangles;
            getLocalSurface((*this->input_)[idx], local_triangles, local_points);
            computeLRF((*this->input_)[idx], local_triangles, lrf_matrix);
        }

        PointCloudIn transformed_cloud;
        transformCloud ((*input_)[idx], lrf_matrix, local_points, transformed_cloud);

        std::array<PointInT, 3> axes;
        axes[0].x = 1.0f; axes[0].y = 0.0f; axes[0].z = 0.0f;
        axes[1].x = 0.0f; axes[1].y = 1.0f; axes[1].z = 0.0f;
        axes[2].x = 0.0f; axes[2].y = 0.0f; axes[2].z = 1.0f;
        std::vector <float> feature;
        for (const auto &axis : axes)
        {
            float theta = step_;
            do
            {
                //rotate local surface and get bounding box
                PointCloudIn rotated_cloud;
                Eigen::Vector3f min, max;
                rotateCloud (axis, theta, transformed_cloud, rotated_cloud, min, max);

                //for each projection (XY, XZ and YZ) compute distribution matrix and central moments
                for (unsigned int i_proj = 0; i_proj < 3; i_proj++)
                {
                    Eigen::MatrixXf distribution_matrix;
                    distribution_matrix.resize (number_of_bins_, number_of_bins_);
                    getDistributionMatrix (i_proj, min, max, rotated_cloud, distribution_matrix);

                    // TODO remove this needless copy due to API design
                    std::vector <float> moments;
                    computeCentralMoments (distribution_matrix, moments);

                    feature.insert (feature.end (), moments.begin (), moments.end ());
                }

                theta += step_;
            } while (theta < 90.0f);
        }

        const float norm = std::accumulate(
                feature.cbegin(), feature.cend(), 0.f, [](const auto& sum, const auto& val) {
                    return sum + std::abs(val);
                });
        float invert_norm;
        if (norm < std::numeric_limits <float>::epsilon ())
            invert_norm = 1.0f;
        else
            invert_norm = 1.0f / norm;

        output.emplace_back ();
        for (std::size_t i_dim = 0; i_dim < feature_size; i_dim++)
            output.back().histogram[i_dim] = feature[i_dim] * invert_norm;
    }
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT> void
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::buildListOfPointsTriangles ()
{
    triangles_of_the_point_.clear ();

    std::vector <unsigned int> dummy;
    dummy.reserve (100);
    triangles_of_the_point_.resize (surface_->points. size (), dummy);

    for (std::size_t i_triangle = 0; i_triangle < triangles_.size (); i_triangle++)
        for (const auto& vertex: triangles_[i_triangle].vertices)
            triangles_of_the_point_[vertex].push_back (i_triangle);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT> void
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::getLocalSurface (
        const PointInT& point, std::set <unsigned int>& local_triangles, pcl::Indices& local_points) const
{
    std::vector <float> distances;
    tree_->radiusSearch (point, support_radius_, local_points, distances);

    for (const auto& pt: local_points)
        local_triangles.insert (triangles_of_the_point_[pt].begin (),
                                triangles_of_the_point_[pt].end ());
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT> void
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::computeLRF (
        const PointInT& point, const std::set <unsigned int>& local_triangles, Eigen::Matrix3f& lrf_matrix) const
{
    std::size_t number_of_triangles = local_triangles.size ();

    std::vector<Eigen::Matrix3f, Eigen::aligned_allocator<Eigen::Matrix3f> > scatter_matrices;
    std::vector <float> triangle_area (number_of_triangles), distance_weight (number_of_triangles);

    scatter_matrices.reserve (number_of_triangles);
    triangle_area.clear ();
    distance_weight.clear ();

    float total_area = 0.0f;
    const float coeff = 1.0f / 12.0f;
    const float coeff_1_div_3 = 1.0f / 3.0f;

    Eigen::Vector3f feature_point (point.x, point.y, point.z);

    for (const auto& triangle: local_triangles)
    {
        Eigen::Vector3f pt[3];
        for (unsigned int i_vertex = 0; i_vertex < 3; i_vertex++)
        {
            const unsigned int index = triangles_[triangle].vertices[i_vertex];
            pt[i_vertex] (0) = (*surface_)[index].x;
            pt[i_vertex] (1) = (*surface_)[index].y;
            pt[i_vertex] (2) = (*surface_)[index].z;
        }

        const float curr_area = ((pt[1] - pt[0]).cross (pt[2] - pt[0])).norm ();
        triangle_area.push_back (curr_area);
        total_area += curr_area;

        distance_weight.push_back (std::pow (support_radius_ - (feature_point - (pt[0] + pt[1] + pt[2]) * coeff_1_div_3).norm (), 2.0f));

        Eigen::Matrix3f curr_scatter_matrix;
        curr_scatter_matrix.setZero ();
        for (const auto &i_pt : pt)
        {
            Eigen::Vector3f vec = i_pt - feature_point;
            curr_scatter_matrix += vec * (vec.transpose ());
            for (const auto &j_pt : pt)
                curr_scatter_matrix += vec * ((j_pt - feature_point).transpose ());
        }
        scatter_matrices.emplace_back (coeff * curr_scatter_matrix);
    }

    if (std::abs (total_area) < std::numeric_limits <float>::epsilon ())
        total_area = 1.0f / total_area;
    else
        total_area = 1.0f;

    Eigen::Matrix3f overall_scatter_matrix;
    overall_scatter_matrix.setZero ();
    std::vector<float> total_weight (number_of_triangles);
    const float denominator = 1.0f / 6.0f;
    for (std::size_t i_triangle = 0; i_triangle < number_of_triangles; i_triangle++)
    {
        const float factor = distance_weight[i_triangle] * triangle_area[i_triangle] * total_area;
        overall_scatter_matrix += factor * scatter_matrices[i_triangle];
        total_weight[i_triangle] = factor * denominator;
    }

    Eigen::Vector3f v1, v2, v3;
    computeEigenVectors (overall_scatter_matrix, v1, v2, v3);

    float h1 = 0.0f;
    float h3 = 0.0f;
    std::size_t i_triangle = 0;
    for (const auto& triangle: local_triangles)
    {
        Eigen::Vector3f pt[3];
        for (unsigned int i_vertex = 0; i_vertex < 3; i_vertex++)
        {
            const unsigned int index = triangles_[triangle].vertices[i_vertex];
            pt[i_vertex] (0) = (*surface_)[index].x;
            pt[i_vertex] (1) = (*surface_)[index].y;
            pt[i_vertex] (2) = (*surface_)[index].z;
        }

        float factor1 = 0.0f;
        float factor3 = 0.0f;
        for (const auto &i_pt : pt)
        {
            Eigen::Vector3f vec = i_pt - feature_point;
            factor1 += vec.dot (v1);
            factor3 += vec.dot (v3);
        }
        h1 += total_weight[i_triangle] * factor1;
        h3 += total_weight[i_triangle] * factor3;
        i_triangle++;
    }

    if (h1 < 0.0f) v1 = -v1;
    if (h3 < 0.0f) v3 = -v3;

    v2 = v3.cross (v1);

    lrf_matrix.row (0) = v1;
    lrf_matrix.row (1) = v2;
    lrf_matrix.row (2) = v3;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT> void
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::computeEigenVectors (
        const Eigen::Matrix3f& matrix,
        Eigen::Vector3f& major_axis, Eigen::Vector3f& middle_axis, Eigen::Vector3f& minor_axis) const
{
    Eigen::EigenSolver <Eigen::Matrix3f> eigen_solver;
    eigen_solver.compute (matrix);

    Eigen::EigenSolver <Eigen::Matrix3f>::EigenvectorsType eigen_vectors;
    Eigen::EigenSolver <Eigen::Matrix3f>::EigenvalueType eigen_values;
    eigen_vectors = eigen_solver.eigenvectors ();
    eigen_values = eigen_solver.eigenvalues ();

    unsigned int temp = 0;
    unsigned int major_index = 0;
    unsigned int middle_index = 1;
    unsigned int minor_index = 2;

    if (eigen_values.real () (major_index) < eigen_values.real () (middle_index))
    {
        temp = major_index;
        major_index = middle_index;
        middle_index = temp;
    }

    if (eigen_values.real () (major_index) < eigen_values.real () (minor_index))
    {
        temp = major_index;
        major_index = minor_index;
        minor_index = temp;
    }

    if (eigen_values.real () (middle_index) < eigen_values.real () (minor_index))
    {
        temp = minor_index;
        minor_index = middle_index;
        middle_index = temp;
    }

    major_axis = eigen_vectors.col (major_index).real ();
    middle_axis = eigen_vectors.col (middle_index).real ();
    minor_axis = eigen_vectors.col (minor_index).real ();
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT> void
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::transformCloud (
        const PointInT& point, const Eigen::Matrix3f& matrix,
        const pcl::Indices& local_points, PointCloudIn& transformed_cloud) const
{
    const auto number_of_points = local_points.size ();
    transformed_cloud.clear ();
    transformed_cloud.reserve (number_of_points);

    for (const auto& idx: local_points)
    {
        Eigen::Vector3f transformed_point ((*surface_)[idx].x - point.x,
                                           (*surface_)[idx].y - point.y,
                                           (*surface_)[idx].z - point.z);

        transformed_point = matrix * transformed_point;

        PointInT new_point;
        new_point.x = transformed_point (0);
        new_point.y = transformed_point (1);
        new_point.z = transformed_point (2);
        transformed_cloud.emplace_back (new_point);
    }
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT> void
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::rotateCloud (
        const PointInT& axis, const float angle, const PointCloudIn& cloud, PointCloudIn& rotated_cloud,
        Eigen::Vector3f& min, Eigen::Vector3f& max) const
{
    Eigen::Matrix3f rotation_matrix;
    const float x = axis.x;
    const float y = axis.y;
    const float z = axis.z;
    const float rad = M_PI / 180.0f;
    const float cosine = std::cos (angle * rad);
    const float sine = std::sin (angle * rad);
    rotation_matrix << cosine + (1 - cosine) * x * x,      (1 - cosine) * x * y - sine * z,    (1 - cosine) * x * z + sine * y,
            (1 - cosine) * y * x + sine * z,    cosine + (1 - cosine) * y * y,      (1 - cosine) * y * z - sine * x,
            (1 - cosine) * z * x - sine * y,    (1 - cosine) * z * y + sine * x,    cosine + (1 - cosine) * z * z;

    const auto number_of_points = cloud.size ();

    rotated_cloud.header = cloud.header;
    rotated_cloud.width = number_of_points;
    rotated_cloud.height = 1;
    rotated_cloud.clear ();
    rotated_cloud.reserve (number_of_points);

    min (0) = std::numeric_limits <float>::max ();
    min (1) = std::numeric_limits <float>::max ();
    min (2) = std::numeric_limits <float>::max ();
    max (0) = -std::numeric_limits <float>::max ();
    max (1) = -std::numeric_limits <float>::max ();
    max (2) = -std::numeric_limits <float>::max ();

    for (const auto& pt: cloud.points)
    {
        Eigen::Vector3f point (pt.x, pt.y, pt.z);
        point = rotation_matrix * point;

        PointInT rotated_point;
        rotated_point.x = point (0);
        rotated_point.y = point (1);
        rotated_point.z = point (2);
        rotated_cloud.emplace_back (rotated_point);

        for (int i = 0; i < 3; ++i)
        {
            min(i) = std::min(min(i), point(i));
            max(i) = std::max(max(i), point(i));
        }
    }
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT> void
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::getDistributionMatrix (
        const unsigned int projection, const Eigen::Vector3f& min, const Eigen::Vector3f& max,
        const PointCloudIn& cloud, Eigen::MatrixXf& matrix) const
{
    matrix.setZero ();

    const unsigned int coord[3][2] = {
            {0, 1},
            {0, 2},
            {1, 2}};

    const float u_bin_length = (max (coord[projection][0]) - min (coord[projection][0])) / number_of_bins_;
    const float v_bin_length = (max (coord[projection][1]) - min (coord[projection][1])) / number_of_bins_;

    for (const auto& pt: cloud.points)
    {
        Eigen::Vector3f point (pt.x, pt.y, pt.z);

        const float u_length = point (coord[projection][0]) - min[coord[projection][0]];
        const float v_length = point (coord[projection][1]) - min[coord[projection][1]];

        const float u_ratio = u_length / u_bin_length;
        unsigned int row = static_cast <unsigned int> (u_ratio);
        if (row == number_of_bins_) row--;

        const float v_ratio = v_length / v_bin_length;
        unsigned int col = static_cast <unsigned int> (v_ratio);
        if (col == number_of_bins_) col--;

        matrix (row, col) += 1.0f;
    }

    matrix /= std::max<float> (1, cloud.size ());
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PointInT, typename PointOutT, typename PointRFT> void
ROPSEstimationWithLocalReferenceFrames <PointInT, PointOutT, PointRFT>::computeCentralMoments (
        const Eigen::MatrixXf& matrix, std::vector <float>& moments) const
{
    float mean_i = 0.0f;
    float mean_j = 0.0f;

    for (unsigned int i = 0; i < number_of_bins_; i++)
        for (unsigned int j = 0; j < number_of_bins_; j++)
        {
            const float m = matrix (i, j);
            mean_i += static_cast <float> (i + 1) * m;
            mean_j += static_cast <float> (j + 1) * m;
        }

    const unsigned int number_of_moments_to_compute = 4;
    const float power[number_of_moments_to_compute][2] = {
            {1.0f, 1.0f},
            {2.0f, 1.0f},
            {1.0f, 2.0f},
            {2.0f, 2.0f}};

    float entropy = 0.0f;
    moments.resize (number_of_moments_to_compute + 1, 0.0f);
    for (unsigned int i = 0; i < number_of_bins_; i++)
    {
        const float i_factor = static_cast <float> (i + 1) - mean_i;
        for (unsigned int j = 0; j < number_of_bins_; j++)
        {
            const float j_factor = static_cast <float> (j + 1) - mean_j;
            const float m = matrix (i, j);
            if (m > 0.0f)
                entropy -= m * std::log (m);
            for (unsigned int i_moment = 0; i_moment < number_of_moments_to_compute; i_moment++)
                moments[i_moment] += std::pow (i_factor, power[i_moment][0]) * std::pow (j_factor, power[i_moment][1]) * m;
        }
    }

    moments[number_of_moments_to_compute] = entropy;
}

#endif


// Copyright (C) 2005-2006 The Trustees of Indiana University.

// Use, modification and distribution is subject to the Boost Software
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

//  Authors: Douglas Gregor
//           Andrew Lumsdaine
#ifndef BOOST_GRAPH_DISTRIBUTED_FRUCHTERMAN_REINGOLD_HPP
#define BOOST_GRAPH_DISTRIBUTED_FRUCHTERMAN_REINGOLD_HPP

#ifndef BOOST_GRAPH_USE_MPI
#error "Parallel BGL files should not be included unless <boost/graph/use_mpi.hpp> has been included"
#endif

#include <boost/graph/fruchterman_reingold.hpp>

namespace boost { namespace graph { namespace distributed {

class simple_tiling
{
 public:
  simple_tiling(int columns, int rows, bool flip = true)
    : columns(columns), rows(rows), flip(flip)
  {
  }

  // Convert from a position (x, y) in the tiled display into a
  // processor ID number
  int operator()(int x, int y) const
  {
    return flip? (rows - y - 1) * columns + x : y * columns + x;
  }

  // Convert from a process ID to a position (x, y) in the tiled
  // display
  std::pair<int, int> operator()(int id)
  {
    int my_col = id % columns;
    int my_row = flip? rows - (id / columns) - 1 : id / columns;
    return std::make_pair(my_col, my_row);
  }

  int columns, rows;

 private:
  bool flip;
};

// Force pairs function object that does nothing
struct no_force_pairs
{
  template<typename Graph, typename ApplyForce>
  void operator()(const Graph&, const ApplyForce&)
  {
  }
};

// Computes force pairs in the distributed case.
template<typename PositionMap, typename DisplacementMap, typename LocalForces,
         typename NonLocalForces = no_force_pairs>
class distributed_force_pairs_proxy
{
 public:
  distributed_force_pairs_proxy(const PositionMap& position,
                                const DisplacementMap& displacement,
                                const LocalForces& local_forces,
                                const NonLocalForces& nonlocal_forces = NonLocalForces())
    : position(position), displacement(displacement),
      local_forces(local_forces), nonlocal_forces(nonlocal_forces)
  {
  }

  template<typename Graph, typename ApplyForce>
  void operator()(const Graph& g, ApplyForce apply_force)
  {
    // Flush remote displacements
    displacement.flush();

    // Receive updated positions for all of our neighbors
    synchronize(position);

    // Reset remote displacements
    displacement.reset();

    // Compute local repulsive forces
    local_forces(g, apply_force);

    // Compute neighbor repulsive forces
    nonlocal_forces(g, apply_force);
  }

 protected:
  PositionMap position;
  DisplacementMap displacement;
  LocalForces local_forces;
  NonLocalForces nonlocal_forces;
};

template<typename PositionMap, typename DisplacementMap, typename LocalForces>
inline
distributed_force_pairs_proxy<PositionMap, DisplacementMap, LocalForces>
make_distributed_force_pairs(const PositionMap& position,
                             const DisplacementMap& displacement,
                             const LocalForces& local_forces)
{
  typedef
    distributed_force_pairs_proxy<PositionMap, DisplacementMap, LocalForces>
    result_type;
  return result_type(position, displacement, local_forces);
}

template<typename PositionMap, typename DisplacementMap, typename LocalForces,
         typename NonLocalForces>
inline
distributed_force_pairs_proxy<PositionMap, DisplacementMap, LocalForces,
                              NonLocalForces>
make_distributed_force_pairs(const PositionMap& position,
                             const DisplacementMap& displacement,
                             const LocalForces& local_forces,
                             const NonLocalForces& nonlocal_forces)
{
  typedef
    distributed_force_pairs_proxy<PositionMap, DisplacementMap, LocalForces,
                                  NonLocalForces>
      result_type;
  return result_type(position, displacement, local_forces, nonlocal_forces);
}

// Compute nonlocal force pairs based on the shared borders with
// adjacent tiles.
template<typename PositionMap>
class neighboring_tiles_force_pairs
{
 public:
  typedef typename property_traits<PositionMap>::value_type Point;
  typedef typename point_traits<Point>::component_type Dim;

  enum bucket_position { left, top, right, bottom, end_position };

  neighboring_tiles_force_pairs(PositionMap position, Point origin,
                                Point extent, simple_tiling tiling)
    : position(position), origin(origin), extent(extent), tiling(tiling)
  {
  }

  template<typename Graph, typename ApplyForce>
  void operator()(const Graph& g, ApplyForce apply_force)
  {
    // TBD: Do this some smarter way
    if (tiling.columns == 1 && tiling.rows == 1)
      return;

    typedef typename graph_traits<Graph>::vertex_descriptor vertex_descriptor;
#ifndef BOOST_NO_STDC_NAMESPACE
    using std::sqrt;
#endif // BOOST_NO_STDC_NAMESPACE

    // TBD: num_vertices(g) should be the global number of vertices?
    Dim two_k = Dim(2) * sqrt(extent[0] * extent[1] / num_vertices(g));

    std::vector<vertex_descriptor> my_vertices[4];
    std::vector<vertex_descriptor> neighbor_vertices[4];

    // Compute cutoff positions
    Dim cutoffs[4];
    cutoffs[left] = origin[0] + two_k;
    cutoffs[top] = origin[1] + two_k;
    cutoffs[right] = origin[0] + extent[0] - two_k;
    cutoffs[bottom] = origin[1] + extent[1] - two_k;

    // Compute neighbors
    typename PositionMap::process_group_type pg = position.process_group();
    std::pair<int, int> my_tile = tiling(process_id(pg));
    int neighbors[4] = { -1, -1, -1, -1 } ;
    if (my_tile.first > 0)
      neighbors[left] = tiling(my_tile.first - 1, my_tile.second);
    if (my_tile.second > 0)
      neighbors[top] = tiling(my_tile.first, my_tile.second - 1);
    if (my_tile.first < tiling.columns - 1)
      neighbors[right] = tiling(my_tile.first + 1, my_tile.second);
    if (my_tile.second < tiling.rows - 1)
      neighbors[bottom] = tiling(my_tile.first, my_tile.second + 1);

    // Sort vertices along the edges into buckets
    BGL_FORALL_VERTICES_T(v, g, Graph) {
      if (position[v][0] <= cutoffs[left]) my_vertices[left].push_back(v);
      if (position[v][1] <= cutoffs[top]) my_vertices[top].push_back(v);
      if (position[v][0] >= cutoffs[right]) my_vertices[right].push_back(v);
      if (position[v][1] >= cutoffs[bottom]) my_vertices[bottom].push_back(v);
    }

    // Send vertices to neighbors, and gather our neighbors' vertices
    bucket_position pos;
    for (pos = left; pos < end_position; pos = bucket_position(pos + 1)) {
      if (neighbors[pos] != -1) {
        send(pg, neighbors[pos], 0, my_vertices[pos].size());
        if (!my_vertices[pos].empty())
          send(pg, neighbors[pos], 1,
               &my_vertices[pos].front(), my_vertices[pos].size());
      }
    }

    // Pass messages around
    synchronize(pg);

    // Receive neighboring vertices
    for (pos = left; pos < end_position; pos = bucket_position(pos + 1)) {
      if (neighbors[pos] != -1) {
        std::size_t incoming_vertices;
        receive(pg, neighbors[pos], 0, incoming_vertices);

        if (incoming_vertices) {
          neighbor_vertices[pos].resize(incoming_vertices);
          receive(pg, neighbors[pos], 1, &neighbor_vertices[pos].front(),
                  incoming_vertices);
        }
      }
    }

    // For each neighboring vertex, we need to get its current position
    for (pos = left; pos < end_position; pos = bucket_position(pos + 1))
      for (typename std::vector<vertex_descriptor>::iterator i =
             neighbor_vertices[pos].begin();
           i != neighbor_vertices[pos].end();
           ++i)
        request(position, *i);
    synchronize(position);

    // Apply forces in adjacent bins. This is O(n^2) in the worst
    // case. Oh well.
    for (pos = left; pos < end_position; pos = bucket_position(pos + 1)) {
      for (typename std::vector<vertex_descriptor>::iterator i =
             my_vertices[pos].begin();
           i != my_vertices[pos].end();
           ++i)
        for (typename std::vector<vertex_descriptor>::iterator j =
               neighbor_vertices[pos].begin();
             j != neighbor_vertices[pos].end();
             ++j)
          apply_force(*i, *j);
    }
  }

 protected:
  PositionMap position;
  Point origin;
  Point extent;
  simple_tiling tiling;
};

template<typename PositionMap>
inline neighboring_tiles_force_pairs<PositionMap>
make_neighboring_tiles_force_pairs
 (PositionMap position,
  typename property_traits<PositionMap>::value_type origin,
  typename property_traits<PositionMap>::value_type extent,
  simple_tiling tiling)
{
  return neighboring_tiles_force_pairs<PositionMap>(position, origin, extent,
                                                    tiling);
}

template<typename DisplacementMap, typename Cooling>
class distributed_cooling_proxy
{
 public:
  typedef typename Cooling::result_type result_type;

  distributed_cooling_proxy(const DisplacementMap& displacement,
                            const Cooling& cooling)
    : displacement(displacement), cooling(cooling)
  {
  }

  result_type operator()()
  {
    // Accumulate displacements computed on each processor
    synchronize(displacement.data->process_group);

    // Allow the underlying cooling to occur
    return cooling();
  }

 protected:
  DisplacementMap displacement;
  Cooling cooling;
};

template<typename DisplacementMap, typename Cooling>
inline distributed_cooling_proxy<DisplacementMap, Cooling>
make_distributed_cooling(const DisplacementMap& displacement,
                         const Cooling& cooling)
{
  typedef distributed_cooling_proxy<DisplacementMap, Cooling> result_type;
  return result_type(displacement, cooling);
}

template<typename Point>
struct point_accumulating_reducer {
  BOOST_STATIC_CONSTANT(bool, non_default_resolver = true);

  template<typename K>
  Point operator()(const K&) const { return Point(); }

  template<typename K>
  Point operator()(const K&, const Point& p1, const Point& p2) const
  { return Point(p1[0] + p2[0], p1[1] + p2[1]); }
};

template<typename Graph, typename PositionMap,
         typename AttractiveForce, typename RepulsiveForce,
         typename ForcePairs, typename Cooling, typename DisplacementMap>
void
fruchterman_reingold_force_directed_layout
 (const Graph&    g,
  PositionMap     position,
  typename property_traits<PositionMap>::value_type const& origin,
  typename property_traits<PositionMap>::value_type const& extent,
  AttractiveForce attractive_force,
  RepulsiveForce  repulsive_force,
  ForcePairs      force_pairs,
  Cooling         cool,
  DisplacementMap displacement)
{
  typedef typename property_traits<PositionMap>::value_type Point;

  // Reduction in the displacement map involves summing the forces
  displacement.set_reduce(point_accumulating_reducer<Point>());

  // We need to track the positions of all of our neighbors
  BGL_FORALL_VERTICES_T(u, g, Graph)
    BGL_FORALL_ADJ_T(u, v, g, Graph)
      request(position, v);

  // Invoke the "sequential" Fruchterman-Reingold implementation
  boost::fruchterman_reingold_force_directed_layout
    (g, position, origin, extent,
     attractive_force, repulsive_force,
     make_distributed_force_pairs(position, displacement, force_pairs),
     make_distributed_cooling(displacement, cool),
     displacement);
}

template<typename Graph, typename PositionMap,
         typename AttractiveForce, typename RepulsiveForce,
         typename ForcePairs, typename Cooling, typename DisplacementMap>
void
fruchterman_reingold_force_directed_layout
 (const Graph&    g,
  PositionMap     position,
  typename property_traits<PositionMap>::value_type const& origin,
  typename property_traits<PositionMap>::value_type const& extent,
  AttractiveForce attractive_force,
  RepulsiveForce  repulsive_force,
  ForcePairs      force_pairs,
  Cooling         cool,
  DisplacementMap displacement,
  simple_tiling   tiling)
{
  typedef typename property_traits<PositionMap>::value_type Point;

  // Reduction in the displacement map involves summing the forces
  displacement.set_reduce(point_accumulating_reducer<Point>());

  // We need to track the positions of all of our neighbors
  BGL_FORALL_VERTICES_T(u, g, Graph)
    BGL_FORALL_ADJ_T(u, v, g, Graph)
      request(position, v);

  // Invoke the "sequential" Fruchterman-Reingold implementation
  boost::fruchterman_reingold_force_directed_layout
    (g, position, origin, extent,
     attractive_force, repulsive_force,
     make_distributed_force_pairs
      (position, displacement, force_pairs,
       make_neighboring_tiles_force_pairs(position, origin, extent, tiling)),
     make_distributed_cooling(displacement, cool),
     displacement);
}

} } } // end namespace boost::graph::distributed

#endif // BOOST_GRAPH_DISTRIBUTED_FRUCHTERMAN_REINGOLD_HPP


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2013, 2014, 2015.
// Modifications copyright (c) 2013-2015 Oracle and/or its affiliates.

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_GET_TURN_INFO_LA_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_GET_TURN_INFO_LA_HPP

#include <boost/geometry/core/assert.hpp>

#include <boost/geometry/util/condition.hpp>

#include <boost/geometry/algorithms/detail/overlay/get_turn_info.hpp>
#include <boost/geometry/algorithms/detail/overlay/get_turn_info_for_endpoint.hpp>

// TEMP, for spikes detector
//#include <boost/geometry/algorithms/detail/overlay/get_turn_info_ll.hpp>

namespace geofeatures_boost {} namespace boost = geofeatures_boost; namespace geofeatures_boost { namespace geometry {

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace overlay {

template<typename AssignPolicy>
struct get_turn_info_linear_areal
{
    // Currently only Linear spikes are handled
    // Areal spikes are ignored
    static const bool handle_spikes = true;

    template
    <
        typename Point1,
        typename Point2,
        typename TurnInfo,
        typename RobustPolicy,
        typename OutputIterator
    >
    static inline OutputIterator apply(
                Point1 const& pi, Point1 const& pj, Point1 const& pk,
                Point2 const& qi, Point2 const& qj, Point2 const& qk,
                bool is_p_first, bool is_p_last,
                bool is_q_first, bool is_q_last,
                TurnInfo const& tp_model,
                RobustPolicy const& robust_policy,
                OutputIterator out)
    {
        typedef intersection_info<Point1, Point2, typename TurnInfo::point_type, RobustPolicy>
            inters_info;

        inters_info inters(pi, pj, pk, qi, qj, qk, robust_policy);

        char const method = inters.d_info().how;

        // Copy, to copy possibly extended fields
        TurnInfo tp = tp_model;

        // Select method and apply
        switch(method)
        {
            case 'a' : // collinear, "at"
            case 'f' : // collinear, "from"
            case 's' : // starts from the middle
                get_turn_info_for_endpoint<true, true>(
                    pi, pj, pk, qi, qj, qk,
                    is_p_first, is_p_last, is_q_first, is_q_last,
                    tp_model, inters, method_none, out);
                break;

            case 'd' : // disjoint: never do anything
                break;

            case 'm' :
            {
                if ( get_turn_info_for_endpoint<false, true>(
                        pi, pj, pk, qi, qj, qk,
                        is_p_first, is_p_last, is_q_first, is_q_last,
                        tp_model, inters, method_touch_interior, out) )
                {
                    // do nothing
                }
                else
                {
                    typedef touch_interior
                        <
                            TurnInfo
                        > policy;

                    // If Q (1) arrives (1)
                    if ( inters.d_info().arrival[1] == 1 )
                    {
                        policy::template apply<0>(pi, pj, pk, qi, qj, qk,
                                    tp, inters.i_info(), inters.d_info(),
                                    inters.sides());
                    }
                    else
                    {
                        // Swap p/q
                        side_calculator
                            <
                                typename inters_info::robust_point2_type,
                                typename inters_info::robust_point1_type
                            > swapped_side_calc(inters.rqi(), inters.rqj(), inters.rqk(),
                                                inters.rpi(), inters.rpj(), inters.rpk());
                        policy::template apply<1>(qi, qj, qk, pi, pj, pk,
                                    tp, inters.i_info(), inters.d_info(),
                                    swapped_side_calc);
                    }

                    if ( tp.operations[1].operation == operation_blocked )
                    {
                        tp.operations[0].is_collinear = true;
                    }

                    replace_method_and_operations_tm(tp.method,
                                                     tp.operations[0].operation,
                                                     tp.operations[1].operation);

                    // this function assumes that 'u' must be set for a spike
                    calculate_spike_operation(tp.operations[0].operation,
                                              inters, is_p_last);

                    AssignPolicy::apply(tp, pi, qi, inters);

                    *out++ = tp;
                }
            }
            break;
            case 'i' :
            {
                crosses<TurnInfo>::apply(pi, pj, pk, qi, qj, qk,
                                         tp, inters.i_info(), inters.d_info());

                replace_operations_i(tp.operations[0].operation, tp.operations[1].operation);

                AssignPolicy::apply(tp, pi, qi, inters);
                *out++ = tp;
            }
            break;
            case 't' :
            {
                // Both touch (both arrive there)
                if ( get_turn_info_for_endpoint<false, true>(
                        pi, pj, pk, qi, qj, qk,
                        is_p_first, is_p_last, is_q_first, is_q_last,
                        tp_model, inters, method_touch, out) )
                {
                    // do nothing
                }
                else
                {
                    touch<TurnInfo>::apply(pi, pj, pk, qi, qj, qk,
                            tp, inters.i_info(), inters.d_info(), inters.sides());

                    if ( tp.operations[1].operation == operation_blocked )
                    {
                        tp.operations[0].is_collinear = true;
                    }

                    // workarounds for touch<> not taking spikes into account starts here
                    // those was discovered empirically
                    // touch<> is not symmetrical!
                    // P spikes and Q spikes may produce various operations!
                    // Only P spikes are valid for L/A
                    // TODO: this is not optimal solution - think about rewriting touch<>

                    if ( tp.operations[0].operation == operation_blocked )
                    {
                        // a spike on P on the same line with Q1
                        if ( inters.is_spike_p() )
                        {
                            if ( inters.sides().qk_wrt_p1() == 0 )
                            {
                                tp.operations[0].is_collinear = true;
                            }
                            else
                            {
                                tp.operations[0].operation = operation_union;
                            }
                        }
                    }
                    else if ( tp.operations[0].operation == operation_continue
                           && tp.operations[1].operation == operation_continue )
                    {
                        // P spike on the same line with Q2 (opposite)
                        if ( inters.sides().pk_wrt_q1() == -inters.sides().qk_wrt_q1()
                          && inters.is_spike_p() )
                        {
                            tp.operations[0].operation = operation_union;
                            tp.operations[1].operation = operation_union;
                        }
                    }
                    else if ( tp.operations[0].operation == operation_none
                           && tp.operations[1].operation == operation_none )
                    {
                        // spike not handled by touch<>
                        if ( inters.is_spike_p() )
                        {
                            tp.operations[0].operation = operation_intersection;
                            tp.operations[1].operation = operation_union;

                            if ( inters.sides().pk_wrt_q2() == 0 )
                            {
                                tp.operations[0].operation = operation_continue; // will be converted to i
                                tp.operations[0].is_collinear = true;
                            }
                        }
                    }

                    // workarounds for touch<> not taking spikes into account ends here

                    replace_method_and_operations_tm(tp.method,
                                                     tp.operations[0].operation,
                                                     tp.operations[1].operation);

                    bool ignore_spike
                        = calculate_spike_operation(tp.operations[0].operation,
                                                    inters, is_p_last);

// TODO: move this into the append_xxx and call for each turn?
                    AssignPolicy::apply(tp, pi, qi, inters);

                    if ( ! BOOST_GEOMETRY_CONDITION(handle_spikes)
                      || ignore_spike
                      || ! append_opposite_spikes<append_touches>( // for 'i' or 'c' i???
                                tp, inters, is_p_last, is_q_last, out) )
                    {
                        *out++ = tp;
                    }
                }
            }
            break;
            case 'e':
            {
                if ( get_turn_info_for_endpoint<true, true>(
                        pi, pj, pk, qi, qj, qk,
                        is_p_first, is_p_last, is_q_first, is_q_last,
                        tp_model, inters, method_equal, out) )
                {
                    // do nothing
                }
                else
                {
                    tp.operations[0].is_collinear = true;

                    if ( ! inters.d_info().opposite )
                    {
                        // Both equal
                        // or collinear-and-ending at intersection point
                        equal<TurnInfo>::apply(pi, pj, pk, qi, qj, qk,
                            tp, inters.i_info(), inters.d_info(), inters.sides());

                        turn_transformer_ec<false> transformer(method_touch);
                        transformer(tp);

// TODO: move this into the append_xxx and call for each turn?
                        AssignPolicy::apply(tp, pi, qi, inters);

                        // conditionally handle spikes
                        if ( ! BOOST_GEOMETRY_CONDITION(handle_spikes)
                          || ! append_collinear_spikes(tp, inters, is_p_last, is_q_last,
                                                       method_touch, append_equal, out) )
                        {
                            *out++ = tp; // no spikes
                        }
                    }
                    else
                    {
                        equal_opposite
                            <
                                TurnInfo,
                                AssignPolicy
                            >::apply(pi, qi,
                                     tp, out, inters);
                    }
                }
            }
            break;
            case 'c' :
            {
                // Collinear
                if ( get_turn_info_for_endpoint<true, true>(
                        pi, pj, pk, qi, qj, qk,
                        is_p_first, is_p_last, is_q_first, is_q_last,
                        tp_model, inters, method_collinear, out) )
                {
                    // do nothing
                }
                else
                {
                    tp.operations[0].is_collinear = true;

                    if ( ! inters.d_info().opposite )
                    {
                        method_type method_replace = method_touch_interior;
                        append_version_c version = append_collinear;

                        if ( inters.d_info().arrival[0] == 0 )
                        {
                            // Collinear, but similar thus handled as equal
                            equal<TurnInfo>::apply(pi, pj, pk, qi, qj, qk,
                                    tp, inters.i_info(), inters.d_info(), inters.sides());

                            method_replace = method_touch;
                            version = append_equal;
                        }
                        else
                        {
                            collinear<TurnInfo>::apply(pi, pj, pk, qi, qj, qk,
                                    tp, inters.i_info(), inters.d_info(), inters.sides());

                            //method_replace = method_touch_interior;
                            //version = append_collinear;
                        }

                        turn_transformer_ec<false> transformer(method_replace);
                        transformer(tp);

// TODO: move this into the append_xxx and call for each turn?
                        AssignPolicy::apply(tp, pi, qi, inters);

                        // conditionally handle spikes
                        if ( ! BOOST_GEOMETRY_CONDITION(handle_spikes)
                          || ! append_collinear_spikes(tp, inters, is_p_last, is_q_last,
                                                       method_replace, version, out) )
                        {
                            // no spikes
                            *out++ = tp;
                        }
                    }
                    else
                    {
                        // Is this always 'm' ?
                        turn_transformer_ec<false> transformer(method_touch_interior);

                        // conditionally handle spikes
                        if ( BOOST_GEOMETRY_CONDITION(handle_spikes) )
                        {
                            append_opposite_spikes<append_collinear_opposite>(
                                    tp, inters, is_p_last, is_q_last, out);
                        }

                        // TODO: ignore for spikes?
                        //       E.g. pass is_p_valid = !is_p_last && !is_pj_spike,
                        //       the same with is_q_valid

                        collinear_opposite
                            <
                                TurnInfo,
                                AssignPolicy
                            >::apply(pi, pj, pk, qi, qj, qk,
                                tp, out, inters,
                                inters.sides(), transformer,
                                !is_p_last, true); // qk is always valid
                    }
                }
            }
            break;
            case '0' :
            {
                // degenerate points
                if ( BOOST_GEOMETRY_CONDITION(AssignPolicy::include_degenerate) )
                {
                    only_convert::apply(tp, inters.i_info());

                    if ( is_p_first
                      && equals::equals_point_point(pi, tp.point) )
                    {
                        tp.operations[0].position = position_front;
                    }
                    else if ( is_p_last
                           && equals::equals_point_point(pj, tp.point) )
                    {
                        tp.operations[0].position = position_back;
                    }
                    // tp.operations[1].position = position_middle;

                    AssignPolicy::apply(tp, pi, qi, inters);
                    *out++ = tp;
                }
            }
            break;
            default :
            {
#if defined(BOOST_GEOMETRY_DEBUG_ROBUSTNESS)
                std::cout << "TURN: Unknown method: " << method << std::endl;
#endif
#if ! defined(BOOST_GEOMETRY_OVERLAY_NO_THROW)
                throw turn_info_exception(method);
#endif
            }
            break;
        }

        return out;
    }

    template <typename Operation,
              typename IntersectionInfo>
    static inline bool calculate_spike_operation(Operation & op,
                                                 IntersectionInfo const& inters,
                                                 bool is_p_last)
    {
        bool is_p_spike = ( op == operation_union || op == operation_intersection )
                       && ! is_p_last
                       && inters.is_spike_p();

        if ( is_p_spike )
        {
            int const pk_q1 = inters.sides().pk_wrt_q1();

            bool going_in = pk_q1 < 0; // Pk on the right
            bool going_out = pk_q1 > 0; // Pk on the left

            int const qk_q1 = inters.sides().qk_wrt_q1();

            // special cases
            if ( qk_q1 < 0 ) // Q turning R
            {
                // spike on the edge point
                // if it's already known that the spike is going out this musn't be checked
                if ( ! going_out
                  && equals::equals_point_point(inters.rpj(), inters.rqj()) )
                {
                    int const pk_q2 = inters.sides().pk_wrt_q2();
                    going_in = pk_q1 < 0 && pk_q2 < 0; // Pk on the right of both
                    going_out = pk_q1 > 0 || pk_q2 > 0; // Pk on the left of one of them
                }
            }
            else if ( qk_q1 > 0 ) // Q turning L
            {
                // spike on the edge point
                // if it's already known that the spike is going in this musn't be checked
                if ( ! going_in
                  && equals::equals_point_point(inters.rpj(), inters.rqj()) )
                {
                    int const pk_q2 = inters.sides().pk_wrt_q2();
                    going_in = pk_q1 < 0 || pk_q2 < 0; // Pk on the right of one of them
                    going_out = pk_q1 > 0 && pk_q2 > 0; // Pk on the left of both
                }
            }

            if ( going_in )
            {
                op = operation_intersection;
                return true;
            }
            else if ( going_out )
            {
                op = operation_union;
                return true;
            }
        }

        return false;
    }

    enum append_version_c { append_equal, append_collinear };

    template <typename TurnInfo,
              typename IntersectionInfo,
              typename OutIt>
    static inline bool append_collinear_spikes(TurnInfo & tp,
                                               IntersectionInfo const& inters,
                                               bool is_p_last, bool /*is_q_last*/,
                                               method_type method, append_version_c version,
                                               OutIt out)
    {
        // method == touch || touch_interior
        // both position == middle

        bool is_p_spike = ( version == append_equal ?
                            ( tp.operations[0].operation == operation_union
                           || tp.operations[0].operation == operation_intersection ) :
                            tp.operations[0].operation == operation_continue )
                       && ! is_p_last
                       && inters.is_spike_p();

        // TODO: throw an exception for spike in Areal?
        /*bool is_q_spike = tp.operations[1].operation == operation_continue
                       && inters.is_spike_q();

        // both are collinear spikes on the same IP, we can just follow both
        if ( is_p_spike && is_q_spike )
        {
            return false;
        }
        // spike on Linear - it's turning back on the boundary of Areal
        else*/
        if ( is_p_spike )
        {
            tp.method = method;
            tp.operations[0].operation = operation_blocked;
            tp.operations[1].operation = operation_union;
            *out++ = tp;
            tp.operations[0].operation = operation_continue; // boundary
            //tp.operations[1].operation = operation_union;
            *out++ = tp;

            return true;
        }
        // spike on Areal - Linear is going outside
        /*else if ( is_q_spike )
        {
            tp.method = method;
            tp.operations[0].operation = operation_union;
            tp.operations[1].operation = operation_continue;
            *out++ = tp;
            *out++ = tp;

            return true;
        }*/

        return false;
    }

    enum append_version_o { append_touches, append_collinear_opposite };

    template <append_version_o Version,
              typename TurnInfo,
              typename IntersectionInfo,
              typename OutIt>
    static inline bool append_opposite_spikes(TurnInfo & tp,
                                              IntersectionInfo const& inters,
                                              bool is_p_last, bool /*is_q_last*/,
                                              OutIt out)
    {
        static const bool is_version_touches = (Version == append_touches);

        bool is_p_spike = ( is_version_touches ?
                            ( tp.operations[0].operation == operation_continue
                           || tp.operations[0].operation == operation_intersection ) : // i ???
                            true )
                       && ! is_p_last
                       && inters.is_spike_p();

        // TODO: throw an exception for spike in Areal?
        /*bool is_q_spike = ( ( Version == append_touches
                           && tp.operations[1].operation == operation_continue )
                         || ( Version == append_collinear_opposite
                           && tp.operations[1].operation == operation_none ) )
                       && inters.is_spike_q();

        if ( is_p_spike && is_q_spike )
        {
            // u/u or nothing?
            return false;
        }
        else*/
        if ( is_p_spike )
        {
            if ( BOOST_GEOMETRY_CONDITION(is_version_touches)
              || inters.d_info().arrival[0] == 1 )
            {
                if ( BOOST_GEOMETRY_CONDITION(is_version_touches) )
                {
                    tp.operations[0].is_collinear = true;
                    //tp.operations[1].is_collinear = false;
                    tp.method = method_touch;
                }
                else
                {
                    tp.operations[0].is_collinear = true;
                    //tp.operations[1].is_collinear = false;

                    BOOST_GEOMETRY_ASSERT(inters.i_info().count > 1);
                    base_turn_handler::assign_point(tp, method_touch_interior, inters.i_info(), 1);

                    AssignPolicy::apply(tp, inters.pi(), inters.qi(), inters);
                }

                tp.operations[0].operation = operation_blocked;
                tp.operations[1].operation = operation_continue; // boundary
                *out++ = tp;
                tp.operations[0].operation = operation_continue; // boundary
                //tp.operations[1].operation = operation_continue; // boundary
                *out++ = tp;

                return true;
            }
        }
        /*else if ( is_q_spike )
        {
            tp.operations[0].is_collinear = true;
            tp.method = is_version_touches ? method_touch : method_touch_interior;
            tp.operations[0].operation = operation_continue;
            tp.operations[1].operation = operation_continue; // boundary
            *out++ = tp;
            *out++ = tp;

            return true;
        }*/

        return false;
    }

    static inline void replace_method_and_operations_tm(method_type & method,
                                                        operation_type & op0,
                                                        operation_type & op1)
    {
        if ( op0 == operation_blocked && op1 == operation_blocked )
        {
            // NOTE: probably only if methods are WRT IPs, not segments!
            method = (method == method_touch ? method_equal : method_collinear);
        }

        // Assuming G1 is always Linear
        if ( op0 == operation_blocked )
        {
            op0 = operation_continue;
        }

        if ( op1 == operation_blocked )
        {
            op1 = operation_continue;
        }
        else if ( op1 == operation_intersection )
        {
            op1 = operation_union;
        }

        // spikes in 'm'
        if ( method == method_error )
        {
            method = method_touch_interior;
            op0 = operation_union;
            op1 = operation_union;
        }
    }

    template <bool IsFront>
    class turn_transformer_ec
    {
    public:
        explicit turn_transformer_ec(method_type method_t_or_m)
            : m_method(method_t_or_m)
        {}

        template <typename Turn>
        void operator()(Turn & turn) const
        {
            operation_type & op0 = turn.operations[0].operation;
            operation_type & op1 = turn.operations[1].operation;

            // NOTE: probably only if methods are WRT IPs, not segments!
            if ( BOOST_GEOMETRY_CONDITION(IsFront)
              || op0 == operation_intersection || op0 == operation_union
              || op1 == operation_intersection || op1 == operation_union )
            {
                turn.method = m_method;
            }

            turn.operations[0].is_collinear = op0 != operation_blocked;

            // Assuming G1 is always Linear
            if ( op0 == operation_blocked )
            {
                op0 = operation_continue;
            }

            if ( op1 == operation_blocked )
            {
                op1 = operation_continue;
            }
            else if ( op1 == operation_intersection )
            {
                op1 = operation_union;
            }
        }

    private:
        method_type m_method;
    };

    static inline void replace_operations_i(operation_type & /*op0*/, operation_type & op1)
    {
        // assuming Linear is always the first one
        op1 = operation_union;
    }

    // NOTE: Spikes may NOT be handled for Linear endpoints because it's not
    //       possible to define a spike on an endpoint. Areal geometries must
    //       NOT have spikes at all. One thing that could be done is to throw
    //       an exception when spike is detected in Areal geometry.

    template <bool EnableFirst,
              bool EnableLast,
              typename Point1,
              typename Point2,
              typename TurnInfo,
              typename IntersectionInfo,
              typename OutputIterator>
    static inline bool get_turn_info_for_endpoint(
                            Point1 const& pi, Point1 const& /*pj*/, Point1 const& /*pk*/,
                            Point2 const& qi, Point2 const& /*qj*/, Point2 const& /*qk*/,
                            bool is_p_first, bool is_p_last,
                            bool /*is_q_first*/, bool is_q_last,
                            TurnInfo const& tp_model,
                            IntersectionInfo const& inters,
                            method_type /*method*/,
                            OutputIterator out)
    {
        namespace ov = overlay;
        typedef ov::get_turn_info_for_endpoint<AssignPolicy, EnableFirst, EnableLast> get_info_e;

        const std::size_t ip_count = inters.i_info().count;
        // no intersection points
        if ( ip_count == 0 )
            return false;

        if ( !is_p_first && !is_p_last )
            return false;

// TODO: is_q_last could probably be replaced by false and removed from parameters

        linear_intersections intersections(pi, qi, inters.result(), is_p_last, is_q_last);
        linear_intersections::ip_info const& ip0 = intersections.template get<0>();
        linear_intersections::ip_info const& ip1 = intersections.template get<1>();

        const bool opposite = inters.d_info().opposite;

        // ANALYSE AND ASSIGN FIRST

        // IP on the first point of Linear Geometry
        bool was_first_point_handled = false;
        if ( BOOST_GEOMETRY_CONDITION(EnableFirst)
          && is_p_first && ip0.is_pi && !ip0.is_qi ) // !q0i prevents duplication
        {
            TurnInfo tp = tp_model;
            tp.operations[0].position = position_front;
            tp.operations[1].position = position_middle;

            if ( opposite ) // opposite -> collinear
            {
                tp.operations[0].operation = operation_continue;
                tp.operations[1].operation = operation_union;
                tp.method = ip0.is_qj ? method_touch : method_touch_interior;
            }
            else
            {
                method_type replaced_method = method_touch_interior;

                if ( ip0.is_qj )
                {
                    side_calculator
                        <
                            typename IntersectionInfo::robust_point1_type,
                            typename IntersectionInfo::robust_point2_type,
                            typename IntersectionInfo::robust_point2_type
                        > side_calc(inters.rqi(), inters.rpi(), inters.rpj(),
                                    inters.rqi(), inters.rqj(), inters.rqk());

                    std::pair<operation_type, operation_type>
                        operations = get_info_e::operations_of_equal(side_calc);

                    tp.operations[0].operation = operations.first;
                    tp.operations[1].operation = operations.second;

                    replaced_method = method_touch;
                }
                else
                {
                    side_calculator
                        <
                            typename IntersectionInfo::robust_point1_type,
                            typename IntersectionInfo::robust_point2_type,
                            typename IntersectionInfo::robust_point2_type,
                            typename IntersectionInfo::robust_point1_type,
                            typename IntersectionInfo::robust_point1_type,
                            typename IntersectionInfo::robust_point2_type,
                            typename IntersectionInfo::robust_point1_type,
                            typename IntersectionInfo::robust_point2_type
                        > side_calc(inters.rqi(), inters.rpi(), inters.rpj(),
                                    inters.rqi(), inters.rpi(), inters.rqj());

                    std::pair<operation_type, operation_type>
                        operations = get_info_e::operations_of_equal(side_calc);

                    tp.operations[0].operation = operations.first;
                    tp.operations[1].operation = operations.second;
                }

                turn_transformer_ec<true> transformer(replaced_method);
                transformer(tp);
            }

            // equals<> or collinear<> will assign the second point,
            // we'd like to assign the first one
            base_turn_handler::assign_point(tp, tp.method, inters.i_info(), 0);

            // NOTE: is_collinear is not set for the first endpoint of L
            // for which there is no preceding segment
            // here is_p_first_ip == true
            tp.operations[0].is_collinear = false;

            AssignPolicy::apply(tp, pi, qi, inters);
            *out++ = tp;

            was_first_point_handled = true;
        }

        // ANALYSE AND ASSIGN LAST

        // IP on the last point of Linear Geometry
        if ( BOOST_GEOMETRY_CONDITION(EnableLast)
          && is_p_last
          && ( ip_count > 1 ? (ip1.is_pj && !ip1.is_qi) : (ip0.is_pj && !ip0.is_qi) ) ) // prevents duplication
        {
            TurnInfo tp = tp_model;

            if ( inters.i_info().count > 1 )
            {
                //BOOST_GEOMETRY_ASSERT( result.template get<1>().dir_a == 0 && result.template get<1>().dir_b == 0 );
                tp.operations[0].is_collinear = true;
                tp.operations[1].operation = opposite ? operation_continue : operation_union;
            }
            else //if ( result.template get<0>().count == 1 )
            {
                side_calculator
                    <
                        typename IntersectionInfo::robust_point1_type,
                        typename IntersectionInfo::robust_point2_type,
                        typename IntersectionInfo::robust_point2_type
                    > side_calc(inters.rqi(), inters.rpj(), inters.rpi(),
                                inters.rqi(), inters.rqj(), inters.rqk());

                std::pair<operation_type, operation_type>
                    operations = get_info_e::operations_of_equal(side_calc);

                tp.operations[0].operation = operations.first;
                tp.operations[1].operation = operations.second;

                turn_transformer_ec<false> transformer(method_none);
                transformer(tp);

                tp.operations[0].is_collinear = tp.both(operation_continue);
            }

            tp.method = ( ip_count > 1 ? ip1.is_qj : ip0.is_qj ) ? method_touch : method_touch_interior;
            tp.operations[0].operation = operation_blocked;
            tp.operations[0].position = position_back;
            tp.operations[1].position = position_middle;

            // equals<> or collinear<> will assign the second point,
            // we'd like to assign the first one
            unsigned int ip_index = ip_count > 1 ? 1 : 0;
            base_turn_handler::assign_point(tp, tp.method, inters.i_info(), ip_index);

            AssignPolicy::apply(tp, pi, qi, inters);
            *out++ = tp;

            // don't ignore the first IP if the segment is opposite
            return !( opposite && ip_count > 1 ) || was_first_point_handled;
        }

        // don't ignore anything for now
        return false;
    }
};

}} // namespace detail::overlay
#endif // DOXYGEN_NO_DETAIL

}} // namespace geofeatures_boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_GET_TURN_INFO_LA_HPP


/*!
 * \file   pred_corr_solver.cpp
 * \author Robert I A Patterson
 *
 * \brief  Routines for solving with predictor corrector coupling
 *
 *  Copyright (C) 2009 Robert I A Patterson.
 *

 Licence:
    This file is part of "brush".

    brush is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

  Contact:
    Prof Markus Kraft
    Dept of Chemical Engineering
    University of Cambridge
    New Museums Site
    Pembroke Street
    Cambridge
    CB2 3RA
    UK

    Email:       mk306@cam.ac.uk
    Website:     http://como.cheng.cam.ac.uk
 */
#include "pred_corr_solver.h"

#include "reactor1d.h"
#include "local_geometry1d.h"

#include "swp_cell.h"
#include "swp_solver.h"

#include "choose_index.hpp"

#include <stdexcept>
#include <limits>
#include <algorithm>
#include <vector>
#include <list>
#include <cassert>
#include <boost/functional/hash.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/random/bernoulli_distribution.hpp>
#include <boost/random/variate_generator.hpp>

using namespace Brush;

/*!
 * This method has a number of anonymous arguments, which are provided as part
 * of the design for a two-way gas-particle coupled solver.  The arguments are
 * anonymous, because their values are not currently used.  Names are given in
 * the header file and should be copied here when they are needed.
 *
 *@param[in]    reset_chem              Chemical species concentrations as function of spatial position
 *@param[in]    corrector_iterations    Number of corrector iterations to perform during full coupling
 *@param[in]    split_diffusion         True if diffusion is to be simulated via splitting
 *@param[in]    drift_adjustment        Only relevant when split_diffusion is true, see \ref mDiffusionDriftAdjustment
 *@param[in]    split_advection         True if advection is to be simulated via splitting
 *@param[in]    strang_advection        Use Strang splitting between transport and particle processes
 *@param[in]    cstr_transport          Transport between cell centres with CSTR random jumps
 *
 *@todo     Need generalisation to move away from using ResetChemistry
 */
/*
 * Following parameters are skipped from the doxygen documentation, becuase they are not
 * names, which is because they are not yet used.
 * param[in]                            Relative tolerance for ODE solver used for gas phase
 * param[in]                            Absolute tolerance for ODE solver used for gas phase
 */
Brush::PredCorrSolver::PredCorrSolver(const ResetChemistry& reset_chem,
                                      const unsigned corrector_iterations,
                                      const double , const double ,
                                      const bool split_diffusion,
                                      const double drift_adjustment,
                                      const bool split_advection,
                                      const bool strang_splitting,
                                      const bool cstr_transport)
    : mResetChemistry(reset_chem)
    , mCorrectorIterations(corrector_iterations)
    , mDeferralRatio(10.0)
    , mSplitDiffusion(split_diffusion)
    , mDiffusionDriftAdjustment(drift_adjustment)
    , mSplitAdvection(split_advection)
    , mStrangTransportSplitting(strang_splitting)
    , mCSTRTransport(cstr_transport)
{}

/*!
 * Advance the solution to t_stop by means of n_steps equal length application
 * of the predictor corrector algorithm.
 *
 *\param[in,out]        reac        Reactor describing system state
 *\param[in]            t_start     Time from which to advance solution
 *\param[in]            t_stop      Time to which to advance solution
 *\param[in]            n_steps     Number of predictor corrector steps (ie number of splits between transport and particle processes)
 *\param[in]            seed        Value that is unique to this particular time interval and path to use in seeding the RNGs
 */
void Brush::PredCorrSolver::solve(Reactor1d &reac, const double t_start, const double t_stop, const int n_steps,
                                  size_t seed) const {
    const double dt = (t_stop - t_start) / n_steps;

    // Start building a RNG seed for this part of the calculation.  The
    // result should be different in each call, if this function is called
    // repeatedly in order to simulate forward in time.
    boost::hash_combine(seed, t_start);
    boost::hash_combine(seed, t_stop);

    // Build one RNG for each cell, so that the cells can be simulated
    // independently apart from inter-cell transport.
    std::vector<Sweep::rng_type> cellRNGs(reac.getNumCells());
    for(unsigned i = 0; i < reac.getNumCells(); ++i) {
        // Now make a RNG seed that is unique for each cell
        size_t cellSeed = seed;
        boost::hash_combine(cellSeed, i);
        cellRNGs[i].seed(cellSeed);
    }

    for(int i = 1; i <= n_steps; ++i) {
        predictorCorrectorStep(reac, t_start + (i - 1) * dt, t_start + i * dt, cellRNGs);
    }
}

/*!
 * Advance the solution to t_stop by means of a predictor step followed
 * by zero or more corrector iterations
 *
 *\param[in,out]        reac        Reactor describing system state
 *\param[in]            t_start     Time from which to advance solution
 *\param[in]            t_stop      Time to which to advance solution
 *\param[in]            cell_rngs   Vector of independent RNGs, one for each cell
 *
 *\pre  reac.getNumCells() == cell_rngs.size()
 */
void Brush::PredCorrSolver::predictorCorrectorStep(Reactor1d &reac, const double t_start,
                                                   const double t_stop,
                                                   std::vector<Sweep::rng_type>& cell_rngs) const {
    //std::cout << "Predictor corrector step from " << t_start << " to " << t_stop << '\n';

    //=========== Predictor step =====================
    // Advance the chemistry
    solveChemistry(reac, t_start, t_stop);

    solveParticlesByCell(reac, t_start, t_stop, cell_rngs);

//    std::cout << "Particle counts and sample volume at end of predictorCorrectorStep ";
//    for(size_t i = 0; i < reac.getNumCells(); ++i) {
//        std::cout << reac.getCell(i).ParticleCount() << ' ' << reac.getCell(i).SampleVolume();
//    }
//    std::cout << std::endl;

    //=========== Corrector steps ====================
    for(unsigned i = 0; i < mCorrectorIterations; ++i) {
        ;
    }
}

/*!
 * Advance the chemistry part of the solution without performing any particle
 * events.  In this implementation the stop time is ignored and the chemistry
 * is interpolated from the reset object for the reactor time.
 *
 *\param[in,out]        reac        Reactor describing system state
 *\param[in]            t_start     Time from which to advance solution
 *\param[in]            t_stop      Time to which to advance solution
 */
void Brush::PredCorrSolver::solveChemistry(Reactor1d &reac,
                                           const double t_start, const double t_stop) const {

    // Expansion and contraction of the gas phase changes the particle
    // concentration, since the same mass of gas still contains the same
    // number of soot particles.  The change in volume containing a fixed mass
    // of gas is inversely proportional to the change in gas mass density.
    const size_t numCells = reac.getNumCells();
    const Sweep::Mechanism &mech = reac.getParticleMechanism();

    fvector oldDensity(numCells);
    for(size_t i = 0; i != numCells; ++i) {
        Sweep::Cell & mix = reac.getCell(i);
        oldDensity[i] = mix.GasPhase().MassDensity();

//        fvector chemRates;
//    	mech.CalcGasChangeRates(t_start, mix, Geometry::LocalGeometry1d(reac.getGeometry(), i),
//    	                        chemRates);
//    	//create system object (rhs functor) here

    }

    // Update the chemistry to the new time
    reac.ReplaceChemistry(mResetChemistry, true);

    // Update the soot particle concentration to account for the expansion /
    // contraction of the gas.
    for(size_t i = 0; i != numCells; ++i) {
        Sweep::Cell & mix = reac.getCell(i);

        // Collect data for calculating the new value
        mix.AdjustSampleVolume(oldDensity[i] / mix.GasPhase().MassDensity());
    }
}

/*!
 * Advance the particle part of the solution, which may also affect the chemical
 * species concentrations.
 *
 * Note that the flags activating split simulation of particle transport do not
 * disable any diffusion processes that may have been specified in the particle
 * mechanism.
 *
 *\param[in,out]        reac        Reactor describing system state
 *\param[in]            t_start     Time from which to advance solution
 *\param[in]            t_stop      Time to which to advance solution
 *\param[in]            cell_rngs   Vector of independent RNGs, one for each cell
 */
void Brush::PredCorrSolver::solveParticlesByCell(Reactor1d &reac, const double t_start, const double t_stop,
                                                 std::vector<Sweep::rng_type>& cell_rngs) const {

    const size_t numCells = reac.getNumCells();
    const Sweep::Mechanism &mech = reac.getParticleMechanism();

    // Select time step for Strang or first order splitting
    const double firstStop = mStrangTransportSplitting ? (t_start + t_stop) / 2.0 : t_stop;

#pragma omp parallel for schedule(dynamic) ordered
    for(int i = numCells; i > 0; --i) {
        // Get details of cell i
        Sweep::Cell& cell = reac.getCell(i - 1);
        Geometry::LocalGeometry1d geom(reac.getGeometry(), i - 1);

        solveParticlesInOneCell(cell, geom, mech, t_start, firstStop, cell_rngs[i - 1]);
    }

    // Now do the split particle transport, if there is any
    if(mSplitDiffusion || mSplitAdvection) {
        splitParticleTransport(reac, t_start, t_stop, cell_rngs);
    }

    if(mStrangTransportSplitting) {
        // Do the second part of the particle simulation for the Strang splitting
#pragma omp parallel for schedule(dynamic) ordered
        for(int i = numCells; i > 0; --i) {
            // Get details of cell i
            Sweep::Cell& cell = reac.getCell(i - 1);
            Geometry::LocalGeometry1d geom(reac.getGeometry(), i - 1);

            solveParticlesInOneCell(cell, geom, mech, firstStop, t_stop, cell_rngs[i - 1]);
        }
    }

}

/*!
 * Advance the particle part of the solution, which may also affect the chemical
 * species concentrations.
 *
 * @todo  The choice of deferral length should be integrated with Sweep::Solver
 * with a view to using Sweep::Solver::Run()
 *
 *\param[in,out]        cell        Contents of one grid cell
 *\param[in]            geom        Position information regarding adjoining cells
 *\param[in]            mech        Mechanism defining the particle processes
 *\param[in]            t           Time from which to advance solution
 *\param[in]            t_stop      Time to which to advance solution
 *\param[in,out]        rng         RNG to use for the simulation
 */
void Brush::PredCorrSolver::solveParticlesInOneCell(Sweep::Cell &cell, const Geometry::LocalGeometry1d &geom,
                                                    const Sweep::Mechanism &mech, double t, double t_stop,
                                                    Sweep::rng_type &rng) const {
    // Carry out a repeated sequence of jump process simulation (with LPDA updates
    // for particles involved in jumps) followed by LPDA updates for the full
    // population.
    do {
        // Ignore the individual rate term information in the final argument
        fvector dummyVec;
        const double deferredRate = mech.CalcDeferredRateTerms(t, cell ,geom, dummyVec);

        // Calculate both the rates of the individual jump processes and the total
        fvector jumpRates;
        double jumpRate = mech.CalcJumpRateTerms(t, cell, geom, jumpRates);

        // Calculate the latest time at which all particles must be updated
        // with deferred events.
        double maxDeferralEnd = t_stop;
        if(deferredRate / jumpRate > mDeferralRatio) {
            // A large number of deferred events are expected between jump events
            // so force updates to take place more frequently by shortening the
            // time until the next update of all particles.
            maxDeferralEnd = std::min(t_stop, mDeferralRatio * deferredRate);
        }

        // Simulate jumps upto maxDeferralEnd
        // Put a very small tolerance on the comparison
        while(t <= maxDeferralEnd * (1.0 - std::numeric_limits<double>::epsilon())) {
            // Perform one non-deferred event

            jumpRate = mech.CalcJumpRateTerms(t, cell, geom, jumpRates);
            Sweep::Solver::timeStep(t, maxDeferralEnd, cell, geom, mech,
                                    jumpRates, jumpRate, rng);
        }

        // Perform all events from deferred processes.
        if (mech.AnyDeferred()) {
            mech.LPDA(maxDeferralEnd, cell, rng);
        }
    } while(t <= t_stop * (1.0 - std::numeric_limits<double>::epsilon()));

 }

/*!
 *@param[in,out]    reac        Reactor in which particles are being transported
 *@param[in]        t_start     Time at which position was last calculated by splitting
 *@param[in]        t_stop      Time upto which transport is to be simulated
 *@param[in,out]    cell_rngs   Random number generators, one per cell
 */
void Brush::PredCorrSolver::splitParticleTransport(Reactor1d &reac, const double t_start,
                                                   const double t_stop, std::vector<Sweep::rng_type>& cell_rngs) const {
    const size_t numCells = reac.getNumCells();

    // Element i of the outer vector will contain the outflow from cell i of the reactor.
    // Element j of the inner vector will contain particles destined for cell j of the reactor.
    // After this vector of vectors of lists had been populated, it will be necessary to merge
    // the lists of particles being transported into each cell of the reactor.
    std::vector<inflow_lists_vector> inflowLists(numCells, inflow_lists_vector(numCells));
    std::vector<double> statisticalWeights(numCells);
    std::vector<double> cellVolumes(numCells);
    std::vector<double> maxCapacities(numCells);
    std::vector<double> velocities(numCells);

    // Loop over each cell updating particles positions and removing any
    // particles that are moving to new cells.
#pragma omp parallel for schedule(dynamic)
    for(long int i = 0; i < numCells; ++i) {
        Sweep::Cell &mix = reac.getCell(i);

        // Flamelet advection needs some information on adjoining cells to calculate gradients
        const Geometry::LocalGeometry1d geom(reac.getGeometry(), i);
        //std::vector<const Sweep::Cell*> neighbouringCells(2, NULL);
        std::vector<const Sweep::Cell*> neighbouringCells(2);
        neighbouringCells[0] = neighbouringCells[1] = NULL;

        // See if there is a left neighbour
        int neighbourIndex = geom.calcDestination(Geometry::left);
        if(neighbourIndex >= 0)
            neighbouringCells[0] = &(reac.getCell(neighbourIndex));

        // See if there is a right neighbour
        neighbourIndex = geom.calcDestination(Geometry::right);
        if(neighbourIndex >= 0)
            neighbouringCells[1] = &(reac.getCell(neighbourIndex));

        // The weight will be needed when the particles are put back into the cell
        statisticalWeights[i] = 1.0 / mix.SampleVolume();
        cellVolumes[i] = geom.cellVolume();
        maxCapacities[i] = static_cast<double>(mix.Particles().Capacity());
        velocities[i] = mix.GasPhase().Velocity();

        // Remove all particles from the cell and add them
        // to the inflow list for the appropriate cell
        inflowLists[i] = updateParticleListPositions(t_start, t_stop, mix, i,
                                                     reac.getParticleMechanism(),
                                                     reac.getGeometry(), neighbouringCells,
                                                     cell_rngs[i]);
    }// loop over all cells and build up lists of particles that are moving cells

    // There has to be a synchronisation point here, in that parallel replacement of
    // particles in cells cannot begin until the positions and cells of all particles
    // have been calculated.

#pragma omp parallel for schedule(dynamic)
    for(long int i = 0; i < numCells; ++i) {
        // i is the index of the destination cell

        // Build up a list of particle pointers to pass into the cell
        // this variable is local to each loop iteration
        Sweep::PartPtrList partList;

        for(size_t j = 0; j != numCells; ++j) {
            // Adjustment factor for particle statistical weight
            const double weightFactor = (maxCapacities[j] * statisticalWeights[j]) /
                                      (maxCapacities[i] * statisticalWeights[i]);

            const double repeatCountConst =  cellVolumes[j] * maxCapacities[i] * velocities[i]
                                         / cellVolumes[i] / maxCapacities[j] / velocities[j];
//            if(i == j + 1) {
//                std::cout << j << ' ' << " max capac " << maxCapacities[j] << " inv sample vol " << statisticalWeights[j] << " cell volume " << cellVolumes[j] <<std::endl;
//                std::cout << i << ' ' << " max capac " << maxCapacities[i] << " inv sample vol " << statisticalWeights[i] << " cell volume " << cellVolumes[i] <<std::endl;
//                std::cout << j << ' ' << i << ' ' << " repeat count " << repeatCountConst << " weight factor " << weightFactor  << std::endl;
//            }

            // Work through the particles coming from cell j
            std::list<Sweep::Particle*>::const_iterator it = inflowLists[j][i].begin();
            const std::list<Sweep::Particle*>::const_iterator itEnd = inflowLists[j][i].end();
            while(it != itEnd) {
                // Repeat count in destination cell (may be less than one), fractional parts are probability
                // of a particle being added to destination.
                double repeatCount = repeatCountConst;

                (*it)->setStatisticalWeight((*it)->getStatisticalWeight() * weightFactor);

                while(repeatCount > 0) {
                    if(repeatCount > 1) {
                        partList.push_back((*it)->Clone());
                    }
                    else {
                        // Final copy of the particle can be the original, but is only added with
                        // probability repeatCount.
                        boost::random::bernoulli_distribution<double> particleDecider(repeatCount);
                        if(particleDecider(cell_rngs[i]))
                            partList.push_back((*it));
                        else {
                            delete (*it);
                        }
                    }
                    repeatCount -= 1.0;
                }
                ++it;
            }
        }
        if(partList.size() > reac.getCell(i).Particles().Capacity())
            std::cout << "Setting " << partList.size() << " particles on cell with center " << reac.getGeometry().cellCentre(i) << std::endl;

        reac.getCell(i).SetParticles(partList.begin(), partList.end(), statisticalWeights[i], cell_rngs[i]);
    }
}

/*!
 *@param[in]        t_start             Time at which position was last calculated by splitting
 *@param[in]        t_stop              Time at which new position must be calculated
 *@param[in]        mix                 Mixture in which the particle is moving
 *@param[in]        mech                Mechanism specifying calculation of particle transport properties
 *@param[in]        geom                Information on locations of surrounding cells
 *@param[in]        neighbouringCells   Pointers to the contents of surrounding cells
 *@param[in,out]    sp                  Particle requiring updated position
 *@param[in,out]    rng                 Random number generator
 */
void Brush::PredCorrSolver::updateParticlePosition(const double t_start, const double t_stop, const Sweep::Cell &mix,
                                                   const Sweep::Mechanism &mech,
                                                   const Geometry::LocalGeometry1d & geom,
                                                   const std::vector<const Sweep::Cell*> & neighbouringCells,
                                                   Sweep::Particle& sp, Sweep::rng_type &rng) const
{
    double newPosition = sp.getPosition();
    {
        const double dt = t_stop - t_start; //sp.getPositionTime();
        assert(dt >= 0.0);
        if(mSplitAdvection){
            const double velocity = mech.AdvectionVelocity(mix, sp, neighbouringCells, geom);
            newPosition += dt * velocity;
        }
        if(mSplitDiffusion){
            const double diffusionCoeff = mech.DiffusionCoefficient(mix, sp);
            typedef boost::normal_distribution<double> normal_distrib;
            normal_distrib diffusionDistrib(0.0, std::sqrt(diffusionCoeff * dt));
            boost::variate_generator<Sweep::rng_type&, normal_distrib> diffusionGenerator(rng, diffusionDistrib);
            newPosition += diffusionGenerator();

            if(mDiffusionDriftAdjustment > 0.0) {
                // Drift correction to allow for different forms of stochastic integral
                const double drift = mDiffusionDriftAdjustment * mech.GradDiffusionCoefficient(mix, sp, neighbouringCells, geom);
                newPosition += dt * drift;
            }
        }
    }
    sp.setPositionAndTime(newPosition, t_stop);
}

/*!
 * CSTR stands for Continuously Stirred Tank Reactor, a chemical engineering term
 * referring to a perfectly mixed reactor.  Because it is perfectly mixed particles
 * cannot be said to flow through the reactor, rather they have a residence time
 * distribution.  In this case the expected residence time is taken as the length
 * of the reactor divided by the velocity and each particle has a probability of
 * leaving the reactor at each time step.  The probability is given by the ratio
 * of the time step length to the expected residence time.
 *
 *@param[in]        t_start             Time at which position was last calculated by splitting
 *@param[in]        t_stop              Time at which new position must be calculated
 *@param[in]        mix                 Mixture in which the particle is moving
 *@param[in]        mech                Mechanism specifying calculation of particle transport properties
 *@param[in]        geom                Information on locations of surrounding cells
 *@param[in]        neighbouringCells   Pointers to the contents of surrounding cells
 *@param[in,out]    sp                  Particle requiring updated position
 *@param[in,out]    rng                 Random number generator
 *
 *@pre      The expected residence time of the particle must be greater than the time step length.
 */
void Brush::PredCorrSolver::updateParticlePositionCSTR(const double t_start, const double t_stop, const Sweep::Cell &mix,
                                                       const Sweep::Mechanism &mech,
                                                       const Geometry::LocalGeometry1d & geom,
                                                       const std::vector<const Sweep::Cell*> & neighbouringCells,
                                                       Sweep::Particle& sp, Sweep::rng_type &rng) const
{
    // Time over which transport is occurring
    const double dt = t_stop - t_start;

    // Probability according to CSTR that particle leaves cell in this time step
    const double p = mech.AdvectionVelocity(mix, sp, neighbouringCells, geom) * dt / geom.cellVolume();
    assert(p <= 1);

    // The particle will either end up at the centre of this cell or the next
    double newPosition = geom.cellCentre();

    // Generate a sample to decide if the particle leaves
    boost::random::bernoulli_distribution<double> leaveCell(p);
    if(leaveCell(rng))
        // Add the distance to the next cell centre
        newPosition += geom.calcSpacing(Geometry::right);

    sp.setPositionAndTime(newPosition, t_stop);
}

/*!
 *@param[in]        t_start             Time at which position was last calculated by splitting
 *@param[in]        t_stop              Time at which new position must be calculated
 *@param[in,out]    mix                 Mixture from which the particles are moving
 *@param[in]        cell_index          Index of cell containing the particles to be transported
 *@param[in]        mech                Mechanism specifying calculation of particle transport properties
 *@param[in]        geom                Information on locations of surrounding cells
 *@param[in]        neighbouringCells   Pointers to the contents of surrounding cells
 *@param[in,out]    rng                 Random number generator
 *
 *@return       Vector of lists of particles to be transported into other cells
 */
Brush::PredCorrSolver::inflow_lists_vector
  Brush::PredCorrSolver::updateParticleListPositions(const double t_start, const double t_stop, Sweep::Cell &mix,
                                                     const size_t cell_index, const Sweep::Mechanism &mech,
                                                     const Geometry::Geometry1d & geom,
                                                     const std::vector<const Sweep::Cell*> & neighbouringCells,
                                                     Sweep::rng_type &rng) const {
    // Build up the return value in this vector of lists
    inflow_lists_vector outflow(geom.numCells());

    // The local geometry will be needed repeatedly for this cell
    Geometry::LocalGeometry1d localGeom(geom, cell_index);

    // Now go through the particles updating their position
    Sweep::Ensemble::iterator itPart = mix.Particles().begin();
    const Sweep::Ensemble::iterator itPartEnd = mix.Particles().end();
    while(itPart != itPartEnd ) {
        // Actually calculate new position of particle
        if(mCSTRTransport)
            updateParticlePositionCSTR(t_start, t_stop, mix, mech, localGeom, neighbouringCells, **itPart, rng);
        else
            updateParticlePosition(t_start, t_stop, mix, mech, localGeom, neighbouringCells, **itPart, rng);

        // Find the index of the cell into which the particle is moving
        const int destination = geom.containingCell((*itPart)->getPosition());

        if(destination >= 0) {
            // On moving to a new cell reset the coagulation count
            if(static_cast<unsigned>(destination) != cell_index)
               (*itPart)->resetCoagCount();

            // Add the details of the particle to a list ready for inserting
            // into its destination cell
            outflow[destination].push_back(*itPart);

        }
        else {
            // Particle has left simulation domain
            delete *itPart;
            *itPart = NULL;
        }

        // Remove the particle from this cell and move the iterator on
        // to the next particle
        ++itPart;
    } // loop over all particles in cell i updating their position

    // Empty the particle ensemble
    mix.Particles().TakeParticles();

    return outflow;
}