Skip to content

Commit 309ff50

Browse files
authored
Merge pull request #371 from mkelley/calspec-sun-202304
Add CALSPEC model solar spectrum
2 parents 8950548 + 4ba3da1 commit 309ff50

File tree

3 files changed

+71
-43
lines changed

3 files changed

+71
-43
lines changed

docs/sbpy/calib.rst

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ Spectral Standards and Photometric Calibration (`sbpy.calib`)
55

66
sbpy's photometric calibration is based on spectra of the Sun and Vega. For example, they are used to convert between :ref:`reflectance, cross-section, and magnitude <reflectance-equivalencies>`, between :ref:`Afρ and spectral flux density <afrho-to-from-flux-density>`, and between :ref:`Vega-based and other magnitude systems <vega-magnitudes>`. sbpy has built-in spectra for each, and users may provide their own.
77

8-
The spectrum of `Bohlin (2014) <https://dx.doi.org/10.1088/0004-6256/147/6/127>`_ is the default and only built-in spectrum for Vega. It is distributed with sbpy. Four solar spectra are built-in:
8+
The spectrum of `Bohlin (2014) <https://dx.doi.org/10.1088/0004-6256/147/6/127>`_ is the default and only built-in spectrum for Vega. It is distributed with sbpy. Five solar spectra are built-in:
99

1010
* Castelli1996 - Castelli model from Colina et al. (1996).
1111
* E490_2014 - E490 (2014) standard.
1212
* E490_2014LR - A low resolution version of the E490 standard.
1313
* Kurucz1993 - Kurucz (1993) model.
14+
* calspec - R=5000, created by R. Bohlin from Kurucz Special Model
1415

15-
The E490 spectra are included with sbpy, and the Kurucz and Castelli spectra are downloaded as needed from `STScI's astronomical catalog <https://www.stsci.edu/hst/instrumentation/reference-data-for-calibration-and-tools/astronomical-catalogs>`_.
16+
The E490 spectra are included with sbpy, and the others are downloaded as needed from MAST's `Spectral Atlas Files for Synphot Software (REFERENCE-ATLASES) <https://archive.stsci.edu/hlsp/reference-atlases>`_ or STScI's `CALSPEC Database <https://www.stsci.edu/hst/instrumentation/reference-data-for-calibration-and-tools/astronomical-catalogs/calspec>`_.
1617

1718
Each star has a class for use within sbpy. The classes can be initialized with the default spectrum using :func:`~sbpy.calib.SpectralStandard.from_default`:
1819

@@ -32,9 +33,10 @@ The names of the built-in sources are stored as an internal array. They can be
3233
name description
3334
------------ -----------------------------------------------------------------
3435
Castelli1996 Castelli model, scaled and presented by Colina et al. (1996)
35-
E490_2014 E490-00a (2014) reference solar spectrum (Table 3)
36-
E490_2014LR E490-00a (2014) low resolution reference solar spectrum (Table 4)
37-
Kurucz1993 Kurucz (1993) model, scaled by Colina et al. (1996)
36+
E490_2014 E490-00a (2014) reference solar spectrum (Table 3)
37+
E490_2014LR E490-00a (2014) low resolution reference solar spectrum (Table 4)
38+
Kurucz1993 Kurucz (1993) model, scaled by Colina et al. (1996)
39+
calspec R=5000, created by R. Bohlin from Kurucz Special Model
3840
>>> sun = Sun.from_builtin('E490_2014LR')
3941
>>> print(sun)
4042
<Sun: E490-00a (2014) low resolution reference solar spectrum (Table 4)>

sbpy/calib/solar_sources.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ class SolarSpectra:
5252
'bibcode': '1996AJ....112..307C'
5353
}
5454

55+
calspec = {
56+
"filename": ("https://archive.stsci.edu/hlsps/reference-atlases/cdbs/"
57+
"current_calspec/sun_mod_001.fits"),
58+
"description": "R=5000, created by R. Bohlin from Kurucz Special Model",
59+
"bibcode": "2014PASP..126..711B"
60+
}
61+
5562

5663
class SolarPhotometry:
5764
"""Built-in solar photometry.

sbpy/calib/tests/test_sun.py

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,56 +10,64 @@
1010

1111
try:
1212
import scipy
13+
1314
HAS_SCIPY = True
1415
except ImportError:
1516
HAS_SCIPY = False
1617

1718

1819
class TestSun:
1920
def test___repr__(self):
20-
with solar_spectrum.set('E490_2014LR'):
21-
assert (repr(Sun.from_default()) ==
22-
('<Sun: E490-00a (2014) low resolution reference '
23-
'solar spectrum (Table 4)>'))
21+
with solar_spectrum.set("E490_2014LR"):
22+
assert repr(Sun.from_default()) == (
23+
"<Sun: E490-00a (2014) low resolution reference "
24+
"solar spectrum (Table 4)>"
25+
)
2426

2527
sun = Sun.from_array([1, 2] * u.um, [1, 2] * u.Jy)
26-
assert repr(sun) == '<Sun>'
28+
assert repr(sun) == "<Sun>"
2729

2830
def test_from_builtin(self):
29-
sun = Sun.from_builtin('E490_2014LR')
30-
assert sun.description == solar_sources.SolarSpectra.E490_2014LR['description']
31+
sun = Sun.from_builtin("E490_2014LR")
32+
assert (
33+
sun.description
34+
== solar_sources.SolarSpectra.E490_2014LR["description"]
35+
)
3136

3237
def test_from_builtin_unknown(self):
3338
with pytest.raises(UndefinedSourceError):
34-
Sun.from_builtin('not a solar spectrum')
39+
Sun.from_builtin("not a solar spectrum")
3540

3641
def test_from_default(self):
37-
with solar_spectrum.set('E490_2014LR'):
42+
with solar_spectrum.set("E490_2014LR"):
3843
sun = Sun.from_default()
39-
assert sun.description == solar_sources.SolarSpectra.E490_2014LR['description']
44+
assert (
45+
sun.description
46+
== solar_sources.SolarSpectra.E490_2014LR["description"]
47+
)
4048

4149
def test_call_single_wavelength(self):
42-
with solar_spectrum.set('E490_2014'):
50+
with solar_spectrum.set("E490_2014"):
4351
sun = solar_spectrum.get()
4452
f = sun(0.5555 * u.um)
4553
assert np.isclose(f.value, 1897)
4654

4755
def test_call_single_frequency(self):
48-
with solar_spectrum.set('E490_2014'):
56+
with solar_spectrum.set("E490_2014"):
4957
sun = solar_spectrum.get()
5058
f = sun(3e14 * u.Hz)
51-
assert np.isclose(f.value, 2.49484251e+14)
59+
assert np.isclose(f.value, 2.49484251e14)
5260

53-
@pytest.mark.skipif('not HAS_SCIPY')
61+
@pytest.mark.skipif("not HAS_SCIPY")
5462
def test_sun_observe_wavelength_array(self):
5563
from scipy.integrate import trapz
5664

57-
unit = 'W/(m2 um)'
65+
unit = "W/(m2 um)"
5866

5967
# compare Sun's rebinning with an integration over the spectrum
60-
sun = Sun.from_builtin('E490_2014')
68+
sun = Sun.from_builtin("E490_2014")
6169

62-
wave0 = sun.wave.to('um').value
70+
wave0 = sun.wave.to("um").value
6371
fluxd0 = sun.fluxd.to(unit).value
6472

6573
wave = np.linspace(0.35, 0.55, 6)
@@ -71,20 +79,21 @@ def test_sun_observe_wavelength_array(self):
7179
fluxd1 = np.zeros(len(wave))
7280
for i in range(len(wave)):
7381
j = (wave0 >= left_bins[i]) * (wave0 <= right_bins[i])
74-
fluxd1[i] = (trapz(fluxd0[j] * wave0[j], wave0[j]) /
75-
trapz(wave0[j], wave0[j]))
82+
fluxd1[i] = trapz(fluxd0[j] * wave0[j], wave0[j]) / trapz(
83+
wave0[j], wave0[j]
84+
)
7685

7786
fluxd2 = sun.observe(wave * u.um, unit=unit).value
7887

7988
assert np.allclose(fluxd1, fluxd2, rtol=0.005)
8089

8190
def test_filt_units(self):
8291
"""Colina et al. V=-26.75 mag, for zero-point flux density
83-
36.7e-10 ergs/s/cm2/Å.
92+
36.7e-10 ergs/s/cm2/Å.
8493
"""
85-
sun = Sun.from_builtin('E490_2014')
86-
V = bandpass('johnson v')
87-
weff, fluxd = sun.observe_bandpass(V, unit='erg/(s cm2 AA)')
94+
sun = Sun.from_builtin("E490_2014")
95+
V = bandpass("johnson v")
96+
weff, fluxd = sun.observe_bandpass(V, unit="erg/(s cm2 AA)")
8897
assert np.isclose(weff.value, 5502, rtol=0.001)
8998
assert np.isclose(fluxd.value, 183.94, rtol=0.0003)
9099

@@ -95,8 +104,8 @@ def test_filt_vegamag(self):
95104
agreement is good.
96105
97106
"""
98-
sun = Sun.from_builtin('E490_2014')
99-
V = bandpass('johnson v')
107+
sun = Sun.from_builtin("E490_2014")
108+
V = bandpass("johnson v")
100109
fluxd = sun.observe(V, unit=JMmag)
101110
assert np.isclose(fluxd.value, -26.75, atol=0.006)
102111

@@ -107,8 +116,8 @@ def test_filt_abmag(self):
107116
optical.
108117
109118
"""
110-
sun = Sun.from_builtin('E490_2014')
111-
V = bandpass('johnson v')
119+
sun = Sun.from_builtin("E490_2014")
120+
V = bandpass("johnson v")
112121
fluxd = sun.observe(V, unit=u.ABmag)
113122
assert np.isclose(fluxd.value, -26.77, atol=0.007)
114123

@@ -119,19 +128,19 @@ def test_filt_stmag(self):
119128
optical.
120129
121130
"""
122-
sun = Sun.from_builtin('E490_2014')
123-
V = bandpass('johnson v')
131+
sun = Sun.from_builtin("E490_2014")
132+
V = bandpass("johnson v")
124133
fluxd = sun.observe(V, unit=u.STmag)
125134
assert np.isclose(fluxd.value, -26.76, atol=0.003)
126135

127136
def test_filt_solar_fluxd(self):
128-
with solar_fluxd.set({'V': -26.76 * VEGAmag}):
137+
with solar_fluxd.set({"V": -26.76 * VEGAmag}):
129138
sun = Sun(None)
130-
fluxd = sun.observe('V', unit=VEGAmag)
139+
fluxd = sun.observe("V", unit=VEGAmag)
131140
assert np.isclose(fluxd.value, -26.76)
132141

133142
def test_meta(self):
134-
sun = Sun.from_builtin('E490_2014')
143+
sun = Sun.from_builtin("E490_2014")
135144
assert sun.meta is None
136145

137146
@pytest.mark.remote_data
@@ -143,8 +152,8 @@ def test_kurucz_nan_error(self):
143152
NaNs in Kurucz file should not affect this calculation.
144153
145154
"""
146-
sun = Sun.from_builtin('Kurucz1993')
147-
V = bandpass('johnson v')
155+
sun = Sun.from_builtin("Kurucz1993")
156+
V = bandpass("johnson v")
148157
fluxd = sun.observe(V, unit=u.ABmag)
149158
assert np.isclose(fluxd.value, -26.77, atol=0.005)
150159

@@ -163,15 +172,25 @@ def test_castelli96(self):
163172
2022-06-05: sbpy calculates 184.5 ergs/s/cm^2/A; agreement within 0.2%
164173
"""
165174

166-
sun = Sun.from_builtin('Castelli1996')
167-
V = bandpass('johnson v')
168-
fluxd = sun.observe(V, unit='erg/(s cm2 AA)')
175+
sun = Sun.from_builtin("Castelli1996")
176+
V = bandpass("johnson v")
177+
fluxd = sun.observe(V, unit="erg/(s cm2 AA)")
169178
assert np.isclose(fluxd.value, 184.2, rtol=0.002)
170179

180+
@pytest.mark.remote_data
181+
def test_calspec(self):
182+
"""Verify CALSPEC solar model calibration."""
183+
184+
sun = Sun.from_builtin("calspec")
185+
V = bandpass("johnson v")
186+
fluxd = sun.observe(V, unit=VEGAmag)
187+
assert np.isclose(fluxd.value, -26.75, rtol=0.002)
188+
171189
def test_show_builtin(self, capsys):
172190
Sun.show_builtin()
173191
captured = capsys.readouterr()
174192
sources = inspect.getmembers(
175-
Sun._sources, lambda v: isinstance(v, dict))
193+
Sun._sources, lambda v: isinstance(v, dict)
194+
)
176195
for k, v in sources:
177196
assert k in captured.out

0 commit comments

Comments
 (0)