-->

Etiquetas

El color de los objetos celestes: parte II

Tras mi anterior entrada en este blog, dedicada al tama del color de los objetos celestes, esta entrada constituye su continuación, en la cual examinaremos los datos de un catálogo de estrellas que contenga datos fotométricos y espectrográficos y formaremos con ellos gráficos color-color en los que se tratará de diferenciar entre las diferentes clases estelares. Es decir, si el post anterior introducía una serie de conceptos teóricos, ahora vamos a aplicar estos conceptos a datos reales.

Importaciones y referencias

El catálogo que vamos a utilizar es el "Bright Star Catalogue", el cual incluye prácticamente todas las estrellas visibles a simple vista, hasta la magnitud 6.5 o más brillantes. Aquí hay una primera descripción.

Este catálogo está disponible online aquí. Para seguir los ejercicios de esta entrada habrá que descargar de este sitio los tres ficheros de los que consta el catálogo:

  • bsc5.dat El catálogo
  • bsc5.readme Una descripción del catálogo
  • bsc5.notes Notas del catálogo

También será util este artículo de la wikipedia dedicado a la clasificación de las estrellas por las características de su espectro.

Como de costumbre, esta entrada está escrita íntegramente con el Notabook de IPython, y se utilizarán una serie de librerías de Python que a continuación importamos:

In [1]:
%matplotlib inline

from __future__ import division

import quantities as pq
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# Generar un cuadro con versiones de las librerías utilizadas en este notebook
#https://github.com/jrjohansson/version_information
%load_ext version_information
%version_information numpy, matplotlib, quantities, pandas
Out[1]:
SoftwareVersion
Python2.7.9 64bit [GCC 4.4.7 20120313 (Red Hat 4.4.7-1)]
IPython2.3.1
OSLinux 3.13.0 44 generic x86_64 with debian jessie sid
numpy1.9.1
matplotlib1.4.2
quantities0.10.1
pandas0.15.2
Sat Jan 24 12:07:22 2015 CET

Lectura de los datos del "Bright Star Catalogue" (BSC)

Una vez se han descargado los tres ficheros que componen el BSC, Vamos a leer una serie de campos importandolos en un dataframe de Pandas. Sobre esto dos observaciones:

  • La estructura de las entradas en el BSC viene detallada en el archivo bsc5.readme, junto con una somera descripción.
  • No vamos a leer todos los campos, sino tan solo aquellos que vamos a utilizar más adelante.
  • Al tratarse de registros con campos de ancho fijo, utilizaremos la función read_fwf() de la librería Pandas que permite proporcionar una descripción de la posición de comienzo y longitud de cada campo.
In [2]:
colspecs = [(0,4), (4,14), (25,31), (31,37), (41,42), (43,44), (51,60), 
            (90,96), (96,102), (102,107), (109,114), (115,120), (127,147)]
In [3]:
labels =['HR', 'Name', 'HD', 'SAO', 'IRflag', 'Multiple', 'VarID', 
         'GLON', 'GLAT', 'Vmag', 'B-V', 'U-B', 'SpType']
In [4]:
bsc = pd.read_fwf('../datos/bsc5.dat', header= None, colspecs=colspecs,
                    names=labels, index_col=0)

Veamos una muestra de los datos:

In [5]:
bsc.head()
Out[5]:
Name HD SAO IRflag Multiple VarID GLON GLAT Vmag B-V U-B SpType
HR
1 NaN 3 36042 NaN NaN NaN 114.44 -16.88 6.70 0.07 0.08 A1Vn
2 NaN 6 128569 NaN NaN NaN 98.33 -61.14 6.29 1.10 1.02 gG9
3 33 Psc 28 128572 I NaN Var? 93.75 -65.93 4.61 1.04 0.89 K0IIIbCN-0.5
4 86 Peg 87 91701 NaN NaN NaN 106.19 -47.98 5.51 0.90 NaN G5III
5 NaN 123 21085 NaN NaN V640 Cas 117.03 -3.92 5.96 0.67 0.20 G5V
In [6]:
bsc.tail()
Out[6]:
Name HD SAO IRflag Multiple VarID GLON GLAT Vmag B-V U-B SpType
HR
9106 NaN 225233 255629 NaN NaN NaN 307.68 -43.80 7.31 0.44 0.01 F2V
9107 NaN 225239 53622 NaN NaN NaN 112.17 -27.24 6.12 0.62 0.09 G2V
9108 NaN 225253 255631 NaN NaN NaN 308.18 -45.21 5.59 -0.12 -0.42 B8IV-V
9109 NaN 225276 73731 I NaN NaN 110.22 -35.07 6.25 1.40 1.59 K4IIIb
9110 NaN 225289 10962 NaN NaN V567 Cas 117.40 -1.06 5.80 -0.09 -0.32 B8IVpHgMn

Diagrama color-color de la secuencia principal

El propósito final de este post es hacer diagramas color-color de los diferentes tipos de estrellas, es decir, clases espectrales, para lo cual comenzaremos con las estrellas en la secuencia principal. Utilizaremos los índices de color del sistema UBV, al ser estos los recogidos en el BSC

Se denominan así las estrellas (las cuales constituyen aproximádamente el 80% de la población estelar) que utilizan hidrógeno como combustible en sus procesos de fusión nuclear.

Para diferenciar estas estrellas del resto nos servremos del campo de tipo espectral del BSC (la columna del dataframe con el nombre SpType). El BSC utiliza el sistema de clasificación de Morgan-Keenan (MK), el cual incluye la clasificación espectral en el esquema de clasificación de Harvard (O B A F G K M más un numeral 0-9), al cual se añade una clasificación basada en la luminosidad, que utiliza numerales romanos I-VI, a los cuales se añade en ocasiones una letra minúscula "a" o "b". Ejemplos de la clasificación resultante son: "B5III", "A5Ia", "K0V", etc. En particular, la "V" significa "main sequence", que es lo que nos va a permitir reconocer las estrellas pertenecientes a la secuencia principal.

Observación importante: téngase en cuenta que el "Bright Star Catalogue" solo incluye las estrellas más brillantes, básicamente aquellas que son visibles a simple vista, por lo que el número de estrellas de una determinada clase espectral en el catálogo no es en modo alguno representativa de su abundancia en el universo. Las estrellas menos brillantes o más lejanas no estarán representadas en el BSC.

¿Cuantos tipos espectrales existen en el catálogo?

In [7]:
bsc['SpType'].nunique()
Out[7]:
1975

¿Hay algunos tipos espectrales sin datos (marcados como NaN en Pandas)?

In [8]:
pd.isnull(bsc['SpType']).sum()
Out[8]:
14

Hay 14. Eliminemos del dataframe estas entradas

In [9]:
b = pd.notnull(bsc['SpType'])
bsc = bsc[b]
bsc.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 9096 entries, 1 to 9110
Data columns (total 12 columns):
Name        3143 non-null object
HD          9096 non-null float64
SAO         9071 non-null float64
IRflag      1743 non-null object
Multiple    1577 non-null object
VarID       2172 non-null object
GLON        9096 non-null float64
GLAT        9096 non-null float64
Vmag        9096 non-null float64
B-V         8786 non-null float64
U-B         7206 non-null float64
SpType      9096 non-null object
dtypes: float64(7), object(5)
memory usage: 923.8+ KB

En el resumen anterior comprobamos que el dataframe tiene ya 14 entradas menos, y el campo SpType carece de valores no nulos.

A cpntinuación vamos a seleccionar las estrellas en la secuencia principal, creando un nuevo dataframe al cual llamaremos main(). Estas son aquellas que en la clasificación MK tienen una clase de luminosidad 'V':

In [10]:
b1 = bsc['SpType'].str.contains('V')
b2 = bsc['SpType'].str.contains('IV')
b3 = bsc['SpType'].str.contains('VI')

# Clases de luminosidad de tipo "V", no "IV", ni "VI" ni "VII"
b = np.logical_and(b1,np.logical_and(np.logical_not(b2),np.logical_not(b3)))
main = bsc[b]
main.shape
Out[10]:
(3025, 12)

Hagamos una primera comprobación visual:

In [11]:
main[0:10]
Out[11]:
Name HD SAO IRflag Multiple VarID GLON GLAT Vmag B-V U-B SpType
HR
1 NaN 3 36042 NaN NaN NaN 114.44 -16.88 6.70 0.07 0.08 A1Vn
5 NaN 123 21085 NaN NaN V640 Cas 117.03 -3.92 5.96 0.67 0.20 G5V
8 NaN 166 73743 NaN NaN 33 111.26 -32.83 6.13 0.75 0.33 K0V
9 NaN 203 166053 NaN NaN NaN 52.21 -79.14 6.18 0.38 0.05 A7V
10 NaN 256 147090 NaN NaN NaN 74.36 -75.90 6.19 0.14 0.10 A6Vn
12 NaN 319 166066 NaN NaN 46 55.56 -79.07 5.94 0.14 0.06 A2Vp:
24 Kap1Scl 493 166083 NaN NaN NaN 25.24 -80.63 5.42 0.42 0.08 F2V
26 34 Psc 560 91750 NaN NaN NaN 106.87 -50.43 5.51 -0.07 -0.24 B9Vn
32 NaN 661 255642 NaN W NaN 306.98 -43.58 6.64 0.37 0.06 F2V+F6V
33 6 Cet 693 147133 NaN NaN NaN 82.24 -75.06 4.89 0.49 -0.03 F7V

Ahora queremos comprobar que la primera letra de estas clases espectrales está en la secuencia O, B, A, F, G, K, M

In [12]:
main['SpType'].map(lambda s:s[0]).unique()
Out[12]:
array(['A', 'G', 'K', 'F', 'B', 'M', 'O'], dtype=object)

Preparamos ahora un diccionario de Python con los códigos de colores que vamos a asignar a cada clase espectral, los cuales irán del azul (tipo O) al rojo (tipo M):

In [13]:
colors = {'O':'#0000FF', 'B':'#CCCCFF', 'A':'#FFFFFF', 'F':'#FFFFB2', 'G':'#FFFF00', 'K':'#FFA500', 'M':'#FF0000'}

Y generamos el diagrama color-color con Python Matplotlib

In [14]:
fig, ax = plt.subplots(figsize=(10, 10))
ax.set_axis_bgcolor('0.6')
ax.grid()
ax.set_title(u'Color-color secuencia principal catálogo BSC')

ax.title.set_fontsize(20)
ax.set_xlabel('B-V')
ax.xaxis.label.set_fontsize(20)
ax.set_ylabel('U-B')
ax.yaxis.label.set_fontsize(20)

for cls in 'OBAFGKM':
    f = lambda s: s[0] == cls
    b = main['SpType'].astype('string').map(f)
    x = main[b]['B-V']
    y = main[b]['U-B']
    c = colors[cls]
    ax.scatter(x, y, c = c, s=6, edgecolors='none', label = cls)

legend = ax.legend(scatterpoints=1,markerscale = 6, shadow=True)
frame = legend.get_frame()
frame.set_facecolor('0.90')

Se ve que de la clase M hay muy poquitas estrellas, ¡pero esto es porque solo estamos considerando aquellas visibles a simple vista! y son poco luminosas. De hecho son las más abundantes con diferencia.

Diagrama color-color de las estrellas gigantes

Previamente, vamos a ver si en el BSC existen estrellas enanas, entendiendo por tal las correspondientes a las clases de luminosidad VI y VII

In [15]:
b =  bsc['SpType'].str.contains('VI')
bsc[b].shape
Out[15]:
(0, 12)

Vemos que no hay estrellas de estos tipos en el catálogo BSC, lo que era de esperar al tratarse de un catálogo de las estrellas más brillantes del cielo. Esto significa que en el campo SpType no hay valores "VI" ni VII", lo que será tenido en cuenta al hacer la selección.

Vamos pues a incorporar al gráfico las estrellas pertenecientes a clases de luminosidad de tipos gigantes:

  • I Supergigantes
  • II Gigantes brillantes
  • III Gigantes normales
  • IV Subgigantes

Vamos a ver cuantas estrellas hay de estas clases en el bsc:

In [16]:
b =  bsc['SpType'].str.contains('I')
giants = bsc[b]
giants.shape
Out[16]:
(5161, 12)

Y veamos a continuación si entre estas aparecen nuevas clases espectrales además de las habituales O, B, A, F, G, K, M

In [17]:
giants['SpType'].map(lambda s:s[0]).unique()
Out[17]:
array(['K', 'G', 'B', 'F', 'M', 'A', 'O', 'S', 'C', 'W'], dtype=object)

Vemos que en efecto aparecen nuevas clases espectrales más exóticas: W (estrellas azules y brillantes que poseen sobre todo helio en lugar de hidrógeno en sus atmósferas), y las estrellas de carbono de tipos C y S, gigantes y supergigantes rojas en fases finales de su evolución, de modo que vamos a ampliar nuestro diccionario de colores para acomodar estos nuevos tipos

In [18]:
colors = {'O':'#0000FF', 'B':'#CCCCFF', 'A':'#FFFFFF', 'F':'#FFFFB2', 'G':'#FFFF00', 'K':'#FFA500', 'M':'#FF0000',
          'W':'#000099' ,'S':'#B80000', 'C':'#780000'}

A continuación vamos a ver cual es la incidencia de los nuevos tipos W, S y C. En el gráfico a continuación veremos que su presencia en el BSC es muy escasa, y en el caso de las estrellas de carbono su posición en un esquema color-color es ocupando posiciones muy extremas (magnitudes muy elevadas, y por lo tanto baja temperatura y color rojo intenso).

In [19]:
fig, ax = plt.subplots(figsize=(10, 10))
ax.set_axis_bgcolor('0.6')

ax.grid()
ax.set_title(u'Color-color secuencia principal catálogo BSC')

ax.title.set_fontsize(20)
ax.set_xlabel('B-V')
ax.xaxis.label.set_fontsize(20)
ax.set_ylabel('U-B')
ax.yaxis.label.set_fontsize(20)

for cls in 'WSC':
    f = lambda s: s[0] == cls
    b = giants['SpType'].astype('string').map(f)
    x = giants[b]['B-V']
    y = giants[b]['U-B']
    c = colors[cls]
    ax.scatter(x, y, c = c, s=30,
               edgecolors='None', label = cls)
  
legend = ax.legend(scatterpoints=1,markerscale = 2, shadow=True)
frame = legend.get_frame()
frame.set_facecolor('0.90')

Dada la escasez de estrellas de las clases W, S y C en el catálogo BSC, una vez hemos visto donde se ubican en los gráficos color-color, vamos a omitir su representación, ya que su inclusión alteraria mucho la escala de los ejes. A continuación generaremos un gráfico en el que vamos a superponer las estrellas de la secuencia principal y las gigantes, con excepción de las de tipo W, S y C. Con el fin de diferenciarlas, las estrellas de la secuencia principal se van a representar ahora con puntos pequeños de color negro, mientras que las estrellas gigantes (tipos I, II, III y IV) se van a mostrar con los colores convencionales, y discos semitransparentes.

En el mismo gráfico vamos a superponer la línea recta correspondiente al diagrama color-color "ideal" que correspondería a un cuerpo negro. para ello utilizaremos las funciones definidas en el post anterior.

In [20]:
def B(wl,T):
    '''wl es un array de longitudes de onda con unidades de longitud
    T es una temperatura expresada en Kelvin
    el resultado es un array de valores de la radiancia espectral
    con unidades W/(m**2 * nm * sr)
    '''
    I = 2 * pq.constants.h * (pq.c)**2 / wl**5 *  \
        1 / (np.exp((pq.constants.h*pq.c \
        / (wl*pq.constants.k*T)).simplified)-1)
    return I.rescale(pq.watt/(pq.m**2 * pq.nm *pq.sr))

# Definición de las constantes del sistema fotométrico UBV
lambda_u = 365 * pq.nm
delta_u = 68 * pq.nm
lambda_b = 440 * pq.nm
delta_b = 98 * pq.nm
lambda_v = 550 * pq.nm
delta_v = 89 * pq.nm

# Cálculo de Cu-b
T = 42000*pq.kelvin
F = B(lambda_u, T) * delta_u/(B(lambda_b, T)* delta_b)
Cub = -1.19 + 2.5 * np.log10(F)

# Cálculo de Cb-v
F = B(lambda_b, T) * delta_b/(B(lambda_v, T)* delta_v)
Cbv = -0.33 + 2.5 * np.log10(F)

# Funciones para calcular los índices de color del cuerpo negro
def get_UB(T):
    F = B(lambda_u, T) * delta_u/(B(lambda_b, T)* delta_b)
    return -2.5 * np.log10(F) + Cub

def get_BV(T):
    F = B(lambda_b, T) * delta_b/(B(lambda_v, T)* delta_v)
    return -2.5 * np.log10(F) + Cbv

Y generamos el diagrama:

In [21]:
fig, ax = plt.subplots(figsize=(10, 10))
ax.set_axis_bgcolor('0.6')

#ax.set_xlim(-1, 6)
#ax.set_ylim(-1, 2.5)
ax.grid()
ax.set_title(u'Color-color secuencia principal y gigantes catálogo BSC')

ax.title.set_fontsize(20)
ax.set_xlabel('B-V')
ax.xaxis.label.set_fontsize(20)
ax.set_ylabel('U-B')
ax.yaxis.label.set_fontsize(20)


for cls in 'OBAFGKM':
    f = lambda s: s[0] == cls
    b = giants['SpType'].astype('string').map(f)
    x = giants[b]['B-V']
    y = giants[b]['U-B']
    c = colors[cls]
    ax.scatter(x, y, c = c, s=50, alpha = 0.5,
               edgecolors='None', label = cls)

x = main['B-V']
y = main['U-B']
ax.scatter(x, y, c = 'black', s=6, edgecolors='none',
           label='Main')    
    
legend = ax.legend(scatterpoints=1, markerscale = 2,shadow=True)
frame = legend.get_frame()
frame.set_facecolor('0.90')

T1 = 2500 * pq.kelvin
T2 = 25000 * pq.kelvin

BminusV_1 = get_BV(T1)
UminusB_1 = get_UB(T1)
BminusV_2 = get_BV(T2)
UminusB_2 = get_UB(T2)

# Basta con dos puntos para determinar la recta
ax.plot([BminusV_1, BminusV_2], [UminusB_1, UminusB_2], lw = 3, c='k') 
ax.text(0.9,0.4,'blackbody', fontsize=20, rotation = 35);

La figura anterior presenta características interesantes. La primera, que la radiación emitida por las estrellas se aparta en cierta medida de la correspondiente a un cuerpo negro (recordemos que esta es una línea recta). Las estrellas no son cuerpos negros ideales. La mejor aproximación es la correspondiente a las estrellas más calientes (O y B en la figura).

No obstante, la característica más sobresaliente es la brusca desviación de las estrellas de la clase A respecto de la línea de emisión del cuerpo negro. Si nos fijamos en la secuencia principal, esta describe una curva en forma de U invertida que se separa y luego vuelve a aproximarse a la línea del cuerpo negro. Esta característica ilustra el fenómeno conocido como "Balmer jump" o "salto de Balmer".

Básicamente, cuando la temperatura superficial de la estrella alcanza un valor crítico, lo que ocurre con las estrellas de clase espectral a partir de A0, se produce una intensa absorción de los fotones emitidos por el interior de la estrella en la región del ultravioleta. Dichos fotones son absorbidos por los átomos de hidrógeno en la superficie de la estrella excitando sus electrones, lo que se traduce en la aparición de las líneas de absorción de la serie de Balmer del hidrógeno en el espectro de estas estrellas. Esto implica que, como efecto de la absorción de estos fotones, se debilita la emisión de radiación por la estrella en la región del ultravioleta, y la magnitud U-B crece (recordemos que a menor flujo radiante recibido mayor magnitud), es decir, toma valores positivos mas deprisa de lo que le correspondería a un cuerpo negro, separandose bruscamente de dicha línea como se aprecia en la figura.

A partir de la clase espectral A5 la temperatura superficial de la estrella disminuye y hay menos átomos de hidrógeno en los niveles energéticos que les permiten capturar dichos fotones. Consecuentemente la radiación en el ultravioleta se recupera y el índice de color U-B deja de crecer. La curva se aproxima de nuevo, también bruscamente, a la línea correspondiente a la radiación de un cuerpo negro.

Este fenómeno está perfectamente explicado en el libro de Keith Robinson: Starlight: An Introduction to Stellar Physics for Amateurs

Diagramas color-color de las clases de luminosidad MK

Para terminar este post, ya un tanto largo, realizaremos un conjunto de diagramas color-color para representar el comportamiento en estos diagramas de las distintas clases de luminosidad del esquema MK

In [22]:
# Creación de un dataframe con solo estrellas clase IV
bIV = giants['SpType'].str.contains('IV')
giantsIV = giants[bIV]
giantsIV.shape
Out[22]:
(1274, 12)
In [23]:
# Creación de un dataframe con solo estrellas clase III
bIII = giants['SpType'].str.contains('III')
giantsIII = giants[bIII]
giantsIII.shape
Out[23]:
(3525, 12)
In [24]:
# Creación de un dataframe con solo estrellas clase II
bII = giants['SpType'].str.contains('II')
b = np.logical_and(bII, np.logical_not(bIII))
giantsII = giants[b]
giantsII.shape
Out[24]:
(287, 12)
In [25]:
# Creación de un dataframe con solo estrellas clase I
bI = giants['SpType'].str.contains('I')
b = np.logical_and(bI, np.logical_not(bII))
giantsI = giants[b]
giantsI.shape
Out[25]:
(1349, 12)
In [26]:
fig = plt.figure(figsize=(10,15))
ax1 = fig.add_subplot(321)
x = main['B-V']
y = main['U-B']
ax1.scatter(x, y, c = 'black', s=0.1)
ax1.set_xlim(-0.5, 2.5)
ax1.set_ylim(-1.5, 3)
ax1.set_xlabel('B-V')
ax1.set_ylabel('U-B')
ax1.grid()
ax1.set_title('Secuencia principal (MK V)');

ax2 = fig.add_subplot(322)
x = giantsIV['B-V']
y = giantsIV['U-B']
ax2.scatter(x, y, c = 'black', s=0.1)
ax2.set_xlim(-0.5, 2.5)
ax2.set_ylim(-1.5, 3)
ax2.set_xlabel('B-V')
ax2.set_ylabel('U-B')
ax2.grid()
ax2.set_title('Subgigantes (MK IV)');

ax3 = fig.add_subplot(323)
x = giantsIII['B-V']
y = giantsIII['U-B']
ax3.scatter(x, y, c = 'black', s=0.1)
ax3.set_xlim(-0.5, 2.5)
ax3.set_ylim(-1.5, 3)
ax3.set_xlabel('B-V')
ax3.set_ylabel('U-B')
ax3.grid()
ax3.set_title('Gigantes (MK III)');

ax4 = fig.add_subplot(324)
x = giantsII['B-V']
y = giantsII['U-B']
ax4.scatter(x, y, c = 'black', s=0.1)
ax4.set_xlim(-0.5, 2.5)
ax4.set_ylim(-1.5, 3)
ax4.set_xlabel('B-V')
ax4.set_ylabel('U-B')
ax4.grid()
ax4.set_title('Gigantes luminosas (MK II)');

ax5 = fig.add_subplot(325)
x = giantsI['B-V']
y = giantsI['U-B']
ax5.scatter(x, y, c = 'black', s=0.1)
ax5.set_xlim(-0.5, 2.5)
ax5.set_ylim(-1.5, 3)
ax5.set_xlabel('B-V')
ax5.set_ylabel('U-B')
ax5.grid()
ax5.set_title('Supergigantes (MK I)');

Esto es todo por ahora: ¡hasta el próximo post!