/**
 * This file is part of yasimSBML (http://www.labri.fr/perso/ghozlane/metaboflux/about/yasimSBML.php)
 * Copyright (C) 2010 Amine Ghozlane from LaBRI and University of Bordeaux 1
 *
 * yasimSBML is free software: you can redistribute it and/or modify
 * it under the terms of the Lesser GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * yasimSBML is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the Lesser GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * \file simulation.c
 * \brief Simulate a petri net
 * \author {Amine Ghozlane}
 * \version 1.0
 * \date 27 octobre 2009
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <unistd.h>
#include <gsl/gsl_rng.h>
#include <sbml/SBMLTypes.h>
#include "especes.h"
#include "simulation.h"

/*TODO Verification des BoundaryConditions,Local parameter values, Global parameter values, Reaction, des equations */

/**
 * \fn void SBML_initEspeceAmounts(Model_t *mod, pEspeces molecules, int nbEspeces)
 * \author Amine Ghozlane
 * \brief  Alloc memory and initialize the struct Especes
 * \param  mod Model of the SBML file
 * \param  molecules Struct Especes
 * \param  nbEspeces Number of molecules
 */
void SBML_initEspeceAmounts(Model_t *mod, pEspeces molecules, int nbEspeces) {
  int i;
  Species_t *esp;

  /* Initialisation des quantites des especes*/
  for (i = 0; i < nbEspeces; i++) {
      esp = Model_getSpecies(mod, i);
      /*printf("espece: %s, compartiment : %s\n",Species_getId(esp),Species_getCompartment(esp));*/
      Especes_save(molecules, i, Species_getInitialAmount(esp),
          Species_getId(esp), Species_getCompartment(esp));
  }
}

/**
 * \fn void SBML_setReactions(Model_t *mod, pEspeces molecules, pScore result, double *reactions_ratio, int nbReactions, int nbEspeces)
 * \author Amine Ghozlane
 * \brief  Alloc memory and initialize the struct Especes
 * \param  mod Model of the SBML file
 * \param  molecules Struct Especes
 * \param  result Struct Score
 * \param  reactions_ratio List of computed reaction ratio
 * \param  nbReactions Number of reaction
 * \param  nbEspeces Number of molecules
 */
void SBML_setReactions(Model_t *mod, pEspeces molecules, int nbReactions, int nbEspeces) {
  int ref = 0, i, j;
  SpeciesReference_t *reactif;
  Species_t *especeId;
  Reaction_t *react;
  const char *kf;
  const ASTNode_t *km;
  KineticLaw_t *kl;

  /*fprintf(stdout, "Debut reaction\n");*/
  /* Recherche les reactions ou apparaissent chaque espece */
  for (i = 0; i < nbReactions; i++) {
      react = Model_getReaction(mod, i);
      for (j = 0; j < (int)Reaction_getNumReactants(react); j++) {
          reactif = Reaction_getReactant(react, j);
          especeId = Model_getSpeciesById(mod, SpeciesReference_getSpecies(
              reactif));
          ref = Especes_find(molecules, Species_getId(especeId), nbEspeces);
          /*printf("Reactif :Quantite de ref %d :%f \n", ref,Especes_getQuantite(molecules, ref));*/

          kl = Reaction_getKineticLaw(react);

          if (KineticLaw_isSetFormula(kl)) {
              kf = KineticLaw_getFormula(kl);
              /*printf("c du kf %s\n", kf);*/
              Especes_allocReactions(molecules, ref, react,
                  SBML_evalExpression(kf));
          } else {
              km = KineticLaw_getMath(kl);
              printf("C du kM... cas encore ignore\n");
              exit(1);
          }
      }
  }
  /*fprintf(stdout, "Fin reaction\n");*/
}

/**
 * \fn double SBML_evalExpression(const char *formule)
 * \author Amine Ghozlane
 * \brief  Get the reaction ratio define in the sbml
 * \param  formule Formule SBML
 * \return Return double value of the constraint
 */
double SBML_evalExpression(const char *formule) {
  return atof(formule);
}

/**
 * \fn int SBML_checkQuantite(Model_t *mod, Reaction_t *react, int nbEspeces, pEspeces molecules)
 * \author Amine Ghozlane
 * \brief  Determine the number of reaction for one molecule
 * \param  mod Model of the SBML file
 * \param  react Reaction id
 * \param  nbEspeces Number of molecules
 * \param  molecules Struct Especes
 * \return Number of reaction for one molecule
 */
int SBML_checkQuantite(Model_t *mod, Reaction_t *react, int nbEspeces, pEspeces molecules)
{
  double quantite = 0.0, minStep = 0.0, temp = 0.0;
  int ref = 0, i;
  SpeciesReference_t *reactif;
  Species_t *especeId;

  reactif = Reaction_getReactant(react, 0);
  especeId = Model_getSpeciesById(mod, SpeciesReference_getSpecies(reactif));
  ref = Especes_find(molecules, Species_getId(especeId), nbEspeces);

  if ((quantite = Especes_getQuantite(molecules, ref)) <= 0.0) {
      /*printf("je fais le return\n");*/
      return END;
  }
  /*printf("quantite : %d\n",quantite);*/
  minStep = quantite / SpeciesReference_getStoichiometry(reactif);
  /*printf("quantite : %d, minStep init : %d\n",quantite,minStep);*/
  /* Cas ou le nombre de pas est egal a 0 */
  if(minStep==0.0) return END;

  for (i = 1; i < (int)Reaction_getNumReactants(react); i++) {
      reactif = Reaction_getReactant(react, i);
      especeId = Model_getSpeciesById(mod, SpeciesReference_getSpecies(reactif));
      ref = Especes_find(molecules, Species_getId(especeId), nbEspeces);
      quantite= Especes_getQuantite(molecules, ref);
      /* La quantite est egale a 0 */
      if(quantite<=0.0) return END;
      temp = floor(Especes_getQuantite(molecules, ref)/SpeciesReference_getStoichiometry(reactif));
      /* Cas ou le nombre de pas est egal a 0 */
      if(temp==0.0) return END;
      /* Si le nouveau nombre est inferieur au precedent, on change la valeur de minStep */
      if (minStep > temp) minStep = temp;
  }

  return (int)minStep;
}

/**
 * \fn Reaction_t * SBML_reactChoice(pEspeces molecules, const gsl_rng * r, int ref)
 * \author Amine Ghozlane
 * \brief  Determine randomly the reaction to achieve for several nodes reactions
 * \param  molecules Struct Especes
 * \param  r Random number generator
 * \param  ref Number reference of one molecule
 * \return Id of the selected reaction
 */
Reaction_t * SBML_reactChoice(pEspeces molecules, const gsl_rng * r, int ref) {

  pReaction temp = NULL;
  pReaction Q = molecules[ref].system;
  double value = gsl_rng_uniform(r) * 100.0;
  double choice = 0.0;

  /*printf("Choix de la reaction :\n");
	 printf("value : %f\n", value);*/

  /* Choix de la reaction a realiser */
  if (value < Q->ratio)
    temp = Q;
  else {
      do {
          /*printf("reaction %s : ratio %f\n", Reaction_getId(Q->link), Q->ratio);*/
          choice += Q->ratio;
          /*printf("choice : %f\n", choice);*/
          Q = Q->suivant;
          temp = Q;
          /*printf("ratio suivant : %f\n",Q->ratio );*/
      } while (Q->suivant != NULL && value <= (choice + Q->suivant->ratio)
          && value > choice);
  }
  if (temp == NULL) {
      fprintf(stderr, "on a un probleme de ratio\n");
      exit(EXIT_FAILURE);
  }

  /*printf("Resultat :\n");
	 printf("value : %f, choice :%f\n", value, (choice + Q->ratio));
	 printf("reaction %s : ratio %f\n", Reaction_getId(temp->link), temp->ratio);*/
  /*if (Q->suivant == NULL)
	 printf("choice :%f", choice);*/
  /*printf("c bon\n");*/

  return (temp->link);

}

/**
 * \fn void SBML_reaction(Model_t *mod, pEspeces molecules, Reaction_t *react, int nbEspeces)
 * \author Amine Ghozlane
 * \brief  Simulation of a discrete transision
 * \param  mod Model of the SBML file
 * \param  molecules Struct Especes
 * \param  react Reaction id
 * \param  nbEspeces Number of molecules
 */
void SBML_reaction(Model_t *mod, pEspeces molecules, Reaction_t *react, int nbEspeces)
{
  SpeciesReference_t *reactif;
  Species_t *especeId;
  int i, ref = 0;

  /*boucle pour retirer des reactifs*/
  for (i = 0; i < (int)Reaction_getNumReactants(react); i++) {
      reactif = Reaction_getReactant(react, i);
      especeId = Model_getSpeciesById(mod, SpeciesReference_getSpecies(reactif));
      ref = Especes_find(molecules, Species_getId(especeId), nbEspeces);

      /*printf("Reactif : %s",Species_getId(especeId) );
		 printf("ref : %d\n",ref);
		 printf("Reactif :Quantite de ref %d :%d \n",ref,Espece_getQuantite(molecules,ref));*/
      Especes_setQuantite(molecules, ref,(Especes_getQuantite(molecules, ref)- SpeciesReference_getStoichiometry(reactif)));
      /*printf("Apres Reactif: Quantite de ref %d :%d \n",ref,Espece_getQuantite(molecules,ref));*/
  }

  /*boucle pour ajouter des produits */
  for (i = 0; i < (int)Reaction_getNumProducts(react); i++) {
      reactif = Reaction_getProduct(react, i);
      especeId = Model_getSpeciesById(mod, SpeciesReference_getSpecies(reactif));
      /*printf("Produit : %s",Species_getId(especeId) );*/
      ref = Especes_find(molecules, Species_getId(especeId), /*Species_getCompartment(especeId),*/ nbEspeces);
      /*printf("Produit : ref : %d\n",ref);
		 printf("Produit : Quantite de ref %d :%d \n",ref,Espece_getQuantite(molecules,ref));*/

      Especes_setQuantite(molecules, ref,(Especes_getQuantite(molecules, ref)+ SpeciesReference_getStoichiometry(reactif)));
      /*printf("Apres Produit : Quantite de ref %d :%d \n",ref,Espece_getQuantite(molecules,ref));*/
  }
}

/**
 * \fn void SBML_allocTest(pTestReaction T, int nbReactions)
 * \author Amine Ghozlane
 * \brief  Alloc memory and initialize the struct pTestReaction
 * \param  T Empty struct TestReaction
 * \param  nbReactions Number of reactions
 */
void SBML_allocTest(pTestReaction T, int nbReactions)
{
  int i;

  /* Initialisation des tableaux des reactions */
  T->tabReactions= (Reaction_t **) malloc(nbReactions * sizeof(Reaction_t *));
  T->minStepTab = (int*) malloc(nbReactions * sizeof(int));

  if (T->tabReactions == NULL)
    exit(EXIT_FAILURE);
  if (T->minStepTab == NULL)
    exit(EXIT_FAILURE);

  for (i = 0; i < nbReactions; i++) {
      T->tabReactions[i] = NULL;
      T->minStepTab[i] = 0;
  }
}

