Raw File
embedding.cc
#include "embedding.h"

#include <glog/logging.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>

#include <Eigen/Eigen>
#include <algorithm>
#include <fstream>
#include <iostream>
#include <limits>
#include <map>
#include <queue>
#include <random>
#include <sstream>

#include "hyperbolicspace.h"

using namespace Eigen;

#define FOR(i, n) for (int(i) = 0; (i) < (n); (i)++)

Embedding::Embedding(const Graph& G, vector<Coordinate>& points)
    : G(G), points(points) {
  current_forces = vector<Coordinate>(G.n, Coordinate());
}

double Embedding::max_radius() const {
  double max_radius = 0.0;

  FOR(i, G.n) {
    double radius = points[i].length();
    max_radius = std::max(radius, max_radius);
  }

  return max_radius;
}

void Embedding::apply_permutation(const std::vector<int>& permutation) {
  std::vector<Coordinate> new_points(G.n);

  FOR(i, G.n) { new_points[i] = points[permutation[i]]; }
  points = new_points;
}

std::string Embedding::to_string(bool use_labels) const {
  std::ostringstream oss;
  FOR(i, G.n) {
    if (use_labels) {
      oss << G.labels[i] << "\t" << points[i].x << "\t" << points[i].y << "\t"
          << points[i].z << std::endl;
    } else {
      oss << i << "\t" << points[i].x << "\t" << points[i].y << "\t"
          << points[i].z << std::endl;
    }
  }

  return oss.str();
}

string Embedding::to_string_spherical(bool use_labels) const {
  ostringstream oss;
  FOR(i, G.n) {
    Coordinate spherical_point = points[i].cartesian_to_spherical();
    if (use_labels) {
      oss << G.labels[i] << "\t(" << spherical_point.y * 180.0 / M_PI << ", "
          << spherical_point.z * 180.0 / M_PI << ")" << std::endl;
    } else {
      oss << i << "\t(" << spherical_point.y * 180.0 / M_PI << ", "
          << spherical_point.z * 180.0 / M_PI << ")" << std::endl;
    }
  }

  return oss.str();
}

string Embedding::to_string_2D(bool use_labels) const {
  ostringstream oss;
  FOR(i, G.n) {
    const auto point = points[i];
    const double radius = point.length();
    const double angle = point.phi();
    const double angleDegrees = angle / M_PI * 180.0;

    if (use_labels) {
      oss << G.labels[i] << "\t" << radius << "\t" << angleDegrees << "\n";
    } else {
      oss << i << "\t" << radius << "\t" << angleDegrees << "\n";
    }
  }

  return oss.str();
}

Coordinate Embedding::combined_force_on_vertex(Force& force, int vertex) {
  /**
   * Collect all the forces that act on the vertex.
   */
  Coordinate combinedForce = current_forces[vertex] * 0.25;
  FOR(j, G.n) {
    if (vertex != j) {
      /**
       * We now determine the force that vertex i applied on j. That is,
       * i is the sender and j is the receiver.
       */
      bool attractive = G.adjacent(vertex, j);
      Coordinate iCoord = points[vertex];
      Coordinate jCoord = points[j];

      Coordinate force_vector =
          force.force_from_coordinate_to_coordinate(jCoord, iCoord, attractive);
      combinedForce += force_vector;
    }
  }

  return combinedForce;
}

Coordinate Embedding::combined_approximate_force_on_vertex(Force& force,
                                                           int vertex) {
  // To simulate velocity, we consider the current_force again.
  Coordinate combined_force = current_forces[vertex] * this->velocity;

  return combined_force;
}

double Embedding::combined_radial_force_on_vertex(Force& force, int vertex) {
  /**
   * The force is determined as follows. If the currently expected
   * degree of the vertex (only depending on the radii of the other
   * vertices) is too small, we move the vertex inwards. If this expected
   * degree is too large, we move it outwards.
   */
  double currently_expected_degree = current_expected_degree_of_vertex(vertex);
  int actual_degree = G.edges[vertex].size();

  if (currently_expected_degree < actual_degree) {
    return -force.temperature;
  } else if (currently_expected_degree > actual_degree) {
    return force.temperature;
  } else {
    return 0.0;
  }
}

double Embedding::combined_approximate_radial_force_on_vertex(Force& force,
                                                              int vertex) {
  double combined_force = 0.0;

  return combined_force;
}

void Embedding::apply_force_to_vertices(Force& force) {
  int n = G.n;
  /**
   * We first determine the new positioins for all vertices and save them
   * afterwards. Else the new positions will already affect the current
   * iteration.
   */
  vector<Coordinate> new_coordinate_for_vertex(G.n);

  double temperate_force_sum = 0.0;
  FOR(i, n) {
    Coordinate combined_force = combined_force_on_vertex(force, i);
    current_forces[i] = combined_force;
    Coordinate rotation_axis = combined_force / combined_force.length();
    double rotation_angle = combined_force.length();

    temperate_force_sum += rotation_angle;

    new_coordinate_for_vertex[i] =
        points[i].coordinate_by_rotation(rotation_axis, rotation_angle);
  }

  FOR(i, n) { points[i] = new_coordinate_for_vertex[i]; }

  potential_history.push_back(temperate_force_sum / force.temperature);
  temperate_force_history.push_back(temperate_force_sum);
}

void Embedding::apply_sampled_force_to_vertices(
    Force& force, const double sample_balancing_coefficient) {
  double temperate_force_sum = 0.0;

  /**
   * We first determine the new positioins for all vertices and save them
   * afterwards. Else the new positions will already affect the current
   * iteration.
   */
  vector<Coordinate> new_coordinate_for_vertex(G.n);

  FOR(i, G.n) {
    Coordinate combined_force = combined_approximate_force_on_vertex(force, i);

    current_forces[i] = combined_force;

    double rotation_angle = combined_force.length();
    Coordinate rotation_axis = combined_force / rotation_angle;

    temperate_force_sum += rotation_angle;

    /**
     * Now we obtain the new coordinate by applying the rotation.
     */
    new_coordinate_for_vertex[i] =
        points[i].coordinate_by_rotation(rotation_axis, rotation_angle);
  }

  /**
   * Updating the cell data structure
   */
  FOR(i, G.n) { points[i] = new_coordinate_for_vertex[i]; }

  potential_history.push_back(temperate_force_sum / force.temperature);
  temperate_force_history.push_back(temperate_force_sum);
}

void Embedding::apply_force_to_vertices_and_flatten_towards_plane_with_normal(
    Force& force, Force& plane_force, Coordinate& unit_plane_normal) {
  int n = G.n;

  /**
   * We first determine the new positioins for all vertices and save them
   * afterwards. Else the new positions will already affect the current
   * iteration.
   */
  vector<Coordinate> new_coordinate_for_vertex(G.n);

  double temperate_force_sum = 0.0;

  double max_radius = this->max_radius();

  FOR(i, n) {
    Coordinate combined_force = combined_force_on_vertex(force, i);

    current_forces[i] = combined_force;
    double radius = points[i].length();

    /**
     *  Now we add the force that pulls the vertex towards the plane.
     *  Therefore, we get the coordinate of the vertex on the plane.
     *
     *  Meaning we take the radius of the vertex and project its angular
     *  coordinate onto the plane.
     */
    Coordinate rotation_axis_onto_plane =
        Coordinate::normal_vector_for_plane_from_points(
            Coordinate(), unit_plane_normal, points[i]);

    rotation_axis_onto_plane /= rotation_axis_onto_plane.length();
    Coordinate point_on_normal_vector = unit_plane_normal * radius;

    Coordinate point_on_plane = point_on_normal_vector.coordinate_by_rotation(
        rotation_axis_onto_plane, M_PI_2);
    double temp_temperature = plane_force.temperature;
    plane_force.temperature *= (radius / max_radius);
    Coordinate plane_attraction_force =
        plane_force.force_from_coordinate_to_coordinate(point_on_plane,
                                                        points[i], true);
    plane_force.temperature = temp_temperature;

    combined_force += plane_attraction_force;

    Coordinate rotation_axis = combined_force / combined_force.length();
    double rotation_angle = combined_force.length();
    temperate_force_sum += plane_attraction_force.length();

    new_coordinate_for_vertex[i] =
        points[i].coordinate_by_rotation(rotation_axis, rotation_angle);
  }

  FOR(i, n) { points[i] = new_coordinate_for_vertex[i]; }

  potential_history.push_back(temperate_force_sum / force.temperature);
  temperate_force_history.push_back(temperate_force_sum);
  average_plane_distance_history.push_back(
      average_distance_of_embedding_to_plane_with_normal(unit_plane_normal));
}

void Embedding::
    apply_approximate_force_to_vertices_and_flatten_towards_plane_with_normal(
        Force& force, Force& plane_force, Coordinate& unit_plane_normal) {
  /**
   * We first determine the new positioins for all vertices and save them
   * afterwards. Else the new positions will already affect the current
   * iteration.
   */
  vector<Coordinate> new_coordinate_for_vertex(G.n);

  double temperate_force_sum = 0.0;

  double max_radius = this->max_radius();

  FOR(i, G.n) {
    Coordinate combined_force = combined_approximate_force_on_vertex(force, i);

    current_forces[i] = combined_force;
    double radius = points[i].length();

    /**
     *  Now we add the force that pulls the vertex towards the plane.
     *  Therefore, we get the coordinate of the vertex on the plane.
     *
     *  Meaning we take the radius of the vertex and project its angular
     *  coordinate onto the plane.
     */
    Coordinate rotation_axis_onto_plane =
        Coordinate::normal_vector_for_plane_from_points(
            Coordinate(), unit_plane_normal, points[i]);

    rotation_axis_onto_plane /= rotation_axis_onto_plane.length();
    Coordinate point_on_normal_vector = unit_plane_normal * radius;

    Coordinate point_on_plane = point_on_normal_vector.coordinate_by_rotation(
        rotation_axis_onto_plane, M_PI_2);
    double temp_temperature = plane_force.temperature;
    plane_force.temperature *= (radius / max_radius);
    Coordinate plane_attraction_force =
        plane_force.force_from_coordinate_to_coordinate(point_on_plane,
                                                        points[i], true);
    plane_force.temperature = temp_temperature;

    combined_force += plane_attraction_force;

    Coordinate rotation_axis = combined_force / combined_force.length();
    double rotation_angle = combined_force.length();

    temperate_force_sum += plane_attraction_force.length();

    new_coordinate_for_vertex[i] =
        points[i].coordinate_by_rotation(rotation_axis, rotation_angle);
  }

  FOR(i, G.n) { points[i] = new_coordinate_for_vertex[i]; }

  potential_history.push_back(temperate_force_sum / force.temperature);
  temperate_force_history.push_back(temperate_force_sum);
  average_plane_distance_history.push_back(
      average_distance_of_embedding_to_plane_with_normal(unit_plane_normal));
}

void Embedding::apply_radial_force_to_vertices(Force& force) {
  int n = G.n;

  /**
   * We first determine the new positioins for all vertices and save them
   * afterwards. Else the new positions will already affect the current
   * iteration.
   */
  vector<Coordinate> new_coordinate_for_vertex(G.n);

  double radial_potential_sum = 0.0;
  FOR(i, n) {
    double combined_force = combined_radial_force_on_vertex(force, i);

    radial_potential_sum += fabs(combined_force);

    double current_radius = points[i].length();
    double new_radius = current_radius + combined_force;
    if (new_radius < 0.0) {
      new_radius = 0.0;
    }

    Coordinate current_point = points[i] / current_radius;
    new_coordinate_for_vertex[i] = current_point * new_radius;
  }

  FOR(i, G.n) {
    /**
     * Updating the cell data structure.
     */
    // Coordinate current_spherical = points[i].cartesian_to_spherical();
    // Coordinate new_spherical =
    //     new_coordinate_for_vertex[i].cartesian_to_spherical();
    // cell_structure.remove_node_with_position(i, current_spherical);
    // int new_cell = cell_structure.add_node_with_position(i, new_spherical);
    // cell_for_vertex[i] = new_cell;
    points[i] = new_coordinate_for_vertex[i];
  }

  radial_potential_history.push_back(radial_potential_sum / force.temperature);
  temperate_radial_potential_history.push_back(radial_potential_sum);
}

void Embedding::apply_approximate_radial_force_to_vertices(Force& force) {
  /**
   * We first determine the new positioins for all vertices and save them
   * afterwards. Else the new positions will already affect the current
   * iteration.
   */
  vector<Coordinate> new_coordinate_for_vertex(G.n);

  double radial_potential_sum = 0.0;
  FOR(i, G.n) {
    double combined_force =
        combined_approximate_radial_force_on_vertex(force, i);

    radial_potential_sum += fabs(combined_force);

    double current_radius = points[i].length();
    double new_radius = current_radius + combined_force;
    if (new_radius > 0.0 && current_radius > 0.0) {
      new_coordinate_for_vertex[i] = (points[i] / current_radius) * new_radius;
    }
  }

  FOR(i, G.n) {
    /**
     * Updating the cell data structure.
     */
    points[i] = new_coordinate_for_vertex[i];
  }

  radial_potential_history.push_back(radial_potential_sum / force.temperature);
  temperate_radial_potential_history.push_back(radial_potential_sum);
}

void Embedding::read_embedding_from_file(
    const string& filename, unordered_map<std::string, int>& label_to_node) {
  std::ifstream file;
  file.open(filename.c_str());
  CHECK(file.good()) << "Maybe the file " << filename << " doesn't exist?";

  points.resize(G.n);

  // read all nodes
  string current_label, current_x, current_y;
  while (file >> current_label && file >> current_x && file >> current_y) {
    if ((file >> std::ws).peek() == '#') {
      // ignore rest of the line
      std::getline(file, current_label);
      continue;
    }

    current_label = current_label.substr(0, current_label.find("/"));
    current_x = current_x.substr(0, current_x.find("/"));
    // current_x.erase(std::remove(current_x.begin(), current_x.end(), '('),
    //                 current_x.end());
    current_y = current_y.substr(0, current_y.find("/"));
    if ((label_to_node).find(current_label) != label_to_node.end()) {
      double x = atof(current_x.c_str());
      double y = atof(current_y.c_str());
      points[label_to_node[current_label]] = Coordinate(x, y, 0.0);
    }
  }
  file.close();
}

void Embedding::read_embedding_from_file_3D(
    const string& filename, unordered_map<std::string, int>& label_to_node) {
  std::ifstream file;
  file.open(filename.c_str());
  CHECK(file.good()) << "Maybe the file " << filename << " doesn't exist?";

  points.resize(G.n);

  // read all nodes
  string current_label, current_x, current_y, current_z;
  while (file >> current_label && file >> current_x && file >> current_y &&
         file >> current_z) {
    if ((file >> std::ws).peek() == '#') {
      // ignore rest of the line
      std::getline(file, current_label);
      continue;
    }

    current_label = current_label.substr(0, current_label.find("/"));
    current_x = current_x.substr(0, current_x.find("/"));
    // current_x.erase(std::remove(current_x.begin(), current_x.end(), '('),
    //                 current_x.end());
    current_y = current_y.substr(0, current_y.find("/"));
    current_z = current_z.substr(0, current_z.find("/"));
    if ((label_to_node).find(current_label) != label_to_node.end()) {
      double x = atof(current_x.c_str());
      double y = atof(current_y.c_str());
      double z = atof(current_z.c_str());
      points[label_to_node[current_label]] = Coordinate(x, y, z);
    }
  }
  file.close();
}

void Embedding::draw(const char* filename, const vector<int>& nodes,
                     int nodeDegreeThreshhold, const string& add_to_svg,
                     int height, int width) const {
  int add = 0;

  FILE* file = fopen(filename, "w");
  if (file == NULL) {
    LOG(WARNING) << "Could not open file. " << strerror(errno);
    LOG(WARNING) << "Current working dir=" << getcwd(NULL, 0);
    ;
  }

  vector<std::string> colors = {"red",
                                "aliceblue",
                                "antiquewhite",
                                "aqua",
                                "aquamarine",
                                "azure",
                                "beige",
                                "bisque",
                                "blanchedalmond",
                                "blue",
                                "blueviolet",
                                "brown",
                                "burlywood",
                                "cadetblue",
                                "chartreuse",
                                "chocolate",
                                "coral",
                                "cornflowerblue",
                                "cornsilk",
                                "crimson",
                                "cyan",
                                "darkblue",
                                "darkcyan",
                                "darkgoldenrod",
                                "darkgray",
                                "darkgreen",
                                "darkgrey",
                                "darkkhaki",
                                "darkmagenta",
                                "darkolivegreen",
                                "darkorange",
                                "darkorchid",
                                "darkred",
                                "darksalmon",
                                "darkseagreen",
                                "darkslateblue",
                                "darkslategray",
                                "darkslategrey",
                                "darkturquoise",
                                "darkviolet",
                                "deeppink",
                                "deepskyblue",
                                "dimgray",
                                "dimgrey",
                                "dodgerblue",
                                "firebrick",
                                "floralwhite",
                                "forestgreen",
                                "fuchsia",
                                "gainsboro",
                                "ghostwhite",
                                "gold",
                                "goldenrod",
                                "gray",
                                "green",
                                "greenyellow",
                                "grey",
                                "honeydew",
                                "hotpink",
                                "indianred",
                                "indigo",
                                "ivory",
                                "khaki",
                                "lavender",
                                "lavenderblush",
                                "lawngreen",
                                "lemonchiffon",
                                "lightblue",
                                "lightcoral",
                                "lightcyan",
                                "lightgoldenrodyellow",
                                "lightgray",
                                "lightgreen",
                                "lightgrey",
                                "lightpink",
                                "lightsalmon",
                                "lightseagreen",
                                "lightskyblue",
                                "lightslategray",
                                "lightslategrey",
                                "lightsteelblue",
                                "lightyellow",
                                "lime",
                                "limegreen",
                                "linen",
                                "magenta(Safe",
                                "16=fuchsia",
                                "Hex3)",
                                "maroon",
                                "mediumaquamarine",
                                "mediumblue",
                                "mediumorchid",
                                "mediumpurple",
                                "mediumseagreen",
                                "mediumslateblue",
                                "mediumspringgreen",
                                "mediumturquoise",
                                "mediumvioletred",
                                "midnightblue",
                                "mintcream",
                                "mistyrose",
                                "moccasin",
                                "navajowhite",
                                "navy",
                                "oldlace",
                                "olive",
                                "olivedrab",
                                "orange",
                                "orangered",
                                "orchid",
                                "palegoldenrod",
                                "palegreen",
                                "paleturquoise",
                                "palevioletred",
                                "papayawhip",
                                "peachpuff",
                                "peru",
                                "pink",
                                "plum",
                                "powderblue",
                                "purple",
                                "rosybrown",
                                "royalblue",
                                "saddlebrown",
                                "salmon",
                                "sandybrown",
                                "seagreen",
                                "seashell",
                                "sienna",
                                "silver",
                                "skyblue",
                                "slateblue",
                                "slategray",
                                "slategrey",
                                "snow",
                                "springgreen",
                                "steelblue",
                                "tan",
                                "teal",
                                "thistle",
                                "tomato",
                                "turquoise",
                                "violet",
                                "wheat",
                                "white",
                                "whitesmoke",
                                "yellow",
                                "yellowgreen"};

  fprintf(file,
          "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE svg PUBLIC "
          "\"-//W3C//DTD SVG 1.1//EN\" "
          "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n\n<svg "
          "xmlns=\"http://www.w3.org/2000/svg\"\nxmlns:xlink=\"http://"
          "www.w3.org/1999/xlink\" "
          "xmlns:ev=\"http://www.w3.org/2001/xml-events\"\nversion=\"1.1\" "
          "baseProfile=\"full\"\nwidth=\"%d\" height=\"%d\">\n\n",
          width + 2 * add, height + 2 * add);

  // comment in/out to see edges
  int num_nodes = 0;
  double R = this->max_radius();
  FOR(i, G.n)
  if (nodes[i] > 0) ++num_nodes;
  if (num_nodes <= 500000) {
    FOR(i, G.n) {
      FOR(jj, (int)G.edges[i].size()) {
        int j = G.edges[i][jj];
        if (!nodes[i] || !nodes[j]) continue;

        // skip long edges
        // TODO remove
        // if (HYPT::dist(pts[i], pts[j]) > 1.5 * R) continue;
        //
        // double opacity =
        //     std::min(1.0, (1.5 - Force::hyperbolicDistanceBetweenCoordinates(
        //                              points[i], points[j]) /
        //                    (R * 2.0)));
        double opacity = 1.0;

        bool doubleedge = false;
        FOR(k, jj)
        if (G.edges[i][k] == j) doubleedge = true;
        double x1 =
            add +
            0.5 * width * (1. + cos(points[i].phi()) * points[i].length() / R);
        double x2 =
            add +
            0.5 * width * (1. + cos(points[j].phi()) * points[j].length() / R);
        double y1 =
            add +
            0.5 * height * (1. + sin(points[i].phi()) * points[i].length() / R);
        double y2 =
            add +
            0.5 * height * (1. + sin(points[j].phi()) * points[j].length() / R);
        fprintf(file,
                "<line x1=\"%.8f\" y1=\"%.8f\" x2=\"%.8f\" y2=\"%.8f\" "
                "stroke=\"%s\" stroke-width=\"0.3\" opacity=\"%.8f\"/>\n",
                x1, y1, x2, y2, doubleedge ? "blue" : "gray", opacity);
      }
    }
  }

  FOR(i, G.n) {
    CHECK(nodes[i] < colors.size() + 2);
    if (nodes[i] == 0) continue;
    if (nodes[i] == 1)
      fprintf(file,
              "<circle cx=\"%.8f\" cy=\"%.8f\" r=\"%.8f\" fill=\"black\" "
              "stroke=\"black\" stroke-width=\"0\"/>\n",
              add + 0.5 * width *
                        (1. + cos(points[i].phi()) * points[i].length() / R),
              add + 0.5 * height *
                        (1. + sin(points[i].phi()) * points[i].length() / R),
              0.0015 * min(width, height));
    else
      fprintf(file,
              "<circle cx=\"%.8f\" cy=\"%.8f\" r=\"%.8f\" fill=\"%s\" "
              "stroke=\"%s\" stroke-width=\"0\"/>\n",
              add + 0.5 * width *
                        (1. + cos(points[i].phi()) * points[i].length() / R),
              add + 0.5 * height *
                        (1. + sin(points[i].phi()) * points[i].length() / R),
              0.003 * min(width, height), colors[nodes[i] - 2].c_str(),
              colors[nodes[i] - 2].c_str());
    //    if (num_nodes < 5000)

    if (G.edges[i].size() > nodeDegreeThreshhold) {
      fprintf(file,
              "<text x=\"%.8f\" y=\"%.8f\" font-family=\"sans-serif\" "
              "font-size=\"%dpx\" fill=\"red\" >%s</text>\n",
              add +
                  0.5 * width *
                      (1. + cos(points[i].phi()) * points[i].length() / R) +
                  0.002 * min(width, height),
              add + 0.5 * height *
                        (1. + sin(points[i].phi()) * points[i].length() / R),
              15, G.labels[i].c_str());
    }
  }

  // Boundary of hypRG
  fprintf(file,
          "<circle cx=\"%.8f\" cy=\"%.8f\" r=\"%.8f\" style=\"stroke: blue; "
          "fill: none;\"/>\n",
          add + 0.5 * width, add + 0.5 * height, 0.5 * min(width, height));

  fwrite(add_to_svg.c_str(), sizeof(char), add_to_svg.size(), file);

  fprintf(file, "\n</svg>\n");
  fclose(file);
}

