Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  BasicFunctors.gi   Sprache: unbekannt

 
# SPDX-License-Identifier: GPL-2.0-or-later
# Modules: A homalg based package for the Abelian category of finitely presented modules over computable rings
#
# Implementations
#

##  Implementation stuff for some tool functors.

####################################
#
# install global functions/variables:
#
####################################

##
## Cokernel
##

##
BindGlobal( "_Functor_Cokernel_OnModules", ### defines: Cokernel(Epi)
  function( phi )
    local R, T, p, rel, gen, coker, id, epi, gen_iso, img_emb, emb;
    
    if HasCokernelEpi( phi ) then
        return Range( CokernelEpi( phi ) );
    fi;
    
    R := HomalgRing( phi );
    
    T := Range( phi );
    
    ## this is probably obsolete but clarifies our idea:
    p := PositionOfTheDefaultSetOfGenerators( T );  ## avoid future possible side effects of the following command(s)
    
    rel := UnionOfRelations( phi );
    
    gen := GeneratorsOfModule( T );
    
    gen := UnionOfRelations( gen, rel * gen );
    
    coker := Presentation( gen, rel );
    
    ## the identity matrix is the matrix of the natural epimorphism
    ## w.r.t. the p-th set of relations of T and the first set of relations of coker:
    id := HomalgIdentityMatrix( NrGenerators( gen ), R );
    
    ## the natural epimorphism:
    epi := HomalgMap( id, [ T, p ], [ coker, 1 ] );
    
    ## for graded modules we need to know on which presentation the cokernel was computed
    epi!.DefaultPresentationOfCokernelEpi := [ p, 1 ];
    
    ## even if IsMorphism( phi ) = false, in this data structure IsMorphism( epi ) = true
    SetIsMorphism( epi, true );
    
    ## we cannot check this assertion, since
    ## checking it would cause an infinite loop
    SetIsEpimorphism( epi, true );
    
    ## set the attribute CokernelEpi (specific for Cokernel):
    SetCokernelEpi( phi, epi );
    
    ## the generalized inverse of the natural epimorphism
    ## (cf. [Bar, Cor. 4.8])
    gen_iso := HomalgMap( id, [ coker, 1 ], [ T, p ] );
    
    ## set the morphism aid map
    SetMorphismAid( gen_iso, phi );
    
    ## set the generalized inverse of the natural epimorphism
    SetInverseOfGeneralizedMorphismWithFullDomain( epi, gen_iso );
    
    ## we cannot check this assertion, since
    ## checking it would cause an infinite loop
    SetIsGeneralizedIsomorphism( gen_iso, true );
    
    #=====# end of the core procedure #=====#
    
    ## abelian category: [HS, Prop. II.9.6]
    if HasImageObjectEmb( phi ) then
        img_emb := ImageObjectEmb( phi );
        SetKernelEmb( epi, img_emb );
        if not HasCokernelEpi( img_emb ) then
            SetCokernelEpi( img_emb, epi );
        fi;
    elif HasIsMonomorphism( phi ) and IsMonomorphism( phi ) then
        SetKernelEmb( epi, phi );
    fi;
    
    ## this is in general NOT a morphism,
    ## BUT it is one modulo the image of phi in T, and then even a monomorphism:
    ## this is enough for us since we will always view it this way (cf. [BR08, 3.1.1,(2), 3.1.2] )
    emb := HomalgMap( id, [ coker, 1 ], [ T, p ] );
    SetMorphismAid( emb, phi );
    
    ## we cannot check this assertion, since
    ## checking it would cause an infinite loop
    SetIsGeneralizedIsomorphism( emb, true );
    
    ## save the natural embedding in the cokernel (thanks GAP):
    coker!.NaturalGeneralizedEmbedding := emb;
    
    return coker;
    
end );

##  <#GAPDoc Label="functor_Cokernel:code">
##      <Listing Type="Code"><![CDATA[
BindGlobal( "functor_Cokernel_for_fp_modules",
        CreateHomalgFunctor(
                [ "name", "Cokernel" ],
                [ "category", HOMALG_MODULES.category ],
                [ "operation", "Cokernel" ],
                [ "natural_transformation", "CokernelEpi" ],
                [ "special", true ],
                [ "number_of_arguments", 1 ],
                [ "1", [ [ "covariant" ],
                        [ IsMapOfFinitelyGeneratedModulesRep,
                          [ IsHomalgChainMorphism, IsImageSquare ] ] ] ],
                [ "OnObjects", _Functor_Cokernel_OnModules ]
                )
        );
##  ]]></Listing>
##  <#/GAPDoc>

functor_Cokernel_for_fp_modules!.ContainerForWeakPointersOnComputedBasicMorphisms := true;

##
## ImageObject
##

BindGlobal( "_Functor_ImageObject_OnModules", ### defines: ImageObject(Emb)
  function( phi )
    local T, p, img, emb, coker_epi, img_submodule;
    
    if HasImageObjectEmb( phi ) then
        return Source( ImageObjectEmb( phi ) );
    fi;
    
    T := Range( phi );
    
    ## this is probably obsolete but clarifies our idea:
    p := PositionOfTheDefaultSetOfGenerators( T );  ## avoid future possible side effects of the following command(s)
    
    ## the image module
    img := MatrixOfMap( phi ) / T;
    
    ## emb is the matrix of the natural embedding
    ## w.r.t. the first set of relations of img and the p-th set of relations of T
    emb := MatrixOfGenerators( img, 1 );
    
    emb := HomalgMap( emb, [ img, 1 ], [ T, p ] );
    
    ## check assertion
    Assert( 5, IsMonomorphism( emb ) );
    SetIsMonomorphism( emb, true );
    
    INSTALL_TODO_LIST_ENTRIES_FOR_MORPHISMS_AND_IMAGE_EMBEDDINGS( phi, emb );
    
    ## set the attribute ImageObjectEmb (specific for ImageObject):
    ## (since ImageObjectEmb is listed below as a natural transformation
    ##  for the functor ImageObject, a method will be automatically installed
    ##  by InstallFunctor to fetch it by first invoking the main operation ImageObject)
    SetImageObjectEmb( phi, emb );
    
    #=====# end of the core procedure #=====#
    
    ## abelian category: [HS, Prop. II.9.6]
    if HasCokernelEpi( phi ) then
        coker_epi := CokernelEpi( phi );
        SetCokernelEpi( emb, coker_epi );
        if not HasKernelEmb( coker_epi ) then
            SetKernelEmb( coker_epi, emb );
        fi;
    fi;
    
    ## at last define the image submodule
    img_submodule := ImageSubobject( phi );
    
    SetUnderlyingSubobject( img, img_submodule );
    SetEmbeddingInSuperObject( img_submodule, emb );
    
    MatchPropertiesAndAttributesOfSubobjectAndUnderlyingObject( img_submodule, img );
    
    ## save the natural embedding in the image (thanks GAP):
    img!.NaturalGeneralizedEmbedding := emb;
    
    return img;
    
end );

##  <#GAPDoc Label="functor_ImageObject:code">
##      <Listing Type="Code"><![CDATA[
BindGlobal( "functor_ImageObject_for_fp_modules",
        CreateHomalgFunctor(
                [ "name", "ImageObject for modules" ],
                [ "category", HOMALG_MODULES.category ],
                [ "operation", "ImageObject" ],
                [ "natural_transformation", "ImageObjectEmb" ],
                [ "number_of_arguments", 1 ],
                [ "1", [ [ "covariant" ],
                        [ IsMapOfFinitelyGeneratedModulesRep and
                          AdmissibleInputForHomalgFunctors ] ] ],
                [ "OnObjects", _Functor_ImageObject_OnModules ]
                )
        );
##  ]]></Listing>
##  <#/GAPDoc>

functor_ImageObject_for_fp_modules!.ContainerForWeakPointersOnComputedBasicMorphisms := true;

##
## Hom
##

BindGlobal( "_Functor_Hom_OnModules", ### defines: Hom (object part)
  function( M, N )
    local s, t, dM, dN, P1, l0, l1, _l0, matM, matN, R, HP0N, HP1N, r, c, idN,
          alpha, hom, gen, proc_to_readjust_generators, proc_to_normalize_generators, p;
    
    CheckIfTheyLieInTheSameCategory( M, N );
    
    s := PositionOfTheDefaultSetOfGenerators( M );
    t := PositionOfTheDefaultSetOfGenerators( N );
    
    dM := PresentationMorphism( M );
    dN := PresentationMorphism( N );
    
    P1 := Source( dM );
    
    l0 := NrGenerators( M );
    l1 := NrGenerators( P1 );
    
    _l0 := NrGenerators( N );
    
    matM := MatrixOfMap( dM );
    matN := MatrixOfMap( dN );
    
    R := HomalgRing( M );
    
    if l0 = 0 then
        HP0N := HomalgZeroMatrix( 0, 0, R );
    else
        HP0N := DiagMat( ListWithIdenticalEntries( l0, Involution( matN ) ) );
    fi;
    
    if l1 = 0 then
        HP1N := HomalgZeroMatrix( 0, 0, R );
    else
        HP1N := DiagMat( ListWithIdenticalEntries( l1, Involution( matN ) ) );
    fi;
    
    if IsHomalgLeftObjectOrMorphismOfLeftObjects( M ) then
        r := l0;
        c := _l0;
        
        proc_to_normalize_generators :=
          function( mat, M_with_s, N_with_t )
            local M, s, N, t, mor, mat_old;
            
            ## for better readability of the code:
            M := M_with_s[1];
            s := M_with_s[2];
            
            N := N_with_t[1];
            t := N_with_t[2];
            
            ## we assume mat to be a matrix of a morphism
            ## w.r.t. the CURRENT generators of source and target:
            mor := HomalgMap( mat, M, N );
            
            mat_old := MatrixOfMap( mor, s, t );
            
            return ConvertTransposedMatrixToColumn( mat_old );
        end;
        
        proc_to_readjust_generators :=
          function( gen, M_with_s, N_with_t )
            local c, r, mat_old, mor;
            
            ## M_with_s = [ M, s ]
            ## N_with_t = [ N, t ]
            
            r := CallFuncList( NrGenerators, M_with_s );
            c := CallFuncList( NrGenerators, N_with_t );
            
            mat_old := ConvertColumnToTransposedMatrix( gen, r, c );
            
            ## the matrix of the morphism will be displayed
            ## w.r.t. the CURRENT generators of source and target
            mor := HomalgMap( mat_old, M_with_s, N_with_t );
            
            ## check assertion
            Assert( 3, IsMorphism( mor ) );
            
            SetIsMorphism( mor, true );
            
            return mor;
        end;
        
        HP0N := RightPresentation( HP0N );
        HP1N := RightPresentation( HP1N );
        
    else
        r := _l0;
        c := l0;
        
        proc_to_normalize_generators :=
          function( mat, M_with_s, N_with_t )
            local M, s, N, t, mor, mat_old;
            
            ## for better readability of the code:
            M := M_with_s[1];
            s := M_with_s[2];
            
            N := N_with_t[1];
            t := N_with_t[2];
            
            ## we assume mat to be a matrix of a morphism
            ## w.r.t. the CURRENT generators of source and target:
            mor := HomalgMap( mat, M, N );
            
            mat_old := MatrixOfMap( mor, s, t );
            
            return ConvertTransposedMatrixToRow( mat_old );
        end;
        
        proc_to_readjust_generators :=
          function( gen, M_with_s, N_with_t )
            local c, r, mat_old, mor;
            
            ## M_with_s = [ M, s ]
            ## N_with_t = [ N, t ]
            
            c := CallFuncList( NrGenerators, M_with_s );
            r := CallFuncList( NrGenerators, N_with_t );
            
            mat_old := ConvertRowToTransposedMatrix( gen, r, c );
            
            ## the matrix of the morphism will be displayed
            ## w.r.t. the CURRENT generators of source and target
            mor := HomalgMap( mat_old, M_with_s, N_with_t );
            
            ## check assertion
            Assert( 3, IsMorphism( mor ) );
            
            SetIsMorphism( mor, true );
            
            return mor;
        end;
        
        HP0N := LeftPresentation( HP0N );
        HP1N := LeftPresentation( HP1N );
        
    fi;
    
    idN := HomalgIdentityMatrix( _l0, R );
    
    alpha := KroneckerMat( matM, idN );
    
    alpha := HomalgMap( alpha, HP0N, HP1N );
    
    SetIsMorphism( alpha, true );
    
    hom := Kernel( alpha );
    
    #=====# end of the core procedure #=====#
    
    SetProcedureToNormalizeGenerators( hom, [ proc_to_normalize_generators, [ M, s ], [ N, t ] ] );
    SetProcedureToReadjustGenerators( hom, [ proc_to_readjust_generators, [ M, s, ], [ N, t ] ] );
    
    return hom;
    
end );

##
BindGlobal( "_Functor_Hom_OnMaps", ### defines: Hom (morphism part)
  function( F_source, F_target, arg_before_pos, phi, arg_behind_pos )
    local R, L, idL, hull_phi, covariant, emb_source, emb_target, mor;
    
    R := HomalgRing( phi );
    
    if arg_before_pos = [ ] and Length( arg_behind_pos ) = 1 then
        ## Hom( phi, L )
        
        L := arg_behind_pos[1];
        
        idL := HomalgIdentityMatrix( NrGenerators( L ), R );
        
        hull_phi := KroneckerMat( MatrixOfMap( phi ), idL );
        
        covariant := false;
        
    elif Length( arg_before_pos ) = 1 and arg_behind_pos = [ ] then
        ## Hom( L, phi )
        
        L := arg_before_pos[1];
        
        idL := HomalgIdentityMatrix( NrGenerators( L ), R );
        
        hull_phi := Involution( KroneckerMat( idL, MatrixOfMap( phi ) ) );
        
        covariant := true;
        
    else
        Error( "wrong input\n" );
    fi;
    
    emb_source := NaturalGeneralizedEmbedding( F_source );
    emb_target := NaturalGeneralizedEmbedding( F_target );
    
    hull_phi := HomalgMap( hull_phi, Range( emb_source ), Range( emb_target ) );
    
    SetIsMorphism( hull_phi, true );
    
    mor := CompleteImageSquare( emb_source, hull_phi, emb_target );
    
    ## HasIsIsomorphism( phi ) and IsIsomorphism( phi ), resp.
    ## HasIsMorphism( phi ) and IsMorphism( phi ), and
    ## UpdateObjectsByMorphism( mor )
    ## will be taken care of in FunctorMap
    
    if covariant then
        ## Hom( L, - )
        
        if HasIsMonomorphism( phi ) and IsMonomorphism( phi ) then
            
            ## check assertion
            Assert( 3, IsMonomorphism( mor ) );
            
            SetIsMonomorphism( mor, true );
            
        fi;
        
        if HasIsEpimorphism( phi ) and IsEpimorphism( phi ) and
          HasIsProjective( L ) and IsProjective( L ) then
            
            ## check assertion
            Assert( 3, IsEpimorphism( mor ) );
            
            SetIsEpimorphism( mor, true );
            
        fi;
        
    else
        ## Hom( -, L )
        
        if HasIsEpimorphism( phi ) and IsEpimorphism( phi ) then
            
            ## check assertion
            Assert( 3, IsMonomorphism( mor ) );
            
            SetIsMonomorphism( mor, true );

        fi;
        
        if HasIsMonomorphism( phi ) and IsMonomorphism( phi ) and
          HasIsInjective( L ) and IsInjective( L ) then
            
            ## check assertion
            Assert( 3, IsEpimorphism( mor ) );
            
            SetIsEpimorphism( mor, true );
            
        fi;
        
    fi;
    
    return mor;
    
end );

##  <#GAPDoc Label="Functor_Hom:code">
##      <Listing Type="Code"><![CDATA[
BindGlobal( "Functor_Hom_for_fp_modules",
        CreateHomalgFunctor(
                [ "name", "Hom" ],
                [ "category", HOMALG_MODULES.category ],
                [ "operation", "Hom" ],
                [ "number_of_arguments", 2 ],
                [ "1", [ [ "contravariant", "right adjoint", "distinguished" ] ] ],
                [ "2", [ [ "covariant", "left exact" ] ] ],
                [ "OnObjects", _Functor_Hom_OnModules ],
                [ "OnMorphisms", _Functor_Hom_OnMaps ],
                [ "MorphismConstructor", HOMALG_MODULES.category.MorphismConstructor ]
                )
        );
##  ]]></Listing>
##  <#/GAPDoc>

Functor_Hom_for_fp_modules!.ContainerForWeakPointersOnComputedBasicObjects := true;

Functor_Hom_for_fp_modules!.ContainerForWeakPointersOnComputedBasicMorphisms := true;

##
InstallMethod( NatTrIdToHomHom_R,
        "for homalg modules",
        [ IsFinitelyPresentedModuleRep ],
        
  function( M )
    local HM, iota, HHM, bas, epsilon;
    
    HM := Hom( M );
    
    iota := MatrixOfGenerators( HM );
    
    HHM := Hom( HM );
    
    bas := MatrixOfGenerators( HHM );
    
    if IsHomalgLeftObjectOrMorphismOfLeftObjects( M ) then
        epsilon := RightDivide( iota, bas );
    else
        epsilon := LeftDivide( iota, bas );
    fi;
    
    epsilon := HomalgMap( epsilon, M, HHM );
    
    SetPropertiesIfKernelIsTorsionObject( epsilon );
    
    return epsilon;
    
end );

##
InstallMethod( LeftDualizingFunctor,
        "for homalg rings",
        [ IsHomalgRing, IsString ],
        
  function( R, name )
    
    return InsertObjectInMultiFunctor( Functor_Hom_for_fp_modules, 2, 1 * R, name );
    
end );

##
InstallMethod( LeftDualizingFunctor,
        "for homalg rings",
        [ IsHomalgRing ],
        
  function( R )
    
    if not IsBound( R!.Functor_R_Hom ) then
        if IsBound( R!.creation_number ) then
            R!.Functor_R_Hom := LeftDualizingFunctor( R, Concatenation( "R", String( R!.creation_number ), "_Hom" ) );
        else
            Error( "the homalg ring doesn't have a creation number\n" );
        fi;
    fi;
    
    return R!.Functor_R_Hom;
    
end );

##
InstallMethod( RightDualizingFunctor,
        "for homalg rings",
        [ IsHomalgRing, IsString ],
        
  function( R, name )
    
    return InsertObjectInMultiFunctor( Functor_Hom_for_fp_modules, 2, R * 1, name );
    
end );

##
InstallMethod( RightDualizingFunctor,
        "for homalg rings",
        [ IsHomalgRing ],
        
  function( R )
    
    if not IsBound( R!.Functor_Hom_R ) then
        if IsBound( R!.creation_number ) then
            R!.Functor_Hom_R := RightDualizingFunctor( R, Concatenation( "Hom_R", String( R!.creation_number ) ) );
        else
            Error( "the homalg ring doesn't have a creation number\n" );
        fi;
    fi;
    
    return R!.Functor_Hom_R;
    
end );

##
InstallMethod( Dualize,
        "for homalg modules or submodules",
        [ IsFinitelyPresentedModuleOrSubmoduleRep ],
        
  Hom );

##
InstallMethod( Dualize,
        "for homalg module maps",
        [ IsMapOfFinitelyGeneratedModulesRep ],
        
  Hom );

##
## TensorProduct
##

BindGlobal( "_Functor_TensorProduct_OnModules", ### defines: TensorProduct (object part)
  function( M, N )
    local R, rl, l0, _l0, matM, matN, idM, idN, MN,
          F, gen, proc_to_readjust_generators, proc_to_normalize_generators, p;
    
    R := HomalgRing( M );
    
    ## do not use CheckIfTheyLieInTheSameCategory here
    if not IsIdenticalObj( R, HomalgRing( N ) ) then
        Error( "the rings of the source and target modules are not identical\n" );
    fi;
    
    if IsHomalgRightObjectOrMorphismOfRightObjects( M ) then
        if IsHomalgLeftObjectOrMorphismOfLeftObjects( N ) then
            rl := [ true, true ];
        else
            rl := [ true, false ];
        fi;
    else
        if IsHomalgLeftObjectOrMorphismOfLeftObjects( N ) then
            rl := [ false, true ];
        else
            rl := [ false, false ];
        fi;
    fi;
    
    l0 := NrGenerators( M );
    _l0 := NrGenerators( N );
    
    matM := MatrixOfMap( PresentationMorphism( M ) );
    matN := MatrixOfMap( PresentationMorphism( N ) );
    
    if rl = [ true, true ] or rl = [ false, false ] then
        matM := Involution( matM ); ## the first module follows the second
    fi;
    
    idM := HomalgIdentityMatrix( l0, R );
    idN := HomalgIdentityMatrix( _l0, R );
    
    matM := KroneckerMat( matM, idN );
    matN := KroneckerMat( idM, matN );
    
    ## the result has the parity of the second module
    if rl[2] then
        MN := UnionOfRows( matM, matN );
        F := HomalgFreeLeftModule( NrGenerators( M ) * NrGenerators( N ), R );
    else
        MN := UnionOfColumns( matM, matN );
        F := HomalgFreeRightModule( NrGenerators( M ) * NrGenerators( N ), R );
    fi;
    
    MN := HomalgMap( MN, "free", F );
    
    return Cokernel( MN );
    
end );

##
BindGlobal( "_Functor_TensorProduct_OnMaps", ### defines: TensorProduct (morphism part)
  function( F_source, F_target, arg_before_pos, phi, arg_behind_pos )
    local R, L, M_or_mor, N_or_mor, rl, idL, hull_phi,
          emb_source, emb_target, mor;
    
    R := HomalgRing( phi );
    
    if arg_before_pos = [ ] and Length( arg_behind_pos ) = 1 then
        ## phi \tensor L
        
        L := arg_behind_pos[1];
        
        M_or_mor := phi;
        N_or_mor := L;
        
    elif Length( arg_before_pos ) = 1 and arg_behind_pos = [ ] then
        ##  L \tensor phi
        
        L := arg_before_pos[1];
        
        M_or_mor := L;
        N_or_mor := phi;
        
    else
        Error( "wrong input\n" );
    fi;
    
    ## do not use CheckIfTheyLieInTheSameCategory here
    if not IsIdenticalObj( R, HomalgRing( L ) ) then
        Error( "the module and the morphism are not defined over identically the same ring\n" );
    fi;
    
    ## decide whether the output module is left/right depending on the input
    if IsHomalgRightObjectOrMorphismOfRightObjects( M_or_mor ) then
        if IsHomalgLeftObjectOrMorphismOfLeftObjects( N_or_mor ) then
            rl := [ true, true ];
        else
            rl := [ true, false ];
        fi;
    else
        if IsHomalgLeftObjectOrMorphismOfLeftObjects( N_or_mor ) then
            rl := [ false, true ];
        else
            rl := [ false, false ];
        fi;
    fi;
    
    if IsMapOfFinitelyGeneratedModulesRep( M_or_mor )
       and IsFinitelyPresentedModuleRep( N_or_mor ) then
        
        phi := M_or_mor;
        L := N_or_mor;
        
        idL := HomalgIdentityMatrix( NrGenerators( L ), R );
        
        if rl = [ true, true ] or rl = [ false, false ] then
            phi := Involution( MatrixOfMap( phi ) ); ## the first module follows the second
        else
            phi := MatrixOfMap( phi );
        fi;
        
        hull_phi := KroneckerMat( phi, idL );
        
    elif IsMapOfFinitelyGeneratedModulesRep( N_or_mor )
      and IsFinitelyPresentedModuleRep( M_or_mor ) then
        
        phi := N_or_mor;
        L := M_or_mor;
        
        idL := HomalgIdentityMatrix( NrGenerators( L ), R );
        
        hull_phi := KroneckerMat( idL, MatrixOfMap( phi ) );
        
    fi;
    
    emb_source := NaturalGeneralizedEmbedding( F_source );
    emb_target := NaturalGeneralizedEmbedding( F_target );
    
    hull_phi := HomalgMap( hull_phi, Range( emb_source ), Range( emb_target ) );
    
    SetIsMorphism( hull_phi, true );
    
    mor := CompleteImageSquare( emb_source, hull_phi, emb_target );
    
    ## HasIsIsomorphism( phi ) and IsIsomorphism( phi ), resp.
    ## HasIsMorphism( phi ) and IsMorphism( phi ), and
    ## UpdateObjectsByMorphism( mor )
    ## will be taken care of in FunctorMap
    
    if HasIsEpimorphism( phi ) and IsEpimorphism( phi ) then
        
        ## check assertion
        Assert( 3, IsEpimorphism( mor ) );
        
        SetIsEpimorphism( mor, true );
        
    fi;
    
    if HasIsMonomorphism( phi ) and IsMonomorphism( phi ) and
      HasIsProjective( L ) and IsProjective( L ) then
        
        ## check assertion
        Assert( 3, IsMonomorphism( mor ) );
        
        SetIsMonomorphism( mor, true );
        
    fi;
    
    return mor;
    
end );

##  <#GAPDoc Label="Functor_TensorProduct:code">
##      <Listing Type="Code"><![CDATA[
BindGlobal( "Functor_TensorProduct_for_fp_modules",
        CreateHomalgFunctor(
                [ "name", "TensorProduct" ],
                [ "category", HOMALG_MODULES.category ],
                [ "operation", "TensorProductOp" ],
                [ "number_of_arguments", 2 ],
                [ "1", [ [ "covariant", "left adjoint", "distinguished" ] ] ],
                [ "2", [ [ "covariant", "left adjoint" ] ] ],
                [ "OnObjects", _Functor_TensorProduct_OnModules ],
                [ "OnMorphisms", _Functor_TensorProduct_OnMaps ],
                [ "MorphismConstructor", HOMALG_MODULES.category.MorphismConstructor ]
                )
        );
##  ]]></Listing>
##  <#/GAPDoc>

Functor_TensorProduct_for_fp_modules!.ContainerForWeakPointersOnComputedBasicObjects := true;

Functor_TensorProduct_for_fp_modules!.ContainerForWeakPointersOnComputedBasicMorphisms := true;

##
## BaseChange
##

