def __init__(self, A, elt=None, check=True): """ TESTS:: sage: A = FiniteDimensionalAlgebra(GF(3), [Matrix([[1,0], [0,1]]), Matrix([[0,1], [0,0]])]) sage: A(QQ(4)) Traceback (most recent call last): ... TypeError: elt should be a vector, a matrix, or an element of the base field sage: B = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0], [0,1]]), Matrix([[0,1], [-1,0]])]) sage: elt = B(Matrix([[1,1], [-1,1]])); elt e0 + e1 sage: TestSuite(elt).run() sage: B(Matrix([[0,1], [1,0]])) Traceback (most recent call last): ... ValueError: matrix does not define an element of the algebra """ AlgebraElement.__init__(self, A) k = A.base_ring() n = A.degree() if elt is None: self._vector = vector(k, n) self._matrix = Matrix(k, n) else: if isinstance(elt, int): elt = Integer(elt) elif isinstance(elt, list): elt = vector(elt) if A == elt.parent(): self._vector = elt._vector.base_extend(k) self._matrix = elt._matrix.base_extend(k) elif k.has_coerce_map_from(elt.parent()): e = k(elt) if e == 0: self._vector = vector(k, n) self._matrix = Matrix(k, n) elif A.is_unitary(): self._vector = A._one * e self._matrix = Matrix.identity(k, n) * e else: raise TypeError("algebra is not unitary") elif is_Vector(elt): self._vector = elt.base_extend(k) self._matrix = Matrix( k, sum([elt[i] * A.table()[i] for i in range(n)])) elif is_Matrix(elt): if not A.is_unitary(): raise TypeError("algebra is not unitary") self._vector = A._one * elt if not check or sum( [self._vector[i] * A.table()[i] for i in range(n)]) == elt: self._matrix = elt else: raise ValueError( "matrix does not define an element of the algebra") else: raise TypeError("elt should be a vector, a matrix, " + "or an element of the base field")
def __init__(self, A, elt=None, check=True): """ TESTS:: sage: A = FiniteDimensionalAlgebra(GF(3), [Matrix([[1,0], [0,1]]), Matrix([[0,1], [0,0]])]) sage: A(QQ(4)) Traceback (most recent call last): ... TypeError: elt should be a vector, a matrix, or an element of the base field sage: B = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0], [0,1]]), Matrix([[0,1], [-1,0]])]) sage: elt = B(Matrix([[1,1], [-1,1]])); elt e0 + e1 sage: TestSuite(elt).run() sage: B(Matrix([[0,1], [1,0]])) Traceback (most recent call last): ... ValueError: matrix does not define an element of the algebra """ AlgebraElement.__init__(self, A) k = A.base_ring() n = A.degree() if elt is None: self._vector = vector(k, n) self._matrix = Matrix(k, n) else: if isinstance(elt, int): elt = Integer(elt) elif isinstance(elt, list): elt = vector(elt) if A == elt.parent(): self._vector = elt._vector.base_extend(k) self._matrix = elt._matrix.base_extend(k) elif k.has_coerce_map_from(elt.parent()): e = k(elt) if e == 0: self._vector = vector(k, n) self._matrix = Matrix(k, n) elif A.is_unitary(): self._vector = A._one * e self._matrix = Matrix.identity(k, n) * e else: raise TypeError("algebra is not unitary") elif is_Vector(elt): self._vector = elt.base_extend(k) self._matrix = Matrix(k, sum([elt[i] * A.table()[i] for i in xrange(n)])) elif is_Matrix(elt): if not A.is_unitary(): raise TypeError("algebra is not unitary") self._vector = A._one * elt if not check or sum([self._vector[i]*A.table()[i] for i in xrange(n)]) == elt: self._matrix = elt else: raise ValueError("matrix does not define an element of the algebra") else: raise TypeError("elt should be a vector, a matrix, " + "or an element of the base field")
def is_unitary(self): """ Return ``True`` if ``self`` has a two-sided multiplicative identity element. EXAMPLES:: sage: A = FiniteDimensionalAlgebra(QQ, []) sage: A.is_unitary() True sage: B = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0], [0,1]]), Matrix([[0,1], [-1,0]])]) sage: B.is_unitary() True sage: C = FiniteDimensionalAlgebra(QQ, [Matrix([[0,0], [0,0]]), Matrix([[0,0], [0,0]])]) sage: C.is_unitary() False sage: D = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0], [0,1]]), Matrix([[1,0], [0,1]])]) sage: D.is_unitary() False .. NOTE:: If a finite-dimensional algebra over a field admits a left identity, then this is the unique left identity, and it is also a right identity. """ k = self.base_ring() n = self.degree() # B is obtained by concatenating the elements of # self.table(), and v by concatenating the rows of # the n times n identity matrix. B = reduce(lambda x, y: x.augment(y), self.table(), Matrix(k, n, 0)) v = vector(Matrix.identity(k, n).list()) try: self._one = B.solve_left(v) return True except ValueError: return False
def _jordan_2_adic(G): r""" Transform a symmetric matrix over the `2`-adic integers into jordan form. Note that if the precision is too low, this method fails. The method is only tested for input over `\ZZ_2` of ``'type=fixed-mod'``. INPUT: - ``G`` -- symmetric `n` by `n` matrix in `\ZZ_p` OUTPUT: - ``D`` -- the jordan matrix - ``B`` -- transformation matrix, i.e, ``D = B * G * B^T`` The matrix ``D`` is a block diagonal matrix consisting of `1` by `1` and `2` by `2` blocks. The `2` by `2` blocks are matrices of the form `[[2a, b], [b, 2c]] * 2^k` with `b` of valuation `0`. EXAMPLES:: sage: from sage.quadratic_forms.genera.normal_form import _jordan_2_adic sage: R = Zp(2, prec=3, print_mode='terse', show_prec=False) sage: A4 = Matrix(R,4,[2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2]) sage: A4 [2 7 0 0] [7 2 7 0] [0 7 2 7] [0 0 7 2] sage: D, B = _jordan_2_adic(A4) sage: D [ 2 7 0 0] [ 7 2 0 0] [ 0 0 12 7] [ 0 0 7 2] sage: D == B*A4*B.T True sage: B.determinant().valuation() == 0 True """ R = G.base_ring() D = copy(G) n = G.ncols() # transformation matrix B = Matrix.identity(R, n) # indices of the diagonal entrys which are already used cnt = 0 minval = None while cnt < n: pivot = _find_min_p(D, cnt) piv1 = pivot[1] piv2 = pivot[2] minval = pivot[0] # the smallest valuation is on the diagonal if piv1 == piv2: # move pivot to position [cnt,cnt] if piv1 != cnt: B.swap_rows(cnt, piv1) D.swap_rows(cnt, piv1) D.swap_columns(cnt, piv1) # we are already orthogonal to the part with i < cnt # now make the rest orthogonal too for i in range(cnt + 1, n): if D[i, cnt] != 0: c = D[i, cnt] // D[cnt, cnt] B[i, :] += -c * B[cnt, :] D[i, :] += -c * D[cnt, :] D[:, i] += -c * D[:, cnt] cnt = cnt + 1 # the smallest valuation is off the diagonal else: # move this 2 x 2 block to the top left (starting from cnt) if piv1 != cnt: B.swap_rows(cnt, piv1) D.swap_rows(cnt, piv1) D.swap_columns(cnt, piv1) if piv2 != cnt + 1: B.swap_rows(cnt + 1, piv2) D.swap_rows(cnt + 1, piv2) D.swap_columns(cnt + 1, piv2) # we split off a 2 x 2 block # if it is the last 2 x 2 block, there is nothing to do. if cnt != n - 2: content = R(2**minval) eqn_mat = D[cnt:cnt + 2, cnt:cnt + 2].list() eqn_mat = Matrix(R, 2, 2, [e // content for e in eqn_mat]) # calculate the inverse without using division inv = eqn_mat.adjugate() * eqn_mat.det().inverse_of_unit() B1 = B[cnt:cnt + 2, :] B2 = D[cnt + 2:, cnt:cnt + 2] * inv for i in range(B2.nrows()): for j in range(B2.ncols()): B2[i, j] = B2[i, j] // content B[cnt + 2:, :] -= B2 * B1 D[cnt:, cnt:] = B[cnt:, :] * G * B[cnt:, :].transpose() cnt += 2 return D, B
def _jordan_odd_adic(G): r""" Return the Jordan decomposition of a symmetric matrix over an odd prime. INPUT: - a symmetric matrix over `\ZZ_p` of type ``'fixed-mod'`` OUTPUT: - ``D`` -- a diagonal matrix - ``B`` -- a unimodular matrix such that ``D = B * G * B.T`` EXAMPLES:: sage: from sage.quadratic_forms.genera.normal_form import _jordan_odd_adic sage: R = Zp(3, prec=2, print_mode='terse', show_prec=False) sage: A4 = Matrix(R,4,[2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2]) sage: A4 [2 8 0 0] [8 2 8 0] [0 8 2 8] [0 0 8 2] sage: D, B = _jordan_odd_adic(A4) sage: D [2 0 0 0] [0 2 0 0] [0 0 1 0] [0 0 0 8] sage: D == B*A4*B.T True sage: B.determinant().valuation() == 0 True """ R = G.base_ring() D = copy(G) n = G.ncols() # transformation matrix B = Matrix.identity(R, n) # indices of the diagonal entrys which are already used cnt = 0 minval = 0 while cnt < n: pivot = _find_min_p(D, cnt, minval) piv1 = pivot[1] piv2 = pivot[2] minval = pivot[0] # the smallest valuation is on the diagonal if piv1 == piv2: # move pivot to position [cnt,cnt] if piv1 != cnt: B.swap_rows(cnt, piv1) D.swap_rows(cnt, piv1) D.swap_columns(cnt, piv1) # we are already orthogonal to the part with i < cnt # now make the rest orthogonal too for i in range(cnt + 1, n): if D[i, cnt] != 0: c = D[i, cnt] // D[cnt, cnt] B[i, :] += -c * B[cnt, :] D[i, :] += -c * D[cnt, :] D[:, i] += -c * D[:, cnt] cnt = cnt + 1 else: # the smallest valuation is off the diagonal row = pivot[1] col = pivot[2] B[row, :] += B[col, :] D[row, :] += D[col, :] D[:, row] += D[:, col] # the smallest valuation is now on the diagonal return D, B
def p_adic_normal_form(G, p, precision=None, partial=False, debug=False): r""" Return the transformation to the `p`-adic normal form of a symmetric matrix. Two ``p-adic`` quadratic forms are integrally equivalent if and only if their Gram matrices have the same normal form. Let `p` be odd and `u` be the smallest non-square modulo `p`. The normal form is a block diagonal matrix with blocks `p^k G_k` such that `G_k` is either the identity matrix or the identity matrix with the last diagonal entry replaced by `u`. If `p=2` is even, define the `1` by `1` matrices:: sage: W1 = Matrix([1]); W1 [1] sage: W3 = Matrix([3]); W3 [3] sage: W5 = Matrix([5]); W5 [5] sage: W7 = Matrix([7]); W7 [7] and the `2` by `2` matrices:: sage: U = Matrix(2,[0,1,1,0]); U [0 1] [1 0] sage: V = Matrix(2,[2,1,1,2]); V [2 1] [1 2] For `p=2` the partial normal form is a block diagonal matrix with blocks `2^k G_k` such that $G_k$ is a block diagonal matrix of the form `[U`, ... , `U`, `V`, `Wa`, `Wb]` where we allow `V`, `Wa`, `Wb` to be `0 \times 0` matrices. Further restrictions to the full normal form apply. We refer to [MirMor2009]_ IV Definition 4.6. for the details. INPUT: - ``G`` -- a symmetric `n` by `n` matrix in `\QQ` - ``p`` -- a prime number -- it is not checked whether it is prime - ``precision`` -- if not set, the minimal possible is taken - ``partial`` -- boolean (default: ``False``) if set, only the partial normal form is returned. OUTPUT: - ``D`` -- the jordan matrix over `\QQ_p` - ``B`` -- invertible transformation matrix over `\ZZ_p`, i.e, ``D = B * G * B^T`` EXAMPLES:: sage: from sage.quadratic_forms.genera.normal_form import p_adic_normal_form sage: D4 = Matrix(ZZ, 4, [2,-1,-1,-1,-1,2,0,0,-1,0,2,0,-1,0,0,2]) sage: D4 [ 2 -1 -1 -1] [-1 2 0 0] [-1 0 2 0] [-1 0 0 2] sage: D, B = p_adic_normal_form(D4, 2) sage: D [ 2 1 0 0] [ 1 2 0 0] [ 0 0 2^2 2] [ 0 0 2 2^2] sage: D == B * D4 * B.T True sage: A4 = Matrix(ZZ, 4, [2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2]) sage: A4 [ 2 -1 0 0] [-1 2 -1 0] [ 0 -1 2 -1] [ 0 0 -1 2] sage: D, B = p_adic_normal_form(A4, 2) sage: D [0 1 0 0] [1 0 0 0] [0 0 2 1] [0 0 1 2] We can handle degenerate forms:: sage: A4_extended = Matrix(ZZ, 5, [2, -1, 0, 0, -1, -1, 2, -1, 0, 0, 0, -1, 2, -1, 0, 0, 0, -1, 2, -1, -1, 0, 0, -1, 2]) sage: D, B = p_adic_normal_form(A4_extended, 5) sage: D [1 0 0 0 0] [0 1 0 0 0] [0 0 1 0 0] [0 0 0 5 0] [0 0 0 0 0] and denominators:: sage: A4dual = A4.inverse() sage: D, B = p_adic_normal_form(A4dual, 5) sage: D [5^-1 0 0 0] [ 0 1 0 0] [ 0 0 1 0] [ 0 0 0 1] TESTS:: sage: Z = Matrix(ZZ,0,[]) sage: p_adic_normal_form(Z, 3) ([], []) sage: Z = matrix.zero(10) sage: p_adic_normal_form(Z, 3)[0] == 0 True """ p = ZZ(p) # input checks!! G0, denom = G._clear_denom() d = denom.valuation(p) r = G0.rank() if r != G0.ncols(): U = G0.hermite_form(transformation=True)[1] else: U = G0.parent().identity_matrix() kernel = U[r:, :] nondeg = U[:r, :] # continue with the non-degenerate part G = nondeg * G * nondeg.T * p**d if precision is None: # in Zp(2) we have to calculate at least mod 8 for things to make sense. precision = G.det().valuation(p) + 4 R = Zp(p, prec=precision, type='fixed-mod') G = G.change_ring(R) G.set_immutable() # is not changed during computation D = copy(G) # is transformed into jordan form n = G.ncols() # The trivial case if n == 0: return G.parent().zero(), G.parent().zero() # the transformation matrix is called B B = Matrix.identity(R, n) if p == 2: D, B = _jordan_2_adic(G) else: D, B = _jordan_odd_adic(G) D, B1 = _normalize(D) B = B1 * B # we have reached a normal form for p != 2 # for p == 2 extra work is necessary if p == 2: D, B1 = _two_adic_normal_forms(D, partial=partial) B = B1 * B nondeg = B * nondeg B = nondeg.stack(kernel) D = Matrix.block_diagonal([D, Matrix.zero(kernel.nrows())]) if debug: assert B.determinant().valuation() == 0 # B is invertible! if p == 2: assert B * G * B.T == Matrix.block_diagonal( collect_small_blocks(D)) else: assert B * G * B.T == Matrix.diagonal(D.diagonal()) return D / p**d, B
def primary_decomposition(self): """ Return the primary decomposition of ``self``. .. NOTE:: ``self`` must be unitary, commutative and associative. OUTPUT: - a list consisting of the quotient maps ``self`` -> `A`, with `A` running through the primary factors of ``self`` EXAMPLES:: sage: A = FiniteDimensionalAlgebra(GF(3), [Matrix([[1, 0], [0, 1]]), Matrix([[0, 1], [0, 0]])]) sage: A.primary_decomposition() [Morphism from Finite-dimensional algebra of degree 2 over Finite Field of size 3 to Finite-dimensional algebra of degree 2 over Finite Field of size 3 given by matrix [1 0] [0 1]] sage: B = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0,0], [0,1,0], [0,0,0]]), Matrix([[0,1,0], [0,0,0], [0,0,0]]), Matrix([[0,0,0], [0,0,0], [0,0,1]])]) sage: B.primary_decomposition() [Morphism from Finite-dimensional algebra of degree 3 over Rational Field to Finite-dimensional algebra of degree 1 over Rational Field given by matrix [0] [0] [1], Morphism from Finite-dimensional algebra of degree 3 over Rational Field to Finite-dimensional algebra of degree 2 over Rational Field given by matrix [1 0] [0 1] [0 0]] """ k = self.base_ring() n = self.degree() if n == 0: return [] if not (self.is_unitary() and self.is_commutative() and (self._assume_associative or self.is_associative())): raise TypeError("algebra must be unitary, commutative and associative") # Start with the trivial decomposition of self. components = [Matrix.identity(k, n)] for b in self.table(): # Use the action of the basis element b to refine our # decomposition of self. components_new = [] for c in components: # Compute the matrix of b on the component c, find its # characteristic polynomial, and factor it. b_c = c.solve_left(c * b) fact = b_c.characteristic_polynomial().factor() if len(fact) == 1: components_new.append(c) else: for f in fact: h, a = f e = h(b_c) ** a ker_e = e.kernel().basis_matrix() components_new.append(ker_e * c) components = components_new quotients = [] for i in range(len(components)): I = Matrix(k, 0, n) for j,c in enumerate(components): if j != i: I = I.stack(c) quotients.append(self.quotient_map(self.ideal(I, given_by_matrix=True))) return quotients
def __getitem__(self, key): r""" Return a slice of the sequence. EXAMPLES:: sage: C.<x> = CFiniteSequences(QQ) sage: r = C.from_recurrence([3,3],[2,1]) sage: r[2] 9 sage: r[101] 16158686318788579168659644539538474790082623100896663971001 sage: r = C(1/(1-x)) sage: r[5] 1 sage: r = C(x) sage: r[0] 0 sage: r[1] 1 sage: r = C(0) sage: r[66] 0 sage: lucas = C.from_recurrence([1,1],[2,1]) sage: lucas[5:10] [11, 18, 29, 47, 76] sage: r = C((2-x)/x/(1-x-x*x)) sage: r[0:4] [1, 3, 4, 7] sage: r = C(1-2*x^2) sage: r[0:4] [1, 0, -2, 0] sage: r[-1:4] # not tested, python will not allow this! [0, 1, 0 -2, 0] sage: r = C((-2*x^3 + x^2 + 1)/(-2*x + 1)) sage: r[0:5] # handle ogf > 1 [1, 2, 5, 8, 16] sage: r[-2] 0 sage: r = C((-2*x^3 + x^2 - x + 1)/(2*x^2 - 3*x + 1)) sage: r[0:5] [1, 2, 5, 9, 17] sage: s=C((1-x)/(-x^2 - x + 1)) sage: s[0:5] [1, 0, 1, 1, 2] sage: s=C((1+x^20+x^40)/(1-x^12)/(1-x^30)) sage: s[0:20] [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0] sage: s=C(1/((1-x^2)*(1-x^6)*(1-x^8)*(1-x^12))) sage: s[999998] 289362268629630 """ if isinstance(key, slice): m = max(key.start, key.stop) return [self[ii] for ii in range(*key.indices(m + 1))] elif isinstance(key, Integral): from sage.matrix.constructor import Matrix d = self._deg if (self._off <= key and key < self._off + len(self._a)): return self._a[key - self._off] elif d == 0: return 0 (quo, rem) = self.numerator().quo_rem(self.denominator()) wp = quo[key - self._off] if key < self._off: return wp A = Matrix(QQ, 1, d, self._c) B = Matrix.identity(QQ, d - 1) C = Matrix(QQ, d - 1, 1, 0) if quo == 0: V = Matrix(QQ, d, 1, self._a[:d][::-1]) else: V = Matrix(QQ, d, 1, self._aa[:d][::-1]) M = Matrix.block([[A], [B, C]], subdivide=False) return wp + list(M**(key - self._off) * V)[d - 1][0] else: raise TypeError("invalid argument type")
def _jordan_2_adic(G): r""" Transform a symmetric matrix over the `2`-adic integers into jordan form. Note that if the precision is too low, this method fails. The method is only tested for input over `\ZZ_2` of ``'type=fixed-mod'``. INPUT: - ``G`` -- symmetric `n` by `n` matrix in `\ZZ_p` OUTPUT: - ``D`` -- the jordan matrix - ``B`` -- transformation matrix, i.e, ``D = B * G * B^T`` The matrix ``D`` is a block diagonal matrix consisting of `1` by `1` and `2` by `2` blocks. The `2` by `2` blocks are matrices of the form `[[2a, b], [b, 2c]] * 2^k` with `b` of valuation `0`. EXAMPLES:: sage: from sage.quadratic_forms.genera.normal_form import _jordan_2_adic sage: R = Zp(2, prec=3, print_mode='terse', show_prec=False) sage: A4 = Matrix(R,4,[2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2]) sage: A4 [2 7 0 0] [7 2 7 0] [0 7 2 7] [0 0 7 2] sage: D, B = _jordan_2_adic(A4) sage: D [ 2 7 0 0] [ 7 2 0 0] [ 0 0 12 7] [ 0 0 7 2] sage: D == B*A4*B.T True sage: B.determinant().valuation() == 0 True """ R = G.base_ring() D = copy(G) n = G.ncols() # transformation matrix B = Matrix.identity(R, n) # indices of the diagonal entrys which are already used cnt = 0 minval = None while cnt < n: pivot = _find_min_p(D, cnt) piv1 = pivot[1] piv2 = pivot[2] minval_last = minval minval = pivot[0] # the smallest valuation is on the diagonal if piv1 == piv2: # move pivot to position [cnt,cnt] if piv1 != cnt: B.swap_rows(cnt, piv1) D.swap_rows(cnt, piv1) D.swap_columns(cnt, piv1) # we are already orthogonal to the part with i < cnt # now make the rest orthogonal too for i in range(cnt+1, n): if D[i, cnt] != 0: c = D[i, cnt]//D[cnt, cnt] B[i, :] += -c * B[cnt, :] D[i, :] += -c * D[cnt, :] D[:, i] += -c * D[:, cnt] cnt = cnt + 1 # the smallest valuation is off the diagonal else: # move this 2 x 2 block to the top left (starting from cnt) if piv1 != cnt: B.swap_rows(cnt, piv1) D.swap_rows(cnt, piv1) D.swap_columns(cnt, piv1) if piv2 != cnt+1: B.swap_rows(cnt+1, piv2) D.swap_rows(cnt+1, piv2) D.swap_columns(cnt+1, piv2) # we split off a 2 x 2 block # if it is the last 2 x 2 block, there is nothing to do. if cnt != n-2: content = R(2 ** minval) eqn_mat = D[cnt:cnt+2, cnt:cnt+2].list() eqn_mat = Matrix(R, 2, 2, [e // content for e in eqn_mat]) # calculate the inverse without using division inv = eqn_mat.adjoint() * eqn_mat.det().inverse_of_unit() B1 = B[cnt:cnt+2, :] B2 = D[cnt+2:, cnt:cnt+2] * inv for i in range(B2.nrows()): for j in range(B2.ncols()): B2[i, j]=B2[i, j] // content B[cnt+2:, :] -= B2 * B1 D[cnt:, cnt:] = B[cnt:, :] * G * B[cnt:, :].transpose() cnt += 2 return D, B
def _jordan_odd_adic(G): r""" Return the Jordan decomposition of a symmetric matrix over an odd prime. INPUT: - a symmetric matrix over `\ZZ_p` of type ``'fixed-mod'`` OUTPUT: - ``D`` -- a diagonal matrix - ``B`` -- a unimodular matrix such that ``D = B * G * B.T`` EXAMPLES:: sage: from sage.quadratic_forms.genera.normal_form import _jordan_odd_adic sage: R = Zp(3, prec=2, print_mode='terse', show_prec=False) sage: A4 = Matrix(R,4,[2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2]) sage: A4 [2 8 0 0] [8 2 8 0] [0 8 2 8] [0 0 8 2] sage: D, B = _jordan_odd_adic(A4) sage: D [2 0 0 0] [0 2 0 0] [0 0 1 0] [0 0 0 8] sage: D == B*A4*B.T True sage: B.determinant().valuation() == 0 True """ R = G.base_ring() D = copy(G) n = G.ncols() # transformation matrix B = Matrix.identity(R, n) # indices of the diagonal entrys which are already used cnt = 0 minval = 0 while cnt < n: pivot = _find_min_p(D, cnt, minval) piv1 = pivot[1] piv2 = pivot[2] minval = pivot[0] # the smallest valuation is on the diagonal if piv1 == piv2: # move pivot to position [cnt,cnt] if piv1 != cnt: B.swap_rows(cnt, piv1) D.swap_rows(cnt, piv1) D.swap_columns(cnt, piv1) # we are already orthogonal to the part with i < cnt # now make the rest orthogonal too for i in range(cnt+1,n): if D[i, cnt]!= 0: c = D[i, cnt] // D[cnt, cnt] B[i, :] += - c * B[cnt, :] D[i, :] += - c * D[cnt, :] D[:, i] += - c * D[:, cnt] cnt = cnt + 1 else: # the smallest valuation is off the diagonal row = pivot[1] col = pivot[2] B[row, :] += B[col, :] D[row, :] += D[col, :] D[:, row] += D[:, col] # the smallest valuation is now on the diagonal return D, B
def p_adic_normal_form(G, p, precision=None, partial=False, debug=False): r""" Return the transformation to the `p`-adic normal form of a symmetric matrix. Two ``p-adic`` quadratic forms are integrally equivalent if and only if their Gram matrices have the same normal form. Let `p` be odd and `u` be the smallest non-square modulo `p`. The normal form is a block diagonal matrix with blocks `p^k G_k` such that `G_k` is either the identity matrix or the identity matrix with the last diagonal entry replaced by `u`. If `p=2` is even, define the `1` by `1` matrices:: sage: W1 = Matrix([1]); W1 [1] sage: W3 = Matrix([3]); W3 [3] sage: W5 = Matrix([5]); W5 [5] sage: W7 = Matrix([7]); W7 [7] and the `2` by `2` matrices:: sage: U = Matrix(2,[0,1,1,0]); U [0 1] [1 0] sage: V = Matrix(2,[2,1,1,2]); V [2 1] [1 2] For `p=2` the partial normal form is a block diagonal matrix with blocks `2^k G_k` such that $G_k$ is a block diagonal matrix of the form `[U`, ... , `U`, `V`, `Wa`, `Wb]` where we allow `V`, `Wa`, `Wb` to be `0 \times 0` matrices. Further restrictions to the full normal form apply. We refer to [MirMor2009]_ IV Definition 4.6. for the details. INPUT: - ``G`` -- a symmetric `n` by `n` matrix in `\QQ` - ``p`` -- a prime number -- it is not checked whether it is prime - ``precision`` -- if not set, the minimal possible is taken - ``partial`` -- boolean (default: ``False``) if set, only the partial normal form is returned. OUTPUT: - ``D`` -- the jordan matrix over `\QQ_p` - ``B`` -- invertible transformation matrix over `\ZZ_p`, i.e, ``D = B * G * B^T`` EXAMPLES:: sage: from sage.quadratic_forms.genera.normal_form import p_adic_normal_form sage: D4 = Matrix(ZZ, 4, [2,-1,-1,-1,-1,2,0,0,-1,0,2,0,-1,0,0,2]) sage: D4 [ 2 -1 -1 -1] [-1 2 0 0] [-1 0 2 0] [-1 0 0 2] sage: D, B = p_adic_normal_form(D4, 2) sage: D [ 2 1 0 0] [ 1 2 0 0] [ 0 0 2^2 2] [ 0 0 2 2^2] sage: D == B * D4 * B.T True sage: A4 = Matrix(ZZ, 4, [2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2]) sage: A4 [ 2 -1 0 0] [-1 2 -1 0] [ 0 -1 2 -1] [ 0 0 -1 2] sage: D, B = p_adic_normal_form(A4, 2) sage: D [0 1 0 0] [1 0 0 0] [0 0 2 1] [0 0 1 2] We can handle degenerate forms:: sage: A4_extended = Matrix(ZZ, 5, [2, -1, 0, 0, -1, -1, 2, -1, 0, 0, 0, -1, 2, -1, 0, 0, 0, -1, 2, -1, -1, 0, 0, -1, 2]) sage: D, B = p_adic_normal_form(A4_extended, 5) sage: D [1 0 0 0 0] [0 1 0 0 0] [0 0 1 0 0] [0 0 0 5 0] [0 0 0 0 0] and denominators:: sage: A4dual = A4.inverse() sage: D, B = p_adic_normal_form(A4dual, 5) sage: D [5^-1 0 0 0] [ 0 1 0 0] [ 0 0 1 0] [ 0 0 0 1] TESTS:: sage: Z = Matrix(ZZ,0,[]) sage: p_adic_normal_form(Z, 3) ([], []) sage: Z = matrix.zero(10) sage: p_adic_normal_form(Z, 3)[0] == 0 True """ p = ZZ(p) # input checks!! G0, denom = G._clear_denom() d = denom.valuation(p) r = G0.rank() if r != G0.ncols(): U = G0.hermite_form(transformation=True)[1] else: U = G0.parent().identity_matrix() kernel = U[r:,:] nondeg = U[:r,:] # continue with the non-degenerate part G = nondeg * G * nondeg.T * p**d if precision == None: # in Zp(2) we have to calculate at least mod 8 for things to make sense. precision = G.det().valuation(p) + 4 R = Zp(p, prec = precision, type = 'fixed-mod') G = G.change_ring(R) G.set_immutable() # is not changed during computation D = copy(G) # is transformed into jordan form n = G.ncols() # The trivial case if n == 0: return G.parent().zero(), G.parent().zero() # the transformation matrix is called B B = Matrix.identity(R, n) if(p == 2): D, B = _jordan_2_adic(G) else: D, B = _jordan_odd_adic(G) D, B1 = _normalize(D) B = B1 * B # we have reached a normal form for p != 2 # for p == 2 extra work is necessary if p==2: D, B1 = _two_adic_normal_forms(D, partial=partial) B = B1 * B nondeg = B * nondeg B = nondeg.stack(kernel) D = Matrix.block_diagonal([D, Matrix.zero(kernel.nrows())]) if debug: assert B.determinant().valuation() == 0 # B is invertible! if p==2: assert B*G*B.T == Matrix.block_diagonal(collect_small_blocks(D)) else: assert B*G*B.T == Matrix.diagonal(D.diagonal()) return D/p**d, B
def __getitem__(self, key): r""" Return a slice of the sequence. EXAMPLES:: sage: C.<x> = CFiniteSequences(QQ) sage: r = C.from_recurrence([3,3],[2,1]) sage: r[2] 9 sage: r[101] 16158686318788579168659644539538474790082623100896663971001 sage: r = C(1/(1-x)) sage: r[5] 1 sage: r = C(x) sage: r[0] 0 sage: r[1] 1 sage: r = C(0) sage: r[66] 0 sage: lucas = C.from_recurrence([1,1],[2,1]) sage: lucas[5:10] [11, 18, 29, 47, 76] sage: r = C((2-x)/x/(1-x-x*x)) sage: r[0:4] [1, 3, 4, 7] sage: r = C(1-2*x^2) sage: r[0:4] [1, 0, -2, 0] sage: r[-1:4] # not tested, python will not allow this! [0, 1, 0 -2, 0] sage: r = C((-2*x^3 + x^2 + 1)/(-2*x + 1)) sage: r[0:5] # handle ogf > 1 [1, 2, 5, 8, 16] sage: r[-2] 0 sage: r = C((-2*x^3 + x^2 - x + 1)/(2*x^2 - 3*x + 1)) sage: r[0:5] [1, 2, 5, 9, 17] sage: s=C((1-x)/(-x^2 - x + 1)) sage: s[0:5] [1, 0, 1, 1, 2] sage: s=C((1+x^20+x^40)/(1-x^12)/(1-x^30)) sage: s[0:20] [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0] sage: s=C(1/((1-x^2)*(1-x^6)*(1-x^8)*(1-x^12))) sage: s[999998] 289362268629630 """ if isinstance(key, slice): m = max(key.start, key.stop) return [self[ii] for ii in xrange(*key.indices(m + 1))] elif isinstance(key, (int, Integer)): from sage.matrix.constructor import Matrix d = self._deg if self._off <= key and key < self._off + len(self._a): return self._a[key - self._off] elif d == 0: return 0 (quo, rem) = self.numerator().quo_rem(self.denominator()) wp = quo[key - self._off] if key < self._off: return wp A = Matrix(QQ, 1, d, self._c) B = Matrix.identity(QQ, d - 1) C = Matrix(QQ, d - 1, 1, 0) if quo == 0: V = Matrix(QQ, d, 1, self._a[:d][::-1]) else: V = Matrix(QQ, d, 1, self._aa[:d][::-1]) M = Matrix.block([[A], [B, C]], subdivide=False) return wp + list(M ** (key - self._off) * V)[d - 1][0] else: raise TypeError("invalid argument type")
def primary_decomposition(self): """ Return the primary decomposition of ``self``. .. NOTE:: ``self`` must be unitary, commutative and associative. OUTPUT: - a list consisting of the quotient maps ``self`` -> `A`, with `A` running through the primary factors of ``self`` EXAMPLES:: sage: A = FiniteDimensionalAlgebra(GF(3), [Matrix([[1, 0], [0, 1]]), Matrix([[0, 1], [0, 0]])]) sage: A.primary_decomposition() [Morphism from Finite-dimensional algebra of degree 2 over Finite Field of size 3 to Finite-dimensional algebra of degree 2 over Finite Field of size 3 given by matrix [1 0] [0 1]] sage: B = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0,0], [0,1,0], [0,0,0]]), Matrix([[0,1,0], [0,0,0], [0,0,0]]), Matrix([[0,0,0], [0,0,0], [0,0,1]])]) sage: B.primary_decomposition() [Morphism from Finite-dimensional algebra of degree 3 over Rational Field to Finite-dimensional algebra of degree 1 over Rational Field given by matrix [0] [0] [1], Morphism from Finite-dimensional algebra of degree 3 over Rational Field to Finite-dimensional algebra of degree 2 over Rational Field given by matrix [1 0] [0 1] [0 0]] """ k = self.base_ring() n = self.degree() if n == 0: return [] if not (self.is_unitary() and self.is_commutative() and (self._assume_associative or self.is_associative())): raise TypeError( "algebra must be unitary, commutative and associative") # Start with the trivial decomposition of self. components = [Matrix.identity(k, n)] for b in self.table(): # Use the action of the basis element b to refine our # decomposition of self. components_new = [] for c in components: # Compute the matrix of b on the component c, find its # characteristic polynomial, and factor it. b_c = c.solve_left(c * b) fact = b_c.characteristic_polynomial().factor() if len(fact) == 1: components_new.append(c) else: for f in fact: h, a = f e = h(b_c)**a ker_e = e.kernel().basis_matrix() components_new.append(ker_e * c) components = components_new quotients = [] for i in range(len(components)): I = Matrix(k, 0, n) for j, c in enumerate(components): if j != i: I = I.stack(c) quotients.append( self.quotient_map(self.ideal(I, given_by_matrix=True))) return quotients
def _diagonal_isometry(V, W): r""" Given two diagonal, rationally equivalent quadratic forms, computes a transition matrix mapping from one to the other. .. NOTE:: This function is an auxiliary method of ``isometry``, which is the method that should be called as it performs error-checking that is not present in this function. INPUT: - ``V`` -- a diagonal quadratic form - ``W`` -- a diagonal quadratic form OUTPUT: - A matrix ``T`` representing the isometry transformation, such that if ``VM`` is the gram matrix of ``V`` and ``WM`` is the gram matrix of ``W``, then ``VM == T.transpose() * WM * T`` yields ``True``. EXAMPLES:: sage: from sage.quadratic_forms.quadratic_form__equivalence_testing import _diagonal_isometry sage: Q = DiagonalQuadraticForm(QQ, [1, 2, 4]) sage: F = DiagonalQuadraticForm(QQ, [2, 2, 2]) sage: T = _diagonal_isometry(Q, F); T [ 0 1 0] [-1/2 0 1] [ 1/2 0 1] sage: Q.Gram_matrix() == T.T * F.Gram_matrix() * T True sage: T = _diagonal_isometry(F, Q); T [ 0 -1 -1] [ 1 0 0] [ 0 -1/2 1/2] sage: F.Gram_matrix() == T.T * Q.Gram_matrix() * T True """ import copy from sage.quadratic_forms.quadratic_form import DiagonalQuadraticForm from sage.matrix.constructor import Matrix from sage.modules.free_module_element import vector # We need to modify V and W, so copy them into Q and F respectively. Q, F = copy.deepcopy(V), copy.deepcopy(W) # Let FM denote the Gram matrix of F. FM = F.Gram_matrix() n = Q.dim() # This matrix represents a new basis for W, where the columns of the # matrix are the vectors of the basis. We initialize it to the standard basis. change_of_basis_matrix = Matrix.identity(QQ, n) # The goal of this loop is to obtain a new basis for W such that the # Gram matrix of V with respect to the standard basis equals the Gram matrix # of W with respect to the new basis. for i in range(n): # If the first terms are not equal... if Q.Gram_matrix()[0][0] != F.Gram_matrix()[0][0]: # Find a vector w in F such that F(w) equals the first term of Q. w = F.solve(Q.Gram_matrix()[0][0]) w = vector(QQ, i * [0] + w.list()) # We want to extend the basis of W to include the vector w. # Find a non-fixed vector in the current basis to replace by w. j = i # The new set of vectors must still be linearly independent (i.e. the matrix is non-singular). while True: temp_matrix = Matrix(change_of_basis_matrix) temp_matrix.set_column(j, change_of_basis_matrix * w) if not temp_matrix.is_singular(): break j = j + 1 change_of_basis_matrix = temp_matrix # We want to fix w to be the basis vector at position i, so swap it with whatever is already there. col = change_of_basis_matrix.column(i) change_of_basis_matrix.set_column(i, change_of_basis_matrix.column(j)) change_of_basis_matrix.set_column(j, col) # Orthogonalize the basis. change_of_basis_matrix = _gram_schmidt(change_of_basis_matrix, i, W.bilinear_map) # Obtain the diagonal gram matrix of F. FM = W(change_of_basis_matrix).Gram_matrix_rational() # Now we have that QM[0][0] == FM[0][0] where QM and FM are the Gram matrices # of Q and F respectively. We remove the first variable from each form and continue. F = DiagonalQuadraticForm(F.base_ring(), FM.diagonal()) F = F.extract_variables(range(i + 1, F.dim())) Q = Q.extract_variables(range(1, Q.dim())) return change_of_basis_matrix