def apply_matrix(self, m, in_place=True, mapping=False): r""" Carry out the GL(2,R) action of m on this surface and return the result. If in_place=True, then this is done in place and changes the surface. This can only be carried out if the surface is finite and mutable. If mapping=True, then we return a GL2RMapping between this surface and its image. In this case in_place must be False. If in_place=False, then a copy is made before the deformation. """ if mapping == True: assert in_place == False, "Can not modify in place and return a mapping." return GL2RMapping(self, m) if not in_place: if self.is_finite(): from sage.structure.element import get_coercion_model cm = get_coercion_model() field = cm.common_parent(self.base_ring(), m.base_ring()) s = self.copy(mutable=True, new_field=field) return s.apply_matrix(m) else: return m * self else: # Make sure m is in the right state from sage.matrix.constructor import Matrix m = Matrix(self.base_ring(), 2, 2, m) assert m.det() != self.base_ring().zero( ), "Can not deform by degenerate matrix." assert self.is_finite( ), "In place GL(2,R) action only works for finite surfaces." us = self.underlying_surface() assert us.is_mutable( ), "In place changes only work for mutable surfaces." for label in self.label_iterator(): us.change_polygon(label, m * self.polygon(label)) if m.det() < self.base_ring().zero(): # Polygons were all reversed orientation. Need to redo gluings. # First pass record new gluings in a dictionary. new_glue = {} seen_labels = set() for p1 in self.label_iterator(): n1 = self.polygon(p1).num_edges() for e1 in range(n1): p2, e2 = self.opposite_edge(p1, e1) n2 = self.polygon(p2).num_edges() if p2 in seen_labels: pass elif p1 == p2 and e1 > e2: pass else: new_glue[(p1, n1 - 1 - e1)] = (p2, n2 - 1 - e2) seen_labels.add(p1) # Second pass: reassign gluings for (p1, e1), (p2, e2) in iteritems(new_glue): us.change_edge_gluing(p1, e1, p2, e2) return self
def apply_matrix(self,m,in_place=True, mapping=False): r""" Carry out the GL(2,R) action of m on this surface and return the result. If in_place=True, then this is done in place and changes the surface. This can only be carried out if the surface is finite and mutable. If mapping=True, then we return a GL2RMapping between this surface and its image. In this case in_place must be False. If in_place=False, then a copy is made before the deformation. """ if mapping==True: assert in_place==False, "Can not modify in place and return a mapping." return GL2RMapping(self, m) if not in_place: if self.is_finite(): from sage.structure.element import get_coercion_model cm = get_coercion_model() field = cm.common_parent(self.base_ring(), m.base_ring()) s=self.copy(mutable=True, new_field=field) return s.apply_matrix(m) else: return m*self else: # Make sure m is in the right state from sage.matrix.constructor import Matrix m=Matrix(self.base_ring(), 2, 2, m) assert m.det()!=self.base_ring().zero(), "Can not deform by degenerate matrix." assert self.is_finite(), "In place GL(2,R) action only works for finite surfaces." us=self.underlying_surface() assert us.is_mutable(), "In place changes only work for mutable surfaces." for label in self.label_iterator(): us.change_polygon(label,m*self.polygon(label)) if m.det()<self.base_ring().zero(): # Polygons were all reversed orientation. Need to redo gluings. # First pass record new gluings in a dictionary. new_glue={} seen_labels=set() for p1 in self.label_iterator(): n1=self.polygon(p1).num_edges() for e1 in xrange(n1): p2,e2=self.opposite_edge(p1,e1) n2=self.polygon(p2).num_edges() if p2 in seen_labels: pass elif p1==p2 and e1>e2: pass else: new_glue[(p1, n1-1-e1)]=(p2, n2-1-e2) seen_labels.add(p1) # Second pass: reassign gluings for (p1,e1),(p2,e2) in new_glue.iteritems(): us.change_edge_gluing(p1,e1,p2,e2) return self
def _normalize_2x2(G): r""" Normalize this indecomposable `2` by `2` block. INPUT: ``G`` - a `2` by `2` matrix over `\ZZ_p` with ``type = 'fixed-mod'`` of the form:: [2a b] [ b 2c] * 2^n with `b` of valuation 1. OUTPUT: A unimodular `2` by `2` matrix ``B`` over `\ZZ_p` with ``B * G * B.transpose()`` either:: [0 1] [2 1] [1 0] * 2^n or [1 2] * 2^n EXAMPLES:: sage: from sage.quadratic_forms.genera.normal_form import _normalize_2x2 sage: R = Zp(2, prec = 15, type = 'fixed-mod', print_mode='series', show_prec=False) sage: G = Matrix(R, 2, [-17*2,3,3,23*2]) sage: B =_normalize_2x2(G) sage: B * G * B.T [2 1] [1 2] sage: G = Matrix(R,2,[-17*4,3,3,23*2]) sage: B = _normalize_2x2(G) sage: B*G*B.T [0 1] [1 0] sage: G = 2^3 * Matrix(R, 2, [-17*2,3,3,23*2]) sage: B = _normalize_2x2(G) sage: B * G * B.T [2^4 2^3] [2^3 2^4] """ from sage.rings.all import PolynomialRing from sage.modules.free_module_element import vector B = copy(G.parent().identity_matrix()) R = G.base_ring() P = PolynomialRing(R, 'x') x = P.gen() # The input must be an even block odd1 = (G[0, 0].valuation() < G[1, 0].valuation()) odd2 = (G[1, 1].valuation() < G[1, 0].valuation()) if odd1 or odd2: raise ValueError("Not a valid 2 x 2 block.") scale = 2**G[0, 1].valuation() D = Matrix(R, 2, 2, [d // scale for d in G.list()]) # now D is of the form # [2a b ] # [b 2c] # where b has valuation 1. G = copy(D) # Make sure G[1, 1] has valuation 1. if D[1, 1].valuation() > D[0, 0].valuation(): B.swap_columns(0, 1) D.swap_columns(0, 1) D.swap_rows(0, 1) if D[1, 1].valuation() != 1: # this works because # D[0, 0] has valuation at least 2 B[1, :] += B[0, :] D = B * G * B.transpose() assert D[1, 1].valuation() == 1 if mod(D.det(), 8) == 3: # in this case we can transform D to # 2 1 # 1 2 # Find a point of norm 2 # solve: 2 == D[1,1]*x^2 + 2*D[1,0]*x + D[0,0] pol = (D[1, 1] * x**2 + 2 * D[1, 0] * x + D[0, 0] - 2) // 2 # somehow else pari can get a hickup see trac #24065 pol = pol // pol.leading_coefficient() sol = pol.roots()[0][0] B[0, 1] = sol D = B * G * B.transpose() # make D[0, 1] = 1 B[1, :] *= D[1, 0].inverse_of_unit() D = B * G * B.transpose() # solve: v*D*v == 2 with v = (x, -2*x+1) if D[1, 1] != 2: v = vector([x, -2 * x + 1]) pol = (v * D * v - 2) // 2 # somehow else pari can get a hickup see trac #24065 pol = pol // pol.leading_coefficient() sol = pol.roots()[0][0] B[1, :] = sol * B[0, :] + (-2 * sol + 1) * B[1, :] D = B * G * B.transpose() # check the result assert D == Matrix(G.parent(), 2, 2, [2, 1, 1, 2]), "D1 \n %r" % D elif mod(D.det(), 8) == 7: # in this case we can transform D to # 0 1 # 1 0 # Find a point representing 0 # solve: 0 == D[1,1]*x^2 + 2*D[1,0]*x + D[0,0] pol = (D[1, 1] * x**2 + 2 * D[1, 0] * x + D[0, 0]) // 2 # somehow else pari can get a hickup, see trac #24065 pol = pol // pol.leading_coefficient() sol = pol.roots()[0][0] B[0, :] += sol * B[1, :] D = B * G * B.transpose() # make the second basis vector have 0 square as well. B[1, :] = B[1, :] - D[1, 1] // (2 * D[0, 1]) * B[0, :] D = B * G * B.transpose() # rescale to get D[0,1] = 1 B[0, :] *= D[1, 0].inverse_of_unit() D = B * G * B.transpose() # check the result assert D == Matrix(G.parent(), 2, 2, [0, 1, 1, 0]), "D2 \n %r" % D return B
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 _normalize_2x2(G): r""" Normalize this indecomposable `2` by `2` block. INPUT: ``G`` - a `2` by `2` matrix over `\ZZ_p` with ``type = 'fixed-mod'`` of the form:: [2a b] [ b 2c] * 2^n with `b` of valuation 1. OUTPUT: A unimodular `2` by `2` matrix ``B`` over `\ZZ_p` with ``B * G * B.transpose()`` either:: [0 1] [2 1] [1 0] * 2^n or [1 2] * 2^n EXAMPLES:: sage: from sage.quadratic_forms.genera.normal_form import _normalize_2x2 sage: R = Zp(2, prec = 15, type = 'fixed-mod', print_mode='series', show_prec=False) sage: G = Matrix(R, 2, [-17*2,3,3,23*2]) sage: B =_normalize_2x2(G) sage: B * G * B.T [2 1] [1 2] sage: G = Matrix(R,2,[-17*4,3,3,23*2]) sage: B = _normalize_2x2(G) sage: B*G*B.T [0 1] [1 0] sage: G = 2^3 * Matrix(R, 2, [-17*2,3,3,23*2]) sage: B = _normalize_2x2(G) sage: B * G * B.T [2^4 2^3] [2^3 2^4] """ from sage.rings.all import PolynomialRing from sage.modules.free_module_element import vector B = copy(G.parent().identity_matrix()) R = G.base_ring() P = PolynomialRing(R, 'x') x = P.gen() # The input must be an even block odd1 = (G[0, 0].valuation() < G[1, 0].valuation()) odd2 = (G[1, 1].valuation() < G[1, 0].valuation()) if odd1 or odd2: raise ValueError("Not a valid 2 x 2 block.") scale = 2 ** G[0,1].valuation() D = Matrix(R, 2, 2, [d // scale for d in G.list()]) # now D is of the form # [2a b ] # [b 2c] # where b has valuation 1. G = copy(D) # Make sure G[1, 1] has valuation 1. if D[1, 1].valuation() > D[0, 0].valuation(): B.swap_columns(0, 1) D.swap_columns(0, 1) D.swap_rows(0, 1) if D[1, 1].valuation() != 1: # this works because # D[0, 0] has valuation at least 2 B[1, :] += B[0, :] D = B * G * B.transpose() assert D[1, 1].valuation() == 1 if mod(D.det(), 8) == 3: # in this case we can transform D to # 2 1 # 1 2 # Find a point of norm 2 # solve: 2 == D[1,1]*x^2 + 2*D[1,0]*x + D[0,0] pol = (D[1,1]*x**2 + 2*D[1,0]*x + D[0,0]-2) // 2 # somehow else pari can get a hickup see `trac`:#24065 pol = pol // pol.leading_coefficient() sol = pol.roots()[0][0] B[0, 1] = sol D = B * G * B.transpose() # make D[0, 1] = 1 B[1, :] *= D[1, 0].inverse_of_unit() D = B * G * B.transpose() # solve: v*D*v == 2 with v = (x, -2*x+1) if D[1, 1] != 2: v = vector([x, -2*x + 1]) pol = (v*D*v - 2) // 2 # somehow else pari can get a hickup `trac`:#24065 pol = pol // pol.leading_coefficient() sol = pol.roots()[0][0] B[1, :] = sol * B[0,:] + (-2*sol + 1)*B[1, :] D = B * G * B.transpose() # check the result assert D == Matrix(G.parent(), 2, 2, [2, 1, 1, 2]), "D1 \n %r" %D elif mod(D.det(), 8) == 7: # in this case we can transform D to # 0 1 # 1 0 # Find a point representing 0 # solve: 0 == D[1,1]*x^2 + 2*D[1,0]*x + D[0,0] pol = (D[1,1]*x**2 + 2*D[1,0]*x + D[0,0])//2 # somehow else pari can get a hickup, see `trac`:#24065 pol = pol // pol.leading_coefficient() sol = pol.roots()[0][0] B[0,:] += sol*B[1, :] D = B * G * B.transpose() # make the second basis vector have 0 square as well. B[1, :] = B[1, :] - D[1, 1]//(2*D[0, 1])*B[0,:] D = B * G * B.transpose() # rescale to get D[0,1] = 1 B[0, :] *= D[1, 0].inverse_of_unit() D = B * G * B.transpose() # check the result assert D == Matrix(G.parent(), 2, 2, [0, 1, 1, 0]), "D2 \n %r" %D return B
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