Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 29 additions & 19 deletions src/vse_sync_pp/analyzers/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,21 +379,29 @@ def _test_common(self, data):
return (False, "short test duration")
if len(data) - 1 < self._duration_min:
return (False, "short test samples")
if self._rate is None:
self._rate = self.calculate_rate(data)
if self._lpf_signal is None:
self._lpf_signal = calculate_filter(data, self._transient, self._rate)
return None

def _explain_common(self, data):
if len(data) == 0:
return {}
if self._rate is None:
self._rate = self.calculate_rate(data)
self._rate = self.calculate_rate(self._data)
if self._lpf_signal is None:
self._lpf_signal = calculate_filter(data, self._transient, self._rate)
return None

def toplot(self):
self.close()
self._generate_taus()
yield from zip(self._taus, self._samples)

def _generate_taus(self):
if self._rate is None:
self._rate = self.calculate_rate(self._data)
if self._lpf_signal is None:
self._lpf_signal = calculate_filter(self._data, self._transient, self._rate)
return None


class TimeDeviationAnalyzerBase(TimeIntervalErrorAnalyzerBase):
"""Analyze Time Deviation (TDEV).
Expand All @@ -413,11 +421,15 @@ def __init__(self, config):
# TDEV samples
self._samples = None

def _generate_taus(self):
super()._generate_taus()
if self._samples is None:
self._taus, self._samples, errors, ns = allantools.tdev(self._lpf_signal, rate=self._rate, data_type="phase", taus=self._taus_list) # noqa

def test(self, data):
result = self._test_common(data)
if result is None:
if self._samples is None:
self._taus, self._samples, errors, ns = allantools.tdev(self._lpf_signal, rate=self._rate, data_type="phase", taus=self._taus_list) # noqa
self._generate_taus()
if out_of_range(self._taus, self._samples, self._accuracy, self._limit):
return (False, "unacceptable time deviation")
return (True, None)
Expand All @@ -426,14 +438,11 @@ def test(self, data):
def explain(self, data):
analysis = self._explain_common(data)
if analysis is None:
if self._samples is None:
self._taus, self._samples, errors, ns = allantools.tdev(self._lpf_signal, rate=self._rate, data_type="phase", taus=self._taus_list) # noqa
self._generate_taus()
return {
'timestamp': self._timestamp_from_dec(data.iloc[0].timestamp),
'duration': data.iloc[-1].timestamp - data.iloc[0].timestamp,
'tdev': self._statistics(self._samples, 'ns'),
'tdev_taus': self._taus.tolist(),
'tdev_samples': self._samples.tolist(),
}
return analysis

Expand All @@ -446,8 +455,8 @@ class MaxTimeIntervalErrorAnalyzerBase(TimeIntervalErrorAnalyzerBase):
"""
def __init__(self, config):
super().__init__(config)
# required system maximum time interval error output in us
self._accuracy = config.requirement('maximum-time-interval-error-in-locked-mode/us')
# required system maximum time interval error output in ns
self._accuracy = config.requirement('maximum-time-interval-error-in-locked-mode/ns')
# limit of inaccuracy at observation point
self._limit = config.parameter('maximum-time-interval-error-limit/%')
# list of observation windows intervals to calculate MTIE
Expand All @@ -456,11 +465,15 @@ def __init__(self, config):
# MTIE samples
self._samples = None

def _generate_taus(self):
super()._generate_taus()
if self._samples is None:
self._taus, self._samples, errors, ns = allantools.mtie(self._lpf_signal, rate=self._rate, data_type="phase", taus=self._taus_list) # noqa

def test(self, data):
result = self._test_common(data)
if result is None:
if self._samples is None:
self._taus, self._samples, errors, ns = allantools.mtie(self._lpf_signal, rate=self._rate, data_type="phase", taus=self._taus_list) # noqa
self._generate_taus()
if out_of_range(self._taus, self._samples, self._accuracy, self._limit):
return (False, "unacceptable mtie")
return (True, None)
Expand All @@ -469,13 +482,10 @@ def test(self, data):
def explain(self, data):
analysis = self._explain_common(data)
if analysis is None:
if self._samples is None:
self._taus, self._samples, errors, ns = allantools.mtie(self._lpf_signal, rate=self._rate, data_type="phase", taus=self._taus_list) # noqa
self._generate_taus()
return {
'timestamp': self._timestamp_from_dec(data.iloc[0].timestamp),
'duration': data.iloc[-1].timestamp - data.iloc[0].timestamp,
'mtie': self._statistics(self._samples, 'ns'),
'mtie_taus': self._taus.tolist(),
'mtie_samples': self._samples.tolist(),
}
return analysis
86 changes: 61 additions & 25 deletions src/vse_sync_pp/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,85 @@

import numpy as np
import matplotlib.pyplot as plt
from collections import namedtuple

from .common import open_input

from .parsers import PARSERS


Axis = namedtuple("Axis", ["desc", "attr", "scale", "scale_kwargs"], defaults=[None, None, None, None])
TIMESERIES = Axis("Time", "timestamp")


class Plotter():
"""Rudimentary plotter of data values against timestamp"""
def __init__(self, y_name, y_desc=None):
self._x_name = 'timestamp'
self._y_name = y_name
if y_desc is None:
self._y_desc = y_name
else:
self._y_desc = y_desc
def __init__(self, x, y):
self._x = x
self._y = y
self._x_data = []
self._y_data = []

@staticmethod
def _extract_attr(axis, data):
return getattr(data, axis.attr)

def _set_yscale(self, ax):
if self._y.scale is not None:
ax.set_yscale(self._y.scale, **(self._y.scale_kwargs or {}))
elif any((abs(v) > 10 for v in self._y_data)):
ax.set_yscale("symlog", linthresh=10)

def append(self, data):
"""Append x and y data points extracted from `data`"""
x_val = getattr(data, self._x_name)
self._x_data.append(x_val)
y_val = getattr(data, self._y_name)
self._y_data.append(y_val)
self._x_data.append(self._extract_attr(self._x, data))
self._y_data.append(self._extract_attr(self._y, data))

def _plot_scatter(self, ax):
ax.axhline(0, color='black')
self._set_yscale(ax)
if self._x.scale is not None:
ax.set_xscale(self._x.scale, **(self._x.scale_kwargs or {}))
ax.plot(self._x_data, self._y_data, '.')
ax.grid()
ax.set_title(f'{self._x.desc} vs {self._y.desc}')

def _plot_hist(self, ax):
counts, bins = np.histogram(
np.array(self._y_data, dtype=float),
bins='fd'
)
ax.hist(bins[:-1], bins, weights=counts)
self._set_yscale(ax)
if self._x.scale is not None:
ax.set_xscale(self._x.scale, **(self._x.scale_kwargs or {}))
ax.set_title(f'Histogram of {self._y.desc}')

def plot(self, filename):
"""Plot data to `filename`"""
fig, (ax1, ax2) = plt.subplots(2, constrained_layout=True)
fig.set_size_inches(10, 8)
ax1.axhline(0, color='black')
if any((abs(v) > 10 for v in self._y_data)):
ax1.set_yscale('symlog', linthresh=10)
ax1.plot(self._x_data, self._y_data, '.')
ax1.grid()
ax1.set_title(f'{self._x_name} vs {self._y_desc}')
counts, bins = np.histogram(
np.array(self._y_data, dtype=float),
bins='scott',
)
ax2.hist(bins[:-1], bins, weights=counts)
ax2.set_yscale('symlog', linthresh=10)
ax2.set_title(f'Histogram of {self._y_desc}')
self._plot_scatter(ax1)
self._plot_hist(ax2)
ax3 = ax2.twinx()
ax3.set_ylabel('CDF')
ax3.ecdf(self._y_data, color="black", linewidth=2)
plt.savefig(filename)
return fig, (ax1, ax2, ax3)

def plot_scatter(self, filename):
fig, ax = plt.subplots(1, constrained_layout=True)
fig.set_size_inches(10, 4)
self._plot_scatter(ax)
plt.savefig(filename)
return fig, ax

def plot_histogram(self, filename):
fig, ax = plt.subplots(1, constrained_layout=True)
fig.set_size_inches(10, 4)
self._plot_hist(ax)
plt.savefig(filename)
return fig, ax


def main():
Expand All @@ -75,7 +111,7 @@ def main():
)
args = aparser.parse_args()
parser = PARSERS[args.parser]()
plotter = Plotter(parser.y_name)
plotter = Plotter(TIMESERIES, Axis(parser.y_name, parser.y_name))
with open_input(args.input) as fid:
method = parser.canonical if args.canonical else parser.parse
for parsed in method(fid):
Expand Down
14 changes: 7 additions & 7 deletions src/vse_sync_pp/requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

REQUIREMENTS = {
'G.8272/PRTC-A': {
'maximum-time-interval-error-in-locked-mode/us': {
(None, 273): lambda t: 0.000275 * t + 0.025,
(274, None): lambda t: 0.10
'maximum-time-interval-error-in-locked-mode/ns': {
(None, 273): lambda t: 0.275 * t + 25,
(274, 10000): lambda t: 100
},
'time-deviation-in-locked-mode/ns': {
(None, 100): lambda t: 3,
Expand All @@ -16,9 +16,9 @@
'time-error-in-locked-mode/ns': 100,
},
'G.8272/PRTC-B': {
'maximum-time-interval-error-in-locked-mode/us': {
(None, 54.5): lambda t: 0.000275 * t + 0.025,
(54.5, None): lambda t: 0.04
'maximum-time-interval-error-in-locked-mode/ns': {
(None, 54.5): lambda t: 0.275 * t + 25,
(54.5, 10000): lambda t: 40
},
'time-deviation-in-locked-mode/ns': {
(None, 100): lambda t: 1,
Expand All @@ -32,7 +32,7 @@
'time-deviation-in-locked-mode/ns': {
(None, 100000): lambda t: 100
},
'maximum-time-interval-error-in-locked-mode/us': {
'maximum-time-interval-error-in-locked-mode/ns': {
(None, 100000): lambda t: 1
}
},
Expand Down
16 changes: 0 additions & 16 deletions tests/vse_sync_pp/analyzers/test_gnss.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,6 @@ class TestMaxTimeIntervalErrorAnalyzer(TestCase, metaclass=AnalyzerTestBuilder):
'stddev': 0,
'variance': 0,
},
'mtie_samples': [0.],
'mtie_taus': [1.],
},
},
{
Expand Down Expand Up @@ -359,8 +357,6 @@ class TestMaxTimeIntervalErrorAnalyzer(TestCase, metaclass=AnalyzerTestBuilder):
'stddev': 0,
'variance': 0,
},
'mtie_samples': [0.],
'mtie_taus': [1.],
},
},
{
Expand Down Expand Up @@ -400,8 +396,6 @@ class TestMaxTimeIntervalErrorAnalyzer(TestCase, metaclass=AnalyzerTestBuilder):
'stddev': 0,
'variance': 0,
},
'mtie_samples': [0.],
'mtie_taus': [1.],
},
},
{
Expand Down Expand Up @@ -441,8 +435,6 @@ class TestMaxTimeIntervalErrorAnalyzer(TestCase, metaclass=AnalyzerTestBuilder):
'stddev': 0,
'variance': 0,
},
'mtie_samples': [0.],
'mtie_taus': [1.],
},
},
)
Expand Down Expand Up @@ -534,8 +526,6 @@ class TestTimeDeviationAnalyzer(TestCase, metaclass=AnalyzerTestBuilder):
'stddev': 0,
'variance': 0,
},
'tdev_samples': [0.],
'tdev_taus': [1.],
},
},
{
Expand Down Expand Up @@ -583,8 +573,6 @@ class TestTimeDeviationAnalyzer(TestCase, metaclass=AnalyzerTestBuilder):
'stddev': 0,
'variance': 0,
},
'tdev_samples': [0.],
'tdev_taus': [1.],
},
},
{
Expand Down Expand Up @@ -632,8 +620,6 @@ class TestTimeDeviationAnalyzer(TestCase, metaclass=AnalyzerTestBuilder):
'stddev': 0,
'variance': 0,
},
'tdev_samples': [0.],
'tdev_taus': [1.],
},
},
{
Expand Down Expand Up @@ -681,8 +667,6 @@ class TestTimeDeviationAnalyzer(TestCase, metaclass=AnalyzerTestBuilder):
'stddev': 0,
'variance': 0,
},
'tdev_samples': [0.],
'tdev_taus': [1.],
},
},
)
Loading