Beispiel #1
0
    def __init__(self, free_group, relations):
        """
        The Python constructor

        TESTS::

            sage: G = FreeGroup('a, b')
            sage: H = G / (G([1]), G([2])^3)
            sage: H
            Finitely presented group < a, b | a, b^3 >

            sage: F = FreeGroup('a, b')
            sage: J = F / (F([1]), F([2, 2, 2]))
            sage: J is H
            True

            sage: TestSuite(H).run()
            sage: TestSuite(J).run()
        """
        from sage.groups.free_group import is_FreeGroup
        assert is_FreeGroup(free_group)
        assert isinstance(relations, tuple)
        self._free_group = free_group
        self._relations = relations
        self._assign_names(free_group.variable_names())
        parent_gap = free_group.gap() / libgap([ rel.gap() for rel in relations])
        ParentLibGAP.__init__(self, parent_gap)
        Group.__init__(self)
Beispiel #2
0
    def __init__(self, free_group, relations):
        """
        The Python constructor.

        TESTS::

            sage: G = FreeGroup('a, b')
            sage: H = G / (G([1]), G([2])^3)
            sage: H
            Finitely presented group < a, b | a, b^3 >

            sage: F = FreeGroup('a, b')
            sage: J = F / (F([1]), F([2, 2, 2]))
            sage: J is H
            True

            sage: TestSuite(H).run()
            sage: TestSuite(J).run()
        """
        from sage.groups.free_group import is_FreeGroup
        assert is_FreeGroup(free_group)
        assert isinstance(relations, tuple)
        self._free_group = free_group
        self._relations = relations
        self._assign_names(free_group.variable_names())
        parent_gap = free_group.gap() / libgap(
            [rel.gap() for rel in relations])
        ParentLibGAP.__init__(self, parent_gap)
        Group.__init__(self)
Beispiel #3
0
def get_minword_wh_nbrs(F, word):
    """
    Assuming word is (AutF)-minimial, find
    all words that can be obtained from
    word by exactly one Whitehead move.

    The assumption on minimality is not
    necessary, but we just find whitehead
    move that don't change the length.
    """
    assert is_FreeGroup(F), "F must be a free group"
    r = F.rank()
    assert word in F, "word must be in F"

    letters = list(range(1, r + 1)) + list(range(-r, 0))

    word_len = len(word.Tietze())
    nbrs = set()
    for v in letters:
        for choices in product([(0, 0), (0, 1), (1, 0), (1, 1)], repeat=r - 1):
            phi = get_whitehead_move(F, v, choices)
            nbr = phi(word)
            if len(nbr.Tietze()) == word_len:
                nbrs.add(nbr)

    return list(nbrs)
Beispiel #4
0
def minimize_naive(F, word):
    """
    Find a minimal automorphic representative for a word in F.
    Uses Whitehead minimization.

    This is a naive implementation, going over all possible moves
    instead of finding the most efficient one.
    """
    assert is_FreeGroup(F), "F must be a free group"
    r = F.rank()
    assert word in F, "word must be in F"

    letters = list(range(1, r + 1)) + list(range(-r, 0))

    curr_word = word
    curr_len = len(word.Tietze())
    for v in letters:
        for choices in product([(0, 0), (0, 1), (1, 0), (1, 1)], repeat=r - 1):
            phi = get_whitehead_move(F, v, choices)
            new_word = phi(word)
            new_len = len(new_word.Tietze())
            if new_len < curr_len:
                curr_word = new_word
                curr_len = new_len
    if curr_len < len(word.Tietze()):
        return minimize_naive(F, curr_word)
    else:
        return word
Beispiel #5
0
    def _get_action_(self, S, op, self_on_left):
        """
        Let the coercion system discover actions of the braid group on free groups.

            sage: B.<b0,b1,b2> = BraidGroup()
            sage: F.<f0,f1,f2,f3> = FreeGroup()
            sage: f1 * b1
            f1*f2*f1^-1

            sage: from sage.structure.all import get_coercion_model
            sage: cm = get_coercion_model()
            sage: cm.explain(f1, b1, operator.mul)
            Action discovered.
                Right action by Braid group on 4 strands on Free Group on generators {f0, f1, f2, f3}
            Result lives in Free Group on generators {f0, f1, f2, f3}
            Free Group on generators {f0, f1, f2, f3}
            sage: cm.explain(b1, f1, operator.mul)
            Will try _r_action and _l_action
            Unknown result parent.
        """
        import operator

        if is_FreeGroup(S) and op == operator.mul and not self_on_left:
            return self.mapping_class_action(S)
        return None
Beispiel #6
0
    def __init__(self, F, arg):
        """
        If arg is a list:
        Generate the core graph corresponding to the group generated by group_gens.
        If arg is a graph:
        Check for validity and do all folds.
        """
        assert is_FreeGroup(F), "F must be a free group"
        self.F = F
        self.F_rank = F.rank()
        self.letter_types = range(1, self.F_rank + 1)
        self.letters = list(range(-self.F_rank, 0)) + list(range(1, self.F_rank + 1))
        # -r, ..., -1, 1, ..., r

        if isinstance(arg, list):
            group_gens = arg
            assert all([gen in F for gen in group_gens]), "The generators must be elements of F."
            self.group_gens = group_gens

            G = MultiDiGraph()
            G.add_node((0,))  # the marked vertex (id)
            for i, gen in enumerate(self.group_gens):
                word = gen.Tietze()
                word_len = len(word)
                # new_nodes = [(i, j) for j in range(1, word_len)]
                # G.add_nodes_from(new_nodes)
                get_node = lambda j: (0,) if (j % word_len == 0) else (i, j)
                for j in range(word_len):
                    G.add_edge(get_node(j), get_node(j + 1), label=word[j])
                    G.add_edge(get_node(j + 1), get_node(j), label=-word[j])

        elif isinstance(arg, MultiDiGraph):
            # We are going to copy the graph, add reverse edges when needed,
            # and sort the edges.
            # The reason we sort the edges is to get a "canonical" version
            # of the object, so subgroup_gens would be the same in different
            # objects with the same graph.
            G = MultiDiGraph()
            G.add_nodes_from(arg.nodes())
            edges = arg.edges(data='label')
            G_edges = [e for e in edges]
            assert len(edges) == len(arg.edges()), "Every edge has to be labelled."
            for src, dst, letter in edges:
                assert letter in self.letters, \
                    f"The edge betwen {src} and {dst} has an invalid label"
                if (dst, src, -letter) not in G.edges(data='label'):
                    G_edges.append((dst, src, -letter))
            G.add_weighted_edges_from(sorted(G_edges), weight='label')

        else:
            raise ValueError("arg must be a list of words or a MultiDiGraph.")

        self.G = do_all_folds(G)

        # The subgraph of positive edges
        G_pos = MultiDiGraph()
        G_pos.add_edges_from([e for e in self.G.edges(data=True) if e[2]['label'] > 0])
        self.G_pos = G_pos

        self.subgroup_gens = tuple(sorted(self.get_subgroup()))
Beispiel #7
0
def get_whitehead_move_of_cut(F, v, Y):
    """
    Generate the whitehead move corresponding
    to v with and the cut Y.
    Recall that the whitehead move is defined by
    x -> v^(-1 if -x in Y else 0) * x * v^(1 if x in Y else 0)
    v -> v

    F - a free group.
    v - a letter of F.
    Y - a set containing v but not -v.
    """
    assert is_FreeGroup(F), "F must be a free group"
    r = F.rank()
    assert v in range(1, r + 1) or v in range(
        -r, 0), "v must be a valid letter of F \
                                            (a number in [-r,...,-1,1,...,r])"

    assert v in Y and -v not in Y, "The cut must seperate v and -v"

    gen_imgs = []
    for x in range(1, r + 1):
        img = []
        if -x in Y:
            img.append(-v)
        img.append(x)
        if x in Y:
            img.append(v)
        gen_imgs.append(img)
    gen_imgs[abs(v) - 1] = [abs(v)]

    return F.hom([F(img) for img in gen_imgs])
Beispiel #8
0
def get_whitehead_move(F, v, choices):
    """
    Generate the whitehead move corresponding
    to v with the given choices of (s,t).
    Recall that the whitehead move is defined by
    v->v, x_i-> v^s * x * v^-t

    F - a free group.
    v - a letter of F.
    choices - a list of tuples of pair of binary numbers, of length
    rank(F) - 1.
    """
    assert is_FreeGroup(F), "F must be a free group"
    r = F.rank()
    assert v in range(1, r + 1) or v in range(
        -r, 0), "v must be a valid letter of F \
                                            (a number in [-r,...,-1,1,...,r])"

    assert len(choices) == r - 1, "choices must be of length rank(F) - 1"

    gen_imgs = []
    for x in range(1, abs(v)):
        s, t = choices[x - 1]
        gen_imgs.append([c for c in [s * v, x, -t * v] if c])
    gen_imgs.append([abs(v)])
    for x in range(abs(v) + 1, r + 1):
        s, t = choices[x - 2]
        gen_imgs.append([c for c in [s * v, x, -t * v] if c])
    return F.hom([F(img) for img in gen_imgs])
Beispiel #9
0
def minimize(F, word):
    """
    Find the minimal form of a word.
    A minimal form is a representative of (Aut(F)w)
    of shortest length.

    This is a smart implementation - for each letter
    v, we find the best v-cuts.
    """
    assert is_FreeGroup(F), "F must be a free group"
    assert word in F, "word must be in F"

    curr_word = word
    new_word = minimize_one_step(F, curr_word)
    while len(new_word.Tietze()) < len(curr_word.Tietze()):
        curr_word, new_word = new_word, minimize_one_step(F, new_word)

    return curr_word
Beispiel #10
0
def are_automorphic(F, w1, w2):
    assert is_FreeGroup(F), "F must be a free group"
    assert w1 in F and w2 in F, "Both words must be in F"
    w1 = canonical_letter_permute_form(F, minimize(F, w1))
    w2 = canonical_letter_permute_form(F, minimize(F, w2))
    if w1 == w2:
        return True
    if len(w1.Tietze()) != len(w2.Tietze()):
        return False
    G = Graph()
    G.add_nodes_from([w1, w2])
    tested_words = set()
    while len(tested_words) < len(G.nodes()):
        edges_to_add = []
        for word in G.nodes():
            if word in tested_words:
                continue
            nbrs = [canonical_letter_permute_form(F, nbr) for nbr in get_minword_wh_nbrs(F, word)]
            edges_to_add += [(word, nbr) for nbr in nbrs]
            tested_words.add(word)
        G.add_edges_from(edges_to_add)
    return has_path(G, w1, w2)
Beispiel #11
0
def get_whitehead_graph(F, word):
    """
    Construct the Whitehead graph of the word.
    This is the graph whose vertices are the
    letters of F, and for any (xy) appearing in
    the word there's an edge between x and y^-1.
    """
    assert is_FreeGroup(F), "F must be a free group"
    assert word in F, "word must be in F"
    r = F.rank()
    G = Graph()
    G.add_nodes_from(list(range(1, r + 1)) + list(range(-r, 0)))
    word_repr = word.Tietze()
    edges = [(word_repr[i], -word_repr[i + 1])
             for i in range(-1,
                            len(word_repr) - 1)]
    for u, v in edges:
        if not G.has_edge(u, v):
            G.add_edge(u, v, capacity=1)
        else:
            G[u][v]['capacity'] += 1
    return G
Beispiel #12
0
def minimize_one_step(F, word):
    """
    Find the shortest word that can be
    obtained by applying a single Whitehead
    move to a word.
    """
    assert is_FreeGroup(F), "F must be a free group"
    r = F.rank()
    assert word in F, "word must be in F"

    word = shortest_cyclic_shift(F, word)

    letters = list(range(1, r + 1)) + list(range(-r, 0))

    G = get_whitehead_graph(F, word)

    best_change = 0
    best_move = tuple()
    for v in letters:
        cap, cut = minimum_cut(G, v, -v)
        change = cap - sum([G[v][u]['capacity'] for u in G[v]])
        if change < best_change:
            best_change = change
            best_move = (v, cut[0])

    assert best_change <= 0, "Something is wrong"

    if best_change == 0:
        return word

    v, cut = best_move
    phi = get_whitehead_move_of_cut(F, v, cut)
    new_word = shortest_cyclic_shift(F, phi(word))
    assert len(new_word.Tietze()) == len(
        word.Tietze()) + best_change, "Something is wrong"

    return new_word
Beispiel #13
0
    def _get_action_(self, S, op, self_on_left):
        """
        Let the coercion system discover actions of the braid group on free groups.

            sage: B.<b0,b1,b2> = BraidGroup()
            sage: F.<f0,f1,f2,f3> = FreeGroup()
            sage: f1 * b1
            f1*f2*f1^-1

            sage: from sage.structure.all import get_coercion_model
            sage: cm = get_coercion_model()
            sage: cm.explain(f1, b1, operator.mul)
            Action discovered.
                Right action by Braid group on 4 strands on Free Group on generators {f0, f1, f2, f3}
            Result lives in Free Group on generators {f0, f1, f2, f3}
            Free Group on generators {f0, f1, f2, f3}
            sage: cm.explain(b1, f1, operator.mul)
            Will try _r_action and _l_action
            Unknown result parent.
        """
        import operator
        if is_FreeGroup(S) and op==operator.mul and not self_on_left:
            return self.mapping_class_action(S)
        return None
Beispiel #14
0
def canonical_letter_permute_form(F, word):
    """
    Apply a permutation of the letters of F so
    that the different letters appearing in the
    word appear in order (1,2,3,...).
    """
    assert is_FreeGroup(F), "F must be a free group"
    r = F.rank()
    assert word in F, "word must be in F"

    word_rep = word.Tietze()
    letter_order = []
    sgns = []
    for letter in word_rep:
        if abs(letter) not in letter_order:
            letter_order.append(abs(letter))
            sgns.append((1 if letter > 0 else -1))
    gen_imgs = [i for i in range(1, r + 1)]
    for i, letter in enumerate(letter_order):
        gen_imgs[letter - 1] = (sgns[i] * (i + 1),)
    for i, v in enumerate([v for v in range(1, r + 1) if v not in letter_order]):
        gen_imgs[v - 1] = (len(letter_order) + i + 1,)
    phi = F.hom([F(img) for img in gen_imgs])
    return phi(word)
Beispiel #15
0
def get_all_aut_classes(F, length, dirname="aut_classes_cache/", verbose=True):
    """
    Get all automorphism classes of words in F_r with bounded length.
    Caches the result to dirname.
    """
    assert is_FreeGroup(F), "F must be a free group"
    r = F.rank()

    cache_dir = os.fsencode(dirname)
    cache_file = os.fsencode(f"r{r}-len{length}.pkl")
    if not os.path.exists(cache_dir):
        os.mkdir(cache_dir)
    if os.path.exists(cache_dir + cache_file):
        aut_classes = pickle.load(open(cache_dir + cache_file, 'rb'))
        return [set([F(w.Tietze()) for w in cls]) for cls in aut_classes]
    # Maybe we computed something bigger before
    for file in os.listdir(cache_dir):
        filename = os.fsdecode(file)
        r_str, len_str = filename.split(".")[0].split("-")
        r_cached = int(r_str[1:])
        len_cached = int(len_str[3:])
        if r_cached >= r and len_cached >= length:
            cached_aut_classes = pickle.load(open(cache_dir + file, 'rb'))
            aut_classes = []
            for cls in cached_aut_classes:
                word = cls.pop()
                word_rep = word.Tietze()
                if len(word_rep) == 0:
                    aut_classes.append(set([F(1)]))
                elif len(word_rep) < length and max(
                        set([abs(x) for x in word_rep])) < r:
                    cls.add(word)
                    aut_classes.append(set([F(w.Tietze()) for w in cls]))
            pickle.dump(aut_classes,
                        open(f"aut_classes_cache/r{r}-len{length}.pkl", 'wb'))
            return aut_classes

    letters = list(range(1, r + 1)) + list(range(-r, 0))

    minimal_words = set()
    all_words = set()  # To avoid stuff like (1,-1,2,3) and (2,-2,2,3)

    # We only consider words that are in "canonical order"
    # We also assume we only check tuple starting with a
    # (They can still be a*a^-1*b*...)
    tuples = product(letters, repeat=length - 1)
    if verbose:
        print(f"Minimizing all words in {F} of length <={length}")
        print(f"{2*(len(letters)) ** (length - 1)} words to minimize.")
        tuples = tqdm(tuples)
    for tup in tuples:
        tup = [1] + list(tup)
        word = F(tup)
        if word not in all_words and word == canonical_letter_permute_form(
                F, word):
            all_words.add(word)
            minimal_words.add(
                canonical_letter_permute_form(F, minimize(F, word)))
    if length > 0:
        # Due to cancellations (e.g. (1,-1, ...)), we only
        # need to check words of length N, N - 1.
        tuples = product(letters, repeat=length - 1)
        if verbose:
            tuples = tqdm(tuples)
        for tup in tuples:
            word = F(tup)
            if word == canonical_letter_permute_form(
                    F, word):  # We only consider canonized orders
                minimal_words.add(
                    canonical_letter_permute_form(F, minimize(F, word)))
    if verbose:
        print(
            f"Finished minimizing letters, found {len(minimal_words)} minimal words."
        )
        print("Creating the Whitehead moves graph on minimal words.")

    G = Graph()
    G.add_nodes_from(minimal_words)
    for word in minimal_words:
        nbrs = get_minword_wh_nbrs(F, word)
        assert (len(nbrs[0].Tietze()) == len(
            word.Tietze())), "Something's wrong"
        for nbr in nbrs:
            nbr = canonical_letter_permute_form(F, nbr)
            assert nbr in minimal_words, f"Found a word ({nbr}) not in minimal_words"
            G.add_edge(word, nbr)
    aut_classes = list(connected_components(G))
    pickle.dump(aut_classes,
                open(f"aut_classes_cache/r{r}-len{length}.pkl", 'wb'))
    return aut_classes