/*-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

  File Name : leef.c

Purpose: embedding learning on a weighted graph

Creation Date : 15-01-2017

Last Modified : Wed 15 Mar 2017 03:07:53 PM CDT

Created By : Huan Gui (huangui2@illinois.edu)

_._._._._._._._._._._._._._._._._._._._._.*/

#include <assert.h>
#include <gsl/gsl_rng.h>
#include <math.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <set>

#include "ransampl.h"

#define MAX_STRING 100
#define MAX_SENTENCE 1000
#define MAX_SIGMOID 8.0
#define MAX_N_NODE_TYPE 10
#define MAX_N_EDGE_TYPE 10      // maximum number of edge types

typedef double real;          // Precision of float numbers

const int mini_batch = 1;
const int sigmoid_table_size = 2000;
const long hash_size = 30000000;     // For each type of nodes, there are 3M nodes
const int node_count_inc = 1000, neighbor_initial_count = 10;
const long neg_table_size = 1e8;
const long edge_table_size = 1e9;
const real sigmoid_coeff = sigmoid_table_size / MAX_SIGMOID / 2.0;
real sample_power = 0.75;  // defined for negative sampling as the power of frequency
real prior_power = 1;  // defined for negative sampling as the power of frequency

real max_sim = 0.5;
real max_iteration = 10;

struct ClassNeighbor {
  long node_id;
  long edge_id;
};

struct ClassNode {
  char * name;
  real degree;
  real weight;  // for the authority of nodes
  int *n_neighbor;
  int * max_n_neighbor;
  ClassNeighbor **neighbors;
};

struct ClassEdge {
  long src_id;
  long dst_id;
  real weight;
  real t_weight;
};

int n_node_type, n_edge_type;

// the node type for source node and dest node
int edge_node_type[MAX_N_EDGE_TYPE][2];

char output_folder[MAX_SENTENCE], input_folder[MAX_SENTENCE];
char embed_folder[MAX_SENTENCE], weight_folder[MAX_SENTENCE];
char train_network_file[MAX_N_EDGE_TYPE][MAX_SENTENCE];
char input_fix_embedding_file[MAX_SENTENCE];
char input_node_weight_file[MAX_SENTENCE];
char output_embedding[MAX_N_NODE_TYPE][MAX_SENTENCE];
char query_file[MAX_SENTENCE];

struct ClassNode* nodes[MAX_N_NODE_TYPE];
struct ClassEdge* edges[MAX_N_EDGE_TYPE];

set::set<long> query;

int author_type, term_type, paper_type, term_paper_edge_type;
int num_threads = 20, dim = 200;
int negative_k = 5;

// create hash table and negative table for each type of nodes and edges, respectively
long *node_hash_table[MAX_N_NODE_TYPE];
long *neg_table[MAX_N_NODE_TYPE];

ransampl_ws *edge_alias[MAX_N_EDGE_TYPE];
ransampl_ws *topic_edge_alias;

// The maximum size of each types of nodes, and the current count
long max_sz_node[MAX_N_NODE_TYPE], node_cnt[MAX_N_NODE_TYPE];

// total samples of edges to update parameters, and the current samples
long total_samples = 15, current_sample, edge_cnt[MAX_N_EDGE_TYPE];
real init_alpha = 0.025, alpha;

// the embedding of each type of nodes, relative to different types of nodes
real *emb_node[MAX_N_NODE_TYPE];

real *sigmoid_table;

//specify the types of nodes, and edge names
char node_names[MAX_N_NODE_TYPE][MAX_STRING];
char edge_names[MAX_N_EDGE_TYPE][MAX_STRING];

bool output_types[MAX_N_NODE_TYPE];
bool load_types[MAX_N_NODE_TYPE];
bool node_weight[MAX_N_NODE_TYPE];
bool edge_weight[MAX_N_NODE_TYPE];
bool valid_edge[MAX_N_NODE_TYPE][MAX_N_NODE_TYPE];

real beta[MAX_N_NODE_TYPE];
real node_sample_power[MAX_N_NODE_TYPE];

const gsl_rng_type * gsl_T;
gsl_rng * gsl_r;

clock_t start;

// the hash function is the same for all types of nodes
inline unsigned long GetHash(char *input) {
  unsigned long hash = 5381;
  char c;
  while ((c = *input) != '\0') {
    hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
    ++input;
  }
  return hash % hash_size;
}

// initialize the hash table for all types of nodes
void InitHashTable() {
  for (int node_type = 0; node_type < n_node_type; ++node_type) {
    node_hash_table[node_type] = (long *) malloc(hash_size * sizeof(long));
    for (long k = 0; k < hash_size; ++k) node_hash_table[node_type][k] = -1;
  }
}

void InsertHashTable(char *key, int value, int type) {
  unsigned long loc = GetHash(key);
  while (node_hash_table[type][loc] != -1) loc = (loc + 1) % hash_size;
  node_hash_table[type][loc] = value;
}

// hashing based on the name string
long SearchHashTable(char *key, int type) {
  unsigned long addr = GetHash(key);
  while (1) {
    if (node_hash_table[type][addr] == -1) return -1;
    if (!strcmp(key, nodes[type][node_hash_table[type][addr]].name))
      return node_hash_table[type][addr];
    addr = (addr + 1) % hash_size;
  }
  return -1;
}

void AddNeighbor(int src_type, int dst_type, long src_id, long dst_id,
    int edge_type, long edge_id) {
  assert(valid_edge[src_type][dst_type]);
  ClassNode &node = nodes[src_type][src_id];
  int n_neighbor = node.n_neighbor[dst_type];

  if (n_neighbor + 2 >= node.max_n_neighbor[dst_type]) {
    node.max_n_neighbor[dst_type] *= 2;
    ClassNeighbor *tmp = (struct ClassNeighbor*) realloc(
        node.neighbors[dst_type],
        node.max_n_neighbor[dst_type] * sizeof(struct ClassNeighbor));
    if (tmp != NULL) {
      node.neighbors[dst_type] = tmp;
    } else {
      printf("Fail to reallocate\n");
      exit(1);
    }
  }
  node.neighbors[dst_type][n_neighbor].node_id = dst_id;
  node.neighbors[dst_type][n_neighbor].edge_id = edge_id;
  node.n_neighbor[dst_type] += 1;
}

// Add a node to the vertex set
int AddNode(char *name, int node_type) {
  int length = strlen(name) + 1;
  if (length > MAX_STRING) length = MAX_STRING;

  ClassNode & node = nodes[node_type][node_cnt[node_type]];
  // insert the node, and initial the value of name, node_type, and degree;
  node.name = (char *) calloc(length, sizeof(char));
  node.degree = 0;
  node.weight = 1;

  node.n_neighbor = (int *) calloc(n_node_type, sizeof(int));
  node.max_n_neighbor = (int *) calloc(n_node_type, sizeof(int));
  node.neighbors = (ClassNeighbor **) calloc(n_node_type, sizeof(ClassNeighbor *));

  for (int i = 0; i < n_node_type; ++i) {
    if (valid_edge[node_type][i]) {
      node.max_n_neighbor[i] = neighbor_initial_count;
      node.neighbors[i] = (ClassNeighbor *) calloc(neighbor_initial_count,
          sizeof(ClassNeighbor));
    }
  }

  if (length < MAX_STRING) {
    strcpy(node.name, name);
  } else {
    strncpy(node.name, name, MAX_STRING - 1);
    node.name[MAX_STRING - 1] = '\0';
  }

  ++node_cnt[node_type];
  if (node_cnt[node_type] + 2 >= max_sz_node[node_type]) {
    max_sz_node[node_type] += node_count_inc;
    struct ClassNode *tmp = (struct ClassNode *) realloc(nodes[node_type],
        max_sz_node[node_type] * sizeof(struct ClassNode));
    if (tmp != NULL) {
      nodes[node_type] = tmp;
    } else {
      printf("fail to reallocate\n");
    }
  }
  InsertHashTable(name, node_cnt[node_type] - 1, node_type);
  return node_cnt[node_type] - 1;
}

void UpdateEdgeWeight(int edge_type) {
  long src_id, dst_id;
  int src_type = edge_node_type[edge_type][0];
  int dst_type = edge_node_type[edge_type][1];

  for (long k = 0; k < edge_cnt[edge_type]; ++k) {
    src_id = edges[edge_type][k].src_id;
    dst_id = edges[edge_type][k].dst_id;

    nodes[src_type][src_id].degree -= edges[edge_type][k].weight;
    nodes[dst_type][dst_id].degree -= edges[edge_type][k].weight;

    edges[edge_type][k].weight *=
      nodes[src_type][src_id].weight * nodes[dst_type][dst_id].weight;

    nodes[src_type][src_id].degree += edges[edge_type][k].weight;
    nodes[dst_type][dst_id].degree += edges[edge_type][k].weight;
  }
  //  printf("Finish updating edge weight of edge %s \n", edge_names[edge_type]);
}

void ReadNodeWeight(int node_type) {
  FILE *fin;
  char field[MAX_STRING], str[10000];
  real weight;
  long node_id, n_lines = 0;

  sprintf(input_node_weight_file,
      "%s/%s.weights", weight_folder, node_names[node_type]);
  //  printf("%s\n", input_node_weight_file);

  fin = fopen(input_node_weight_file, "rb");
  while (fgets(str, sizeof(str), fin)) ++n_lines;
  fclose(fin);

  fin = fopen(input_node_weight_file, "rb");
  for (long k = 0; k < n_lines; ++k) {
    fscanf(fin, "%s %lf", field, &weight);
    if (prior_power != 1) {
      weight = pow(weight, prior_power);
    }
    node_id = SearchHashTable(field, node_type);
    if (node_id == -1) {
      printf("Invalid node name %s of type %s \n",
          field, node_names[node_type]);
      exit(1);
    }
    nodes[node_type][node_id].weight = weight;
  }
  //printf("Finish updating the weight for nodes %s \n", node_names[node_type]);
}

/* Read edges of different types in the network */
void ReadEdgeData(int edge_type) {
  FILE *fin;
  real weight;

  long src_id, dst_id;
  int src_type = edge_node_type[edge_type][0];
  int dst_type = edge_node_type[edge_type][1];
  char str[MAX_SENTENCE], src_name[MAX_STRING], dst_name[MAX_STRING];

  // Get how many number of edges we have
  fin = fopen(train_network_file[edge_type], "rb");

  printf("input: %s \n", train_network_file[edge_type]);
  edge_cnt[edge_type] = 0;
  while (fgets(str, sizeof(str), fin)) ++edge_cnt[edge_type];
  fclose(fin);
  printf("Number of edges (%s): %ld          \n", edge_names[edge_type], edge_cnt[edge_type]);

  edges[edge_type] = (ClassEdge *) malloc(
      edge_cnt[edge_type] * sizeof(ClassEdge));

  fin = fopen(train_network_file[edge_type], "rb");

  for (long k = 0; k < edge_cnt[edge_type]; ++k) {
    fscanf(fin, "%s %s %f", src_name, dst_name, &weight);
    assert(weight > 0);

    src_id = SearchHashTable(src_name, src_type);
    if (src_id == -1) {
      src_id = AddNode(src_name, src_type);
    }
    dst_id = SearchHashTable(dst_name, dst_type);
    if (dst_id == -1) {
      dst_id = AddNode(dst_name, dst_type);
    }
    edges[edge_type][k].src_id = src_id;
    edges[edge_type][k].dst_id = dst_id;
    edges[edge_type][k].weight = weight;

    if (src_type == author_type || src_type == term_type) {
      AddNeighbor(src_type, dst_type, src_id, dst_id, edge_type, edge_id);
      AddNeighbor(dst_type, src_type, dst_id, src_id, edge_type, edge_id);
    }
    // update node degree
    nodes[src_type][src_id].degree += weight;
    nodes[dst_type][dst_id].degree += weight;

    if (k % 50000 == 0) {
      printf("Loading edges : %.3lf%%%c", k / (real)(edge_cnt[edge_type] + 1) * 100, 13);
      fflush(stdout);
    }
  }
  fclose(fin);
}

/* Read the fixed embedding of terms */
void ReadFixEmbedding(int node_type) {
  FILE *fin;
  char str[(dim + 1) * MAX_STRING + 1000];
  long node_id;
  long fix_node_count = node_cnt[node_type], count = 0;
  char *token;

  sprintf(input_fix_embedding_file,
      "%s/%s.embeddings", embed_folder, node_names[node_type]);

  if (access(input_fix_embedding_file, F_OK) == -1) {
    printf("Can't read the file %s \n", input_fix_embedding_file);
    exit(1);
  }

  // Get how many number of edges we have
  fin = fopen(input_fix_embedding_file, "rb");

  // printf("debug: finish init\n");
  if (fgets(str, sizeof(str), fin) == NULL) {
    printf("Can't read the file %s \n", input_fix_embedding_file);
    exit(1);
  }
  while (fgets(str, sizeof(str), fin)) {
    // printf("debug: parse %s\n", str);
    if (count >= fix_node_count) break;
    ++count;

    token = strtok(str, " ");
    node_id = SearchHashTable(token, node_type);
    if (node_id == -1) {
      continue;
    }
    for (int k = 0; k < dim; ++k) {
      token = strtok(NULL, " ");
      if (token == NULL) {
        printf("Error: embedding length dismatch!\n");
        exit(1);
      }
      emb_node[node_type][node_id * dim + k] = atof(token) / 20;
    }
  }
  fclose(fin);
  printf("Embedding of node %s imported!\n", node_names[node_type]);
}

void InitNodeVector(int node_type, real *emb_node) {
  long offset = 0;
  for (long i = 0; i < node_cnt[node_type]; ++i) {
    for (int k = 0; k < dim; ++k) {
      emb_node[offset++] = (gsl_rng_uniform(gsl_r) - 0.5) / dim;
    }
  }
}

/* Initialize the vertex embedding and the context embedding */
void InitVector(){
  printf("Initializing node vector ...                       \n %c", 13);
  fflush(stdout);
  // initialize the target and context embedding of each type of node
  for (int node_type = 0; node_type < n_node_type; ++node_type) {
    posix_memalign((void **)& emb_node[node_type], 128,
        node_cnt[node_type] * dim * sizeof(real));
    if (emb_node[node_type] == NULL) {
      printf("Error: memory allocation failed\n");
      exit(1);
    }
    InitNodeVector(node_type, emb_node[node_type]);
  }
}

// Given a node type, sample a negative node
void InitNegTable(int node_type) {
  real sum = 0, cur_sum = 0, por = 0;
  long vid = 0;
  printf("Initializing negative table for %s ... \t\t\t\t          %c", node_names[node_type], 13);

  fflush(stdout);
  neg_table[node_type] = (long *)malloc(neg_table_size * sizeof(long));

  for (long k = 0; k < node_cnt[node_type]; ++k) {
    sum += pow(nodes[node_type][k].degree,
        node_sample_power[node_type]);
  }

  for (long k = 0; k < neg_table_size; ++k) {
    if ((real)(k + 1) / neg_table_size > por) {
      cur_sum += pow(nodes[node_type][vid].degree,
          node_sample_power[node_type]);
      por = cur_sum / sum;
      ++vid;
    }
    neg_table[node_type][k] = vid - 1;
  }
}

void InitEdgeAlias(int edge_type) {
  printf("Initializing alias table for edge %s ...                   %c",
      edge_names[edge_type], 13);
  real *prob = (real *) calloc(edge_cnt[edge_type], sizeof(real));
  real total_weight = 0;
  for (int i = 0; i < edge_cnt[edge_type]; ++i) {
    total_weight += edges[edge_type][i].weight;
  }
  for (int i = 0; i < edge_cnt[edge_type]; ++i) {
    prob[i] = edges[edge_type][i].weight / total_weight;
  }
  edge_alias[edge_type] = ransampl_alloc(edge_cnt[edge_type]);
  ransampl_set(edge_alias[edge_type], prob);
}

/* Fastly compute sigmoid function */
void InitSigmoidTable() {
  real x;
  printf("Initializing sigmoid table ...                     %c", 13);
  fflush(stdout);
  sigmoid_table = (real *)malloc((sigmoid_table_size + 1) * sizeof(real));
  for (int k = 0; k < sigmoid_table_size; ++k) {
    x = 2 * (real)(MAX_SIGMOID * k) / sigmoid_table_size - MAX_SIGMOID;
    sigmoid_table[k] = 1 / (1 + exp(-x));
  }
}

inline real FastSigmoid(real x) {
  if (x > MAX_SIGMOID) return 1;
  else if (x < -MAX_SIGMOID) return 0;
  return sigmoid_table[(int)((x + MAX_SIGMOID) * sigmoid_coeff)];
}

inline long SampleNegativeNode(int node_type, gsl_rng * & gsl_r_local) {
  //  return gsl_rng_uniform_int(gsl_r_local, node_cnt[node_type]);
  return neg_table[node_type][
    gsl_rng_uniform_int(gsl_r_local, neg_table_size)];
}

inline long SampleEdge(int edge_type, gsl_rng * & gsl_r_local) {
  return ransampl_draw(edge_alias[edge_type],
      gsl_rng_uniform(gsl_r_local),
      gsl_rng_uniform(gsl_r_local));
}

inline void Update(int src_type, int dst_type, long src_id,
    long end_id, long *neg_node_ids, real *context_update) {
  long target_id, target_offset, context_offset;
  real f = 0, g = 0, label = 1;
  real decay_target = alpha * beta[dst_type];
  real decay_context = alpha * beta[src_type];

  memset(context_update, 0, dim * sizeof(real));

  context_offset = src_id * dim;
  for (int i = 0; i <= negative_k; ++i) {
    if (i == negative_k) {
      target_id = end_id;
      label = 1;
    } else {
      target_id = neg_node_ids[i];
      if (target_id == end_id) continue;
      label = 0;
    }
    target_offset = target_id * dim;
    f = 0;
    for (int j = 0; j < dim; j ++) {
      f += emb_node[dst_type][target_offset + j] *
        emb_node[src_type][context_offset + j];
    }
    g = (label - FastSigmoid(f)) * alpha;
    for (int j = 0; j < dim; ++j) {
      context_update[j] += g * emb_node[dst_type][target_offset + j]
        - decay_context * emb_node[src_type][context_offset + j];
      emb_node[dst_type][target_offset + j] +=
        g * emb_node[src_type][context_offset + j]
        - decay_target * emb_node[dst_type][target_offset + j];
    }
  }

  for (int j = 0; j < dim; ++j) {
    emb_node[src_type][context_offset + j] += context_update[j];
  }

}

void *TrainThread(void *id) {
  long count = 0, last_count = 0;
  long limit = total_samples / num_threads + 2;
  int src_type, dst_type;
  long edge_id, src_id, dst_id;
  long  *neg_node_ids;

  neg_node_ids = (long *) malloc(negative_k * sizeof(long));

  gsl_rng * gsl_r_local = gsl_rng_alloc(gsl_T);
  gsl_rng_set(gsl_r_local, (unsigned long int) id);

  real *err_update = (real *) malloc(dim * sizeof(real));

  real ratio;
  clock_t now;

  while (1)
  {
    // decide when to exist the learning
    if (count > limit) {
      current_sample += count - last_count;
      break ;
    }

    // update output information
    if (count - last_count == 10000) {
      current_sample += count - last_count;
      last_count = count;
      ratio = current_sample / (real)(total_samples + 1);
      if (count % 100000 == 0) {
        now=clock();
        printf("%cAlpha: %f  Progress: %.2lf%%  Events/thread/sec: %.2fk",
            13, alpha, ratio * 100,
            current_sample / ((real)(now - start + 1) / (real)CLOCKS_PER_SEC * 1000));
        fflush(stdout);
      }
      alpha = init_alpha * (1 - ratio);
    }
    ++count;

    for (int edge_type = 0; edge_type < n_edge_type; ++edge_type) {
      src_type = edge_node_type[edge_type][0];
      dst_type = edge_node_type[edge_type][1];

      edge_id = SampleEdge(edge_type, gsl_r_local);
      src_id = edges[edge_type][edge_id].src_id;
      dst_id = edges[edge_type][edge_id].dst_id;

      //printf("%d %d %d %s %s \n", edge_type, src_type, dst_type, nodes[src_type][src_id].name, nodes[dst_type][dst_id].name);

      for (int i = 0; i < negative_k; ++i) {
        neg_node_ids[i] = SampleNegativeNode(dst_type, gsl_r_local);
      }
      Update(src_type, dst_type, src_id, dst_id, neg_node_ids, err_update);
    }
  }
  free(neg_node_ids);
  free(err_update);
  pthread_exit(NULL);
}

// output the embeddings of nodes and the context embedding
void Output() {
  // for each type of nodes, output the embeddings
  char file_name[MAX_STRING];
  FILE *fo;
  long offset;

  // output the embeddings of nodes
  for (int node_type = 0; node_type < n_node_type; ++node_type) {
    if (!output_types[node_type]) continue;
    printf("storing the embedding of nodes %s\n", node_names[node_type]);
    sprintf(file_name, "%s/%s.embeddings", output_folder, node_names[node_type]);
    fo = fopen(file_name, "wb");
    fprintf(fo, "%ld %d\n", node_cnt[node_type], dim);
    offset = 0;
    for (long i = 0; i < node_cnt[node_type]; ++i) {
      fprintf(fo, "%s ", nodes[node_type][i].name);
      for (int k = 0; k < dim; ++k) {
        fprintf(fo, "%lf ", emb_node[node_type][offset++]);
      }
      fprintf(fo, "\n");
    }
    fclose(fo);
  }
}

// query string is delimited by #
void readQuery(char *qString, set::set<long> query) {
  query.clear();
  char delim[] = "#";
  char *token = strtok(name, delim);
  long node_id;
  while (token != NULL) {
    node_id = SearchHashTable(token, term_type);
    if (node_id == -1) {
      node_id = AddNode(token, map_node_type);
    }
    query.insert(node_id);
    token = strtok(NULL, delim);
  }
}

void hitPapers(set::set<long> &query, set::set<long> &paper_set) {
  paper_set.clear();
  for (set::set<long>::iterator it = query.begin(); it != query.end(); ++it) {
    long term_id = *it;
    ClassNode &node = nodes[term_type][term_id];
    for (int j = 0; j < node.n_neighbor[paper_type]; ++j) {
      paper_set.insert(node.neighbors[paper_type][j]);
    }
  }
}

void nodeExpansion(int src_type, int dst_type, set::set<long> &src_set, set::set<long> &dst_set) {
  dst_set.clear();
  for (set::set<long>::iterator it = src_set.begin(); it != src_set.end(); ++it) {
    long src_id = *it;
    ClassNode &node = nodes[src_type][src_id];
    for (int j = 0; j < node.n_neighbor[dst_type]; ++j) {
      dst_set.insert(node.neighbors[dst_type][j]);
    }
  }
}

void queryExpansion(set::set<long> &paper_set, set::set<long> &query, set::set<long> &new_query) {
  new_query.clear();
  long t_n_edges = 0;
  std::map<long, real> topic_edges;
  for (set::set<long>::iterator it = paper_set.begin(), it != paper_set.end(); ++it) {
    long paper_id = *it;
    class &node = *it;
    for (int j = 0; j < node.n_neighbor[term_type]; ++j) {
      long edge_id = node.neighbors[term_type][j].edge_id;
      real weight = edges[term_paper_edge_type][edge_id].weight;
      topic_edges[edge_id] = weight;
    }
  }

  long * sample_topic_edges = (long *) malloc(topic_edges.size() * sizeof(long));
  real * sample_prob = (real *) malloc(topic_edges.size() * sizeof(real));

  real sum_weight = 0;
  int k = 0;
  for (std::map<long, real>::iterator it = topic_edges.begin(); it != topic_edges.end(); ++it) {
    sample_topic_edges[k] = it->first;
    sample_prob[k] = it->second;
    sum_weight += sample_prob[k];
    k ++;
  }
  topic_edge_alias = ransampl_alloc(topic_edges.size());
  ransampl_set(topic_edge_alias, sample_prob);
}

set::set<long> expandDocument(set::set<long> query) {
  long * new_query;
  set::set<long> author_set, paper_set;
  set::set<long> new_query;
  while (true) {
    hitPapers(query, paper_set);
    nodeExpansion(paper_type, author_type, paper_set, author_set);
    nodeExpansion(author_type, paper_type, author_set, paper_set);
    queryExpansion(paper_set, query, new_query);
  }
  hitPapers(query, paper_set);
  return paper_set;
}

void TrainModel() {
  gsl_rng_env_setup();
  gsl_T = gsl_rng_rand48;
  gsl_r = gsl_rng_alloc(gsl_T);
  gsl_rng_set(gsl_r, 314159265);

  InitHashTable();
  printf("HashTable Initialized! \n");

  for (int edge_type = 0; edge_type < n_edge_type; ++edge_type) {
    printf("Start to read edges of %s\n", edge_names[edge_type]);
    ReadEdgeData(edge_type);
  }

  printf("Node types: %d \n", n_node_type);
  for (int i = 0; i < n_node_type; i ++) {
    printf("%s ", node_names[i]);
  }
  printf("\n");

  for (int node_type = 0; node_type < n_node_type; ++node_type) {
    if (node_weight[node_type]) {
      ReadNodeWeight(node_type);
    }
  }

  printf("Edge types: %d \n", n_edge_type);
  for (int i = 0; i < n_edge_type; i ++) {
    printf("%s ", edge_names[i]);
  }
  printf("\n");

  printf("--------------------------------                            \n");
  for (int k = 0; k < n_node_type; ++k) {
    printf("Number of nodes (%s): %ld          \n", node_names[k], node_cnt[k]);
  }
  // map each ege into corresponding
  total_samples = (long) (1000000 * total_samples);
  printf("--------------------------------\n");
  printf("Dimension: %d\n", dim);
  printf("Initial alpha: %.3lf\n", alpha);
  printf("Thread : %d \n", num_threads);
  printf("--------------------------------\n");

  printf("Load node weight : \n");
  for (int node_type = 0; node_type < n_node_type; ++node_type) {
    printf("\t%s : %s \n", node_names[node_type],
        node_weight[node_type] ? "Yes":"No");
  }
  printf("\n");

  printf("Prior power : %f \n\n", prior_power);

  printf("Update edge weight : \n");
  for (int edge_type = 0; edge_type < n_edge_type; ++edge_type) {
    if (edge_weight[edge_type] && (node_weight[edge_node_type[edge_type][0]]
          || node_weight[edge_node_type[edge_type][1]])) {
      printf("\t%s : %s \n", edge_names[edge_type], "Yes");
      UpdateEdgeWeight(edge_type);
    }
    else {
      printf("\t%s : %s \n", edge_names[edge_type], "No");
    }
  }
  printf("\n");

  printf("Output node types:");
  for (int m = 0; m < n_node_type; ++m){
    if (output_types[m]) {
      printf(" %s", node_names[m]);
    }
  }
  printf("\n\n");

  InitVector();

  InitSigmoidTable();
  printf("Finish initialization!                \n");

  FILE *fin = fopen(query_file, "rb");
  char c;
  int nQuery = 0;
  while (fgets(str, sizeof(str), fin)) ++nQuery;
  fclose();

  fin = fopen(query_file, "rb");
  set::set<long> papers, query;
  char query_string[MAX_SENTENCE];
  for (int q = 0; q < nQuery; q++) {
    int pos = 0;
    do {
      c = fgetc(fin);
      if (c == EOF || c == '\n') break;
      else if (pos < MAX_SENTENCE - 1) query_string[pos++] = (char) c;
    }
    query_string[pos] = 0;
    readQuery(query_string, query);
    papers.clear();
    papers = expandDocument(query);

  }

  struct timeval t1, t2;
  gettimeofday(&t1, NULL);
  start = clock();
  pthread_t *pt = (pthread_t *)malloc((num_threads) * sizeof(pthread_t));

  for (long a = 0; a < num_threads; ++a) {
    pthread_create(&pt[a], NULL, TrainThread, (void *)a);
  }
  for (int a = 0; a < num_threads; ++a) {
    pthread_join(pt[a], NULL);
  }

  gettimeofday(&t2, NULL);
  printf("\nTotal optimization time: %.2lf minitues\n", (real)(t2.tv_sec - t1.tv_sec) / 60);
  Output();
}

int ArgPos(char *str, int argc, char **argv) {
  for (int a = 0; a < argc; ++a) {
    if (!strcmp(str, argv[a])) {
      if (a == argc - 1) {
        printf("Argument missing for %s\n", str);
        exit(1);
      }
      return a;
    }
  }
  return -1;
}

int main(int argc, char **argv) {
  int i, j, k;
  char str[MAX_SENTENCE], source[MAX_SENTENCE], dest[MAX_SENTENCE];
  strcpy(output_folder, "output");
  strcpy(input_folder, "data");
  InitSigmoidTable();

  if (argc == 1) {
    printf("\t leef: Local Embedding for Expert Finding\n");
    printf("Options:");
    printf("\tPlease specify the parameter file!\n");
    printf("\t-para <file> \n");
    printf("\t\tProvide the parameter file on edges\n");
    printf("\t-size <int>\n");
    printf("\t\tSet dimension of vertex embeddings; default is 200\n");
    printf("\t-scoring <int>\n");
    printf("\t\tScoring function, 0: complete element-wise product,"
        "1: complete pairwise inner product, 2: pairwise inner product with central type\n");
    printf("\t-iter <int>\n");
    printf("\t\tSet the number of training samples as the number of iterations of number of edges\n");
    printf("\t-tthread <int>\n");
    printf("\t\tUse <int> threads for tensor updating(default 1)\n");
    printf("\t-wthread <int>\n");
    printf("\t\tUse <int> threads for word2vec updating(default 1)\n");
    printf("\t-negative <int>\n");
    printf("\t\tSample <int> negative samples for word2vec updating(default 1)\n");
    printf("\t-alpha <float>\n");
    printf("\t\tSet the starting learning rate; default is 0.025\n");
    printf("\t-input input folder ...\n");
    printf("\t\tSet the folder data which read data\n");
    printf("\t-output output folder ...\n");
    printf("\t\tSet the folder to which output data\n");
    return 0;
  }

  // global parameters shared across all the edges
  if ((i = ArgPos((char *) "-size", argc, argv)) > 0)
    dim = atoi(argv[i + 1]);
  if ((i = ArgPos((char *) "-iter", argc, argv)) > 0)
    total_samples = atoi(argv[i + 1]);
  if ((i = ArgPos((char *) "-alpha", argc, argv)) > 0)
    init_alpha = atof(argv[i + 1]);
  if ((i = ArgPos((char *) "-thread", argc, argv)) > 0) {
    num_threads = atoi(argv[i + 1]);
  }
  if ((i = ArgPos((char *) "-negative", argc, argv)) > 0) {
    negative_k = atoi(argv[i + 1]);
  }

  for (i = 0; i < n_node_type; ++i) {
    for (j = 0; j < n_node_type; ++j) {
      valid_edge[i][j] = false;
    }
  }

  if ((i = ArgPos((char *) "-n_node", argc, argv)) > 0) {
    n_node_type = atoi(argv[i + 1]);
  }

  for (int i = 0; i < n_node_type; ++i) {
    nodes[i] =(struct ClassNode *) calloc(node_count_inc,
        sizeof(struct ClassNode));
    max_sz_node[i] = node_count_inc;
    node_sample_power[i] = sample_power;
  }

  if ((i = ArgPos((char *) "-nodes", argc, argv)) > 0) {
    for (j = 0; j < n_node_type; ++j) {
      strcpy(node_names[j], argv[i + j + 1]);
      if (!strcmp(node_names[j], "author")) {
        author_type = j;
      } else if (!strcmp(node_names[j], "term")) {
        term_type = j;
      } else if (!strcmp(node_names[j], "paper")) {
        paper_type = j;
      }
    }
  }

  if ((i = ArgPos((char *) "-n_edge", argc, argv)) > 0) {
    n_edge_type = atoi(argv[i + 1]);
  }

  if ((i = ArgPos((char *) "-output_folder", argc, argv)) > 0)
    strcpy(output_folder, argv[i + 1]);
  if ((i = ArgPos((char *) "-input_folder", argc, argv)) > 0)
    strcpy(input_folder, argv[i + 1]);
  if ((i = ArgPos((char *) "-embed_folder", argc, argv)) > 0)
    strcpy(embed_folder, argv[i + 1]);
  if ((i = ArgPos((char *) "-weight_folder", argc, argv)) > 0)
    strcpy(weight_folder, argv[i + 1]);

  if ((i = ArgPos((char *) "-edges", argc, argv)) > 0) {
    for (j = 0; j < n_edge_type; ++j) {
      strcpy(edge_names[j], argv[i + j + 1]);
      sprintf(train_network_file[j], "%s/%s.net", input_folder, edge_names[j]);
      printf("Input %d: %s\n", j, train_network_file[j]);
      strcpy(edge_names[j], argv[i + 1 + j]);
      strcpy(str, edge_names[j]);
      int pch = strchr(str, '_') - str;
      str[pch] = '\0';
      strcpy(source, str);
      strcpy(dest, &str[pch + 1]);
      for (k = 0; k < n_node_type; ++k) {
        if (strcmp(source, node_names[k]) == 0) {
          edge_node_type[j][0] = k;
        } else if (strcmp(dest, node_names[k]) == 0) {
          edge_node_type[j][1] = k;
        }
      }
      if (edge_node_type[j][0] == term_type && edge_node_type[j][1] == paper_type) {
        term_paper_edge_type = j;
      }
      valid_edge[edge_node_type[j][0]][edge_node_type[j][1]] = true;
      valid_edge[edge_node_type[j][1]][edge_node_type[j][0]] = true;
    }
  }

  // the regularization parameter for the embeddings
  if ((i = ArgPos((char *) "-beta", argc, argv)) >= 0) {
    for (int j = 0; j < n_node_type; ++j) {
      beta[j] = atof(argv[i + 1 + j]);
    }
  }

  if ((i = ArgPos((char *) "-prior_power", argc, argv)) >= 0) {
    prior_power = atof(argv[i + 1]);
  }

  if ((i = ArgPos((char *) "-sample_power", argc, argv)) >= 0) {
    for (int j = 0; j < n_node_type; ++j) {
      node_sample_power[j] = atof(argv[i + 1 + j]);
    }
  }

  if ((i = ArgPos((char *) "-choose_output", argc, argv)) >= 0) {
    for (int j = 0; j < n_node_type; ++j) {
      output_types[j] = (argv[i + 1][j] == '1');
    }
  }

  if ((i = ArgPos((char *) "-load_embed", argc, argv)) >= 0) {
    printf("Loading embeddings is disabled..\n");
    for (int j = 0; j < n_node_type; ++j) {
      if (argv[i + 1][j] == '1') {
        load_types[j] = true;
      }
    }
  }

  if ((i = ArgPos((char *) "-load_weight", argc, argv)) >= 0) {
    for (int j = 0; j < n_node_type; ++j) {
      if (argv[i + 1][j] == '1') {
        node_weight[j] = true;
      }
    }
  }

  if ((i = ArgPos((char *) "-edge_weight", argc, argv)) >= 0) {
    for (int j = 0; j < n_node_type; ++j) {
      if (argv[i + 1][j] == '1') {
        edge_weight[j] = true;
      } else {
        edge_weight[j] = false;
      }
    }
  }

  if ((i = ArgPos((char *) "-query_file", argc, argv)) >= 0) {
    strcpy(query_file, argv[i + 1]);
  }

  alpha = init_alpha;
  TrainModel();
  return 0;
}


// Copyright András Vukics 2021. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE.txt)
/// \briefFileDefault
#ifndef CPPQEDCORE_UTILS_ODE_GSL_H_INCLUDED
#define CPPQEDCORE_UTILS_ODE_GSL_H_INCLUDED

#include "ArrayTraits.h"
#include "ODE.h"

#include <gsl/gsl_errno.h>
#include <gsl/gsl_odeiv2.h>

#include <cstddef>

namespace cppqedutils {

namespace ode_engine {

template <typename StateType>
class ControlledErrorStepperGSL
{
public:
  using state_type=StateType;
  using deriv_type=state_type;
  using time_type=double;
  using value_type=ElementType_t<StateType>;
  using stepper_category=bno::controlled_stepper_tag;

