linkbudget.link_container¶
link_container ¶
link_container.py
The LinkContainer -- the object that holds an ordered list of
components and drives the computation -- and the whole family of signal-chain
components (Component and its subclasses).
A budget is built by adding components in order; compute() then threads
signal and noise power through each, recording the per-stage gain and SNR.
LinkContainer ¶
LinkContainer(power_units: str = 'W', carrier_frequency: float | None = None, noise_bandwidth: float | None = None, data_rate: float | None = None, symbol_rate: float | None = None, warn: bool = True)
Holds the ordered chain of components and drives the computation.
Add components with add_component, then publish (report
it), summary (figures of merit) or compute (just fill
data_list).
power_units is a display label for whatever linear unit the
signal_power / noise_power values are in -- keep it consistent
across the whole chain (ThermalNoise works in watts).
carrier_frequency / noise_bandwidth / data_rate /
symbol_rate are optional context summary uses to derive the
figures of merit. With warn=True (the default), compute
emits a LinkBudgetWarning for each dubious
configuration it finds.
Source code in linkbudget/link_container.py
carrier_frequency
instance-attribute
¶
noise_bandwidth
instance-attribute
¶
symbol_rate
instance-attribute
¶
add_publisher ¶
Add another publisher; publish() runs every installed one.
A new container has none installed, so publish() falls back to a
StdOutPublisher; adding one here replaces that fallback.
Source code in linkbudget/link_container.py
publish ¶
Compute the budget and hand it to every installed publisher.
With no publisher installed, falls back to a single StdOutPublisher.
To skip output entirely, call compute instead.
Source code in linkbudget/link_container.py
summary ¶
Compute the budget and return a BudgetSummary
with the standard figures of merit (EIRP, G/T, C/N0, Eb/N0, link
margin, ...).
Source code in linkbudget/link_container.py
add_component ¶
check ¶
Compute the budget and return a list of warnings about physically
dubious or likely-wrong configurations (an empty list means it looks
fine). These are the messages compute() emits as
LinkBudgetWarning unless the container was
built with warn=False.
Source code in linkbudget/link_container.py
compute ¶
Run signal and noise power through every component in order.
Fills data_list with one stage dict per component, adding
snr, signal_gain and noise_gain to each. Emits a
LinkBudgetWarning per issue found unless
self.warn is false.
Source code in linkbudget/link_container.py
Component ¶
Bases: ABC
Abstract base class for every link-budget component.
A component transforms an incoming (signal_power, noise_power) pair
and returns a stage dict (see StageData). The class attributes
below are read by LinkContainer.summary():
is_propagation-- setTrueon path-loss stages; the signal power entering the first one is the budget's EIRP and their combined loss is the total propagation loss.is_margin-- setTrueon aLinkMargin.role--"tx"/"rx"on an antenna, so the receive antenna can be identified for G/T.
Components are duck-typed -- LinkContainer only calls
propagate_signal -- so inheriting from this class is a convention, not
a requirement.
Source code in linkbudget/link_container.py
propagate_signal
abstractmethod
¶
Propagate signal and noise power through this component.
Returns a dict describing this stage of the link, with keys such as
name, description, signal_power_in/signal_power_out
and noise_power_in/noise_power_out.
Source code in linkbudget/link_container.py
SignalSource ¶
Bases: Component
Injects signal and/or noise power into the chain.
Use it as a transmitter (signal_power only) or as a noise source
(noise_power only); its powers are added to whatever is already in
the chain.
Source code in linkbudget/link_container.py
signal_power
instance-attribute
¶
propagate_signal ¶
Add this source's signal_power and noise_power to the chain.
Source code in linkbudget/link_container.py
FreeSpacePathLoss ¶
Bases: Component
Free-space path loss over distance (metres) at frequency (Hz).
Applies (4*pi*d*f / c)**2 of loss to both the signal and the noise.
For a two-way reflective (radar) path use RadarPathLoss instead.
Source code in linkbudget/link_container.py
propagate_signal ¶
Divide the signal and noise power by the free-space path loss.
Source code in linkbudget/link_container.py
Gain ¶
Bases: Component
Applies a fixed gain (or, if negative, a loss) to both the signal and the noise.
gain is in dB when db=True (the default) or a linear multiplier
when db=False. Set role="tx" / "rx" to mark an antenna so
LinkContainer.summary() can find the receive antenna for G/T.
Source code in linkbudget/link_container.py
propagate_signal ¶
Multiply the signal and noise power by the (linearised) gain.
Source code in linkbudget/link_container.py
QuantizationNoise ¶
QuantizationNoise(name: str = 'Quantization noise', description: str = '', total_bits: float = 12, headroom_db: float = 0)
Bases: Component
Adds quantization noise for a converter of total_bits resolution
driven headroom_db below full scale.
Signal power passes through unchanged; the quantization noise
(S+N) / (4**effective_bits) / 12 is added to the noise, where
effective_bits = total_bits - headroom_db / 6.0206.
Source code in linkbudget/link_container.py
propagate_signal ¶
Add the quantization noise to the noise power.
Source code in linkbudget/link_container.py
AnalogToDigitalConverter ¶
AnalogToDigitalConverter(name: str = 'ADC', description: str = '', gain_db: float = 0.0, noise_figure_db: float = 0.0, total_bits: float = 12.0, headroom_db: float = 0.0, sample_rate: float | None = None, noise_bandwidth: float | None = None)
Bases: Component
Analog-to-digital converter, in one step: an input buffer / driver
stage (gain_db, noise_figure_db) followed by quantization noise
from total_bits and headroom_db.
Internally composes RFComponent with QuantizationNoise,
so it behaves exactly like those two back to back. sample_rate (Hz)
is informational only. noise_bandwidth (Hz) is forwarded to the input
stage to select the Friis added-noise model (see RFComponent).
Source code in linkbudget/link_container.py
sample_rate
instance-attribute
¶
noise_bandwidth
instance-attribute
¶
propagate_signal ¶
Apply the buffer stage, then the quantization noise.
Source code in linkbudget/link_container.py
DigitalToAnalogConverter ¶
DigitalToAnalogConverter(name: str = 'DAC', description: str = '', total_bits: float = 12.0, headroom_db: float = 0.0, gain_db: float = 0.0, noise_figure_db: float = 0.0, sample_rate: float | None = None, noise_bandwidth: float | None = None)
Bases: Component
Digital-to-analog converter, in one step: quantization noise from
total_bits / headroom_db followed by an output driver stage
(gain_db, noise_figure_db) -- the reverse ordering of
AnalogToDigitalConverter.
Internally composes QuantizationNoise with RFComponent.
sample_rate (Hz) is informational only. noise_bandwidth (Hz) is
forwarded to the output stage to select the Friis added-noise model (see
RFComponent).
Source code in linkbudget/link_container.py
sample_rate
instance-attribute
¶
noise_bandwidth
instance-attribute
¶
propagate_signal ¶
Apply the quantization noise, then the output driver stage.
Source code in linkbudget/link_container.py
SubBandTune ¶
SubBandTune(name: str = 'Sub-band Tuner', description: str = '', input_upper_freq: float = 1000000.0, input_lower_freq: float = 0.0, signal_upper_freq: float = 4000000.0, signal_lower_freq: float = 2000000.0, output_upper_freq: float = 3000000.0, output_lower_freq: float = 2000000.0)
Bases: Component
Models filtering / retuning to a narrower sub-band.
Noise scales by the output / input bandwidth ratio; the signal
scales by the fraction of the signal band that survives inside the
output band (zero if they do not overlap). All six band edges are in
Hz; each *_upper_freq must exceed its *_lower_freq.
Source code in linkbudget/link_container.py
propagate_signal ¶
Scale signal by the surviving-band fraction and noise by the output/input bandwidth ratio.
Source code in linkbudget/link_container.py
ThermalNoise ¶
ThermalNoise(name: str = 'Thermal noise floor', description: str = '', temperature_k: float = 290.0, bandwidth: float = 1.0)
Bases: Component
Adds a physical thermal noise floor of k * temperature_k * bandwidth
watts to the noise power (signal unchanged).
Because the floor is an absolute power in watts, every power value in the budget should also be in watts if you use this component.
Source code in linkbudget/link_container.py
temperature_k
instance-attribute
¶
propagate_signal ¶
Add k*T*B to the noise power.
Source code in linkbudget/link_container.py
ImplementationLoss ¶
ImplementationLoss(name: str = 'Implementation loss', description: str = '', cable_loss_db: float = 0.0, pointing_loss_db: float = 0.0, polarization_loss_db: float = 0.0, other_loss_db: float = 0.0)
Bases: Component
Aggregate implementation-margin loss: attenuates the signal only (the noise is left untouched), matching the link-budget convention of "implementation loss" as an SNR penalty rather than a physical attenuator.
The four *_loss_db terms are summed into total_loss_db. For a
physical attenuator that reduces signal and noise together, use a
negative-dB Gain.
Source code in linkbudget/link_container.py
propagate_signal ¶
Divide the signal power by the summed loss; leave noise untouched.
Source code in linkbudget/link_container.py
CableLoss ¶
Bases: ImplementationLoss
ImplementationLoss specialised to a single cable-loss term.
Source code in linkbudget/link_container.py
PointingLoss ¶
Bases: ImplementationLoss
ImplementationLoss specialised to a single pointing-loss term.
Source code in linkbudget/link_container.py
PolarizationLoss ¶
Bases: ImplementationLoss
ImplementationLoss specialised to a single polarization-loss term.
Source code in linkbudget/link_container.py
NoiseFigure ¶
NoiseFigure(name: str = 'Noise figure', description: str = '', noise_figure: float = 1.0, noise_bandwidth: float | None = None, reference_temp_k: float = convert.REFERENCE_NOISE_TEMP_K)
Bases: Component
A lossless / unity-gain noisy stage: adds the noise of a stage with
noise_figure (dB) and leaves the signal unchanged.
Pass noise_bandwidth (Hz) to use the Friis added-noise form
N_out = N_in + (F - 1) k T0 B -- an absolute noise power, so it only
makes sense alongside a ThermalNoise floor in watts. Without it, the
scale-free N_out = N_in * F is used.
Source code in linkbudget/link_container.py
noise_bandwidth
instance-attribute
¶
reference_temp_k
instance-attribute
¶
propagate_signal ¶
Add this stage's excess noise to the noise power.
Source code in linkbudget/link_container.py
RFComponent ¶
RFComponent(name: str = 'RF Component', description: str = '', gain: float = 1.0, noise_figure: float = 1.0, noise_bandwidth: float | None = None, reference_temp_k: float = convert.REFERENCE_NOISE_TEMP_K)
Bases: Component
A generic gain block with a noise figure (e.g. an amplifier).
The signal scales by gain (dB). By default the noise scales by
gain times the noise_figure (dB) noise factor, so the SNR degrades
by exactly the noise figure wherever the stage sits.
Pass noise_bandwidth (Hz) to instead use the Friis added-noise form
N_out = (N_in + (F - 1) k T0 B) * G -- the excess noise is referred to
the input, so a noisy stage placed after gain barely matters. This is an
absolute noise power, so use it alongside a ThermalNoise floor in watts.
Source code in linkbudget/link_container.py
noise_bandwidth
instance-attribute
¶
reference_temp_k
instance-attribute
¶
propagate_signal ¶
Apply the gain to the signal and add this stage's noise.
Source code in linkbudget/link_container.py
RadarCrossSection ¶
Bases: Component
Scales the signal and noise by a plain (unitless) rcs_db power
multiplier -- the target-reflection term of a two-way radar path.
Chain it between two RadarPathLossOneWay legs to reproduce a
RadarPathLoss.
Source code in linkbudget/link_container.py
propagate_signal ¶
Multiply the signal and noise power by 10**(rcs_db/10).
Source code in linkbudget/link_container.py
RadarPathLoss ¶
RadarPathLoss(name: str = 'Radar path (two-way)', description: str = '', distance: float | None = None, tx_distance: float | None = None, rx_distance: float | None = None, frequency: float = 1000000000.0, rcs_db: float = 0.0)
Bases: Component
Two-way radar path loss in a single physically-consistent step: transmitted signal reflected off a target of the given radar cross section and returned to the receiver.
::
Pr / Pt = (wavelength**2 * rcs) / ((4*Pi)**3 * tx_distance**2 * rx_distance**2)
For monostatic radar (co-located transmitter/receiver) pass just
distance; for bistatic pass tx_distance and rx_distance.
Unlike chaining FreeSpacePathLoss twice around
RadarCrossSection, this applies the wavelength-dependent term
exactly once (matching the standard radar range equation) rather than
once per leg -- which otherwise overstates the loss by
wavelength**2 / (4*Pi) (~41 dB at X-band).
Source code in linkbudget/link_container.py
propagate_signal ¶
Apply the two-way radar range-equation path term.
Source code in linkbudget/link_container.py
RadarPathLossOneWay ¶
RadarPathLossOneWay(name: str = 'Radar path (one-way)', description: str = '', distance: float = 1.0, frequency: float = 1000000000.0)
Bases: Component
One calibrated leg of a two-way radar path.
::
RadarPathLossOneWay(distance=R1) -> RadarCrossSection(rcs_db)
-> RadarPathLossOneWay(distance=R2)
is equivalent to RadarPathLoss(tx_distance=R1, rx_distance=R2, rcs_db=rcs_db).
Each leg implements Pr / Pt = wavelength / ((4*Pi)**1.5 * distance**2), so
two legs plus the plain RadarCrossSection multiplier reproduce
the radar range equation.
This is not the same formula as FreeSpacePathLoss -- for a
direct, non-reflective antenna-to-antenna link use that instead.
Source code in linkbudget/link_container.py
propagate_signal ¶
Apply this leg's half of the two-way wavelength term.
Source code in linkbudget/link_container.py
ArrayFactor ¶
ArrayFactor(name: str = 'Array Factor', description: str = '', num_elements: int = 1, role: str | None = None)
Bases: Component
Antenna-array gain from an element count: scales both the signal and
the noise by num_elements.
Set role="tx" / "rx" to mark the array as an antenna for
LinkContainer.summary().
Source code in linkbudget/link_container.py
propagate_signal ¶
Multiply the signal and noise power by num_elements.
Source code in linkbudget/link_container.py
Mixer ¶
Mixer(name: str = 'Mixer', description: str = '', lo_frequency: float = 0.0, rf_frequency: float | None = None, conversion_loss_db: float = 0.0, noise_figure_db: float | None = None, mode: str = 'downconvert', image_reject_db: float | None = None, noise_bandwidth: float | None = None, reference_temp_k: float = convert.REFERENCE_NOISE_TEMP_K)
Bases: Component
Frequency-converts the signal against a local oscillator, applying conversion loss/gain and noise figure.
noise_figure_db defaults to conversion_loss_db (the passive-mixer
rule of thumb). If rf_frequency is given the resulting
if_frequency is reported (rf + lo for mode="upconvert",
|rf - lo| otherwise). If image_reject_db is given, the extra
noise folded in from the unrejected image band is added on top; omit it
to assume an ideal, fully image-rejected mixer.
Pass noise_bandwidth (Hz) to use the Friis added-noise form for the
noise-figure term (N_out = (N_in + (F - 1) k T0 B) * G, with the image
noise then k T0 B G / image_reject); without it the scale-free
N_out = N_in * G * F is used.
Source code in linkbudget/link_container.py
lo_frequency
instance-attribute
¶
rf_frequency
instance-attribute
¶
noise_figure_db
instance-attribute
¶
noise_bandwidth
instance-attribute
¶
reference_temp_k
instance-attribute
¶
propagate_signal ¶
Apply conversion loss/gain, noise figure and any image noise.
Source code in linkbudget/link_container.py
Integrate ¶
Bases: Component
Coherent integration (e.g. pulse integration): the signal power scales
by timespan (the coherent processing gain) while the noise power is
left unchanged, so the SNR improves by timespan.
Source code in linkbudget/link_container.py
propagate_signal ¶
Multiply the signal power by timespan; leave noise unchanged.