metasurface-py: a Python toolkit for programmable electromagnetic surfaces
A programmable metasurface gets represented at three very different levels. An electromagnetics engineer models the metal geometry, substrate, and diodes in a full-wave solver. An antenna engineer replaces that detail with a complex reflection coefficient per element and computes aperture fields. A communications researcher abstracts the whole surface into a diagonal matrix of ideal phase shifts. Each level answers questions the others cannot, and each is blind to something: the solver is too expensive for system sweeps, the ideal matrix assumes phase states nobody can build, and one-off array-factor scripts fill the gap without tests, data models, or reusable constraints.
metasurface-py is my attempt at a proper middle layer. It is an open-source Python toolkit (BSD-3-Clause, on PyPI) for designing, analyzing, and optimizing programmable surfaces: reconfigurable intelligent surfaces (RIS), reflectarrays, and surface-wave-fed modulated metasurface antennas. Reduced-order electromagnetic models sit underneath aperture synthesis, link budgets, sensing metrics, and constrained optimization, so a study can run thousands of configurations in the time one full-wave run takes.
pip install metasurface-py
To be precise about what it is: a reduced-order research framework, currently in alpha (v0.3.0), and deliberately no replacement for a full-wave solver. Element physics enters through analytic models, lookup tables, or imported data; HFSS, CST, Meep, and openEMS remain the right tools for characterizing a unit cell, and measurement remains the arbiter. The package’s job is everything between the characterized element and the system answer.
A concrete example
The quickstart steers a 32-by-32 reflective surface with two-bit phase control at 28 GHz:
import numpy as np
from metasurface_py.geometry import RectangularLattice
from metasurface_py.elements import PhaseOnlyCell, DiscretePhaseSpace
from metasurface_py.surfaces import Metasurface
from metasurface_py.em import steering_phase, far_field_pattern
from metasurface_py.core.types import AngleGrid
from metasurface_py.plotting import plot_pattern_2d
lattice = RectangularLattice(nx=32, ny=32, dx=5.4e-3, dy=5.4e-3)
cell = PhaseOnlyCell(state_space=DiscretePhaseSpace(num_bits=2))
surface = Metasurface(lattice=lattice, cell=cell, mode="reflect")
freq = 28e9
phase = steering_phase(lattice, theta_steer=np.radians(30), phi_steer=0.0, freq=freq)
state = surface.set_state(phase).quantize(cell.state_space.codebook)
angles = AngleGrid.from_degrees(theta=np.arange(-90, 91, dtype=float),
phi=np.array([0.0, 90.0]))
pattern = far_field_pattern(surface, state, freq=freq, angles=angles)
plot_pattern_2d(pattern, cut_phi=0.0)

The quickstart’s output, generated by running the code above. The main beam lands at the commanded 30 degrees, and the sidelobe structure is the honest signature of two-bit quantization rather than an idealized continuous-phase pattern.
The ideal steering phase is computed first, then quantized to the element’s actual codebook, and the pattern is evaluated for the quantized state. That ordering is the point of the whole package: what gets scored is the surface you could build.
Hardware effects are first-class
During my PhD I
fabricated and measured a two-bit programmable reflectarray,
and the board disagreed with its idealization in every way hardware does: quantized states,
column-level control granularity, a shifted resonance. metasurface-py makes those effects
explicit inputs instead of after-the-fact excuses. DiscretePhaseSpace(num_bits=...) handles
any quantization depth; apply_group_constraint ties elements to shared control lines (the
column-driven board is one line of code); apply_mask kills chosen elements to study failure
tolerance; add_manufacturing_noise perturbs states; and AmplitudePhaseCell couples
amplitude loss to phase state, which is how real tunable elements behave. Every one of these
composes with the same pattern, link, and optimization code paths.
From fields to systems
The same aperture representation feeds several analysis layers. On the fields side:
far-field patterns, directivity, sidelobe level, beamwidth, near-field focusing
(focusing_phase), and dual-beam synthesis (multi_beam_phase). On the communications side:
RIS-assisted link budgets (RISLink, narrowband SISO by default, with MIMORISLink and
WidebandRISLink for MIMO and OFDM studies) and free-space path-loss models. On the sensing
side: monostatic and bistatic radar cross section, detection SNR, Fisher information and
position CRLB for localization, and a joint communications-sensing objective for ISAC
tradeoffs. A modulated-metasurface-antenna module adds surface-wave-fed apertures with
leaky-wave dispersion models.
Optimization respects the hardware path. The typical workflow is relax, optimize, quantize:
solve a continuous version with optimize_continuous (L-BFGS-B or differential evolution),
map to realizable states with relax_then_quantize, refine discretely, and evaluate the
implemented aperture. pareto_sweep handles multi-objective questions like gain against
sidelobe level.
Validation
The package carries 229 unit and property tests, and its examples reproduce three published results: the N-squared SNR scaling of Wu and Zhang (IEEE Transactions on Wireless Communications, 2019), the RIS path-loss scaling of Basar et al. (IEEE Access, 2019), and the modulated metasurface antenna designs of Faenzi et al. (Scientific Reports, 2019), with reference metrics checked into the repository.

The package’s reduced-order reproduction of Faenzi et al. (2019): patterns, dispersion, and bandwidth for modulated metasurface antennas, plotted against the paper’s measured gain and bandwidth.
The limits are stated as plainly as the features. Mutual coupling uses a canonical dipole approximation; edge diffraction, feed blockage, and bias-network scattering are outside the models; and a reduced-order answer that matters should be checked against full-wave simulation or measurement before anyone commits hardware to it.
Try it
pip install metasurface-py gets the package;
the docs site has API references and tutorials;
four Colab-ready notebooks cover getting started, optimization, RIS links, and sensing; and a
hosted Streamlit app exposes a beam-pattern designer,
an RIS link calculator, and an optimization tab without any install. The
repository has the nine example scripts,
including the three reproductions.
metasurface-py is an independent project I build on my own time. The views are my own and do not represent any current or former employer.
Frequently asked questions
What is metasurface-py?
An open-source Python toolkit (BSD-3-Clause) for designing, analyzing, and optimizing programmable electromagnetic surfaces: reconfigurable intelligent surfaces, reflectarrays, and surface-wave-fed metasurface antennas. It combines reduced-order EM models with hardware-aware aperture synthesis, RIS link budgets, sensing metrics, and constrained optimization.
Is metasurface-py a full-wave electromagnetic solver?
No. It is deliberately the layer between full-wave unit-cell simulation and abstract system models: reduced-order models that make aperture and system studies tractable. Element-level physics still comes from a full-wave tool or measurement; results that matter should be verified with full-wave simulation or hardware.
What hardware effects can it model?
Quantized phase states (one, two, three bits or any codebook), grouped control lines, dead elements, manufacturing noise, and amplitude-phase coupling, all applied to the aperture before patterns, link budgets, or optimizations are evaluated, so the implemented surface is what gets scored.
How is the package validated?
With 229 unit and property tests plus reproductions of three published results: the RIS SNR scaling law of Wu and Zhang (2019), the RIS path-loss behavior of Basar et al. (2019), and the modulated metasurface antenna results of Faenzi et al. (2019), with reference metrics checked in.