Introducing emslab: Cloud-native openEMS antenna simulations
openEMS is a fantastic open-source FDTD solver that provides full-wave antenna simulation capabilities, scripted geometry (great for AI agents), and no license fees. But the workflow around it was stuck in one place: your machine. Everything — the solver, the Qt geometry viewer, the whole toolchain build — lived and ran locally, and every project inherited that limitation.
emslab is our answer: a cloud-native platform that keeps the openEMS
you know (same APIs, same tutorials, same post-processing) and moves
everything else to where it belongs. One compose up gives you a
JupyterLab front end, a solver job service, and a web geometry viewer —
on your laptop, on a shared workstation, or split between the two.
Source and images live at gitlab.com/strike-research/emslab.
What was wrong with the old workflow
If you’ve run openEMS the traditional way, this will sound familiar:
Everything ran locally. The solver executed inside your Python or Octave process, on whatever machine you were able to get openEMS and its extensions to build on. Maybe your laptop struggled under the load while a workstation in the corner sat idle because getting your model onto it — toolchain build and all — was an afternoon of yak-shaving. With emslab, the solver is a containerized service reached over HTTP. Notebooks run wherever you are; simulations run wherever the cores are. A laptop-plus-remote-solver setup is one SSH tunnel away.
Geometry checking needed a local Qt viewer. AppCSXCAD is a desktop Qt/VTK application: it needs a display, doesn’t work over a plain SSH session, and there’s no way to send a colleague what you’re looking at. emslab replaces it with a web viewer (three.js) served by the platform. It draws every CSX primitive with the property colors, overlays the FDTD mesh lines, and slices the model at mesh planes so you can inspect how geometry actually lands on the grid. The entire view state — camera, hidden properties, slice plane — serializes into the URL, so “look at this” is literally copying a link.
One script, one run, start to finish. The classic workflow is a standalone script per model: edit, run everything, wait, squint at the plots, repeat. Iteration state lived in your head. emslab is built around JupyterLab (Python and Octave kernels): geometry, simulation, and post-processing live in one notebook, you re-run only the cell you changed, and plots land inline next to the code that made them. Notebooks auto-pair to plain-text scripts (jupytext), so models still diff and review cleanly in git.
Every run re-solved everything. Re-running a script re-simulated the model — even if nothing changed — because there was nothing between you and the solver to notice. In emslab every simulation goes through a job queue and is content-addressed: the model XML and its assets are hashed, and an identical submission returns the finished results instantly instead of burning an hour recomputing them. Restart your kernel, re-run the whole notebook top to bottom, and only the work that actually changed gets solved. A separate quick queue keeps short validation runs from waiting behind a long solve, and parameter sweeps get the same caching per point.
The important part: none of this changed the openEMS API. Published
openEMS tutorials run unmodified. A thin shim layer redirects Run()
(Python) and RunOpenEMS (Octave) through the job service with the same
synchronous feel — submit, live progress with timestep and energy-decay
numbers, results fetched back where the script expects them.
Trying it: five minutes to a running platform
You need podman or docker with the compose plugin — nothing else touches the host.
git clone https://gitlab.com/strike-research/emslab.git
cd emslab
podman compose -f docker/compose.yaml up -d # or: docker compose ...
Open http://127.0.0.1:8888 (token: emslab) and you’re in JupyterLab
with Python and Octave kernels. Prebuilt images come from the registry;
there is no local openEMS build.
Paste any openEMS example into a notebook — here’s the shape of it in Python:
from openEMS import openEMS # the remote-execution shim is active
from CSXCAD import ContinuousStructure
fdtd = openEMS(NrTS=30000, EndCriteria=1e-5)
csx = ContinuousStructure()
# ... build your antenna exactly as in any openEMS tutorial ...
fdtd.Run(sim_path) # runs on the solver service,
# live progress bar in the notebook
Before committing to a solve, check the geometry in the browser:
import emslab
emslab.preview(fdtd) # renders inline + prints a shareable viewer URL
Slice through the substrate, toggle the ground plane, confirm the feed
lands on a mesh line — then run. Re-execute the notebook later and the
Run() comes back in under a second with the cached results; change one
dimension and only that model is re-solved. Sweeps build on the same
mechanism:
result = emslab.Client().sweep(build_patch, [{"width_mm": w} for w in (28, 30, 32)])
print(result.table()) # per-case state, cached or solved, job ids
To put the solver on a remote server while keeping your notebooks local, start everything except JupyterLab on the server, keep its ports on loopback, and tunnel from your laptop:
ssh -N -L 8000:127.0.0.1:8000 -L 8100:127.0.0.1:8100 you@solver-host &
podman run -d --network host \
--userns=keep-id:uid=1000,gid=1000 \
-v ~/ems-work:/home/emslab/work:Z \
registry.gitlab.com/strike-research/emslab/jupyterlab:latest
Your notebooks stay as plain files in a local folder — your git repos, your backups — while every simulation runs on the remote box. No URL configuration needed; the defaults land on the tunnel. The repository README covers this split setup, trusted-network variants, and a hosted multi-user overlay (TLS, subdomains) in detail.
Need a hand — or an antenna?
emslab is open source and designed to be self-serve. But if you’d rather spend your time on the electromagnetics than the plumbing, we can help:
- Platform setup and integration — deploying emslab on your hardware or cloud, wiring it into your team’s workflow, extending it where your process needs more.
- Antenna design and simulation — patch arrays, wideband elements, matching networks; from first FDTD model to a design you can hand to a fab, with the simulation workflow left behind for your team to keep iterating.
If either sounds useful, start a conversation — a short call is usually enough to tell whether we can help.