#############################################################################
##
##  This file is part of GAP, a system for computational discrete algebra.
##
##  SPDX-License-Identifier: GPL-2.0-or-later
##
##  Copyright of GAP belongs to its developers, whose names are too numerous
##  to list here. Please refer to the COPYRIGHT file for details.
##


#############################################################################
##
##  This file contains some generic methods for the vector/matrix object
##  interface defined in 'matobj1.gd' and 'matobj2.gd'.
##


#############################################################################
##
##  <#GAPDoc Label="MatrixObjCompare">
##  <ManSection>
##  <Heading>Comparison of Vector and Matrix Objects</Heading>
##  <Oper Name="\=" Arg='v1, v2' Label="for two vector objects"/>
##  <Oper Name="\=" Arg='M1, M2' Label="for two matrix objects"/>
##  <Oper Name="\&lt;" Arg='v1, v2' Label="for two vector objects"/>
##  <Oper Name="\&lt;" Arg='M1, M2' Label="for two matrix objects"/>
##
##  <Description>
##  Two vector objects in <Ref Filt="IsList"/> are equal if they are equal as
##  lists.
##  Two matrix objects in <Ref Filt="IsList"/> are equal if they are equal as
##  lists.
##  <P/>
##  Two vector objects of which at least one is not in <Ref Filt="IsList"/>
##  are equal with respect to <Ref Oper="\="/> if they have the same
##  <Ref Attr="ConstructingFilter" Label="for a vector object"/> value,
##  the same <Ref Attr="BaseDomain" Label="for a vector object"/> value,
##  the same length,
##  and the same entries.
##  <P/>
##  Two matrix objects of which at least one is not in <Ref Filt="IsList"/>
##  are equal with respect to <Ref Oper="\="/> if they have the same
##  <Ref Attr="ConstructingFilter" Label="for a matrix object"/> value,
##  the same <Ref Attr="BaseDomain" Label="for a matrix object"/> value,
##  the same dimensions,
##  and the same entries.
##  <P/>
##  We do <E>not</E> state a general rule how vector and matrix objects
##  shall behave w.r.t. the comparison by <Ref Oper="\&lt;"/>.
##  Note that a <Q>row lexicographic order</Q> would be quite unnatural
##  for matrices that are internally represented via a list of columns.
##  <P/>
##  Note that the operations <Ref Oper="\="/> and <Ref Oper="\&lt;"/>
##  are used to form sorted lists and sets of objects,
##  see for example <Ref Oper="Sort"/> and <Ref Oper="Set"/>.
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallMethod( \=,
    "for two vector objects",
    [ IsVectorObj, IsVectorObj ],
    function( v1, v2 )
    if IsList( v1 ) and IsList( v2 ) then
      TryNextMethod();
    fi;
    return ConstructingFilter( v1 ) = ConstructingFilter( v2 ) and
           BaseDomain( v1 ) = BaseDomain( v2 ) and
           Length( v1 ) = Length( v2 ) and
           ForAll( [ 1 .. Length( v1 ) ], i -> v1[i] = v2[i] );
    end );

InstallMethod( \=,
    "for two matrix objects",
    [ IsMatrixObj, IsMatrixObj ],
    function( M1, M2 )
    return ConstructingFilter( M1 ) = ConstructingFilter( M2 ) and
           BaseDomain( M1 ) = BaseDomain( M2 ) and
           NumberRows( M1 ) = NumberRows( M2 ) and
           NumberColumns( M1 ) = NumberColumns( M2 ) and
           ForAll( [ 1 .. NumberRows( M1 ) ],
                   i -> ForAll( [ 1 .. NumberColumns( M1 ) ],
                                j -> M1[i,j] = M2[i,j] ) );
    end );

InstallMethod( Unpack,
    "generic method for matrix objects",
    [ IsMatrixObj ],
    function(M)
      return List([1..NrRows(M)], row -> List([1..NrCols(M)], col -> M[row,col]));
    end );

InstallMethod( OneOfBaseDomain,
    "generic method for IsVectorObj",
    [ IsVectorObj ],
    v -> One( BaseDomain( v ) ) );

InstallMethod( ZeroOfBaseDomain,
    "generic method for IsVectorObj",
    [ IsVectorObj ],
    v -> Zero( BaseDomain( v ) ) );

InstallMethod( OneOfBaseDomain,
    "generic method for IsMatrixOrMatrixObj",
    [ IsMatrixOrMatrixObj ],
    M -> One( BaseDomain( M ) ) );

InstallMethod( ZeroOfBaseDomain,
    "generic method for IsMatrixOrMatrixObj",
    [ IsMatrixOrMatrixObj ],
    M -> Zero( BaseDomain( M ) ) );


InstallMethod( WeightOfVector,
    "generic method for vector objects",
    [ IsVectorObj ],
  function(v)
    local i,n;
    n := 0;
    for i in [1..Length(v)] do
        if not IsZero(v[i]) then
            n := n + 1;
        fi;
    od;
    return n;
  end );

InstallMethod( DistanceOfVectors,
    "generic method for two vector objects",
  [IsVectorObj, IsVectorObj],
  function( v, w)
    local i,n;
    if Length(v) <> Length(w) then
        Error("vectors must have same length");
        return fail;
    fi;
    n := 0;
    for i in [1..Length(v)] do
        if v[i] <> w[i] then
            n := n + 1;
        fi;
    od;
    return n;
  end );

#
# TODO: possibly rename the following
#
InstallGlobalFunction( DefaultVectorRepForBaseDomain,
function( basedomain )
    if IsFinite(basedomain) and IsField(basedomain) and Size(basedomain) = 2 then
        return IsGF2VectorRep;
    elif IsFinite(basedomain) and IsField(basedomain) and Size(basedomain) <= 256 then
        return Is8BitVectorRep;
##  This should be enabled when the code for
##       IsZmodnZVectorRep/IsZmodnZMatrixRep
##  is finished and tested:
##      elif IsFinite(basedomain) and IsZmodnZObj(Zero(basedomain)) then
##        return IsZmodnZVectorRep;
    fi;
    return IsPlistVectorRep;
end);
InstallGlobalFunction( DefaultMatrixRepForBaseDomain,
function( basedomain )
    if IsFinite(basedomain) and IsField(basedomain) and Size(basedomain) = 2 then
        return IsGF2MatrixRep;
    elif IsFinite(basedomain) and IsField(basedomain) and Size(basedomain) <= 256 and IsFFECollection(basedomain) then
        return Is8BitMatrixRep;
##  This should be enabled when the code for
##       IsZmodnZVectorRep/IsZmodnZMatrixRep
##  is finished and tested:
##      elif IsFinite(basedomain) and IsZmodnZObj(Zero(basedomain)) then
##        return IsZmodnZMatrixRep;
    fi;
    return IsPlistMatrixRep;
end);



# methods to create vector objects

#############################################################################
##
#O  Vector( <filt>, <R>, <list> )
#O  Vector( <filt>, <R>, <vec> )
#O  Vector( <R>, <list> )
#O  Vector( <R>, <vec> )
#O  Vector( <list>, <vec> )
#O  Vector( <vec1>, <vec2> )
##
##  Compute the missing arguments for 'NewVector' and then call it.
##
InstallMethod( Vector,
    [ IsOperation, IsSemiring, IsList ],
    NewVector );

InstallMethod( Vector,
    [ IsOperation, IsSemiring, IsVectorObj ],
    function( rep, R, v )
    if IsPlistRep( v ) then
      TryNextMethod();
    fi;
    return NewVector( rep, R, Unpack( v ) );
    end );

InstallMethod( Vector,
    [ IsSemiring, IsList ],
    { R, l } -> NewVector( DefaultVectorRepForBaseDomain( R ), R, l ) );

InstallMethod( Vector,
    [ IsSemiring, IsVectorObj ],
    function( R, v )
    if IsPlistRep( v ) then
      TryNextMethod();
    fi;
    return NewVector( DefaultVectorRepForBaseDomain( R ),
                          R, Unpack( v ) );
    end );

InstallMethod( Vector,
    [ IsList, IsVectorObj ],
    { l, example } -> NewVector( ConstructingFilter( example ),
                                 BaseDomain( example ), l ) );

InstallMethod( Vector,
    [ IsVectorObj, IsVectorObj ],
    function( v, example )
    if IsPlistRep( v ) then
      TryNextMethod();
    fi;
    return NewVector( ConstructingFilter( example ),
                      BaseDomain( example ), Unpack( v ) );
    end );

InstallMethod( Vector,
    [ IsList ],
function(l)
  local dom;
  dom := DefaultScalarDomainOfMatrixList([[l]]);
  return NewVector(DefaultVectorRepForBaseDomain(dom), dom, l);
end);

# The following method is used for example
# in code dealing with elements of 'AlgebraicExtension's.
# (Note that we want to return a vector with the same mutability status
# as the given vector, and we need not copy it if it is mutable.)
InstallOtherMethod( Vector,
    [ IsList and IsCyclotomicCollection, IsList and IsCyclotomicCollection ],
    { v, example } -> v );


# The following method is used when one deals with matrices that are not in
# 'IsPlistep' but whose 'CompatibleVectorFilter' is 'IsPlistRep'.
# Currently 'IsBlockMatrixRep' is an example for that.
InstallTagBasedMethod( NewVector,
  IsPlistRep,
  { filter, basedomain, list } -> Unpack( list ) );


#############################################################################
##
#M  ZeroVector( <filt>, <R>, <len> )
#M  ZeroVector( <R>, <len> )
##
InstallMethod( ZeroVector,
  [ IsOperation, IsSemiring, IsInt ],
  function( rep, basedomain, len )
    if len < 0 then Error("ZeroVector: length must be non-negative"); fi;
    return NewZeroVector( rep, basedomain, len );
  end );

InstallMethod( ZeroVector,
  [ IsSemiring, IsInt ],
  function( basedomain, len )
    if len < 0 then Error("ZeroVector: length must be non-negative"); fi;
    return NewZeroVector( DefaultVectorRepForBaseDomain(basedomain), basedomain, len );
  end );


#############################################################################
##
#M  NewZeroVector( <filt>, <R>, <n> )
##
##  The default method is based on 'NewVector'.
##  Note that 'NewVector' checks for consistency of <filt> and <R>.
##  The drawback of the default method is that 'NewVector' will make a
##  shallow copy of the list.
##
InstallTagBasedMethod( NewZeroVector,
  function( filter, R, n )
    return NewVector( filter, R, ListWithIdenticalEntries( n, Zero( R ) ) );
  end );


#############################################################################
##
#M  ZeroVector( <len>, <v> )
#M  ZeroVector( <len>, <M> )
##
InstallMethod( ZeroVector,
    "for length and vector object",
    [ IsInt, IsVectorObj ],
    { len, v } -> NewZeroVector( ConstructingFilter( v ),
                                 BaseDomain( v ), len ) );

InstallMethod( ZeroVector,
    "for length and matrix or matrix object",
    [ IsInt, IsMatrixOrMatrixObj ],
    { len, M } -> NewZeroVector( CompatibleVectorFilter( M ),
                                 BaseDomain( M ), len ) );

InstallMethod( CompatibleVectorFilter,
    "for IsMatrix",
    [ IsMatrix ],
    {} -> -RankFilter(IsMatrix),
    M -> IsPlistRep );

# compatibility method for representations as list of elements
# (covers the cases of the second argument in `IsRowVector` or `IsMatrix`)
InstallOtherMethod( ZeroVector, "for an integer and a plain list",
  [ IsInt, IsPlistRep ],
  -1, # rank lower than default as only fallback
  function( l, t )
    return ListWithIdenticalEntries( l, ZeroOfBaseDomain( t ) );
  end);


#############################################################################
##
#M  Matrix( <filt>, <R>, <list>, <ncols> )
#M  Matrix( <filt>, <R>, <list> )
#M  Matrix( <filt>, <R>, <M> )
#M  Matrix( <R>, <list>, <ncols> )
#M  Matrix( <R>, <list> )
#M  Matrix( <R>, <M> )
#M  Matrix( <list>, <ncols>, <M> )
#M  Matrix( <list>, <M> )
#M  Matrix( <M1>, <M2> )
#M  Matrix( <list>, <ncols> )
#M  Matrix( <list> )
##
##  Compute the missing arguments for 'NewMatrix' and then call it.
##
InstallMethod( Matrix,
  [ IsOperation, IsSemiring, IsList, IsInt ],
  { filt, R, list, nrCols } -> NewMatrix( filt, R, nrCols, list ) );

InstallMethod( Matrix,
    [ IsOperation, IsSemiring, IsList ],
    function( filt, R, list )
    if Length( list ) = 0 then
      Error( "<list> must be not empty; ",
             "to create empty matrices, please specify nrCols");
    fi;
    return NewMatrix( filt, R, Length( list[1] ), list );
  end );

InstallMethod( Matrix,
    [ IsOperation, IsSemiring, IsMatrixObj ],
    { filt, R, mat } -> NewMatrix( filt, R, NrCols( mat ), Unpack( mat ) ) );
# TODO: can we do better? encourage MatrixObj implementors to overload this?

InstallMethod( Matrix,
    [ IsSemiring, IsList, IsInt ],
    { R, list, nrCols } -> NewMatrix( DefaultMatrixRepForBaseDomain( R ),
                                      R, nrCols, list ) );

InstallMethod( Matrix,
    [ IsSemiring, IsList ],
# rank higher than a method in the semigroups package, which otherwise jumps
# in and causes an error when testing
# line 318 of semigroups-3.4.0/gap/elements/semiringmat.gi
20,
function( R, list )
local l;
    if Length(list) = 0 then
      TryNextMethod();
    fi;
    l:=Length(list[1]);
    if ForAny([2..Length(list)],x->Length(list[x])<>l) then
      TryNextMethod();
    fi;
    return NewMatrix( DefaultMatrixRepForBaseDomain( R ),
                      R, l, list );
end );

InstallMethod( Matrix,
    [ IsSemiring, IsMatrixObj ],
    { R, M } -> NewMatrix( DefaultMatrixRepForBaseDomain( R ),
                           R, NrCols( M ), Unpack( M ) ) );
# TODO: can we do better? encourage MatrixObj implementors to overload this?

#
#
#
InstallMethod( Matrix,
  [IsList, IsInt],
  function( list, nrCols )
    local basedomain, rep;
    if Length(list) = 0 then Error("list must be not empty"); fi;
    if Length(list[1]) = 0 then Error("list[1] must be not empty, please specify base domain explicitly"); fi;
    basedomain := DefaultScalarDomainOfMatrixList([list]);
    rep := DefaultMatrixRepForBaseDomain(basedomain);
    return NewMatrix( rep, basedomain, nrCols, list );
  end );

InstallMethod( Matrix,
  [IsList],
  function( list )
    local rep, R;
    if Length( list ) = 0 then
      Error( "<list> must be not empty" );
    elif Length( list[1] ) = 0 then
      Error( "<list>[1] must be not empty, ",
             "please specify base domain explicitly" );
    fi;
    R:= DefaultScalarDomainOfMatrixList( [ list ] );
    rep := DefaultMatrixRepForBaseDomain( R );
    return NewMatrix( rep, R , Length( list[1] ), list );
  end );

#
# matrix constructors using example objects (as last argument)
#
InstallMethod( Matrix,
  [IsList, IsInt, IsMatrixOrMatrixObj],
  function( list, nrCols, example )
    return NewMatrix( ConstructingFilter(example), BaseDomain(example), nrCols, list );
  end );

InstallMethod( Matrix, "generic convenience method with 2 args",
  [IsList, IsMatrixOrMatrixObj],
  function( list, example )
    if Length(list) = 0 then
        ErrorNoReturn("Matrix: two-argument version not allowed with empty first arg");
    fi;
    if not (IsList(list[1]) or IsVectorObj(list[1])) then
        ErrorNoReturn("Matrix: flat data not supported in two-argument version");
    fi;
    return NewMatrix( ConstructingFilter(example), BaseDomain(example), Length(list[1]), list );
  end );

InstallMethod( Matrix,
  [IsMatrixObj, IsMatrixOrMatrixObj],
    function( mat, example )
    # TODO: can we avoid using Unpack? resp. make this more efficient
    # perhaps adjust NewMatrix to take an IsMatrixOrMatrixObj?
    return NewMatrix( ConstructingFilter(example), BaseDomain(example), NrCols(mat), Unpack(mat) );
  end );


#############################################################################
##
#M  NewZeroMatrix( <filt>, <R>, <m>, <n> )
##
##  The default method is based on 'NewMatrix'.
##  Note that 'NewMatrix' checks for consistency of <filt> and <R>.
##  The drawback of the default method is that 'NewMatrix' will make a
##  shallow copy of the lists.
##
InstallTagBasedMethod( NewZeroMatrix,
  function( filter, basedomain, rows, cols )
    local z, v, m;

    z:= Zero( basedomain );
    v:= ListWithIdenticalEntries( cols, z );
    m:= List( [ 1 .. rows ], i -> ShallowCopy( v ) );
    return NewMatrix( filter, basedomain, cols, m );
  end );

#
#
#
InstallMethod( ZeroMatrix,
  [IsInt, IsInt, IsMatrixOrMatrixObj],
  function( rows, cols, example )
    if rows < 0 or cols < 0 then Error("ZeroMatrix: the number of rows and cols must be non-negative"); fi;
    return NewZeroMatrix( ConstructingFilter(example), BaseDomain(example), rows, cols );
  end );

InstallMethod( ZeroMatrix,
  [IsSemiring, IsInt, IsInt],
  function( basedomain, rows, cols )
    if rows < 0 or cols < 0 then Error("ZeroMatrix: the number of rows and cols must be non-negative"); fi;
    return NewZeroMatrix( DefaultMatrixRepForBaseDomain(basedomain), basedomain, rows, cols );
  end );

InstallMethod( ZeroMatrix,
  [IsOperation, IsSemiring, IsInt, IsInt],
  function( rep, basedomain, rows, cols )
    if rows < 0 or cols < 0 then Error("ZeroMatrix: the number of rows and cols must be non-negative"); fi;
    return NewZeroMatrix( rep, basedomain, rows, cols );
  end );


