@@ -56,17 +56,33 @@ DETRAY_HOST_DEVICE inline bool expand_bracket(const scalar_t a,
5656 scalar_t f_u{f (upper)};
5757 std::size_t n_tries{0u };
5858
59- // If there is no sign change in interval, we don't know if there is a root
60- while (!math::signbit (f_l * f_u)) {
61- // No interval could be found to bracket the root
62- // Might be correct, if there is not root
63- if ((n_tries == 1000u ) || !std::isfinite (f_l) || !std::isfinite (f_u)) {
59+ // / Check if the bracket has become invalid
60+ const auto check_bracket = [a, b, &bracket](std::size_t n, scalar_t fl,
61+ scalar_t fu, scalar_t l,
62+ scalar_t u) {
63+ if ((n == 1000u ) || !std::isfinite (fl) || !std::isfinite (fu) ||
64+ !std::isfinite (l) || !std::isfinite (u)) {
6465#ifndef NDEBUG
65- std::cout << " WARNING: Could not bracket a root" << std::endl;
66+ std::cout << " WARNING: Could not bracket a root (a=" << l
67+ << " , b=" << u << " , f(a)=" << fl << " , f(b)=" << fu
68+ << " , root might not exist). Running Newton-Raphson "
69+ " without bisection."
70+ << std::endl;
6671#endif
72+ // Reset value
6773 bracket = {a, b};
6874 return false ;
6975 }
76+ return true ;
77+ };
78+
79+ // If there is no sign change in interval, we don't know if there is a root
80+ while (!math::signbit (f_l * f_u)) {
81+ // No interval could be found to bracket the root
82+ // Might be correct, if there is no root
83+ if (!check_bracket (n_tries, f_l, f_u, lower, upper)) {
84+ return false ;
85+ }
7086 scalar_t d{k * (upper - lower)};
7187 // Make interval larger in the direction where the function is smaller
7288 if (math::fabs (f_l) < math::fabs (f_u)) {
@@ -79,8 +95,86 @@ DETRAY_HOST_DEVICE inline bool expand_bracket(const scalar_t a,
7995 ++n_tries;
8096 }
8197
82- bracket = {lower, upper};
83- return true ;
98+ if (!check_bracket (n_tries, f_l, f_u, lower, upper)) {
99+ return false ;
100+ } else {
101+ bracket = {lower, upper};
102+ return true ;
103+ }
104+ }
105+
106+ // / @brief Find a root using the Newton-Raphson algorithm
107+ // /
108+ // / @param s initial guess for the root
109+ // / @param evaluate_func evaluate the function and its derivative
110+ // / @param max_path don't consider root if it is too far away
111+ // /
112+ // / @see Numerical Recepies pp. 445
113+ // /
114+ // / @return pathlength to root and the last step size
115+ template <typename scalar_t , typename function_t >
116+ DETRAY_HOST_DEVICE inline std::pair<scalar_t , scalar_t > newton_raphson (
117+ function_t &evaluate_func, scalar_t s,
118+ const scalar_t convergence_tolerance = 1 .f * unit<scalar_t >::um,
119+ const std::size_t max_n_tries = 1000u ,
120+ const scalar_t max_path = 5 .f * unit<scalar_t >::m) {
121+
122+ constexpr scalar_t inv{detail::invalid_value<scalar_t >()};
123+ constexpr scalar_t epsilon{std::numeric_limits<scalar_t >::epsilon ()};
124+
125+ if (math::fabs (s) >= max_path) {
126+ #ifndef NDEBUG
127+ std::cout << " WARNING: Initial path estimate outside search area: s="
128+ << s << std::endl;
129+ #endif
130+ }
131+ if (math::fabs (s) >= inv) {
132+ throw std::invalid_argument (" ERROR: Initial path estimate invalid" );
133+ }
134+
135+ // Run the iteration on s
136+ scalar_t s_prev{0 .f };
137+ std::size_t n_tries{0u };
138+ auto [f_s, df_s] = evaluate_func (s);
139+
140+ while (math::fabs (s - s_prev) > convergence_tolerance) {
141+
142+ // Root already found?
143+ if (math::fabs (f_s) < convergence_tolerance) {
144+ return std::make_pair (s, epsilon);
145+ }
146+
147+ // No intersection can be found if dividing by zero
148+ if (math::fabs (df_s) == 0 .f ) {
149+ #ifndef NDEBUG
150+ std::cout << " WARNING: Newton step encountered invalid derivative "
151+ " - skipping"
152+ << std::endl;
153+ #endif
154+ return std::make_pair (inv, inv);
155+ }
156+
157+ // Newton step
158+ s_prev = s;
159+ s -= f_s / df_s;
160+
161+ // Update function evaluation
162+ std::tie (f_s, df_s) = evaluate_func (s);
163+
164+ ++n_tries;
165+
166+ // No intersection found within max number of trials
167+ if (n_tries >= max_n_tries) {
168+ #ifndef NDEBUG
169+ std::cout << " WARNING: Helix intersector did not "
170+ " converge after "
171+ << n_tries << " steps - skipping" << std::endl;
172+ #endif
173+ return std::make_pair (inv, inv);
174+ }
175+ }
176+ // Final pathlengt to root and latest step size
177+ return std::make_pair (s, math::fabs (s - s_prev));
84178}
85179
86180// / @brief Find a root using the Newton-Raphson and Bisection algorithms
@@ -111,29 +205,55 @@ DETRAY_HOST_DEVICE inline std::pair<scalar_t, scalar_t> newton_raphson_safe(
111205 };
112206
113207 // Initial bracket
114- scalar_t a{math::fabs (s) == 0 .f ? -0 .1f : 0 .9f * s};
115- scalar_t b{math::fabs (s) == 0 .f ? 0 .1f : 1 .1f * s};
208+ if (math::fabs (s) >= max_path) {
209+ #ifndef NDEBUG
210+ std::cout << " WARNING: Initial path estimate outside search area: s="
211+ << s << std::endl;
212+ #endif
213+ }
214+ if (math::fabs (s) >= inv) {
215+ throw std::invalid_argument (" ERROR: Initial path estimate invalid" );
216+ }
217+ scalar_t a{math::fabs (s) == 0 .f ? -0 .2f : 0 .8f * s};
218+ scalar_t b{math::fabs (s) == 0 .f ? 0 .2f : 1 .2f * s};
116219 std::array<scalar_t , 2 > br{};
117220 bool is_bracketed = expand_bracket (a, b, f, br);
118221
119222 // Update initial guess on the root after bracketing
120223 s = is_bracketed ? 0 .5f * (br[1 ] + br[0 ]) : s;
121224
122- if (is_bracketed) {
225+ if (!is_bracketed) {
226+ #ifndef NDEBUG
227+ std::cout << " WARNING: Bracketing failed for initial path estimate: s="
228+ << s << std::endl;
229+ #endif
230+ } else {
123231 // Check bracket
124232 [[maybe_unused]] auto [f_a, df_a] = evaluate_func (br[0 ]);
125233 [[maybe_unused]] auto [f_b, df_b] = evaluate_func (br[1 ]);
126234
127- assert (math::signbit (f_a * f_b) && " Incorrect bracket around root" );
235+ // Bracket is not guaranteed to contain a root
236+ if (!math::signbit (f_a * f_b)) {
237+ throw std::runtime_error (
238+ " Incorrect bracket around root: No sign change!" );
239+ }
240+
241+ // No bisection algorithm possible if one bracket boundary is inf
242+ // (is already checked in bracketing alg)
243+ if ((math::fabs (br[0 ]) >= inv) || (math::fabs (br[1 ]) >= inv)) {
244+ throw std::runtime_error (
245+ " Incorrect bracket around root: Boundary reached inf!" );
246+ }
128247
129248 // Root is not within the maximal pathlength
130- bool bracket_outside_tol{s > max_path &&
131- (( br[0 ] < - max_path && br[ 1 ] < -max_path) ||
132- (br[0 ] > max_path && br[ 1 ] > max_path)) };
249+ bool bracket_outside_tol{math::fabs (s) > max_path &&
250+ math::fabs ( br[0 ]) >= max_path &&
251+ math::fabs (br[1 ]) >= max_path};
133252 if (bracket_outside_tol) {
134253#ifndef NDEBUG
135- std::cout << " INFO: Root outside maximum search area - skipping"
136- << std::endl;
254+ std::cout << " INFO: Root outside maximum search area (s = " << s
255+ << " , a: " << br[0 ] << " , b: " << br[1 ] << " )"
256+ << " - skipping" << std::endl;
137257#endif
138258 return std::make_pair (inv, inv);
139259 }
@@ -201,7 +321,9 @@ DETRAY_HOST_DEVICE inline std::pair<scalar_t, scalar_t> newton_raphson_safe(
201321 } else {
202322 // No intersection can be found if dividing by zero
203323 if (!is_bracketed && math::fabs (df_s) == 0 .f ) {
204- std::cout << " WARNING: Encountered invalid derivative "
324+ std::cout << " WARNING: Newton step encountered invalid "
325+ " derivative at s="
326+ << s << " after " << n_tries << " steps - skipping"
205327 << std::endl;
206328
207329 return std::make_pair (inv, inv);
@@ -223,13 +345,14 @@ DETRAY_HOST_DEVICE inline std::pair<scalar_t, scalar_t> newton_raphson_safe(
223345 ((a < -max_path && b < -max_path) ||
224346 (a > max_path && b > max_path))) {
225347#ifndef NDEBUG
226- std::cout << " WARNING: Root finding left the search space"
227- << std::endl;
348+ std::cout << " WARNING: Root finding left the search space at (s = "
349+ << s << " , a: " << a << " , b: " << b << " ) after "
350+ << n_tries << " steps - skipping" << std::endl;
228351#endif
229352 return std::make_pair (inv, inv);
230353 }
231-
232354 ++n_tries;
355+
233356 // No intersection found within max number of trials
234357 if (n_tries >= max_n_tries) {
235358
@@ -241,17 +364,15 @@ DETRAY_HOST_DEVICE inline std::pair<scalar_t, scalar_t> newton_raphson_safe(
241364 std::to_string (s) + " in [" + std::to_string (a) + " , " +
242365 std::to_string (b) + " ]" );
243366 } else {
244- #ifndef NDEBUG
245367 std::cout << " WARNING: Helix intersector did not "
246368 " converge after "
247- << n_tries << " steps unbracketed search"
369+ << n_tries << " steps unbracketed search - skipping "
248370 << std::endl;
249- #endif
250371 }
251372 return std::make_pair (inv, inv);
252373 }
253374 }
254- // Final pathlengt to root and latest step size
375+ // Final pathlengt to root and latest step size
255376 return std::make_pair (s, math::fabs (s - s_prev));
256377}
257378
0 commit comments