def _complex_relative(self,n,*args):
   if len(args)==2:
     complex = []
     for i in range(args[0],args[1]):
       complex.append(self._complex_relative(n,i))
     return complex
   if n<0 or n>self.poly_ring.ngens():
     return []
   if n==0:
     hom_basis = self._p_graded_module(n).homogeneous_part_basis(self.degree+args[0])
     return [LogarithmicDifferentialForm(n,b,self) for b in hom_basis]
   base = self._p_graded_module(n).homogeneous_part_basis(self.degree+args[0])
   if len(base)==0:
     return []
   vs_base = VectorSpace(QQ,len(base))
   df_base = [LogarithmicDifferentialForm(n,b,self) for b in base]
   pre_base = self._p_graded_module(n-1).homogeneous_part_basis(self.degree+args[0])
   if len(pre_base)==0:
     return df_base
   dh = [self.divisor.derivative(g) for g in self.poly_ring.gens()]
   dh = LogarithmicDifferentialForm(1,dh,self)
   rel_gens = []
   for b in pre_base:
     b_form = LogarithmicDifferentialForm(n-1,b,self)
     w = dh.wedge(b_form)
     rel_gens.append(lift_to_basis([w],df_base))
   rel = vs_base.subspace(rel_gens)
   comp = orth_complement(vs_base,rel)
   #Lift
   rel_complex = []
   for vec in comp.basis():
     rel_complex.append(_weighted_sum(vec,df_base,self))
   return rel_complex
Beispiel #2
0
 def __init__(self, surface, label, point, ring=None, limit=None):
     self._s = surface
     if ring is None:
         self._ring = surface.base_ring()
     else:
         self._ring = ring
     p = surface.polygon(label)
     point = VectorSpace(self._ring, 2)(point)
     point.set_immutable()
     pos = p.get_point_position(point)
     assert pos.is_inside(), \
         "Point must be positioned within the polygon with the given label."
     # This is the correct thing if point lies in the interior of the polygon with the given label.
     self._coordinate_dict = {label: {point}}
     if pos.is_in_edge_interior():
         label2, e2 = surface.opposite_edge(label, pos.get_edge())
         point2 = surface.edge_transformation(label, pos.get_edge())(point)
         point2.set_immutable()
         if label2 in self._coordinate_dict:
             self._coordinate_dict[label2].add(point2)
         else:
             self._coordinate_dict[label2] = {point2}
     if pos.is_vertex():
         self._coordinate_dict = {}
         sing = surface.singularity(label, pos.get_vertex(), limit=limit)
         for l, v in sing.vertex_set():
             new_point = surface.polygon(l).vertex(v)
             new_point.set_immutable()
             if l in self._coordinate_dict:
                 self._coordinate_dict[l].add(new_point)
             else:
                 self._coordinate_dict[l] = {new_point}
     # Freeze the sets.
     for label, point_set in iteritems(self._coordinate_dict):
         self._coordinate_dict[label] = frozenset(point_set)
 def homology(self,varient="complement",*args):
   hom = {}
   cc = self.chain_complex(varient,(-1,self.poly_ring.ngens()+1),*args)
   #Some need wider return range
   for i in range(self.poly_ring.ngens()+1):
     vs_am = VectorSpace(QQ,len(cc[i]))
     if len(cc[i])==0:
       hom[i] = []
       continue
     if len(cc[i-1])!=0:
       d_im = []
       for b in cc[i-1]:
         d_b = self.differential(b,varient,*args)
         d_im.append(lift_to_basis(d_b,cc[i]))
       img = vs_am.subspace(d_im)
     else:
       img = vs_am.subspace([vs_am.zero()])
     if len(cc[i+1])!=0:
       d_ker = []
       for b in cc[i]:
         d_b = self.differential(b,varient,*args)
         d_ker.append(lift_to_basis(d_b,cc[i+1]))
       ker = (matrix(QQ,d_ker)).left_kernel()
     else:
       ker = vs_am
     quo = ker.quotient(img)
     hom[i] = []
     for b in quo.basis():
       vec = quo.lift(b)
       part_sum = LogarithmicDifferentialForm.make_zero(i,self)
       for c,f in zip(vec,cc[i]):
         part_sum = part_sum + c*f
       hom[i].append(part_sum)
   return hom
def ProjectiveGeometryDesign(n, d, F, algorithm=None):
    """
    Returns a projective geometry design.

    A projective geometry design of parameters `n,d,F` has for points the lines
    of `F^{n+1}`, and for blocks the `d+1`-dimensional subspaces of `F^{n+1}`,
    each of which contains `\\frac {|F|^{d+1}-1} {|F|-1}` lines.

    INPUT:

    - ``n`` is the projective dimension

    - ``d`` is the dimension of the subspaces of `P = PPn(F)` which
      make up the blocks.

    - ``F`` is a finite field.

    - ``algorithm`` -- set to ``None`` by default, which results in using Sage's
      own implementation. In order to use GAP's implementation instead (i.e. its
      ``PGPointFlatBlockDesign`` function) set ``algorithm="gap"``. Note that
      GAP's "design" package must be available in this case, and that it can be
      installed with the ``gap_packages`` spkg.

    EXAMPLES:

    The points of the following design are the `\\frac {2^{2+1}-1} {2-1}=7`
    lines of `\mathbb{Z}_2^{2+1}`. It has `7` blocks, corresponding to each
    2-dimensional subspace of `\mathbb{Z}_2^{2+1}`::

        sage: designs.ProjectiveGeometryDesign(2, 1, GF(2))
        Incidence structure with 7 points and 7 blocks
        sage: BD = designs.ProjectiveGeometryDesign(2, 1, GF(2), algorithm="gap") # optional - gap_packages (design package)
        sage: BD.is_block_design()                                     # optional - gap_packages (design package)
        (True, [2, 7, 3, 1])
    """
    q = F.order()
    from sage.interfaces.gap import gap, GapElement
    from sage.sets.set import Set
    if algorithm is None:
        V = VectorSpace(F, n + 1)
        points = list(V.subspaces(1))
        flats = list(V.subspaces(d + 1))
        blcks = []
        for p in points:
            b = []
            for i in range(len(flats)):
                if p.is_subspace(flats[i]):
                    b.append(i)
            blcks.append(b)
        v = (q**(n + 1) - 1) / (q - 1)
        return BlockDesign(v, blcks, name="ProjectiveGeometryDesign")
    if algorithm == "gap":  # Requires GAP's Design
        gap.load_package("design")
        gap.eval("D := PGPointFlatBlockDesign( %s, %s, %d )" % (n, q, d))
        v = eval(gap.eval("D.v"))
        gblcks = eval(gap.eval("D.blocks"))
        gB = []
        for b in gblcks:
            gB.append([x - 1 for x in b])
        return BlockDesign(v, gB, name="ProjectiveGeometryDesign")
 def __init__(self, surface, label, point, ring = None, limit=None):
     self._s = surface
     if ring is None:
         self._ring = surface.base_ring()
     else:
         self._ring = ring
     p = surface.polygon(label)
     point = VectorSpace(self._ring,2)(point)
     point.set_immutable()
     pos = p.get_point_position(point)
     assert pos.is_inside(), \
         "Point must be positioned within the polygon with the given label."
     # This is the correct thing if point lies in the interior of the polygon with the given label.
     self._coordinate_dict = {label: {point}}
     if pos.is_in_edge_interior():
         label2,e2 = surface.opposite_edge(label, pos.get_edge())
         point2 = surface.edge_transformation(label, pos.get_edge())(point)
         point2.set_immutable()
         if label2 in self._coordinate_dict:
             self._coordinate_dict[label2].add(point2)
         else:
             self._coordinate_dict[label2]={point2}
     if pos.is_vertex():
         self._coordinate_dict = {}
         sing = surface.singularity(label, pos.get_vertex(), limit=limit)
         for l,v in sing.vertex_set():
             new_point = surface.polygon(l).vertex(v)
             new_point.set_immutable()
             if l in self._coordinate_dict:
                 self._coordinate_dict[l].add(new_point)
             else:
                 self._coordinate_dict[l] = {new_point}
     # Freeze the sets.
     for label,point_set in self._coordinate_dict.iteritems():
         self._coordinate_dict[label] = frozenset(point_set)
Beispiel #6
0
def _puncture(v, points):
    r"""
    Returns v punctured as the positions listed in ``points``.

    INPUT:

    - ``v`` -- a vector or a list of vectors

    - ``points`` -- a set of integers, or an integer

    EXAMPLES::

        sage: v = vector(GF(7), (2,3,0,2,1,5,1,5,6,5,3))
        sage: from sage.coding.punctured_code import _puncture
        sage: _puncture(v, {4, 3})
        (2, 3, 0, 5, 1, 5, 6, 5, 3)
    """
    if not isinstance(points, (Integer, int, set)):
        raise TypeError(
            "points must be either a Sage Integer, a Python int, or a set")
    if isinstance(v, list):
        size = len(v[0])
        S = VectorSpace(v[0].base_ring(), size - len(points))
        l = []
        for i in v:
            new_v = [i[j] for j in range(size) if j not in points]
            l.append(S(new_v))
        return l
    S = VectorSpace(v.base_ring(), len(v) - len(points))
    new_v = [v[i] for i in range(len(v)) if i not in points]
    return S(new_v)
Beispiel #7
0
def nonisomorphic_cubes_Z2(n, avoid_complete=False):
  """
  Returns a generator for all n-dimensional Cube-like graphs
  (Cayley graphs over Z_2^n) with their generators.
  With avoid_complete=True avoids the complete graph.
  Iterates over tuples (generatorSet, G).
  """
  vs = VectorSpace(GF(2), n)
  basegens = vs.gens()
  optgens = [v for v in vs if sum(map(int,v)) >= 2]
  total = 2**len(optgens)
  seen_graphs = set()
  c = 0
  for g in powerset(optgens):
    c += 1
    gens = tuple(list(basegens) + g)
    if c % (total / 100 + 1) == 0:
      log.debug("Generating (%d of %d)" % (c, total))
    if avoid_complete:
      if len(g) >= len(optgens):
        continue
    G = CayleyGraph(vs, gens)

    canon = tuple(Graph(G).canonical_label().edges())
    if canon in seen_graphs:
      continue
    log.debug("Unique graph (%d of %d) gens=%s" % (c, total, gens))
    seen_graphs.add(canon)

    yield (gens, G)
 def test_satisfy_inter(self):
   v_space = VectorSpace(QQ,4)
   sub = v_space.subspace([[1,-1,1,1],[2,-3,4,5]])
   comp = orth_complement(v_space,sub)
   zero = v_space.subspace([v_space.zero()])
   inter = sub.intersection(comp)
   self.assertEqual(zero,inter)
Beispiel #9
0
def ProjectiveGeometryDesign(n, d, F, algorithm=None):
    """
    Returns a projective geometry design.

    A projective geometry design of parameters `n,d,F` has for points the lines
    of `F^{n+1}`, and for blocks the `d+1`-dimensional subspaces of `F^{n+1}`,
    each of which contains `\\frac {|F|^{d+1}-1} {|F|-1}` lines.

    INPUT:

    - ``n`` is the projective dimension

    - ``d`` is the dimension of the subspaces of `P = PPn(F)` which
      make up the blocks.

    - ``F`` is a finite field.

    - ``algorithm`` -- set to ``None`` by default, which results in using Sage's
      own implementation. In order to use GAP's implementation instead (i.e. its
      ``PGPointFlatBlockDesign`` function) set ``algorithm="gap"``. Note that
      GAP's "design" package must be available in this case, and that it can be
      installed with the ``gap_packages`` spkg.

    EXAMPLES:

    The points of the following design are the `\\frac {2^{2+1}-1} {2-1}=7`
    lines of `\mathbb{Z}_2^{2+1}`. It has `7` blocks, corresponding to each
    2-dimensional subspace of `\mathbb{Z}_2^{2+1}`::

        sage: designs.ProjectiveGeometryDesign(2, 1, GF(2))
        Incidence structure with 7 points and 7 blocks
        sage: BD = designs.ProjectiveGeometryDesign(2, 1, GF(2), algorithm="gap") # optional - gap_packages (design package)
        sage: BD.is_block_design()                                     # optional - gap_packages (design package)
        (True, [2, 7, 3, 1])
    """
    q = F.order()
    from sage.interfaces.gap import gap, GapElement
    from sage.sets.set import Set
    if algorithm is None:
        V = VectorSpace(F, n+1)
        points = list(V.subspaces(1))
        flats = list(V.subspaces(d+1))
        blcks = []
        for p in points:
            b = []
            for i in range(len(flats)):
                if p.is_subspace(flats[i]):
                    b.append(i)
            blcks.append(b)
        v = (q**(n+1)-1)/(q-1)
        return BlockDesign(v, blcks, name="ProjectiveGeometryDesign")
    if algorithm == "gap":   # Requires GAP's Design
        gap.load_package("design")
        gap.eval("D := PGPointFlatBlockDesign( %s, %s, %d )"%(n,q,d))
        v = eval(gap.eval("D.v"))
        gblcks = eval(gap.eval("D.blocks"))
        gB = []
        for b in gblcks:
            gB.append([x-1 for x in b])
        return BlockDesign(v, gB, name="ProjectiveGeometryDesign")
Beispiel #10
0
def nonisomorphic_cubes_Z2(n, avoid_complete=False):
    """
  Returns a generator for all n-dimensional Cube-like graphs
  (Cayley graphs over Z_2^n) with their generators.
  With avoid_complete=True avoids the complete graph.
  Iterates over tuples (generatorSet, G).
  """
    vs = VectorSpace(GF(2), n)
    basegens = vs.gens()
    optgens = [v for v in vs if sum(map(int, v)) >= 2]
    total = 2**len(optgens)
    seen_graphs = set()
    c = 0
    for g in powerset(optgens):
        c += 1
        gens = tuple(list(basegens) + g)
        if c % (total / 100 + 1) == 0:
            log.debug("Generating (%d of %d)" % (c, total))
        if avoid_complete:
            if len(g) >= len(optgens):
                continue
        G = CayleyGraph(vs, gens)

        canon = tuple(Graph(G).canonical_label().edges())
        if canon in seen_graphs:
            continue
        log.debug("Unique graph (%d of %d) gens=%s" % (c, total, gens))
        seen_graphs.add(canon)

        yield (gens, G)
Beispiel #11
0
def ProjectiveGeometryDesign(n, d, F, algorithm=None):
    """
    INPUT:

    - ``n`` is the projective dimension

    - ``v`` is the number of points `PPn(GF(q))`

    - ``d`` is the dimension of the subspaces of `P = PPn(GF(q))` which
      make up the blocks

    - ``b`` is the number of `d`-dimensional subspaces of `P`

    Wraps GAP Design's PGPointFlatBlockDesign. Does *not* require
    GAP's Design.

    EXAMPLES::

        sage: designs.ProjectiveGeometryDesign(2, 1, GF(2))
        Incidence structure with 7 points and 7 blocks
        sage: BD = designs.ProjectiveGeometryDesign(2, 1, GF(2), algorithm="gap") # optional - gap_packages (design package)
        sage: BD.is_block_design()                                     # optional - gap_packages (design package)
        (True, [2, 7, 3, 1])
    """
    q = F.order()
    from sage.interfaces.gap import gap, GapElement
    from sage.sets.set import Set
    if algorithm == None:
        V = VectorSpace(F, n+1)
        points = list(V.subspaces(1))
        flats = list(V.subspaces(d+1))
        blcks = []
        for p in points:
            b = []
            for i in range(len(flats)):
                if p.is_subspace(flats[i]):
                    b.append(i)
            blcks.append(b)
        v = (q**(n+1)-1)/(q-1)
        return BlockDesign(v, blcks, name="ProjectiveGeometryDesign")
    if algorithm == "gap":   # Requires GAP's Design
        gap.load_package("design")
        gap.eval("D := PGPointFlatBlockDesign( %s, %s, %d )"%(n,q,d))
        v = eval(gap.eval("D.v"))
        gblcks = eval(gap.eval("D.blocks"))
        gB = []
        for b in gblcks:
            gB.append([x-1 for x in b])
        return BlockDesign(v, gB, name="ProjectiveGeometryDesign")
Beispiel #12
0
def ProjectiveGeometryDesign(n, d, F, algorithm=None):
    """
    INPUT:

    - ``n`` is the projective dimension

    - ``v`` is the number of points `PPn(GF(q))`

    - ``d`` is the dimension of the subspaces of `P = PPn(GF(q))` which
      make up the blocks

    - ``b`` is the number of `d`-dimensional subspaces of `P`

    Wraps GAP Design's PGPointFlatBlockDesign. Does *not* require
    GAP's Design.

    EXAMPLES::

        sage: designs.ProjectiveGeometryDesign(2, 1, GF(2))
        Incidence structure with 7 points and 7 blocks
        sage: BD = designs.ProjectiveGeometryDesign(2, 1, GF(2), algorithm="gap") # optional - gap_packages (design package)
        sage: BD.is_block_design()                                     # optional - gap_packages (design package)
        (True, [2, 7, 3, 1])
    """
    q = F.order()
    from sage.interfaces.gap import gap, GapElement
    from sage.sets.set import Set
    if algorithm == None:
        V = VectorSpace(F, n + 1)
        points = list(V.subspaces(1))
        flats = list(V.subspaces(d + 1))
        blcks = []
        for p in points:
            b = []
            for i in range(len(flats)):
                if p.is_subspace(flats[i]):
                    b.append(i)
            blcks.append(b)
        v = (q**(n + 1) - 1) / (q - 1)
        return BlockDesign(v, blcks, name="ProjectiveGeometryDesign")
    if algorithm == "gap":  # Requires GAP's Design
        gap.load_package("design")
        gap.eval("D := PGPointFlatBlockDesign( %s, %s, %d )" % (n, q, d))
        v = eval(gap.eval("D.v"))
        gblcks = eval(gap.eval("D.blocks"))
        gB = []
        for b in gblcks:
            gB.append([x - 1 for x in b])
        return BlockDesign(v, gB, name="ProjectiveGeometryDesign")
    def ambient_vector_space(self):
        """
        Return the ambient vector space.

        .. SEEALSO::

            :meth:`ambient_module`

        OUTPUT:

        The vector space (over the fraction field of the base ring)
        where the linear expressions live.

        EXAMPLES::

            sage: from sage.geometry.linear_expression import LinearExpressionModule
            sage: L = LinearExpressionModule(QQ, ('x', 'y', 'z'))
            sage: L.ambient_vector_space()
            Vector space of dimension 3 over Rational Field
            sage: M = LinearExpressionModule(ZZ, ('r', 's'))
            sage: M.ambient_module()
            Ambient free module of rank 2 over the principal ideal domain Integer Ring
            sage: M.ambient_vector_space()
            Vector space of dimension 2 over Rational Field
        """
        from sage.modules.free_module import VectorSpace
        field = self.base_ring().fraction_field()
        return VectorSpace(field, self.ngens())
Beispiel #14
0
    def Vrepresentation_space(self):
        r"""
        Return the ambient vector space.

        This is the vector space or module containing the
        Vrepresentation vectors.

        OUTPUT:

        A free module over the base ring of dimension :meth:`ambient_dim`.

        EXAMPLES::

            sage: from sage.geometry.polyhedron.parent import Polyhedra
            sage: Polyhedra(QQ, 4).Vrepresentation_space()
            Vector space of dimension 4 over Rational Field
            sage: Polyhedra(QQ, 4).ambient_space()
            Vector space of dimension 4 over Rational Field
        """
        if self.base_ring() in Fields():
            from sage.modules.free_module import VectorSpace
            return VectorSpace(self.base_ring(), self.ambient_dim())
        else:
            from sage.modules.free_module import FreeModule
            return FreeModule(self.base_ring(), self.ambient_dim())
Beispiel #15
0
    def __init__(self, space, number_errors, number_erasures):
        r"""


        TESTS:

        If the sum of number of errors and number of erasures
        exceeds (or may exceed, in the case of tuples) the dimension of the input space,
        it will return an error::

            sage: n_err, n_era = 21, 21
            sage: Chan = channels.ErrorErasureChannel(GF(59)^40, n_err, n_era)
            Traceback (most recent call last):
            ...
            ValueError: The total number of errors and erasures can not exceed the dimension of the input space
        """
        if isinstance(number_errors, (Integer, int)):
            number_errors = (number_errors, number_errors)
        if not isinstance(number_errors, (tuple, list)):
            raise ValueError("number_errors must be a tuple, a list, an Integer or a Python int")

        if isinstance(number_erasures, (Integer, int)):
            number_erasures = (number_erasures, number_erasures)
        if not isinstance(number_erasures, (tuple, list)):
            raise ValueError("number_erasures must be a tuple, a list, an Integer or a Python int")

        output_space = cartesian_product([space, VectorSpace(GF(2), space.dimension())])
        super(ErrorErasureChannel, self).__init__(space, output_space)
        if number_errors[1] + number_erasures[1] > space.dimension():
            raise ValueError("The total number of errors and erasures can not exceed the dimension of the input space")
        self._number_errors = number_errors
        self._number_erasures = number_erasures
 def _differential_relative(self,form,*args):
   n = form.degree
   deg = self._p_graded_module(n).total_degree(form.vec)
   deg -= self.degree
   der = form.derivative()
   full = self._p_graded_module(n+1).homogeneous_part_basis(self.degree+deg)
   full_forms = [LogarithmicDifferentialForm(n+1,b,self) for b in full]
   full_space = VectorSpace(QQ,len(full))
   target_comp = self._complex_relative(n+1,deg)
   comp_vecs = []
   for b in target_comp:
     comp_vecs.append(lift_to_basis([b],full_forms))
   comp_vecs = full_space.subspace(comp_vecs)
   lift_full = lift_to_basis([der],full_forms)
   lift_prog = comp_vecs.basis_matrix()*vector(lift_full)
   return [_weighted_sum(lift_prog,target_comp,self)]
Beispiel #17
0
    def _repr_vector_space(self, dim):
        """
        Return a string representation of the vector space of given dimension

        INPUT:

        - ``dim`` -- integer.

        OUTPUT:

        String representation of the vector space of dimension ``dim``.

        EXAMPLES::

            sage: F = FilteredVectorSpace(3, base_ring=RDF)
            sage: F._repr_vector_space(1234)
            'RDF^1234'
            sage: F3 = FilteredVectorSpace(3, base_ring=GF(3))
            sage: F3._repr_vector_space(1234)
            'GF(3)^1234'
            sage: F3 = FilteredVectorSpace(3, base_ring=AA)
            sage: F3._repr_vector_space(1234)
            'Vector space of dimension 1234 over Algebraic Real Field'
        """
        if dim == 0:
            return '0'
        try:
            return self._repr_field_name() + '^' + str(dim)
        except NotImplementedError:
            return repr(VectorSpace(self.base_ring(), dim))
Beispiel #18
0
    def __init__(self, center, radius_squared, base_ring=None):
        r"""
        Construct a circle from a Vector representing the center, and the
        radius squared.
        """
        if base_ring is None:
            self._base_ring = radius_squared.parent()
        else:
            self._base_ring = base_ring

        # for calculations:
        self._V2 = VectorSpace(self._base_ring,2)
        self._V3 = VectorSpace(self._base_ring,3)

        self._center = self._V2(center)
        self._radius_squared = self._base_ring(radius_squared)
Beispiel #19
0
 def __init__(self, similarity_surface, ring=None):
     self._s = similarity_surface
     if ring is None:
         self._base_ring = self._s.base_ring()
     else:
         self._base_ring = ring
     from sage.modules.free_module import VectorSpace
     self._V = VectorSpace(self._base_ring, 2)
Beispiel #20
0
def test():
    "Run unit tests for the module."
    log.getLogger().setLevel(log.DEBUG)

    from sage.graphs.graph_generators import graphs
    K2 = graphs.CompleteGraph(2)
    K4 = graphs.CompleteGraph(4)
    Z2_3 = VectorSpace(GF(2), 3)
    Z2_2 = VectorSpace(GF(2), 2)

    # nonisomorphic_cubes_Z2
    assert len(list(nonisomorphic_cubes_Z2(1))) == 1
    assert len(list(nonisomorphic_cubes_Z2(2))) == 2
    assert len(list(nonisomorphic_cubes_Z2(3))) == 6

    # CayleyGraph
    K4b = CayleyGraph(Z2_2, [(1, 0), (0, 1), (1, 1)])
    assert K4.is_isomorphic(K4b)

    # squash_cube
    Ge = CayleyGraph(Z2_2, [(1, 0), (0, 1)])
    Smap = squash_cube(Ge, (1, 1))
    K2e = Ge.subgraph(Smap.values())
    assert K2.is_isomorphic(K2e)
    assert is_hom(Ge, K2e, Smap)

    # hom_is_by_subspace
    Gg = CayleyGraph(Z2_3, [(1, 0, 0), (0, 1, 0)])
    Hg = CayleyGraph(Z2_2, [(1, 0), (0, 1), (1, 1)])
    homg = {
        (0, 0, 0): (0, 0),
        (1, 0, 0): (1, 0),
        (0, 1, 0): (0, 1),
        (1, 1, 0): (1, 1),
        (0, 0, 1): (0, 0),
        (1, 0, 1): (1, 0),
        (0, 1, 1): (1, 1),
        (1, 1, 1): (0, 1)
    }
    assert is_hom(Gg, Hg, homg)
    assert hom_is_by_subspace(Gg, Z2_3, homg, require_isomorphic=False)
    assert not hom_is_by_subspace(Gg, Z2_3, homg, require_isomorphic=True)
    for h in extend_hom(Gg, K2, limit=10):
        assert hom_is_by_subspace(Gg, Z2_3, h, require_isomorphic=True)

    log.info("All tests passed.")
Beispiel #21
0
def squash_cube(G, c):
    """
  Assuming G is a cube-like _undirected_ graph (Cayley over Z_2^n),
  given a vector c from Z_2^n, finds a homomorphism unifying
  vectors differing by c. One of the vertives (v, v+c) still must
  be chosen as the target for the homomorphism in each such pair.

  Returns a hom map to a subgraph or None if none such exists.
  """
    n = len(c)
    vs = VectorSpace(GF(2), n)
    c = vs(c)

    pairs = {}  # vertex -> pair
    for v in G.vertices():
        w = tuple(vs(v) + c)
        if G.has_edge(v, w):
            return False  # contracting an edge!
        if w in pairs or v in pairs: continue
        pairs[v] = (v, w)
        pairs[w] = (v, w)
    chosen = {}  # pair -> 0 or 1 (first or second in the pair)
    undecided = set(pairs.values())  # undecided pairs

    def extend(pair, sel):
        """Extend `chosen[]` by setting pair to sel (0 or 1).
    Recursively chooses for pairs forced by this coloring.
    Returns False on inconsistency, True if all the forcing
    succeeded."""
        if pair in chosen:
            if chosen[pair] != sel:
                return False
            return True
        chosen[pair] = sel
        if pair in undecided:
            undecided.remove(pair)
        v = pair[sel]
        w = pair[1 - sel]

        for n in G.neighbors(v):
            np = pairs[n]
            ni = np.index(n)
            if G.has_edge(n, w):
                continue
            if not extend(np, ni):
                return False
        return True

    while undecided:
        p = undecided.pop()
        if not extend(p, 0):
            return None
    # create the hom-map
    hom = {}
    for p, sel in chosen.items():
        hom[p[0]] = p[sel]
        hom[p[1]] = p[sel]
    return hom
Beispiel #22
0
def reduced_charges(M):
    """
    Given a snappy manifold M, we find all reduced charges so that:
    (1) no loop in the triangulation passes an odd number of pi's.
    (2) no tetrahedron has three pi's and 
    """
    out = sol_and_kernel(M)
    x, A = out
    nt = M.num_tetrahedra()
    dim = 3 * nt
    V = VectorSpace(ZZ2, dim)
    AA = V.subspace(A)  # the reduced kernel
    xx = V(x)  # the reduced solution

    charges = [xx + sum(B) for B in powerset(AA.basis())]
    charges = [c for c in charges if not has_pi_triple(c)
               ]  # reject if there are three pi's in any tet.
    return charges
Beispiel #23
0
    def cycle_space(self, degree):
        r"""
        Returns the space of cycles of degree i

        Zi = ker(der: C_i -> C_{i-1})
        """
        assert (degree > -2 and degree < 3)
        if degree == -1:
            return VectorSpace(QQ, 1)
        return self._objects[degree].cycle_space()
Beispiel #24
0
    def ambient_space(self):
        r"""
        Returns the ambient vector space of ``self``.

        EXAMPLES::

            sage: C = codes.HammingCode(GF(2), 3)
            sage: C.ambient_space()
            Vector space of dimension 7 over Finite Field of size 2
        """
        return VectorSpace(self.base_ring(),self.length())
Beispiel #25
0
def circle_from_three_points(p,q,r,base_ring=None):
    r"""
    Construct a circle from three points on the circle.
    """
    if base_ring is None:
        base_ring=p.base_ring()
    V2 = VectorSpace(base_ring.fraction_field(), 2)
    V3 = VectorSpace(base_ring.fraction_field(), 3)
    
    v1=V3((p[0]+q[0],p[1]+q[1],2))
    v2=V3((p[1]-q[1],q[0]-p[0],0))
    line1=v1.cross_product(v2)
    v1=V3((p[0]+r[0],p[1]+r[1],2))
    v2=V3((p[1]-r[1],r[0]-p[0],0))
    line2=v1.cross_product(v2)
    center_3 = line1.cross_product(line2)
    if center_3[2].is_zero():
        raise ValueError("The three points lie on a line.")
    center = V2( (center_3[0]/center_3[2], center_3[1]/center_3[2]) )
    return Circle(center, (p[0]-center[0])**2+(p[1]-center[1])**2 )
Beispiel #26
0
    def message_space(self):
        r"""
        Return the message space of ``self``.

        EXAMPLES::

            sage: C = codes.ParityCheckCode(GF(5),7)
            sage: E = codes.encoders.ParityCheckCodeStraightforwardEncoder(C)
            sage: E.message_space()
            Vector space of dimension 7 over Finite Field of size 5
        """
        return VectorSpace(self.code().base_field(), self.code().dimension())
Beispiel #27
0
    def __init__(self, base_field, length, default_encoder_name, default_decoder_name, metric='Hamming'):
        """
        Initializes mandatory parameters that any linear code shares.

        This method only exists for inheritance purposes as it initializes
        parameters that need to be known by every linear code. The class
        :class:`sage.coding.linear_code_no_metric.AbstractLinearCodeNoMetric`
        should never be directly instantiated.

        INPUT:

        - ``base_field`` -- the base field of ``self``

        - ``length`` -- the length of ``self`` (a Python int or a Sage Integer, must be > 0)

        - ``default_encoder_name`` -- the name of the default encoder of ``self``

        - ``default_decoder_name`` -- the name of the default decoder of ``self``

        - ``metric`` -- (default: ``Hamming``) the metric of ``self``
        """

        self._registered_encoders['Systematic'] = LinearCodeSystematicEncoder

        if not base_field.is_field():
            raise ValueError("'base_field' must be a field (and {} is not one)".format(base_field))
        if not default_encoder_name in self._registered_encoders:
            raise ValueError("You must set a valid encoder as default encoder for this code, by filling in the dictionary of registered encoders")
        if not default_decoder_name in self._registered_decoders:
            raise ValueError("You must set a valid decoder as default decoder for this code, by filling in the dictionary of registered decoders")

        #if not self.dimension() <= length:
        #    raise ValueError("The dimension of the code can be at most its length, {}".format(length))

        super(AbstractLinearCodeNoMetric, self).__init__(length, default_encoder_name, default_decoder_name, metric)
        cat = Modules(base_field).FiniteDimensional().WithBasis().Finite()
        facade_for = VectorSpace(base_field, self._length)
        self.Element = type(facade_for.an_element()) #for when we made this a non-facade parent
        Parent.__init__(self, base=base_field, facade=facade_for, category=cat)
Beispiel #28
0
    def chain_space(self, degree):
        r"""
        Chain space.

        INPUT:

        - ``degree`` -- either

        """
        assert (degree > -2 and degree < 3)
        if degree == -1:
            return VectorSpace(QQ, 1)
        return self._objects[degree].chain_space()
    def tiny_integrals_on_basis(self, P, Q):
        r"""
        Evaluate the integrals `\{\int_P^Q x^i dx/2y \}_{i=0}^{2g-1}`
        by formally integrating a power series in a local parameter `t`.
        `P` and `Q` MUST be in the same residue disc for this result to make sense.

        INPUT:

        - P a point on self
        - Q a point on self (in the same residue disc as P)

        OUTPUT:

        The integrals `\{\int_P^Q x^i dx/2y \}_{i=0}^{2g-1}`

        EXAMPLES::

            sage: K = pAdicField(17, 5)
            sage: E = EllipticCurve(K, [-31/3, -2501/108]) # 11a
            sage: P = E(K(14/3), K(11/2))
            sage: TP = E.teichmuller(P);
            sage: E.tiny_integrals_on_basis(P, TP)
            (17 + 14*17^2 + 17^3 + 8*17^4 + O(17^5), 16*17 + 5*17^2 + 8*17^3 + 14*17^4 + O(17^5))

        ::

            sage: K = pAdicField(11, 5)
            sage: x = polygen(K)
            sage: C = HyperellipticCurve(x^5 + 33/16*x^4 + 3/4*x^3 + 3/8*x^2 - 1/4*x + 1/16)
            sage: P = C.lift_x(11^(-2))
            sage: Q = C.lift_x(3*11^(-2))
            sage: C.tiny_integrals_on_basis(P,Q)
            (3*11^3 + 7*11^4 + 4*11^5 + 7*11^6 + 5*11^7 + O(11^8), 3*11 + 10*11^2 + 8*11^3 + 9*11^4 + 7*11^5 + O(11^6), 4*11^-1 + 2 + 6*11 + 6*11^2 + 7*11^3 + O(11^4), 11^-3 + 6*11^-2 + 2*11^-1 + 2 + O(11^2))


        Note that this fails if the points are not in the same residue disc::

            sage: S = C(0,1/4)
            sage: C.tiny_integrals_on_basis(P,S)
            Traceback (most recent call last):
            ...
            ValueError: (11^-2 + O(11^3) : 11^-5 + 8*11^-2 + O(11^0) : 1 + O(11^5)) and (0 : 3 + 8*11 + 2*11^2 + 8*11^3 + 2*11^4 + O(11^5) : 1 + O(11^5)) are not in the same residue disc

        """
        if P == Q:
            V = VectorSpace(self.base_ring(), 2 * self.genus())
            return V(0)
        R = PolynomialRing(self.base_ring(), ['x', 'y'])
        x, y = R.gens()
        return self.tiny_integrals([x**i for i in range(2 * self.genus())], P,
                                   Q)
Beispiel #30
0
def make_random_instance(n, m):
    solution = VectorSpace(ZZ2, n).random_element()
    results = []
    quad_forms = []
    for _ in range(m):
        e = []
        for _ in range(n * (n + 1) / 2):
            e.append(ZZ2.random_element())
        quad_form = QuadraticForm(ZZ2, n, e)
        quad_forms.append(quad_form)
        results.append(quad_form(solution))
    instance = Instance(n, quad_forms, results)
    prover = Prover(instance, solution)
    return (instance, prover)
def HomologyGroup(n, base_ring, invfac=None):
    """
    Abelian group on `n` generators which represents a homology group in a
    fixed degree.

    INPUT:
    
    - ``n`` -- integer; the number of generators

    - ``base_ring`` -- ring; the base ring over which the homology is computed

    - ``inv_fac`` -- list of integers; the invariant factors -- ignored
      if the base ring is a field

    OUTPUT:

    A class that can represent the homology group in a fixed
    homological degree.

    EXAMPLES::

        sage: from sage.homology.homology_group import HomologyGroup
        sage: G = AbelianGroup(5, [5,5,7,8,9]); G
        Multiplicative Abelian group isomorphic to C5 x C5 x C7 x C8 x C9
        sage: H = HomologyGroup(5, ZZ, [5,5,7,8,9]); H
        C5 x C5 x C7 x C8 x C9
        sage: AbelianGroup(4)
        Multiplicative Abelian group isomorphic to Z x Z x Z x Z
        sage: HomologyGroup(4, ZZ)
        Z x Z x Z x Z
        sage: HomologyGroup(100, ZZ)
        Z^100
    """
    if base_ring.is_field():
        return VectorSpace(base_ring, n)

    # copied from AbelianGroup:
    if invfac is None:
        if isinstance(n, (list, tuple)):
            invfac = n
            n = len(n)
        else:
            invfac = []
    if len(invfac) < n:
        invfac = [0] * (n - len(invfac)) + invfac
    elif len(invfac) > n:
        raise ValueError("invfac (={}) must have length n (={})".format(
            invfac, n))
    M = HomologyGroup_class(n, invfac)
    return M
Beispiel #32
0
    def chain_space(self):
        r"""
        Return the space of chain on this finite set of vertices.

        EXAMPLES::

            sage: from surface_dynamics.all import *

            sage: o = Origami('(1,2,3,4)', '(1,5)')
            sage: V = o._vertices()
            sage: V.chain_space()
            Vector space of dimension 3 over Rational Field
        """
        return VectorSpace(QQ, self.cardinality())
Beispiel #33
0
    def ambient_vector_space(self):
        """
        Return the ambient (unfiltered) vector space.

        OUTPUT:

        A vector space.

        EXAMPLES::

            sage: V = FilteredVectorSpace(1, 0)
            sage: V.ambient_vector_space()
            Vector space of dimension 1 over Rational Field
        """
        return VectorSpace(self.base_ring(), self.dimension())
Beispiel #34
0
def _smallscale_present_linearlayer(nsboxes=16):
    """
    .. TODO::

        switch to sage.crypto.linearlayer
        (:trac:`25735`) as soon as it is included in sage

    EXAMPLES::

        sage: from sage.crypto.block_cipher.present import _smallscale_present_linearlayer
        sage: _smallscale_present_linearlayer(4)
        [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
        [0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0]
        [0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]
        [0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0]
        [0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
        [0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0]
        [0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0]
        [0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0]
        [0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0]
        [0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0]
        [0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0]
        [0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0]
        [0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0]
        [0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0]
        [0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0]
        [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1]
    """
    from sage.modules.free_module import VectorSpace
    from sage.modules.free_module_element import vector
    from sage.matrix.constructor import Matrix
    from sage.rings.finite_rings.finite_field_constructor import GF

    def present_llayer(n, x):
        dim = 4 * n
        y = [0] * dim
        for i in range(dim - 1):
            y[i] = x[(n * i) % (dim - 1)]
        y[dim - 1] = x[dim - 1]
        return vector(GF(2), y)

    m = Matrix(GF(2), [
        present_llayer(nsboxes, ei)
        for ei in VectorSpace(GF(2), 4 * nsboxes).basis()
    ])
    return m
Beispiel #35
0
    def ambient_vector_space(self):
        """
        Return the ambient (unfiltered) vector space.

        OUTPUT:

        A vector space.

        EXAMPLES::

            sage: V = FilteredVectorSpace(2, 0)
            sage: W = FilteredVectorSpace(2, 2)
            sage: F = MultiFilteredVectorSpace({'a':V, 'b':W})
            sage: F.ambient_vector_space()
            Vector space of dimension 2 over Rational Field
        """
        return VectorSpace(self.base_ring(), self.dimension())
Beispiel #36
0
    def Hrepresentation_space(self):
        r"""
        Return the linear space containing the H-representation vectors.

        OUTPUT:

        A free module over the base ring of dimension :meth:`ambient_dim` + 1.

        EXAMPLES::

            sage: from sage.geometry.polyhedron.parent import Polyhedra
            sage: Polyhedra(ZZ, 2).Hrepresentation_space()
            Ambient free module of rank 3 over the principal ideal domain Integer Ring
        """
        if self.base_ring() in Fields():
            from sage.modules.free_module import VectorSpace
            return VectorSpace(self.base_ring(), self.ambient_dim() + 1)
        else:
            from sage.modules.free_module import FreeModule
            return FreeModule(self.base_ring(), self.ambient_dim() + 1)
Beispiel #37
0
def construct_from_generators_indices(generators, filtration, base_ring, check):
    """
    Construct a filtered vector space.

    INPUT:

    - ``generators`` -- a list/tuple/iterable of vectors, or something
      convertible to them. The generators spanning various
      subspaces.

    - ``filtration`` -- a list or iterable of filtration steps. Each
      filtration step is a pair ``(degree, ray_indices)``. The
      ``ray_indices`` are a list or iterable of ray indices, which
      span a subspace of the vector space. The integer ``degree``
      stipulates that all filtration steps of degree higher or equal
      than ``degree`` (up to the next filtration step) are said
      subspace.

    EXAMPLES::

        sage: from sage.modules.filtered_vector_space import construct_from_generators_indices
        sage: gens = [(1,0), (0,1), (-1,-1)]
        sage: V = construct_from_generators_indices(gens, {1:[0,1], 3:[1]}, QQ, True);  V
        QQ^2 >= QQ^1 >= QQ^1 >= 0

    TESTS::

        sage: gens = [(int(1),int(0)), (0,1), (-1,-1)]
        sage: construct_from_generators_indices(iter(gens), {int(0):[0, int(1)], 2:[2]}, QQ, True)
        QQ^2 >= QQ^1 >= QQ^1 >= 0
    """
    # normalize generators
    generators = map(list, generators)

    # deduce dimension
    if len(generators) == 0:
        dim = ZZ(0)
    else:
        dim = ZZ(len(generators[0]))
    ambient = VectorSpace(base_ring, dim)

    # complete generators to a generating set
    if matrix(base_ring, generators).rank() < dim:
        complement = ambient.span(generators).complement()
        generators = generators + list(complement.gens())
    # normalize generators II
    generators = tuple(ambient(v) for v in generators)

    for v in generators:
        v.set_immutable()

    # normalize filtration data
    normalized = dict()
    for deg, gens in iteritems(filtration):
        deg = normalize_degree(deg)
        gens = map(ZZ, gens)
        if any(i < 0 or i >= len(generators) for i in gens):
            raise ValueError('generator index out of bounds')
        normalized[deg] = tuple(sorted(gens))
    try:
        del normalized[minus_infinity]
    except KeyError:
        pass
    filtration = normalized

    return FilteredVectorSpace_class(base_ring, dim, generators, filtration, check=check)
Beispiel #38
0
def mcfarland_1973_construction(q, s):
    r"""
    Return a difference set.

    The difference set returned has the following parameters

    .. MATH::

        v = \frac{q^{s+1}(q^{s+1}+q-2)}{q-1},
        k = \frac{q^s (q^{s+1}-1)}{q-1},
        \lambda = \frac{q^s(q^s-1)}{q-1}

    This construction is due to [McF1973]_.

    INPUT:

    - ``q``, ``s`` - (integers) parameters for the difference set (see the above
      formulas for the expression of ``v``, ``k``, ``l`` in terms of ``q`` and
      ``s``)

    .. SEEALSO::

        The function :func:`are_mcfarland_1973_parameters` makes the translation
        between the parameters `(q,s)` corresponding to a given triple
        `(v,k,\lambda)`.

    REFERENCES:

    .. [McF1973] Robert L. McFarland
       "A family of difference sets in non-cyclic groups"
       Journal of Combinatorial Theory (A) vol 15 (1973).
       http://dx.doi.org/10.1016/0097-3165(73)90031-9

    EXAMPLES::

        sage: from sage.combinat.designs.difference_family import (
        ....:    mcfarland_1973_construction, is_difference_family)

        sage: G,D = mcfarland_1973_construction(3, 1)
        sage: assert is_difference_family(G, D, 45, 12, 3)

        sage: G,D = mcfarland_1973_construction(2, 2)
        sage: assert is_difference_family(G, D, 64, 28, 12)
    """
    from sage.rings.finite_rings.finite_field_constructor import GF
    from sage.modules.free_module import VectorSpace
    from sage.rings.finite_rings.integer_mod_ring import Zmod
    from sage.categories.cartesian_product import cartesian_product
    from itertools import izip

    r = (q**(s+1)-1) // (q-1)
    F = GF(q,'a')
    V = VectorSpace(F, s+1)
    K = Zmod(r+1)

    G = cartesian_product([F]*(s+1) + [K])

    D = []
    for k,H in izip(K, V.subspaces(s)):
        for v in H:
            D.append(G((tuple(v) + (k,))))

    return G,[D]
Beispiel #39
0
def construct_from_generators_indices(generators, filtration, base_ring,
                                      check):
    """
    Construct a filtered vector space.

    INPUT:

    - ``generators`` -- a list/tuple/iterable of vectors, or something
      convertible to them. The generators spanning various
      subspaces.

    - ``filtration`` -- a list or iterable of filtration steps. Each
      filtration step is a pair ``(degree, ray_indices)``. The
      ``ray_indices`` are a list or iterable of ray indices, which
      span a subspace of the vector space. The integer ``degree``
      stipulates that all filtration steps of degree higher or equal
      than ``degree`` (up to the next filtration step) are said
      subspace.

    EXAMPLES::

        sage: from sage.modules.filtered_vector_space import construct_from_generators_indices
        sage: gens = [(1,0), (0,1), (-1,-1)]
        sage: V = construct_from_generators_indices(gens, {1:[0,1], 3:[1]}, QQ, True);  V
        QQ^2 >= QQ^1 >= QQ^1 >= 0

    TESTS::

        sage: gens = [(int(1),int(0)), (0,1), (-1,-1)]
        sage: construct_from_generators_indices(iter(gens), {int(0):[0, int(1)], 2:[2]}, QQ, True)
        QQ^2 >= QQ^1 >= QQ^1 >= 0
    """
    # normalize generators
    generators = [list(g) for g in generators]

    # deduce dimension
    if len(generators) == 0:
        dim = ZZ(0)
    else:
        dim = ZZ(len(generators[0]))
    ambient = VectorSpace(base_ring, dim)

    # complete generators to a generating set
    if matrix(base_ring, generators).rank() < dim:
        complement = ambient.span(generators).complement()
        generators = generators + list(complement.gens())
    # normalize generators II
    generators = tuple(ambient(v) for v in generators)

    for v in generators:
        v.set_immutable()

    # normalize filtration data
    normalized = dict()
    for deg, gens in filtration.items():
        deg = normalize_degree(deg)
        gens = [ZZ(i) for i in gens]
        if any(i < 0 or i >= len(generators) for i in gens):
            raise ValueError('generator index out of bounds')
        normalized[deg] = tuple(sorted(gens))
    try:
        del normalized[minus_infinity]
    except KeyError:
        pass
    filtration = normalized

    return FilteredVectorSpace_class(base_ring,
                                     dim,
                                     generators,
                                     filtration,
                                     check=check)
def algebraic_topological_model(K, base_ring=None):
    r"""
    Algebraic topological model for cell complex ``K``
    with coefficients in the field ``base_ring``.

    INPUT:

    - ``K`` -- either a simplicial complex or a cubical complex
    - ``base_ring`` -- coefficient ring; must be a field

    OUTPUT: a pair ``(phi, M)`` consisting of

    - chain contraction ``phi``
    - chain complex `M`

    This construction appears in a paper by Pilarczyk and Réal [PR]_.
    Given a cell complex `K` and a field `F`, there is a chain complex
    `C` associated to `K` with coefficients in `F`. The *algebraic
    topological model* for `K` is a chain complex `M` with trivial
    differential, along with chain maps `\pi: C \to M` and `\iota: M
    \to C` such that

    - `\pi \iota = 1_M`, and
    - there is a chain homotopy `\phi` between `1_C` and `\iota \pi`.

    In particular, `\pi` and `\iota` induce isomorphisms on homology,
    and since `M` has trivial differential, it is its own homology,
    and thus also the homology of `C`. Thus `\iota` lifts homology
    classes to their cycle representatives.

    The chain homotopy `\phi` satisfies some additional properties,
    making it a *chain contraction*:

    - `\phi \phi = 0`,
    - `\pi \phi = 0`,
    - `\phi \iota = 0`.

    Given an algebraic topological model for `K`, it is then easy to
    compute cup products and cohomology operations on the cohomology
    of `K`, as described in [G-DR03]_ and [PR]_.

    Implementation details: the cell complex `K` must have an
    :meth:`~sage.homology.cell_complex.GenericCellComplex.n_cells`
    method from which we can extract a list of cells in each
    dimension. Combining the lists in increasing order of dimension
    then defines a filtration of the complex: a list of cells in which
    the boundary of each cell consists of cells earlier in the
    list. This is required by Pilarczyk and Réal's algorithm.  There
    must also be a
    :meth:`~sage.homology.cell_complex.GenericCellComplex.chain_complex`
    method, to construct the chain complex `C` associated to this
    chain complex.

    In particular, this works for simplicial complexes and cubical
    complexes. It doesn't work for `\Delta`-complexes, though: the list
    of their `n`-cells has the wrong format.

    Note that from the chain contraction ``phi``, one can recover the
    chain maps `\pi` and `\iota` via ``phi.pi()`` and
    ``phi.iota()``. Then one can recover `C` and `M` from, for
    example, ``phi.pi().domain()`` and ``phi.pi().codomain()``,
    respectively.

    EXAMPLES::

        sage: from sage.homology.algebraic_topological_model import algebraic_topological_model
        sage: RP2 = simplicial_complexes.RealProjectivePlane()
        sage: phi, M = algebraic_topological_model(RP2, GF(2))
        sage: M.homology()
        {0: Vector space of dimension 1 over Finite Field of size 2,
         1: Vector space of dimension 1 over Finite Field of size 2,
         2: Vector space of dimension 1 over Finite Field of size 2}
        sage: T = cubical_complexes.Torus()
        sage: phi, M = algebraic_topological_model(T, QQ)
        sage: M.homology()
        {0: Vector space of dimension 1 over Rational Field,
         1: Vector space of dimension 2 over Rational Field,
         2: Vector space of dimension 1 over Rational Field}

    If you want to work with cohomology rather than homology, just
    dualize the outputs of this function::

        sage: M.dual().homology()
        {0: Vector space of dimension 1 over Rational Field,
         1: Vector space of dimension 2 over Rational Field,
         2: Vector space of dimension 1 over Rational Field}
        sage: M.dual().degree_of_differential()
        1
        sage: phi.dual()
        Chain homotopy between:
          Chain complex endomorphism of Chain complex with at most 3 nonzero terms over Rational Field
          and Chain complex morphism:
            From: Chain complex with at most 3 nonzero terms over Rational Field
            To:   Chain complex with at most 3 nonzero terms over Rational Field

    In degree 0, the inclusion of the homology `M` into the chain
    complex `C` sends the homology generator to a single vertex::

        sage: K = simplicial_complexes.Simplex(2)
        sage: phi, M = algebraic_topological_model(K, QQ)
        sage: phi.iota().in_degree(0)
        [0]
        [0]
        [1]

    In cohomology, though, one needs the dual of every degree 0 cell
    to detect the degree 0 cohomology generator::

        sage: phi.dual().iota().in_degree(0)
        [1]
        [1]
        [1]

    TESTS::

        sage: T = cubical_complexes.Torus()
        sage: C = T.chain_complex()
        sage: H, M = T.algebraic_topological_model()
        sage: C.differential(1) * H.iota().in_degree(1).column(0) == 0
        True
        sage: C.differential(1) * H.iota().in_degree(1).column(1) == 0
        True
        sage: coC = T.chain_complex(cochain=True)
        sage: coC.differential(1) * H.dual().iota().in_degree(1).column(0) == 0
        True
        sage: coC.differential(1) * H.dual().iota().in_degree(1).column(1) == 0
        True
    """
    if not base_ring.is_field():
        raise ValueError('the coefficient ring must be a field')

    # The following are all dictionaries indexed by dimension.
    # For each n, gens[n] is an ordered list of the n-cells generating the complex M.
    gens = {}
    # For each n, phi_dict[n] is a dictionary of the form {idx:
    # vector}, where idx is the index of an n-cell in the list of
    # n-cells in K, and vector is the image of that n-cell, as an
    # element in the free module of (n+1)-chains for K.
    phi_dict = {}
    # For each n, pi_dict[n] is a dictionary of the same form, except
    # that the target vectors should be elements of the chain complex M.
    pi_dict = {}
    # For each n, iota_dict[n] is a dictionary of the form {cell:
    # vector}, where cell is one of the generators for M and vector is
    # its image in C, as an element in the free module of n-chains.
    iota_dict = {}

    for n in range(K.dimension()+1):
        gens[n] = []
        phi_dict[n] = {}
        pi_dict[n] = {}
        iota_dict[n] = {}

    C = K.chain_complex(base_ring=base_ring)
    # old_cells: cells one dimension lower.
    old_cells = []

    for dim in range(K.dimension()+1):
        n_cells = K.n_cells(dim)
        diff = C.differential(dim)
        # diff is sparse and low density. Dense matrices are faster
        # over finite fields, but for low density matrices, sparse
        # matrices are faster over the rationals.
        if base_ring != QQ:
            diff = diff.dense_matrix()

        rank = len(n_cells)
        old_rank = len(old_cells)
        V_old = VectorSpace(base_ring, old_rank)
        zero = V_old.zero_vector()

        for c_idx, c in enumerate(zip(n_cells, VectorSpace(base_ring, rank).gens())):
            # c is the pair (cell, the corresponding standard basis
            # vector in the free module of chains). Separate its
            # components, calling them c and c_vec:
            c_vec = c[1]
            c = c[0]
            # No need to set zero values for any of the maps: we will
            # assume any unset values are zero.
            # From the paper: phi_dict[c] = 0.

            # c_bar = c - phi(bdry(c))
            c_bar = c_vec
            bdry_c = diff * c_vec
            # Apply phi to bdry_c and subtract from c_bar.
            for (idx, coord) in bdry_c.iteritems():
                try:
                    c_bar -= coord * phi_dict[dim-1][idx]
                except KeyError:
                    pass

            bdry_c_bar = diff * c_bar

            # Evaluate pi(bdry(c_bar)).
            pi_bdry_c_bar = zero

            for (idx, coeff) in bdry_c_bar.iteritems():
                try:
                    pi_bdry_c_bar += coeff * pi_dict[dim-1][idx]
                except KeyError:
                    pass

            # One small typo in the published algorithm: it says
            # "if bdry(c_bar) == 0", but should say
            # "if pi(bdry(c_bar)) == 0".
            if not pi_bdry_c_bar:
                # Append c to list of gens.
                gens[dim].append(c)
                # iota(c) = c_bar
                iota_dict[dim][c] = c_bar
                # pi(c) = c
                pi_dict[dim][c_idx] = c_vec
            else:
                # Take any u in gens so that lambda_i = <u, pi(bdry(c_bar))> != 0.
                # u_idx will be the index of the corresponding cell.
                for (u_idx, lambda_i) in pi_bdry_c_bar.iteritems():
                    # Now find the actual cell.
                    u = old_cells[u_idx]
                    if u in gens[dim-1]:
                        break

                # pi(c) = 0: no need to do anything about this.
                for c_j_idx in range(old_rank):
                    # eta_ij = <u, pi(c_j)>.
                    try:
                        eta_ij = pi_dict[dim-1][c_j_idx][u_idx]
                    except (KeyError, IndexError):
                        eta_ij = 0
                    if eta_ij:
                        # Adjust phi(c_j).
                        try:
                            phi_dict[dim-1][c_j_idx] += eta_ij * lambda_i**(-1) * c_bar
                        except KeyError:
                            phi_dict[dim-1][c_j_idx] = eta_ij * lambda_i**(-1) * c_bar
                        # Adjust pi(c_j).
                        try:
                            pi_dict[dim-1][c_j_idx] += -eta_ij * lambda_i**(-1) * pi_bdry_c_bar
                        except KeyError:
                            pi_dict[dim-1][c_j_idx] = -eta_ij * lambda_i**(-1) * pi_bdry_c_bar

                gens[dim-1].remove(u)
                del iota_dict[dim-1][u]
        old_cells = n_cells

    # Now we have constructed the raw data for M, pi, iota, phi, so we
    # have to convert that to data which can be used to construct chain
    # complexes, chain maps, and chain contractions.

    # M_data will contain (trivial) matrices defining the differential
    # on M. Keep track of the sizes using "M_rows" and "M_cols", which are
    # just the ranks of consecutive graded pieces of M.
    M_data = {}
    M_rows = 0
    # pi_data: the matrices defining pi. Similar for iota_data and phi_data.
    pi_data = {}
    iota_data = {}
    phi_data = {}
    for n in range(K.dimension()+1):
        n_cells = K.n_cells(n)
        # Remove zero entries from pi_dict and phi_dict.
        pi_dict[n] = {i: pi_dict[n][i] for i in pi_dict[n] if pi_dict[n][i]}
        phi_dict[n] = {i: phi_dict[n][i] for i in phi_dict[n] if phi_dict[n][i]}
        # Convert gens to data defining the chain complex M with
        # trivial differential.
        M_cols = len(gens[n])
        M_data[n] = zero_matrix(base_ring, M_rows, M_cols)
        M_rows = M_cols
        # Convert the dictionaries for pi, iota, phi to matrices which
        # will define chain maps and chain homotopies.
        pi_cols = []
        phi_cols = []
        for (idx, c) in enumerate(n_cells):
            # First pi:
            if idx in pi_dict[n]:
                column = vector(base_ring, M_rows)
                for (entry, coeff) in pi_dict[n][idx].iteritems():
                    # Translate from cells in n_cells to cells in gens[n].
                    column[gens[n].index(n_cells[entry])] = coeff
            else:
                column = vector(base_ring, M_rows)
            pi_cols.append(column)

            # Now phi:
            try:
                column = phi_dict[n][idx]
            except KeyError:
                column = vector(base_ring, len(K.n_cells(n+1)))
            phi_cols.append(column)
        # Now iota:
        iota_cols = [iota_dict[n][c] for c in gens[n]]

        pi_data[n] = matrix(base_ring, pi_cols).transpose()
        iota_data[n] = matrix(base_ring, len(gens[n]), len(n_cells), iota_cols).transpose()
        phi_data[n] = matrix(base_ring, phi_cols).transpose()

    M = ChainComplex(M_data, base_ring=base_ring, degree=-1)
    pi = ChainComplexMorphism(pi_data, C, M)
    iota = ChainComplexMorphism(iota_data, M, C)
    phi = ChainContraction(phi_data, pi, iota)
    return phi, M
def HughesPlane(q2, check=True):
    r"""
    Return the Hughes projective plane of order ``q2``.

    Let `q` be an odd prime, the Hughes plane of order `q^2` is a finite
    projective plane of order `q^2` introduced by D. Hughes in [Hu57]_. Its
    construction is as follows.

    Let `K = GF(q^2)` be a finite field with `q^2` elements and `F = GF(q)
    \subset K` be its unique subfield with `q` elements. We define a twisted
    multiplication on `K` as

    .. MATH::

        x \circ y =
        \begin{cases}
        x\ y & \text{if y is a square in K}\\
        x^q\ y & \text{otherwise}
        \end{cases}

    The points of the Hughes plane are the triples `(x, y, z)` of points in `K^3
    \backslash \{0,0,0\}` up to the equivalence relation `(x,y,z) \sim (x \circ
    k, y \circ k, z \circ k)` where `k \in K`.

    For `a = 1` or `a \in (K \backslash F)` we define a block `L(a)` as the set of
    triples `(x,y,z)` so that `x + a \circ y + z = 0`. The rest of the blocks
    are obtained by letting act the group `GL(3, F)` by its standard action.

    For more information, see :wikipedia:`Hughes_plane` and [We07].

    .. SEEALSO::

        :func:`DesarguesianProjectivePlaneDesign` to build the Desarguesian
        projective planes

    INPUT:

    - ``q2`` -- an even power of an odd prime number

    - ``check`` -- (boolean) Whether to check that output is correct before
      returning it. As this is expected to be useless (but we are cautious
      guys), you may want to disable it whenever you want speed. Set to
      ``True`` by default.

    EXAMPLES::

        sage: H = designs.HughesPlane(9)
        sage: H
        (91,10,1)-Balanced Incomplete Block Design

    We prove in the following computations that the Desarguesian plane ``H`` is
    not Desarguesian. Let us consider the two triangles `(0,1,10)` and `(57, 70,
    59)`. We show that the intersection points `D_{0,1} \cap D_{57,70}`,
    `D_{1,10} \cap D_{70,59}` and `D_{10,0} \cap D_{59,57}` are on the same line
    while `D_{0,70}`, `D_{1,59}` and `D_{10,57}` are not concurrent::

        sage: blocks = H.blocks()
        sage: line = lambda p,q: next(b for b in blocks if p in b and q in b)

        sage: b_0_1 = line(0, 1)
        sage: b_1_10 = line(1, 10)
        sage: b_10_0 = line(10, 0)
        sage: b_57_70 = line(57, 70)
        sage: b_70_59 = line(70, 59)
        sage: b_59_57 = line(59, 57)

        sage: set(b_0_1).intersection(b_57_70)
        {2}
        sage: set(b_1_10).intersection(b_70_59)
        {73}
        sage: set(b_10_0).intersection(b_59_57)
        {60}

        sage: line(2, 73) == line(73, 60)
        True

        sage: b_0_57 = line(0, 57)
        sage: b_1_70 = line(1, 70)
        sage: b_10_59 = line(10, 59)

        sage: p = set(b_0_57).intersection(b_1_70)
        sage: q = set(b_1_70).intersection(b_10_59)
        sage: p == q
        False

    TESTS:

    Some wrong input::

        sage: designs.HughesPlane(5)
        Traceback (most recent call last):
        ...
        EmptySetError: No Hughes plane of non-square order exists.

        sage: designs.HughesPlane(16)
        Traceback (most recent call last):
        ...
        EmptySetError: No Hughes plane of even order exists.

    Check that it works for non-prime `q`::

        sage: designs.HughesPlane(3**4)    # not tested - 10 secs
        (6643,82,1)-Balanced Incomplete Block Design
    """
    if not q2.is_square():
        raise EmptySetError("No Hughes plane of non-square order exists.")
    if q2 % 2 == 0:
        raise EmptySetError("No Hughes plane of even order exists.")
    q = q2.sqrt()
    K = FiniteField(q2, prefix='x')
    F = FiniteField(q, prefix='y')
    A = q3_minus_one_matrix(F)
    A = A.change_ring(K)
    m = K.list()
    V = VectorSpace(K, 3)
    zero = K.zero()
    one = K.one()
    points = [(x, y, one) for x in m for y in m] + \
             [(x, one, zero) for x in m] + \
             [(one, zero, zero)]
    relabel = {tuple(p): i for i, p in enumerate(points)}
    blcks = []
    for a in m:
        if a not in F or a == 1:
            # build L(a)
            aa = ~a
            l = []
            l.append(V((-a, one, zero)))
            for x in m:
                y = -aa * (x + one)
                if not y.is_square():
                    y *= aa**(q - 1)
                l.append(V((x, y, one)))
            # compute the orbit of L(a)
            blcks.append(
                [relabel[normalize_hughes_plane_point(p, q)] for p in l])
            for i in range(q2 + q):
                l = [A * j for j in l]
                blcks.append(
                    [relabel[normalize_hughes_plane_point(p, q)] for p in l])
    from .bibd import BalancedIncompleteBlockDesign
    return BalancedIncompleteBlockDesign(q2**2 + q2 + 1, blcks, check=check)
Beispiel #42
0
def _possible_normalizers(E, SA):
    r"""Find a list containing all primes `l` such that the Galois image at `l`
    is contained in the normalizer of a Cartan subgroup, such that the
    corresponding quadratic character is ramified only at the given primes.

    INPUT:

    - ``E`` - EllipticCurve - over a number field K.

    - ``SA`` - list - a list of primes of K.

    OUTPUT:

    - list - A list of primes, which contains all primes `l` such that the
             Galois image at `l` is contained in the normalizer of a Cartan
             subgroup, such that the corresponding quadratic character is
             ramified only at primes in SA.

    - If `E` has geometric CM that is not defined over its ground field, a
      ValueError is raised.

    EXAMPLES::

        sage: E = EllipticCurve([0,0,0,-56,4848])
        sage: 5 in sage.schemes.elliptic_curves.gal_reps_number_field._possible_normalizers(E, [ZZ.ideal(2)])
        True
    """

    E = _over_numberfield(E)
    K = E.base_field()
    SA = [K.ideal(I.gens()) for I in SA]

    x = K['x'].gen()
    selmer_group = K.selmer_group(SA, 2) # Generators of the selmer group.

    if selmer_group == []:
        return []

    V = VectorSpace(GF(2), len(selmer_group))
    # We think of this as the character group of the selmer group.

    traces_list = []
    W = V.zero_subspace()

    deg_one_primes = K.primes_of_degree_one_iter()

    while W.dimension() < V.dimension() - 1:
        P = deg_one_primes.next()

        k = P.residue_field()

        defines_valid_character = True
        # A prime P defines a quadratic residue character
        # on the Selmer group. This variable will be set
        # to zero if any elements of the selmer group are
        # zero mod P (i.e. the character is ramified).

        splitting_vector = [] # This will be the values of this
        # character on the generators of the Selmer group.

        for a in selmer_group:
            abar = k(a)
            if abar == 0:
                # Ramification.
                defines_valid_character = False
                break

            if abar.is_square():
                splitting_vector.append(GF(2)(0))
            else:
                splitting_vector.append(GF(2)(1))

        if not defines_valid_character:
            continue

        if splitting_vector in W:
            continue

        try:
            Etilde = E.change_ring(k)
        except ArithmeticError: # Bad reduction.
            continue

        tr = Etilde.trace_of_frobenius()

        if tr == 0:
            continue

        traces_list.append(tr)

        W = W + V.span([splitting_vector])

    bad_primes = set([])

    for i in traces_list:
        for p in i.prime_factors():
            bad_primes.add(p)

    # We find the unique vector v in V orthogonal to W:
    v = W.matrix().transpose().kernel().basis()[0]

    # We find the element a of the selmer group corresponding to v:
    a = 1
    for i in xrange(len(selmer_group)):
        if v[i] == 1:
            a *= selmer_group[i]

    # Since we've already included the above bad primes, we can assume
    # that the quadratic character corresponding to the exceptional primes
    # we're looking for is given by mapping into Gal(K[\sqrt{a}]/K).

    patience = 5 * K.degree()
    # Number of Frobenius elements to check before suspecting that E
    # has CM and computing the set of CM j-invariants of K to check.
    # TODO: Is this the best value for this parameter?

    while True:
        P = deg_one_primes.next()

        k = P.residue_field()

        if not k(a).is_square():
            try:
                tr = E.change_ring(k).trace_of_frobenius()
            except ArithmeticError: # Bad reduction.
                continue

            if tr == 0:
                patience -= 1

                if patience == 0:
                    # We suspect E has CM, so we check:
                    if E.j_invariant() in cm_j_invariants(K):
                        raise ValueError("The curve E should not have CM.")

            else:
                for p in tr.prime_factors():
                    bad_primes.add(p)

                bad_primes = sorted(bad_primes)
                return bad_primes
Beispiel #43
0
 def __init__(self, base_field):
     self._f=base_field
     # The vector space of vectors 
     self._vs = VectorSpace(self._f,2)
     Group.__init__(self, category=Groups().Infinite())
Beispiel #44
0
class SimilarityGroup(UniqueRepresentation,Group):
    r'''Group representing all similarities in the plane.
    This is the group generated by rotations, translations and dilations.
    '''

    Element = Similarity

    def _element_constructor_(self, *args, **kwds):
        if len(args)!=1:
            return self.element_class(self, *args, **kwds)
        x = args[0]
        p=parent(x)
        if self._f.has_coerce_map_from(p):
            return self.element_class( self,self._f(x), self._f.zero(), self._f.zero(), self._f.zero())
        if isinstance(p, SimilarityGroup):
            return self.element_class(self, x.a(), x.b(), x.s(), x.t())
        if isinstance(p, TranslationGroup):
            return self.element_class( self,self._f.one(), self._f.zero(), x.s(), x.t() )
        return self.element_class(self, x, **kwds)

    def _coerce_map_from_(self, S):
        if self._f.has_coerce_map_from(S):
            return True
        if isinstance(S, SimilarityGroup):
            return self._f.has_coerce_map_from(S._f)
        if isinstance(S, TranslationGroup):
            return self._f.has_coerce_map_from(S.base_field())
           
    def __init__(self, base_field):
        self._f=base_field
        # The vector space of vectors 
        self._vs = VectorSpace(self._f,2)
        Group.__init__(self, category=Groups().Infinite())

    def _repr_(self):
        return "SimilarityGroup over field "+str(self._f)

    def one(self):
        return self.element_class(self,self._f.one(),self._f.zero(),self._f.zero(),self._f.zero())

    def an_element(self):
        return self.element_class(self,self._f(ZZ_3),self._f(ZZ_4),self._f(ZZ_2),self._f(-ZZ_1))

    def is_abelian(self):
        return False

    def gens(self):
        pairs=[
            (self._f.one(),self._f.zero()),
            (self._f(ZZ_2),self._f.zero()),
            (self._f.zero(),self._f(ZZ_2)),
            (self._f(ZZ_3),self._f(ZZ_4))]
        l=[]
        for p in pairs:
            for v in self._vs.gens():
                l.append(self.element_class(self,p[0],p[1],v[0],v[1]))
        return l
    
    # For pickling:
    #def __reduce__(self):
    #    return self.__class__, (self._f,)
        
    #def _cmp_(self, other):
    #    return self._f == other._f

    #__cmp__=_cmp_

    def base_field(self):
        return self._f
 def test_full(self):
   v_space = VectorSpace(QQ,4)
   zero = v_space.subspace([v_space.zero()])
   comp = orth_complement(v_space,v_space)
   self.assertEqual(zero,comp)
 def test_comp(self):
   v_space = VectorSpace(QQ,4)
   sub = v_space.subspace([[1,1,1,1],[2,2,-3,-1]])
   comp = orth_complement(v_space,sub)
   true_comp = v_space.subspace([[19,-17,3,-5],[1,-1,0,0]])
   self.assertEqual(comp,true_comp)
Beispiel #47
0
def linear_transformation(arg0, arg1=None, arg2=None, side='left'):
    r"""
    Create a linear transformation from a variety of possible inputs.

    FORMATS:

    In the following, ``D`` and ``C`` are vector spaces over
    the same field that are the domain and codomain
    (respectively) of the linear transformation.

    ``side`` is a keyword that is either 'left' or 'right'.
    When a matrix is used to specify a linear transformation,
    as in the first two call formats below, you may specify
    if the function is given by matrix multiplication with
    the vector on the left, or the vector on the right.
    The default is 'left'. Internally representations are
    always carried as the 'left' version, and the default
    text representation is this version.  However, the matrix
    representation may be obtained as either version, no matter
    how it is created.

    - ``linear_transformation(A, side='left')``

      Where ``A`` is a matrix.  The domain and codomain are inferred
      from the dimension of the matrix and the base ring of the matrix.
      The base ring must be a field, or have its fraction field implemented
      in Sage.

    - ``linear_transformation(D, C, A, side='left')``

      ``A`` is a matrix that behaves as above.  However, now the domain
      and codomain are given explicitly. The matrix is checked for
      compatibility with the domain and codomain.  Additionally, the
      domain and codomain may be supplied with alternate ("user") bases
      and the matrix is interpreted as being a representation relative
      to those bases.

    - ``linear_transformation(D, C, f)``

      ``f`` is any function that can be applied to the basis elements of the
      domain and that produces elements of the codomain.  The linear
      transformation returned is the unique linear transformation that
      extends this mapping on the basis elements.  ``f`` may come from a
      function defined by a Python ``def`` statement, or may be defined as a
      ``lambda`` function.

      Alternatively, ``f`` may be specified by a callable symbolic function,
      see the examples below for a demonstration.

    - ``linear_transformation(D, C, images)``

      ``images`` is a list, or tuple, of codomain elements, equal in number
      to the size of the basis of the domain.  Each basis element of the domain
      is mapped to the corresponding element of the ``images`` list, and the
      linear transformation returned is the unique linear transfromation that
      extends this mapping.

    OUTPUT:

    A linear transformation described by the input.  This is a
    "vector space morphism", an object of the class
    :class:`sage.modules.vector_space_morphism`.

    EXAMPLES:

    We can define a linear transformation with just a matrix, understood to
    act on a vector placed on one side or the other.  The field for the
    vector spaces used as domain and codomain is obtained from the base
    ring of the matrix, possibly promoting to a fraction field.  ::

        sage: A = matrix(ZZ, [[1, -1, 4], [2, 0, 5]])
        sage: phi = linear_transformation(A)
        sage: phi
        Vector space morphism represented by the matrix:
        [ 1 -1  4]
        [ 2  0  5]
        Domain: Vector space of dimension 2 over Rational Field
        Codomain: Vector space of dimension 3 over Rational Field
        sage: phi([1/2, 5])
        (21/2, -1/2, 27)

        sage: B = matrix(Integers(7), [[1, 2, 1], [3, 5, 6]])
        sage: rho = linear_transformation(B, side='right')
        sage: rho
        Vector space morphism represented by the matrix:
        [1 3]
        [2 5]
        [1 6]
        Domain: Vector space of dimension 3 over Ring of integers modulo 7
        Codomain: Vector space of dimension 2 over Ring of integers modulo 7
        sage: rho([2, 4, 6])
        (2, 6)

    We can define a linear transformation with a matrix, while explicitly
    giving the domain and codomain.  Matrix entries will be coerced into the
    common field of scalars for the vector spaces.  ::

        sage: D = QQ^3
        sage: C = QQ^2
        sage: A = matrix([[1, 7], [2, -1], [0, 5]])
        sage: A.parent()
        Full MatrixSpace of 3 by 2 dense matrices over Integer Ring
        sage: zeta = linear_transformation(D, C, A)
        sage: zeta.matrix().parent()
        Full MatrixSpace of 3 by 2 dense matrices over Rational Field
        sage: zeta
        Vector space morphism represented by the matrix:
        [ 1  7]
        [ 2 -1]
        [ 0  5]
        Domain: Vector space of dimension 3 over Rational Field
        Codomain: Vector space of dimension 2 over Rational Field

    Matrix representations are relative to the bases for the domain
    and codomain.  ::

        sage: u = vector(QQ, [1, -1])
        sage: v = vector(QQ, [2, 3])
        sage: D = (QQ^2).subspace_with_basis([u, v])
        sage: x = vector(QQ, [2, 1])
        sage: y = vector(QQ, [-1, 4])
        sage: C = (QQ^2).subspace_with_basis([x, y])
        sage: A = matrix(QQ, [[2, 5], [3, 7]])
        sage: psi = linear_transformation(D, C, A)
        sage: psi
        Vector space morphism represented by the matrix:
        [2 5]
        [3 7]
        Domain: Vector space of degree 2 and dimension 2 over Rational Field
        User basis matrix:
        [ 1 -1]
        [ 2  3]
        Codomain: Vector space of degree 2 and dimension 2 over Rational Field
        User basis matrix:
        [ 2  1]
        [-1  4]
        sage: psi(u) == 2*x + 5*y
        True
        sage: psi(v) == 3*x + 7*y
        True

    Functions that act on the domain may be used to compute images of
    the domain's basis elements, and this mapping can be extended to
    a unique linear transformation.  The function may be a Python
    function (via ``def`` or ``lambda``) or a Sage symbolic function.  ::

        sage: def g(x):
        ...     return vector(QQ, [2*x[0]+x[2], 5*x[1]])
        ...
        sage: phi = linear_transformation(QQ^3, QQ^2, g)
        sage: phi
        Vector space morphism represented by the matrix:
        [2 0]
        [0 5]
        [1 0]
        Domain: Vector space of dimension 3 over Rational Field
        Codomain: Vector space of dimension 2 over Rational Field

        sage: f = lambda x: vector(QQ, [2*x[0]+x[2], 5*x[1]])
        sage: rho = linear_transformation(QQ^3, QQ^2, f)
        sage: rho
        Vector space morphism represented by the matrix:
        [2 0]
        [0 5]
        [1 0]
        Domain: Vector space of dimension 3 over Rational Field
        Codomain: Vector space of dimension 2 over Rational Field

        sage: x, y, z = var('x y z')
        sage: h(x, y, z) = [2*x + z, 5*y]
        sage: zeta = linear_transformation(QQ^3, QQ^2, h)
        sage: zeta
        Vector space morphism represented by the matrix:
        [2 0]
        [0 5]
        [1 0]
        Domain: Vector space of dimension 3 over Rational Field
        Codomain: Vector space of dimension 2 over Rational Field

        sage: phi == rho
        True
        sage: rho == zeta
        True


    We create a linear transformation relative to non-standard bases,
    and capture its representation relative to standard bases.  With this, we
    can build functions that create the same linear transformation relative
    to the nonstandard bases.  ::

        sage: u = vector(QQ, [1, -1])
        sage: v = vector(QQ, [2, 3])
        sage: D = (QQ^2).subspace_with_basis([u, v])
        sage: x = vector(QQ, [2, 1])
        sage: y = vector(QQ, [-1, 4])
        sage: C = (QQ^2).subspace_with_basis([x, y])
        sage: A = matrix(QQ, [[2, 5], [3, 7]])
        sage: psi = linear_transformation(D, C, A)
        sage: rho = psi.restrict_codomain(QQ^2).restrict_domain(QQ^2)
        sage: rho.matrix()
        [ -4/5  97/5]
        [  1/5 -13/5]

        sage: f = lambda x: vector(QQ, [(-4/5)*x[0] + (1/5)*x[1], (97/5)*x[0] + (-13/5)*x[1]])
        sage: psi = linear_transformation(D, C, f)
        sage: psi.matrix()
        [2 5]
        [3 7]

        sage: s, t = var('s t')
        sage: h(s, t) = [(-4/5)*s + (1/5)*t, (97/5)*s + (-13/5)*t]
        sage: zeta = linear_transformation(D, C, h)
        sage: zeta.matrix()
        [2 5]
        [3 7]

    Finally, we can give an explicit list of images for the basis
    elements of the domain.  ::

        sage: x = polygen(QQ)
        sage: F.<a> = NumberField(x^3+x+1)
        sage: u = vector(F, [1, a, a^2])
        sage: v = vector(F, [a, a^2, 2])
        sage: w = u + v
        sage: D = F^3
        sage: C = F^3
        sage: rho = linear_transformation(D, C, [u, v, w])
        sage: rho.matrix()
        [      1       a     a^2]
        [      a     a^2       2]
        [  a + 1 a^2 + a a^2 + 2]
        sage: C = (F^3).subspace_with_basis([u, v])
        sage: D = (F^3).subspace_with_basis([u, v])
        sage: psi = linear_transformation(C, D, [u+v, u-v])
        sage: psi.matrix()
        [ 1  1]
        [ 1 -1]

    TESTS:

    We test some bad inputs.  First, the wrong things in the wrong places.  ::

        sage: linear_transformation('junk')
        Traceback (most recent call last):
        ...
        TypeError: first argument must be a matrix or a vector space, not junk

        sage: linear_transformation(QQ^2, QQ^3, 'stuff')
        Traceback (most recent call last):
        ...
        TypeError: third argument must be a matrix, function, or list of images, not stuff

        sage: linear_transformation(QQ^2, 'garbage')
        Traceback (most recent call last):
        ...
        TypeError: if first argument is a vector space, then second argument must be a vector space, not garbage

        sage: linear_transformation(QQ^2, Integers(7)^2)
        Traceback (most recent call last):
        ...
        TypeError: vector spaces must have the same field of scalars, not Rational Field and Ring of integers modulo 7

    Matrices must be over a field (or a ring that can be promoted to a field),
    and of the right size.  ::

        sage: linear_transformation(matrix(Integers(6), [[2, 3],[4, 5]]))
        Traceback (most recent call last):
        ...
        TypeError: matrix must have entries from a field, or a ring with a fraction field, not Ring of integers modulo 6

        sage: A = matrix(QQ, 3, 4, range(12))
        sage: linear_transformation(QQ^4, QQ^4, A)
        Traceback (most recent call last):
        ...
        TypeError: domain dimension is incompatible with matrix size

        sage: linear_transformation(QQ^3, QQ^3, A, side='right')
        Traceback (most recent call last):
        ...
        TypeError: domain dimension is incompatible with matrix size

        sage: linear_transformation(QQ^3, QQ^3, A)
        Traceback (most recent call last):
        ...
        TypeError: codomain dimension is incompatible with matrix size

        sage: linear_transformation(QQ^4, QQ^4, A, side='right')
        Traceback (most recent call last):
        ...
        TypeError: codomain dimension is incompatible with matrix size

    Lists of images can be of the wrong number, or not really
    elements of the codomain.  ::

        sage: linear_transformation(QQ^3, QQ^2, [vector(QQ, [1,2])])
        Traceback (most recent call last):
        ...
        ValueError: number of images should equal the size of the domain's basis (=3), not 1

        sage: C = (QQ^2).subspace_with_basis([vector(QQ, [1,1])])
        sage: linear_transformation(QQ^1, C, [vector(QQ, [1,2])])
        Traceback (most recent call last):
        ...
        ArithmeticError: some proposed image is not in the codomain, because
        element [1, 2] is not in free module


    Functions may not apply properly to domain elements,
    or return values outside the codomain.  ::

        sage: f = lambda x: vector(QQ, [x[0], x[4]])
        sage: linear_transformation(QQ^3, QQ^2, f)
        Traceback (most recent call last):
        ...
        ValueError: function cannot be applied properly to some basis element because
        vector index out of range

        sage: f = lambda x: vector(QQ, [x[0], x[1]])
        sage: C = (QQ^2).span([vector(QQ, [1, 1])])
        sage: linear_transformation(QQ^2, C, f)
        Traceback (most recent call last):
        ...
        ArithmeticError: some image of the function is not in the codomain, because
        element [1, 0] is not in free module

    A Sage symbolic function can come in a variety of forms that are
    not representative of a linear transformation. ::

        sage: x, y = var('x, y')
        sage: f(x, y) = [y, x, y]
        sage: linear_transformation(QQ^3, QQ^3, f)
        Traceback (most recent call last):
        ...
        ValueError: symbolic function has the wrong number of inputs for domain

        sage: linear_transformation(QQ^2, QQ^2, f)
        Traceback (most recent call last):
        ...
        ValueError: symbolic function has the wrong number of outputs for codomain

        sage: x, y = var('x y')
        sage: f(x, y) = [y, x*y]
        sage: linear_transformation(QQ^2, QQ^2, f)
        Traceback (most recent call last):
        ...
        ValueError: symbolic function must be linear in all the inputs:
        unable to convert y to a rational

        sage: x, y = var('x y')
        sage: f(x, y) = [x, 2*y]
        sage: C = (QQ^2).span([vector(QQ, [1, 1])])
        sage: linear_transformation(QQ^2, C, f)
        Traceback (most recent call last):
        ...
        ArithmeticError: some image of the function is not in the codomain, because
        element [1, 0] is not in free module
    """
    from sage.matrix.constructor import matrix
    from sage.modules.module import is_VectorSpace
    from sage.modules.free_module import VectorSpace
    from sage.categories.homset import Hom
    from sage.symbolic.ring import SymbolicRing
    from sage.modules.vector_callable_symbolic_dense import Vector_callable_symbolic_dense
    from inspect import isfunction

    if not side in ['left', 'right']:
        raise ValueError("side must be 'left' or 'right', not {0}".format(side))
    if not (is_Matrix(arg0) or is_VectorSpace(arg0)):
        raise TypeError('first argument must be a matrix or a vector space, not {0}'.format(arg0))
    if is_Matrix(arg0):
        R = arg0.base_ring()
        if not R.is_field():
            try:
                R = R.fraction_field()
            except (NotImplementedError, TypeError):
                msg = 'matrix must have entries from a field, or a ring with a fraction field, not {0}'
                raise TypeError(msg.format(R))
        if side == 'right':
            arg0 = arg0.transpose()
            side = 'left'
        arg2 = arg0
        arg0 = VectorSpace(R, arg2.nrows())
        arg1 = VectorSpace(R, arg2.ncols())
    elif is_VectorSpace(arg0):
        if not is_VectorSpace(arg1):
            msg = 'if first argument is a vector space, then second argument must be a vector space, not {0}'
            raise TypeError(msg.format(arg1))
        if arg0.base_ring() != arg1.base_ring():
            msg = 'vector spaces must have the same field of scalars, not {0} and {1}'
            raise TypeError(msg.format(arg0.base_ring(), arg1.base_ring()))

    # Now arg0 = domain D, arg1 = codomain C, and
    #   both are vector spaces with common field of scalars
    #   use these to make a VectorSpaceHomSpace
    # arg2 might be a matrix that began in arg0
    D = arg0
    C = arg1
    H = Hom(D, C, category=None)

    # Examine arg2 as the "rule" for the linear transformation
    # Pass on matrices, Python functions and lists to homspace call
    # Convert symbolic function here, to a matrix
    if is_Matrix(arg2):
        if side == 'right':
            arg2 = arg2.transpose()
    elif isinstance(arg2, (list, tuple)):
        pass
    elif isfunction(arg2):
        pass
    elif isinstance(arg2, Vector_callable_symbolic_dense):
        args = arg2.parent().base_ring()._arguments
        exprs = arg2.change_ring(SymbolicRing())
        m = len(args)
        n = len(exprs)
        if m != D.degree():
            raise ValueError('symbolic function has the wrong number of inputs for domain')
        if n != C.degree():
            raise ValueError('symbolic function has the wrong number of outputs for codomain')
        arg2 = [[e.coefficient(a) for e in exprs] for a in args]
        try:
            arg2 = matrix(D.base_ring(), m, n, arg2)
        except TypeError as e:
            msg = 'symbolic function must be linear in all the inputs:\n' + e.args[0]
            raise ValueError(msg)
        # have matrix with respect to standard bases, now consider user bases
        images = [v*arg2 for v in D.basis()]
        try:
            arg2 = matrix([C.coordinates(C(a)) for a in images])
        except (ArithmeticError, TypeError) as e:
            msg = 'some image of the function is not in the codomain, because\n' + e.args[0]
            raise ArithmeticError(msg)
    else:
        msg = 'third argument must be a matrix, function, or list of images, not {0}'
        raise TypeError(msg.format(arg2))

    # arg2 now compatible with homspace H call method
    # __init__ will check matrix sizes versus domain/codomain dimensions
    return H(arg2)
 def test_satisfy_sum(self):
   v_space = VectorSpace(QQ,4)
   sub = v_space.subspace([[1,1,1,-1],[2,-3,41,5]])
   comp = orth_complement(v_space,sub)
   self.assertEqual(v_space,sub+comp)