/*
 *
 * This class provides the functions to embed a graph in the SD 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, Robert Jankowski
 * WWW:      antoineallard.info, robertjankowski.github.io
 * Version:  1.0
 * Date:     October 2023
 *
 *
 * 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 EMBEDDINGSD_HPP_INCLUDED
#define EMBEDDINGSD_HPP_INCLUDED

// Standard Template Library
#include <algorithm>
// #include <execution>
// #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>
#include <filesystem>
// 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"
#include "integrate_expected_degree.hpp"
#include "readjust_positions.hpp"

class embeddingSD_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;
    // To adjust both kappas and beta after the angular positions inferred?
    bool KAPPA_BETA_POST_INFERENCE_MODE = false;
    // 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;
    // Dimension of the embeddings.
    int DIMENSION = 2;
    // Only calculate kappas and exit
    bool ONLY_KAPPAS = 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).
    const double BETA_ABS_MAX = 25;
    const 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.
    const int MIN_NB_ANGLES_TO_TRY = 100;
    // // Parameter governing the refined 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.
    const int EXP_CLUST_NB_INTEGRATION_MC_STEPS = 600;
    // Number of steps for integration for the expected distance between adjacent vertices.
    const int EXP_DIST_NB_INTEGRATION_STEPS = 1000;
    // Maximal number of attempts to reach convergence of the updated values of kappa.
    const int KAPPA_MAX_NB_ITER_CONV =  500;
    const int KAPPA_MAX_NB_ITER_CONV_2 = 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.
    const double MIN_TWO_SIGMAS_NORMAL_DIST = PI; // PI / 6; // For larger dimensions it is better to have a higher sigma
    // Various numerical/convergence thresholds.
    const double NUMERICAL_CONVERGENCE_THRESHOLD_1 = 1e-2;
    const double NUMERICAL_CONVERGENCE_THRESHOLD_2 = 5e-5;
    const double NUMERICAL_CONVERGENCE_THRESHOLD_3 = 0.5;
    // double MAXIMIZATION_CONVERGENCE_THRESHOLD = 0.01;
    const 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_view VERSION = "0.9";
    // Tab.
    std::string_view 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<>>> 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;
    // Positions of the vertices in S^D
    std::vector<std::vector<double>> d_positions;
    // 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();
    void load_already_inferred_parameters(int dim);
    // === Infering parameters ===
    // Builds the cumulative distribution to choose degree classes in the calculation of clustering.
    void build_cumul_dist_for_mc_integration();
    void build_cumul_dist_for_mc_integration(int dim);
    // Computes various properties of the random ensemble (before finding optimal positions).
    void compute_random_ensemble_average_degree();
    void compute_random_ensemble_clustering(int dim);
    void compute_random_ensemble_clustering();
    double compute_random_ensemble_clustering_for_degree_class(int d1, int dim);
    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_all_vertices(int dim);
    void infer_kappas_beta_for_all_vertices();
    void infer_kappas_beta_for_all_vertices(int dim);

    void infer_kappas_given_beta_for_degree_class();
    void infer_kappas_given_beta_for_degree_class(int dim);
    // === Embedding ===
    // Computes the log-likelihood between two vertices.
    double compute_pairwise_loglikelihood(int v1, double t1, int v2, double t2, bool neighbors);
    double compute_pairwise_loglikelihood(int dim, int v1, std::vector<double> pos1, int v2, std::vector<double> pos2, bool neighbors, double radius);
    // 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 find_initial_ordering(std::vector<std::vector<double>> &positions, int dim);
    void infer_initial_positions();
    void infer_initial_positions(int dim);
    // 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_positions(int dim);
    int refine_angle(int dim, int v1, double radius);
    
    // 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();
    // Infers the parameters (kappas, beta) piotr to the D-dimensional embedding.
    void infer_parameters(int dim);
    // 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();
    void compute_inferred_ensemble_expected_degrees(int dim, double radius);
    // === Characterization of the inferred ensemble using simulations ===
    void analyze_simulated_adjacency_list();
    void generate_simulated_adjacency_list();
    void generate_simulated_adjacency_list(int dim, bool random_positions);
    // === 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();
    void save_inferred_connection_probability(int dim);
    // Writes the inferred properties of vertices into a file.
    void save_inferred_ensemble_characterization();
    void save_inferred_ensemble_characterization(int dim, bool random_positions);

    // Writes the inferred theta density into a file.
    void save_inferred_theta_density();
    // Writes the inferred info into a file.
    void save_inferred_coordinates();
    void save_inferred_coordinates(int dim);
    // 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();
    // Calculate mu which controls average degree
    inline double calculateMu() const;
    // Gets the degree of the random vertex and computes probability of being connected
    // See 2.i. point from A.3 part in Mercator paper
    std::pair<int, double> degree_of_random_vertex_and_prob_conn(int d1, double R);
    std::pair<int, double> degree_of_random_vertex_and_prob_conn(int d1, double R, int dim);
    // Draw random angular distance between nodes with degree d1 and d2, which are connected with probability p12
    // See Eq. (A3) in Mercator paper
    double draw_random_angular_distance(int d1, int d2, double R, double p12);
    double draw_random_angular_distance(int d1, int d2, double R, double p12, int dim);
    // Compute the radius in S^D model with a given network size.
    inline double compute_radius(int dim, int N) const;
    // Calculate mu in D-dimension
    inline double calculate_mu(int dim) const;
    // Generate random normalized vector in D+1 dimensional space
    std::vector<double> generate_random_d_vector(int dim, double radius);
    // Generate random normalized vector in D+1 dimensional space with the first coordinate: x1 = Cos[angle] (after normalization)
    std::vector<double> generate_random_d_vector_with_first_coordinate(int dim, double angle, double radius);
    // Compute angle between two vectors in D+1 dimensional space
    double compute_angle_d_vectors(const std::vector<double> &v1, const std::vector<double> &v2);
    // Compute Kolmogorov-Smirnov statistic
    double ks_stats(const std::map<int, double> &cdf1, const std::map<int, double> &cdf2);
    // Compute two-sample Kolmogorov-Smirnov test at level alpha
    bool ks_test(const std::map<int, double> &cdf1, const std::map<int, double> &cdf2, double alpha=0.05);
    // Normalize vector
    void normalize_and_rescale_vector(std::vector<double> &v, double radius);
    // Compute euclidean distance between two vectors
    double euclidean_distance(const std::vector<double> &v1, const std::vector<double> &v2);
    // Save hidden degrees and exit
    void save_kappas_and_exit();
  // Public functions to perform the embeddings.
  public:
    // Constructor (empty).
    embeddingSD_t() {};
    // Destructor (empty).
    ~embeddingSD_t() {};
    // Performs the embedding.
    void embed();
    void embed(int dim); // Perform the embedding in D dimension
    void embed(std::string edgelist_filename) { EDGELIST_FILENAME = edgelist_filename; embed(); };
};





// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_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 embeddingSD_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 embeddingSD_t::build_cumul_dist_for_mc_integration(int dim) {
  int v1;
  double tmp_val, tmp_cumul;
  const double R = compute_radius(dim, nb_vertices);
  mu = calculate_mu(dim);
  // 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) {
      const auto kappa1 = random_ensemble_kappa_per_degree_class[*it1];
      const auto kappa2 = random_ensemble_kappa_per_degree_class[*it2];
      tmp_val = compute_integral_expected_degree_dimensions(dim, R, mu, beta, kappa1, kappa2);
      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 embeddingSD_t::build_cumul_dist_for_mc_integration()
{
  // Variables.
  int v1;
  double tmp_val, tmp_cumul;
  // Parameters.
  double R = nb_vertices / (2 * PI);
  mu = calculateMu();
  // 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 embeddingSD_t::compute_clustering()
{
  average_clustering = 0;
  // 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 embeddingSD_t::compute_inferred_ensemble_expected_degrees(int dim, double radius)
{
  // 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) {
    for(int v2(v1 + 1); v2<nb_vertices; ++v2) {
      const auto dtheta = compute_angle_d_vectors(d_positions[v1], d_positions[v2]);
      const auto chi = radius * dtheta / std::pow(mu * kappa[v1] * kappa[v2], 1.0 / dim);
      const auto prob = 1 / (1 + std::pow(chi, beta));
      inferred_ensemble_expected_degree[v1] += prob;
      inferred_ensemble_expected_degree[v2] += prob;  
    }
  }
}

// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_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 embeddingSD_t::compute_random_ensemble_average_degree()
{
  // Computes the ensemble average degree.
  random_ensemble_average_degree = 0;
  for (const auto &it2: random_ensemble_expected_degree_per_degree_class)
    random_ensemble_average_degree += it2.second * degree2vertices[it2.first].size();
  random_ensemble_average_degree /= nb_vertices;
}


void embeddingSD_t::compute_random_ensemble_clustering(int dim)
{
  // Reinitializes the average clustering for the ensemble.
  random_ensemble_average_clustering = 0;
  // Computes the inferred ensemble clustering spectrum for all degree classes over 1.
  auto it = degree_class.begin();
  auto end = degree_class.end();
  while(*it < 2) { ++it; }
  for(; it!=end; ++it)
  {
    // Computes the clustering coefficient for the degree class and updates the average value.
    double p23 = compute_random_ensemble_clustering_for_degree_class(*it, dim);
    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;
}

// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_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.
  auto it = degree_class.begin();
  auto end = degree_class.end();
  while(*it < 2) { ++it; }
  for(; it!=end; ++it)
  {
    // Computes the clustering coefficient for the degree class and updates the average value.
    double 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;
}

std::pair<int, double> embeddingSD_t::degree_of_random_vertex_and_prob_conn(int d1, double R, int dim)
{
  auto d = cumul_prob_kgkp[d1].lower_bound(uniform_01(engine))->second;
  const auto kappa1 = random_ensemble_kappa_per_degree_class[d1];
  const auto kappa2 = random_ensemble_kappa_per_degree_class[d];
  auto p = compute_integral_expected_degree_dimensions(dim, R, mu, beta, kappa1, kappa2);
  return std::make_pair(d, p);
}

std::pair<int, double> embeddingSD_t::degree_of_random_vertex_and_prob_conn(int d1, double R)
{
  auto d = cumul_prob_kgkp[d1].lower_bound(uniform_01(engine))->second;
  auto p = hyp2f1a(beta, -std::pow((PI * R) / (mu * random_ensemble_kappa_per_degree_class[d1] * random_ensemble_kappa_per_degree_class[d]), beta));
  return std::make_pair(d, p);
}

double embeddingSD_t::draw_random_angular_distance(int d1, int d2, double R, double p12, int dim)
{
  double pc = uniform_01(engine);
  double zmin = 0, zmax = PI, z, pz;
  const auto kappa1 = random_ensemble_kappa_per_degree_class[d1];
  const auto kappa2 = random_ensemble_kappa_per_degree_class[d2];
  while((zmax - zmin) > NUMERICAL_CONVERGENCE_THRESHOLD_2)
  {
    z = (zmax + zmin) / 2;
    pz = compute_integral_expected_degree_dimensions(dim, R, mu, beta, kappa1, kappa2, z) / p12;
    if(pz > pc)
      zmax = z;
    else
      zmin = z;
  }
  return (zmax + zmin) / 2;
}

double embeddingSD_t::draw_random_angular_distance(int d1, int d2, double R, double p12)
{
  double pc = uniform_01(engine);
  double zmin = 0, zmax = PI, z, pz;
  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;
  }
  return (zmax + zmin) / 2;
}

// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
double embeddingSD_t::compute_random_ensemble_clustering_for_degree_class(int d1, int dim)
{
  // Variables.
  double z12, z13, da;
  // Parameters.
  double p23 = 0;
  const int nb_points = EXP_CLUST_NB_INTEGRATION_MC_STEPS;
  const double R = compute_radius(dim, nb_vertices);
  mu = calculate_mu(dim);
  // MC integration.
#pragma omp parallel for default(shared) reduction(+:p23)
  for(int i=0; i<nb_points; ++i)
  {
    // Gets the degree of vertex 2 and 3; and Computes their probability of being connected (A.3.2.i).
    const auto [d2, p12] = degree_of_random_vertex_and_prob_conn(d1, R, dim);
    const auto [d3, p13] = degree_of_random_vertex_and_prob_conn(d1, R, dim);

    // Random angular distances between vertex (1, 2) and (1, 3) (A.3.2.ii).
    z12 = draw_random_angular_distance(d1, d2, R, p12, dim);
    z13 = draw_random_angular_distance(d1, d3, R, p13, dim);

    // Draw two random D+1 vector with first coordinate constrained by angle and compute distance between them
    const auto v1 = generate_random_d_vector_with_first_coordinate(dim, z12, R);
    const auto v2 = generate_random_d_vector_with_first_coordinate(dim, z13, R);
    const auto d_angle = compute_angle_d_vectors(v1, v2);
    if (d_angle < NUMERICAL_ZERO) {
      p23 += 1;
    } else {
      const auto kappa1 = random_ensemble_kappa_per_degree_class[d2];
      const auto kappa2 = random_ensemble_kappa_per_degree_class[d3];
      const auto inside = (R * d_angle / std::pow(mu * kappa1 * kappa2, 1.0 / dim));
      p23 += 1.0 / (1 + std::pow(inside, beta));
    }
  }
  // Returns the value of the local clustering coefficient for this degree class (A.3.2.iv).
  return p23 / nb_points;
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
double embeddingSD_t::compute_random_ensemble_clustering_for_degree_class(int d1)
{
  // Variables.
  double z12, z13, da;
  double p23 = 0;
  // Parameters.
  int nb_points = EXP_CLUST_NB_INTEGRATION_MC_STEPS;
  const double R = nb_vertices / (2 * PI);
  mu = calculateMu();
  // MC integration.
#pragma omp parallel for default(shared) reduction(+:p23)
  for(int i=0; i<nb_points; ++i)
  {
    // Gets the degree of vertex 2 and 3; and Computes their probability of being connected (A.3.2.i).
    const auto [d2, p12] = degree_of_random_vertex_and_prob_conn(d1, R);
    const auto [d3, p13] = degree_of_random_vertex_and_prob_conn(d1, R);

    // Random angular distances between vertex (1, 2) and (1, 3) (A.3.2.ii).
    z12 = draw_random_angular_distance(d1, d2, R, p12);
    z13 = draw_random_angular_distance(d1, d3, R, p13);

    // Set the angular distances (A.3.2.iii)
    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 (A.3.2.iv).
  return p23 / nb_points;
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
double embeddingSD_t::compute_pairwise_loglikelihood(int dim, int v1, std::vector<double> pos1, int v2, std::vector<double> pos2, bool neighbors, double radius)
{
  // Avoids to compute the pairwise loglikelihood of the vertex with itself.
  if(v1 == v2)
    return 0;
  const auto dtheta = compute_angle_d_vectors(pos1, pos2);
  const auto chi = radius * dtheta / std::pow(mu * kappa[v1] * kappa[v2], 1.0 / dim);
  const auto prob = 1 / (1 + std::pow(chi, beta));
  if (neighbors) { // TODO: Is it correct ??
    return std::log(prob);
  } else {
    return std::log(1 - prob);
  }  
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
double embeddingSD_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.
  double fraction = (nb_vertices * da) / (2 * PI * mu * kappa[v1] * kappa[v2]);

  if(neighbors)
  {
    return -beta * std::log(fraction); // TODO: Correct if fraction >> 1, test it
    // My solution:
    // return -std::log(1 + std::pow(fraction, beta));
  }
  else // not neighbors
  {
    return -std::log(1 + std::pow(fraction, -beta));
  }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_t::embed()
{
  if (DIMENSION > 1) {
    embed(DIMENSION);
    exit(0);
  }
  // Gets current time.
  time0 = time_since_epoch_in_seconds();
  time_started = std::time(nullptr);
  // Initialization.
  initialize();
  // Gets current time.
  time1 = time_since_epoch_in_seconds();
  
  if (ONLY_KAPPAS) {
    if (REFINE_MODE) {
      auto custom_beta = beta;
      load_already_inferred_parameters();
      beta = custom_beta; // because the beta is updated from the file with inferred parameters
      mu = calculateMu();
      infer_kappas_given_beta_for_all_vertices();
    } else {
      infer_parameters();
    }
    save_kappas_and_exit();
  }

  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();
  }
  if(KAPPA_BETA_POST_INFERENCE_MODE) 
  {
    // Post-processing: adjust beta+kappas based on the inferred positions.
    infer_kappas_beta_for_all_vertices();
  }
  // Gets the current time.
  time5 = time_since_epoch_in_seconds();
  time_ended = std::time(nullptr);
  // 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(nullptr);
  //
  finalize();
}

void embeddingSD_t::embed(int dim)
{
  // Gets current time.
  time0 = time_since_epoch_in_seconds();
  time_started = std::time(nullptr);
  // Initialization.
  initialize();
  // Gets current time.
  time1 = time_since_epoch_in_seconds();
  if (ONLY_KAPPAS) {
    if (REFINE_MODE) {
      auto custom_beta = beta;
      load_already_inferred_parameters(dim);
      beta = custom_beta;
      mu = calculate_mu(dim);
    } else {
      infer_parameters(dim);
    }
    infer_kappas_given_beta_for_all_vertices(dim);
    save_kappas_and_exit();
  }
  
  if(REFINE_MODE)
  {
    // Loads the parameters from the .inf_coord file.
    load_already_inferred_parameters(dim);
  }
  if(!REFINE_MODE)
  {
    // Pre-processing: infers the parameters used for the embedding.
    infer_parameters(dim);
  }
  // Gets current time.
  time2 = time_since_epoch_in_seconds();
  if(!REFINE_MODE)
  {
    // First phase: educated guess of the positions.
    infer_initial_positions(dim);
  }
  time3 = time_since_epoch_in_seconds();
  
  if(MAXIMIZATION_MODE)
  {
    refine_positions(dim);
  }
  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(dim);
  }
  if(KAPPA_BETA_POST_INFERENCE_MODE) 
  {
    // Post-processing: adjust beta+kappas based on the inferred positions.
    infer_kappas_beta_for_all_vertices(dim);
  }
  time5 = time_since_epoch_in_seconds();
  save_inferred_coordinates(dim);
  if(VALIDATION_MODE)
  {
    //save_inferred_theta_density();
    save_inferred_connection_probability(dim);
  }
  time6 = time_since_epoch_in_seconds();
  if (CHARACTERIZATION_MODE)
  {
    save_inferred_ensemble_characterization(dim, false);
  }
  time7 = time_since_epoch_in_seconds();
  time_ended = std::time(nullptr);
  finalize();
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_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.empty())
  {
    // 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.empty())
    {
      // 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 embeddingSD_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;
      std::clog << "Internal parameters and options" << std::endl;
      std::clog << TAB << "ALREADY_INFERRED_PARAMETERS_FILENAME   " << ALREADY_INFERRED_PARAMETERS_FILENAME
                << std::endl;
      std::clog << TAB << "BETA_ABS_MAX                           " << BETA_ABS_MAX << std::endl;
      std::clog << TAB << "BETA_ABS_MIN                           " << BETA_ABS_MIN << std::endl;
      std::clog << TAB << "CHARACTERIZATION_MODE                  " << (CHARACTERIZATION_MODE ? "true" : "false")
                << std::endl;
      std::clog << TAB << "CHARACTERIZATION_NB_GRAPHS             " << CHARACTERIZATION_NB_GRAPHS << std::endl;
      std::clog << TAB << "CLEAN_RAW_OUTPUT_MODE                  " << (CLEAN_RAW_OUTPUT_MODE ? "true" : "false")
                << std::endl;
      // std::clog << TAB << "CLOSE_ANGULAR_RANGE_FACTOR             " << CLOSE_ANGULAR_RANGE_FACTOR  << std::endl;
      std::clog << TAB << "CUSTOM_BETA                            " << (CUSTOM_BETA ? "true" : "false") << std::endl;
      std::clog << TAB << "CUSTOM_CHARACTERIZATION_NB_GRAPHS      "
                << (CUSTOM_CHARACTERIZATION_NB_GRAPHS ? "true" : "false") << std::endl;
      std::clog << TAB << "CUSTOM_INFERRED_COORDINATES            " << (CUSTOM_INFERRED_COORDINATES ? "true" : "false")
                << std::endl;
      std::clog << TAB << "CUSTOM_OUTPUT_ROOTNAME_MODE            " << (CUSTOM_OUTPUT_ROOTNAME_MODE ? "true" : "false")
                << std::endl;
      std::clog << TAB << "CUSTOM_SEED                            " << (CUSTOM_SEED ? "true" : "false") << std::endl;
      std::clog << TAB << "EDGELIST_FILENAME:                     " << EDGELIST_FILENAME << std::endl;
      std::clog << TAB << "EXP_CLUST_NB_INTEGRATION_MC_STEPS      " << EXP_CLUST_NB_INTEGRATION_MC_STEPS << std::endl;
      std::clog << TAB << "EXP_DIST_NB_INTEGRATION_STEPS          " << EXP_DIST_NB_INTEGRATION_STEPS << std::endl;
      std::clog << TAB << "KAPPA_MAX_NB_ITER_CONV                 " << KAPPA_MAX_NB_ITER_CONV << std::endl;
      std::clog << TAB << "KAPPA_POST_INFERENCE_MODE              " << (KAPPA_POST_INFERENCE_MODE ? "true" : "false")
                << std::endl;
      // std::clog << TAB << "LIMIT_FOR_CONVERGENCE_CRITERION        " << LIMIT_FOR_CONVERGENCE_CRITERION << std::endl;
      // std::clog << TAB << "MAX_NB_ITER_MAXIMIZATION               " << MAX_NB_ITER_MAXIMIZATION << std::endl;
      std::clog << TAB << "MAXIMIZATION_MODE                      " << (MAXIMIZATION_MODE ? "true" : "false")
                << std::endl;
      // std::clog << TAB << "MINIMAL_ANGULAR_CONVERGENCE_THRESHOLD  " << MINIMAL_ANGULAR_CONVERGENCE_THRESHOLD << std::endl;
      // std::clog << TAB << "MINIMAL_ANGULAR_RESOLUTION             " << MINIMAL_ANGULAR_RESOLUTION << std::endl;
      // std::clog << TAB << "NB_VERTICES_IN_CORE                    " << NB_VERTICES_IN_CORE << std::endl;
      std::clog << TAB << "MIN_NB_ANGLES_TO_TRY                   " << MIN_NB_ANGLES_TO_TRY << std::endl;
      std::clog << TAB << "NUMERICAL_CONVERGENCE_THRESHOLD_1      " << NUMERICAL_CONVERGENCE_THRESHOLD_1 << std::endl;
      std::clog << TAB << "NUMERICAL_CONVERGENCE_THRESHOLD_2      " << NUMERICAL_CONVERGENCE_THRESHOLD_2 << std::endl;
      std::clog << TAB << "NUMERICAL_CONVERGENCE_THRESHOLD_3      " << NUMERICAL_CONVERGENCE_THRESHOLD_3 << std::endl;
      std::clog << TAB << "NUMERICAL_ZERO                         " << NUMERICAL_ZERO << std::endl;
      std::clog << TAB << "QUIET_MODE                             " << (QUIET_MODE ? "true" : "false") << std::endl;
      std::clog << TAB << "REFINE_MODE                            " << (REFINE_MODE ? "true" : "false") << std::endl;
      // std::clog << TAB << "REFINED_MAX_STEP_LENGTH_DIVISOR        " << REFINED_MAX_STEP_LENGTH_DIVISOR << std::endl;
      std::clog << TAB << "ROOTNAME_OUTPUT:                       " << ROOTNAME_OUTPUT << std::endl;
      std::clog << TAB << "SEED                                   " << SEED << std::endl;
      std::clog << TAB << "VALIDATION_MODE                        " << (VALIDATION_MODE ? "true" : "false")
                << std::endl;
      std::clog << TAB << "VERBOSE_MODE                           " << (VERBOSE_MODE ? "true" : "false") << std::endl;
      std::clog << TAB << "VERSION                                " << VERSION << std::endl;
      std::clog << std::endl;
      std::clog << "Ended on: " << format_time(time_ended) << std::endl;
      std::clog << "Elapsed CPU time (embedding):            " << std::setw(10) << std::fixed << time5 - time0
                << " seconds" << std::endl;
      std::clog << TAB << "initialization:                      " << std::setw(10) << std::fixed << time1 - time0
                << " seconds" << std::endl;

      if (!REFINE_MODE) {
          std::clog << TAB << "parameters inference:                " << std::setw(10) << std::fixed << time2 - time1 << " seconds" << std::endl;
          std::clog << TAB << "initial positions:                   " << std::setw(10) << std::fixed << time3 - time2 << " seconds" << std::endl;
      }

      if (REFINE_MODE)
          std::clog << TAB << "loading previous positions:          " << std::setw(10) << std::fixed << time3 - time1 << " seconds" << std::endl;

      if (MAXIMIZATION_MODE)
          std::clog << TAB << "refining positions:                  " << std::setw(10) << std::fixed << time4 - time3 << " seconds" << std::endl;

      std::clog << TAB << "adjusting kappas:                    " << std::setw(10) << std::fixed << time5 - time4 << " seconds" << std::endl;

      if (VALIDATION_MODE || CHARACTERIZATION_MODE)
          std::clog << "Elapsed CPU time (validation):           " << std::setw(10) << std::fixed << time7 - time5 << " seconds" << std::endl;

      if (VALIDATION_MODE)
          std::clog << TAB << "validating embedding:                " << std::setw(10) << std::fixed << time6 - time5 << " seconds" << std::endl;

      if (CHARACTERIZATION_MODE)
          std::clog << TAB << "characterizing ensemble:             " << std::setw(10) << std::fixed << time7 - time6 << " seconds" << std::endl;

      std::clog << "===========================================================================================" << std::endl;

      // Puts the streams back into place (to avoid a segmentation fault when exiting program).
      if (!VERBOSE_MODE) {
          logfile.close();
          // Reset the rdbuf of clog.
          std::clog.rdbuf(old_rdbuf);
      }
  }
}


void embeddingSD_t::find_initial_ordering(std::vector<std::vector<double>> &positions, int dim)
{
  const auto radius = compute_radius(dim, nb_vertices);
  mu = calculate_mu(dim);
  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.
  // For D dimension also include 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 S^D.
  double k1, k2, expected_distance, val, t(0), norm(0);
  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.
      auto it  = adjacency_list[v1].begin();
      auto 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.
            const auto top_expected_distance = compute_integral_expected_theta(dim, radius, mu, beta, k1, k2);
            const auto bottom_expected_distance = compute_integral_expected_degree_dimensions(dim, radius, mu, beta, k1, k2);
            expected_distance = top_expected_distance / bottom_expected_distance;
            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 transform 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 D+2 eigenvectors associated with the D+1 smallest eigenvalues.
  // Constructs matrix operation object.
  Spectra::SparseGenMatProd<double> op(L);

  // Containers for the eigenvectors.
  Eigen::MatrixXcd evectors;

  // Convergence parameter.
  int ncv = 10 + dim;
  // 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)
  {
    // Constructs eigen solver object.
    Spectra::GenEigsSolver< double, Spectra::SMALLEST_MAGN, Spectra::SparseGenMatProd<double> > eigs(&op, dim + 2, 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; } 

  std::vector<std::pair<std::vector<double>, int>> ordering_set;
  positions.clear();
  positions.resize(nb_vertices);
  ordering_set.clear();
  for(int v1(0), n1; v1<nb_vertices; ++v1) {
    n1 = newID[v1];
    if(n1 != -1)
    {
      std::vector<double> pos;
      double norm = 0;
      for (int i=0; i<dim+1; ++i) {
        const auto v = evectors(n1, i).real();
        pos.push_back(v);
      }
      // Ensure that the norm of the vector is equal to one and the points are spread on the sphere (when D=2)
      normalize_and_rescale_vector(pos, radius);
      positions[v1] = pos;
      ordering_set.push_back(std::make_pair(pos, v1));
    } 
  }

  // Add the vertices with degree 1
  std::vector<int> list_neigh_degree_one;
  auto it2 = ordering_set.begin();
  auto 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]);
    auto it  = adjacency_list[v1].begin();
    auto end = adjacency_list[v1].end();
    for(int v2; it!=end; ++it) {
      v2 = *it;
      if(degree[v2] == 1)
        list_neigh_degree_one.push_back(v2);
    }
    const auto kappa2 = kappa[v1];  
    for (int v=0; v<list_neigh_degree_one.size(); ++v) {
      // Draw value from the distribution of angular distance \Delta\theta 
      // between two connected nodes with hidden degrees kappa1 and kappa2
      const auto kappa1 = kappa[list_neigh_degree_one[v]];
      const auto p12 = compute_integral_expected_degree_dimensions(dim, radius, mu, beta, kappa1, kappa2);
      const auto random_angle = draw_random_angular_distance(degree[v1], 1, radius, p12, dim);
      // Generate random vector with a given angular seperation

      // Create rotation matrix -  positions[v1] -> (1, 0, 0, ...)
      std::vector<double> axis(dim+1, 0); axis[0] = 1;
      auto vec1 = positions[v1];
      normalize_and_rescale_vector(vec1, 1); // unit vector needed in computation of rotation matrix
      auto M = compute_rotation_matrix(axis, vec1);
      
      auto new_position = generate_random_d_vector_with_first_coordinate(dim, random_angle, radius);
      // Go back to the initial rotation, in this way the random_angle is the same.
      auto new_rotated_positions = rotate_vector(M, new_position);

      // Add position of node with degree 1 to list at the correct place
      positions[list_neigh_degree_one[v]] = new_rotated_positions;
    }
  }
}

// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_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);
  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.
      auto it  = adjacency_list[v1].begin();
      auto 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);
            // expected_distance = nb_vertices / (2.0 * PI) * expected_distance; // R <theta_ij>
            // 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 transform 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;
  auto it2 = ordering_set.begin();
  auto 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]);
    auto it  = adjacency_list[v1].begin();
    auto 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 embeddingSD_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 embeddingSD_t::generate_simulated_adjacency_list(int dim, bool random_positions=true)
{
  // 1. For each node generate vector of size D+1
  const auto radius = compute_radius(dim, nb_vertices);
  if (random_positions) {
    d_positions.clear();
    d_positions.resize(nb_vertices);
    for (int i = 0; i < nb_vertices; ++i)
      d_positions[i] = generate_random_d_vector(dim, radius);
  }
  mu = calculate_mu(dim);

  // Initializes the container.
  simulated_adjacency_list.clear();
  simulated_adjacency_list.resize(nb_vertices);
  // Generates the adjacency list.
  // TODO: for large networks it could be parallelized by splitting vertices into groups
  //  and computing probability inside each of them.
  for (int v1=0; v1 < nb_vertices; ++v1) {
    const auto positions1 = d_positions[v1];
    for (int v2 = v1 + 1; v2 < nb_vertices; ++v2) {
      const auto dtheta = compute_angle_d_vectors(positions1, d_positions[v2]);
      const auto chi = radius * dtheta / std::pow(mu * kappa[v1] * kappa[v2], 1.0 / dim);
      const auto prob = 1 / (1 + std::pow(chi, beta));
      if (uniform_01(engine) < prob) {
        simulated_adjacency_list[v1].insert(v2);
        simulated_adjacency_list[v2].insert(v1);
      }
    }
  }
}

// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_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 embeddingSD_t::infer_initial_positions(int dim)
{
  if(!QUIET_MODE) {
    std::clog << "Finding initial positions/ordering...";
    std::clog.flush();
  }
  find_initial_ordering(d_positions, dim);
}

// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_t::infer_initial_positions()
{
  if(!QUIET_MODE) {
    std::clog << "Finding initial positions/ordering...";
    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;
  auto it = ordering.begin();
  auto 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.\n\n";
}

void embeddingSD_t::infer_kappas_given_beta_for_all_vertices(int dim)
{
  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;
  const double radius = compute_radius(dim, nb_vertices);
  while (keep_going && (cnt < KAPPA_MAX_NB_ITER_CONV))
  {
    // Updates the expected degree of individual vertices.
    compute_inferred_ensemble_expected_degrees(dim, radius);
    // 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 embeddingSD_t::infer_kappas_beta_for_all_vertices(int dim)
{
  const double BETA_ABS_MIN_DIM = dim + 0.01;
  const double BETA_ABS_MAX_DIM = dim + 100; // what should be the maximum beta?
  double beta_max = -1;
  double beta_min = dim;
  while(true)
  {
    if(!QUIET_MODE) {
      std::clog << TAB;
      std::clog << std::fixed << std::setw(11) << beta << " ";
      std::clog.flush();
    }
    mu = calculate_mu(dim);
    // Readjust the values of kappa.
    infer_kappas_given_beta_for_all_vertices(dim);
  
    // Compute the clustering from generated synthetic networks with the inferred positions
    const int NTIMES = 5;
    random_ensemble_average_clustering = 0;

    for (int i=0; i<NTIMES; ++i) {
      generate_simulated_adjacency_list(dim, false);
      analyze_simulated_adjacency_list();
      double current_clustering = 0;

      for(int v1(0); v1<nb_vertices; ++v1)
      {
        int d1 = simulated_degree[v1];
        if(d1 > 1)
        {
          auto value = simulated_nb_triangles[v1];
          value /= d1 * (d1 - 1) / 2;
          current_clustering += value;
        }
      }
      random_ensemble_average_clustering += current_clustering / nb_vertices_degree_gt_one;
    }
    random_ensemble_average_clustering /= NTIMES;
    
    if(!QUIET_MODE) { std::clog << std::fixed << std::setw(20) << random_ensemble_average_clustering << " \n"; }

    if(std::fabs(random_ensemble_average_clustering - average_clustering) < 0.05)
      break;

    if(random_ensemble_average_clustering > average_clustering)
    {
      beta_max = beta;
      beta = (beta_max + beta_min) / 2;
      if(beta < BETA_ABS_MIN_DIM)
      {
        if(!QUIET_MODE)
          std::clog << "WARNING: value too close to 1, using beta = " << std::fixed << std::setw(11) << beta << ".\n";
        break;
      }
    }
    else
    {
      beta_min = beta;
      if(beta_max == -1)
        beta *= 1.5;
      else
        beta = (beta_max + beta_min) / 2;
    }
    if(beta > BETA_ABS_MAX_DIM)
    {
      if(!QUIET_MODE)
        std::clog << "WARNING: value too high, using beta = " << std::fixed << std::setw(11) << beta << ".\n";
      break;
    }
  }
}


void embeddingSD_t::infer_kappas_beta_for_all_vertices()
{
  double beta_max = -1;
  double beta_min = 1;
  while(true)
  {
    if(!QUIET_MODE) {
      std::clog << TAB;
      std::clog << std::fixed << std::setw(11) << beta << " ";
      std::clog.flush();
    }
    mu = calculateMu();
    // Readjust the values of kappa.
    infer_kappas_given_beta_for_all_vertices();
  
    // Compute the clustering from generated synthetic networks with the inferred positions
    const int NTIMES = 5;
    random_ensemble_average_clustering = 0;

    for (int i=0; i<NTIMES; ++i) {
      generate_simulated_adjacency_list();
      analyze_simulated_adjacency_list();
      double current_clustering = 0;

      for(int v1(0); v1<nb_vertices; ++v1)
      {
        int d1 = simulated_degree[v1];
        if(d1 > 1)
        {
          auto value = simulated_nb_triangles[v1];
          value /= d1 * (d1 - 1) / 2;
          current_clustering += value;
        }
      }
      random_ensemble_average_clustering += current_clustering / nb_vertices_degree_gt_one;
    }
    random_ensemble_average_clustering /= NTIMES;
    
    if(!QUIET_MODE) { std::clog << std::fixed << std::setw(20) << random_ensemble_average_clustering << " \n"; }

    if( std::fabs(random_ensemble_average_clustering - average_clustering) < 0.05)
      break;

    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 << ".\n";
        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 << ".\n";
      break;
    }
  }
}

// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_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 embeddingSD_t::infer_kappas_given_beta_for_degree_class(int dim)
{
  // Variable.
  double prob_conn;
  const auto radius = compute_radius(dim, nb_vertices);
  mu = calculate_mu(dim);
  // Iterators.
  std::set<int>::iterator it1, it2, end;
  // Initializes the kappas for each degree class.
  it1 = degree_class.begin();
  end = degree_class.end();
  // 1. Initialize
  for(; it1!=end; ++it1)
  {
    random_ensemble_kappa_per_degree_class[*it1] = *it1;
  }

  // 2. 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_2))
  {
    // 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=degree_class.begin(); it1!=end; ++it1)
    // std::for_each(std::execution::seq, degree_class.begin(), degree_class.end(), [&](const auto &it1)
    {
      it2 = it1;
      auto kappa_i = random_ensemble_kappa_per_degree_class[*it1];
      auto kappa_j = random_ensemble_kappa_per_degree_class[*it2];
      auto integral = compute_integral_expected_degree_dimensions(dim, radius, mu, beta, kappa_i, kappa_j);
      prob_conn = integral;
     
      random_ensemble_expected_degree_per_degree_class[*it1] += prob_conn * (degree2vertices[*it2].size() - 1);
      for(++it2; it2!=end; ++it2)
      {
        kappa_i = random_ensemble_kappa_per_degree_class[*it1];
        kappa_j = random_ensemble_kappa_per_degree_class[*it2];
        integral = compute_integral_expected_degree_dimensions(dim, radius, mu, beta, kappa_i, kappa_j);
        prob_conn = integral;
        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;
        break;
      }
    }
    // 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_2) {
      if (!QUIET_MODE) {
        std::clog << std::endl;
        std::clog << TAB << "WARNING: maximum number of iterations reached before convergence. This limit can be"  << std::endl;
        std::clog << TAB << "         adjusted by setting the parameters KAPPA_MAX_NB_ITER_CONV_2 to desired value." << std::endl;
        std::clog << TAB << std::fixed << std::setw(11) << " " << " ";
      }
    }
}

// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_t::infer_kappas_given_beta_for_degree_class()
{
  // Variable.
  double prob_conn;
  // Parameters.
  mu = calculateMu();
  // 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;
        break;
      }
    }
    // 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;
      std::clog << TAB << "WARNING: maximum number of iterations reached before convergence. This limit can be"  << std::endl;
      std::clog << TAB << "         adjusted by setting the parameters KAPPA_MAX_NB_ITER_CONV to desired value." << std::endl;
      std::clog << TAB << std::fixed << std::setw(11) << " " << " ";
    }
  }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_t::infer_parameters(int dim)
{
  if(!QUIET_MODE) { std::clog << "Inferring parameters..."; }

  if (!QUIET_MODE) {
    std::clog << std::endl;
    std::clog << TAB;
    std::clog << std::fixed << std::setw(11) << "beta" << " ";
    std::clog << std::fixed << std::setw(20) << "avg. clustering" << " \n";
  }

  const double BETA_ABS_MIN_DIM = dim + 0.01;
  const double BETA_ABS_MAX_DIM = dim + 100; // what should be the maximum beta?

  if (!CUSTOM_BETA) {
    // Sets initial value to beta, beta > dim
    beta = dim + uniform_01(engine);
    // Iterates until convergence is reached.
    double beta_max = -1;
    double beta_min = dim;
    random_ensemble_average_clustering = 10; 

    while(true) {
      if(!QUIET_MODE) {
        std::clog << TAB;
        std::clog << std::fixed << std::setw(11) << beta << " ";
        std::clog.flush();
      }
      // 1. Infers the values of kappa
      infer_kappas_given_beta_for_degree_class(dim);
      // Computes the cumulative distribution used in the MC integration.
      build_cumul_dist_for_mc_integration(dim);
      // Computes the ensemble clustering.
      compute_random_ensemble_clustering(dim);
      if(!QUIET_MODE) { std::clog << std::fixed << std::setw(20) << random_ensemble_average_clustering << " \n"; }

      // Checks if the expected clustering is close enough. (A.3. last paragraph)
      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_DIM)
        {
          beta = BETA_ABS_MIN_DIM;
          if(!QUIET_MODE)
            std::clog << "WARNING: value too close to D, using beta = " << std::fixed << std::setw(11) << beta << ".\n";
          break;
        }
      }
      else
      {
        beta_min = beta;
        if(beta_max == -1)
          beta *= 1.5;
        else
          beta = (beta_max + beta_min) / 2;
      }
      if(beta > BETA_ABS_MAX_DIM)
      {
        if(!QUIET_MODE)
          std::clog << "WARNING: value too high, using beta = " << std::fixed << std::setw(11) << beta << ".\n";
        break;
      }
    }
  } else {
    infer_kappas_given_beta_for_degree_class(dim);
    build_cumul_dist_for_mc_integration(dim);
    compute_random_ensemble_clustering(dim);
  }

  // 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]];

  const auto radius = compute_radius(dim, nb_vertices);
  if(!QUIET_MODE) {
    if(!CUSTOM_BETA)
      std::clog << "                       ";
    std::clog << "...............................................................done."                                         << std::endl;
    std::clog                                                                                                                   << std::endl;
    std::clog << "Inferred ensemble (random positions)"                                                                         << std::endl;
    std::clog << TAB << "Average degree:                 " << random_ensemble_average_degree                                    << std::endl;
    std::clog << TAB << "Minimum degree:                 " << random_ensemble_expected_degree_per_degree_class.begin()->first   << std::endl;
    std::clog << TAB << "Maximum degree:                 " << (--random_ensemble_expected_degree_per_degree_class.end())->first << std::endl;
    std::clog << TAB << "Average clustering:             " << random_ensemble_average_clustering                                << std::endl;
    std::clog << TAB << "Parameters"                                                                                            << std::endl;
    if(!CUSTOM_BETA)
      std::clog << TAB << "  - beta:                       " << beta                                                            << std::endl;
    else
      std::clog << TAB << "  - beta:                       " << beta  << " (custom)"                                            << std::endl;
    std::clog << TAB << "  - mu:                           " << mu                                                              << std::endl;
    std::clog << TAB << "  - radius_S^D (R):               " << radius                              << std::endl;
    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();

  // Initialize random positions
  d_positions.clear();
  d_positions.resize(nb_vertices);
  for (int i=0; i<nb_vertices; ++i)
    d_positions[i] = generate_random_d_vector(dim, radius);
}

// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_t::infer_parameters()
{
  if(!QUIET_MODE) { std::clog << "Inferring parameters..."; }
  if(!CUSTOM_BETA)
  {
    if (!QUIET_MODE) {
      std::clog << std::endl;
      std::clog << TAB;
      std::clog << std::fixed << std::setw(11) << "beta" << " ";
      std::clog << std::fixed << std::setw(20) << "avg. clustering" << " \n";
    }
    // 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;
        std::clog << std::fixed << std::setw(11) << beta << " ";
        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 << " \n"; }

      // Checks if the expected clustering is close enough. (A.3. last paragraph)
      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 << ".\n";
          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 << ".\n";
        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(!QUIET_MODE) {
    if(!CUSTOM_BETA)
      std::clog << "                       ";
    std::clog << "...............................................................done."                                         << std::endl;
    std::clog                                                                                                                   << std::endl;
    std::clog << "Inferred ensemble (random positions)"                                                                         << std::endl;
    std::clog << TAB << "Average degree:                 " << random_ensemble_average_degree                                    << std::endl;
    std::clog << TAB << "Minimum degree:                 " << random_ensemble_expected_degree_per_degree_class.begin()->first   << std::endl;
    std::clog << TAB << "Maximum degree:                 " << (--random_ensemble_expected_degree_per_degree_class.end())->first << std::endl;
    std::clog << TAB << "Average clustering:             " << random_ensemble_average_clustering                                << std::endl;
    std::clog << TAB << "Parameters"                                                                                            << std::endl;
    if(!CUSTOM_BETA)
      std::clog << TAB << "  - beta:                       " << beta                                                            << std::endl;
    else
      std::clog << TAB << "  - beta:                       " << beta  << " (custom)"                                            << std::endl;
    std::clog << TAB << "  - mu:                         " << mu                                                                << std::endl;
    std::clog << TAB << "  - radius_S1 (R):              " << nb_vertices / (2 * PI)                                            << std::endl;
    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();

}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_t::initialize()
{
  // Sets the default rootname for output files.
  if(!CUSTOM_OUTPUT_ROOTNAME_MODE)
  {
    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(nullptr);
  }
  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 << "D-Mercator: accurate embeddings of graphs in the SD 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;
      std::clog << TAB << "Nb vertices:                    " << nb_vertices << std::endl;
      std::clog << TAB << "Nb edges:                       " << nb_edges << std::endl;
      std::clog << TAB << "Average degree:                 " << average_degree << std::endl;
      std::clog << TAB << "Minimum degree:                 " << *(degree_class.begin()) << std::endl;
      std::clog << TAB << "Maximum degree:                 " << *(--degree_class.end()) << std::endl;
      std::clog << TAB << "Nb of degree class:             " << degree_class.size() << std::endl;
      std::clog << TAB << "Average clustering:             " << average_clustering << std::endl;
      std::clog << std::endl;
  }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_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 embeddingSD_t::load_already_inferred_parameters(int dim) 
{
    // 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);
  d_positions.clear();
  d_positions.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 >> name2_str >> std::ws; // omit hyp radius
    std::vector<double> tmp_positions;
    for (int i=0; i<dim+1; ++i) {
      one_line >> name3_str >> std::ws;
      tmp_positions.push_back(stod(name3_str));
    }
    d_positions[Name2Num[name1_str]] = tmp_positions;
    one_line.clear();
  }
  // Closes the stream.
  hidden_variables_file.close();
  // Clears unused objects.
  Name2Num.clear();
}

// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_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.emplace_back();
          ++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.emplace_back();
          ++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 embeddingSD_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);
  auto it = layer_set.rbegin();
  auto end = layer_set.rend();
  for(int v(0); it!=end; ++it, ++v)
  {
    ordered_list_of_vertices[v] = it->second.second;
  }
  layer_set.clear();
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
int embeddingSD_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;
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
int embeddingSD_t::refine_angle(int dim, int v1, double radius)
{
  // Variables.
  int has_moved = 0;
  double tmp_angle;
  double tmp_loglikelihood;
  auto best_position = d_positions[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(dim, v1, best_position, v2, d_positions[v2], false, radius);
  }
  it2 = adjacency_list[v1].begin();
  end = adjacency_list[v1].end();
  for(; it2!=end; ++it2)
  {
    previous_loglikelihood += compute_pairwise_loglikelihood(dim, v1, best_position, *it2, d_positions[*it2], true, radius);
  }
  double best_loglikelihood = previous_loglikelihood;

  // Compute the weighted average positions of the neighbors
  std::vector<double> mean_vector(dim + 1, 0);
  it2 = adjacency_list[v1].begin();
  end = adjacency_list[v1].end();
  for(; it2!=end; ++it2)
  {
    // Identifies the neighbor.
    const auto pos2 = d_positions[*it2];
    const auto k2 = kappa[*it2];

    for (int i=0; i<dim+1; ++i)
      mean_vector[i] += pos2[i] / (k2 * k2);
  }
  normalize_and_rescale_vector(mean_vector, radius);

  // 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)
  {
    double dtheta = compute_angle_d_vectors(mean_vector, d_positions[*it2]);
    if(dtheta > max_angle)
      max_angle = dtheta;
  }
  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)
  {
    // Get the position in the standard range.
    std::vector<double> proposed_position(dim + 1, 0);
    for (int i=0; i<dim+1; ++i)
      proposed_position[i] = max_angle * normal_01(engine) + mean_vector[i] / radius; // multivariate normal distribution
    normalize_and_rescale_vector(proposed_position, radius);

    // Computes the local loglikelihood.
    tmp_loglikelihood = 0;
    for(int v2(0); v2<nb_vertices; ++v2)
    {
      tmp_loglikelihood += compute_pairwise_loglikelihood(dim, v1, proposed_position, v2, d_positions[v2], false, radius);
    }
    it2 = adjacency_list[v1].begin();
    end = adjacency_list[v1].end();
    for(; it2!=end; ++it2)
    {
      tmp_loglikelihood += compute_pairwise_loglikelihood(dim, v1, proposed_position, *it2, d_positions[*it2], true, radius);
    }
    // Preserves the optimal angular sector.
    if(tmp_loglikelihood > best_loglikelihood)
    {
      best_loglikelihood = tmp_loglikelihood;
      best_position = proposed_position;
      has_moved = 1;
    }
  }
  // Registers the best position found.
  d_positions[v1] = best_position;
  // Returns 1 if the vertex changed position, and 0 otherwise.
  return has_moved;
}

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

  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;
  const auto radius = compute_radius(dim, nb_vertices);
  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(dim, ordered_list_of_vertices[v_i], radius);
    }
    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; }
  }

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

// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_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 embeddingSD_t::save_inferred_connection_probability(int dim)
{
  // Builds the bins.
  std::map<double, int> bins;
  std::map<double, int>::iterator it;
  int bound = 20;
  int cnt = 0;
  double dt = 0.05;
  const auto radius = compute_radius(dim, nb_vertices);
  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];
    const auto pos1 = d_positions[v1];
    for(int v2(v1 + 1); v2<nb_vertices; ++v2)
    {
      da = compute_angle_d_vectors(pos1, d_positions[v2]);
      dist = (radius * da) / std::pow(mu * k1 * kappa[v2], 1.0 / dim);
      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 embeddingSD_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 embeddingSD_t::save_inferred_coordinates(int dim)
{
  const auto R = compute_radius(dim, nb_vertices);
  // 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(2 * R / std::pow(mu * kappa_min * kappa_min, 1.0 / dim));
  double min_radial_position = hyp_radius - (2.0 / dim) * std::log(kappa_max / kappa_min);
  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_S^D:        " << R                            << std::endl;
  coordinates_file << "#   - radius_H^D+1       " << 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.Hyp.Rad"       << " ";
  for (int i=0; i<dim+1; ++i) {
    coordinates_file << std::setw(width_values)     << "Inf.Pos." << i+1   << " ";
  }
  //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));
  }
  auto it  = ordered_names.begin();
  auto 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) << hyp_radius - (2.0 / dim) * std::log(kappa[v] / kappa_min) << " ";
    for (int i=0; i<dim+1; ++i) {
      coordinates_file << std::setw(width_values) << d_positions[v][i]                                            << " ";
    }
    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 << "DIMENSION                              " << dim                                          << 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]                                          << " ";
      for (int i=0; i<dim+1; ++i) {
          coordinates_file << d_positions[v][i]                             << " ";
      }
      //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 embeddingSD_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));
  }
  auto it  = ordered_names.begin();
  auto 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 embeddingSD_t::save_inferred_ensemble_characterization()
{
  save_inferred_ensemble_characterization(1, false);
}

// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_t::save_inferred_ensemble_characterization(int dim, bool random_positions=true)
{
  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(auto &elem : characterizing_inferred_ensemble_vprops)
  {
    elem.clear();
    elem.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();
      if (dim == 1)
        generate_simulated_adjacency_list();
      else
        generate_simulated_adjacency_list(dim, random_positions);
      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));
  }
  auto it  = ordered_names.begin();
  auto 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();
  std::map<int, double> inf_degree_ccdf;
  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;
    inf_degree_ccdf.insert(std::make_pair(d, avg_comp_cumul_degree_dist[d]));
  }
  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();
  std::map<int, double> obs_degree_ccdf;
  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                                           << " ";
    obs_degree_ccdf.insert(std::make_pair(d, 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);

  // Compute KS test
  double alpha = 0.05;
  if (!QUIET_MODE) {
    std::clog << std::endl;
    std::clog << "Computing two-sample Kolmogorov-Smirnov test......" << std::endl;
  }
  const auto ks_result = ks_test(inf_degree_ccdf, obs_degree_ccdf, alpha);
  if (!QUIET_MODE) {
    if (ks_result) {
      std::clog << "The null hypothesis is rejected at level " << alpha << std::endl;
      std::clog << "Two data samples does NOT come from the same distribution" << std::endl;  
    } else {
      std::clog << "The null hypothesis is not rejected at level " << alpha << std::endl;
      std::clog << "Two data samples does come from the same distribution" << std::endl;
    }
  }
}


// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
// =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
void embeddingSD_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 embeddingSD_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 embeddingSD_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 embeddingSD_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 embeddingSD_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;
    width_names = 14;
    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();
    std::exit(12); // Custom exist code to rerun Mercator with only GCC
  }
}

inline double embeddingSD_t::calculateMu() const
{
  return beta * std::sin(PI / beta) / (2.0 * PI * average_degree);
}

inline double embeddingSD_t::compute_radius(int dim, int N) const
{
  const auto inside = N / (2 * std::pow(PI, (dim + 1) / 2.0)) * std::tgamma((dim + 1) / 2.0);
  return std::pow(inside, 1.0 / dim);
}

inline double embeddingSD_t::calculate_mu(int dim) const
{
  const auto top = beta * std::tgamma(dim / 2.0) * std::sin(dim * PI / beta);
  const auto bottom = average_degree * 2 * std::pow(PI, 1 + dim / 2.0);
  return top / bottom;
}

std::vector<double> embeddingSD_t::generate_random_d_vector(int dim, double radius) {
  std::vector<double> positions;
  positions.resize(dim + 1);
  double norm{0};
  for (auto &pos : positions) {
    pos = normal_01(engine);
    norm += pos * pos;
  }
  norm /= std::sqrt(norm);
  // Normalize vector
  for (auto &pos: positions)
    pos = pos / norm * radius;

  return positions;
}

std::vector<double> embeddingSD_t::generate_random_d_vector_with_first_coordinate(int dim, double angle, double radius) {
  // Assuming that the initial vector v = (1, 0, 0)
  std::vector<double> positions;
  positions.resize(dim + 1);
  double norm{0};
  for (int i = 1; i < dim + 1; i++) { // without the first element
    positions[i] = normal_01(engine);
    norm += positions[i] * positions[i];
  }
  const auto firstNorm = norm / std::sqrt(norm);
  positions[0] = 1.0 / std::tan(angle) * firstNorm;
  normalize_and_rescale_vector(positions, radius);
  return positions;
}

double embeddingSD_t::compute_angle_d_vectors(const std::vector<double> &v1, const std::vector<double> &v2) {
  double angle{0}, norm1{0}, norm2{0};
  for (int i = 0; i < v1.size(); ++i) {
    angle += v1[i] * v2[i];
    norm1 += v1[i] * v1[i];
    norm2 += v2[i] * v2[i];
  }
  norm1 /= sqrt(norm1);
  norm2 /= sqrt(norm2);
  
  const auto result = angle / (norm1 * norm2);
  if (std::fabs(result - 1) < NUMERICAL_ZERO)
    return 0; // the same vectors
  else
    return std::acos(result);
}

double embeddingSD_t::ks_stats(const std::map<int, double> &inf_ccdf, 
                               const std::map<int, double> &obs_ccdf) {
  // Map info: key=degree, value=ccdf value for a given degree
  double statistic = 0;
  for (auto inf_vals = inf_ccdf.begin(); inf_vals != inf_ccdf.end(); ++inf_vals) {
    for (auto obs_vals = obs_ccdf.begin(); obs_vals != obs_ccdf.end(); ++obs_vals) {
      const int d_inf = inf_vals->first;
      const int d_obs = obs_vals->first;
      if (d_inf == d_obs) {
        const double diff = std::fabs(inf_vals->second - obs_vals->second);
        if (diff > statistic)
          statistic = diff;
      }
    }
  }
  return statistic;
}

bool embeddingSD_t::ks_test(const std::map<int, double> &inf_ccdf, 
                            const std::map<int, double> &obs_ccdf, 
                            double alpha) {
  const double n = inf_ccdf.size();
  const double m = obs_ccdf.size();
  const double statistic = ks_stats(inf_ccdf, obs_ccdf);
  const double c_alpha = std::sqrt(-std::log(alpha / 2) * 0.5);
  const double critical_value = c_alpha * std::sqrt((n + m) / (n * m));
  if (!QUIET_MODE) {
    std::clog << "Test statistic: " << statistic << ", critical_value: " << critical_value << std::endl;
  }
  return statistic > critical_value;
}

void embeddingSD_t::normalize_and_rescale_vector(std::vector<double> &v, double radius) {
  int dim = v.size() - 1;
  double norm=0;
  for (int i=0; i<dim + 1; ++i)
    norm += v[i] * v[i];
  
  norm = std::sqrt(norm);
  for (int i=0; i<dim + 1; ++i)
    v[i] /= norm;
  
  for (int i=0; i<dim + 1; ++i)
    v[i] *= radius;
}

double embeddingSD_t::euclidean_distance(const std::vector<double> &v1, const std::vector<double> &v2) {
  double distance=0;
  for (int i=0; i<v1.size(); ++i)
    distance += (v1[i] - v2[i]) * (v1[i] - v2[i]);
  return std::sqrt(distance);
}

void embeddingSD_t::save_kappas_and_exit() {
  // Save hidden degrees to file
  auto path = std::filesystem::path(EDGELIST_FILENAME);
  auto dirname = path.parent_path().string();
  auto filename = path.filename().string();
  std::string kappas_file = filename.substr(0, filename.find("."));
  kappas_file.append(".kappas");
  dirname.append("/");
  dirname.append(kappas_file.c_str()); // dirname + filename.kappas
  std::ofstream outfile(dirname);
  std::for_each(kappa.begin(), kappa.end(), [&outfile](auto x){
    outfile << x << '\n';
  });
  outfile.close();
  std::exit(13);
}


#endif // EMBEDDINGSD_HPP_INCLUDED