Skip to content
dc0skPublic

About

A free and modern NEC client written in rust.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

fnec-rust

version license

fnec-rust is a Rust-native antenna modeling workspace targeting near-100% practical compatibility with 4nec2, while keeping the codebase modular, testable, and portable.

Features

  • 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 I2 makes the structure occur I2 times, 360°/I2 apart 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 hallen as a receiving antenna — induced segment currents rather than a feedpoint impedance. A RECEIVE_PATTERN table 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 -1 suppresses the CURRENTS table, PT 0 prints all segments
    • TL / NT: transmission lines (NEC-2 TL layout, 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-51seg gives 63.35 − j213.68 Ω (nec2c 64.51 − j212.78 Ω); a 50 Ω, 0.1 m TL between two λ/2 dipoles gives 84.05 + j28.65 Ω (nec2c 84.826 + j31.131 Ω). An unusable card, or fnec's retired TL … NSEG TYPE … layout, is refused with an error (the latter with the NEC-2 rewrite)
  • 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-51seg regression 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-51seg is 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-deferred and dipole-gn2-near-ground-51seg are regression-gated
      • Wires standing on perfect ground (GN 1) — the ground-mounted monopole — are solved by explicit images (FND-082; monopole-pec-26seg 39.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 GN card alone, as in nec2c; a GN type outside -1..=2 is refused by name (FND-241)
      • GE I1 says, as NEC-2 reads it, whether a wire end at z = 0 joins its image: 1 joins it (the ground-mounted monopole), 0 and -1 leave it open, so a wire standing on the ground under GE 0 is refused naming GE 1 (FND-242); GE outside -1..=1, and GE 1 with no GN ground, 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-loaded top-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 mpie where the MPIE takes it
  • Segment current distribution table in CLI output (CURRENTS section after feedpoint table)
  • RP radiation-pattern execution in CLI output (RADIATION_PATTERN section 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
    • sinusoidal falls back to Hallen on single collinear chains when its residual budget is exceeded, and the budget is user-tunable via --sin-fallback-rel-max (or FNEC_SIN_FALLBACK_REL_MAX)
  • CLI binary fnec with 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. See docs/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

Quick start

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.

Support fnec-rust

Donate via PayPal

If this project helps your work, please consider supporting ongoing development:

https://www.paypal.com/donate/?hosted_button_id=WY9U4MQ3ZAQWC

Goals

  • 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)

Workspace layout

crates/
	nec_parser/
	nec_model/
	nec_solver/
	nec_accel/
	nec_report/
	nec_project/
apps/
	nec-cli/
	nec-gui/
docs/

Contributor setup

Install the required cargo tools:

cargo install cargo-audit cargo-sbom cargo-fuzz

Install the local git hooks:

make install-hooks

This configures core.hooksPath to use .githooks/.

Local workflow checks

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.

Troubleshooting: Mobile approval dialogs

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.

Version bump workflow

When bumping the workspace version:

  1. Update Cargo.toml.
  2. Regenerate the SPDX SBOM: cargo sbom --output-format spdx-json > SBOM.spdx.json
  3. Update docs/changelog.md.
  4. Update docs/releasenotes.md.
  5. Update documentation references that include the new version.

Branch policy

main stays protected. Work happens on feature branches only, followed by pull requests.

About

A free and modern NEC client written in rust.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages