# This file was *autogenerated* from the file local_matrix_group.sage
from sage.all_cmdline import *   # import sage library
_sage_const_2 = Integer(2); _sage_const_1 = Integer(1); _sage_const_0 = Integer(0)
"""
Local Matrix Groups

This module contains several extensions to classes inherited from sage matrix group classes.
It fills in missing methods

##############################################################################
#       Copyright (C) 2016 Sebastian Oehms <[email protected]>
#
#  Distributed under the terms of the GNU General Public License (GPL)
#
#  The full text of the GPL is available at:
#
#                  http://www.gnu.org/licenses/
##############################################################################


This module contains extensions of the following classes

    sage class                                     |  extended class
-------------------------------------------------------------------------------
    UnitaryMatrixGroup_generic                     |  local_UnitaryMatrixGroup_generic
    UnitaryMatrixGroup_gap                         |  local_UnitaryMatrixGroup_gap


This module contains the following new classes

    new class                                      |  inherited from
-------------------------------------------------------------------------------
    MatrixGroup_subgroup:                          |  FinitelyGeneratedMatrixGroup_gap

AUTHOR

   - Sebastian Oehms, Sept. 2016

"""


####################################################################################################
# Extension to UnitaryMatrixGroup_gap and GU()
####################################################################################################
from sage.groups.matrix_gps.unitary import *
from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_gap
from sage.groups.libgap_wrapper import ParentLibGAP
from lib.utils_gap_interface import *



class MatrixGroup_subgroup(FinitelyGeneratedMatrixGroup_gap):
    """
    This class can be used to create subgroups of matrix_groups beeing generated by a finite list of generators in the 
    ambient group. It is an analogy to the PermutationGroup_subgroup class It can be used in cases of matrix groups
    which don't pocess a subgroup attribute.

    If you don't see this well formatted type 

    sage: print MatrixGroup_subgroup.__doc__
     
    This class has two methods overwriting the corresponding methods of FinitelyGeneratedMatrixGroup_gap class.

      - "__init__": to register the information corresponding to the ambient group

      - "_repr_:    to print a representing string containing the ambient group


    INPUT (to the constructor):

      - "ambient": the matrix group for which the subgroup shoul be craeted

      - "generators": list of elements in the ambient group generating the subgroup to be defined


    EXAMPLE:

        sage: UCF = UniversalCyclotomicField()
        sage: G = GL(2, UCF)
        sage: M = matrix( UCF, [[1, E(12)], [0,-1]] ); M
        [       1 -E(12)^7]
        [       0       -1]
        sage: S = G.subgroup([M])
        ---------------------------------------------------------------------------
        AttributeError                            Traceback (most recent call last)
        .......
        AttributeError: 'LinearMatrixGroup_generic_with_category' object has no attribute 'subgroup'
        sage: 
        sage: from lib.local_matrix_group import *
        sage: S = MatrixGroup_subgroup(G, [M])
        sage: S
        Subgroup of General Linear Group of degree 2 over Universal Cyclotomic Field generated by ([       1 -E(12)^7]
        [       0       -1],)
        sage: 

    TESTS:

        sage: UCF = UniversalCyclotomicField()
        sage: G = GL(2, UCF)
        sage: M = matrix( UCF, [[1, E(12)], [0,-1]]  )
        sage: S = MatrixGroup_subgroup(G, [M])
        sage: TestSuite(S).run()

    AUTHOR

      - Sebastian Oehms, Okt. 2016


    """

    def __init__(self, ambient, generators ):
        """
        Python constructor.

        for more information type

        sage: print MatrixGroup_subgroup.__doc__ 
    

        AUTHOR

          - Sebastian Oehms, Sept. 2016

        """

        for g in generators:
            if g not in ambient:
                raise ValueError("Generator %s is not in the group"%(g))
        gap_gens = [libgap(matrix_gen) for matrix_gen in generators]
        gap_group = libgap.Group(gap_gens)
        base_ring = ambient.base_ring()
        degree = ambient.degree()
        FinitelyGeneratedMatrixGroup_gap.__init__(self, degree, base_ring, gap_group)
        self._ambient = ambient

        

    def _repr_(self):
        """
        Return a string representation

        If you don't see this well formatted type 

        sage: print MatrixGroup_subgroup._repr_.__doc__

        OUTPUT:

        String decribing self

        TESTS:

           sage: UCF = UniversalCyclotomicField()
           sage: G = GL(2, UCF)
           sage: M = matrix( UCF, [[1, E(12)], [0,-1]] )
           sage: S = MatrixGroup_subgroup(G, [M])
           sage: S
           Subgroup of General Linear Group of degree 2 over Universal Cyclotomic Field generated by ([       1 -E(12)^7]
           [       0       -1],)
           sage: 
        """

        return "Subgroup of %s generated by:\n%s"%(self._ambient ,self.gens()) 




class local_UnitaryMatrixGroup_generic(UnitaryMatrixGroup_generic):
    """
    This class is an extension of the sage class UnitaryMatrixGroup_generic 

    If you don't see this well formatted type 

    sage: print local_UnitaryMatrixGroup_generic.__doc__

    This class contains the following additional methods

      - _conjugate
      - _adjoined

      - subgroup 
      - invariant_bilinear_form / invariant_form

    Furthermore the original method 

      - _check_matrix 
  
    has been overwritten since the original one does not behave correctly in the case of finite fields
     
    EXAMPLES:

        See the method documentation. For instance, type:

        sage: print local_UnitaryMatrixGroup_generic.subgroup.__doc__
        sage: print local_UnitaryMatrixGroup_generic.invariant_bilinea_form.__doc__
       

    TESTS:

        sage: G34 = GU(3,4)
        sage: TestSuite(G33).run()
        sage: UCF = UniversalCyclotomicField()
        sage: G3UCF = GU(3, UCF)
        sage: TestSuite(G3UCF).run()
        sage: h=matrix(UCF, 3,3,[[0,0,1],[0,1,0],[1,0,0]] )
        sage: G3h = GU(3, UCF, hermitian_form=h)
        sage: TestSuite(G3h).run()
           

    AUTHOR

      - Sebastian Oehms, Sept. 2016

    """

    _hermitian_form_ = None


    def subgroup(self, generators):
        """
        Return the subgroup generated by the given generators

        If you don't see this well formatted type 

        sage: print local_UnitaryMatrixGroup_generic.subgroup.__doc__

        In case where self is an instance of ParentLibGAP this method is identical to the corresponding method of that class.
        In all other cases an instance of the class MatrixGroup_subgroup is returned

        INPUT:

        - ``generators`` -- a list/tuple/iterable of group elements of self

        OUTPUT:

          The subgroup generated by ``generators`` as an in instance of

          - FinitelyGeneratedMatrixGroup_gap  if self is an instance of ParentLibGAP

          - MatrixGroup_subgroup  elsewise

        EAMPLE:

         sage: from lib.local_matrix_group import *
         sage: UCF = UniversalCyclotomicField()
         sage: M = matrix( UCF, [[0, 1], [1,0]] )
         sage: GU2=GU(2,UCF, hermitian_form=M); GU2
         General Unitary Group of degree 2 over Universal Cyclotomic Field with respect to hermitian form [0 1]
         [1 0]
         sage: S = GU2.subgroup( [M] ); S
         Subgroup of General Unitary Group of degree 2 over Universal Cyclotomic Field with respect to hermitian form [0 1]
         [1 0] generated by ([0 1]
         [1 0],)
         sage: isinstance( GU3, ParentLibGAP)
         False
         sage: isinstance( S, ParentLibGAP)
         True
         sage: isinstance( S, MatrixGroup_subgroup )
         True
         sage: 

        TESTS:

           see the tests for the class
           
        AUTHOR:

          - Sebastian Oehms, Sept. 2016


        """

        if isinstance( self, ParentLibGAP ):
            try:
                SubGroup = ParentLibGAP.subgroup( self, generators )
            except:         
                SubGroup = MatrixGroup_subgroup(self, generators )
        else:
            SubGroup = MatrixGroup_subgroup(self, generators )
        return SubGroup


    def __set_hermitian_form__(self, hermitian_form ):
        """
        This method registers the hermitian form to which should be kept invariant be self
        """
        self._hermitian_form_ = hermitian_form



 

#    @cached_method
    def invariant_bilinear_form(self):
        """
        Return the sesquilinear form preserved by the unitary group. 

        If you don't see this well formatted type 

        sage: print local_UnitaryMatrixGroup_generic.invariant_bilinear_form.__doc__

        If a hermitian_form has been set explicitely by the method __set_hermitian_form__ it will be returned.
        Elsewise the gap function "InvariantSesquilinearForm" is used.

        Compare the corresponding methods with respect to the orthogonal groups

        OUTPUT:

           the hermitian form as a matrix with entries in the base_ring 

        EXAMPLES:

           sage: GU25 = GU(2,5)
           sage: GU52.invariant_bilinear_form()
           [0 0 0 0 1]
           [0 0 0 1 0]
           [0 0 1 0 0]
           [0 1 0 0 0]
           [1 0 0 0 0]
           sage: GU3Q = GU(3,QQ)
           sage: GU3Q.invariant_bilinear_form()
           [1 0 0]
           [0 1 0]
           [0 0 1]


        TESTS:

           see the tests for the class
           
        AUTHOR

          - Sebastian Oehms, Sept. 2016

        """
        if self._hermitian_form_ != None:
            return self._hermitian_form_

        if self.degree() > _sage_const_1 :
            try:
                m = self.gap().InvariantSesquilinearForm()['matrix'].matrix()
            except AttributeError:
                m = self.one().matrix()
        else:
            m = matrix( _sage_const_1 ,_sage_const_1 , [_sage_const_1 ] )
        m.set_immutable()
        return m



#    @cached_method
    def invariant_form(self):
        """
        Return the sesquilinear form preserved by the unitary group. Compare the corresponding methods with respect to the 
        symplectic groups. It is identical to invariant_bilinear_form (duplicate names in sage). For more information type

        sage: print local_UnitaryMatrixGroup_generic.invariant_bilinear_form.__doc__
        """

        return self.invariant_bilinear_form()



    def _conjugate(self, mat):
        """
        This internal method calculates the elementwise conjugate of the matrix mat
        
        INPUT:

          - mat matrix with entries from the base_ring of self

        OUTPUT:

          - the conjugate of mat as matrix of the same type

        EXAMPLES:

            sage: from lib.local_matrix_group import *
            sage: GU52 = GU(5,2)
            sage: gl = GU52.some_elements()
            sage: g = gl[0]; g
            [1 0 0 0 0]
            [0 a 0 0 0]
            [0 0 1 0 0]
            [0 0 0 a 0]
            [0 0 0 0 1]
            sage: GU52._conjugate( g.matrix() )
            [    1     0     0     0     0]
            [    0 a + 1     0     0     0]
            [    0     0     1     0     0]
            [    0     0     0 a + 1     0]
            [    0     0     0     0     1]
            sage:  

        TESTS:

           see the tests for the class
           
        AUTHOR

          - Sebastian Oehms, Sept. 2016
  
        """
        if isinstance(self.base_ring(), UniversalCyclotomicField):
            # Note: UniversalCyclotomicField.is_finite() gives True, a bug?
            result = mat.conjugate()
        elif finite_field_sqrt( self.base_ring() ):
            matconj = mat.dict()
            for ind in matconj.keys():
                matconj.update({ind:matconj[ind].frobenius()})
            result = matrix(matconj)
        else:
            result = mat.conjugate()
        return result

    def _adjoint(self, mat):
        """
        Return the adjoint (conjugate and transpose) matrix of mat. 
        
        INPUT:

          - mat matrix with entries from the base_ring of self

        OUTPUT:

          - the conjugate and transpose of mat as matrix of the same type

        EXAMPLES:

            sage: from lib.local_matrix_group import *
            sage: GU52 = GU(5,2)
            sage: gl = GU52.gens()
            sage: g = gl[1]; g
            [0 1 0 0 0]
            [a 0 1 0 1]
            [1 0 1 0 0]
            [1 0 0 0 0]
            [0 0 0 1 0]
            sage: GU52._adjoint( g.matrix() )
            [    0 a + 1     1     1     0]
            [    1     0     0     0     0]
            [    0     1     1     0     0]
            [    0     0     0     0     1]
            [    0     1     0     0     0]
            sage: 

        TESTS:

           see the tests for the class
           
        AUTHOR

          - Sebastian Oehms, Sept. 2016

        """
        result = self._conjugate(mat.transpose())
        return result
      

    def _check_matrix(self, x, *args):
        """
        Check whether the matrix ``x`` is unitary. This method overwrites the original sage-method with the same name. In 
        addition to it
 
           - it takes into acount a special user defined hermitian form
           - it works well in the case of a finite field (which is not the case for the original version)

        If you don't see this well formatted type 

        sage: print local_UnitaryMatrixGroup_generic._check_matrix.__doc__

        To read the original docstring type:
 
        sage: print sage.groups.matrix_gps.unitary.UnitaryMatrixGroup_generic._check_matrix.__doc__

        Description of the bug wich should be avoided by this variation of the original method:

        If an element of a finite unitary group is converted to the underlying matrix class it can not be converted
        back to the unitary group since the method unitary for the matrix gives false (see example below)
        Note: the unitary method attached to the matrix is not changed. It continues to returns false for elements of finite
        unitary groups. 
        

        INPUT:

          same as for the original method

        OUTPUT:

          same as for the original method

        EXAMPLES: 

         First try with the original method to convert an element of a unitary group to a matrix and back:
          
          sage: G32=GU(3,2)
          sage: g1, g2 =G32.gens()
          sage: g1m = g1.matrix(); g1m
          [a 0 0]
          [0 1 0]
          [0 0 a]
          sage: g1m.is_unitary()
          False
          sage: g1m in G32
          False

         Now, do the same thing using local_UnitaryMatrixGroup_generic._check_matrix.__doc__:

          sage: from lib.local_matrix_group import *
          sage: G32=GU(3,2)
          sage: g1, g2 = G32.gens()
          sage: g1m=g1.matrix()
          sage: g1m.is_unitary()
          False
          sage: g1m in G32
          True

        TESTS:

           see the tests for the class
           

        AUTHOR

          - Sebastian Oehms, Sept. 2016

        """

        try:
            finite_field = finite_field_sqrt( self.base_ring() )
        except:
            finite_field = False

        def check_unitary( self, x ):
            """
            This function supports an alternative check to x.is_unitary() 
            for finite fields or explicit hermitian form
            """
            if self._hermitian_form_ != None or finite_field:

                xadjoint = self._adjoint( x )
                F = self.invariant_bilinear_form()
                xadFx = xadjoint * F * x
                result = ( F == xadFx )
                if result == True:
                    return result

                # if result is not True this may be caused by differnt types
 
                try:
                    Fmat = F.matrix()
                except AttributeError:
                    Fmat = F
                try:
                    xadFxmat = xadFx.matrix()
                except AttributeError:
                    xadFxmat = xadFx
                diff = Fmat - xadFxmat
                result = diff.is_zero()
                return result

            else:

                return x.is_unitary()
            

        if self._special and x.determinant() != _sage_const_1 :
            raise TypeError('matrix must have determinant one')
        if not check_unitary(self, x ):
            raise TypeError('matrix must be unitary')


    # -------------------------------------------------------------------------------
    # Methods for test_suite 
    # -------------------------------------------------------------------------------
    def _test_extensions(self, **options):
        """
        Method called by TestSuite

        If you don't see this well formatted type 

        sage: print CubicBraidGroup_class._test_constructions.__doc__

        the following is checked:
           - if _check_matrix works
           - if subgroup works

        AUTHOR

          - Sebastian Oehms, Sept. 2016

        """

        #------------------------------------------------------------------------------------
        # Test _check_matrix
        #------------------------------------------------------------------------------------
        try:
            genList = self.gens()
        except AttributeError:
            genList = self.some_elements()
        g = genList[_sage_const_0 ]
        gm = g.matrix()
        test_check_matrix = self._tester(**options)
        test_check_matrix.assert_( gm in self )

        #------------------------------------------------------------------------------------
        # Test subgroup
        #------------------------------------------------------------------------------------
        SubGroup = self.subgroup( [gm] )
        test_subgroup = self._tester(**options)
        test_subgroup.assert_( SubGroup.ambient() == self )




class local_UnitaryMatrixGroup_gap(local_UnitaryMatrixGroup_generic, NamedMatrixGroup_gap):
    pass


###############################################################################
# General Unitary Group
###############################################################################

def GU(n, R, var='a', hermitian_form=None ):
    """ 
    This function overwrites the sage-Funtion with the same name. It uses the extended classes 
    local_UnitaryMatrixGroup_gap and local_UnitaryMatrixGroup_generic in stead of the original classes 
    UnitaryMatrixGroup_gap and UnitaryMatrixGroup_generic additional features are:
  
        - use a special hermitain form via keyword-paremeter: "hermitian_form= ..."
        - ask for the hermitian form to which GU is related via method "invariant_form"
        - define a subgroup of GU to a list of generators via method "subgroup"

    to read to original docstring of GU type:
 
    sage print sage.groups.matrix_gps.unitary.GU.__doc__ 

    EXAMPLES:

      sage: from lib.local_matrix_group import *
      sage: K=UniversalCyclotomicField()
      sage: h=matrix(K, 3,3,[[0,0,1],[0,1,0],[1,0,0]] )
      sage: GU3h=GU(3, K, hermitian_form=h); GU3h
      Unitary Group of degree 3 over Universal Cyclotomic Field with respect to hermitian form
      [0 0 1]
      [0 1 0]
      [1 0 0]
      sage: GU3=GU(3, K); GU3
      General Unitary Group of degree 3 over Universal Cyclotomic Field
      sage: GU3 == GU3h
      False
      sage: x=matrix(K, 3,3,[[0,1,0],[0,0,1],[1,0,0]] ); x
      [0 1 0]
      [0 0 1]
      [1 0 0]
      sage: x in GU3h
      False
      sage: x in GU3
      True

    AUTHOR

      - Sebastian Oehms, Sept. 2016    

    """

    degree, ring = normalize_args_vectorspace(n, R, var=var)
    if is_FiniteField(ring):
        q = ring.cardinality()
        if var not in ring:
            ring = GF(q ** _sage_const_2 , name=var)
    if hermitian_form == None:
        name = 'General Unitary Group of degree {0} over {1}'.format(degree, ring)
        ltx  = r'\text{{GU}}_{{{0}}}({1})'.format(degree, latex(ring))
    else:
        name = 'Unitary Group of degree {0} over {1} with respect to hermitian form\n{2}'.format(degree, ring, hermitian_form)
        ltx  = r'\text{{GU}}_{{{0}}}({1},{2})'.format(degree, latex(ring), latex(hermitian_form))

    if is_FiniteField(ring):
        # unitary groups in gap are defined for finite fields only
        cmd  = 'GU({0}, {1})'.format(degree, q)
        UnitaryGroup = local_UnitaryMatrixGroup_gap(degree, ring, False, name, ltx, cmd )
    else:
        UnitaryGroup = local_UnitaryMatrixGroup_generic(degree, ring, False, name, ltx )

    UnitaryGroup.__set_hermitian_form__(hermitian_form)

    return UnitaryGroup