Skip to content

Commit 259d1c4

Browse files
caseyflexmomchil-flex
authored andcommitted
Changed flux and DFT definitions with complex fields to only use real part, and fixed TFSF flux leakage bug
1 parent 5c63398 commit 259d1c4

File tree

8 files changed

+38
-29
lines changed

8 files changed

+38
-29
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- Credit cost for remote mode solver has been modified to be defined in advance based on the mode solver details. Previously, the cost was based on elapsed runtime. On average, there should be little difference in the cost.
1717
- Mode solves that are part of an FDTD simulation (i.e. for mode sources and monitors) are now charged at the same flex credit cost as a corresponding standalone mode solver call.
1818
- Any `FreqMonitor.freqs` or `Source.source_time.freq0` smaller than `1e5` now raise an error as this must be incorrect setup that is outside the Tidy3D intended range (note default frequency is `Hz`).
19+
- When using complex fields (e.g. with Bloch boundaries), FluxTimeMonitor and frequency-domain fields (including derived quantities like flux) now only use the real part of the time-domain electric field.
1920

2021
### Fixed
22+
- Fixed energy leakage in TFSF when using complex fields.
2123

2224
## [2.5.0rc3] - 2023-11-30
2325

tests/sims/simulation_2_5_0rc3.h5

-449 KB
Binary file not shown.

tidy3d/components/data/monitor_data.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -843,8 +843,8 @@ def poynting(self) -> ScalarFieldTimeDataArray:
843843
# Tangential fields are ordered as E1, E2, H1, H2
844844
tan_fields = self._colocated_tangential_fields
845845
dim1, dim2 = self._tangential_dims
846-
e_x_h = tan_fields["E" + dim1] * tan_fields["H" + dim2]
847-
e_x_h -= tan_fields["E" + dim2] * tan_fields["H" + dim1]
846+
e_x_h = np.real(tan_fields["E" + dim1]) * np.real(tan_fields["H" + dim2])
847+
e_x_h -= np.real(tan_fields["E" + dim2]) * np.real(tan_fields["H" + dim1])
848848
return e_x_h
849849

850850
@cached_property

tidy3d/components/data/sim_data.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111

1212
from .monitor_data import MonitorDataTypes, MonitorDataType, AbstractFieldData, FieldTimeData
1313
from ..simulation import Simulation
14-
from ..boundary import BlochBoundary
15-
from ..source import TFSF
1614
from ..types import Ax, Axis, annotate_type, FieldVal, PlotScale, ColormapType
1715
from ..viz import equal_aspect, add_ax_if_none
1816
from ...exceptions import DataError, Tidy3dKeyError
@@ -116,16 +114,10 @@ def source_spectrum(self, source_index: int) -> Callable:
116114
times = self.simulation.tmesh
117115
dt = self.simulation.dt
118116

119-
# get boundary information to determine whether to use complex fields
120-
boundaries = self.simulation.boundary_spec.to_list
121-
boundaries_1d = [boundary_1d for dim_boundary in boundaries for boundary_1d in dim_boundary]
122-
complex_fields = any(isinstance(boundary, BlochBoundary) for boundary in boundaries_1d)
123-
complex_fields = complex_fields and not isinstance(source, TFSF)
124-
125117
# plug in mornitor_data frequency domain information
126118
def source_spectrum_fn(freqs):
127119
"""Source amplitude as function of frequency."""
128-
spectrum = source_time.spectrum(times, freqs, dt, complex_fields)
120+
spectrum = source_time.spectrum(times, freqs, dt)
129121

130122
# Remove user defined amplitude and phase from the normalization
131123
# such that they would still have an effect on the output fields.

tidy3d/components/medium.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ def _get_n0(
155155
)
156156
return n0
157157

158+
@property
159+
def complex_fields(self) -> bool:
160+
"""Whether the model uses complex fields."""
161+
pass
162+
158163

159164
class NonlinearSusceptibility(NonlinearModel):
160165
"""Model for an instantaneous nonlinear chi3 susceptibility.
@@ -215,6 +220,11 @@ def _validate_numiters(cls, val):
215220
)
216221
return val
217222

223+
@property
224+
def complex_fields(self) -> bool:
225+
"""Whether the model uses complex fields."""
226+
return False
227+
218228

219229
class TwoPhotonAbsorption(NonlinearModel):
220230
"""Model for two-photon absorption (TPA) nonlinearity which gives an intensity-dependent
@@ -287,6 +297,11 @@ def _validate_medium(self, medium: AbstractMedium):
287297
if self.n0 is not None:
288298
self._validate_medium_freqs(medium, [])
289299

300+
@property
301+
def complex_fields(self) -> bool:
302+
"""Whether the model uses complex fields."""
303+
return True
304+
290305

