Stellar Temperatures from Spectra#

Katie Lester (Mount Holyoke College)

Description: A series of exercises building the intuition and practice for model comparison with data with \(\chi^2\) minimization, using stellar models and several (real) stellar spectra.

Intended Audience: Beginner to Intermediate Undergraduate

tags: stellar-spectroscopy,model-fitting, numpy, for-loops

Requirements: requirements.txt

Last Updated: July 23, 2024

Learning Objectives

  • learn to read in tabular data to Python

  • be able to calculate a chi-squared goodness of fit statistic

  • practice for loops and indexing

  • learn data visualization skills

  • Understand how stellar spectra change with effective temperature & how to quantify how well two datasets match.

Introduction#

In this activity, we’re going to look at spectra of some real stars and of computer models, compare stars of different spectral types, and finding the temperatures of our mystery stars.

The files for this activity contain the model spectra for different effective temperatures (“models.csv”) and the observed spectra (“Star#.csv”) for 3 real stars. The files have:

  • Wavelength – units of nm

  • Normalized Flux – the fluxes have been “continuum normalized”, meaning that the overall blackbody shape has been divided out. So the flux values vary from 0-1 in value, rather than normal erg/cm2/s/nm units.

You can download them here: models.csv | Star 1 | Star 2 | Star 3

Hint

Be sure to have your data files in the same directory as your code where you’re loading the data!

Part 1 - Model spectra#

Start by importing the python packages we’ll need by running the cell below.

# start by importing useful packages
import numpy as np
import matplotlib.pyplot as plt
from astropy.io import ascii
%matplotlib inline
from astropy.table import Table

Our model spectra correspond to effective temperatures of 4000, 6000, 8000 and 10,000 K. We will use the custom function below (“get_model”) to read in the model for a given temperature, then return the wavelength and flux of the model spectrum in datatable format (“model”).

Run the cell below so we can use this function.

def get_model(temperature):
    # input = effective temperature
    templist = [4000, 6000, 8000, 10000]
    if temperature not in templist:
        print(' Please choose a new temperature:')
        print('   possible values =', templist)
        pass
    else:
        data = ascii.read("models.csv")
        model = Table([data["wavelength"], data["flux_"+str(temperature)]])
        model.rename_column("flux_"+str(temperature), 'flux')
        return(model)

Part 2 - observed spectra#

Now, we’re going to compare the observed spectra of a real star to the models from Part I and determine this star’s spectral type (and surface temperature).

One way to determine the spectral type of Star 1 would be “by eye”, where you visually choose which model looks most similar to Star 1. A better way would be comparing the spectra using a chi-squared (\(\chi^2\)) goodness-of-fit statistic:

\[\chi^2 = \sum\frac{(data - model)^2}{model}\]

This statistic quantifies how different (or similar) two data sets are, where the “best fitting” model is going to have the lowest \(\chi^2\) value. Notice that this metric is computed as the sum over the normalized difference between every equivalent point in the model to the data; individual points with very large discrepancies can thus drive the \(\chi^2\) metric up considerably.

We’re going to calculate \(\chi^2\) value for each of the temperatures, and save these values in an array called “chi2”. This array will have the same size as the effective temperature array you made earlier.

First, we need to make an empty numpy array to store the \(\chi^2\) values. You can make a blank numpy array using:

x = np.zeros(length)

where “length” is the number of elements in the array.

Now we can find the minimum \(\chi^2\) value and find the best-fit temperature for Star 1.

Part 3 - New Mystery Stars#

Now we’ll move on to the other stars, repeating the process from Part II.

Concluding thoughts#

Question: How well did the model spectra fit the real stars’ spectra? Do you think our fitting code worked successfully?

Question: What’s one situation where our code would NOT be able to find the temperature of the star? Is there a way we could update our code / analysis to solve the problem?

Write out a discussion for these two questions

Bonus/Extra Credit: Array Manipulation#

In the exercises above, we used a for-loop to iterate over all the models in our library, comparing each to an individual stellar spectrum. Each model has the same length as the real spectrum (which is why the model-data array-wise subtraction worked). This means that it is in fact possible to compute our chi2 array for all models and a given star in one step, without using a for-loop.

You may have learned that to compute the point-wise, e.g., sum, multiplication, etc., between two arrays, they have to be the same shape. That’s not strictly true, however, due to the ability for numpy arrays to be broadcast. You’ve probably used this technique before actually! When we compute

a = np.array([1,2,3,4,5])
b = a - 2 
print(b)
array([-1,0,1,2,3])

the actual “array” subtraction is array([1,2,3,4,5]) - array([2,2,2,2,2]). Numpy is smart enough to extend our integer (2) to match the length of a to facilitate the subtraction. Sometimes, in more complex scenarios (like our spectra), we can be a bit more explicit and tell numpy how to broadcast our arrays to facilitate a single, vectorized computation.