/*

PhyML:  a program that  computes maximum likelihood phylogenies from
DNA or AA homologous sequences.

Copyright (C) Stephane Guindon. Oct 2003 onward.

All parts of the source except where indicated are distributed under
the GNU public licence. See http://www.opensource.org for details.

*/

#include "mixt.h"

int n_sec1 = 0;

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Chain_All(t_tree *mixt_tree)
{
  t_tree *curr, *next;
  int     i;

  curr = mixt_tree;
  next = mixt_tree->next;

  do
  {
    MIXT_Chain_String(curr->mod->aa_rate_mat_file, next->mod->aa_rate_mat_file);
    MIXT_Chain_String(curr->mod->modelname, next->mod->modelname);
    MIXT_Chain_String(curr->mod->custom_mod_string,
                      next->mod->custom_mod_string);
    MIXT_Chain_Scalar_Dbl(curr->mod->kappa, next->mod->kappa);
    MIXT_Chain_Scalar_Dbl(curr->mod->lambda, next->mod->lambda);
    MIXT_Chain_Scalar_Dbl(curr->mod->br_len_mult, next->mod->br_len_mult);
    MIXT_Chain_Scalar_Dbl(curr->mod->br_len_mult_unscaled,
                          next->mod->br_len_mult_unscaled);
    MIXT_Chain_Scalar_Dbl(curr->mod->mr, next->mod->mr);
    MIXT_Chain_Vector_Dbl(curr->mod->Pij_rr, next->mod->Pij_rr);
    MIXT_Chain_Vector_Dbl(curr->mod->e_frq->user_b_freq,
                          next->mod->e_frq->user_b_freq);
    MIXT_Chain_Vector_Dbl(curr->mod->e_frq->pi, next->mod->e_frq->pi);
    MIXT_Chain_Vector_Dbl(curr->mod->e_frq->pi_unscaled,
                          next->mod->e_frq->pi_unscaled);
    for (i = 0; i < 2 * mixt_tree->n_otu - 1; ++i)
      MIXT_Chain_Scalar_Dbl(curr->a_edges[i]->l, next->a_edges[i]->l);
    for (i = 0; i < 2 * mixt_tree->n_otu - 1; ++i)
      MIXT_Chain_Scalar_Dbl(curr->a_edges[i]->l_old, next->a_edges[i]->l_old);
    for (i = 0; i < 2 * mixt_tree->n_otu - 1; ++i)
      MIXT_Chain_Scalar_Dbl(curr->a_edges[i]->l_var, next->a_edges[i]->l_var);
    for (i = 0; i < 2 * mixt_tree->n_otu - 1; ++i)
      MIXT_Chain_Scalar_Dbl(curr->a_edges[i]->l_var_old,
                            next->a_edges[i]->l_var_old);
    MIXT_Chain_Rmat(curr->mod->r_mat, next->mod->r_mat);
    MIXT_Chain_RAS(curr->mod->ras, next->mod->ras);
    MIXT_Chain_Efrq(curr->mod->e_frq, next->mod->e_frq);
    MIXT_Chain_Eigen(curr->mod->eigen, next->mod->eigen);

    curr = next;
    next = next->next;
  } while (next);

  MIXT_Chain_Models(mixt_tree);
  MIXT_Chain_Edges(mixt_tree);
  MIXT_Chain_Nodes(mixt_tree);
  MIXT_Chain_Sprs(mixt_tree);
  Make_Rmat_Weight(mixt_tree);
  Make_Efrq_Weight(mixt_tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Chain_Edges(t_tree *mixt_tree)
{
  int     i;
  t_edge *b;
  t_tree *tree;

  tree = mixt_tree;
  do
  {
    for (i = 0; i < 2 * tree->n_otu - 1; ++i)
    {
      b = tree->a_edges[i];

      if (tree->next) b->next = tree->next->a_edges[i];
      if (tree->prev) b->prev = tree->prev->a_edges[i];
      if (tree->next_mixt) b->next_mixt = tree->next_mixt->a_edges[i];
      if (tree->prev_mixt) b->prev_mixt = tree->prev_mixt->a_edges[i];
    }

    if (tree->e_root != NULL)
    {
      b = tree->e_root;

      if (tree->next) b->next = tree->next->e_root;
      if (tree->prev) b->prev = tree->prev->e_root;
      if (tree->next_mixt) b->next_mixt = tree->next_mixt->e_root;
      if (tree->prev_mixt) b->prev_mixt = tree->prev_mixt->e_root;
    }
    tree = tree->next;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Chain_Models(t_tree *mixt_tree)
{
  t_tree *tree;

  tree = mixt_tree;
  do
  {
    if (tree->next != NULL)
    {
      tree->mod->next       = tree->next->mod;
      tree->next->mod->prev = tree->mod;
    }
    else
    {
      tree->mod->next = NULL;
    }
    tree = tree->next;
  } while (tree);

  tree = mixt_tree;
  do
  {
    if (tree->next_mixt != NULL)
    {
      tree->mod->next_mixt            = tree->next_mixt->mod;
      tree->next_mixt->mod->prev_mixt = tree->mod;
    }
    else
    {
      tree->mod->next_mixt = NULL;
    }

    tree = tree->next_mixt;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Chain_Nodes(t_tree *mixt_tree)
{
  int     i;
  t_node *n;
  t_tree *tree;

  tree = mixt_tree;
  do
  {
    for (i = 0; i < 2 * tree->n_otu - 2; ++i)
    {
      n = tree->a_nodes[i];

      if (tree->next) n->next = tree->next->a_nodes[i];
      if (tree->prev) n->prev = tree->prev->a_nodes[i];
      if (tree->next_mixt) n->next_mixt = tree->next_mixt->a_nodes[i];
      if (tree->prev_mixt) n->prev_mixt = tree->prev_mixt->a_nodes[i];
    }

    if (tree->n_root != NULL)
    {
      n = tree->n_root;
      if (tree->next) n->next = tree->next->n_root;
      if (tree->prev) n->prev = tree->prev->n_root;
      if (tree->next_mixt) n->next_mixt = tree->next_mixt->n_root;
      if (tree->prev_mixt) n->prev_mixt = tree->prev_mixt->n_root;
    }
    tree = tree->next;
  } while (tree);
}
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Chain_Sprs(t_tree *mixt_tree)
{
  int     i;
  t_tree *tree;

  tree = mixt_tree;
  do
  {
    if (tree->next) tree->best_spr->next = tree->next->best_spr;
    if (tree->prev) tree->best_spr->prev = tree->prev->best_spr;
    if (tree->next_mixt) tree->best_spr->next_mixt = tree->next_mixt->best_spr;
    if (tree->prev_mixt) tree->best_spr->prev_mixt = tree->prev_mixt->best_spr;

    for (i = 0; i < 2 * tree->n_otu - 2; ++i)
    {
      if (tree->next)
        tree->spr_list_one_edge[i]->next = tree->next->spr_list_one_edge[i];
      if (tree->prev)
        tree->spr_list_one_edge[i]->prev = tree->prev->spr_list_one_edge[i];
      if (tree->next_mixt)
        tree->spr_list_one_edge[i]->next_mixt =
            tree->next_mixt->spr_list_one_edge[i];
      if (tree->prev_mixt)
        tree->spr_list_one_edge[i]->prev_mixt =
            tree->prev_mixt->spr_list_one_edge[i];

      if (tree->next)
        tree->spr_list_all_edge[i]->next = tree->next->spr_list_all_edge[i];
      if (tree->prev)
        tree->spr_list_all_edge[i]->prev = tree->prev->spr_list_all_edge[i];
      if (tree->next_mixt)
        tree->spr_list_all_edge[i]->next_mixt =
            tree->next_mixt->spr_list_all_edge[i];
      if (tree->prev_mixt)
        tree->spr_list_all_edge[i]->prev_mixt =
            tree->prev_mixt->spr_list_all_edge[i];
    }
    tree = tree->next;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Chain_String(t_string *curr, t_string *next)
{
  if (!next)
  {
    return;
  }
  else
  {
    t_string *buff, *last;

    last = NULL;

    /*! Search backward */
    buff = curr;
    while (buff)
    {
      if (buff == next) break;
      buff = buff->prev;
    }

    /*! Search forward */
    if (!buff)
    {
      buff = curr;
      while (buff)
      {
        if (buff == next) break;
        buff = buff->next;
      }
    }

    if (!buff)
    {
      last = curr;
      while (last->next)
      {
        last = last->next;
      }

      last->next = next;
      next->prev = last;
    }
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Chain_Vector_Dbl(vect_dbl *curr, vect_dbl *next)
{
  if (!next)
  {
    return;
  }
  else
  {
    vect_dbl *buff, *last;

    last = NULL;

    buff = curr;
    while (buff)
    {
      if (buff == next) break;
      buff = buff->prev;
    }

    /*! Search forward */
    if (!buff)
    {
      buff = curr;
      while (buff)
      {
        if (buff == next) break;
        buff = buff->next;
      }
    }

    if (!buff)
    {
      last = curr;
      while (last->next)
      {
        last = last->next;
      }

      last->next = next;
      next->prev = last;
    }
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Chain_Scalar_Dbl(scalar_dbl *curr, scalar_dbl *next)
{
  if (!next)
  {
    return;
  }
  else
  {
    scalar_dbl *buff, *last;

    last = NULL;

    /*! Search backward */
    buff = curr;
    while (buff)
    {
      if (buff == next) break;
      buff = buff->prev;
    }

    /*! Search forward */
    if (!buff)
    {
      buff = curr;
      while (buff)
      {
        if (buff == next) break;
        buff = buff->next;
      }
    }

    /*! Not chained yet. Add next at the end of chained list */
    if (!buff)
    {
      last = curr;
      while (last->next)
      {
        last = last->next;
      }

      last->next = next;
      next->prev = last;
    }
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Chain_Rmat(t_rmat *curr, t_rmat *next)
{
  if (!next)
  {
    return;
  }
  else
  {
    t_rmat *buff, *last;

    last = NULL;

    buff = curr;
    while (buff)
    {
      if (buff == next) break;
      buff = buff->prev;
    }

    /*! Search forward */
    if (!buff)
    {
      buff = curr;
      while (buff)
      {
        if (buff == next) break;
        buff = buff->next;
      }
    }

    if (!buff)
    {
      last = curr;
      while (last->next)
      {
        last = last->next;
      }

      last->next = next;
      next->prev = last;
    }
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Chain_Efrq(t_efrq *curr, t_efrq *next)
{
  if (!next)
  {
    return;
  }
  else
  {
    t_efrq *buff, *last;

    last = NULL;

    buff = curr;
    while (buff)
    {
      if (buff == next) break;
      buff = buff->prev;
    }

    /*! Search forward */
    if (!buff)
    {
      buff = curr;
      while (buff)
      {
        if (buff == next) break;
        buff = buff->next;
      }
    }

    if (!buff)
    {
      last = curr;
      while (last->next)
      {
        last = last->next;
      }

      last->next = next;
      next->prev = last;
    }
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Chain_Eigen(eigen *curr, eigen *next)
{
  if (!next)
  {
    return;
  }
  else
  {
    eigen *buff, *last;

    last = NULL;

    buff = curr;
    while (buff)
    {
      if (buff == next) break;
      buff = buff->prev;
    }

    /*! Search forward */
    if (!buff)
    {
      buff = curr;
      while (buff)
      {
        if (buff == next) break;
        buff = buff->next;
      }
    }

    if (!buff)
    {
      last = curr;
      while (last->next)
      {
        last = last->next;
      }

      last->next = next;
      next->prev = last;
    }
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Chain_RAS(t_ras *curr, t_ras *next)
{
  if (!next)
    return;
  else
  {
    t_ras *buff, *last;

    last = NULL;

    buff = curr;
    while (buff)
    {
      if (buff == next) break;
      buff = buff->prev;
    }

    /*! Search forward */
    if (!buff)
    {
      buff = curr;
      while (buff)
      {
        if (buff == next) break;
        buff = buff->next;
      }
    }

    if (!buff)
    {
      last = curr;
      while (last->next)
      {
        last = last->next;
      }

      last->next = next;
      next->prev = last;
    }
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Chain_Rates(t_rate *curr, t_rate *next)
{
  if (!next)
    return;
  else
  {
    t_rate *buff, *last;

    last = NULL;

    buff = curr;
    while (buff)
    {
      if (buff == next) break;
      buff = buff->prev;
    }

    /*! Search forward */
    if (!buff)
    {
      buff = curr;
      while (buff)
      {
        if (buff == next) break;
        buff = buff->next;
      }
    }

    if (!buff)
    {
      last = curr;
      while (last->next)
      {
        last = last->next;
      }

      last->next = next;
      next->prev = last;
    }
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Chain_Cal(t_tree *mixt_tree)
{
  int i;

  for (i = 0; i < mixt_tree->times->n_cal - 1; i++)
  {
    mixt_tree->times->a_cal[i]->next     = mixt_tree->times->a_cal[i + 1];
    mixt_tree->times->a_cal[i + 1]->prev = mixt_tree->times->a_cal[i];
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Turn_Branches_OnOff_In_All_Elem(int onoff, t_tree *mixt_tree)
{
  t_tree *tree;

  /*! Turn all branches to ON state */
  tree = mixt_tree;
  do
  {
    MIXT_Turn_Branches_OnOff_In_One_Elem(ON, tree);
    tree = tree->next_mixt;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Turn_Branches_OnOff_In_One_Elem(int onoff, t_tree *mixt_tree)
{
  int     i;
  t_tree *tree;

  if (mixt_tree->is_mixt_tree == NO)
  {
    PhyML_Fprintf(stderr, "\n. Err. in file %s at line %d (function '%s') \n",
                  __FILE__, __LINE__, __FUNCTION__);
    Exit("\n");
  }

  tree = mixt_tree;

  do
  {
    for (i = 0; i < 2 * tree->n_otu - 1; ++i)
      tree->a_edges[i]->l->onoff = onoff;
    tree = tree->next;
  } while (tree && tree->is_mixt_tree == NO);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Post_Order_Lk(t_node *mixt_a, t_node *mixt_d, t_tree *mixt_tree)
{
  t_tree *tree;
  t_node *a, *d;

  tree = mixt_tree;
  a    = mixt_a;
  d    = mixt_d;

  assert(a);
  assert(d);
  assert(tree);

  do
  {
    if (tree->is_mixt_tree)
    {
      tree = tree->next;
      a    = a->next;
      d    = d->next;
    }

    assert(a);
    assert(d);
    assert(tree);

    if (tree->mod->ras->invar == NO) Post_Order_Lk(a, d, tree);

    tree = tree->next;
    a    = a->next;
    d    = d->next;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Pre_Order_Lk(t_node *mixt_a, t_node *mixt_d, t_tree *mixt_tree)
{
  t_tree *tree;
  t_node *a, *d;

  tree = mixt_tree;
  a    = mixt_a;
  d    = mixt_d;

  assert(a);
  assert(d);
  assert(tree);

  do
  {
    if (tree->is_mixt_tree)
    {
      tree = tree->next;
      a    = a->next;
      d    = d->next;
    }

    assert(a);
    assert(d);
    assert(tree);

    if (tree->mod->ras->invar == NO) Pre_Order_Lk(a, d, tree);

    tree = tree->next;
    a    = a->next;
    d    = d->next;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

phydbl MIXT_Lk(t_edge *mixt_b, t_tree *mixt_tree)
{
  t_tree      *tree, *cpy_mixt_tree;
  t_edge      *b, *cpy_mixt_b;
  phydbl       sum_lnL;
  unsigned int site, br, ns;
  phydbl      *sum_scale_left_cat, *sum_scale_rght_cat;
  phydbl       sum;
  phydbl       site_lk_cat, site_lk, log_site_lk, inv_site_lk;
  int          num_prec_issue;
  int          ambiguity_check, state;
  int          l;
  phydbl       r_mat_weight_sum, e_frq_weight_sum, sum_probas;
  phydbl       len;
  phydbl      *expl, *dot_prod;

  tree          = NULL;
  b             = NULL;
  expl          = NULL;
  cpy_mixt_tree = mixt_tree;
  cpy_mixt_b    = mixt_b;
  len           = -1.;
  ns            = 0;

  if (mixt_b == NULL)
  {
    /* Update RAS structure (mixt_tree level) */
    tree = mixt_tree;
    do
    {
      Update_RAS(tree->mod);
      tree = tree->next_mixt;
    } while (tree);

    /* Update other model structure (tree level) */
    tree = mixt_tree->next;
    do
    {
      if (tree->is_mixt_tree == YES) tree = tree->next;

      if (!cpy_mixt_b)
      {
        if (!Update_Boundaries(tree->mod) || !Update_Efrq(tree->mod) ||
            !Update_Eigen(tree->mod))
        {
          PhyML_Fprintf(stderr, "\n. move: %s",
                        mixt_tree->mcmc->move_name[mixt_tree->mcmc->move_idx]);
          Generic_Exit(__FILE__, __LINE__, __FUNCTION__);
        }
      }
      tree = tree->next;
    } while (tree);
  }

  if (!cpy_mixt_b)
  {
    for (br = 0; br < 2 * mixt_tree->n_otu - 3; ++br)
      MIXT_Update_PMat_At_Given_Edge(mixt_tree->a_edges[br], mixt_tree);

    if (mixt_tree->n_root && mixt_tree->ignore_root == NO)
    {
      MIXT_Update_PMat_At_Given_Edge(mixt_tree->n_root->b[1], mixt_tree);
      MIXT_Update_PMat_At_Given_Edge(mixt_tree->n_root->b[2], mixt_tree);
    }
  }
  else
  {
    if (mixt_tree->n_root != NULL &&
        (mixt_b == mixt_tree->n_root->b[1] ||
         mixt_b == mixt_tree->n_root->b[2]) &&
        mixt_tree->ignore_root == YES)
    {
      MIXT_Update_PMat_At_Given_Edge(mixt_tree->e_root, mixt_tree);
    }
    else
    {
      MIXT_Update_PMat_At_Given_Edge(mixt_b, mixt_tree);
    }
  }

  if (!cpy_mixt_b)
  {
    if (mixt_tree->n_root != NULL)
    {
      if (mixt_tree->ignore_root == NO)
      {
        MIXT_Post_Order_Lk(mixt_tree->n_root, mixt_tree->n_root->v[1],
                           mixt_tree);
        MIXT_Post_Order_Lk(mixt_tree->n_root, mixt_tree->n_root->v[2],
                           mixt_tree);

        MIXT_Update_Partial_Lk(mixt_tree, mixt_tree->n_root->b[1],
                               mixt_tree->n_root);
        MIXT_Update_Partial_Lk(mixt_tree, mixt_tree->n_root->b[2],
                               mixt_tree->n_root);

        if (mixt_tree->both_sides == YES)
        {
          MIXT_Pre_Order_Lk(mixt_tree->n_root, mixt_tree->n_root->v[1],
                            mixt_tree);
          MIXT_Pre_Order_Lk(mixt_tree->n_root, mixt_tree->n_root->v[2],
                            mixt_tree);
        }
      }
      else
      {
        MIXT_Post_Order_Lk(mixt_tree->e_root->rght, mixt_tree->e_root->left,
                           mixt_tree);
        MIXT_Post_Order_Lk(mixt_tree->e_root->left, mixt_tree->e_root->rght,
                           mixt_tree);

        if (mixt_tree->both_sides == YES)
        {
          MIXT_Pre_Order_Lk(mixt_tree->e_root->rght, mixt_tree->e_root->left,
                            mixt_tree);
          MIXT_Pre_Order_Lk(mixt_tree->e_root->left, mixt_tree->e_root->rght,
                            mixt_tree);
        }
      }
    }
    else
    {
      MIXT_Post_Order_Lk(mixt_tree->a_nodes[0], mixt_tree->a_nodes[0]->v[0],
                         mixt_tree);
      if (mixt_tree->both_sides == YES)
      {
        MIXT_Pre_Order_Lk(mixt_tree->a_nodes[0], mixt_tree->a_nodes[0]->v[0],
                          mixt_tree);
      }
    }
  }

  do /*! Consider each element of the data partition */
  {
    mixt_tree->c_lnL = 0.0;
    tree             = mixt_tree->next;
    do
    {
      tree->c_lnL = 0.0;
      tree        = tree->next;
    } while (tree && tree->is_mixt_tree == NO);

    Set_Br_Len_Var(mixt_b, mixt_tree);

    if (!cpy_mixt_b)
    {
      if (mixt_tree->n_root)
      {
        if (mixt_tree->ignore_root == NO)
          mixt_b = (mixt_tree->n_root->v[1]->tax == NO)
                       ? (mixt_tree->n_root->b[2])
                       : (mixt_tree->n_root->b[1]);
        else
          mixt_b = mixt_tree->e_root;
      }
      else
      {
        mixt_b = mixt_tree->a_nodes[0]->b[0];
      }
    }

    sum_scale_left_cat = (phydbl *)mCalloc(
        MAX(mixt_tree->mod->ras->n_catg, mixt_tree->mod->n_mixt_classes),
        sizeof(phydbl));
    sum_scale_rght_cat = (phydbl *)mCalloc(
        MAX(mixt_tree->mod->ras->n_catg, mixt_tree->mod->n_mixt_classes),
        sizeof(phydbl));

    r_mat_weight_sum =
        MIXT_Get_Sum_Chained_Scalar_Dbl(mixt_tree->next->mod->r_mat_weight);
    e_frq_weight_sum =
        MIXT_Get_Sum_Chained_Scalar_Dbl(mixt_tree->next->mod->e_frq_weight);
    sum_probas = MIXT_Get_Sum_Of_Probas_Across_Mixtures(
        r_mat_weight_sum, e_frq_weight_sum, mixt_tree);

    b    = mixt_b->next;
    tree = mixt_tree->next;
    do
    {
      while (tree->mod->ras->invar == YES)
      {
        tree = tree->next;
        b    = b->next;

        if (tree == NULL || tree->is_mixt_tree == YES)
        {
          PhyML_Fprintf(stderr, "\n. %p", (void *)tree);
          PhyML_Fprintf(stderr,
                        "\n. Err. in file %s at line %d (function '%s') \n",
                        __FILE__, __LINE__, __FUNCTION__);
          Exit("\n");
        }
      }

      if (tree->update_eigen_lr == YES) Update_Eigen_Lr(b, tree);

      if (tree->use_eigen_lr == YES)
      {
        len = b->l->v;
        len *= tree->mod->br_len_mult->v;
        len *= tree->mixt_tree->mod->ras->gamma_rr
                   ->v[tree->mod->ras->parent_class_number];
        if (len < tree->mod->l_min)
          len = tree->mod->l_min;
        else if (len > tree->mod->l_max)
          len = tree->mod->l_max;
        expl = tree->expl;
        for (l = 0; l < tree->mod->ns; l++)
          expl[l] = (phydbl)POW(tree->mod->eigen->e_val[l], len);
      }

      tree = tree->next;
      b    = b->next;

    } while (tree && tree->is_mixt_tree == NO);

    tree = mixt_tree;
    do
    {
      tree->numerical_warning = NO;
      tree                    = tree->next;
    } while (tree);

    mixt_tree->c_lnL = .0;

    for (site = 0; site < mixt_tree->data->n_pattern; ++site)
    {
      b    = mixt_b->next;
      tree = mixt_tree->next;

      /*! Skip calculations if model has zero rate */
      while (tree->mod->ras->invar == YES)
      {
        tree = tree->next;
        b    = b->next;

        if (tree == NULL || tree->is_mixt_tree == YES)
        {
          PhyML_Fprintf(stderr, "\n. %p", (void *)tree);
          PhyML_Fprintf(stderr,
                        "\n. Err. in file %s at line %d (function '%s') \n",
                        __FILE__, __LINE__, __FUNCTION__);
          Exit("\n");
        }
      }

      ambiguity_check = -1;
      state           = -1;

      if ((b->rght->tax) && (!mixt_tree->mod->s_opt->greedy) &&
          (mixt_tree->data->wght[site] > SMALL))
      {
        ambiguity_check = b->rght->c_seq->is_ambigu[site];
        if (!ambiguity_check) state = b->rght->c_seq->d_state[site];
      }

      /*! For all classes in the mixture */
      do
      {
        if (tree->is_mixt_tree == YES)
        {
          tree = tree->next;
          b    = b->next;
        }

        ns                     = tree->mod->ns;
        tree->curr_site        = site;
        tree->apply_lk_scaling = NO;
        site_lk_cat            = 0.0;
        expl                   = tree->expl;
        dot_prod               = tree->dot_prod;

        if (!(tree->mod->ras->invar == YES && mixt_tree->is_mixt_tree == YES) &&
            (tree->data->wght[tree->curr_site] > SMALL))
        {
          if ((b->rght->tax) && (tree->mod->s_opt->greedy == NO))
          {
            ambiguity_check = b->rght->c_seq->is_ambigu[tree->curr_site];
            if (ambiguity_check == NO)
              state = b->rght->c_seq->d_state[tree->curr_site];
          }

          if (tree->use_eigen_lr == YES)
          {
            site_lk_cat = Lk_Core_Eigen_Lr(expl, dot_prod + site * ns, b, tree);
          }
          else
          {
            if (b->rght->tax == YES)
              site_lk_cat = Lk_Core(
                  state, ambiguity_check, b->p_lk_left + site * ns,
                  b->p_lk_tip_r + site * ns, b->Pij_rr, b->tPij_rr, b, tree);
            else
              site_lk_cat = Lk_Core(
                  state, ambiguity_check, b->p_lk_left + site * ns,
                  b->p_lk_rght + site * ns, b->Pij_rr, b->tPij_rr, b, tree);
          }
        }

        tree->apply_lk_scaling = YES;

        sum_scale_left_cat[tree->mod->ras->parent_class_number] =
            (b->sum_scale_left) ? (b->sum_scale_left[site]) : (0.0);

        sum_scale_rght_cat[tree->mod->ras->parent_class_number] =
            (b->sum_scale_rght) ? (b->sum_scale_rght[site]) : (0.0);

        sum = sum_scale_left_cat[tree->mod->ras->parent_class_number] +
              sum_scale_rght_cat[tree->mod->ras->parent_class_number];

        if (sum > 1024.)
        {
          /* PhyML_Fprintf(stderr,"\n. Numerical precision issue detected (sum =
           * %g)!!!",sum); */
          sum                                = 1023.;
          tree->mixt_tree->numerical_warning = YES;
          tree->numerical_warning            = YES;
        }

        tree->unscaled_site_lk_cat[site] = site_lk_cat;

        site_lk_cat /= pow(2, sum);

        tree->site_lk_cat[0] = site_lk_cat;

        tree = tree->next;
        b    = b->next;
      } while (tree && tree->is_mixt_tree == NO);
      // done with all trees in the mixture for this partition element.
      // All likelihood values are in site_lk_cat[0]

      tree    = mixt_tree->next;
      b       = mixt_b->next;
      site_lk = .0;

      do
      {
        if (tree->mod->ras->invar == YES)
        {
          tree = tree->next;
          b    = b->next;
          if (!(tree && tree->is_mixt_tree == NO)) break;
        }

        site_lk += tree->site_lk_cat[0] *
                   mixt_tree->mod->ras->gamma_r_proba
                       ->v[tree->mod->ras->parent_class_number] *
                   tree->mod->r_mat_weight->v / r_mat_weight_sum *
                   tree->mod->e_frq_weight->v / e_frq_weight_sum / sum_probas;

        tree = tree->next;
        b    = b->next;
      } while (tree && tree->is_mixt_tree == NO);
      

      /* Scaling for invariants */
      if (mixt_tree->mod->ras->invar == YES)
      {
        num_prec_issue = NO;

        tree = mixt_tree->next;
        while (tree->mod->ras->invar == NO)
        {
          tree = tree->next;
          if (tree == NULL || tree->is_mixt_tree == YES)
          {
            PhyML_Fprintf(stderr, "\n. tree: %p", tree);
            PhyML_Fprintf(stderr, "\n. Err in file %s at line %d", __FILE__,
                          __LINE__);
            Exit("\n");
          }
        }

        /*! 'tree' will give the correct state frequencies (as opposed to
         * mixt_tree) */
        inv_site_lk = Invariant_Lk(0, site, &num_prec_issue, tree);

        if (num_prec_issue == YES) // inv_site_lk >> site_lk
        {
          site_lk = inv_site_lk * mixt_tree->mod->ras->pinvar->v;
        }
        else
        {
          site_lk = site_lk * (1. - mixt_tree->mod->ras->pinvar->v) +
                    inv_site_lk * mixt_tree->mod->ras->pinvar->v;
        }
      }

      if (site_lk < SMALL)
      {
        /* PhyML_Fprintf(stderr,"\n. site = %d",site); */
        /* PhyML_Fprintf(stderr,"\n. invar = %d",mixt_tree->data->invar[site]);
         */
        /* PhyML_Fprintf(stderr,"\n. mixt = %d",mixt_tree->is_mixt_tree); */
        /* PhyML_Fprintf(stderr,"\n. lk = %G log(lk) = %f <
         * %G",site_lk,log_site_lk,-BIG); */
        /* for(class=0;class<mixt_tree->mod->ras->n_catg;class++)
         * PhyML_Fprintf(stderr,"\n. rr=%f
         * p=%f",mixt_tree->mod->ras->gamma_rr->v[class],mixt_tree->mod->ras->gamma_r_proba->v[class]);
         */
        /* PhyML_Fprintf(stderr,"\n. pinv = %G",mixt_tree->mod->ras->pinvar->v);
         */
        /* PhyML_Fprintf(stderr,"\n. bl mult =
         * %G",mixt_tree->mod->br_len_mult->v); */
        /* PhyML_Fprintf(stderr,"\n. Err. in file %s at line
         * %d.\n",__FILE__,__LINE__); */
        /* Exit("\n"); */

        site_lk                      = SMALL;
        mixt_tree->numerical_warning = YES;
      }

      log_site_lk = log(site_lk);

      // ... or using the log-likelihood
      if (isinf(site_lk) || isnan(site_lk))
      {
        mixt_tree->cur_site_lk[site] = exp(log_site_lk);
      }
      else
      {
        mixt_tree->cur_site_lk[site] = site_lk;
      }

      mixt_tree->c_lnL_sorted[site] = log_site_lk;

      mixt_tree->c_lnL += mixt_tree->data->wght[site] * log_site_lk;
    }

    Free(sum_scale_left_cat);
    Free(sum_scale_rght_cat);

    mixt_tree = mixt_tree->next_mixt;
    mixt_b    = mixt_b->next_mixt;
  } while (mixt_tree);

  mixt_tree = cpy_mixt_tree;
  mixt_b    = cpy_mixt_b;

  sum_lnL = .0;
  do
  {
    sum_lnL += mixt_tree->c_lnL;
    mixt_tree = mixt_tree->next_mixt;
  } while (mixt_tree);

  mixt_tree = cpy_mixt_tree;
  do
  {
    mixt_tree->c_lnL = sum_lnL;
    mixt_tree        = mixt_tree->next_mixt;
  } while (mixt_tree);

  mixt_tree = cpy_mixt_tree;

  return mixt_tree->c_lnL;
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Update_Partial_Lk(t_tree *mixt_tree, t_edge *mixt_b, t_node *mixt_d)
{
  t_tree *tree;
  t_edge *b;
  t_node *d;

  tree = mixt_tree;
  b    = mixt_b;
  d    = mixt_d;

  do
  {
    if (tree->is_mixt_tree)
    {
      tree = tree->next;
      b    = b->next;
      d    = d->next;
    }

    if (tree->mod->ras->invar == NO) Update_Partial_Lk(tree, b, d);

    tree = tree->next;
    b    = b->next;
    d    = d->next;

  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Update_Partial_Pars(t_tree *mixt_tree, t_edge *mixt_b, t_node *mixt_d)
{
  t_tree *tree;
  t_edge *b;
  t_node *d;

  tree = mixt_tree;
  b    = mixt_b;
  d    = mixt_d;

  do
  {
    if (tree->is_mixt_tree)
    {
      tree = tree->next;
      b    = b->next;
      d    = d->next;
    }

    Update_Partial_Pars(tree, b, d);

    tree = tree->next;
    b    = b->next;
    d    = d->next;

  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Update_PMat_At_Given_Edge(t_edge *mixt_b, t_tree *mixt_tree)
{
  t_tree *tree;
  t_edge *b;

  tree = mixt_tree;
  b    = mixt_b;

  do
  {
    if (tree->is_mixt_tree)
    {
      tree = tree->next;
      b    = b->next;
    }

    if (tree->mod->ras->invar == NO) Update_PMat_At_Given_Edge(b, tree);

    tree = tree->next;
    b    = b->next;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

int *MIXT_Get_Number_Of_Classes_In_All_Mixtures(t_tree *mixt_tree)
{
  int    *n_catg;
  t_tree *tree;
  int class;

  if (mixt_tree->is_mixt_tree == YES)
  {
    n_catg = NULL;
    tree   = mixt_tree;
    class  = 0;
    do
    {
      if (!class)
        n_catg = (int *)mCalloc(1, sizeof(int));
      else
        n_catg = (int *)realloc(n_catg, (class + 1) * sizeof(int));

      tree = tree->next;

      n_catg[class] = 0;
      do
      {
        n_catg[class]++;
        tree = tree->next;
      } while (tree && tree->is_mixt_tree == NO);

      class ++;
    } while (tree);
  }
  else
  {
    n_catg    = (int *)mCalloc(1, sizeof(int));
    n_catg[0] = mixt_tree->mod->ras->n_catg;
    if (mixt_tree->mod->ras->invar == YES) n_catg[0]++;
  }
  return (n_catg);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

t_tree **MIXT_Record_All_Mixtures(t_tree *mixt_tree)
{
  t_tree **tree_list;
  int      n_trees;
  t_tree  *tree;

  tree_list = NULL;
  n_trees   = 0;
  tree      = mixt_tree;
  do
  {
    if (!tree_list)
      tree_list = (t_tree **)mCalloc(1, sizeof(t_tree *));
    else
      tree_list =
          (t_tree **)realloc(tree_list, (n_trees + 1) * sizeof(t_tree *));

    tree_list[n_trees] = tree;
    n_trees++;
    tree = tree->next;
  } while (tree);

  tree_list = (t_tree **)realloc(tree_list, (n_trees + 1) * sizeof(t_tree *));
  tree_list[n_trees] = NULL;

  return (tree_list);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Break_All_Mixtures(int *c_max, t_tree *mixt_tree)
{
  t_tree *tree;
  int     c, i, n;

  if (mixt_tree->is_mixt_tree == NO) return;

  c    = 0;
  n    = -1;
  tree = mixt_tree;
  do
  {
    if (tree->is_mixt_tree == YES)
    {
      c = 0;
      n++;
      tree = tree->next;
    }

    if (c == (c_max[n] - 1) && tree->next != NULL &&
        tree->next->is_mixt_tree == NO)
    {
      if (tree->mixt_tree->next_mixt == NULL)
      {
        tree->next = NULL;
        for (i = 0; i < 2 * tree->n_otu - 1; ++i) tree->a_edges[i]->next = NULL;
        for (i = 0; i < 2 * tree->n_otu - 1; ++i) tree->a_nodes[i]->next = NULL;
        for (i = 0; i < 2 * tree->n_otu - 2; ++i)
          tree->spr_list_one_edge[i]->next = NULL;
      }
      else
      {
        tree->next = tree->mixt_tree->next_mixt;
        for (i = 0; i < 2 * tree->n_otu - 1; ++i)
          tree->a_edges[i]->next = tree->mixt_tree->next_mixt->a_edges[i];
        for (i = 0; i < 2 * tree->n_otu - 1; ++i)
          tree->a_nodes[i]->next = tree->mixt_tree->next_mixt->a_nodes[i];
        for (i = 0; i < 2 * tree->n_otu - 2; ++i)
          tree->spr_list_one_edge[i]->next =
              tree->mixt_tree->next_mixt->spr_list_one_edge[i];
      }
    }

    tree = tree->next;
    c++;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Reconnect_All_Mixtures(t_tree **tree_list, t_tree *mixt_tree)
{
  t_tree *tree;
  int     n_trees;

  if (mixt_tree->is_mixt_tree == NO) return;

  tree    = mixt_tree;
  n_trees = 0;
  do
  {
    tree = tree_list[n_trees];
    if (tree->is_mixt_tree == NO) tree->next = tree_list[n_trees + 1];
    n_trees++;
    tree = tree->next;
  } while (tree);

  MIXT_Chain_All(mixt_tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

int *MIXT_Record_Has_Invariants(t_tree *mixt_tree)
{
  int    *has_invariants;
  t_tree *tree;
  int     n_trees;

  has_invariants = NULL;
  tree           = mixt_tree;
  n_trees        = 0;
  do
  {
    if (!n_trees)
      has_invariants = (int *)mCalloc(1, sizeof(int));
    else
      has_invariants =
          (int *)realloc(has_invariants, (n_trees + 1) * sizeof(int));
    has_invariants[n_trees] = (tree->mod->ras->invar == YES) ? 1 : 0;
    n_trees++;
    tree = tree->next;
  } while (tree);

  return (has_invariants);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Reset_Has_Invariants(int *has_invariants, t_tree *mixt_tree)
{
  t_tree *tree;
  int     n_trees;

  tree    = mixt_tree;
  n_trees = 0;
  do
  {
    tree->mod->ras->invar = has_invariants[n_trees];
    n_trees++;
    tree = tree->next;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Check_Invar_Struct_In_Each_Partition_Elem(t_tree *mixt_tree)
{
  if (mixt_tree->is_mixt_tree == NO)
    return;
  else
  {
    t_tree *tree;
    int     n_inv;

    n_inv = 0;
    tree  = mixt_tree;
    do
    {
      if (tree->is_mixt_tree)
      {
        tree  = tree->next;
        n_inv = 0;
      }

      if (tree->mod->ras->invar == YES) n_inv++;

      if (n_inv > 1)
      {
        PhyML_Fprintf(
            stderr, "\n. Found %d classes of the mixture for file '%s' set to",
            n_inv, tree->mixt_tree->io->in_align_file);
        PhyML_Fprintf(
            stderr,
            "\n. invariable. Only one such class per mixture is allowed.");
        PhyML_Fprintf(stderr, "\n. Err. in file %s at line %d\n\n", __FILE__,
                      __LINE__);
        Warn_And_Exit("\n");
      }

      if (tree->mixt_tree->mod->ras->invar == NO &&
          tree->mod->ras->invar == YES)
      {
        PhyML_Fprintf(stderr,
                      "\n. Unexpected settings for 'siterates' in a partition "
                      "element (file '%s')",
                      tree->mixt_tree->io->in_align_file);
        PhyML_Fprintf(stderr, "\n. Err. in file %s at line %d\n\n", __FILE__,
                      __LINE__);
        Warn_And_Exit("\n");
      }

      tree = tree->next;
    } while (tree);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Check_RAS_Struct_In_Each_Partition_Elem(t_tree *mixt_tree)
{
  if (mixt_tree->is_mixt_tree == NO)
    return;
  else
  {
    t_tree *tree;
    int     n_classes;

    n_classes = 0;
    tree      = mixt_tree;
    do
    {
      if (tree->is_mixt_tree)
      {
        if (tree->mod->ras->invar == YES)
        {
          if (tree->next->mod->ras->invar == NO)
          {
            PhyML_Fprintf(
                stderr,
                "\n. The invariant site class has to be the first element in");
            PhyML_Fprintf(
                stderr,
                "\n. each <mixtureelem> component. Please amend you XML");
            PhyML_Fprintf(stderr, "\n. file accordingly.\n");
            Exit("\n.");
          }
        }
        tree      = tree->next;
        n_classes = 0;
      }

      if (tree && tree->mod->ras->invar == NO) n_classes++;

      if ((tree->next && tree->next->is_mixt_tree == YES) ||
          (!tree->next)) /*! current tree is the last element of this mixture */
      {
        if (n_classes < tree->mixt_tree->mod->ras->n_catg)
        {
          PhyML_Fprintf(
              stderr,
              "\n. %d class%s found in 'partitionelem' for file '%s' while",
              n_classes, (n_classes > 1) ? "es\0" : "\0",
              tree->mixt_tree->io->in_align_file);
          PhyML_Fprintf(
              stderr,
              "\n. the corresponding 'siterates' element defined %d class%s.",
              tree->mixt_tree->mod->ras->n_catg,
              (tree->mixt_tree->mod->ras->n_catg > 1) ? "es\0" : "\0");
          PhyML_Fprintf(stderr, "\n. Err. in file %s at line %d\n\n", __FILE__,
                        __LINE__);
          Warn_And_Exit("\n");
        }
      }

      tree = tree->next;
    } while (tree);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Prune_Subtree(t_node *mixt_a, t_node *mixt_d, t_edge **mixt_target,
                        t_edge **mixt_residual, t_tree *mixt_tree)
{
  t_node *a, *d;
  t_edge *target, *residual;
  t_tree *tree;

  MIXT_Turn_Branches_OnOff_In_One_Elem(OFF, mixt_tree);

  tree     = mixt_tree;
  a        = mixt_a;
  d        = mixt_d;
  target   = (mixt_target) ? (*mixt_target) : NULL;
  residual = (mixt_residual) ? (*mixt_residual) : NULL;

  do
  {
    if (tree->is_mixt_tree == YES)
    {
      tree     = tree->next;
      a        = a->next;
      d        = d->next;
      target   = target ? target->next : NULL;
      residual = residual ? residual->next : NULL;
    }

    Prune_Subtree(a, d, &target, &residual, tree);

    tree     = tree->next;
    a        = a->next;
    d        = d->next;
    target   = target ? target->next : NULL;
    residual = residual ? residual->next : NULL;
  } while (tree && tree->is_mixt_tree == NO);

  if (tree) Prune_Subtree(a, d, &target, &residual, tree);

  /*! Turn branches of this mixt_tree to ON after recursive call
    to Prune_Subtree such that, if branches of mixt_tree->next
    point to those of mixt_tree, they are set to OFF when calling
    Prune */
  MIXT_Turn_Branches_OnOff_In_One_Elem(ON, mixt_tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Graft_Subtree(t_edge *mixt_target, t_node *mixt_link,
                        t_node *mixt_link_daughter, t_edge *mixt_residual,
                        t_node *mixt_target_nd, t_tree *mixt_tree)
{
  t_edge *target, *residual;
  t_node *link, *link_daughter, *target_nd;
  t_tree *tree;

  MIXT_Turn_Branches_OnOff_In_One_Elem(OFF, mixt_tree);

  tree          = mixt_tree;
  target        = mixt_target;
  residual      = mixt_residual;
  link          = mixt_link;
  link_daughter = mixt_link_daughter;
  target_nd     = mixt_target_nd;

  do
  {
    if (tree->is_mixt_tree == YES)
    {
      tree          = tree->next;
      target        = target->next;
      residual      = residual->next;
      link          = link->next;
      link_daughter = link_daughter ? link_daughter->next : NULL;
      target_nd     = target_nd ? target_nd->next : NULL;
    }

    Graft_Subtree(target, link, link_daughter, residual, target_nd, tree);

    tree          = tree->next;
    target        = target->next;
    residual      = residual->next;
    link          = link->next;
    link_daughter = link_daughter ? link_daughter->next : NULL;
    target_nd     = target_nd ? target_nd->next : NULL;
  } while (tree && tree->is_mixt_tree == NO);

  if (tree)
    Graft_Subtree(target, link, link_daughter, residual, target_nd, tree);

  /*! Turn branches of this mixt_tree to ON after recursive call
    to Graft_Subtree such that, if branches of mixt_tree->next
    point to those of mixt_tree, they are set to OFF when calling
    Graft */
  MIXT_Turn_Branches_OnOff_In_One_Elem(ON, mixt_tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Multiply_Scalar_Dbl(scalar_dbl *this, phydbl scalar)
{
  if (this == NULL)
    return;
  else
  {
    scalar_dbl *buff;
    buff = this;
    do
    {
      buff->v *= scalar;
      buff = buff->next;
    } while (buff);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Set_Scalar_Dbl(scalar_dbl *this, phydbl value)
{
  if (this == NULL)
    return;
  else
  {
    scalar_dbl *buff;
    buff = this;
    do
    {
      buff->v = value;
      buff    = buff->next;
    } while (buff);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Br_Len_Opt(t_edge *mixt_b, t_tree *mixt_tree)
{
  t_edge     *b;
  scalar_dbl *l;

  MIXT_Turn_Branches_OnOff_In_All_Elem(ON, mixt_tree);

  mixt_tree->ignore_mixt_info = YES;

  b = mixt_b;
  l = b->l;

  do
  {
    if (l->onoff == ON) Br_Len_Opt(&(l->v), mixt_b, mixt_tree);
    l = l->next;
  } while (l);

  mixt_tree->ignore_mixt_info = NO;
}

/*////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////*/

void MIXT_Br_Len_Involving_Invar(t_tree *mixt_tree)
{
  int         i;
  scalar_dbl *l;

  for (i = 0; i < 2 * mixt_tree->n_otu - 1; ++i)
  {
    l = mixt_tree->a_edges[i]->l;
    do
    {
      l->v *= (1. - mixt_tree->mod->ras->pinvar->v);
      l = l->next;
    } while (l);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Br_Len_Not_Involving_Invar(t_tree *mixt_tree)
{
  int         i;
  scalar_dbl *l;

  for (i = 0; i < 2 * mixt_tree->n_otu - 1; ++i)
  {
    l = mixt_tree->a_edges[i]->l;
    do
    {
      l->v /= (1. - mixt_tree->mod->ras->pinvar->v);
      l = l->next;
    } while (l);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

phydbl MIXT_Unscale_Br_Len_Multiplier_Tree(t_tree *mixt_tree)
{
  int         i;
  scalar_dbl *l;

  for (i = 0; i < 2 * mixt_tree->n_otu - 1; ++i)
  {
    l = mixt_tree->a_edges[i]->l;
    do
    {
      l->v /= mixt_tree->mod->br_len_mult->v;
      l = l->next;
    } while (l);
  }
  return (-1);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

phydbl MIXT_Rescale_Br_Len_Multiplier_Tree(t_tree *mixt_tree)
{
  int         i;
  scalar_dbl *l;

  for (i = 0; i < 2 * mixt_tree->n_otu - 1; ++i)
  {
    l = mixt_tree->a_edges[i]->l;
    do
    {
      l->v *= mixt_tree->mod->br_len_mult->v;
      l = l->next;
    } while (l);
  }
  return (-1);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

phydbl MIXT_Rescale_Free_Rate_Tree(t_tree *mixt_tree)
{
  int     i, side_effect, at_boundary;
  t_edge *b;

  side_effect = NO;
  for (i = 0; i < 2 * mixt_tree->n_otu - 1; ++i)
  {
    b = mixt_tree->a_edges[i]->next;

    at_boundary = NO;
    if (b->l->v > mixt_tree->mod->l_max - 1.E-100 &&
        b->l->v < mixt_tree->mod->l_max + 1.E-100)
      at_boundary = YES;
    if (b->l->v > mixt_tree->mod->l_min - 1.E-100 &&
        b->l->v < mixt_tree->mod->l_min + 1.E-100)
      at_boundary = YES;

    b->l->v *= mixt_tree->mod->ras->free_rate_mr->v;

    if (b->l->v > mixt_tree->mod->l_max && at_boundary == NO) side_effect = YES;
    if (b->l->v < mixt_tree->mod->l_min && at_boundary == NO) side_effect = YES;
  }

  return side_effect;
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Set_Ignore_Root(int yesno, t_tree *mixt_tree)
{
  t_tree *tree;

  tree = mixt_tree->next;

  if (tree != NULL)
  {
    do
    {
      Set_Ignore_Root(yesno, tree);
      tree = tree->next;
    } while (tree);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Set_Alias_Subpatt(int onoff, t_tree *mixt_tree)
{
  t_tree *tree;

  tree = mixt_tree;
  do
  {
    tree->update_alias_subpatt = onoff;
    tree                       = tree->next;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Check_Edge_Lens_In_All_Elem(t_tree *mixt_tree)
{
  t_tree *tree;

  /*! Check that all the edges in a mixt_tree at pointing
    to a single set of lengths
  */
  tree = mixt_tree;
  do
  {
    MIXT_Check_Edge_Lens_In_One_Elem(tree);
    tree = tree->next_mixt;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Check_Edge_Lens_In_One_Elem(t_tree *mixt_tree)
{
  t_tree *tree;
  int     i;

  tree = mixt_tree->next;
  do
  {
    if (tree->next && tree->next->is_mixt_tree == NO)
    {
      for (i = 0; i < 2 * tree->n_otu - 1; ++i)
      {
        if (tree->a_edges[i]->l != tree->next->a_edges[i]->l)
        {
          PhyML_Fprintf(stderr, "\n. %p %p", tree->a_edges[i]->l,
                        tree->next->a_edges[i]->l);
          PhyML_Fprintf(stderr, "\n. Only one set of edge lengths is allowed ");
          PhyML_Fprintf(stderr,
                        "\n. in a 'partitionelem'. Please fix your XML file.");
          Exit("\n");
        }
      }
    }
    tree = tree->next;
  } while (tree && tree->next && tree->next->is_mixt_tree == NO);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

int MIXT_Pars(t_edge *mixt_b, t_tree *mixt_tree)
{
  t_edge *b;
  t_tree *tree;

  b                 = mixt_b;
  tree              = mixt_tree;
  mixt_tree->c_pars = 0;

  do
  {
    if (tree->next)
    {
      Pars(b ? b->next : NULL, tree->next);
      mixt_tree->c_pars += tree->next->c_pars;
    }

    if (mixt_b != NULL) b = b->next_mixt;
    tree = tree->next_mixt;
  } while (tree);

  tree = mixt_tree;
  do
  {
    tree->c_pars = mixt_tree->c_pars;
    tree         = tree->next_mixt;
  } while (tree);

  return (mixt_tree->c_pars);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Bootstrap(char *best_tree, xml_node *root)
{
  xml_node *n, *p_elem;
  char     *bootstrap;

  assert(root);

  n = XML_Search_Node_Name("phyml", NO, root);

  bootstrap = XML_Get_Attribute_Value(n, "bootstrap");

  if (!bootstrap)
    return;
  else
  {
    int       n_boot, i, j, k;
    xml_attr *boot_attr, *seqfile_attr, *out_attr, *boot_out_attr;
    char     *orig_align, *boot_out_file_name, *xml_boot_file_name, *buff;
    FILE     *boot_fp_in_align, *xml_boot_file_fp;
    option   *io, *dum;
    align   **boot_data, **orig_data;
    int       position, elem;
    xml_node *boot_root;
    int       pid;
    char     *s;
    
    orig_align = (char *)mCalloc(T_MAX_NAME, sizeof(char));

    xml_boot_file_name = (char *)mCalloc(T_MAX_NAME, sizeof(char));
    strcpy(xml_boot_file_name, "phyml_boot_config.");
    pid = (int)getpid();
    sprintf(xml_boot_file_name + strlen(xml_boot_file_name), "%d", pid);
    strcat(xml_boot_file_name, ".xml");

    out_attr = XML_Search_Attribute(root, "output.file");
    assert(out_attr);
    boot_out_file_name = (char *)mCalloc(T_MAX_NAME, sizeof(char));
    strcpy(boot_out_file_name, out_attr->value);
    s = XML_Get_Attribute_Value(root, "run.id");
    if (s)
    {
      strcat(boot_out_file_name, "_");
      strcat(boot_out_file_name, s);
    }

    n_boot = atoi(bootstrap);

    io = NULL;
    for (i = 0; i < n_boot; ++i)
    {
      boot_root = XML_Copy_XML_Graph(root);

      /*! Set the number of bootstrap repeats to 0
        in each generated XML file */
      boot_attr = XML_Search_Attribute(boot_root, "bootstrap");
      assert(boot_attr);
      strcpy(boot_attr->value, "0");

      /*! Set the output file name for each bootstrap analysis */
      boot_out_attr = XML_Search_Attribute(boot_root, "output.file");
      assert(boot_out_attr);
      buff = (char *)mCalloc(T_MAX_NAME, sizeof(char));
      strcpy(buff, boot_out_attr->value);
      Free(boot_out_attr->value);
      boot_out_attr->value = buff;
      sprintf(boot_out_attr->value + strlen(boot_out_attr->value), "_boot.%d",
              pid);

      XML_Add_Attribute(boot_root, "add", "true"); // Add all trees in bootstrap outut tree file 

      p_elem = boot_root;
      elem   = 0;
      do
      {
        p_elem = XML_Search_Node_Name("partitionelem", YES, p_elem);
        if (!p_elem) break;

        io = (option *)Make_Input();
        Set_Defaults_Input(io);

        /*! Get the original sequence file name and the corresponding
          attribute in the XML graph
        */
        seqfile_attr = NULL;
        seqfile_attr = XML_Search_Attribute(p_elem, "file.name");
        assert(seqfile_attr);

        strcpy(orig_align, seqfile_attr->value);

        /*! Open the original sequence file */
        io->fp_in_align = Openfile(orig_align, 0);

        /*! Read in the original sequence file */
        orig_data = Get_Seq(io);
        rewind(io->fp_in_align);

        /*! Read in the original sequence file and put
         it in 'boot_data' structure */
        boot_data = Get_Seq(io);

        fclose(io->fp_in_align);

        /*! Bootstrap resampling: sample from original and put in boot */
        for (j = 0; j < boot_data[0]->len; ++j)
        {
          position = Rand_Int(0, (int)(boot_data[0]->len - 1.0));
          for (k = 0; k < io->n_otu; ++k)
            boot_data[k]->state[j] = orig_data[k]->state[position];
        }

        /*! Modify the sequence file attribute in the original XML
          graph */
        buff = (char *)mCalloc(T_MAX_NAME, sizeof(char));
        Free(seqfile_attr->value);
        seqfile_attr->value = buff;
        sprintf(seqfile_attr->value, "%s_%d_%d", orig_align, elem, i);

        /*! Open a new sequence file with the modified attribute name */
        boot_fp_in_align = Openfile(seqfile_attr->value, 1);

        /*! Print the bootstrap data set in it */
        Print_Seq(boot_fp_in_align, boot_data, io->n_otu);
        fclose(boot_fp_in_align);

        Free_Seq(orig_data, io->n_otu);
        Free_Seq(boot_data, io->n_otu);

        Free_Input(io);
        elem++;
      } while (p_elem);

      /*! Open bootstrap XML file in writing mode */
      xml_boot_file_fp = Openfile(xml_boot_file_name, 1);

      /*! Write the bootstrap XML graph */
      XML_Write_XML_Graph(xml_boot_file_fp, boot_root);
      fclose(xml_boot_file_fp);

      /*! Reconstruct the tree */
      dum = PhyML_XML(xml_boot_file_name);
      Free(dum);

      /*! Remove the bootstrap alignment files */
      p_elem = boot_root;
      do
      {
        p_elem = XML_Search_Node_Name("partitionelem", YES, p_elem);
        if (!p_elem) break;
        seqfile_attr = XML_Search_Attribute(p_elem, "file.name");
        unlink(seqfile_attr->value);
      } while (p_elem);

      XML_Free_XML_Tree(boot_root);
    }

    Free(xml_boot_file_name);
    Free(orig_align);
    Free(boot_out_file_name);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Set_Pars_Thresh(t_tree *mixt_tree)
{
  t_tree *tree;

  tree = mixt_tree;
  do
  {
    tree->mod->s_opt->pars_thresh = (tree->io->datatype == AA) ? (15) : (5);
    tree                          = tree->next_mixt;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

phydbl MIXT_Get_Mean_Edge_Len(t_edge *mixt_b, t_tree *mixt_tree)
{
  phydbl  sum;
  int     n;
  t_tree *tree;
  t_edge *b;

  if (mixt_tree->is_mixt_tree == NO) return mixt_b->l->v;

  b    = mixt_b;
  tree = mixt_tree;
  sum  = .0;
  n    = 0;
  do
  {
    if (tree->is_mixt_tree == YES)
    {
      tree = tree->next;
      b    = b->next;
    }

    sum += b->l->v *
           (tree->mixt_tree ? tree->mixt_tree->mod->br_len_mult->v : 1.0);
    n++;
    b    = b->next;
    tree = tree->next;
  } while (b);

  return (sum / (phydbl)n);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

phydbl MIXT_Get_Sum_Chained_Scalar_Dbl(scalar_dbl *s)
{
  scalar_dbl *s_buff;
  phydbl      sum;

  s_buff = s;
  sum    = .0;
  do
  {
    sum += s_buff->v;
    s_buff = s_buff->next;
  } while (s_buff);

  return sum;
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

phydbl MIXT_Get_Sum_Of_Probas_Across_Mixtures(phydbl  r_mat_weight_sum,
                                              phydbl  e_frq_weight_sum,
                                              t_tree *mixt_tree)
{
  t_tree *tree;
  phydbl  sum;

  sum  = .0;
  tree = mixt_tree->next;
  do
  {
    // e.g., if mixture has two classes, one of these
    // corresponding to invariable sites. We need to skip it.
    if (tree->mod->ras->invar == YES) tree = tree->next;

    sum += mixt_tree->mod->ras->gamma_r_proba
               ->v[tree->mod->ras->parent_class_number] *
           tree->mod->r_mat_weight->v / r_mat_weight_sum *
           tree->mod->e_frq_weight->v / e_frq_weight_sum;

    tree = tree->next;

  } while (tree && tree->is_mixt_tree == NO);

  return (sum);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Set_Br_Len_Var(t_edge *mixt_b, t_tree *mixt_tree)
{
  t_tree *tree;

  if (mixt_b != NULL)
  {
    t_edge *b;

    tree = mixt_tree->next;
    b    = mixt_b->next;

    do
    {
      Set_Br_Len_Var(b, tree);
      tree = tree->next;
      b    = b->next;
    } while (tree);
  }
  else
  {
    tree = mixt_tree->next;

    do
    {
      Set_Br_Len_Var(NULL, tree);
      tree = tree->next;
    } while (tree);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Set_Br_Len(phydbl val, t_edge *mixt_b, t_tree *mixt_tree)
{
  scalar_dbl *l;

  l = mixt_b->l;
  do
  {
    l->v = val;
    l    = l->next;
  } while (l);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Add_Root(t_edge *mixt_b, t_tree *mixt_tree)
{
  t_tree *tree;
  t_edge *b;

  tree = mixt_tree;
  b    = mixt_b;
  do
  {
    if (tree->is_mixt_tree)
    {
      tree = tree->next;
      b    = b->next;
    }

    // Condition is true when tree is not chained
    if (b == NULL) break;

    Add_Root(b, tree);

    tree = tree->next;
    b    = b->next;
  } while (tree);

  tree = mixt_tree;
  do
  {
    assert(tree->n_root != NULL);

    if (tree->next) tree->n_root->next = tree->next->n_root;
    if (tree->prev) tree->n_root->prev = tree->prev->n_root;
    if (tree->next_mixt) tree->n_root->next_mixt = tree->next_mixt->n_root;
    if (tree->prev_mixt) tree->n_root->prev_mixt = tree->prev_mixt->n_root;

    tree = tree->next;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_RATES_Update_Edge_Lengths(t_tree *mixt_tree)
{
  t_tree *tree;

  tree = mixt_tree->next;
  do
  {
    RATES_Update_Edge_Lengths(tree);
    tree = tree->next;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_RATES_Update_One_Edge_Length(t_edge *mixt_b, t_tree *mixt_tree)
{
  t_tree *tree;
  t_edge *b;

  b = mixt_tree->next->a_edges[mixt_b->num];

  tree = mixt_tree->next;
  do
  {
    RATES_Update_One_Edge_Length(b, tree);
    tree = tree->next;
    if (tree != NULL) b = tree->a_edges[mixt_b->num];
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Update_Br_Len_Multipliers(t_mod *mod)
{
  phydbl sum;
  t_mod *loc;
  int    n_mixt;

  loc    = mod;
  sum    = 0.0;
  n_mixt = 0;
  do
  {
    /* if(loc->s_opt->opt_br_len_mult == YES) */
    /*   { */
    sum += loc->br_len_mult_unscaled->v;
    n_mixt++;
    /* } */
    loc = loc->next_mixt;
  } while (loc);

  loc = mod;
  do
  {
    if (loc->s_opt->opt_br_len_mult == YES)
    {
      loc->br_len_mult->v = loc->br_len_mult_unscaled->v / sum;
      loc->br_len_mult->v *= (phydbl)(n_mixt);
      /* printf("\n. HERE %f
       * %f\n",loc->br_len_mult_unscaled->v,loc->br_len_mult->v); */
    }
    loc = loc->next_mixt;
  } while (loc);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Init_Model(t_mod *mod)
{
  mod = mod->next;

  if (mod != NULL)
  {
    do
    {
      Init_Model(mod->io->cdata, mod, mod->io);
      if (mod->is_mixt_mod == YES) return;
      mod = mod->next;
    } while (mod);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Check_Model_Validity(t_tree *mixt_tree)
{
  // Verify that models associated to distinct data partition elements do not
  // share the same empirical character frequencies.

  t_mod *mod_in, *mod_out;

  mod_out = mixt_tree->mod;

  do
  {
    mod_in = mod_out;
    do
    {
      if (mod_in->io->cdata != mod_out->io->cdata)
      {
        if (mod_in->e_frq == mod_out->e_frq)
        {
          if (mod_in->io->datatype == NT && mod_in->e_frq->type != USER &&
              mod_in->whichmodel != JC69 && mod_in->whichmodel != K80)
          {
            PhyML_Fprintf(stderr, "\n. A vector of observed nucleotide "
                                  "frequencies should correspond ");
            PhyML_Fprintf(stderr, "\n. to one data set only. If you are using "
                                  "the XML interface, ");
            PhyML_Fprintf(stderr, "\n. please amend your file accordingly.");
            Exit("\n");
          }
          else if (mod_in->io->datatype == AA &&
                   mod_in->e_frq->type == EMPIRICAL)
          {
            PhyML_Fprintf(stderr, "\n. A vector of observed amino-acid "
                                  "frequencies should correspond ");
            PhyML_Fprintf(stderr, "\n. to one data set only. If you are using "
                                  "the XML interface, ");
            PhyML_Fprintf(stderr, "\n. please amend your file accordingly.");
            Exit("\n");
          }
        }
      }
      mod_in = mod_in->next;
    } while (mod_in);
    mod_out = mod_out->next;
  } while (mod_out);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

t_tree *MIXT_Starting_Tree(t_tree *mixt_tree)
{
  t_tree *tree;

  tree = NULL;

  if (mixt_tree->io->mod->s_opt->random_input_tree == NO)
  {
    switch (mixt_tree->io->in_tree)
    {
    case 2: // user-defined input tree
    {
      assert(mixt_tree->io->fp_in_tree);

      tree =
          Read_User_Tree(mixt_tree->io->cdata, mixt_tree->mod, mixt_tree->io);

      break;
    }
    case 1:
    case 0:
    {
      // Build a BioNJ tree from the analysis of
      // the first partition element
      tree = Dist_And_BioNJ(mixt_tree->data, mixt_tree->mod, mixt_tree->io);
      break;
    }
    default:
      assert(FALSE);
    }
  }
  else
  {
    tree = Make_Tree_From_Scratch(mixt_tree->n_otu, mixt_tree->data);
    Random_Tree(tree);
  }

  return tree;
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Connect_Cseqs_To_Nodes(t_tree *mixt_tree)
{
  t_tree *tree;

  tree = mixt_tree->next;

  if (tree != NULL)
  {
    do
    {
      Connect_CSeqs_To_Nodes(tree->data, mixt_tree->io, tree);
      tree = tree->next;
    } while (tree);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Init_T_Beg(t_tree *mixt_tree)
{
  t_tree *tree;

  /*! Initialize t_beg in each mixture tree */
  tree = mixt_tree->next_mixt;
  if (tree != NULL) Init_T_Beg(tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Init_T_End(t_tree *mixt_tree)
{
  t_tree *tree;

  /*! Initialize t_beg in each mixture tree */
  tree = mixt_tree;
  do
  {
    time(&(tree->t_current));
    tree = tree->next_mixt;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Prepare_All(int num_rand_tree, t_tree *mixt_tree)
{
  t_tree *tree;

  MIXT_Check_Model_Validity(mixt_tree);
  Init_Model(mixt_tree->mod->io->cdata, mixt_tree->mod, mixt_tree->mod->io);
  Set_Model_Parameters(mixt_tree->mod);
  Print_Data_Structure(NO, stdout, mixt_tree);
  tree = MIXT_Starting_Tree(mixt_tree);
  Copy_Tree(tree, mixt_tree);
  Free_Tree(tree);

  if (mixt_tree->io->mod->s_opt->random_input_tree)
  {
    PhyML_Printf("\n\n. [%3d/%3d]", num_rand_tree + 1,
                 mixt_tree->io->mod->s_opt->n_rand_starts);
    Random_Tree(mixt_tree);
  }

  MIXT_Connect_Cseqs_To_Nodes(mixt_tree);
  MIXT_Init_T_Beg(mixt_tree);

  MIXT_Make_Tree_For_Pars(mixt_tree);
  Make_Tree_For_Lk(mixt_tree);
  Make_Spr(mixt_tree);

  MIXT_Chain_All(mixt_tree);
  MIXT_Check_Edge_Lens_In_All_Elem(mixt_tree);
  MIXT_Turn_Branches_OnOff_In_All_Elem(ON, mixt_tree);
  MIXT_Check_Invar_Struct_In_Each_Partition_Elem(mixt_tree);
  MIXT_Check_RAS_Struct_In_Each_Partition_Elem(mixt_tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Make_Tree_For_Pars(t_tree *mixt_tree)
{
  t_tree *tree;

  tree = mixt_tree;
  do
  {
    if (tree->is_mixt_tree == NO) Make_Tree_For_Pars(tree);
    tree = tree->next;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Make_Tree_For_Lk(t_tree *mixt_tree)
{
  t_tree *tree;

  tree = mixt_tree;
  do
  {
    if (tree->is_mixt_tree == NO)
      Make_Tree_For_Lk(tree);
    else
    {
      tree->c_lnL_sorted = (phydbl *)mCalloc(tree->data->n_pattern, sizeof(phydbl));
      tree->cur_site_lk  = (phydbl *)mCalloc(tree->data->n_pattern, sizeof(phydbl));
      tree->old_site_lk  = (phydbl *)mCalloc(tree->data->n_pattern, sizeof(phydbl));
      tree->site_lk_cat  = (phydbl *)mCalloc(
          MAX(tree->mod->ras->n_catg, tree->mod->n_mixt_classes),
          sizeof(phydbl));
      tree->unscaled_site_lk_cat = (phydbl *)mCalloc(
          MAX(tree->mod->ras->n_catg, tree->mod->n_mixt_classes) *
              tree->data->n_pattern,
          sizeof(phydbl));
      tree->fact_sum_scale = (int *)mCalloc(tree->data->n_pattern, sizeof(int));

#if (defined(__AVX__) || defined(__SSE3__))
#ifndef WIN32
      if (posix_memalign((void **)&tree->expl, BYTE_ALIGN,
                         (size_t)2 * tree->mod->n_mixt_classes * tree->mod->ns *
                             sizeof(phydbl)))
        Generic_Exit(__FILE__, __LINE__, __FUNCTION__);
#else
      tree->expl = _aligned_malloc(2 * tree->mod->n_mixt_classes *
                                       tree->mod->ns * sizeof(phydbl),
                                   BYTE_ALIGN);
#endif
#else
      tree->expl = (phydbl *)mCalloc(
          2 * tree->mod->n_mixt_classes * tree->mod->ns, sizeof(phydbl));
#endif

      for (int i = 0; i < 2 * tree->n_otu - 1; ++i)
        Make_Edge_NNI(tree->a_edges[i]);

      tree->log_lks_aLRT = (phydbl **)mCalloc(3, sizeof(phydbl *));
      for (int i = 0; i < 3; i++)
        tree->log_lks_aLRT[i] =
            (phydbl *)mCalloc(tree->data->init_len, sizeof(phydbl));
    }

    tree = tree->next;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Init_Partial_Lk_Tips_Double(t_tree *mixt_tree)
{
  t_tree *tree;

  tree = mixt_tree->next;
  do
  {
    Init_Partial_Lk_Tips_Double(tree);
    tree = tree->next;
    if (tree != NULL && tree->is_mixt_tree == YES) tree = tree->next;
  } while (tree != NULL);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Init_Partial_Lk_Tips_Double_One_Character(int node_idx, int curr_site,
                                                    t_tree *mixt_tree)
{
  t_tree *tree;

  tree = mixt_tree->next;
  do
  {
    Init_Partial_Lk_Tips_Double_One_Character(node_idx, curr_site,tree);
    tree = tree->next;
    if (tree != NULL && tree->is_mixt_tree == YES) tree = tree->next;
  } while (tree != NULL);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Init_Partial_Lk_Tips_Double_One_Site(int site, t_tree *mixt_tree)
{
  t_tree *tree;

  tree = mixt_tree->next;
  do
  {
    Init_Partial_Lk_Tips_Double_One_Site(site, tree);
    tree = tree->next;
    if (tree != NULL && tree->is_mixt_tree == YES) tree = tree->next;
  } while (tree != NULL);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Ancestral_Sequences_One_Node(t_node *mixt_d, t_tree *mixt_tree,
                                       int print)
{
  if (mixt_d->tax)
    return;
  else
  {
    t_node *v0, *v1, *v2; // three neighbours of d
    t_edge *b0, *b1, *b2;
    int     i, j;
    int     catg;
    phydbl  p0, p1, p2;
    phydbl *p;
    t_node *d, *curr_mixt_d;
    t_tree *tree, *curr_mixt_tree;
    int     site, csite;
    phydbl *p_lk0, *p_lk1, *p_lk2;
    int    *sum_scale0, *sum_scale1, *sum_scale2;
    phydbl  r_mat_weight_sum, e_frq_weight_sum, sum_probas;
    phydbl *Pij0, *Pij1, *Pij2;
    int     NsNs, Ns, NsNg;
    FILE   *fp;

    if (!mixt_d) return;

    curr_mixt_tree = mixt_tree;
    curr_mixt_d    = mixt_d;
    fp             = mixt_tree->io->fp_out_ancestral_seq;

    do /* For each partition element */
    {
      if (curr_mixt_tree->next)
      {
        r_mat_weight_sum = MIXT_Get_Sum_Chained_Scalar_Dbl(
            curr_mixt_tree->next->mod->r_mat_weight);
        e_frq_weight_sum = MIXT_Get_Sum_Chained_Scalar_Dbl(
            curr_mixt_tree->next->mod->e_frq_weight);
        sum_probas = MIXT_Get_Sum_Of_Probas_Across_Mixtures(
            r_mat_weight_sum, e_frq_weight_sum, curr_mixt_tree);
      }
      else
      {
        r_mat_weight_sum = 1.;
        e_frq_weight_sum = 1.;
        sum_probas       = 1.;
      }

      Ns   = curr_mixt_tree->next ? curr_mixt_tree->next->mod->ns
                                  : curr_mixt_tree->mod->ns;
      NsNs = Ns * Ns;
      NsNg = Ns * curr_mixt_tree->mod->ras->n_catg;

      p = (phydbl *)mCalloc(Ns, sizeof(phydbl));

      /* for(site=0;site<curr_mixt_tree->n_pattern;site++) // For each site in
       * the current partition element */
      for (site = 0; site < curr_mixt_tree->data->init_len;
           site++) // For each site in the current partition element
      {
        csite = curr_mixt_tree->data->sitepatt[site];

        d    = curr_mixt_d->next ? curr_mixt_d->next : curr_mixt_d;
        tree = curr_mixt_tree->next ? curr_mixt_tree->next : curr_mixt_tree;

        for (i = 0; i < tree->mod->ns; i++) p[i] = .0;

        do // For each class of the mixture model that applies to the current
           // partition element
        {
          if (tree->is_mixt_tree == YES)
          {
            tree = tree->next;
            d    = d->next;
          }

          v0 = d->v[0];
          v1 = d->v[1];
          v2 = d->v[2];

          b0 = d->b[0];
          b1 = d->b[1];
          b2 = d->b[2];

          Pij0 = b0->Pij_rr;
          Pij1 = b1->Pij_rr;
          Pij2 = b2->Pij_rr;

          if (v0 == b0->left)
          {
            p_lk0      = b0->p_lk_left;
            sum_scale0 = b0->sum_scale_left;
          }
          else
          {
            p_lk0      = b0->p_lk_rght;
            sum_scale0 = b0->sum_scale_rght;
          }

          if (v1 == b1->left)
          {
            p_lk1      = b1->p_lk_left;
            sum_scale1 = b1->sum_scale_left;
          }
          else
          {
            p_lk1      = b1->p_lk_rght;
            sum_scale1 = b1->sum_scale_rght;
          }

          if (v2 == b2->left)
          {
            p_lk2      = b2->p_lk_left;
            sum_scale2 = b2->sum_scale_left;
          }
          else
          {
            p_lk2      = b2->p_lk_rght;
            sum_scale2 = b2->sum_scale_rght;
          }

          for (catg = 0; catg < tree->mod->ras->n_catg; catg++)
          {
            for (i = 0; i < Ns; i++)
            {
              p0 = .0;
              if (v0->tax)
                for (j = 0; j < tree->mod->ns; j++)
                {
                  p0 += v0->b[0]->p_lk_tip_r[csite * Ns + j] *
                        Pij0[catg * NsNs + i * Ns + j];

                  /* printf("\n. p0 %d %f", */
                  /*        v0->b[0]->p_lk_tip_r[site*Ns+j], */
                  /*        Pij0[catg*NsNs+i*Ns+j]); */
                }
              else
                for (j = 0; j < tree->mod->ns; j++)
                {
                  p0 +=
                      p_lk0[csite * NsNg + catg * Ns + j] *
                      Pij0[catg * NsNs + i * Ns + j] /
                      (phydbl)POW(
                          2,
                          sum_scale0[catg * curr_mixt_tree->data->n_pattern + csite]);

                  /* p0 += p_lk0[site*NsNg+catg*Ns+j] * Pij0[catg*NsNs+i*Ns+j];
                   */

                  /* printf("\n. p0 %f %f", */
                  /*        p_lk0[site*NsNg+catg*Ns+j], */
                  /*        Pij0[catg*NsNs+i*Ns+j]); */
                }
              p1 = .0;
              if (v1->tax)
                for (j = 0; j < tree->mod->ns; j++)
                {
                  p1 += v1->b[0]->p_lk_tip_r[csite * Ns + j] *
                        Pij1[catg * NsNs + i * Ns + j];

                  /* printf("\n. p1 %d %f", */
                  /*        v1->b[0]->p_lk_tip_r[site*Ns+j], */
                  /*        Pij1[catg*NsNs+i*Ns+j]); */
                }

              else
                for (j = 0; j < tree->mod->ns; j++)
                {
                  p1 +=
                      p_lk1[csite * NsNg + catg * Ns + j] *
                      Pij1[catg * NsNs + i * Ns + j] /
                      (phydbl)POW(
                          2,
                          sum_scale1[catg * curr_mixt_tree->data->n_pattern + csite]);

                  /* p1 += p_lk1[site*NsNg+catg*Ns+j] * Pij1[catg*NsNs+i*Ns+j];
                   */

                  /* printf("\n. p1 %f %f", */
                  /*        p_lk1[site*NsNg+catg*Ns+j], */
                  /*        Pij1[catg*NsNs+i*Ns+j]); */
                }

              p2 = .0;
              if (v2->tax)
                for (j = 0; j < tree->mod->ns; j++)
                {
                  p2 += v2->b[0]->p_lk_tip_r[csite * Ns + j] *
                        Pij2[catg * NsNs + i * Ns + j];
                  /* printf("\n. p2 %d %f", */
                  /*        v2->b[0]->p_lk_tip_r[site*Ns+j], */
                  /*        Pij2[catg*NsNs+i*Ns+j]); */
                }
              else
                for (j = 0; j < tree->mod->ns; j++)
                {
                  p2 +=
                      p_lk2[csite * NsNg + catg * Ns + j] *
                      Pij2[catg * NsNs + i * Ns + j] /
                      (phydbl)POW(
                          2,
                          sum_scale2[catg * curr_mixt_tree->data->n_pattern + csite]);

                  /* p2 += p_lk2[site*NsNg+catg*Ns+j] * Pij2[catg*NsNs+i*Ns+j];
                   */

                  /* printf("\n. p2 %f %f", */
                  /*        p_lk2[site*NsNg+catg*Ns+j], */
                  /*        Pij2[catg*NsNs+i*Ns+j]);  */
                }

              p[i] += p0 * p1 * p2 * tree->mod->e_frq->pi->v[i] /
                      tree->cur_site_lk[csite] *
                      curr_mixt_tree->mod->ras->gamma_r_proba
                          ->v[tree->mod->ras->parent_class_number] *
                      tree->mod->r_mat_weight->v / r_mat_weight_sum *
                      tree->mod->e_frq_weight->v / e_frq_weight_sum /
                      sum_probas;

              if (print == YES)
                printf("\n class: %d prob: %f",
                       tree->mod->ras->parent_class_number,
                       curr_mixt_tree->mod->ras->gamma_r_proba
                           ->v[tree->mod->ras->parent_class_number]);
            }
          }

          if (print == YES)
          {
            PhyML_Fprintf(fp, "%4d\t%4d\t", site + 1, d->num);
            for (i = 0; i < Ns; i++)
            {
              PhyML_Fprintf(fp, "%.4f\t", p[i]);
            }
            PhyML_Fprintf(fp, "\n");
            fflush(NULL);
          }

          tree = tree->next;
          d    = d->next;

        } while (tree && d && tree->is_mixt_tree == NO);
      }

      Free(p);
      curr_mixt_tree = curr_mixt_tree->next_mixt;
      curr_mixt_d    = curr_mixt_d->next_mixt;
    } while (curr_mixt_tree != NULL);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

// First and second derivative of the log-likelihood with respect
// to the length of edge b
phydbl MIXT_dLk(phydbl *l, t_edge *mixt_b, t_tree *mixt_tree)
{
  t_tree      *tree, *cpy_mixt_tree;
  t_edge      *b, *cpy_mixt_b;
  unsigned int site, class, nclasses, ns, state;
  phydbl      *sum_scale_left_cat, *sum_scale_rght_cat;
  phydbl       sum, mult;
  phydbl      *lk, *dlk;
  phydbl       site_lk, site_dlk;
  phydbl       log_site_lk, inv_site_lk;
  int          num_prec_issue;
  phydbl       r_mat_weight_sum, e_frq_weight_sum, sum_probas;
  phydbl       len, var;
  phydbl      *expl, *dot_prod;
  phydbl       rr;
  phydbl       ev, expevlen;
  phydbl       one_m_pinv;
  short int    length_found;

  tree          = NULL;
  b             = NULL;
  expl          = NULL;
  lk            = NULL;
  dlk           = NULL;
  cpy_mixt_tree = mixt_tree;
  cpy_mixt_b    = mixt_b;
  len           = -1.;

  if (mixt_tree->update_eigen_lr == YES)
    MIXT_Update_Eigen_Lr(mixt_b, mixt_tree);

  /*! Make sure that l is one of the lengths of mixt_b */
  b = mixt_b;
  while (b)
  {
    if (&(b->l->v) == l) break;
    b = b->next;
  }
  assert(b != NULL);

  do /*! Consider each element of the data partition */
  {
    b            = mixt_b;
    tree         = mixt_tree;
    length_found = NO;
    do
    {
      if (&(b->l->v) == l)
      {
        length_found = YES;
        break;
      }

      b    = b->next;
      tree = tree->next;
    } while (tree && tree->is_mixt_tree == NO);

    /*! For computational efficiency, the dlk and lk computation for
      data partition elements corresponding to trees in which l is not
      found could be skipped. We compute dlk and lk nonetheless so that
      these two values are the derivative and lielihood computed for the
      whole data set, not juste the data partitions that "contain" l
    */

    tree = mixt_tree;
    do
    {
      tree->c_lnL  = .0;
      tree->c_dlnL = .0;
      tree         = tree->next;
    } while (tree);

    ns       = mixt_tree->mod->ns;
    nclasses = MIXT_Mixt_Size(mixt_tree);

    lk  = (phydbl *)mCalloc(nclasses, sizeof(phydbl));
    dlk = (phydbl *)mCalloc(nclasses, sizeof(phydbl));

    sum_scale_left_cat = (phydbl *)mCalloc(nclasses, sizeof(phydbl));
    sum_scale_rght_cat = (phydbl *)mCalloc(nclasses, sizeof(phydbl));

    r_mat_weight_sum =
        MIXT_Get_Sum_Chained_Scalar_Dbl(mixt_tree->next->mod->r_mat_weight);
    e_frq_weight_sum =
        MIXT_Get_Sum_Chained_Scalar_Dbl(mixt_tree->next->mod->e_frq_weight);
    sum_probas = MIXT_Get_Sum_Of_Probas_Across_Mixtures(
        r_mat_weight_sum, e_frq_weight_sum, mixt_tree);

    // Fill in expl vector for each rate class
    tree  = mixt_tree->next;
    b     = mixt_b->next;
    class = 0;
    do
    {
      if (tree->mod->ras->invar == YES)
        rr = 0.0;
      else
      {
        rr = 1.0;
        rr *= tree->mod->br_len_mult->v;
        rr *= mixt_tree->mod->ras->gamma_rr
                  ->v[tree->mod->ras->parent_class_number];
      }

      if (length_found == YES)
        len = (*l) * rr;
      else
        len = b->l->v * rr;

      if (isinf(len) || isnan(len))
      {
        PhyML_Fprintf(stderr, "\n. len=%f rr=%f l=%f", len, rr, *l);
        assert(FALSE);
      }

      if (tree->mod->ras->invar == NO)
      {
        if (len < tree->mod->l_min)
          len = tree->mod->l_min;
        else if (len > tree->mod->l_max)
          len = tree->mod->l_max;
      }
      else
      {
        len = 0.0;
      }

      if (length_found == YES)
        var = (*l) * tree->mod->l_var_sigma->v * rr * rr;
      else
        var = b->l->v * tree->mod->l_var_sigma->v * rr * rr;

      expl = mixt_tree->expl;

      for (state = 0; state < tree->mod->ns; ++state)
      {
        ev       = tree->mod->eigen->e_val[state];
        expevlen = exp(ev * len);

        if (tree->mod->gamma_mgf_bl == YES)
        {
          expl[class * 2 * ns + 2 * state] =
              POW(1. - ev * var / len, -len * len / var);
          expl[class * 2 * ns + 2 * state + 1] =
              expl[class * 2 * ns + 2 * state];
          expl[class * 2 * ns + 2 * state + 1] *=
              -(ev * rr / (1. - ev * var / len) +
                2. * len * rr * LOG(1. - ev * var / len) / var);
        }
        else
        {
          expl[class * 2 * ns + 2 * state]     = expevlen;
          expl[class * 2 * ns + 2 * state + 1] = expevlen * ev * rr;
        }
      }

      class ++;

      tree = tree->next;
      b    = b->next;
    } while (tree && tree->is_mixt_tree == NO);

    assert(class == nclasses);

    mixt_tree->c_lnL  = .0;
    mixt_tree->c_dlnL = .0;

    for (site = 0; site < mixt_tree->data->n_pattern; ++site)
    {
      for (class = 0; class < nclasses; ++class)
      {
        dlk[class] = 0.0;
        lk[class]  = 0.0;
      }

      b     = mixt_b->next;
      tree  = mixt_tree->next;
      class = 0;
      /*! For all classes in the mixture */
      do
      {
        if (tree->mod->ras->invar == NO &&
            tree->data->wght[tree->curr_site] > SMALL)
        {
          tree->curr_site = site;
          dot_prod        = tree->dot_prod + site * ns;
          expl            = mixt_tree->expl + 2 * ns * class;

          if (tree->mod->io->datatype == NT || tree->mod->io->datatype == AA)
          {
#if (defined(__AVX__))
            AVX_Lk_dLk_Core_One_Class_Eigen_Lr(dot_prod, expl ? expl : NULL, ns,
                                               lk + class, dlk + class);
#elif (defined(__SSE3__))
            SSE_Lk_dLk_Core_One_Class_Eigen_Lr(dot_prod, expl ? expl : NULL, ns,
                                               lk + class, dlk + class);
#else
            Lk_dLk_Core_One_Class_Eigen_Lr(dot_prod, expl ? expl : NULL, ns,
                                           lk + class, dlk + class);
#endif
          }
          else
          {
            Lk_dLk_Core_One_Class_Eigen_Lr(dot_prod, expl ? expl : NULL, ns,
                                           lk + class, dlk + class);
          }

          tree->apply_lk_scaling = YES;

          sum_scale_left_cat[tree->mod->ras->parent_class_number] =
              (b->sum_scale_left) ? (b->sum_scale_left[site]) : (0.0);

          sum_scale_rght_cat[tree->mod->ras->parent_class_number] =
              (b->sum_scale_rght) ? (b->sum_scale_rght[site]) : (0.0);

          sum = sum_scale_left_cat[tree->mod->ras->parent_class_number] +
                sum_scale_rght_cat[tree->mod->ras->parent_class_number];

          if (sum > 1024.)
          {
            /* PhyML_Fprintf(stderr,"\n. Numerical precision issue detected (sum
             * = %g)!!!",sum); */
            sum                                = 1023.;
            tree->mixt_tree->numerical_warning = YES;
            tree->numerical_warning            = YES;
          }

          mult = pow(2, sum);

          /* PhyML_Printf("\n> tree: %p class: %d lk: %f dlk: %g sum: %g site:
           * %d dot_prod[0]: %g expl[0]: %g expl[1]: %g expl[2]:
           * %g",tree,class,log(lk[class]),dlk[class],sum,site,dot_prod[0],expl[0],expl[1],expl[2]);
           */

          lk[class] /= mult;
          dlk[class] /= mult;
        }

        tree = tree->next;
        b    = b->next;
        class ++;

      } while (tree && tree->is_mixt_tree == NO);
      // done with all trees in the mixture for this partition element.

      tree     = mixt_tree->next;
      class    = 0;
      site_lk  = .0;
      site_dlk = .0;

      if (mixt_tree->mod->ras->invar == YES)
        one_m_pinv = 1. - mixt_tree->mod->ras->pinvar->v;
      else
        one_m_pinv = 1.;

      do
      {
        if (tree->mod->ras->invar == NO &&
            mixt_tree->data->wght[tree->curr_site] > SMALL)
        {
          site_lk += lk[class] *
                     mixt_tree->mod->ras->gamma_r_proba
                         ->v[tree->mod->ras->parent_class_number] *
                     tree->mod->r_mat_weight->v / r_mat_weight_sum *
                     tree->mod->e_frq_weight->v / e_frq_weight_sum / sum_probas;

          site_dlk += dlk[class] * one_m_pinv *
                      mixt_tree->mod->ras->gamma_r_proba
                          ->v[tree->mod->ras->parent_class_number] *
                      tree->mod->r_mat_weight->v / r_mat_weight_sum *
                      tree->mod->e_frq_weight->v / e_frq_weight_sum /
                      sum_probas;

          /* PhyML_Printf("\n. MIXT_DLK site: %d mixt_tree: %p tree: %p class:
           * %d l: %p lk: %g dlk: %g", */
          /*              site, */
          /*              mixt_tree, */
          /*              tree, */
          /*              class, */
          /*              l, */
          /*              lk[class], */
          /*              dlk[class]); */
        }

        class ++;
        tree = tree->next;
      } while (tree && tree->is_mixt_tree == NO);

      /* Scaling for invariants */
      if (mixt_tree->mod->ras->invar == YES)
      {
        num_prec_issue = NO;

        tree = mixt_tree->next;
        while (tree->mod->ras->invar == NO)
        {
          tree = tree->next;
          if (!tree || tree->is_mixt_tree == YES)
          {
            PhyML_Fprintf(stderr, "\n. tree: %p", tree);
            PhyML_Fprintf(stderr, "\n. Err in file %s at line %d", __FILE__,
                          __LINE__);
            Exit("\n");
          }
        }

        tree->apply_lk_scaling = YES;

        /*! 'tree' will give the correct state frequencies (as opposed to
         * mixt_tree) */
        inv_site_lk = Invariant_Lk(0, site, &num_prec_issue, tree);

        if (num_prec_issue == YES) // inv_site_lk >> site_lk
        {
          site_lk = inv_site_lk * mixt_tree->mod->ras->pinvar->v;
        }
        else
        {
          site_lk = site_lk * (1. - mixt_tree->mod->ras->pinvar->v) +
                    inv_site_lk * mixt_tree->mod->ras->pinvar->v;
        }
      }

      log_site_lk = log(site_lk);

      if (isinf(log_site_lk) || isnan(log_site_lk))
      {
        PhyML_Fprintf(stderr, "\n. site = %d", site);
        PhyML_Fprintf(stderr, "\n. invar = %d", mixt_tree->data->invar[site]);
        PhyML_Fprintf(stderr, "\n. mixt = %d", mixt_tree->is_mixt_tree);
        PhyML_Fprintf(stderr, "\n. lk = %G log(lk) = %f < %G", site_lk,
                      log_site_lk, -BIG);
        for (class = 0; class < mixt_tree->mod->ras->n_catg; ++class)
          PhyML_Printf("\n. rr=%f p=%f",
                       mixt_tree->mod->ras->gamma_rr->v[class],
                       mixt_tree->mod->ras->gamma_r_proba->v[class]);
        PhyML_Fprintf(stderr, "\n. pinv = %G", mixt_tree->mod->ras->pinvar->v);
        PhyML_Fprintf(stderr, "\n. bl mult = %G",
                      mixt_tree->mod->br_len_mult->v);
        PhyML_Fprintf(stderr, "\n. Err. in file %s at line %d.\n", __FILE__,
                      __LINE__);
        Exit("\n");
      }

      mixt_tree->c_lnL += mixt_tree->data->wght[site] * log_site_lk;
      mixt_tree->c_dlnL += mixt_tree->data->wght[site] * (site_dlk / site_lk);

      /* PhyML_Printf("\n. site: %4d lnL: %15f dlnL:
       * %15f",site,site_lk,site_dlk); */
    }

    Free(sum_scale_left_cat);
    Free(sum_scale_rght_cat);

    Free(lk);
    Free(dlk);

    mixt_tree = mixt_tree->next_mixt;
    mixt_b    = mixt_b->next_mixt;
  } while (mixt_tree);

  // Sum c_dlnL across all partition element and set each c_dlnL equal to
  // this sum

  sum       = .0;
  mixt_tree = cpy_mixt_tree;
  do
  {
    sum += mixt_tree->c_dlnL;
    mixt_tree = mixt_tree->next_mixt;
  } while (mixt_tree);

  mixt_tree = cpy_mixt_tree;
  do
  {
    mixt_tree->c_dlnL = sum;
    mixt_tree         = mixt_tree->next_mixt;
  } while (mixt_tree);

  // Do the same with likelihood values
  sum       = .0;
  mixt_tree = cpy_mixt_tree;
  do
  {
    sum += mixt_tree->c_lnL;
    mixt_tree = mixt_tree->next_mixt;
  } while (mixt_tree);

  mixt_tree = cpy_mixt_tree;
  do
  {
    mixt_tree->c_lnL = sum;
    mixt_tree        = mixt_tree->next_mixt;
  } while (mixt_tree);

  mixt_tree = cpy_mixt_tree;
  mixt_b    = cpy_mixt_b;

  return mixt_tree->c_lnL;
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
// Returns the number of trees (including mixt_trees) in the
// whole model.
int MIXT_Part_Mixt_Size(t_tree *mixt_tree)
{
  if (mixt_tree->is_mixt_tree == NO)
    return 1;
  else
  {
    int     num;
    t_tree *tree;

    num  = 0;
    tree = mixt_tree;
    do
    {
      num++;
      tree = tree->next;
    } while (tree);
    return num;
  }
  return -1;
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

// Returns the number of trees in the partition element corresponding to
// mixt_tree.
int MIXT_Mixt_Size(t_tree *mixt_tree)
{
  if (mixt_tree->is_mixt_tree == NO)
    return 1;
  else
  {
    int     num;
    t_tree *tree;

    num  = 0;
    tree = mixt_tree->next;
    do
    {
      num++;
      tree = tree->next;
    } while (tree && tree->is_mixt_tree == NO);

    return num;
  }
  return -1;
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Set_Both_Sides(int yesno, t_tree *mixt_tree)
{
  t_tree *tree;

  assert(mixt_tree->is_mixt_tree == YES);

  tree = mixt_tree->next;

  if (tree != NULL)
  {
    do
    {
      Set_Both_Sides(yesno, tree);
      if (tree->is_mixt_tree == YES) return;
      tree = tree->next;
    } while (tree);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Set_Model_Parameters(t_mod *mixt_mod)
{
  t_mod *mod = mixt_mod->next;

  if (mod != NULL)
  {
    do
    {
      Set_Model_Parameters(mod);
      if (mod->is_mixt_mod == YES) return;
      mod = mod->next;
    } while (mod);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Update_Eigen(t_mod *mixt_mod)
{
  t_mod *mod;

  mod = mixt_mod;

  do
  {
    if (mod->is_mixt_mod) mod = mod->next;
    if (!Update_Boundaries(mod)) Generic_Exit(__FILE__, __LINE__, __FUNCTION__);
    if (!Update_Eigen(mod)) Generic_Exit(__FILE__, __LINE__, __FUNCTION__);
    mod = mod->next;
  } while (mod);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Update_Eigen_Lr(t_edge *mixt_b, t_tree *mixt_tree)
{
  t_edge *b;
  t_tree *tree;

  tree = mixt_tree->next;
  b    = mixt_b->next;

  if (tree != NULL)
  {
    do
    {
      Update_Eigen_Lr(b, tree);
      if (tree->is_mixt_tree == YES) return;

      tree = tree->next;
      b    = b->next;
    } while (tree);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Set_Update_Eigen(int yn, t_mod *mixt_mod)
{
  t_mod *mod;

  mod = mixt_mod;
  do
  {
    mod->update_eigen = yn;
    mod               = mod->next;
  } while (mod);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Set_Update_Eigen_Lr(int yn, t_tree *mixt_tree)
{
  t_tree *tree;
  int     dum;

  tree = mixt_tree->next;
  do
  {
    dum                     = mixt_tree->is_mixt_tree;
    mixt_tree->is_mixt_tree = NO;

    Set_Update_Eigen_Lr(yn, tree);

    mixt_tree->is_mixt_tree = dum;

    tree = tree->next;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Set_Use_Eigen_Lr(int yn, t_tree *mixt_tree)
{
  t_tree *tree;
  int     dum;

  tree = mixt_tree->next;
  do
  {
    dum                     = mixt_tree->is_mixt_tree;
    mixt_tree->is_mixt_tree = NO;

    Set_Use_Eigen_Lr(yn, tree);

    mixt_tree->is_mixt_tree = dum;

    tree = tree->next;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Sample_Ancestral_Seq(int fullmutmap, int fromprior, t_tree *mixt_tree)
{
  t_tree *tree, *loc_mixt_tree;
  phydbl  r_mat_weight_sum, e_frq_weight_sum, sum_probas, *class_proba;
  int     i;

  r_mat_weight_sum =
      MIXT_Get_Sum_Chained_Scalar_Dbl(mixt_tree->next->mod->r_mat_weight);
  e_frq_weight_sum =
      MIXT_Get_Sum_Chained_Scalar_Dbl(mixt_tree->next->mod->e_frq_weight);
  sum_probas = MIXT_Get_Sum_Of_Probas_Across_Mixtures(
      r_mat_weight_sum, e_frq_weight_sum, mixt_tree);

  tree          = mixt_tree;
  loc_mixt_tree = tree;

  // For each element in the partition (i.e., each gene), select a
  // class of the mixture according to its post prob.
  do
  {
    tree = loc_mixt_tree->next;

    class_proba = NULL;
    i           = 0;
    do
    {
      if (i == 0)
        class_proba = (phydbl *)mCalloc(1, sizeof(phydbl));
      else
        class_proba = (phydbl *)mRealloc(class_proba, i + 1, sizeof(phydbl));

      class_proba[i] = tree->site_lk_cat[0] *
                       loc_mixt_tree->mod->ras->gamma_r_proba
                           ->v[tree->mod->ras->parent_class_number] *
                       tree->mod->r_mat_weight->v / r_mat_weight_sum *
                       tree->mod->e_frq_weight->v / e_frq_weight_sum /
                       sum_probas;

      tree = tree->next;
      i++;
    } while (tree && tree->is_mixt_tree == NO);

    tree = loc_mixt_tree->next;
    i    = Sample_i_With_Proba_pi(class_proba, i);

    do
    {
      i--;
      if (i < 0) break;
      tree = tree->next;
      assert(tree);
    } while (1);

    assert(tree->is_mixt_tree == NO);

    Sample_Ancestral_Seq(fullmutmap, fromprior, tree);

    Free(class_proba);

    loc_mixt_tree = loc_mixt_tree->next_mixt;
  } while (loc_mixt_tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
// Propagate changes in the "master" tree to other trees. Partial
// likelihood vectors (and scaling stuff) is not up-to-date here so
// requires full tree traversal to be updated properly.
void MIXT_Propagate_Tree_Update(t_tree *mixt_tree)
{
  t_tree *tree;
  int     i;

  assert(!mixt_tree->prev);

  tree = mixt_tree->next;
  if (!tree) return;

  do
  {
    for (i = 0; i < 2 * mixt_tree->n_otu - 1; ++i)
    {
      tree->a_nodes[i]->v[0] =
          mixt_tree->a_nodes[i]->v[0]
              ? tree->a_nodes[mixt_tree->a_nodes[i]->v[0]->num]
              : NULL;
      tree->a_nodes[i]->v[1] =
          mixt_tree->a_nodes[i]->v[1]
              ? tree->a_nodes[mixt_tree->a_nodes[i]->v[1]->num]
              : NULL;
      tree->a_nodes[i]->v[2] =
          mixt_tree->a_nodes[i]->v[2]
              ? tree->a_nodes[mixt_tree->a_nodes[i]->v[2]->num]
              : NULL;
      if (tree->times != NULL) tree->times->nd_t[i] = mixt_tree->times->nd_t[i];
    }

    /* Connect_Edges_To_Nodes_Serial(tree); */
    for (i = 0; i < 2 * mixt_tree->n_otu - 1; ++i)
    {
      if (mixt_tree->n_root == NULL ||
          (mixt_tree->a_edges[i] != mixt_tree->n_root->b[1] &&
           mixt_tree->a_edges[i] != mixt_tree->n_root->b[2]))
        Connect_One_Edge_To_Two_Nodes(
            tree->a_nodes[mixt_tree->a_edges[i]->left->num],
            tree->a_nodes[mixt_tree->a_edges[i]->rght->num],
            tree->a_edges[mixt_tree->a_edges[i]->num], tree);
      tree->a_edges[mixt_tree->a_edges[i]->num]->num =
          mixt_tree->a_edges[i]->num;
    }

    /* Necessary? Increase computation time due to large number of calls from
     * Ldsk_To_Tree function */
    /* Reorganize_Edges_Given_Lk_Struct(tree); */
    /* Init_Partial_Lk_Tips_Double(tree); */

    if (mixt_tree->n_root != NULL)
    {
      assert(mixt_tree->e_root);
      assert(mixt_tree->n_root->v[1]);
      assert(mixt_tree->n_root->v[2]);
      assert(mixt_tree->n_root->b[1]);
      assert(mixt_tree->n_root->b[2]);

      tree->n_root = tree->a_nodes[mixt_tree->n_root->num];
      tree->e_root = tree->a_edges[mixt_tree->e_root->num];

      tree->n_root->v[1] = tree->a_nodes[mixt_tree->n_root->v[1]->num];
      tree->n_root->v[2] = tree->a_nodes[mixt_tree->n_root->v[2]->num];

      tree->n_root->b[1] = tree->a_edges[mixt_tree->n_root->b[1]->num];
      tree->n_root->b[2] = tree->a_edges[mixt_tree->n_root->b[2]->num];

      tree->n_root->b[1]->left = tree->n_root;
      tree->n_root->b[2]->left = tree->n_root;
      tree->n_root->b[1]->rght = tree->n_root->v[1];
      tree->n_root->b[2]->rght = tree->n_root->v[2];
    }

    Update_Ancestors(tree->n_root, tree->n_root->v[2], tree->n_root->b[2],
                     tree);
    Update_Ancestors(tree->n_root, tree->n_root->v[1], tree->n_root->b[1],
                     tree);

    tree = tree->next;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Set_Bl_From_Rt(int yn, t_tree *mixt_tree)
{
  t_tree *tree;

  tree = mixt_tree->next;

  if (tree != NULL)
  {
    do
    {
      Set_Bl_From_Rt(yn, tree);
      if (tree->is_mixt_tree == YES) return;
      tree = tree->next;
    } while (tree);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Copy_Tree(t_tree *ori, t_tree *cpy)
{
  assert(!((cpy && !ori) || (!cpy && ori)));

  if (cpy == NULL || ori == NULL) return;

  if (ori->is_mixt_tree == YES && cpy->is_mixt_tree == YES)
  {
    do
    {
      ori->ignore_mixt_info = YES;
      cpy->ignore_mixt_info = YES;
      Copy_Tree(ori, cpy);
      if (ori->is_mixt_tree == YES) ori->ignore_mixt_info = NO;
      if (cpy->is_mixt_tree == YES) cpy->ignore_mixt_info = NO;

      ori = ori->next;
      cpy = cpy->next;
    } while (cpy);
  }
  else if (ori->is_mixt_tree == NO && cpy->is_mixt_tree == YES)
  {

    do
    {
      cpy->ignore_mixt_info = YES;
      Copy_Tree(ori, cpy);
      if(cpy->is_mixt_tree == YES) cpy->ignore_mixt_info = NO;

      cpy = cpy->next;
    } while (cpy);

  }
  else if (ori->is_mixt_tree == YES && cpy->is_mixt_tree == NO)
  {
    ori->ignore_mixt_info = YES;
    Copy_Tree(ori, cpy);
    ori->ignore_mixt_info = NO;
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

calign *MIXT_Copy_Cseq(calign *ori, option *io, t_tree *mixt_tree)
{
  calign *cpy;
  t_tree *tree;
  

  cpy = Copy_Cseq(ori, io, NULL);

// Propagate to trees in the mixture
  tree = mixt_tree->next;
  do
  {
    tree->data = cpy;
    
    tree       = tree->next;
  } while (tree != NULL && tree->is_mixt_tree == NO);

  return (cpy);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Init_NNI_Score(phydbl val, t_edge *mixt_b, t_tree *mixt_tree)
{
  t_edge *b;
  t_tree *tree;

  tree = mixt_tree->next;
  b    = mixt_b->next;
  do
  {
    Init_NNI_Score(val, b, tree);
    if (tree->is_mixt_tree == YES) return;
    tree = tree->next;
    b    = b->next;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

t_tree *MIXT_Duplicate_Tree(t_tree *ori)
{
  t_tree *tree, *cpy;
  int     i;

  assert(ori->is_mixt_tree == YES);

  ori->is_mixt_tree = NO;
  cpy               = Duplicate_Tree(ori);
  ori->is_mixt_tree = YES;
  cpy->is_mixt_tree = YES;
  cpy->prev         = NULL;
  tree              = ori;

  do
  {
    if (tree->next != NULL)
    {
      i                        = tree->next->is_mixt_tree;
      tree->next->is_mixt_tree = NO;
      cpy->next                = Duplicate_Tree(tree->next);
      tree->next->is_mixt_tree = i;

      cpy->next->is_mixt_tree = tree->next->is_mixt_tree;
      cpy->next->prev         = cpy;
    }
    else
      break;

    tree = tree->next;
    cpy  = cpy->next;
  } while (tree != NULL);

  do cpy = cpy->prev;
  while (cpy->prev);

  do
  {
    for (i = 0; i < 2 * tree->n_otu - 1; ++i)
    {
      Free_Scalar_Dbl(cpy->a_edges[i]->l);
      Free_Scalar_Dbl(cpy->a_edges[i]->l_old);
      Free_Scalar_Dbl(cpy->a_edges[i]->l_var);
      Free_Scalar_Dbl(cpy->a_edges[i]->l_var_old);
    }

    if (cpy->next == NULL) break;

    cpy = cpy->next;
  } while (1);

  do cpy = cpy->prev;
  while (cpy->prev);

  for (i = 0; i < 2 * tree->n_otu - 1; ++i)
  {
    cpy->a_edges[i]->l     = Duplicate_Scalar_Dbl(ori->a_edges[i]->l);
    cpy->a_edges[i]->l_old = Duplicate_Scalar_Dbl(ori->a_edges[i]->l_old);
    cpy->a_edges[i]->l_var = Duplicate_Scalar_Dbl(ori->a_edges[i]->l_var);
    cpy->a_edges[i]->l_var_old =
        Duplicate_Scalar_Dbl(ori->a_edges[i]->l_var_old);
  }

  tree = cpy;

  do
  {
    for (i = 0; i < 2 * tree->n_otu - 1; ++i)
    {
      cpy->a_edges[i]->l         = tree->a_edges[i]->l;
      cpy->a_edges[i]->l_old     = tree->a_edges[i]->l_old;
      cpy->a_edges[i]->l_var     = tree->a_edges[i]->l_var;
      cpy->a_edges[i]->l_var_old = tree->a_edges[i]->l_var_old;
    }

    if (cpy->next == NULL) break;

    cpy = cpy->next;
  } while (1);

  do cpy = cpy->prev;
  while (cpy->prev);

  return (cpy);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Print_Site_Lk(t_tree *mixt_tree, FILE *fp)
{
  t_tree *tree;
  int     site, catg;
  char   *s;
  phydbl  postmean, sum;

  assert(mixt_tree->is_mixt_tree == YES);
  assert(mixt_tree->io->print_site_lnl == YES);

  if (!mixt_tree->io->print_trace)
  {
    tree = mixt_tree;
    do
    {
      PhyML_Fprintf(fp, "# Note : P(D|M) is the probability of site D given "
                        "the model M (i.e., the site likelihood)\n");
      if (tree->mod->ras->n_catg > 1 || tree->mod->ras->invar)
      {
        PhyML_Fprintf(fp, "# P*(D|M,rr[x]) is the scaled probability of site D "
                          "given the model M and the relative rate\n");
        PhyML_Fprintf(fp, "# of evolution rr[x], where x is the class of rate "
                          "to be considered.\n");
        PhyML_Fprintf(fp, "# The actual conditional probability is given by "
                          "P*(D|M,rr[x])/2^F, where\n");
        PhyML_Fprintf(fp, "# F is the scaling factor (see column 'Scaler').\n");
        PhyML_Fprintf(fp, "# For invariant sites, P(D|M,rr[0]=0) is the actual "
                          "conditional probability\n");
        PhyML_Fprintf(fp, "# (i.e., it is not scaled).\n");
        break;
      }
      tree = tree->next_mixt;
    } while (tree);
    PhyML_Fprintf(fp, "\n");

    s = (char *)mCalloc(T_MAX_LINE, sizeof(char));

    do
    {
      PhyML_Fprintf(fp, "# Alignment file name: %s\n\n",
                    mixt_tree->io->in_align_file);

      if (mixt_tree->mod->ras->n_catg > 1)
      {
        PhyML_Fprintf(fp, "\n# Estimated class frequencies:");
        for (catg = 0; catg < mixt_tree->mod->ras->n_catg; ++catg)
        {
          sprintf(s, "\n# P(rr[%d]=%5.4f)=%5.4f", catg + 1,
                  mixt_tree->mod->ras->gamma_rr->v[catg],
                  mixt_tree->mod->ras->gamma_r_proba->v[catg]);
          PhyML_Fprintf(fp, "%-23s", s);
        }
        PhyML_Fprintf(fp, "\n\n");
      }

      sprintf(s, "Site");
      PhyML_Fprintf(fp, "%-12s", s);

      sprintf(s, "P(D|M)");
      PhyML_Fprintf(fp, "%-15s", s);

      sprintf(s, "Scaler");
      PhyML_Fprintf(fp, "%-7s", s);

      sprintf(s, "Pattern");
      PhyML_Fprintf(fp, "%-9s", s);

      if (mixt_tree->mod->ras->n_catg > 1)
      {
        for (catg = 0; catg < mixt_tree->mod->ras->n_catg; catg++)
        {
          sprintf(s, "P*(D|M,rr[%d]=%5.4f)", catg + 1,
                  mixt_tree->mod->ras->gamma_rr->v[catg]);
          PhyML_Fprintf(fp, "%-23s", s);
        }

        sprintf(s, "Posterior mean");
        PhyML_Fprintf(fp, "%-22s", s);
      }

      if (mixt_tree->mod->ras->invar)
      {
        sprintf(s, "P(D|M,rr[0]=0)");
        PhyML_Fprintf(fp, "%-16s", s);
      }

      sprintf(s, "NDistinctStates");
      PhyML_Fprintf(fp, "%-16s", s);

      PhyML_Fprintf(fp, "\n");

      assert(mixt_tree->next->is_mixt_tree == NO);
      Init_Ui_Tips(mixt_tree->next);

      for (site = 0; site < mixt_tree->data->init_len; site++)
      {
        PhyML_Fprintf(fp, "%-12d", site + 1);

        PhyML_Fprintf(fp, "%-15g",
                      mixt_tree->cur_site_lk[mixt_tree->data->sitepatt[site]]);

        PhyML_Fprintf(
            fp, "%-7d",
            mixt_tree->fact_sum_scale[mixt_tree->data->sitepatt[site]]);

        PhyML_Fprintf(fp, "%-9d", mixt_tree->data->sitepatt[site]);

        if (mixt_tree->mod->ras->n_catg > 1)
        {
          tree = mixt_tree->next;
          do
          {
            PhyML_Fprintf(
                fp, "%-23g",
                tree->unscaled_site_lk_cat[tree->data->sitepatt[site]]);
            tree = tree->next;
          } while (tree && tree->is_mixt_tree == NO);

          tree     = mixt_tree->next;
          postmean = .0;
          sum      = .0;
          do
          {
            postmean += mixt_tree->mod->ras->gamma_rr
                            ->v[tree->mod->ras->parent_class_number] *
                        tree->unscaled_site_lk_cat[tree->data->sitepatt[site]] *
                        mixt_tree->mod->ras->gamma_r_proba
                            ->v[tree->mod->ras->parent_class_number];

            sum += tree->unscaled_site_lk_cat[tree->data->sitepatt[site]] *
                   mixt_tree->mod->ras->gamma_r_proba
                       ->v[tree->mod->ras->parent_class_number];

            tree = tree->next;
          } while (tree && tree->is_mixt_tree == NO);

          postmean /= sum;

          PhyML_Fprintf(fp, "%-22g", postmean);
        }

        if (mixt_tree->mod->ras->invar)
        {
          if ((phydbl)mixt_tree->data->invar[mixt_tree->data->sitepatt[site]] >
              -0.5)
            PhyML_Fprintf(
                fp, "%-16g",
                mixt_tree->mod->e_frq->pi->v
                    [mixt_tree->data->invar[mixt_tree->data->sitepatt[site]]]);
          else
            PhyML_Fprintf(fp, "%-16g", 0.0);
        }

        assert(mixt_tree->next != NULL);

        PhyML_Fprintf(fp, "%-16d",
                      Number_Of_Diff_States_One_Site(
                          mixt_tree->data->sitepatt[site], mixt_tree->next));

        PhyML_Fprintf(fp, "\n");
      }
      Free(s);

      mixt_tree = mixt_tree->next_mixt;
    } while (mixt_tree);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Exchange_Nodes(t_node *a, t_node *d, t_node *w, t_node *v,
                         t_tree *mixt_tree)
{
  t_tree *tree;

  tree = mixt_tree->next;
  do
  {
    Exchange_Nodes(tree->a_nodes[a->num], tree->a_nodes[d->num],
                   tree->a_nodes[w->num], tree->a_nodes[v->num], tree);
    if (tree->is_mixt_tree == YES) return;
    tree = tree->next;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Free_Tree(t_tree *mixt_tree)
{
  t_tree *tree;
  t_tree *next;

  Free_All_Edges_Lens(mixt_tree);

  tree = mixt_tree;
  do
  {
    if (tree->mat) Free_Mat(tree->mat);
    if (tree->t_dir) Free(tree->t_dir);
    if (tree->short_l) Free(tree->short_l);
    if (tree->mutmap) Free(tree->mutmap);
    Free_Bip(tree);
    Free(tree->curr_path);
    Free_All_Nodes_Light(tree);
    Free_All_Edges_Light(tree);
    tree = tree->next;
  } while (tree);

  tree = mixt_tree;
  next = mixt_tree->next;
  do
  {
    Free(tree);
    tree = next;
    if (!tree) break;
    next = next->next;
  } while (tree);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Repeat_Task(void (*Task_Function)(t_tree *tree), t_tree *mixt_tree)
{
  t_tree *tree, *next;

  tree = mixt_tree->next;

  if (tree != NULL)
  {
    do
    {
      next = tree->next;
      (*Task_Function)(tree);
      if (tree->is_mixt_tree == YES) return;
      tree = next;
    } while (tree != NULL);
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void MIXT_Cv(t_tree *mixt_tree)
{
  PhyML_Printf("\n");
  
  switch (mixt_tree->mod->cv_type)
  {
  case KFOLD_POS : 
  {
    PhyML_Printf("\n. Performing K-fold cross-validation\n");
    MIXT_Kfold_Pos_Cv(mixt_tree);
    break;
  }
  case KFOLD_COL:
  {
    PhyML_Printf("\n. Performing leave-one-column-out cross-validation\n");
    MIXT_Kfold_Col_Cv(mixt_tree);
    break;
  }
  case MAXFOLD:
  {
    PhyML_Printf("\n. Performing leave-one-column x one-sequence-out cross-validation\n");
    MIXT_Maxfold_Cv(mixt_tree);
    break;    
  }
  default : return;
  }
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
// Cross-validation routine whereby each position (i.e., site x taxon) is
// hidden, the corresponding external branch is optimized and the log-likelihood
// of the tree is computed for that particular position, for the actual character
// observed at that position. We also calculate here the probabity of each character
// and provide the area under the ROC curve.
void MIXT_Maxfold_Cv(t_tree *mixt_tree) 
{
  phydbl    *state_probs, *site_loglk, *weights;
  short int *truth;
  int        n_prob_vectors;
  char       init_char;
  
  // state_probs = NULL;
  // site_loglk  = NULL;
  // truth       = NULL;
  // weights     = NULL;

  n_prob_vectors = 0;

  do
  {
    Set_Both_Sides(YES, mixt_tree);
    Set_Update_Eigen(YES, mixt_tree->mod);
    Lk(NULL, mixt_tree);
    Set_Update_Eigen(NO, mixt_tree->mod);

    for (int site = 0; site < mixt_tree->data->n_pattern; ++site)
    {
      for (int tax_id = 0; tax_id < mixt_tree->data->n_otu; ++tax_id)
      {
        if (mixt_tree->data->c_seq[tax_id]->is_ambigu[site] == NO)
        {
          init_char = mixt_tree->data->c_seq[tax_id]->state[site];

          // Hide characters in the original alignment uniformly at random
          CV_Hide_Align_At_Given_Pos(mixt_tree->data, tax_id, site);

          Init_Partial_Lk_Tips_Double_One_Character(tax_id, site, mixt_tree);

          MIXT_Br_Len_Opt(mixt_tree->a_nodes[tax_id]->b[0], mixt_tree);
          // PhyML_Printf("\n. site:%12d tax_id: %12d  lnL: %15f", site, tax_id, mixt_tree->c_lnL);


          // Go back to the original alignment
          mixt_tree->data->c_seq[tax_id]->state[site] = init_char;

          mixt_tree->data->c_seq[tax_id]->d_state[site] = Assign_State(
              mixt_tree->data->c_seq[tax_id]->state + site,
              mixt_tree->mod->io->datatype, mixt_tree->mod->io->state_len);

          // Compute likelihoods on test set (i.e., probability of each possible
          // state at masked positions)
          CV_State_Probs_At_Hidden_Positions(&state_probs, &truth, &site_loglk,
                                             &weights, &n_prob_vectors,
                                             mixt_tree);

          mixt_tree->data->n_masked = 0;
          Free(mixt_tree->data->masked_pos);
          mixt_tree->data->masked_pos = NULL;

          Check_Ambiguities(mixt_tree->data, mixt_tree->mod->io->datatype,
                            mixt_tree->mod->io->state_len);

          Init_Partial_Lk_Tips_Double_One_Character(tax_id, site, mixt_tree);
        }
      }
    }

    PhyML_Fprintf(mixt_tree->io->fp_out_stats, "\n");
    PhyML_Fprintf(mixt_tree->io->fp_out_stats,
                  "\n. CV type: fast k-fold position (aka maxfold).");

    ROC(state_probs, truth, mixt_tree->mod->ns, n_prob_vectors, weights,
        "MAXFOLD", mixt_tree->io->fp_out_stats);

    Free(state_probs);
    Free(site_loglk);
    Free(truth);
    Free(weights);

    state_probs = NULL;
    site_loglk  = NULL;
    truth       = NULL;
    weights     = NULL;

    mixt_tree = mixt_tree->next_mixt;
    
  } while (mixt_tree != NULL);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
// Cross-validation routine whereby positions (i.e., one position = site x
// taxon) are hidden uniformly at random. For each site, one taxon is chosen
// uniformy at random and the corresponding position is masked. Do that for all
// sites in the alignment and then optimize the phylogenetic model (tree
// topology + edge length + etc.). The log-likelihood of the tree is computed 
// at each masked position, for the actual character observed at each of these positions. 
// We also calculate here the probabity of each character and provide the area under the ROC curve.
void MIXT_Kfold_Pos_Cv(t_tree *mixt_tree)
{
  phydbl    *state_probs, *site_loglk, *weights;
  short int *truth;
  calign    *c_seq_cpy;
  int        kfold;
  int        n_prob_vectors;
  t_tree    *tree;

  state_probs = NULL;
  site_loglk  = NULL;
  truth       = NULL;
  weights     = NULL;
  tree        = NULL;

  kfold          = 10;
  // kfold          = 1;
  n_prob_vectors = 0;

  do
  {
    // for (int i = 0; i < kfold*5; ++i)
      for (int i = 0; i < 5; ++i)
      {
        // Create a copy of the original alignment
        c_seq_cpy = Copy_Cseq(mixt_tree->data, mixt_tree->io, mixt_tree);

        // Hide characters in the original alignment uniformly at random
        // CV_Hide_Align_At_Random_One_Per_Site(mixt_tree->data);
        CV_Hide_Align_At_Random_Pos(mixt_tree->data, (phydbl)(1. / kfold));

        c_seq_cpy->n_masked = mixt_tree->data->n_masked;
        c_seq_cpy->masked_pos =
            (int *)mCalloc(c_seq_cpy->n_masked, sizeof(int));
        for (int i = 0; i < c_seq_cpy->n_masked; i++)
          c_seq_cpy->masked_pos[i] = mixt_tree->data->masked_pos[i];



        // Optimize all parameters on training data set (i.e., whole alignement
        // with masked positions)
        Init_Partial_Lk_Tips_Double(mixt_tree);
        // Global_Spr_Search(mixt_tree);
        Round_Optimize(mixt_tree,ROUND_MAX);
        // Optimize_Br_Len_Serie(mixt_tree->n_otu, mixt_tree);

        Set_Both_Sides(YES, mixt_tree);
        Set_Update_Eigen(YES, mixt_tree->mod);
        Lk(NULL, mixt_tree);
        Set_Update_Eigen(NO, mixt_tree->mod);

        // Go back to the original alignment
        Free_Calign(mixt_tree->data);
        mixt_tree->data = Copy_Cseq(c_seq_cpy, mixt_tree->io, mixt_tree);
        Free_Calign(c_seq_cpy);

        MIXT_Connect_Cseqs_To_Nodes(mixt_tree);
        Init_Partial_Lk_Tips_Double(mixt_tree);
        Set_Both_Sides(YES, mixt_tree);
        Set_Update_Eigen(YES, mixt_tree->mod);
        Lk(NULL, mixt_tree);
        // PhyML_Printf("\n. j:%d  lnL: %f", i, mixt_tree->c_lnL);

        // Compute likelihoods on test set (i.e., probability of each possible
        // state at masked positions)
        CV_State_Probs_At_Hidden_Positions(&state_probs, &truth, &site_loglk,
                                           &weights, &n_prob_vectors,
                                           mixt_tree);

        mixt_tree->data->n_masked = 0;
        Free(mixt_tree->data->masked_pos);
        mixt_tree->data->masked_pos = NULL;

        tree = mixt_tree->next;
        do
        {
          tree->data = tree->mixt_tree->data;
          tree       = tree->next;
        } while (tree && tree->is_mixt_tree == NO);

      }

    Init_Partial_Lk_Tips_Double(mixt_tree);

    PhyML_Fprintf(mixt_tree->io->fp_out_stats, "\n");
    PhyML_Fprintf(mixt_tree->io->fp_out_stats, "\n. CV type: k-fold position.");

    ROC(state_probs, truth, mixt_tree->mod->ns, n_prob_vectors, weights,
        "KFOLDPOS",mixt_tree->io->fp_out_stats);

    Free(state_probs);
    Free(site_loglk);
    Free(truth);
    Free(weights);

    state_probs = NULL;
    site_loglk  = NULL;
    truth       = NULL;
    weights     = NULL;

    mixt_tree = mixt_tree->next_mixt;

  } while (mixt_tree != NULL);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
// Plain cross-validation routine whereby columns are hidden uniformly at random.
void MIXT_Kfold_Col_Cv(t_tree *mixt_tree)
{
  phydbl    *state_probs, *site_loglk, *weights;
  short int *truth;
  calign    *c_seq_cpy;
  int        kfold;
  int        n_prob_vectors;
  t_tree    *tree;

  state_probs = NULL;
  site_loglk  = NULL;
  truth       = NULL;
  weights     = NULL;
  tree        = NULL;

  kfold          = 10;
  n_prob_vectors = 0;

  do
  {
    for (int i = 0; i < kfold; ++i)
    {
      // Create a copy of the original alignment
      c_seq_cpy = Copy_Cseq(mixt_tree->data, mixt_tree->io, mixt_tree);

      // Hide characters in the original alignment uniformly at random
      CV_Hide_Align_At_Random_Col(mixt_tree->data, 1. / (phydbl)kfold);

      c_seq_cpy->n_masked   = mixt_tree->data->n_masked;
      c_seq_cpy->masked_pos = (int *)mCalloc(c_seq_cpy->n_masked, sizeof(int));
      for (int i = 0; i < c_seq_cpy->n_masked; i++)
        c_seq_cpy->masked_pos[i] = mixt_tree->data->masked_pos[i];

      // Optimize all parameters on training data set (i.e., whole alignement
      // with masked positions)
      Init_Partial_Lk_Tips_Double(mixt_tree);
      // Round_Optimize(mixt_tree,ROUND_MAX);
      Global_Spr_Search(mixt_tree);
      Set_Both_Sides(YES, mixt_tree);
      Set_Update_Eigen(YES, mixt_tree->mod);
      Lk(NULL, mixt_tree);
      Set_Update_Eigen(NO, mixt_tree->mod);
      // PhyML_Printf("\n. i:%d  lnL: %f", i, mixt_tree->c_lnL);


      // Go back to the original alignment
      Free_Calign(mixt_tree->data);
      mixt_tree->data = Copy_Cseq(c_seq_cpy, mixt_tree->io, mixt_tree);
      Free_Calign(c_seq_cpy);

      MIXT_Connect_Cseqs_To_Nodes(mixt_tree);
      Init_Partial_Lk_Tips_Double(mixt_tree);
      Set_Both_Sides(NO, mixt_tree);
      Set_Update_Eigen(NO, mixt_tree->mod);
      Lk(NULL, mixt_tree);
      // PhyML_Printf("\n. j:%d  lnL: %f", i, mixt_tree->c_lnL);


      // Compute likelihoods on test set (i.e., probability of each possible
      // state at masked positions)
      CV_Score_At_Hidden_Cols(&site_loglk, &weights, &n_prob_vectors,
                              mixt_tree);

      mixt_tree->data->n_masked = 0;
      Free(mixt_tree->data->masked_pos);
      mixt_tree->data->masked_pos = NULL;

      tree = mixt_tree->next;
      do
      {
        tree->data = tree->mixt_tree->data;
        tree       = tree->next;
      } while (tree && tree->is_mixt_tree == NO);

    }

    Init_Partial_Lk_Tips_Double(mixt_tree);

    phydbl sum   = 0.0;
    phydbl sum_w = 0.0;
    for (int i = 0; i < n_prob_vectors; ++i)
    {
      // PhyML_Printf("\n. loglk: %15g", site_loglk[i]);
      sum += site_loglk[i] * weights[i];
      sum_w += weights[i];
    }

    PhyML_Fprintf(mixt_tree->io->fp_out_stats, "\n");
    PhyML_Fprintf(mixt_tree->io->fp_out_stats, "\n. CV type: k-fold column.");
    PhyML_Fprintf(mixt_tree->io->fp_out_stats, "\n. CV score: %f ", sum / sum_w);

    Free(state_probs);
    Free(site_loglk);
    Free(truth);
    Free(weights);

    state_probs = NULL;
    site_loglk  = NULL;
    truth       = NULL;
    weights     = NULL;


    mixt_tree = mixt_tree->next_mixt;

  } while (mixt_tree != NULL);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