291306
class KerrNonlinearity(NonlinearModel):
292307
"""Model for Kerr nonlinearity which gives an intensity-dependent refractive index
@@ -358,6 +373,11 @@ def _validate_medium(self, medium: AbstractMedium):
358373
if self.n0 is not None:
359374
self._validate_medium_freqs(medium, [])
360375

376+
@property
377+
def complex_fields(self) -> bool:
378+
"""Whether the model uses complex fields."""
379+
return True
380+
361381

362382
NonlinearModelType = Union[NonlinearSusceptibility, TwoPhotonAbsorption, KerrNonlinearity]
363383

tidy3d/components/simulation.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2403,6 +2403,10 @@ def complex_fields(self) -> bool:
24032403
"""
24042404
if any(isinstance(boundary[0], BlochBoundary) for boundary in self.boundary_spec.to_list):
24052405
return True
2406+
for medium in self.scene.mediums:
2407+
if medium.nonlinear_spec is not None:
2408+
if any(model.complex_fields for model in medium._nonlinear_models):
2409+
return True
24062410
return False
24072411

24082412
@cached_property

tidy3d/components/source.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ def plot_spectrum(
4646
num_freqs: int = 101,
4747
val: PlotVal = "real",
4848
ax: Ax = None,
49-
complex_fields: bool = False,
5049
) -> Ax:
5150
"""Plot the complex-valued amplitude of the source time-dependence.
51+
Note: Only the real part of the time signal is used.
5252
5353
Parameters
5454
----------
@@ -61,8 +61,6 @@ def plot_spectrum(
6161
Number of frequencies to plot within the SourceTime.frequency_range.
6262
ax : matplotlib.axes._subplots.Axes = None
6363
Matplotlib axes to plot on, if not specified, one is created.
64-
complex_fields : bool
65-
Whether time domain fields are complex, e.g., for Bloch boundaries
6664
6765
Returns
6866
-------
@@ -72,7 +70,7 @@ def plot_spectrum(
7270

7371
fmin, fmax = self.frequency_range()
7472
return self.plot_spectrum_in_frequency_range(
75-
times, fmin, fmax, num_freqs=num_freqs, val=val, ax=ax, complex_fields=complex_fields
73+
times, fmin, fmax, num_freqs=num_freqs, val=val, ax=ax
7674
)
7775

7876
@abstractmethod

tidy3d/components/time.py

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,17 @@ def amp_time(self, time: float) -> complex:
4040
Returns
4141
-------
4242
complex
43-
Complex-valued amplitude at that time..
43+
Complex-valued amplitude at that time.
4444
"""
4545

4646
def spectrum(
4747
self,
4848
times: ArrayFloat1D,
4949
freqs: ArrayFloat1D,
5050
dt: float,
51-
complex_fields: bool = False,
5251
) -> complex:
53-
"""Complex-valued spectrum as a function of frequency
52+
"""Complex-valued spectrum as a function of frequency.
53+
Note: Only the real part of the time signal is used.
5454
5555
Parameters
5656
----------
@@ -62,8 +62,6 @@ def spectrum(
6262
dt : float or np.ndarray
6363
Time step to weight FT integral with.
6464
If array, use to weigh each of the time intervals in ``times``.
65-
complex_fields : bool
66-
Whether time domain fields are complex, e.g., for Bloch boundaries
6765
6866
Returns
6967
-------
@@ -73,10 +71,7 @@ def spectrum(
7371

7472
times = np.array(times)
7573
freqs = np.array(freqs)
76-
time_amps = self.amp_time(times)
77-
78-
if not complex_fields:
79-
time_amps = np.real(time_amps)
74+
time_amps = np.real(self.amp_time(times))
8075

8176
# if all time amplitudes are zero, just return (complex-valued) zeros for spectrum
8277
if np.all(np.equal(time_amps, 0.0)):
@@ -86,7 +81,7 @@ def spectrum(
8681
relevant_time_inds = np.where(np.abs(time_amps) / np.amax(np.abs(time_amps)) > DFT_CUTOFF)
8782
# find first and last index where the filter is True
8883
start_ind = relevant_time_inds[0][0]
89-
stop_ind = relevant_time_inds[0][-1]
84+
stop_ind = relevant_time_inds[0][-1] + 1
9085
time_amps = time_amps[start_ind:stop_ind]
9186
times_cut = times[start_ind:stop_ind]
9287
if times_cut.size == 0:
@@ -118,9 +113,9 @@ def plot_spectrum_in_frequency_range(
118113
num_freqs: int = 101,
119114
val: PlotVal = "real",
120115
ax: Ax = None,
121-
complex_fields: bool = False,
122116
) -> Ax:
123117
"""Plot the complex-valued amplitude of the time-dependence.
118+
Note: Only the real part of the time signal is used.
124119
125120
Parameters
126121
----------
@@ -137,8 +132,6 @@ def plot_spectrum_in_frequency_range(
137132
Number of frequencies to plot within the [fmin, fmax].
138133
ax : matplotlib.axes._subplots.Axes = None
139134
Matplotlib axes to plot on, if not specified, one is created.
140-
complex_fields : bool
141-
Whether time domain fields are complex, e.g., for Bloch boundaries
142135
143136
Returns
144137
-------
@@ -154,7 +147,7 @@ def plot_spectrum_in_frequency_range(
154147
dt = np.mean(dts)
155148
freqs = np.linspace(fmin, fmax, num_freqs)
156149

157-
spectrum = self.spectrum(times=times, dt=dt, freqs=freqs, complex_fields=complex_fields)
150+
spectrum = self.spectrum(times=times, dt=dt, freqs=freqs)
158151

159152
if val == "real":
160153
ax.plot(freqs, spectrum.real, color="blueviolet", label="real")

0 commit comments

Comments
 (0)