from sage.misc.cachefunc import cached_method
from sage.combinat.free_module import CombinatorialFreeModule
from sage.modules.with_basis.indexed_element import IndexedFreeModuleElement
from sage.rings.all import AA

from mjo.matrix_algebra import MatrixAlgebra, MatrixAlgebraElement

class Octonion(IndexedFreeModuleElement):
    def conjugate(self):
        r"""
        SETUP::

            sage: from mjo.hurwitz import Octonions

        EXAMPLES::

            sage: O = Octonions()
            sage: x = sum(O.gens())
            sage: x.conjugate()
            e0 - e1 - e2 - e3 - e4 - e5 - e6 - e7

        TESTS::

        Conjugating twice gets you the original element::

            sage: O = Octonions()
            sage: x = O.random_element()
            sage: x.conjugate().conjugate() == x
            True

        """
        from sage.rings.all import ZZ
        from sage.matrix.matrix_space import MatrixSpace
        C = MatrixSpace(ZZ,8).diagonal_matrix((1,-1,-1,-1,-1,-1,-1,-1))
        return self.parent().from_vector(C*self.to_vector())

    def real(self):
        r"""
        Return the real part of this octonion.

        The real part of an octonion is its projection onto the span
        of the first generator. In other words, the "first dimension"
        is real and the others are imaginary.

        SETUP::

            sage: from mjo.hurwitz import Octonions

        EXAMPLES::

            sage: O = Octonions()
            sage: x = sum(O.gens())
            sage: x.real()
            e0

        TESTS:

        This method is idempotent::

            sage: O = Octonions()
            sage: x = O.random_element()
            sage: x.real().real() == x.real()
            True

        """
        return (self + self.conjugate())/2

    def imag(self):
        r"""
        Return the imaginary part of this octonion.

        The imaginary part of an octonion is its projection onto the
        orthogonal complement of the span of the first generator. In
        other words, the "first dimension" is real and the others are
        imaginary.

        SETUP::

            sage: from mjo.hurwitz import Octonions

        EXAMPLES::

            sage: O = Octonions()
            sage: x = sum(O.gens())
            sage: x.imag()
            e1 + e2 + e3 + e4 + e5 + e6 + e7

        TESTS:

        This method is idempotent::

            sage: O = Octonions()
            sage: x = O.random_element()
            sage: x.imag().imag() == x.imag()
            True

        """
        return (self - self.conjugate())/2

    def _norm_squared(self):
        return (self*self.conjugate()).coefficient(0)

    def norm(self):
        r"""
        Return the norm of this octonion.

        SETUP::

            sage: from mjo.hurwitz import Octonions

        EXAMPLES::

            sage: O = Octonions()
            sage: O.one().norm()
            1

        TESTS:

        The norm is nonnegative and belongs to the base field::

            sage: O = Octonions()
            sage: n = O.random_element().norm()
            sage: n >= 0 and n in O.base_ring()
            True

        The norm is homogeneous::

            sage: O = Octonions()
            sage: x = O.random_element()
            sage: alpha = O.base_ring().random_element()
            sage: (alpha*x).norm() == alpha.abs()*x.norm()
            True

        """
        return self._norm_squared().sqrt()

    # The absolute value notation is typically used for complex numbers...
    # and norm() isn't supported in AA, so this lets us use abs() in all
    # of the division algebras we need.
    abs = norm

    def inverse(self):
        r"""
        Return the inverse of this element if it exists.

        SETUP::

            sage: from mjo.hurwitz import Octonions

        EXAMPLES::

            sage: O = Octonions()
            sage: x = sum(O.gens())
            sage: x*x.inverse() == O.one()
            True

        ::

            sage: O = Octonions()
            sage: O.one().inverse() == O.one()
            True

        TESTS::

            sage: O = Octonions()
            sage: x = O.random_element()
            sage: x.is_zero() or ( x*x.inverse() == O.one() )
            True

        """
        if self.is_zero():
            raise ValueError("zero is not invertible")
        return self.conjugate()/self._norm_squared()



class Octonions(CombinatorialFreeModule):
    r"""
    SETUP::

        sage: from mjo.hurwitz import Octonions

    EXAMPLES::

        sage: Octonions()
        Octonion algebra with base ring Algebraic Real Field
        sage: Octonions(field=QQ)
        Octonion algebra with base ring Rational Field

    """
    def __init__(self,
                 field=AA,
                 prefix="e"):

        # Not associative, not commutative
        from sage.categories.magmatic_algebras import MagmaticAlgebras
        category = MagmaticAlgebras(field).FiniteDimensional()
        category = category.WithBasis().Unital()

        super().__init__(field,
                         range(8),
                         element_class=Octonion,
                         category=category,
                         prefix=prefix,
                         bracket=False)

        # The product of each basis element is plus/minus another
        # basis element that can simply be looked up on
        # https://en.wikipedia.org/wiki/Octonion
        e0, e1, e2, e3, e4, e5, e6, e7 = self.gens()
        self._multiplication_table = (
            (e0, e1, e2, e3, e4, e5, e6, e7),
            (e1,-e0, e3,-e2, e5,-e4,-e7, e6),
            (e2,-e3,-e0, e1, e6, e7,-e4,-e5),
            (e3, e2,-e1,-e0, e7,-e6, e5,-e4),
            (e4,-e5,-e6,-e7,-e0, e1, e2, e3),
            (e5, e4,-e7, e6,-e1,-e0,-e3, e2),
            (e6, e7, e4,-e5,-e2, e3,-e0,-e1),
            (e7,-e6, e5, e4,-e3,-e2, e1,-e0),
        )

    def product_on_basis(self, i, j):
        return self._multiplication_table[i][j]

    def one_basis(self):
        r"""
        Return the monomial index (basis element) corresponding to the
        octonion unit element.

        SETUP::

            sage: from mjo.hurwitz import Octonions

        TESTS:

        This gives the correct unit element::

            sage: O = Octonions()
            sage: x = O.random_element()
            sage: x*O.one() == x and O.one()*x == x
            True

        """
        return 0

    def _repr_(self):
        return ("Octonion algebra with base ring %s" % self.base_ring())

    def multiplication_table(self):
        """
        Return a visual representation of this algebra's multiplication
        table (on basis elements).

        SETUP::

            sage: from mjo.hurwitz import Octonions

        EXAMPLES:

        The multiplication table is what Wikipedia says it is::

            sage: Octonions().multiplication_table()
            ┌────╥────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
            │ *  ║ e0 │ e1  │ e2  │ e3  │ e4  │ e5  │ e6  │ e7  │
            ╞════╫════╪═════╪═════╪═════╪═════╪═════╪═════╪═════╡
            │ e0 ║ e0 │ e1  │ e2  │ e3  │ e4  │ e5  │ e6  │ e7  │
            ├────╫────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
            │ e1 ║ e1 │ -e0 │ e3  │ -e2 │ e5  │ -e4 │ -e7 │ e6  │
            ├────╫────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
            │ e2 ║ e2 │ -e3 │ -e0 │ e1  │ e6  │ e7  │ -e4 │ -e5 │
            ├────╫────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
            │ e3 ║ e3 │ e2  │ -e1 │ -e0 │ e7  │ -e6 │ e5  │ -e4 │
            ├────╫────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
            │ e4 ║ e4 │ -e5 │ -e6 │ -e7 │ -e0 │ e1  │ e2  │ e3  │
            ├────╫────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
            │ e5 ║ e5 │ e4  │ -e7 │ e6  │ -e1 │ -e0 │ -e3 │ e2  │
            ├────╫────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
            │ e6 ║ e6 │ e7  │ e4  │ -e5 │ -e2 │ e3  │ -e0 │ -e1 │
            ├────╫────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
            │ e7 ║ e7 │ -e6 │ e5  │ e4  │ -e3 │ -e2 │ e1  │ -e0 │
            └────╨────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘

        """
        n = self.dimension()
        # Prepend the header row.
        M = [["*"] + list(self.gens())]

        # And to each subsequent row, prepend an entry that belongs to
        # the left-side "header column."
        M += [ [self.monomial(i)] + [ self.monomial(i)*self.monomial(j)
                                    for j in range(n) ]
               for i in range(n) ]

        from sage.misc.table import table
        return table(M, header_row=True, header_column=True, frame=True)





class HurwitzMatrixAlgebraElement(MatrixAlgebraElement):
    def conjugate(self):
        r"""
        Return the entrywise conjugate of this matrix.

        SETUP::

            sage: from mjo.hurwitz import ComplexMatrixAlgebra

        EXAMPLES::

            sage: A = ComplexMatrixAlgebra(2, QQbar, ZZ)
            sage: M = A([ [ I,   1 + 2*I],
            ....:         [ 3*I,     4*I] ])
            sage: M.conjugate()
            ┌──────┬──────────┐
            │ -I   │ -2*I + 1 │
            ├──────┼──────────┤
            │ -3*I │ -4*I     │
            └──────┴──────────┘

        ::

            sage: A = ComplexMatrixAlgebra(2, QQbar, QQ)
            sage: M = A([ [ 1, 2],
            ....:         [ 3, 4] ])
            sage: M.conjugate() == M
            True
            sage: M.to_vector()
            (1, 0, 2, 0, 3, 0, 4, 0)

        """
        d = self.monomial_coefficients()
        A = self.parent()
        new_terms = ( A._conjugate_term((k,v)) for (k,v) in d.items() )
        return self.parent().sum_of_terms(new_terms)

    def conjugate_transpose(self):
        r"""
        Return the conjugate-transpose of this matrix.

        SETUP::

            sage: from mjo.hurwitz import ComplexMatrixAlgebra

        EXAMPLES::

            sage: A = ComplexMatrixAlgebra(2, QQbar, ZZ)
            sage: M = A([ [ I,   2*I],
            ....:         [ 3*I, 4*I] ])
            sage: M.conjugate_transpose()
            ┌──────┬──────┐
            │ -I   │ -3*I │
            ├──────┼──────┤
            │ -2*I │ -4*I │
            └──────┴──────┘
            sage: M.conjugate_transpose().to_vector()
            (0, -1, 0, -3, 0, -2, 0, -4)

        """
        d = self.monomial_coefficients()
        A = self.parent()
        new_terms = ( A._conjugate_term( ((k[1],k[0],k[2]), v) )
                      for (k,v) in d.items() )
        return self.parent().sum_of_terms(new_terms)

    def is_hermitian(self):
        r"""

        SETUP::

            sage: from mjo.hurwitz import (ComplexMatrixAlgebra,
            ....:                          HurwitzMatrixAlgebra)

        EXAMPLES::

            sage: A = ComplexMatrixAlgebra(2, QQbar, ZZ)
            sage: M = A([ [ 0,I],
            ....:         [-I,0] ])
            sage: M.is_hermitian()
            True

        ::

            sage: A = ComplexMatrixAlgebra(2, QQbar, ZZ)
            sage: M = A([ [ 0,0],
            ....:         [-I,0] ])
            sage: M.is_hermitian()
            False

        ::

            sage: A = HurwitzMatrixAlgebra(2, AA, QQ)
            sage: M = A([ [1, 1],
            ....:         [1, 1] ])
            sage: M.is_hermitian()
            True

        """
        # A tiny bit faster than checking equality with the conjugate
        # transpose.
        return all( self[i,j] == self[j,i].conjugate()
                    for i in range(self.nrows())
                    for j in range(i+1) )


    def is_skew_symmetric(self):
        r"""
        Return whether or not this matrix is skew-symmetric.

        SETUP::

            sage: from mjo.hurwitz import (ComplexMatrixAlgebra,
            ....:                          HurwitzMatrixAlgebra)

        EXAMPLES::

            sage: A = ComplexMatrixAlgebra(2, QQbar, ZZ)
            sage: M = A([ [ 0,I],
            ....:         [-I,1] ])
            sage: M.is_skew_symmetric()
            False

        ::

            sage: A = ComplexMatrixAlgebra(2, QQbar, ZZ)
            sage: M = A([ [   0, 1+I],
            ....:         [-1-I,   0] ])
            sage: M.is_skew_symmetric()
            True

        ::

            sage: A = HurwitzMatrixAlgebra(2, AA, QQ)
            sage: M = A([ [1, 1],
            ....:         [1, 1] ])
            sage: M.is_skew_symmetric()
            False

        ::

            sage: A = ComplexMatrixAlgebra(2, QQbar, ZZ)
            sage: M = A([ [2*I   ,  1 + I],
            ....:         [-1 + I, -2*I] ])
            sage: M.is_skew_symmetric()
            False

        """
        # A tiny bit faster than checking equality with the negation
        # of the transpose.
        return all( self[i,j] == -self[j,i]
                    for i in range(self.nrows())
                    for j in range(i+1) )


class HurwitzMatrixAlgebra(MatrixAlgebra):
    r"""
    A class of matrix algebras whose entries come from a Hurwitz
    algebra.

    For our purposes, we consider "a Hurwitz" algebra to be the real
    or complex numbers, the quaternions, or the octonions. These are
    typically also referred to as the Euclidean Hurwitz algebras, or
    the normed division algebras.

    By the Cayley-Dickson construction, each Hurwitz algebra is an
    algebra over the real numbers, so we restrict the scalar field in
    this case to be real. This also allows us to more accurately
    produce the generators of the matrix algebra.
    """
    Element = HurwitzMatrixAlgebraElement

    def __init__(self, n, entry_algebra, scalars, **kwargs):
        from sage.rings.all import RR
        if not scalars.is_subring(RR):
            # Not perfect, but it's what we're using.
            raise ValueError("scalar field is not real")

        super().__init__(n, entry_algebra, scalars, **kwargs)


    @staticmethod
    def _conjugate_term(t):
        r"""
        Conjugate the given ``(index, coefficient)`` term, returning
        another such term.

        Given a term ``((i,j,e), c)``, it's straightforward to
        conjugate the entry ``e``, but if ``e``-conjugate is ``-e``,
        then the resulting ``((i,j,-e), c)`` is not a term, since
        ``(i,j,-e)`` is not a monomial index! So when we build a sum
        of these conjugates we can wind up with a nonsense object.

        This function handles the case where ``e``-conjugate is
        ``-e``, but nothing more complicated. Thus it makes sense in
        Hurwitz matrix algebras, but not more generally.

        SETUP::

            sage: from mjo.hurwitz import ComplexMatrixAlgebra

        EXAMPLES::

            sage: A = ComplexMatrixAlgebra(2, QQbar, ZZ)
            sage: M = A([ [ I,   1 + 2*I],
            ....:         [ 3*I,     4*I] ])
            sage: t = list(M.monomial_coefficients().items())[1]
            sage: t
            ((1, 0, I), 3)
            sage: A._conjugate_term(t)
            ((1, 0, I), -3)

        """
        if t[0][2].conjugate() == t[0][2]:
            return t
        else:
            return (t[0], -t[1])


    def entry_algebra_gens(self):
        r"""
        Return a tuple of the generators of (that is, a basis for) the
        entries of this matrix algebra.

        This works around the inconsistency in the ``gens()`` methods
        of the real/complex numbers, quaternions, and octonions.

        SETUP::

            sage: from mjo.hurwitz import Octonions, HurwitzMatrixAlgebra

        EXAMPLES:

        The inclusion of the unit element is inconsistent across
        (subalgebras of) Hurwitz algebras::

            sage: AA.gens()
            (1,)
            sage: QQbar.gens()
            (I,)
            sage: QuaternionAlgebra(AA,1,-1).gens()
            (i, j, k)
            sage: Octonions().gens()
            (e0, e1, e2, e3, e4, e5, e6, e7)

        The unit element is always returned by this method, so the
        sets of generators have cartinality 1,2,4, and 8 as you'd
        expect::

            sage: HurwitzMatrixAlgebra(2, AA, AA).entry_algebra_gens()
            (1,)
            sage: HurwitzMatrixAlgebra(2, QQbar, AA).entry_algebra_gens()
            (1, I)
            sage: Q = QuaternionAlgebra(AA,-1,-1)
            sage: HurwitzMatrixAlgebra(2, Q, AA).entry_algebra_gens()
            (1, i, j, k)
            sage: O = Octonions()
            sage: HurwitzMatrixAlgebra(2, O, AA).entry_algebra_gens()
            (e0, e1, e2, e3, e4, e5, e6, e7)

        """
        gs = self.entry_algebra().gens()
        one = self.entry_algebra().one()
        if one in gs:
            return gs
        else:
            return (one,) + tuple(gs)



class OctonionMatrixAlgebra(HurwitzMatrixAlgebra):
    r"""
    The algebra of ``n``-by-``n`` matrices with octonion entries over
    (a subfield of) the real numbers.

    The usual matrix spaces in SageMath don't support octonion entries
    because they assume that the entries of the matrix come from a
    commutative and associative ring, and the octonions are neither.

    SETUP::

        sage: from mjo.hurwitz import Octonions, OctonionMatrixAlgebra

    EXAMPLES::

        sage: OctonionMatrixAlgebra(3)
        Module of 3 by 3 matrices with entries in Octonion algebra with base
        ring Algebraic Real Field over the scalar ring Algebraic Real Field

    ::

        sage: OctonionMatrixAlgebra(3,scalars=QQ)
        Module of 3 by 3 matrices with entries in Octonion algebra with
        base ring Rational Field over the scalar ring Rational Field

    ::

        sage: O = Octonions(RR)
        sage: A = OctonionMatrixAlgebra(1,O)
        sage: A
        Module of 1 by 1 matrices with entries in Octonion algebra with
        base ring Real Field with 53 bits of precision over the scalar
        ring Algebraic Real Field
        sage: A.one()
        ┌─────────────────────┐
        │ 1.00000000000000*e0 │
        └─────────────────────┘
        sage: A.gens()
        (┌─────────────────────┐
         │ 1.00000000000000*e0 │
         └─────────────────────┘,
         ┌─────────────────────┐
         │ 1.00000000000000*e1 │
         └─────────────────────┘,
         ┌─────────────────────┐
         │ 1.00000000000000*e2 │
         └─────────────────────┘,
         ┌─────────────────────┐
         │ 1.00000000000000*e3 │
         └─────────────────────┘,
         ┌─────────────────────┐
         │ 1.00000000000000*e4 │
         └─────────────────────┘,
         ┌─────────────────────┐
         │ 1.00000000000000*e5 │
         └─────────────────────┘,
         ┌─────────────────────┐
         │ 1.00000000000000*e6 │
         └─────────────────────┘,
         ┌─────────────────────┐
         │ 1.00000000000000*e7 │
         └─────────────────────┘)

    ::

        sage: A = OctonionMatrixAlgebra(2)
        sage: e0,e1,e2,e3,e4,e5,e6,e7 = A.entry_algebra().gens()
        sage: A([ [e0+e4, e1+e5],
        ....:     [e2-e6, e3-e7] ])
        ┌─────────┬─────────┐
        │ e0 + e4 │ e1 + e5 │
        ├─────────┼─────────┤
        │ e2 - e6 │ e3 - e7 │
        └─────────┴─────────┘

    ::

        sage: A1 = OctonionMatrixAlgebra(1,scalars=QQ)
        sage: A2 = OctonionMatrixAlgebra(1,scalars=QQ)
        sage: cartesian_product([A1,A2])
        Module of 1 by 1 matrices with entries in Octonion algebra with
        base ring Rational Field over the scalar ring Rational Field (+)
        Module of 1 by 1 matrices with entries in Octonion algebra with
        base ring Rational Field over the scalar ring Rational Field

    TESTS::

        sage: A = OctonionMatrixAlgebra(ZZ.random_element(10))
        sage: x = A.random_element()
        sage: x*A.one() == x and A.one()*x == x
        True

    """
    def __init__(self, n, entry_algebra=None, scalars=AA, **kwargs):
        if entry_algebra is None:
            entry_algebra = Octonions(field=scalars)
        super().__init__(n,
                         entry_algebra,
                         scalars,
                         **kwargs)

class QuaternionMatrixAlgebra(HurwitzMatrixAlgebra):
    r"""
    The algebra of ``n``-by-``n`` matrices with quaternion entries over
    (a subfield of) the real numbers.

    The usual matrix spaces in SageMath don't support quaternion entries
    because they assume that the entries of the matrix come from a
    commutative ring, and the quaternions are not commutative.

    SETUP::

        sage: from mjo.hurwitz import QuaternionMatrixAlgebra

    EXAMPLES::

        sage: QuaternionMatrixAlgebra(3)
        Module of 3 by 3 matrices with entries in Quaternion
        Algebra (-1, -1) with base ring Algebraic Real Field
        over the scalar ring Algebraic Real Field

    ::

        sage: QuaternionMatrixAlgebra(3,scalars=QQ)
        Module of 3 by 3 matrices with entries in Quaternion
        Algebra (-1, -1) with base ring Rational Field over
        the scalar ring Rational Field

    ::

        sage: Q = QuaternionAlgebra(RDF, -1, -1)
        sage: A = QuaternionMatrixAlgebra(1,Q)
        sage: A
        Module of 1 by 1 matrices with entries in Quaternion Algebra
        (-1.0, -1.0) with base ring Real Double Field over the scalar
        ring Algebraic Real Field
        sage: A.one()
        ┌─────┐
        │ 1.0 │
        └─────┘
        sage: A.gens()
        (┌─────┐
         │ 1.0 │
         └─────┘,
         ┌───┐
         │ i │
         └───┘,
         ┌───┐
         │ j │
         └───┘,
         ┌───┐
         │ k │
         └───┘)

    ::

        sage: A = QuaternionMatrixAlgebra(2)
        sage: i,j,k = A.entry_algebra().gens()
        sage: A([ [1+i, j-2],
        ....:     [k,   k+j] ])
        ┌───────┬────────┐
        │ 1 + i │ -2 + j │
        ├───────┼────────┤
        │ k     │ j + k  │
        └───────┴────────┘

    ::

        sage: A1 = QuaternionMatrixAlgebra(1,scalars=QQ)
        sage: A2 = QuaternionMatrixAlgebra(2,scalars=QQ)
        sage: cartesian_product([A1,A2])
        Module of 1 by 1 matrices with entries in Quaternion Algebra
        (-1, -1) with base ring Rational Field over the scalar ring
        Rational Field (+) Module of 2 by 2 matrices with entries in
        Quaternion Algebra (-1, -1) with base ring Rational Field over
        the scalar ring Rational Field

    TESTS::

        sage: A = QuaternionMatrixAlgebra(ZZ.random_element(10))
        sage: x = A.random_element()
        sage: x*A.one() == x and A.one()*x == x
        True

    """
    def __init__(self, n, entry_algebra=None, scalars=AA, **kwargs):
        if entry_algebra is None:
            # The -1,-1 gives us the "usual" definition of quaternion
            from sage.algebras.quatalg.quaternion_algebra import (
                QuaternionAlgebra
            )
            entry_algebra = QuaternionAlgebra(scalars,-1,-1)
        super().__init__(n, entry_algebra, scalars, **kwargs)

    def _entry_algebra_element_to_vector(self, entry):
        r"""

        SETUP::

            sage: from mjo.hurwitz import QuaternionMatrixAlgebra

        EXAMPLES::

            sage: A = QuaternionMatrixAlgebra(2)
            sage: u = A.entry_algebra().one()
            sage: A._entry_algebra_element_to_vector(u)
            (1, 0, 0, 0)
            sage: i,j,k = A.entry_algebra().gens()
            sage: A._entry_algebra_element_to_vector(i)
            (0, 1, 0, 0)
            sage: A._entry_algebra_element_to_vector(j)
            (0, 0, 1, 0)
            sage: A._entry_algebra_element_to_vector(k)
            (0, 0, 0, 1)

        """
        from sage.modules.free_module import FreeModule
        d = len(self.entry_algebra_gens())
        V = FreeModule(self.entry_algebra().base_ring(), d)
        return V(entry.coefficient_tuple())

class ComplexMatrixAlgebra(HurwitzMatrixAlgebra):
    r"""
    The algebra of ``n``-by-``n`` matrices with complex entries over
    (a subfield of) the real numbers.

    These differ from the usual complex matrix spaces in SageMath
    because the scalar field is real (and not assumed to be the same
    as the space from which the entries are drawn). The space of
    `1`-by-`1` complex matrices will have dimension two, for example.

    SETUP::

        sage: from mjo.hurwitz import ComplexMatrixAlgebra

    EXAMPLES::

        sage: ComplexMatrixAlgebra(3)
        Module of 3 by 3 matrices with entries in Algebraic Field
        over the scalar ring Algebraic Real Field

    ::

        sage: ComplexMatrixAlgebra(3,scalars=QQ)
        Module of 3 by 3 matrices with entries in Algebraic Field
        over the scalar ring Rational Field

    ::

        sage: A = ComplexMatrixAlgebra(1,CC)
        sage: A
        Module of 1 by 1 matrices with entries in Complex Field with
        53 bits of precision over the scalar ring Algebraic Real Field
        sage: A.one()
        ┌──────────────────┐
        │ 1.00000000000000 │
        └──────────────────┘
        sage: A.gens()
        (┌──────────────────┐
         │ 1.00000000000000 │
         └──────────────────┘,
         ┌────────────────────┐
         │ 1.00000000000000*I │
         └────────────────────┘)

    ::

        sage: A = ComplexMatrixAlgebra(2)
        sage: (I,) = A.entry_algebra().gens()
        sage: A([ [1+I, 1],
        ....:     [-1, -I] ])
        ┌─────────┬──────┐
        │ 1 + 1*I │ 1    │
        ├─────────┼──────┤
        │ -1      │ -1*I │
        └─────────┴──────┘

    ::

        sage: A1 = ComplexMatrixAlgebra(1,scalars=QQ)
        sage: A2 = ComplexMatrixAlgebra(2,scalars=QQ)
        sage: cartesian_product([A1,A2])
        Module of 1 by 1 matrices with entries in Algebraic Field over
        the scalar ring Rational Field (+) Module of 2 by 2 matrices with
        entries in Algebraic Field over the scalar ring Rational Field

    TESTS::

        sage: A = ComplexMatrixAlgebra(ZZ.random_element(10))
        sage: x = A.random_element()
        sage: x*A.one() == x and A.one()*x == x
        True

    """
    def __init__(self, n, entry_algebra=None, scalars=AA, **kwargs):
        if entry_algebra is None:
            from sage.rings.all import QQbar
            entry_algebra = QQbar
        super().__init__(n, entry_algebra, scalars, **kwargs)

    def _entry_algebra_element_to_vector(self, entry):
        r"""

        SETUP::

            sage: from mjo.hurwitz import ComplexMatrixAlgebra

        EXAMPLES::

            sage: A = ComplexMatrixAlgebra(2, QQbar, QQ)
            sage: A._entry_algebra_element_to_vector(QQbar(1))
            (1, 0)
            sage: A._entry_algebra_element_to_vector(QQbar(I))
            (0, 1)

        """
        from sage.modules.free_module import FreeModule
        d = len(self.entry_algebra_gens())
        V = FreeModule(self.entry_algebra().base_ring(), d)
        return V((entry.real(), entry.imag()))