@@ -152,7 +152,42 @@ Return a named tuple `(; x, u)` containing the operating point of the system `P`
152152"""
153153operating_point (P:: NamedStateSpace ) = get_extra (P, :operating_point , (x= zeros (P. nx), u= zeros (P. nu)))
154154operating_point (P:: AbstractStateSpace ) = (x= zeros (P. nx), u= zeros (P. nu))
155- set_operating_point! (P:: NamedStateSpace , xu:: NamedTuple{(:x,:u)} ) = set_extra! (P, :operating_point , xu)
155+ function set_operating_point! (P:: NamedStateSpace , xu:: NamedTuple{(:x,:u)} )
156+ length (xu. x) == P. nx ||
157+ throw (ArgumentError (" Operating point x must have length $(P. nx) , got length $(length (xu. x)) " ))
158+ length (xu. u) == P. nu ||
159+ throw (ArgumentError (" Operating point u must have length $(P. nu) , got length $(length (xu. u)) " ))
160+ set_extra! (P, :operating_point , xu)
161+ end
162+ has_operating_point (P:: NamedStateSpace ) = haskey (P. extra, :operating_point )
163+
164+
165+ """
166+ infer_operating_point(P1, P2, method = :obsv)
167+
168+ Return the operating point of `P1` inferred from the operating point of `P2` and a similarity transform between the two systems. The method for finding the similarity transform can be specified, default is `:obsv`.
169+
170+ ```
171+ s1 = named_ss(ssrand(1,1,2), "P", x = :x, u = :u1, y=:y1)
172+ opx = randn(s1.nx)
173+ opu = randn(s1.nu)
174+ RobustAndOptimalControl.set_operating_point!(s1, (x = opx, u = opu))
175+
176+ s2, _ = balance_statespace(s1) # s1 and s2 are similar systems
177+ op2 = RobustAndOptimalControl.infer_operating_point(s2, s1)
178+ @test RobustAndOptimalControl.operating_point(s2) == op2
179+ ```
180+ """
181+ function infer_operating_point (P1, P2, method = :obsv )
182+ T = ControlSystemsBase. find_similarity_transform (P1, P2, method)
183+ op2 = operating_point (P2)
184+ op1 = (x = T* op2. x, u = op2. u)
185+ end
186+
187+ function infer_operating_point! (P1, P2, method = :obsv )
188+ op = infer_operating_point (P1, P2, method)
189+ set_operating_point! (P1, op)
190+ end
156191
157192const NamedIndex = Union{Symbol, Vector{Symbol}, Colon}
158193
@@ -265,29 +300,43 @@ iterable(v) = v
265300function Base. getindex (sys:: NamedStateSpace{T,S} , i:: NamedIndex , j:: NamedIndex ) where {T,S}
266301 i,j = iterable .((i, j))
267302 ii = i isa Colon ? i : names2indices (i, sys. y)
268- jj = j isa Colon ? j : names2indices (j, sys. u)
303+ jj = j isa Colon ? j : names2indices (j, sys. u)
269304
270- return NamedStateSpace {T,S} (
305+ newsys = NamedStateSpace {T,S} (
271306 sys. sys[ii, jj],
272307 sys. x,
273308 sys. u[jj],
274309 sys. y[ii],
275310 sys. name,
311+ sys. extra,
276312 ) # |> sminreal
313+ if has_operating_point (sys)
314+ op = operating_point (sys)
315+ op = (; op. x, u= op. u[jj])
316+ set_operating_point! (newsys, op)
317+ end
318+ return newsys
277319end
278320
279321function Base. getindex (sys:: NamedStateSpace{T,S} , inds... ) where {T,S}
280322 if size (inds, 1 ) != 2
281323 error (" Must specify 2 indices to index statespace model" )
282324 end
283325 rows, cols = CS. index2range (inds... )
284- return NamedStateSpace {T,S} (
326+ newsys = NamedStateSpace {T,S} (
285327 sys. sys[rows, cols],
286328 sys. x,
287329 sys. u[cols],
288330 sys. y[rows],
289331 sys. name,
332+ sys. extra,
290333 ) # |> sminreal
334+ if has_operating_point (sys)
335+ op = operating_point (sys)
336+ op = (; op. x, u= op. u[cols])
337+ set_operating_point! (newsys, op)
338+ end
339+ return newsys
291340end
292341
293342function Base. show (io:: IO , G:: NamedStateSpace )
@@ -300,6 +349,10 @@ function Base.show(io::IO, G::NamedStateSpace)
300349 length (G. x) < 50 && (print (io, " \n With state names: " ); println (io, join (G. x, ' ' )))
301350 length (G. u) < 50 && (print (io, " input names: " ); println (io, join (G. u, ' ' )))
302351 length (G. y) < 50 && (print (io, " output names: " ); println (io, join (G. y, ' ' )))
352+ if has_operating_point (G)
353+ op = operating_point (G)
354+ println (io, " Operating point: x = " , op. x, " , u = " , op. u)
355+ end
303356end
304357
305358function Base.:- (s1:: NamedStateSpace{T,S} ) where {T <: CS.TimeEvolution , S}
@@ -309,6 +362,7 @@ function Base.:-(s1::NamedStateSpace{T,S}) where {T <: CS.TimeEvolution, S}
309362 s1. u,
310363 s1. y,
311364 s1. name,
365+ s1. extra,
312366 )
313367end
314368
@@ -319,6 +373,7 @@ function Base.:+(s1::NamedStateSpace{T,S}, s2::NamedStateSpace{T,S}) where {T <:
319373 s1. u,
320374 s1. y,
321375 " " ,
376+ s1. extra,
322377 )
323378end
324379
@@ -336,6 +391,7 @@ function Base.:*(s1::NamedStateSpace{T}, s2::NamedStateSpace{T}) where {T <: CS.
336391 s2. u,
337392 s1. y,
338393 " " ,
394+ s1. extra,
339395 )
340396end
341397
@@ -346,6 +402,7 @@ function Base.:*(s1::Number, s2::NamedStateSpace{T, S}) where {T <: CS.TimeEvolu
346402 s2. u,
347403 [Symbol (string (y)* " _scaled" ) for y in s2. y],
348404 isempty (s2. name) ? " " : s2. name* " _scaled" ,
405+ s2. extra,
349406 )
350407end
351408
@@ -356,6 +413,7 @@ function Base.:*(s1::NamedStateSpace{T, S}, s2::Number) where {T <: CS.TimeEvolu
356413 [Symbol (string (u)* " _scaled" ) for u in s1. u],
357414 s1. y,
358415 isempty (s1. name) ? " " : s1. name* " _scaled" ,
416+ s1. extra,
359417 )
360418end
361419
@@ -367,6 +425,7 @@ function Base.:*(s1::AbstractMatrix, s2::NamedStateSpace{T, S}) where {T <: CS.T
367425 s2. u,
368426 [Symbol (string (y)* " _scaled" ) for y in s2. y],
369427 isempty (s2. name) ? " " : s2. name* " _scaled" ,
428+ s2. extra,
370429 )
371430 else
372431 return * (promote (s1, s2)... )
@@ -381,6 +440,7 @@ function Base.:*(s1::NamedStateSpace{T, S}, s2::AbstractMatrix) where {T <: CS.T
381440 [Symbol (string (u)* " _scaled" ) for u in s1. u],
382441 s1. y,
383442 isempty (s1. name) ? " " : s1. name* " _scaled" ,
443+ s1. extra,
384444 )
385445 else
386446 return * (promote (s1, s2)... )
@@ -405,25 +465,45 @@ function Base.hcat(systems::NamedStateSpace{T,S}...) where {T,S}
405465 x = generate_unique_x_names (systems... )
406466 u = reduce (vcat, getproperty .(systems, :u ))
407467 check_unique (u, " u" )
468+ if any (has_operating_point, systems)
469+ opx = reduce (vcat, operating_point (sys). x for sys in systems)
470+ opu = reduce (vcat, operating_point (sys). u for sys in systems)
471+ op = (; x = opx, u = opu)
472+ extra = Dict (:operating_point => op)
473+ else
474+ extra = Dict {Symbol, Any} ()
475+ end
408476 return NamedStateSpace {T,S} (
409477 hcat (getproperty .(systems, :sys )... ),
410478 x,
411479 u,
412480 systems[1 ]. y,
413481 " " ,
482+ extra
414483 )
415484end
416485
417486function Base. vcat (systems:: NamedStateSpace{T,S} ...) where {T,S}
418487 x = generate_unique_x_names (systems... )
419488 y = reduce (vcat, getproperty .(systems, :y ))
420489 check_unique (y, " y" )
490+ if any (has_operating_point, systems)
491+ allequal (operating_point (sys). u for sys in systems) ||
492+ throw (ArgumentError (" All systems must have the same input operating point u to be concatenated." ))
493+ opx = reduce (vcat, operating_point (sys). x for sys in systems)
494+ opu = operating_point (systems[1 ]). u
495+ op = (; x = opx, u = opu)
496+ extra = Dict (:operating_point => op)
497+ else
498+ extra = Dict {Symbol, Any} ()
499+ end
421500 return NamedStateSpace {T,S} (
422501 vcat (getproperty .(systems, :sys )... ),
423502 x,
424503 systems[1 ]. u,
425504 y,
426505 " " ,
506+ extra,
427507 )
428508end
429509
@@ -443,7 +523,7 @@ function measure(s::NamedStateSpace, names)
443523 C[i, inds[i]] = 1
444524 end
445525 s2 = ss (A,B,C,0 , s. timeevol)
446- sminreal (named_ss (s2; s. x, s. u, y= names, name= s. name))
526+ sminreal (named_ss (s2; s. x, s. u, y= names, name= s. name, s . extra ))
447527end
448528
449529"""
@@ -541,7 +621,7 @@ function ControlSystemsBase.feedback(s1::NamedStateSpace{T}, s2::NamedStateSpace
541621 @assert sys. nu == length (W1) + length (W2)
542622 @assert sys. ny == length (Z1) + length (Z2)
543623 @assert sys. nx == length (x1)
544- nsys = NamedStateSpace {T,typeof(sys)} (sys, x1, [s1. u[W1]; s2. u[W2]], [s1. y[Z1]; s2. y[Z2]], " " )
624+ nsys = NamedStateSpace {T,typeof(sys)} (sys, x1, [s1. u[W1]; s2. u[W2]], [s1. y[Z1]; s2. y[Z2]], " " , Dict {Symbol, Any} () )
545625 # sminreal(nsys)
546626end
547627
@@ -838,7 +918,7 @@ function named_ss(sys::ExtendedStateSpace{T}, name="";
838918 throw (ArgumentError (" Length of performance names must match sys.nz ($(sys. nz) )" ))
839919
840920 sys2 = ss (sys)
841- NamedStateSpace {T, typeof(sys2)} (sys2, x, [w; u], [z; y], name)
921+ NamedStateSpace {T, typeof(sys2)} (sys2, x, [w; u], [z; y], name, Dict {Symbol, Any} () )
842922end
843923
844924
893973
894974function CS. c2d (s:: NamedStateSpace{Continuous} , Ts:: Real , method:: Symbol = :zoh , args... ;
895975 kwargs... )
896- named_ss (c2d (s. sys, Ts, method, args... ; kwargs... ); s. x, s. u, s. y, s. name)
976+ named_ss (c2d (s. sys, Ts, method, args... ; kwargs... ); s. x, s. u, s. y, s. name, s . extra )
897977end
898978
899979
@@ -910,8 +990,13 @@ function CS.append(systems::NamedStateSpace...; kwargs...)
910990 x = reduce (vcat, getproperty .(systems, :x ))
911991 y = reduce (vcat, getproperty .(systems, :y ))
912992 u = reduce (vcat, getproperty .(systems, :u ))
913-
914- return named_ss (systype (A, B, C, D, timeevol); x, y, u, kwargs... )
993+
994+ newsys = named_ss (systype (A, B, C, D, timeevol); x, y, u, kwargs... )
995+ if any (has_operating_point, systems)
996+ op = (; x = reduce (vcat, operating_point (s). x for s in systems), u = reduce (vcat, operating_point (s). u for s in systems))
997+ set_operating_point! (newsys, op)
998+ end
999+ return newsys
9151000end
9161001
9171002
@@ -941,7 +1026,7 @@ function CS.minreal(sys::NamedStateSpace, args...; kwargs...)
9411026 named_ss (msys; sys. u, sys. y, sys. name)
9421027end
9431028
944- for fun in [:baltrunc , :balreal , :balance_statespace ]
1029+ for fun in [:baltrunc , :balreal ]
9451030 @eval function CS. $ (fun)(sys:: NamedStateSpace , args... ; kwargs... )
9461031 msys, rest... = CS.$ (fun)(sys. sys, args... ; kwargs... )
9471032 named_ss (msys; sys. u, sys. y, sys. name), rest...
0 commit comments