]> gitweb.michael.orlitzky.com - sage.d.git/blob - mjo/matrix_algebra.py
matrix_algebra: put basis in the usual (row,column) order.
[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 self._entry_algebra = entry_algebra
202
203 # Needs to make the (overridden) method call when, for example,
204 # the entry algebra is the complex numbers and its gens() method
205 # lies to us.
206 entry_basis = self.entry_algebra_gens()
207
208 basis_indices = [(i,j,e) for j in range(n)
209 for i in range(n)
210 for e in entry_basis]
211
212 super().__init__(scalars,
213 basis_indices,
214 category=category,
215 prefix=prefix,
216 bracket='(')
217
218 def _repr_(self):
219 return ("Module of %d by %d matrices with entries in %s"
220 " over the scalar ring %s" %
221 (self.nrows(),
222 self.ncols(),
223 self.entry_algebra(),
224 self.base_ring()) )
225
226 def entry_algebra(self):
227 r"""
228 Return the algebra that our elements' entries come from.
229 """
230 return self._entry_algebra
231
232 def entry_algebra_gens(self):
233 r"""
234 Return a tuple of the generators of (that is, a basis for) the
235 entries of this matrix algebra.
236
237 This can be overridden in subclasses to work around the
238 inconsistency in the ``gens()`` methods of the various
239 entry algebras.
240 """
241 return self.entry_algebra().gens()
242
243 def nrows(self):
244 return self._nrows
245 ncols = nrows
246
247 def product_on_basis(self, mon1, mon2):
248 r"""
249
250 SETUP::
251
252 sage: from mjo.hurwitz import Octonions
253 sage: from mjo.matrix_algebra import MatrixAlgebra
254
255 TESTS::
256
257 sage: O = Octonions(QQ)
258 sage: e = O.gens()
259 sage: e[2]*e[1]
260 -e3
261 sage: A = MatrixAlgebra(2,O,QQ)
262 sage: A.product_on_basis( (0,0,e[2]), (0,0,e[1]) )
263 +-----+---+
264 | -e3 | 0 |
265 +-----+---+
266 | 0 | 0 |
267 +-----+---+
268
269 """
270 (i,j,e1) = mon1
271 (k,l,e2) = mon2
272 if j == k:
273 # If e1*e2 has a negative sign in front of it,
274 # then (i,l,e1*e2) won't be a monomial!
275 p = e1*e2
276 if (i,l,p) in self.indices():
277 return self.monomial((i,l,p))
278 else:
279 return -self.monomial((i,l,-p))
280 else:
281 return self.zero()
282
283 def from_list(self, entries):
284 r"""
285 Construct an element of this algebra from a list of lists of
286 entries.
287
288 SETUP::
289
290 sage: from mjo.matrix_algebra import MatrixAlgebra
291
292 EXAMPLES::
293
294 sage: A = MatrixAlgebra(2, QQbar, ZZ)
295 sage: A.from_list([[0,I],[-I,0]])
296 +----+---+
297 | 0 | I |
298 +----+---+
299 | -I | 0 |
300 +----+---+
301
302 """
303 nrows = len(entries)
304 ncols = 0
305 if nrows > 0:
306 ncols = len(entries[0])
307
308 if (not all( len(r) == ncols for r in entries )) or (ncols != nrows):
309 raise ValueError("list must be square")
310
311 def convert(e_ij):
312 if e_ij in self.entry_algebra():
313 # Don't re-create an element if it already lives where
314 # it should!
315 return e_ij
316
317 try:
318 # This branch works with e.g. QQbar, where no
319 # to/from_vector() methods are available.
320 return self.entry_algebra()(e_ij)
321 except TypeError:
322 # We have to pass through vectors to convert from the
323 # given entry algebra to ours. Otherwise we can fail to
324 # convert an element of (for example) Octonions(QQ) to
325 # Octonions(AA).
326 return self.entry_algebra().from_vector(e_ij.to_vector())
327
328 return sum( (self.monomial( (i,j, convert(entries[i][j])) )
329 for i in range(nrows)
330 for j in range(ncols) ),
331 self.zero() )
332
333 def _element_constructor_(self, elt):
334 if elt in self:
335 return self
336 else:
337 return self.from_list(elt)