11
$\begingroup$

I want to have a function value of an expression where some variables are solutions to some set of equations, with some values of parameters. I had an idea to use pure functions for that.

However, since both the expression and the equations are lengthy I'd like to place them in a separate expressions defined earlier on. Here is Minimal Working Example:

MWE1 = b x^2 + a y^2 + 3 + δ;
Dx = D[MWE1, x];
Dy = D[MWE1, y];
MWF[a_, b_] := 
  Function[{δ}, (Evaluate[MWE1]) /. 
    FindRoot[
      {Evaluate[Dx] == 0, Evaluate[Dy] == 0}, {{x, -3, 3}, {y, -3, 3}}, 
      AccuracyGoal -> Infinity]]

And here is an attempt to test it:

test3 = MWF[1, 1];
test3[1]
 3 + b x^2 + a y^2 + δ /. 
     FindRoot[
      {Evaluate[Dx] == 0, Evaluate[Dy] == 0}, {{x, -3, 3}, {y, -3, 3}},
      AccuracyGoal -> ∞]

However, if I paste the expressions MWE1, Dx, Dy directly to the definition of MWF, the function works like charm.

Is it possible to use predefined expressions inside the definition? Do so would make the code much clearer.

$\endgroup$
4
  • $\begingroup$ Have a look at With $\endgroup$
    – Sascha
    Aug 22, 2016 at 13:43
  • 1
    $\begingroup$ @Sascha Yes but that tends to mess things up due to how scoping is implemented using variable renaming. Replace is better, but can also end up with surprising results, see my answer. I don't have a good solution! :'( $\endgroup$
    – Szabolcs
    Aug 22, 2016 at 13:48
  • $\begingroup$ @Szabolcs very good point, I haven't though of that at all $\endgroup$
    – Sascha
    Aug 22, 2016 at 13:52
  • 4
    $\begingroup$ Related: (10086), (11461), (13757), (20766), (46751), (69590) $\endgroup$
    – Mr.Wizard
    Aug 22, 2016 at 21:38

4 Answers 4

6
$\begingroup$

Your issue is quite simple and fundamental: you try to use lexical scoping (SetDelayed and Function) as it would be dynamic scoping (Block). So your problem can be solved easily by outsourcing the dynamic scoping to Block with minimal modification of your original code:

MWE1 = b x^2 + a y^2 + 3 + δ;
Dx = D[MWE1, x];
Dy = D[MWE1, y];
MWF[aa_, bb_] = 
 Function[{δδ}, 
  Block[{δ = δδ, a = aa, b = bb}, 
   MWE1 /. FindRoot[{Dx == 0, Dy == 0}, {{x, -3, 3}, {y, -3, 3}}, 
     AccuracyGoal -> Infinity]]]

test3 = MWF[1, 1];
test3[1]

NDSolve`ScaledVectorNorm::urange: The scaling vector has a zero component, so the zero absolute tolerance leads to an unbounded value.

4.

Recommended reading:

  1. Plot using With versus Plot using Block

  2. What are the use cases for different scoping constructs?


Update

I just realized that it is possible to achieve what you want by means of anonymous pure function # &:

MWE1 = b x^2 + a y^2 + 3 + δ;
Dx = D[MWE1, x];
Dy = D[MWE1, y];
MWF[a_, b_] = Function[{δ},
    #1 /. FindRoot[{#2 == 0, #3 == 0}, {{x, -3, 3}, {y, -3, 3}}, AccuracyGoal -> Infinity]
    ] &[MWE1, Dx, Dy]

test3 = MWF[1, 1];
test3[1]

This method utilizes the fact that anonymous pure functions do not rename variables in the nested scoping constructs.

$\endgroup$
6
  • $\begingroup$ Alexey, would you please review my Related: links and tell me if you believe this question is a duplicate? $\endgroup$
    – Mr.Wizard
    Aug 22, 2016 at 21:42
  • $\begingroup$ @Alexey Popkov I tried that method, defining MWF[aa_, bb_] := Function[{δδ}, Module[{a = aa, b = bb, δ = δδ}, MWE1 /. FindRoot[{Dx == 0, Dy == 0}, {{x, -3, 3}, {y, -3, 3}}, AccuracyGoal -> Infinity]]] but it does not work. What version of mathematica are you running? I've recently upgraded, may that be source of that difference? $\endgroup$
    – Picek
    Aug 23, 2016 at 10:00
  • $\begingroup$ @Picek Module performs lexical scoping, so it can't work in place of Block where exactly dynamic scoping is necessary! Replace Module with Block and this will work. $\endgroup$ Aug 23, 2016 at 10:02
  • 1
    $\begingroup$ @Picek I have added another solution which doesn't use dynamic scoping and despite this doesn't imply any artificial tricks (like fooling enclosing scoping construct in order to avoid renaming of variables). $\endgroup$ Aug 23, 2016 at 12:14
  • $\begingroup$ @Mr.Wizard Taking into account that 3 people already voted for (10086), I've voted in favor of (69590). In my opinion none of the questions you linked to is an exact duplicate of this one but (10086) and (69590) together cover the scope of this question. $\endgroup$ Aug 24, 2016 at 7:31
8
$\begingroup$

Why doesn't it work?

Evaluate only has effect at the first level in a held expression.

{Hold[1 + 1], Hold[Evaluate[1 + 1]], Hold[f[1 + 1]]}
(* {Hold[1 + 1], Hold[2], Hold[f[1 + 1]]} *)

Why does Evaluate only work at the first level?

Because the evaluator does not look for exceptions beyond the first level inside an expression having a head with HoldAll. If it did, it may potentially spend a lot of time scanning large expressions.

Read about evaluation:

What's the solution?

Typically one has to use Replace or ReplaceAll.

Function[x, y] /. y -> x^2
(* Function[x, x^2] *)

All this falls under the label of , a difficult topic where things may go wrong all too easily due to how Mathematica implements scoping using variable renaming.

Example of how things may go wrong:

Can't we just use With instead of Replace? Let's try:

With[{y = x^2}, Function[x, y]]

(* Function[x$, x^2] *)

Oops! The function argument got renamed from x to x$ to prevent a name collision. At the same time it "broke" our function. This is the reason why Replace and ReplaceAll are better than With for such purposes.

But is Replace bulletproof? Not at all, unfortunately. When doing such a transformation, we might want to localize y. Let's see what happens.

Module[{y},
 Function[x, y] /. y -> x^2
]

(* Function[x$, x^2] *)

Oops! The renaming kicked in again. (Note: Block won't rename here but it's not always a good replacement to Module.)

Doing such things can also lead to situations where SetSystemOptions["StrictLexicalScoping" -> True] may make a significant difference (may fix or break things). This is why Stan Wagon's FindRoots2D function won't work (as posted on this site) if strict lexical scoping is turned on.

Sometimes we can use a trick like this to avoid variable renaming:

Module[{y}, Function @@ Hold[x, y] /. y -> x^2]
(* Function[x, x^2] *)

Writing Function @@ Hold[...] instead of Function[...] prevents the renaming (for better or worse).

To sum up, code generation seems to be quite difficult in Mathematica. Caution is advised. Now if someone could prove me wrong, I would be very happy ... Looking forward to other answers!

$\endgroup$
2
  • $\begingroup$ Szabolcs, would you please review my Related: links and tell me if you believe this question is a duplicate? $\endgroup$
    – Mr.Wizard
    Aug 22, 2016 at 21:40
  • $\begingroup$ Would you vote for 13757 as a 2nd duplicate question? I have already voted for 10086. $\endgroup$
    – m_goldberg
    Aug 23, 2016 at 12:52
4
$\begingroup$

It can be done, but I suspect you will find it a bit more tricky than you expected. Here is one way to do it. Note that I don't introduce a variable named δ. It is easier to work with Slot ( # ).

{a, b, x, y} = Range[4]; (* to ensure scope conflicts assert themselves *)

MWF[a_, b_] =
  Block[{MWE1, Dx, Dy, a, b, x, y},
    MWE1 = b x^2 + a y^2 + 3 + #;
    Dx = D[MWE1, x];
    Dy = D[MWE1, y];
    With[{f = MWE1, dx = Dx, dy = Dy},
      Function[f /.
        FindRoot[{dx == 0, dy == 0}, {{x, -3, 3}, {y, -3, 3}}, 
          AccuracyGoal -> ∞]]]]
3 + b x^2 + a y^2 + #1 /. 
  FindRoot[{2 b x == 0, 2 a y == 0}, {{x, -3, 3}, {y, -3, 3}}, 
    AccuracyGoal -> ∞] &
testF = MWF[1, 1]
3 + 1 x^2 + 1 y^2 + #1 /. 
  FindRoot[{2 x == 0, 2 y == 0}, {{x, -3, 3}, {y, -3, 3}}, 
    AccuracyGoal -> ∞] &
Block[{x, y}, testF[1]]

msg

4.

$\endgroup$
4
  • $\begingroup$ m_goldberg, would you please review my Related: links and tell me if you believe this question is a duplicate? $\endgroup$
    – Mr.Wizard
    Aug 22, 2016 at 21:42
  • $\begingroup$ @Mr.Wizard. I would vote for the aggregate of 10086, 13757, and 46751 as constituting a duplicate. Can we close that way? If three people each vote for a different one, would that do it? $\endgroup$
    – m_goldberg
    Aug 23, 2016 at 0:56
  • $\begingroup$ Yes, that would do it. $\endgroup$
    – Mr.Wizard
    Aug 23, 2016 at 12:39
  • $\begingroup$ @Mr.Wizard. I will vote for 10086 to get things started. $\endgroup$
    – m_goldberg
    Aug 23, 2016 at 12:49
1
$\begingroup$

Another method would be to use delayed evaluation to work around the issue of variable renaming.

MWE1[a_, b_][x_, y_][δ_] := b x^2 + a  y^2 + 3 + δ;
Dx[a_, b_][x_, y_][δ_] := D[MWE1[a, b][x, y][δ], x];
Dy[a_, b_][x_, y_][δ_] := D[MWE1[a, b][x, y][δ], y];

Note that one has to introduce a Module to properly scope x and y.

MWF[a_, b_] := Module[{x, y}, 
Function[{δ}, (MWE1[a, b][x, y][δ]) /. 
FindRoot[{
  Dx[a, b][x, y][δ] == 0, 
  Dy[a, b][x, y][δ] == 0}, 
  {{x, -3, 3}, {y, -3, 3}}, 
 AccuracyGoal -> Infinity]]
]

testing confirms that all symbols are properly scoped and that the function can be called

a = "test"; b = "test"; x = "test"; y = "test"; δ = "test";;
MWF[1, 1][1]

screenshot

$\endgroup$
1
  • $\begingroup$ That works for sure, but it does not have the desired result since the whole point is that in my original calculation MWE1 is much more complicated and executing derivatives on every allocation of MWF must be avoided $\endgroup$
    – Picek
    Aug 23, 2016 at 9:37

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