]> gitweb.michael.orlitzky.com - sage.d.git/blob - mjo/matrix_algebra.py
d67347b3aa1252acaf165448d1ea35ebb4a77cd7
[sage.d.git] / mjo / matrix_algebra.py
1 from sage.misc.table import table
2 from sage.categories.magmatic_algebras import MagmaticAlgebras
3 from sage.misc.cachefunc import cached_method
4 from sage.combinat.free_module import CombinatorialFreeModule
5 from sage.modules.with_basis.indexed_element import IndexedFreeModuleElement
6
7 class MatrixAlgebraElement(IndexedFreeModuleElement):
8 def nrows(self):
9 return self.parent().nrows()
10 ncols = nrows
11
12 @cached_method
13 def rows(self):
14 r"""
15 SETUP::
16
17 sage: from mjo.matrix_algebra import MatrixAlgebra
18
19 EXAMPLES::
20
21 sage: M = MatrixAlgebra(2, QQbar,RDF)
22 sage: A = M.monomial((0,0,1)) + 4*M.monomial((0,1,1))
23 sage: A
24 +-----+-----+
25 | 1.0 | 4.0 |
26 +-----+-----+
27 | 0 | 0 |
28 +-----+-----+
29 sage: A.rows()
30 [[1.0, 4.0], [0, 0]]
31
32 """
33 zero = self.parent().entry_algebra().zero()
34 l = [[zero for j in range(self.ncols())] for i in range(self.nrows())]
35 for (k,v) in self.monomial_coefficients().items():
36 (i,j,e) = k
37 l[i][j] += v*e
38 return l
39
40 def _repr_(self):
41 r"""
42 Display this matrix as a table.
43
44 The SageMath Matrix class representation is not easily reusable,
45 but using a table fakes it.
46
47 SETUP::
48
49 sage: from mjo.matrix_algebra import MatrixAlgebra
50
51 EXAMPLES::
52
53 sage: MatrixAlgebra(2,ZZ,ZZ).zero()
54 +---+---+
55 | 0 | 0 |
56 +---+---+
57 | 0 | 0 |
58 +---+---+
59
60 """
61 return table(self.rows(), frame=True)._repr_()
62
63
64 def list(self):
65 r"""
66 Return one long list of this matrix's entries.
67
68 SETUP::
69
70 sage: from mjo.matrix_algebra import MatrixAlgebra
71
72 EXAMPLES::
73
74 sage: A = MatrixAlgebra(2,ZZ,ZZ)
75 sage: A([[1,2],[3,4]]).list()
76 [1, 2, 3, 4]
77
78 """
79 return sum( self.rows(), [] )
80
81
82 def __getitem__(self, indices):
83 r"""
84
85 SETUP::
86
87 sage: from mjo.matrix_algebra import MatrixAlgebra
88
89 EXAMPLES::
90
91 sage: M = MatrixAlgebra(2,ZZ,ZZ)([[1,2],[3,4]])
92 sage: M[0,0]
93 1
94 sage: M[0,1]
95 2
96 sage: M[1,0]
97 3
98 sage: M[1,1]
99 4
100
101 """
102 i,j = indices
103 return self.rows()[i][j]
104
105 def trace(self):
106 r"""
107 Return the sum of this matrix's diagonal entries.
108
109 SETUP::
110
111 sage: from mjo.matrix_algebra import MatrixAlgebra
112
113 EXAMPLES:
114
115 The trace (being a sum of entries) belongs to the same algebra
116 as those entries, and NOT the scalar ring::
117
118 sage: entries = MatrixSpace(ZZ,2)
119 sage: scalars = ZZ
120 sage: M = MatrixAlgebra(2, entries, scalars)
121 sage: I = entries.one()
122 sage: Z = entries.zero()
123 sage: M([[I,Z],[Z,I]]).trace()
124 [2 0]
125 [0 2]
126
127 """
128 zero = self.parent().entry_algebra().zero()
129 return sum( (self[i,i] for i in range(self.nrows())), zero )
130
131 def matrix_space(self):
132 r"""
133
134 SETUP::
135
136 sage: from mjo.matrix_algebra import MatrixAlgebra
137
138 TESTS::
139
140 sage: set_random_seed()
141 sage: entries = QuaternionAlgebra(QQ,-1,-1)
142 sage: M = MatrixAlgebra(3, entries, QQ)
143 sage: M.random_element().matrix_space() == M
144 True
145
146 """
147 return self.parent()
148
149
150 class MatrixAlgebra(CombinatorialFreeModule):
151 r"""
152 An algebra of ``n``-by-``n`` matrices over an arbitrary scalar
153 ring whose entries come from a magmatic algebra that need not
154 be the same as the scalars.
155
156 The usual matrix spaces in SageMath don't support separate spaces
157 for the entries and the scalars; in particular they assume that
158 the entries come from a commutative and associative ring. This
159 is problematic in several interesting matrix algebras, like those
160 where the entries are quaternions or octonions.
161
162 SETUP::
163
164 sage: from mjo.matrix_algebra import MatrixAlgebra
165
166 EXAMPLES::
167
168 The existence of a unit element is determined dynamically::
169
170 sage: MatrixAlgebra(2,ZZ,ZZ).one()
171 +---+---+
172 | 1 | 0 |
173 +---+---+
174 | 0 | 1 |
175 +---+---+
176
177 """
178 Element = MatrixAlgebraElement
179
180 def __init__(self, n, entry_algebra, scalars, prefix="A", **kwargs):
181
182 category = MagmaticAlgebras(scalars).FiniteDimensional()
183 category = category.WithBasis()
184
185 if "Unital" in entry_algebra.category().axioms():
186 category = category.Unital()
187 entry_one = entry_algebra.one()
188 self.one = lambda: sum( (self.monomial((i,i,entry_one))
189 for i in range(self.nrows()) ),
190 self.zero() )
191
192 if "Associative" in entry_algebra.category().axioms():
193 category = category.Associative()
194
195 self._nrows = n
196
197 # Since the scalar ring is real but the entries are not,
198 # sticking a "1" in each position doesn't give us a basis for
199 # the space. We actually need to stick each of e0, e1, ... (a
200 # basis for the entry algebra itself) into each position.
201 I = range(n)
202 J = range(n)
203 self._entry_algebra = entry_algebra
204
205 # Needs to make the (overridden) method call when, for example,
206 # the entry algebra is the complex numbers and its gens() method
207 # lies to us.
208 entry_basis = self.entry_algebra_gens()
209
210 basis_indices = [(i,j,e) for i in range(n)
211 for j in range(n)
212 for e in entry_basis]
213
214 super().__init__(scalars,
215 basis_indices,
216 category=category,
217 prefix=prefix,
218 bracket='(')
219
220 def _repr_(self):
221 return ("Module of %d by %d matrices with entries in %s"
222 " over the scalar ring %s" %
223 (self.nrows(),
224 self.ncols(),
225 self.entry_algebra(),
226 self.base_ring()) )
227
228 def entry_algebra(self):
229 r"""
230 Return the algebra that our elements' entries come from.
231 """
232 return self._entry_algebra
233
234 def entry_algebra_gens(self):
235 r"""
236 Return a tuple of the generators of (that is, a basis for) the
237 entries of this matrix algebra.
238
239 This can be overridden in subclasses to work around the
240 inconsistency in the ``gens()`` methods of the various
241 entry algebras.
242 """
243 return self.entry_algebra().gens()
244
245 def nrows(self):
246 return self._nrows
247 ncols = nrows
248
249 def product_on_basis(self, mon1, mon2):
250 r"""
251
252 SETUP::
253
254 sage: from mjo.hurwitz import Octonions
255 sage: from mjo.matrix_algebra import MatrixAlgebra
256
257 TESTS::
258
259 sage: O = Octonions(QQ)
260 sage: e = O.gens()
261 sage: e[2]*e[1]
262 -e3
263 sage: A = MatrixAlgebra(2,O,QQ)
264 sage: A.product_on_basis( (0,0,e[2]), (0,0,e[1]) )
265 +-----+---+
266 | -e3 | 0 |
267 +-----+---+
268 | 0 | 0 |
269 +-----+---+
270
271 """
272 (i,j,e1) = mon1
273 (k,l,e2) = mon2
274 if j == k:
275 # If e1*e2 has a negative sign in front of it,
276 # then (i,l,e1*e2) won't be a monomial!
277 p = e1*e2
278 if (i,l,p) in self.indices():
279 return self.monomial((i,l,p))
280 else:
281 return -self.monomial((i,l,-p))
282 else:
283 return self.zero()
284
285 def from_list(self, entries):
286 r"""
287 Construct an element of this algebra from a list of lists of
288 entries.
289
290 SETUP::
291
292 sage: from mjo.matrix_algebra import MatrixAlgebra
293
294 EXAMPLES::
295
296 sage: A = MatrixAlgebra(2, QQbar, ZZ)
297 sage: A.from_list([[0,I],[-I,0]])
298 +----+---+
299 | 0 | I |
300 +----+---+
301 | -I | 0 |
302 +----+---+
303
304 """
305 nrows = len(entries)
306 ncols = 0
307 if nrows > 0:
308 ncols = len(entries[0])
309
310 if (not all( len(r) == ncols for r in entries )) or (ncols != nrows):
311 raise ValueError("list must be square")
312
313 def convert(e_ij):
314 if e_ij in self.entry_algebra():
315 # Don't re-create an element if it already lives where
316 # it should!
317 return e_ij
318
319 try:
320 # This branch works with e.g. QQbar, where no
321 # to/from_vector() methods are available.
322 return self.entry_algebra()(e_ij)
323 except TypeError:
324 # We have to pass through vectors to convert from the
325 # given entry algebra to ours. Otherwise we can fail to
326 # convert an element of (for example) Octonions(QQ) to
327 # Octonions(AA).
328 return self.entry_algebra().from_vector(e_ij.to_vector())
329
330 return sum( (self.monomial( (i,j, convert(entries[i][j])) )
331 for i in range(nrows)
332 for j in range(ncols) ),
333 self.zero() )
334
335 def _element_constructor_(self, elt):
336 if elt in self:
337 return self
338 else:
339 return self.from_list(elt)