void Embedding::get_phi_phi_plot(vector<double>& other_phi_values,
                                 vector<pair<double, double>>& phi_phi_plot) {
  FOR(i, G.n) {
    double phi_value = points[i].phi();
    double other_phi_value = other_phi_values[i];

    phi_phi_plot[i] = pair<double, double>(other_phi_value, phi_value);
  }
}

void Embedding::get_r_r_plot(vector<double>& other_r_values,
                             vector<pair<double, double>>& r_r_plot) {
  FOR(i, G.n) {
    double r_value = points[i].length();
    double other_r_value = other_r_values[i];

    r_r_plot[i] = pair<double, double>(other_r_value, r_value);
  }
}

void Embedding::get_phi_values(vector<double>& phi_values) {
  FOR(i, G.n) { phi_values[i] = points[i].phi(); }
}

void Embedding::get_r_values(vector<double>& r_values) {
  FOR(i, G.n) { r_values[i] = points[i].length(); }
}

double Embedding::log_likelihood() const {
  double R = this->max_radius();
  double T = 0.1;
  double summed_log_likelihood = 0.0;
  FOR(i, G.n) {
    FOR(j, G.n) {
      if (i != j) {
        double distance = Force::hyperbolic_distance_between_coordinates(
            points[i], points[j]);
        if (G.adjacent(i, j)) {
          summed_log_likelihood +=
              log((1.0 / (1.0 + exp((1.0 / (2.0 * T)) * (distance - R)))));
        } else {
          if (distance < R) {
            double inverted_distance = 2.0 * R - distance;
            summed_log_likelihood += log(
                1.0 / (1.0 + exp((1.0 / (2.0 * T)) * (inverted_distance - R))));
          } else {
            summed_log_likelihood += log(
                1.0 - (1.0 / (1.0 + exp((1.0 / (2.0 * T)) * (distance - R)))));
          }
        }
      }
    }
  }

  return summed_log_likelihood;
}

string Embedding::edge_histogram() {
  vector<pair<int, int>> histogram;
  edge_length_histogram(histogram);

  ostringstream oss;
  oss << "Bucket, Edges, Non-Edges" << std::endl;
  FOR(i, histogram.size()) {
    pair<int, int> bucket = histogram[i];
    oss << i << "," << bucket.first << "," << bucket.second << std::endl;
  }
  return oss.str();
}

void Embedding::edge_length_histogram(vector<std::pair<int, int>>& histogram) {
  int number_of_buckets = 100;
  // double max_distance = 2 * R;

  double max_distance = 0.0;
  FOR(i, G.n - 1) {
    for (int j = i + 1; j < G.n; j++) {
      double vertex_distance =
          Force::hyperbolic_distance_between_coordinates(points[i], points[j]);
      if (vertex_distance > max_distance) {
        max_distance = vertex_distance;
      }
    }
  }

  double bucket_size = max_distance / double(number_of_buckets);
  vector<int> edges(number_of_buckets + 1, 0);
  vector<int> non_edges(number_of_buckets + 1, 0);

  int nan_count = 0;
  FOR(i, G.n) {
    FOR(j, G.n) {
      if (i < j) {
        double vertex_distance = Force::hyperbolic_distance_between_coordinates(
            points[i], points[j]);
        if (vertex_distance == vertex_distance) {
          int bucket = int(vertex_distance / bucket_size);
          if (G.adjacent(i, j)) {
            edges[bucket] += 1;
          } else {
            non_edges[bucket] += 1;
          }
        } else {
          nan_count += 1;
        }
      }
    }
  }

  std::cout << "Ignored " << nan_count << " NaNs." << std::endl;

  FOR(i, number_of_buckets) {
    pair<int, int> bucket_content(edges[i], non_edges[i]);
    histogram.push_back(bucket_content);
  }
}