##
BindGlobal( "_functor_BaseChange_OnModules", ### defines: BaseChange (object part)
  function( _R, M )
    local R, S, lift, mat, left, distinguished, N;
    
    R := HomalgRing( _R );
    
    if IsIdenticalObj( HomalgRing( M ), R ) then
        return M;
    fi;
    
    S := HomalgRing( M );
    
    lift := HasRingRelations( S ) and IsIdenticalObj( R, AmbientRing( S ) );
    
    mat := MatrixOfRelations( M );
    
    left := IsHomalgLeftObjectOrMorphismOfLeftObjects( M );
    distinguished := IsBound( M!.distinguished ) and M!.distinguished = true;
    
    distinguished := distinguished and not lift;
    
    if not distinguished then
        if lift then
            if left then
                mat := StackedRelations( mat );
            else
                mat := AugmentedRelations( mat );
            fi;
        else
            mat := R * mat;
            if HasRingRelations( R ) then
                if left then
                    mat := GetRidOfObsoleteRows( mat );
                else
                    mat := GetRidOfObsoleteColumns( mat );
                fi;
            fi;
        fi;
    fi;
    
    if left then
        if distinguished then
            if HasIsZero( M ) and IsZero( M ) then
                N := 0 * R;
            else
                N := 1 * R;
            fi;
        else
            N := LeftPresentation( mat );
        fi;
    else
        if distinguished then
            if HasIsZero( M ) and IsZero( M ) then
                N := R * 0;
            else
                N := R * 1;
            fi;
        else
            N := RightPresentation( mat );
        fi;
    fi;
    
    return N;
    
end );

##
InstallOtherMethod( BaseChange,
        "for homalg maps",
        [ IsHomalgRing, IsMapOfFinitelyGeneratedModulesRep ], 1001,
        
  function( R, phi )
    
    return HomalgMap( R * MatrixOfMap( phi ), R * Source( phi ), R * Range( phi ) );
    
end );

##
InstallOtherMethod( BaseChange,
        "for homalg maps",
        [ IsHomalgModule, IsMapOfFinitelyGeneratedModulesRep ], 1001,
        
  function( _R, phi )
    local R;
    
    return BaseChange( HomalgRing( _R ), phi );
    
end );

BindGlobal( "functor_BaseChange_for_fp_modules",
        CreateHomalgFunctor(
                [ "name", "BaseChange" ],
                [ "category", HOMALG_MODULES.category ],
                [ "operation", "BaseChange" ],
                [ "number_of_arguments", 2 ],
                [ "1", [ [ "covariant" ] ] ],
                [ "2", [ [ "covariant" ] ] ],
                [ "OnObjects", _functor_BaseChange_OnModules ]
                )
        );

functor_BaseChange_for_fp_modules!.ContainerForWeakPointersOnComputedBasicObjects := true;

#functor_BaseChange_for_fp_modules!.ContainerForWeakPointersOnComputedBasicMorphisms :=
#  ContainerForWeakPointers( TheTypeContainerForWeakPointersOnComputedValuesOfFunctor );

####################################
#
# methods for operations & attributes:
#
####################################

##
## Cokernel( phi ) and CokernelEpi( phi )
##

##  <#GAPDoc Label="functor_Cokernel">
##  <ManSection>
##    <Var Name="functor_Cokernel"/>
##    <Description>
##      The functor that associates to a map its cokernel.
##      <#Include Label="functor_Cokernel:code">
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##  <#GAPDoc Label="Cokernel">
##  <ManSection>
##    <Oper Arg="phi" Name="Cokernel"/>
##    <Description>
##      The following example also makes use of the natural transformation <C>CokernelEpi</C>.
##      <Example><![CDATA[
##  gap> zz := HomalgRingOfIntegers( );
##  Z
##  gap> M := HomalgMatrix( "[ 2, 3, 4,   5, 6, 7 ]", 2, 3, zz );;
##  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 );;
##  gap> N := LeftPresentation( N );
##  <A non-torsion left module presented by 2 relations for 4 generators>
##  gap> mat := HomalgMatrix( "[ \
##  > 1, 0, -3, -6, \
##  > 0, 1,  6, 11, \
##  > 1, 0, -3, -6  \
##  > ]", 3, 4, zz );;
##  gap> phi := HomalgMap( mat, M, N );;
##  gap> IsMorphism( phi );
##  true
##  gap> phi;
##  <A homomorphism of left modules>
##  gap> coker := Cokernel( phi );
##  <A left module presented by 5 relations for 4 generators>
##  gap> ByASmallerPresentation( coker );
##  <A rank 1 left module presented by 1 relation for 2 generators>
##  gap> Display( coker );
##  Z/< 8 > + Z^(1 x 1)
##  gap> nu := CokernelEpi( phi );
##  <An epimorphism of left modules>
##  gap> Display( nu );
##  [ [  -5,   0 ],
##    [  -6,   1 ],
##    [   1,  -2 ],
##    [   0,   1 ] ]
##  
##  the map is currently represented by the above 4 x 2 matrix
##  gap> DefectOfExactness( phi, nu );
##  <A zero left module>
##  gap> ByASmallerPresentation( nu );
##  <A non-zero epimorphism of left modules>
##  gap> Display( nu );
##  [ [   2,   0 ],
##    [   1,  -2 ],
##    [   0,   1 ] ]
##  
##  the map is currently represented by the above 3 x 2 matrix
##  gap> PreInverse( nu );
##  false
##  ]]></Example>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallFunctor( functor_Cokernel_for_fp_modules );

##
## ImageObject( phi ) and ImageObjectEmb( phi )
##

##  <#GAPDoc Label="functor_ImageObject">
##  <ManSection>
##    <Var Name="functor_ImageObject"/>
##    <Description>
##      The functor that associates to a map its image.
##      <#Include Label="functor_ImageObject:code">
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##  <#GAPDoc Label="ImageObject">
##  <ManSection>
##    <Oper Arg="phi" Name="ImageObject"/>
##    <Description>
##      The following example also makes use of the natural transformations <C>ImageObjectEpi</C>
##      and <C>ImageObjectEmb</C>.
##      <Example><![CDATA[
##  gap> zz := HomalgRingOfIntegers( );
##  Z
##  gap> M := HomalgMatrix( "[ 2, 3, 4,   5, 6, 7 ]", 2, 3, zz );;
##  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 );;
##  gap> N := LeftPresentation( N );
##  <A non-torsion left module presented by 2 relations for 4 generators>
##  gap> mat := HomalgMatrix( "[ \
##  > 1, 0, -3, -6, \
##  > 0, 1,  6, 11, \
##  > 1, 0, -3, -6  \
##  > ]", 3, 4, zz );;
##  gap> phi := HomalgMap( mat, M, N );;
##  gap> IsMorphism( phi );
##  true
##  gap> phi;
##  <A homomorphism of left modules>
##  gap> im := ImageObject( phi );
##  <A left module presented by yet unknown relations for 3 generators>
##  gap> ByASmallerPresentation( im );
##  <A free left module of rank 1 on a free generator>
##  gap> pi := ImageObjectEpi( phi );
##  <A non-zero split epimorphism of left modules>
##  gap> epsilon := ImageObjectEmb( phi );
##  <A monomorphism of left modules>
##  gap> phi = pi * epsilon;
##  true
##  ]]></Example>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallFunctorOnObjects( functor_ImageObject_for_fp_modules );

##
## Kernel( phi ) and KernelEmb( phi )
##

##  <#GAPDoc Label="Kernel:map">
##  <ManSection>
##    <Oper Arg="phi" Name="Kernel" Label="for maps"/>
##    <Description>
##      The following example also makes use of the natural transformation <C>KernelEmb</C>.
##      <Example><![CDATA[
##  gap> zz := HomalgRingOfIntegers( );
##  Z
##  gap> M := HomalgMatrix( "[ 2, 3, 4,   5, 6, 7 ]", 2, 3, zz );;
##  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 );;
##  gap> N := LeftPresentation( N );
##  <A non-torsion left module presented by 2 relations for 4 generators>
##  gap> mat := HomalgMatrix( "[ \
##  > 1, 0, -3, -6, \
##  > 0, 1,  6, 11, \
##  > 1, 0, -3, -6  \
##  > ]", 3, 4, zz );;
##  gap> phi := HomalgMap( mat, M, N );;
##  gap> IsMorphism( phi );
##  true
##  gap> phi;
##  <A homomorphism of left modules>
##  gap> ker := Kernel( phi );
##  <A cyclic left module presented by yet unknown relations for a cyclic generato\
##  r>
##  gap> Display( ker );
##  Z/< -3 >
##  gap> ByASmallerPresentation( last );
##  <A cyclic torsion left module presented by 1 relation for a cyclic generator>
##  gap> Display( ker );
##  Z/< 3 >
##  gap> iota := KernelEmb( phi );
##  <A monomorphism of left modules>
##  gap> Display( iota );
##  [ [  0,  1,  2 ] ]
##  
##  the map is currently represented by the above 1 x 3 matrix
##  gap> DefectOfExactness( iota, phi );
##  <A zero left module>
##  gap> ByASmallerPresentation( iota );
##  <A non-zero monomorphism of left modules>
##  gap> Display( iota );
##  [ [  1,  0 ] ]
##  
##  the map is currently represented by the above 1 x 2 matrix
##  gap> PostInverse( iota );
##  <A non-zero split epimorphism of left modules>
##  ]]></Example>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##
## DefectOfExactness( cpx_post_pre )
##

##  <#GAPDoc Label="DefectOfExactness">
##  <ManSection>
##    <Oper Arg="phi, psi" Name="DefectOfExactness"/>
##    <Description>
##      We follow the associative convention for applying maps.
##      For left modules <A>phi</A> is applied first and from the right.
##      For right modules <A>psi</A> is applied first and from the left.
##      <P/>
##      The following example also makes use of the natural transformation <C>KernelEmb</C>.
##      <Example><![CDATA[
##  gap> zz := HomalgRingOfIntegers( );
##  Z
##  gap> M := HomalgMatrix( "[ 2, 3, 4, 0,   5, 6, 7, 0 ]", 2, 4, zz );;
##  gap> M := LeftPresentation( M );
##  <A non-torsion left module presented by 2 relations for 4 generators>
##  gap> N := HomalgMatrix( "[ 2, 3, 4, 5,   6, 7, 8, 9 ]", 2, 4, zz );;
##  gap> N := LeftPresentation( N );
##  <A non-torsion left module presented by 2 relations for 4 generators>
##  gap> mat := HomalgMatrix( "[ \
##  > 1, 3,  3,  3, \
##  > 0, 3, 10, 17, \
##  > 1, 3,  3,  3, \
##  > 0, 0,  0,  0  \
##  > ]", 4, 4, zz );;
##  gap> phi := HomalgMap( mat, M, N );;
##  gap> IsMorphism( phi );
##  true
##  gap> phi;
##  <A homomorphism of left modules>
##  gap> iota := KernelEmb( phi );
##  <A monomorphism of left modules>
##  gap> DefectOfExactness( iota, phi );
##  <A zero left module>
##  gap> hom_iota := Hom( iota ); ## a shorthand for Hom( iota, zz );
##  <A homomorphism of right modules>
##  gap> hom_phi := Hom( phi ); ## a shorthand for Hom( phi, zz );
##  <A homomorphism of right modules>
##  gap> DefectOfExactness( hom_iota, hom_phi );
##  <A cyclic right module on a cyclic generator satisfying yet unknown relations>
##  gap> ByASmallerPresentation( last );
##  <A cyclic torsion right module on a cyclic generator satisfying 1 relation>
##  gap> Display( last );
##  Z/< 2 >
##  ]]></Example>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##
## Hom( M, N )
##

##  <#GAPDoc Label="Functor_Hom">
##  <ManSection>
##    <Var Name="Functor_Hom"/>
##    <Description>
##      The bifunctor <C>Hom</C>.
##      <#Include Label="Functor_Hom:code">
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##  <#GAPDoc Label="Hom">
##  <ManSection>
##    <Oper Arg="o1,o2" Name="Hom"/>
##    <Description>
##      <A>o1</A> resp. <A>o2</A> could be a module, a map, a complex (of modules or of again of complexes),
##      or a chain morphism.
##      <P/>
##      Each generator of a module of homomorphisms is displayed as a matrix of appropriate dimensions.
##      <Example><![CDATA[
##  gap> zz := HomalgRingOfIntegers( );
##  Z
##  gap> M := HomalgMatrix( "[ 2, 3, 4,   5, 6, 7 ]", 2, 3, zz );;
##  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 );;
##  gap> N := LeftPresentation( N );
##  <A non-torsion left module presented by 2 relations for 4 generators>
##  gap> mat := HomalgMatrix( "[ \
##  > 1, 0, -3, -6, \
##  > 0, 1,  6, 11, \
##  > 1, 0, -3, -6  \
##  > ]", 3, 4, zz );;
##  gap> phi := HomalgMap( mat, M, N );;
##  gap> IsMorphism( phi );
##  true
##  gap> phi;
##  <A homomorphism of left modules>
##  gap> psi := Hom( phi, M );
##  <A homomorphism of right modules>
##  gap> ByASmallerPresentation( psi );
##  <A non-zero homomorphism of right modules>
##  gap> Display( psi );
##  [ [   1,   1,   1,   0 ],
##    [   2,   2,   0,   0 ],
##    [   0,   0,  -2,   0 ] ]
##  
##  the map is currently represented by the above 3 x 4 matrix
##  gap> homNM := Source( psi );
##  <A rank 2 right module on 4 generators satisfying 2 relations>
##  gap> IsIdenticalObj( homNM, Hom( N, M ) ); ## the caching at work
##  true
##  gap> homMM := Range( psi );
##  <A rank 1 right module on 3 generators satisfying 2 relations>
##  gap> IsIdenticalObj( homMM, Hom( M, M ) ); ## the caching at work
##  true
##  gap> Display( homNM );
##  Z/< 3 > + Z/< 3 > + Z^(2 x 1)
##  gap> Display( homMM );
##  Z/< 3 > + Z/< 3 > + Z^(1 x 1)
##  gap> IsMonomorphism( psi );
##  false
##  gap> IsEpimorphism( psi );
##  false
##  gap> GeneratorsOfModule( homMM );
##  <A set of 3 generators of a homalg right module>
##  gap> Display( last );
##  [ [  0,  0,  0 ],
##    [  0,  1,  2 ],
##    [  0,  0,  0 ] ]
##  
##  the map is currently represented by the above 3 x 3 matrix
##  
##  [ [  0,  2,  4 ],
##    [  0,  0,  0 ],
##    [  0,  2,  4 ] ]
##  
##  the map is currently represented by the above 3 x 3 matrix
##  
##  [ [   0,   0,   1 ],
##    [   0,   2,   2 ],
##    [   0,   0,   1 ] ]
##  
##  the map is currently represented by the above 3 x 3 matrix
##  
##  a set of 3 generators given by the the above matrices
##  gap> GeneratorsOfModule( homNM );
##  <A set of 4 generators of a homalg right module>
##  gap> Display( last );
##  [ [  0,  1,  2 ],
##    [  0,  1,  2 ],
##    [  0,  1,  2 ],
##    [  0,  0,  0 ] ]
##  
##  the map is currently represented by the above 4 x 3 matrix
##  
##  [ [  0,  1,  2 ],
##    [  0,  0,  0 ],
##    [  0,  0,  0 ],
##    [  0,  2,  4 ] ]
##  
##  the map is currently represented by the above 4 x 3 matrix
##  
##  [ [   0,   0,   1 ],
##    [   0,   1,  -1 ],
##    [   0,   1,   5 ],
##    [   0,   1,   1 ] ]
##  
##  the map is currently represented by the above 4 x 3 matrix
##  
##  [ [   0,   0,   0 ],
##    [   0,   0,   1 ],
##    [   0,   0,  -2 ],
##    [   0,   0,   1 ] ]
##  
##  the map is currently represented by the above 4 x 3 matrix
##  
##  a set of 4 generators given by the the above matrices
##  ]]></Example>
##      If for example the source <M>N</M> gets a new presentation, you will see the effect on the generators:
##      <Example><![CDATA[
##  gap> ByASmallerPresentation( N );
##  <A rank 2 left module presented by 1 relation for 3 generators>
##  gap> GeneratorsOfModule( homNM );
##  <A set of 4 generators of a homalg right module>
##  gap> Display( last );
##  [ [  0,  3,  6 ],
##    [  0,  1,  2 ],
##    [  0,  0,  0 ] ]
##  
##  the map is currently represented by the above 3 x 3 matrix
##  
##  [ [   0,   9,  18 ],
##    [   0,   0,   0 ],
##    [   0,   2,   4 ] ]
##  
##  the map is currently represented by the above 3 x 3 matrix
##  
##  [ [   0,   9,  18 ],
##    [   0,   1,   5 ],
##    [   0,   1,   1 ] ]
##  
##  the map is currently represented by the above 3 x 3 matrix
##  
##  [ [   0,   0,   0 ],
##    [   0,   0,  -2 ],
##    [   0,   0,   1 ] ]
##  
##  the map is currently represented by the above 3 x 3 matrix
##  
##  a set of 4 generators given by the the above matrices
##  ]]></Example>
##      Now we compute a certain natural filtration on <C>Hom</C><M>(M,M)</M>:
##      <Example><![CDATA[
##  gap> dM := Resolution( M );
##  <A non-zero right acyclic complex containing a single morphism of left modules\
##   at degrees [ 0 .. 1 ]>
##  gap> hMM := Hom( dM, dM );
##  <A non-zero acyclic cocomplex containing a single morphism of right complexes \
##  at degrees [ 0 .. 1 ]>
##  gap> BMM := HomalgBicomplex( hMM );
##  <A non-zero bicocomplex containing right modules at bidegrees [ 0 .. 1 ]x
##  [ -1 .. 0 ]>
##  gap> II_E := SecondSpectralSequenceWithFiltration( BMM );
##  <A stable cohomological spectral sequence with sheets at levels 
##  [ 0 .. 2 ] each consisting of right modules at bidegrees [ -1 .. 0 ]x
##  [ 0 .. 1 ]>
##  gap> Display( II_E );
##  The associated transposed spectral sequence:
##  
##  a cohomological spectral sequence at bidegrees
##  [ [ 0 .. 1 ], [ -1 .. 0 ] ]
##  ---------
##  Level 0:
##  
##   * *
##   * *
##  ---------
##  Level 1:
##  
##   * *
##   . .
##  ---------
##  Level 2:
##  
##   s s
##   . .
##  
##  Now the spectral sequence of the bicomplex:
##  
##  a cohomological spectral sequence at bidegrees
##  [ [ -1 .. 0 ], [ 0 .. 1 ] ]
##  ---------
##  Level 0:
##  
##   * *
##   * *
##  ---------
##  Level 1:
##  
##   * *
##   * *
##  ---------
##  Level 2:
##  
##   s s
##   . s
##  gap> filt := FiltrationBySpectralSequence( II_E );
##  <A descending filtration with degrees [ -1 .. 0 ] and graded parts:
##    
##  -1:   <A non-zero cyclic torsion right module on a cyclic generator satisfying
##       yet unknown relations>
##     0:   <A rank 1 right module on 3 generators satisfying 2 relations>
##  of
##  <A right module on 4 generators satisfying yet unknown relations>>
##  gap> ByASmallerPresentation( filt );
##  <A descending filtration with degrees [ -1 .. 0 ] and graded parts:
##    
##  -1:   <A non-zero cyclic torsion right module on a cyclic generator satisfying 1\
##   relation>
##     0:   <A rank 1 right module on 2 generators satisfying 1 relation>
##  of
##  <A rank 1 right module on 3 generators satisfying 2 relations>>
##  gap> Display( filt );
##  Degree -1:
##  
##  Z/< 3 >
##  ----------
##  Degree 0:
##  
##  Z/< 3 > + Z^(1 x 1)
##  gap> Display( homMM );
##  Z/< 3 > + Z/< 3 > + Z^(1 x 1)
##  ]]></Example>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallFunctor( Functor_Hom_for_fp_modules );

