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, 2016 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, 2016 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, 2016 at 9:49

2 Answers 2

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, 2016 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, 2016 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, 2016 at 11:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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