Jorge Martinez

Aerospace Engineer and Senior Software Developer


Astrofísica Extragaláctica - Actividad Guiada I

Jorge Martínez Garrido

September 3, 2023

astronomy astrophysics galaxy cepheid stars


El presente informe contiene las soluciones a los ejercicios propuestos en la Actividad Guiada I de la asignatura “Astrofísica Extragaláctica”.

El informe presentan dos casos prácticos relacionados con la astronomía. En el primero, se utiliza el Telescopio Espacial Hubble para calcular la distancia a la galaxia M100 mediante el método de las Cefeidas, acompañado de la creación de un diagrama que representa la relación entre el período y la luminosidad de estas estrellas variables. En el segundo caso, se emplea la herramienta Aladin para determinar la distancia a la galaxia M31 y se compara esta medición con datos previamente publicados en artículos científicos de Astronomía y Astrofísica, nuevamente generando un diagrama Periodo-Luminosidad. Finalmente, se destaca que, a partir de las distancias calculadas a estas galaxias, se obtiene una estimación de la constante de Hubble y se aproxima la edad del Universo, proporcionando una perspectiva valiosa sobre la medición de distancias cósmicas y la comprensión del universo en su conjunto.

Cálculo de la distancia a la galaxia M100

Para resolver los ejercicios se han utilizado los datos del Hubble Space Telescope (HST). Dichos datos se encuentran comprimidos, por lo que es necesario descomprimirlos para su posterior procesado:

!unzip -u data/HST.zip -d data/ >/dev/null 2>&1

Representación de la curva de luz de cada estrella Cefeida

Como resultado de la extracción previa, se han generado varios archivos TXT que almacenan datos cruciales. Estos archivos presentan una estructura de dos columnas: la primera columna registra el tiempo en días, mientras que la segunda columna proporciona la magnitud de la estrella en estudio.

A partir de estos datos, es posible construir y representar gráficamente la curva de luz de cada estrella. Esta representación visual permite una observación más detallada de las fluctuaciones en la magnitud de las estrellas a lo largo del tiempo, dado que se trata de estrellas Cefeidas, conocidas por sus variaciones características.

import matplotlib.pyplot as plt
import pandas as pd
from pathlib import Path


data = Path('data')
stars_df_list = [
    pd.read_csv(file, sep='\t', header=None, names=['Time', 'Magnitude']) 
    for file in data.glob("*.txt")
] 

fig, axes = plt.subplots(3, 4, figsize=(15, 10))
fig.tight_layout(pad=3.0)

for i, df in enumerate(stars_df_list):
    row = i // 4
    col = i % 4
    ax = axes[row, col]
    
    # Plot the data
    ax.plot(df['Time'], df['Magnitude'], "r-*", label=f'Star {i+1}')
    ax.invert_yaxis()  # Invert the Y-axis
    ax.set_title(f'Star {i+1}')
    ax.set_xlabel('Time (days)')
    ax.set_ylabel('Magnitude (mag)')
    ax.grid(True)
    ax.legend()
    
fig.suptitle('Figura 1 - Evolución de la magnitud en el tiempo para estrellas cefeidas')
plt.subplots_adjust(top=0.90)
plt.show()

Cálculo del período de oscilación

El período de oscilación de cada estrella se puede calcular como la diferencia de tiempo entre dos mínimos consecutivos en la magnitud.

import pandas as pd


def find_period_from_mag_and_time_df(df):
    min_magnitudes = df.nsmallest(2, 'Magnitude')
    min_times = min_magnitudes["Time"].values
    return abs(min_times[0] - min_times[1])

stars_periods = [find_period_from_mag_and_time_df(df) for df in stars_df_list]

stars_periods_df = pd.DataFrame({'Cefeida': list(range(1, len(stars_periods) + 1)),
                   'Periodo (dias)': list(map(int, stars_periods))})
print("Tabla 1 - Período de oscilación de cada estrella")
stars_periods_df
Tabla 1 - Período de oscilación de cada estrella

CefeidaPeriodo (dias)
0152
1224
2326
3425
4539
5642
6731
7826
8934
91021
101144
111230

Cálculo de la magnitud abosluta

Conocido el período de oscilación, es posible utilizar la siguiente relación de Feast y Catchpole para calcular la magnitud absoluta $M_V$ de una Cefeida:

$$ M_V = -1.43 - 2.81 \cdot \log{(P)} $$

donde $P$ es el período en dias.

import numpy as np


def find_mag_absolute_from_period(period):
    return -1.43 - 2.81 * np.log10(period)

stars_absolute_magnitudes = [find_mag_absolute_from_period(P) for P in stars_periods]


stars_absolute_magnitudes_df = pd.DataFrame(
    {
        'Cefeida': list(range(1, len(stars_absolute_magnitudes) + 1)),
        'Mv (mag)': list(np.round(stars_absolute_magnitudes, 2))
    }
)
print("Tabla 2 - Magnitud absoluta de cada estrella")
stars_absolute_magnitudes_df
Tabla 2 - Magnitud absoluta de cada estrella

CefeidaMv (mag)
01-6.27
12-5.32
23-5.43
34-5.38
45-5.90
56-6.00
67-5.62
78-5.42
89-5.76
910-5.19
1011-6.05
1112-5.59

Cálculo de la magnitud aparente

Es posible calcular la magnitud aparente $m$ a partir de los datos de las curvas de luz. Para ello, se aplica el procedimiento estándar originado en el siglo XX. Se obtienen los valores mínimo y máximo de la magnitud, y luego se calcula la media de estos valores. Ese resultado se considera como el valor válido de la magnitud aparente.

def find_mag_apparent_from_df(df):
    min_mag = df['Magnitude'].min()
    max_mag = df['Magnitude'].max()
    return (min_mag + max_mag) / 2

stars_apparent_magnitude = [find_mag_apparent_from_df(df) for df in stars_df_list]

stars_apparent_magnitude_df = pd.DataFrame(
    {
        'Cefeida': list(range(1, len(stars_apparent_magnitude) + 1)),
        'm (mag)': list(np.round(stars_apparent_magnitude, 2))
    }
)
print("Tabla 3 - Magnitud aparente de cada estrella")
stars_apparent_magnitude_df
Tabla 3 - Magnitud aparente de cada estrella

Cefeidam (mag)
0124.87
1226.21
2325.67
3425.42
4525.39
5625.64
6726.38
7826.36
8926.33
91026.23
101125.26
111226.44

Cálculo de la distancia de cada estrella a la Tierra

Una vez que tenemos conocida tanto la magnitud absoluta (M) como la magnitud aparente (m) de una estrella, podemos aplicar la siguiente relación:

$$ m - M = -5 + 5 \cdot \log{(D)} $$

donde $D$ representa la distancia a la estrella. Esta fórmula nos permite determinar la distancia a la estrella en función de sus magnitudes. Para obtener la distancia de manera explícita, podemos reescribir la fórmula de la siguiente manera:

$$ D=10^{\left(1 + \frac{m−M}{5}\right)} $$

Esta expresión nos brinda una forma más clara y directa de calcular la distancia a una estrella en función de las magnitudes observadas.

from astropy import units as u

def find_distance_from_mag(m, M):
    return (10 ** (1 + (m - M) / 5)) * u.pc

stars_distances = [
    find_distance_from_mag(m, M)
    for m, M in zip(stars_apparent_magnitude, stars_absolute_magnitudes)
]

stars_distances_df = pd.DataFrame(
    {
        'Cefeida': list(range(1, len(stars_distances) + 1)),
        'Distancia (Mpc)': [
            np.round(d.to_value(u.Mpc), 2) 
            for d in stars_distances]
    }
)
print("Tabla 4 - Distancia de cada estrella a la Tierra en Mpc")
stars_distances_df
Tabla 4 - Distancia de cada estrella a la Tierra en Mpc

CefeidaDistancia (Mpc)
0116.93
1220.25
2316.62
3414.46
4518.15
5621.35
6725.09
7822.68
8926.14
91019.25
101118.25
111225.43

Cálculo de la distancia media a M100 a la Tierra

Para obtener la distancia media de M100 a la Tierra, se puede utilizar el valor medio de la distancia de la Tierra con las estrellas estudiadas:

m100_distance = np.mean([d.to_value(u.Mpc) for d in stars_distances]) * u.Mpc
print(f"Distancia media a M100 = {m100_distance:.1f}")
Distancia media a M100 = 20.4 Mpc
Galaxia M100

Comparacion de la distancia obtenida con los resultados oficiales

El valor de la distancia obtenido por Freedman es de $17.1\pm 1.8$ megaparsecs. Por otro lado, el valor obtenido en este informe es de $20.4$ megaparsecs.

Si bien, el valor calculado se encuentra fuera del margen de error de Freedman, es importante incurrir en el hecho de que no se ha considerado el polvo estelar. Las mediciones astronómicas pueden variar debido a diferentes técnicas y enfoques utilizados en diferentes estudios. Además, la distancia a galaxias distantes como M100 puede ser complicada de determinar con precisión debido a varios factores, como el efecto de la expansión del universo y la presencia de materia interestelar (como el polvo), que puede afectar las mediciones de distancia.

Cálculo de la distancia a Andrómeda (M31)

Para calcular la distancia a M31, hemos empleado los datos proporcionados por Joshi, específicamente los incluidos en la tabla 4, que se pueden consultar en https://cdsarc.cds.unistra.fr/viz-bin/nph-Cat/txt?J/A+A/402/113/table4.dat.

Dicha tabla incluye valores de posición, magnitud, y período, entre otros, para un total de 26 estrellas Cefeidas. Siguiendo los mismos pasos del ejercicio anterior, hemos calculado la distancia a M31.

Galaxia M31

Lectura de los datos

Para poder procesar los datos de la tabla, se utiliza la siguiente rutina escrita en Python:

file_path = 'data/joshi.dat'
joshi_data = pd.read_csv(
    file_path, delimiter='|', skipinitialspace=True, 
    header=0, na_values=['***', '---']
)
joshi_data.rename(columns=lambda x: x.strip(), inplace=True)
joshi_data = joshi_data.iloc[1:]
joshi_data.reset_index(drop=True, inplace=True)

Cálculo de la magnitud abosluta y relativa

Aplicamos la relación de Joshi para obtener la magnitud absoluta en la banda I, conocida como $M_I$:

$$ M_I = -1.94 - 2.96 \cdot \log(P) $$

Teniendo en cuenta que los datos incluyen el error de observacion, aplicamos la propagacion de errores:

$$ \Delta M_I = \sqrt{\left(\frac{-2.96}{P} \right)^2 \cdot (\Delta P)^2} $$

Para la magnitud relativa en esta banda, utilizaremos los datos de la columna Icmag.

def find_mag_absolute_from_period(period):
    return -1.97 - 2.81 * np.log10(period)

def find_mag_absolute_err_from_period_err(period, error):
    return np.sqrt((-2.96 / period) ** 2 * error ** 2)

stars_names = [f"V{ith_star}" for ith_star in range(1, len(joshi_data["ID"]) + 1)]
stars_absolute_magnitudes = [
    find_mag_absolute_from_period(P)
    for P in joshi_data["Per"].astype(float)
]
stars_absolute_magnitudes_err = [
    find_mag_absolute_err_from_period_err(float(P), float(error))
    for P, error in zip(joshi_data["Per"], joshi_data["e_Per"])
]
stars_apparent_magnitude = joshi_data["Icmag"].astype(float)


stars_apparent_magnitude_df = pd.DataFrame({
    "Estrella": stars_names,
    "M_I": np.round(stars_absolute_magnitudes, 3),
    "ΔM_I": np.round(stars_absolute_magnitudes_err, 3),
    "m_I": stars_apparent_magnitude,
})


print("Tabla 5 - Magnitudes de cada estrella")
stars_apparent_magnitude_df
Tabla 5 - Magnitudes de cada estrella

EstrellaM_IΔM_Im_I
0V1-4.4220.00119.98
1V2-4.5910.00119.69
2V3-4.6290.00120.28
3V4-4.6730.00319.54
4V5-4.7540.00220.04
5V6-4.8260.00319.76
6V7-4.8400.00120.27
7V8-4.9150.00319.55
8V9-5.1710.00119.60
9V10-5.2270.00119.84
10V11-5.2960.00218.87
11V12-5.3120.00220.08
12V13-5.3350.00219.46
13V14-5.3460.00219.58
14V15-5.3500.00219.91
15V16-5.3820.00419.74
16V17-5.3990.00219.60
17V18-5.4790.00219.09
18V19-5.4860.00519.60
19V20-5.6310.00118.99
20V21-5.7110.00319.31
21V22-5.9920.00419.19
22V23-6.0700.00218.92
23V24-6.3130.00419.57
24V25-6.5750.00518.35
25V26-6.8830.00418.82

Cálculo de la distancia de cada estrella a la Tierra

Aplicamos la formula $m - M = -5 + 5 \cdot \log{(D)}$ para calcular la distancia a M31. Dado que esta formula devuelve el valor en parsecs, se aplica la conversión a millones de años luz para la distancia:

stars_distances = [
    find_distance_from_mag(m, M)
    for m, M in zip(stars_apparent_magnitude, stars_absolute_magnitudes)
]

Generamos el histograma:

fig, ax = plt.subplots()
ax.hist(
    [d.to_value(u.Mlyr) for d in stars_distances], 
    bins=10, color='red', edgecolor='black', alpha=0.7
)
ax.set_xlabel('Distancia (millones de años luz)')
ax.set_ylabel('Frecuencia')
ax.set_title('Figura 2 - Histograma de distancias')
plt.show()

El histograma de la figura 2 es fundamental para conocer la distribución de los datos, ya que no siguen una distribución normal. Esto puede deberse a los factores citados por Joshi en su artículo:

Podría haber una incertidumbre adicional en la determinación de la distancia debido al efecto de mezcla en las Ceféidas, así como a la extinción variable dentro de la región observada.

Por ello, eliminamos los valores más extremos y aplicamos la media al nuevo espacio muestral:

trim_size = int(0.5 * len(stars_distances))

m31_distance = np.mean(
    [
        d.to_value(u.Mlyr) 
        for d in sorted(stars_distances)[0:-trim_size]
    ]
) * u.Mlyr
print(f"Distancia reducida media a M31 = {m31_distance:.1f}")
Distancia reducida media a M31 = 2.7 Mlyr

Cálculo de la curva de luz para las estrellas estudiadas de M31

Es posible representar la curva de luz de las estrellas utilizando las columnas del período y de la magnitud aparente para la banda infrarroja:

from matplotlib.ticker import MultipleLocator


fig, ax = plt.subplots()

periods=np.log10(joshi_data["Per"].astype(float))
magnitudes=joshi_data["Icmag"].astype(float)

ax.scatter(periods, magnitudes, marker="o", color="r", s=25)
for i in range(len(stars_names)):
    plt.annotate(
        stars_names[i], (periods[i], magnitudes[i]), 
        textcoords="offset points", xytext=(0, 10), ha='center'
    )


ax.set_title("Figura 3 - Magnitud aparente en el filtro infrarrojo en función del periodo")
ax.set_xlabel("log10(P) (days)")
ax.set_ylabel("I (mag)")

ax.set_xticks([1.0, 1.2, 1.4, 1.6])
ax.set_yticks([21, 20, 19, 18])
ax.xaxis.set_minor_locator(MultipleLocator(0.20))
ax.yaxis.set_minor_locator(MultipleLocator(0.20))

ax.invert_yaxis()

Estimando $H_0$ y la edad $t$ del Universo

Una vez conocidas la distancia a las galaxias M31 y M100, es posible utilizar este dato para calcular la constante de Hubble $H_0$ y la edad del Universo $t$.

Cálculo de $H_0$

Es posible calcular la constante de Hubble conocidas la distancia y la velocidad de expansión de una galaxia. La relación es la siguiente:

$$ H_0 = \frac{v}{D} $$

siendo $v$ la velocidad de expansión y $D$ la distancia a la que se encuentra la galaxia.

Para obtener la velocidad de expansión, se han tomado los datos proporcionados por https://ned.ipac.caltech.edu.

data = pd.DataFrame({
    "Name": ["M31", "M100"],
    "Distance": [np.round(m31_distance, 2), np.round(m100_distance, 2)],
    "Velocity": [297 * u.km / u.s, 1570 * u.km / u.s],
    "DeltaV": [23 * u.km / u.s, 23 * u.km / u.s],
})
data

NameDistanceVelocityDeltaV
0M312.7 Mlyr297.0 km / s23.0 km / s
1M10020.38 Mpc1570.0 km / s23.0 km / s
data["H0"] = [np.round(value) for value in data["Velocity"] / data["Distance"]]
data

NameDistanceVelocityDeltaVH0
0M312.7 Mlyr297.0 km / s23.0 km / s110.0 km / (Mlyr s)
1M10020.38 Mpc1570.0 km / s23.0 km / s77.0 km / (Mpc s)

Calculando la media para la constante de Hubble, se encuentra:

H0 = data["H0"].mean()
print(f"Constante de Hubble {H0.to(u.km / u.Mpc / u.s):.3f}")
Constante de Hubble 217.886 km / (Mpc s)

Cálculo de $t$

La edad del Universo puede calcularse a través de la relación:

$$ t = \frac{1}{H_0} $$

Aplicando dicha relación se obtiene:

data["t"] = [np.round((1 / H0).to(u.Myr), 2) for H0 in data["H0"]]
data

NameDistanceVelocityDeltaVH0t
0M312.7 Mlyr297.0 km / s23.0 km / s110.0 km / (Mlyr s)2725.39 Myr
1M10020.38 Mpc1570.0 km / s23.0 km / s77.0 km / (Mpc s)12698.6 Myr

Calculando la media para la edad, es encuentra:

universe_age = data["t"].mean()
print(f"Edad media del universo {universe_age.to_value(u.Myr):.0f} millones de años")
Edad media del universo 7712 millones de años

Conocida la edad de la Tierra, es posible calcular cúan mayor es el Universo:

earth_age = 4543 * u.Mlyr

delta_age = (universe_age / earth_age).value
print(f"La edad del Universo es {delta_age:.2f} veces mayor que la edad de la Tierra")
La edad del Universo es 1.70 veces mayor que la edad de la Tierra

Es importante indicar que los datos anteriores son escasos para obtener resultados precisos. Sin embargo, el proceso demuestra que es posible medir la edad del universo a partir de la distancia obtenida gracias al estudio de las Cefeidas y la velocidad de expansión de la galaxia en la que se encuentran.

Conclusión

El informe nos proporciona un método eficiente para estimar la edad del Universo mediante el estudio de las Cefeidas. Estas estrellas exhiben una variación periódica en su brillo, lo que nos permite calcular con precisión su distancia. Esta distancia se emplea posteriormente para determinar la distancia promedio a las galaxias en las que residen estas estrellas. Al medir la distancia a múltiples galaxias y considerar su velocidad de expansión, podemos calcular la constante de Hubble y, en última instancia, deducir la edad del Universo.