Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,30 @@
## Unreleased
## 0.9.0 - 2026-10-16

### Added

* Conductivity-load (LD 5) regression coverage. Native and package tests pin
the following:
* `structureLossW` equals the segment-centre sum of NEC's `ZINT` resistance
in all three branches.
* Every selected segment is loaded with its own radius, feed segments
included.
* `prepare()` at a new frequency recomputes the load, on the stateful model
and on `NecArraySolver`.
* Perfect-ground loss counts physical segments only.
* The ring evaluator accepts loaded wires.
* Results agree with the original FORTRAN NEC-2 on copper, stainless, and
low-σ decks.
`docs/wasm-api.md` documents the formula and the accounting rule.

### Bug Fixes

* **Out-of-range load segments get an accurate error:** a tag-relative load
range that runs past the tag's last segment now fails at `addLoad` with
"SEGMENT TARGET IS OUT OF RANGE FOR THE TAG". The old message, "NO SEGMENT
HAS AN ITAG OF n", named a tag that does exist. The error class
(`NecGeometryError`) and the absent-tag message are unchanged.

## 0.8.0 - 2026-09-27

### Bug Fixes

Expand All @@ -10,8 +36,6 @@
collision with upstream's C `nec_ground_connection`. Native C++ callers must
update the type name and rebuild. C/WASM entry points and values are unchanged.

## 0.8.0 - 2026-09-27

### Added

* Opt-in ring band-limited far-field evaluation for ordinary fields, both
Expand Down
56 changes: 55 additions & 1 deletion docs/wasm-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,60 @@ tag-relative segment addressing follow the
[EX card](https://www.nec2.org/part_3/cards/ex.html). Load units and targeting
follow the [LD card](https://www.nec2.org/part_3/cards/ld.html).

### Conductivity loads (LD 5)

A `{ kind: "conductivity", conductivitySPerM: σ }` load is NEC-2 LD type 5.
Perfect conductor is expressed by having no load. This section is normative
for consumers that compute conductor loss from exported currents.

- **Scope.** Every segment the target selects is loaded, including source
(port) segments. A tag-only target selects every segment of the tag.
- **Radius.** Each segment uses its own radius, `radiiM` in current
distributions and `radiusM` in NECQ.
- **Impedance per metre.** NEC's `ZINT` is used, with \(\lambda\) the NEC
free-space wavelength and \(a\) the segment radius. Internally NEC uses
\(\omega\mu_0=2\pi\cdot120\pi/\lambda\), not \(\mu_0c_0\). Define
\(x=a\sqrt{2\pi\cdot120\pi\,\sigma/\lambda}=\sqrt2\,a/\delta\). Then
\[Z'=\frac{j}{a}\sqrt{\frac{60}{\sigma\lambda}}\;
\frac{\mathrm{ber}\,x+j\,\mathrm{bei}\,x}{\mathrm{ber}'x+j\,\mathrm{bei}'x}
\quad[\Omega/\mathrm{m}],\]
evaluated in three branches:
- \(x\le8\): NEC's Kelvin-function polynomials;
- \(8<x\le110\): NEC's asymptotic series;
- \(x>110\): the constant ratio \(0.70710678-0.70710678j\), which gives
\(R'=\sqrt{60/(\sigma\lambda)}\cdot0.70710678/a\).

The first two branches match the exact \(J_0/J_1\) form to about
\(2\times10^{-7}\). The last branch drops the \(1/(\sqrt2\,x)\) correction,
so it reads \(0.35\%\) below the exact Bessel value at \(x\approx200\), and
\(R'\) jumps by about \(-0.64\%\) across \(x=110\). Consumers that must
reproduce `structureLossW` copy the three branches literally. The reference
implementation is `ld5ResistancePerMetre` in
`packages/necpp-wasm/test/conductivity-load.test.mjs`.
- **Frequency.** Each `prepare()` at a new frequency recomputes \(Z'\), on
both the stateful model and `NecArraySolver`. A model swept
\(f_1\to f_2\) is bitwise identical to a fresh model prepared at \(f_2\).
- **Loss accounting.** `structureLossW` is the segment-centre rule
\(\sum_i\tfrac12\,\mathrm{Re}(Z'_i)\,L_i\,|I_i(0)|^2\), where
\(I_i(0)=A_i+C_i\). It is not a quadrature of \(|I(s)|^2\) along the
segment. The sum runs over physical segments only, so perfect-ground images
are excluded. With four Gauss–Legendre NECQ nodes, \(I(0)\) is recovered
exactly from the even part \(A+C\cos ks\) at the two node pairs. The
Gauss–Legendre integral of \(|I|^2\) differs from `structureLossW` by about
\(10^{-5}\) to \(4\times10^{-4}\) on dipoles and helices.
- **Impedance shift.** \(\mathrm{Re}\,Z(\sigma)-\mathrm{Re}\,Z(\mathrm{PEC})\)
is not the conductor loss. The internal reactance \(\mathrm{Im}\,Z'\)
redistributes the current, which changes the radiated power by a comparable
amount: +9 % of the loss on a 0.47 λ copper dipole, −80 % on a helix. Use
`structureLossW` or the centre rule above.
- **Validation.** `addLoad` rejects \(\sigma\le0\) and non-finite values with
`NecInputError`. A tag-relative range that runs past the tag's last segment
fails with `NecGeometryError` "SEGMENT TARGET IS OUT OF RANGE FOR THE TAG";
it is never silently clipped. Out-of-range absolute targets and absent tags
also fail at `addLoad`, not at `prepare`.
- **Field evaluators.** Loads do not change the field kernels. The ring
evaluator accepts loaded wires as ordinary wires.

## Array layout and ownership

`ComplexVector.real` and `.imag` have the same length. A vector supplied to a
Expand Down Expand Up @@ -245,7 +299,7 @@ enumerates every operation/state pair plus both `prepare()` branches.
| `addWire(wire)` | Positive integer tag/count; distinct finite endpoints and positive finite radius, all in m | `void`; copies the definition | `NecInputError` for shape/range errors; `NecGeometryError` for engine geometry limits |
| `completeGeometry(options?)` | Ground connection plus optional finalized reflection/rotation descriptor | `GeometryCompletionResult`; `symmetry` is absent for ordinary completion | `NecInputError` for an invalid descriptor; `NecGeometryError` for intersections, invalid junctions, symmetry/ground conflicts, or a ground-incompatible structure |
| `definePorts(ports)` | Nonempty ordered tag and one-based segment pairs | `void`; copies and freezes order | `NecPortError` for missing/duplicate ports or non-source-capable segments; `NecInputError` for malformed integers |
| `addLoad(load)` | Segment target and impedance, RLC, or conductivity values in the units above | `void`; invalidates prepared data | `NecInputError` for invalid values/ranges; `NecGeometryError` when no segment matches |
| `addLoad(load)` | Segment target and impedance, RLC, or conductivity values in the units above | `void`; invalidates prepared data | `NecInputError` for invalid values/ranges; `NecGeometryError` when no segment matches or a tag-relative range runs past the tag's last segment |
| `clearLoads()` | None | `void`; removes every load and invalidates prepared data | No non-state failure |
| `setGround(ground)` | Free space, perfect ground, or finite ground with method, relative permittivity, and S/m conductivity | `void`; invalidates prepared data | `NecInputError` for nonphysical values; `NecGeometryError` if inconsistent with geometry completion |
| `prepare({ frequencyMHz })` | One positive finite MHz value | `void`; retains/factors the interaction matrix | `NecInputError` for frequency; `NecPortError` if ports are absent; `NecSolverError` for fill/factorization failure |
Expand Down
2 changes: 1 addition & 1 deletion packages/necpp-wasm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -864,7 +864,7 @@ appropriate CORS header.
import { createNecModel } from "@necpp-engine/wasm";

const model = await createNecModel({
wasmUrl: new URL("https://cdn.example.test/necpp/0.8.0/nec2pp.wasm"),
wasmUrl: new URL("https://cdn.example.test/necpp/0.9.0/nec2pp.wasm"),
});
model.dispose();
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"fixtureSchema": "current-quadrature-v1",
"abiVersion": 1,
"engineVersion": "2.5.0",
"packageVersion": "0.8.0",
"packageVersion": "0.9.0",
"cases": [
{
"id": "dipole",
Expand Down
4 changes: 2 additions & 2 deletions packages/necpp-wasm/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/necpp-wasm/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@necpp-engine/wasm",
"version": "0.8.0",
"version": "0.9.0",
"private": false,
"type": "module",
"description": "Stateful NEC2++ electromagnetic solver and symmetric-array API for Node and browsers",
Expand Down
2 changes: 1 addition & 1 deletion packages/necpp-wasm/src/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
* CMake `project(necpp VERSION ...)` value compiled into the shipped WASM.
* `abiVersion` is the stable C ABI prefix `necpp_wasm_v1`.
*/
export const packageVersion = "0.8.0";
export const packageVersion = "0.9.0";
export const abiVersion = 1;
export const engineVersion = "2.5.0";
Loading
Loading