Skip to content

Commit 080207b

Browse files
authored
Merge pull request #906 from JuliaControl/ss_inv
handle proper quotient of proper statespace systems
2 parents 27c1b00 + 313d2b4 commit 080207b

File tree

3 files changed

+63
-7
lines changed

3 files changed

+63
-7
lines changed

lib/ControlSystemsBase/src/discrete.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ ZoH sampling is exact for linear systems with piece-wise constant inputs (step i
2121
2222
FoH sampling is exact for linear systems with piece-wise linear inputs (ramp invariant), this is a good choice for simulation of systems with smooth continuous inputs.
2323
24-
To approximate the behavior of a continuous-time system well in the frequency domain, the `:tustin` (trapezoidal / bilinear) method may be most appropriate. In this case, the pre-warping argument can be used to ensure that the frequency response of the discrete-time system matches the continuous-time system at a given frequency. The tustin transformation alters the meaning of the state components, while ZoH and FoH preserve the meaning of the state components. The Tustin method is commonly used to discretize a continuous-tiem controller.
24+
To approximate the behavior of a continuous-time system well in the frequency domain, the `:tustin` (trapezoidal / bilinear) method may be most appropriate. In this case, the pre-warping argument can be used to ensure that the frequency response of the discrete-time system matches the continuous-time system at a given frequency. The tustin transformation alters the meaning of the state components, while ZoH and FoH preserve the meaning of the state components. The Tustin method is commonly used to discretize a continuous-time controller.
2525
2626
The forward-Euler method generally requires the sample time to be very small
2727
relative to the time constants of the system, and its use is generally discouraged.
@@ -99,8 +99,8 @@ function d2c(sys::AbstractStateSpace{<:Discrete}, method::Symbol=:zoh; w_prewarp
9999
ny, nu = size(sys)
100100
nx = nstates(sys)
101101
if method === :zoh
102-
M = log([A B;
103-
zeros(nu, nx) I])./sys.Ts
102+
M = log(Matrix([A B;
103+
zeros(nu, nx) I]))./sys.Ts
104104
Ac = M[1:nx, 1:nx]
105105
Bc = M[1:nx, nx+1:nx+nu]
106106
if eltype(A) <: Real

lib/ControlSystemsBase/src/types/StateSpace.jl

+51-2
Original file line numberDiff line numberDiff line change
@@ -378,15 +378,64 @@ end
378378

379379

380380
## DIVISION ##
381-
/(sys1::AbstractStateSpace, sys2::AbstractStateSpace) = sys1*inv(sys2)
381+
382+
383+
"""
384+
/(sys1::AbstractStateSpace{TE}, sys2::AbstractStateSpace{TE}; atol::Real = 0, atol1::Real = atol, atol2::Real = atol, rtol::Real = max(size(sys1.A, 1), size(sys2.A, 1)) * eps(real(float(one(numeric_type(sys1))))) * iszero(min(atol1, atol2)))
385+
386+
Compute `sys1 / sys2 = sys1 * inv(sys2)` in a way that tries to handle situations in which the inverse `sys2` is non-proper, but the resulting system `sys1 / sys2` is proper.
387+
388+
See `ControlSystemsBase.MatrixPencils.isregular` for keyword arguments `atol`, `atol1`, `atol2`, and `rtol`.
389+
"""
390+
function Base.:(/)(sys1::AbstractStateSpace{TE}, sys2::AbstractStateSpace{TE};
391+
atol::Real = 0, atol1::Real = atol, atol2::Real = atol,
392+
rtol::Real = max(size(sys1.A,1),size(sys2.A,1))*eps(real(float(one(numeric_type(sys1)))))*iszero(min(atol1,atol2))) where {TE<:ControlSystemsBase.TimeEvolution}
393+
T1 = float(numeric_type(sys1))
394+
T2 = float(numeric_type(sys2))
395+
T = promote_type(T1,T2)
396+
timeevol = common_timeevol(sys1, sys2)
397+
ny2, nu2 = sys2.ny, sys2.nu
398+
nu2 == ny2 || error("The system sys2 must be square")
399+
ny1, nu1 = sys1.ny, sys1.nu
400+
nu1 == nu2 || error("The systems sys1 and sys2 must have the same number of inputs")
401+
nx1 = sys1.nx
402+
nx2 = sys2.nx
403+
if nx2 > 0
404+
A, B, C, D = ssdata([sys2; sys1])
405+
Ai = [A B; C[1:ny2,:] D[1:ny2,:]]
406+
Ei = [I zeros(T,nx1+nx2,ny2); zeros(T,ny2,nx1+nx2+ny2)] |> Matrix # TODO: rm call to Matrix when type piracy in https://github.com/JuliaLinearAlgebra/LinearMaps.jl/issues/219 is fixed
407+
MatrixPencils.isregular(Ai, Ei; atol1, atol2, rtol) ||
408+
error("The system sys2 is not invertible")
409+
Ci = [C[ny2+1:ny1+ny2,:] D[ny2+1:ny1+ny2,:]]
410+
Bi = [zeros(T,nx1+nx2,nu1); -I] |> Matrix # TODO: rm call to Matrix when type piracy in https://github.com/JuliaLinearAlgebra/LinearMaps.jl/issues/219 is fixed
411+
Di = zeros(T,ny1,nu1)
412+
Ai, Ei, Bi, Ci, Di = MatrixPencils.lsminreal(Ai, Ei, Bi, Ci, Di; fast = true, atol1 = 0, atol2, rtol, contr = true, obs = true, noseig = true)
413+
if Ei != I
414+
luE = lu!(Ei, check=false)
415+
issuccess(luE) || throw(ArgumentError("The system sys2 is not invertible"))
416+
Ai = luE\Ai
417+
Bi = luE\Bi
418+
end
419+
else
420+
D2 = T.(sys2.D)
421+
LUD = lu(D2)
422+
(norm(D2,Inf) <= atol1 || rcond(LUD.U) <= 10*nu1*eps(real(float(one(T))))) &&
423+
error("The system sys2 is not invertible")
424+
Ai, Bi, Ci, Di = ssdata(sys1)
425+
rdiv!(Bi,LUD); rdiv!(Di,LUD)
426+
end
427+
428+
return StateSpace{TE, T}(Ai, Bi, Ci, Di, timeevol)
429+
end
382430

383431
function /(n::Number, sys::ST) where ST <: AbstractStateSpace
384432
# Ensure s.D is invertible
385433
A, B, C, D = ssdata(sys)
434+
size(D, 1) == size(D, 2) || error("The inverted system must have the same number of inputs and outputs")
386435
Dinv = try
387436
inv(D)
388437
catch
389-
error("D isn't invertible")
438+
error("D isn't invertible. If you are trying to form a quotient between two systems `N(s) / D(s)` where the quotient is proper but the inverse of `D(s)` isn't, consider calling `N / D` instead of `N * inv(D)")
390439
end
391440
return basetype(ST)(A - B*Dinv*C, B*Dinv, -n*Dinv*C, n*Dinv, sys.timeevol)
392441
end

lib/ControlSystemsBase/test/test_statespace.jl

+9-2
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,18 @@
159159

160160
# Division
161161
@test 1/C_222_d == SS([-6 -3; 2 -11],[1 0; 0 2],[-1 0; -0 -1],[1 -0; 0 1])
162-
@test C_221/C_222_d == SS([-5 -3 -1 0; 2 -9 -0 -2; 0 0 -6 -3;
163-
0 0 2 -11],[1 0; 0 2; 1 0; 0 2],[1 0 0 0],[0 0])
162+
@test hinfnorm((C_221/C_222_d) - SS([-5 -3 -1 0; 2 -9 -0 -2; 0 0 -6 -3;
163+
0 0 2 -11],[1 0; 0 2; 1 0; 0 2],[1 0 0 0],[0 0]))[1] < 1e-10
164164
@test 1/D_222_d == SS([-0.8 -0.8; -0.8 -1.93],[1 0; 0 2],[-1 0; -0 -1],
165165
[1 -0; 0 1],0.005)
166166

167+
# Division when denominator inverse is non-proper but quotient is proper
168+
G1 = tf([1], [1, 1])
169+
G2 = tf([1], [1, 1, 1])
170+
G1s = ss(G1)
171+
G2s = ss(G2)
172+
@test tf(G2s / G1s) G2 / G1
173+
167174
fsys = ss(1,1,1,0)/3 # Int becomes FLoat after division
168175
@test fsys.B[]*fsys.C[] == 1/3
169176

0 commit comments

Comments
 (0)