Skip to content

How many segments? Convergence you can measure

The reference page covers the short version: more segments resolve the current better, cost grows as N²–N³, run the convergence sweep and stop past the knee. That answer is right — and incomplete in one important way. A convergence curve that has flattened is not yet evidence that it flattened at the right value, and a curve that refuses to flatten can be telling you four different things, only one of which is fixed by adding segments.

This page is the longer answer, built from a catalog-wide convergence census (91 designs, every solver basis, meshes from N=7 to N=641). All the numbers below are from those measurements.

A single “coarse vs fine” comparison can mislead — curves overshoot. Run a geometric ladder: solve at N = 21, 61, 161, 321 (each rung ≈3× the last) and watch the whole trajectory. The workbench’s convergence sweep does exactly this and plots R and X against N per port.

Read it with a quantitative criterion, not by eye: call a rung converged when its impedance is within 1–2 % of the finest rung’s (|ΔZ|/|Z|). Here is a real one — the catalog’s verticals.dominator on the sinusoidal basis:

N2161161321641
Z (sin)23.1−1.4j28.3+1.2j31.6+4.0j30.8+1.2j30.7−0.1j

Two lessons in one table. First, the curve overshoots — at N=161 it is past the answer and still moving, so a 21/161 pair would have estimated the error with the wrong sign and the wrong size. Second, the plateau only starts around N=321. If your ladder’s last two rungs still differ by more than your tolerance, the ladder is too short to conclude anything.

The trap that motivates this whole page: a curve can be flat at the wrong value. Flatness measures self-consistency, not accuracy.

The strong test is the mutual limit: solve the same ladder on a different basis and require the two to converge to the same value. That is what the workbench’s solver slots are for — slot A (B-spline d=2) and slot B (d=1) are different discretizations of the same physics, so their agreement is evidence about the answer, while either one alone is only evidence about itself. dominator again, slot A this time:

N2161161321641
Z (bspline)30.6+3.2j30.6+2.8j30.7+2.5j30.9+0.9j30.9−0.5j

The d=2 basis is already at N=21 where the sinusoidal basis arrives at N=321–641 — and they arrive at the same place. That pair of facts is what “converged and correct” looks like. In the census, the two bases reached a mutual limit (<2 % apart at the finest affordable mesh) on 66 of 91 designs; on those, bspline was already within 2 % of the limit at N=21 on 53 — the sinusoidal basis on 36 — with conv@N advantages up to 15×. That measurement is why the default solver slot now runs bspline at N=15: on the scorable catalog it is converged out of the box, at ~35 % less cost per solve than N=21.

Why the sinusoidal basis lags has since been attributed, not just measured: momwire’s basis × testing instrument showed the census gap is mostly the point matching, not the basis — swap only the testing scheme (the sinusoidal-galerkin backend) and the junction-heavy gaps collapse ~100×, with the residual on clean geometry down at the feed-model level. The mechanism write-up is momwire chapter 15.

When the sweep does not flatten, resist the reflex to just raise N. The census decomposed every non-converging catalog design into four classes, and they want four different responses.

1. Slow basis convergence. The value is creeping monotonically and slot A (bspline) is flat at the value slot B is creeping toward. This is the common case — port-fed and junction-heavy designs converge slowly on the sinusoidal/pulse family. Response: trust the flat bspline value, or extend the ladder if you need the confirmation.

2. Mesh-density mismatch in the geometry. One part of the structure refines while another is pinned coarse, and the graded junction between them poisons the solve — the curve may diverge with refinement. (Two catalog verticals did exactly this until their radials were made to refine with the mesh; the tell was sin/PyNEC marching away from a flat bspline while the meshes decoupled.) Response: refine uniformly — in your own builders, just return None segment counts; the framework meshes every such wire at the design density, nominal_nsegs segments per quarter-wavelength at your declared design_freq (so N=15 means a segment length of λ/60, on every design). Integer counts are still honored verbatim as the legacy path, but hand-assigned counts are how every one of these defects was written.

3. Physics-limited: the near-open feed. A feed near a current null (|Z| in the thousands of ohms — end-fed designs, some multi-element arrays) is genuinely mesh-sensitive: the whole current distribution contributes to a huge, delicately-balanced impedance. The tell: both bases crawl together, in lockstep, at any port model. No basis and no default fixes this class; expect a slow drift of a few percent, read the admittance if you need a well-conditioned number, and treat the last percent as physical uncertainty rather than solver error.

One refinement since that was first written (momwire#213): on this class the apparent disagreement between bases — sin-Galerkin vs B-spline reading percent-level apart at the same mesh — turned out to be almost entirely the feed model, not the physics. The Sin-Galerkin solver’s Converged feed-model setting (the gear-menu control; the workbench recommends it on the designs that need it) removes two to three orders of magnitude of that cross-basis gap, measured out to N=641. What it does not change is the paragraph above: each solver’s own rung-to-rung drift is still percent-level, so the mesh budget and the “treat the last percent as physical” advice stand exactly as written.

4. A wire’s segments approaching its own radius (Δ/a). The oldest rule in thin-wire MoM is also the one that produced the census’s most spectacular failure. The thin-wire kernel needs each segment to stay long compared to the wire’s radius: the NEC-2 guideline is Δ/a > 8 for ~1 % accuracy, “reasonable solutions” down to about 2 — and below about 1 the discretized equation is genuinely ill-posed on the point-matched sinusoidal/pulse family (sin, PyNEC, nec2c — errors here are correlated across all three). In practice nobody violates this by choosing N too large globally; it happens when a builder gives a short wire a long wire’s segment count. The census’s folded inverted-V read 223−30j identically on both bases at N=21…61, then the sinusoidal basis went to 280−1188j at N=321 — and the cause was a 10 cm link wire silently carrying the full per-wire count, its segments down to 0.6× the wire radius. With that one wire meshed proportionally the sinusoidal ladder is dead flat at 223−30j through N=641. Two amplifiers made the disguise convincing: the folded element’s near-λ/4 shorted-stub mode sits at an antiresonance pole that turns a small localized error into a wildly wrong reactance (with no visible oscillation — the current stays smooth), and coarse meshes agree beautifully because the coarse mesh itself keeps every wire above the Δ/a floor. Response: when a ladder breaks at fine N, check per-wire Δ/a first — in your own builders, let auto_mesh assign the counts rather than reusing the nominal count on short wires (the catalog is now linted for exactly this, both the ratio at fine mesh and its growth up the ladder). The d=2 basis is immune — a Galerkin method regularizes the reduced-kernel ill-posedness — which is also why a flat bspline next to an exploding sin curve is the tell.

When the low Δ/a is the design rather than a meshing slip — tubing, cages, a fat-wire imported deck — the fix is not a finer mesh but a better kernel: the extended thin-wire kernel (the extended kernel (EK) check in a solver slot’s gear menu, --extended-kernel on the CLI) integrates the O(a²) term the filament approximation drops. It is worth several percent below Δ/a ≈ 3 and a fraction of a percent above Δ/a ≈ 10 — so it moves a genuinely fat wire and tells you nothing about a thin one. It does not rescue a segment shorter than its own radius: below Δ/a ≈ 1 the discretization is ill-posed whichever kernel fills it.

One genuine residue survives the Δ/a accounting, and it is now pinned (issue #484): multi-wire fan feeds — several dipole pairs sharing one feed wire — where the point-matched family drifts slowly and monotonically at fine mesh while the Galerkin bases hold flat, with every wire comfortably above the Δ/a floor. Three discriminating experiments settled what drives it. Varying the element spacing over a 7× range moves the error not at all — the traditional close-parallel-wire lore (the NEC-2 manual’s aligned-segments requirement, Cebik’s segment-length ≈ wire-spacing practice) does not explain this class. What does is the junction fan degree: a one-element control converges cleanly, and the fine-mesh error grows monotonically with each dipole pair added to the shared feed junction (a five-band fan reads ~30 % low on R at N=321). And it is a property of the method family, not one implementation: our sinusoidal solver and PyNEC (nec2++) drift in lockstep to a fraction of an ohm, while d=1 and d=2 B-splines — two independent Galerkin bases — sit flat on the same value from N=21. (That d=1 shares the immunity despite its merely-C⁰ junctions identifies Galerkin’s stationary impedance as the protective property: collocation takes junction-charge error at first order in Z, and a shared feed junction multiplies the error sites by the fan degree.) Response: on fan-fed multiband geometry, read the fine-mesh answer on the B-spline bases — and remember nec2c inherits the same family bias, so it is not an independent referee here.

One geometry class earns a specific note because the payoff runs the other way — not “raise N until it settles” but “you may lower it”. On single closed-loop designs the d=2 basis reaches the converged impedance at a mesh ~2–3× coarser than the sinusoidal basis needs (convergence anchor, free space; “converged” = within 2 % of the finest rung):

designsinusoidalB-spline d=2
loops.diamond_loop (square loop)N≥21N≥7
loops.delta_loop (triangle)N≥15N≥7

A dense solve scales as (basis count)², so 2–3× fewer segments is a ~4–9× cheaper solve at equal accuracy — on bspline a single-loop design can run below the N=15 default without losing the answer. Verify with the convergence sweep as usual, and don’t generalize the discount: it is loop-specific. On open linear structures (beams.yagi) and the two-element quad, every basis converges at the same N — which is why the workbench applies no automatic basis-dependent coarsening. The slider is yours.

Feeds and ports deserve their own paragraph

Section titled “Feeds and ports deserve their own paragraph”

The driving-point readout is the most mesh-sensitive number in the whole solve, because the port model can change under refinement even when the physics doesn’t:

  • A delta-gap feed narrows whenever the mesh subdivides its segment — on a short named port wire the readout can jump the first time the wire splits. Keep feed wires short (a dedicated ~1-segment stub at default mesh) so this happens far up the ladder, if ever.
  • A port where a transmission line or network attaches can use a distributed port (PortOnWire(name, distributed=True)) — the port then spans the wire’s fixed physical extent and is mesh-stable by construction. The catalog’s Sterba-curtain TL variant runs its nine ports this way; they refine like every other wire and hold one basis-agreed value.
  • A port positioned along a wire (PortOnWire(..., at=...)) stays where it was asked for as the mesh refines, and so does every other port on the same wire. At every rung the wire keeps that rung’s segment count: no engine re-counts it to reach a port. Where every gap is already a segment centre (PyNEC, NEC-2, momwire’s B-spline, sinusoidal and pulse solvers) or a knot (NEC-5, momwire’s razor and B-spline degree 1) of that mesh, the wire stays whole; otherwise it is split so every gap sits exactly there. On the segment-centre engines 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. On the knot engines the wire is cut at every port and each port is fed at the knot the pieces share. Every momwire solver feeds positioned ports exactly, split like its reference engine family. The solve carries a FeedPlacement advisory saying so. Imported NEC decks place their off-centre sources and loads this way.
  • A lumped load (termination resistor, trap) is genuinely a point element — keep it on a delta gap, on a short named wire that meshes at the design density like everything else. The load stays on the wire’s middle segment as the mesh refines, so the point model survives refinement; pinning the wire’s count instead (the old convention) turned out to bias the converged value itself — the catalog’s trap study saw a pinned trap hold bspline flat at the wrong reactance while the density-meshed model converged cleanly. What you should not do is spread the load’s port current over a finite extent: gap-averaging a physical resistor under-counts its dissipation. Even the historically hardest case — a load at a near-current-null (high-|Z|) attachment, where the delta gap’s parasitic susceptance competes with the tiny real admittance — turned out to be mesh-stable under this treatment: the catalog’s terminated longwire kept pinned counts for years on evidence that refinement made its readout drift, but re-probing on the modern port machinery showed the density-meshed model reaching a clean mutual limit where the pinned one left the bases permanently apart (issue #526). The old drift was the port readout, not the physics.

A convergence study is easiest to read as two curves against the segment count, on a log axis: resistance on one vertical scale and reactance on another. R and X usually sit tens of ohms apart and move by fractions of an ohm, so one shared scale flattens whichever moves less. Both the workbench and the command line draw it this way.

In the workbench, pick the convergence analysis on the Sweep chart (its header’s analysis list), which sets the parameter to density (N per λ/4). Set the range and the number of points in the chart’s header (for example 10 to 500, 20 points, log spacing). R sits on the left axis and X on the right, each with its own range (click an axis). Hover a point to read its values. The dotted line on each axis is the extrapolated converged value, and the dashed guide marks the design’s current density. The chart’s Smith view draws the same sweep as a labelled trail. Stop keeps the points you have. The same view sweeps any numeric knob, not only density: right-click a knob and choose Sweep this knob….

To compare engines, run the sweep once per solver slot. The fixed reference lines, R = Z₀ and X = 0, make it easy to see which way each curve is heading.

From the command line, the same chart, one panel per engine:

Terminal window
python -m antennaknobs sweep --builder dipoles.invvee:dipole \
--set freq=14 design_freq=14 --param nominal_nsegs \
--log --range 10 500 --npoints 20 \
--engine nec2,nec5,momwire:razor-2p --panels --callouts

--log spaces the points geometrically and rounds integer parameters, --panels gives each engine its own twin-axis panel, and --callouts labels the first and last values. --r-range and --x-range fix an axis when you want several charts on the same scale. See the CLI reference for the rest.

The dotted line on each axis, and the Z∞ in the chart’s header, is an estimate of the value the curve is heading for as N grows without limit. It is Richardson extrapolation: once the mesh is fine enough, the answer follows a smooth low-order curve in h = 1/N, and following that curve to h = 0 gives the limit without solving an infinite mesh.

Two panels. Left: razor-2p&#x27;s feed reactance on the HenTenna plotted against h = 1/N on linear axes, the fine rungs lying on a straight line that meets h = 0 at X∞ = 38.848 Ω, with B-spline&#x27;s points flat at 38.915 Ω. Right: the change per doubling on log-log axes, razor-2p following a slope of −1 from about 230 segments on, B-spline&#x27;s changes ten to a hundred times smaller.

The two panels answer different questions, and both are needed:

  • Right, log–log: is it safe to extrapolate? Plot how much the value moves per doubling of N, |Z(N) − Z(2N)|. On a log–log chart this is a straight line of slope −p when the solver converges at order p. Here razor-2p (the formulation NEC-5 uses) follows slope −1 from about 230 segments: each doubling halves the step. The first two rungs sit off the line; that coarse range is where no extrapolation should be trusted. The successive differences need no knowledge of the answer, so the chart cannot assume its own conclusion.
  • Left, linear in h: what is the answer? In that first-order range the points lie on a straight line in h, and its intercept at h = 0 is the limit: X∞ = 38.848 Ω. B-spline, an independent formulation, sits almost flat at 38.915 Ω. The two limits agree to 0.07 Ω, and that agreement is what makes the number an answer rather than a guess (see Flat is not the same as right).

The workbench and the command line compute Z∞ the same way, against the segment count each rung actually meshed, and both make the right-hand check before extrapolating:

  • Asymptotic. When the last two local slopes of the log–log chart agree within 10 % (and the order they give is between 0.25 and 4), the observed order p is used: Z = Z∞ + C·N^−p is fitted through the last three rungs. The value is shown with its order, p 0.98. One p serves R and X, taken from the complex step |ΔZ|.
  • Rough. With only three rungs, a bent log–log line, or steps that change direction, the first order is assumed and Z∞ comes from the last two rungs. It is labelled rough: add finer rungs before quoting it.
  • Converged. When the last step is below one part in a million of |Z|, there is nothing to extrapolate and Z∞ is the finest rung.

A rough reading often has a cause you can see in the mesh. A short feed wire gains segments two at a time, which keeps the source centred: 1 → 3 on B-spline, 2 → 4 on razor-2p. So on a density ladder the fed segment stays the same length for several rungs while the rest of the mesh refines, then jumps, and each jump puts a kink in the ladder. When that happens within the last four rungs, the rough reading names the step. On the default inverted vee’s 0.1 m feed wire, for example, the app’s ladder reads rough on both engines:

  • on B-spline, the fed segment goes 100 → 33 mm between N = 65 and 95;
  • on razor-2p, it goes 50 → 25 mm between N = 94 and 134.

That is a property of the design’s feed wire, not of the engine. The rough value is still the best first-order estimate the ladder supports.

On the HenTenna, razor-2p’s ladder is asymptotic, with p = 0.98 and X∞ = 38.836 Ω, 0.012 Ω from the first-order line in the figure. The buried radial vertical’s point-matched ladder is not: its local slopes drift from 0.5 toward 0.3, and an observed-order fit there lands more than 1 Ω from the independent answer, so it is reported rough, at first order. (The figure is scratch/richardson-demo/richardson_demo.py, from sweep --param nominal_nsegs --markers 10 20 40 80 160 320 640 on the two engines.)

Charting a free-space dipole and a full-wave loop this way at 14 MHz, on the same geometry as a SimNEC model built for the purpose, reproduced SimNEC’s NEC-2 and NEC-5 curves, to the printed digits at nearly every point. It also showed two things worth knowing before you trust a curve:

  • A delta-gap engine measures the feed as well as the antenna. On a design fed through a short dedicated feed wire (most of the catalog’s dipoles and loops), NEC-2, PyNEC and momwire’s sinusoidal solver move a long way with N, and can jump where the feed wire first splits into more than one segment. That movement is the feed model changing, not the antenna converging. The workbench warns when this is the case. The same designs move far less on the B-spline solver, razor-2p and NEC-5. At its default 28.47 MHz, between 10 and 500 segments per quarter-wave, the catalog dipole’s resistance changes by about 0.2 Ω on B-spline, while on PyNEC (NEC-2) it climbs about 6 Ω and drops more than 1 Ω where the feed wire splits.
  • A loop’s curve shape depends on the engine. The same square loop’s resistance rises monotonically on NEC-2 but peaks and falls back on NEC-5 and razor-2p before settling. Both settle to the same value within a fraction of an ohm. Judge convergence by where the curves end up, not by their shape at coarse N.
  1. Solve at the defaults (bspline, N=15). For most of the catalog you are already converged — the census says 80 % of scorable designs are within 2 % at coarse mesh on this basis.
  2. Run a convergence sweep: the Sweep chart with density as the parameter (see Charting a convergence study). Flat within your tolerance across the top rungs → done.
  3. Not flat? Turn on slot B and compare trajectories:
    • B creeping toward a flat A → class 1, trust A;
    • both marching away together as N grows → check your geometry for pinned-coarse wires meeting refined ones (class 2);
    • both crawling in lockstep at high |Z| → class 3, physics — accept the band, don’t chase it;
    • agreement at coarse N that breaks or drifts at fine N while the other slot stays flat → class 4 — check per-wire Δ/a first (a short wire carrying a long wire’s segment count is the classic cause; fix the density, don’t just back N off), and on fan-feed geometry with healthy Δ/a, trust the flat d=2 value (#484).
  4. Report the value both bases agree on, at the coarsest mesh past the plateau — that is the defensible number, and the cheapest one.

From the command line, --overlay draws every engine’s R and X on one shared pair of axes, so an offset between bases reads at a glance:

Terminal window
python -m antennaknobs sweep --builder specialty.hentenna \
--param nominal_nsegs --range 8 160 --npoints 14 \
--engine momwire:bspline,momwire:razor-2p,momwire:sinusoidal \
--overlay --callouts --fn hentenna-overlay.png