#############################################################################
##
#M  NewIdentityMatrix( <filt>, <R>, <n> )
##
##  The default method is based on 'NewZeroMatrix'.
##  Note that 'NewZeroMatrix' checks for consistency of <filt> and <R>.
##
InstallTagBasedMethod( NewIdentityMatrix,
  function( filter, basedomain, dim )
    local mat, one, i;

    mat:= NewZeroMatrix( filter, basedomain, dim, dim );
    one:= One( basedomain );
    for i in [ 1 .. dim ] do
      mat[i,i]:= one;
    od;
    return mat;
  end );

#
#
#
InstallMethod( IdentityMatrix,
  [IsInt, IsMatrixOrMatrixObj],
  function( dim, example )
    if dim < 0 then Error("IdentityMatrix: the dimension must be non-negative"); fi;
    return NewIdentityMatrix( ConstructingFilter(example), BaseDomain(example), dim );
  end );

InstallMethod( IdentityMatrix,
  [IsSemiring, IsInt],
  function( basedomain, dim )
    if dim < 0 then Error("IdentityMatrix: the dimension must be non-negative"); fi;
    return NewIdentityMatrix( DefaultMatrixRepForBaseDomain(basedomain), basedomain, dim );
  end );

InstallMethod( IdentityMatrix,
  [IsOperation, IsSemiring, IsInt],
  function( rep, basedomain, dim )
    if dim < 0 then Error("IdentityMatrix: the dimension must be non-negative"); fi;
    return NewIdentityMatrix( rep, basedomain, dim );
  end );


#############################################################################
##
#M  NewCompanionMatrix( <filt>, <pol>, <R> )
##
##  The default method is based on 'NewZeroMatrix'.
##  Note that 'NewZeroMatrix' checks for consistency of <filt> and <R>,
##  and 'SetMatElm' checks for consistency of the matrix and the coefficients
##  of <pol>.
##
InstallTagBasedMethod( NewCompanionMatrix,
  function( filter, pol, basedomain )
    local one, l, n, mat, i;

    one:= One( basedomain );
    l:= CoefficientsOfUnivariatePolynomial( pol );
    n:= Length( l ) - 1;
    if n <= 0 then
      Error( "NewCompanionMatrix: degree of <pol> must be at least 1" );
    elif not IsOne( l[n+1] ) then
      Error( "NewCompanionMatrix: polynomial <pol> is not monic" );
    fi;
    mat:= NewZeroMatrix( filter, basedomain, n, n );
    for i in [ 1 .. n-1 ] do
      mat[i+1,i]:= one;
    od;
    for i in [ 1 .. n ] do
      mat[i,n]:= - l[i];
    od;

    return mat;
  end );

#
#
#
InstallMethod( CompanionMatrix,
    "for a polynomial and a matrix or matrix object",
    [ IsUnivariatePolynomial, IsMatrixOrMatrixObj ],
    function( pol, M )
    return NewCompanionMatrix( ConstructingFilter( M ), pol, BaseDomain( M ) );
    end );

InstallMethod( CompanionMatrix,
    "for a filter, a polynomial, and a semiring",
    [ IsOperation, IsUnivariatePolynomial, IsSemiring ],
    function( filt, pol, R )
    return NewCompanionMatrix( filt, pol, R );
    end );

InstallMethod( CompanionMatrix,
    "for a polynomial and a semiring",
    [ IsUnivariatePolynomial, IsSemiring ],
    function( pol, R )
    return NewCompanionMatrix( DefaultMatrixRepForBaseDomain( R ), pol, R );
    end );

#
#
#
InstallMethod( KroneckerProduct, "for two matrices",
  [ IsMatrixOrMatrixObj, IsMatrixOrMatrixObj ],
  function( A, B )
    local rowsA, rowsB, colsA, colsB, AxB, i, j;

    if not IsIdenticalObj(BaseDomain(A),BaseDomain(B)) then
        ErrorNoReturn("KroneckerProduct: Matrices not over same base domain");
    fi;

    rowsA := NumberRows(A);
    colsA := NumberColumns(A);
    rowsB := NumberRows(B);
    colsB := NumberColumns(B);

    AxB := ZeroMatrix( rowsA * rowsB, colsA * colsB, A );

    # Cache matrices
    # not implemented yet

    for i in [1..rowsA] do
      for j in [1..colsA] do
        CopySubMatrix( A[i,j] * B, AxB,
                [ 1 .. rowsB ], [ rowsB * (i-1) + 1 .. rowsB * i ],
                [ 1 .. colsB ], [ (j-1) * colsB + 1 .. j * colsB ] );
      od;
    od;

    if not IsMutable(A) and not IsMutable(B) then
        MakeImmutable(AxB);
    fi;

    return AxB;
  end );

InstallGlobalFunction( ConcatenationOfVectors,
  function( arg )
    local i,len,pos,res,total;
    if Length( arg ) = 1 and IsList( arg[1] ) then
        arg := arg[1];
    fi;
    if Length(arg) = 0 then
        Error("must have at least one vector to concatenate");
    fi;
    total := Sum(arg,Length);
    res := ZeroVector(total,arg[1]);
    pos := 1;
    for i in [1..Length(arg)] do
        len := Length(arg[i]);
        CopySubVector(arg[i],res,[1..len],[pos..pos+len-1]);
        pos := pos + len;
    od;
    return res;
  end );

InstallMethod( TraceMat,
    "for a matrix or matrix object",
    [ IsMatrixOrMatrixObj ],
    function( M )
    local s, i;

    if NumberRows( M ) <> NumberColumns( M ) then
      Error( "matrix <M> must be square" );
    fi;
    s:= ZeroOfBaseDomain( M );
    for i in [ 1 .. NumberRows( M ) ] do
      s:= s + M[ i, i ];
    od;

    return s;
  end );

InstallMethod( PositionNonZero,
    "generic method for a vector object",
    [ IsVectorObj ],
    function( v )
    local i;

    for i in [ 1 .. Length( v ) ] do
      if not IsZero( v[i] ) then
        return i;
      fi;
    od;
    return i+1;
    end );

InstallMethod( PositionLastNonZero,
    "generic method for a vector object",
    [ IsVectorObj ],
    function( v )
    local i;

    i:= Length( v );
    while i > 0 and IsZero( v[i] ) do
      i:= i-1;
    od;
    return i;
    end );

InstallMethod( ListOp,
  "generic method for a vector object",
  [ IsVectorObj ],
  function(vec)
  local result, i, len;
  len := Length(vec);
  result := [];
  if 0 < len then
    result[len] := vec[len];
    for i in [ 1 .. len - 1 ] do
      result[i] := vec[i];
    od;
  fi;
  return result;
end );
#T delegation question -- use {}? or Unpack?

InstallMethod( ListOp,
  "generic method for a vector object and a function",
  [ IsVectorObj, IsFunction ],
  function(vec,func)
  local result, i, len;
  len := Length(vec);
  result := [];
  if 0 < len then
    result[len] := func(vec[len]);
    for i in [ 1 .. len - 1 ] do
      result[i] := func(vec[i]);
    od;
  fi;
  return result;
end );

InstallMethod( Unpack,
  "generic method for a vector object",
  [ IsVectorObj ],
  ListOp ); ## Potentially slower than a direct implementation,
            ## but avoids code duplication.

InstallOtherMethod( Unpack,
  "generic method for plain lists",
  [ IsRowVector and IsPlistRep ],
  ShallowCopy );

InstallMethod( \{\},
  "generic method for a vector object and a list",
  [ IsVectorObj, IsList ],
    function( v, poss )
    if IsPlistRep( v ) then
      TryNextMethod();
    fi;
    return Vector( Unpack( v ){ poss }, v );
    end );

InstallOtherMethod( ExtractSubVector,
    "generic method for a row vector or vector object and a list",
    [ IsRowVectorOrVectorObj, IsList ],
    { v, l } -> v{ l } );

InstallMethod( ExtractSubMatrix,
    "generic method for a matrix object and two lists",
    [ IsMatrixObj, IsList, IsList ],
    { M, rowpos, colpos } -> Matrix( Unpack( M ){ rowpos }{ colpos }, M ) );

InstallOtherMethod( ExtractSubMatrix,
    "generic method for a matrix and two lists",
    [ IsMatrix, IsList, IsList ],
    { M, rowpos, colpos } -> M{ rowpos }{ colpos } );

InstallMethod( CopySubVector,
    "generic method for row vectors and vector objects",
  [ IsRowVectorOrVectorObj, IsRowVectorOrVectorObj and IsMutable,
    IsList, IsList ],
  function(src, dst, scols, dcols)
    local i;
    if Length( dcols ) <> Length( scols ) then
      ErrorNoReturn( "source and destination index lists must be of equal length" );
    fi;
    for i in [ 1 .. Length( dcols ) ] do
      dst[dcols[i]] := src[scols[i]];
    od;
end );

InstallMethod( CopySubVector,
    "generic method for row vectors",
  [ IsRowVector, IsRowVector and IsMutable, IsList, IsList ],
  function(src, dst, scols, dcols)
    dst{dcols} := src{scols};
end );


#############################################################################
##
#M  ChangedBaseDomain( <v>, <R> )
#M  ChangedBaseDomain( <M>, <R> )
##
InstallMethod( ChangedBaseDomain,
    "for a vector object and a semiring",
    [ IsVectorObj, IsSemiring ],
    { v, R } -> Vector( R, v ) );

InstallMethod( ChangedBaseDomain,
    "for a matrix or matrix object and a semiring",
    [ IsMatrixOrMatrixObj, IsSemiring ],
    { M, R } -> Matrix( R, M ) );


############################################################################
##
#M  Randomize( [Rs, ]v )
#M  Randomize( [Rs, ]M )
##
InstallMethodWithRandomSource( Randomize,
  "for a random source and a vector object",
  [ IsRandomSource, IsVectorObj and IsMutable ],
  function( rs, vec )
    local basedomain, i;
    basedomain := BaseDomain( vec );
    for i in [ 1 .. Length( vec ) ] do
        vec[ i ] := Random( rs, basedomain );
    od;
    return vec;
end );

InstallMethodWithRandomSource( Randomize,
  "for a random source and a mutable matrix or matrix object",
  [ IsRandomSource, IsMatrixOrMatrixObj and IsMutable ],
  function( rs, mat )
    local basedomain, i, j;
    basedomain := BaseDomain( mat );
    for i in [ 1 .. NrRows( mat ) ] do
      for j in [ 1 .. NrCols( mat ) ] do
        mat[i,j]:= Random( rs, basedomain );
      od;
    od;
    return mat;
end );


#############################################################################
##
##  Arithmetical operations for vector objects
##


#############################################################################
##
#M  <v1> + <v2>
#M  <v1> - <v2>
#M  <s> * <v>
#M  <v> * <s>
#M  <v1> * <v2>
#M  <v> / <s>
##
##  <#GAPDoc Label="VectorObj_BinaryArithmetics">
##  <ManSection>
##  <Heading>Binary Arithmetical Operations for Vector Objects</Heading>
##  <Meth Name="\+" Arg="v1, v2" Label="for two vector objects"/>
##  <Meth Name="\-" Arg="v1, v2" Label="for two vector objects"/>
##  <Meth Name="\*" Arg="s, v" Label="for scalar and vector object"/>
##  <Meth Name="\*" Arg="v, s" Label="for vector object and scalar"/>
##  <Meth Name="\*" Arg="v1, v2" Label="for two vector objects"/>
##  <Meth Name="ScalarProduct" Arg="v1, v2" Label="for two vector objects"/>
##  <Meth Name="\/" Arg="v, s" Label="for vector object and scalar"/>
##
##  <Description>
##  The sum and the difference, respectively,
##  of two vector objects <A>v1</A> and <A>v2</A>
##  is a new mutable vector object whose entries are the sums and the
##  differences of the entries of the arguments.
##  <P/>
##  The product of a scalar <A>s</A> and a vector object <A>v</A> (from the
##  left or from the right) is a new mutable vector object whose entries
##  are the corresponding products.
##  <P/>
##  The quotient of a vector object <A>v</A> and a scalar <A>s</A>
##  is a new mutable vector object whose entries are the corresponding
##  quotients.
##  <P/>
##  The product of two vector objects <A>v1</A> and <A>v2</A> as well as the
##  result of <Ref Meth="ScalarProduct" Label="for two vector objects"/> is
##  the standard scalar product of the two arguments
##  (an element of the base domain of the vector objects).
##  <P/>
##  All this is defined only if the vector objects have the same length and
##  are defined over the same base domain and have the same representation,
##  and if the products with the given scalar belong to the base domain;
##  otherwise it is not specified what happens.
##  If the result is a vector object then it has the same representation and
##  the same base domain as the given vector object(s).
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallMethod( \+,
    "for two vector objects",
    [ IsVectorObj, IsVectorObj ],
    function( v1, v2 )
    if IsPlistRep( v1 ) then
      TryNextMethod();
    fi;
    return Vector( Unpack( v1 ) + Unpack( v2 ), v1 );
    end );

InstallMethod( \-,
    "for two vector objects",
    [ IsVectorObj, IsVectorObj ],
    function( v1, v2 )
    if IsPlistRep( v1 ) then
      TryNextMethod();
    fi;
    return Vector( Unpack( v1 ) - Unpack( v2 ), v1 );
    end );

InstallMethod( \*,
    "for two vector objects (standard scalar product)",
    [ IsVectorObj, IsVectorObj ],
    { v1, v2 } -> Sum( [ 1 .. Length( v1 ) ],
                       i -> v1[i] * v2[i],
                       ZeroOfBaseDomain( v1 ) ) );

InstallMethod( \*,
    "for vector object and scalar",
    [ IsVectorObj, IsScalar ],
    function( v, s )
    if IsList( v ) then
      TryNextMethod();
    fi;
    return Vector( Unpack( v ) * s, v );
    end );

InstallMethod( \*,
    "for scalar and vector object",
    [ IsScalar, IsVectorObj ],
    function( s, v )
    if IsList( v ) then
      TryNextMethod();
    fi;
    return Vector( s * Unpack( v ), v );
    end );

InstallMethod( \/,
    "for vector object and scalar",
    [ IsVectorObj, IsScalar ],
    function( v, s )
    if IsPlistRep( v ) then
      TryNextMethod();
    fi;
    return Vector( Unpack( v ) / s, v );
    end );

InstallOtherMethod( ScalarProduct,
    "for two vector objects",
    [ IsVectorObj, IsVectorObj ],
    \* );


#############################################################################
##
#M  AddVector( <dst>, <src>[, <mul>[, <from>, <to>]] )
#M  AddVector( <dst>, <mul>, <src>[, <from>, <to>] )
##
InstallMethod( AddVector,
    "for two vector objects",
    [ IsVectorObj and IsMutable, IsVectorObj ],
    function( dst, src )
    local i;

    for i in [ 1 .. Length( dst ) ] do
      dst[i]:= dst[i] + src[i];
    od;
    end );

InstallMethod( AddVector,
    "for two vector objects and an object",
    [ IsVectorObj and IsMutable, IsVectorObj, IsObject ],
    function( dst, src, c )
    local i;

    for i in [ 1 .. Length( dst ) ] do
      dst[i]:= dst[i] + src[i] * c;
    od;
    end );

InstallMethod( AddVector,
    "for a vector object, an object, a vector object",
    [ IsVectorObj and IsMutable, IsObject, IsVectorObj ],
    function( dst, c, src )
    local i;

    for i in [ 1 .. Length( dst ) ] do
      dst[i]:= dst[i] + c * src[i];
    od;
    end );

InstallMethod( AddVector,
    "for two vector objects, an object, two pos. integers",
    [ IsVectorObj and IsMutable, IsVectorObj, IsObject, IsPosInt, IsPosInt ],
    function( dst, src, c, from, to )
    local i;

    for i in [ from .. to ] do
      dst[i]:= dst[i] + src[i] * c;
    od;
    end );

InstallMethod( AddVector,
    "for a vector object, an object, a vector object, two pos. integers",
    [ IsVectorObj and IsMutable, IsObject, IsVectorObj, IsPosInt, IsPosInt ],
    function( dst, c, src, from, to )
    local i;

    for i in [ from .. to ] do
      dst[i]:= dst[i] + c * src[i];
    od;
    end );


#############################################################################
##
#M  MultVectorLeft( <vec>, <mul>[, <from>, <to>] )
#M  MultVectorRight( <vec>, <mul>[, <from>, <to>] )
##
##  Note that 'MultVector' is declared as a synonym for 'MultVectorLeft' in
##  'lib/listcoef.gd'.
##
InstallMethod( MultVectorLeft,
  "generic method for a mutable vector object, and an object",
  [ IsVectorObj and IsMutable, IsObject ],
  function( v, s )
    local i;
    for i in [1 .. Length(v)] do
      v[i] := s * v[i];
    od;
  end );

InstallMethod( MultVectorRight,
  "generic method for a mutable vector object, and an object",
  [ IsVectorObj and IsMutable, IsObject ],
  function( v, s )
    local i;
    for i in [1 .. Length(v)] do
      v[i] := v[i] * s;
    od;
  end );

InstallMethod( MultVectorLeft,
  "generic method for a mutable vector object, an object, an int, and an int",
  [ IsVectorObj and IsMutable, IsObject, IsInt, IsInt ],
  function( v, s, from, to )
    local i;
    for i in [from .. to] do
      v[i] := s * v[i];
    od;
  end );

InstallMethod( MultVectorRight,
  "generic method for a mutable vector object, an object, an int, and an int",
  [ IsVectorObj and IsMutable, IsObject, IsInt, IsInt ],
  function( v, s, from, to )
    local i;
    for i in [from .. to] do
      v[i] := v[i] * s;
    od;
  end );


#############################################################################
##
##  Arithmetical operations for matrix objects
##


#############################################################################
##
#M  <M1> + <M2>
#M  <M1> - <M2>
#M  <s> * <M>
#M  <M> * <s>
#M  <M1> * <M2>
#M  <M> / <s>
#M  <M> ^ <n>
##
##  <#GAPDoc Label="MatrixObj_BinaryArithmetics">
##  <ManSection>
##  <Heading>Binary Arithmetical Operations for Matrix Objects</Heading>
##  <Meth Name="\+" Arg="M1, M2" Label="for two matrix objects"/>
##  <Meth Name="\-" Arg="M1, M2" Label="for two matrix objects"/>
##  <Meth Name="\*" Arg="s, M" Label="for scalar and matrix object"/>
##  <Meth Name="\*" Arg="M, s" Label="for Matrix object and scalar"/>
##  <Meth Name="\*" Arg="M1, M2" Label="for two matrix objects"/>
##  <Meth Name="\/" Arg="M, s" Label="for matrix object and scalar"/>
##  <Meth Name="\^" Arg="M, n" Label="for matrix object and integer"/>
##
##  <Description>
##  The sum and the difference, respectively,
##  of two matrix objects <A>M1</A> and <A>M2</A>
##  is a new fully mutable matrix object whose entries are the sums and the
##  differences of the entries of the arguments.
##  <P/>
##  The product of a scalar <A>s</A> and a matrix object <A>M</A> (from the
##  left or from the right) is a new fully mutable matrix object whose entries
##  are the corresponding products.
##  <P/>
##  The product of two matrix objects <A>M1</A> and <A>M2</A> is a new fully
##  mutable matrix object; if both <A>M1</A> and <A>M2</A> are in the filter
##  <Ref Filt="IsOrdinaryMatrix"/> then the entries of the result are those
##  of the ordinary matrix product.
##  <P/>
##  The quotient of a matrix object <A>M</A> and a scalar <A>s</A>
##  is a new fully mutable matrix object whose entries are the corresponding
##  quotients.
##  <P/>
##  For a nonempty square matrix object <A>M</A> over an associative
##  base domain, and a positive integer <A>n</A>,
##  <A>M</A><C>^</C><A>n</A> is a fully mutable matrix object whose entries
##  are those of the <A>n</A>-th power of <A>M</A>.
##  If <A>n</A> is zero then <A>M</A><C>^</C><A>n</A> is an identity matrix,
##  and if <A>n</A> is a negative integer and <A>M</A> is invertible then
##  <A>M</A><C>^</C><A>n</A> is the (<C>-</C><A>n</A>)-th power of the
##  inverse of <A>M</A>.
##  <P/>
##  All this is defined only if the matrix objects have the same dimensions and
##  are defined over the same base domain and have the same representation,
##  and if the products with the given scalar belong to the base domain;
##  otherwise it is not specified what happens.
##  If the result is a matrix object then it has the same representation and
##  the same base domain as the given matrix object(s).
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallMethod( \+,
    "for two matrix objects",
    [ IsMatrixObj, IsMatrixObj ],
    -SUM_FLAGS,
    { M1, M2 } -> Matrix( Unpack( M1 ) + Unpack( M2 ), M1 ) );

InstallMethod( \-,
    "for two matrix objects",
    [ IsMatrixObj, IsMatrixObj ],
    -SUM_FLAGS,
    { M1, M2 } -> Matrix( Unpack( M1 ) - Unpack( M2 ), M1 ) );

InstallMethod( \*,
    "for two ordinary matrix objects (ordinary matrix product)",
    [ IsMatrixObj and IsOrdinaryMatrix, IsMatrixObj and IsOrdinaryMatrix ],
    -SUM_FLAGS,
    { M1, M2 } -> Matrix( Unpack( M1 ) * Unpack( M2 ), M1 ) );

InstallMethod( \*,
    "for matrix object and scalar",
    [ IsMatrixObj, IsScalar ],
    -SUM_FLAGS,
    { M, s } -> Matrix( Unpack( M ) * s, M ) );

InstallMethod( \*,
    "for scalar and matrix object",
    [ IsScalar, IsMatrixObj ],
    -SUM_FLAGS,
    { s, M } -> Matrix( s * Unpack( M ), M ) );

InstallMethod( \*,
    "for (non-matrix) matrix object and matrix",
    [ IsMatrixObj, IsMatrix ],
    function( matobj, mat )
      if IsMatrix( matobj ) then
        TryNextMethod();
      fi;
      Error( "<matobj> * <mat> is not defined" );
    end );

InstallMethod( \*,
    "for matrix and (non-matrix) matrix object",
    [ IsMatrix, IsMatrixObj ],
    function( mat, matobj )
      if IsMatrix( matobj ) then
        TryNextMethod();
      fi;
      Error( "<mat> * <matobj> is not defined" );
    end );

InstallMethod( \/,
    "for matrix object and scalar",
    [ IsMatrixObj, IsScalar ],
    -SUM_FLAGS,
    { M, s } -> Matrix( Unpack( M ) / s, M ) );

#T no default methods should be needed for M^n, n an integer!


############################################################################
##
#M  \*( <vecobj>, <matobj> )
#M  \*( <matobj>, <vecobj> )
#M  \^( <vecobj>, <matobj> )
##
##  One of &GAP;'s strategies to study (small) matrix groups is
##  to compute a faithful permutation representations of the action on
##  orbits of row vectors, via right multiplication,
##  see <Ref Sect="Nice Monomorphisms"/>.
##  The code in question uses <Ref Func="OnPoints"/> as the default action,
##  which means that the operation <Ref Oper="\^"/> gets called.
##  Therefore, we declare this operation for the case that the two arguments
##  are in <Ref Cat="IsVectorObj"/> and <Ref Cat="IsMatrixOrMatrixObj"/>,
##  and install the multiplication as a method for this situation;
##  thus one need not install individual <Ref Oper="\^"/> methods
##  in special cases.
##  <P/>
##  For other code dealing with the multiplication of vectors and matrices,
##  it is recommended to use the multiplication <Ref Oper="\*"/> directly.
##
InstallMethod( \*,
    [ IsVectorObj, IsMatrixObj ],
    { v, M } -> Vector( Unpack( v ) * Unpack( M ), v ) );

InstallMethod( \*,
    [ IsMatrixObj, IsVectorObj ],
    { M, v } -> Vector( Unpack( M ) * Unpack( v ), v ) );

InstallOtherMethod( \^,
    [ IsVectorObj, IsMatrixObj ],
    \* );


############################################################################
##
#M  IsEmptyMatrix( <matobj> )
##
InstallMethod( IsEmptyMatrix,
  [ IsMatrixOrMatrixObj ],
  mat -> NrRows(mat) = 0 or NrCols(mat) = 0
);


#############################################################################
##
#M  ShallowCopy( <vec> )
##
InstallMethod( ShallowCopy,
    [ IsVectorObj ],
    -SUM_FLAGS,
    v -> Vector( Unpack( v ), v ) );


#############################################################################
##
#M  MutableCopyMatrix( <M> )
##
InstallMethod( MutableCopyMatrix,
    [ IsMatrixOrMatrixObj ],
    -SUM_FLAGS,
    M -> Matrix( Unpack( M ), M ) );


#############################################################################
##
#M  CopySubMatrix( <src>, <dst>, <srcrows>, <dstrows>, <srccols>, <dstcols> )
##
InstallMethod( CopySubMatrix,
    [ IsMatrixOrMatrixObj, IsMatrixOrMatrixObj and IsMutable,
      IsList, IsList, IsList, IsList ],
    function( src, dst, srcrows, dstrows, srccols, dstcols )
    local i, j;

    if Length( dstrows ) <> Length( srcrows ) then
      ErrorNoReturn( "source and destination row lists must be of equal length" );
    fi;
    if Length( dstcols ) <> Length( srccols ) then
      ErrorNoReturn( "source and destination column lists must be of equal length" );
    fi;

    for i in [ 1 .. Length( srcrows ) ] do
      for j in [ 1 .. Length( srccols ) ] do
        dst[ dstrows[i], dstcols[j] ]:= src[ srcrows[i], srccols[j] ];
      od;
    od;
    end );


#############################################################################
##
#M  TransposedMatImmutable( <M> )
##
InstallMethod( TransposedMatImmutable,
    [ IsMatrixOrMatrixObj ],
    M -> MakeImmutable( TransposedMatMutable( M ) ) );


#############################################################################
##
#M  AdditiveInverseMutable( <v> )
#M  AdditiveInverseSameMutability( <v> )
#M  ZeroMutable( <v> )
#M  ZeroSameMutability( <v> )
#M  IsZero( <v> )
#M  Characteristic( <v> )
##
##  <#GAPDoc Label="VectorObj_UnaryArithmetics">
##  <ManSection>
##  <Heading>Unary Arithmetical Operations for Vector Objects</Heading>
##  <Oper Name="AdditiveInverseMutable" Arg="v"
##   Label="for vector object"/>
##  <Oper Name="AdditiveInverseSameMutability" Arg="v"
##   Label="for vector object"/>
##  <Oper Name="ZeroMutable" Arg="v" Label="for vector object"/>
##  <Oper Name="ZeroSameMutability" Arg="v" Label="for vector object"/>
##  <Prop Name="IsZero" Arg="v" Label="for vector object"/>
##  <Attr Name="Characteristic" Arg="v" Label="for vector object"/>
##
##  <Returns>a vector object</Returns>
##  <Description>
##  For a vector object <A>v</A>,
##  the operations for computing the additive inverse with prescribed
##  mutability return a vector object with the same
##  <Ref Attr="ConstructingFilter" Label="for a vector object"/> and
##  <Ref Attr="BaseDomain" Label="for a vector object"/> values,
##  such that the sum with <A>v</A> is a zero vector.
##  It is not specified what happens if the base domain does not admit
##  the additive inverses of the entries.
##  <P/>
##  Analogously, the operations for computing a zero vector with
##  prescribed mutability return a vector object compatible with <A>v</A>.
##  <P/>
##  <Ref Prop="IsZero" Label="for vector object"/> returns <K>true</K> if
##  all entries in <A>v</A> are zero, and <K>false</K> otherwise.
##  <P/>
##  <Ref Attr="Characteristic" Label="for vector object"/> returns
##  the corresponding value of the
##  <Ref Attr="BaseDomain" Label="for a vector object"/> value of <A>v</A>.
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
##
##  We do not need default methods for 'AdditiveInverseImmutable' and
##  'ZeroImmutable' for vector objects because these methods
##  (create a mutable version and make it immutable)
##  are already installed more generally.
##
InstallMethod( AdditiveInverseMutable,
    [ IsVectorObj ],
    v -> Vector( AdditiveInverseMutable( Unpack( v ) ), v ) );

InstallMethod( AdditiveInverseSameMutability,
    [ IsVectorObj ],
    function( v )
    if IsMutable( v ) then
      return AdditiveInverseMutable( v );
    else
      return AdditiveInverseImmutable( v );
    fi;
    end );

InstallMethod( ZeroMutable,
    [ IsVectorObj ],
    v -> Vector( ZeroMutable( Unpack( v ) ), v ) );

InstallMethod( ZeroSameMutability,
    [ IsVectorObj ],
    function( v )
    if IsMutable( v ) then
      return ZeroMutable( v );
    else
      return ZeroImmutable( v );
    fi;
    end );

InstallMethod( IsZero,
    [ IsVectorObj ],
    v -> IsZero( Unpack( v ) ) );

InstallMethod( Characteristic,
    [ IsVectorObj ],
    -SUM_FLAGS,
    v -> Characteristic( BaseDomain( v ) ) );


#############################################################################
##
#M  AdditiveInverseMutable( <M> )
#M  AdditiveInverseSameMutability( <M> )
#M  ZeroMutable( <M> )
#M  ZeroSameMutability( <M> )
#M  OneMutable( <M> )
#M  OneSameMutability( <M> )
#M  InverseMutable( <M> )
#M  InverseSameMutability( <M> )
#M  IsZero( <M> )
#M  Characteristic( <M> )
##
##  <#GAPDoc Label="MatrixObj_UnaryArithmetics">
##  <ManSection>
##  <Heading>Unary Arithmetical Operations for Matrix Objects</Heading>
##  <Oper Name="AdditiveInverseMutable" Arg="M"
##   Label="for matrix object"/>
##  <Oper Name="AdditiveInverseSameMutability" Arg="M"
##   Label="for matrix object"/>
##  <Oper Name="ZeroMutable" Arg="M" Label="for matrix object"/>
##  <Oper Name="ZeroSameMutability" Arg="M" Label="for matrix object"/>
##  <Oper Name="OneMutable" Arg="M" Label="for matrix object"/>
##  <Oper Name="OneSameMutability" Arg="M" Label="for matrix object"/>
##  <Oper Name="InverseMutable" Arg="M" Label="for matrix object"/>
##  <Oper Name="InverseSameMutability" Arg="M" Label="for matrix object"/>
##  <Prop Name="IsZero" Arg="M" Label="for matrix object"/>
##  <Prop Name="IsOne" Arg="M" Label="for matrix object"/>
##  <Attr Name="Characteristic" Arg="M" Label="for matrix object"/>
##
##  <Returns>a matrix object</Returns>
##  <Description>
##  For a vector object <A>M</A>,
##  the operations for computing the additive inverse with prescribed
##  mutability return a matrix object with the same
##  <Ref Attr="ConstructingFilter" Label="for a matrix object"/> and
##  <Ref Attr="BaseDomain" Label="for a matrix object"/> values,
##  such that the sum with <A>M</A> is a zero matrix.
##  It is not specified what happens if the base domain does not admit
##  the additive inverses of the entries.
##  <P/>
##  Analogously, the operations for computing a zero matrix with
##  prescribed mutability return a matrix object compatible with <A>M</A>.
##  <P/>
##  The operations for computing an identity matrix with
##  prescribed mutability return a matrix object compatible with <A>M</A>,
##  provided that the base domain admits this and <A>M</A> is square and
##  nonempty.
##  <P/>
##  Analogously, the operations for computing an inverse matrix with
##  prescribed mutability return a matrix object compatible with <A>M</A>,
##  provided that <A>M</A> is invertible.
##  <!-- over its base domain? -->
##  (If <A>M</A> is not invertible then the operations return <K>fail</K>.)
##  <P/>
##  <Ref Prop="IsZero" Label="for matrix object"/> returns <K>true</K> if
##  all entries in <A>M</A> are zero, and <K>false</K> otherwise.
##  <Ref Prop="IsOne" Label="for matrix object"/> returns <K>true</K> if
##  <A>M</A> is nonempty and square and contains the identity of the
##  base domain in the diagonal, and zero in all other places.
##  <P/>
##  <Ref Attr="Characteristic" Label="for matrix object"/> returns
##  the corresponding value of the
##  <Ref Attr="BaseDomain" Label="for a matrix object"/> value of <A>M</A>.
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
##
##  We do not need default methods for 'AdditiveInverseImmutable',
##  'ZeroImmutable', 'OneImmutable', 'InverseImmutable',
##  for matrix objects because these methods
##  (create a mutable version and make it immutable)
##  are already installed more generally.
##
InstallMethod( AdditiveInverseMutable,
    [ IsMatrixObj ],
    M -> Matrix( AdditiveInverseMutable( Unpack( M ) ), M ) );

InstallMethod( AdditiveInverseSameMutability,
    [ IsMatrixOrMatrixObj ],
    function( M )
    if IsMutable( M ) then
      return AdditiveInverseMutable( M );
    else
      return AdditiveInverseImmutable( M );
    fi;
    end );

InstallMethod( ZeroMutable,
    [ IsMatrixObj ],
    M -> ZeroMatrix( NumberRows( M ), NumberColumns( M ), M ) );

InstallMethod( ZeroSameMutability,
    [ IsMatrixOrMatrixObj ],
    function( M )
    if IsMutable( M ) then
      return ZeroMutable( M );
    else
      return ZeroImmutable( M );
    fi;
    end );

InstallMethod( OneMutable,
    [ IsMatrixObj ],
    function( M )
    local nrows;
    nrows:= NrRows( M );
    if nrows <> NrCols( M ) then
      Error( "<M> must be square (not ", nrows, " by ", NrCols( M ), ")" );
    fi;
    return IdentityMatrix( nrows, M );
    end );

InstallMethod( OneSameMutability,
    [ IsMatrixOrMatrixObj ],
    function( M )
    if IsMutable( M ) then
      return OneMutable( M );
    else
      return OneImmutable( M );
    fi;
    end );

InstallMethod( InverseMutable,
    [ IsMatrixObj ],
    M -> Matrix( InverseMutable( Unpack( M ) ), M ) );

InstallMethod( InverseSameMutability,
    [ IsMatrixOrMatrixObj ],
    function( M )
    if IsMutable( M ) then
      return InverseMutable( M );
    else
      return InverseImmutable( M );
    fi;
    end );

InstallMethod( Characteristic,
    [ IsMatrixOrMatrixObj ],
    M -> Characteristic( BaseDomain( M ) ) );

InstallOtherMethod(DefaultFieldOfMatrix,[IsMatrixOrMatrixObj],BaseDomain);


#############################################################################
##
#M  Display( <obj> )
#M  ViewObj( <obj> )
#M  PrintObj( <obj> )
#M  DisplayString( <obj> )
#M  ViewString( <obj> )
#M  PrintString( <obj> )
#M  String( <obj> )
##
##  According to the Section "View and Print" in the GAP Reference Manual,
##  we need methods (only) for
##  'String' (which then covers 'PrintString' and 'PrintObj'),
##  'ViewString' (which then covers 'ViewObj'),
##  and 'DisplayString' (which then covers 'Display').
##
##  By default, we *view* and *display* vector objects and matrix objects as
##  '<vector object of length ... over ...>' and
##  '<matrix object of dimensions ... over ...>', respectively,
##  and to *print* them as 'NewVector( ... )' and 'NewMatrix( ... )',
##  respectively.
##  (Most likely, this will be overloaded for any type of such objects.)
##
BindGlobal( "ViewStringForVectorObj",
    v -> Concatenation( "<vector object of length ", String( Length( v ) ),
             " over ", String( BaseDomain( v ) ), ">" ) );

InstallMethod( ViewString,
    [ IsVectorObj ],
    ViewStringForVectorObj );

InstallMethod( DisplayString,
    [ IsVectorObj ],
    ViewStringForVectorObj );

InstallMethod( String,
    [ IsVectorObj ],
    v -> Concatenation( "NewVector( ",
             NameFunction( ConstructingFilter( v ) ), ", ",
             String( BaseDomain( v ) ), ", ",
             String( Unpack( v ) ), " )" ) );

BindGlobal( "ViewStringForMatrixObj",
    M -> Concatenation( "<matrix object of dimensions ",
             String( NumberRows( M ) ), "x", String( NumberColumns( M ) ),
             " over ", String( BaseDomain( M ) ), ">" ) );

InstallMethod( ViewString,
    [ IsMatrixObj ],
    ViewStringForMatrixObj );

InstallMethod( DisplayString,
    [ IsMatrixObj ],
    ViewStringForMatrixObj );

InstallMethod( String,
    [ IsMatrixObj ],
    M -> Concatenation( "NewMatrix( ",
               NameFunction( ConstructingFilter( M ) ), ", ",
               String( BaseDomain( M ) ), ", ",
               String( NumberColumns( M ) ), ", ",
               String( Unpack( M ) ), " )" ) );


############################################################################
##
#M  CompatibleVector( <M> )
##
InstallMethod( CompatibleVector,
    "for a matrix or matrix object",
    [ IsMatrixOrMatrixObj ],
    M -> NewZeroVector( CompatibleVectorFilter( M ), BaseDomain( M ),
                        NumberRows( M ) ) );


############################################################################
##
#M  RowsOfMatrix( <M> )
##
InstallMethod( RowsOfMatrix,
    "for a matrix or matrix object",
    [ IsMatrixOrMatrixObj ],
    function( M )
    local R, f;

    R:= BaseDomain( M );
    f:= CompatibleVectorFilter( M );
    return List( Unpack( M ), row -> Vector( f, R, row ) );
    end );


############################################################################
##
##  Backwards compatibility
##
##  We should remove the methods as soon as they are not used anymore.
##

InstallMethod( DimensionsMat,
    "for a matrix or matrix object",
    [ IsMatrixOrMatrixObj ],
    M -> [ NumberRows( M ), NumberColumns( M ) ] );

InstallOtherMethod( Randomize,
    "for random source as 2nd argument: switch arguments",
    [ IsObject and IsMutable, IsRandomSource ],
    { obj, rs } -> Randomize( rs, obj ) );

InstallMethod( Length,
    "for a matrix or matrix object",
    [ IsMatrixOrMatrixObj ],
    -SUM_FLAGS,
    NumberRows );

# Install fallback methods for m[i,j] which delegate to ASS_LIST / ELM_LIST
# for code using an intermediate version of the MatrixObj specification
# (any package installing methods for MatElm resp. SetMatElm
# should be fine without these). We lower the rank so that these are only
# used as a last resort.
InstallMethod( \[\,\], "for a matrix object and two positions",
  [ IsMatrixOrMatrixObj, IsPosInt, IsPosInt ],
  {} -> -RankFilter(IsMatrixOrMatrixObj),
  ELM_LIST );

InstallMethod( \[\,\]\:\=, "for a matrix object, two positions, and an object",
  [ IsMatrixOrMatrixObj and IsMutable, IsPosInt, IsPosInt, IsObject ],
  {} -> -RankFilter(IsMatrixOrMatrixObj),
  ASS_LIST );

############################################################################
# Elementary matrix operations
############################################################################

InstallMethod( MultMatrixRowLeft, "for a mutable matrix object, a row number, and a scalar",
  [ IsMatrixOrMatrixObj and IsMutable, IsInt, IsObject ],
  function( mat, row, scalar )
    local i;

    for i in [1..NrCols(mat)] do
        mat[row,i] := scalar * mat[row,i];
    od;

  end );

InstallEarlyMethod( MultMatrixRowLeft,
  function( mat, i, scalar )
    if IsPlistRep(mat) then
      mat[i] := scalar * mat[i];
    else
      TryNextMethod();
    fi;
  end );

############################################################################

InstallMethod( MultMatrixColumnRight, "for a mutable matrix object, a column number, and a scalar",
  [ IsMatrixOrMatrixObj and IsMutable, IsInt, IsObject ],
  function( mat, column, scalar )
    local i;

    for i in [1..NrRows(mat)] do
        mat[i,column] := mat[i,column] * scalar;
    od;

  end );

InstallEarlyMethod( MultMatrixColumnRight,
  function( mat, i, scalar )
    local row;
    if IsPlistRep(mat) then
      for row in mat do
          row[i] := row[i] * scalar;
      od;
    else
      TryNextMethod();
    fi;
  end );

############################################################################

InstallMethod( MultMatrixRowRight, "for a mutable matrix object, a row number, and a scalar",
  [ IsMatrixOrMatrixObj and IsMutable, IsInt, IsObject ],
  function( mat, row, scalar )
      local i;

    for i in [1..NrCols(mat)] do
        mat[row,i] := mat[row,i] * scalar;
    od;

  end );

InstallEarlyMethod( MultMatrixRowRight,
  function( mat, i, scalar )
    if IsPlistRep(mat) then
      mat[i] := mat[i] * scalar;
    else
      TryNextMethod();
    fi;
  end );

############################################################################

InstallMethod( MultMatrixColumnLeft, "for a mutable matrix object, a column number, and a scalar",
  [ IsMatrixOrMatrixObj and IsMutable, IsInt, IsObject ],
  function( mat, column, scalar )
    local i;

    for i in [1..NrRows(mat)] do
        mat[i,column] := scalar * mat[i,column];
    od;

  end );

InstallEarlyMethod( MultMatrixColumnLeft,
  function( mat, i, scalar )
    local row;
    if IsPlistRep(mat) then
      for row in mat do
          row[i] := scalar * row[i];
      od;
    else
      TryNextMethod();
    fi;
  end );

############################################################################

InstallMethod( AddMatrixRowsLeft, "for a mutable matrix object, two row numbers, and a scalar",
  [ IsMatrixOrMatrixObj and IsMutable, IsInt, IsInt, IsObject ] ,
  function( mat, row1, row2, scalar )
    local i;

    for i in [1..NrCols(mat)] do
        mat[row1,i] := mat[row1,i] + scalar * mat[row2,i];
    od;

  end );

InstallEarlyMethod( AddMatrixRowsLeft,
  function( mat, i, j, scalar )
    if IsPlistRep(mat) then
      mat[i] := mat[i] + scalar * mat[j];
    else
      TryNextMethod();
    fi;
  end );

 ############################################################################

InstallMethod( AddMatrixRowsRight, "for a mutable matrix object, two row numbers, and a scalar",
  [ IsMatrixOrMatrixObj and IsMutable, IsInt, IsInt, IsObject ] ,
  function( mat, row1, row2, scalar )
    local i;

    for i in [1..NrCols(mat)] do
        mat[row1,i] := mat[row1,i] + mat[row2,i] * scalar;
    od;

  end );

InstallEarlyMethod( AddMatrixRowsRight,
  function( mat, i, j, scalar )
    if IsPlistRep(mat) then
      mat[i] := mat[i] + mat[j] * scalar;
    else
      TryNextMethod();
    fi;
  end );

############################################################################

InstallMethod( AddMatrixColumnsRight, "for a mutable matrix object, two column numbers, and a scalar",
  [ IsMatrixOrMatrixObj and IsMutable, IsInt, IsInt, IsObject ] ,
  function( mat, column1, column2, scalar )
    local i;

    for i in [1..NrRows(mat)] do
        mat[i,column1] := mat[i,column1] + mat[i,column2] * scalar;
    od;

  end );

InstallEarlyMethod( AddMatrixColumnsRight,
  function( mat, i, j, scalar )
    local row;
    if IsPlistRep(mat) then
      for row in mat do
          row[i] := row[i] + row[j] * scalar;
      od;
    else
      TryNextMethod();
    fi;
  end );

############################################################################

InstallMethod( AddMatrixColumnsLeft, "for a mutable matrix object, two column numbers, and a scalar",
  [ IsMatrixOrMatrixObj and IsMutable, IsInt, IsInt, IsObject ] ,
  function( mat, column1, column2, scalar )
    local i;

    for i in [1..NrRows(mat)] do
        mat[i,column1] := mat[i,column1] + scalar * mat[i,column2];
    od;

  end );

InstallEarlyMethod( AddMatrixColumnsLeft,
  function( mat, i, j, scalar )
    local row;
    if IsPlistRep(mat) then
      for row in mat do
          row[i] := row[i] + scalar * row[j];
      od;
    else
      TryNextMethod();
    fi;
  end );

############################################################################

InstallMethod( PositionNonZeroInRow,
  "for a row list matrix and a row number",
  [ IsRowListMatrix, IsPosInt ],
  function( mat, row )
    return PositionNonZero( mat[row] );
  end );

InstallMethod( PositionNonZeroInRow,
  "for a row list matrix, a row number, and a start position",
  [ IsRowListMatrix, IsPosInt, IsInt ],
  function( mat, row, from )
    return PositionNonZero( mat[row], from );
  end );

InstallMethod( PositionNonZeroInRow,
  "for a matrix or matrix object and a row number",
  [ IsMatrixOrMatrixObj, IsPosInt ],
  function( mat, row )
    return PositionNonZeroInRow( mat, row, 0 );
  end );

InstallMethod( PositionNonZeroInRow,
  "for a matrix or matrix object, a row number, and a start position",
  [ IsMatrixOrMatrixObj, IsPosInt, IsInt ],
  function( mat, row, from )
    local col, ncols, zero;

    ncols := NrCols( mat );
    zero := ZeroOfBaseDomain( mat );
    for col in [ Maximum( 1, from + 1 ) .. ncols ] do
      if mat[row, col] <> zero then
        return col;
      fi;
    od;

    return ncols + 1;
  end );

InstallEarlyMethod( PositionNonZeroInRow,
    function ( mat, row )
    if IsPlistRep( mat ) then
      return PositionNonZero( mat[row] );
    fi;
    TryNextMethod();
    end );

InstallEarlyMethod( PositionNonZeroInRow,
    function ( mat, row, from )
    if IsPlistRep( mat ) then
      return PositionNonZero( mat[row], from );
    fi;
    TryNextMethod();
    end );


############################################################################

InstallMethod( SwapMatrixRows, "for a mutable matrix object, and two row numbers",
  [ IsMatrixOrMatrixObj and IsMutable, IsInt, IsInt ],
  function( mat, row1, row2 )
    local temp, i;

    if row1 <> row2 then
        for i in [1..NrCols(mat)] do
            temp := mat[row1,i];
            mat[row1,i] := mat[row2,i];
            mat[row2,i] := temp;
        od;
    fi;

  end );

############################################################################

InstallMethod( SwapMatrixColumns, "for a mutable matrix object, and two column numbers",
  [ IsMatrixOrMatrixObj and IsMutable, IsInt, IsInt ],
  function( mat, column1, column2 )
    local temp, i;

    if column1 <> column2 then
        for i in [1..NrRows(mat)] do
            temp := mat[i,column1];
            mat[i,column1] := mat[i,column2];
            mat[i,column2] := temp;
        od;
    fi;

  end );

#############################################################################
##
#M  AddMatrix( <mat1>, <mat2> )
##

InstallMethod( AddMatrix, "for a mutable matrix object and a matrix object",
  [ IsMatrixOrMatrixObj and IsMutable, IsMatrixOrMatrixObj ],
  function( dstmat, srcmat )
    local i, j;
    if DimensionsMat(dstmat) <> DimensionsMat(srcmat) then
      Error("AddMatrix: matrices must have the same dimensions");
    fi;
    for i in [1..NrRows(dstmat)] do
      for j in [1..NrCols(dstmat)] do
        dstmat[i,j] := dstmat[i,j] + srcmat[i,j];
      od;
    od;
  end );

InstallEarlyMethod( AddMatrix,
  function( dstmat, srcmat )
    local i;
    if IsPlistRep(dstmat) and IsPlistRep(srcmat) then
      if DimensionsMat(dstmat) <> DimensionsMat(srcmat) then
        Error("AddMatrix: matrices must have the same dimensions");
      fi;
      for i in [1..NrRows(dstmat)] do
        AddRowVector(dstmat[i], srcmat[i]);
      od;
    else
      TryNextMethod();
    fi;
  end );

InstallMethod( AddMatrix, "for a mutable IsRowListMatrix and a IsRowListMatrix",
  [ IsRowListMatrix and IsMutable, IsRowListMatrix ],
  function( dstmat, srcmat )
    local i;
    if DimensionsMat(dstmat) <> DimensionsMat(srcmat) then
      Error("AddMatrix: matrices must have the same dimensions");
    fi;
    for i in [1..NrRows(dstmat)] do
      AddRowVector(dstmat[i], srcmat[i]);
    od;
  end );

InstallMethod( AddMatrix, "for a mutable 8bit matrix and an 8bit matrix",
  [ Is8BitMatrixRep and IsMutable, Is8BitMatrixRep ],
  function( dstmat, srcmat )
    local i;
    if DimensionsMat(dstmat) <> DimensionsMat(srcmat) then
      Error("AddMatrix: matrices must have the same dimensions");
    fi;
    for i in [1..NrRows(dstmat)] do
      ADD_ROWVECTOR_VEC8BITS_2(dstmat[i], srcmat[i]);
    od;
  end );

#############################################################################
##
#M  AddMatrix( <mat1>, <mat2>, <mult> )
##

InstallMethod( AddMatrix, "for a mutable matrix object, a matrix object, and a scalar",
  [ IsMatrixOrMatrixObj and IsMutable, IsMatrixOrMatrixObj, IsScalar ],
  function( dstmat, srcmat, scalar )
    local i, j;
    if DimensionsMat(dstmat) <> DimensionsMat(srcmat) then
      Error("AddMatrix: matrices must have the same dimensions");
    fi;
    for i in [1..NrRows(dstmat)] do
      for j in [1..NrCols(dstmat)] do
        dstmat[i,j] := dstmat[i,j] + srcmat[i,j] * scalar;
      od;
    od;
  end );

InstallEarlyMethod( AddMatrix,
  function( dstmat, srcmat, scalar )
    local i;
    if IsPlistRep(dstmat) and IsPlistRep(srcmat) then
      if DimensionsMat(dstmat) <> DimensionsMat(srcmat) then
        Error("AddMatrix: matrices must have the same dimensions");
      fi;
      for i in [1..NrRows(dstmat)] do
        AddRowVector(dstmat[i], srcmat[i], scalar);
      od;
    else
      TryNextMethod();
    fi;
  end );

InstallMethod( AddMatrix, "for a mutable IsRowListMatrix, an IsRowListMatrix, and a scalar",
  [ IsRowListMatrix and IsMutable, IsRowListMatrix, IsScalar ],
  function( dstmat, srcmat, scalar )
    local i;
    if DimensionsMat(dstmat) <> DimensionsMat(srcmat) then
      Error("AddMatrix: matrices must have the same dimensions");
    fi;
    for i in [1..NrRows(dstmat)] do
      AddRowVector(dstmat[i], srcmat[i], scalar);
    od;
  end );

InstallMethod( AddMatrix, "for a mutable 8bit matrix, an 8bit matrix, and a scalar",
  [ Is8BitMatrixRep and IsMutable, Is8BitMatrixRep, IsFFE ],
  function( dstmat, srcmat, scalar )
    local i;
    if DimensionsMat(dstmat) <> DimensionsMat(srcmat) then
      Error("AddMatrix: matrices must have the same dimensions");
    fi;
    for i in [1..NrRows(dstmat)] do
      ADD_ROWVECTOR_VEC8BITS_3(dstmat[i], srcmat[i], scalar);
    od;
  end );

#############################################################################
##
#M  MultMatrixRight( <mat>, <mult> )
##

InstallMethod( MultMatrixRight, "for a mutable matrix object and a scalar",
  [ IsMatrixOrMatrixObj and IsMutable, IsScalar ],
  function( mat, scalar )
    local i, j;
    for i in [1..NrRows(mat)] do
      for j in [1..NrCols(mat)] do
        mat[i,j] := mat[i,j] * scalar;
      od;
    od;
  end );

InstallEarlyMethod( MultMatrixRight,
  function( mat, scalar )
    local i;
    if IsPlistRep(mat) and IsScalar(scalar) then
      for i in [1..NrRows(mat)] do
        MultVectorRight(mat[i], scalar);
      od;
    else
      TryNextMethod();
    fi;
  end );

InstallMethod( MultMatrixRight, "for a mutable IsRowListMatrix and a scalar",
  [ IsRowListMatrix and IsMutable, IsScalar ],
  function( mat, scalar )
    local i;
    for i in [1..NrRows(mat)] do
      MultVectorRight(mat[i], scalar);
    od;
  end );

#############################################################################
##
#M  MultMatrixLeft( <mat>, <mult> )
##

InstallMethod( MultMatrixLeft, "for a mutable matrix object and a scalar",
  [ IsMatrixOrMatrixObj and IsMutable, IsScalar ],
  function( mat, scalar )
    local i, j;
    for i in [1..NrRows(mat)] do
      for j in [1..NrCols(mat)] do
        mat[i,j] := scalar * mat[i,j];
      od;
    od;
  end );

InstallEarlyMethod( MultMatrixLeft,
  function( mat, scalar )
    local i;
    if IsPlistRep(mat) and IsScalar(scalar) then
      for i in [1..NrRows(mat)] do
        MultVectorLeft(mat[i], scalar);
      od;
    else
      TryNextMethod();
    fi;
  end );

InstallMethod( MultMatrixLeft, "for a mutable IsRowListMatrix and a scalar",
  [ IsRowListMatrix and IsMutable, IsScalar ],
  function( mat, scalar )
    local i;
    for i in [1..NrRows(mat)] do
      MultVectorLeft(mat[i], scalar);
    od;
  end );

############################################################################
##  Fallback method for DeterminantMatrix

InstallMethod(DeterminantMatrix, ["IsMatrixObj"],
function( mat )
  return DeterminantMat( Unpack( mat ) );
end);
