I am developing some functions to help me work with Quantum Mechanics Momentum Operators. Here, for simplicity, I represent them with these simple functions because the actions of the operators themselves is not the problem.
f1[x_] := c1*x
f2[x_] := c2*x
f3[x_] := c3*x
f4[x_] := c4*x
These operators/functions appear alone or as compositions in algebraic expressions and inside vectors. Something like this:
vec = {f1@*f3 - f2@*f4, f3 + 2 f4 + 4, const1 + Exp[x] - f1};
Now I need to apply this vector to an argument [a], and I'm pretty sure that the main function to achieve this goal is Through. The correct end result should be:
{a c1 c3 - a c2 c4, a c3 + 2 a c4 + 4, const1 + Exp[x] - a c1}
Firs step:
vec2 = Through[vec[a]]
{(f1@*f3 - f2@*f4)[a], (4 + f3 + 2 f4)[a], (const1 + E^x - f1)[a]}
This first step works as expected, feeding this argument [a] to each component of the vector.
Now I need this to keep happening inside the expressions of each component of the vector, so that the operators f1,f2,.. get evaluated with the argument [a]. Constants and anything that is not an operator should remain unchanged throughout the process. To do this I Map Through to that output, but now I get mixed results:
Map[Through, vec2]
{a c1 c3 + (-(f2@*f4))[a], a c3 + 4[a] + (2 f4)[a],
const1[a] + (E^x)[a] + (-f1)[a]}
It works fine when the argument hits the operator directly, with operators alone or with composition, but fails otherwise:
When anything goes with the operators/compositions: (-(f2@*f4))[a] or (2 f4)[a] or (-f1)[a], because the operator doesn't get evaluated.
It feeds the argument to everything, not just operators: 4[a], const1[a] + (E^x)[a].
I have tried playing with the second argument of Through (Through[expr,h] performs the transformation wherever h occurs in the head of expr.), with other functions different to Map and with different levelspecs for Map and other functions, but honestly I don't think sharing those results would help.
I will add that, if I apply Through again to a relevant part like:
Through[(-(f2@*f4))[a]]
a c2 c4 (-1)[a]
The operator composition does get evaluated but it stills try to also evaluate (-1)[a]. Another thing is that I am not sure how to do this other than by hand. This makes me think that a proper recursive/multilevel mapping with the correct head argument of Through (or other kind of discrimination) would do the trick. And what drives me crazy is that I am almost sure that it will be a single line of code! Any help would be greatly appreciated. Thanks a lot!
Edit: Explicit list of operators & ReplaceAll approach
The more I study it, the more robust and sound seem to me the implementation proposed by @Lukas Lang based on the use of a specific head to identify operators. What I love of this approach is that it now looks easy to add special behaviours to handle more and more operations, like Dot products, Cross products of operators or combined actions of operators on |j,m> or TensorProduct[|j1,m1>,|j2,m2>]
spaces, etc. In the following days I will try to develop it further, at least for problems involving Angular Momentum and Addition of Angular Momentum.
I'm pretty convinced that it's not as sound as @Lujas Lang approach, but for educational purposes I would like to share another possibility that I was considering. It involves maintaining an explicit list of operators and then use a function that use a simple pattern matching set of rules to "apply" operators to arguments when needed:
ClearAll[f1, f2, f3, f4, opApply, opList]
opList = f3 | f2 | f1 | f4;
f1[x_] := c1*x
f2[x_] := c2*x
f3[x_] := c3*x
f4[x_] := c4*x
opApply[list_List[args__]] := opApply /@ Through[list[args]]
opApply[head_[args__]] :=
ReplaceAll[Expand[head], {op1_@*op2_ /; (MatchQ[op1, opList] && MatchQ[op2, opList]) :> op1[op2[args]], op1_ /; MatchQ[op1, opList] :> op1[args]}]
vec = {f1@*f3 - f2@*f4, f3 + 2 f4 + 4, const1 + Exp[x] - f1};
vec[a]
% // opApply
Output:
{f1@*f3 - f2@*f4, 4 + f3 + 2 f4, const1 + E^x - f1}[a]
{a c1 c3 - a c2 c4, 4 + a c3 + 2 a c4, -a c1 + const1 + E^x}
It's not well tested, it's just to show the idea.
On the one hand, it's cumbersome in it's own way: you have to maintain the list of operators and the list of rules may get a little bit crazy or difficult to read if you want to handle many operations or situations.
On the other hand, it looks that have the potential to let you work freely with operators in a very natural notation and just use opApply when you need to as a special form of Through if you wish.
Since I am learning Wolfram Language, I would love to hear some opinions on this approach. Advantages? Disadvantages? Could it work good in complex situations? Should it be avoided for any reason?