Skip to content

Manifolds

Each surface is a class implementing a shared three-method interface: project a vector into the tangent plane, project a stray point back onto the surface, and sample noise that already lies in the tangent plane. Every method has a vectorized _multiple twin that takes an (N, d) array, and those are what the simulators call.

See driving the manifolds directly for worked examples, including how to add a surface of your own.

Manifold

Manifold

Bases: ABC

Abstract base class for manifold implementations.

A manifold is a space that has constraints.

Every subclass of Manifold should implement methods to project vectors onto tangent spaces, project points back onto the manifold and sample tangent-space noise.

project_to_tangent abstractmethod

project_to_tangent(x, v)

Projects a vector v onto the tangent space at point x.

The tangent space is directions that a point x is able to move to while it stays on the manifold. It is also known as the local approximation at a point.

The vector v may be pointing to the manifold, violating the geometry of the manifold, or pointing off the manifold. This method ensures that the returned vector satisfies manifold constraints.

Parameters:

Name Type Description Default
x

A point on the manifold.

required
v

The vector to project.

required

Returns:

Type Description

A tangent vector at x.

Source code in src/wanderwalk/manifolds/base.py
@abstractmethod
def project_to_tangent(self, x, v):
    """Projects a vector v onto the tangent space at point x.

    The tangent space is directions that a point x is able to
    move to while it stays on the manifold. It is also known
    as the local approximation at a point.

    The vector v may be pointing to the manifold, violating the
    geometry of the manifold, or pointing off the manifold. This
    method ensures that the returned vector satisfies manifold
    constraints.

    Arguments:
        x: A point on the manifold.
        v: The vector to project.

    Returns:
        A tangent vector at x.
    """
    pass

project_to_manifold abstractmethod

project_to_manifold(x)

Projects a point x onto the manifold.

The point x may be slightly off of the manifold due to numerical computations throughout the algorithm. This method ensures the point x is projected back onto the manifold before it is used again.

Parameters:

Name Type Description Default
x

A point that may not be on the manifold.

required

Returns:

Type Description

A point on the manifold.

Source code in src/wanderwalk/manifolds/base.py
@abstractmethod
def project_to_manifold(self, x):
    """Projects a point x onto the manifold.

    The point x may be slightly off of the manifold due to numerical
    computations throughout the algorithm. This method ensures the
    point x is projected back onto the manifold before it is used
    again.

    Arguments:
        x: A point that may not be on the manifold.

    Returns:
        A point on the manifold.
    """
    pass

sample_tangent_noise abstractmethod

sample_tangent_noise(x)

Generates a vector which lies in the tangent space at point x. It represents sample noise because the returned vector is random.

Random Euclidean noise does not always keep the point x on the manifold. This method ensures that the generated noise lies entirely within the tangent space at point x on the manifold.

Parameters:

Name Type Description Default
x

A point on the manifold.

required

Returns:

Type Description

A noise vector in tangent space.

Source code in src/wanderwalk/manifolds/base.py
@abstractmethod
def sample_tangent_noise(self, x):
    """Generates a vector which lies in the tangent space at point x.
    It represents sample noise because the returned vector is random.

    Random Euclidean noise does not always keep the point x on
    the manifold. This method ensures that the generated noise lies
    entirely within the tangent space at point x on the manifold.

    Arguments:
        x: A point on the manifold.

    Returns:
        A noise vector in tangent space.
    """
    pass

Sphere

Sphere

Bases: Manifold

The Sphere class represents the unit sphere manifold. Points on the manifold must therefore remain normalized and noise must be tangent to the sphere.

Since points on the manifold must be normalized, the magnitude of a point x is equal to 1 (unit length). The tangent vector v of a point x satisfies dot product of x and v is equal to 0. This means they are orthogonal to each other and the tangent vector v is on the tangent space of x.

The Sphere class contains methods to ensure these constraints, as well as a method to compute the Euler-Maruyama step for the next position which is also on the manifold.

project_to_manifold

project_to_manifold(x)

Normalizes a point x, ensuring it is a point on the sphere.

Parameters:

Name Type Description Default
x

A point in R^3 that may or may not lie on the sphere.

required

Returns:

Type Description

The normalized form of point x.

Source code in src/wanderwalk/manifolds/sphere.py
def project_to_manifold(self, x):
    """Normalizes a point x, ensuring it is a point on the sphere.

    Arguments:
        x: A point in R^3 that may or may not lie on the sphere.

    Returns:
        The normalized form of point x.
    """
    norm = np.linalg.norm(x)
    return x / norm

project_to_manifold_multiple

project_to_manifold_multiple(X)

Normalizes more than one point (all the points are defined as X), ensuring they are all on the sphere.

Parameters:

Name Type Description Default
X

Many points in R^3 that may or may not lie on the sphere.

required

Returns:

Type Description

The normalized form of all of the points of X.

Source code in src/wanderwalk/manifolds/sphere.py
def project_to_manifold_multiple(self, X):
    """Normalizes more than one point (all the points are defined
    as X), ensuring they are all on the sphere.

    Arguments:
        X: Many points in R^3 that may or may not lie on the sphere.

    Returns:
        The normalized form of all of the points of X.
    """
    norms = np.linalg.norm(X, axis=1, keepdims=True)
    return X / norms

project_to_tangent

project_to_tangent(x, v)

Removes the radial component and only returns the tangential component of vector v at point x. This makes vector v lie in the tangent space of point x, which represents all the possible directions the point x can move while staying on the sphere.

Uses the formula

v_tangential = v - (dot product of v and x)x

This removes the component of vector v in the direction of x, leaving only the component orthogonal to x.

Parameters:

Name Type Description Default
x

A point on the sphere.

required
v

The tangent vector in R^3 at point x, which may or may not be tangent at point x.

required

Returns:

Type Description

The component of vector v which lies in the tangent space

at point x.

Source code in src/wanderwalk/manifolds/sphere.py
def project_to_tangent(self, x, v):
    """Removes the radial component and only returns the tangential
    component of vector v at point x. This makes vector v lie in
    the tangent space of point x, which represents all the possible
    directions the point x can move while staying on the sphere.

    Uses the formula:
        v_tangential = v - (dot product of v and x)x

    This removes the component of vector v in the direction of x,
    leaving only the component orthogonal to x.

    Arguments:
        x: A point on the sphere.
        v: The tangent vector in R^3 at point x, which may or may
            not be tangent at point x.

    Returns:
        The component of vector v which lies in the tangent space
        at point x.
    """
    dot_prod = np.dot(v, x)
    return v - (dot_prod * x)

project_to_tangent_multiple

project_to_tangent_multiple(X, V)

Removes the radial component and only returns the tangential component of every vector in the group of vectors V at every point in the group of points X. This makes each vector in V lie in the tangent space of its respective point in the group of points X, which represents all the possible directions the point can move while staying on the sphere.

Uses the formula

v_tangential = v - (dot product of v and x)x

This removes the component of a vector in the direction of a point, leaving only the component orthogonal to the point.

Parameters:

Name Type Description Default
X

A set of points on the sphere.

required
V

A set of tangent vectors in R^3 which correspond to a point in X, which may or may not be tangent at that point.

required

Returns:

Type Description

The component of each vector in V which lies in the tangent space

of its respective point in X.

Source code in src/wanderwalk/manifolds/sphere.py
def project_to_tangent_multiple(self, X, V):
    """Removes the radial component and only returns the tangential
    component of every vector in the group of vectors V at every point
    in the group of points X. This makes each vector in V lie in the
    tangent space of its respective point in the group of points X,
    which represents all the possible directions the point can move
    while staying on the sphere.

    Uses the formula:
        v_tangential = v - (dot product of v and x)x

    This removes the component of a vector in the direction of a point,
    leaving only the component orthogonal to the point.

    Arguments:
        X: A set of points on the sphere.
        V: A set of tangent vectors in R^3 which correspond to a point
            in X, which may or may not be tangent at that point.

    Returns:
        The component of each vector in V which lies in the tangent space
        of its respective point in X.
    """
    dot_prod = np.sum(V * X, axis=1, keepdims=True)
    return V - (dot_prod * X)

sample_tangent_noise

sample_tangent_noise(x)

Generates a random Gaussian vector in R^3 and projects it onto the tangent space at point x, which is on the sphere.

Parameters:

Name Type Description Default
x

A point on the sphere.

required

Returns:

Type Description

A random Gaussian vector in R^3 which is on the tangent space

at point x on the sphere.

Source code in src/wanderwalk/manifolds/sphere.py
def sample_tangent_noise(self, x):
    """
    Generates a random Gaussian vector in R^3 and projects it onto
    the tangent space at point x, which is on the sphere.

    Arguments:
        x: A point on the sphere.

    Returns:
        A random Gaussian vector in R^3 which is on the tangent space
        at point x on the sphere.
    """
    rand_vect = np.random.randn(3)
    return self.project_to_tangent(x, rand_vect)

sample_tangent_noise_multiple

sample_tangent_noise_multiple(X)

Generates random Gaussian vectors for many points in R^3 on the sphere and projects each vector onto the tangent space of its respective point.

Parameters:

Name Type Description Default
X

A set of points on the sphere.

required

Returns:

Type Description

A set of random Gaussian tangent vectors in R^3.

Source code in src/wanderwalk/manifolds/sphere.py
def sample_tangent_noise_multiple(self, X):
    """
    Generates random Gaussian vectors for many points in R^3 on the sphere
    and projects each vector onto the tangent space of its respective point.

    Arguments:
        X: A set of points on the sphere.

    Returns:
        A set of random Gaussian tangent vectors in R^3.
    """
    N = X.shape[0] # Get number of points in X
    noise = np.random.randn(N, 3)
    tangent_noise = self.project_to_tangent_multiple(X, noise)
    return tangent_noise

sample_tangent_noise_anisotropic

sample_tangent_noise_anisotropic(x)

Chooses one tangent direction at point x, generates one Gaussian random number and scales that tangent direction by the random number. This generates Brownian noise in only one tangent direction at point x on the sphere.

Parameters:

Name Type Description Default
x

A point on the sphere.

required

Returns:

Type Description

A random Gaussian tangent vector in R^3 at point x whose motion is

constrained to only one tangent direction.

Source code in src/wanderwalk/manifolds/sphere.py
def sample_tangent_noise_anisotropic(self, x):
    """
    Chooses one tangent direction at point x, generates one Gaussian random
    number and scales that tangent direction by the random number. This
    generates Brownian noise in only one tangent direction at point x on the
    sphere.

    Arguments:
        x: A point on the sphere.

    Returns:
        A random Gaussian tangent vector in R^3 at point x whose motion is
        constrained to only one tangent direction.
    """
    random_vector = np.array([1.0, 1.0, 1.0])
    tangent_vector = self.project_to_tangent(x, random_vector)
    tangent_norm = np.linalg.norm(tangent_vector)
    if tangent_norm < 1e-8:
        # x is (nearly) parallel to random_vector, so its tangential
        # component vanishes. Fall back to a direction that is
        # orthogonal to random_vector, so it can never be degenerate
        # at the same point.
        random_vector = np.array([1.0, -1.0, 0.0])
        tangent_vector = self.project_to_tangent(x, random_vector)
        tangent_norm = np.linalg.norm(tangent_vector)
    unit_tangent_vector = tangent_vector / tangent_norm
    noise = np.random.randn() # Scalar noise
    unit_tangent_vector *= noise # Vector is constrained to only one direction (such as North/South)
    return unit_tangent_vector

sample_tangent_noise_anisotropic_multiple

sample_tangent_noise_anisotropic_multiple(X)

Generates anisotropic noise for many points on the sphere. Each point can only move in one fixed tangent direction, which is scaled by one scalar Gaussian random noise variable.

Parameters:

Name Type Description Default
X

A set of points on the sphere.

required

Returns:

Type Description

Random Gaussian tangent vectors in R^3.

Source code in src/wanderwalk/manifolds/sphere.py
def sample_tangent_noise_anisotropic_multiple(self, X):
    """
    Generates anisotropic noise for many points on the sphere. Each point
    can only move in one fixed tangent direction, which is scaled by one
    scalar Gaussian random noise variable.

    Arguments:
        X: A set of points on the sphere.

    Returns:
        Random Gaussian tangent vectors in R^3.
    """
    N = X.shape[0] # Get number of points in X
    vector = np.array([1.0, 1.0, 1.0]) # Chosen tangent vector for each point in X
    # Keep only the tangential component of every tangent vector respect to a point in X
    tangent_directions = self.project_to_tangent_multiple(X, np.tile(vector, (N, 1)))
    norms = np.linalg.norm(tangent_directions, axis=1, keepdims=True)
    # For any points (nearly) parallel to vector, the tangential component
    # vanishes; recompute those rows with a direction orthogonal to
    # vector, which can never be degenerate at the same points.
    degenerate = norms[:, 0] < 1e-8
    if np.any(degenerate):
        fallback_vector = np.array([1.0, -1.0, 0.0])
        fallback_directions = self.project_to_tangent_multiple(
            X[degenerate], np.tile(fallback_vector, (degenerate.sum(), 1))
        )
        tangent_directions[degenerate] = fallback_directions
        norms[degenerate] = np.linalg.norm(fallback_directions, axis=1, keepdims=True)
    unit_tangent_directions = tangent_directions / norms
    noise = np.random.randn(N, 1)
    anisotropic_noise = unit_tangent_directions * noise # Noise in only one direction (along the vector)
    return anisotropic_noise

euler_maruyama_step

euler_maruyama_step(x, dt)

Simulates one step of Brownian motion from point x to the next point on the sphere. Noise is first generated for point x and then scaled by the square root of the time step. Then the next point becomes the previous plus the scaled noise and must be projected onto the sphere.

Variance measures how spread out the random positions are. It grows linearly with time (t). Standard deviation is the square root of variance and represents the net displacement (the distance from the start). Hence, the distance the walker travels from the starting point increases with the square root of t.

The formula used to find the noise scaled is

Change in W_t = square root of change in t * Z, Z ~ N(0, 1)

where change in W_t is the total random change in the system for the respective time step, t is the time step and Z is the standard normal random noise.

Parameters:

Name Type Description Default
x

A point on the sphere.

required
dt

A time step.

required

Returns:

Type Description

The next point on the sphere.

Source code in src/wanderwalk/manifolds/sphere.py
def euler_maruyama_step(self, x, dt):
    """Simulates one step of Brownian motion from point x to the next
    point on the sphere. Noise is first generated for point x and
    then scaled by the square root of the time step. Then the next
    point becomes the previous plus the scaled noise and must be
    projected onto the sphere.

    Variance measures how spread out the random positions are. It grows
    linearly with time (t). Standard deviation is the square root of
    variance and represents the net displacement (the distance from the
    start). Hence, the distance the walker travels from the starting point
    increases with the square root of t.

    The formula used to find the noise scaled is:
        Change in W_t = square root of change in t * Z, Z ~ N(0, 1)

    where change in W_t is the total random change in the system for the respective
    time step, t is the time step and Z is the standard normal random
    noise.

    Arguments:
        x: A point on the sphere.
        dt: A time step.

    Returns:
        The next point on the sphere.
    """
    noise = self.sample_tangent_noise(x)
    noise_scaled = np.sqrt(dt) * noise
    x += noise_scaled
    return self.project_to_manifold(x)

Torus

Torus

Torus(R, r)

Bases: Manifold

The Torus class defines a geometric representation of a torus in R^3. It stores the torus parameters R and r, the major and minor radius, respectively.

The class also includes methods for parameterization (converting between torus coordinates (u, v) and Cartesian coordinates (x, y, z)), computing surface normals and tangent directions, projecting points in R^3 onto the torus surface, and simulating Brownian motion using the Euler-Maruyama method.

The Torus class ensures that all simulated points are constrained to the torus manifold for each step to an updated position on the torus. It also enables simulations to store the trajectory a point takes after many time steps.

Initializes the major and minor radii that define the torus' geometry.

Parameters:

Name Type Description Default
R

Distance from the center of the central hole to the center of the tube.

required
r

Radius of the tube.

required
Source code in src/wanderwalk/manifolds/torus.py
def __init__(self, R, r):
    """Initializes the major and minor radii that define the torus'
    geometry.

    Arguments:
        R: Distance from the center of the central hole to the center
            of the tube.
        r: Radius of the tube.
    """
    self.R = R
    self.r = r

parametrize

parametrize(u, v)

Converts parameters (u, v) on the torus to Cartesian (x, y, z) coordinates. Uses formulas

x(u, v) = (R + rcos(v))cos(u) y(u, v) = (R + rcos(v))sin(u) z(u, v) = rsin(v)

The toroidal angle (u) ranges from 0 to 2 pi and rotates around the main z-axis. The poloidal angle (v) ranges from 0 to 2 pi and rotates around the circular cross section of the tube.

Parameters:

Name Type Description Default
u

The toroidal angle of the torus.

required
v

The poloidal angle of the torus.

required

Returns:

Type Description

The Cartesian coordinates of a point on the torus.

Source code in src/wanderwalk/manifolds/torus.py
def parametrize(self, u, v):
    """Converts parameters (u, v) on the torus to Cartesian (x, y, z)
    coordinates. Uses formulas

    x(u, v) = (R + rcos(v))cos(u)
    y(u, v) = (R + rcos(v))sin(u)
    z(u, v) = rsin(v)

    The toroidal angle (u) ranges from 0 to 2 pi and rotates around the
    main z-axis. The poloidal angle (v) ranges from 0 to 2 pi and rotates
    around the circular cross section of the tube.

    Arguments:
        u: The toroidal angle of the torus.
        v: The poloidal angle of the torus.

    Returns:
        The Cartesian coordinates of a point on the torus.
    """
    x = (self.R + self.r * math.cos(v)) * math.cos(u)
    y = (self.R + self.r * math.cos(v)) * math.sin(u)
    z = self.r * math.sin(v)
    point = np.array([x, y, z])
    return point

normal_vector

normal_vector(u, v)

Computes the unit normal vector at a point on the torus. The normal vector points perpendicular to the torus (away from the tube at that location).

Uses the analytic formula for the torus normal, expressed as

N(u, v) = (cos(u)cos(v), sin(u)cos(v), sin(v))

The Cartesian components of the normal vector are

x = cos(u)cos(v), y = sin(u)cos(v), z = sin(v)

Parameters:

Name Type Description Default
u

The toroidal angle of the torus.

required
v

The poloidal angle of the torus.

required

Returns:

Type Description

The Cartesian coordinates of the unit normal vector at a

point on the torus.

Source code in src/wanderwalk/manifolds/torus.py
def normal_vector(self, u, v):
    """Computes the unit normal vector at a point on the torus.
    The normal vector points perpendicular to the torus (away from the
    tube at that location).

    Uses the analytic formula for the torus normal, expressed as

    N(u, v) = (cos(u)cos(v), sin(u)cos(v), sin(v))

    The Cartesian components of the normal vector are

    x = cos(u)cos(v),
    y = sin(u)cos(v),
    z = sin(v)

    Arguments:
        u: The toroidal angle of the torus.
        v: The poloidal angle of the torus.

    Returns:
        The Cartesian coordinates of the unit normal vector at a
        point on the torus.
    """
    x = math.cos(u) * math.cos(v)
    y = math.sin(u) * math.cos(v)
    z = math.sin(v)
    normal_vector = np.array([x, y, z])
    return normal_vector

angles_from_point

angles_from_point(x)

Recovers the angles (u, v) from a Cartesian point. Inverse of parametrize.

Parameters:

Name Type Description Default
x

A point on the torus in R^3.

required

Returns:

Type Description

A tuple (u, v) of the toroidal and poloidal angles of x.

Source code in src/wanderwalk/manifolds/torus.py
def angles_from_point(self, x):
    """Recovers the angles (u, v) from a Cartesian point. Inverse of
    parametrize.

    Arguments:
        x: A point on the torus in R^3.

    Returns:
        A tuple (u, v) of the toroidal and poloidal angles of x.
    """
    x_coor = x[0]
    y_coor = x[1]
    z_coor = x[2]
    u = math.atan2(y_coor, x_coor)
    rho = math.sqrt(x_coor**2 + y_coor**2)
    v = math.atan2(z_coor, rho - self.R)
    return u, v

angles_from_points

angles_from_points(X)

Vectorized version of angles_from_point for many points at once.

Parameters:

Name Type Description Default
X

An (N, 3) array of points on the torus.

required

Returns:

Type Description

A tuple (u, v) of (N,) arrays holding the toroidal and poloidal

angles of each point in X.

Source code in src/wanderwalk/manifolds/torus.py
def angles_from_points(self, X):
    """Vectorized version of angles_from_point for many points at once.

    Arguments:
        X: An (N, 3) array of points on the torus.

    Returns:
        A tuple (u, v) of (N,) arrays holding the toroidal and poloidal
        angles of each point in X.
    """
    x_coor = X[:, 0]
    y_coor = X[:, 1]
    z_coor = X[:, 2]
    u = np.arctan2(y_coor, x_coor)
    rho = np.sqrt(x_coor**2 + y_coor**2)
    v = np.arctan2(z_coor, rho - self.R)
    return u, v

project_to_tangent

project_to_tangent(x, v)

Projects a vector v onto the tangent plane at a point x on the torus.

Takes a Cartesian point, matching the signature shared by every manifold. Recovers the angles from x, then defers to project_to_tangent_at_angles.

Parameters:

Name Type Description Default
x

A point on the torus in R^3.

required
v

An arbitrary vector in R^3 at point x.

required

Returns:

Type Description

The component of v which lies in the tangent plane at x.

Source code in src/wanderwalk/manifolds/torus.py
def project_to_tangent(self, x, v):
    """Projects a vector v onto the tangent plane at a point x on the torus.

    Takes a Cartesian point, matching the signature shared by every
    manifold. Recovers the angles from x, then defers to
    project_to_tangent_at_angles.

    Arguments:
        x: A point on the torus in R^3.
        v: An arbitrary vector in R^3 at point x.

    Returns:
        The component of v which lies in the tangent plane at x.
    """
    u_angle, v_angle = self.angles_from_point(x)
    return self.project_to_tangent_at_angles(u_angle, v_angle, v)

project_to_tangent_multiple

project_to_tangent_multiple(X, V)

Vectorized version of project_to_tangent for many points at once.

Parameters:

Name Type Description Default
X

An (N, 3) array of points on the torus.

required
V

An (N, 3) array of vectors, one per point in X.

required

Returns:

Type Description

An (N, 3) array holding the tangential component of each vector

in V at its corresponding point in X.

Source code in src/wanderwalk/manifolds/torus.py
def project_to_tangent_multiple(self, X, V):
    """Vectorized version of project_to_tangent for many points at once.

    Arguments:
        X: An (N, 3) array of points on the torus.
        V: An (N, 3) array of vectors, one per point in X.

    Returns:
        An (N, 3) array holding the tangential component of each vector
        in V at its corresponding point in X.
    """
    u_angles, v_angles = self.angles_from_points(X)

    normals = np.stack([
        np.cos(u_angles) * np.cos(v_angles),
        np.sin(u_angles) * np.cos(v_angles),
        np.sin(v_angles)
    ], axis=1)

    normal_components = np.sum(V * normals, axis=1, keepdims=True)
    return V - normal_components * normals

project_to_tangent_at_angles

project_to_tangent_at_angles(u, v, vector)

Projects a vector onto the tangent plane of the torus. This is done by removing the normal component of the vector and only leaving the tangential component.

The dot product of the vector and the unit normal vector measures how much of the vector points in the normal direction (away from the torus). When multiplied by the unit normal vector, it is the normal component of the vector.

The formula used to find the tangential component of the vector is

tangential_vector = vector - (dot product of vector and N) * N

where N is the normal vector.

Parameters:

Name Type Description Default
u

The toroidal angle of the torus.

required
v

The poloidal angle of the torus.

required
vector

An arbitrary vector.

required

Returns:

Type Description

The tangential vector at (u, v).

Source code in src/wanderwalk/manifolds/torus.py
def project_to_tangent_at_angles(self, u, v, vector):
    """Projects a vector onto the tangent plane of the torus. This
    is done by removing the normal component of the vector and only
    leaving the tangential component.

    The dot product of the vector and the unit normal vector measures
    how much of the vector points in the normal direction (away from
    the torus). When multiplied by the unit normal vector, it is the
    normal component of the vector.

    The formula used to find the tangential component of the vector is

    tangential_vector = vector - (dot product of vector and N) * N

    where N is the normal vector.

    Arguments:
        u: The toroidal angle of the torus.
        v: The poloidal angle of the torus.
        vector: An arbitrary vector.

    Returns:
        The tangential vector at (u, v).
    """
    normal = self.normal_vector(u, v)
    tangential_vector = vector - (np.dot(vector, normal)) * normal
    return tangential_vector

project_to_manifold_multiple

project_to_manifold_multiple(X)

Vectorized version of project_to_manifold for many points at once.

Parameters:

Name Type Description Default
X

An (N, 3) array of points in R^3 that may or may not lie on the torus.

required

Returns:

Type Description

An (N, 3) array containing the nearest point on the torus for

each input point.

Source code in src/wanderwalk/manifolds/torus.py
def project_to_manifold_multiple(self, X):
    """Vectorized version of project_to_manifold for many points at once.

    Arguments:
        X: An (N, 3) array of points in R^3 that may or may not lie on
            the torus.

    Returns:
        An (N, 3) array containing the nearest point on the torus for
        each input point.
    """
    x_coor = X[:, 0]
    y_coor = X[:, 1]

    distance_from_z = np.sqrt(x_coor**2 + y_coor**2)
    center_x = self.R * (x_coor / distance_from_z)
    center_y = self.R * (y_coor / distance_from_z)
    center = np.stack([center_x, center_y, np.zeros_like(center_x)], axis=1)

    offset = X - center
    offset_norm = np.linalg.norm(offset, axis=1, keepdims=True)
    unit_offset = offset / offset_norm

    return center + self.r * unit_offset

sample_tangent_noise_multiple

sample_tangent_noise_multiple(X)

Vectorized version of sample_tangent_noise for many points at once.

Parameters:

Name Type Description Default
X

An (N, 3) array of points on the torus.

required

Returns:

Type Description

An (N, 3) array of random tangent vectors, one per input point.

Source code in src/wanderwalk/manifolds/torus.py
def sample_tangent_noise_multiple(self, X):
    """Vectorized version of sample_tangent_noise for many points at once.

    Arguments:
        X: An (N, 3) array of points on the torus.

    Returns:
        An (N, 3) array of random tangent vectors, one per input point.
    """
    u, v = self.angles_from_points(X)

    X_u = np.stack([
        -(self.R + self.r * np.cos(v)) * np.sin(u),
        (self.R + self.r * np.cos(v)) * np.cos(u),
        np.zeros_like(u)
    ], axis=1)

    X_v = np.stack([
        -self.r * np.sin(v) * np.cos(u),
        -self.r * np.sin(v) * np.sin(u),
        self.r * np.cos(v)
    ], axis=1)

    e_u = X_u / np.linalg.norm(X_u, axis=1, keepdims=True)
    e_v = X_v / np.linalg.norm(X_v, axis=1, keepdims=True)

    N = X.shape[0]
    a = np.random.randn(N, 1)
    b = np.random.randn(N, 1)

    return a * e_u + b * e_v

sample_tangent_noise

sample_tangent_noise(x)

Generates a random Gaussian vector in R^3 and projects it onto the tangent space at point x, which is on the torus.

The Cartesian point x is first converted into its corresponding parameters (u, v) using the torus' geometry.

Then, the derivative of X with respect to u and the derivate of X with respect to v are calculated. Both of them are normalized. These are the tangent drections (perpendicular to each other) on the surface.

Two Gaussian random vectors are generated, multiplied to each tangent direction, and then summed to produce a single vector. This is the random step the point takes on the torus.

Parameters:

Name Type Description Default
x

A point on the torus.

required

Returns:

Type Description

A random vector in R^3 which is on the tangent space at point

x on the torus.

Source code in src/wanderwalk/manifolds/torus.py
def sample_tangent_noise(self, x):
    """
    Generates a random Gaussian vector in R^3 and projects it onto
    the tangent space at point x, which is on the torus.

    The Cartesian point x is first converted into its corresponding
    parameters (u, v) using the torus' geometry.

    Then, the derivative of X with respect to u and the derivate of X
    with respect to v are calculated. Both of them are normalized.
    These are the tangent drections (perpendicular to each other) on
    the surface.

    Two Gaussian random vectors are generated, multiplied to each
    tangent direction, and then summed to produce a single vector.
    This is the random step the point takes on the torus.

    Arguments:
        x: A point on the torus.

    Returns:
        A random vector in R^3 which is on the tangent space at point
        x on the torus.
    """
    u, v = self.angles_from_point(x)

    X_u = np.array([
        -(self.R + self.r * np.cos(v)) * np.sin(u),
        (self.R + self.r * np.cos(v)) * np.cos(u),
        0
    ])

    X_v = np.array([
        -self.r * np.sin(v) * np.cos(u),
        -self.r * np.sin(v) * np.sin(u),
        self.r * np.cos(v)
    ])

    e_u = X_u / np.linalg.norm(X_u)
    e_v = X_v / np.linalg.norm(X_v)

    a = np.random.randn()
    b = np.random.randn()

    return a * e_u + b * e_v

sample_tangent_noise_anisotropic

sample_tangent_noise_anisotropic(x)

Generated anistropic noise in one tangent direction at point x on the torus.

A torus has two orthogonal tangent direction: - The direction around the large circle of the torus (R) - The direction around the smaller circular cross-sections of the torus (r)

The toroidal direction is the motion around the large circle, while the poloidal direction is the motion around the cross-section.

A Gaussian random vector is first generated and then multiplied by a scalar value (the noise). This is the random step the point takes on the torus, constrained to one tangent direction.

Parameters:

Name Type Description Default
x

A point on the torus.

required

Returns:

Type Description

A random vector in R^3 constrained to one tangent direction

and at point x on the torus.

Source code in src/wanderwalk/manifolds/torus.py
def sample_tangent_noise_anisotropic(self, x):
    """
    Generated anistropic noise in one tangent direction at point
    x on the torus.

    A torus has two orthogonal tangent direction:
    - The direction around the large circle of the torus (R)
    - The direction around the smaller circular cross-sections of the
    torus (r)

    The toroidal direction is the motion around the large circle, while
    the poloidal direction is the motion around the cross-section.

    A Gaussian random vector is first generated and then multiplied
    by a scalar value (the noise). This is the random step the point
    takes on the torus, constrained to one tangent direction.

    Arguments:
        x: A point on the torus.

    Returns:
        A random vector in R^3 constrained to one tangent direction
        and at point x on the torus.
    """
    x_coor = x[0]
    y_coor = x[1]
    z_coor = x[2]
    u = math.atan2(y_coor, x_coor)
    rho = math.sqrt(x_coor**2 + y_coor**2)
    v = math.atan2(z_coor, rho - self.R)

    X_u = np.array([
        -(self.R + self.r * math.cos(v)) * math.sin(u),
        (self.R + self.r * math.cos(v)) * math.cos(u),
        0.0
    ])

    e_u = X_u / np.linalg.norm(X_u)

    scalar_noise = np.random.randn()

    return scalar_noise * e_u

sample_tangent_noise_anisotropic_multiple

sample_tangent_noise_anisotropic_multiple(X)

Generates anistropic Gaussian noise for many points on the torus.

For each point, a scalar Gaussian variable (the noise) is multiplied by one tangent direction (e_u). Here, this is the direction around the large circle of the torus (R).

Parameters:

Name Type Description Default
X

An (N, 3) array of points on the torus.

required

Returns:

Type Description

An (N, 3) array of random Gaussian tangent vectors.

Source code in src/wanderwalk/manifolds/torus.py
def sample_tangent_noise_anisotropic_multiple(self, X):
    """
    Generates anistropic Gaussian noise for many points on
    the torus.

    For each point, a scalar Gaussian variable (the noise) is
    multiplied by one tangent direction (e_u). Here, this is the
    direction around the large circle of the torus (R).

    Arguments:
        X: An (N, 3) array of points on the torus.

    Returns:
        An (N, 3) array of random Gaussian tangent vectors.
    """
    x_coor = X[:, 0]
    y_coor = X[:, 1]
    z_coor = X[:, 2]

    u = np.arctan2(y_coor, x_coor)
    rho = np.sqrt(x_coor**2 + y_coor**2)
    v = np.arctan2(z_coor, rho - self.R)

    X_u = np.stack([
        -(self.R + self.r * np.cos(v)) * np.sin(u),
        (self.R + self.r * np.cos(v)) * np.cos(u),
        np.zeros_like(u)
    ], axis=1)

    e_u = X_u / np.linalg.norm(X_u, axis=1, keepdims=True)

    N = X.shape[0]
    scalar_noise = np.random.randn(N, 1)

    return scalar_noise * e_u

project_to_manifold

project_to_manifold(x)

Projects a point in R^3 onto the torus. The projection is computed analytically rather than numerically.

A torus consists of a large circle (radius R) and small circles (radius r) on every cross-section of the large circle.

First, the nearest point on the major circle of radius R in the xy-plane is computed. This point is used as the center of the nearest tube cross-section of the torus. The offset is then computed from the tube center to the input point. It is normalized and scaled to the length of the tube's radius, which is r. The endpoint of the scaled offset vector is the nearest point on the torus, which is returned.

Parameters:

Name Type Description Default
x

A point in R^3 that may or may not be on the torus.

required

Returns:

Type Description

The nearest point on the torus from the input point.

Source code in src/wanderwalk/manifolds/torus.py
def project_to_manifold(self, x):
    """Projects a point in R^3 onto the torus. The projection is computed
    analytically rather than numerically.

    A torus consists of a large circle (radius R) and small circles (radius
    r) on every cross-section of the large circle.

    First, the nearest point on the major circle of radius R in the xy-plane
    is computed. This point is used as the center of the nearest tube
    cross-section of the torus. The offset is then computed from the tube center
    to the input point. It is normalized and scaled to the length of the tube's
    radius, which is r. The endpoint of the scaled offset vector is the nearest
    point on the torus, which is returned.

    Arguments:
        x: A point in R^3 that may or may not be on the torus.

    Returns:
        The nearest point on the torus from the input point.
    """
    x_coor = x[0]
    y_coor = x[1]

    distance_from_z = math.sqrt(x_coor**2 + y_coor**2)
    center_x = self.R * (x_coor / distance_from_z)
    center_y = self.R * (y_coor / distance_from_z)
    center = np.array([center_x, center_y, 0])

    offset = x - center
    offset_norm = np.linalg.norm(offset)
    unit_offset = offset / offset_norm

    point = center + self.r * unit_offset
    return point

euler_maruyama_step

euler_maruyama_step(x, dt)

Simulates one step of Brownian motion from point x to the next point on the torus. Noise is first generated for point x and then scaled by the square root of the time step. Then the next point becomes the previous plus the scaled noise and must be projected onto the torus.

Variance measures how spread out the random positions are. It grows linearly with time (t). Standard deviation is the square root of variance and represents the net displacement (the distance from the start). Hence, the distance the walker travels from the starting point increases with the square root of t.

The formula used to find the noise scaled is

Change in W_t = square root of change in t * Z, Z ~ N(0, 1)

where change in W_t is the total random change in the system for the respective time step, t is the time step and Z is the standard normal random noise.

Parameters:

Name Type Description Default
x

A point on the torus.

required
dt

A time step.

required

Returns:

Type Description

The next point on the torus.

Source code in src/wanderwalk/manifolds/torus.py
def euler_maruyama_step(self, x, dt):
    """Simulates one step of Brownian motion from point x to the next
    point on the torus. Noise is first generated for point x and
    then scaled by the square root of the time step. Then the next
    point becomes the previous plus the scaled noise and must be
    projected onto the torus.

    Variance measures how spread out the random positions are. It grows
    linearly with time (t). Standard deviation is the square root of
    variance and represents the net displacement (the distance from the
    start). Hence, the distance the walker travels from the starting point
    increases with the square root of t.

    The formula used to find the noise scaled is:
        Change in W_t = square root of change in t * Z, Z ~ N(0, 1)

    where change in W_t is the total random change in the system for the respective
    time step, t is the time step and Z is the standard normal random
    noise.

    Arguments:
        x: A point on the torus.
        dt: A time step.

    Returns:
        The next point on the torus.
    """
    noise = self.sample_tangent_noise(x)
    noise_scaled = np.sqrt(dt) * noise
    x_updated = x + noise_scaled
    return self.project_to_manifold(x_updated)

PoincareDisk

PoincareDisk

PoincareDisk(epsilon=1e-10)

Bases: Manifold

The PoincareDisk class represents the hyperbolic plane H^2 using the Poincare disk model: the open unit disk {(x, y) : x^2 + y^2 < 1} in R^2, equipped with the conformal metric g_ij = lambda(x,y)^2 * delta_ij where lambda(x,y) = 2 / (1 - x^2 - y^2).

Unlike Sphere and Torus, H^2 has no isometric embedding into R^3 (Hilbert's theorem), so points here are 2D vectors.

Every formula in this class is derived step by step in docs/writeups/2-poincare-disk-derivation.md, starting from this project's own stated convention that Brownian motion's generator is half the Laplace-Beltrami operator. In particular:

  • The governing Ito SDE for a point X_t in the disk is driftless: dX_t = lambda(X_t)^{-1} dW_t = ((1 - |X_t|^2) / 2) dW_t

  • The radial process rho_t = 2artanh(|X_t|) (the geodesic distance from the origin) satisfies dRho_t = dBeta_t + (1/2)coth(rho_t) dt. This closed-form target is the primary check for this manifold since H^2 has no stationary distribution to compare against.

Initializes the Poincare disk.

Parameters:

Name Type Description Default
epsilon

How far inside the unit circle a point is clamped to if a numerical step pushes it to or past the boundary.

1e-10
Source code in src/wanderwalk/manifolds/hyperbolic.py
def __init__(self, epsilon=1e-10):
    """Initializes the Poincare disk.

    Arguments:
        epsilon: How far inside the unit circle a point is clamped to
            if a numerical step pushes it to or past the boundary.
    """
    self.epsilon = epsilon

conformal_factor

conformal_factor(x)

Computes the conformal factor lambda(x) = 2 / (1 - |x|^2) at a point x in the disk. This is the scalar by which the Euclidean metric is multiplied to get the hyperbolic metric at x.

Parameters:

Name Type Description Default
x

A point in the open unit disk.

required

Returns:

Type Description

The conformal factor lambda(x) at point x.

Source code in src/wanderwalk/manifolds/hyperbolic.py
def conformal_factor(self, x):
    """Computes the conformal factor lambda(x) = 2 / (1 - |x|^2) at a
    point x in the disk. This is the scalar by which the Euclidean
    metric is multiplied to get the hyperbolic metric at x.

    Arguments:
        x: A point in the open unit disk.

    Returns:
        The conformal factor lambda(x) at point x.
    """
    norm_sq = np.dot(x, x)
    return 2.0 / (1.0 - norm_sq)

project_to_tangent

project_to_tangent(x, v)

Returns v unchanged.

Unlike Sphere and Torus, which are embedded in R^3 and therefore need to remove the ambient normal component of a vector to obtain a tangent vector, H^2 in the Poincare disk model is intrinsically 2-dimensional: the tangent space at every interior point x is all of R^2, since there is no ambient subspace to restrict to.

Note this method only ensures v lies in the correct 2D subspace (trivially true here), it does not account for the fact that the tangent space's inner product is non-Euclidean.

Parameters:

Name Type Description Default
x

A point in the disk.

required
v

A vector in R^2.

required

Returns:

Type Description

v, unchanged.

Source code in src/wanderwalk/manifolds/hyperbolic.py
def project_to_tangent(self, x, v):
    """Returns v unchanged.

    Unlike Sphere and Torus, which are embedded in R^3 and therefore
    need to remove the ambient normal component of a vector to obtain
    a tangent vector, H^2 in the Poincare disk model is intrinsically
    2-dimensional: the tangent space at every interior point x is all
    of R^2, since there is no ambient subspace to restrict to.

    Note this method only ensures v lies in the correct 2D subspace
    (trivially true here), it does not account for the fact that the
    tangent space's inner product is non-Euclidean.

    Arguments:
        x: A point in the disk.
        v: A vector in R^2.

    Returns:
        v, unchanged.
    """
    return v

project_to_tangent_multiple

project_to_tangent_multiple(X, V)

Vectorized version of project_to_tangent for many points/vectors at once. Returns V unchanged, for every tangent space here is all of R^2.

Parameters:

Name Type Description Default
X

A set of points in the disk.

required
V

A set of vectors in R^2, one per point in X.

required

Returns:

Type Description

V, unchanged.

Source code in src/wanderwalk/manifolds/hyperbolic.py
def project_to_tangent_multiple(self, X, V):
    """Vectorized version of project_to_tangent for many points/vectors
    at once. Returns V unchanged, for every tangent space here is all of R^2.

    Arguments:
        X: A set of points in the disk.
        V: A set of vectors in R^2, one per point in X.

    Returns:
        V, unchanged.
    """
    return V

sample_tangent_noise

sample_tangent_noise(x)

Generates the tangent noise at point x for the Euler-Maruyama step, per the Ito SDE derived in docs/writeups/2-poincare-disk-derivation.md section 3:

sample_tangent_noise(x) = ((1 - |x|^2) / 2) * Z,  Z ~ N(0, I_2)

Since project_to_tangent is the identity here, all of the manifold-specific work for turning flat Gaussian noise into the correct tangent noise happens in this scaling factor, which is exactly lambda(x)^{-1}, the inverse conformal factor.

Parameters:

Name Type Description Default
x

A point in the disk.

required

Returns:

Type Description

A random vector in R^2, scaled for the hyperbolic metric at x.

Source code in src/wanderwalk/manifolds/hyperbolic.py
def sample_tangent_noise(self, x):
    """Generates the tangent noise at point x for the
    Euler-Maruyama step, per the Ito SDE derived in
    docs/writeups/2-poincare-disk-derivation.md section 3:

        sample_tangent_noise(x) = ((1 - |x|^2) / 2) * Z,  Z ~ N(0, I_2)

    Since project_to_tangent is the identity here, all
    of the manifold-specific work for turning flat Gaussian noise into
    the correct tangent noise happens in this scaling factor, which is
    exactly lambda(x)^{-1}, the inverse conformal factor.

    Arguments:
        x: A point in the disk.

    Returns:
        A random vector in R^2, scaled for the hyperbolic metric at x.
    """
    z = np.random.randn(2)
    return z / self.conformal_factor(x)

sample_tangent_noise_multiple

sample_tangent_noise_multiple(X)

Vectorized version of sample_tangent_noise for many points at once.

Parameters:

Name Type Description Default
X

An (N, 2) array of points in the disk.

required

Returns:

Type Description

An (N, 2) array of random tangent vectors, one per input point.

Source code in src/wanderwalk/manifolds/hyperbolic.py
def sample_tangent_noise_multiple(self, X):
    """Vectorized version of sample_tangent_noise for many points at
    once.

    Arguments:
        X: An (N, 2) array of points in the disk.

    Returns:
        An (N, 2) array of random tangent vectors, one per input point.
    """
    N = X.shape[0]
    Z = np.random.randn(N, 2)
    norms_sq = np.sum(X * X, axis=1, keepdims=True)
    conformal_factors = 2.0 / (1.0 - norms_sq)
    return Z / conformal_factors

project_to_manifold

project_to_manifold(x)

Clamps a point x back inside the open unit disk if a numerical step has pushed it to or past the boundary.

The true continuous-time process on H^2 never reaches the boundary |x| = 1 in finite time (see docs/writeups/2-poincare-disk-derivation.md section 6). It exists to prevent floating-point arithmetic from producing an undefined or negative (1 - |x|^2), which the conformal factor and the noise scaling in sample_tangent_noise both depend on.

Parameters:

Name Type Description Default
x

A point in R^2 that may lie at or beyond the unit circle due to numerical error.

required

Returns:

Type Description

x, unchanged if |x| < 1 - epsilon; otherwise x rescaled

radially to have norm exactly 1 - epsilon.

Source code in src/wanderwalk/manifolds/hyperbolic.py
def project_to_manifold(self, x):
    """Clamps a point x back inside the open unit disk if a numerical
    step has pushed it to or past the boundary.

    The true continuous-time process on H^2 never reaches the
    boundary |x| = 1 in finite time (see
    docs/writeups/2-poincare-disk-derivation.md section 6). It exists
    to prevent floating-point arithmetic from producing an
    undefined or negative (1 - |x|^2), which the conformal factor and
    the noise scaling in sample_tangent_noise both depend on.

    Arguments:
        x: A point in R^2 that may lie at or beyond the unit circle
            due to numerical error.

    Returns:
        x, unchanged if |x| < 1 - epsilon; otherwise x rescaled
        radially to have norm exactly 1 - epsilon.
    """
    norm = np.linalg.norm(x)
    if norm >= 1.0 - self.epsilon:
        return x * ((1.0 - self.epsilon) / norm)
    return x

project_to_manifold_multiple

project_to_manifold_multiple(X)

Vectorized version of project_to_manifold for many points at once.

Parameters:

Name Type Description Default
X

An (N, 2) array of points in R^2 that may lie at or beyond the unit circle due to numerical error.

required

Returns:

Type Description

An (N, 2) array with any offending points rescaled radially to

have norm exactly 1 - epsilon.

Source code in src/wanderwalk/manifolds/hyperbolic.py
def project_to_manifold_multiple(self, X):
    """Vectorized version of project_to_manifold for many points at
    once.

    Arguments:
        X: An (N, 2) array of points in R^2 that may lie at or beyond
            the unit circle due to numerical error.

    Returns:
        An (N, 2) array with any offending points rescaled radially to
        have norm exactly 1 - epsilon.
    """
    norms = np.linalg.norm(X, axis=1, keepdims=True)
    clamped = np.where(
        norms >= 1.0 - self.epsilon,
        X * ((1.0 - self.epsilon) / norms),
        X,
    )
    return clamped

euler_maruyama_step

euler_maruyama_step(x, dt)

Simulates one step of Brownian motion from point x to the next point in the disk. Noise is first generated for point x (already scaled for the hyperbolic metric, see sample_tangent_noise) and then scaled by the square root of the time step. Then the next point becomes the previous plus the scaled noise, clamped back inside the disk if numerical error pushed it to or past the boundary.

Parameters:

Name Type Description Default
x

A point in the disk.

required
dt

A time step.

required

Returns:

Type Description

The next point in the disk.

Source code in src/wanderwalk/manifolds/hyperbolic.py
def euler_maruyama_step(self, x, dt):
    """Simulates one step of Brownian motion from point x to the next
    point in the disk. Noise is first generated for point x (already
    scaled for the hyperbolic metric, see sample_tangent_noise) and
    then scaled by the square root of the time step. Then the next
    point becomes the previous plus the scaled noise, clamped back
    inside the disk if numerical error pushed it to or past the
    boundary.

    Arguments:
        x: A point in the disk.
        dt: A time step.

    Returns:
        The next point in the disk.
    """
    noise = self.sample_tangent_noise(x)
    noise_scaled = np.sqrt(dt) * noise
    x_updated = x + noise_scaled
    return self.project_to_manifold(x_updated)

geodesic_distance_from_origin

geodesic_distance_from_origin(x)

Computes the hyperbolic (geodesic) distance from the origin to point x, using the standard Poincare-disk radial distance formula:

rho(x) = 2 * artanh(|x|) = ln((1 + |x|) / (1 - |x|))

This is the arc length of the straight-line radius from the origin to x, measured with the hyperbolic metric (see docs/writeups/2-poincare-disk-derivation.md section 4).

Parameters:

Name Type Description Default
x

A point in the disk.

required

Returns:

Type Description

The geodesic distance from the origin to x.

Source code in src/wanderwalk/manifolds/hyperbolic.py
def geodesic_distance_from_origin(self, x):
    """Computes the hyperbolic (geodesic) distance from the origin to
    point x, using the standard Poincare-disk radial distance formula:

        rho(x) = 2 * artanh(|x|) = ln((1 + |x|) / (1 - |x|))

    This is the arc length of the straight-line radius from the origin
    to x, measured with the hyperbolic metric (see
    docs/writeups/2-poincare-disk-derivation.md section 4).

    Arguments:
        x: A point in the disk.

    Returns:
        The geodesic distance from the origin to x.
    """
    r = np.linalg.norm(x)
    return np.log((1.0 + r) / (1.0 - r))

geodesic_distance_from_origin_multiple

geodesic_distance_from_origin_multiple(X)

Vectorized version of geodesic_distance_from_origin for many points at once.

Parameters:

Name Type Description Default
X

An (N, 2) array of points in the disk.

required

Returns:

Type Description

An (N,) array of geodesic distances from the origin.

Source code in src/wanderwalk/manifolds/hyperbolic.py
def geodesic_distance_from_origin_multiple(self, X):
    """Vectorized version of geodesic_distance_from_origin for many
    points at once.

    Arguments:
        X: An (N, 2) array of points in the disk.

    Returns:
        An (N,) array of geodesic distances from the origin.
    """
    r = np.linalg.norm(X, axis=1)
    return np.log((1.0 + r) / (1.0 - r))

geodesic_distance

geodesic_distance(z, w)

Computes the hyperbolic (geodesic) distance between two points z and w in the disk, using the standard closed-form Poincare-disk distance formula:

d(z, w) = arccosh(1 + 2|z - w|^2 / ((1 - |z|^2)(1 - |w|^2)))

Parameters:

Name Type Description Default
z

A point in the disk.

required
w

A point in the disk.

required

Returns:

Type Description

The geodesic distance between z and w.

Source code in src/wanderwalk/manifolds/hyperbolic.py
def geodesic_distance(self, z, w):
    """Computes the hyperbolic (geodesic) distance between two points z
    and w in the disk, using the standard closed-form Poincare-disk
    distance formula:

        d(z, w) = arccosh(1 + 2|z - w|^2 / ((1 - |z|^2)(1 - |w|^2)))

    Arguments:
        z: A point in the disk.
        w: A point in the disk.

    Returns:
        The geodesic distance between z and w.
    """
    diff_norm_sq = np.dot(z - w, z - w)
    z_norm_sq = np.dot(z, z)
    w_norm_sq = np.dot(w, w)
    argument = 1.0 + 2.0 * diff_norm_sq / ((1.0 - z_norm_sq) * (1.0 - w_norm_sq))
    return np.arccosh(argument)

geodesic_distance_multiple

geodesic_distance_multiple(Z, w)

Vectorized version of geodesic_distance: computes the distance from every point in Z to a single point w.

Parameters:

Name Type Description Default
Z

An (N, 2) array of points in the disk.

required
w

A single point in the disk.

required

Returns:

Type Description

An (N,) array of geodesic distances from each point in Z to w.

Source code in src/wanderwalk/manifolds/hyperbolic.py
def geodesic_distance_multiple(self, Z, w):
    """Vectorized version of geodesic_distance: computes the distance
    from every point in Z to a single point w.

    Arguments:
        Z: An (N, 2) array of points in the disk.
        w: A single point in the disk.

    Returns:
        An (N,) array of geodesic distances from each point in Z to w.
    """
    diff = Z - w
    diff_norm_sq = np.sum(diff * diff, axis=1)
    z_norm_sq = np.sum(Z * Z, axis=1)
    w_norm_sq = np.dot(w, w)
    argument = 1.0 + 2.0 * diff_norm_sq / ((1.0 - z_norm_sq) * (1.0 - w_norm_sq))
    return np.arccosh(argument)