Skip to content

Commit e37335c

Browse files
Fix bounds calculations
1 parent 0ab6950 commit e37335c

File tree

5 files changed

+186
-39
lines changed

5 files changed

+186
-39
lines changed

examples/aperture_model/005_interpolate_on_curve.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,10 @@ def arc_matrix(length: float, angle: float, tilt: float) -> np.ndarray:
5858
length = 3.2
5959
radius = 0.6
6060

61-
bend_name = env.new('bend', xt.Bend, length=length, angle=angle, k0=0)
62-
anti_bend_name = env.new('anti_bend', xt.Bend, length=length, angle=-angle, k0=0)
63-
line = env.new_line(name='line', components=[bend_name, anti_bend_name])
61+
bend = env.new('bend', xt.Bend, length=length, angle=angle, k0=0)
62+
drift = env.new('drift', xt.Drift, length=length)
63+
anti_bend = env.new('anti_bend', xt.Bend, length=length, angle=-angle, k0=0)
64+
line = env.new_line(name='line', components=[bend, drift, anti_bend])
6465
sv = line.survey()
6566

6667
shape = Circle(radius=radius)
@@ -87,19 +88,34 @@ def arc_matrix(length: float, angle: float, tilt: float) -> np.ndarray:
8788
survey_index=1,
8889
transformation=transform_matrix(),
8990
),
91+
TypePosition(
92+
type_index=2,
93+
survey_reference_name=sv.name[2],
94+
survey_index=2,
95+
transformation=transform_matrix(),
96+
),
9097
],
9198
types=[
9299
ApertureType(curvature=angle / length, positions=profile_positions),
100+
ApertureType(curvature=0, positions=profile_positions),
93101
ApertureType(curvature=-angle / length, positions=profile_positions),
94102
],
95103
profiles=profiles,
96-
type_names=['type0', 'type1'],
104+
type_names=['type0', 'type1', 'type2'],
97105
profile_names=['circ0'],
98106
)
99107

100108
ap = Aperture(line=line, model=model, num_profile_points=256)
101109

102-
s_samples = np.linspace(0.0, 2 * length, 33, dtype=np.float32)
110+
bounds_table = ap.get_bounds_table()
111+
bounds_s = [0, length, length, 2 * length, 2 * length, 3 * length]
112+
xo.assert_allclose(bounds_table.s, bounds_s, atol=1e-6, rtol=1e-6)
113+
xo.assert_allclose(bounds_table.s_start, bounds_s, atol=1e-6, rtol=1e-6)
114+
xo.assert_allclose(bounds_table.s_end, bounds_s, atol=1e-6, rtol=1e-6)
115+
assert all(bounds_table.type_name == ['type0', 'type0', 'type1', 'type1', 'type2', 'type2'])
116+
assert all(bounds_table.profile_name == ['circ0'])
117+
118+
s_samples = np.linspace(0, 3 * length, 51, dtype=np.float32)
103119
sections, poses = ap.cross_sections_at_s(s_samples)
104120

105121
ax = plt.figure().add_subplot(projection="3d")

xtrack/aperture/aperture.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,7 @@ def get_bounds_table(self):
743743

744744
table = Table(
745745
data={
746+
'name': np.array([f'{pn}_in_{tn}' for pn, tn in zip(profile_names, type_names)], dtype=np.str_),
746747
'type_name': np.array(type_names, dtype=np.str_),
747748
'profile_name': np.array(profile_names, dtype=np.str_),
748749
's': np.array(s_positions, dtype=np.float32),
@@ -751,7 +752,7 @@ def get_bounds_table(self):
751752
'shape': np.array(shapes, dtype=object),
752753
'shape_param': np.array(shape_params, dtype=object),
753754
},
754-
index='type_name',
755+
index='name',
755756
)
756757
return table
757758

xtrack/aperture/headers/path3d.h

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,12 @@ float_type arc_segment_plane_intersect(const ArcSegment3D segment, const Point3D
287287
}
288288

289289

290-
inline float_type segment_plane_intersect(const Segment3D segment, const Point3D plane_point, const Point3D normal)
290+
inline float_type segment3d_plane_intersect(const Segment3D segment, const Point3D plane_point, const Point3D normal)
291+
/*
292+
Given a `segment` (line or arc) and a plane defined with a point and a normal, get a parameter `t` along
293+
the length of the `segment` at which the plane intersects the segment. If `t` is not in [0, 1] the plane
294+
does not intersect the segment.
295+
*/
291296
{
292297
switch (segment.type) {
293298
case SEGMENT3D_LINE:
@@ -298,11 +303,13 @@ inline float_type segment_plane_intersect(const Segment3D segment, const Point3D
298303
}
299304

300305

301-
inline float_type closest_t_on_segment(const Point3D p, const Point3D a, const Point3D b)
306+
inline float_type closest_t_on_line_segment(const Point3D p, const LineSegment3D segment)
302307
/*
303308
Closest point parameter t on segment [a,b] to point p.
304309
*/
305310
{
311+
const Point3D a = segment.start;
312+
const Point3D b = segment.end;
306313
const float_type eps = APER_PRECISION;
307314
const Point3D ab = point3d_sub(b, a);
308315
const Point3D ap = point3d_sub(p, a);
@@ -315,6 +322,99 @@ inline float_type closest_t_on_segment(const Point3D p, const Point3D a, const P
315322
}
316323

317324

325+
inline float_type closest_t_on_arc_segment(const Point3D p, const ArcSegment3D segment)
326+
/*
327+
Closest point parameter t on an ArcSegment3D to point p.
328+
329+
The returned value is the normalized arc parameter, such that:
330+
arc_segment_point_at(segment, t)
331+
is the closest point on the supporting arc.
332+
333+
Like closest_t_on_segment(), this returns the unconstrained parameter.
334+
Clamp to [0, 1] at the call site if you need the closest point strictly
335+
on the finite arc segment.
336+
*/
337+
{
338+
const float_type eps = APER_PRECISION;
339+
340+
if (segment.length < eps) return 0.f;
341+
342+
/*
343+
Transform p into the local frame of segment.start.
344+
345+
Since segment.start is a rigid transform, its inverse is:
346+
local = R^T * (p - T)
347+
*/
348+
const float_type px = p.x - segment.start.mat[0][3];
349+
const float_type py = p.y - segment.start.mat[1][3];
350+
const float_type pz = p.z - segment.start.mat[2][3];
351+
352+
const float_type qx =
353+
segment.start.mat[0][0] * px +
354+
segment.start.mat[1][0] * py +
355+
segment.start.mat[2][0] * pz;
356+
357+
const float_type qy =
358+
segment.start.mat[0][1] * px +
359+
segment.start.mat[1][1] * py +
360+
segment.start.mat[2][1] * pz;
361+
362+
const float_type qz =
363+
segment.start.mat[0][2] * px +
364+
segment.start.mat[1][2] * py +
365+
segment.start.mat[2][2] * pz;
366+
367+
/*
368+
Undo the arc roll. In this frame the arc lies in the x-z plane:
369+
x = (cos(theta) - 1) / k
370+
z = sin(theta) / k
371+
with theta = k * s.
372+
*/
373+
const float_type c_roll = cos(segment.roll);
374+
const float_type s_roll = sin(segment.roll);
375+
376+
const float_type x = c_roll * qx + s_roll * qy;
377+
const float_type z = qz;
378+
379+
const float_type k = segment.curvature;
380+
381+
/* Straight-line limit */
382+
if (fabs(k) < eps) {
383+
return z / segment.length;
384+
}
385+
386+
/*
387+
The arc is part of the circle:
388+
(x + 1/k)^2 + z^2 = (1/k)^2
389+
390+
If C = (-1/k, 0) is the circle center in the local x-z plane, then
391+
the closest point on the supporting circle is obtained by projecting
392+
(x, z) radially from C.
393+
394+
For a point exactly on the arc:
395+
x = (cos(theta) - 1) / k
396+
z = sin(theta) / k
397+
398+
so:
399+
atan2(k*z, 1 + k*x) = theta
400+
*/
401+
const float_type theta = atan2(k * z, 1.f + k * x);
402+
403+
return theta / (k * segment.length);
404+
}
405+
406+
407+
inline float_type closest_t_on_segment(const Point3D p, const Segment3D segment)
408+
{
409+
switch (segment.type) {
410+
case SEGMENT3D_LINE:
411+
return closest_t_on_line_segment(p, segment.line);
412+
case SEGMENT3D_ARC:
413+
return closest_t_on_arc_segment(p, segment.arc);
414+
}
415+
}
416+
417+
318418
inline Pose arc_matrix(const float_type length, const float_type angle, const float_type tilt)
319419
/*
320420
Get a transformation to the point at `length` along an arc of `angle`.

xtrack/aperture/headers/polygons.h

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ void cross_sections_at_s(
2222
void build_polygon_for_profile(float_type *const, const uint32_t, const Profile);
2323
uint32_t find_aperture_info_for_s(const ApertureBounds, const float_type s, const uint32_t lower_bound);
2424

25-
static inline float_type survey_s_for_aperture(const TypePosition, const ProfilePosition, const SurveyData, uint32_t*);
25+
static inline float_type survey_s_for_aperture(const TypePosition, const ProfilePosition, const float_type curvature, const SurveyData, uint32_t*);
2626
static inline void bounds_on_s_for_aperture(
2727
const TypePosition,
2828
const ProfilePosition,
29+
const float_type curvature,
2930
const SurveyData,
3031
const Point2D* const,
3132
const uint32_t num_poly_points,
@@ -97,6 +98,7 @@ void build_profile_polygons(
9798
const TypePosition type_pos = ApertureModel_getp1_type_positions(model, type_pos_idx);
9899
const uint32_t type_idx = TypePosition_get_type_index(type_pos);
99100
const ApertureType aper_type = ApertureModel_getp1_types(model, type_idx);
101+
const float_type curvature = ApertureType_get_curvature(aper_type);
100102

101103
/* Get the profile position, and the polygon */
102104
const ProfilePosition profile_pos = ApertureType_getp1_positions(aper_type, profile_pos_idx);
@@ -107,13 +109,13 @@ void build_profile_polygons(
107109

108110
/* Get the survey s where the aperture actually sits */
109111
uint32_t installed_survey_index;
110-
const float_type found_s = survey_s_for_aperture(type_pos, profile_pos, survey, &installed_survey_index);
112+
const float_type found_s = survey_s_for_aperture(type_pos, profile_pos, curvature, survey, &installed_survey_index);
111113
ApertureBounds_set_s_positions(aperture_bounds, idx, found_s);
112114

113115
/* Get the bounds in s that the aperture spans */
114116
float_type min_s, max_s;
115117
const Point2D* const profile_points = (Point2D*)poly;
116-
bounds_on_s_for_aperture(type_pos, profile_pos, survey, profile_points, num_points, installed_survey_index, &min_s, &max_s);
118+
bounds_on_s_for_aperture(type_pos, profile_pos, curvature, survey, profile_points, num_points, installed_survey_index, &min_s, &max_s);
117119
ApertureBounds_set_s_start(aperture_bounds, idx, min_s);
118120
ApertureBounds_set_s_end(aperture_bounds, idx, max_s);
119121
}
@@ -399,13 +401,13 @@ void cross_sections_at_s(
399401
We want to move to the type frame, so calculate the transformations
400402
*/
401403
const Pose plane_in_world = pose_matrix_from_survey(survey_at_s, i);
402-
// const Pose type_in_world = aperture_type_pose_in_world(type_pos, survey);
403-
Pose type_in_world;
404-
for (int i = 0; i < 4; i++) {
405-
for (int j = 0; j < 4; j++) {
406-
type_in_world.mat[i][j] = TypePosition_get_transformation(type_pos, i, j);
407-
}
408-
}
404+
const Pose type_in_world = aperture_type_pose_in_world(type_pos, survey);
405+
// Pose type_in_world;
406+
// for (int i = 0; i < 4; i++) {
407+
// for (int j = 0; j < 4; j++) {
408+
// type_in_world.mat[i][j] = TypePosition_get_transformation(type_pos, i, j);
409+
// }
410+
// }
409411
const Pose world_in_type = pose_inverse_rigid(type_in_world);
410412
const Pose plane_in_type = matrix_multiply(world_in_type, plane_in_world);
411413
const Pose type_in_plane = pose_inverse_rigid(plane_in_type);
@@ -571,6 +573,7 @@ uint32_t find_aperture_info_for_s(
571573
static inline float_type survey_s_for_aperture(
572574
const TypePosition type_pos,
573575
const ProfilePosition profile_pos,
576+
const float_type curvature,
574577
const SurveyData survey,
575578
uint32_t* found_survey_index
576579
)
@@ -588,9 +591,7 @@ static inline float_type survey_s_for_aperture(
588591

589592
// Transformation from plane (s = 0) -> world
590593
Pose plane_in_world;
591-
// TODO: Include curvature and correct frame calculation
592-
const Pose world_in_world = identity();
593-
aperture_profile_pose_frame(type_pos, profile_pos, 0, survey, world_in_world, &plane_in_world);
594+
aperture_profile_pose_frame(type_pos, profile_pos, curvature, survey, identity(), &plane_in_world);
594595

595596
/*
596597
For data from MAD-X etc. it's likely that it's the type's reference survey point where the profile
@@ -601,16 +602,16 @@ static inline float_type survey_s_for_aperture(
601602
ZigZagIterator it = zigzag_iterator_new(survey_idx, 0, num_survey_entries - 1);
602603
do
603604
{
604-
LineSegment3D segment = survey_segment(survey, it.index);
605+
Segment3D segment = survey_segment(survey, it.index);
605606
const Point3D plane_point = plane_initial_point(plane_in_world);
606607
const Point3D normal = plane_normal_vector(plane_in_world);
607-
const float_type t = line_segment_plane_intersect(segment, plane_point, normal);
608+
const float_type t = segment3d_plane_intersect(segment, plane_point, normal);
608609
const float_type type_s = SurveyData_get_s(survey, it.index);
609610

610611
if (-eps <= t && t <= 1 + eps)
611612
{
612613
/* Current survey segment is intersected by the installed profile plane: compute the position. */
613-
const float_type dist = t * segment_get_length(segment);
614+
const float_type dist = t * segment3d_get_length(segment);
614615
found_s = type_s + dist;
615616
*found_survey_index = it.index;
616617
break;
@@ -624,6 +625,7 @@ static inline float_type survey_s_for_aperture(
624625
static inline void bounds_on_s_for_aperture(
625626
const TypePosition type_pos,
626627
const ProfilePosition profile_pos,
628+
const float_type curvature,
627629
const SurveyData survey,
628630
const Point2D* const profile_points,
629631
const uint32_t num_poly_points,
@@ -642,18 +644,14 @@ static inline void bounds_on_s_for_aperture(
642644
segment (as opposed to being clamped to the edge points) we have found the right segment.
643645
This is a fair assumption as the diameter of a profile << radius of curvature of the survey,
644646
but if that is not the case, the bounds will not be correct.
645-
646-
TODO: Handling of arcs, for now the algorithm deals with straight segments only.
647647
*/
648648
{
649649
const float_type eps = APER_PRECISION;
650650
const uint32_t num_survey_entries = SurveyData_len_s(survey);
651651

652652
// Transformation profile local -> world frame
653653
Pose profile_in_world;
654-
// TODO: Include curvature and correct frame calculation
655-
const Pose world_in_world = identity();
656-
aperture_profile_pose_frame(type_pos, profile_pos, 0, survey, world_in_world, &profile_in_world);
654+
aperture_profile_pose_frame(type_pos, profile_pos, curvature, survey, identity(), &profile_in_world);
657655

658656
float_type out_min = INFINITY;
659657
float_type out_max = -INFINITY;
@@ -675,17 +673,13 @@ static inline void bounds_on_s_for_aperture(
675673
ZigZagIterator it = zigzag_iterator_new(installed_survey_index, 0, num_survey_entries - 1);
676674
do
677675
{
678-
const LineSegment3D seg = survey_segment(survey, it.index);
679-
680-
const Point3D a = seg.start;
681-
const Point3D b = seg.end;
682-
683-
const float_type t = closest_t_on_segment(pt_in_world, a, b);
676+
const Segment3D seg = survey_segment(survey, it.index);
677+
const float_type t = closest_t_on_segment(pt_in_world, seg);
684678

685679
if (-eps < t && t < 1 + eps) {
686680
/* Candidate s on this segment */
687681
const float_type seg_s_start = SurveyData_get_s(survey, it.index);
688-
const float_type seg_len = segment_get_length(seg);
682+
const float_type seg_len = segment3d_get_length(seg);
689683
closest_s = seg_s_start + t * seg_len;
690684
break;
691685
}

xtrack/aperture/headers/survey_tools.h

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,52 @@ inline Point3D survey_point(SurveyData survey, uint32_t idx) {
4545
}
4646

4747

48-
inline LineSegment3D survey_segment(SurveyData survey, uint32_t idx) {
49-
Point3D entry = survey_point(survey, idx);
50-
Point3D exit = survey_point(survey, idx + 1);
48+
inline LineSegment3D survey_line_segment(SurveyData survey, uint32_t idx) {
49+
const Point3D entry = survey_point(survey, idx);
50+
const Point3D exit = survey_point(survey, idx + 1);
5151
return (LineSegment3D) {
5252
.start = entry,
5353
.end = exit
5454
};
5555
}
5656

5757

58+
inline ArcSegment3D survey_arc_segment(SurveyData survey, uint32_t idx) {
59+
const Pose start = pose_matrix_from_survey(survey, idx);
60+
const float_type length = SurveyData_get_length(survey, idx);
61+
const float_type angle = SurveyData_get_angle(survey, idx);
62+
const float_type roll = SurveyData_get_tilt(survey, idx);
63+
64+
return (ArcSegment3D) {
65+
.start = start,
66+
.length = length,
67+
.curvature = angle / length,
68+
.roll = roll
69+
};
70+
}
71+
72+
73+
inline Segment3D survey_segment(SurveyData survey, uint32_t idx) {
74+
const float_type angle = SurveyData_get_angle(survey, idx);
75+
const float_type length = SurveyData_get_length(survey, idx);
76+
77+
if (fabs(angle) < APER_PRECISION || fabs(length) < APER_PRECISION)
78+
{
79+
return (Segment3D) {
80+
.type = SEGMENT3D_LINE,
81+
.line = survey_line_segment(survey, idx)
82+
};
83+
}
84+
else
85+
{
86+
return (Segment3D) {
87+
.type = SEGMENT3D_ARC,
88+
.arc = survey_arc_segment(survey, idx)
89+
};
90+
}
91+
}
92+
93+
5894
SurveyEntry_s interpolate_survey_table_entry(
5995
const SurveyData survey,
6096
const float_type s_target,

0 commit comments

Comments
 (0)