Group Abstract Group Abstract

Message Boards Message Boards

NDSolve error: Encountered non-numerical value

Posted 23 days ago

POSTED BY: Yumin Tian
8 Replies

A fifth or sixth workaround (Version 14.1 and higher):

SetAttributes[y, NonThreadable];
NDSolve[{y'[t] == -{1, 0} y[t], y[0] == {1, 2}}, y, {t, 0, 1}]
(*  {{y -> InterpolatingFunction[{{0., 1.}}, "<>"]}}  *)
POSTED BY: Michael Rogers

This feels somewhat embarrassing to me:

In[7]:= Assuming[{y \[Element] Vectors[2]}, {1, 0}*y]

Out[7]= {y, 0}

or this

In[11]:= Assuming[{y \[Element] Vectors[2]}, 0*y]

Out[11]= 0
POSTED BY: Gianluca Gorni

One more workaround:

y = ArraySymbol["y", {2}];
NDSolve[{y'[t] == -{1, 0} y[t],
  y[0] == {1, 2}},
 y, {t, 0, 1}]
POSTED BY: Gianluca Gorni
Posted 22 days ago

Then how can you explain David's point?

POSTED BY: Yumin Tian

Setting the 0 term to a very small nonzero number appears to work:

POSTED BY: David Trimas

Not a great error message for the situation, but I don't think y'[t] == -{1, 0} * y[t], which evaluates to y'[t] == {-y[t], 0}, can be satisfied by any y[t] because the dimensions are different. So there is an error, and one should get an error message.

That said, a message like NDSolve::ndfdmc (about dimensions) would seem more appropriate. The source of the problem of getting the right message might be that at t == 0, NDSolve[] expects y'[0] to have the same dimensions as y[0] == {1, 1}; instead, the ODE sets y'[0] == {-y[0], 0} == {{-1, -1}, 0}; and finally, Dimensions[{1, 1}] and Dimensions[{{-1, -1}, 0}] are both {2} — that is, the computed dimensions agree even though the expressions are not compatible.

POSTED BY: Michael Rogers
Posted 23 days ago

No, y'[t] == {-y[t], 0} is a well-defined equation, because 0 can have any dimension. The point here is, the first equation by its form is a vector differential equation, so it should work in the same way as the second equation does, but the second works while the first doesn't. Also, as david noticed, it isn't a consistent behavior. So I think I should file a bug report, but how (if possible) to do that?

POSTED BY: Yumin Tian

Re "0 can have any dimension":

First, we should agree, which I think we do, that your actual input to NDSolve[] in your first example is NDSolve[{y'[t] == {-y[t], 0}, y[0] == {1, 1}}, y, {t, 0, 1}]. The ODE is y'[t] == {-y[t], 0} and the IC is y[0] == {1, 1}. This is what I'm discussing, and if we don't agree, then we're talking past each other.

I'm not sure that 0 can have any dimension is really true. There is an operation called threading that can make scalars seem so (e.g. Solve[{x, y} == 1], which is broken down as x == 1 and y == 1 as if Thread[{x, y} == 1] were the input). In your example, the problem is that NDSolve[] performs a sort of de-threading of the input you give. Threading is not invertible, so NDSolve[] must be de-threading by making certain assumptions. In some circumstances, "making certain assumptions" is called a bug. You can file a bug report by using the menu command in Mathematica: Help > Give Feedback... which will take you to a webform for reporting bugs. See the Appendix below for more on de-threading.

Anyway, back to "0 can have any dimension": Why do you think that? Perhaps it is something I don't know. I've always understood that some functions will thread scalars in some circumstances. Not every function handles threading the same (see the case study in the Appendix for an example).

@David Trimas's "workaround" points out the problem with de-threading. His example feels like a red herring to me, but it's not irrelevant. I don't know when the de-threading was implemented. I didn't notice it until I looked into David's example. The proper workaround, I would suggest, is the following use of Indexed[], which I suppose de-threading was implemented to make unnecessary:

NDSolve[{
  y'[t] == -{1, 0}*{Indexed[y[t], 1], Indexed[y[t], 2]},
  y[0] == {1, 1}},
 y, {t, 0, 1}]

@khan ali has kindly pointed out how the DiagonalMatrix approach is different from, and not inconsistent with, the vector approach.

Here are a couple more, rather recherché, workarounds. The second is faster, but it's based on the assumption that once the ODE is evaluated numerically, it is never again evaluated symbolically in the call to NDSolve[].

times // ClearAll;
times[a : __?(VectorQ[#, NumericQ] &)] := Times[a];
NDSolve[{
  y'[t] == -times[{1, 0}, y[t]],
  y[0] == {1, 1}},
 y, {t, 0, 1}]

times // ClearAll;
times[a : __?(VectorQ[#, NumericQ] &)] := (times = Times)[a];
NDSolveValue[{
  y'[t] == -times[{1, 0}, y[t]],
  y[0] == {1, 1}},
 y, {t, 0, 1}]

You say that the ODE is the vector form of the differential equation. I disagree that {-y[t], 0} is a vector, which is why I defined above what the ODE is in my view. A vector in Mathematica is a list of scalars and not, say, a column matrix, as it sometimes is in math&science. If {-y[t], 0} is a vector, then -y[t] should be a scalar. But it is itself a vector. It might be that "0 can have any dimension" is supposed to explain why {-y[t], 0} can be a vector. I can interpret that only as {-y[t], 0} is the same as the vector -y[t]. I don't see how the outer List[] can be stripped away, even if 0 can have any dimension. And {-y[t]} is a $1\times2$ matrix (tensor rank 2), not a vector. So y'[t], which is a 2-vector (tensor rank 1), cannot be equal to the matrix expression {-y[t]}; but if you de-thread {-y[t]} as {-1, -1} * y[t] when y[t] has the value of a numeric 2-vector {y1, y2}, then the de-threaded expression {-1, -1} * {y1, y2} evaluates to the 2-vector {-y1, -y2}.

In David's workaround, {-y[t], $MachineEpsilon*y[t]} is de-threaded and evaluated as a 2-vector as follows:

{-y[t], $MachineEpsilon * y[t]}     -->
  {-1, $MachineEpsilon} * y[t]      -->
  {-1, $MachineEpsilon} * {y1, y2}  -->
  {-y1, $MachineEpsilon * y2}

So the differential system being integrated in effect is ${y_1'(t)} = {-y_1(t)}$, $y_2'(t) = 2^{-52} y_2(t)$, where y[t] is represented by $\big({y_1}(t)$, ${y_2}(t)\big)$.

In the OP's first example, {-y[t], 0} is not de-threaded because (I assume) the second element does not have a factor of y[t] literally present(!): y[t] appears only in the first element. This is the source of the failure of NDSolve[] in this case.

Appendix

What is de-threading? How does it arise in NDSolve[]?

It's my name for a partial inverse operation to Thread[]. One might thread at multiple levels when a expressions consists of nested lists. I'll stick to the simple case of a vector list. Then the basic idea is that if y = dethread[x], then Thread[y] === x; the converse need not be true.

To see how NDSolve[] processes the ODE (when successful), one can inspect the numerical function it creates for evaluating y'[t]. When it's unsuccessful, no numerical function is created, as in the case of the OP's first example. The function expression shows the formula used, in which we can see the de-threading. The symbol y stands for the value of y[t]. I've shown the dimensions NDSolve[] is using: {} denotes that t is a scalar; {n} denotes that the argument y and the result (for y') is a vector of length n.

checkODE[args___] := Module[{state},
   {state} = NDSolve`ProcessEquations[args];
   Grid[
    {#, state["NumericalFunction"]@#} & /@ {"FunctionExpression", 
      "ArgumentDimensions", "ResultDimensions"}
    , Alignment -> Left, Spacings -> {1, Automatic}]
   ];

checkODE[ (* like David Trimas's ODE: y[t] in each component *)
 {y'[t] == {-1, -2, -3} y[t], y[0] == {1, 1, 1}}
 , y, {t, 0, 1}]
checkODE[ (* yet another workaround for the OP's problem *)
 {y'[t] == {-y[t], 2 (1 + y[t]) - 2 - 2 y[t]}, y[0] == {1, 1}}
 , y, {t, 0, 1}]
checkODE[ (* buggy!!! *)
 {y'[t] == -{t + 1, t + 2, t + y[t]}*y[t], y[0] == {1, 1, 1}}
 , y, {t, 0, 1}]

output

Note that the last example is a real bug. NDSolve[] produces a solution but the wrong one: The first two components in the wrong answer are identical since the function expression is missing part of each linear term:

-{t + 1, t + 2, t + y[t]}*y[t] - (* ODE's y'[t] *)
  (- t y + {0, 0, -1} y^2 /. y -> y[t]) // (* NumericalFunction's y' *)
 Simplify
(*  {-y[t], -2 y[t], 0}  <-- should be {0, 0, 0} *)

One might also question whether it is clear what the ODE y'[t] == -{t + 1, t + 2, t + y[t]}*y[t] is supposed to mean in the first place. I suppose a reasonable interpretation is that it represents the three independent 1D ODEs: $$y'=-(t+1)y \,,\quad y' = -(t+2)y \,,\quad y'=-(ty+y^2)\,.$$

Case study: Solve is more forgiving than DSolve, which in turn is more forgiving than NDSolve

NDSolve[{{x'[t], y'[t]} == 0     , {x[0], y[0]} == 0},
 {x, y}, {t, 0, 1}] (* failure: NDSolve::underdet *)
NDSolve[{{x'[t], y'[t]} == {0, 0}, {x[0], y[0]} == 0},
 {x, y}, {t, 0, 1}] (* failure: NDSolve::ndnco *)
NDSolve[{{x'[t], y'[t]} == {0, 0}, {x[0], y[0]} == {0, 0}},
 {x, y}, {t, 0, 1}] (* success *)

DSolve[{{x'[t], y'[t]} == 0     , {x[0], y[0]} == 0},
 {x, y}, {t, 0, 1}] (* failure: DSolve::nolist *)
DSolve[{{x'[t], y'[t]} == {0, 0}, {x[0], y[0]} == 0},
 {x, y}, {t, 0, 1}] (* success *)
DSolve[{{x'[t], y'[t]} == {0, 0}, {x[0], y[0]} == {0, 0}},
 {x, y}, {t, 0, 1}] (* success *)

Solve[{{x'[t], y'[t]} == 0     , {x[0], y[0]} == 0     }] (* success *)
Solve[{{x'[t], y'[t]} == {0, 0}, {x[0], y[0]} == 0     }] (* success *)
Solve[{{x'[t], y'[t]} == {0, 0}, {x[0], y[0]} == {0, 0}}] (* success *)

I'm not sure this ordering is always the case, but Solve[] is usually more robust than either of NDSolve[] or DSolve[].

POSTED BY: Michael Rogers
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard