Source code for reiz.time

# -*- coding: utf-8 -*-
"""A clock based on LSL local_clock
"""
from pylsl import local_clock


[docs]class Clock(): """allows to keep track of time reset the clock with :meth:`~.reset`, and get the time since this reset with :meth:`~.now`. Measure intermediate times with :meth:`~.tick`, which return time since last call of :meth:`tick`, or each call of :meth:`~.tick` updates the cumulative time since the last call of :meth:`reset`. If you don't want to add the time measured, use :meth:`~.pause` """ def __init__(self): "create a new instance requiring no arguments" self._error = 0. self.reset()
[docs] def time(self): "time in seconds (usually since computer boot) according to LSL" return local_clock()
[docs] def tick(self) -> float: """time since last call of :meth:`~.tick` returns ------- delta_t: float time passed in seconds since the last call of :meth:`~.tick` or :meth:`~.pause`. This is time counting into the cumulative time """ ts = self.time() delta_t = ts - self.last_tick self.last_tick = ts return delta_t
[docs] def sleep(self, duration: float): """Sleep for duration in seconds blocking is slightly more accurate than non-blocking sleep, e.g. as available with :meth:`time.sleep` from the stdlib. There is one disadvantage of this kind of busy sleep: it can cause slight oversleeping, as there is an overhead of the function being called and returning, which is not accounted for. See :meth:`~.sleep_debiased` for an alternative sleep with asymptotic minimisation of the error. args ---- duration: float how many seconds to sleep blocking returns ------- duration: float the time in seconds spent sleeping """ t1 = t0 = self.time() dt = 0 while dt <= duration: t1 = self.time() dt = t1-t0 return dt
[docs] def sleep_debiased(self, duration: float): """Sleep for duration in seconds with attempts for debiasing` sometimes, you execute some other commands, and these commands have a variable runtime. If we would naively sleep everytime for n seconds afterwards, we would inherit this jitter. By using :meth:`~.tick` before these commands, and :meth:`~.sleep_debiased` after these commands, we can normalize the runtime to a fixed period (as long as the sleep duration is longer than the runtime of the commands). Additionally, this function keeps track of any oversleeping or undersleeping, and will minimize the temporal error asymptotically returns ------- duration: float the time in seconds spent sleeping since the last call of :meth:`~.tick` or :meth:`~.sleep_debiased`. Example ------- This example shows how we can regularize the time spent in each cycle to 200ms in spite of there being an element of random runtime .. code-block:: python import time import random from reiz.time import Clock clock = Clock() t = 0. msg = "{0:3.5f}, {1:3.5f}, {2:3.5f}, slept for {3:3.5f}s" for i in range(1, 11): time.sleep(random.random()/10) dt = clock.sleep_debiased(0.2) t += dt print(msg.format(i*0.2, clock.now(), t, dt)) """ bias = self.tick() dt = self.sleep(duration - bias - self._error) self._error += dt + bias - duration tick_bias = self.tick()-dt self._error += tick_bias return dt + bias + tick_bias
[docs] def reset(self): """reset the clock resets the counter keeping track of the cumulative time spend since instantiaion or the last call of :meth:`~.reset` """ self._t0 = self.last_tick = self.time()
[docs] def now(self): """return the cumulative time passed since the last call of :meth:`reset` """ return self.time()-self._t0
clock = Clock() #: a default :class:`.Clock` instance ready for your experiment