Skip to content

linkbudget.publishers

publishers

publishers.py

The Publisher base class, the default StdOutPublisher, and the small number-formatting helpers the file publishers share.

A publisher receives the computed data_list (a list of per-stage dicts) plus the container's power_units label and turns it into output. Write your own by subclassing Publisher and implementing publish.

POWER_FIELDS module-attribute

POWER_FIELDS = {'signal_power_in', 'signal_power_out', 'noise_power_in', 'noise_power_out', 'quantization_noise', 'thermal_noise_power', 'image_noise_power', 'excess_noise_power', 'antenna_noise_power'}

Publisher

Publisher()

Bases: ABC

Base class for everything that reports a computed link budget.

Subclasses implement publish, which receives data_list -- the list of per-stage dicts produced by LinkContainer.compute() -- and the container's power_units display label.

Source code in linkbudget/publishers.py
@abstractmethod
def __init__(self) -> None:
    pass

publish abstractmethod

publish(data_list: StageList, power_units: str = 'W') -> None

Report data_list (each stage dict carries at least name, description, signal_power_in/out, noise_power_in/out, signal_gain, noise_gain and snr).

Source code in linkbudget/publishers.py
@abstractmethod
def publish(self, data_list: StageList, power_units: str = 'W') -> None:
    """Report ``data_list`` (each stage dict carries at least ``name``,
    ``description``, ``signal_power_in``/``out``, ``noise_power_in``/``out``,
    ``signal_gain``, ``noise_gain`` and ``snr``)."""

StdOutPublisher

StdOutPublisher()

Bases: Publisher

Print a summary table and a detailed per-stage report to the console.

This is the publisher a LinkContainer installs by default.

Source code in linkbudget/publishers.py
def __init__(self) -> None:
    pass

publish_summary

publish_summary(data_list: StageList, power_units: str = 'W') -> None

Print the one-row-per-stage summary table.

Source code in linkbudget/publishers.py
def publish_summary(self, data_list: StageList, power_units: str = 'W') -> None:
    """Print the one-row-per-stage summary table."""
    print('-'*120)
    print('|      LINK BUDGET SUMMARY')
    print('-'*120)
    print(f'|    Name                | Signal Power Out ({power_units}) | '
          f'Noise Power Out ({power_units}) | Signal Gain (dB) | '
          f'Noise Gain (dB) | SNR (dB)      |')
    print('-'*120)
    for index, data, in enumerate(data_list):
        name = pad_string(data['name'], 20, side='right')
        #sig_power = pad_string(trunc_float(data['signal_power_in']), 30)
        #noise_power = pad_string(data['noise_power_in'], 15)
        sig_power_out = pad_string(float_to_bounded_str(data['signal_power_out']), 20)
        noise_power_out = pad_string(float_to_bounded_str(data['noise_power_out']), 15)
        noise_gain = pad_string(
            float_to_bounded_str(convert.linear_to_db(data['noise_gain'])), 15)
        signal_gain = pad_string(
            float_to_bounded_str(convert.linear_to_db(data['signal_gain'])), 16)
        snr = pad_string(float_to_bounded_str(convert.linear_to_db(data['snr'])), 13)
        description = data['description']
        print(f' {index + 1}. {name} | {sig_power_out} | {noise_power_out} | '
              f'{signal_gain} | {noise_gain} | {snr}')
        print(f'        {description}')

publish_detailed

publish_detailed(data_list: StageList, power_units: str = 'W') -> None

Print every field of every stage, one stage per section.

Source code in linkbudget/publishers.py
def publish_detailed(self, data_list: StageList, power_units: str = 'W') -> None:
    """Print every field of every stage, one stage per section."""
    print('-'*120)
    print('|      DETAILED LINK BUDGET REPORT')
    print('-'*120)
    for index, data, in enumerate(data_list):
        print('-'*120)
        print(f' {index + 1}. {data["name"]}')
        for key in data:
            pretty_key = label_with_units(key, power_units)
            print(f'      {pretty_key}: {format_number(data[key])}')

publish

publish(data_list: StageList, power_units: str = 'W') -> None

Print the summary table followed by the detailed report.

Source code in linkbudget/publishers.py
def publish(self, data_list: StageList, power_units: str = 'W') -> None:
    """Print the summary table followed by the detailed report."""
    self.publish_summary(data_list, power_units)
    print()
    self.publish_detailed(data_list, power_units)

label_with_units

label_with_units(key: str, power_units: str) -> str

Turn a stage-dict key into a display label.

Underscores become spaces, and (power_units) is appended to keys that hold an absolute power (see POWER_FIELDS).

Source code in linkbudget/publishers.py
def label_with_units(key: str, power_units: str) -> str:
    """Turn a stage-dict key into a display label.

    Underscores become spaces, and ``(power_units)`` is appended to keys that
    hold an absolute power (see `POWER_FIELDS`).
    """
    pretty_key = key.replace('_', ' ')
    if key in POWER_FIELDS:
        return f'{pretty_key} ({power_units})'
    return pretty_key

pad_string

pad_string(string: Any, length: int, side: str = 'left') -> str

Pad string with spaces to length characters.

side is "left" (pad on the left) or anything else (pad on the right). A string already at least length long is returned unchanged.

Source code in linkbudget/publishers.py
def pad_string(string: Any, length: int, side: str = 'left') -> str:
    """Pad ``string`` with spaces to ``length`` characters.

    ``side`` is ``"left"`` (pad on the left) or anything else (pad on the
    right).  A string already at least ``length`` long is returned unchanged.
    """
    string = str(string)
    str_len = len(string)
    if str_len >= length:
        return string

    rem_length = length - str_len
    if side != 'left':
        return string + (' ' * rem_length)
    return (' ' * rem_length) + string

trunc_float

trunc_float(flt: float) -> float

Round flt to six decimal places (via "%f" formatting).

Source code in linkbudget/publishers.py
def trunc_float(flt: float) -> float:
    """Round ``flt`` to six decimal places (via ``"%f"`` formatting)."""
    return float(f'{flt:4f}')

format_number

format_number(val: Any, sig_figs: int = 6) -> str

Format a number to at most sig_figs significant figures, choosing between fixed-point and scientific notation based on its magnitude (like printf's %g) rather than the length of its default string representation -- this avoids both spuriously switching to scientific notation for floating-point noise (e.g. 2.9999999999999996 instead of 3) and needlessly spelling out very large/small magnitudes in full.

Source code in linkbudget/publishers.py
def format_number(val: Any, sig_figs: int = 6) -> str:
    """
    Format a number to at most `sig_figs` significant figures, choosing
    between fixed-point and scientific notation based on its magnitude
    (like printf's %g) rather than the length of its default string
    representation -- this avoids both spuriously switching to scientific
    notation for floating-point noise (e.g. 2.9999999999999996 instead of
    3) and needlessly spelling out very large/small magnitudes in full.
    """
    if isinstance(val, bool) or not isinstance(val, (int, float)):
        return str(val)
    if math.isnan(val) or math.isinf(val):
        return str(val)
    if val == 0:
        return '0.0'
    return f'{float(val):.{sig_figs}g}'

float_to_bounded_str

float_to_bounded_str(val: float, str_length: int = 12, sig_figs: int = 6) -> str

Format val like format_number, then drop significant figures one at a time until the string fits in str_length characters (a best-effort result is returned if even one figure does not fit).

Source code in linkbudget/publishers.py
def float_to_bounded_str(val: float, str_length: int = 12, sig_figs: int = 6) -> str:
    """Format ``val`` like `format_number`, then drop significant
    figures one at a time until the string fits in ``str_length`` characters
    (a best-effort result is returned if even one figure does not fit)."""
    val_str = format_number(val, sig_figs)
    if len(val_str) <= str_length:
        return val_str

    # still too long (e.g. a very large exponent) -- shrink precision to fit
    for figs in range(sig_figs - 1, 0, -1):
        val_str = format_number(val, figs)
        if len(val_str) <= str_length:
            return val_str
    return val_str