General reference

This page documents various conventions and protocols adopted by meep_adjoint.

1. Tweaks to meep conventions

The meep_adjoint package introduces minor revisions of some of the standard ways of doing business in meep and the meep python module .

1a. Pythonic representation of spatial vectors

Many functions in meep and meep_adjoint have input parameters or return values describing coordinates of spatial points or components of spatial vectors. meep python module typically requires that these be packaged in the form of a specialized data structure, but this seems cumbersome and un-pythonic when one could simply use a list or np.array of integers or floating-point numbers. meep_adjoint accepts lists or np.arrays for all spatial-vector function parameters, using internal wrapper routines to shield users from the need to convert back and forth to mp.Vector3.

1b. Unified handling and labeling of spatial regions: The Subregion class

An idiosyncrasy of the meep C++ back-end, library, which was retained in the definition of the meep python interface , is the use of multiple (at least seven 1) distinct data structures to describe subregions of the computational geometry depending on the task at hand. meep_adjoint replaces all of these with the single new class Subregion. In addition to eliminating redundancies in the code and API, the Subregion class has a string-valued name field that facilitates the identification of subregions and underlies the canonical naming convention for objective quantities, as described next. (For more about Subregions, consult the Subregions.)

2. Objective quantities and naming conventions

meep_adjoint uses the term objective quantity to refer to frequency-domain Poynting fluxes, electromagnetic field energies, and other frequency-domain quantities computed from frequency-domain fields in subregions of the spatial FDTD grid. (A subregion over which objective quantities are defined is called an objective region.) Each objective quantity has a unique label consisting of two character strings—a one- or two-character code identifying the physical quantity (flux, energy, etc.), and a string label identifying the subregion—separated by an underscore. The subregion label is just the string passed as the name parameter to the Subregion constructor, while the codes identifying various physical quantities are tabulated here:

Codes for physical quantities

Code

Quantity

S

Poynting flux

UE

electric-field energy

UH or UM

magnetic-field energy

UT or UEH or UEM

total (electric + magnetic) field energy

P3 or F3

expansion coefficient for forward-traveling (``plus’’) eigenmode #3

M7 or B7

expansion coefficient for backward-traveling (``minus’’) eigenmode #7

For example, if our geometry contains an objective region named North, then the following objective quantities (among others) are implicitly defined and may appear in the mathematical expression defining the objective function:

  • S_North, the Poynting flux through the region

  • UM_North, the magnetic-field energy integrated over the region

  • P1_North, the eigenmode expansion coefficient for the forward-traveling wave of eigenmode 1

  • M2_North, the eigenmode expansion coefficient for the backward-traveling wave of eigenmode 2

Note: Needless to say, not all physical quantities are sensible, or even defined, for all subregions. For example, Poynting fluxes and eigenmode coefficients are only defined for subregions of codimension one with a specified normal direction, while field-energy quantities are typically associated with codimension-zero subregions. For example, if your simulation contains a codim-1 subregion named FluxMon and a codim-0 subregion named Cavity, then

  • the objective quantities S_FluxMon and UT_Cavity make perfect sense

  • the quantity UT_FluxMon is defined and will be computed by meep_adjoint without complaint, but is probably not physically meaningful

  • the quantity S_Cavity is undefined and will trigger an error if you ask meep_adjoint to compute it.

3. Expansion bases: Built-in and user-defined function spaces for material designs

The ultimate goal of a design optimization is a function \(\epsilon^\text{des}(\mathbf{x}), \mathbf{x} \in \mathcal{V}^\text{des},\) specifying the permittivity throughout the design region. This is a continuous entity with infinitely many degrees of freedom. On the other hand, the output of a numerical optimizer is a discrete entity—specifically, a finite set of numbers \(\{\beta_1, \dots, \beta_D\}\), which are conveniently written as the components of a \(D\)-dimensional vector \(\boldsymbol\beta\in \mathbb{R}^D\). The bridge between continuous and discrete is furnished by choosing a basis of expansion functions, \(\{b_1(\mathbf{x}), \cdots, b_D(\mathbf{x})\}\), where each \(b_d(\mathbf{x}), 1\le d \le D\) is a real-valued scalar function defined for \(\mathbf{x}\in \mathcal{V}^\text{des}\). We approximate the design function we seek as a finite expansion in this basis, i.e.

\[\epsilon^\text{des}(\mathbf x)\approx \sum_{d=1}^D \beta_d b_d(\mathbf{x}),\]

and then ask the optimizer to determine the best values for the ${beta}$ coefficients.

To state the obvious, the efficiency and practical efficacy of such a discretization scheme is highly dependent on the choice of basis functions, and the optimal choice of basis for a given problem is problem-dependent. For this reason, meep_adjoint doesn’t presume to enforce any one basis as the canonical choice for all problems; instead, we envision that some performance-conscious users will want to define their own specialized high-performance basis sets for specific problems, and one goal of the meep_adjoint API is to allow this full freedom of basis-set design to any user who wants to exploit it.

On the other hand, we simultaneously expect many other users to be uninterested in delving into such nitty-gritty details—and willing to sacrifice some efficiency in exchange for not having to take the time to construct a basis set and write a python class describing it to meep_adjoint—whereupon a second API desideratum is to shield time-constrained users who don’t want to define their own basis set from the hassle of needing to do so.

The strategy we adopt to reconcile these imperatives is as follows:

  • As a template for all user-supplied basis sets, we define an abstract base class Basis, which simply abstracts the essential data and methods common to all expansion bases independent of geometric or implementation details. This allows performance-seeking users the freedom to implement their own derived subclasses for custom-designed basis sets of arbitrary specialization and complexity.

  • Meanwhile, for users hoping to avoid worrying about basis sets, meep_adjoint ships with a built-in pre-implemented subclass of Basis called FiniteElementBasis, which uses the open-source FENICS finite-element package to offer a wide range of general-purpose basis functions. Using a FiniteElementBasis basis in a meep_adjoint problem is as easy as specifying the bounding box of the design region and (optionally!) configuring a couple of configuration options to choose the type of finite-element function and the discretization lengthscale.

3a. The Basis abstract base class

3b. FiniteElementBasis: A built-in basis for general-purpose use

3c. Subclassing Basis to define your own customized basis

4. Ways to specify functions

A number of functions and API methods in meep_adjoint accept an input parameter describing a spatially-varying scalar function \(g(\mathbf{x})\) in some domain of the computational cell. Examples include the design parameter to OptimizationProblem.update_design and the g parameter to Basis.project. These parameters accept inputs of any the following types.

  • A character string defining a mathematical expression, i.e. g=`sin(x)*cos*(y)`.

  • A numerical type (integer or floating point), in which case the function is taken to be constant throughout the domain.

  • A python function (i.e callable)

  • A float-valued array of the appropriate dimensionality and aspect ratio, whose entries are interpreted as function samples at evenly-spaced grid sites throughout the domain; function values at interstitial points are determined by interpolation. Note that this sampling grid need not coincide with meep’s FDTD grid; it may be coarser or finer, as would be the case for an array of field values output by a previous :meep: calculation of the same geometry at higher or lower resolution.


Footnotes

1

Namely: Volume, FluxRegion, FieldsRegion, Near2FarRegion, ForceRegion, EnergyRegion, and ModeRegion, and perhaps I am missing some.

def add_dft_fields(self, components, freq_min, freq_max, nfreq, where=None, center=None, size=None): def _add_flux(self, fcen, df, nfreq, fluxes):