#############################################################################
##
#W  gpd.gi                     Gpd Package                       Chris Wensley
#W                                                                & Emma Moore
##
##  This file contains generic methods for groupoids
##
#H  @(#)$Id: gpd.gi,v 1.01 2006/05/17 gap Exp $
##
#Y  Copyright (C) 1999,2001 University of St. Andrews, North Haugh,
#Y                          St. Andrews, Fife KY16 9SS, Scotland
##
Revision.("gpd/gap/gpd_gi") := 
   "@(#)$Id: gpd.gi,v 1.01 2006/05/17 gap Exp $";

#############################################################################
##  set default value for InfoClass
SetInfoLevel( InfoGpd, 1 );

#############################################################################
##  Standard error messages

GROUPOID_CONSTRUCTORS := Concatenation( 
    "The standard operations which construct a groupoid are:\n",
    "1.  ConnectedGroupoid( list of objects, object group );\n",
    "2.  GroupAsGroupoid( single object, group );\n",
    "3.  GroupoidByUnion( list of groupoids );\n",
    "4.  Groupoid( one of the previous parameter options );" );

SUBGROUPOID_CONSTRUCTORS := Concatenation( 
    "The standard operations which construct a subgroupoid are:\n", 
    "1.  SubgroupoidByComponents( groupoid, list of [imobs,hom] pairs );\n",
    "2.  FullSubgroupoid( groupoid, list of objects );\n", 
    "3.  MaximalDiscreteSubgroupoid( groupoid );\n", 
    "4.  DiscreteSubgroupoid( groupoid, list of obs, list of subgps );\n",
    "5.  FullIdentitySubgrupoid( groupoid );\n", 
    "6.  DiscreteIdentitySubgroupoid( groupoid );\n", 
    "7.  Subgroupoid( one of the previous parameter options );" );

#############################################################################
##
#M  ConnectedGroupoidNC                                            
#M  ConnectedGroupoid                                            
##
InstallMethod( ConnectedGroupoidNC, "generic method for a connected groupoid",
    true, [ IsHomogeneousList, IsGroup ], 0,
function( obs, gp )

    local  fam, filter, gpd;

    fam := FamilyObj( [ obs, gp ] );
    filter := IsConnectedGroupoidRep;
    gpd := Objectify( NewType( fam, filter ), rec () );
    gpd!.objects := obs;
    gpd!.group := gp;
    SetIsConnectedGroupoid( gpd, true );
    if ( Length( obs ) = 1 ) then 
        SetIsDiscreteGroupoid( gpd, true );
    fi;
    return gpd; 
end );

InstallMethod( ConnectedGroupoid, "generic method for a connected groupoid",
    true, [ IsHomogeneousList, IsGroup ], 0,
function( obs, gp )
    if not IsDuplicateFree( obs ) then
        Error( "objects must be distinct" );
    fi;  
    Sort( obs );  
    return ConnectedGroupoidNC( obs, gp );
end );

#############################################################################
##
#M  ComponentsOfGroupoid
##
InstallMethod( ComponentsOfGroupoid, "for a connected groupoid",
    true, [ IsConnectedGroupoid ], 0,
function( G )
    return [ G ];
end );

#############################################################################
##
#M  ObjectsOfGroupoid
##
InstallMethod( ObjectsOfGroupoid, "for a connected groupoid",
    true, [ IsGroupoid ], 0,
function( G )

    local  obs; 

    if ( HasIsConnectedGroupoid( G ) and IsConnectedGroupoid( G ) ) then
        return G!.objects;
    else
        obs := Concatenation( 
            List( ComponentsOfGroupoid(G), c -> c!.objects ) );
        if not IsDuplicateFree( obs ) then
            Error( "same object in more than one component" );
        fi;
        Sort( obs );
        return obs;
   fi;
end );

############################################################################# 
## 
#M  ComponentOfObject
## 
InstallMethod( ComponentOfObject, "generic method for groupoid and element",
    true, [ IsGroupoid, IsObject ], 0,
function( G, obj )

    local  comp, c, objc;

    if IsConnectedGroupoid( G ) then
        if not ( obj in G!.objects ) then
            Error( "obj not an object of G" );
        else
            return G;
        fi;
    elif not ( obj in ObjectsOfGroupoid(G) ) then
        Info( InfoGpd, 1, "obj not an object of G" );
        return fail;
    fi;
    comp := ComponentsOfGroupoid( G );
    for c in comp do
        objc := c!.objects;
        if ( obj in objc ) then
            return c;
        fi;
    od;
    Info( InfoGpd, 1, "it appears that obj is not an object in groupoid G" );
    return fail;
end );
  
############################################################################# 
## 
#M  ComponentNrOfObject
## 
InstallMethod( ComponentNrOfObject, "generic method for groupoid and object",
    true, [ IsGroupoid, IsObject ], 0,
function( G, obj )

    local  comp, i, objc, nc;
    comp := ComponentsOfGroupoid( G );
    for i in [1..Length( comp )] do
        objc := comp[i]!.objects;
        if ( obj in objc ) then
            return i;
        fi;
    od;
    Info( InfoGpd, 1, "it appears that obj is not an object in groupoid G" );
    return fail;
end );
  
#############################################################################
##
#M  IsConnectedGroupoid
##
InstallMethod( IsConnectedGroupoid, "for a groupoid", true,
    [ IsGroupoid ], 0,
function( gpd )
    return ( Length( ObjectsOfGroupoid( gpd ) ) = 1 );
end );

#############################################################################
##
#M  IsPermGroupoid
#M  IsFpGroupoid
#M  IsPcGroupoid
##
InstallMethod( IsPermGroupoid, "for a groupoid", true,
    [ IsGroupoid ], 0,
function( gpd )
    return ForAll( ComponentsOfGroupoid( gpd ), c -> IsPermGroup( c!.group ) );
end );

InstallMethod( IsFpGroupoid, "for a groupoid", true,
    [ IsGroupoid ], 0,
function( gpd )
    return ForAll( ComponentsOfGroupoid( gpd ), c -> IsFpGroup( c!.group ) );
end );

InstallMethod( IsPcGroupoid, "for a groupoid", true,
    [ IsGroupoid ], 0,
function( gpd )
    return ForAll( ComponentsOfGroupoid( gpd ), c -> IsPcGroup( c!.group ) );
end );

#############################################################################
##
#M  GroupoidByUnionNC 
#M  GroupoidByUnion
##
InstallMethod( GroupoidByUnionNC, "generic method for a groupoid",
    true, [ IsHomogeneousList ], 0,
function( comp1 )

    local  len, comp2, L, fam, filter, gpd, i, obs;

    ## order components by first object
    len := Length( comp1 );
    obs := List( comp1, g -> g!.objects[1] );
    L := [1..len];
    SortParallel( obs, L );
    if ( L <> [1..len] ) then
        Info( InfoGpd, 2, "ordering components by first object" );
    fi;
    comp2 := List( L, i -> comp1[i] );
    fam := FamilyObj( [ comp2 ] );
    filter := IsGroupoidRep;
    gpd := Objectify( NewType( fam, filter ), rec () );
    SetComponentsOfGroupoid( gpd, comp2 );
    obs := ObjectsOfGroupoid( gpd );
    return gpd; 
end );

