Repository: sagemath/sage

Issue:
random_element for colored and signed permutations
<div id="comment:0"></div>

Before this ticket,

```
sage: SignedPermutations(10).random_element()
```
takes forever.

Component: **combinatorics**

Author: **Martin Rubey**

Branch/Commit: **[u/mantepse/random_element_for_colored_and_signed_permutations](https://github.com/sagemath/sagetrac-mirror/tree/u/mantepse/random_element_for_colored_and_signed_permutations) @ [`86d9ac7`](https://github.com/sagemath/sagetrac-mirror/commit/86d9ac7eb908e3c2a538cb431980b33ca194fa79)**

_Issue created by migration from https://trac.sagemath.org/ticket/34925_




Discussion:
Branch: **[u/mantepse/random_element_for_colored_and_signed_permutations](https://github.com/sagemath/sagetrac-mirror/tree/u/mantepse/random_element_for_colored_and_signed_permutations)**
Author: **Martin Rubey**
Commit: **[`86d9ac7`](https://github.com/sagemath/sagetrac-mirror/commit/86d9ac7eb908e3c2a538cb431980b33ca194fa79)**
Description changed:
``````diff
--- 
+++ 
@@ -1 +1,6 @@
+Before this ticket,
 
+```
+sage: SignedPermutations(10).random_element()
+```
+takes forever.
``````

<div id="comment:2"></div>

New commits:
<table><tr><td><a href="https://github.com/sagemath/sagetrac-mirror/commit/86d9ac7eb908e3c2a538cb431980b33ca194fa79"><code>86d9ac7</code></a></td><td><code>random_element for colored and signed permutations</code></td></tr></table>

<div id="comment:3" align="right">comment:3</div>

There is a design decision for `SignedPermutations` I do not understand: it inherits from `ColoredPermutations`, but the colors are `{+1, -1}` instead of `IntegerModRing(2)`.

In fact, also `IntegerModRing` looks strange to me, because we really only consider the colors as a group, not a ring.

It seems to me that the only reason for this is the result of `SignedPermutation.colors()`.  However, this choice requires us to implement very many methods twice.

I see a few possibilities:

1. keep the decision and ignore its downsides
2. introduce a group containing two elements `{1, -1}`.
3. only override `SignedPermutation.colors()`.
4. change the colors to `{0, 1}`.
The issue for this ticket (mostly just for the official record) comes from the fact it is using the default `random_element()` that lists all the elements and pulls a random element from that.

At some point, we probably should also implement an `unrank()` and `__getitem__` method since that is also easy. I just unrolled the `unrank()` for the Cartesian product.
```
def unrank(n):
    k = self._m * self._n
    N = n // k
    perm = self._P.unrank(N)
    colors = [None] * self._n
    C = n % k
    for i in range(self._n-1,-1,-1):
        colors[i] = self._C.unrank(C % self._m)
        C //= self._m
    return self.element_class(self, tuple(colors), perm) 
```

Some comments about the design:

Since `IntegerModRing` is a ring, it is also naturally a group (under `+`). Plus it is fast.

The colors themselves don't need to be an actual elements of a group parent since they are used internally. If it helps, you can think of them as elements in a facade parent. It is more natural for the type B Weyl/Coxeter group combinatorics people to work with `{1, -1}` as the "colors", and there isn't too much extra that needs to be most-duplicated IIRC. If this ends up scaling to a bigger problem, then we can revisit the design and see if we can remove the duplication.

Resolving patch:
diff --git a/src/sage/combinat/colored_permutations.py b/src/sage/combinat/colored_permutations.py
--- a/src/sage/combinat/colored_permutations.py
+++ b/src/sage/combinat/colored_permutations.py
@@ -7,6 +7,7 @@
     generalized to `G \wr S_n`
 """
 import itertools
+from random import choice
 
 from sage.structure.element import MultiplicativeGroupElement
 from sage.structure.parent import Parent
@@ -313,13 +314,13 @@ def has_left_descent(self, i):
             [False, True, False, True]
         """
         if self.parent()._m == 1:
-            return self._perm[i-1] > self._perm[i]
+            return self._perm[i - 1] > self._perm[i]
 
         if i == self.parent()._n:
             return self._colors[-1] != 0
-        if self._colors[i-1] != 0:
-            return self._colors[i] == 0 or self._perm[i-1] < self._perm[i]
-        return self._colors[i] == 0 and self._perm[i-1] > self._perm[i]
+        if self._colors[i - 1] != 0:
+            return self._colors[i] == 0 or self._perm[i - 1] < self._perm[i]
+        return self._colors[i] == 0 and self._perm[i - 1] > self._perm[i]
 
     def reduced_word(self):
         r"""
@@ -563,7 +564,7 @@ def coxeter_matrix(self):
         """
         from sage.combinat.root_system.cartan_type import CartanType
         if self._m == 1:
-            return CartanType(['A', self._n-1]).coxeter_matrix()
+            return CartanType(['A', self._n - 1]).coxeter_matrix()
         return CartanType(['B', self._n]).coxeter_matrix()
 
     @cached_method
@@ -580,6 +581,23 @@ def one(self):
         return self.element_class(self, [self._C.zero()] * self._n,
                                   self._P.identity())
 
+    def random_element(self):
+        """
+        Return an element drawn uniformly at random.
+
+        EXAMPLES::
+
+            sage: C = ColoredPermutations(4, 3)
+            sage: s = C.random_element(); s # random
+            [[0, 2, 1], [2, 1, 3]]
+            sage: s in C
+            True
+        """
+        return self.element_class(self,
+                                  [self._C.random_element()
+                                   for _ in range(self._n)],
+                                  self._P.random_element())
+
     def simple_reflection(self, i):
         r"""
         Return the ``i``-th simple reflection of ``self``.
@@ -883,7 +901,7 @@ def codegrees(self):
             True
         """
         # Special case for the usual symmetric group
-        last = self._n-1 if self._m == 1 else self._n
+        last = self._n - 1 if self._m == 1 else self._n
         return tuple(self._m * i for i in reversed(range(last)))
 
     def number_of_reflection_hyperplanes(self):
@@ -979,8 +997,9 @@ def is_well_generated(self):
 
     Element = ColoredPermutation
 
+
 #####################################################################
-## Signed permutations
+#  Signed permutations
 
 
 class SignedPermutation(ColoredPermutation,
@@ -1122,11 +1141,11 @@ def __call__(self, i):
         if i in ZZ and 1 <= abs(i) <= len(self):
             i = ZZ(i)
             if i < 0:
-                return -self._colors[-i-1] * self._perm[-i-1]
-            return self._colors[i-1] * self._perm[i-1]
-        else:
-            raise TypeError("i (= %s) must equal +/- an integer between %s and %s"
-                            % (i,1,len(self)))
+                return -self._colors[-i - 1] * self._perm[-i - 1]
+            return self._colors[i - 1] * self._perm[i - 1]
+
+        raise TypeError("i (= %s) must equal +/- an integer between %s and %s"
+                        % (i, 1, len(self)))
 
     def to_matrix(self):
         """
@@ -1218,8 +1237,8 @@ def to_cycles(self, singletons=True, use_min=True, negative_singletons=True):
             s = self._colors[i]
             while next_val != cycle_first:
                 cycle.append(s * next_val)
-                s *= self._colors[next_val-1]
-                l[next_val-1], next_val = False, l[next_val-1]
+                s *= self._colors[next_val - 1]
+                l[next_val - 1], next_val = False, l[next_val - 1]
             if s != 1:
                 cycle.extend([-e for e in cycle])
 
@@ -1336,6 +1355,23 @@ def one(self):
         return self.element_class(self, [ZZ.one()] * self._n,
                                   self._P.identity())
 
+    def random_element(self):
+        """
+        Return an element drawn uniformly at random.
+
+        EXAMPLES::
+
+            sage: C = SignedPermutations(7)
+            sage: s = C.random_element(); s # random
+            [7, 6, -4, -5, 2, 3, -1]
+            sage: s in C
+            True
+        """
+        return self.element_class(self,
+                                  [choice([ZZ.one(), -ZZ.one()])
+                                   for _ in range(self._n)],
+                                  self._P.random_element())
+
     def simple_reflection(self, i):
         r"""
         Return the ``i``-th simple reflection of ``self``.
@@ -1454,7 +1490,7 @@ def _coerce_map_from_(self, C):
             False
         """
         if isinstance(C, Permutations) and C.n == self._n:
-            return lambda P, x: P.element_class(P, [1]*P._n, x)
+            return lambda P, x: P.element_class(P, [1] * P._n, x)
         if isinstance(C, ColoredPermutations) and C._n == self._n and C._m == 2:
             return lambda P, x: P.element_class(P,
                                                 [1 if v == 0 else -1
@@ -1496,7 +1532,7 @@ def long_element(self, index_set=None):
     Element = SignedPermutation
 
 # TODO: Make this a subgroup
-#class EvenSignedPermutations(SignedPermutations):
+# class EvenSignedPermutations(SignedPermutations):
 #    """
 #    Group of even signed permutations.
 #    """


Test patch: