-
Notifications
You must be signed in to change notification settings - Fork 0
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
Conversation
There was a problem hiding this 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.
Questions for tomorrow: |
Update July 10Changes:
Questions:
|
src/tdastro/effects/redshift.py
Outdated
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
super().__init__(**kwargs) | ||
self.required_parameters = ["redshift", "t0"] | ||
self.add_parameter("redshift", redshift, required=True, **kwargs) | ||
self.add_parameter("t0", t0, required=True, **kwargs) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/tdastro/effects/redshift.py
Outdated
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 |
There was a problem hiding this comment.
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()
.
src/tdastro/base_models.py
Outdated
@@ -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) |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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...
super().__init__(**kwargs) | ||
self.required_parameters = ["redshift", "t0"] | ||
self.add_parameter("redshift", redshift, required=True, **kwargs) | ||
self.add_parameter("t0", t0, required=True, **kwargs) |
There was a problem hiding this comment.
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
Outdated
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) |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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...
flux_density : `numpy.ndarray` | ||
The redshifted results. | ||
""" | ||
return flux_density / (1 + self.redshift) |
There was a problem hiding this comment.
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.
Change Description
Closes #4
Solution Description
Redshift conversions
observation_times
,observation_wavelengths
toemitted_times
,emitted_wavelengths
.emitted_times
,emitted_wavelengths
values to a given PhysicalModel to get theemitted_flux
.emitted_flux
toobserved_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.