Skip to content

QSOGen Quasar SEDs for realistic colors and magnitudes for LSST Euclid and Roman bands#397

Open
timedilatesme wants to merge 42 commits intoLSST-strong-lensing:mainfrom
timedilatesme:accurate-quasar-SEDs
Open

QSOGen Quasar SEDs for realistic colors and magnitudes for LSST Euclid and Roman bands#397
timedilatesme wants to merge 42 commits intoLSST-strong-lensing:mainfrom
timedilatesme:accurate-quasar-SEDs

Conversation

@timedilatesme
Copy link
Collaborator

@timedilatesme timedilatesme commented Jan 20, 2026

Currently SLSim quasars are too blue. This PR aims to solve that by using empirical quasar SEDs and compute magnitudes while generating the quasar population with QuasarRate class. Here is a list of what's complete and what's not.

  • QSOGen module integrated into the QuasarRate class.
  • Now magnitudes in all bands are computed w/in the QuasarRate class.
  • Test functions for all modified files are ready.
  • Notebook updates.
  • To be able to access QSOGen quasar/point source spectra from within the lens class. (will be implemented in a new PR).
  • Defined new functions to handle slsim and speclite filtername conventions. (Inspired from Bryce's SNR PR #398).
  • Added a new roman-lsst-like.yml skypy config file so that both can be simulated simultaneously w/o Eucli z>3 error.

@codecov
Copy link

codecov bot commented Feb 1, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.50%. Comparing base (f39af79) to head (e1873ef).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #397      +/-   ##
==========================================
+ Coverage   98.42%   98.50%   +0.07%     
==========================================
  Files          99      102       +3     
  Lines        7511     7804     +293     
==========================================
+ Hits         7393     7687     +294     
+ Misses        118      117       -1     
Files with missing lines Coverage Δ
slsim/ImageSimulation/image_quality_lenstronomy.py 100.00% <100.00%> (ø)
.../SourceCatalogues/QuasarCatalog/qsogen/__init__.py 100.00% <100.00%> (ø)
...es/SourceCatalogues/QuasarCatalog/qsogen/config.py 100.00% <100.00%> (ø)
...es/SourceCatalogues/QuasarCatalog/qsogen/qsosed.py 100.00% <100.00%> (ø)
...urces/SourceCatalogues/QuasarCatalog/quasar_pop.py 98.93% <100.00%> (+0.53%) ⬆️
slsim/Sources/SourceTypes/quasar.py 98.80% <100.00%> (+1.05%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@review-notebook-app
Copy link

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@timedilatesme timedilatesme marked this pull request as ready for review February 2, 2026 13:54
@timedilatesme
Copy link
Collaborator Author

Hello @sibirrer , Finally the tests are complete for this PR and it is ready for review.

I also add @Henry-Best-01 for a quick look especially at the quasar.py file where I have defined a new method to initialize the agn_class which was necessary as this class contains info about parameters necessary for microlensing calculations (w/in the update_microlensing_kwargs_source_morphology method) so I had to define it outside the light_curve method.

@Henry-Best-01 : So currently the mean magnitudes are drawn from the QSOGen while variability is drawn from the Thin Disk Model currently implemented. I am working on incorporating your suggestions from last Thursday so that we effectively use observationally inferred temp. profile for the variability as well. I will try to fix that in another PR.

Also the updated notebook now shows correct colors for the generated quasar population.

@sibirrer
Copy link
Contributor

sibirrer commented Feb 2, 2026

Thank you @timedilatesme ! I will assign @Henry-Best-01 as reviewer (need to be added as triage in this repo first).
One definition might be very similar to another PR #398 (get the band pass filter names converted to speclite). I might merge the other PR first, just fyi

Copy link
Contributor

@Henry-Best-01 Henry-Best-01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall great! I left some comments across various files, please let me know if any suggestions don't make sense.

alpha_red: -0.5
cosmology: !astropy.cosmology.default_cosmology.get []
filters: [
'Roman-F062', 'Roman-F087', 'Roman-F106', 'Roman-F129', 'Roman-F158', 'Roman-F184', 'Roman-F146', 'Roman-F213',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering if you missed 'lsst2016-u'. Disregard this comment if it's not important.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so by default in skypy yml files we've not been including u band info as it's only available for galaxies upto redshift of 4. After which skypy SEDs don't work. This is the same as other previously defined defined yml files as well.

However, if one wants to include u band info then that can be done by simply specifying it while instantiating the SkypyPipeline class, which does update this yml file. so I think it should be fine to miss u band by default

magnitudes: $red.M
coefficients: $red.coeff
filter: bessell-B
mag_F062, mag_F087, mag_F106, mag_F129, mag_F158, mag_F184, mag_F146, mag_F213, mag_g, mag_r, mag_i, mag_z, mag_y: !skypy.galaxies.spectrum.kcorrect.apparent_magnitudes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above, just wondering if you're missing 'mag_u'

cosmology: !astropy.cosmology.default_cosmology.get []
filters: [
'Roman-F062', 'Roman-F087', 'Roman-F106', 'Roman-F129', 'Roman-F158', 'Roman-F184', 'Roman-F146', 'Roman-F213',
'lsst2016-g', 'lsst2016-r', 'lsst2016-i', 'lsst2016-z', 'lsst2016-y']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering if the 'lsst2023' filters should be used here to match up to date speclite filters. If these are required to query SkyPy, disregard since I am not an expert on using SkyPy.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think yes we should use those, I was just following what was already there for other yml files but i think it's better to update everything to 2023 conventions. perhaps in another PR.

return [get_speclite_filtername(band) for band in bands]


ALL_SUPPORTED_BANDS = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is ALL_SUPPORTED_BANDS used, or is this an artifact of keeping track of the bands? If unused, perhaps it should be placed in a comment at the top of the file for informational purposes. Disregard this comment if used elsewhere.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind, I found where you import it!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, perhaps we can convince Matthew to make his code pip installable and remove these files. Until then, no problem!


# Use loose tolerance because emission lines are added ON TOP of the continuum
# which might shift the exact value at 3000A slightly
assert np.isclose(qs.flux[idx_3000], expected_f3000, rtol=0.2)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worthwhile to test with a quasar spectrum where the emission lines are not added / extremely suppressed? You should be able to set base_params["scal_emline"] = 0 or 1e-8 to do this I believe.

Then the tolerance can be tightened up a little.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

souns great! added this test.

base_params["gflag"] = True

# Wavelength array that does NOT cover 4000-5000A
bad_wav = np.linspace(1000, 3000, 500)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test when the bad wavelength partially covers this range, and is only values larger than this range? (e.g. ranges (3000 - 4400) and (6000 - 10000) )

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added this test as well!

redshifts=np.linspace(0.5, 1.0, 5),
sky_area=Quantity(0.05, unit="deg2"),
noise=True,
cosmo=FlatLambdaCDM(H0=70, Om0=0.3),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QSOGen assumes a cosmology of H0 = 71, Om0 = 0.27 which is hard coded. I think this should be tested consistently by changing these parameters to match.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now QSOGen uses the same cosmology as QuasarRate!

assert np.all(table["ps_mag_r"] > 0)

# Check that the magnitudes are distinct (g != r)
assert not np.allclose(table["ps_mag_g"], table["ps_mag_r"])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there the possibility that an emission line + redshift combo rarely makes these magnitudes equal? I am not sure what the precision on this columns values are, but if you have precision of 1 or 2 decimal places (e.g. due to modeling mag_err), I imagine sometimes this test could fail.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could certainly be possible that for a few quasars it may turn out to be equal. But certainly it would not be equal for all of them. The assertion will be correct if at least one of them has these magnitudes different which might mean that this test would not fail for a broad redshift range? I am keeping absolute tolerance to be millimags and I think the mags should be not this close for all quasars.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh great, millimags will be fine! I sometimes mix up what np.all() and np.any() do for tables. I appreciate the clarification!

# Typical quasar colors are between -1 and +2 roughly
assert np.all(
np.abs(g_i_color) < 5.0
), "Derived colors are unphysical, anchoring likely failed."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you test if this does fail at high redshift?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried testing this for z upto 5.5 which is the max i could test for with current implementation (that uses Richards QLF), I did not see this failing upto those redshifts. Perhaps It'll fail after 7? Do you think then it might be worth to keep the failing test for high-z? Maybe not now but in future?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be useful to define a mock file that has redshifts beyond 5.5 (something simple like integer redshifts 1 through 12 with some sort of mag _i associated with them, the exact values don't matter). I'm thinking in the future we'll have expanded catalogs similar to Richards' and a small test that the architecture will support them is nice.

@timedilatesme
Copy link
Collaborator Author

Hi @Henry-Best-01 , Thank you so much for the review! I have tried addressing mostly all of your comments and here's a summary:

  1. It'd certainly be great to have speclite filters supported directly (with speclite names) but this needs to be done across all modules in SLSim and I think it's better to do in a separate PR.
  2. I've added most tests but those that need z>7 testing can't be done due to Richards+ 2006 QLF that we use, as it only produces quasars upto z = 5.5.
  3. QSOGen now uses same cosmology as QuasarRate.

Please let me know if any of this needs further improvement!

Copy link
Contributor

@Henry-Best-01 Henry-Best-01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work!

The only last addition I would request is a small test catalog (perhaps ~10 lines) that extends to z ~ 12. The values don't need to be realistic, but it would be good to see if all the code in place is able (or not!) to handle it. If this is difficult or time consuming, then I would say it's okay to not worry about it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants