OpenPFC / Build a simulation
A clock should advance only once
Separate proposed steps, rejected attempts, accepted time and output boundaries.
Download runnable lesson (.zip)
Includes the model, inputs, required headers and a short run guide.
A proposed step, a trial state and an accepted simulation time are separate objects. This refactors the time utility example so its output schedule is observable even when the nominal step does not divide the save interval. There is no differential equation in this model: it isolates the clock.
Reject an attempt without moving the clock
time.begin_attempt(dt);
const double first = time.get_attempted_dt();
time.reject_attempt();
time.increment_step_rejection();
output << "rejected,0," << time.get_current() << ',' << first << ','
<< time.do_save() << '\n';The clock remains at zero and its increment remains zero. The rejection counter is recorded explicitly. A rejected attempt is not an output event, even if do_save() is still true at the unchanged initial time.
Land on a save boundary
With a proposed \(h=0.13\) and saveat=0.2, the first accepted step ends at 0.13 and the second is shortened to 0.07. They end at 0.13 and 0.2 rather than jumping past the requested output time. The same clipping handles the final time.
while (!time.done()) {
time.begin_attempt(dt);
const double attempted = time.get_attempted_dt();
// Advance the model by `attempted` here. This lesson isolates the clock.
time.commit_attempt();
time.increment_step_success();
output << "accepted," << time.get_increment() << ',' << time.get_current()
<< ',' << attempted << ',' << time.do_save() << '\n';
}A real model must integrate with get_attempted_dt(), then commit its field and clock consistently. get_dt() describes a proposal, not necessarily the interval just taken. The example calls the success counter only after a commit; the adaptive controller handles those counters itself in the adaptive RK lesson.
Inspect the accepted schedule
openpfc run model.cpp input.json --source-root .. --launcher=noneresults/clock.csv labels initial, rejected and accepted records, with the actual interval and save query. Change dt and saveat independently. Check that saves occur at 0, .2, .4, .6, .8 and 1 for the default configuration. The regression checks the complete ten-step accepted schedule and the unchanged rejected state. The clock does not write field files: call your writer only after an appropriate accepted output event. The public Time contract explains the fixed-step next() path and attempt transactions.
The complete C++ model and starting input are available with this lesson. It also uses the shared lesson.hpp; keep its relative location when saving the files.