##
## TensorProduct( M, N ) ( M * N )
##

##  <#GAPDoc Label="Functor_TensorProduct">
##  <ManSection>
##    <Var Name="Functor_TensorProduct"/>
##    <Description>
##      The tensor product bifunctor.
##      <#Include Label="Functor_TensorProduct:code">
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##  <#GAPDoc Label="TensorProduct">
##  <ManSection>
##    <Oper Arg="o1,o2" Name="TensorProduct"/>
##    <Oper Arg="o1,o2" Name="\*" Label="TensorProduct"/>
##    <Description>
##      <A>o1</A> resp. <A>o2</A> could be a module, a map, a complex (of modules or of again of complexes),
##      or a chain morphism.
##      <P/>
##      The symbol <C>*</C> is a shorthand for several operations associated with the functor <C>Functor_TensorProduct_for_fp_modules</C>
##      installed under the name <C>TensorProduct</C>.
##      <Example><![CDATA[
##  gap> zz := HomalgRingOfIntegers( );
##  Z
##  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, -3, -6, \
##  > 0, 1,  6, 11, \
##  > 1, 0, -3, -6  \
##  > ]", 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> L := Hom( zz, M );
##  <A rank 1 right module on 3 generators satisfying yet unknown relations>
##  gap> ByASmallerPresentation( L );
##  <A rank 1 right module on 2 generators satisfying 1 relation>
##  gap> Display( L );
##  Z/< 3 > + Z^(1 x 1)
##  gap> L;
##  <A rank 1 right module on 2 generators satisfying 1 relation>
##  gap> psi := phi * L;
##  <A homomorphism of right modules>
##  gap> ByASmallerPresentation( psi );
##  <A non-zero homomorphism of right modules>
##  gap> Display( psi );
##  [ [   0,   0,   1,   1 ],
##    [   0,   0,   8,   1 ],
##    [   0,   0,   0,  -2 ],
##    [   0,   0,   0,   2 ] ]
##  
##  the map is currently represented by the above 4 x 4 matrix
##  gap> ML := Source( psi );
##  <A rank 1 right module on 4 generators satisfying 3 relations>
##  gap> IsIdenticalObj( ML, M * L ); ## the caching at work
##  true
##  gap> NL := Range( psi );
##  <A rank 2 right module on 4 generators satisfying 2 relations>
##  gap> IsIdenticalObj( NL, N * L ); ## the caching at work
##  true
##  gap> Display( ML );
##  Z/< 3 > + Z/< 3 > + Z/< 3 > + Z^(1 x 1)
##  gap> Display( NL );
##  Z/< 3 > + Z/< 12 > + Z^(2 x 1)
##  ]]></Example>
##      Now we compute a certain natural filtration on the tensor product <M>M</M><C>*</C><M>L</M>:
##      <Example><![CDATA[
##  gap> P := Resolution( M );
##  <A non-zero right acyclic complex containing a single morphism of left modules\
##   at degrees [ 0 .. 1 ]>
##  gap> GP := Hom( P );
##  <A non-zero acyclic cocomplex containing a single morphism of right modules at\
##   degrees [ 0 .. 1 ]>
##  gap> CE := Resolution( GP );
##  <An acyclic cocomplex containing a single morphism of right complexes at degre\
##  es [ 0 .. 1 ]>
##  gap> FCE := Hom( CE, L );
##  <A non-zero acyclic complex containing a single morphism of left cocomplexes a\
##  t degrees [ 0 .. 1 ]>
##  gap> BC := HomalgBicomplex( FCE );
##  <A non-zero bicomplex containing left modules at bidegrees [ 0 .. 1 ]x
##  [ -1 .. 0 ]>
##  gap> II_E := SecondSpectralSequenceWithFiltration( BC );
##  <A stable homological spectral sequence with sheets at levels 
##  [ 0 .. 2 ] each consisting of left modules at bidegrees [ -1 .. 0 ]x
##  [ 0 .. 1 ]>
##  gap> Display( II_E );
##  The associated transposed spectral sequence:
##  
##  a homological spectral sequence at bidegrees
##  [ [ 0 .. 1 ], [ -1 .. 0 ] ]
##  ---------
##  Level 0:
##  
##   * *
##   * *
##  ---------
##  Level 1:
##  
##   * *
##   . .
##  ---------
##  Level 2:
##  
##   s s
##   . .
##  
##  Now the spectral sequence of the bicomplex:
##  
##  a homological spectral sequence at bidegrees
##  [ [ -1 .. 0 ], [ 0 .. 1 ] ]
##  ---------
##  Level 0:
##  
##   * *
##   * *
##  ---------
##  Level 1:
##  
##   * *
##   . s
##  ---------
##  Level 2:
##  
##   s s
##   . s
##  gap> filt := FiltrationBySpectralSequence( II_E );
##  <An ascending filtration with degrees [ -1 .. 0 ] and graded parts:
##     0:   <A rank 1 left module presented by 1 relation for 2 generators>
##    -1:   <A non-zero left module presented by 2 relations for 2 generators>
##  of
##  <A non-zero left module presented by 4 relations for 4 generators>>
##  gap> ByASmallerPresentation( filt );
##  <An ascending filtration with degrees [ -1 .. 0 ] and graded parts:
##     0:   <A rank 1 left module presented by 1 relation for 2 generators>
##    -1:   <A non-zero torsion left module presented by 2 relations
##               for 2 generators>
##  of
##  <A rank 1 left module presented by 3 relations for 4 generators>>
##  gap> Display( filt );
##  Degree 0:
##  
##  Z/< 3 > + Z^(1 x 1)
##  ----------
##  Degree -1:
##  
##  Z/< 3 > + Z/< 3 >
##  gap> Display( ML );
##  Z/< 3 > + Z/< 3 > + Z/< 3 > + Z^(1 x 1)
##  ]]></Example>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallFunctor( Functor_TensorProduct_for_fp_modules );

## for convenience
InstallOtherMethod( \*,
        "for homalg modules",
        [ IsStructureObjectOrObjectOrMorphism, IsFinitelyPresentedModuleRep ],
        
  function( M, N )
    
    return TensorProduct( M, N );
    
end );

## for convenience
InstallOtherMethod( \*,
        "for homalg modules",
        [ IsFinitelyPresentedModuleRep, IsStructureObjectOrObjectOrMorphism ],
        
  function( M, N )
    
    return TensorProduct( M, N );
    
end );

## for convenience
InstallOtherMethod( \*,
        "for homalg modules",
        [ IsHomalgComplex, IsHomalgComplex ],
        
  function( M, N )
    
    return TensorProduct( M, N );
    
end );

##  <#GAPDoc Label="\*:ModuleBaseChange">
##  <ManSection>
##    <Oper Arg="R, M" Name="\*" Label="transfer a module over a different ring"/>
##    <Oper Arg="M, R" Name="\*" Label="transfer a module over a different ring (right)"/>
##    <Returns>a &homalg; module</Returns>
##    <Description>
##      Transfers the <M>S</M>-module <A>M</A> over the &homalg; ring <A>R</A>. This works only in three cases:
##      <Enum>
##        <Item><M>S</M> is a subring of <A>R</A>.</Item>
##        <Item><A>R</A> is a residue class ring of <M>S</M> constructed using <C>/</C>.</Item>
##        <Item><A>R</A> is a subring of <M>S</M> and the entries of the current matrix of <M>S</M>-relations of <A>M</A>
##          lie in <A>R</A>.</Item>
##      </Enum>
##      CAUTION: So it is not suited for general base change.
##      <Example><![CDATA[
##  gap> zz := HomalgRingOfIntegers( );
##  Z
##  gap> Display( zz );
##  <An internal ring>
##  gap> Z4 := zz / 4;
##  Z/( 4 )
##  gap> Display( Z4 );
##  <A residue class ring>
##  gap> M := HomalgDiagonalMatrix( [ 2 .. 4 ], zz );
##  <An unevaluated diagonal 3 x 3 matrix over an internal ring>
##  gap> M := LeftPresentation( M );
##  <A torsion left module presented by 3 relations for 3 generators>
##  gap> Display( M );
##  Z/< 2 > + Z/< 3 > + Z/< 4 >
##  gap> M;
##  <A torsion left module presented by 3 relations for 3 generators>
##  gap> N := Z4 * M; ## or N := M * Z4;
##  <A non-torsion left module presented by 2 relations for 3 generators>
##  gap> ByASmallerPresentation( N );
##  <A non-torsion left module presented by 1 relation for 2 generators>
##  gap> Display( N );
##  Z/( 4 )/< |[ 2 ]| > + Z/( 4 )^(1 x 1)
##  gap> N;
##  <A non-torsion left module presented by 1 relation for 2 generators>
##  ]]></Example>
##      <Example><![CDATA[
##  gap> zz := HomalgRingOfIntegers( );
##  Z
##  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> Z4 := zz / 4;
##  Z/( 4 )
##  gap> Display( Z4 );
##  <A residue class ring>
##  gap> M4 := Z4 * M;
##  <A non-torsion left module presented by 2 relations for 3 generators>
##  gap> Display( M4 );
##  [ [  2,  3,  4 ],
##    [  5,  6,  7 ] ]
##  
##  modulo [ 4 ]
##  
##  Cokernel of the map
##  
##  Z/( 4 )^(1x2) --> Z/( 4 )^(1x3),
##  
##  currently represented by the above matrix
##  gap> d := Resolution( 2, M4 );
##  <A right acyclic complex containing 2 morphisms of left modules at degrees 
##  [ 0 .. 2 ]>
##  gap> dd := Hom( d, Z4 );
##  <A cocomplex containing 2 morphisms of right modules at degrees [ 0 .. 2 ]>
##  gap> DD := Resolution( 2, dd );
##  <A cocomplex containing 2 morphisms of right complexes at degrees [ 0 .. 2 ]>
##  gap> D := Hom( DD, Z4 );
##  <A complex containing 2 morphisms of left cocomplexes at degrees [ 0 .. 2 ]>
##  gap> C := zz * D;
##  <A "complex" containing 2 morphisms of left cocomplexes at degrees [ 0 .. 2 ]>
##  gap> LowestDegreeObject( C );
##  <A "cocomplex" containing 2 morphisms of left modules at degrees [ 0 .. 2 ]>
##  gap> Display( last );
##  -------------------------
##  at cohomology degree: 2
##  0
##  ------------^------------
##  (an empty 1 x 0 matrix)
##  
##  the map is currently represented by the above 1 x 0 matrix
##  -------------------------
##  at cohomology degree: 1
##  Z/< 4 > 
##  ------------^------------
##  [ [  0 ],
##    [  1 ],
##    [  2 ],
##    [  1 ] ]
##  
##  the map is currently represented by the above 4 x 1 matrix
##  -------------------------
##  at cohomology degree: 0
##  Z/< 4 > + Z/< 4 > + Z/< 4 > + Z/< 4 > 
##  -------------------------
##  ]]></Example>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##
InstallFunctor( functor_BaseChange_for_fp_modules );

##
## Ext( c, M, N )
##

##  <#GAPDoc Label="Functor_Ext">
##  <ManSection>
##    <Var Name="Functor_Ext"/>
##    <Description>
##      The bifunctor <C>Ext</C>.
##      <P/>
##      <#Include Label="Functor_Ext:code">
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##  <#GAPDoc Label="Functor_Ext:code">
##    Below is the only <E>specific</E> line of code used to define <C>Functor_Ext_for_fp_modules</C>
##    and all the different operations <C>Ext</C> in &homalg;.
##      <Listing Type="Code"><![CDATA[
RightSatelliteOfCofunctor( Functor_Hom_for_fp_modules, "Ext" );
##  ]]></Listing>
##  <#/GAPDoc>

##  <#GAPDoc Label="RightSatelliteOfCofunctor:example">
##    <#Include Label="Functor_Ext:code">
##  <#/GAPDoc>

##  <#GAPDoc Label="Ext">
##  <ManSection>
##    <Oper Arg="[c,]o1,o2[,str]" Name="Ext"/>
##    <Description>
##      Compute the <A>c</A>-th extension object of <A>o1</A> with <A>o2</A> where <A>c</A> is a nonnegative integer
##      and <A>o1</A> resp. <A>o2</A> could be a module, a map, a complex (of modules or of again of complexes),
##      or a chain morphism. If <A>str</A>=<Q>a</Q> then the (cohomologically) graded object
##      <M>Ext^i(</M><A>o1</A>,<A>o2</A><M>)</M> for <M>0 \leq i \leq</M><A>c</A> is computed.
##      If neither <A>c</A> nor <A>str</A> is specified then the cohomologically graded object
##      <M>Ext^i(</M><A>o1</A>,<A>o2</A><M>)</M> for <M>0 \leq i \leq d</M> is computed,
##      where <M>d</M> is the length of the internally computed free resolution of <A>o1</A>.
##      <P/>
##      Each generator of a module of extensions is displayed as a matrix of appropriate dimensions.
##      <Example><![CDATA[
##  gap> zz := HomalgRingOfIntegers( );
##  Z
##  gap> M := HomalgMatrix( "[ 2, 3, 4,   5, 6, 7 ]", 2, 3, zz );;
##  gap> M := LeftPresentation( M );
##  <A non-torsion left module presented by 2 relations for 3 generators>
##  gap> N := TorsionObject( M );
##  <A cyclic torsion left module presented by yet unknown relations for a cyclic \
##  generator>
##  gap> iota := TorsionObjectEmb( M );
##  <A monomorphism of left modules>
##  gap> psi := Ext( 1, iota, N );
##  <A homomorphism of right modules>
##  gap> ByASmallerPresentation( psi );
##  <A non-zero homomorphism of right modules>
##  gap> Display( psi );
##  [ [  1 ] ]
##  
##  the map is currently represented by the above 1 x 1 matrix
##  gap> extNN := Range( psi );
##  <A non-zero cyclic torsion right module on a cyclic generator satisfying 1 rel\
##  ation>
##  gap> IsIdenticalObj( extNN, Ext( 1, N, N ) ); ## the caching at work
##  true
##  gap> extMN := Source( psi );
##  <A non-zero cyclic torsion right module on a cyclic generator satisfying 1 rel\
##  ation>
##  gap> IsIdenticalObj( extMN, Ext( 1, M, N ) ); ## the caching at work
##  true
##  gap> Display( extNN );
##  Z/< 3 >
##  gap> Display( extMN );
##  Z/< 3 >
##  ]]></Example>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##

##
## Tor( c, M, N )
##

##  <#GAPDoc Label="Functor_Tor">
##  <ManSection>
##    <Var Name="Functor_Tor"/>
##    <Description>
##      The bifunctor <C>Tor</C>.
##      <P/>
##      <#Include Label="Functor_Tor:code">
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##  <#GAPDoc Label="Functor_Tor:code">
##    Below is the only <E>specific</E> line of code used to define <C>Functor_Tor_for_fp_modules</C>
##    and all the different operations <C>Tor</C> in &homalg;.
##      <Listing Type="Code"><![CDATA[
LeftSatelliteOfFunctor( Functor_TensorProduct_for_fp_modules, "Tor" );
##  ]]></Listing>
##  <#/GAPDoc>

##  <#GAPDoc Label="LeftSatelliteOfFunctor:example">
##    <#Include Label="Functor_Tor:code">
##  <#/GAPDoc>

##  <#GAPDoc Label="Tor">
##  <ManSection>
##    <Oper Arg="[c,]o1,o2[,str]" Name="Tor"/>
##    <Description>
##      Compute the <A>c</A>-th torsion object of <A>o1</A> with <A>o2</A> where <A>c</A> is a nonnegative integer
##      and <A>o1</A> resp. <A>o2</A> could be a module, a map, a complex (of modules or of again of complexes),
##      or a chain morphism. If <A>str</A>=<Q>a</Q> then the (cohomologically) graded object
##      <M>Tor_i(</M><A>o1</A>,<A>o2</A><M>)</M> for <M>0 \leq i \leq</M><A>c</A> is computed.
##      If neither <A>c</A> nor <A>str</A> is specified then the cohomologically graded object
##      <M>Tor_i(</M><A>o1</A>,<A>o2</A><M>)</M> for <M>0 \leq i \leq d</M> is computed,
##      where <M>d</M> is the length of the internally computed free resolution of <A>o1</A>.
##      <Example><![CDATA[
##  gap> zz := HomalgRingOfIntegers( );
##  Z
##  gap> M := HomalgMatrix( "[ 2, 3, 4,   5, 6, 7 ]", 2, 3, zz );;
##  gap> M := LeftPresentation( M );
##  <A non-torsion left module presented by 2 relations for 3 generators>
##  gap> N := TorsionObject( M );
##  <A cyclic torsion left module presented by yet unknown relations for a cyclic \
##  generator>
##  gap> iota := TorsionObjectEmb( M );
##  <A monomorphism of left modules>
##  gap> psi := Tor( 1, iota, N );
##  <A homomorphism of left modules>
##  gap> ByASmallerPresentation( psi );
##  <A non-zero homomorphism of left modules>
##  gap> Display( psi );
##  [ [  1 ] ]
##  
##  the map is currently represented by the above 1 x 1 matrix
##  gap> torNN := Source( psi );
##  <A non-zero cyclic torsion left module presented by 1 relation for a cyclic ge\
##  nerator>
##  gap> IsIdenticalObj( torNN, Tor( 1, N, N ) ); ## the caching at work
##  true
##  gap> torMN := Range( psi );
##  <A non-zero cyclic torsion left module presented by 1 relation for a cyclic ge\
##  nerator>
##  gap> IsIdenticalObj( torMN, Tor( 1, M, N ) ); ## the caching at work
##  true
##  gap> Display( torNN );
##  Z/< 3 >
##  gap> Display( torMN );
##  Z/< 3 >
##  ]]></Example>
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##

##
## RHom( c, M, N )
##

##  <#GAPDoc Label="Functor_RHom">
##  <ManSection>
##    <Var Name="Functor_RHom"/>
##    <Description>
##      The bifunctor <C>RHom</C>.
##      <P/>
##      <#Include Label="Functor_RHom:code">
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##  <#GAPDoc Label="Functor_RHom:code">
##    Below is the only <E>specific</E> line of code used to define <C>Functor_RHom_for_fp_modules</C>
##    and all the different operations <C>RHom</C> in &homalg;.
##      <Listing Type="Code"><![CDATA[
RightDerivedCofunctor( Functor_Hom_for_fp_modules );
##  ]]></Listing>
##  <#/GAPDoc>

##  <#GAPDoc Label="RightDerivedCofunctor:example">
##    <#Include Label="Functor_RHom:code">
##  <#/GAPDoc>

##  <#GAPDoc Label="RHom">
##  <ManSection>
##    <Oper Arg="[c,]o1,o2[,str]" Name="RHom"/>
##    <Description>
##      Compute the <A>c</A>-th extension object of <A>o1</A> with <A>o2</A> where <A>c</A> is a nonnegative integer
##      and <A>o1</A> resp. <A>o2</A> could be a module, a map, a complex (of modules or of again of complexes),
##      or a chain morphism. The string <A>str</A> may take different values:
##      <List>
##        <Item>If <A>str</A>=<Q>a</Q> then <M>R^i Hom(</M><A>o1</A>,<A>o2</A><M>)</M> for <M>0 \leq i \leq</M><A>c</A>
##          is computed.</Item>
##        <Item>If <A>str</A>=<Q>c</Q> then the <A>c</A>-th connecting homomorphism with respect to
##          the short exact sequence <A>o1</A> is computed.</Item>
##        <Item>If <A>str</A>=<Q>t</Q> then the exact triangle upto cohomological degree <A>c</A> with respect to
##          the short exact sequence <A>o1</A> is computed.</Item>
##      </List>
##      If neither <A>c</A> nor <A>str</A> is specified then the cohomologically graded object
##      <M>R^i Hom(</M><A>o1</A>,<A>o2</A><M>)</M> for <M>0 \leq i \leq d</M> is computed,
##      where <M>d</M> is the length of the internally computed free resolution of <A>o1</A>.
##      <P/>
##      Each generator of a module of derived homomorphisms is displayed as a matrix of appropriate dimensions.
##      <#Include Label="RHom_Z">
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##

##
## LTensorProduct( c, M, N )
##

##  <#GAPDoc Label="Functor_LTensorProduct">
##  <ManSection>
##    <Var Name="Functor_LTensorProduct"/>
##    <Description>
##      The bifunctor <C>LTensorProduct</C>.
##      <P/>
##      <#Include Label="Functor_LTensorProduct:code">
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##  <#GAPDoc Label="Functor_LTensorProduct:code">
##    Below is the only <E>specific</E> line of code used to define <C>Functor_LTensorProduct_for_fp_modules</C>
##    and all the different operations <C>LTensorProduct</C> in &homalg;.
##      <Listing Type="Code"><![CDATA[
LeftDerivedFunctor( Functor_TensorProduct_for_fp_modules );
##  ]]></Listing>
##  <#/GAPDoc>

##  <#GAPDoc Label="LeftDerivedFunctor:example">
##    <#Include Label="Functor_LTensorProduct:code">
##  <#/GAPDoc>

##  <#GAPDoc Label="LTensorProduct">
##  <ManSection>
##    <Oper Arg="[c,]o1,o2[,str]" Name="LTensorProduct"/>
##    <Description>
##      Compute the <A>c</A>-th torsion object of <A>o1</A> with <A>o2</A> where <A>c</A> is a nonnegative integer
##      and <A>o1</A> resp. <A>o2</A> could be a module, a map, a complex (of modules or of again of complexes),
##      or a chain morphism. The string <A>str</A> may take different values:
##      <List>
##        <Item>If <A>str</A>=<Q>a</Q> then <M>L_i TensorProduct(</M><A>o1</A>,<A>o2</A><M>)</M> for <M>0 \leq i \leq</M><A>c</A>
##          is computed.</Item>
##        <Item>If <A>str</A>=<Q>c</Q> then the <A>c</A>-th connecting homomorphism with respect to
##          the short exact sequence <A>o1</A> is computed.</Item>
##        <Item>If <A>str</A>=<Q>t</Q> then the exact triangle upto cohomological degree <A>c</A> with respect to
##          the short exact sequence <A>o1</A> is computed.</Item>
##      </List>
##      If neither <A>c</A> nor <A>str</A> is specified then the cohomologically graded object
##      <M>L_i TensorProduct(</M><A>o1</A>,<A>o2</A><M>)</M> for <M>0 \leq i \leq d</M> is computed,
##      where <M>d</M> is the length of the internally computed free resolution of <A>o1</A>.
##      <P/>
##      Each generator of a module of derived homomorphisms is displayed as a matrix of appropriate dimensions.
##      <#Include Label="LTensorProduct_Z">
##    </Description>
##  </ManSection>
##  <#/GAPDoc>
##

##
## HomHom( M, K, N ) = Hom( Hom( M, K ), N )
##

##  <#GAPDoc Label="Functor_HomHom">
##  <ManSection>
##    <Var Name="Functor_HomHom"/>
##    <Description>
##      The bifunctor <C>HomHom</C>.
##      <P/>
##      <#Include Label="Functor_HomHom:code">
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##  <#GAPDoc Label="Functor_HomHom:code">
##    Below is the only <E>specific</E> line of code used to define <C>Functor_HomHom_for_fp_modules</C>
##    and all the different operations <C>HomHom</C> in &homalg;.
##      <Listing Type="Code"><![CDATA[
Functor_Hom_for_fp_modules * Functor_Hom_for_fp_modules;
##  ]]></Listing>
##  <#/GAPDoc>

##  <#GAPDoc Label="ComposeFunctors:example">
##    <#Include Label="Functor_HomHom:code">
##  <#/GAPDoc>

##
## LHomHom( M, K, N ) = L(Hom( Hom( -, K ), N ))( M )
##

##  <#GAPDoc Label="Functor_LHomHom">
##  <ManSection>
##    <Var Name="Functor_LHomHom"/>
##    <Description>
##      The bifunctor <C>LHomHom</C>.
##      <P/>
##      <#Include Label="Functor_LHomHom:code">
##    </Description>
##  </ManSection>
##  <#/GAPDoc>

##  <#GAPDoc Label="Functor_LHomHom:code">
##    Below is the only <E>specific</E> line of code used to define <C>Functor_LHomHom_for_fp_modules</C>
##    and all the different operations <C>LHomHom</C> in &homalg;.
##      <Listing Type="Code"><![CDATA[
LeftDerivedFunctor( Functor_HomHom_for_fp_modules );
##  ]]></Listing>
##  <#/GAPDoc>

##  <#GAPDoc Label="LeftDerivedFunctor:example2">
##    <#Include Label="Functor_LHomHom:code">
##  <#/GAPDoc>


[ 0.64Quellennavigators  Projekt   ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge