/*
 *
 * This class provides the functions to embed a graph in the S1 space.
 *
 * Compilation requires the c++11 standard (to use #include <random>), the Eigen, the Spectra as
 * well as the hyp2f1.a library.
 *
 * Author:   Antoine Allard
 * WWW:      antoineallard.info
 * Version:  1.0
 * Date:     May 2018
 *
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 */

#ifndef EMBEDDINGS1_HPP_INCLUDED
#define EMBEDDINGS1_HPP_INCLUDED

// Standard Template Library
#include <algorithm>
// #include <chrono>
#include <cmath>
#include <ctime>
#include <complex>
#include <exception>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <map>
#include <random>
#include <set>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
// Eigen library
#include "Eigen/Core"
#include "Eigen/SparseCore"
// Spectra library
#include "Spectra/GenEigsSolver.h"
#include "Spectra/MatOp/SparseGenMatProd.h"
// Custom library for specific Gaussian hypergeometric functions.
#include "hyp2f1.hpp"


class embeddingS1_t
{
  private:
    // pi
    const double PI = 3.141592653589793238462643383279502884197;
  // Flags controlling options.
  public:
    // Characterizes the inferred ensemble (generates CHARACTERIZATION_NB_GRAPHS graphs and measure
    //   various structural properties).
    bool CHARACTERIZATION_MODE = false;
    // Also write a raw file containing the inferred coordinates without any "commented" lines.
    bool CLEAN_RAW_OUTPUT_MODE = false;
    // Has a custom value for beta been provided?
    bool CUSTOM_BETA = false;
    // Indicates whether the number of graphs generated during the characterization phase is the default value ot not.
    bool CUSTOM_CHARACTERIZATION_NB_GRAPHS = false;
    // Using already inferred coordinates.
    bool CUSTOM_INFERRED_COORDINATES = false;
    // Has a custom name for various output files generated by the program been provided?
    bool CUSTOM_OUTPUT_ROOTNAME_MODE = false;
    // Has a custom value for the seed been provided?
    bool CUSTOM_SEED = false;
    // Will the kappas be adjusted after the angular positions inferred?
    bool KAPPA_POST_INFERENCE_MODE = true;
    // Will or will not position the vertices to maximize the log-likelihood.
    bool MAXIMIZATION_MODE = true;
    // Does not provide any information during the embedding process.
    bool QUIET_MODE = false;
    // Refining only the already inferred positions.
    bool REFINE_MODE = false;
    // Provides various files that characterize the inferred ensemble.
    bool VALIDATION_MODE = false;
    // Print information about the embedding process on screen instead than on the log file.
    bool VERBOSE_MODE = false;

  // Global parameters.
  public:
    // Name of the file containing the previously inferred parameters.
    std::string ALREADY_INFERRED_PARAMETERS_FILENAME;
    // Minimal/maximal value of beta that the program can handle (bounds).
    double BETA_ABS_MAX = 25;
    double BETA_ABS_MIN = 1.01;
    // Number of graphs to generate during the characterization of the inferred ensemble.
    int CHARACTERIZATION_NB_GRAPHS = 100;
    // Number of new positions to try.
    int MIN_NB_ANGLES_TO_TRY = 100;
    // // Parameter governing the refinied search for optimal position during the maximization phase.
    // int CLOSE_ANGULAR_RANGE_FACTOR = 2;
    // Edgelist filename.
    std::string EDGELIST_FILENAME;
    // Number of points for MC integration in the calculation of expected clustering.
    int EXP_CLUST_NB_INTEGRATION_MC_STEPS = 600;
    // Number of steps for integration for the expected distance between adjacent vertices.
    int EXP_DIST_NB_INTEGRATION_STEPS = 1000;
    // Maximal number of attempts to reach convergence of the updated values of kappa.
    int KAPPA_MAX_NB_ITER_CONV = 500;
    // // Criterion for the change of the nature of the criterion for the convergence during the
    // //   maximization of the angular positions.
    // int LIMIT_FOR_CONVERGENCE_CRITERION = 625;
    // Criterion for changing the calculation of the log-likelihood.
    // int NB_VERTICES_IN_CORE = 1000;
    // Maximal number of steps in the maximization phase.
    // int MAX_NB_ITER_MAXIMIZATION = 15;
    // // Maximal angular step when looking for optimal position.
    // int MINIMAL_ANGULAR_RESOLUTION = 100;
    // Minimal value for the convergence of maximization.
    // double MINIMAL_ANGULAR_CONVERGENCE_THRESHOLD = 0.005;
    // Minimal values for 2 sigmas when drawing new angles to test.
    double MIN_TWO_SIGMAS_NORMAL_DIST = PI / 6;
    // Various numerical/convergence thresholds.
    double NUMERICAL_CONVERGENCE_THRESHOLD_1 = 1e-2;
    double NUMERICAL_CONVERGENCE_THRESHOLD_2 = 5e-5;
    double NUMERICAL_CONVERGENCE_THRESHOLD_3 = 0.5;
    // double MAXIMIZATION_CONVERGENCE_THRESHOLD = 0.01;
    double NUMERICAL_ZERO = 1e-10;
    // // Parameter governing the refinied search for optimal position during the maximization phase.
    // int REFINED_MAX_STEP_LENGTH_DIVISOR = 5;
    // Rootname of output files.
    std::string ROOTNAME_OUTPUT;
    // Random number generator seed.
    int SEED;
  private:
    // Version of the code.
    std::string VERSION = "0.9";
    // Tab.
    std::string TAB = "    ";

  // General internal objects.
  private:
    // Random number generator
    std::mt19937 engine;
    std::uniform_real_distribution<double> uniform_01;
    std::normal_distribution<double> normal_01;
    // Objects mapping the name and the numerical ID of vertices.
    std::map< std::string, int > Name2Num;
    std::vector<std::string> Num2Name;
    // List of degree classes.
    std::set<int> degree_class;
    // Cumulative probability used for the calculation of clustering using MC integration.
    std::map< int, std::map< double, int, std::less<double> > > cumul_prob_kgkp;
    // List containing the order in which the vertices will be considered in the maximization phase.
    std::vector<int> ordered_list_of_vertices;
    // Time stamps.
    double time0, time1, time2, time3, time4, time5, time6, time7;
    time_t time_started, time_ended;
    // Stream used to output the log to file.
    std::ofstream logfile;
    std::streambuf *old_rdbuf;
    // Widths of the columns in output file.
    int width_names;
    int width_values;

  // Objects related to the original graph.
  private:
    // Number of vertices.
    int nb_vertices;
    int nb_vertices_degree_gt_one;
    // Number of edges.
    int nb_edges;
    // Average degree.
    double average_degree;
    // Average local clustering coefficient.
    double average_clustering;
    // Average degree of neighbors.
    std::vector<double> sum_degree_of_neighbors;
    // Local clustering.
    std::vector<double> nbtriangles;
    // Adjacency list.
    std::vector< std::set<int> > adjacency_list;
    // Degree.
    std::vector<int> degree;
    // ID of vertices in each degree class.
    std::map<int, std::vector<int> > degree2vertices;

  // Objects related to the inferred graph ensemble.
  public:
    // Parameter beta (clustering).
    double beta;
  private:
    // Average degree of the inferred ensemble.
    double random_ensemble_average_degree;
    // Average local clustering coefficient of the inferred ensemble.
    double random_ensemble_average_clustering;
    // Maps containing the expected degree of each degree class.
    std::map<int, double> random_ensemble_expected_degree_per_degree_class;
    // Expected degrees in the inferred ensemble (analytical, no finite-size effect).
    std::vector<double> inferred_ensemble_expected_degree;
    // List of kappas by degree class.
    std::map<int, double> random_ensemble_kappa_per_degree_class;
    // Parameter mu (average degree).
    double mu;
    // Hidden variables of the vertices.
    std::vector<double> kappa;
    // Positions of the vertices.
    std::vector<double> theta;
    // sinus and cosinus of theta.
    // std::vector<double> sin_theta;
    // std::vector<double> cos_theta;

  // Objects related to the characterization of the inferred ensemble (by simulations).
  private:
    // Simulated adjacency list.
    std::vector< std::set<int> > simulated_adjacency_list;
    // Simulated degrees (one instance).
    std::vector<double> simulated_degree;
    // Simulated average degree of neighbors (one instance).
    std::vector<double> simulated_sum_degree_of_neighbors;
    // Simulated clustering coefficients (one instance).
    std::vector<double> simulated_nb_triangles;
    // Simulated degree distribution (one instance).
    std::map<int, double> simulated_stat_degree;
    // Simulated average sum of degree of neighbors by degree class.
    std::map<int, double> simulated_stat_sum_degree_neighbors;
    // Simulated average average degree of neighbors by degree class.
    std::map<int, double> simulated_stat_avg_degree_neighbors;
    // Simulated average number of triangles gathered by degree class.
    std::map<int, double> simulated_stat_nb_triangles;
    // Simulated average clustering coefficient by degree class.
    std::map<int, double> simulated_stat_clustering;
    // Characterization of vertices (all instances).
    std::vector< std::vector< std::vector<double> > > characterizing_inferred_ensemble_vprops;
    // Vertices statistics characterization (all instances).
    std::map< int, std::vector<double> > characterizing_inferred_ensemble_vstat;

  // Internal functions.
  private:
    // === Initialization ===
    // Extracts all relevant information about the degrees.
    void analyze_degrees();
    // Computes the average local clustering coefficient.
    void compute_clustering();
    // Loads the graph from an edgelist in a file.
    void load_edgelist();
    // Loads the already inferred parameters.
    void load_already_inferred_parameters();
    // === Infering parameters ===
    // Builds the cumulative distribution to choose degree classes in the calculation of clustering.
    void build_cumul_dist_for_mc_integration();
    // Computes various properties of the random ensemble (before finding optimal positions).
    void compute_random_ensemble_average_degree();
    void compute_random_ensemble_clustering();
    double compute_random_ensemble_clustering_for_degree_class(int d1);
    // Infers the values of kappa.
    void infer_kappas_given_beta_for_all_vertices();
    void infer_kappas_given_beta_for_degree_class();
    // === Embedding ===
    // Computes the log-likelihood between two vertices.
    // double compute_pairwise_loglikelihood(int v1, double t1, int v2, double t2);
    double compute_pairwise_loglikelihood(int v1, double t1, int v2, double t2, bool neighbors);
    // Finds the initial ordering of vertices based on the Eigen Map method.
    void find_initial_ordering(std::vector<int> &ordering, std::vector<double> &raw_theta);
    void infer_initial_positions();
    // Maximizes the log-likelihood by moving the vertices.
    // double find_optimal_angle(int v1);
    // double find_approximate_optimal_angle(int v1);
    void refine_positions();
    int refine_angle(int v1);
    // void refine_angle2(int v1);
    // void refine_angle3(int v1);
    // void refine_angle4(int v1);
    // void refine_angle5(int v1);
    // void infer_optimal_positions();
    void finalize();
    // Loads the network and computes topological properties.
    void initialize();
    // Infers the parameters (kappas, beta) prior to the embedding.
    void infer_parameters();
    // Extracts the onion decomposition (OD) of the graph and orders the vertices according to it.
    void order_vertices();
    // Updates the value of the expected degrees given the inferred positions of theta.
    void compute_inferred_ensemble_expected_degrees();
    // === Characterization of the inferred ensemble using simulations ===
    void analyze_simulated_adjacency_list();
    void generate_simulated_adjacency_list();
    // === Miscellaneous ===
    // Extracts the onion decomposition.
    void extract_onion_decomposition(std::vector<int> &coreness, std::vector<int> &od_layer);
    // Gets and format current date/time.
    std::string format_time(time_t _time);
    // Gets the time since epoch in seconds with decimals.
    double time_since_epoch_in_seconds();
    // === Output ===
    // Writes the inferred connection probability into a file.
    void save_inferred_connection_probability();
    // Writes the inferred properties of vertices into a file.
    void save_inferred_ensemble_characterization();
    // Writes the inferred theta density into a file.
    void save_inferred_theta_density();
    // Writes the inferred info into a file.
    void save_inferred_coordinates();
    // Function associated to the extraction of the components.
    int get_root(int i, std::vector<int> &clust_id);
    void merge_clusters(std::vector<int> &size, std::vector<int> &clust_id);
    void check_connected_components();

  // Public functions to perform the embeddings.
  public:
    // Constructor (empty).
    embeddingS1_t() {};
    // Destructor (empty).
    ~embeddingS1_t() {};
    // Performs the embedding.
    void embed();
    void embed(std::string edgelist_filename) { EDGELIST_FILENAME = edgelist_filename; embed(); };
};





// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::analyze_degrees()
{
  // Resets the value of the average degree.
  average_degree = 0;
  // Resets the number of vertices with a degree greater than 1.
  nb_vertices_degree_gt_one = 0;
  // Resizes the list of degrees.
  degree.clear();
  degree.resize(nb_vertices);
  if(VALIDATION_MODE)
  {
    sum_degree_of_neighbors.clear();
    sum_degree_of_neighbors.resize(nb_vertices, 0);
  }
  // Populates the list of degrees, the average degree and
  //   the list of vertices of each degree class.
  std::set<int>::iterator it, end;
  for(int n(0), k; n<nb_vertices; ++n)
  {
    k = adjacency_list[n].size();
    degree[n] = k;
    average_degree += k;
    degree2vertices[k].push_back(n);
    degree_class.insert(k);
    if(k > 1)
    {
      ++nb_vertices_degree_gt_one;
    }
    if(VALIDATION_MODE)
    {
      it = adjacency_list[n].begin();
      end = adjacency_list[n].end();
      for(; it!=end; ++it)
      {
        sum_degree_of_neighbors[*it] +=     k;
      }
    }
  }
  // Completes the computation of the average degree.
  average_degree /= nb_vertices;
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::analyze_simulated_adjacency_list()
{
  // Initializes the containers.
  simulated_degree.clear();
  simulated_degree.resize(nb_vertices);
  simulated_sum_degree_of_neighbors.clear();
  simulated_sum_degree_of_neighbors.resize(nb_vertices, 0);
  simulated_nb_triangles.clear();
  simulated_nb_triangles.resize(nb_vertices, 0);
  simulated_stat_degree.clear();
  simulated_stat_sum_degree_neighbors.clear();
  simulated_stat_avg_degree_neighbors.clear();
  simulated_stat_nb_triangles.clear();
  simulated_stat_clustering.clear();
  // Analyzes the degree and the average degree of neighbors.
  std::set<int>::iterator it, end;
  for(int v1(0), d1; v1<nb_vertices; ++v1)
  {
    d1 = simulated_adjacency_list[v1].size();
    simulated_degree[v1] = d1;
    it = simulated_adjacency_list[v1].begin();
    end = simulated_adjacency_list[v1].end();
    for(; it!=end; ++it)
    {
      simulated_sum_degree_of_neighbors[*it] += d1;
    }
  }
  // Computes the clustering.
  // Variables.
  double nb_triangles, tmp_val;
  // Vector objects.
  std::vector<int> intersection;
  // Set objects.
  std::set<int> neighbors_v2;
  // Iterator objects.
  std::set<int>::iterator it1, end1, it2, end2;
  std::map<int, std::vector<int> >::iterator it3, end3;
  std::vector<int>::iterator it4;
  // Computes the intersection for the in- and out- neighbourhoods of each vertex.
  for(int v1(0), d1; v1<nb_vertices; ++v1)
  {
    // Resets the local clustering coefficient.
    nb_triangles = 0;
    // Performs the calculation only if degree > 1.
    d1 = simulated_degree[v1];
    if( d1 > 1 )
    {
      // Loops over the neighbors of vertex v1.
      it1 = simulated_adjacency_list[v1].begin();
      end1 = simulated_adjacency_list[v1].end();
      for(; it1!=end1; ++it1)
      {
        // Performs the calculation only if degree > 1.
        if( simulated_degree[*it1] > 1 )
        {
          // Builds an ordered list of the neighbourhood of v2
          it2 = simulated_adjacency_list[*it1].begin();
          end2 = simulated_adjacency_list[*it1].end();
          neighbors_v2.clear();
          for(; it2!=end2; ++it2)
          {
            if(*it1 < *it2) // Ensures that triangles will be counted only once.
            {
              neighbors_v2.insert(*it2);
            }
          }
          // Counts the number of triangles.
          if(neighbors_v2.size() > 0)
          {
            intersection.clear();
            intersection.resize(std::min(simulated_adjacency_list[v1].size(), neighbors_v2.size()));
            it4 = std::set_intersection(simulated_adjacency_list[v1].begin(), simulated_adjacency_list[v1].end(),
                                  neighbors_v2.begin(), neighbors_v2.end(), intersection.begin());
            intersection.resize(it4-intersection.begin());
            nb_triangles += intersection.size();
          }
        }
      }
      // Adds the contribution of vertex v1 to the average clustering coefficient.
      // tmp_val = 2 * nb_triangles / (d1 * (d1 - 1));
      simulated_nb_triangles[v1] = nb_triangles;
      // simulated_nb_triangles[v1] = tmp_val;
    }
  }
  //
  for(int v1(0), d1; v1<nb_vertices; ++v1)
  {
    // Gets the degree of the vertex in the current generated graph.
    d1 = simulated_degree[v1];
    // Adds the key to the various map containers if this degree class has not been encountered yet.
    if( simulated_stat_degree.find(d1) == simulated_stat_degree.end() )
    {
      simulated_stat_degree[d1] = 0;
      simulated_stat_sum_degree_neighbors[d1] = 0;
      simulated_stat_avg_degree_neighbors[d1] = 0;
      simulated_stat_nb_triangles[d1] = 0;
      simulated_stat_clustering[d1] = 0;
    }
    // Compiles the average quantities by degree class.
    simulated_stat_degree[d1] += 1;
    if(d1 > 0)
    {
      simulated_stat_sum_degree_neighbors[d1] += simulated_sum_degree_of_neighbors[v1];
      simulated_stat_avg_degree_neighbors[d1] += simulated_sum_degree_of_neighbors[v1] / d1;
    }
    if(d1 > 1)
    {
      simulated_stat_nb_triangles[d1] += simulated_nb_triangles[v1];
      simulated_stat_clustering[d1] += 2 * simulated_nb_triangles[v1] / d1 / (d1 - 1);
    }
  }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::build_cumul_dist_for_mc_integration()
{
  // Variables.
  int v1;
  double tmp_val, tmp_cumul;
  // Parameters.
  double R = nb_vertices / (2 * PI);
  mu = beta * std::sin(PI / beta) / (2.0 * PI * average_degree);
  // Temporary container.
  std::map<int, double> nkkp;
  // Resets the main object.
  cumul_prob_kgkp.clear();
  // Iterator objects.
  std::set<int>::iterator it1, end1, it2, end2;
  // For all degree classes over 1.
  it1 = degree_class.begin();
  end1 = degree_class.end();
  while(*it1 < 2) { ++it1; }
  for(; it1!=end1; ++it1)
  {
    // Reinitializes the temporary container.
    nkkp.clear();

    // For all degree classes.
    it2 = degree_class.begin();
    end2 = degree_class.end();
    for(; it2!=end2; ++it2)
    {
      // Initializes the temporary container.
      nkkp[*it2] = 0;
    }

    // For all degree classes.
    it2 = degree_class.begin();
    end2 = degree_class.end();
    for(; it2!=end2; ++it2)
    {
      tmp_val = hyp2f1a(beta, -std::pow((PI * R) / (mu * random_ensemble_kappa_per_degree_class[*it1] * random_ensemble_kappa_per_degree_class[*it2]), beta));
      nkkp[*it2] = degree2vertices[*it2].size() * tmp_val / random_ensemble_expected_degree_per_degree_class[*it1];
    }

    // Initializes the cumulating variable.
    tmp_cumul = 0;
    // Initializes the sub-container.
    cumul_prob_kgkp[*it1];
    // For all degree classes.
    it2 = degree_class.begin();
    end2 = degree_class.end();
    for(; it2!=end2; ++it2)
    {

      tmp_val = nkkp[*it2];
      if(tmp_val > NUMERICAL_ZERO)
      {
        // Cumulates the probabilities;
        tmp_cumul += tmp_val;
        // Builds the cumulative distribution.
        cumul_prob_kgkp[*it1][tmp_cumul] = *it2;
      }
    }
  }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::compute_clustering()
{
  // Variables.
  double nb_triangles, tmp_val;
  // Vector objects.
  std::vector<int> intersection;
  // Set objects.
  std::set<int> neighbors_v2;
  // Iterator objects.
  std::vector<int>::iterator it;
  std::set<int>::iterator it1, end1, it2, end2;
  std::map<int, std::vector<int> >::iterator it3, end3;
  if(VALIDATION_MODE)
  {
    // Initializes the individual clustering coefficients.
    nbtriangles.clear();
    nbtriangles.resize(nb_vertices, 0);
  }
  // Computes the intersection for the in- and out- neighbourhoods of each vertex.
  for(int v1(0), d1; v1<nb_vertices; ++v1)
  {
    // Resets the local clustering coefficient.
    nb_triangles = 0;
    // Performs the calculation only if degree > 1.
    d1 = degree[v1];
    if( d1 > 1 )
    {
      // Loops over the neighbors of vertex v1.
      it1 = adjacency_list[v1].begin();
      end1 = adjacency_list[v1].end();
      for(; it1!=end1; ++it1)
      {
        // Performs the calculation only if degree > 1.
        if( degree[*it1] > 1 )
        {
          // Builds an ordered list of the neighbourhood of v2
          it2 = adjacency_list[*it1].begin();
          end2 = adjacency_list[*it1].end();
          neighbors_v2.clear();
          for(; it2!=end2; ++it2)
          {
            if(*it1 < *it2) // Ensures that triangles will be counted only once.
            {
              neighbors_v2.insert(*it2);
            }
          }
          // Counts the number of triangles.
          if(neighbors_v2.size() > 0)
          {
            intersection.clear();
            intersection.resize(std::min(adjacency_list[v1].size(), neighbors_v2.size()));
            it = std::set_intersection(adjacency_list[v1].begin(), adjacency_list[v1].end(),
                                  neighbors_v2.begin(), neighbors_v2.end(), intersection.begin());
            intersection.resize(it-intersection.begin());
            nb_triangles += intersection.size();
          }
        }
      }
      // Adds the contribution of vertex v1 to the average clustering coefficient.
      tmp_val = 2 * nb_triangles / (d1 * (d1 - 1));
      average_clustering += tmp_val;
      if(VALIDATION_MODE)
      {
        nbtriangles[v1] = nb_triangles;
      }
    }
  }
  // Completes the calculation of the average local clustering coefficient.
  average_clustering /= nb_vertices_degree_gt_one;
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::compute_inferred_ensemble_expected_degrees()
{
  // Variables.
  double kappa1, theta1, dtheta, prob;
  double prefactor = nb_vertices / (2 * PI * mu);
  // Computes the new expected degrees given the inferred values of theta.
  inferred_ensemble_expected_degree.clear();
  inferred_ensemble_expected_degree.resize(nb_vertices, 0);
  for(int v1(0); v1<nb_vertices; ++v1)
  {
    kappa1 = kappa[v1];
    theta1 = theta[v1];
    for(int v2(v1 + 1); v2<nb_vertices; ++v2)
    {
      dtheta = PI - std::fabs(PI - std::fabs(theta1 - theta[v2]));
      prob = 1 / (1 + std::pow((prefactor * dtheta) / (kappa1 * kappa[v2]), beta));
      inferred_ensemble_expected_degree[v1] += prob;
      inferred_ensemble_expected_degree[v2] += prob;
    }
  }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::compute_random_ensemble_average_degree()
{
  // Computes the ensemble average degree.
  random_ensemble_average_degree = 0;
  std::map<int, double>::iterator it2 = random_ensemble_expected_degree_per_degree_class.begin();
  std::map<int, double>::iterator end2 = random_ensemble_expected_degree_per_degree_class.end();
  for(; it2!=end2; ++it2)
  {
    random_ensemble_average_degree += it2->second * degree2vertices[it2->first].size();
  }
  random_ensemble_average_degree /= nb_vertices;
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::compute_random_ensemble_clustering()
{
  // Reinitializes the average clustering for the ensemble.
  random_ensemble_average_clustering = 0;
  // Computes the inferred ensemble clustering spectrum for all degree classes over 1.
  std::set<int>::iterator it = degree_class.begin();
  std::set<int>::iterator end = degree_class.end();
  while(*it < 2) { ++it; }
  for(double p23; it!=end; ++it)
  {
    // Computes the clustering coefficient for the degree class and updates the average value.
    p23 = compute_random_ensemble_clustering_for_degree_class(*it);
    // ensemble_clustering_spectrum[*it] = p23;
    random_ensemble_average_clustering += p23 * degree2vertices[*it].size();
  }
  // Completes the calculation of the average clustering coefficient of the inferred ensemble.
  random_ensemble_average_clustering /= nb_vertices_degree_gt_one;
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
double embeddingS1_t::compute_random_ensemble_clustering_for_degree_class(int d1)
{
  // Variables.
  int d2, d3;
  double p12, p13, pc, pz, zmin, zmax, z, z12, z13, da, tmp;
  double p23 = 0;
  // Parameters.
  int nb_points = EXP_CLUST_NB_INTEGRATION_MC_STEPS;
  double R = nb_vertices / (2 * PI);
  mu = beta * std::sin(PI / beta) / (2.0 * PI * average_degree);
  // MC integration.
  for(int i(0); i<nb_points; ++i)
  {
    // Gets the degree of vertex 2;
    d2 = cumul_prob_kgkp[d1].lower_bound(uniform_01(engine))->second;
    // Computes their probability of being connected.
    p12 = hyp2f1a(beta, -std::pow((PI * R) / (mu * random_ensemble_kappa_per_degree_class[d1] * random_ensemble_kappa_per_degree_class[d2]), beta));

    // Gets the degree of vertex 3;
    d3 = cumul_prob_kgkp[d1].lower_bound(uniform_01(engine))->second;
    // Computes their probability of being connected.
    p13 = hyp2f1a(beta, -std::pow((PI * R) / (mu * random_ensemble_kappa_per_degree_class[d1] * random_ensemble_kappa_per_degree_class[d3]), beta));

    //
    pc = uniform_01(engine);
    zmin = 0;
    zmax = PI;
    while( (zmax - zmin) > NUMERICAL_CONVERGENCE_THRESHOLD_2 )
    {
      z = (zmax + zmin) / 2;
      pz = (z / PI) * hyp2f1a(beta, -std::pow( (z * R) / (mu * random_ensemble_kappa_per_degree_class[d1] * random_ensemble_kappa_per_degree_class[d2]), beta)) / p12;
      if(pz > pc)
      {
        zmax = z;
      }
      else
      {
        zmin = z;
      }
    }
    z12 = (zmax + zmin) / 2;

    //
    pc = uniform_01(engine);
    zmin = 0;
    zmax = PI;
    while( (zmax - zmin) > NUMERICAL_CONVERGENCE_THRESHOLD_2 )
    {
      z = (zmax + zmin) / 2;
      pz = (z / PI) * hyp2f1a(beta, -std::pow( (z * R) / (mu * random_ensemble_kappa_per_degree_class[d1] * random_ensemble_kappa_per_degree_class[d3]), beta)) / p13;
      if(pz > pc)
      {
        zmax = z;
      }
      else
      {
        zmin = z;
      }
    }
    z13 = (zmax + zmin) / 2;


    //
    if(uniform_01(engine) < 0.5)
    {
      da = std::fabs(z12 + z13);
    }
    else
    {
      da = std::fabs(z12 - z13);
    }
    da = std::min(da, (2.0 * PI) - da);
    if(da < NUMERICAL_ZERO)
    {
      p23 += 1;
    }
    else
    {
      p23 += 1.0 / (1.0 + std::pow((da * R) / (mu * random_ensemble_kappa_per_degree_class[d2] * random_ensemble_kappa_per_degree_class[d3]), beta));
    }
  }
  // Returns the value of the local clustering coefficient for this degree class.
  return p23 / nb_points;
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
double embeddingS1_t::compute_pairwise_loglikelihood(int v1, double t1, int v2, double t2, bool neighbors)
{
  // Avoids to compute the pairwise loglikelihood of the vertex with itself.
  if(v1 == v2)
  {
    return 0;
  }
  // Computes the angular separation.
  double da = PI - std::fabs(PI - std::fabs(t1 - t2));
  // Computes the loglikelihood between vertives v1 and v2 according to whether they are neighbors or not.
  if(neighbors)
  {
    return -1 * beta * std::log( (nb_vertices * da) / (2 * PI * mu * kappa[v1] * kappa[v2]) );
  }
  else // not neighbors
  {
    return -1 * std::log( 1 + std::pow( (nb_vertices * da) / (2 * PI * mu * kappa[v1] * kappa[v2]), -beta) );
  }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::embed()
{
  // Gets current time.
  time0 = time_since_epoch_in_seconds();
  time_started = std::time(NULL);
  // Initialization.
  initialize();
  // Gets current time.
  time1 = time_since_epoch_in_seconds();
  if(REFINE_MODE)
  {
    // Loads the parameters from the .inf_coord file.
    load_already_inferred_parameters();
  }
  if(!REFINE_MODE)
  {
    // Pre-processing: infers the parameters used for the embedding.
    infer_parameters();
  }
  // Gets current time.
  time2 = time_since_epoch_in_seconds();
  if(!REFINE_MODE)
  {
    // First phase: educated guess of the positions.
    infer_initial_positions();
  }
  // Gets current time.
  time3 = time_since_epoch_in_seconds();
  if(MAXIMIZATION_MODE)
  {
    // Second phase: likelihood maximization.
    // infer_optimal_positions();
    refine_positions();
  }
  // Gets current time.
  time4 = time_since_epoch_in_seconds();
  if(KAPPA_POST_INFERENCE_MODE)
  {
    // Post-processing: adjusts the values of kappa based on the inferred positions.
    infer_kappas_given_beta_for_all_vertices();
  }
  // Gets the current time.
  time5 = time_since_epoch_in_seconds();
  time_ended = std::time(NULL);
  // Saves the inferred coordinates in a file.
  save_inferred_coordinates();
  if(VALIDATION_MODE)
  {
    save_inferred_theta_density();
    save_inferred_connection_probability();
  }
  // Gets the current time.
  time6 = time_since_epoch_in_seconds();
  if(CHARACTERIZATION_MODE)
  {
    save_inferred_ensemble_characterization();
  }
  // Gets the current time.
  time7 = time_since_epoch_in_seconds();
  time_ended = std::time(NULL);
  //
  finalize();
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::extract_onion_decomposition(std::vector<int> &coreness, std::vector<int> &od_layer)
{
  // Builds two lists (std::vector, std::set) of the degree of the vertices.
  std::vector<int> DegreeVec(nb_vertices);
  std::set<std::pair<int, int> > DegreeSet;
  for(int v(0); v<nb_vertices; ++v)
  {
    DegreeSet.insert(std::make_pair(degree[v], v));
    DegreeVec[v] = degree[v];
  }

  // Determines the coreness and the layer based on the modified algorithm of Batagelj and
  //   Zaversnik by Hébert-Dufresne, Grochow and Allard.
  int v1, v2, d1, d2;
  int current_layer = 0;
  // int current_core = 0;
  std::set<int>::iterator it1, end;
  std::set< std::pair<int, int> > LayerSet;
  // std::set< std::pair<int, int> > order_in_layer;
  std::set< std::pair<int, int> >::iterator m_it;
  // std::set< std::pair<int, int> >::iterator o_it, o_end;
  while(DegreeSet.size() > 0)
  {
    // Populates the set containing the vertices belonging to the same layer.
    m_it = DegreeSet.begin();
    d1 = m_it->first;
    // Increases the layer id.
    current_layer += 1;
    // Sets the coreness and the layer the vertices with the same degree.
    while(m_it->first == d1 && m_it != DegreeSet.end())
    {
      // Sets the coreness and the layer.
      v1 = m_it->second;
      coreness[v1] = d1;
      od_layer[v1] = current_layer;
      // Looks at the next vertex.
      ++m_it;
    }
    // Adds the vertices of the layer to the set.
    LayerSet.insert(DegreeSet.begin(), m_it);
    // Removes the vertices of the current layer.
    DegreeSet.erase(DegreeSet.begin(), m_it);
    // Modifies the "effective" degree of the neighbors of the vertices in the layer.
    while(LayerSet.size() > 0)
    {
      // Gets information about the next vertex of the layer.
      v1 = LayerSet.begin()->second;
      // Reduces the "effective" degree of its neighbours.
      it1 = adjacency_list[v1].begin();
      end = adjacency_list[v1].end();
      for(; it1!=end; ++it1)
      {
        // Identifies the neighbor.
        v2 = *it1;
        d2 = DegreeVec[v2];
        // Finds the neighbor in the list "effective" degrees.
        m_it = DegreeSet.find(std::make_pair(d2, v2));
        if(m_it != DegreeSet.end())
        {
          if(d2 > d1)
          {
            DegreeVec[v2] = d2 - 1;
            DegreeSet.erase(m_it);
            DegreeSet.insert(std::make_pair(d2 - 1, v2));
          }
        }
      }
      // Removes the vertices from the LayerSet.
      LayerSet.erase(LayerSet.begin());
    }
  }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::finalize()
{
  // Resets the formating of std::clog.
  std::clog << std::resetiosflags(std::ios::floatfield | std::ios::fixed | std::ios::showpoint);
  // Writes the parameters used in the log for reproductibility.
  if(!QUIET_MODE) { std::clog << std::endl; }
  if(!QUIET_MODE) { std::clog << "Internal parameters and options"                                                                          << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "ALREADY_INFERRED_PARAMETERS_FILENAME   " << ALREADY_INFERRED_PARAMETERS_FILENAME                   << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "BETA_ABS_MAX                           " << BETA_ABS_MAX                                           << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "BETA_ABS_MIN                           " << BETA_ABS_MIN                                           << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "CHARACTERIZATION_MODE                  " << (CHARACTERIZATION_MODE       ? "true" : "false")       << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "CHARACTERIZATION_NB_GRAPHS             " << CHARACTERIZATION_NB_GRAPHS                             << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "CLEAN_RAW_OUTPUT_MODE                  " << (CLEAN_RAW_OUTPUT_MODE       ? "true" : "false")       << std::endl; }
  // if(!QUIET_MODE) { std::clog << TAB << "CLOSE_ANGULAR_RANGE_FACTOR             " << CLOSE_ANGULAR_RANGE_FACTOR                             << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "CUSTOM_BETA                            " << (CUSTOM_BETA                 ? "true" : "false")       << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "CUSTOM_CHARACTERIZATION_NB_GRAPHS      " << (CUSTOM_CHARACTERIZATION_NB_GRAPHS ? "true" : "false") << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "CUSTOM_INFERRED_COORDINATES            " << (CUSTOM_INFERRED_COORDINATES ? "true" : "false")       << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "CUSTOM_OUTPUT_ROOTNAME_MODE            " << (CUSTOM_OUTPUT_ROOTNAME_MODE ? "true" : "false")       << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "CUSTOM_SEED                            " << (CUSTOM_SEED                 ? "true" : "false")       << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "EDGELIST_FILENAME:                     " << EDGELIST_FILENAME                                      << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "EXP_CLUST_NB_INTEGRATION_MC_STEPS      " << EXP_CLUST_NB_INTEGRATION_MC_STEPS                      << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "EXP_DIST_NB_INTEGRATION_STEPS          " << EXP_DIST_NB_INTEGRATION_STEPS                          << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "KAPPA_MAX_NB_ITER_CONV                 " << KAPPA_MAX_NB_ITER_CONV                                 << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "KAPPA_POST_INFERENCE_MODE              " << (KAPPA_POST_INFERENCE_MODE   ? "true" : "false")       << std::endl; }
  // if(!QUIET_MODE) { std::clog << TAB << "LIMIT_FOR_CONVERGENCE_CRITERION        " << LIMIT_FOR_CONVERGENCE_CRITERION                        << std::endl; }
  // if(!QUIET_MODE) { std::clog << TAB << "MAX_NB_ITER_MAXIMIZATION               " << MAX_NB_ITER_MAXIMIZATION                               << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "MAXIMIZATION_MODE                      " << (MAXIMIZATION_MODE           ? "true" : "false")       << std::endl; }
  // if(!QUIET_MODE) { std::clog << TAB << "MINIMAL_ANGULAR_CONVERGENCE_THRESHOLD  " << MINIMAL_ANGULAR_CONVERGENCE_THRESHOLD                  << std::endl; }
  // if(!QUIET_MODE) { std::clog << TAB << "MINIMAL_ANGULAR_RESOLUTION             " << MINIMAL_ANGULAR_RESOLUTION                             << std::endl; }
  // if(!QUIET_MODE) { std::clog << TAB << "NB_VERTICES_IN_CORE                    " << NB_VERTICES_IN_CORE                                    << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "MIN_NB_ANGLES_TO_TRY                   " << MIN_NB_ANGLES_TO_TRY                                   << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "NUMERICAL_CONVERGENCE_THRESHOLD_1      " << NUMERICAL_CONVERGENCE_THRESHOLD_1                      << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "NUMERICAL_CONVERGENCE_THRESHOLD_2      " << NUMERICAL_CONVERGENCE_THRESHOLD_2                      << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "NUMERICAL_CONVERGENCE_THRESHOLD_3      " << NUMERICAL_CONVERGENCE_THRESHOLD_3                      << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "NUMERICAL_ZERO                         " << NUMERICAL_ZERO                                         << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "QUIET_MODE                             " << (QUIET_MODE                  ? "true" : "false")       << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "REFINE_MODE                            " << (REFINE_MODE                 ? "true" : "false")       << std::endl; }
  // if(!QUIET_MODE) { std::clog << TAB << "REFINED_MAX_STEP_LENGTH_DIVISOR        " << REFINED_MAX_STEP_LENGTH_DIVISOR                        << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "ROOTNAME_OUTPUT:                       " << ROOTNAME_OUTPUT                                        << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "SEED                                   " << SEED                                                   << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "VALIDATION_MODE                        " << (VALIDATION_MODE             ? "true" : "false")       << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "VERBOSE_MODE                           " << (VERBOSE_MODE                ? "true" : "false")       << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "VERSION                                " << VERSION                                                << std::endl; }
  if(!QUIET_MODE) { std::clog                                                                                                               << std::endl; }
  if(!QUIET_MODE) { std::clog << "Ended on: "                                     << format_time(time_ended)                                << std::endl; }
  if(!QUIET_MODE) { std::clog << "Elapsed CPU time (embedding):            "          << std::setw(10) << std::fixed << time5 - time0 << " seconds"     << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "initialization:                      "       << std::setw(10) << std::fixed << time1 - time0 << " seconds"     << std::endl; }
  if(!REFINE_MODE)
    if(!QUIET_MODE) { std::clog << TAB << "parameters inference:                "     << std::setw(10) << std::fixed << time2 - time1 << " seconds"     << std::endl; }
  if(!REFINE_MODE)
    if(!QUIET_MODE) { std::clog << TAB << "initial positions:                   "     << std::setw(10) << std::fixed << time3 - time2 << " seconds"     << std::endl; }
  if(REFINE_MODE)
    if(!QUIET_MODE) { std::clog << TAB << "loading previous positions:          "     << std::setw(10) << std::fixed << time3 - time1 << " seconds"     << std::endl; }
  if(MAXIMIZATION_MODE)
    if(!QUIET_MODE) { std::clog << TAB << "refining positions:                  "     << std::setw(10) << std::fixed << time4 - time3 << " seconds"     << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "adjusting kappas:                    "       << std::setw(10) << std::fixed << time5 - time4 << " seconds"     << std::endl; }
  if(VALIDATION_MODE || CHARACTERIZATION_MODE)
    if(!QUIET_MODE) { std::clog << "Elapsed CPU time (validation):           "        << std::setw(10) << std::fixed << time7 - time5 << " seconds"     << std::endl; }
  if(VALIDATION_MODE)
    if(!QUIET_MODE) { std::clog << TAB << "validating embedding:                "     << std::setw(10) << std::fixed << time6 - time5 << " seconds"     << std::endl; }
  if(CHARACTERIZATION_MODE)
    if(!QUIET_MODE) { std::clog << TAB << "characterizing ensemble:             "     << std::setw(10) << std::fixed << time7 - time6 << " seconds"     << std::endl; }
  if(!QUIET_MODE) { std::clog << "==========================================================================================="        << std::endl; }
  // Puts the streams back into place (to avoid a segmentation fault when exiting program).
  if(!QUIET_MODE)
  {
    if(!VERBOSE_MODE)
    {
      logfile.close();
      // Reset the rdbuf of clog.
      std::clog.rdbuf(old_rdbuf);
    }
  }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::find_initial_ordering(std::vector<int> &ordering, std::vector<double> &raw_theta)
{
  if(!QUIET_MODE) { std::clog << std::endl << TAB << "Building the weights matrix..."; }
  // Initializes the sparse matrix.
  Eigen::SparseMatrix<double> L(nb_vertices_degree_gt_one, nb_vertices_degree_gt_one);
  L.reserve(Eigen::VectorXi::Constant(nb_vertices_degree_gt_one, 3));
  // ASSUMES THAT THERE IS ONLY ONE CONNECTED COMPONENT.

  // Sets contiguous IDs excluding vertices with degree 1.
  std::vector<int> newID(nb_vertices, -1);
  int n(0);
  for(int v(0); v<nb_vertices; ++v)
  {
    if(degree[v] > 1)
    {
      newID[v] = n;
      ++n;
    }
  }
  if(n != nb_vertices_degree_gt_one)
    std::cout << "There is something wrong here." << std::endl;

  // First, fills the matrix with the expected distance between connected vertices according to S1.
  double k1, k2, expected_distance, val, t(0), norm(0);
  std::set<int>::iterator it, end;
  for(int v1(0), v2, d1, d2, n1, n2; v1<nb_vertices; ++v1)
  {
    n1 = newID[v1];
    if(n1 != -1)
    {
      // Gets the degree and kappa of the first vertex.
      d1 = degree[v1];
      k1 = random_ensemble_kappa_per_degree_class[d1];
      // Loops over its neighbors.
      it  = adjacency_list[v1].begin();
      end = adjacency_list[v1].end();
      for(; it!=end; ++it)
      {
        // Identifies the second vertex.
        v2 = *it;
        n2 = newID[v2];
        if(n2 != -1)
        {
          // Ensures that every edge is looked only once.
          if(v1 < v2)
          {
            // Gets the degree and kappa of the second vertex.
            d2 = degree[v2];
            k2 = random_ensemble_kappa_per_degree_class[d2];
            // Computes the expected arc length distance between connected pairs of vertices.
            expected_distance = PI * hyp2f1b(beta, -std::pow(nb_vertices / (2.0 * mu * k1 * k2), beta));
            expected_distance /= 2 * hyp2f1a(beta, -std::pow(nb_vertices / (2.0 * mu * k1 * k2), beta));
            if(expected_distance < 0 || expected_distance > PI)
            {
              // Just a verification.
              std::cerr << "Warning. Expected angular distance out of range." << std::endl;
              std::terminate();
            }
            // Transforms the arc length distance into Euclidean distances (unit circle).
            expected_distance = 2 * std::sin(expected_distance / 2);
            // Fills the matrix.
            L.insert(n1, n2) = expected_distance;
            L.insert(n2, n1) = expected_distance;
            // Scaling factor of the weights (see below).
            t += 2 * expected_distance * expected_distance;
            norm += 2;
          }
        }
      }
    }
  }
  t /= norm;
  // Second, takes the appropriate scaled exponential of each entry and computes the "strengths".
  double value;
  std::vector<double> strength(nb_vertices_degree_gt_one, 0);
  for(int k(0), kk(L.outerSize()), v1, v2; k<kk; ++k)
  {
    for(Eigen::SparseMatrix<double>::InnerIterator it(L, k); it; ++it)
    {
      v1 = it.row();
      v2 = it.col();
      value = it.value();
      value = std::exp(-1 * value * value / t);
      L.coeffRef(v1, v2) = value;
      strength[v1] += value;
    }
  }
  // Third, multiply by the left with the inverse matrix of the strengths to tranform the
  //   generalized eigenvalue problem into a "regular" eigenvalue problem.
  for(int k(0), kk(L.outerSize()), v1, v2; k<kk; ++k)
  {
    for(Eigen::SparseMatrix<double>::InnerIterator it(L, k); it; ++it)
    {
      v1 = it.row();
      v2 = it.col();
      value = it.value();
      L.coeffRef(v1, v2) = -1 * value / strength[v1];
    }
  }
  // Fourth, fills the diagonal.
  for(int v1(0); v1<nb_vertices_degree_gt_one; ++v1)
  {
    L.insert(v1, v1) = 1;
  }
  if(!QUIET_MODE) { std::clog << " Matrix built." << std::endl; }

  // Finds the 3 eigenvectors associated with the 3 smallest eigenvalues.
  // Constructs matrix operation object.
  Spectra::SparseGenMatProd<double> op(L);
  // Containers for the eigenvectors.
  Eigen::MatrixXcd evectors;
  // Convergence parameter.
  int ncv = 7;
  // Initializes and computes.
  // if(!QUIET_MODE) { std::clog << std::endl << TAB << "Using Spectra parameter ncv = " << ncv << " to compute eigenvectors...";}
  // eigs.init();
  // int nconv = eigs.compute();
  // Flag indicating whether another iteration is required.
  if(!QUIET_MODE) { std::clog << std::endl; }
  bool keep_going = true;
  while(keep_going)
  {
    // // Checks for errors.
    // if(eigs.info() != Spectra::SUCCESSFUL)
    // {
    //   if(eigs.info() == Spectra::NOT_COMPUTED)
    //     std::cerr << "NOT_COMPUTED" << std::endl;
    //   if(eigs.info() == Spectra::NOT_CONVERGING)
    //     std::cerr << "NOT_CONVERGING" << std::endl;
    //   if(eigs.info() == Spectra::NUMERICAL_ISSUE)
    //     std::cerr << "NUMERICAL_ISSUE" << std::endl;
    //   std::terminate();
    // }
    // Constructs eigen solver object.
    Spectra::GenEigsSolver< double, Spectra::SMALLEST_MAGN, Spectra::SparseGenMatProd<double> > eigs(&op, 3, ncv);
    // Initializes and computes.
    if(!QUIET_MODE) { std::clog << TAB << "Computing eigenvectors using Spectra parameter ncv = " << ncv << "..."; }
    eigs.init();
    int nconv = eigs.compute();
    // Retrieves the eigenvectors.
    evectors = eigs.eigenvectors();

    // Checks whether another iteration is required.
    if(eigs.info() != Spectra::SUCCESSFUL)
    {
      // Checks that the convergence parameter has not reached its maximal value.
      if(ncv == nb_vertices)
      {
        std::cerr << std::endl << "The algorithm computing the eigenvectors (Spectra library) cannot converge at all... Exiting." << std::endl << std::endl;
        std::terminate();
      }
      if(!QUIET_MODE) { std::clog << " Convergence not reached." << std::endl; }
      // Increases the convergence parameter.
      ncv = std::pow(ncv, 1.5);
      if(ncv > nb_vertices)
      {
        ncv = nb_vertices;
      }
    }
    else
    {
      keep_going = false;
    }
  }
  if(!QUIET_MODE) { std::clog << " Convergence reached." << std::endl; }

  /*TEST*/// Initializes the container for the raw angular positions.
  /*TEST*/raw_theta.clear();
  /*TEST*/raw_theta.resize(nb_vertices);
  // Orders the vertices.
  double angle;
  std::set< std::pair<double, int> > ordering_set;
  ordering_set.clear();
  for(int v1(0), n1; v1<nb_vertices; ++v1)
  {
    n1 = newID[v1];
    if(n1 != -1)
    {
      // Positions the vertex into the ordered list.
      angle = std::atan2(evectors(n1, 0).real(), evectors(n1, 1).real()) + PI;
      ordering_set.insert(std::make_pair(angle, v1));
      /*TEST*/raw_theta[v1] = angle;
    }
  }
  // Adds the vertices with degree 1.
  ordering.clear();
  ordering.reserve(nb_vertices);
  std::vector<int> list_neigh_degree_one;
  std::set< std::pair<double, int> >::iterator it2 = ordering_set.begin();
  std::set< std::pair<double, int> >::iterator end2 = ordering_set.end();
  for(int v1; it2!=end2; ++it2)
  {
    // Gets the identity of the vertex.
    v1 = it2->second;
    // Counts the number of neighbors with degree 1
    list_neigh_degree_one.clear();
    list_neigh_degree_one.reserve(degree[v1]);
    it  = adjacency_list[v1].begin();
    end = adjacency_list[v1].end();
    for(int v2; it!=end; ++it)
    {
      v2 = *it;
      if(degree[v2] == 1)
      {
        list_neigh_degree_one.push_back(v2);
      }
    }
    // Adds the neighbors of degree 1 around the vertex in the ordering.
    for(int v(0), vv(list_neigh_degree_one.size() / 2); v<vv; ++v)
    {
      ordering.push_back(list_neigh_degree_one[v]);
      /*TEST*/raw_theta[list_neigh_degree_one[v]] = raw_theta[v1];
    }
    ordering.push_back(v1);
    for(int v(list_neigh_degree_one.size() / 2), vv(list_neigh_degree_one.size()); v<vv; ++v)
    {
      ordering.push_back(list_neigh_degree_one[v]);
      /*TEST*/raw_theta[list_neigh_degree_one[v]] = raw_theta[v1];
    }
  }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
std::string embeddingS1_t::format_time(time_t _time)
{
  // Gets the current date/time.
  struct tm *aTime = gmtime(& _time);
  int year    = aTime->tm_year + 1900;
  int month   = aTime->tm_mon + 1;
  int day     = aTime->tm_mday;
  int hours   = aTime->tm_hour;
  int minutes = aTime->tm_min;
  // Format the string.
  std::string the_time = std::to_string(year) + "/";
  if(month < 10)
    the_time += "0";
  the_time += std::to_string(month) + "/";
  if(day < 10)
    the_time += "0";
  the_time += std::to_string(day) + " " + std::to_string(hours) + ":";
  if(minutes < 10)
    the_time += "0";
  the_time += std::to_string(minutes) + " UTC";
  // Returns the date/time.
  return the_time;
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::generate_simulated_adjacency_list()
{
  // Initializes the container.
  simulated_adjacency_list.clear();
  simulated_adjacency_list.resize(nb_vertices);
  // Generates the adjacency list.
  double kappa1, theta1, dtheta, prob;
  double prefactor = nb_vertices / (2 * PI * mu);
  for(int v1(0); v1<nb_vertices; ++v1)
  {
    kappa1 = kappa[v1];
    theta1 = theta[v1];
    for(int v2(v1 + 1); v2<nb_vertices; ++v2)
    {
      dtheta = PI - std::fabs(PI - std::fabs(theta1 - theta[v2]));
      prob = 1 / (1 + std::pow((prefactor * dtheta) / (kappa1 * kappa[v2]), beta));
      if(uniform_01(engine) < prob)
      {
        simulated_adjacency_list[v1].insert(v2);
        simulated_adjacency_list[v2].insert(v1);
      }
    }
  }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::infer_initial_positions()
{
  if(!QUIET_MODE) { std::clog << "Finding initial positions/ordering..."; }
  if(!QUIET_MODE) { std::clog.flush(); }
  // Gets the original ordering of vertices.
  std::vector<int> ordering;
  std::vector<double> raw_theta;
  find_initial_ordering(ordering, raw_theta);

  if(ordering.size() != nb_vertices)
    std::cout << TAB << "WARNING: All degree-one vertices have not all been reinserted. Does the original edgelist have more than one connected component." << std::endl;

  // Initializes the container for the angular positions.
  theta.clear();
  theta.resize(nb_vertices);

  // Spaces the vertices based on the expected angular distance between (non-)neighbors.
  int v0, v1, n(0);
  double factor, int1, int2, b, tmp;
  double norm = 0;
  double possible_dtheta1, possible_dtheta2;
  double dx = PI / EXP_DIST_NB_INTEGRATION_STEPS;
  double prefactor = nb_vertices / (2 * PI * mu);
  double avg_gap = 2 * PI / nb_vertices;
  std::vector<int>::iterator it = ordering.begin();
  std::vector<int>::iterator end = ordering.end();
  // Identifies the first vertex (takes care of the periodic condition of the circle).
  v0 = *ordering.rbegin();
  for(; it!=end; ++it, ++n)
  {
    // Identifies the second vertex.
    v1 = *it;
    // Numerical integration of the expected angular distance between two consecutive vertices.
    int1 = 0;
    int2 = 0;
    tmp = 0;
    factor = prefactor / ( random_ensemble_kappa_per_degree_class[degree[v0]] * random_ensemble_kappa_per_degree_class[degree[v1]] );
    // Adjusts the sign of beta according to whether the vertices are connected or not.
    b = beta;
    if(adjacency_list[v0].find(v1) == adjacency_list[v0].end())
    {
      b = -beta; // not connected
    }
    // Lower bound of the integral (no contribution if not connected).
    if(b > 0)
    {
      int2 += 0.5;
    }
    // Upper bound of the integral.
    tmp = std::exp(-PI / avg_gap) / ( 1 + std::pow(factor * PI, b) );
    int1 += PI * tmp / 2;
    int2 += tmp / 2;
    // In-between points.
    for(double da = dx; da < PI; da += dx)
    {
      tmp = std::exp(-da / avg_gap) / ( 1 + std::pow(factor * da, b) );
      int1 += da * tmp;
      int2 += tmp;
    }
    // Uses the value of the expected gap to position the vertices.
    /*TEST*/possible_dtheta1 = int1 / int2;
    /*TEST*/possible_dtheta2 = PI - std::fabs(PI - std::fabs(raw_theta[v1] - raw_theta[v0]));
    if(possible_dtheta1 > possible_dtheta2)
    {
      norm += possible_dtheta1;
    }
    else
    {
      norm += possible_dtheta2;
    }
    // norm += int1 / int2;
    theta[v1] = norm;
    // Switches the identities of the vertices prior to the next gap.

    v0 = v1;
  }
  if(!QUIET_MODE) { std::clog << std::endl << TAB << "Sum of the angular positions (before adjustment): " << norm << std::endl; }
  // Rescales the angles to limit their span in the [0, 2 PI) range.
  norm /= 2 * PI;
  for(int v(0); v<nb_vertices; ++v)
  {
    theta[v] /= norm;
  }
  // The angle of the "last" vertex will be 2pi, moves it to 0.
  theta[v1] = 0;
  if(!QUIET_MODE) { std::clog << "                                     .................................................done." << std::endl; }
  if(!QUIET_MODE) { std::clog << std::endl; }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::infer_kappas_given_beta_for_all_vertices()
{
  if(!QUIET_MODE) { std::clog << "Updating values of kappa based on inferred positions..." << std::endl; }
  if(!QUIET_MODE) { std::clog.flush(); }
  // Finds the values of kappa generating the degree classes, given the parameters.
  int cnt = 0;
  bool keep_going = true;
  while( keep_going && (cnt < KAPPA_MAX_NB_ITER_CONV) )
  {
    // Updates the expected degree of individual vertices.
    compute_inferred_ensemble_expected_degrees();
    // Verifies convergence.
    keep_going = false;
    for(int v(0); v<nb_vertices; ++v)
    {
      if(std::fabs(inferred_ensemble_expected_degree[v] - degree[v]) > NUMERICAL_CONVERGENCE_THRESHOLD_3)
      {
        keep_going = true;
        continue;
      }
    }
    // Modifies the value of the kappas prior to the next iteration, if required.
    if(keep_going)
    {
      for(int v(0); v<nb_vertices; ++v)
      {
        kappa[v] += (degree[v] - inferred_ensemble_expected_degree[v]) * uniform_01(engine);
        kappa[v] = std::fabs(kappa[v]);
      }
    }
    ++cnt;
  }
  // Resets the values of kappa since convergence has not been reached.
  if(cnt >= KAPPA_MAX_NB_ITER_CONV)
  {
    if(!QUIET_MODE) { std::clog << TAB << "WARNING: maximum number of iterations reached before convergence. This limit can be"  << std::endl; }
    if(!QUIET_MODE) { std::clog << TAB << "         adjusted by setting the parameters KAPPA_MAX_NB_ITER_CONV to desired value." << std::endl; }
  }
  else
  {
    if(!QUIET_MODE) { std::clog << TAB << "Convergence reached after " << cnt << " iterations." << std::endl; }
  }
  if(!QUIET_MODE) { std::clog << "                                                       ...............................done." << std::endl; }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::infer_kappas_given_beta_for_degree_class()
{
  // Variable.
  double prob_conn;
  // Parameters.
  mu = beta * std::sin(PI / beta) / (2.0 * PI * average_degree);
  // Iterators.
  std::set<int>::iterator it1, it2, end;
  // Initializes the kappas for each degree class.
  it1 = degree_class.begin();
  end = degree_class.end();
  for(; it1!=end; ++it1)
  {
    random_ensemble_kappa_per_degree_class[*it1] = *it1;
  }

  // Finds the values of kappa generating the degree classes, given the parameters.
  int cnt = 0;
  bool keep_going = true;
  while( keep_going && (cnt < KAPPA_MAX_NB_ITER_CONV) )
  {
    // Initializes the expected degree of each degree class.
    it1 = degree_class.begin();
    end = degree_class.end();
    for(; it1!=end; ++it1)
    {
      random_ensemble_expected_degree_per_degree_class[*it1] = 0;
    }
    // Computes the expected degrees given the actual kappas.
    it1 = degree_class.begin();
    end = degree_class.end();
    for(; it1!=end; ++it1)
    {
      it2 = it1;
      prob_conn = hyp2f1a(beta, -std::pow(nb_vertices / (2.0 * mu * random_ensemble_kappa_per_degree_class[*it1] * random_ensemble_kappa_per_degree_class[*it2]), beta));
      random_ensemble_expected_degree_per_degree_class[*it1] += prob_conn * (degree2vertices[*it2].size() - 1);
      for(++it2; it2!=end; ++it2)
      {
        prob_conn = hyp2f1a(beta, -std::pow(nb_vertices / (2.0 * mu * random_ensemble_kappa_per_degree_class[*it1] * random_ensemble_kappa_per_degree_class[*it2]), beta));
        random_ensemble_expected_degree_per_degree_class[*it1] += prob_conn * degree2vertices[*it2].size();
        random_ensemble_expected_degree_per_degree_class[*it2] += prob_conn * degree2vertices[*it1].size();
      }
    }
    // Verifies convergence.
    keep_going = false;
    it1 = degree_class.begin();
    end = degree_class.end();
    for(; it1!=end; ++it1)
    {
      if(std::fabs(random_ensemble_expected_degree_per_degree_class[*it1] - *it1) > NUMERICAL_CONVERGENCE_THRESHOLD_1)
      {
        keep_going = true;
      }
    }
    // Modifies the value of the kappas prior to the next iteration, if required.
    if(keep_going)
    {
      it1 = degree_class.begin();
      end = degree_class.end();
      for(; it1!=end; ++it1)
      {
        random_ensemble_kappa_per_degree_class[*it1] += (*it1 - random_ensemble_expected_degree_per_degree_class[*it1]) * uniform_01(engine);
        random_ensemble_kappa_per_degree_class[*it1] = std::fabs(random_ensemble_kappa_per_degree_class[*it1]);
      }
    }
    ++cnt;
  }
  if(cnt >= KAPPA_MAX_NB_ITER_CONV)
  {
    if(!QUIET_MODE) { std::clog << std::endl; }
    // if(!QUIET_MODE) { std::clog << "WARNING: maximum number of iterations reached before convergence" << std::endl; }
    if(!QUIET_MODE) { std::clog << TAB << "WARNING: maximum number of iterations reached before convergence. This limit can be"  << std::endl; }
    if(!QUIET_MODE) { std::clog << TAB << "         adjusted by setting the parameters KAPPA_MAX_NB_ITER_CONV to desired value." << std::endl; }
    if(!QUIET_MODE) { std::clog << TAB; }
    if(!QUIET_MODE) { std::clog << std::fixed << std::setw(11) << " " << " "; }
  }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::infer_parameters()
{
  if(!QUIET_MODE) { std::clog << "Inferring parameters..."; }
  if(!CUSTOM_BETA)
  {
    if(!QUIET_MODE) { std::clog << std::endl; }
    if(!QUIET_MODE) { std::clog << TAB; }
    if(!QUIET_MODE) { std::clog << std::fixed << std::setw(11) << "beta"            << " "; }
    if(!QUIET_MODE) { std::clog << std::fixed << std::setw(20) << "avg. clustering" << " "; }
    if(!QUIET_MODE) { std::clog << std::endl; }
    // Sets initial value to beta.
    beta = 2 + uniform_01(engine);
    // Iterates until convergence is reached.
    double beta_max = -1;
    double beta_min = 1;
    random_ensemble_average_clustering = 10;  // dummy value to enter the while loop.
    while( true )
    {
      if(!QUIET_MODE) { std::clog << TAB; }
      if(!QUIET_MODE) { std::clog << std::fixed << std::setw(11) << beta << " "; }
      if(!QUIET_MODE) { std::clog.flush(); }
      // Infers the values of kappa.
      infer_kappas_given_beta_for_degree_class();
      // Computes the cumulative distribution used in the MC integration.
      build_cumul_dist_for_mc_integration();
      // Computes the ensemble clustering.
      compute_random_ensemble_clustering();
      if(!QUIET_MODE) { std::clog << std::fixed << std::setw(20) << random_ensemble_average_clustering << " "; }
      if(!QUIET_MODE) { std::clog << std::endl; }

      // Checks if the expected clustering is close enough.
      if( std::fabs(random_ensemble_average_clustering - average_clustering) < NUMERICAL_CONVERGENCE_THRESHOLD_1 )
      {
        break;
      }

      // Modifies the bounds on beta if another iteration is required.
      if(random_ensemble_average_clustering > average_clustering)
      {
        beta_max = beta;
        beta = (beta_max + beta_min) / 2;
        if(beta < BETA_ABS_MIN)
        {
          if(!QUIET_MODE) { std::clog << "WARNING: value too close to 1, using beta = " << std::fixed << std::setw(11) << beta << "."; }
          if(!QUIET_MODE) { std::clog << std::endl; }
          break;
        }
      }
      else
      {
        beta_min = beta;
        if(beta_max == -1)
        {
          beta *= 1.5;
        }
        else
        {
          beta = (beta_max + beta_min) / 2;
        }
      }
      if(beta > BETA_ABS_MAX)
      {
        if(!QUIET_MODE) { std::clog << "WARNING: value too high, using beta = " << std::fixed << std::setw(11) << beta << "."; }
        if(!QUIET_MODE) { std::clog << std::endl; }
        break;
      }
    }
  }
  else
  {
    // Infers the values of kappa.
    infer_kappas_given_beta_for_degree_class();
    // Computes the cumulative distribution used in the MC integration.
    build_cumul_dist_for_mc_integration();
    // Computes the ensemble clustering.
    compute_random_ensemble_clustering();
  }
  // Computes the ensemble average degree.
  compute_random_ensemble_average_degree();
  // Sets the kappas.
  kappa.clear();
  kappa.resize(nb_vertices);
  for(int v(0); v<nb_vertices; ++v)
  {
    kappa[v] = random_ensemble_kappa_per_degree_class[ degree[v] ];
  }

  if(!CUSTOM_BETA)
  {
    if(!QUIET_MODE) { std::clog << "                       "; }
  }
  if(!QUIET_MODE) { std::clog << "...............................................................done."                                         << std::endl; }
  if(!QUIET_MODE) { std::clog                                                                                                                   << std::endl; }
  if(!QUIET_MODE) { std::clog << "Inferred ensemble (random positions)"                                                                         << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "Average degree:                 " << random_ensemble_average_degree                                    << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "Minimum degree:                 " << random_ensemble_expected_degree_per_degree_class.begin()->first   << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "Maximum degree:                 " << (--random_ensemble_expected_degree_per_degree_class.end())->first << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "Average clustering:             " << random_ensemble_average_clustering                                << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "Parameters"                                                                                            << std::endl; }
  if(!CUSTOM_BETA)
  {
    if(!QUIET_MODE) { std::clog << TAB << "  - beta:                       " << beta                                                            << std::endl; }
  }
  else
  {
    if(!QUIET_MODE) { std::clog << TAB << "  - beta:                       " << beta  << " (custom)"                                            << std::endl; }
  }
  if(!QUIET_MODE) { std::clog << TAB << "  - mu:                         " << mu                                                                << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "  - radius_S1 (R):              " << nb_vertices / (2 * PI)                                            << std::endl; }
  if(!QUIET_MODE) { std::clog                                                                                                                   << std::endl; }

  // Cleans containers that are no longer useful.
  cumul_prob_kgkp.clear();
  degree2vertices.clear();
  random_ensemble_expected_degree_per_degree_class.clear();
  // random_ensemble_kappa_per_degree_class.clear();
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::initialize()
{
  // Sets the default rootname for output files.
  if(CUSTOM_OUTPUT_ROOTNAME_MODE == false)
  {
    size_t lastdot = EDGELIST_FILENAME.find_last_of(".");
    if(lastdot == std::string::npos)
    {
      ROOTNAME_OUTPUT = EDGELIST_FILENAME;
    }
    ROOTNAME_OUTPUT = EDGELIST_FILENAME.substr(0, lastdot);
  }
  // // Gets current time.
  // time0 = std::time(NULL);
  // Initializes the random number generator.
  if(!CUSTOM_SEED)
  {
    SEED = std::time(NULL);
  }
  engine.seed(SEED);
  // Change the stream std::clog to a file.
  if(!QUIET_MODE)
  {
    if(!VERBOSE_MODE)
    {
     logfile.open(ROOTNAME_OUTPUT + ".inf_log");
     // Get the rdbuf of clog.
     // We need it to reset the value before exiting.
     old_rdbuf = std::clog.rdbuf();
     // Set the rdbuf of clog.
     std::clog.rdbuf(logfile.rdbuf());
    }
  }
  // Outputs options and parameters on screen.
  if(!QUIET_MODE) { std::clog                                                                                                  << std::endl; }
  if(!QUIET_MODE) { std::clog << "===========================================================================================" << std::endl; }
  if(!QUIET_MODE) { std::clog << "Mercador: accurate embeddings of graphs in the S1 space"                                     << std::endl; }
  if(!QUIET_MODE) { std::clog << "version: "           << VERSION                                                              << std::endl; }
  if(!QUIET_MODE) { std::clog << "started on: "        << format_time(time_started)                                            << std::endl; }
  if(!QUIET_MODE) { std::clog << "edgelist filename: " << EDGELIST_FILENAME                                                    << std::endl; }
  if(REFINE_MODE)
  {
    if(!QUIET_MODE) { std::clog << "inferred positions filename: " << ALREADY_INFERRED_PARAMETERS_FILENAME                     << std::endl; }
  }
  if(!QUIET_MODE) { std::clog << "seed: "              << SEED                                                                 << std::endl; }

  if(!QUIET_MODE) { std::clog                                                                                                  << std::endl; }
  if(!QUIET_MODE) { std::clog << "Loading edgelist..."; }
  load_edgelist();
  if(!QUIET_MODE) { std::clog << "...................................................................done."                    << std::endl; }

  if(!QUIET_MODE) { std::clog                                                                                                  << std::endl; }
  if(!QUIET_MODE) { std::clog << "Checking number of connected components..."; }
  check_connected_components();
  if(!QUIET_MODE) { std::clog << "............................................done."                                           << std::endl; }

  if(!QUIET_MODE) { std::clog                                                                                                  << std::endl; }
  if(!QUIET_MODE) { std::clog << "Analyzing degrees..."; }
  analyze_degrees();
  if(!QUIET_MODE) { std::clog << "..................................................................done."                     << std::endl; }

  if(!QUIET_MODE) { std::clog                                                                                                  << std::endl; }
  if(!QUIET_MODE) { std::clog << "Computing local clustering..."; }
  compute_clustering();
  if(!QUIET_MODE) { std::clog << ".........................................................done."                              << std::endl; }

  if(!QUIET_MODE) { std::clog                                                                                                  << std::endl; }
  if(!QUIET_MODE) { std::clog << "Ordering vertices..."; }
  order_vertices();
  if(!QUIET_MODE) { std::clog << "..................................................................done."                     << std::endl; }
  if(!QUIET_MODE) { std::clog                                                                                                  << std::endl; }

  // Sets the decimal precision of the log.
  std::clog.precision(4);

  // Sets the width of the columns in the output files.
  width_values = 15;
  width_names = 14;
  for(int v(0), l; v<nb_vertices; ++v)
  {
    l = Num2Name[v].length();
    if(l > width_names)
    {
      width_names = l;
    }
  }
  width_names += 1;

  if(!QUIET_MODE) { std::clog << "Properties of the graph"                                                                     << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "Nb vertices:                    " << nb_vertices                                      << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "Nb edges:                       " << nb_edges                                         << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "Average degree:                 " << average_degree                                   << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "Minimum degree:                 " << *(degree_class.begin())                          << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "Maximum degree:                 " << *(--degree_class.end())                          << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "Nb of degree class:             " << degree_class.size()                              << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "Average clustering:             " << average_clustering                               << std::endl; }
  if(!QUIET_MODE) { std::clog                                                                                                  << std::endl; }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::load_already_inferred_parameters()
{
  // Stream object.
  std::stringstream one_line;
  // String objects.
  std::string full_line, name1_str, name2_str, name3_str;
  // Resets the containers.
  kappa.clear();
  kappa.resize(nb_vertices);
  theta.clear();
  theta.resize(nb_vertices);
  // Opens the stream and terminates if the operation did not succeed.
  std::fstream hidden_variables_file(ALREADY_INFERRED_PARAMETERS_FILENAME.c_str(), std::fstream::in);
  if( !hidden_variables_file.is_open() )
  {
    std::cerr << "Could not open file: " << ALREADY_INFERRED_PARAMETERS_FILENAME << "." << std::endl;
    std::terminate();
  }
  // Extracts the beta and mu parameters.
  // Ignores the first 9 lines of the file.
  for(int l(0); l<8; ++l)
  {
    std::getline(hidden_variables_file, full_line);
  }
  // Gets the 10th lines containing the value of beta.
  std::getline(hidden_variables_file, full_line);
  hidden_variables_file >> std::ws;
  one_line.str(full_line);
  one_line >> std::ws;
  one_line >> name1_str >> std::ws;
  one_line >> name1_str >> std::ws;
  one_line >> name1_str >> std::ws;
  one_line >> name1_str >> std::ws;
  beta = std::stod(name1_str);
  one_line.clear();
  // Gets the 11th lines containing the value of mu.
  std::getline(hidden_variables_file, full_line);
  hidden_variables_file >> std::ws;
  one_line.str(full_line);
  one_line >> std::ws;
  one_line >> name1_str >> std::ws;
  one_line >> name1_str >> std::ws;
  one_line >> name1_str >> std::ws;
  one_line >> name1_str >> std::ws;
  mu = std::stod(name1_str);
  one_line.clear();
  // Reads the hidden variables file line by line.
  while( !hidden_variables_file.eof() )
  {
    // Reads a line of the file.
    std::getline(hidden_variables_file, full_line);
    hidden_variables_file >> std::ws;
    one_line.str(full_line);
    one_line >> std::ws;
    one_line >> name1_str >> std::ws;
    // Skips lines of comment.
    if(name1_str == "#")
    {
      one_line.clear();
      continue;
    }
    one_line >> name2_str >> std::ws;
    kappa[ Name2Num[name1_str] ] = std::stod(name2_str);
    one_line >> name3_str >> std::ws;
    theta[ Name2Num[name1_str] ] = std::stod(name3_str);
    one_line.clear();
  }
  // Closes the stream.
  hidden_variables_file.close();
  // Clears unused objects.
  Name2Num.clear();
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::load_edgelist()
{
  // Stream objects.
  std::ifstream edgelist_file;
  std::stringstream one_line;
  // Variables.
  int v1, v2;
  // String objects.
  std::string full_line, name1_str, name2_str;
  // Iterator objects.
  std::map< std::string, int >::iterator name_it;
  // Resets the number of vertices and of edges.
  nb_vertices = 0;
  nb_edges = 0;
  // Resets the containers.
  adjacency_list.clear();
  // Opens the stream and terminates if the operation did not succeed.
  edgelist_file.open(EDGELIST_FILENAME.c_str(), std::ios_base::in);
  if( !edgelist_file.is_open() )
  {
    std::cerr << "Could not open file: " << EDGELIST_FILENAME << "." << std::endl;
    std::terminate();
  }
  else
  {
    // Reads the edgelist file line by line.
    while( !edgelist_file.eof() )
    {
      // Reads a line of the file.
      std::getline(edgelist_file, full_line); edgelist_file >> std::ws;
      one_line.str(full_line); one_line >> std::ws;
      one_line >> name1_str >> std::ws;
      // Skips lines of comment.
      if(name1_str == "#")
      {
        one_line.clear();
        continue;
      }
      one_line >> name2_str >> std::ws;
      one_line.clear();
      // Does not consider self-loops.
      if(name1_str != name2_str)
      {
        // Is name1 new?
        name_it = Name2Num.find(name1_str);
        if( name_it == Name2Num.end() )
        {
          // New vertex.
          v1 = nb_vertices;
          Name2Num[name1_str] = v1;
          Num2Name.push_back(name1_str);
          adjacency_list.push_back(std::set<int>());
          ++nb_vertices;
        }
        else
        {
          // Known vertex.
          v1 = name_it->second;
        }
        // Is name2 new?
        name_it = Name2Num.find(name2_str);
        if( name_it == Name2Num.end() )
        {
          // New vertex.
          v2 = nb_vertices;
          Name2Num[name2_str] = v2;
          Num2Name.push_back(name2_str);
          adjacency_list.push_back(std::set<int>());
          ++nb_vertices;
        }
        else
        {
          // Known vertex.
          v2 = name_it->second;
        }
        // Adds the edge to the adjacency list (multiedges are ignored due to std::set).
        std::pair< std::set<int>::iterator, bool > add1 = adjacency_list[v1].insert(v2);
        std::pair< std::set<int>::iterator, bool > add2 = adjacency_list[v2].insert(v1);
        if(add1.second && add2.second) // Both bool should always agree.
        {
          ++nb_edges;
        }
      }
    }
  }
  // Closes the stream.
  edgelist_file.close();
  if(!REFINE_MODE)
  {
    // Clears unused objects.
    Name2Num.clear();
  }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::order_vertices()
{
  // Containers related to the results of the onion decomposition.
  std::vector<int> coreness(nb_vertices);
  std::vector<int> od_layer(nb_vertices);
  // Extracts the onion decomposition.
  extract_onion_decomposition(coreness, od_layer);
  // Orders the vertices based on their layer.
  std::set< std::pair<int, std::pair<double, int> > > layer_set;
  for(int v(0); v<nb_vertices; ++v)
  {
    layer_set.insert(std::make_pair(od_layer[v], std::make_pair(uniform_01(engine), v)));
  }
  // Fills the ordered list of vertices.
  ordered_list_of_vertices.resize(nb_vertices);
  std::set< std::pair<int, std::pair<double, int> > >::reverse_iterator it = layer_set.rbegin();
  std::set< std::pair<int, std::pair<double, int> > >::reverse_iterator end = layer_set.rend();
  for(int v(0); it!=end; ++it, ++v)
  {
    ordered_list_of_vertices[v] = it->second.second;
  }
  layer_set.clear();
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
int embeddingS1_t::refine_angle(int v1)
{
  // Variables.
  int has_moved = 0;
  double tmp_angle;
  double tmp_loglikelihood;
  double best_angle = theta[v1];
  // Iterators.
  std::set<int>::iterator it2, end;
  // Computes the current loglikelihood.
  double previous_loglikelihood = 0;
  for(int v2(0); v2<nb_vertices; ++v2)
  {
    previous_loglikelihood += compute_pairwise_loglikelihood(v1, best_angle, v2, theta[v2], false);
  }
  it2 = adjacency_list[v1].begin();
  end = adjacency_list[v1].end();
  for(; it2!=end; ++it2)
  {
    previous_loglikelihood += compute_pairwise_loglikelihood(v1, best_angle, *it2, theta[*it2], true);
  }
  double best_loglikelihood = previous_loglikelihood;

  // Computes the weighted average angular positions of the neighbors.
  it2 = adjacency_list[v1].begin();
  end = adjacency_list[v1].end();
  double t2, k2, da;
  double sum_sin_theta = 0;
  double sum_cos_theta = 0;
  for(; it2!=end; ++it2)
  {
    // Identifies the neighbor.
    t2 = theta[*it2];
    k2 = kappa[*it2];

    // Computes the average angle of neighbors.
    sum_sin_theta += std::sin(t2) / (k2 * k2);
    sum_cos_theta += std::cos(t2) / (k2 * k2);
  }
  double average_theta = std::atan2(sum_sin_theta, sum_cos_theta) + PI;
  while(average_theta > (2 * PI))
    average_theta = average_theta - (2 * PI);
  while(average_theta < 0)
    average_theta = average_theta + (2 * PI);

  // Finds the largest angular distance between the neighbor and the average position.
  double max_angle = MIN_TWO_SIGMAS_NORMAL_DIST;
  it2 = adjacency_list[v1].begin();
  for(; it2!=end; ++it2)
  {
    da = PI - std::fabs(PI - std::fabs(average_theta - theta[*it2]));
    if(da > max_angle)
    {
      max_angle = da;
    }
  }
  max_angle /= 2;

  // Considers various wisely chosen new angular positions and keeps the best.
  int _nb_new_angles_to_try = MIN_NB_ANGLES_TO_TRY * std::max(1.0, std::log(nb_vertices));
  for(int e(0); e<_nb_new_angles_to_try; ++e)
  {
    // Gets the angle in the standard range.
    tmp_angle = (normal_01(engine) * max_angle) + average_theta;
    while(tmp_angle > (2 * PI))
      tmp_angle = tmp_angle - (2 * PI);
    while(tmp_angle < 0)
      tmp_angle = tmp_angle + (2 * PI);

    // Computes the local loglikelihood.
    tmp_loglikelihood = 0;
    for(int v2(0); v2<nb_vertices; ++v2)
    {
      tmp_loglikelihood += compute_pairwise_loglikelihood(v1, tmp_angle, v2, theta[v2], false);
    }
    it2 = adjacency_list[v1].begin();
    end = adjacency_list[v1].end();
    for(; it2!=end; ++it2)
    {
      tmp_loglikelihood += compute_pairwise_loglikelihood(v1, tmp_angle, *it2, theta[*it2], true);
    }
    // Preserves the optimal angular sector.
    if(tmp_loglikelihood > best_loglikelihood)
    {
      best_loglikelihood = tmp_loglikelihood;
      best_angle = tmp_angle;
      has_moved = 1;
    }
  }

  // Registers the best position found.
  theta[v1] = best_angle;
  // Returns 1 if the vertex changed position, and 0 otherwise.
  return has_moved;
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::refine_positions()
{
  if(!QUIET_MODE) { std::clog << "Refining the positions..."; }
  if(!QUIET_MODE) { std::clog << std::endl; }

  // // Imposes a global random shift on the angular positions.
  // double theta_shift = 2 * PI * uniform_01(engine);
  // // double theta_shift = PI / 2;
  // for(int i(0); i<nb_vertices; ++i)
  // {
  //   theta[i] = theta[i] + theta_shift;
  //   while(theta[i] >= (2 * PI))
  //     theta[i] = theta[i] - (2 * PI);
  //   while(theta[i] < 0)
  //     theta[i] = theta[i] + (2 * PI);
  // }

  double start_time, stop_time;
  std::string vertices_range;
  int delta_nb_vertices = nb_vertices / 19.999999;
  if(delta_nb_vertices < 1) { delta_nb_vertices = 1; }
  int width = 2 * (std::log10(nb_vertices) + 1) + 6;
  for(int v_i(0), v_f(0), v_m, n_v; v_f<nb_vertices;)
  {
    v_f = (v_i + delta_nb_vertices);
    v_f = (v_f > nb_vertices) ? nb_vertices : v_f;
    n_v = v_f - v_i;
    start_time = time_since_epoch_in_seconds();
    if(!QUIET_MODE) { vertices_range = "[" + std::to_string(v_i+1) + "," + std::to_string(v_f) + "]..."; }
    if(!QUIET_MODE) { std::clog << TAB << "...of vertices " << std::setw(width) << vertices_range; }
    for(v_m = 0; v_i<v_f; ++v_i)
    {
      v_m += refine_angle( ordered_list_of_vertices[v_i] );
    }
    stop_time = time_since_epoch_in_seconds();
    if(!QUIET_MODE) { std::clog << "...done in " << std::setw(6) << std::fixed << stop_time - start_time << " seconds (" << std::setw(std::log10(delta_nb_vertices) + 1) << v_m << "/" << std::setw(std::log10(delta_nb_vertices) + 1) << n_v << " changed position)" << std::endl; }
  }
  // for(int j(0); j<5; ++j)
  // {
  //   start_time = std::time(NULL);
  //   if(!QUIET_MODE) { std::clog << TAB << "refining the positions of the core vertices (" + ( (nb_vertices>NB_VERTICES_IN_CORE) ? std::to_string(NB_VERTICES_IN_CORE) : std::to_string(nb_vertices) ) + " vertices, iteration #" + std::to_string(j+1) + ")"; std::clog.clear(); }
  //   for(int i(0); (i<nb_vertices) && (i<NB_VERTICES_IN_CORE); ++i)
  //   {
  //     refine_angle( ordered_list_of_vertices[i] );
  //   }
  //   stop_time = std::time(NULL);
  //   if(!QUIET_MODE) { std::clog << TAB << "[done in " << stop_time - start_time << " seconds]" << std::endl; }
  // }
  // start_time = std::time(NULL);
  // if(nb_vertices > NB_VERTICES_IN_CORE)
  // {
  //   if(!QUIET_MODE) { std::clog << TAB << "refining the positions of the remaining vertices (" + std::to_string(nb_vertices - NB_VERTICES_IN_CORE) + " vertices)"; std::clog.clear(); }
  //   for(int i(NB_VERTICES_IN_CORE); i<nb_vertices; ++i)
  //   {
  //     refine_angle( ordered_list_of_vertices[i] );
  //   }
  //   stop_time = std::time(NULL);
  //   if(!QUIET_MODE) { std::clog << TAB << "[done in " << stop_time - start_time << " seconds]" << std::endl; }
  // }

  if(!QUIET_MODE) { std::clog << "                         .............................................................done." << std::endl; }
  if(!QUIET_MODE) { std::clog << std::endl; }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::save_inferred_connection_probability()
{
  // Builds the bins.
  std::map<double, int> bins;
  std::map<double, int>::iterator it;
  int bound = 20;
  int cnt = 0;
  double dt = 0.05;
  for(double t(-bound), tt(bound + 0.000001); t<tt; t+=dt, ++cnt)
  {
    bins[std::pow(10, t)] = cnt;
  }
  // Builds the containers.
  std::vector<double> n(bins.size(), 0);
  std::vector<double> p(bins.size(), 0);
  std::vector<double> x(bins.size(), 0);
  // Computes the connection probability for every pair of vertices.
  double k1;
  double t1;
  double da;
  double dist;
  for(int v1(0), i; v1<nb_vertices; ++v1)
  {
    k1 = kappa[v1];
    t1 = theta[v1];
    for(int v2(v1 + 1); v2<nb_vertices; ++v2)
    {
      da = PI - std::fabs( PI - std::fabs(t1 - theta[v2]) );
      dist = (nb_vertices * da) / (2 * PI * mu * k1 * kappa[v2]);
      i = bins.lower_bound(dist)->second;
      n[i] += 1;
      x[i] += dist;
      if(adjacency_list[v1].find(v2) != adjacency_list[v1].end())
      {
        p[i] += 1;
      }
    }
  }
  // Writes the connection probability into a file.
  std::string pconn_filename = ROOTNAME_OUTPUT + ".inf_pconn";
  std::fstream pconn_file(pconn_filename.c_str(), std::fstream::out);
  if( !pconn_file.is_open() )
  {
    std::cerr << "Could not open file: " << pconn_filename << "." << std::endl;
    std::terminate();
  }
  pconn_file << "#";
  pconn_file << std::setw(width_values - 1) << "RescaledDist" << " ";
  pconn_file << std::setw(width_values)     << "InfConnProb"  << " ";
  pconn_file << std::setw(width_values)     << "ThConnProb"   << " ";
  pconn_file << std::endl;
  for(int i(0), ii(n.size()); i<ii; ++i)
  {
    if(n[i] > 0)
    {
      pconn_file << std::setw(width_values) << x[i] / n[i]           << " ";
      pconn_file << std::setw(width_values) << p[i] / n[i]           << " ";
      pconn_file << std::setw(width_values) << 1 / (1 + std::pow(x[i] / n[i], beta) ) << " ";
      pconn_file << std::endl;
    }
  }
  // Closes the stream.
  pconn_file.close();
  if(!QUIET_MODE) { std::clog << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "=> Inferred connection probability saved to " << ROOTNAME_OUTPUT + ".inf_pconn" << std::endl; }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::save_inferred_coordinates()
{
  // Finds the minimal and maximal values of kappa.
  double kappa_min = *std::min_element(kappa.begin(), kappa.end());
  double kappa_max = *std::max_element(kappa.begin(), kappa.end());
  // Computes the hyperbolic radius (adjusts it in case some vertices have a negative radial position).
  double hyp_radius = 2 * std::log( nb_vertices / (PI * mu * kappa_min * kappa_min) );
  double min_radial_position = hyp_radius - 2 * std::log( kappa_min / kappa_max );
  bool warning = false;
  if(min_radial_position < 0)
  {
    hyp_radius += std::fabs(min_radial_position);
    warning = true;
  }
  // Sets the name of the file to write the hidden variables into.
  std::string coordinates_filename = ROOTNAME_OUTPUT + ".inf_coord";
  // // Gets the current time.
  // time1 = std::time(NULL);
  // Opens the stream and terminates if the operation did not succeed.
  std::fstream coordinates_file(coordinates_filename.c_str(), std::fstream::out);
  if( !coordinates_file.is_open() )
  {
    std::cerr << "Could not open file: " << coordinates_filename << "." << std::endl;
    std::terminate();
  }
  // Writes the header.
  coordinates_file << "# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=" << std::endl;
  coordinates_file << "# Embedding started at:  " << format_time(time_started)    << std::endl;
  coordinates_file << "# Ended at:              " << format_time(time_ended)      << std::endl;
  coordinates_file << "# Elapsed CPU time:      " << time5 - time0 << " seconds"  << std::endl;
  coordinates_file << "# Edgelist file:         " << EDGELIST_FILENAME            << std::endl;
  coordinates_file << "#"                                                         << std::endl;
  coordinates_file << "# Parameters"                                              << std::endl;
  coordinates_file << "#   - nb. vertices:      " << nb_vertices                  << std::endl;
  coordinates_file << "#   - beta:              " << beta                         << std::endl;
  coordinates_file << "#   - mu:                " << mu                           << std::endl;
  coordinates_file << "#   - radius_S1:         " << nb_vertices / (2 * PI)       << std::endl;
  coordinates_file << "#   - radius_H2:         " << hyp_radius                   << std::endl;
  coordinates_file << "#   - kappa_min:         " << kappa_min                    << std::endl;
  coordinates_file << "# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=" << std::endl;
  coordinates_file << "#";
  coordinates_file << std::setw(width_names - 1) << "Vertex"          << " ";
  coordinates_file << std::setw(width_values)     << "Inf.Kappa"       << " ";
  coordinates_file << std::setw(width_values)     << "Inf.Theta"       << " ";
  coordinates_file << std::setw(width_values)     << "Inf.Hyp.Rad."    << " ";
  coordinates_file << std::endl;
  // Structure containing the desired method to compared strings (put shorter ones before longer ones).
  struct compare
  {
    bool operator()(const std::pair<std::string, int>& lhs, const std::pair<std::string, int>& rhs) const
    {
      if(lhs.first.size() == rhs.first.size())
      {
        if(lhs.first == rhs.first)
        {
          return lhs.second < rhs.second;
        }
        else
        {
          return lhs.first < rhs.first;
        }
      }
      else
      {
        return lhs.first.size() < rhs.first.size();
      }
    }
  };
  // Writes the hidden variables.
  std::set< std::pair<std::string, int>, compare > ordered_names;
  for(int v(0); v<nb_vertices; ++v)
  {
    ordered_names.insert(std::make_pair(Num2Name[v], v));
  }
  std::set< std::pair<std::string, int> >::iterator it  = ordered_names.begin();
  std::set< std::pair<std::string, int> >::iterator end = ordered_names.end();
  for(int v; it!=end; ++it)
  {
    v = it->second;
    coordinates_file << std::setw(width_names) << it->first                                                      << " ";
    coordinates_file << std::setw(width_values) << kappa[v]                                                       << " ";
    coordinates_file << std::setw(width_values) << theta[v]                                                       << " ";
    coordinates_file << std::setw(width_values) << hyp_radius - 2 * std::log( kappa[v] / kappa_min )              << " ";
    // coordinates_file << std::setw(width) << 2 * std::log( nb_vertices / (PI * mu * kappa_min * kappa[v]) ) << " ";
    coordinates_file << std::endl;
  }
  coordinates_file << "#"                                                                                                          << std::endl;
  coordinates_file << "# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~="        << std::endl;
  coordinates_file << "# Internal parameters and options"                                                                          << std::endl;
  coordinates_file << "# " << TAB << "ALREADY_INFERRED_PARAMETERS_FILENAME   " << ALREADY_INFERRED_PARAMETERS_FILENAME             << std::endl;
  coordinates_file << "# " << TAB << "BETA_ABS_MAX                           " << BETA_ABS_MAX                                     << std::endl;
  coordinates_file << "# " << TAB << "BETA_ABS_MIN                           " << BETA_ABS_MIN                                     << std::endl;
  coordinates_file << "# " << TAB << "CHARACTERIZATION_MODE                  " << (CHARACTERIZATION_MODE       ? "true" : "false") << std::endl;
  coordinates_file << "# " << TAB << "CHARACTERIZATION_NB_GRAPHS             " << CHARACTERIZATION_NB_GRAPHS                       << std::endl;
  coordinates_file << "# " << TAB << "CLEAN_RAW_OUTPUT_MODE                  " << (CLEAN_RAW_OUTPUT_MODE       ? "true" : "false") << std::endl;
  // coordinates_file << "# " << TAB << "CLOSE_ANGULAR_RANGE_FACTOR             " << CLOSE_ANGULAR_RANGE_FACTOR                       << std::endl;
  coordinates_file << "# " << TAB << "CUSTOM_BETA                            " << (CUSTOM_BETA                 ? "true" : "false") << std::endl;
  coordinates_file << "# " << TAB << "CUSTOM_INFERRED_COORDINATES            " << (CUSTOM_INFERRED_COORDINATES ? "true" : "false") << std::endl;
  coordinates_file << "# " << TAB << "CUSTOM_OUTPUT_ROOTNAME_MODE            " << (CUSTOM_OUTPUT_ROOTNAME_MODE ? "true" : "false") << std::endl;
  coordinates_file << "# " << TAB << "CUSTOM_SEED                            " << (CUSTOM_SEED                 ? "true" : "false") << std::endl;
  coordinates_file << "# " << TAB << "EDGELIST_FILENAME:                     " << EDGELIST_FILENAME                                << std::endl;
  coordinates_file << "# " << TAB << "EXP_CLUST_NB_INTEGRATION_MC_STEPS      " << EXP_CLUST_NB_INTEGRATION_MC_STEPS                << std::endl;
  coordinates_file << "# " << TAB << "EXP_DIST_NB_INTEGRATION_STEPS          " << EXP_DIST_NB_INTEGRATION_STEPS                    << std::endl;
  coordinates_file << "# " << TAB << "KAPPA_MAX_NB_ITER_CONV                 " << KAPPA_MAX_NB_ITER_CONV                           << std::endl;
  coordinates_file << "# " << TAB << "KAPPA_POST_INFERENCE_MODE              " << (KAPPA_POST_INFERENCE_MODE   ? "true" : "false") << std::endl;
  // coordinates_file << "# " << TAB << "LIMIT_FOR_CONVERGENCE_CRITERION        " << LIMIT_FOR_CONVERGENCE_CRITERION                  << std::endl;
  // coordinates_file << "# " << TAB << "MAX_NB_ITER_MAXIMIZATION               " << MAX_NB_ITER_MAXIMIZATION                         << std::endl;
  coordinates_file << "# " << TAB << "MAXIMIZATION_MODE                      " << (MAXIMIZATION_MODE           ? "true" : "false") << std::endl;
  // coordinates_file << "# " << TAB << "MINIMAL_ANGULAR_CONVERGENCE_THRESHOLD  " << MINIMAL_ANGULAR_CONVERGENCE_THRESHOLD            << std::endl;
  // coordinates_file << "# " << TAB << "MINIMAL_ANGULAR_RESOLUTION             " << MINIMAL_ANGULAR_RESOLUTION                       << std::endl;
  // coordinates_file << "# " << TAB << "NB_VERTICES_IN_CORE                    " << NB_VERTICES_IN_CORE                              << std::endl;
  coordinates_file << "# " << TAB << "MIN_NB_ANGLES_TO_TRY                   " << MIN_NB_ANGLES_TO_TRY                             << std::endl;
  coordinates_file << "# " << TAB << "NUMERICAL_CONVERGENCE_THRESHOLD_1      " << NUMERICAL_CONVERGENCE_THRESHOLD_1                << std::endl;
  coordinates_file << "# " << TAB << "NUMERICAL_CONVERGENCE_THRESHOLD_2      " << NUMERICAL_CONVERGENCE_THRESHOLD_2                << std::endl;
  coordinates_file << "# " << TAB << "NUMERICAL_CONVERGENCE_THRESHOLD_3      " << NUMERICAL_CONVERGENCE_THRESHOLD_3                << std::endl;
  coordinates_file << "# " << TAB << "NUMERICAL_ZERO                         " << NUMERICAL_ZERO                                   << std::endl;
  coordinates_file << "# " << TAB << "QUIET_MODE                             " << (QUIET_MODE                  ? "true" : "false") << std::endl;
  coordinates_file << "# " << TAB << "REFINE_MODE                            " << (REFINE_MODE                 ? "true" : "false") << std::endl;
  // coordinates_file << "# " << TAB << "REFINED_MAX_STEP_LENGTH_DIVISOR        " << REFINED_MAX_STEP_LENGTH_DIVISOR                  << std::endl;
  coordinates_file << "# " << TAB << "ROOTNAME_OUTPUT:                       " << ROOTNAME_OUTPUT                                  << std::endl;
  coordinates_file << "# " << TAB << "SEED                                   " << SEED                                             << std::endl;
  coordinates_file << "# " << TAB << "VALIDATION_MODE                        " << (VALIDATION_MODE             ? "true" : "false") << std::endl;
  coordinates_file << "# " << TAB << "VERBOSE_MODE                           " << (VERBOSE_MODE                ? "true" : "false") << std::endl;
  coordinates_file << "# " << TAB << "VERSION                                " << VERSION                                          << std::endl;
  coordinates_file << "# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~="        << std::endl;
  // Closes the stream.
  coordinates_file.close();

  if(!QUIET_MODE) { std::clog << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "=> Inferred coordinates saved to " << ROOTNAME_OUTPUT + ".inf_coord" << std::endl; }

  if(CLEAN_RAW_OUTPUT_MODE)
  {
    // Sets the name of the file to write the hidden variables into.
    coordinates_filename = ROOTNAME_OUTPUT + ".inf_coord_raw";
    // Opens the stream and terminates if the operation did not succeed.
    coordinates_file.open(coordinates_filename.c_str(), std::fstream::out);
    if( !coordinates_file.is_open() )
    {
      std::cerr << "Could not open file: " << coordinates_filename << "." << std::endl;
      std::terminate();
    }
    // Writes the hidden variables.
    it  = ordered_names.begin();
    end = ordered_names.end();
    for(int v; it!=end; ++it)
    {
      v = it->second;
      // coordinates_file << it->first                                         << " ";
      coordinates_file << kappa[v]                                          << " ";
      coordinates_file << theta[v]                                          << " ";
      coordinates_file << hyp_radius - 2 * std::log( kappa[v] / kappa_min ) << " ";
      coordinates_file << std::endl;
    }
    // Closes the stream.
    coordinates_file.close();

    if(!QUIET_MODE) { std::clog << std::endl; }
    if(!QUIET_MODE) { std::clog << TAB << "=> Raw inferred coordinates also saved to " << ROOTNAME_OUTPUT + ".inf_coord_raw" << std::endl; }
  }

  if(warning)
  {
    if(!QUIET_MODE) { std::clog << "WARNING: Hyperbolic radius has been adjusted to account for negative radial positions." << std::endl; }
  }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::save_inferred_ensemble_characterization()
{
  if(!QUIET_MODE) { std::clog << std::endl; }
  if(!QUIET_MODE) { std::clog << "Characterizing the inferred ensemble..." << std::endl; }
  // Iterators.
  std::map<int, double>::iterator it2, end2;
  std::map<int, std::vector<double> >::iterator it3, end3;
  // Initializes the containers.
  characterizing_inferred_ensemble_vprops.clear();
  characterizing_inferred_ensemble_vprops.resize(5);
  for(int i(0); i<characterizing_inferred_ensemble_vprops.size(); ++i)
  {
    characterizing_inferred_ensemble_vprops[i].clear();
    characterizing_inferred_ensemble_vprops[i].resize(nb_vertices, std::vector<double>(2, 0));
  }
  characterizing_inferred_ensemble_vstat.clear();
  // Objects to compute the complementary cumulative degree distribution.
  std::vector<double> single_comp_cumul_degree_dist;
  std::vector<double> avg_comp_cumul_degree_dist;
  std::vector<double> std_comp_cumul_degree_dist;
  std::vector<int> nb_comp_cumul_degree_dist;
  // Sets the number of graphs to be generated in function of the size of the original graph.
  if(!CUSTOM_CHARACTERIZATION_NB_GRAPHS)
  {
    if     (nb_vertices < 500  ) { CHARACTERIZATION_NB_GRAPHS = 1000; }
    else if(nb_vertices < 1000 ) { CHARACTERIZATION_NB_GRAPHS = 500;  }
    else if(nb_vertices < 10000) { CHARACTERIZATION_NB_GRAPHS = 100;  }
    else                         { CHARACTERIZATION_NB_GRAPHS = 10;   }
  }
  if(!QUIET_MODE) { std::clog << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "A total of " << CHARACTERIZATION_NB_GRAPHS << " graphs will be generated (chosen in function of the total number" << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "of vertices). To change this value, set the flag 'CUSTOM_CHARACTERIZATION_NB_GRAPHS'" << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "to 'true' and set the variable 'CHARACTERIZATION_NB_GRAPHS' to the desired value." << std::endl; }
  if(!QUIET_MODE) { std::clog << std::endl; }
  // Performs the simulations.
  int delta_nb_graphs = CHARACTERIZATION_NB_GRAPHS / 19.999999;
  if(delta_nb_graphs < 1) { delta_nb_graphs = 1; }
  int width = 2 * (std::log10(CHARACTERIZATION_NB_GRAPHS) + 1) + 6;
  double d1, value;
  std::string graph_range;
  double start_time, stop_time;
  for(int g_i(0), g_f, d_max; g_i<CHARACTERIZATION_NB_GRAPHS;)
  {
    g_f = (g_i + delta_nb_graphs);
    g_f = (g_f > CHARACTERIZATION_NB_GRAPHS) ? CHARACTERIZATION_NB_GRAPHS : g_f;
    start_time = time_since_epoch_in_seconds();
    if(!QUIET_MODE) { graph_range = "[" + std::to_string(g_i+1) + "," + std::to_string(g_f) + "]..."; }
    if(!QUIET_MODE) { std::clog << TAB << "Generating and analyzing graphs " << std::setw(width) << graph_range; }
    for(; g_i<g_f; ++g_i)
    {
      single_comp_cumul_degree_dist.clear();
      generate_simulated_adjacency_list();
      analyze_simulated_adjacency_list();
      for(int v1(0); v1<nb_vertices; ++v1)
      {
        // Degree.
        d1 = simulated_degree[v1];
        characterizing_inferred_ensemble_vprops[0][v1][0] += d1;
        characterizing_inferred_ensemble_vprops[0][v1][1] += d1 * d1;
        if(d1 > 0)
        {
          // Sum of the degree of neighbors.
          value = simulated_sum_degree_of_neighbors[v1];
          characterizing_inferred_ensemble_vprops[1][v1][0] += value;
          characterizing_inferred_ensemble_vprops[1][v1][1] += value * value;
          // Average degree of neighbors.
          value /= d1;
          characterizing_inferred_ensemble_vprops[2][v1][0] += value;
          characterizing_inferred_ensemble_vprops[2][v1][1] += value * value;
        }
        if(d1 > 1)
        {
          // Number of triangles attached on the vertex.
          value = simulated_nb_triangles[v1];
          characterizing_inferred_ensemble_vprops[3][v1][0] += value;
          characterizing_inferred_ensemble_vprops[3][v1][1] += value * value;
          // Clustering coefficient.
          value /= d1 * (d1 - 1) / 2;
          characterizing_inferred_ensemble_vprops[4][v1][0] += value;
          characterizing_inferred_ensemble_vprops[4][v1][1] += value * value;
        }
      }
      // Compiles the various statistics about the degree classes.
      it2 = simulated_stat_degree.begin();
      end2 = simulated_stat_degree.end();
      d_max = -1;
      // single_comp_cumul_degree_dist.clear();
      for(int d, norm; it2!=end2; ++it2)
      {
        // Gets the degree class.
        d = it2->first;
        // Initializes the degree class if it has not been encountered yet.
        if( characterizing_inferred_ensemble_vstat.find(d) == characterizing_inferred_ensemble_vstat.end() )
        {
          characterizing_inferred_ensemble_vstat[d] = std::vector<double>((2 * 5) + 1, 0);
        }
        // // Adjusts the size of the vector containing the complementary cumulative distribution if necessary.
        if(d > d_max)
        {
          d_max = d;
          single_comp_cumul_degree_dist.resize(d_max + 1, 0);
        }
        // Gets the number of vertices in this degree class.
        norm = it2->second;
        // Degree distribution.
        value = simulated_stat_degree[d] / nb_vertices;
        characterizing_inferred_ensemble_vstat[d][0] += value;
        characterizing_inferred_ensemble_vstat[d][1] += value * value;
        // Complementary cumulative degree distribution.
        for(int q(0); q<=d; ++q)
        {
          single_comp_cumul_degree_dist[q] += value;
          // comp_cumul_degree_dist_n[q] += 1;
        }
        // Sum of the degree of neighbors.
        value = simulated_stat_sum_degree_neighbors[d] / norm;
        characterizing_inferred_ensemble_vstat[d][2] += value;
        characterizing_inferred_ensemble_vstat[d][3] += value * value;
        // Average of the degree of neighbors.
        value = simulated_stat_avg_degree_neighbors[d] / norm;
        characterizing_inferred_ensemble_vstat[d][4] += value;
        characterizing_inferred_ensemble_vstat[d][5] += value * value;
        // Number of triangles attached on the vertex.
        value = simulated_stat_nb_triangles[d] / norm;
        characterizing_inferred_ensemble_vstat[d][6] += value;
        characterizing_inferred_ensemble_vstat[d][7] += value * value;
        // Clustering coefficient.
        value = simulated_stat_clustering[d] / norm;
        characterizing_inferred_ensemble_vstat[d][8] += value;
        characterizing_inferred_ensemble_vstat[d][9] += value * value;
        // Counts the number of time the degree class has been observed.
        characterizing_inferred_ensemble_vstat[d][10] += 1;
      }
      // Counts which degree classes have been reached.
      if((d_max + 1) > nb_comp_cumul_degree_dist.size())
      {
        avg_comp_cumul_degree_dist.resize(d_max + 1, 0);
        std_comp_cumul_degree_dist.resize(d_max + 1, 0);
        nb_comp_cumul_degree_dist.resize(d_max + 1, 0);
      }
      for(int r(0); r<=d_max; ++r)
      {
        avg_comp_cumul_degree_dist[r] += single_comp_cumul_degree_dist[r];
        std_comp_cumul_degree_dist[r] += single_comp_cumul_degree_dist[r] * single_comp_cumul_degree_dist[r];
        nb_comp_cumul_degree_dist[r] += 1;
      }
    }
    // Compiles the complementary cumulative degree distribution.
    stop_time = time_since_epoch_in_seconds();
    if(!QUIET_MODE) { std::clog << "...done in " << std::setw(6) << std::fixed << stop_time - start_time << " seconds" << std::endl; }
  }
  // Finalizes the characterization.
  for(int i(0); i<characterizing_inferred_ensemble_vprops.size(); ++i)
  {
    for(int v1(0); v1<nb_vertices; ++v1)
    {
      characterizing_inferred_ensemble_vprops[i][v1][0] /= CHARACTERIZATION_NB_GRAPHS;
      characterizing_inferred_ensemble_vprops[i][v1][1] /= CHARACTERIZATION_NB_GRAPHS;
      characterizing_inferred_ensemble_vprops[i][v1][1] -= characterizing_inferred_ensemble_vprops[i][v1][0] * characterizing_inferred_ensemble_vprops[i][v1][0];
      characterizing_inferred_ensemble_vprops[i][v1][1] *= CHARACTERIZATION_NB_GRAPHS / (CHARACTERIZATION_NB_GRAPHS - 1);
      characterizing_inferred_ensemble_vprops[i][v1][1] = std::sqrt( characterizing_inferred_ensemble_vprops[i][v1][1] );
    }
  }
  it3 = characterizing_inferred_ensemble_vstat.begin();
  end3 = characterizing_inferred_ensemble_vstat.end();
  for(int norm, d; it3!=end3; ++it3)
  {
    d = it3->first;
    norm = characterizing_inferred_ensemble_vstat[d][10];
    for(int i(0); i<10; ++++i)
    {
      characterizing_inferred_ensemble_vstat[d][i + 0] /= norm;
      if(norm > 1)
      {
        characterizing_inferred_ensemble_vstat[d][i + 1] /= norm;
        characterizing_inferred_ensemble_vstat[d][i + 1] -= characterizing_inferred_ensemble_vstat[d][i + 0] * characterizing_inferred_ensemble_vstat[d][i + 0];
        characterizing_inferred_ensemble_vstat[d][i + 1] *= norm / (norm - 1);
        if( characterizing_inferred_ensemble_vstat[d][i + 1] < 0 )
        {
          characterizing_inferred_ensemble_vstat[d][i + 1] = 0;
        }
        else
        {
          characterizing_inferred_ensemble_vstat[d][i + 1] = std::sqrt( characterizing_inferred_ensemble_vstat[d][i + 1] );
        }
      }
      else
      {
        characterizing_inferred_ensemble_vstat[d][i + 1] = 0;
      }
    }
  }
  // Complete the characterization of the complementary cumulative degree distribution.
  for(int i(0), ii(nb_comp_cumul_degree_dist.size()); i<ii; ++i)
  {
    if(nb_comp_cumul_degree_dist[i] > 0)
    {
      avg_comp_cumul_degree_dist[i] /= nb_comp_cumul_degree_dist[i];
      if(nb_comp_cumul_degree_dist[i] > 1)
      {
        std_comp_cumul_degree_dist[i] /= nb_comp_cumul_degree_dist[i];
        std_comp_cumul_degree_dist[i] -= avg_comp_cumul_degree_dist[i] * avg_comp_cumul_degree_dist[i];
        std_comp_cumul_degree_dist[i] *= nb_comp_cumul_degree_dist[i] / (nb_comp_cumul_degree_dist[i] - 1);
        if(std_comp_cumul_degree_dist[i] < 0)
        {
          std_comp_cumul_degree_dist[i] = 0;
        }
        else
        {
          std_comp_cumul_degree_dist[i] = std::sqrt(std_comp_cumul_degree_dist[i]);
        }
      }
      else
      {
        std_comp_cumul_degree_dist[i] = 0;
      }
    }
  }
  if(!QUIET_MODE) { std::clog << "                                       ...............................................done." << std::endl; }
  // Sets the name of the file to write the vertices properties into.
  std::string vertex_properties_filename = ROOTNAME_OUTPUT + ".inf_vprop";
  // Opens the stream and terminates if the operation did not succeed.
  std::fstream vertex_properties_file(vertex_properties_filename.c_str(), std::fstream::out);
  if( !vertex_properties_file.is_open() )
  {
    std::cerr << "Could not open file: " << vertex_properties_filename << "." << std::endl;
    std::terminate();
  }
  // Writes the header.
  vertex_properties_file << "#";
  vertex_properties_file << std::setw(width_names - 1) << "Vertex"          << " ";
  vertex_properties_file << std::setw(width_values)     << "Degree"          << " ";
  vertex_properties_file << std::setw(width_values)     << "Avg.Degree"      << " ";
  vertex_properties_file << std::setw(width_values)     << "Std.Degree"      << " ";
  vertex_properties_file << std::setw(width_values)     << "Sum.Deg.N"       << " ";
  vertex_properties_file << std::setw(width_values)     << "Avg.Sum.Deg.N"   << " ";
  vertex_properties_file << std::setw(width_values)     << "Std.Sum.Deg.N"   << " ";
  vertex_properties_file << std::setw(width_values)     << "Avg.Deg.N"       << " ";
  vertex_properties_file << std::setw(width_values)     << "Avg.Avg.Deg.N"   << " ";
  vertex_properties_file << std::setw(width_values)     << "Std.Avg.Deg.N"   << " ";
  vertex_properties_file << std::setw(width_values)     << "NbTriang"        << " ";
  vertex_properties_file << std::setw(width_values)     << "Avg.NbTriang"    << " ";
  vertex_properties_file << std::setw(width_values)     << "Std.NbTriang"    << " ";
  vertex_properties_file << std::setw(width_values)     << "Clustering"      << " ";
  vertex_properties_file << std::setw(width_values)     << "Avg.Clustering"  << " ";
  vertex_properties_file << std::setw(width_values)     << "Std.Clustering"  << " ";
  vertex_properties_file << std::endl;
  // Structure containing the desired method to compared strings (put shorter ones before longer ones).
  struct compare
  {
    bool operator()(const std::pair<std::string, int>& lhs, const std::pair<std::string, int>& rhs) const
    {
      if(lhs.first.size() == rhs.first.size())
      {
        if(lhs.first == rhs.first)
        {
          return lhs.second < rhs.second;
        }
        else
        {
          return lhs.first < rhs.first;
        }
      }
      else
      {
        return lhs.first.size() < rhs.first.size();
      }
    }
  };
  // Writes the hidden variables.
  std::set< std::pair<std::string, int>, compare > ordered_names;
  for(int v(0); v<nb_vertices; ++v)
  {
    ordered_names.insert(std::make_pair(Num2Name[v], v));
  }
  std::set< std::pair<std::string, int> >::iterator it  = ordered_names.begin();
  std::set< std::pair<std::string, int> >::iterator end = ordered_names.end();
  for(int v, d; it!=end; ++it)
  {
    v = it->second;
    d = degree[v];
    vertex_properties_file << std::setw(width_names) << it->first                                        << " ";
    vertex_properties_file << std::setw(width_values) << degree[v]                                        << " ";
    vertex_properties_file << std::setw(width_values) << characterizing_inferred_ensemble_vprops[0][v][0] << " ";
    vertex_properties_file << std::setw(width_values) << characterizing_inferred_ensemble_vprops[0][v][1] << " ";
    vertex_properties_file << std::setw(width_values) << sum_degree_of_neighbors[v]                       << " ";
    vertex_properties_file << std::setw(width_values) << characterizing_inferred_ensemble_vprops[1][v][0] << " ";
    vertex_properties_file << std::setw(width_values) << characterizing_inferred_ensemble_vprops[1][v][1] << " ";
    if(d > 0)
    {
      vertex_properties_file << std::setw(width_values) << sum_degree_of_neighbors[v] / d                 << " ";
    }
    else
    {
      vertex_properties_file << std::setw(width_values) << sum_degree_of_neighbors[v]                     << " ";
    }
    vertex_properties_file << std::setw(width_values) << characterizing_inferred_ensemble_vprops[2][v][0] << " ";
    vertex_properties_file << std::setw(width_values) << characterizing_inferred_ensemble_vprops[2][v][1] << " ";
    vertex_properties_file << std::setw(width_values) << nbtriangles[v]                                   << " ";
    vertex_properties_file << std::setw(width_values) << characterizing_inferred_ensemble_vprops[3][v][0] << " ";
    vertex_properties_file << std::setw(width_values) << characterizing_inferred_ensemble_vprops[3][v][1] << " ";
    if(d > 1)
    {
      vertex_properties_file << std::setw(width_values) << nbtriangles[v] / (d * (d-1) / 2)               << " ";
    }
    else
    {
      vertex_properties_file << std::setw(width_values) << nbtriangles[v]                                 << " ";
    }
    vertex_properties_file << std::setw(width_values) << characterizing_inferred_ensemble_vprops[4][v][0] << " ";
    vertex_properties_file << std::setw(width_values) << characterizing_inferred_ensemble_vprops[4][v][1] << " ";
    vertex_properties_file << std::endl;
  }
  vertex_properties_file.close();
  if(!QUIET_MODE) { std::clog << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "=> Vertices properties of the inferred ensemble saved to " << ROOTNAME_OUTPUT + ".inf_vprop" << std::endl; }

  // Sets the name of the file to write the vertices properties into.
  std::string vertex_stat_filename = ROOTNAME_OUTPUT + ".inf_vstat";
  // Opens the stream and terminates if the operation did not succeed.
  std::fstream vertex_stat_file(vertex_stat_filename.c_str(), std::fstream::out);
  if( !vertex_stat_file.is_open() )
  {
    std::cerr << "Could not open file: " << vertex_stat_filename << "." << std::endl;
    std::terminate();
  }
  // Writes the header.
  vertex_stat_file << "#";
  vertex_stat_file << std::setw(width_values - 1) << "Degree"          << " ";
  // vertex_stat_file << std::setw(width_values)     << "DegDistObs"      << " ";
  vertex_stat_file << std::setw(width_values)     << "DegDistEns"      << " ";
  vertex_stat_file << std::setw(width_values)     << "DegDistEnsStd"   << " ";
  vertex_stat_file << std::setw(width_values)     << "CDegDistEns"      << " ";
  vertex_stat_file << std::setw(width_values)     << "CDegDistEnsStd"   << " ";
  // vertex_stat_file << std::setw(width_values)     << "SumDegNObs"      << " ";
  vertex_stat_file << std::setw(width_values)     << "SumDegNEns"      << " ";
  vertex_stat_file << std::setw(width_values)     << "SumDegNEnsStd"   << " ";
  // vertex_stat_file << std::setw(width_values)     << "AvgDegNObs"      << " ";
  vertex_stat_file << std::setw(width_values)     << "AvgDegNEns"      << " ";
  vertex_stat_file << std::setw(width_values)     << "AvgDegNEnsStd"   << " ";
  // vertex_stat_file << std::setw(width_values)     << "NbTriangObs"     << " ";
  vertex_stat_file << std::setw(width_values)     << "NbTriangEns"     << " ";
  vertex_stat_file << std::setw(width_values)     << "NbTriangEnsStd"  << " ";
  // vertex_stat_file << std::setw(width_values)     << "ClustObs"        << " ";
  vertex_stat_file << std::setw(width_values)     << "ClustEns"        << " ";
  vertex_stat_file << std::setw(width_values)     << "ClustEnsStd"     << " ";
  vertex_stat_file << std::endl;
  // Writes the hidden variables.
  it3 = characterizing_inferred_ensemble_vstat.begin();
  end3 = characterizing_inferred_ensemble_vstat.end();
  for(int v, d; it3!=end3; ++it3)
  {
    d = it3->first;
    vertex_stat_file << std::setw(width_values) << d                                            << " ";
    vertex_stat_file << std::setw(width_values) << characterizing_inferred_ensemble_vstat[d][0] << " ";
    vertex_stat_file << std::setw(width_values) << characterizing_inferred_ensemble_vstat[d][1] << " ";
    vertex_stat_file << std::setw(width_values) << avg_comp_cumul_degree_dist[d]                << " ";
    vertex_stat_file << std::setw(width_values) << std_comp_cumul_degree_dist[d]                << " ";
    vertex_stat_file << std::setw(width_values) << characterizing_inferred_ensemble_vstat[d][2] << " ";
    vertex_stat_file << std::setw(width_values) << characterizing_inferred_ensemble_vstat[d][3] << " ";
    vertex_stat_file << std::setw(width_values) << characterizing_inferred_ensemble_vstat[d][4] << " ";
    vertex_stat_file << std::setw(width_values) << characterizing_inferred_ensemble_vstat[d][5] << " ";
    vertex_stat_file << std::setw(width_values) << characterizing_inferred_ensemble_vstat[d][6] << " ";
    vertex_stat_file << std::setw(width_values) << characterizing_inferred_ensemble_vstat[d][7] << " ";
    vertex_stat_file << std::setw(width_values) << characterizing_inferred_ensemble_vstat[d][8] << " ";
    vertex_stat_file << std::setw(width_values) << characterizing_inferred_ensemble_vstat[d][9] << " ";
    vertex_stat_file << std::endl;
  }
  vertex_stat_file.close();
  if(!QUIET_MODE) { std::clog << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "=> Inferred ensemble statistics by degree class saved to " << ROOTNAME_OUTPUT + ".inf_vstat" << std::endl; }


  if(!QUIET_MODE) { std::clog << std::endl; }
  if(!QUIET_MODE) { std::clog << "Extracting the original graph statistics by degree class..."; }


  // Extracts the vertex statistics of the original edgelist by degree class.
  std::map<int, double> original_stat_degree;
  std::map<int, double> original_stat_sum_degree_neighbors;
  std::map<int, double> original_stat_avg_degree_neighbors;
  std::map<int, double> original_stat_nb_triangles;
  std::map<int, double> original_stat_clustering;
  for(int v1(0), d1; v1<nb_vertices; ++v1)
  {
    // Gets the degree of the vertex in the current generated graph.
    d1 = degree[v1];
    // Adds the key to the various map containers if this degree class has not been encountered yet.
    if( original_stat_degree.find(d1) == original_stat_degree.end() )
    {
      original_stat_degree[d1] = 0;
      original_stat_sum_degree_neighbors[d1] = 0;
      original_stat_avg_degree_neighbors[d1] = 0;
      original_stat_nb_triangles[d1] = 0;
      original_stat_clustering[d1] = 0;
    }
    // Compiles the average quantities by degree class.
    original_stat_degree[d1] += 1;
    if(d1 > 0)
    {
      original_stat_sum_degree_neighbors[d1] += sum_degree_of_neighbors[v1];
      original_stat_avg_degree_neighbors[d1] += sum_degree_of_neighbors[v1] / d1;
    }
    if(d1 > 1)
    {
      original_stat_nb_triangles[d1] += nbtriangles[v1];
      original_stat_clustering[d1] += 2 * nbtriangles[v1] / d1 / (d1 - 1);
    }
  }
  if(!QUIET_MODE) { std::clog << "...........................done." << std::endl; }

  // Sets the name of the file to write the vertices properties into.
  std::string graph_stat_filename = ROOTNAME_OUTPUT + ".obs_vstat";
  // Opens the stream and terminates if the operation did not succeed.
  std::fstream graph_stat_file(graph_stat_filename.c_str(), std::fstream::out);
  if( !graph_stat_file.is_open() )
  {
    std::cerr << "Could not open file: " << graph_stat_filename << "." << std::endl;
    std::terminate();
  }

  // Writes the header.
  graph_stat_file << "#";
  graph_stat_file << std::setw(width_values - 1) << "Degree"          << " ";
  graph_stat_file << std::setw(width_values)     << "DegDist"         << " ";
  graph_stat_file << std::setw(width_values)     << "CDegDist"        << " ";
  graph_stat_file << std::setw(width_values)     << "SumDegN"         << " ";
  graph_stat_file << std::setw(width_values)     << "AvgDegN"         << " ";
  graph_stat_file << std::setw(width_values)     << "NbTriang"        << " ";
  graph_stat_file << std::setw(width_values)     << "Clust"           << " ";
  graph_stat_file << std::endl;
  // Writes the hidden variables.
  double ccdegdist = 1;
  it2 = original_stat_degree.begin();
  end2 = original_stat_degree.end();
  for(int v, d, norm; it2!=end2; ++it2)
  {
    d = it2->first;
    norm = it2->second;
    graph_stat_file << std::setw(width_values) << d                                                   << " ";
    graph_stat_file << std::setw(width_values) << original_stat_degree[d] / nb_vertices               << " ";
    graph_stat_file << std::setw(width_values) << ccdegdist                                           << " ";
    ccdegdist -= original_stat_degree[d] / nb_vertices;
    graph_stat_file << std::setw(width_values) << original_stat_sum_degree_neighbors[d] / norm        << " ";
    graph_stat_file << std::setw(width_values) << original_stat_avg_degree_neighbors[d] / norm        << " ";
    graph_stat_file << std::setw(width_values) << original_stat_nb_triangles[d] / norm                << " ";
    graph_stat_file << std::setw(width_values) << original_stat_clustering[d] / norm                  << " ";
    graph_stat_file << std::endl;
  }
  graph_stat_file.close();

  if(!QUIET_MODE) { std::clog << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "=> Original graph statistics by degree class saved to " << ROOTNAME_OUTPUT + ".obs_vstat" << std::endl; }
  // // Gets the current time.
  // time2 = std::time(NULL);
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::save_inferred_theta_density()
{
  // Builds the bins.
  std::map<double, int> bins;
  std::map<double, int>::iterator it;
  int cnt = 0;
  int nb_bins = 25;
  double dt = 2 * PI / nb_bins;
  for(double t(dt), tt(2 * PI + 0.001); t<tt; t+=dt, ++cnt)
  {
    bins[t] = cnt;
  }
  // Builds the containers.
  std::vector<double> n(bins.size(), 0);
  // Computes the connection probability for every pair of vertices.
  for(int v1(0), i; v1<nb_vertices; ++v1)
  {
    i = bins.upper_bound(theta[v1])->second;
    n[i] += 1;
  }
  // Writes the connection probability into a file.
  std::string theta_density_filename = ROOTNAME_OUTPUT + ".inf_theta_density";
  std::fstream theta_density_file(theta_density_filename.c_str(), std::fstream::out);
  if( !theta_density_file.is_open() )
  {
    std::cerr << "Could not open file: " << theta_density_filename << "." << std::endl;
    std::terminate();
  }
  theta_density_file << "#";
  theta_density_file << std::setw(width_values - 1) << "Theta"       << " ";
  theta_density_file << std::setw(width_values)     << "InfDensity"  << " ";
  theta_density_file << std::setw(width_values)     << "ThDensity"   << " ";
  theta_density_file << std::endl;
  for(int i(0), ii(n.size()); i<ii; ++i)
  {
    theta_density_file << std::setw(width_values) << ((i + 0.5) * dt)    << " ";
    theta_density_file << std::setw(width_values) << n[i] / nb_vertices  << " ";
    theta_density_file << std::setw(width_values) << 1.0 / (nb_bins - 1) << " ";
    theta_density_file << std::endl;
  }
  // Closes the stream.
  theta_density_file.close();
  if(!QUIET_MODE) { std::clog << std::endl; }
  if(!QUIET_MODE) { std::clog << TAB << "=> Inferred theta density saved to " << ROOTNAME_OUTPUT + ".inf_theta_density" << std::endl; }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
double embeddingS1_t::time_since_epoch_in_seconds()
{
  // double tmp = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
  // return tmp / 1000;
  // https://en.cppreference.com/w/cpp/chrono/c/clock
  clock_t t = clock();
  return ((float)t) / (CLOCKS_PER_SEC);
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
int embeddingS1_t::get_root(int i, std::vector<int> &clust_id)
{
  while(i != clust_id[i])
  {
    clust_id[i] = clust_id[clust_id[i]];
    i = clust_id[i];
  }
  return i;
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::merge_clusters(std::vector<int> &size, std::vector<int> &clust_id)
{
  // Variables.
  int v1, v2, v3, v4;
  // Iterators.
  std::set<int>::iterator it, end;
  // Loops over the vertices.
  for(int i(0); i<nb_vertices; ++i)
  {
    // Loops over the neighbors.
    it  = adjacency_list[i].begin();
    end = adjacency_list[i].end();
    for(; it!=end; ++it)
    {
      if(get_root(i, clust_id) != get_root(*it, clust_id))
      {
        // Adjust the root of vertices.
        v1 = i;
        v2 = *it;
        if(size[v2] > size[v1])
          std::swap(v1, v2);
        v3 = get_root(v1, clust_id);
        v4 = get_root(v2, clust_id);
        clust_id[v4] = v3;
        size[v3] += size[v4];
      }
    }
  }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingS1_t::check_connected_components()
{
  // Vector containing the ID of the component to which each node belongs.
  std::vector<double> Vertex2Prop(nb_vertices, -1);

  // Vector containing the size of the components.
  std::vector<int> connected_components_size;

  // Set ordering the component according to their size.
  std::set< std::pair<int, int> > ordered_connected_components;

  // Starts with every vertex as an isolated cluster.
  std::vector<int> clust_id(nb_vertices);
  std::vector<int> clust_size(nb_vertices, 1);
  for(int v(0); v<nb_vertices; ++v)
  {
    clust_id[v] = v;
  }
  // Merges clusters until the minimal set is obtained.
  merge_clusters(clust_size, clust_id);
  clust_size.clear();
  // Identifies the connected component to which each vertex belongs.
  int nb_conn_comp = 0;
  int comp_id;
  std::map<int, int> CompID;
  for(int v(0); v<nb_vertices; ++v)
  {
    comp_id = get_root(v, clust_id);
    if(CompID.find(comp_id) == CompID.end())
    {
      CompID[comp_id] = nb_conn_comp;
      connected_components_size.push_back(0);
      ++nb_conn_comp;
    }
    Vertex2Prop[v] = CompID[comp_id];
    connected_components_size[CompID[comp_id]] += 1;
  }

  // Orders the size of the components.
  for(int c(0); c<nb_conn_comp; ++c)
  {
    ordered_connected_components.insert( std::make_pair(connected_components_size[c], c) );
  }

  int lcc_id = (--ordered_connected_components.end())->second;
  int lcc_size = (--ordered_connected_components.end())->first;

  if(lcc_size != nb_vertices)
  {
    if(!QUIET_MODE) { std::clog << std::endl; }
    if(!QUIET_MODE) { std::clog << TAB << "- More than one component found!!" << std::endl; }
    if(!QUIET_MODE) { std::clog << TAB << "- " << lcc_size << "/" << nb_vertices << " vertices in the largest component." << std::endl; }
    std::cerr << std::endl;
    std::cerr << "More than one component found (" << lcc_size << "/" << nb_vertices << " vertices in the largest component." << std::endl;

    std::string edgelist_rootname;
    size_t lastdot = EDGELIST_FILENAME.find_last_of(".");
    if(lastdot == std::string::npos)
    {
      edgelist_rootname = EDGELIST_FILENAME;
    }
    edgelist_rootname = EDGELIST_FILENAME.substr(0, lastdot);

    // Sets the name of the file to write the hidden variables into.
    std::string edgelist_filename = edgelist_rootname + "_GC.edge";
    // Opens the stream and terminates if the operation did not succeed.
    std::fstream edgelist_file(edgelist_filename.c_str(), std::fstream::out);
    if( !edgelist_file.is_open() )
    {
      std::cerr << "Could not open file: " << edgelist_filename << "." << std::endl;
      std::terminate();
    }

    std::set<int>::iterator it, end;
    for(int v1(0), v2, c1, c2; v1<nb_vertices; ++v1)
    {
      c1 = Vertex2Prop[v1];
      if(c1 == lcc_id)
      {
        it  = adjacency_list[v1].begin();
        end = adjacency_list[v1].end();
        for(; it!=end; ++it)
        {
          v2 = *it;
          c2 = Vertex2Prop[v2];
          if(c2 == lcc_id)
          {
            if(v1 < v2)
            {
              edgelist_file << std::setw(width_names) << Num2Name[v1] << " ";
              edgelist_file << std::setw(width_names) << Num2Name[v2] << " ";
              edgelist_file << std::endl;
            }
          }
        }
      }
    }
    // Closes the stream.
    edgelist_file.close();

    if(!QUIET_MODE) { std::clog << TAB << "- Edges belonging to the largest component saved to " << edgelist_rootname + "_GC.edge." << std::endl; }
    if(!QUIET_MODE) { std::clog << TAB << "- Please rerun the program using this new edgelist." << std::endl; }
    if(!QUIET_MODE) { std::clog << std::endl; }
    // if(!QUIET_MODE) { std::clog << "                                          "; }

    if(QUIET_MODE)  { std::clog << std::endl; }
    std::cerr << "Edges belonging to the largest component saved to " << edgelist_rootname + "_GC.edge. Please rerun the program using this new edgelist." << std::endl;
    std::cerr << std::endl;
    std::terminate();
  }
}





#endif // EMBEDDINGS1_HPP_INCLUDED


// // =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// // =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// double embeddingS1_t::compute_pairwise_loglikelihood(int v1, double t1, int v2, double t2)
// {
//   // Avoids to compute the pairwise loglikelihood of the vertex with itself.
//   if(v1 == v2)
//   {
//     return 0;
//   }
//   // Computes the angular separation.
//   double da = PI - std::fabs(PI - std::fabs(t1 - t2));
//   // Sets the first vertices to be the one with smaller degree.
//   if(degree[v1] > degree[v2])
//   {
//     std::swap(v1, v2);
//   }
//   // Computes the loglikelihood between vertives v1 and v2 (checks with the vertex with smaller degree).
//   if(adjacency_list[v1].find(v2) == adjacency_list[v1].end()) // not neighbors
//   {
//     return -1 * std::log( 1 + std::pow( (nb_vertices * da) / (2 * PI * mu * kappa[v1] * kappa[v2]), -beta) );
//   }
//   else // neighbors
//   {
//     return -1 * std::log( 1 + std::pow( (nb_vertices * da) / (2 * PI * mu * kappa[v1] * kappa[v2]),  beta) );
//   }
// }


// // =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// // =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// double embeddingS1_t::find_optimal_angle(int v1)
// {
//   // Variables.
//   double tmp_angle;
//   double tmp_loglikelihood;
//   double previous_angle = theta[v1];
//   double best_angle = previous_angle;
//   // double max_step_length = 2 * PI / MINIMAL_ANGULAR_RESOLUTION;
//   double max_step_length = 2 * 2 * PI / nb_vertices;
//   // Computes the current loglikelihood.
//   double previous_loglikelihood = 0;
//   for(int v2(0); v2<nb_vertices; ++v2)
//   {
//     previous_loglikelihood += compute_pairwise_loglikelihood(v1, best_angle, v2, theta[v2]);
//   }
//   double best_loglikelihood = previous_loglikelihood;
//   // For "all" angles, starting at a random position.
//   double t0 = 2 * PI * uniform_01(engine);
//   double t1 = t0;
//   while( (t1 - t0) < (2 * PI) )
//   {
//     // Gets the angle in the standard range.
//     tmp_angle = (t1 > (2 * PI)) ? (t1 - (2 * PI)) : t1;
//     // Computes the local loglikelihood.
//     tmp_loglikelihood = 0;
//     for(int v2(0); v2<nb_vertices; ++v2)
//     {
//       tmp_loglikelihood += compute_pairwise_loglikelihood(v1, tmp_angle, v2, theta[v2]);
//     }
//     // Preserves the optimal angular sector.
//     if(tmp_loglikelihood > best_loglikelihood)
//     {
//       best_loglikelihood = tmp_loglikelihood;
//       best_angle = tmp_angle;
//     }
//     // Increases the angle.
//     t1 += max_step_length * uniform_01(engine);
//   }
//   // // Refines the position around the best position found above.
//   // double close_angular_range = CLOSE_ANGULAR_RANGE_FACTOR * 2 * PI / nb_vertices;
//   // double refined_max_step_length = 2 * PI / nb_vertices / REFINED_MAX_STEP_LENGTH_DIVISOR;
//   // t0 = best_angle - close_angular_range ;
//   // t0 = (t0 < 0) ? (t0 + (2 * PI)) : t0;
//   // t1 = t0;
//   // while( (t1 - t0) < (2 * close_angular_range) )
//   // {
//   //   // Gets the angle in the standard range.
//   //   tmp_angle = (t1 > (2 * PI)) ? (t1 - (2 * PI)) : t1;
//   //   // Computes the local loglikelihood.
//   //   tmp_loglikelihood = 0;
//   //   for(int v2(0); v2<nb_vertices; ++v2)
//   //   {
//   //     tmp_loglikelihood += compute_pairwise_loglikelihood(v1, tmp_angle, v2, theta[v2]);
//   //   }
//   //   // Preserves the optimal angular sector.
//   //   if(tmp_loglikelihood > best_loglikelihood)
//   //   {
//   //     best_loglikelihood = tmp_loglikelihood;
//   //     best_angle = tmp_angle;
//   //   }
//   //   // Increases the angle.
//   //   t1 += refined_max_step_length * uniform_01(engine);
//   // }
//   // Registers the best position found.
//   theta[v1] = best_angle;
//   // Returns the variation in the global loglikelihood.
//   // return (best_loglikelihood - previous_loglikelihood) / nb_edges;
//   return PI - std::fabs( PI - std::fabs(best_angle - previous_angle) );
// }


// // =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// // =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// void embeddingS1_t::refine_angle5(int v1)
// {
//   // Variables.
//   double tmp_angle;
//   double tmp_loglikelihood;
//   double best_angle = theta[v1];
//   // Computes the current loglikelihood.
//   double previous_loglikelihood = 0;
//   for(int v2(0); v2<nb_vertices; ++v2)
//   {
//     previous_loglikelihood += compute_pairwise_loglikelihood(v1, best_angle, v2, theta[v2]);
//   }
//   double best_loglikelihood = previous_loglikelihood;

//   // Computes the weighted average angular positions of the neighbors.
//   std::set<int>::iterator it2 = adjacency_list[v1].begin();
//   std::set<int>::iterator end = adjacency_list[v1].end();
//   double t2, k2, da;
//   double sum_sin_theta = 0;
//   double sum_cos_theta = 0;
//   for(; it2!=end; ++it2)
//   {
//     // Identifies the neighbor.
//     t2 = theta[*it2];
//     k2 = kappa[*it2];

//     // Computes the average angle of neighbors.
//     sum_sin_theta += std::sin(t2) / (k2 * k2);
//     sum_cos_theta += std::cos(t2) / (k2 * k2);
//   }
//   double average_theta = std::atan2(sum_sin_theta, sum_cos_theta);
//   while(average_theta > (2 * PI))
//     average_theta = average_theta - (2 * PI);
//   while(average_theta < 0)
//     average_theta = average_theta + (2 * PI);

//   // Finds the largest angular distance between the neighbor and the average position.
//   // double max_angle = PI / 4;
//   double max_angle = PI / 6;
//   it2 = adjacency_list[v1].begin();
//   for(; it2!=end; ++it2)
//   {
//     da = PI - std::fabs(PI - std::fabs(average_theta - theta[*it2]));
//     if(da > max_angle)
//     {
//       max_angle = da;
//     }
//   }
//   max_angle /= 2;

//   // std::clog << average_theta << "    " << max_angle << std::endl;
//   // std::clog.flush();

//   // Considers various wisely chosen new angular positions and keeps the best.
//   int _nb_new_angles_to_try = NB_NEW_ANGLES_TO_TRY * std::log(nb_vertices);
//   // int _nb_new_angles_to_try = max_angle * nb_vertices / PI;
//   // if(_nb_new_angles_to_try < NB_NEW_ANGLES_TO_TRY)
//   //   _nb_new_angles_to_try = NB_NEW_ANGLES_TO_TRY;
//   for(int e(0); e<_nb_new_angles_to_try; ++e)
//   {
//     // Gets the angle in the standard range.
//     tmp_angle = (normal_01(engine) * max_angle) + average_theta;
//     while(tmp_angle > (2 * PI))
//       tmp_angle = tmp_angle - (2 * PI);
//     while(tmp_angle < 0)
//       tmp_angle = tmp_angle + (2 * PI);

//     // Computes the local loglikelihood.
//     tmp_loglikelihood = 0;
//     for(int v2(0); v2<nb_vertices; ++v2)
//     {
//       tmp_loglikelihood += compute_pairwise_loglikelihood(v1, tmp_angle, v2, theta[v2]);
//     }
//     // Preserves the optimal angular sector.
//     if(tmp_loglikelihood > best_loglikelihood)
//     {
//       best_loglikelihood = tmp_loglikelihood;
//       best_angle = tmp_angle;
//     }
//   }

//   // Registers the best position found.
//   theta[v1] = best_angle;
// }


// // =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// // =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// void embeddingS1_t::refine_angle3(int v1)
// {
//   // Variables.
//   double tmp_angle;
//   double tmp_loglikelihood;
//   double previous_angle = theta[v1];
//   double best_angle = previous_angle;
//   // double max_step_length = 2 * PI / MINIMAL_ANGULAR_RESOLUTION;
//   double max_step_length = 2 * 2 * PI / nb_vertices;
//   // Computes the current loglikelihood.
//   double previous_loglikelihood = 0;
//   for(int v2(0); v2<nb_vertices; ++v2)
//   {
//     previous_loglikelihood += compute_pairwise_loglikelihood(v1, best_angle, v2, theta[v2]);
//   }
//   double best_loglikelihood = previous_loglikelihood;
//   // For "all" angles, starting at a random position.
//   double t0 = 2 * PI * uniform_01(engine);
//   double t1 = t0;
//   while( (t1 - t0) < (2 * PI) )
//   {
//     // Gets the angle in the standard range.
//     // tmp_angle = (t1 > (2 * PI)) ? (t1 - (2 * PI)) : t1;
//     tmp_angle = t1;
//     while(tmp_angle > (2 * PI))
//       tmp_angle = tmp_angle - (2 * PI);
//     while(tmp_angle < 0)
//       tmp_angle = tmp_angle + (2 * PI);
//     // Computes the local loglikelihood.
//     tmp_loglikelihood = 0;
//     for(int v2(0); v2<nb_vertices; ++v2)
//     {
//       tmp_loglikelihood += compute_pairwise_loglikelihood(v1, tmp_angle, v2, theta[v2]);
//     }
//     // Preserves the optimal angular sector.
//     if(tmp_loglikelihood > best_loglikelihood)
//     {
//       best_loglikelihood = tmp_loglikelihood;
//       best_angle = tmp_angle;
//     }
//     // Increases the angle.
//     t1 += max_step_length * uniform_01(engine);
//   }
//   // // Refines the position around the best position found above.
//   // // double close_angular_range = CLOSE_ANGULAR_RANGE_FACTOR * 2 * PI / nb_vertices;
//   // // double refined_max_step_length = 2 * PI / nb_vertices / REFINED_MAX_STEP_LENGTH_DIVISOR;
//   // // t0 = best_angle - close_angular_range ;
//   // t0 = best_angle_prov - (200 * max_step_length) ;
//   // // t0 = (t0 < 0) ? (t0 + (2 * PI)) : t0;
//   // while(t0 > (2 * PI))
//   //   t0 = t0 - (2 * PI);
//   // while(t0 < 0)
//   //   t0 = t0 + (2 * PI);
//   // t1 = t0;
//   // while( (t1 - t0) < (2 * 200 * max_step_length) )
//   // {
//   //   // Gets the angle in the standard range.
//   //   // tmp_angle = (t1 > (2 * PI)) ? (t1 - (2 * PI)) : t1;
//   //   tmp_angle = t1;
//   //   while(tmp_angle > (2 * PI))
//   //     tmp_angle = tmp_angle - (2 * PI);
//   //   while(tmp_angle < 0)
//   //     tmp_angle = tmp_angle + (2 * PI);
//   //   // Computes the local loglikelihood.
//   //   tmp_loglikelihood = 0;
//   //   for(int v2(0); v2<nb_vertices; ++v2)
//   //   {
//   //     tmp_loglikelihood += compute_pairwise_loglikelihood(v1, tmp_angle, v2, theta[v2]);
//   //   }
//   //   // Preserves the optimal angular sector.
//   //   if(tmp_loglikelihood > best_loglikelihood)
//   //   {
//   //     best_loglikelihood = tmp_loglikelihood;
//   //     best_angle = tmp_angle;
//   //   }
//   //   // Increases the angle.
//   //   t1 += max_step_length * uniform_01(engine);
//   // }
//   // Registers the best position found.
//   theta[v1] = best_angle;
//   // Returns the variation in the global loglikelihood.
//   // return (best_loglikelihood - previous_loglikelihood) / nb_edges;
//   // return PI - std::fabs( PI - std::fabs(best_angle - previous_angle) );
// }


// // =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// // =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// void embeddingS1_t::refine_angle4(int v1)
// {
//   // Variables.
//   double tmp_angle;
//   double best_angle_prov;
//   double tmp_loglikelihood;
//   double tmp_partial_loglikelihood;
//   double previous_angle = theta[v1];
//   double best_angle = previous_angle;
//   // double max_step_length = 2 * PI / MINIMAL_ANGULAR_RESOLUTION;
//   double max_step_length = 2 * 2 * PI / nb_vertices;
//   // Computes the current loglikelihood.
//   double previous_loglikelihood = 0;
//   for(int v2(0); v2<nb_vertices; ++v2)
//   {
//     previous_loglikelihood += compute_pairwise_loglikelihood(v1, best_angle, v2, theta[v2]);
//   }
//   double best_loglikelihood = previous_loglikelihood;
//   // For "all" angles, starting at a random position.
//   std::set<int>::iterator it2;
//   std::set<int>::iterator end;
//   double best_partial_loglikelihood = -1e100;
//   double t0 = 2 * PI * uniform_01(engine);
//   double t1 = t0;
//   while( (t1 - t0) < (2 * PI) )
//   {
//     // Gets the angle in the standard range.
//     // tmp_angle = (t1 > (2 * PI)) ? (t1 - (2 * PI)) : t1;
//     tmp_angle = t1;
//     while(tmp_angle >= (2 * PI))
//       tmp_angle = tmp_angle - (2 * PI);
//     // while(tmp_angle < 0)
//     //   tmp_angle = tmp_angle + (2 * PI);
//     // Computes the local loglikelihood.
//     tmp_partial_loglikelihood = 0;
//     it2 = adjacency_list[v1].begin();
//     end = adjacency_list[v1].end();
//     for(; it2!=end; ++it2)
//     {
//       tmp_partial_loglikelihood += compute_pairwise_loglikelihood(v1, tmp_angle, *it2, theta[*it2]);
//     }
//     // std::cout << tmp_partial_loglikelihood << std::endl;
//     // Preserves the optimal angular sector.
//     if(tmp_partial_loglikelihood > best_partial_loglikelihood)
//     {
//       best_partial_loglikelihood = tmp_partial_loglikelihood;
//       best_angle_prov = tmp_angle;
//     }
//     // Increases the angle.
//     t1 += max_step_length * uniform_01(engine);
//   }
//   // Refines the position around the best position found above.
//   // double close_angular_range = CLOSE_ANGULAR_RANGE_FACTOR * 2 * PI / nb_vertices;
//   // double refined_max_step_length = 2 * PI / nb_vertices / REFINED_MAX_STEP_LENGTH_DIVISOR;
//   // t0 = best_angle - close_angular_range ;
//   // t0 = best_angle_prov - (250 * max_step_length) ;
//   t0 = best_angle_prov - (PI / 12) ;
//   // t0 = (t0 < 0) ? (t0 + (2 * PI)) : t0;
//   // while(t0 > (2 * PI))
//   //   t0 = t0 - (2 * PI);
//   while(t0 < 0)
//     t0 = t0 + (2 * PI);
//   t1 = t0;
//   // while( (t1 - t0) < (2 * 250 * max_step_length) )
//   while( (t1 - t0) < (2 * PI / 12) )
//   {
//     // Gets the angle in the standard range.
//     // tmp_angle = (t1 > (2 * PI)) ? (t1 - (2 * PI)) : t1;
//     tmp_angle = t1;
//     while(tmp_angle >= (2 * PI))
//       tmp_angle = tmp_angle - (2 * PI);
//     // while(tmp_angle < 0)
//     //   tmp_angle = tmp_angle + (2 * PI);
//     // Computes the local loglikelihood.
//     tmp_loglikelihood = 0;
//     for(int v2(0); v2<nb_vertices; ++v2)
//     {
//       tmp_loglikelihood += compute_pairwise_loglikelihood(v1, tmp_angle, v2, theta[v2]);
//     }
//     // Preserves the optimal angular sector.
//     if(tmp_loglikelihood > best_loglikelihood)
//     {
//       best_loglikelihood = tmp_loglikelihood;
//       best_angle = tmp_angle;
//     }
//     // Increases the angle.
//     t1 += max_step_length * uniform_01(engine);
//   }
//   // Registers the best position found.
//   theta[v1] = best_angle;
//   // Returns the variation in the global loglikelihood.
//   // return (best_loglikelihood - previous_loglikelihood) / nb_edges;
//   // return PI - std::fabs( PI - std::fabs(best_angle - previous_angle) );
// }


// // =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// // =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// void embeddingS1_t::refine_angle2(int v1)
// {
//   // Variables.
//   double tmp_angle;
//   double tmp_loglikelihood;
//   double best_angle = theta[v1];
//   // Computes the current loglikelihood.
//   double previous_loglikelihood = 0;
//   for(int v2(0); v2<nb_vertices; ++v2)
//   {
//     previous_loglikelihood += compute_pairwise_loglikelihood(v1, best_angle, v2, theta[v2]);
//   }
//   double best_loglikelihood = previous_loglikelihood;

//   // Computes the weighted average angular positions of the neighbors.
//   std::set<int>::iterator it2 = adjacency_list[v1].begin();
//   std::set<int>::iterator end = adjacency_list[v1].end();
//   double t2, k2, da;
//   double sum_sin_theta = 0;
//   double sum_cos_theta = 0;
//   for(; it2!=end; ++it2)
//   {
//     // Identifies the neighbor.
//     t2 = theta[*it2];
//     k2 = kappa[*it2];

//     // Computes the average angle of neighbors.
//     sum_sin_theta += std::sin(t2) / (k2 * k2);
//     sum_cos_theta += std::cos(t2) / (k2 * k2);
//   }
//   double average_theta = std::atan2(sum_sin_theta, sum_cos_theta);
//   while(average_theta > (2 * PI))
//     average_theta = average_theta - (2 * PI);
//   while(average_theta < 0)
//     average_theta = average_theta + (2 * PI);

//   // Finds the largest angular distance between the neighbor and the average position.
//   double max_angle = PI / 6;
//   // double max_angle = PI / 12;
//   it2 = adjacency_list[v1].begin();
//   for(; it2!=end; ++it2)
//   {
//     da = PI - std::fabs(PI - std::fabs(average_theta - theta[*it2]));
//     if(da > max_angle)
//     {
//       max_angle = da;
//     }
//   }
//   max_angle /= 2;

//   // std::clog << average_theta << "    " << max_angle << std::endl;
//   // std::clog.flush();

//   // Considers various wisely chosen new angular positions and keeps the best.
//   int _nb_new_angles_to_try = max_angle * nb_vertices / PI;
//   if(_nb_new_angles_to_try < NB_NEW_ANGLES_TO_TRY)
//     _nb_new_angles_to_try = NB_NEW_ANGLES_TO_TRY;
//   for(int e(0); e<_nb_new_angles_to_try; ++e)
//   {
//     // Gets the angle in the standard range.
//     tmp_angle = (normal_01(engine) * max_angle) + average_theta;
//     while(tmp_angle > (2 * PI))
//       tmp_angle = tmp_angle - (2 * PI);
//     while(tmp_angle < 0)
//       tmp_angle = tmp_angle + (2 * PI);

//     // Computes the local loglikelihood.
//     tmp_loglikelihood = 0;
//     for(int v2(0); v2<nb_vertices; ++v2)
//     {
//       tmp_loglikelihood += compute_pairwise_loglikelihood(v1, tmp_angle, v2, theta[v2]);
//     }
//     // Preserves the optimal angular sector.
//     if(tmp_loglikelihood > best_loglikelihood)
//     {
//       best_loglikelihood = tmp_loglikelihood;
//       best_angle = tmp_angle;
//     }
//   }

//   // Registers the best position found.
//   theta[v1] = best_angle;
// }


// // =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// // =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// void embeddingS1_t::infer_optimal_positions()
// {
//   // Sets the convergence threshold.
//   double maximization_convergence_threshold = MINIMAL_ANGULAR_CONVERGENCE_THRESHOLD;
//   // if(nb_vertices < LIMIT_FOR_CONVERGENCE_CRITERION)
//   // {
//   //   maximization_convergence_threshold = MINIMAL_ANGULAR_CONVERGENCE_THRESHOLD;
//   //   // maximization_convergence_threshold = 2 * PI / nb_vertices;
//   // }
//   // else
//   // {
//   //   maximization_convergence_threshold = 2 * PI / std::sqrt(LIMIT_FOR_CONVERGENCE_CRITERION * nb_vertices);
//   // }
//   if(!QUIET_MODE) { std::clog << "Maximizing positions..."; }
//   if(!QUIET_MODE) { std::clog << std::endl; }
//   if(!QUIET_MODE) { std::clog << TAB << "Convergence threshold: " << maximization_convergence_threshold << std::endl; }
//   if(!QUIET_MODE) { std::clog << TAB << "            "; }
//   if(!QUIET_MODE) { std::clog << "Average angular displacement by centrality"; }
//   if(!QUIET_MODE) { std::clog << TAB << "                    "; }
//   if(!QUIET_MODE) { std::clog << "Distribution of the angular displacement"; }
//   if(!QUIET_MODE) { std::clog << std::endl; }
//   if(!QUIET_MODE) { std::clog << TAB; }
//   if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << "iter."   << " "; }
//   if(!QUIET_MODE) { std::clog << TAB; }
//   if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << "[1,20)"   << " "; }
//   if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << "[20,40)"  << " "; }
//   if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << "[40,60)"  << " "; }
//   if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << "[60,80)"  << " "; }
//   if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << "[80,100]" << " "; }
//   if(!QUIET_MODE) { std::clog << TAB; }
//   if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << "min"     << " "; }
//   if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << "5th"     << " "; }
//   if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << "25th"    << " "; }
//   if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << "median"  << " "; }
//   if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << "average" << " "; }
//   if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << "75th"    << " "; }
//   if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << "95th"    << " "; }
//   if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << "max"     << " "; }
//   if(!QUIET_MODE) { std::clog << TAB; }
//   if(!QUIET_MODE) { std::clog << std::fixed << std::setw(12) << "time (sec.)"     << " "; }
//   if(!QUIET_MODE) { std::clog << std::endl; }
//   // Variables.
//   time_t start_time, stop_time;
//   double avg_diff_angle;
//   double avg_diff_angle_per_sector;
//   int nb_iterations = 1;
//   int index_05 = (0.05 * nb_vertices) - 1;
//   int index_25 = (0.25 * nb_vertices) - 1;
//   int index_50 = (0.50 * nb_vertices) - 1;
//   int index_75 = (0.75 * nb_vertices) - 1;
//   int index_95 = (0.95 * nb_vertices) - 1;
//   std::vector<double> angular_displacement(nb_vertices, 10);
//   bool keep_going = true;
//   while( keep_going )
//   {
//     // // Looks for inversions and correct them.
//     // correct_inversions();
//     start_time = std::time(NULL);
//     if(!QUIET_MODE) { std::clog << TAB; }
//     if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << nb_iterations << " "; }
//     if(!QUIET_MODE) { std::clog.flush(); }
//     // Finds the best angle taking into account the position of other vertices.
//     avg_diff_angle = 0;
//     for(int i(0); (i<nb_vertices && i<NB_VERTICES_IN_CORE); ++i)
//     {
//       angular_displacement[i] = find_optimal_angle( ordered_list_of_vertices[i] );
//       avg_diff_angle += angular_displacement[i];
//     }
//     for(int i(NB_VERTICES_IN_CORE); i<nb_vertices; ++i)
//     {
//       angular_displacement[i] = find_approximate_optimal_angle( ordered_list_of_vertices[i] );
//       avg_diff_angle += angular_displacement[i];
//     }
//     avg_diff_angle /= nb_vertices;
//     // Characterizes the angular displacement of various sectors of the ordered list of vertices.
//     if(!QUIET_MODE) { std::clog << TAB; }
//     keep_going = false;
//     for(int i(0), ii(5), i_begin(0), i_end; i<ii; ++i)
//     {
//       i_end = ( (double) (i + 1) * nb_vertices / ii ) + 1;
//       i_end = (i_end > nb_vertices) ? (nb_vertices) : i_end;
//       avg_diff_angle_per_sector = 0;
//       for(int j(i_begin); j<i_end; ++j)
//       {
//         avg_diff_angle_per_sector += angular_displacement[j];
//       }
//       avg_diff_angle_per_sector /= (i_end - i_begin);
//       i_begin = i_end;
//       // if(avg_diff_angle_per_sector > MAXIMIZATION_CONVERGENCE_THRESHOLD)
//       if(avg_diff_angle_per_sector > maximization_convergence_threshold)
//       {
//         keep_going = true;
//       }
//       if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << avg_diff_angle_per_sector << " "; }
//     }
//     // Orders the angular displacements.
//     std::sort(angular_displacement.begin(), angular_displacement.end());
//     // Characterizes the dispersion.
//     if(!QUIET_MODE) { std::clog << TAB; }
//     if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << angular_displacement[0]               << " "; }
//     if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << angular_displacement[index_05]        << " "; }
//     if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << angular_displacement[index_25]        << " "; }
//     if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << angular_displacement[index_50]        << " "; }
//     if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << avg_diff_angle                        << " "; }
//     if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << angular_displacement[index_75]        << " "; }
//     if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << angular_displacement[index_95]        << " "; }
//     if(!QUIET_MODE) { std::clog << std::fixed << std::setw(8) << angular_displacement[nb_vertices - 1] << " "; }
//     // Time elapsed.
//     stop_time = std::time(NULL);
//     if(!QUIET_MODE) { std::clog << TAB; }
//     if(!QUIET_MODE) { std::clog << std::fixed << std::setw(12) << stop_time - start_time << " "; }
//     if(!QUIET_MODE) { std::clog << std::endl; }
//     // Updates the number of iterations.
//     ++nb_iterations;
//     if(nb_iterations == MAX_NB_ITER_MAXIMIZATION)
//     {
//       if(!QUIET_MODE) { std::clog << "WARNING: maximum number of iterations reached" << std::endl; }
//       break;
//     }
//   }
//   if(!QUIET_MODE) { std::clog << "                       ...............................................................done." << std::endl; }
//   if(!QUIET_MODE) { std::clog << std::endl; }
// }


// // =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// // =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// double embeddingS1_t::find_approximate_optimal_angle(int v1)
// {
//   // Variables.
//   double tmp_angle;
//   double tmp_loglikelihood;
//   double previous_angle = theta[v1];
//   double best_angle = previous_angle;
//   // double max_step_length = 2 * PI / MINIMAL_ANGULAR_RESOLUTION;
//   double max_step_length = 2 * 2 * PI / nb_vertices;
//   // Iterators.
//   std::set<int>::iterator it, end;
//   // Computes the current loglikelihood.
//   double previous_loglikelihood = 0;
//   it = adjacency_list[v1].begin();
//   end = adjacency_list[v1].end();
//   for(int v2; it!=end; ++it)
//   {
//     v2 = *it;
//     previous_loglikelihood += compute_pairwise_loglikelihood(v1, best_angle, v2, theta[v2]);
//   }
//   double best_loglikelihood = previous_loglikelihood;
//   // For "all" angles, starting at a random position.
//   double t0 = 2 * PI * uniform_01(engine);
//   double t1 = t0;
//   while( (t1 - t0) < (2 * PI) )
//   {
//     // Gets the angle in the standard range.
//     tmp_angle = (t1 > (2 * PI)) ? (t1 - (2 * PI)) : t1;
//     // Computes the local loglikelihood.
//     tmp_loglikelihood = 0;
//     it = adjacency_list[v1].begin();
//     end = adjacency_list[v1].end();
//     for(int v2; it!=end; ++it)
//     {
//       v2 = *it;
//       tmp_loglikelihood += compute_pairwise_loglikelihood(v1, tmp_angle, v2, theta[v2]);
//     }
//     // Preserves the optimal angular sector.
//     if(tmp_loglikelihood > best_loglikelihood)
//     {
//       best_loglikelihood = tmp_loglikelihood;
//       best_angle = tmp_angle;
//     }
//     // Increases the angle.
//     t1 += max_step_length * uniform_01(engine);
//   }
//   // // Refines the position around the best position found above.
//   // double close_angular_range = CLOSE_ANGULAR_RANGE_FACTOR * 2 * PI / nb_vertices;
//   // double refined_max_step_length = 2 * PI / nb_vertices / REFINED_MAX_STEP_LENGTH_DIVISOR;
//   // t0 = best_angle - close_angular_range ;
//   // t0 = (t0 < 0) ? (t0 + (2 * PI)) : t0;
//   // t1 = t0;
//   // while( (t1 - t0) < (2 * close_angular_range) )
//   // {
//   //   // Gets the angle in the standard range.
//   //   tmp_angle = (t1 > (2 * PI)) ? (t1 - (2 * PI)) : t1;
//   //   // Computes the local loglikelihood.
//   //   tmp_loglikelihood = 0;
//   //   it = adjacency_list[v1].begin();
//   //   end = adjacency_list[v1].end();
//   //   for(int v2; it!=end; ++it)
//   //   {
//   //     v2 = *it;
//   //     tmp_loglikelihood += compute_pairwise_loglikelihood(v1, tmp_angle, v2, theta[v2]);
//   //   }
//   //   // Preserves the optimal angular sector.
//   //   if(tmp_loglikelihood > best_loglikelihood)
//   //   {
//   //     best_loglikelihood = tmp_loglikelihood;
//   //     best_angle = tmp_angle;
//   //   }
//   //   // Increases the angle.
//   //   t1 += refined_max_step_length * uniform_01(engine);
//   // }
//   // Registers the best position found.
//   theta[v1] = best_angle;
//   // Returns the variation in the global loglikelihood.
//   // return (best_loglikelihood - previous_loglikelihood) / nb_edges;
//   return PI - std::fabs( PI - std::fabs(best_angle - previous_angle) );
// }
