Skip to content

mors.spectrum

spectrum

Module for deriving stellar spectra from band-integrated fluxes

bands_ascending = ['xr', 'e1', 'e2', 'uv', 'pl'] module-attribute

bands_limits = {'xr': [0.517, 12.5], 'e1': [10.0, 32.0], 'e2': [32.0, 92.0], 'uv': [92.0, 400.0], 'pl': [400.0, 1000000000.0], 'bo': [0.001, 1000000000.0]} module-attribute

log = logging.getLogger('fwl.' + __name__) module-attribute

Spectrum()

Source code in src/mors/spectrum.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def __init__(self):

    # Flags
    self.loaded = False

    # Arrays (scaled to 1 AU)
    self.nbins = 0      # Number of bins
    self.wl = []        # Wavelength bins [nm]
    self.fl = []        # Flux bins [erg s-1 cm-2 nm-1]
    self.binwidth = []  # Width of wavelength bins [nm]

    # Extensions
    self.ext_long   = -1 # Index where Planck function extension starts
    self.ext_short  = -1 # Index where shortwave extension starts

    # Integrated fluxes for each band [erg s-1 cm-2]
    self.fl_integ = {}
    for b in bands_limits.keys():
        self.fl_integ[b] = 0.0

CalcBandFluxes()

Calculate integrated fluxes for each band.

Integrated fluxes will have units of [erg s-1 cm-2] scaled to 1 AU.

Source code in src/mors/spectrum.py
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def CalcBandFluxes(self):
    """Calculate integrated fluxes for each band.

    Integrated fluxes will have units of [erg s-1 cm-2] scaled to 1 AU.
    """

    # For each spectral band
    idxs = []
    i_lo = 0
    for b in bands_ascending:

        # Get band indicies
        for i in range(i_lo, self.nbins, 1):
            wb = WhichBand(self.wl[i])

            # Out of range
            if wb == None:
                continue

            if b in wb:
                # In current band
                idxs.append(i)
            else:
                # End of band
                i_lo = i
                break

        # With idxs defined, integrate over band
        band_wl = self.wl[idxs]
        band_fl = self.fl[idxs]
        self.fl_integ[b] = np.trapezoid(band_fl,band_wl)

        # Reset idxs
        idxs = []

    # For bolometric "band"
    self.fl_integ["bo"] = np.trapezoid(self.fl,self.wl)

    return self.fl_integ

ExtendPlanck(Teff, R_star, wl_max)

Extend spectrum to longer wavelengths using planck function.

Parameters:

Name Type Description Default
Teff float

Effective temperature of star

required
R_star float

Radius of star [m]

required
wl_max float

New maximum wavelength [nm]

required
Source code in src/mors/spectrum.py
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
def ExtendPlanck(self, Teff:float, R_star:float, wl_max:float):
    """Extend spectrum to longer wavelengths using planck function.

    Parameters
    ----------
    Teff : float
        Effective temperature of star
    R_star : float
        Radius of star [m]
    wl_max : float
        New maximum wavelength [nm]
    """


    # Already extended
    if wl_max < self.wl[-1]:
        return

    # Calc wavelength extension
    wl_ext = np.logspace(np.log10(self.wl[-1]), np.log10(wl_max), 300)[1:]

    # Evalulate planck function
    fl_ext = PlanckFunction_surf(wl_ext, Teff)

    # Scale to 1 AU
    fl_ext = ScaleTo1AU(fl_ext, R_star)

    # Update Spectrum object data
    self.ext_long = len(self.wl)
    spec_wl = np.concatenate((self.wl, wl_ext))
    spec_fl = np.concatenate((self.fl, fl_ext))

    # Store
    self.LoadDirectly(spec_wl, spec_fl)

ExtendShortwave(wl_min)

Extend spectrum to shorter wavelengths using constant value.

Parameters:

Name Type Description Default
wl_min float

New minimum wavelength [nm]

required
Source code in src/mors/spectrum.py
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
def ExtendShortwave(self, wl_min:float):
    """Extend spectrum to shorter wavelengths using constant value.

    Parameters
    ----------
    wl_min : float
        New minimum wavelength [nm]
    """

    # Already extended
    if wl_min > self.wl[0]:
        return

    # Calc wavelength extension
    wl_ext = np.logspace(np.log10(wl_min), np.log10(self.wl[0]), 200)[:-1]

    # Evalulate planck function
    fl_ext = np.ones(np.shape(wl_ext)) * self.fl[0]

    # Update Spectrum object data
    self.ext_short = len(wl_ext)
    spec_wl = np.concatenate((wl_ext,self.wl))
    spec_fl = np.concatenate((fl_ext,self.fl))

    # Store
    self.LoadDirectly(spec_wl, spec_fl)

LoadDirectly(spec_wl, spec_fl)

Store spectral data in object.

Scaled to 1 AU.

Parameters:

Name Type Description Default
spec_wl ndarray

Array of wavelengths [nm]

required
spec_fl ndarray

Array of fluxes [erg s-1 cm-2 nm-1]

required
Source code in src/mors/spectrum.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
def LoadDirectly(self, spec_wl:np.ndarray, spec_fl:np.ndarray):
    """Store spectral data in object.

    Scaled to 1 AU.

    Parameters
    ----------
    spec_wl : np.ndarray
        Array of wavelengths [nm]
    spec_fl : np.ndarray
        Array of fluxes [erg s-1 cm-2 nm-1]
    """


    # Check length
    if len(spec_wl) != len(spec_fl):
        raise Exception("Stellar spectrum size mismatch (%d and %d)"%(len(spec_wl), len(spec_fl)))
    if len(spec_wl) < 10:
        raise Exception("Stellar spectrum size too small (%d bins)"%len(spec_wl))

    # Check reversal (should be wl ascending)
    if spec_wl[4] < spec_wl[0]:
        spec_wl = spec_wl[::-1]
        spec_fl = spec_fl[::-1]

    # Replace NaN and zero values
    for i in range(len(spec_fl)):
        if not np.isfinite(spec_fl[i]):
            spec_fl[i] = 0.0
        spec_fl[i] = max(spec_fl[i], 1e-20)

    # Calculate bin width
    binwidth_wl = spec_wl[1:] - spec_wl[0:-1]

    # Store
    self.nbins = len(spec_wl)
    self.wl = spec_wl
    self.fl = spec_fl
    self.binwidth = np.array(binwidth_wl, dtype=float)

    self.loaded = True

    return self

LoadTSV(fp)

Load stellar spectrum from TSV file into memory.

Scaled to 1 AU. File should be whitespace delimited with units of [nm] and [erg s-1 cm-2 nm-1].

Parameters:

Name Type Description Default
fp str

Path to file

required
Source code in src/mors/spectrum.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
def LoadTSV(self, fp:str):
    """Load stellar spectrum from TSV file into memory.

    Scaled to 1 AU. File should be whitespace delimited with
    units of [nm] and [erg s-1 cm-2 nm-1].

    Parameters
    ----------
    fp : str
        Path to file
    """

    log.debug("Loading stellar spectrum from TSV file")

    # Check path
    fp = os.path.abspath(fp)
    if not os.path.isfile(fp):
        raise Exception("Cannot find TSV file at '%s'"%fp)

    # Load file
    spec_data = np.loadtxt(fp).T
    spec_wl = spec_data[0]
    spec_fl = spec_data[1]

    # Process data
    spec_wl = np.array(spec_wl, dtype=float)
    spec_fl = np.array(spec_fl, dtype=float)

    # Store
    self.LoadDirectly(spec_wl, spec_fl)

    self.loaded = True

    return self

WriteTSV(fp)

Write spectrum to file(s) on disk.

Parameters:

Name Type Description Default
fp str

Path to file

required
Source code in src/mors/spectrum.py
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
def WriteTSV(self, fp:str):
    """Write spectrum to file(s) on disk.

    Parameters
    ----------
    fp : str
        Path to file
    """

    log.debug("Writing stellar spectrum to TSV file")

    fp = os.path.abspath(fp)
    X = np.array([self.wl,self.fl]).T
    header = "WL(nm)\t Flux(ergs/cm**2/s/nm)    Stellar flux (1 AU)"

    np.savetxt(fp, X, header=header,fmt='%1.4e',delimiter='\t')

    return fp

