From ad1cc714a3a08f696386eaa3173b990b24868db7 Mon Sep 17 00:00:00 2001 From: Fredrik Bagge Carlson Date: Tue, 20 Jan 2026 08:17:16 +0100 Subject: [PATCH 1/3] add `isunstable` --- lib/ControlSystemsBase/Project.toml | 2 +- lib/ControlSystemsBase/src/types/Lti.jl | 26 +++++++++++++++++++- lib/ControlSystemsBase/test/test_analysis.jl | 12 ++++++++- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/lib/ControlSystemsBase/Project.toml b/lib/ControlSystemsBase/Project.toml index e525a91ac..b1958f68d 100644 --- a/lib/ControlSystemsBase/Project.toml +++ b/lib/ControlSystemsBase/Project.toml @@ -39,7 +39,7 @@ Hungarian = "0.7.0" ImplicitDifferentiation = "0.7.2" LinearAlgebra = "<0.0.1, 1" MacroTools = "0.5" -Makie = "0.24" +Makie = "0.22, 0.23, 0.24" MatrixEquations = "1, 2.1" MatrixPencils = "1.8.3" Polynomials = "3.0, 4.0" diff --git a/lib/ControlSystemsBase/src/types/Lti.jl b/lib/ControlSystemsBase/src/types/Lti.jl index 546418104..adfbc8d63 100644 --- a/lib/ControlSystemsBase/src/types/Lti.jl +++ b/lib/ControlSystemsBase/src/types/Lti.jl @@ -107,10 +107,34 @@ common_timeevol(systems::LTISystem...) = common_timeevol(timeevol(sys) for sys i """ isstable(sys) -Returns `true` if `sys` is stable, else returns `false`.""" +Returns `true` if `sys` is stable, else returns `false`. + +Marginally stable systems are considered unstable by this function, see [`isunstable`](@ref) for a function that returns true only for exponentially unstable systems, that is, `!isunstable(sys)` implies that `sys` is either stable or marginally stable. +""" isstable(sys::LTISystem{Continuous}) = all(real.(poles(sys)) .< 0) isstable(sys::LTISystem{<:Discrete}) = all(abs.(poles(sys)) .< 1) + +""" + isunstable(sys) + +Returns `true` if `sys` is exponentially unstable, else returns `false`. +Marginally stable systems (systems with a single integrator) are considered stable by this function, see [`isstable`](@ref) for a function that returns true only for exponentially stable systems. +""" +function isunstable(sys::LTISystem) + inte, p, z, tolp, tolz = integrator_excess_with_tol(sys) + if inte > 1 + return true + end + # Go through all poles on the imaginary axis and check if they are duplicated + for pi in p + abs(real(pi)) > sqrt(sqrt(eps(abs(pi)))) && continue # The pole is too far away to be on the imaginary axis + # check if there are multiple poles with this imaginary part + count_eigval_multiplicity(p, complex(0.0, imag(pi)))[1] > 1 && return true + end + return iscontinuous(sys) ? any(real.(p) .> tolp) : any(abs.(p) .> tolp) +end + # Fallback since LTISystem not AbstractArray Base.size(sys::LTISystem, i::Integer) = size(sys)[i] diff --git a/lib/ControlSystemsBase/test/test_analysis.jl b/lib/ControlSystemsBase/test/test_analysis.jl index ee9e5c473..e8f466fa4 100644 --- a/lib/ControlSystemsBase/test/test_analysis.jl +++ b/lib/ControlSystemsBase/test/test_analysis.jl @@ -428,4 +428,14 @@ zss = tzeros(G) ptf = poles(Gtf) ztf = tzeros(Gtf) pzpk = poles(zpk(G)) -zzpk = tzeros(zpk(G)) \ No newline at end of file +zzpk = tzeros(zpk(G)) + + + +## Test isunstable +using ControlSystemsBase: isunstable +@test !isunstable(tf(1, [1,1])) +@test isunstable(tf(1, [1,-1])) +@test isunstable(tf(1, [1,0,0])) # Double integrator +@test isunstable(zpk([1], [im, im, -im, -im], 1)) # Repeated pole on imaginary axis not in origin +@test !isunstable(zpk([1], [im+1e-8im, im, -im-1e-8im, -im], 1)) # Almost repeated pole on imaginary axis not in origin \ No newline at end of file From 863ccd85af7daca5697f336046bda0ba4edd7bc7 Mon Sep 17 00:00:00 2001 From: Fredrik Bagge Carlson Date: Tue, 20 Jan 2026 08:18:35 +0100 Subject: [PATCH 2/3] bump version --- lib/ControlSystemsBase/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ControlSystemsBase/Project.toml b/lib/ControlSystemsBase/Project.toml index b1958f68d..05161e44c 100644 --- a/lib/ControlSystemsBase/Project.toml +++ b/lib/ControlSystemsBase/Project.toml @@ -2,7 +2,7 @@ name = "ControlSystemsBase" uuid = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e" authors = ["Dept. Automatic Control, Lund University"] repo = "https://github.com/JuliaControl/ControlSystems.jl.git" -version = "1.20.1" +version = "1.20.2" [deps] ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" From 0d406568bcb3c8751ad3fbf866d01fe69f9a29b3 Mon Sep 17 00:00:00 2001 From: Fredrik Bagge Carlson Date: Tue, 20 Jan 2026 08:19:56 +0100 Subject: [PATCH 3/3] update docstring --- lib/ControlSystemsBase/src/types/Lti.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ControlSystemsBase/src/types/Lti.jl b/lib/ControlSystemsBase/src/types/Lti.jl index adfbc8d63..719cddb74 100644 --- a/lib/ControlSystemsBase/src/types/Lti.jl +++ b/lib/ControlSystemsBase/src/types/Lti.jl @@ -119,7 +119,7 @@ isstable(sys::LTISystem{<:Discrete}) = all(abs.(poles(sys)) .< 1) isunstable(sys) Returns `true` if `sys` is exponentially unstable, else returns `false`. -Marginally stable systems (systems with a single integrator) are considered stable by this function, see [`isstable`](@ref) for a function that returns true only for exponentially stable systems. +Marginally stable systems (systems with a simple poles on the imaginary axis) are considered stable by this function, see [`isstable`](@ref) for a function that returns true only for exponentially stable systems. """ function isunstable(sys::LTISystem) inte, p, z, tolp, tolz = integrator_excess_with_tol(sys)