-
Notifications
You must be signed in to change notification settings - Fork 638
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tutorial for extraction efficiency of a collection of dipoles in a disc in cylindrical coordinates #2726
Conversation
e523afe
to
2174b16
Compare
The fact that your vacuum radiation patterns are not independent of the location of the source looks wrong. Not sure what the bug is — maybe you should have summed E and H over m before computing the E*xH power? |
Here are three radiation patterns scaled by There is good agreement in the radiation pattern between the theoretical result and the simulation for the dipole at Schematic of the simulation cell. |
Would be good to overlay the analytical formula (for a circularly polarized source). (Given the formula for the field of a dipole, add it to its rotation by 90° multiplied by i to get the field of a circularly polarized dipole. Then plug in the Poynting flux. Someone has probably worked it out already.) |
A good check is that summing the fields over m and then computing the Poynting flux |
The procedure for summing the fields first and then computing the Poynting flux is described in Tutorial/Nonaxisymmetric Dipoles Sources: Note that in Step 2, the Using this approach, the radiation pattern of the dipoles at The same simulation parameters (resolution, field decay threshold for termination, and flux threshold for truncating the Fourier-series expansion) as before were used. For the |
|
||
flux_r = np.real(e_field[:, 1] * h_field[:, 2] - e_field[:, 2] * h_field[:, 1]) | ||
flux_z = np.real(e_field[:, 0] * h_field[:, 1] - e_field[:, 1] * h_field[:, 0]) | ||
flux_rz = np.sqrt(np.square(flux_r) + np.square(flux_z)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flux_rz = np.sqrt(np.square(flux_r) + np.square(flux_z)) | |
flux_rz = flux_r * np.sin(theta_rad) + flux_z * np.cos(theta_rad) |
you should be taking the dot product with n̂
here. (Though I think it should give the same results, since at a large radius the flux should be almost all radially outwards, I think?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I verified that this suggested change does not affect the results.
I have added the radiation pattern for a circularly polarized electric dipole obtained using antenna theory to the plot in the previous comment. There is good agreement between the theoretical result and the simulated radiation pattern for the dipole at |
edit (1/8/2024): disregard. Using It seems that to obtain agreement in the radiation pattern in vacuum for dipoles at These two modifications are:
With these changes, the agreement in the radiation pattern is noticeably improved compared to the original result. |
For debugging purposes, here is the script I used to generate the radiation patterns (shown above) for a dipole in vacuum at """Radiation pattern of a dipole in vacuum in cylindrical coordinates."""
import math
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
import meep as mp
import numpy as np
RESOLUTION_UM = 100
WAVELENGTH_UM = 1.0
NUM_FARFIELD_PTS = 200
FARFIELD_RADIUS = 1e6 * WAVELENGTH_UM
DEBUG_OUTPUT = False
farfield_angles = np.linspace(0, 0.5 * math.pi, NUM_FARFIELD_PTS)
def plot_radiation_pattern_polar(radiation_pattern: np.ndarray):
"""Plots the radiation pattern in polar coordinates.
The angles increase clockwise with zero at the pole (+z direction)
and π/2 at the equator (+r direction).
Args:
radiation_pattern: radial flux of the far fields in polar coordinates.
"""
radiation_pattern_theory = ((np.cos(farfield_angles)**2 + 1) *
radiation_pattern[0] * 0.5)
fig, ax = plt.subplots(subplot_kw={"projection": "polar"}, figsize=(6,6))
ax.plot(
farfield_angles,
radiation_pattern,
"b-",
label="Meep"
)
ax.plot(
farfield_angles,
radiation_pattern_theory,
"r-",
label="theory"
)
ax.set_theta_direction(-1)
ax.set_theta_offset(0.5 * math.pi)
ax.set_thetalim(0, 0.5 * math.pi)
ax.grid(True)
ax.set_rlabel_position(22)
ax.set_ylabel("radial flux (a.u.)")
ax.set_title("radiation pattern in polar coordinates")
ax.legend()
if mp.am_master():
fig.savefig(
"vacuum_cyl_radpattern.png",
dpi=150,
bbox_inches="tight",
)
def radiation_pattern_flux(radiation_pattern: np.ndarray) -> float:
"""Computes the Poynting flux from the radiation pattern."""
dtheta = 0.5 * math.pi / (NUM_FARFIELD_PTS - 1)
dphi = 2 * math.pi
flux_far = (np.sum(radiation_pattern * np.sin(farfield_angles)) *
FARFIELD_RADIUS * FARFIELD_RADIUS * dtheta * dphi)
# We need to multiply the result by two because this integral is only
# for θ in [0, 90°] whereas we need it to be [0, 180°].
flux_far *= 2
return flux_far
def radiation_pattern_from_farfields(
sim: mp.Simulation, n2f_mon: mp.DftNear2Far
) -> np.ndarray:
"""Computes the radiation pattern from the far fields.
Args:
sim: a `Simulation` object.
n2f_mon: a `DftNear2Far` object returned by `Simulation.add_near2far`.
Returns:
Array of radial Poynting flux, one for each point on the circumference
of a quarter circle with angular range of [0, π/2]. 0 rad is the +z
direction and π/2 is +r.
"""
e_field = np.zeros((NUM_FARFIELD_PTS, 3), dtype=np.complex128)
h_field = np.zeros((NUM_FARFIELD_PTS, 3), dtype=np.complex128)
for n in range(NUM_FARFIELD_PTS):
ff = sim.get_farfield(
n2f_mon,
mp.Vector3(
FARFIELD_RADIUS * math.sin(farfield_angles[n]),
0,
FARFIELD_RADIUS * math.cos(farfield_angles[n])
)
)
e_field[n, :] = [ff[j] for j in range(3)]
h_field[n, :] = [ff[j + 3] for j in range(3)]
x_flux = (np.real(np.conj(e_field[:, 1]) * h_field[:, 2] -
np.conj(e_field[:, 2]) * h_field[:, 1]))
z_flux = (np.real(np.conj(e_field[:, 0]) * h_field[:, 1] -
np.conj(e_field[:, 1]) * h_field[:, 0]))
r_flux = np.sqrt(np.square(x_flux) + np.square(z_flux))
return r_flux
def point_dipole_in_vacuum(rpos_um: float, m: int) -> np.ndarray:
pml_um = 1.0
padding_um = 10.0
size_r = padding_um + pml_um
size_z = pml_um + padding_um + pml_um
cell_size = mp.Vector3(size_r, 0, size_z)
pml_layers = [mp.PML(thickness=pml_um)]
frequency = 1 / WAVELENGTH_UM
src_pt = mp.Vector3(rpos_um, 0, 0)
sources = [
mp.Source(
src=mp.GaussianSource(
frequency, fwidth=0.2 * frequency, cutoff=10.0
),
component=mp.Er,
center=src_pt,
)
]
sim = mp.Simulation(
resolution=RESOLUTION_UM,
cell_size=cell_size,
dimensions=mp.CYLINDRICAL,
sources=sources,
boundary_layers=pml_layers,
m=m,
force_complex_fields=True,
)
flux_mon = sim.add_flux(
frequency,
0,
1,
mp.FluxRegion(
center=mp.Vector3(
0.5 * (size_r - pml_um), 0, 0.5 * size_z - pml_um
),
size=mp.Vector3(size_r - pml_um, 0, 0)
),
mp.FluxRegion(
center=mp.Vector3(size_r - pml_um, 0, 0),
size=mp.Vector3(0, 0, size_z - 2 * pml_um),
),
mp.FluxRegion(
center=mp.Vector3(
0.5 * (size_r - pml_um), 0, -0.5 * size_z + pml_um
),
size=mp.Vector3(size_r - pml_um, 0, 0),
weight=-1.0,
),
)
n2f_mon = sim.add_near2far(
frequency,
0,
1,
mp.Near2FarRegion(
center=mp.Vector3(
0.5 * (size_r - pml_um), 0, 0.5 * size_z - pml_um
),
size=mp.Vector3(size_r - pml_um, 0, 0)
),
mp.Near2FarRegion(
center=mp.Vector3(size_r - pml_um, 0, 0),
size=mp.Vector3(0, 0, size_z - 2 * pml_um),
),
mp.Near2FarRegion(
center=mp.Vector3(
0.5 * (size_r - pml_um), 0, -0.5 * size_z + pml_um
),
size=mp.Vector3(size_r - pml_um, 0, 0),
weight=-1.0,
),
)
if DEBUG_OUTPUT:
fig, ax = plt.subplots()
sim.plot2D(ax=ax)
if mp.am_master():
fig.savefig(
"cyl_simulation_layout.png", dpi=150, bbox_inches="tight"
)
sim.run(
until_after_sources=mp.stop_when_fields_decayed(
25.0, mp.Er, src_pt, 1e-8
)
)
flux_near = mp.get_fluxes(flux_mon)[0]
radiation_pattern = radiation_pattern_from_farfields(sim, n2f_mon)
flux_far = radiation_pattern_flux(radiation_pattern)
err = abs(flux_far - flux_near) / flux_near
print(
f"flux-m:, {rpos_um}, {m}, "
f"{flux_near:.6f} (near), {flux_far:.6f} (far), {err:.6f} (error)"
)
return radiation_pattern
if __name__ == "__main__":
# An Er source at r = 0 must be slightly offset due to #2704.
# Requires a single simulation with m = ±1.
rpos_um = 1.5 / RESOLUTION_UM
m = 1
radiation_pattern = point_dipole_in_vacuum(rpos_um, m)
plot_radiation_pattern_polar(
FARFIELD_RADIUS**2 * radiation_pattern
)
if mp.am_master():
fname = f'vacuum_cyl_rpos{rpos_um}_res{RESOLUTION_UM}.npz'
np.savez(
fname,
m=m,
resolution=RESOLUTION_UM,
wavelength_um=WAVELENGTH_UM,
num_farfield_pts=NUM_FARFIELD_PTS,
farfield_angles=farfield_angles,
farfield_radius=FARFIELD_RADIUS,
radiation_pattern=radiation_pattern
)
# An Er source at r > 0 requires Fourier-series expansion of exp(imϕ).
rpos_um = 0.19
m = 0
flux_max = 0
flux_thresh = 1e-2
radiation_patterns = np.zeros(NUM_FARFIELD_PTS)
radiation_pattern_total = np.zeros(NUM_FARFIELD_PTS)
while True:
radiation_pattern = point_dipole_in_vacuum(rpos_um, m)
radiation_pattern_total += radiation_pattern * (1 if m == 0 else 2)
if m == 0:
radiation_patterns = radiation_pattern[:, None]
else:
radiation_patterns = np.hstack(
(radiation_patterns, radiation_pattern[:, None])
)
flux_far = radiation_pattern_flux(radiation_pattern)
if flux_far > flux_max:
flux_max = flux_far
if m > 0 and (flux_far / flux_max) < flux_thresh:
break
else:
m += 1
plot_radiation_pattern_polar(
FARFIELD_RADIUS**2 * radiation_pattern_total
)
if mp.am_master():
fname = f'vacuum_cyl_rpos{rpos_um}_res{RESOLUTION_UM}.npz'
np.savez(
fname,
num_m=m,
resolution=RESOLUTION_UM,
wavelength_um=WAVELENGTH_UM,
num_farfield_pts=NUM_FARFIELD_PTS,
farfield_angles=farfield_angles,
farfield_radius=FARFIELD_RADIUS,
radiation_patterns=radiation_patterns
) |
I figured out why the radiation pattern for an Specifically, in Tutorial/Nonaxisymmetric Dipole Sources, the Fourier-series expansion is described as: This approach produces incorrect results as shown above in the plots of the radiation pattern at various source positions. In order to produce the correct result which matches the radiation pattern from antenna theory, the Fourier-series expansion must be changed to: Note: the This requires changing a single line in the script above: radiation_pattern_total += radiation_pattern * (1 if m == 0 else 2) to radiation_pattern_total += radiation_pattern * (2 if m == 0 else 1) With this change, the radiation pattern at e.g. |
…sc in cylindrical coordinates
…improvements to simulation script
d90ed71
to
62f3402
Compare
With the correction described in the previous comment to the way the Fourier-series expansion for The simulation script as well as the description in the text of the tutorial have been updated with these changes. |
…avity modes with small decay rates for certain dipole configurations
I think the issue is that, as an optimization, Meep only simulates the real parts of the field for m=0 (where the real and imaginary parts are decoupled), which effectively halves the current source for the m=0. To compensate, you could either use |
For the emission at 90°, you might want to make sure that |
If you want to validate this, an ideal tool here would be SCUFF, which should be very efficient for this type of problem. |
Note that a dielectric disk will have very high Q resonance, especially at higher frequencies. This might cause the fields to decay very slowly if you have a broadband source. If these aren't the frequencies of interest, then you might want to either (a) not excite the resonances by using a narrow-band source or (b) use |
Increasing Based on these results, it might therefore be useful to make |
As suggested, setting |
Codecov ReportAll modified and coverable lines are covered by tests ✅
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. Additional details and impacted files@@ Coverage Diff @@
## master #2726 +/- ##
==========================================
- Coverage 74.06% 73.78% -0.29%
==========================================
Files 18 18
Lines 5399 5421 +22
==========================================
+ Hits 3999 4000 +1
- Misses 1400 1421 +21 |
Co-authored-by: Steven G. Johnson <[email protected]>
Co-authored-by: Steven G. Johnson <[email protected]>
Closes #2690.
This script seems to be working and generates the radiation pattern below. The extraction efficiency computed from this radiation pattern is
0.9569
.