diff --git a/doc/docs/Python_User_Interface.md b/doc/docs/Python_User_Interface.md index 2103d3c14..0ac7c7d98 100644 --- a/doc/docs/Python_User_Interface.md +++ b/doc/docs/Python_User_Interface.md @@ -1050,9 +1050,9 @@ Given a frequency `omega` and a `Vector3` `pt`, returns the average eigenvalue o — Initialize the component `c` fields using the function `func` which has a single argument, a `Vector3` giving a position and returns a complex number for the value of the field at that point. -**`add_dft_fields(cs, freq_min, freq_max, nfreq, freq, where=None, center=None, size=None, yee_grid=False)`** +**`add_dft_fields(cs, fcen, df, nfreq, freq, where=None, center=None, size=None, yee_grid=False)`** — -Given a list of field components `cs`, compute the Fourier transform of these fields for `nfreq` equally spaced frequencies covering the frequency range `freq_min` to `freq_max` or a NumPy array `freq` for arbitrarily spaced frequencies over the `Volume` specified by `where` (default to the entire cell). The volume can also be specified via the `center` and `size` arguments. The default routine interpolates the Fourier transformed fields at the center of each voxel within the specified volume. Alternatively, the exact Fourier transformed fields evaluated at each corresponding Yee grid point is available by setting `yee_grid` to `True`. +Given a list of field components `cs`, compute the Fourier transform of these fields for `nfreq` equally spaced frequencies covering the frequency range `fcen-df/2` to `fcen+df/2` or an array/list `freq` for arbitrarily spaced frequencies over the `Volume` specified by `where` (default to the entire cell). The volume can also be specified via the `center` and `size` arguments. The default routine interpolates the Fourier transformed fields at the center of each voxel within the specified volume. Alternatively, the exact Fourier transformed fields evaluated at each corresponding Yee grid point is available by setting `yee_grid` to `True`. **`flux_in_box(dir, box=None, center=None, size=None)`** — @@ -1141,7 +1141,7 @@ Given a bunch of [`FluxRegion`](#fluxregion) objects, you can tell Meep to accum **`add_flux(fcen, df, nfreq, freq, FluxRegions...)`** — -Add a bunch of `FluxRegion`s to the current simulation (initializing the fields if they have not yet been initialized), telling Meep to accumulate the appropriate field Fourier transforms for `nfreq` equally spaced frequencies covering the frequency range `fcen-df/2` to `fcen+df/2` or a NumPy array `freq` for arbitrarily spaced frequencies. Return a *flux object*, which you can pass to the functions below to get the flux spectrum, etcetera. +Add a bunch of `FluxRegion`s to the current simulation (initializing the fields if they have not yet been initialized), telling Meep to accumulate the appropriate field Fourier transforms for `nfreq` equally spaced frequencies covering the frequency range `fcen-df/2` to `fcen+df/2` or an array/list `freq` for arbitrarily spaced frequencies. Return a *flux object*, which you can pass to the functions below to get the flux spectrum, etcetera. As described in the tutorial, you normally use `add_flux` via statements like: @@ -1273,7 +1273,7 @@ A weight factor to multiply the energy density by when it is computed. Default i **`Simulation.add_energy(fcen, df, nfreq, freq, EnergyRegions...)`** — -Add a bunch of `EnergyRegion`s to the current simulation (initializing the fields if they have not yet been initialized), telling Meep to accumulate the appropriate field Fourier transforms for `nfreq` equally spaced frequencies covering the frequency range `fcen-df/2` to `fcen+df/2` or a NumPy array `freq` for arbitrarily spaced frequencies. Return an *energy object*, which you can pass to the functions below to get the energy spectrum, etcetera. +Add a bunch of `EnergyRegion`s to the current simulation (initializing the fields if they have not yet been initialized), telling Meep to accumulate the appropriate field Fourier transforms for `nfreq` equally spaced frequencies covering the frequency range `fcen-df/2` to `fcen+df/2` or an array/list `freq` for arbitrarily spaced frequencies. Return an *energy object*, which you can pass to the functions below to get the energy spectrum, etcetera. As for energy regions, you normally use `add_energy` via statements like: @@ -1355,7 +1355,7 @@ In most circumstances, you should define a set of `ForceRegion`s whose union is **`Simulation.add_force(fcen, df, nfreq, freq, ForceRegions...)`** — -Add a bunch of `ForceRegion`s to the current simulation (initializing the fields if they have not yet been initialized), telling Meep to accumulate the appropriate field Fourier transforms for `nfreq` equally spaced frequencies covering the frequency range `fcen-df/2` to `fcen+df/2` or a NumPy array `freq` for arbitrarily spaced frequencies. Return a `force`object, which you can pass to the functions below to get the force spectrum, etcetera. +Add a bunch of `ForceRegion`s to the current simulation (initializing the fields if they have not yet been initialized), telling Meep to accumulate the appropriate field Fourier transforms for `nfreq` equally spaced frequencies covering the frequency range `fcen-df/2` to `fcen+df/2` or an array/list `freq` for arbitrarily spaced frequencies. Return a `force`object, which you can pass to the functions below to get the force spectrum, etcetera. As for force regions, you normally use `add_force` via statements like: @@ -1415,7 +1415,7 @@ Meep can also calculate the LDOS (local density of states) spectrum, as describe **`Ldos(fcen, df, nfreq, freq)`** — -Create an LDOS object with either frequency bandwidth `df` centered at `fcen`, at `nfreq` equally spaced frequency points or a NumPy array `freq` for arbitrarily spaced frequencies. This can be passed to the `dft_ldos` step function below, and has the properties `freq_min`, `nfreq` and `dfreq`. +Create an LDOS object with either frequency bandwidth `df` centered at `fcen`, at `nfreq` equally spaced frequency points or an array/list `freq` for arbitrarily spaced frequencies. This can be passed to the `dft_ldos` step function below, and has the properties `freq_min`, `nfreq` and `dfreq`. **`get_ldos_freqs(ldos)`** — @@ -1423,7 +1423,7 @@ Given an LDOS object, returns a list of the frequencies that it is computing the **`dft_ldos(fcen=None, df=None, nfreq=None, freq=None, ldos=None)`** — -Compute the power spectrum of the sources (usually a single point dipole source), normalized to correspond to the LDOS, in either a frequency bandwidth `df` centered at `fcen`, at `nfreq` equally spaced frequency points or a NumPy array `freq` for arbitrarily spaced frequencies. One can also pass in an `ldos` created with `DftLdos` as `dft_ldos(ldos=my_ldos)`. +Compute the power spectrum of the sources (usually a single point dipole source), normalized to correspond to the LDOS, in either a frequency bandwidth `df` centered at `fcen`, at `nfreq` equally spaced frequency points or an array/list `freq` for arbitrarily spaced frequencies. One can also pass in an `ldos` created with `DftLdos` as `dft_ldos(ldos=my_ldos)`. The resulting spectrum is outputted as comma-delimited text, prefixed by `ldos:,`, and is also stored in the `ldos_data` variable of the `Simulation` object after the `run` is complete. @@ -1445,7 +1445,7 @@ There are three steps to using the near-to-far-field feature: first, define the **`add_near2far(fcen, df, nfreq, freq, Near2FarRegions..., nperiods=1)`** — -Add a bunch of `Near2FarRegion`s to the current simulation (initializing the fields if they have not yet been initialized), telling Meep to accumulate the appropriate field Fourier transforms for `nfreq` equally spaced frequencies covering the frequency range `fcen-df/2` to `fcen+df/2` or a NumPy array `freq` for arbitrarily spaced frequencies. Return a `near2far` object, which you can pass to the functions below to get the far fields. +Add a bunch of `Near2FarRegion`s to the current simulation (initializing the fields if they have not yet been initialized), telling Meep to accumulate the appropriate field Fourier transforms for `nfreq` equally spaced frequencies covering the frequency range `fcen-df/2` to `fcen+df/2` or an array/list `freq` for arbitrarily spaced frequencies. Return a `near2far` object, which you can pass to the functions below to get the far fields. Each `Near2FarRegion` is identical to `FluxRegion` except for the name: in 3d, these give a set of planes (**important:** all these "near surfaces" must lie in a single *homogeneous* material with *isotropic* ε and μ — and they should *not* lie in the PML regions) surrounding the source(s) of outgoing radiation that you want to capture and convert to a far field. Ideally, these should form a closed surface, but in practice it is sufficient for the `Near2FarRegion`s to capture all of the radiation in the direction of the far-field points. **Important:** as for flux computations, each `Near2FarRegion` should be assigned a `weight` of ±1 indicating the direction of the outward normal relative to the +coordinate direction. So, for example, if you have six regions defining the six faces of a cube, i.e. the faces in the +x, -x, +y, -y, +z, and -z directions, then they should have weights +1, -1, +1, -1, +1, and -1 respectively. Note that, neglecting discretization errors, all near-field surfaces that enclose the same outgoing fields are equivalent and will yield the same far fields with a discretization-induced difference that vanishes with increasing resolution etc. diff --git a/python/simulation.py b/python/simulation.py index ef0431161..a65335dbc 100644 --- a/python/simulation.py +++ b/python/simulation.py @@ -1634,15 +1634,15 @@ def _evaluate_dft_objects(self): def add_dft_fields(self, *args, **kwargs): components = args[0] - if isinstance(args[1],(int,float)) and isinstance(args[2],(int,float)) and isinstance(args[3],int): - freq_min = args[1] - freq_max = args[2] + if len(args) == 4 and isinstance(args[1],(int,float)) and isinstance(args[2],(int,float)) and isinstance(args[3],int): + fcen = args[1] + df = args[2] nfreq = args[3] - freq = [0.5*(freq_min+freq_max)] if nfreq == 1 else np.linspace(freq_min,freq_max,nfreq) - elif isinstance(args[1],(np.ndarray,list)): + freq = [fcen] if nfreq == 1 else np.linspace(fcen-0.5*df,fcen+0.5*df,nfreq) + elif len(args) == 2 and isinstance(args[1],(np.ndarray,list)): freq = args[1] else: - raise TypeError("add_dft_fields only accepts freq_min,freq_max,nfreq (3 numbers) or freq (array/list)") + raise TypeError("add_dft_fields only accepts fcen,df,nfreq (3 numbers) or freq (array/list)") where = kwargs.get('where', None) center = kwargs.get('center', None) size = kwargs.get('size', None) @@ -1681,13 +1681,13 @@ def get_dft_data(self, dft_chunk): return arr def add_near2far(self, *args, **kwargs): - if isinstance(args[0],(int,float)) and isinstance(args[1],(int,float)) and isinstance(args[2],int): + if len(args) > 3 and isinstance(args[0],(int,float)) and isinstance(args[1],(int,float)) and isinstance(args[2],int): fcen = args[0] df = args[1] nfreq = args[2] freq = [fcen] if nfreq == 1 else np.linspace(fcen-0.5*df,fcen+0.5*df,nfreq) near2fars = args[3:] - elif isinstance(args[0],np.ndarray): + elif len(args) > 1 and isinstance(args[0],np.ndarray): freq = args[0] near2fars = args[1:] else: @@ -1809,13 +1809,13 @@ def load_minus_near2far_data(self, n2f, n2fdata): n2f.scale_dfts(complex(-1.0)) def add_force(self, *args): - if isinstance(args[0],(int,float)) and isinstance(args[1],(int,float)) and isinstance(args[2],int): + if len(args) > 3 and isinstance(args[0],(int,float)) and isinstance(args[1],(int,float)) and isinstance(args[2],int): fcen = args[0] df = args[1] nfreq = args[2] freq = [fcen] if nfreq == 1 else np.linspace(fcen-0.5*df,fcen+0.5*df,nfreq) forces = args[3:] - elif isinstance(args[0],np.ndarray): + elif len(args) > 1 and isinstance(args[0],np.ndarray): freq = args[0] forces = args[1:] else: @@ -1862,13 +1862,13 @@ def load_minus_force_data(self, force, fdata): force.scale_dfts(complex(-1.0)) def add_flux(self, *args): - if isinstance(args[0],(int,float)) and isinstance(args[1],(int,float)) and isinstance(args[2],int): + if len(args) > 3 and isinstance(args[0],(int,float)) and isinstance(args[1],(int,float)) and isinstance(args[2],int): fcen = args[0] df = args[1] nfreq = args[2] freq = [fcen] if nfreq == 1 else np.linspace(fcen-0.5*df,fcen+0.5*df,nfreq) fluxes = args[3:] - elif isinstance(args[0],np.ndarray): + elif len(args) > 1 and isinstance(args[0],np.ndarray): freq = args[0] fluxes = args[1:] else: @@ -1883,13 +1883,13 @@ def _add_flux(self, freq, fluxes): return self._add_fluxish_stuff(self.fields.add_dft_flux, freq, fluxes) def add_mode_monitor(self, *args): - if isinstance(args[0],(int,float)) and isinstance(args[1],(int,float)) and isinstance(args[2],int): + if len(args) > 3 and isinstance(args[0],(int,float)) and isinstance(args[1],(int,float)) and isinstance(args[2],int): fcen = args[0] df = args[1] nfreq = args[2] freq = [fcen] if nfreq == 1 else np.linspace(fcen-0.5*df,fcen+0.5*df,nfreq) fluxes = args[3:] - elif isinstance(args[0],np.ndarray): + elif len(args) > 1 and isinstance(args[0],np.ndarray): freq = args[0] fluxes = args[1:] else: @@ -3026,12 +3026,12 @@ def output_sfield_p(sim): def Ldos(*args): - if isinstance(args[0],(int,float)) and isinstance(args[1],(int,float)) and isinstance(args[2],int): + if len(args) == 3 and isinstance(args[0],(int,float)) and isinstance(args[1],(int,float)) and isinstance(args[2],int): fcen = args[0] df = args[1] nfreq = args[2] freq = [fcen] if nfreq == 1 else np.linspace(fcen-0.5*df,fcen+0.5*df,nfreq) - elif isinstance(args[0],np.ndarray): + elif len(args) == 1 and isinstance(args[0],np.ndarray): freq = args[0] else: raise TypeError("Ldos only accepts fcen,df,nfreq (3 numbers) or freq (array/list)") @@ -3041,18 +3041,18 @@ def Ldos(*args): def dft_ldos(*args, **kwargs): ldos = kwargs.get('ldos', None) - if len(args) < 3 and ldos is None: - raise ValueError("dft_ldos accepts either fcen,df,nfreq (3 numbers) or an ldos object (keyword argument)") if ldos is None: - if isinstance(args[0],(int,float)) and isinstance(args[1],(int,float)) and isinstance(args[2],int): + if len(args) == 3 and isinstance(args[0],(int,float)) and isinstance(args[1],(int,float)) and isinstance(args[2],int): fcen = args[0] df = args[1] nfreq = args[2] freq = [fcen] if nfreq == 1 else np.linspace(fcen-0.5*df,fcen+0.5*df,nfreq) - elif isinstance(args[0],np.ndarray): + elif len(args) == 1 and isinstance(args[0],(np.ndarray,list)): freq = args[0] + elif len(args) == 1: + raise ValueError("dft_ldos accepts either fcen,df,nfreq (3 numbers) or freq (array/list) or an ldos object (keyword argument)") else: - raise TypeError("dft_ldos only accepts fcen,df,nfreq (3 numbers) or freq (array/list)") + raise TypeError("dft_ldos only accepts fcen,df,nfreq (3 numbers) or freq (array/list) or an ldos object (keyword argument)") ldos = mp._dft_ldos(freq) def _ldos(sim, todo): diff --git a/python/tests/array_metadata.py b/python/tests/array_metadata.py index 2b07ae27d..8df7070ab 100644 --- a/python/tests/array_metadata.py +++ b/python/tests/array_metadata.py @@ -69,7 +69,7 @@ def vec_func(r): symmetries=symmetries, boundary_layers=pml_layers) - dft_obj = sim.add_dft_fields([mp.Ez], fcen, fcen, 1, where=nonpml_vol) + dft_obj = sim.add_dft_fields([mp.Ez], fcen, 0, 1, where=nonpml_vol) sim.run(until_after_sources=100) Ez = sim.get_dft_array(dft_obj, mp.Ez, 0) diff --git a/python/tests/dft_fields.py b/python/tests/dft_fields.py index 35899445c..fd737a9cf 100644 --- a/python/tests/dft_fields.py +++ b/python/tests/dft_fields.py @@ -45,22 +45,22 @@ def init(self): def test_use_centered_grid(self): sim = self.init() sim.init_sim() - dft_fields = sim.add_dft_fields([mp.Ez], self.fcen, self.fcen, 1, yee_grid=True) + dft_fields = sim.add_dft_fields([mp.Ez], self.fcen, 0, 1, yee_grid=True) sim.run(until=100) - + def test_get_dft_array(self): sim = self.init() sim.init_sim() - dft_fields = sim.add_dft_fields([mp.Ez], self.fcen, self.fcen, 1) + dft_fields = sim.add_dft_fields([mp.Ez], self.fcen, 0, 1) fr = mp.FluxRegion(mp.Vector3(), size=mp.Vector3(self.sxy, self.sxy), direction=mp.X) dft_flux = sim.add_flux(self.fcen, 0, 1, fr) # volumes with zero thickness in x and y directions to test collapsing # of empty dimensions in DFT array and HDF5 output routines thin_x_volume = mp.Volume(center=mp.Vector3(0.35*self.sxy), size=mp.Vector3(y=0.8*self.sxy)) - thin_x_flux = sim.add_dft_fields([mp.Ez], self.fcen, self.fcen, 1, where=thin_x_volume) + thin_x_flux = sim.add_dft_fields([mp.Ez], self.fcen, 0, 1, where=thin_x_volume) thin_y_volume = mp.Volume(center=mp.Vector3(y=0.25*self.sxy), size=mp.Vector3(x=self.sxy)) - thin_y_flux = sim.add_flux(self.fcen, self.fcen, 1, mp.FluxRegion(volume=thin_y_volume)) + thin_y_flux = sim.add_flux(self.fcen, 0, 1, mp.FluxRegion(volume=thin_y_volume)) sim.run(until_after_sources=100) diff --git a/python/tests/modal_volume_metadata.py b/python/tests/modal_volume_metadata.py index 5a949250d..84133d9b0 100644 --- a/python/tests/modal_volume_metadata.py +++ b/python/tests/modal_volume_metadata.py @@ -114,7 +114,7 @@ def get_modal_volume(sim, box=None, dft_cell=None, nf=0): # mv_box is the region within which we compute modal volume mv_box_size = mp.Vector3(L_inner, L_inner, mp.inf) mv_box = mp.Volume(center=origin, size=mv_box_size) -dft_cell = sim.add_dft_fields([mp.Ex, mp.Ey, mp.Ez], fcen-df, fcen+df, 3, where=mv_box) +dft_cell = sim.add_dft_fields([mp.Ex, mp.Ey, mp.Ez], fcen, 2*df, 3, where=mv_box) # Timestep until the sources are finished, pausing at fixed intervals to # compare the modal volume within mv_box as computed from time-domain fields diff --git a/python/tests/n2f_periodic.py b/python/tests/n2f_periodic.py index e47b8b375..c9171369f 100644 --- a/python/tests/n2f_periodic.py +++ b/python/tests/n2f_periodic.py @@ -50,7 +50,7 @@ def test_nea2far_periodic(self): symmetries=symmetries) n2f_obj = sim.add_near2far(fcen, 0, 1, mp.Near2FarRegion(center=n2f_pt, size=mp.Vector3(y=sy)), nperiods=10) - dft_obj = sim.add_dft_fields([mp.Ez], fcen, fcen, 1, center=dft_pt, size=mp.Vector3(y=sy)) + dft_obj = sim.add_dft_fields([mp.Ez], fcen, 0, 1, center=dft_pt, size=mp.Vector3(y=sy)) sim.run(until_after_sources=300)