Beispiel #1
0
def character_variety(group):
    gens = group.generators()
    vars = [g + repr(i) for g in gens for i in range(4)]
    R = PolynomialRing(QQ, vars)
    mats = {
        g:
        matrix(R,
               [[R(g + '0'), R(g + '1')], [R(g + '2'), R(g + '3')]])
        for g in gens
    }
    rels = [A.det() - 1 for A in mats.values()]
    for g in gens:
        mats[g.upper()] = matrix(
            R,
            [[R(g + '3'), -R(g + '1')], [-R(g + '2'), R(g + '0')]])

    def to_mat(word):
        return prod(mats[w] for w in word)

    for word in group.relators():
        w0, w1 = halfes(word)
        diff = to_mat(w0) - to_mat(w1)
        rels += diff.list()

    rels += [R('a2'), R('a1 - 1'), R('b1')]
    return R.ideal(rels)
Beispiel #2
0
def gluing_equation_ideal(manifold):
    n = manifold.num_tetrahedra()
    R = PolynomialRing(QQ, 'z', n)
    zs = R.gens()
    eqns = manifold.gluing_equations('rect')
    c_to_exp = {1: 0, -1: 1}
    hermite = matrix([a + b + [c_to_exp[c]]
                      for a, b, c in eqns]).hermite_form()
    eqns = [(row[:n], row[n:2 * n], row[-1] % 2) for row in hermite
            if not (row[:-1] == 0 and row[-1] % 2 == 0)]
    return R.ideal([polynomial_eval_eqn(eqn, zs) for eqn in eqns])
Beispiel #3
0
def components_containing_irreducible_reps(G):
    R = PolynomialRing(QQ, ['x', 'y', 'z'])
    assert G.generators() == ['a', 'b'], len(G.relators()) == 1
    r = G.relators()[0]
    I = R.ideal([tr(r[:j]) - tr(r[j:]) for j in range(len(r))] + [
        tr('a' + r) - tr('a' + inverse_word(r)),
        tr('b' + r) - tr('b' + inverse_word(r))
    ]).radical()
    contains_irreducible_rep = [
        J for J in I.primary_decomposition() if not tr('abAB') - 2 in J
    ]
    return contains_irreducible_rep
Beispiel #4
0
def belongs_to_radical(f, I):
    """
    Test if f in I.radical().
    """
    R = PolynomialRing(QQ,
                       I.ring().ngens() + 1,
                       I.ring().variable_names() + ('Zoo', ))
    Zoo = R.gens()[-1]
    J = R.ideal([R(g) for g in I.gens()] + [R(1) - Zoo * R(f)])
    return [
        R(1)
    ] == J.groebner_basis(algorithm='singular' if common.plumber else '')
def character_variety_ideal(gens, rels=None):
    """
    sage: M = Manifold('m004')
    sage: I = character_variety_ideal(M.fundamental_group())
    sage: I.dimension()
    1
    sage: len(I.radical().primary_decomposition())
    2
    """
    presentation = character_variety(gens, rels)
    from sage.all import PolynomialRing, QQ
    R = PolynomialRing(QQ, [repr(v) for v in presentation.gens])
    return R.ideal([R(p) for p in presentation.rels])
Beispiel #6
0
def _to_polynomial(f, val1):
    prec = f.prec.value
    R = PolynomialRing(QQ if f.base_ring == ZZ else f.base_ring,
                       names="q1, q2")
    q1, q2 = R.gens()
    I = R.ideal([q1 ** (prec + 1), q2 ** (prec + 1)])
    S = R.quotient_ring(I)
    res = sum([sum([f.fc_dct.get((n, r, m), 0) * QQ(val1) ** r
                    for r in range(-int(floor(2 * sqrt(n * m))), int(floor(2 * sqrt(n * m))) + 1)])
               * q1 ** n * q2 ** m
               for n in range(prec + 1)
               for m in range(prec + 1)])
    return S(res)
Beispiel #7
0
def gluing_equation_ideal_alt(manifold):
    n = manifold.num_tetrahedra()
    vars = []
    for i in range(n):
        vars += ['z%d' % i, 'zp%d' % i, 'zpp%d' % i]
    R = PolynomialRing(QQ, vars)
    zs = R.gens()
    toric_eqns = matrix(manifold.gluing_equations('log')).hermite_form()
    eqns = [toric_to_poly(zs, eqn) for eqn in toric_eqns]
    for i in range(n):
        z, zp, zpp = zs[3 * i:3 * (i + 1)]
        eqns += [z * zp * zpp + 1, zp * (1 - z) - 1]
    return R.ideal(eqns)
Beispiel #8
0
def gluing_variety_ideal(manifold, vars_per_tet=1):
    manifold = manifold.copy()
    n = manifold.num_tetrahedra()
    var_names = ['z%d' % i for i in range(n)]
    ideal_gens = []
    if vars_per_tet != 1:
        assert vars_per_tet == 2
        var_names += ['w%d' % i for i in range(n)]
    R = PolynomialRing(ZZ, var_names)
    if vars_per_tet == 2:
        ideal_gens += [R('z%d + w%d - 1' % (i, i)) for i in range(n)]
    eqn_data = snappy.snap.shapes.enough_gluing_equations(manifold)
    ideal_gens += [
        make_rect_eqn(R, A, B, c, vars_per_tet) for A, B, c in eqn_data
    ]
    return R.ideal(ideal_gens)
Beispiel #9
0
def _mixed_volume_gfan(gen):
    """
    Use gfan to compute the mixed volume of a collection of polytopes.
    Just like Khovanskii, we use the normalised mixed volume which satisfies
    mixed_volume([P,...,P]) = volume(P).
    """

    P = list(gen)
    n = len(P)

    if any(Q.ambient_dim() != n for Q in P):
        raise TypeError(
            'Number of polytopes and ambient dimension do not match')

    if n == 0:
        return 0
    elif n == 1:
        return P[0].volume()

    R = PolynomialRing(QQ, 'x', n)
    I = R.ideal([sum(monomial_exp(R, e) for e in Q.vertices()) for Q in P])
    return ZZ.one() / Integer(factorial(n)) * I.groebner_fan().mixed_volume()
Beispiel #10
0
def gluing_phc_standalone(manifold):
    thingy = manifold_reps.PHCGluingSolutionsOfClosed(manifold)
    R = PolynomialRing(QQ, thingy.variables)
    I = R.ideal([R(eqn) for eqn in thingy.equations])
    return phc_wrapper.find_solutions(I)
Beispiel #11
0
def verify_algebraically_GB(g, P0, alpha, trace_and_norm, verbose=True):
    # input:
    # * P0 (only necessary to shift the series)
    # * [trace_numerator, trace_denominator, norm_numerator, norm_denominator]
    # output:
    # a boolean
    if verbose:
        print "verify_algebraically()"
    L = P0.base_ring()
    assert alpha.base_ring() is L
    L_poly = PolynomialRing(L, "xL")
    xL = L_poly.gen()
    # shifting the series avoids makes our life easier
    trace_numerator, trace_denominator, norm_numerator, norm_denominator = [
        L_poly(coeff)(L_poly.gen() + P0[0]) for coeff in trace_and_norm
    ]
    L_fpoly = L_poly.fraction_field()
    trace = L_fpoly(trace_numerator) / L_fpoly(trace_denominator)
    norm = L_fpoly(norm_numerator) / L_fpoly(norm_denominator)
    L_fpoly_Z2 = PolynomialRing(L_fpoly, "z")
    z = L_fpoly_Z2.gen()
    gP0 = g(L_poly.gen() + P0[0])
    disc = (trace**2 - 4 * norm)

    IsqrtD = L_fpoly_Z2.ideal([z**2 - disc.numerator()])

    R0 = L_fpoly_Z2.quotient_ring(IsqrtD)

    iz = z / R0(z**2).lift()

    x1 = R0((trace - z / sqrt(disc.denominator())) / 2).lift()
    x2 = R0((trace + z / sqrt(disc.denominator())) / 2).lift()
    assert x1 + x2 == trace, "x1 + x2"
    assert R0(x1 * x2) == norm, "x1 * x2"

    dx1 = R0(
        (trace.derivative(xL) -
         iz * sqrt(disc.denominator()) * disc.derivative(xL) / 2) / 2).lift()
    dx2 = R0(
        (trace.derivative(xL) +
         iz * sqrt(disc.denominator()) * disc.derivative(xL) / 2) / 2).lift()
    assert R0(dx1 + dx2) == R0(trace.derivative(xL)), "dx1 + dx2"

    gx1 = R0(g(x1)).lift()
    gx2 = R0(g(x2)).lift()
    igx1 = R0(gx2).lift() / R0(gx2 * gx1).lift()
    assert R0(gx1 * igx1) == 1, "gx1 * igx1"
    igx2 = R0(gx1).lift() / R0(gx1 * gx2).lift()
    assert R0(gx2 * igx2) == 1, "gx2 * igx2"

    square = gx1.numerator()
    if verbose:
        print "Simplifying sqrt( g(x1).numerator() )"
    a1, a2, d1, d2 = simplify_sqrt(square.constant_coefficient().numerator(),
                                   square.monomial_coefficient(z).numerator(),
                                   disc.numerator())

    assert (d1.numerator() // gP0) in L or (d2.numerator() //
                                            gP0) in L, "d1 or d2"
    L_fpoly_Z = PolynomialRing(L_fpoly, 3, "z, y, w")
    z, y, w = L_fpoly_Z.gens()
    Isqrt = L_fpoly_Z.ideal([z - y * w, y**2 - d1, w**2 - d2])
    R = L_fpoly_Z.quotient_ring(Isqrt)

    iz = z / (d1 * d2)
    assert R(z * iz) == 1
    iw = w / R(w**2).lift()
    assert R(w * iw) == 1
    iy = y / R(y**2).lift()
    assert R(iy * y) == 1

    sgx1 = R(a1 * y + a2 * w).lift() / R(sqrt(gx1.denominator())).lift()
    sgx2 = R(a1 * y - a2 * w).lift() / R(sqrt(gx1.denominator())).lift()
    assert R(sgx1**2) == gx1, "sgx1**2"
    assert R(sgx2**2) == gx2, "sgx2**2"

    isgx1 = R(sgx2).lift() / R(sgx1 * sgx2).lift()
    isgx2 = R(sgx1).lift() / R(sgx1 * sgx2).lift()

    assert R(sgx1 * isgx1) == 1, "sgx1 * isgx1"
    assert R(sgx2 * isgx2) == 1, "sgx2 * isgx2"

    if verbose:
        print "adjusting to d1//g(x) or d1/g(x) in L"

    if R(w**2 / gP0).lift().degree() == 0:
        ct = iw * sqrt(R(w**2 / gP0).lift().constant_coefficient())
    else:
        assert R(w**2 / gP0).lift().degree() == 0
        ct = iy * sqrt(R(y**2 / gP0).lift().constant_coefficient())

#    else:
#        assert R(y**2/gP0).lift() in L, "\n%s\n%s\n%s\n%s\n" % ( R(y**2/gP0).lift(),  R(y**2/gP0).lift() in L, R(w**2/gP0).lift(), R(w**2/gP0).lift() in L, )
#        ct = iy * sqrt(L(R(y**2/gP0).lift()))
    eq1 = Matrix([[
        -2 * L_poly(alpha.row(0).list())(L_poly.gen() + P0[0]) * ct,
        R(dx1 * isgx1),
        R(dx2 * isgx2)
    ]])
    eq2 = Matrix([[
        -2 * L_poly(alpha.row(1).list())(L_poly.gen() + P0[0]) * ct,
        R(x1 * dx1 * isgx1),
        R(x2 * dx2 * isgx2)
    ]])
    branches = Matrix(
        R, [[1, 1, 1], [1, 1, -1], [1, -1, 1], [1, -1, 1]]).transpose()
    meq1 = eq1 * branches
    meq2 = eq2 * branches
    algzero = False
    for j in range(4):
        if meq1[0, j] == 0 and meq2[0, j] == 0:
            algzero = True
            break
    if verbose:
        print "Done, verify_algebraically()  = %s" % algzero
    return algzero
class MotionClassifier(SageObject):
    r"""
    This class implements the functionality for determining possible motions of a graph.
    """
    def __init__(self, graph, four_cycles=[], separator='', edges_ordered=[]):
        if not (isinstance(graph, FlexRiGraph) or 'FlexRiGraph' in str(type(graph))):
            raise exceptions.TypeError('The graph must be of the type FlexRiGraph.')
        self._graph = graph

        if four_cycles == []:
            self._four_cycles = self._graph.four_cycles(only_with_NAC=True) 
        else:
            self._four_cycles = four_cycles

        if not self._graph.are_NAC_colorings_named():
            self._graph.set_NAC_colorings_names()

#        -----Polynomial Ring for leading coefficients-----
        ws = []
        zs = []
        lambdas = []
        ws_latex = []
        zs_latex = []
        lambdas_latex = []
        
        if edges_ordered==[]:
            edges_ordered = self._graph.edges(labels=False)
        else:
            if (Set([self._edge2str(e) for e in edges_ordered]) !=
                Set([self._edge2str(e) for e in self._graph.edges(labels=False)])):
                raise ValueError('The provided ordered edges do not match the edges of the graph.')

        for e in edges_ordered:
            ws.append('w' + self._edge2str(e))
            zs.append('z' + self._edge2str(e))
            lambdas.append('lambda' + self._edge2str(e))
            ws_latex.append('w_{' + self._edge2str(e).replace('_', separator) + '}')
            zs_latex.append('z_{' + self._edge2str(e).replace('_', separator) + '}')
            lambdas_latex.append('\\lambda_{' + self._edge2str(e).replace('_', separator) + '}')

        self._ringLC = PolynomialRing(QQ, names=lambdas+ws+zs) #, order='lex')
        self._ringLC._latex_names = lambdas_latex + ws_latex + zs_latex
        self._ringLC_gens = self._ringLC.gens_dict()

        self._ring_lambdas = PolynomialRing(QQ, names=lambdas + ['u'])
        self._ring_lambdas._latex_names = lambdas_latex + ['u']
        self._ring_lambdas_gens = self._ring_lambdas.gens_dict()
        self.aux_var = self._ring_lambdas_gens['u']
        
        xs = []
        ys = []
        xs_latex = []
        ys_latex = []
        for v in self._graph.vertices():
            xs.append('x' + str(v))
            ys.append('y' + str(v))
            xs_latex.append('x_{' + str(v) + '}')
            ys_latex.append('y_{' + str(v) + '}')
            
        self._ring_coordinates = PolynomialRing(QQ, names=lambdas+xs+ys)
        self._ring_coordinates._latex_names = lambdas_latex + xs_latex + ys_latex
        self._ring_coordinates_gens = self._ring_coordinates.gens_dict()
        
        
#        ----Ramification-----
#         if len(self._graph.NAC_colorings()) > 1: 
        self._ring_ramification = PolynomialRing(QQ,
                                                 [col.name() for col in self._graph.NAC_colorings()],
                                                 len(self._graph.NAC_colorings()))
#         else:
#             self._ring_ramification = PolynomialRing(QQ, self._graph.NAC_colorings()[0].name())
        self._ring_ramification_gens = self._ring_ramification.gens_dict()
        self._restriction_NAC_types = self.NAC_coloring_restrictions()

#        -----Graph of 4-cycles-----
        self._four_cycle_graph = Graph([self._four_cycles,[]], format='vertices_and_edges')

        for c1, c2 in Subsets(self._four_cycle_graph.vertices(), 2):
            intersection = self.cycle_edges(c1, sets=True).intersection(self.cycle_edges(c2, sets=True))
            if len(intersection)>=2 and len(intersection[0].intersection(intersection[1]))==1:
                common_vert = intersection[0].intersection(intersection[1])[0]
                self._four_cycle_graph.add_edge(c1, c2, common_vert)