/**
 * \fn void SBML_freeTest(pTestReaction T)
 * \author Amine Ghozlane
 * \brief  Free memory of the struct TestReaction
 * \param T Struct TestReaction gives data on reaction
 */
void SBML_freeTest(pTestReaction T)
{
  if (T->tabReactions != NULL)
    free(T->tabReactions);
  if (T->minStepTab != NULL)
    free(T->minStepTab);
}

/**
 * \fn int SBML_EstimationReaction(Model_t *mod, pTestReaction T, pEspeces molecules, int ref, int nbEspeces)
 * \author Amine Ghozlane
 * \brief  Alloc memory and initialize the struct Especes
 * \param  mod Model of the SBML file
 * \param  T Struct TestReaction gives data on reaction
 * \param  molecules Struct Especes
 * \param  ref Number reference of one molecule
 * \param  nbEspeces Number of molecules
 * \return Estimated number of feasible step by reaction
 */
int SBML_EstimationReaction(Model_t *mod, pTestReaction T, pEspeces molecules, int ref, int nbEspeces)
{
  pReaction Q = molecules[ref].system;
  int i = 0, min = 0, curr = 0;

  /* Compte le nombre de reactions rattachees a une espece */
  while (Q != NULL) {
      T->tabReactions[i] = Q->link;
      T->minStepTab[i] = SBML_checkQuantite(mod, Q->link, nbEspeces,
          molecules);
      if (i == 0)
        min = T->minStepTab[i];
      curr = T->minStepTab[i];
      if (T->minStepTab[i] <= 0)
        return END;
      else if (curr < min)
        min = curr;
      Q = Q->suivant;
      i++;
  }

  return min;
}

/**
 * \fn int SBML_simulate(Model_t *mod, pEspeces molecules, const gsl_rng * r, pTestReaction T, char **banned, int nbBanned, int nbEspeces, int ref)
 * \author Amine Ghozlane
 * \brief  Simulate one step of petri net
 * \param  mod Model of the SBML file
 * \param  molecules Struct Especes
 * \param  r Random number generator
 * \param  T Struct TestReaction gives data on reaction
 * \param  banned List of banned compound
 * \param  nbBanned Number of banned compound
 * \param  nbEspeces Number of molecules
 * \param  ref Number reference of one molecule
 * \return Condition of stop/pursue
 */
int SBML_simulate(Model_t *mod, pEspeces molecules, const gsl_rng * r, pTestReaction T, int nbEspeces, int ref)
{
  Reaction_t *react = NULL;
  int minStep = 0, valid = 0/*, i=0*/;
  /*int nbReactions = Especes_getNbreactions(molecules, ref);*/
  int nbReactions = molecules[ref].nbReactions;

  /*printf("nbreactions : %d, ref : %d\n", nbReactions, ref);
	 printf("molecules : %s\n", molecules[ref].id);*/

  /* Probleme ATP, ADP, NADH, NAD+ */
  if (!strcmp(molecules[ref].id, "ADP") || !strcmp(molecules[ref].id, "ATP")
      || !strcmp(molecules[ref].id, "NADH") || !strcmp(molecules[ref].id,
          "NADplus") || !strcmp(molecules[ref].id, "NADPH") || !strcmp(
              molecules[ref].id, "NADPplus")|| !strcmp(molecules[ref].id, "NADplus_g")||
              !strcmp(molecules[ref].id, "NADH_g")|| !strcmp(molecules[ref].id, "ADP_g")||
              !strcmp(molecules[ref].id, "ATP_g")|| !strcmp(molecules[ref].id, "ADP_c")|| !strcmp(molecules[ref].id, "ATP_c")) {
      /*printf("END : molecules : %s\n", molecules[ref].id);*/
      return END;
  }

  /* Elimine les calculs sur ADP... */
  /*for(j=0;j<BANNI;j++){
	 if(!strcmp(tab[i],molecules[ref].id)){
	 printf("test : tab: %s molecules : %s\n",tab[i],molecules[ref].id );
	 return END;
	 }
	 }*/

  /* Variation selon le cas. */
  switch (nbReactions) {
  /* Cas ou aucune reaction n'est possible */
  case 0:
    /*printf("Cas N°1\n");*/
    return END;
    break;
    /* Cas ou une seule reaction est possible */
  case 1:
    /*printf("Cas N°2\n");*/
    react = molecules[ref].system->link;
    if ((minStep = SBML_checkQuantite(mod, react, nbEspeces, molecules))<= END)
      return END;
    /*printf("minstep = %d\n", minStep);*/

    while (minStep > 0) {
        SBML_reaction(mod, molecules, react, nbEspeces);
        minStep--;
    }
    break;
    /* Cas ou  plusieurs reactions sont possibles*/
  default:
    /*printf("Cas N°3\n");*/
    /* Allocation de la memoire au tableau des reactions */
    SBML_allocTest(T, nbReactions);
    valid = SBML_EstimationReaction(mod, T, molecules, ref, nbEspeces);
    if (valid <= END) {
        /*printf("on ne fait rien\n");*/
        return END;
    }
    while (Especes_getQuantite(molecules, ref) > 0.0 && valid > END) {
        react = SBML_reactChoice(molecules, r, ref);
        /*printf("molecule : %s, reaction %s\n", molecules[ref].id, Reaction_getId(react));*/
        /*printf("reaction : %s\n", Reaction_getId(react));*/
        /*printf("valid : %d, i : %d\n", valid, i);*/
        SBML_reaction(mod, molecules, react, nbEspeces);
        /*if(i>=valid) valid=SBML_EstimationReaction(mod, T, molecules, ref, nbEspeces);*/
        /*i++;*/
        valid--;
    }
    /*printf("Fin de simulation\n");*/

    /* Liberation de la memoire allouee au tableau des reactions */
    SBML_freeTest(T);
    break;
  }
  /*printf("On quitte la simulation\n");*/
  return PURSUE;
}

/**
 * \fn void SBML_compute_simulation(pScore result, Model_t *mod, double *reactions_ratio, gsl_rng * r, char **banned, int nbBanned)
 * \author Amine Ghozlane
 * \brief  Simulation of metabolic network
 * \param  result Struct Score
 * \param  mod Model of the SBML file
 * \param  reactions_ratio List of computed reaction ratio
 * \param  r Random number generator
 * \param  banned List of banned compound
 * \param  nbBanned Number of banned compound
 */
void compute_simulation(Model_t *mod)
{
  int i, nbReactions = 0, nbEspeces = 0, temp = 1, tempo = 0;
  pEspeces molecules;
  const gsl_rng_type *T;
  gsl_rng * r;
  pTestReaction TR = NULL;

  /* Verifications */
  /*TODO numGlobalParameters=Model_getNumParameters(mod);
	checkBoundaryConditions(mod);
	if(numGlobalParameters>0){
	    globalParVec=gsl_vector_alloc(numGlobalParameters);
	    setGlobalParVec();
	  }*/
  /* Allocation memoire et initialisation des generateurs de nombre aleatoire */
  gsl_rng_env_setup();
  if (!getenv("GSL_RNG_SEED"))
    gsl_rng_default_seed = time(NULL);
  T = gsl_rng_default;
  r = gsl_rng_alloc(T);
  gsl_rng_default_seed = gsl_rng_uniform(r);

  /* Allocation memoire */
  TR = (pTestReaction) malloc(1 * sizeof(TestReaction));
  if (T == NULL)
    exit(EXIT_FAILURE);

  /* File information */
  nbReactions = Model_getNumReactions(mod);
  nbEspeces = Model_getNumSpecies(mod);
  /*printf("Nom du model : %s\n",Model_getId(mod));*/
  molecules = Especes_alloc(nbEspeces);
  /* Initialisation de la quantite des especes */
  SBML_initEspeceAmounts(mod, molecules, nbEspeces);
  /* Initialisation des reactions et des ratios*/
  SBML_setReactions(mod, molecules, nbReactions, nbEspeces);

  /*TODO checkRatio(mod,molecules);*/

  /* Test des donnees enregistrees */
  printf("\nEtat des especes :\n\n");
  Especes_print(molecules, nbEspeces);

  printf("\nDebut de simulation ...\n");
  /* Simulation des reactions */
  while (temp > END) {
      temp = 0;
      for (i = 0; i < nbEspeces; i++) {
          tempo = SBML_simulate(mod, molecules, r, TR, nbEspeces, i);
          /*if (tempo != END) {
              Especes_print_2(molecules, nbEspeces);
				 printf("\n");
          }*/
          temp += tempo;
      }
  }
  printf("Fin de simulation...\n");
  printf("\nEtat des especes :\n");
  Especes_print_2(molecules, nbEspeces);
  /* Liberation de la memoire des generateurs aleatoire */
  gsl_rng_free(r);

  /* Liberation de la memoire de la structure Especes */
  Especes_free(molecules, nbEspeces);
  if (TR != NULL)
    free(TR);
}

/**
 * \fn void SBML_score_add(pScore result, pScore result_temp, FILE *debugFile)
 * \author Amine Ghozlane
 * \brief  Add scores
 * \param  result Struct Score used for all the simulation
 * \param  result_temp Struct Score used at each simulation step
 * \param  debugFile File use for debug
 */
void SBML_score_add(pScore result, pScore result_temp, int nbEspeces)
{
  /* Addition des scores */
  int i;

  /* Copie des resultats */
  for(i=0;i<nbEspeces;i++){
      if(result->name[i]==NULL){
          result->name[i]=(char*)malloc(((int)strlen(result_temp->name[i])+1)*sizeof(char));
          assert(result->name[i]!=NULL);
          strcpy(result->name[i], result_temp->name[i]);
      }
      result->quantite[i]+=result_temp->quantite[i];
  }
}

/**
 * \fn void SBML_score_mean(pScore result, int n)
 * \author Amine Ghozlane
 * \brief  Mean quantities for score
 * \param  result Struct Score
 * \param  n Number of simulation step
 */
void SBML_score_mean(pScore result, int nbEspeces, int n)
{
  /* Moyenne des resultats */
  int i;
  for(i=0;i<nbEspeces;i++){
      result->quantite[i]/=n;
  }
}

/**
 * \fn pScore SBML_scoreAlloc(pListParameters a)
 * \author Amine Ghozlane
 * \brief  Allocation of the struct Score
 * \param  a Global parameters : struct ListParameters
 * \return Allocated struct Score
 */
pScore SBML_scoreAlloc(int nbEspeces)
{
  int i;
  /* Allocation de la structure de score */
  pScore result=NULL;
  result=(pScore)malloc(1*sizeof(Score));
  assert(result!=NULL);

  result->name=NULL;
  result->quantite=NULL;
  result->name=(char**)malloc(nbEspeces*sizeof(char*));
  assert(result->name!=NULL);
  result->quantite=(double*)malloc(nbEspeces*sizeof(double));
  assert(result->quantite!=NULL);
  for(i=0;i<nbEspeces;i++){
      result->name[i]=NULL;
      result->quantite[i]=0.0;
  }
  return result;
}

/**
 * \fn void SBML_scoreFree(pScore out)
 * \author Amine Ghozlane
 * \brief  Free the struct Score
 * \param  out Struct score
 */
void SBML_scoreFree(pScore out, int nbEspeces)
{
  int i;
  /* Libere la memoire alloue a la structure de score */
  for(i=0;i<nbEspeces;i++){
      if(out->name[i]!=NULL) free(out->name[i]);
  }
  if(out->name!=NULL) free(out->name);
  if(out->quantite!=NULL) free(out->quantite);
  if(out!=NULL) free(out);
}

/* Affiche le score moyen */

/**
 * \fn void SBML_scoreFree(pScore out)
 * \author Amine Ghozlane
 * \brief  Free the struct Score
 * \param  out Struct score
 * \param  nbEspeces
 */
void SBML_score_print(pScore result, int nbEspeces)
{
  int i;
  /* Print head*/
  /*for(i=0;i<nbEspeces;i++){
      printf("%s;",result->name[i]);
  }
  printf("\n");*/
  /* Print Value */
  for(i=0;i<nbEspeces;i++){
      /*printf("Molecule: %s - Biomass: %.0f\n",result->name[i],result->quantite[i]);*/
      printf("%.0f;",result->quantite[i]);
  }
  printf("\n");
}

/**
 * \fn void SBML_compute_simulation_mean(Model_t *mod, int nb_simulation)
 * \author Amine Ghozlane
 * \brief  X time simulation of metabolic network
 * \param  mod Model of the SBML file
 * \param  nb_simulation Number of simulation step
 */
void SBML_compute_simulation_mean(Model_t *mod, int nb_simulation)
{
  /* Simulation du reseau metabolique  */
  int i, j,nbReactions = 0, nbEspeces = 0, temp = 1, tempo=0, test=0;
  pEspeces molecules=NULL;
  pTestReaction TR=NULL;
  const gsl_rng_type *T;
  gsl_rng * r;
  pScore result=NULL,result_temp=NULL;

  /* Allocation memoire et initialisation des generateurs de nombre aleatoire */
  gsl_rng_env_setup();
  if (!getenv("GSL_RNG_SEED"))
    gsl_rng_default_seed = time(NULL)+(double)getpid();
  T = gsl_rng_default;
  r = gsl_rng_alloc(T);
  /*gsl_rng_default_seed = time(NULL)+(double)getpid();*/

  /* Allocation memoire */
  TR=(pTestReaction)malloc(1*sizeof(TestReaction));
  assert(TR!=NULL);

  /* File information */
  nbReactions = (int)Model_getNumReactions(mod);
  nbEspeces = (int)Model_getNumSpecies(mod);
  molecules = Especes_alloc(nbEspeces);

  /* Initialisation de la structure de score temporaire */
  result=SBML_scoreAlloc(nbEspeces);
  result_temp=SBML_scoreAlloc(nbEspeces);

  /* Initialisation de la quantite des especes */
  SBML_initEspeceAmounts(mod, molecules, nbEspeces);
  /* Initialisation des reactions et des ratios*/
  SBML_setReactions(mod, molecules, nbReactions, nbEspeces);

  /*printf("Start the simulation\n");*/
  /* SIMULATION */
  for(j=0;j<nb_simulation;j++){
      /* Simulation des reactions */
      while (temp > END) {
          temp = 0;
          for (i = 0; i < nbEspeces; i++) {
              tempo = SBML_simulate(mod, molecules, r, TR, nbEspeces, i);
              temp +=tempo;
          }
          test+=1;
      }
      temp=1;
      tempo=0;
      /*printf("Nombre de tour %d\n",test);*/
      /*Score */
      /* Enregistre le score des especes */
      Especes_scoreSpecies(molecules, nbEspeces, result_temp->name, result_temp->quantite);
      SBML_score_add(result,result_temp,nbEspeces);
      SBML_initEspeceAmounts(mod, molecules, nbEspeces);
  }
  SBML_score_mean(result,nbEspeces,nb_simulation);

  /*printf("End of the simulation...\n");
  printf("Final state of the molecules :\n");*/
  SBML_score_print(result, nbEspeces);

  /* Liberation de la memoire des generateurs aleatoire */
  gsl_rng_free(r);

  /* Liberation de la memoire du score */
  SBML_scoreFree(result, nbEspeces);
  SBML_scoreFree(result_temp, nbEspeces);

  /* Liberation de la memoire de la structure Especes */
  Especes_free(molecules, nbEspeces);
  if(TR!=NULL) free(TR);
}


/**
 * @file   libhades.c
 * @author Michael Hartmann <michael.hartmann@physik.uni-augsburg.de>
 * @date   February, 2016
 * @brief  library to access low-level LAPACK functions
 */

#include <cblas.h>
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <strings.h>

#include <libhades.h>
#include <libhades/parse_npy_dict.h>

/** \defgroup misc miscellaneous functions
 *  @{
 */

/** @brief malloc wrapper
 *
 * This function uses malloc to allocate size bytes of memory and returns a
 * pointer to the memory. If an error occures, an error message is printed to
 * stderr and the program is aborted.
 *
 * @param [in] size amount of memory to allocate
 * @retval ptr pointer to the allocated memory
 */
void *libhades_malloc(size_t size)
{
    void *ptr = malloc(size);
    if(ptr == NULL)
    {
        const int err = errno;
        fprintf(stderr, "malloc can't allocate %zu bytes of memory: %s (%d)\n", size, strerror(err), err);
        abort();
    }
    return ptr;
}

/** @brief free wrapper
 *
 * This function frees the memory allocated by \ref libhades_malloc or \ref
 * libhades_realloc (or malloc and realloc). If the pointer given is NULL, an error is
 * printed to stderr and the program is aborted.
 *
 * @param [in] ptr pointer to the memory that should be freed
 */
void libhades_free(void *ptr)
{
    if(ptr == NULL)
    {
        fprintf(stderr, "Trying to free NULL pointer\n");
        abort();
    }

    free(ptr);
}

/** @brief realloc wrapper
 *
 * This function changes the size of the memory block pointed to by ptr to size
 * bytes. If ptr is NULL, this function behaves like \ref libhades_malloc. If
 * an error occures, an error is printed to stderr and the program is aboprted.
 *
 * @param [in] ptr pointer to the memory block
 * @param [in] size size of the memory block
 * @retval ptr_new pointer to the new memory block
 */
void *libhades_realloc(void *ptr, size_t size)
{
    void *ptr_new = realloc(ptr, size);

    if(ptr_new == NULL)
    {
        int err = errno;
        fprintf(stderr, "realloc can't allocate %zu bytes of memory: %s (%d)\n", size, strerror(err), err);
        abort();
    }

    return ptr_new;
}

static void *(*malloc_cb)(size_t)          = &libhades_malloc;
static void *(*realloc_cb)(void *, size_t) = &libhades_realloc;
static void  (*free_cb)(void *)            = &libhades_free;

/** macro to create functions argmin, argmax, argabsmin, argabsmax */
#define ARGXXX(FUNCTION_NAME, FUNCTION, RELATION) \
size_t FUNCTION_NAME(double list[], size_t size) \
{ \
    size_t index = 0; \
    for(size_t i = 0; i < size; i++) \
        if(FUNCTION(list[i]) RELATION FUNCTION(list[index])) \
            index = i; \
    return index; \
}

/** @brief Return index of smallest element in list
 *
 * @param [in] list
 * @param [in] size elements in list
 *
 * @retval index
 */
ARGXXX(argmin, +, <)

/** @brief Return index of largest element in list
 *
 * @param [in] list
 * @param [in] size elements in list
 *
 * @retval index
 */
ARGXXX(argmax, +, >)

/** @brief Return index of element with smallest absolute value in list
 *
 * @param [in] list
 * @param [in] size elements in list
 *
 * @retval index
 */
ARGXXX(argabsmin, fabs, <)
/** @brief Return index of element with largest absolute value in list
 *
 * @param [in] list
 * @param [in] size elements in list
 *
 * @retval index
 */
ARGXXX(argabsmax, fabs, >)

/** @}*/

/** \defgroup create Creating, printing and freeing matrices
 *  @{
 */

static inline void _swap_int(int *a, int *b);
static inline void _swap_size_t(size_t *a, size_t *b);

static inline void _swap_int(int *a, int *b)
{
    int c = *a;
    *a = *b;
    *b = c;
}

static inline void _swap_size_t(size_t *a, size_t *b)
{
    size_t c = *a;
    *a = *b;
    *b = c;
}

#define MATRIX_SWAP(FUNCTION_NAME, MATRIX_TYPE, TYPE) \
void FUNCTION_NAME(MATRIX_TYPE *A, MATRIX_TYPE *B) \
{ \
    /* swap pointers */ \
    TYPE *ptr = A->M; \
    A->M = B->M; \
    B->M = ptr; \
\
    /* swap rows */ \
    _swap_int(&A->rows, &B->rows); \
\
    /* swap columns */ \
    _swap_int(&A->columns, &B->columns); \
\
    /* swap min */ \
    _swap_int(&A->min, &B->min); \
\
    /* swap size */ \
    _swap_size_t(&A->size, &B->size); \
\
    /* swap type */ \
    _swap_int(&A->type, &B->type); \
\
    /* swap min */ \
    _swap_int(&A->min, &B->min); \
}

/** @brief Swap matrices A and B
 *
 * This function swaps the matrices A and B. The former content of A will be
 * the content of B and vice versa. No data is copied or moved, but the
 * pointers are swapped.
 *
 * @param [in,out] A matrix A
 * @param [in,out] B matrix B
 */
MATRIX_SWAP(matrix_swap, matrix_t, double);

/** @brief Swap matrices A and B
 *
 * See \ref matrix_swap.
 *
 * @param [in,out] A matrix A
 * @param [in,out] B matrix B
 */
MATRIX_SWAP(matrix_complex_swap, matrix_complex_t, complex_t);

/** macro to create a diagnal matrix out of a vector */
#define MATRIX_DIAG(FUNCTION_NAME, MATRIX_TYPE, ZEROS) \
MATRIX_TYPE *FUNCTION_NAME(MATRIX_TYPE *v) \
{ \
    int dim = v->size; \
    MATRIX_TYPE *A = ZEROS(dim, dim, NULL); \
    if(A == NULL) \
        return NULL; \
\
    for(int i = 0; i < dim; i++) \
        matrix_set(A, i,i, v->M[i]); \
\
    return A; \
}

/** @brief Construct a real diagonal matrix from a row or columns vector
 *
 * @param [in] v row or column vector
 *
 * @retval A A = diag(v) if successfull, NULL otherwise
 */
MATRIX_DIAG(matrix_diag, matrix_t, matrix_zeros)

/** @brief Construct a complex diagonal matrix from a row or columns vector
 *
 * @param [in] v row or column vector
 *
 * @retval A A = diag(v) if successfull, NULL otherwise
 */
MATRIX_DIAG(matrix_complex_diag, matrix_complex_t, matrix_complex_zeros)

/** @brief Set functions to allocate and free memory
 *
 * By default wrappers to malloc and free from <stdlib.h> are used to allocate
 * and free memory. If allocation of memory fails or a NULL pointer is freed,
 * the program will terminate.
 *
 * @param [in] _malloc_cb callback to a malloc-alike function
 * @param [in] _free_cb callback to a free-alike function
 */
