Friday, 6 November 2015

ETA for C++

Simulations can take some time, and I'd like to know how long. This is easy, right? Yes, it is. I've done it lots of times, but every time I do, I curse myself for not using an old piece of code.
most likely, there is some standard, best way of doing this, but I haven't found it. Most recently, I did this: I made a simple object "EtaEstimator", that can be updated every (costly) time step and asked for an estimated time of "arrival" at any time. Here's the header:
// eta.hpp
#include <ctime>
#include <cmath> // floor
#include <iostream>

class EtaEstimator {
public:
    EtaEstimator(int ); 
    // constuction starts the clock. Pass the number of steps
    void update();
    void print(std::ostream & ) const;
private:
    double ct, etl; // cumulative time, estimated time left
    int n, N; // steps taken, total amount of steps
    clock_t tick; // time after update ((c) matlab)
    // statics...
    static const int secperday = 86400;
    static const int secperhour = 3600;
    static const int secperminute = 60;
};

std::ostream & operator<<(std::ostream & , const EtaEstimator & );
The members are straight forward too:
// eta.cpp
#include "eta.hpp"

EtaEstimator::EtaEstimator(int N) :
        ct(0.0), etl(0.0), n(0), N(N) {
    tick = clock();
}

void EtaEstimator::update() {
    clock_t dt = clock() - tick;
    tick += dt;
    ct += (double(dt)/CLOCKS_PER_SEC); // prevent integer division
    // CLOCKS_PER_SEC is defined in ctime
    ++n;
    etl = (ct/n) * (N-n);
}

void EtaEstimator::print(std::ostream & os) const {
    double etlprime = etl;
    int days = floor(etlprime / secperday);
    etlprime -= days * secperday;
    int hours = floor(etlprime / secperhour); 
    etlprime -= hours * secperhour;
    int minutes = floor(etlprime / secperminute);
    etlprime -= minutes * secperminute;
    int seconds = floor(etlprime);
    os << (days > 0 ? std::to_string(days) + " " : "")
       << hours << ":" 
       << (minutes < 10 ? "0" : "") << minutes << ":" 
       << (seconds < 10 ? "0" : "") << seconds;
}

std::ostream & operator<<(std::ostream & os,
        const EtaEstimator & eta) {
    eta.print(os);
    return os;
}
Typical usage of EtaEstimator would be the following:
#include <iostream>
#include "eta.hpp"

// about to do lots of work...
int N = 1000;
EtaEstimator eta(N);
for ( int n = 0; n < N; ++n ) {
    // do something very expensive
    eta.update()
    std::cout << "\rETA: " << eta << std::flush;
}
// ...
PS: std::to_string is a C++11 feature, and can be ignored by using something like
if ( days > 0 ) os << days << " "; // else nothing at all