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
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
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(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
|