From 92e2d25c99ed2e88f9b28d7ec199e3acbec6d306 Mon Sep 17 00:00:00 2001 From: Tim Molteno Date: Tue, 10 Nov 2026 09:56:39 +1300 Subject: [PATCH] docs: document the Sommerfeld near-ground limitation deck (PR #137) Add a Tests entry to the CHANGELOG and a Known-limitation decks section to the testharness README for sommerfeld_near_ground.nec: a horizontal half-wave dipole 0.02 wavelengths above a GN 2 ground with sigma 1e10 (effectively a perfect conductor). Verified on master: every engine descends from SOMNEC and returns R ~ 1.759 ohms / gain ~ 1.043 where the perfect-conductor limit (GN 1) is R = 0.9168 ohms / gain 1.9953; the pre-e3f81e6 interpolation bug accidentally landed near the exact limit (R = 0.924, gain 1.980, reproduced on a8e829e). The deck joins SOM_TESTS and documents current behavior on all engines rather than gating pass/fail. --- CHANGELOG.md | 3 +++ testharness/README.md | 15 +++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6242dbdd..002b731e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ * **`gshank` Shanks-tail convergence floor restored after it caused non-terminating Sommerfeld integrations:** issue #131 item 3 also raised gshank()'s convergence floor from `denm = 1e-3*den*CRIT` to `2e-1*den*CRIT_H` — 200× looser. On decks with wires close to a `GN 2` ground (e.g. a radial screen elevated 0.2 m over epsr 13 / sig 0.005 at 2.7 MHz), the truncated Shanks tail degrades the Sommerfeld tables enough that `rom2()` can never meet its `RX = 1e-4` Romberg test: the integration doubles to the 65536-subdivision cap at every step, emits an endless `ROM2 -- STEP SIZE LIMITED` stream (GB/minute into both stdout and the output file) and effectively never terminates. The same deck completes in ~14 ms with the previous floor, agreeing with `nec2c` (13.906 − j445.84 Ω). The floor is restored to `1e-3*den*CRIT_H` while the milder `CRIT_H = 1.1e-4` tail-test scaling is kept; output is byte-identical (modulo timing lines) to the pre-loosening build on `sommerfeld2/3` and `bruce_sommerfeld`. Regression decks: `testharness/data/wire_order_normal.nec`, `wire_order_reversed.nec`; unit test `[issue139]` (which also fails on this regression via timeout). * **`nec2diff` compares negative values, reports finite sign-flip differences, and exits non-zero when results differ (#138):** the real-valued `diff()` in `BaseInput.h` tested *signed* values against the near-zero floor (`a < 1e-8 && b < 1e-8`), so any two negative numbers compared equal — a 178 Ω negative-reactance change (`−179.39 → −1.00`) or a `−1.71 → −9.71 dBi` gain change reported `Difference = 0`. A pure sign flip (`−179.39 → +179.39`) divided by `|a+b| = 0` and printed `inf`. Both `diff()` overloads now normalize by the larger magnitude (`|a−b|/max(|a|,|b|)`), keeping the near-zero early-out but keyed on absolute values. Two behavioral notes: (1) this metric is close to twice the old `|a−b|/|a+b|` for near-equal values, so the `equalto()` thresholds (sum ≤ 1e-4) now bind about twice as tightly and previously hidden last-digit engine discrepancies are visible in `nec2diff` output; (2) `main()` now tracks a flag across the three sections and returns 1 if any section differs, so scripts and harnesses can rely on the exit status. `nec2diff` with fewer than two arguments prints usage and returns 1 instead of throwing `std::logic_error` from a null `std::string` construction. Regression fixtures and an exit-code test live in `tests/data/issue138/` (ctest `necpp_nec2diff_issue138`); the four cases report 0.994, 0.824, 0.167 (control) and a finite 2.0, matching the reporter's values. +### Tests +* **Sommerfeld near-ground limit deck (`testharness/data/sommerfeld_near_ground.nec`, PR #137):** a horizontal half-wave dipole 0.02 wavelengths above a `GN 2` ground with sigma 1e10 S/m, i.e. a half-space that is effectively a perfect conductor. Over a true perfect conductor (`GN 1`, exact by image theory) the same antenna gives R = 0.9168 Ω and average power gain 1.9953 (the lossless limit is 2); at this height every SOMNEC-derived engine — nec2++, `nec2c` and the original Fortran (aegnec2) — instead returns R ≈ 1.759 Ω and gain ≈ 1.043, whose pattern integral accounts for only about half the supplied power. The deck documents a shared NEC-2 Sommerfeld limitation near ground (NEC-2 Part 3 claims the Sommerfeld/Norton method "is accurate close to the ground", which the inherited SOMNEC implementation does not achieve in the σ→PEC limit), not a translation bug. It also records that the pre-`e3f81e6` interpolation bug landed much closer to the exact limit (R = 0.924, gain = 1.980) purely because its stale coefficients pulled the near-ground values that way — verified on commit `a8e829e`. The deck joins `SOM_TESTS`; it documents current behavior on all engines and is not a pass/fail gate. + ### Performance * **`saoa` shares one `xl*xl` across all five uses:** the Sommerfeld integrand squared `xl` independently for each of `cgam1`, `cgam2` (the two complex `sqrt` arguments), the three `dgam` large-|xl| branches, and the `rho==0` transverse answer. One complex multiply per integrand evaluation is now shared (issue #131). Algebraic identity — byte-identical output on all five `GN 2` decks; benchmarked at ≤0.1% on a noisy laptop, consistent with the multiplication being a small fraction of the transcendental-dominant kernel. * **Triangular solves now use Eigen's blocked `solveInPlace`:** `solve()` (`matrix_algebra.cpp`) replaced its hand-rolled scalar forward/back substitution with `triangularView/triangularView` `solveInPlace`, eliminating two heap temporaries per call and the cache-hostile row-walk of column-major data. Hot in plane-wave sweeps (one solve per incidence angle). diff --git a/testharness/README.md b/testharness/README.md index 7c39dfea..53fe1be5 100644 --- a/testharness/README.md +++ b/testharness/README.md @@ -37,3 +37,18 @@ Drop a `.nec` input file into `data/`. The harness will run it through The `data/` directory contains over 45 models covering dipoles, patches, helices, Sommerfeld ground, arrays, and more. + +### Known-limitation decks + +Some decks document behavior rather than assert correctness, so their +output files record what every engine currently produces: + +- `sommerfeld_near_ground.nec` — a horizontal half-wave dipole 0.02 + wavelengths above a `GN 2` ground with sigma 1e10 S/m (an effectively + perfect half-space). Over a true perfect conductor (`GN 1`) the same + antenna gives R = 0.9168 ohms and average power gain 1.9953 (the lossless + limit is 2), but every SOMNEC-derived engine — nec2++, nec2c and the + original Fortran — returns R ≈ 1.759 ohms and gain ≈ 1.043 there. This + is a shared NEC-2 Sommerfeld limitation near ground, not a translation + bug (PR #137). Compare against the `GN 1` variant of the deck to see the + gap.