Outputs occurring at incorrect intervals when the timestep is high-precision

Nature of bug

When I set the timestep to be a high-precision number (dt = 0.042735042735042736, pseudo-dt = 0.004273504273504274) and run the simulation for a long-enough time (tend = 1000.0), eventually the times when the .pyfrs files are dumped stop being multiples of dt_out (in this case dt_out = 5.0). In the error log below, the issue first occurs at time 520.04, with the distance to the nearest multiple of dt_out growing from there. Note that dt_out / dt = 117.0 in double precision.

Here are links to the .ini file and to the .pyfrm file (although this does not seem to depend on the latter). Restarting at the last timestep that was still a multiple of dt_out does not fix the issue.

Error print out (if applicable)

root@2e3ab1618ab8:/workspace/bug# pyfr run -b cuda mesh.pyfrm inc-flow.ini
root@2e3ab1618ab8:/workspace/bug# ls -l inc-flow-5*.pyfrs
-rw-r--r-- 1 root root 801672 Feb 20 01:10 inc-flow-5.00.pyfrs
-rw-r--r-- 1 root root 801679 Feb 20 01:11 inc-flow-50.00.pyfrs
-rw-r--r-- 1 root root 801685 Feb 20 01:24 inc-flow-500.00.pyfrs
-rw-r--r-- 1 root root 801685 Feb 20 01:24 inc-flow-505.00.pyfrs
-rw-r--r-- 1 root root 801685 Feb 20 01:24 inc-flow-510.00.pyfrs
-rw-r--r-- 1 root root 801683 Feb 20 01:25 inc-flow-515.00.pyfrs
-rw-r--r-- 1 root root 801685 Feb 20 01:25 inc-flow-520.04.pyfrs
-rw-r--r-- 1 root root 801684 Feb 20 01:25 inc-flow-525.09.pyfrs
-rw-r--r-- 1 root root 801684 Feb 20 01:25 inc-flow-530.13.pyfrs
-rw-r--r-- 1 root root 801684 Feb 20 01:25 inc-flow-535.17.pyfrs
-rw-r--r-- 1 root root 801683 Feb 20 01:25 inc-flow-540.21.pyfrs
-rw-r--r-- 1 root root 801684 Feb 20 01:25 inc-flow-545.26.pyfrs
-rw-r--r-- 1 root root 801679 Feb 20 01:11 inc-flow-55.00.pyfrs
-rw-r--r-- 1 root root 801685 Feb 20 01:26 inc-flow-550.30.pyfrs
-rw-r--r-- 1 root root 801684 Feb 20 01:26 inc-flow-555.34.pyfrs
-rw-r--r-- 1 root root 801686 Feb 20 01:26 inc-flow-560.38.pyfrs
-rw-r--r-- 1 root root 801686 Feb 20 01:26 inc-flow-565.43.pyfrs
-rw-r--r-- 1 root root 801685 Feb 20 01:26 inc-flow-570.47.pyfrs
-rw-r--r-- 1 root root 801686 Feb 20 01:26 inc-flow-575.51.pyfrs
-rw-r--r-- 1 root root 801685 Feb 20 01:26 inc-flow-580.56.pyfrs
-rw-r--r-- 1 root root 801684 Feb 20 01:27 inc-flow-585.60.pyfrs
-rw-r--r-- 1 root root 801687 Feb 20 01:27 inc-flow-590.64.pyfrs
-rw-r--r-- 1 root root 801686 Feb 20 01:27 inc-flow-595.68.pyfrs

PyFR information

  • PyFR version: 2.1
  • OS: Ubuntu 22.04
  • Compiler and version: GNU Make 4.3
  • Backend (if applicable): CUDA 12.2

This is mostly expected behaviour. PyFR aims to hit the first time t after the time requested by the plugin. For a plugin time of t = 545.0 this happens to be 545.0427350427318. This is an unfortunate consequence of double precision arithmetic and the fact that with dual-time there is currently no support for changing dt to exactly hit plugin times (although there is a long standing PR for this, see Support for variable time stepping by sambitmishra98 · Pull Request #495 · PyFR/PyFR · GitHub).

Regards, Freddie.

Got it, thanks. For my purposes (fixed high-precision dt) I’ve added a check before running to warn if this issue arises before tend. In case it’s useful for others, here is what I’m using:

def actual_dt_out_matches_expected(tstart, tend, dt, dt_out, dt_min=1e-12, rounding=2):

    expected = []
    tcurr = tstart
    while tcurr <= tend:
        expected.append(tcurr)
        tcurr += dt_out

    actual = [tstart]
    tcurr = tstart
    tout_last = tstart
    tol = 5 * dt_min
    while tcurr <= tend:
        tcurr += dt
        if tcurr - tout_last >= dt_out - tol:
            actual.append(tcurr)
            tout_last = tcurr

    return expected == [round(t, rounding) for t in actual]

I’ll look into using Kahan summation for the next major release.

Regards, Freddie.