In [1]:
"""
Plummer model generator
This module contains a function used to create Plummer (1911) models, which 
follow a spherically symmetric density profile of the form:
rho = c * (1 + r**2)**(-5/2)
"""

import numpy
import numpy.random

from math import pi, sqrt

from amuse.units import nbody_system
from amuse import datamodel

__all__ = ["new_plummer_sphere", "new_plummer_model"]

class MakePlummerModel(object):
    def __init__(self, number_of_particles, convert_nbody = None, radius_cutoff = 22.8042468, mass_cutoff = 0.999,
            do_scale = False, random_state = None, random = None):
        self.number_of_particles = number_of_particles
        self.convert_nbody = convert_nbody
        self.mass_cutoff = min(mass_cutoff, self.calculate_mass_cuttof_from_radius_cutoff(radius_cutoff))
        self.do_scale = do_scale
        if not random_state == None:
            print("DO NOT USE RANDOM STATE")
        
        self.random_state = None
        
        if random is None:
            self.random = numpy.random
        else:
            self.random = random

    def calculate_mass_cuttof_from_radius_cutoff(self, radius_cutoff):
        if radius_cutoff > 99999:
            return 1.0
        scale_factor = 16.0 / (3.0 * pi)
        rfrac = radius_cutoff * scale_factor
        denominator = pow(1.0 + rfrac ** 2, 1.5)
        numerator = rfrac ** 3
        return numerator/denominator

    def calculate_radius(self, index):
        mass_min = (index * self.mass_cutoff) / self.number_of_particles
        mass_max = ((index+1) * self.mass_cutoff) / self.number_of_particles
        random_mass_fraction = self.random.uniform(mass_min, mass_max)
        radius = 1.0 / sqrt( pow (random_mass_fraction, -2.0/3.0) - 1.0)
        return radius

    def calculate_radius_uniform_distribution(self):
        return 1.0 /  numpy.sqrt( numpy.power(self.random.uniform(0,self.mass_cutoff,(self.number_of_particles,1)), -2.0/3.0) - 1.0)

    def new_positions_spherical_coordinates(self):
        pi2 = pi * 2
        radius = self.calculate_radius_uniform_distribution()
        theta = numpy.arccos(self.random.uniform(-1.0,1.0, (self.number_of_particles,1)))
        phi = self.random.uniform(0.0,pi2, (self.number_of_particles,1))
        return (radius,theta,phi)

    def new_velocities_spherical_coordinates(self, radius):
        pi2 = pi * 2
        x,y = self.new_xy_for_velocity()
        velocity = x * sqrt(2.0) * numpy.power( 1.0 + radius*radius, -0.25)
        theta = numpy.arccos(self.random.uniform(-1.0,1.0, (self.number_of_particles,1)))
        phi = self.random.uniform(0.0,pi2, (self.number_of_particles,1))
        return (velocity,theta,phi)

    def coordinates_from_spherical(self, radius, theta, phi):
        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)

    def new_xy_for_velocity(self):
        number_of_selected_items = 0
        selected_values_for_x = numpy.zeros(0)
        selected_values_for_y = numpy.zeros(0)
        while (number_of_selected_items < self.number_of_particles):
            x = self.random.uniform(0,1.0, (self.number_of_particles-number_of_selected_items))
            y = self.random.uniform(0,0.1, (self.number_of_particles-number_of_selected_items))
            g = (x**2) * numpy.power(1.0 - x**2, 3.5)
            compare = y <= g
            selected_values_for_x = numpy.concatenate((selected_values_for_x, x.compress(compare)))
            selected_values_for_y= numpy.concatenate((selected_values_for_x, y.compress(compare)))
            number_of_selected_items = len(selected_values_for_x)
        return numpy.atleast_2d(selected_values_for_x).transpose(), numpy.atleast_2d(selected_values_for_y).transpose()

    def new_model(self):
        m = numpy.zeros((self.number_of_particles,1)) + (1.0 / self.number_of_particles)
        radius, theta, phi = self.new_positions_spherical_coordinates()
        position =  numpy.hstack(self.coordinates_from_spherical(radius, theta, phi))
        radius, theta, phi = self.new_velocities_spherical_coordinates(radius)
        velocity = numpy.hstack(self.coordinates_from_spherical(radius, theta, phi))
        position = position / 1.695
        velocity = velocity / sqrt(1 / 1.695)
        return (m, position, velocity)

    @property
    def result(self):
        masses = numpy.ones(self.number_of_particles) / self.number_of_particles
        radius, theta, phi = self.new_positions_spherical_coordinates()
        x,y,z =  self.coordinates_from_spherical(radius, theta, phi)
        radius, theta, phi = self.new_velocities_spherical_coordinates(radius)
        vx,vy,vz = self.coordinates_from_spherical(radius, theta, phi)
 
        result = datamodel.Particles(self.number_of_particles)
        result.mass = nbody_system.mass.new_quantity(masses)
        result.x = nbody_system.length.new_quantity(x.reshape(self.number_of_particles)/1.695)
        result.y = nbody_system.length.new_quantity(y.reshape(self.number_of_particles)/1.695)
        result.z = nbody_system.length.new_quantity(z.reshape(self.number_of_particles)/1.695)
        result.vx = nbody_system.speed.new_quantity(vx.reshape(self.number_of_particles) / sqrt(1/1.695))
        result.vy = nbody_system.speed.new_quantity(vy.reshape(self.number_of_particles) / sqrt(1/1.695))
        result.vz = nbody_system.speed.new_quantity(vz.reshape(self.number_of_particles) / sqrt(1/1.695))
        result.radius = 0 | nbody_system.length

        result.move_to_center()
        if self.do_scale:
            result.scale_to_standard()

        if not self.convert_nbody is None:
            result = datamodel.ParticlesWithUnitsConverted(result, self.convert_nbody.as_converter_from_si_to_generic())
            result = result.copy()

        return result

def new_plummer_model(number_of_particles, *list_arguments, **keyword_arguments):
    """
    Create a plummer sphere with the given number of particles. Returns
    a set of stars with equal mass and positions and velocities distributed
    to fit a plummer star distribution model. The model is centered around the
    origin. Positions and velocities are optionally scaled such that the kinetic and
    potential energies are 0.25 and -0.5 in nbody-units, respectively.
    :argument number_of_particles: Number of particles to include in the plummer sphere
    :argument convert_nbody:  When given will convert the resulting set to SI units
    :argument radius_cutoff: Cutoff value for the radius (defaults to 22.8042468)
    :argument mass_cutoff: Mass percentage inside radius of 1
    :argument do_scale: scale the result to exact nbody units (M=1, K=0.25, U=-0.5)
    """
    uc = MakePlummerModel(number_of_particles, *list_arguments, **keyword_arguments)
    return uc.result

new_plummer_sphere = new_plummer_model

In [23]:
import numpy as np
from amuse.units import units

In [34]:
Mcluster= 100. | units.MSun
Rcluster= 1. | units.parsec
converter= nbody_system.nbody_to_si(Mcluster,Rcluster)
N = 1000
stars = new_plummer_sphere(N,converter)
stars.x.as_quantity_in(units.parsec)
stars.vx.as_quantity_in(units.kms)
stars.mass.as_quantity_in(units.MSun)

In [46]:
print(stars)


                 key         mass       radius           vx           vy           vz            x            y            z
                   -           kg            m    m * s**-1    m * s**-1    m * s**-1            m            m            m
====================  ===========  ===========  ===========  ===========  ===========  ===========  ===========  ===========
 8570000922529145642    1.989e+29    0.000e+00   -7.630e+01   -2.202e+02   -9.571e+01   -7.165e+15    1.175e+16   -1.924e+14
16866305370086961643    1.989e+29    0.000e+00    3.083e+02   -1.495e+02   -2.422e+02   -1.127e+16    1.692e+16   -2.547e+16
 3019341560404727600    1.989e+29    0.000e+00    6.799e+01    2.047e+02    3.433e+02    5.700e+15    2.645e+16   -4.216e+16
 9117812955890796496    1.989e+29    0.000e+00   -6.986e+01   -1.746e+02    1.463e+02   -9.282e+15   -2.395e+16   -3.681e+15
10494064922514935983    1.989e+29    0.000e+00    5.127e+01    4.360e+01    3.053e+02    1.462e+16   -1.553e+16   -1.161e+16
  933562790889811644    1.989e+29    0.000e+00   -9.603e+01   -4.110e+02    3.400e+02    1.993e+16   -3.579e+16   -4.827e+15
 2799784261108908469    1.989e+29    0.000e+00   -1.425e+02   -1.016e+02   -1.030e+02    4.252e+15   -6.147e+14   -1.468e+16
14204795523942641610    1.989e+29    0.000e+00   -3.458e+02    5.481e-01    3.205e+01    4.841e+16   -2.640e+16    2.751e+16
15968786880919290942    1.989e+29    0.000e+00   -7.995e+01   -4.651e+01    3.904e+02   -7.712e+15    2.825e+15   -1.334e+16
 5384214454233089650    1.989e+29    0.000e+00   -8.957e+01    2.408e+02   -3.225e+02    5.975e+15    1.324e+16    1.037e+16
11694318008629110429    1.989e+29    0.000e+00    4.534e+01    4.962e+02   -1.972e+01   -6.504e+15    2.327e+16   -1.315e+15
10468859765627707184    1.989e+29    0.000e+00    2.029e+02    2.033e+02    1.601e+02   -1.545e+15   -6.771e+14   -4.466e+15
14037989317420485962    1.989e+29    0.000e+00   -2.562e+02   -3.550e+02   -1.981e+02    1.779e+16   -7.389e+15   -1.755e+16
14696957979004543443    1.989e+29    0.000e+00   -1.271e+02   -1.703e+02   -1.770e+02   -4.040e+15    7.984e+14    8.039e+15
17450387517227513179    1.989e+29    0.000e+00   -4.699e+01   -6.429e+01    2.053e+02   -4.004e+15   -9.026e+15    7.453e+15
16371813288870014278    1.989e+29    0.000e+00   -2.200e+02   -4.478e+02    3.243e+02   -7.467e+15   -1.510e+16    9.998e+14
18257101678874285164    1.989e+29    0.000e+00   -5.997e+01    2.142e+02    8.987e+01   -5.401e+15   -9.939e+16    2.597e+16
 4235285509314623680    1.989e+29    0.000e+00    3.492e+02    2.605e+02    4.722e+02    2.494e+16    9.416e+15   -6.823e+15
 6305373368182115250    1.989e+29    0.000e+00    3.111e+02    6.102e+02   -1.383e+02    4.630e+15   -8.373e+15   -3.081e+15
 8658431730275604591    1.989e+29    0.000e+00    3.789e+02    1.334e+02   -4.368e+02    1.324e+16   -3.405e+15   -7.004e+14
                 ...          ...          ...          ...          ...          ...          ...          ...          ...
 9506530745109383956    1.989e+29    0.000e+00   -6.691e+01   -1.640e+02   -2.043e+02    7.108e+16   -2.860e+16    3.084e+15
 4110116295055423995    1.989e+29    0.000e+00   -1.981e+02   -2.468e+02   -3.306e+02    5.974e+15    4.625e+16    2.774e+16
 7576594238786647211    1.989e+29    0.000e+00   -3.030e+02   -8.113e+01    1.249e+02   -3.970e+15    1.992e+16    1.708e+15
15571495463014044435    1.989e+29    0.000e+00   -3.976e+02    3.159e+02    2.465e+02    2.743e+16    1.053e+16   -1.198e+16
 2754897307865800261    1.989e+29    0.000e+00    1.492e+02   -8.151e+01   -4.655e+02    2.818e+15    7.633e+15    2.567e+15
12394952476165677204    1.989e+29    0.000e+00   -3.713e+02    2.364e+02    5.769e+02   -4.757e+15    1.174e+16    1.225e+15
 2690794349631812192    1.989e+29    0.000e+00    2.197e+02   -1.236e+01   -2.966e+02   -3.550e+15    2.421e+15    1.187e+16
 5015937241314305491    1.989e+29    0.000e+00   -1.377e+02    1.195e+02   -9.651e+01    1.181e+16    8.715e+16    1.038e+17
 7917057405666164567    1.989e+29    0.000e+00   -8.997e+01   -3.953e+02   -8.788e+01    1.028e+16    1.083e+16    1.438e+16
 5324560192936279476    1.989e+29    0.000e+00   -1.737e+02    2.662e+02   -9.893e+01   -1.963e+16    2.257e+15    3.271e+16
15426111458719525995    1.989e+29    0.000e+00    1.271e+02    2.942e+02   -4.832e+02    1.078e+16   -2.429e+16   -1.086e+16
 3278817653049919000    1.989e+29    0.000e+00   -1.906e+02    2.904e+02   -4.827e+01    5.677e+15    9.446e+15    1.006e+16
15047600948894891976    1.989e+29    0.000e+00   -5.902e+02    4.129e+01   -2.340e+02    1.378e+16    1.762e+16    9.621e+15
 9188816936628549142    1.989e+29    0.000e+00   -1.276e+02   -5.699e+02   -6.673e+02   -4.068e+15   -6.627e+15   -5.996e+15
17247018077410035510    1.989e+29    0.000e+00   -1.488e+02   -3.642e+02    1.602e+02    2.065e+16   -6.337e+15   -3.206e+16
15815574283011827068    1.989e+29    0.000e+00   -4.883e+01   -2.192e+02   -1.239e+02    4.104e+15   -7.949e+15   -1.618e+16
 5017310566950932681    1.989e+29    0.000e+00   -8.746e+01   -2.139e+02   -9.114e+00   -1.263e+16   -1.132e+15   -2.200e+16
 2326402129812388904    1.989e+29    0.000e+00   -3.115e+02    2.026e+02    3.376e+02    5.303e+16   -1.899e+16    2.696e+16
 5624911068823610574    1.989e+29    0.000e+00   -3.928e+02    1.455e+02    8.931e+01    5.561e+15    1.716e+14   -1.316e+16
11054286897726687194    1.989e+29    0.000e+00   -6.441e+01    1.774e+02    9.861e+01   -3.865e+15   -2.176e+15   -1.329e+16
====================  ===========  ===========  ===========  ===========  ===========  ===========  ===========  ===========

In [38]:
np.mean(x)


Out[38]:
quantity<-1.45519152284e-14 m>

In [ ]: