Skip to content

Commit e1ab130

Browse files
committed
implement lft system interconnection
1 parent facd020 commit e1ab130

1 file changed

Lines changed: 96 additions & 0 deletions

File tree

control/statesp.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,102 @@ def feedback(self, other=1, sign=-1):
607607

608608
return StateSpace(A, B, C, D, dt)
609609

610+
def lft(self, other, nu=-1, ny=-1):
611+
"""Return the Linear Fractional Transformation.
612+
613+
See definition here:
614+
https://www.mathworks.com/help/control/ref/lft.html
615+
616+
Parameters
617+
----------
618+
other: LTI
619+
The lower LTI system
620+
ny: int, optional
621+
Dimension of (plant) measurement output.
622+
nu: int, optional
623+
Dimension of (plant) control input.
624+
"""
625+
other = _convertToStateSpace(other)
626+
# maximal values for nu, ny
627+
if ny == -1:
628+
ny = min(other.inputs, self.outputs)
629+
if nu == -1:
630+
nu = min(other.outputs, self.inputs)
631+
# dimension check
632+
# TODO
633+
634+
# Figure out the sampling time to use
635+
if (self.dt == None and other.dt != None):
636+
dt = other.dt # use dt from second argument
637+
elif (other.dt == None and self.dt != None) or \
638+
timebaseEqual(self, other):
639+
dt = self.dt # use dt from first argument
640+
else:
641+
raise ValueError("Systems have different sampling times")
642+
643+
# submatrices
644+
A = self.A
645+
B1 = self.B[:, :self.inputs - nu]
646+
B2 = self.B[:, self.inputs - nu:]
647+
C1 = self.C[:self.outputs - ny, :]
648+
C2 = self.C[self.outputs - ny:, :]
649+
D11 = self.D[:self.outputs - ny, :self.inputs - nu]
650+
D12 = self.D[:self.outputs - ny, self.inputs - nu:]
651+
D21 = self.D[self.outputs - ny:, :self.inputs - nu]
652+
D22 = self.D[self.outputs - ny:, self.inputs - nu:]
653+
654+
# submatrices
655+
Abar = other.A
656+
Bbar1 = other.B[:, :ny]
657+
Bbar2 = other.B[:, ny:]
658+
Cbar1 = other.C[:nu, :]
659+
Cbar2 = other.C[nu:, :]
660+
Dbar11 = other.D[:nu, :ny]
661+
Dbar12 = other.D[:nu, ny:]
662+
Dbar21 = other.D[nu:, :ny]
663+
Dbar22 = other.D[nu:, ny:]
664+
665+
# well-posed check
666+
F = np.block([[np.eye(ny), -D22], [-Dbar11, np.eye(nu)]])
667+
if matrix_rank(F) != ny + nu:
668+
raise ValueError("lft not well-posed to working precision.")
669+
670+
# solve for the resulting ss by solving for [y, u] using [x,
671+
# xbar] and [w1, w2].
672+
TH = np.linalg.solve(F, np.block(
673+
[[C2, np.zeros((ny, other.states)), D21, np.zeros((ny, other.inputs - ny))],
674+
[np.zeros((nu, self.states)), Cbar1, np.zeros((nu, self.inputs - nu)), Dbar12]]
675+
))
676+
T11 = TH[:ny, :self.states]
677+
T12 = TH[:ny, self.states: self.states + other.states]
678+
T21 = TH[ny:, :self.states]
679+
T22 = TH[ny:, self.states: self.states + other.states]
680+
H11 = TH[:ny, self.states + other.states: self.states + other.states + self.inputs - nu]
681+
H12 = TH[:ny, self.states + other.states + self.inputs - nu:]
682+
H21 = TH[ny:, self.states + other.states: self.states + other.states + self.inputs - nu]
683+
H22 = TH[ny:, self.states + other.states + self.inputs - nu:]
684+
685+
Ares = np.block([
686+
[A + B2.dot(T21), B2.dot(T22)],
687+
[Bbar1.dot(T11), Abar + Bbar1.dot(T12)]
688+
])
689+
690+
Bres = np.block([
691+
[B1 + B2.dot(H21), B2.dot(H22)],
692+
[Bbar1.dot(H11), Bbar2 + Bbar1.dot(H12)]
693+
])
694+
695+
Cres = np.block([
696+
[C1 + D12.dot(T21), D12.dot(T22)],
697+
[Dbar21.dot(T11), Cbar2 + Dbar21.dot(T12)]
698+
])
699+
700+
Dres = np.block([
701+
[D11 + D12.dot(H21), D12.dot(H22)],
702+
[Dbar21.dot(H11), Dbar22 + Dbar21.dot(H12)]
703+
])
704+
return StateSpace(Ares, Bres, Cres, Dres, dt)
705+
610706
def minreal(self, tol=0.0):
611707
"""Calculate a minimal realization, removes unobservable and
612708
uncontrollable states"""

0 commit comments

Comments
 (0)