/////////////////////////////////////////////////////////////
//                                                         //
// Copyright (c) 2007-2017 by The University of Queensland //
// Centre for Geoscience Computing                         //
// http://earth.uq.edu.au/centre-geoscience-computing      //
//                                                         //
// Primary Business: Brisbane, Queensland, Australia       //
// Licensed under the Open Software License version 3.0    //
// http://www.apache.org/licenses/LICENSE-2.0              //
//                                                         //
/////////////////////////////////////////////////////////////

#include "MNTable2D.h"

// --- STL includes ---
#include <utility>

using namespace std;

using std::make_pair;

// --- System includes ---
#include <cmath>
#include <cstdlib> 
#include <iostream> 
#include <fstream> 

using std::ceil;
using std::endl;
using std::rand;

int MNTable2D::s_output_style=0; 
double MNTable2D::s_small_value=1e-7;

MNTable2D::MNTable2D():m_write_prec(10)
{
}

/*!
  construct MNTable2D

  \param MinPt minimum point (z component ignored)
  \param MaxPt maximum point (z component ignored)
  \param cd cell dimension
  \param ngroups initial number of particle groups
*/
MNTable2D::MNTable2D(const Vector3& MinPt,const Vector3& MaxPt,double cd,unsigned int ngroups)
{
  m_celldim=cd;
  m_ngroups=ngroups;

  // get number of cells
  Vector3 req_size=MaxPt-MinPt; // requested size
  m_nx=int(ceil(req_size.X()/m_celldim))+2;
  m_ny=int(ceil(req_size.Y()/m_celldim))+2;

  // add padding
  m_x0=MinPt.x()-m_celldim;
  m_y0=MinPt.y()-m_celldim;
   
  // allocate cell array
  m_data=new MNTCell[m_nx*m_ny];
  for(int i=0;i<m_nx*m_ny;i++) {
    m_data[i].SetNGroups(m_ngroups);
  }

  // periodicity
  m_x_periodic=0;
  m_y_periodic=0;

  // default output precision
  m_write_prec=10;
}

/*!
  destruct MNTable2D
*/
MNTable2D::~MNTable2D()
{
  if(m_data!=NULL) delete[] m_data;
}


/*!
  Increase the number of particle groups. Do nothing if the number is already large enough

  \param ngroups the new number of particle groups
*/
void MNTable2D::GrowNGroups(unsigned int ngroups)
{
  if(ngroups>m_ngroups){
    m_ngroups=ngroups;
    for(int i=0;i<m_nx*m_ny;i++) {
      m_data[i].SetNGroups(m_ngroups);
    }
  }
}

/*!
  get the cell index for a given position

  \param Pos the position
  \return the cell index if Pos is inside the table, -1 otherwise
*/
int MNTable2D::getIndex(const Vector3& Pos) const
{
  int ret;

  int ix=int(floor((Pos.x()-m_x0)/m_celldim));
  int iy=int(floor((Pos.y()-m_y0)/m_celldim));

  // check if pos is in table excl. padding
  if((ix>0) && (ix<m_nx-1) && (iy>0) && (iy<m_ny-1)){
    ret=idx(ix,iy);
  } else {
    ret=-1;
  }
  
  return ret;
}

/*!
  insert sphere

  \param S the Sphere
  \param gid the group id
*/
bool MNTable2D::insert(const Sphere& S,unsigned int gid)
{
  bool res;

  int id=getIndex(S.Center());
//   std::cout << "center : " << S.Center() << " index : " << id << std::endl; 
  if((id!=-1) && (gid<m_ngroups)){
    m_data[id].insert(S,gid);
    res=true;
  } else {
    res=false;
  }

  return res;
}


/*!
  insert sphere if it doesn't collide with other spheres

  \param S the Sphere
  \param gid the group id
*/
bool  MNTable2D::insertChecked(const Sphere& S,unsigned int gid,double tol)
{
 bool res;

  int id=getIndex(S.Center());
//   std::cout << "center : " << S.Center() << " index : " << id << std::endl; 
  if((id!=-1) && (gid<m_ngroups)){
    const multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(S.Center(),S.Radius()-s_small_value,gid);
    if(close_spheres.size()==0){
      m_data[id].insert(S,gid);
      res=true;
    } else res=false;
  } else {
    res=false;
  }

  return res;
}

/*!
  get the closest spheres to a given point

  \param p the point
  \param nmax the maximum number of spheres returned
*/
const multimap<double,const Sphere*> MNTable2D::getSpheresClosestTo(const Vector3& p,unsigned int nmax) const
{
  multimap<double,const Sphere*> res;

  for(int i=-1;i<=1;i++){
    for(int j=-1;j<=1;j++){
      Vector3 pos=p+Vector3(double(i)*m_celldim,double(j)*m_celldim,0.0);
      int idx=getIndex(pos);
      if(idx!=-1){
	multimap<double,const Sphere*> smap=m_data[idx].getSpheresClosestTo(p,nmax);
	res.insert(smap.begin(),smap.end());
      }
    }
  }
  
  return res;
}

/*!
  get all spheres from one group within a given distance to a point

  \param P the point
  \param d max. distance between the point and the surface of the sphere 
  \param gid the group id

  \warning no check for validity of gid
*/
const multimap<double,const Sphere*> MNTable2D::getSpheresFromGroupNear(const Vector3& P,double d,int gid) const
{
  multimap<double,const Sphere*> res;
  
  for(int i=-1;i<=1;i++){
    for(int j=-1;j<=1;j++){
      Vector3 np=P+Vector3(double(i)*m_celldim,double(j)*m_celldim,0.0);
      int idx=getIndex(np);
      if(idx!=-1){
	multimap<double,const Sphere*> v=m_data[idx].getSpheresFromGroupNear(P,d,gid);
	res.insert(v.begin(),v.end());
      }
    }
  }

  return res;
}

/*!
  get vector of pointers to all spheres of a group

  \param gid goup Id
*/
const vector<const Sphere*> MNTable2D::getAllSpheresFromGroup(int gid) const
{
  vector<const Sphere*> res;

  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
        vector<const Sphere*> cr=m_data[idx(i,j)].getAllSpheresFromGroup(gid);
        res.insert(res.end(),cr.begin(),cr.end());
    }
  }

  return res;
}

/*!
  get the sphere within a given group closest to given sphere

  \param s the sphere
  \param gid the group
*/
const Sphere* MNTable2D::getClosestSphereFromGroup(const Sphere& s,int gid) const
{
  const Sphere* res=NULL;
  int max_dim=m_nx>m_ny ? m_nx : m_ny;
  int found=max_dim;
  double dist=max_dim*m_celldim;

  // search within the same cell 
  int idx=getIndex(s.Center());
  const Sphere* v=m_data[idx].getClosestSphereFromGroup(s,gid,2.0*max_dim);
  if(v!=NULL){
    double d=(v->Center()-s.Center()).norm()-s.Radius();
    found=1;
    if(d<dist){
      res=v;
      dist=d;
    }
  }
  
  // search outside origin cell 
  // get bigger of the 2 table dimensions
  int range=1;
  while((range<max_dim) && (range<found+1)){
    for(int i=-range;i<=range;i++){
      for(int j=-range;j<=range;j++){
	Vector3 np=s.Center()+Vector3(double(i)*m_celldim,double(j)*m_celldim,0.0);
	int idx=getIndex(np);
	if(idx!=-1){
	  const Sphere* v=m_data[idx].getClosestSphereFromGroup(s,gid,dist);
	  if(v!=NULL){
	    double d=(v->Center()-s.Center()).norm()-s.Radius();
	    found=range+1;
	    if(d<dist){
	      res=v;
	      dist=d;
	    }
	  }
	}
      }
    }
    range++;

  }
  return res;
}

/*!
  get the sphere within a given group closest to point 

  \param p the point
  \param gid the group
*/
Sphere* MNTable2D::getClosestSphereFromGroup(const Vector3& p,int gid) const
{
  Sphere* res=NULL;
  int max_dim=m_nx>m_ny ? m_nx : m_ny;
  int found=max_dim;
  double dist=max_dim*m_celldim;

  // search within the same cell 
  int idx=getIndex(p);
  Sphere* v=m_data[idx].getClosestSphereFromGroup(p,gid,2.0*max_dim);
  if(v!=NULL){
    double d=(v->Center()-p).norm();
    found=1;
    if(d<dist){
      res=v;
      dist=d;
    }
  }
  
  // search outside origin cell 
  // get bigger of the 2 table dimensions
  int range=1;
  while((range<max_dim) && (range<found+1)){
    for(int i=-range;i<=range;i++){
      for(int j=-range;j<=range;j++){
	Vector3 np=p+Vector3(double(i)*m_celldim,double(j)*m_celldim,0.0);
	int idx=getIndex(np);
	if(idx!=-1){
	  Sphere* v=m_data[idx].getClosestSphereFromGroup(p,gid,dist);
	  if(v!=NULL){
	    double d=(v->Center()-p).norm();
	    found=range+1;
	    if(d<dist){
	      res=v;
	      dist=d;
	    }
	  }
	}
      }
    }
    range++;

  }
  return res;
}

/*!
  convenience function which calls getClosestSphereFromGroup and returns the
  tag of the found sphere or -1 if none is found 

  \param s the sphere
  \param gid the group
*/
int MNTable2D::getTagOfClosestSphereFromGroup(const Sphere& s,int gid) const
{
  int res;

  const Sphere* sp=getClosestSphereFromGroup(s,gid);
  if(sp!=NULL){
    res=sp->Tag();
  }else{
    res=-1;
  }

  return res;
}

/*!
  tag the spheres in a group A according to the tag of the closest spheres from a another group B

  \param gid1 the group Id A
  \param gid1 the group Id B
*/

void  MNTable2D::tagParticlesToClosest(int gid1, int gid2)
{
   for(int i=0;i<m_nx-1;i++){
     for(int j=0;j<m_ny-1;j++){
      vector<Sphere*> v=m_data[idx(i,j)].getAllSpheresFromGroupNC(gid1);
      for(vector<Sphere*>::iterator iter=v.begin();
	  iter!=v.end();
	  iter++){
	int t=getTagOfClosestSphereFromGroup(*(*iter),gid2);
	(*iter)->setTag(t);
      }
    }
  }
}

/*!
  tag the closest spheres to a given point

  \param p the point
  \param dist max distance
  \param gid the group id
  \param tag the tag
*/
void MNTable2D::tagParticlesNear(const Vector3& p,double dist,int gid, int tag)
{
  for(int i=-1;i<=1;i++){
    for(int j=-1;j<=1;j++){
      Vector3 np=p+Vector3(double(i)*m_celldim,double(j)*m_celldim,0.0);
      int idx=getIndex(np);
      if(idx!=-1){
	m_data[idx].tagSpheresNear(p,dist,gid,tag);
      }
    }
  }
}

/*!
  tag all particles inside a specified volume

  \param V the volume
  \param tag the tag
  \param gid group Id
*/
void MNTable2D::tagParticlesInVolume(const AVolume& V,int tag,unsigned int gid)
{
  for(int i=0;i<m_nx;i++){
    for(int j=0;j<m_ny;j++){
      vector<Sphere*> vs=m_data[idx(i,j)].getSpheresInVolume(&V,gid);
      for(vector<Sphere*>::iterator iter=vs.begin();
	  iter!=vs.end();
	  iter++){
	(*iter)->setTag(tag);
      }
    }
  }
}


/*!
  tag the closest sphere to a given point

  \param p the point
  \param gid the group id
  \param tag the tag
*/
void MNTable2D::tagClosestParticle(const Vector3& p,int gid, int tag)
{
 Sphere* sp=getClosestSphereFromGroup(p,gid);
  if(sp!=NULL){
    sp->setTag(tag);
  }
}



/*!
  insert sphere if it doesn't collide with other spheres

  \param S the Sphere
  \param gid the group id
*/
bool  MNTable2D::checkInsertable(const Sphere& S,unsigned int gid)
{
 bool res;

  int id=getIndex(S.Center());
  //  std::cout << "center : " << S.Center() << " index : " << id << std::endl; 
  if((id!=-1) && (gid<m_ngroups)){
    multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(S.Center(),S.Radius()-s_small_value,gid);
    if(close_spheres.size()==0){ 
      res=true;
    } else res=false;
  } else {
    res=false;
  }

  return res;
}

/*
 get the sum of the area/volume of all particles in a group

  \param gid the group id
*/
double MNTable2D::getSumVolume(int gid)
{
  double res=0.0;

  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      res+=m_data[idx(i,j)].getSumVolume2D(gid);
    }
  }

  return res;
}

