Skip to content

Commit

Permalink
Extend plotter
Browse files Browse the repository at this point in the history
  • Loading branch information
nocturnalastro authored and jnunyez committed Oct 10, 2023
1 parent 6f65d7e commit b03fedc
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 128 deletions.
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(np.array(self._y_data, dtype=float), 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

0 comments on commit b03fedc

Please sign in to comment.