@@ -602,6 +602,107 @@ def feedback(self, other=1, sign=-1):
602602
603603 return StateSpace (A , B , C , D , dt )
604604
605+ def lft (self , other , nu = - 1 , ny = - 1 ):
606+ """Return the Linear Fractional Transformation.
607+
608+ A definition of the LFT operator can be found in Appendix A.7,
609+ page 512 in the 2nd Edition, Multivariable Feedback Control by
610+ Sigurd Skogestad.
611+
612+ An alternative definition can be found here:
613+ https://www.mathworks.com/help/control/ref/lft.html
614+
615+ Parameters
616+ ----------
617+ other: LTI
618+ The lower LTI system
619+ ny: int, optional
620+ Dimension of (plant) measurement output.
621+ nu: int, optional
622+ Dimension of (plant) control input.
623+
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 time bases" )
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+
605706 def minreal (self , tol = 0.0 ):
606707 """Calculate a minimal realization, removes unobservable and
607708 uncontrollable states"""
0 commit comments