RF / amateur-radio toolkit¶
abax ships a set of radio-frequency engineering functions — power/level
conversions, transmission-line and matching math, link-budget and propagation
formulas, antenna helpers, and the Maidenhead grid locator — so you can build a
link budget, antenna, or matching spreadsheet natively. They are backed by
abax/core/science/rf.py (pure standard library;
no third-party dependency).
See also: formula reference · data analysis · index · contest log scoring example.
Units¶
The formula functions use SI base units so they stay unambiguous and unit-neutral:
| Quantity | Unit |
|---|---|
| frequency | hertz (Hz) — e.g. 14.2e6 for 14.2 MHz |
| length / distance / wavelength | metre (m) |
| power | watt (W); levels in dBm / dBW / dB |
| inductance · capacitance | henry (H) · farad (F) |
| impedance | ohm (Ω) |
Put MHz/feet in your own cells and scale, or use CONVERT (see
file formats / functions) — e.g. =CONVERT(A1,"ft","m").
The RF toolkit dialog and Smith chart (Radio menu) accept MHz / feet
directly and show results in both metric and imperial.
Power & levels¶
| Function | Returns |
|---|---|
DBM2W(dbm) / W2DBM(watts) |
dBm ↔ watts |
DBW2W(dbw) / W2DBW(watts) |
dBW ↔ watts |
DB2RATIO(db) / RATIO2DB(power_ratio) |
dB ↔ linear power ratio |
DBADD(db1, db2) |
combine two powers given in dB(m) |
DBUV2DBM(dbuv, [z=50]) |
dBµV (across Z) → dBm |
SUNIT2DBM(s) |
HF S-meter reading → dBm (S9 = −73 dBm) |
NOISEFLOOR(bw_hz, [temp_k=290]) |
thermal noise floor kTB (dBm) |
NF2NT(nf_db, [t0]) / NT2NF(temp_k, [t0]) |
noise figure ↔ noise temperature |
Wavelength, resonance, reactance¶
| Function | Returns |
|---|---|
WAVELENGTH(freq_hz, [vf=1]) / WL2FREQ(m, [vf=1]) |
λ ↔ f (optional velocity factor) |
DIPOLELEN(freq_hz, [k=0.95]) |
physical ½-wave dipole length (m) |
MONOPOLELEN(freq_hz, [k=0.95]) |
physical ¼-wave monopole length (m) |
XL(freq_hz, L_henry) / XC(freq_hz, C_farad) |
inductive / capacitive reactance (Ω) |
RESFREQ(L_henry, C_farad) |
LC resonant frequency (Hz) |
Transmission line & matching¶
| Function | Returns |
|---|---|
VSWR(z_load, [z0=50]) |
VSWR from a load, resistive or complex |
VSWRG(gamma) |
VSWR from Γ or |Γ| |
REFLCOEF(z_load, [z0=50]) |
reflection coefficient Γ; a complex load gives a complex Γ |
RETURNLOSS(gamma) / MISMATCHLOSS(gamma) |
return loss / mismatch loss (dB) from Γ or |Γ| |
VSWR2GAMMA(vswr) |
|Γ| from VSWR |
Z0COAX(d_outer, d_inner, [eps_r=1]) |
coax characteristic impedance (Ω) |
VELFACTOR(eps_r) |
velocity factor 1/√εr |
QWMATCH(z1, z2) |
quarter-wave transformer impedance √(Z₁·Z₂) (Ω) |
SWRPWR(forward_w, reflected_w) |
SWR from forward / reflected power |
Complex loads.
- VSWR, REFLCOEF, RETURNLOSS, MISMATCHLOSS and VSWRG accept an Excel
complex string as well as a number.
- A reactive load's Γ comes back as a complex string, which chains straight
into the others. Plain numbers work exactly as before.
=VSWR("75+25j") → 1.77
=REFLCOEF("75+25j") → 0.230769230769+0.153846153846j
=RETURNLOSS(REFLCOEF("75+25j")) → 11.14 (dB)
L-networks for complex loads. In the Python console:
- rf.l_match_complex(zl, z0, f) returns every lossless L-network that matches
a complex load to Z0, in both topologies: shunt element at the load, or
series element at the load. Each network is verified numerically.
- rf.match_path(zl, solution, z0) traces the Smith-chart path as each element
is added.
The older rf.l_match matches resistances only. It is still used by the RF
toolkit's Matching (L-network) mode, whose inputs are resistive.
Component & antenna design (radio math)¶
Resonant-circuit component values, loaded-Q / bandwidth, inductor design, and
antenna dimensions, backed by
abax/core/science/rf_math.py. All SI units
(farads, henries, metres, hertz) except TOROIDL/TOROIDN, which take the
manufacturer's AL value in nH/turn².
| Function | Returns |
|---|---|
CFROMXC(xc_ohms, freq_hz) |
capacitance for a target reactance (F) |
LFROMXL(xl_ohms, freq_hz) |
inductance for a target reactance (H) |
RESONANTC(freq_hz, L_henry) |
C that resonates with L at f (F) |
RESONANTL(freq_hz, C_farad) |
L that resonates with C at f (H) |
QBW(center_hz, bandwidth_hz) |
loaded Q from centre frequency and bandwidth |
BWQ(center_hz, q) |
bandwidth from centre frequency and Q (Hz) |
AIRCOILL(diameter_m, length_m, turns) |
single-layer air-core inductance, Wheeler (H) |
AIRCOILN(inductance_h, diameter_m, length_m) |
turns for a target air-core inductance |
TOROIDL(al_nh, turns) |
toroid inductance from an AL value (H) |
TOROIDN(inductance_h, al_nh) |
turns for a target toroid inductance |
LOOPLEN(freq_hz) |
full-wave loop circumference (m) |
DISHGAIN(diameter_m, freq_hz, [eff=0.55]) |
parabolic-dish gain (dBi) |
DISHBW(diameter_m, freq_hz) |
parabolic-dish half-power beamwidth (degrees) |
DOPPLER(freq_hz, velocity_mps) |
Doppler shift for a closing/opening velocity (Hz) |
ZINLINER(z_load_r, z_load_x, z0, elec_len_deg) |
real part of lossless-line input impedance Zin (Ω) |
ZINLINEX(z_load_r, z_load_x, z0, elec_len_deg) |
imaginary part of lossless-line input impedance Zin (Ω) |
LINELOSS(length_m, freq_hz, matched_loss_db_per_100m) |
matched line loss (dB) |
ZINLINER/ZINLINEX transform the load ZL = z_load_r + j·z_load_x through a
lossless line of characteristic impedance z0 and electrical length
elec_len_deg (90° = quarter wave, 180° = half wave):
Zin = Z0·(ZL + jZ0·tan βl)/(Z0 + jZL·tan βl). A quarter-wave line maps ZL to
Z0²/ZL; a half-wave line repeats the load. Return the real / imaginary parts
separately, mirroring DIPOLER/DIPOLEX.
Circuit fundamentals¶
Ohm's law and power, series and parallel parts, RC/RL time constants, complex
impedance, and power, backed by
abax/core/science/circuits.py
(pure standard library).
Impedance in two styles. Any argument written z | r, [x] takes either
- one Excel complex string, such as "75+25j" or "50-25i", or a plain number
for a pure resistance; or
- two numbers, R and X.
So =PHASEANGLE(100,-200) and =PHASEANGLE("100-200j") give the same answer.
Complex results (ZSERIESRLC, ADMITTANCE, POLAR2RECT) come back as complex
strings with a j suffix. The existing IMREAL, IMAGINARY, IMABS and
IMARGUMENT functions read them. Positive X is inductive, negative X capacitive.
| Function | Returns |
|---|---|
OHMV(i, r) / OHMI(e, r) / OHMR(e, i) |
Ohm's law: volts / amperes / ohms |
POWERVI(e, i) / POWERIR(i, r) / POWERVR(e, r) |
power (W) three ways |
RSERIES(…) / LSERIES(…) / CPARALLEL(…) |
straight sum (values or ranges) |
RPARALLEL(…) / LPARALLEL(…) / CSERIES(…) |
1 / Σ(1/x) (values or ranges) |
TAURC(r, c) / TAURL(r, l) |
time constant τ (s) |
TCCHARGE(t, tau) / TCDECAY(t, tau) |
fraction reached / remaining after t (0.632 / 0.368 at 1τ) |
TCTIME(fraction, tau) |
time to charge to a fraction of the final value |
ZSERIESRLC(f, r, l, c) / ZPARALLELRLC(f, r, l, c) |
complex impedance; pass 0 to omit a part |
ZMAG(z) / PHASEANGLE(z) / POWERFACTOR(z) |
|Z|, phase (°, + = voltage leads), cos θ |
ADMITTANCE(z) / CONDUCTANCE(z) / SUSCEPTANCE(z) |
Y = 1/Z (complex) and its G / B parts (S) |
POLAR2RECT(mag, angle_deg) |
polar → rectangular complex |
REALPOWER(e, i, θ) / REACTIVEPOWER(e, i, θ) / APPARENTPOWER(e, i) |
W / VAR / VA |
QSERIES(r, x) / QPARALLEL(r, x) |
circuit Q: X/R series, R/X parallel |
Worked examples (Extra pool E5B04, E5B08, E5C10):
=TAURC(RPARALLEL(1e6,1e6), CPARALLEL(220e-6,220e-6)) → 220 (s)
=PHASEANGLE(100, 100-300) → -63.4 (voltage lags)
=ZSERIESRLC(14e6, 400, 0, 38e-12) → 400-299.16…j
Radio-system math¶
Station and system arithmetic, backed by
abax/core/science/radio_calc.py.
| Function | Returns |
|---|---|
ERPW(p_w, gain_dbd, [loss_db…]) / EIRPW(p_w, gain_dbi, [loss_db…]) |
ERP / EIRP in watts; losses are positive dB |
RXLEVEL(ptx_dbm, gtx, grx, path_loss_db, [cable_loss_db]) |
received level (dBm) |
LINKMARGIN(rx_dbm, mds_dbm, [snr_db]) |
margin above MDS + required SNR (dB) |
BWNOISEDB(bw_from, bw_to) |
noise change with bandwidth, 10·log(B₂/B₁) |
OPAMPINV(rf, rin) / OPAMPNONINV(rf, rin) |
op-amp voltage gain −Rf/Rin / 1 + Rf/Rin |
MODINDEX(dev, fm) / DEVRATIO(dev_max, fm_max) |
FM modulation index / deviation ratio |
CARSONBW(dev, fm) |
FM bandwidth 2·(Δf + fm) |
CWBW(wpm, [k=5]) / FSKBW(shift, baud, [k=1.2]) |
necessary bandwidth (Hz), 47 CFR § 2.202 |
ADCBITS(range, resolution) / ADCLEVELS(bits) / ADCLSB(vref, bits) |
converter resolution |
ADCSNR(bits) |
ideal quantization SNR, 6.02·N + 1.76 dB |
NYQUIST(f_max) |
minimum sample rate 2·f_max |
USBMAXFREQ(upper_edge, [bw=3000]) / LSBMINFREQ(lower_edge, [bw=3000]) |
sideband band-edge limits (Hz) |
LINELEN(f, fraction_wl, [vf]) / ELECDEG(length_m, f, [vf]) |
physical ↔ electrical line length |
STUBX(z0, elec_deg, [open_end=FALSE]) |
stub input reactance (Ω); #NUM! at a pole |
ANTEFF(r_rad, r_loss) |
antenna efficiency |
IMD3LO(f1, f2) / IMD3HI(f1, f2) |
third-order IMD products 2f₁−f₂ / 2f₂−f₁ |
IP3(tone_dbm, im3_dbm) / SFDR(ip3_dbm, floor_dbm) |
intercept point / spurious-free dynamic range |
IMAGEFREQ(f_sig, f_if, [high_side_lo=TRUE]) |
superhet image frequency |
Where the necessary-bandwidth formulas come from. 47 CFR § 2.202(g) gives
Bn = B·K for CW (K = 5 for fading circuits, 3 for non-fading) and Bn = 2M + 2DK
for frequency-shift keying (M = B/2, D = half the shift, K = 1.2 typically).
The rule does not state a general words-per-minute-to-baud conversion. CWBW
uses 0.8 baud per wpm, the ratio in the rule's own worked example (25 wpm with
B = 20). The Extra pool uses the same ratio (E8C05: 13 wpm → 52 Hz).
Worked examples (Extra pool E9A02, E4D12, E8C07):
=ERPW(150, 7, 2, 2.2) → 286 (W ERP)
=LINKMARGIN(RXLEVEL(40, 10, 0, 136, 3), -103, 6) → 8 (dB)
=FSKBW(4800, 9600) → 15360 (Hz)
Every calculation question in the current Extra pool is worked, and checked against the answer key, in the Extra Class worked examples.
RF exposure (MPE / SAR)¶
Estimates for the RF exposure evaluation US amateur stations must make, backed
by abax/core/science/rf_exposure.py.
These are estimates. They use the far-field power-density formula. They are not a substitute for your station's evaluation under 47 CFR § 97.13(c) and § 1.1307(b). They are also not a reliable measure closer to the antenna than λ/2π. When a result is close to a limit, measure or model the station properly.
| Function | Returns |
|---|---|
MPELIMIT(freq_hz, [tier]) |
MPE power-density limit, mW/cm² (47 CFR § 1.1310 Table 1) |
MPEEFIELD(freq_hz, [tier]) / MPEHFIELD(freq_hz, [tier]) |
E-field (V/m) / H-field (A/m) limit; #N/A above 300 MHz, where the table gives none |
PWRDENSITY(eirp_w, distance_m, [reflection=1]) |
far-field power density F·EIRP / (4πR²), mW/cm² |
MPEDIST(eirp_w, freq_hz, [tier], [reflection=1]) |
distance (m) beyond which the limit is met |
MPEPERCENT(density_mw_cm2, freq_hz, [tier]) |
a power density as a percent of the limit |
AVGPOWER(peak_w, [duty_cycle=1], [tx_fraction=1]) |
time-averaged power: PEP × duty × transmit fraction |
EXEMPTERP(freq_hz, distance_m) |
exemption-threshold ERP (W), 47 CFR § 1.1307(b)(3)(i)(C) |
EXEMPTMINDIST(freq_hz) |
λ/2π (m), the closest distance where that table applies |
SARLIMIT([tier], [kind]) |
SAR limit, W/kg, 47 CFR § 1.1310(b)–(c). kind is whole_body, peak_1g or extremity_10g |
SAR(σ, e_rms, ρ) |
SAR = σ·E²/ρ, W/kg |
Tiers.
- "uncontrolled" (the default; "general" also works): general population,
averaged over 30 minutes.
- "controlled" ("occupational"): averaged over 6 minutes.
- Under § 97.13(c)(1), you may evaluate members of your immediate household
against the controlled limits, provided you and they have had the
appropriate training and information. Everyone else nearby is evaluated
against the uncontrolled limits.
Units. - Formula frequencies are in hertz, as everywhere else in abax, and are converted to the MHz the FCC tables use. - Power density is in mW/cm², the unit of the FCC table (1 mW/cm² = 10 W/m²). - At a boundary frequency that the table lists in two rows (for example 1.34 MHz), the more restrictive value is used.
Ground reflection.
- reflection is a power-density factor F.
- 1 is free space.
- 4 is full in-phase reflection: the reflected wave doubles the field, which
quadruples the power density.
- The § 1.1307 exemption table is exactly this F = 4 model applied to ERP.
Every row equals the general-population limit × πR² / 1.64, to the rule's
rounding, and abax's tests check this.
- OET Bulletin 65 recommends a smaller factor for typical ground. abax does not
preset it, because the bulletin could not be checked when this was written.
If you use it, enter it yourself from OET-65.
Duty cycle.
- Enter your own mode duty cycle and transmit time.
- 1 (100 %) is the worst case and the default.
- abax does not preset per-mode duty factors (OET-65 Supplement B has a table)
for the same reason as the reflection factor.
Multiple transmitters. Under § 1.1307(b)(5), when a site's total exceeds the
limit, every licensee whose source produces more than 5 % of the applicable
limit there shares responsibility. MPEPERCENT gives that percentage. (Extra
pool E0A04 words it as "5 percent or more".)
Worked example — 1500 W PEP on 10 m into an 8 dBi beam, SSB:
A1: =AVGPOWER(1500, 0.5, 0.5) → 375 (W; your duty and on-time)
A2: =A1*10^(8/10) → 2366 (W EIRP)
A3: =MPEDIST(A2, 28.4e6, "uncontrolled", 4) → 18.4 (m, worst-case reflection)
A4: =MPEDIST(A2, 28.4e6, "controlled", 4) → 8.2 (m)
A5: =EXEMPTERP(28.4e6, 20) → 1711 (W ERP threshold at 20 m)
Not included, because they could not be verified from primary sources: - the § 1.1307(b)(3)(i)(B) SAR-based threshold P_th for distances of 0.5–40 cm - OET-65's per-mode duty factors and its ground-reflection factor - ICNIRP reference levels
These are future work.
RF exposure dialog¶
Tools → Radio → RF exposure estimate (MPE) takes: - frequency - transmitter power (PEP) and feed-line loss - antenna gain (dBi) - duty cycle and transmit time in the averaging period - ground reflection: none, full (worst case) or a custom factor - the distance to the nearest person
It reports: - average power, EIRP and ERP - both tiers' limits and averaging times - the distance at which each limit is met - the density at your distance as a percent of each limit, stated in words ("below the limit" / "ABOVE the limit") - the § 1.1307 exemption check
The log-log graph of density against distance marks both limits, both compliance distances and your distance. Its written summary is also its screen-reader description, updated with every change. The same numbers are in the data table, and Data → new sheet copies them into a worksheet.
| Function | Returns |
|---|---|
FSPL(distance_m, freq_hz) |
free-space path loss (dB) |
FRIIS(ptx_dbm, gtx_dbi, grx_dbi, dist_m, freq_hz) |
received power (dBm) |
EIRP(ptx_dbm, gain_dbi, [loss_db=0]) |
EIRP (dBm) |
FRESNEL(d1_m, d2_m, freq_hz, [zone=1]) |
Fresnel-zone radius (m) |
RADIOHORIZON(h1_m, [h2_m=0]) |
radio line-of-sight distance (km, 4/3 earth) |
SKINDEPTH(freq_hz, [sigma=5.8e7], [mu_r=1]) |
skin depth (m); default copper |
DBI2DBD(dbi) / DBD2DBI(dbd) |
antenna gain reference conversion |
Maidenhead grid locator¶
| Function | Returns |
|---|---|
GRIDSQUARE(lat, lon, [precision=6]) |
locator string, e.g. JN58td |
GRIDLAT(grid) / GRIDLON(grid) |
cell-centre latitude / longitude |
GRIDDIST(grid_a, grid_b) |
great-circle distance (km) |
GRIDBEARING(grid_a, grid_b) |
initial bearing (degrees) |
Worked examples¶
2.4 GHz link at 1 km, 12 dBi antennas, 30 dBm TX:
40 m dipole + feedline match check (75 Ω load on 50 Ω line):
B1: =DIPOLELEN(7.1e6) → 20.05 (m, half-wave with k=0.95)
B2: =VSWR(75, 50) → 1.5
B3: =RETURNLOSS(VSWR2GAMMA(B2)) → 13.98 (dB)
Grid-square distance/bearing (Munich → London):
C1: =GRIDDIST("JN58td", "IO91wm") → ~920 (km)
C2: =GRIDBEARING("JN58td", "IO91wm") → ~300 (degrees, WNW)
Amateur-radio reference data¶
| Function | Returns |
|---|---|
HAMBAND(freq_hz) |
US amateur band name for a frequency (e.g. 14.1e6 → 20m), #N/A outside any band |
CTCSSTONE(n) |
the n-th standard EIA CTCSS tone (1–50), in Hz |
NEARESTCTCSS(freq_hz) |
the standard CTCSS tone nearest a measured frequency |
DXCC(callsign) |
DXCC entity for a callsign (=DXCC("W1AW") → United States); handles portable prefixes and operational suffixes |
DXCC is backed by a 378-prefix table (abax/core/science/dxcc.py); it strips
trailing operational suffixes (/P, /M, /QRP, …) and honours a leading
re-location prefix (DL/W1AW → Germany), matching on the longest prefix in the
table.
ADIF logbook (.adi / .adif)¶
abax reads and writes ADIF (Amateur Data Interchange Format) logbooks, backed by
abax/core/io/adif_io.py (pure standard library):
- Open a
.adi/.adiffile (File → Open) and abax loads it into a sheet named Log — the header row is the union of ADIF field names (in first-seen order) and each QSO record becomes a row. - Save As a
.adi/.adiffile (File → Save As) writes the sheet back out as a valid ADIF document (header row = field names, one<…:len>value…<EOR>record per data row). - The parser skips an optional header (through
<EOH>), is case-insensitive, and measures field lengths in UTF-8 bytes so values with non-ASCII characters survive a round-trip.abax.core.io.adif_iois also exposed in the Python console asadif(parse_adif/to_adif/records_to_grid/grid_to_records).
Combine it with DXCC in the grid — e.g. =DXCC(A2) in a column next to your logged
callsigns — to annotate the entities you've worked.
POTA/SOTA & contest logging¶
abax has a small logging layer for Parks/Summits On The Air activations and
contest operating, backed by abax/core/science/hamlog.py
(pure standard library). It gives the grid two spreadsheet functions and a live
GUI logger, both built on the same dupe / scoring primitives.
| Function | Returns |
|---|---|
ISDUPE(call, band, mode, [log_range]) |
TRUE if (call, band, mode) already appears in log_range |
QSOPOINTS(mode, [ruleset]) |
point value of one QSO in mode under a named ruleset |
log_range is a range of prior QSOs laid out one per row as call \| band \| mode
(extra columns ignored); omit it and the log is treated as empty. QSOPOINTS
defaults to the generic ruleset (1 pt/QSO); under fieldday a CW/digital QSO
scores 2 and phone 1 (ARRL Field Day 7.3.1).
Duplicate detection works per band per mode with callsign
normalisation — a call is upper-cased and stripped of portable decorations
before comparison, so W1AW, w1aw/p and VE3/W1AW all collide, and modes are
folded onto a family (USB/LSB → SSB, FT8/PSK31/RTTY → data) so those
count together. The dupe key defaults to call + band + mode (the POTA/contest
"once per band per mode" convention); the SOTA preset collapses band and
mode so a summit counts once regardless.
Point / multiplier tally. Scoring walks a log in order, marks each QSO new or
dupe (a dupe earns 0 points), applies the ruleset's per-QSO point value, and
counts multipliers (the distinct non-blank multiplier tokens among credited
QSOs). The final score is credited points × multipliers (× 1 when a ruleset has
no multipliers). Built-in presets are generic, pota, sota, fieldday, and
arrl-dx.
Activation log dialog¶
Tools → Radio → Activation log (POTA/SOTA) opens a keyboard-first logger
(HamLogDialog). Pick a ruleset, type a callsign, choose band/mode (time
defaults to now, UTC), and Log QSO: the contact is added to an in-memory log,
scored against the selected ruleset, and checked for dupes. Dupe rows are
highlighted and a running tally — valid QSOs / dupes / points / score —
updates on every entry. Write to sheet drops the whole scored log into a new
worksheet (with a summary block), where the ADIF logbook tools can export it.
Satellite passes (SGP4)¶
abax predicts satellite passes from a two-line element set (TLE) and an
observer, backed by abax/engine/satellite.py.
Given a TLE plus an observer (latitude, longitude, altitude) it computes, for
each pass over a time window, the rise, culmination and set times,
the azimuth at each of those moments, and the maximum elevation at
culmination.
Orbit propagation uses the optional sgp4 package (a pure-Python
implementation of the standard SGP4 model); everything after propagation —
converting the orbit position to the observer's topocentric frame for azimuth and
elevation — is pure standard library. Importing the module never fails:
satellite.available() reports whether the propagation path can run, and a
predictor call raises a descriptive "install sgp4" message when the package is
absent.
from abax.engine import satellite
satellite.available() # True iff the 'sgp4' package is importable
passes = satellite.predict_passes(
tle, # a TLE string (three-line or two-line) or parsed Tle
(40.71, -74.01, 10.0), # observer: lat°, lon°, altitude (m)
start, hours=24, # window start (UTC) and length
min_elevation_deg=10.0, # only report passes above this elevation
)
Each returned pass carries the satellite name, rise / culmination / set
(timezone-aware UTC datetimes), max_elevation, the rise / max / set azimuths,
and the duration in seconds.
sgp4 is an optional dependency shipped in the satellite extra:
Satellite pass predictor dialog¶
Tools → Radio → Satellite passes (SGP4) opens the predictor (SatelliteDialog).
Paste a TLE (the name line is optional and a sample ISS element set is prefilled),
set the observer and the window (start time in UTC, length in hours, minimum
elevation), and Predict (or press F5). Passes appear in a table — rise,
culmination and set with their azimuths, the maximum elevation, and the duration
— and Passes → new sheet drops them into a fresh sheet. When sgp4 is not
installed the dialog stays usable but Predict reports the "install sgp4" message
instead of computing.
GUI tools (the Radio menu)¶
All of the RF/amateur-radio tools live under the Tools → Radio submenu (general math tools stay under Tools → Scientific):
- RF toolkit — a mode-switching dialog for link budget, coax line, antenna dimensions, and L-network matching, showing results in both metric and imperial where it helps.
- Smith chart — enter a load R + jX, Z0 and frequency.
- It shows:
- the load's Γ in rectangular and polar form
- VSWR, return loss and mismatch loss
- every lossless L-network that matches the complex load. Earlier versions matched only the resistive part.
- Draw match plots the chosen network's path. The series element moves along a constant-resistance circle and the shunt element along a constant-conductance circle, ending at the chart center.
- Accessibility:
- The chart is keyboard-focusable, and every mark on it is labeled in text.
- Its screen-reader description covers where the load sits, the VSWR, and the path drawn, and it is re-announced when those change.
- The readout is a read-only text area you can move through line by line.
- RF exposure estimate (MPE) — FCC MPE limits, compliance distances, and the § 1.1307 exemption check, with an accessible density-versus-distance graph (see RF exposure above).
- Circuit calculator — four tabs:
- Ohm's law: enter any two of E, I, R and P and it solves the other two.
- Time constant: RC or RL, charging or discharging, with a graph of the 0–5τ curve.
- Resonance: series or parallel RLC, giving f0, Q and the −3 dB bandwidth, with a response graph.
- Impedance: Z, phase angle, power factor and Y for R, L and C at a frequency.
Fields accept engineering notation (50u, 40 pF, 3.5 MHz). Each graph is
paired with the same data in three other forms:
- a written summary, which is also the chart's screen-reader description and
updates whenever the values change;
- a data table;
- a Data → new sheet button that copies the table into a worksheet.
The graph itself takes keyboard focus.
- Antenna pattern — a polar plot of the analytic dipole / array patterns with
directivity (dBi) and half-power beamwidth. It re-plots live as you change N /
spacing / phase, and exports the pattern as SVG or a NEC .nec deck.
- Antenna modeler — a Method-of-Moments dialog for a real dipole or
Yagi, reporting gain / front-to-back / feed impedance and a radiation cut,
with a Ground option for an over-ground take-off pattern (see Antenna
modeling below).
- Activation log (POTA/SOTA) — the keyboard-first activation logger with live
dupe highlighting and a running score (see POTA/SOTA & contest logging).
- Satellite passes (SGP4) — the TLE + observer pass predictor (see Satellite
passes); needs the optional sgp4 package.
- RF reference — a filterable view of the US amateur band plan (with width and
mid-band wavelength) and the 50 EIA CTCSS tones; double-click (or Send to cell)
writes a value into the grid, and Bands → new sheet drops the band plan in.
- I/Q → SVG — reads a two-column (I, Q) selection and exports the constellation
as an SVG, reporting power in dBFS.
- Solve NEC deck (PyNEC) — see below.
Accessibility of the radio tools¶
Every Radio dialog is checked by tests/test_gui_radio_accessibility.py:
- Every control has a name a screen reader announces, from a buddy label or an
explicit name. Placeholder text is not relied on.
- Every chart is keyboard-focusable and has a written description built from
the numbers it draws. The description is re-announced when the chart
changes. This covers the Smith chart, the circuit and exposure graphs, and
the antenna pattern plots.
The graphs also: - draw a visible focus ring; - label reference lines and points with text, never color alone; - take their colors from the active theme, so the high-contrast themes apply.
The circuit and exposure graphs also show their data as a table, with Data → new sheet.
Exported SVGs have role="img", a <title> and a <desc> describing the
plot, so they stay accessible in a web page or document. This covers:
- Smith chart → SVG
- the antenna pattern's Export SVG
- I/Q constellation → SVG
Antenna impedance¶
Closed-form dipole input impedance by the induced-EMF method (validated against the textbook 73.1 + j42.5 Ω half-wave result):
| Function | Returns |
|---|---|
DIPOLER(length_wl, [radius_wl]) |
input resistance (Ω) |
DIPOLEX(length_wl, [radius_wl]) |
input reactance (Ω) |
RADRESIST(length_wl) |
radiation resistance (Ω) |
RESONANTLEN([radius_wl]) |
resonant length (wavelengths), just under 0.5 λ |
Antenna modeling — Method of Moments & NEC¶
For real wire-antenna analysis, abax has a thin-wire Method of Moments solver (pure stdlib), available in the Python console:
from abax.core.science import mom, wire_mom, nec
mom.dipole_input_impedance(0.5, 1e-3) # a straight dipole
wire_mom.yagi(0.47, [(0.5, -0.25), (0.45, 0.15)], spacing_wl=0.2) # a Yagi
mom— a straight center-fed dipole. A single basis reproduces the induced-EMF impedance to 5 significant figures; the converged multi-segment result matches NEC.wire_mom— arbitrary 3-D wire structures (bent wires, V antennas, parasitic Yagi arrays), with a far-field pattern and front-to-back ratio. It also models multi-wire junctions and an image-plane ground reflection — see Junctions & ground reflection below.nec— read and write NEC2.necdecks (parse_nec/to_nec/solve), so abax exchanges models with 4nec2 / EZNEC / xnec2c. The Antenna pattern viewer's Export NEC button writes a deck for the current geometry.
Junctions & ground reflection¶
wire_mom is more than a straight dipole — two capabilities let it model real
installed antennas rather than an idealised element in free space:
- Multi-wire junctions. When several wires share an endpoint (to within a tiny node tolerance) they form a junction, and the solver enforces Kirchhoff current continuity there instead of pinning the shared point to zero current. At a junction of degree d it builds d − 1 piecewise-sinusoidal bases (a reference arm carrying current into the node, each other arm carrying a share out), so the current the solution pushes into one arm equals the sum it draws out of the rest. This is what lets verticals with radials, loops, and fed T-junctions solve correctly. A single wire's ordinary interior node is just the d = 2 case and reduces to the classic before/after pair, so the free-space single-wire path is unchanged.
- Image-plane ground reflection.
radiation_vector_ground/far_field_intensity_groundsuperpose the structure (assumed at z ≥ 0) with its image in a horizontal ground plane at z = 0: each element gets a mirror image whose horizontal current is negated and vertical current kept, scaled by a reflection coefficient. That turns the free-space elevation cut — which is symmetric about the horizon — into a real take-off pattern for a given install height and ground, asymmetric about the horizon and zero below it. A perfect (PEC) ground uses Γ = −1 (horizontal) / +1 (vertical); a finite ground uses a Fresnel reflection coefficient from a relative permittivity and conductivity (Ground("finite", …)).
Both are surfaced in the Antenna modeler dialog (Tools → Radio → Antenna
modeler, backed by wire_mom). Its Ground chooser offers Free space
(the classic symmetric pattern), Perfect ground (structure on the plane), and
Perfect ground + height (which enables a Height above ground (λ) field and
lifts the geometry before folding in the image reflection). Choose an elevation
cut with a ground option and the plot becomes a genuine over-ground take-off
pattern; the dialog labels it (over ground) so it is never mistaken for the
free-space cut. Over-ground cuts always use the built-in image model even when
PyNEC is present, since PyNEC's free-space read-back cannot express the take-off
pattern.
Optional PyNEC solver (reference-grade)¶
For reference-grade accuracy abax can hand a deck to PyNEC (the SWIG binding
to the classic NEC-2 engine) when it is installed — Radio → Solve NEC deck
(PyNEC), backed by engine/necpy.py. It is a fully optional dependency
with a graceful fallback: if PyNEC is not importable, abax silently uses its
own built-in Method-of-Moments solver instead, so nothing breaks. abax --deps
reports whether PyNEC is present.
Platform note (why it may be absent). PyNEC is a compiled C++/SWIG
extension and does not publish wheels for every platform — notably there are
no Windows wheels. It is included in the nec extra and in all (so picking the
All feature preset attempts it), but on a machine
without a matching wheel that best-effort build can fail quietly; abax then just
keeps using the built-in solver. This is deliberate — PyNEC is a
nice-to-have accelerator, not a requirement.
To install it yourself:
On Windows (or any platform lacking a wheel) the build needs a C/C++ toolchain
and SWIG on PATH — e.g. MSVC Build Tools plus swig. If that is more than you
want, do nothing: the built-in mom / wire_mom / nec path above is the
supported default and matches NEC on the validation cases.
Signal / DSP¶
RF signal work is served by the no-numpy DSP stack (Tools → Signal / data tool):
FFT / STFT / spectrogram, Welch PSD (real one-sided and complex I/Q
two-sided — a two-column selection is read as quadrature), interpolation,
Butterworth/FIR filters, and ODE solvers. See
data-science.md and the console modules fft, spectral,
filters, signal.