void matrix_set_alloc(void *(*_malloc_cb)(size_t), void  (*_free_cb)(void *))
{
    malloc_cb = _malloc_cb;
    free_cb   = _free_cb;
}

/** macro for copying matrices. */
#define MATRIX_COPY(FUNCTION_NAME, MTYPE, TYPE, ALLOC) \
MTYPE *FUNCTION_NAME(MTYPE *A, MTYPE *C) \
{ \
    if(C == NULL) { \
        C = ALLOC(A->rows, A->columns); \
        if(C == NULL) \
            return NULL; \
    } \
\
    C->rows    = A->rows; \
    C->columns = A->columns; \
    C->min     = A->min; \
    C->size    = A->size; \
    C->type    = A->type; \
    C->view    = 0; \
    memcpy(C->M, A->M, C->size*sizeof(TYPE)); \
    return C; \
}

/** @brief Copy real matrix A
 *
 * Copy matrix A into C. If C is NULL, space for the matrix C will be
 * allocated.
 *
 * @param [in]     A real matrix
 * @param [in,out] C real matrix
 *
 * @retval C copy of A
 */
MATRIX_COPY(matrix_copy, matrix_t, double, matrix_alloc)

/** @brief Copy complex matrix A
 *
 * Copy matrix A into C. If C is NULL, space for the matrix C will be
 * allocated.
 *
 * @param [in]     A complex matrix
 * @param [in,out] C complex matrix
 *
 * @retval C copy of A
 */
MATRIX_COPY(matrix_complex_copy, matrix_complex_t, complex_t, matrix_complex_alloc)

/** @brief Copy a real matrix A to a complex matrix C
 *
 * Copy the matrix A to a complex matrix C. The matrix elements of A and C will
 * be identical. If C is NULL, memory for the matrix will be allocated.
 *
 * @param [in] A real matrix
 *
 * @retval C copy of A if successfull, NULL otherwise
 */
matrix_complex_t *matrix_tocomplex(matrix_t *A, matrix_complex_t *C)
{
    if(C == NULL)
    {
        C = matrix_complex_alloc(A->rows, A->columns);
        if(C == NULL)
            return NULL;
    }

    for(size_t i = 0; i < C->size; i++)
        C->M[i] = A->M[i];

    return C;
}

/** @brief Print real matrix M to stream
 *
 * Print the matrix A to the stream given by stream, e.g. stdout or stderr.
 * The format is given by the format string format, the separator of two
 * columns is sep, the separator between lines is given by sep_line. Both sep
 * and sep_line may be NULL.
 *
 * @param [in] stream output stream
 * @param [in] A real matrix
 * @param [in] format output format, e.g. "%lf" or "%g"
 * @param [in] sep separator between columns, e.g. "\t"
 * @param [in] sep_line separator between lines, e.g. "\n"
 */
void matrix_fprintf(FILE *stream, matrix_t *A, const char *format, const char *sep, const char *sep_line)
{
    const int rows = A->rows, columns = A->columns;

    for(int i = 0; i < rows; i++)
    {
        for(int j = 0; j < columns; j++)
        {
            fprintf(stream, format, matrix_get(A, i,j));
            if(sep != NULL)
                fputs(sep, stream);
        }
        if(sep_line != NULL)
            fputs(sep_line, stream);
    }
}

/** @brief Print complex matrix A to stream
 *
 * See matrix_fprintf.
 *
 * @param [in] stream output stream
 * @param [in] A complex matrix
 * @param [in] format output format, e.g. "%+lf%+lfi"
 * @param [in] sep separator between columns, e.g. "\t"
 * @param [in] sep_line separator between lines, e.g. "\n"
 */
void matrix_complex_fprintf(FILE *stream, matrix_complex_t *A, const char *format, const char *sep, const char *sep_line)
{
    const int rows = A->rows, columns = A->columns;

    for(int i = 0; i < rows; i++)
    {
        for(int j = 0; j < columns; j++)
        {
            const complex_t c = matrix_get(A, i,j);
            fprintf(stream, format, CREAL(c), CIMAG(c));
            if(sep != NULL)
                fputs(sep, stream);
        }
        if(sep_line != NULL)
            fputs(sep_line, stream);
    }
}

/** macro for matrix allocations. */
#define MATRIX_ALLOC(FUNCTION_NAME, MTYPE, TYPE) \
MTYPE *FUNCTION_NAME(int rows, int columns) \
{ \
    if(rows <= 0 || columns <= 0) \
        return NULL; \
\
    MTYPE *A = malloc_cb(sizeof(MTYPE)); \
    if(A == NULL) \
        return NULL; \
\
    const size_t size = (size_t)rows*(size_t)columns; \
\
    A->rows    = rows; \
    A->columns = columns; \
    A->min     = MIN(rows, columns); \
    A->size    = size; \
    A->type    = 0; \
    A->view    = 0; \
    A->M       = malloc_cb(size*sizeof(TYPE)); \
    if(A->M == NULL) \
    { \
        free_cb(A); \
        return NULL; \
    } \
\
    return A; \
}

/** @brief Allocate memory for real matrix of m rows and n columns
 *
 * This function will allocate and return a matrix of m lines and n columns.
 * The function given by matrix_set_alloc will be used to allocate memory; by
 * default this is malloc from <stdlib.h>.
 *
 * The matrix elements will be undefined. To allocate a matrix initialized with
 * zeros, see matrix_zeros. To create a unity matrix, see matrix_eye.
 *
 * @param [in] rows rows of matrix M (rows > 0)
 * @param [in] columns columns of matrix M (columns > 0)
 *
 * @retval A real matrix if successful, NULL otherwise
 */
MATRIX_ALLOC(matrix_alloc, matrix_t, double)

/** @brief Allocate memory for complex matrix of m rows and n columns
 *
 * See matrix_alloc.
 *
 * @param [in] rows rows of matrix M (rows > 0)
 * @param [in] columns columns of matrix M (columns > 0)
 *
 * @retval A complex matrix if successful, otherwise NULL
 */
MATRIX_ALLOC(matrix_complex_alloc, matrix_complex_t, complex_t)

/** macro to create zero matrix */
#define MATRIX_ZEROS(FUNCTION_NAME, MTYPE, TYPE, ALLOC, SETALL) \
MTYPE *FUNCTION_NAME(int rows, int columns, MTYPE *A) \
{ \
    if(A == NULL) \
    { \
        A = ALLOC(rows,columns); \
        if(A == NULL) \
            return NULL; \
    } \
\
    SETALL(A, 0); \
\
    return A; \
}

/** @brief Generate a real zero matrix of m lines and n columns
 *
 * Set every element of matrix A to 0. If A is NULL, the matrix will be
 * created. In this case, you have to free the matrix yourself.
 *
 * @param [in] rows rows of matrix M
 * @param [in] columns columns of matrix M
 * @param [in,out] A: matrix
 *
 * @retval A real matrix 0 if successful, otherwise NULL
 */
MATRIX_ZEROS(matrix_zeros, matrix_t, double, matrix_alloc, matrix_setall)

/** @brief Generate a complex zero matrix of m lines and n columns
 *
 * See matrix_zeros.
 *
 * @param [in] rows rows of matrix M
 * @param [in] columns columns of matrix M
 * @param [in,out] A: matrix
 *
 * @retval A complex matrix 0 if successful, otherwise NULL
 */
MATRIX_ZEROS(matrix_complex_zeros, matrix_complex_t, complex_t, matrix_complex_alloc, matrix_complex_setall)

/** macro to create matrix x*Id */
#define MATRIX_SETALL(FUNCTION_NAME, MATRIX_TYPE, TYPE) \
void FUNCTION_NAME(MATRIX_TYPE *A, TYPE x) \
{ \
    const size_t size = A->size; \
    TYPE *M = A->M; \
    for(size_t i = 0; i < size; i++) \
        *M++ = x; \
}

/** @brief Set matrix elements of A to x
 *
 * @param [in,out] A real matrix
 * @param [in] x real number
 */
MATRIX_SETALL(matrix_setall, matrix_t, double)

/** @brief Set matrix elements of A to x
 *
 * @param [in,out] A complex matrix
 * @param [in] x complex number
 */
MATRIX_SETALL(matrix_complex_setall, matrix_complex_t, complex_t)

/** macro to create unity matrix */
#define MATRIX_EYE(FUNCTION_NAME, MTYPE, TYPE, ALLOC, SETALL) \
MTYPE *FUNCTION_NAME(int dim, MTYPE *A) \
{ \
    if(A == NULL) \
    { \
        A = ALLOC(dim,dim); \
        if(A == NULL) \
            return NULL; \
    } \
 \
    const int min = A->min; \
    TYPE *M = A->M; \
    SETALL(A,0); \
    for(int i = 0; i < min; i++) \
        *(M+i*(min+1)) = 1; \
 \
    return A; \
}

/** @brief Create real identity matrix
 *
 * If A == NULL, a identity matrix of dimension dim times dim is created and
 * returned.
 * If A != NULL, the matrix A is set to the identity matrix and the parameter
 * dim is ignored. More specific, for a general (e.g. not square) matrix A, the
 * matrix elements are set to A_ij = Delta_ij.
 *
 * @param [in]     dim dimension of identity matrix (ignored for A == NULL)
 * @param [in,out] A real matrix
 *
 * @retval A identity matrix if successful, NULL otherwise
 */
MATRIX_EYE(matrix_eye, matrix_t, double, matrix_alloc, matrix_setall)

/** @brief Create complex identity matrix
 *
 * See matrix_eye.
 *
 * @param [in]     dim dimension of identity matrix (ignored for A == NULL)
 * @param [in,out] A complex matrix
 *
 * @retval A identity matrix if successful, NULL otherwise
 */
MATRIX_EYE(matrix_complex_eye, matrix_complex_t, complex_t, matrix_complex_alloc, matrix_complex_setall)

/** macro to free matrices */
#define MATRIX_FREE(FUNCTION_NAME, MTYPE) \
void FUNCTION_NAME(MTYPE *A) \
{ \
    if(A != NULL) \
    { \
        if(!A->view && A->M != NULL) \
        { \
            free_cb(A->M); \
            A->M = NULL; \
        } \
        free_cb(A); \
    } \
}

/** @brief Free real matrix
 *
 * This function will free the memory allocated for matrix M. If M is NULL this
 * function will do nothing.
 *
 * @param [in,out] A matrix to free
 */
MATRIX_FREE(matrix_free, matrix_t)

/** @brief Free complex matrix
 *
 * See matrix_free.
 *
 * @param [in,out] A matrix to free
 */
MATRIX_FREE(matrix_complex_free, matrix_complex_t)

/** @}*/

/** \defgroup trdet trace and determinant
 *  @{
 */

/** macro to calculate trace of matrix */
#define MATRIX_TRACE(FUNCTION_NAME, MATRIX_TYPE, TYPE) \
TYPE FUNCTION_NAME(MATRIX_TYPE *A) \
{ \
    const int min = A->min; \
    const TYPE *M = A->M; \
    TYPE trace = 0; \
    for(int i = 0; i < min; i++) \
        trace += *(M+i*(1+min)); \
\
    return trace; \
}

/** @brief Calculate Tr(A) of real matrix A
 *
 * @param [in] A complex matrix
 *
 * @retval x with x=Tr(A)
 */
MATRIX_TRACE(matrix_trace, matrix_t, double)

/** @brief Calculate Tr(A) of complex matrix A
 *
 * @param [in] A complex matrix
 *
 * @retval z with z=Tr(A)
 */
MATRIX_TRACE(matrix_complex_trace, matrix_complex_t, complex_t)

/** @brief Calculate Tr(A*B)
 *
 * This will calculate the trace of A*B: Tr(A*B). Matrix A and B must be square
 * matrices of dimension dim, dim.
 *
 * A and B must not point to the same matrix or the behaviour will be
 * undefined!
 *
 * @param [in] A real matrix
 * @param [in] B real matrix
 *
 * @retval x with x=Tr(A*B)
 */
double matrix_trace_AB(matrix_t *A, matrix_t *B)
{
    const int dim = A->min;
    double sum = 0;
    double *M1 = A->M;
    double *M2 = B->M;

    for(int i = 0; i < dim; i++)
        sum += cblas_ddot(dim, M1+i, dim, M2+i*dim, 1);

    return sum;
}

/** @brief Calculate Tr(A*B) for A,B complex
 *
 * See matrix_trace_AB.
 *
 * @param [in] A complex matrix
 * @param [in] B complex matrix
 *
 * @retval z with z=Tr(A*B)
 */
complex_t matrix_trace_complex_AB(matrix_complex_t *A, matrix_complex_t *B)
{
    const int dim = A->min;
    complex_t sum = 0;

    for(int i = 0; i < dim; i++)
        for(int k = 0; k < dim; k++)
            sum += matrix_get(A, i,k)*matrix_get(B, k,i);

    return sum;
}

/** @brief Calculate Re(Tr(A*B)) for A,B complex
 *
 * See matrix_trace_AB.
 *
 * @param [in] A complex matrix
 * @param [in] B complex matrix
 *
 * @retval x with x=Re(Tr(A*B))
 */
double matrix_trace_complex_AB_real(matrix_complex_t *A, matrix_complex_t *B)
{
    const int dim = A->rows;
    double sum = 0;
    const complex_t *M1 = A->M;
    const complex_t *M2 = B->M;

    for(int i = 0; i < dim; i++)
        for(int k = 0; k < dim; k++)
            sum += CREAL(M1[i*dim+k]*M2[k*dim+i]);

    return sum;
}

/** @}*/

/** \defgroup kron Kronecker product
 *  @{
 */

/** macro to create Kronecker product */
#define MATRIX_KRON(FUNCTION_NAME, MTYPE, TYPE, ALLOC, SETALL) \
MTYPE *FUNCTION_NAME(MTYPE *A, MTYPE *B, MTYPE *C) \
{ \
    const int Am = A->rows, An = A->columns; \
    const int Bm = B->rows, Bn = B->columns; \
    if(C == NULL) \
    { \
        C = ALLOC(Am*Bm, An*Bn); \
        if(C == NULL) \
            return NULL; \
    } \
    SETALL(C, 0); \
\
    for(int m = 0; m < Am; m++) \
        for(int n = 0; n < An; n++) \
        { \
            const TYPE c = matrix_get(A,m,n); \
            if(c != 0) \
            { \
                for(int im = 0; im < Bm; im++) \
                    for(int in = 0; in < Bn; in++) \
                        matrix_set(C, m*Bm+im, n*Bn+in, c*matrix_get(B,im,in)); \
            } \
        } \
\
    return C; \
}

/** @brief Calculate Kronecker product of real matrices A and B
 *
 * Matrix A has dimension Am,An, matrix B has dimension Bm,Bn.
 *
 * If C is not NULL, the Kronecker product will be stored in C. C must have
 * dimension Am+Bm,An+Bn. If C is NULL, memory for the matrix will be allocated
 * and the matrix will be returned. You have to free the memory for the
 * returned matrix yourself.
 *
 * @param [in] A real matrix
 * @param [in] B real matrix
 * @param [in,out] C Kronecker product
 *
 * @retval C Kronecker product of A and B, NULL if no memory could be allocated
 */
MATRIX_KRON(matrix_kron, matrix_t, double, matrix_alloc, matrix_setall)

/** @brief Calculate Kronecker product of complex matrices A and B
 *
 * See matrix_kron.
 *
 * @param [in] A complex matrix
 * @param [in] B complex matrix
 * @param [in,out] C Kronecker product
 *
 * @retval C Kronecker product of A and B
 */
MATRIX_KRON(matrix_complex_kron, matrix_complex_t, complex_t, matrix_complex_alloc, matrix_complex_setall)

/** @}*/

/** \defgroup addsubmult Add, subtract and multiply matrices
 *  @{
 */

/** macro to multiply matrix with scalar factor */
#define MATRIX_MULT_SCALAR(FUNCTION_NAME, MTYPE, TYPE) \
void FUNCTION_NAME(MTYPE *A, TYPE alpha) \
{ \
    const size_t size = A->size; \
    TYPE *M = A->M; \
    for(size_t i = 0; i < size; i++) \
        M[i] *= alpha; \
}

/** @brief Multiply real matrix with a real scalar
 *
 * alpha*A -> A
 *
 * @param [in,out] A real matrix
 * @param [in] alpha real scalar
 * @retval A, A=alpha*A
 */
MATRIX_MULT_SCALAR(matrix_mult_scalar, matrix_t, double)

/** @brief Multiply complex matrix with a complex scalar
 *
 * alpha*A -> A
 *
 * @param [in,out] A complex matrix
 * @param [in] alpha complex scalar
 * @retval A, A=alpha*A
 */
MATRIX_MULT_SCALAR(matrix_complex_mult_scalar, matrix_complex_t, complex_t)

/** @brief Multiply real matrix with a complex scalar
 *
 * alpha*A -> C
 *
 * If C is NULL, memory for the matrix C will be allocated. If C is not NULL, C
 * must have the correct dimension.
 *
 * @param [in] A real matrix
 * @param [in] alpha complex scalar
 * @param [in,out] C complex matrix
 *
 * @retval C, C = alpha*A
 */
matrix_complex_t *matrix_mult_complex_scalar(matrix_t *A, complex_t alpha, matrix_complex_t *C)
{
    const int rows    = A->rows;
    const int columns = A->columns;
    const double *AM  = A->M;
    complex_t *CM;
    if(C == NULL)
    {
        C = matrix_complex_alloc(rows, columns);
        if(C == NULL)
            return NULL;
    }
    CM = C->M;

    for(size_t i = 0; i < C->size; i++)
        CM[i] = alpha*AM[i];

    return C;
}

/* compute alpha*A*B */
#define MATRIX_MULT(FUNCTION_NAME, MATRIX_TYPE, TYPE, ALLOC, BLAS_xGEMM, PTR) \
MATRIX_TYPE *FUNCTION_NAME(MATRIX_TYPE *A, MATRIX_TYPE *B, TYPE alpha, MATRIX_TYPE *C) \
{ \
    TYPE beta = 0; \
\
    if(A->columns != B->rows) \
        return NULL; \
\
    if(C == NULL) \
    { \
        C = ALLOC(A->rows, B->columns); \
        if(C == NULL) \
            return NULL; \
    } \
\
    /* xGEMM ( TRANSA, TRANSB, M, N, K, ALPHA, A, LDA, B, LDB, BETA, C, LDC) */ \
    BLAS_xGEMM(CblasColMajor, /* column order */ \
               CblasNoTrans,  /* don't transpose/conjugate A */ \
               CblasNoTrans,  /* don't transpose/conjugate B */ \
               A->rows,       /* M: rows of A and C */ \
               B->columns,    /* N: columns of B and C */ \
               A->columns,    /* K: columns of A and rows of B */ \
               PTR alpha,     /* alpha: scalar */ \
               A->M,          /* A: matrix A */ \
               A->rows,       /* LDA: leading dimension of A (columns) */ \
               B->M,          /* B: matrix B */ \
               A->columns,    /* LDB: leading dimension of B (columns) */ \
               PTR beta,      /* beta: scalar */ \
               C->M,          /* C: matrix C */ \
               C->rows        /* LDC: leading dimension of C (columns) */ \
    ); \
\
    return C; \
}

/** @brief Multiply real matrices
 *
 * alpha*A*B -> C
 *
 * If C is NULL, memory for the matrix C will be allocated.
 *
 * @param [in] A real matrix
 * @param [in] B real matrix
 * @param [in] alpha real scalar
 * @param [in,out] C real matrix
 *
 * @retval C, C = alpha*A*B
 */
MATRIX_MULT(matrix_mult, matrix_t, double, matrix_alloc, cblas_dgemm, +)

/** @brief Multiply complex matrices
 *
 * alpha*A*B -> C
 *
 * If C is NULL, memory for the matrix C will be allocated.
 *
 * @param [in] A complex matrix
 * @param [in] B complex matrix
 * @param [in] alpha complex scalar
 * @param [in,out] C complex matrix
 *
 * @retval C, C = alpha*A*B
 */
MATRIX_MULT(matrix_complex_mult, matrix_complex_t, complex_t, matrix_complex_alloc, cblas_zgemm, &)

/** macro to add two matrices */
#define MATRIX_ADD(FUNCTION_NAME, TYPE1, MTYPE1, TYPE2, MTYPE2) \
int FUNCTION_NAME(MTYPE1 *A, MTYPE2 *B, TYPE1 alpha, MTYPE1 *C) \
{ \
    const size_t size = A->size; \
    TYPE1 *M3; \
    TYPE1 *M1 = A->M; \
    TYPE2 *M2 = B->M; \
\
    if(C == NULL) \
        M3 = A->M; \
    else \
        M3 = C->M; \
\
    if(A->rows != B->rows || A->columns != B->columns) \
        return LIBHADES_ERROR_SHAPE; \
\
    for(size_t i = 0; i < size; i++) \
        M3[i] = M1[i] + (alpha*M2[i]); \
\
    return 0; \
}

/** @brief Add real matrices A and B
 *
 * Calculate A+alpha*B -> C.
 *
 * The result will be stored in C. If C is NULL, the result is stored in A.
 *
 * @param [in,out] A real matrix
 * @param [in] B real matrix
 * @param [in] alpha real scalar
 * @param [in,out] C real matrix or NULL
 *
 * @retval 0 if successfull
 * @retval LIBHADES_ERROR_SHAPE if matrices have wrong shape
 */
MATRIX_ADD(matrix_add, double, matrix_t, double, matrix_t)

/** @brief Add complex matrices A and B
 *
 * Calculate A+alpha*B -> C.
 *
 * The result will be stored in C. If C is NULL, the result is stored in A.
 *
 * @param [in,out] A complex matrix
 * @param [in] B complex matrix
 * @param [in] alpha complex number
 * @param [in,out] C complex matrix or NULL
 *
 * @retval 0 if successfull
 * @retval LIBHADES_ERROR_SHAPE if matrices have wrong shape
 */
MATRIX_ADD(matrix_complex_add, complex_t, matrix_complex_t, complex_t, matrix_complex_t)

/** @brief Add complex matrix A and real matrix B
 *
 * Calculate A+alpha*B -> C.
 *
 * The result will be stored in C. If C is NULL, the result is stored in A.
 *
 * @param [in,out] A complex matrix
 * @param [in] B real matrix
 * @param [in] alpha complex scalar
 * @param [in,out] C complex matrix or NULL
 *
 * @retval 0 if successfull
 * @retval LIBHADES_ERROR_SHAPE if matrices have wrong shape
 */
MATRIX_ADD(matrix_complex_add_real, complex_t, matrix_complex_t, double, matrix_t)

/** @}*/

/** \defgroup transconj Transpose, conjugate
 *  @{
 */

#define MATRIX_TRANSPOSE(FUNCTION_NAME, MTYPE, TYPE) \
void FUNCTION_NAME(MTYPE *A) \
{ \
    const int rows    = A->rows; \
    const int columns = A->columns; \
    for(int im = 0; im < rows; im++) \
        for(int in = im+1; in < columns; in++) \
        { \
            TYPE temp = matrix_get(A, im, in); \
            matrix_set(A, im, in, matrix_get(A, in, im)); \
            matrix_set(A, in, im, temp); \
        } \
\
    A->rows    = columns; \
    A->columns = rows; \
}

/** @brief Transpose real matrix A
 *
 * The matrix will be transposed.
 *
 * @param [in,out] A real matrix
 *
 * @retval C with C=A^T
 */
MATRIX_TRANSPOSE(matrix_transpose, matrix_t, double)

/** @brief Transpose complex matrix A
 *
 * The matrix will be transposed.
 *
 * @param [in,out] A complex matrix
 *
 * @retval C with C=A^T
 */
MATRIX_TRANSPOSE(matrix_complex_transpose, matrix_complex_t, complex_t)

/** @}*/

/** \defgroup ev Eigenvalue problems
 *  @{
 */

/** @brief Compute eigenvalues and optionally eigenvectors of symmetric matrix A
 *
 * This function computes all eigenvalues and, optionally, eigenvectors of a
 * real symmetric matrix A.
 *
 * See dsyev.
 *
 * @param [in] A real matrix
 * @param [in] JOBZ 'N': only eigenvalues, 'V' eigenvalues and eigenvectors
 * @param [in] UPLO 'U': upper triangle part of A is stored; 'L': lower triangle part of A is stored
 * @param [in] w real matrix of dimension (dim,1) (i.e. a vector); the eigenvalues will be stored in w
 *
 * @retval 0 on success
 */
int eig_sym(matrix_t *A, char *JOBZ, char *UPLO, matrix_t *w)
{
    int info, lwork = -1, N = A->min;
    double workopt;
    double *work;

    dsyev_(JOBZ, UPLO, &N, A->M, &N, w->M, &workopt, &lwork, &info);
    if(info != 0)
        return info;

    lwork = workopt;
    work = malloc_cb(lwork*sizeof(double));
    if(work == NULL)
        return LIBHADES_ERROR_OOM;

    dsyev_(JOBZ, UPLO, &N, A->M, &N, w->M, work, &lwork, &info);

    free_cb(work);

    return info;
}

/** @brief Compute eigenvalues and optionally eigenvectors of Hermitian matrix A
 *
 * This function computes all eigenvalues and, optionally, eigenvectors of a
 * Hermitian symmetric matrix A.
 *
 * See zheev.
 *
 * @param [in] A complex matrix
 * @param [in] JOBZ 'N': only eigenvalues, 'V' eigenvalues and eigenvectors
 * @param [in] UPLO 'U': upper triangle part of A is stored; 'L': lower triangle part of A is stored
 * @param [in] w real matrix of dimension (dim,1) (i.e. a vector); the eigenvalues will be stored in w
 *
 * @retval 0 on success
 */
int eig_herm(matrix_complex_t *A, char *JOBZ, char *UPLO, matrix_t *w)
{
    int info, lwork = -1, N = A->min;
    complex_t workopt;
    complex_t *work = NULL;
    double *rwork;

    rwork = malloc_cb(MAX(1, 3*N-2)*sizeof(double));
    if(rwork == NULL)
        return LIBHADES_ERROR_OOM;

    zheev_(JOBZ, UPLO, &N, A->M, &N, w->M, &workopt, &lwork, rwork, &info);
    if(info != 0)
    {
        free_cb(rwork);
        return info;
    }

    lwork = CREAL(workopt);
    work = malloc_cb(lwork*sizeof(complex_t));
    if(work == NULL)
    {
        free_cb(rwork);
        return LIBHADES_ERROR_OOM;
    }

    zheev_(JOBZ, UPLO, &N, A->M, &N, w->M, work, &lwork, rwork, &info);

    free_cb(rwork);
    free_cb(work);

    return info;
}

/** @brief Compute eigenvalues and optionally eigenvectors of matrix A
 *
 * Compute for an N-by-N complex nonsymmetric matrix A, the eigen- values and,
 * optionally, right eigenvectors
 *
 * See zgeev.
 *
 * @param [in] A real matrix
 * @param [in] w list containing the eigenvalues of A
 * @param [in] vr if vr != NULL, right eigenvectors are computed and will be stored in vr; vr must be a complex matrix of dimension (dim,dim)
 * @param [in] vl if vl != NULL, lefft eigenvectors are computed and will be stored in vl; vl must be a complex matrix of dimension (dim,dim)
 *
 * @retval 0 on success
 */

/* Parameters */
int eig_complex_generic(matrix_complex_t *A, matrix_complex_t *w, matrix_complex_t *vl, matrix_complex_t *vr)
{
    int N = A->min;
    int lwork = -1;
    char jobvr = 'N';
    char jobvl = 'N';
    int info;
    complex_t *evr  = NULL;
    complex_t *evl  = NULL;
    complex_t *work = NULL;
    double *rwork   = NULL;
    complex_t wopt;

    /* if vr is not NULL, calculate right eigenvectors */
    if(vr != NULL)
    {
        jobvr = 'V';
        evr   = vr->M;
    }
    /* if vl is not NULL, calculate left eigenvectors */
    if(vl != NULL)
    {
        jobvl = 'V';
        evl   = vl->M;
    }

    /* get the optimal size for workspace work */
    zgeev_(&jobvl, &jobvr, &N, A->M, &N, w->M, evl, &N, evr, &N, &wopt, &lwork, rwork, &info);

    if(info != 0)
        return info;

    lwork = CREAL(wopt);

    rwork = malloc_cb(2*N*sizeof(double));
    work  = malloc_cb(lwork*sizeof(complex_t));

    if(rwork == NULL || work == NULL)
    {
        if(rwork != NULL)
            free(rwork);
        if(work != NULL)
            free(work);

        return LIBHADES_ERROR_OOM;
    }

    /* SUBROUTINE ZGEEV( JOBVL, JOBVR, N, A, LDA, W, VL, LDVL, VR, LDVR, WORK, LWORK, RWORK, INFO ) */
    zgeev_(
        &jobvl,     /* left eigenvectors of A are not computed */
        &jobvr,     /* calculate/don't calculate right eigenvectors */
        &N,         /* order of matrix A */
        A->M,       /* matrix A */
        &N,         /* LDA - leading dimension of A */
        w->M,       /* eigenvalues */
        evl,        /* left eigenvectors */
        &N,         /* leading dimension of array vl */
        evr,        /* eigenvectors */
        &N,         /* leading dimension of the array VR */
        work,       /* COMPLEX*16 array, dimension (LWORK) */
        &lwork,     /* dimension of the array WORK; LWORK >= max(1,2*N) */
        rwork,      /* (workspace) DOUBLE PRECISION array, dimension (2*N) */
        &info       /* 0 == success */
    );

    free_cb(work);
    free_cb(rwork);

    return info;
}

int eig_complex_vr(matrix_complex_t *M, complex_t lambda, matrix_complex_t *vr)
{
    matrix_complex_t *A = NULL, *b = NULL;
    const int rows    = M->rows;
    const int columns = M->columns;

    /* lapack */
    char trans = 'N';
    int info, lwork;
    complex_t work_size, *work = NULL;
    int m = rows+1, n = columns, nrhs = 1;
    int lda = m, ldb = MAX(m,n);

    if((A = matrix_complex_alloc(rows+1, columns)) == NULL)
        return LIBHADES_ERROR_OOM;

    if((b = matrix_complex_zeros(rows+1,1,NULL)) == NULL)
    {
        matrix_complex_free(A);
        return LIBHADES_ERROR_OOM;
    }
    matrix_set(b, rows,0, 1);

    for(int j = 0; j < columns; j++)
    {
        for(int i = 0; i < rows; i++)
            matrix_set(A, i, j, matrix_get(M, i,j));

        /* - lambda Id */
        matrix_set(A, j,j, matrix_get(A,j,j)-lambda);

        /* set nomalization condition */
        matrix_set(A, rows,j, 1);
    }

    /* determine work size */
    lwork = -1;
    zgels_(&trans, &m, &n, &nrhs, A->M, &lda, b->M, &ldb, &work_size, &lwork, &info);
    lwork = (int)CREAL(work_size);
    if((work = malloc_cb(lwork*sizeof(complex_t))) == NULL)
    {
        matrix_complex_free(A);
        matrix_complex_free(vr);
        return LIBHADES_ERROR_OOM;
    }

    /* calculate eigenvector */
    zgels_(
        &trans, /* find least squares solution of overdetermined system */
        &m,     /* number of rows of matrix A */
        &n,     /* number of columns of matrix A */
        &nrhs,  /* number of columns of matrix B */
        A->M,   /* matrix A (will be overwritten) */
        &lda,   /* leading dimension of A, LDA >= max(1,M) */
        b->M,   /* on entry: right hand side vectors, on exit: solution */
        &ldb,   /* leading dimension of B, LDB >= MAX(1,M,N) */
        work,   /* workspace */
        &lwork, /* dimension of work */
        &info   /* status */
    );

    free_cb(work);
    matrix_complex_free(A);

    for(int i = 0; i < rows; i++)
        matrix_set(vr, i,0, matrix_get(b, i,0));

    matrix_complex_free(b);

    return info;
}

/** @}*/

/** \defgroup exp Calculate matrix exponential
 *  @{
 */

/** macro to calculate matrix norm */
#define MATRIX_NORM(FUNCTION_NAME, MTYPE, LAPACK_FUNC) \
int FUNCTION_NAME(MTYPE *A, char norm_type, double *norm) \
{ \
    double *work = NULL; \
\
    if(norm_type == 'I') \
    { \
        work = malloc_cb(A->rows*sizeof(double)); \
        if(work == NULL) \
            return LIBHADES_ERROR_OOM; \
    } \
\
    *norm = LAPACK_FUNC(&norm_type, &A->rows, &A->columns, A->M, &A->rows, work); \
\
    if(work != NULL) \
        free_cb(work); \
\
    return 0; \
}

/** @brief Compute matrix norm for real matrix
 *
 * See dlange.
 *
 * Returns
 *      max(abs(A(i,j))), norm_type = 'M' or 'm'
 *      norm1(A),         norm_type = '1', 'O' or 'o'
 *      normI(A),         norm_type = 'I' or 'i'
 *      normF(A),         norm_type = 'F', 'f', 'E' or 'e'
 *
 * @param [in]  A real matrix
 * @param [in]  norm_type type of norm, e.g. 'F' for Frobenius norm
 * @param [out] norm value of norm
 *
 * @retval ret 0 if successfull, <0 otherwise
 */
MATRIX_NORM(matrix_norm, matrix_t, dlange_);

/** @brief Compute matrix norm for complex matrix
 *
 * See matrix_norm.
 *
 * @param [in]  A complex matrix
 * @param [in]  norm_type type of norm, e.g. 'F' for Frobenius norm
 * @param [out] norm value of norm
 *
 * @retval ret 0 if successfull, <0 otherwise
 */
MATRIX_NORM(matrix_complex_norm, matrix_complex_t, zlange_);

matrix_complex_t *matrix_complex_exp_taylor(matrix_complex_t *A, int order)
{
    const int rows = A->min;
    matrix_complex_t *C = matrix_complex_alloc(rows,rows);

    /* B = E+A/order */
    matrix_complex_t *B = matrix_complex_copy(A,NULL);
    matrix_complex_mult_scalar(B, 1./order);

    for(int i = 0; i < rows; i++)
        matrix_set(B, i,i, 1+matrix_get(B,i,i));

    for(int k = order-1; k > 0; k--)
    {
        matrix_complex_mult(A, B, 1./order, C);

        /* swap */
        {
            matrix_complex_t *temp;
            temp = C;
            C = B;
            B = temp;
        }

        for(int i = 0; i < rows; i++)
            matrix_set(B, i,i, 1+matrix_get(B,i,i));
    }

    matrix_complex_free(C);

    return B;
}

/** @}*/

/** \defgroup LA LU decomposition, inverting
 *  @{
 */

#define LU_DECOMPOSITION(FUNCTION_NAME, MATRIX_TYPE, XGETRF) \
int FUNCTION_NAME(MATRIX_TYPE *A, int ipiv[]) \
{ \
    int info; \
\
    XGETRF( \
        &A->rows,    /* M number of rows of A */ \
        &A->columns, /* N number of columns of A */ \
        A->M,        /* matrix A to be factored */ \
        &A->columns, /* LDA: leading dimension of A */ \
        ipiv,        /* pivot indices of dimension (min(M,N)) */ \
        &info \
    ); \
\
    return info; \
}

/** @brief Compute LU decomposition of real matrix A
 *
 * See dgetrf.
 *
 * The factorization has the form
 *    A = P * L * U
 * where P is a permutation matrix, L is lower triangular with unit
 * diagonal elements (lower trapezoidal if rows > columns), and U is upper
 * triangular (upper trapezoidal if m < n).
 *
 * @param [in,out] A real matrix
 * @param [out] ipiv pivot indices; array of dimension MIN(rows,columns)
 *
 * @retval INFO
 */
LU_DECOMPOSITION(matrix_lu_decomposition, matrix_t, dgetrf_)

/** @brief Compute LU decomposition of complex matrix A
 *
 * See matrix_lu_decomposition.
 *
 * @param [in,out] A complex matrix
 * @param [out] ipiv pivot indices; array of dimension MIN(rows,columns)
 *
 * @retval INFO
 */
LU_DECOMPOSITION(matrix_complex_lu_decomposition, matrix_complex_t, zgetrf_)

#define MATRIX_INVERT(FUNCTION_NAME, TYPE, MATRIX_TYPE, LU_DECOMPOSITION, XGETRI) \
int FUNCTION_NAME(MATRIX_TYPE *A) \
{ \
    int info, lwork, dim = A->min; \
    int *ipiv = NULL; \
    TYPE *work = NULL; \
    TYPE workopt; \
\
    ipiv = malloc_cb(dim*sizeof(int)); \
    if(ipiv == NULL) \
        return LIBHADES_ERROR_OOM; \
\
    info = LU_DECOMPOSITION(A, ipiv); \
    if(info != 0) \
        goto out; \
\
    lwork = -1; \
    XGETRI(&dim, A->M, &dim, ipiv, &workopt, &lwork, &info); \
    if(info != 0) \
        goto out; \
\
    lwork = (int)workopt; \
    work = malloc_cb(lwork*sizeof(TYPE)); \
    if(work == NULL) \
    { \
        info = LIBHADES_ERROR_OOM; \
        goto out; \
    } \
\
    XGETRI( \
        &dim,   /* order of matrix A */ \
        A->M,   /* factors L and U from LU decomposition */ \
        &dim,   /* LDA: leading dimension of A */ \
        ipiv,   /* pivot indices */ \
        work,   /* workspace of dimension LWORK */ \
        &lwork, /* length of work */ \
        &info \
    ); \
\
out: \
    free_cb(ipiv); \
    if(work != NULL) \
        free_cb(work); \
\
    return info; \
}

/** @brief Invert real matrix A
 *
 * The inverse of A is computed using the LU factorzation of A
 *
 * @param [in] A real matrix
 *
 * @retval INFO
 */
MATRIX_INVERT(matrix_invert, double, matrix_t, matrix_lu_decomposition, dgetri_)

/** @brief Invert complex matrix A
 *
 * See matrix_invert.
 *
 * @param [in] A complex matrix
 *
 * @retval INFO
 */
MATRIX_INVERT(matrix_complex_invert, complex_t, matrix_complex_t, matrix_complex_lu_decomposition, zgetri_)

#define MATRIX_SOLVE(FUNCTION_NAME, MATRIX_TYPE, LU_DECOMPOSITION, XGETRS) \
int FUNCTION_NAME(MATRIX_TYPE *A, MATRIX_TYPE *b) \
{ \
    int N = A->min; \
    char trans = 'N'; \
    int nrhs = b->columns; \
    int info = -1; \
    int *ipiv = malloc_cb(N*sizeof(int)); \
\
    if(ipiv == NULL) \
        return LIBHADES_ERROR_OOM; \
\
    LU_DECOMPOSITION(A, ipiv); \
    XGETRS(&trans, &N, &nrhs, A->M, &N, ipiv, b->M, &N, &info); \
\
    free_cb(ipiv); \
\
    return info; \
}

/** @brief Solve system of linear equations
 *
 * Solve the system of linear equations:
 *      A*x = b
 *
 * @param [in,out] A matrix
 * @param [in,out] b vector/matrix
 *
 * @retval INFO
 */

/** @}*/
MATRIX_SOLVE(matrix_solve, matrix_t, matrix_lu_decomposition, dgetrs_);

/** @brief Solve system of linear equations
 *
 * Solve the system of complex linear equations:
 *      A*x = b
 *
 * @param [in,out] A complex matrix
 * @param [in,out] b complex vector/matrix
 *
 * @retval INFO
 */

/** @}*/
MATRIX_SOLVE(matrix_complex_solve, matrix_complex_t, matrix_complex_lu_decomposition, zgetrs_);

/*
static void _cblas_zaxpy(const int N, const double alpha, const void *X, const int incX, void *Y, const int incY)
{
    complex_t beta = alpha;
    cblas_zaxpy(N, &beta, X, incX, Y, incY);
}
*/

/** @brief Calculate dot product of vectors x and y
 *
 * Calculate dot product of first column of x,y. If x and y have different
 * rows, the minimum is used.
 *
 * @param [in] x vector
 * @param [in] y vector
 * @retval x*y
 */
double vector_dot(matrix_t *x, matrix_t *y)
{
    int incx = 1;
    int incy = 1;
    int N = MIN(x->rows, y->rows);

    return ddot_(&N, x->M, &incx, y->M, &incy);
}

/** @brief Calculate dot product of vectors x and y
 *
 * Calculate dot product of first column of x,y. If x and y have different
 * rows, the minimum is used.
 *
 * @param [in] x vector
 * @param [in] y vector
 * @retval x*y
 */
complex_t vector_complex_dot(matrix_complex_t *x, matrix_complex_t *y)
{
    /*
    int incx = 1;
    int incy = 1;
    int N = MIN(x->rows, y->rows);
    complex_t z = 0;

    zdotc_(&z, &N, x->M, &incx, y->M, &incy);
    return z;
    */

    int N = MIN(x->rows, y->rows);
    complex_t z = 0;
    for(int i = 0; i < N; i++)
        z += matrix_get(x,i,0)*matrix_get(y,i,0);
    return z;
}

#define MATRIX_GET_COLUMN(FUNCTION_NAME, TYPE, MATRIX_TYPE) \
MATRIX_TYPE *FUNCTION_NAME(MATRIX_TYPE *A, int i) \
{ \
    const int rows = A->rows; \
    MATRIX_TYPE *v = malloc_cb(sizeof(MATRIX_TYPE)); \
    if(v == NULL) \
        return NULL; \
\
    v->rows    = rows; \
    v->columns = 1; \
    v->min     = 1; \
    v->size    = rows; \
    v->type    = 0; \
    v->view    = 1; \
    v->M       = &A->M[i*rows]; \
\
    return v; \
}

/** @brief Get i-th column of matrix A
 *
 * @param [in] A matrix
 * @param [in] i column number
 * @retval x*y
 */
MATRIX_GET_COLUMN(matrix_get_column,         double,    matrix_t);

/** @brief Get i-th column of matrix A
 *
 * @param [in] A matrix
 * @param [in] i column number
 * @retval x*y
 */
MATRIX_GET_COLUMN(matrix_complex_get_column, complex_t, matrix_complex_t);

/** \defgroup sparse Functions for sparse matrices
 *  @{
 */

#ifdef SUPPORT_SPARSE

/** @brief Calculate eigenvalues of a sparse complex matrix
 *
 * @param [in] N     number of columns/rows of matrix
 * @param [in] nev   number of eigenvalues to compute
 * @param [in] which LM (largest magnitude), SM (smallest magnitude), LR (largest real part), SR (smallest real part), LI (largest imaginary part), SI (smallest imaginary part)
 * @param [in] Av callback function that implements the matrix-vector operation Av; the input vector is given as in, the vector Av must be written in out
 * @param [in,out] d on exit d contains the Rith approximations (must be of length nev+1)
 * @param [in] mxiter maximum number of Arnoldi update iterations allowed
 * @param [in] tol relative accuracy of the Ritz value
 * @param [in] data pointer that is given to callback function Av
 *
 * @retval 0 if successful
 */
