-->

Etiquetas

The color of celestial objects: part II

The color of celestial objects: part II

Author: Eduardo Martín Calleja

After my previous post on this blog, dedicated to the topic of the color of celestial bodies , this entry is its sequel, in which we will examine data from a star catalog containing photometric and spectroscopic data , and show them in color-color diagrams which will attempt to differentiate between the various star classes . That is, if the previous post introduced a number of theoretical concepts , we will now apply these concepts to real data.

Imports and references

The catalog that we will use is the "Bright Star Catalogue (BSC)", which includes virtually all stars visible to the naked eye, to magnitude 6.5 or brighter. Here's a first description

This catalog is available online here. To follow the exercises from this post we must download the three files that comprise the catalog:

  • bsc5.dat: The catalog
  • bsc5.readme: A catalog description
  • bsc5.notes: Catalog notes

It will also be useful this wikipedia article dedicated to the classification of the stars by the characteristics of its spectrum.

This post will be written as usual with the Ipython Notebook, toghether with some Python libraries which we are going to import:

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 45 generic x86_64 with debian jessie sid
numpy1.9.1
matplotlib1.4.2
quantities0.10.1
pandas0.15.2
Mon Feb 23 19:01:56 2015 CET

Reading data from the "Bright Star Catalogue" (BSC)

Once downloaded the three files which make up the BSC, We will read a number of fields and we'll import them into a Pandas dataframe. Just some remarks about this:

  • The structure of the entries in the BSC is detailed in bsc5.readme file, along with a brief description.
  • We are not going to import all the fields, but only those that we will use later.
  • As they are records made of fixed-width fields, we'll use the read_fwf() function from the Pandas library, which allows to provide a description of the starting position and length of each field.
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)

Let's see a sample of the data:

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

Color-color diagram of the main sequence

The ultimate purpose of this post is to make color-color diagrams of different types of stars, ie spectral classes, for which we will start with the stars on the main sequence. We will use the color indices from the UBV system, as these are those contained in the BSC.

Main sequence stars are the stars (which constitute approximately 80% of the stellar population) using hydrogen as fuel in their nuclear fusion processes.

To distinguish these stars from the rest we will use the BSC spectral type (the column of the dataframe with the name SpType). The BSC uses the Morgan-Keenan (MK) classification system, which includes a spectral class in the Harvard classification scheme (O B A F G K M, plus a numeral 0-9), to which it is added a classification based on the luminosity, which uses Roman numerals I-VI, plus occasionally a lower case "a" or "b"

Examples of the resulting classification are: "B5III", "A5Ia", "K0V" etc. In particular, the "V" means "main sequence", which is what will allow us to recognize the stars belonging to the main sequence.

Important remark: please note that the "Bright Star Catalogue" includes only the brightest stars, basically those that are visible to the naked eye, so that the number of stars of a given spectral class in the catalog is not in any way representative of its abundance in the universe. The fainter or more distant stars will not be represented in the BSC.

How many spectral types exist in the catalog?

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

Are there any missing spectral types (marked as NaN in Pandas)?

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

There are 14. Let's get rid of these entries in the dataframe

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

In the above summary we see that the data frame has 14 records less, and the SpType field lacks nonzero values.

Then we will select the stars belonging to the main sequence, creating a new dataframe which we will call main. These are those that have a luminosity class 'V' in the MK classification.

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

# Luminosity class "V", not ("IV" or "VI" or "VII"9
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)

Let's make a first check:

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

Now we want to check that the first letter of these spectral classes is in the sequence 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)

We prepare now a Python dictionary with the color codes that we will assign to each spectral class, which will go from blue (type O) to red (type M):

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

And we generate a color-color diagram with Python Matplotlib

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

ax.grid()
ax.set_title('Color-color plot: main sequence BSC catalog')

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')

We see that there are very few stars of class M. But this is because we are only considering those visible to the naked eye and they are not very bright!. In fact they are the most abundant by far.

Color-color diagram of the giant stars

Previously, we'll see if in the BSC catalog there are dwarf stars, meaning the ones corresponding to luminosity classes VI and VII

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

We see that there are no stars of these types in the BSC catalog, which was to be expected in a catalog of just the brightest stars in the sky. This means that in the SpType field there are no "VI" and VII" values. This fact will be taken into account when making the selection.

Let's then include in the graph the stars belonging to luminosity classes of the giant types:

  • I Supergiants
  • II Bright giants
  • III Normal giants
  • IV Subgiants

Let's see how many stars there are in these classes in the BSC:

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

And then see if among these are new classes in addition to the usual spectral types 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)

Indeed we see that there appear more exotic new spectral classes: W (bright blue stars that have mostly helium instead of hydrogen in their atmospheres), and carbon stars of types C and S: red giants and supergiants in the final stages of its evolution.

We will expand our dictionary of colors to accommodate these new types

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

Then let's see what is the impact of the new types W, S and C. In the chart below we see that its presence in the BSC is very scarce, and in the case of carbon stars its position in a color-color diagram is taking very extreme positions (very high magnitudes, and therefore low temperature and deep red color).

In [19]:
fig, ax = plt.subplots()
ax.set_axis_bgcolor('0.6')
ax.grid()
ax.set_title('Color-color plot: exotic classes BSC catalog')

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

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')

Given the shortage of stars of classes W, S and C in the BSC catalog, once we saw where they stand in the color-color graphics, we will omit its representation, as their inclusion would alter much the scale of the axes. Then we generate a graph in which we'll overlay the main sequence stars and giants, with the exception of type W, S and C. To differentiate, the main sequence stars will be represented now with black colored small dots, while giant stars (types I, II, III and IV) are displayed with conventional colors, and semitransparent disks.

In the same graph we are going to superimpose the straight line corresponding to the "ideal" color-color diagram, which would correspond to a black body. For this we will use the functions defined in the previous post

In [20]:
def B(wl,T):
    '''wl is an array of wavelengths with units of length
    T is a temperature in Kelvin
    the result is an array of spectral radiances
    with units 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))

#  Defining the UBV photometric system constants
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

# Function to calculate 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)

# Function to calculate Cb-v
F = B(lambda_b, T) * delta_b/(B(lambda_v, T)* delta_v)
Cbv = -0.33 + 2.5 * np.log10(F)

# Functions to calculate blackbody color indices
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

And now we generate the diagram:

In [21]:
fig, ax = plt.subplots(figsize=(10, 10))
ax.set_axis_bgcolor('0.6')
ax.grid()
ax.set_title('Color-color main sequence and giants BSC catalog')

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)

# Just two points are sufficient to determine the line
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);

The figure above presents interesting features. The first, that the radiation emitted by stars deviates somewhat from that corresponding to a black body. The stars are not perfect black bodies. The best fit is the one corresponding to the hottest stars (O and B in the figure).

However, the most striking feature is the sharp deviation of the class A stars from the line of the black body emission. If you look at the main sequence, this describes an inverted U-shaped curve that separates from and then approach again the black body line. This characteristic shows the phenomenon known as "Balmer jump"

Basically, when the surface temperature of the star reaches a critical value, which occurs with stars with spectral class from A0, there is a strong absorption of the photons emitted from the interior of the star in the ultraviolet region.

These photons are absorbed by the hydrogen atoms on the surface of the star exciting their electrons, resulting in the appearance of the absorption lines of the hydrogen Balmer series in the spectrum of these stars.

This implies that, as a result of the absorption of these photons, it weakens the emission of radiation from the star in the ultraviolet region, and the magnitude U-B grows (remember that the lower the radiant flux received, the greater the magnitude), ie takes values positive more quickly than would correspond to a black body, separating sharply from that line as shown in the figure.

From the A5 spectral class onwards, the surface temperature of the star decreases and there are fewer hydrogen atoms in energy levels that allow them to capture these photons. Consequently the ultraviolet radiation is recovered and the color index UB stops growing. The curve approaches again, abruptly too, the line corresponding to the black body radiation.

This phenomenon is very well explained in the book of Keith Robinson: Starlight: An Introduction to Stellar Physics for Amateurs

Color-color diagrams of the MK luminosity classes

To end this post, already a little long, we will make a set of color-color diagrams to represent the behavior in these diagrams of different luminosity classes of the MK scheme

In [22]:
# dataframe with only class IV stars
bIV = giants['SpType'].str.contains('IV')
giantsIV = giants[bIV]
giantsIV.shape
Out[22]:
(1274, 12)
In [23]:
# dataframe with only class III stars
bIII = giants['SpType'].str.contains('III')
giantsIII = giants[bIII]
giantsIII.shape
Out[23]:
(3525, 12)
In [24]:
# dataframe with only class II stars
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]:
# dataframe with only class I stars
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('main sequence (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('Subgiants (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('Giants (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('Bright giants (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('Supergiants (MK I)');

That's all for now. Until next post!

No hay comentarios:

Publicar un comentario