|
2 | 2 | ## Data Type Declarations ## |
3 | 3 | ##################################################################### |
4 | 4 | """ |
5 | | - ExtendedStateSpace{TE, T} <: AbstractStateSpace{TE} |
| 5 | + ExtendedStateSpace{TE, T, S, I} <: AbstractStateSpace{TE} |
6 | 6 |
|
7 | 7 | A type that represents the two-input, two-output system |
8 | 8 | ``` |
|
18 | 18 | - `w` denotes external inputs, such as disturbances or references |
19 | 19 | - `u` denotes control inputs |
20 | 20 |
|
21 | | -The call `lft(P, K)` forms the (lower) linear fractional transform |
| 21 | +The call `lft(P, K)` forms the (lower) linear fractional transform |
22 | 22 | ``` |
23 | 23 | z ┌─────┐ w |
24 | 24 | ◄──┤ │◄── |
@@ -49,52 +49,88 @@ and the following design functions expect `ExtendedStateSpace` as inputs |
49 | 49 | - [`LQGProblem`](@ref) (also accepts other types) |
50 | 50 |
|
51 | 51 | A video tutorial on how to use this type is available [here](https://youtu.be/huYRrn--AKc). |
| 52 | +
|
| 53 | +## Internal Representation |
| 54 | +Internally, this type stores an `AbstractStateSpace` along with index vectors that partition the inputs and outputs: |
| 55 | +- `sys`: The underlying state-space system |
| 56 | +- `w`: Indices for disturbance inputs (corresponds to B1) |
| 57 | +- `u`: Indices for control inputs (corresponds to B2) |
| 58 | +- `z`: Indices for performance outputs (corresponds to C1) |
| 59 | +- `y`: Indices for measured outputs (corresponds to C2) |
| 60 | +
|
| 61 | +The type parameter `I` allows index vectors to be `Vector{Int}`, `UnitRange{Int}`, or `Vector{Symbol}` (for NamedStateSpace). |
52 | 62 | """ |
53 | | -struct ExtendedStateSpace{TE,T} <: AbstractStateSpace{TE} |
54 | | - A::Matrix{T} |
55 | | - B1::Matrix{T} |
56 | | - B2::Matrix{T} |
57 | | - C1::Matrix{T} |
58 | | - C2::Matrix{T} |
59 | | - D11::Matrix{T} |
60 | | - D12::Matrix{T} |
61 | | - D21::Matrix{T} |
62 | | - D22::Matrix{T} |
63 | | - timeevol::TE |
64 | | - function ExtendedStateSpace{TE,T}( |
65 | | - A, |
66 | | - B1, |
67 | | - B2, |
68 | | - C1, |
69 | | - C2, |
70 | | - D11, |
71 | | - D12, |
72 | | - D21, |
73 | | - D22, |
74 | | - timeevol::TE, |
75 | | - ) where {TE,T} |
76 | | - nx = size(A, 1) |
77 | | - nw = size(B1, 2) |
78 | | - nu = size(B2, 2) |
79 | | - nz = size(C1, 1) |
80 | | - ny = size(C2, 1) |
81 | | - |
82 | | - size(A, 2) != nx && nx != 0 && error("A must be square") |
83 | | - size(B1, 1) == nx || error("B1 must have the same row size as A") |
84 | | - size(B2, 1) == nx || error("B2 must have the same row size as A") |
85 | | - size(C1, 2) == nx || error("C1 must have the same column size as A") |
86 | | - size(C2, 2) == nx || error("C2 must have the same column size as A") |
87 | | - size(D11, 2) == nw || error("D11 must have the same column size as B1") |
88 | | - size(D21, 2) == nw || error("D21 must have the same column size as B1") |
89 | | - size(D12, 2) == nu || error("D12 must have the same column size as B2") |
90 | | - size(D22, 2) == nu || error("D22 must have the same column size as B2") |
91 | | - size(D11, 1) == nz || error("D11 must have the same row size as C1") |
92 | | - size(D12, 1) == nz || error("D12 must have the same row size as C1") |
93 | | - size(D21, 1) == ny || error("D21 must have the same row size as C2") |
94 | | - size(D22, 1) == ny || error("D22 must have the same row size as C2") |
95 | | - |
96 | | - new{TE,T}(A, B1, B2, C1, C2, D11, D12, D21, D22, timeevol) |
97 | | - end |
| 63 | +struct ExtendedStateSpace{TE, T, S<:AbstractStateSpace{TE}, I} <: AbstractStateSpace{TE} |
| 64 | + sys::S # The underlying StateSpace |
| 65 | + w::I # Disturbance input indices (corresponds to B1) |
| 66 | + u::I # Control input indices (corresponds to B2) |
| 67 | + z::I # Performance output indices (corresponds to C1) |
| 68 | + y::I # Measured output indices (corresponds to C2) |
| 69 | +end |
| 70 | + |
| 71 | +# Inner constructor taking individual matrices (for backward compatibility) |
| 72 | +function ExtendedStateSpace{TE,T}( |
| 73 | + A, |
| 74 | + B1, |
| 75 | + B2, |
| 76 | + C1, |
| 77 | + C2, |
| 78 | + D11, |
| 79 | + D12, |
| 80 | + D21, |
| 81 | + D22, |
| 82 | + timeevol::TE, |
| 83 | +) where {TE,T} |
| 84 | + nx = size(A, 1) |
| 85 | + nw = size(B1, 2) |
| 86 | + nu = size(B2, 2) |
| 87 | + nz = size(C1, 1) |
| 88 | + ny = size(C2, 1) |
| 89 | + |
| 90 | + size(A, 2) != nx && nx != 0 && error("A must be square") |
| 91 | + size(B1, 1) == nx || error("B1 must have the same row size as A") |
| 92 | + size(B2, 1) == nx || error("B2 must have the same row size as A") |
| 93 | + size(C1, 2) == nx || error("C1 must have the same column size as A") |
| 94 | + size(C2, 2) == nx || error("C2 must have the same column size as A") |
| 95 | + size(D11, 2) == nw || error("D11 must have the same column size as B1") |
| 96 | + size(D21, 2) == nw || error("D21 must have the same column size as B1") |
| 97 | + size(D12, 2) == nu || error("D12 must have the same column size as B2") |
| 98 | + size(D22, 2) == nu || error("D22 must have the same column size as B2") |
| 99 | + size(D11, 1) == nz || error("D11 must have the same row size as C1") |
| 100 | + size(D12, 1) == nz || error("D12 must have the same row size as C1") |
| 101 | + size(D21, 1) == ny || error("D21 must have the same row size as C2") |
| 102 | + size(D22, 1) == ny || error("D22 must have the same row size as C2") |
| 103 | + |
| 104 | + # Build combined matrices |
| 105 | + B = [B1 B2] |
| 106 | + C = [C1; C2] |
| 107 | + D = [D11 D12; D21 D22] |
| 108 | + sys = StateSpace{TE, T}(A, B, C, D, timeevol) |
| 109 | + |
| 110 | + # Compute index vectors |
| 111 | + w_inds = 1:nw |
| 112 | + u_inds = (nw+1):(nw+nu) |
| 113 | + z_inds = 1:nz |
| 114 | + y_inds = (nz+1):(nz+ny) |
| 115 | + |
| 116 | + ExtendedStateSpace{TE, T, typeof(sys), typeof(w_inds)}(sys, w_inds, u_inds, z_inds, y_inds) |
| 117 | +end |
| 118 | + |
| 119 | +# Constructor with all 4 type parameters (for type-preserving operations like negation) |
| 120 | +function ExtendedStateSpace{TE, T, S, I}( |
| 121 | + A, |
| 122 | + B1, |
| 123 | + B2, |
| 124 | + C1, |
| 125 | + C2, |
| 126 | + D11, |
| 127 | + D12, |
| 128 | + D21, |
| 129 | + D22, |
| 130 | + timeevol::TE, |
| 131 | +) where {TE, T, S, I} |
| 132 | + # Delegate to the simpler constructor |
| 133 | + ExtendedStateSpace{TE, T}(A, B1, B2, C1, C2, D11, D12, D21, D22, timeevol) |
98 | 134 | end |
99 | 135 |
|
100 | 136 | function ExtendedStateSpace( |
@@ -181,53 +217,85 @@ function ss( |
181 | 217 | return ExtendedStateSpace(A, B1, B2, C1, C2, D11, D12, D21, D22, Ts) |
182 | 218 | end |
183 | 219 |
|
184 | | -function Base.promote_rule(::Type{StateSpace{TE, F1}}, ::Type{ExtendedStateSpace{TE, F2}}) where {TE, F1, F2} |
| 220 | +function Base.promote_rule(::Type{StateSpace{TE, F1}}, ::Type{<:ExtendedStateSpace{TE, F2}}) where {TE, F1, F2} |
185 | 221 | ExtendedStateSpace{TE, promote_type(F1, F2)} |
186 | 222 | end |
187 | 223 |
|
188 | | -function Base.convert(::Type{ExtendedStateSpace{TE, F2}}, s::StateSpace{TE, F1})where {TE, F1, F2} |
| 224 | +function Base.convert(::Type{<:ExtendedStateSpace{TE}}, s::StateSpace{TE, F1}) where {TE, F1} |
189 | 225 | partition(s, 0, 0) |
190 | 226 | end |
191 | 227 |
|
192 | | -function Base.getproperty(sys::ExtendedStateSpace, s::Symbol) |
193 | | - if s === :Ts |
194 | | - # if !isdiscrete(sys) # NOTE this line seems to be breaking inference of isdiscrete (is there a test for this?) |
195 | | - if isdiscrete(sys) |
196 | | - return timeevol(sys).Ts |
| 228 | +function Base.getproperty(esys::ExtendedStateSpace, s::Symbol) |
| 229 | + # Access to underlying system and index vectors |
| 230 | + if s === :sys || s === :w || s === :u || s === :z || s === :y |
| 231 | + return getfield(esys, s) |
| 232 | + end |
| 233 | + |
| 234 | + # Get the underlying system for matrix extraction |
| 235 | + sys = getfield(esys, :sys) |
| 236 | + w = getfield(esys, :w) |
| 237 | + u = getfield(esys, :u) |
| 238 | + z = getfield(esys, :z) |
| 239 | + y = getfield(esys, :y) |
| 240 | + |
| 241 | + # Extract matrices via indexing |
| 242 | + if s === :A |
| 243 | + return sys.A |
| 244 | + elseif s === :B1 |
| 245 | + return sys.B[:, w] |
| 246 | + elseif s === :B2 |
| 247 | + return sys.B[:, u] |
| 248 | + elseif s === :C1 |
| 249 | + return sys.C[z, :] |
| 250 | + elseif s === :C2 |
| 251 | + return sys.C[y, :] |
| 252 | + elseif s === :D11 |
| 253 | + return sys.D[z, w] |
| 254 | + elseif s === :D12 |
| 255 | + return sys.D[z, u] |
| 256 | + elseif s === :D21 |
| 257 | + return sys.D[y, w] |
| 258 | + elseif s === :D22 |
| 259 | + return sys.D[y, u] |
| 260 | + elseif s === :timeevol |
| 261 | + return sys.timeevol |
| 262 | + elseif s === :Ts |
| 263 | + if isdiscrete(esys) |
| 264 | + return timeevol(esys).Ts |
197 | 265 | else |
198 | 266 | @warn "Getting time 0.0 for non-discrete systems is deprecated. Check `isdiscrete` before trying to access time." |
199 | 267 | return 0.0 |
200 | 268 | end |
201 | 269 | elseif s === :nx |
202 | | - return nstates(sys) |
| 270 | + return nstates(esys) |
203 | 271 | elseif s === :nu |
204 | | - return size(sys.B2, 2) |
205 | | - elseif s === :ny # TODO: now size(sys.C, 1) is not always the same as sys.ny |
206 | | - return size(sys.C2, 1) |
| 272 | + return length(u) |
| 273 | + elseif s === :ny |
| 274 | + return length(y) |
207 | 275 | elseif s === :nw |
208 | | - return size(sys.B1, 2) |
| 276 | + return length(w) |
209 | 277 | elseif s === :nz |
210 | | - return size(sys.C1, 1) |
| 278 | + return length(z) |
211 | 279 | elseif s === :B |
212 | | - [sys.B1 sys.B2] |
| 280 | + return sys.B |
213 | 281 | elseif s === :C |
214 | | - [sys.C1; sys.C2] |
| 282 | + return sys.C |
215 | 283 | elseif s === :D |
216 | | - [sys.D11 sys.D12; sys.D21 sys.D22] |
| 284 | + return sys.D |
217 | 285 | elseif s === :zinds |
218 | | - return 1:size(sys.C1, 1) |
| 286 | + return z |
219 | 287 | elseif s === :yinds |
220 | | - return size(sys.C1, 1) .+ (1:size(sys.C2, 1)) |
| 288 | + return y |
221 | 289 | elseif s === :winds |
222 | | - return 1:size(sys.B1, 2) |
| 290 | + return w |
223 | 291 | elseif s === :uinds |
224 | | - return size(sys.B1, 2) .+ (1:size(sys.B2, 2)) |
| 292 | + return u |
225 | 293 | else |
226 | | - return getfield(sys, s) |
| 294 | + error("type ExtendedStateSpace has no field $s") |
227 | 295 | end |
228 | 296 | end |
229 | 297 |
|
230 | | -Base.propertynames(sys::ExtendedStateSpace) = (:A, :B, :C, :D, :B1, :B2, :C1, :C2, :D11, :D12, :D21, :D22, :Ts, :timeevol, :nx, :ny, :nu, :nw, :nz, :zinds, :yinds, :winds, :uinds) |
| 298 | +Base.propertynames(::ExtendedStateSpace) = (:A, :B, :C, :D, :B1, :B2, :C1, :C2, :D11, :D12, :D21, :D22, :Ts, :timeevol, :nx, :ny, :nu, :nw, :nz, :zinds, :yinds, :winds, :uinds, :sys, :w, :u, :z, :y) |
231 | 299 |
|
232 | 300 | ControlSystemsBase.StateSpace(s::ExtendedStateSpace) = ss(ssdata(s)..., s.timeevol) |
233 | 301 |
|
|
0 commit comments