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:
%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
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.
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)]
labels =['HR', 'Name', 'HD', 'SAO', 'IRflag', 'Multiple', 'VarID',
'GLON', 'GLAT', 'Vmag', 'B-V', 'U-B', 'SpType']
bsc = pd.read_fwf('../datos/bsc5.dat', header= None, colspecs=colspecs,
names=labels, index_col=0)
Let's see a sample of the data:
bsc.head()
bsc.tail()
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?
bsc['SpType'].nunique()
Are there any missing spectral types (marked as NaN in Pandas)?
pd.isnull(bsc['SpType']).sum()
There are 14. Let's get rid of these entries in the dataframe
b = pd.notnull(bsc['SpType'])
bsc = bsc[b]
bsc.info()
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.
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
Let's make a first check:
main[0:10]
Now we want to check that the first letter of these spectral classes is in the sequence O, B, A, F, G, K, M
main['SpType'].map(lambda s:s[0]).unique()
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):
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
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
b = bsc['SpType'].str.contains('VI')
bsc[b].shape
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:
b = bsc['SpType'].str.contains('I')
giants = bsc[b]
giants.shape
And then see if among these are new classes in addition to the usual spectral types O, B, A, F, G, K, M
giants['SpType'].map(lambda s:s[0]).unique()
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
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).
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
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:
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
# dataframe with only class IV stars
bIV = giants['SpType'].str.contains('IV')
giantsIV = giants[bIV]
giantsIV.shape
# dataframe with only class III stars
bIII = giants['SpType'].str.contains('III')
giantsIII = giants[bIII]
giantsIII.shape
# 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
# 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
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