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

Add redshift effect #16

Merged
merged 12 commits into from
Jul 17, 2024
Merged

Add redshift effect #16

merged 12 commits into from
Jul 17, 2024

Conversation

OliviaLynn
Copy link
Member

@OliviaLynn OliviaLynn commented Jul 2, 2024

Change Description

Closes #4

Solution Description

Redshift conversions

  1. Converts desired observation_times, observation_wavelengths to emitted_times, emitted_wavelengths.
  2. We then feed these emitted_times, emitted_wavelengths values to a given PhysicalModel to get the emitted_flux.
  3. Finally, we convert the emitted_flux to observed_flux to get the flux densities as they would appear on Earth.

Conversions used are:
- emitted_wavelength = observed_wavelength / (1 + redshift)
- emitted_time = observed_time / (1 + redshift)
- observed_flux = emitted_flux / (1 + redshift)

Pre-effects

Note that this PR also creates the concept of "pre-effect," which we apply to times, wavelengths in the PhysicalModel's evaluate, just before we call flux_density = self._evaluate(times, wavelengths, **kwargs).

Our initial thoughts were that the only other effect that might implement a pre_effect method would be some kind of lensing (I think Mi said strong lensing?); if we do add another such pre_effect, we should consider the ordering and other factors that could be involved in them playing nicely together.

@OliviaLynn OliviaLynn requested review from hombit, aimalz and mi-dai July 2, 2024 20:53
@OliviaLynn OliviaLynn marked this pull request as ready for review July 2, 2024 20:53
Copy link
Contributor

@jeremykubica jeremykubica left a comment

Choose a reason for hiding this comment

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

Looks really good. I will let the scientists do the actual review to check the correctness of the computations. I added a few small comments.

src/tdastro/effects/redshift.py Outdated Show resolved Hide resolved
src/tdastro/effects/redshift.py Outdated Show resolved Hide resolved
src/tdastro/effects/redshift.py Outdated Show resolved Hide resolved
@OliviaLynn OliviaLynn marked this pull request as draft July 9, 2024 05:21
@OliviaLynn
Copy link
Member Author

OliviaLynn commented Jul 9, 2024

Questions for tomorrow:
1. State of "required parameters"/is it ok to have redshift as a required param
2. Would like to go over pre-effect docstring, feels wordy and I don't know standard language in this area
3. Would like us to further discuss epochs/how we line up timing as part of the shift-and-scale adjustment that is our redshift calculation

@OliviaLynn
Copy link
Member Author

Update July 10

Changes:

  1. Main: Time calculations now performed with respect to epoch aka t0
  2. Converted the EffectModel's required_parameters from a method to an attribute
  3. Uploaded a redshift-demo notebook to a branch in tdastro-sandbox

Questions:

  1. Have some outstanding questions about nomenclature/general wording - especially in 3 places, in links just below, but also highlighted in the PR with a comment per line-of-interest
    a. L27: General wording; also, are we picking t0 or epoch?
    a. L41: I use "observed" and "emitted" throughout the pre-effect code. Should I use something closer to "observation frame" and "rest frame"? (Is rest frame even applicable here?)
    a. L58: Someone tied to previous. I'm not sure how to word what's going on here in a way that's readable but still provides all the information needed.

@OliviaLynn OliviaLynn marked this pull request as ready for review July 10, 2024 20:06
src/tdastro/effects/redshift.py Outdated Show resolved Hide resolved
src/tdastro/effects/redshift.py Outdated Show resolved Hide resolved
src/tdastro/effects/redshift.py Outdated Show resolved Hide resolved
emitted_times_rel_to_t0 = observed_times_rel_to_t0 / (1 + self.redshift)
emitted_times = emitted_times_rel_to_t0 + self.t0
emitted_wavelengths = observed_wavelengths / (1 + self.redshift)
return (emitted_times, emitted_wavelengths)
Copy link
Contributor

@mi-dai mi-dai Jul 10, 2024

Choose a reason for hiding this comment

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

I think what the model usually provide in its look up table is actually this emitted_times_rel_to_t0, not emitted_times, so we should return that in terms of looking up for the flux_density.

Copy link
Contributor

Choose a reason for hiding this comment

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

Since we'd like to have a uniform interface, I'd vote to all models provide the same evaluate(times, wavelengths), where times are not relative to t0. Since model knows its t0, it is not a big deal to use it to get relative times, while computational overhead is negligible (and potentially zero with JIT)

Copy link
Contributor

Choose a reason for hiding this comment

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

OK. I'm fine with using times instead of relative times. I would still like to see if this is convenient enough for users after implementing some models.

src/tdastro/base_models.py Outdated Show resolved Hide resolved
super().__init__(**kwargs)
self.required_parameters = ["redshift", "t0"]
self.add_parameter("redshift", redshift, required=True, **kwargs)
self.add_parameter("t0", t0, required=True, **kwargs)
Copy link
Contributor

Choose a reason for hiding this comment

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

I hadn't thought about doing things this way, but this might be preferable.

The way the code is currently structured these are not meant to be included as parameters for the effect model, but rather to pull them from the physical model (via required_parameters). So for applying an effect, we would do something like: physical_model.redshift.

But this approach could be cleaner. We could get rid of required_parameters altogether and just pass in the PhysicalModel the way you do in the test scripts.

I need to think through the pros and cons a bit.

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe that redshift and t0 are physical model's parameters, not effect's parameters

flux_density : `numpy.ndarray`
The redshifted results.
"""
return flux_density / (1 + self.redshift)
Copy link
Contributor

Choose a reason for hiding this comment

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

See the overall comment in the __init__() function about overall approach.

In the current set up, this should be physical_model.redshift since we want to use a specific redshift for each object.

Copy link
Contributor

Choose a reason for hiding this comment

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

For this and my comments throughout: Olivia and I chatted about it today. If we make effects behave like other nodes in the graph, they will have consistent sampling behavior. In that case we will want to have the effect take in the PhysicalModel's parameters.

The pros of this approach is that 1) it keeps everything in the same graph formulation, 2) keeps the same sampling counter throughout the entire graph, 3) doesn't require a specialized interface for effects (the required_parameters list), and 4) allows for stateful effects.

The cons are: 1) as @hombit) notes there is a chance this could be misspecified during model creation, 2) it requires creating a unique effect for each physical model.

Let's go with the current approach for now. If this causes too much confusion, we can change back to stateless effects.

which will then be redshifted to observation-frame flux densities at the observation-frame
times and wavelengths. # TODO WORDING (3/3)
"""
observed_times_rel_to_t0 = observed_times - self.t0
Copy link
Contributor

Choose a reason for hiding this comment

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

See the overall comment in the __init__() function about overall approach.

In the current set up, the t0 and redshift parameters should come from the PhysicalModel applying this effect instead of the effect's parameter. So you will need to pass a PhysicalModel into pre_effect().

@@ -241,9 +243,15 @@ def evaluate(self, times, wavelengths, resample_parameters=False, **kwargs):
if resample_parameters:
self.sample_parameters(kwargs)

for effect in self.effects:
if hasattr(effect, "pre_effect"): # pre-effects == adjustments done to times and/or wavelengths
times, wavelengths = effect.pre_effect(times, wavelengths, **kwargs)
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 we will want to pass a reference to the PhysicalModel into pre_effect. See the comments in redshift.py. So this would be:

times, wavelengths = effect.pre_effect(times, wavelengths, physical_model=self, **kwargs)

Or something similar.

"""Get the values for a source with no effects and a redshifted source."""
model_no_effects = StepSource(brightness=brightness, t0=t0, t1=t1)
model_redshift = StepSource(brightness=brightness, t0=t0, t1=t1, redshift=redshift)
model_redshift.add_effect(Redshift(redshift=model_redshift, t0=model_redshift))
Copy link
Contributor

Choose a reason for hiding this comment

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

See the overall comment in the __init__() function about overall approach.

If we stick with the current approach, we could just make this: model_redshift.add_effect(Redshift())

However I'm thinking your approach might be cleaner.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with Jeremy, we shouldn't pass redshifts and t0s twice to both classes, even implicitly. We might see bad bugs with these approach when parameters are random number generators...

src/tdastro/base_models.py Outdated Show resolved Hide resolved
src/tdastro/effects/redshift.py Outdated Show resolved Hide resolved
super().__init__(**kwargs)
self.required_parameters = ["redshift", "t0"]
self.add_parameter("redshift", redshift, required=True, **kwargs)
self.add_parameter("t0", t0, required=True, **kwargs)
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe that redshift and t0 are physical model's parameters, not effect's parameters

src/tdastro/effects/redshift.py Show resolved Hide resolved
emitted_times_rel_to_t0 = observed_times_rel_to_t0 / (1 + self.redshift)
emitted_times = emitted_times_rel_to_t0 + self.t0
emitted_wavelengths = observed_wavelengths / (1 + self.redshift)
return (emitted_times, emitted_wavelengths)
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we'd like to have a uniform interface, I'd vote to all models provide the same evaluate(times, wavelengths), where times are not relative to t0. Since model knows its t0, it is not a big deal to use it to get relative times, while computational overhead is negligible (and potentially zero with JIT)

"""Get the values for a source with no effects and a redshifted source."""
model_no_effects = StepSource(brightness=brightness, t0=t0, t1=t1)
model_redshift = StepSource(brightness=brightness, t0=t0, t1=t1, redshift=redshift)
model_redshift.add_effect(Redshift(redshift=model_redshift, t0=model_redshift))
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with Jeremy, we shouldn't pass redshifts and t0s twice to both classes, even implicitly. We might see bad bugs with these approach when parameters are random number generators...

tests/tdastro/effects/test_redshift.py Show resolved Hide resolved
flux_density : `numpy.ndarray`
The redshifted results.
"""
return flux_density / (1 + self.redshift)
Copy link
Contributor

Choose a reason for hiding this comment

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

For this and my comments throughout: Olivia and I chatted about it today. If we make effects behave like other nodes in the graph, they will have consistent sampling behavior. In that case we will want to have the effect take in the PhysicalModel's parameters.

The pros of this approach is that 1) it keeps everything in the same graph formulation, 2) keeps the same sampling counter throughout the entire graph, 3) doesn't require a specialized interface for effects (the required_parameters list), and 4) allows for stateful effects.

The cons are: 1) as @hombit) notes there is a chance this could be misspecified during model creation, 2) it requires creating a unique effect for each physical model.

Let's go with the current approach for now. If this causes too much confusion, we can change back to stateless effects.

@OliviaLynn OliviaLynn merged commit a94c158 into main Jul 17, 2024
4 checks passed
@OliviaLynn OliviaLynn deleted the redshift branch August 9, 2024 03:00
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.

Add redshift effect
4 participants