1

I often have to code explicit schemes which means that I have to look at the evolution of a function by incrementing the time variable t <- t+ dt. It is therefore only natural to have my loops increment on dt:

int N = 7;
double t=0., T = 1., dt=T/N; // or I could have dt=0.03 for example
for(; t<T; t+= dt){
   if(T - t < dt){
      dt = T-t;
   }
   //some functions that use dt, t, T etc
} 

The rationale behind this is that I'm incrementing t by a constant dt at each step, except at the last iteration, where if my current time t is such that T- dt < t < T then I modify my time increment by dt <- T-t.

What are the possible pitfalls of such a procedure or ways I could improve it? I do realise that I might get a very small time increment.

Are there any floating problems that might appear (should I stick to incrementing on integers)?

In terms of optimisation, I assume that this technique is not costly at all, since a basic branch prediction would almost always skip the if block.

EDIT

I realise my question wasn't really good. Usually the dt is given by a CFL condition i.e. it is given so that it is small enough compared to some other parameters.

So from a logical point of view, dt is first given and afterwards we can define an integer N=floor(T/dt), loop with integers up to N, then deal with the leftover time interval N*dt --- T.

The code would be:

double dt = //given by some function;
double t=0., T = 1.;
for(; t<T; t+= dt){
   if(T - t < dt){
      dt = T-t;
   }
   //some functions that use dt, t, T etc
} 
Community
  • 1
  • 1
user3371583
  • 191
  • 9
  • 1
    Double and floating comparisons are not that easy. Take a look at [this](http://stackoverflow.com/questions/17333/most-effective-way-for-float-and-double-comparison) – Richard Dally Jul 30 '15 at 12:07
  • @LeFlou I think that question tries to determine when two doubles are the equal or kinda equal. In my case, I'm just determining which one is larger. It's true that I suppose that after the operations `dt=T-t;` and `t += dt` then `t < T` is false. – user3371583 Jul 30 '15 at 12:28
  • You may want to skip the last iteration if`T-t` is extremely small, in the rounding error range. It will probably not be very meaningful. – Patricia Shanahan Jul 30 '15 at 18:53

2 Answers2

0

First at all the compensation if (T - t < dt) is not needed, because it's only purpose appears to set the last value to t == T, which won't be processed due to inequality ...;t < T;... in the for loop condition.

That being said, finite difference method doesn't work that well with floats unless N is a power of two. If e.g. one wishes to evaluate a function at steps of 0.1f, one will most likely miss a few integral points.

Branch prediction may skip the condition evaluation, but there may also be a penalty/latency in mixing floating point operations with flow control operations.

Due to the cumulating rounding errors, it's possible that the iteration count can't be easily determined by the optimizer, disallowing some optimizations (loop unrolling or even vectorizing).

The inaccuracy can be mitigated simply by linear interpolation: t = c * dt;, but not perfectly, because not for all cases (dbl / N) * N == dbl. In practice the error should be in the epsilon magnitude. To get exact ending value, one has to calculate t = (range * N) / N; this time making sure range * N doesn't drop the least significant bits.

Aki Suihkonen
  • 19,144
  • 1
  • 36
  • 57
  • I'm very sorry. I should have said that the `dt` is given to us by a CFL condition and afterwards we might come up with an `N` to deal with the time interval `N*dt ____ T`. We very rarely have `T/dt` to be an integer in infinite precision. And since the CFL is already restrictive, it is too penalising to pick a smaller `dt` (than the one given by the CFL) that would result in `T/dt` that is a power of two in infinite precision. Again very sorry, I should have stressed the important bits. – user3371583 Jul 30 '15 at 13:13
0

With the new information that dt must be set to a fixed, predetermined value (at least for all but the last step), here is my recommendation:

double T0 = 0.0;
double T  = 1.0;
int    N  = floor((T - T0)/dt);
double t  = T0;
for (int step_number = 0; step_number < N; ++step_number, t += dt)
{
  t = T0 + step_number * dt;
  do_one_step(t, T, dt);
}
if (t < T)
{
  do_one_step(t, T, T - t);
}

The function do_one_step performs the necessary calculations using t, T, and dt for each iteration. The data that must be updated by the function can either be made member variables of the class or can be included in the function parameter list as non-const references.

By the way, I have the last call to the function outside the loop not in order to save the possible cost of the branch condition, but because I find the code to be better organized and easier to understand that way.


The old answer:

You can easily get a very small time increment at the end, as you say, because the result T/N is typically not exact (and 0.03 certainly is not exact).

I would prefer to develop t and dt like this:

int    N  = 7;
double t  = 0.0;
double T  = 1.0;
double dt = (T - t)/N;
for (int step_number = 0; step_number < N; ++step_number, t += dt)
{
  // ... calculations with t, T, dt, etc.
}

(Notice that this says dt = (T - t)/N just in case you ever decide to start the iterations at a non-zero value of t.)

Alternatively, and potentially slightly more accurate if N is very large (because t += dt effectively has to round off dt as soon as t gets much larger):

int    N  = 7;
double T  = 1.0;
double dt = T/N;
for (int step_number = 0; step_number < N; ++step_number)
{
  double t = T0 + step_number * dt;
  // ... calculations with t, T, dt, etc.
}
David K
  • 3,147
  • 2
  • 13
  • 19
  • I apologise. I don't know what I was thinking, but the fact that `dt` is first defined and afterwards we can come up with an `N` is important in this context. First we think of a `dt` and afterwards we come up with an sufficient number of iterations that is sufficient. I made it seem like we are given `N` at the beginning but that is not the case. Sorry again. – user3371583 Jul 30 '15 at 13:05
  • The `t+=dt` in the loop looks like an artifact :P. You also mention that it has to round off. Can you explain that statement? Under what conditions would that happen? My reasoning: If say `t` is of order 1 while `dt` is of order `1e-10` that would still leave about 5 significant figures in `dt`. It also means that we're performing about `10^10` iterations which is a significant number, larger than what one could store in an `uint N`. – user3371583 Jul 30 '15 at 13:57
  • @user3371583 The roundoff (if there is any) is on the order of magnitude you described. If you don't care about the last few digits of `t` (and quite possibly you can afford not to), then you can afford not to do something that is only "slightly more accurate". On the other hand, the second method allows an integer loop counter without also having `t+=dt`; I don't particularly like the `++step, t+=dt` either (though it is a common programming practice; an _artifact_ would be an improper _output_ of the program). – David K Jul 31 '15 at 01:54