2
$\begingroup$

Suppose that I have a function $f:[0,1]^2 \to \mathbb{R}$ and I would like to approximate it from its values in the corners of the domain by the well-known formula $$ f(s,t) \approx (1-s,s) \begin{pmatrix} f(0,0) & f(0,1) \\ f(1,0) & f(1,1) \end{pmatrix} \begin{pmatrix} 1-t\\ t \end{pmatrix} \enspace, $$ which can be found on Wikipedia.

I tried two ways of writing this in Mathematica:

bilinearExpl[f_, s_, t_] :=
        (1 - s) (1 - t) f[0, 0] + (1 - s) t f[0, 1] + (1 - t) s f[1, 0] + s t f[1, 1]
bilinearMatr[f_, s_, t_] := {1 - s, s}.{{f[0, 0], f[0, 1]}, {f[1, 0], f[1, 1]}}.{1 - t, t}

If I try them out, they produce results that are quite much the same:

bilinearExpl[f, s, t]

gives

result of bilinearExpl

whereas

bilinearMatr[f, s, t]

returns

result of bilinearMatr

Taking a function

scalarFunction[x_, y_] := x^2 y

as an example, they also produce the same approximation of scalarFunction.

{Plot3D[scalarFunction[s, t], {s, 0, 1}, {t, 0, 1}, Mesh -> {11, 11}],
 Plot3D[bilinearExpl[scalarFunction, s, t], {s, 0, 1}, {t, 0, 1}, Mesh -> {11, 11}],
 Plot3D[bilinearMatr[scalarFunction, s, t], {s, 0, 1}, {t, 0, 1}, Mesh -> {11, 11}]}

results of both methods on a scalar example

So far so good. The same formula should work for a function $f:[0,1]^2 \to \mathbb{R}^2$ and both methods should give the same results. But they don't! Take, for instance,

vectorFunction[x_, y_] := {x, y}

Then

{ParametricPlot[vectorFunction[s, t], {s, 0, 1}, {t, 0, 1}, Mesh -> {11, 11}],
 ParametricPlot[bilinearExpl[vectorFunction, s, t], {s, 0, 1}, {t, 0, 1}, Mesh -> {11, 11}],
 ParametricPlot[bilinearMatr[vectorFunction, s, t], {s, 0, 1}, {t, 0, 1}, Mesh -> {11, 11}]}

gives

results of the vector-valued example

and the two right plots are certainly not the same.

Question: why are the results of the two (seemingly equivalent) formulas different?

I suppose there is some dimension mismatch of the arrays.

$\endgroup$

2 Answers 2

2
$\begingroup$

FWIW, Mathematica knows about bilinear interpolation:

InterpolatingPolynomial[{{{0, 0}, f[0, 0]}, {{1, 0}, f[1, 0]},
                         {{0, 1}, f[0, 1]}, {{1, 1}, f[1, 1]}}, {s, t}]

For some reason, InterpolatingPolynomial[] chokes when f is vector-valued; I'm not sure why.

In any event: you seem to know about Mathematica's symbolic capabilities, so you should use it to help you debug when things are not going the way you wanted them. Here's what happens when you try to bilinearly interpolate a vector-valued function with your code:

With[{f = {#1, #2} &}, (* bilinearExpl *)
     (1 - t) ((1 - s) f[0, 0] + s f[1, 0]) + t ((1 - s) f[0, 1] + s f[1, 1])]
   {s (1 - t) + s t, t}

This is the correct result; effectively, the interpolation formula's scalar form is mapped across your vector function's components.

With[{f = {#1, #2} &}, (* bilinearMatr *)
     {1 - s, s}.{{f[0, 0], f[0, 1]}, {f[1, 0], f[1, 1]}}.{1 - t, t}]
   {s (1 - t), s (1 - t) + t}

See the difference?

The cure in this case is to reorder your dot products:

With[{f = {#1, #2} &},
     {1 - t, t}.({1 - s, s}.{{f[0, 0], f[0, 1]}, {f[1, 0], f[1, 1]}})]
   {s (1 - t) + s t, t}

(Note the use of parentheses so that the matrix-vector product is done first.)

The reordered dot product formula still makes sense even if the vector-valued function has more than two components:

With[{f = {#1 - #2, #1 + #2, #1 #2} &},
     {1 - t, t}.({1 - s, s}.{{f[0, 0], f[0, 1]}, {f[1, 0], f[1, 1]}})]
   {s (1 - t) + (-1 + s) t, s (1 - t) + (1 + s) t, s t}

while your version will choke:

With[{f = {#1 - #2, #1 + #2, #1 #2} &},
     {1 - s, s}.{{f[0, 0], f[0, 1]}, {f[1, 0], f[1, 1]}}.{1 - t, t}]

Dot::dotsh: Tensors {{s, s, 0}, {-1 + s, 1 + s, s}} and {1 - t, t} have
incompatible shapes.
   {{s, s, 0}, {-1 + s, 1 + s, s}}.{1 - t, t}
$\endgroup$
2
$\begingroup$

I think this might help. Dot documentation says:

enter image description here

Now consider this, noting the operational grouping via parentheses:

rules = {a -> {a1, a2}, b -> {b1, b2}, c -> {c1, c2}, d -> {d1, d2}};

{r, s}.({{a, b}, {c, d}} /. rules).{t, u}

({r, s}.{{a, b}, {c, d}}.{t, u}) /. rules
{(a1 r + c1 s) t + (a2 r + c2 s) u, (b1 r + d1 s) t + (b2 r + d2 s) u}

{(a1 r + c1 s) t + (b1 r + d1 s) u, (a2 r + c2 s) t + (b2 r + d2 s) u}

One possible approach:

ClearAll[bilinearMatr]

Block[{f, s, t},
  bilinearMatr[f_, s_, t_] =
    {1 - s, s}.{{f[0, 0], f[0, 1]}, {f[1, 0], f[1, 1]}}.{1 - t, t}
];

{ParametricPlot[vectorFunction[s, t], {s, 0, 1}, {t, 0, 1}, Mesh -> {11, 11}], 
 ParametricPlot[bilinearExpl[vectorFunction, s, t], {s, 0, 1}, {t, 0, 1}, 
  Mesh -> {11, 11}], 
 ParametricPlot[bilinearMatr[vectorFunction, s, t], {s, 0, 1}, {t, 0, 1}, 
  Mesh -> {11, 11}]}

enter image description here

For automation of this Block/Set work-around see:

$\endgroup$
1
  • $\begingroup$ Thank you for your answer, the trick with the Block seems quite useful. However, I can accept only one answer. $\endgroup$ Dec 21, 2016 at 9:12

Your Answer

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

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