1
From team Blueberry, JAAM.
Just Another Antenna Modeller.
From NEC-2
to openEMS
to a language.
Primarily designed to speed up the development of RF antennas and other such equipment, the FOSS way.
Arecibo Observatory
JAAM
source: NEC-2 Part I, §§I–II
2
NEC-2: We used punch cards for this stuff?
NEC-2 is an integral-equation antenna solver built around currents on wires and conducting surfaces.
For wire antennas, that model is still excellent. The problem is what you have to feed it.
01
Wire-first geometry
Straight wire segments, plus surface patches where needed.
02
Frequency-domain solve
Accurate current and radiation analysis without filling all of free space.
03
Mature and inspectable
Sources, loads, grounds and radiation patterns are explicit.
The kind of structure NEC-2 likes: mostly wire.
Yes, those are indeed wires in a Yagi Antenna.
JAAM
source: NEC-2 User’s Guide, structure modelling guidelines
3
NEC-2: The geometry is numerical
You are not only describing an antenna. You are choosing a discretisation that keeps NEC inside its assumptions.
01
Segment everything
Curves become piecewise-linear wires. The guide recommends Δ < ~0.1λ, often smaller in critical regions.
02
Respect the thin-wire model
The classic approximation assumes radius ≪ wavelength and radius ≪ wire length.
03
Surface models need care
NEC can use patches, but the original docs explicitly warn that surface-field accuracy can vary.
04
Ground is another model choice
Perfect, Sommerfeld/Norton and reflection approximations each come with different costs and caveats.
Curved geometry is still ultimately a set of numerical choices.
JAAM
source: openEMS docs + JAAM docs/finale-demo.md
4
OPENEMS: A shift to 3D
openEMS gives us a full FDTD domain instead of a wire-first model.
That buys us CSXCAD geometry, PML boundaries, lumped ports, graded Cartesian meshes and NF2FF results in one open solver stack.
01
More general geometry
Wires, boxes, materials and volumetric structure live in the same domain.
02
Time-domain solve
One run can cover a frequency band instead of one frequency at a time.
03
Open output path
The generated model and solver artifacts remain inspectable.
The modelling target is no longer a “stick figure”.
JAAM
source: JAAM benchmarks/dipole_reference.py + docs/native-setup.md + openEMS mesh docs
5
openEMS: The Woes of Meshing and Cells
The solver is capable. The authoring surface is not small.
A hand-written run still has to sequence geometry calls, split feeds, reserve PML space, place fixed mesh lines, smooth them, run the solver, then post-process the result.
And the native Python bindings are compiled dependencies. On host they live in system Python, not the project venv.
Powerful solver. Bad place to encode intent.
JAAM
source: JAAM README.md
6
JAAM: Codify Intent, and let the Compiler do the rest.
JAAM is a declarative language and traced compiler in front of openEMS.
The source describes the antenna. The compiler owns units, topology, lowering, mesh construction and artifacts before arbitrary Python ever runs.
.jaam source files
→
ANTLR AST
→
typed IR
→
optimization passes
→
mesh plan
→
CSXCAD + openEMS
→
versioned artifact
The IR is the one true source.
Emitter, visualiser and runtime all consume the same resolved model.
JAAM
source: examples/dipole.jaam + benchmarks/dipole_reference.py
7
EXAMPLE: the same dipole
JAAM source
Hand-written openEMS reference
JAAM still emits a real openEMS model. It just moves feed splitting, PML reservation, units and mesh policy into compiler-owned code. So the code is reusable in normal Python as well!
JAAM
source: src/jaam/passes.py + JAAM README.md
8
OPTIMISATION: The compiler knows your model
Because JAAM owns a typed IR, it can simplify the numerical setup without rewriting the user’s antenna.
canonicalize-wire-vertices
remove exact/redundant points from the structure
merge-collinear-wires
collapse compatible straight runs to save time
graded-mesh-coarsening
coarsen away from geometry to reduce cells
mesh-anchor-pruning
experimental: fewer fixed coordinates for detailed curves
The pruning pass keeps the detailed curve vertices sent to CSXCAD. It only fixes fewer Cartesian mesh coordinates and lets CSXCAD insert intermediate lines.
opt-in until curved-antenna parity is proven
JAAM
source: JAAM README.md, Optimization evidence
9
BENCHMARKS: Grid complexity and size
Actual post-smoothing FDTD cells, measured from CSXCAD GetLines().
Yagi
66,816 → 66,816
0%
Helix
254,113 → 44,030
−82.7%
Parabolic arc
88,264 → 55,760
−36.8%
Straight concat
29,160 → 29,160
0%
The big win is curved geometry.
Something that NEC2 has historically struggled with, and equal in wire antennas.
JAAM
source: JAAM README.md + scripts/compare_dipole_reference.py
10
BENCHMARKS: Did we change the math?
Grid cells are expensive. We cut them down. Did we regress?
Hand-written openEMS dipole vs JAAM
on the same frozen mesh
0.0061 dB
max S11 difference
0.033%
impedance difference
0.00012 dB
max far-field cut difference
Experimental parabolic pruning
52.6 s → 24.2 s
but:
up to 1.24 dB cut drift
14.9% impedance drift
So mesh-anchor pruning stays opt-in.