Source code for pybert.utility.math

"""
Channel math utilities for PyBERT.

Original author: David Banas <capn.freako@gmail.com>

Original date:   June 16, 2024

Copyright (c) 2024 David Banas; all rights reserved World wide.

A partial extraction of the old `pybert/utility.py`, as part of a refactoring.
"""

from functools import reduce
from typing import Any, Iterator, TypeVar

from numpy import (  # type: ignore
    append, array, cumsum, exp, log10,
    maximum, ones, pi, sqrt, where
)
from numpy.fft import fftshift  # type: ignore

from pybert.utility.sigproc import moving_average

from ..common import Rvec

T = TypeVar('T', Any, Any)


[docs] def lfsr_bits(taps: list[int], seed: int) -> Iterator[int]: """ Given a set of tap indices and a seed, generate a PRBS. Args: taps: The set of fed back taps. (Largest determines order of generator.) seed: The initial value of the shift register. Returns: A PRBS generator object with a ``next()`` method for retrieving the next bit in the sequence. """ val = int(seed) num_taps = max(taps) mask = (1 << num_taps) - 1 while True: xor_res = reduce(lambda x, b: x ^ b, [bool(val & (1 << (tap - 1))) for tap in taps]) val = (val << 1) & mask # Just to keep 'val' from growing without bound. if xor_res: val += 1 yield val & 1
[docs] def safe_log10(x): "Guards against pesky 'Divide by 0' error messages." if hasattr(x, "__len__"): x = where(x == 0, 1.0e-20 * ones(len(x)), x) else: if x == 0: x = 1.0e-20 return log10(x)
# pylint: disable=too-many-locals,too-many-arguments,too-many-positional-arguments
[docs] def make_bathtub(centers: Rvec, jit_pdf: Rvec, min_val: float = 0, rj: float = 0, mu_r: float = 0, mu_l: float = 0, extrap: bool = False) -> tuple[Rvec, tuple[int, int]]: """ Generate the "bathtub" curve associated with a particular jitter PDF. Args: centers: List of uniformly spaced bin centers (s). Note: First and last elements are exceptions. jit_pdf: PDF of jitter. Keyword Args: min_val: Minimum value allowed in returned bathtub vector. Default: 0 rj: Standard deviation of Gaussian PDF characterizing random jitter. Default: 0 mu_r: Mean of Gaussian PDF best fit to right tail. Default: 0 mu_l: Mean of Gaussian PDF best fit to left tail. Default: 0 extrap: Extrapolate bathtub tails, using `rj`, if True. Default: False Returns: The vector of probabilities forming the bathtub curve. """ half_len = len(jit_pdf) // 2 dt = centers[2] - centers[1] # Avoiding `centers[0]`, due to its special nature. if jit_pdf[0] or jit_pdf[-1]: # Closed eye? half_ui = centers[-1] # The following line works in conjunction w/ the line just before `return ...`, # to eliminate artifactual "spikes" near the center of the final plot, # which can occur in closed eye situations, due to slight mis-centering of the jitter PDF. jit_pmf = [(jit_pdf[0] + jit_pdf[-1]) * half_ui] + list(array(jit_pdf[1:-1]) * dt) + [0] else: if extrap: # The weird scaling is meant to improve numerical precision through `gaus_pdf()`. gaus_fit = append(gaus_pdf(centers[:half_len] * 1e12, mu_l * 1e12, rj * 1e12), gaus_pdf(centers[half_len:] * 1e12, mu_r * 1e12, rj * 1e12)) * 1e-12 jit_pdf_ext = moving_average(where(jit_pdf == 0, gaus_fit, jit_pdf), n=5) else: jit_pdf_ext = jit_pdf jit_pmf = array(jit_pdf_ext) * dt jit_cdf = cumsum(jit_pmf) * 2 jit_cdf -= (jit_cdf[0] + jit_cdf[-1]) / 2 - 1 # Forcing mid-point to 1, because we're going to... jit_cdf[half_len:] -= 2 * (jit_cdf[half_len:] - 1) # ...fold the second half vertically about the horizontal line: y=1. return maximum(min_val, fftshift(jit_cdf))
[docs] def gaus_pdf(x: Rvec, mu: float, sigma: float) -> Rvec: "Gaussian probability density function." sqrt_2pi = sqrt(2 * pi) return exp(-0.5 * ((x - mu) / sigma) ** 2) / (sigma * sqrt_2pi)
[docs] def all_combs(xss: list[list[T]]) -> list[list[T]]: """ Generate all combinations of input. Args: xss: The lists of candidates for each position in the final output. Returns: All possible combinations of inputs. """ if not xss: return [[]] head, *tail = xss yss = all_combs(tail) return [[x, *ys] for x in head for ys in yss] # type: ignore