PlanckFunction_surf(wl, Teff)

Returns the planck fluxes evaluated at the wavelength array

Parameters:

Name Type Description Default
wl ndarray

Wavelength array [nm]

required
Teff float

Effective temperature of the object

required

Returns:

Name Type Description
yp float

Flux at stellar surface [erg s-1 cm-2 nm-1]

Source code in src/mors/spectrum.py
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
def PlanckFunction_surf(wl:np.ndarray, Teff:float):
    """Returns the planck fluxes evaluated at the wavelength array

    Parameters
    ----------
    wl : np.ndarray
        Wavelength array [nm]
    Teff : float
        Effective temperature of the object

    Returns
    -------
    yp : float
        Flux at stellar surface [erg s-1 cm-2 nm-1]
    """

    yp = np.zeros(np.shape(wl))
    for i,x in enumerate(wl):
        lam = x * 1.0e-9  # nm -> m

        # Calculate planck function value [W m-2 sr-1 m-1]
        # http://spiff.rit.edu/classes/phys317/lectures/planck.html
        yp[i] = 2.0 * const.h_SI * const.c_SI**2.0 / lam**5.0   *   1.0 / ( np.exp(const.h_SI * const.c_SI / (lam * const.k_SI * Teff)) - 1.0)

        # Integrate solid angle (hemisphere), convert units
        yp[i] = yp[i] * np.pi * 1.0e-9 # [W m-2 nm-1]
        yp[i] = yp[i] * 1000.0 # [erg s-1 cm-2 nm-1]

    return yp

ScaleTo1AU(fl, R_star)

Scale spectrum from stellar surface to 1AU

Parameters:

Name Type Description Default
fl ndarray

Spectrum at surface

required
R_star float

Star radius in [m]

required

Returns:

Name Type Description
fl_1AU ndarray

Flux at 1 AU (same units as fl)

Source code in src/mors/spectrum.py
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
def ScaleTo1AU(fl:np.ndarray, R_star:float):
    """Scale spectrum from stellar surface to 1AU

    Parameters
    ----------
    fl : np.ndarray
        Spectrum at surface
    R_star : float
        Star radius in [m]

    Returns
    -------
    fl_1AU : np.ndarray
        Flux at 1 AU (same units as `fl`)
    """

    return fl * (R_star/const.AU_SI)**2

ScaleToSurf(fl, R_star)

Scale spectrum from 1 AU to stellar surface

Parameters:

Name Type Description Default
fl ndarray

Spectrum at 1 AU

required
R_star float

Star radius in [m]

required

Returns:

Name Type Description
fl_surf ndarray

Flux at stellar surface (same units as fl)

Source code in src/mors/spectrum.py
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
def ScaleToSurf(fl:np.ndarray, R_star:float):
    """Scale spectrum from 1 AU to stellar surface

    Parameters
    ----------
    fl : np.ndarray
        Spectrum at 1 AU
    R_star : float
        Star radius in [m]

    Returns
    -------
    fl_surf : np.ndarray
        Flux at stellar surface (same units as `fl`)
    """

    return fl * (const.AU_SI/R_star)**2

WhichBand(wl)

Determine which band(s) this wavelength is inside of

Parameters:

Name Type Description Default
wl float

Wavelength to query [nm]

required

Returns:

Name Type Description
bands list | None

List of band names (strings) which this band is inside of. Bands can overlap, so this list may have a length greater than one. If wl is outside all bandpasses, then this value is None.

Source code in src/mors/spectrum.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def WhichBand(wl:float):
    """Determine which band(s) this wavelength is inside of

    Parameters
    ----------
    wl : float
        Wavelength to query [nm]

    Returns
    -------
    bands : list | None
        List of band names (strings) which this band is inside of. Bands
        can overlap, so this list may have a length greater than one.
        If `wl` is outside all bandpasses, then this value is None.

    """

    # Find band, returning when found
    bands = []
    for b in bands_ascending:
        if bands_limits[b][0] <= wl < bands_limits[b][1]:
            bands.append(b)

    # Not found...
    if len(bands) == 0:
        return None

    # Else...
    return bands