Loading NEC decks
antennaknobs can export any design as a NEC2 card deck;
read_nec is the reverse direction. (SimNEC .ssn circuits have their own
matching pair — see SimNEC round-trip — and MMANA-GAL
.maa models open too, see MMANA-GAL .maa models,
as do EZNEC .ez models, see EZNEC .ez models.) It parses a .nec file — the format
xnec2c, 4nec2, EZNEC, and fifty years of antenna handbooks all speak — into
wire geometry a design can return from build_wires, so you can solve, sweep,
and view a deck someone published without retyping its coordinates.
Set expectations first, though: a NEC deck is mostly frozen geometry, not a
parameterized design. The knobs are where antennaknobs earns its name — a
native builder expresses its dimensions as parameters and wavelength
fractions, so element lengths, spacings, and angles are all draggable and
optimizable, and design_freq moves the whole antenna to another band. A
plain deck has none of that structure to expose; coordinates are just numbers.
The exception is a deck written with 4nec2’s SY variables: each constant it
defines becomes a knob (see SY constants are knobs),
and so does each dcl constant a SimNEC file’s cards read (see
dcl constants are knobs).
Otherwise an imported deck supports the measurement frequency, a height
lift, and a whole-geometry scale — useful, but blunt. Treat the import as a
viewer for published decks, and as a source of dimensions when you decide a
design is worth porting to a real AntennaBuilder.
Quick start
Section titled “Quick start”You don’t need any Python to run a deck. Drop the .nec file in
~/.antennaknobs/designs/ (on Windows, %USERPROFILE%\.antennaknobs\designs\)
and it appears in the workbench’s design list as user.<filename>, with the
deck’s own frequency, per-wire radii, feed and network; the reload button
next to the design picker re-reads it after an edit. A deck is data, so
unlike a Python design it never asks to be allowed. For one-off CLI work the
same loader takes the file straight from a path: every subcommand accepts an
@file.nec builder spec — python -m antennaknobs draw --builder @my_yagi.nec
— and decks mix with named designs in --builders lists (see
Naming a design). In PowerShell quote the
whole spec, --builder "@C:\decks\my_yagi.nec": an unquoted @" opens a
here-string there, and the path never reaches the program.
Write a stub when you want knobs on the deck — a height lift, a scale
stretch — or a label of your own. Put it next to the deck in the same folder;
a stub of the same name as its deck is the design, and the bare deck is not
listed twice:
# my_yagi.py — my_yagi.nec sits next to itfrom types import MappingProxyTypefrom antennaknobs import AntennaBuilder, read_nec
WIRES_FILE = "my_yagi.nec"
class Builder(AntennaBuilder): label = "My Yagi (NEC deck)" default_params = MappingProxyType( { "freq": 14.1, "scale": 1.0, # stretches the whole deck; drag to resonate "height": 10.0, # lifts a z=0 deck above the ground plane } )
def build_wires(self): deck = read_nec(self, WIRES_FILE, network=True) s, h = self.scale, self.height lift = lambda p: (p[0] * s, p[1] * s, p[2] * s + h) # specs=True: `Wire` entries, each carrying ITS OWN radius from the # deck's GW card (and LD 5 conductivity) — a fat driven element # with thin radials solves faithfully, wire by wire. The named # entries carry the deck's feed and network attachment points. return [ w._replace(p0=lift(w.p0), p1=lift(w.p1)) for w in deck.wire_tuples(specs=True) ]
def build_network(self): # The deck's EX drive plus its translated LD/TL/NT cards (see below). return read_nec(self, WIRES_FILE, network=True).network()read_nec(self, name) has the same folder confinement as read_json: it can
only read files next to the design — no absolute paths, no .. that climbs
out, no symlinks pointing elsewhere — so a deck-based design stays safe to
share: the data can only ever become antenna geometry.
The two knobs above are essentially all a deck can offer, and both are worth having:
height— most published decks are drawn at z = 0 in free space, which would put the wires in the workbench’s ground plane.scale— a deck is fixed metres; a uniform stretch of the whole geometry is the only way to move its resonance without editing the deck. It scales everything together — element lengths and spacings and the boom — so it is nothing like a native design’s per-dimension knobs, but it will pull a slightly-off deck onto frequency.
There is no design_freq band scaling, no per-element tuning, and nothing
meaningful for the optimizer to hold onto — those need dimensions expressed
as parameters, which is exactly what porting the deck to a native
AntennaBuilder gives you.
And the radius is not optional. The solvers default to an idealized 0.5 mm
wire; a real deck’s 5 mm Yagi elements have very different reactance. On the
xnec2c 2 m Yagi example deck, the imported geometry matches the independent
nec2c solver to 0.011 Ω with the deck’s radius, and misses by
35 Ω without it. wire_tuples(specs=True) (above) handles this per
wire: every emitted Wire carries a WireSpec with that GW card’s own
radius, so even a mixed-radius deck keeps its reactance. (The pre-#388
recipe — plain wire_tuples() plus a build_wire_material() returning
WireSpec(radius=deck.dominant_radius(), conductivity=deck.conductivity) —
still works, approximating mixed radii with the length-dominant one.)
Two notes on per-wire radii: both engines honor them fully — PyNEC
natively (NEC takes a radius per wire), and every momwire solver since
momwire 0.13.0, the compressed H-matrix family included. And the scale
knob stretches geometry only — specs describe physical wire stock and
are never scaled.
Following the deck’s band
Section titled “Following the deck’s band”A deck’s FR card is exposed as deck.freq_mhz = (lo, hi). Use it at import
time to seed freq and the measurement window, so a 40 m or 2 m deck is
tunable on its own band instead of parked in the default window. A stub can
do this to itself at the bottom of the file — copy this block verbatim:
def _seed_defaults_from_deck(cls): """Follow the deck's FR card: default `freq` to the sweep centre and set the measurement window to the sweep, so the design tunes on its own band. Also surface the run-config cards the import recorded but didn't apply (deck.skipped_note()) as the UI's informational design note. Errors are swallowed here so they surface in build_wires instead of killing the import.""" try: deck = read_nec(cls(), WIRES_FILE, network=True) except Exception: return params = dict(cls.default_params) ui = dict(params.get("ui_params", ())) if deck.freq_mhz: lo, hi = deck.freq_mhz mid = 0.5 * (lo + hi) if lo >= hi: lo, hi = 0.9 * mid, 1.1 * mid params["freq"] = round(mid, 3) ui["meas_freq_range"] = (lo, hi) note = deck.skipped_note() if note: ui["notes"] = note params["ui_params"] = MappingProxyType(ui) cls.default_params = MappingProxyType(params)
_seed_defaults_from_deck(Builder)What is translated
Section titled “What is translated”| Cards | Meaning |
|---|---|
GW | Straight wires (tag, segments, endpoints, radius) |
GA, GH | Arcs and helices, generated as per-segment chords exactly as NEC does internally. A deck read as NEC-4 or NEC-5 takes their GH layout (TURNS ZLEN HR1 HR2 WR1 WR2 ISPX: log or Archimedes spirals, tapered wire) |
CW | NEC-4 and NEC-5 catenary wires, generated as straight 1-segment pieces through the catenary, as GA and GH are |
GC | Tapered wires: the zero-radius GW before it becomes a run of 1-segment wires with geometric lengths and radii. Read as NEC-4 or NEC-5 it also takes their IX forms, a fixed first segment (GC 1) or first and last (GC 2, which computes its own segment count) |
GM | Move / replicate — repetitions compound, each copy transforming the previous one |
GX | Reflect in the Z, Y, X planes, tag increment doubling per plane |
GR | Rotate about Z into a cylindrical array |
GS | Scale — including xnec2c’s tag-range extension (scale only tags I1..I2) |
EX types 0/5 | Voltage-source feeds, resolved through NEC’s (tag, segment) addressing |
EX types 4/6 | Current-source feeds (NEC-5’s type 4, 4nec2’s type 6), in amps — network=True only, see below |
An EX card read after an execute request (XQ, RP, NE, NH) starts the next run’s excitation, as NEC reads it. The first run’s sources are the design’s ports. A later run’s EX cards (SimNEC’s two-run feed-and-probe decks, for example) are kept on deck.later_run_ex and named in the import note, never added as more ports.
Card semantics are transcribed from the nec2c 1.3.1 sources, quirks
included (tag 0 never increments under any transform), and validated against
it: 73 of the 75 xnec2c example decks parse — the other two are rejected with
a deliberate “cannot model” error — and impedance round-trips through the
nec2c CLI agree to a fraction of an ohm.
Decks are read the way real ones are written: free-format fields separated by
spaces and/or commas, missing trailing fields as 0, old Fortran 1.0D+03
exponents, CM/CE comment headers, parsing stops at EN.
A feed on a segment that isn’t its wire’s middle keeps its wire whole. With
network=True it becomes a port
positioned along the wire
at that segment’s centre, and each engine meshes the wire so the gap lands
exactly there; PyNEC and NEC-2 keep the deck’s own segment count. Without
network=True, a wire tuple can only feed its middle segment, so the wire is
still split on the deck’s own segment boundaries and the feed gets a 1-segment
wire of its own: same geometry, same feed point.
Wires are also split wherever another wire’s segment endpoint touches them mid-wire. NEC connects segments whose ends coincide — the grouping into GW cards is irrelevant to it — so a deck may run one wire straight through another and rely on the crossing carrying current (the W8IO whip benchmark’s matching straps cross the whip axis exactly this way). antennaknobs’ engines junction wires at wire ends, so the import shatters such wires at the shared boundary: same segments, same boundaries, and the crossing becomes the real junction NEC’s connection rule implies.
Loading, feedlines, and networks (network=True)
Section titled “Loading, feedlines, and networks (network=True)”read_nec(self, name, network=True) additionally translates the deck’s
LD/TL/NT cards into the workbench’s own port-network branches
(antennaknobs.network: Load, TL, TwoPort, Shunt — the same system
the trap dipole and station designs use), wherever it can express them
exactly:
| Card | Translation |
|---|---|
LD type 0/1 (lumped series/parallel RLC) | A Load per segment in the card’s range (up to 64 segments per card), each a port positioned at its segment’s centre on the uncut host wire. Two or more cards on one segment add in series, as NEC adds them (nec2c and NEC-5 print “LOADED TWICE - IMPEDANCES ADDED”). On a NEC-5 deck the card names one knot instead (see the NEC-5 dialect) |
LD type 4 (fixed impedance) | Load(r=…) when X = 0; a fixed complex-Z Load when reactive |
LD type 5 over the whole structure (wire conductivity) | deck.conductivity, baked into every wire_tuples(specs=True) spec (or feed it to WireSpec in build_wire_material) |
LD type 5 on a tag/range covering whole wires | Per-wire conductivity (deck.wire_conductivity), baked into those wires’ specs=True specs — a ranged card wins over the whole-structure one |
LD type 6 (4nec2 LC-trap) | The parallel RLC 4nec2 itself converts it to: R_p = Q·ωL at the deck’s first FR frequency (F1 is the coil’s unloaded Q, 0 → 100) |
LD type 7 (4nec2 wire insulation) | Per-wire dielectric jackets (deck.wire_insulation → WireSpec.insulation_radius/eps_r) for wires the card covers in full — the solvers model the insulated-wire velocity factor |
EX type 6 (4nec2 current source) | A DrivenCurrent source — the forced complex current drives the network exactly as 4nec2 would |
EX type 4 (NEC-5 current source) | The same DrivenCurrent, at the segment end NEC-5’s rule names (I4, else the sign of I3) — the form EZNEC’s NEC-5 export writes. NEC-2’s type 4 (an elementary current source at a point in space, no segment addressed) is refused by name |
TL | A TL branch: negative z0 (NEC’s crossed line) becomes transposed=True, zero length resolves to the port separation, conductance-only end admittances become Shunt(r=1/G), reactive ones a fixed 1-port Admittance |
NT with an all-real, rank-1 Y matrix | An ideal Transformer (turns ratio plus a series winding resistance) — EZNEC’s own transformer card, read as the transformer it is rather than the pi network that Y would also describe. What the printed digits leave over (Y11·Y22 − Y12²) is a Shunt at port b |
NT with an all-real Y matrix, not rank 1 | Its exact resistive pi: a series TwoPort between the ports plus a Shunt at each |
NT with susceptance | The full 2×2 complex Y as an Admittance branch |
deck.wire_tuples() then emits the deck’s wires, cut only where another wire
touches them and named where something attaches (no legacy ex markers), and deck.network() returns the matching Network —
the deck’s EX cards become its Driven sources — ready to return from
build_wires / build_network as in the quick start above. A deck with no
network cards still works identically: network() is then just the drive.
What cannot be translated exactly stays out, with a per-card reason in
deck.ignored_detail (rendered by skipped_note()): distributed per-metre
RLC (LD 2/3), an LD 5/7 range covering only part of a wire’s segments
(per-wire specs cover whole wires only), and an LD landing on a segment
that also has a TL/NT connection (NEC composes those in series inside
the segment, which the port model doesn’t express).
An NT with susceptance anywhere holds at one frequency only. EZNEC
writes such a card — a line, an L network, or a load — by evaluating it once
at the deck’s FR frequency and freezing the result; an all-real NT (the
pi network or the transformer above) is a real circuit and holds at every
frequency. A deck with a frequency-dependent NT carries an import note
naming the card and the frequency it was written for, and solving or
sweeping away from that frequency raises the same advisory in the solve
response: the network is applied unchanged, so the result does not model it
off that frequency.
What is not applied
Section titled “What is not applied”A deck also carries run configuration, which the workbench manages itself.
Those cards are recorded in deck.ignored rather than translated:
GN/GDground and theGEground flag — the workbench applies its own ground model, but since v0.75.1 that model starts where the deck is: a deck in the designs folder opens with the ground switch set from its cards (GE 0off;GE 1orGN 1perfect;GN 2finite + Sommerfeld andGN 0finite + reflection coefficients, each with the card’s own εr and σ in the soil fields), the line under the ground-slot tabs says “from the file”, and the CLI’s@file.necroute applies the same unless--groundis given. A NEC-5 deck is the exception forGN 0: NEC-5 has no reflection-coefficient ground, so itsGN 0is Sommerfeld, and that line says “Sommerfeld (NEC-5 GN 0)”. A deck reads as NEC-5 when itsGNcard ends in NEC-5’sNOFILE, when a source sits at a segment end, or when EZNEC’s stamp says it wrote NEC-5.GDmeans different things in the two dialects. In a NEC-5 deck a bareGDafterGE 1is the ground: EZNEC’s “Real, MININEC type”, perfect for the currents and the card’s εr and σ for the pattern. It opens as the MININEC-type ground, andGD -1cancels it to free space. In a NEC-2 deckGDis a second medium beyond a cliff, read only by a cliff-modeRP. With the cliff at radius 0 and height 0 afterGN 1, it is the same MININEC idiom, and so is 4nec2’sGN 3. Both open as the MININEC-type ground. Any otherGD(a real cliff, or aGDover a finiteGN) is a card not applied.deck.ground_speccarries it in the CLI’s--groundshape anddeck.groundstill says whether the deck wanted a ground at all. The solver-slot label reads N = deck’s own for such a design: itsGWsegment counts are what the solvers honour, whatever the slot’s N says.LDloading,TL/NTfeedlines and networks — unless imported withnetwork=Trueas aboveFRsweeps (harvested intodeck.freq_mhz),RP/NE/NH/XQoutput requests
Expect readouts to differ from a deck’s published numbers when those numbers relied on its ground or on cards the translation could not express.
deck.skipped_note() turns that record into one human-readable sentence
(“Deck cards not applied: LD (loading), RP (radiation-pattern request) — the
app’s own settings are used instead.”), or None when the deck carries
nothing the workbench overrides. The ground is not listed, because the
workbench seeds its ground from the deck as described above. Deck-backed design stubs put it under
ui_params["notes"] and the workbench shows it beneath the antenna selector,
so the mismatch is explained right where the deck is viewed.
Decks the wire-model genuinely cannot represent are rejected with a clear
error rather than silently approximated: surface patches (SP/SM),
Green’s-function files (GF), and plane-wave excitation.
The 4nec2 dialect
Section titled “The 4nec2 dialect”Real-world decks are mostly written for 4nec2, and the importer reads that
dialect natively: SY symbolic variables with full expressions (BASIC-style
— trig in degrees, ^ power, unit suffixes like 1.5*mm or 36.6pF),
'-comments and !-comments after a card’s fields, fused mnemonics (GW1,8,…), and #14-style AWG wire-gauge
radii. A deck’s EK card (extended thin-wire kernel) is honoured on both
engines — momwire builds the solver with its own O(a²) tube expansion, PyNEC
with NEC’s — so fat-wire decks solve kernel-for-kernel with NEC without a flag
(EK -1, like an absent card, stays reduced; see
the extended thin-wire kernel).
That is the NEC-2 reading. A deck read as NEC-4 or NEC-5 solves with the
extended kernel by default instead, as its own program does — NEC-5’s kernel
behaves as the extended one and NEC-4’s thin-wire model is equivalent to it —
and ignores an EK card (NEC-4 ignores EK; the extended kernel is used where the solver serves it.
closes the dialect sentence). The user’s switch (--extended-kernel /
--no-extended-kernel, the slot’s EK check) still decides either way. A
NEC-4 VC card (its source and end-cap option) is read and listed as a card
not applied.
A remote
1-segment wire parked hundreds of wavelengths away purely to terminate a
TL card is recognised and replaced by a virtual circuit node (the deck
solves in seconds instead of meshing an electrically irrelevant wire; the
substitution is named in skipped_note()). EZNEC’s virtual wire — the short,
distant wire its transformer and lossy-line idiom puts a source or a network
end on — is recognised the same way: each referenced segment becomes a virtual
circuit node, the source drives the node, and the idiom’s open-circuit pins
are kept as the ideal open they stand for, so the deck solves without meshing
the wire and skipped_note() says what was translated. A source EZNEC placed
on that virtual wire is the rig — the generator end of the deck’s feed
system, not the antenna — so it takes the station convention’s port name
("rig"), and "feed" keeps meaning the antenna’s own terminals, the same
naming the SimNEC importer uses. When what a phantom
segment drives is an NT gyrator (zero diagonal, Y12 = Y21 = jB), the only
way NEC-2 can spell a current source, it imports as the current source it is,
whether the phantom wire has one segment or several, and the driving point
reads as the antenna rather than its reciprocal. For a current source on the
virtual wire itself, EZNEC’s NEC-2 export parks the phantom about 1 λ from the
virtual wire; the wires its own legend names (! *Wire #N for virtual segments., ! Wire #N for I srcs, …) are exempt from each other’s clearance,
never from the antenna’s, and the current is forced on the virtual node as
NEC-4’s EX 6 there would be. This dialect support was
validated against a 3,146-deck corpus of published models — ARRL course
material, 4nec2’s own library, and the wider web.
4nec2 (5.7.0 and later) also lets EX, LD, TL and NT give a segment as a
percentage of the wire’s length, measured from its first end: EX 0 2 50% 0 1 0
feeds the middle of wire 2. With network=True the percentage is the exact
position: a port
positioned along the wire
at that point, fed there on every engine, and the wire’s own segment count is
never changed to reach it. When every such point on the wire is already a
segment centre (PyNEC, NEC-2) or a knot (NEC-5) of its mesh, the wire stays
whole and each port is fed there. Otherwise the wire is split so every port on
it is fed exactly, however many share it, and a FeedPlacement advisory says so.
On PyNEC and NEC-2 each port gets its own short wire centred on it, reaching a
quarter of the way to its neighbours or a third of the way to a wire end, with
plain wire in between. A port within a segment of an end, with nothing tighter
nearby, runs its short wire to the end. NEC-5 cuts the wire at every port and
feeds each one at the knot the pieces on either side share.
Another program can feed a percentage that falls exactly on a segment boundary differently. antennaknobs feeds the stated point.
Without network=True, the percentage names the segment whose centre is
nearest. A percentage exactly on the boundary between two segments is equally
near both, so it is refused, and the message names network=True. 0% and
100% use the end segment’s centre. A percentage needs a tag that names one
wire, and an LD range given as two percentages expands over the segments
between them.
With network=True a feed at a wire end is fed at the end itself, not at
the centre of the segment it stands in. That distinction is the whole
difference between the two drive models: a segment gap is E = V/Δ spread
over the mesh cell, where the contact and the cell’s centre are one drive,
while the default point model is E = V·δ(s − s_f), where where in the cell
the point sits IS the answer. On a base-fed vertical over ground the two
placements differ by 29 % in X.
SY constants are knobs
Section titled “SY constants are knobs”A deck that names its dimensions with SY cards keeps them as knobs when it
is loaded as a design (the designs folder, or an @file.nec spec). Each
constant — a symbol whose right-hand side names no other symbol: a number,
literal arithmetic such as 360/16, or a number times a unit symbol such as
1.5*mm — becomes the knob sy_<name> (lower case; 4nec2 names ignore case),
defaulting to the deck’s value. Its label is the SY card’s trailing
' comment, or the symbol’s name without one; a card that assigns several
symbols and whose comment splits on commas into as many pieces labels them
one each. A knob written with a unit symbol reads in that unit (1.5*mm is a
1.5 mm knob); otherwise values are in the deck’s own units, and its GS scale
still applies. A constant that lands directly in an integer field — a
segment count, a tag — is an integer knob.
Every other symbol follows the knobs: the design re-reads the deck from the
current knob values on every build, so a derived symbol (z=len*cos(ang/2))
and a segment count written as int(hgh) come out exactly as they would from
a copy of the deck with that one SY value edited by hand. At its default
values the design is the deck as imported, bit for bit.
Some constants are not knobs, and the design’s note under the antenna selector names them and why:
- a symbol assigned more than once;
- a unit selector: a right-hand side that is only a unit symbol (
Inp=mm), or a constant that sets theGSscale factor (Scal=1); - a symbol that reaches only the
FRcard — the frequency dial covers it; - a symbol that reaches nothing the design is built from (unused, or read only by cards the import does not apply).
A knob that also reaches FR (Cebik’s equation Yagi sizes every element from
Fr) moves the geometry, but the design and measurement frequencies stay at
the deck’s; the note says so. The same holds for a knob that also reaches a
GN, GD, GE or EK card: those are read once, when the design loads.
Ranges are the workbench’s usual ±50 % around the default. A zero default has no such window, so it spans ± the deck’s own extent (the largest coordinate the deck writes).
The deck’s topology is frozen at its own values. A knob value is refused, with a message naming the knob, its value and what broke, when it changes which wire ends meet (or meet another wire at a segment boundary), moves a wire end into, through or out of the ground plane, moves a feed, load or network card onto a different wire, collapses a wire to zero length, or makes the deck itself invalid (a segment count below 1, a feed off its wire). The design keeps building at any good value, the deck’s own included.
A deck with no SY constants loads exactly as before, and a SimNEC .ssn is
not affected.
The NEC-5 dialect
Section titled “The NEC-5 dialect”NEC-5 changed one thing the importer must not guess about: sources can sit
at a segment end (a knot) rather than a segment center — EX grows an
end-selector field, and a negative segment number selects an end too. NEC-2
uses that same field position for print-control flags, so the two dialects
genuinely collide. The importer resolves it conservatively: the spellings
that can only be NEC-5 (a negative segment, or the end-selector value NEC-2
never defines) are refused with the dialect named — never silently read
as a NEC-2 center feed half a segment away — while the one ambiguous value
keeps its legal NEC-2 meaning. The exception is NEC-5’s current source,
EX type 4: NEC-2’s type 4 addresses no segment, so a segment-addressed
type 4 can only be NEC-5, and the importer reads its end field by NEC-5’s
full rule (I4 names the end; when zero, a positive segment number means
end 2). With network=True, a voltage source on an interior knot keeps its wire
whole. On the middle knot it is an ordinary middle-of-wire port, the way
antennaknobs feeds every centre-fed design; on any other interior knot it is a
port positioned
at that knot. A source at a wire end, an EX 4 current source, or a
source on a knot where another wire joins imports as a PortAtVertex, voltage
or current alike. Either way it solves on momwire, and the deck also solves
natively on the NEC-5 engine, which speaks the form as a
first-class citizen. NEC-5’s GN card also names a
ground file, and the NOFILE that says there is none is accepted. NEC-5’s UM card, the medium the structure sits in, is read when it says air (relative permittivity 1, conductivity 0), which is what every solver here models; any other medium is refused by name.
A deck that shows neither NOFILE nor an explicit end field reads as NEC-2,
because the EX card alone cannot say which program it was written for.
Such a deck declares itself NEC-5 with a comment card whose whole text is
NEC-5 (CM NEC-5), or with EZNEC’s own stamp line —
CM ! Written by EZNEC/Pro+ v. 7.0 in NEC-5 format. — whose format token
names the dialect: NEC-5 declares it, NEC-4.2 reads it as NEC-4 (below),
NEC-2 keeps the NEC-2 reading, and any other token is refused by name. An EX with I4 = 0 then reads NEC-5’s way: end 2 of a positive
segment, end 1 of a negative one, and the same rule reaches the ends NT
and TL cards name, so TL 3,2,2,-1 and EX 4,2,-1 resolve to one port. A
comment that only mentions NEC-5 declares nothing.
On a deck read as NEC-5, a discrete LD card (types 0, 1, 4 and 6) addresses
a segment end the same way: I3 is the segment and I4 its end, not the last
segment of a range. Each such card is one load at that knot, placed like a
source there, and a load on the fed knot shares the source’s port.
Which dialect a deck was read in, and choosing it
Section titled “Which dialect a deck was read in, and choosing it”The import says which reading it took, and why, as the first sentence of the
design’s note — on the workbench above the knobs, for an opened deck and a
deck in the designs folder alike: Read as NEC-5 (sources and loads at segment
ends): EZNEC’s stamp on line 1 says NEC-5 format. or Read as NEC-2 (sources
and loads at segment centres): no NEC-4 or NEC-5 marker found. The four
markers that read a deck as NEC-5 are EZNEC’s NEC-5 stamp, a CM NEC-5 card,
and an EX in NEC-5’s segment-end form; the note names the first one in the
deck, by line. Two markers are weaker, the NEC-4 GH/GC layout below
(which leans NEC-4) and a GN card ending in NOFILE: NEC-4 and NEC-5 both
write it, so it only rules NEC-2 out. A deck whose only marker it is reads
wholly as NEC-5 — its plain EX cards included, at end 2 of their segment —
and the note says the choice was a default: …ends in NOFILE, which NEC-4 and
NEC-5 both write, and nothing else in the deck says which — choose Read as
NEC-4 if it came from a NEC-4 program. antennaknobs’ own NEC-5 decks carry
CM NEC-5, and its NEC-4.2 decks CM NEC-4.2, so each reads back as what it
is. EZNEC’s NEC-4.2 stamp (or a
CM NEC-4.2 card) reads it as NEC-4: NEC-2’s segment centres, with NEC-4’s own cards — GN 3 is
NEC-4.2’s newer Sommerfeld (not 4nec2’s MININEC-type ground), EX 6 its
segment current source, GE -1 admits buried wires, and a GN that names a
ground-table file (or NOFILE) is accepted, the file not read (it is NEC-4’s
cache; the ground is computed from the card, and the note says so). EZNEC’s
NEC-2 stamp reads as NEC-2, but EZNEC writes GN 3 only for its ‘Extended’
real ground, NEC-4’s Sommerfeld, in that export too, so a stamped GN 3 deck
reads as NEC-4 and the note says why; a 4nec2 deck carries no stamp and keeps
the MININEC-type ground. GH and
GC take NEC-4’s layouts (shared by NEC-5). A card only that layout spells,
a GH whose ninth field is a literal 0 (ISPX; NEC-2’s radius there would be
zero) or a GC 1/GC 2, rules NEC-2 out and leans NEC-4, the dialect of the
decks that carry it: with no unambiguous marker the deck reads wholly as
NEC-4, sources and loads at segment centres, and the note says it was a
lean: …the GH card on line 4 uses NEC-4’s layout, which NEC-5 shares, and
nothing else in the deck says which — choose Read as NEC-5 if it came from a
NEC-5 program. It outranks NOFILE (which says nothing between NEC-4 and
NEC-5) and yields to every unambiguous marker, a NEC-5 segment-end EX
included. Read as NEC-2 by choice, such a card is refused, naming the NEC-4
reading that spells it. A NEC-5
deck with no marker reads as NEC-2, every source and load half a segment from
where its author put it, so the dialect can be chosen: Read as under an
opened deck’s name (auto / NEC-2 / NEC-4 / NEC-5) re-opens it read that way,
and the page’s link carries the choice (&dialect=nec5, absent for auto).
In Python it is parse_nec(text, dialect="nec5") (or "nec2", "nec4";
None detects), and
builder_from_file(path, dialect=...) / builder_from_text(...) for a file
design. A chosen dialect reads a CM declaration (or an EZNEC stamp naming a
writer with no capture) as the comment it is, and the note says what
detection would have read. What one dialect cannot spell is refused by name
under the other: NEC-5’s segment-end EX (a negative segment, I4 = 2, or
EX 4) cannot be read as NEC-2 or NEC-4, and 4nec2’s percentage position
cannot be read as NEC-5. NOFILE is accepted under NEC-2, since NEC-4.2’s own
decks end GN with it too. A comment card whose whole text is NEC-4 or
NEC-4.2 declares NEC-4 the way CM NEC-5 declares NEC-5; antennaknobs’
own NEC-4.2 export (--dialect nec4) writes CM NEC-4.2, so it reads back
as NEC-4 under detection.
A deck can also say which program it is for with a keyed comment, whose
whole text is engine= or dialect= and a value: CM engine=nec4,
CM dialect=4nec2 (: works as well as =, in any case). nec2, 4nec2
and nec2c declare NEC-2, the only way a deck can declare it; nec4,
nec4.1 and nec4.2 declare NEC-4; nec5 declares NEC-5. A declared deck
reads as that dialect, as if it had been chosen, ahead of every guess above,
and the note names the card: Read as NEC-2 (sources and loads at segment
centres): a CM engine=4nec2 card on line 1. So a GN 3 deck that declares
4nec2 keeps 4nec2’s MININEC ground even with a buried wire. Two declarations
that disagree, keyed or whole-comment, are refused by name; a value not in
that list declares nothing. Read as still wins over a declaration.
MMANA-GAL .maa models
Section titled “MMANA-GAL .maa models”An MMANA-GAL model opens wherever a deck does: Open… on the workbench
(hosted and local; the link carries it), the designs folder, and an
@file.maa builder spec on the command line. There is no need to export
it to .nec first. The import builds the same deck a .nec becomes, and the
design’s note begins Read as MMANA-GAL (MININEC): ….
Read as MMANA. MMANA is MININEC-based, so a source or load sits on a
pulse, the junction between two segments, not at a segment centre. Each one
becomes a port at that segment end, as a NEC-5 segment-end source does. A
position is w<wire><b|c|e>[offset]: the wire’s start, centre or end, then
an offset in pulses (w1c+1 is one pulse toward the end, w3e1 one pulse
in from the end). MMANA’s own library sometimes writes the centre letter as a
Cyrillic с, and that is accepted. The section headers are cosmetic and
often Russian (* Провода *), so blocks are found by their position. The
file may be cp1251, and the workbench reads it that way.
The mesh is the file’s, not MMANA’s. Almost every MMANA wire says
SEG = -1: mesh it for me, tapered at both ends. Leaving such a wire as one
segment is wrong. Copying MMANA’s mesher bit for bit is not attempted
either. Every automatic wire is meshed from the file’s own segmentation line
DM1, DM2, SC, EC:
- At a tapered end, segments start at λ/(DM1·EC).
- They grow by the factor SC per segment.
- They stop growing at λ/DM2, the length over the rest of the wire.
SEG -2 and -3 taper only the start or only the end. 0 is a regular
mesh no coarser than λ/DM2. A positive SEG is a manual count, kept exactly.
A wire tapered at both ends is meshed symmetrically, so its centre is always
a segment end. No segment is shorter than two radii. The note gives the
lengths the file’s numbers came to. Because the mesh is ours, MMANA’s
numbers will not match exactly: on MMANA-GAL’s own 20 m dipole the impedance
lands within half an ohm, and on a two-element quad within 5 %. That is
the MININEC-versus-MoM gap, and our side is mesh-converged in both.
An odd segment count is rounded up to even, as MMANA does (SEG 7 is meshed
as 8), so a wire’s centre is always a pulse. Nothing is snapped. A centre
that is still not on the mesh (w1c on a regular, SEG 0 wire whose count comes
out odd) cuts the wire at its exact centre, and the note says so. An offset
that would need MMANA’s own automatic mesh to place is refused.
The ground line is G, H, M, R, Az, El, X:
- G: 0 is free space, 1 is perfect ground, 2 is real ground.
- H: added to every z.
- M: the wire material. 0 is lossless; copper wire or pipe (1, 2) is 5.8·10⁷ S/m; aluminium wire or pipe (3, 4) is 3.5·10⁷ S/m.
- R: the reference impedance for SWR, which becomes the design’s Zo.
- Az, El: MMANA’s front-to-back readout ranges, which are not physics.
MMANA’s real ground is the MININEC type: perfect ground for the currents
and the impedance, with the soil used for the pattern only. The soil is not
in the .maa (MMANA keeps it in its own settings), so εr 13,
σ 0.005 S/m is assumed and the note says so; set your own in the ground panel.
MMANA’s wire loss is about twice what these conductivities give on its 20 m
dipole, and MMANA’s loss adds no reactance.
Loads come in three types:
- Type 0 is
L µH, C pF, Q. With C = 0 it is a coil, with L = 0 a capacitor, and with both a parallel L‖C trap. - Type 1 is
R, jXin ohms, fixed at every frequency. - Type 2 is MMANA’s Laplace form, which is refused.
Q is the coil’s, or the capacitor’s when there is no coil. It becomes a loss resistance held at the file’s frequency: a series ωL/Q, or a trap’s parallel Q·ωL. MMANA’s Use loads switch is honoured: a file whose loads are switched off opens without them, and the note says so.
Refused by name, each with its file and line:
- a stack (the wire count line’s six-field Make Stack form);
- a Laplace load;
- an insulator wire (R = 0);
- an iron or user-defined material;
- a starred taper-wire type (
<>*,->*); - a design frequency of 0 (MMANA would quietly use 14.15 MHz);
- a source or load at a free wire end, where MININEC has no pulse;
- an offset on an automatically meshed wire;
- wires that cross between their ends, or a wire end on another wire’s middle.
Objects on different wires’ ends at one junction of three or more wires are each a series gap in their own wire, as MMANA’s positions name them.
A stepped-radius wire (a negative R pointing into the $$$ taper wire set)
is read for the plain <> and -> types.
EZNEC .ez models
Section titled “EZNEC .ez models”An EZNEC model opens wherever a deck does: Open… on the workbench (hosted
and local; the link carries the file), the designs folder, and an
@file.ez builder spec on the command line. There is no need to export it
to .nec first. The .ez is binary; the import writes it as NEC cards (the
Files view shows them) and builds the same deck a .nec becomes.
Where things sit. EZNEC places a source, load or line end at a percentage
along a wire and runs it on the segment that percentage lands on, as its own
.nec export does. EZNEC’s own engines (its NEC-2 and NEC-4 cores) put it at
that segment’s centre, so that is the default reading. A file saved with
EZNEC set to the external NEC-5 engine is read at segment ends instead, at
the knot EZNEC’s NEC-5 export names (0 % is the wire’s end itself). Both
readings follow EZNEC’s own placement rule, as its author describes it:
the nearest segment or knot, worked out in single precision. A part placed
exactly halfway between two (50 % of an odd number of segments under NEC-5,
or of an even number under NEC-2) goes whichever way that rounding takes it,
which EZNEC itself treats as ambiguous; the import reproduces EZNEC’s choice.
A load placed where a source is shares the source’s knot. From
Python the reading can be chosen, as a .nec’s dialect can
(read_ez(..., reading="nec5")); the workbench’s Read as control is for
.nec decks only.
A split source at a wire end is, as EZNEC’s exports write it, one source either side of the junction (a voltage halved, a current repeated), or under NEC-5 one source at the junction’s knot. Placed inside a wire, EZNEC moves it to a knot, the one its NEC-5 export names: a voltage is then two sources at half the voltage on the segments either side, and a current is one source on the segment that meets the knot. A split voltage is modelled as one source at the joint under every reading, so it shows one feed with its whole impedance, as EZNEC’s Src Dat does. The deck in the Files view keeps EZNEC’s two NEC-2 cards, and the design’s notes say they are read as one. On the Bobtail models in the sitting, that one feed is within 0.1 % of the sum of the two half-voltage feeds.
Loads keep their type, so a frequency sweep evaluates each at every frequency rather than at the model frequency only:
- R + jX is a fixed impedance.
- Series and parallel RLC are the deck’s own frequency-dependent load (a zero part is a missing part: a short in series, an open in parallel).
- A trap is R and L in series, across C. With no C it is just R and L in series.
- A resistance that varies with frequency is given at a frequency F. In a series or trap configuration it follows √(f/F), like conductor loss. In a parallel one it falls as F/f, like dielectric loss.
- A Laplace load is read only on request (below).
Lines and networks. A lossless line keeps its reversal (NEC’s crossed
line), length and velocity factor. A stub end becomes the line into an open
or a short, so it follows frequency too. A stub on a real wire hangs across its segment, as EZNEC’s exports write it
(a one-port network to a spare circuit wire): in parallel with a source
there, and elsewhere in series with the wire current; a series load on the
same segment stays in the wire. A length of 0 is the distance
between the line’s connection points (segment centres, or knots under
NEC-5); a length in degrees keeps those degrees at every frequency, as EZNEC
does. A lossy line’s loss follows √(f/F) from its loss frequency F; one with
no loss frequency is held at the model frequency, and the app flags it away
from there. Transformers are EZNEC’s own two-port, Y-parameter networks
their fixed admittances, and L networks their two branches, fixed R + jX or
R/L/C parts that follow frequency. A parallel R/L/C branch with a part left
unset reads that part as absent (open), as a parallel RLC load does; EZNEC’s
own .nec export of such a model agrees, and the design’s notes say so. A
parallel shunt branch with no parts at all is no element, which EZNEC shows as
Open, Open, Open.
Checked against EZNEC 7.0.4 at a sitting with the census files the reader used to refuse: a Laplace load is Z(s) with s = jω, ω in rad/s; a pre-3.0 file’s blank load type, with its older field reading R + jX, is an R + jX load; the shorter version stamps 700007, 700008 and 60023 are versions 7.0.7, 7.0.8 and 6.0.23; and a wire-loss table with a single entry for a many-wire model gives every wire that material. Each now opens as EZNEC reads it.
Wire loss comes from the per-wire loss table when the file has one, else from the model-wide resistivity. Insulation becomes the wire’s jacket. Ground is free space, perfect, or the first medium with EZNEC’s high-accuracy (Sommerfeld) or MININEC-type analysis. EZNEC’s extended high-accuracy ground is the same Sommerfeld ground.
Virtual segments are stored by EZNEC on one extra wire. The import rebuilds it 100 λ away, as EZNEC’s own export does, and the deck reader treats its segments as circuit nodes, stubs and loads on them included.
A parallel-connected load (connection P) is wired across its
segment rather than inserted in the wire. EZNEC writes one, in all three of
its export formats, as a one-port network to a spare circuit wire with
admittance 1/Z, and the import does the same: on a virtual segment it is a
shunt across that node, at a source in parallel with it, and on a real wire
away from a source it carries the wire current as a series load of the same
Z would. Its R, L and C, in series or in parallel, only set that one Z, which
follows frequency. Under NEC-5 a P load on a free wire end is refused, as
EZNEC’s NEC-5 export refuses it (“One or more loads are incorrectly placed”);
under NEC-2 it sits on the end segment, as EZNEC’s NEC-2 export moves it.
These readings were checked against EZNEC itself: its own exports of each kind of file, solved here, at the model frequency and away from it. Files from before EZNEC 4.0 open too, as the model EZNEC 7 saves them as.
A second ground medium beyond a boundary (a straight line at X, or a
circle of radius R) is written as EZNEC’s NEC-2 export writes it, as NEC-2’s
GD card. In NEC-2 it changes only the far field, and EZNEC agrees: moving it
from 1 m down to 50 m down leaves the impedance unchanged to every digit. The
design’s notes say that AK draws the pattern over the first medium alone.
Under NEC-5 it is left out, as EZNEC’s NEC-5 engine deletes it.
Files AutoEZ writes (AutoEZ creates the .ez, then has EZNEC open it)
differ from ones EZNEC saves in two ways that matter here. They record no
engine, so they are read as EZNEC’s own engines read them, with sources and
loads at segment centres: if you ran the model on EZNEC’s NEC-5 engine, use
Read as NEC-5, which puts them on segment ends, as NEC-5 does. And they
store each load’s R where EZNEC stores its impedance at the model frequency,
so that value is not checked against the load’s parts. The design’s notes say
both. Saving the model from EZNEC instead records the engine and the
impedances.
Refusals come in four kinds, each naming the field and value:
- An unknown format: a file version, block type or block revision the
reader does not know. The
.ezformat still changes between EZNEC versions, so these are never guessed at. (Blocks another program marks as its own are skipped.) - A reading the format leaves undefined, and EZNEC’s exports have not
settled: a reversed transformer or Y network, a series load on a virtual
segment, a line loss given at no frequency, the extended ground under a
buried wire, a pre-3.0 file’s Laplace load type, data in a record the format
reserves. These are
refused unless you ask for the reading: on the web app with the refusal’s
Open with this reading button, on the command line by setting
ANTENNAKNOBS_EZ_ACCEPT_GUESSES=1. Each reading taken is then listed in the design’s notes, and a link to the design keeps it. - Not representable here: a third ground medium, a second one under the MININEC-type analysis, NEC radials, an active plane-wave source, an insulation with dielectric loss, a magnetic wire.
- A malformed file: counts or lengths that contradict each other.
A setting that only changes EZNEC’s display (plot ranges, units, the near-field request) never refuses a file; an odd value is noted.
Programmatic use
Section titled “Programmatic use”Outside a design, parse_nec(text, name=...) takes raw deck text and returns
the same NecDeck; name labels errors, which always carry the offending
line number (my_yagi.nec, line 7: GW card: segment count must be >= 1, got 0).
from antennaknobs.nec_import import parse_nec
deck = parse_nec(open("some.nec").read(), name="some.nec")NecDeck field | Meaning |
|---|---|
wires | tuple[NecWire, ...] — every straight wire after all transforms (tag, n_seg, p1, p2, radius) |
feeds | tuple[NecFeed, ...] — each EX source resolved onto a wire (wire index, 1-based seg, complex voltage; current=True marks a forced current in amps, 4nec2’s EX 6 or NEC-5’s EX 4; edge 1/2 marks a NEC-5 end source) |
freq_mhz | The FR card’s sweep range as (lo, hi) MHz, or None |
ground | True if the deck requested a ground plane (GE flag or a GN card) |
ground_spec, ground_method | The ground the deck models, in the CLI’s --ground shape — None (free space), "pec", ("finite", eps_r, sigma) for GN 2, ("finite-fast", eps_r, sigma) for a NEC-2 deck’s GN 0 (a NEC-5 deck’s GN 0 is "finite"), ("mininec", eps_r, sigma) for the MININEC-type ground — and the finite model’s name ("sommerfeld" / "fast" / "mininec") |
ground_card, nec5_dialect | The card the ground came from ("GN 0" / "GN 2" / "GD" / "GN 1 + GD" / "GN 3", or None), and whether the deck reads as NEC-5 (see dialect / dialect_reason for which marker said so) |
comments | The CM header text, line by line |
ignored | Mnemonics of run-configuration cards seen but not applied |
loads, tls, nts | The translated LD/TL/NT records (network=True only) |
conductivity | Whole-structure LD 5 wire conductivity in S/m, or None |
ignored_detail | (mnemonic, reason) per card network=True still could not translate |
plus four methods: wire_tuples() (the deck as build_wires() tuples;
raises if no voltage source drives the antenna), network() (the translated
cards + EX drives as a Network, network=True only),
dominant_radius(), and skipped_note() (the not-applied record as one
informational sentence, reasons included).
parse_nec(text, sy_overrides={"hgh": 22.0}) sets SY symbols by name, as if
the deck’s text had been edited at their definitions; a name the deck does not
define, or assigns more than once, is refused. classify_sy(text) returns each
symbol with its classification ("knob", "derived", "redefined",
"unit", "frequency" or "inert"), label, unit and the cards it reaches.