File: Chunks_and_Symmetry.md

package info (click to toggle)
meep-openmpi 1.25.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 64,556 kB
  • sloc: cpp: 32,214; python: 27,958; lisp: 1,225; makefile: 505; sh: 249; ansic: 131; javascript: 5
file content (302 lines) | stat: -rw-r--r-- 14,983 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
---
# Chunks and Symmetry
---

As described in [Computer Physics Communications, Vol. 181, pp. 687-702 (2010)](http://ab-initio.mit.edu/~oskooi/papers/Oskooi10.pdf),
Meep subdivides geometries into **chunks**. Each chunk is a contiguous region of space — a line, rectangle, or parallelepiped for 1d/2d/3d Cartesian geometries, or an annular section in a cylindrical geometry—whose sizes are automatically determined by `libmeep.` In [parallel calculations](Parallel_Meep.md), each chunk is assigned, in its entirety, to precisely one process — that is, no chunk exists partly on one processor and partly on another.

Many internal operations in Meep consist of looping over points in the
[Yee grid](Yee_Lattice.md), generally performing some operation involving the field
components and material parameters at each point.
In principle, this involves nested `for` loops; in practice,
it is complicated by several factors, including the following:

+ For calculations that [exploit symmetry](Exploiting_Symmetry.md), only a
portion of the full grid is actually stored in memory, and obtaining values
for field components at a point that *isn't* stored requires a tricky
procedure discussed below.

+ Similarly, for Bloch-periodic geometries, only grid points in the unit cell
are stored, but we may want the fields at a point lying outside the unit cell,
again requiring a bit of a shell game to process correctly.

+ Because of the staggered nature of the [Yee grid](Yee_Lattice.md),
"looping over grid points" can mean multiple things — are we visiting
only **E**-field sites, or only **H**-field sites, or both? Either
way, obtaining a full set of field-component values at any one grid point
necessarily involves a certain average over neighboring grid points.

To shield developers from the need to grapple with these complications
when implementing loops over grid points, `libmeep` provides
a convenient routine called `loop_in_chunks` and a set of macros
that take care of many of the above hassles. This is discussed in more
detail below.

[TOC]

## Chunk Data Structures

For each chunk in a geometry, `libmeep` creates instances of
the data structures `structure_chunk` (storing data on the
geometry of the chunk and the material properties at grid
points in the chunk) and `fields_chunk` (storing the actual values
of the time-domain field components at grid points in the chunk).
Frequency-domain (DFT) field components are handled by a separate
data structure called `dft_chunk.` Each instance of `dft_chunk`
is associated with a single instance of `fields_chunk` (namely,
whichever one stores the time-domain fields at the grid points
covered by the DFT chunk); however, because DFT fields are typically
only tabulated on a subset of the full grid, the grid volume
covered by a `dft_chunk` may be only a subset of the volume
covered by its parent `fields_chunk`, and not all `fields_chunks`
have `dft_chunks` associated with them.

## Chunking of a 2d Geometry

Our running example throughout this page will be a 2d geometry,
of dimensions $(L_x, L_y)=(8,6)$, with PML layers of thickness 1
on all sides, discretized with 5 points per unit length
to yield a 40 × 30 grid.

### Chunking in the Single-Processor Case

In a single-processor run, `libmeep` subdivides this geometry
into 9 chunks (click for larger image):

<p align="center"> <a href="../images/Chunks_NP1.png"><img src="../images/Chunks_NP1.png" style='height: 100%; width: 100%; object-fit: contain'/></a></p>

The width of the 8 chunks around the perimeter is set by the PML thickness.

Note that the chunks are not of uniform sizes and that their ordering is somewhat arbitrary. In particular, consecutive chunks are *not* necessarily adjacent.

#### Chunk Statistics

As noted above, each chunk is a contiguous region of space defined
by a Cartesian product of intervals for each coordinate; to specify
the extents of the chunk it thus suffices to specify the endpoints
of the interval for each coordinate, or equivalently the coordinates
of the lower-left and upper-right grid points in the chunk. For each
chunk, these are represented by `ivecs` named `is` and `ie`
(stored in the `fields_chunk` and `dft_chunk` structures).

Here's an example of how this looks for chunk 3 in the figure above:

<p align="center"> <a href="../images/ChunkStatistics.png"><img src="../images/ChunkStatistics.png" style='height: 100%; width: 100%; object-fit: contain'/></a></p>

In this case we have `is=(29,-29)` and `ie=(39,-19)`.

### Chunking in the Multiprocessor Case

When running in parallel mode, each of the chunks identified for the
single-processor case may be further subdivided into new chunks which
can be assigned to different processors.
For example, on a run with 8 processors, the 9 chunks identified
in the single-processor case become 24 chunks:

<p align="center"> <a href="../images/Chunks_NP8.png"><img src="../images/Chunks_NP8.png" style='height: 100%; width: 100%; object-fit: contain'/></a></p>

In this image, grid points with different $z$ coordinates (different
heights off the $xy$ plane) are handled by different processors,
while points with the same $z$ coordinate but different colors
live in different chunks. In this case, processes 0, 2, 5, and 7
each own 4 chunks, while processes 1, 3, 4, and 6 each own 2 chunks.

## Symmetries

Meep's approach to handling symmetries is discussed
from the user's perspective in [Exploiting Symmetry](Exploiting_Symmetry.md)
and from a high-level algorithmic perspective in [Computer Physics Communications, Vol. 181, pp. 687-702 (2010)](http://ab-initio.mit.edu/~oskooi/papers/Oskooi10.pdf).

The following is a brief synopsis of the implementation of this feature.

+ The action of the symmetry group classifies grid points into *orbits,*
  sets of grid points that transform into one another under symmetry
  transformations. For example, in the figure with XY mirror symmetry below,
  the orbit of
  $\mathbf{x}_a$ is $\{\mathbf{x}_a, \mathbf{x}_b, \mathbf{x}_c, \mathbf{x}_d\}$.
  Meep chooses one element from each orbit (the "parent" grid point)
  to serve as a representative of the orbit, with the remaining elements of
  the orbit classified as "children" equivalent to the parent under
  the group action.
  (Any point in the orbit could serve equally well as the parent;
   the convention in meep is to choose the point with the lowest (most negative)
   grid indices, i.e the point closest to the lower-left corner of the overall
   grid---$\mathbf{x}_a$ in this case---but nothing depends on this particular choice.)

+ For each orbit, field components are only stored for the parent, not for
  any children. This reduces memory requirements by a factor $M$,
  the number of points in each orbit, known in meep as the ``multiplicity''
  of the symmetry; for example, $M=2$ for a geometry with *Y*-mirror symmetry,
  $M=4$ for an *XY*-mirror symmetry, $M=N$ for an *N*-fold rotational
  symmetry, etc.

+ Loops over grid points run only over parent points, i.e. points with field
  components stored in memory. However, each parent point is now visited
  $M$ times, once for each distinct symmetry transformation
  $\mathcal{S}_m (m=1,\cdots,M) $ in the symmetry group
  (including the identity transformation). On the $m$th visit to a given
  parent point $\mathbf{x}_p$, we

    **(1)** look up the components of the fields $\mathbf{E}_p, \mathbf{H}_p$
            stored in memory for $\mathbf{x}_p$,

    **(2)** apply the transformation $\mathcal{S}_m$ to both the grid-point
            coordinates and the field components of the parent point to yield
            the coordinates and field components of the $m$th child point, i.e.
            $$       \mathbf{x}_{cm} = \mathcal{S}_m \mathbf{x}_p,
              \quad \mathbf{E}_{cm} = \mathcal{S}_m \mathbf{E}_p,
              \quad \mathbf{H}_{cm} = \mathcal{S}_m \mathbf{H}_p.
            $$
           If the materials are anisotropic (i.e. the permittivity and/or permeability
           are tensors) we must transform those appropriately as well.

    **(3)** use the coordinates and field components of the child point to carry
            out the operation in question.

### Chunking in the Presence of Symmetries

As noted above, in the presence of symmetries only a portion of the
full grid is actually stored in memory. For example, adding a $y$ mirror
symmetry (symmetry under reflection about the $x$-axis) eliminates
points in the upper half-plane $(y>0)$; the points that remain are
now subdivided into 6 chunks (in the single-processor case):

<p align="center"> <a href="../images/Chunks_YSymmetry.png"><img src="../images/Chunks_YSymmetry.png" style='height: 100%; width: 100%; object-fit: contain'/></a></p>

Adding an $x$ mirror symmetry on top of this (so that now the geometry has
both $x$ and $y$ mirror symmetry) reduces the number of stored grid points by an
additional factor of 2; now the geometry is subdivided into just 4 chunks
in the single-processor case:

<p align="center"> <a href="../images/Chunks_XYSymmetry.png"><img src="../images/Chunks_XYSymmetry.png" style='height: 100%; width: 100%; object-fit: contain'/></a></p>

In these figures, points in shaded regions are "children" &mdash; that is, points
for which Meep stores no field components, since they are related by symmetry
to "parent" points in the unshaded region. In the second figure we have
indicated one complete orbit: parent point $\mathbf{x}_a$ is carried to
child points $\{\mathbf{x}_b, \mathbf{x}_c, \mathbf{x}_d\}$ under the
operations of the symmetry group.

### Coordinates and Field Components of Symmetry-Reduced Points

Symmetry transformations in `libmeep` are described by a class called
simply `symmetry,` which offers class methods for transforming grid
points and field components:

```c++

  symmetry S = mirror(X,gv) + mirror(Y,gv);    // XY mirror symmetry

  ivec iparent;                                // grid indices of parent point
  vec  rparent;                                // cartesian coordinates of parent point

  ...

  ivec ichild = S.transform(iparent, +1);      // grid indices of child point
   vec rchild = S.transform(rparent, +1);      // cartesian coordinates of child point

  component cchild  = Ex;                      // desired field component at child point
  component cparent = S.transform(cchild, -1); // corresponding component at parent point
```

## The `loop_in_chunks` Routine

To allow developers to implement loops over grid points without
stressing out over the various complications outlined above,
the `fields` class in `libmeep` offers a convenient method called
`loop_in_chunks.`

To use this routine, you will first write a "chunk-loop function"
which carries out some operation involving grid points and (optionally)
field components at grid points. Then you pass your routine to
`loop_in_chunks` with some additional arguments customizing the
type of loop you want (see below). Your loop function will then be
called once for every `chunk` in the problem---including both
chunks whose fields are present in memory, and those
whose aren't due to being eliminated by symmetry---with a long list
of arguments describing the chunk in question.

The body of your chunk-loop function will typically want to execute
a loop over all grid points in the chunk. This is facilitated by a host
of utility macros and API functions that operate on the arguments to your function
to yield quantities of interest: grid-point coordinates, field-component
values, etc.

### The Chunk Loop Function

The chunk-loop function that you write and pass to `loop_in_chunks`
has the following prototype:

```c++
void field_chunkloop(fields_chunk *fc, int ichunk, component cgrid, ivec is, ivec ie,
                     vec s0, vec s1, vec e0, vec e1, double dV0, double dV1,
                     ivec shift, std::complex<double> shift_phase,
	             const symmetry &S, int sn, void *chunkloop_data);
```

Notwithstanding this formidable-looking beast of a calling convention,
most of the arguments here are things that you can blindly pass on to
API functions and convenience macros, which will return quantities whose
significance is easy to understand.

Here's a skeleton chunk-loop function
that executes a loop over all grid points in the chunk,
obtaining on each loop iteration both the integer indices and the cartesian
coordinates of the child point, as well as values for a list
of field components of interest (specified before the loop in the
constructor of `chunkloop_field_components`). You can fill in the rest of the loop body
to do whatever you want with `ichild,` `rchild,` and `data.values,` and the
results will be identical whether or not you declare symmetries when
defining your geometry. (Well, the results will be identical assuming
the physical problem you're considering really is symmetric, which Meep [does not check](Exploiting_Symmetry.md).)

```c++
void my_chunkloop(fields_chunk *fc, int ichunk, component cgrid, ivec is, ivec ie,
                  vec s0, vec s1, vec e0, vec e1, double dV0, double dV1,
                  ivec shift, std::complex<double> shift_phase,
	          const symmetry &S, int sn, void *chunkloop_data)
{
  // some preliminary setup
  vec rshift(shift * (0.5*fc->gv.inva));  // shift into unit cell for PBC geometries

  // prepare the list of field components to fetch at each grid point
  component components[] = {Ex, Hz};
  chunkloop_field_components data(fc, cgrid, shift_phase, S, sn, 2, components);

  // loop over all grid points in chunk
  LOOP_OVER_IVECS(fc->gv, is, ie, idx)
   {
     // get grid indices and coordinates of parent point
     IVEC_LOOP_ILOC(gv, iparent);  // grid indices
     IVEC_LOOP_LOC(gv, rparent);   // cartesian coordinates

     // apply symmetry transform to get grid indices and coordinates of child point
     ivec ichild = S.transform(iparent, sn) + shift;
     vec rchild = S.transform(rparent, sn) + rshift;

     // fetch field components at child point
     data.update_values(idx);
     std::complex<double> Ex = data.values[0],
                          Hz = data.values[1];
   }
}
```

## Is There a Version of `loop_in_chunks` for `dft_chunks`?

No, but the routine `process_dft_component()` in `src/dft.cpp` effectively
implements such a routine for a hard-coded set of operations on DFT
components (namely: outputting to HDF5, fetching DFT array slices,
and computing mode-overlap coefficients).

## How the Images were Created

The images above were obtained with the help of a simple C++ code
called `WriteChunkInfo` that calls `libmeep` API functions to
obtain info on the chunk structure of the 40&times;30 grid
we considered. This code (plus a simple hand-written `Makefile`)
lives in the `doc/docs/Developer_Codes` subdirectory of the meep source
distribution.

+ [`WriteChunkInfo.cpp`](/Developer_Codes/WriteChunkInfo.cpp)
+ [`Makefile.manual`](/Developer_Codes/Makefile.manual)