InstallMethod( GroupoidByUnion, "generic method for a groupoid",
    true, [ IsList ], 0,
function( parts )

    local  npa, part, comps, nco, obs, obp, i, gi, nobj;

    npa := Length( parts );
    comps := [ ];
    obs := [ ];
    for part in parts do
        if not IsGroupoid( part ) then
            Info( InfoGpd, 1, GROUPOID_CONSTRUCTORS );
            return fail;
        fi;
        obp := ObjectsOfGroupoid( part );
        if ( Intersection( obs, obp ) <> [ ] ) then
            Info( InfoGpd, 1, "components must have disjoint object sets" );
            return fail;
        fi;
        if ( HasIsConnectedGroupoid(part) and IsConnectedGroupoid(part) ) then
            Add( comps, part );
        else
            Append( comps, ComponentsOfGroupoid( part ) );
        fi;
        obs := Union( obs, obp );
    od;
    return GroupoidByUnionNC( comps );
end );

#############################################################################
##
#M  GroupAsGroupoid
##
InstallMethod( GroupAsGroupoid, "generic method for a group",
    true, [ IsObject, IsGroup ], 0,
function( o, gp )
    if ( IsList( o ) and ( Length(o) = 1 ) ) then
        Info( InfoGpd, 2, "object given as a singleton list" );
        return ConnectedGroupoidNC( o, gp );
    else
        return ConnectedGroupoidNC( [ o ], gp );
    fi;
end );

#############################################################################
##
#F  Groupoid( <comp> )              groupoid as list of components
#F  Groupoid( <gp>, <obj> )         group as groupoid 
#F  Groupoid( <gp>, <obs> )         connected groupoid 
##
InstallGlobalFunction( Groupoid, function( arg )

    local  nargs, id, rays;

    nargs := Length( arg );
    # list of components
    if ( ( nargs = 1 ) and IsList( arg[1] ) 
         and  ForAll( arg[1], G -> IsGroupoid(G) ) ) then
        Info( InfoGpd, 2, "ByUnion" );
        return GroupoidByUnion( arg[1] );
    # group * tree groupoid
    elif ( ( nargs = 2 ) and IsList( arg[1] ) and IsGroup( arg[2] ) ) then
        Info( InfoGpd, 2, "objects plus group" );
        return ConnectedGroupoid( arg[1], arg[2] );
    # one-object groupoid
    elif ( ( nargs = 2 ) and IsObject( arg[1] ) and IsGroup( arg[2] ) ) then
        Info( InfoGpd, 2, "AsGroup" );
        return GroupAsGroupoid( arg[1], arg[2] );
    else
        Info( InfoGpd, 1, GROUPOID_CONSTRUCTORS );
        return fail;
    fi;
end );

#############################################################################
##
#M  PrintObj
##
InstallMethod( PrintObj, "for a groupoid", true, [ IsGroupoid ], 0,
function ( gpd )
    local comp, c, i;

    if IsPermGroupoid( gpd ) then
        Print( "Perm " );
    elif IsFpGroupoid( gpd ) then
        Print( "Fp " );
    elif IsPcGroupoid( gpd ) then
        Print( "Pc " );
    fi;
    if IsConnectedGroupoid( gpd ) then
        Print( "Connected Groupoid:\n< " ); 
        Print( gpd!.objects, ", ", gpd!.group, " >" );
    else
        comp := ComponentsOfGroupoid( gpd );
        Print( "Groupoid with components:" );
        for i in [1..Length(comp)] do
            c := comp[i];
            Print( "\n< ", c!.objects, ", ", c!.group, " >" );
        od;
    fi;
end );

#############################################################################
##
#M  ViewObj
##
InstallMethod( ViewObj, "for a groupoid", true, [ IsGroupoid ], 0,
function ( gpd )
    local comp, c, i;

    if IsPermGroupoid( gpd ) then
        Print( "Perm " );
    elif IsFpGroupoid( gpd ) then
        Print( "Fp " );
    elif IsPcGroupoid( gpd ) then
        Print( "Pc " );
    fi;
    if IsConnectedGroupoid( gpd ) then
        Print( "Connected Groupoid:\n< " ); 
        Print( gpd!.objects, ", ", gpd!.group, " >" );
    else
        comp := ComponentsOfGroupoid( gpd );
        Print( "Groupoid with components:" );
        for i in [1..Length(comp)] do
            c := comp[i];
            Print( "\n< ", c!.objects, ", ", c!.group, " >" );
        od;
    fi;
end );

##############################################################################
##
#M  Display( <gpd> ) . . . . . . . . . . . . . . . . . . .  display a groupoid
##
InstallMethod( Display, "for a groupoid", [ IsGroupoid ],
function( gpd )
    
    local comp, c, i, gp, len;

    if IsPermGroupoid( gpd ) then
        Print( "Perm " );
    elif IsFpGroupoid( gpd ) then
        Print( "Fp " );
    elif IsPcGroupoid( gpd ) then
        Print( "Pc " );
    fi;
    if IsConnectedGroupoid( gpd ) then
        Print( "Connected Groupoid: " );
        if HasName( gpd ) then
            Print( gpd );
        fi;
        Print( "\n" ); 
        Print( "  objects: ", gpd!.objects, "\n" );
        gp := gpd!.group;
        Print( "    group: " );
        if HasName( gp ) then
            Print( gp, " = <", GeneratorsOfGroup( gp ), ">\n" );
        else
            Print( gp, "\n" );
        fi;
    else
        comp := ComponentsOfGroupoid( gpd );
        len := Length( comp );
        Print( "Groupoid with ", len, " components:\n" );
        for i in [1..len] do
            c := comp[i];
            Print( "< objects: ", c!.objects, "\n" );
            gp := c!.group;
            Print( "    group: " );
            if HasName( gp ) then
                Print( gp, " = <", GeneratorsOfGroup( gp ), "> >\n" );
            else
                Print( gp, " >\n" );
            fi;
        od;
    fi;
end );

#############################################################################
##
#M  \=( <g1>, <g2> )  . . . . . . . . . . . . test if two groupoids are equal
##
InstallMethod( \=, "for a connected groupoid", IsIdenticalObj,
    [ IsConnectedGroupoid, IsGroupoid ], 
function ( g1, g2 )
    if not IsConnectedGroupoid( g2 ) then
        return false;
    fi;
    return( ( g1!.objects = g2!.objects ) and ( g1!.group = g2!.group ) );
end );

InstallMethod( \=, "for a groupoid", true, [ IsGroupoid, IsGroupoid ], 
function ( g1, g2 )
    local  c1, c2, len, obj, i, j;
    c1 := ComponentsOfGroupoid( g1 );
    c2 := ComponentsOfGroupoid( g2 );
    len := Length( c1 );
    if ( ( len <> Length(c2) ) or ( ObjectsOfGroupoid(g1) <> ObjectsOfGroupoid(g2) ) ) then
        return false;
    fi;
    for i in [1..len] do
        obj := c1[i]!.objects[1];
        j := ComponentNrOfObject( g2, obj );
        if ( c1[i] <> c2[j] ) then
            return false;
        fi;
    od;
    return true;
end );

#############################################################################
##
#M  Size 
##
InstallOtherMethod( Size, "generic method for a groupoid", true,
    [ IsGroupoid ], 0,
function( gpd )

    local  c, s, sg;
    s := 0;
    for c in ComponentsOfGroupoid( gpd ) do
        if ( s <> infinity ) then
            sg := Size( c!.group );
            if ( sg = infinity ) then
                s := infinity;
            else
                s := s + Length( c!.objects )^2 * sg;
            fi;
        fi;
    od;
    return s;
end );

## ======================================================================= ##
##                           Groupoid Elements                             ##
## ======================================================================= ##

#############################################################################
##
#M  GroupoidElementNC 
#M  GroupoidElement 
##
InstallMethod( GroupoidElementNC, "generic method for a groupoid element",
    true, [ IsMultiplicativeElement, IsObject, IsObject ], 0,
function( g, i, j )

    local  fam, filter, e;
     
    fam := FamilyObj( [ g, i, j ] );
    filter := IsGroupoidElementRep;
    e := Objectify( NewType( fam, filter ), [ g, i, j ] );
    SetIsGroupoidElement( e, true );
    return e;
end );

InstallMethod( GroupoidElement, "generic method for a groupoid element",
    true, [ IsGroupoid, IsMultiplicativeElement, IsObject, IsObject ], 0,
function( gpd, g, i, j )

    local  comp;

    comp := ComponentOfObject( gpd, i );
    if ( ( comp = fail ) or not ( j in comp!.objects ) 
                         or not ( g in comp!.group ) ) then 
        Info( InfoGpd, 1, "one of g,i,j not in the groupoid?" );
        Info( InfoGpd, 1, 
            "Usage: GroupoidElement( gpd, gp elt, tail, head );" );
        return fail;
    else
        return GroupoidElementNC( g, i, j );
    fi;
end );

#############################################################################
##
#M  GroupElement
#M  Tail
#M  Head
##
InstallMethod( GroupElement,
    "generic method for a groupoid element",
    true, [ IsGroupoidElementRep ], 0,
function( e )
    return e![1];
end );

InstallMethod( Tail,
    "generic method for a groupoid element",
    true, [ IsGroupoidElementRep ], 0,
function( e )
    return e![2];
end );

InstallMethod( Head,
    "generic method for a groupoid element",
    true, [ IsGroupoidElementRep ], 0,
function( e )
    return e![3];
end );

#############################################################################
##
#M  PrintObj
#M  ViewObj
##
InstallMethod( PrintObj, "for a groupoid element",
    [ IsGroupoidElementRep ],
function ( e )
    Print( "[", e![1], " : ", e![2], " -> ", e![3], "]" );
end );

InstallMethod( ViewObj, "for a groupoid element",
    [ IsGroupoidElementRep ],
function ( e )
    Print( "[", e![1], " : ", e![2], " -> ", e![3], "]" );
end );

#############################################################################
##
#M  \=( <e1>, <e2> )  . . . . . . . . test if two groupoid elements are equal
##
InstallMethod( \=, "for two groupoid elements", IsIdenticalObj,
    [ IsGroupoidElement, IsGroupoidElement ], 0,
function( e1, e2 )
    return ForAll( [1..3], i -> ( e1![i] = e2![i] ) );
end );

#############################################################################
##
#M  \*( <e1>, <e2> )  . . . . . . . . . . .  product of two groupoid elements
#M  \^( <e>, <n> )  . . . . . . . . . . . . . . . power of a groupoid element
#M  \^( <e1>, <e2> )  . . . . .  conjugate of one groupoid element by another
##
InstallMethod( \*, "for two groupoid elements", IsIdenticalObj,
    [ IsGroupoidElement, IsGroupoidElement ], 0,
function( e1, e2 )
    if ( e1![3] <> e2![2] ) then
        return fail;
    fi;
    return GroupoidElementNC( e1![1]*e2![1], e1![2], e2![3] );
end );

InstallMethod( \^, "for a groupoid element and an integer", true,
    [ IsGroupoidElement, IsInt ], 0,
function( e, n )
    local  g, t, h;

    t := e![2];
    h := e![3];
    if ( n = 1 ) then
        return e;
    elif ( n = -1 ) then
        return GroupoidElementNC( e![1]^-1, h, t );
    elif ( t <> h ) then
        return fail;
    fi;
    g := e![1]^n;
    return GroupoidElementNC( g, t, h );
end );

InstallMethod( \^, "for two groupoid elements", IsIdenticalObj,
  [ IsGroupoidElement, IsGroupoidElement ], 0, 
function( e1, e2 )
    if not ( ( e1![2] = e1![3] ) and ( e1![3] = e2![2] ) ) then
        return fail;
    fi;
    return GroupoidElementNC( e1![1]^e2![1], e2![3], e2![3] );
end );

#############################################################################
##
#M  Order( <e> )  . . . . . . . . . . . . . . . . . . . of a groupoid element
##
InstallOtherMethod( Order, "for a groupoid element", true,
    [ IsGroupoidElement ], 0,
function( e )
    local  ord;
    if not ( e![2] = e![3] ) then
        Error( "tail of e <> head of e" );
    fi;
    return Order( e![1] );
end );

#############################################################################
##
#M  <e> in <G> 
##
InstallMethod( \in, "for a groupoid element and a connected groupoid",
    true, [ IsGroupoidElement, IsConnectedGroupoid ], 0,
function( e, gpd )
    local  obs;
    obs := gpd!.objects;
    return ( (e![1] in gpd!.group) and (e![2] in obs) and (e![3] in obs) );
end );

InstallMethod( \in, "for a groupoid element and a groupoid",
    true, [ IsGroupoidElement, IsGroupoid ], 0,
function( e, gpd )
    local  comp;
    comp := ComponentOfObject( gpd, e![2] );
    if ( comp = fail ) then
        return false;
    else
        return ( e in comp );
    fi;
end );

############################################################################# 
## 
#M  IdentityElement
## 
InstallMethod( IdentityElement, "for a connected groupoid and object",
    true, [ IsConnectedGroupoid, IsObject ], 0,
function( gpd, obj )
    return GroupoidElementNC( One(gpd!.group), obj, obj );
end );

InstallMethod( IdentityElement, "generic method for groupoid and object",
    true, [ IsGroupoid, IsObject ], 0,
function( gpd, obj )
    local  comp;
    comp := ComponentOfObject( gpd, obj );
    return GroupoidElementNC( One(comp!.group), obj, obj );
end );

#############################################################################
##
#M  PrintObj
##
InstallMethod( PrintObj, "for a subset of elements of a groupoid", true, 
    [ IsHomsetCosets ], 0,
function ( elts )
    
    if ( elts![5] = "s" ) then 
        Print( "<star at ", elts![2], " with group ", elts![1], ">" );
    elif ( elts![5] = "c" ) then 
        Print( "<costar at ", elts![3], " with group ", elts![1], ">" );
    elif ( elts![5] = "h" ) then 
        Print( "<homset ", elts![2][1], " -> ", elts![3][1],
               " with group ", elts![1], ">" );
    elif ( elts![5] = "u" ) then 
        Print( "<union of all homsets with group ", elts![1], ">" );
    elif ( elts![5] = "r" ) then 
        Print( "RightCoset(", ActingDomain(elts![1]), ",", elts![4], ")" );
    elif ( elts![5] = "l" ) then 
        Print( "LeftCoset(", elts![4], ",", elts![1], ">" );
    elif ( elts![5] = "d" ) then 
        Print( "<", elts![1], ".", elts![4], ">" );
    else
        Print( "<object>");
    fi;
end );

#############################################################################
##
#M  Iterator( <homsets> ) . . . . iterator for star, costar, homset or coset
##
InstallMethod( Iterator, "for a subset of a connected groupoid", 
    [ IsHomsetCosets ], 
function( elts )
    return IteratorByFunctions( rec( 
        IsDoneIterator := function( iter )
            return ( IsDoneIterator( iter!.groupIterator ) 
                     and ( iter!.tpos = iter!.tlen )
                     and ( iter!.hpos = iter!.hlen ) );
            end, 
        NextIterator := function( iter )
            if ( iter!.tpos = 0 ) then
                iter!.gpelt := NextIterator( iter!.groupIterator );
                iter!.tpos := 1;
                iter!.hpos := 1;
           elif ((iter!.tpos = iter!.tlen) and (iter!.hpos = iter!.hlen)) then 
                iter!.gpelt := NextIterator( iter!.groupIterator );
                iter!.tpos := 1;
                iter!.hpos := 1;
            elif ( iter!.hpos = iter!.hlen ) then
                iter!.hpos := 1;
                iter!.tpos := iter!.tpos + 1;
            else 
                iter!.hpos := iter!.hpos + 1;
            fi;
            if ( elts![5] in [ "s", "c", "h", "u", "r", "d" ] ) then 
                return GroupoidElementNC( iter!.gpelt, iter!.tobs[iter!.tpos],
                                                      iter!.hobs[iter!.hpos] );
#           elif ( elts![5] = "r" ) then ## right coset
#              return GroupoidElementNC( iter!.gpelt, iter!.tobs[iter!.tpos], 
#                         iter!.hobs[iter!.hpos] ) * elts![4]; 
### LeftCoset not implemented for groups
            elif ( elts![5] = "l" ) then ## left coset
                return elts![4] * GroupoidElementNC( iter!.gpelt, 
                         iter!.tobs[iter!.tpos], iter!.hobs[iter!.hpos] ); 
#           elif ( elts![5] = "d" ) then ## double coset
#               return GroupoidElementNC( 
#                          Image( Projection( elts![1], 1 ), iter!.gpelt ), 
#                            iter!.tobs[iter!.tpos], elts![4]![2] ) 
#                      * elts![4] * 
#                      GroupoidElementNC( 
#                          Image( Projection( elts![1], 2 ), iter!.gpelt ), 
#                            elts![4]![3], iter!.hobs[iter!.hpos] ); 
            fi;
            end,
        ShallowCopy := iter -> 
            rec( groupIterator := ShallowCopy( iter!.groupIterator ),
                 gpelt := iter!.gpelt,
                 tobs := iter!.tobs,
                 tlen := iter!.tlen,
                 hobs := iter!.hobs,
                 hlen := iter!.hlen,
                 tpos := iter!.tpos,
                 hpos := iter!.hpos ),
        groupIterator := Iterator( elts![1] ),
        gpelt := 0,
        tobs := elts![2],
        tlen := Length( elts![2] ),
        hobs := elts![3],
        hlen := Length( elts![3] ),
        tpos := 0,
        hpos := 0 ) );
end );

#############################################################################
##
#M  UnionHomsets
##
InstallMethod( UnionHomsets, "for a connected groupoid", true, 
    [ IsConnectedGroupoid ], 0,
function( gpd )

    local  g, obs, fam, filter, e;
    
    g := gpd!.group;
    obs := gpd!.objects;
    fam := FamilyObj( [ g, obs, obs, 0, "u" ] );
    filter := IsHomsetCosetsRep;
    e := Objectify( NewType( fam, filter ), [ g, obs, obs, 0, "u" ] );
    SetIsHomsetCosets( e, true );
    return e;
end );

#############################################################################
##
#M  ObjectStarNC 
#M  ObjectStar 
##
InstallMethod( ObjectStarNC, "for a connected groupoid and an object",
    true, [ IsConnectedGroupoid, IsObject ], 0,
function( gpd, obj )

    local  g, obs, fam, filter, e;
    
    g := gpd!.group;
    obs := gpd!.objects;
    fam := FamilyObj( [ g, [obj], obs, "s" ] );
    filter := IsHomsetCosetsRep;
    e := Objectify( NewType( fam, filter ), [ g, [obj], obs, 0, "s" ] );
    SetIsHomsetCosets( e, true );
    return e;
end );

InstallMethod( ObjectStar, "generic method for a groupoid and an object",
    true, [ IsGroupoid, IsObject ], 0,
function( gpd, obj )

    local  comp;
    if ( IsConnectedGroupoid(gpd) and ( obj in ObjectsOfGroupoid(gpd) ) ) then
        return ObjectStarNC( gpd, obj );
    else
        comp := ComponentOfObject( gpd, obj );
        if ( comp = fail ) then
            Info( InfoGpd, 1, "obj not an object in gpd" );
            return fail;
        else
            return ObjectStarNC( comp, obj );
        fi;
    fi;
end );

#############################################################################
##
#M  ObjectCostarNC 
#M  ObjectCostar 
##
InstallMethod( ObjectCostarNC, "for a connected groupoid and an object",
    true, [ IsConnectedGroupoid, IsObject ], 0,
function( gpd, obj )

    local  g, obs, fam, filter, e;
    
    g := gpd!.group;
    obs := gpd!.objects;
    fam := FamilyObj( [ g, obs, [obj], "c" ] );
    filter := IsHomsetCosetsRep;
    e := Objectify( NewType( fam, filter ), [ g, obs, [obj], 0, "c" ] );
    SetIsHomsetCosets( e, true );
    return e;
end );

InstallMethod( ObjectCostar, "generic method for a groupoid and an object",
    true, [ IsGroupoid, IsObject ], 0,
function( gpd, obj )

    local  comp;
    if ( IsConnectedGroupoid(gpd) and ( obj in ObjectsOfGroupoid(gpd) ) ) then
        return ObjectCostarNC( gpd, obj );
    else
        comp := ComponentOfObject( gpd, obj );
        if ( comp = fail ) then
            Info( InfoGpd, 1, "obj not an object in gpd" );
            return fail;
        else
            return ObjectCostarNC( comp, obj );
        fi;
    fi;
end );

#############################################################################
##
#M  HomsetNC 
#M  Homset
##
InstallMethod( HomsetNC, "for a connected groupoid and two objects",
    true, [ IsConnectedGroupoid, IsObject, IsObject ], 0,
function( gpd, o1, o2 )

    local  g, obs, fam, filter, e;
    
    g := gpd!.group;
    fam := FamilyObj( [ g, [o1], [o2], "h" ] );
    filter := IsHomsetCosetsRep;
    e := Objectify( NewType( fam, filter ), [ g, [o1], [o2], 0, "h" ] );
    SetIsHomsetCosets( e, true );
    return e;
end );

InstallMethod( Homset, "generic method for a groupoid and two objects",
    true, [ IsGroupoid, IsObject, IsObject ], 0,
function( gpd, o1, o2 )

    local  obs, comp;

    obs := ObjectsOfGroupoid( gpd );
    if not ( ( o1 in obs ) and ( o2 in obs ) ) then
        Info( InfoGpd, 1, "o1,o2 not objects in gpd" );
        return fail;
    fi;
    if IsConnectedGroupoid( gpd ) then
        return HomsetNC( gpd, o1, o2 );
    else
        comp := ComponentOfObject( gpd, o1 );
        if not ( o2 in comp!.objects ) then
            Info( InfoGpd, 1, "o1,o2 not object in same component" );
            return fail;
        else
            return HomsetNC( comp, o1, o2 );
        fi;
    fi;
end );