  ControlledErrorStepperGSL(double epsAbs, double epsRel)
    : control_{gsl_odeiv2_control_standard_new(epsAbs,epsRel,1,1), [] (auto* ptr) {if (ptr) gsl_odeiv2_control_free(ptr);}} {}

private:
  using ValueVector=std::vector<double>;
  using Aux_t=std::tuple<SystemFunctional_t<double,StateType>&,Extents_t<Rank_v<StateType>>>;

public:
  /// Realizes part of the logic of [gsl_odeiv2_evolve_apply](http://www.gnu.org/software/gsl/doc/html/ode-initval.html#c.gsl_odeiv2_evolve_apply)
  /**
   * Differences:
   * - First-same-as-last steppers not supported (at the moment only RKCK is supported)
   * - There is no guard against the stepsize becoming infinitesimal
   */
  template <typename System, typename S>
  auto try_step(System sys, S&& stateInOut, double& time, double& dtTry)
  {
    auto totalExtent=Size<StateType>::_(stateInOut);

    if (!step_) { // this means that tryStep is called for the first time
      step_.reset(gsl_odeiv2_step_alloc(gsl_odeiv2_step_rkck,totalExtent), [] (auto* ptr) {if (ptr) gsl_odeiv2_step_free(ptr);});
      yerr_=std::make_shared<ValueVector>(totalExtent);
      dydt_out_=std::make_shared<ValueVector>(totalExtent);
    }
#ifndef NDEBUG
    else if (!IsStorageContiguous<StateType>::_(stateInOut)) {
      throw NonContiguousStorageException{"ControlledErrorStepperGSL::tryStep"};
    }
    else if (yerr_->size()!=Size<StateType>::_(stateInOut) || dydt_out_->size()!=Size<StateType>::_(stateInOut)) {
      throw std::runtime_error("Dimensionality mismatch in ControlledErrorStepperGSL::tryStep");
    }
#endif // NDEBUG

    SystemFunctional_t<double,StateType> sysVariable=sys; // it’s important to convert `sys` (which can be a lambda) to a variable of known type

    Aux_t aux{sysVariable,Extents<StateType>::_(stateInOut)};

    gsl_odeiv2_system dydt{ControlledErrorStepperGSL::lowLevelSystemFunction,nullptr,totalExtent,&aux};

    auto step_status = gsl_odeiv2_step_apply (step_.get(), time, dtTry, Data<StateType>::_(stateInOut), yerr_->data(), nullptr, dydt_out_->data(), &dydt);

    if (step_status == GSL_EFAULT || step_status == GSL_EBADFUNC) throw std::runtime_error("GSL bad step_status");

    if (step_status != GSL_SUCCESS) {
      dtTry *= 0.5;
      return bno::fail;
    }
    else {
      time+=dtTry;
      /* const auto hadjust_status = */
      gsl_odeiv2_control_hadjust (control_.get(), step_.get(), Data<StateType>::_(stateInOut), yerr_->data(), dydt_out_->data(), &dtTry);
      // if (hadjust_status == GSL_ODEIV_HADJ_DEC) throw std::runtime_error{"Unexpected change of dtTry in ControlledErrorStepperGSL::tryStep"};
      return bno::success;
    }
  }

private:
  static int lowLevelSystemFunction(double t, const double* y, double* dydt, void* aux)
  {
    auto [highLevelSystemFunction,arrayExtents] = *static_cast<Aux_t*>(aux);

    const StateType yInterface(Create_c<StateType>::_(y,arrayExtents));

    StateType dydtInterface(Create<StateType>::_(dydt,arrayExtents));

    highLevelSystemFunction(yInterface,dydtInterface,t);

    return GSL_SUCCESS;
  }

  std::shared_ptr<gsl_odeiv2_step> step_;

  const std::shared_ptr<gsl_odeiv2_control> control_;

  // Needs to be easily copyable, as Steppers are carried around by value
  std::shared_ptr<ValueVector> yerr_, dydt_out_;

};

template <typename StateType>
struct MakeControlledErrorStepper<ControlledErrorStepperGSL<StateType>>
{
  static auto _(double epsRel, double epsAbs)
  {
    return ControlledErrorStepperWithParameters<ControlledErrorStepperGSL<StateType>>{
      {epsRel,epsAbs},epsRel,epsAbs};
  }

  template <typename ParsBase>
  static auto _(const Pars<ParsBase>& p)
  {
    return _(p.epsRel,p.epsAbs);
  }

};

template <typename StateType>
inline const std::string StepperDescriptor<ControlledErrorStepperGSL<StateType>> = "GSL RKCK controlled stepper";

} // ode_engine

template <typename StateType>
using ODE_EngineGSL = ode_engine::Base<ode_engine::ControlledErrorStepperGSL<StateType>>;

} // cppqedutils

#endif // CPPQEDCORE_UTILS_ODE_GSL_H_INCLUDED


#include <stdio.h>
#include <stdlib.h>
#include <cblas.h>
#include <math.h>
#include <string.h>
#include "../inc/knnring.h"
#include "mpi.h"

// Application Entry Point
/*
 * X is this node's points from which distances will be calculated
 * n is the number of points in X and each incoming-outcoming block
 * d is the number of dimensions of each point
 * k is the number of minimum points to calculate
 */
knnresult distrAllkNN(double * X, int n, int d, int k){

	// Timers
	double start = MPI_Wtime();
	double end;

	// Get processes number and process id
	int p, id;
	MPI_Comm_rank(MPI_COMM_WORLD, &id); // Task ID
	MPI_Comm_size(MPI_COMM_WORLD, &p); // # tasks

	// Find IDs of previous and next node in ring
	int prev, nxt;
	if(id == 0)
		prev = p - 1;
	else
		prev = id - 1;
	if(id == p-1)
		nxt = 0;
	else
		nxt = id + 1;

	// MPI variables used for async communication
    MPI_Request reqSend, reqReceive;
    //MPI_Status status;

    // Y matrix for receiving points
	double* Y = calloc(n*d, sizeof(double));

	// Temporary Y matrix, used for trading in nodes with even ids so that we don't overwrite Y before sending it while receiving
	double* tempY = calloc(n*d, sizeof(double));

	// Start pre-fetching and sending data asynchronously
    MPI_Isend(X, n*d, MPI_DOUBLE, nxt, 2, MPI_COMM_WORLD, &reqSend);
    MPI_Irecv(tempY, n*d, MPI_DOUBLE, prev, 2, MPI_COMM_WORLD, &reqReceive);

	// This is the id that corresponds to the node that initially had the current block of points Y
	// It's used to reconstruct the ids array we receive so we don't have to also send ids
	int blockID = id;

	// Generate ids matrix
	int* ids = calloc(n, sizeof(int));
	for(int i = 0; i < n; i++){
		// The first node gets points [0, n], second [n+1, 2n] etc. Node 0 gets the last n points
		if(id != 0)
			ids[i] = (id - 1) * n + i;
		else
			ids[i] = (p-1) * n + i;
	}

	// First calculate using the original dataset, then move on to sending/receiving blocks from others
	knnresult results = kNN(X, X, n, n, d, k);

	// IDs in the knnresult structure are relative. They start from 0 and go up to n-1. We need to map them to the actual ids generated above
	for(int i = 0; i < n * k; i++)
		results.nidx[i] = ids[results.nidx[i]];

	// Temporary results struct, used for calculations in incoming block
	knnresult newResults;

	// Pointers and temporary arrays used for merging results.
	int ptrResults = 0;
	int ptrNewResults = 0;
	double* tempResDis = calloc(n * k, sizeof(double));
	int* tempResIDs = calloc(n * k, sizeof(int));

	// Variables to hold minimum and maximum reduction
	double mindis, maxdis, tempmindis, tempmaxdis;

	// Trade blocks p-1 times in the ring
	for(int i = 0; i < p-1; i++){

		// Wait for sending/receiving to complete before proceeding
		MPI_Wait(&reqSend, MPI_STATUS_IGNORE);
		MPI_Wait(&reqReceive, MPI_STATUS_IGNORE);

		// Write the received points into Y so we can start pre-fetching the next ones in tempY
		memcpy(Y, tempY, n * d * sizeof(double));

		// Last time we go in data doesn't need to be prefetched
		if(i < p-2){
			// Start pre-fetching
			MPI_Isend(Y, n*d, MPI_DOUBLE, nxt, 2, MPI_COMM_WORLD, &reqSend);
			MPI_Irecv(tempY, n*d, MPI_DOUBLE, prev, 2, MPI_COMM_WORLD, &reqReceive);
		}

		// Reconstruct ids array of received block of points based on the node and the number of blocks already traded
		blockID--;
		if(blockID < 0)
			blockID = p-1;
		for(int j = 0; j < n; j++){
			if(blockID != 0)
				ids[j] = (blockID - 1) * n + j;
			else
				ids[j] = (p-1) * n + j;
		}

		// Run calculations on received points
		newResults = kNN(Y, X, n, n, d, k);

		// Again map ids as done initially
		for(int l = 0; l < n * k; l++)
			newResults.nidx[l] = ids[newResults.nidx[l]];

		// Copy points and ids array of current results to temporary arrays to safely alter the results struct bellow
		memcpy(tempResDis, results.ndist, n * k * sizeof(double));
		memcpy(tempResIDs, results.nidx, n * k * sizeof(int));

		// Merge results and newResults arrays in a merge-sort fashion
		// Iterate for each query point
		for(int r = 0; r < n; r++){
			ptrResults = 0;
			ptrNewResults = 0;

			// Iterate for each neighbor
			for(int j = 0; j < k; j++){

				// If the point in older results is closer than the new one, add it, else add the other one and increment the appropriate array pointer
				if(tempResDis[r*k + ptrResults] < newResults.ndist[r*k + ptrNewResults]){
					results.ndist[r*k + j] = tempResDis[r*k + ptrResults];
					results.nidx[r*k + j] = tempResIDs[r*k + ptrResults];
					ptrResults++;
				}else{
					results.ndist[r*k + j] = newResults.ndist[r*k + ptrNewResults];
					results.nidx[r*k + j] = newResults.nidx[r*k + ptrNewResults];
					ptrNewResults++;
				}

			}

		}

	}

	// Find overall minimum and maximum distances of knn neighbors - excluding initial 0s
	mindis = results.ndist[1];
	maxdis = results.ndist[k-1];

	// Locally reduce minimum and maximum
	for(int i = 0; i < n; i++){
		if(results.ndist[k*i] < mindis)
			mindis = results.ndist[k*i + 1];
		if(results.ndist[k*i + k - 1] > maxdis)
			maxdis = results.ndist[k*i + k - 1];
	}

	MPI_Reduce(&mindis, &tempmindis, 1, MPI_DOUBLE, MPI_MIN, 0, MPI_COMM_WORLD);
	MPI_Reduce(&maxdis, &tempmaxdis, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);

	end = MPI_Wtime();
	printf("Execution time for node %d: %f s\n", id, end-start);

	if(id == 0){

		if(mindis < tempmindis)
			tempmindis = mindis;
		if(maxdis > tempmaxdis)
			tempmaxdis = maxdis;
		printf("Minimum distance is %f, maximum is %f\n", tempmindis, tempmaxdis);
		printf("Note: this message is printed twice because the tester runs two tests: one for column major and one for row major in this order.\n");
	}

	return results;
}

// Application Entry Point
knnresult kNN(double * X, double * Y, int n, int m, int d, int k)
{

	// Calculate distances matrix D - D is row-major and nxm
	double* tempD = calculateD(X, Y, n, m, d, k);

	double* D = calloc(m*n, sizeof(double));

	// Transpose D to mxn
	//cblas_dimatcopy(CblasRowMajor, CblasTrans, n, m, 1.0, D, m, n);

	double* identityMat = calloc(n*n, sizeof(double));
	for(int f=0; f<n; f++)
		identityMat[f*n + f] = 1;

	cblas_dgemm(CblasRowMajor, CblasTrans, CblasNoTrans, m, n, n, 1, tempD, m, identityMat, n, 0, D, n);

	// Free memory
	free(identityMat);
	free(tempD);

	// Create results struct
	knnresult results;
	results.k = k;
	results.m = m;
	results.ndist = calloc(m*k, sizeof(double));
	results.nidx = calloc(m*k, sizeof(int));

	// Create ids array for the X array
	int* ids = calloc(n, sizeof(int));

	// K-Select using quickselect to find k smallest distances for each point of Y
	for(int j = 0; j < m; j++){

		// Re-set ids vector before executing quickselect each time
		for(int i = 0; i < n; i++)
				ids[i] = i;

		// Call quickselect on the row of D to sort it up to its k-th element
		kselect(D + j * n, ids, n, k);

		// Quicksort the results
		quickSort(D + j * n, ids, 0, n-1);

		// Write results (point id and distance)
		for(int l = 0; l < k; l++){

			results.ndist[j * k + l] = D[j * n + l];
			results.nidx[j * k + l] = ids[l];
		}
	}

	// Free memory and return results
	free(ids);
	return results;

}

// Function used to calculate D = sqrt(sum(X.^2,2) -2* X*Y.' + sum(Y.^2,2).');
double* calculateD(double * X, double * Y, int n, int m, int d, int k){

		// Temporary variable
		double temp = 0;

		// Distance matrix for results
		double* D = calloc(n*m, sizeof(double));

		// Euclidean norm vectors
		double* normX = calloc(n, sizeof(double));
		double* normY = calloc(m, sizeof(double));

		// Matrice to store -2*X*Y'
		double* XY = calloc(n*m, sizeof(double));

		// Calculate -2*XY (https://software.intel.com/en-us/mkl-tutorial-c-multiplying-matrices-using-dgemm)
		cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasTrans, n, m, d, -2, X, d, Y, d, 0, XY, m);

		// Calculate sum(X.^2,2), sum(Y.^2,2)
		for(int i = 0; i < n; i++){
			temp = cblas_dnrm2(d, X+i*d, 1);
			normX[i] = temp * temp;
		}
		for(int i = 0; i < m; i++){
			temp = cblas_dnrm2(d, Y+i*d, 1);
			normY[i] = temp * temp;
		}

		// XY = sum(X.^2,2) -2* X*Y.'
	        for (int i=0; i<n; i++)
	    		for(int j=0; j<m; j++)
				XY[i*m+j] += normX[i] + normY[j];

		// D = sqrt(sum(X.^2,2) -2* X*Y.' + sum(Y.^2,2).');
		for(int i = 0; i < n*m; i++)
			D[i] = sqrt(fabs(XY[i]));

		// Free memory
		free(normX);
		free(normY);
		free(XY);

		return D;
}

// A utility function to swap two elements
void swap_double(double *a, double *b){
    double t = *a;
    *a = *b;
    *b = t;
}

// A utility function to swap two elements
void swap_int(int *a, int *b){
    int t = *a;
    *a = *b;
    *b = t;
}

// QuickSort Partition function. low and high are the range of indexes in arr where partition should work
int partition (double arr[], int *ids, int low, int high){

    // Select a pivot and initialize flag to position of smallest element before pivot
    double pivot = arr[high];
    int i = (low - 1);

    // Go through the array examining each element
    for (int j = low; j <= high - 1; j++)
    {
        // If current element is smaller than the pivot, increment i and swap it out with the one currently pointed by i
        if (arr[j] < pivot)
        {
            i++;
            // Swap distances and corresponding point ids
            swap_double(&arr[i], &arr[j]);
            swap_int(&ids[i], &ids[j]);
        }
    }

    // Finally place pivot in its correct position in the array and return the position as the middle point
    swap_double(&arr[i + 1], &arr[high]);
    swap_int(&ids[i + 1], &ids[high]);
    return (i + 1);
}

// Returns the median using the QuickSelect algorithm
double kselect(double arr[], int *ids, int length, int k){
	return quickSelect(arr, ids, length, k);
}

// Returns the idx-th element of arr when arr is sorted. idx is the index (starting from 1) of the point we want to find when the array is sorted.
double quickSelect(double arr[], int *ids, int length, int idx){

    // Check to end recursion
    if (length == 1){
        return arr[0];
    }

    // Select last array element as pivot
    double pivot = arr[length - 1];
    // Get index of pivot after we partition the array
    int pivotIndex = partition(arr, ids, 0, length - 1);

    // Create the higher and lower arrays that occur after partitioning in QuickSort fashion
    int lowerLength = pivotIndex;
    pivotIndex++;
    int higherLength = (length - (lowerLength + 1));
    // At this point pivotIndex, lowerLength and higherLength all start from 1 not 0

    // Variable to store result of following recursive calls
    double result = 0;

    // This means that the point we're looking (median in our case) is in the lower partition
    if (idx <= lowerLength){
        result = quickSelect(arr, ids, lowerLength, idx);
    }
    // This means that idx-th element is our pivot point
    else if(idx == pivotIndex){
        result = pivot;
    }
    // This means that the median is in the higher partition
    else{
        result = quickSelect(arr + pivotIndex, ids + pivotIndex, higherLength, idx - pivotIndex);
    }

    // Return result
    return result;
}

// Simple quicksort implementation that also sorts the ids array according to the way the main array was sorted
void quickSort(double arr[], int *ids, int low, int high)
{
    if (low < high)
    {
        // idx is the partition point
        int idx = partition(arr, ids, low, high);

        // Divide and conquer
        quickSort(arr, ids, low, idx - 1);
        quickSort(arr, ids, idx + 1, high);
    }
}


/* C functions for running vbgmm from Cython*/

/*System includes*/
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <sys/stat.h>
#include <float.h>

/*GSL includes*/
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_sf.h>
#include <gsl/gsl_integration.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_roots.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_statistics_double.h>
#include <gsl/gsl_fft_complex.h>
#include <gsl/gsl_complex_math.h>
#include <gsl/gsl_multimin.h>
#include <gsl/gsl_deriv.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_cblas.h>
#include <gsl/gsl_blas.h>
#include <pthread.h>
#include <gsl/gsl_sf_exp.h>
#include <omp.h>

/*User includes*/
#include "c_vbgmm_fit.h"

void c_vbgmm_fit (double* adX, int nN, int nD, int nK, int seed, int* anAssign, int nThreads, int nIter)
{
    int debug = 0;
    int bAssign = 0;

    if (nIter < 1){
        nIter = DEF_MAX_ITER;
    }

    driverMP(adX, nN, nD, anAssign, nK, seed, nIter, DEF_EPSILON, debug, bAssign, nThreads);

    return;
}

int driverMP(double *adX, int nN, int nD, int *anAssign, int nKStart, unsigned long lSeed,
                                        int nMaxIter, double dEpsilon, int debug, int bAssign, int nThreads)
{
    t_Params           tParams;
    t_Data             tData;
    gsl_rng            *ptGSLRNG     = NULL;
    const gsl_rng_type *ptGSLRNGType = NULL;
    t_VBParams tVBParams;
    t_Cluster  *ptCluster = NULL;
    int i = 0, nK = nKStart, nNthreads = 0;
    int nT = 0;
    char *szCOutFile = NULL;

    /*initialise GSL RNG*/
    gsl_rng_env_setup();

    gsl_set_error_handler_off();

    ptGSLRNGType = gsl_rng_default;
    ptGSLRNG     = gsl_rng_alloc(ptGSLRNGType);

    /*set OMP thread number*/
    nNthreads = nThreads;
    nT = nN / 32  + 1;
    printf("%d %d %d\n",nN,nT,nNthreads);
    if (nT < nNthreads){
        nNthreads = nT;
    }

    omp_set_num_threads(nNthreads);
    fprintf(stderr,"Setting %d OMP threads\n",nNthreads);

    /*set clusters params*/
    tParams.nKStart  = nKStart;
    tParams.nMaxIter = nMaxIter;
    tParams.dEpsilon = dEpsilon;
    tParams.lSeed    = lSeed;

    fprintf(stderr,"Generate input data\n");
    fflush(stderr);
    generateInputData(adX, nN, nD, &tData);

    setVBParams(&tVBParams, &tData);

    if(debug>0){
        szCOutFile = (char *) malloc(sizeof(char)*MAX_FILE_NAME_LENGTH);

	    sprintf(szCOutFile,"%sr%d.csv",DEF_FILE_STUB,0);
	}

    if(bAssign > 0){
        nK = 0;

        for(i = 0; i < nN; i++){
            if(anAssign[i] > nK){
                nK = anAssign[i];
            }
        }
    }
    ptCluster = malloc(sizeof(t_Cluster));
    allocateCluster(ptCluster,nN,nK,nD,&tData,lSeed,nMaxIter,dEpsilon,szCOutFile);

    ptCluster->bAssign = bAssign;
    if(bAssign > 0){
        for(i = 0; i < nN; i++){
            ptCluster->anMaxZ[i] = anAssign[i];
        }
    }

    ptCluster->ptVBParams = &tVBParams;

    ptCluster->nThread = 0;

    fitEM_MP((void *) ptCluster);

    compressCluster(ptCluster);

    calcCovarMatrices(ptCluster,&tData);

    for(i = 0; i < nN; i++){
      anAssign[i] = ptCluster->anMaxZ[i];
    }

    if(debug>0){
        free(szCOutFile);
	}

    /*free up memory in data object*/
    destroyData(&tData);

    /*free up best BIC clusters*/

    destroyCluster(ptCluster);
    free(ptCluster);

    gsl_rng_free(ptGSLRNG);
    gsl_matrix_free(tVBParams.ptInvW0);

    return EXIT_SUCCESS;

    memoryError:
        fprintf(stderr, "Failed allocating memory in driver\n");
        fflush(stderr);
        exit(EXIT_FAILURE);
}

void generateInputData(double *adX, int nN, int nD, t_Data *ptData)
{
    double  **aadX = NULL;
    int  i = 0, j = 0;

    /*allocate memory for data matrix*/
    aadX = (double **) malloc(nN*sizeof(double*));
    if(!aadX)
        goto memoryError;

    for(i = 0; i < nN; i++){
        aadX[i] = (double *) malloc(nD*sizeof(double));
        if(!aadX[i])
	        goto memoryError;
    }

    for(i = 0; i < nN; i++){
        for(j = 0; j < nD; j++){
            aadX[i][j] = adX[i*nD + j];
        }
    }
    ptData->nD = nD;
    ptData->nN = nN;
    ptData->aadX = aadX;
    return;

 memoryError:
    fprintf(stderr, "Failed allocating memory in readInputData\n");
    fflush(stderr);
    exit(EXIT_FAILURE);
}

void destroyData(t_Data *ptData)
{
    int nN = ptData->nN;
    int i = 0;

    for(i = 0; i < nN; i++){
        free(ptData->aadX[i]);
    }
    free(ptData->aadX);

    return;
}

void calcSampleVar(t_Data *ptData,double *adVar, double *adMu)
{
    double **aadX = ptData->aadX;
    int i = 0, n = 0;
    int nD = ptData->nD, nN = ptData->nN;
    /*sample means*/
    double dN = (double) nN;

    for(i = 0; i < nD; i++){
        adMu[i] = 0.0;
        adVar[i] = 0.0;
    }

    for(i = 0; i < nD; i++){
        for(n = 0; n < nN; n++){
            adMu[i] += aadX[n][i];
            adVar[i] += aadX[n][i]*aadX[n][i];
        }

        adMu[i] /= dN;

        adVar[i] = (adVar[i] - dN*adMu[i]*adMu[i])/(dN - 1.0);
    }

    return;
}

void setVBParams(t_VBParams *ptVBParams, t_Data *ptData)
{
    int i = 0, nD = ptData->nD;
    double adVar[nD], adMu[nD];

    ptVBParams->dBeta0 = DEF_BETA0;
    ptVBParams->dNu0 = (double) nD;
    ptVBParams->ptInvW0 = gsl_matrix_alloc(nD,nD);

    calcSampleVar(ptData,adVar, adMu);
    gsl_matrix_set_zero (ptVBParams->ptInvW0);

    for(i = 0; i < nD; i++){
        double dRD = adVar[i]*((double) nD);

        gsl_matrix_set(ptVBParams->ptInvW0,i,i,dRD);
    }

    ptVBParams->dLogWishartB = dLogWishartB(ptVBParams->ptInvW0, nD, ptVBParams->dNu0, TRUE);
}

void allocateCluster(t_Cluster *ptCluster, int nN, int nK, int nD, t_Data *ptData, long lSeed, int nMaxIter, double dEpsilon, char *szCOutFile)
{
    int i = 0, j = 0, k = 0;

    ptCluster->szCOutFile = szCOutFile;
    ptCluster->ptVBParams = NULL;
    ptCluster->lSeed = lSeed;
    ptCluster->nMaxIter = nMaxIter;
    ptCluster->dEpsilon = dEpsilon;
    ptCluster->ptData = ptData;

    ptCluster->nN = nN;
    ptCluster->nK = nK;
    ptCluster->nKSize = nK;
    ptCluster->nD = nD;

    ptCluster->dVBL = 0.0;

    ptCluster->anMaxZ = (int *) malloc(nN*sizeof(int)); /*destroyed*/
    if(!ptCluster->anMaxZ)
        goto memoryError;

    ptCluster->anW = (int *) malloc(nK*sizeof(int)); /*destroyed*/
    if(!ptCluster->anW)
        goto memoryError;

    for(i = 0; i < nN; i++){
        ptCluster->anMaxZ[i] = NOT_SET;
    }

    for(i = 0; i < nK; i++){
        ptCluster->anW[i] = 0;
    }

    ptCluster->aadZ = (double **) malloc(nN*sizeof(double *)); /*destroyed*/
    if(!ptCluster->aadZ)
        goto memoryError;

    for(i = 0; i < nN; i++){
        ptCluster->aadZ[i] = (double *) malloc(nK*sizeof(double)); /*destroyed*/
        if(!ptCluster->aadZ[i])
            goto memoryError;

        for(j = 0; j < nK; j++){
            ptCluster->aadZ[i][j] = 0.0;
        }
    }

    ptCluster->adLDet = (double *) malloc(nK*sizeof(double)); /*all*/
    ptCluster->adPi   = (double *) malloc(nK*sizeof(double));
    ptCluster->adBeta = (double *) malloc(nK*sizeof(double));
    ptCluster->adNu   = (double *) malloc(nK*sizeof(double)); /*destroyed*/

    if(!ptCluster->adLDet || !ptCluster->adPi)
        goto memoryError;

    if(!ptCluster->adBeta || !ptCluster->adNu)
        goto memoryError;

    for(k = 0; k < nK; k++){
        ptCluster->adLDet[k] = 0.0;
        ptCluster->adPi[k]   = 0.0;
        ptCluster->adBeta[k] = 0.0;
        ptCluster->adNu[k] = 0.0;
    }

    ptCluster->aadMu = (double **) malloc(nK*sizeof(double *));
    if(!ptCluster->aadMu)
        goto memoryError;

    ptCluster->aadM = (double **) malloc(nK*sizeof(double *));
    if(!ptCluster->aadM)
        goto memoryError;

    for(i = 0; i < nK; i++){
        ptCluster->aadM[i] = (double*) malloc (nD*sizeof(double));
        if(!ptCluster->aadM[i])
            goto memoryError;

        ptCluster->aadMu[i] = (double*) malloc (nD*sizeof(double));
        if(!ptCluster->aadMu[i])
            goto memoryError;
    }

    ptCluster->aptSigma = (gsl_matrix **) malloc(nK*sizeof(gsl_matrix *));
    if(!ptCluster->aptSigma)
        goto memoryError;

    for(i = 0; i < nK ; i++){
        ptCluster->aptSigma[i] = (gsl_matrix*) gsl_matrix_alloc (nD, nD);
    }

    ptCluster->aptCovar = (gsl_matrix **) malloc(nK*sizeof(gsl_matrix *));
    if(!ptCluster->aptCovar)
        goto memoryError;

    for(i = 0; i < nK ; i++){
        ptCluster->aptCovar[i] = (gsl_matrix*) gsl_matrix_alloc (nD, nD);
    }

    return;

    memoryError:
    fprintf(stderr, "Failed allocating memory in allocateCluster\n");
    fflush(stderr);
    exit(EXIT_FAILURE);
}

void destroyCluster(t_Cluster* ptCluster)
{
  int i = 0, nN = ptCluster->nN, nKSize = ptCluster->nKSize;

  if(ptCluster->szCOutFile != NULL){
	  free(ptCluster->szCOutFile);
  }

  free(ptCluster->anMaxZ);

  free(ptCluster->anW);

  for(i = 0; i < nN; i++){
    free(ptCluster->aadZ[i]);
  }
  free(ptCluster->aadZ);

  free(ptCluster->adLDet);
  free(ptCluster->adPi);
  free(ptCluster->adBeta);
  free(ptCluster->adNu);

  for(i = 0; i < nKSize; i++){
    free(ptCluster->aadMu[i]);
    free(ptCluster->aadM[i]);
  }

  free(ptCluster->aadMu);
  free(ptCluster->aadM);

  for(i = 0; i < nKSize ; i++){
    gsl_matrix_free(ptCluster->aptSigma[i]);
    gsl_matrix_free(ptCluster->aptCovar[i]);
  }
  free(ptCluster->aptSigma);
  free(ptCluster->aptCovar);
  return;
}

void* fitEM_MP(void *pvCluster)
{
    t_Cluster          *ptCluster = (t_Cluster *) pvCluster;
    gsl_rng            *ptGSLRNG     = NULL;
    const gsl_rng_type *ptGSLRNGType = NULL;
    int i = 0, k = 0;
    /*initialise GSL RNG*/
    ptGSLRNGType = gsl_rng_default;
    ptGSLRNG     = gsl_rng_alloc(ptGSLRNGType);

    gsl_rng_set (ptGSLRNG, ptCluster->lSeed);

    if(ptCluster->bAssign == FALSE){
        initKMeans(ptGSLRNG, ptCluster, ptCluster->ptData);
    }
    else{
        for(i = 0; i < ptCluster->nN; i++){
            for(k = 0; k < ptCluster->nK; k++){
                ptCluster->aadZ[i][k] = 0.0;
            }
            ptCluster->aadZ[i][ptCluster->anMaxZ[i]] = 1.0;
        }
        performMStepMP(ptCluster, ptCluster->ptData);
    }

    gmmTrainVB_MP(ptCluster, ptCluster->ptData);

    gsl_rng_free(ptGSLRNG);

    return NULL;
}

void compressCluster(t_Cluster *ptCluster)
{
    int i = 0, k = 0, nNewK = 0, nN = ptCluster->nN;
    double **aadNewZ = NULL, dN = (double) nN;

    for(i = 0; i < ptCluster->nK; i++){
        if(ptCluster->adPi[i] > 0.0){
            nNewK++;
        }
    }

    aadNewZ = (double **) malloc(nN*sizeof(double *));
    if(!aadNewZ)
        goto memoryError;

    for(i = 0; i < nN; i++){
        aadNewZ[i] = (double *) malloc(nNewK*sizeof(double));
        if(!aadNewZ[i])
            goto memoryError;
    }

    for(i = 0; i < nN; i++){
        int nC = 0;
        for(k = 0; k < ptCluster->nK; k++){
            if(ptCluster->adPi[k] > 0.0){
	            aadNewZ[i][nC] = ptCluster->aadZ[i][k];
	            nC++;
            }
        }
    }

    for(i = 0; i < nN; i++){
        free(ptCluster->aadZ[i]);
    }
    free(ptCluster->aadZ);

    /*reset Z and K*/
    ptCluster->aadZ = aadNewZ;
    ptCluster->nK = nNewK;

    /*recalculate Pi*/
    for(k = 0; k < ptCluster->nK; k++){
        ptCluster->adPi[k] = 0.0;
        for(i = 0; i < nN; i++){
            ptCluster->adPi[k] += ptCluster->aadZ[i][k];
        }
        ptCluster->adPi[k] /= dN;
    }

    /*assign to best clusters*/
    for(i = 0; i < nN; i++){
        double dMaxZ = ptCluster->aadZ[i][0];
        int    nMaxK = 0;
        for(k = 1; k < ptCluster->nK; k++){
            if(ptCluster->aadZ[i][k] > dMaxZ){
	            nMaxK = k;
	            dMaxZ = ptCluster->aadZ[i][k];
            }
        }
        ptCluster->anMaxZ[i] = nMaxK;
    }

    for(k = 0; k < ptCluster->nK; k++){
        ptCluster->anW[k] = 0;
    }

    for(i = 0; i < nN; i++){
        ptCluster->anW[ptCluster->anMaxZ[i]]++;
    }

    return;

    memoryError:
    fprintf(stderr, "Failed allocating memory in main\n");
    fflush(stderr);
    exit(EXIT_FAILURE);
}

double decomposeMatrix(gsl_matrix *ptSigmaMatrix, int nD)
{
  double dDet = 0.0;
  int status;
  int l = 0;

  status = gsl_linalg_cholesky_decomp(ptSigmaMatrix);

  if(status == GSL_EDOM){
    fprintf(stderr,"Failed Cholesky decomposition in decomposeMatrix\n");
    fflush(stderr);
    exit(EXIT_FAILURE);
  }
  else{
    for(l = 0; l < nD; l++){
      double dT = gsl_matrix_get(ptSigmaMatrix,l,l);
      dDet += 2.0*log(dT);
    }
    gsl_linalg_cholesky_invert(ptSigmaMatrix);
    return dDet;
  }
}

double mstep(int k, double *adMu, double* adM, int nN, int nD, double** aadZ, double** aadX, double *pdBeta, double *pdNu, double *pdLDet, t_VBParams *ptVBParams, gsl_matrix *ptCovarK, gsl_matrix *ptSigmaMatrix)
{
    double dPi = 0.0, dBeta = 0.0, dLDet = 0.0, dNu = 0.0;
    int i = 0, j = 0, l = 0, m = 0;
    double      dF = 0.0;
    double **aadCovar = NULL;
    double **aadInvWK = NULL;

    aadCovar = (double **) malloc(nD*sizeof(double*));
    if(!aadCovar)
        goto memoryError;

    for(i = 0; i < nD; i++){
        aadCovar[i] = (double *) malloc(nD*sizeof(double));
        if(!aadCovar[i])
            goto memoryError;
    }

    aadInvWK = (double **) malloc(nD*sizeof(double*));
    if(!aadInvWK)
        goto memoryError;

    for(i = 0; i < nD; i++){
        aadInvWK[i] = (double *) malloc(nD*sizeof(double));
        if(!aadInvWK[i])
            goto memoryError;
    }

    /*recompute mixture weights and means*/
    for(j = 0; j < nD; j++){
        adMu[j] = 0.0;
        for(l = 0; l < nD; l++){
            aadCovar[j][l] = 0.0;
            aadInvWK[j][l] = 0.0;
        }
    }

    for(i = 0; i < nN; i++){
        if(aadZ[i][k] > MIN_Z){
            dPi += aadZ[i][k];
            for(j = 0; j < nD; j++){
                adMu[j] += aadZ[i][k]*aadX[i][j];
            }
        }
    }

    /*normalise means*/
    if(dPi > MIN_PI){
        /*Equation 10.60*/
        dBeta = ptVBParams->dBeta0 + dPi;

        for(j = 0; j < nD; j++){
            /*Equation 10.61*/
            adM[j] = adMu[j]/dBeta;
            adMu[j] /= dPi;
        }

        dNu = ptVBParams->dNu0 + dPi;

        /*calculate covariance matrices*/
        for(i = 0; i < nN; i++){
            if(aadZ[i][k] > MIN_Z){
                double adDiff[nD];

                for(j = 0; j < nD; j++){
                    adDiff[j] = aadX[i][j] - adMu[j];
                }

                for(l = 0; l < nD; l++){
                    for(m = 0; m <=l ; m++){
                        aadCovar[l][m] += aadZ[i][k]*adDiff[l]*adDiff[m];
                    }
                }
            }
        }

        for(l = 0; l < nD; l++){
            for(m = l + 1; m < nD; m++){
                aadCovar[l][m] = aadCovar[m][l];
            }
        }

        /*save sample covariances for later use*/
        for(l = 0; l < nD; l++){
            for(m = 0; m < nD; m++){
                double dC = aadCovar[l][m] / dPi;
                gsl_matrix_set(ptCovarK,l,m,dC);
            }
        }

        /*Now perform equation 10.62*/
        dF = (ptVBParams->dBeta0*dPi)/dBeta;
        for(l = 0; l < nD; l++){
            for(m = 0; m <= l; m++){
                aadInvWK[l][m] = gsl_matrix_get(ptVBParams->ptInvW0, l,m) + aadCovar[l][m] + dF*adMu[l]*adMu[m];
            }
        }

        for(l = 0; l < nD; l++){
            for(m = 0; m <= l ; m++){
                aadCovar[l][m] /= dPi;
                gsl_matrix_set(ptSigmaMatrix, l, m, aadInvWK[l][m]);
                gsl_matrix_set(ptSigmaMatrix, m, l, aadInvWK[l][m]);
            }
       }

    /*Implement Equation 10.65*/
      dLDet = ((double) nD)*log(2.0);

      for(l = 0; l < nD; l++){
        double dX = 0.5*(dNu - (double) l);
        dLDet += gsl_sf_psi (dX);
      }

      dLDet -= decomposeMatrix(ptSigmaMatrix,nD);
    }
    else{
      /*Equation 10.60*/
      dPi = 0.0;

      dBeta = ptVBParams->dBeta0;

      for(j = 0; j < nD; j++){
        /*Equation 10.61*/
        adM[j] = 0.0;
        adMu[j] = 0.0;
      }

      dNu = ptVBParams->dNu0;

      for(l = 0; l < nD; l++){
        for(m = 0; m <= l; m++){
          aadInvWK[l][m] = gsl_matrix_get(ptVBParams->ptInvW0, l,m);
        }
      }

      for(l = 0; l < nD; l++){
        for(m = 0; m <= l ; m++){
            aadInvWK[l][m] = gsl_matrix_get(ptVBParams->ptInvW0, l,m);
        }
      }

      for(l = 0; l < nD; l++){
        for(m = 0; m <= l ; m++){
            gsl_matrix_set(ptSigmaMatrix, l, m, aadInvWK[l][m]);
            gsl_matrix_set(ptSigmaMatrix, m, l, aadInvWK[l][m]);
        }
      }

      /*Implement Equation 10.65*/
      dLDet = ((double) nD)*log(2.0);

      for(l = 0; l < nD; l++){
        double dX = 0.5*(dNu - (double) l);
        dLDet += gsl_sf_psi (dX);
      }

      dLDet -= decomposeMatrix(ptSigmaMatrix,nD);
    }

    /*free up memory*/
    for(i = 0; i < nD; i++){
        free(aadCovar[i]);
        free(aadInvWK[i]);
    }

    free(aadCovar);
    free(aadInvWK);

    (*pdBeta) = dBeta;
    (*pdNu)   = dNu;
    (*pdLDet) = dLDet;
    return dPi;

    memoryError:
        fprintf(stderr, "Failed allocating memory in mstep\n");
        fflush(stderr);
        exit(EXIT_FAILURE);
}

