Hi all,
I am implementing some algorithms for modal parameters extraction for the next release of StructuralVibration.jl
. One of the method implemented is the Least-Squares Complex Frequency method (LSCF). The code often produce the right results, but for a reason I don’t understand it sometimes fails.
Here is a MWE to reproduce the result:
using StructuralVibration
# Structure parameters of the beam
L = 1. # Length
b = 0.03 # Width
h = 0.01 # Thickness
S = b*h # Cross-section area
Iz = b*h^3/12 # Moment of inertia
# Material parameters
E = 2.1e11 # Young's modulus
ρ = 7850. # Density
ξ = 0.01 # Damping ratio
# Mesh
xexc = 0:0.05:L
xm = xexc[2]
# Mode calculation - Simply supported boundary conditions
beam = Beam(L, S, Iz, E, ρ)
fmax = 500.
ωn, kn = modefreq(beam, 2fmax)
ϕexc = modeshape(beam, kn, xexc)
ϕm = modeshape(beam, kn, xm)
# FRF calculation
freq = 1.:0.1:fmax
prob = ModalFRFProblem(ωn, ξ, freq, ϕm, ϕexc)
H = solve(prob; ismat = true).u
p_lscf = lscf(H, freq, 20)
Here is the function lscf
:
using LinearAlgebra, Polynomials
function lscf(frf, freq, order)
# FRF post-processing - Reshape FRF matrix
(nm, ne, nf) = size(frf)
FRF = reshape(frf, (nm*ne, nf))
# LSCF computation
ω = 2π*(freq .- freq[1]) # Reduced angular frequency
Δt = 1/(2(freq[end] - freq[1])) # Sampling interval
modelOrder = 0:order
nmodel = order + 1
# Reduced normal equation computation
M = similar(freq, nmodel, nmodel)
Rk = similar(M)
Sk = similar(M)
Tk = similar(M)
X0 = exp.(-1im*ω*modelOrder'*Δt)
Yk = similar(X0)
for Hk in eachrow(FRF)
# Build Yk
@. Yk = -Hk*X0
# Build Rk, Sk, Tk
Rk .= real(X0'X0)
Sk .= real(X0'Yk)
Tk .= real(Yk'Yk)
# Accumulate M
M .+= (Tk .- Sk'*(Rk\Sk))
end
# Solve the normal equation
A = -M[1:order, 1:order]
b = M[1:order, nmodel]
α = [A\b; 1.]
# Compute the poles
V = roots(Polynomial(α))
p = -log.(V)/Δt
# Poles that do not appear as pairs of complex conjugate numbers with a positive real part or that are purely real are suppressed. For complex conjugate poles, only the pole with a positive imaginary part is retained.
valid_poles = p[@. imag(p) < 0. && real(p) ≤ 0. && !isreal(p)]
poles = intersect(p, conj.(valid_poles))
# To avoid modifying the stability of the dynamic system in case of frequency offset, we set real(poles_new) = real(poles_old). This means that dn_new = dn_old * fn_old / fn_new.
if freq[1] != 0.
fn, ξn = poles2modal(poles)
poles .= modal2poles(fn .+ freq[1], @. ξn*fn/(fn + freq[1]))
end
sort!(poles, by = abs)
return poles
end
function modal2poles(fn, ξn)
ωn = 2π*fn
return @. -ξn*ωn + 1im*ωn*√(1 - ξn^2)
end
function poles2modal(poles)
ωn = abs.(poles)
fn = ωn/(2π)
ξn = @. -real(poles)/ωn
return fn, ξn
end
I suspect that my problem is related to the matrix inversions I have to do, but I am not sure why.
If you have any idea or suggestions to help me to make the code more robust numerically, I would really appreciate.
For checking the math behind, this paper is perfect.
Thank you very much