OpenPFC / A visible simulation loop
Let the driver tell the step how far to go
The final clock interval can be shorter than the requested timestep. The numerical update has to know that.
Download runnable lesson (.zip)
Includes the model, inputs, required headers and a short run guide.
This refactoring of 05_simulator separates the time loop from a small spectral heat update. The problem is one cosine mode, \(u(x,0)=1+.2\cos x\), repeated in the two transverse directions. Its amplitude makes the effect of every accepted interval visible.
Give the driver a step and three hooks
pfc::sim::run(
time,
[&](double, double interval) {
fft.forward(u.vec(), modes);
for (std::size_t i = 0; i < modes.size(); ++i)
modes[i] /= 1 + interval * k2[i];
fft.backward(modes, u.vec());
amplitude /= 1 + interval;
event("step", interval);
},
[&](const auto &) { event("start"); }, [&](const auto &) { event("apply"); },
[&](const auto &) { event("save"); });The native driver calls start once, performs an initial save when due, then advances the clock before apply, step and any due save. The two-argument step receives the current time and the actual clock advance. apply is a place for time-dependent conditions; this example records the hook without changing the field.
The default dt: .07 and end: .2 produce intervals .07,.07,.06. For backward Euler, the mode’s final amplitude is
\[ A=.2\prod_m(1+\Delta t_m)^{-1}. \tag{1}\]
Reusing a multiplier prepared for .07 on the last interval would be a stable computation of the wrong elapsed time.
A save request is not automatically a step boundary
saveat: .1 supplies output opportunities to the fixed-step Time driver; it does not split .07 steps at time .1. Read the actual save rows in results/events_rank_*.csv. For explicit output-time clipping and rejected attempts, compare the transactional clock lesson.
The historical one-argument step callback is also supported, but it cannot see a clipped final interval. A fixed-dt method used through that interface needs a run whose every interval really equals its private dt.
Read the ledger and the field together
openpfc run model.cpp input.json --launcher=noneEach event records its hook, step, accepted time, interval and expected discrete mode amplitude. results/field_rank_*.csv contains the actual final owned field. The tests check the hook ordering, the clipped final interval and every field sample on one and two ranks.
Try an end time shorter than dt, keeping saveat no greater than that end time, then one that is an exact multiple. Predict the multiplier product before running. Keep the source and input together.