[1]:
import clustertools as cts
from clustertools.analysis.functions import meq_func
import numpy as np
import matplotlib.pyplot as plt
A new version of galpy (1.8.1) is available, please upgrade using pip/conda/... to get the latest features and bug fixes!
/Users/webbjere/Codes/clustertools/clustertools/analysis/profiles.py:37: FutureWarning: all profiles are setup such that the returned radial bins and profile values are in linear space and not normalized by the effective radius. Previously select profiles had unique returns.
  warnings.warn('all profiles are setup such that the returned radial bins and profile values are in linear space and not normalized by the effective radius. Previously select profiles had unique returns.',FutureWarning)

Functions

Load a snapshot of a cluster in file 00000.dat, which has position units of pc and velocity units of km/s in clustercentric coordinates. Stellar masses are in solar units and were generated using a Salpeter IMF.

[2]:
cluster=cts.load_cluster('snapshot',filename='00000.dat',units='pckms',origin='cluster',ofilename='orbit.dat',ounits='kpckms')

Once initialized, any function within clustertools can be called internally via cluster.function_name() or externally via output=function_name(cluster). An internal call sets variables within cluster while an external call will return the calculated values but change nothing within cluster.

In the event that the snapshot is not centered, the centre of the cluster can be found via:

[3]:
cluster.find_centre()
[3]:
(-0.14946119556654258,
 -0.22087743738380078,
 -0.038780116570714403,
 0.014544487149392757,
 -0.097072929729564258,
 -0.028231116712277449)

which not only returns the position and velocity of the centre of mass, but also sets cluster.xc, cluster.yc, cluster.zc, cluster.vxc, cluster.vyc, cluster.vzc Alternatively, if you don’t want to set any StarCluster variables just call the function externally:

[4]:
xc,yc,zc,vxc,vyc,vzc=cts.find_centre(cluster)
print(xc,yc,zc)
-0.149461195567 -0.220877437384 -0.0387801165707

It is important to note that, by default, the centre is calculated to be the centre of density. Similar to NBODY6 and phigrape, once the centre of density is found for the entire cluster population, a centralized subset of stars within an ever decreasing radius are used to find the true centre of density. The parameters rmin and nmax set the minimum radius that can encompass the subset of stars and the maximum number of stars within the subset.

Setting density=False will instead find a central subset of stars to find the cluster’s of centre by removing stars beyond nsigma standard deviations of the previously calculated centre. For systems with a large number of escaped stars, which make finding the cluster’s centre difficult, it may also help to tell the function where to start. For example, it is possible to tell the cluster to start looking for the centre 1 pc away from the origin along the x-axis and remove stars beyond two standard deviations via:

[5]:
cluster.find_centre(xstart=1.,density=False, nsigma=1)
[5]:
(0.47255599444213536,
 -0.046784905321136626,
 0.03368417753435262,
 0.0073060547641532469,
 -0.05167863301943116,
 -0.0025985741474006487)

Other functions that can be called include:

[6]:
print('Half-Mass Relaxation Time: ',cluster.half_mass_relaxation_time())
print('Core Relaxation Time: ',cluster.core_relaxation_time())
print('Lagrange Radii: ',cluster.rlagrange())
print('Virial Radius: ',cluster.virial_radius())
Half-Mass Relaxation Time:  50.5231000207
Core Relaxation Time:  19.6957365444
Lagrange Radii:  [0.73326598977617097, 1.0765583789354674, 1.3646287125197105, 1.7124821982817706, 1.9617869129434311, 2.3496933983866723, 3.1257216188480137, 3.9773141578345004, 5.0211933936198365, 8.3242591110873079]
Virial Radius:  2.51937714706

All functions allow for projected values to be used instead:

[7]:
print('Half-Mass Relaxation Time: ',cluster.half_mass_relaxation_time(projected=True))
print('Core Relaxation Time: ',cluster.core_relaxation_time(projected=True))
print('Lagrange Radii: ',cluster.rlagrange(projected=True))
print('Virial Radius: ',cluster.virial_radius(projected=True))
Half-Mass Relaxation Time:  33.8815120123
Core Relaxation Time:  20.6789810497
Lagrange Radii:  [0.49097752209716733, 0.7013820946414554, 0.95595297621692432, 1.2127055798991366, 1.5030278513713957, 1.8280543898615964, 2.3000812878962309, 3.068226381254116, 4.1294671402214904, 8.1792344411375115]
Virial Radius:  1.57999162536

The core radius of the cluster can be calculated by finding where the density drops to 1/3 the central value. The parameter mfrac sets the mass fraction of the cluster that defintes the central region (default 0.1). When projected=True the core radius is where the density drops to 1/2 the central value. rcore also has a plotting feature

[8]:
cluster.rcore(plot=True)
print(cluster.rc)
0.865233682914
../_images/notebooks_functions_16_1.png

It is also possible to easily measure the mass function and its slope within a given mass range:

[9]:
m_mean, m_hist, dm, alpha, ealpha, yalpha, eyalpha=cts.mass_function(cluster,mmin=0.1,mmax=0.8,plot=True)
../_images/notebooks_functions_18_0.png

Calling the mass function function internally simply sets the value of alpha and saves the mass range over which alpha was measured with cluster.mmin and cluster.mmax

[10]:
cluster.mass_function(mmin=0.1,mmax=0.8)
[10]:
-2.2623485836819826

Looking at how velocity dispersion changes as a function of stellar mass is also a proxy for a cluster’s dynamical state, with the power-law slope eta evolving towards (but never reaching) -0.5. An eta of -0.5 corresponds to a state of complete energy equipartition. The external call to eta_function (shown below) and the internal call behave similarly to mass_function above.

[11]:
m_mean, sigvm, eta, eeta, yeta, eyeta=cts.eta_function(cluster,plot=True)
../_images/notebooks_functions_22_0.png

Alternatively, as per Bianchini et al. 2016, the state of energy equipartition can be probed by the meq parameter

[12]:
m_mean, sigvm, meq, emeq, ymeq, eymeq=cts.meq_function(cluster,plot=True)
../_images/notebooks_functions_24_0.png

Similarly, as per Bianchini et al. 2018, MNRAS, 475, 96, if you can measure meq you can also measure the kinematic concentration ck, which is the ratio of meq within the half-mass radius to meq at the half mass radius:

[13]:
cluster.ckin()
[13]:
1.0000000000004985

If the cluster’s galactocentric position and velocity are known, then its tidal radius and limiting radius can also be determined. Note when calculating the limiting radius, it is possible to select plot=True to see how the cluster’s density profile reaches the background density.

Note that you need to specify a galpy potential in order to calclated these values. For a Milky Way-like external tidal field, MWPotential2014 from Bovy J., 2015, ApJS, 216, 29 is useful. Different potentials can be used by assigning a galpy potential to the pot variable.

[14]:
from galpy.potential import MWPotential2014

print('Tidal Radius: ',cluster.rtidal(pot=MWPotential2014))
print('Limiting Radius: ',cluster.rlimiting(pot=MWPotential2014,plot=True))
Tidal Radius:  10.6993452944
Limiting Radius:  5.35685836913
../_images/notebooks_functions_28_1.png

It is also worth noting that it is possible to calculate the tidal radius iteratively. For example, lets assume a snapshot also contains a large number of stars that are likely not members of the cluster. By setting rtiterate>0, clustertools will first calculate the tidal radius using the mass of all stars in the snapshot (rt_old). However it will then calculate the tidal radius using only the mass of stars within the initial calculation (rt_new). This process is repeated rtiterate times or until the ratio of rt_new/rt_old is not less than rtconverge, which by default is 0.9.

[15]:
print('Tidal Radius: ',cluster.rtidal(pot=MWPotential2014,rtiterate=10))
Tidal Radius:  10.6993452944

Since not all snapshot will contain the energy of each star, clustertools can calculate these values as well by simply calling:

[16]:
ek, pot=cts.energies(cluster)

If energies were instead called internally, such that cluster.ek,cluster.pot,cluster.etot are set, a subset of stars could be defined that are gravitationally bound (E<0):

[17]:
cluster.energies()
bound_cluster=cts.sub_cluster(cluster,emax=0.)

Calling energies internally also set the virial parameter Qvir:

[18]:
print('Qvir = ',cluster.qvir)
Qvir =  -1.40814083302

Finally, in case one needs to know the distance to each stars nearest neighbour:

[19]:
distance_to_neighbour=cts.closest_star(cluster)
print(np.amin(distance_to_neighbour))
0.032610081984
[ ]: