Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use synphot package to perform bandpass integration #21

Closed
MetinSa opened this issue Apr 19, 2024 · 16 comments
Closed

Use synphot package to perform bandpass integration #21

MetinSa opened this issue Apr 19, 2024 · 16 comments
Assignees
Labels
enhancement New feature or request
Milestone

Comments

@MetinSa
Copy link
Collaborator

MetinSa commented Apr 19, 2024

from pyOpenSci/software-submission#161:

Instead of manually performing bandpass integrations with the custom Bandpass type, we should use the Astropy affiliated synphot package. This would also provide better integration with Astropy.

@MetinSa MetinSa self-assigned this Apr 19, 2024
@MetinSa MetinSa added the enhancement New feature or request label Apr 19, 2024
@MetinSa
Copy link
Collaborator Author

MetinSa commented Apr 20, 2024

I noticed that synphot is only available for Python >= 3.10, so I will put this off until ZodiPy drop 3.9 unless there is a work around @pllim? Is it correct that according to https://numpy.org/neps/nep-0029-deprecation_policy.html astropy-affiliated packages should drop python 3.9 already on Apr 05 this year?

@pllim
Copy link
Contributor

pllim commented Apr 20, 2024

Hello and I apologize for any inconvenience. Yes, synphot dropped support for Python 3.9 but only recently via spacetelescope/synphot_refactor#387 . You should still be able to get synphot < 1.4 in Python 3.9 .

@pllim
Copy link
Contributor

pllim commented Apr 20, 2024

Would be happy to discuss more if you are interested. Integration is nothing too fancy. synphot uses trapezoid integration under the curve provided by scipy (used to be np.trapz in older versions). See https://synphot.readthedocs.io/en/latest/api/synphot.spectrum.BaseSpectrum.html#synphot.spectrum.BaseSpectrum.integrate (analytical was added much later and defers to astropy.modeling but not widely used).

@MetinSa
Copy link
Collaborator Author

MetinSa commented Apr 22, 2024

Thanks it should work with synphot < 1.4.

Yeah that would be nice! I want to use synphot to compute a table of bandpass integrated blackbody emission values where the blackbodies have varying temperature. It should work for either a center frequency or an empirical bandpass provided by the user as two 1D arrays corresponding to weights and frequencies. See get_bandpass_interpolation_table in https://github.com/Cosmoglobe/zodipy/blob/main/zodipy/_bandpass.py.
Not quite sure how I would do this using these models but synphot.models.Empirical1d or synphot.spectrum.BaseSpectrum seem relevant? I also see that there is a synphot.models.Blackbody1D which means that I probably don't need to use any custom b_nu function in ZodiPy either?

@pllim
Copy link
Contributor

pllim commented Apr 22, 2024

Generally speaking, this would give you a blackbody spectrum (before passing through the bandpass):

from astropy import units as u
from synphot import SourceSpectrum
from synphot.models import BlackBodyNorm1D

bb_temp = 5777 * u.K
bb = SourceSpectrum(BlackBodyNorm1D, temperature=bb_temp)

# Sampling frequencies. This samples most of the flux but feel free to extend as needed.
freq = bb.waveset.to(u.Hz, u.spectral())

# Sampled flux
flux_unit = u.MJy
flux = bb(freq, flux_unit=flux_unit)

But to put it through bandpass first before sampling, you have to construct an Observation, as documented in https://synphot.readthedocs.io/en/latest/synphot/observation.html .

Often times, you might also want to renormalize the source spectrum first to some standard you want before passing it through the bandpass using https://synphot.readthedocs.io/en/latest/api/synphot.spectrum.BaseSourceSpectrum.html#synphot.spectrum.BaseSourceSpectrum.normalize .

So a more complete example would be more like this:

from astropy import units as u
from synphot import SourceSpectrum, SpectralElement, Observation, units as syn_units
from synphot.models import BlackBodyNorm1D, Box1D

bb_temp = 5777 * u.K
bb = SourceSpectrum(BlackBodyNorm1D, temperature=bb_temp)

# Say, you know it is 17 VEGAMAG in Johnson V filter
vega = SourceSpectrum.from_vega()  # For unit conversion  
johnson_v = SpectralElement.from_filter('johnson_v')  
bb_norm = bb.normalize(17 * syn_units.VEGAMAG, johnson_v, vegaspec=vega)

# Now pass the normalized blackbody into your bandpass. Using box here but there are many available.
bandpass = SpectralElement(Box1D, amplitude=1, x_0=5000 * u.AA, width=10 * u.AA)
obs = Observation(bb_norm, bandpass)

# https://synphot.readthedocs.io/en/latest/synphot/formulae.html#synphot-formula-effstim
effstim = obs.effstim(flux_unit=u.MJy)

When it comes to an Observation, usually people want the effective stimulus instead of integration under the curve.

Hope this helps!

@MetinSa
Copy link
Collaborator Author

MetinSa commented Apr 28, 2024

Thanks you, that helps!

I am still having some trouble... Is this the way to go if I want to bandpass integrate for a range of different blackbodies:

bandpass = SpectralElement(Empirical1D, points=frequencies, lookup_table=weights)
for temp in temperatures:
    bb = SourceSpectrum(BlackBody1D(temp))
    obs = Observation(bb, bandpass)
    bp_integrated_emission = obs.integrate()

I am also interested in getting the emission in spectral flux density units, so for instance MJy/sr.
How efficient is this compared to just evaluation b_nu(frequencies) and then bandpass integrating with np.trapz for instance?

@pllim
Copy link
Contributor

pllim commented Apr 28, 2024

@MetinSa , is that a real bandpass or you are using it to represent something else? If you have a minimal example with just numbers and expected answers, I can see what in synphot can best represent your use case. Thanks!

@MetinSa
Copy link
Collaborator Author

MetinSa commented Apr 28, 2024

@pllim yes the frequency and weights arrays represent a user inputted bandpass in normalized Jy units. I made a gist where i read in the DIRBE 25um bandpass and do the bandpass integration i wish to do in ZodiPy with synphot https://gist.github.com/MetinSa/9732a366fbe932a044819dcaa948df88.

Thank you for being so quick and nice to respond:)

@pllim
Copy link
Contributor

pllim commented Apr 29, 2024

When I run your code, I find that normalized_weights (the actual numbers multiplied to blackbody flux) is all negative. In the context of "bandpass", in which the values should only be between 0 and 1 (inclusive), this is un-physical. Could you please clarify?

@MetinSa
Copy link
Collaborator Author

MetinSa commented Apr 29, 2024

Ahh, I had forgotton to flip the frequencies and weights arrays after changing to frequency convention so that its in increasing order. Updated the gist now.

@pllim
Copy link
Contributor

pllim commented Apr 29, 2024

Just for my own record, looks like you are only interested in this part of the blackbody curve.

@MetinSa
Copy link
Collaborator Author

MetinSa commented Apr 29, 2024

Right, for this particular bandpass, but it should be able to handle arbitrary frequency/wavelength ranges and temperatures between ~50-500K

@pllim
Copy link
Contributor

pllim commented Apr 29, 2024

I don't grok the bandpass normalization, but that doesn't matter.

I tried re-constructing what I think you are doing with synphot API but I cannot get the numbers to match exactly. If all you want to do is simple trapezoid integration with some custom normalization, synphot API might be a little of an overkill, because there are a lot of assumptions and checks that went into full-blown synthetic photometry. Furthermore, for historical reason, all the internal calculations are done in wavelength space but looks like you want to calculate in frequency space natively.

However, synphot does have a synphot.blackbody.blackbody_nu function that you can use to replace your b_nu. p.s. I noticed that your b_nu is missing the per-steradian in returned flux unit.

Also, in tracing your calculation, I think you need to get rid of the per-micron in the integrated flux unit (because you already integrated over a range of Hz/micron), so the answer I think should be 2.2052944808204042 W / m2 and not 2.2052944808204042 W / (um m2).

I hope this helps and thanks for considering synphot.

@pllim
Copy link
Contributor

pllim commented Apr 29, 2024

Also, in numpy 2.0, np.trapz has been deprecated in favor of scipy.integrate.trapezoid. FYI.

@MetinSa
Copy link
Collaborator Author

MetinSa commented Apr 30, 2024

I had the feeling that synphot might be a bit overkill for this purpose yes, but this was useful for me nevertheless. I will simply move over to use astropy.models.physical_model.BlackBody which can handle wavelengths too , instead of the custom b_nu implementation.

Also, in numpy 2.0, np.trapz has been deprecated in favor of scipy.integrate.trapezoid. FYI.

Thats a good catch, thanks @pllim!

@MetinSa MetinSa closed this as completed Apr 30, 2024
@MetinSa MetinSa mentioned this issue Apr 30, 2024
16 tasks
@pllim
Copy link
Contributor

pllim commented Apr 30, 2024

Yes, the blackbody model might work too. Good luck!

@MetinSa MetinSa added this to the v1.0.0 milestone May 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants