'Return the normal closure of a subgroup/set of permutations.
If ``S`` is a subset of a group ``G``, the normal closure of ``A`` in ``G``
is defined as the intersection of all normal subgroups of ``G`` that
contain ``A`` ([1], p.14). Alternatively, it is the group generated by
the conjugates ``x^{-1}yx`` for ``x`` a generator of ``G`` and ``y`` a
generator of the subgroup ``\left\langle S\right\rangle`` generated by
``S`` (for some chosen generating set for ``\left\langle S\right\rangle``)
([1], p.73).
Parameters
other
a subgroup/list of permutations/single permutation
k
an implementation-specific parameter that determines the number
of conjugates that are adjoined to ``other`` at once
Examples
>>> from sympy.combinatorics.named_groups import (SymmetricGroup,
... CyclicGroup, AlternatingGroup)
>>> S = SymmetricGroup(5)
>>> C = CyclicGroup(5)
>>> G = S.normal_closure(C)
>>> G.order()
60
>>> G.is_subgroup(AlternatingGroup(5))
True
See Also
commutator, derived_subgroup, random_pr
Notes
The algorithm is described in [1], pp. 73-74; it makes use of the
generation of random elements for permutation groups by the product
replacement algorithm.'

def normal_closure(self, other, k=10):

    if hasattr(other, 'generators'):
        degree = self.degree
        identity = _af_new(list(range(degree)))
        if all(((g == identity) for g in other.generators)):
            return other
        Z = PermutationGroup(other.generators[:])
        (base, strong_gens) = Z.schreier_sims_incremental()
        strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
        (basic_orbits, basic_transversals) = _orbits_transversals_from_bsgs(base, strong_gens_distr)
        self._random_pr_init(r=10, n=20)
        _loop = True
        while _loop:
            Z._random_pr_init(r=10, n=10)
            for i in range(k):
                g = self.random_pr()
                h = Z.random_pr()
                conj = (h ^ g)
                res = _strip(conj, base, basic_orbits, basic_transversals)
                if ((res[0] != identity) or (res[1] != (len(base) + 1))):
                    gens = Z.generators
                    gens.append(conj)
                    Z = PermutationGroup(gens)
                    strong_gens.append(conj)
                    (temp_base, temp_strong_gens) = Z.schreier_sims_incremental(base, strong_gens)
                    (base, strong_gens) = (temp_base, temp_strong_gens)
                    strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
                    (basic_orbits, basic_transversals) = _orbits_transversals_from_bsgs(base, strong_gens_distr)
            _loop = False
            for g in self.generators:
                for h in Z.generators:
                    conj = (h ^ g)
                    res = _strip(conj, base, basic_orbits, basic_transversals)
                    if ((res[0] != identity) or (res[1] != (len(base) + 1))):
                        _loop = True
                        break
                if _loop:
                    break
        return Z
    elif hasattr(other, '__getitem__'):
        return self.normal_closure(PermutationGroup(other))
    elif hasattr(other, 'array_form'):
        return self.normal_closure(PermutationGroup([other]))