#        -----Cycle with orthogonal diagonals due to NAC-----
        self._orthogonal_diagonals = {
                delta.name(): [cycle for cycle in self._four_cycle_graph if delta.cycle_has_orthogonal_diagonals(cycle)]
                for delta in self._graph.NAC_colorings()}

    @doc_index("Constraints on edge lengths")
    def four_cycles_ordered(self):
        r"""
        Heuristic order of 4-cycles.
        """
        cliques = self._four_cycle_graph.cliques_maximal()
        cycles = max(cliques,
                     key=lambda clique: sum([self._four_cycle_graph.degree(v) for v in clique]))
        missing_cliques = {tuple(clique):0 for clique in cliques}
        missing_cliques.pop(tuple(cycles))
        while missing_cliques:
            next_clique = max(missing_cliques.keys(),
                             key=lambda clique:sum([1 for c in clique for c2 in self._four_cycle_graph.neighbors(c) if c2 in cycles]))
            missing_cliques.pop(next_clique)
            missing_cycles = {c:0 for c in next_clique if not c in cycles}
            while missing_cycles:
                next_cycle = max(missing_cycles.keys(),
                                key=lambda c:sum([1 for c2 in self._four_cycle_graph.neighbors(c) if c2 in cycles]))
                cycles.append(next_cycle)
                missing_cycles.pop(next_cycle)

        missing_cycles = {c:0 for c in self._four_cycle_graph.vertices() if not c in cycles}
        while missing_cycles:
            next_cycle = max(missing_cycles.keys(),
                            key=lambda c:sum([1 for c2 in self._four_cycle_graph.neighbors(c) if c2 in cycles]))
            cycles.append(next_cycle)
            missing_cycles.pop(next_cycle)
        return cycles

    def _repr_(self):
        return 'Motion Classifier of ' + str(self._graph)

    @staticmethod
    def _edge2str(e):
        if e[0]<e[1]:
            return str(e[0]) + '_' + str(e[1])
        else:
            return str(e[1]) + '_' + str(e[0])

    
    @staticmethod
    @doc_index("Other")
    def cycle_edges(cycle, sets=False):
        r"""
        Return edges of a 4-cycle.
        """
        if sets:
            return Set([Set(list(e)) for e in zip(cycle, list(cycle[1:])+[cycle[0]])])
        else:
            return [list(e) for e in zip(cycle, list(cycle[1:])+[cycle[0]])]

    @staticmethod
    @doc_index("Other")
    def four_cycle_normal_form(cycle, motion_type):
        r"""
        Return a 4-cycle with a motion type in the normal form.
        """
        i = cycle.index(min(cycle))
        oe =  ['o', 'e']
        if i % 2 == 1 and motion_type in oe:
            motion_type = oe[1 - oe.index(motion_type)]
        tmp_c = cycle[i:]+cycle[:i]
        if tmp_c[1]<tmp_c[3]:
            return tmp_c,  motion_type
        else:
            return (tmp_c[0], tmp_c[3], tmp_c[2], tmp_c[1]), motion_type

    @staticmethod
    @doc_index("Other")
    def normalized_motion_types(motion_types):
        r"""
        Return motion types in the normal form.
        """
        res = {}
        for c, t in motion_types.items():
            norm_c, norm_t = MotionClassifier.four_cycle_normal_form(c, t)
            res[norm_c] = norm_t
        return res

    def _w(self, e):
        if e[0] < e[1]:
            return self._ringLC_gens['w'+self._edge2str(e)]
        else:
            return -self._ringLC_gens['w'+self._edge2str(e)]

    def _z(self, e):
        if e[0] < e[1]:
            return self._ringLC_gens['z'+self._edge2str(e)]
        else:
            return -self._ringLC_gens['z'+self._edge2str(e)]

    def _lam(self, e):
        return self._ringLC_gens['lambda'+self._edge2str(e)]
    
    @doc_index("Constraints on edge lengths")
    def lam(self, u,v):
        r"""
        Return the variable for edge length in the ring of edge lengths.
        """
        return self._ring_lambdas_gens['lambda'+self._edge2str([u,v])]

    @doc_index("Motion types consistent with 4-cycles")
    def mu(self, delta):
        r"""
        Return the variable for a given NAC-coloring.
        """
        if type(delta)==str:
            return self._ring_ramification_gens[delta]
        else:
            return self._ring_ramification_gens[delta.name()]

    @doc_index("System of equations for coordinates")
    def x(self, v):
        r"""
        Return the variable for x coordinate of a vertex. 
        """
        return self._ring_coordinates_gens['x'+str(v)]

    @doc_index("System of equations for coordinates")
    def y(self, v):
        r"""
        Return the variable for y coordinate of a vertex. 
        """
        return self._ring_coordinates_gens['y'+str(v)]

    @doc_index("System of equations for coordinates")
    def l(self, u,v):
        r"""
        Return the variable for edge length in the ring with coordinates.
        """
        return self._ring_coordinates_gens['lambda'+self._edge2str([u,v])]

    @doc_index("Constraints on edge lengths")
    def equations_from_leading_coefs(self, delta, extra_eqs=[], check=True):
        r"""
        Return equations for edge lengths from leading coefficients system.

        EXAMPLES::

            sage: from flexrilog import GraphGenerator, MotionClassifier
            sage: K33 = GraphGenerator.K33Graph()
            sage: M = MotionClassifier(K33)
            sage: M.equations_from_leading_coefs('epsilon56')
            [lambda1_2^2 - lambda1_4^2 - lambda2_3^2 + lambda3_4^2]

        ::

            sage: M.equations_from_leading_coefs('omega1')
            Traceback (most recent call last):
            ...
            ValueError: The NAC-coloring must be a singleton.

        ::

            sage: M.equations_from_leading_coefs('omega1', check=False)
            [lambda2_5^2*lambda3_4^2 - lambda2_5^2*lambda3_6^2 - lambda2_3^2*lambda4_5^2 + lambda3_6^2*lambda4_5^2 + lambda2_3^2*lambda5_6^2 - lambda3_4^2*lambda5_6^2]
        """

        if type(delta) == str:
            delta = self._graph.name2NAC_coloring(delta)

        if check:
            if not delta.is_singleton():
                raise exceptions.ValueError('The NAC-coloring must be a singleton.')
        eqs_lengths=[]
        for e in self._graph.edges():
            eqs_lengths.append(self._z(e)*self._w(e) - self._lam(e)**_sage_const_2)


        eqs_w=[]
        eqs_z=[]
        for T in self._graph.spanning_trees():
            for e in self._graph.edges():
                eqw = 0
                eqw_all = 0
                eqz = 0
                eqz_all = 0
                path = T.shortest_path(e[0],e[1])
                for u,v in zip(path, path[1:]+[path[0]]):
                    if delta.is_red(u,v):
                        eqz+=self._z([u,v])
                    else:
                        eqw+=self._w([u,v])
                    eqw_all+=self._w([u,v])
                    eqz_all+=self._z([u,v])
                if eqw:
                    eqs_w.append(eqw)
                else:
                    eqs_w.append(eqw_all)
                if eqz:
                    eqs_z.append(eqz)
                else:
                    eqs_z.append(eqz_all)

        equations = (ideal(eqs_w).groebner_basis()
                     + ideal(eqs_z).groebner_basis()
                     + eqs_lengths
                     + [self._ringLC(eq) for eq in extra_eqs])
        return [self._ring_lambdas(eq)
                for eq in ideal(equations).elimination_ideal(flatten(
                    [[self._w(e), self._z(e)] for e in self._graph.edges()])).basis
                ]

    @staticmethod
    def _pair_ordered(u,v):
        if u<v:
            return (u, v)
        else:
            return (v, u)

    @staticmethod
    def _edge_ordered(u,v):
        return MotionClassifier._pair_ordered(u, v)

#    @staticmethod
    def _set_two_edge_same_lengths(self, H, u, v, w, y, k):
        if H[self._edge_ordered(u,v)]==None and H[self._edge_ordered(w,y)]==None:
            H[self._edge_ordered(u,v)] = k
            H[self._edge_ordered(w,y)] = k
            return 1
        elif H[self._edge_ordered(u,v)]==None:
            H[self._edge_ordered(u,v)] = H[self._edge_ordered(w,y)]
            return 0
        elif H[self._edge_ordered(w,y)]==None:
            H[self._edge_ordered(w,y)] = H[self._edge_ordered(u,v)]
            return 0
        elif H[self._edge_ordered(u,v)]!=H[self._edge_ordered(w,y)]:
            col= H[self._edge_ordered(u,v)]
            for u,v in H.keys():
                if H[(u,v)]==col:
                    H[(u,v)] = H[self._edge_ordered(w,y)]
            return 0
        return 0

    def _set_same_lengths(self, H, types):
        for u,v in H.keys():
            H[(u,v)] = None
        k=1
        for c in types:
            motion = types[c]
            if motion=='a' or motion=='p':
                k += self._set_two_edge_same_lengths(H, c[0], c[1], c[2], c[3], k)
                k += self._set_two_edge_same_lengths(H, c[1], c[2], c[0], c[3], k)
            elif motion=='o':
                k += self._set_two_edge_same_lengths(H, c[0], c[1], c[1], c[2], k)
                k += self._set_two_edge_same_lengths(H, c[2], c[3], c[0], c[3], k)
            elif motion=='e':
                k += self._set_two_edge_same_lengths(H, c[1], c[2], c[2], c[3], k)
                k += self._set_two_edge_same_lengths(H, c[0], c[1], c[0], c[3], k)

    @doc_index("Constraints on edge lengths")
    def motion_types2same_edge_lenghts(self, motion_types):
        r"""
        Return the dictionary of same edge lengths enforced by given motion types.
        """
        H = {self._edge_ordered(u,v):None for u,v in self._graph.edges(labels=False)}
        self._set_same_lengths(H, motion_types)
        return H

    @doc_index("Motion types consistent with 4-cycles")
    def NAC_coloring_restrictions(self):
        r"""
        Return types of restrictions of NAC-colorings to 4-cycles.

        EXAMPLE::

            sage: from flexrilog import MotionClassifier, GraphGenerator
            sage: MC = MotionClassifier(GraphGenerator.K33Graph())
            sage: MC.NAC_coloring_restrictions()
            {(1, 2, 3, 4): {'L': ['omega3', 'omega1', 'epsilon36', 'epsilon16'],
              'O': ['epsilon34', 'epsilon14', 'epsilon23', 'epsilon12'],
              'R': ['omega4', 'epsilon45', 'omega2', 'epsilon25']},
            ...
             (3, 4, 5, 6): {'L': ['omega5', 'omega3', 'epsilon25', 'epsilon23'],
              'O': ['epsilon56', 'epsilon36', 'epsilon45', 'epsilon34'],
              'R': ['omega6', 'epsilon16', 'omega4', 'epsilon14']}}
        """
        res = {cycle:{'O':[], 'L':[], 'R':[]} for cycle in self._four_cycles}
        for delta in self._graph.NAC_colorings():
            for cycle in self._four_cycles:
                colors = [delta.color(e) for e in self.cycle_edges(cycle)]
                if colors[0]==colors[1]:
                    if colors[0]!=colors[2]:
                        res[cycle]['R'].append(delta.name())
                elif colors[1]==colors[2]:
                    res[cycle]['L'].append(delta.name())
                else:
                    res[cycle]['O'].append(delta.name())
        return res

    @doc_index("Motion types consistent with 4-cycles")
    def ramification_formula(self, cycle, motion_type):
        r"""
        Return ramification formula for a given 4-cycle and motion type.

        EXAMPLES::

            sage: from flexrilog import MotionClassifier, GraphGenerator
            sage: MC = MotionClassifier(GraphGenerator.K33Graph())
            sage: MC.ramification_formula((1,2,3,4), 'a')
            [epsilon34,
             epsilon14,
             epsilon23,
             epsilon12,
             omega3 + omega1 + epsilon36 + epsilon16 - omega4 - epsilon45 - omega2 - epsilon25]
        """
        eqs_present = []
        eqs_zeros = []
        NAC_types = self.motion_types2NAC_types(motion_type)
        for t in ['L','O','R']:
            if t in NAC_types:
                eqs_present.append(sum([self.mu(delta) for delta in self._restriction_NAC_types[cycle][t]]))
            else:
                eqs_zeros += [self.mu(delta) for delta in self._restriction_NAC_types[cycle][t]]
        
        if 0 in eqs_present:
            return [self.mu(delta) for delta in self._graph.NAC_colorings()]
        if len(eqs_present)==2:
            return eqs_zeros + [eqs_present[0] - eqs_present[1]]
        elif len(eqs_present)==3:
            return eqs_zeros + [eqs_present[0] - eqs_present[1], eqs_present[1] - eqs_present[2]]
        else:
            return eqs_zeros

    @staticmethod
    @doc_index("Other")
    def motion_types2NAC_types(m):
        r"""
        Return NAC-coloring types for a given motion type.
        """
        if m=='g':
            return ['L','R','O']
        if m=='a':
            return ['L','R']
        if m=='p':
            return ['O']
        if m=='e':
            return ['R','O']
        if m=='o':
            return ['L','O']

    @staticmethod
    @doc_index("Other")
    def NAC_types2motion_type(t):
        r"""
        Return the motion type for given types of NAC-colorings.
        """
        if Set(t)==Set(['L','R','O']):
            return 'g'
        if Set(t)==Set(['L','R']):
            return 'a'
        if Set(t)==Set(['O']):
            return 'p'
        if Set(t)==Set(['R','O']):
            return 'e'
        if Set(t)==Set(['L','O']):
            return 'o'
    
    @doc_index("Other")
    def active_NACs2motion_types(self, active):
        r"""
        Return the motion types of 4-cycles for a given set of active NAC-colorings. 
        """
        motion_types = {cycle:[] for cycle in self._four_cycles}
        for delta in active:
            if type(delta)!=str:
                delta = delta.name()
            for cycle in motion_types:
                motion_types[cycle] += [t for t, colorings 
                                        in self._restriction_NAC_types[cycle].items()
                                        if delta in colorings]
        for cycle in motion_types:
            motion_types[cycle] = self.NAC_types2motion_type(motion_types[cycle])
        return motion_types

    @staticmethod
    def _same_edge_lengths(K, edges_to_check):
        if edges_to_check:
            length = K[MotionClassifier._edge_ordered(edges_to_check[0][0], edges_to_check[0][1])]
            if length==None:
                return False
            for u,v in edges_to_check:
                if length!=K[MotionClassifier._edge_ordered(u,v)]:
                    return False
            return True
        else:
            return True

    @doc_index("Motion types consistent with 4-cycles")
    def consequences_of_nonnegative_solution_assumption(self, eqs):
        r"""
        Return equations implied by the assumption of the existence of nonnegative solutions.
        """
        n_zeros_prev = -1
        zeros = []
        gb = eqs
        while n_zeros_prev!=len(zeros):
            n_zeros_prev = len(zeros)
            gb = self._ring_ramification.ideal(gb + zeros).groebner_basis()
#             gb = self._ring_ramification.ideal(gb + zeros).groebner_basis()
            zeros = []
            for eq in gb:
                coefs = eq.coefficients()
                if sum([sgn(a)*sgn(b) for a,b in zip(coefs[:-1],coefs[1:])])==len(coefs)-1:
                    zeros += eq.variables()
        return [zeros, gb]

#    @staticmethod
#    def consequences_of_nonnegative_solution_assumption(eqs):
#        n_zeros_prev = -1
#        zeros = {}
#        gb = ideal(eqs).groebner_basis()
#        while n_zeros_prev!=len(zeros):
#            n_zeros_prev = len(zeros)
#            gb = [eq.substitute(zeros) for eq in gb if eq.substitute(zeros)!=0]
#            for eq in gb:
#                coefs = eq.coefficients()
#                if sum([sgn(a)*sgn(b) for a,b in zip(coefs[:-1],coefs[1:])])==len(coefs)-1:
#                    for zero_var in eq.variables():
#                        zeros[zero_var] = 0
#        return [zeros.keys(), gb]

    @doc_index("Motion types consistent with 4-cycles")
    def consistent_motion_types(self):#, cycles=[]):
        r"""
        Return the list of motion types consistent with 4-cycles.
        """
#         if cycles==[]:
        cycles = self.four_cycles_ordered()

        k23s = [Graph(self._graph).subgraph(k23_ver).edges(labels=False) for k23_ver in self._graph.induced_K23s()]

        aa_pp = [('a', 'a'), ('p', 'p')]
        ao = [('a','o'), ('o','a')]
        ae = [('a','e'), ('e','a')]
        oe = [('o', 'e'), ('e', 'o')]
        oo_ee = [('e','e'), ('o','o')]

        H = {self._edge_ordered(u,v):None for u,v in self._graph.edges(labels=False)}
        types_prev=[[{}, []]]
        
        self._num_tested_combinations = 0

        for i, new_cycle in enumerate(cycles):
            types_ext = []
            new_cycle_neighbors = [[c2,
                                    new_cycle.index(self._four_cycle_graph.edge_label(new_cycle, c2)),
                                    c2.index(self._four_cycle_graph.edge_label(new_cycle, c2)),
                                   ] for c2 in self._four_cycle_graph.neighbors(new_cycle) if c2 in cycles[:i]]
            for types_original, ramification_eqs_prev in types_prev:
                for type_new_cycle in ['g','a','p','o','e']:
                    self._num_tested_combinations +=1
                    types = deepcopy(types_original)
                    types[tuple(new_cycle)] = type_new_cycle
    #                 H = deepcopy(orig_graph)
                    inconsistent = False

                    for c2, new_index, c2_index in new_cycle_neighbors:
                        type_pair = (types[new_cycle], types[c2])
                        if (type_pair in aa_pp
                                or (type_pair in oe and new_index%2 == c2_index%2)
                                or (type_pair in oo_ee and new_index%2 != c2_index%2)):
                            inconsistent = True
                            break
                        if type_pair in ao:
                            ind_o = type_pair.index('o')
                            if [new_index, c2_index][ind_o] % 2 == 1:
                            # odd deltoid (1,2,3,4) is consistent with 'a' if the common vertex is odd,
                            # but Python lists are indexed from 0
                                inconsistent = True
                                break
                        if type_pair in ae:
                            ind_e = type_pair.index('e')
                            if [new_index, c2_index][ind_e] % 2 == 0:
                                inconsistent = True
                                break
                    if inconsistent:
                        continue

                    self._set_same_lengths(H, types)
                    for c in types:
                        if types[c]=='g':
                            labels = [H[self._edge_ordered(c[i-1],c[i])] for i in range(0,4)]
                            if (not None in labels
                                and ((len(Set(labels))==2 and labels.count(labels[0])==2)
                                    or len(Set(labels))==1)):
                                inconsistent = True
                                break
                    if inconsistent:
                        continue
                    for K23_edges in k23s:
                        if MotionClassifier._same_edge_lengths(H, K23_edges):
                            inconsistent = True
                            break
                    if inconsistent:
                        continue

                    ramification_eqs = ramification_eqs_prev + self.ramification_formula(new_cycle, type_new_cycle)
                    zero_variables, ramification_eqs = self.consequences_of_nonnegative_solution_assumption(ramification_eqs)
                    for cycle in types:
                        if inconsistent:
                            break
                        for t in self.motion_types2NAC_types(types[cycle]):
                            has_necessary_NAC_type = False
                            for delta in self._restriction_NAC_types[cycle][t]:
                                if not self.mu(delta) in zero_variables:
                                    has_necessary_NAC_type = True
                                    break
                            if not has_necessary_NAC_type:
                                inconsistent = True
                                break
                    if inconsistent:
                        continue

                    types_ext.append([types, ramification_eqs])

            types_prev=types_ext

        return [t for t, _ in types_prev]

    @doc_index("Other")
    def active_NAC_coloring_names(self, motion_types):
        r"""
        Return the names of active NAC-colorings for given motion types.
        """
        return [delta.name() for delta in self.motion_types2active_NACs(motion_types)]
        
    @doc_index("Motion types consistent with 4-cycles")
    def motion_types2active_NACs(self, motion_types):
        r"""
        Return the active NAC-colorings for given motion types, if uniquely determined.
        """
        zeros, eqs = self.consequences_of_nonnegative_solution_assumption(
            flatten([self.ramification_formula(c, motion_types[c]) for c in motion_types]))

        if self._ring_ramification.ideal(eqs).dimension()==1:
            return [delta for delta in self._graph.NAC_colorings() if not self.mu(delta) in zeros]
        else:
            raise NotImplementedError('There might be more solutions (dim '+str(
                self._ring_ramification.ideal(eqs).dimension()) + ')')

    @doc_index("General methods")
    def motion_types_equivalent_classes(self,  motion_types_list):
        r"""
        Split a list of motion types into isomorphism classes.
        """
        aut_group = self._graph.automorphism_group()
        classes = [
            [( motion_types_list[0],
              self.normalized_motion_types( motion_types_list[0]),
              Counter([('d' if t in ['e','o'] else t) for c, t in  motion_types_list[0].items()]))]
        ]
        for next_motion in  motion_types_list[1:]:
            added = False
            next_sign = Counter([('d' if t in ['e','o'] else t) for c, t in next_motion.items()])
            for cls in classes:
                repr_motion_types = cls[0][1]
                if cls[0][2]!=next_sign:
                    continue
                for sigma in aut_group:
                    next_motion_image = self.normalized_motion_types({tuple(sigma(v) for v in c): t
                                                        for c,t in next_motion.items()})
                    for c in repr_motion_types:
                        if repr_motion_types[c]!=next_motion_image[c]:
                            break
                    else:
                        cls.append([next_motion])
                        added = True
                        break
#                    if not False in [repr_motion_types[c]==next_motion_image[c] for c in repr_motion_types]:
#                        cls.append(next_motion)
#                        added = True
#                        break
                if added:
                    break
            else:
                classes.append([(next_motion, self.normalized_motion_types(next_motion), next_sign)])
        return [[t[0] for t in cls] for cls in classes]

    @doc_index("General methods")
    def check_orthogonal_diagonals(self, motion_types,  active_NACs, extra_cycles_orthog_diag=[]):
        r"""
        Check the necessary conditions for orthogonal diagonals.

        TODO:
        
        return orthogonality_graph
        """
        perp_by_NAC = [cycle for delta in active_NACs for cycle in self._orthogonal_diagonals[delta]]
        deltoids = [cycle for cycle, t in motion_types.items() if t in ['e','o']]

        orthogonalLines = []
        for perpCycle in perp_by_NAC + deltoids + extra_cycles_orthog_diag:
            orthogonalLines.append(Set([Set([perpCycle[0],perpCycle[2]]), Set([perpCycle[1],perpCycle[3]])]))

        orthogonalityGraph = Graph(orthogonalLines, format='list_of_edges', multiedges=False)
        n_edges = -1

        while  n_edges != orthogonalityGraph.num_edges():
            n_edges = orthogonalityGraph.num_edges()
            for perp_subgraph in orthogonalityGraph.connected_components_subgraphs():
                isBipartite, partition = perp_subgraph.is_bipartite(certificate=True)
                if isBipartite:
                    graph_0 = Graph([v.list() for v in partition if partition[v]==0])
                    graph_1 = Graph([v.list() for v in partition if partition[v]==1])
                    for comp_0 in graph_0.connected_components():
                        for comp_1 in graph_1.connected_components():
                            for e0 in Subsets(comp_0,2):
                                for e1 in Subsets(comp_1,2):
                                    orthogonalityGraph.add_edge([Set(e0), Set(e1)])
                else:
                    raise exceptions.RuntimeError('A component of the orthogonality graph is not bipartite!')

        self._orthogonality_graph = orthogonalityGraph
        check_again = False
        H = {self._edge_ordered(u,v):None for u,v in self._graph.edges(labels=False)}
        self._set_same_lengths(H, motion_types)

        for c in motion_types:
            if not orthogonalityGraph.has_edge(Set([c[0],c[2]]),Set([c[1],c[3]])):
                continue
            if motion_types[c]=='a':       # inconsistent since antiparallel motion cannot have orthogonal diagonals
                return False
            elif motion_types[c]=='p':     # this cycle must be rhombus
                self._set_two_edge_same_lengths(H, c[0], c[1], c[2], c[3], 0)
                self._set_two_edge_same_lengths(H, c[0], c[1], c[1], c[2], 0)
                self._set_two_edge_same_lengths(H, c[0], c[1], c[0], c[3], 0)
                check_again = True

        for c in motion_types:
            if motion_types[c]=='g':
                labels = [H[self._edge_ordered(c[i-1],c[i])] for i in range(0,4)]
                if (not None in labels
                    and ((len(Set(labels))==2 and labels.count(labels[0])==2)
                        or len(Set(labels))==1)):
                    return False
                if (orthogonalityGraph.has_edge(Set([c[0],c[2]]),Set([c[1],c[3]]))
                    and True in [(H[self._edge_ordered(c[i-1], c[i])]==H[self._edge_ordered(c[i-2], c[i-1])]
                                  and H[self._edge_ordered(c[i-1],c[i])]!= None) for i in range(0,4)]):
                    return False

        if check_again:
            for K23_edges in [Graph(self._graph).subgraph(k23_ver).edges(labels=False) for k23_ver in self._graph.induced_K23s()]:
                if MotionClassifier._same_edge_lengths(H, K23_edges):
                    return False

        return True

    @doc_index("General methods")
    def possible_motion_types_and_active_NACs(self,
                                              comments = {},
                                              show_table=True,
                                              one_representative=True,
                                              tab_rows=False,
                                              keep_orth_failed=False,
                                              equations=False):
        r"""
        Wraps the function for consistent motion types, conditions on orthogonality of diagonals and splitting into equivalence classes.
        """
        types = self.consistent_motion_types()
        classes = self.motion_types_equivalent_classes(types)
        valid_classes = []
        
        motions = [ 'g','a','p','d']
        if one_representative:
            header = [['index', '#', 'motion types'] + motions + ['active NACs', 'comment']]
        else:
            header = [['index', '#', 'elem.', 'motion types'] + motions + ['active NACs', 'comment']]
        if equations:
            header[0].append('equations')
        rows = []
        for i, cls in enumerate(classes):
            rows_cls = []
            to_be_appended = True
            for j, t in enumerate(cls):
                row = [i, len(cls)]
                if not one_representative:
                    row.append(j)
                row.append(' '.join([t[c] for c in self.four_cycles_ordered()]))
                row += [Counter([('d' if s in ['e','o'] else s) for c, s in t.items()])[m] for m in motions]
                try:
                    active = self.active_NAC_coloring_names(t)
                    row.append([self.mu(name) for name in sorted(active)])
                    if self.check_orthogonal_diagonals(t, active):
                        row.append(comments.get(i,''))
                    else:
                        to_be_appended = False
                        if not keep_orth_failed:
                            continue
                        else:
                            row.append('orthogonality check failed' + str(comments.get(i,'')))
                            
                except NotImplementedError as e:
                    zeros, eqs = self.consequences_of_nonnegative_solution_assumption(
                        flatten([self.ramification_formula(c, t[c]) for c in t]))
                    row.append([eq for eq in eqs if not eq in zeros])
                    row.append(str(comments.get(i,'')) + str(e))
                    
                if equations:
                    zeros, eqs = self.consequences_of_nonnegative_solution_assumption(
                        flatten([self.ramification_formula(c, t[c]) for c in t]))
                    row.append([eq for eq in eqs if not eq in zeros])
                    
                rows_cls.append(row)
                
                if one_representative:
                    break
            if to_be_appended or keep_orth_failed:
                valid_classes.append(cls)
                if one_representative:
                    rows += rows_cls
                else:
                    rows.append(rows_cls)
        if show_table:
            if one_representative:
                T = table(header + rows)
            else:
                T = table(header + [row for rows_cls in rows for row in rows_cls])
            T.options()['header_row'] = True
            display(T)

        if tab_rows:
            return valid_classes, rows
        return valid_classes
        

    @doc_index("Constraints on edge lengths")
    def motion_types2equations(self, motion_types,
                                           active_NACs=None,
                                           groebner_basis=True,
                                           extra_eqs=[]):
        r"""
        Return equations enforced by edge lengths and singleton active NAC-colorings.
        """
        if active_NACs==None:
            active_NACs = self.motion_types2active_NACs(motion_types)
        
        eqs_same_lengths = self.motion_types2same_lengths_equations(motion_types)
        eqs = flatten([self.equations_from_leading_coefs(delta, check=False,
                                                 extra_eqs=eqs_same_lengths + extra_eqs)
                    for delta in active_NACs if delta.is_singleton(active_NACs)
                ])
        if groebner_basis:
            return ideal(eqs).groebner_basis()
        else:
            return eqs
    
    @doc_index("General methods")
    def degenerate_triangle_equation(self, u, v, w):
        r"""
        Return the equation for a degenerate triangle.
        """
        return self.lam(u,v) - self.lam(u,w) - self.lam(w,v)
        

    @doc_index("Constraints on edge lengths")
    def motion_types2same_lengths_equations(self, motion_types):
        r"""
        Return the equations for edge lengths enforced by motion types.
        """
        eqs = []
        for c, motion in motion_types.items():
            if motion=='a' or motion=='p':
                eqs.append(self.lam(c[0], c[1]) - self.lam(c[2], c[3]))
                eqs.append(self.lam(c[1], c[2]) - self.lam(c[0], c[3]))
            elif motion=='o':
                eqs.append(self.lam(c[0], c[1]) - self.lam(c[1], c[2]))
                eqs.append(self.lam(c[2], c[3]) - self.lam(c[0], c[3]))
            elif motion=='e':
                eqs.append(self.lam(c[1], c[2]) - self.lam(c[2], c[3]))
                eqs.append(self.lam(c[0], c[1]) - self.lam(c[0], c[3]))
        return [eq for eq in ideal(eqs).groebner_basis()] if eqs else []


    @doc_index("Constraints on edge lengths")
    def graph_with_same_edge_lengths(self, motion_types, plot=True):
        r"""
        Return a graph with edge labels corresponding to same edge lengths.

        INPUT:
        
        - `plot` -- if `True` (default), then plot of the graph is returned. 
        
        OUTPUT:
        
        The edge labels of the output graph are same for if the edge lengths
        are same due to `motion_types`. 
        """
        H = {self._edge_ordered(u,v):None for u,v in self._graph.edges(labels=False)}
        self._set_same_lengths(H, motion_types)
        G_labeled = Graph([[u,v,H[(u,v)]] for u,v in H])
        G_labeled._pos = self._graph._pos
        if plot:
            return G_labeled.plot(edge_labels=True, color_by_label=True)
        else:
            return G_labeled

    @doc_index("Constraints on edge lengths")
    def singletons_table(self, active_NACs=None):
        r"""
        Return table whether (active) NAC-colorings are singletons.
        """
        rows = [['NAC-coloring', 'is singleton']]
        if active_NACs==None:
            active_NACs = self._graph.NAC_colorings()
            only_active = False
        else:
            only_active = True
            rows[0].append('is singleton w.r.t. active')

        for delta in active_NACs:
            rows.append([delta.name(), delta.is_singleton()])
            if only_active:
                rows[-1].append(delta.is_singleton(active_NACs))
        T = table(rows)
        T.options()['header_row'] = True
        return T
    
    @doc_index("System of equations for coordinates")
    def edge_equations_ideal(self, fixed_edge, eqs_lamdas=[], extra_eqs=[], show_input=False):
        r"""
        Return the ideal of equations for coordinates of vertices and given edge constraints.
        """
        equations = []
        for u,v in self._graph.edges(labels=False):
            equations.append((self.x(u)-self.x(v))**_sage_const_2 + (self.y(u)-self.y(v))**_sage_const_2 - self.l(u,v)**_sage_const_2)
        equations += [
            self.x(fixed_edge[0]),
            self.y(fixed_edge[0]),
            self.y(fixed_edge[1]),
            self.x(fixed_edge[1]) - self.l(fixed_edge[0], fixed_edge[1]),
            ] + [
                self._ring_coordinates(eq) for eq in list(eqs_lamdas) + list(extra_eqs)
                ]
        if show_input:
            for eq in equations:
                show(eq)
        return ideal(equations)
    
    @doc_index("General methods")
    def edge_lengths_dimension(self, eqs_lambdas):
        r"""
        Return the dimension of the variaty of edge lengths.
        """
        return ideal(eqs_lambdas + [self.aux_var]).dimension()
    
    @doc_index("Other")
    def edge_lengts_dict2eqs(self, edge_lengths):
        r"""
        Return equations with asigned edge lengths.
        """
        return [self.lam(e[0],e[1]) - QQ(edge_lengths[e]) for e in edge_lengths ]

    @doc_index("General methods")
    def edge_lengths_satisfy_eqs(self, eqs, edge_lengths, print_values=False):
        r"""
        Check if a given dictionary of edge lengths satisfy given equations.
        """
        I = ideal(self.edge_lengts_dict2eqs(edge_lengths))
        if print_values:
            print([(eq.reduce(I)) for eq in eqs])
        return sum([(eq.reduce(I))**2 for eq in eqs])==0

    
    @staticmethod
    @doc_index("Other")
    def show_factored_eqs(eqs, only_print=False, numbers=False,
                          variables=False, print_latex=False,
                          print_eqs=True):
        r"""
        Show given equations factored.
        """
        for i, eq in enumerate(eqs):
            factors = factor(eq)
            if numbers:
                print(i)
            if variables:
                print(latex(eq.variables()))
            if print_latex:
                print(latex(factors) + '=0\,, \\\\')
            if print_eqs:
                if only_print:
                    print(factors)
                else:
                    show(factors)
    
    @staticmethod
    @doc_index("General methods")
    def is_subcase(eqs_a, eqs_b):
        r"""
        Return if `eqs_a` is a subcase of `eqs_b`, i.e., the ideal of `eqs_a` contains the ideal of `eqs_b`.
        """
        I_a = ideal(eqs_a)
        for eq in eqs_b:
            if not eq in I_a:
                return False
        return True
    
    @doc_index("Other")
    def motion_types2tikz(self,
                          motion_types,
                          color_names=[]
                          , vertex_style='lnodesmall',
                          none_gray=False,
                          ):
        r"""
        Return TikZ code for the graph with edges colored according to the lengths enforced by motion types.
        """
        H = {self._edge_ordered(u,v):None for u,v in self._graph.edges(labels=False)}
        self._set_same_lengths(H, motion_types)
        edge_partition = [[e for e in H if H[e]==el] for el in Set(H.values()) if el!=None]
        if none_gray:
            edge_partition.append([e for e in H if H[e]==None]) 
        else:
            edge_partition += [[e] for e in H if H[e]==None]
        if color_names==[]:
            color_names = ['edge, col{}'.format(i) for i in range(1,len(edge_partition)+1)]
        if none_gray:
            color_names[-1] = 'edge'

        self._graph.print_tikz(colored_edges= edge_partition,
                               color_names=color_names[:len(edge_partition)],
                               vertex_style=vertex_style)
Beispiel #13
0
b = matrix(R, 2, 2, R.gens()[4:8])
c = matrix(R, 2, 2, R.gens()[8:12])
A = inverse_sl2(a)
B = inverse_sl2(b)
C = inverse_sl2(c)


def eval_word(word):
    mats = {'a': a, 'b': b, 'c': c, 'A': A, 'B': B, 'C': C}
    return prod([mats[x] for x in word])


I = R.ideal([a.det() - 1, b.det() - 1, c.det() - 1] +
            sum([(eval_word(R) - 1).list() for R in G.relators()], []) +
            [eval_word(w)[1, 0]
             for w in periph] + [eval_word(w)[0, 0] - 1 for w in periph] +
            [eval_word(w)[1, 1] - 1 for w in periph] +
            [eval_word(periph[0])[0, 1] -
             1, eval_word('a')[0, 1]])

# Magma confirms that I has dimension 0 via:
#
# sage: magma(I).Dimension()
# 0
#
# So there is a parbolic representation to SL(2, C) where every
# parabolic has trace +2, and so ptolemy really is missing a rep.  I
# didn't check that the rep lands in SL(2, R).


# Doesn't work because of PHC error "raised STORAGE_ERROR : stack
Beispiel #14
0
def extended_ptolemy_equations(manifold,
                               gen_obs_class=None,
                               nonzero_cond=True,
                               return_full_var_dict=False,
                               notation='short'):
    """
    We assign ptolemy coordinates ['a', 'b', 'c', 'd', 'e', 'f'] to the
    *directed* edges::

        [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]


    where recall that the basic orientation convention of t3m and
    SnapPy is that a positively oriented simplex is as below.


              1
             /|\
           d/ | \e
           /  |  \
          /   |   \
         2----|----3   with back edge from 2 to 3 labelled f.
          \   |   /
          b\  |a /c
            \ | /
             \|/
              0

    sage: M = Manifold('m016')
    sage: I = extended_ptolemy_equations(M)
    sage: I.dimension()
    1
    """

    if gen_obs_class is None:
        gen_obs_class = manifold.ptolemy_generalized_obstruction_classes(2)[0]

    m_star, l_star = peripheral.peripheral_cohomology_basis(manifold)

    n = manifold.num_tetrahedra()

    if notation == 'short':
        var_names = ['a', 'b', 'c', 'd', 'e', 'f']
        first_var_name = 'a0'
    else:
        var_names = [
            'c_1100_', 'c_1010_', 'c_1001_', 'c_0110_', 'c_0101_', 'c_0011_'
        ]
        first_var_name = 'c_1100_0'

    tet_vars = [x + repr(d) for d in range(n) for x in var_names]

    def var(tet, edge):
        return tet_vars[6 * tet + directed_edges.index(edge)]

    all_arrows = arrows_around_edges(manifold)
    independent_vars = [var(a[0][0], a[0][2]) for a in all_arrows]
    assert first_var_name in independent_vars
    if nonzero_cond:
        nonzero_cond_vars = [v.swapcase() for v in independent_vars]
    else:
        nonzero_cond_vars = []
    R = PolynomialRing(QQ, ['M', 'L', 'm', 'l'] + independent_vars +
                       nonzero_cond_vars)
    M, L, m, l = R('M'), R('L'), R('m'), R('l')

    def var(tet, edge):
        return tet_vars[6 * tet + directed_edges.index(edge)]

    in_terms_of_indep_vars = {v: R(v) for v in independent_vars}
    in_terms_of_indep_vars['M'] = M
    in_terms_of_indep_vars['L'] = L
    edge_gluings = EdgeGluings(gen_obs_class)

    in_terms_of_indep_vars_data = {v: (1, 0, 0, v) for v in independent_vars}

    for around_one_edge in arrows_around_edges(manifold):
        tet0, face0, edge0 = around_one_edge[0]
        indep_var = R(var(tet0, edge0))
        sign, m_e, l_e = 1, 0, 0
        for tet1, face1, edge1 in around_one_edge[:-1]:
            (tet2, face2, edge2), a_sign = edge_gluings[tet1, face1, edge1]
            sign = a_sign * sign
            m_e -= sum(m_star[tet1, t3m.TwoSubsimplices[face1],
                              t3m.ZeroSubsimplices[v]] for v in edge1)
            l_e -= sum(l_star[tet1, t3m.TwoSubsimplices[face1],
                              t3m.ZeroSubsimplices[v]] for v in edge1)
            mvar = M if m_e > 0 else m
            lvar = L if l_e > 0 else l
            dep_var = var(tet2, edge2)
            in_terms_of_indep_vars_data[dep_var] = (sign, m_e, l_e,
                                                    var(tet0, edge0))
            in_terms_of_indep_vars[dep_var] = sign * (mvar**abs(m_e)) * (
                lvar**abs(l_e)) * indep_var

    tet_vars = [in_terms_of_indep_vars[v] for v in tet_vars]
    rels = [R(first_var_name) - 1, M * m - 1, L * l - 1]
    for tet in range(n):
        a, b, c, d, e, f = tet_vars[6 * tet:6 * (tet + 1)]
        rels.append(simplify_equation(c * d + a * f - b * e))

    # These last equations ensure the ptolemy coordinates are nonzero.
    # For larger numbers of tetrahedra, this appears to make computing
    # Groebner basis much faster even though there are extra variables
    # compared the approach where one uses a single variable.

    if nonzero_cond:
        for v in independent_vars:
            rels.append(R(v) * R(v.swapcase()) - 1)

    if return_full_var_dict == 'data':
        return R.ideal(rels), in_terms_of_indep_vars_data
    if return_full_var_dict:
        return R.ideal(rels), in_terms_of_indep_vars
    else:
        return R.ideal(rels)