int sparse_complex_eig(int N, int nev, char *which, void (*Av)(int N, complex_t *in, complex_t *out, void *data), complex_t *d, int mxiter, double tol, void *data)
{
    int ret = LIBHADES_ERROR_OOM;
    int info = 0;
    int ido = 0;
    char *bmat = "I";          /* standard eigenproblem */
    int ncv = MIN(2*nev+2, N);

    int ishift = 1;
    int mode = 1;
    int iparam[11] = { ishift, 0, mxiter, 1, 0, 0, mode, 0, 0, 0, 0 };

    int ipntr[14];
    int lworkl = ncv*(3*ncv + 5);
    int rvec = 0;
    char *howmny = "P";

    complex_t *workd = NULL, *workl = NULL, *resid = NULL, *v = NULL, *workev = NULL;
    int *select = NULL;
    double *rwork = NULL;

    /* allocate memory */
    ret = LIBHADES_ERROR_OOM;

    workd = malloc_cb(3*N*sizeof(complex_t));
    if(workd == NULL)
        goto out;
    workl = malloc_cb(lworkl*sizeof(complex_t));
    if(workl == NULL)
        goto out;
    rwork = malloc_cb(ncv*sizeof(double));
    if(rwork == NULL)
        goto out;
    resid  = malloc_cb(N*sizeof(complex_t));
    if(resid == NULL)
        goto out;
    v = malloc_cb(N*ncv*sizeof(complex_t));
    if(v == NULL)
        goto out;
    select = malloc_cb(ncv*sizeof(int));
    if(select == NULL)
        goto out;
    workev = malloc_cb((2*ncv)*sizeof(complex_t));
    if(workev == NULL)
        goto out;

    /* loop */
    while(1)
    {
        /* http://www.caam.rice.edu/software/ARPACK/UG/node138.html */
        znaupd_(
            &ido, bmat, &N, which, &nev, &tol, resid, &ncv, v, &N, iparam,
            ipntr, workd, workl, &lworkl, rwork, &info, strlen(bmat),
            strlen(which)
        );

        if(ido == 1 || ido == -1)
            Av(N, &workd[ipntr[0]-1], &workd[ipntr[1]-1], data);
        else if(ido == 99)
            break;
        else
        {
            ret = ido;
            goto out;
        }
    }

    if(info != 0)
    {
        ret = info;
        goto out;
    }

    /* http://www.mathkeisan.com/usersguide/man/zneupd.html */
    zneupd_(
        &rvec, howmny, select, d, NULL, &N, NULL, workev, bmat, &N, which,
        &nev, &tol, resid, &ncv, v, &N, iparam, ipntr, workd, workl, &lworkl,
        rwork, &info, strlen(howmny), strlen(bmat), strlen(which)
    );

    ret = info;

out:
    if(resid != NULL)
        free_cb(resid);
    if(v != NULL)
        free_cb(v);
    if(workd != NULL)
        free_cb(workd);
    if(workl != NULL)
        free_cb(workl);
    if(rwork != NULL)
        free_cb(rwork);
    if(select != NULL)
        free_cb(select);
    if(workev != NULL)
        free_cb(workev);

    return ret;
}

#endif
/** @}*/

/** \defgroup io Save/load matrices
 *  @{
 */

#define MATRIX_LOAD_FROM_STREAM(FUNCTION_NAME, MATRIX_TYPE, TYPE, ALLOC, TRANSPOSE, IS_COMPLEX) \
MATRIX_TYPE *FUNCTION_NAME(FILE *stream, int *ret) \
{ \
    MATRIX_TYPE *M; \
    uint16_t len; \
    int rows, columns, fortran_order, is_complex; \
    char header[10] = { 0 }; \
    char dict[2048] = { 0 }; \
\
    if(ret != NULL) \
        *ret = 0; \
\
    /* read magic string, major and minor number */ \
    fread(header, 8, 1, stream); \
    if(memcmp(header, "\x93NUMPY\x01\x00", 8) != 0) \
    { \
        if(ret != NULL) \
            *ret = LIBHADES_ERROR_HEADER; \
        return NULL; \
    } \
\
    /* read length of dict */ \
    fread(&len, sizeof(uint16_t), 1, stream); \
\
    if(len >= sizeof(dict)/sizeof(dict[0])) \
    { \
        if(ret != NULL) \
            *ret = LIBHADES_ERROR_INV_LENGTH; \
        return NULL; \
    } \
\
    fread(dict, sizeof(char), len, stream); \
\
    if(npy_dict_get_fortran_order(dict, &fortran_order) != 0) \
    { \
        if(ret != NULL) \
            *ret = LIBHADES_ERROR_ORDER; \
        return NULL; \
    } \
\
    if(npy_dict_get_shape(dict, &rows, &columns) != 0) \
    { \
        if(ret != NULL) \
            *ret = LIBHADES_ERROR_SHAPE; \
        return NULL; \
    } \
\
    if(npy_dict_get_descr(dict, &is_complex) != 0) \
    { \
        if(ret != NULL) \
            *ret = LIBHADES_ERROR_DESCR; \
        return NULL; \
    } \
\
    if(is_complex != IS_COMPLEX) \
    { \
        if(ret != NULL) \
            *ret = LIBHADES_ERROR_FORMAT; \
        return NULL; \
    } \
\
    M = ALLOC(rows,columns); \
    fread(M->M, sizeof(TYPE), rows*columns, stream); \
\
    if(!fortran_order) \
        TRANSPOSE(M); \
\
    M->min  = MIN(rows,columns); \
    M->size = (size_t)rows*(size_t)columns; \
    M->view = 0; \
    M->type = 0; \
\
    return M; \
}

/** @brief Load real matrix from stream
 *
 * Load real matrix A from a stream. This function will also allocate memory
 * for the matrix.
 *
 * If error != NULL, error will be set to:
 *  0                         if successful
 *  LIBHADES_ERROR_HEADER     if magic or major/minor number is invalid
 *  LIBHADES_ERROR_INV_LENGTH if length of dictionary is invalid (too long)
 *  LIBHADES_ERROR_ORDER      if order is invalid (Fortran/C order)
 *  LIBHADES_ERROR_SHAPE      if shape is invalid (rows/columns)
 *  LIBHADES_ERROR_DESCR      if dtype is wrong/not supported
 *  LIBHADES_ERROR_FORMAT     if wrong format (real instead of complex)
 *  0 rows/columns wrong
 *
 * @param [in] stream file handle of a opened file
 * @param [out] error error code
 * @retval A matrix
 * @retval NULL if an error occured
 */
MATRIX_LOAD_FROM_STREAM(matrix_load_from_stream, matrix_t, double, matrix_alloc, matrix_transpose, 0);

/** @brief Load complex matrix from stream
 *
 * Load complex matrix A from a stream. This function will also allocate memory
 * for the matrix.
 *
 * If error != NULL, error will be set to:
 *  0                         if successful
 *  LIBHADES_ERROR_HEADER     if magic or major/minor number is invalid
 *  LIBHADES_ERROR_INV_LENGTH if length of dictionary is invalid (too long)
 *  LIBHADES_ERROR_ORDER      if order is invalid (Fortran/C order)
 *  LIBHADES_ERROR_SHAPE      if shape is invalid (rows/columns)
 *  LIBHADES_ERROR_DESCR      if dtype is wrong/not supported
 *  LIBHADES_ERROR_FORMAT     if wrong format (complex instead of real)
 *  0 rows/columns wrong
 *
 * @param [in] stream file handle of a opened file
 * @param [out] error error code
 * @retval A matrix
 * @retval NULL if an error occured
 */
MATRIX_LOAD_FROM_STREAM(matrix_complex_load_from_stream, matrix_complex_t, complex_t, matrix_complex_alloc, matrix_complex_transpose, 1);

#define MATRIX_LOAD(FUNCTION_NAME, MATRIX_TYPE, LOAD_FUNCTION) \
MATRIX_TYPE *FUNCTION_NAME(const char *filename, int *ret) \
{ \
    FILE *stream; \
    MATRIX_TYPE *M; \
\
    if((stream = fopen(filename, "r")) == NULL) \
    { \
        if(ret != NULL) \
            *ret = LIBHADES_ERROR_IO; \
        return NULL; \
    } \
\
    M = LOAD_FUNCTION(stream, ret); \
\
    fclose(stream); \
\
    return M; \
}

/** @brief Load real matrix from file
 *
 * Load real matrix A from file given by filename. This function will also
 * allocate memory for the matrix. See \ref matrix_load_from_stream for errors.
 *
 * @param [in] filename path to the file
 * @param [out] error error code
 * @retval A matrix
 * @retval NULL if an error occured
 */
MATRIX_LOAD(matrix_load, matrix_t, matrix_load_from_stream);

/** @brief Load complex matrix from file
 *
 * Load complex matrix A from file given by filename. This function will also
 * allocate memory for the matrix. See \ref matrix_complex_load_from_stream for
 * errors.
 *
 * @param [in] filename path to the file
 * @param [out] error error code
 * @retval A matrix
 * @retval NULL if an error occured
 */
MATRIX_LOAD(matrix_complex_load, matrix_complex_t, matrix_complex_load_from_stream);

#define MATRIX_SAVE_TO_STREAM(FUNCTION_NAME, TYPE, MATRIX_TYPE, DTYPE) \
void FUNCTION_NAME(MATRIX_TYPE *M, FILE *stream) \
{ \
    char d_str[512] = { 0 }; \
    uint16_t len = 0; \
    const int rows = M->rows, columns = M->columns; \
\
    /* write magic string, major number and minor number */ \
    fwrite("\x93NUMPY\x01\x00", sizeof(char), 8, stream); \
\
    /* write length of header and header */ \
    snprintf(d_str, sizeof(d_str)/sizeof(d_str[0]), "{'descr': '%s', 'fortran_order': True, 'shape': (%d, %d), }", DTYPE, rows, columns); \
\
    len = strlen(d_str); \
\
    fwrite(&len,  sizeof(len),  1,   stream); \
    fwrite(d_str, sizeof(char), len, stream); \
\
    /* write matrix */ \
    fwrite(M->M, sizeof(TYPE), M->size, stream); \
}

/** @brief Save real matrix A to stream
 *
 * Save real matrix A to a file handle given by stream. The datatype
 * corresponds to Numpy's npy file format.
 *
 * @param [in] A      matrix to be dumped to file
 * @param [in] stream file handle of opened file
 */
MATRIX_SAVE_TO_STREAM(matrix_save_to_stream, double, matrix_t, "<f8");

/** @brief Save complex matrix A to stream
 *
 * Save complex matrix A to a file handle given by stream. The datatype
 * corresponds to Numpy's npy file format.
 *
 * @param [in] A      matrix to be dumped to file
 * @param [in] stream file handle of opened file
 */
MATRIX_SAVE_TO_STREAM(matrix_complex_save_to_stream, complex_t, matrix_complex_t, "<c16");

#define MATRIX_SAVE(FUNCTION_NAME, MATRIX_TYPE, SAVE_FUNCTION) \
int FUNCTION_NAME(MATRIX_TYPE *M, const char *filename) \
{ \
    FILE *stream = fopen(filename, "w"); \
    if(stream == NULL) \
        return LIBHADES_ERROR_IO; \
\
    SAVE_FUNCTION(M, stream); \
\
    fclose(stream); \
\
    return 0; \
};

/** @brief Save real matrix A to file
 *
 * Save real matrix A to file given by filename. The datatype corresponds to
 * Numpy's .npy file format.
 *
 * @param [in] A        matrix to be dumped to file
 * @param [in] filename path to the file
 * @retval 0 if successful
 * @retval LIBHADES_ERROR_IO if file could not be opened
 */
MATRIX_SAVE(matrix_save, matrix_t, matrix_save_to_stream);

/** @brief Save complex matrix A to file
 *
 * Save complex matrix A to file given by filename. The datatype corresponds to
 * Numpy's .npy file format.
 *
 * @param [in] A        matrix to be dumped to file
 * @param [in] filename path to the file
 * @retval 0 if successful
 * @retval LIBHADES_ERROR_IO if file could not be opened
 */
MATRIX_SAVE(matrix_complex_save, matrix_complex_t, matrix_complex_save_to_stream);

/** @}*/