File indexing completed on 2026-10-26 09:34:36
0001
0002
0003
0004
0005
0006
0007 #pragma once
0008
0009 #include <cmath>
0010
0011 #include "corecel/Constants.hh"
0012 #include "corecel/Types.hh"
0013 #include "corecel/cont/Array.hh"
0014 #include "corecel/math/Algorithms.hh"
0015 #include "corecel/math/NumericLimits.hh"
0016 #include "corecel/math/PolyEvaluator.hh"
0017 #include "corecel/math/SoftEqual.hh"
0018
0019 namespace celeritas
0020 {
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045
0046
0047
0048
0049
0050 class FerrariSolver
0051 {
0052 public:
0053
0054
0055 using Real4 = Array<real_type, 4>;
0056 using Real5 = Array<real_type, 5>;
0057 using result_type = Real4;
0058
0059
0060 public:
0061
0062 inline CELER_FUNCTION FerrariSolver(real_type tolerance);
0063
0064
0065 inline CELER_FUNCTION FerrariSolver() : FerrariSolver{default_tol_} {}
0066
0067
0068 inline CELER_FUNCTION result_type operator()(Real5 const& abcde) const;
0069
0070
0071 inline CELER_FUNCTION result_type operator()(Real4 const& abcd) const;
0072
0073 private:
0074
0075
0076 using Real2 = Array<real_type, 2>;
0077 using Real3 = Array<real_type, 3>;
0078
0079
0080
0081
0082 static constexpr real_type default_tol_
0083 = (std::is_same_v<real_type, double> ? 1e-5 : 5e-2f);
0084
0085
0086 static constexpr real_type no_solution_
0087 = NumericLimits<real_type>::infinity();
0088
0089
0090
0091
0092 SoftZero<real_type> const soft_zero_;
0093
0094
0095
0096 inline CELER_FUNCTION int place_root(
0097 result_type& roots, real_type new_root, int free_index) const;
0098
0099
0100 inline CELER_FUNCTION result_type calc_biquadratic_roots(
0101 real_type qb, real_type p, real_type r) const;
0102
0103
0104 inline CELER_FUNCTION Real3 real_roots_normalized_cubic(
0105 real_type b, real_type c, real_type d) const;
0106
0107
0108 inline CELER_FUNCTION Real2 real_roots_normalized_quadratic(
0109 real_type b, real_type c) const;
0110 };
0111
0112
0113
0114
0115
0116
0117
0118
0119 CELER_FUNCTION FerrariSolver::FerrariSolver(real_type tolerance)
0120 : soft_zero_{tolerance}
0121 {
0122 }
0123
0124
0125
0126
0127
0128
0129
0130
0131
0132
0133 CELER_FUNCTION auto FerrariSolver::operator()(Real5 const& abcde) const
0134 -> result_type
0135 {
0136 using namespace celeritas::literals;
0137
0138 CELER_EXPECT(abcde[0] != 0);
0139 auto [a, b, c, d, e] = abcde;
0140
0141
0142 real_type ba = b / a, ca = c / a, da = d / a, ea = e / a;
0143
0144 constexpr real_type half{0.5};
0145 real_type qb = 0.25_r * ba;
0146
0147
0148 real_type p = PolyEvaluator{-half * ca, 0, 3}(qb);
0149 real_type q = PolyEvaluator{half * da, -ca, 0, 4}(qb);
0150 real_type r = PolyEvaluator{-ea, da, -ca, 0, 3}(qb);
0151
0152
0153 if (soft_zero_(q))
0154 {
0155 return calc_biquadratic_roots(qb, p, r);
0156 }
0157
0158
0159 Real3 z = FerrariSolver::real_roots_normalized_cubic(
0160 p, r, p * r - half * ipow<2>(q));
0161 real_type z0 = (z[1] == no_solution_) ? z[0] : max(z[0], max(z[1], z[2]));
0162
0163 real_type s2 = 2 * p + 2 * z0;
0164 if (s2 >= 0)
0165 {
0166 real_type s = std::sqrt(s2);
0167 real_type t;
0168 if (soft_zero_(s))
0169 {
0170 t = ipow<2>(z0) + r;
0171 }
0172 else
0173 {
0174 t = -q / s;
0175 }
0176 auto const [r0, r1] = real_roots_normalized_quadratic(s * half, z0 + t);
0177 auto const [r2, r3]
0178 = real_roots_normalized_quadratic(-s * half, z0 - t);
0179
0180 result_type roots(
0181 no_solution_, no_solution_, no_solution_, no_solution_);
0182 int idx = 0;
0183 idx = place_root(roots, r0 - qb, idx);
0184 idx = place_root(roots, r1 - qb, idx);
0185 idx = place_root(roots, r2 - qb, idx);
0186 place_root(roots, r3 - qb, idx);
0187
0188 return roots;
0189 }
0190 else
0191 {
0192 return result_type(
0193 no_solution_, no_solution_, no_solution_, no_solution_);
0194 }
0195 }
0196
0197
0198
0199
0200
0201
0202
0203 CELER_FUNCTION auto FerrariSolver::operator()(Real4 const& abcd) const
0204 -> result_type
0205 {
0206 auto [a, b, c, d] = abcd;
0207
0208 real_type ba = b / a, ca = c / a, da = d / a;
0209 Real3 cubic_roots = real_roots_normalized_cubic(ba, ca, da);
0210 auto [z0, z1, z2] = cubic_roots;
0211
0212 result_type roots(no_solution_, no_solution_, no_solution_, no_solution_);
0213
0214 int idx = 0;
0215 idx = place_root(roots, z0, idx);
0216 idx = place_root(roots, z1, idx);
0217 place_root(roots, z2, idx);
0218 return roots;
0219 }
0220
0221
0222
0223
0224
0225
0226
0227
0228
0229 CELER_FUNCTION int FerrariSolver::place_root(
0230 result_type& roots, real_type new_root, int free_index) const
0231 {
0232 if (!(new_root == no_solution_ || new_root <= 0))
0233 {
0234 roots[free_index] = new_root;
0235 free_index += 1;
0236 }
0237 return free_index;
0238 }
0239
0240
0241
0242
0243
0244
0245
0246
0247
0248 CELER_FUNCTION auto FerrariSolver::calc_biquadratic_roots(
0249 real_type qb, real_type p, real_type r) const -> result_type
0250 {
0251 auto ir = real_roots_normalized_quadratic(-p, -r);
0252 result_type roots(no_solution_, no_solution_, no_solution_, no_solution_);
0253 int idx = 0;
0254 if (ir[1] != no_solution_ && ir[1] > 0)
0255 {
0256 real_type sqrt_ir1 = std::sqrt(ir[1]);
0257 real_type from_pos1 = sqrt_ir1 - qb;
0258 idx = place_root(roots, from_pos1, idx);
0259 if (from_pos1 > 0)
0260 {
0261 idx = place_root(roots, -sqrt_ir1 - qb, idx);
0262 }
0263 }
0264 if (ir[0] != no_solution_ && ir[0] > 0)
0265 {
0266 real_type sqrt_ir0 = std::sqrt(ir[0]);
0267 real_type from_pos0 = sqrt_ir0 - qb;
0268 idx = place_root(roots, from_pos0, idx);
0269 if (from_pos0 > 0)
0270 {
0271 place_root(roots, -sqrt_ir0 - qb, idx);
0272 }
0273 }
0274 return roots;
0275 }
0276
0277
0278
0279
0280
0281
0282
0283
0284
0285
0286
0287
0288
0289
0290
0291 CELER_FUNCTION auto FerrariSolver::real_roots_normalized_cubic(
0292 real_type b, real_type c, real_type d) const -> Real3
0293 {
0294 using namespace celeritas::literals;
0295
0296 constexpr real_type half = 0.5_r;
0297 constexpr real_type third = 1_r / 3;
0298 real_type third_b = b * third;
0299
0300
0301 real_type q = ipow<2>(third_b) - third * c;
0302 real_type r = half * PolyEvaluator{d, -c, 0, 2}(third_b);
0303
0304 real_type q3 = ipow<3>(q);
0305 real_type r2 = ipow<2>(r);
0306
0307 real_type discrim = r2 - q3;
0308
0309 if (soft_zero_(q) && soft_zero_(r) && soft_zero_(discrim))
0310 {
0311 return Real3(-std::cbrt(d), no_solution_, no_solution_);
0312 }
0313 else if (discrim <= 0)
0314 {
0315 real_type theta = std::acos(r / std::sqrt(q3));
0316 real_type n2_root_q = -2_r * std::sqrt(q);
0317 real_type twth_pi = constants::pi * 2_r * third;
0318 real_type third_theta = theta * third;
0319
0320 real_type z0 = n2_root_q * std::cos(third_theta) - third_b;
0321 real_type z1 = n2_root_q * std::cos(third_theta + twth_pi) - third_b;
0322 real_type z2 = n2_root_q * std::cos(third_theta - twth_pi) - third_b;
0323
0324 return Real3(z0, z1, z2);
0325 }
0326 else
0327 {
0328 real_type nr_a = -signum(r)
0329 * std::cbrt(std::fabs(r) + std::sqrt(discrim));
0330 real_type nr_b = nr_a == 0 ? 0 : q / nr_a;
0331 real_type z0 = nr_a + nr_b - third_b;
0332 return Real3(z0, no_solution_, no_solution_);
0333 }
0334 }
0335
0336
0337
0338
0339
0340
0341
0342
0343
0344
0345
0346
0347
0348
0349 CELER_FUNCTION auto FerrariSolver::real_roots_normalized_quadratic(
0350 real_type hb, real_type c) const -> Real2
0351 {
0352 real_type qb2 = ipow<2>(hb);
0353 if (soft_zero_(qb2 - c))
0354 {
0355
0356 return Real2(-hb, no_solution_);
0357 }
0358 else if (qb2 > c)
0359 {
0360
0361 real_type ht = std::sqrt(qb2 - c);
0362 return Real2(-hb - ht, -hb + ht);
0363 }
0364 else
0365 {
0366 return Real2(no_solution_, no_solution_);
0367 }
0368 }
0369
0370
0371 }