UNCLASSIFIED
TM-TOOL-003
NEC ANTENNA MODELING SYSTEM
NEC2/NEC4 File Generation, Running, Parsing, and Optimization
Prepared by: Mervyn Martin, KO6NNH  •  Merced, California  •  26 May 2026
Amateur Radio / Electronics — Not for commercial use

Chapter 1 — Introduction and Scope

This manual covers the NEC antenna modeling system: a Python toolkit that generates NEC2/NEC4 input decks, runs the engine, parses output, and displays radiation patterns, impedance, and gain. The system supports dipoles, verticals, Yagis, log-periodics, quad loops, phased arrays, and ground-mounted verticals with buried radials. An optional REST API exposes the toolkit to web front-ends or remote callers.

Engines supported: nec2c (Linux, free), NEC4 (licensed), 4nec2 (Windows GUI via Wine), necpp (open-source alternative).

Chapter 2 — Theory of Operation

2-1 NEC Card Format Summary

NEC input is a plain-text deck of two-character card mnemonics, one per line:

CardPurposeKey fields
CMCommentFree text
CEEnd comments(no fields)
GWWire segmenttag, segs, x1 y1 z1, x2 y2 z2, radius
GSScale geometryscale factor (e.g., 0.3048 = feet to meters)
GEEnd geometry0=free space, 1=ground present
GNGround parameterstype, εr, σ (good earth: 13, 0.005)
EXExcitation (source)tag, segment, V_real, V_imag
FRFrequencystart MHz, step MHz, N steps
RPRadiation patterntheta/phi start, step, N points, mode
ENEnd of input(last card)

2-2 Segment Count Guidelines

NEC accuracy depends on adequate segmentation. Rule: 10–20 segments per wavelength, odd count preferred for symmetric elements with a center source. Minimum 3 segments per wire. The system function:

def recommended_segments(length_m, freq_mhz, min_segs=3):
    wavelength = 299.792458e6 / (freq_mhz * 1e6)
    segs = max(min_segs, int(length_m / wavelength * 10))
    return segs if segs % 2 == 1 else segs + 1

Chapter 3 — Installation and Dependencies

3-1 Python Environment

Python 3.10 or later is required. Install dependencies:

pip install -r requirements.txt

Core packages: numpy, scipy, pandas, matplotlib, fastapi, uvicorn, pydantic, httpx, tqdm, requests. Optional: scikit-rf (Smith chart), plotly (interactive 3D patterns).

3-2 NEC2 Engine Installation (Debian/Ubuntu)

sudo apt install nec2c

Verify: nec2c --version should return without error. The runner (nec_runner.py) auto-detects available engines in the order: nec2c, nec4, necpp, 4nec2.

Chapter 4 — Generating NEC Input Files

4-1 Dipole Example

from nec_generator import Dipole, NECModel
ant = Dipole(freq_mhz=14.25, height_m=10.0)
model = ant.to_nec_model()
model.write("dipole_20m.nec")

This generates a half-wave dipole resonant at 14.25 MHz, 10 m above real ground (εr=13, σ=0.005 S/m). The GW card for each half-element uses recommended_segments() to set segment count automatically.

4-2 Vertical with Radials

from nec_generator import VerticalWithRadials
ant = VerticalWithRadials(freq_mhz=7.1, n_radials=32, radial_length_m=10.0,
                          height_m=0.1)
model = ant.to_nec_model()
model.write("vertical_40m.nec")

4-3 Frequency Sweep

from freq_sweep import FrequencySweep
sweep = FrequencySweep(model, start_mhz=1.8, stop_mhz=30.0, n_steps=200)
results = sweep.run()
sweep.plot_impedance(results)
sweep.plot_swr(results, z0=50.0)

Chapter 5 — Running and Parsing

5-1 Running the Engine

from nec_runner import NECRunner, NECEngine
runner = NECRunner(engine=NECEngine.NEC2)
result = runner.run(model)
if not result.success:
    print(result.stderr)

The runner writes a temporary .nec file, invokes nec2c as a subprocess, captures stdout/stderr, and returns a NECResult object. Timeout default: 60 seconds (adjustable for large sweeps).

5-2 Parsing Output

from nec_parser import NECOutputParser
parser = NECOutputParser()
points = parser.parse(result.output_file)
# points: list of {freq_mhz, gain_dbi, z_real, z_imag, swr, theta, phi}

Chapter 6 — Optimization

Three optimizers are available:

  • genetic_optimizer.py: Genetic algorithm; best for multi-parameter problems with discontinuous cost functions. Suitable for Yagi element lengths + spacings.
  • pso_optimizer.py: Particle swarm; faster convergence for smooth continuous cost surfaces. Suitable for matching network component values.
  • batch_optimizer.py / batch_multiband.py: Evaluates a parameter grid; useful for initial design-space exploration before running a stochastic optimizer.

Cost function examples: maximize F/B ratio, minimize SWR at target frequency, maximize gain over ground at 20° elevation.

Chapter 7 — Verification and Acceptance

  1. Run the included test case (half-wave dipole in free space at 14.25 MHz). Expected: feedpoint impedance 73+j42.5Ω, gain 2.15 dBi.
  2. Verify against ARRL Antenna Book reference tables for dipole impedance vs. height above ground. Deviations >5% indicate a segmentation or ground parameter error.
  3. For a calibration-quality check: compare modeled resonant frequency against NanoVNA measurement of a physical antenna. Agreement within 2% is typical for wire antennas over flat ground.
  4. Log: NEC version, Python version, test antenna type, modeled vs. reference gain, modeled vs. measured resonant frequency.

Appendix A — Common NEC Errors

ErrorCauseFix
SEGMENT FAILUREWire too short for segment countReduce min_segs or increase length
Impedance = 0+j0Source on wrong tag/segmentVerify EX card tag matches GW tag
Gain >30 dBiWire below ground (Z < 0)Ensure all Z coordinates ≥ 0 or use buried-radial GN type
NaN impedanceSingular matrix (parallel wires touching)Increase wire separation >2 × radius

Appendix B — Dipole Resonant Length Formula

L_half (meters) = (142.5 / f_MHz) × k
  k = velocity factor:
    Bare wire in free space: k = 0.975
    Wire near ground (h < λ/4): k = 0.94–0.97
    Insulated wire: k = 0.93–0.97 depending on insulation