# Implementing symmetry assumptions in FullSimplify

I want to symmetrise a long expression, M, that involves a function of 4 arguments, f[u1,u2,d1,d2], and its products (for context, see my edit below).

In doing so, I want to make use of the fact that f is symmetric under exchange of either its first pair, or second pair of arguments, i.e:

myAssumpts={f[u1,u2,d1,d2]==f[u2,u1,d1,d2]}&&{f[u1,u2,d1,d2]==f[u1,u2,d2,d1]}


As a minimal working example, let's say I want to check that I have implemented the above symmetries of f properly, so I want to write something like

Assuming[myAssumps,FullSimplify[2*f[1,2,3,4]-f[2,1,3,4]-f[1,2,4,3]]


I should get 0, but I just get back the argument of FullSimplify above.

So questions are:

1. How do I implement myAssumpts in a way that recognises the symmetry of any exchange of first-pair, or second-pair, of arguments?
2. My actual expression M has products like f[0,1,5,6]*f[2,3,4,0], and I want to symmetrise the whole expression with respect to all permutations of {1,2,3} (in the first pair of arguments), and {4,5,6} (in the second pair). Is there an easy way to do this automatically, e.g. using Permutations?
3. In reality the arguments should be tensor-like indices, so something like Subsuperscript[f, {d1, d2}, {u1, u2}], but I'm very wary of using sub/superscripts in Mathematica. Can anyone tell me how to do this safely with sub/superscripts?

Edit for context: The actual problem I'm working on is to find a symmetrised version of M[1,2,3,4,5,6], which is a function of 6 variables.

In my case, M[1,2,3,4,5,6] is a sum of products, e.g. g[1]*f[1,0,4,5]*f[2,3,6,0]-2*g[2]*f[2,3,5,0]*f[1,0,5,6]+... with a whole bunch of other terms, involving some other function g.

The symmetry group of M[1,2,3,4,5,6] has 36 elements: I can permute any of {1,2,3} together with any permutation of {4,5,6}, and I'm trying to find a way of permuting the indices, subject to the 4 symmetries of f[1,2,3,4] (i.e. permute {1,2} and/or {3,4}).

• It is difficult to say something without the expression. However, one mathematical (group-theoretical) trick you could try out. It is the summation over the symmetry group. In your case, within the example of a function of two variables u[x1,x2]==u[x2,x1] one considers another function v[x1,x2]=1/2*(u[x1,x2]+u[x2,x1]). May 9, 2023 at 9:00
• f[OrderlessPatternSequence[u1_, u2_], OrderlessPatternSequence[d1_, d2_]] := {{u1, u2}, {d1, d2}} and then without FullSimplify or any Assumptions: 2*f[1, 2, 3, 4] - f[2, 1, 3, 4] - f[1, 2, 4, 3]
– Syed
May 9, 2023 at 9:10
• @AlexeiBoulbitch yes indeed what I'm trying to do is a version of what you suggest, and I've edited my question to reflect this. Can you suggest a way to do the symmetrisation, while implementing all the permutations of indices, and symmetries of f? May 10, 2023 at 11:22

You can define rules, so that x1 and x2 and also x3 and x4 are always sorted. Like:

f[x1_, x2_, x3_, x4_] /; x1 > x2 = f[x2, x1, x3, x4];
f[x1_, x2_, x3_, x4_] /; x3 > x4 = f[x1, x2, x4, x3];


Then:

FullSimplify[2*f[1, 2, 3, 4] - f[2, 1, 3, 4] - f[1, 2, 4, 3]]

0


Concerning Super/Subscripts, I would work with ordinary functions and only format the result. You should not work against the system.

• Thanks, this is probably the way. I only haven't marked this as the answer as I still need some help implementing the permutations. May 10, 2023 at 11:23

You can symmetrize a given object over a list of arguments with the following function:

symmetrize[list_][function_] := Plus @@ (
function /. Thread[#] & /@ (list -> # & /@ Permutations[list])/Factorial[Length[list]]
);


For instance

In: f[a, b, c] // symmetrize[{a, b}]
Out: 1/2 f[a, b, c] + 1/2 f[b, a, c]


You can also apply your choice of symmetry (symmetric in two pairs of arguments) with the following command:

applySymmetry = Function[# /. f[u1_, u2_, d1_, d2_] :>
f[Sequence @@ Sort[{u1, u2}], Sequence @@ Sort[{d1, d2}]]
];


Finally, we can arrange that the function is shown as a tensor with subscripts and superscripts:

Format[f[a_, b_, c_, d_]] := Subsuperscript[f, Row[{a, b}], Row[{c, d}]];


In the case of OP's example:

In: g[1]*f[1, 0, 4, 5]*f[2, 3, 6, 0] - 2*g[2]*f[2, 3, 5, 0]*f[1, 0, 5, 6]
//symmetrize[{1, 2, 3}]//applySymmetry//Simplify


$$\frac{1}{3} \left((g(1)+g(2)) \left(-f_{03}^{56}\right) f_{12}^{05}-g(2) f_{01}^{56} f_{23}^{05}-g(3) f_{01}^{56} f_{23}^{05}-(g(1)+g(3)) f_{02}^{56} f_{13}^{05}+g(3) f_{03}^{45} f_{12}^{06}+g(2) f_{02}^{45} f_{13}^{06}+g(1) f_{01}^{45} f_{23}^{06}\right)$$

It might be that the best solution depends on the definitions for f and m (avoiding initial capitals per best practices), the right-hand sides (RHS) of a defining formula. It appears that m has at least one in terms of f and other functions.

Some ideas:

1. If f[a,b,c,d] always evaluates to some value ((a+b)/(c+d), for instance), then the symmetry should be present in the value, and we should have to implement symmetry rules for f per se. The function m as described in the OP seems to fit in this category: the symmetry of m should be handled by the symmetry of f in the formula for m.

2. The usual trick is to put the arguments into a canonical order.

3. Comparators such as > and < work only on numeric arguments. OrderedQ[] compares any list of expressions. Sort[] put expressions into a canonical order.

4. The attribute Orderless is used by Mathematica to automatically sort arguments into a canonical order. Unfortunately, it sorts all arguments, and in the present case, we want to sort subsets of the arguments.

5. Functions like HypergeometricPFQ and MeijerG use lists to group arguments into ordered subsets, but there's no ready-made mechanism for making lists into orderless subsets. One can make one, though.

### Orderless subset grouping

ESC<ESC and ESC>ESC can be used to enter AngleBracket, and AngleBracket will be displayed using them.

AngleBracket // ClearAll;
SetAttributes[AngleBracket, Orderless];
f // ClearAll;
f[a_, b_, c_, d_] :=
f[\[LeftAngleBracket]a, b\[RightAngleBracket],
\[LeftAngleBracket]c, d\[RightAngleBracket]];
(* other definitions of f[] *)
m // ClearAll;
m[a_, b_, c_, d_, e_, f_] :=
m[\[LeftAngleBracket]a, b, c\[RightAngleBracket],
\[LeftAngleBracket]d, e, f\[RightAngleBracket]];
(* other definitions of m[] *)

f[u2, u1, d1, d2]
(*
f[\[LeftAngleBracket]u1,  u2\[RightAngleBracket],
\[LeftAngleBracket]d1,  d2\[RightAngleBracket]]
*)

f[u1, u2, d1, d2] == f[u2, u1, d1, d2]
(*  True  *)


### Sorting subsets of arguments

f // ClearAll;
f[a_, b_, c_, d_] /; ! (OrderedQ[{a, b}] && OrderedQ[{c, d}]) :=
f[Sequence @@ Sort[{a, b}], Sequence @@ Sort[{c, d}]];
(* other definitions of f[] *)
m // ClearAll;
m[a_, b_, c_, d_, e_, f_] /;
! (OrderedQ[{a, b, c}] && OrderedQ[{d, e, f}]) :=
m[Sequence @@ Sort[{a, b, c}], Sequence @@ Sort[{d, e, f}]];
(* other definitions of m[] *)

f[u2, u1, d1, d2]
(*  f[u1, u2, d1, d2]  *)

f[u1, u2, d1, d2] == f[u2, u1, d1, d2]
(*  True  *)


### Manual canonicalization

canonicalize may be applied to any expression whenever desired to bring f[] (and m[] if desired, as shown) into canonical form. It may be used as a transformation function in (Full)Simplify, but simplification may reject the transformation if it does not lead to a measurably simpler expression. One could write a ComplexityFunction to go with it that adds a penalty if the expression contains an f or m with unordered arguments.

ClearAll[m, f, canonicalize];
canonicalize[expr_] := expr /. {
f[a_, b_, c_, d_] :>
f[Sequence @@ Sort[{a, b}], Sequence @@ Sort[{c, d}]],
m[a_, b_, c_, d_, e_, f_] :>
m[Sequence @@ Sort[{a, b, c}], Sequence @@ Sort[{d, e, f}]]};

f[u2, u1, d1, d2] // canonicalize
(*  f[u1, u2, d1, d2]  *)

f[u1, u2, d1, d2] == f[u2, u1, d1, d2] // canonicalize
(*  True  *)

Simplify[f[u1, u2, d1, d2] == f[u2, u1, d1, d2],
TransformationFunctions -> {Automatic, canonicalize}]
(*  True  *)

Simplify[f[u2, u1, d1, d2],
TransformationFunctions -> {Automatic, canonicalize},
ComplexityFunction -> (
SimplifySimplifyCount[#] +
Count[
#,
f[a_, b_, c_, d_] /; ! (OrderedQ[{a, b}] && OrderedQ[{c, d}]),
{0, Infinity}] &)]
(*  f[u1, u2, d1, d2]  *)


It turns out that the ComplexityFunction is not needed on the example f[u2, u1, d1, d2]. However, the expression and its canonicalization have the same complexity:

SimplifySimplifyCount /@ {f[u2, u1, d1, d2], f[u1, u2, d1, d2]}
(*  {5, 5}  *)


The documentation indicates that returning either one would be correct. If you want to guarantee which will be returned, then use an appropriate complexity function. The one above measure the unordered expression as more complex:

SimplifySimplifyCount[#] +
Count[
#,
f[a_, b_, c_, d_] /; ! (OrderedQ[{a, b}] && OrderedQ[{c, d}]),
{0, Infinity}] & /@
{f[u2, u1, d1, d2], f[u1, u2, d1, d2]}
(*  {6, 5}  *)


### Remark on "other definitions of f"

Care must be taken when giving multiple definitions. If the problem is located in the RHS of a definition — for instance, if the formula has a symmetry that Mathematica does not recognize in some cases, then f[a,b,c,d] must be canonicalized before such a definition is applied.

Note that the canonical order may change if one substitutes an expression for an argument:

f[a, b, c, d] // canonicalize
% /. b -> 2
% // canonicalize
(*
f[a, b, c, d]  -- canonical
f[a, 2, c, d]  -- substituted, no longer canonical
f[2, a, c, d]  -- canonical
*)


You may not want to let evaluation of f[a,b,c,d] occur until all arguments are numeric. NumericQ` is good for that.