Fix the normalization for muon analysis to account for different PixelShapes.#2845
Fix the normalization for muon analysis to account for different PixelShapes.#2845burmist-git wants to merge 21 commits into
Conversation
…otons (from muon) incident on the telescope mirror.
…factor in this way.
mexanick
left a comment
There was a problem hiding this comment.
@burmist-git thank you for this PR. I've posted a couple of quick comments above. In addition, please add the changelog statement and fix the issues identified by sonar.
| @vectorize( | ||
| [double(double, double, double, double)], cache=not CTAPIPE_DISABLE_NUMBA_CACHE | ||
| ) | ||
| def chord_length(radius, rho, phi0, phi): |
There was a problem hiding this comment.
What's the reason to break the order of mandatory arguments? Can't we get away with
def chord_length(radius, rho, phi, phi0):
?
There was a problem hiding this comment.
Not just the order, also the meaning.
Rho is now absolute instead of a fraction of the radius. Why? That seems like completely needless breaking change, just moving one division or multiplication around.
There was a problem hiding this comment.
Yes, the rho and phi0 were changed. And indeed, we can reduce some of the changes.
However, we are modifying this function for a good reason: to make it easier to read and to reuse the same function in the new method.
Old version arguments: (scalar) mirror radius, (scalar) impact parameter relative to the mirror, omitting phi0 and using only one vector phi.
New version arguments: (scalar) mirror radius, (scalar) impact parameter, (scalar) phi0, and one vector phi.
The latest implementation is more natural for three reasons:
- It includes all the required parameters in a single function.
- If Rmirror is zero, we need to check this before performing the division by Rmirror, since it enters the MINUIT minimizer. It is more natural to handle this inside the function itself, making it more self-contained. For example:
- if Rmirror (e.g. for a hole) is 0, the function simply provides the correct answer (zero).
The argument ordering is clearer: (scalar), (scalar), (scalar), (vector).
This function is used only in this module, so I don’t really understand your concerns.
| phi = phi - phi0 | ||
|
|
||
| phi_modulo = (phi + np.pi) % (2 * np.pi) - np.pi | ||
| if phi < 0: |
There was a problem hiding this comment.
use math.abs() or np.abs()
There was a problem hiding this comment.
If you are commenting about this line:
phi_modulo *= -1
then it is correct. Since modulo returns sign less value and we need to return the minus sign back.
There was a problem hiding this comment.
looking closer at your code, I don't understand why do you even need this. phi_modulo is used in
np.sin(phi_modulo) ** 2np.cos(phi_modulo)np.abs(phi_modulo)
The result of the operations above doesn't depend on the sign of phi_modulo.
There was a problem hiding this comment.
Ok i see. Let me run the tests.
There was a problem hiding this comment.
Isn't reflecting at the origin just wrong here? Shouldn't you add 2π instead?
There was a problem hiding this comment.
Consider e.g. a small negative value (you'd change -π/4 to +π/4 for example).
There was a problem hiding this comment.
It is not needed there at all, the line above returns phi_modulo correctly normalized within [-π, π].
| from ctapipe.image.muon.intensity_fitter import chord_length | ||
|
|
||
| length = chord_length(radius, rho, phi.to_value(u.rad)) | ||
| length = chord_length(radius, rho, phi0.to_value(u.rad), phi.to_value(u.rad)) |
There was a problem hiding this comment.
Looks like the order of parameters is wrong now (phi <-> phi0)
|
|
||
|
|
||
| def intersect_circle(mirror_radius, r, angle, hole_radius=0): | ||
| def intersect_circle(mirror_radius, r, phi0, angle, hole_radius=0): |
There was a problem hiding this comment.
phi0 is not used in the function, so there's no need (at least at the moment) to add it.
Also, when adding, considering adding it as a last-order keyword argument to preserve the legacy calls.
There was a problem hiding this comment.
This has been corrected. It was the reason this wrapper was written.
|
The code is ready for review. Sonar reports incorrect code coverage for functions using vectorization. |
|
@maxnoe Any feed back on this MR ? |
|
|
||
| phi_modulo = (phi + np.pi) % (2 * np.pi) - np.pi | ||
|
|
||
| rho_r = np.abs(rho) / radius |
There was a problem hiding this comment.
What is the motivation for this redefinition? It seems like an arbitrary choice, so why change it now?
Please revert.
There was a problem hiding this comment.
Also, calling abs internally on a quantity that you expect to be always positive might result in very hard debug issues as it just hides wrong input.
There was a problem hiding this comment.
@maxnoe concerning this : #2845 (comment)
here is the reason why :

So, in other words, we can minimize the symmetric function with respect to rho. And of course, the same logic is applied to phi.
There was a problem hiding this comment.
@maxnoe concerning this : #2845 (comment)
Here I put this as explanation :
#2845 (comment)
There was a problem hiding this comment.
Then I'd expect this transformation as part of the likelihood definition, not in the low-level function.
I think here it is misleading.
There was a problem hiding this comment.
What should we do if this value is negative? Raise an error?
If the value is negative, it still returns a result. This is non-physical and incorrect, so we cannot output it without handling it properly.
There was a problem hiding this comment.
Yes, raising a ValueError(f"r must be > 0, got {r}") seems appropriate here.
| @@ -317,7 +344,12 @@ def image_prediction_no_units( | |||
| # diameter. In any case, since in the end we do a data-MC comparison of the muon | |||
| # ring analysis outputs, it is not critical that this value is exact. | |||
There was a problem hiding this comment.
@moralejo Is this is ok to replace. In the last two sentences… :
# Actually, for the large rings (relative to pixel
# size) we are concerned with, a good enough approximation is the ratio between a
# circle's area and that of the square whose side is equal to the circle's
# diameter or flat-to-flat distance for hexagonal pixel.
maxnoe
left a comment
There was a problem hiding this comment.
Please better explain why the change of API of these functions is needed or an improvement (both adding phi0 and changing the definition of rho).
If we decide that the absolute is better, then the parameter should also be renamed, since rho is used in the current definition by the linked references.
I.e. rho should only be used for the relative quantity.
In general, please separate bug fixes like the modulo check from improvements like the pixel type scaling from API changes.
Adding phi0 and changing rho (to be renamed to impact_parameter) need to be done for at least three reasons:
Sure - we can rename it to impact_parameter.
Ok.
Ok. |
| radius: float or ndarray | ||
| radius of circle | ||
| rho: float or ndarray | ||
| impact_point: float or ndarray |
There was a problem hiding this comment.
point implies 2d, but we pass radial coordinates r, phi0. So I'd call it impact_distance (as also the docsting already uses)
There was a problem hiding this comment.
Dear @maxnoe, this is a friendly reminder about this MR — let’s merge it :-)
|
| pixels_on_circle = int(circumference / pixel_diameter) | ||
|
|
||
| ang = phi + linspace_two_pi(pixels_on_circle * oversampling) | ||
| ang = linspace_two_pi(pixels_on_circle * oversampling) - phi |
There was a problem hiding this comment.
Please explain this change. Why do you change the order of this array?
There was a problem hiding this comment.
These two changes are linked. The reason is that phi0 should be subtracted from phi. In this way, the fitted value of phi0 gives us the azimuth of the impact point, whereas before it was giving us the complementary angle of the impact point. This was leading to inconsistencies between the reconstructed and true xcore and ycore values. In addition to the coordinate frame transformation.
There was a problem hiding this comment.
But phi0 is subtracted from phi. Now you again subtract phi from the array of all angles.
There was a problem hiding this comment.
And: if you are changing the coordinate system of the muon fit, don't you think this is a major change that needs to be mentioned both in the motivation for the pull request and in the changelog?
Please, explain what you are doing so that people can actually review and understand.
There was a problem hiding this comment.
Please note that phi0 is set to 0. As we agree do not change the current fitter.
There was a problem hiding this comment.
Even more misleading.
Please... do not mix bug fixes with such changes.
I would propose to close this merge request.
Please re-open the many changes you do here in a way that they are separate, their impact can be properly explained, assessed and understood.
There was a problem hiding this comment.
Well reuse this one for Fix the normalization for muon analysis to account for different PixelShapes.
I will rename the MR.
| ang = np.arctan2(dy, dx) | ||
| # Add muon rotation angle | ||
| ang += phi_rad | ||
| ang -= phi_rad |
There was a problem hiding this comment.




issue : #2844 (comment)
Parent issue : #2815
The goal is to improve the existing intensity fitter and prepare it for adding another method to measure the impact point and optical efficiency.
This pull request contains two main tasks: