Open Source MoM Solver for Antenna Design and Analysis
Get involved at https://www.openresearch.institute/getting-started/
Find #antenna-design channel on ORI Slack after securing an invitation (see link above).
Project: Conformal Method of Moments (CMoM) Antenna Simulation Engine
Organization: Open Research Institute (ORI)
License: CERN-OHL-S-2.0 (hardware) / GPL-3.0-or-later (software)
Status: Pre-development, Design and Documentation Phase
Revision: 0.1
The Method of Moments (MoM) is the foundational numerical technique for wire antenna simulation. The dominant open source implementation, NEC-2, was released into the public domain by Lawrence Livermore National Laboratory and remains the engine underlying most accessible antenna modeling tools. However, NEC-2 uses straight wire segments and a thin-wire kernel approximation, which introduces systematic errors in the following categories.
- Curved antennas (helices, loops, spirals)
- Closely spaced parallel wires (open-wire transmission lines, tightly wound helices)
- Thick conductors (if the wire radius is a significant fraction of segment length)
- Bent wires at acute angles (segment-to-segment near-field interaction artifacts makes a mess)
The Conformal Method of Moments (CMoM) with Exact Kernel addresses all of these limitations. It replaces straight segments with curved conformal segments that follow the actual wire geometry, and replaces the thin-wire kernel with the full cylindrical surface integral, evaluated with a technique called adaptive numerical quadrature.
To the best of ORI's knowledge, no open source implementation of CMoM with Exact Kernel currently exists. The only known production implementation is proprietary (AN-SOF Antenna Simulator, Golden Engineering LLC). The goal of this project is to produce the first open source CMoM engine, making high-fidelity curved-wire antenna simulation available to the amateur radio satellite service, academic researchers, and the broader RF engineering community.
This project phase covers the design and documentation work only. No production code will be written until the design documents for all phases are complete, reviewed, and approved. The output of this phase is a repository of design documents that fully specify the architecture, mathematics, interfaces, and validation strategy for each development phase.
The subsequent development phases (not yet in scope) will implement the engine described. This means we are following a 1. design 2. document 3. code and 4. test process.
The MoM/CMoM engine decomposes naturally into four sequential computational phases. Each phase is a candidate for independent development, testing, and validation. This decomposition drives the project structure and makes it more possible for open source contributors to step in and participate. Many hands make light work.
What it does: Accepts a wire structure description and produces a segment mesh. This is a set of conformal (curved) cylindrical segments, each described by its geometry, material properties, and connectivity.
Inputs: Wire descriptions (endpoints, radius, material, number of segments), source locations, load locations.
Outputs: Ordered list of conformal segments with parametric descriptions; connectivity graph; source and load maps.
Key design decisions:
- Native wire description format (internal representation)
- Parametric representation of curved segments (arc, helix, spline)
- Segment length guidelines relative to wavelength
- Handling of wire junctions and T-connections
- NEC input deck parsing (see Section 6!)
Validation: Segment counts, geometric continuity at junctions, visual inspection of discretized geometry against analytic wire descriptions.
What it does: Computes every element Z[m,n] of the N×N impedance matrix, where Z[m,n] is the interaction between testing function m and basis function n via the exact kernel Green's function integral. Like matrices? Good! Because that's what this is all about.
Inputs: Segment mesh from Phase 1; frequency.
Outputs: Dense complex N×N impedance matrix [Z].
Computational character: O(N²). Embarrassingly parallel. Every element is independent. This is the primary target for parallelization via Rayon.
Glad you asked!
Rayon is a Rust library (crate) for data parallelism. It lets you parallelize iterator-based code with minimal changes — often just replacing .iter() with .par_iter(). The classic example is this:
// Sequential
segments.iter().for_each(|s| compute(s));
// Parallel with Rayon — one word change
segments.par_iter().for_each(|s| compute(s));
Rayon handles all the thread pool stuff, the so-called "work stealing", and load balancing all under the hood. You don't spawn threads manually or manage synchronization. You just describe what's independent and Rayon figures out how to distribute it across cores. This is why we're doing this in Rust instead of C++.
Why it matters for Arcanum specifically?
The matrix fill is the perfect Rayon workload. You have N² elements to compute, every single one is independent of every other, and each computation (a Gauss-Legendre quadrature integral) is non-trivial enough to justify the parallelism overhead. In pseudocode:
z_matrix.par_iter_mut()
.enumerate()
.for_each(|(idx, element)| {
let m = idx / n_segments;
let n = idx % n_segments;
*element = compute_zmn(segments[m], segments[n], frequency);
});
Every core on your machine fills its share of the matrix simultaneously. On an 8-core machine you get roughly 8x speedup on the dominant bottleneck essentially for free. This is a really great thing.
The D&D analogy (you knew one was coming): if the matrix fill were a dungeon map that you had to draw, sequential iteration is one cartographer drawing every room alone. Rayon is handing out sections of the map to every cartographer you can find, all simultaneously, with no coordination needed because the rooms don't depend on each other.
It's one of the most loved crates in the Rust ecosystem precisely because the payoff-to-effort ratio is extraordinary. We are going to take advantage of that ratio in Arcanum.
Key design decisions:
- Exact kernel formulation (full cylindrical surface integral vs. thin-wire approximation?)
- Adaptive Gauss-Legendre quadrature order for non-singular elements
- Special treatment of self-impedance (singular) and near-neighbor (near-singular) elements
- Parallelization strategy (element-level, row-level? some other level?)
- Numerical precision targets (how good is good enough?)
Validation: Self-impedance of a thin straight segment against analytic thin-wire result (should converge to thin-wire limit as radius goes to 0); mutual impedance between parallel segments against published tables; matrix symmetry checks.
What it does: Solves the linear system [Z][I] = [V] for the current vector [I], given the excitation vector [V] assembled from source definitions. Sounds easy, right?
Inputs: Impedance matrix [Z] from Phase 2; excitation vector [V] from source definitions. Now we're cooking.
Outputs: Complex current vector [I] — one current amplitude per segment.
Computational characteristics: Complexity is O(N³) for direct LU factorization. For antenna sizes relevant to ORI (ground station antennas maybe, terrestrial antennas and feeds, yagis, modest arrays for 219 MHz, etc.), direct solve via LU factorization is the initial target. Iterative solvers (GMRES) are a future extension.
Key design decisions:
- LU factorization via
faercrate (Rust dense linear algebra) - Excitation vector assembly (delta-gap voltage source model; current source model)
- Multiple right-hand sides (frequency sweep efficiency)
- Condition number monitoring and ill-conditioning diagnostics (can't have it go off the rails)
Validation: Input impedance of a half-wave dipole is 73 + j42.5 Ω (classical result); small loop radiation resistance has a closed-form analytic result; two-wire transmission line impedance to exact transmission line theory.
What it does: Computes all observable antenna parameters from the solved current distribution [I].
Inputs: Current vector [I] from Phase 3; segment mesh from Phase 1; frequency; observation geometry.
Outputs: Input impedance, VSWR, radiated power, gain, directivity, radiation patterns (2D and 3D), near fields, radar cross section (RCS). This is the stuff we care about.
Computational character: Fast relative to Phases 2 and 3. Near-field computation at many observation points can be parallelized but is not the bottleneck.
Key design decisions:
- Far-field integration method?
- Near-field computation grid specification?
- Pattern normalization conventions??
- Output format (native? interface to plotting layer so people can use their own??)
Validation: Half-wave dipole pattern shape (figure-8 in E-plane, omnidirectional in H-plane); isotropic radiator gain = 0 dBi; energy conservation check (radiated power vs. input power). The Hello World of atenna design.
Best of both worlds with Rust and Python.
| Layer | Technology | Rationale |
|---|---|---|
| Core engine | Rust | Performance, memory safety, parallelism via Rayon |
| Python bindings | PyO3 | Jupyter notebook usability, integration with existing ORI tooling |
| Scripting / post-processing | Python | NumPy, Matplotlib, existing link budget notebooks |
| Linear algebra | faer (Rust) |
Dense LU, benchmarks competitive with LAPACK |
| Parallelism | rayon (Rust) |
Data-parallel matrix fill with zero-synchronization |
| Quadrature | gauss-quad (Rust) |
Gauss-Legendre nodes and weights |
| Geometry | nalgebra (Rust) |
3D vector math, parametric curves |
┌─────────────────────────────────────┐
│ Python API / Jupyter interface │ gives user-facing: notebooks, scripts, visuals
├─────────────────────────────────────┤
│ PyO3 binding layer │ has type conversion, error mapping
├─────────────────────────────────────┤
│ Rust core library │
│ ┌───────────┐ ┌────────────────┐ │
│ │ Phase 1 │ │ Phase 2 │ │
│ │ Geometry │→ │ Matrix Fill │ │
│ │ (mesh) │ │ (Rayon parallel│ │
│ └───────────┘ │ exact kernel) │ │
│ └───────┬────────┘ │
│ ┌───────────┐ │ │
│ │ Phase 3 │←─────────┘ │
│ │ Solve │ │
│ │ (faer LU)│ │
│ └─────┬─────┘ │
│ ↓ │
│ ┌───────────┐ │
│ │ Phase 4 │ │
│ │ Post- │ │
│ │ Processing│ │
│ └───────────┘ │
└─────────────────────────────────────┘
Each phase exposes a clean data boundary with inputs and outputs defined like ports in VHDL modules. Phases communicate through well-defined structs, not shared mutable state. This mirrors the AXI-Stream philosophy used in ORI's FPGA modem work. We work hard to have well-defined handshake points between independent processing stages.
Validation is not an afterthought. Each phase has analytic ground truth available from classical antenna theory. The validation suite is part of the design document for each phase and must be specified before implementation begins. The answers are findable and well described. We won't have to guess that it works because we can validate at each phase.
| Case | Phase | Expected Result | Source |
|---|---|---|---|
| Half-wave thin dipole impedance | 2, 3 | 73.1 + j42.5 Ω | Classical; King-Middleton |
| Small circular loop radiation resistance | 2, 3 | Closed form: R = 20π²(C/λ)⁴ | Classical |
| Small square loop radiation resistance | 2, 3 | Same limit as circular | Shape independence |
| Two-wire transmission line impedance | 2, 3 | Z₀ = (120/√εᵣ)·ln(D/d) | Transmission line theory |
| Half-wave dipole far-field pattern | 4 | cos²(θ/2)/sin(θ) shape | Classical |
| Helix axial mode impedance | 2, 3, 4 | ~140 Ω (Kraus) | Kraus, Antenna Theory |
Note on NEC-2 as a validation reference: NEC-2 is a different solver (straight-segment, thin-wire kernel MoM) and is not used as a primary validation target. In the degenerate limit of straight wires with very small radius-to-segment-length ratios, CMoM must converge to the same result as the thin-wire approximation. This is a necessary but minimal sanity check, not a correctness standard. All primary validation is against analytic closed-form results from classical antenna theory.
For each validation case, results must be demonstrated to converge monotonically as segment count N increases. Convergence plots are required deliverables for each phase design document.
The .nec file format is the lingua franca of wire antenna modeling. Decades of community antenna models exist as .nec files. These files come from EZNEC users, 4NEC2 users, academic NEC-2 runs, and direct NEC card deck authors. Supporting .nec import means every one of those models can be opened in this engine immediately, with no remodeling effort, and simulated with higher accuracy than the original NEC-2 solver could provide.
This is a first-class adoption requirement, not a compatibility convenience.
A .nec file is a complete simulation input deck. It is not only a geometry description. It contains all information needed to run a complete simulation: geometry, excitation, loads, ground model, frequency sweep, and output requests. It uses a card-based format inherited from punched-card computing, where each line begins with a two-letter card mnemonic.
The .nec format is entirely independent of the NEC-2 solver. It is a file interchange format that this engine reads and maps onto its own internal data structures. We do not use NEC-2 as a solver at any point.
Each card type maps to a specific phase's data structures. The parser is a frontend that fans out into the pipeline.
| Card | Description | Phase |
|---|---|---|
GW |
Straight wire segment | 1 — Geometry |
GA |
Arc (curved wire) | 1 — Geometry |
GH |
Helix | 1 — Geometry |
GM |
Geometry move/rotate/scale | 1 — Geometry |
GS |
Geometry scale | 1 — Geometry |
GE |
Geometry end (required terminator) | 1 — Geometry |
EX |
Excitation (voltage or current source) | 3 — Matrix Solve |
LD |
Load (impedance on segment) | 3 — Matrix Solve |
FR |
Frequency (single or sweep) | 2, 3 — Matrix Fill / Solve |
GN |
Ground definition | 1, 2 — Geometry / Matrix Fill |
RP |
Radiation pattern output request | 4 — Post-Processing |
NE |
Near electric field output request | 4 — Post-Processing |
NH |
Near magnetic field output request | 4 — Post-Processing |
EN |
End of input deck | Parser |
- The parser must be a standalone module with no dependency on phase implementations. It produces intermediate data structures that each phase consumes independently.
- Unknown or unsupported cards must produce a warning, not a silent failure or crash.
- The parser must round-trip: a model loaded from
.necand re-exported should produce a valid.necfile that NEC-2 can also read (export is a secondary goal but the round-trip property constrains the internal representation). GW(straight wire) is the minimum viable card set for initial implementation.GAandGHare high priority for ORI use cases (helix ground station antennas).
NEC import does not imply NEC-2 solver compatibility. The CMoM engine will produce different (more accurate) results than NEC-2 on the same input geometry. This is expected and desirable — particularly for curved geometry cards (GA, GH) where NEC-2's straight-segment approximation degrades accuracy. Users should expect improved results, not identical results.
docs/
├── nec-import/
│ ├── design.md Parser architecture, card routing, error handling
│ ├── card-reference.md Supported cards with field definitions
│ └── validation.md Round-trip tests, known-good .nec reference decks
Arcanum/
├── README.md This document
├── LICENSE
├── Cargo.toml Cargo workspace root (no code)
├── pyproject.toml maturin build config for Python package
│
├── crates/ One Rust crate per computational phase
│ ├── arcanum-nec-import/ NEC deck parser — rlib, no external deps
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs pub fn parse(), pub fn parse_file()
│ │ ├── cards.rs NecCard enum and all card structs
│ │ ├── lexer.rs Stage 1: raw text → ParsedDeck
│ │ ├── router.rs Stage 2: ParsedDeck → SimulationInput
│ │ ├── tag_registry.rs Tag → wire index + segment count map
│ │ ├── errors.rs ParseError, ParseWarnings
│ │ └── tests/ Rust unit tests (V-PARSE, V-FMT, V-ROUTE,
│ │ └── ... V-ERR, V-WARN from validation.md)
│ │
│ ├── arcanum-geometry/ Phase 1: geometry discretization — rlib
│ │ ├── Cargo.toml deps: arcanum-nec-import, nalgebra
│ │ └── src/
│ │ ├── lib.rs
│ │ └── tests/
│ │
│ ├── arcanum-matrix-fill/ Phase 2: impedance matrix fill — rlib
│ │ ├── Cargo.toml deps: arcanum-geometry, rayon, gauss-quad
│ │ └── src/
│ │ ├── lib.rs
│ │ └── tests/
│ │
│ ├── arcanum-matrix-solve/ Phase 3: LU solve + excitation — rlib
│ │ ├── Cargo.toml deps: arcanum-matrix-fill, faer
│ │ └── src/
│ │ ├── lib.rs
│ │ └── tests/
│ │
│ ├── arcanum-postprocess/ Phase 4: patterns and near fields — rlib
│ │ ├── Cargo.toml deps: arcanum-matrix-solve, nalgebra
│ │ └── src/
│ │ ├── lib.rs
│ │ └── tests/
│ │
│ └── arcanum-py/ PyO3 bindings — cdylib (Python extension)
│ ├── Cargo.toml deps: all five crates above, pyo3
│ └── src/
│ └── lib.rs #[pymodule], #[pyfunction], #[pyclass] wrappers
│
├── python/
│ └── arcanum/ Python package (installed alongside extension)
│ ├── __init__.py Imports compiled extension, re-exports symbols
│ ├── nec_import.py Python helpers and type stubs for nec-import
│ ├── geometry.py Phase 1 helpers [future]
│ ├── matrix_fill.py Phase 2 helpers [future]
│ ├── matrix_solve.py Phase 3 helpers [future]
│ └── postprocess.py Phase 4 helpers [future]
│
├── tests/ Python integration tests (pytest, via PyO3)
│ ├── conftest.py Top-level fixtures
│ ├── nec_import/
│ │ ├── conftest.py reference_deck() fixture
│ │ ├── test_parse.py V-PARSE cases
│ │ ├── test_fmt.py V-FMT cases
│ │ ├── test_errors.py V-ERR cases
│ │ ├── test_warnings.py V-WARN cases
│ │ └── test_real.py V-REAL-001 through V-REAL-004
│ ├── geometry/ Phase 1 tests [future]
│ ├── matrix_fill/ Phase 2 tests [future]
│ ├── matrix_solve/ Phase 3 tests [future]
│ └── postprocess/ Phase 4 tests [future]
│
├── docs/
│ ├── nec-import/
│ │ ├── design.md Parser architecture, card routing, error handling
│ │ ├── card-reference.md Supported cards with field definitions
│ │ ├── validation.md Test cases and reference deck specifications
│ │ ├── plan.md Implementation plan
│ │ └── reference-decks/ Known-good .nec files for V-REAL test cases
│ ├── phase1-geometry/
│ │ ├── design.md Wire format, segment types, junction handling
│ │ ├── math.md Parametric curve representations
│ │ └── validation.md Geometric test cases
│ ├── phase2-matrix-fill/
│ │ ├── design.md Kernel formulation, quadrature strategy
│ │ ├── math.md Exact kernel integral derivation
│ │ └── validation.md Impedance matrix test cases
│ ├── phase3-matrix-solve/
│ │ ├── design.md LU solver, excitation assembly
│ │ ├── math.md EFIE formulation, excitation models
│ │ └── validation.md Dipole impedance, loop resistance
│ ├── phase4-postprocessing/
│ │ ├── design.md Field integrals, pattern computation
│ │ ├── math.md Far-field and near-field formulas
│ │ └── validation.md Pattern shape, energy conservation
│ └── references/
│ └── bibliography.md Key literature
│
└── .github/
└── workflows/
└── ci.yml Rust tests + Python tests + lint on push/PR
Key reference materials for citations and review. Thank you to Microwave Update for the list.
- Champagne, Williams & Wilton — The Use of Curved Segments for Numerically Modeling Thin Wire Antennas (1992) — foundational curved-wire MoM
- Rogers & Butler — An Efficient Curved-Wire Integral Equation Solution Technique (2001) — CMoM implementation
- Harrington, R.F. — Field Computation by Moment Methods (1968) — foundational MoM text
- Burke & Poggio — NEC-2 Method of Moments Code (1981) — NEC-2 reference implementation
- Fikioris, G. — exact kernel formulations for cylindrical antennas
- Kraus, J.D. — Antennas — validation reference for helix and loop cases
- King & Middleton — thin dipole impedance analytic results
- Wait, J.R. — ground loss models for LF/MF antennas
The design phase is complete when:
- All four phase design documents (geometry, matrix fill, matrix solve, post-processing) are written, reviewed, and merged to main.
- The NEC import design document is written, reviewed, and merged to main, including the card reference and validation cases.
- All mathematical derivations are present in the
math.mdfiles with sufficient detail that an independent implementer could write the code from the document alone. - All validation cases are specified with expected numerical results and convergence criteria.
- The repository structure is established and documented.
- At least two ORI contributors have reviewed each design document.
See CONTRIBUTING.md for environment setup, build instructions, and how to run the test suite and linters.
The NEC input deck format accepted by the parser is documented in docs/nec-import/input-format.md.
Document maintained by Open Research Institute. Contributions welcome under our participant and developer policies at https://www.openresearch.institute/developer-and-participant-policies/.