I will describe a few approaches. Every approach assumes that the kernel is fresh, with no definitions present. The reader is assumed to Quit[]
before trying each approach, otherwise the approaches will interfere.
First approach (not recommended). If one interprets OP's question literally, one would redefine .
, whose full name is Dot
, and this can be done as follows:
Unprotect[Dot];
Dot[x_,Y] := Y.x.ConjugateTranspose[Y];
Protect[Dot];
There are a variety of problems with this. For example, if we enter
a.b.Y
what are we going to get? There are at least two possibilities:
The input could be interpreted as a.(b.Y)
in which case the evaluation would give a.Y.b.ConjugateTranspose[Y]
and then Y.a.ConjugateTranspose[Y].b.ConjugateTranspose[Y]
.
The input could be interpreted as (a.b).Y
in which case we'd get Y.a.b.ConjugateTranspose[Y]
.
Now it turns out that we get the second of these, but it would have been difficult to guess. Perhaps we'd find an explanation somewhere in the documentation, but it is not the kind of thing one should rely on when writing robust code.
For a variety of reasons, it is usually not a good idea to modify built-in functions.
Second approach (not recommended). This is a variant of the first approach, where instead of unprotecting Dot
one makes the same definition but as an upvalue for Y
:
Y /: Dot[x_,Y] := Y.x.ConjugateTranspose[Y];
The main difference is that the definition is stored with the symbol Y
rather than with the built-in symbol Dot
. For more details see Evaluation of Expressions which is a must-read if one wants to set up ones own symbolic computation in Mathematica. See also Associating Definitions with Different Symbols and this.
Third approach. As @lericr points out, for more serious symbolic computation, one should set up ones own symbols. This can be tough because one has to make all definitions oneself. But at least there will be no interference with existing definitions, and there is no risk of messing up the built-in functions. So here one could define
myDot[x_,Y] := Y.x.ConjugateTranspose[Y];
On the right hand side I now used the ordinary .
(which is a shorthand for Dot
) but perhaps OP wants to use myDot
on the right hand side, that depends on the application.
Btw, if one does not like the way the symbol is printed in the notebook, one can redefine it using Format
, for example
Format[myDot[x__]]:=Infix[{x},"***"];
will make sure that an input such as myDot[a,b]
is printed as a***b
. I recommend not using Format
when getting started with symbolic programming, since it can be confusing.
To be clear, in this way of doing things, nothing is automatic. For example, myDot[a,myDot[b,c]]
and myDot[myDot[a,b],c]
are not the same thing. If we want them to be the same thing, we could define
myDot[x___,myDot[y___],z___] := myDot[x,y,z];
which will transform both expressions above to myDot[x,y,z]
. There are other ways to accomplish this kind of thing, including SetAttributes[myDot,{Flat,OneIdentity}]
.
Fourth approach. I do not understand what OP means with
Note that ideally I would not want, for technical reasons, to do this by defining a function like ... because I want to keep it symbolic ...
Functions are certainly very compatible with symbolic computation! In any case, let me mention that the things above can similarly be accomplished using rules, for example one could define
rls = {Dot[x_,Y] :> Y.x.ConjugateTranspose[Y]};
Then one could use something like
a.Y /. rls
(* shorthand for ReplaceAll[Dot[a,Y],rls] *)