void performMStepMP(t_Cluster *ptCluster, t_Data *ptData){
    int k = 0;
    int nN = ptData->nN, nK = ptCluster->nK, nD = ptData->nD;
    double **aadZ = ptCluster->aadZ,**aadX = ptData->aadX;
    double **aadCovar = NULL, **aadInvWK = NULL;
    t_VBParams *ptVBParams = ptCluster->ptVBParams;

    /*perform M step*/
#pragma omp parallel for
    for(int k = 0; k < nK; k++){ /*loop components*/
        double dPi = 0.0, dBeta = 0.0, dNu = 0.0, dLDet = 0.0;

        dPi = mstep(k, ptCluster->aadMu[k],ptCluster->aadM[k], nN, nD, aadZ, aadX, &dBeta, &dNu, &dLDet, ptVBParams, ptCluster->aptCovar[k], ptCluster->aptSigma[k]);

        ptCluster->adPi[k] = dPi;
        ptCluster->adBeta[k] = dBeta;
        ptCluster->adNu[k] = dNu;
        ptCluster->adLDet[k] = dLDet;
    }

    /*Normalise pi*/

    if(1){
        double dNP = 0.0;

        for(k = 0; k < nK; k++){
            dNP += ptCluster->adPi[k];
        }

        for(k = 0; k < nK; k++){
            ptCluster->adPi[k] /= dNP;
        }
    }

    return;

    memoryError:
        fprintf(stderr, "Failed allocating memory in performMStep\n");
        fflush(stderr);
        exit(EXIT_FAILURE);
}

void initKMeans(gsl_rng *ptGSLRNG, t_Cluster *ptCluster, t_Data *ptData)
{
    /*very simple initialisation assign each data point to random cluster*/
    int i = 0, k = 0, nN = ptData->nN, nK = ptCluster->nK, nD = ptData->nD;
    double **aadMu = ptCluster->aadMu, **aadX = ptData->aadX;
    int *anMaxZ = ptCluster->anMaxZ, *anW = ptCluster->anW, nChange = nN;
    int nIter = 0, nMaxIter = ptCluster->nMaxIter;
    for(i = 0; i < nN; i++){
        int nIK = gsl_rng_uniform_int (ptGSLRNG, nK);
        ptCluster->anMaxZ[i] = nIK;
        anW[nIK]++;
    }

    updateMeans(ptCluster, ptData);

    while(nChange > 0 && nIter < nMaxIter){
        nChange = 0;
        /*reassign vectors*/
        for(i = 0; i < nN; i++){
        double dMinDist = DBL_MAX;
        int    nMinK = NOT_SET;

        for(k = 0; k < nK; k++){
	        double dDist = calcDist(aadX[i],aadMu[k],nD);

	        if(dDist < dMinDist){
	            nMinK = k;
	            dMinDist = dDist;
	        }
      }

      if(nMinK != anMaxZ[i]){
        int nCurr = anMaxZ[i];
	    nChange++;
	    anW[nCurr]--;
	    anW[nMinK]++;
	    anMaxZ[i] = nMinK;

	    /*check for empty clusters*/
	    if(anW[nCurr] == 0){
	        int nRandI =  gsl_rng_uniform_int (ptGSLRNG, nN);
	        int nKI = 0;
	        /*select at random from non empty clusters*/

	        while(anW[anMaxZ[nRandI]] == 1){
	            nRandI =  gsl_rng_uniform_int (ptGSLRNG, nN);
	        }

	        nKI = anMaxZ[nRandI];
	        anW[nKI]--;
	        anW[nCurr] = 1;
	        anMaxZ[nRandI] = nCurr;
	    }
    }
    }
    //printf("%d %d\n",nIter,nChange);
    nIter++;
    updateMeans(ptCluster, ptData);
  }

  for(i = 0; i < nN; i++){
    for(k = 0; k < nK; k++){
      ptCluster->aadZ[i][k] = 0.0;
    }
    ptCluster->aadZ[i][anMaxZ[i]] = 1.0;
  }

  performMStepMP(ptCluster, ptData);
  return;
}

double eqnA(int nD, gsl_matrix *ptCovarK, gsl_matrix *ptSigmaK, double *adMuK, double *adMK, double dLDetK, double dNuK, double logd2Pi, double dBetaK,double dNK)
{
    int l = 0;
    gsl_matrix *ptRes  = gsl_matrix_alloc(nD,nD);
    gsl_vector *ptDiff = gsl_vector_alloc(nD);
    double dT1 = 0.0, dT2 = 0.0, dF = 0.0;
    gsl_vector *ptR = gsl_vector_alloc(nD);
    double dD = (double) nD;
    double dRet = 0.0;

    gsl_blas_dgemm (CblasNoTrans, CblasNoTrans, 1.0,ptCovarK,ptSigmaK,0.0,ptRes);

    for(l = 0; l < nD; l++){
	    dT1 += gsl_matrix_get(ptRes,l,l);
    }

    for(l = 0; l < nD; l++){
        gsl_vector_set(ptDiff,l,adMuK[l] - adMK[l]);
    }

    gsl_blas_dsymv (CblasLower, 1.0, ptSigmaK, ptDiff, 0.0, ptR);

    gsl_blas_ddot (ptDiff, ptR, &dT2);

    dF = dLDetK - dNuK*(dT1 + dT2) - dD*(logd2Pi + (1.0/dBetaK));

    dRet = 0.5*dNK*dF;

    gsl_matrix_free(ptRes);
    gsl_vector_free(ptDiff);
    gsl_vector_free(ptR);

    return dRet;
}

double eqnB(int nD, gsl_matrix *ptInvW0, gsl_matrix *ptSigmaK, double* adMK, double dBeta0, double d2Pi, double dLDetK, double dBetaK, double dNuK, double dNu0)
{
    int l = 0;
    double dD = (double) nD;
    gsl_matrix *ptRes  = gsl_matrix_alloc(nD,nD);
    gsl_vector *ptDiff = gsl_vector_alloc(nD);
    gsl_vector *ptR = gsl_vector_alloc(nD);
    double dT1 = 0.0, dT2 = 0.0, dF = 0.0;
    double dRet = 0.0;

    gsl_blas_dgemm (CblasNoTrans, CblasNoTrans, 1.0,ptInvW0,ptSigmaK,0.0,ptRes);

    for(l = 0; l < nD; l++){
        dT1 += gsl_matrix_get(ptRes,l,l);
    }

    for(l = 0; l < nD; l++){
	    gsl_vector_set(ptDiff,l,adMK[l]);
    }

    gsl_blas_dsymv (CblasLower, 1.0, ptSigmaK, ptDiff, 0.0, ptR);

    gsl_blas_ddot (ptDiff, ptR, &dT2);

    dF = dD*log(dBeta0/d2Pi) + dLDetK - ((dD*dBeta0)/dBetaK) - dBeta0*dNuK*dT2 - dNuK*dT1;

    dRet = 0.5*(dF + (dNu0 - dD - 1.0)*dLDetK);

    gsl_matrix_free(ptRes);
    gsl_vector_free(ptDiff);
    gsl_vector_free(ptR);

    return dRet;
}

double calcVBL_MP(t_Cluster* ptCluster)
{
    int nN = ptCluster->nN;
    int nK = ptCluster->nK, nD = ptCluster->nD;
    double dBishop1 = 0.0, dBishop2 = 0.0, dBishop3 = 0.0, dBishop4 = 0.0, dBishop5 = 0.0; /*Bishop equations 10.71...*/
    double dD = (double) nD;
    double** aadMu = ptCluster->aadMu, **aadM = ptCluster->aadM, **aadZ = ptCluster->aadZ;
    double* adBeta = ptCluster->adBeta, *adNu = ptCluster->adNu, *adLDet = ptCluster->adLDet, *adPi = ptCluster->adPi;
    double adNK[nK], adRet[nK];
    double d2Pi = 2.0*M_PI, logd2Pi = log(d2Pi), dBeta0 = ptCluster->ptVBParams->dBeta0, dNu0 = ptCluster->ptVBParams->dNu0, dRet = 0.0;
    double dK = 0.0;

    for(int k = 0; k < nK; k++){
        adNK[k] = 0.0;
    }

    /*Equation 10.72*/
    for(int i = 0; i < nN; i++){
        for(int k = 0; k < nK; k++){
            adNK[k] += aadZ[i][k];
            if(adPi[k] > 0.0){
	            dBishop2 += aadZ[i][k]*log(adPi[k]);
            }
        }
    }

    for(int k = 0; k < nK; k++){
        if(adNK[k] > 0.0){
            dK++;
        }
    }

    /*Equation 10.71*/
#pragma omp parallel for
    for(int k = 0; k < nK; k++){
        if(adNK[k] > 0.0){
            adRet[k] = eqnA(nD, ptCluster->aptCovar[k], ptCluster->aptSigma[k], aadMu[k],         aadM[k], adLDet[k], adNu[k], logd2Pi, adBeta[k],adNK[k]);
        }
        else{
            adRet[k] = 0.0;
        }
    }

    for(int k = 0; k < nK; k++){
        dBishop1 += adRet[k];
    }

    /*Equation 10.74*/
#pragma omp parallel for
    for(int k = 0; k < nK; k++){
        if(adNK[k] > 0.0){
            adRet[k] = eqnB(nD, ptCluster->ptVBParams->ptInvW0, ptCluster->aptSigma[k], aadM[k], ptCluster->ptVBParams->dBeta0, d2Pi, adLDet[k], adBeta[k], adNu[k],ptCluster->ptVBParams->dNu0);
        }
    }

    for(int k = 0; k < nK; k++){
        dBishop3 += adRet[k];
    }

    dBishop3 += dK*ptCluster->ptVBParams->dLogWishartB;

    /*Equation 10.75*/
    for(int i = 0; i < nN; i++){
        for(int k = 0; k < nK; k++){
            if(aadZ[i][k] > 0.0){
	            dBishop4 += aadZ[i][k]*log(aadZ[i][k]);
            }
        }
    }

    /*Equation 10.77*/
    for(int k = 0; k < nK; k++){
        if(adNK[k] > 0.0){
            dBishop5 += 0.5*adLDet[k] + 0.5*dD*log(adBeta[k]/d2Pi) - 0.5*dD - dWishartExpectLogDet(ptCluster->aptSigma[k], adNu[k], nD);
        }
    }

    dRet = dBishop1 + dBishop2 + dBishop3 - dBishop4 - dBishop5;

    return dRet;
}

void calcZ_MP(t_Cluster* ptCluster, t_Data *ptData){
    double **aadX = ptData->aadX, **aadZ = ptCluster->aadZ;
    int nK = ptCluster->nK, nD = ptCluster->nD, nN = ptData->nN;
    double dD = (double) nD;
    double** aadM = ptCluster->aadM, *adPi = ptCluster->adPi;

#pragma omp parallel for
    for(int i = 0; i < nN; i++){
        double dMinDist = DBL_MAX;
        double dTotalZ  = 0.0;
        double dNTotalZ = 0.0;
        double adDist[nK];
        int k = 0, l = 0;
        gsl_vector *ptDiff = gsl_vector_alloc(nD);
        gsl_vector *ptRes = gsl_vector_alloc(nD);

        for(k = 0; k < nK; k++){
            if(adPi[k] > 0.){
                /*set vector to data point*/
                for(l = 0; l < nD; l++){
                    gsl_vector_set(ptDiff,l,aadX[i][l] - aadM[k][l]);
                }

                gsl_blas_dsymv (CblasLower, 1.0, ptCluster->aptSigma[k], ptDiff, 0.0, ptRes);

                gsl_blas_ddot (ptDiff, ptRes, &adDist[k]);

                adDist[k] *= ptCluster->adNu[k];

                adDist[k] -= ptCluster->adLDet[k];

                adDist[k] += dD/ptCluster->adBeta[k];

                if(adDist[k] < dMinDist){
                    dMinDist = adDist[k];
                }
            }
        }

        for(k = 0; k < nK; k++){
            if(adPi[k] > 0.){
                aadZ[i][k] = adPi[k]*exp(-0.5*(adDist[k]-dMinDist));
                dTotalZ += aadZ[i][k];
            }
            else{
                aadZ[i][k] = 0.0;
            }
        }

        for(k = 0; k < nK; k++){
            double dF = aadZ[i][k] / dTotalZ;
            if(dF < MIN_Z){
                aadZ[i][k] = 0.0;
            }
            dNTotalZ += aadZ[i][k];
        }
        if(dNTotalZ > 0.){
            for(k = 0; k < nK; k++){
                aadZ[i][k] /= dNTotalZ;
            }
        }
        gsl_vector_free(ptRes);
        gsl_vector_free(ptDiff);
    }

    return;
}

void gmmTrainVB_MP(t_Cluster *ptCluster, t_Data *ptData)
{
    int i = 0, k = 0,nIter = 0;
    int nN = ptData->nN, nK = ptCluster->nK;
    /*change in log-likelihood*/
    double dLastVBL = 0.0, dDelta = DBL_MAX;
    double   **aadZ = ptCluster->aadZ;
    int    nMaxIter = ptCluster->nMaxIter;
    double dEpsilon = ptCluster->dEpsilon;
    FILE   *ofp = NULL;

    if(ptCluster->szCOutFile){
	    ofp = fopen(ptCluster->szCOutFile,"w");
	    if(!ofp){
		    fprintf(stderr, "Failed to open file %s in gmmTrainVB\n",ptCluster->szCOutFile);
		    fflush(stderr);
	    }
    }

    /*calculate data likelihood*/
    calcZ_MP(ptCluster,ptData);
    ptCluster->dVBL = calcVBL_MP(ptCluster);

    while(nIter < nMaxIter && dDelta > dEpsilon){

        /*update parameter estimates*/
        performMStepMP(ptCluster, ptData);

        /*calculate responsibilities*/
        calcZ_MP(ptCluster,ptData);

        dLastVBL = ptCluster->dVBL;
        ptCluster->dVBL = calcVBL_MP(ptCluster);
        dDelta = fabs(ptCluster->dVBL - dLastVBL);

        fprintf(stderr,"%d,%f,%f\n",nIter, ptCluster->dVBL, dDelta);
        fflush(stderr);
        if(ofp){
    	    fprintf(ofp,"%d,%f,%f,",nIter, ptCluster->dVBL, dDelta);
    	    for(k = 0; k < nK-1; k++){
    		    fprintf(ofp,"%f,",ptCluster->adPi[k]);
    	    }
    	    fprintf(ofp,"%f\n",ptCluster->adPi[nK - 1]);
	        fflush(ofp);
        }
        nIter++;
    }

    if(ofp){
	    fclose(ofp);
    }

    /*assign to best clusters*/
    for(i = 0; i < nN; i++){
        double dMaxZ = aadZ[i][0];
        int    nMaxK = 0;
        for(k = 1; k < nK; k++){
            if(aadZ[i][k] > dMaxZ){
	            nMaxK = k;
	            dMaxZ = aadZ[i][k];
            }
        }
        ptCluster->anMaxZ[i] = nMaxK;
    }

    return;
}

void calcCovarMatrices(t_Cluster *ptCluster, t_Data *ptData)
{
    int i = 0, j = 0, k = 0, l = 0, m = 0;
    int nN = ptData->nN, nK = ptCluster->nK, nD = ptData->nD;
    double **aadZ = ptCluster->aadZ,**aadX = ptData->aadX;
    double *adPi = ptCluster->adPi, **aadCovar = NULL;
    double dN = (double) nN;

    aadCovar = (double **) malloc(nD*sizeof(double*));
    if(!aadCovar)
        goto memoryError;

    for(i = 0; i < nD; i++){
        aadCovar[i] = (double *) malloc(nD*sizeof(double));
        if(!aadCovar[i])
            goto memoryError;
    }

    for(k = 0; k < nK; k++){ /*loop components*/
        double*     adMu          = ptCluster->aadMu[k];
        gsl_matrix  *ptSigmaMatrix = ptCluster->aptSigma[k];
        /*recompute mixture weights and means*/
        for(j = 0; j < nD; j++){
            adMu[j] = 0.0;
            for(l = 0; l < nD; l++){
	            aadCovar[j][l] = 0.0;
            }
            /*prevents singularities*/
        aadCovar[j][j] = MIN_COVAR;
    }

    /* compute weight associated with component k*/
    adPi[k] = 0.0;
    for(i = 0; i < nN; i++){
        adPi[k] += aadZ[i][k];
        for(j = 0; j < nD; j++){
	        adMu[j] += aadZ[i][k]*aadX[i][j];
        }
    }
    /*normalise means*/
    for(j = 0; j < nD; j++){
        adMu[j] /= adPi[k];
    }

    /*calculate covariance matrices*/
    for(i = 0; i < nN; i++){
        double adDiff[nD];

        for(j = 0; j < nD; j++){
	        adDiff[j] = aadX[i][j] - adMu[j];
        }

        for(l = 0; l < nD; l++){
	        for(m = 0; m <=l ; m++){
	            aadCovar[l][m] += aadZ[i][k]*adDiff[l]*adDiff[m];
	        }
        }
    }

    for(l = 0; l < nD; l++){
        for(m = l + 1; m < nD; m++){
	        aadCovar[l][m] = aadCovar[m][l];
        }
    }

    for(l = 0; l < nD; l++){
        for(m = 0; m < nD; m++){
	        aadCovar[l][m] /= adPi[k];
	        gsl_matrix_set(ptSigmaMatrix, l, m, aadCovar[l][m]);
        }
    }

    adPi[k] /= dN; /*normalise weights*/
  }
  /*free up memory*/
  for(i = 0; i < nD; i++){
    free(aadCovar[i]);
  }

  //gsl_matrix_free(ptSigmaMatrix);
  free(aadCovar);

  return;
 memoryError:
  fprintf(stderr, "Failed allocating memory in performMStep\n");
  fflush(stderr);
  exit(EXIT_FAILURE);
}

/*note assuming you are using inverse W matrix*/
double dLogWishartB(gsl_matrix *ptInvW, int nD, double dNu, int bInv)
{
  int i = 0;
  double dRet = 0.0, dT = 0.0;
  double dLogDet = 0.0, dD = (double) nD;
  gsl_matrix* ptTemp = gsl_matrix_alloc(nD,nD);

  gsl_matrix_memcpy(ptTemp, ptInvW);

  dLogDet = decomposeMatrix(ptTemp, nD);

  if(bInv == TRUE){
    dRet = 0.5*dNu*dLogDet;
  }
  else{
    dRet = -0.5*dNu*dLogDet;
  }

  dT = 0.5*dNu*dD*log(2.0);

  dT += 0.25*dD*(dD - 1.0)*log(M_PI);

  for(i = 0; i < nD; i++){
    dT += gsl_sf_lngamma(0.5*(dNu - (double) i));
  }

  gsl_matrix_free(ptTemp);

  return dRet - dT;
}

double dWishartExpectLogDet(gsl_matrix *ptW, double dNu, int nD)
{
  int i = 0;
  double dRet = 0.0, dLogDet = 0.0, dD = (double) nD;
  gsl_matrix* ptTemp = gsl_matrix_alloc(nD,nD);

  gsl_matrix_memcpy(ptTemp, ptW);

  dLogDet = decomposeMatrix(ptW, nD);

  dRet = dD*log(2.0) + dLogDet;

  for(i = 0; i < nD; i++){
    dRet += gsl_sf_psi(0.5*(dNu - (double) i));
  }

  gsl_matrix_free(ptTemp);

  return dRet;
}

void updateMeans(t_Cluster *ptCluster, t_Data *ptData)
{
  int i = 0, j = 0, k = 0;
  int nN = ptData->nN, nK = ptCluster->nK, nD = ptData->nD;
  int *anMaxZ = ptCluster->anMaxZ;
  int *anW    = ptCluster->anW;
  double **aadX = ptData->aadX, **aadMu = ptCluster->aadMu;

  for(k = 0; k < nK; k++){

    for(j = 0; j < nD; j++){
      aadMu[k][j] = 0.0;
    }
  }

  for(i = 0; i < nN; i++){
    int nZ = anMaxZ[i];

    for(j = 0; j < nD; j++){
      aadMu[nZ][j] += aadX[i][j];
    }
  }

  for(k = 0; k < nK; k++){ /*loop components*/

    /*normalise means*/
    if(anW[k] > 0){
      for(j = 0; j < nD; j++){
	    aadMu[k][j] /= (double) anW[k];
      }
    }
    else{
      for(j = 0; j < nD; j++){
	    aadMu[k][j] = 0.0;
      }
    }
  }

  return;
}

double calcDist(double* adX, double *adMu, int nD)
{
  double dDist = 0.0;
  int i = 0;

  for(i = 0; i < nD; i++){
    double dV = adX[i] - adMu[i];
    dDist += dV*dV;
  }

  return sqrt(dDist);
}


#ifndef UTILS_H
#define UTILS_H

#include <gsl/gsl_vector.h>
#include <gsl/gsl_sort_vector.h>
#include <gsl/gsl_sort.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_permutation.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_eigen.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_multimin.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_sf_psi.h>
#include <math.h>
#include <assert.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#include <memory.h>

#define outlog(format, args...) \
    fprintf(stderr, format, args); \
    fprintf(stderr, "\n");

int compare (const void * a, const void * b);

inline double safe_log(double x) {
  if (x <= 0)
    return(-10000);
  else
    return(log(x));
}
double log_sum(double, double);

inline double vget(const gsl_vector* v, int i) { return(gsl_vector_get(v, i)); }

inline void vset(gsl_vector* v, int i, double x) { gsl_vector_set(v, i, x); }

// Increment a vector element by a double.
inline void vinc(gsl_vector* v, int i, double x) {
  vset(v, i, vget(v, i) + x);
}

inline double mget(const gsl_matrix* m, int i, int j)
{ return(gsl_matrix_get(m, i, j)); }

inline void mset(gsl_matrix* m, int i, int j, double x)
{ gsl_matrix_set(m, i, j, x); }

// Increment a matrix element by a double.
void minc(gsl_matrix*, int, int, double);

void col_sum(const gsl_matrix*, gsl_vector*);
void row_sum(const gsl_matrix*, gsl_vector*);

void vct_fprintf(FILE* file, const gsl_vector* v);
void mtx_fprintf(FILE* file, const gsl_matrix* m);
void mtx_fscanf(FILE* file, gsl_matrix* m);

inline bool check_sym(const gsl_matrix *m) {
  for (size_t i = 0; i < m->size1-1; i ++)
    for (size_t j=i; j < m->size2; j ++)
      if (mget(m, i, j) != mget(m, j, i)) {
        printf("not sym\n");
        return false;
      }
  return true;
}

double log_det(const gsl_matrix*);

void matrix_inverse(const gsl_matrix*, gsl_matrix*);
void matrix_vector_solve(const gsl_matrix* m, const gsl_vector* b, gsl_vector* v);

void sym_eigen(gsl_matrix*, gsl_vector*, gsl_matrix*);

inline double vsum(const gsl_vector* v) {
  double val = 0;
  int i, size = v->size;
  for (i = 0; i < size; i++)
    val += vget(v, i);
  return(val);
}

double vnorm(const gsl_vector * v);

void gsl_vector_apply(gsl_vector* x, double(*fun)(double));
void vct_log(gsl_vector* v);
void mtx_log(gsl_matrix* x);
void vct_exp(gsl_vector* x);
void mtx_exp(gsl_matrix* x);

double mahalanobis_distance(const gsl_matrix * m, const gsl_vector* u, const gsl_vector* v);
double mahalanobis_prod(const gsl_matrix * m, const gsl_vector* u, const gsl_vector* v);
double matrix_dot_prod(const gsl_matrix * m1, const gsl_matrix* m2);

void choose_k_from_n(int k, int n, int* result, int* src);

double log_normalize(gsl_vector* x);
double vnormalize(gsl_vector* x);

int dir_exists(const char *dname);
bool file_exists(const char * filename);
void make_directory(const char* name);

double  digamma(double x);
unsigned int rmultinomial(const gsl_vector* v);
double  rgamma(double a, double b);
double  rbeta(double a, double b);
unsigned int rbernoulli(double p);
double runiform();
void rshuffle (void* base, size_t n, size_t size);
unsigned long int runiform_int(unsigned long int n);

// new and free random number generator
gsl_rng* new_random_number_generator(long seed);
void free_random_number_generator(gsl_rng * random_number_generator);

#endif