fnec-rust is a Rust-native antenna modeling workspace targeting near-100% practical compatibility with 4nec2, while keeping the codebase modular, testable, and portable.
- Parse 4nec2 / NEC2 deck files (GW, GM, GR, GE, GN, EX, FR, RP, EN cards)
- GM (Geometry Move): rotate and/or translate wire ranges in place, or append one transformed copy when
tag_increment > 0 - GR (Geometry Repeat): as NEC-2 reads it —
GR I1 I2makes the structure occurI2times, 360°/I2apart about z - GS (scale) and GX (reflect), as nec2c reads them
- EX types 1 / 2 / 3 (incident plane wave: linear, right- and left-elliptic): solved on
--solver hallenas a receiving antenna — induced segment currents rather than a feedpoint impedance. ARECEIVE_PATTERNtable is emitted only when the card defines more than one incidence direction (NTHETA·NPHI > 1). Straight and bent wires, degree-3+ junctions and closed loops (on the section graph, FND-162 stage 5), loaded or not (the loads are columns of the graph system, FND-197) - EX type 4 (current source): solved; the feedpoint reports
Z = V_port / i0, which matches the voltage-source impedance on the same geometry because impedance is a property of the port and not of the drive - EX type 5 (NEC's current-slope-discontinuity voltage source): fnec models it by the applied-field method, so it solves and gives the same answer as type 0. That is a documented approximation of NEC's numerics, not an identity of the two cards
- PT: print control is applied at runtime —
PT -1suppresses theCURRENTStable,PT 0prints all segments - TL / NT: transmission lines (NEC-2
TLlayout, plus fnec's velocity-factor and loss-dB extensions) and two-port networks are solved as networks across the port segments' gaps, in parallel, as in NEC-2 (--solver hallen, voltage sources only). Validated against nec2c:dipole-nt-tl-equiv-freesp-51seggives 63.35 − j213.68 Ω (nec2c 64.51 − j212.78 Ω); a 50 Ω, 0.1 mTLbetween two λ/2 dipoles gives 84.05 + j28.65 Ω (nec2c 84.826 + j31.131 Ω). An unusable card, or fnec's retiredTL … NSEG TYPE …layout, is refused with an error (the latter with the NEC-2 rewrite)
- GM (Geometry Move): rotate and/or translate wire ranges in place, or append one transformed copy when
- Hallén MoM solver — physically accurate feedpoint impedance for thin-wire antennas
- Validated: 51-segment λ/2 dipole at 14.2 MHz → 79.33 + j45.04 Ω (nec2c 79.35 + j46.22 Ω on the same mesh, 79.656 + j46.647 at 9 × the mesh, the corpus's converged reference; every free wire end refined into thirds, FND-227)
- GN 1 (perfect ground at z=0) is supported via image method;
dipole-ground-51segregression is 74.76 + j42.78 Ω (nec2c 74.79 + j43.94 Ω on the same mesh, 75.073 + j44.377 at 9 ×) - GN 0 (simple finite-ground reflection coefficient path) is supported;
dipole-gn0-fresnel-51segis regression-gated - GN 2 low above-ground finite-conductivity cases are supported on the current scoped path; the supported subset is strictly above-ground (
z > 0);dipole-gn2-deferredanddipole-gn2-near-ground-51segare regression-gated - Wires standing on perfect ground (GN 1) — the ground-mounted monopole — are solved by explicit images (FND-082;
monopole-pec-26seg39.53 + j22.65 Ω vs nec2c 39.58 + j23.21 on the same mesh, 39.678 + j23.448 at 9 ×). Wires touching finite ground, or lying in or below the ground plane, fail fast with an actionable error - The ground comes from the last
GNcard alone, as in nec2c; aGNtype outside-1..=2is refused by name (FND-241) GE I1says, as NEC-2 reads it, whether a wire end at z = 0 joins its image:1joins it (the ground-mounted monopole),0and-1leave it open, so a wire standing on the ground underGE 0is refused namingGE 1(FND-242);GEoutside-1..=1, andGE 1with noGNground, are refused (FND-241)- Multi-wire junctioned / non-collinear Hallen (unique to fnec-rust): per-wire local cos(k·s) homogeneous vectors, KCL junction continuity rows, and correct passive-wire (zero) RHS — no other NEC2-compatible tool handles junctioned wire topologies via the Hallen integral equation; validated against nec2c on the
dipole-loadedtop-hat geometry (13.16 − j886.74 Ω; nec2c 13.463 − j896.03 on the same mesh, 13.153 − j882.44 at 9 ×, still moving at 27 ×, FND-289); external cross-check recommended for novel geometries - Degree-3+ (T/Y) junctions and closed loops on the default Hallén solver (FND-162 stages 2+3): a section graph gives every straight section its own homogeneous constants and closes each node with a Kirchhoff current row plus equal-scalar-potential rows (with the corner term); a loop is a cycle of sections. Converges to nec2c 1.3.1 with refinement, e.g. a stem-fed Y 3.19 → 1.68 → 0.74 % and a 1 λ square loop 1.84 → 1.05 → 0.57 % at 11/21/41 segments per wire (
crates/nec_solver/tests/graph_nec2c.rs). Scope: voltage sources (EX 0/5) or an EX 4 current source, lumped LD loads and TL/NT networks allowed, contact with perfect ground through the image problem, no plane wave, every straight section ≥ 2 segments; a junction or loop with a one-segment run is refused by name (FND-251), naming--solver mpiewhere the MPIE takes it
- GN 1 (perfect ground at z=0) is supported via image method;
- Validated: 51-segment λ/2 dipole at 14.2 MHz → 79.33 + j45.04 Ω (nec2c 79.35 + j46.22 Ω on the same mesh, 79.656 + j46.647 at 9 × the mesh, the corpus's converged reference; every free wire end refined into thirds, FND-227)
- Segment current distribution table in CLI output (
CURRENTSsection after feedpoint table) - RP radiation-pattern execution in CLI output (
RADIATION_PATTERNsection with theta/phi gain rows) - Corpus validation can track and optionally gate external parity candidates (RP and impedance) recorded in
corpus/reference-results.json - Pulse-basis and continuity-basis Pocklington solvers (EXPERIMENTAL — known to diverge for thin wires)
- Sinusoidal-basis Hallen solve path with guarded residual fallback to Hallen
sinusoidalfalls back to Hallen on single collinear chains when its residual budget is exceeded, and the budget is user-tunable via--sin-fallback-rel-max(orFNEC_SIN_FALLBACK_REL_MAX)
- CLI binary
fnecwith selectable solver and RHS modes - FR sweep support in CLI output and corpus validation gating
- Residual diagnostics printed to stderr on every run
- Second solver:
--solver mpie(subsectional mixed-potential EFIE) — handles near-ground currents and patterns, which the Hallen formulation does not, and also solves degree-3 (T/Y) junctions and closed loops; it is an alternative to the default Hallén solve for those. Opt-in and additive; the Hallen corpus is untouched. Seedocs/mpie-solver-scope.md - Sommerfeld surface-wave ground:
--ground-solver sommerfeld— a rigorous near-ground model in place of the reflection-coefficient approximation - Modular crate workspace: parser, model, solver, accel, report, project, CLI, GUI
cargo build --release
./target/release/fnec dipole.nec
Example deck (dipole.nec):
GW 1 51 0 0 -5.282 0 0 5.282 0.001
GE
EX 0 1 26 0 1.0 0.0
FR 0 1 0 0 14.2 0.0
EN
Example output (report contract v1):
FNEC FEEDPOINT REPORT
FORMAT_VERSION 1
FREQ_MHZ 14.200000
SOLVER_MODE hallen
PULSE_RHS Nec2
FEEDPOINTS
TAG SEG V_RE V_IM I_RE I_IM Z_RE Z_IM
1 26 1.000000 0.000000 0.009533 -0.005412 79.331456 45.036813
See docs/cli-guide.md for full option reference and docs/card-support-matrix.md for the NEC card support matrix. For the 3-D GUI workbench (cargo run -p nec-gui), see docs/gui-guide.md.
If this project helps your work, please consider supporting ongoing development:
https://www.paypal.com/donate/?hosted_button_id=WY9U4MQ3ZAQWC
- Primary compatibility target: 4nec2 input and workflow expectations
- Secondary input dialect support: xnec2c where it provides real benefit
- Solver implemented from scratch in Rust
- CLI and GUI frontends over a shared core
- Multithreaded CPU execution with staged GPU acceleration
- Explicit Linux-on-ARM targets: Raspberry Pi 4 (VideoCore VI GPU) and Raspberry Pi 5 (VideoCore VII GPU)
crates/
nec_parser/
nec_model/
nec_solver/
nec_accel/
nec_report/
nec_project/
apps/
nec-cli/
nec-gui/
docs/
Install the required cargo tools:
cargo install cargo-audit cargo-sbom cargo-fuzzInstall the local git hooks:
make install-hooksThis configures core.hooksPath to use .githooks/.
Run ./scripts/check-all.sh before pushing — it is the whole gate, across
both cargo trees. bindings/fnec_py is deliberately outside the workspace (a
cdylib with its own lockfile), so cargo fmt --all --check at the repository root
exits 0 on an unformatted bindings crate and CI then fails on it. Measured
both ways:
| misformat in | root fmt --all --check |
scripts/check-all.sh |
|---|---|---|
crates/nec_solver |
1 | 1 |
bindings/fnec_py |
0 — the blind spot | 1 |
Pass --fast to skip the test suite. It also runs check-doc-attachment.py, which
needs a base revision to catch a doc comment an insert has separated from its item;
the script picks the merge base with origin/main itself. The script needs
PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 for the bindings clippy run on a machine
whose Python is newer than pyo3 supports; it sets that itself, and CI must not —
CI pins the interpreter instead.
- Pre-commit:
cargo fmt --all -- --check,cargo test --workspace - Pre-push:
cargo audit - Docs validation:
./scripts/validate-doc-frontmatter.sh - Parser fuzz scaffold:
(cd fuzz && cargo fuzz run nec_parser_parse corpus/nec_parser_parse) - Remote ARM64 check (Pi over SSH):
bash ./scripts/pi-remote-workspace-check.sh <user@host> - Remote ARM64 benchmark CSV (Pi over SSH):
bash ./scripts/pi-remote-benchmark.sh <user@host> - Remote benchmark with explicit single-thread baseline + parallel CPU sweep:
FNEC_BENCH_EXECS="cpu-single cpu" bash ./scripts/pi-remote-benchmark.sh <user@host> - Remote benchmark with persistent history append:
FNEC_BENCH_HISTORY="benchmarks/pi-benchmark-history.csv" bash ./scripts/pi-remote-benchmark.sh <user@host> - Benchmark CSV delta view:
bash ./scripts/pi-benchmark-compare.sh <base.csv> <candidate.csv> - Benchmark CSV delta gate example:
bash ./scripts/pi-benchmark-compare.sh --max-delta-pct 15 --fail-on-mode-drift <base.csv> <candidate.csv> - Append benchmark CSV to persistent history:
bash ./scripts/pi-benchmark-history.sh append <candidate.csv> benchmarks/pi-benchmark-history.csv - Benchmark history trend summary:
bash ./scripts/pi-benchmark-history.sh trend benchmarks/pi-benchmark-history.csv
GitHub Actions includes benchmark-compare.yml, which runs on PRs and compares benchmarks/pi-base.csv vs benchmarks/pi-candidate.csv when both files exist. If either file is missing, the job reports a clean skip.
When it runs, it also writes a benchmark delta preview to the Actions job summary.
If you review or approve workflow/tool actions from a mobile device, some approval dialogs may be truncated, fail to render all options, or not submit reliably.
- Prefer desktop browser approval for multi-step actions (branch push, workflow changes, multi-file edits).
- If a mobile approval appears stuck, refresh and retry from desktop rather than re-running commands repeatedly.
- Keep command payloads and commit messages concise when you know approvals may be handled on mobile.
When bumping the workspace version:
- Update
Cargo.toml. - Regenerate the SPDX SBOM:
cargo sbom --output-format spdx-json > SBOM.spdx.json - Update
docs/changelog.md. - Update
docs/releasenotes.md. - Update documentation references that include the new version.
main stays protected. Work happens on feature branches only, followed by pull requests.