4

I want to implement a physics engine in a game in order to compute trajectories of bodies with forces applied to them. This engine would calculate each state of the object based on its previous state. Of course this means a lot of calculation between two units of time to be sufficiently precise.

To do that properly, I wanted first to know how big are the differences between this method of getting positions, and with kinematic equations. So I made this code which stores the positions (x, y, z) given by the simulations and by the equations in a file.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "header.h"


Body nouveauCorps(Body body, Vector3 force, double deltaT){
    double m = body.mass;
    double t = deltaT;

    //Newton's second law:
    double ax = force.x/m;
    double ay = force.y/m;
    double az = force.z/m;

    body.speedx += ax*t;
    body.speedy += ay*t;
    body.speedz += az*t;

    body.x +=t*body.speedx;
    body.y +=t*body.speedy;
    body.z +=t*body.speedz;

    return body;
}

int main()
{
    //Initial conditions:
    double posX = 1.4568899;
    double posY = 5.6584225;
    double posZ = -8.8944444;
    double speedX = 0.232323;
    double speedY = -1.6565656;
    double speedZ = -8.6565656;
    double mass = 558.74;

    //Force applied:
    Vector3 force = {5.8745554, -97887.568, 543.5875};

    Body body = {posX, posY, posZ, speedX, speedY, speedZ, mass};

    double duration = 10.0;
    double pointsPS = 100.0; //Points Per Second
    double pointsTot = duration * pointsPS;

    char name[20];
    sprintf(name, "BN_%fs-%fpts.txt", duration, pointsPS);

    remove(name);
    FILE* fichier = NULL;
    fichier = fopen(name, "w");

    for(int i=1; i<=pointsTot; i++){
        body = nouveauCorps(body, force, duration/pointsTot);
        double t = i/pointsPS;

        //Make a table: TIME | POS_X, Y, Z by simulation | POS_X, Y, Z by modele (reference)
        fprintf(fichier, "%e \t %e \t %e \t %e \t %e \t %e \t %e\n", t, body.x, body.y, body.z, force.x*(t*t)/2.0/mass + speedX*t + posX, force.y*(t*t)/2.0/mass + speedY*t + posY, force.z*(t*t)/2.0/mass + speedZ*t + posZ);
    }
    return 0;
}

The problem is that with simple numbers (like with a simple fall in a -9.81 gravity field) I got nice positions, but with bigger (and quite random) numbers, I get inaccurate positions.

Is that a floating point issue?

Here are the results, with relative errors. (Note: label axes are in French, Temps = Time).

Graphs

  • Black+dashed : values from kinematic equations
  • Red : 100 points per second
  • Orange : 1000 points per second
  • Green : 10000 points per second
Paul Floyd
  • 5,530
  • 5
  • 29
  • 43
T0T0R
  • 153
  • 6
  • That is a common when performing floating-point calculations – eyllanesc Jul 19 '17 at 20:19
  • 1
    See https://stackoverflow.com/questions/588004/is-floating-point-math-broken and https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html – Jesper Juhl Jul 19 '17 at 20:19
  • 2
    I think it is rather a simulation issue, than a floating point issue. For example, you can improve simulation, if you use the `pos+=v_old*t+a/2*t^2` formula. Another tip is to decrease deltaT. If the simulation gets better, then it is certainly a simulation issue. – geza Jul 19 '17 at 20:24
  • 1
    You may also want to change the [numerical method](https://en.wikipedia.org/wiki/Numerical_methods_for_ordinary_differential_equations) you are using. – Bob__ Jul 19 '17 at 20:24
  • If you want a nice demonstration, try computing an object on a circular path (centripetal force acting perpendicular to the current velocity at all points). See what you get... – EOF Jul 19 '17 at 20:25
  • 1
    This is not a floating point issue. For forward Euler (the integration scheme you're currently using), the cumulative error is `O(Δt)` which lines up closely with your plot of the error (it goes down by a factor of 10 in each case because you're reducing the step size by a factor of 10 in each case). This is a fundamental issue with numerical integration. – Kyle Jul 19 '17 at 20:39
  • BTW, are you programming in C or C++? – Bob__ Jul 19 '17 at 21:14

1 Answers1

6

This is not a floating point issue. In fact, even if you were using exact arithmetic you'd see the same problem.

This error is really fundamental to numerical integration itself and the particular method you're using and the ODE you're solving.

In this case you're using an integration scheme known as Forward Euler. This is probably the simplest approach to solving a first-order ODE. Of course, this leaves it with some undesirable features.

For one, it introduces error at each step. The size of the error is O(Δt²). That means the error over a single time step is roughly proportional to the square of the size of the time step. So if you cut the size of the time step in half, roughly you drop the incremental error to 1/4 the value.

But since you decrease the time step, you have to make more steps to simulate the same amount of time. So you're adding up more but smaller errors. This is why the cumulative error is O(Δt). So really over the whole simulated time if you take time steps that are half as big, you get half as much cumulative error.

Ultimately this cumulative error is what you're seeing. And you can see in your error plot that the ultimate error ends up decreasing by about a factor of 10 each time you increase the number of time steps by a factor of 10: because the time step is 10 times smaller, so the total error ends up about 10 times smaller.


The other issue is that Forward Euler exhibits what's known as conditional stability. This means it's possible for the cumulative error to grow without bound in certain cases. To see why, let's look at a simple ODE:

x' = -k * x

Where k is some constant. The exact solution of this ODE is x(t) = x(0) * exp( -k * t ). So as long as k is positive, x should tend to 0 as time increases.

However, if we try to approximate this using Forward Euler, we get something that looks like this:

x(t + Δt) = x(t) + Δt * ( -k * x[n] )
          = ( 1 - k * Δt ) * x(t)

This is a simple recurrence relation that we can solve:

x(t) = ( 1 - k * Δt )^(t / Δt) * x(0)

Now, we know the exact solution tens to 0 as t gets larger. But the Forward Euler solution only does that if |1 - k * Δt| < 1. Notice how that expression depends on the step size as well as the k term from our ODE. If k is really really big, we need a really really tiny time step to keep the solution from blowing up. This is why it possesses what's known as conditional stability: the stability of the solution is conditional on the time step.

There are also a number of other issues, but this is a broad topic and I can't cover everything in a single answer.

Kyle
  • 6,500
  • 2
  • 31
  • 41
  • This seems to me a bit complicated because I haven't learn yet about differential equations... If I understand wou well, decreasing Δt (which, in green, is at 0.0001) won't change errors that much if I keep it in a reasonable range (for a game). So I need to focus on an other method to solve my equations ? – T0T0R Jul 20 '17 at 11:01
  • @T0T0R More specifically you need to understand that outside of very narrow circumstances (i.e. when you can get an exact solution) there will always be some amount of error in your solution. – Kyle Jul 20 '17 at 14:14