Skip to content

Commit 084727c

Browse files
committed
implement lft system interconnection
1 parent 47deb9e commit 084727c

File tree

1 file changed

+96
-0
lines changed

1 file changed

+96
-0
lines changed

control/statesp.py

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

612612
return StateSpace(A, B, C, D, dt)
613613

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

0 commit comments

Comments
 (0)