/*
 get the number of particles in a group

  \param gid the group id
*/
int  MNTable2D::getNrParticles(int gid)
{
  int res=0.0;

  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      res+=m_data[idx(i,j)].getNrParticles(gid);
    }
  }

  return res;
}

/*!
 */
void MNTable2D::removeTagged(int gid,int tag,int mask)
{
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      m_data[idx(i,j)].removeTagged(gid,tag,mask);
    }
  }
}



/*!
  generate bonds between particles of a group

  \param gid the group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param btag bond tag
*/
void MNTable2D::generateBonds(int gid,double tol,int btag)
{
  //std::cout << "MNTable2D::generateBonds( " << gid << " , " << tol << " , " << btag << " )" << std::endl;  // loop over all cells 
  // loop over all cells 
  for(int i=0;i<m_nx-1;i++){
    for(int j=0;j<m_ny-1;j++){
      int id=idx(i,j);
      for(int ii=-1;ii<=1;ii++){
	for(int jj=-1;jj<=1;jj++){
	  int id2=idx(i+ii,j+jj);
	  vector<pair<int,int> > bonds;
	  if((ii==0) && (jj==0) && (i!=0) && (j!=0)){ // intra-cell, not for boundary
	    bonds=m_data[id].getBonds(gid,tol);
	  } else if(id2>id){ // inter-cell
   	    bonds=m_data[id].getBonds(gid,tol,m_data[id2]);
	  } 
	  for(vector<pair<int,int> >::iterator iter=bonds.begin();
	      iter!=bonds.end();
	      iter++){	    
	    if(m_bonds[btag].find(*iter)==m_bonds[btag].end()){
	      m_bonds[btag].insert(*iter);
	    }
	  }
	}
      }
    }
  }
}

/*!
  generate bonds between particles with identical particle tag of a group

  \param gid the group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param btag bond tag
  \param ptag the particle tag
  \param mask the mask for the particle tag
*/
void MNTable2D::generateBondsWithMask(int gid,double tol,int btag, int ptag, int mask)
{
  //std::cout << "MNTable2D::generateBonds( " << gid << " , " << tol << " , " << btag << 
  //  " " << ptag <<" " << mask<<" )" << std::endl;  // loop over all cells 
  // loop over all cells 
  for(int i=0;i<m_nx-1;i++){
    for(int j=0;j<m_ny-1;j++){
      int id=idx(i,j);
      for(int ii=-1;ii<=1;ii++){
	for(int jj=-1;jj<=1;jj++){
	  int id2=idx(i+ii,j+jj);
	  vector<pair<int,int> > bonds;
	  if((ii==0) && (jj==0) && (i!=0) && (j!=0)){ // intra-cell, not for boundary 
	    bonds=m_data[id].getBonds(gid,tol,ptag,mask);
	  } else if(id2>id){ // inter-cell
   	    bonds=m_data[id].getBonds(gid,tol,m_data[id2],ptag,mask);
	  } 
	  for(vector<pair<int,int> >::iterator iter=bonds.begin();
	      iter!=bonds.end();
	      iter++){	    
	    m_bonds[btag].insert(*iter);
	  }
	}
      }
    }
  }
}


/*!
  generate bonds between particles with given particle tag

  \param gid the group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param btag bond tag
  \param ptag1 the 1st particle tag
  \param mask1 the mask for the 1st particle tag
  \param ptag2 the 2nd particle tag
  \param mask2 the mask for the 2nd particle tag
*/
void MNTable2D::generateBondsTaggedMasked(int gid,double tol,int btag,int ptag1,int mask1,int ptag2,int mask2)
{
  for(int i=0;i<m_nx-1;i++){
    for(int j=0;j<m_ny-1;j++){
      int id=idx(i,j);
      for(int ii=-1;ii<=1;ii++){
	for(int jj=-1;jj<=1;jj++){
	  int id2=idx(i+ii,j+jj);
	  vector<pair<int,int> > bonds;
	  if((ii==0) && (jj==0) && (i!=0) && (j!=0)){ // intra-cell, not for boundary 
	    bonds=m_data[id].getBondsTaggedMasked(gid,tol,ptag1,mask1,ptag2,mask2);
	  } else if(id2>id){ // inter-cell
   	    bonds=m_data[id].getBondsTaggedMasked(gid,tol,m_data[id2],ptag1,mask1,ptag2,mask2);
	  } 
	  for(vector<pair<int,int> >::iterator iter=bonds.begin();
	      iter!=bonds.end();
	      iter++){	    
	    m_bonds[btag].insert(*iter);
	  }
	}
      }
    }
  }
}

/*!
  generate bonds between particles with identical particle tag of a group with a given
  probablility (0..1)

  \param gid the group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param prob probablility of bond generation
  \param btag bond tag
  \param ptag the particle tag
  \param mask the mask for the particle tag
*/
void MNTable2D::generateRandomBonds(int gid,double tol,double prob,int btag, int ptag, int mask)
{
  //std::cout << "MNTable2D::generateBonds( " << gid << " , " << tol << " , " << btag << 
  //  " " << ptag <<" " << mask<<" )" << std::endl;  // loop over all cells 
  // loop over all cells 
  for(int i=0;i<m_nx-1;i++){
    for(int j=0;j<m_ny-1;j++){
      int id=idx(i,j);
      for(int ii=-1;ii<=1;ii++){
	for(int jj=-1;jj<=1;jj++){
	  int id2=idx(i+ii,j+jj);
	  vector<pair<int,int> > bonds;
	  if((ii==0) && (jj==0) && (i!=0) && (j!=0)){ // intra-cell, not for boundary 
	    bonds=m_data[id].getBonds(gid,tol,ptag,mask);
	  } else if(id2>id){ // inter-cell
   	    bonds=m_data[id].getBonds(gid,tol,m_data[id2],ptag,mask);
	  } 
	  for(vector<pair<int,int> >::iterator iter=bonds.begin();
	      iter!=bonds.end();
	      iter++){	    
	    double rn=((double)(rand())/(double)(RAND_MAX));
	    if(rn<prob) m_bonds[btag].insert(*iter);
	  }
	}
      }
    }
  }
}

/*!
  generate bonds between particles of a group

  \param gid the group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param btag1 tag for bonds within clusters (same particle tag)
  \param btag2 tag for bonds betweem clusters (different particle tag)
*/
void MNTable2D::generateClusterBonds(int gid,double tol,int btag1, int btag2)
{
  //std::cout << "MNTable2D::generateBonds( " << gid << " , " << tol << " , " << btag << " )" << std::endl;  // loop over all cells 
  // loop over all cells 
  for(int i=0;i<m_nx-1;i++){
    for(int j=0;j<m_ny-1;j++){
      int id=idx(i,j);
      for(int ii=-1;ii<=1;ii++){
	for(int jj=-1;jj<=1;jj++){
	  int id2=idx(i+ii,j+jj);
	  vector<pair<int,int> > same_bonds;
	  vector<pair<int,int> > diff_bonds;
	  if((ii==0) && (jj==0) && (i!=0) && (j!=0)){ // intra-cell, not for boundary
	    same_bonds=m_data[id].getBondsSame(gid,tol);
	    diff_bonds=m_data[id].getBondsDiff(gid,tol);
	  } else if(id2>id){ // inter-cell
   	    same_bonds=m_data[id].getBondsSame(gid,tol,m_data[id2]);
   	    diff_bonds=m_data[id].getBondsDiff(gid,tol,m_data[id2]);
	  } 
	  // insert intra-cluster bonds
	  for(vector<pair<int,int> >::iterator iter=same_bonds.begin();
	      iter!=same_bonds.end();
	      iter++){	    
	    if(m_bonds[btag1].find(*iter)==m_bonds[btag1].end()){
	      m_bonds[btag1].insert(*iter);
	    }
	  }
	  // insert inter-cluster bonds
	  for(vector<pair<int,int> >::iterator iter=diff_bonds.begin();
	      iter!=diff_bonds.end();
	      iter++){	    
	    if(m_bonds[btag2].find(*iter)==m_bonds[btag2].end()){
	      m_bonds[btag2].insert(*iter);
	    }
	  }
	}
      }
    }
  }
}



/*!
  insert bond between particles with given ID

  \param id1 id of 1st particle
  \param id2 id of 2nd particle
  \param btag  bond tag
*/
void MNTable2D::insertBond(int id1,int id2,int btag)
{
  if(id1<id2){
    m_bonds[btag].insert(make_pair(id1,id2));
  } else {
    m_bonds[btag].insert(make_pair(id2,id1));
  }
}

/*!
  tag all particles close to a line

  \param L the line
  \param dist max. distance
  \param tag the tag
  \param gid group Id
*/
void  MNTable2D::tagParticlesAlongLine(const Line2D& L,double dist,int tag,unsigned int gid)
{ 
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      vector<Sphere*> vs=m_data[idx(i,j)].getSpheresNearObject(&L,dist,gid);
      for(vector<Sphere*>::iterator iter=vs.begin();
	  iter!=vs.end();
	  iter++){
	(*iter)->setTag(tag);
      }
    }
  }
}

/*!
  tag all particles close to a line

  \param L the line
  \param dist max. distance
  \param tag the tag
  \param the tag mask
  \param gid group Id
*/
void  MNTable2D::tagParticlesAlongLineWithMask(const Line2D& L,double dist,int tag,int mask,unsigned int gid)
{ 
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      vector<Sphere*> vs=m_data[idx(i,j)].getSpheresNearObject(&L,dist,gid);
      for(vector<Sphere*>::iterator iter=vs.begin();
	  iter!=vs.end();
	  iter++){
	int oldtag=(*iter)->Tag();
	int newtag=(tag & mask) | (oldtag & ~mask);
	(*iter)->setTag(newtag);
      }
    }
  }
}

/*!
  tag all particles close to a line segment

  \param L the line segment
  \param dist max. distance
  \param tag the tag
  \param mask the tag mask
  \param gid group Id
*/
void MNTable2D::tagParticlesAlongLineSegment(const LineSegment2D& L,double dist,int tag ,int mask, unsigned int gid)
{ 
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      vector<Sphere*> vs=m_data[idx(i,j)].getSpheresNearObject(&L,dist,gid);
      for(vector<Sphere*>::iterator iter=vs.begin();
	  iter!=vs.end();
	  iter++){
	int oldtag=(*iter)->Tag();
	int newtag=(tag & mask) | (oldtag & ~mask);
	(*iter)->setTag(newtag);
      }
    }
  }
}

/*!
  break bonds along a line segment

  \param L the line segment
  \param dist max. distance from the line
  \param tag the bond tag
  \param gid group Id
*/
void MNTable2D::breakBondsAlongLineSegment(const LineSegment2D& L,double dist,int tag, unsigned int gid)
{
  // get  particles near line
  set<int> near_particle_set;

  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      vector<Sphere*> vs=m_data[idx(i,j)].getSpheresNearObject(&L,dist,gid);
      for(vector<Sphere*>::iterator iter=vs.begin();
	  iter!=vs.end();
	  iter++){
	near_particle_set.insert((*iter)->Id());
      }
    }
  }
   
  // for all bonds with tag
  set<pair<int,int> >::iterator iter=m_bonds[tag].begin();
  while(iter!=m_bonds[tag].end()){
    // check if both particles near line 
    if(near_particle_set.find(iter->first)!=near_particle_set.end() &&
       near_particle_set.find(iter->second)!=near_particle_set.end()){
      // remove
      set<pair<int,int> >::iterator iter_t=iter;
      iter++;
      m_bonds[tag].erase(iter_t);
    } else {
      iter++;
    }
  }
}

/*!
  Set output style

  \param style the output style: 0 = debug, 1 = .geo
*/
void MNTable2D::SetOutputStyle(int style)
{
  MNTable2D::s_output_style=style;
}

/*!
  Set output precision

  \param prec number of significant digits
*/
void MNTable2D::SetOutputPrecision(int prec)
{
  m_write_prec=prec;
}

/*!
  Output the content of a MNTable2D to an ostream. The output format depends on the value of MNTable2D::s_output_style (see  MNTable2D::SetOutputStyle). If it is set to 0, output suitable for debugging is generated, if it is set to 1 output in the esys .geo format is generated. If MNTable2D::s_output_style is set to 2, the output format is VTK-XML.

  \param ost the output stream
  \param T the table
*/
ostream& operator << (ostream& ost,const MNTable2D& T)
{
  if(MNTable2D::s_output_style==0){ // debug style
    // don't print padding
    MNTCell::SetOutputStyle(0);
    for(int i=1;i<T.m_nx;i++){
      for(int j=1;j<T.m_ny-1;j++){
	ost << "=== Cell " << i << " , " << j << " === " << endl;
	ost << T.m_data[T.idx(i,j)];
      }
    }
  } else if (MNTable2D::s_output_style==1){ // geo style
    int nparts=0;
    for(int i=1;i<T.m_nx-1;i++){
      for(int j=1;j<T.m_ny-1;j++){
	nparts+=T.m_data[T.idx(i,j)].NParts();
      }
    }
    // header
    ost << "LSMGeometry 1.2" << endl;
    ost << "BoundingBox " << T.m_x0+T.m_celldim << " " <<  T.m_y0+T.m_celldim << " 0.0 " << T.m_x0+double(T.m_nx-1)*T.m_celldim << " " <<  T.m_y0+double(T.m_ny-1)*T.m_celldim << " 0.0 " << endl;
    ost << "PeriodicBoundaries "  << T.m_x_periodic << " "  << T.m_y_periodic << " 0" << endl;
    ost << "Dimension 2D" << endl;
    // particles
    ost << "BeginParticles" << endl;
    ost << "Simple" << endl;
    ost << nparts << endl;
    MNTCell::SetOutputStyle(1);
     for(int i=1;i<T.m_nx-1;i++){
      for(int j=1;j<T.m_ny-1;j++){
	ost <<  T.m_data[T.idx(i,j)];
      }
    }
    ost << "EndParticles" << endl;
    
    // bonds
    ost << "BeginConnect" << endl;
    // sum bonds
    int nbonds=0;
    for(map<int,set<pair<int,int> > >::const_iterator iter=T.m_bonds.begin();
	iter!=T.m_bonds.end();
	iter++){
      nbonds+=iter->second.size();
    }
    ost << nbonds << endl;
    for(map<int,set<pair<int,int> > >::const_iterator iter=T.m_bonds.begin();
	iter!=T.m_bonds.end();
	iter++){
      for(set<pair<int,int> >::const_iterator v_iter=iter->second.begin();
	  v_iter!=iter->second.end();
	  v_iter++){
	ost << v_iter->first << " " << v_iter->second << " " << iter->first << endl;
      }
    }
    ost << "EndConnect" << endl;
  } else if (MNTable2D::s_output_style==2){ // vtk-xml
    T.WriteAsVtkXml(ost);
  }

  return ost;
}

/*!
  output content as VTK-XML format

  \param vtkfile the output stream
*/
void MNTable2D::WriteAsVtkXml(ostream& vtkfile) const
{
  // inverse mapping index<->particle id
  map<int,int> inv_map;

  int pcount=0;
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      vector<int> ids=m_data[idx(i,j)].getIdList();
      for(vector<int>::iterator iter=ids.begin();
	  iter!=ids.end();
	  iter++){
	inv_map.insert(make_pair(*iter,pcount));
	pcount++;
      }
    }
  }
  
  // get bond count
  int bcount=0;

  for(map<int,set<pair<int,int> > >::const_iterator iter=m_bonds.begin();
      iter!=m_bonds.end();
      iter++){
    bcount+=iter->second.size();
  }
  // write header 
  vtkfile << "<VTKFile type=\"UnstructuredGrid\" version=\"0.1\">\n";
  vtkfile << "<UnstructuredGrid>\n";
  int nparts=0;
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      nparts+=m_data[idx(i,j)].NParts();
    }
  }
  vtkfile << "<Piece NumberOfPoints=\"" << nparts << "\" NumberOfCells=\"" << bcount << "\">\n";
  // write particle pos
  vtkfile << "<Points>\n";
  vtkfile << "<DataArray NumberOfComponents=\"3\" type=\"Float64\" format=\"ascii\">\n";
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      m_data[idx(i,j)].writePositions(vtkfile);
    }
  }
  vtkfile << endl;
  vtkfile << "</DataArray>\n";
  vtkfile << "</Points>\n";

  // --- write particle data ---
  // radius
  vtkfile << "<PointData Scalars=\"radius\">\n";
  vtkfile << "<DataArray type=\"Float64\" Name=\"radius\" NumberOfComponents=\"1\" format=\"ascii\">\n";
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      m_data[idx(i,j)].writeRadii(vtkfile);
    }
  }
  vtkfile << endl;
  vtkfile << "</DataArray>\n";

  // ID
  vtkfile << "<DataArray type=\"Int32\" Name=\"particleID\" NumberOfComponents=\"1\" format=\"ascii\">\n";
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      m_data[idx(i,j)].writeIDs(vtkfile);
    }
  }
  vtkfile << endl;
  vtkfile << "</DataArray>\n";

  // Tag
  vtkfile << "<DataArray type=\"Int32\" Name=\"particleTag\" NumberOfComponents=\"1\" format=\"ascii\">\n";
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      m_data[idx(i,j)].writeTags(vtkfile);
    }
  }
  vtkfile << endl;
  vtkfile << "</DataArray>\n";  

  vtkfile << "</PointData>\n";

  // --- write cells section ---
  // connections
  vtkfile << "<Cells>\n";
  vtkfile << "<DataArray type=\"Int32\" NumberOfComponents=\"1\" Name=\"connectivity\" format=\"ascii\">\n";
  for(map<int,set<pair<int,int> > >::const_iterator iter=m_bonds.begin();
      iter!=m_bonds.end();
      iter++){
    for(set<pair<int,int> >::const_iterator iter_b=iter->second.begin();
	iter_b!=iter->second.end();
	iter_b++){
      vtkfile << inv_map[iter_b->first] << " " << inv_map[iter_b->second] << endl;
    }
  }

  vtkfile << "</DataArray>\n";
  // offsets
   vtkfile << "<DataArray type=\"Int32\" NumberOfComponents=\"1\" Name=\"offsets\" format=\"ascii\">\n";
  for (size_t i = 1; i < bcount*2; i+=2) vtkfile << i+1 << "\n";
  vtkfile << "</DataArray>\n";

  // element type
  vtkfile << "<DataArray type=\"UInt8\" NumberOfComponents=\"1\" Name=\"types\" format=\"ascii\">\n";
  const int CELL_LINE_TYPE = 3;
  for (size_t i = 0; i < bcount; i++)   vtkfile << CELL_LINE_TYPE << endl;
  vtkfile << "</DataArray>\n";
  vtkfile << "</Cells>\n";
  
  // cell data
  vtkfile << "<CellData>\n";  
  vtkfile << "<DataArray type=\"Int32\" Name=\"bondTag\" NumberOfComponents=\"1\" format=\"ascii\">\n";
  for(map<int,set<pair<int,int> > >::const_iterator iter=m_bonds.begin();
      iter!=m_bonds.end();
      iter++){
    for(set<pair<int,int> >::const_iterator iter_b=iter->second.begin();
	iter_b!=iter->second.end();
	iter_b++){
      vtkfile << iter->first << endl;
    }
  }
  vtkfile << "</DataArray>\n";  
  vtkfile << "</CellData>\n";
  // write footer
  vtkfile << "</Piece>\n";
  vtkfile << "</UnstructuredGrid>\n";
  vtkfile << "</VTKFile>\n";
}

void MNTable2D::write(char *filename, int outputStyle)
{
   ofstream outFile;
   
   // set output precision
   outFile.precision(m_write_prec);

   SetOutputStyle(outputStyle);

   outFile.open(filename);
   outFile << *(this);
   outFile.close();
}

boost::python::list MNTable2D::getSphereListFromGroup(int gid) const
{
   boost::python::list l;
   vector<const Sphere*> sphere_vector;

   sphere_vector = getAllSpheresFromGroup(gid);

   for (vector<const Sphere*>::iterator iter=sphere_vector.begin(); iter!= sphere_vector.end(); iter++) {
      l.append(*(*(iter)));
   }

   return l;
}

/*!
  return the bonds of a given tag as list of <int,int> pairs

  \param btag the bond tag
*/
boost::python::list MNTable2D::getBondList(int btag)
{
   boost::python::list l;
   
   for(set<pair<int,int> >::const_iterator iter=m_bonds[btag].begin();
       iter!=m_bonds[btag].end();
       iter++){
     l.append(boost::python::make_tuple(iter->first,iter->second));
   }
   return l;
}
