Skip to content

Running momwire as a NEC-2 engine

SimNEC (AE6TY) does not link its NEC solver — it shells out to a nec2c executable, starts one copy, and keeps it. Decks go down that process’s stdin framed by an NX card, printouts come back on stdout, and SimNEC’s own circuit solver reads two numbers per feedpoint out of each printout to build the antenna’s Y matrix.

momwire ships that process. pip install momwire puts momwire-nec2c on your path: the NEC-2 launcher (momwire-nec2c and its siblings), a drop-in for the engine binary with momwire’s solver behind it. It has two front ends, SimNEC and 4nec2; this page covers SimNEC. The Smith chart, the tuner and the sweeps stay SimNEC’s; the electromagnetics become momwire’s.

The deck language is momwire’s own nec2 dialect — wire structures and the two-port cards attached to them, specified card by card in the deck grammar. This page is about running the engine; that page is about what it accepts.

Terminal window
pip install momwire
which momwire-nec2c # e.g. ~/.venvs/momwire/bin/momwire-nec2c
momwire-nec2c -version # NEC2 momwire-nec2c [sb] <version>

python -m momwire.portal is the same program under its long name, for an environment where the console script is awkward to reach.

Before a live session, run the built-in smoke. It needs no checkout: it spawns one resident copy of itself, sends embedded decks through it the way SimNEC does, exercises every basis, and prints PASS or FAIL.

Terminal window
momwire-nec2c --selftest

Open SimNEC’s NEC portal dialog and give it the path the which above printed. Two rules decide whether SimNEC accepts the command, and both fail in ways that look nothing like their causes.

The filename must contain nec2c. SimNEC picks the engine — deck dialect, daemon protocol, printout parse offsets, all of it — off the command’s file name, lowercased, before anything inside the file is consulted. A name carrying none of nec2c / nec5 / nec42 is refused with NO NEC Command Available, and a name carrying nec5 or nec42 selects a different column layout that this engine does not emit. That is why the script is called momwire-nec2c and not something tidier.

The full path must not contain the substring out. SimNEC refuses any command whose path contains it — Can’t execute ‘out’ file: — a guard against pointing the engine field at a printout file. The shipped name is clear of it; your install prefix may not be. A venv under ~/checkouts/routing/ or C:\Users\scout\ refuses this engine no matter what it is called, and the fix is to install momwire somewhere else.

Both rules apply to wrappers and symlinks exactly as they do to the script.

Choosing the physics: one command per engine

Section titled “Choosing the physics: one command per engine”

Since momwire 0.36.1 every solver is its own command — pip install momwire puts all of these on your path, and SimNEC’s engine dialog takes any of them directly, no arguments and no wrapper scripts:

momwire-nec2c # the default — degree-2 B-spline (bs2)
momwire-nec2c-bspline-d1 # the same physics at degree 1 (tent basis)
momwire-nec2c-sinusoidal # NEC-2's own formulation
momwire-nec2c-sinusoidal-galerkin # the same basis, tested variationally
momwire-nec2c-hmatrix # same physics, hierarchical (ACA) solve
momwire-nec2c-arrayblock # same physics, element-block/FFT solve
momwire-nec2c-pulse # Harrington's pulse basis, point-matched

The mechanism is the name: everything after nec2c- in the executable’s basename selects the basis (a Windows .exe suffix is stripped first), so a copy, symlink or hard link you make yourself works exactly like the shipped commands. A name that matches no solver fails the -version probe loudly at configure time, naming its source. The same choice has two other spellings with a fixed precedence — an explicit --basis <name> flag beats the filename, and the filename beats a MOMWIRE_NEC2C_BASIS environment variable; the plain momwire-nec2c with neither is the default.

Four axes live in that list. sinusoidal and sinusoidal-galerkin are the fidelity ladder: the three-term basis point-matched with NEC’s own delta-gap source answers “does this reproduce NEC-2’s behaviour, mesh walk and all”, and the Galerkin entry and the B-spline default answer “what does a better-converged basis say”. There was a second Galerkin name until momwire#654: it differed only in its feed model, which is not a choice a portal dialog can put to you meaningfully, and the better of the two is now the plain name’s default. There is no sinusoidal-converged: a zero-width gap has no collocation right-hand side, so the flag does not offer a name the solver refuses. bspline-d1 is the degree axis — the same B-spline physics at degree 1, so pairing it with the default is the cheapest convergence check available. hmatrix and arrayblock are the solve axis, not a physics choice: the same B-spline operator held compressed and solved iteratively, so a large array answers without a dense fill. arrayblock is the one to reach for on a repeated-element array — identical elements on a regular lattice become an FFT convolution over the element grid — and it degrades to the hierarchical solve on a deck with no repeated structure, so it is safe to leave on. Its answers must agree with bspline’s; if they do not, the deck is telling you about conditioning, not about basis. There is no razor command here. The tent-basis, razor-blade formulation NEC-5 identifies with (razor-2p) is not a nec2 engine: a NEC-2 deck puts its source at a segment centre and razor places its gap at a knot, so it would answer for an antenna half a segment from the one you drew. Since momwire#821 the portal refuses the name at configure time rather than serve that — momwire-nec2c-razor-nec5 and momwire-nec2c-razor-2p, which shipped from 0.36.1 to 0.46.0, are gone, and a copy you kept fails the version probe with the reason. Razor is served where its grid is the deck’s: the NEC-5 dialect’s momwire-nec5-razor-2p, for EZNEC’s NEC-5 slot and SimNEC’s own NEC-5 engine alike (below), which writes a source at a node.

On a tapered or stepped-radius wire, sinusoidal is NEC-2-faithful, not converged. At a junction where two wire radii meet it follows NEC-2, and that includes NEC-2’s well-known stepped-radius weakness: it mostly ignores the step, and more so as the mesh is refined. On a 6 m dipole at 14 MHz with a 0.5 mm half joined to a 1 mm half, the step’s effect on Z, in ohms (momwire#1632):

20 seg/half40 seg/half80 seg/half
NEC-2 (PyNEC)−0.59 − 8.90j−0.77 − 1.21j−1.01 + 9.43j
sinusoidal−0.59 − 8.86j−0.76 − 1.18j−1.01 + 9.47j
sinusoidal-galerkin−0.02 − 33.44j−0.00 − 34.04j+0.01 − 34.42j
bspline−0.07 − 32.55j−0.03 − 33.42j−0.01 − 33.95j
NEC-5+0.03 − 34.69j+0.03 − 34.80j

That is by design: this row exists to be the NEC-2-closest rung of the ladder, and the nec2 slot is meant to match nec2c, so inheriting NEC-2’s own behaviour on a taper is what “closest” means there. The solver says so: every sinusoidal solve with two radii at one junction raises a RadiusStepJunction advisory naming the wires. A NEC-4.2-style step for the NEC-4 dialect is momwire#1644. For the converged answer use bspline or sinusoidal-galerkin; bspline-d1 with EK armed (the EK card, above) is the row measured closest to NEC-5 on Ward Harriman AE6TY’s flagship 20:1 tapered dipole. sinusoidal-galerkin REFUSES EK outright on any deck where two wires of different radii meet at a junction — measured divergent rather than merely inaccurate there; see the EK card entry in the nec2 deck grammar reference.

Two portal entries differing only in --basis give you cross-basis validation inside SimNEC itself. Switch engines from the dialog and watch whether the answer holds. The printout banner records which physics answered (VERSION:nec2c.ae6ty.momwire.9.1+sg), and a mistyped basis fails the version probe loudly at configure time rather than serving the default.

It always is — and the named commands above exist precisely so that the file is the choice. Point the dialog at momwire-nec2c-sinusoidal-galerkin and that is the engine; two entries differing only in name are two engines. Both filename rules above apply to every spelling, shipped or hand-made: keep nec2c in the name, and keep out out of the whole path.

SimNEC also has a NEC-5 engine slot, and momwire fills it too (since 0.66.0). SimNEC decides which deck syntax to write from the engine’s path, not from anything the engine says: a path containing nec2c gets NEC-2 decks, one containing nec5 gets NEC-5 decks. So the NEC-5 slot takes a different family of commands, and the momwire-nec2c names above will not work there:

Terminal window
momwire-nec5 # the default — degree-2 B-spline (bs2)
momwire-nec5-razor-2p # NEC-5's own formulation (tent basis, razor testing)
momwire-nec5-bspline-d1 # degree-1 B-spline
momwire-nec5-sinusoidal-galerkin # sinusoidal basis, tested variationally
momwire-nec5-hmatrix # bs2, hierarchical (ACA) solve
momwire-nec5-arrayblock # bs2, element-block/FFT solve

razor-2p is the one to pick for agreement with the licensed NEC-5: it runs the same algorithm, and in SimNEC it reproduces the licensed engine’s impedances to about 0.01 Ω on the examples we have run. There is no sinusoidal or pulse command in this family: a NEC-5 deck puts its source at a knot, and those two bases can only feed a segment centre, so they would refuse every deck SimNEC writes.

Setup is the same as for the NEC-2 slot: which momwire-nec5-razor-2p and give SimNEC that absolute path (one without spaces is safest). Its -version probe answers NEC5 momwire-nec5-razor-2p [sb] <version>. These commands run the same warm engine as the EZNEC drop-in, so a run after the first answers in milliseconds, and SimNEC’s N-port measurement (one run block per port in a single deck) is served as NEC-5 serves it. On Windows without Python, the drop-in zip, momwire-windows.zip, ships momwire-nec5.exe and momwire-nec5-razor-2p.exe, the same names it tells EZNEC’s NEC-5 slot to use.

SimNEC runs <command> -version and reads the first line. Every momwire launcher answers four fields separated by spaces (momwire#1606):

NEC2 momwire-nec2c [sb] 0.82.0
  1. The deck dialect the launcher reads: NEC2, NEC42 or NEC5.
  2. The launcher’s name, which always starts with momwire.
  3. The interface modes it serves beyond one deck per call, in square brackets: s for streaming, b for batch (decks chained by NX). Every launcher answers [sb].
  4. The full momwire version.

The dialect comes first because SimNEC takes the engine number from the first digit of the line, and its NEC-2 and NEC-4.2 checks need 2 and 4 there. SimNEC shows the string in the portal dialog’s NECVersion row, stamps it as a CM version comment card on every deck it sends, and stores it in saved circuits — so a momwire session is identifiable as one everywhere the version travels.

The number is momwire’s own version — the engine’s, which is the number that changes solve results. An engine release moves it; nothing else does.

Every NEC-2 launcher answers this one form. The older --legacy-probe flag was removed in 0.83.0: a portal dialog line that still carries it is refused at the probe with a line saying so, and the fix is to delete the flag. The probe line names the launcher, not the engine: --basis never changes it, and only the printout banner records which basis answered. A copy named for a basis (momwire-nec2c-pulse) gives that name.

The portal reads a deck file directly, the way you would test any NEC engine:

Terminal window
momwire-nec2c --basis sinusoidal < dipole.nec > dipole.printout

One framing rule makes this work: a deck is solved when its terminator card arrives. Inside SimNEC that card is NX, appended automatically. Standalone, a stock .nec file’s final EN card does the same job and then ends the run. End of input terminates a deck too, exactly as EN does — so a file with neither, a deck that stops at XQ, solves and exits the same way.

Since momwire 0.83.0 the NEC-2 commands also take the call forms every momwire launcher answers (momwire#1611):

Terminal window
momwire-nec2c dipole.nec dipole.out # input file, output file
momwire-nec2c - - < chain.nec # streaming: decks on stdin, printouts on stdout
momwire-nec2c < nec2d.tmp # 4nec2's form: the two file names on stdin

The two-file form writes the same printout the redirect above prints, with Windows (CRLF) line endings, as every momwire launcher writes its printout file. A deck it cannot read gets a printout that says so. - - is SimNEC’s own NEC-2 protocol spelled out, so it prints exactly what the bare command prints. With no arguments, the first line on stdin decides: a NEC card means decks, as SimNEC sends them, and anything else (or the name of a file that exists) is the input file’s name, with the output file’s on the next line, as 4nec2 writes nec2d.tmp.

A SimNEC circuit whose antenna is written in SimNEC’s own script language (NECWire(...), NECSource(...), dcl variables) has no cards in its .ssn file. SimNEC runs the script and sends the resulting deck to the engine. To get that model out as a .nec, have the engine keep a copy of every deck it receives:

Terminal window
mkdir -p ~/simnec-decks
momwire-nec2c --save-decks ~/simnec-decks

Put that command in SimNEC’s portal dialog, run the circuit once, and pick up the newest file from the directory. Each deck is saved as <date>-<time>-<n>.nec. The cards come through as SimNEC sent them, with its CM version comment first, and the file ends in EN so it reads as one standalone deck. antennaknobs opens it directly, as does any NEC-2 program.

The option is off unless you ask for it. MOMWIRE_NEC2C_SAVE_DECKS=<dir> does the same for a host that passes environment instead of arguments, and the flag wins if both are given. The directory must already exist; if it doesn’t, the -version probe fails at once. Saving never changes the answer, and a deck that cannot be written (a full disk, say) is still solved.

A deck this engine cannot model is reported and stepped over, never guessed at. The printout names the offending card and why, and still carries the NX sentinel SimNEC blocks in readLine() waiting for — an engine that dies or forgets the sentinel hangs SimNEC’s UI with no timeout, which is strictly worse than an error message. The daemon survives and runs the next deck.

The refusal leads with a line whose first word is exactly ERROR:, which is what trips SimNEC’s own NEC ERROR (1) frame, so a refused deck surfaces as a visible error dialog rather than an empty result with no explanation.

The complete list — cards refused by name, fields refused by value, and the structural refusals — is the grammar’s, with the exact message text:

  • cards refused by name — the out-of-dialect geometry cards, SP / SM, and the rest of what is not a wire;
  • fields refused by value — EX types other than 0, RP modes 1 and 4–6, spherical NE / NH grids, near fields over the GN 0 reflection-coefficient ground, GN radial ground screens, the unsupported LD types, the four IS cases a lossless whole-wire jacket cannot express, and the three TL / NT cases NEC itself halts on or destroys in silence;
  • structural refusals — malformed cards, and addresses that name nothing.

Two behaviours upstream of any engine are worth knowing, because no engine can report them:

  • SimNEC deletes SP cards from a script silently before the deck reaches a NEC-2-class engine. A pasted patch model solves as bare wire with no warning from anyone. SimNEC’s own Surface elements are properly wire-gridded instead, and those run here fine.
  • No apostrophes in CM / CE lines pasted into the script editor — SimNEC’s script parser reads ' as a quote and errors.

SimNEC probes an N-port antenna by sending N excitation groups in one deck, and a stock nec2c refills and refactors the whole moment matrix for each — N fills for one matrix. This engine takes the union of every group’s ports, fills and factors once per geometry and frequency, and answers each group by back-substitution on the cached factors. A three-port deck costs one fill, not three.

The deck need not be the boundary either. The protocol is stateless — a sweep arrives as N separate decks, each re-sending the whole geometry — but the engine may remember. With --cache, a solved structure is kept under a key built from everything that decides its moment matrix (geometry after transforms, ground, loading, port placement, kernel flag, basis), so a knob dragged back to a value already probed, or a restarted sweep, is answered without parsing, meshing or filling anything. The same structure at a new frequency reuses the mesh and pays only the fill.

That is off by default, because what the cache exploits is how often a real session re-sends a structure it has already sent — a property of your sessions, not of the bench. Measure first:

momwire-nec2c --cache-stats /tmp/nec-cache-stats.json

Put that in the portal dialog’s engine command and run a normal session. Every deck is solved exactly as it is without the flag — nothing is cached, nothing retained, the answers are the stock answers — but the engine computes each deck’s key and counts how many decks a cache would have served. The file is rewritten after every deck, because SimNEC ends a session by killing the process and anything written at exit is never written:

{"mode": "dry-run", "decks_rendered": 412, "hits": 273, "misses": 139,
"fills": 412, "evictions": 0, "bytes": 0, "entries": 0}

hits against decks_rendered is the saving on offer. SimNEC runs a crew of engine processes, and each counts only its own stream — give each portal entry its own stats path (--cache-stats /tmp/nec-stats.$$.json in a wrapper) or the crew members overwrite one file. If the numbers justify it:

momwire-nec2c --cache
momwire-nec2c --cache --cache-stats /tmp/nec-cache-stats.json

The first serves; the second serves and keeps measuring, which is the one to run for a while after switching over — entries and bytes then report what the process is actually holding. Neither flag changes a byte of the printout, in any mode.

A momwire process is not a 2 MB C binary: each carries NumPy, SciPy and momwire — about 90 MB resident before it solves anything — plus the dense complex matrix and its factors, which grow as the square of the segment count. It is also much quicker per deck once warm, so a smaller crew keeps up with a larger one of the C engine.

Without --cache that is the whole budget; a process keeps nothing between decks. With it, each process’s memory of past structures is capped at a few hundred MB, and a full cache costs a re-solve rather than a failure.

On a 16 GB machine, set the NEC crew size to 4. Larger crews buy little — the win here is the single fill per geometry, not parallelism across decks — and they multiply the per-process floor by the segment count you are least expecting.

SimNEC does not keep one engine per session. It starts a crew, sends a burst of decks, and destroys the processes — a live crew-16 session was measured starting 51 engines, each alive about a second. Every one of them paid the full cold start (Python, NumPy, parse, mesh, first fill: ~650 ms measured here) before it answered anything, and every one took its --cache with it when it died, so the cache’s main case — a value you retype a few minutes later — could never hit.

No crew size fixes that. momwire-nec2c-shared changes the shape instead:

Terminal window
which momwire-nec2c-shared # e.g. ~/.venvs/momwire/bin/momwire-nec2c-shared

Point the portal dialog at that path instead of momwire-nec2c, with the same flags you were using. Nothing else changes — no preference, no new setting, no restart discipline. What SimNEC now spawns is a few-hundred-line forwarder that imports neither momwire nor NumPy; it connects to one long-lived server holding the engine, and pumps your decks to it. On this box:

stock momwire-nec2cmomwire-nec2c-shared
-version probe610 ms70 ms
one cached deck, spawn to NX sentinel650 ms79 ms

The first client to arrive pays ~700 ms to start the server; every client after it is the right-hand column. The printout is identical either way — all 45 reference decks are compared byte for byte in the test suite, refusals included.

The server is chosen by a name hashed from the momwire version, the interpreter, and the engine flags, under $XDG_RUNTIME_DIR/momwire-portal/ (or /tmp/momwire-portal-<uid>/ where that is unset). That is the same two entries are two engines rule the flags already carried: two portal-dialog entries differing only in --basis get two servers, and a crew of sixteen clients configured alike get one.

All three spellings of the basis count, not just the flag: a basis chosen by the executable’s name or by MOMWIRE_NEC2C_BASIS is resolved before the hash is taken, so it picks out its own server exactly as --basis does. The shared command reads its own name the way the stock one does, with its own shared segment consumed rather than mistaken for a solver:

Terminal window
ln -s "$(which momwire-nec2c-shared)" ~/bin/momwire-nec2c-shared-bspline-d1
momwire-nec2c-shared-bspline-d1 -version # a bspline-d1 server, and only bspline-d1 decks

momwire-nec2c-bspline-d1 works as a link name too — the spelling the named commands above teach — and a bare momwire-nec2c-shared selects nothing, so it stays the default engine. A name matching no solver fails the -version probe loudly, naming its source, exactly as the stock command does. So the crew arithmetic above inverts — one interpreter, one ~90 MB floor, one --cache warmed by every crew member at once instead of sixteen cold ones, and one thread-pool budget, since solves are serialised across connections.

The server exits when no client has been connected for 15 minutes (--idle-timeout SECONDS on the same command line changes it) and removes its socket on the way out. A server killed less politely leaves a socket file behind; the next client notices it answers nothing, removes it, and starts a fresh one, so there is nothing to clean up by hand. Its log sits next to the socket — that is also where a deck’s warnings go, since with several clients on one process there is no honest stderr to return them on.

Upgrading momwire changes the hash, so the new version never gets served by whatever is still resident from the old one. Servers from before are left to their idle timeout.

Before pointing a session at it, run its own smoke — it starts a private server, sends decks through two separate client invocations, and checks that the second was answered by the process the first left behind:

Terminal window
momwire-nec2c-shared --selftest

SimNEC is proprietary freeware. This engine reproduces the printout layout SimNEC’s reader expects, worked out from observed output, and contains no nec2c code — interoperability of the same kind as emitting a NEC deck or a Touchstone file.