Skip to content

Cosmo extension for gwpopulation#88

Merged
ColmTalbot merged 30 commits intoColmTalbot:mainfrom
HuiTong5:cosmo_dev
May 15, 2024
Merged

Cosmo extension for gwpopulation#88
ColmTalbot merged 30 commits intoColmTalbot:mainfrom
HuiTong5:cosmo_dev

Conversation

@HuiTong5
Copy link
Copy Markdown
Contributor

Try to add cosmology extension for gwpopulation which allows joint cosmology & population analysis, i.e., spectral siren.

The main changes are:

  1. add a cosmology-aware subclass of bilby.hyper.model.Model for cosmology related calculation
  2. have redshift models with changeable cosmology models and corresponding cosmo funcs
  3. make sure the caching step for mass distribution is correct given the situation that the source frame mass samples for spectral siren fit could vary given different cosmological parameters.

Copy link
Copy Markdown
Owner

@ColmTalbot ColmTalbot left a comment

Choose a reason for hiding this comment

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

Hi @HuiTong5 thanks for the contribution! I think this is definitely a change worth having in experimental for a while. I think that until there is a numpy/jax/cupy agnostic cosmology calculator, it should probably stay there.

It looks like the pre-commits are failing, you run these locally following these instructions.

We should also add some tests for this, you can probably base it off the tests for the Redshift models and think about some unit tests for the cosmology conversions.

I'll add that if you would prefer to package this up independently you can see a template here, but that isn't my preference in this case.

if self.astropy_conv == True:

samples['redshift'] = xp.asarray([z_at_value(cosmo.luminosity_distance, d*u.Mpc,zmax=self.z_max) for d in to_numpy(data['luminosity_distance'])])
samples['mass_1'] = data['mass_1']/(1+samples['redshift'])
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This conditional block seems overly restrictive and unnecessarily complicated, for example, what if someone is only fitting mass_1? This will raise an error in this case.

I think we should take some time to find a neater solution to this. Two possibilities come to mind:

  • having a preset list of mass parameters that are checked for, e.g, ["mass_1", "mass_2", "total_mass", "chirp_mass"]. Even better would be to have these be passed as arguments to __init__, something like "redshifted_keys".
        for key in data:
            if key in self.redshift_keys:
                samples[key] = data[key] / (1 + samples["redshift"])
            else:
                samples[key] = data[key]
  • looking for a specific flag in keys, i.e., make all redshifted parameters have a _detector suffix, "mass_1_detector" etc. This breaks more strongly from Bilby, but that is already pretty broken by the difference in definition of "mass_1".
         for key in data:
            if key.endswith("_detector"):
                data[key[:-9]] = data[key] / (1 + data["redshift"])
    This can also be combined with the explicit "redshifted_keys" to avoid looping over the entire dictionary and just looking for specific parameters.

Either of these avoid hardcoding any value and in here we just need. Personally, I think the second option is neater as it avoids having the same parameter names meaning different things and potentially also avoides making a new dictionary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The second option sounds more consistent with your other comments on gwpopulation_pipe. I may try that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated in 78835d2 following option 2.

interp_dl_to_z = splrep(dl,zs,s=0)

samples['redshift'] = xp.nan_to_num(xp.asarray(splev(to_numpy(data['luminosity_distance']),interp_dl_to_z,ext=0)))
samples['mass_1'] = data['mass_1']/(1+samples['redshift'])
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Everything from here to L157 is just repeating what was done before, regardless of how we wind up dealing with the implementation of the redshifting, only the following (and equivalent above) should be in the if/else.

            zs = to_numpy(self.zs)
            dl = cosmo.luminosity_distance(to_numpy(zs)).value
            interp_dl_to_z = splrep(dl,zs,s=0)

            samples['redshift'] = xp.nan_to_num(xp.asarray(splev(to_numpy(data['luminosity_distance']),interp_dl_to_z,ext=0)))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 597fc2c

samples = dict()
if self.astropy_conv == True:

samples['redshift'] = xp.asarray([z_at_value(cosmo.luminosity_distance, d*u.Mpc,zmax=self.z_max) for d in to_numpy(data['luminosity_distance'])])
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

z_at_value is now automatically vectorized, so you can remove this list comprehension and just do

Suggested change
samples['redshift'] = xp.asarray([z_at_value(cosmo.luminosity_distance, d*u.Mpc,zmax=self.z_max) for d in to_numpy(data['luminosity_distance'])])
samples['redshift'] = xp.asarray(z_at_value(cosmo.luminosity_distance, to_numpy(data['luminosity_distance']) * u.Mpc, zmax=self.z_max))

We can also possibly avoid repeated transfer between array types by setting

data["luminosity_distance"] = to_numpy(data["luminosity_distance"]) somewhere.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

When fitting cosmology I think this will cause a slowdown when transferring computations between astropy and gwpopulation. I think this class should include a function like (assuming flatLambdaCDM):

def comoving_distance(z):
    E_z = xp.sqrt(Om * (1+self.zs)**3 + (1-Om))
    return c/H0 * cumtrapz(Ez, 0, z)
def luminosity_distance(z):
    return (1+z) * self.comoving_distance(z)

This way these transformations can be handled all natively in the CPU/GPU agnostic manner. To be generic there could be an option to have these computed with astropy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

z_at_value comment fixed in 597fc2c.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Indeed, not only this conversion, but also some other calculations by astropy may introduce a significant slowdown. I've implemented some interpolation to replace astropy.cosmo call somewhere else. I will have a more comprehensive thinking of possible approximation of all the astropy calculations in this PR.


return samples

def dL_by_dz(self, z, dl, **parameters):
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

dl should be included in the docstring.

Also, please use full variable names, redshift, luminosity_distance for readability. The method name is probably fine, but luminosity distance_to_redshift_jacobian would be better.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I've just noticed that this is taking both the redshift and distance when there should be a deterministic mapping between them. I worry that this is inviting user error.

What if you move the logic to convert from distance to redshift to inside this method (or another method that calls this) and then return both redshift and dl_by_dz (lowercase for variable names)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

docstring and variables names fixed in 597fc2c.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated in 78835d where I combine the conversion of samples from detector frame to source frame and the calculation of Jabobian term in one func which only need detector frame samples as input.

def dL_by_dz(self, z, dl, **parameters):

"""
Calculates the detector frame to source frame Jacobian d_det/d_sour for dL and z
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Again, we don't need to use abbreviations here, a longer docstring will be helpful. Ideally, with an equation, there are docs here that explain some of the subtleties of getting this to render correctly in the online docs.

Hui Tong and others added 2 commits April 30, 2024 00:12
Co-authored-by: Colm Talbot <talbotcolm@gmail.com>
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 30, 2024

Codecov Report

Attention: Patch coverage is 66.66667% with 4 lines in your changes are missing coverage. Please review.

Project coverage is 94.18%. Comparing base (b7edefa) to head (5ab859e).

Files Patch % Lines
gwpopulation/models/mass.py 66.67% 2 Missing ⚠️
gwpopulation/vt.py 66.67% 2 Missing ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #88      +/-   ##
==========================================
- Coverage   94.74%   94.18%   -0.56%     
==========================================
  Files          11       11              
  Lines         799      808       +9     
==========================================
+ Hits          757      761       +4     
- Misses         42       47       +5     
Flag Coverage Δ
python3.10 94.06% <66.67%> (-0.56%) ⬇️
python3.11 94.06% <66.67%> (-0.56%) ⬇️
python3.9 94.05% <66.67%> (-0.56%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@ColmTalbot
Copy link
Copy Markdown
Owner

@afarah18 has some code based on this paper that we might be able to adapt to handle the cosmological conversions. It isn't as flexible as astropy, it currently just handles variable H0 and Omega_m, but I suspect it can be extended to w quite easily.

@HuiTong5
Copy link
Copy Markdown
Contributor Author

Thanks for sharing! I will take a look at the paper and Amenda's code when I have time. I have pushed some updates based on our previous discussion. I haven't done any actual calculations/testing, so there may be a lot of bugs in it. I'll do some testing soon. But I think the overall framework has improved to some extent.

@ColmTalbot
Copy link
Copy Markdown
Owner

The new changes generally look good! Amanda and I are working on packaging up the other cosmology implementation and I'll probably match the astropy API closely so it should be a drop in replacement that I can do later.

Hui Tong added 4 commits May 12, 2024 21:16
…anyone would like to turn it on given the enormous time cost. Can add astropy_conv back later with other cosmology implementation. Aslo, minor fix for vt surveyed_hypervolume
@ColmTalbot
Copy link
Copy Markdown
Owner

I made some changes to the way the modules that need the backend to change are specified, the short version is that you should now add the new module to the setup.cfg file instead of the backend.py.

I also pushed a version of the cosmology implementation that @afarah18 and I put together in https://github.com/ColmTalbot/wcosmo. It will be a fairly simple drop-in change once this has been merged and I added some similar code to what you have in this PR so that I can use it as a free-standing version in the meantime.

@HuiTong5
Copy link
Copy Markdown
Contributor Author

HuiTong5 commented May 14, 2024

The new repo for cosmology implementation looks great! I noticed there content in models.py in the new repo is almost the same as the new module cosmo_models.py in this PR. What's the plan for this PR? Should I call the functions in the new repo to avoid repetitive code?

@ColmTalbot
Copy link
Copy Markdown
Owner

The new repo for cosmology implementation looks great! I noticed there content in models.py in the new repo is almost the same as the new module cosmo_models.py in this PR. What's the plan for this PR? Should I call the functions in the new repo to avoid repetitive code?

That repo is essentially a holding place so that I can use the code while we get the changes into gwpopulation. I should have added an attribution to you in there as it was strongly influenced by this PR, sorry! I'll do that today. My plan is:

  • finalize and merge this PR using astropy as currently
  • I open a new PR to use the cosmology code in the new repo either natively or as an external import

The latter stage would involve removing models.py basically entirely.

@HuiTong5
Copy link
Copy Markdown
Contributor Author

HuiTong5 commented May 15, 2024

I had a quick test on the time cost for single likelihood evaluation on the head node of pcdev3. The cosmo one costed roughly 0.53s and non cosmo one costed roughly 0.15s.

@ColmTalbot
Copy link
Copy Markdown
Owner

ColmTalbot commented May 15, 2024

I had a quick test on the time cost for single likelihood evaluation on the head node of pcdev3. The cosmo one costed roughly 0.53s and non cosmo one costed roughly 0.15s.

Nice! Which backend is that using?

I'm going to merge this now, so I can make an attempt at implementing the other cosmology code. Thanks @HuiTong5! EDIT: I just saw this is marked as draft, so I'll hold off until you remove that label.

@HuiTong5
Copy link
Copy Markdown
Contributor Author

I was using cupy

@HuiTong5
Copy link
Copy Markdown
Contributor Author

I'm going to merge this now, so I can make an attempt at implementing the other cosmology code. Thanks @HuiTong5! EDIT: I just saw this is marked as draft, so I'll hold off until you remove that label.

I don't think I plan to have any other changes if you are happy with this. What label should I have? Should I have DEV or I just need to simply remove the "Draft".

@ColmTalbot ColmTalbot changed the title Draft: Cosmo extension for gwpopulation Cosmo extension for gwpopulation May 15, 2024
@ColmTalbot
Copy link
Copy Markdown
Owner

Great, I removed Draft from the issue title, in future, there is a "mark as draft" button on PRs somewhere that makes it more clear.

@ColmTalbot ColmTalbot merged commit 261f2b8 into ColmTalbot:main May 15, 2024
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.

4 participants