for j in xrange(n/2):
submat = M[2*k:2*k+2,2*j:2*j+2]
if submat[0,0] != submat[1,1]:
- raise ValueError('bad real submatrix')
+ raise ValueError('bad on-diagonal submatrix')
if submat[0,1] != -submat[1,0]:
- raise ValueError('bad imag submatrix')
+ raise ValueError('bad off-diagonal submatrix')
z = submat[0,0] + submat[0,1]*i
elements.append(z)
return matrix(F, n/2, elements)
+
+def _embed_quaternion_matrix(M):
+ """
+ Embed the n-by-n quaternion matrix ``M`` into the space of real
+ matrices of size 4n-by-4n by first sending each quaternion entry
+ `z = a + bi + cj + dk` to the block-complex matrix
+ ``[[a + bi, c+di],[-c + di, a-bi]]`, and then embedding those into
+ a real matrix.
+
+ EXAMPLES::
+
+ sage: Q = QuaternionAlgebra(QQ,-1,-1)
+ sage: i,j,k = Q.gens()
+ sage: x = 1 + 2*i + 3*j + 4*k
+ sage: M = matrix(Q, 1, [[x]])
+ sage: _embed_quaternion_matrix(M)
+ [ 1 2 3 4]
+ [-2 1 -4 3]
+ [-3 4 1 -2]
+ [-4 -3 2 1]
+
+ """
+ quaternions = M.base_ring()
+ n = M.nrows()
+ if M.ncols() != n:
+ raise ValueError("the matrix 'M' must be square")
+
+ F = QuadraticField(-1, 'i')
+ i = F.gen()
+
+ blocks = []
+ for z in M.list():
+ t = z.coefficient_tuple()
+ a = t[0]
+ b = t[1]
+ c = t[2]
+ d = t[3]
+ cplx_matrix = matrix(F, 2, [[ a + b*i, c + d*i],
+ [-c + d*i, a - b*i]])
+ blocks.append(_embed_complex_matrix(cplx_matrix))
+
+ # We should have real entries by now, so use the realest field
+ # we've got for the return value.
+ return block_matrix(quaternions.base_ring(), n, blocks)
+
+
+def _unembed_quaternion_matrix(M):
+ """
+ The inverse of _embed_quaternion_matrix().
+
+ EXAMPLES::
+
+ sage: M = matrix(QQ, [[ 1, 2, 3, 4],
+ ....: [-2, 1, -4, 3],
+ ....: [-3, 4, 1, -2],
+ ....: [-4, -3, 2, 1]])
+ sage: _unembed_quaternion_matrix(M)
+ [1 + 2*i + 3*j + 4*k]
+
+ TESTS::
+
+ sage: set_random_seed()
+ sage: Q = QuaternionAlgebra(QQ, -1, -1)
+ sage: M = random_matrix(Q, 3)
+ sage: _unembed_quaternion_matrix(_embed_quaternion_matrix(M)) == M
+ True
+
+ """
+ n = ZZ(M.nrows())
+ if M.ncols() != n:
+ raise ValueError("the matrix 'M' must be square")
+ if not n.mod(4).is_zero():
+ raise ValueError("the matrix 'M' must be a complex embedding")
+
+ Q = QuaternionAlgebra(QQ,-1,-1)
+ i,j,k = Q.gens()
+
+ # Go top-left to bottom-right (reading order), converting every
+ # 4-by-4 block we see to a 2-by-2 complex block, to a 1-by-1
+ # quaternion block.
+ elements = []
+ for l in xrange(n/4):
+ for m in xrange(n/4):
+ submat = _unembed_complex_matrix(M[4*l:4*l+4,4*m:4*m+4])
+ if submat[0,0] != submat[1,1].conjugate():
+ raise ValueError('bad on-diagonal submatrix')
+ if submat[0,1] != -submat[1,0].conjugate():
+ raise ValueError('bad off-diagonal submatrix')
+ z = submat[0,0].real() + submat[0,0].imag()*i
+ z += submat[0,1].real()*j + submat[0,1].imag()*k
+ elements.append(z)
+
+ return matrix(Q, n/4, elements)
+
+
# The usual inner product on R^n.
def _usual_ip(x,y):
return x.vector().inner_product(y.vector())