This is a simple Line and Parabola fit (from HPS reconstruction by Robert Johnson)
This method is based on the conformal map transformation. It estimates the full bound track parameters, i.e. (loc0, loc1, phi, theta, q/p, t) at the bottom space point. The bottom space is assumed to be the first element in the range defined by the iterators. The magnetic field (which might be along any direction) is also necessary for the momentum estimation.
96 {
97
98 size_t num_sp = std::distance(spBegin, spEnd);
99 if (num_sp != 3) {
100 std::cout << "ERROR::less than 3 point provided" << std::endl;
101 return std::nullopt;
102 }
103
104
105 double b_field_in_tesla = bField.norm() / Acts::UnitConstants::T;
106 double b_field_min_in_tesla = bFieldMin / Acts::UnitConstants::T;
107
108 if (b_field_in_tesla < b_field_min_in_tesla) {
109
110
111 std::cout << "The magnetic field at the bottom space point: B = "
112 << b_field_in_tesla
113 << " T is smaller than |B|_min = " << b_field_min_in_tesla
114 << " T. Estimation is not performed." << std::endl;
115 return std::nullopt;
116 }
117
118
119 std::array<Acts::Vector3, 3> sp_global_positions = {
120 Acts::Vector3::Zero(), Acts::Vector3::Zero(), Acts::Vector3::Zero()};
121
122
123 for (size_t isp = 0; isp < 3; ++isp) {
124 spacepoint_iterator_t it = std::next(spBegin, isp);
125 if (*it == nullptr) {
126 std::cout << "Empty space point found. This should not happen."
127 << std::endl;
128 return std::nullopt;
129 }
130 const auto& sp = *it;
131 sp_global_positions[isp] = Acts::Vector3(sp->x(), sp->y(), sp->z());
132 }
133
134
135
136
137
138
139 Acts::Vector3 rel_vec = sp_global_positions[1] - sp_global_positions[0];
140 Acts::Vector3 new_z_axis = bField.normalized();
141 Acts::Vector3 new_y_axis = new_z_axis.cross(rel_vec).normalized();
142 Acts::Vector3 new_x_axis = new_y_axis.cross(new_z_axis);
143 Acts::RotationMatrix3 rotation;
144 rotation.col(0) = new_x_axis;
145 rotation.col(1) = new_y_axis;
146 rotation.col(2) = new_z_axis;
147
148 Acts::Translation3 trans(sp_global_positions[0]);
149
150 Acts::Transform3 transform(trans * rotation);
151
152
153 Acts::Vector3 local1 = transform.inverse() * sp_global_positions[1];
154 Acts::Vector3 local2 = transform.inverse() * sp_global_positions[2];
155
156
157 auto uv_transform = [](const Acts::Vector3& local) -> Acts::Vector2 {
158 Acts::Vector2 uv;
159 double denominator = local.x() * local.x() + local.y() * local.y();
160 uv.x() = local.x() / denominator;
161 uv.y() = local.y() / denominator;
162 return uv;
163 };
164
165 Acts::Vector2 uv1 = uv_transform(local1);
166 Acts::Vector2 uv2 = uv_transform(local2);
167
168
169
170 double a = (uv2.y() - uv1.y()) / (uv2.x() - uv1.x());
171 double b = uv2.y() - a * uv2.x();
172
173 double rho = -2.0 * b / std::hypot(1., a);
174
175
176 double rn = local2.x() * local2.x() + local2.y() * local2.y();
177
178 double inv_tan_theta =
179 local2.z() * std::sqrt(1. / rn) / (1. + rho * rho * rn);
180
181
182 Acts::Vector3 trans_direction(1., a, std::hypot(1, a) * inv_tan_theta);
183
184 [[maybe_unused]] Acts::Vector3 direction =
185 rotation * trans_direction.normalized();
186
187
188 Acts::BoundVector params = Acts::BoundVector::Zero();
189
190
191
192
193
194 Acts::Vector3 bottom_local_pos = Tp.inverse() * sp_global_positions[0];
195
196
197 params[Acts::eBoundLoc0] = bottom_local_pos.x();
198 params[Acts::eBoundLoc1] = bottom_local_pos.y();
199
200
201
202 double q_over_pt =
203 rho * (Acts::UnitConstants::m) / (0.3 * b_field_in_tesla);
204
205 params[Acts::eBoundQOverP] = q_over_pt / std::hypot(1., inv_tan_theta);
206
207
208
209 double p_in_ge_v = std::abs(1.0 / params[Acts::eBoundQOverP]);
210 double pz_in_ge_v = 1.0 / std::abs(q_over_pt) * inv_tan_theta;
211 double mass_in_ge_v = mass / Acts::UnitConstants::GeV;
212
213
214 double v = p_in_ge_v / std::hypot(p_in_ge_v, mass_in_ge_v);
215 double vz = pz_in_ge_v / std::hypot(p_in_ge_v, mass_in_ge_v);
216
217
218 double pathz = sp_global_positions[0].dot(bField) / bField.norm();
219
220
221 if (pathz != 0) {
222 params[Acts::eBoundTime] = pathz / vz;
223 } else {
224 params[Acts::eBoundTime] = sp_global_positions[0].norm() / v;
225 }
226
227 return params;
228 }