Skip to content

Instantly share code, notes, and snippets.

@sekrasoft
Created December 29, 2023 14:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sekrasoft/896c77a78717cce9b326d6be97af6e31 to your computer and use it in GitHub Desktop.
Save sekrasoft/896c77a78717cce9b326d6be97af6e31 to your computer and use it in GitHub Desktop.
Watch accuracy monitoring

Overview

This is a python script allowing a user to monitor and measure their watch accuracy.

The script analyzes a data file watch1.txt containing the next human readable data:

# some comment lines starting with '#'
--- YYYY-MM
=== space separated watch list
DDHH diff1[/corr1] diff2[/corr2] ...
DDHH diff1[/corr1] diff2[/corr2] ...
...

=== another watch list
DDHH diff3[/corr3] ...
...

--- YYYY-MM
DDHH diff3[/corr3] ...
...

The file shall contain

  1. Month directive (e.g. --- 2023-08) that activates the chosen month until the next month directive appears
  2. Watch list (e.g. === watch1 watch2 watch3) that activates the list until the next watch list appears
  3. Data point (e.g. 2010 +0.5 -65/+60 -3) containing day, hour and space separated measurements consisting of the time difference (e.g. the actual time is 10:00:00, the watch shows 09:58:55 so the difference is -65) and the user correction if the watch is corrected after the time measurement (e.g. seeing 09:58:55 and adding a minute yields -65/+60). A data point could be skipped. In this case just leave a minus sign instead the measurement.

One can monitor multiple watch sets at the time, mention months and watches in any order and multiple times. However, the data points of the same watch shall be sorted by time.

The example could be found in watch.txt.

The script calculates the average accuracy and its error. The accuracy value is defined as a1 for linear approximation a1 t + a0 of the measures accuracy data. The error is defined as (sigma + 1) / (measurement period) where sigma is RMS of the approximation deviation from the measured accuracy data.

Executing the Script

  1. Drawing graphs for all mentioned watches:
    # cd to the directory containing watch.py
    python watch1.py
    
  2. Drawing graphs for the chosen watches:
    # cd to the directory containing watch.py
    python watch1.py Watch3 Watch4 Watch5
    
    Output:
    Watch3                          0.16 ±  0.05
    Watch4                          0.08 ±  0.05
    Watch5                          0.29 ±  0.09
    

Requirements

  1. python interpreter
  2. numpy and matplotlib packages
import re, time, datetime, numpy, math, sys
class Stats:
def __init__(self, name):
self.name = name
self.shift = 0.
self.stamps = []
self.values = []
def add(self, date, value, corr=None):
#print('%-30s %s %s %s' % (self.name, date, value, corr))
self.stamps.append(date)
self.values.append(value + self.shift)
if corr:
self.shift -= corr
def fit(self, points=False):
if len(self.stamps) < 2:
if points: return [], []
return 0, 0, -1
day = float(60*60*24)
stamp0 = time.mktime(self.stamps[0].timetuple()) / day
stamps = [time.mktime(d.timetuple()) / day - stamp0 for d in self.stamps]
value0 = self.values[0]
values = [v - value0 for v in self.values]
a, b = numpy.polyfit(stamps, values, 1)
if not points:
dv = 0.5 + math.sqrt(sum((v - (a * s + b))**2 for v, s in zip(values, stamps)) / len(stamps))
return a, b, 2*dv / (stamps[-1] - stamps[0])
return [self.stamps[0], self.stamps[-1]], [a * stamps[0] + b + value0, a * stamps[-1] + b + value0]
def middlestamp(self):
stamp0 = time.mktime(self.stamps[0].timetuple())
stamp1 = time.mktime(self.stamps[-1].timetuple())
return (stamp0 + stamp1) // 2
stats = {}
allowed = set(sys.argv[1:])
with open("watch1.txt", "r") as f:
names = None
stampprefix = None
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
if line.startswith('==='):
names = line.split()[1:]
for name in names:
if name not in stats:
stats[name] = Stats(name)
continue
if line.startswith('---'):
stampprefix = line.split(None, 1)[1]
continue
try:
data = line.split()
stamp = '%s-%s %s' % (stampprefix, data[0][:2], data[0][2:])
date = datetime.datetime.strptime(stamp, '%Y-%m-%d %H')
assert(len(names) == len(data)-1)
for name, info in zip(names, data[1:]):
if info == '-' or (allowed and name not in allowed):
continue
m = re.match(r'^([-+]?\d+(?:\.\d*)?)(?:\/([-+]?\d+(?:\.\d*)?))?$', info)
v = float(m.group(1))
if m.group(2): c = float(m.group(2))
else: c = None
stats[name].add(date, v, c)
except:
print('bad line: %s' % (line,))
raise
classes = {}
day = 60*60*24
month = 30 * day
for s in stats.values():
a, b, da = s.fit()
if da < 0: continue
sid = int(math.log(abs(a) + da)), s.middlestamp() // 9 // month
classes.setdefault(sid, []).append((s, '%s: %.2f ± %.2f s/d' % (s.name, a, da)))
print('%-30s %5.2f ± %5.2f' % (s.name, a, da))
import matplotlib.pyplot as plt
fig, axes = plt.subplots(nrows=len(classes), ncols=1)
if len(classes) == 1: axes = [axes]
plt.subplots_adjust(left=0.05, bottom=0.05, right=0.99, top=0.99, wspace=None, hspace=None)
for i, (cls, arrays) in enumerate(classes.items()):
ax = axes[i]
ax.set_xlabel('time')
ax.set_ylabel('error (seconds)')
for s, label in arrays:
ax.plot(s.stamps, s.values, 'o-', label=label)
ax.plot(*s.fit(True), '-')
ax.legend()
plt.show()
--- 2023-08
=== Automatic Electronic
2500 - +0.2
2902 -32 +0.5
2922 -43 +0.5
--- 2023-09
0500 - +0.8
0200 -32 -
0703 -72 +1.1
1123 -63/+60 +1.0
1302 -12 +1.0
1321 -15 +1.1
1504 - +1.3
--- 2023-12
=== Watch3 Watch4
0412 +2 +1.5
1202 +3.5 +2
=== Watch3 Watch4 Watch5
1518 +4 +2.5 +11.5
2113 +5 +3 +13.5
2914 +6 +3.5 +15.5
=== Watch6 Watch7 Watch8
1202 -6 -8 +96
1518 -8 +40 +61
2113 -11 +88 +126
2914 -16 +20 -
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment