#############################################################################
##
##  HomalgMap.gi                Modules package              Mohamed Barakat
##
##  Copyright 2007-2010, Mohamed Barakat, University of Kaiserslautern
##
##  Implementation stuff for homalg maps ( = module homomorphisms ).
##
#############################################################################

##  <#GAPDoc Label="Maps:intro">
##    A &homalg; map is a data structures for maps (module homomorphisms) between finitely generated modules.
##    Each map in &homalg; knows its source (&see; <Ref BookName="homalg" Attr="Source"/>) and its target
##    (&see; <Ref BookName="homalg" Attr="Range"/>). A map is represented by a &homalg; matrix relative to the
##    current set of generators of the source resp. target &homalg; module. As with modules
##    (&see; Chapter <Ref Chap="Modules"/>), maps in &homalg; are realized in an intrinsic manner:
##    If the presentations of the source or/and target module are altered after the map was constructed,
##    a new adapted representation matrix of the map is automatically computed whenever needed.
##    For this the internal transition matrices of the modules are used. &homalg; uses the so-called
##    <E>associative</E> convention for maps. This means that maps of left modules are applied
##    from the right, whereas maps of right modules from the left.
##  <#/GAPDoc>

####################################
#
# representations:
#
####################################

##  <#GAPDoc Label="IsMapOfFinitelyGeneratedModulesRep">
##  <ManSection>
##    <Filt Type="Representation" Arg="phi" Name="IsMapOfFinitelyGeneratedModulesRep"/>
##    <Returns><C>true</C> or <C>false</C></Returns>
##    <Description>
##      The &GAP; representation of maps between finitley generated &homalg; modules. <P/>
##      (It is a representation of the &GAP; category <Ref BookName="homalg" Filt="IsHomalgChainMorphism"/>,
##       which is a subrepresentation of the &GAP; representation <C>IsStaticMorphismOfFinitelyGeneratedObjectsRep</C>.)
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
DeclareRepresentation( "IsMapOfFinitelyGeneratedModulesRep",
        IsHomalgMap and IsStaticMorphismOfFinitelyGeneratedObjectsRep,
        [ "source", "target", "matrices", "index_pairs_of_presentations" ] );

####################################
#
# families and types:
#
####################################

# a new family:
BindGlobal( "TheFamilyOfHomalgMaps",
        NewFamily( "TheFamilyOfHomalgMaps" ) );

# four new types:
BindGlobal( "TheTypeHomalgMapOfLeftModules",
        NewType( TheFamilyOfHomalgMaps,
                AdmissibleInputForHomalgFunctors and
                IsMapOfFinitelyGeneratedModulesRep and
                IsHomalgLeftObjectOrMorphismOfLeftObjects ) );

BindGlobal( "TheTypeHomalgMapOfRightModules",
        NewType( TheFamilyOfHomalgMaps,
                AdmissibleInputForHomalgFunctors and
                IsMapOfFinitelyGeneratedModulesRep and
                IsHomalgRightObjectOrMorphismOfRightObjects ) );

BindGlobal( "TheTypeHomalgSelfMapOfLeftModules",
        NewType( TheFamilyOfHomalgMaps,
                AdmissibleInputForHomalgFunctors and
                IsMapOfFinitelyGeneratedModulesRep and
                IsHomalgSelfMap and
                IsHomalgLeftObjectOrMorphismOfLeftObjects ) );

BindGlobal( "TheTypeHomalgSelfMapOfRightModules",
        NewType( TheFamilyOfHomalgMaps,
                AdmissibleInputForHomalgFunctors and
                IsMapOfFinitelyGeneratedModulesRep and
                IsHomalgSelfMap and
                IsHomalgRightObjectOrMorphismOfRightObjects ) );

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

##  <#GAPDoc Label="HomalgRing:map">
##  <ManSection>
##    <Oper Arg="phi" Name="HomalgRing"/>
##    <Returns>a &homalg; ring</Returns>
##    <Description>
##      The &homalg; ring of the &homalg; map <A>phi</A>.
##      <Example><![CDATA[
##  gap> ZZ := HomalgRingOfIntegers( );
##  Z
##  gap> phi := HomalgIdentityMap( 2 * ZZ );
##  <The identity morphism of a non-zero left module>
##  gap> R := HomalgRing( phi );
##  Z
##  gap> IsIdenticalObj( R, ZZ );
##  true
##  ]]></Example>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallMethod( HomalgRing,
        "for homalg maps",
        [ IsHomalgMap ],
        
  function( phi )
    
    return HomalgRing( Source( phi ) );
    
end );

##
InstallMethod( homalgResetFilters,
        "for homalg maps",
        [ IsHomalgMap ],
        
  function( phi )
    local property;
    
    for property in LIHOM.intrinsic_properties do
        ResetFilterObj( phi, ValueGlobal( property ) );
    od;
    
end );

## provided to avoid branching in the code and always returns fail
InstallMethod( PositionOfTheDefaultPresentation,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( M )
    
    return fail;
    
end );

##
InstallMethod( PairOfPositionsOfTheDefaultPresentations,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( phi )
    local pos_s, pos_t;
    
    pos_s := PositionOfTheDefaultPresentation( Source( phi ) );
    pos_t := PositionOfTheDefaultPresentation( Range( phi ) );
    
    if IsHomalgLeftObjectOrMorphismOfLeftObjects( phi ) then
        return [ pos_s, pos_t ];
    else
        return [ pos_t, pos_s ];
    fi;
    
end );

##
InstallMethod( MatrixOfMap,		## FIXME: make this optimal by finding shortest ways
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep, IsInt, IsInt ],
        
  function( phi, _pos_s, _pos_t )
    local pos_s, pos_t, index_pair, l, dist, min, pos, matrix;
    
    if _pos_s < 1 then
        pos_s := PositionOfTheDefaultPresentation( Source( phi ) );
    else
        pos_s := _pos_s;
    fi;
    
    if _pos_t < 1 then
        pos_t := PositionOfTheDefaultPresentation( Range( phi ) );
    else
        pos_t := _pos_t;
    fi;
    
    if IsHomalgLeftObjectOrMorphismOfLeftObjects( phi ) then
        index_pair := [ pos_s, pos_t ];
    else
        index_pair := [ pos_t, pos_s ];
    fi;
    
    l := phi!.index_pairs_of_presentations;
    
    if not index_pair in l then
        
        dist := List( l, a -> AbsInt( index_pair[1] - a[1] ) + AbsInt( index_pair[2] - a[2] ) );
        
        min := Minimum( dist );
        
        pos := PositionProperty( dist, a -> a = min );
        
        if IsHomalgLeftObjectOrMorphismOfLeftObjects( phi ) then
            matrix :=
              TransitionMatrix( Source( phi ), pos_s, l[pos][1] )
              * phi!.matrices.( String( l[pos] ) )
              * TransitionMatrix( Range( phi ), l[pos][2], pos_t );
        else
            matrix :=
              TransitionMatrix( Range( phi ), pos_t, l[pos][1] )
              * phi!.matrices.( String( l[pos] ) )
              * TransitionMatrix( Source( phi ), l[pos][2], pos_s );
        fi;
        
        phi!.matrices.( String( index_pair ) ) := matrix;
        
        Add( l, index_pair );
        
    fi;
    
    if IsBound( phi!.reduced_matrices.( String( index_pair ) ) ) then
        return phi!.reduced_matrices.( String( index_pair ) );
    else
        return phi!.matrices.( String( index_pair ) );
    fi;
    
end );

##
InstallMethod( MatrixOfMap,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep and IsHomalgSelfMap, IsPosInt ],
        
  function( phi, pos_s_t )
    
    return MatrixOfMap( phi, pos_s_t, pos_s_t );
    
end );

##
InstallMethod( MatrixOfMap,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( phi )
    
    return MatrixOfMap( phi, 0, 0 );
    
end );

##
InstallMethod( ZeroMutable,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( phi )
    
    return TheZeroMorphism( Source( phi ), Range( phi ) );
    
end );

## a synonym of `-<elm>':
InstallMethod( AdditiveInverseMutable,
        "of homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( phi )
    local psi;
    
    psi := MinusOne( HomalgRing( phi ) ) * phi;
    
    SetPropertiesOfAdditiveInverse( psi, phi ); # not needed because of "*" ?
    
    return psi;
    
end );

##
InstallMethod( BasisOfModule,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( phi )
    
    BasisOfModule( Source( phi ) );
    BasisOfModule( Range( phi ) );
    
    return phi;
    
end );

##
InstallMethod( DecideZero,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( phi )
    local rel, index_pair, matrix, reduced, zero;
    
    rel := RelationsOfModule( Range( phi ) );
    
    index_pair := PairOfPositionsOfTheDefaultPresentations( phi );
    
    matrix := MatrixOfMap( phi );
    
    reduced := DecideZero( matrix, rel );
    
    zero := IsZero( reduced );
    
    SetIsZero( phi, zero );
    
    if not zero then
        SetIsZero( Source( phi ), false );
        SetIsZero( Range( phi ), false );
    fi;
    
    ## replace the original matrix by the reduced one; this is important
    ## since we want to keep the reduced form of a matrix over a
    ## residue class ring, although they are ``equal'' when compared using =
    
    phi!.matrices.(String( index_pair )) := reduced;
    
    phi!.reduced_matrices.(String( index_pair )) := reduced;
    
    return phi;
    
end );

##
InstallMethod( OnLessGenerators,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( phi )
    
    OnLessGenerators( Source( phi ) );
    OnLessGenerators( Range( phi ) );
    
    return phi;
    
end );

##
InstallMethod( UnionOfRelations,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( phi )
    
    return UnionOfRelations( MatrixOfMap( phi ), Range( phi ) );
    
end );

##
InstallMethod( SyzygiesGenerators,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( phi )
    local syz;
    
    syz := SyzygiesGenerators( MatrixOfMap( phi ), Range( phi ) );
    
    if NrRelations( syz ) = 0 then
        SetIsMonomorphism( phi, true );
    fi;
    
    return syz;
    
end );

##
InstallMethod( ReducedSyzygiesGenerators,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( phi )
    local syz;
    
    syz := ReducedSyzygiesGenerators( MatrixOfMap( phi ), Range( phi ) );
    
    if NrRelations( syz ) = 0 then
        SetIsMonomorphism( phi, true );
    fi;
    
    return syz;
    
end );

##
InstallMethod( Preimage,
        "for a matrix and a homalg map",
        [ IsHomalgMatrix, IsMapOfFinitelyGeneratedModulesRep ],
        
  function( m, phi )
    local M, rel, mat;
    
    M := Range( phi );
    
    rel := MatrixOfRelations( M );
    
    mat := MatrixOfMap( phi );
    
    if IsHomalgLeftObjectOrMorphismOfLeftObjects( phi ) then
        return RightDivide( m, mat, rel );
    else
        return LeftDivide( m, mat, rel );
    fi;
    
end );

##
InstallMethod( SuccessivePreimages,
        "for a matrix and a homalg selfmap",
        [ IsHomalgMatrix, IsHomalgSelfMap and IsMapOfFinitelyGeneratedModulesRep ],
        
  function( m, phi )
    local preimages, pre, n;
    
    preimages := [ m ];
    
    pre := Preimage( m, phi );
    
    while IsHomalgMatrix( pre ) do
        
        Add( preimages, pre );
        
        n := Length( preimages );
        
        pre := Preimage( preimages[n], phi );
        
    od;
    
    return preimages;
    
end );

##  <#GAPDoc Label="PreInverse">
##  <ManSection>
##    <Oper Arg="phi" Name="PreInverse"/>
##    <Returns>a &homalg; map, <C>false</C>, or <C>fail</C></Returns>
##    <Description>
##      Compute a pre-inverse of the morphism <A>phi</A> in case one exists.
##      For a pre-inverse to exist <A>phi</A> must be an epimorphism. For <E>commutative</E> rings
##      &homalg; has an algorithm installed which decides the existence and returns
##      a pre-inverse in case one exists. If a pre-inverse does not exist then <C>false</C>
##      is returned. The algorithm finds a particular solution of a two-side inhomogeneous linear system
##      over <M>R := </M><C>HomalgRing</C><M>( <A>phi</A> )</M>.
##      For <E>non</E>commutative rings a heuristic method is installed. If it
##      finds a pre-inverse it returns it, otherwise it returns <C>fail</C>
##      (&see; <Ref Label="Modules-limitation" Text="Principal limitation"/>).
##      The operation <C>PreInverse</C> is used to install a method for the property
##      <Ref BookName="homalg" Prop="IsSplitEpimorphism"/>. <P/>
##      <C>PreInverse</C> checks if it can decide the projectivity of <C>Range</C><M>( <A>phi</A> )</M>.
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallMethod( PreInverse,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( phi )
    local R, S, T, M, p, Ib, Ic, b, c, P, d, Id, PI, B, A, L, sigma;
    
    ## no need to search for phi!.PreInverse
    ## as this is not the highest priority method
    
    R := HomalgRing( phi );
    
    if not ( HasIsCommutative( R ) and IsCommutative( R ) ) then
        TryNextMethod( );
    fi;
    
    S := Source( phi );
    T := Range( phi );
    
    M := MatrixOfRelations( ReducedBasisOfModule( S ) );
    
    p := MatrixOfMap( phi );
    
    #=====# begin of the core procedure #=====#
    
    Ib := MatrixOfMap( TheIdentityMorphism( S ) );
    Ic := MatrixOfMap( TheIdentityMorphism( T ) );
    
    b := NrRows( Ib );
    c := NrRows( Ic );
    
    P := ReducedBasisOfModule( T );
    
    d := NrRelations( P );
    Id := HomalgIdentityMatrix( d, R );
    
    P := MatrixOfRelations( P );
    
    PI := Involution( P );
    
    B := EntriesOfHomalgMatrix( Ic );
    
    B := Concatenation( ListWithIdenticalEntries( b * d, Zero( R ) ), B );
    
    if IsHomalgLeftObjectOrMorphismOfLeftObjects( phi ) then
        B := HomalgMatrix( B, 1, b * d + c * c, R );
        A := UnionOfColumns( KroneckerMat( PI, Ib ), KroneckerMat( Ic, p ) );
        L := DiagMat( [ KroneckerMat( Id, M ), KroneckerMat( Ic, P ) ] );
        sigma := RightDivide( B, A, L );
    else
        B := HomalgMatrix( B, b * d + c * c, 1, R );
        A := UnionOfRows( KroneckerMat( Ib, PI ), KroneckerMat( p, Ic ) );
        L := DiagMat( [ KroneckerMat( M, Id ), KroneckerMat( P, Ic ) ] );
        sigma := LeftDivide( A, B, L );
    fi;
    
    if IsBool( sigma ) then	## no split
        
        ## from a method below we already know that phi is an epimorphism
        if IsEpimorphism( phi ) then	## to be sure ;)
            ## so T is not projective since phi is not split
            SetIsProjective( T, false );
        fi;
        
        phi!.PreInverse := false;
        
        return phi!.PreInverse;
        
    fi;
    
    ## we already have every thing to build the (matrix of the) split sigma
    
    sigma := EntriesOfHomalgMatrix( sigma );
    
    if IsHomalgLeftObjectOrMorphismOfLeftObjects( phi ) then
        sigma := HomalgMatrix( sigma, c, b, R );
    else
        sigma := HomalgMatrix( sigma, b, c, R );
    fi;
    
    sigma := HomalgMap( sigma, T, S );
    
    DecideZero( sigma );
    
    Assert( 3, IsMonomorphism( sigma ) );
    
    SetIsSplitEpimorphism( phi, true );
    SetIsSplitMonomorphism( sigma, true );
    
    ## a direct summand of a projective module is again projective [HS, I.4.5]
    if HasIsProjective( S ) and IsProjective( S ) then
        SetIsProjective( T, true );
    fi;
    
    phi!.PreInverse := sigma;
    sigma!.PostInverse := phi;
    
    return sigma;
    
end );

##
InstallMethod( PreInverse,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( phi )
    local S, T, Id, A, L, sigma;
    
    if IsBound(phi!.PreInverse) then
        return phi!.PreInverse;
    fi;
    
    if not IsEpimorphism( phi ) then
        
        return false;
        
    elif IsIsomorphism( phi ) then
        
        ## only in case the standard method for IsIsomorphism wasn't triggered:
        UpdateObjectsByMorphism( phi );
        
        sigma := phi ^ -1;
        
        phi!.PreInverse := sigma;
        phi!.PostInverse := sigma;
        sigma!.PreInverse := phi;
        sigma!.PostInverse := phi;
        
        return sigma;
        
    fi;
    
    DecideZero( phi );
    
    S := Source( phi );
    T := Range( phi );
    
    Id := HomalgIdentityMatrix( NrGenerators( T ), HomalgRing( T ) );
    A := MatrixOfMap( phi );
    L := MatrixOfRelations( T );
    
    if IsHomalgLeftObjectOrMorphismOfLeftObjects( phi ) then
        sigma := RightDivide( Id, A, L );
    else
        sigma := LeftDivide( A, Id, L );
    fi;
    
    if not IsHomalgMatrix( sigma ) then	## no split even on the level of matrices
        
        ## from above we already know that phi is an epimorphism,
        ## so T is not projective since phi is not split [HS, I.4.7.(3)]
        SetIsProjective( T, false );
        
        phi!.PreInverse := false;
        
        return phi!.PreInverse;
        
    elif HasIsZero( sigma ) and IsZero( sigma ) then
        
        ## if the matrix is nonzero we still don't know if a split exists
        
        SetIsZero( T, true );
        SetIsZero( phi, true );
        
        sigma := TheZeroMorphism( T, S );
        
        phi!.PreInverse := sigma;
        sigma!.PostInverse := phi;
        
        return sigma;
        
    fi;
    
    sigma := HomalgMap( sigma, T, S );
    
    if IsMorphism( sigma ) then
        
        DecideZero( sigma );
        
        Assert( 3, IsMonomorphism( sigma ) );
        
        SetIsSplitEpimorphism( phi, true );
        SetIsSplitMonomorphism( sigma, true );
        
        ## a direct summand of a projective module is again projective [HS, I.4.5]
        if HasIsProjective( S ) and IsProjective( S ) then
            SetIsProjective( T, true );
        fi;
        
        phi!.PreInverse := sigma;
        sigma!.PostInverse := phi;
        
        return sigma;
        
    fi;
    
    TryNextMethod( );
    
end );

