Antenna Forge: inverse antenna design with a real MoM solver in the optimization loop
AI-driven inverse antenna design with real NEC2 + openEMS in the loop. Try the live in-browser playground.
At a glance
- What is it?
- Antenna Forge (YAF) is a Python platform that runs differential evolution and gradient methods against real NEC2 and openEMS evaluations instead of a surrogate model. The pitch is honest and the flagship Yagi result is reproducible from one script, but the repository also advertises a much larger service stack than the README demonstrates.
- Who is it for?
- Adopt Antenna Forge if you need a wire-antenna or planar-EM optimizer whose objective function is a real Method-of-Moments or FDTD solve, and you are willing to read the scripts rather than the API surface: start with scripts/demo_inverse_design.py, then scripts/case_yagi.py, and check that necpp imports before anything else.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 107 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem Antenna Forge addresses: optimizing against physics, not against a model of physics
Most machine-learning antenna design work optimizes a surrogate. A neural network is trained on a corpus of simulated geometries, then the optimizer searches the surrogate because a full-wave solve is too slow to call thousands of times. The failure mode is well known to anyone who has done it: the optimizer finds the region where the surrogate is wrong, not the region where the antenna is good. Antenna Forge takes the opposite position. The README states that in the flagship case study every single one of the 5858 optimizer iterations is evaluated by real NEC2 through the necpp Python binding, and that the demo script raises SolverUnavailable rather than fabricating output if necpp is missing. That refusal to fall back to an analytical model is the design decision the whole project rests on. It targets RF engineers and researchers who want to search a continuous geometry space (element lengths, spacings) and trust the resulting numbers, plus ML people who want a physically grounded objective to benchmark their surrogate against. It is not aimed at someone who wants a schematic editor or a link-budget calculator.
What actually runs in the loop: differential evolution over a NEC2 objective
The mechanism visible in the README is narrow and concrete. A geometry is described by continuous parameters, a solver adapter converts them into a solver input, the solver returns impedance and radiation data, and a post-processing step turns that into the scalar the optimizer minimizes. In the dipole demo, golden-section search runs over a single parameter, the dipole length, from a bracket of plus or minus 75 mm, and converges in 14 iterations and 16 real NEC2 solver calls to L = 477.892 mm at 300 MHz with R = 71.85 ohm, X = +0.03 ohm and G = 2.13 dBi. In the Yagi case, the parameter vector is 9-dimensional (5 element lengths plus 4 inter-element spacings) and the driver is scipy.optimize.differential_evolution, a population-based method that tolerates a noisy or non-smooth objective better than a gradient method would. The README notes that the optimizer recovers director tapering (0.440 to 0.434 to 0.429 m) and a 0.243 lambda reflector spacing without any antenna theory in the objective, which is the expected behaviour when the physics is real: the textbook recipe is a local optimum of the same function. The architecture diagram shows a wider ambition (FastAPI gateway, Celery workers, PostgreSQL and Qdrant, a React and Three.js frontend), but the scripts are the part the README actually demonstrates end to end.
Getting a result: the scripts that need necpp, and the Docker stack that does not
There are two entry paths in the README and they have different prerequisites. The script path is the one with verified output. After cloning with git clone https://github.com/1ove9/antenna-forge.git yaf, running python3 scripts/demo_wow.py should write docs/assets/dipole_demo.png with three panels: input impedance R(f) and X(f) with the resonance point marked, an E-plane polar pattern, and S11(f) over VSWR(f) with the minus 10 dB bandwidth shaded. python3 scripts/demo_inverse_design.py writes the convergence plot, and python3 scripts/case_yagi.py followed by python3 scripts/plot_yagi.py writes JSON into results/ and the design figure. All of these depend on the necpp binding being importable. The service path is different: cp .env.example .env, then docker compose up -d, then curl http://localhost:8000/health expecting {"status": "ok", "version": "0.1.0"}, with the frontend on port 5173. The REST surface is shown as POST /api/v1/designs with keys name, frequency_range, size_constraint, polarization and material_palette, and POST /api/v1/simulations with design_id, solver, frequency_min and frequency_max. Note the units in that second call: 2400000000, not 2.4e9. The README does not show a response body for either POST, so what comes back is unverified here.
The Yagi comparison is the strongest evidence, and it has a stated cost
The headline claim is a five-element Yagi-Uda at 300 MHz compared against the Viezbicke five-element design from NBS TN 688, same element count, same NEC2 backend. The README reports forward gain of +12.63 dBi against +11.03 dBi, front-to-back of 15.00 dB against 13.79 dB, and a boom length of 1.17 lambda against 1.00 lambda. The honest part is that last row. The AI design is 17 percent longer, so it is not a free gain; it is a gain bought with physical extent, and the README says so in the same table rather than burying it. The README also claims strict dominance on both axes over 3 of 4 published five-element references (Viezbicke, ARRL Handbook, DL6WU, Lawson/Cebik) and points to section 6 of docs/case_study_yagi.md for the full comparison. That broader claim cannot be checked from the README alone. The wall-clock figure, 12.7 s for 5858 solver calls on a laptop, implies roughly 2 ms per NEC2 evaluation, which is plausible for a thin-wire MoM model of five elements on a coarse frequency grid but is not something this review can confirm. Treat the per-call cost as the number to measure first, because it sets the ceiling on how many parameters you can search.
Where the repository overpromises relative to what it demonstrates
The core modules table lists solvers for openEMS, NEC2, MEEP, HFSS, CST and FEKO, AI backends spanning diffusion, VAE, GAN, FNO, PINN, differentiable FDTD and Bayesian optimization, and physics models for metasurfaces, RIS, OAM, graphene and space-time modulation. The README demonstrates exactly two of those solvers (NEC2 in both demos, openEMS named in the description) and none of the generative backends beyond a truncated command for a VAE demo and a differentiable FDTD demo. HFSS, CST and FEKO are commercial tools, so a solver adapter for them is either a thin wrapper around a licensed install or a stub, and the README does not say which. There are no releases retrieved for this repository, and no test suite or CI configuration is mentioned anywhere in the README. For an engineering tool whose entire value proposition is that the numbers are real, the absence of a visible regression suite is the gap I would press on hardest. The port protocols (SolverAdapter, AIBackend, CADBackend) suggest the intent is a pluggable architecture, but intent and a working adapter for a given solver are different claims.
When Antenna Forge is the wrong tool
Three cases. First, if your antenna is not well described by thin-wire Method of Moments or a planar FDTD grid, the loop has nothing to optimize against. The demonstrated geometry is wire elements at 300 MHz; a packaged patch array with a finite substrate and a feed network is an openEMS problem, and the README gives no worked openEMS example, so you would be building that path yourself. Second, if you need a guaranteed answer rather than a search, this is the wrong shape of tool: differential evolution with 9 parameters took 5858 evaluations, and adding parameters multiplies that. A parametric sweep with a closed-form starting point is cheaper when you already know the topology. Third, if you need a supported service with an upgrade path, the Docker stack plus PostgreSQL plus Qdrant plus Celery is a lot of moving parts for a project with no releases and no stated test coverage. The script path is far more defensible than the service path right now. The SolverUnavailable behaviour is also a policy choice you should agree with: a pipeline that hard-fails when necpp is absent is correct for a research claim and inconvenient for a batch job that would rather degrade.
Alternatives and the actual difference in approach
The most direct comparison is the standard practice of surrogate-assisted optimization: train a neural operator (the README itself mentions FNO) or a Gaussian-process model on a few hundred full-wave solves, then run thousands of cheap evaluations against the model and verify the top candidates with a real solve. That approach scales to high-dimensional geometry and to expensive solvers like HFSS, which is exactly where Antenna Forge's per-call cost becomes painful. The trade is fidelity during search: a surrogate can be confidently wrong in regions it never saw, and Antenna Forge's answer is to never let that happen. A second alternative is a mature commercial optimizer bundled with a solver suite, where the solver adapter is the vendor's problem rather than yours, at the cost of a licence and far less visibility into the objective. A third is writing the loop yourself: scipy.optimize plus a necpp or openEMS call is perhaps a hundred lines, and the README's own dipole demo is close to that. What Antenna Forge adds over the hundred lines is the geometry kernel, the results schema, and the 5858-record JSON history that the README explicitly offers as a free training set for a surrogate. That last item is the most interesting thing in the repository for an ML reader, because it inverts the usual workflow: use the expensive solver to generate ground truth, then train the cheap model on it.
Maintenance, licence and what to check before depending on it
The licence is MIT, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are retained. That is the permissive end of the spectrum, and it means the solver adapters and the geometry kernel can be lifted into a proprietary product. It says nothing about the licences of the dependencies, and those matter here: necpp wraps the NEC2 code, openEMS has its own licence, and the README lists HFSS, CST and FEKO, all commercial. Anyone shipping a product built on this should check the licence of each solver backend independently. On maintenance, the repository is not archived and the last push is dated 2026-06-01, but no releases were retrieved, so there is no versioned artifact to pin. The upgrade cost is therefore the cost of tracking main: the API example uses a design_id UUID and a solver string, and any change to that contract breaks clients silently because there is no release note trail. The practical first step is to run python3 scripts/demo_wow.py and confirm the dipole figure matches the README's stated numbers (R near 73 ohm, G near 2.13 dBi) on your own machine and your own Python version. If that figure reproduces, the physics path is real and the rest is engineering. If it does not, the discrepancy is the first thing to resolve.
Editorial conclusion
Adopt Antenna Forge if you need a wire-antenna or planar-EM optimizer whose objective function is a real Method-of-Moments or FDTD solve, and you are willing to read the scripts rather than the API surface: start with scripts/demo_inverse_design.py, then scripts/case_yagi.py, and check that necpp imports before anything else. Do not adopt it if you need a supported solver abstraction layer across HFSS, CST and FEKO, or a maintained web service, because the README shows no tests, no CI and no releases. Verify two things first: that the SolverUnavailable path is what you want in your own pipeline, and that the 5858-call Yagi run reproduces on your machine before you build tooling on top of results/yagi_optimized.json.
Community notes