Skip to content

add impactpoint_intensity_fitter.py#2814

Draft
burmist-git wants to merge 38 commits into
mainfrom
impactpoint_intensity_fitter
Draft

add impactpoint_intensity_fitter.py#2814
burmist-git wants to merge 38 commits into
mainfrom
impactpoint_intensity_fitter

Conversation

@burmist-git
Copy link
Copy Markdown
Member

@burmist-git burmist-git commented Aug 20, 2025

An important error has been found and fixed (in the Chord calculation).
It shows an improvement in the resolution and provides more symmetric distributions.
With the current method, we achieve a 1.7 m resolution on the impact point (from simulation),
whereas ARTEMIS achieved 0.75 m.

We need to implement a new method to reconstruct the impact point.

I will separate the impact point reconstruction from the intensity fitter.
First provides the impact point with known precision.
Second provides the reconstructed and expected intensity and optical throughput.
The intensity fitter will use all available information about the muon to predict
expected intensity.

image image

@ctao-dpps-sonarqube

This comment has been minimized.

@ctao-dpps-sonarqube

This comment has been minimized.

@ctao-dpps-sonarqube

This comment has been minimized.

@ctao-dpps-sonarqube
Copy link
Copy Markdown

Failed

  • 23.08% Duplicated Lines (%) on New Code (is greater than 3.00%)
  • 73.60% Coverage on New Code (is less than 80.00%)

Analysis Details

2 Issues

  • Bug 0 Bugs
  • Vulnerability 0 Vulnerabilities
  • Code Smell 2 Code Smells

Coverage and Duplications

  • Coverage 73.60% Coverage (94.30% Estimated after merge)
  • Duplications 23.08% Duplicated Code (0.80% Estimated after merge)

Project ID: cta-observatory_ctapipe_AY52EYhuvuGcMFidNyUs

View in SonarQube

@maxnoe
Copy link
Copy Markdown
Member

maxnoe commented Aug 21, 2025

If there is an issue in the intensity fitter, why add a second implementation instead of fixing it?

@burmist-git
Copy link
Copy Markdown
Member Author

I fixed the problems and bugs I found so far. It does help, but the resolution is still not enough.

The point is very simple :

I think the key point here is that we perform the fit with fewer parameters, and therefore achieve better resolution.

I will separate the impact point reconstruction from the intensity fitter.
First provides the impact point with known precision.
Second provides the reconstructed and expected intensity and optical throughput.

First, we need to achieve a better fit. If we succeed, we will also learn how to improve the existing one.

@maxnoe
Copy link
Copy Markdown
Member

maxnoe commented Aug 21, 2025

Note that the current intensity fit keeps the ring center fixed (opposed to just using it as an initial guess).

Maybe with the chord bug fixed, we can lift this restriction and the fit will perform better if it is allowed to adjust x/y?

@burmist-git
Copy link
Copy Markdown
Member Author

burmist-git commented Aug 21, 2025

I still think adding more free parameters will make it worse.

Moreover, it often fails to converge to the global maximum of the log-likelihood.

Another major issue (in current fit) is the camera shadowing, which is not well implemented.

image

Copy link
Copy Markdown
Contributor

@mexanick mexanick left a comment

Choose a reason for hiding this comment

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

Overall, this PR needs significant improvements before it can be merged. At the moment it does not meet our project’s quality standards:
– There is extensive duplicated code instead of reuse, which will make maintenance harder.
– Documentation is largely missing, making it difficult to understand intent and usage.
– Tests are essentially absent (the current placeholder test is insufficient).
– Several PEP8/style conventions are not followed (e.g., single-letter/cryptic variable names, commented-out code, etc).

@@ -0,0 +1,558 @@
"""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think creation of a new module is justified here. The new fitter class returns the same container as the MuonIntensityFitter in the ctapipe.image.muon.intensity_fitter, therefore it should be also implemented there.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It’s not a new module in my opinion — we need to completely remove the old fitter. The new one has a clearer logic. It is easy to follow and in addition fully vectorized that is why this optimization can be removed.

]


def chord_length(radius, rho, phi0, phi):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This function is an almost exact copy-paste of the chord_length of ctapipe.image.muon.intensity_fitter. Please reuse the code instead of copying it, and do not strip vectorization and other performance optimizations. Besides, in case of the API update, the documentation shall be updated too. A new argument (phi0) was introduced, that breaks the order of arguments and lacks description.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It is far from being an exact copy. This function has two main modifications: first, it uses modulo
2pi, and therefore we remove the limits on the fitting parameter. Second, we explicitly include phi0 (defines the direction with respect to the mirror of the impact point) in the function instead of hiding it somewhere in the fitting procedure this makes it much easier to read.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If this is a general improvement, improve the function. Do not copy and leave a "suboptimal" slightly different version elsewhere.

I'm still not sure where this pull request is going and why a separate fitter is needed at all as opposed to freezing the efficiency and then re-fitting it with the impact frozen.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The current algorithm is inefficient, with many values stuck at the limits or default settings. An initial estimate produced 30% more fitted events, and the proposed method is straightforward and easy to follow. We also decouple the impact point fitting from the optical efficiency.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Then improve the current implementation, please.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I have already implemented a simple and easy-to-debug method, with decoupled impact point width and optical efficiency measurements.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I a parallel structure. If this new method doesn't have disadvantages, there is no point in this duplication. Just improve the existing one please

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You can also enable one or the other approach by adding configuration options to MuonIntensityFitter if you think it's valuable to keep both.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Do you mean ‘replace’? Yes, that can be done very quickly. Or do you mean improving the current likelihood? Because the main improvement, in my view, comes from fitting only the phi distribution with only 18 bins in angle.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You can also enable one or the other approach by adding configuration options to MuonIntensityFitter if you think it's valuable to keep both.

In principle, no — we do not need it.

return chord_length(radius, rho, 0, phi - phi0)


def gauss_pedestal(x, A, mu, sigma, pedestal):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this function used to describe the ring width? Please elaborate more the fitting approach.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Previously, the ring width was fitted with a 2D Gaussian, now we are doing the same in 1D. This makes it simpler and easier to read and interpret.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No, the ring width is described using a 1d normal distribution:

def gaussian_cdf(x, mu, sig):

Copy link
Copy Markdown
Member Author

@burmist-git burmist-git Sep 19, 2025

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not sure what your point is. The existing likelihood function transforms into polar coordinates as soon as possible and continues from there.

No 2d gaussian fit is performed as you wrote here:

Previously, the ring width was fitted with a 2D Gaussian,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The ring width could be fitted in 1D, whereas it is currently fitted effectively in 2D.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't understand your line of argument here. The ring width is a parameter in a likelihood which is described by a 1D probability distribution.

It's not 2d

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The likelihood fit itself is in 2D.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

And why do you think this is an issue? How else can you take the ring center location into account for the image prediction?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Just like in the 1D fit of the phi distribution.

)

# Predicted total number of Cherenkov photons falling on the mirror
pred_total_Cher_phot = (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why do you re-write this? There's a well-defined function image_prediction_no_units that provides you with this. Also, I believe the difference of normalization here is the reason why you have a different final number. As it is correctly noted in the documentation of image_prediction_no_units, since we are measuring the throughput relative to the simulation, the overall normalization doesn't matter as it cancels out.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This function has issues and errors:

-The expected measured number of signal events does not depend on the pixel FoV, but this function does. The new function fixes this issue.
-There is an analytical formula for the trivial case (rho=0 and no shadowing), which should be included in the tests. The current function gives the wrong result and new function fixes this issue.

)
fit.errordef = Minuit.LEAST_SQUARES
#
fit.errors["A"] = 10
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How these values are obtained and what units they represent?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This value defines the initial step of the fit. Usually, we set it much smaller and should be changed to an smaller number. Minuit works with scalars only, you cannot define values in units like meters, for example.

Comment thread src/ctapipe/image/muon/processor.py Outdated

self.intensity_fitter = MuonIntensityFitter(subarray=subarray, parent=self)
# self.intensity_fitter = MuonIntensityFitter(subarray=subarray, parent=self)
self.intensity_fitter = MuonImpactpointIntensityFitter(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you're leaving both options, it should be possible to select one of the fitters.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is clear form very fest comment: #2814 (comment)

And discussion and motivation of the issue.

@@ -0,0 +1,34 @@
import astropy.units as u
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please provide a proper testing of the code you've added.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Since I discovered new issues and problems in the code (the lat one today), we need to work through it in more detail. Yes, we definitely need to add more tests.


weights = (phi_y > 0).astype(int)

phi_x = ((np.roll(hist_phi[1], 1) + hist_phi[1]) / 2.0)[1:]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this a very complicated way to say 0.5 * (edges[:-1] + edges[1:])

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

thank you

@ctao-sonarqube
Copy link
Copy Markdown

Quality Gate failed Quality Gate failed

Failed conditions
13 New issues
64.6% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE SonarQube for IDE

impact_x=rho * np.cos(Quantity(fit2.values["phi0"], u.rad)),
impact_y=rho * np.sin(Quantity(fit2.values["phi0"], u.rad)),
chord_fit_ampl=fit2.values["amplitude"],
phi_bin_0=phi_y[0],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You can just put the array into the container

Copy link
Copy Markdown
Member Author

@burmist-git burmist-git Sep 25, 2025

Choose a reason for hiding this comment

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

Yes, I know. We decided to split this MR into parts, so I just pushed the code to switch the branches. Please ignore this draft MR, until it’s moved out of draft. Also can this MR can be completely deleted i will let you know. I just want to keep the history of changes for current development.

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