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


Quelle  linalg.gi   Sprache: unbekannt

 
#############################################################################
##
#W  linalg.gi               GAP 4 package `cvec'                
##                                                            Max Neunhoeffer
##
##  Copyright (C) 2007  Max Neunhoeffer, Lehrstuhl D f. Math., RWTH Aachen
##  This file is free software, see license information at the end.
##
##  This file contains the higher levels for efficient implementation of
##  some linear algebra routines for compact vectors over finite fields. 
##


#############################################################################
# Creation of semi-echelonised basis:
#############################################################################

InstallMethod( SEBMaker, "generic method",
  [ IsRowListMatrix, IsList],
  function( vectors, pivots )
    local b;
    b := rec( vectors := vectors, pivots := pivots );
    return Objectify(NewType(FamilyObj(b.vectors),SEBType and IsMutable),b);
  end );

InstallMethod( SEBMaker, "for a cmat, and an integer list",
  [ IsCMatRep, IsList ],
  function( vectors, pivots )
    local b,cl;
    b := rec( vectors := vectors, pivots := pivots );
    cl := CVEC_NewCVecClassSameField(b.vectors!.vecclass,1);
    b.helper := CVEC_New(cl);
    return Objectify(NewType(FamilyObj(b.vectors),
                             SEBType and IsMutable and IsCMatSEB),b);
  end );

InstallOtherMethod( SEBMaker, "empty plist method",
  [ IsList and IsEmpty, IsList and IsEmpty ],
  function( vectors, pivots )
    local b;
    b := rec( vectors := vectors, pivots := pivots );
    return Objectify(NewType(FamilyObj(b.vectors),SEBType and IsMutable),b);
  end );

InstallMethod( SemiEchelonBasisMutable, "for a semi echelonised basis record",
  [IsRecord],
  function(r)
    local i, b;
    b := rec( vectors := r.vectors, pivots := [] );
    for i in [1..NumberColumns(b.vectors)] do
        if IsBound(r.heads[i]) and r.heads[i] > 0 then
            b.pivots[r.heads[i]] := i;
        fi;
    od;
    return SEBMaker(b.vectors,b.pivots);
  end);

InstallMethod( EmptySemiEchelonBasis, "for a row list matrix",
  [ IsRowListMatrix ],
  function( m )
    return SEBMaker( Matrix([],NumberColumns(m),m), [] );
  end );

InstallMethod( EmptySemiEchelonBasis, "for a row vector",
  [ IsRowVector ],
  function( v )
    return EmptySemiEchelonBasis(CMat([v]));
  end );

InstallMethod( Vectors, "for a semi echelonsised basis", [ SEBType ],
  function(b)
    return b!.vectors;
  end );

InstallMethod( BasisVectors, "for a semi echelonised basis", [ SEBType ],
  function(b)
    return b!.vectors;
  end );

InstallMethod( Length, "for a semi echelonised basis", [ SEBType ],
  function(b)
    return Length(b!.vectors);
  end );

InstallMethod( Pivots, "for a semi echelonised basis", [ SEBType ],
  function(b)
    return b!.pivots;
  end );

InstallMethod( ViewObj, "for a semi echelonised basis",
  [ SEBType ],
  function(b)
    Print("<");
    if not(IsMutable(b)) or not(IsMutable(b!.vectors)) then
        Print("immutable ");
    fi;
    if Length(b!.vectors) = 0 then
        Print("empty semi echelonized basis>");
    else
      Print("semi echelonized basis over ",BaseDomain(b!.vectors)," of length ",
          NumberRows(b!.vectors),">");
    fi;
  end);

InstallMethod( Display, "for a semi echelonised basis",
  [ SEBType ],
  function(b)
    Print("<");
    if not(IsMutable(b)) or not(IsMutable(b!.vectors)) then
        Print("immutable ");
    fi;
    if Length(b!.vectors) = 0 then
        Print("empty semi echelonized basis>");
    else
        Print("semi echelonized basis over ",BaseDomain(b!.vectors),
              " of length ", NumberRows(b!.vectors),"\n");
        Display(b!.vectors);
        Print(">");
    fi;
  end );

#############################################################################
# High speed cleaning of vectors, semi-echelonised matrices:
#############################################################################

BindGlobal( "CVEC_CleanRow", function( basis, vec, extend, dec)
  local c,firstnz,j,lc,len,lev,mo,newpiv,pivs;
  # INPUT
  # basis: component object with fields
  #        pivots   : integer list of pivot columns of basis matrix
  #        vectors : matrix of basis vectors in semi echelon form
  # vec  : vector of same length as basis vectors
  # extend : boolean value indicating whether the basis will we extended
  #          note that for the greased case also the basis vectors before
  #          the new one may be changed
  # OUTPUT
  # returns decomposition of vec in the basis, if possible.
  # otherwise returns 'fail' and adds cleaned vec to basis and updates
  # pivots
  # NOTES
  # destructive in both arguments

  # Clear dec vector if given:
  if dec <> fail then
    MultVector(dec,Zero(dec[1]));
  fi;
  
  # First a little shortcut:
  firstnz := PositionNonZero(vec);
  if firstnz > Length(vec) then
      return true;
  fi;

  len := Length(basis!.vectors);
  if len = 1 and IsPlistRep(basis!.vectors) then
      ConvertToMatrixRep(basis!.vectors);
  fi;
          
  for j in [1..len] do
    if basis!.pivots[j] >= firstnz then
      c := vec[ basis!.pivots[ j ] ];
      if not IsZero( c ) then
        if dec <> fail then
          dec[ j ] := c;
        fi;
        AddRowVector( vec, basis!.vectors[ j ], -c );
      fi;
      #firstnz := basis!.pivots[j]+1;
    fi;
  od;

  newpiv := PositionNonZero( vec );
  if newpiv = Length( vec ) + 1 then
    return true;
  else
    if extend then
      c := vec[newpiv];
      if dec <> fail then
        dec[len+1] := c;
      fi;
      MultVector( vec, c^-1 );
      Add( basis!.vectors, vec );
      Add( basis!.pivots, newpiv );
    fi;
    return false;
  fi;
end );
    
InstallMethod( CleanRow, 
  "GAP method for a semi echelon basis, a vector, and a boolean or vector", 
  [SEBType and IsMutable, IsVectorObj, IsBool, IsObject], CVEC_CleanRow );

InstallMethod( CleanRow, 
  "kernel method for a record, a cvec, and a boolean or cvec", 
  [SEBType and IsMutable and IsCMatSEB, IsCVecRep, IsBool, IsObject], 
   CVEC_CLEANROWKERNEL );

InstallMethod( IO_Pickle, "for a file and a cmat semi echelonized basis",
  [ IsFile, IsCMatSEB and IsList and IsSemiEchelonized ],
  function( f, seb )
    return IO_GenericObjectPickler(f,"CSEB",[seb!.vectors,seb!.pivots],
                                   seb,[],[],[]);
  end );

IO_Unpicklers.CSEB :=
  function( f )
    local vectors, pivots, seb;
    vectors := IO_Unpickle(f); 
    if vectors = IO_Error then return IO_Error; fi;
    pivots := IO_Unpickle(f);
    if vectors = IO_Error then return IO_Error; fi;
    seb := SEBMaker(vectors,pivots);
    IO_GenericObjectUnpickler(f,seb,[],[]);
    return seb;
  end;

InstallMethod( Coefficients, "for a semi echelonized basis, and a vector",
  [SEBType, IsVectorObj],
  function(b,v)
    local dec;
    dec := ZeroVector(Length(b),v);
    if CleanRow(b,ShallowCopy(v),false,dec) then
        return dec;
    else
        return fail;
    fi;
  end );

InstallMethod( LinearCombination, "for a semi echelonized basis, and a vector",
  [SEBType, IsVectorObj],
  function(b,v)
    return v * b!.vectors;
  end );


#############################################################################
# Computing semi-echelonised bases from matrices:
#############################################################################

InstallMethod( SemiEchelonBasisMutableX, "for a row list matrix", 
  [IsRowListMatrix and IsMutable],
  function( m )
    local b,v;
    b := EmptySemiEchelonBasis(m);
    for v in m do CleanRow(b,v,true,fail); od;
    return b;
  end );
InstallMethod( SemiEchelonBasisMutable, "for a row list matrix", 
  [IsRowListMatrix],
  function( m )
    return SemiEchelonBasisMutableX( MutableCopyMat( m ) );
  end );
InstallMethod( SemiEchelonBasisMutableX, "for a row list matrix", 
  [IsRowListMatrix],
  function( m )
    return SemiEchelonBasisMutableX( MutableCopyMat( m ) );
  end );

InstallMethod( SemiEchelonBasisMutableTX, "for a row list matrix", 
  [IsRowListMatrix and IsMutable],
  function( m )
    local b,coeffs,dec,i,j,mo,newcoeffs,newrelation,relations,rl,s,v,zerov;
    rl := NumberColumns(m);
    b := EmptySemiEchelonBasis( m );
    zerov := ZeroVector(rl,m);
    dec := ZeroVector(Length(m),zerov);  # Maximal length of the basis
    if Length(m) = 0 then
        b!.coeffs := Matrix([],0,m);
        b!.relations := rec( vectors := Matrix([],0,m), pivots := [] );
        return b;
    fi;
    # In the end, we want: coeffs * m = b!.vectors:
    coeffs := Matrix([],Length(m),m);
    # In the end, we want relations to be a basis for the nullspace of m:
    relations := Matrix([],Length(m),m);
    i := 0;  # this is the length of coeffs
    mo := -One(BaseDomain(m));
    for j in [1..Length(m)] do
        v := m[j];
        if not CleanRow(b,v,true,dec) then
            # a new vector in the basis, we have to produce a coeff line:
            # now dec * b!.vectors = v (original one)
            # need: coeffs * mat = b!.vectors[Length(b!.vectors)]
            # ==> we use b!.vectors[Length(b!.vectors)]
            #               = (v-dec{[1..i]}*b!.vectors)/dec[i+1]
            if i > 0 then
                s := dec[i+1]^-1;
                newcoeffs := ((-s) * dec{[1..i]}) * coeffs;
                newcoeffs[j] := s;
                Add(coeffs,newcoeffs);
            else
                newcoeffs := ZeroMutable(dec);
                newcoeffs[j] := dec[1]^-1;
                Add(coeffs,newcoeffs);
            fi;
            i := i + 1;
        else
            if i > 0 then
                newrelation := dec{[1..i]} * coeffs;
                newrelation[j] := mo;
            else
                newrelation := ZeroMutable(dec);
                newrelation[j] := mo;
            fi;
            Add(relations,newrelation);
        fi;
    od;
    b!.coeffs := coeffs;
    b!.relations := relations;
    return b;
  end);
InstallMethod( SemiEchelonBasisMutableT, "for a row list matrix", 
  [IsRowListMatrix],
  function( m )
    return SemiEchelonBasisMutableTX( MutableCopyMat( m ) );
  end );
InstallMethod( SemiEchelonBasisMutableTX, "for an immutable row list matrix", 
  [IsRowListMatrix],
  function( m )
    return SemiEchelonBasisMutableTX( MutableCopyMat( m ) );
  end );

InstallMethod( SemiEchelonBasisMutablePX, "for a row list matrix", 
  [IsRowListMatrix and IsMutable],
  function( m )
    # In the end we want b!.p * b!.vectors = m
    local b,p,dec,j,v,zerov, decl;
    b := EmptySemiEchelonBasis( m );
    zerov := ZeroVector(NumberColumns(m),m);
    decl := Minimum( 100, Length(m) );
    dec := ZeroVector(decl,zerov);
    if Length(m) = 0 then
        b!.p := Matrix( [], 0, m );
        return b;
    fi;
    p := [];   # we collect the vectors in a list because we do not know
               # their final length
    for j in [1..Length(m)] do
        v := m[j];
        if Length( b!.vectors ) >= decl then
            decl := Maximum( decl + 100, Length(m) );
            dec := ZeroVector( decl, dec );
        fi;
        CleanRow(b,v,true,dec);
        Add( p, ShallowCopy( dec ) );
    od;
    decl := Length( b!.vectors );
    b!.p := Matrix([],decl,m);
    j := 1;
    while j <= Length( p ) and Length( p[ j ] ) < decl do
        dec := ZeroVector( decl, dec );
        CopySubVector( p[ j ], dec, [1..Length(p[j])],[1..Length(p[j])] );
        Add(b!.p,dec);
        j := j + 1;
    od;
    while j <= Length( p ) do
        Add(b!.p,p[ j ]{[1..decl]});
        j := j + 1;
    od;
    return b;
  end);
InstallMethod( SemiEchelonBasisMutableP, "for a row list matrix", 
  [IsRowListMatrix],
  function( m )
    return SemiEchelonBasisMutablePX( MutableCopyMat( m ) );
  end );
InstallMethod( SemiEchelonBasisMutablePX, "for an immutable row list matrix", 
  [IsRowListMatrix],
  function( m )
    return SemiEchelonBasisMutablePX( MutableCopyMat( m ) );
  end );

InstallMethod( NullspaceMatMutableX, "for an immutable row list matrix", 
  [IsRowListMatrix],
  function( m )
    local b;
    b := SemiEchelonBasisMutableTX( MutableCopyMat( m ) );
    return b!.relations;
  end );
InstallMethod( NullspaceMatMutableX, "for a row list matrix", 
  [IsRowListMatrix and IsMutable],
  function( m )
    local b;
    b := SemiEchelonBasisMutableTX(m);
    return b!.relations;
  end );
InstallMethod( NullspaceMatMutable, "for a row list matrix", 
  [IsRowListMatrix],
  function( m )
    return NullspaceMatMutableX( MutableCopyMat( m ) );
  end );

InstallOtherMethod( NullspaceMatDestructive, "for a cmat", 
  [IsCMatRep and IsOrdinaryMatrix],
  function( m )
    local res;
    res := NullspaceMatMutableX( m );
    MakeImmutable(res);
    return res;
  end );

InstallOtherMethod( NullspaceMat, "for a cmat", 
  [IsCMatRep and IsOrdinaryMatrix],
  function( m )
    local res;
    res := NullspaceMatMutableX( MutableCopyMat( m ) );
    MakeImmutable(res);
    return res;
  end );

InstallMethod( SemiEchelonBasisNullspaceX, "for a row list matrix",
  [IsRowListMatrix],
  function( m )
    local n;
    n := NullspaceMatMutableX( m );
    return SemiEchelonBasisMutableX( n );
  end );
InstallMethod( SemiEchelonBasisNullspace, "for a row list matrix",
  [IsRowListMatrix],
  function( m )
    local n;
    n := NullspaceMatMutable( m );
    return SemiEchelonBasisMutableX( n );
  end );


#############################################################################
# Intersections and sums of spaces given by bases:
#############################################################################

InstallMethod( IntersectionAndSumRowSpaces, "for two semi echelonised bases",
  [ SEBType and IsMutable, SEBType and IsMutable ],
  function( b1, b2 )
    # Here is a proof why the following works. It is obviously better
    # than Zassenhaus' algorithm as it only works with vectors of the
    # original size rather than vectors of double length. It also
    # can profit from b1 and b2 already being semi-echelonised.
    #
    # Let V be the full row space and W be the row space generated by b1.
    # The semi-echenolised basis b1 defines a direct sum decomposition
    # V = W + W_0 where W_0 is the subspace of V of vectors having zeros
    # in the pivot columns of b1. Cleaning with b1 is an algorithm that
    # computes the projection p_0 onto W_0. The resulting dec contains 
    # coefficients such that the corresponding linear combination of b1
    # produces the result of the projection p onto W. Thus we can effectively
    # compute both projections.
    #
    # Let U be the span of b2.
    #
    # If d1 is the dimension of b1 and d2 that of b2 and s the dimension
    # of the sum, then the intersection has dimension d1+d2-s.
    #
    # By cleaning every vector of b2 with b1 we essentially compute
    # the image of U under the projection p_0. Computing a semi-echelonised
    # basis of the image in the variable "extension" computes this
    # image p_0(U) and at the same time produces generators for the
    # kernel, since every relation found in p_0(U) gives rise to an
    # element of the kernel, which is exactly the intersection U \cap W.
    # On the other hand these generators for the kernel are automatically
    # linearly independent, since the last non-zero entry is in different
    # columns. Thus they span the kernel by a dimension argument.
    #
    local d,dec,extension,images,res,v,w;
    images := Matrix([],NumberColumns(b1!.vectors),b1!.vectors);
    dec := ZeroVector( Length(b2), b1!.vectors );
    d := Length(b1!.vectors);
    # Calculate image:
    for v in b2!.vectors do
        w := ShallowCopy(v);
        CleanRow(b1,w,false,fail);
        Add(images,w);
    od;
    extension := SemiEchelonBasisMutableTX(images);
    res := rec( intersection := extension!.relations * b2!.vectors,
                sum := SEBMaker(ShallowCopy(b1!.vectors),
                                ShallowCopy(b1!.pivots)) );
    Append(res.sum!.vectors,extension!.vectors);
    Append(res.sum!.pivots,extension!.pivots);
    return res;
  end );

InstallMethod( IntersectionAndSumRowSpaces, "for two row list matrices",
  [ IsRowListMatrix, IsRowListMatrix ],
  function( m1, m2 )
    local b1, b2;
    b1 := SemiEchelonBasisMutable(m1);
    b2 := SemiEchelonBasisMutable(m2);
    return IntersectionAndSumRowSpaces(b1,b2);
  end );

# Just an experiment:
BindGlobal( "SumIntersectionMatCMat", 
  function(m1,m2)
    local i,int,mat,n,sum,v,w,zero;
    n := NumberColumns(m1);
    mat := Matrix([],2*n,m1);
    zero := ZeroVector(2*n,m1);
    for v in m1 do
        w := ShallowCopy(zero);
        CopySubVector(v,w,[1..n],[1..n]);
        CopySubVector(v,w,[1..n],[n+1..2*n]);
        Add(mat,w);
    od;
    for v in m2 do
        w := ShallowCopy(zero);
        CopySubVector(v,w,[1..n],[1..n]);
        Add(mat,w);
    od;
    mat := SemiEchelonBasisMutableX(mat);
    sum := Matrix([],n,m1);
    int := Matrix([],n,m1);
    for i in [1..Length(mat!.vectors)] do
        if mat!.pivots[i] > n then
            Add(int,mat!.vectors[i]{[n+1..2*n]});
        else
            Add(sum,mat!.vectors[i]{[1..n]});
        fi;
    od;
    return [sum,int];
  end );

# Make it public:
InstallMethod( SumIntersectionMat, "for two cmats",
  [ IsCMatRep, IsCMatRep ],
  SumIntersectionMatCMat );


#############################################################################
# Characteristic polynomials:
#############################################################################

InstallGlobalFunction( CVEC_CharacteristicPolynomialFactors,
function(m,indetnr)
  # m must be square
  local L,b,b2,closed,d,dec,f,facs,fam,i,l,lambda,newlambda,o,p,
        newpiv,pivs,subdim,v,vcopy,zero;
  zero := ZeroVector(NumberColumns(m),m);
  d := Length(m);
  b := EmptySemiEchelonBasis(m);
  pivs := [1..d];
  facs := [];
  f := BaseDomain(m);
  o := One(f);
  fam := FamilyObj(o);
  dec := ShallowCopy(zero);
  while Length(pivs) > 0 do
      subdim := Length(b!.pivots);
      p := pivs[1];
      v := ShallowCopy(zero);
      v[p] := o;
      b2 := EmptySemiEchelonBasis(m);
      Add(b2!.vectors,v);
      Add(b2!.pivots,p);
      lambda := [dec{[1]}];
      lambda[1][1] := o;
      RemoveSet(pivs,p);
      while true do   # break is used to jump out
          v := v * m;
          vcopy := ShallowCopy(v);
          CleanRow(b,vcopy,false,fail);
          closed := CleanRow(b2,vcopy,true,dec);
          if closed then break; fi;
          Add(lambda,dec{[1..Length(b2!.pivots)]});
          RemoveSet(pivs,b2!.pivots[Length(b2!.pivots)]);
      od;
      d := Length(b2!.pivots);
      # we have the lambdas, expressing v*m^(i-1) in terms of the semiechelon
      # basis, now we have to express v*m^d in terms of the v*m^(i-1), that
      # means inverting a matrix: 
      L := ZeroMatrix(d,Length(lambda[d]),m);
      for i in [1..d] do
          CopySubVector(lambda[i],L[i],[1..i],[1..i]);
      od;
      l := - dec{[1..d]} * L^-1;
      l := Unpack(l);
      Add(l,o);
      ConvertToVectorRep(l,Size(f));
      Add(facs,UnivariatePolynomialByCoefficients(fam,l,indetnr));
      # Add b2 to b:
      Append(b!.vectors,b2!.vectors);
      Append(b!.pivots,b2!.pivots);
  od;
  return facs;
end );

InstallMethod( CharacteristicPolynomialOfMatrix, 
  "for a row list matrix and an indet nr", 
  [IsRowListMatrix, IsPosInt],
  function( m, indetnr )
    local facs;
    facs := CVEC_CharacteristicPolynomialFactors(m, indetnr);
    return rec( poly := Product(facs), factors := facs );
  end );

InstallMethod( CharacteristicPolynomialOfMatrix, "for a row list matrix", 
  [IsRowListMatrix],
  function( m )
    local facs;
    facs := CVEC_CharacteristicPolynomialFactors(m, 1);
    return rec( poly := Product(facs), factors := facs );
  end );

InstallMethod( FactorsOfCharacteristicPolynomial, "for a row list matrix", 
  [IsRowListMatrix],
  function( m )
    return FactorsOfCharacteristicPolynomial(m,1);
  end );

InstallMethod( FactorsOfCharacteristicPolynomial, 
  "for a row list matrix and an indet nr", 
  [IsRowListMatrix, IsPosInt],
  function( m, indetnr )
    local f,facs,irrfacs,pr;
    facs := CVEC_CharacteristicPolynomialFactors(m,indetnr);
    pr := PolynomialRing(BaseDomain(m),[indetnr]);
    irrfacs := [];
    for f in facs do
        Append(irrfacs,Factors(pr,f));
    od;
    Sort(irrfacs);
    return irrfacs;
  end );

BindGlobal( "CVEC_ActionOnQuotient", function( gens, basis )
  local dimsubspc, dimfullspc, dimquotspc, diff, zerov, imgens, x, i, k;
  # INPUT
  # gens : List of matrices
  # basis : basis of submodule ie record with fields
  #         pivots   : integer list of pivot columns
  #         vectors : matrix of basis in semi-echelon form
  # OUTPUT
  # List of matrices representing the action of the module given by 'gens'
  # on the quotient space induced by 'basis'
  # NOTES
  # 
  dimsubspc := Length( basis!.vectors );
  dimfullspc := NumberColumns( basis!.vectors );
  dimquotspc := dimfullspc - dimsubspc;
  diff := Difference( [1..dimfullspc], basis!.pivots );
  zerov := ZeroVector( dimquotspc, basis!.vectors ); #prepare vector type
  imgens := []; # stores result
  for i in [ 1 .. Length( gens ) ] do
    imgens[ i ] := ZeroMatrix( dimquotspc, dimquotspc, basis!.vectors );
    # grab rows corresponding to added basis vectors:
    x := MutableCopyMat(gens[ i ]{ diff }); 
    for k in [1..Length(x)] do # clean rows with subspace basis
      CleanRow( basis, x[ k ], false, fail );
    od;
    # now remove zero columns
    CopySubMatrix( x, imgens[ i ], [1..dimquotspc], [1..dimquotspc], 
                                   diff , [1..dimquotspc] );
  od;
  return imgens;
end );

InstallGlobalFunction( CVEC_MinimalPolynomial, function(m)
  # m must be square
  # This is the old algorithm, implemented for RowListMatrices
  local L,b,b2,closed,d,dec,f,fam,i,l,lambda,o,p,pivs,poly,subdim,v,vcopy,zero;

  zero := ZeroVector(NumberColumns(m),m);
  d := NumberRows(m);
  b := EmptySemiEchelonBasis(m);
  pivs := [1..d];
  f := BaseDomain(m);
  poly := One(PolynomialRing(f,[1]));
  o := One(f);
  fam := FamilyObj(o);
  dec := ShallowCopy(zero);
  while Length(pivs) > 0 do
      subdim := Length(b!.pivots);
      p := pivs[1];
      v := ShallowCopy(zero);
      v[p] := o;
      b2 := EmptySemiEchelonBasis(m);
      Add(b2!.vectors,v);
      Add(b2!.pivots,p);
      lambda := [dec{[1]}];
      lambda[1][1] := o;
      RemoveSet(pivs,p);
      while true do   # break is used to jump out
          v := v * m;
          vcopy := ShallowCopy(v);
          closed := CleanRow(b2,vcopy,true,dec);
          if closed then break; fi;
          Add(lambda,dec{[1..Length(b2!.pivots)]});
          RemoveSet(pivs,b2!.pivots[Length(b2!.pivots)]);
      od;
      d := Length(b2!.pivots);
      # we have the lambdas, expressing v*m^(i-1) in terms of the semiechelon
      # basis, now we have to express v*m^d in terms of the v*m^(i-1), that
      # means inverting a matrix: 
      L := ZeroMatrix(d,d,m);
      for i in [1..d] do
          CopySubVector(lambda[i],L[i],[1..i],[1..i]);
      od;
      l := - dec{[1..d]} * L^-1;
      l := Unpack(l);
      Add(l,o);
      ConvertToVectorRep(l,Size(f));
      poly := Lcm(poly,UnivariatePolynomialByCoefficients(fam,l,1));
      # Add b2 to b:
      for v in b2!.vectors do
          if not(CleanRow(b,v,true,fail)) then
              RemoveSet(pivs,b!.pivots[Length(b!.pivots)]);
          fi;
      od;
  od;
  return poly;
end );

InstallGlobalFunction( CVEC_CharAndMinimalPolynomial, function( m, indetnr )
  # This is an early try to implement a deterministic, faster minimal
  # polynomial algorithm. It now uses the RowListMatrix interface.
  # Currently, this does not work properly, the method works in principle,
  # but there is something wrong with the factoring out of subspaces and
  # the corresponding lower and upper bounds. DO NOT USE!
  local col,deg,facs,havedim,i,irreds,l,lowbound,lowbounds,mp,mult,
        multfactoredout,multmin,nrblocks,ns,p,pr,targetmult,upbound,x;
  # First the characteristic polynomial:
  facs := CVEC_CharacteristicPolynomialFactors(m,indetnr);
  if Length(facs) = 1 then
      return rec( charpoly := facs[1], irreds := facs, mult := [1],
                  minpoly := facs[1], multmin := [1] );
  fi;
  Info(InfoCVec,2,
       "More than 1 factor, factorising characteristic polynomial...");
  # Factor all parts:
  pr := PolynomialRing(BaseDomain(m),[indetnr]);
  col := List(facs,f->Collected(Factors(pr,f)));
  # Collect all irreducible factors:
  irreds := [];
  mult := [];
  lowbounds := [];
  multmin := [];
  for l in col do
      for i in l do
          p := Position(irreds,i[1]);
          if p = fail then
              Add(irreds,i[1]);
              Add(mult,i[2]);
              Add(lowbounds,i[2]);
              Add(multmin,0);
              p := Sortex(irreds);
              mult := Permuted(mult,p);
              lowbounds := Permuted(lowbounds,p);
          else
              mult[p] := mult[p] + i[2];
              if i[2] > lowbounds[p] then
                  lowbounds[p] := i[2];
              fi;
          fi;
      od;
  od;
  mp := irreds[1]^0;
  Info(InfoCVec,2,"Degrees of irreducible factors of charpoly:",
       List(irreds,DegreeOfLaurentPolynomial));
  for i in [1..Length(irreds)] do
      deg := DegreeOfLaurentPolynomial(irreds[i]);
      Info(InfoCVec,2,"Working on irreducible factor of degree ",deg,"...");
      if mult[i] = lowbounds[i] then
          Info(InfoCVec,2,"Found factor of degree ",deg," with multiplicity ",
                mult[i]);
          mp := mp * irreds[i]^mult[i];
          multmin[i] := mult[i];
      else
          x := Value(irreds[i],m);
          targetmult := mult[i];      # the multiplicity to reach
          lowbound := lowbounds[i];   # from the calc. of the charpoly
          upbound := targetmult;      # an upper bound
          Info(InfoCVec,2,"First lower bound: ",lowbound,
               " upper bound: ",upbound);
          multfactoredout := 0;       # no quotient yet
          # Note that when we divide something out, we adjust targetmult
          # and record this in multfactoredout.
          # This stores the number of dimensions each Jordan block is
          # made smaller by our current quotient.
          # We also adjust lowbound and upbound when we go to a quotient!
          while true do   # break is used to leave 
              # This loop tries to determine the size of the largest Jordan
              # block of the matrix x and either exits with 
              #    lowbound=upbound=that size
              # or reduces the problem to a smaller one in some quotient,
              # thereby adjusting multfactoredout by the number of rows/cols
              # that are divided away by the quotient and going to the
              # next iteration.
              # I.e. in the end the right multiplicity is always equal to
              #    lowbound+multfactoredout

              # First calculate a nullspace and get some estimates:
              Info(InfoCVec,2,"Target multiplicity: ",targetmult,
                   ", already factored out: ",multfactoredout);
              ns := SemiEchelonBasisNullspace(x);
              havedim := Length(ns!.vectors);
              Info(InfoCVec,2,"Found nullspace of dimension ",havedim);
              # We have a lower bound for the multiplicity in the minpoly
              # from earlier and one from the number of generalized Jordan 
              # blocks we see:
              nrblocks := havedim/deg;   # this is in quotient!
              # note that lowbound is absolute i.e. in the original space:
              lowbound := Maximum(lowbound,
                                  QuoInt(targetmult+nrblocks-1,nrblocks));
              upbound := Minimum(upbound,targetmult-nrblocks+1);
              Info(InfoCVec,2,"Lower bound: ",lowbound," upper bound: ",
                   upbound);
              if lowbound = upbound then break; fi;

              # Now we divide out the nullspace and go to lowbound:
              lowbound := lowbound-1;   # Adjustment because of quotient!
              Info(InfoCVec,2,"Factoring out nullspace of dimension ",havedim,
                   " and going to power ",lowbound);
              x := CVEC_ActionOnQuotient([x],ns)[1];
              multfactoredout := multfactoredout + 1;
              targetmult := targetmult - nrblocks;
              x := x^lowbound;
              Info(InfoCVec,2,"Target multiplicity: ",targetmult);
              ns := SemiEchelonBasisNullspace(x);
              havedim := Length(ns!.vectors);
              Info(InfoCVec,2,"Found nullspace of dimension ",havedim);

              # Check, whether we have the complete generalized eigenspace:
              if havedim/deg = targetmult then
                  # lowbound is correct!
                  upbound := lowbound;
                  break;
              fi;
              
              # Now we want to go to the quotient and redo everything:
              Info(InfoCVec,2,"Factoring out nullspace of dimension ",havedim,
                   " and going to power ",lowbound);
              x := CVEC_ActionOnQuotient([x],ns)[1];
              multfactoredout := multfactoredout + 1 + lowbound;
              targetmult := targetmult - Length(ns!.vectors)/deg;
              lowbound := 0;   # we do not know anything about this quotient
              upbound := targetmult;
          od;
          Info(InfoCVec,2,"Done! Multiplicity is ",lowbound+multfactoredout);
          mp := mp * irreds[i]^(lowbound+multfactoredout);
          multmin[i] := (lowbound+multfactoredout);
      fi;
  od;
  return rec(charpoly := Product(facs), irreds := irreds, mult := mult,
             minpoly := mp, multmin := multmin);
end );

