/*
 *
 * gengraph - generation of random simple connected graphs with prescribed
 *            degree sequence
 *
 * Copyright (C) 2006  Fabien Viger
 *
 * This program 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 3 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, see <http://www.gnu.org/licenses/>.
 */
#include "gengraph_definitions.h"
#include <cassert>
#include <cstdlib>
#include <cstdio>
#include <cmath>

#include "gengraph_qsort.h"
#include "gengraph_hash.h"
#include "gengraph_degree_sequence.h"
#include "gengraph_graph_molloy_hash.h"

#include "config.h"
#include "igraph_math.h"
#include "igraph_constructors.h"
#include "igraph_error.h"
#include "igraph_statusbar.h"
#include "igraph_progress.h"

namespace gengraph {

//_________________________________________________________________________
void graph_molloy_hash::compute_neigh() {
  int *p = links;
  for(int i=0; i<n; i++) {
    neigh[i] = p;
    p += HASH_SIZE(deg[i]);
  }
}

//_________________________________________________________________________
void graph_molloy_hash::compute_size() {
  size = 0;
  for(int i=0; i<n; i++) size += HASH_SIZE(deg[i]);
}

//_________________________________________________________________________
void graph_molloy_hash::init() {
  for(int i=0; i<size; i++) links[i]=HASH_NONE;
}
  
//_________________________________________________________________________
graph_molloy_hash::graph_molloy_hash(degree_sequence &degs) {
  igraph_status("Allocating memory for graph...", 0);
  int s = alloc(degs);
  igraph_statusf("%d bytes allocated successfully\n", 0, s);
}

//_________________________________________________________________________
int graph_molloy_hash::alloc(degree_sequence &degs) {
  n = degs.size();
  a = degs.sum();
  assert(a%2 == 0);

  deg = degs.seq();
  compute_size();
  deg = new int[n+size];
  if(deg==NULL) return 0;
  int i;
  for(i=0; i<n; i++) deg[i]=degs[i];
  links = deg+n;
  init();
  neigh = new int*[n];
  if(neigh==NULL) return 0;
  compute_neigh();
  return sizeof(int *)*n + sizeof(int)*(n+size);
}

//_________________________________________________________________________
graph_molloy_hash::~graph_molloy_hash() {
  if(deg!=NULL) delete[] deg;
  if(neigh!=NULL) delete[] neigh;
  deg = NULL;
  neigh = NULL;
}

//_________________________________________________________________________
graph_molloy_hash::graph_molloy_hash(int *svg) {
  // Read n
  n = *(svg++);
  // Read a
  a = *(svg++);
  assert(a%2 == 0);
  // Read degree sequence
  degree_sequence dd(n, svg);
  // Build neigh[] and alloc links[]
  alloc(dd);
  dd.detach();
  // Read links[]
  restore(svg+n);
}

//_________________________________________________________________________
int *graph_molloy_hash::hard_copy() {
  int *hc = new int[2+n+a/2]; // to store n,a,deg[] and links[]
  hc[0] = n;
  hc[1] = a;
  memcpy(hc+2,deg,sizeof(int)*n);
  int *p = hc+2+n;
  int *l = links;
  for(int i=0; i<n; i++) for(int j=HASH_SIZE(deg[i]); j--; l++) {
    register int d;
    if((d = *l)!=HASH_NONE && d>=i) *(p++)=d;
  }
  assert(p==hc+2+n+a/2);
  return hc;
}

//_________________________________________________________________________
bool graph_molloy_hash::is_connected() {
  bool *visited = new bool[n];
  int *buff = new int[n];
  int comp_size = depth_search(visited, buff);
  delete[] visited;
  delete[] buff;
  return (comp_size==n);
}

//_________________________________________________________________________
int* graph_molloy_hash::backup() {
  int *b = new int[a/2];
  int *c = b;
  int *p = links;
  for(int i=0; i<n; i++)
    for(int d=HASH_SIZE(deg[i]); d--; p++) if(*p!=HASH_NONE && *p>i) *(c++)=*p;
  assert(c==b+(a/2));
  return b;
}

//_________________________________________________________________________
void graph_molloy_hash::restore(int* b) {
  init();
  int i;
  int *dd = new int[n];
  memcpy(dd,deg,sizeof(int)*n);
  for(i=0; i<n; i++) deg[i] = 0;
  for(i=0; i<n-1; i++) {
    while (deg[i] < dd[i]) {
      add_edge(i, *b, dd);
      b++;
    }
  }
  delete[] dd;
}

//_________________________________________________________________________
bool graph_molloy_hash::isolated(int v, int K, int *Kbuff, bool *visited) {
  if(K<2) return false;
#ifdef OPT_ISOLATED
  if(K<=deg[v]+1) return false;
#endif //OPT_ISOLATED
  int *seen  = Kbuff;
  int *known = Kbuff;
  int *max   = Kbuff + K;
  *(known++) = v;
  visited[v] = true;
  bool is_isolated = true;
  
  while(known != seen) {
    v = *(seen++);
    int *ww = neigh[v];
    int w;
    for(int d=HASH_SIZE(deg[v]); d--; ww++) if((w=*ww)!=HASH_NONE && !visited[w]) {
#ifdef OPT_ISOLATED
      if(K<=deg[w]+1 || known == max) {
#else //OPT_ISOLATED
      if(known == max) {
#endif //OPT_ISOLATED
        is_isolated = false;
        goto end_isolated;
      }
      visited[w] = true;
      *(known++) = w;
    }
  }
end_isolated:
  // Undo the changes to visited[]...
  while(known != Kbuff) visited[*(--known)] = false;
  return is_isolated;
}

//_________________________________________________________________________
int graph_molloy_hash::random_edge_swap(int K, int *Kbuff, bool *visited) {
  // Pick two random vertices a and c
  int f1 = pick_random_vertex();
  int f2 = pick_random_vertex();
  // Check that f1 != f2
  if(f1==f2) return 0;
  // Get two random edges (f1,*f1t1) and (f2,*f2t2)
  int *f1t1 = random_neighbour(f1);
  int t1 = *f1t1;
  int *f2t2 = random_neighbour(f2);
  int t2 = *f2t2;
  // Check simplicity
  if(t1==t2 || f1==t2 || f2==t1) return 0;
  if(is_edge(f1,t2) || is_edge(f2,t1)) return 0;
  // Swap
  int *f1t2 = H_rpl(neigh[f1],deg[f1],f1t1,t2);
  int *f2t1 = H_rpl(neigh[f2],deg[f2],f2t2,t1);
  int *t1f2 = H_rpl(neigh[t1],deg[t1],f1,f2);
  int *t2f1 = H_rpl(neigh[t2],deg[t2],f2,f1);
  // isolation test
  if(K<=2) return 1;
  if( !isolated(f1, K, Kbuff, visited) && !isolated(f2, K, Kbuff, visited) )
    return 1;
  // undo swap
  H_rpl(neigh[f1],deg[f1],f1t2,t1);
  H_rpl(neigh[f2],deg[f2],f2t1,t2);
  H_rpl(neigh[t1],deg[t1],t1f2,f1);
  H_rpl(neigh[t2],deg[t2],t2f1,f2);
  return 0;
}

//_________________________________________________________________________
unsigned long graph_molloy_hash::shuffle(unsigned long times, 
					 unsigned long maxtimes, int type) {
  igraph_progress("Shuffle", 0, 0);
  // assert(verify());
  // counters
  unsigned long nb_swaps = 0;
  unsigned long all_swaps = 0;
  unsigned long cost = 0;
  // window
  double T = double(min((unsigned long)(a),times)/10);
  if(type==OPTIMAL_HEURISTICS) T=double(optimal_window());
  if(type==BRUTE_FORCE_HEURISTICS) T=double(times*2);
  // isolation test parameter, and buffers
  double K = 2.4;
  int *Kbuff = new int[int(K)+1];
  bool *visited = new bool[n];
  for(int i=0; i<n; i++) visited[i] = false;
  // Used for monitoring , active only if VERBOSE()
  int failures = 0;
  int successes = 0;
  double avg_K = 0;
  double avg_T = 0;
  unsigned long next = times;
  next=0;
  
  // Shuffle: while #edge swap attempts validated by connectivity < times ...
  while(times>nb_swaps && maxtimes>all_swaps) {
    // Backup graph
    int *save = backup();
    // Prepare counters, K, T
    unsigned long swaps = 0;
    int K_int = 0;
    if(type == FINAL_HEURISTICS || type == BRUTE_FORCE_HEURISTICS) K_int=int(K);
    unsigned long T_int = (unsigned long)(floor(T));
    if(T_int<1) T_int=1;
    // compute cost
    cost += T_int;
    if(K_int>2) cost += (unsigned long)(K_int)*(unsigned long)(T_int);
    // Perform T edge swap attempts
    for(int i=T_int; i>0; i--) {
      // try one swap
      swaps += (unsigned long)(random_edge_swap(K_int, Kbuff, visited));
      all_swaps++;
      // Verbose
      if(nb_swaps+swaps>next) {
        next = (nb_swaps+swaps)+max((unsigned long)(100),(unsigned long)(times/1000));
        int progress = int(double(nb_swaps+swaps) / double(times));
        igraph_progress("Shuffle",  progress, 0);
      }
    }
    // test connectivity
    cost+=(unsigned long)(a/2);
    bool ok = is_connected();
    // performance monitor
    {
      avg_T += double(T_int); avg_K += double(K_int);
      if(ok) successes++; else failures++;
    }
    // restore graph if needed, and count validated swaps
    if(ok) nb_swaps += swaps;
    else {
      restore(save);
      next=nb_swaps;
    }
    delete[] save;
    // Adjust K and T following the heuristics.
    switch(type) {
      int steps;
      case GKAN_HEURISTICS:
        if (ok) T+=1.0; else T*=0.5;
        break;
      case FAB_HEURISTICS:
        steps = 50 / (8+failures+successes);
	if(steps<1) steps=1;
	while(steps--) if(ok) T*=1.17182818; else T*=0.9;
	if(T>double(5*a)) T=double(5*a);
        break;
      case FINAL_HEURISTICS:
        if(ok) { if((K+10.0)*T>5.0*double(a)) K/=1.03; else T*=2; }
	else { K*=1.35; delete[] Kbuff; Kbuff = new int[int(K)+1]; }
	break;
      case OPTIMAL_HEURISTICS:
        if(ok) T=double(optimal_window());
	break;
      case BRUTE_FORCE_HEURISTICS:
        K*=2; delete[] Kbuff; Kbuff = new int[int(K)+1];
	break;
      default:
        IGRAPH_ERROR("Error in graph_molloy_hash::shuffle(): " 
		     "Unknown heuristics type", IGRAPH_EINVAL);
        return 0;
    }
  }

  delete[] Kbuff;
  delete[] visited;

  if (maxtimes <= all_swaps) { 
    IGRAPH_WARNING("Cannot shuffle graph, maybe there is only a single one?");
  }
  
  // Status report
  {
    igraph_status("*** Shuffle Monitor ***\n", 0);
    igraph_statusf(" - Average cost : %f / validated edge swap\n", 0,
		   double(cost)/double(nb_swaps));
    igraph_statusf(" - Connectivity tests : %d (%d successes, %d failures)\n",
		   0, successes + failures, successes, failures);
    igraph_statusf(" - Average window : %d\n", 0, 
		   int(avg_T/double(successes+failures)));
    if(type==FINAL_HEURISTICS || type==BRUTE_FORCE_HEURISTICS)
      igraph_statusf(" - Average isolation test width : %f\n", 0,
		     avg_K/double(successes+failures));
  }
  return nb_swaps;
}

//_________________________________________________________________________
void graph_molloy_hash::print(FILE *f) {
  int i,j;
  for(i=0; i<n; i++) {
    fprintf(f,"%d",i);
    for(j=0; j<HASH_SIZE(deg[i]); j++) if(neigh[i][j]!=HASH_NONE) fprintf(f," %d",neigh[i][j]);
    fprintf(f,"\n");
  }
}

int graph_molloy_hash::print(igraph_t *graph) {
  int i, j;
  long int ptr=0;
  igraph_vector_t edges;

  IGRAPH_VECTOR_INIT_FINALLY(&edges, a); // every edge is counted twice....

  for (i=0; i<n; i++) {
    for (j=0; j<HASH_SIZE(deg[i]); j++) {
      if (neigh[i][j]!=HASH_NONE) {
	if (neigh[i][j] > i) {
	  VECTOR(edges)[ptr++] = i;
	  VECTOR(edges)[ptr++] = neigh[i][j];
	}
      }
    }
  }
  
  IGRAPH_CHECK(igraph_create(graph, &edges, n, /*undirected=*/ 0));
  igraph_vector_destroy(&edges);
  IGRAPH_FINALLY_CLEAN(1);
  
  return 0;
}

//_________________________________________________________________________
bool graph_molloy_hash::try_shuffle(int T, int K, int *backup_graph) {
  // init all
  int *Kbuff = NULL;
  bool *visited = NULL;
  if(K>2) {
    Kbuff = new int[K];
    visited = new bool[n];
    for(int i=0; i<n; i++) visited[i]=false;
  }
  int *back = backup_graph;
  if(back==NULL) back=backup();
  // perform T edge swap attempts
  while(T--) random_edge_swap(K, Kbuff, visited);
  // clean
  if(visited != NULL) delete[] visited;
  if(Kbuff   != NULL) delete[] Kbuff;
  // check & restore
  bool yo = is_connected();
  restore(back);
  if(backup_graph == NULL) delete[] back;
  return yo;
}

//_________________________________________________________________________
#define _TRUST_BERNOULLI_LOWER 0.01

bool bernoulli_param_is_lower(int success, int trials, double param) {
  if(double(success)>=double(trials)*param) return false;
  double comb = 1.0;
  double fact = 1.0;
  for(int i=0; i<success; i++) {
    comb *= double(trials-i);
    fact *= double(i+1);
  }
  comb /= fact;
  comb *= pow(param, double(success))* exp(double(trials-success)*log1p(-param));
  double sum = comb;
  while(success && sum < _TRUST_BERNOULLI_LOWER) {
    comb *= double(success) * (1.0-param) / (double(trials-success) * param);
    sum += comb;
    success--;
  }
  // fprintf(stderr,"bernoulli test : %d/%d success against p=%f -> %s\n",success, trials, param, (sum < _TRUST_BERNOULLI_LOWER) ? "lower" : "can't say");
  return (sum < _TRUST_BERNOULLI_LOWER);
} 

//_________________________________________________________________________
#define _MIN_SUCCESS_FOR_BERNOULLI_TRUST 100
double graph_molloy_hash::average_cost(int T, int *backup, double min_cost) {
  if(T<1) return 1e+99;
  int successes = 0;
  int trials = 0;
  while(successes < _MIN_SUCCESS_FOR_BERNOULLI_TRUST &&
        !bernoulli_param_is_lower(successes, trials, 1.0/min_cost)) {
    if(try_shuffle(T,0,backup)) successes++;
    trials++;
  }
  if(successes >= _MIN_SUCCESS_FOR_BERNOULLI_TRUST)
    return double(trials)/double(successes)*(1.0+double(a/2)/double(T));
  else
    return 2.0*min_cost;
}

//_________________________________________________________________________
int graph_molloy_hash::optimal_window() {
  int Tmax;
  int optimal_T=1;
  double min_cost=1e+99;
  int *back=backup();
  // on cherche une borne sup pour Tmax
  int been_greater = 0;
  for(Tmax=1; Tmax<=5*a ;Tmax*=2) {
    double c = average_cost(Tmax, back, min_cost);
    if(c > 1.5 * min_cost) break;
    if(c > 1.2 * min_cost && ++been_greater >= 3) break;
    if(c < min_cost) {
      min_cost = c;
      optimal_T = Tmax;
    }
    igraph_statusf("Tmax = %d [%f]", 0, Tmax, min_cost);
  }
  // on cree Tmin
  int Tmin = int(0.5*double(a)/(min_cost-1.0));
  igraph_statusf("Optimal T is in [%d, %d]\n", 0, Tmin, Tmax);
  // on cherche autour
  double span = 2.0;
  int try_again = 4;
  while(span>1.05 && optimal_T <= 5*a) {
    igraph_statusf("Best T [cost]: %d [%f]", 0, optimal_T, min_cost);
    int T_low  = int(double(optimal_T)/span);
    int T_high = int(double(optimal_T)*span);
    double c_low  = average_cost(T_low , back, min_cost);
    double c_high = average_cost(T_high, back, min_cost);
    if(c_low<min_cost && c_high<min_cost) {
      if(try_again--) continue;
      {
	igraph_status("Warning: when looking for optimal T,\n", 0);
        igraph_statusf("Low: %d [%f]  Middle: %d [%f]  High: %d [%f]\n", 0, 
		       T_low, c_low, optimal_T, min_cost, T_high, c_high);
      }
      delete[] back;
      return optimal_T;
    }
    if(c_low<min_cost) { optimal_T = T_low; min_cost = c_low; }
    else if(c_high<min_cost) { optimal_T = T_high; min_cost = c_high; };
    span = pow(span,0.618);
  }  
  delete[] back;
  return optimal_T;
}

//_________________________________________________________________________
double graph_molloy_hash::eval_K(int quality) {
  double K = 5.0;
  double avg_K = 1.0;
  for(int i=quality; i--; ) {
    int int_K = int(floor(K+0.5));
    if(try_shuffle(a/(int_K+1),int_K)) {
      K*=0.8; /*fprintf(stderr,"+");*/ }
    else {
      K*=1.25; /*fprintf(stderr,"-");*/ }
    if(i<quality/2) avg_K *= K;
  }
  return pow(avg_K,1.0/double(quality/2));
}

//_________________________________________________________________________
double graph_molloy_hash::effective_K(int K, int quality) {
  if(K<3) return 0.0;
  long sum_K = 0;
  int *Kbuff = new int[K];
  bool *visited = new bool[n];
  int i;
  for(i=0; i<n; i++) visited[i] = false;
  for(int i=0; i<quality; i++) {
    // assert(verify());
    int f1,f2,t1,t2;
    int *f1t1, *f2t2;
    do {
      // Pick two random vertices
      do {
        f1 = pick_random_vertex();
        f2 = pick_random_vertex();
      } while(f1==f2);
      // Pick two random neighbours
      f1t1 = random_neighbour(f1);
      t1 = *f1t1;
      f2t2 = random_neighbour(f2);
      t2 = *f2t2;
      // test simplicity
    }
    while (t1==t2 || f1==t2 || f2==t1 || is_edge(f1,t2) || is_edge(f2,t1));
    // swap
    swap_edges(f1,t2,f2,t1);
    // assert(verify());
    sum_K += effective_isolated(deg[f1]>deg[t2] ? f1 : t2, K, Kbuff, visited);
    // assert(verify());
    sum_K += effective_isolated(deg[f2]>deg[t1] ? f2 : t1, K, Kbuff, visited);
    // assert(verify());
    // undo swap
    swap_edges(f1,t2,f2,t1);
    // assert(verify());
  }
  delete[] Kbuff;
  delete[] visited;
  return double(sum_K)/double(2*quality);
}

//_________________________________________________________________________
long graph_molloy_hash::effective_isolated(int v, int K, int *Kbuff, bool *visited) {
  int i;
  for(i=0; i<K; i++) Kbuff[i]=-1;
  long count = 0;
  int left = K;
  int *KB = Kbuff;
  //yapido = (my_random()%1000 == 0);
  depth_isolated(v, count, left, K, KB, visited);
  while(KB-- != Kbuff) visited[*KB] = false;
  //if(yapido) fprintf(stderr,"\n");
  return count;
}

//_________________________________________________________________________
void graph_molloy_hash::depth_isolated(int v, long &calls, int &left_to_explore, int dmax, int * &Kbuff, bool *visited) {
  if(left_to_explore==0) return;
//  if(yapido) fprintf(stderr,"%d ",deg[v]);
  if(--left_to_explore == 0) return;
  if(deg[v]+1>=dmax) {
    left_to_explore = 0;
    return;
  }
  *(Kbuff++) = v;
  visited[v] = true;
//  print();
//  fflush(stdout);
  calls++;
  int *copy = NULL;
  int *w = neigh[v];
  if(IS_HASH(deg[v])) {
    copy = new int[deg[v]];
    H_copy(copy,w,deg[v]);
    w = copy;
  }
  qsort(deg, w, deg[v]);
  w+=deg[v];
  for(int i=deg[v]; i--; ) {
    if(visited[*--w]) calls++;
    else depth_isolated(*w, calls, left_to_explore, dmax, Kbuff, visited);
    if(left_to_explore==0) break;
  }
  if(copy!=NULL) delete[] copy;
}

//_________________________________________________________________________
int graph_molloy_hash::depth_search(bool *visited, int *buff, int v0) {
  for(int i=0; i<n; i++) visited[i] = false;
  int *to_visit = buff;
  int nb_visited = 1;
  visited[v0]=true;
  *(to_visit++)=v0;
  while(to_visit != buff && nb_visited<n) {
    int v = *(--to_visit);
    int *ww = neigh[v];
    int w;
    for(int k=HASH_SIZE(deg[v]); k--; ww++) {
      if(HASH_NONE!=(w=*ww) && !visited[w]) {
        visited[w]=true;
        nb_visited++;
        *(to_visit++)=w;
      }
    }
  }
  return nb_visited;
}

//_________________________________________________________________________
// bool graph_molloy_hash::verify() {
//   fprintf(stderr,"Warning: graph_molloy_hash::verify() called..\n");
//   fprintf(stderr,"   try to convert graph into graph_molloy_opt() instead\n");
//   return true;
// }


/*____________________________________________________________________________
  Not to use anymore : use graph_molloy_opt class instead

bool graph_molloy_hash::verify() {
int i;
  assert(neigh[0]==links);
  // verify edges count
  int sum = 0;
  for(i=0; i<n; i++) sum+=deg[i];
  assert(sum==a);
  // verify neigh[] and deg[] compatibility
  for(i=0; i<n-1; i++) assert(neigh[i]+HASH_SIZE(deg[i])==neigh[i+1]);
  // verify hash tables : do we see everyone ?
  for(i=0; i<n; i++) for(int j=HASH_SIZE(deg[i]); j--; )
    if(neigh[i][j]!=HASH_NONE) assert(H_is(neigh[i],deg[i],neigh[i][j]));
  degree_sequence dd(n,deg);
  graph_molloy_opt g(dd);
  dd.detach();
  int *bb = backup();
  g.restore(bb);
  delete[] bb;
  return g.verify();
}

graph_molloy_hash::graph_molloy_hash(FILE *f) { 
  char *buff = new char[FBUFF_SIZE];
  // How many vertices ?
  if(VERBOSE()) fprintf(stderr,"Read file: #vertices=");
  int i;
  int n=0;
  while(fgets(buff,FBUFF_SIZE,f)) if(sscanf(buff,"%d",&i)==1 && i>n) n=i;
  n++;
  // degrees ?
  if(VERBOSE()) fprintf(stderr,"%d, #edges=",n);
  int *degs = new int[n];
  rewind(f);
  while(fgets(buff,FBUFF_SIZE,f)) {
    int d = 0;
    if(sscanf(buff,"%d",&i)==1) {
      char *b = buff;
      while(skip_int(b)) d++;
      degs[i]=d;
    }
  }
  // allocate memory
  degree_sequence dd(n,degs);
  if(VERBOSE()) fprintf(stderr,"%d\nAllocating memory...",dd.sum());
  alloc(dd);
  // add edges
  if(VERBOSE()) fprintf(stderr,"done\nCreating edges...");
  rewind(f);
  for(i=0; i<n; i++) deg[i]=0;
  int line=0;
  int j;
  while(fgets(buff,FBUFF_SIZE,f)) {
    line++;
    if(sscanf(buff,"%d",&i)==1) {
      char *b = buff;
      while(skip_int(b)) {
        if(sscanf(b,"%d",&j)!=1) {
          fprintf(stderr,"\nParse error at line %d, col %d : integer expected\n",line,int(b-buff));
          exit(6);
        }
        if(i<j) add_edge(i,j,dd.seq());
      }
    }
  }
  if(VERBOSE()) fprintf(stderr,"done\n");
  delete[] buff;
}


int graph_molloy_hash::max_degree() {
  int m=0;
  for(int k=0; k<n; k++) if(deg[k]>m) m=deg[k];
  return m;
}


bool graph_molloy_hash::havelhakimi() {

  int i;
  int dmax = max_degree()+1;
  // Sort vertices using basket-sort, in descending degrees
  int *nb = new int[dmax];
  int *sorted = new int[n];
  // init basket
  for(i=0; i<dmax; i++) nb[i]=0;
  // count basket
  for(i=0; i<n; i++) nb[deg[i]]++;
  // cumul
  int c = 0;
  for(i=dmax-1; i>=0; i--) {
    int t=nb[i];
    nb[i]=c;
    c+=t;
  }                                            
  // sort
  for(i=0; i<n; i++) sorted[nb[deg[i]]++]=i;
  // Init edge count
  for(i=0; i<n; i++) deg[i] = 0;

// Binding process starts
  int first = 0;  // vertex with biggest residual degree
  int d = dmax-1; // maximum residual degree available

  for(c=a/2; c>0; ) {
    // pick a vertex. we could pick any, but here we pick the one with biggest degree
    int v = sorted[first];
    // look for current degree of v
    while(nb[d]<=first) d--;
    // store it in dv
    int dv = d;
    // bind it !
    c -= dv;
    int dc = d;         // residual degree of vertices we bind to
    int fc = ++first;   // position of the first vertex with degree dc

    while(dv>0 && dc>0) {
      int lc = nb[dc];
      if(lc!=fc) {
        while(dv>0 && lc>fc) {
          // binds v with sorted[--lc]
          dv--;
          int w = sorted[--lc];
          add_edge(v,w);
        }
        fc = nb[dc];
        nb[dc] = lc;
      }
      dc--;
    }
    if(dv != 0) { // We couldn't bind entirely v
      if(VERBOSE()) {
        fprintf(stderr,"Error in graph_molloy_hash::havelhakimi() :\n");
        fprintf(stderr,"Couldn't bind vertex %d entirely (%d edges remaining)\n",v,dv);
      }
      delete[] nb;
      delete[] sorted;
      return false;
    }
  }
  assert(c==0);
  delete[] nb;
  delete[] sorted;
  return true;
}


bool graph_molloy_hash::make_connected() {
  assert(verify());
  if(a/2 < n-1) {
    // fprintf(stderr,"\ngraph::make_connected() failed : #edges < #vertices-1\n");
    return false;
  }
  int i;
 
// Data struct for the visit :
// - buff[] contains vertices to visit
// - dist[V] is V's distance modulo 4 to the root of its comp, or -1 if it hasn't been visited yet
#define MC_BUFF_SIZE (n+2)
  int *buff = new int[MC_BUFF_SIZE];
  unsigned char * dist  = new unsigned char[n];
#define NOT_VISITED 255
#define FORBIDDEN   254
  for(i=n; i>0; dist[--i]=NOT_VISITED);

// Data struct to store components : either surplus trees or surplus edges are stored at buff[]'s end
// - A Tree is coded by one of its vertices
// - An edge (a,b) is coded by the TWO ints a and b
  int *ffub = buff+MC_BUFF_SIZE;
  edge *edges = (edge *) ffub;
  int *trees = ffub;
  int *min_ffub = buff+1+(MC_BUFF_SIZE%2 ? 0 : 1);

// There will be only one "fatty" component, and trees.
  edge fatty_edge;
  fatty_edge.from = -1;
  bool enough_edges = false;

  // start main loop
  for(int v0=0; v0<n; v0++) if(dist[v0]==NOT_VISITED) {
    // is v0 an isolated vertex?
    if(deg[v0]==0) {
#ifdef VERBOSE
      fprintf(stderr,"graph_molloy_opt::make_connected() returned FALSE : vertex %d has degree 0\n",v0);
#endif //VERBOSE
      delete[] dist;
      delete[] buff;
      return false;
    }
    dist[v0] = 0; // root
    int *to_visit = buff;
    int *current  = buff;
    *(to_visit++) = v0;

    // explore component connected to v0
    bool is_a_tree = true;
    while(current != to_visit) {
      int v = *(current++);
      unsigned char current_dist = dist[v];
      unsigned char next_dist = (current_dist+1) & 0x03;
      //unsigned char prev_dist = (current_dist-1) & 0x03;
      int* ww = neigh[v];
      int w;
      for(int k=HASH_SIZE(deg[v]); k--; ww++) if((w=*ww)!=HASH_NONE) {
        if(dist[w]==NOT_VISITED) {
          // we didn't visit w yet
  	      dist[w] = next_dist;
  	      *(to_visit++) = w;
          if(to_visit>min_ffub) min_ffub+=2; // update limit of ffub's storage
          //assert(verify());
        }
        else if(dist[w]==next_dist || (w!=HASH_NONE && w>v && dist[w]==current_dist)) {
          // we found a removable edge
          if(is_a_tree) {
            // we must first merge with the fatty component
            is_a_tree = false;
            if(fatty_edge.from < 0) {
              // we ARE the first component! fatty is us
              fatty_edge.from = v;
              fatty_edge.to   = w;
            }
            else {
              // we connect to fatty
              swap_edges(fatty_edge.from, fatty_edge.to, v, w);
              //assert(verify());
            }
          }
          else {
            // we have removable edges to give!
            if(trees!=ffub) {
              // some trees still.. Let's merge with them!
              assert(trees>=min_ffub);
              assert(edges==(edge *)ffub);
              swap_edges(v,w,*trees,neigh[*trees][0]);
              trees++;
              //assert(verify());
            }
            else if(!enough_edges) {
              // Store the removable edge for future use
              if(edges<=(edge *)min_ffub+1)
                enough_edges = true;
              else {
                edges--;
                edges->from = v;
                edges->to   = w;
              }
            }
          }
        }
      }
    }
    // Mark component
    while(to_visit!=buff) dist[*(--to_visit)] = FORBIDDEN;
    // Check if it is a tree
    if(is_a_tree ) {
      assert(deg[v0]!=0);
      if(edges!=(edge *)ffub) {
        // let's bind the tree we found with a removable edge in stock
        assert(trees == ffub);
        if(edges<(edge *)min_ffub) edges=(edge *)min_ffub;
        swap_edges(v0,neigh[v0][0],edges->from,edges->to);
        edges++;
        assert(verify());
    }
      else {
        // add the tree to the list of trees
        assert(trees>min_ffub);
        *(--trees) = v0;
        assert(verify());
      }
    }
  }
  delete[] buff;
  delete[] dist;
  return(trees == ffub);
}

int64_t graph_molloy_hash::slow_connected_shuffle(int64_t times) {
  assert(verify());
  int64_t nb_swaps = 0;
  int T = 1;

  while(times>nb_swaps) {
    // Backup graph
    int *save = backup();
    // Swaps
    int swaps = 0;
    for(int i=T; i>0; i--) {
      // Pick two random vertices a and c
      int f1 = pick_random_vertex();
      int f2 = pick_random_vertex();
      // Check that f1 != f2
      if(f1==f2) continue;
      // Get two random edges (f1,*f1t1) and (f2,*f2t2)
      int *f1t1 = random_neighbour(f1);
      int t1 = *f1t1;
      int *f2t2 = random_neighbour(f2);
      int t2 = *f2t2;
      // Check simplicity
      if(t1==t2 || f1==t2 || f2==t1) continue;
      if(is_edge(f1,t2) || is_edge(f2,t1)) continue;
      // Swap
      H_rpl(neigh[f1],deg[f1],f1t1,t2);
      H_rpl(neigh[f2],deg[f2],f2t2,t1);
      H_rpl(neigh[t1],deg[t1],f1,f2);
      H_rpl(neigh[t2],deg[t2],f2,f1);
      swaps++;
    }
    // test connectivity
    bool ok = is_connected();
    if(ok) {
      nb_swaps += swaps;
    }
    else {
      restore(save);
    }
    delete[] save;
  }
  return nb_swaps;
}


int graph_molloy_hash::width_search(unsigned char *dist, int *buff, int v0) {
  for(int i=0; i<n; i++) dist[i] = 0;
  int *to_visit = buff;
  int *to_add = buff;
  int nb_visited = 1;
  dist[v0]=1;
  *(to_add++)=v0;
  while(to_visit != to_add && nb_visited<n) {
    int v = *(to_visit++);
    int *ww = neigh[v];
    int w;
    unsigned char d = next_dist(dist[v]);
    for(int k=HASH_SIZE(deg[v]); k--; ww++) {
      if(HASH_NONE!=(w=*ww) && dist[w]==0) {
        dist[w]=d;
        nb_visited++;
        *(to_add++)=w;
      }
    }
  }
  return nb_visited;
}


  
int *graph_molloy_hash::vertex_betweenness_rsp(bool trivial_paths) {
  int i;
  unsigned char *dist = new unsigned char[n];
  int *buff = new int[n];
  int *b = new int[n];
  int *bb = new int[n];
  for(i=0; i<n; i++) b[i]=0;
  for(int v0 = 0; v0<n; v0++) {
    for(i=0; i<n; i++) bb[i]=0;
    int nb_vertices = width_search(dist, buff, v0);
    while(--nb_vertices) {
      int v=buff[nb_vertices];
      int d = prev_dist(dist[v]);
      int *adj = neigh[v];
      int adj_size = deg[v];
      int *ww;
      do ww=H_random(adj,adj_size); while(dist[*ww]!=d);
      if(trivial_paths || *ww!=v0) bb[*ww] += bb[v]+1;
      if(trivial_paths) bb[v]++;
    }
    for(i=0; i<n; i++) b[i]+=bb[i];
  }
  delete[] dist;
  delete[] buff;
  delete[] bb;
  return b;
}

double *graph_molloy_hash::vertex_betweenness_asp(bool trivial_paths) {
  int i;
  unsigned char *dist = new unsigned char[n];
  int *buff = new int[n];
  double *b = new double[n];
  double *bb = new double[n];
  for(i=0; i<n; i++) b[i]=0.0;
  for(int v0 = 0; v0<n; v0++) {
    for(i=0; i<n; i++) bb[i]=0.0;
    int nb_vertices = width_search(dist, buff, v0);
    if(!trivial_paths) dist[v0]=2;
    while(--nb_vertices) {
      int v=buff[nb_vertices];
      int d = prev_dist(dist[v]);
      int nb_father = 0;
      int *ww = neigh[v];
      int k;
      for(k=HASH_SIZE(deg[v]); k--; ww++) if(*ww != HASH_NONE && dist[*ww]==d) nb_father++;
      if(nb_father!=0) {
        double badd = (bb[v]+1.0)/double(nb_father);
        ww = neigh[v];
        for(k=HASH_SIZE(deg[v]); k--; ww++) if(*ww != HASH_NONE && dist[*ww]==d) bb[*ww]+=badd;
      }
      if(trivial_paths) bb[v]+=1.0;
    }
    for(i=0; i<n; i++) b[i]+=bb[i];
  }
  delete[] dist;
  delete[] buff;
  delete[] bb;
  return b;
}
    
//___________________________________________________________________________________
//*/

} // namespace gengraph
