Skip to content
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

GaussianBeam2DSource Bug Cases Found #2356

Closed
hammy4815 opened this issue Dec 27, 2022 · 6 comments
Closed

GaussianBeam2DSource Bug Cases Found #2356

hammy4815 opened this issue Dec 27, 2022 · 6 comments
Labels

Comments

@hammy4815
Copy link
Contributor

A few bugs were discovered regarding the new GaussianBeam2DSource implementation.

  1. Using materials other than air produces slight issues after creating the equivalent source.

For example, as can be seen below, after changing the refractive index to 1.5, small issues arise and back propagation below the source region now shows up. I am unsure at this moment if this is an issue in the creation of the beam fields themselves, or in the call to equivalent source, but it appears that a factor of $n$ or $\epsilon$ is either missing or accidentally added.

Screenshot 2022-12-27 at 2 42 48 PM

Currently, as given here in equation 4.4, the equivalent current sources do not contain a factor of $\epsilon$ or $\mu$, matching how it is currently implemented in MEEP.

Screenshot 2022-12-27 at 2 51 39 PM

  1. Under stronger index changes, or a significant wavelength change, an overflow occurs in the normalization of the fields before calling equivalent source, and the time fields remain zero.

Screenshot 2022-12-27 at 2 46 39 PM

This is because calling scipy.special.hankel produces enormous results (on the order of 1e120). When the beam waist is similar in size to the effective wavelength, the order of magnitude is low enough to avoid overflows. We likely need a clever way to fix this issue at this step (calling the hankel function).

@smartalecH smartalecH added the bug label Dec 28, 2022
@oskooi
Copy link
Collaborator

oskooi commented Dec 28, 2022

The refractive index $n$ (of free space) is implicit in the fields $E_+$ and $H_+$ .There is no need for the index to be accounted for separately in the expression for the equivalent current.

Note that in the original implementation of the GaussianBeam object via the complex point source in #711 (and also the analytic expression for the electric field of a Gaussian beam), the beam's wavevector $\vec{k}$ in free space includes the index $n$:

meep/src/sources.cpp

Lines 604 to 608 in 802257f

double n = sqrt(eps * mu);
double k = 2 * pi * freq * n;
double ZR = sqrt(mu / eps);
double z0 = k * w0 * w0 / 2;
double kz0 = k * z0;

@hammy4815
Copy link
Contributor Author

hammy4815 commented Jan 5, 2023

Just an update - I fixed the first issue regarding the material change.

There was an extra factor of Z (impedance) on H during my normalization that shouldn't be there.

meep/python/source.py

Lines 902 to 903 in d29eadc

fields2D[:3] = fields2D[:3] / w_field_norm
fields2D[3:] = fields2D[3:] / (w_field_norm * np.sqrt(mu / eps))

In addition, the resolution needs to be high enough to support higher epsilon.

meep/python/source.py

Lines 851 to 856 in d29eadc

x = (
np.linspace(
center.x() - size.x() / 2,
center.x() + size.x() / 2,
int(2 * sim.resolution * size.x()),
)

Finally, I reformulated the way I use the polarization vector, because my previous method did not take into account the phases properly. This was the code before:

meep/python/source.py

Lines 905 to 914 in d29eadc

# Multiply by E0 and H0
fields2D[0] = fields2D[0] * beam_E0.x
fields2D[1] = fields2D[1] * beam_E0.y
fields2D[2] = fields2D[2] * beam_E0.z
fields2D[3] = fields2D[3] * (beam_E0.z)
fields2D[4] = fields2D[4] * (beam_E0.z)
fields2D[5] = fields2D[5] * np.sqrt(
np.abs(beam_E0.x) ** 2 + np.abs(beam_E0.y) ** 2
)

Now, in green2d, the polarization vector used in the formulation matches the user provided polarization vector for the electric source:

p = np.zeros(3, dtype=complex)
p[0] = beam_E0.x
p[1] = beam_E0.y
p[2] = beam_E0.z
pdotrhat = p[0] * rhat[0] + p[1] * rhat[1]
rhatcrossp = rhat[0] * p[1] - rhat[1] * p[0]

And for the magnetic source that is pointed orthogonal to the electric source:

p = - np.array([
    - p[2] * np.imag(kdir),
    p[2] * np.real(kdir),
    p[0] * np.imag(kdir) - p[1] * np.real(kdir)
])
pdotrhat = p[0] * rhat[0] + p[1] * rhat[1]
rhatcrossp = rhat[0] * p[1] - rhat[1] * p[0]

I will create a PR for these changes, but am still trying to solve the final problem of the overflow. The hankel functions are returning nan's during overflow, which causes problems.

@hammy4815
Copy link
Contributor Author

hammy4815 commented Jan 7, 2023

Hi guys,

I have an update on the issue with nan's and infs for high index. Maybe I'm missing something here, but the problem seems more fundamental than a normalization issue.

Consider the beam with waist size $w_0 = 1 \mu m$, frequency $f = 1$, $\omega = 2\pi$, in a medium of $n = 6$ corresponding to a $k=2\pi 6 = 12\pi$:

The complex origin of the dipole is placed at $z_0 = jkw_0^2/2=j6\pi$ which in two dimensions if propagating in the y direction is the vector $\mathbf{p_0}=[0,\pm j6\pi]$ depending on the direction of $\mathbf{k}$.

At the origin $\mathbf{r} = [0, 0]$, the radial distance $\mathbf{r}-\mathbf{p_0} = \pm j6\pi$. The argument of the hankel function is $kr= \pm12\pi * j6\pi = \pm j72\pi^2 \approx \pm710j$.

Passing these arguments into jv, yv, hankel1, and hankel2 shows that this argument is too (imaginarily) large and the bessel/hankel functions break down. This argument is located at the beam waist and should have maximal amplitude.

from scipy.special import jv, yv, hankel1, hankel2
>>> yv(0, +710j)
(nan+nanj)
>>> yv(0, -710j)
(nan+nanj)
>>> jv(0, -710j)
(inf+nanj)
>>> jv(0, +710j)
(inf+nanj)
>>> hankel1(0, +710j)
0j
>>> hankel2(0, -710j)
(-0+0j)
>>> hankel1(0, -710j)
(nan+nanj)
>>> hankel1(0, +710j)
0j

Note that hankel1 = jv + j * yv
and hankel2 = jv - j * yv

If anyone has any insights or thoughts, I'd love to hear them. Maybe there is some approximation for large arguments we can normalize analytically beforehand?

@stevengj
Copy link
Collaborator

You can always use the exponentially scaled Hankel functions scipy.special.hankel1e and scipy.special.hankel2e. This lets you pull out the exponential factor analytically if you want to rescale.

@hammy4815
Copy link
Contributor Author

You can always use the exponentially scaled Hankel functions scipy.special.hankel1e and scipy.special.hankel2e. This lets you pull out the exponential factor analytically if you want to rescale.

This works well. It appears to be working now that I switched to the exponential Hankel functions and am careful to scale and normalize correctly for the hankel1 vs hankel2. I will create a new PR with the changes and a description of how I am performing the proper scaling.

@stevengj
Copy link
Collaborator

Closed by #2363 and #2371

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants