How to generate all involutive permutations?
I wondered how well Involutions
has been implemented, so I tried to reimplement it myself. The following implementation can be up to 15 times faster than Involutions
.
Needs["Combinatorica`"];
PackedQ = Developer`PackedArrayQ;
ToPack = Developer`ToPackedArray;
myInvolutions[list_List] := Block[{data, A, n, g},
n = Length[list];
A = ToPack[
UpperTriangularize[{Range[3, n]}[[ConstantArray[1, n - 1]]]]];
A[[2 ;;]] += ToPack[LowerTriangularize[{Range[2, n - 1]}[[ConstantArray[1, n - 2]]]]];
g[2, {{}}] = ToPack[{{{1, 2}}}];
g[n_, data_] := With[{m = Length[data]},
Join[
Transpose[{{
ConstantArray[1, {(n - 1) m}],
Flatten[Table[ConstantArray[i, {m}], {i, 2, n}]]
}},
{2, 3, 1}
],
ArrayReshape[
A[[1 ;; (n - 1), Flatten[data]]], {(n - 1) m,
Quotient[n - 2, 2], 2}
],
2]
];
data = {{}};
Join @@ Join[
{{list}},
Table[
data = g[2 i, data];
getPermutationLists[
list,
ArrayReshape[
Subsets[Range[n], {2 i}][[All, Flatten[data]]],
{Binomial[n, 2 i] Length[data], Sequence @@ Rest[Dimensions[data]]}
]
],
{i, 1, Quotient[n, 2]}]
]
]
getPermutationLists = Compile[{{ran, _Integer, 1}, {idx, _Integer, 2}},
Block[{a = ran, i, j, k, x},
Do[
i = Compile`GetElement[idx, k, 1];
j = Compile`GetElement[idx, k, 2];
x = Compile`GetElement[a, i];
a[[i]] = Compile`GetElement[a, j];
a[[j]] = x,
{k, 1, Length[idx]}
];
a
],
CompilationTarget -> "C",
RuntimeAttributes -> {Listable},
Parallelization -> True,
RuntimeOptions -> "Speed"
];
Here is a test:
n = 14;
a = Range[n];
aa = myInvolutions[a]; // AbsoluteTiming // First
bb = Involutions[a]; // AbsoluteTiming // First
Sort[aa] == Sort[bb]
5.63692
83.2192
True
Hard to beat Henrik's answer, but I'd like to show how you could do it by hand, without using a package.
From the decomposition of permutations into cycles, you can see that involutive permutations are the compositions of fixed points and transpositions.
Accordingly, you can build all the fixed points i
(for identity) and transpositions t
with:
n = 10;
a = Range[n];
t = Subsets[a, {2}];
i = Subsets[a, {1}];
Then, the transpositions will be among
s = Subsets[t, Floor[n/2]];
Unfortunately, s
also includes non-disjoint cycles (like {1, 2}, {1, 3}
). I don't know how to assemble only disjoint cycles together, but this is an unefficient brute-force approach:
inv = Select[s, Length[Flatten@#] == Length[DeleteDuplicates@Flatten@#] &];
That's a bit stupid because it builds one million subsets for $n=10$, to keep only 1000 in the end. This can be improved. Note however that contrary to your fully brute-force approach, here it's only exhaustive for transpositions, so it's still much much better.
Now, the number of permutations is given by
Length@inv
(* 9496 *)
And you can also rebuild the permutations matrices from inv
:
cycles = Cycles /@ inv;
invo = Permute[Range[n], #] & /@ cycles
And @@ Map[PermutationProduct[#, #] == Range[n] &, invo]
(* True *)
The partially obsolete Combinatorica package has a function called Involutions
that returns all involutive permutations:
Needs["Combinatorica`"];
a = Range[10];
inv = Involutions[a];
And @@ Map[PermutationProduct[#, #] == a &, inv]
True
Note also the existence of NumberOfInvolutions
. Guess what it does...
The code for Involutions
is actually available and can be printed as follows:
Needs["Combinatorica`"];
Needs["GeneralUtilities`"];
PrintDefinitions[Involutions]