@necpp-engine/wasm 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COPYING +340 -0
- package/README.md +402 -0
- package/dist/deck.d.ts +4 -0
- package/dist/deck.js +129 -0
- package/dist/errors.d.ts +35 -0
- package/dist/errors.js +57 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +22 -0
- package/dist/loader.d.ts +3 -0
- package/dist/loader.js +144 -0
- package/dist/model.d.ts +21 -0
- package/dist/model.js +702 -0
- package/dist/nec2pp.generated.js +15 -0
- package/dist/nec2pp.wasm +0 -0
- package/dist/state-machine.d.ts +16 -0
- package/dist/state-machine.js +76 -0
- package/dist/types.d.ts +247 -0
- package/dist/types.js +1 -0
- package/dist/versions.d.ts +10 -0
- package/dist/versions.js +10 -0
- package/dist/wasm-internal.d.ts +61 -0
- package/dist/wasm-internal.js +1 -0
- package/dist/worker-client.d.ts +11 -0
- package/dist/worker-client.js +325 -0
- package/dist/worker-entry.d.ts +1 -0
- package/dist/worker-entry.js +93 -0
- package/dist/worker-protocol.d.ts +62 -0
- package/dist/worker-protocol.js +257 -0
- package/dist/worker-runtime.d.ts +14 -0
- package/dist/worker-runtime.js +130 -0
- package/dist/worker.d.ts +5 -0
- package/dist/worker.js +3 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# `@necpp-engine/wasm`
|
|
2
|
+
|
|
3
|
+
Stateful NEC2++ antenna simulation for Node and browsers, exposed through a
|
|
4
|
+
handwritten TypeScript API. The package owns the WebAssembly details: callers
|
|
5
|
+
do not copy artifacts, build NEC decks, parse reports, or handle native
|
|
6
|
+
pointers.
|
|
7
|
+
|
|
8
|
+
> **License:** this package and its `nec2pp.wasm` engine are
|
|
9
|
+
> **GPL-2.0-or-later**. Distributing an application that includes or serves the
|
|
10
|
+
> package is distribution of GPL software. Read [License](#license) before
|
|
11
|
+
> shipping it in a product.
|
|
12
|
+
|
|
13
|
+
## Five-minute dipole
|
|
14
|
+
|
|
15
|
+
Install with `npm install @necpp-engine/wasm`. The package is ESM-only and
|
|
16
|
+
requires Node 24 or newer.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { createNecModel } from "@necpp-engine/wasm";
|
|
20
|
+
|
|
21
|
+
const model = await createNecModel();
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
model.addWire({
|
|
25
|
+
tag: 1,
|
|
26
|
+
segments: 11,
|
|
27
|
+
start: [0, 0, -0.25],
|
|
28
|
+
end: [0, 0, 0.25],
|
|
29
|
+
radiusM: 0.001,
|
|
30
|
+
});
|
|
31
|
+
model.completeGeometry();
|
|
32
|
+
model.definePorts([{ tag: 1, segment: 6, name: "feed" }]);
|
|
33
|
+
model.prepare({ frequencyMHz: 300 });
|
|
34
|
+
|
|
35
|
+
const { impedance, admittance } = model.computeImpedanceMatrix();
|
|
36
|
+
const solution = model.solveCurrents({
|
|
37
|
+
real: new Float64Array([1]),
|
|
38
|
+
imag: new Float64Array([0]),
|
|
39
|
+
});
|
|
40
|
+
const field = model.computeFarField({
|
|
41
|
+
radiusM: 1,
|
|
42
|
+
theta: { startDeg: 0, count: 181, stepDeg: 1 },
|
|
43
|
+
phi: { startDeg: 0, count: 1, stepDeg: 0 },
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
console.log({
|
|
47
|
+
zOhm: [impedance.real[0], impedance.imag[0]],
|
|
48
|
+
ySiemens: [admittance.real[0], admittance.imag[0]],
|
|
49
|
+
requiredVoltageV: [solution.voltages.real[0], solution.voltages.imag[0]],
|
|
50
|
+
fieldSamples: field.eThetaReal.length,
|
|
51
|
+
});
|
|
52
|
+
} finally {
|
|
53
|
+
model.dispose();
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This complete example is executed from the packed npm tarball in CI.
|
|
58
|
+
|
|
59
|
+
## Numerical conventions
|
|
60
|
+
|
|
61
|
+
- Coordinates, wire radius, and field radius are metres. Public frequencies
|
|
62
|
+
are MHz.
|
|
63
|
+
- Phasors use `e^(+j omega t)` and outgoing propagation uses `e^(-jkR)`.
|
|
64
|
+
- Port voltage is in complex volts. Port current is in complex amperes and is
|
|
65
|
+
positive **into** the modeled antenna.
|
|
66
|
+
- `V = Z I` and `I = Y V`; impedance is in ohms and admittance is in siemens.
|
|
67
|
+
- Complex far-field components are V/m. `radiusM` defaults to 1 m and is
|
|
68
|
+
retained in every result.
|
|
69
|
+
- Theta is the polar angle down from +Z. Phi is azimuth from +X toward +Y.
|
|
70
|
+
- Matrices are row-major: `index = row * columns + column`. Far-field samples
|
|
71
|
+
are theta-fast: `index = phiIndex * thetaCount + thetaIndex`.
|
|
72
|
+
- Fields are referenced to the model origin and remain far-field
|
|
73
|
+
approximations even when a small radius is requested.
|
|
74
|
+
- Every returned typed array is a JavaScript-owned copy. It remains valid
|
|
75
|
+
across later solves, WebAssembly memory growth, and model disposal.
|
|
76
|
+
|
|
77
|
+
Coordinate orientation:
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
+Z theta=0 deg
|
|
81
|
+
|
|
|
82
|
+
|\ r
|
|
83
|
+
| \
|
|
84
|
+
| * sample
|
|
85
|
+
| / theta
|
|
86
|
+
|/
|
|
87
|
+
+Y origin -------- +X phi=0 deg
|
|
88
|
+
\ /
|
|
89
|
+
\ phi /
|
|
90
|
+
\------/
|
|
91
|
+
|
|
92
|
+
Phi increases from +X toward +Y; theta increases from +Z toward the XY plane.
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The full normative contract, including loads, ground, tolerances, and every
|
|
96
|
+
state transition, is in
|
|
97
|
+
[`docs/wasm-api.md`](https://github.com/andrekuehne/necpp/blob/master/docs/wasm-api.md).
|
|
98
|
+
|
|
99
|
+
## Geometry and ports
|
|
100
|
+
|
|
101
|
+
Build geometry first, complete it once, then define an ordered port list.
|
|
102
|
+
Wire tags are positive integers. A port segment is one-based among all
|
|
103
|
+
segments carrying that tag, so an 11-segment dipole is centre-fed at segment
|
|
104
|
+
6. Port order fixes the order used by every vector, matrix row/column, and
|
|
105
|
+
embedded field basis.
|
|
106
|
+
|
|
107
|
+
The initial environment is free space with no loads. Call `addLoad()`,
|
|
108
|
+
`clearLoads()`, or `setGround()` after geometry completion and before
|
|
109
|
+
`prepare()`. Changing ground or loads later is allowed, but invalidates the
|
|
110
|
+
factorization and returns the model to `geometry-complete`.
|
|
111
|
+
|
|
112
|
+
## Z and Y matrices
|
|
113
|
+
|
|
114
|
+
`computeImpedanceMatrix()` factors the electromagnetic interaction matrix once
|
|
115
|
+
and returns both port matrices. Entry `Z[row,column]` is the voltage at port
|
|
116
|
+
`row` produced by a unit current at port `column`, with all other requested
|
|
117
|
+
port currents zero. `Y` has the analogous voltage-driven interpretation.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import type { ComplexMatrix } from "@necpp-engine/wasm";
|
|
121
|
+
|
|
122
|
+
function entry(matrix: ComplexMatrix, row: number, column: number) {
|
|
123
|
+
const index = row * matrix.columns + column;
|
|
124
|
+
return {
|
|
125
|
+
real: matrix.real[index],
|
|
126
|
+
imag: matrix.imag[index],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
declare const z: ComplexMatrix;
|
|
131
|
+
const selfImpedance = entry(z, 0, 0);
|
|
132
|
+
const mutualImpedance = entry(z, 0, 1);
|
|
133
|
+
console.log({ selfImpedance, mutualImpedance });
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The returned `conditionEstimate` is omitted only when the native implementation
|
|
137
|
+
cannot estimate it. Matrix formation throws `NecConditioningError` instead of
|
|
138
|
+
returning a singular or excessively ill-conditioned inverse.
|
|
139
|
+
|
|
140
|
+
## Voltage- and current-driven arrays
|
|
141
|
+
|
|
142
|
+
`solveVoltages()` applies exactly the requested simultaneous complex voltages.
|
|
143
|
+
`solveCurrents()` first computes the required voltages with `V = Z I`, then
|
|
144
|
+
executes one simultaneous source solve. Both return achieved voltages and
|
|
145
|
+
currents, per-port powers, and active impedances in stable port order.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import type { NecModel } from "@necpp-engine/wasm";
|
|
149
|
+
|
|
150
|
+
declare const model: NecModel;
|
|
151
|
+
|
|
152
|
+
const voltageDriven = model.solveVoltages({
|
|
153
|
+
real: new Float64Array([1, 0]),
|
|
154
|
+
imag: new Float64Array([0, 1]),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const currentDriven = model.solveCurrents({
|
|
158
|
+
real: new Float64Array([1, 0]),
|
|
159
|
+
imag: new Float64Array([0, -1]),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
console.log(voltageDriven.currents, currentDriven.voltages);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Matrix impedance and active impedance are different quantities. `Z[i,j]` is a
|
|
166
|
+
fixed property of the prepared model. Active impedance is `V[i] / I[i]` for
|
|
167
|
+
one particular simultaneous excitation, so mutual coupling makes it change
|
|
168
|
+
when array weights change. An exactly zero achieved current produces
|
|
169
|
+
`NaN + jNaN` active impedance; inspect the voltage/current vectors instead of
|
|
170
|
+
dividing by zero. Time-average input power is
|
|
171
|
+
`0.5 * Re(V * conjugate(I))` watts.
|
|
172
|
+
|
|
173
|
+
## Complex far fields and beamforming
|
|
174
|
+
|
|
175
|
+
`computeFarField()` uses the most recent public solve. At the default 1 m,
|
|
176
|
+
`eTheta*` and `ePhi*` are split real/imaginary V/m arrays. At another range,
|
|
177
|
+
the field follows `e^(-jkR) / R` while retaining the same angular far-field
|
|
178
|
+
approximation.
|
|
179
|
+
|
|
180
|
+
`computeEmbeddedFarFields()` returns one complex basis pattern per port.
|
|
181
|
+
Unit-current normalization makes array beamforming a direct weighted sum. The
|
|
182
|
+
arrays are basis-major, followed by the normal theta-fast sample layout.
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import type {
|
|
186
|
+
EmbeddedFarFieldResult,
|
|
187
|
+
NecModel,
|
|
188
|
+
} from "@necpp-engine/wasm";
|
|
189
|
+
|
|
190
|
+
declare const model: NecModel;
|
|
191
|
+
|
|
192
|
+
const embedded = model.computeEmbeddedFarFields(
|
|
193
|
+
{
|
|
194
|
+
radiusM: 1,
|
|
195
|
+
theta: { startDeg: 90, count: 1, stepDeg: 0 },
|
|
196
|
+
phi: { startDeg: 0, count: 361, stepDeg: 1 },
|
|
197
|
+
},
|
|
198
|
+
{ kind: "unit-current", valueA: 1 },
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const phaseStepRad = Math.PI / 3;
|
|
202
|
+
const weights = embedded.ports.map((_, port) => ({
|
|
203
|
+
real: Math.cos(port * phaseStepRad),
|
|
204
|
+
imag: Math.sin(port * phaseStepRad),
|
|
205
|
+
}));
|
|
206
|
+
|
|
207
|
+
function combineETheta(basis: EmbeddedFarFieldResult) {
|
|
208
|
+
const real = new Float64Array(basis.samplesPerPort);
|
|
209
|
+
const imag = new Float64Array(basis.samplesPerPort);
|
|
210
|
+
for (let port = 0; port < basis.ports.length; port += 1) {
|
|
211
|
+
const weight = weights[port]!;
|
|
212
|
+
for (let sample = 0; sample < basis.samplesPerPort; sample += 1) {
|
|
213
|
+
const index = port * basis.samplesPerPort + sample;
|
|
214
|
+
const er = basis.eThetaReal[index]!;
|
|
215
|
+
const ei = basis.eThetaImag[index]!;
|
|
216
|
+
real[sample] = real[sample]! + weight.real * er - weight.imag * ei;
|
|
217
|
+
imag[sample] = imag[sample]! + weight.real * ei + weight.imag * er;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return { real, imag };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
console.log(combineETheta(embedded));
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
For a one-off excitation, `solveCurrents()` plus `computeFarField()` is simpler.
|
|
227
|
+
Embedded fields are useful when many weight sets share one geometry and
|
|
228
|
+
frequency: compute the bases once, then combine them in JavaScript without
|
|
229
|
+
additional native solves.
|
|
230
|
+
|
|
231
|
+
## Direct mode and worker mode
|
|
232
|
+
|
|
233
|
+
| Mode | Import | Calls | Best for |
|
|
234
|
+
|---|---|---|---|
|
|
235
|
+
| Direct | `@necpp-engine/wasm` | Synchronous after creation | Node, tests, small browser models |
|
|
236
|
+
| Worker | `@necpp-engine/wasm/worker` | Asynchronous and serialized | Browser UI and realistic solves |
|
|
237
|
+
|
|
238
|
+
The factory is always asynchronous because it instantiates WebAssembly. A
|
|
239
|
+
direct browser solve then occupies the main thread until it finishes. The
|
|
240
|
+
worker facade preserves model state in a package-supplied module worker and
|
|
241
|
+
transfers large result buffers back to the caller.
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import { createNecWorkerModel } from "@necpp-engine/wasm/worker";
|
|
245
|
+
|
|
246
|
+
const model = await createNecWorkerModel({
|
|
247
|
+
onProgress: ({ operation, phase }) => console.log(operation, phase),
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
await model.addWire({
|
|
252
|
+
tag: 1,
|
|
253
|
+
segments: 11,
|
|
254
|
+
start: [0, 0, -0.25],
|
|
255
|
+
end: [0, 0, 0.25],
|
|
256
|
+
radiusM: 0.001,
|
|
257
|
+
});
|
|
258
|
+
await model.completeGeometry();
|
|
259
|
+
await model.definePorts([{ tag: 1, segment: 6 }]);
|
|
260
|
+
await model.prepare({ frequencyMHz: 300 });
|
|
261
|
+
console.log(await model.computeImpedanceMatrix());
|
|
262
|
+
} finally {
|
|
263
|
+
await model.dispose();
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Worker calls cannot interrupt a synchronous native calculation. Use
|
|
268
|
+
`model.terminate()` for immediate cancellation; it kills the worker and
|
|
269
|
+
rejects outstanding operations. Create a new model to continue afterward.
|
|
270
|
+
|
|
271
|
+
## Lifecycle and disposal
|
|
272
|
+
|
|
273
|
+
The normal lifecycle is
|
|
274
|
+
`empty -> geometry-building -> geometry-complete -> prepared -> solved`.
|
|
275
|
+
`computeImpedanceMatrix()` and embedded-field calculation are legal while
|
|
276
|
+
prepared; combined far fields require a latest solution. Repeating
|
|
277
|
+
`prepare()` at the same frequency is idempotent. New excitations and field
|
|
278
|
+
grids reuse the retained factorization. Geometry cannot change after
|
|
279
|
+
`completeGeometry()`.
|
|
280
|
+
|
|
281
|
+
Always dispose in `finally`. Direct `dispose()` and worker `await dispose()`
|
|
282
|
+
are idempotent. Every other operation after disposal throws `NecStateError`.
|
|
283
|
+
|
|
284
|
+
## Node, browser, Vite, and CDN loading
|
|
285
|
+
|
|
286
|
+
Node and browsers use the same package and public types. By default,
|
|
287
|
+
`nec2pp.wasm` is resolved beside the installed JavaScript with
|
|
288
|
+
`new URL("./nec2pp.wasm", import.meta.url)`; consumers do not copy it.
|
|
289
|
+
|
|
290
|
+
Direct mode needs no Vite configuration. For the module-worker entry point,
|
|
291
|
+
use this Vite configuration:
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
import { defineConfig } from "vite";
|
|
295
|
+
|
|
296
|
+
export default defineConfig({
|
|
297
|
+
build: { target: "es2024" },
|
|
298
|
+
worker: { format: "es" },
|
|
299
|
+
});
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Production servers should serve `.wasm` as `application/wasm`. To host the
|
|
303
|
+
binary on a CDN, pass an HTTP(S) URL. Cross-origin servers must also send an
|
|
304
|
+
appropriate CORS header.
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
import { createNecModel } from "@necpp-engine/wasm";
|
|
308
|
+
|
|
309
|
+
const model = await createNecModel({
|
|
310
|
+
wasmUrl: new URL("https://cdn.example.test/necpp/0.1.0/nec2pp.wasm"),
|
|
311
|
+
});
|
|
312
|
+
model.dispose();
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
`wasmBinary` accepts an `ArrayBuffer` or `Uint8Array` when the host application
|
|
316
|
+
wants to fetch/cache the bytes itself. `wasmUrl` and `wasmBinary` are mutually
|
|
317
|
+
exclusive. `runDeck(deck)` remains available as a compatibility escape hatch
|
|
318
|
+
for complete NEC text decks.
|
|
319
|
+
|
|
320
|
+
## Error handling
|
|
321
|
+
|
|
322
|
+
Every package-defined operational error derives from `NecError` and has a
|
|
323
|
+
stable `code`: `NEC_STATE`, `NEC_INPUT`, `NEC_GEOMETRY`, `NEC_PORT`,
|
|
324
|
+
`NEC_CONDITIONING`, `NEC_SOLVER`, or `NEC_RUNTIME`. Messages and `details` are
|
|
325
|
+
diagnostic rather than a compatibility contract.
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
import {
|
|
329
|
+
NecConditioningError,
|
|
330
|
+
NecError,
|
|
331
|
+
createNecModel,
|
|
332
|
+
} from "@necpp-engine/wasm";
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
const model = await createNecModel();
|
|
336
|
+
try {
|
|
337
|
+
model.computeImpedanceMatrix();
|
|
338
|
+
} finally {
|
|
339
|
+
model.dispose();
|
|
340
|
+
}
|
|
341
|
+
} catch (error: unknown) {
|
|
342
|
+
if (error instanceof NecConditioningError) {
|
|
343
|
+
console.error("Port matrix cannot be inverted reliably", error.details);
|
|
344
|
+
} else if (error instanceof NecError) {
|
|
345
|
+
console.error(error.code, error.message, error.details);
|
|
346
|
+
} else {
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## Performance and browser memory
|
|
353
|
+
|
|
354
|
+
- Segment count dominates factorization time and memory. Thin-wire modeling
|
|
355
|
+
still requires physically sensible segment length/radius ratios; begin with
|
|
356
|
+
modest odd segment counts and refine while checking convergence.
|
|
357
|
+
- Keep a prepared model alive while changing excitations or angular grids.
|
|
358
|
+
Recreating it discards the expensive factorization.
|
|
359
|
+
- Prefer embedded fields when exploring many array weights at one frequency.
|
|
360
|
+
- Field storage scales with `theta.count * phi.count`; embedded storage also
|
|
361
|
+
multiplies by port count and by four `Float64Array` components. A full
|
|
362
|
+
181 x 361 field is about 2 MiB for the four component arrays; four embedded
|
|
363
|
+
bases are about 8 MiB, excluding axes and temporary/native storage.
|
|
364
|
+
- Returned arrays are copies, so release references when results are no longer
|
|
365
|
+
needed. Compute cuts instead of dense spheres when possible.
|
|
366
|
+
- Use worker mode for browser responsiveness. Each worker model owns an
|
|
367
|
+
isolated WebAssembly instance and memory, so dispose unused models rather
|
|
368
|
+
than pooling many idle workers.
|
|
369
|
+
- There is no shared-memory or thread requirement. Normal cross-origin
|
|
370
|
+
isolation headers are not needed for this package.
|
|
371
|
+
|
|
372
|
+
## Complete Vite array example
|
|
373
|
+
|
|
374
|
+
The repository's
|
|
375
|
+
[four-element array application](https://github.com/andrekuehne/necpp/tree/master/examples/wasm-array-vite)
|
|
376
|
+
installs the packed package, computes Z/Y, applies progressive complex current
|
|
377
|
+
weights, displays achieved port quantities, and plots a normalized azimuth
|
|
378
|
+
cut. CI builds and runs that exact application in Chromium from the same
|
|
379
|
+
tarball used by every release gate.
|
|
380
|
+
|
|
381
|
+
## Versions
|
|
382
|
+
|
|
383
|
+
| Export | Meaning |
|
|
384
|
+
|---|---|
|
|
385
|
+
| `packageVersion` | Semantic version of the public TypeScript API |
|
|
386
|
+
| `engineVersion` | NEC2++ version compiled into the shipped WebAssembly |
|
|
387
|
+
| `abiVersion` | Stable internal C ABI; currently `1` |
|
|
388
|
+
|
|
389
|
+
Instantiation rejects a binary whose ABI or engine version does not match the
|
|
390
|
+
facade. Package and engine versions intentionally have independent version
|
|
391
|
+
lines; the release records both.
|
|
392
|
+
|
|
393
|
+
## License
|
|
394
|
+
|
|
395
|
+
`@necpp-engine/wasm` is distributed under **GPL-2.0-or-later**, matching
|
|
396
|
+
NEC2++. The npm tarball includes `COPYING` with the full license text.
|
|
397
|
+
|
|
398
|
+
If you convey the JavaScript loader, WebAssembly binary, or an application
|
|
399
|
+
containing them, review and satisfy the GPL's corresponding-source, license,
|
|
400
|
+
and notice requirements for your distribution. This README is a technical
|
|
401
|
+
notice, not legal advice. Consult qualified counsel for a product-specific
|
|
402
|
+
licensing decision.
|
package/dist/deck.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DeckResult, RunDeckOptions } from "./types.js";
|
|
2
|
+
import type { NecWasmModule } from "./wasm-internal.js";
|
|
3
|
+
export declare function validateDeckText(deckText: unknown): asserts deckText is string;
|
|
4
|
+
export declare function runDeckWithModule(module: NecWasmModule, deckText: string, options?: RunDeckOptions): DeckResult;
|
package/dist/deck.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { NecConditioningError, NecGeometryError, NecInputError, NecPortError, NecRuntimeError, NecSolverError, } from "./errors.js";
|
|
2
|
+
const textEncoder = new TextEncoder();
|
|
3
|
+
const textDecoder = new TextDecoder();
|
|
4
|
+
function decodeBytes(module, pointer, length) {
|
|
5
|
+
if (!Number.isSafeInteger(pointer)
|
|
6
|
+
|| pointer < 0
|
|
7
|
+
|| !Number.isSafeInteger(length)
|
|
8
|
+
|| length < 0
|
|
9
|
+
|| pointer + length > module.HEAPU8.length) {
|
|
10
|
+
throw new NecRuntimeError("The native deck runner returned an invalid buffer");
|
|
11
|
+
}
|
|
12
|
+
return textDecoder.decode(module.HEAPU8.slice(pointer, pointer + length));
|
|
13
|
+
}
|
|
14
|
+
function decodeCString(module, pointer) {
|
|
15
|
+
if (!Number.isSafeInteger(pointer)
|
|
16
|
+
|| pointer <= 0
|
|
17
|
+
|| pointer >= module.HEAPU8.length) {
|
|
18
|
+
throw new NecRuntimeError("The native module returned an invalid string");
|
|
19
|
+
}
|
|
20
|
+
const end = module.HEAPU8.indexOf(0, pointer);
|
|
21
|
+
if (end < 0) {
|
|
22
|
+
throw new NecRuntimeError("The native module returned an unterminated string");
|
|
23
|
+
}
|
|
24
|
+
return decodeBytes(module, pointer, end - pointer);
|
|
25
|
+
}
|
|
26
|
+
function deckError(module, deck, status) {
|
|
27
|
+
let message = `Deck execution failed with native status ${status}`;
|
|
28
|
+
try {
|
|
29
|
+
const nativeMessage = decodeCString(module, module._necpp_wasm_v1_deck_last_error(deck));
|
|
30
|
+
if (nativeMessage.length > 0) {
|
|
31
|
+
message = nativeMessage;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Keep the stable fallback message.
|
|
36
|
+
}
|
|
37
|
+
const details = { operation: "runDeck", nativeStatus: status };
|
|
38
|
+
switch (status) {
|
|
39
|
+
case 2:
|
|
40
|
+
throw new NecInputError(message, { details });
|
|
41
|
+
case 3:
|
|
42
|
+
throw new NecGeometryError(message, { details });
|
|
43
|
+
case 4:
|
|
44
|
+
throw new NecPortError(message, { details });
|
|
45
|
+
case 5:
|
|
46
|
+
throw new NecConditioningError(message, { details });
|
|
47
|
+
case 6:
|
|
48
|
+
throw new NecSolverError(message, { details });
|
|
49
|
+
default:
|
|
50
|
+
throw new NecRuntimeError(message, { details });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function assertNotAborted(options) {
|
|
54
|
+
if (options?.signal?.aborted === true) {
|
|
55
|
+
throw new NecInputError("Deck execution was aborted before it started", {
|
|
56
|
+
details: { operation: "runDeck", aborted: true },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export function validateDeckText(deckText) {
|
|
61
|
+
if (typeof deckText !== "string" || deckText.length === 0) {
|
|
62
|
+
throw new NecInputError("deck must be a nonempty string");
|
|
63
|
+
}
|
|
64
|
+
if (deckText.includes("\0")) {
|
|
65
|
+
throw new NecInputError("deck cannot contain embedded NUL characters");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function runDeckWithModule(module, deckText, options) {
|
|
69
|
+
assertNotAborted(options);
|
|
70
|
+
validateDeckText(deckText);
|
|
71
|
+
const bytes = textEncoder.encode(deckText);
|
|
72
|
+
if (bytes.length === 0) {
|
|
73
|
+
throw new NecInputError("deck must contain UTF-8 input");
|
|
74
|
+
}
|
|
75
|
+
let deck = 0;
|
|
76
|
+
let inputPointer = 0;
|
|
77
|
+
try {
|
|
78
|
+
deck = module._necpp_wasm_v1_deck_create();
|
|
79
|
+
if (!Number.isSafeInteger(deck) || deck <= 0) {
|
|
80
|
+
throw new NecRuntimeError("Failed to create the native deck runner");
|
|
81
|
+
}
|
|
82
|
+
inputPointer = module._malloc(bytes.length);
|
|
83
|
+
if (!Number.isSafeInteger(inputPointer) || inputPointer <= 0) {
|
|
84
|
+
throw new NecRuntimeError("WASM memory allocation failed");
|
|
85
|
+
}
|
|
86
|
+
module.HEAPU8.set(bytes, inputPointer);
|
|
87
|
+
assertNotAborted(options);
|
|
88
|
+
const status = module._necpp_wasm_v1_deck_process(deck, inputPointer, bytes.length);
|
|
89
|
+
if (status !== 0) {
|
|
90
|
+
deckError(module, deck, status);
|
|
91
|
+
}
|
|
92
|
+
const reportLength = module._necpp_wasm_v1_deck_output_length(deck);
|
|
93
|
+
const report = decodeBytes(module, module._necpp_wasm_v1_deck_output(deck), reportLength);
|
|
94
|
+
const engineVersion = decodeCString(module, module._necpp_wasm_v1_engine_version());
|
|
95
|
+
return { report, engineVersion };
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (error instanceof NecInputError
|
|
99
|
+
|| error instanceof NecGeometryError
|
|
100
|
+
|| error instanceof NecPortError
|
|
101
|
+
|| error instanceof NecConditioningError
|
|
102
|
+
|| error instanceof NecSolverError
|
|
103
|
+
|| error instanceof NecRuntimeError) {
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
throw new NecRuntimeError("runDeck failed at the WASM boundary", {
|
|
107
|
+
cause: error,
|
|
108
|
+
details: { operation: "runDeck" },
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
if (inputPointer !== 0) {
|
|
113
|
+
try {
|
|
114
|
+
module._free(inputPointer);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Preserve the operation's result.
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (deck !== 0) {
|
|
121
|
+
try {
|
|
122
|
+
module._necpp_wasm_v1_deck_delete(deck);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Cleanup is contained at the ABI.
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { NecModelState } from "./types.js";
|
|
2
|
+
export type NecErrorCode = "NEC_STATE" | "NEC_INPUT" | "NEC_GEOMETRY" | "NEC_PORT" | "NEC_SOLVER" | "NEC_CONDITIONING" | "NEC_RUNTIME";
|
|
3
|
+
export interface NecErrorOptions {
|
|
4
|
+
readonly cause?: unknown;
|
|
5
|
+
readonly details?: Readonly<Record<string, unknown>>;
|
|
6
|
+
}
|
|
7
|
+
/** Base class for every package-defined operational error. */
|
|
8
|
+
export declare class NecError<TCode extends NecErrorCode = NecErrorCode> extends Error {
|
|
9
|
+
readonly code: TCode;
|
|
10
|
+
readonly details: Readonly<Record<string, unknown>> | undefined;
|
|
11
|
+
constructor(code: TCode, message: string, options?: NecErrorOptions);
|
|
12
|
+
}
|
|
13
|
+
export declare class NecStateError extends NecError<"NEC_STATE"> {
|
|
14
|
+
readonly operation: string;
|
|
15
|
+
readonly state: NecModelState;
|
|
16
|
+
constructor(operation: string, state: NecModelState, message?: string);
|
|
17
|
+
}
|
|
18
|
+
export declare class NecInputError extends NecError<"NEC_INPUT"> {
|
|
19
|
+
constructor(message: string, options?: NecErrorOptions);
|
|
20
|
+
}
|
|
21
|
+
export declare class NecGeometryError extends NecError<"NEC_GEOMETRY"> {
|
|
22
|
+
constructor(message: string, options?: NecErrorOptions);
|
|
23
|
+
}
|
|
24
|
+
export declare class NecPortError extends NecError<"NEC_PORT"> {
|
|
25
|
+
constructor(message: string, options?: NecErrorOptions);
|
|
26
|
+
}
|
|
27
|
+
export declare class NecSolverError extends NecError<"NEC_SOLVER"> {
|
|
28
|
+
constructor(message: string, options?: NecErrorOptions);
|
|
29
|
+
}
|
|
30
|
+
export declare class NecConditioningError extends NecError<"NEC_CONDITIONING"> {
|
|
31
|
+
constructor(message: string, options?: NecErrorOptions);
|
|
32
|
+
}
|
|
33
|
+
export declare class NecRuntimeError extends NecError<"NEC_RUNTIME"> {
|
|
34
|
+
constructor(message: string, options?: NecErrorOptions);
|
|
35
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Base class for every package-defined operational error. */
|
|
2
|
+
export class NecError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
details;
|
|
5
|
+
constructor(code, message, options = {}) {
|
|
6
|
+
super(message, { cause: options.cause });
|
|
7
|
+
this.name = "NecError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.details = options.details;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export class NecStateError extends NecError {
|
|
13
|
+
operation;
|
|
14
|
+
state;
|
|
15
|
+
constructor(operation, state, message) {
|
|
16
|
+
super("NEC_STATE", message ?? `Operation ${operation} is not valid while the model is ${state}`, { details: { operation, state } });
|
|
17
|
+
this.name = "NecStateError";
|
|
18
|
+
this.operation = operation;
|
|
19
|
+
this.state = state;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class NecInputError extends NecError {
|
|
23
|
+
constructor(message, options) {
|
|
24
|
+
super("NEC_INPUT", message, options ?? {});
|
|
25
|
+
this.name = "NecInputError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class NecGeometryError extends NecError {
|
|
29
|
+
constructor(message, options) {
|
|
30
|
+
super("NEC_GEOMETRY", message, options ?? {});
|
|
31
|
+
this.name = "NecGeometryError";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export class NecPortError extends NecError {
|
|
35
|
+
constructor(message, options) {
|
|
36
|
+
super("NEC_PORT", message, options ?? {});
|
|
37
|
+
this.name = "NecPortError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export class NecSolverError extends NecError {
|
|
41
|
+
constructor(message, options) {
|
|
42
|
+
super("NEC_SOLVER", message, options ?? {});
|
|
43
|
+
this.name = "NecSolverError";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export class NecConditioningError extends NecError {
|
|
47
|
+
constructor(message, options) {
|
|
48
|
+
super("NEC_CONDITIONING", message, options ?? {});
|
|
49
|
+
this.name = "NecConditioningError";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export class NecRuntimeError extends NecError {
|
|
53
|
+
constructor(message, options) {
|
|
54
|
+
super("NEC_RUNTIME", message, options ?? {});
|
|
55
|
+
this.name = "NecRuntimeError";
|
|
56
|
+
}
|
|
57
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { NecConditioningError, NecError, NecGeometryError, NecInputError, NecPortError, NecRuntimeError, NecSolverError, NecStateError, } from "./errors.js";
|
|
2
|
+
export { abiVersion, engineVersion, packageVersion } from "./versions.js";
|
|
3
|
+
export type { NecErrorCode, NecErrorOptions } from "./errors.js";
|
|
4
|
+
export type { AngleSweep, CartesianPointM, CompleteGeometryOptions, ComplexMatrix, ComplexVector, ConductivityLoad, CreateNecModelOptions, CreateNecWorkerModelOptions, DeckResult, DistributedParallelRlcLoad, DistributedSeriesRlcLoad, EmbeddedFarFieldResult, EmbeddedFieldNormalization, FarFieldRequest, FarFieldResult, FiniteGround, FreeSpaceGround, GroundConnection, GroundModel, ImpedanceLoad, ImpedanceResult, LoadDefinition, NecModel, NecModelState, NecWorkerModel, NecWorkerOperation, NecWorkerProgressEvent, NecWorkerProgressListener, ParallelRlcLoad, PerfectGround, PortDefinition, PortSolution, PrepareOptions, RunDeckOptions, SegmentSelection, SeriesRlcLoad, WireDefinition, } from "./types.js";
|
|
5
|
+
import type { CreateNecModelOptions, DeckResult, NecModel, RunDeckOptions } from "./types.js";
|
|
6
|
+
/** Create an isolated stateful NEC model backed by a new WASM module instance. */
|
|
7
|
+
export declare function createNecModel(options?: CreateNecModelOptions): Promise<NecModel>;
|
|
8
|
+
/** Compatibility escape hatch for complete NEC text decks. */
|
|
9
|
+
export declare function runDeck(deck: string, options?: RunDeckOptions): Promise<DeckResult>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export { NecConditioningError, NecError, NecGeometryError, NecInputError, NecPortError, NecRuntimeError, NecSolverError, NecStateError, } from "./errors.js";
|
|
2
|
+
export { abiVersion, engineVersion, packageVersion } from "./versions.js";
|
|
3
|
+
import { runDeckWithModule, validateDeckText } from "./deck.js";
|
|
4
|
+
import { NecInputError } from "./errors.js";
|
|
5
|
+
import { instantiateNecModule } from "./loader.js";
|
|
6
|
+
import { createModelFromModule } from "./model.js";
|
|
7
|
+
/** Create an isolated stateful NEC model backed by a new WASM module instance. */
|
|
8
|
+
export async function createNecModel(options) {
|
|
9
|
+
const module = await instantiateNecModule(options);
|
|
10
|
+
return createModelFromModule(module);
|
|
11
|
+
}
|
|
12
|
+
/** Compatibility escape hatch for complete NEC text decks. */
|
|
13
|
+
export async function runDeck(deck, options) {
|
|
14
|
+
validateDeckText(deck);
|
|
15
|
+
if (options?.signal?.aborted === true) {
|
|
16
|
+
throw new NecInputError("Deck execution was aborted before it started", {
|
|
17
|
+
details: { operation: "runDeck", aborted: true },
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const module = await instantiateNecModule(options);
|
|
21
|
+
return runDeckWithModule(module, deck, options);
|
|
22
|
+
}
|
package/dist/loader.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { CreateNecModelOptions } from "./types.js";
|
|
2
|
+
import type { NecWasmModule, NecWasmModuleFactory } from "./wasm-internal.js";
|
|
3
|
+
export declare function instantiateNecModule(options: CreateNecModelOptions | undefined, factory?: NecWasmModuleFactory): Promise<NecWasmModule>;
|