2
$\begingroup$

I don't understand the source of the failure of the following attempts to solve an equation involving the following very simple BezierFunction.

f = BezierFunction[{{0, 0}, {0, 0.8}, {1, 1}}];

The following three approaches fail, but in what I find to be a very odd way: the returned solution is 0.5, with no warning.

Solve[0.5 == Last[f[x]], x]
NSolve[0.5 == Last[f[x]], x]
Reduce[0.5 == Last[f[x]], x]

The following approach is successful (but produces a warning):

FindRoot[0.5 == f[x][[2]], {x, 0.5}]

To see what I find mysterious, consider the following trace.

Trace@NSolve[0.5 == Last[f[x]], x]

In particular, I would like to understand why Last[BezierFunction[{{0, 0}, {0, 0.8}, {1, 1}}][x]] evaluates to x within this trace. This is certainly not the behavior we get from ordinary functions (thank goodness):

h = t |-> {t + 1, t + 2};
Last[h[x]]  (* x+2 *)

Instead, it behaves like an undefined symbol:

Clear[h]
Last[h[x]]  (* x *)
$\endgroup$

2 Answers 2

2
$\begingroup$

In Mathematica, everything is an expression. When you use First or Last (which are just shortcuts to Part), you extract a part of your expression. For example:

Clear[g, x, y, z];

g[x] // Last
(* x *)

Part[g[x], -1]
(* x *)

g[x, y, z] // First
(* x *)

g[x, y, z] // Last
(* z *)

In your case, you have the following expression:

expr = BezierFunction[{{0, 0}, {0, 0.8}, {1, 1}}][x];
expr // Last
(* x *)

Is there something weird going on? No. You wanted to get the last part of your expression. Why does it look different if you use an "ordinary" function?

Last[(t |-> {t + 1, t + 2})[3]]
(* 5 *)

It is because this function will get evaluated when you give it a symbolic argument x! But the BezierFunction will stay unevaluated when you give it a symbolic argument. It will evaluate only if you give it a numerical argument.

BezierFunction[{{0, 0}, {0, 0.8}, {1, 1}}][x]
(* BezierFunction[{{0, 0}, {0, 0.8}, {1, 1}}][x] *)

BezierFunction[{{0, 0}, {0, 0.8}, {1, 1}}][.5] 
(* {0.25, 0.65} *)

BezierFunction[{{0, 0}, {0, 0.8}, {1, 1}}][.5] // Last
(* 0.65 *)

If you want to use it in Solve, you have to prevent Last acting on the expression too soon (that is: before the numerical argument is passed to the function). For example, you can do the following:

bf = BezierFunction[{{0, 0}, {0, 0.8}, {1, 1}}];
f[x_?NumericQ] := Last[bf[x]]

Solve[0.5 == f[x], x]
(* {{x -> 0.361508}} *)
$\endgroup$
5
  • $\begingroup$ That very last bit is helpful, but I am left with three questions. i. Is it documented that it will not evaluate a symbolic argument? ii. Why not evaluate symbolically e.g. to polynomials? iii. How does this help me understand why FindRoot[0.5 == Last[f[x]], {x, 0.5}] fails but FindRoot[0.5 == f[x][[2]], {x, 0.5}] succeeds? Thanks! $\endgroup$
    – Alan
    Mar 27, 2023 at 19:59
  • $\begingroup$ @Alan i. AFAIK it's not documented, so are InterpolatingFunction, BSplineFunction, etc. ii. I'd say this design is convenient, because BezierFunction is mainly for numeric purpose, and nobody want to see a horrible large polynomial as the output in most cases. Also, according to the wiki, the polynomial form of Bézier curve is not numerically stable. If you do want a polynomial, check this: mathematica.stackexchange.com/q/29633/1871 iii. As documented: Last[expr] is equivalent to expr[[-1]]. $\endgroup$
    – xzczd
    Mar 28, 2023 at 2:42
  • $\begingroup$ @xzczd Yes of course. But I think your point is, Last will be applied to the unevaluated expression, while in contrast the entire expression Part[f[x],2] must remain 'unevaluated' and so subsequently becomes useful. That raises the question, why doesn't NSolve[0.5 == f[x][[2]], x] work, and there the answer is apparently: because the solution strategy tries to invert f instead of inverting x|->f[x][[2]] (or using another strategy entirely). $\endgroup$
    – Alan
    Mar 28, 2023 at 13:34
  • 1
    $\begingroup$ @Alan, the error message will tell you why FindRoot[0.5 == f[x][[2]], {x, 0.5}] "works" and FindRoot[0.5 == Last[f[x]], {x, 0.5}] doesn't. There is no second part of f[x], and it will therefore stay in this "unevaluated" form f[x][[2]]. However, there is the last part of f[x], so it will immediately evaluate to x before solving. $\endgroup$
    – Domen
    Mar 28, 2023 at 13:40
  • $\begingroup$ @Domen Yes, see my previous comment. $\endgroup$
    – Alan
    Mar 28, 2023 at 16:22
3
$\begingroup$
f = BezierFunction[{{0, 0}, {0, 0.8}, {1, 1}}];
FindRoot[0.5 == Indexed[f[x], 2], {x, 0.5}]

{x -> 0.361508}

Or

FindRoot[0.5 == Last[f[x]], {x, 0.5}, Evaluated -> False]

{x -> 0.361508}

$\endgroup$
1
  • $\begingroup$ FindRoot was already working, but this nicely gets rid of the warning. I especially like the clever use of Indexed. But is there a way to make say NSolve work? $\endgroup$
    – Alan
    Mar 28, 2023 at 13:34

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.