#############################################################################
##
#M  ElementsOfGroupoid
##
InstallMethod( ElementsOfGroupoid, "for a connected groupoid", true,
    [ IsConnectedGroupoid ], 0,
function( gpd )

    local  iter, elts;

    elts := [ ];
    iter := Iterator( UnionHomsets( gpd ) );
    while not IsDoneIterator( iter ) do
        Add( elts, NextIterator( iter ) );
    od;
    return elts;
end );

InstallMethod( ElementsOfGroupoid, "generic method for a groupoid", true,
    [ IsGroupoid ], 0,
function( gpd )

    local  comps, c, elts;
    comps := ComponentsOfGroupoid( gpd );
    elts := [ ];
    for c in comps do
        Append( elts, ElementsOfGroupoid( c ) );
    od;
    return elts;
end );

#############################################################################
##
#M  Iterator( <gpd> ) . . . . . . . . . . . . . . . . iterator for a groupoid
##
InstallMethod( Iterator, "for a connected groupoid", [ IsConnectedGroupoid ], 
function( gpd )
    return IteratorByFunctions( rec( 
        IsDoneIterator := function( iter )
            return ( IsDoneIterator( iter!.groupIterator ) 
                     and ( iter!.tpos = iter!.len )
                     and ( iter!.hpos = iter!.len ) );
            end, 
        NextIterator := function( iter )
            if ( iter!.tpos = 0 ) then
                iter!.gpelt := NextIterator( iter!.groupIterator );
                iter!.tpos := 1;
                iter!.hpos := 1;
            elif ((iter!.tpos = iter!.len) and (iter!.hpos = iter!.len)) then 
                iter!.gpelt := NextIterator( iter!.groupIterator );
                iter!.tpos := 1;
                iter!.hpos := 1;
            elif ( iter!.hpos = iter!.len ) then
                iter!.hpos := 1;
                iter!.tpos := iter!.tpos + 1;
            else 
                iter!.hpos := iter!.hpos + 1;
            fi;
            return GroupoidElementNC( iter!.gpelt, iter!.obs[iter!.tpos],
                                                   iter!.obs[iter!.hpos] );
            end,
        ShallowCopy := iter -> 
            rec( groupIterator := ShallowCopy( iter!.groupIterator ),
                 gpelt := iter!.gpelt,
                 obs := iter!.obs,
                 len := iter!.len,
                 tpos := iter!.tpos,
                 hpos := iter!.hpos ),
        groupIterator := Iterator( gpd!.group ),
        gpelt := One( gpd!.group ),
        obs := gpd!.objects,
        len := Length( gpd!.objects ),
        tpos := 0,
        hpos := 0 ) );
end );

InstallMethod( Iterator, "generic method for a groupoid", [ IsGroupoid ], 
function( gpd )
    return IteratorByFunctions( rec( 
        IsDoneIterator := function( iter )
            return ( IsDoneIterator( iter!.groupoidIterator ) 
                     and ( iter!.cpos = iter!.len ) );
            end, 
        NextIterator := function( iter )
            if IsDoneIterator( iter!.groupoidIterator ) then 
                iter!.cpos := iter!.cpos + 1;
                iter!.groupoidIterator := 
                    Iterator( iter!.components[iter!.cpos] );
            fi;
            return NextIterator( iter!.groupoidIterator );
            end,
        ShallowCopy := iter -> 
            rec( components := iter!.components,
                 len := iter!.len,
                 groupoidIterator := ShallowCopy( iter!.groupoidIterator ),
                 cpos := iter!.cpos ),
        components := ComponentsOfGroupoid( gpd ),
        len := Length( ComponentsOfGroupoid( gpd ) ),
        groupoidIterator := Iterator( ComponentsOfGroupoid( gpd )[1] ),
        cpos := 1 ) );
end );

## ======================================================================= ##
##                                Subgroupoids                             ##
## ======================================================================= ##

#############################################################################
##
#F  Subgroupoid( <gpd>, <subgp> )        connected groupoids
#F  Subgroupoid( <gpd>, <gps>, <obs> )   discrete subgroupoid 
#F  Subgroupoid( <gpd>, <comp> )         subgroupoid as list of components
##
InstallGlobalFunction( Subgroupoid, function( arg )

    local  nargs, gpd, id, rays, gp, sub;
    nargs := Length( arg );
    gpd := arg[1];
    if not IsGroupoid( gpd ) then 
        Info( InfoGpd, 1, "arg[1] is not a groupoid" );
        return fail;
    fi;
    # by subgroup
    if ( ( nargs = 2 ) and IsConnectedGroupoid( arg[1] )
                       and IsGroup( arg[2] ) ) then
        gp := gpd!.group;
        Info( InfoGpd, 2, "connected subgroupoid" );
        sub := ConnectedGroupoid( arg[2], gpd!.objects );
        SetParentAttr( sub, gpd );
        return sub;
    fi;
    # discrete subgroupoid
    if ( ( nargs = 3 ) and IsHomogeneousList( arg[2] ) 
                       and IsHomogeneousList( arg[3] ) ) then
        Info( InfoGpd, 2, "discrete subgroupoid" );
        return DiscreteSubgroupoid( arg[1], arg[2], arg[3] );
    fi;
    # list of components
    if ( ( nargs = 2 ) and IsHomogeneousList( arg[2] ) ) then
        Info( InfoGpd, 3, "subgroupoid by components" );
        return SubgroupoidByComponents( arg[1], arg[2] );
    fi;
    Info( InfoGpd, 1, SUBGROUPOID_CONSTRUCTORS );
    return fail;
end );

#############################################################################
##
#F  IsSubgroupoid( <G>, <U> )
##
InstallGlobalFunction( IsSubgroupoid,
    function( G, U )

    local  objG, objU, compU, obj, C;

    if not ( IsGroupoid(G) and IsGroupoid(U) ) then
        return false;
    fi;
    if ( HasParentAttr( U ) and ( ParentAttr( U ) = G ) ) then 
        return true;
    fi;
    if not IsSubset( ObjectsOfGroupoid(G), ObjectsOfGroupoid(U) ) then
        return false;
    fi;
    if ( IsConnectedGroupoid(G) and IsConnectedGroupoid(U) ) then
        objG := G!.objects;
        objU := U!.objects;
        if not IsSubset( objG, objU ) then
            return false;
        fi;
        if not IsSubgroup( G!.group, U!.group ) then
            return false;
        fi;
        return true;
    elif IsConnectedGroupoid( U ) then
        obj := U!.objects[1];
        C := ComponentOfObject( G, obj );
        return IsSubgroupoid( C, U );
    else
        compU := ComponentsOfGroupoid( U );
        return ForAll( compU, C -> IsSubgroupoid( G, C ) );
    fi;
end );

