// Intrinsics used by code from paper 12:
// _Computer aided discovery of a fast algorithm for testing conjugacy
//  in braid groups_, by Volker Gebhardt

// This is actually the file "braid.m" referred to in the paper,
// renamed to be consistent with the other papers.  There may be
// some cosmetic differences between the code here and the code
// shown in the paper.  Also, note that some extra intrinsics not
// mentioned in the paper are also included here.


// Section 2: Background: braid groups and testing conjugacy
// Subsection 2.2: Permutation braids and normal form

intrinsic MyNormalForm(x::GrpBrdElt) -> RngIntElt, SeqEnum
{ Return the infimum of x and a sequence of permutations defining the
  simple factors in the normal form of x. (x must be positive.) }

  require Id(Parent(x)) le x : "The argument must be a positive element.";
  delta := FundamentalElement(Parent(x));
  k := 0;
  seq := [];
  while not IsId(x) do
    d := GCD(x, delta);
    if d eq delta then
       k +:= 1;
     else
       Append(~seq, InducedPermutation(d)^-1);
    end if;
    x := d^-1 * x;
  end while;
  return k, seq;
end intrinsic;

// Subsection 2.3: Super summit sets and conjugacy testing

intrinsic MySuperSummitRepresentative(x::GrpBrdElt) -> GrpBrdElt, GrpBrdElt
{ Return a representative of the super summit set of x and an element
  conjugating x to this representative }

  n := NumberOfStrings(Parent(x));
  conj := Id(Parent(x));
  // maximise infimum
  count := n*(n - 1)/2 - 1;
  inf := Infimum(x);
  while count gt 0 and CanonicalLength(x) gt 0 do
    x, c := Cycle(x);  count -:= 1;
    conj := conj * c;
    if Infimum(x) gt inf then
      count := n*(n - 1)/2 - 1;
      inf := Infimum(x);
    end if;
  end while;
  // minimise supremum
  count := n*(n - 1)/2 - 1;
  sup := Supremum(x);
  while count gt 0 and CanonicalLength(x) gt 0 do
    x, c := Decycle(x);  count -:= 1;
    conj := conj * c;
    if Supremum(x) lt sup then
       count := n*(n - 1)/2 - 1;
       sup := Supremum(x);
    end if;
  end while;
  return x, conj;
end intrinsic;

intrinsic MyIsConjugate(x::GrpBrdElt, y::GrpBrdElt) -> BoolElt, RngIntElt, GrpBrdElt
{ Return whether x and y are conjugate, the number of super summit
  elements computed for the test, and an element conjugating x to y. }

  x, c_x := MySuperSummitRepresentative(x);
  S := {@ x @};
  conj := {@ c_x @};
  y, c_y := MySuperSummitRepresentative(y);
  if y eq x then
    return true, 1, c_x * c_y^-1;
  end if;
  // close S with respect to conjugation by simple elements
  pos := 1;
  while pos le #S do
    for s in { MinimalElementConjugatingToSuperSummit(S[pos], a)
               : a in Generators(Parent(x)) }
    do
      ns := LeftNormalForm(S[pos]^s);
      if ns notin S then
        Include(~S, ns);
        Include(~conj, LeftNormalForm(conj[pos]*s));
        if y eq ns then
          return true, #S, conj[pos] * s * c_y^-1;
        end if;
      end if;
    end for;
    pos +:= 1;
  end while;
  // S is closed with respect to conjugation by simple elements, that
  // is, S is the super summit set of x. Since S does not contain y,
  // the super summit sets of x and y are distinct.
  return false, #S, _;
end intrinsic;

// This next intrinsic (MyIsConjugate_Old) is not used in the paper
intrinsic MyIsConjugate_Old(x::GrpBrdElt, y::GrpBrdElt) -> BoolElt, RngIntElt
{ Return whether x and y are conjugate and the number of super summit
  elements computed for the test. }

  x := MySuperSummitRepresentative(x);
  S := {@ x @};
  y := MySuperSummitRepresentative(y);
  if y eq x then
    return true, 1;
  end if;
  // close S with respect to conjugation by simple elements
  pos := 1;
  while pos le #S do
    for s in { MinimalElementConjugatingToSuperSummit(S[pos], a)
               : a in Generators(Parent(x)) }
    do
      ns := LeftNormalForm(S[pos]^s);
      if ns notin S then
        Include(~S,ns);
        if y eq ns then
          return true, #S;
        end if;
      end if;
    end for;
    pos +:= 1;
  end while;
  // S is closed with respect to conjugation by simple elements, that
  // is, S is the super summit set of x. Since S does not contain y,
  // the super summit sets of x and y are distinct.
  return false, #S;
end intrinsic;


// Section 3: Coming across another class invariant
// Subsection 3.2: Identifying the smaller invariant

intrinsic Circuits(S::SetIndx) -> SetIndx
{ Given the super summit set S of an element x, return a set containing
  the vertex sets of the circuits of the graph induced on S by cycling. }

  C := {@ @};
  seen := [ false : s in S ];
  while exists(pos){ i : i in [1..#seen] | not seen[i] } do
    // follow trajectory until we arrive at previuosly seen element
    T := [ S | ];
    while not seen[pos] do
      seen[pos] := true;
      Append(~T, S[pos]);
      pos := Index(S, Cycle(S[pos]));
    end while;
    // if periodic part of trajectory is new, add it to C
    if S[pos] in T then
      Include(~C, {@ T[i] : i in [Index(T, S[pos])..#T] @});
    end if;
  end while;
  return C;
end intrinsic;

// Subsection 3.3: Looking for a way of computing ultra summit sets

intrinsic SatisfiesConvexity(S::SetIndx) -> BoolElt
{ Return whether a set S of braid group elements satisfies the "convexity"
  property.  S must be closed under conjugation with the fundamental element }

  B := Universe(S);
  D := [ B | s : s in Sym(NumberOfStrings(B)) ];  // simple elts.
  seen := [ false : i in [1..#S] ];
  // mark elements reachable from S[1] by a chain of elements
  //   of S linked by conjugation with simple elements
  new := { 1 };
  seen[1] := true;
  while #new gt 0 do
    ExtractRep(~new, ~i);
    u := S[i];
    for d in D do
      idx := Index(S, u^d);
      // u^d is in S if and only if idx is positive
      if idx gt 0 and not seen[idx] then
        Include(~new, idx);
        seen[idx] := true;
      end if;
    end for;
  end while;
  return forall{ i : i in [1..#S] | seen[i] };
end intrinsic;

intrinsic SatisfiesGCD(S::SetIndx) -> BoolElt
{ Return whether a set S of braid group elements satisfies the "gcd" property }

  B := Universe(S);
  D := [ B | s : s in Sym(NumberOfStrings(B)) ];  // simple elts.
  for s in S do
    for i := 1 to #D-1 do
      if s^D[i] in S then
        for j := i+1 to #D do
          if s^D[j] in S then
            if s^LeftGCD(D[i], D[j]) notin S then
              return false;
            end if;
          end if;
        end for;
      end if;
    end for;
  end for;
  return true;
end intrinsic;


// Section 4: On the way to a proof
// Subsection 4.1: Linking cycling and conjugation

intrinsic SimpleElementAdjacency(C::SetIndx) -> Mtrx
{ Given an indexed set of sets of elements of a braid group, return the
  the adjacency matrix for conjugation with simple elements }

  if #C eq 0 then
    return Matrix(Integers(), 0, 0, []);
  end if;
  B := Universe(C[1]);
  D := [ B | s : s in Sym(NumberOfStrings(B)) ];  // simple elts.
  A := Matrix(Integers(), #C, #C, [ 0 : i in [1..(#C)^2] ]);
  Ux := &join(C);
  for i := 1 to #C do
    for b in D do
      for p in C[i] do
        pb := p^b;
        if pb in Ux and exists(j){ k : k in [1..#C] | pb in C[k] } then
          A[i,j] +:= 1;
        end if;
      end for;
    end for;
  end for;
  return A;
end intrinsic;

intrinsic CheckTransport(x::GrpBrdElt, s::GrpBrdElt) -> RngIntElt
{ Return the number of simple elements t satisfying Cycle(x^s) eq Cycle(x)^t }

  B := Parent(x);
  D := [ B | s : s in Sym(NumberOfStrings(B)) ];  // simple elts.
  return #{ t : t in D | Cycle(x)^t eq Cycle(x^s) };
end intrinsic;

// Subsection 4.2: Defining the transport map

intrinsic MyTransport(x::GrpBrdElt, s::GrpBrdElt) -> GrpBrdElt
{ Return the transport of s along x -> Cycle(x).  x and x^s must be super
  summit elements }

  k := -Infimum(x);
  delta := FundamentalElement(Parent(x));
  lfx  := GCD(delta, delta^k*x);
  lfxs := GCD(delta, delta^k*(x^s));
  return (lfx^d)^-1 * s * lfxs^d where d := delta^k;
end intrinsic;

// Subsection 4.3: Proving properties of the transport map

intrinsic MovingFactors(x::GrpBrdElt, s::GrpBrdElt) -> SeqEnum
{ Given an element x = \delta^k A_1..A_r and a simple element s such that x
  and g^s are super summit elements, return a sequence [s_1,...,s_(r+1)]
  such that the i-th simple factor in the normal form of x^s is
  s_i^-1 A_i s_(i+1) }

  require IsSuperSummitRepresentative(x)
      and IsSuperSummitRepresentative(x^s) :
      "x and x^s should be super summit elements";
  B := Parent(x);
  delta := FundamentalElement(B);
  mov := [ s^d where d := delta^Infimum(x) ];
  xs := delta^(-Infimum(x)) * x^s;
  x := delta^(-Infimum(x)) * x;
  r := CanonicalLength(x);
  for i := 1 to r do
    Ai := GCD(delta, x);
    Asi := GCD(delta, xs);
    Append(~mov, NormalForm(Ai^-1 * mov[#mov] * Asi));
    x := Ai^-1 * x;
    xs := Asi^-1 * xs;
  end for;
  return mov;
end intrinsic;

intrinsic TestMovingFactors(x::GrpBrdElt) -> BoolElt
{ Return whether moving factors for conjugates of x satisfy implications
  for partial ordering and gcd }

  require IsSuperSummitRepresentative(x) :
          "x should be a super summit element";
  B := Parent(x);
  r := CanonicalLength(x);
  Dx := [ B | s : s in Sym(NumberOfStrings(B))
                | IsSuperSummitRepresentative(x^(B!s)) ];
  for s in Dx do
    for t in Dx do
      u := GCD(s, t);
      movs := MovingFactors(x, s);
      movt := MovingFactors(x, t);
      movu := MovingFactors(x, u);
      if exists{ i : i in [1..r+1] | movu[i] ne GCD(movs[i], movt[i]) } then
        return false;
      end if;
    end for;
  end for;
  return true;
end intrinsic;


// Section 5: Computing minimal simple elements

intrinsic Pullback(x::GrpBrdElt, s::GrpBrdElt) -> GrpBrdElt
{ Given a super summit element x and a simple element s, return the minimal
  simple element u such that x^u is a super summit element and s is a divisor
  of the transport of u from x to c(x). }

  B := Parent(x);
  require s in B : "Elements do not belong to a common braid group";
  LeftNormalForm(~x);
  require IsSuperSummitRepresentative(x) :
          "The first argument must be a super summit element";
  require IsSimple(s) : "The second argument must be simple";
  delta := FundamentalElement(B);
  tau := delta^Infimum(x);
  tau1 := tau*delta;
  cfp := [ B | cf : cf in CFP(x)[3] ];  // the simple factors
  A1s := cfp[1]^-1*delta;
  m := s^tau; // this should be moving into first simple factor
  // ...ensure that the product of the first simple factor of
  //    conjugate with m is simple
  u1 := (A1s^-1 * LCM(A1s, m))^t where t := tau1^-1;
  // ...and that m is a divisor of the second simple factor
  u2 := m;
  for i := 2 to #cfp do
    u2 := cfp[i]^-1 * LCM(cfp[i], u2);
  end for;
  u := MinimalElementConjugatingToSuperSummit(x, LeftLCM(u1, u2));
  return LeftNormalForm(u);
end intrinsic;


// The remaining intrinsics in this file are not part of the paper,
// but may be of interest to those wishing to experiment with Braid
// groups and ultra summit sets

intrinsic Orbit(g::GrpBrdElt, op::Intrinsic) -> SeqEnum, SeqEnum, RngIntElt
{ The orbit of g under the operation op, the conjugating elements along the
  orbit and the index of the first element in the periodic part. }

  conj := Id(Parent(g));
  Orb := [ g ];
  Conj := [ conj ];
  repeat
    g, c := op(g);
    conj *:= c;
    index := Index(Orb, g);
    Append(~Orb, g);
    Append(~Conj, conj);
  until index gt 0;
  return Orb, Conj, index;
end intrinsic;

intrinsic MyMinimalElementConjugatingToUltraSummit(x::GrpBrdElt, a::GrpBrdElt
     : Shortcut := false) -> GrpBrdElt, BoolElt
{ Given an ultra summit element x and a simple element a, return the minimal
  element c such that a le c and x^c is an ultra summit element.  If the flag
  Shortcut is set to true, the computation will be aborted if c is seen to
  be non-minimal; in this case, the function returns id, true. }

  B := Parent(x);
  require a in B : "Elements do not belong to a common braid group";
  require IsUltraSummitRepresentative(x) :
          "The first argument must be an ultra summit element";
  orb := Orbit(x, Cycle);
  require IsSimple(a) : "The second argument must be simple";
  a := MinimalElementConjugatingToSuperSummit(x, a);
  // compute periodic points under iterated transport along the
  // trajectory of x under cycling
  tran := {@ B | @};
  b := a;
  idx := 0;
  repeat
    Include(~tran, b);
    for i := 1 to #orb - 1 do
      b := Transport(orb[i], b);
      if IsId(b) then
        idx := -1;
        break;
      end if;
    end for;
    if idx eq 0 then
      idx := Index(tran, b);
    end if;
  until idx ne 0;
  if idx gt 0 then
    // case 1: if a stable point with divisor a exists, this is the
    //         desired element c
    if exists(f){ tran[i] : i in [idx..#tran] | a le tran[i] } then
      return f, false;
    end if;
    // case 2: if there is no stable point with divisor a and the iterated
    //         transport has non-trivial stable points, the desired element
    //         will not be minimal
    if Shortcut then
      return B!1, true;
    end if;
  end if;
  // case 3: pull a back along the trajectory of x until pullback becomes
  //         stable; a suitable stable point will bring us into case 1
  tran := {@ B | @};
  b := a;
  repeat
    Include(~tran, b);
    for i := #orb - 1 to 1 by -1 do
      b := Pullback(orb[i], b);
    end for;
    idx := Index(tran, b);
  until idx gt 0;
  l := #tran+1 - idx; // we want a multiple of the period length l...
  idx +:= idx mod l;  // ...this is it
  // compute stable points under iterated transport along the period
  b := tran[idx];
  tran := {@ B | @};
  repeat
    Include(~tran, b);
    for i := 1 to #orb - 1 do
      b := Transport(orb[i], b);
    end for;
    idx := Index(tran, b);
  until idx gt 0;
  if not exists(f){ tran[i] : i in [idx..#tran] | a le tran[i] } then
    assert false;  // this is a problem...
  end if;
  return f, false;
end intrinsic;

intrinsic MyUltraSummitRepresentative(x::GrpBrdElt) -> GrpBrdElt
{ Given an element x of a braid group, return an element of the ultra summit
  set of x. }

  x, c := SuperSummitRepresentative(x);
  orb, conj , i := Orbit(x, Cycle);
  return orb[i], c*conj[i];
end intrinsic;

intrinsic MyIsConjugate_Ultra(x::GrpBrdElt, y::GrpBrdElt) -> BoolElt, RngIntElt, GrpBrdElt
{ Return whether x and y are conjugate, the number of super summit
  elements computed for the test and an element conjugating x to y. }

  x, c_x := MyUltraSummitRepresentative(x);
  U := {@ x @};
  conj := {@ c_x @};
  y, c_y := MyUltraSummitRepresentative(y);
  if y eq x then
    return true, 1, c_x * c_y^-1;
  end if;
  // close U with respect to conjugation by simple elements
  pos := 1;
  while pos le #U do
    for s in { MyMinimalElementConjugatingToUltraSummit(U[pos], a)
               : a in Generators(Parent(x)) }
    do
      nu := LeftNormalForm(U[pos]^s);
      if nu notin U then
        Include(~U, nu);
        Include(~conj, LeftNormalForm(conj[pos]*s));
        if y eq nu then
          return true, #U, conj[pos] * s * c_y^-1;
        end if;
      end if;
    end for;
    pos +:= 1;
  end while;
  // U is closed with respect to conjugation by simple elements, that
  // is, U is the ultra summit set of x. Since U does not contain y,
  // the ultra summit sets of x and y are distinct.
  return false, #U, _;
end intrinsic;
