5
$\begingroup$

I have a computationally intense function f that returns a matrix. Sometimes only the diagonal is needed, which is faster to compute, thus there's a syntax for f to return only the diagonal, say f[...,Diagonal->True]. I would like Diagonal[f[...]] to evaluate to f[...,Diagonal->True].

Naive attempt at using UpValue fails

f[x_] := x.Transpose[x];
Diagonal[f[x_]] ^:= f[x, Diagonal->True];

because f[...] is evaluated first.

How can I achieve the desired behaviour? What would be the "Mathematica way" of handling this situation?

Update:

It appears that the requested behaviour might not be implementable in Mathematica.

Related approaches proposed so far:

  • Hold argument to Diagonal: Diagonal[Unevaluated@f[...]] with UpValues f /: Diagonal[f[x_]] := f[x, Diagonal -> True]

    Works, but is more cumbersome than directly calling f[..., Diagonal->True] as one needs to remember using Unevaluated instead of an option to f

  • Replace Diagonal by a convenience function diagonal, which works with HoldAllComplete

    Works, but is more cumbersome than directly calling f[..., Diagonal->True] as one needs to remember using another function diagonal instead of an option to f

  • Define only UpValues for f, so it is only evaluated if it has a surrounding context

    Works, but requires modifying $Post, which is as undesirable as modifying the built-in Diagonal.

$\endgroup$
3
  • $\begingroup$ You can use f /: Diagonal[f[x_]] := f[x, Diagonal -> True] but it won't help because in standard evaluation sequence arguments of Diagonal will be evaluated before custom rules. (tutorial/Evaluation). Then you can use Diagonal[Unevaluated@f[{{a, b}, {c, d}}]] but is that handy? $\endgroup$ – Kuba Jul 13 '16 at 9:27
  • $\begingroup$ @Kuba This would not be handy as it would be easier to call f[..., Diagonal->True] directly. Changing Diagonal via Unprotect to hold its argument is undesirable as well due to affecting other uses of Diagonal. $\endgroup$ – mrupp Jul 13 '16 at 9:48
  • $\begingroup$ That is what I meant. I'd go with additional diagonalF but maybe I'm missing something. $\endgroup$ – Kuba Jul 13 '16 at 9:49
1
$\begingroup$

I know of no method to achieve the specific syntax and evaluation that you want that I would recommend in practice. However for the sake of discussion a way to avoid the unwanted evaluation of the argument is to only define UpValues on f, which only evaluates f[x] when it has some surrounding expression. To get evaluation of a bare input/output we need something like $Post = Identity;. The order of definitions matters here as we need the Diagonal rule to trigger first.

ClearAll[f]

Diagonal[f[x_]] ^:= myFdiagonal[x]

h_[a___, f[x_], b___] ^:= h[a, x.Transpose[x], b]

$Post = Identity;

Now:

f[{{1}, {3}}]

Diagonal[ f[{{1}, {3}}] ]
{{1, 3}, {3, 9}}

myFdiagonal[{{1}, {3}}]

f[x] should work inside any head that does not have HoldAllComplete and as long as a competing UpValue of a different function does not supersede it.

$\endgroup$
1
  • $\begingroup$ It seems even Mathematica itself does not do what I wanted: There is Integrate and NIntegrate, where N[Integrate[...]] appears to not directly translate to NIntegrate, but to first evaluate Integrate and then N. I have accepted your answer for stating that it is likely not possible to do what I wanted. $\endgroup$ – mrupp Jul 15 '16 at 15:15
3
$\begingroup$

I don't think up-values are the way to go. I think you should write a new function, say diag, that has a special behavior for f, but works like Diagonal for any other args. Something like

f[x_] := x.Transpose[x]
f[x_, Diagonal -> True] := Diagonal[x]

SetAttributes[diag, HoldAllComplete]
diag[f[x_]] := f[x, Diagonal -> True]
diag[args___] := Diagonal[args]

Then with

m = {{a, b}, {c, d}};

you would get

f[m]

{{a^2 + b^2, a c + b d}, {a c + b d, c^2 + d^2}}

diag[f[m]]

{a, d}

but

diag[Inverse[m]]

{d/(-b c + a d), a/(-b c + a d)}

I realize this is not so pretty a solution as you hoped for, but I believe it to be the least troublesome.

$\endgroup$
2
  • $\begingroup$ This is an option. However, if the user can remember to use diag, then he can remember to use f[..., Diagonal -> True], which overall would be simpler. I was curious whether there is a way to transparently use the efficient solution (f[..., Diagonal->True]) even if the user forgets to explicitly request it and just uses Diagonal[f[...]]. $\endgroup$ – mrupp Jul 13 '16 at 11:28
  • $\begingroup$ @mrupp. diag is purely a convenience function. Its only purpose is to make code entry easier. I thought that is what you are trying to accomplish. $\endgroup$ – m_goldberg Jul 13 '16 at 11:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.