// Authors: David Blei (blei@cs.princeton.edu)
//          Sean Gerrish (sgerrish@cs.princeton.edu)
//
// Copyright 2011 Sean Gerrish and David Blei
// All Rights Reserved.
//
// See the README for this package for details about modifying or
// distributing this software.

#include "lda-seq.h"

#include "gflags.h"
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_vector_double.h>

extern int LDA_INFERENCE_MAX_ITER;

static double* scaled_influence = NULL;

// const int TIME = 61;
const int TIME = -3;
const double PI = 3.141592654;

DEFINE_int32(lda_sequence_max_iter,
	     20,
	     "The maximum number of iterations.");
DEFINE_int32(lda_sequence_min_iter,
	     1,
	     "The maximum number of iterations.");

DEFINE_int32(forward_window,
	     1,
	     "The forward window for deltas. "
	     "If negative, we use a beta with mean "
	     "5.");

DEFINE_string(normalize_docs,
	      "normalize",
	      "Describes how documents's wordcounts "
	      "are considered for finding influence. "
	      "Options are \"normalize\", \"none\", "
	      "\"occurrence\", \"log\", or \"log_norm\".");

DEFINE_int32(save_time,
	     1e20,
	     "Save a specific time.  If -1, save all times.");

DEFINE_int32(fix_topics,
	     0,
	     "Fix a set of this many topics. This amounts "
	     "to fixing these topics' variance at 1e-10.");

DECLARE_string(model);
DECLARE_int32(max_number_time_points);
DECLARE_double(sigma_d);
DECLARE_double(sigma_l);
DECLARE_double(sigma_c);
DECLARE_double(sigma_cv);

/*
 * populate an LDA model at a particular time point
 *
 */

inf_var* inf_var_alloc(int number_topics,
		       corpus_seq_t* corpus_seq) {
  // Hate to do this, but I had trouble using it before.  This should
  // be the first place we use it; otherwise we'll get a sigsev nil.
  if (scaled_influence == NULL) {
    scaled_influence = NewScaledInfluence(FLAGS_max_number_time_points);
  }

  inf_var* inf_var_ptr = (inf_var*) malloc(sizeof(inf_var));
  inf_var_ptr->doc_weights = (gsl_matrix**) malloc(sizeof(gsl_matrix*)
						   * corpus_seq->len);
  inf_var_ptr->renormalized_doc_weights = (gsl_matrix**) malloc(
    sizeof(gsl_matrix*)
    * corpus_seq->len);
  inf_var_ptr->ntime = corpus_seq->len;
  int i=0;
  for (i=0; i < corpus_seq->len; ++i) {
    corpus_t* corpus = corpus_seq->corpus[i];
    outlog("creating matrix. %d %d", corpus->ndocs, number_topics);
    if (corpus->ndocs == 0) {
      inf_var_ptr->doc_weights[i] = (gsl_matrix*) malloc(sizeof(gsl_matrix));
      inf_var_ptr->doc_weights[i]->size1 = 0;
      inf_var_ptr->doc_weights[i]->size2 = number_topics;
      inf_var_ptr->renormalized_doc_weights[i] = (gsl_matrix*) malloc(sizeof(gsl_matrix));
      inf_var_ptr->renormalized_doc_weights[i]->size1 = 0;
      inf_var_ptr->renormalized_doc_weights[i]->size2 = number_topics;
    } else {
      inf_var_ptr->doc_weights[i] = gsl_matrix_calloc(corpus->ndocs,
						      number_topics);
      inf_var_ptr->renormalized_doc_weights[i] = gsl_matrix_calloc(corpus->ndocs,
								   number_topics);
    }
  }
  return inf_var_ptr;
}

void inf_var_free(inf_var* ptr) {
  // TODO.
}

// Solves the linear system Ax = b for x.
// Assumes that x is already allocated.
void Solve(gsl_matrix* A,
	   const gsl_vector* b,
	   gsl_vector* x) {
    int permutation_sign;
    gsl_permutation* permutation = gsl_permutation_alloc(b->size);
    gsl_linalg_LU_decomp(A, permutation, &permutation_sign);
    gsl_linalg_LU_solve(A, permutation, b, x);
    gsl_permutation_free(permutation);
}

// Find the sums of influence of all documents in advance.
// g has scores for everything *up to but not including* g.
void InfluenceTotalFixed(lda_seq* seq,
		    const corpus_seq_t* data) {
  gsl_vector* exp_tmp = gsl_vector_alloc(seq->nterms);

  for (int k=0; k < seq->ntopics; ++k) {

    for (int s=0; s < seq->nseq; ++s) {
      // Pull out elements of g, and make sure to set them to 0!
      gsl_vector_view g = gsl_matrix_column(
	  seq->influence_sum_lgl[k], s);
      gsl_vector_set_zero(&g.vector);

      for (int t=0; t <= s; ++t) {
	gsl_vector_view w_phi_l =
	  gsl_matrix_column(seq->topic[k]->w_phi_l, t);
	gsl_vector_memcpy(exp_tmp, &w_phi_l.vector);
	gsl_vector_scale(exp_tmp, scaled_influence[s - t]);
	gsl_vector_add(&g.vector, exp_tmp);
      }
    }
  }
  gsl_vector_free(exp_tmp);
}

void DumpTimeDocTopicStats(const char* root,
			   size_t t,
			   corpus_t* corpus,
			   gsl_matrix** phi) {
  // For all documents, dump the top topics.

  char name[400];
  // Dump the top topics for each word.
  sprintf(name, "%s%ld_doc_term_topics.dat", root, t);
  FILE* f = fopen(name, "w");
  for (unsigned int d=0; d < corpus->ndocs; ++d) {
    gsl_matrix* phi_d = phi[d];
    doc_t* doc = corpus->doc[d];
    for (unsigned int n=0; n < doc->nterms; ++n) {

      unsigned int w = doc->word[n];

      // First, find the max topic weight.
      unsigned int max_topic_index = 0;
      double max_topic_weight = gsl_matrix_get(phi_d, n, 0);
      for (unsigned int k=0; k < phi_d->size2; ++k) {
	double phi_d_n_k = gsl_matrix_get(phi_d, n, k);
	if (phi_d_n_k > max_topic_weight) {
	  max_topic_weight = phi_d_n_k;
	  max_topic_index = k;
	}
      }

      fprintf(f, "%d:%d:%.3f", w, max_topic_index, max_topic_weight);

      if (n < doc->nterms - 1) {
	fprintf(f, " ");
      }
    }
    fprintf(f, "\n");
  }
  fclose(f);
}

void PrepareRegressionComponents(
    corpus_t* corpus,
    lda_seq* seq,
    unsigned int k,
    gsl_matrix* W_phi,
    gsl_matrix* W_phi_var,
    gsl_vector* d_phi_var_tmp,
    gsl_vector* response,
    gsl_vector* d_tmp,
    gsl_matrix* dd_tmp,
    gsl_vector* document_weights,
    gsl_matrix* W_phi_tmp,
    gsl_vector* exp_h_tmp) {

  // Then left-multiply this by W_phi:
  gsl_blas_dgemv(CblasTrans,
		 1.0,
		 W_phi,
		 response,
		 0.0,
		 d_tmp);

  // Set up the transformation matrix.
  // First, set up W_phi^T \Lambda W_phi.
  gsl_matrix_memcpy(W_phi_tmp, W_phi);
  for (unsigned int d=0; d < corpus->ndocs; ++d) {
    gsl_vector_view col = gsl_matrix_column(W_phi_tmp, d);
    gsl_vector_mul(&col.vector, exp_h_tmp);
  }

  // Yuck.  Maybe we should do this sparsely?
  // Probably won't be too bad, at least for now.
  gsl_blas_dgemm(CblasTrans,
		 CblasNoTrans,
		 1.0,
		 W_phi_tmp,
		 W_phi,
		 0.0,
		 dd_tmp);

  gsl_blas_dgemv(CblasTrans,
		 1.0,
		 W_phi_var,
		 exp_h_tmp,
		 0.0,
		 d_phi_var_tmp);

  // Next, add elements to the diagonal of dd_tmp.
  for (unsigned int d=0; d < corpus->ndocs; ++d) {
    double value = gsl_matrix_get(dd_tmp, d, d);
    value += (seq->topic[k]->chain_variance
	      / (FLAGS_sigma_d * FLAGS_sigma_d));
    // sgerrish: Is this supposed to be multiplied by anything?
    value += gsl_vector_get(d_phi_var_tmp, d);
    gsl_matrix_set(dd_tmp, d, d, value);
  }
}

void SetExpHTmp(lda_seq* seq,
		const corpus_seq_t* data,
		unsigned int t,
		unsigned int k,
		unsigned int n,
		gsl_vector* exp_h_tmp,
		gsl_vector* zw_tmp,
		gsl_vector** exp_i_tmp) {
  gsl_vector_set_zero(exp_h_tmp);
  for (int i = t; i < seq->nseq; ++i) {
    gsl_vector_view mean_i_current =
      gsl_matrix_column(seq->topic[k]->e_log_prob, i);
    gsl_vector_view var_i_current =
      gsl_matrix_column(seq->topic[k]->variance, i + 1);

    // Set up exp_h_tmp.
    gsl_vector_memcpy(zw_tmp, &var_i_current.vector);
    gsl_vector_sub(zw_tmp, &mean_i_current.vector);
    gsl_vector_scale(zw_tmp, 2.0);
    for (n=0; n < data->nterms; ++n) {
      gsl_vector_set(zw_tmp, n,
		     exp(gsl_vector_get(zw_tmp, n)));
    }
    gsl_vector_scale(zw_tmp,
		     scaled_influence[i - t]
		     * scaled_influence[i - t]);
    gsl_vector_add(exp_h_tmp, zw_tmp);

    // Set up exp_i_tmp.
    gsl_vector_memcpy(exp_i_tmp[i], &var_i_current.vector);
    gsl_vector_scale(exp_i_tmp[i], 0.5);
    gsl_vector_sub(exp_i_tmp[i], &mean_i_current.vector);
    for (n=0; n < data->nterms; ++n) {
      gsl_vector_set(exp_i_tmp[i], n,
		     exp(gsl_vector_get(exp_i_tmp[i], n)));
    }
  }
}

double update_inf_var_fixed(lda_seq* seq,
			    const corpus_seq_t* data,
			    gsl_matrix** phi,
			    size_t t,
			    const char* root,
			    int dump_doc_stats) {
  double lhood = 0.0;
  // Note that we're missing a suspicious factor of -2 on the document
  // weights currently.  We won't worry about that for now (since
  // we're still experimenting), but it should soon be fixed.
  corpus_t* corpus = data->corpus[t];
  if (!corpus->ndocs) {
    return lhood;
  }
  inf_var* influence = seq->influence;
  if (t != TIME && 0) {
    return lhood;
  }

  // We need access to the following:
  gsl_matrix* documents_topics_t = influence->doc_weights[t];
  gsl_matrix* renormalized_documents_topics_t = influence->renormalized_doc_weights[t];

  size_t k;

  gsl_matrix* W_phi = gsl_matrix_calloc(
    seq->nterms, corpus->ndocs);
  gsl_matrix* W2_phi2 = gsl_matrix_calloc(
    seq->nterms, corpus->ndocs);
  gsl_matrix* W_phi_var =
    gsl_matrix_calloc(seq->nterms, corpus->ndocs);
  gsl_matrix* W_phi_tmp = gsl_matrix_alloc(
    seq->nterms, corpus->ndocs);
  gsl_matrix* dd_tmp = gsl_matrix_alloc(
    corpus->ndocs, corpus->ndocs);
  gsl_vector* xd_tmp = gsl_vector_calloc(
    corpus->ndocs);
  gsl_vector* yw_tmp = gsl_vector_calloc(
    seq->nterms);
  gsl_vector* zw_tmp = gsl_vector_calloc(
    seq->nterms);
  gsl_vector* terms_inc_tmp = gsl_vector_calloc(
    seq->nterms);
  gsl_vector** exp_i_tmp = new gsl_vector*[seq->nseq];
  for (int i=0; i < seq->nseq; ++i) {
    exp_i_tmp[i] = gsl_vector_calloc(seq->nterms);
  }

  int n;

  gsl_vector* response = gsl_vector_calloc(seq->nterms);
  gsl_vector* exp_h_tmp = gsl_vector_calloc(seq->nterms);
  gsl_vector* d_tmp = gsl_vector_calloc(corpus->ndocs);
  gsl_vector* d_phi_var_tmp = gsl_vector_calloc(corpus->ndocs);

  //  assert(post->phi->size2 == documents_topics_t->size2);
  double* total_terms = (double*) malloc(sizeof(double) * corpus->ndocs);
  double* renormalization_totals = (double*) malloc(sizeof(double) * corpus->ndocs);
  for (k=0; k < documents_topics_t->size2; ++k) {
    // Set up W_phi and W_phi_var.
    for (int d=0; d < corpus->ndocs; ++d) {
      doc_t* doc = corpus->doc[d];
      total_terms[d] = 0.0;
      renormalization_totals[d] = 0.0;
      double log_norm_sum = 0.0;
      for (n=0; n < doc->nterms; ++n) {
	total_terms[d] += doc->count[n];
	if (FLAGS_normalize_docs == "normalize") {
	  renormalization_totals[d] = 1.0;
	} else if (FLAGS_normalize_docs == "log") {
	  renormalization_totals[d] += log(doc->count[n] + 1);
	} else if (FLAGS_normalize_docs == "log_norm") {
	  double weight = log(doc->count[n] / doc->total);
	  renormalization_totals[d] += weight;
	} else if (FLAGS_normalize_docs == "identity") {
	  renormalization_totals[d] += doc->count[n];
	} else if (FLAGS_normalize_docs == "occurrence") {
	  renormalization_totals[d] = 1.0;
	}
      }
      assert(doc->total == total_terms[d]);

      for (n=0; n < doc->nterms; ++n) {
	// Phi, for the doc's term n and topic k.
	double phi_d_n_k = gsl_matrix_get(phi[d], n, k);

	// This cast should happen automatically because total_terms is a double,
	// but we make the cast here to be explicit and to avoid bugs later.
	double number_terms;
	if (FLAGS_normalize_docs == "normalize") {
	  number_terms = ((double) doc->count[n]
			  / (double) total_terms[d]);
	} else if (FLAGS_normalize_docs == "log") {
	  number_terms = log(doc->count[n] + 1.0);
	} else if (FLAGS_normalize_docs == "log_norm") {
	  number_terms = log(doc->count[n] / total_terms[d]);
	  renormalization_totals[d] += log(doc->count[n] / total_terms[d]);
	} else if (FLAGS_normalize_docs == "identity") {
	  number_terms = doc->count[n];
	} else if (FLAGS_normalize_docs == "occurrence") {
	  number_terms = ((double) doc->count[n] / (double) total_terms[d]);
	  assert(doc->count[n] == 1);
	} else {
	  assert(0);
	}

	gsl_matrix_set(W_phi, doc->word[n], d,
		       number_terms * phi_d_n_k);
	gsl_matrix_set(W2_phi2, doc->word[n], d,
		       number_terms * number_terms
		       * phi_d_n_k * phi_d_n_k);
	gsl_matrix_set(W_phi_var, doc->word[n], d,
		       number_terms * number_terms
		       * (phi_d_n_k - phi_d_n_k * phi_d_n_k));
      }
    }

    gsl_vector_view document_weights = gsl_matrix_column(
      documents_topics_t,
      k);
    gsl_vector_view renormalized_document_weights = gsl_matrix_column(
      renormalized_documents_topics_t,
      k);
    assert(seq->topic[k]->e_log_prob->size2 == data->len);

    // Now, with w_phi_var, etc. set, determine
    // \sum_{i=t}^{T-1} r(...) h(t, i)
    SetExpHTmp(seq,
	       data,
	       t,
	       k,
	       n,
	       exp_h_tmp,
	       zw_tmp,
	       exp_i_tmp);

    // Next, set up the weighted response,
    // \exp_{-m + v / 2) \circ (m_{t+1} - m_t + v_t}).
    // Here we also subtract the current l's contribution to influence_sum_lgl.
    gsl_vector_view w_phi_l_t = gsl_matrix_column(seq->topic[k]->w_phi_l, t);
    gsl_vector_set_zero(response);
    for (int i = t; i < seq->nseq - 1; ++i) {
      gsl_vector_view total_influence_time_i =
	gsl_matrix_column(seq->influence_sum_lgl[k], i);
      gsl_vector_memcpy(zw_tmp, &w_phi_l_t.vector);
      gsl_vector_scale(zw_tmp, scaled_influence[i - t]);
      gsl_vector_sub(&total_influence_time_i.vector, zw_tmp);

      // Now, copy this total influence at time i back into zw_tmp.
      gsl_vector_memcpy(zw_tmp, &total_influence_time_i.vector);
      gsl_vector_mul(zw_tmp, exp_i_tmp[i]);

      gsl_vector_view mean_i_current =
	gsl_matrix_column(seq->topic[k]->e_log_prob, i);
      gsl_vector_view mean_i_next =
	gsl_matrix_column(seq->topic[k]->e_log_prob, i + 1);
      gsl_vector_view var_i_current =
	gsl_matrix_column(seq->topic[k]->variance, i + 1);
      gsl_vector_memcpy(terms_inc_tmp, &mean_i_next.vector);
      gsl_vector_sub(terms_inc_tmp, &mean_i_current.vector);
      gsl_vector_add(terms_inc_tmp, &var_i_current.vector);
      gsl_vector_sub(terms_inc_tmp, zw_tmp);
      assert(data->nterms == terms_inc_tmp->size);
      gsl_vector_mul(terms_inc_tmp, exp_i_tmp[i]);
      gsl_vector_scale(terms_inc_tmp, scaled_influence[i - t]);

      gsl_vector_add(response, terms_inc_tmp);
    }

    PrepareRegressionComponents(corpus,
				seq,
				k,
				W_phi,
				W_phi_var,
				d_phi_var_tmp,
				response,
				d_tmp,
				dd_tmp,
				&document_weights.vector,
				W_phi_tmp,
				exp_h_tmp);
    // Finally, solve for the document weights d!
    Solve(dd_tmp,
	  d_tmp,
	  &document_weights.vector);

    // Keep track of the iteration so we can dump certain stats
    // occasionally (but not always).
    static int dump_count = 0;
    ++dump_count;

    if (FLAGS_save_time == t) {
      outlog("Updating topic %ld, time %ld.", k, t);
      char name[400];
      sprintf(name, "%s%ld_%ld_weighted_document_terms.dat", root, k, t);
      FILE* f = fopen(name, "w");
      params_write_sparse_gsl_matrix(f, "W_phi", W_phi);
      fclose(f);

      sprintf(name, "%s%ld_%ld_weighted_document_terms_var.dat", root, k, t);
      f = fopen(name, "w");
      params_write_sparse_gsl_matrix(f, "W_phi_var", W_phi_var);
      fclose(f);

      sprintf(name, "%s%ld_%ld_phi.dat", root, k, t);
      f = fopen(name, "w");
      params_write_gsl_matrix(f, "phi 0", phi[0]);
      //     params_write_gsl_matrix(f, "phi 2", phi[1]);
      // params_write_gsl_matrix(f, "phi 2", phi[7]);
      fclose(f);

      sprintf(name, "%s%ld_%ld_weighted_document_terms_sq.dat", root, k, t);
      f = fopen(name, "w");
      params_write_sparse_gsl_matrix(f, "W2_phi2", W2_phi2);
      fclose(f);

      sprintf(name, "%s%ld_%ld_weighted_response.dat", root, k, t);
      f = fopen(name, "w");
      params_write_gsl_vector_multiline(f, "weighted_response", d_tmp);
      fclose(f);

      sprintf(name, "%s%ld_%ld_response.dat", root, k, t);
      f = fopen(name, "w");
      params_write_gsl_vector_multiline(f, "response", response);
      fclose(f);

      sprintf(name, "%s%ld_%ld_exp_h.dat", root, k, t);
      f = fopen(name, "w");
      params_write_gsl_vector_multiline(f, "exp_h", exp_h_tmp);
      fclose(f);

      if (dump_doc_stats || dump_count % 4 == 0) {
	sprintf(name, "%s%ld_%ld_document_document_matrix.dat", root, k, t);
	f = fopen(name, "w");
	params_write_gsl_matrix(f, "document_document_matrix", dd_tmp);
	fclose(f);

	sprintf(name, "%s%ld_%ld_exp_h.dat", root, k, t);
	f = fopen(name, "w");
	params_write_gsl_vector_multiline(f, "exp_h", exp_h_tmp);
	fclose(f);
      }
    }

    if (FLAGS_save_time == -1) {
      // First, dump phi's for the top topics.
      DumpTimeDocTopicStats(root, t, corpus, phi);
    }
    outlog("Done updating topic %ld, time %ld.", k, t);

    for (int d = 0; d < document_weights.vector.size; ++d) {
      gsl_vector_set(&renormalized_document_weights.vector, d,
		     vget(&document_weights.vector, d)
		     * renormalization_totals[d]);
    }

    // Now copy this and several products to
    // the sslm_var object.
    gsl_vector_view w_phi_l =
      gsl_matrix_column(seq->topic[k]->w_phi_l, t);
    gsl_blas_dgemv(CblasNoTrans,
		   1.0,
		   W_phi,
		   &document_weights.vector,
		   0.0,
		   &w_phi_l.vector);

    // Copy this value back into lgl.
    for (int i=t; i < seq->nseq - 1; ++i) {
      gsl_vector_view total_influence_time_i =
	gsl_matrix_column(seq->influence_sum_lgl[k], i);
      gsl_vector_memcpy(zw_tmp, &w_phi_l.vector);
      gsl_vector_scale(zw_tmp, scaled_influence[i - t]);
      gsl_vector_add(&total_influence_time_i.vector, zw_tmp);
    }

     // Keep track of the term we need to add to m_update_coeff.
     gsl_vector_memcpy(terms_inc_tmp, &w_phi_l.vector);
     gsl_vector_mul(terms_inc_tmp, &w_phi_l.vector);

     // Copy and square the document weights vector.
     for (int i = 0; i < xd_tmp->size; ++i) {
       double value = gsl_vector_get(&document_weights.vector, i);
       value = value * value + FLAGS_sigma_l * FLAGS_sigma_l;
       gsl_vector_set(xd_tmp, i, value);
     }
     gsl_blas_dgemv(CblasNoTrans,
		    1.0,
		    W_phi_var,
		    xd_tmp,
		    0.0,
		    yw_tmp);
     gsl_vector_add(terms_inc_tmp, yw_tmp);

     for (int i = 0; i < xd_tmp->size; ++i) {
       gsl_vector_set(xd_tmp, i, FLAGS_sigma_l * FLAGS_sigma_l);
     }
     gsl_blas_dgemv(CblasNoTrans,
		    1.0,
		    W2_phi2,
		    xd_tmp,
		    0.0,
		    yw_tmp);
     gsl_vector_add(terms_inc_tmp, yw_tmp);

     // Store an update coefficient for the beta updates.
     for (int i = t; i < seq->nseq; ++i) {
       gsl_vector_view m_update_coeff_h =
	 gsl_matrix_column(seq->topic[k]->m_update_coeff, i);
       if (t == 0) {
	 gsl_vector_set_zero(&m_update_coeff_h.vector);
       }
       gsl_vector_memcpy(yw_tmp, terms_inc_tmp);
       gsl_vector_scale(yw_tmp, scaled_influence[i - t]);
       gsl_vector_add(&m_update_coeff_h.vector, yw_tmp);
     }

     for (int i = t; i < seq->nseq; ++i) {
       gsl_vector_view m_update_coeff_g =
	 gsl_matrix_column(seq->topic[k]->m_update_coeff_g, i);
       if (t == 0) {
	 gsl_vector_set_zero(&m_update_coeff_g.vector);
       }
       gsl_vector_memcpy(yw_tmp, &w_phi_l.vector);
       gsl_vector_scale(yw_tmp, scaled_influence[i - t]);
       gsl_vector_add(&m_update_coeff_g.vector, yw_tmp);
     }

     for (int i = 0; i < corpus->ndocs; ++i) {
       double value = gsl_vector_get(&document_weights.vector, i);

       // While we're here, increment the likelihood.
       lhood += (-(value * value + FLAGS_sigma_l * FLAGS_sigma_l)
		 / (2.0 * FLAGS_sigma_d * FLAGS_sigma_d)
		 - 0.5 * log(2 * PI)
		 - log(FLAGS_sigma_d * FLAGS_sigma_d));
     }
  }
  free(total_terms);
  free(renormalization_totals);
  gsl_matrix_free(W_phi);
  gsl_matrix_free(W_phi_tmp);
  gsl_matrix_free(W2_phi2);
  gsl_matrix_free(W_phi_var);
  gsl_matrix_free(dd_tmp);
  gsl_vector_free(exp_h_tmp);
  gsl_vector_free(response);
  gsl_vector_free(terms_inc_tmp);
  for (int i=0; i < seq->nseq; ++i) {
    gsl_vector_free(exp_i_tmp[i]);
  }
  delete[] exp_i_tmp;
  gsl_vector_free(d_tmp);
  gsl_vector_free(d_phi_var_tmp);
  gsl_vector_free(xd_tmp);
  gsl_vector_free(yw_tmp);
  gsl_vector_free(zw_tmp);

  return lhood;
}

void make_lda_from_seq_slice(lda* lda_m,
			     lda_seq* lda_seq_m,
			     int time) {
     // set lda model topics
     // !!! note: we should be able to point to the view...

     int k;
     for (k = 0; k < lda_seq_m->ntopics; k++)
     {
	 // get topic
	 gsl_vector s =
	     gsl_matrix_column(lda_seq_m->topic[k]->e_log_prob,
			       time).vector;
	 gsl_vector d =
	     gsl_matrix_column(lda_m->topics, k).vector;
	 gsl_blas_dcopy(&s, &d);
     }
     gsl_blas_dcopy(lda_seq_m->alpha, lda_m->alpha);
 }

static gsl_matrix* g_alloc(lda_seq* model,
			   const corpus_seq_t* data,
			   int time) {
     gsl_matrix* g = gsl_matrix_calloc(model->nterms,
				       model->ntopics);
     double exp_m, m, m_next;
     for (int k = 0; k < model->ntopics; ++k) {
       for (int w=0; w < model->nterms; ++w) {
	 double variance_first = mget(model->topic[k]->variance, w, time);
	 double m = mget(model->topic[k]->e_log_prob, w, time);
	 double m_next;
	 exp_m = exp(-m + variance_first / 2.0);
	 gsl_matrix_set(g, w, k,
			(scaled_influence[0]
			 * -variance_first
			 * exp_m));

	 for (int i=time; i < model->nseq - 1; ++i) {
	   // This loop is kind of going overboard, but at least we
	   // do this once per E-M iteration.
	   double influence_other_times = 0.0;
	   for (int j = 0; j < i; ++j) {
	     exp_m = exp(-mget(model->topic[k]->e_log_prob, w, j)
			 + mget(model->topic[k]->variance, w, j) / 2.0);
	     // Note that we skip the other docs in this time period.
	     // Those get special treatment below.
	     if (j != time) {
	       influence_other_times += (
	          mget(model->topic[k]->w_phi_l, w, j)
		  * scaled_influence[i - j]
		  * exp_m);
	      }
	   }

	   m = mget(model->topic[k]->e_log_prob, w, i);
	   m_next = mget(model->topic[k]->e_log_prob, w, i + 1);
	   // Increment the current count by this value.
	   gsl_matrix_set(g, w, k,
			  mget(g, w, k) +
			  (scaled_influence[i - time]
			   * (m_next - m - influence_other_times)));
	 }
	 exp_m = exp(-m
		     + mget(model->topic[k]->variance, w, time) / 2.0);
	 gsl_matrix_set(g, w, k, mget(g, w, k) * exp_m);
       }
     }
     return g;
}

static gsl_matrix* g3_alloc(lda_seq* model,
			    const corpus_seq_t* data,
			    int time) {
     gsl_matrix* g = gsl_matrix_calloc(model->nterms,
				       model->ntopics);
     double exp_m, m, m_next, variance, total;
     for (int k = 0; k < model->ntopics; ++k) {
       for (int w=0; w < model->nterms; ++w) {
	 total = 0.0;
	 for (int i=time; i < model->nseq - 1; ++i) {
	   // This loop is kind of going overboard, but at least we
	   // do this once per E-M iteration.
	   variance = mget(model->topic[k]->variance, w, i + 1);
	   m = mget(model->topic[k]->e_log_prob, w, i);
	   m_next = mget(model->topic[k]->e_log_prob, w, i + 1);
	   exp_m = exp(-m + variance / 2.0);
	   total += (scaled_influence[i - time]
		     * exp_m
		     * (m_next - m + variance));
	 }
	 gsl_matrix_set(g, w, k, total);
       }
     }
     return g;
}

static gsl_matrix* g4_alloc(lda_seq* model,
		    const corpus_seq_t* data,
		    int time) {
     gsl_matrix* g = gsl_matrix_calloc(model->nterms,
				       model->ntopics);
     double exp_m, exp_m_scaled, m, total, variance, w_phi_l;
     for (int k = 0; k < model->ntopics; ++k) {
       for (int w=0; w < model->nterms; ++w) {
	 total = 0.0;
	 for (int i=time; i < model->nseq - 1; ++i) {
	   // This loop is kind of going overboard, but at least we
	   // do this once per E-M iteration.
	   variance = mget(model->topic[k]->variance, w, i + 1);
	   m = mget(model->topic[k]->e_log_prob, w, i);
	   exp_m = exp(-2.0 * m + 2.0 * variance);
	   exp_m_scaled = exp_m * scaled_influence[i - time];
	   for (int j=0; j <= i; ++j) {
	     w_phi_l = mget(model->topic[k]->w_phi_l, w, j);
	     total += exp_m_scaled * w_phi_l * scaled_influence[i - j];
	   }
	 }
	 gsl_matrix_set(g, w, k, total);
       }
     }
     return g;
}

static gsl_matrix* g5_alloc(lda_seq* model,
			    const corpus_seq_t* data,
			    int time) {
     gsl_matrix* g = gsl_matrix_calloc(model->nterms,
				       model->ntopics);
     double exp_m, m, total, variance;
     for (int k = 0; k < model->ntopics; ++k) {
       for (int w=0; w < model->nterms; ++w) {
	 total = 0.0;
	 for (int i=time; i < model->nseq - 1; ++i) {
	   // This loop is kind of going overboard, but at least we
	   // do this once per E-M iteration.
	   variance = mget(model->topic[k]->variance, w, i + 1);
	   m = mget(model->topic[k]->e_log_prob, w, i);
	   exp_m = exp(-2.0 * m + 2.0 * variance);
	   total += exp_m * (scaled_influence[i - time]
			     * scaled_influence[i - time]);
	 }
	 gsl_matrix_set(g, w, k, total);
       }
     }
     return g;
}

 /*
  * compute the likelihood of a sequential corpus under an LDA seq
  * model.  return the likelihood bound.
  *
  */

static void InferDTMSeq(const int K,
			unsigned int iter,
			unsigned int last_iter,
			const corpus_seq_t* data,
			gsl_matrix* gammas,
			gsl_matrix* lhoods,
			lda* lda_model,
			lda_post* post,
			lda_seq* model,
			gsl_matrix** suffstats,
			double* bound) {
  int doc_index = 0;
  for (int t = 0; t < data->len; t++) {
    // Prepare coefficients for the phi updates.  This change is
    // relatively painless.
    make_lda_from_seq_slice(lda_model, model, t);
    int ndocs = data->corpus[t]->ndocs;
    for (int d = 0; d < ndocs; d++) {
      gsl_vector gam   = gsl_matrix_row(gammas, doc_index).vector;
      gsl_vector lhood = gsl_matrix_row(lhoods, doc_index).vector;
      post->gamma = &gam;
      post->doc   = data->corpus[t]->doc[d];
      post->lhood = &lhood;
      double doc_lhood;
      // For now, only do the standard, phi-based update.
      if (iter == 0) {
	doc_lhood = fit_lda_post(d, t,
				 post, NULL,
				 NULL,
				 NULL,
				 NULL,
				 NULL);
      } else {
	doc_lhood = fit_lda_post(d, t, post, model, NULL,
				 NULL, NULL, NULL);
      }
      if (suffstats != NULL) {
	update_lda_seq_ss(t,
			  data->corpus[t]->doc[d],
			  post,
			  suffstats);
      }
      *bound += doc_lhood;
      doc_index++;
    }
  }
}

static void InferDIMSeq(const int K,
			unsigned int iter,
			unsigned int last_iter,
			const char* file_root,
			const corpus_seq_t* data,
			gsl_matrix* gammas,
			gsl_matrix* lhoods,
			lda* lda_model,
			lda_post* post,
			lda_seq* model,
			gsl_matrix** suffstats,
			double* bound) {
  int doc_index = 0;
  for (int t = 0; t < data->len; t++) {
    // Prepare coefficients for the phi updates.  This change is
    // relatively painless.
    gsl_matrix* g = g_alloc(model, data, t);

    gsl_matrix* g3_matrix = g3_alloc(model, data, t);
    gsl_matrix* g4_matrix = g4_alloc(model, data, t);
    gsl_matrix* g5_matrix = g5_alloc(model, data, t);

    make_lda_from_seq_slice(lda_model, model, t);
    int ndocs = data->corpus[t]->ndocs;
    gsl_matrix** phi_t = (gsl_matrix**) malloc(ndocs
					       * sizeof(gsl_matrix*));
    for (int d = 0; d < ndocs; d++) {
      gsl_vector gam   = gsl_matrix_row(gammas, doc_index).vector;
      gsl_vector lhood = gsl_matrix_row(lhoods, doc_index).vector;
      post->gamma = &gam;
      post->doc   = data->corpus[t]->doc[d];
      post->lhood = &lhood;
      double doc_lhood;
      // For now, only do the standard, phi-based update.
      if (iter == 0) {
	doc_lhood = fit_lda_post(d, t,
				 post, NULL,
				 NULL,
				 NULL,
				 NULL,
				 NULL);
      } else {
	doc_lhood = fit_lda_post(d, t, post, model, g,
				 g3_matrix, g4_matrix, g5_matrix);
      }
      if (suffstats != NULL) {
	update_lda_seq_ss(t,
			  data->corpus[t]->doc[d],
			  post,
			  suffstats);
      }
      phi_t[d] = gsl_matrix_alloc(post->doc->nterms, K);
      gsl_matrix_view phi_view = gsl_matrix_submatrix(
        post->phi,
	0, 0, post->doc->nterms, K);
      gsl_matrix_memcpy(phi_t[d], &phi_view.matrix);
      *bound += doc_lhood;
      doc_index++;
    }

    if (t < data->len - 1) {
      if (FLAGS_model == "fixed") {
	double l_bound = update_inf_var_fixed(
          model,
	  data,
	  // Also want a copy of phi for each doc.
	  // Can keep this as a vector for now.
	  phi_t,
	  t,
	  file_root,
	  last_iter || iter >= FLAGS_lda_sequence_min_iter);
	*bound += l_bound;
      }
    }

    for (int d=0; d < ndocs; ++d) {
      gsl_matrix_free(phi_t[d]);
    }
    free(phi_t);
    gsl_matrix_free(g);
    gsl_matrix_free(g3_matrix);
    gsl_matrix_free(g4_matrix);
    gsl_matrix_free(g5_matrix);
  }
}

double lda_seq_infer(lda_seq* model,
		     const corpus_seq_t* data,
		     gsl_matrix** suffstats,
		     gsl_matrix* gammas,
		     gsl_matrix* lhoods,
		     unsigned int iter,
		     unsigned int last_iter,
		     const char* file_root) {
  int K = model->ntopics; int W = model->nterms;
  double bound = 0.0;
  lda* lda_model = new_lda_model(K, W);
  lda_post post;
  post.phi = gsl_matrix_calloc(data->max_nterms, K);
  post.log_phi = gsl_matrix_calloc(data->max_nterms, K);
  post.model = lda_model;

  if (FLAGS_model == "fixed") {
    // First, pre-compute the functions f and g.
    InfluenceTotalFixed(model, data);
    InferDIMSeq(K,
		iter,
		last_iter,
		file_root,
		data,
		gammas,
		lhoods,
		lda_model,
		&post,
		model,
		suffstats,
		&bound);
  } else if (FLAGS_model == "dtm") {
    InferDTMSeq(K,
		iter,
		last_iter,
		data,
		gammas,
		lhoods,
		lda_model,
		&post,
		model,
		suffstats,
		&bound);
  } else {
    printf("Error.  Unknown model.\n");
    exit(1);
  }

  gsl_matrix_free(post.phi);
  gsl_matrix_free(post.log_phi);
  free_lda_model(lda_model);
  return(bound);
}

 /*
  * fit an lda sequence model:
  *
  * . for each time period
  * .     set up lda model with E[log p(w|z)] and \alpha
  * .     for each document
  * .         perform posterior inference
  * .         update sufficient statistics/likelihood
  * .
  * . maximize topics
  *
  */

