Skip to content

Commit dc1d477

Browse files
authored
Merge pull request #800 from JuliaControl/highorder
warn more against high-order transfer functions
2 parents 569a7d1 + b064857 commit dc1d477

File tree

3 files changed

+17
-0
lines changed

3 files changed

+17
-0
lines changed

docs/src/man/numerical.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,21 @@
33
## Numerical accuracy
44
Transfer functions, and indeed polynomials in general, are infamous for having poor numerical properties. Consider the simple polynomial $ax^n - 1$ which, due to rounding of the polynomial coefficients, is represented as $(a+\epsilon)x^n - 1$ where $\epsilon$ is on the order of `eps(a)`. The roots of this polynomial have a much larger $\epsilon$, due to the n:th root in the expression $\dfrac{1}{\sqrt[n]{(a + \epsilon)}}$. For this reason, it's ill-advised to use high-order transfer functions. Orders as low as 6 may already be considered high. When a transfer function is converted to a state-space representation using `ss(G)`, balancing is automatically performed in an attempt at making the numerical properties of the model better.
55

6+
This problem is illustrated below, where we first create a statespace system ``G`` and convert this to a transfer function ``G_1``. We then perturb a *single element* of the dynamics matrix ``A`` by adding the machine epsilon for `Float64` (`eps() = 2.22044e-16`), and convert this perturbed statespace system to a transfer function ``G_2``. The difference between the two transfer functions is enormous, the norm of the difference in their denominator coefficient vectors is on the order of ``10^{96}``.
7+
8+
```julia
9+
sys = ssrand(1,1,100);
10+
G1 = tf(sys);
11+
sys.A[1,1] += eps();
12+
G2 = tf(sys);
13+
norm(denvec(G1)[] - denvec(G2)[])
14+
6.270683106765845e96
15+
```
16+
If we plot the poles of the two systems, they are also very different
17+
```julia
18+
scatter(poles(G1)); scatter!(poles(G2))
19+
```
20+
![Noisy poles](https://user-images.githubusercontent.com/3797491/215962177-38447944-6cca-4070-95ea-7f3829efee2e.png))
621

722

823
## Frequency-response calculation

lib/ControlSystemsBase/src/types/SisoTfTypes/SisoRational.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ struct SisoRational{T} <: SisoTf{T}
99
# The numerator is zero, make the denominator 1
1010
den = one(den)
1111
end
12+
T <: AbstractFloat && length(den) > 20 && eps(T) >= eps(Float64) && @warn "High-order transfer functions are highly sensitive to numerical errors. The result may be inaccurate. Consider making use of statespace systems instead" maxlog=1
1213
new{T}(num, den)
1314
end
1415
end

lib/ControlSystemsBase/src/types/conversion.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ function convert(::Type{TransferFunction{TE,SisoRational{T}}}, sys::AbstractStat
252252
matrix = Matrix{SisoRational{T}}(undef, size(sys))
253253

254254
A, B, C, D = ssdata(sys)
255+
T <: AbstractFloat && size(A, 1) > 20 && eps(T) >= eps(Float64) && @warn "High-order transfer functions are highly sensitive to numerical errors. The result may be inaccurate." maxlog=1
255256

256257
# The following follows from the matrix inversion lemma:
257258
# det(X + uᵀv) = det(X)(1 + vᵀX⁻¹u), or

0 commit comments

Comments
 (0)