BELFEM 0.9.3
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//------------------------------------------------------------------------------
50 template < typename T >
51 void
52 ferrari ( const Vector< T > & A, Vector< T > & X )
53 {
54 BELFEM_ASSERT( A.length() == 5,
55 "coefficient vector needs to have a length of 5" );
56
57 // quartic degenerates to a cubic
58 if ( std::abs( A( 0 ) ) < BELFEM_EPSILON )
59 {
60 cardano( A( 1 ), A( 2 ), A( 3 ), A( 4 ), X );
61 return ;
62 }
63
64 // normalize to x^4 + a*x^3 + b*x^2 + c*x + d = 0
65 const T a = A( 1 ) / A( 0 );
66 const T b = A( 2 ) / A( 0 );
67 const T c = A( 3 ) / A( 0 );
68 const T d = A( 4 ) / A( 0 );
69
70 // depress with x = y - lambda : y^4 + p*y^2 + q*y + r = 0
71 const T lambda = 0.25 * a ;
72 const T p = b - 0.375 * a * a ;
73 const T q = c - 0.5 * a * b + 0.125 * a * a * a ;
74 const T r = d - 0.25 * a * c + 0.0625 * a * a * b
75 - ( 3.0 / 256.0 ) * a * a * a * a ;
76
77 // real roots in y, at most four
78 T Y[4];
79
80 uint tCount = 0 ;
81
82 // the biquadratic path handles q ~ 0. It also serves as the
83 // fallback when roundoff collapses the clamped resolvent root
84 // s^2 to zero on an ill-conditioned cubic. Then q/s would be Inf/NaN.
85 // In exact arithmetic, that occurs only
86 // when q is negligible anyway
87 bool biquadratic = std::abs( q ) < BELFEM_EPSILON ;
88
89 T z = 0.0 ;
90 T s = 0.0 ;
91
92 if ( ! biquadratic )
93 {
94 // resolvent cubic in z = s^2, temporarily using X as scratch
95 Vector< T > & Z = X ;
96 cardano( T( 1 ), 2.0 * p, p * p - 4.0 * r, -q * q, Z );
97
98 // cardano sorts ascending; the largest root is >= 0 in exact
99 // arithmetic (the cubic evaluates to -q^2 < 0 at z = 0) and is
100 // the numerically robust choice for s
101 z = std::max( Z( Z.length() - 1 ), T( 0 ) );
102 s = std::sqrt( z );
103
104 biquadratic = ( s < BELFEM_EPSILON );
105 }
106
107 if ( biquadratic )
108 {
109 // biquadratic: z^2 + P*z + R = 0 with z = y^2.
110 T D = p * p - 4.0 * r ;
111
112 if ( D > -BELFEM_EPSILON )
113 {
114 D = std::sqrt( std::max( D, T( 0 ) ) );
115
116 // stable quadratic: avoid cancellation in the smaller root.
117 // the sign must come from p, the linear coefficient of
118 // z^2 + p*z + r ( q is ~0 in this branch )
119 const T w = -0.5 * ( p + std::copysign( D, p ) );
120
121 // the two z-candidates ( w == 0 only if p = D = 0 )
122 const T z0 = w ;
123 const T z1 = std::abs( w ) > BELFEM_EPSILON ? r / w : w ;
124
125 for ( const T zk : { z0, z1 } )
126 {
127 if ( zk > BELFEM_EPSILON )
128 {
129 const T tS = std::sqrt( zk );
130 Y[ tCount++ ] = -tS ;
131 Y[ tCount++ ] = tS ;
132 }
133 else if ( zk > -BELFEM_EPSILON )
134 {
135 Y[ tCount++ ] = 0.0 ;
136 }
137 }
138 }
139 // D < 0 : no real roots
140 }
141 else
142 {
143 // constant terms of the two quadratic factors
144 const T t1 = 0.5 * ( p + z - q / s );
145 const T t2 = 0.5 * ( p + z + q / s );
146
147 // y^2 + s*y + t1 = 0
148 T D = s * s - 4.0 * t1 ;
149
150 if ( D > BELFEM_EPSILON )
151 {
152 D = std::sqrt( D );
153 Y[ tCount++ ] = 0.5 * ( -s - D );
154 Y[ tCount++ ] = 0.5 * ( -s + D );
155 }
156 else if ( D > -BELFEM_EPSILON )
157 {
158 // double root (clamped)
159 Y[ tCount++ ] = -0.5 * s ;
160 }
161
162 // y^2 - s*y + t2 = 0
163 D = s * s - 4.0 * t2 ;
164 if ( D > BELFEM_EPSILON )
165 {
166 D = std::sqrt( D );
167 Y[ tCount++ ] = 0.5 * ( s - D );
168 Y[ tCount++ ] = 0.5 * ( s + D );
169 }
170 else if ( D > -BELFEM_EPSILON )
171 {
172 Y[ tCount++ ] = 0.5 * s ;
173 }
174 }
175
176 // shift back ( x = y - lambda ), sort ascending (insertion sort,
177 // at most four entries), and drop duplicates from multiple roots
178 for ( uint i = 0 ; i < tCount ; ++i )
179 {
180 Y[ i ] -= lambda ;
181 }
182 for ( uint i = 1 ; i < tCount ; ++i )
183 {
184 const T v = Y[ i ];
185 uint j = i ;
186 while ( j > 0 && Y[ j - 1 ] > v )
187 {
188 Y[ j ] = Y[ j - 1 ];
189 --j ;
190 }
191 Y[ j ] = v ;
192 }
193
194 uint tNumRoots = 0 ;
195 for ( uint i = 0 ; i < tCount ; ++i )
196 {
197 if ( tNumRoots == 0 || std::abs( Y[ i ] - Y[ tNumRoots - 1 ] )
198 > BELFEM_EPSILON * std::max( T( 1 ), std::abs( Y[ i ] ) ) )
199 {
200 Y[ tNumRoots++ ] = Y[ i ];
201 }
202 }
203
204 X.set_size( tNumRoots );
205 for ( uint i = 0 ; i < tNumRoots ; ++i )
206 {
207 X( i ) = Y[ i ];
208 }
209 }
210//------------------------------------------------------------------------------
211}
212#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:215
size_t length() const
get the length of the vector
Definition cl_AR_Vector.hpp:235
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:52
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:65
constexpr real BELFEM_EPSILON
Definition typedefs.hpp:89
@ q
Definition cl_Material.hpp:176