Generate synthetic clusters

Generate synthetic clusters#

This tutorial shows how you can easily generate synthetic clusters with ASteCA.

We start by calling the Isochrones class to instantiate an object, called here isochs, with PARSEC isochrones. This is an example file but you can use whatever isochrone service fits your needs:

import asteca

# Load isochrones
isochs = asteca.Isochrones(
    model='parsec',
    isochs_path="../_static/parsec/",
    magnitude="Gmag",
    color=("G_BPmag", "G_RPmag"),
    magnitude_effl=6390.7,
    color_effl=(5182.58, 7825.08),
    verbose=2
)
Instantiating isochrones
Model          : PARSEC
N_files        : 1
N_mets         : 3
N_ages         : 11
N_isochs       : 2000
z    range     : [0.01, 0.02]
loga range     : [7.0, 9.5]
Magnitude      : Gmag
Color          : G_BPmag-G_RPmag
Isochrone object generated

Next we instantiate a Synthetic object called synthcl, using the isochs object we just created. We are using all default values for the arguments of Synthetic (and a fixed seed for reproducibility):

# Synthetic clusters parameters
synthcl = asteca.Synthetic(isochs, seed=457304, verbose=2)
Instantiating synthetic
Default params : {'met': 0.0152, 'loga': 8.0, 'alpha': 0.09, 'beta': 0.94, 'Rv': 3.1, 'DR': 0.0, 'Av': 0.2, 'dm': 9.0}
Extinction law : CCMO
Diff reddening : uniform
IMF            : chabrier_2014
Max init mass  : 10000
Gamma dist     : D&K
Random seed    : 457304
Synthetic clusters object generated

Now we just need to feed the generate() method of the synthcl object a dictionary with values for the fundamental parameters:

# Define dictionary with model parameters
params = {"alpha": 0.1, "beta": 1.0, "Rv": 3.1, "met": 0.0152, "DR": 0., "loga": 8.0, "dm": 10, "Av": 0.2}

# Generate the synthetic cluster
synth_arr = synthcl.generate(params)

As can be seen when we instantiated the synthcl object above, the Synthetic class contains a def_params argument with default values for all the fundamental parameters:

def_params = {'Av': 0.2, 'DR': 0.0, 'Rv': 3.1, 'alpha': 0.09, 'beta': 0.94, 'dm': 9.0, 'loga': 8.0, 'met': 0.0152}

This means that the user can generate synthetic clusters using dictionaries with only some parameters, and the rest will be fixed to their default values; for example:

params = {"met": 0.0152, "loga": 8.0, "dm": 10, "Av": 0.2}
synth_arr = synthcl.generate(params)
print(synth_arr.shape)
(4, 100)

The generated synth_arr array of shape (Ndim, Nstars) contains data associated to a synthetic cluster with the fundamental parameters stored in the params dictionary (plus the default ones if any were used).

The array contains Ndim=4 columns which are: magnitude, color, primary stellar mass and secondary stellar mass; i.e.: the mass of the secondary member of the binary system (a value of np.nan for this mass means that the system is not binary).

By default the generate() method returns 100 stars but we can modify that via the N_stars argument:

synth_arr = synthcl.generate(params, N_stars=250)
print(synth_arr.shape)
(4, 250)

We can plot the resulting array to show a synthetic CMD (color-magnitude diagram):

Hide code cell source
import numpy as np
import matplotlib.pyplot as plt

def cmd_plot(color, mag, label, ax=None):
    """Function to generate a CMD plot"""
    if ax is None:
        ax = plt.subplot(111)
    label = label + f", N={len(mag)}"
    ax.scatter(color, mag, alpha=0.25, label=label)
    ax.legend()
    ax.set_ylim(mag.max() + 1, mag.min() - 1)  # Invert y axis


# Generate a boolean mask identifying the binary systems
# as those where the secondary mass values are *not* np.nan
binary_msk = ~np.isnan(synth_arr[-1])

# Extract magnitude and color
mag, color = synth_arr[0], synth_arr[1]

# Plot single systems
cmd_plot(color[~binary_msk], mag[~binary_msk], "Single systems")

# Plot binary systems
cmd_plot(color[binary_msk], mag[binary_msk], "Binary systems")
../_images/cd30aebe69ce683d4fd54e58c4909d3bcfa1c3575ee4c5afd801c4eeb77658fc.png

Note

In the above plot the maximum magnitude value is given by the maximum value allowed by the theoretical isochrones and the synthetic photometric data has no associated uncertainties. The next section shows how both the maximum observed magnitude and the photometric uncertainties, as well as the number of synthetic stars, can be extracted from an observed cluster data file.

Calibrating an observed cluster#

Assuming we have a cluster file composed only of the most probable members of a given cluster cluster, we first load the file as a pandas.DataFrame() and use this to define a Cluster object:

import pandas as pd

obs_df = pd.read_csv("../_static/cluster.csv")

my_cluster = asteca.Cluster(
    ra=obs_df["RA_ICRS"],
    dec=obs_df["DE_ICRS"],
    magnitude=obs_df["Gmag"],
    e_mag=obs_df["e_Gmag"],
    color=obs_df["BP-RP"],
    e_color=obs_df['e_BP-RP'],
    verbose=2
)
Instantiating cluster
Columns read   : RA, DEC, Magnitude, e_mag, Color, e_color
N_stars        : 2759
N_clust_min    : 25
N_clust_max    : 5000
Cluster object generated

The CMD of the observed cluster looks like:

Hide code cell source
cmd_plot(my_cluster.color, my_cluster.mag, "Observed stars")
../_images/beb53b1143e633b3c298ac9b9947e0bad6fd7520397d06198b3a8daa506f9628.png

To calibrate the synthcl object we simply feed the my_cluster object to the calibrate() method:

# Calibrate the `synthcl` object
synthcl.calibrate(my_cluster)
Calibrated observed cluster
N_stars_obs    : 2759
Max magnitude  : 19.00
Error distribution loaded

The number of observed stars, the maximum magnitude value and the distribution of photometric uncertainties are now extracted from the oberved cluster data.

We can generate a synthetic cluster calibrated to the observed data simply by calling the generate() method with a dictionary of parameters, as done in the previous section:

params = {"alpha": 0.1, "beta": 1.0, "Rv": 3.1, "met": 0.0152, "DR": 0., "loga": 8.0, "dm": 8, "Av": 0.2}
synth_arr = synthcl.generate(params)
print(synth_arr.shape)
(4, 2759)

Notice that the array now contains the same number of synthetic stars as those present in the observed cluster. Plotting both the observed and the synthetic cluster side by side we get:

Hide code cell source
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
# Observed cluster
cmd_plot(my_cluster.color, my_cluster.mag, "Observed stars", ax1)

# Boolean mask identifying the binary systems
binary_msk = ~np.isnan(synth_arr[-1])
# Extract magnitude and color
mag, color = synth_arr[0], synth_arr[1]
# Plot single systems
cmd_plot(color[~binary_msk], mag[~binary_msk], "Single systems", ax2)
# Plot binary systems
cmd_plot(color[binary_msk], mag[binary_msk], "Binary systems", ax2)
../_images/4fa80ddcdda50548aa2ed8caaa4196510ce27d0232d7ecc6e436d23fc9064b0a.png

where we see that the synthetic maximum magnitude now matches the observed one, as does the number of synthetic stars generated. Also the synthetic photometry now looks affected by the same uncertainty than the observed cluster, as expected.

Furthermore we can obtain the theoretical isochrone associated to the synthetic cluster via the get_isochrone() method, and add it to the plot:

Hide code cell source
# Get isochrone associated to the synthetic cluster
isoch_arr = synthcl.get_isochrone(params)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
# Observed cluster
cmd_plot(my_cluster.color, my_cluster.mag, "Observed stars", ax1)

# Synthetic cluster
# Boolean mask identifying the binary systems
binary_msk = ~np.isnan(synth_arr[-1])
# Extract magnitude and color
mag, color = synth_arr[0], synth_arr[1]
# Plot single systems
cmd_plot(color[~binary_msk], mag[~binary_msk], "Single systems", ax2)
# Plot binary systems
cmd_plot(color[binary_msk], mag[binary_msk], "Binary systems", ax2)
# Plot the isochrone
ax2.plot(isoch_arr[1], isoch_arr[0], c="k");
../_images/5efaedf4bd2f35941382c28eef4bc8e918e527e1cd6539c123def03775bed91e.png