BELFEM 0.9.0
Berkeley Lab Finite Element Framework
Loading...
Searching...
No Matches
fn_ferrari.hpp
Go to the documentation of this file.
1/*
2 * BELFEM -- The Berkeley Lab Finite Element Framework
3 * Copyright (c) 2026, The Regents of the University of California,
4 * through Lawrence Berkeley National Laboratory (subject to receipt of any required
5 * approvals from the U.S. Dept. of Energy). All rights reserved.
6 *
7 * Developers: Christian Messe
8 *
9 * See the top-level LICENSE file for the complete license and disclaimer.
10 */
11
12#ifndef BELFEM_FN_FERRARI_HPP
13#define BELFEM_FN_FERRARI_HPP
14
15#include "typedefs.hpp"
16#include "assert.hpp"
17#include "cl_Vector.hpp"
18#include "fn_cardano.hpp"
19
20namespace belfem
21{
22//------------------------------------------------------------------------------
54 template < typename T >
55 void
56 ferrari ( const Vector< T > & A, Vector< T > & X )
57 {
58 BELFEM_ASSERT( A.length() == 5,
59 "coefficient vector needs to have a length of 5" );
60
61 // quartic degenerates to a cubic
62 if ( std::abs( A( 0 ) ) < BELFEM_EPSILON )
63 {
64 cardano( A( 1 ), A( 2 ), A( 3 ), A( 4 ), X );
65 return ;
66 }
67
68 // normalize to x^4 + a*x^3 + b*x^2 + c*x + d = 0
69 const T a = A( 1 ) / A( 0 );
70 const T b = A( 2 ) / A( 0 );
71 const T c = A( 3 ) / A( 0 );
72 const T d = A( 4 ) / A( 0 );
73
74 // depress with x = y - lambda : y^4 + p*y^2 + q*y + r = 0
75 const T lambda = 0.25 * a ;
76 const T p = b - 0.375 * a * a ;
77 const T q = c - 0.5 * a * b + 0.125 * a * a * a ;
78 const T r = d - 0.25 * a * c + 0.0625 * a * a * b
79 - ( 3.0 / 256.0 ) * a * a * a * a ;
80
81 // real roots in y, at most four
82 T Y[4];
83
84 uint tCount = 0 ;
85
86 // the biquadratic path handles q ~ 0. It also serves as the
87 // fallback when roundoff collapses the clamped resolvent root
88 // s^2 to zero on an ill-conditioned cubic (Grok audit 2026-07-23:
89 // q/s would go Inf/NaN) — in exact arithmetic that only happens
90 // when q is negligible anyway
91 bool biquadratic = std::abs( q ) < BELFEM_EPSILON ;
92
93 T z = 0.0 ;
94 T s = 0.0 ;
95
96 if ( ! biquadratic )
97 {
98 // resolvent cubic in z = s^2, temporarily using X as scratch
99 Vector< T > & Z = X ;
100 cardano( T( 1 ), 2.0 * p, p * p - 4.0 * r, -q * q, Z );
101
102 // cardano sorts ascending; the largest root is >= 0 in exact
103 // arithmetic (the cubic evaluates to -q^2 < 0 at z = 0) and is
104 // the numerically robust choice for s
105 z = std::max( Z( Z.length() - 1 ), T( 0 ) );
106 s = std::sqrt( z );
107
108 biquadratic = ( s < BELFEM_EPSILON );
109 }
110
111 if ( biquadratic )
112 {
113 // biquadratic: z^2 + P*z + R = 0 with z = y^2.
114 T D = p * p - 4.0 * r ;
115
116 if ( D > -BELFEM_EPSILON )
117 {
118 D = std::sqrt( std::max( D, T( 0 ) ) );
119
120 // stable quadratic: avoid cancellation in the smaller root.
121 // the sign must come from p, the linear coefficient of
122 // z^2 + p*z + r ( q is ~0 in this branch )
123 const T w = -0.5 * ( p + std::copysign( D, p ) );
124
125 // the two z-candidates ( w == 0 only if p = D = 0 )
126 const T z0 = w ;
127 const T z1 = std::abs( w ) > BELFEM_EPSILON ? r / w : w ;
128
129 for ( const T zk : { z0, z1 } )
130 {
131 if ( zk > BELFEM_EPSILON )
132 {
133 const T tS = std::sqrt( zk );
134 Y[ tCount++ ] = -tS ;
135 Y[ tCount++ ] = tS ;
136 }
137 else if ( zk > -BELFEM_EPSILON )
138 {
139 Y[ tCount++ ] = 0.0 ;
140 }
141 }
142 }
143 // D < 0 : no real roots
144 }
145 else
146 {
147 // constant terms of the two quadratic factors
148 const T t1 = 0.5 * ( p + z - q / s );
149 const T t2 = 0.5 * ( p + z + q / s );
150
151 // y^2 + s*y + t1 = 0
152 T D = s * s - 4.0 * t1 ;
153
154 if ( D > BELFEM_EPSILON )
155 {
156 D = std::sqrt( D );
157 Y[ tCount++ ] = 0.5 * ( -s - D );
158 Y[ tCount++ ] = 0.5 * ( -s + D );
159 }
160 else if ( D > -BELFEM_EPSILON )
161 {
162 // double root (clamped)
163 Y[ tCount++ ] = -0.5 * s ;
164 }
165
166 // y^2 - s*y + t2 = 0
167 D = s * s - 4.0 * t2 ;
168 if ( D > BELFEM_EPSILON )
169 {
170 D = std::sqrt( D );
171 Y[ tCount++ ] = 0.5 * ( s - D );
172 Y[ tCount++ ] = 0.5 * ( s + D );
173 }
174 else if ( D > -BELFEM_EPSILON )
175 {
176 Y[ tCount++ ] = 0.5 * s ;
177 }
178 }
179
180 // shift back ( x = y - lambda ), sort ascending (insertion sort,
181 // at most four entries), and drop duplicates from multiple roots
182 for ( uint i = 0 ; i < tCount ; ++i )
183 {
184 Y[ i ] -= lambda ;
185 }
186 for ( uint i = 1 ; i < tCount ; ++i )
187 {
188 const T v = Y[ i ];
189 uint j = i ;
190 while ( j > 0 && Y[ j - 1 ] > v )
191 {
192 Y[ j ] = Y[ j - 1 ];
193 --j ;
194 }
195 Y[ j ] = v ;
196 }
197
198 uint tNumRoots = 0 ;
199 for ( uint i = 0 ; i < tCount ; ++i )
200 {
201 if ( tNumRoots == 0 || std::abs( Y[ i ] - Y[ tNumRoots - 1 ] )
202 > BELFEM_EPSILON * std::max( T( 1 ), std::abs( Y[ i ] ) ) )
203 {
204 Y[ tNumRoots++ ] = Y[ i ];
205 }
206 }
207
208 X.set_size( tNumRoots );
209 for ( uint i = 0 ; i < tNumRoots ; ++i )
210 {
211 X( i ) = Y[ i ];
212 }
213 }
214//------------------------------------------------------------------------------
215}
216#endif //BELFEM_FN_FERRARI_HPP
#define BELFEM_ASSERT(aCheck,...)
Definition assert.hpp:244
Column vector.
Definition cl_BZ_Vector.hpp:41
void set_size(const size_t aNumRows)
change the size of the vector
Definition cl_AR_Vector.hpp:237
size_t length() const
get the length of the vector
Definition cl_AR_Vector.hpp:257
USER GUIDES:
Definition cl_Capacitor.cpp:16
void ferrari(const Vector< T > &A, Vector< T > &X)
solve a quartic equation A(0)*x^4 + A(1)*x^3 + A(2)*x^2 + A(3)*x + A(4) = 0 for its real roots using ...
Definition fn_ferrari.hpp:56
unsigned int uint
Definition typedefs.hpp:30
void cardano(const T a, const T b, const T c, const T d, Vector< T > &X)
solve a cubic equation a*x^3 + b*x^2 + c*x + d = 0 for its real roots (Cardano / trigonometric method...
Definition fn_cardano.hpp:66
constexpr real BELFEM_EPSILON
Definition typedefs.hpp:90
@ q
Definition cl_Material.hpp:176