##
InstallMethod( PostInverse,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( phi )
    local S, T, A, chi;
    
    if IsBound(phi!.PostInverse) then
        return phi!.PostInverse;
    fi;
    
    if not IsMonomorphism( phi ) then
        
        return false;
        
    elif IsIsomorphism( phi ) then
        
        ## only in case the standard method for IsIsomorphism wasn't triggered:
        UpdateObjectsByMorphism( phi );
        
        chi := phi ^ -1;
        
        phi!.PostInverse := chi;
        phi!.PreInverse := chi;
        chi!.PostInverse := phi;
        chi!.PreInverse := phi;
        
        return chi;
        
    fi;
    
    DecideZero( phi );
    
    S := Source( phi );
    T := Range( phi );
    
    A := MatrixOfMap( phi );
    
    if IsHomalgLeftObjectOrMorphismOfLeftObjects( phi ) then
        chi := RightInverse( A );
    else
        chi := LeftInverse( A );
    fi;
    
    ## a post-inverse might still exist even if Right/LeftInverse fails
    ## (this is fundamentally different from the situation in PreInverse)
    if IsBool( chi ) then
        TryNextMethod( );
    fi;
    
    if HasIsZero( chi ) and IsZero( chi ) then
        
        ## if the matrix is nonzero we still don't know if a split exists
        
        SetIsZero( S, true );
        SetIsZero( phi, true );
        
        chi := TheZeroMorphism( T, S );
        
        phi!.PostInverse := chi;
        chi!.PreInverse := phi;
        
        return chi;
        
        return TheZeroMorphism( T, S );
        
    fi;
    
    chi := HomalgMap( chi, T, S );
    
    if IsMorphism( chi ) then
        
        DecideZero( chi );
        
        Assert( 3, IsEpimorphism( chi ) );
        
        SetIsSplitMonomorphism( phi, true );
        SetIsSplitEpimorphism( chi, true );
        
        ## a direct factor of an injective module is again injective [HS, I.6.3]
        if HasIsInjective( T ) and IsInjective( T ) then
            SetIsInjective( S, true );
        fi;
        
        phi!.PostInverse := chi;
        chi!.PreInverse := phi;
        
        return chi;
        
    fi;
    
    TryNextMethod( );
    
end );

##
InstallMethod( \/,
        "for a homalg map and a module of homomorphisms",
        [ IsMapOfFinitelyGeneratedModulesRep, IsFinitelyPresentedModuleRep ],
        
  function( phi, H )
    local S_T, left, R, n, gen, proc, rel, map;
    
    if not FunctorOfGenesis( H ) = Functor_Hom_for_fp_modules then
        
        TryNextMethod( );
        
    fi;
    
    S_T := Genesis( H )[1][1].arguments_of_functor;
    
    if not IsIdenticalObj( Source( phi ), S_T[1] ) or
       not IsIdenticalObj( Range( phi ), S_T[2] ) then
        
        return false;
        
    fi;
    
    if not IsMorphism( phi ) then
        
        return false;
        
    fi;
    
    left := IsHomalgLeftObjectOrMorphismOfLeftObjects( H );
    
    if IsZero( phi ) then
        
        R := HomalgRing( phi );
        
        n := NrGenerators( H );
        
        if left then
            
            return HomalgZeroMatrix( 1, n, R );
            
        else
            
            return HomalgZeroMatrix( n, 1, R );
            
        fi;
        
    fi;
    
    gen := GeneratorsOfModule( H );
    
    DecideZero( gen );
    
    proc := ProcedureToNormalizeGenerators( gen );
    
    rel := RelationsOfHullModule( gen );
    
    BasisOfModule( rel );
    
    gen := MatrixOfGenerators( H );
    
    DecideZero( phi );
    
    map := MatrixOfMap( phi );
    
    map := CallFuncList( proc[1], Concatenation( [ map ], proc{[ 2 .. Length( proc ) ]} ) );
    
    if left then	## H not phi !!!
        return RightDivide( map, gen, rel );
    else
        return LeftDivide( gen, map, rel );
    fi;
    
end );

####################################
#
# constructor functions and methods:
#
####################################

##  <#GAPDoc Label="HomalgMap">
##  <ManSection>
##    <Func Arg="mat, M, N" Name="HomalgMap" Label="constructor for maps"/>
##    <Func Arg="mat[, string]" Name="HomalgMap" Label="constructor for maps between free modules"/>
##    <Returns>a &homalg; map</Returns>
##    <Description>
##      This constructor returns a map (homomorphism) of finitely presented modules. It is represented by the
##      &homalg; matrix <A>mat</A> relative to the current set of generators of the source &homalg; module <A>M</A>
##      and target module <A>N</A> (&see; <Ref Sect="Modules:Constructors"/>). Unless the source module is free
##      <E>and</E> given on free generators the returned map will cautiously be indicated using
##      parenthesis: <Q>homomorphism</Q>. To verify if the result is indeed a well defined map use
##      <Ref BookName="homalg" Prop="IsMorphism"/>. If the presentations of the source or/and
##      target module are altered after the map was constructed, a new adapted representation matrix of the map is
##      automatically computed whenever needed. For this the internal transition matrices of the modules are used.
##      If source and target are identical objects, and only then, the map is created as a selfmap (endomorphism).
##      &homalg; uses the so-called <E>associative</E> convention for maps. This means that maps of left modules are
##      applied from the right, whereas maps of right modules from the left.
##      <Example><![CDATA[
##  gap> ZZ := HomalgRingOfIntegers( );;
##  gap> M := HomalgMatrix( "[ 2, 3, 4,   5, 6, 7 ]", 2, 3, ZZ );
##  <A 2 x 3 matrix over an internal ring>
##  gap> M := LeftPresentation( M );
##  <A non-torsion left module presented by 2 relations for 3 generators>
##  gap> N := HomalgMatrix( "[ 2, 3, 4, 5,   6, 7, 8, 9 ]", 2, 4, ZZ );
##  <A 2 x 4 matrix over an internal ring>
##  gap> N := LeftPresentation( N );
##  <A non-torsion left module presented by 2 relations for 4 generators>
##  gap> mat := HomalgMatrix( "[ \
##  > 1, 0, -2, -4, \
##  > 0, 1,  4,  7, \
##  > 1, 0, -2, -4  \
##  > ]", 3, 4, ZZ );
##  <A 3 x 4 matrix over an internal ring>
##  gap> phi := HomalgMap( mat, M, N );
##  <A "homomorphism" of left modules>
##  gap> IsMorphism( phi );
##  true
##  gap> phi;
##  <A homomorphism of left modules>
##  gap> Display( phi );
##  [ [   1,   0,  -2,  -4 ],
##    [   0,   1,   4,   7 ],
##    [   1,   0,  -2,  -4 ] ]
##  
##  the map is currently represented by the above 3 x 4 matrix
##  gap> ByASmallerPresentation( M );
##  <A rank 1 left module presented by 1 relation for 2 generators>
##  gap> Display( last );
##  Z/< 3 > + Z^(1 x 1)
##  gap> Display( phi );
##  [ [   2,   1,   0,  -1 ],
##    [   1,   0,  -2,  -4 ] ]
##  
##  the map is currently represented by the above 2 x 4 matrix
##  gap> ByASmallerPresentation( N );
##  <A rank 2 left module presented by 1 relation for 3 generators>
##  gap> Display( N );
##  Z/< 4 > + Z^(1 x 2)
##  gap> Display( phi );
##  [ [  -8,   0,   0 ],
##    [  -3,  -1,  -2 ] ]
##  
##  the map is currently represented by the above 2 x 3 matrix
##  gap> ByASmallerPresentation( phi );
##  <A non-zero homomorphism of left modules>
##  gap> Display( phi );
##  [ [   0,   0,   0 ],
##    [   1,  -1,  -2 ] ]
##  
##  the map is currently represented by the above 2 x 3 matrix
##  ]]></Example>
##  To construct a map with source being a not yet specified free module
##      <Example><![CDATA[
##  gap> N;
##  <A rank 2 left module presented by 1 relation for 3 generators>
##  gap> SetPositionOfTheDefaultSetOfGenerators( N, 1 );
##  gap> N;
##  <A rank 2 left module presented by 2 relations for 4 generators>
##  gap> psi := HomalgMap( mat, "free", N );
##  <A homomorphism of left modules>
##  gap> Source( psi );
##  <A free left module of rank 3 on free generators>
##  ]]></Example>
##  To construct a map between not yet specified free left modules
##      <Example><![CDATA[
##  gap> chi := HomalgMap( mat );	## or chi := HomalgMap( mat, "l" );
##  <A homomorphism of left modules>
##  gap> Source( chi );
##  <A free left module of rank 3 on free generators>
##  gap> Range( chi );
##  <A free left module of rank 4 on free generators>
##  ]]></Example>
##  To construct a map between not yet specified free right modules
##      <Example><![CDATA[
##  gap> kappa := HomalgMap( mat, "r" );
##  <A homomorphism of right modules>
##  gap> Source( kappa );
##  <A free right module of rank 4 on free generators>
##  gap> Range( kappa );
##  <A free right module of rank 3 on free generators>
##  ]]></Example>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallGlobalFunction( _HomalgMap,
  function( arg )
    local nargs, source, pos_s, target, pos_t, R, type, matrix, left, matrices, reduced_matrices,
          mat, nr_rows, nr_columns, index_pair, morphism, option;
    
    nargs := Length( arg );
    
    if IsHomalgRelations( arg[1] ) then
        mat := MatrixOfRelations( arg[1] );
    fi;
    
    if nargs > 1 then
        if IsHomalgModule( arg[2] ) then
            source := arg[2];
            pos_s := PositionOfTheDefaultPresentation( source );
        elif arg[2] = "free" and nargs > 2 and IsHomalgModule( arg[3] )
          and ( IsHomalgMatrix( arg[1] ) or IsHomalgRelations( arg[1] ) ) then
            if IsHomalgMatrix( arg[1] ) then
                mat := arg[1];
            fi;
            if IsHomalgLeftObjectOrMorphismOfLeftObjects( arg[3] ) then
                nr_rows := NrRows( mat );
                source := HomalgFreeLeftModule( nr_rows, HomalgRing( arg[3] ) );
            else
                nr_columns := NrColumns( mat );
                source := HomalgFreeRightModule( nr_columns, HomalgRing( arg[3] ) );
            fi;
            pos_s := PositionOfTheDefaultPresentation( source );
        elif IsHomalgRing( arg[2] ) and not ( IsList( arg[1] ) and nargs = 2 ) then
            source := "ring";
        elif IsList( arg[2] ) and IsHomalgModule( arg[2][1] ) and IsPosInt( arg[2][2] ) then
            source := arg[2][1];
            pos_s := arg[2][2];
            if not IsBound( SetsOfRelations( source )!.( pos_s ) ) then
                Error( "the source module does not possess a ", arg[2][2], ". set of relations (this positive number is given as the second entry of the list provided as the second argument)\n" );
            fi;
        fi;
    fi;
    
    if not IsBound( source ) then
        
        if IsHomalgMatrix( arg[1] ) then
            ResetFilterObj( arg[1], IsMutable );
            matrix := arg[1];
        elif IsHomalgRelations( arg[1] ) then
            matrix := MatrixOfRelations( arg[1] );
            left := IsHomalgRelationsOfLeftModule( arg[1] );
        elif IsHomalgRing( arg[nargs] ) then
            matrix := HomalgMatrix( arg[1], arg[nargs] );
        else
            Error( "The second argument must be the source module or the last argument should be an IsHomalgRing\n" );
        fi;
        
        R := HomalgRing( matrix );
        
        if nargs > 1 and IsStringRep( arg[2] ) and Length( arg[2] ) > 0
           and  LowercaseString( arg[2]{[1..1]} ) = "r" then
            left := false;	## we explicitly asked for a morphism of right modules
        elif not IsBound( left ) then
            left := true;
        fi;
        
        if left then
            source := HomalgFreeLeftModule( NrRows( matrix ), R );
            target := HomalgFreeLeftModule( NrColumns( matrix ), R );
            type := TheTypeHomalgMapOfLeftModules;
        else
            source := HomalgFreeRightModule( NrColumns( matrix ), R );
            target := HomalgFreeRightModule( NrRows( matrix ), R );
            type := TheTypeHomalgMapOfRightModules;
        fi;
        
        matrices := rec( );
        
        morphism := rec( 
                         matrices := matrices,
                         reduced_matrices := rec( ),
                         index_pairs_of_presentations := [ [ 1, 1 ] ]);
        
        matrices.( String( [ 1, 1 ] ) ) := matrix;
        
        ## Objectify:
        ObjectifyWithAttributes(
                morphism, type,
                Source, source,
                Range, target );
        
        if ( HasNrRelations( source ) = true and NrRelations( source ) = 0 ) then
            SetIsMorphism( morphism, true );
        fi;
        
        if HasIsZero( source ) and IsZero( source ) then
            SetIsGeneralizedMonomorphism( morphism, true );	## we don't know yet if IsMorhphism( morphism ) = true
        fi;
        
        if HasIsZero( target ) and IsZero( target ) then
            SetIsGeneralizedEpimorphism( morphism, true );	## we don't know yet if IsMorhphism( morphism ) = true
        fi;
        
        return morphism;
        
    fi;
    
    if nargs > 2 then
        if IsHomalgModule( arg[3] ) then
            target := arg[3];
            pos_t := PositionOfTheDefaultPresentation( target );
        elif arg[3] = "free" and IsHomalgModule ( source )
          and ( IsHomalgMatrix( arg[1] ) or IsHomalgRelations( arg[1] ) ) then
            if IsHomalgLeftObjectOrMorphismOfLeftObjects( source ) then
                if IsHomalgMatrix( arg[1] ) then
                    nr_columns := NrColumns( arg[1] );
                elif IsHomalgRelations( arg[1] ) then
                    nr_columns := NrColumns( MatrixOfRelations( arg[1] ) );
                fi;
                target := HomalgFreeLeftModule( nr_columns, HomalgRing( arg[1] ) );
            else
                if IsHomalgMatrix( arg[1] ) then
                    nr_rows := NrRows( arg[1] );
                elif IsHomalgRelations( arg[1] ) then
                    nr_rows := NrRows( MatrixOfRelations( arg[1] ) );
                fi;
                target := HomalgFreeRightModule( nr_rows, HomalgRing( arg[1] ) );
            fi;
            pos_t := PositionOfTheDefaultPresentation( target );
        elif IsHomalgRing( arg[3] ) then
            if source = "ring" then
                source := HomalgFreeLeftModule( 1, arg[2] );
                if not IsIdenticalObj( arg[2], arg[3] ) then
                    Error( "the source and target modules must be defined over the same ring\n" );
                fi;
                target := source;	## we get an endomorphism
                pos_s := PositionOfTheDefaultPresentation( source );
                pos_t := pos_s;
            else
                target := HomalgFreeLeftModule( 1, arg[3] );
                pos_t := PositionOfTheDefaultPresentation( target );
            fi;
        elif IsList( arg[3] ) and IsHomalgModule( arg[3][1] ) and IsPosInt( arg[3][2] ) then
            target := arg[3][1];
            pos_t := arg[3][2];
            if not IsBound( SetsOfRelations( target )!.( pos_t ) ) then
                Error( "the target module does not possess a ", arg[3][2], ". set of relations (this positive number is given as the second entry of the list provided as the third argument)\n" );
            fi;
        fi;
    elif source = "ring" then
        source := HomalgFreeLeftModule( 1, arg[2] );
        target := source;	## we get an endomorphism
        pos_s := PositionOfTheDefaultPresentation( source );
        pos_t := pos_s;
    else
        pos_t := pos_s;
    fi;
    
    R := HomalgRing( source );
    
    if IsBound( target ) and not IsIdenticalObj( source, target ) then
        if not IsIdenticalObj( R, HomalgRing( target ) ) then
            Error( "the source and target modules must be defined over the same ring\n" );
        elif IsHomalgLeftObjectOrMorphismOfLeftObjects( source ) and IsHomalgLeftObjectOrMorphismOfLeftObjects( target ) then
            type := TheTypeHomalgMapOfLeftModules;
        elif IsHomalgRightObjectOrMorphismOfRightObjects( source ) and IsHomalgRightObjectOrMorphismOfRightObjects( target ) then
            type := TheTypeHomalgMapOfRightModules;
        else
            Error( "the source and target modules of a morphism must either both be left or both be right modules\n" );
        fi;
    else
        target := source;
        if IsHomalgLeftObjectOrMorphismOfLeftObjects( source ) then
            type := TheTypeHomalgSelfMapOfLeftModules;
        else
            type := TheTypeHomalgSelfMapOfRightModules;
        fi;
    fi;
    
    if IsHomalgLeftObjectOrMorphismOfLeftObjects( source ) then
        nr_rows := NrGenerators( source, pos_s );
        nr_columns := NrGenerators( target, pos_t );
        index_pair := [ pos_s, pos_t ];
    else
        nr_columns := NrGenerators( source, pos_s );
        nr_rows := NrGenerators( target, pos_t );
        index_pair := [ pos_t, pos_s ];
    fi;
    
    matrices := rec( );
    
    reduced_matrices := rec( );
    
    morphism := rec( 
                     matrices := matrices,
                     reduced_matrices := reduced_matrices,
                     index_pairs_of_presentations := [ index_pair ]);
    
    if IsList( arg[1] ) and Length( arg[1] ) = 1 and IsString( arg[1][1] ) and Length( arg[1][1] ) > 0 then
        
        option := arg[1][1];
        
        if Length( option ) > 3 and LowercaseString( option{[1..4]} ) = "zero" then
            ## the zero map:
            
            matrix := HomalgZeroMatrix( nr_rows, nr_columns, R );
            
            matrices.( String( index_pair ) ) := matrix;
            
            reduced_matrices.( String( index_pair ) ) := matrix;
            
            ## Objectify:
            ObjectifyWithAttributes(
                    morphism, type,
                    Source, source,
                    Range, target,
                    IsZero, true );
            
            if HasIsZero( source ) and IsZero( source ) then
                SetIsSplitMonomorphism( morphism, true );
            fi;
            
            if HasIsZero( target ) and IsZero( target ) then
                SetIsSplitEpimorphism( morphism, true );
            fi;
            
        elif Length( option ) > 7 and  LowercaseString( option{[1..8]} ) = "identity" then
            ## the identity map:
            
            if nr_rows <> nr_columns then
                Error( "for a matrix of a morphism to be the identity matrix the number of generators of the source and target module must coincide\n" );
            fi;
            
            matrix := HomalgIdentityMatrix( nr_rows, R );
            
            matrices.( String( index_pair ) ) := matrix;
            
            if IsIdenticalObj( source, target ) then
                if pos_s = pos_t then
                    ## Objectify:
                    ObjectifyWithAttributes(
                            morphism, type,
                            Source, source,
                            Range, target,
                            IsMorphism, true,
                            IsOne, true );
                else
                    ## Objectify:
                    ObjectifyWithAttributes(
                            morphism, type,
                            Source, source,
                            Range, target );
                fi;
            else
                ## Objectify:
                ObjectifyWithAttributes(
                        morphism, type,
                        Source, source,
                        Range, target,
                        IsMorphism, true,
                        IsEpimorphism, true );
            fi;
            
        else
            Error( "wrong first argument: ", arg[1], "\n" );
        fi;
        
    else
        
        if IsHomalgMatrix( arg[1] ) then
            if not IsIdenticalObj( HomalgRing( arg[1] ), R ) then
                Error( "the matrix and the modules are not defined over identically the same ring\n" );
            fi;
            ResetFilterObj( arg[1], IsMutable );
            matrix := arg[1];
        elif IsHomalgRelations( arg[1] ) then
            if not IsIdenticalObj( HomalgRing( arg[1] ), R ) then
                Error( "the matrix and the modules are not defined over identically the same ring\n" );
            fi;
            matrix := MatrixOfRelations( arg[1] );
        elif IsString( arg[1] ) then
            if IsHomalgLeftObjectOrMorphismOfLeftObjects( source ) then
                nr_rows := NrGenerators( source, pos_s );
                nr_columns := NrGenerators( target, pos_t );
            elif IsHomalgRightObjectOrMorphismOfRightObjects( source ) then
                nr_rows := NrGenerators( target, pos_t );
                nr_columns := NrGenerators( source, pos_s );
            fi;
            matrix := HomalgMatrix( arg[1], nr_rows, nr_columns, R );
        elif IsList( arg[1] ) then
            matrix := HomalgMatrix( arg[1], R );
        else
            Error( "the first argument must be in { IsHomalgMatrix, IsHomalgRelations, IsMatrix, IsString, IsList } but received: ",  arg[1], "\n" );
        fi;
        
        if IsHomalgLeftObjectOrMorphismOfLeftObjects( source )
           and ( NrGenerators( source, pos_s ) <> NrRows( matrix )
                 or NrGenerators( target, pos_t ) <> NrColumns( matrix ) ) then
            Error( "the dimensions of the matrix do not match the numbers of generators of the modules\n" );
        elif IsHomalgRightObjectOrMorphismOfRightObjects( source )
           and ( NrGenerators( source, pos_s ) <> NrColumns( matrix )
                 or NrGenerators( target, pos_t ) <> NrRows( matrix ) ) then
            Error( "the dimensions of the matrix do not match the numbers of generators of the modules\n" );
        fi;
        
        matrices.( String( index_pair ) ) := matrix;
        
        ## Objectify:
        ObjectifyWithAttributes(
                morphism, type,
                Source, source,
                Range, target );
        
    fi;
    
    if ( HasNrRelations( source ) = true and NrRelations( source ) = 0 ) then
        SetIsMorphism( morphism, true );
    fi;
    
    if HasIsZero( source ) and IsZero( source ) then
        SetIsGeneralizedMonomorphism( morphism, true );	## we don't know yet if IsMorhphism( morphism ) = true
    fi;
    
    if HasIsZero( target ) and IsZero( target ) then
        SetIsGeneralizedEpimorphism( morphism, true );	## we don't know yet if IsMorhphism( morphism ) = true
    fi;
    
    return morphism;
    
end );

##
InstallMethod( HomalgMap,
        "for three objects",
        [ IsObject, IsObject, IsObject ],
        
  _HomalgMap );

##
InstallMethod( HomalgMap,
        "for two objects",
        [ IsObject, IsObject ],
        
  _HomalgMap );

##
InstallMethod( HomalgMap,
        "for an object",
        [ IsObject ],
        
  _HomalgMap );

##  <#GAPDoc Label="HomalgZeroMap">
##  <ManSection>
##    <Func Arg="M, N" Name="HomalgZeroMap" Label="constructor for zero maps"/>
##    <Returns>a &homalg; map</Returns>
##    <Description>
##      The constructor returns the zero map between the source &homalg; module <A>M</A>
##      and the target &homalg; module <A>N</A>.
##      <Example><![CDATA[
##  gap> ZZ := HomalgRingOfIntegers( );;
##  gap> M := HomalgMatrix( "[ 2, 3, 4,   5, 6, 7 ]", 2, 3, ZZ );
##  <A 2 x 3 matrix over an internal ring>
##  gap> M := LeftPresentation( M );
##  <A non-torsion left module presented by 2 relations for 3 generators>
##  gap> N := HomalgMatrix( "[ 2, 3, 4, 5,   6, 7, 8, 9 ]", 2, 4, ZZ );
##  <A 2 x 4 matrix over an internal ring>
##  gap> N := LeftPresentation( N );
##  <A non-torsion left module presented by 2 relations for 4 generators>
##  gap> HomalgZeroMap( M, N );
##  <The zero morphism of left modules>
##  ]]></Example>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallGlobalFunction( HomalgZeroMap,
  function( arg )
    
    return CallFuncList( HomalgMap, Concatenation( [ [ "zero" ] ], arg ) );
    
end );

##  <#GAPDoc Label="HomalgIdentityMap">
##  <ManSection>
##    <Func Arg="M, N" Name="HomalgIdentityMap" Label="constructor for identity maps"/>
##    <Returns>a &homalg; map</Returns>
##    <Description>
##      The constructor returns the identity map of the &homalg; module <A>M</A>.
##      <Example><![CDATA[
##  gap> ZZ := HomalgRingOfIntegers( );;
##  gap> M := HomalgMatrix( "[ 2, 3, 4,   5, 6, 7 ]", 2, 3, ZZ );
##  <A 2 x 3 matrix over an internal ring>
##  gap> M := LeftPresentation( M );
##  <A non-torsion left module presented by 2 relations for 3 generators>
##  gap> HomalgIdentityMap( M );
##  <The identity morphism of a non-zero left module>
##  ]]></Example>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallGlobalFunction( HomalgIdentityMap,
  function( arg )
    
    return CallFuncList( HomalgMap, Concatenation( [ [ "identity" ] ], arg ) );
    
end );

##
InstallMethod( OnAFreeSource,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( phi )
    local psi;
    
    psi := HomalgMap( MatrixOfMap( phi ), "free", Range( phi ) );
    
    if HasIsMorphism( phi ) and IsMorphism( phi ) or HasIsGeneralizedMorphismWithFullDomain( phi ) and IsGeneralizedMorphismWithFullDomain( phi ) then
        Assert( 3, IsMorphism( psi ) );
        SetIsMorphism( psi, true );
    fi;
    
    if HasIsEpimorphism( phi ) and IsEpimorphism( phi ) then
        Assert( 3, IsEpimorphism( psi ) );
        SetIsEpimorphism( psi, true );
    fi;
    
    return psi;
    
end );

## works without side effects
InstallMethod( RemoveMorphismAid,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( phi )
    
    return HomalgMap( MatrixOfMap( phi ), Source( phi ), Range( phi ) );
    
end );

## works without side effects
InstallMethod( GeneralizedMorphism,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep, IsObject ],
        
  function( phi, morphism_aid_map )
    local morphism_aid_map1, psi;
    
    if not IsHomalgMap( morphism_aid_map ) and not ( IsList( morphism_aid_map ) and Length( morphism_aid_map ) = 1 and IsHomalgMap( morphism_aid_map[1] ) ) then
        return phi;
    fi;
    
    if not IsList( morphism_aid_map ) and not IsIdenticalObj( Range( phi ), Range( morphism_aid_map ) ) then
        Error( "the targets of the two morphisms must coincide or the aid must be a list\n" );
    fi;
    
    if IsList( morphism_aid_map ) then
        morphism_aid_map1 := morphism_aid_map;
    else
        ## we don't need the source of the morphism aid map
        morphism_aid_map1 := OnAFreeSource( morphism_aid_map );
    fi;
    
    ## prepare a copy of phi
    psi := HomalgMap( MatrixOfMap( phi ), Source( phi ), Range( phi ) );
    
    SetMorphismAid( psi, morphism_aid_map1 );
    
    ## some properties of the morphism phi imply
    ## properties for the generalized morphism psi
    SetPropertiesOfGeneralizedMorphism( psi, phi );
    
    return psi;
    
end );

## works without side effects
InstallMethod( AddToMorphismAid,
        "for homalg maps",
        [ IsHomalgMap, IsObject ],
        
  function( phi, morphism_aid_map )
    local morphism_aid_map1, morphism_aid_map0;
    
    if not IsHomalgMap( morphism_aid_map ) then
        return phi;
    fi;
    
    if not IsIdenticalObj( Range( phi ), Range( morphism_aid_map ) ) then
        Error( "the targets of the two morphisms must coincide\n" );
    fi;
    
    ## we don't need the source of the new morphism aid map
    morphism_aid_map1 := OnAFreeSource( morphism_aid_map );
    
    if HasMorphismAid( phi ) then
        ## we don't need the source of the old morphism aid map
        morphism_aid_map0 := OnAFreeSource( MorphismAid( phi ) );
        morphism_aid_map1 := CoproductMorphism( morphism_aid_map0, morphism_aid_map1 );
    fi;
    
    return GeneralizedMorphism( phi, morphism_aid_map1 );
    
end );

##
InstallMethod( ShallowCopy,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( phi )
    local psi;
    
    if HasMorphismAid( phi ) then
        TryNextMethod( );
    fi;
    
    if IsHomalgEndomorphism( phi ) then
        psi := HomalgMap( MatrixOfMap( phi ), ShallowCopy( Source( phi ) ) );
    else
        psi := HomalgMap( MatrixOfMap( phi ), ShallowCopy( Source( phi ) ), ShallowCopy( Range( phi ) ) );
    fi;
    
    MatchPropertiesAndAttributes( phi, psi, LIHOM.intrinsic_properties, LIHOM.intrinsic_attributes );
    
    return psi;
    
end );

##
InstallMethod( UpdateObjectsByMorphism,
        "for homalg maps",
        [ IsHomalgMap and IsIsomorphism ],
        
  function( phi )
    
    if HasIsZero( Source( phi ) ) or HasIsZero( Range( phi ) ) then
        IsZero( phi );
    fi;
    
    MatchPropertiesAndAttributes( Source( phi ), Range( phi ), LIMOD.intrinsic_properties, LIMOD.intrinsic_attributes );
    
end );

##
InstallMethod( AnIsomorphism,
        "for homalg modules",
        [ IsFinitelyPresentedModuleRep ],
        
  function( M )
    local rel, N, iso;
    
    rel := RelationsOfModule( M );
    
    ## important since each set of relations knows the module it represents
    rel := ShallowCopy( rel );
    
    N := Presentation( GeneratorsOfModule( M ), rel );
    
    ## define the obvious isomorphism between N an M
    iso := HomalgIdentityMatrix( NrGenerators( M ), HomalgRing( M ) );
    
    iso := HomalgMap( iso, N, M );
    
    SetIsIsomorphism( iso, true );
    
    ## copy the known properties and attributes of im to def
    UpdateObjectsByMorphism( iso );
    
    return iso;
    
end );

##
InstallMethod( Pullback,
        "for a ring map and a module map",
        [ IsHomalgRingMap, IsMapOfFinitelyGeneratedModulesRep ],
        
  function( phi, f )
    local Sf, Tf, S, T, map;
    
    Sf := Source( f );
    Tf := Range( f );
    
    if IsBound( Sf!.distinguished ) and Sf!.distinguished = true then
        if IsHomalgLeftObjectOrMorphismOfLeftObjects( Sf ) then
            S := 1 * Range( phi );
        else
            S := Range( phi ) * 1;
        fi;
    else
        S := Pullback( phi, Sf );
    fi;
    
    if IsBound( Tf!.distinguished ) and Tf!.distinguished = true then
        if IsHomalgLeftObjectOrMorphismOfLeftObjects( Tf ) then
            T := 1 * Range( phi );
        else
            T := Range( phi ) * 1;
        fi;
    else
        T := Pullback( phi, Tf );
    fi;
    
    map := Pullback( phi, MatrixOfMap( f ) );
    
    map := HomalgMap( map, S, T );
    
    return map;
    
end );

####################################
#
# View, Print, and Display methods:
#
####################################

##
InstallMethod( Display,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  function( o )
  
    Display( o, "" );
    
end );


##
InstallMethod( Display,
        "for homalg maps",
        [ IsMapOfFinitelyGeneratedModulesRep, IsString ],
        
  function( o, extra_information )
    local T, mat;
    
    T := Range( o );
    
    mat := MatrixOfMap( o );
    
    Display( mat );
    
    if extra_information <> "" then
        Print( "\nthe ", extra_information, " map is currently represented by the above ", NrRows( mat ), " x ", NrColumns( mat ), " matrix\n" );
    else
        Print( "\nthe map is currently represented by the above ", NrRows( mat ), " x ", NrColumns( mat ), " matrix\n" );
    fi;
    
end );
