#############################################################################
##
#W img.gd                                                   Laurent Bartholdi
##
#H   _FR(#)$Id: img.gd,v 1.24 2009/06/17 21:12:44 gap Exp $
##
#Y Copyright (C) 2006, Laurent Bartholdi
##
#############################################################################
##
##  Iterated monodromy groups
##
#############################################################################

#############################################################################
##
#M IMGMachine
##
## <#GAPDoc Label="IMGMachine">
## <ManSection>
##   <Oper Name="IMGMachine" Arg="m[,w]" Label="FR machine"/>
##   <Returns>An IMG FR machine.</Returns>
##   <Description>
##     This function creates a new IMG FR machine, starting from a group
##     FR machine <A>m</A>. If a state <C>w</C> is specified, and that
##     state defines the trivial FR element, then it is used
##     as IMG relator; if the state <C>w</C> is non-trivial, then
##     a new generator <C>f</C> is added to <A>m</A>, equal to the
##     inverse of <C>w</C>; and the IMG relator is chosen to be <C>w*f</C>.
##     Finally, if no relator is specified, and the product (in some ordering)
##     of the generators is trivial, then that product is used as IMG
##     relator. In other cases, the method returns <K>fail</K>.
##
##     <P/> Note that IMG elements and FR elements are compared differently
##     (see the example below); namely, an FR element is trivial precisely
##     when it acts trivially on sequences. An IMG element is trivial
##     precisely when a finite number of applications of free cancellation,
##     the IMG relator, and the decomposition map, result in trivial elements
##     of the underlying free group.
##
##     <P/> A standard FR machine can be recovered from an IMG FR machine
##     by <Ref Oper="AsGroupFRMachine"/>, <Ref Oper="AsMonoidFRMachine"/>,
##     and <Ref Oper="AsSemigroupFRMachine"/>.
## <Example><![CDATA[
## gap> m := UnderlyingFRMachine(BasilicaGroup);
## <Mealy machine on alphabet [ 1 .. 2 ] with 3 states>
## gap> g := AsGroupFRMachine(m);
## <FR machine with alphabet [ 1 .. 2 ] on Group( [ f1, f2 ] )>
## gap> IMGMachine(g,Product(GeneratorsOfFRMachine(g)));
## <FR machine with alphabet [ 1 .. 2 ] on Group( [ f1, f2, t ] )/[ f1*f2*t ]>
## gap> Display(last);
##  G  |              1         2
## ----+-----------------+---------+
##  f1 |          <id>,2      f2,1
##  f2 |          <id>,1      f1,2
##   t | f2^-1*f1*f2*t,2   f1^-1,1
## ----+-----------------+---------+
## Relator: f1*f2*t
## gap> g := AsGroupFRMachine(GuptaSidkiMachine);
## <FR machine with alphabet [ 1 .. 3 ] on Group( [ f1, f2 ] )>
## gap> m := IMGMachine(g,GeneratorsOfFRMachine(g)[1]);
## <FR machine with alphabet [ 1 .. 3 ] on Group( [ f1, f2, t ] )/[ f1*t ]>
## gap> x := FRElement(g,2)^3; IsOne(x);
## <3|identity ...>
## true
## gap> x := FRElement(m,2)^3; IsOne(x);
## <3#f2^3>
## false
## ]]></Example>
##   </Description>
## </ManSection>
##
## <ManSection>
##   <Attr Name="IMGRelator" Arg="m"/>
##   <Returns>The relator of the IMG FR machine.</Returns>
##   <Description>
##     This attribute stores the product of generators that is trivial.
##     In essence, it records an ordering of the generators whose
##     product is trivial in the punctured sphere's fundamental group.
##   </Description>
## </ManSection>
##
## <ManSection>
##   <Oper Name="Mating" Arg="m1,m2[,w1,w2]"/>
##   <Returns>An IMG FR machine.</Returns>
##   <Description>
##     This function mates two IMG FR machines. The arguments <A>w1,w2</A>
##     are IMG FR elements that represent adding elements; they may be
##     omitted if <A>m1,m2</A> contain pre-specified adding machines
##     (through the attribute <Ref Attr="AddingElement"/>). The elements
##     <A>w1</A> and <A>w2</A> must satisfy the same recursion.
##
##     <P/> The mating is defined as follows: one removes a disc around
##     the adding machine in <A>m1</A> and <A>m2</A>; one applies complex
##     conjugation to <A>m2</A>; and one glues the hollowed spheres along
##     their boundary circle.
## <Example><![CDATA[
## gap> # the Tan-Shishikura examples
## gap> a := ComplexRootsOfUnivariatePolynomial((z-1)*(3*z^2-2*z^3)+1);;
## gap> c := ComplexRootsOfUnivariatePolynomial((z^3+z)^3+z);;
## gap> am := List(a,a->IMGMachine((a-1)*(3*z^2-2*z^3)+1));;
## gap> cm := List(c,c->IMGMachine(z^3+c));;
## gap> m := ListX(am,cm,Mating);;
## gap> # m[!!!] is obstructed
## gap> # m[!!!] is not
## ]]></Example>
##   </Description>
## </ManSection>
##
## <ManSection>
##   <Oper Name="PolynomialFRMachine" Arg="d,per,pre"/>
##   <Oper Name="PolynomialIMGMachine" Arg="d,per,pre[,formal]"/>
##   <Oper Name="PolynomialMealyMachine" Arg="d,per,pre"/>
##   <Returns>An IMG FR machine.</Returns>
##   <Description>
##     This function creates a group, IMG or Mealy machine that describes
##     a topological polynomial. The polynomial is described symbolically
##     in the language of <E>external angles</E>. For more details, see
##     <Cite Key="MR762431"/> and <Cite Key="MR812271"/> (in the quadratic
##      case), <Cite Key="MR1149891"/> (in the preperiodic case), and
##     <Cite Key="math.DS/9305207"/> (in the general case).
##
##     <P/> <A>d</A> is the degree of the polynomial. <A>per</A> and
##     <A>pre</A> are lists of angles or preangles. In what follows,
##     angles are rational numbers, considered modulo 1.  Each entry in
##     <A>per</A> or <A>pre</A> is either a rational (interpreted as an
##     angle), or a list of angles <M>[a_1,\ldots,a_i]</M> such that
##     <M>da_1=\ldots=da_i</M>.
##
##     <P/> Note that the last generator if the machine produced is an
##     adding machine, representing a loop going counterclockwise
##     around infinity (in the compactification of <M>\mathbb C</M> by a
##     disk, this loop goes <E>clockwise</E> around that disk).
##
##     <P/> Formal!!!
## !!!! say inverse function is ExternalAngles !!!
## <Example><![CDATA[
## gap> PolynomialIMGMachine(2,[0],[]); # the adding machine
## <FR machine with alphabet [ 1 .. 2 ] on Group( [ f1, f2 ] )/[ f2*f1 ]>
## gap> Display(last);
##  G  |     1        2
## ----+--------+--------+
##  f1 | <id>,2     f1,1
##  f2 |   f2,2   <id>,1
## ----+--------+--------+
## Relator: f2*f1
## gap> Display(PolynomialIMGMachine(2,[1/3],[])); # the Basilica
##  G  |      1         2
## ----+---------+---------+
##  f1 | f1^-1,2   f2*f1,1
##  f2 |    f1,1    <id>,2
##  f3 |    f3,2    <id>,1
## ----+---------+---------+
## Relator: f3*f2*f1
## gap> Display(PolynomialIMGMachine(2,[],[1/6])); # z^2+I
##  G  |            1         2
## ----+---------------+---------+
##  f1 | f1^-1*f2^-1,2   f2*f1,1
##  f2 |          f1,1      f3,2
##  f3 |          f2,1    <id>,2
##  f4 |          f4,2    <id>,1
## ----+---------------+---------+
## Relator: f4*f3*f2*f1
## gap> PolynomialIMGMachine(3,[[0,1/3],[5/9,8/9]],[]);
## <FR machine with alphabet [ 1 .. 3 ] on Group( [ f1, f2, f3 ] )/[ f3*f2*f1 ]>
## gap> PolynomialIMGMachine(3,[[0,1/3]],[[5/9,8/9]]);
## <FR machine with alphabet [ 1 .. 3 ] on Group( [ f1, f2, f3, f4 ] )/[ f4*f3*f2*f1 ]>
## ]]></Example>
##     The following construct the examples in Poirier's paper:
## <Listing><![CDATA[
## PoirierExamples := function(arg)
##     if arg=[1] then
##         return PolynomialIMGMachine(2,[1/7],[]);
##     elif arg=[2] then
##         return PolynomialIMGMachine(2,[],[1/2]);
##     elif arg=[3,1] then
##         return PolynomialIMGMachine(2,[],[5/12]);
##     elif arg=[3,2] then
##         return PolynomialIMGMachine(2,[],[7/12]);
##     elif arg=[4,1] then
##         return PolynomialIMGMachine(3,[[3/4,1/12],[1/4,7/12]],[]);
##     elif arg=[4,2] then
##         return PolynomialIMGMachine(3,[[7/8,5/24],[5/8,7/24]],[]);
##     elif arg=[4,3] then
##         return PolynomialIMGMachine(3,[[1/8,19/24],[3/8,17/24]],[]);
##     elif arg=[5] then
##         return PolynomialIMGMachine(3,[[3/4,1/12],[3/8,17/24]],[]);
##     elif arg=[6,1] then
##         return PolynomialIMGMachine(4,[],[[1/4,3/4],[1/16,13/16],[5/16,9/16]]);
##     elif arg=[6,2] then
##         return PolynomialIMGMachine(4,[],[[1/4,3/4],[3/16,15/16],[7/16,11/16]]);
##     elif arg=[7] then
##         return PolynomialIMGMachine(5,[[0,4/5],[1/5,2/5,3/5]],[[1/5,4/5]]);
##     elif arg=[9,1] then
##         return PolynomialIMGMachine(3,[[0,1/3],[5/9,8/9]],[]);
##     elif arg=[9,2] then
##         return PolynomialIMGMachine(3,[[0,1/3]],[[5/9,8/9]]);
##     fi;
## end;
## ]]></Listing>
##   </Description>
## </ManSection>
## <#/GAPDoc>
##
DeclareAttribute("IMGRelator",IsGroupFRMachine);
DeclareSynonym("IsIMGMachine",IsGroupFRMachine and HasIMGRelator);
DeclareAttribute("AsGroupFRMachine",IsGroupHomomorphism);
DeclareAttribute("AsMonoidFRMachine",IsMagmaHomomorphism);
DeclareAttribute("AsSemigroupFRMachine",IsMagmaHomomorphism);
DeclareAttribute("IMGMachine",IsFRMachine);
DeclareOperation("IMGMachine",[IsFRMachine,IsWord]);
DeclareProperty("IsKneadingMachine",IsFRMachine);
DeclareProperty("IsPlanarKneadingMachine",IsFRMachine);
InstallTrueMethod(IsBoundedFRMachine,IsKneadingMachine);
InstallTrueMethod(IsLevelTransitive,IsKneadingMachine);

DeclareAttribute("AddingElement",IsIMGMachine);
DeclareSynonym("IsPolynomialIMGMachine",IsIMGMachine and HasAddingElement);
DeclareOperation("PolynomialMealyMachine",[IsPosInt,IsList,IsList]);
DeclareOperation("PolynomialFRMachine",[IsPosInt,IsList,IsList]);
DeclareOperation("PolynomialIMGMachine",[IsPosInt,IsList,IsList]);
DeclareOperation("PolynomialIMGMachine",[IsPosInt,IsList,IsList,IsBool]);
DeclareOperation("PolynomialIMGMachine",[IsGroupFRMachine]);

DeclareAttribute("CriticalPortrait",IsPolynomialIMGMachine);
        
DeclareOperation("Mating",[IsPolynomialIMGMachine,IsPolynomialIMGMachine]);

DeclareCategory("IsIMGElement",IsGroupFRElement);
DeclareCategoryCollections("IsIMGElement");
DeclareAttribute("AsIMGElement",IsGroupFRElement);
#############################################################################

#############################################################################
##
#M ChangeFRMachineBasis
##
## <#GAPDoc Label="ChangeFRMachineBasis">
## <ManSection>
##   <Attr Name="ChangeFRMachineBasis" Arg="m[,l]"/>
##   <Returns>An equivalent FR machine, in a new basis.</Returns>
##   <Description>
##     This function constructs a new group FR machine, given a group
##     FR machine <A>m</A> and a list of states <A>l</A> (as elements of
##     the free object <C>StateSet(m)</C>).
##
##     <P/> The new machine
##     has the following transitions: if alphabet letter <C>a</C> is mapped
##     to <C>b</C> by state <C>s</C> in <A>m</A>, leading to state <C>t</C>,
##     then in the new machine the new state is <C>l[a]&circum;-1*t*l[b]</C>.
##
##     <P/> The group generated by the new machine is isomorphic to the
##     group generated by <A>m</A>. This command amounts to a change of
##     basis of the associated bimodule (see <Cite Key="MR2162164"
##     Where="Section 2.2"/>). It amounts to conjugation by the automorphism
##     <C>c=FRElement("c",[l[1]*c,...,l[n]*c],[()],1)</C>.
##
##     <P/> If the second argument is absent, this command attempts to
##     choose one that makes many entries of the recursion trivial.
## <Example><![CDATA[
## gap> n := FRMachine(["tau","mu"],[[[],[1]],[[],[-2]]],[(1,2),(1,2)]);;
## gap> Display(n);
##  G   |     1         2
## -----+--------+---------+
##  tau | <id>,2     tau,1
##   mu | <id>,2   mu^-1,1
## -----+--------+---------+
## gap> nt := ChangeFRMachineBasis(n,GeneratorsOfFRMachine(n){[1,1]});;
## gap> Display(nt);
##  G   |     1                    2
## -----+--------+--------------------+
##  tau | <id>,2                tau,1
##   mu | <id>,2   tau^-1*mu^-1*tau,1
## -----+--------+--------------------+
## ]]></Example>
##   </Description>
## </ManSection>
## <#/GAPDoc>
##
DeclareOperation("ChangeFRMachineBasis", [IsFRMachine, IsCollection]);
DeclareOperation("ChangeFRMachineBasis", [IsFRMachine]);
#############################################################################

#############################################################################
##
#E Triangulations
##
## <#GAPDoc Label="Triangulations">
## <ManSection>
##   <Oper Name="DelaunayTriangulation" Arg="points"/>
##   <Returns>A Delaunay triangulation of the sphere.</Returns>
##   <Description>
##     If <A>points</A> is a list of points on the unit sphere, represented
##     by their 3D coordinates, this function creates a triangulation of
##     the sphere with these points as vertices. This triangulation is
##     such that the angles are as equilateral as possible.
##
##     <P/> This triangulation is a recursive collection of records, one
##     for each vertex, oriented edge or face. Each such object has a
##     <C>pos</C> component giving its coordinates; and an <C>index</C>
##     component identifying it uniquely. Additionally, vertices and
##     faces have a <C>n</C> component which lists their neighbours in CCW
##     order, and edges have <C>from,to,left,right,reverse</C> components.
##
##     <P/> If all points are aligned on a great circle, or if all points
##     are in a hemisphere, some points are added so as to make the
##     triangulation simplicial with all edges of length <M>&lt;\pi</M>.
##     These vertices additionally have a <C>fake</C> component set to
##     <K>true</K>.
##
##     <P/> A triangulation may be plotted with <C>Draw</C>; this requires
##     <Package>appletviewer</Package> to be installed. The command
##     <C>Draw(t:detach)</C> detaches the subprocess after it is started.
##
##     <P/> This command makes essential use of Renka's package
##     "Algorithm 772" (<Cite Key="MR1672176"/>), which must have been compiled
##     before &GAP; was run.
##
## <Example><![CDATA[
## gap> octagon := Concatenation(IdentityMat(3),-IdentityMat(3))*MACFLOAT_1;
## gap> dt := DelaunayTriangulation(octagon);
## <triangulation with 6 vertices, 24 edges and 8 faces>
## gap> dt!.v;
## [ <vertex 1>, <vertex 2>, <vertex 3>, <vertex 4>, <vertex 5>, <vertex 6> ]
## gap> last[1].n;
## [ <edge 17>, <edge 1>, <edge 2>, <edge 11> ]
## gap> last[1].from;
## <vertex 1>
## ]]></Example>
##   </Description>
## </ManSection>
##
## <ManSection>
##   <Oper Name="LocateInTriangulation" Arg="t,[seed,]point"/>
##   <Returns>The face in <A>t</A> containing <A>point</A>.</Returns>
##   <Description>
##     This command locates the face in <A>t</A> that contains <A>point</A>;
##     or, if <A>point</A> lies on an edge or a vertex, it returns that
##     edge or vertex.
##
##     <P/> The optional second argument specifies a starting vertex,
##     edge, face, or vertex index from which to start the search. Its only
##     effect is to speed up the algorithm.
## <Example><![CDATA[
## gap> cube := Tuples([-1,1],3)/Sqrt(Float(3));;
## gap> dt := DelaunayTriangulation(cube);
## <triangulation with 8 vertices, 36 edges and 12 faces>
## gap> LocateInTriangulation(dt,dt!.v[1].pos);
## <vertex 1>
## gap> LocateInTriangulation(dt,[3/5,0,4/5]*MACFLOAT_1);
## <face 9>
## ]]></Example>
##   </Description>
## </ManSection>
## <#/GAPDoc>
##
DeclareCategory("IsSphereTriangulation", IsObject);
TYPE_TRIANGULATION := NewType(FamilyObj(rec()), IsSphereTriangulation);

DeclareOperation("DelaunayTriangulation", [IsList]);
DeclareOperation("LocateInTriangulation", [IsSphereTriangulation,IsList]);
DeclareOperation("LocateInTriangulation", [IsSphereTriangulation,IsObject,IsList]);
DeclareOperation("Draw", [IsSphereTriangulation]);
#############################################################################

#############################################################################
##
#E Spiders
##
## <#GAPDoc Label="Spiders">
## <ManSection>
##   <Oper Name="RationalFunction" Arg="[z,] m"/>
##   <Returns>A rational function.</Returns>
##   <Description>
##   This command runs a modification of Hubbard and Schleicher's
##   "spider algorithm" <Cite Key="MR1315537"/> on the IMG FR machine <A>m</A>.
##   It either returns a rational function <C>f</C> whose associated machine
##   is <A>m</A>; or a record describing the Thurston obstruction to
##   realizability of <C>f</C>.
##
##   <P/> This obstruction record <C>r</C> contains a list <C>r.multicurve</C>
##   of conjugacy classes in <C>StateSet(m)</C>, which represent
##   short multicurves; a matrix <C>r.mat</C>, and a spider <C>r.spider</C>
##   on which the obstruction was discovered.
##
##   <P/> If a rational function is returned, it has preset attributes
##   <C>Spider(f)</C> and <C>IMGMachine(f)</C> which is a simplified
##   version of <A>m</A>. This rational function is also normalized so that
##   its post-critical points have barycenter=0 and has two post-critical
##   points at infinity and on the positive real axis.
##   Furthermore, if <A>m</A> is polynomial-like, then the returned map is
##   a polynomial.
## <Example><![CDATA[
## gap> m := PolynomialIMGMachine(2,[1/3],[]);
## <FR machine with alphabet [ 1, 2 ] on Group( [ f1, f2, f3 ] )/[ f3*f2*f1 ]>
## gap> RationalFunction(m);
## 0.866025*z^2+(-1)*z+(-0.288675)
## ]]></Example>
##   </Description>
## </ManSection>
##
## <ManSection>
##   <Oper Name="FRMachine" Arg="f" Label="rational function"/>
##   <Oper Name="IMGMachine" Arg="f" Label="rational function"/>
##   <Returns>An IMG FR machine.</Returns>
##   <Description>
##   This function computes a triangulation of the sphere, on the
##   post-critical set of <A>f</A>, and lifts it through the map <A>f</A>.
##   the action of the fundamental group of the punctured sphere is
##   then read into an IMG fr machine <C>m</C>, which is returned.
##
##   <P/> This machine has a preset attribute <C>Spider(m)</C>.
##
##   <P/> An approximation of the Julia set of <A>f</A> can be computed,
##   and plotted on the spider, with the form <C>IMGMachine(f:julia)</C>
##   or <C>IMGMachine(f:julia:=gridsize)</C>.
## <Example><![CDATA[
## gap> z := Indeterminate(COMPLEX_FIELD);;
## gap> IMGMachine(z^2-1);
## <FR machine with alphabet [ 1, 2 ] on Group( [ f1, f2, f3 ] )/[ f2*f1*f3 ]>
## gap> Display(last);
##  G  |            1        2
## ----+---------------+--------+
##  f1 |          f2,2   <id>,1
##  f2 | f3^-1*f1*f3,1   <id>,2
##  f3 |        <id>,2     f3,1
## ----+---------------+--------+
## Relator: f2*f1*f3
## ]]></Example>
##   </Description>
## </ManSection>
##
## <ManSection>
##   <Oper Name="FindThurstonObstruction" Arg="list"/>
##   <Returns>A description of the obstruction corresponding to <A>list</A>, or <K>fail</K>.</Returns>
##   <Description>
##   !!!!
##   </Description>
## </ManSection>
## <#/GAPDoc>
##
DeclareCategory("IsSpider", IsObject);
TYPE_SPIDER := NewType(FamilyObj(rec()), IsSpider);
DeclareOperation("Draw", [IsSpider]);
DeclareAttribute("VERTICES_FR", IsSpider);
DeclareAttribute("IMGRELATORS_FR", IsSpider);
DeclareAttribute("TREEBOUNDARY_FR", IsSpider);
DeclareAttribute("NFFUNCTION_FR", IsSpider);

DeclareAttribute("Spider", IsFRMachine);
DeclareAttribute("Spider", IsRationalFunction);
DeclareAttribute("IMGORDERING_FR", IsIMGMachine);

DeclareAttribute("RationalFunction", IsFRMachine);
DeclareOperation("RationalFunction", [IsRingElement, IsFRMachine]);
DeclareOperation("FRMachine", [IsRationalFunction]);
DeclareAttribute("IMGMachine", IsRationalFunction);

DeclareOperation("FindThurstonObstruction", [IsIMGElementCollection]);
#############################################################################

#############################################################################
##
#E DBRationalIMGGroup
##
## <#GAPDoc Label="DBRationalIMGGroup">
## <ManSection>
##   <Func Name="DBRationalIMGGroup" Arg="sequence/map"/>
##   <Returns>An IMG group from Dau's database.</Returns>
##   <Description>
##     This function returns the iterated monodromy group from a database
##     of groups associated to quadratic rational maps. This database has
##     been compiled by Dau Truong Tan <Cite Key="tan:database"/>.
##
##     <P/> When called with no arguments, this command returns the database
##     contents in raw form.
##
##     <P/> The argments can be a sequence; the first integer is the size
##     of the postcritical set, the second argument is an index for the
##     postcritical graph, and sometimes a third argument distinguishes
##     between maps with same post-critical graph.
##
##     <P/> If the argument is a rational map, the command returns the
##     IMG group of that map, assuming its canonical quadratic rational form
##     form exists in the database.
## <Example><![CDATA[
## gap> DBRationalIMGGroup(z^2-1);
## IMG((z-1)^2)
## gap> DBRationalIMGGroup(z^2+1); # not post-critically finite
## fail
## gap> DBRationalIMGGroup(4,1,1);
## IMG((z/h+1)^2|2h^3+2h^2+2h+1=0,h~-0.64)
## ]]></Example>
##   </Description>
## </ManSection>
##
## <ManSection>
##   <Func Name="PostCriticalMachine" Arg="f"/>
##   <Returns>The Mealy machine of <A>f</A>'s post-critical orbit.</Returns>
##   <Description>
##     This function constructs a Mealy machine <C>P</C> on the alphabet
##     <C>[1]</C>, which describes the post-critical set of <A>f</A>.
##     It is in fact an oriented graph with constant out-degree 1. It is
##     most conveniently passed to <Ref Oper="Draw"/>.
##
##     <P/> The attribute <C>Correspondence(P)</C> is the list of values
##     associated with the stateset of <C>P</C>.
## <Example><![CDATA[
## gap> z := Indeterminate(Rationals,"z");;
## gap> m := PostCriticalMachine(z^2);
## <Mealy machine on alphabet [ 1 ] with 2 states>
## gap> Display(m);
##    |  1
## ---+-----+
##  a | a,1
##  b | b,1
## ---+-----+
## gap> Correspondence(m);
## [ 0, infinity ]
## gap> m := PostCriticalMachine(z^2-1);; Display(m); Correspondence(m);
##    |  1
## ---+-----+
##  a | c,1
##  b | b,1
##  c | a,1
## ---+-----+
## [ -1, infinity, 0 ]
## ]]></Example>
##   </Description>
## </ManSection>
## <#/GAPDoc>
##
DeclareGlobalFunction("PostCriticalMachine");
DeclareGlobalFunction("DBRationalIMGGroup");
#############################################################################

#E img.gd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ends here
