diff --git a/doc/docs/ModeExpansion.md b/doc/docs/ModeExpansion.md
new file mode 100644
index 000000000..26a568494
--- /dev/null
+++ b/doc/docs/ModeExpansion.md
@@ -0,0 +1,794 @@
+# Eigenmode decomposition of arbitrary field configurations
+
+*Eigenmode decomposition* exploits Meep's interconnectivity
+with the [MPB][MPB] mode solver to express an arbitrary
+time-harmonic field configuration as a superposition of
+the normal harmonic modes of your structure.
+
+[TOC]
+
+## Theoretical background[^1]
+
+Consider a waveguide structure of infinite extent in the $x$
+direction with constant cross section in the transverse
+$[\vec\rho=(y,z)]$ directions. For any given
+angular frequency $\omega$ we may solve the time-harmonic
+Maxwell equations to obtain the *normal modes* of the
+structure---an infinite set of vector-valued
+functions of the transverse coordinates
+$\{\mathbf{E}^\pm_n(\vec{\rho}), \mathbf{H}^\pm_n(\vec{\rho})\}$,
+with associated propagation constants $\{\beta_n\}$,
+that furnish a complete expansion basis for
+time-harmonic electromagnetic fields at frequency $\omega$.
+That is, given any arbitrary frequency-$\omega$ field
+configuration of the form
+$$ \mathbf{E}(\mathbf{r},t) = \mathbf{E}(\mathbf{r}) e^{-i\omega t} $$
+$$ \mathbf{H}(\mathbf{r},t) = \mathbf{H}(\mathbf{r}) e^{-i\omega t} $$
+we have the *exact* expansions
+
+$$
+ \mathbf{E}(\mathbf{r}) =
+ \mathbf{E}(x,\vec{\rho}) =
+ \sum_{n} \left\{ \alpha^+_n \mathbf E^+_n(\vec \rho)e^{+i\beta_n x}
+ + \alpha^-_n \mathbf E^-_n(\vec \rho)e^{-i\beta_n x}
+ \right\}
+ \tag{1}
+$$
+$$
+ \mathbf{H}(\mathbf{r}) =
+ \mathbf{H}(x,\vec{\rho}) =
+ \sum_{n} \left\{ \alpha^+_n \mathbf H^+_n(\vec \rho)e^{+i\beta_n x}
+ + \alpha^-_n \mathbf H^-_n(\vec \rho)e^{-i\beta_n x}
+ \right\}
+ \tag{2}
+$$
+where (as discussed further [below](ModeExpansion.md#UnderTheHood))
+the expansion coefficients $\{\alpha^{\pm}_n\}$
+may be extracted from knowledge of the time-harmonic
+fields $\mathbf{E},\mathbf{H}$ on any cross-sectional
+surface $S$ transverse to the waveguide.
+
+The idea of mode expansion in Meep is to compute
+the $\{\alpha_n^\pm\}$ coefficients above for any
+*arbitrary* time-harmonic field distribution
+resulting from a Meep calculation. In calculations
+of this sort,
+
++ the $\{\mathbf{E},\mathbf{H}\}$ fields on the RHS
+ of equations (1a,b) above will be frequency-domain
+ fields stored in a `dft_flux` object in a Meep
+ run, where you will have arranged this `dft_flux` object
+ to live on a cross-sectional surface $S$ transverse
+ to the waveguide;
+
+- the $\{\mathbf{E}^\pm_n,\mathbf{H}^\pm_n\}$ eigenmodes
+ and $\{\beta_n\}$ propagation constants are computed
+ automatically under the hood by [MPB][MPB] as normal modes
+ of an infinitely extended waveguide with the same
+ cross-sectional material distribution that your structure
+ has on the transverse slice $S$, and
+
+- the $\alpha_n^\pm$ coefficients for as many bands
+ as you like are computed by calling `get_eigenmode_coefficients(),`
+ as discussed below.
+
+## Main function prototype
+
+The highest-level interface to the mode-expansion implementation
+in Meep is the libmeep function `meep::fields::get_eigenmode_coefficients,`
+callable from C++ or python. This routine makes use
+of several [lower-level libmeep functions](ModeExpansion.md#OtherRoutines)
+that you may also find useful;
+these are documented
+[below](ModeExpansion.md#OtherRoutines)
+and their use is illustrated in the tutorial that follows.
+
+The C++ prototype for the top-level routine is
+
+```c++
+std::vector
+ fields::get_eigenmode_coefficients(dft_flux *flux,
+ direction d,
+ const volume &where,
+ std::vector bands,
+ kpoint_func k_func=0,
+ void *user_data=0);
+```
+where
+
++ `flux` is a `dft_flux` object pre-populated with frequency-domain field data resulting from a time-domain Meep calculation you have run to tabulate fields on a cross-sectional slice perpendicular to your waveguide
+
++ `d` is the direction of power flow in the waveguide
+
++ `where` is a `volume` describing the cross-sectional surface $S$
+
++ `bands` is an array of integers that you populate with the indices of the modes for which you want expansion coefficients
+
++ `user_func` is an *optional* function you supply to provide initial estimates of the wavevector of a mode with given
+frequency and band index; its prototype is
+
+```c++
+ vec (*kpoint_func)(void user_data, double freq, int band);
+```
+
+which returns a `vec` giving your best guess for the
+wavevector of the `band`th mode at frequency `freq`.
+
+The return value of `get_mode_coefficients` is an array
+of type `cdouble` (short for `std::complex`),
+of length `2 * num_freqs * num_bands`, where `num_freqs`
+is the number of frequencies stored in your `flux` object
+(equal to `flux->Nfreq`) and `num_bands` is the length
+of your `bands` input array.
+The expansion coefficients $\{\alpha^+,\alpha^-\}$
+for the mode with frequency `nf` and band index `nb`
+are stored sequentially starting at slot
+`2*nb*num_freqs + nf` of this array:
+
+````c++
+ std::vector coeffs=f.get_eigenmode_coefficient(...)
+ fields::get_eigenmode_coefficients(dft_flux *flux,
+ direction d,
+ const volume &where,
+ std::vector bands,
+ kpoint_func k_func=0,
+ void *user_data=0);
+
+ int num_bands = bands.size();
+ int num_freqs = Flux->Nfreq;
+ for(int nb=0; nb
+$$ w(x) =
+ \begin{cases}
+ w_A, \qquad &x < -\frac{L}{2} \\[5pt]
+ T_p(x), \qquad &x \in \left[ -\frac{L}{2}, +\frac{L}{2}\right] \\[5pt]
+ w_B, \qquad &x > +\frac{L}{2} \\
+ \end{cases}
+ \tag{3}
+$$
+where the taper function $T_p(x)$ is a $C^{p}$ function,
+i.e. $p$ is the index of its first discontinuous derivative.
+For the cases $p=\{0,1,2\}$, the taper functions are
+$$ T_p(x)=\begin{cases}
+ w_0 + \Delta \left(\frac{x}{L}\right), \qquad &p=0 \\[5pt]
+ w_0 + \Delta \Big[ \frac{3}{2}\left(\frac{x}{L}\right)
+ -2\left(\frac{x}{L}\right)^3
+ \Big],\qquad&p=1,\\[5pt]
+ w_0 + \Delta \Big[ \frac{15}{8}\left(\frac{x}{L}\right)
+ -5\left(\frac{x}{L}\right)^3
+ +6\left(\frac{x}{L}\right)^5
+ \Big],\qquad&p=2
+ \end{cases}
+$$
+where
+$$ w_0\equiv \frac{w_A+w_B}{2}, \qquad \Delta = w_B - w_A$$
+are the average and difference of the smaller and larger waveguide
+thicknesses.
+
+Here are pictures of the $p=0,1,2$ taper geometries for the case of a
+taper of length $L=4$ between waveguides of thickness $w_A=1$
+and $w_B=3$. (See below for the python code that produces these
+plots.)
+
+ p =0 Taper
+
+![Taper2D_p0.png](ModeExpansionFiles/Taper2D_p0.png)
+
+ p =1 Taper
+
+![Taper2D_p1.png](ModeExpansionFiles/Taper2D_p1.png)
+
+ p =2 Taper
+
+![Taper2D_p2.png](ModeExpansionFiles/Taper2D_p2.png)
+
+In these figures, the dashed lines at $x=x_{A,B}$
+indicate the locations of cross-sectional planes
+that we will use in our calculation:
+the plane at $x=x_A$ is where we will place an
+eigenmode source in our Meep calculation to describe
+incoming power entering from the smaller waveguide,
+while the plane at $x=x_B$ is where we will
+tabulate the Fourier-domain fields in our Meep
+calculation and determine their overlaps with
+the eigenmodes of the larger waveguide to
+compute mode-expansion coefficients.
+
+### User-defined material function
+
+Because the material geometries we will be studying here
+are too complicated to be described as assemblies of
+the [usual geometric primitives like blocks and cylinders](Python_User_Interface.md#GeometricObject),
+we will instead write our own [user-defined material function](Python_User_Interface.md#material_function), which inputs the coordinates of a point in space
+and fills in a [medium structure](Python_User_Interface.md#medium)
+for the material properties at that point.
+Actually, since the material geometry in this case involves a
+spatially-varying but otherwise simple (isotropic, linear, lossless)
+dielectric function, we can get away with the slightly simpler
+[user-defined epsilon function](Python_User_Interface.md#epsilon_function),
+for which we need only furnish a function of position that
+returns a scalar relative permittivity. This is implemented
+by the `my_eps_func()` routine in `wvg-taper.py;` note that
+it invokes a subroutine `w_func` that evaluates [equation (3) above](ModeExpansion.md#Equation1)
+to compute the $x$-dependent waveguide width $w(x)$.
+
+```python
+##################################################
+# x-dependent width of waveguide
+##################################################
+def w_func(x, L, p, wA, wB):
+ if L==0:
+ return wA if x<0 else wB
+ x0=x/L
+ if (x0 < -0.5):
+ return wA;
+ elif (x0 > +0.5):
+ return wB;
+ elif p==2:
+ return 0.5*(wA+wB) + (wB-wA)*x0*(15.0 + x0*x0*(-40.0 + x0*x0*48.0))/8.0;
+ elif p==1:
+ return 0.5*(wA+wB) + (wB-wA)*x0*(1.5 - 2.0*x0*x0);
+ else: # default t p==0, simple linear taper
+ return 0.5*(wA+wB) + (wB-wA)*x0;
+
+##################################################
+# user-defined function for position-dependent material properties
+##################################################
+def my_eps_func(loc, L, p, wA, wB, eps_out, eps_in):
+
+ if ( abs(loc.y) > 0.5*w_func(loc.x, L, p, wA, wB) ):
+ return eps_out; # outside waveguide
+ else:
+ return eps_in; # inside waveguide
+```
+
+We can pass `my_eps_func` as the value of the
+`epsilon_func` keyword argument to the
+[`Simulation` class constructor](Python_User_Interface.md#SimulationClass);
+however, because this expects a function of just a single
+argument (the spatial point), we use a `lambda` construction
+to package the remaining arguments, i.e. something like
+
+```python
+eps_func = lambda loc: my_eps_func(loc, L, p, wA, wB,
+ eps_ambient, eps_waveguide)
+
+sim=mp.Simulation( cell_size=mp.Vector3(2*LX, 2*LY),
+ resolution=resolution,
+ boundary_layers=[mp.PML(DPML)],
+ epsilon_func = eps_func
+ )
+```
+
+The [`wvg-taper.py`](ModeExpansionFiles/wvg-taper.py) code defines
+a class called `wvg-taper` that accepts keyword arguments for
+various geometric parameters and instantiates a `Simulation` object
+as in the code snippet above. For example, here's how we
+made the pictures of the structures shown above:
+a couple of examples involving waveguides and tapers of
+various geometries:
+
+```python
+>>> execfile("wvg-taper.py");
+>>> wt=wvg_taper(wA=1, wB=3, L=4, p=0); wt.plot_eps();
+>>> wt=wvg_taper(wA=1, wB=3, L=4, p=1); wt.plot_eps();
+>>> wt=wvg_taper(wA=1, wB=3, L=4, p=2); wt.plot_eps();
+```
+The `plot_eps()` class method that produces these plots
+just calls [`Simulation.get_array`](Python_User_Interface.md)
+to get a `numpy` array of ε values at the grid
+points, then plots it using the `imshow` routine in
+matplotlib:
+
+```python
+ def plot_eps(self):
+
+ eps=self.sim.get_array(center = mp.Vector3(0,0),
+ size = self.sim.cell_size,
+ component = mp.Dielectric)
+ plt.figure()
+ plt.imshow(eps.transpose())
+ plt.show(block=False)
+```
+
+### Visualizing eigenmode profiles
+
+Next, before doing any timestepping let's calculate
+and plot the field profiles of some waveguide modes,
+for both the smaller and larger waveguides.
+This calculation is done by the `plot_modes`
+function in the `wvg_taper` class; you can look
+at the [full Python code](ModeExpansionFiles/wvg-taper.py)
+to see how it's done in full detail, but
+here is a synopsys:
+
++ For the lowest-frequency ($n=1$) eigenmode of the
+ smaller waveguide, and for the first several
+ eigenmodes of the larger waveguide, we call the `meep::fields::get_eigenmode`
+ routine in libmeep.
+ This routine inputs a frequency (`fcen`), an integer (`nb`), and a
+ `meep::volume` specifying a cross-sectional slice through our
+ geometry, then invokes [MPB][MPB] to determine the `nb`th
+ eigenmode at frequency `fcen` for an *infinitely extended*
+ waveguide with constant cross section matching that of
+ our slice. For example, to compute the $n=1$ eigenmode
+ for an infinite waveguide whose cross section matches
+ that of our structure at $x=x_A$, we say
+
+````python
+nb = 1; # want first eigenmode
+vA = mp.volume( mp.vec(xA, -YP), mp.vec(xA,+YP) ) # cross section at x_A
+modeA = f.get_eigenmode(fcen, mp.X, vA, vA, nb, k0, True, 0, 0.0, 1.0e-4)
+````
+
+The return value of `get_eigenmode` is a data structure
+containing information on the computed eigenmode; for
+example, to get the group velocity or propagation vector
+of the mode you could say
+
+````python
+vgrp = get_group_velocity(modeA);
+k_vector = get_k(modeA)
+````
+
+Alternatively, you can call the `meep::fields::output_mode_fields`
+routine to export the $\mathbf{E}$ and $\mathbf{H}$
+components of the eigenmode (at grid points lying in the cross-sectional
+plane) to an HDF5 file, i.e.
+
+````python
+f.output_mode_fields(modeA, fluxA, vA, "modeA");
+````
+
+where `fluxA` is a [`meep::dft_flux`][DFTFlux] structure
+created for the cross section described by `vA`. This will
+create a file called `modeA.h5` containing values of
+field components at grid points in `vA`.
+
++ Having computed eigenmodes with `get_eigenmode` and written their
+ field profiles to HDF5 files with `output_mode_fields`, we
+ can read the data back in for postprocessing, such as (for example)
+ plotting eigenmode field profiles. This is done by the `plot_fields`
+ routine in [`wvg-taper.py`](ModeExpansionFiles/wvg-taper.py);
+ the basic flow looks something like this:
+
+````python
+
+ # open HDF5 file
+ file = h5py.File("modeA.h5", 'r')
+
+ # read array of Ey values on grid points
+ ey = file["ey" + suffix + ".r"][()] + 1j*file["ey" + suffix + ".i"][()];
+
+ # plot real part
+ plt.plot(np.real(Ey))
+````
+
+The `plot_modes` routine in `wvg-taper.py` repeats this process for the
+lowest mode $x_A$ (`ModeA1`) and the first several modes at $x_B$
+(`ModeB1...B6`) and plots the results:
+
+
+![ModeProfiles2D.png](ModeExpansionFiles/ModeProfiles2D.png)
+
+
+### Adding an eigenmode source and timestepping
+
+The next step is to add an *eigenmode source* inside the
+smaller waveguide---that is, a collection of Meep point
+sources, lying on the cross-sectional surface at $x_A$,
+whose radiated fields reproduce the fields of a
+given waveguide eigenmode at a given frequency:
+
+
+````python
+sources = [ mp.EigenModeSource(src=mp.GaussianSource(fcen, fwidth=df),
+ center=mp.Vector3(xA,0.0),
+ size=mp.Vector3(0.0,LY),
+ eig_band=band_num
+ )
+ ]
+self.sim=mp.Simulation( cell_size=mp.Vector3(LX, LY),
+ resolution=resolution,
+ boundary_layers=[mp.PML(DPML)],
+ force_complex_fields=True,
+ epsilon_func = eps_func,
+ sources=sources
+ )
+````
+
+Next, we timestep to accumulate Fourier-domain fields
+on a cross-sectional plane within the larger waveguide.
+This is done by the `get_flux()` method in
+[`wvg_taper.py](ModeExpansionFiles/wvg_taper.py).
+
+The timestepping continues until the instantaneous
+Poynting flux through the flux plane at $x_B$
+has decayed to 0.1% of its maximum value.
+When the timestepping is finished, the Fourier-domain
+fields on the plane at $x_B$ are stored
+in a [`dft_flux`](DFTFlux) object called `fluxB.`
+and we can call `meep::fields::output_flux_fields`
+to export the fields to an HDF5 file, similar to
+`output_mode_fields` which we used above:
+
+````python
+f.output_flux_fields(fluxB, vB, 'fluxB')
+````
+
+This produces a file called `fluxB.h5`. One slight
+difference from `output_mode_fields` is that `dft_flux`
+objects typically store field data for multiple
+frequencies, so the field-component datasets
+in the `HDF5` file have names like `ey_0.r`, `ey_1.i`.
+
+### Visualizing DFT fields
+
+Having written Fourier-transform fields to HDF5
+files, we can read in the data and plot, as we
+did previously for mode profiles. In the `wvg_taper.py`
+code this is again handled by the `plot_fields`
+routine.
+Here are the results of this process for
+a few different values of the taper length $L$ and
+smoothness index $p$:
+
+![FluxVsLP.png](ModeExpansionFiles/FluxVsLP.png)
+
+Take-home messages:
+
++ For $L=0$ (no taper, i.e. abrupt junction) the fields
+ at $x_B$ look nothing like the fields of the lowest
+ eigenmode for the larger structure (second row
+ of [this plot](ModeExpansion.md#ModeProfiles));
+ clearly there is significant contamination from
+ higher modes.
++ As we increase the taper length and the smoothness index
+ the fields at $x_B$ more and more closely resemble the
+ lowest eigenmode fields, indicating that the taper is
+ working to transfer power adiabatically from lowest mode
+ to lowest mode.
+
+### Making movies
+
+The `get_flux()` routine in the
+[`wvg_taper.py`](ModeExpansionFiles/wvg_taper.py)
+supports a keyword parameter
+`frame_interval` which, if nonzero, defines an
+interval (in meep time) at which images of the
+instantaneous Poynting flux over the entire geometry
+are to be written to `.h5` files.
+The default is `frame_interval=0`, in which case
+these images will not be written.
+
+If you specify (say) `frame_interval=1`
+to `get_flux()` for a geometry with (say) taper
+length $L=1.2$ and smoothness index $p=1$, you
+will get approximately 100 files with names like
+````bash
+ L1.2_p1_f1.png
+ L1.2_p1_f2.png
+ L1.2_p1_f3.png
+ ...
+ L1.2_p1_f105.png
+ ...
+````
+
+To assemble all these frame files into a movie
+using [FFMPEG](https://www.ffmpeg.org/), go like this:
+
+````bash
+ # ffmpeg -i 'L1.2_p1_f%d.png' L1.2_p1.mpg
+````
+
+(Note that the string `%d` in the input filename
+is a wildcard that will match all integer values; it needs
+to be in single quotes to protect it from shell expansion.)
+
+Here are the movies for the various cases considered above:
+
+
+
+ Taper
+ Movie
+
+ L =0
+
+
+
+
+
+
+ L=1, p=0
+
+
+
+
+
+
+ L=2, p=0
+
+
+
+
+
+
+ L=3, p=0
+
+
+
+
+
+
+ L=3, p=1
+
+
+
+
+
+
+ L=3, p=2
+
+
+
+
+
+
+
+
+
+
+### Extracting mode-expansion coefficients
+
+Finally, we call `get_mode_coefficients` to compute
+the inner product of the Meep DFT fields in the larger waveguide
+with each of a user-specified list of eigenmodes of the larger
+waveguide to compute the fraction of the power carried by
+each mode.
+
+### Intra-modal scattering losses vs. taper length and smoothness
+
+Repeating this calculation for many taper lengths $L$ and
+smoothness indices $p=0,1$ yields the following plots
+showing the rate of decay of inter-mode scattering losses
+as the taper length $L\to\infty.$
+
+![TaperData.png](ModeExpansionFiles/TaperData.png)
+
+
+## Related computational routines
+
+Besides `get_eigenmode_coefficients,` there are a few
+computational routines in `libmeep` that you may find useful
+for problems like those considered above.
+
+### Routine for computing MPB eigenmodes (in `mpb.cpp`)
+````
+ void *fields::get_eigenmode(double &omega,
+ direction d, const volume &where,
+ const volume &eig_vol,
+ int band_num,
+ const vec &kpoint, bool match_frequency,
+ int parity,
+ double resolution,
+ double eigensolver_tol);
+````
+
+Calls MPB to compute the `band_num`th eigenmode at frequency `omega`
+for the portion of your geometry lying in `where` (typically
+a cross-sectional slice of a waveguide). `kpoint` is an initial
+starting guess for what the propagation vector of the waveguide
+mode will be.
+
+### Routines for working with MPB eigenmodes (in `mpb.cpp`)
+
+The return value of `get_eigenmode` is an opaque pointer to
+a data structure storing information about the computed eigenmode,
+which may be passed to the following routines:
+
+````
+// get a single component of the eigenmode field at a given point in space
+std::complex eigenmode_amplitude(const vec &p, void *vedata, component c);
+
+// get the group velocity of the eigenmode
+double get_group_velocity(void *vedata);
+
+// free all memory associated with the eigenmode
+void destroy_eigenmode_data(void *vedata);
+````
+
+### Routines for exporting frequency-domain fields (in `dft.cpp`)
+
+````
+ void output_flux_fields(dft_flux *flux, const volume where,
+ const char *HDF5FileName);
+
+ void output_mode_fields(void *mode_data, dft_flux *flux,
+ const volume where,
+ const char *HDF5FileName);
+````
+
+`output_flux_fields` exports the components of the (frequency-domain) fields
+stored in `flux` to an HDF5 file with the given file name. `where` is the
+`volume` passed to the `flux` constructor. In general, `flux` will store
+data for fields at multiple frequencies, each of which will
+
+`output_mode_fields` is similar, but instead exports the components of the eigenmode
+described by `mode_data` (which should be the return value of a call to `get_eigenmode`).
+
+
+### Routines for computing overlap integrals (in `dft.cpp`)
+````
+ std::complex get_mode_flux_overlap(void *mode_data,
+ dft_flux *flux,
+ int num_freq,
+ const volume where);
+
+ std::complex get_mode_mode_overlap(void *mode1_data,
+ void *mode2_data,
+ dft_flux *flux,
+ const volume where);
+````
+
+`get_mode_flux_overlap` computes the overlap integral
+(defined by [equation (*) above](#OverlapEquation))
+between the eigenmode described by `mode_data`
+and the fields stored in `flux` (for the `num_freq`th stored
+frequency, where `num_freq` ranges from 0 to `flux->Nfreq-1`.)
+`mode_data` should be the return value of a previous call to
+`get_eigenmode.`
+
+`get_mode_mode_overlap` is similar, but computes the overlap
+integral between two eigenmodes. (`mode1_data` and `mode2_data` may be
+identical, in which case you get the inner product of the
+mode with itself; by the normalization convention used in MPB,
+this should equal the group velocity of the mode.)
+
+
+## Under the hood: How mode expansion works
+
+The theoretical basis of the mode-expansion algorithm
+is the orthogonality relation satisfied by the normal
+modes:
+$$ \left\langle \mathbf{E}_m^{\sigma} \right|
+ \left. \mathbf{H}^\tau_n \right\rangle
+ =C_{m}\delta_{mn}\delta_{\sigma\tau}
+ \qquad \Big( \{\sigma,\tau\}\in\{+,-\}\Big)
+ \tag{4}
+$$
+where the inner product involves an integration over
+transverse coordinates:
+
+$$ \left\langle \mathbf{f} \right| \left. \mathbf{g} \right\rangle
+ \equiv
+ \int_{S}
+ \Big[ \mathbf{f}^*(\vec \rho) \times \mathbf{g}(\vec \rho)\Big]
+ \cdot \hat{\mathbf{n}} \, dA
+ \tag{5}
+$$
+where $S$ is any surface transverse to the direction of propagation
+and $\hat{\mathbf{n}}$ is the unit normal vector to $S$ (i.e.
+just $\hat{\mathbf{z}}$ in the case considered above).
+The normalization constant $C_{m}$ is a matter of convention,
+but in [MPB][MPB] it is taken to be the group velocity of the
+mode, $v_m$, times the area $A_S$ of the cross-sectional surface $S$:
+$$ C_m = v_m A_S. \tag{6} $$
+
+Now consider a Meep calculation in which we have accumulated
+frequency-domain $\mathbf E^{\text{meep}}$ and $\mathbf H^{\text{meep}}$
+fields on a `dft-flux`
+object located on a cross-sectional surface $S$. Invoking the
+eigenmode expansion [(1)](ModeExpansion.md#EigenmodeExpansions)
+and choosing (without loss of generality) the origin
+of the $x$ axis to be the position of the cross-sectional plane,
+the tangential components of the frequency-domain Meep fields
+take the form
+$$ \mathbf E^{\text{meep}}_\parallel
+ = \sum_{n} (\alpha_n^+ + \alpha_n^-)\mathbf{E}_{n\parallel}^+,
+ \tag{7}
+$$
+$$ \mathbf H^{\text{meep}}_\parallel
+ = \sum_{n} (\alpha_n^+ - \alpha_n^-)\mathbf{H}_{n\parallel}^+,
+ \tag{8}
+$$
+where we used the well-known relations between the tangential
+components of the forward-traveling and backward-traveling field
+modes:
+$$ \mathbf{E}^+_{n\parallel} =+\mathbf{E}^-_{n\parallel},
+ \qquad
+ \mathbf{H}^+_{n\parallel} =-\mathbf{H}^-_{n\parallel}.
+$$
+Taking the inner product (5) of both sides of equations (7) and (8)
+with the $\mathbf{H}$ and $\mathbf{E}$ fields of each eigenmode
+and using equations (4) and (6), we find
+$$ \left\langle \mathbf{H}_m
+ \right|\left. \mathbf{E}^{\text{meep}} \right\rangle
+ =+(\alpha_n^+ + \alpha_n^-) v_m A_S
+$$
+$$ \left\langle \mathbf{E}_m
+ \right|\left. \mathbf{H}^{\text{meep}} \right\rangle
+ =-(\alpha_n^+ - \alpha_n^+) v_m A_S
+$$
+Thus, by evaluating the integrals on the LHS of these equations---numerically,
+using the MPB-computed eigenmode fields $\{\mathbf{E}, \mathbf{H}\}_m$
+and the Meep-computed fields $\{\mathbf{E}, \mathbf{H}\}^{\text{meep}}\}$
+as tabulated on the computational grid---and combining the results
+appropriately, we can extract the coefficients $\{\alpha^\pm_m\}$
+in the expansion (1). This calculation is carried out by the
+routine [`meep::fields::get_mode_flux_overlap`](ModeExpansion.md#OverlapRoutines).
+(Although simple in principle, the implementation is complicated by
+the fact that, in multi-processor calculations, the Meep fields
+needed to evaluate the integrals are generally not all present
+on any one processor, but are instead distributed over multiple
+processors, requiring some interprocess communication to evaluate
+the full integral.)
+
+The Poynting flux carried by the Meep fields (7,8) may be expressed
+in the form
+$$ S_x = \frac{1}{2}\text{Re }
+ \left\langle \mathbf{E}^{\text{meep}}\right|
+ \left. \mathbf{H}^{\text{meep}}\right\rangle
+ = \frac{1}{2}\sum_n \left\{ |\alpha_n^+|^2 - |\alpha_n^-|^2) \right\} v_n A_S
+$$
+and thus the fractional power carried by any one (forward- or backward-traveling)
+eigenmode is given by
+$$ \text{fractional power carried by }\pm\text{-traveling mode }n=
+ \frac{|\alpha_n^\pm|^2 v_n A_S}{2S_x}
+$$
+
+[^1]:
+ The theory of waveguide modes is covered in many references;
+ one that we have found useful is [Snyder and Love, *Optical Waveguide Theory* (Springer, 1983)](http://www.springer.com/us/book/9780412099502).
+
+
+[MPB]: https://mpb.readthedocs.io/en/latest/
+[DFTFlux]: https://meep.readthedocs.io/en/latest/Scheme_User_Interface/#Flux_spectra.md
diff --git a/doc/docs/ModeExpansionFiles/FluxVsLP.png b/doc/docs/ModeExpansionFiles/FluxVsLP.png
new file mode 100644
index 000000000..f3c562c26
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/FluxVsLP.png differ
diff --git a/doc/docs/ModeExpansionFiles/L0.0_p0_f85.png b/doc/docs/ModeExpansionFiles/L0.0_p0_f85.png
new file mode 100644
index 000000000..2b3752529
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/L0.0_p0_f85.png differ
diff --git a/doc/docs/ModeExpansionFiles/L0_p0.mpg b/doc/docs/ModeExpansionFiles/L0_p0.mpg
new file mode 100644
index 000000000..d697f2e67
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/L0_p0.mpg differ
diff --git a/doc/docs/ModeExpansionFiles/L1.0_p0_f85.png b/doc/docs/ModeExpansionFiles/L1.0_p0_f85.png
new file mode 100644
index 000000000..d7322841b
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/L1.0_p0_f85.png differ
diff --git a/doc/docs/ModeExpansionFiles/L1_p0.mpg b/doc/docs/ModeExpansionFiles/L1_p0.mpg
new file mode 100644
index 000000000..ed1fb866b
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/L1_p0.mpg differ
diff --git a/doc/docs/ModeExpansionFiles/L2.0_p0_f85.png b/doc/docs/ModeExpansionFiles/L2.0_p0_f85.png
new file mode 100644
index 000000000..c2e8532ba
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/L2.0_p0_f85.png differ
diff --git a/doc/docs/ModeExpansionFiles/L2_p0.mpg b/doc/docs/ModeExpansionFiles/L2_p0.mpg
new file mode 100644
index 000000000..c829e4651
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/L2_p0.mpg differ
diff --git a/doc/docs/ModeExpansionFiles/L3.0_p0_f85.png b/doc/docs/ModeExpansionFiles/L3.0_p0_f85.png
new file mode 100644
index 000000000..773a074ac
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/L3.0_p0_f85.png differ
diff --git a/doc/docs/ModeExpansionFiles/L3.0_p1_f85.png b/doc/docs/ModeExpansionFiles/L3.0_p1_f85.png
new file mode 100644
index 000000000..6fdc6c162
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/L3.0_p1_f85.png differ
diff --git a/doc/docs/ModeExpansionFiles/L3.0_p2_f85.png b/doc/docs/ModeExpansionFiles/L3.0_p2_f85.png
new file mode 100644
index 000000000..9a8aa1a0b
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/L3.0_p2_f85.png differ
diff --git a/doc/docs/ModeExpansionFiles/L3_p0.mpg b/doc/docs/ModeExpansionFiles/L3_p0.mpg
new file mode 100644
index 000000000..3178a0c68
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/L3_p0.mpg differ
diff --git a/doc/docs/ModeExpansionFiles/L3_p1.mpg b/doc/docs/ModeExpansionFiles/L3_p1.mpg
new file mode 100644
index 000000000..b92922bb3
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/L3_p1.mpg differ
diff --git a/doc/docs/ModeExpansionFiles/L3_p2.mpg b/doc/docs/ModeExpansionFiles/L3_p2.mpg
new file mode 100644
index 000000000..9cdde3913
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/L3_p2.mpg differ
diff --git a/doc/docs/ModeExpansionFiles/ModeProfiles2D.png b/doc/docs/ModeExpansionFiles/ModeProfiles2D.png
new file mode 100644
index 000000000..35ea07a26
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/ModeProfiles2D.png differ
diff --git a/doc/docs/ModeExpansionFiles/Taper2D.svg b/doc/docs/ModeExpansionFiles/Taper2D.svg
new file mode 100644
index 000000000..c8d101c5f
--- /dev/null
+++ b/doc/docs/ModeExpansionFiles/Taper2D.svg
@@ -0,0 +1,1453 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/docs/ModeExpansionFiles/Taper2D_p0.png b/doc/docs/ModeExpansionFiles/Taper2D_p0.png
new file mode 100644
index 000000000..f42f94c23
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/Taper2D_p0.png differ
diff --git a/doc/docs/ModeExpansionFiles/Taper2D_p1.png b/doc/docs/ModeExpansionFiles/Taper2D_p1.png
new file mode 100644
index 000000000..6a99fccd9
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/Taper2D_p1.png differ
diff --git a/doc/docs/ModeExpansionFiles/Taper2D_p2.png b/doc/docs/ModeExpansionFiles/Taper2D_p2.png
new file mode 100644
index 000000000..ce2b6d96e
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/Taper2D_p2.png differ
diff --git a/doc/docs/ModeExpansionFiles/TaperData.png b/doc/docs/ModeExpansionFiles/TaperData.png
new file mode 100644
index 000000000..1aec483a0
Binary files /dev/null and b/doc/docs/ModeExpansionFiles/TaperData.png differ
diff --git a/doc/docs/ModeExpansionFiles/wvg-taper.cpp b/doc/docs/ModeExpansionFiles/wvg-taper.cpp
new file mode 120000
index 000000000..5bd2f5aa1
--- /dev/null
+++ b/doc/docs/ModeExpansionFiles/wvg-taper.cpp
@@ -0,0 +1 @@
+../../../libmeepgeom/wvg-taper.cpp
\ No newline at end of file
diff --git a/doc/docs/ModeExpansionFiles/wvg-taper.py b/doc/docs/ModeExpansionFiles/wvg-taper.py
new file mode 100644
index 000000000..cd079ee5e
--- /dev/null
+++ b/doc/docs/ModeExpansionFiles/wvg-taper.py
@@ -0,0 +1,363 @@
+import meep as mp
+import numpy as np
+import h5py as h5py
+import matplotlib.pyplot as plt
+import matplotlib.animation as animation
+
+##################################################
+# x-dependent width of waveguide
+##################################################
+def w_func(x, L, p, wA, wB):
+ if L==0:
+ return wA if x<0 else wB
+ x0=x/L
+ if (x0 < -0.5):
+ return wA;
+ elif (x0 > +0.5):
+ return wB;
+ elif p==2:
+ return 0.5*(wA+wB) + (wB-wA)*x0*(15.0 + x0*x0*(-40.0 + x0*x0*48.0))/8.0;
+ elif p==1:
+ return 0.5*(wA+wB) + (wB-wA)*x0*(1.5 - 2.0*x0*x0);
+ else: # default t p==0, simple linear taper
+ return 0.5*(wA+wB) + (wB-wA)*x0;
+
+##################################################
+# user-defined function for spatially-varying epsilon
+##################################################
+def my_eps_func(loc, L, p, wA, wB, eps_out, eps_in):
+
+ if ( abs(loc.y) > 0.5*w_func(loc.x, L, p, wA, wB) ):
+ return eps_out; # outside waveguide
+ else:
+ return eps_in; # inside waveguide
+
+##################################################
+# user-provided functions to estimate k-vectors for waveguide eigenmodes
+##################################################
+def equal_float(a,b,Tol=1.0e-6):
+ if ( abs(a-b) < Tol*max(abs(a),abs(b)) ):
+ return True;
+ else:
+ return False;
+
+def k_guess(freq, band_num, w):
+
+ # hard-coded dispersion relations for waveguides of given size
+ if ( equal_float(w,1.0) and equal_float(freq, 0.15) ):
+ if (band_num>=1): return mp.vec(0.419984,0)
+
+ if ( equal_float(w,3.0) and equal_float(freq, 0.15) ):
+ if (band_num==1):
+ return mp.vec(0.494499,0)
+ if (band_num==2):
+ return mp.vec(0.486657,0)
+ if (band_num==3):
+ return mp.vec(0.434539,0)
+ if (band_num==4):
+ return mp.vec(0.397068,0)
+ if (band_num==5):
+ return mp.vec(0.322812,0)
+ if (band_num>=6):
+ return mp.vec(0.211186,0)
+
+ return mp.vec(0.0,0.0)
+
+##################################################
+##################################################
+##################################################
+class wvg_taper:
+
+ ##################################################
+ # constructor
+ ##################################################
+ def __init__(self,
+ wA=1.0, wB=3.0, # smaller, larger waveguide thickness
+ LWaveguide=3.0, # length of each waveguide section
+ LTaper=3.0, pTaper=0, # taper length and smoothness index
+ eps_waveguide=11.7, # permittivity inside waveguide
+ eps_ambient=1.0, # permittivity of medium
+ LY=6.0, # width of computational cell
+ DPML=0.5, # PML thickness
+ fcen=0.15, df=0.075, # center frequency / width
+ band_num=1, # index of eigenmode source
+ resolution=25.0, # grid points per unit length
+ ):
+
+ #--------------------------------------------------------------------
+ #- user-defined epsilon function
+ #--------------------------------------------------------------------
+ eps_func = lambda loc: my_eps_func(loc, LTaper, pTaper, wA, wB,
+ eps_ambient, eps_waveguide)
+
+ #--------------------------------------------------------------------
+ #- eigenmode source at midpoint of smaller waveguide
+ #--------------------------------------------------------------------
+ LX = 2.0*(DPML + LWaveguide) + LTaper;
+ xA = -0.5*LX + DPML + 0.5*LWaveguide;
+ xB = +0.5*LX - DPML - 0.5*LWaveguide;
+ sources = [ mp.EigenModeSource(src=mp.GaussianSource(fcen, fwidth=df),
+ center=mp.Vector3(xA,0.0),
+ size=mp.Vector3(0.0,LY),
+ eig_band=band_num
+ )
+ ]
+
+ self.sim=mp.Simulation( cell_size=mp.Vector3(LX, LY),
+ resolution=resolution,
+ boundary_layers=[mp.PML(DPML)],
+ force_complex_fields=True,
+ epsilon_func = eps_func,
+ sources=sources
+ )
+
+ self.sim.run(mp.at_beginning(mp.output_epsilon), until=1.0)
+ f=self.sim.fields;
+
+ #--------------------------------------------------
+ # add DFT flux regions at midpoints of smaller and larger waveguides
+ #--------------------------------------------------
+ YP = 0.5*LY - DPML;
+ self.vA = mp.volume( mp.vec(xA, -YP), mp.vec(xA,+YP) )
+ self.vB = mp.volume( mp.vec(xB, -YP), mp.vec(xB,+YP) )
+ nf=1;
+ self.fluxA=f.add_dft_flux_plane(self.vA, fcen-0.5*df, fcen+0.5*df, nf);
+ self.fluxB=f.add_dft_flux_plane(self.vB, fcen-0.5*df, fcen+0.5*df, nf);
+
+ #--------------------------------------------------
+ # save some other fields in the wvg_taper class for later use
+ #--------------------------------------------------
+ self.xA = xA;
+ self.xB = xB;
+ self.wA = wA;
+ self.wB = wB;
+ self.LTaper = LTaper;
+ self.pTaper = pTaper;
+ self.fcen = fcen;
+ self.df = df;
+ self.band_num = band_num;
+
+ ##################################################
+ # plot permittivity over the computational grid
+ ##################################################
+ def plot_eps(self):
+
+ eps=self.sim.get_array(center = mp.Vector3(0,0),
+ size = self.sim.cell_size,
+ component = mp.Dielectric)
+
+ interp='gaussian'
+ cmap='coolwarm'
+ LX=self.sim.cell_size.x;
+ LY=self.sim.cell_size.y;
+ extent=[-0.5*LX,0.5*LX,-0.5*LY,0.5*LY];
+ plt.figure()
+
+ # plot epsilon grid
+ plt.rc('xtick', labelsize=15)
+ plt.rc('ytick', labelsize=15)
+ plt.imshow(eps.transpose(), interpolation=interp, cmap=cmap, extent=extent)
+ plt.xlabel(r"$x$",fontsize=40)
+ plt.ylabel(r"$y$",fontsize=40,rotation=0)
+ plt.colorbar()
+
+ # add lines and annotations to indicate locations of flux planes
+ plt.plot(self.xA*np.ones(100), np.linspace(-0.5*LY, 0.5*LY, 100), "--", lw=5, color='white')
+ plt.text(self.xA, -0.5*LY-0.5, r'$x_A$', fontsize=40, horizontalalignment='center')
+ plt.plot(self.xB*np.ones(100), np.linspace(-0.5*LY, 0.5*LY, 100), "--", lw=5, color='white');
+ plt.text(self.xB, -0.5*LY-0.5, r'$x_B$', fontsize=40, horizontalalignment='center')
+
+ plt.show(block=False)
+
+ ##################################################
+ # subroutine invoked by plot_modes and plot_flux
+ # to plot field components stored in an hdf5 file
+ ##################################################
+ def plot_fields(self, num_rows, plot_row, file_name, row_name, is_flux):
+
+ # read (tranverse) field components on cross-sectional plane from file
+ file = h5py.File(file_name + ".h5", 'r')
+ suffix = "_0" if is_flux else "";
+ ey = file["ey" + suffix + ".r"][()] + 1j*file["ey" + suffix + ".i"][()];
+ ez = file["ez" + suffix + ".r"][()] + 1j*file["ez" + suffix + ".i"][()];
+ hy = file["hy" + suffix + ".r"][()] + 1j*file["hy" + suffix + ".i"][()];
+ hz = file["hz" + suffix + ".r"][()] + 1j*file["ez" + suffix + ".i"][()];
+ if np.size(ey.shape)>1:
+ ey = np.real(ey[1,:]);
+ ez = np.real(ez[1,:]);
+ hy = np.real(hy[1,:]);
+ hz = np.real(hz[1,:]);
+
+ # plot ey,ez,hy,hz
+ eMax=max(np.absolute(ey).max(), np.absolute(ez).max());
+ hMax=max(np.absolute(hy).max(), np.absolute(hz).max());
+ NY=ey.shape[0];
+ yMax=self.vA.get_min_corner().y();
+ yRange=np.linspace(-yMax,yMax,NY);
+ r0=4*(plot_row-1);
+
+ # plot labels
+ plt.subplot(num_rows,4,r0+1); plt.plot(yRange,ey); plt.ylim(-eMax,eMax);
+ plt.subplot(num_rows,4,r0+2); plt.plot(yRange,ez); plt.ylim(-eMax,eMax);
+ plt.subplot(num_rows,4,r0+3); plt.plot(yRange,hy); plt.ylim(-hMax,hMax);
+ plt.subplot(num_rows,4,r0+4); plt.plot(yRange,hz); plt.ylim(-hMax,hMax);
+ plt.subplot(num_rows,4,1); plt.title(r're($E_y$)',fontsize=40)
+ plt.subplot(num_rows,4,2); plt.title(r're($E_z$)',fontsize=40)
+ plt.subplot(num_rows,4,3); plt.title(r're($H_y$)',fontsize=40)
+ plt.subplot(num_rows,4,4); plt.title(r're($H_z$)',fontsize=40)
+ plt.subplot(num_rows,4,r0+1); plt.ylabel(row_name,fontsize=32, rotation=0,
+ horizontalalignment='right')
+ plt.show(block=False);
+
+ ##################################################
+ # generate plots of eigenmode profiles
+ ##################################################
+ def plot_modes(self, nbMax=7):
+
+ ##################################################
+ # calculate the eigenmodes and write field components
+ # on cross-sectional planes to HDF5 files
+ ##################################################
+ f = self.sim.fields;
+ vA = self.vA;
+ vB = self.vB;
+ fcen = self.fcen;
+
+ plt.clf();
+
+ # eigenmode #1 in narrower waveguide
+ nb=self.band_num;
+ k0=k_guess(fcen,nb,self.wA);
+ modeA = f.get_eigenmode(fcen, mp.X, vA, vA, nb, k0, True, 0, 0.0, 1.0e-4)
+ f.output_mode_fields(modeA, self.fluxA, vA, "modeA");
+ self.plot_fields(nbMax, 1, "modeA", "Mode A1", False);
+
+ # eigenmodes #1-6 in wider waveguide
+ for nb in range(1,nbMax):
+ k0=k_guess(fcen,nb,self.wB);
+ modeB = f.get_eigenmode(fcen, mp.X, vB, vB, nb, k0, True, 0, 0.0, 1.0e-4)
+ file_name="modeB" + str(nb);
+ row_name="Mode B" + str(nb);
+ f.output_mode_fields(modeB, self.fluxB, vB, file_name);
+ self.plot_fields(nbMax, nb+1, file_name, row_name, False);
+
+ for np in range(25,29):
+ plt.subplot(nbMax,4,np); plt.xlabel(r'$y$',fontsize=32)
+
+ ##################################################
+ # add an eigenmode-source excitation for the #band_numth mode
+ # of the smaller waveguide, then timestep to accumulate DFT
+ # flux in the larger waveguide.
+ # if frame_interval>0, a movie is created showing
+ # the fields on the xy plane with one frame
+ # every frame_interval time units (in meep time)
+ ##################################################
+ def get_flux(self, frame_interval=0):
+
+ #--------------------------------------------------
+ #--------------------------------------------------
+ #--------------------------------------------------
+ nextFrameTime = 1e100;
+ f=self.sim.fields;
+ vA=self.vA;
+ vB=self.vB;
+ LTaper=self.LTaper;
+ pTaper=self.pTaper;
+ origin=mp.Vector3(0,0,0)
+ LX=self.sim.cell_size.x;
+ LY=self.sim.cell_size.y;
+ extent=[-0.5*LX,0.5*LX,-0.5*LY,0.5*LY];
+ full_grid=mp.Vector3(LX, LY, 0)
+ frames=[]
+ fig=0
+ plt.ion()
+ num_frames=0
+ if frame_interval>0:
+ fig=plt.figure();
+ nextFrameTime=f.round_time();
+
+ #--------------------------------------------------
+ # timestep until Poynting flux through larger waveguide has
+ # decayed to 0.1% its max value
+ #--------------------------------------------------
+ pvInterval=1.0; # check PV decay every 1.0 meep time units
+ nextPVTime=f.round_time() + pvInterval;
+ MaxPV=0.0;
+ Stop=False;
+ SMax=0.0; # max poynting flux at any point
+ while Stop==False:
+
+ f.step();
+
+ # check for poynting-flux decay at regular intervals
+ FieldsDecayed=False;
+ if f.round_time() > nextPVTime:
+ nextPVTime += pvInterval;
+ ThisPV=f.flux_in_box(mp.X,vB)
+ if (ThisPV > MaxPV):
+ MaxPV = ThisPV;
+ elif (ThisPV < 0.001*MaxPV):
+ FieldsDecayed=True;
+
+ # write movie-frame files at regular intervals if requested
+ if f.round_time() > nextFrameTime:
+ nextFrameTime += frame_interval;
+ eps=self.sim.get_array(center=origin,size=full_grid,component=mp.Dielectric)
+ Sx=self.sim.get_array(center=origin,size=full_grid,component=mp.Sx)
+ SMax = max( abs(Sx.max()), SMax )
+ plt.clf();
+ eps=-eps*SMax/11.7;
+ plt.imshow(eps.transpose(), extent=extent,
+ interpolation='gaussian', cmap='coolwarm')
+ plt.imshow(Sx.transpose(), alpha=0.5, extent=extent,
+ interpolation='gaussian', cmap='coolwarm')
+ plt.savefig('L{}_p{}_f{}.png'.format(LTaper,pTaper,num_frames));
+ num_frames+=1;
+
+ SourcesFinished = f.round_time() > f.last_source_time();
+ Stop = (SourcesFinished and FieldsDecayed);
+
+ print("finished timestepping at {}".format(f.round_time()))
+ file_name="fluxB_p" + str(self.pTaper) + "_L" + str(self.LTaper);
+ f.output_flux_fields(wt.fluxB, wt.vB, file_name);
+ if self.LTaper==0:
+ row_name="Flux B\n(L=0)"
+ else:
+ row_name="Flux B\n(L=" + str(self.LTaper) + ", p=" + str(self.pTaper) + ")"
+ nbMax=7;
+ self.plot_fields(nbMax,1,file_name,row_name,True)
+ plt.subplot(nbMax,4,1);
+ for np in range(1,5):
+ plt.subplot(nbMax,4,np); plt.xlabel(r'$y$',fontsize=32)
+
+ # postprocessing to generate movie:
+ # ffmpeg -i frame%d.png output.mpg
+
+ ##################################################
+ # postprocessing: compute coefficients in normal-mode
+ # expansion of DFT fields in larger waveguide
+ ##################################################
+ def get_eigenmode_coefficients(self, flux, d, vol,
+ bands, k_guess, k_guess_data):
+ f=self.sim.fields
+ return f.get_eigenmode_coefficients(flux, d, vol, bands,
+ k_guess, k_guess_data)
+
+##################################################
+##################################################
+##################################################
+wt=wvg_taper(LTaper=0.0,pTaper=0,resolution=25)
+#wt.plot_modes()
+wt.get_flux(frame_interval=1);
+wt=wvg_taper(LTaper=1.0,pTaper=0,resolution=50)
+wt.get_flux(frame_interval=1);
+wt=wvg_taper(LTaper=2.0,pTaper=0,resolution=50)
+wt.get_flux(frame_interval=1);
+wt=wvg_taper(LTaper=3.0,pTaper=0,resolution=50)
+wt.get_flux(frame_interval=1);
+wt=wvg_taper(LTaper=3.0,pTaper=0,resolution=50)
+wt.get_flux(frame_interval=1);
+wt=wvg_taper(LTaper=3.0,pTaper=1,resolution=50)
+wt.get_flux(frame_interval=1);
+wt=wvg_taper(LTaper=3.0,pTaper=2,resolution=50)
+wt.get_flux(frame_interval=1);
diff --git a/libmeepgeom/Makefile.am b/libmeepgeom/Makefile.am
index aa827e2d3..cb8f387c2 100644
--- a/libmeepgeom/Makefile.am
+++ b/libmeepgeom/Makefile.am
@@ -34,7 +34,10 @@ TESTS = cyl-ellipsoid-ll array-slice-ll
#LOG_COMPILER = $(RUNCODE)
-noinst_PROGRAMS = bend-flux-ll
+noinst_PROGRAMS = bend-flux-ll wvg-taper
+
+wvg_taper_SOURCES = wvg-taper.cpp
+wvg_taper_LDADD = libmeepgeom.la $(MEEPLIBS)
bend_flux_ll_SOURCES = bend-flux-ll.cpp
bend_flux_ll_LDADD = libmeepgeom.la $(MEEPLIBS)
diff --git a/libmeepgeom/wvg-taper.cpp b/libmeepgeom/wvg-taper.cpp
new file mode 100644
index 000000000..f5370532b
--- /dev/null
+++ b/libmeepgeom/wvg-taper.cpp
@@ -0,0 +1,560 @@
+/***************************************************************/
+/* demonstration of mode expansion in a strip-waveguide taper */
+/***************************************************************/
+#include
+#include
+#include
+#include
+
+#include "meep.hpp"
+
+#include "ctl-math.h"
+#include "ctlgeom.h"
+
+#include "meepgeom.hpp"
+
+#ifndef DATADIR
+ #define DATADIR "./"
+#endif
+
+using namespace meep;
+
+typedef std::complex cdouble;
+
+extern std::vector mode_group_velocities;
+
+vector3 v3(double x, double y=0.0, double z=0.0)
+{
+ vector3 v;
+ v.x=x; v.y=y; v.z=z;
+ return v;
+}
+
+/***************************************************************/
+/* dummy material function needed to pass to structure( ) */
+/* constructor as a placeholder before we can call */
+/* set_materials_from_geometry */
+/***************************************************************/
+double dummy_eps(const vec &) { return 1.0; }
+
+/***************************************************************/
+/***************************************************************/
+/***************************************************************/
+void usage(char *progname, const char *msg=0)
+{
+ if (msg) master_printf("%s\n",msg);
+ master_printf("usage: %s [options]\n",progname);
+ master_printf("options: \n");
+ master_printf(" --use-symmetry use geometric symmetries\n");
+ master_printf(" --write-files write reference data files\n");
+ abort();
+}
+
+/***************************************************************/
+/* user-supplied routine for estimating dispersion relations */
+/* to accelerate MPB calculations */
+/***************************************************************/
+bool equal_float(double x, double y) { return (float)x == (float)y; }
+
+vec k_guess(void *user_data, double freq, int band_num)
+{
+ double w = *((double *)user_data);
+
+ if ( equal_float(freq,0.41) && equal_float(w,0.5))
+ { if (band_num==1) return vec(0.9892331, 0.0, 0.0);
+ if (band_num==2) return vec(0.6175083, 0.0, 0.0);
+ if (band_num==3) return vec(0.5469879, 0.0, 0.0);
+ if (band_num==4) return vec(0.5245156, 0.0, 0.0);
+ if (band_num==5) return vec(0.4267270, 0.0, 0.0);
+ if (band_num>=6) return vec(0.4245740, 0.0, 0.0);
+ }
+ else if ( equal_float(freq,0.41) && equal_float(w,1.0))
+ { if (band_num==1) return vec(1.073627, 0.0, 0.0);
+ if (band_num==2) return vec(0.9856316, 0.0, 0.0);
+ if (band_num==3) return vec(0.8233921, 0.0, 0.0);
+ if (band_num==4) return vec(0.5844210, 0.0, 0.0);
+ if (band_num==5) return vec(0.5497692, 0.0, 0.0);
+ if (band_num>=6) return vec(0.5116020, 0.0, 0.0);
+ }
+ else if ( equal_float(freq,0.15) && equal_float(w,3.0))
+ { if (band_num==1) return vec(0.494476, 0.0, 0.0);
+ if (band_num==2) return vec(0.486399, 0.0, 0.0);
+ if (band_num==3) return vec(0.435861, 0.0, 0.0);
+ if (band_num==4) return vec(0.397068, 0.0, 0.0);
+ if (band_num==5) return vec(0.322812, 0.0, 0.0);
+ if (band_num>=6) return vec(0.211186, 0.0, 0.0);
+ }
+ else if ( equal_float(freq,0.15) && equal_float(w,2.0))
+ { if (band_num==1) return vec(0.426302, 0.0, 0.0);
+ if (band_num==2) return vec(0.4534589, 0.0, 0.0);
+ if (band_num==3) return vec(0.3446421, 0.0, 0.0);
+ if (band_num==4) return vec(0.1860106, 0.0, 0.0);
+ if (band_num>=5) return vec(0.1475703, 0.0, 0.0);
+ }
+ else // if ( equal_float(freq,0.15) && equal_float(w,1.0))
+ { if (band_num==1) return vec(0.419984, 0.0, 0.0);
+ if (band_num>=2) return vec(0.426302, 0.0, 0.0);
+ };
+
+ return vec(0.0, 0.0, 0.0);
+}
+
+/***************************************************************/
+/* user-defined material function for 2D/3D waveguide geometry */
+/***************************************************************/
+typedef struct wvg_data
+ { double wA; // width of smaller waveguide
+ double wB; // width of larger waveguide
+ double taper_length; // taper length (=0 for no taper)
+ int taper_order; // index of first discontinuous derivative (1,2)
+ double eps_wvg; // permittivity of waveguide material
+ double eps_ambient; // permittivity of surrounding medium
+ bool three_d; // true if we are in the 3D case
+
+ // these fields used only for 3D case
+ double z_substrate; // z-coordinate of substrate-oxide interface
+ double z_oxide; // z-coordinate of oxide-wvg interface
+ double z_wvg; // z-coordinate of wvg upper surface
+ double eps_substrate; // dielectric constant of substrate
+ double eps_oxide; // dielectric constant of oxide layer
+ } wvg_data;
+
+void wvg_material(vector3 loc, void *user_data, meep_geom::medium_struct *m)
+{
+ wvg_data *wdata=(wvg_data *)user_data;
+
+ double z = loc.z;
+ double eps = wdata->eps_ambient; // assume we are in ambient medium
+ if (wdata->three_d && zz_substrate) // 3D, in substrate
+ {
+ eps = wdata->eps_substrate;
+ }
+ else if (wdata->three_d && zz_oxide) // 3D, in oxide layer
+ {
+ eps = wdata->eps_oxide;
+ }
+ else if (wdata->three_d && z>wdata->z_wvg ) // 3D, above waveguide
+ {
+ eps = wdata->eps_ambient;
+ }
+ else // 2D or 3D, inside waveguide
+ {
+ double x0 = loc.x / wdata->taper_length;
+ double wA = wdata->wA, wB = wdata->wB, w;
+ if (x0 <= -0.5 )
+ w = wA;
+ else if ( x0 >= 0.5)
+ w = wB;
+ else if (wdata->taper_order==2)
+ w = 0.5*(wA+wB) + (wB-wA)*x0*(15.0 + x0*x0*(-40.0 + x0*x0*48.0))/8.0;
+ else if (wdata->taper_order==1)
+ w = 0.5*(wA+wB) + (wB-wA)*x0*(1.5 - 2.0*x0*x0);
+ else // p=0, i.e. linear taper
+ w = 0.5*(wA+wB) + (wB-wA)*x0;
+
+ eps = ( fabs(loc.y)<=0.5*w ) ? wdata->eps_wvg : wdata->eps_ambient;
+ };
+
+ m->epsilon_diag.x=m->epsilon_diag.y=m->epsilon_diag.z=eps;
+
+}
+
+/***************************************************************/
+/***************************************************************/
+/***************************************************************/
+int main(int argc, char *argv[])
+{
+ initialize mpi(argc, argv);
+
+ /***************************************************************/
+ /* parse command-line options **********************************/
+ /***************************************************************/
+ bool three_d = false;
+ double taper_length = 0.0;
+ double wvg_length = 3.0;
+ double ratio = 3.0;
+ double freq = 0.25;
+ int taper_order = 0;
+ int band_num = 1;
+ int num_bands = 6;
+ bool use_symmetry = false;
+ bool plot_modes = false;
+ bool plot_flux = false;
+ bool plot_structure = false;
+ double frame_interval = 0.0;
+ char *filebase = const_cast("wt");
+ double LY = 3.5; // half-width of cell in transverse direction double LZ=1.5;
+ double LZ = 2.0; // half-width of cell in transverse direction double LZ=1.5;
+ double dpml = 0.50; // PML thickness
+ double res = 50.0; // resolution
+ for(int narg=1; narg= argc )
+ usage(argv[0], "error: no argument given for --ratio");
+ sscanf(argv[narg], "%le", &ratio);
+ master_printf("setting ratio=%e\n",ratio);
+ }
+ else if (!strcasecmp(argv[narg],"--taper-length"))
+ { if ( ++narg >= argc )
+ usage(argv[0], "error: no argument given for --taper-length");
+ sscanf(argv[narg], "%le", &taper_length);
+ master_printf("setting taper_length=%e\n",taper_length);
+ }
+ else if (!strcasecmp(argv[narg],"--taper-order"))
+ { if ( ++narg >= argc )
+ usage(argv[0], "error: no argument given for --taper-order");
+ sscanf(argv[narg], "%i", &taper_order);
+ master_printf("setting taper_order=%i\n",taper_order);
+ }
+ else if (!strcasecmp(argv[narg],"--use-symmetry") )
+ { use_symmetry=true;
+ master_printf("Using symmetry.\n");
+ }
+ else if (!strcasecmp(argv[narg],"--plot-modes") )
+ plot_modes=true;
+ else if (!strcasecmp(argv[narg],"--plot-flux") )
+ plot_flux=true;
+ else if (!strcasecmp(argv[narg],"--plot-structure") )
+ plot_structure=true;
+ else if (!strcasecmp(argv[narg],"--band-num"))
+ { if ( ++narg >= argc )
+ usage(argv[0], "error: no argument given for --band-num");
+ sscanf(argv[narg], "%i", &band_num);
+ master_printf("setting band-num=%i\n",band_num);
+ }
+ else if (!strcasecmp(argv[narg],"--num-bands"))
+ { if ( ++narg >= argc )
+ usage(argv[0], "error: no argument given for --num-bands");
+ sscanf(argv[narg], "%i", &num_bands);
+ master_printf("setting num-bands=%i\n",num_bands);
+ }
+ else if (!strcasecmp(argv[narg],"--frame-interval"))
+ { if ( ++narg >= argc )
+ usage(argv[0], "error: no argument given for --frame-interval");
+ sscanf(argv[narg], "%le", &frame_interval);
+ master_printf("setting frame-interval=%e\n",frame_interval);
+ }
+ else if (!strcasecmp(argv[narg],"--freq"))
+ { if ( ++narg >= argc )
+ usage(argv[0], "error: no argument given for --freq");
+ sscanf(argv[narg], "%le", &freq);
+ master_printf("setting freq=%e\n",freq);
+ }
+ else if (!strcasecmp(argv[narg],"--resolution"))
+ { if ( ++narg >= argc )
+ usage(argv[0], "error: no argument given for --resolution");
+ sscanf(argv[narg], "%le", &res);
+ master_printf("setting resolution=%e\n",res);
+ }
+ else if (!strcasecmp(argv[narg],"--LY"))
+ { if ( ++narg >= argc )
+ usage(argv[0], "error: no argument given for --LY");
+ sscanf(argv[narg], "%le", &LY);
+ master_printf("setting LY=%e\n",LY);
+ }
+ else if (!strcasecmp(argv[narg],"--LZ"))
+ { if ( ++narg >= argc )
+ usage(argv[0], "error: no argument given for --LZ");
+ sscanf(argv[narg], "%le", &LZ);
+ master_printf("setting LZ=%e\n",LZ);
+ }
+ else if (!strcasecmp(argv[narg],"--dpml"))
+ { if ( ++narg >= argc )
+ usage(argv[0], "error: no argument given for --dpml");
+ sscanf(argv[narg], "%le", &dpml);
+ master_printf("setting dpml=%e\n",dpml);
+ }
+ else if (!strcasecmp(argv[narg],"--filebase"))
+ { if ( ++narg >= argc )
+ usage(argv[0], "error: no argument given for --filebase");
+ filebase=strdup(argv[narg]);
+ }
+ else
+ { master_printf("unknown command-line option %s (aborting)",argv[narg]);
+ usage(argv[0]);
+ };
+ };
+
+ if (taper_length==0.0)
+ taper_length=0.1/res;
+
+ /***************************************************************/
+ /* initialize computational cell */
+ /****************** ********************************************/
+ double LX = dpml + wvg_length + 0.5*taper_length;
+ geometry_lattice.size.x = 2*LX;
+ geometry_lattice.size.y = 2*LY;
+ geometry_lattice.size.z = ( (three_d) ? 2*LZ : 0.0 );
+ grid_volume gv = three_d ? vol3d(2*LX, 2*LY, 2*LZ, res) : voltwo(2*LX, 2*LY, res);
+ gv.center_origin();
+ symmetry sym = use_symmetry ? -mirror(Y,gv) : identity();
+ structure the_structure(gv, dummy_eps, pml(dpml), sym);
+
+ /***************************************************************/
+ /* specify user-defined material function */
+ /***************************************************************/
+ double wA = 1.0;
+ double wB = wA*ratio;
+ double h_substrate = 0.0; // no substrate by default
+ double h_oxide = 1.5; // oxide layer thickness (3D case)
+ double h_wvg = 0.22; // waveguide thickness (3D case)
+ double eps_Si = 11.7; // dielectric constant of silicon
+ double eps_SiO2 = 2.1; // dielectric constant of SiO2
+
+ wvg_data wdata;
+ wdata.wA = wA;
+ wdata.wB = wB;
+ wdata.taper_length = taper_length;
+ wdata.taper_order = taper_order;
+ wdata.eps_wvg = eps_Si;
+ wdata.eps_ambient = 1.0; // ambient medium is vacuum
+ wdata.three_d = three_d;
+ if (three_d)
+ { wdata.z_substrate = -LZ + h_substrate;
+ wdata.z_oxide = -LZ + h_substrate + h_oxide;
+ wdata.z_wvg = -LZ + h_substrate + h_oxide + h_wvg;
+ wdata.eps_substrate = eps_Si;
+ wdata.eps_oxide = eps_SiO2;
+ };
+ meep_geom::material_type my_material
+ = meep_geom::make_user_material(wvg_material, (void *)&wdata);
+ bool use_anisotropic_averaging = true;
+ double sbtol = DEFAULT_SUBPIXEL_TOL;
+ int maxeval = DEFAULT_SUBPIXEL_MAXEVAL;
+ bool ensure_periodicity = false;
+ bool verbose = false;
+ geometric_object_list g={0,0};
+ meep_geom::set_materials_from_geometry(&the_structure, g,
+ use_anisotropic_averaging,
+ sbtol, maxeval, ensure_periodicity,
+ verbose, my_material);
+ fields f(&the_structure);
+
+ /***************************************************************/
+ /* plot structure if requested *********************************/
+ /***************************************************************/
+ if (plot_structure)
+ { char filename[100];
+ snprintf(filename,100,"%s_L%g_p%i",filebase,taper_length,taper_order);
+ h5file *eps_file=f.open_h5file("eps", h5file::WRITE, filename, false);
+ f.output_hdf5(Dielectric,f.total_volume(),eps_file,false,false,0);
+ delete eps_file;
+ }
+
+ /***************************************************************/
+ /* add source */
+ /***************************************************************/
+ double fcen = freq;
+ double df = 0.5*fcen; // bandwidth
+ int nfreq = 1; // number of frequency points
+ gaussian_src_time gsrc(fcen, df);
+
+ double xA = -LX + dpml + 0.5*wvg_length;
+ double xB = +LX - dpml - 0.5*wvg_length;
+ double LYP = LY-dpml;
+ double LZP = three_d ? LZ-dpml : 0.0;
+ volume *fvA, *fvB;
+ double volA, volB;
+ if (three_d)
+ { fvA = new volume( vec(xA, -LYP, -LZP), vec(xA, +LYP, +LZP) );
+ fvB = new volume( vec(xB, -LYP, -LZP), vec(xB, +LYP, +LZP) );
+ volA = 4.0*LYP*LZP;
+ volB = 4.0*LYP*LZP;
+ }
+ else
+ { fvA = new volume( vec(xA, -LYP), vec(xA, +LYP) );
+ fvB = new volume( vec(xB, -LYP), vec(xB, +LYP) );
+ volA = 2.0*LYP;
+ volB = 2.0*LYP;
+ };
+ direction dA = X; // f.normal_direction(*fvA);
+ direction dB = X; // f.normal_direction(*fvB);
+
+ bool match_frequency = true;
+ int parity = 0; // NO_PARITY
+ double tol=1.0e-4;
+ vec kpoint=k_guess((void *)&wA, fcen, band_num);
+ f.add_eigenmode_source(Dielectric, gsrc, dA, *fvA, *fvA, band_num,
+ kpoint, match_frequency, parity, res, tol, 1.0);
+
+ /***************************************************************/
+ /* add flux planes */
+ /***************************************************************/
+ dft_flux fluxA=f.add_dft_flux_plane(*fvA, fcen-0.5*df, fcen+0.5*df, nfreq);
+ dft_flux fluxB=f.add_dft_flux_plane(*fvB, fcen-0.5*df, fcen+0.5*df, nfreq);
+
+ volume fvA1( vec(xA + 0.25*wvg_length, -LYP), vec(xA+0.25*wvg_length, +LYP) );
+ dft_flux fluxA1=f.add_dft_flux_plane(fvA1, fcen-0.5*df, fcen+0.5*df, nfreq);
+ volume fvB1( vec(xB - 0.25*wvg_length, -LYP), vec(xB-0.25*wvg_length, +LYP) );
+ dft_flux fluxB1=f.add_dft_flux_plane(fvB1, fcen-0.5*df, fcen+0.5*df, nfreq);
+
+ /***************************************************************/
+ /* timestep until all conditions for stopping are met. */
+ /* conditions for stopping: */
+ /* (1) sources have expired */
+ /* (2) poynting flux through destination flux plane has */
+ /* decayed below 1% of its maximum value */
+ /***************************************************************/
+ double PVCheckInterval=1.0, PVTol=0.01;
+ double NextPVCheckTime=f.round_time() + PVCheckInterval;
+ double NextFileTime=f.round_time();
+ double MaxPV=0.0;
+ bool Stop=false;
+ while(!Stop)
+ {
+ f.step();
+
+ // do poynting-flux magnitude check at regular time intervals
+ bool FieldsDecayed=false;
+ if ( f.round_time() > NextPVCheckTime )
+ { NextPVCheckTime += PVCheckInterval;
+ double ThisPV = f.flux_in_box(X,*fvB);
+ if (ThisPV > MaxPV)
+ MaxPV = ThisPV;
+ else if ( ThisPV < PVTol*MaxPV )
+ FieldsDecayed=true;
+ }
+
+ // output HDF5 data at regular intervals if user requested that
+ if (frame_interval>0.0 && f.round_time()>NextFileTime)
+ { NextFileTime += frame_interval;
+ f.output_hdf5(Sx,f.total_volume(),0,false,false,filebase);
+ }
+
+ bool SourcesFinished = ( f.round_time() > f.last_source_time() );
+
+ Stop = SourcesFinished && FieldsDecayed;
+ }
+
+ /***************************************************************/
+ /* plot eigenmode field patterns if requested ******************/
+ /***************************************************************/
+ void *mode_data_A, **mode_data_B = new void*[num_bands];
+ if (plot_modes)
+ for(int nb=-1; nb %c%i...\n",AB,band_num);
+ if (am_master())
+ {
+ double vgrp=get_group_velocity(mode_data);
+ vec k=get_k(mode_data);
+ snprintf(filename,100,"%s.modeData",filebase);
+ FILE *ff = fopen(filename,"a");
+ fprintf(ff,"nb=%c%i: \n",AB,band_num);
+ fprintf(ff," vgrp=%e, k=%e\n",vgrp,k.x());
+ fprintf(ff," vgrp*area=%e\n",vgrp*vol);
+ fprintf(ff," mf0={%e,%e}\n",real(mfOverlap[0]),imag(mfOverlap[0]));
+ fprintf(ff," mf1={%e,%e}\n",real(mfOverlap[1]),imag(mfOverlap[1]));
+
+ cdouble alpha[2];
+ alpha[0]=0.5*(mfOverlap[0]+mfOverlap[1])/(vgrp*vol);
+ alpha[1]=0.5*(mfOverlap[0]-mfOverlap[1])/(vgrp*vol);
+
+ fprintf(ff," aP={%e,%e}\n",real(alpha[0]),imag(alpha[0]));
+ fprintf(ff," aM={%e,%e}\n",real(alpha[1]),imag(alpha[1]));
+
+ fclose(ff);
+ };
+
+ }; // if (plot_modes...) ... for(int nb=...)
+
+ /***************************************************************/
+ /* write output files ******************************************/
+ /***************************************************************/
+ if (plot_flux)
+ { char filename[100];
+ snprintf(filename,100,"%s_fluxA",filebase);
+ f.output_flux_fields(fluxA, *fvA, filename);
+ snprintf(filename,100,"%s_fluxB",filebase);
+ f.output_flux_fields(fluxB, *fvB, filename);
+ snprintf(filename,100,"%s.fluxData",filebase);
+ if (am_master)
+ { FILE *ff=fopen(filename,"a");
+ fprintf(ff,"flux A = %e\n",fluxA.flux()[0]);
+ fprintf(ff,"flux A1 = %e\n",fluxA1.flux()[0]);
+ fprintf(ff,"flux B = %e\n",fluxB.flux()[0]);
+ fprintf(ff,"flux B1 = %e\n",fluxB1.flux()[0]);
+ fclose(ff);
+ };
+ };
+
+ /***************************************************************/
+ /* compute mode-expansion coefficients *************************/
+ /***************************************************************/
+ std::vector bands(num_bands);
+ for(int n=0; n vgrp(0);
+ std::vector coeffs =
+ f.get_eigenmode_coefficients(fluxB, dB, *fvB, bands, vgrp, k_guess, (void *)&wB);
+
+ double *Aflux=fluxA1.flux();
+ double *Bflux=fluxB.flux();
+ double *B2flux=fluxB1.flux();
+ if (am_master())
+ {
+ char filename[100];
+ snprintf(filename,100,"%s.coefficients",filebase);
+ FILE *ff=fopen(filename,"a");
+ fprintf(ff,"# fluxA = %e\n",Aflux[0]);
+ fprintf(ff,"# fluxB1 = %e\n",Bflux[0]);
+ fprintf(ff,"# fluxB2 = %e\n",B2flux[0]);
+ printf("freq | band | alpha^+ | alpha^-\n");
+ printf("------------------------------------------------\n");
+ for(int nf=0; nf cdouble;
+
namespace meep {
struct dft_chunk_data { // for passing to field::loop_in_chunks as void*
@@ -493,4 +495,385 @@ dft_flux fields::add_dft_flux_plane(const volume &where,
return add_dft_flux(NO_DIRECTION, where, freq_min, freq_max, Nfreq);
}
+/***************************************************************/
+/***************************************************************/
+/***************************************************************/
+typedef enum { OUTPUT_FLUX, OUTPUT_MODE, MODE_FLUX, MODE_MODE, NO_OP } flux_operation;
+
+cdouble dft_chunk::do_flux_operation(int rank,
+ direction *ds,
+ ivec min_corner,
+ h5file *file,
+ double *buffer,
+ int reim,
+ void *mode1_data,
+ component mode1_c,
+ void *mode2_data,
+ component mode2_c,
+ int num_freq,
+ double flux_sign)
+{
+ /*****************************************************************/
+ /* compute the size of the chunk we own and its strides etc. */
+ /*****************************************************************/
+ int start[3]={0,0,0}, count[3]={1,1,1};
+ int offset[3]={0,0,0}, stride[3]={1,1,1};
+ ivec isS = S.transform(is, sn) + shift;
+ ivec ieS = S.transform(ie, sn) + shift;
+
+ ivec permute(zero_ivec(fc->gv.dim));
+ for (int i = 0; i < 3; ++i)
+ permute.set_direction(fc->gv.yucky_direction(i), i);
+ permute = S.transform_unshifted(permute, sn);
+ LOOP_OVER_DIRECTIONS(permute.dim, d)
+ permute.set_direction(d, abs(permute.in_direction(d)));
+
+ for (int i = 0; i < rank; ++i)
+ { direction d = ds[i];
+ int isd = isS.in_direction(d), ied = ieS.in_direction(d);
+ start[i] = (min(isd, ied) - min_corner.in_direction(d)) / 2;
+ count[i] = abs(ied - isd) / 2 + 1;
+ if (ied < isd) offset[permute.in_direction(d)] = count[i] - 1;
+ };
+
+ for (int i = 0; i < rank; ++i)
+ { direction d = ds[i];
+ int j = permute.in_direction(d);
+ for (int k = i + 1; k < rank; ++k)
+ stride[j] *= count[k];
+ offset[j] *= stride[j];
+ if (offset[j]) stride[j] *= -1;
+ };
+
+ /*****************************************************************/
+ /*****************************************************************/
+ /*****************************************************************/
+ char *s=getenv("MEEP_UNCONJUGATED_INNER_PRODUCT");
+ bool unconjugated_inner_product = (s && s[0]=='1');
+
+ /***************************************************************/
+ /* loop over all grid points in our piece of the volume */
+ /***************************************************************/
+ vec rshift(shift * (0.5*fc->gv.inva));
+ int chunk_idx = 0;
+ cdouble integral=0.0;
+ LOOP_OVER_IVECS(fc->gv, is, ie, idx)
+ {
+ IVEC_LOOP_LOC(fc->gv, loc);
+ loc = S.transform(loc, sn) + rshift;
+ double w = IVEC_LOOP_WEIGHT(s0, s1, e0, e1, dV0 + dV1 * loop_i2);
+ cdouble fluxval = flux_sign*dft[ Nomega*(chunk_idx++) + num_freq];
+ if (include_dV_and_interp_weights)
+ fluxval /= (sqrt_dV_and_interp_weights ? sqrt(w) : w);
+
+ cdouble mode1val=0.0, mode2val=0.0;
+ if (mode1_data)
+ mode1val=eigenmode_amplitude(mode1_data,loc,mode1_c);
+ if (mode2_data)
+ mode2val=eigenmode_amplitude(mode2_data,loc,mode2_c);
+
+ if (file)
+ { int idx2 = ((((offset[0] + offset[1] + offset[2])
+ + loop_i1 * stride[0])
+ + loop_i2 * stride[1])
+ + loop_i3 * stride[2]);
+ cdouble val = (mode1_data ? mode1val : fluxval);
+ buffer[idx2] = reim ? imag(val) : real(val);
+ }
+ else
+ { if (!unconjugated_inner_product)
+ mode1val = conj(mode1val);
+ if (mode2_data)
+ integral += w*mode1val*mode2val;
+ else
+ integral += w*mode1val*fluxval;
+ };
+
+ }; // LOOP_OVER_IVECS(fc->gv, is, ie, idx)
+
+ if (file)
+ file->write_chunk(rank, start, count, buffer);
+
+ return integral;
+
+}
+
+/***************************************************************/
+/* flux_operation is an omnibus routine that serves as the */
+/* computational back end for the following routines: */
+/* output_flux_fields() */
+/* output_mode_fields() */
+/* get_mode_flux_overlap() */
+/* get_mode_mode_overlap() */
+/* */
+/* This routine does one or two things depending on the input. */
+/* */
+/* (A) HDF5 file output (if HDF5FileName is non-null) */
+/* */
+/* (A1) If mode1_data is NULL, write all field components */
+/* stored in flux (at all frequencies) to HDF5 file. */
+/* */
+/* (A2) If mode1_data is non_null, write all field */
+/* components of the eigenmode field described by */
+/* mode1_data to HDF5 file. */
+/* */
+/* (B) Computation of overlap integrals. */
+/* */
+/* (B1) If mode1_data is non-NULL and mode2_data is NULL, */
+/* compute overlap integrals between the eigenmode */
+/* field described by mode1_data and the fields in */
+/* the flux object at the #num_freqth of the */
+/* frequencies for which flux contains data. */
+/* */
+/* (B2) If flux is NULL and mode1_data, mode2_data are */
+/* both non-NULL, compute overlap integrals between */
+/* the eigenmode fields described by mode1_data and */
+/* mode2_data. */
+/* */
+/* overlap integral 1: */
+/* 0.5*( E^\dagger \times H + H^\dagger \times E) */
+/* overlap integral 2: */
+/* 0.5*( E^\dagger \times H - H^\dagger \times E) */
+/***************************************************************/
+void fields::do_flux_operation(dft_flux flux, int num_freq, const volume where,
+ const char *HDF5FileName,
+ void *mode1_data, void *mode2_data,
+ cdouble *overlaps)
+{
+ /***************************************************************/
+ /* look at input arguments to figure out what to do ***********/
+ /***************************************************************/
+ flux_operation flux_ops[4];
+ int num_ops=0;
+ if (HDF5FileName!=0 && mode1_data==0)
+ flux_ops[num_ops++] = OUTPUT_FLUX;
+ if (HDF5FileName!=0 && mode1_data!=0)
+ flux_ops[num_ops++] = OUTPUT_MODE;
+ if (HDF5FileName==0 && mode1_data!=0 && mode2_data==0)
+ flux_ops[num_ops++] = MODE_FLUX;
+ if (HDF5FileName==0 && mode1_data!=0 && mode2_data!=0)
+ flux_ops[num_ops++] = MODE_MODE;
+ if (num_ops==0)
+ abort("no operation specified for do_flux_operation");
+ if (num_ops>1)
+ abort("more than one operation specified for do_flux_operation");
+
+ flux_operation flux_op=flux_ops[0];
+
+ /***************************************************************/
+ /* get statistics on the volume slice **************************/
+ /***************************************************************/
+ int bufsz=0;
+ ivec min_corner = gv.round_vec(where.get_max_corner()) + one_ivec(gv.dim);
+ ivec max_corner = gv.round_vec(where.get_min_corner()) - one_ivec(gv.dim);
+ for (dft_chunk *E=flux.E; E; E=E->next_in_dft)
+ {
+ ivec isS = E->S.transform(E->is, E->sn) + E->shift;
+ ivec ieS = E->S.transform(E->ie, E->sn) + E->shift;
+ min_corner = min(min_corner, min(isS, ieS));
+ max_corner = max(max_corner, max(isS, ieS));
+ int this_bufsz=1;
+ LOOP_OVER_DIRECTIONS(E->fc->gv.dim, d)
+ this_bufsz *= (E->ie.in_direction(d) - E->is.in_direction(d)) / 2 + 1;
+ bufsz = max(bufsz, this_bufsz);
+ };
+ max_corner = max_to_all(max_corner);
+ min_corner = -max_to_all(-min_corner); // i.e., min_to_all
+
+ /***************************************************************/
+ /***************************************************************/
+ /***************************************************************/
+ int rank = 0, dims[3];
+ direction ds[3];
+ LOOP_OVER_DIRECTIONS(gv.dim, d) {
+ if (rank >= 3) abort("too many dimensions in output_hdf5_flux");
+ int n = (max_corner.in_direction(d)
+ - min_corner.in_direction(d)) / 2 + 1;
+ if (n > 1) {
+ ds[rank] = d;
+ dims[rank++] = n;
+ }
+ };
+
+ /***************************************************************/
+ /* buffer for process-local contributions to HDF5 output files,*/
+ /* like h5_output_data::buf in h5fields.cpp */
+ /***************************************************************/
+ realnum *buffer = 0;
+ if (HDF5FileName)
+ buffer = new realnum[bufsz];
+
+ /***************************************************************/
+ /* set up some lists of components over which we will loop. */
+ /* (a) for OUTPUT_MODE, we use all 6 components of E/H fields */
+ /* (b) for OUTPUT_FLUX, we use only the 4 tangential cmpts */
+ /* stored in the flux object */
+ /* (c) for computing overlap integrals (MODE_FLUX / MODE_MODE)*/
+ /* by default we use only 2 tangential components of the */
+ /* mode field (Ex, Ey) together with the 'conjugates' of */
+ /* those components (Hy, Hx) from the flux/mode field; */
+ /* but this may be overridden by setting the */
+ /* MEEP_OVERLAP_ALGORITHM environment variable to choose */
+ /* a different form of the overlap integrand */
+ /***************************************************************/
+ component all_components[6] = {Ex, Ey, Ez, Hx, Hy, Hz};
+ component cE[2]={Ex, Ey}, cH[2]={Hy, Hx};
+ switch (normal_direction(where))
+ { case X: cE[0] = Ey, cE[1] = Ez, cH[0] = Hz, cH[1] = Hy; break;
+ case Y: cE[0] = Ez, cE[1] = Ex, cH[0] = Hx, cH[1] = Hz; break;
+ case R: cE[0] = Ep, cE[1] = Ez, cH[0] = Hz, cH[1] = Hp; break;
+ case P: cE[0] = Ez, cE[1] = Er, cH[0] = Hr, cH[1] = Hz; break;
+ case Z:
+ if (gv.dim == Dcyl)
+ cE[0] = Er, cE[1] = Ep, cH[0] = Hp, cH[1] = Hr;
+ else
+ cE[0] = Ex, cE[1] = Ey, cH[0] = Hy, cH[1] = Hx;
+ break;
+ default: abort("invalid flux component!");
+ };
+
+ // tangential components and their 'conjugates'
+ component tang_components[4];
+ component conj_components[4];
+ tang_components[0] = cE[0]; conj_components[0] = cH[0];
+ tang_components[1] = cE[1]; conj_components[1] = cH[1];
+ tang_components[2] = cH[0]; conj_components[2] = cE[0];
+ tang_components[3] = cH[1]; conj_components[3] = cE[1];
+
+ /***************************************************************/
+ /* set up limits for loops over frequencies, components, etc. */
+ /* */
+ /* which of the frequencies in the flux object will we use? */
+ /* -- if writing flux fields to HDF5: all frequencies */
+ /* -- if computing overlap: only freq #num_freq */
+ /* -- otherwise: flux fields not used */
+ /* */
+ /* which field components will we consider? (see above) */
+ /* -- if writing mode fields to HDF5: all components */
+ /* -- if writing flux fields to HDF5: tangential cmpts only */
+ /* -- if computing an overlap integral: tangential cmpts only */
+ /* */
+ /* loop over real/imaginary parts of field components? */
+ /* -- if writing flux or mode fields to HDF5: yes */
+ /* -- if computing overlap integral: no */
+ /***************************************************************/
+ int nf_min=0, nf_max=0, num_components=0, reim_max=0;
+ switch(flux_op)
+ { case OUTPUT_FLUX:
+ nf_min=0; nf_max=flux.Nfreq-1; num_components=4; reim_max=1;
+ break;
+ case OUTPUT_MODE:
+ nf_min=0; nf_max=0; num_components=6; reim_max=1;
+ break;
+ case MODE_FLUX:
+ nf_min=nf_max=num_freq; num_components=4; reim_max=0;
+ break;
+ case MODE_MODE:
+ nf_min=nf_max=0; num_components=4; reim_max=0;
+ break;
+ case NO_OP:
+ abort("%s:%i: internal error",__FILE__,__LINE__);
+ };
+
+ /***************************************************************/
+ /* Note: for flux_op = OUTPUT_MODE or MODE_MODE, we do not */
+ /* actually use the fields stored in the flux_object. */
+ /* We still need to loop over dft_chunks to visit all */
+ /* points in the flux region, just as is done for */
+ /* OUTPUT_FLUX or FLUX_MODE; but we only need to do this */
+ /* once, not 4 times (once for both tangential components*/
+ /* of E and H). So in this case we set c_flux = the first*/
+ /* tangential component of the E field stored in the */
+ /* flux object and call do_flux_operation only for */
+ /* dft_chunks storing that component. */
+ /***************************************************************/
+ bool append_data = false;
+ bool single_precision = false;
+ bool First = true;
+ cdouble integrals[2] = {0.0, 0.0};
+ for(int nf=nf_min; nf<=nf_max; nf++)
+ for(int nc=0; nccreate_or_extend_data(dataname, rank, dims, append_data, single_precision);
+ };
+
+ // the second component of the E field is stored with a
+ // minus sign in the flux object, which we need to remove
+ double flux_sign = 1.0;
+ if ( (flux_op==OUTPUT_FLUX || flux_op==MODE_FLUX) && (c_flux==cE[1]) )
+ flux_sign = -1.0;
+
+ int which_integral= nc/2;
+ double integral_sign = ( nc%2 ? -1.0 : 1.0 );
+
+ // for debugging purposes, environment variable MEEP_MODE_FLUX_TERM may be set to 0,1,2,3
+ // to retain only the , -, , or contribution to overlap integrals
+ char *s=getenv("MEEP_MODE_FLUX_TERM");
+ if (s && ( nc != (s[0]-'0'))) integral_sign=0.0;
+
+ for (dft_chunk *EH = (c_flux>=Hx ? flux.H : flux.E); EH; EH=EH->next_in_dft)
+ if (EH->c == c_flux)
+ integrals[which_integral]
+ += integral_sign * EH->do_flux_operation(rank, ds, min_corner, file, buffer, reim,
+ mode1_data, c_mode, mode2_data, c_mode2, nf, flux_sign);
+
+ if (file)
+ { file->done_writing_chunks();
+ file->prevent_deadlock(); // hackery
+ delete file;
+ };
+ };
+
+ if (buffer)
+ delete[] buffer;
+
+ if (overlaps)
+ { overlaps[0] = sum_to_all(integrals[0]);
+ overlaps[1] = sum_to_all(integrals[1]);
+ };
+}
+
+/***************************************************************/
+/* entry points to flux_operation for various specific */
+/* calculations */
+/***************************************************************/
+void fields::output_flux_fields(dft_flux flux, const volume where, const char *HDF5FileName)
+{ do_flux_operation(flux, 0, where, HDF5FileName); }
+
+void fields::output_mode_fields(void *mode_data, dft_flux flux, const volume where, const char *HDF5FileName)
+{ do_flux_operation(flux, 0, where, HDF5FileName, mode_data); }
+
+void fields::get_mode_flux_overlap(void *mode_data, dft_flux flux, int num_freq, const volume where, cdouble overlaps[2])
+{ return do_flux_operation(flux, num_freq, where, 0, mode_data, 0, overlaps); }
+
+void fields::get_mode_mode_overlap(void *mode1_data, void *mode2_data, dft_flux flux, const volume where, cdouble overlaps[2])
+{ return do_flux_operation(flux, 0, where, 0, mode1_data, mode2_data, overlaps); }
+
} // namespace meep
diff --git a/src/meep.hpp b/src/meep.hpp
index d3ce73be6..5e971e370 100644
--- a/src/meep.hpp
+++ b/src/meep.hpp
@@ -835,6 +835,17 @@ class dft_chunk {
void scale_dft(std::complex scale);
+ // chunk-by-chunk helper routine called by
+ // fields::do_flux_operation
+ std::complex do_flux_operation(int rank, direction *ds,
+ ivec min_corner, h5file *file,
+ double *buffer, int reim,
+ void *mode1_data,
+ component mode1_c,
+ void *mode2_data,
+ component mode2_c,
+ int num_freq, double flux_sign);
+
void operator-=(const dft_chunk &chunk);
// the frequencies to loop_in_chunks
@@ -1187,6 +1198,13 @@ typedef double (*field_rfunction)(const std::complex *fields,
field_rfunction derived_component_func(derived_component c, const grid_volume &gv,
int &nfields, component cs[12]);
+/***************************************************************/
+/* prototype for optional user-supplied function to provide an */
+/* initial estimate of the wavevector of band #band at */
+/* frequency freq for eigenmode calculations */
+/***************************************************************/
+typedef vec (*kpoint_func)(void *user_data, double freq, int band);
+
class fields {
public:
int num_chunks;
@@ -1363,8 +1381,19 @@ class fields {
void require_component(component c);
// mpb.cpp
+
+ // the return value of get_eigenmode is an opaque pointer
+ // that can be passed to eigenmode_amplitude() to get
+ // values of field components at arbitrary points in space.
+ // call destroy_eigenmode_data() to deallocate it when finished.
+ void *get_eigenmode(double omega_src, direction d, const volume where,
+ const volume eig_vol, int band_num,
+ const vec &kpoint, bool match_frequency=true,
+ int parity=0, double resolution=0.0,
+ double eigensolver_tol=1.0e-7, bool verbose=false);
+
void add_eigenmode_source(component c, const src_time &src,
- direction d, const volume &where,
+ direction d, const volume &where,
const volume &eig_vol,
int band_num,
const vec &kpoint, bool match_frequency,
@@ -1373,6 +1402,14 @@ class fields {
std::complex amp,
std::complex A(const vec &) = 0);
+ std::vector< std::complex >
+ get_eigenmode_coefficients(dft_flux flux, direction d,
+ const volume &where,
+ std::vector bands,
+ std::vector &vgrp,
+ kpoint_func k_func=0,
+ void *k_func_data=0);
+
// initialize.cpp:
void initialize_field(component, std::complex f(const vec &));
void initialize_with_nth_te(int n);
@@ -1450,6 +1487,32 @@ class fields {
dft_flux add_dft_flux(const volume_list *where,
double freq_min, double freq_max, int Nfreq);
+ /********************************************************/
+ /* "flux operations" include things like */
+ /* (1) exporting dft_flux fields to HDF5 files */
+ /* (2) computing eigenmode decomposition coefficients */
+ /* of dft_flux fields */
+ /* these are calculations that involve similar loops */
+ /* over chunks, etc, so we consolidate them into a */
+ /* single omnibus routine (do_flux_operation) with */
+ /* multiple entry points for particular calculations. */
+ /********************************************************/
+ void do_flux_operation(dft_flux flux, int num_freq, const volume where,
+ const char *HDF5FileName,
+ void *mode1_data=0, void *mode2_data=0,
+ std::complex *integrals=0);
+ void output_flux_fields(dft_flux flux, const volume where,
+ const char *HDF5FileName);
+ void output_mode_fields(void *mode_data, dft_flux flux,
+ const volume where,
+ const char *HDF5FileName);
+ void get_mode_flux_overlap(void *mode_data, dft_flux flux, int num_freq,
+ const volume where,
+ std::complexoverlaps[2]);
+ void get_mode_mode_overlap(void *mode_data, void *mode2_data, dft_flux flux,
+ const volume where,
+ std::complexoverlaps[2]);
+
// stress.cpp
dft_force add_dft_force(const volume_list *where,
double freq_min, double freq_max, int Nfreq);
@@ -1634,6 +1697,15 @@ void green3d(std::complex *EH, const vec &x,
double freq, double eps, double mu,
const vec &x0, component c0, std::complex f0);
+// non-class methods for working with mpb eigenmode data
+//
+void destroy_eigenmode_data(void *vedata);
+std::complex eigenmode_amplitude(void *vedata,
+ const vec &p,
+ component c);
+double get_group_velocity(void *vedata);
+vec get_k(void *vedata);
+
} /* namespace meep */
#endif /* MEEP_H */
diff --git a/src/mpb.cpp b/src/mpb.cpp
index 9299e8dab..d3561c02b 100644
--- a/src/mpb.cpp
+++ b/src/mpb.cpp
@@ -15,7 +15,9 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+#include
#include
+#include
#include "meep.hpp"
#include "config.h"
@@ -28,6 +30,8 @@
using namespace std;
+typedef complex cdouble;
+
namespace meep {
#ifdef HAVE_MPB
@@ -61,26 +65,76 @@ static void meep_mpb_eps(symmetric_matrix *eps,
maxwell_sym_matrix_invert(eps, eps_inv);
}
-static const complex *meep_mpb_A_data = 0;
-static const int *meep_mpb_A_n = 0;
-static const double *meep_mpb_A_s = 0;
-static int meep_mpb_A_component = 0;
-static vec meep_mpb_A_center;
-static complex one(const vec &pt) {(void) pt; return 1.0;}
-static complex (*meep_mpb_A_A)(const vec &) = 0;
-static complex meep_mpb_A(const vec &p) {
- const complex *data = meep_mpb_A_data + meep_mpb_A_component;
- int nx = meep_mpb_A_n[0];
- int ny = meep_mpb_A_n[1];
- int nz = meep_mpb_A_n[2];
- const double *s = meep_mpb_A_s;
+/**************************************************************/
+/* prototype for position-dependent amplitude function passed */
+/* to add_volume_source */
+/**************************************************************/
+typedef complex (*amplitude_function)(const vec &);
+
+// default implementation of amplitude_function
+static complex default_amp_func(const vec &pt)
+ {(void) pt; return 1.0;}
+
+/*******************************************************************/
+/* structure storing all data needed to compute position-dependent */
+/* amplitude for eigenmode source (the fields of this structure */
+/* were formerly global variables) */
+/*******************************************************************/
+typedef struct eigenmode_data
+ {
+ maxwell_data *mdata;
+ scalar_complex *fft_data_H, *fft_data_E;
+ evectmatrix H;
+ int n[3];
+ double s[3];
+ double k[3];
+ vec center;
+ amplitude_function amp_func;
+ int band_num;
+ double omega;
+ double group_velocity;
+ } eigenmode_data;
+
+/*******************************************************************/
+/* compute position-dependent amplitude for eigenmode source */
+/* (similar to the routine formerly called meep_mpb_A) */
+/*******************************************************************/
+complex eigenmode_amplitude(void *vedata, const vec &p,
+ component c)
+{
+ eigenmode_data *edata = (eigenmode_data *)vedata;
+ if ( !edata || !(edata->mdata) )
+ abort("%s:%i: internal error",__FILE__,__LINE__);
+
+ int *n = edata->n;
+ double *s = edata->s;
+ vec center = edata->center;
+ amplitude_function amp_func = edata->amp_func;
+
+ complex *cdata = (complex *)( (c>=Hx) ? edata->fft_data_H : edata->fft_data_E );
+ const complex *data;
+ switch(c)
+ {
+ case Ex: cdata = (complex *)edata->fft_data_E; data = cdata + 0; break;
+ case Ey: cdata = (complex *)edata->fft_data_E; data = cdata + 1; break;
+ case Ez: cdata = (complex *)edata->fft_data_E; data = cdata + 2; break;
+ case Hx: cdata = (complex *)edata->fft_data_H; data = cdata + 0; break;
+ case Hy: cdata = (complex *)edata->fft_data_H; data = cdata + 1; break;
+ case Hz: cdata = (complex *)edata->fft_data_H; data = cdata + 2; break;
+ default:
+ abort("invalid component in eigenmode_amplitude");
+ };
+
+ int nx = n[0];
+ int ny = n[1];
+ int nz = n[2];
double r[3] = {0,0,0};
- vec p0(p - meep_mpb_A_center);
- LOOP_OVER_DIRECTIONS(p.dim, d) r[d%3] = p0.in_direction(d) / s[d%3] + 0.5;
+ vec p0(p - center);
+ LOOP_OVER_DIRECTIONS(p.dim, d)
+ r[d%3] = p0.in_direction(d) / s[d%3] + 0.5;
double rx = r[0], ry = r[1], rz = r[2];
/* linearly interpolate the amplitude from MPB at point p */
-
int x, y, z, x2, y2, z2;
double dx, dy, dz;
@@ -116,29 +170,61 @@ static complex meep_mpb_A(const vec &p) {
#undef D
return (complex(double(real(ret)), double(imag(ret)))
- * meep_mpb_A_A(p));
+ * amp_func(p));
}
-#endif /* HAVE_MPB */
+/***************************************************************/
+/* entry point to eigenmode_amplitude with the right prototype */
+/* for passage as the A parameter to add_volume_source */
+/***************************************************************/
+static eigenmode_data *global_eigenmode_data=0;
+static component global_eigenmode_component;
+static complex meep_mpb_A(const vec &p)
+{ return eigenmode_amplitude((void *)global_eigenmode_data, p,
+ global_eigenmode_component);
+}
-void fields::add_eigenmode_source(component c0, const src_time &src,
- direction d, const volume &where,
- const volume &eig_vol,
- int band_num,
- const vec &kpoint, bool match_frequency,
- int parity,
- double resolution, double eigensolver_tol,
- complex amp,
- complex A(const vec &)) {
-#ifdef HAVE_MPB
- if (resolution <= 0) resolution = 2 * gv.a; // default to twice resolution
+/****************************************************************/
+/* call MPB to get the band_numth eigenmode at freq omega_src. */
+/* */
+/* this routine constitutes the first 75% of what was formerly */
+/* add_eigenmode_source; it has been split off as a separate */
+/* routine to allow it to be followed either by */
+/* (a) add_eigenmode_src() */
+/* or */
+/* (b) get_eigenmode_coefficient() */
+/* */
+/* the return value is an opaque pointer to an eigenmode_data */
+/* structure (needs to be opaque to allow compilation without */
+/* MPB, in which case maxwell_data and other types aren't */
+/* defined). this structure may then be passed to */
+/* eigenmode_amplitude (above) to compute eigenmode E and H */
+/* field components at arbitrary points in space. */
+/* call destroy_eigenmode_data() to deallocate when finished. */
+/****************************************************************/
+void *fields::get_eigenmode(double omega_src,
+ direction d, const volume where,
+ const volume eig_vol,
+ int band_num,
+ const vec &kpoint, bool match_frequency,
+ int parity,
+ double resolution,
+ double eigensolver_tol,
+ bool verbose)
+{
+ /*--------------------------------------------------------------*/
+ /*- part 1: preliminary setup for calling MPB -----------------*/
+ /*--------------------------------------------------------------*/
+
+ //bool verbose=true;
+ if (resolution <= 0.0) resolution = 2 * gv.a; // default to twice resolution
int n[3], local_N, N_start, alloc_N, mesh_size[3] = {1,1,1};
mpb_real k[3] = {0,0,0}, kcart[3] = {0,0,0};
double s[3] = {0,0,0}, o[3] = {0,0,0};
mpb_real R[3][3] = {{0,0,0},{0,0,0},{0,0,0}};
mpb_real G[3][3] = {{0,0,0},{0,0,0},{0,0,0}};
mpb_real kdir[3] = {0,0,0};
- double omega_src = real(src.frequency()), kscale = 1.0;
+ double kscale = 1.0;
double match_tol = eigensolver_tol * 10;
if (d == NO_DIRECTION || coordinate_mismatch(gv.dim, d))
@@ -168,6 +254,10 @@ void fields::add_eigenmode_source(component c0, const src_time &src,
s[1] = eig_vol.in_direction(Y);
k[0] = kpoint.in_direction(X);
k[1] = kpoint.in_direction(Y);
+ // the following line was missing from the original mpb.cpp,
+ // but I think it's needed! Consider a waveguide of
+ // constant (x,y) cross section with power flow in the z direction.
+ //k[2] = kpoint.in_direction(Z);
break;
case D1:
o[2] = eig_vol.in_direction_min(Z);
@@ -178,17 +268,20 @@ void fields::add_eigenmode_source(component c0, const src_time &src,
abort("unsupported dimensionality in add_eigenmode_source");
}
- master_printf("KPOINT: %g, %g, %g\n", k[0], k[1], k[2]);
+ if (!quiet && verbose)
+ master_printf("KPOINT: %g, %g, %g\n", k[0], k[1], k[2]);
// if match_frequency is true, all we need is a direction for k
// and a crude guess for its value; we must supply this if k==0.
if (match_frequency && k[0] == 0 && k[1] == 0 && k[2] == 0) {
k[d-X] = omega_src * sqrt(get_eps(eig_vol.center()));
- master_printf("NEW KPOINT: %g, %g, %g\n", k[0], k[1], k[2]);
+ if(!quiet && verbose)
+ master_printf("NEW KPOINT: %g, %g, %g\n", k[0], k[1], k[2]);
if (s[d-X] > 0) {
k[d-X] *= s[d-X]; // put k in G basis (inverted when we compute kcart)
if (fabs(k[d-X]) > 0.4) // ensure k is well inside the Brillouin zone
k[d-X] = k[d-X] > 0 ? 0.4 : -0.4;
+ if(!quiet && verbose)
master_printf("NEWER KPOINT: %g, %g, %g\n", k[0], k[1], k[2]);
}
}
@@ -258,6 +351,12 @@ void fields::add_eigenmode_source(component c0, const src_time &src,
mpb_real knew[3]; for (int i = 0; i < 3; ++i) knew[i] = k[i];
+ mpb_real vgrp; // Re( W[0]* (dTheta/dk) W[0] ) = group velocity
+
+ /*--------------------------------------------------------------*/
+ /*- part 2: newton iteration loop with call to MPB on each step */
+ /*- until eigenmode converged to requested tolerance */
+ /*--------------------------------------------------------------*/
do {
eigensolver(H, eigvals, maxwell_operator, (void *) mdata,
#if MPB_VERSION_MAJOR > 1 || (MPB_VERSION_MAJOR == 1 && MPB_VERSION_MINOR >= 6)
@@ -269,7 +368,7 @@ void fields::add_eigenmode_source(component c0, const src_time &src,
W, 3,
eigensolver_tol, &num_iters,
EIGS_DEFAULT_FLAGS |
- (am_master() && !quiet ? EIGS_VERBOSE : 0));
+ (am_master() && verbose && !quiet ? EIGS_VERBOSE : 0));
if (!quiet)
master_printf("MPB solved for omega_%d(%g,%g,%g) = %g after %d iters\n",
band_num, knew[0],knew[1],knew[2],
@@ -284,19 +383,18 @@ void fields::add_eigenmode_source(component c0, const src_time &src,
// compute the group velocity in the k direction
maxwell_ucross_op(W[0], W[1], mdata, kdir); // W[1] = (dTheta/dk) W[0]
- mpb_real v, vscratch; // v = Re( W[0]* (dTheta/dk) W[0] ) = g. velocity
- evectmatrix_XtY_diag_real(W[0], W[1], &v, &vscratch);
- v /= sqrt(eigvals[band_num - 1]);
+ mpb_real vscratch;
+ evectmatrix_XtY_diag_real(W[0], W[1], &vgrp, &vscratch);
+ vgrp /= sqrt(eigvals[band_num - 1]);
// return to original size
evectmatrix_resize(&W[0], band_num, 0);
evectmatrix_resize(&W[1], band_num, 0);
// update k via Newton step
- kscale = kscale - (sqrt(eigvals[band_num - 1]) - omega_src) / (v*klen0);
- if (!quiet)
- master_printf("Newton step: group velocity v=%g, kscale=%g\n",
- v, kscale);
+ kscale = kscale - (sqrt(eigvals[band_num - 1]) - omega_src) / (vgrp*klen0);
+ if (!quiet && verbose)
+ master_printf("Newton step: group velocity v=%g, kscale=%g\n", vgrp, kscale);
if (kscale < 0 || kscale > 100)
abort("Newton solver not converging -- need a better starting kpoint");
for (int i = 0; i < 3; ++i) knew[i] = k[i] * kscale;
@@ -306,20 +404,20 @@ void fields::add_eigenmode_source(component c0, const src_time &src,
&& fabs(sqrt(eigvals[band_num - 1]) - omega_src) >
omega_src * match_tol);
+ if (!match_frequency)
+ omega_src = sqrt(eigvals[band_num - 1]);
+
+ // cleanup temporary storage
+ delete[] eigvals;
evect_destroy_constraints(constraints);
for (int i = 0; i < 3; ++i)
destroy_evectmatrix(W[i]);
- src_time *src_mpb = src.clone();
- if (!match_frequency)
- src_mpb->set_frequency(omega_src = sqrt(eigvals[band_num - 1]));
-
+ /*--------------------------------------------------------------*/
+ /*- part 3: do one stage of postprocessing to tabulate H-field */
+ /*- components on the internal storage buffer in mdata */
+ /*--------------------------------------------------------------*/
complex *cdata = (complex *) mdata->fft_data;
- meep_mpb_A_s = s;
- meep_mpb_A_n = n;
- meep_mpb_A_data = cdata;
- meep_mpb_A_center = eig_vol.center() - where.center();
- meep_mpb_A_A = A ? A : one;
maxwell_compute_h_from_H(mdata, H, (scalar_complex*)cdata, band_num - 1, 1);
/* choose deterministic phase, maximizing power in real part;
@@ -347,62 +445,286 @@ void fields::add_eigenmode_source(component c0, const src_time &src,
for (i = 0; i < H.n; ++i) hdata[i*H.p + (band_num-1)] *= phase;
}
+ /*--------------------------------------------------------------*/
+ /* do a second round of post-processing to tabulate E-fields -*/
+ /* on a (separate) internal storage buffer. (Previously -*/
+ /* there was only one internal buffer which held either E-field */
+ /* or H-field data, but this is inconvenient for cases in which */
+ /* you want the E and H fields of an eigenmode simultaneously.) */
+ /*--------------------------------------------------------------*/
+ int NFFT = 3*mdata->fft_output_size;
+ scalar_complex *fft_data_E=(scalar_complex *)malloc(NFFT*sizeof(scalar_complex));
+
+ maxwell_compute_d_from_H(mdata, H, fft_data_E, band_num - 1, 1);
+ // d_from_H actually computes -omega*D (see mpb/src/maxwell/maxwell_op.c)
+ double scale = -1.0 / omega_src;
+ cdouble *efield=(cdouble *)fft_data_E;
+ for (int n = 0; n < NFFT; ++n)
+ efield[n] *= scale;
+
+ maxwell_compute_e_from_d(mdata, fft_data_E, 1);
+
+ /*--------------------------------------------------------------*/
+ /*- part 4: initialize and return output data structures. */
+ /*--------------------------------------------------------------*/
+ eigenmode_data *edata = new eigenmode_data;
+ edata->mdata = mdata;
+ edata->fft_data_H = mdata->fft_data;
+ edata->fft_data_E = fft_data_E;
+ edata->H = H;
+ edata->n[0] = n[0];
+ edata->n[1] = n[1];
+ edata->n[2] = n[2];
+ edata->s[0] = s[0];
+ edata->s[1] = s[1];
+ edata->s[2] = s[2];
+ edata->k[0] = knew[0];
+ edata->k[1] = knew[1];
+ edata->k[2] = knew[2];
+ edata->center = eig_vol.center() - where.center();
+ edata->amp_func = default_amp_func;
+ edata->band_num = band_num;
+ edata->omega = omega_src;
+ edata->group_velocity = (double) vgrp;
+ return (void *)edata;
+}
+
+void destroy_eigenmode_data(void *vedata)
+{
+ eigenmode_data *edata = (eigenmode_data *)vedata;
+ destroy_evectmatrix( edata->H );
+ destroy_maxwell_data( edata->mdata );
+ free(edata->fft_data_E);
+ delete edata;
+}
+
+double get_group_velocity(void *vedata)
+{ eigenmode_data *edata = (eigenmode_data *)vedata;
+ return edata->group_velocity;
+}
+
+vec get_k(void *vedata)
+{ eigenmode_data *edata = (eigenmode_data *)vedata;
+ return vec(edata->k[0], edata->k[1], edata->k[2]);
+}
+
+/***************************************************************/
+/* helper routine for add_eigenmode_source that calls */
+/* add_volume_source only if certain conditions are met */
+/***************************************************************/
+void add_volume_source_check(component c, const src_time &src, const volume &where,
+ cdouble A(const vec &), cdouble amp,
+ fields *f, component c0, direction d, int parity)
+{
+ if (!f->gv.has_field(c)) return;
+ if (c0!=Centered && c0!=c) return;
+ if (component_direction(c)==d) return;
+ if (f->gv.dim==D2) // parity checks
+ {
+ if ( (parity&EVEN_Z_PARITY) && is_tm(c) ) return;
+ if ( (parity&ODD_Z_PARITY) && !is_tm(c) ) return;
+ };
+ f->add_volume_source(c, src, where, A, amp);
+}
+
+/***************************************************************/
+/* call get_eigenmode() to solve for the specified eigenmode, */
+/* then call add_volume_source() to add current sources whose */
+/* radiated fields reproduce the eigenmode fields */
+/***************************************************************/
+void fields::add_eigenmode_source(component c0, const src_time &src,
+ direction d, const volume &where,
+ const volume &eig_vol,
+ int band_num,
+ const vec &kpoint, bool match_frequency,
+ int parity,
+ double resolution, double eigensolver_tol,
+ complex amp,
+ complex A(const vec &)) {
+
+ /*--------------------------------------------------------------*/
+ /* step 1: call MPB to compute the eigenmode */
+ /*--------------------------------------------------------------*/
+ double omega_src = real(src.frequency());
+ global_eigenmode_data
+ =(eigenmode_data *)get_eigenmode(omega_src, d, where,
+ eig_vol, band_num,
+ kpoint, match_frequency,
+ parity, resolution,
+ eigensolver_tol);
+
+ global_eigenmode_data->amp_func = A ? A : default_amp_func;
+
+ src_time *src_mpb = src.clone();
+ if (!match_frequency)
+ src_mpb->set_frequency(omega_src);
+
+ /*--------------------------------------------------------------*/
+ // step 2: add sources whose radiated field reproduces the */
+ // the eigenmode */
+ // electric current K = nHat \times H */
+ // magnetic current N = -nHat \times E */
+ /*--------------------------------------------------------------*/
if (is_D(c0)) c0 = direction_component(Ex, component_direction(c0));
if (is_B(c0)) c0 = direction_component(Hx, component_direction(c0));
-
- // use principle of equivalence to obtain equivalent currents
- FOR_ELECTRIC_COMPONENTS(c)
- if (gv.has_field(c) && (c0 == Centered || c0 == c)
- && component_direction(c) != d
- && (gv.dim != D2 || !(parity & (EVEN_Z_PARITY | ODD_Z_PARITY))
- || ((parity & EVEN_Z_PARITY) && !is_tm(c))
- || ((parity & ODD_Z_PARITY) && is_tm(c)))) {
- // E current source = d x (eigenmode H)
- if ((d + 1) % 3 == component_direction(c) % 3) {
- meep_mpb_A_component = (d + 2) % 3;
- add_volume_source(c, *src_mpb, where, meep_mpb_A, -amp);
- }
- else {
- meep_mpb_A_component = (d + 1) % 3;
- add_volume_source(c, *src_mpb, where, meep_mpb_A, amp);
- }
- }
-
- maxwell_compute_d_from_H(mdata, H, (scalar_complex*)cdata, band_num - 1, 1);
- { // d_from_H actually computes -omega*D (see mpb/src/maxwell/maxwell_op.c)
- double scale = -1.0 / omega_src;
- int N = mdata->fft_output_size * 3;
- for (int i = 0; i < N; ++i) cdata[i] *= scale;
- }
- maxwell_compute_e_from_d(mdata, (scalar_complex*)cdata, 1);
- // use principle of equivalence to obtain equivalent currents
- FOR_MAGNETIC_COMPONENTS(c)
- if (gv.has_field(c) && (c0 == Centered || c0 == c)
- && component_direction(c) != d
- && (gv.dim != D2 || !(parity & (EVEN_Z_PARITY | ODD_Z_PARITY))
- || ((parity & EVEN_Z_PARITY) && !is_tm(c))
- || ((parity & ODD_Z_PARITY) && is_tm(c)))) {
- // H current source = - d x (eigenmode E)
- if ((d + 1) % 3 == component_direction(c) % 3) {
- meep_mpb_A_component = (d + 2) % 3;
- add_volume_source(c, *src_mpb, where, meep_mpb_A, amp);
- }
- else {
- meep_mpb_A_component = (d + 1) % 3;
- add_volume_source(c, *src_mpb, where, meep_mpb_A, -amp);
- }
- }
+ component cE[3]={Ex, Ey, Ez}, cH[3]={Hx, Hy, Hz};
+ int n = (d==X ? 0 : (d==Y ? 1 : 2));
+ int np1 = (n+1)%3;
+ int np2 = (n+2)%3;
+ // Kx = -Hy, Ky = Hx (for d==Z)
+ global_eigenmode_component = cH[np1];
+ add_volume_source_check(cE[np2], *src_mpb, where, meep_mpb_A, +1.0*amp, this, c0, d, parity);
+ global_eigenmode_component = cH[np2];
+ add_volume_source_check(cE[np1], *src_mpb, where, meep_mpb_A, -1.0*amp, this, c0, d, parity);
+ // Nx = +Ey, Ny = -Ex (for d==Z)
+ global_eigenmode_component = cE[np1];
+ add_volume_source_check(cH[np2], *src_mpb, where, meep_mpb_A, -1.0*amp, this, c0, d, parity);
+ global_eigenmode_component = cE[np2];
+ add_volume_source_check(cH[np1], *src_mpb, where, meep_mpb_A, +1.0*amp, this, c0, d, parity);
delete src_mpb;
- destroy_evectmatrix(H);
- delete[] eigvals;
- destroy_maxwell_data(mdata);
-#else /* !defined(HAVE_MPB) */
- (void) c0; (void) src; (void) d; (void) where; (void) eig_vol; (void) band_num;
- (void) kpoint; (void) match_frequency; (void) parity; (void) resolution;
- (void) eigensolver_tol; (void) amp; (void) A;
+ destroy_eigenmode_data( (void *)global_eigenmode_data);
+}
+
+/***************************************************************/
+/* get eigenmode coefficients for all frequencies in flux */
+/* and all band indices in the caller-populated bands array. */
+/* */
+/* the array returned has length num_freqs x num_bands, with */
+/* the positive/ negative coefficients for frequency #nf, */
+/* band #nb stored in slot [ 2*nb*num_freqs + 2*nf + 0/1 ] */
+/***************************************************************/
+std::vector
+ fields::get_eigenmode_coefficients(dft_flux flux, direction d,
+ const volume &where,
+ std::vector bands,
+ std::vector &vgrp,
+ kpoint_func k_func,
+ void *k_func_data)
+{
+ double freq_min = flux.freq_min;
+ double dfreq = flux.dfreq;
+ int num_freqs = flux.Nfreq;
+ int num_bands = bands.size();
+ bool match_frequency = true;
+ int parity = 0; // NO_PARITY
+ double resolution = a;
+ double eig_tol = 1.0e-4;
+ std::vector coeffs( 2 * num_freqs * num_bands );
+
+ char *LogFile=getenv("MEEP_EIGENMODE_LOGFILE");
+
+ vgrp.resize(num_bands*num_freqs);
+
+ // loop over all bands and all frequencies
+ for(int nb=0; nb amp,
+ complex A(const vec &)) {
+ (void) c0; (void) src; (void) d; (void) where; (void) eig_vol;
+ (void) band_num; (void) kpoint; (void) match_frequency;
+ (void) parity; (void) resolution; (void) eigensolver_tol;
+ (void) amp; (void) A;
abort("Meep must be configured/compiled with MPB for add_eigenmode_source");
-#endif
}
+std::vector fields::get_eigenmode_coefficients(dft_flux flux,
+ direction d,
+ const volume &where,
+ std::vector bands,
+ std::vector vgrp,
+ kpoint_func k_func,
+ void *k_func_data)
+{ (void) flux; (void) d; (void) where; (void) bands,
+ (void) vgrp; (void) k_func; (void) k_func_data;
+ abort("Meep must be configured/compiled with MPB for get_eigenmode_coefficient");
+}
+
+void destroy_eigenmode_data(void *vedata)
+{ (void) vedata; }
+
+std::complex eigenmode_amplitude(void *vedata,
+ const vec &p,
+ component c)
+{ (void) vedata; (void) p; (void) c; return 0.0; }
+
+double get_group_velocity(void *vedata)
+{ (void) vedata; return 0.0; }
+
+vec get_k(void *vedata)
+{ (void) vedata; return vec(0.0,0.0,0.0); }
+
+#endif // HAVE_MPB
+
} // namespace meep