#############################################################################
##
##  Basic.gi                    MatricesForHomalg package    Mohamed Barakat
##
##  Copyright 2007-2008 Lehrstuhl B für Mathematik, RWTH Aachen
##
##  Implementations of homalg basic procedures.
##
#############################################################################

####################################
#
# methods for operations:
#
####################################

##  <#GAPDoc Label="BasisOfRows">
##  <ManSection>
##    <Oper Arg="M" Name="BasisOfRows" Label="for matrices"/>
##    <Oper Arg="M, T" Name="BasisOfRows" Label="for pairs of matrices"/>
##    <Returns>a &homalg; matrix</Returns>
##    <Description>
##      With one argument it is a synonym of <Ref Oper="BasisOfRowModule" Label="for matrices"/>.
##      with two arguments it is a synonym of <Ref Oper="BasisOfRowsCoeff" Label="for matrices"/>.
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallMethod( BasisOfRows,
        "for homalg matrices",
        [ IsHomalgMatrix ],
        
  BasisOfRowModule );

##
InstallMethod( BasisOfRows,
        "for homalg matrices",
        [ IsHomalgMatrix, IsHomalgMatrix and IsVoidMatrix ],
        
  BasisOfRowsCoeff );

##  <#GAPDoc Label="BasisOfColumns">
##  <ManSection>
##    <Oper Arg="M" Name="BasisOfColumns" Label="for matrices"/>
##    <Oper Arg="M, T" Name="BasisOfColumns" Label="for pairs of matrices"/>
##    <Returns>a &homalg; matrix</Returns>
##    <Description>
##      With one argument it is a synonym of <Ref Oper="BasisOfColumnModule" Label="for matrices"/>.
##      with two arguments it is a synonym of <Ref Oper="BasisOfColumnsCoeff" Label="for matrices"/>.
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallMethod( BasisOfColumns,
        "for homalg matrices",
        [ IsHomalgMatrix ],
        
  BasisOfColumnModule );

##
InstallMethod( BasisOfColumns,
        "for homalg matrices",
        [ IsHomalgMatrix, IsHomalgMatrix and IsVoidMatrix ],
        
  BasisOfColumnsCoeff );

##
InstallMethod( DecideZero,
        "for a ring element and a homalg ring element",
        [ IsRingElement, IsHomalgRingElement ],
        
  function( r, rel )
    local red;
    
    rel := HomalgMatrix( [ rel ], 1, 1, HomalgRing( rel ) );
    
    return DecideZero( r, rel );
    
end );

##
InstallMethod( DecideZero,
        "for homalg matrices",
        [ IsRingElement, IsHomalgMatrix ],
        
  function( r, rel )
    local red;
    
    r := HomalgMatrix( [ r ], 1, 1, HomalgRing( rel ) );
    
    if NrColumns( rel ) = 1 then
        red := DecideZeroRows( r, rel );
    elif NrRows( rel ) = 1 then
        red := DecideZeroColumns( r, rel );
    else
        Error( "either the number of columns or the number of rows of the matrix of relations must be 1\n" );
    fi;
    
    return red[ 1, 1 ];
    
end );

##
InstallMethod( DecideZero,
        "for homalg matrices",
        [ IsRingElement, IsHomalgRingRelations ],
        
  function( r, rel )
    
    return DecideZero( r, MatrixOfRelations( rel ) );
    
end );

##
InstallMethod( DecideZero,
        "for homalg matrices",
        [ IsHomalgMatrix, IsHomalgMatrix ], 1001,
        
  function( M, rel )
    
    if IsEmptyMatrix( M ) then
        return M;
    fi;
    
    if NrColumns( rel ) = 1 then
        rel := DiagMat( ListWithIdenticalEntries( NrColumns( M ), rel ) );
        return DecideZeroRows( M, rel );
    elif NrRows( rel ) = 1 then
        rel := DiagMat( ListWithIdenticalEntries( NrRows( M ), rel ) );
        return DecideZeroColumns( M, rel );
    fi;
    
    Error( "either the number of columns or the number of rows of the matrix of relations must be 1\n" );
    
end );

##
InstallMethod( DecideZero,
        "for homalg matrices",
        [ IsHomalgMatrix, IsList ],
        
  function( M, rel )
    local R;
    
    R := HomalgRing( M );
    
    rel := List( rel,
                 function( r )
                   if IsString( r ) then
                       return HomalgRingElement( r, R );
                   elif IsRingElement( r ) then
                       return r;
                   else
                       Error( r, " is neither a string nor a ring element\n" );
                   fi;
                 end );
    
    ## we prefer (to reduce the) rows
    rel := HomalgMatrix( rel, Length( rel ), 1, R );
    
    return DecideZero( M, rel );
    
end );

##
InstallMethod( DecideZero,
        "for homalg matrices",
        [ IsHomalgMatrix ],
        
  function( M )
    
    IsZero( M );
    
    return M;
    
end );

##  <#GAPDoc Label="SyzygiesOfRows">
##  <ManSection>
##    <Oper Arg="M" Name="SyzygiesOfRows" Label="for matrices"/>
##    <Oper Arg="M, M2" Name="SyzygiesOfRows" Label="for pairs of matrices"/>
##    <Returns>a &homalg; matrix</Returns>
##    <Description>
##      With one argument it is a synonym of <Ref Oper="SyzygiesGeneratorsOfRows" Label="for matrices"/>.
##      with two arguments it is a synonym of <Ref Oper="SyzygiesGeneratorsOfRows" Label="for pairs of matrices"/>.
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallMethod( SyzygiesOfRows,
        "for homalg matrices",
        [ IsHomalgMatrix ],
        
  SyzygiesGeneratorsOfRows );

##
InstallMethod( SyzygiesOfRows,
        "for homalg matrices",
        [ IsHomalgMatrix, IsHomalgMatrix ],
        
  SyzygiesGeneratorsOfRows );

##
InstallMethod( SyzygiesOfRows,
        "for a list of two homalg matrices",
        [ IsList ],
        
  L -> CallFuncList( SyzygiesGeneratorsOfRows, L ) );

##
InstallMethod( LazySyzygiesOfRows,
        "for two homalg matrices",
        [ IsHomalgMatrix, IsHomalgMatrix ],
        
  function( M, N )
    
    return HomalgMatrixWithAttributes( [
                   EvalSyzygiesOfRows, [ M, N ],
                   NrColumns, NrRows( M )
                   ], HomalgRing( M ) );
    
end );

##
InstallMethod( IsZero,
        "for homalg matrices (HasEvalSyzygiesOfRows)",
        [ IsHomalgMatrix and HasEvalSyzygiesOfRows ], 100,
        
  function( C )
    local e;
    
    e :=  EvalSyzygiesOfRows( C );
    
    return IsZero( SyzygiesOfRows( e[1], e[2] ) );
    
end );

##
InstallMethod( Eval,
        "for homalg matrices (HasEvalSyzygiesOfRows)",
        [ IsHomalgMatrix and HasEvalSyzygiesOfRows ],
        
  function( C )
    local e;
    
    e :=  EvalSyzygiesOfRows( C );
    
    return Eval( SyzygiesOfRows( e[1], e[2] ) );
    
end );

##  <#GAPDoc Label="SyzygiesOfColumns">
##  <ManSection>
##    <Oper Arg="M" Name="SyzygiesOfColumns" Label="for matrices"/>
##    <Oper Arg="M, M2" Name="SyzygiesOfColumns" Label="for pairs of matrices"/>
##    <Returns>a &homalg; matrix</Returns>
##    <Description>
##      With one argument it is a synonym of <Ref Oper="SyzygiesGeneratorsOfColumns" Label="for matrices"/>.
##      with two arguments it is a synonym of <Ref Oper="SyzygiesGeneratorsOfColumns" Label="for pairs of matrices"/>.
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallMethod( SyzygiesOfColumns,
        "for homalg matrices",
        [ IsHomalgMatrix ],
        
  SyzygiesGeneratorsOfColumns );

##
InstallMethod( SyzygiesOfColumns,
        "for homalg matrices",
        [ IsHomalgMatrix, IsHomalgMatrix ],
        
  SyzygiesGeneratorsOfColumns );

##
InstallMethod( SyzygiesOfColumns,
        "for a list of two homalg matrices",
        [ IsList ],
        
  L -> CallFuncList( SyzygiesGeneratorsOfColumns, L ) );

##
InstallMethod( LazySyzygiesOfColumns,
        "for two homalg matrices",
        [ IsHomalgMatrix, IsHomalgMatrix ],
        
  function( M, N )
    
    return HomalgMatrixWithAttributes( [
                   EvalSyzygiesOfColumns, [ M, N ],
                   NrRows, NrColumns( M )
                   ], HomalgRing( M ) );
    
end );

##
InstallMethod( IsZero,
        "for homalg matrices (HasEvalSyzygiesOfColumns)",
        [ IsHomalgMatrix and HasEvalSyzygiesOfColumns ], 100,
        
  function( C )
    local e;
    
    e :=  EvalSyzygiesOfColumns( C );
    
    return IsZero( SyzygiesOfColumns( e[1], e[2] ) );
    
end );

##
InstallMethod( Eval,
        "for homalg matrices (HasEvalSyzygiesOfColumns)",
        [ IsHomalgMatrix and HasEvalSyzygiesOfColumns ],
        
  function( C )
    local e;
    
    e :=  EvalSyzygiesOfColumns( C );
    
    return Eval( SyzygiesOfColumns( e[1], e[2] ) );
    
end );

##  <#GAPDoc Label="ReducedSyzygiesOfRows">
##  <ManSection>
##    <Oper Arg="M" Name="ReducedSyzygiesOfRows" Label="for matrices"/>
##    <Oper Arg="M, M2" Name="ReducedSyzygiesOfRows" Label="for pairs of matrices"/>
##    <Returns>a &homalg; matrix</Returns>
##    <Description>
##      With one argument it is a synonym of <Ref Oper="ReducedSyzygiesGeneratorsOfRows" Label="for matrices"/>.
##      With two arguments it calls <C>ReducedBasisOfRowModule</C>( <C>SyzygiesGeneratorsOfRows</C>( <A>M</A>, <A>M2</A> ) ).
##      (&see; <Ref Oper="ReducedBasisOfRowModule" Label="for matrices"/> and
##       <Ref Oper="SyzygiesGeneratorsOfRows" Label="for pairs of matrices"/>)
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallMethod( ReducedSyzygiesOfRows,
        "for homalg matrices",
        [ IsHomalgMatrix ],
        
  ReducedSyzygiesGeneratorsOfRows );

##
InstallMethod( ReducedSyzygiesOfRows,
        "for homalg matrices",
        [ IsHomalgMatrix, IsHomalgMatrix ],
        
  function( M, M2 )
    
    ## a LIMAT method takes care of the case when M2 is _known_ to be zero
    ## checking IsZero( M2 ) causes too many obsolete calls
    
    ## a priori computing a basis of the syzygies matrix
    ## causes obsolete computations, at least in general
    return ReducedBasisOfRowModule( SyzygiesGeneratorsOfRows( M, M2 ) );
    
end );

##  <#GAPDoc Label="ReducedSyzygiesOfColumns">
##  <ManSection>
##    <Oper Arg="M" Name="ReducedSyzygiesOfColumns" Label="for matrices"/>
##    <Oper Arg="M, M2" Name="ReducedSyzygiesOfColumns" Label="for pairs of matrices"/>
##    <Returns>a &homalg; matrix</Returns>
##    <Description>
##      With one argument it is a synonym of <Ref Oper="ReducedSyzygiesGeneratorsOfColumns" Label="for matrices"/>.
##      With two arguments it calls <C>ReducedBasisOfColumnModule</C>( <C>SyzygiesGeneratorsOfColumns</C>( <A>M</A>, <A>M2</A> ) ).
##      (&see; <Ref Oper="ReducedBasisOfColumnModule" Label="for matrices"/> and
##       <Ref Oper="SyzygiesGeneratorsOfColumns" Label="for pairs of matrices"/>)
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallMethod( ReducedSyzygiesOfColumns,
        "for homalg matrices",
        [ IsHomalgMatrix ],
        
  ReducedSyzygiesGeneratorsOfColumns );

##
InstallMethod( ReducedSyzygiesOfColumns,
        "for homalg matrices",
        [ IsHomalgMatrix, IsHomalgMatrix ],
        
  function( M, M2 )
    
    ## a LIMAT method takes care of the case when M2 is _known_ to be zero
    ## checking IsZero( M2 ) causes too many obsolete calls
    
    ## a priori computing a basis of the syzygies matrix
    ## causes obsolete computations, at least in general
    return ReducedBasisOfColumnModule( SyzygiesGeneratorsOfColumns( M, M2 ) );
    
end );

##
InstallMethod( SyzygiesBasisOfRows,		### defines: SyzygiesBasisOfRows (SyzygiesBasis)
        "for homalg matrices",
        [ IsHomalgMatrix ],
        
  function( M )
    local S;
    
    S := SyzygiesOfRows( M );
    
    return BasisOfRows( S );
    
end );

##
InstallMethod( SyzygiesBasisOfRows,		### defines: SyzygiesBasisOfRows (SyzygiesBasis)
        "for homalg matrices",
        [ IsHomalgMatrix, IsHomalgMatrix ],
        
  function( M1, M2 )
    local S;
    
    S := SyzygiesOfRows( M1, M2 );
    
    return BasisOfRows( S );
    
end );

##
InstallMethod( SyzygiesBasisOfColumns,		### defines: SyzygiesBasisOfColumns (SyzygiesBasis)
        "for homalg matrices",
        [ IsHomalgMatrix ],
        
  function( M )
    local S;
    
    S := SyzygiesOfColumns( M );
    
    return BasisOfColumns( S );
    
end );

##
InstallMethod( SyzygiesBasisOfColumns,		### defines: SyzygiesBasisOfColumns (SyzygiesBasis)
        "for homalg matrices",
        [ IsHomalgMatrix, IsHomalgMatrix ],
        
  function( M1, M2 )
    local S;
    
    S := SyzygiesOfColumns( M1, M2 );
    
    return BasisOfColumns( S );
    
end );

##  <#GAPDoc Label="RightDivide">
##  <ManSection>
##    <Oper Arg="B, A" Name="RightDivide" Label="for pairs of matrices"/>
##    <Returns>a &homalg; matrix or fail</Returns>
##    <Description>
##      Let <A>B</A> and <A>A</A> be matrices having the same number of columns and defined over the same ring.
##      The matrix <C>RightDivide</C>( <A>B</A>, <A>A</A> ) is a particular solution of the inhomogeneous (one sided) linear system
##      of equations <M>X<A>A</A>=<A>B</A></M> in case it is solvable. Otherwise <C>fail</C> is returned.
##      The name <C>RightDivide</C> suggests <Q><M>X=<A>B</A><A>A</A>^{-1}</M></Q>.
##      This generalizes <Ref Attr="LeftInverse" Label="for matrices"/> for which <A>B</A> becomes the identity matrix.
##      (&see; <Ref Oper="SyzygiesGeneratorsOfRows" Label="for matrices"/>)
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallMethod( RightDivide,			### defines: RightDivide (RightDivideF)
        "for homalg matrices",
        [ IsHomalgMatrix, IsHomalgMatrix ],
        
  function( B, A )				## CAUTION: Do not use lazy evaluation here!!!
    local R, T, B_;
    
    R := HomalgRing( B );
    
    ## B_ = B + T * A
    T := HomalgVoidMatrix( R );
    B_ := DecideZeroRowsEffectively( B, A, T );
    
    ## if B_ does not vanish
    if not IsZero( B_ ) then
        ## A is not a right factor of B, i.e.
        ## the rows of A are not a generating set
        return fail;
    fi;
    
    ## check assertion
    Assert( 5, IsZero( B + T * A ) );
    
    return -T;
    
end );

##  <#GAPDoc Label="LeftDivide">
##  <ManSection>
##    <Oper Arg="A, B" Name="LeftDivide" Label="for pairs of matrices"/>
##    <Returns>a &homalg; matrix or fail</Returns>
##    <Description>
##      Let <A>A</A> and <A>B</A> be matrices having the same number of rows and defined over the same ring.
##      The matrix <C>LeftDivide</C>( <A>A</A>, <A>B</A> ) is a particular solution of the inhomogeneous (one sided) linear system
##      of equations <M><A>A</A>X=<A>B</A></M> in case it is solvable. Otherwise <C>fail</C> is returned.
##      The name <C>LeftDivide</C> suggests <Q><M>X=<A>A</A>^{-1}<A>B</A></M></Q>.
##      This generalizes <Ref Attr="RightInverse" Label="for matrices"/> for which <A>B</A> becomes the identity matrix.
##      (&see; <Ref Oper="SyzygiesGeneratorsOfColumns" Label="for matrices"/>)
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallMethod( LeftDivide,			### defines: LeftDivide (LeftDivideF)
        "for homalg matrices",
        [ IsHomalgMatrix, IsHomalgMatrix ],
        
  function( A, B )				## CAUTION: Do not use lazy evaluation here!!!
    local R, T, B_;
    
    R := HomalgRing( B );
    
    ## B_ = B + A * T
    T := HomalgVoidMatrix( R );
    B_ := DecideZeroColumnsEffectively( B, A, T );
    
    ## if B_ does not vanish
    if not IsZero( B_ ) then
        ## A is not a left factor of B, i.e.
        ## the columns of A are not a generating set
        return fail;
    fi;
    
    ## check assertion
    Assert( 5, IsZero( B + A * T ) );
    
    return -T;
    
end );

##  <#GAPDoc Label="RelativeRightDivide">
##  <ManSection>
##    <Oper Arg="B, A, L" Name="RightDivide" Label="for triples of matrices"/>
##    <Returns>a &homalg; matrix or fail</Returns>
##    <Description>
##      Let <A>B</A>, <A>A</A> and <A>L</A> be matrices having the same number of columns and defined over the same ring.
##      The matrix <C>RightDivide</C>( <A>B</A>, <A>A</A>, <A>L</A> ) is a particular solution of the inhomogeneous (one sided)
##      linear system of equations <M>X<A>A</A>+Y<A>L</A>=<A>B</A></M> in case it is solvable (for some <M>Y</M> which is forgotten).
##      Otherwise <C>fail</C> is returned. The name <C>RightDivide</C> suggests <Q><M>X=<A>B</A><A>A</A>^{-1}</M> modulo <A>L</A></Q>.
##      (Cf. <Cite Key="BR" Where="Subsection 3.1.1"/>)
##    <Listing Type="Code"><![CDATA[
InstallMethod( RightDivide,
        "for homalg matrices",
        [ IsHomalgMatrix, IsHomalgMatrix, IsHomalgMatrix ],
        
  function( B, A, L )	## CAUTION: Do not use lazy evaluation here!!!
    local R, BL, ZA, AL, ZB, T, B_;
    
    R := HomalgRing( B );
    
    BL := BasisOfRows( L );
    
    ## first reduce A modulo L
    ZA := DecideZeroRows( A, BL );
    
    AL := UnionOfRows( ZA, BL );
    
    ## also reduce B modulo L
    ZB := DecideZeroRows( B, BL );
    
    ## B_ = ZB + T * AL
    T := HomalgVoidMatrix( R );
    B_ := DecideZeroRowsEffectively( ZB, AL, T );
    
    ## if B_ does not vanish
    if not IsZero( B_ ) then
        return fail;
    fi;
    
    T := CertainColumns( T, [ 1 .. NrRows( A ) ] );
    
    ## check assertion
    Assert( 5, IsZero( DecideZeroRows( B + T * A, BL ) ) );
    
    return -T;
    
end );
##  ]]></Listing>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##  <#GAPDoc Label="RelativeLeftDivide">
##  <ManSection>
##    <Oper Arg="A, B, L" Name="LeftDivide" Label="for triples of matrices"/>
##    <Returns>a &homalg; matrix or fail</Returns>
##    <Description>
##      Let <A>A</A>, <A>B</A> and <A>L</A> be matrices having the same number of columns and defined over the same ring.
##      The matrix <C>LeftDivide</C>( <A>A</A>, <A>B</A>, <A>L</A> ) is a particular solution of the inhomogeneous (one sided)
##      linear system of equations <M><A>A</A>X+<A>L</A>Y=<A>B</A></M> in case it is solvable (for some <M>Y</M> which is forgotten).
##      Otherwise <C>fail</C> is returned. The name <C>LeftDivide</C> suggests <Q><M>X=<A>A</A>^{-1}<A>B</A></M> modulo <A>L</A></Q>.
##      (Cf. <Cite Key="BR" Where="Subsection 3.1.1"/>)
##    <Listing Type="Code"><![CDATA[
InstallMethod( LeftDivide,
        "for homalg matrices",
        [ IsHomalgMatrix, IsHomalgMatrix, IsHomalgMatrix ],
        
  function( A, B, L )	## CAUTION: Do not use lazy evaluation here!!!
    local R, BL, ZA, AL, ZB, T, B_;
    
    R := HomalgRing( B );
    
    BL := BasisOfColumns( L );
    
    ## first reduce A modulo L
    ZA := DecideZeroColumns( A, BL );
    
    AL := UnionOfColumns( ZA, BL );
    
    ## also reduce B modulo L
    ZB := DecideZeroColumns( B, BL );
    
    ## B_ = ZB + AL * T
    T := HomalgVoidMatrix( R );
    B_ := DecideZeroColumnsEffectively( ZB, AL, T );
    
    ## if B_ does not vanish
    if not IsZero( B_ ) then
        return fail;
    fi;
    
    T := CertainRows( T, [ 1 .. NrColumns( A ) ] );
    
    ## check assertion
    Assert( 5, IsZero( DecideZeroColumns( B + A * T, BL ) ) );
    
    return -T;
    
end );
##  ]]></Listing>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##  <#GAPDoc Label="LeftInverse:method">
##  <ManSection>
##    <Meth Arg="RI" Name="LeftInverse" Label="for matrices"/>
##    <Returns>a &homalg; matrix or fail</Returns>
##    <Description>
##      The left inverse of the matrix <A>RI</A>. The lazy version of this operation is
##      <Ref Meth="LeftInverseLazy" Label="for matrices"/>.
##      (&see; <Ref Oper="RightDivide" Label="for pairs of matrices"/>)
##    <Listing Type="Code"><![CDATA[
InstallMethod( LeftInverse,
        "for homalg matrices",
        [ IsHomalgMatrix ],
        
  function( RI )
    local Id, LI;
    
    Id := HomalgIdentityMatrix( NrColumns( RI ), HomalgRing( RI ) );
    
    LI := RightDivide( Id, RI );	## ( cf. [BR08, Subsection 3.1.3] )
    
    ## CAUTION: for the following SetXXX RightDivide is assumed
    ## NOT to be lazy evaluated!!!
    
    SetIsLeftInvertibleMatrix( RI, IsHomalgMatrix( LI ) );
    
    if IsBool( LI ) then
        return fail;
    fi;
    
    if HasIsInvertibleMatrix( RI ) and IsInvertibleMatrix( RI ) then
        SetIsInvertibleMatrix( LI, true );
    else
        SetIsRightInvertibleMatrix( LI, true );
    fi;
    
    SetRightInverse( LI, RI );
    
    SetNrColumns( LI, NrRows( RI ) );
    
    if NrRows( RI ) = NrColumns( RI ) then
        ## a left inverse of a ring element is unique
        ## and coincides with the right inverse
        SetRightInverse( RI, LI );
        SetLeftInverse( LI, RI );
    fi;
    
    return LI;
    
end );
##  ]]></Listing>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##  <#GAPDoc Label="RightInverse:method">
##  <ManSection>
##    <Meth Arg="LI" Name="RightInverse" Label="for matrices"/>
##    <Returns>a &homalg; matrix or fail</Returns>
##    <Description>
##      The right inverse of the matrix <A>LI</A>. The lazy version of this operation is
##      <Ref Meth="RightInverseLazy" Label="for matrices"/>.
##      (&see; <Ref Oper="LeftDivide" Label="for pairs of matrices"/>)
##    <Listing Type="Code"><![CDATA[
InstallMethod( RightInverse,
        "for homalg matrices",
        [ IsHomalgMatrix ],
        
  function( LI )
    local Id, RI;
    
    Id := HomalgIdentityMatrix( NrRows( LI ), HomalgRing( LI ) );
    
    RI := LeftDivide( LI, Id );	## ( cf. [BR08, Subsection 3.1.3] )
    
    ## CAUTION: for the following SetXXX LeftDivide is assumed
    ## NOT to be lazy evaluated!!!
    
    SetIsRightInvertibleMatrix( LI, IsHomalgMatrix( RI ) );
    
    if IsBool( RI ) then
        return fail;
    fi;
    
    if HasIsInvertibleMatrix( LI ) and IsInvertibleMatrix( LI ) then
        SetIsInvertibleMatrix( RI, true );
    else
        SetIsLeftInvertibleMatrix( RI, true );
    fi;
    
    SetLeftInverse( RI, LI );
    
    SetNrRows( RI, NrColumns( LI ) );
    
    if NrRows( LI ) = NrColumns( LI ) then
        ## a right inverse of a ring element is unique
        ## and coincides with the left inverse
        SetLeftInverse( LI, RI );
        SetRightInverse( RI, LI );
    fi;
    
    return RI;
    
end );
##  ]]></Listing>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##
InstallMethod( IsRightInvertibleMatrix,
        "for homalg matrices",
        [ IsHomalgMatrix ],
        
  function( M )
    
    return not IsBool( RightInverse( M ) );
    
end );

##
InstallMethod( IsLeftInvertibleMatrix,
        "for homalg matrices",
        [ IsHomalgMatrix ],
        
  function( M )
    
    return not IsBool( LeftInverse( M ) );
    
end );

##  <#GAPDoc Label="GenerateSameRowModule">
##  <ManSection>
##    <Oper Arg="M, N" Name="GenerateSameRowModule" Label="for pairs of matrices"/>
##    <Returns><C>true</C> or <C>false</C></Returns>
##    <Description>
##      Check if the row span of <A>M</A> and of <A>N</A> are identical or not
##      (&see; <Ref Oper="RightDivide" Label="for pairs of matrices"/>).
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallMethod( GenerateSameRowModule,
        "for homalg matrices",
        [ IsHomalgMatrix, IsHomalgMatrix ],
        
  function( M, N )
    
    return not ( IsBool( RightDivide( N, M ) ) or IsBool( RightDivide( M, N ) ) );
    
end );

##  <#GAPDoc Label="GenerateSameColumnModule">
##  <ManSection>
##    <Oper Arg="M, N" Name="GenerateSameColumnModule" Label="for pairs of matrices"/>
##    <Returns><C>true</C> or <C>false</C></Returns>
##    <Description>
##      Check if the column span of <A>M</A> and of <A>N</A> are identical or not
##      (&see; <Ref Oper="LeftDivide" Label="for pairs of matrices"/>).
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallMethod( GenerateSameColumnModule,
        "for homalg matrices",
        [ IsHomalgMatrix, IsHomalgMatrix ],
        
  function( M, N )
    
    return not ( IsBool( LeftDivide( M, N ) ) or IsBool( LeftDivide( N, M ) ) );
    
end );

##---------------------
##
## the lazy evaluation:
##
##---------------------

##  <#GAPDoc Label="Eval:HasEvalLeftInverse">
##  <ManSection>
##    <Meth Arg="LI" Name="Eval" Label="for matrices created with LeftInverseLazy"/>
##    <Returns>see below</Returns>
##    <Description>
##      In case the matrix <A>LI</A> was created using
##      <Ref Meth="LeftInverseLazy" Label="for matrices"/>
##      then the filter <C>HasEvalLeftInverse</C> for <A>LI</A> is set to true and the method listed below
##      will be used to set the attribute <C>Eval</C>. (&see; <Ref Oper="LeftInverse" Label="for matrices"/>)
##    <Listing Type="Code"><![CDATA[
InstallMethod( Eval,
        "for homalg matrices",
        [ IsHomalgMatrix and HasEvalLeftInverse ],
        
  function( LI )
    local left_inv;
    
    left_inv := LeftInverse( EvalLeftInverse( LI ) );
    
    if IsBool( left_inv ) then
        return false;
    fi;
    
    return Eval( left_inv );
    
end );
##  ]]></Listing>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##  <#GAPDoc Label="Eval:HasEvalRightInverse">
##  <ManSection>
##    <Meth Arg="RI" Name="Eval" Label="for matrices created with RightInverseLazy"/>
##    <Returns>see below</Returns>
##    <Description>
##      In case the matrix <A>RI</A> was created using
##      <Ref Meth="RightInverseLazy" Label="for matrices"/>
##      then the filter <C>HasEvalRightInverse</C> for <A>RI</A> is set to true and the method listed below
##      will be used to set the attribute <C>Eval</C>. (&see; <Ref Oper="RightInverse" Label="for matrices"/>)
##    <Listing Type="Code"><![CDATA[
InstallMethod( Eval,
        "for homalg matrices",
        [ IsHomalgMatrix and HasEvalRightInverse ],
        
  function( RI )
    local right_inv;
    
    right_inv := RightInverse( EvalRightInverse( RI ) );
    
    if IsBool( right_inv ) then
        return false;
    fi;
    
    return Eval( right_inv );
    
end );
##  ]]></Listing>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##
InstallGlobalFunction( BestBasis,		### defines: BestBasis
  function( arg )
    local M, R, RP, nargs, m, n, B, U, V;
    
    if not IsHomalgMatrix( arg[1] ) then
        Error( "expecting a homalg matrix as a first argument, but received ", arg[1], "\n" );
    fi;
    
    M := arg[1];
    
    R := HomalgRing( M );
    
    RP := homalgTable( R );
    
    if IsBound(RP!.BestBasis) then
        
        return CallFuncList( RP!.BestBasis, arg );
        
    elif IsBound(RP!.RowReducedEchelonForm) then
        
        nargs := Length( arg );
        
        m := NrRows( M );
        n := NrColumns( M );
        
        if nargs > 1 and IsHomalgMatrix( arg[2] ) then ## not BestBasis( M, "", V )
            B := RowReducedEchelonForm( M, arg[2] );
        else
            B := RowReducedEchelonForm( M );
        fi;
        
        if nargs > 2 and IsHomalgMatrix( arg[3] ) then ## not BestBasis( M, U, "" )
            B := ColumnReducedEchelonForm( B, arg[3] );
        else
            B := ColumnReducedEchelonForm( B );
        fi;
        
        if m - NrRows( B ) = 0 and n - NrColumns( B ) = 0 then
            return B;
        elif m - NrRows( B ) = 0 and n - NrColumns( B ) > 0 then
            return UnionOfColumns( B, HomalgZeroMatrix( m, n - NrColumns( B ), R ) );
        elif m - NrRows( B ) > 0 and n - NrColumns( B ) = 0 then
            return UnionOfRows( B, HomalgZeroMatrix( m - NrRows( B ), n, R ) );
        else
            return DiagMat( [ B, HomalgZeroMatrix( m - NrRows( B ), n - NrColumns( B ), R ) ] );
        fi;
        
    fi;
    
    TryNextMethod( );
    
end );

##
InstallGlobalFunction( SimplerEquivalentMatrix,	### defines: SimplerEquivalentMatrix (BetterGenerators) (incomplete)
  function( arg )
    local M, R, RP, nargs, compute_U, compute_V, compute_UI, compute_VI,
          nar_U, nar_V, nar_UI, nar_VI, MM, m, n, finished, U, V, UI, VI, u, v,
          barg, one, clean_rows, unclean_rows, clean_columns, unclean_columns,
          M_orig, modified, eliminate_units, unit_free, a;
    
    if not IsHomalgMatrix( arg[1] ) then
        Error( "expecting a homalg matrix as a first argument, but received ", arg[1], "\n" );
    fi;
    
    M := arg[1];
    
    R := HomalgRing( M );
    
    RP := homalgTable( R );
  
    if IsBound(RP!.SimplerEquivalentMatrix) then
        return RP!.SimplerEquivalentMatrix( arg );
    fi;
    
    nargs := Length( arg );
    
    if nargs = 1 then
        ## SimplerEquivalentMatrix(M)
        compute_U := false;
        compute_V := false;
        compute_UI := false;
        compute_VI := false;
    elif nargs = 2 and IsHomalgMatrix( arg[2] ) then
        ## SimplerEquivalentMatrix(M,V)
        compute_U := false;
        compute_V := true;
        compute_UI := false;
        compute_VI := false;
        nar_V := 2;
    elif nargs = 3 and IsHomalgMatrix( arg[2] ) and IsString( arg[3] ) then
        ## SimplerEquivalentMatrix(M,VI,"")
        compute_U := false;
        compute_V := false;
        compute_UI := false;
        compute_VI := true;
        nar_VI := 2;
    elif nargs = 6 and IsHomalgMatrix( arg[2] ) and IsHomalgMatrix( arg[3] )
      and IsString( arg[4] ) and IsString( arg[5] ) and IsString( arg[6] ) then
        ## SimplerEquivalentMatrix(M,U,UI,"","","")
        compute_U := true;
        compute_V := false;
        compute_UI := true;
        compute_VI := false;
        nar_U := 2;
        nar_UI := 3;
    elif nargs = 5 and IsHomalgMatrix( arg[2] ) and IsHomalgMatrix( arg[3] )
      and IsString( arg[4] ) and IsString( arg[5] ) then
        ## SimplerEquivalentMatrix(M,V,VI,"","")
        compute_U := false;
        compute_V := true;
        compute_UI := false;
        compute_VI := true;
        nar_V := 2;
        nar_VI := 3;
    elif nargs = 4 and IsHomalgMatrix( arg[2] ) and IsHomalgMatrix( arg[3] )
      and IsString( arg[4] ) then
        ## SimplerEquivalentMatrix(M,UI,VI,"")
        compute_U := false;
        compute_V := false;
        compute_UI := true;
        compute_VI := true;
        nar_UI := 2;
        nar_VI := 3;
    elif nargs = 5 and IsHomalgMatrix( arg[2] ) and IsHomalgMatrix( arg[3] )
      and IsHomalgMatrix( arg[4] ) and IsHomalgMatrix( arg[5] ) then
        ## SimplerEquivalentMatrix(M,U,V,UI,VI)
        compute_U := true;
        compute_V := true;
        compute_UI := true;
        compute_VI := true;
        nar_U := 2;
        nar_V := 3;
        nar_UI := 4;
        nar_VI := 5;
    elif IsHomalgMatrix( arg[2] ) and IsHomalgMatrix( arg[3] ) then
        ## SimplerEquivalentMatrix(M,U,V)
        compute_U := true;
        compute_V := true;
        compute_UI := false;
        compute_VI := false;
        nar_U := 2;
        nar_V := 3;
    else
        Error( "Wrong input!\n" );
    fi;
    
    m := NrRows( M );
    n := NrColumns( M );
    
    finished := false;
    
    #=====# begin of the core procedure #=====#
    
    if compute_U then
        U := HomalgIdentityMatrix( m, R );
    fi;
    
    if compute_V then
        V := HomalgIdentityMatrix( n, R );
    fi;
    
    if compute_UI then
        UI := HomalgIdentityMatrix( m, R );
    fi;
    
    if compute_VI then
        VI := HomalgIdentityMatrix( n, R );
    fi;
    
    if IsZero( M ) then
        finished := true;
    fi;
    
    if not finished
       and ( IsBound( RP!.BestBasis )
             or IsBound( RP!.RowReducedEchelonForm )
             or IsBound( RP!.ColumnReducedEchelonForm ) ) then
        
        if compute_U or compute_UI then
            U := HomalgVoidMatrix( R );
        fi;
        
        if compute_V or compute_VI then
            V := HomalgVoidMatrix( R );
        fi;
        
        if not ( compute_U or compute_UI or compute_V or compute_VI ) then
            barg := [ M ];
        elif ( compute_U or compute_UI ) and not ( compute_V or compute_VI ) then
            barg := [ M, U ];
        elif ( compute_V or compute_VI ) and not ( compute_U or compute_UI ) then
            barg := [ M, "", V ];
        else
            barg := [ M, U, V ];
        fi;
        
        M := CallFuncList( BestBasis, barg );
        
        ## FIXME:
        #if ( compute_V or compute_VI ) then
        #    if IsList( V ) and V <> [] and IsString( V[1] ) then
        #        if LowercaseString( V[1]{[1..3]} ) = "inv" then
        #            VI := V[2];
        #            if compute_V then
        #                V := LeftInverse( VI, arg[1], "NO_CHECK" );
        #            fi;
        #        else
        #            Error( "Cannot interpret the first string in V ", V[1], "\n" );
        #        fi;
        #    fi;
        #fi;
        
        if compute_UI then
            UI := RightInverseLazy( U );
        fi;
        
        if compute_VI then
            VI := LeftInverseLazy( V ); ## this is indeed a LeftInverse
        fi;
        
        ## don't compute a "basis" here, since it is not clear if to do it for rows or for columns!
        ## this differs from the Maple code, where we only worked with left modules
        
        if IsEmptyMatrix( M ) then
            finished := true;
        fi;
        
    fi;
    
    if not finished and
       ( ( ( compute_V or compute_VI ) and not ( compute_U or compute_UI ) ) or
         ( ( compute_U or compute_UI ) and not ( compute_V or compute_VI ) ) ) then
        
        M := GetRidOfRowsAndColumnsWithUnits( M );
        
        if compute_U then
            U := M[1] * U;
        fi;
        
        if compute_UI then
            UI := UI * M[2];
        fi;
        
        if compute_VI then
            VI := M[4] * VI;
        fi;
        
        if compute_V then
            V := V * M[5];
        fi;
        
        M := M[3];
        
        if IsEmptyMatrix( M ) then
            finished := true;
        fi;
        
    elif not finished and
      not ( IsBound( RP!.BestBasis )
            or IsBound( RP!.RowReducedEchelonForm )
            or IsBound( RP!.ColumnReducedEchelonForm ) ) then
        
        M_orig := M;
        
        MM := MutableCopyMat( M );
        
        modified := false;
        
        if IsIdenticalObj( MM, M ) then
            Error( "unable to get a real copy of the matrix\n" );
        fi;
        
        ## CAUTION: since MM is mutable the code below
        ##          should be aware of not introducing units
        if HasIsUnitFree( M ) and IsUnitFree( M ) then
            SetIsUnitFree( MM, true );
        fi;
        
        M := MM;
        
        m := NrRows( M );
        n := NrColumns( M );
        
        if compute_U then
            U := HomalgInitialIdentityMatrix( m, R );
        fi;
        if compute_UI then
            UI := HomalgInitialIdentityMatrix( m, R );
        fi;
        
        if compute_V then
            V := HomalgIdentityMatrix( n, R );
        fi;
        if compute_VI then
            VI := HomalgIdentityMatrix( n, R );
        fi;
        
        one := One( R );
        
        clean_rows := [ ];
        unclean_rows := [ 1 .. m ];
        clean_columns := [ ];
        unclean_columns := [ 1 .. n ];
        
        eliminate_units := function( arg )
            local pos, i, j, r, q, v, vi, u, ui;
            
            unit_free := true;
            
            if Length( arg ) > 0 then
                pos := arg[1];
            else
                pos := GetUnitPosition( M, clean_columns );
            fi;
            
            if pos = fail then
                clean_rows := GetCleanRowsPositions( M, clean_columns );
                unclean_rows := Filtered( [ 1 .. m ], a -> not a in clean_rows );
                
                return clean_columns;
            else
                modified := true;
                unit_free := false;
            fi;
            
            while true do
                i := pos[1];
                j := pos[2];
                
                Add( clean_columns, j );
                Remove( unclean_columns, Position( unclean_columns, j ) );
                
                ## divide the i-th row by the unit M[i][j]
                
                r := M[ i, j ];
                
                if not IsOne( r ) then
                    
                    M := DivideRowByUnit( M, i, r, j );
                    
                    if compute_U then
                        U := DivideRowByUnit( U, i, r, 0 );
                    fi;
                    
                    if compute_UI then
                        q := one / r;
                        UI := DivideColumnByUnit( UI, i, q, 0 );
                    fi;
                fi;
                
                ## cleanup the i-th row
                
                v := HomalgInitialIdentityMatrix( n, R );
                
                if compute_VI then
                    vi := HomalgInitialIdentityMatrix( n, R );
                else
                    vi := "";
                fi;
                
                CopyRowToIdentityMatrix( M, i, [ v, vi ], j );
                
                if compute_V then
                    ResetFilterObj( v, IsMutable );
                    V := V * v;
                fi;
                
                if compute_VI then
                    ResetFilterObj( vi, IsMutable );
                    VI := vi * VI;
                fi;
                
                ## caution: M will now have two attributes EvalCompose and Eval
                M := M * v;
                
                ## attributes do not get saved for mutable objects since GAP 4.11
                ## so we MUST evaluate M before making it mutable
                Eval( M ); SetIsMutableMatrix( M, true );
                
                ## cleanup the j-th column
                
                if compute_U then
                    u := HomalgInitialIdentityMatrix( NrRows( U ), R );
                else
                    u := "";
                fi;
                
                if compute_UI then
                    ui := HomalgInitialIdentityMatrix( NrColumns( UI ), R );
                else
                    ui := "";
                fi;
                
                if compute_U or compute_UI then
                    CopyColumnToIdentityMatrix( M, j, [ u, ui ], i );
                fi;
                
                if compute_U then
                    ResetFilterObj( u, IsMutable );
                    ResetFilterObj( U, IsMutable );
                    U := u * U;
                fi;
                
                if compute_UI then
                    ResetFilterObj( ui, IsMutable );
                    ResetFilterObj( UI, IsMutable );
                    UI := UI * ui;
                fi;
                
                # an M := u * M would simply cause:
                M := SetColumnToZero( M, i, j );
                
                pos := GetUnitPosition( M, clean_columns );
                
                if pos = fail then
                    break;
                fi;
                
                ## attributes do not get saved for mutable objects since GAP 4.11
                ## so we MUST evaluate M before making it mutable
                Eval( M ); SetIsMutableMatrix( M, true );
                
                if compute_U then
                    U := MutableCopyMat( U );
                fi;
                
                if compute_UI then
                    UI := MutableCopyMat( UI );
                fi;
                
            od;
            
            clean_rows := GetCleanRowsPositions( M, clean_columns );
            unclean_rows := Filtered( [ 1 .. m ], a -> not a in clean_rows );
            
            return clean_columns;
        end;
        
        unit_free := false;
        
        while not unit_free do
            
            ## don't compute a "basis" here, since it is not clear if to do it for rows or for columns!
            ## this differs from the Maple code, where we only worked with left modules
            
            m := NrRows( M );
            n := NrColumns( M );
            
            ## eliminate_units alters unit_free
            eliminate_units();
            
            ## FIXME: add heuristics
            
        od;
        
        if not modified then
            M := M_orig;
        fi;
        
        SetIsUnitFree( M, true );
        MakeImmutable( M );
        
        if IsEmptyMatrix( M ) then
            finished := true;
        fi;
        
    fi;
    
    if compute_U then
        SetPreEval( arg[nar_U], U );
        ResetFilterObj( arg[nar_U], IsVoidMatrix );
        Assert( 6, IsHomalgMatrix( LeftDivide( M, U * arg[1] ) ) );
    fi;
    
    if compute_V then
        SetPreEval( arg[nar_V], V );
        ResetFilterObj( arg[nar_V], IsVoidMatrix );
        Assert( 6, IsHomalgMatrix( RightDivide( arg[1] * V, M ) ) );
    fi;
    
    if compute_UI then
        if not IsBound( UI ) then
            UI := HomalgIdentityMatrix( NrRows( M ), R );
        fi;
        SetPreEval( arg[nar_UI], UI );
        ResetFilterObj( arg[nar_UI], IsVoidMatrix );
        Assert( 6, IsHomalgMatrix( LeftDivide( arg[1], UI * M ) ) );
    fi;
    
    if compute_VI then
        if not IsBound( VI ) then
            VI := HomalgIdentityMatrix( NrColumns( M ), R );
        fi;
        SetPreEval( arg[nar_VI], VI );
        ResetFilterObj( arg[nar_VI], IsVoidMatrix );
        Assert( 6, IsHomalgMatrix( RightDivide( M * VI, arg[1] ) ) );
    fi;
    
    return M;
    
end );

##  <#GAPDoc Label="SimplifyHomalgMatrixByLeftAndRightMultiplicationWithInvertibleMatrices">
##  <ManSection>
##    <Oper Arg="M" Name="SimplifyHomalgMatrixByLeftAndRightMultiplicationWithInvertibleMatrices" Label="for matrices"/>
##    <Returns>a list of 5 &homalg; matrices</Returns>
##    <Description>
##      The input is a &homalg; matrix <A>M</A>.
##      The output is a 5-tuple of &homalg; matrices <A>S</A>, <A>U</A>, <A>V</A>, <A>UI</A>, <A>VI</A>,
##      such that <A> U M V = S</A>. Moreover, <A>U</A> and <A>V</A> are invertible with inverses <A>UI</A>, <A>VI</A>, respectively.
##      The idea is that the matrix <A>S</A> should look "simpler" than <A>M</A>.
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallGlobalFunction( SimplifyHomalgMatrixByLeftAndRightMultiplicationWithInvertibleMatrices,
    function( M )
        local ring, U, V, UI, VI, S;
        
        ring := HomalgRing( M );
        
        U := HomalgVoidMatrix( ring );
        
        V := HomalgVoidMatrix( ring );
        
        UI := HomalgVoidMatrix( ring );
        
        VI := HomalgVoidMatrix( ring );
        
        S := SimplerEquivalentMatrix( M, U, V, UI, VI );
        
        return [ S, U, V, UI, VI ];
        
end );

##  <#GAPDoc Label="SimplifyHomalgMatrixByLeftMultiplicationWithInvertibleMatrix">
##  <ManSection>
##    <Oper Arg="M" Name="SimplifyHomalgMatrixByLeftMultiplicationWithInvertibleMatrix" Label="for matrices"/>
##    <Returns>a list of 3 &homalg; matrices</Returns>
##    <Description>
##      The input is a &homalg; matrix <A>M</A>.
##      The output is a 3-tuple of &homalg; matrices <A>S</A>, <A>T</A>, <A>TI</A>
##      such that <A> T M = S</A>. Moreover, <A>T</A> is invertible with inverse <A>TI</A>.
##      The idea is that the matrix <A>S</A> should look "simpler" than <A>M</A>.
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallGlobalFunction( SimplifyHomalgMatrixByLeftMultiplicationWithInvertibleMatrix,
    function( M )
        local R, RP, T, S;
        
        R := HomalgRing( M );
        
        RP := homalgTable( R );
        
        if IsBound(RP!.RowReducedEchelonForm) or IsBound(RP!.ColumnReducedEchelonForm) then
            
            T := HomalgVoidMatrix( R );
            
            S := RowReducedEchelonForm( M, T );
            
        else
            
            T := HomalgIdentityMatrix( NrRows( M ), R );
            
            S := M;
            
        fi;
        
        return [ S, T, LeftInverseLazy( T ) ];
        
end );

##  <#GAPDoc Label="SimplifyHomalgMatrixByRightMultiplicationWithInvertibleMatrix">
##  <ManSection>
##    <Oper Arg="M" Name="SimplifyHomalgMatrixByRightMultiplicationWithInvertibleMatrix" Label="for matrices"/>
##    <Returns>a list of 3 &homalg; matrices</Returns>
##    <Description>
##      The input is a &homalg; matrix <A>M</A>.
##      The output is a 3-tuple of &homalg; matrices <A>S</A>, <A>T</A>, <A>TI</A>
##      such that <A> M T = S</A>. Moreover, <A>T</A> is invertible with inverse <A>TI</A>.
##      The idea is that the matrix <A>S</A> should look "simpler" than <A>M</A>.
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallGlobalFunction( SimplifyHomalgMatrixByRightMultiplicationWithInvertibleMatrix,
    function( M )
        local R, RP, T, S;
        
        R := HomalgRing( M );
        
        RP := homalgTable( R );
        
        if IsBound(RP!.RowReducedEchelonForm) or IsBound(RP!.ColumnReducedEchelonForm) then
            
            T := HomalgVoidMatrix( R );
            
            S := ColumnReducedEchelonForm( M, T );
            
        else
            
            T := HomalgIdentityMatrix( NrColumns( M ), R );
            
            S := M;
            
        fi;
        
        return [ S, T, LeftInverseLazy( T ) ];
        
end );
