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 the first composite type: Sum #157

Merged
merged 12 commits into from
Apr 16, 2024
Merged

Add the first composite type: Sum #157

merged 12 commits into from
Apr 16, 2024

Conversation

rmjarvis
Copy link
Owner

@rmjarvis rmjarvis commented Jul 7, 2023

This is finally the first composite type that I've been working up to. Sum is probably a little simpler than Convolve, so I wanted to start with that. But the other two I have in mind are Convolve and Transform, similar to the corresponding GalSIm object types.

A Sum PSF is a model made from adding together multiple other PSF types. Each component can have its own model and interpolation. The parameters are stored in a list, so each component has its own numpy.array of parameters in the star.fit objects, each of which can be whatever length is required.

The initialization defaults to hsm for the first component and zero for the others, but the user can override this as desired. I suspect for most use cases, the first component will describe most of the PSF patter, and the later components will fill in the bits that weren't well modeled earlier (e.g. the tree-ring residuals we've seen in DES), so I think this will be a reasonable default initialization. Certainly we don't want multiple components all using the hsm initialization, since the sum would then be N x the PSF, which wouldn't be a good initial guess for the components. I suspect that getting good initial guesses for various components will be a big part of making composite PSFs work well, so there may be more work needed eventually on this front.

Copy link
Collaborator

@theoschutt theoschutt left a comment

Choose a reason for hiding this comment

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

Some in-line clarification questions, but otherwise looks good to me.

piff/sumpsf.py Outdated
self.chisq_thresh = chisq_thresh
self.max_iter = max_iter
self.kwargs = {
# If components is a list, mark the number of components here for I/O purposed.
Copy link
Collaborator

Choose a reason for hiding this comment

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

typo: purposed -> purposes

piff/sumpsf.py Outdated
else:
self._num = None

# else components are not yet built. This will be called again when that's done.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this comment meant to be here or above the else statement?

default_init='zero'

# If any components are degenerate, mark the sum as degenerate.
self.degenerate_points = any([comp.degenerate_points for comp in self.components])
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do basis interpolation schemes still work when other components in the Sum have non-basis interpolation methods?
Edit: hmm, after looking at single_iteration, I think I can answer my own question that each component still fits separately, so there's no "interaction" between interpolations. But I'll leave it up in case I'm wrong...

Copy link
Owner Author

Choose a reason for hiding this comment

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

That's correct. The composition is of full PSF objects, so they don't interfere with each other.

The only thing this assignment does is to not do outlier rejection on the first iteration if any of the components use basis interpolation. I'm not sure how important that is, but it seemed sensible.

piff/sumpsf.py Outdated

# Draw the current models for each component
current_models = []
for k, comp in enumerate(self.components):
Copy link
Collaborator

Choose a reason for hiding this comment

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

technically don't need k and enumerate here

Comment on lines +187 to +185
if kk != k:
new_image -= current_models[kk][i].image
Copy link
Collaborator

Choose a reason for hiding this comment

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

To make sure I understand what happens on the second and later iterations through all the components: on the first iteration, the first model component (c1) isn't affected by later components (say, c2 and c3) because they're initialized to zero flux. But on later iterations, c1 will be fitting to the data with the best-fit c2 and c3 models from the previous iteration subtracted (and c2 will fit with c1 and c3 subtracted, and so on). Correct?

Copy link
Owner Author

Choose a reason for hiding this comment

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

That's right. We always subtract off the current best model of all other components, and fit the component being worked on to the residual.
I think this should be pretty stable when applying some tweak to a primary model that is pretty good at describing most of the pattern, but has a residual with a very different interpolation pattern. E.g. maybe we add on a PixelGrid with much larger extent, but no interpolation, so it just captures the average large-scale residuals around a standard basis-interpolated model over a smaller footprint. None of my unit tests tried this out, but that's what I mostly have in mind for how the SumPSF would get used in practice.

piff/sumpsf.py Outdated
Comment on lines 195 to 197
# The fit components in new_stars are what we want, but the data parts are
# corrupted by subtracting the other components, which we don't want.
# Just copy over the fits.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Something about this wording was very confusing. Possibly-too-wordy suggestion:

new_stars now has the updated fit components, but the data parts are corrupted by subtracting the other components during fitting. Update the stars by copying the undisturbed data from the original stars and the updated fits from new_stars.

Comment on lines +312 to +314
# Note: There is no useful test to make of the component parameters.
#print('comp0: ',test_star.fit.params[0])
#print('comp1: ',test_star.fit.params[1])
Copy link
Collaborator

Choose a reason for hiding this comment

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

What do you mean here?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Usually I had been testing the fitted parameters to some known true values. But for this test, both models are PixelGrid, so the parameters aren't anything I know a priori. The only thing to test is to use the models to draw images of the total PSF model and see if it matches the data acceptably.

print('image max diff = ',np.max(np.abs(test_image[b].array - test_star.image[b].array)))
np.testing.assert_allclose(test_image[b].array, test_star.image[b].array, atol=1.e-8)

# Repeat with both components being PixelGrid mdoels.
Copy link
Collaborator

Choose a reason for hiding this comment

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

typo: mdoels -> models

@rmjarvis
Copy link
Owner Author

@jmeyers314 @aaronroodman
Would either of you like to take a look at this before merging? I'll be doing Convolution next if you're rather wait for that as a more realistic composite model.

Copy link
Collaborator

@jmeyers314 jmeyers314 left a comment

Choose a reason for hiding this comment

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

Hey Mike. Code all looks good. I'm wondering if there's room for another practical test though - something like Kolmogorov-to-capture-bulk + PixelGrid-to-mop-up-details, maybe with very different spatial scales?

@rmjarvis
Copy link
Owner Author

I'm game. Thoughts on what to use for the truth? Maybe a GalSim atmospheric screen and then use GP for the interpolation of Kolmogorov for the primary component. Would that have a usefully interesting residual to mop up? Or would I need to include some optical component to give it something that wouldn't be well described by Kolmogorov?

@jmeyers314
Copy link
Collaborator

I'm not sure. If you keep the simulated exposure time short enough I'm sure an atmsopheric screen true psf would have interesting residuals to mop up.

You might be able to get away with something simpler though, e.g., truth = Moffat with quickly spatially varying beta maybe?
Definitely bonus points though if you get this to work with a GP.

@rmjarvis rmjarvis force-pushed the sum branch 2 times, most recently from 92cf9c5 to 03b0d10 Compare March 29, 2024 21:25
@rmjarvis
Copy link
Owner Author

Hi Josh, I finally got back to this after 8 months of letting your very good suggestion sit unaddressed.

I added a test using Atmospheric + Optical screens for the PSF. The model is a Kolmogorov PSF with GP interpolation + a PixelGrid with just a Mean interpolation.

If I don't include the PixelGrid part, here are the images of the residuals (made by the new unit test when running from main):
test_atmpsf_stars2
You can see that there is a roughly consistent pattern to the residuals. Mostly because the optical screen aberrations were constant over the CCD, so they produces a pretty coherent pattern.

When I include the PixelGrid as a second component in the Sum, here are the residuals:
test_atmpsf_stars

This seems pretty good to me and shows a more realistic use case for how the Sum PSF might be used in practice.

@rmjarvis rmjarvis merged commit 5e5944c into main Apr 16, 2024
9 checks passed
@rmjarvis rmjarvis deleted the sum branch April 16, 2024 04:21
@rmjarvis rmjarvis added this to the Version 1.4 milestone Jun 28, 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.

3 participants