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: + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
TaperMovie
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