add polygon_chord - #2969
Conversation
|
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. |
I already looked into using shapely for a way to support trigger / voltage patches in the past here: |
|
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,) |
There was a problem hiding this comment.
I think we should deduce these (if needed) from the vertices. You can write a helper for this.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
These should be quantities, as we discussed offline, the best would be to use telescope coordinate system lat/lon.
There was a problem hiding this comment.
Also, perhaps tuples of (x,y) or arrays of them would be more straightforward to define points (arrays of points)
|
|
||
| Returns | ||
| ------- | ||
| float |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
why not using
try:
...
except ZeroDivisionError:
# determinant is zero, the cord goes on the edge, handle this
There was a problem hiding this comment.
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
| SQRT2 = np.sqrt(2) | ||
|
|
||
|
|
||
| def polygon_chord(mu_x, mu_y, phi, ri_x, ri_y, vi_x, vi_y): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Yes, I see. Let me finish implementing it with multiple tiles.
|
|
||
| Returns | ||
| ------- | ||
| float |
There was a problem hiding this comment.
Every branch returns (length, x_int, y_int) and not only a float. This should be reflected in the docstring.
There was a problem hiding this comment.
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 : |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
It is indeed only a float. The function is not vectorized. All of them return only one number.
… set of projection angles.
|
|
@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 |
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. |
|
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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
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. |
Sure, this has to be done. I would consider adding it in the next PR, but if needed, I can add it now. |
|
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. |
|
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. |
|
@maxnoe please look at the functional requirements regarding C-CALP-0308 : https://gitlab.cta-observatory.org/cta-computing/dpps/calibrationpipeline/documentation/calibpipe-level-c-requirements/-/merge_requests/10#note_422640 |
|
I understand the overall feedback and will work on optimizing the code and incorporating it into the intensity fitter. |
Thanks @burmist-git
@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. |
|
Just in case, @maxnoe, this is the optimization I’m proposing : |
| 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)) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.




Polygonal description of the chord.
096_arbitrary_shaped_mirror_and_polygon_chord.pdf
Analytical solution :
Input:
Ray origin, ray direction, polygon edges
Compute the ray direction vector.
Compute the intersection parameters (t, s) for all polygon edges.
Keep only intersections satisfying:
0 ≤ t < 1 and s ≥ 0.
Compute the intersection coordinates.
If no intersections:
return 0
If one intersection:
return distance from the ray origin to the intersection
If two intersections:
return distance between the two intersections
Otherwise:
Sort the distances from the ray origin to all intersections.
Return the alternating sum of the sorted distances.
Approximation with Fourier transformation: