(* ------------------------------------------------------------------------- *)
(*
Unitarize

Mathematica package for testing spherical polygon inequalities
and simultaneously unitarizing sets of matrices in SL_2(C).

Provides the following functions:

  PolygonInequalities[] -- check the spherical polygon inequalities
  Unitarizable[] -- check simultaneous unitarizablility of matrices
  Unitarizer[] -- compute simultaneous unitarizer of matrices
  SL2[] -- check whether a matrix is in SL_2(C)
  SU2[] -- check whether a matrix is in SU_2

See below for complete documentation.

Nick Schmitt
July 29 2002
*)
(* ------------------------------------------------------------------------- *)

BeginPackage[
  "Unitarize`",
    {"DiscreteMath`Combinatorica`", "LinearAlgebra`MatrixManipulation`" }
]

PolygonInequalities::usage=
"PolygonInequalities[value,...] returns True if the values satisfy the
spherical polygon inequalities and False otherwise.";

SL2::usage=
"SL2[M] returns True if the matrix M is in SL_2(C) and False otherwise."

SU2::usage=
"SU2[M] returns True if the matrix M is in SU_2 and False otherwise."

Unitarizable::usage=
"Unitarizable[M1,...] returns True if the matrices M1,... in SL_2(C)
are simultaneously unitarizable and False otherwise."

Unitarizer::usage=
"Unitarizable[M1,...] returns a simultaneous unitarizer T of the
matrices M1,... in SL_2(C). T is upper triangular and has positive diagonal.
If the matrices are not simultaneously unitarizable, False is returned."

Tolerance::usage=
"An option to functions in the Unitarize package. Tolerance->ep causes checks
for equality and inequality to be performed within tolerance ep."

Verbose::usage=
"Option to PolygonInequalities[]. The option Verbose->True causes
PolygonInequalities[] to print which spherical polygon inequality first
failed.";

CheckStrict::usage=
"Option to PolygonInequalities[]. The option CheckStrict->True causes
PolygonInequalities[] to return a symbol from the list
{Fail, Strict, Static} rather than returning {False, True}.";

Fail::usage=
"Return value of PolygonInequality[] when the option CheckStrict->True is
used. Fail is returned if at least one of the spherical polygon inequalities
fails."

Strict::usage=
"Return value of PolygonInequality[] when the option CheckStrict->True is
used. Strict is returned if all the spherical polygon inequalities are
strict inequalities."

Static::usage=
"Return value of PolygonInequality[] when the option CheckStrict->True is
used. Static is returned if at least one of the spherical polygon inequalities
is a strict equality."

(* 
The DiscreteMath`Combinatorica` package defines functions M[] and V[].
These are Remove[]'d so they can be used in other contexts.
*)
Unprotect[M];
Remove[M];
Unprotect[V];
Remove[V];

(* ------------------------------------------------------------------------- *)
Begin["`Private`"]

Unitarize::tolerance=
"Tolerance is negative."

(* ------------------------------------------------------------------------- *)
(* PolygonInequalities[] *)

Options[PolygonInequalities]=
{
  Verbose->False,
  CheckStrict->False,
  Tolerance->0
};

(* list version *)
PolygonInequalities[{v__?NumberQ}, opts___?OptionQ] :=
Module[{verbose, checkstrict, tolerance, retval},

  (* parse options *)
  {verbose, checkstrict, tolerance} = {Verbose, CheckStrict, Tolerance}/.
    Flatten[{opts, Options[PolygonInequalities]}];

  If[checkstrict && tolerance != 0, Message[PolygonInequalities::ep];Return[]];

  (* turn off messages if !verbose *)
  If[!verbose,
    Off[PolygonInequalities::count, PolygonInequalities::range,
      PolygonInequalities::fail]];

  (* compute polygon inequalities *)
  retval = Catch[polygonInequalities[{v},opts]];

  (* turn on messages if !verbose *)
  If[!verbose,
    On[PolygonInequalities::count, PolygonInequalities::range,
      PolygonInequalities::fail]];

  (* return *)
  retval = If[retval === Exception, Fail, retval];
  retval = If[checkstrict,retval,If[retval===Fail,False,True]];

  Return[retval];
];

(* sequence version *)
PolygonInequalities[v__?NumberQ, opts___?OptionQ] :=
  PolygonInequalities[{v},opts];


PolygonInequalities::ep =
  "CheckStrict->True and Tolerance is not 0.";
PolygonInequalities::count =
  "Not enough input values (must be at least two).";
PolygonInequalities::range =
  "Value `1` is not in the interval [0, 1/2].";
PolygonInequalities::fail =
  "Values failed the spherical polygon inequality `1`.";

(* private list version *)
polygonInequalities[{v0__?NumberQ},opts___?OptionQ] :=
Module[{v = {v0}, tolerance, n, S, P, A, B, s, static=False},

  n = Length[v];

  (* parse options *)
  {tolerance} = {Tolerance}/.
    Flatten[{opts, Options[PolygonInequalities]}];
  If[tolerance < 0, Message[Unitarize::tolerance];Throw[Exception]];

  (* check the number of values *)
  If[n < 2, Message[PolygonInequalities::count, n]; Throw[Exception]];
  
  (* check that the values lie in the interval [0,1/2] *)
  Do[
    If[! NumberQ[v[[i]]], Message[PolygonInequalities::num, i];
      Throw[Exception]];
    If[v[[i]] > 1/2 || v[[i]] < 0, Message[PolygonInequalities::range, i];
      Throw[Exception]];
    , {i, n}];
  
  (* check the spherical polygon inequalities *)
  S = Range[n];
  Do[
    P = KSubsets[S, k];
    Do[
      A = P[[i]];
      B = Complement[S, A];
      s = Sum[v[[A[[j]]]], {j, Length[A]}] - Sum[v[[B[[j]]]], {j, Length[B]}];
      s = s - (k-1)/2;
      If[ s > tolerance,
        s = StringJoin[
            Join[
              Table["+\[Nu]" <> ToString[A[[j]]], {j, Length[A]}],
              Table["-\[Nu]" <> ToString[B[[j]]], {j, Length[B]}]
              ], " \[LessSlantEqual] ", ToString[(k - 1)/2]];
        Message[PolygonInequalities::fail, s]; Throw[Exception]];
      If[tolerance==0, static = static || (s == 0)];
    , {i, Length[P]}];
  , {k, 1, n, 2}];

  Return[ If[static, Static, Strict] ];
];

(* ------------------------------------------------------------------------- *)
(* Unitarizable[] *)

Options[Unitarizable]=
{
  Tolerance->0
};

Unitarizable::sl2=
"Matrix is not in SL_2(C).";

(* single matrix version *)
Unitarizable[{M_?MatrixQ}, opts___?OptionQ] :=
Module[{r},
  r = Catch[unitarizable[{M},opts]];
  Return[If[r === Exception,Null,r]];
];

(* list of matrices version *)
Unitarizable[{M__?MatrixQ}, opts___?OptionQ] := MatrixQ[Unitarizer[{M},opts]]

(* sequence of matrices version *)
Unitarizable[M__?MatrixQ, opts___?OptionQ] := Unitarizable[{M},opts];

(* private single matrix version *)
unitarizable[{M_?MatrixQ}, opts___?OptionQ] :=
Module[{tolerance, t},

  (* parse options *)
  {tolerance} = {Tolerance}/.
    Flatten[{opts, Options[Unitarizable]}];
  If[tolerance < 0, Message[Unitarize::tolerance];Throw[Exception]];

  (* check that the matrix are in SL_2(C) *)
  If[!SL2[M,opts],Message[Unitarizable::sl2];Throw[Exception]];

  t = Chop[Simplify[Tr[M]/2],tolerance];
  If[Im[t] != 0, Return[False]];
  If[Abs[t] == 1, Return[M==IdentityMatrix[2] || M==-IdentityMatrix[2]]];
  Return[t > -1 && t < 1];
];

(* ------------------------------------------------------------------------- *)
(* Unitarizer[] *)

Options[Unitarizer]=
{
  Tolerance->0
};

Unitarizer::sl2=
"Matrix `1` is not in SL_2(C).";
Unitarizer::fail1=
"The dimension of the kernel is >1: not implemented";

(* list of matrices version *)
Unitarizer[{M__?MatrixQ}, opts___?OptionQ] :=
Module[{r},
  r = Catch[unitarizer[{M},opts]];
  Return[If[r === Exception,Null,r]];
];

(* sequence of matrices version *)
Unitarizer[M__?MatrixQ, opts___?OptionQ] := Unitarizer[{M},opts];

(* private list of matrices version *)
unitarizer[{M0__?MatrixQ}, opts___?OptionQ] :=
Module[{M={M0}, tolerance, n, f, L, nullity, X, det},

  (* parse options *)
  {tolerance} = {Tolerance}/.
    Flatten[{opts, Options[Unitarizer]}];
  If[tolerance < 0, Message[Unitarize::tolerance];Throw[Exception]];

  n = Length[M];
  
  (* check that the matrices are in SL_2(C) *)
  Do[If[!SL2[M[[i]],opts],Message[Unitarizer::sl2,i];Throw[Exception]],{i,n}];

  (* check that each matrix is unitarizable *)
  Do[If[!Unitarizable[M[[i]],opts],f=False],{i,n}];
  If[!f,Return[False]];

  L = linearMap[M];
  L = NullSpace[L];

  nullity = Length[L];
  If[nullity==0,Return[False]];

  (* the case when the kernel dimension is >1: NOT YET IMPLEMENTED *)
  If[nullity>1,
     Message[Unitarizer::fail1];Throw[Exception];
    ];

  (* the case when the kernel dimension == 1 *)
  X = unflatten[L[[1]]];
  X = Expand[(X + star[X])/2];

  If[X === ZeroMatrix[2],
     X = unflatten[L[[1]]];
     X = Expand[(I X + star[I X])/2];
    ];
  X = Chop[X,tolerance];
  If[X[[1,1]]<0,X=-X];

  (* compute unitarizer A such that X = A* A *)
  det = Chop[Simplify[Det[X]],tolerance];
  If[det<=0,Return[False]];
  X = {X[[1]],{0,Sqrt[Det[X]]}};

  (* make Det[X]==1 *)
  det = Chop[Simplify[Det[X]],tolerance];
  X = X/Sqrt[det];

  X = Chop[X,tolerance];
  Return[X];
];

(* ------------------------------------------------------------------------- *)

Options[SL2]=
{
  Tolerance->0
};

SL2[M_?MatrixQ, opts___?OptionQ] :=
Module[{tolerance},

  (* parse options *)
  {tolerance} = {Tolerance}/.
    Flatten[{opts, Options[SL2]}];
  If[tolerance < 0, Message[Unitarize::tolerance];Throw[Exception]];

  (* check whether M is in SL_2(C) *)
  Return[ Dimensions[M]==={2,2} && Chop[Det[M]-1,tolerance]==0 ];
]

(* ------------------------------------------------------------------------- *)

Options[SU2]=
{
  Tolerance->0
};

SU2[M_?MatrixQ, opts___?OptionQ] :=
Module[{tolerance},

  (* parse options *)
  {tolerance} = {Tolerance}/.
    Flatten[{opts, Options[SU2]}];

  (* check whether M is in SU_2(C) *)
  Return[ SL2[M,opts] &&
          Chop[Expand[star[M]-Inverse[M]],tolerance] == ZeroMatrix[2] ];
]

(* ------------------------------------------------------------------------- *)
(* private utility functions *)

toLinear[M_,V_]:=
  Transpose[Table[Coefficient[M,V[[i]]],{i,1,Length[V]}]];

linearMap[M_?MatrixQ] :=
Module[{X, X11, X12, X21, X22, Mx, Q},
  X = {{X11, X12}, {X21, X22}};
  Mx = star[Inverse[M]];
  Q = X.M - Mx.X;
  Return[toLinear[Flatten[Q], Flatten[X]]];
];

linearMap[M_List]:= Flatten[Map[linearMap[#]&,M],1];

star[A_?MatrixQ]:=Transpose[ComplexExpand[Conjugate[A]]];

unflatten[X_List]:={{X[[1]],X[[2]]},{X[[3]],X[[4]]}};


End[]
EndPackage[]
