web-dev-qa-db-fra.com

Comment distribuer des points uniformément à la surface des hypersphères dans des dimensions supérieures?

Je suis intéressé à distribuer uniformément n points sur la surface des sphères dans les dimensions 3 et supérieures.

Pour être plus précis:

  • Donné un certain nombre de points N et nombre de dimensions D (où D> 1, N> 1)
  • La distance de chaque point à l'origine doit être 1
  • La distance minimale entre deux points doit être aussi grande que possible
  • La distance de chaque point à son voisin le plus proche ne doit pas nécessairement être la même pour chaque point (en effet, il n'est pas possible que ce soit la même chose à moins que le nombre de points ne forme les sommets d'un solide platonique ou si N <= D ).

Je ne suis pas intéressé par:

  • Création d'une distribution aléatoire uniforme sur une hypersphère, car je souhaite que la distance minimale entre deux points soit aussi importante que possible au lieu d'être distribuée au hasard.
  • Les méthodes de type de simulation de la repulsion des particules, car elles sont difficiles à mettre en œuvre et prennent une période extrêmement longue pour fonctionner pour la grande n (idéalement, la méthode doit être déterministe et dans O (n)).

Une méthode qui satisfait que ces critères s'appelle le réseau Fibonacci, mais je n'ai pu trouver que des implémentations de code pour cela en 2D et 3D.

La méthode derrière le réseau Fibonacci (également appelée la spirale Fibonacci) consiste à générer une ligne 1D que les spirales autour de la surface de la sphère de sorte que la surface couverte par la ligne soit à peu près la même à chaque tour. Vous pouvez ensuite déposer n points également répartis sur la spirale et ils seront à peu près répartis de manière uniforme à la surface de la sphère.

Dans cette réponse Il y a une implémentation python pour 3 dimensions qui génère ce qui suit:

enter image description here

Je voulais savoir si la spirale de Fibonacci pourrait être étendue aux dimensions supérieures à 3 et a posté une question sur l'échange de pile de mathématiques. À ma grande surprise, j'ai reçu deux réponses étonnantes qui aussi loin que je peux dire (parce que je ne comprends pas complètement les mathématiques montrées) montre qu'il est en effet possible d'étendre cette méthode à n dimensions.

Malheureusement, je ne comprends pas assez des mathématiques montrés pour pouvoir transformer soit la réponse à (pseudo). Je suis un programmeur informatique expérimenté, mais mon fond de mathématiques ne va que jusqu'à présent.

Je vais copier dans ce que je crois être la partie la plus importante de l'une des réponses ci-dessous (malheureusement SO ne prend pas en charge Mathjax, donc je devais copier comme une image)

enter image description here

Difficultés présentées par ce qui précède que je lutte avec:

  • Comment résoudre la fonction inverse utilisée pour ψn?
  • L'exemple donné est pour d = 3. Comment générer des formules pour DH?

Quiconque comprendrait-il que les mathématiques impliquées puissent progresser vers une implémentation pseudo-code de la seule réponse à la question liée au réseau de fibonacci? Je comprends une mise en œuvre complète peut être assez difficile, donc je serais heureux d'une mise en œuvre de la partie qui me conduit assez loin pour pouvoir compléter le reste moi-même.

Pour faciliter la tâche, j'ai déjà codé une fonction qui prend des coordonnées sphériques en n dimensions et les transforme en coordonnées cartésiennes, la mise en œuvre peut donc générer l'un ou l'autre comme je peux facilement convertir.

De plus, je vois qu'une réponse utilise le prochain numéro principal pour chaque dimension supplémentaire. Je peux facilement coder une fonction qui génère chaque prime successive, vous pouvez donc supposer que cela est déjà implémenté.

À défaut d'une mise en œuvre du réseau Fibonacci dans N Dimensions, je serais heureux d'accepter une méthode différente qui répond aux contraintes ci-dessus.

21
Karl

Toute la réponse précédente a fonctionné, mais manquait toujours de code réel. Il manquait deux vrais morceaux, ce qui implémente en général.

  1. Nous devons calculer l'intégrale de sin^(d-2)(x). Cela a une forme fermée si vous intégrez une intégration récursive par des pièces. Ici, je le met en œuvre à la mode récursive, bien que pour la dimension ~> 100 J'ai trouvé une intégration numérique de sin^d être plus rapide
  2. Nous devons calculer la fonction inverse de cette intégrale, qui pour sin^d, d > 1 n'a pas de forme de fermeture. Ici, je le calculer à l'aide de la recherche binaire, même s'il y a probablement de meilleures façons, comme indiqué dans d'autres réponses.

Ces deux combinés avec un moyen de générer des premiers résultats dans l'algorithme complet:

from itertools import count, islice
from math import cos, gamma, pi, sin, sqrt
from typing import Callable, Iterator, List

def int_sin_m(x: float, m: int) -> float:
    """Computes the integral of sin^m(t) dt from 0 to x recursively"""
    if m == 0:
        return x
    Elif m == 1:
        return 1 - cos(x)
    else:
        return (m - 1) / m * int_sin_m(x, m - 2) - cos(x) * sin(x) ** (
            m - 1
        ) / m

def primes() -> Iterator[int]:
    """Returns an infinite generator of prime numbers"""
    yield from (2, 3, 5, 7)
    composites = {}
    ps = primes()
    next(ps)
    p = next(ps)
    assert p == 3
    psq = p * p
    for i in count(9, 2):
        if i in composites:  # composite
            step = composites.pop(i)
        Elif i < psq:  # prime
            yield i
            continue
        else:  # composite, = p*p
            assert i == psq
            step = 2 * p
            p = next(ps)
            psq = p * p
        i += step
        while i in composites:
            i += step
        composites[i] = step

def inverse_increasing(
    func: Callable[[float], float],
    target: float,
    lower: float,
    upper: float,
    atol: float = 1e-10,
) -> float:
    """Returns func inverse of target between lower and upper

    inverse is accurate to an absolute tolerance of atol, and
    must be monotonically increasing over the interval lower
    to upper    
    """
    mid = (lower + upper) / 2
    approx = func(mid)
    while abs(approx - target) > atol:
        if approx > target:
            upper = mid
        else:
            lower = mid
        mid = (upper + lower) / 2
        approx = func(mid)
    return mid

def uniform_hypersphere(d: int, n: int) -> List[List[float]]:
    """Generate n points over the d dimensional hypersphere"""
    assert d > 1
    assert n > 0
    points = [[1 for _ in range(d)] for _ in range(n)]
    for i in range(n):
        t = 2 * pi * i / n
        points[i][0] *= sin(t)
        points[i][1] *= cos(t)
    for dim, prime in Zip(range(2, d), primes()):
        offset = sqrt(prime)
        mult = gamma(dim / 2 + 0.5) / gamma(dim / 2) / sqrt(pi)

        def dim_func(y):
            return mult * int_sin_m(y, dim - 1)

        for i in range(n):
            deg = inverse_increasing(dim_func, i * offset % 1, 0, pi)
            for j in range(dim):
                points[i][j] *= sin(deg)
            points[i][dim] *= cos(deg)
    return points

Qui produit l'image suivante pour 200 points sur une sphère:

sphere distribution

1
Erik