Raw File
hyperbolicspace.h
//
//  hyperbolicspace.hpp
//  threege
//
//  Created by Maximilian Katzmann on 24.10.16.
//  Copyright © 2016 Max Katzmann. All rights reserved.
//

#ifndef hyperbolicspace_hpp
#define hyperbolicspace_hpp

#include <math.h>
#include <stdio.h>

#include <cmath>

#include "coordinate.h"
#include "force.h"

using namespace std;

class HyperbolicSpace {
 public:
  static double distance_between_native_coordinate(const Coordinate &coord1,
                                                   const Coordinate &coord2) {
    /**
     * Check whether one of the coordinates is the origin. If it is, we can
     * simply return the radius of the second coordinate.
     */
    Coordinate origin = Coordinate();
    if (coord1 == origin) {
      return coord2.x;
    }
    if (coord2 == origin) {
      return coord1.x;
    }

    double lambda1 = coord1.y;
    double lambda2 = coord2.y;
    double phi1 = coord1.z;
    double phi2 = coord2.z;

    double deltaLambda = abs(lambda1 - lambda2);

    /**
     * The central angle is determined using the following function, which is
     * said to be rather robust against numerical difficulties.
     */
    double deltaSigma = atan2(
        sqrt(
            (cos(phi2) * sin(deltaLambda)) * (cos(phi2) * sin(deltaLambda)) +
            (cos(phi1) * sin(phi2) - sin(phi1) * cos(phi2) * cos(deltaLambda)) *
                (cos(phi1) * sin(phi2) -
                 sin(phi1) * cos(phi2) * cos(deltaLambda))),
        (sin(phi1) * sin(phi2) + cos(phi1) * cos(phi2) * cos(deltaLambda)));

    /**
     * Now we now the angular distance between the to particles. Next up: the
     * radial coordinates.
     */

    double r1 = coord1.x;
    double r2 = coord2.x;

    return acosh(cosh(r1) * cosh(r2) - sinh(r1) * sinh(r2) * cos(deltaSigma));
  }

  static double euclideanRadialDistanceToHyperbolicRadialDistance(double d) {
    return 2.0 * atanh(d);
  }

  static double hyperbolicRadialDistanceToEuclideanRadialDistance(double d) {
    return tanh(d / 2.0);
  }

  static Coordinate coordinate_mirrored_on_xz_plane(Coordinate coordinate) {
    return Coordinate(coordinate.x, -(coordinate.y - (2.0 * M_PI)),
                      coordinate.z);
  }
  static Coordinate coordinate_mirrored_on_xy_plane(Coordinate coordinate) {
    return Coordinate(coordinate.x, coordinate.y, -coordinate.z);
  }

  static Coordinate translate_point_along_x_axis_natively(
      Coordinate point, double hyperbolicDistance) {
    Coordinate original_point = point;

    Coordinate reference_point;
    if (hyperbolicDistance > 0.0) {
      reference_point = Coordinate(abs(hyperbolicDistance), M_PI, 0.0);
    } else if (hyperbolicDistance < 0.0) {
      reference_point = Coordinate(abs(hyperbolicDistance), 0.0, 0.0);
    } else {
      return point;
    }

    if (original_point.y > M_PI) {
      point = HyperbolicSpace::coordinate_mirrored_on_xz_plane(point);
    }

    if (original_point.z < 0) {
      point = HyperbolicSpace::coordinate_mirrored_on_xy_plane(point);
    }

    double d = abs(hyperbolicDistance);
    double r = point.x;
    double phi = point.z;

    double r_prime = HyperbolicSpace::distance_between_native_coordinate(
        point, reference_point);

    double z = asinh(sinh(r) * sin(phi));
    double x = acosh(cosh(r) / cosh(z));
    double x_prime = acosh(cosh(r_prime) / cosh(z));
    double phi_prime = asin(sinh(z) / sinh(r_prime));
    double lambda_prime =
        acos((cosh(x_prime) * cosh(d) - cosh(x)) / (sinh(x_prime) * sinh(d)));

    if (hyperbolicDistance < 0.0) {
      lambda_prime = M_PI - lambda_prime;
    }

    if (lambda_prime != lambda_prime) {
      lambda_prime = point.y;
    }

    if (phi_prime != phi_prime) {
      phi_prime = point.z;
    }

    Coordinate translated_coordinate =
        Coordinate(r_prime, lambda_prime, phi_prime);
    if (original_point.y > M_PI) {
      translated_coordinate = HyperbolicSpace::coordinate_mirrored_on_xz_plane(
          translated_coordinate);
    }

    if (original_point.z < 0) {
      translated_coordinate = HyperbolicSpace::coordinate_mirrored_on_xy_plane(
          translated_coordinate);
    }

    return translated_coordinate;
  }

  /**
   *  ASSUMES CARTESIAN COORDINATES!
   */
  static Coordinate translate_point_by_vector(const Coordinate &point,
                                              const Coordinate &vector) {
    if (fabs(point.x - vector.x) < 0.0000000001 &&
        fabs(point.y - vector.y) < 0.0000000001 &&
        fabs(point.z - vector.z) < 0.0000000001) {
      return Coordinate();
    }

    if (vector.y == 0.0 && vector.z == 0.0) {
      Coordinate spherical_point = point.cartesian_to_spherical();
      Coordinate translated_coordinate =
          HyperbolicSpace::translate_point_along_x_axis_natively(
              spherical_point, vector.x);
      return translated_coordinate.spherical_to_cartesian();
    }

    /**
     *  First we rotate everything such that the vector lies on the x-axis.
     */
    Coordinate reference_point = Coordinate(vector.length(), 0.0, 0.0);

    Coordinate origin = Coordinate();
    Coordinate rotation_axis = Coordinate::normal_vector_for_plane_from_points(
        origin, vector, reference_point);

    Coordinate spherical_vector = vector.cartesian_to_spherical();
    Coordinate spherical_reference_point =
        reference_point.cartesian_to_spherical();

    double rotation_angle =
        spherical_vector.central_angle_to_coordinate(spherical_reference_point);

    Coordinate rotated_point =
        point.coordinate_by_rotation(rotation_axis, rotation_angle);

    /**
     *  Now we translate everything such that the vector is placed on the
     * origin.
     */
    Coordinate rotated_point_spherical = rotated_point.cartesian_to_spherical();
    Coordinate translated_point_spherical =
        HyperbolicSpace::translate_point_along_x_axis_natively(
            rotated_point_spherical, -reference_point.x);

    Coordinate translated_point =
        translated_point_spherical.spherical_to_cartesian();

    if (translated_point.x != translated_point.x) {
      std::cout << "Here!" << std::endl;
    }

    /**
     *  Finally we rotate everything back.
     */
    Coordinate inverted_rotation_axis = rotation_axis * -1;
    return translated_point.coordinate_by_rotation(inverted_rotation_axis,
                                                   rotation_angle);
  }

  /**
   * Determines the weighted centroid on the line between the two passed
   * coordinates.
   *
   * @note It is assummed that the coordinates are present in spherical
   * coordinates.
   *
   * @param coord1 The spherical representation of the first coordinate.
   * @param weight1 The weight of the first coordinate.
   * @param coord2 The spherical representation of the second coordinate.
   * @param weight2 The weight of the second coordinate.
   * @return The weighted centroid on the line between the two coordinates.
   */
  static Coordinate weighted_centroid_between_coordinates(
      const Coordinate &coord1, double weight1, const Coordinate &coord2,
      double weight2) {
    if (coord1.x == 0.0) {
      double new_radius = coord2.x * (weight2 / (weight1 + weight2));
      return Coordinate(new_radius, coord2.y, coord2.z);
    }

    if (coord2.x == 0.0) {
      double new_radius = coord1.x * (weight1 / (weight1 + weight2));
      return Coordinate(new_radius, coord1.y, coord1.z);
    }

    double central_angle = coord1.central_angle_to_coordinate(coord2);

    double difference_to_M_PI = fabs(central_angle - M_PI);
    if (difference_to_M_PI < 0.0000000001) {
      double distance = coord1.x + coord2.x;
      double weighted_distance_from_q =
          distance * (weight1 / (weight1 + weight2));

      double new_radius = coord2.x - weighted_distance_from_q;

      if (new_radius < 0.0) {
        return Coordinate(fabs(new_radius), coord1.y, coord1.z);
      } else {
        return Coordinate(new_radius, coord2.y, coord2.z);
      }
    }

    if (fabs(central_angle) < 0.0000000001) {
      double distance = fabs(coord1.x - coord2.x);
      double weighted_distance_from_q =
          distance * (weight1 / (weight1 + weight2));

      if (coord2.x > coord1.x) {
        return Coordinate(coord2.x - weighted_distance_from_q, coord2.y,
                          coord2.z);
      } else {
        return Coordinate(coord2.x + weighted_distance_from_q, coord2.y,
                          coord2.z);
      }
    }

    double d =
        HyperbolicSpace::distance_between_native_coordinate(coord1, coord2);
    double d_q = d * (weight1 / (weight1 + weight2));
    double r_p = coord1.x;
    double r_q = coord2.x;

    double gamma =
        acos((cosh(d) * cosh(r_q) - cosh(r_p)) / (sinh(d) * sinh(r_q)));

    double r_c =
        acosh(cosh(d_q) * cosh(r_q) - sinh(d_q) * sinh(r_q) * cos(gamma));
    double alpha =
        acos((cosh(r_c) * cosh(r_q) - cosh(d_q)) / (sinh(r_c) * sinh(r_q)));

    /**
     *  Now we rotate q towards p by alpha and afterwards set the radius of
     *  the resulting point to r_c.
     */
    Coordinate coord_p_cartesian = coord1.spherical_to_cartesian();
    Coordinate coord_q_cartesian = coord2.spherical_to_cartesian();
    Coordinate origin = Coordinate();
    Coordinate rotation_axis = Coordinate::normal_vector_for_plane_from_points(
        origin, coord_q_cartesian, coord_p_cartesian);

    Coordinate rotated_q =
        coord_q_cartesian.coordinate_by_rotation(rotation_axis, alpha);
    Coordinate rotated_q_spherical = rotated_q.cartesian_to_spherical();
    rotated_q_spherical.x = r_c;

    return rotated_q_spherical;
  }
};

#endif /* hyperbolicspace_hpp */
back to top