File: Field_Functions.md

package info (click to toggle)
meep-mpi-default 1.17.1-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 51,672 kB
  • sloc: cpp: 29,881; python: 17,210; lisp: 1,225; makefile: 477; sh: 249; ansic: 133; javascript: 5
file content (170 lines) | stat: -rw-r--r-- 9,294 bytes parent folder | download | duplicates (6)
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
---
# Field Functions
---

As described in [Python Interface](Python_User_Interface.md), Meep provides several routines to integrate, analyze, and output arbitrary user-defined functions of the field components. See the functions whose names end with `_field_function`. This facility, while powerful, requires a bit more programming than most Meep usage, and is best illustrated by a few examples.

Note: field functions can be applied to time-domain and [frequency-domain](Python_User_Interface.md#frequency-domain-solver) fields.

[TOC]

Arbitrary Functions
-------------------

Every field-function that can be passed to these routines is of the form *f*(**r**,components...), where **r** is a position vector and "components..." are zero or more field components that the function depends on. The set of desired components is user-specified. As an example, suppose we are interested in the arbitrary function:

$$f(\mathbf{r}, E_x, H_z, \varepsilon) = x |\mathbf{r}| + E_x - \varepsilon H_z$$

We would define this function by:

**Python**
```py
def f(r, ex, hz, eps):
    return (r.x * r.norm() + ex) - (eps * hz)
```

**Scheme**
```scm
(define (f r ex hz eps)
   (- (+ (* (vector3-x r) (vector3-norm r)) ex) (* eps hz)))
```

Note that the (mandatory) first argument `r` is a [`Vector3`](Python_User_Interface.md#vector3) (Python) or [`vector3`](https://libctl.readthedocs.io/en/latest/User_Reference) (Scheme) object.

Now, suppose we want to compute the integral of this function, over the whole cell. We can do this by calling the function `integrate_field_function` (Python) or `integrate-field-function` (Scheme), as follows:

**Python**
```py
print("The integral of our weird function is: {}"
	   .format(meep.Simulation.integrate_field_function([meep.Ex, meep.Hz, meep.Dielectric], f)))
```

**Scheme**
```scm
(print "The integral of our weird function is: "
       (integrate-field-function (list Ex Hz Dielectric) f) "\n")
```

Note that the first argument to `integrate_field_function` (Python) or `integrate-field-function` (Scheme) is a list, which is a standard type, of `component` constants, specifying in order the list of field components the function `f` expects to be passed. Meep will then call `f` for every point in the cell in parallel on a parallel machine, and return the integral approximated by a [trapezoidal rule](https://en.wikipedia.org/wiki/trapezoidal_rule).

You can also specify an optional third argument to `integrate_field_function` (Python) or `integrate-field-function` (Scheme), specifying an integration volume in case you don't want the integral over the whole cell. For example, the following code computes the integral of `f` along a line from (-1,0,0) to (1,0,0):

**Python**
```py
print("The integral of our weird function from (-1,0,0) to (1,0,0) is: {}"
	   .format(meep.Simulation.integrate_field_function([meep.Ex, meep.Hz, meep.Dielectric], f, meep.Volume(size=meep.Vector3(2,0,0), center=meep.Vector3(0,0,0)))))
```

**Scheme**
```scm
(print "The integral of our weird function from (-1,0,0) to (1,0,0) is: "
       (integrate-field-function (list Ex Hz Dielectric) f (volume (size 2 0 0) (center 0 0 0))) "\n")
```

Maximum Absolute Value
----------------------

Instead of computing the integral, Meep also provides a function to compute the maximum absolute value of our given function:

**Python**
```py
print("The maximum absolute value of our weird function from (-1,0,0) to (1,0,0) is: {}"
	   .format(meep.Simulation.max_abs_field_function([meep.Ex, meep.Hz, meep.Dielectric], f, meep.Volume(size=meep.Vector3(2,0,0), center=meep.Vector3(0,0,0)))))
```

**Scheme**
```scm
(print "The maximum absolute value of our weird function from (-1,0,0) to (1,0,0) is: "
       (max-abs-field-function (list Ex Hz Dielectric) f (volume (size 2 0 0) (center 0 0 0))) "\n")
```

Outputting to an HDF5 File
--------------------------

We can also output our function to an HDF5 file, similar to the built-in functions to output selected field components, and so on. The following outputs an HDF5 file consisting of our function `f` evaluated at every point in the cell:

**Python**
```py
meep.Simulation.output_field_function("weird-function", [meep.Ex, meep.Hz, meep.Dielectric], f)
```

**Scheme**
```scm
(output-field-function "weird-function" (list Ex Hz Dielectric) f)
```

The first argument is used for the name of the dataset within the HDF5, and is also used for the name of the HDF5 file itself plus a `.h5` suffix and a time stamp, unless you have specified the output file via `to_appended` (Python) or `to-appended` (Scheme) or other means.

The above example calls the integration, maximum, and output routines only once, at the current time. Often, you will want to pass them to `meep.Simulation.run(..., until=...)` (Python) or `run-until` (Scheme) instead, using `at_every` (Python) or `at-every` (Scheme) to print or output at periodic time intervals. A common mistake is to do something like the following:

**Python**
```py
meep.Simulation.run(
        mp.at_every(1, meep.Simulation.output_field_function("weird-function", [meep.Ex, meep.Hz, meep.Dielectric], f)),
        until=200)
```

**Scheme**
```scm
(run-until 200
        (at-every 1 (output-field-function "weird-function" (list Ex Hz Dielectric) f)))
```

This is **wrong**, and will cause Meep to exit with a strange error message. The reason is that the step functions you pass to `meep.Simulation.run` (Python) or `run-until` (Scheme) must be *functions*. For example, if you call `meep.Simulation.run(meep.output_hfield, until=200)` (Python) or `(run-until 200 output-hfield)` (Scheme),`output_hfield` (Python) or  `output-hfield` (Scheme)  is the name of a *function* which `meep.Simulation.run` (Python) or `run-until` (Scheme) will call to output the field. The incorrect code above, however, first *calls* the function `output_field_function` (Python) or `output-field-function` (Scheme) to output an HDF5 file, and then passes the *result* of this function to `meep.Simulation.run` (Python) or `run-until` (Scheme). Instead, you must write a new function which you can pass to `meep.Simulation.run` (Python) or `run-until` (Scheme), like the following:

**Python**
```py
def my_weird_output(sim):
    meep.Simulation.output_field_function("weird-function", [meep.Ex, meep.Hz, meep.Dielectric], f)

meep.Simulation.run(meep.at_every(1,my_weird_output), until=200)
```

**Scheme**
```scm
(define (my-weird-output)
	(output-field-function "weird-function" (list Ex Hz Dielectric) f))

(run-until 200 (at-every 1 my-weird-output))
```

We have defined a function `my_weird_output` (Python) of one argument (the simulation instance) and `my-weird-output` (Scheme) of no arguments that, when called, outputs our function `f`. We then pass this function to `meep.Simulation.run` (Python) or `run-until` (Scheme). In contrast, our incorrect code above corresponds to passing `my_weird_output(t)` (Python) or `(my-weird-output)` (Scheme), the *result* of calling `my_weird_output` to `meep.Simulation.run` (Python) or `my-weird-output` to `run-until` (Scheme).

As described in [Synchronizing the Magnetic and Electric Fields](Synchronizing_the_Magnetic_and_Electric_Fields.md), because this example function combines electric and magnetic fields, we may want to synchronize them in time in order to compute this function more accurately, by wrapping it with `synchronized_magnetic` (Python) or `synchronized-magnetic` (Scheme):

**Python**
```py
meep.Simulation.run(meep.synchronized_magnetic(meep.at_every(1,my_weird_output)), until=200)
```

**Scheme**
```scm
(run-until 200 (synchronized-magnetic (at-every 1 my-weird-output)))
```

For more information, see [Python Interface/Writing Your Own Step Functions](Python_User_Interface.md#writing-your-own-step-functions) or [Scheme Interface/Writing Your Own Step Functions](Scheme_User_Interface.md#writing-your-own-step-functions).

Coordinates of the Yee Grid
---------------------------

As a final example, the function `integrate_field_function` (Python) or `integrate-field-function` (Scheme) can be used to obtain the coordinates of the Yee grid. As long as the field arguments are on the *same* grid (e.g., $E_x$ and $D_x$, $E_y$ and $D_y$, etc.), the integral is computed over the exact Yee grid coordinates rather than being interpolated to the center of each grid point if fields from *different* grids are used (consistent with Meep's paradigm of [pervasive interpolation](Introduction.md#the-illusion-of-continuity)).

**Python**
```py
def f(r,fc):
    print("({:.5f}, {:.5f}, {:.5f})".format(r.x,r.y,r.z))
    return 0

meep.Simulation.integrate_field_function([mp.Ex],f)
```

**Scheme**
```scm
(define (f r fc)
  (begin
    (print "(" (vector3-x r) ", " (vector3-y r) ", " (vector3-z r) ")\n")
    0))
(integrate-field-function (list Ex) f)
```

This function prints the $(x,y,z)$ Yee grid coordinates of all $E_x$ fields and returns a value of 0 which is never used. In contrast, the output functions `output_field_function` (Python) or `output-field-function` (Scheme) (as well as `output-real-field-function`) interpolate *all* fields onto the center of each grid point.