home *** CD-ROM | disk | FTP | other *** search
- #############################################################################
- ##
- #A rowspace.g GAP library J\"urgen Mnich
- ##
- #A @(#)$Id: rowspace.g,v 3.9 1993/02/09 14:25:55 martin Rel $
- ##
- #Y Copyright 1990-1992, Lehrstuhl D fuer Mathematik, RWTH Aachen, Germany
- ##
- ## This file contains all functions for row spaces.
- ##
- ## $Log: rowspace.g,v $
- #H Revision 3.9 1993/02/09 14:25:55 martin
- #H made undefined globals local
- #H
- #H Revision 3.8 1992/12/16 19:47:27 martin
- #H replaced quoted record names with escaped ones
- #H
- #H Revision 3.7 1992/04/07 16:15:32 jmnich
- #H adapted to changes in the finite field module
- #H
- #H Revision 3.6 1992/04/03 13:10:09 fceller
- #H changed 'Shifted...' into 'Sifted...'
- #H
- #H Revision 3.5 1992/03/17 12:31:20 jmnich
- #H minor style changes, more bug fixes
- #H
- #H Revision 3.4 1992/02/29 13:25:11 jmnich
- #H general library review, some bug fixes
- #H
- #H Revision 3.3 1992/01/09 13:37:06 jmnich
- #H fixed a minor bug in 'BaseTypeRowSpace'
- #H
- #H Revision 3.2 1992/01/07 12:21:07 jmnich
- #H changed a lot
- #H
- #H Revision 3.1 1991/09/24 14:24:59 fceller
- #H Initial Release under RCS
- #H
- ##
-
-
- #############################################################################
- ##
- #F InfoVectorSpace1(...) . . . . . . . . . . . . . . . . package information
- #F InfoVectorSpace2(...) . . . . . . . . . . . . . package debug information
- ##
- if not IsBound( InfoVectorSpace1 ) then InfoVectorSpace1 := Ignore; fi;
- if not IsBound( InfoVectorSpace2 ) then InfoVectorSpace2 := Ignore; fi;
-
-
- #############################################################################
- ##
- #F IntegerTable( <field> ) . . . . . . . . . . . . . calculate integer table
- ##
- IntegerTable := function( field )
- local pow, int, i;
-
- if IsBound( field.integers ) then return field.integers; fi;
- if not IsFinite( field ) then
- Error( "sorry, field has to be finite\n" );
- fi;
- if field.degree <> 1 then
- Error( "sorry, field has to be a prime field\n" );
- fi;
-
- pow := 1;
- int := Int( field.root );
- field.integers := [1..field.char-1];
- field.integers[1] := pow;
- for i in [2..field.char-1] do
- pow := (pow * int) mod field.char;
- field.integers[i] := pow;
- od;
- return field.integers;
- end;
-
-
- #############################################################################
- ##
- #F RowSpace( <gens>, <field>[, <zero>] ) . . . . . . . . create a row space
- ##
- RowSpace := function( arg )
- local gens, v, zero, i;
-
- if Length( arg ) < 2 or Length( arg ) > 3
- or (not IsList( arg[1] ) and not IsInt( arg[1] ))
- or not IsField( arg[2] ) then
- Error( "usage: RowSpace( <generators>, <field>[, <zero>] )\n",
- " or: RowSpace( <dimension>, <field>[, <zero>] )" );
- fi;
-
- # process the arguments, extracting the zero element
-
- if IsInt( arg[1] ) then
- if arg[1] < 1 then
- Error( "sorry, dimension must be a positive integer" );
- fi;
- gens := [ [] ];
- for i in [1..arg[1]] do gens[1][i] := arg[2].zero; od;
- for i in [2..arg[1]] do gens[i] := ShallowCopy( gens[1] ); od;
- for i in [1..arg[1]] do gens[i][i] := arg[2].one; od;
- ForAll( gens, IsVector );
-
- if Length( arg ) = 3 then zero := arg[3];
- else zero := 0 * gens[1];
- fi;
- else
- if Length( arg ) = 3 then zero := arg[3];
- elif arg[1] <> [] then zero := 0 * arg[1][1];
- else
- Error( "sorry, need at least one element" );
- fi;
- gens := [];
- for v in arg[1] do
- if v <> zero then Add( gens, v ); fi;
- od;
- fi;
-
- # create the row space record
-
- return rec(
- generators := gens,
- field := arg[2],
- zero := zero,
- isDomain := true,
- isVectorSpace := true,
- isRowSpace := true,
- isFinite := IsFinite( arg[2] ) or gens = [],
- operations := RowSpaceOps
- );
- end;
-
-
- #############################################################################
- ##
- #F IsRowSpace( <obj> ) . . . . . . . . . . test if an object is a row space
- ##
- IsRowSpace := function( obj )
- return IsRec( obj )
- and IsBound( obj.isRowSpace ) and obj.isRowSpace;
- end;
-
-
- #############################################################################
- ##
- #V RowSpaceOps . . . . . . . . . . . . . . operations record for row spaces
- ##
- RowSpaceOps := ShallowCopy( VectorSpaceOps );
-
-
- #############################################################################
- ##
- #F RowSpaceOps.\=( <V>, <W> ) . . . . . . test if two row spaces are equal
- ##
- ## Two row spaces are considered equal if they are written over the same
- ## field and the canonical bases (i.e. those that are computed via a full
- ## gauss algorithm) are identical. If the attached bases were not computed,
- ## test whether the base of <V> is a subset of <W> and compare the
- ## dimensions.
- ##
- RowSpaceOps.\= := function( V, W )
- local iseq, vbase, wbase;
-
- if IsRowSpace( V ) and IsRowSpace( W ) then
- if V.field = W.field then
- vbase := Base( V );
- wbase := Base( W );
- if V.isComputedBase and W.isComputedBase then
- iseq := vbase = wbase;
- else
- iseq := ForAll( vbase, x -> x in W )
- and Dimension( V ) = Dimension( W );
- fi;
- else
- iseq := false;
- fi;
- else
- iseq := DomainOps.\=( V, W );
- fi;
- return iseq;
- end;
-
-
- #############################################################################
- ##
- #F RowSpaceOps.\<( <V>, <W> ) . . . test if row space <V> is less than <W>
- ##
- ## The algorithm to test one row space being less than an other one may seem
- ## odd, as it checks wether the reversed canonical base is less than that of
- ## the other row space if the fields are equal. Otherwise the decision is
- ## made upon the ordering on the fields.
- ##
- RowSpaceOps.\< := function( V, W )
- local isless, vbase, wbase;
-
- if IsRowSpace( V ) and IsRowSpace( W ) then
- if V.field = W.field then
- vbase := Base( V );
- wbase := Base( W );
- if not V.isComputedBase then
- vbase := V.operations.Base( V );
- fi;
- if not W.isComputedBase then
- wbase := W.operations.Base( W );
- fi;
- isless := Reversed( vbase ) < Reversed( wbase );
- else
- isless := V.field < W.field;
- fi;
- else
- isless := DomainOps.\<( V, W );
- fi;
- return isless;
- end;
-
-
- #############################################################################
- ##
- #F RowSpaceOps.\in( <v>, <V> ) . . . . . . . . . . . test if <v> is in <V>
- ##
- RowSpaceOps.\in := function( v, V )
- return SiftedVector( V, v ) = V.zero;
- end;
-
-
- #############################################################################
- ##
- #F RowSpaceOps.Print( <obj> ) . . . . . . . . . . . . . . print a row space
- ##
- RowSpaceOps.Print := function( V )
- if IsBound( V.name ) then
- Print( V.name );
- elif V.generators = [] then
- Print( "RowSpace( [ ], ", V.field, ", ", V.zero, " )" );
- elif Information( V ).isStandardBase then
- Print( "RowSpace( ", Dimension( V ), ", ", V.field, " )" );
- else
- Print( "RowSpace( ", V.generators, ", ", V.field, " )" );
- fi;
- end;
-
-
- #############################################################################
- ##
- #F RowSpaceOps.Intersection( <V>, <W> ) . . intersection of two row spaces
- ##
- RowSpaceOps.Intersection := function( V, W )
- local vbase, wbase, mat, v, vdim, mlen, fpos, gens, i, j;
-
- if V.zero <> W.zero or V.field <> W.field then
- Error( "sorry, row spaces are incompatible" );
- fi;
-
- vbase := Base( V );
- if vbase = [] then return RowSpace( [], V.field, V.zero ); fi;
- wbase := Base( W );
- if wbase = [] then return RowSpace( [], V.field, V.zero ); fi;
-
- # set up the matrix for the zassenhaus algorithm
-
- mat := [];
- for v in vbase do
- v := ShallowCopy( v );
- Append( v, v );
- IsVector( v );
- Add( mat, v );
- od;
- for v in wbase do
- v := ShallowCopy( v );
- Append( v, W.zero );
- IsVector( v );
- Add( mat, v );
- od;
-
- # triangulize matrix and extract the base for the intersection space
-
- TriangulizeMat( mat );
- vdim := Length( V.zero );
- mlen := Length( mat );
- fpos := 1;
- while fpos <= mlen and Position( mat[fpos], V.field.one ) <= vdim do
- fpos := fpos + 1;
- od;
- gens := [];
- for i in [fpos..mlen] do
- v := ShallowCopy( V.zero );
- for j in [1..vdim] do
- v[j] := mat[i][vdim+j];
- od;
- Add( gens, v );
- od;
- return RowSpace( gens, V.field, V.zero );
- end;
-
-
- #############################################################################
- ##
- #F RowSpaceOps.Base( <V> ) . . . . . . . . . . . . . . . base of a row space
- ##
- RowSpaceOps.Base := function( V )
- if V.generators = [] then return [];
- else return BaseMat( V.generators );
- fi;
- end;
-
-
- #############################################################################
- ##
- #F BaseTypeRowSpace( <V> ) . . . . . . . . . determine type of attached base
- ##
- ## the following basetypes (where one type includes all previous types)
- ## are currently supported:
- ##
- ## TriangulizedBase the matrix of the base is in upper triangular form
- ## NormedBase each vector of the basis is (additionally) normed
- ## NormalizedBase all base vectors have 0 at another vectors weight
- ## StandardBaseSubset the matrix of the base just has identity matrix rows
- ## StandardBase the matrix of the base is the identity matrix
- ##
- BaseTypeRowSpace := function( V )
- local base, dim, vdim, normed, isnormalized, ismonomial, i, j;
-
- isnormalized := function ()
- local i, j;
- for i in [2..dim] do
- for j in [1..i-1] do
- if base[j][V.weights[i]] <> V.field.zero then
- return false;
- fi;
- od;
- od;
- return true;
- end;
-
- ismonomial := function ()
- local i, j;
- for i in [1..dim] do
- for j in [V.weights[i]+1..vdim] do
- if base[i][j] <> V.field.zero then return false; fi;
- od;
- od;
- return true;
- end;
-
- if not IsBound( V.base ) then Base( V ); fi;
-
- base := V.base;
- dim := Length( base );
- vdim := Length( V.zero );
- normed := true;
-
- V.weights := [];
- V.isStandardBase := false;
- V.isStandardBaseSubset := false;
- V.isNormalizedBase := false;
- V.isNormedBase := false;
- V.isTriangulizedBase := false;
-
- for i in [1..dim] do
- j := 1;
- while base[i][j] = V.field.zero do j := j + 1; od;
- V.weights[i] := j;
- if base[i][j] <> V.field.one then normed := false; fi;
- od;
-
-
- # finally determine the exact state of the base, that is, set all
- # the flags to their correct value.
-
- if IsSet( V.weights ) then
- V.isTriangulizedBase := true;
- if normed then
- V.isNormedBase := true;
- if isnormalized() then
- V.isNormalizedBase := true;
- if V.weights = [1..vdim] then
- V.isStandardBase := true;
- V.isStandardBaseSubset := true;
- elif ismonomial() then
- V.isStandardBaseSubset := true;
- fi;
- fi;
- fi;
- fi;
- end;
-
-
- #############################################################################
- ##
- #F RowSpaceOps.Information( <V> ) . . . . . information about the row space
- ##
- RowSpaceOps.Information := function( V )
- local base, dim, size, info, pows, pow, i;
-
-
- # first step: general information about the row space
-
- info := rec(
- field := V.field,
- zero := V.zero,
- isFinite := IsFinite( V )
- );
-
-
- # second step: details about the field
-
- info.isFiniteField := IsFinite( V.field );
- if info.isFiniteField then
- info.isFinitePrimeField := V.field.degree = 1;
- if info.isFinitePrimeField then
- info.integers := IntegerTable( V.field );
- fi;
- else
- info.isFinitePrimeField := false;
- fi;
-
-
- # third step: base and dimension
-
- base := Base( V );
- dim := Length( base );
-
- info.base := base;
- info.dimension := dim;
-
-
- # fourth step: enumeration information
- # only applicable if the field is finite
-
- if info.isFiniteField then
- size := Size( V.field );
- pows := [];
- pow := 1;
-
- for i in [1..dim] do
- pows[i] := pow;
- pow := pow * size;
- od;
-
- info.powers := Reversed( pows );
- info.exponents := List( base, x -> size );
- fi;
-
-
- # fifth step: coefficients information
-
- info.zeroCoefficients := List( base, x -> V.field.zero );
- IsVector( info.zeroCoefficients );
-
-
- # sixth step: general base information
-
- BaseTypeRowSpace( info );
-
- return info;
- end;
-
-
- #############################################################################
- ##
- #F RowSpaceOps.Coefficients( <V>, <v> ) . . . . coefficients of <v> in <V>
- ##
- RowSpaceOps.Coefficients := function( V, v )
- local cf, info, z, i;
-
- if not IsBound( V.information ) then
- Information( V );
- fi;
- info := V.information;
- if info.isStandardBase then
- cf := ShallowCopy( v );
- elif info.isNormalizedBase then
- cf := ShallowCopy( info.zeroCoefficients );
- for i in [1..info.dimension] do cf[i] := v[info.weights[i]]; od;
- elif info.isNormedBase then
- cf := ShallowCopy( info.zeroCoefficients );
- for i in [1..info.dimension] do
- z := v[info.weights[i]];
- if z <> V.field.zero then
- v := v - z * info.base[i];
- cf[i] := z;
- fi;
- od;
- elif info.isTriangulizedBase then
- cf := ShallowCopy( info.zeroCoefficients );
- for i in [1..info.dimension] do
- z := v[info.weights[i]];
- if z <> V.field.zero then
- z := z / info.base[i][info.weights[i]];
- v := v - z * info.base[i];
- cf[i] := z;
- fi;
- od;
- else
- Error( "sorry, can't compute coefficients for base" );
- fi;
- return cf;
- end;
-
-
- #############################################################################
- ##
- #F SiftedVector( <V>, <v> ) . . . . . . . . . . . residuum of <v> over <V>
- ##
- SiftedVector := function( V, v )
- return V.operations.SiftedVector( V, v );
- end;
-
-
- #############################################################################
- ##
- #F RowSpaceOps.SiftedVector( <V>, <v> ) . . . . . residuum of <v> over <V>
- ##
- RowSpaceOps.SiftedVector := function( V, v )
- local info, z, i;
-
- if not IsBound( V.information ) then
- Information( V );
- fi;
- info := V.information;
- if info.isStandardBase then
- v := 0 * v;
- elif info.isStandardBaseSubset then
- v := ShallowCopy( v );
- for i in [1..info.dimension] do
- v[info.weights[i]] := V.field.zero;
- od;
- elif info.isNormedBase then
- for i in [1..info.dimension] do
- z := v[info.weights[i]];
- if z <> V.field.zero then
- v := v - z * info.base[i];
- fi;
- od;
- elif info.isTriangulizedBase then
- for i in [1..info.dimension] do
- z := v[info.weights[i]];
- if z <> V.field.zero then
- v := v - z / info.base[i][info.weights[i]] * info.base[i];
- fi;
- od;
- else
- Error( "sorry, can't compute residuum for base" );
- fi;
- return v;
- end;
-
-
- #############################################################################
- ##
- #F RowSpaceOps.Enumeration( <V> ) . . . enumeration for the elements of <V>
- ##
- RowSpaceOps.Enumeration := function( V )
- local enum, e;
-
- if not IsFinite( V ) then
- Error( "sorry, row space is infinite" );
- fi;
-
- e := ShallowCopy( Information( V ) );
-
- e.number := function ( enum, v )
- local num, z, i;
-
- if enum.base = [] then
- return 1;
- fi;
-
- if enum.isFinitePrimeField then
- if enum.isStandardBase then
- #T num := 1;
- #T for i in [1..enum.dimension] do
- #T z := LogVecFFE( v, i );
- #T if z <> false then
- #T num := num + enum.powers[i] * enum.integers[z+1];
- #T fi;
- #T od;
- num := NumberVecFFE( v, enum.powers, enum.integers );
- elif enum.isNormalizedBase then
- num := 1;
- for i in [1..enum.dimension] do
- z := LogVecFFE( v, enum.weights[i] );
- if z <> false then
- num := num + enum.powers[i] * enum.integers[z+1];
- fi;
- od;
- elif enum.isNormedBase then
- num := 1;
- for i in [1..enum.dimension] do
- z := LogVecFFE( v, enum.weights[i] );
- if z <> false then
- num := num + enum.powers[i] * enum.integers[z+1];
- v := v - z * enum.base[i];
- fi;
- od;
- elif enum.isTriangulizedBase then
- num := 1;
- for i in [1..enum.dimension] do
- z := v[enum.weights[i]];
- if z <> V.field.zero then
- z := z / enum.base[i][enum.weights[i]];
- num := num + enum.powers[i] * enum.integers[LogFFE(z)+1];
- v := v - z * enum.base[i];
- fi;
- od;
- else
- Error( "sorry, can't compute number of element for base" );
- fi;
- else
- if enum.isStandardBase then
- num := 1;
- for i in [1..enum.dimension] do
- z := LogVecFFE( v, i );
- if z <> false then
- num := num + enum.powers[i] * (z+1);
- fi;
- od;
- elif enum.isNormalizedBase then
- num := 1;
- for i in [1..enum.dimension] do
- z := LogVecFFE( v, enum.weights[i] );
- if z <> false then
- num := num + enum.powers[i] * (z+1);
- fi;
- od;
- elif enum.isNormedBase then
- num := 1;
- for i in [1..enum.dimension] do
- z := v[enum.weights[i]];
- if z <> V.field.zero then
- num := num + enum.powers[i] * (LogFFE( z )+1);
- v := v - z * enum.base[i];
- fi;
- od;
- elif enum.isTriangulizedBase then
- num := 1;
- for i in [1..enum.dimension] do
- z := v[enum.weights[i]];
- if z <> V.field.zero then
- z := z / enum.base[i][enum.weights[i]];
- num := num + enum.powers[i] * (LogFFE( z )+1);
- v := v - z * enum.base[i];
- fi;
- od;
- else
- Error( "sorry, can't compute number of element for base" );
- fi;
- fi;
- return num;
- end;
-
- e.element := function( enum, num )
- local cf, v, i;
-
- if enum.base = [] then
- return enum.zero;
- fi;
-
- if enum.isFinitePrimeField then
- if enum.isStandardBase then
- v := MakeVecFFE( CoefficientsInt( enum.exponents, num-1 ), enum.field.one );
- elif enum.isStandardBaseSubset then
- cf := MakeVecFFE( CoefficientsInt( enum.exponents, num-1 ), enum.field.one );
- v := ShallowCopy( enum.zero );
- for i in [1..enum.dimension] do
- v[enum.weights[i]] := cf[i];
- od;
- else
- v := CoefficientsInt( enum.exponents, num-1 ) * enum.base;
- fi;
- else
- if enum.isStandardBase then
- cf := CoefficientsInt( enum.exponents, num-1 );
- v := ShallowCopy( enum.zero );
- for i in [1..enum.dimension] do
- if cf[i] <> V.field.zero then
- v[i] := enum.field.root ^ cf[i];
- fi;
- od;
- elif enum.isStandardBaseSubset then
- cf := CoefficientsInt( enum.exponents, num-1 );
- v := ShallowCopy( enum.zero );
- for i in [1..enum.dimension] do
- if cf[i] <> V.field.zero then
- v[enum.weights[i]] := enum.field.root ^ cf[i];
- fi;
- od;
- else
- cf := CoefficientsInt( enum.exponents, num-1 );
- v := enum.zero;
- for i in [1..enum.dimension] do
- if cf[i] <> V.field.zero then
- v := v + enum.field.root ^ cf[i] * enum.base[i];
- fi;
- od;
- fi;
- fi;
- return v;
- end;
-
- return e;
- end;
-
-
- #############################################################################
- ##
- #F RowSpaceOps.\mod( <V>, <W> ) . . . . . . . . . . . construct a modspace
- ##
- ## The infix operator 'mod' for row spaces will create a faked factorspace of
- ## the given row spaces. This new structure is basically there to calculate
- ## coefficients of vectors in the corresponding factorspace.
- ##
- RowSpaceOps.\mod := function( V, W )
- local vbase, wbase, base, fac, i;
-
- # a base for the Modspace is the canonical one
-
- vbase := Base( V );
- wbase := Base( W );
-
- if vbase = [] or wbase = [] then return ShallowCopy( V ); fi;
-
- base := BaseMat( List( vbase, x -> SiftedVector( W, x ) ) );
-
- fac := rec(
-
- # fields that are the same with row spaces
-
- generators := base,
- base := base,
- field := V.field,
- zero := V.zero,
-
- # special Modspace fields
-
- parentspace := V,
- subspace := W,
- parentInfo := Information( V ),
- subInfo := Information( W ),
- mergedSpace := rec(),
-
- # flags and operations
-
- isDomain := true,
- isModspace := true,
- isFinite := IsFinite( V.field ) or base = [],
- isComputedBase := V.isComputedBase,
- operations := ModspaceOps
- );
-
- if fac.parentInfo.isTriangulizedBase
- and fac.subInfo.isTriangulizedBase then
- base := ShallowCopy( vbase );
- for i in [1..fac.subInfo.dimension] do
- base[Position(
- fac.parentInfo.weights,
- fac.subInfo.weights[i] )] := wbase[i];
- od;
- fac.mergedSpace := RowSpace( base, V.field, V.zero );
- AddBase( fac.mergedSpace, base );
- else
- Error( "sorry, bases have to be triangulized" );
- fi;
-
- return fac;
- end;
-
-
- #############################################################################
- ##
- #V ModspaceOps . . . . . . . . . . . . . . . operations record for modspaces
- ##
- ModspaceOps := ShallowCopy( RowSpaceOps );
-
-
- #############################################################################
- ##
- #F ModspaceOps.Base( <V> ) . . . . . . . . . . . . . . . base of a modspace
- ##
- ModspaceOps.Base := function( V )
- return V.generators;
- end;
-
-
- #############################################################################
- ##
- #F ModspaceOps.Coefficients( <V>, <v> ) . . . . coefficients of <v> in <V>
- ##
- ModspaceOps.Coefficients := function( V, v )
- local info, minfo, cf, z, i, j;
-
- if V.subInfo.isStandardBase then
- return [];
- else
- if not IsBound( V.information ) then
- Information( V );
- fi;
- if not IsBound( V.mergedSpace.information ) then
- Information( V.mergedSpace );
- fi;
- info := V.information;
- minfo := V.mergedSpace.information;
-
- if minfo.isNormalizedBase then
- cf := ShallowCopy( info.zeroCoefficients );
- for i in [1..info.dimension] do
- cf[i] := v[info.weights[i]];
- od;
- elif minfo.isNormedBase then
- cf := ShallowCopy( info.zeroCoefficients );
- j := 1;
- for i in [1..minfo.dimension] do
- z := v[minfo.weights[i]];
- if z <> V.field.zero then
- v := v - z * minfo.base[i];
- if minfo.weights[i] in info.weights then
- cf[j] := z;
- j := j + 1;
- fi;
- elif minfo.weights[i] in info.weights then
- j := j + 1;
- fi;
- od;
- elif minfo.isTriangulizedBase then
- cf := ShallowCopy( info.zeroCoefficients );
- j := 1;
- for i in [1..minfo.dimension] do
- z := v[minfo.weights[i]];
- if z <> V.field.zero then
- z := z / minfo.base[i][minfo.weights[i]];
- v := v - z * minfo.base[i];
- if minfo.weights[i] in info.weights then
- cf[j] := z;
- j := j + 1;
- fi;
- elif minfo.weights[i] in info.weights then
- j := j + 1;
- fi;
- od;
- fi;
- fi;
- return cf;
- end;
-
-
- #############################################################################
- ##
- #F ModspaceOps.Print( <V> ) . . . . . . . . . . . . . . print of a modspace
- ##
- ModspaceOps.Print := function( V )
- Print( V.parentspace, " mod ", V.subspace );
- end;
-
-
- #############################################################################
- ##
- #E Emacs . . . . . . . . . . . . . . . . . . . . . . . local emacs variables
- ##
- ## Local Variables:
- ## mode: outline
- ## outline-regexp: "#F\\|#V\\|#E"
- ## fill-column: 73
- ## fill-prefix: "## "
- ## eval: (hide-body)
- ## End:
- ##
-