double fit_lda_seq(lda_seq* m, const corpus_seq_t* data,
		   const corpus_seq_t* heldout, const char* file_root) {
     int K = m->ntopics, W = m->nterms;
     int k;

     // initialize sufficient statistics

     gsl_matrix* topic_suffstats[K];
     for (k = 0; k < K; k++) {
	 topic_suffstats[k] = gsl_matrix_calloc(W, data->len);
     }

     // set up variables

     char name[400];
     gsl_matrix* gammas = gsl_matrix_calloc(data->ndocs, K);
     gsl_matrix* lhoods = gsl_matrix_calloc(data->ndocs, K+1);
     gsl_matrix* heldout_gammas = NULL;
     gsl_matrix* heldout_lhoods = NULL;
     if (heldout != NULL)
     {
	 heldout_gammas = gsl_matrix_calloc(heldout->ndocs, K);
	 heldout_lhoods = gsl_matrix_calloc(heldout->ndocs, K+1);
     }

     double bound = 0, heldout_bound = 0, old_bound;
     double convergence = LDA_SEQ_EM_THRESH + 1;

     char root[400];
     sprintf(root, "%s/lda-seq/", file_root);
     make_directory(root);

     char em_log_filename[400];
     sprintf(em_log_filename, "%s/em_log.dat", file_root);
     FILE* em_log = fopen(em_log_filename, "w");

     // run EM

     int iter = 0;
     // LDA_INFERENCE_MAX_ITER = 1;
     short final_iters_flag = 0;
     unsigned int last_iter = 0;
     while (iter < FLAGS_lda_sequence_min_iter ||
	    ((final_iters_flag == 0 || convergence > LDA_SEQ_EM_THRESH)
	     && iter <= FLAGS_lda_sequence_max_iter)
	    && !last_iter) {

	 if (!(iter < FLAGS_lda_sequence_min_iter ||
	       ((final_iters_flag == 0 || convergence > LDA_SEQ_EM_THRESH)
		&& iter <= FLAGS_lda_sequence_max_iter))) {
	   last_iter = 1;
	 }
	 outlog("\nEM iter %3d\n", iter);
	 outlog("%s", "E step\n");
	 fprintf(em_log, "%17.14e %5.3e\n", bound, convergence);

	 old_bound = bound;
	 gsl_matrix_set_zero(gammas);
	 gsl_matrix_set_zero(lhoods);
	 if (heldout != NULL) {
	   gsl_matrix_set_zero(heldout_gammas);
	   gsl_matrix_set_zero(heldout_lhoods);
	 }

	 for (k = 0; k < K; k++) {
	     gsl_matrix_set_zero(topic_suffstats[k]);
	 }

	 // compute the likelihood of a sequential corpus under an LDA
	 // seq model and find the evidence lower bound.
	 bound = lda_seq_infer(m,
			       data,
			       topic_suffstats,
			       gammas,
			       lhoods,
			       iter,
			       last_iter,
			       file_root);
	 if (heldout != NULL) {
	   heldout_bound = lda_seq_infer(m,
					 heldout,
					 NULL,
					 heldout_gammas,
					 heldout_lhoods,
					 iter,
					 last_iter,
					 file_root);
	 }

	// print out the gammas and likelihoods.
        sprintf(name, "%s/gam.dat", root);
        mtx_fprintf(name, gammas);
        sprintf(name, "%s/lhoods.dat", root);
        mtx_fprintf(name, lhoods);
        if (heldout != NULL)
        {
            sprintf(name, "%s/heldout_lhoods.dat", root);
            mtx_fprintf(name, heldout_lhoods);
            sprintf(name, "%s/heldout_gam.dat", root);
            mtx_fprintf(name, heldout_gammas);
        }
        outlog("%s", "\nM step");

	// fit the variational distribution
        double topic_bnd = fit_lda_seq_topics(m, topic_suffstats);
        bound += topic_bnd;

        write_lda_seq(m, root);
        if ((bound - old_bound) < 0)
        {
            if (LDA_INFERENCE_MAX_ITER == 1)
                LDA_INFERENCE_MAX_ITER = 2;
            if (LDA_INFERENCE_MAX_ITER == 2)
                LDA_INFERENCE_MAX_ITER = 5;
            if (LDA_INFERENCE_MAX_ITER == 5)
                LDA_INFERENCE_MAX_ITER = 10;
            if (LDA_INFERENCE_MAX_ITER == 10)
                LDA_INFERENCE_MAX_ITER = 20;

            outlog( "\nWARNING: bound went down %18.14f; "
		    "increasing var iter to %d\n",
                    bound-old_bound, LDA_INFERENCE_MAX_ITER);
        }

	// check for convergence
        convergence = fabs((bound - old_bound) / old_bound);
        if (convergence < LDA_SEQ_EM_THRESH) {
            final_iters_flag = 1;
            LDA_INFERENCE_MAX_ITER = 500;
            outlog("starting final iterations : max iter = %d\n",
                   LDA_INFERENCE_MAX_ITER);
            convergence = 1.0;
        }
        outlog("\n(%02d) lda seq bound=% 15.7f; "
	       "heldout bound=% 15.7f, conv=% 15.7e\n",
               iter, bound, heldout_bound, convergence);
        iter++;
    }
    return(bound);
}

/*
 * read and write lda sequence variational distribution
 *
 */

void write_lda_seq(const lda_seq* model, const char* root) {
  char name[400];
  sprintf(name, "%sinfo.dat", root);
  FILE* f = fopen(name, "w");

  params_write_int(f, "NUM_TOPICS", model->ntopics);
  params_write_int(f, "NUM_TERMS", model->nterms);
  params_write_int(f, "SEQ_LENGTH", model->nseq);
  params_write_gsl_vector(f, "ALPHA", model->alpha);

  fclose(f);

  int k;
  for (k = 0; k < model->ntopics; k++) {
    const int tmp = k;
    outlog("\nwriting topic %03d", tmp);
    sprintf(name, "%stopic-%03d", root, tmp);
    write_sslm_var(model->topic[tmp], name);
  }

  if (FLAGS_model == "fixed") {
    for (int t=0; t < model->influence->ntime; ++t) {
      sprintf(name, "%sinfluence_time-%03d", root, t);
      outlog("\nwriting influence weights for time %d to %s", t, name);
      gsl_matrix* influence_t = model->influence->doc_weights[t];
      assert(model->ntopics == influence_t->size2);
      mtx_fprintf(name, influence_t);

      sprintf(name, "%srenormalized_influence_time-%03d", root, t);
      outlog("\nwriting influence weights for time %d to %s", t, name);
      influence_t = model->influence->renormalized_doc_weights[t];
      assert(model->ntopics == influence_t->size2);
      mtx_fprintf(name, influence_t);
    }
  }
}

// Read information about a particular model.
// This model should be named "{root}info.dat"
// and should contain the following rows:
// number_topics
// number_times
// alpha, as a gsl vector
lda_seq* read_lda_seq(const char* root, corpus_seq_t* data) {
  char name[400];
  lda_seq* model = (lda_seq*) malloc(sizeof(lda_seq));

  sprintf(name, "%sinfo.dat", root);
  FILE* f = fopen(name, "r");
  if (f == NULL) {
    outlog("Unable to open file %s.  Failing.", name);
    exit(1);
  }
  params_read_int(f, "NUM_TOPICS", &(model->ntopics));
  params_read_int(f, "NUM_TERMS", &(model->nterms));
  params_read_int(f, "SEQ_LENGTH", &(model->nseq));
  params_read_gsl_vector(f, "ALPHA", &(model->alpha));
  fclose(f);

  model->topic = (sslm_var**) malloc(sizeof(sslm_var*) * model->ntopics);

  for (int k = 0; k < model->ntopics; k++) {
    outlog( "reading topic %d", k);
    sprintf(name, "%stopic-%03d", root, k);
    model->topic[k] = read_sslm_var(name);

    model->topic[k]->w_phi_l =
      gsl_matrix_alloc(model->nterms, model->nseq);
    model->topic[k]->w_phi_sum =
      gsl_matrix_alloc(model->nterms, model->nseq);
    model->topic[k]->w_phi_l_sq =
      gsl_matrix_alloc(model->nterms, model->nseq);

    if (FLAGS_model == "dim"
	|| FLAGS_model == "regression") {
      sprintf(name, "%sw_phi_l-%d", root, k);
      mtx_fscanf(name, model->topic[k]->w_phi_l);

      sprintf(name, "%sw_phi_sum-%d", root, k);
      mtx_fscanf(name, model->topic[k]->w_phi_sum);

      sprintf(name, "%sw_phi_l_sq-%d", root, k);
      mtx_fscanf(name, model->topic[k]->w_phi_l_sq);
    }
  }

  if (FLAGS_model == "dim"
      || FLAGS_model == "regression"
      && data != NULL) {
    model->influence = (inf_var*) malloc(sizeof(inf_var));
    model->influence->doc_weights =
      (gsl_matrix**) malloc(sizeof(gsl_matrix*));
    int t;
    model->influence->ntime = model->nseq;
    for (t=0; t < model->nseq; ++t) {
      //	outlog("%d %d", t, model->influence->ntime);
      sprintf(name, "%sinfluence_time-%03d", root, t);
      outlog("\n reading influence weights for time %d from %s", t, name);
      model->influence->doc_weights[t] =
	gsl_matrix_alloc(data->corpus[t]->ndocs,
			 model->ntopics);
      mtx_fscanf(name, model->influence->doc_weights[t]);
    }
  } else {
    model->influence = NULL;
  }

  return(model);
}

/*
 * update lda sequence sufficient statistics from an lda posterior
 *
 */

void update_lda_seq_ss(int time,
                       const doc_t* doc,
                       const lda_post* post,
                       gsl_matrix** ss) {
    int K = post->phi->size2, N = doc->nterms;
    int k, n;

    for (k = 0; k < K; k++)
    {
        gsl_matrix* topic_ss = ss[k];
        for (n = 0; n < N; n++)
        {
            int w = doc->word[n];
            int c = doc->count[n];
            minc(topic_ss, w, time, c * mget(post->phi, n, k));
        }
    }
}

/*
 * fit lda sequence
 *
 */

double fit_lda_seq_topics(lda_seq* model,
                          gsl_matrix** ss) {
    double lhood = 0, lhood_term = 0;
    int k;

    for (k = 0; k < model->ntopics; k++)
    {
        outlog( "\nfitting topic %02d", k);
        lhood_term = fit_sslm(model->topic[k], ss[k]);
        lhood += lhood_term;
    }
    return(lhood);
}

/*
 * allocate lda seq
 *
 */

lda_seq* new_lda_seq(corpus_seq_t* data,
		     int W, int T, int K) {
    lda_seq* model = (lda_seq*) malloc(sizeof(lda_seq));

    model->ntopics = K;
    model->nterms = W;
    model->nseq = T;

    model->alpha = gsl_vector_alloc(K);
    model->topic = (sslm_var**) malloc(sizeof(sslm_var*) * K);

    // Create the vectors of total counts for each time.
    model->influence_sum_lgl = (gsl_matrix**) malloc(sizeof(gsl_matrix*) * K);

    for (int k = 0; k < K; k++) {
      //      model->w_phi_l = (gsl_matrix*) malloc(sizeof(gsl_matrix));
      // model->w_phi_l_sq = (gsl_matrix*) malloc(sizeof(gsl_matrix*));
      model->influence_sum_lgl[k] = gsl_matrix_calloc(W, T);

      model->topic[k] = sslm_var_alloc(W, T);
      if (k < FLAGS_fix_topics) {
	model->topic[k]->chain_variance = 1e-10;
      }
      model->topic[k]->w_phi_l = gsl_matrix_calloc(W, T);
      model->topic[k]->w_phi_sum = gsl_matrix_calloc(W, T);
      model->topic[k]->w_phi_l_sq = gsl_matrix_calloc(W, T);
    }
    model->influence = inf_var_alloc(K, data);

    return(model);
}

/*
 * initialize from sufficient statistics (expected counts).
 *
 */

void init_lda_seq_from_ss(lda_seq* model,
                          double topic_chain_variance,
                          double topic_obs_variance,
                          double alpha,
                          gsl_matrix* init_suffstats) {
    gsl_vector_set_all(model->alpha, alpha);

    for (int k = 0; k < model->ntopics; k++)
    {
        gsl_vector slice = gsl_matrix_column(init_suffstats, k).vector;
        sslm_counts_init(model->topic[k],
                         topic_obs_variance,
                         topic_chain_variance,
                         &slice);
	if (k < FLAGS_fix_topics) {
	  model->topic[k]->chain_variance = 1e-10;
	}
	model->topic[k]->w_phi_l = gsl_matrix_calloc(model->nterms,
						     model->nseq);
	model->topic[k]->w_phi_sum = gsl_matrix_calloc(model->nterms,
						       model->nseq);
	model->topic[k]->w_phi_l_sq = gsl_matrix_calloc(model->nterms,
							model->nseq);
    }

}


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

#include <cusp/complex.h>
#include <cblas.h>

#define CUSP_CBLAS_COMPLEX_DEFS_1(FUNC_MACRO)                                               \
  FUNC_MACRO(cusp::complex<float> , float , c)                                              \
  FUNC_MACRO(cusp::complex<double>, double, z)

#define CUSP_CBLAS_COMPLEX_DEFS_2(FUNC_MACRO)                                               \
  FUNC_MACRO(cusp::complex<float> , float , sc)                                             \
  FUNC_MACRO(cusp::complex<double>, double, dz)

#define CUSP_CBLAS_COMPLEX_DEFS_3(FUNC_MACRO)                                               \
  FUNC_MACRO(cusp::complex<float> , float , cs)                                             \
  FUNC_MACRO(cusp::complex<double>, double, zd)

#define CUSP_CBLAS_COMPLEX_AMAX(T,V,name)                                                   \
  template<int dummy>                                                                       \
  int amax( const int n, const T* X, const int incX )                                       \
  {                                                                                \
    return cblas_i##name##amax(n, (const V*) X, incX);                                      \
  }

#define CUSP_CBLAS_COMPLEX_ASUM(T,V,name)                                                   \
  template<int dummy>                                                                       \
  V asum( const int n, const T* X, const int incX )                                         \
  {                                                                                \
    return cblas_##name##asum(n, (const V*) X, incX);                                       \
  }

#define CUSP_CBLAS_COMPLEX_AXPY(T,V,name)                                                   \
  template<int dummy>                                                                       \
  void axpy( const int n, const T alpha, const T* X, const int incX, T* Y, const int incY ) \
  {                                                                                \
    cblas_##name##axpy(n, (const V*) &alpha, (const V*) X, incX, (V*) Y, incY);             \
  }

#define CUSP_CBLAS_COMPLEX_COPY(T,V,name)                                                   \
  template<int dummy>                                                                       \
  void copy( const int n, const T* X, const int incX, T* Y, const int incY )                \
  {                                                                                \
    cblas_##name##copy(n, (const V*) X, incX, (V*) Y, incY);                                \
  }

// #define CUSP_CBLAS_COMPLEX_DOTC(T,V,name)
//   T dotc( const int n, const T* X, const int incX, const T* Y, const int incY )
//   {
//     typedef typename thrust::detail::eval_if<
//       thrust::detail::is_same<V,float>::value,
//           thrust::detail::identity_<openblas_complex_float>,
//           thrust::detail::identity_<openblas_complex_double>
//     >::type Cmplx;
//     Cmplx z;
//     cblas_##name##dotc_sub(n, (const V*) X, incX, (const V*) Y, incY, &z);
//     return T(z.real, z.imag);
//   }
//
// #define CUSP_CBLAS_COMPLEX_DOTU(T,V,name)
//   T dotu( const int n, const T* X, const int incX, const T* Y, const int incY )
//   {
//     typedef typename thrust::detail::eval_if<
//       thrust::detail::is_same<V,float>::value,
//           thrust::detail::identity_<openblas_complex_float>,
//           thrust::detail::identity_<openblas_complex_double>
//     >::type Cmplx;
//     Cmplx z;
//     cblas_##name##dotu_sub(n, (const V*) X, incX, (const V*) Y, incY, &z);
//     return T(z.real, z.imag);
//   }

#define CUSP_CBLAS_COMPLEX_NRM2(T,V,name)                                                   \
  template<int dummy>                                                                       \
  V nrm2( const int n, const T* X, const int incX )                                         \
  {                                                                                \
    return cblas_##name##nrm2(n, (const V*) X, incX);                                       \
  }

#define CUSP_CBLAS_COMPLEX_SCAL(T,V,name)                                                   \
  template<int dummy>                                                                       \
  void scal( const int n, const V alpha, T* X, const int incX )                             \
  {                                                                                \
    cblas_##name##scal(n, alpha, (V*) X, incX);                                             \
  }

#define CUSP_CBLAS_COMPLEX_SWAP(T,V,name)                                                   \
  template<int dummy>                                                                       \
  void swap( const int n, T* X, const int incX, T* Y, const int incY )                      \
  {                                                                                \
    cblas_##name##swap(n, (V*) X, incX, (V*) Y, incY);                                      \
  }

#define CUSP_CBLAS_COMPLEX_GEMV(T,V,name)                                                   \
  template<int dummy>                                                                       \
  void gemv(  CBLAS_ORDER order,  CBLAS_TRANSPOSE trans,                                    \
           int m, int n, T alpha, const T* A, int lda,                                      \
           const T* x, int incx, T beta, T* y, int incy)                                    \
{                                                                                \
    cblas_##name##gemv(order, trans, m, n, (const V*) &alpha, (const V*) A, lda,            \
                       (const V*) x, incx, (const V*) &beta, (V*) y, incy);                 \
}

#define CUSP_CBLAS_COMPLEX_GERC(T,V,name)                                                   \
  template<int dummy>                                                                       \
  void ger(  CBLAS_ORDER order, int m, int n, T alpha, const T* x, int incx,                \
            const T* y, int incy, T* A, int lda)                                            \
{                                                                                \
    cblas_##name##gerc(order, m, n, (const V*) &alpha,                                      \
                      (const V*) x, incx, (const V*) y, incy,                               \
                      (V*) A, lda);                                                         \
}

#define CUSP_CBLAS_COMPLEX_HEMV(T,V,name)                                                   \
  template<int dummy>                                                                       \
  void hemv(  CBLAS_ORDER order,  CBLAS_UPLO uplo,                                          \
             int n, T alpha, const T* A, int lda,                                           \
             const T* x, int incx, T beta, T* y, int incy)                                  \
{                                                                                \
    cblas_##name##hemv(order, uplo, n, (const V*) &alpha, (const V*) A, lda,                \
                       (const V*) x, incx, (const V*) &beta, (V*) y, incy);                 \
}

#define CUSP_CBLAS_COMPLEX_HER(T,V,name)                                                    \
  template<int dummy>                                                                       \
  void her(  CBLAS_ORDER order,  CBLAS_UPLO uplo,                                           \
            int n, const V alpha, const T* x, int incx, T* A, int lda)                      \
{                                                                                \
    cblas_##name##her(order, uplo, n, alpha,                                                \
                      (const V*) x, incx, (V*) A, lda);                                     \
}

#define CUSP_CBLAS_COMPLEX_TRMV(T,V,name)                                                   \
  template<int dummy>                                                                       \
  void trmv(  CBLAS_ORDER order,  CBLAS_UPLO uplo,                                          \
              CBLAS_TRANSPOSE trans,  CBLAS_DIAG diag,                                      \
             int n, const T* A, int lda, T* x, int incx)                                    \
{                                                                                \
    cblas_##name##trmv(order, uplo, trans, diag, n,                                         \
                       (const V*) A, lda, (V*) x, incx);                                    \
}

#define CUSP_CBLAS_COMPLEX_TRSV(T,V,name)                                                   \
  template<int dummy>                                                                       \
  void trsv(  CBLAS_ORDER order,  CBLAS_UPLO uplo,                                          \
              CBLAS_TRANSPOSE trans,  CBLAS_DIAG diag,                                      \
             int n, const T* A, int lda, T* x, int incx)                                    \
{                                                                                \
    cblas_##name##trsv(order, uplo, trans, diag, n,                                         \
                       (const V*) A, lda, (V*) x, incx);                                    \
}

#define CUSP_CBLAS_COMPLEX_GEMM(T,V,name)                                                   \
  template<int dummy>                                                                       \
void gemm(  CBLAS_ORDER order,                                                              \
            CBLAS_TRANSPOSE transa,  CBLAS_TRANSPOSE transb,                                \
           int m, int n, int k, T alpha, const T* A, int lda,                               \
           const T* B, int ldb, T beta, T* C, int ldc)                                      \
{                                                                                \
    cblas_##name##gemm(order, transa, transb,                                               \
                       m, n, k, (const V*) &alpha, (const V*) A, lda,                       \
                       (const V*) B, ldb, (const V*) &beta, (V*) C, ldc);                   \
}

#define CUSP_CBLAS_COMPLEX_SYMM(T,V,name)                                                   \
  template<int dummy>                                                                       \
  void symm(  CBLAS_ORDER order,                                                            \
              CBLAS_SIDE side,  CBLAS_UPLO uplo,                                            \
             int m, int n, T alpha, const T* A, int lda,                                    \
             const T* B, int ldb, T beta, T* C, int ldc)                                    \
{                                                                                \
    cblas_##name##symm(order, side, uplo, m, n,                                             \
                       (const V*) &alpha, (const V*) A, lda,                                \
                       (const V*) B, ldb, (const V*) &beta, (V*) C, ldc);                   \
}

#define CUSP_CBLAS_COMPLEX_SYRK(T,V,name)                                                   \
  template<int dummy>                                                                       \
  void syrk(  CBLAS_ORDER order,                                                            \
              CBLAS_UPLO uplo,  CBLAS_TRANSPOSE trans,                                      \
             int n, int k, T alpha, const T* A, int lda,                                    \
             T beta, T* C, int ldc)                                                         \
{                                                                                \
    cblas_##name##syrk(order, uplo, trans, n, k,                                            \
                       (const V*) &alpha, (const V*) A, lda,                                \
                       (const V*) &beta, (V*) C, ldc);                                      \
}

#define CUSP_CBLAS_COMPLEX_SYR2K(T,V,name)                                                  \
  template<int dummy>                                                                       \
  void syr2k(  CBLAS_ORDER order,                                                           \
               CBLAS_UPLO uplo,  CBLAS_TRANSPOSE trans,                                     \
              int n, int k, T& alpha, const T* A, int lda,                                  \
              const T* B, int ldb, T& beta, T* C, int ldc)                                  \
{                                                                                \
    cblas_##name##syr2k(order, uplo, trans, n, k,                                           \
                        (const V*) &alpha, (const V*) A, lda,                               \
                        (const V*) B, ldb, (const V*) &beta, (V*) C, ldc);                  \
}

#define CUSP_CBLAS_COMPLEX_TRMM(T,V,name)                                                   \
  template<int dummy>                                                                       \
  void trmm(  CBLAS_ORDER order,                                                            \
              CBLAS_SIDE side,  CBLAS_UPLO uplo,                                            \
              CBLAS_TRANSPOSE trans,  CBLAS_DIAG diag,                                      \
             int m, int n, T alpha, const T* A, int lda,                                    \
             T* B, int ldb)                                                                 \
{                                                                                \
    cblas_##name##trmm(order, side, uplo, trans, diag, m, n,                                \
                      (const V*) &alpha, (const V*) A, lda, (V*) B, ldb);                   \
}

#define CUSP_CBLAS_COMPLEX_TRSM(T,V,name)                                                   \
  template<int dummy>                                                                       \
  void trsm(  CBLAS_ORDER order,                                                            \
              CBLAS_SIDE side,  CBLAS_UPLO uplo,                                            \
              CBLAS_TRANSPOSE trans,  CBLAS_DIAG diag,                                      \
             int m, int n, T alpha, const T* A, int lda,                                    \
             T* B, int ldb)                                                                 \
{                                                                                \
    cblas_##name##trsm(order, side, uplo, trans, diag, m, n,                                \
                       (const V*) &alpha, (const V*) A, lda, (V*) B, ldb);                  \
}

namespace cusp
{
namespace system
{
namespace cpp
{
namespace detail
{
namespace cblas
{

// LEVEL 1
CUSP_CBLAS_COMPLEX_DEFS_1(CUSP_CBLAS_COMPLEX_AMAX);
CUSP_CBLAS_COMPLEX_DEFS_2(CUSP_CBLAS_COMPLEX_ASUM);
CUSP_CBLAS_COMPLEX_DEFS_1(CUSP_CBLAS_COMPLEX_AXPY);
CUSP_CBLAS_COMPLEX_DEFS_1(CUSP_CBLAS_COMPLEX_COPY);
// CUSP_CBLAS_COMPLEX_DEFS_1(CUSP_CBLAS_COMPLEX_DOTC);
CUSP_CBLAS_COMPLEX_DEFS_2(CUSP_CBLAS_COMPLEX_NRM2);
CUSP_CBLAS_COMPLEX_DEFS_3(CUSP_CBLAS_COMPLEX_SCAL);
CUSP_CBLAS_COMPLEX_DEFS_1(CUSP_CBLAS_COMPLEX_SWAP);

// LEVEL 2
CUSP_CBLAS_COMPLEX_DEFS_1(CUSP_CBLAS_COMPLEX_GEMV);
CUSP_CBLAS_COMPLEX_DEFS_1(CUSP_CBLAS_COMPLEX_GERC);
CUSP_CBLAS_COMPLEX_DEFS_1(CUSP_CBLAS_COMPLEX_HEMV);
CUSP_CBLAS_COMPLEX_DEFS_1(CUSP_CBLAS_COMPLEX_HER);
CUSP_CBLAS_COMPLEX_DEFS_1(CUSP_CBLAS_COMPLEX_TRMV);
CUSP_CBLAS_COMPLEX_DEFS_1(CUSP_CBLAS_COMPLEX_TRSV);

// LEVEL 3
CUSP_CBLAS_COMPLEX_DEFS_1(CUSP_CBLAS_COMPLEX_GEMM);
CUSP_CBLAS_COMPLEX_DEFS_1(CUSP_CBLAS_COMPLEX_SYMM);
CUSP_CBLAS_COMPLEX_DEFS_1(CUSP_CBLAS_COMPLEX_SYRK);
CUSP_CBLAS_COMPLEX_DEFS_1(CUSP_CBLAS_COMPLEX_SYR2K);
CUSP_CBLAS_COMPLEX_DEFS_1(CUSP_CBLAS_COMPLEX_TRMM);
CUSP_CBLAS_COMPLEX_DEFS_1(CUSP_CBLAS_COMPLEX_TRSM);

} // end namespace cblas
} // end namespace detail
} // end namespace cpp
} // end namespace system
} // end namespace cusp


/****************************************************************************
 * Moreno2011Cell.h - defines cell_param struct - i.e. Cell
 *
 * Based on code from Moreno et al. 2011
 *
 *  Created on: Sep 30, 2014
 *      Author: Anthony Varghese
 ***************************************************************************/

#ifndef CELL_H_
#define CELL_H_

#define M_PI 3.14159265358979323846 /* pi - needed if using -std=c++11 */
#include <cmath>
#include <gsl/gsl_blas.h>

namespace cellmodel{
	class Cells;
}

enum NaChannelMMSolver{
	NotUsed,
	Iterative,
	LUDecomp
};

class Moreno2011Cell {
public:
	Moreno2011Cell();
	virtual ~Moreno2011Cell();
	Moreno2011Cell(const Moreno2011Cell& source);
	Moreno2011Cell& operator=(const Moreno2011Cell& rhs);

