Light Sources¶
The rainbowconnection
allows us to create different light sources. These sources can have very different spectra, but all represent spherically symmetrical emission. Here, we walk through some of the basic properties and functionality of these light sources.
from rainbowconnection import Sun, Star, Thermal, Incandescent, Sodium
import matplotlib.pyplot as plt, numpy as np
import astropy.units as u
Basics of Spectrum
Objects¶
Let's start by creating a Spectrum
object to represent the Sun, and then walk through a few of its basic methods.
s = Sun()
Above we created an object containing the spectrum of the Sun. If we run a notebook cell containing just the name of that variable, it will display a default representation of it, which in this case is just the name "Sun"
.
s
Sun
Make simple spectrum plots.¶
The .plot()
method makes a simple summary plot of the spectrum. By default, it adds a reference rainbow along the top of the plot, and sets the color of the line to match the estimated visual color of the source.
s.plot();
Retrieve wavelength and spectrum arrays¶
All Spectrum
objects have a .wavelength
attribute that contains the default wavelengths at which the spectrum is defined and a .spectrum()
method that (with no inputs) returns the spectrum at those default wavelengths. Both have astropy.units
attached to them; these units ensure consistency among all the physical quantities we're using.
s.wavelength
s.spectrum()
You can store these arrays in variables, do math with them, or build whatever you want from them. Let's plot them, approximately reproducing part of the figure above.
default_wavelength = s.wavelength
default_spectrum = s.spectrum()
plt.plot(default_wavelength, default_spectrum);
Because you might want to know the spectrum at a different set of wavelengths than the default one, the .spectrum
function can also accept a grid of wavelengths as an input argument, to return the spectrum at those wavelengths, as in the following. The wavelengths need to have astropy
units associated with them to help make sure we don't make any accidental unit mistakes. In the code below, let's get the spectrum for just the visible light range, and overplot it on the default wavelengths.
my_wavelength = np.linspace(400, 700, 50)*u.nm
my_spectrum = s.spectrum(my_wavelength)
plt.plot(default_wavelength, default_spectrum)
plt.plot(my_wavelength, my_spectrum, linewidth=3)
plt.xlabel(f"Wavelength ({my_wavelength.unit})")
plt.ylabel(f"Spectrum ({my_spectrum.unit})");
Integrate over wavelength¶
The .integrate
method will integrate the spectrum over specified wavelength limits, doing a numerical integral with the values in the array. If no wavelengths are specified, or if the limits go from 0 to infinity, the integral will be performed over the whole wavelength range.
If $S_\lambda$ is our original spectrum in units like $\mathrm{W~nm^{-1}}$, then doing the integral over wavelength will get rid of the units of $\mathrm{nm^{-1}}$, leaving just $\mathrm{W}$.
s.integrate(400 * u.nm, 600 * u.nm)
s.integrate()
Different Light Sources¶
Sun
is just one example of a Spectrum
object. We can create lots of different kinds of these objects to represent different kinds of light sources, and all of them will inherit the above methods. Here are a few examples of available sources.
t = Thermal(teff=7000 * u.K, radius=1 * u.mm)
t.plot();
i = Incandescent()
i.plot();
na = Sodium(power=12 * u.W)
na.plot();
st = Star(teff=3000*u.K, radius=0.2*u.Rsun, mass=0.2*u.Msun)
st.plot();
Convert from luminosity to flux¶
We can use the .at()
method to normalize light sources to be viewed at a particular distance. Most light sources start off expressed in units of luminosity ($W$), but sources viewed .at
some distance will have units of flux ($W/m^2$).
solarconstant = Sun().at(1 * u.AU)
solarconstant.integrate().to("W/m**2")
Plotting Light Sources¶
We can plot or visualize Spectrum
objects in different ways. We've already seen the .plot()
method, which also accepts various keyword arguments. The plots are generate with standard matplotlib.pyplot
tools, so common commands can be used to modify plots that have been created.
s.plot();
s.plot(rainbow=False, color="aquamarine", linewidth=3);
To include multiple spectra on the same plot, we can catch the axes
object returned by each plotting command and feed it in as a keyword argument to future plots. For example, we might want to directly compare the Sun's spectrum to a Planck approximation:
t = Thermal(5800 * u.K, 1 * u.Rsun)
ax = t.plot(wavelength=s.wavelength, color="gray", linestyle="--")
s.plot(ax=ax, color="hotpink")
plt.xscale("log")
plt.yscale("log");
Or, we might want to compare the Planck thermal emission spectra of sources with different temperatures.
ax = None
for T in np.arange(1000, 20000, 1000) * u.K:
t = Thermal(T)
ax = t.plot(ax)
plt.yscale("log")
plt.xscale("log")
plt.xlim(100, 1000)
plt.ylim(1e12, 1e28);
We can plot the spectrum with a rainbow included filling in the space under the curve, to directly visualize the amount of visible light of particular colors.
s.plot_as_rainbow();
Or we can plot as the spectrum that would be seen visually through a slit spectroscope. In these visualizations, the y-axis is meaningless; all that matters is the brightness at each wavelength relative to the reference rainbow above (which represents approximately uniform brightness across wavelength).
s.plot_as_slit_rainbow();
We can also integrate the spectrum into RGB (= red, green, blue) wavelength bins, crudely approximating what many people can detect with their eyes (except folks who experience some kinds of color-blindness or amazing magical tetrachromacy).
s.plot_rgb();
To access the RGB color of a source, for plotting purposes, use the .to_color()
method. It will return three numbers, representing the relative amounts of red, green, and blue intensity needed to represent the color of this light source to the human eye.
s.to_color()
array([ 1. , 0.96032747, 0.94778801])
With these tools, we can plot the Stefan-Boltzmann law using the raw $\sigma T^4$ equation and this more complicated Spectrum
objects (which we can also use to figure out the apparent color of each temperature.).
# plot the Stefan-Boltzman Law
temperatures = np.arange(1000, 15000, 500) * u.K
plt.plot(temperatures, 5.67e-8 * temperatures**4, zorder=-1, color="gray")
# plot individual fluxes, with colors
for T in temperatures:
t = Thermal(T, radius=1 * u.mm).at(1 * u.mm)
plt.scatter(t.teff, t.integrate(), color=t.to_color(), edgecolor="black", s=100)
# tidy up the plot
plt.xlabel("Temperature (K)")
plt.ylabel("Surface Flux (W/m$^2$)");