├── .gitattributes ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── AUTHORS.rst ├── CMakeLists.txt ├── LICENSE.rst ├── README.rst ├── demos ├── .gitignore ├── dao69.py ├── feature_demo.py ├── plot_sdss_2mass_transmission.py └── specbymass.py ├── docs ├── .gitignore ├── Makefile ├── _static │ └── pyfsps_logo.svg ├── _templates │ ├── sidebarintro.html │ └── sidebarlogo.html ├── _themes │ ├── LICENSE │ ├── README.rst │ ├── dfm │ │ ├── layout.html │ │ ├── localtoc.html │ │ ├── relations.html │ │ ├── static │ │ │ └── flasky.css_t │ │ └── theme.conf │ └── flask_theme_support.py ├── conf.py ├── filter_table.rst ├── filters.rst ├── filters_api.rst ├── index.rst ├── installation.rst └── stellarpop_api.rst ├── pyproject.toml ├── requirements.txt ├── scripts └── fsps_filter_table.py ├── src └── fsps │ ├── CMakeLists.txt │ ├── __init__.py │ ├── filters.py │ ├── fsps.f90 │ ├── fsps.py │ └── sps_home.py ├── tests ├── options.py ├── simple.py └── tests.py └── tools └── f2py_include.py /.gitattributes: -------------------------------------------------------------------------------- 1 | .github/* export-ignore 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | startup.sh 3 | *.o 4 | *.so 5 | *.mod 6 | *.pyc 7 | *.pyf 8 | build 9 | src/fsps/_fsps* 10 | dist 11 | *.egg-info 12 | .cache 13 | env 14 | fsps_version.py 15 | .vscode 16 | *.dll 17 | .libs 18 | demos/figures 19 | .mesonpy* 20 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/fsps/libfsps"] 2 | path = src/fsps/libfsps 3 | url = https://github.com/cconroy20/fsps 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: "v5.0.0" 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | exclude_types: [json, binary] 8 | - id: check-yaml 9 | - repo: https://github.com/psf/black 10 | rev: "25.1.0" 11 | hooks: 12 | - id: black-jupyter 13 | - repo: https://github.com/astral-sh/ruff-pre-commit 14 | rev: "v0.11.11" 15 | hooks: 16 | - id: ruff 17 | args: [--fix, --exit-non-zero-on-fix] 18 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | submodules: 4 | include: all 5 | 6 | build: 7 | os: ubuntu-20.04 8 | apt_packages: 9 | - gcc 10 | - g++ 11 | - gfortran 12 | tools: 13 | python: "3.10" 14 | 15 | python: 16 | install: 17 | - method: pip 18 | path: . 19 | 20 | sphinx: 21 | builder: dirhtml 22 | configuration: docs/conf.py 23 | fail_on_warning: true 24 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Python-FSPS is developed by (alphabetical by last name): 2 | 3 | * `Dan Foreman-Mackey `_ 4 | * `Ben Johnson `_ 5 | * `Jonathan Sick `_ 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17.2...3.26) 2 | project(${SKBUILD_PROJECT_NAME} LANGUAGES C Fortran) 3 | 4 | find_package( 5 | Python 6 | COMPONENTS Interpreter Development.Module NumPy 7 | REQUIRED) 8 | 9 | # Find the f2py headers 10 | execute_process( 11 | COMMAND "${PYTHON_EXECUTABLE}" 12 | "${CMAKE_CURRENT_SOURCE_DIR}/tools/f2py_include.py" 13 | OUTPUT_VARIABLE F2PY_INCLUDE_DIR 14 | OUTPUT_STRIP_TRAILING_WHITESPACE) 15 | 16 | add_subdirectory(src/fsps) 17 | -------------------------------------------------------------------------------- /LICENSE.rst: -------------------------------------------------------------------------------- 1 | Copyright 2013-2023 Python-FSPS developers. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | **THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.** 19 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://github.com/dfm/python-fsps/workflows/Tests/badge.svg 2 | :target: https://github.com/dfm/python-fsps/actions?query=workflow%3ATests 3 | 4 | **Read the documentation:** 5 | `python-fsps.readthedocs.io `_ 6 | 7 | If you use this code, follow the citation requirements `on the FSPS 8 | homepage `_ and reference 9 | these Python bindings: 10 | 11 | .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.591505.svg 12 | :target: https://doi.org/10.5281/zenodo.591505 13 | -------------------------------------------------------------------------------- /demos/.gitignore: -------------------------------------------------------------------------------- 1 | *.fits 2 | -------------------------------------------------------------------------------- /demos/dao69.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import absolute_import, division, print_function, unicode_literals 5 | 6 | import matplotlib.pyplot as pl 7 | import numpy as np 8 | import pyfits 9 | 10 | import fsps 11 | 12 | # Measurements of cluster parameters. 13 | tage = 10.0 ** (8.04 - 9) 14 | logmass = 4.09 15 | dist_mod = 24.5 16 | 17 | # Set up the stellar population model. 18 | sp = fsps.StellarPopulation(imf_type=2, dust_type=1, mwr=3.1, dust2=0.3) 19 | 20 | # The measured magnitudes from the literature. 21 | data = {"wfc3_f160w": 16.386, "wfc3_f275w": 17.398, "wfc_acs_f814w": 17.155} 22 | 23 | # There are also a few filters that we have data for but aren't included in 24 | # the standard FSPS install: 25 | # "F110W": 16.877, 26 | # "F336W": 17.349, 27 | # "F475W": 17.762, 28 | 29 | # Load the observed spectrum. 30 | f = pyfits.open("DAO69.fits") 31 | obs_spec = np.array(f[0].data, dtype=float) 32 | f.close() 33 | 34 | obs_spec /= 5e-20 35 | 36 | # The observed wavelength grid in the data is magically this: 37 | obs_lambda = np.arange(0, 4540) * 1.2 + 3700 38 | 39 | # Compute the model magnitudes. 40 | for b, v in data.iteritems(): 41 | print(b, v, sp.get_mags(zmet=20, tage=tage, band=b) - 2.5 * logmass + dist_mod) 42 | 43 | # Compute the model spectrum in ``L_sun / A``. 44 | lam, spec = sp.get_spectrum(zmet=20, tage=tage, peraa=True) 45 | spec *= 3.839e33 * 10.0 ** (logmass - dist_mod / 2.5) 46 | 47 | f = 1.0 # obs_spec[0, obs_lambda < 5000.][-1] / spec[lam < 5000.][-1] 48 | print(obs_spec[0, obs_lambda < 5000.0][-1] / spec[lam < 5000.0][-1]) 49 | 50 | pl.loglog(obs_lambda, obs_spec[0], "k") 51 | pl.loglog(lam, spec * f, "r") 52 | 53 | pl.xlim(3700, 7000) 54 | # pl.ylim(10 ** 3, 10 ** 4.5) 55 | pl.savefig("spectrum.png") 56 | -------------------------------------------------------------------------------- /demos/feature_demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """feature_demo.py - This script creates a set of PDFs that illustrate the 5 | effect on the SED of successively turning on various options or changing the 6 | value of some variables. 7 | """ 8 | 9 | import os 10 | 11 | import matplotlib.pyplot as pl 12 | from matplotlib.backends.backend_pdf import PdfPages 13 | 14 | import fsps 15 | 16 | 17 | def makefig(sps, tage=13.7, oldspec=None, **plotkwargs): 18 | w, spec = sps.get_spectrum(tage=tage) 19 | fig, ax = pl.subplots() 20 | if oldspec is not None: 21 | ax.plot(w, oldspec / w * 1e19, color="gray", linewidth=2, alpha=0.5) 22 | ax.plot(w, spec / w * 1e19, "C2", linewidth=2) 23 | return fig, ax, spec 24 | 25 | 26 | def prettify(fig, ax, label=None): 27 | ax.set_xlim(0.9e3, 1e6) 28 | ax.set_xscale("log") 29 | ax.set_ylim(0.01, 2) 30 | # ax.set_yscale('log') 31 | ax.set_xlabel(r"rest-frame $\lambda$ ($\AA$)", fontsize=20) 32 | ax.set_ylabel(r"$\lambda \, f_\lambda$", fontsize=20) 33 | ax.tick_params(axis="both", which="major", labelsize=16) 34 | if label is not None: 35 | ax.text(0.63, 0.85, label, transform=ax.transAxes, fontsize=16) 36 | 37 | fig.tight_layout() 38 | return fig, ax 39 | 40 | 41 | if __name__ == "__main__": 42 | pl.rc("text", usetex=True) 43 | pl.rc("font", family="serif") 44 | pl.rc("axes", grid=False) 45 | pl.rc("xtick", direction="in") 46 | pl.rc("ytick", direction="in") 47 | pl.rc("xtick", top=True) 48 | pl.rc("ytick", right=True) 49 | 50 | sps = fsps.StellarPopulation(zcontinuous=1) 51 | ilib, slib, dlib = sps.libraries 52 | print(ilib, slib) 53 | os.makedirs("./figures", exist_ok=True) 54 | pdf = PdfPages("./figures/features.pdf") 55 | 56 | # Basic spectrum 57 | sps.params["sfh"] = 4 58 | sps.params["tau"] = 5.0 59 | sps.params["logzsol"] = 0.0 60 | sps.params["dust_type"] = 4 # kriek and Conroy 61 | sps.params["imf_type"] = 2 # kroupa 62 | sps.params["imf3"] = 2.3 63 | fig, ax, spec = makefig(sps) 64 | fig, ax = prettify(fig, ax, label=r"$\tau=5$, Age$=13.7$,\\n$\log Z/Z_\odot=0.0$") 65 | pdf.savefig(fig) 66 | pl.close(fig) 67 | 68 | # change IMF 69 | sps.params["imf3"] = 2.5 70 | fig, ax, spec = makefig(sps, oldspec=spec) 71 | fig, ax = prettify(fig, ax, label=r"IMF slope") 72 | pdf.savefig(fig) 73 | 74 | # Attenuate 75 | sps.params["add_dust_emission"] = False 76 | sps.params["dust2"] = 0.2 77 | fig, ax, spec = makefig(sps, oldspec=spec) 78 | fig, ax = prettify(fig, ax, label=r"Dust Attenuation") 79 | pdf.savefig(fig) 80 | pl.close(fig) 81 | 82 | # Dust emission 83 | sps.params["add_dust_emission"] = True 84 | fig, ax, spec = makefig(sps, oldspec=spec) 85 | fig, ax = prettify(fig, ax, label=r"Dust Emission") 86 | pdf.savefig(fig) 87 | pl.close(fig) 88 | 89 | # Dust temperature 90 | sps.params["duste_umin"] = 10 91 | fig, ax, spec = makefig(sps, oldspec=spec) 92 | fig, ax = prettify(fig, ax, label=r"Dust SED\\n({})".format(dlib)) 93 | pdf.savefig(fig) 94 | pl.close(fig) 95 | 96 | # AGN emission 97 | sps.params["fagn"] = 0.3 98 | fig, ax, spec = makefig(sps, oldspec=spec) 99 | fig, ax = prettify(fig, ax, label=r"AGN dust\\n(Nenkova)") 100 | pdf.savefig(fig) 101 | pl.close(fig) 102 | 103 | # Nebular emission 104 | sps.params["add_neb_emission"] = True 105 | sps.params["gas_logu"] = -3.5 106 | fig, ax, spec = makefig(sps, oldspec=spec) 107 | fig, ax = prettify(fig, ax, label=r"Neb. emission\\n(Byler)") 108 | pdf.savefig(fig) 109 | pl.close(fig) 110 | 111 | # change logu 112 | sps.params["gas_logu"] = -1.0 113 | fig, ax, spec = makefig(sps, oldspec=spec) 114 | fig, ax = prettify(fig, ax, label=r"Change U$_{neb}$") 115 | pdf.savefig(fig) 116 | pl.close(fig) 117 | 118 | # change logz 119 | sps.params["logzsol"] = -0.5 120 | sps.params["gas_logz"] = -0.5 121 | fig, ax, spec = makefig(sps, oldspec=spec) 122 | fig, ax = prettify(fig, ax, label=r"$\log Z/Z_\odot=-0.5$") 123 | pdf.savefig(fig) 124 | pl.close(fig) 125 | 126 | # IGM absorption 127 | sps.params["zred"] = 6.0 128 | sps.params["add_igm_absorption"] = True 129 | fig, ax, spec = makefig(sps, oldspec=spec) 130 | fig, ax = prettify(fig, ax, label=r"IGM attenuation\\n(Madau, $z=6$)") 131 | pdf.savefig(fig) 132 | pl.close(fig) 133 | 134 | pdf.close() 135 | -------------------------------------------------------------------------------- /demos/plot_sdss_2mass_transmission.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | Demonstration of plotting FSPS's un-normalized filter transmission tables 5 | (i.e., contents of $SPS_HOME/data/all_filters.dat). 6 | """ 7 | 8 | from matplotlib import gridspec 9 | from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas 10 | from matplotlib.figure import Figure 11 | 12 | import fsps 13 | 14 | names = ["sdss_u", "sdss_g", "sdss_r", "sdss_i", "2mass_J", "2mass_H", "2mass_Ks"] 15 | shortnames = ["u", "g", "r", "i", "J", "H", "Ks"] 16 | colors = ["violet", "dodgerblue", "maroon", "black", "c", "m", "y"] 17 | filters = [fsps.get_filter(n) for n in names] 18 | 19 | fig = Figure(figsize=(3.5, 3.5)) 20 | canvas = FigureCanvas(fig) 21 | gs = gridspec.GridSpec( 22 | 1, 23 | 1, 24 | left=0.17, 25 | right=0.95, 26 | bottom=0.15, 27 | top=0.95, 28 | wspace=None, 29 | hspace=None, 30 | width_ratios=None, 31 | height_ratios=None, 32 | ) 33 | ax = fig.add_subplot(gs[0]) 34 | 35 | for _name, fltr, c, shortname in zip(names, filters, colors, shortnames): 36 | lmbd, trans = fltr.transmission 37 | lambda_eff = fltr.lambda_eff / 10000.0 # µm 38 | ax.plot(lmbd / 10000.0, trans, ls="-", lw=1.0, c=c) 39 | ax.annotate( 40 | shortname, 41 | (lambda_eff, trans.max()), 42 | textcoords="offset points", 43 | xytext=(0.0, 5.0), 44 | ha="center", 45 | va="bottom", 46 | ) 47 | 48 | ax.set_ylim(0.0, 1.1) 49 | ax.set_xlabel(r"$\lambda~\mu\mathrm{m}$") 50 | ax.set_ylabel(r"$T$") 51 | gs.tight_layout(fig, pad=1.08, h_pad=None, w_pad=None, rect=None) 52 | canvas.print_figure("sdss_2mass_transmission_demo.pdf", format="pdf") 53 | -------------------------------------------------------------------------------- /demos/specbymass.py: -------------------------------------------------------------------------------- 1 | # This demo shows how to make a plot of the fractional contribution of differnt 2 | # stellar mass ranges to the total spectrum, for different properties of the 3 | # stellar population. There is also some use of the filter objects. 4 | 5 | from itertools import product 6 | 7 | import matplotlib.pyplot as pl 8 | import numpy as np 9 | 10 | import fsps 11 | 12 | # make an SPS object with interpolation in metallicity enabled, and 13 | # set a few parameters 14 | sps = fsps.StellarPopulation(zcontinuous=1) 15 | sps.params["imf_type"] = 0 # Salpeter IMF 16 | sps.params["sfh"] = 1 # Tau model SFH 17 | 18 | # Get a few filter transmission curves 19 | filterlist = { 20 | "galex_fuv": "FUV", 21 | "galex_nuv": "NUV", 22 | "sdss_u": "u", 23 | "sdss_g": "g", 24 | "sdss_i": "i", 25 | "2mass_j": "J", 26 | } 27 | filters = [fsps.filters.get_filter(f) for f in filterlist] 28 | 29 | 30 | # This is the main function 31 | def specplots( 32 | tage=10.0, 33 | const=1.0, 34 | tau=1.0, 35 | neb=False, 36 | z=0.0, 37 | savefig=True, 38 | masslims=[1.0, 3.0, 15.0, 30.0, 120.0], 39 | **kwargs, 40 | ): 41 | r"""Make a number of plots showing the fraction of the total light due to 42 | stars within certain stellar mass ranges. 43 | 44 | :param tage: 45 | The age of the stellar population 46 | 47 | :param const: 48 | 0 or 1. fraction of SF in constant SFR fraction 49 | 50 | :param tau: 51 | If const is 0, this is the decay timescale (in Gyr) of the 52 | exponentially declining SFR. 53 | 54 | :param neb: 55 | Boolean. Whether to include the contribution of emission lines. 56 | 57 | :param z: 58 | Metallicity, in units of log Z/Z_\odot 59 | 60 | :param masslims: 61 | The edges of the stellar mass bins to plot, in solar masses. 62 | 63 | :param savefig: 64 | If True save the figures to pdfs 65 | 66 | :returns cfig: 67 | matplotlib figure giving a plot of the spectrum of stars *less than* a 68 | given upper stellar mass limit 69 | 70 | :returns dfig: 71 | matplotlib figure giving a plot of the spectrum of stars in given mass 72 | ranges. 73 | 74 | :returns ffig: 75 | matplotlib figure giving a plot of the fraction of total flux due to 76 | stars in given mass ranges, as a function of wavelength. 77 | """ 78 | # Set the FSPS params 79 | sps.params["const"] = const 80 | sps.params["tau"] = tau 81 | sps.params["add_neb_emission"] = neb 82 | sps.params["masscut"] = 120 83 | sps.params["logzsol"] = z 84 | # Try to set any extra params 85 | for k, v in kwargs.items(): 86 | try: 87 | sps.params[k] = v 88 | except KeyError: 89 | pass 90 | 91 | # Make a pretty string with the FSPS parameters 92 | sfh = {1: "Constant SFR", 0: r"$\tau_{{SF}}={}$".format(sps.params["tau"])} 93 | sfhname = {1: "const", 0: "tau{}".format(sps.params["tau"])} 94 | name = "{}_z{}_neb{}".format(sfhname[int(const)], z, neb) 95 | 96 | # get the total spectrum due to *all* stars 97 | wave, spec_tot = sps.get_spectrum(tage=tage) 98 | 99 | # Set up stellar mass ranges and arrays to hold output spectra 100 | mspec = np.zeros([len(sps.wavelengths), len(masslims)]) 101 | dmspec = np.zeros_like(mspec) 102 | 103 | # set up figure and axis objects 104 | cfig, cax = pl.subplots(figsize=(12.4, 7.5)) 105 | dfig, dax = pl.subplots(figsize=(12.4, 7.5)) 106 | ffig, fax = pl.subplots(figsize=(12.4, 7.5)) 107 | [ax.set_xlim(0.9e3, 1e4) for ax in [cax, fax, dax]] 108 | 109 | # Loop over upper mass limits, generating spectrum for each one 110 | for i, mc in enumerate(masslims): 111 | # set the upper stellar mass limit 112 | sps.params["masscut"] = mc 113 | # get the spectrum at age = tage and store it 114 | w, spec = sps.get_spectrum(tage=tage) 115 | mspec[:, i] = spec.copy() 116 | # plot the spectrum due to stars less massive than the limit 117 | cax.plot(w, spec, label="M < {}".format(mc)) 118 | 119 | if i == 0: 120 | # Plot the lowest mass bin spectrum 121 | dax.plot(w, spec, label="{} < M < {}".format(0.08, mc)) 122 | fax.plot(w, spec / spec_tot, label="{} < M < {}".format(0.08, mc)) 123 | # skip the rest of the loop 124 | continue 125 | 126 | # Subtract the spectrum from the last upper limit to ge tthe 127 | # spectrum due to stars within the bin 128 | dmspec[:, i] = spec - mspec[:, i - 1] 129 | # Plot it 130 | label = "{} < M < {}".format(masslims[i - 1], mc) 131 | dax.plot(w, dmspec[:, i], label=label) 132 | # Plot the total spectrum 133 | if mc == masslims[-1]: 134 | dax.plot(w, spec, label="Total", color="black") 135 | 136 | # plot the fractional 137 | fax.plot(w, dmspec[:, i] / spec_tot, label=label) 138 | 139 | # prettify the axes, titles, etc 140 | [ax.legend(loc=0) for ax in [cax, dax, fax]] 141 | [ax.set_xlabel(r"$\lambda (\AA)$") for ax in [cax, dax, fax]] 142 | [ax.set_ylabel(r"$F_\lambda$") for ax in [cax, dax]] 143 | [ax.set_yscale("log") for ax in [cax, dax]] 144 | [ax.set_ylim(1e-20, 1e-14) for ax in [cax, dax]] 145 | fstring = r"Age = {} Gyr, {}, log$Z/Z_\odot$={}" 146 | vals = 10.0, sfh[int(sps.params["const"])], sps.params["logzsol"] 147 | [ax.set_title(fstring.format(*vals)) for ax in [cax, dax, fax]] 148 | fax.set_ylabel("$F/F_{tot}$") 149 | 150 | # plot filter transmission curves as shaded regions 151 | for f in filters: 152 | wavelength, transmission = f.transmission 153 | tmax = transmission.max() 154 | wmax = wavelength[transmission.argmax()] 155 | fax.fill_between(wavelength, transmission / tmax * 0.8, alpha=0.3, color="grey") 156 | fax.text(wmax, 0.83, filterlist[f.name], fontdict={"size": 16}) 157 | 158 | if savefig: 159 | # save to pdf 160 | cfig.savefig("cspec_" + name + ".pdf") 161 | dfig.savefig("dspec_" + name + ".pdf") 162 | ffig.savefig("fspec_" + name + ".pdf") 163 | 164 | return cfig, dfig, ffig 165 | 166 | 167 | if __name__ == "__main__": 168 | # set the stellar population age 169 | tage = 10.0 # in Gyr 170 | 171 | # Parameters to loop over 172 | # ---------- 173 | # log Z/Z_sun 174 | zs = [-1.0, 0.0] 175 | # Whether to include nebular emission 176 | nebs = [True, False] 177 | # Fraction of SF in constant SFR. should be 0 or 1 for these plots 178 | const = [1.0] 179 | 180 | # loop over parameters, and show the fractional plot 181 | for z, n, c in product(zs, nebs, const): 182 | cf, df, ff = specplots(tage=tage, z=z, const=c, neb=n, savefig=False) 183 | ff.show() 184 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | !*.png 3 | _static/examples/*.png 4 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | default: dirhtml 20 | 21 | help: 22 | @echo "Please use \`make ' where is one of" 23 | @echo " html to make standalone HTML files" 24 | @echo " dirhtml to make HTML files named index.html in directories" 25 | @echo " singlehtml to make a single large HTML file" 26 | @echo " pickle to make pickle files" 27 | @echo " json to make JSON files" 28 | @echo " htmlhelp to make HTML files and a HTML help project" 29 | @echo " qthelp to make HTML files and a qthelp project" 30 | @echo " devhelp to make HTML files and a Devhelp project" 31 | @echo " epub to make an epub" 32 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 33 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 34 | @echo " text to make text files" 35 | @echo " man to make manual pages" 36 | @echo " texinfo to make Texinfo files" 37 | @echo " info to make Texinfo files and run them through makeinfo" 38 | @echo " gettext to make PO message catalogs" 39 | @echo " changes to make an overview of all changed/added/deprecated items" 40 | @echo " linkcheck to check all external links for integrity" 41 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 42 | 43 | clean: 44 | -rm -rf $(BUILDDIR)/* 45 | 46 | html: 47 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 48 | @echo 49 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 50 | 51 | dirhtml: 52 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 53 | @echo 54 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 55 | 56 | singlehtml: 57 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 58 | @echo 59 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 60 | 61 | pickle: 62 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 63 | @echo 64 | @echo "Build finished; now you can process the pickle files." 65 | 66 | json: 67 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 68 | @echo 69 | @echo "Build finished; now you can process the JSON files." 70 | 71 | htmlhelp: 72 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 73 | @echo 74 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 75 | ".hhp project file in $(BUILDDIR)/htmlhelp." 76 | 77 | qthelp: 78 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 79 | @echo 80 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 81 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 82 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Daft.qhcp" 83 | @echo "To view the help file:" 84 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Daft.qhc" 85 | 86 | devhelp: 87 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 88 | @echo 89 | @echo "Build finished." 90 | @echo "To view the help file:" 91 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Daft" 92 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Daft" 93 | @echo "# devhelp" 94 | 95 | epub: 96 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 97 | @echo 98 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 99 | 100 | latex: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo 103 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 104 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 105 | "(use \`make latexpdf' here to do that automatically)." 106 | 107 | latexpdf: 108 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 109 | @echo "Running LaTeX files through pdflatex..." 110 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 111 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 112 | 113 | text: 114 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 115 | @echo 116 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 117 | 118 | man: 119 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 120 | @echo 121 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 122 | 123 | texinfo: 124 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 125 | @echo 126 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 127 | @echo "Run \`make' in that directory to run these through makeinfo" \ 128 | "(use \`make info' here to do that automatically)." 129 | 130 | info: 131 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 132 | @echo "Running Texinfo files through makeinfo..." 133 | make -C $(BUILDDIR)/texinfo info 134 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 135 | 136 | gettext: 137 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 138 | @echo 139 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 140 | 141 | changes: 142 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 143 | @echo 144 | @echo "The overview file is in $(BUILDDIR)/changes." 145 | 146 | linkcheck: 147 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 148 | @echo 149 | @echo "Link check complete; look for any errors in the above output " \ 150 | "or in $(BUILDDIR)/linkcheck/output.txt." 151 | 152 | doctest: 153 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 154 | @echo "Testing of doctests in the sources finished, look at the " \ 155 | "results in $(BUILDDIR)/doctest/output.txt." 156 | 157 | examples: ../examples/*.py _static/examples.json 158 | python gen_example.py 159 | -------------------------------------------------------------------------------- /docs/_static/pyfsps_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 19 | 32 | 44 | 58 | 69 | 81 | 82 | 87 | 90 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /docs/_templates/sidebarintro.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |

8 | Python-FSPS is a set of bindings to Charlie Conroy's 9 | 10 | Flexible Stellar Population Synthesis Fortran library. 11 |

12 | -------------------------------------------------------------------------------- /docs/_templates/sidebarlogo.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |

8 | Python-FSPS is a set of bindings to Charlie Conroy's 9 | 10 | Flexible Stellar Population Synthesis Fortran library. 11 |

12 | -------------------------------------------------------------------------------- /docs/_themes/LICENSE: -------------------------------------------------------------------------------- 1 | Further modifications: 2 | 3 | Copyright 2012 Dan Foreman-Mackey 4 | 5 | 6 | Modifications: 7 | 8 | Copyright (c) 2011 Kenneth Reitz. 9 | 10 | 11 | Original Project: 12 | 13 | Copyright (c) 2010 by Armin Ronacher. 14 | 15 | 16 | Some rights reserved. 17 | 18 | Redistribution and use in source and binary forms of the theme, with or 19 | without modification, are permitted provided that the following conditions 20 | are met: 21 | 22 | * Redistributions of source code must retain the above copyright 23 | notice, this list of conditions and the following disclaimer. 24 | 25 | * Redistributions in binary form must reproduce the above 26 | copyright notice, this list of conditions and the following 27 | disclaimer in the documentation and/or other materials provided 28 | with the distribution. 29 | 30 | * The names of the contributors may not be used to endorse or 31 | promote products derived from this software without specific 32 | prior written permission. 33 | 34 | We kindly ask you to only use these themes in an unmodified manner just 35 | for Flask and Flask-related products, not for unrelated projects. If you 36 | like the visual style and want to use it for your own projects, please 37 | consider making some larger changes to the themes (such as changing 38 | font faces, sizes, colors or margins). 39 | 40 | THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 41 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 42 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 43 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 44 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 45 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 46 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 47 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 48 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 49 | ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE 50 | POSSIBILITY OF SUCH DAMAGE. 51 | -------------------------------------------------------------------------------- /docs/_themes/README.rst: -------------------------------------------------------------------------------- 1 | dfm Sphinx Style 2 | ================ 3 | 4 | This repository contains sphinx styles based on Kenneth Reitz's modifications 5 | to Mitsuhiko's Flask theme. 6 | 7 | 8 | Usage 9 | ----- 10 | 11 | 1. Put this folder as `_themes` into your docs folder. 12 | 13 | 2. Add this to your ``conf.py``: :: 14 | 15 | sys.path.append(os.path.abspath('_themes')) 16 | html_theme_path = ['_themes'] 17 | html_theme = 'dfm' 18 | -------------------------------------------------------------------------------- /docs/_themes/dfm/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends "basic/layout.html" %} {%- block extrahead %} {{ super() }} {% if 2 | theme_touch_icon %} 3 | 7 | {% endif %} 8 | 12 | 13 | 14 | 19 | 20 | 21 | 22 | {% endblock %} {%- block relbar2 %}{% endblock %} {%- block footer %} 23 | 24 | 25 | 26 | {%- endblock %} 27 | -------------------------------------------------------------------------------- /docs/_themes/dfm/localtoc.html: -------------------------------------------------------------------------------- 1 | {# 2 | basic/localtoc.html 3 | ~~~~~~~~~~~~~~~~~~~ 4 | 5 | Sphinx sidebar template: local table of contents. 6 | 7 | :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. 8 | :license: BSD, see LICENSE for details. 9 | #} 10 | {%- if display_toc %} 11 |

{{ _('Contents') }}

12 | {{ toc }} 13 | {%- endif %} 14 | -------------------------------------------------------------------------------- /docs/_themes/dfm/relations.html: -------------------------------------------------------------------------------- 1 |

Related Topics

2 | 20 | -------------------------------------------------------------------------------- /docs/_themes/dfm/static/flasky.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * flasky.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. 6 | * Modifications by Kenneth Reitz. 7 | * Further modification by Dan Foreman-Mackey, then by 8 | * Jonathan Sick for python-fsps. 9 | * :license: Flask Design License, see LICENSE for details. 10 | */ 11 | 12 | {% set page_width = '940px' %} 13 | {% set sidebar_width = '220px' %} 14 | 15 | @import url("basic.css"); 16 | 17 | body { 18 | font-family: "Source Sans Pro", "Helvetica Neue", "Helvetica", sans-serif; 19 | font-size: 16px; 20 | line-height: 1.8em; 21 | background-color: white; 22 | color: #000; 23 | margin: 0; 24 | padding: 0; 25 | } 26 | 27 | div.document { 28 | width: {{ page_width }}; 29 | margin: 30px auto 0 auto; 30 | } 31 | 32 | div.documentwrapper { 33 | float: left; 34 | width: 100%; 35 | } 36 | 37 | div.bodywrapper { 38 | margin: 0 0 0 {{ sidebar_width }}; 39 | } 40 | 41 | div.sphinxsidebar { 42 | width: {{ sidebar_width }}; 43 | } 44 | 45 | hr { 46 | border: 1px solid #B1B4B6; 47 | } 48 | 49 | div.body { 50 | background-color: #ffffff; 51 | color: #3E4349; 52 | padding: 0 30px 0 30px; 53 | } 54 | 55 | .section img { 56 | max-width: 100%; 57 | } 58 | 59 | img { 60 | max-width: 100%; 61 | } 62 | 63 | img.floatingflask { 64 | padding: 0 0 10px 10px; 65 | float: right; 66 | } 67 | 68 | div.footer { 69 | width: {{ page_width }}; 70 | margin: 20px auto 30px auto; 71 | font-size: 14px; 72 | color: #888; 73 | text-align: right; 74 | } 75 | 76 | div.footer a { 77 | color: #888; 78 | } 79 | 80 | div.related { 81 | display: none; 82 | } 83 | 84 | div.sphinxsidebar a { 85 | color: #444; 86 | text-decoration: none; 87 | border-bottom: 1px dotted #999; 88 | } 89 | 90 | div.sphinxsidebar a:hover { 91 | border-bottom: 1px solid #999; 92 | } 93 | 94 | div.sphinxsidebar { 95 | font-size: 14px; 96 | line-height: 1.8; 97 | } 98 | 99 | div.sphinxsidebarwrapper { 100 | padding: 18px 10px; 101 | } 102 | 103 | div.sphinxsidebarwrapper p.logo { 104 | padding: 0; 105 | margin: -10px 0 0 -70px; 106 | text-align: center; 107 | } 108 | 109 | div.sphinxsidebar h3, 110 | div.sphinxsidebar h4 { 111 | font-family: "lft-etica", Helvetica, sans-serif; 112 | color: #444; 113 | font-size: 24px; 114 | font-weight: 600; 115 | margin: 0 0 5px 0; 116 | padding: 0; 117 | } 118 | 119 | div.sphinxsidebar h4 { 120 | font-size: 20px; 121 | } 122 | 123 | div.sphinxsidebar h3 a { 124 | color: #444; 125 | } 126 | 127 | div.sphinxsidebar p.logo a, 128 | div.sphinxsidebar h3 a, 129 | div.sphinxsidebar p.logo a:hover, 130 | div.sphinxsidebar h3 a:hover { 131 | border: none; 132 | } 133 | 134 | div.sphinxsidebar p { 135 | color: #555; 136 | margin: 10px 0; 137 | } 138 | 139 | div.sphinxsidebar ul { 140 | margin: 10px 0; 141 | padding: 0; 142 | color: #000; 143 | } 144 | 145 | div.sphinxsidebar input { 146 | border: 1px solid #ccc; 147 | font-family: "Source Sans Pro", Helvetica, sans-serif; 148 | font-size: 1em; 149 | } 150 | 151 | /* -- body styles ----------------------------------------------------------- */ 152 | 153 | a { 154 | color: #124600; 155 | } 156 | 157 | a:hover { 158 | color: #7FD600; 159 | } 160 | 161 | div.body h1, 162 | div.body h2 { 163 | font-family: "Source Sans Pro", Helvetica, sans-serif; 164 | font-weight: 700; 165 | margin: 30px 0px 10px 0px; 166 | padding: 0; 167 | } 168 | div.body h3, 169 | div.body h4, 170 | div.body h5, 171 | div.body h6 { 172 | font-family: "Source Sans Pro", Helvetica, sans-serif; 173 | font-weight: 700; 174 | margin: 30px 0px 10px 0px; 175 | padding: 0; 176 | } 177 | 178 | div.body h1 { padding-top: 0; font-size: 240%; } 179 | div.body h2 { font-size: 150%; } 180 | div.body h3 { font-size: 120%; } 181 | div.body h4 { font-size: 110%; } 182 | div.body h5 { font-size: 100%; } 183 | div.body h6 { font-size: 100%; } 184 | 185 | 186 | div.body h1 { 187 | margin: 0px 0 20px 0; 188 | line-height: 1.3em; 189 | } 190 | div.body h2 { 191 | margin: 35px 0 20px 0; 192 | } 193 | 194 | a.headerlink { 195 | color: #ddd; 196 | padding: 0 4px; 197 | text-decoration: none; 198 | } 199 | 200 | a.headerlink:hover { 201 | color: #444; 202 | background: #eaeaea; 203 | } 204 | 205 | div.body p, div.body dd, div.body li { 206 | /* line-height: 1.8em; */ 207 | } 208 | 209 | div.highlight { 210 | background-color: white; 211 | } 212 | 213 | dt:target, .highlight { 214 | background: #FAF3E8; 215 | } 216 | 217 | div.note { 218 | background-color: #eee; 219 | border: 1px solid #ccc; 220 | } 221 | 222 | div.seealso { 223 | background-color: #ffc; 224 | border: 1px solid #ff6; 225 | } 226 | 227 | div.topic { 228 | background-color: #eee; 229 | } 230 | 231 | p.admonition-title { 232 | display: inline; 233 | } 234 | 235 | p.admonition-title:after { 236 | content: ":"; 237 | } 238 | 239 | pre, tt { 240 | font-family: "Source Code Pro", "Menlo", "Deja Vu Sans Mono", "Bitstream Vera Sans Mono", monospace; 241 | font-size: 0.9em; 242 | } 243 | 244 | img.screenshot { 245 | } 246 | 247 | .class dt, .function dt { 248 | line-height: 1em; 249 | } 250 | 251 | dl.class dt em, dl.function dt em, dl.class dt span, dl.function dt span { 252 | font-family: "Source Code Pro", "Menlo", "Deja Vu Sans Mono", "Bitstream Vera Sans Mono", monospace; 253 | font-size: 0.7em; 254 | text-indent: 50px; 255 | } 256 | 257 | dl.class dt em, dl.function dt em { 258 | padding: 0 3px; 259 | } 260 | 261 | dl.class em.property, dl.function em.property { 262 | font-family: "Source Sans Pro", Helvetica, sans-serif; 263 | font-weight: 700; 264 | padding-right: 5px; 265 | font-size: 0.9em; 266 | } 267 | 268 | dl.class, dl.function { 269 | padding-bottom: 50px; 270 | } 271 | 272 | tt.descname, tt.descclassname { 273 | font-size: 0.95em; 274 | } 275 | 276 | tt.descname { 277 | padding-right: 0.08em; 278 | } 279 | 280 | img.screenshot { 281 | -moz-box-shadow: 2px 2px 4px #eee; 282 | -webkit-box-shadow: 2px 2px 4px #eee; 283 | box-shadow: 2px 2px 4px #eee; 284 | } 285 | 286 | table.docutils { 287 | border: 1px solid #888; 288 | -moz-box-shadow: 2px 2px 4px #eee; 289 | -webkit-box-shadow: 2px 2px 4px #eee; 290 | box-shadow: 2px 2px 4px #eee; 291 | } 292 | 293 | table.docutils td, table.docutils th { 294 | border: 1px solid #888; 295 | padding: 0.25em 0.7em; 296 | } 297 | 298 | table.field-list, table.footnote { 299 | border: none; 300 | -moz-box-shadow: none; 301 | -webkit-box-shadow: none; 302 | box-shadow: none; 303 | } 304 | 305 | table.footnote { 306 | margin: 15px 0; 307 | width: 100%; 308 | border: 1px solid #eee; 309 | background: #fdfdfd; 310 | font-size: 0.9em; 311 | } 312 | 313 | table.footnote + table.footnote { 314 | margin-top: -15px; 315 | border-top: none; 316 | } 317 | 318 | table.field-list th { 319 | padding: 0 0.8em 0 0; 320 | } 321 | 322 | table.field-list td { 323 | padding: 0; 324 | } 325 | 326 | table.footnote td.label { 327 | width: 0px; 328 | padding: 0.3em 0 0.3em 0.5em; 329 | } 330 | 331 | table.footnote td { 332 | padding: 0.3em 0.5em; 333 | } 334 | 335 | dl { 336 | margin: 0; 337 | padding: 0; 338 | } 339 | 340 | dl dd { 341 | margin-left: 30px; 342 | } 343 | 344 | blockquote { 345 | margin: 0 0 0 -10px; 346 | padding: 0 0 0 10px; 347 | border-left: 2px solid #ddd; 348 | } 349 | 350 | ul, ol { 351 | margin: 10px 0 10px 30px; 352 | padding: 0; 353 | } 354 | 355 | pre { 356 | background: #eee; 357 | padding: 7px 30px; 358 | margin: 15px -30px; 359 | line-height: 1.3em; 360 | } 361 | 362 | dl pre, blockquote pre, li pre { 363 | margin-left: -60px; 364 | padding-left: 60px; 365 | } 366 | 367 | dl dl pre { 368 | margin-left: -90px; 369 | padding-left: 90px; 370 | } 371 | 372 | tt { 373 | background-color: #ecf0f3; 374 | color: #222; 375 | /* padding: 1px 2px; */ 376 | } 377 | 378 | tt.xref, a tt { 379 | background-color: #FBFBFB; 380 | border-bottom: 1px solid white; 381 | } 382 | 383 | a.footnote-reference { 384 | text-decoration: none; 385 | font-size: 0.7em; 386 | vertical-align: top; 387 | border-bottom: 1px dotted #004B6B; 388 | } 389 | 390 | a.footnote-reference:hover { 391 | border-bottom: 1px solid #6D4100; 392 | } 393 | 394 | a:hover tt { 395 | background: #EEE; 396 | } 397 | 398 | 399 | @media screen and (max-width: 870px) { 400 | 401 | div.sphinxsidebar { 402 | display: none; 403 | } 404 | 405 | div.document { 406 | width: 100%; 407 | 408 | } 409 | 410 | div.documentwrapper { 411 | margin-left: 0; 412 | margin-top: 0; 413 | margin-right: 0; 414 | margin-bottom: 0; 415 | } 416 | 417 | div.bodywrapper { 418 | margin-top: 0; 419 | margin-right: 0; 420 | margin-bottom: 0; 421 | margin-left: 0; 422 | } 423 | 424 | ul { 425 | margin-left: 0; 426 | } 427 | 428 | .document { 429 | width: auto; 430 | } 431 | 432 | .footer { 433 | width: auto; 434 | } 435 | 436 | .bodywrapper { 437 | margin: 0; 438 | } 439 | 440 | .footer { 441 | width: auto; 442 | } 443 | 444 | .github { 445 | display: none; 446 | } 447 | } 448 | 449 | 450 | 451 | @media screen and (max-width: 875px) { 452 | 453 | body { 454 | margin: 0; 455 | padding: 20px 30px; 456 | } 457 | 458 | div.documentwrapper { 459 | float: none; 460 | background: white; 461 | } 462 | 463 | div.sphinxsidebar { 464 | display: block; 465 | float: none; 466 | width: 102.5%; 467 | margin: 50px -30px -20px -30px; 468 | padding: 10px 20px; 469 | background: #333; 470 | color: white; 471 | } 472 | 473 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 474 | div.sphinxsidebar h3 a { 475 | color: white; 476 | } 477 | 478 | div.sphinxsidebar a { 479 | color: #aaa; 480 | } 481 | 482 | div.sphinxsidebar p.logo { 483 | display: none; 484 | } 485 | 486 | div.document { 487 | width: 100%; 488 | margin: 0; 489 | } 490 | 491 | div.related { 492 | display: block; 493 | margin: 0; 494 | padding: 10px 0 20px 0; 495 | } 496 | 497 | div.related ul, 498 | div.related ul li { 499 | margin: 0; 500 | padding: 0; 501 | } 502 | 503 | div.footer { 504 | display: none; 505 | } 506 | 507 | div.bodywrapper { 508 | margin: 0; 509 | } 510 | 511 | div.body { 512 | min-height: 0; 513 | padding: 0; 514 | } 515 | 516 | .rtd_doc_footer { 517 | display: none; 518 | } 519 | 520 | .document { 521 | width: auto; 522 | } 523 | 524 | .footer { 525 | width: auto; 526 | } 527 | 528 | .footer { 529 | width: auto; 530 | } 531 | 532 | .github { 533 | display: none; 534 | } 535 | } 536 | 537 | 538 | /* misc. */ 539 | 540 | .revsys-inline { 541 | display: none!important; 542 | } 543 | -------------------------------------------------------------------------------- /docs/_themes/dfm/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | pygments_style = flask_theme_support.FlaskyStyle 5 | 6 | [options] 7 | touch_icon = 8 | -------------------------------------------------------------------------------- /docs/_themes/flask_theme_support.py: -------------------------------------------------------------------------------- 1 | # flasky extensions. flasky pygments style based on tango style 2 | from pygments.style import Style 3 | from pygments.token import ( 4 | Comment, 5 | Error, 6 | Generic, 7 | Keyword, 8 | Literal, 9 | Name, 10 | Number, 11 | Operator, 12 | Other, 13 | Punctuation, 14 | String, 15 | Whitespace, 16 | ) 17 | 18 | 19 | class FlaskyStyle(Style): 20 | background_color = "#f8f8f8" 21 | default_style = "" 22 | 23 | styles = { 24 | # No corresponding class for the following: 25 | # Text: "", # class: '' 26 | Whitespace: "underline #f8f8f8", # class: 'w' 27 | Error: "#a40000 border:#ef2929", # class: 'err' 28 | Other: "#000000", # class 'x' 29 | Comment: "italic #8f5902", # class: 'c' 30 | Comment.Preproc: "noitalic", # class: 'cp' 31 | Keyword: "bold #004461", # class: 'k' 32 | Keyword.Constant: "bold #004461", # class: 'kc' 33 | Keyword.Declaration: "bold #004461", # class: 'kd' 34 | Keyword.Namespace: "bold #004461", # class: 'kn' 35 | Keyword.Pseudo: "bold #004461", # class: 'kp' 36 | Keyword.Reserved: "bold #004461", # class: 'kr' 37 | Keyword.Type: "bold #004461", # class: 'kt' 38 | Operator: "#582800", # class: 'o' 39 | Operator.Word: "bold #004461", # class: 'ow' - like keywords 40 | Punctuation: "bold #000000", # class: 'p' 41 | # because special names such as Name.Class, Name.Function, etc. 42 | # are not recognized as such later in the parsing, we choose them 43 | # to look the same as ordinary variables. 44 | Name: "#000000", # class: 'n' 45 | Name.Attribute: "#c4a000", # class: 'na' - to be revised 46 | Name.Builtin: "#004461", # class: 'nb' 47 | Name.Builtin.Pseudo: "#3465a4", # class: 'bp' 48 | Name.Class: "#000000", # class: 'nc' - to be revised 49 | Name.Constant: "#000000", # class: 'no' - to be revised 50 | Name.Decorator: "#888", # class: 'nd' - to be revised 51 | Name.Entity: "#ce5c00", # class: 'ni' 52 | Name.Exception: "bold #cc0000", # class: 'ne' 53 | Name.Function: "#000000", # class: 'nf' 54 | Name.Property: "#000000", # class: 'py' 55 | Name.Label: "#f57900", # class: 'nl' 56 | Name.Namespace: "#000000", # class: 'nn' - to be revised 57 | Name.Other: "#000000", # class: 'nx' 58 | Name.Tag: "bold #004461", # class: 'nt' - like a keyword 59 | Name.Variable: "#000000", # class: 'nv' - to be revised 60 | Name.Variable.Class: "#000000", # class: 'vc' - to be revised 61 | Name.Variable.Global: "#000000", # class: 'vg' - to be revised 62 | Name.Variable.Instance: "#000000", # class: 'vi' - to be revised 63 | Number: "#990000", # class: 'm' 64 | Literal: "#000000", # class: 'l' 65 | Literal.Date: "#000000", # class: 'ld' 66 | String: "#4e9a06", # class: 's' 67 | String.Backtick: "#4e9a06", # class: 'sb' 68 | String.Char: "#4e9a06", # class: 'sc' 69 | String.Doc: "italic #8f5902", # class: 'sd' - like a comment 70 | String.Double: "#4e9a06", # class: 's2' 71 | String.Escape: "#4e9a06", # class: 'se' 72 | String.Heredoc: "#4e9a06", # class: 'sh' 73 | String.Interpol: "#4e9a06", # class: 'si' 74 | String.Other: "#4e9a06", # class: 'sx' 75 | String.Regex: "#4e9a06", # class: 'sr' 76 | String.Single: "#4e9a06", # class: 's1' 77 | String.Symbol: "#4e9a06", # class: 'ss' 78 | Generic: "#000000", # class: 'g' 79 | Generic.Deleted: "#a40000", # class: 'gd' 80 | Generic.Emph: "italic #000000", # class: 'ge' 81 | Generic.Error: "#ef2929", # class: 'gr' 82 | Generic.Heading: "bold #000080", # class: 'gh' 83 | Generic.Inserted: "#00A000", # class: 'gi' 84 | Generic.Output: "#888", # class: 'go' 85 | Generic.Prompt: "#745334", # class: 'gp' 86 | Generic.Strong: "bold #000000", # class: 'gs' 87 | Generic.Subheading: "bold #800080", # class: 'gu' 88 | Generic.Traceback: "bold #a40000", # class: 'gt' 89 | } 90 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | from importlib.metadata import version as get_version 7 | from pathlib import Path 8 | 9 | if "SPS_HOME" not in os.environ: 10 | path = Path(__file__).absolute() 11 | sps_home = path.parent.parent / "src" / "fsps" / "libfsps" 12 | os.environ["SPS_HOME"] = str(sps_home) 13 | 14 | extensions = [ 15 | "sphinx.ext.autodoc", 16 | "sphinx.ext.intersphinx", 17 | "sphinx.ext.mathjax", 18 | ] 19 | 20 | source_suffix = ".rst" 21 | master_doc = "index" 22 | exclude_patterns = ["_build", "_themes"] 23 | html_static_path = ["_static"] 24 | html_theme_path = ["_themes"] 25 | templates_path = ["_templates"] 26 | 27 | # General information about the project. 28 | project = "Python FSPS" 29 | copyright = "2013-2023 Python-FSPS developers" 30 | version = get_version("fsps") 31 | 32 | html_show_sphinx = True 33 | html_show_sourcelink = False 34 | html_use_smartypants = True 35 | pygments_style = "sphinx" 36 | sys.path.append(os.path.abspath("_themes")) 37 | html_theme_path = ["_themes"] 38 | html_theme = "dfm" 39 | # Custom sidebar templates, maps document names to template names. 40 | html_sidebars = { 41 | "index": ["sidebarintro.html", "searchbox.html"], 42 | "**": [ 43 | "sidebarlogo.html", 44 | "localtoc.html", 45 | "relations.html", 46 | "searchbox.html", 47 | ], 48 | } 49 | -------------------------------------------------------------------------------- /docs/filter_table.rst: -------------------------------------------------------------------------------- 1 | === ================ ========== ======== =============== ======================================================================================== 2 | ID Name M_sun Vega M_sun AB lambda_eff (Å) Description 3 | === ================ ========== ======== =============== ======================================================================================== 4 | 1 v 4.81 4.81 5477.6 Johnson V (from Bessell 1990 via M. Blanton) - this defines the Vega system 5 | 2 u 5.55 6.35 3584.8 Johnson U (from Bessell 1990 via M. Blanton) 6 | 3 b 5.46 5.35 4370.9 Johnson B (from Bessell 1990 via M. Blanton) 7 | 4 buser_b2 5.41 5.30 4433.5 Buser B2 (from BC03) 8 | 5 cousins_r 4.41 4.60 6576.9 Cousins R (from Bessell 1990 via M. Blanton) 9 | 6 cousins_i 4.10 4.52 7891.2 Cousins I (from Bessell 1990 via M. Blanton) 10 | 7 cfht_b 5.41 5.28 4418.6 CFHT B-band (from Blanton's kcorrect) 11 | 8 cfht_r 4.37 4.59 6616.0 CFHT R-band (from Blanton's kcorrect) 12 | 9 cfht_i 4.08 4.52 8166.0 CFHT I-band (from Blanton's kcorrect) 13 | 10 2mass_j 3.67 4.56 12387.7 2MASS J filter (total response w/atm) 14 | 11 2mass_h 3.34 4.70 16488.9 2MASS H filter (total response w/atm) 15 | 12 2mass_ks 3.30 5.14 21635.6 2MASS Ks filter (total response w/atm) 16 | 13 sdss_u 5.47 6.39 3556.5 SDSS Camera u Response Function, airmass = 1.3 (June 2001) 17 | 14 sdss_g 5.23 5.12 4702.5 SDSS Camera g Response Function, airmass = 1.3 (June 2001) 18 | 15 sdss_r 4.50 4.64 6175.6 SDSS Camera r Response Function, airmass = 1.3 (June 2001) 19 | 16 sdss_i 4.18 4.53 7489.9 SDSS Camera i Response Function, airmass = 1.3 (June 2001) 20 | 17 sdss_z 4.00 4.51 8946.8 SDSS Camera z Response Function, airmass = 1.3 (June 2001) 21 | 18 wfpc2_f255w 7.44 9.06 2601.0 HST WFPC2 F255W (``_) 22 | 19 wfpc2_f300w 6.05 7.40 2994.3 HST WFPC2 F300W (``_) 23 | 20 wfpc2_f336w 5.46 6.64 3359.2 HST WFPC2 F336W (``_) 24 | 21 wfpc2_f439w 5.51 5.36 4311.7 HST WFPC2 F439W (``_) 25 | 22 wfpc2_f450w 5.31 5.22 4555.6 HST WFPC2 F450W (``_) 26 | 23 wfpc2_f555w 4.83 4.82 5438.9 HST WFPC2 F555W (``_) 27 | 24 wfpc2_f606w 4.61 4.70 5996.8 HST WFPC2 F606W (``_) 28 | 25 wfpc2_f814w 4.11 4.52 8012.3 HST WFPC2 F814W (``_) 29 | 26 wfpc2_f850lp 4.00 4.52 9129.0 HST WFPC2 F850LP (``_) 30 | 27 wfc_acs_f435w 5.48 5.38 4318.1 HST ACS F435W (``_) 31 | 28 wfc_acs_f475w 5.21 5.10 4744.3 HST ACS F475W (``_) 32 | 29 wfc_acs_f555w 4.85 4.84 5359.5 HST ACS F555W (``_) 33 | 30 wfc_acs_f606w 4.64 4.72 5912.4 HST ACS F606W (``_) 34 | 31 wfc_acs_f625w 4.47 4.63 6310.5 HST ACS F625W (``_) 35 | 32 wfc_acs_f775w 4.14 4.53 7693.3 HST ACS F775W (``_) 36 | 33 wfc_acs_f814w 4.10 4.52 8059.6 HST ACS F814W (``_) 37 | 34 wfc_acs_f850lp 4.00 4.51 9054.5 HST ACS F850LP (``_) 38 | 35 wfc3_uvis_f218w 8.95 10.71 2226.5 HST WFC3 UVIS F218W (``_) Chip #1 39 | 36 wfc3_uvis_f225w 8.37 10.10 2372.5 HST WFC3 UVIS F225W (``_) Chip #1 40 | 37 wfc3_uvis_f275w 6.99 8.54 2710.1 HST WFC3 UVIS F275W (``_) Chip #1 41 | 38 wfc3_uvis_f336w 5.47 6.65 3355.1 HST WFC3 UVIS F336W (``_) Chip #1 42 | 39 wfc3_uvis_f390w 5.64 5.85 3924.4 HST WFC3 UVIS F390W (``_) Chip #1 43 | 40 wfc3_uvis_f438w 5.49 5.34 4326.5 HST WFC3 UVIS F438W (``_) Chip #1 44 | 41 wfc3_uvis_f475w 5.19 5.08 4773.7 HST WFC3 UVIS F475W (``_) Chip #1 45 | 42 wfc3_uvis_f555w 4.89 4.86 5308.2 HST WFC3 UVIS F555W (``_) Chip #1 46 | 43 wfc3_uvis_f606w 4.65 4.73 5887.4 HST WFC3 UVIS F606W (``_) Chip #1 47 | 44 wfc3_uvis_f775w 4.15 4.53 7648.5 HST WFC3 UVIS F775W (``_) Chip #1 48 | 45 wfc3_uvis_f814w 4.11 4.52 8029.5 HST WFC3 UVIS F814W (``_) Chip #1 49 | 46 wfc3_uvis_f850lp 4.00 4.52 9168.9 HST WFC3 UVIS F850LP (``_) Chip #1 50 | 47 wfc3_ir_f098m 3.96 4.51 9862.9 HST WFC3 IR F098M (``_) 51 | 48 wfc3_ir_f105w 3.89 4.53 10550.7 HST WFC3 IR F105W (``_) 52 | 49 wfc3_ir_f110w 3.79 4.54 11534.4 HST WFC3 IR F110W (``_) 53 | 50 wfc3_ir_f125w 3.67 4.56 12486.1 HST WFC3 IR F125W (``_) 54 | 51 wfc3_ir_f140w 3.52 4.60 13923.9 HST WFC3 IR F140W (``_) 55 | 52 wfc3_ir_f160w 3.40 4.65 15370.8 HST WFC3 IR F160W (``_) 56 | 53 irac_1 3.21 5.99 35569.7 Spitzer IRAC Channel 1 (3.6um) 57 | 54 irac_2 3.16 6.41 45020.4 Spitzer IRAC Channel 2 (4.5um) 58 | 55 irac_3 3.12 6.87 57454.1 Spitzer IRAC Channel 3 (5.8um) 59 | 56 irac_4 3.09 7.47 79162.1 Spitzer IRAC Channel 4 (8.0um) 60 | 57 isaac_ks 3.30 5.13 21622.1 ISAAC Ks 61 | 58 fors_v 4.78 4.79 5536.1 FORS V 62 | 59 fors_r 4.41 4.60 6564.6 FORS R 63 | 60 nicmos_f110w 3.82 4.54 11248.6 HST NICMOS F110W 64 | 61 nicmos_f160w 3.37 4.68 16060.4 HST NICMOS F160W 65 | 62 galex_fuv 14.91 17.24 1535.1 GALEX FUV 66 | 63 galex_nuv 8.41 10.15 2300.7 GALEX NUV 67 | 64 des_g 5.17 5.08 4800.9 DES g (from Huan Lin, for DES camera) 68 | 65 des_r 4.46 4.62 6362.6 DES r (from Huan Lin, for DES camera) 69 | 66 des_i 4.13 4.52 7750.1 DES i (from Huan Lin, for DES camera) 70 | 67 des_z 4.00 4.52 9153.7 DES z (from Huan Lin, for DES camera) 71 | 68 des_y 3.96 4.51 9907.8 DES Y (from Huan Lin, for DES camera) 72 | 69 wfcam_z 4.01 4.52 8826.3 WFCAM (UKIRT) Z (from Hewett et al. 2006, via A. Smith) 73 | 70 wfcam_y 3.91 4.51 10314.1 WFCAM (UKIRT) Y (from Hewett et al. 2006, via A. Smith) 74 | 71 wfcam_j 3.65 4.56 12500.9 WFCAM (UKIRT) J (from Hewett et al. 2006, via A. Smith) 75 | 72 wfcam_h 3.35 4.70 16359.1 WFCAM (UKIRT) H (from Hewett et al. 2006, via A. Smith) 76 | 73 wfcam_k 3.30 5.17 22084.0 WFCAM (UKIRT) K (from Hewett et al. 2006, via A. Smith) 77 | 74 steidel_un 5.44 6.34 3602.5 Steidel Un (via A. Shapley; see Steidel et al. 2003) 78 | 75 steidel_g 5.19 5.08 4753.2 Steidel G (via A. Shapley; see Steidel et al. 2003) 79 | 76 steidel_rs 4.33 4.58 6781.4 Steidel Rs (via A. Shapley; see Steidel et al. 2003) 80 | 77 steidel_i 4.09 4.52 7985.7 Steidel I (via A. Shapley; see Steidel et al. 2003) 81 | 78 megacam_u 5.68 6.03 3802.9 CFHT MegaCam u* (``_, Dec 2010) 82 | 79 megacam_g 5.14 5.05 4844.4 CFHT MegaCam g' (``_, Dec 2010) 83 | 80 megacam_r 4.48 4.63 6247.7 CFHT MegaCam r' (``_, Dec 2010) 84 | 81 megacam_i 4.17 4.53 7538.7 CFHT MegaCam i' (``_, Dec 2010) 85 | 82 megacam_z 4.00 4.51 8860.0 CFHT MegaCam z' (``_, Dec 2010) 86 | 83 wise_w1 3.22 5.89 33682.1 WISE W1, 3.4um (``_) 87 | 84 wise_w2 3.16 6.45 46178.9 WISE W2, 4.6um (``_) 88 | 85 wise_w3 3.06 8.19 120717.6 WISE W3, 12um (``_) 89 | 86 wise_w4 3.01 9.62 221933.5 WISE W4, 22um (``_) 90 | 87 uvot_w2 8.47 10.29 2057.9 UVOT W2 (from Erik Hoversten, 2011) 91 | 88 uvot_m2 8.84 10.60 2246.9 UVOT M2 (from Erik Hoversten, 2011) 92 | 89 uvot_w1 6.91 8.47 2582.6 UVOT W1 (from Erik Hoversten, 2011) 93 | 90 mips_24 3.00 9.74 235917.9 Spitzer MIPS 24um 94 | 91 mips_70 2.95 12.05 708981.4 Spitzer MIPS 70um 95 | 92 mips_160 2.92 13.78 1553640.7 Spitzer MIPS 160um 96 | 93 scuba_450wb 2.87 16.13 4561884.3 SCUBA 450WB (``_) 97 | 94 scuba_850wb 2.84 17.50 8563901.9 SCUBA 850WB (``_) 98 | 95 pacs_70 2.95 12.07 707688.0 Herschel PACS 70um 99 | 96 pacs_100 2.94 12.82 1007889.7 Herschel PACS 100um 100 | 97 pacs_160 2.92 13.81 1618854.2 Herschel PACS 160um 101 | 98 spire_250 2.90 14.78 2482638.8 Herschel SPIRE 250um 102 | 99 spire_350 2.88 15.52 3483962.4 Herschel SPIRE 350um 103 | 100 spire_500 2.86 16.27 5000972.5 Herschel SPIRE 500um 104 | 101 iras_12 3.06 8.05 110332.1 IRAS 12um 105 | 102 iras_25 3.01 9.62 230701.4 IRAS 25um 106 | 103 iras_60 2.97 11.50 581751.6 IRAS 60um 107 | 104 iras_100 2.94 12.77 994956.5 IRAS 100um 108 | 105 bessell_l 3.21 5.96 34800.1 Bessell L band (Bessell & Brett 1988) 109 | 106 bessell_lp 3.19 6.12 38275.9 Bessell L' band (Bessell & Brett 1988) 110 | 107 bessell_m 3.15 6.51 47328.3 Bessell M band (Bessell & Brett 1988) 111 | 108 stromgren_u 5.34 6.47 3478.5 Stromgren u (Bessell 2011) 112 | 109 stromgren_v 5.67 5.53 4107.9 Stromgren v (Bessell 2011) 113 | 110 stromgren_b 5.22 5.06 4665.3 Stromgren b (Bessell 2011) 114 | 111 stromgren_y 4.81 4.80 5459.0 Stromgren y (Bessell 2011) 115 | 112 1500a 15.67 18.05 1498.5 Idealized 1500A bandpass with 15% bandwidth, FWHM = 225A from M. Dickinson 116 | 113 2300a 8.85 10.62 2297.7 Idealized 2300A bandpass with 15% bandwidth, FWHM = 345A from M. Dickinson 117 | 114 2800a 6.72 8.20 2797.1 Idealized 2800A bandpass with 15% bandwidth, FWHM = 420A from M. Dickinson 118 | 115 jwst_f070w nan nan nan JWST F070W (``_) 119 | 116 jwst_f090w nan nan nan JWST F090W (``_) 120 | 117 jwst_f115w nan nan nan JWST F115W (``_) 121 | 118 jwst_f150w nan nan nan JWST F150W (``_) 122 | 119 jwst_f200w nan nan nan JWST F200W (``_) 123 | 120 jwst_f277w nan nan nan JWST F277W (``_) 124 | 121 jwst_f356w nan nan nan JWST F356W (``_) 125 | 122 jwst_f444w nan nan nan JWST F444W (``_) 126 | === ================ ========== ======== =============== ======================================================================================== 127 | -------------------------------------------------------------------------------- /docs/filters.rst: -------------------------------------------------------------------------------- 1 | Table of FSPS Filters 2 | ===================== 3 | 4 | For convenience, we provide provide a table of filters supported by FSPS. 5 | These filters can be referred to by providing their *Name* string, which is case-insensitive. 6 | For example, to model an SED in HST/ACS F660W and F814W bandpasses:: 7 | 8 | import fsps 9 | sp = fsps.StellarPopulation() 10 | sp.get_mags(bands=['WFC_ACS_F606W', 'WFC_ACS_F814W']) 11 | 12 | 13 | .. include:: filter_table.rst 14 | -------------------------------------------------------------------------------- /docs/filters_api.rst: -------------------------------------------------------------------------------- 1 | Working with Filters 2 | ==================== 3 | 4 | Python-FSPS provides simple access to FSPS's filter set. 5 | To find what filters are available, use :func:`fsps.find_filter` or :func:`fsps.list_filters` (see also :doc:`filters`). 6 | :func:`fsps.get_filter` is used to get a :class:`fsps.filters.Filter` instance, which in turn provides properties such as the filter's transmission curve, Solar absolute magnitude, and effective wavelength. 7 | 8 | Example 9 | ------- 10 | 11 | :: 12 | 13 | >>> import fsps 14 | >>> fsps.find_filter('sdss') 15 | ['sdss_u', 'sdss_g', 'sdss_i', 'sdss_r', 'sdss_z'] 16 | >>> g = fsps.get_filter('sdss_g') 17 | >>> g.msun_ab 18 | 5.12 19 | >>> g.lambda_eff 20 | 4702.5 21 | 22 | 23 | API Reference 24 | ------------- 25 | 26 | .. autofunction:: fsps.find_filter 27 | 28 | .. autofunction:: fsps.list_filters 29 | 30 | .. autofunction:: fsps.get_filter 31 | 32 | .. autoclass:: fsps.filters.Filter 33 | :inherited-members: 34 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Flexible Stellar Population Synthesis for Python 2 | ================================================ 3 | 4 | These are a set of Python bindings to `Charlie Conroy's Flexible Stellar 5 | Population Synthesis (FSPS) Fortran library 6 | `_. 7 | Python-FSPS makes it easy to generate spectra and magnitudes for arbitrary 8 | stellar populations. 9 | These bindings are updated in conjunction with FSPS, and are known to work with 10 | FSPS v3.0. 11 | 12 | 13 | User Guide 14 | ---------- 15 | 16 | .. toctree:: 17 | :maxdepth: 2 18 | 19 | installation 20 | stellarpop_api 21 | filters_api 22 | filters 23 | 24 | 25 | Contributors 26 | ------------ 27 | 28 | .. include:: ../AUTHORS.rst 29 | 30 | 31 | License 32 | ------- 33 | 34 | *Copyright 2013-2023 Python-FSPS developers.* 35 | 36 | These bindings are available under the `MIT License 37 | `_. 38 | 39 | If you make use of this code, please reference these Python bindings, 40 | 41 | .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.591505.svg 42 | :target: https://doi.org/10.5281/zenodo.591505 43 | 44 | and see the `FSPS homepage `_ for additional usage 45 | restrictions and citation requirements. 46 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Prerequisites 5 | ------------- 6 | 7 | Python-FSPS no longer has any compile time prerequisites (besides a working 8 | Fortran compiler), but (if you're not installing using git, see the "development 9 | version" section below) it does require a clone of the `FSPS 10 | `_ project for the data files. To get this 11 | set up, you can clone that repository using: 12 | 13 | .. code-block:: bash 14 | 15 | export SPS_HOME="/path/where/you/want/to/download/fsps" 16 | git clone https://github.com/cconroy20/fsps.git $SPS_HOME 17 | 18 | Where, on different systems, you might have to specify the `SPS_HOME` 19 | environment variable. Python-FSPS requires that variable to be set and it will 20 | fail to import if it is set incorrectly. 21 | 22 | Python-FSPS is built against specific versions of the FSPS Fortran API and data 23 | files, so it is important that you have a recent version of FSPS through git. 24 | Currently Python-FSPS is built against FSPS v3.2. 25 | 26 | Installing stable version 27 | ------------------------- 28 | 29 | Python-FSPS is available as a package on `PyPI 30 | `_ that you can install using `pip`: 31 | 32 | .. code-block:: bash 33 | 34 | python -m pip install fsps 35 | 36 | Choosing Stellar Libraries 37 | --------------------------- 38 | 39 | FSPS can use several different stellar isochrone and spectral libraries, but 40 | switching between these libraries in python-FSPS requires (re-)installing 41 | python-FSPS with specific compiler flags. The available libraries are described 42 | in more detail in the FSPS documentation, but their names are: 43 | 44 | * Stellar Isochrone libraries 45 | 46 | - `MIST` (default) 47 | - `PADOVA` 48 | - `PARSEC` 49 | - `BPASS` (in this case the spectral library and SSP parameters cannot be changed) 50 | - `BASTI` 51 | - `GENEVA` 52 | 53 | * Stellar spectral libraries 54 | 55 | - `MILES` (default) 56 | - `BASEL` 57 | 58 | * Dust emission libraries 59 | 60 | - `DL07` (default) 61 | - `THEMIS` 62 | 63 | Changing any of these libraries requires switching off the relevant default, 64 | which for isochrones is `MIST` and for spectra is `MILES`, and switching on the 65 | desired library. As an example, you can change to Padova isochrones and the 66 | BaSeL low resolution synthetic stellar library by re-installing: 67 | 68 | .. code-block:: bash 69 | 70 | pip uninstall fsps 71 | FFLAGS="-DMIST=0 -DPADOVA=1 -DMILES=0 -DBASEL=1" python -m pip install fsps --no-binary fsps 72 | 73 | where the `--no-binary fsps` flag is required to force building from source. 74 | 75 | Installing development version 76 | ------------------------------ 77 | 78 | Python-FSPS is being actively developed on GitHub so you can got there to get 79 | the most recent development version. You can do this by cloning the `python-fsps 80 | repository `_ and building: 81 | 82 | .. code-block:: bash 83 | 84 | git clone --recursive https://github.com/dfm/python-fsps.git 85 | cd python-fsps 86 | python -m pip install . 87 | 88 | Flags can be prepended to change the stellar libraries as described above. This 89 | repository includes FSPS as a submodule, so if you forget the `--recursive` flag 90 | above, you can get the submodule by running the following commands in the root 91 | directory of the Python-FSPS repository: 92 | 93 | .. code-block:: bash 94 | 95 | git submodule init 96 | git submodule update 97 | 98 | If you install Python-FSPS using this method, you don't actually need a separate 99 | FSPS clone and you can just set the `SPS_HOME` variable as: 100 | 101 | .. code-block:: bash 102 | 103 | export SPS_HOME=$(pwd)/src/fsps/libfsps 104 | 105 | It is recommended that you install using `pip` even for a local clone. In the 106 | root directory of the repository, run: 107 | 108 | .. code-block:: bash 109 | 110 | python -m pip install . 111 | 112 | Starting with version 0.5.0, Python-FSPS no longer supports "development" or 113 | "editable" builds, but the builds should be cached and re-used if you run `pip 114 | install` multiple times. 115 | 116 | Running the unit tests 117 | ---------------------- 118 | 119 | The unit tests are implemented in the `tests` subdirectory, and can be executed 120 | using `nox `_: 121 | 122 | .. code-block:: bash 123 | 124 | python -m pip install nox 125 | python -m nox 126 | 127 | or `pytest `_ directly: 128 | 129 | .. code-block:: bash 130 | 131 | python -m pip install ".[test]" 132 | python -m pytest -n 2 -v tests/tests.py 133 | 134 | where `-n 2` specifies the number of parallel processes to use. The tests can be 135 | quite slow, but also memory intensive, so it can be useful to run them in 136 | parallel, but don't overdo it and run out of memory! 137 | -------------------------------------------------------------------------------- /docs/stellarpop_api.rst: -------------------------------------------------------------------------------- 1 | Modelling Stellar Populations 2 | ============================= 3 | 4 | :class:`fsps.StellarPopulation` drives FSPS and is generally the only API needed. 5 | :func:`fsps.find_filter` can also be used interactively to search for a filter (see also :doc:`filters`). 6 | 7 | Example 8 | ------- 9 | 10 | Lets start by initializing a simple stellar population 11 | with solar metallicity 12 | and some dust with a Calzetti et al (2000) extinction curve:: 13 | 14 | >>> import fsps 15 | >>> sp = fsps.StellarPopulation(compute_vega_mags=False, zcontinuous=1, 16 | sfh=0, logzsol=0.0, dust_type=2, dust2=0.2) 17 | >>> sp.libraries 18 | ('mist', 'miles', 'DL07') 19 | 20 | The last line indicates that we are using the MIST isochrones and MILES spectral 21 | library. These can be changed only by reinstalling python-FSPS with appropriate 22 | flags. 23 | 24 | Let's get the AB magnitudes in SDSS bands, for a simple stellar population (SSP) 25 | that is 13.7 Gyr old:: 26 | 27 | >>> sdss_bands = fsps.find_filter('sdss') 28 | >>> print(sdss_bands) 29 | ['sdss_u', 'sdss_g', 'sdss_i', 'sdss_r', 'sdss_z'] 30 | >>> sp.get_mags(tage=13.7, bands=sdss_bands) 31 | array([ 9.85484694, 7.8785663 , 6.56580511, 6.99828574, 6.16779663]) 32 | 33 | Now we can change a parameter (say, lower the metallicity) and get a new set of magnitudes:: 34 | 35 | >>> sp.params['logzsol'] = -1 36 | >>> sp.get_mags(tage=13.7, bands=sdss_bands) 37 | array([ 8.5626572 , 7.07918435, 6.05304881, 6.38592117, 5.84199 ]) 38 | 39 | We can also get the spectrum, here in units of :math:`L_\odot/\mathrm{Hz}`, 40 | as well as the total stellar mass formed by 13.7 Gyr 41 | and the surviving stellar mass at 13.7 Gyr (both in units of :math:`M_\odot`):: 42 | 43 | >>> wave, spec = sp.get_spectrum(tage=13.7) 44 | >>> sp.formed_mass 45 | 1.0 46 | >>> sp.stellar_mass 47 | 0.57809244144339011 48 | 49 | It is highly recommended that only one instance of :class:`fsps.StellarPopulation` 50 | be used in a given program. 51 | 52 | Example using nebular emission 53 | ------------------------------ 54 | 55 | We initialize a simple stellar population and set the flag to 56 | include nebular emission:: 57 | 58 | >>> sp = fsps.StellarPopulation(zcontinuous=1, 59 | add_neb_emission=1) 60 | 61 | We can change the stellar metallicity, the gas phase metallicity, the 62 | gas ionization parameter, and then return the total spectrum at 1 Myr:: 63 | 64 | >>> sp.params['logzsol'] = -1.0 65 | >>> sp.params['gas_logz'] = -1.0 66 | >>> sp.params['gas_logu'] = -2.5 67 | >>> wave, spec = sp.get_spectrum(tage=0.001) 68 | 69 | Note: for the nebular model to be fully self-consistent, the gas phase 70 | metallicity and the stellar metallicity should be set to the same value. 71 | This effectively adds the emission spectrum to the same stellar spectrum 72 | that was used as the ionizing spectrum in Cloudy. 73 | If users choose to vary the gas phase metallicity at constant stellar 74 | metallicity, expect non-hydrogenic emission lines to be accurate within 1-15%. 75 | 76 | Emission line wavelengths and line luminosities can be accessed through 77 | the stellar population object:: 78 | 79 | >>> sp.emline_wavelengths 80 | >>> sp.emline_luminosity 81 | 82 | 83 | API Reference 84 | ------------- 85 | 86 | .. autoclass:: fsps.StellarPopulation 87 | :inherited-members: 88 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "fsps" 3 | description = "Python bindings for Charlie Conroy's FSPS" 4 | authors = [{ name = "Dan Foreman-Mackey", email = "foreman.mackey@gmail.com" }] 5 | readme = "README.rst" 6 | requires-python = ">=3.9" 7 | license = { text = "MIT License" } 8 | classifiers = [ 9 | "Development Status :: 5 - Production/Stable", 10 | "Intended Audience :: Developers", 11 | "Intended Audience :: Science/Research", 12 | "License :: OSI Approved :: MIT License", 13 | "Operating System :: OS Independent", 14 | "Programming Language :: Python", 15 | ] 16 | dynamic = ["version"] 17 | dependencies = ["numpy"] 18 | 19 | [project.optional-dependencies] 20 | test = ["pytest", "pytest-xdist"] 21 | 22 | [project.urls] 23 | "Homepage" = "https://dfm.io/python-fsps" 24 | "Source" = "https://github.com/dfm/python-fsps" 25 | "Bug Tracker" = "https://github.com/dfm/python-fsps/issues" 26 | 27 | [build-system] 28 | requires = ["scikit-build-core", "numpy"] 29 | build-backend = "scikit_build_core.build" 30 | 31 | [tool.scikit-build] 32 | ninja.minimum-version = "1.10" 33 | cmake.minimum-version = "3.17.2" 34 | sdist.exclude = ["src/fsps/libfsps"] 35 | sdist.include = [ 36 | "src/fsps/libfsps/src/*.f90", 37 | "src/fsps/libfsps/LICENSE", 38 | "src/fsps/libfsps/README", 39 | "src/fsps/fsps_version.py", 40 | ] 41 | metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" 42 | 43 | [tool.setuptools_scm] 44 | write_to = "src/fsps/fsps_version.py" 45 | 46 | [tool.cibuildwheel] 47 | skip = "pp* *-win32 *-musllinux_* *-manylinux_i686" 48 | test-command = "SPS_HOME={project}/src/fsps/libfsps python {project}/tests/simple.py" 49 | environment = { SPS_HOME = "$(pwd)/src/fsps/libfsps" } 50 | 51 | [tool.black] 52 | target-version = ["py38", "py39"] 53 | line-length = 88 54 | 55 | [tool.ruff] 56 | line-length = 88 57 | target-version = "py39" 58 | select = ["F", "I", "E", "W", "YTT", "B", "Q", "PLE", "PLR", "PLW"] 59 | ignore = [ 60 | "E402", # Allow module level imports below top of file 61 | "E741", # Allow ambiguous variable names 62 | "PLW0603", # Allow global statements 63 | "PLR0912", # Allow many branches 64 | "PLR0913", # Allow many arguments to function calls 65 | "PLR2004", # Allow magic numbers 66 | ] 67 | exclude = ["demos/*"] 68 | 69 | [tool.ruff.isort] 70 | known-first-party = ["fsps"] 71 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | -------------------------------------------------------------------------------- /scripts/fsps_filter_table.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | Print a table of filters implemented by FSPS. 5 | 6 | The table is formatted in restructured text for use with the python-fsps's 7 | Sphinx documentation. 8 | """ 9 | 10 | from __future__ import absolute_import, print_function 11 | 12 | import re 13 | 14 | import fsps 15 | 16 | # Pattern via https://gist.github.com/uogbuji/705383 17 | URL_P = re.compile( 18 | r'(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?\xab\xbb\u201c\u201d\u2018\u2019]))' 19 | ) 20 | 21 | 22 | def main(): 23 | colnames = ("ID", "Name", "M_sun Vega", "M_sun AB", "lambda_eff (Å)", "Description") 24 | filters = [fsps.get_filter(name) for name in fsps.list_filters()] 25 | filter_list = make_filter_list(filters) 26 | txt = make_table(filter_list, colnames) 27 | print(txt) 28 | 29 | 30 | def make_filter_list(filters): 31 | """Transform filters into list of table rows.""" 32 | filter_list = [] 33 | filter_ids = [] 34 | for f in filters: 35 | filter_ids.append(f.index) 36 | fullname = URL_P.sub(r"`<\1>`_", f.fullname) 37 | filter_list.append( 38 | ( 39 | str(f.index + 1), 40 | f.name, 41 | "{0:.2f}".format(f.msun_vega), 42 | "{0:.2f}".format(f.msun_ab), 43 | "{0:.1f}".format(f.lambda_eff), 44 | fullname, 45 | ) 46 | ) 47 | 48 | def sortf(item): 49 | return int(item[0]) 50 | 51 | filter_list.sort(key=sortf) 52 | return filter_list 53 | 54 | 55 | def make_table(data, col_names): 56 | """Code for this RST-formatted table generator comes from 57 | http://stackoverflow.com/a/11350643 58 | """ 59 | n_cols = len(data[0]) 60 | assert n_cols == len(col_names) 61 | col_sizes = [max(len(r[i]) for r in data) for i in range(n_cols)] 62 | for i, cname in enumerate(col_names): 63 | col_sizes[i] = max(col_sizes[i], len(cname)) 64 | formatter = " ".join("{:<%d}" % c for c in col_sizes) 65 | rows = "\n".join([formatter.format(*row) for row in data]) 66 | header = formatter.format(*col_names) 67 | divider = formatter.format(*["=" * c for c in col_sizes]) 68 | output = "\n".join((divider, header, divider, rows, divider)) 69 | return output 70 | 71 | 72 | if __name__ == "__main__": 73 | main() 74 | -------------------------------------------------------------------------------- /src/fsps/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/sps_vars.f90") 2 | message(FATAL_ERROR "The source code in the FSPS submodule was not found." 3 | "Please run 'git submodule update --init' to initialize the submodule.") 4 | endif() 5 | 6 | # Generate the f2py wrappers 7 | set(F2PY_SOURCES 8 | "${CMAKE_CURRENT_BINARY_DIR}/_fspsmodule.c" 9 | "${CMAKE_CURRENT_BINARY_DIR}/_fsps-f2pywrappers2.f90" 10 | ) 11 | add_custom_command( 12 | OUTPUT ${F2PY_SOURCES} 13 | DEPENDS fsps.f90 14 | VERBATIM 15 | COMMAND "${Python_EXECUTABLE}" -m numpy.f2py 16 | "${CMAKE_CURRENT_SOURCE_DIR}/fsps.f90" -m _fsps --lower 17 | --build-dir "${CMAKE_CURRENT_BINARY_DIR}") 18 | 19 | # List out the explicit FSPS sources; we don't use a glob here since 20 | # the 'src' directory includes some executables that we don't want to 21 | # include here. 22 | set( 23 | FSPS_SOURCES 24 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/sps_vars.f90" 25 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/sps_utils.f90" 26 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/add_agb_dust.f90" 27 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/add_bs.f90" 28 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/add_dust.f90" 29 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/add_nebular.f90" 30 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/add_remnants.f90" 31 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/add_xrb.f90" 32 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/agn_dust.f90" 33 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/attn_curve.f90" 34 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/compsp.f90" 35 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/csp_gen.f90" 36 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/funcint.f90" 37 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/get_lumdist.f90" 38 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/get_tuniv.f90" 39 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/getindx.f90" 40 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/getmags.f90" 41 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/getspec.f90" 42 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/igm_absorb.f90" 43 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/imf.f90" 44 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/imf_weight.f90" 45 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/intsfwght.f90" 46 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/linterp.f90" 47 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/linterparr.f90" 48 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/locate.f90" 49 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/mod_gb.f90" 50 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/mod_hb.f90" 51 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/pz_convol.f90" 52 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/sbf.f90" 53 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/setup_tabular_sfh.f90" 54 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/sfh_weight.f90" 55 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/sfhinfo.f90" 56 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/sfhlimit.f90" 57 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/sfhstat.f90" 58 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/smoothspec.f90" 59 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/spec_bin.f90" 60 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/sps_setup.f90" 61 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/ssp_gen.f90" 62 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/tsum.f90" 63 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/vacairconv.f90" 64 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/write_isochrone.f90" 65 | "${CMAKE_CURRENT_SOURCE_DIR}/libfsps/src/ztinterp.f90" 66 | ) 67 | 68 | # Define the Python library 69 | python_add_library( 70 | _fsps 71 | MODULE 72 | "${CMAKE_CURRENT_SOURCE_DIR}/fsps.f90" 73 | ${FSPS_SOURCES} 74 | ${F2PY_SOURCES} 75 | "${F2PY_INCLUDE_DIR}/fortranobject.c" 76 | WITH_SOABI) 77 | target_link_libraries(_fsps PUBLIC Python::NumPy) 78 | target_include_directories(_fsps PUBLIC "${F2PY_INCLUDE_DIR}") 79 | install(TARGETS _fsps DESTINATION ${SKBUILD_PROJECT_NAME}) 80 | -------------------------------------------------------------------------------- /src/fsps/__init__.py: -------------------------------------------------------------------------------- 1 | # Check the that SPS_HOME variable is set properly 2 | from fsps.sps_home import check_sps_home 3 | 4 | check_sps_home() 5 | 6 | del check_sps_home 7 | # End check 8 | 9 | from fsps.filters import find_filter as find_filter 10 | from fsps.filters import get_filter as get_filter 11 | from fsps.filters import list_filters as list_filters 12 | from fsps.fsps import StellarPopulation as StellarPopulation 13 | from fsps.fsps_version import __version__ as __version__ 14 | -------------------------------------------------------------------------------- /src/fsps/filters.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tools for working with the FSPS filter set. 3 | 4 | This module uses filter information shipped with FSPS itself in 5 | ``$SPS_HOME/data``. 6 | """ 7 | 8 | __all__ = ["find_filter", "FILTERS", "get_filter", "list_filters"] 9 | 10 | import os 11 | 12 | import numpy as np 13 | 14 | # Cache for $SPS_HOME/data/magsun.dat parsed by numpy 15 | MSUN_TABLE = None 16 | 17 | # Cache for $SPS_HOME/data/filter_lambda_eff.dat parsed by numpy 18 | LAMBDA_EFF_TABLE = None 19 | 20 | # Cache for bandpass transmission listings: a dictionary keyed by bandpass 21 | # name with values of wavelength, transmission tuples. 22 | TRANS_CACHE = None 23 | 24 | 25 | class Filter(object): 26 | def __init__(self, index, name, fullname): 27 | self.index = index - 1 28 | self.name = name.lower() 29 | self.fullname = fullname 30 | 31 | def __str__(self): 32 | return "".format(self.name) 33 | 34 | def __repr__(self): 35 | return "".format(self.name) 36 | 37 | @property 38 | def msun_ab(self): 39 | """Solar absolute magnitude in Filter, AB zeropoint.""" 40 | # if self._msun_ab is None: 41 | if MSUN_TABLE is None: 42 | self._load_msun_table() 43 | if self.index < MSUN_TABLE.shape[0]: 44 | assert MSUN_TABLE[self.index, 0] == self.index + 1 45 | return float(MSUN_TABLE[self.index, 1]) 46 | else: 47 | return np.nan 48 | 49 | @property 50 | def msun_vega(self): 51 | """Solar absolute magnitude in Filter, VEGAMAG zeropoint.""" 52 | if MSUN_TABLE is None: 53 | self._load_msun_table() 54 | if self.index < MSUN_TABLE.shape[0]: 55 | assert MSUN_TABLE[self.index, 0] == self.index + 1 56 | return float(MSUN_TABLE[self.index, 2]) 57 | else: 58 | return np.nan 59 | 60 | @property 61 | def lambda_eff(self): 62 | """Effective wavelength of Filter, in Angstroms.""" 63 | if LAMBDA_EFF_TABLE is None: 64 | self._load_lambda_eff_table() 65 | if self.index < LAMBDA_EFF_TABLE.shape[0]: 66 | assert LAMBDA_EFF_TABLE[self.index, 0] == self.index + 1 67 | return float(LAMBDA_EFF_TABLE[self.index, 1]) 68 | else: 69 | return np.nan 70 | 71 | @property 72 | def transmission(self): 73 | """Returns filter transmission: a tuple of wavelength (Angstroms) and 74 | an un-normalized transmission arrays. 75 | """ 76 | if TRANS_CACHE is None: 77 | # Load the cache for all filters. 78 | self._load_transmission_cache() 79 | try: 80 | return TRANS_CACHE[self.name] 81 | except KeyError as e: 82 | e.args += "Could not find transmission data " "for {0}".format(self.name) 83 | raise 84 | 85 | def _load_msun_table(self): 86 | global MSUN_TABLE 87 | MSUN_TABLE = np.loadtxt(os.path.expandvars("$SPS_HOME/data/magsun.dat")) 88 | 89 | def _load_lambda_eff_table(self): 90 | global LAMBDA_EFF_TABLE 91 | LAMBDA_EFF_TABLE = np.loadtxt( 92 | os.path.expandvars("$SPS_HOME/data/filter_lambda_eff.dat") 93 | ) 94 | 95 | def _load_transmission_cache(self): 96 | """Parse the allfilters.dat file into the TRANS_CACHE.""" 97 | global TRANS_CACHE 98 | path = os.path.expandvars("$SPS_HOME/data/allfilters.dat") 99 | names = list_filters() 100 | TRANS_CACHE = {} 101 | filter_index = -1 102 | lambdas, trans = [], [] 103 | with open(path) as f: 104 | for line in f: 105 | line.strip() 106 | if line[0].startswith("#"): 107 | # Close out filter 108 | if filter_index > -1: 109 | TRANS_CACHE[names[filter_index]] = ( 110 | np.array(lambdas), 111 | np.array(trans), 112 | ) 113 | # Start new filter 114 | filter_index += 1 115 | lambdas, trans = [], [] 116 | else: 117 | try: 118 | l, t = line.split() 119 | lambdas.append(float(l)) 120 | trans.append(float(t)) 121 | except ValueError: 122 | pass 123 | # Close out the last filter 124 | TRANS_CACHE[names[filter_index]] = (np.array(lambdas), np.array(trans)) 125 | 126 | 127 | def _load_filter_dict(): 128 | """ 129 | Load the filter list, creating a dictionary of :class:`Filter` instances. 130 | """ 131 | # Load filter table from FSPS 132 | filter_list_path = os.path.expandvars( 133 | os.path.join("$SPS_HOME", "data", "FILTER_LIST") 134 | ) 135 | filters = {} 136 | with open(filter_list_path) as f: 137 | for line in f: 138 | columns = line.strip().split() 139 | if len(columns) > 1: 140 | fsps_id, key = columns[:2] 141 | else: 142 | continue 143 | comment = " ".join(columns[2:]) 144 | filters[key.lower()] = Filter(int(fsps_id), key, comment) 145 | 146 | return filters 147 | 148 | 149 | FILTERS = _load_filter_dict() 150 | 151 | 152 | def find_filter(band): 153 | """ 154 | Find the FSPS name for a filter. 155 | 156 | Usage: 157 | 158 | :: 159 | 160 | >>> import fsps 161 | >>> fsps.find_filter("F555W") 162 | ['wfpc2_f555w', 'wfc_acs_f555w'] 163 | 164 | :param band: 165 | Something like the name of the band. 166 | 167 | """ 168 | b = band.lower() 169 | possible = [] 170 | for k in FILTERS.keys(): 171 | if b in k: 172 | possible.append(k) 173 | return possible 174 | 175 | 176 | def get_filter(name): 177 | """Returns the :class:`fsps.filters.Filter` instance associated with the 178 | filter name. 179 | 180 | :param name: 181 | Name of the filter, as found with :func:`find_filter`. 182 | """ 183 | try: 184 | return FILTERS[name.lower()] 185 | except KeyError as e: 186 | e.args += ( 187 | "Filter {0} does not exist. " 188 | "Try using fsps.find_filter('{0}').".format(name), 189 | ) 190 | raise 191 | 192 | 193 | def list_filters(): 194 | """Returns a list of all FSPS filter names. 195 | 196 | Filters are sorted by their FSPS index. 197 | """ 198 | lst = [(name, f.index) for name, f in FILTERS.items()] 199 | lst.sort(key=lambda x: x[1]) 200 | return [l[0] for l in lst] 201 | -------------------------------------------------------------------------------- /src/fsps/fsps.f90: -------------------------------------------------------------------------------- 1 | module driver 2 | 3 | use sps_vars 4 | use sps_utils 5 | implicit none 6 | save 7 | 8 | !f2py intent(hide) pset 9 | type(PARAMS) :: pset 10 | 11 | !f2py intent(hide) ocompsp 12 | type(COMPSPOUT), dimension(ntfull) :: ocompsp 13 | 14 | integer :: is_setup=0 15 | 16 | !f2py intent(hide) has_ssp 17 | integer, dimension(nz) :: has_ssp=0 18 | 19 | !f2py intent(hide) has_ssp_age 20 | integer, dimension(nz,nt) :: has_ssp_age=0 21 | 22 | 23 | contains 24 | 25 | subroutine setup(compute_vega_mags0, vactoair_flag0) 26 | 27 | ! Load all the data files/templates into memory. 28 | 29 | implicit none 30 | 31 | integer, intent(in) :: compute_vega_mags0, vactoair_flag0 32 | 33 | 34 | compute_vega_mags = compute_vega_mags0 35 | vactoair_flag = vactoair_flag0 36 | call sps_setup(-1) 37 | is_setup = 1 38 | 39 | ! We will only compute mags when asked for through get_mags. 40 | pset%mag_compute=0 41 | 42 | end subroutine 43 | 44 | subroutine set_ssp_params(imf_type0,imf_upper_limit0,imf_lower_limit0,& 45 | imf1,imf2,imf3,vdmc,mdave,dell,& 46 | delt,sbss,fbhb,pagb,add_stellar_remnants0,& 47 | tpagb_norm_type0,add_agb_dust_model0,agb_dust,& 48 | redgb,agb,masscut,fcstar,evtype,use_wr_spectra0,& 49 | logt_wmb_hot0,add_xrb_emission0,frac_xrb,smooth_lsf0) 50 | 51 | ! Set the parameters that affect the SSP computation. 52 | 53 | implicit none 54 | 55 | integer, intent(in) :: imf_type0,add_stellar_remnants0,tpagb_norm_type0,& 56 | add_agb_dust_model0,use_wr_spectra0,add_xrb_emission0,smooth_lsf0 57 | double precision, intent(in) :: imf_upper_limit0, imf_lower_limit0,& 58 | imf1,imf2,imf3,vdmc,mdave,dell,& 59 | delt,sbss,fbhb,pagb,agb_dust,& 60 | redgb,agb,masscut,fcstar,evtype,& 61 | logt_wmb_hot0,frac_xrb 62 | 63 | imf_type=imf_type0 64 | imf_upper_limit=imf_upper_limit0 65 | imf_lower_limit=imf_lower_limit0 66 | add_stellar_remnants=add_stellar_remnants0 67 | tpagb_norm_type=tpagb_norm_type0 68 | add_agb_dust_model=add_agb_dust_model0 69 | use_wr_spectra=use_wr_spectra0 70 | logt_wmb_hot=logt_wmb_hot0 71 | smooth_lsf=smooth_lsf0 72 | add_xrb_emission=add_xrb_emission0 73 | pset%imf1=imf1 74 | pset%imf2=imf2 75 | pset%imf3=imf3 76 | pset%vdmc=vdmc 77 | pset%mdave=mdave 78 | pset%dell=dell 79 | pset%delt=delt 80 | pset%sbss=sbss 81 | pset%fbhb=fbhb 82 | pset%pagb=pagb 83 | pset%agb_dust=agb_dust 84 | pset%redgb=redgb 85 | pset%agb=agb 86 | pset%masscut=masscut 87 | pset%fcstar=fcstar 88 | pset%evtype=evtype 89 | pset%frac_xrb=frac_xrb 90 | 91 | has_ssp(:) = 0 92 | has_ssp_age(:,:) = 0 93 | 94 | end subroutine 95 | 96 | subroutine set_csp_params(smooth_velocity0,redshift_colors0,& 97 | compute_light_ages0,nebemlineinspec0,& 98 | dust_type0,add_dust_emission0,add_neb_emission0,& 99 | add_neb_continuum0,cloudy_dust0,add_igm_absorption0,& 100 | zmet,sfh,wgp1,wgp2,wgp3,tau,& 101 | const,tage,fburst,tburst,dust1,dust2,dust3,& 102 | logzsol,zred,pmetals,dust_clumps,frac_nodust,& 103 | dust_index,dust_tesc,frac_obrun,uvb,mwr,& 104 | dust1_index,sf_start,sf_trunc,sf_slope,& 105 | duste_gamma,duste_umin,duste_qpah,& 106 | sigma_smooth,min_wave_smooth,max_wave_smooth,& 107 | gas_logu,gas_logz,igm_factor,fagn,agn_tau) 108 | 109 | ! Set all the parameters that don't affect the SSP computation. 110 | 111 | implicit none 112 | 113 | integer, intent(in) :: smooth_velocity0,redshift_colors0,& 114 | compute_light_ages0,nebemlineinspec0,& 115 | dust_type0,add_dust_emission0,add_neb_emission0,& 116 | add_neb_continuum0,cloudy_dust0,add_igm_absorption0,& 117 | zmet,sfh,wgp1,wgp2,wgp3 118 | double precision, intent(in) :: tau,& 119 | const,tage,fburst,tburst,dust1,dust2,dust3,& 120 | logzsol,zred,pmetals,dust_clumps,frac_nodust,& 121 | dust_index,dust_tesc,frac_obrun,uvb,mwr,& 122 | dust1_index,sf_start,sf_trunc,sf_slope,& 123 | duste_gamma,duste_umin,duste_qpah,& 124 | sigma_smooth,min_wave_smooth,max_wave_smooth,& 125 | gas_logu,gas_logz,igm_factor,fagn,agn_tau 126 | 127 | smooth_velocity=smooth_velocity0 128 | redshift_colors=redshift_colors0 129 | compute_light_ages=compute_light_ages0 130 | nebemlineinspec=nebemlineinspec0 131 | dust_type=dust_type0 132 | add_dust_emission=add_dust_emission0 133 | add_neb_emission=add_neb_emission0 134 | add_neb_continuum=add_neb_continuum0 135 | cloudy_dust=cloudy_dust0 136 | add_igm_absorption=add_igm_absorption0 137 | 138 | pset%zmet=zmet 139 | pset%sfh=sfh 140 | pset%wgp1=wgp1 141 | pset%wgp2=wgp2 142 | pset%wgp3=wgp3 143 | 144 | pset%tau=tau 145 | pset%const=const 146 | pset%tage=tage 147 | pset%fburst=fburst 148 | pset%tburst=tburst 149 | pset%dust1=dust1 150 | pset%dust2=dust2 151 | pset%dust3=dust3 152 | pset%logzsol=logzsol 153 | pset%zred=zred 154 | pset%pmetals=pmetals 155 | pset%dust_clumps=dust_clumps 156 | pset%frac_nodust=frac_nodust 157 | pset%dust_index=dust_index 158 | pset%dust_tesc=dust_tesc 159 | pset%frac_obrun=frac_obrun 160 | pset%uvb=uvb 161 | pset%mwr=mwr 162 | pset%dust1_index=dust1_index 163 | pset%sf_start=sf_start 164 | pset%sf_trunc=sf_trunc 165 | pset%sf_slope=sf_slope 166 | pset%duste_gamma=duste_gamma 167 | pset%duste_umin=duste_umin 168 | pset%duste_qpah=duste_qpah 169 | pset%sigma_smooth=sigma_smooth 170 | pset%min_wave_smooth=min_wave_smooth 171 | pset%max_wave_smooth=max_wave_smooth 172 | pset%gas_logu=gas_logu 173 | pset%gas_logz=gas_logz 174 | pset%igm_factor=igm_factor 175 | pset%fagn=fagn 176 | pset%agn_tau=agn_tau 177 | 178 | end subroutine 179 | 180 | subroutine ssps 181 | 182 | ! Loop over the metallicity grid and compute all the SSPs. 183 | 184 | implicit none 185 | integer :: zi 186 | do zi=1,nz 187 | call ssp(zi) 188 | enddo 189 | 190 | end subroutine 191 | 192 | subroutine ssp(zi) 193 | 194 | ! Compute a SSP at a single metallicity. 195 | 196 | implicit none 197 | integer, intent(in) :: zi 198 | pset%zmet = zi 199 | call ssp_gen(pset, mass_ssp_zz(:,zi),lbol_ssp_zz(:,zi),& 200 | spec_ssp_zz(:,:,zi)) 201 | if (minval(pset%ssp_gen_age) .eq. 1) then 202 | has_ssp(zi) = 1 203 | endif 204 | has_ssp_age(zi,:) = pset%ssp_gen_age 205 | 206 | end subroutine 207 | 208 | subroutine compute_zdep(ns,n_age,ztype) 209 | 210 | ! Compute the full CSP (and the SSPs if they aren't already cached). 211 | ! After interpolation in metallicity 212 | 213 | implicit none 214 | integer, intent(in) :: ns,n_age,ztype 215 | double precision, dimension(ns,n_age) :: spec 216 | double precision, dimension(n_age) :: mass,lbol 217 | integer :: zlo,zmet 218 | double precision :: zpos 219 | character(100) :: outfile 220 | 221 | if (ztype .eq. 0) then 222 | ! Build the SSP for one metallicity, then feed to compsp 223 | zmet = pset%zmet 224 | if (has_ssp(zmet) .eq. 0) then 225 | call ssp(zmet) 226 | endif 227 | !mass = mass_ssp_zz(:,zmet) 228 | !lbol = lbol_ssp_zz(:,zmet) 229 | !spec = spec_ssp_zz(:,:,zmet) 230 | call compsp(0,1,outfile,mass_ssp_zz(:,zmet),lbol_ssp_zz(:,zmet),& 231 | spec_ssp_zz(:,:,zmet),pset,ocompsp) 232 | endif 233 | 234 | if (ztype .eq. 1) then 235 | zpos = pset%logzsol 236 | ! Find the bracketing metallicity indices and generate ssps if 237 | ! necessary, then interpolate, and feed the result to compsp 238 | zlo = max(min(locate(log10(zlegend/zsol),zpos),nz-1),1) 239 | do zmet=zlo,zlo+1 240 | if (has_ssp(zmet) .eq. 0) then 241 | call ssp(zmet) 242 | endif 243 | enddo 244 | call ztinterp(zpos,spec,lbol,mass) 245 | call compsp(0,1,outfile,mass,lbol,spec,pset,ocompsp) 246 | endif 247 | 248 | if (ztype .eq. 2) then 249 | zpos = pset%logzsol 250 | ! Build the SSPs for *every* metallicity if necessary, then 251 | ! comvolve with the MDF, and then feed to compsp 252 | do zmet=1,nz 253 | if (has_ssp(zmet) .eq. 0) then 254 | call ssp(zmet) 255 | endif 256 | enddo 257 | call ztinterp(zpos,spec,lbol,mass,zpow=pset%pmetals) 258 | call compsp(0,1,outfile,mass,lbol,spec,pset,ocompsp) 259 | endif 260 | 261 | if (ztype .eq. 3) then 262 | ! Build the SSPs for *every* metallicity and feed all of them to compsp 263 | ! for z-dependent tabular 264 | do zmet=1,nz 265 | if (has_ssp(zmet) .eq. 0) then 266 | call ssp(zmet) 267 | endif 268 | enddo 269 | call compsp(0,nz,outfile,mass_ssp_zz,lbol_ssp_zz,& 270 | spec_ssp_zz,pset,ocompsp) 271 | endif 272 | 273 | 274 | end subroutine 275 | 276 | subroutine get_spec(ns,n_age,spec_out) 277 | 278 | ! Get the grid of spectra for the computed CSP at all ages. 279 | 280 | implicit none 281 | integer :: i 282 | integer, intent(in) :: ns,n_age 283 | double precision, dimension(n_age,ns), intent(out) :: spec_out 284 | do i=1,n_age 285 | spec_out(i,:) = ocompsp(i)%spec 286 | enddo 287 | 288 | end subroutine 289 | 290 | subroutine get_mags(ns,n_age,n_bands,z_red,mc,mags) 291 | 292 | ! Get the photometric magnitudes in all the recognized bands. 293 | 294 | implicit none 295 | integer :: i 296 | integer, intent(in) :: ns, n_age, n_bands 297 | double precision, intent(in) :: z_red 298 | integer, dimension(n_bands), intent(in) :: mc 299 | double precision, dimension(n_age,n_bands), intent(out) :: mags 300 | double precision, dimension(ns) :: tspec 301 | do i=1,n_age 302 | tspec = ocompsp(i)%spec 303 | call getmags(z_red,tspec,mags(i,:),mc) 304 | enddo 305 | 306 | end subroutine 307 | 308 | subroutine interp_ssp(ns,zpos,tpos,spec,mass,lbol) 309 | 310 | ! Return the SSPs interpolated to the target metallicity 311 | !(zpos) and target age (tpos) 312 | 313 | implicit none 314 | 315 | integer, intent(in) :: ns 316 | double precision, intent(in) :: zpos 317 | double precision, intent(in) :: tpos 318 | 319 | double precision, dimension(ns,1), intent(inout) :: spec 320 | double precision, dimension(1), intent(inout) :: mass,lbol 321 | 322 | double precision, dimension(nt) :: time 323 | 324 | integer :: zlo,zmet,tlo 325 | 326 | zlo = max(min(locate(log10(zlegend/0.0190),zpos),nz-1),1) 327 | time = timestep_isoc(zlo,:) 328 | tlo = max(min(locate(time,tpos),nt-1),1) 329 | 330 | do zmet=zlo,zlo+1 331 | if ((has_ssp_age(zmet,tlo) .eq. 0) .or. (has_ssp_age(zmet,tlo+1) .eq. 0)) then 332 | pset%ssp_gen_age = 0 333 | pset%ssp_gen_age(tlo:tlo+1) = 1 334 | call ssp(zmet) 335 | pset%ssp_gen_age = 1 336 | endif 337 | enddo 338 | 339 | call ztinterp(zpos,spec,lbol,mass,tpos=tpos) 340 | 341 | end subroutine 342 | 343 | 344 | subroutine smooth_spectrum(ns,wave,spec,sigma_broad,minw,maxw) 345 | 346 | ! Smooth the spectrum by a gaussian of width sigma_broad 347 | 348 | implicit none 349 | integer, intent(in) :: ns 350 | double precision, intent(in) :: sigma_broad,minw,maxw 351 | double precision, dimension(ns), intent(in) :: wave 352 | double precision, dimension(ns), intent(inout) :: spec 353 | 354 | call smoothspec(wave,spec,sigma_broad,minw,maxw) 355 | 356 | end subroutine 357 | 358 | subroutine stellar_spectrum(ns,mact,logt,lbol,logg,phase,ffco,lmdot,wght,spec_out) 359 | 360 | ! Get a stellar spectrum for a given set of parameters 361 | 362 | implicit none 363 | integer :: i 364 | integer, intent(in) :: ns 365 | double precision, intent(in) :: mact, logt, lbol, logg, phase, ffco, lmdot, wght 366 | double precision, dimension(ns), intent(inout) :: spec_out 367 | 368 | call getspec(pset,mact,logt,lbol,logg,phase,ffco,lmdot,wght,spec_out) 369 | 370 | end subroutine 371 | 372 | subroutine get_ssp_weights(n_age, n_z, ssp_wghts_out) 373 | 374 | ! Return the weights of each SSP in the CSP 375 | 376 | implicit none 377 | integer, intent(in) :: n_age,n_z 378 | double precision, dimension(n_age,n_z), intent(inout) :: ssp_wghts_out 379 | ssp_wghts_out = weight_ssp 380 | 381 | end subroutine 382 | 383 | subroutine get_csp_components(ns, csp1, csp2) 384 | 385 | ! Return the unattenuated 'young' and 'old' stellar continuua 386 | 387 | implicit none 388 | integer, intent(in) :: ns 389 | double precision, dimension(ns), intent(inout) :: csp1, csp2 390 | csp1 = spec_young 391 | csp2 = spec_old 392 | 393 | end subroutine 394 | 395 | 396 | subroutine get_ssp_spec(ns,n_age,n_z,ssp_spec_out,ssp_mass_out,ssp_lbol_out) 397 | 398 | ! Return the contents of the ssp spectral array, 399 | ! regenerating the ssps if necessary 400 | 401 | implicit none 402 | integer, intent(in) :: ns,n_age,n_z 403 | integer :: zi 404 | double precision, dimension(ns,n_age,n_z), intent(inout) :: ssp_spec_out 405 | double precision, dimension(n_age,n_z), intent(inout) :: ssp_mass_out, ssp_lbol_out 406 | do zi=1,nz 407 | if (has_ssp(zi) .eq. 0) then 408 | call ssp(zi) 409 | endif 410 | enddo 411 | 412 | ssp_spec_out = spec_ssp_zz 413 | ssp_mass_out = mass_ssp_zz 414 | ssp_lbol_out = lbol_ssp_zz 415 | 416 | end subroutine 417 | 418 | subroutine set_sfh_tab(ntab, age, sfr, met) 419 | 420 | ! Fill the sfh_tab array 421 | 422 | implicit none 423 | integer, intent(in) :: ntab 424 | double precision, dimension(ntab), intent(in) :: age, sfr, met 425 | ntabsfh = ntab 426 | sfh_tab(1,1:ntabsfh) = age 427 | sfh_tab(2, 1:ntabsfh) = sfr 428 | sfh_tab(3, 1:ntabsfh) = met 429 | 430 | end subroutine 431 | 432 | subroutine set_ssp_lsf(nsv, sigma, wlo, whi) 433 | 434 | ! Fill the lsfinfo structure 435 | 436 | implicit none 437 | integer, intent(in) :: nsv 438 | double precision, dimension(nsv), intent(in) :: sigma 439 | double precision, intent(in) :: wlo, whi 440 | lsfinfo%minlam = wlo 441 | lsfinfo%maxlam = whi 442 | lsfinfo%lsf = sigma 443 | 444 | end subroutine 445 | 446 | subroutine get_setup_vars(cvms, vta_flag) 447 | 448 | implicit none 449 | integer, intent(out) :: cvms, vta_flag 450 | cvms = compute_vega_mags 451 | vta_flag = vactoair_flag 452 | 453 | end subroutine 454 | 455 | subroutine get_nz(n_z) 456 | 457 | ! Get the number of metallicity bins (hard coded in sps_vars). 458 | implicit none 459 | integer, intent(out) :: n_z 460 | n_z = nz 461 | 462 | end subroutine 463 | 464 | subroutine get_zlegend(n_z,z_legend) 465 | 466 | ! Get the available metallicity values. 467 | implicit none 468 | integer, intent(in) :: n_z 469 | double precision, dimension(n_z), intent(out) :: z_legend 470 | z_legend = zlegend 471 | 472 | end subroutine 473 | 474 | subroutine get_zsol(z_sol) 475 | 476 | ! Get the definition of solar metallicity. 477 | implicit none 478 | double precision, intent(out) :: z_sol 479 | z_sol = zsol 480 | 481 | end subroutine 482 | 483 | subroutine get_timefull(n_age,timefull) 484 | 485 | ! Get the actual time steps of the SSPs. 486 | implicit none 487 | integer, intent(in) :: n_age 488 | double precision, dimension(n_age), intent(out) :: timefull 489 | 490 | timefull = time_full 491 | 492 | end subroutine 493 | 494 | subroutine get_ntfull(n_age) 495 | 496 | ! Get the total number of time steps (hard coded in sps_vars). 497 | implicit none 498 | integer, intent(out) :: n_age 499 | n_age = ntfull 500 | 501 | end subroutine 502 | 503 | subroutine get_nspec(ns) 504 | 505 | ! Get the number of wavelength bins in the spectra (hard coded in 506 | ! sps_vars). 507 | implicit none 508 | integer, intent(out) :: ns 509 | ns = nspec 510 | 511 | end subroutine 512 | 513 | subroutine get_nbands(nb) 514 | 515 | ! Get the number of known filters (hard coded in sps_vars). 516 | implicit none 517 | integer, intent(out) :: nb 518 | nb = nbands 519 | 520 | end subroutine 521 | 522 | subroutine get_nemline(nline) 523 | 524 | ! Get the number of emission lines (hard coded in sps_vars). 525 | implicit none 526 | integer, intent(out) :: nline 527 | nline = nemline 528 | 529 | end subroutine 530 | 531 | subroutine get_emlambda(nline,em_lambda) 532 | 533 | ! Get the emission line wavelengths 534 | implicit none 535 | integer, intent(in) :: nline 536 | double precision, dimension(nline), intent(out) :: em_lambda 537 | if (vactoair_flag .eq. 1) then 538 | em_lambda = vactoair(nebem_line_pos) 539 | else 540 | em_lambda = nebem_line_pos 541 | endif 542 | 543 | end subroutine 544 | 545 | subroutine get_lambda(ns,lambda) 546 | 547 | ! Get the grid of wavelength bins. 548 | implicit none 549 | integer, intent(in) :: ns 550 | double precision, dimension(ns), intent(out) :: lambda 551 | if (vactoair_flag .eq. 1) then 552 | lambda = vactoair(spec_lambda) 553 | else 554 | lambda = spec_lambda 555 | endif 556 | 557 | end subroutine 558 | 559 | subroutine get_res(ns,res) 560 | 561 | ! Get the resolution array of the spectral library 562 | implicit none 563 | integer, intent(in) :: ns 564 | double precision, dimension(ns), intent(out) :: res 565 | res = spec_res 566 | 567 | end subroutine 568 | 569 | subroutine get_libraries(isocname,specname,dustname) 570 | 571 | implicit none 572 | 573 | character(4), intent(out) :: isocname 574 | character(5), intent(out) :: specname 575 | character(6), intent(out) :: dustname 576 | isocname = isoc_type 577 | specname = spec_type 578 | dustname = str_dustem 579 | 580 | end subroutine 581 | 582 | subroutine get_isochrone_dimensions(n_age,n_mass) 583 | 584 | implicit none 585 | 586 | ! Get the dimensions of the produced isochrones. 587 | integer, intent(out) :: n_age,n_mass 588 | n_age = nt 589 | n_mass = n_mass 590 | 591 | end subroutine 592 | 593 | subroutine get_nmass_isochrone(zz, tt, nmass) 594 | 595 | implicit none 596 | 597 | ! Get the number of masses included in a specific isochrone. 598 | integer, intent(in) :: zz,tt 599 | integer, intent(out) :: nmass 600 | nmass = nmass_isoc(zz,tt) 601 | 602 | end subroutine 603 | 604 | subroutine get_stats(n_age,nline,age,mass_csp,lbol_csp,sfr,mdust,mformed,emlines) 605 | 606 | implicit none 607 | 608 | ! Get some stats about the computed SP. 609 | integer :: i 610 | integer, intent(in) :: n_age,nline 611 | double precision, dimension(n_age), intent(out) :: age,mass_csp,& 612 | lbol_csp,sfr,mdust,& 613 | mformed 614 | double precision, dimension(n_age,nline), intent(out) :: emlines 615 | 616 | do i=1,n_age 617 | age(i) = ocompsp(i)%age 618 | mass_csp(i) = ocompsp(i)%mass_csp 619 | lbol_csp(i) = ocompsp(i)%lbol_csp 620 | sfr(i) = ocompsp(i)%sfr 621 | mdust(i) = ocompsp(i)%mdust 622 | mformed(i) = ocompsp(i)%mformed 623 | emlines(i,:) = ocompsp(i)%emlines 624 | enddo 625 | 626 | end subroutine 627 | 628 | subroutine get_filter_data(nb, wave_eff, mag_vega, mag_sun) 629 | 630 | !get info about the filters 631 | implicit none 632 | integer, intent(in) :: nb 633 | double precision, dimension(nb), intent(out) :: wave_eff,mag_vega,mag_sun 634 | wave_eff = filter_leff 635 | mag_vega = magvega - magvega(1) 636 | mag_sun = magsun 637 | 638 | end subroutine 639 | 640 | subroutine write_isoc(outfile) 641 | 642 | implicit none 643 | 644 | character(100), intent(in) :: outfile 645 | 646 | call write_isochrone(outfile, pset) 647 | 648 | end subroutine 649 | 650 | end module 651 | -------------------------------------------------------------------------------- /src/fsps/fsps.py: -------------------------------------------------------------------------------- 1 | __all__ = ["StellarPopulation"] 2 | 3 | import os 4 | 5 | import numpy as np 6 | 7 | from fsps._fsps import driver 8 | from fsps.filters import FILTERS 9 | 10 | 11 | class StellarPopulation(object): 12 | r""" 13 | This is the main interface to use when interacting with FSPS from Python. 14 | Most of the Fortran API is exposed through Python hooks with various 15 | features added for user friendliness. It is recommended to only 16 | instantiate one StellarPopulation object in a given program. When 17 | initializing, you can set any of the parameters of the system using keyword 18 | arguments. Below, you'll find a list of the options that you can include 19 | (with the comments taken directly from the `FSPS docs 20 | `_). Unless 21 | otherwise noted, you can change these values later using the ``params`` 22 | property—which is ``dict``-like. For example: 23 | 24 | :: 25 | 26 | sp = StellarPopulation(imf_type=2, zcontinuous=1) 27 | sp.params["imf_type"] = 1 28 | sp.params["logzsol"] = -0.3 29 | sp.params["sfh"] = 1 30 | 31 | :param compute_vega_mags: (default: False) 32 | A switch that sets the zero points of the magnitude system: ``True`` 33 | uses Vega magnitudes versus AB magnitudes. Can only be changed during 34 | initialization. 35 | 36 | :param vactoair_flag: (default: False) 37 | If ``True``, output wavelengths in air (rather than vac). Can only be 38 | changed during initialization. 39 | 40 | :param zcontinuous: (default: 0) 41 | Flag specifying how interpolation in metallicity of the simple stellar 42 | populations (SSPs) is performed before computing composite stellar 43 | population (CSP) models: 44 | 45 | * 0: No interpolation, use the metallicity index specified by ``zmet``. 46 | * 1: The SSPs are interpolated to the value of ``logzsol`` before the 47 | spectra and magnitudes are computed, and the value of ``zmet`` is 48 | ignored. 49 | * 2: The SSPs are convolved with a metallicity distribution function 50 | specified by the ``logzsol`` and ``pmetals`` parameters. The value of 51 | ``zmet`` is ignored. 52 | * 3: Use all available SSP metallicities when computing the composite 53 | model, for use exclusively with tabular SFHs where the metallicity 54 | evolution as function of age is given (see `set_tabular_sfh()`). The 55 | values of ``zmet`` and ``logzsol`` are ignored. Furthermore 56 | ``add_neb_emission`` must be set to False. 57 | 58 | Can only be changed during initialization. 59 | 60 | :param add_agb_dust_model: (default: True) 61 | Switch to turn on/off the AGB circumstellar dust model presented in 62 | Villaume (2014). NB: The AGB dust emission is scaled by the parameter 63 | `agb_dust`. 64 | 65 | :param add_dust_emission: (default: True) 66 | Switch to turn on/off the Draine & Li 2007 dust emission model. 67 | 68 | :param add_igm_absorption: (default: False) 69 | Switch to include IGM absorption via Madau (1995). The ``zred`` 70 | parameter must be non-zero for this switch to have any effect. The 71 | optical depth can be scaled using the ``igm_factor`` parameter. 72 | 73 | :param add_neb_emission: (default: False) 74 | Switch to turn on/off a nebular emission model (both continuum and line 75 | emission), based on Cloudy models from Nell Byler. Contrary to FSPS, 76 | this option is turned off by default. 77 | 78 | :param add_neb_continuum: (default: True) 79 | Switch to turn on/off the nebular continuum component (automatically 80 | turned off if ``add_neb_emission`` is ``False``). 81 | 82 | :param add_stellar_remnants: (default: True) 83 | Switch to add stellar remnants in the stellar mass computation. 84 | 85 | :param redshift_colors: (default: False) 86 | Flag specifying how to compute magnitudes. This has no effect in 87 | python-FSPS. Magnitudes are always computed at a fixed redshift 88 | specified by ``zred`` or the ``redshift`` parameter of ``get_mags``. 89 | See `get_mags` for details. 90 | 91 | :param compute_light_ages: (default: False) 92 | Flag specifying whether to compute light- and mass-weighted ages. If 93 | ``True`` then the returned spectra are actually light-weighted ages (in 94 | Gyr) at every wavelength, the returned magnitudes are filter 95 | transmission weighted averages of these, the ``log_lbol`` attribute is 96 | the bolometric luminosity weighted age, and the ``stellar_mass`` 97 | attribute gives the mass-weighted age. 98 | 99 | :param nebemlineinspec: (default: True) 100 | Flag to include the emission line fluxes in the spectrum. Turning this off 101 | is a significant speedup in model calculation time. If not set, the line 102 | luminosities are still computed. 103 | 104 | :param smooth_velocity: (default: True) 105 | Switch to choose smoothing in velocity space (``True``) or wavelength 106 | space. 107 | 108 | :param smooth_lsf: (default: False) 109 | Switch to apply smoothing of the SSPs by a wavelength dependent line 110 | spread function. See the ``set_lsf()`` method for details. Only takes 111 | effect if ``smooth_velocity`` is True. 112 | 113 | :param cloudy_dust: (default: False) 114 | Switch to include dust in the Cloudy tables. 115 | 116 | :param agb_dust: (default: 1.0) 117 | Scales the circumstellar AGB dust emission. 118 | 119 | :param tpagb_norm_type: (default: 2) 120 | Flag specifying TP-AGB normalization scheme: 121 | 122 | * 0: default Padova 2007 isochrones 123 | * 1: Conroy & Gunn 2010 normalization 124 | * 2: Villaume, Conroy, Johnson 2015 normalization 125 | 126 | :param dell: (default: 0.0) 127 | Shift in :math:`\log L_\mathrm{bol}` of the TP-AGB isochrones. Note 128 | that the meaning of this parameter and the one below has changed to 129 | reflect the updated calibrations presented in Conroy & Gunn (2009). 130 | That is, these parameters now refer to a modification about the 131 | calibrations presented in that paper. Only has effect if 132 | ``tpagb_norm_type=1``. 133 | 134 | :param delt: (default: 0.0) 135 | Shift in :math:`\log T_\mathrm{eff}` of the TP-AGB isochrones. Only 136 | has effect if ``tpagb_norm_type=1``. 137 | 138 | :param redgb: (default: 1.0) 139 | Modify weight given to RGB. Only available with BaSTI isochrone set. 140 | 141 | :param agb: (default: 1.0) 142 | Modify weight given to TP-AGB. This only has effect for FSPS v3.1 or 143 | higher. 144 | 145 | :param fcstar: (default: 1.0) 146 | Fraction of stars that the Padova isochrones identify as Carbon stars 147 | that FSPS assigns to a Carbon star spectrum. Set this to 0.0 if for 148 | example the users wishes to turn all Carbon stars into regular M-type 149 | stars. 150 | 151 | :param sbss: (default: 0.0) 152 | Specific frequency of blue straggler stars. See Conroy et al. (2009a) 153 | for details and a plausible range. 154 | 155 | :param fbhb: (default: 0.0) 156 | Fraction of horizontal branch stars that are blue. The blue HB stars 157 | are uniformly spread in :math:`\log T_\mathrm{eff}` to :math:`10^4` 158 | K. See Conroy et al. (2009a) for details and a plausible range. 159 | 160 | :param pagb: (default: 1.0) 161 | Weight given to the post–AGB phase. A value of 0.0 turns off post-AGB 162 | stars; a value of 1.0 implies that the Vassiliadis & Wood (1994) tracks 163 | are implemented as–is. 164 | 165 | :param frac_xrb: (default: 1.0) 166 | Scaling factor for the X-ray source spectrum to be added to the SSPs. 167 | 168 | :param zred: (default: 0.0) 169 | Redshift. If this value is non-zero and if ``redshift_colors=1``, the 170 | magnitudes will be computed for the spectrum placed at redshift 171 | ``zred``. 172 | 173 | :param zmet: (default: 1) 174 | The metallicity is specified as an integer ranging between 1 and nz. If 175 | ``zcontinuous > 0`` then this parameter is ignored. 176 | 177 | :param logzsol: (default: 0.0) 178 | Parameter describing the metallicity, given in units of :math:`\log 179 | (Z/Z_\odot)`. Only used if ``zcontinuous > 0``. 180 | 181 | :param pmetals: (default: 2.0) 182 | The power for the metallicty distribution function. The MDF is given by 183 | :math:`(Z \, e^{-Z})^{\mathrm{pmetals}}` where :math:`Z = 184 | z/(z_\odot \, 10^{\mathrm{logzsol}})` and z is the metallicity in 185 | linear units (i.e., :math:`z_\odot = 0.019`). Using a negative value 186 | will result in smoothing of the SSPs by a three-point triangular kernel 187 | before linear interpolation (in :math:`\log Z`) to the requested 188 | metallicity. Only used if ``zcontinuous = 2``. 189 | 190 | :param imf_type: (default: 2) 191 | Common variable defining the IMF type: 192 | 193 | * 0: Salpeter (1955) 194 | * 1: Chabrier (2003) 195 | * 2: Kroupa (2001) 196 | * 3: van Dokkum (2008) 197 | * 4: Dave (2008) 198 | * 5: tabulated piece-wise power law IMF, specified in ``imf.dat`` file 199 | located in the data directory 200 | 201 | :param imf_upper_limit: (default: 120) 202 | The upper limit of the IMF, in solar masses. Note that if this is 203 | above the maximum mass in the isochrones then those stars will not 204 | contribute to the spectrum but will affect the overall IMF 205 | normalization. 206 | 207 | :param imf_lower_limit: (default: 0.08) 208 | The lower limit of the IMF, in solar masses. Note that if this is 209 | below the minimum mass in the isochrones then those stars will not 210 | contribute to the spectrum but will affect the overall IMF 211 | normalization. 212 | 213 | :param imf1: (default: 1.3) 214 | Logarithmic slope of the IMF over the range :math:`0.08 < M < 0.5 215 | M_\odot`. Only used if ``imf_type=2``. 216 | 217 | :param imf2: (default: 2.3) 218 | Logarithmic slope of the IMF over the range :math:`0.5 < M < 1 219 | M_\odot`. Only used if ``imf_type=2``. 220 | 221 | :param imf3: (default: 2.3) 222 | Logarithmic slope of the IMF over the range 223 | :math:`1.0 < M < \mathrm{imf\_upper\_limit} M_\odot`. 224 | Only used if ``imf_type=2``. 225 | 226 | :param vdmc: (default: 0.08) 227 | IMF parameter defined in van Dokkum (2008). Only used if 228 | ``imf_type=3``. 229 | 230 | :param mdave: (default: 0.5) 231 | IMF parameter defined in Dave (2008). Only used if ``imf_type=4``. 232 | 233 | :param evtype: (default: -1) 234 | Compute SSPs for only the given evolutionary type. All phases used when 235 | set to -1. 236 | 237 | :param use_wr_spectra: (default: 1) 238 | Turn on/off the WR spectral library. If off (0), will use the main 239 | default library instead 240 | 241 | :param logt_wmb_hot: (default: 0.0) 242 | Use the Eldridge (2017) WMBasic hot star library above this value of 243 | :math:`\log T_\mathrm{eff}` or 25,000K, whichever is larger. 244 | 245 | :param add_xrb_emission: (default: 0) 246 | Turn on/off the x-ray binary population spectra from Garofali et al. 247 | 248 | :param masscut: (default: 150.0) 249 | Truncate the IMF above this value. 250 | 251 | :param sigma_smooth: (default: 0.0) 252 | If smooth_velocity is True, this gives the velocity dispersion in km/s. 253 | Otherwise, it gives the width of the gaussian wavelength smoothing in 254 | Angstroms. These widths are in terms of :math:`\sigma`, *not* FWHM. 255 | 256 | :param min_wave_smooth: (default: 1e3) 257 | Minimum wavelength to consider when smoothing the spectrum. 258 | 259 | :param max_wave_smooth: (default: 1e4) 260 | Maximum wavelength to consider when smoothing the spectrum. 261 | 262 | :param gas_logu: (default: -2) 263 | Log of the gas ionization parameter; relevant only for the nebular 264 | emission model. 265 | 266 | :param gas_logz: (default: 0.0) 267 | Log of the gas-phase metallicity; relevant only for the nebular 268 | emission model. In units of :math:`\log (Z/Z_\odot)`. 269 | 270 | :param igm_factor: (default: 1.0) 271 | Factor used to scale the IGM optical depth. 272 | 273 | :param sfh: (default: 0) 274 | Defines the type of star formation history, normalized such that one 275 | solar mass of stars is formed over the full SFH. Default value is 0. 276 | 277 | * 0: Compute a simple stellar population (SSP). 278 | * 1: Tau-model. A six parameter SFH (tau model plus a constant 279 | component and a burst) with parameters ``tau``, ``const``, 280 | ``sf_start``, ``sf_trunc``, ``tburst``, and ``fburst`` (see below). 281 | * 2: This option is not supported in Python-FSPS. 282 | * 3: Compute a tabulated SFH, which is supplied through the 283 | ``set_tabular_sfh`` method. See that method for details. 284 | * 4: Delayed tau-model. This is the same as option 1 except that the 285 | tau-model component takes the form :math:`t\,e^{−t/\tau}`. 286 | * 5: Delayed tau-model with a transition at a time ``sf_trunc`` to a 287 | linearly decreasing SFH with the slope specified by ``sf_slope``. See 288 | Simha et al. 2014 for details. 289 | 290 | :param tau: (default: 1.0) 291 | Defines e-folding time for the SFH, in Gyr. Only used if ``sfh=1`` or 292 | ``sfh=4``. 293 | 294 | :param const: (default: 0.0) 295 | Defines the constant component of the SFH. This quantity is defined as 296 | the fraction of mass formed in a constant mode of SF; the range is 297 | therefore :math:`0 \le C \le 1`. Only used if ``sfh=1`` or ``sfh=4``. 298 | 299 | :param sf_start: (default: 0.0) 300 | Start time of the SFH, in Gyr. Only used if ``sfh=1`` or ``sfh=4`` or 301 | ``sfh=5``. 302 | 303 | :param sf_trunc: (default: 0.0) 304 | Truncation time of the SFH, in Gyr. If set to 0.0, there is no 305 | trunction. Only used if ``sfh=1`` or ``sfh=4`` or ``sfh=5``. 306 | 307 | :param tage: (default: 0.0) 308 | If set to a non-zero value, the 309 | :func:`fsps.StellarPopulation.compute_csp` method will compute the 310 | spectra and magnitudes only at this age, and will therefore only output 311 | one age result. The units are Gyr. (The default is to compute and 312 | return results from :math:`t \approx 0` to the maximum age in the 313 | isochrones). 314 | 315 | :param fburst: (default: 0.0) 316 | Defines the fraction of mass formed in an instantaneous burst of star 317 | formation. Only used if ``sfh=1`` or ``sfh=4``. 318 | 319 | :param tburst: (default: 11.0) 320 | Defines the age of the Universe when the burst occurs. If ``tburst > 321 | tage`` then there is no burst. Only used if ``sfh=1`` or ``sfh=4``. 322 | 323 | :param sf_slope: (default: 0.0) 324 | For ``sfh=5``, this is the slope of the SFR after time ``sf_trunc``. 325 | 326 | :param dust_type: (default: 0) 327 | Common variable defining the attenuation curve for dust around 'old' stars: 328 | 329 | * 0: power law with index dust index set by ``dust_index``. 330 | * 1: Milky Way extinction law (with the :math:`R = A_V /E(B - V)` value 331 | given by ``mwr``) parameterized by Cardelli et al. (1989), with 332 | variable UV bump strength (see ``uvb`` below). 333 | * 2: Calzetti et al. (2000) attenuation curve. Note that if this value 334 | is set then the dust attenuation is applied to all starlight equally 335 | (not split by age), and therefore the only relevant parameter is 336 | ``dust2``, which sets the overall normalization (you must set 337 | ``dust1=0.0`` for this to work correctly). 338 | * 3: allows the user to access a variety of attenuation curve models 339 | from Witt & Gordon (2000) using the parameters ``wgp1`` and 340 | ``wgp2``. In this case the parameters ``dust1`` and ``dust2`` have no 341 | effect because the WG00 models specify the full attenuation curve. 342 | * 4: Kriek & Conroy (2013) attenuation curve. In this model the slope 343 | of the curve, set by the parameter ``dust_index``, is linked to the 344 | strength of the UV bump and is the *offset* in slope from Calzetti. 345 | * 5: The SMC bar extinction curve from Gordon et al. (2003) 346 | * 6: The Reddy et al. (2015) attenuation curve. 347 | 348 | :param dust_tesc: (default: 7.0) 349 | Stars younger than ``dust_tesc`` are attenuated by both ``dust1`` and 350 | ``dust2``, while stars older are attenuated by ``dust2`` only. Units 351 | are :math:`\log (\mathrm{yrs})`. 352 | 353 | :param dust1: (default: 0.0) 354 | Dust parameter describing the attenuation of young stellar light, 355 | i.e. where ``t <= dust_tesc`` (for details, see Conroy et al. 2009a). 356 | 357 | :param dust2: (default: 0.0) 358 | Dust parameter describing the attenuation of old stellar light, 359 | i.e. where ``t > dust_tesc`` (for details, see Conroy et al. 2009a). 360 | 361 | :param dust3: (default: 0.0) 362 | Dust parameter describing extra attenuation of old stellar light that 363 | does _not_ afect the young (``t < dust_tesc`` ) star light. The 364 | attenuation curve will be the one specified by ``dust_type`` 365 | 366 | :param dust_clumps: (default: -99.) 367 | Dust parameter describing the dispersion of a Gaussian PDF density 368 | distribution for the old dust. Setting this value to -99.0 sets the 369 | distribution to a uniform screen. See Conroy et al. (2009b) for 370 | details. Values other than -99 are no longer supported. 371 | 372 | :param frac_nodust: (default: 0.0) 373 | Fraction of starlight that is not attenuated by the diffuse dust 374 | component (i.e. that is not affected by ``dust2``). 375 | 376 | :param frac_obrun: (default: 0.0) 377 | Fraction of the young stars (age < dust_tesc) that are not attenuated by 378 | ``dust1`` and that do not contribute to any nebular emission, 379 | representing runaway OB stars or escaping ionizing radiation. These 380 | stars are still attenuated by ``dust2``. 381 | 382 | :param dust_index: (default: -0.7) 383 | Power law index of the attenuation curve. Only used when 384 | ``dust_type=0``. 385 | 386 | :param dust1_index: (default: -1.0) 387 | Power law index of the attenuation curve affecting stars younger than 388 | dust_tesc corresponding to ``dust1``. Used for all dust types. 389 | 390 | :param mwr: (default: 3.1) 391 | The ratio of total to selective absorption which characterizes the MW 392 | extinction curve: :math:`R = A_V /E(B - V)`. Only used when 393 | ``dust_type=1``. 394 | 395 | :param uvb: (default: 1.0) 396 | Parameter characterizing the strength of the 2175A extinction feature 397 | with respect to the standard Cardelli et al. determination for the 398 | MW. Only used when ``dust_type=1``. 399 | 400 | :param wgp1: (default: 1) 401 | Integer specifying the optical depth in the Witt & Gordon (2000) 402 | models. Values range from 1 − 18, corresponding to optical depths of 403 | 0.25, 0.50, 0.75, 1.00, 1.50, 2.00, 2.50, 3.00, 3.50, 4.00, 4.50, 404 | 5.00, 5.50, 6.00, 7.00, 8.00, 9.00, 10.0. Note that these optical 405 | depths are defined differently from the optical depths defined by 406 | the parameters ``dust1`` and ``dust2``. See Witt & Gordon (2000) 407 | for details. 408 | 409 | :param wgp2: (default: 1) 410 | Integer specifying the type of large-scale geometry and extinction 411 | curve. Values range from 1-6, corresponding to MW+dusty, MW+shell, 412 | MW+cloudy, SMC+dusty, SMC+shell, SMC+cloudy. Dusty, shell, and cloudy 413 | specify the geometry and are described in Witt & Gordon (2000). 414 | 415 | :param wgp3: (default: 1) 416 | Integer specifying the local geometry for the Witt & Gordon (2000) 417 | dust models. A value of 0 corresponds to a homogeneous distribution, 418 | and a value of 1 corresponds to a clumpy distribution. See Witt & 419 | Gordon (2000) for details. 420 | 421 | :param duste_gamma: (default: 0.01) 422 | Parameter of the Draine & Li (2007) dust emission model. Specifies the 423 | relative contribution of dust heated at a radiation field strength of 424 | :math:`U_\mathrm{min}` and dust heated at :math:`U_\mathrm{min} < U \le 425 | U_\mathrm{max}`. Allowable range is 0.0 - 1.0. 426 | 427 | :param duste_umin: (default: 1.0) 428 | Parameter of the Draine & Li (2007) dust emission model. Specifies the 429 | minimum radiation field strength in units of the MW value. Valid range 430 | is 0.1 - 25.0. 431 | 432 | :param duste_qpah: (default: 3.5) 433 | Parameter of the Draine & Li (2007) dust emission model. Specifies the 434 | grain size distribution through the fraction of grain mass in 435 | PAHs. This parameter has units of % and a valid range of 0.0 - 10.0. 436 | 437 | :param fagn: (default: 0.0) 438 | The total luminosity of the AGN, expressed as a fraction of the 439 | bolometric stellar luminosity (so it can be greater than 1). The shape 440 | of the AGN SED is from the Nenkova et al. 2008 templates. 441 | 442 | :param agn_tau: (default: 10) 443 | Optical depth of the AGN dust torus, which affects the shape of the AGN 444 | SED. Outside the range (5, 150) the AGN SED is an 445 | extrapolation. 446 | """ 447 | 448 | def __init__( 449 | self, compute_vega_mags=False, vactoair_flag=False, zcontinuous=0, **kwargs 450 | ): 451 | # Set up the parameters to their default values. 452 | self.params = ParameterSet( 453 | add_agb_dust_model=True, 454 | add_dust_emission=True, 455 | add_igm_absorption=False, 456 | add_neb_emission=False, 457 | add_neb_continuum=True, 458 | add_stellar_remnants=True, 459 | redshift_colors=False, 460 | compute_light_ages=False, 461 | nebemlineinspec=True, 462 | smooth_velocity=True, 463 | smooth_lsf=False, 464 | cloudy_dust=False, 465 | agb_dust=1.0, 466 | tpagb_norm_type=2, 467 | dell=0.0, 468 | delt=0.0, 469 | redgb=1.0, 470 | agb=1.0, 471 | fcstar=1.0, 472 | fbhb=0.0, 473 | sbss=0.0, 474 | pagb=1.0, 475 | frac_xrb=1.0, 476 | zred=0.0, 477 | zmet=1, 478 | logzsol=0.0, 479 | pmetals=2.0, 480 | imf_type=2, 481 | imf_upper_limit=120, 482 | imf_lower_limit=0.08, 483 | imf1=1.3, 484 | imf2=2.3, 485 | imf3=2.3, 486 | vdmc=0.08, 487 | mdave=0.5, 488 | evtype=-1, 489 | use_wr_spectra=1, 490 | logt_wmb_hot=0.0, 491 | add_xrb_emission=0, 492 | masscut=150.0, 493 | sigma_smooth=0.0, 494 | min_wave_smooth=1e3, 495 | max_wave_smooth=1e4, 496 | gas_logu=-2, 497 | gas_logz=0.0, 498 | igm_factor=1.0, 499 | sfh=0, 500 | tau=1.0, 501 | const=0.0, 502 | sf_start=0.0, 503 | sf_trunc=0.0, 504 | tage=0.0, 505 | dust_tesc=7.0, 506 | fburst=0.0, 507 | tburst=11.0, 508 | sf_slope=0.0, 509 | dust_type=0, 510 | dust1=0.0, 511 | dust2=0.0, 512 | dust3=0.0, 513 | dust_clumps=-99.0, 514 | frac_nodust=0.0, 515 | frac_obrun=0.0, 516 | dust_index=-0.7, 517 | dust1_index=-1.0, 518 | mwr=3.1, 519 | uvb=1.0, 520 | wgp1=1, 521 | wgp2=1, 522 | wgp3=1, 523 | duste_gamma=0.01, 524 | duste_umin=1.0, 525 | duste_qpah=3.5, 526 | fagn=0.0, 527 | agn_tau=10.0, 528 | ) 529 | 530 | # Parse any input options. 531 | for k, v in self.params.iteritems(): 532 | self.params[k] = kwargs.pop(k, v) 533 | 534 | # Make sure that we didn't get any unknown options. 535 | if len(kwargs): 536 | raise TypeError( 537 | "__init__() got an unexpected keyword argument " 538 | "'{0}'".format(list(kwargs)[0]) 539 | ) 540 | 541 | # Before the first time we interact with the FSPS driver, we need to 542 | # run the ``setup`` method. 543 | if not driver.is_setup: 544 | driver.setup(compute_vega_mags, vactoair_flag) 545 | else: 546 | cvms, vtaflag = driver.get_setup_vars() 547 | assert compute_vega_mags == bool(cvms) 548 | assert vactoair_flag == bool(vtaflag) 549 | self._zcontinuous = zcontinuous 550 | # Caching. 551 | self._wavelengths = None 552 | self._resolutions = None 553 | self._emwavelengths = None 554 | self._zlegend = None 555 | self._solar_metallicity = None 556 | self._ssp_ages = None 557 | self._stats = None 558 | self._libraries = None 559 | 560 | def _update_params(self): 561 | self.params.check_params() 562 | if self.params.dirtiness == 2: 563 | driver.set_ssp_params(*[self.params[k] for k in self.params.ssp_params]) 564 | if self.params.dirtiness >= 1: 565 | driver.set_csp_params(*[self.params[k] for k in self.params.csp_params]) 566 | self.params.dirtiness = 0 567 | 568 | def _compute_csp(self): 569 | self._update_params() 570 | 571 | NSPEC = driver.get_nspec() 572 | NTFULL = driver.get_ntfull() 573 | driver.compute_zdep(NSPEC, NTFULL, self._zcontinuous) 574 | 575 | self._stats = None 576 | 577 | def get_spectrum(self, zmet=None, tage=0.0, peraa=False): 578 | r""" 579 | Return spectra for the current CSP. 580 | 581 | :param zmet: (default: None) 582 | The (integer) index of the metallicity to use. By default, use 583 | the current value of ``self.params["zmet"]``. 584 | 585 | :param tage: (default: 0.0) 586 | The age of the stellar population in Gyr) for which to obtain a 587 | spectrum. By default, this will compute a grid of ages from 588 | :math:`t \approx 0` to the maximum age in the isochrones. 589 | 590 | :param peraa: (default: False) 591 | If ``True``, return the spectrum in :math:`L_\odot/A`. Otherwise, 592 | return the spectrum in the FSPS standard 593 | :math:`L_\odot/\mathrm{Hz}`. 594 | 595 | :returns wavelengths: 596 | The wavelength grid in Angstroms. 597 | 598 | :returns spectrum: 599 | The spectrum in :math:`L_\odot/\mathrm{Hz}` or :math:`L_\odot/A`. 600 | If an age was provided by the ``tage`` parameter then the result 601 | is a 1D array with ``NSPEC`` values. Otherwise, it is a 2D array 602 | with shape ``(NTFULL, NSPEC)``. 603 | """ 604 | self.params["tage"] = tage 605 | if zmet is not None: 606 | self.params["zmet"] = zmet 607 | 608 | if self.params.dirty: 609 | self._compute_csp() 610 | 611 | wavegrid = self.wavelengths 612 | if peraa: 613 | factor = 3e18 / wavegrid**2 614 | 615 | else: 616 | factor = np.ones_like(wavegrid) 617 | 618 | NSPEC = driver.get_nspec() 619 | if (tage > 0.0) or (tage == -99): 620 | return wavegrid, driver.get_spec(NSPEC, 1)[0] * factor 621 | 622 | NTFULL = driver.get_ntfull() 623 | return wavegrid, driver.get_spec(NSPEC, NTFULL) * factor[None, :] 624 | 625 | def get_mags(self, zmet=None, tage=0.0, redshift=None, bands=None): 626 | r""" 627 | Get the magnitude of the CSP. 628 | 629 | :param zmet: (default: None) 630 | The (integer) index of the metallicity to use. By default, use the 631 | current value of ``self.params["zmet"]``. 632 | 633 | :param tage: (default: 0.0) 634 | The age of the stellar population in Gyr. By default, this will 635 | compute a grid of ages from :math:`t \approx 0` to the maximum age 636 | in the isochrones. 637 | 638 | :param redshift: (default: None) 639 | Optionally redshift the spectrum first. If not supplied, the 640 | redshift given by ``StellarPopulation.params["zred"]`` is assumed. 641 | If supplied, the value of ``zred`` is ignored (and IGM attenuation 642 | will not work properly). 643 | 644 | :param bands: (default: None) 645 | The names of the filters that you would like to compute the 646 | magnitude for. This should correspond to the result of 647 | :func:`fsps.find_filter`. 648 | 649 | :returns mags: 650 | The magnitude grid. If an age was was provided by the ``tage`` 651 | parameter then the result is a 1D array with ``NBANDS`` values. 652 | Otherwise, it is a 2D array with shape ``(NTFULL, NBANDS)``. If a 653 | particular set of bands was requested then this return value will 654 | be properly compressed along that axis, ordered according to the 655 | ``bands`` argument. If ``redshift`` is not 0, the units are 656 | apparent observed frame magnitude :math:`m` assuming 657 | :math:`\Omega_m=0.3, \Omega_\Lambda=0.7` 658 | """ 659 | if redshift is None: 660 | zr = self.params["zred"] 661 | elif (self.params["zred"] > 0) & (redshift != self.params["zred"]): 662 | zr = redshift 663 | print("Warning: redshift is different than 'zred'.") 664 | else: 665 | zr = redshift 666 | self.params["tage"] = tage 667 | if zmet is not None: 668 | self.params["zmet"] = zmet 669 | 670 | if self.params.dirty: 671 | self._compute_csp() 672 | 673 | if tage > 0.0: 674 | NTFULL = 1 675 | else: 676 | NTFULL = driver.get_ntfull() 677 | NBANDS = driver.get_nbands() 678 | NSPEC = driver.get_nspec() 679 | band_array = np.ones(NBANDS, dtype=bool) 680 | if bands is not None: 681 | user_sorted_inds = np.array([FILTERS[band.lower()].index for band in bands]) 682 | band_array[ 683 | np.array( 684 | [i not in user_sorted_inds for i in range(NBANDS)], 685 | dtype=bool, 686 | ) 687 | ] = False 688 | 689 | inds = np.array(band_array, dtype=int) 690 | mags = driver.get_mags(NSPEC, NTFULL, zr, inds) 691 | 692 | if tage > 0.0: 693 | if bands is not None: 694 | return mags[0, user_sorted_inds] 695 | else: 696 | return mags[0, :] 697 | elif bands is not None: 698 | return mags[:, user_sorted_inds] 699 | else: 700 | return mags 701 | 702 | def _ztinterp(self, zpos, tpos, peraa=False): 703 | r""" 704 | Return an SSP spectrum, mass, and luminosity interpolated to a target 705 | metallicity and age. This effectively wraps the ZTINTERP subroutine. 706 | Only the SSPs bracketing a given metallicity will be regenerated, if 707 | parameters are dirty. 708 | 709 | :param zpos: 710 | The metallicity, in units of :math:`\log(Z/Z_\odot)` 711 | 712 | :param tpos: 713 | The desired age, in Gyr. 714 | 715 | :param peraa: (default: False) 716 | If true, return spectra in units of :math:`L_\odot/A`, otherwise 717 | :math:`L_\odot/\mathrm{Hz}` 718 | 719 | :returns spec: 720 | The SSP spectrum, interpolated to zpos and tpos. 721 | 722 | :returns mass: 723 | The stellar mass of the SSP at tpos. 724 | 725 | :returns lbol: 726 | The bolometric luminosity of the returned SSP. 727 | """ 728 | if self.params.dirtiness == 2: 729 | self._update_params() 730 | 731 | NSPEC = driver.get_nspec() 732 | spec, mass, lbol = np.zeros(NSPEC), np.zeros(1), np.zeros(1) 733 | logt_yrs = np.log10(tpos * 1e9) 734 | driver.interp_ssp(zpos, logt_yrs, spec, mass, lbol) 735 | 736 | if peraa: 737 | wavegrid = self.wavelengths 738 | factor = 3e18 / wavegrid**2 739 | spec *= factor 740 | 741 | return spec, mass, lbol 742 | 743 | def _all_ssp_spec(self, update=True, peraa=False): 744 | r""" 745 | Return the contents of the ssp_spec_zz array. 746 | 747 | :param update: (default: True) 748 | If True, forces an update of the SSPs if the ssp parameters have 749 | changed. Otherwise simply dumps the current contents of the 750 | ``ssp_spec_zz`` array. 751 | 752 | :param peraa: (default: False) 753 | If true, return spectra in units of :math:`L_\odot/A`, otherwise 754 | :math:`L_\odot/\mathrm{Hz}` 755 | 756 | :returns spec: 757 | The spectra of the SSPs, having shape (nspec, ntfull, nz). 758 | 759 | :returns mass: 760 | The mass of the SSPs, having shape (ntfull, nz). 761 | 762 | :returns lbol: 763 | The bolometric luminosity of the SSPs, having shape (ntfull, nz). 764 | """ 765 | 766 | if (self.params.dirtiness == 2) and update: 767 | self._update_params() 768 | 769 | NSPEC = driver.get_nspec() 770 | NTFULL = driver.get_ntfull() 771 | NZ = driver.get_nz() 772 | spec = np.zeros([NSPEC, NTFULL, NZ], order="F") 773 | mass = np.zeros([NTFULL, NZ], order="F") 774 | lbol = np.zeros([NTFULL, NZ], order="F") 775 | driver.get_ssp_spec(spec, mass, lbol) 776 | 777 | if peraa: 778 | wavegrid = self.wavelengths 779 | factor = 3e18 / wavegrid**2 780 | spec *= factor[:, None, None] 781 | 782 | return spec, mass, lbol 783 | 784 | @property 785 | def _csp_young_old(self): 786 | """Get the (unattenuated) young and old component spectra of the CSP 787 | 788 | :returns young: 789 | The young stellar spectrum 790 | 791 | :returns old: 792 | The old stellar spectrum 793 | """ 794 | 795 | NS = driver.get_nspec() 796 | young, old = np.zeros(NS), np.zeros(NS) 797 | driver.get_csp_components(young, old) 798 | return young, old 799 | 800 | @property 801 | def _ssp_weights(self): 802 | r"""Get the weights of the SSPs for the CSP 803 | 804 | :returns weights: 805 | The weights ``w`` of each SSP s.t. the total spectrum is the sum 806 | :math:`L_{\lambda} = \sum_i,j w_i,j \, S_{i,j,\lambda}` where 807 | math:`i,j` run over age and metallicity. 808 | """ 809 | 810 | NTFULL = driver.get_ntfull() 811 | NZ = driver.get_nz() 812 | weights = np.zeros([NTFULL, NZ], order="F") 813 | driver.get_ssp_weights(weights) 814 | return weights 815 | 816 | def _get_stellar_spectrum( 817 | self, 818 | mact, 819 | logt, 820 | lbol, 821 | logg, 822 | phase, 823 | comp, 824 | mdot=0, 825 | weight=1, 826 | zmet=None, 827 | peraa=True, 828 | ): 829 | r""" 830 | Get the spectrum of a star with a given set of physical parameters. 831 | This uses the metallicity given by the current value of ``zmet``. 832 | 833 | :param mact: 834 | Actual stellar mass (after taking into account mass loss). Used to 835 | calculate surface gravity. 836 | 837 | :param logt: 838 | The log of the effective temperature. 839 | 840 | :param lbol: 841 | Stellar luminosity, in units of :math:`L_\odot` 842 | 843 | :param logg: 844 | Log of the surface gravity g. Note that this variable is actually 845 | ignored, and logg is calculated internally using ``mact``, 846 | ``lbol``, and ``logt``. 847 | 848 | :param phase: 849 | The evolutionary phase, 0 through 6. 850 | 851 | :param comp: 852 | Composition, in terms of C/O ratio. Only used for AGB stars 853 | (phase=5), where the division between carbon and oxyygen rich stars 854 | is :math:`C/O = 1`. 855 | 856 | :param mdot: 857 | The log of the mass loss rate. 858 | 859 | :param weight: 860 | The IMF weight 861 | 862 | :returns outspec: 863 | The spectrum of the star, in :math:`L_\odot/\mathrm{Hz}` 864 | """ 865 | if zmet is not None: 866 | self.params["zmet"] = zmet 867 | 868 | if self.params.dirty: 869 | self._update_params() 870 | 871 | NSPEC = driver.get_nspec() 872 | outspec = np.zeros(NSPEC) 873 | driver.stellar_spectrum( 874 | mact, logt, lbol, logg, phase, comp, mdot, weight, outspec 875 | ) 876 | if peraa: 877 | wavegrid = self.wavelengths 878 | factor = 3e18 / wavegrid**2 879 | outspec *= factor 880 | 881 | return outspec 882 | 883 | def isochrones(self, outfile="pyfsps_tmp"): 884 | r""" 885 | Write the isochrone data (age, mass, weights, phases, magnitudes, etc.) 886 | to a .cmd file, then read it into a huge numpy array. Only parameters 887 | listed in ``StellarPopulation.params.ssp_params`` affect the output of 888 | this method. This method does not work for the BPASS isochrones. 889 | 890 | :param outfile: (default: 'pyfsps_tmp') 891 | The file root name of the .cmd file, which will be placed in the 892 | $SPS_HOME/OUTPUTS/ directory 893 | 894 | :returns dat: 895 | A huge numpy structured array containing information about every 896 | isochrone point for the current metallicity. In general the 897 | columns may be isochrone specific, but for Padova they are 898 | 899 | * age: log age, yrs 900 | * log(Z): log metallicity 901 | * mini: initial stellar mass in solar masses 902 | * mact: actual stellar mass (accounting for mass loss) 903 | * logl: log bolometric luminosity, solar units 904 | * logt: log temperature (K) 905 | * logg: log gravity 906 | * phase: (see evtype) 907 | * log(weight): IMF weight corresponding to a total of 1 Msol formed. 908 | * log(mdot): mass loss rate (Msol/yr) 909 | """ 910 | if self.isoc_library.decode("utf-8") == "bpss": 911 | raise ValueError("CMDs cannot be generated for the BPASS isochrones.") 912 | 913 | if self.params.dirty: 914 | self._compute_csp() 915 | 916 | from . import list_filters 917 | 918 | absfile = os.path.join(os.environ["SPS_HOME"], "OUTPUTS", outfile + ".cmd") 919 | driver.write_isoc(outfile) 920 | 921 | with open(absfile, "r") as f: 922 | # drop the comment hash and mags field 923 | header = f.readline().split()[1:-1] 924 | header += list_filters() 925 | cmd_data = np.loadtxt( 926 | absfile, 927 | comments="#", 928 | dtype=np.dtype([(n, float) for n in header]), 929 | ) 930 | return cmd_data 931 | 932 | def set_tabular_sfh(self, age, sfr, Z=None): 933 | r""" 934 | Set a tabular SFH for use with the ``sfh=3`` option. See the FSPS 935 | documentation for information about tabular SFHs. This SFH will be 936 | piecewise linearly interpolated. 937 | 938 | :param age: 939 | Time since the beginning of the universe in Gyr. Must be 940 | increasing. ndarray of shape (ntab,) 941 | 942 | :param sfr: 943 | The SFR at each ``age``, in Msun/yr. Must be an ndarray same 944 | length as ``age``, and contain at least one non-zero value. 945 | 946 | :param Z: (optional) 947 | The metallicity at each age, in units of absolute metallicity 948 | (e.g. Z=0.019 for solar with the Padova isochrones and MILES 949 | stellar library). 950 | """ 951 | assert len(age) == len(sfr), "age and sfr have different size." 952 | assert np.all(age[1:] > age[:-1]), "Ages must be increasing." 953 | assert np.any(sfr > 1e-33), "At least one sfr must be > 1e-33." 954 | assert np.all(sfr >= 0.0), "sfr cannot be negative." 955 | ntab = len(age) 956 | if Z is None: 957 | Z = np.zeros(ntab) 958 | assert self._zcontinuous != 3 959 | else: 960 | assert len(Z) == ntab 961 | assert np.all(Z >= 0), "All values of Z must be greater than or equal 0." 962 | assert self._zcontinuous == 3, "_zcontinuous must be 3 for multi-Z tabular." 963 | assert self.params["add_neb_emission"] is False, ( 964 | "Cannot compute nebular emission " "with multi-metallicity tabular SFH." 965 | ) 966 | 967 | driver.set_sfh_tab(age * 1e9, sfr, Z) 968 | if self.params["sfh"] == 3: 969 | self.params.dirtiness = max(1, self.params.dirtiness) 970 | else: 971 | print( 972 | "Warning: You are setting a tabular SFH, " 973 | "but the ``sfh`` parameter is not 3" 974 | ) 975 | 976 | def set_lsf(self, wave, sigma, wmin=None, wmax=None): 977 | r""" 978 | Set a wavelength dependent Gaussian line-spread function that will be 979 | applied to the SSPs. Only takes effect if ``smooth_lsf`` and 980 | ``smooth_velocity`` are True. 981 | 982 | :param wave: 983 | Wavelength in angstroms, sorted ascending. If `wmin` or `wmax` 984 | are not specified they are taken from the minimum and maximum of 985 | this array. ndarray. 986 | 987 | :param sigma: 988 | The dispersion of the Gaussian LSF at the wavelengths given by 989 | `wave`, in km/s. If 0, no smoothing is applied at that wavelength. 990 | ndarray of same shape as `wave`. 991 | 992 | :param wmin: (optional) 993 | The minimum wavelength (in AA) for which smoothing will be 994 | applied. If not given, it is taken from the minimum of `wave`. 995 | 996 | :param wmax: (optional) 997 | The maximum wavelength (in AA) for which smoothing will be 998 | applied. If not given, it is taken from the maximum of `wave`. 999 | """ 1000 | 1001 | if wmin is None: 1002 | wmin = wave.min() 1003 | if wmax is None: 1004 | wmax = wave.max() 1005 | sig = np.interp(self.wavelengths, wave, sigma) 1006 | driver.set_ssp_lsf(sig, wmin, wmax) 1007 | if self.params["smooth_lsf"]: 1008 | self.params.dirtiness = max(2, self.params.dirtiness) 1009 | else: 1010 | print( 1011 | "Warning: You are setting an LSF for the SSPs, " 1012 | "but the ``smooth_lsf`` parameter is not True." 1013 | ) 1014 | if not self.params["smooth_velocity"]: 1015 | print( 1016 | "Warning: You are setting an LSF for the SSPs, " 1017 | "but the ``smooth_velocity`` parameter is not True." 1018 | ) 1019 | 1020 | def smoothspec(self, wave, spec, sigma, minw=None, maxw=None): 1021 | r""" 1022 | Smooth a spectrum by a gaussian with standard deviation given by sigma. 1023 | Whether the smoothing is in velocity space or in wavelength space 1024 | depends on the value of the value of smooth_velocity. 1025 | 1026 | :param wave: 1027 | The input wavelength grid. 1028 | 1029 | :param spec: 1030 | The input spectrum. 1031 | 1032 | :param sigma: 1033 | The standard deviation of the gaussian broadening function. 1034 | 1035 | :param minw: 1036 | Optionally set the minimum wavelength to consider when 1037 | broadening. 1038 | 1039 | :param maxw: 1040 | Optionally set the maximum wavelength to consider when 1041 | broadening. 1042 | 1043 | :returns outspec: 1044 | The smoothed spectrum, on the same wavelength grid as the input. 1045 | """ 1046 | if maxw is None: 1047 | maxw = np.max(wave) 1048 | if minw is None: 1049 | minw = np.min(wave) 1050 | assert len(wave) == len(spec) 1051 | outspec = np.array(spec) 1052 | driver.smooth_spectrum(wave, outspec, sigma, minw, maxw) 1053 | return outspec 1054 | 1055 | def filter_data(self): 1056 | r""" 1057 | Return effective wavelengths, and vega and solar magnitudes 1058 | of all filters. 1059 | 1060 | :returns lambda_eff: 1061 | Effective wavelength of each filter. 1062 | 1063 | :returns magvega: 1064 | The AB magnitude of Vega (used to convert between AB and Vega 1065 | systems). 1066 | 1067 | :returns magsun: 1068 | The AB absolute magnitude of the Sun. 1069 | """ 1070 | NBANDS = driver.get_nbands() 1071 | lambda_eff, magvega, magsun = driver.get_filter_data(NBANDS) 1072 | return lambda_eff, magvega, magsun 1073 | 1074 | @property 1075 | def wavelengths(self): 1076 | r"""The wavelength scale for the computed spectra, in Angstroms""" 1077 | if self._wavelengths is None: 1078 | NSPEC = driver.get_nspec() 1079 | self._wavelengths = driver.get_lambda(NSPEC) 1080 | return self._wavelengths.copy() 1081 | 1082 | @property 1083 | def resolutions(self): 1084 | r"""The resolution array, in km/s dispersion. Negative numbers indicate 1085 | poorly defined, approximate, resolution (based on coarse opacity 1086 | binning in theoretical spectra)""" 1087 | if self._resolutions is None: 1088 | NSPEC = driver.get_nspec() 1089 | self._resolutions = driver.get_res(NSPEC) 1090 | return self._resolutions.copy() 1091 | 1092 | @property 1093 | def emline_wavelengths(self): 1094 | r"""Emission line wavelengths, in Angstroms""" 1095 | if self._emwavelengths is None: 1096 | NLINE = driver.get_nemline() 1097 | self._emwavelengths = driver.get_emlambda(NLINE) 1098 | return self._emwavelengths.copy() 1099 | 1100 | @property 1101 | def zlegend(self): 1102 | r"""The available metallicities.""" 1103 | if self._zlegend is None: 1104 | NZ = driver.get_nz() 1105 | self._zlegend = driver.get_zlegend(NZ) 1106 | return self._zlegend 1107 | 1108 | @property 1109 | def solar_metallicity(self): 1110 | r"""The definition of solar metallicity, as a mass fraction. 1111 | E.g. Z_sol \sim 0.014-0.02""" 1112 | if self._solar_metallicity is None: 1113 | self._solar_metallicity = driver.get_zsol() 1114 | return self._solar_metallicity 1115 | 1116 | @property 1117 | def ssp_ages(self): 1118 | r"""The age grid of the SSPs, in log(years), used by FSPS.""" 1119 | if self._ssp_ages is None: 1120 | NTFULL = driver.get_ntfull() 1121 | self._ssp_ages = driver.get_timefull(NTFULL) 1122 | return self._ssp_ages 1123 | 1124 | @property 1125 | def log_age(self): 1126 | r"""log10(age/yr).""" 1127 | return self._stat(0) 1128 | 1129 | @property 1130 | def stellar_mass(self): 1131 | r"""Surviving stellar mass in solar masses (including remnants if the 1132 | FSPS parameter ``add_stellar_remants=1``). 1133 | """ 1134 | return self._stat(1) 1135 | 1136 | @property 1137 | def log_lbol(self): 1138 | r"""log(bolometric luminosity / :math:`L_\odot`).""" 1139 | return self._stat(2) 1140 | 1141 | @property 1142 | def sfr(self): 1143 | r"""Star formation rate (:math:`M_\odot/yr`).""" 1144 | return self._stat(3) 1145 | 1146 | @property 1147 | def dust_mass(self): 1148 | r"""Dust mass, in solar masses.""" 1149 | return self._stat(4) 1150 | 1151 | @property 1152 | def formed_mass(self): 1153 | r"""Integral of the SFH, in solar masses.""" 1154 | return self._stat(5) 1155 | 1156 | @property 1157 | def emline_luminosity(self): 1158 | r"""emission line luminosities, in :math:`L_\odot`. shape=(ne)""" 1159 | return self._stat(6) 1160 | 1161 | def _get_grid_stats(self): 1162 | if self.params.dirty: 1163 | self._compute_csp() 1164 | 1165 | if self._stats is None: 1166 | self._stats = driver.get_stats(driver.get_ntfull(), driver.get_nemline()) 1167 | 1168 | return self._stats 1169 | 1170 | def _stat(self, k): 1171 | stats = self._get_grid_stats() 1172 | if self.params["tage"] > 0: 1173 | return stats[k][0] 1174 | return stats[k] 1175 | 1176 | def sfr_avg(self, times=None, dt=0.1): 1177 | r""" 1178 | The average SFR between ``time``-``dt`` and ``time``, given the 1179 | SFH parameters, for ``sfh=1`` or ``sfh=4``. Like SFHSTAT in FSPS. 1180 | Requires scipy, as it uses gamma functions. 1181 | 1182 | :param times: (default, None) 1183 | The times (in Gyr of cosmic time) at which the average SFR over the 1184 | last ``dt`` is desired. if ``None``, uses the current value of the 1185 | ``tage`` in the parameter set. Scalar or iterable. 1186 | 1187 | :param dt: (default: 0.1) 1188 | The time interval over which the recent SFR is averaged, in Gyr. 1189 | defaults to 100 Myr (i.e. sfr8). 1190 | 1191 | :returns sfr_avg: 1192 | The SFR at ``tage`` averaged over the last ``dt`` Gyr, in units of 1193 | solar masses per year, such that :math:`1 M_\odot` formed by 1194 | ``tage``. Same shape as ``times``. For ``times < sf_start + dt`` 1195 | the value of ``sfr_avg`` is ``nan``, for ``times > tage`` the value 1196 | is 0. 1197 | """ 1198 | from scipy.special import gammainc 1199 | 1200 | assert self.params["sf_trunc"] <= 0, "sfr_avg not supported for sf_trunc > 0" 1201 | if self.params["sfh"] == 1: 1202 | power = 1 1203 | elif self.params["sfh"] == 4: 1204 | power = 2 1205 | else: 1206 | raise ValueError("sfr_avg not supported for this SFH type.") 1207 | 1208 | tau, sf_start = self.params["tau"], self.params["sf_start"] 1209 | if self.params["tage"] <= 0: 1210 | tage = 10 ** (np.max(self.ssp_ages) - 9) 1211 | else: 1212 | tage = np.array(self.params["tage"]) 1213 | 1214 | if times is None: 1215 | times = tage 1216 | else: 1217 | times = np.array(times) 1218 | 1219 | tb = (self.params["tburst"] - sf_start) / tau 1220 | tmax = (tage - sf_start) / tau 1221 | normalized_times = (np.array([times, times - dt]).T - sf_start) / tau 1222 | 1223 | tau_mass_frac = gammainc(power, normalized_times) / gammainc(power, tmax) 1224 | burst_in_past = tb <= normalized_times 1225 | mass = ( 1226 | tau_mass_frac 1227 | * (1 - self.params["const"] - (tb < tmax) * self.params["fburst"]) 1228 | + self.params["const"] * normalized_times / tmax 1229 | + burst_in_past * self.params["fburst"] 1230 | ) 1231 | 1232 | avsfr = (mass[..., 0] - mass[..., 1]) / dt / 1e9 # Msun/yr 1233 | 1234 | # These lines change behavior when you request sfrs outside the range 1235 | # (sf_start + dt, tage) 1236 | # avsfr[times > tage] = np.nan # does not work for scalars 1237 | avsfr *= times <= tage 1238 | # avsfr[np.isfinite(avsfr)] = 0.0 # does not work for scalars 1239 | 1240 | return np.clip(avsfr, 0, np.inf) 1241 | 1242 | @property 1243 | def isoc_library(self): 1244 | r"""The name of the stellar isochrone library being used in FSPS.""" 1245 | return self.libraries[0] 1246 | 1247 | @property 1248 | def spec_library(self): 1249 | r"""The name of the stellar spectral library being used in FSPS.""" 1250 | return self.libraries[1] 1251 | 1252 | @property 1253 | def duste_library(self): 1254 | r"""The name of the dust emission SED library being used in FSPS.""" 1255 | return self.libraries[2] 1256 | 1257 | @property 1258 | def libraries(self): 1259 | if self._libraries is None: 1260 | self._libraries = driver.get_libraries() 1261 | return self._libraries 1262 | 1263 | 1264 | class ParameterSet(object): 1265 | ssp_params = [ 1266 | "imf_type", 1267 | "imf_upper_limit", 1268 | "imf_lower_limit", 1269 | "imf1", 1270 | "imf2", 1271 | "imf3", 1272 | "vdmc", 1273 | "mdave", 1274 | "dell", 1275 | "delt", 1276 | "sbss", 1277 | "fbhb", 1278 | "pagb", 1279 | "add_stellar_remnants", 1280 | "tpagb_norm_type", 1281 | "add_agb_dust_model", 1282 | "agb_dust", 1283 | "redgb", 1284 | "agb", 1285 | "masscut", 1286 | "fcstar", 1287 | "evtype", 1288 | "use_wr_spectra", 1289 | "logt_wmb_hot", 1290 | "add_xrb_emission", 1291 | "frac_xrb", 1292 | "smooth_lsf", 1293 | ] 1294 | 1295 | csp_params = [ 1296 | "smooth_velocity", 1297 | "redshift_colors", 1298 | "compute_light_ages", 1299 | "nebemlineinspec", 1300 | "dust_type", 1301 | "add_dust_emission", 1302 | "add_neb_emission", 1303 | "add_neb_continuum", 1304 | "cloudy_dust", 1305 | "add_igm_absorption", 1306 | "zmet", 1307 | "sfh", 1308 | "wgp1", 1309 | "wgp2", 1310 | "wgp3", 1311 | "tau", 1312 | "const", 1313 | "tage", 1314 | "fburst", 1315 | "tburst", 1316 | "dust1", 1317 | "dust2", 1318 | "dust3", 1319 | "logzsol", 1320 | "zred", 1321 | "pmetals", 1322 | "dust_clumps", 1323 | "frac_nodust", 1324 | "dust_index", 1325 | "dust_tesc", 1326 | "frac_obrun", 1327 | "uvb", 1328 | "mwr", 1329 | "dust1_index", 1330 | "sf_start", 1331 | "sf_trunc", 1332 | "sf_slope", 1333 | "duste_gamma", 1334 | "duste_umin", 1335 | "duste_qpah", 1336 | "sigma_smooth", 1337 | "min_wave_smooth", 1338 | "max_wave_smooth", 1339 | "gas_logu", 1340 | "gas_logz", 1341 | "igm_factor", 1342 | "fagn", 1343 | "agn_tau", 1344 | ] 1345 | 1346 | @property 1347 | def all_params(self): 1348 | return self.ssp_params + self.csp_params 1349 | 1350 | @property 1351 | def dirty(self): 1352 | return self.dirtiness > 0 1353 | 1354 | def __init__(self, **kwargs): 1355 | self.dirtiness = 2 1356 | self._params = kwargs 1357 | try: 1358 | self.iteritems = self._params.iteritems 1359 | except AttributeError: 1360 | self.iteritems = self._params.items 1361 | 1362 | def check_params(self): 1363 | NZ = driver.get_nz() 1364 | assert self._params["zmet"] in range( 1365 | 1, NZ + 1 1366 | ), "zmet={0} out of range [1, {1}]".format(self._params["zmet"], NZ) 1367 | assert self._params["dust_type"] in range( 1368 | 7 1369 | ), "dust_type={0} out of range [0, 6]".format(self._params["dust_type"]) 1370 | assert self._params["imf_type"] in range( 1371 | 6 1372 | ), "imf_type={0} out of range [0, 5]".format(self._params["imf_type"]) 1373 | assert (self._params["tage"] <= 0) | ( 1374 | self._params["tage"] > self._params["sf_start"] 1375 | ), "sf_start={0} is greater than tage={1}".format( 1376 | self._params["sf_start"], self._params["tage"] 1377 | ) 1378 | assert ( 1379 | self._params["const"] + self._params["fburst"] 1380 | ) <= 1, "const + fburst > 1" 1381 | 1382 | def __getitem__(self, k): 1383 | return self._params[k] 1384 | 1385 | def __setitem__(self, k, v): 1386 | original = self._params[k] 1387 | is_changed = original != v 1388 | 1389 | if is_changed: 1390 | if k in self.ssp_params: 1391 | self.dirtiness = 2 1392 | elif k in self.csp_params: 1393 | self.dirtiness = max(1, self.dirtiness) 1394 | 1395 | self._params[k] = v 1396 | -------------------------------------------------------------------------------- /src/fsps/sps_home.py: -------------------------------------------------------------------------------- 1 | __all__ = ["check_sps_home"] 2 | 3 | import os 4 | 5 | 6 | def check_sps_home(): 7 | if "SPS_HOME" not in os.environ: 8 | raise RuntimeError("You need to have the SPS_HOME environment variable") 9 | 10 | path = os.environ["SPS_HOME"] 11 | if not os.path.isdir(path): 12 | raise RuntimeError(f"SPS_HOME environment variable '{path}' is not a directory") 13 | 14 | if not os.path.exists(os.path.join(path, "data", "emlines_info.dat")): 15 | raise RuntimeError( 16 | f"The FSPS directory at {path} doesn't seem to have the right data " 17 | "files installed" 18 | ) 19 | -------------------------------------------------------------------------------- /tests/options.py: -------------------------------------------------------------------------------- 1 | import fsps 2 | 3 | sps = fsps.StellarPopulation() 4 | assert sps.libraries[0] == b"pdva", "Incorrect isochrone library" 5 | assert sps.libraries[1] == b"basel", "Incorrect spectral library" 6 | -------------------------------------------------------------------------------- /tests/simple.py: -------------------------------------------------------------------------------- 1 | from fsps import StellarPopulation 2 | 3 | pop = StellarPopulation() 4 | pop.get_spectrum() 5 | 6 | print("success") 7 | -------------------------------------------------------------------------------- /tests/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | 6 | import numpy as np 7 | import pytest 8 | from numpy.testing import assert_allclose 9 | 10 | from fsps import StellarPopulation, filters 11 | 12 | skip_slow_tests = pytest.mark.skipif( 13 | (sys.platform.startswith("win") or sys.platform.startswith("darwin")) 14 | and "CI" in os.environ, 15 | reason="Slow tests only run on Linux CI", 16 | ) 17 | 18 | 19 | @pytest.fixture(scope="module") 20 | def pop_and_params(): 21 | pop = StellarPopulation(zcontinuous=1) 22 | params = dict([(k, pop.params[k]) for k in pop.params.all_params]) 23 | return pop, params 24 | 25 | 26 | def _reset_default_params(pop, params): 27 | pop._zcontinuous = 1 28 | for k in pop.params.all_params: 29 | pop.params[k] = params[k] 30 | 31 | 32 | @skip_slow_tests 33 | def test_isochrones(pop_and_params): 34 | """Just test that `isochrones()` method runs""" 35 | 36 | # recomputes SSPs 37 | 38 | pop, params = pop_and_params 39 | _reset_default_params(pop, params) 40 | pop.params["imf_type"] = 0 41 | pop.isochrones() 42 | 43 | 44 | @skip_slow_tests 45 | def test_imf3(pop_and_params): 46 | """Make sure that changing the (upper) imf changes the parameter dirtiness 47 | and also the SSP spectrum""" 48 | 49 | # recomputes SSPs 50 | 51 | pop, params = pop_and_params 52 | _reset_default_params(pop, params) 53 | pop.params["imf_type"] = 2 54 | pop.params["imf3"] = 2.3 55 | w, model1 = pop.get_spectrum(tage=0.2) 56 | 57 | # check that changing the IMF does something 58 | pop.params["imf3"] = 8.3 59 | assert pop.params.dirtiness == 2 60 | w, model2 = pop.get_spectrum(tage=0.2) 61 | assert not np.allclose(model1 / model2 - 1.0, 0.0) 62 | 63 | # Do we *really* need to do this second check? 64 | pop.params["imf3"] = 2.3 65 | assert pop.params.dirtiness == 2 66 | w, model1b = pop.get_spectrum(tage=0.2) 67 | assert pop.params.dirtiness == 0 68 | 69 | assert_allclose(model1 / model1b - 1.0, 0.0) 70 | 71 | 72 | def test_param_checks(pop_and_params): 73 | # recomputes SSPs 74 | 75 | pop, params = pop_and_params 76 | _reset_default_params(pop, params) 77 | pop.params["sfh"] = 1 78 | pop.params["tage"] = 2 79 | pop.params["sf_start"] = 0.5 80 | # this should never raise an error: 81 | w, s = pop.get_spectrum(tage=pop.params["tage"]) 82 | # this used to raise an assertion error in the setter: 83 | pop.params["sf_start"] = pop.params["tage"] + 0.1 84 | # this also used to raise an assertion error in the setter: 85 | pop.params["imf_type"] = 8 86 | # fix the invalid IMF but leave the invalid sf_start > tage 87 | pop.params["imf_type"] = 1 88 | with pytest.raises(AssertionError): 89 | w, s = pop.get_spectrum(tage=pop.params["tage"]) 90 | pop.params["tage"] = 1.0 91 | pop.params["sf_start"] = 0.1 92 | w, s = pop.get_spectrum(tage=pop.params["tage"]) 93 | 94 | 95 | def test_smooth_lsf(pop_and_params): 96 | # recomputes SSPs 97 | 98 | pop, params = pop_and_params 99 | _reset_default_params(pop, params) 100 | tmax = 1.0 101 | wave_lsf = np.arange(4000, 7000.0, 10) 102 | x = (wave_lsf - 5500) / 1500.0 103 | # a quadratic lsf dependence that goes from ~50 to ~100 km/s 104 | sigma_lsf = 50 * (1.0 + 0.4 * x + 0.6 * x**2) 105 | w, spec = pop.get_spectrum(tage=tmax) 106 | pop.params["smooth_lsf"] = True 107 | assert pop.params.dirtiness == 2 108 | pop.set_lsf(wave_lsf, sigma_lsf) 109 | w, smspec = pop.get_spectrum(tage=tmax) 110 | hi = w > 7100 111 | sm = (w < 7000) & (w > 3000) 112 | assert np.allclose(spec[hi] / smspec[hi] - 1.0, 0.0) 113 | assert not np.allclose(spec[sm] / smspec[sm] - 1.0, 0.0) 114 | pop.set_lsf(wave_lsf, sigma_lsf * 2) 115 | assert pop.params.dirtiness == 2 116 | 117 | 118 | @skip_slow_tests 119 | def test_tabular(pop_and_params): 120 | """Test that you get the right shape spectral arrays for tabular SFHs, that 121 | the parameter dirtiness is appropriately managed for changing tabular SFH, 122 | and that multi-metallicity SFH work.""" 123 | 124 | # uses default SSPs, but makes them for every metallicity 125 | 126 | pop, params = pop_and_params 127 | _reset_default_params(pop, params) 128 | 129 | import os 130 | 131 | fn = os.path.join(os.environ["SPS_HOME"], "data/sfh.dat") 132 | age, sfr, z = np.genfromtxt(fn, unpack=True, skip_header=0) 133 | 134 | # Mono-metallicity 135 | pop.params["sfh"] = 3 136 | pop.set_tabular_sfh(age, sfr) 137 | w, spec = pop.get_spectrum(tage=0) 138 | pop.set_tabular_sfh(age, sfr) 139 | assert pop.params.dirty 140 | w, spec = pop.get_spectrum(tage=0) 141 | assert spec.shape[0] == len(pop.ssp_ages) 142 | assert pop.params["sfh"] == 3 143 | w, spec_last = pop.get_spectrum(tage=-99) 144 | assert spec_last.ndim == 1 145 | w, spec = pop.get_spectrum(tage=age.max()) 146 | assert np.allclose(spec / spec_last - 1.0, 0.0) 147 | pop.params["logzsol"] = -1 148 | w, spec_lowz = pop.get_spectrum(tage=age.max()) 149 | assert not np.allclose(spec / spec_lowz - 1.0, 0.0) 150 | 151 | # test the formed mass for single age 152 | assert np.allclose(np.trapz(sfr, age) * 1e9, pop.formed_mass) 153 | 154 | # Multi-metallicity 155 | pop._zcontinuous = 3 156 | pop.set_tabular_sfh(age, sfr, z) 157 | w, spec_multiz = pop.get_spectrum(tage=age.max()) 158 | assert not np.allclose(spec_lowz / spec_multiz - 1.0, 0.0) 159 | 160 | pop._zcontinuous = 1 161 | pop.set_tabular_sfh(age, sfr) 162 | # get mass weighted metallicity 163 | mbin = np.gradient(age) * sfr 164 | mwz = (z * mbin).sum() / mbin.sum() 165 | pop.params["logzsol"] = np.log10(mwz / pop.solar_metallicity) 166 | w, spec_onez = pop.get_spectrum(tage=age.max()) 167 | assert not np.allclose(spec_onez / spec_multiz - 1.0, 0.0) 168 | 169 | 170 | def test_get_mags(pop_and_params): 171 | """Basic test for supplying filter names to get_mags""" 172 | 173 | # uses default SSPs 174 | 175 | pop, params = pop_and_params 176 | _reset_default_params(pop, params) 177 | fuv1 = pop.get_mags(bands=["galex_fuv"])[:, 0] 178 | mags = pop.get_mags() 179 | fuv2 = mags[:, 61] # this should be galex_FUV 180 | fuv3 = mags[:, 62] # this should *not* be galex_FUV 181 | assert np.all(fuv1 == fuv2) 182 | assert np.all(fuv1 != fuv3) 183 | 184 | 185 | def test_ssp(pop_and_params): 186 | """Test that you get a sensible wavelength array, and that you get a 187 | sensible V-band magnitude for a 1-Gyr SSP""" 188 | 189 | # uses default SSPs 190 | 191 | pop, params = pop_and_params 192 | _reset_default_params(pop, params) 193 | pop.params["sfh"] = 0 194 | wave, spec = pop.get_spectrum(tage=1, peraa=True) 195 | assert (wave[0] > 0) & (wave[0] < wave[-1]) & (wave[0] < 912.0) 196 | assert (wave[-1] > 1e6) & (wave[-1] < 1e10) 197 | Mv = 4.62 # AB absolute magnitude for a Zsol 1Gyr old SSP 198 | # This also tests get_mags 199 | mag = pop.get_mags(tage=1, bands=["v"]) 200 | assert np.all(abs(mag - Mv) < 1.0) 201 | assert np.all((pop.stellar_mass < 1.0) & (pop.stellar_mass > 0)) 202 | assert pop.params.dirtiness == 0 203 | 204 | 205 | def test_libraries(pop_and_params): 206 | """This does not require or build clean SSPs""" 207 | 208 | # uses default SSPs 209 | 210 | pop, params = pop_and_params 211 | _reset_default_params(pop, params) 212 | ilib, splib, dlib = pop.libraries 213 | assert ilib == pop.isoc_library 214 | assert splib == pop.spec_library 215 | assert dlib == pop.duste_library 216 | 217 | 218 | def test_filters(): 219 | """Test all the filters got transmission data loaded.""" 220 | 221 | # uses default SSPs 222 | 223 | flist = filters.list_filters() 224 | # force trans cache to load 225 | filters.FILTERS[flist[0]]._load_transmission_cache() 226 | for f in flist: 227 | assert f in filters.TRANS_CACHE, "transmission not loaded for {}".format(f) 228 | 229 | 230 | def test_csp_dirtiness(pop_and_params): 231 | """Make sure that changing CSP parameters increases dirtiness to 1""" 232 | # uses default SSPs 233 | 234 | pop, params = pop_and_params 235 | _reset_default_params(pop, params) 236 | pop.params["sfh"] = 1 237 | pop.params["tau"] = 1.0 238 | wave, spec = pop.get_spectrum(tage=1.0) 239 | assert pop.params.dirtiness == 0 240 | pop.params["tau"] = 3.0 241 | assert pop.params.dirtiness == 1 242 | 243 | 244 | def test_redshift(pop_and_params): 245 | """Test redshifting, make sure that 246 | 1. redshifting does not persist in cached arrays 247 | 2. specifying redshift via get_mags keyword or param key are consistent 248 | """ 249 | 250 | # uses default SSPs 251 | 252 | pop, params = pop_and_params 253 | _reset_default_params(pop, params) 254 | pop.params["sfh"] = 0 255 | pop.params["zred"] = 0.0 256 | pop.params["add_igm_absorption"] = False 257 | v1 = pop.get_mags(redshift=1.0, tage=1.0, bands=["v"]) 258 | v2 = pop.get_mags(redshift=1.0, tage=1.0, bands=["v"]) 259 | assert np.all(v1 == v2) 260 | 261 | pop.params["zred"] = 1.0 262 | v3 = pop.get_mags(redshift=None, tage=1.0, bands=["v"]) 263 | v4 = pop.get_mags(redshift=None, tage=1.0, bands=["v"]) 264 | v5 = pop.get_mags(redshift=0.0, tage=1.0, bands=["v"]) 265 | assert np.all(v3 == v4) 266 | 267 | assert np.all(v3 == v1) 268 | assert np.all(v5 != v4) 269 | 270 | 271 | def test_dust3(pop_and_params): 272 | """Make sure nebular lines are actually added.""" 273 | 274 | # uses default SSPs 275 | 276 | pop, params = pop_and_params 277 | _reset_default_params(pop, params) 278 | pop.params["sfh"] = 4 279 | pop.params["dust_type"] = 4 280 | pop.params["tau"] = 5.0 281 | pop.params["dust1"] = 0 282 | pop.params["dust2"] = 0.5 283 | 284 | # make sure dust3 affects the population when there are old stars 285 | pop.params["dust3"] = 0.0 286 | mag1 = pop.get_mags(tage=1.0, bands=["u", "b"]) 287 | pop.params["dust3"] = 1.0 288 | mag2 = pop.get_mags(tage=1.0, bands=["u", "b"]) 289 | assert np.all(mag2 > mag1) 290 | 291 | # make sure the dust3 isn't affecting young populations 292 | pop.params["dust_tesc"] = 8 293 | pop.params["dust3"] = 0.0 294 | mag3 = pop.get_mags(tage=0.05, bands=["u", "b"]) 295 | pop.params["dust3"] = 1.0 296 | mag4 = pop.get_mags(tage=0.05, bands=["u", "b"]) 297 | assert_allclose(mag3, mag4) 298 | 299 | 300 | def test_nebemlineinspec(pop_and_params): 301 | """Make sure nebular lines are actually added.""" 302 | 303 | # uses default SSPs 304 | 305 | pop, params = pop_and_params 306 | _reset_default_params(pop, params) 307 | pop.params["sfh"] = 4 308 | pop.params["tau"] = 5.0 309 | pop.params["add_neb_emission"] = True 310 | pop.params["nebemlineinspec"] = False 311 | wave, spec_neboff = pop.get_spectrum(tage=1.0) 312 | pop.params["nebemlineinspec"] = True 313 | wave, spec_nebon = pop.get_spectrum(tage=1.0) 314 | assert (spec_nebon - spec_neboff).sum() > 0 315 | assert np.all(np.isfinite(pop.emline_luminosity)) 316 | assert np.all(np.isfinite(pop.emline_wavelengths)) 317 | ha_idx = (wave > 6556) & (wave < 6573) 318 | assert (spec_nebon - spec_neboff)[ha_idx].sum() > 0 319 | 320 | 321 | def test_mformed(pop_and_params): 322 | """Make sure formed mass integrates to 1 for parameteric SFH""" 323 | # uses default SSPs 324 | 325 | pop, params = pop_and_params 326 | _reset_default_params(pop, params) 327 | pop.params["sfh"] = 1 328 | pop.params["const"] = 0.5 329 | w, s = pop.get_spectrum(tage=0) 330 | assert pop.formed_mass[-1] == 1 331 | assert pop.formed_mass[50] < 1.0 332 | assert pop.formed_mass[50] > 0.0 333 | w, s = pop.get_spectrum(tage=0) 334 | assert pop.formed_mass[-1] == 1.0 335 | 336 | 337 | def test_light_ages(pop_and_params): 338 | """Make sure light weighting works, and gives sensible answers for the 339 | light-weighted age in the FUV and mass-weighted age stored in 340 | `stellar_mass`""" 341 | # uses default SSPs 342 | 343 | pop, params = pop_and_params 344 | _reset_default_params(pop, params) 345 | tmax = 5.0 346 | pop.params["sfh"] = 1 347 | pop.params["const"] = 0.5 348 | w, spec = pop.get_spectrum(tage=tmax) 349 | mstar = pop.stellar_mass 350 | lbol = pop.log_lbol 351 | pop.params["compute_light_ages"] = True 352 | w, light_age = pop.get_spectrum(tage=tmax) 353 | assert np.all(np.abs(np.log10(spec / light_age)) > 1) 354 | # make sure fuv really from young stars 355 | fuv = (w > 1220) & (w < 2000) 356 | assert (light_age[fuv]).max() < 0.1 357 | assert (light_age[fuv]).max() > 1e-5 358 | assert pop.log_lbol != lbol 359 | assert pop.stellar_mass != mstar 360 | assert pop.stellar_mass < tmax 361 | # luminosity weighted age always less than mass-weighted age 362 | # assert pop.log_lbol < pop.stellar_mass 363 | 364 | 365 | def test_smoothspec(pop_and_params): 366 | # FIXME: This is not very stringent 367 | 368 | # uses default SSPs 369 | 370 | pop, params = pop_and_params 371 | _reset_default_params(pop, params) 372 | wave, spec = pop.get_spectrum(tage=1, peraa=True) 373 | spec2 = pop.smoothspec(wave, spec, 160.0, minw=1e3, maxw=1e4) 374 | assert (spec - spec2 == 0.0).sum() > 0 375 | 376 | 377 | @skip_slow_tests 378 | def test_ssp_weights(pop_and_params): 379 | """Check that weights dotted into ssp is the same as the returned spectrum 380 | when there's no dust or emission lines and zcontinuous=0""" 381 | 382 | # uses default SSPs 383 | 384 | pop, params = pop_and_params 385 | _reset_default_params(pop, params) 386 | 387 | import os 388 | 389 | fn = os.path.join(os.environ["SPS_HOME"], "data/sfh.dat") 390 | age, sfr, z = np.genfromtxt(fn, unpack=True, skip_header=0) 391 | pop.params["sfh"] = 3 392 | pop.set_tabular_sfh(age, sfr) 393 | zind = -3 394 | pop.params["logzsol"] = np.log10(pop.zlegend[zind] / pop.solar_metallicity) 395 | 396 | wave, spec = pop.get_spectrum(tage=age.max()) 397 | mstar = pop.stellar_mass 398 | wght = pop._ssp_weights 399 | ssp, smass, slbol = pop._all_ssp_spec() 400 | 401 | assert np.allclose((smass[:, zind] * wght[:, 0]).sum(), mstar) 402 | 403 | 404 | # Requires scipy 405 | # def test_sfr_avg(): 406 | 407 | # _reset_default_params() 408 | # pop.params['sfh'] = 1.0 409 | # pop.params['const'] = 0.5 410 | # w, spec = pop.get_spectrum(tage=0) 411 | # sfr6 = pop.sfr_avg(dt=1e-3) 412 | # dsfr = np.log10(pop.sfr/pop.sfr6) 413 | # good = pop.ssp_age > 6 414 | # assert np.all(np.abs(dsfr[good]) < 1e-2) 415 | 416 | 417 | # def test_imf3_multiprocessing(): 418 | # from multiprocessing import Pool 419 | # pool = Pool() 420 | # thetas = np.linspace(2.3, 8.3, 4) 421 | # single = map(_get_model, thetas) 422 | # multi = pool.map(_get_model, thetas) 423 | # assert_allclose(single, multi) 424 | -------------------------------------------------------------------------------- /tools/f2py_include.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | 5 | import numpy.f2py 6 | 7 | try: 8 | incl_dir = numpy.f2py.get_include() 9 | except AttributeError: 10 | incl_dir = os.path.join(os.path.dirname(numpy.f2py.__file__), "src") 11 | 12 | print(os.path.normpath(incl_dir)) 13 | --------------------------------------------------------------------------------