	void setParams(const double* par);
	void setXpos(unsigned int p){ pos_x = p; }
	void Copy(const Moreno2011Cell& in);
	void setEnvironment( cellmodel::Cells* env );
	void setState(double* newstate) {
		for (unsigned int i = 0; i < nstates; i++)
			state[i] = newstate[i];
	}
	/*
	 * Calling method is responsible for allocating Y array
	 */
	void getState(double* Y) {
		for (unsigned int i = 0; i < nstates; i++)
			Y[i] = state[i];
	}
	/*
	 * Calling method is responsible for allocating dYdt array
	 */
	void getRates(double* dYdt) {
		for (unsigned int i = 0; i < nstates; i++)
			dYdt[i] = rates[i];
	}
	void Calculate_Points(double t);
	double CalculateCurrents(double t);
	double CalculateCurrents(double t, const double Vx);
	void Calculate_All(const double t);
	void SetInitialConditions();
	void Calculate_Reset(double t);
	void setI_stimulus(double t);
	void updatedVdt(const double Iaxnm1, const double Iaxnp1){
		dV_old = dVdt;
		I_axnm1 = Iaxnm1;
		I_axnp1 = Iaxnp1;
		I_axial = Iaxnp1 + Iaxnm1;
		dVdt = -I_axial - I_total; // Rate of change of Vm
	}
	double getV()	const {	return V;	}
	double getdVdt()const {	return dVdt;	}
	double getI_total()const {	return I_total;	}
	void   setV	  (const double Vnew)	{ V = Vnew; }
	void   setdVdt(const double dVdtnew){ dVdt = dVdtnew; }
	/*
	 * Forward Euler update of V
	 */
	void   fe_updateV(const double dt)	{ V = V + dVdt * dt; }
	double getI_K1()	const {	return I_K1; }
	double getI_Na()	const {	return I_Na; }
	double getI_axnm1() const {	return I_axnm1; }
	double getI_axnp1() const {	return I_axnp1; }
	double getI_axial() const {	return I_axial; }
	// Na Channel States
	double getNaClosed()const { return C1 + C2 + C3; }
	double getNaIC23()	const { return IC2 + IC3; }
	double getNaIF()	const { return IF; }
	double getNaOS()	const { return OS; }
	double getNaO()		const { return O; }
	double getNaDClosed() const { return DC1 + DC2 + DC3; }
	double getNaDIC23()	const { return DIC2 + DIC3; }
	double getNaDIF()	const { return DIF; }
	double getNaDOS()	const { return DOS; }
	double getNaDO()	const { return DO; }
	double getNaDIM1()	const { return DIM1; }
	double getNaD_Closed() const { return D_C1 + D_C2 + D_C3; }
	double getNaD_IC23()const { return D_IC2 + D_IC3; }
	double getNaD_IF()	const { return D_IF; }
	double getNaD_OS()	const { return D_OS; }
	double getNaD_O()	const { return D_O; }
	double getNaD_IM1()	const { return D_IM1; }

	/*
	 * computeRushLarsenStepState
	 * - take a time step using Rush Larsen for gating variables
	 *   and semi-implicit for Markov State models
	 * Calling method is responsible for allocating Y array
	 */
	void computeRushLarsenStepState( const double t, const double dt );
	/*
	 * computeRushLarsen2StepState
	 * - take a time step using Second-order Rush Larsen for all states
	 *   and semi-implicit for Markov State models
	 * Calling method is responsible for allocating Y array
	 */
	void computeRushLarsen2StepState( const double t, const double dt, const int step, const double Vhalf );

	// Indices into the state array
	static const unsigned int nNaChFirstState = 0;
	// Number of states for Na Channel Markov state model:
	static const unsigned int nNaChLastState = 29;
	static const unsigned int nNaChStates = nNaChLastState + 1;
	// start of gating variables:
	static const unsigned int ngatem = nNaChStates;
	static const unsigned int nLastState = ngatem + 20;		// last state
	// Currently, the cell transmembrane potential is the last state
	static const unsigned int nV = nLastState;
	static const unsigned int nstates = nV + 1;	// Total number of states

	// Cell States
	double state[nstates]; // state variables for integration

	double peak_slope, t_min, V_min, t_thr, V_thr, t_max, V_max, t_EAD, V_EAD;
	double t_EAD2, V_EAD2, t_90, V_90, dV_old;
	bool flag2;
	double APD_90, DI, L_EAD;

	// To measure the work involved in the iterations:
	int 	MarkovIterations;
	double	MarkovIterationError;

private:
	// Need a pointer back to the container class to
	// find out conditions like waitTime, drug conc, etc.
	cellmodel::Cells* cellenv = nullptr;

	/****************************************************************************
	 Universal Constants
	 *****************************************************************************/
	static constexpr double R = 8314.472;		// J/mol*K
	static constexpr double T = 310;			// K
	static constexpr double F = 96485.3415;
	static constexpr double RTF = R*T/F;

	// const double Cm = 2.0;					// uF/ cm^2 -- not used anywhere
	static constexpr double CAP = 0.185;		// Cellular capacitance
	static constexpr double rho = 162;			// ohm*cm

	// Channel conductances - these can be set externally
	// used to be:
	// static constexpr double G_Na = 15.0;	//27.5  //18.5
	double G_Na = 15.0;		// 15.0 in Moreno et al 2011
	double GK1	= 5.405;	// 5.405 in Moreno et al 2011

	// Ion Valences
	static constexpr double z_Na = 1;			// Valence of Na
	static constexpr double z_Ca = 2;			// Valence of Ca
	static constexpr double z_K = 1;			// Valence of K

	//Cell Geometry
	static constexpr double pi = M_PI;			// Should be 3.141592
	static constexpr double S_cg = 0.2;			// Surface to volume ratio (um^-1)

	//Intracellular volumes
	static constexpr double V_cyto=0.016404;	//16404 uL
	static constexpr double V_sr=0.001094;
	static constexpr double V_ss=0.00005468;

	// initial conditions of ion concentrations
	static constexpr double Na_out = 140;
	static constexpr double Ca_out = 2.0;
	static constexpr double K_out = 5.4;

	static constexpr double Vmin_default =	 0.0;	// mV - a "large" value so that minimums (expected to be < 0) can be detected
	static constexpr double Vthr_default = -90.0;	// mV - a "low" value so that the threshold can be detected

	bool initializedExternally = false;
	static const NaChannelMMSolver NaChanSolver = Iterative; // LUDecomp; // Iterative;

	// Arrays for each cell:
	double rates[nstates]; // rates of change of state - derivative
	// Total number of currents and auxilliary values:
	static const unsigned int naux = 26;
	double currents[naux];

	int Cell_type;
	unsigned int pos_x=0;
//	unsigned int pos_y=0, pos_z=0; // Position of cell in 3-d

	// Na Channel constants
	static constexpr double Q10  = 3;
	// This does not work in clang, ok in gcc
	//static constexpr double Tfactor = 1.0/(pow(Q10, (37.0-(T-273))/10.0));
	static constexpr double pH   = 7.4;

	// Na channel markov state model - the individual states are only needed for outputs
	double &IC3		= state[ 0], &IC2	= state[1];
	double &IF		= state[ 2], &IM1	= state[3];
	double &IM2		= state[ 4], &C3	= state[5];
	double &C2		= state[ 6], &O		= state[7];
	double &OS		= state[ 8], &C1	= state[9];

	double &DIC3	= state[10], &DIC2	= state[11];
	double &DIF 	= state[12], &DIM1	= state[13];
	double &DIM2	= state[14], &DC3 	= state[15];
	double &DC2		= state[16], &DO	= state[17];
	double &DOS		= state[18], &DC1	= state[19];

	double &D_IC3	= state[20], &D_IC2 = state[21];
	double &D_IF	= state[22], &D_IM1 = state[23];
	double &D_IM2	= state[24], &D_C3  = state[25];
	double &D_C2	= state[26], &D_O   = state[27];
	double &D_OS	= state[28], &D_C1  = state[29];
	double &V  = state[ngatem + 20];

	// Gating variables
	double &m	= state[ngatem],		&h	  = state[ngatem + 1];
	double &j   = state[ngatem + 2];
	double &d   = state[ngatem + 3],	&f	  = state[ngatem + 4];
	double &f2  = state[ngatem + 5],	&f_Ca = state[ngatem + 6];
	double &r   = state[ngatem + 7],	&s	  = state[ngatem + 8];
	double &xr1 = state[ngatem + 9],	&xr2  = state[ngatem + 10];
	double &xs  = state[ngatem + 11];
	double &R_bar = state[ngatem + 12];
	double &mL    = state[ngatem + 13], &hL	  = state[ngatem + 14];
	double &Ca_in = state[ngatem + 15], &Ca_sr = state[ngatem + 16];
	double &Ca_ss = state[ngatem + 17];
	double &Na_in = state[ngatem + 18], &K_in = state[ngatem + 19];
	double &dVdt  = rates[ngatem + 20];

	// old gating variable values before half-step in 2nd order Rush Larsen
	double oldgates[20];
	double &oldm	= oldgates[0],	&oldh		= oldgates[1];
	double &oldj	= oldgates[2];
	double &oldd	= oldgates[3],	&oldf		= oldgates[4];
	double &oldf2	= oldgates[5],	&oldf_Ca	= oldgates[6];
	double &oldr	= oldgates[7],	&olds		= oldgates[8];
	double &oldxr1	= oldgates[9],	&oldxr2 	= oldgates[10];
	double &oldxs	= oldgates[11];
	double &oldR_bar = oldgates[12];
	double &oldmL    = oldgates[13], &oldhL		= oldgates[14];
	double &oldCa_in = oldgates[15], &oldCa_sr	= oldgates[16];
	double &oldCa_ss = oldgates[17];
	double &oldNa_in = oldgates[18], &oldK_in	= oldgates[19];

	// Currents and auxilliary values - anything that may be useful to print out for plotting
	double &I_total = currents[0];
	double &I_axial = currents[1];
	double &I_axnm1 = currents[2], &I_axnp1 = currents[3];
	double &I_stim	= currents[4];
	double &I_Na	= currents[5], &I_Na_L = currents[6];
	double &mI_Na	= currents[7], &I_Na_b = currents[8];
	double &I_Ca_L	= currents[9], &I_Ca_b = currents[10];
	double &I_Kr	= currents[11], &I_Ks = currents[12];
	double &I_K1	= currents[13], &I_Kp = currents[14];
	double &I_to	= currents[15];
	double &I_Na_Ca = currents[16], &I_Na_K = currents[17];
	double &I_p_Ca	= currents[18];
	double &I_Na_ion_total	= currents[19];
	double &I_Ca_ion_total	= currents[20];
	double &I_K_ion_total	= currents[21];
	double &I_tr	= currents[22], &I_leak = currents[23];
	double &I_up	= currents[24], &I_rel = currents[25];

	// RATES
	// Na channel markov state model
	double &dIC3	= rates[ 0], &dIC2	= rates[1];
	double &dIF		= rates[ 2], &dIM1	= rates[3];
	double &dIM2	= rates[ 4], &dC3	= rates[5];
	double &dC2		= rates[ 6], &dO	= rates[7];
	double &dOS		= rates[ 8], &dC1	= rates[9];
	double &dDIC3	= rates[10], &dDIC2 = rates[11];
	double &dDIF	= rates[12], &dDIM1 = rates[13];
	double &dDIM2	= rates[14], &dDC3	= rates[15];
	double &dDC2	= rates[16], &dDO	= rates[17];
	double &dDOS	= rates[18], &dDC1	= rates[19];
	double &dD_IC3	= rates[20], &dD_IC2 = rates[21];
	double &dD_IF	= rates[22], &dD_IM1 = rates[23];
	double &dD_IM2	= rates[24], &dD_C3	= rates[25];
	double &dD_C2	= rates[26], &dD_O	= rates[27];
	double &dD_OS	= rates[28], &dD_C1	= rates[29];

	// Gating variable rates
	double &dm		= rates[ngatem     ],	&dh		= rates[ngatem +  1];
	double &dj		= rates[ngatem +  2];
	double &dd		= rates[ngatem +  3],	&df		= rates[ngatem +  4];
	double &df2		= rates[ngatem +  5],	&df_Ca	= rates[ngatem +  6];
	double &dr		= rates[ngatem +  7],	&ds		= rates[ngatem +  8];
	double &dxr1	= rates[ngatem +  9],	&dxr2	= rates[ngatem + 10];
	double &dxs		= rates[ngatem + 11];
	double &dR_bar	= rates[ngatem + 12];
	double &dmL		= rates[ngatem + 13],	&dhL	= rates[ngatem + 14];
	double &dCa_in	= rates[ngatem + 15],	&dCa_sr	= rates[ngatem + 16];
	double &dCa_ss	= rates[ngatem + 17];
	double &dNa_in	= rates[ngatem + 18],	&dK_in	= rates[ngatem + 19];

	// Cell data points - markers of cell function
	double I_total_old, dI_total, dI_total_old, t_I_total, I_total_pt;
	double V_I_total;
	bool   flagI_total, flag_EAD, flag_EAD2;
	double t_90_old;
	double CV, CV_EAD;

	// The temporary array is used for a modified-Newton iteration to solve
	// for the markov channel state variables.
	static const unsigned int	i3  = 0,  i2  = 1,	iF  = 2,  im  = 3,
								iM  = 4,  c3  = 5,	c2  = 6,  op  = 7,
								oS  = 8,  c1  = 9;
	static const unsigned int	di3 = 10, di2 = 11,	diF = 12, dim = 13,
								diM	= 14, dc3 = 15,	dc2	= 16, dop = 17,
								doS	= 18, dc1 = 19;
	static const unsigned int	Di3	= 20, Di2 = 21,	DiF	= 22, Dim = 23,
								DiM	= 24, Dc3 = 25,	Dc2	= 26, Dop = 27,
								DoS	= 28, Dc1 = 29;

	// BLAS vectors and methods to allocate, free and copy them
	gsl_vector* Yn   = nullptr;
	gsl_vector* Ynph = nullptr;
	gsl_vector* Ynp1 = nullptr;
	gsl_vector* Ynp1i= nullptr;
	gsl_vector* DiagonalAn = nullptr;
	gsl_vector* DiagonalAnInv = nullptr;
	gsl_matrix* OffDiagonalAn = nullptr;
	void allocTemps();
	void freeTemps();
	void copyTemps(const Moreno2011Cell& source);

	/*********************************************************************************************************
	 Current Function Prototypes
	 *********************************************************************************************************/
	void WT_SCN5A_Initial_Conditions();
	void WT_SCN5A_Lidocaine();
	void WT_SCN5A_Flecainide();
	void checkNaChannelStates();
	void calcI_total(double t);
	void calcI_Na(double t);	//Fast sodium current
	void calcI_Na_L();
	void calcI_K1();			//Time independent potassium current
	void calcI_Kp();			//Plateau K current, time independent, K_out insensitive
	void calcI_Na_Ca();			//Sodium calcium exchanger
	void calcI_Na_K();			//Sodium potassium pump
	void calcI_Ca_L();			//L type calcium channel Ca contribution
	void calcI_Kr();			//Rapid rectifier
	void calcI_Ks();			//Slow rectifier
	void calcI_to();			//Transient outward current
	void calcI_p_Ca();			//Sarcolemmal calcium pump
	void calcI_Ca_b();			//Calcium background current
	void calcI_Na_b();			//Sodium background current

	void calcI_tr();
	void calcI_leak();			//Leak from SR
	void calcI_up();			//Uptake to SR
	void calcI_rel();			//Release from SR

	//Dynamic Concentrations
	void Calculate_Na_in();			//Dynamic [Na] in Myoplasm
	void Calculate_K_in();			//Dynamic [K] in Myoplasm
	void Calculate_Ca_in();			//Dynamic [Ca] in Myoplasm
	void Calculate_Ca_sr();			//Dynamic [Ca] in JSR
	void Calculate_Ca_ss();			//Dynamic [Ca] in NSR

	// Semi-implicit and Rush-Larsen
	struct NaChannelParameters{
		double pKa, portion, diffusion,
				drug_charged, drug_neutral,
				drug_distance, kd0, kd_open;
		double	a11, a12, a13, b11, b12, b13,
				a3,	 b3,  a2,  b2,  a4,  b4, a5, b5,
				ax,	 bx,  ax1, bx1,
				a13c,a22, b33, a33,
				a44, b44, a55, b55,
				ax2, a13n, a_22, b_33,
				a_44,b_44, a_55, b_55,
				kon, koff, kcoff, kcon,
				b13c,b22,  k_on,  k_off,
				ki_on, ki_off, kc_on, kc_off,
				a_33, b13n,	b_22, bx2;
		void Initialize( cellmodel::Cells* cellenv );
		void ComputeVDepTerms(const double V, cellmodel::Cells* env );
		void set_NaCh_Matrices( gsl_matrix* M, gsl_vector* D);
	};
	NaChannelParameters NaChParams;
	static constexpr double tolerance = 1e-7;
	void semi_impl_I_Na(double t, const double dt);	//Fast sodium current
	void semi_impl_WT_SCN5A(const double dt);
	void semi_impl_WT_SCN5A_LU(const double dt);
	void exponential_I_Na1(double t, const double dt);
	void exponential_I_Na2(double t, const double dt, const double Vhalf);

	void rush_I_Na_L(const double dt);
	void rush_I_Ca_L(const double dt);			//L type calcium channel Ca contribution
	void rush_I_Kr(const double dt);			//Rapid rectifier
	void rush_I_Ks(const double dt);			//Slow rectifier
	void rush_I_to(const double dt);			//Transient outward current

	void rush_I_rel(const double dt);			//Release from SR
	void rush_Na_in(const double dt);			//Dynamic [Na] in Myoplasm
	void rush_K_in(const double dt);			//Dynamic [K] in Myoplasm
	void rush_Ca_in(const double dt);			//Dynamic [Ca] in Myoplasm
	void rush_Ca_sr(const double dt);			//Dynamic [Ca] in JSR
	void rush_Ca_ss(const double dt);			//Dynamic [Ca] in NSR

	// Old code - not used and not needed - new code is working
	void semi_impl_WT_SCN5A_Lidocaine(const double dt);
	void semi_impl_WT_SCN5A_Flecainide(const double dt);
	void set_NaCh_Matrices( gsl_matrix* M, gsl_vector* D,
			const double a11,	const double a12,	const double a13,
			const double b11,	const double b12,	const double b13,
			const double a3,	const double b3,	const double a2, const double b2,
			const double a4,	const double b4,	const double a5, const double b5,
			const double ax,	const double bx,	const double ax1,const double bx1,
			const double a13c,  const double a22,	const double b33,const double a33,
			const double a44,   const double b44,	const double a55,const double b55,
			const double ax2,   const double a13n,  const double a_22, const double b_33,
			const double a_44,	const double b_44,  const double a_55, const double b_55,
			const double kon,   const double koff,  const double kcoff, const double kcon,
			const double b13c,	const double b22,	const double k_on,	const double k_off,
			const double ki_on,	const double ki_off,const double kc_on, const double kc_off,
			const double a_33,	const double b13n,	const double b_22,	const double bx2 );
	double MCtemp[nNaChStates];
	double &mIC3	= MCtemp[ 0], &mIC2 	= MCtemp[1];
	double &mIF		= MCtemp[ 2], &mIM1		= MCtemp[3];
	double &mIM2	= MCtemp[ 4], &mC3		= MCtemp[5];
	double &mC2		= MCtemp[ 6], &mO		= MCtemp[7];
	double &mOS		= MCtemp[ 8], &mC1		= MCtemp[9];
	double &mDIC3	= MCtemp[10], &mDIC2	= MCtemp[11];
	double &mDIF	= MCtemp[12], &mDIM1	= MCtemp[13];
	double &mDIM2	= MCtemp[14], &mDC3		= MCtemp[15];
	double &mDC2	= MCtemp[16], &mDO		= MCtemp[17];
	double &mDOS	= MCtemp[18], &mDC1		= MCtemp[19];
	double &mD_IC3	= MCtemp[20], &mD_IC2	= MCtemp[21];
	double &mD_IF	= MCtemp[22], &mD_IM1	= MCtemp[23];
	double &mD_IM2	= MCtemp[24], &mD_C3	= MCtemp[25];
	double &mD_C2	= MCtemp[26], &mD_O		= MCtemp[27];
	double &mD_OS	= MCtemp[28], &mD_C1	= MCtemp[29];
	void semi_impl_WT_SCN5A_Lidocaine_compare_BLAS_old(const double dt);
	void semi_impl_WT_SCN5A_Flecainide_old (const double dt);
};

#endif /* CELL_H_ */


#ifndef MOVE_H
#define MOVE_H
#include <cmath>
#include "param.h"
#include "mpiutils.h"
#include <gsl/gsl_rng.h>

namespace weakarg
{
extern gsl_rng * rng;

/**
    @brief This virtual class represents a move of the MCMC
*/
class Move
{
protected:
    std::string name;///<The moves name
    std::string description;///<The moves description
    Param*param;///<Parameter set on which the move is performed
    double alpha;///<Weighting for this move (probability is alpha/sum_i(alpha_i) )
    int intDist; //FMA_CHANGES: Number of intermediate distns for AISRJ
    int numcalls;///<Number of attempts at the move
    int numaccept;///<Number of acceptances of the move
public:
    Move(Param*p,double a=1.0);///<Constructor specifying on which Parameter the move is to be performed
    virtual Move * clone()=0;
    virtual ~Move()=0;
    virtual int move()=0;///<Performs the move once
    virtual inline int getTopoCounts(){return(-1);};
    virtual inline int getTopoAcc(){return(-1);};
    virtual inline int move(vector<int> *samplespace){cout<<"Problem in Move!"<<endl;return(move());};///<Performs the move once
    inline void setParam(Param * param)
    {
        this->param=param;
    }///<Change the Parameter on which the move is to be performed
    inline std::string desc()
    {
        return description;
    }///<Returns a description of the move
    inline std::string getName()
    {
        return name;
    }///<Returns a description of the move
    inline void setName(std::string newname)
    {
        name=newname;
    }
    inline void setAlpha(double a)
    {
        alpha=a;
    }
    ;///<Sets alpha for the move
    inline double getAlpha()
    {
        return(alpha);
    }
    inline int getCounts()
    {
        return(numcalls);
    }
    inline int getAcc()
    {
        return(numaccept);
    }

    ;///<Returns alpha for the move
};

} // end namespace weakarg
#endif


/*                                                                                */
/* rt - retention time (predictor) v4.0.20160301                                                      */
/*                                                                                */
/* (c) Magnus Palmblad, Division of Ion Physics, Uppsala University, 2002-                            */
/*                                                                                */
/*                                                                                */
/* Version 4.0 of rt now also accepts pepXML input instead of just tab-delimited files, recognizes    */
/* the input format automatically and can generate output (retention coefficients) either as a tab-   */
/* delimited text file or an XML structure. Known bugs: This version does not extract modifications   */
/* from the pepXML files and only works with the pepXML files generated from PeptideProphet.          */
/* The program does not perform detailed checks on the input, i.e. peptide lengths and syntax errors. */
/*                                                                                */
/* Usage: rt -i <training set in tab delimited or pepXML format> [-f <tab>|<XML> output format)]>     */
/*                                                                                */
/* compile with gcc -o rt rt3.c -lgsl -lgslcblas -lpepXML -LpepXMLLib                                 */
/*                                                                                */
/*                                                                                */
/* The mzIdentML support is extremely "quick-and-dirty" but work with tested idconvert-ed pepXML      */
/*                                                                                */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <math.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_multifit.h>
#include "pepXMLLib/pepXMLReader.h"

#define AMINO_ACIDS "ARNDCEQGHILKMFPSTWYVmsty"
#define MAX_PEPTIDE_LENGTH 100

int main(int argc, char *argv[])
{
  char line[250];
  char infile[85], outfile[85]; /* for reading input and writing output */
  char slask[85];

  typedef struct {
    char sequence[MAX_PEPTIDE_LENGTH];
    int composition[24];
    float retention;
  } training_set_type;

  training_set_type *training_set;

  char output[256];
  char output_format[256];
  char input_format[20];

  char *p, q[100], **PEP;

  pmsms_pipeline_analysis pepXML_file;
  pdelegate_list dlgl;
  pdelegate_type dlg;

  FILE *inp, *outp;

  int a, b, i, j, k, l, m, n, seqlen, n_training,length;
  double xi, yi, ei, chisq;
  gsl_matrix *X, *cov;
  gsl_vector *y, *w, *c;
  gsl_multifit_linear_workspace *work;

  /* parsing command line parameters */

  if ((argc<2)||(argc>5))
    {
      printf("usage: rt -i <training set in tab delimited or PeptideProphet pepXML format> [-f <tab>|<XML> output format)] (type rt --help for more information\n");
      return -1;
    }

  if( (strcmp(argv[1],"--help")==0) || (strcmp(argv[1],"-help")==0) || (strcmp(argv[1],"-h")==0) ) /* want help? */
    {
      printf("rt - (c) Magnus Palmblad 2002-2012\n\nusage: rt -i <training set in tab-delimited or pepXML format (after PeptideProphet)> [-f <tab>|<XML> output format)]\nrt trains a linear model for predicting retention time, or in fact any additive property as function of amino acid composition. The input should either be a tab-delimited text file with peptide sequences in the first column and a numerical value (retention time) in the second column, or a pepXML file generated by PeptideProphet in the Trans-Proteomic Pipeline. For more information, see http://www.ms-utils.org/rt or e-mail magnus.palmblad@gmail.com\n");
      return 0;
    }

  for(i=1;i<argc;i++) {
    if( (argv[i][0]=='-') && (argv[i][1]=='i') ) strcpy(infile,&argv[strlen(argv[i])>2?i:i+1][strlen(argv[i])>2?2:0]);
    if( (argv[i][0]=='-') && (argv[i][1]=='f') ) strcpy(output_format,&argv[strlen(argv[i])>2?i:i+1][strlen(argv[i])>2?2:0]);
  }

  if( (toupper(output_format[0])!='T') && (toupper(output_format[0])!='X') && (toupper(output_format[0])!='C') ) {
    printf("usage: rt -i <training set in tab delimited or pepXML format> [-f <tab>|<XML> output format)]\n");
    return -1;
  }

  strcpy(input_format,"tab"); /* default to tab-delimited input */

  /* reading training set file */

  //printf("checking input format and scanning training set in file %s...", infile); fflush(stdout);
  n=0;
  if ((inp = fopen(infile, "r"))==NULL) {printf("error opening training set file\n");return -1;}
  fgets(line, 250, inp);
  if(line[0]=='<') {strcpy(input_format,"pepXML"); n++;}
  fgets(line, 250, inp);
  while(line[i]) {line[i]=toupper(line[i]); i++;}
  if(strstr(line,"MZIDENTML")!=NULL) {strcpy(input_format,"mzIdentML"); n++;}

  while (fgets(line, 250, inp) != NULL) n++;

  PEP=(char**)malloc(sizeof(char*)*n);
  for(i=0;i<10000;i++) PEP[i]=(char*)malloc(sizeof(char)*200);

  if(strcmp(input_format,"tab")==0)
    {
      n_training=n;

      // printf("done\nallocating memory for %i peptides in training set...", n_training); fflush(stdout);
      training_set=(training_set_type*)malloc(sizeof(training_set_type)*n_training);

      // printf("done\nreading training set in tab delimited format (peptide tab retention time) from file %s...", infile); fflush(stdout);
      if ((inp = fopen(infile, "r"))==NULL) {printf("error opening training set file\n");return -1;}
      n=0;

      while (fgets(line, 250, inp) != NULL)
	{
	  sscanf(line,"%s\t%f",training_set[n].sequence,&(training_set[n].retention));
	  n++;
	}
      n_training=n;
      fclose(inp);
      // printf("done\n");
    }

  if(strcmp(input_format,"pepXML")==0)
    {
      // printf("done\nscanning training set in pepXML format from file %s...", infile); fflush(stdout);
      n=0;
      pepXML_file = read_pepxml_file(infile, 0, 0, NULL);
      for (i=0; i<pepXML_file->run_summary_count; i++) {
	for (j=0; j<pepXML_file->run_summary_array[i].spectrum_query_count; j++) n++;
      }
      n_training=n;

      // printf("done\nallocating memory for %i peptides in training set...", n_training); fflush(stdout);
      training_set=(training_set_type*)malloc(sizeof(training_set_type)*n_training);

      n=0;
      // printf("reading training set in pepXML format from file %s...", infile); fflush(stdout);
      for (i=0; i<pepXML_file->run_summary_count; i++) {
	for (j=0; j<pepXML_file->run_summary_array[i].spectrum_query_count; j++) {
	  strcpy(training_set[n].sequence, pepXML_file->run_summary_array[i].spectrum_query_array[j].search_result_array[0].search_hit_array[0].peptide);
	  training_set[n].retention = pepXML_file->run_summary_array[i].spectrum_query_array[j].retention_time_sec;
	  n++;
	}
      }
    }

  if(strcmp(input_format,"mzIdentML")==0)
    {
      //printf("done\nscanning training set in mzIdentML format from file %s...", infile); fflush(stdout);
      n=0;
      if ((inp = fopen(infile, "r"))==NULL) {printf("error opening training set file\n");return -1;}
      while (fgets(line, 250, inp) != NULL)
	{
	  if(strstr(line,"PeptideEvidence")!=NULL) break;
	  if(strstr(line,"<Peptide id")!=NULL)
	    {
	      //PEP[a]=(char*)malloc(sizeof(char)*strlen(q));
	      p=strtok(line,"\""); p=strtok('\0',"\"");
	      a=atoi(p+4);
	      //printf("\n%i = ",a); fflush(stdout);
	      p=strtok('\0',"\"");
	      if(strstr(p,"<PeptideSequence>")!=NULL)
		{
		  p=strchr(p+10,'>')+1;
		  i=strspn(p,AMINO_ACIDS);
		  strncpy(q,p,i); q[i]='\0';
		  // printf("PEP_%i=%s\n",a,q); fflush(stdout);
		  strcpy(PEP[a],q);
		}
	      n++;
	    }
	}

      n_training=n;

      // printf("done\nallocating memory for %i peptides in training set...", n_training); fflush(stdout);
      training_set=(training_set_type*)malloc(sizeof(training_set_type)*n_training*10);

      n=0;
      while (fgets(line, 250, inp) != NULL)
	{
	  if(strstr(line,"peptide_ref")!=NULL)
	    {
	      p=strstr(line,"peptide_ref");
	      a=atoi(strtok(p+17,"\""));
	    }
	  if(strstr(line,"name=\"scan start time\" value")!=NULL)
	    {
	      p=strstr(line,"name=\"scan start time\" value");
	      i=strcspn(p+31,"\"");
	      strncpy(q,p+30,i); q[i]='\0';
	      // if(strlen(PEP[a])>6) {printf("%i, %s, %s\n",a,PEP[a],q); fflush(stdout);}
	      if(strlen(PEP[a])>6) {strcpy(training_set[n].sequence,PEP[a]); training_set[n].retention=atof(q); n++;}
	    }
	}
    }

  n_training=n;
  //return 0;

  /* allocating memory for training */

  X=gsl_matrix_alloc(n_training,25); /* 20 normal amino acids, pS, pT, pY, oxidized M and V0 */
  y=gsl_vector_alloc(n_training);
  w=gsl_vector_alloc(n_training);
  c=gsl_vector_alloc(25);
  cov=gsl_matrix_alloc(25,25);

  for(i=0;i<25;i++) gsl_vector_set(c,i,1.0);

  /* calculate training set X matrix (amino acid composition for each peptide in training set) */

  for(n=0;n<n_training;n++)
    {
      gsl_vector_set(y,n,training_set[n].retention);
      gsl_vector_set(w,n,0.36);

      for(i=0;i<24;i++) training_set[n].composition[i]=0;
      {
	for(i=0;i<strlen(training_set[n].sequence);i++)
	  {
	    a=24-strlen(strchr(AMINO_ACIDS,training_set[n].sequence[i]));
	    training_set[n].composition[a]++;
	  }
      }

      for(i=0;i<24;i++) gsl_matrix_set(X,n,i,training_set[n].composition[i]);
      gsl_matrix_set(X,n,24,1); /* to get V0 - the "offset" between runs */
    }

  work=gsl_multifit_linear_alloc(n_training,25);
  gsl_multifit_wlinear(X,w,y,c,cov,&chisq,work);
  gsl_multifit_linear_free(work);

  if(toupper(output_format[0])=='T') /* output as tab-delimited text */
   {
     // printf("\nbest fit to experimental retention data:\n");
     for(i=0;i<24;i++) printf("%c\t%f\n",AMINO_ACIDS[i],gsl_vector_get(c,(i)));
     printf("O\t%f\n\n",gsl_vector_get(c,(24)));

     return 0;
   }

  if(toupper(output_format[0])=='C') /* output CSV */
   {
     // printf("\nbest fit to experimental retention data:\n");
     for(i=0;i<24;i++) printf("%c,%f\n",AMINO_ACIDS[i],gsl_vector_get(c,(i)));
     printf("O,%f\n\n",gsl_vector_get(c,(24)));

     return 0;
   }

  if(toupper(output_format[0])=='X') /* output XML */
   {
     // printf("\nretenion model in XML structure:\n\n"); /* comment out in deployed versions */

     printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
     printf("<rt_model training_file=\"%s\" rt_version=\"4.0\" input_format=\"%s\">\n",infile,input_format);
     for(i=0;i<19;i++) printf(" <retention_coefficient amino_acid=\"%c\" value=\"%f\"/>\n",AMINO_ACIDS[i],gsl_vector_get(c,(i)));
     printf(" <retention_coefficient amino_acid=\"m\" modification=\"oxidation\" value=\"%f\"/>\n",gsl_vector_get(c,(19)));
     for(i=21;i<24;i++) printf(" <retention_coefficient amino_acid=\"%c\" modification=\"phosphorylation\" value=\"%f\"/>\n",AMINO_ACIDS[i],gsl_vector_get(c,(i)));
     printf(" <retention_coefficient amino_acid=\"O\" modification=\"constant offset\" value=\"%f\"/>\n",gsl_vector_get(c,(24)));
     printf("</rt_model>\n");

     return 0;
   }
}


/*
** ECM - eigenvector centrality mapping, I/O interface
**
** G.Lohmann, MPI-KYB, updated Oct 2018
*/

#include <viaio/Vlib.h>
#include <viaio/VImage.h>
#include <viaio/mu.h>
#include <viaio/option.h>

#include <gsl/gsl_matrix.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_math.h>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#ifdef _OPENMP
#include <omp.h>
#endif /*_OPENMP*/

#define SQR(x) ((x) * (x))
#define ReLU(x) ((x) > 0 ? (x) : 0)

VImage WriteOutput(VAttrList list,VImage map,float *eigvec,size_t n)
{
  size_t i;
  int b,r,c;
  int nrows=0,ncols=0,ntimesteps=0;
  int nslices = VAttrListNumImages(list);
  VImage *src = VAttrListGetImages(list,nslices);
  VImageDimensions(src,nslices,&ntimesteps,&nrows,&ncols);

  VImage dest = VCreateImage(nslices,nrows,ncols,VFloatRepn);
  VFillImage(dest,VAllBands,0);
  VCopyImageAttrs (src[0], dest);
  VSetAttr(VImageAttrList(dest),"modality",NULL,VStringRepn,"ECM");

  for (i=0; i<n; i++) {
    b = VPixel(map,0,0,i,VInteger);
    r = VPixel(map,0,1,i,VInteger);
    c = VPixel(map,0,2,i,VInteger);
    if (b < 0 || r < 0 || c < 0) VError("  WriteOutput: illegal address %d %d %d",b,r,c);
    VPixel(dest,b,r,c,VFloat) = eigvec[i];
  }
  return dest;
}

void NormalizeData(gsl_matrix_float *X,int nt)
{
  int i,j;
  double s1,s2,mean,sd,u,nx,tiny=1.0e-6;
  float *arr=NULL;
  nx = (double)nt;

  for (i=0; i<X->size1; i++) {
    s1=s2=0;
    arr = gsl_matrix_float_ptr(X,i,0);
    for (j=0; j<nt; j++) {
      u = (double)arr[j];
      s1 += u;
      s2 += u*u;
    }
    mean = s1/nx;
    sd = sqrt((s2 - nx * mean * mean) / (nx - 1.0));

    arr = gsl_matrix_float_ptr(X,i,0);
    for (j=0; j<nt; j++) {
      u = (double)arr[j];
      if (sd > tiny) arr[j] = (float)((u-mean)/sd);
      else arr[j] = 0;
    }
  }

  for (i=0; i<X->size1; i++) {
    for (j=0; j<X->size2; j++) {
      double u = (double)gsl_matrix_float_get(X,i,j);
      if (gsl_isnan(u) || gsl_isinf(u)) gsl_matrix_float_set(X,i,j,0);
    }
  }
}

VImage VoxelMap(VAttrList list,VImage mask,size_t nvox)
{
  int b,r,c;

  /* read image dims */
  int nrows=0,ncols=0,ntimesteps=0;
  int nslices = VAttrListNumImages(list);
  VImage *src = VAttrListGetImages(list,nslices);
  VImageDimensions(src,nslices,&ntimesteps,&nrows,&ncols);

  /*  voxel addresses */
  VImage map = VCreateImage(1,5,nvox,VIntegerRepn);
  if (map == NULL) VError(" error allocating addr map");
  VFillImage(map,VAllBands,0);
  VPixel(map,0,3,0,VInteger) = nslices;
  VPixel(map,0,3,1,VInteger) = nrows;
  VPixel(map,0,3,2,VInteger) = ncols;

  int i = 0;
  for (b=0; b<nslices; b++) {
    for (r=0; r<nrows; r++) {
      for (c=0; c<ncols; c++) {
	if (VGetPixel(mask,b,r,c) < 0.1) continue;
	VPixel(map,0,0,i,VInteger) = b;
	VPixel(map,0,1,i,VInteger) = r;
	VPixel(map,0,2,i,VInteger) = c;
	i++;
      }
    }
  }
  return map;
}

/* count number of voxels */
size_t NumVoxels(VImage mask)
{
  int b,r,c;
  size_t nvox = 0;
  for (b=0; b<VImageNBands(mask); b++) {
    for (r=0; r<VImageNRows(mask); r++) {
      for (c=0; c<VImageNColumns(mask); c++) {
	if (VGetPixel(mask,b,r,c) < 0.1) continue;
	nvox++;
      }
    }
  }
  fprintf(stderr," nvoxels: %ld\n",(long)nvox);
  return nvox;
}

gsl_matrix_float *ReadDataECM(VAttrList list,VImage mask,VImage map,VShort first,VShort length,int type,size_t nvox)
{
  size_t i,j;
  int b,r,c;

  /* read image dims */
  int nrows=0,ncols=0,ntimesteps=0,nt=0;
  int nslices = VAttrListNumImages(list);
  VImage *src = VAttrListGetImages(list,nslices);
  VImageDimensions(src,nslices,&ntimesteps,&nrows,&ncols);

  if (VImageNRows(mask) != nrows)
    VError(" inconsistent image dims, mask has %d rows, image has %d rows",VImageNRows(mask),nrows);
  if (VImageNColumns(mask) != ncols)
    VError(" inconsistent image dims, mask has %d columns, image has %d columns",VImageNColumns(mask),ncols);
  if (VImageNBands(mask) != nslices)
    VError(" inconsistent image dims, mask has %d slices, image has %d slices",VImageNBands(mask),nslices);

   /* get time steps to include */
  if (length < 1) length = ntimesteps-1;
  int last = first + length;
  if (last >= ntimesteps) last = ntimesteps-1;
  if (first < 0) first = 0;
  nt = last - first + 1;
  if (nt < 2) VError(" not enough timesteps, nt= %d",nt);
  fprintf(stderr," ntimesteps: %d, first= %d, last= %d, nt= %d\n",
	  (int)ntimesteps,(int)first,(int)last,(int)nt);

  /* alloc and fill  matrix X */
  size_t dim = nt;
  if (type == 0 || type == 7) dim = 2*nt;
  gsl_matrix_float *X = gsl_matrix_float_calloc(nvox,dim);
  for (i=0; i<nvox; i++) {

    b = VPixel(map,0,0,i,VInteger);
    r = VPixel(map,0,1,i,VInteger);
    c = VPixel(map,0,2,i,VInteger);

    float *ptr = gsl_matrix_float_ptr(X,i,0);
    int k;
    j = 0;
    for (k=first; k<=last; k++) {
      if (j >= X->size2) VError(" j= %d %d",j,X->size2);
      if (k >= VImageNBands(src[b])) VError(" k= %d %d",k, VImageNBands(src[b]));
      *ptr++ = (float) VGetPixel(src[b],k,r,c);
      j++;
    }
  }

  /* normalize */
  NormalizeData(X,nt);

  return X;
}