web-dev-qa-db-fra.com

Échantillonnage de points aléatoires uniformément répartis à l'intérieur d'un volume sphérique

Je cherche à pouvoir générer un échantillon aléatoire aléatoire d'emplacements de particules qui tombent dans un volume sphérique.

L'image ci-dessous (gracieuseté de http://nojhan.free.fr/metah/ ) montre ce que je recherche. Ceci est une tranche à travers la sphère, montrant une distribution uniforme des points:

Uniformly distributed circle

Voici ce que j'obtiens actuellement:

Uniformly Distributed but Cluster Of Points

Vous pouvez voir qu'il y a un groupe de points au centre en raison de la conversion entre les coordonnées sphériques et cartésiennes.

Le code que j'utilise est:

def new_positions_spherical_coordinates(self):
   radius = numpy.random.uniform(0.0,1.0, (self.number_of_particles,1)) 
   theta = numpy.random.uniform(0.,1.,(self.number_of_particles,1))*pi
   phi = numpy.arccos(1-2*numpy.random.uniform(0.0,1.,(self.number_of_particles,1)))
   x = radius * numpy.sin( theta ) * numpy.cos( phi )
   y = radius * numpy.sin( theta ) * numpy.sin( phi )
   z = radius * numpy.cos( theta )
   return (x,y,z)

Ci-dessous se trouve du code MATLAB qui crée soi-disant un échantillon sphérique uniforme, similaire à l'équation donnée par http://nojhan.free.fr/metah . Je n'arrive pas à déchiffrer ou à comprendre ce qu'ils ont fait.

function X = randsphere(m,n,r)

% This function returns an m by n array, X, in which 
% each of the m rows has the n Cartesian coordinates 
% of a random point uniformly-distributed over the 
% interior of an n-dimensional hypersphere with 
% radius r and center at the Origin.  The function 
% 'randn' is initially used to generate m sets of n 
% random variables with independent multivariate 
% normal distribution, with mean 0 and variance 1.
% Then the incomplete gamma function, 'gammainc', 
% is used to map these points radially to fit in the 
% hypersphere of finite radius r with a uniform % spatial distribution.
% Roger Stafford - 12/23/05

X = randn(m,n);
s2 = sum(X.^2,2);
X = X.*repmat(r*(gammainc(s2/2,n/2).^(1/n))./sqrt(s2),1,n);

J'apprécierais grandement toute suggestion sur la génération d'un échantillon vraiment uniforme à partir d'un volume sphérique en Python.

Il semble y avoir beaucoup d'exemples montrant comment échantillonner à partir d'une coque sphérique uniforme, mais cela semble être un problème plus facile. Le problème est lié à la mise à l'échelle - il devrait y avoir moins de particules à un rayon de 0,1 qu'à un rayon de 1,0 pour générer un échantillon uniforme à partir du volume de la sphère.

Edit: Correction et suppression du fait que je demandais normalement et je voulais dire uniforme.

35
Tim McJilton

Bien que je préfère la méthode de suppression des sphères, pour être complet j'offre la solution exacte .

En coordonnées sphériques, profitant de la règle d'échantillonnage :

phi = random(0,2pi)
costheta = random(-1,1)
u = random(0,1)

theta = arccos( costheta )
r = R * cuberoot( u )

vous avez maintenant un (r, theta, phi) groupe qui peut être transformé en (x, y, z) de la manière habituelle

x = r * sin( theta) * cos( phi )
y = r * sin( theta) * sin( phi )
z = r * cos( theta )
37
dmckee

Générez un ensemble de points uniformément répartis dans un cube, puis jetez ceux dont la distance du centre dépasse le rayon de la sphère souhaitée.

14
Jim Lewis

Il existe une manière brillante de générer uniformément des points sur une sphère dans un espace à n dimensions, et vous l'avez signalé dans votre question (je veux dire le code MATLAB).

Pourquoi ça marche? La réponse est: regardons la densité de probabilité de la distribution normale à n dimensions. Il est égal (jusqu'à constant)

exp (-x_1 * x_1/2) * exp (-x_2 * x_2/2) ... = exp (-r * r/2), donc cela ne dépend pas de la direction, seulement de la distance! Cela signifie qu'après avoir normalisé le vecteur, la densité de la distribution résultante sera constante à travers la sphère.

Cette méthode devrait être définitivement préférée en raison de sa simplicité, de sa généralité et de son efficacité (et de sa beauté). Le code, qui génère 1000 événements sur la sphère en trois dimensions:

size = 1000
n = 3 # or any positive integer
x = numpy.random.normal(size=(size, n)) 
x /= numpy.linalg.norm(x, axis=1)[:, numpy.newaxis]

BTW, le bon lien à regarder: http://www-alg.ist.hokudai.ac.jp/~jan/randsphere.pdf

Quant à avoir une distribution uniforme dans une sphère, au lieu de normaliser un vecteur, vous devez multiplier le vercor par un certain f (r): f (r) * r est distribué avec une densité proportionnelle à r ^ n sur [0,1], ce qui a été fait dans le code que vous avez publié

13
Alleo

Le vecteur 3d gaussien normalisé est uniformément distribué sur la sphère, voir http://mathworld.wolfram.com/SpherePointPicking.html

Par exemple:

N = 1000
v = numpy.random.uniform(size=(3,N)) 
vn = v / numpy.sqrt(numpy.sum(v**2, 0))
2
ppv

Je suis d'accord avec Alleo. J'ai traduit votre code Matlab en Python et il peut générer des milliers de points très rapidement (une fraction de seconde dans mon ordinateur pour 2D et 3D). Je l'ai même exécuté jusqu'à 5D hypersphères . J'ai trouvé votre code si utile que je l'applique dans une étude. Tim McJilton, qui dois-je ajouter comme référence?

import numpy as np
from scipy.special import gammainc
from matplotlib import pyplot as plt
def sample(center,radius,n_per_sphere):
    r = radius
    ndim = center.size
    x = np.random.normal(size=(n_per_sphere, ndim))
    ssq = np.sum(x**2,axis=1)
    fr = r*gammainc(ndim/2,ssq/2)**(1/ndim)/np.sqrt(ssq)
    frtiled = np.tile(fr.reshape(n_per_sphere,1),(1,ndim))
    p = center + np.multiply(x,frtiled)
    return p

fig1 = plt.figure(1)
ax1 = fig1.gca()
center = np.array([0,0])
radius = 1
p = sample(center,radius,10000)
ax1.scatter(p[:,0],p[:,1],s=0.5)
ax1.add_artist(plt.Circle(center,radius,fill=False,color='0.5'))
ax1.set_xlim(-1.5,1.5)
ax1.set_ylim(-1.5,1.5)
ax1.set_aspect('equal')

uniform sample

2
Daniel

Serait-ce assez uniforme pour vos besoins?

In []: p= 2* Rand(3, 1e4)- 1
In []: p= p[:, sum(p* p, 0)** .5<= 1]
In []: p.shape
Out[]: (3, 5216)

Une tranche

In []: plot(p[0], p[2], '.')

ressemble à: enter image description here

2
eat
import numpy as np
import matplotlib.pyplot as plt





r= 30.*np.sqrt(np.random.Rand(1000))
#r= 30.*np.random.Rand(1000)
phi = 2. * np.pi * np.random.Rand(1000)



x = r * np.cos(phi)
y = r * np.sin(phi)


plt.figure()
plt.plot(x,y,'.')
plt.show()

voici ce que vous voulez

0
Olives99