I'm not sure if this is relevant for particular use case of OP, but we can force M
to normalize itself to canonical order, for arbitrary symbolic arguments, by using OrderedQ@{j, i}
instead of i>j
in the Condition
.
I also like yohbs idea of overriding Set...
functions, but I'd use a "softer" overriding method by using UpValues
.
Putting it together:
declareAntisymmetric // ClearAll
declareAntisymmetric@sym_Symbol := Module[{inside},
sym[i_, i_] = 0;
sym[i_, j_] /; OrderedQ@{j, i} := -sym[j, i];
sym /: (set : Set | SetDelayed /; Not@TrueQ@inside)[sym[i_, j_], val_] :=
Block[{inside = True},
set[sym[j, i], -val];
set[sym[i, j], val]
]
]
Alternatively, if you prefer not to have pairs of DownValues
as created by above function, but at the expense of potential swapping overhead at each call to your antisymmetric function. You can use something like following:
declareAntisymmetric // ClearAll
declareAntisymmetric@sym_Symbol := Module[{swapped},
sym[i_, i_] = 0;
sym[i_, j_] /; OrderedQ@{i, j} && Not@TrueQ@swapped :=
Block[{swapped = True}, -sym[j, i]];
sym[i_, j_] /; OrderedQ@{j, i} := -sym[j, i]
]
In above, function declared as antisymmetric can swap arguments from ordered to unordered and from unordered to ordered, but the former swap can be performed only once per function call. This version is based on answer to "Assign value to an odd function" post, by Simon Rochester.
Both above declareAntisymmetric
functions give the same outputs in following code.
You can declare M
to be antisymmetric and define specific values:
M // ClearAll
M // declareAntisymmetric
M[2, 1] = a;
M[3, j_] := 2 j - 6
Which gives expected result for arbitrary mixture of symbolic and numeric arguments:
M[1, 2] (* -a *)
M[3, y] (* -6 + 2 y *)
M[x, 3] (* 6 - 2 x *)
3 M[a, b] + M[b, a] (* 2 M[a, b] *)
SymmetrizedArray[]
with theAntisymmetric[]
symmetry mode? $\endgroup$