double Embedding::current_expected_degree_of_vertex(int vertex) {
  double R = this->max_radius();
  double vertex_radius = points[vertex].length();
  double first_factor = 2.0 / M_PI * exp((R - vertex_radius) / 2.0);

  double radii_sum = 0.0;
  FOR(i, G.n) {
    if (i != vertex) {
      double other_vertex_radius = points[i].length();
      radii_sum += exp(-(other_vertex_radius / 2.0));
    }
  }

  return first_factor * radii_sum;
}

Coordinate Embedding::fitting_plane() const {
  int n = G.n;
  Matrix<double, 3, Dynamic> point_cloud_matrix;

  FOR(i, n) {
    vector<double> point = {points[i].x, points[i].y, points[i].z};
    Vector3d column(point.data());
    point_cloud_matrix.conservativeResize(point_cloud_matrix.rows(),
                                          point_cloud_matrix.cols() + 1);
    point_cloud_matrix.col(point_cloud_matrix.cols() - 1) = column;
  }

  /**
   *  Perform a singular value decomposition of the point cloud matrix.
   */
  Eigen::JacobiSVD<Matrix<double, 3, Dynamic>> svd(
      point_cloud_matrix, Eigen::ComputeFullU | Eigen::ComputeFullV);
  VectorXd singular_values = svd.singularValues();
  vector<double> singulars(
      singular_values.data(),
      singular_values.data() + singular_values.rows() * singular_values.cols());

  /**
   *  Find the smallest singular value.
   */
  int index_of_minimal_singular = 0;
  double minimal_singular = std::numeric_limits<double>::max();
  FOR(i, singulars.size()) {
    if (singulars[i] < minimal_singular) {
      minimal_singular = singulars[i];
      index_of_minimal_singular = i;
    }
  }

  VectorXd minimal_left_singular_vector =
      svd.matrixU().col(index_of_minimal_singular);
  vector<double> plane_normal_vector(
      minimal_left_singular_vector.data(),
      minimal_left_singular_vector.data() +
          minimal_left_singular_vector.rows() *
              minimal_left_singular_vector.cols());

  return Coordinate(plane_normal_vector[0], plane_normal_vector[1],
                    plane_normal_vector[2]);
}

Coordinate Embedding::euclidean_centroid() const {
  Coordinate centroid = Coordinate();
  FOR(i, points.size()) { centroid += points[i]; }

  centroid /= (double)points.size();
  return centroid;
}

Coordinate Embedding::approximate_centroid() const {
  int min_degree = G.edges[G.n - 1].size();
  int first_index_of_degree_one_vertices = 0;
  for (int i = G.n - 1; i > 0; i--) {
    if (G.edges[i].size() > min_degree) {
      first_index_of_degree_one_vertices = i;
      break;
    }
  }

  /**
   *  Randomly sampling degree-1 vertices and choosing the pair with the
   *  largest distance.
   */
  std::random_device rd;   // obtain a random number from hardware
  std::mt19937 eng(rd());  // seed the generator
  std::uniform_int_distribution<> distr(first_index_of_degree_one_vertices,
                                        G.n - 1);  // define the range

  double max_distance = 0.0;
  Coordinate max_dist_point_1 = Coordinate();
  Coordinate max_dist_point_2 = Coordinate();

  for (int k = 0; k < 20; k++) {
    int vertex_1 = distr(eng);
    int vertex_2 = distr(eng);

    Coordinate point_1 = points[vertex_1];
    Coordinate point_2 = points[vertex_2];

    double distance =
        Force::hyperbolic_distance_between_coordinates(point_1, point_2);

    if (distance > max_distance) {
      max_distance = distance;
      max_dist_point_1 = point_1;
      max_dist_point_2 = point_2;
    }
  }

  if (max_distance == 0.0) {
    return Coordinate();
  }

  Coordinate max_point_1_spherical = max_dist_point_1.cartesian_to_spherical();
  Coordinate max_point_2_spherical = max_dist_point_2.cartesian_to_spherical();

  Coordinate approximate_centroid =
      HyperbolicSpace::weighted_centroid_between_coordinates(
          max_point_1_spherical, 1.0, max_point_2_spherical, 1.0);
  return approximate_centroid.spherical_to_cartesian();
}

void Embedding::center() {
  Coordinate apprximate_centroid = approximate_centroid();
  move_vector_to_origin(apprximate_centroid);
}

void Embedding::move_vector_to_origin(const Coordinate& vector) {
  if (fabs(vector.x) > 0.0000000001 || fabs(vector.y) > 0.0000000001 ||
      fabs(vector.z) > 0.0000000001) {
    FOR(i, points.size()) {
      Coordinate old_coordinate_spherical = points[i].cartesian_to_spherical();
      Coordinate new_coordinate =
          HyperbolicSpace::translate_point_by_vector(points[i], vector);
      points[i] = new_coordinate;
    }
  }
}

double Embedding::distance_from_point_to_plane_with_normal(Coordinate& point,
                                                           Coordinate& normal) {
  double denominator =
      sqrt(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z);

  double enumerator =
      normal.x * point.x + normal.y * point.y + normal.z * point.z;

  return abs(enumerator) / denominator;
}

double Embedding::average_distance_of_embedding_to_plane_with_normal(
    Coordinate& normal) {
  double total_distance = 0.0;
  FOR(i, G.n) {
    total_distance +=
        distance_from_point_to_plane_with_normal(points[i], normal);
  }

  return total_distance / (double)G.n;
}

bool Embedding::is_stable() const { return is_stable_with_threshold(0.25); }

bool Embedding::is_stable_with_threshold(double threshhold) const {
  int iterations = potential_history.size();
  if (iterations > 10) {
    bool descending = true;
    for (int i = 1; i < 9; i++) {
      if (potential_history[iterations - i] >=
          potential_history[iterations - (i + 1)]) {
        descending = false;
        break;
      }
    }

    if (descending) {
      double previous_potential = potential_history[iterations - 2];
      double current_potential = potential_history[iterations - 1];

      double potential_difference = previous_potential - current_potential;
      if (potential_difference < threshhold) {
        return true;
      }
    }

    if (iterations > 20) {
      /**
       * Checking for oscillations.
       */
      double max_difference = 0.0;
      for (int i = 1; i < 19; i++) {
        double difference = fabs(potential_history[iterations - i] -
                                 potential_history[iterations - (i + 1)]);
        if (difference > max_difference) {
          max_difference = difference;
        }
      }

      if (max_difference < 1.0) {
        return true;
      }
    }
  }

  return false;
}

bool Embedding::is_radially_stable() const {
  int iterations = radial_potential_history.size();
  if (iterations >= 2) {
    double previous_potential = radial_potential_history[iterations - 2];
    double current_potential = radial_potential_history[iterations - 1];

    if (current_potential < previous_potential) {
      double potential_difference = previous_potential - current_potential;
      if (potential_difference < 0.25) {
        return true;
      }
    }
  }

  return false;
}

void Embedding::convert_to_2D_embedding() {
  Coordinate fitting_plane_normal = fitting_plane();
  fitting_plane_normal /= fitting_plane_normal.length();

  Coordinate z_axis = Coordinate(0.0, 0.0, 1.0);

  int n = G.n;
  FOR(i, n) {
    /**
     *  Now we add the force that pulls the vertex towards the plane.
     *  Therefore, we get the coordinate of the vertex on the plane.
     *
     *  Meaning we take the radius of the vertex and project its angular
     *  coordinate onto the plane.
     */
    Coordinate rotation_axis_onto_plane =
        Coordinate::normal_vector_for_plane_from_points(
            Coordinate(), fitting_plane_normal, points[i]);

    rotation_axis_onto_plane /= rotation_axis_onto_plane.length();
    Coordinate point_on_normal_vector =
        fitting_plane_normal * points[i].length();

    Coordinate point_on_plane = point_on_normal_vector.coordinate_by_rotation(
        rotation_axis_onto_plane, M_PI_2);

    /**
     *  Now all the points are on the same plane. It remains to rotated the
     *  plane such that it is equal to the xy-plane.
     */

    Coordinate rotation_axis_onto_x_y_plane =
        Coordinate::normal_vector_for_plane_from_points(Coordinate(), z_axis,
                                                        fitting_plane_normal);

    double rotation_angle =
        atan2(z_axis.cross_product(fitting_plane_normal).length(),
              z_axis.dot_product(fitting_plane_normal));

    Coordinate new_coordinate = point_on_plane.coordinate_by_rotation(
        rotation_axis_onto_x_y_plane, M_PI - rotation_angle);

    points[i] = new_coordinate;
  }
}
back to top