InstallMethod( CharAndMinimalPolynomialOfMatrix, "for a matrix and an indet nr",
  [IsCMatRep, IsPosInt],
  function( m, indetnr )
    return MinimalPolynomialOfMatrixMC(m,0,indetnr);
  end );

InstallMethod( CharAndMinimalPolynomialOfMatrix, "for a matrix", [IsCMatRep],
  function( m )
    return MinimalPolynomialOfMatrixMC(m,0);
  end );

InstallMethod( MinimalPolynomialOfMatrix, "for a matrix and an indet nr",
  [IsCMatRep, IsPosInt],
  function( m, indetnr )
    local res;
    res := CVEC_CharAndMinimalPolynomial(m,indetnr);
    return res.minpoly;
  end );

InstallMethod( MinimalPolynomialOfMatrix, "for a matrix", [IsCMatRep],
  function( m )
    local res;
    res := CVEC_CharAndMinimalPolynomial(m,1);
    return res.minpoly;
  end );

InstallGlobalFunction( CVEC_GlueMatrices, function(l)
  # all elements of the list l must be CMats over the same field
  # l must not be empty
  local d,g,i,m,n,pr,pos,x;
  n := Sum(l,NumberRows);
  m := ZeroMatrix(n,n,l[1]);
  pos := 1;
  for i in [1..Length(l)] do
      d := NumberRows(l[i]);
      CopySubMatrix(l[i],m,[1..d],[pos..pos+d-1],
                           [1..d],[pos..pos+d-1]);
      pos := pos + d;
  od;
  if InfoLevel(InfoCVec) >= 2 then 
      OverviewMat(m);
      Print("\n");
  fi;
  return m;
end );
  
InstallGlobalFunction( CVEC_ScrambleMatrices,
  function( l )
  local n,p,d,g,pr,x,xi;
  n := NumberRows(l[1]);
  p := Characteristic(l[1]);
  d := DegreeFFE(l[1]);
  g := GL(n,p^d);
  g := List(GeneratorsOfGroup(g),CMat);
  pr := ProductReplacer(g,rec(scramble := Maximum(QuoInt(n,5),200)));
  x := Next(pr);
  xi := x^-1;
  l := List(l,y->x * y * xi);
  if InfoLevel(InfoCVec) >= 2 and Length(l) = 1 then
      OverviewMat(l[1]);
      Print("\n");
  fi;
  return l;
end );

InstallGlobalFunction( CVEC_MakeJordanBlock, function(f,pol,s)
  local c,cl,d,deg,i,m,n,o,p,pos;
  p := Characteristic(f);
  d := DegreeOverPrimeField(f);
  deg := DegreeOfLaurentPolynomial(pol);
  n := s * deg;
  m := CVEC_ZeroMat(n,n,p,d);
  c := CompanionMat(pol);
  c := CMat(List(c,CVec));
  o := OneMutable(c);
  pos := 1;
  for i in [1..s] do
      CopySubMatrix(c,m,[1..deg],[pos..pos+deg-1],[1..deg],[pos..pos+deg-1]);
      pos := pos + deg;
  od;
  pos := 1;
  for i in [1..s-1] do
      CopySubMatrix(o,m,[1..deg],[pos..pos+deg-1],[1..deg],
                        [pos+deg..pos+2*deg-1]);
      pos := pos + deg;
  od;
  return m;
end );

InstallGlobalFunction( CVEC_MakeExample, function(f,p,l)
  # p a list of irredcible polynomials
  # l a list of lists of the same length than p, each a list of sizes of
  # generalized Jordan blocks
  local i,ll,s,x;
  ll := [];
  for i in [1..Length(p)] do
      for s in l[i] do
          Add(ll,CVEC_MakeJordanBlock(f,p[i],s));
      od;
  od;
  x := CVEC_ScrambleMatrices([CVEC_GlueMatrices(ll)]);
  return x[1];
end );

# The following function is used in the Monte Carlo minimal polynomial
# algorithm:
InstallGlobalFunction( CVEC_CalcOrderPolyTuned, 
function( opi, v, i, indetnr )
  local coeffs,g,h,j,k,ordpol,vv,w,Top,top;
  
  Top := range -> range[Length(range)];

  ordpol := [];  # comes factorised
  while i >= 1 do
      coeffs := v{opi.ranges[i]};
      if IsZero(coeffs) then
          i := i - 1;
          continue;
      fi;
      coeffs := Unpack(coeffs);
      ConvertToVectorRep(coeffs,Size(opi.f));
      h := UnivariatePolynomialByCoefficients(opi.fam,coeffs,indetnr);
      g := opi.rordpols[i]/Gcd(opi.rordpols[i],h);
      Add(ordpol,g);
      # This is the part coming from the ith cyclic space, now go down:
      coeffs := CoefficientsOfUnivariatePolynomial(g);
      w := coeffs[1]*v;
      top := Top(opi.ranges[i]);
      for j in [2..Length(coeffs)] do
          # Now apply base changed matrix to v:
          #   v := v * opi.mm;
          # but remember that we only store the interesting rows of mm:
          vv := ZeroMutable(v);
          CopySubVector(v,vv,[1..top-1],[2..top]);
          for k in [2..i] do
              vv[opi.ranges[k][1]] := opi.z;
          od;
          for k in [1..i] do
              AddRowVector(vv,opi.mm[k],v[Top(opi.ranges[k])],
                           1,Top(opi.ranges[k]));
          od;
          v := vv;
          # Done.
          AddRowVector(w,v,coeffs[j]);
      od;
      # Now w is one subspace lower
      v := w;
      i := i - 1;
      if IsZero(v) then break; fi;
      #Print("i=",i," new vector: ");
      #Display(v);
  od;
  return Product(ordpol);
end );
    
# The following function is used in the Monte Carlo minimal polynomial
# algorithm:
InstallGlobalFunction( CVEC_FactorMultiplicity,
function( p, f )
  local m,r;
  m := 0;
  while true do   # we use break
    r := QuotientRemainder( p, f );
    if not(IsZero(r[2])) then break; fi;
    m := m + 1;
    p := r[1];
  od;
  return m;
end );

InstallGlobalFunction( MinimalPolynomialOfMatrixMC, 
function( arg )
  # The new algorithm of Cheryl and Max. Can be used as Monte Carlo algorithm
  # or as deterministic algorithm with verification.
  #    Neunhöffer, Max; Praeger, Cheryl E., Computing minimal polynomials 
  #    of matrices.  LMS J. Comput. Math. 11 (2008), 252–279.
  # Arguments: m, eps [,indetnr]
  #   m a matrix
  #   eps is a cyclotomic which is an upper bound for the error probability
  #       if it is set to 0 then deterministic verification is done
  #   indetnr is the number of an indeterminate, if omitted 1 is taken
  local A,B,S,coeffs,col,d,dec,eps,g,i,indetnr,irreds,j,l,lcm,lowbounds,m,bound,
        mm,mult,multmin,newBrow,nrunclear,opi,ordpol,ordpolsinv,p,pivs, est,
        pr,prob,proof,res,rl,se,ti,ti2,veri,verify,w,wcopy,ww,zero, wred, c;
  if Length(arg) < 2 or Length(arg) > 3 then
      Error("Usage: m, eps [,indetnr]"); 
  fi;
  m := arg[1];
  eps := arg[2];
  if Length(arg) = 3 then
      indetnr := arg[3];
  else
      indetnr := 1;
  fi;
  verify := IsZero(eps);
  if verify then
      eps := 1/10000;  # a good compromise to start deterministic verification?
  fi;

  ti := Runtime();
  rl := NumberColumns(m);
  zero := ZeroVector(rl,m);
  pivs := [1..rl];   # those are the columns we still want pivots for
  S := EmptySemiEchelonBasis(m);
  # order polynomial infrastructure (grows over time):
  opi := rec( f := BaseDomain(m),
              d := [],          # Degrees of the relative cyclic spaces
              ranges := [],     # numbers of basis vectors
              rordpols := [],   # list of relative order polynomials
              mm := Matrix([],rl,m), # will be crucial rows of base-changed m
            );  
  opi.o := One(opi.f);
  opi.z := Zero(opi.f);
  opi.fam := FamilyObj(opi.o);
  pr := PolynomialRing(opi.f,[indetnr]);
  # We keep the base change between the basis
  #  Y = [x_1,x_1*m,x_1*m^2,...,x_1*(m^(d_1-1)),x_2,...,x_2*m^(d_2-1),...]
  # and the semi echelonised basis S by keeping Y=A*S and S=B*Y at the same 
  # time. We are only interested in B, but we get A as a byproduct.
  A := Matrix([],rl,m);
  B := Matrix([],rl,m);
  ordpolsinv := []; # here we collect information to be used for the order pols

  Info(InfoCVec,2,"Spinning up vectors...");
  while Length(S!.vectors) < Length(m) do
      # random vector cleaned with known subspace
      wred := ShallowCopy(zero);
      while IsZero(wred) do
        Randomize(wred);
        c := wred{S!.pivots};
        wred{S!.pivots} := 0*c;
      od;
      # add random vector from subspace
      if Length(c) > 0  then
        w := wred + c*S!.vectors;
      else
        w := wred;
      fi;
      d := Length(S!.vectors);  # dimension of subspace:
      l := d;  # is always equal to Length(S!.vectors)
      while true do
          dec := ShallowCopy(zero);
          wcopy := ShallowCopy(w);
          if CleanRow(S,wcopy,true,dec) then break; fi;
          l := l + 1;
          Add(A,dec);
          # Now update B:
          # We know: with l=Length(S) we have dec{[1..l]}*S = Y[l]
          # Thus: dec[l]*S[l] = Y[l] - dec{[1..l-1]}*S{[1..l-1]}
          #                   = Y[l] - dec{[1..l-1]}*B*Y{[1..l-1]}
          # by a slight abuse of "*" because dec{[1..l-1]}*B has full length.
          if l=1 then
            newBrow := ShallowCopy(zero);
          else
            newBrow := dec{[1..l-1]}*B;
          fi;
          MultVector(newBrow,-opi.o);
          newBrow[l] := opi.o;
          MultVector(newBrow,dec[l]^-1);
          Add(B,newBrow);
          w := w * m;
      od;
      # Now we have extended the basis S together with A and B, such that
      # we still have Y = A*S and S = B*Y. The latest dec expresses 
      # x*m^something in terms of S, first express it in terms of Y, then
      # we can read off the relative order polynomial from components
      # components d+1 .. Length(S!.vectors).
      dec := dec{[1..l]} * B;
      Add(opi.mm,dec);
      coeffs := -dec{[d+1..l]};
      coeffs := Unpack(coeffs);
      Add(coeffs,opi.o);
      ConvertToVectorRep(coeffs,Size(opi.f));
      Add(opi.rordpols,
          UnivariatePolynomialByCoefficients(opi.fam,coeffs,indetnr));
      Add(opi.d,l-d);  # the degree of the order polynomial
      Add(opi.ranges,[d+1..l]);
      SubtractSet(pivs,S!.pivots{[d+1..l]});
  od;

  ti2 := Runtime();
  Info(InfoCVec,2,"Time until now: ",ti2-ti," lap: ",ti2-ti);

  Info(InfoCVec,2,"Factoring relative order polynomials...");
  # Factor all parts:
  col := List(opi.rordpols,f->Collected(Factors(pr,f)));
  Info(InfoCVec,2,"Time until now: ",Runtime()-ti," lap: ",Runtime()-ti2);
  ti2 := Runtime();
  Info(InfoCVec,2,"Sorting and collecting factors...");
  # Collect all irreducible factors:
  irreds := [];
  mult := [];
  lowbounds := [];
  multmin := [];
  for l in col do
      for i in l do
          p := PositionSorted(irreds,i[1]);
          if p > Length(irreds) or irreds[p] <> i[1] then
              Add(irreds,i[1]);
              Add(mult,i[2]);
              Add(lowbounds,i[2]);
              Add(multmin,0);
              p := Sortex(irreds);
              mult := Permuted(mult,p);
              lowbounds := Permuted(lowbounds,p);
          else
              mult[p] := mult[p] + i[2];
              if i[2] > lowbounds[p] then
                  lowbounds[p] := i[2];
              fi;
          fi;
      od;
  od;
  repeat   # this is used via break to jump to the end where we return

    if Length(opi.rordpols)^2 < Length(m) then # a quick check for cyclicity:
        lcm := Lcm(opi.rordpols);
        if Degree(lcm) = rl then
            Info(InfoCVec,2,"Cyclic matrix, this proves minpoly=charpoly!");
            proof := true;
            multmin := ShallowCopy(mult);
            ordpol := lcm;
            break;   # go to the end, report success
        fi;
    fi;

    nrunclear := 0;   # number of irreducible factors the multiplicity of which
                      # are not yet known
    est := [];        # for estimate that we have not yet found full 
                      # multiplicities, see Prop. 6.1, 6.3 (only need to
                      # consider irreds not known to have same multiplicity
                      # as in characteristic polynomial)
    for i in [1..Length(irreds)] do
        if mult[i] > lowbounds[i] then
            nrunclear := nrunclear + 1;
            Add(est, Degree(irreds[i]));
        fi;
    od;
    Info(InfoCVec,2,"Time until now: ",Runtime()-ti," lap: ",Runtime()-ti2);
    ti2 := Runtime();
    Info(InfoCVec,2,"Number of irreducible factors in charpoly: ",
                    Length(irreds)," mult. in minpoly unknown: ",nrunclear);

    if nrunclear = 0 then
        proof := true;
        multmin := ShallowCopy(mult);
        ordpol := Product([1..Length(irreds)],i->irreds[i]^multmin[i]);
        break;
    fi;

    ordpol := Lcm(Set(opi.rordpols)); # this is a lower bound for the minpoly
                               # in particular, it is a multiple of rordpols[1]

    i := 2;    # here we count, which abs. order polynomial to do next
    veri := 1; # here we count, which irred. factor to verify next
    p := 1/Size(opi.f);   # probability to miss a big Jordan block with the
                          # order polynomial of one vector (upper bound)
    proof := false;

    # This is an upper estimate of the probability not to find a generator
    # of a cyclic space:
    prob := p;   # we already have the order polynomial for one vector 
    while not(proof) do
        Info(InfoCVec,2,"Calculating order polynomials...");
        while i <= Length(opi.rordpols) do
            w := ShallowCopy(zero);
            w[opi.ranges[i][1]] := opi.o;
            g := CVEC_CalcOrderPolyTuned(opi,w,i,indetnr);
            if not(IsZero(ordpol mod g)) then
                ordpol := Lcm(ordpol,g);
            fi;
            if Degree(ordpol) = Length(m) then   # a cyclic matrix!
                proof := true;
                multmin := ShallowCopy(mult);
                Info(InfoCVec,2,"Cyclic matrix, found proof.");
                break;
            fi;
            prob := prob * p;  # = (1/q)^u in Prop. 6.3
            bound := Sum(est, d-> prob^d);
            i := i + 1;
            Info( InfoCVec, 2, "Probability to have missed a factor: ",
                   1.0*bound);
            if bound < eps  then
                break;   # this is the probability to have missed one
            fi;
        od;

        Info(InfoCVec,2,"Checking multiplicities...");
        nrunclear := 0;
        est := [];
        for j in [1..Length(irreds)] do
            multmin[j] := CVEC_FactorMultiplicity(ordpol,irreds[j]);
            if j >= veri and multmin[j] < mult[j] then
                nrunclear := nrunclear + 1;
                Add(est, Degree(irreds[j]));
            fi;
        od;
        if nrunclear = 0 then proof := true; break; fi;   # result is correct!

        if proof or not(verify) then break; fi;

        if i > Length(opi.rordpols) then
            proof := true;
            Info(InfoCVec,2,"Have found proof by computing all absolute ",
                 "order polynomials!");
            break;
        fi;

        Info(InfoCVec,2,"Time until now: ",Runtime()-ti,
             " lap: ",Runtime()-ti2);
        ti2 := Runtime();

        Info(InfoCVec,2,"Verifying result (",nrunclear,
             " unclear multiplicities) ...");
        while veri <= Length(irreds) do
            if multmin[veri] < mult[veri] then
                Info(InfoCVec,2,"Working on factor: ",irreds[veri],
                                " multiplicity: ",multmin[veri]);
                mm := Value(irreds[veri],m)^multmin[veri];
                se := SemiEchelonBasisMutableX(mm);
                if Length(mm)-Length(se!.vectors) < mult[veri] then
                    Info(InfoCVec,2,"Found too small multiplicity!");
                    break;
                fi;
            fi;
            veri := veri + 1;
        od;
        if veri > Length(irreds) then 
            Info(InfoCVec,2,"Verified all irreducible factors, found proof.");
            proof := true; 
        fi;
    od;   # until proof found or break hit
  until true;    # also used to jump out early

  Info(InfoCVec,2,"Time until now: ",Runtime()-ti," lap: ",Runtime()-ti2);
  res := rec(minpoly := ordpol, charpoly := Product( opi.rordpols ),
             opi := opi, irreds := irreds, mult := mult, multmin := multmin,
             proof := proof, prob := eps,
             A := A, B := B, S := S);
  res.iscyclic := Degree(res.minpoly) = Degree(res.charpoly);
  return res;
end );

InstallMethod( MinimalPolynomial, "new MC method with verification",
  [ IsField, IsRowListMatrix and IsOrdinaryMatrix, IsPosInt ],
  function(f,m,indet)
    if f <> BaseDomain(m) then TryNextMethod(); fi;
    return MinimalPolynomialOfMatrixMC(m,0,indet).minpoly;
  end );

InstallMethod( MinimalPolynomialMatrixNC, "new MC method with verification",
  [ IsField, IsRowListMatrix and IsOrdinaryMatrix, IsPosInt ],
  function(f,m,indet)
    return MinimalPolynomialOfMatrixMC(m,0,indet).minpoly;
  end );


##
##  This program is free software; you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation; either version 2 of the License,
##  or (at your option) any later version.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program; if not, write to the Free Software
##  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
##

[ Dauer der Verarbeitung: 0.12 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


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