Source code for ptp.bias

"""Bias Compensator
"""
import logging

import numpy as np
from scipy import stats

logger = logging.getLogger(__name__)


[docs]class Bias(): """Bias Compensator The PTP timestamp differences can be modeled as: .. math:: t_2[n] - t_1[n] = x[n] + d_{ms}[n] .. math:: t_3[n] - t_4[n] = x[n] - d_{sm}[n] By summing the two equations and dividing by two, one can obtain the true time offset as: .. math:: ((t_2[n] - t_1[n]) + (t_3[n] - t_4[n]))/2 = x[n] + (d_{ms}[n] - d_{sm}[n])/2 This is equivalent to: .. math:: \\tilde{x}[n] = x[n] + (d_{ms}[n] - d_{sm}[n])/2, since the slave's time offset measurement is computed by: .. math:: \\tilde{x}[n] = ((t_2[n] - t_1[n]) - (t_4[n] - t_3[n])) / 2 This means that the time offset measurements are noisy: .. math:: \\tilde{x}[n] = x[n] + w[n], where the noise term is: .. math:: w[n] = (d_{ms}[n] - d_{sm}[n])/2, which is also known as the "delay asymmetry". The asymmetry w[n] typically has a non-zero mean, since the distributions of the master-to-slave (m-to-s) and slave-to-master (s-to-m) delays usually have distinct mean and differ also on other statistics (like minimum value, maximum, mode, etc). This class provides mechanisms for calculating and compensating w[n]. While doing so, it also considers the post processing that is to be applied on :math:`\\tilde{x}[n]`. For example, if sample-minimum packet selection is applied on a window of :math:`\\tilde{x}[n]`, the value that compensates w[n] differs from the case where :math:`\\tilde{x}[n]` measurements are used directly. """ def __init__(self, data): """Initialize bias compensator Args: data : Array of objects with simulation data """ self.data = data
[docs] def calc_true_asymmetry(self, target='estimates', metric='avg'): """Calculate the delay asymmetry of interest for bias compensation Processes the true m-to-s and s-to-m delays in the dataset in order to compute the delay asymmetry of interest. For example, the asymmetry between the minimum m-to-s and the minimum s-to-m delays. In practice, a PTP slave can only compute such values if locked to another accurate time source, such as GNSS. The slave needs to measure the true m-to-s and s-to-m delays of PTP messages to compute the true asymmetries that are computed in this function. Args: target : Whether to calculate the asymmetry to compensate bias of time offset estimates or to compensate t4 timestamps. Valid options are ["timestamps", "estimates"] (default: "estimates"). metric : When calculating the true bias of estimates, define the asymmetry metric of interest. Select among 'avg', 'min', 'max', 'median', or 'mode' (default: 'avg'). Returns: The true delay asymmetry of interest """ d_ms = np.array([r["d"] for r in self.data]) d_sm = np.array([r["d_bw"] for r in self.data]) # Correction value for timestamps if (target == 'timestamps'): corr = np.mean(d_ms - d_sm) # Correction value for post-processed or raw time offset estimates elif (target == 'estimates'): if (metric == 'avg'): corr = (np.mean(d_ms) - np.mean(d_sm)) / 2 elif (metric == 'min'): corr = (np.amin(d_ms) - np.amin(d_sm)) / 2 elif (metric == 'max'): corr = (np.amax(d_ms) - np.amax(d_sm)) / 2 elif (metric == 'median'): corr = (np.median(d_ms) - np.median(d_sm)) / 2 elif (metric == 'mode'): d_ms_mode, _ = stats.mode(np.round(d_ms)) d_sm_mode, _ = stats.mode(np.round(d_sm)) corr = (d_ms_mode[0] - d_sm_mode[0]) / 2 else: raise ValueError('Unknown metric {}'.format(metric)) else: raise ValueError("Unknown target") return corr
[docs] def compensate(self, corr, target='estimates', toffset_key='x_est'): """Compensate the bias of time offset estimations due to delay asymmetry In order to correct the asymmetry, a correction value can be either added directly to the raw time offset measurements ('x_est') or added to one of the four timestamps of a two-way exchange. For timestamp compensation, this function adds a correction to timestamp 't4'. The rationale is elaborated in the sequel. 1) Correcting time offset estimates When compensating time offset estimates/measurements, the goal is either to compensate the raw w[n] (see the expression above) or the bias that arises by processing m-to-s and s-to-m timestamp differences through packet selection. For example, when raw time offset measurements x_est[n] are considered, the compensation value should be the mean of w[n], like so: .. code-block:: x_est_corr[n] = x_est[n] - mean{w[n]} ~= x[n] + (w[n] - mean{w[n]}). The resulting corrected time offset measurements will then be unbiased as desired. On the other hand, if e.g. sample minimum is used, the bias that arises comes from the sample-minimum output: .. code-block:: samp_min[n] = (min{x[n] + d_ms[n]} + min{x[n] - d_sm[n]}) / 2 ~= x[n] + (min{d_ms[n]} - min{d_sm[n]})/2 ~= x[n] + w_sm[n], where .. code-block:: w_sm[n] = (min{d_ms[n]} - min{d_sm[n]})/2 Thus, the asymmetry to be compensated is "w_sm[n]" in this case. Similar expressions can be derived by considering other packet selection operators. 2) Correcting timestamps The idea of correcting timestamps is that it can be done before any post-processing. In practice, this would be done by the PTP slave itself directly, in which case the slave would correct a known static asymmetry. Ultimately, the timestamps delivered for post-processing stages would already be compensated and ideally symmetric on average. When timestamps are compensated, it suffices to adjust t_4[n]. By considering that: .. code-block:: t4_corr[n] = t_4[n] - corr[n] The expression for timestamp differences becomes: .. code-block:: t_2[n] - t_1[n] = x[n] + d_ms[n] t_3[n] - (t_4[n] - corr[n]) = x[n] - d_sm[n] which can be arranged to: .. code-block:: t_2[n] - t_1[n] = x[n] + d_ms[n] t_3[n] - t_4[n] = x[n] - (d_sm[n] + corr[n]) This means that, to ensure symmetry, we need: .. code-block:: d_ms[n] = (d_sm[n] + corr[n]) So that: .. code-block:: corr[n] = d_ms[n] - d_sm[n] In practice, the correction value corr[n] must be a constant that reflects the static asymmetry. Hence, the constant correction is: .. code-block:: corr = mean{d_ms[n] - d_sm[n]} Note, however, that this correction won't be sufficient in case packet selection is later applied. This is because some selection operators suffer from asymmetries other than the mean asymmetry. For example, it is perfectly possible that on average, after compensation, the m-to-s and s-to-m delays become symmetric, while their maximum/minimum/mode/median remain asymmetric. Furthermore, the compensation applied to t4 can both help or worsen the asymmetry of the other statistics. Not necessarily it will help. Nevertheless, note that some post-processing strategies will benefit from compensation of timestamps: sample-average, EWMA and LS will all benefit. Args: corr : Correction value to be applied. target : Whether to apply bias compensation to time offset estimates (post-processed or not) or to timestamps, specifically by adjusting 't4'. Valid options are ["timestamps", "estimates"] (default: "estimates"). toffset_key : Time offset estimation key """ # Correction for timestamps if (target == 'timestamps'): logger.info("Compensating bias on timestamps") logger.info("Set t4 -= %f ns" % (corr)) for d in self.data: d["t4"] -= corr # Correction for post-processed or raw time offset estimates elif (target == 'estimates'): logger.info("Compensating bias on %s" % (toffset_key)) logger.info("Set %s -= %f ns" % (toffset_key, corr)) for d in self.data: if (toffset_key in d): d[toffset_key] -= corr else: raise ValueError("Unknown target")