Beispiel #15
0
 def enum_points(I):
     possibleValues = get_elements()
     R = I.ring()
     F = R.base()
     ch = F.characteristic()
     n = R.ngens()
     if n == 0:
         if I.is_zero():
             yield []
         return
     if I.is_one():
         return
     if all(map(lambda _: _.degree() == 1,
                I.gens())) and (ch > 0 or I.dimension() == 0):
         # solve using linear algebra
         f = R.hom(n * [0], F)
         A = matrix([f(g.coefficient(xi)) for xi in R.gens()]
                    for g in I.gens())
         b = vector(-g.constant_coefficient() for g in I.gens())
         v0 = A.solve_right(b)
         r = A.rank()
         if r == n:
             yield list(v0)
         else:
             K = A.right_kernel().matrix()
             for v in F**(n - r):
                 yield list(v * K + v0)
         return
     if ch > 0 and I.is_homogeneous():
         yield [F(0)] * n
         for pt in enum_proj_points(I):
             for sca in get_elements():
                 if sca != 0:
                     yield [x * sca for x in pt]
         return
     elim = I.elimination_ideal(I.ring().gens()[1:])
     g = elim.gens()[0]
     if g != 0:
         S = F['u']
         pr1 = R.hom([S.gen()] + [0] * (n - 1), S)
         possibleValues = (v[0] for v in pr1(g).roots() if bound == None or
                           global_height([v[0], F(1)]) <= bound + tolerance)
         if split:
             nonSplit = (f[0] for f in factor(pr1(g)) if f[0].degree() > 1)
             for f in nonSplit:
                 if ch == 0:
                     F_ = f.splitting_field('a')
                     # `polredbest` from PARI/GP, improves performance significantly
                     f = gen_to_sage(
                         pari(F_.gen().minpoly('x')).polredbest(),
                         {'x': S.gen()})
                 F_ = f.splitting_field('a')
                 R_ = PolynomialRing(F_, 'x', n)
                 I = R_.ideal(
                     [f.change_ring(base_change(F, F_)) for f in I.gens()])
                 for pt in enum_points(I):
                     yield pt
                 return
     R_ = PolynomialRing(F, 'x', n - 1)
     if n == 1:
         for v in possibleValues:
             yield [v]
     else:
         for v in possibleValues:
             pr2 = R.hom([v] + list(R_.gens()), R_)
             for rest in enum_points(pr2(I)):
                 yield [v] + rest
Beispiel #16
0
def rational_points(X,
                    F=None,
                    split=False,
                    bound=None,
                    tolerance=0.01,
                    prec=53):
    r"""
    Return an iterator of rational points on a scheme ``X``

    INPUT:

    - ``X`` - a scheme, affine or projective

    OPTIONS:
    
    - ``F`` - coefficient field
    
    - ``split`` - whether to compute the splitting field when the scheme is 0-dimensional

    - ``bound`` - a bound for multiplicative height

    - ``tolerance`` - tolerance used for computing height
    
    - ``prec`` - precision used for computing height

    OUTPUT:

    - an iterator of rational points on ``X``
    
    ALGORITHM:

    Use brute force plus some elimination. The complexity is approximately
    `O(n^d)`, where `n` is the size of the field (or the number of elements
    with bounded height when the field is infinite) and `d` is the dimension of
    the scheme ``X``. Significantly faster than the current available
    algorithms in Sage, especially for low dimension schemes in large
    ambient spaces.

    EXAMPLES::
    
        sage: from rational_points import rational_points

    A curve of genus 9 over `\mathbf F_{97}` with many rational points (from
    the website `<https://manypoints.org>`_)::
    
        sage: A.<x,y,z> = AffineSpace(GF(97),3)
        sage: C = A.subscheme([x^4+y^4+1+3*x^2*y^2+13*y^2+68*x^2,z^2+84*y^2+x^2+67])
        sage: len(list(rational_points(C)))
        228
    
    The following example is from the `documentation
    <http://magma.maths.usyd.edu.au/magma/handbook/text/1354>`_ of Magma: a
    space rational curve with one cusp. Unfeasible with the old methods::
    
        sage: F = GF(7823)
        sage: P.<x,y,z,w> = ProjectiveSpace(F,3)
        sage: C = P.subscheme([4*x*z+2*x*w+y^2+4*y*w+7821*z^2+7820*w^2,\
        ....: 4*x^2+4*x*y+7821*x*w+7822*y^2+7821*y*w+7821*z^2+7819*z*w+7820*w^2])
        sage: len(list(rational_points(C))) # long time
        7824
        
    31 nodes on a `Togliatti surface
    <https://en.wikipedia.org/wiki/Togliatti_surface>`_: only 7 of them are
    defined over the field of definition. Use ``split=True`` to automatically
    find the splitting field::
    
        sage: q = QQ['q'].0
        sage: F.<q> = NumberField(q^4-10*q^2+20)
        sage: P.<x,y,z,w> = ProjectiveSpace(F,3)
        sage: f = 5*q*(2*z-q*w)*(4*(x^2+y^2-z^2)+(1+3*(5-q^2))*w^2)^2-64*(x-w)*\
        ....: (x^4-4*x^3*w-10*x^2*y^2-4*x^2*w^2+16*x*w^3-20*x*y^2*w+5*y^4+16*w^4-20*y^2*w^2)
        sage: X = P.subscheme([f]+f.jacobian_ideal())
        sage: len(list(rational_points(X)))
        7
        sage: len(list(rational_points(X,split=True)))
        31

    Enumeration of points on a projective plane over a number field::
        
        sage: a = QQ['a'].0
        sage: F.<a> = NumberField(a^3-5)
        sage: P.<x,y,z> = ProjectiveSpace(F, 2)
        sage: len(list(rational_points(P, bound=RR(5^(1/3)))))
        49
    """
    def base_change(k, F):
        ch = F.characteristic()
        if ch == 0:
            return k.embeddings(F)[0]
        else:
            return F

    def enum_proj_points(I):
        R = I.ring()
        k = R.base()
        n = R.ngens() - 1
        for i in range(n + 1):
            R_ = PolynomialRing(k, 'x', n - i)
            v = [k(0)] * i + [k(1)]
            pr = R.hom(v + list(R_.gens()), R_)
            for rest in enum_points(pr(I)):
                pt = v + rest
                if bound == None or global_height(
                        pt, prec=prec) <= bound + tolerance:
                    yield pt

    def enum_points(I):
        possibleValues = get_elements()
        R = I.ring()
        F = R.base()
        ch = F.characteristic()
        n = R.ngens()
        if n == 0:
            if I.is_zero():
                yield []
            return
        if I.is_one():
            return
        if all(map(lambda _: _.degree() == 1,
                   I.gens())) and (ch > 0 or I.dimension() == 0):
            # solve using linear algebra
            f = R.hom(n * [0], F)
            A = matrix([f(g.coefficient(xi)) for xi in R.gens()]
                       for g in I.gens())
            b = vector(-g.constant_coefficient() for g in I.gens())
            v0 = A.solve_right(b)
            r = A.rank()
            if r == n:
                yield list(v0)
            else:
                K = A.right_kernel().matrix()
                for v in F**(n - r):
                    yield list(v * K + v0)
            return
        if ch > 0 and I.is_homogeneous():
            yield [F(0)] * n
            for pt in enum_proj_points(I):
                for sca in get_elements():
                    if sca != 0:
                        yield [x * sca for x in pt]
            return
        elim = I.elimination_ideal(I.ring().gens()[1:])
        g = elim.gens()[0]
        if g != 0:
            S = F['u']
            pr1 = R.hom([S.gen()] + [0] * (n - 1), S)
            possibleValues = (v[0] for v in pr1(g).roots() if bound == None or
                              global_height([v[0], F(1)]) <= bound + tolerance)
            if split:
                nonSplit = (f[0] for f in factor(pr1(g)) if f[0].degree() > 1)
                for f in nonSplit:
                    if ch == 0:
                        F_ = f.splitting_field('a')
                        # `polredbest` from PARI/GP, improves performance significantly
                        f = gen_to_sage(
                            pari(F_.gen().minpoly('x')).polredbest(),
                            {'x': S.gen()})
                    F_ = f.splitting_field('a')
                    R_ = PolynomialRing(F_, 'x', n)
                    I = R_.ideal(
                        [f.change_ring(base_change(F, F_)) for f in I.gens()])
                    for pt in enum_points(I):
                        yield pt
                    return
        R_ = PolynomialRing(F, 'x', n - 1)
        if n == 1:
            for v in possibleValues:
                yield [v]
        else:
            for v in possibleValues:
                pr2 = R.hom([v] + list(R_.gens()), R_)
                for rest in enum_points(pr2(I)):
                    yield [v] + rest

    #######################################################################
    # begin of main function
    try:
        I = X.defining_ideal()
        R = I.ring()
    except:  # when X has no defining ideal, i.e. when it's the whole space
        R = X.coordinate_ring()
        I = R.ideal([])
    k = R.base()
    n = R.ngens()
    ambient = X.ambient_space()
    if F:  # specified coefficient field
        R_ = PolynomialRing(F, 'x', n)
        I = R_.ideal([f.change_ring(base_change(k, F)) for f in I.gens()])
        k = F
        ambient = ambient.change_ring(k)
        split = False
    ch = k.characteristic()
    if (X.is_projective() and I.dimension()
            == 1) or (not X.is_projective()
                      and I.dimension() == 0):  # 0-dimensional dimension
        # in 0-dim use elimination only
        bound = None
        get_elements = lambda: []
    else:  # positive dimension
        split = False  # splitting field does not work in positive dimension
        if ch == 0:
            if bound == None:
                raise ValueError("need to specify a valid bound")
            if is_RationalField(k):
                get_elements = lambda: k.range_by_height(floor(bound) + 1)
            else:
                get_elements = lambda: k.elements_of_bounded_height(
                    bound=bound**(k.degree()),
                    tolerance=tolerance,
                    precision=prec)
        else:  # finite field
            bound = None
            get_elements = lambda: k
    if X.is_projective():  # projective case
        for pt in enum_proj_points(I):
            if split:
                # TODO construct homogeneous coordinates from a bunch of
                # elements from different fields
                yield pt
            else:
                yield ambient(pt)
    else:  # affine case
        for pt in enum_points(I):
            if split:
                yield pt
            else:
                yield ambient(pt)
def AffineIntegralScheme(R, F, generators):
    r""" Return an integral affine scheme with given base ring and function field.

    INPUT:

    - ``R`` -- an integral domain
    - ``F`` -- a field, finitely generated over the fraction field of `R`
    - ``generators`` -- a list of elements of `F`

    OUTPUT: an affine `R`-scheme `X` with function field `F`, whose coordinate
    ring is generated over `R` by the given generators.

    `X` will be equipped with attributes ``_function_field`` (which returns `F`)
    and ``_generators`` (which returns the list of generators defining `X`).

    .. NOTE::

    At the moment, we assume that `F` is a function field of transcendence degree
    one over the fraction field of `R`, and that the defining equation for
    `F` has coefficients in `R`. In particula, the scheme `X` will be an affine
    arithmetic surface.

    """
    # first we create the ideal defining X
    n = len(generators)
    P = AffineSpace(R, n, 'x')
    A = P.coordinate_ring()
    if hasattr(F, "polynomial"):
        # F is a function field over Frac(R) in two variables x, y
        # with equation G0(x,y)=0. Write the generators as
        # f_i =g_i(x,y)/h_i(x,y) and define the ideal generated by
        # G0 and h_i(x,y)*z_i - g_i(x,y). We obtain the ideal defining
        # X by eliminating x, y from these equations.
        B = PolynomialRing(R, n + 2, 'z')
        x = B.gens()[n]
        y = B.gens()[n + 1]
        G0 = function_field_equation(R, F)(x, y)
        G = [G0]
        for i in range(n):
            f = generators[i]
            # we have to write f = g/h, where g, h are elements of B
            g, h = function_field_element(f, R)
            g = g(x, y)
            h = h(x, y)
            G.append(h * B.gens()[i] - g)
        J = B.ideal(G).elimination_ideal([x, y])
        # in general, this is not a prime ideal; we have to first
        # replace it by its radical. Probably we need to write a
        # Singular script for this
        G = []
        for g in J.gens():
            G.append(g(A.gens() + (A.zero(), A.zero())))
    else:
        # F is a rational function field over Frac(R)
        B = PolynomialRing(R, n + 1, 'z')
        x = B.gens()[n]
        G = []
        for i in range(n):
            f = generators[i]
            g = f.numerator()
            a = common_denominator_of_polynomial(g, R)
            h = f.denominator()
            b = common_denominator_of_polynomial(h, R)
            c = lcm(a, b)
            g = c * g
            h = c * h
            G.append(h(x) * B.gens()[i] - g(x))
        J = B.ideal(G).elimination_ideal([x])
        G = []
        for g in J.gens():
            G.append(g(A.gens() + tuple([A.zero()])))
    J_list = horizontal_minimal_ass_primes(A.ideal(G))
    assert len(
        J_list
    ) == 1, "some thing strange happened: there is more than one horizontal pime ideal"
    X = P.subscheme(J_list[0])
    X._function_field = F
    X._generators = generators
    return X