#############################################################################
##
#M  SubgroupoidByComponents
##
InstallMethod( SubgroupoidByComponents,
    "generic method for a groupoid and a list of components", true,
    [ IsGroupoid, IsHomogeneousList ], 0,
function( gpd, comps )

    local  compU, U, c;

    compU := List( comps, c -> ConnectedGroupoid( c[1], c[2] ) );
    if ( Length( compU ) > 1 ) then
        U := GroupoidByUnion( compU );
    else
        U := compU[1];
    fi;
    if not IsSubgroupoid( gpd, U ) then 
        Info( InfoGpd, 1, "union of comps is not a subgroupoid of gpd" );
        return fail;
    fi;
    if ( ObjectsOfGroupoid( gpd ) = ObjectsOfGroupoid( U ) ) then
        SetIsWideSubgroupoid( U, true );
    else
        SetIsWideSubgroupoid( U, false );
    fi;
    if ForAll( comps, c -> ( Length( c[1] ) = 1 ) ) then
        SetIsDiscreteGroupoid( U, true );
    else
        SetIsDiscreteGroupoid( U, false );
    fi;
    SetParentAttr( U, gpd );
    for c in compU do
        SetParentAttr( c, ComponentOfObject( gpd, c!.objects[1] ) );
    od;
    return U;
end );

#############################################################################
##
#M  DiscreteSubgroupoid
##
InstallMethod( DiscreteSubgroupoid, "generic method for a groupoid", true,
    [ IsGroupoid, IsHomogeneousList, IsHomogeneousList ], 0,
function( G, obs, gps )

    local  compU, U, len, o, compG, obsg, C, i;

    obsg := ObjectsOfGroupoid( G );
    len := Length( obs );
    compU := ListWithIdenticalEntries( len, 0 );
    if not ( len = Length( gps ) ) then
        Error( "object and subgroup lists not of same length" );
    fi;
    for i in [1..len] do
        o := obs[i];
        if not ( o in obsg ) then
            Error( "subgroupoid object not in groupoid" );
        fi;
        C := ComponentOfObject( G, o );
        if not IsSubgroup( C!.group, gps[i] ) then
            Error( "not a subgroup of object group" );
        fi;
        compU[i] := [ [o], gps[i] ];
    od;
    U := SubgroupoidByComponents( G, compU );
    SetIsDiscreteGroupoid( U, true );
    SetParentAttr( U, G );
    return U;
end );

#############################################################################
##
#M  MaximalDiscreteSubgroupoid
##
InstallMethod( MaximalDiscreteSubgroupoid,
    "generic method for a groupoid", true, [ IsGroupoid ], 0,
function( gpd )

    local  obs, len, gps, i, comp;

    obs := ObjectsOfGroupoid( gpd );
    len := Length( obs );
    gps := ListWithIdenticalEntries( len, 0 );
    for i in [1..len] do
        comp := ComponentOfObject( gpd, obs[i] );
        gps[i] := comp!.group;
    od;
    return DiscreteSubgroupoid( gpd, obs, gps );
end );

#############################################################################
##
#M  FullSubgroupoid
#M  FullIdentitySubgroupoid
#M  DiscreteIdentitySubgroupoid
##
InstallMethod( FullSubgroupoid, "for a connected groupoid and set of objects", 
    true, [ IsConnectedGroupoid, IsHomogeneousList ], 0,
function( G, sobs )
    return SubgroupoidByComponents( G, [ [sobs, G!.group] ] );
end );

InstallMethod( FullSubgroupoid, "for a groupoid and set of objects", true,
    [ IsGroupoid, IsHomogeneousList ], 0,
function( G, sobs )
    local  c1, c2, len, j, c, obc, sobc;
    c1 := ComponentsOfGroupoid( G );
    len := Length( c1 );
    c2 := [ ];
    for j in [1..len] do
        c := c1[j];
        obc := c!.objects;
        sobc := Intersection( sobs, obc );
        if ( sobc <> [ ] ) then
            Add( c2, [ sobc, c!.group ] );
        fi;
    od;
    return SubgroupoidByComponents( G, c2 );
end );

InstallMethod( FullIdentitySubgroupoid, "for a connected groupoid", 
    true, [ IsConnectedGroupoid ], 0,
function( gpd ) 
    local  id, sub;
    id := IdentitySubgroup( gpd!.group );
    sub := ConnectedGroupoidNC( gpd!.objects, id );
    SetParentAttr( sub, gpd );
    return sub;
end );

InstallMethod( FullIdentitySubgroupoid, "generic method for a groupoid", 
    true, [ IsGroupoid ], 0,
function( gpd )
    local  comps, subs;
    comps := ComponentsOfGroupoid( gpd );
    subs := List( comps, c -> FullIdentitySubgroupoid(c) );
    return GroupoidByUnion( subs );
end );

InstallMethod( DiscreteIdentitySubgroupoid, "for a connected groupoid", 
    true, [ IsConnectedGroupoid ], 0,
function( gpd )
    local  id, obs, gps;
    obs := gpd!.objects;
    id := IdentitySubgroup( gpd!.group );
    gps := List( obs, o -> id );
    return DiscreteSubgroupoid( gpd, obs, gps );
end );

InstallMethod( DiscreteIdentitySubgroupoid, "generic method for a groupoid", 
    true, [ IsGroupoid ], 0,
function( gpd )
    local  comps, subs;
    comps := ComponentsOfGroupoid( gpd );
    subs := List( comps, c -> DiscreteIdentitySubgroupoid(c) );
    return GroupoidByUnion( subs );
end );

#############################################################################
##
#M  ComponentPositions
##
InstallMethod( ComponentPositions, "for a groupoid and a subgroupoid", true,
    [ IsGroupoid, IsGroupoid ], 0,
function( G, U )

    local  obsG, obU, lenG, lenU, pos, i, oi, j, found, p;

    if IsConnectedGroupoid( G ) then
        if IsConnectedGroupoid( U ) then
            pos := [ 1 ];
        else
            pos := List( ComponentsOfGroupoid(U), c -> 1 );
        fi;
    else
        obsG := List( ComponentsOfGroupoid(G), c -> c!.objects );
        lenG := Length( obsG );
        obU := List( ComponentsOfGroupoid(U), c -> c!.objects[1] );
        lenU := Length( obU );
        pos := ListWithIdenticalEntries( lenU, 0 );
        for i in [1..lenU] do
            oi := obU[i];
            found := false;
            j := 0;
            while ( not found ) do
                j := j+1;
                p := Position( obsG[j], oi );
                if ( p <> fail ) then
                    pos[i] := j;
                    found := true;
                fi;
            od;
        od;
    fi;
    Info( InfoGpd, 3, "component positions = ", pos ); 
    return pos;
end );

#############################################################################
##
#M  IsWideSubgroupoid
##
InstallMethod( IsWideSubgroupoid, "for a subgroupoid", true,
    [ IsGroupoid ], 0,
function( U )
    return ( ObjectsOfGroupoid( U ) = ObjectsOfGroupoid( Parent( U ) ) );
end );

#############################################################################
##
#M  IsDiscreteGroupoid
##
InstallMethod( IsDiscreteGroupoid, "for a groupoid", true,
    [ IsGroupoid ], 0,
function( gpd )

    local  c;
    for c in ComponentsOfGroupoid( gpd ) do
        if not ( Length( c!.objects ) = 1 ) then
            return false;
        fi;
    od;
    return true;
end );

## ======================================================================== ##
##                            Groupoid Cosets                               ##
## ======================================================================== ##

##############################################################################
##
#M  RightCoset
#M  LeftCoset
#M  DoubleCoset
##
InstallOtherMethod( RightCoset, "for groupoid, subgroupoid, groupoid element",
    true, [ IsConnectedGroupoid, IsGroupoid, IsGroupoidElementRep ], 0,
function( G, U, e )

    local  obsG, obsU, compU, gu, o1, o2, rc, fam, filter, c;
    
    ## not fully checking that U is a subgroupoid of G
    obsG := G!.objects;
    o1 := e![2];
    o2 := e![3];
    compU := ComponentOfObject( U, o1 );
    gu := compU!.group;
    obsU := compU!.objects;
    if not ( ( o1 in obsG ) and ( o2 in obsG ) ) then
        Info( InfoGpd, 1, "it appears that element e is not in G" );
        return fail;
    fi;
    rc := RightCoset( gu, e![1] );
    fam := FamilyObj( [ rc, obsU, [o1], e, "r" ] );
    filter := IsHomsetCosetsRep;
    c := Objectify( NewType( fam, filter ), [ rc, obsU, [o1], e, "r" ] );
    SetIsHomsetCosets( c, true );
    return c;
end);

InstallOtherMethod( RightCoset, "for groupoid, subgroupoid, groupoid element",
    true, [ IsGroupoid, IsGroupoid, IsGroupoidElementRep ], 0,
function( G, U, e )

    local  compG, compsU;
    
    ## not yet checking that U is a subgroupoid of G
    compG := ComponentOfObject( G, e![2] );
    compsU := FullSubgroupoid( U, compG!.objects );
    return RightCoset( compG, compsU, e );
end);

InstallOtherMethod( LeftCoset, "for groupoid, subgroupoid, groupoid element",
    true, [ IsConnectedGroupoid, IsGroupoid, IsGroupoidElementRep ], 0,
function( G, U, e )

    local  obsG, obsU, compU, gu, o1, o2, fam, filter, c;
    
    ## not fully checking that U is a subgroupoid of G
    obsG := G!.objects;
    o1 := e![2];
    o2 := e![3];
    compU := ComponentOfObject( U, o2 );
    gu := compU!.group;
    obsU := compU!.objects;
    if not ( ( o1 in obsG ) and ( o2 in obsG ) ) then
        Info( InfoGpd, 1, "it appears that element e is not in G" );
        return fail;
    fi;
    fam := FamilyObj( [ gu, [o2], obsU, e, "l" ] );
    filter := IsHomsetCosetsRep;
    c := Objectify( NewType( fam, filter ), [ gu, [o2], obsU, e, "l" ] );
    SetIsHomsetCosets( c, true );
    return c;
end);

InstallOtherMethod( LeftCoset, "for groupoid, subgroupoid, groupoid element",
    true, [ IsGroupoid, IsGroupoid, IsGroupoidElementRep ], 0,
function( G, U, e )

    local  compG, compsU;
    
    ## not yet checking that U is a subgroupoid of G
    compG := ComponentOfObject( G, e![3] );
    compsU := FullSubgroupoid( U, compG!.objects );
    return RightCoset( compG, compsU, e );
end);

InstallOtherMethod( DoubleCoset, "for groupoid; 2 subgpds; element", true, 
    [ IsConnectedGroupoid, IsGroupoid, IsGroupoidElementRep, IsGroupoid ], 0,
function( G, U, e, V )

    local  gg, obsG, obsU, compU, obsV, compV, gu, gv, dc, ou, ov, 
           fam, filter, c;
    
    ## not yet checking that U,V are subgroupoids of G
    gg := G!.group;
    obsG := G!.objects;
    ou := e![2];
    compU := ComponentOfObject( U, ou );
    gu := compU!.group;
    obsU := compU!.objects;
    ov := e![3];
    compV := ComponentOfObject( V, ov );
    gv := compV!.group;
    obsV := compV!.objects;
    dc := DoubleCoset( gu, e![1], gv );
    if not ( ( ou in obsG ) and ( ov in obsG ) ) then
        Info( InfoGpd, 1, "it appears that element e is not in G" );
        return fail;
    fi;
    fam := FamilyObj( [ dc, obsU, obsV, e, "d" ] );
    filter := IsHomsetCosetsRep;
    c := Objectify( NewType( fam, filter ), [ dc, obsU, obsV, e, "d" ] );
    SetIsHomsetCosets( c, true );
    return c;
end);

InstallOtherMethod( DoubleCoset, "for groupoid, 2 subgpds, groupoid element",
    true, [ IsGroupoid, IsGroupoid, IsGroupoidElementRep, IsGroupoid ], 0,
function( G, U, e, V )

    local  compG, obs, compsU, compsV;
    
    ## not yet checking that U is a subgroupoid of G
    compG := ComponentOfObject( G, e![3] );
    obs := compG!.objects;
    compsU := FullSubgroupoid( U, obs );
    compsV := FullSubgroupoid( V, obs );
    return RightCoset( compG, compsU, e, compsV );
end);

#############################################################################
########  Why not `IsIdenticalObj' in the following declarations ??? ########

############################################################################# 
## 
#M  RightCosetRepresentatives
## 
InstallMethod( RightCosetRepresentatives, "generic method for a subgroupoid",
    true, [ IsConnectedGroupoid, IsGroupoid ], 0,
function( G, U )

    local  gg, obG, nobG, reps, c, o1, gc, rc, nrc, L, j, b, g, o;

    if not ( IsSubgroupoid( G, U ) and IsWideSubgroupoid( U ) ) then
        Error( "U not a wide subgroupoid of G" );
    fi;
    gg := G!.group;
    obG := G!.objects;
    nobG := Length( obG );
    reps := [ ];
    for c in ComponentsOfGroupoid( U ) do
        o1 := c!.objects[1];
        gc := c!.group;
        rc := RightCosets( gg, gc );
        nrc := Length( rc );
        L := ListWithIdenticalEntries( nrc*nobG, 0 );
        j := 0;
        for b in rc do
            g := Representative(b);
            for o in obG do
                j := j+1;
                L[j] := GroupoidElementNC( g, o1, o );
            od;
        od;
        Append( reps, L );
    od;
    return reps;
end );    

InstallMethod( RightCosetRepresentatives, "generic method for a subgroupoid",
    true, [ IsGroupoid, IsGroupoid ], 0,
function( G, U )

    local  pos, cG, cU, ncU, reps, i, filt, comp, m, j, subU;

    if not ( IsSubgroupoid( G, U ) and IsWideSubgroupoid( U ) ) then
        Error( "U not a wide subgroupoid of G" );
    fi;
    pos := ComponentPositions( G, U );
    reps := [ ];
    cG := ComponentsOfGroupoid( G );
    cU := ComponentsOfGroupoid( U );
    ncU := Length( cU );
    for i in [1..Length(cG)] do
        filt := Filtered( [1..ncU], p -> ( pos[p] = i ) );
        comp := List( filt, p -> cU[p] );
        if ( Length(filt) = 1 ) then
            subU := comp[1];
        else
            subU := GroupoidByUnionNC( comp );
        fi;
        Append( reps, RightCosetRepresentatives( cG[i], subU ) );
    od;
    return reps;
end );

############################################################################# 
## 
#M  LeftCosetRepresentatives
## 
InstallMethod( LeftCosetRepresentatives, "generic method for a subgroupoid",
    true, [ IsConnectedGroupoid, IsGroupoid ], 0,
function( G, U )

    local  gg, obG, nobG, reps, c, o1, gc, rc, nrc, L, j, b, g, o;

    if not ( IsSubgroupoid( G, U ) and IsWideSubgroupoid( U ) ) then
        Error( "U not a wide subgroupoid of G" );
    fi;
    gg := G!.group;
    obG := G!.objects;
    nobG := Length( obG );
    reps := [ ];
    for c in ComponentsOfGroupoid( U ) do
        o1 := c!.objects[1];
        gc := c!.group;
        rc := RightCosets( gg, gc );
        nrc := Length( rc );
        L := ListWithIdenticalEntries( nrc*nobG, 0 );
        j := 0;
        for b in rc do
            g := Representative(b)^(-1);
            for o in obG do
                j := j+1;
                L[j] := GroupoidElementNC( g, o, o1 );
            od;
        od;
        Append( reps, L );
    od;
    return reps;
end );    

InstallMethod( LeftCosetRepresentatives, "generic method for a subgroupoid",
    true, [ IsGroupoid, IsGroupoid ], 0,
function( G, U )

    local  pos, cG, cU, ncU, reps, i, filt, comp, m, j, subU;

    if not ( IsSubgroupoid( G, U ) and IsWideSubgroupoid( U ) ) then
        Error( "U not a wide subgroupoid of G" );
    fi;
    pos := ComponentPositions( G, U );
    reps := [ ];
    cG := ComponentsOfGroupoid( G );
    cU := ComponentsOfGroupoid( U );
    ncU := Length( cU );
    for i in [1..Length(cG)] do
        filt := Filtered( [1..ncU], p -> ( pos[p] = i ) );
        comp := List( filt, p -> cU[p] );
        if ( Length(filt) = 1 ) then
            subU := comp[1];
        else
            subU := GroupoidByUnionNC( comp );
        fi;
        Append( reps, LeftCosetRepresentatives( cG[i], subU ) );
    od;
    return reps;
end );

############################################################################# 
## 
#M  LeftCosetRepresentativesFromObject
## 
InstallMethod( LeftCosetRepresentativesFromObject, "for a subgroupoid",
    true, [ IsConnectedGroupoid, IsGroupoid, IsObject ], 0,
function( G, U, o )

    local  gg, obG, nobG, reps, c, o1, gc, rc, nrc, L, j, b, g;

    if not ( IsSubgroupoid( G, U ) and IsWideSubgroupoid( U ) ) then
        Error( "U not a wide subgroupoid of G" );
    fi;
    gg := G!.group;
    obG := G!.objects;
    if not ( o in obG ) then
        Error( "chosen object not in the groupoid" );
    fi;
    reps := [ ];
    for c in ComponentsOfGroupoid( U ) do
        o1 := c!.objects[1];
        gc := c!.group;
        rc := RightCosets( gg, gc );
        nrc := Length( rc );
        L := ListWithIdenticalEntries( nrc, 0 );
        j := 0;
        for b in rc do
            g := Representative(b)^(-1);
            j := j+1;
            L[j] := GroupoidElementNC( g, o, o1 );
        od;
        Append( reps, L );
    od;
    return reps;
end );    

InstallMethod( LeftCosetRepresentativesFromObject, "for a subgroupoid",
    true, [ IsGroupoid, IsGroupoid, IsObject ], 0,
function( G, U, o )

    local  pos, cG, co, i, cU, ncU, filt, comp, m, j, subU;

    if not ( IsSubgroupoid( G, U ) and IsWideSubgroupoid( U ) ) then
        Error( "U not a wide subgroupoid of G" );
    fi;
    pos := ComponentPositions( G, U );
    cG := ComponentsOfGroupoid( G );
    co := ComponentOfObject( G, o );
    if ( co = fail ) then
        Error( "chosen object not in the groupoid" );
    fi;
    i := Position( cG, co );
    cU := ComponentsOfGroupoid( U );
    ncU := Length( cU );
    filt := Filtered( [1..ncU], p -> ( pos[p] = i ) );
    comp := List( filt, p -> cU[p] );
    if ( Length(filt) = 1 ) then
        subU := comp[1];
    else
        subU := GroupoidByUnionNC( comp );
    fi;
    return LeftCosetRepresentativesFromObject( co, subU, o );
end );

############################################################################# 
## 
#M  DoubleCosetRepresentatives
## 
InstallMethod( DoubleCosetRepresentatives, "generic method for 2 subgroupoids",
    true, [ IsConnectedGroupoid, IsGroupoid, IsGroupoid ], 0,
function( G, U, V )

    local  gg, obG, nobG, reps, cu, ou, gu, cv, ov, gv, dc, c, r;

    if not ( IsSubgroupoid( G, U ) and IsWideSubgroupoid( U ) and 
             IsSubgroupoid( G, V ) and IsWideSubgroupoid( V ) ) then
        Error( "U,V not wide subgroupoids of G" );
    fi;
    gg := G!.group;
    obG := G!.objects;
    nobG := Length( obG );
    reps := [ ];
    for cu in ComponentsOfGroupoid( U ) do
        ou := cu!.objects[1];
        gu := cu!.group;
        for cv in ComponentsOfGroupoid( V ) do
            ov := cv!.objects[1];
            gv := cv!.group;
            dc := DoubleCosets( gg, gu, gv );
            for c in dc do
                r := Representative( c );
                Add( reps, GroupoidElementNC( r, ou, ov ) );
            od;
        od;
    od;
    return reps;
end );    

##  still need to implement  DoubleCosetRepresentatives for G not connected

#############################################################################
##
#M  RightCosetsNC
#M  LeftCosetsNC
#M  DoubleCosetsNC
##
##  right cosets refine costars and left cosets refine stars
##
InstallOtherMethod( RightCosetsNC, "for groupoids", true,
    [ IsGroupoid, IsGroupoid ], 0,
function( G, U )

    local  cosets, reps, nr, i, e;

    reps := RightCosetRepresentatives( G, U );
    nr := Length( reps );
    cosets := ListWithIdenticalEntries( nr, 0 );
    for i in [1..nr] do
        e := reps[i];
        cosets[i] := RightCoset( G, U, e );
    od;
    return cosets;
end);
    
InstallMethod( LeftCosetsNC, "for groupoids", true, 
    [ IsGroupoid, IsGroupoid ], 0,
function( G, U )

    local  cosets, reps, nr, i, e;

    reps := LeftCosetRepresentatives( G, U );
    nr := Length( reps );
    cosets := ListWithIdenticalEntries( nr, 0 );
    for i in [1..nr] do
        e := reps[i];
        cosets[i] := LeftCoset( G, U, e );
    od;
    return cosets;
end);
    
InstallOtherMethod( DoubleCosetsNC, "for groupoids", true,
    [ IsGroupoid, IsGroupoid, IsGroupoid ], 0,
function( G, U, V )

    local  cosets, reps, nr, i, e;

    reps := DoubleCosetRepresentatives( G, U, V );
    nr := Length( reps );
    cosets := ListWithIdenticalEntries( nr, 0 );
    for i in [1..nr] do
        e := reps[i];
        cosets[i] := DoubleCoset( G, U, e, V );
    od;
    return cosets;
end);
    
#############################################################################
##
##  GpdBuildManual()
##
GpdBuildManual:=function()
local gpd_path, gpd_main, gpd_files, gpd_bookname;
gpd_path:=Concatenation(
               GAPInfo.PackagesInfo.("gpd")[1].InstallationPath,"/doc/");
gpd_main := "manual.xml";
gpd_bookname:="Gpd";
MakeGAPDocDoc( gpd_path, gpd_main, [ ], gpd_bookname );;
end;

##############################################################################
##
#E  gpd.gi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ends here
##
