Skip to content

add polygon_chord - #2969

Draft
burmist-git wants to merge 24 commits into
mainfrom
chord_length
Draft

add polygon_chord#2969
burmist-git wants to merge 24 commits into
mainfrom
chord_length

Conversation

@burmist-git

@burmist-git burmist-git commented Mar 24, 2026

Copy link
Copy Markdown
Member

Polygonal description of the chord.

096_arbitrary_shaped_mirror_and_polygon_chord.pdf

Analytical solution :

Input:
Ray origin, ray direction, polygon edges

  1. Compute the ray direction vector.

  2. Compute the intersection parameters (t, s) for all polygon edges.

  3. Keep only intersections satisfying:
    0 ≤ t < 1 and s ≥ 0.

  4. Compute the intersection coordinates.

  5. If no intersections:
    return 0

  6. If one intersection:
    return distance from the ray origin to the intersection

  7. If two intersections:
    return distance between the two intersections

  8. Otherwise:
    Sort the distances from the ray origin to all intersections.
    Return the alternating sum of the sorted distances.

image

Approximation with Fourier transformation:

gr_reco_lst gr_reco_box gr_reco_hexagon

@burmist-git
burmist-git requested a review from maxnoe March 24, 2026 09:56
@kosack

kosack commented Mar 24, 2026

Copy link
Copy Markdown
Member

Maybe we should consider adding shapely as a dependency? It might simplify some of these computations without having to re-implement all of computational geometry.

@maxnoe

maxnoe commented Mar 26, 2026

Copy link
Copy Markdown
Member

Maybe we should consider adding shapely as a dependency?

I already looked into using shapely for a way to support trigger / voltage patches in the past here:
#855 (comment)

@burmist-git

Copy link
Copy Markdown
Member Author

This is possible, but the function itself is very small, so using an external library would be unnecessary. I wouldn’t make it more complex than it is.

X-coordinates of the starting points of the polygon edges.
ri_y : ndarray of shape (N,)
Y-coordinates of the starting points of the polygon edges.
vi_x : ndarray of shape (N,)

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 think we should deduce these (if needed) from the vertices. You can write a helper for this.

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.

Yes, I'd also prefer the vertices as input. But maybe the function as it is here is fine and that transformation is made once outside of this function.

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.

Yes agree with @maxnoe . This is a low-level function. Let's keep it as it is, since all the necessary computations should be handled by the higher-level function that calls it.


Parameters
----------
mu_x : float

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.

These should be quantities, as we discussed offline, the best would be to use telescope coordinate system lat/lon.

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.

Also, perhaps tuples of (x,y) or arrays of them would be more straightforward to define points (arrays of points)


Returns
-------
float

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.

Should be also a unit of length (or angular, equivalent in the telescope frame)

- Each polygon edge is defined as:
(x, y) = (ri_x, ri_y) + t * (vi_x, vi_y), with 0 <= t < 1
- The function computes intersections by solving a 2D linear system.
- A small epsilon_d (`1e-20`) is added to the denominator to avoid division by zero.

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 not using

try:
  ...
except ZeroDivisionError:
  # determinant is zero, the cord goes on the edge, handle this

@kosack kosack Apr 13, 2026

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.

Just remember that catching an exception is quite a bit slower than adding an epsilon, so if you need this to be fast, the epsilon method might be better. Also if you want to use numba to speed things up

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 doesn't vectorize

SQRT2 = np.sqrt(2)


def polygon_chord(mu_x, mu_y, phi, ri_x, ri_y, vi_x, vi_y):

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 function should be njit, as it will be called. If possible, it would also be good if it could be vectorized over multiple tiles.

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.

Yes, I see. Let me finish implementing it with multiple tiles.


Returns
-------
float

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Every branch returns (length, x_int, y_int) and not only a float. This should be reflected in the docstring.

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 indeed only a float. The function is not vectorized.

x_int = status[mask][:,0]*status[mask][:,4] + status[mask][:,1]
y_int = status[mask][:,2]*status[mask][:,4] + status[mask][:,3]

if x_int.shape[0] == 0 :

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I suggest to return the same type (an array) for every if loop branch. I don't say that the code is wrong, but returning the same type will make it cleaner.

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 indeed only a float. The function is not vectorized. All of them return only one number.

@burmist-git
burmist-git requested review from Voutsi and kosack August 5, 2026 16:23
@ctao-sonarqube

ctao-sonarqube Bot commented Aug 7, 2026

Copy link
Copy Markdown

Quality Gate failed Quality Gate failed

Failed conditions
1 New issue
74.1% 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

@burmist-git
burmist-git requested a review from maxnoe August 10, 2026 08:19
@burmist-git

Copy link
Copy Markdown
Member Author

@Voutsi @kosack @maxnoe The code, tests, and documentation are ready. All tests are green and have passed. There is currently an issue with the SonarQube check related to code complexity (there are several nested if conditions). I know how to fix it, but before making the change, I want to make sure that the current architecture is appropriate and will be accepted for merging. I will fix it as soon as I receive feedback from the reviewers. For the tests with the full simulation chain (not the unit tests), I have requested the simulation from SimPipe.https://gitlab.cta-observatory.org/groups/cta-computing/dpps/-/work_items?sort=created_date&state=opened&search=muons&first_page_size=20&show=eyJpaWQiOiIyOTUiLCJmdWxsX3BhdGgiOiJjdGEtY29tcHV0aW5nL2RwcHMvY2FsaWJyYXRpb25waXBlbGluZS9jYWxpYnBpcGUiLCJpZCI6MTg4OTJ9

@maxnoe

maxnoe commented Aug 10, 2026

Copy link
Copy Markdown
Member

The code, tests, and documentation are ready.

The polygon_chord function is a low-level function. I'd expect it to be (maybe optionally) used in the muon fitter.

At the moment, this function is not used anywhere. I don't really see a benefit in adding this function without it being possible to actually use it in the muon fit.

@maxnoe

maxnoe commented Aug 10, 2026

Copy link
Copy Markdown
Member

I also thought that we had concluded at the meeting in Geneva that we would need to handle individual mirror tiles, so what is the motivation for still adding this polygon approximation?

vi_y = []

for ver_i in vertices_list:
ver_f = np.roll(ver_i, 1, axis=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.

In my experience, np.roll is extremely expensive, especially when called in a loop here, as it makes full copies of its input data.

In general I am much worried about the (runtime) performance of the code here, as it contains a lot of raw python loops without numba compilation for functions that are meant to be called in a likelihood optimization.

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.

For the optimization, let’s leave it for the next iteration. The Numba optimization can be applied to cases with zero, one, or two intersection points. However, for more complex shapes, such as the one we want to have for a the LST mirror (not the single facet), this requires a sorting function that cannot be used with Numba.

np.squeeze(x_int),
np.squeeze(y_int),
)
return np.squeeze(np.sqrt((x_int - mu_x) ** 2 + (y_int - mu_y) ** 2))

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.

please do not duplicate computation of the same things in different branches.

compute first, then return, i.e.

chord_length = ...
if return intersections:
    return chord_length, ...
return chord_length

@burmist-git

Copy link
Copy Markdown
Member Author

I also thought that we had concluded at the meeting in Geneva that we would need to handle individual mirror tiles, so what is the motivation for still adding this polygon approximation?

The hexagonal shape of the individual tiles can also be used or any other shape. However, that was not really the conclusion we reached regarding the use of the tiles individually. We considered using the individual tiles as an option, and this is now possible.

@burmist-git

Copy link
Copy Markdown
Member Author

The code, tests, and documentation are ready.

The polygon_chord function is a low-level function. I'd expect it to be (maybe optionally) used in the muon fitter.

At the moment, this function is not used anywhere. I don't really see a benefit in adding this function without it being possible to actually use it in the muon fit.

Sure, this has to be done. I would consider adding it in the next PR, but if needed, I can add it now.

@maxnoe

maxnoe commented Aug 10, 2026

Copy link
Copy Markdown
Member

As I am very worried about the performance of the code being added here (I think it will be prohibitively slow as is), I'd like to see the performance of it when used as part of the fitter before merging.

@burmist-git

burmist-git commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

As I am very worried about the performance of the code being added here (I think it will be prohibitively slow as is), I'd like to see the performance of it when used as part of the fitter before merging.

These are the requirements - we need to fulfill them. As we agreed during the meeting, performance comes later as a requirement. So, this option should be available in ctapipe.

@maxnoe

maxnoe commented Aug 10, 2026

Copy link
Copy Markdown
Member

I disagree, if we see here that the performance is as bad as I expect it to be from looking at the code here, this will not be a question of CalibPipe performance requirements, but just that I won't merge this in ctapipe.

@burmist-git

burmist-git commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

I disagree, if we see here that the performance is as bad as I expect it to be from looking at the code here, this will not be a question of CalibPipe performance requirements, but just that I won't merge this in ctapipe.

Please add this comment here, under the Level C requirements for muons : https://gitlab.cta-observatory.org/cta-computing/dpps/calibrationpipeline/documentation/calibpipe-level-c-requirements/-/merge_requests/10

| C-CALP-0308 | Arbitrary description of the primary mirror shape. | CalibPipe shall account for the non-circularity of the reflectors when reconstructing the muon impact point. | T: Dedicated simulations of muon events with a fixed impact point shall be processed using CalibPipe. Qualification shall be conducted by comparing the reconstructed impact point against the ground truth, taking into account the corresponding uncertainties. | CTAO-B_DPPS-494, CTAO-B_DPPS-439, CTAO-B_DPPS-408 | UC-120.2.2, UC-120.2.2.1, UC-120.2.2.2, UC-120.2.2.3, UC-120.2.10, UC-120.2.10.1, UC-120.2.10.2, UC-120.2.10.3, UC-120.2.13, UC-120.2.14|

Now we are in a position to keep track of whether the requirements have been fulfilled or not, and why.

@burmist-git

Copy link
Copy Markdown
Member Author

@burmist-git

Copy link
Copy Markdown
Member Author

I understand the overall feedback and will work on optimizing the code and incorporating it into the intensity fitter.

@Voutsi

Voutsi commented Aug 13, 2026

Copy link
Copy Markdown

I understand the overall feedback and will work on optimizing the code and incorporating it into the intensity fitter.

Thanks @burmist-git

I disagree, if we see here that the performance is as bad as I expect it to be from looking at the code here

@maxnoe , will be the optimisation discussed above enough in order for this PR to be acceptable? I am speaking only performance-wise, of course all other threads should be as well resolved.

@burmist-git

Copy link
Copy Markdown
Member Author

Just in case, @maxnoe, this is the optimization I’m proposing :

#2969 (comment)

t = (c1 * vmu_y - c2 * vmu_x) / determinant
s = (vi_y * c1 - vi_x * c2) / determinant

status = np.column_stack((vi_x, ri_x, vi_y, ri_y, t, s))

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.

Why do you column_stack this (making a copy) just to then index into the individual comments making it basically unreadable what you are accessing?

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.

Yes, thank you. It can be removed. Initially, I did it to have everything in the same 2D array, but you’re right, it can be removed.

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.

5 participants