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

Special field updates for r = 0 and m = 0, ±1 in cylindrical coordinates #2538

Merged
merged 22 commits into from
Nov 3, 2023

Conversation

oskooi
Copy link
Collaborator

@oskooi oskooi commented May 26, 2023

Fixes #2489.

While #2383 fixed a bug for the $z$-PML at $r = 0$ for $m = \pm 1$, it seems to have introduced a separate bug for the $m = 0$ case. This is because #2383 actually never tested the $m = 0$ case which is why it went undetected until #2489.

This PR reverts the change to update_eh.cpp in #2383 but only for $m = 0$. It is not yet clear to me why this fixes the issue reported in #2489 but the test results suggests it does.

A new unit test for $m = 0$ (currently missing) based on the results in #2489 will also be added to this PR in a subsequent commit.

Note: two test cases in test_adjoint_cyl.py involving $m = 0$ required adjusting the tolerance of their error check.

@oskooi oskooi added the bug label May 26, 2023
@oskooi oskooi changed the title Special case for update $E$ from $D$ in cylindrical coordinates for $m = 0$ and $r = 0$ Specialized field updates for r = 0 in cylindrical coordinates May 30, 2023
@oskooi
Copy link
Collaborator Author

oskooi commented May 30, 2023

Added a new function step_update_EDHB0 in step_generic.cpp based on the suggestion from @stevengj. This seems to fix the problem with one caveat.

As shown below, this fix produces the correct results whereby the DFT fields for $E_r$ and $E_z$ match the near-to-far fields for three test cases of $m = 0, 1, 2$. Results shown for $E_r$ only.

src_er_mon_er_dft_vs_n2f_m0_dair1 0_res100

src_er_mon_er_dft_vs_n2f_m1_dair1 0_res100

src_er_mon_er_dft_vs_n2f_m2_dair1 0_res100

The caveat is there is still one case which does show a discrepancy: $H_r$ at $r = 0$ for $|m| = 1$. This case is not necessarily a bug because the $H_r$ from $B_r$ update is not affected by step_update_EDHB0 since $H_r = B_r$ (i.e., no PML for $H_r$ at $r = 0$). Fixing this would require storing $H_r$ as a separate array but the additional storage over the entire cell is probably not worth it just to fix the output at $r = 0$.

src_er_mon_hr_dft_vs_n2f_m1_dair1 0_res100

@stevengj
Copy link
Collaborator

The caveat is there is still one case which does show a discrepancy: Hr at r=0 for m=1.

I suspect that this is an unrelated problem with interpolation.

#define STEP_UPDATE_EDHB0(f, fc, gv, is, ie, g, u, s, chi2, chi3, fw, dsigw, sigw, kapw) \
do { \
step_update_EDHB0(f, fc, gv, is, ie, g, u, s, chi2, chi3, fw, dsigw, sigw, kapw); \
} while (0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a macro here rather than just calling step_update_EDHB0 directly?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this line

(echo $(PRELUDE); echo; sed 's/LOOP_OVER/S1LOOP_OVER/g' $(top_srcdir)/src/step_generic.cpp | sed 's/step_curl/step_curl_stride1/' | sed 's/step_update_EDHB/step_update_EDHB_stride1/' | sed 's/step_beta/step_beta_stride1/') > $@

is generating a stride1 version called (confusingly) STEP_UPDATE_EDHB_stride10, which we should call from the macro similar to STEP_UPDATE_EDHB

@stevengj
Copy link
Collaborator

I assume it still fixes #2182?

@oskooi
Copy link
Collaborator Author

oskooi commented May 30, 2023

I assume it still fixes #2182?

test_pml_cyl.py added in #2383 to verify that the $z$-PML is working correctly is failing for $|m| = 1$. The fields at $r = 0$ are oscillating in time long after the source as turned off as they were prior to #2383. The change introduced in #2383 to make the $z$-PML work correctly was to remove the step-update at $r = 0$ in update_eh.cpp.

The $z$-PML at $r = 0$ is working correctly for $m = 0$ but that is not part of the existing tests in test_pml_cyl.py. This test is to be added in this PR.

@oskooi
Copy link
Collaborator Author

oskooi commented May 30, 2023

Perhaps there is separate bug in the step-curl update for $B_r$ (= $H_r$) at $r = 0$ for $|m| = 1$ in fields_chunk::step_db of step_db.cpp:

meep/src/step_db.cpp

Lines 313 to 347 in e4504ed

else if (fabs(m) == 1) {
// D_stuff: d(Dp)/dt = d(Hr)/dz - d(Hz)/dr
// B_stuff: d(Br)/dt = d(Ep)/dz - i*m*Ez/r
component cc = ft == D_stuff ? Dp : Br;
direction d_c = component_direction(cc);
if (!f[cc][cmp]) continue;
const realnum *f_p = f[ft == D_stuff ? Hr : Ep][cmp];
const realnum *f_m = ft == D_stuff ? f[Hz][cmp] : (f[Ez][1 - cmp] + (nz + 1));
const realnum *cndinv = s->condinv[cc][d_c];
realnum *fcnd = f_cond[cc][cmp];
const direction dsig = cycle_direction(gv.dim, d_c, 1);
const realnum *siginv = s->sigsize[dsig] > 1 ? s->siginv[dsig] : 0;
const int dk = gv.iyee_shift(cc).in_direction(dsig);
const direction dsigu = cycle_direction(gv.dim, d_c, 2);
const realnum *siginvu = s->sigsize[dsigu] > 1 ? s->siginv[dsigu] : 0;
const int dku = gv.iyee_shift(cc).in_direction(dsigu);
realnum *fu = siginvu && f_u[cc][cmp] ? f[cc][cmp] : 0;
realnum *the_f = fu ? f_u[cc][cmp] : f[cc][cmp];
int sd = ft == D_stuff ? +1 : -1;
realnum f_m_mult = ft == D_stuff ? 2 : (1 - 2 * cmp) * m;
for (int iz = (ft == D_stuff); iz < nz + (ft == D_stuff); ++iz) {
realnum df;
realnum dfcnd = (sd * Courant) * (f_p[iz] - f_p[iz - sd] - f_m_mult * f_m[iz]) *
(cndinv ? cndinv[iz] : 1);
if (fcnd) fcnd[iz] += dfcnd;
the_f[iz] += (df = dfcnd * (siginv ? siginv[dk + 2 * (dsig == Z) * iz] : 1));
if (fu) fu[iz] += siginvu[dku + 2 * (dsigu == Z) * iz] * df;
}
if (ft == D_stuff) {
ZERO_Z(f[Dz][cmp]);
if (f_cond[Dz][cmp]) ZERO_Z(f_cond[Dz][cmp]);
if (f_u[Dz][cmp]) ZERO_Z(f_u[Dz][cmp]);
}
}

src/update_eh.cpp Outdated Show resolved Hide resolved
@stevengj
Copy link
Collaborator

stevengj commented Jun 1, 2023

Would be good to see Ep (before and after this PR) for m=1. Without this PR, it seems like Ep at r=0 would never be updated, which seems like it would screw up the Hr update at r=0, so I'm confused about why removing the Ep update would help. (Unless Ep is getting updated somewhere else?)

@stevengj
Copy link
Collaborator

stevengj commented Jun 1, 2023

@mochen4 should also check to make sure that he didn't change what portion of the array is "owned" for cylindrical coordinates when he was updating loop_in_chunks (#1895), since that would affect the step_db code. (Also check whether #2182 was present before #1895.)

@oskooi
Copy link
Collaborator Author

oskooi commented Jun 1, 2023

with 2538

src_er_mon_ep_dft_vs_n2f_m1_dair1 0_res50

before 2538

src_er_mon_ep_dft_vs_n2f_m1_dair1 0_res50

src_er_mon_hr_dft_vs_n2f_m1_dair1 0_res50

@stevengj
Copy link
Collaborator

stevengj commented Jun 1, 2023

This is confusing to me because it doesn't seem like Ep should be updated at all at r=0 without this patch. (The previous update_EDHB line skips r=0 for Ep because of the little_owned_corner0.)

Maybe put in some printf calls to see where the Ep is being changed for r=0?

@stevengj
Copy link
Collaborator

stevengj commented Jun 1, 2023

Another quick check is to try setting μ = 1 + 1e-14 (i.e. force it to store and update H separately), in case the missing H update at r=0 (which affects the r=0 PML) is actually the real source of #2182.

@oskooi
Copy link
Collaborator Author

oskooi commented Jun 4, 2023

This is confusing to me because it doesn't seem like Ep should be updated at all at r=0 without this patch. (The previous update_EDHB line skips r=0 for Ep because of the little_owned_corner0.)

Maybe put in some printf calls to see where the Ep is being changed for r=0?

Looks like the reason the DFT and N2F fields for $E_\phi$ and $H_r$ do not exactly match at $r = 0$ for $|m| = 1$ (as shown in the figures above) is not because of the changes introduced in this PR but rather because get_dft_array is interpolating the fields to the center of the voxel. This means that ep and hr which are defined on the Yee grid at $r = 0$ are being interpolated to $r = \Delta / 2$. The key point is that we should not be using get_dft_array at $r = 0$ as part of the validation check.

I inspected the time-domain (and not the DFT) fields at $r = 0$ by printing out the raw values (see code snippet below). The results are consistent with the expected values at $r = 0$ for $|m| = 1$ for which $E_\phi$ and $H_r$ can be nonzero.

Summary of Results

  • $\varepsilon = 1$ everywhere

    • pre 2538: ep is zero at $r = 0$ whereas hr is nonzero at $r = 0$
    • 2538: ep and hr are nonzero at $r = 0$
  • $\varepsilon = 1$, $\mu = 1.01$ everywhere via default_material=mp.Medium(mu=1.01)

    • pre 2538: ep and hr are zero at $r = 0$
    • 2538: ep and hr are nonzero at $r = 0$

Notes

  • for $\varepsilon = 1$ and pre 2538:
    • dp at $r = 0$ is nonzero due to step-curl at $r = 0$ while ep is zero (never changed from its initial value)
    • hr is nonzero at $r = 0$ is expected because hr = br and br is nonzero due to step-curl at $r = 0$
diff --git a/src/update_eh.cpp b/src/update_eh.cpp
index 31e1c9e6..9b6d0c88 100644
--- a/src/update_eh.cpp
+++ b/src/update_eh.cpp
@@ -207,6 +207,20 @@ bool fields_chunk::update_eh(field_type ft, bool skip_w_components) {
             }
           }
         }
+
+        if (ec == Ep || ec == Hr) {
+          realnum *E = f[ec][cmp];
+          const int yee_idx = gv.yee_index(ec);
+          const int sR = gv.stride(R), nZ = gv.num_direction(Z);
+          bool zero_everywhere = true;
+          for (int iZ = 0; iZ < nZ; iZ++) {
+            const int i = yee_idx + iZ - sR;
+            if (E[i] != 0)
+              zero_everywhere = false;
+          }
+          master_printf("zero-at-r0?, %s, %d, %d\n", component_name(ec), cmp, zero_everywhere);
+        }
+
       }
     }
   }

It seems therefore that this PR is producing the expected results (in the non-PML region anyway). However, we still need to explain why the $z$-PML at $r = 0$ is not working correctly due to the failing test in test_pml_cyl.py.

@mochen4
Copy link
Collaborator

mochen4 commented Jun 8, 2023

@mochen4 should also check to make sure that he didn't change what portion of the array is "owned" for cylindrical coordinates when he was updating loop_in_chunks (#1895), since that would affect the step_db code. (Also check whether #2182 was present before #1895.)

I checked and found the ownership isn't changed by #1895, and #2182 was present before #1895.

@oskooi
Copy link
Collaborator Author

oskooi commented Jun 8, 2023

A couple of changes:

  1. Added a new test case for m = 0 which fails without this PR.
  2. Modified the m = -1 test case to use a narrowband source similar to the fix used in #2182 (comment) in order to get the unit test to pass.

This PR does fix a bug for the m = 0 but uncovers the bug for m = -1 originally reported in #2182.

There are two options for how to proceed: (1) we merge this PR as-is and then work on a bug fix for |m| = 1 or (2) include the additional bug fix in this PR.

@stevengj
Copy link
Collaborator

stevengj commented Jun 8, 2023

As discussed today, we went over the r=0 updates at

meep/src/step_db.cpp

Lines 313 to 346 in 955dcaa

else if (fabs(m) == 1) {
// D_stuff: d(Dp)/dt = d(Hr)/dz - d(Hz)/dr
// B_stuff: d(Br)/dt = d(Ep)/dz - i*m*Ez/r
component cc = ft == D_stuff ? Dp : Br;
direction d_c = component_direction(cc);
if (!f[cc][cmp]) continue;
const realnum *f_p = f[ft == D_stuff ? Hr : Ep][cmp];
const realnum *f_m = ft == D_stuff ? f[Hz][cmp] : (f[Ez][1 - cmp] + (nz + 1));
const realnum *cndinv = s->condinv[cc][d_c];
realnum *fcnd = f_cond[cc][cmp];
const direction dsig = cycle_direction(gv.dim, d_c, 1);
const realnum *siginv = s->sigsize[dsig] > 1 ? s->siginv[dsig] : 0;
const int dk = gv.iyee_shift(cc).in_direction(dsig);
const direction dsigu = cycle_direction(gv.dim, d_c, 2);
const realnum *siginvu = s->sigsize[dsigu] > 1 ? s->siginv[dsigu] : 0;
const int dku = gv.iyee_shift(cc).in_direction(dsigu);
realnum *fu = siginvu && f_u[cc][cmp] ? f[cc][cmp] : 0;
realnum *the_f = fu ? f_u[cc][cmp] : f[cc][cmp];
int sd = ft == D_stuff ? +1 : -1;
realnum f_m_mult = ft == D_stuff ? 2 : (1 - 2 * cmp) * m;
for (int iz = (ft == D_stuff); iz < nz + (ft == D_stuff); ++iz) {
realnum df;
realnum dfcnd = (sd * Courant) * (f_p[iz] - f_p[iz - sd] - f_m_mult * f_m[iz]) *
(cndinv ? cndinv[iz] : 1);
if (fcnd) fcnd[iz] += dfcnd;
the_f[iz] += (df = dfcnd * (siginv ? siginv[dk + 2 * (dsig == Z) * iz] : 1));
if (fu) fu[iz] += siginvu[dku + 2 * (dsigu == Z) * iz] * df;
}
if (ft == D_stuff) {
ZERO_Z(f[Dz][cmp]);
if (f_cond[Dz][cmp]) ZERO_Z(f_cond[Dz][cmp]);
if (f_u[Dz][cmp]) ZERO_Z(f_u[Dz][cmp]);
}

and so far they look correct, but we didn't check the PML terms yet. Would be good to compare those to the ones (for r≠0) in step_generic.cpp, since the PML equations should be the same (they don't involve spatial derivatives).

@oskooi
Copy link
Collaborator Author

oskooi commented Jun 9, 2023

I also verified that we are not adding the i*m/r term twice at $r = 0$ during the update for d(Br)/dt = d(Ep)/dz - i*m*Ez/r. There are two separate blocks where this happens:

meep/src/step_db.cpp

Lines 156 to 157 in eb8ff3a

// in cylindrical coordinates, we now have to add the i*m/r terms... */
if (gv.dim == Dcyl && m != 0) DOCMP FOR_FT_COMPONENTS(ft, cc) {

meep/src/step_db.cpp

Lines 281 to 282 in eb8ff3a

// deal with annoying r=0 boundary conditions for m=0 and m=1
if (gv.dim == Dcyl && gv.origin_r() == 0.0) DOCMP {

In the first instance, the loop over the $r$ axis is set up such that it never starts at $r = 0$ for the case in which the field chunk includes $r = 0$:

const realnum ir0 = gv.origin_r() * gv.a + 0.5 * gv.iyee_shift(cc).in_direction(R);

for (int ir = ir0 == 0; ir <= gv.nr(); ++ir) {

@oskooi
Copy link
Collaborator Author

oskooi commented Jun 12, 2023

we didn't check the PML terms yet. Would be good to compare those to the ones (for r≠0) in step_generic.cpp, since the PML equations should be the same (they don't involve spatial derivatives).

The special step-curl update equation with $z$-PML for Dp and |m| = 1 at $r = 0$ in step_db.cpp does not seem to match the step-curl update equations for $r \neq 0$ in step_generic.cpp.

Specifically, in step_db.cpp we have cc = Dp, d_c = P, f_p = f[Hr][cmp], f_m = f[Hz][cmp], dsig = Z, siginv = s->siginv[Z], dsigu = R, siginvu = 0, fu = 0, the_f = f[Dp][cmp], s_d = +1, f_m_mult = 2, and fcnd = 0. Using these parameters, the update equations are:

meep/src/step_db.cpp

Lines 334 to 341 in eb8ff3a

for (int iz = (ft == D_stuff); iz < nz + (ft == D_stuff); ++iz) {
realnum df;
realnum dfcnd = (sd * Courant) * (f_p[iz] - f_p[iz - sd] - f_m_mult * f_m[iz]) *
(cndinv ? cndinv[iz] : 1);
if (fcnd) fcnd[iz] += dfcnd;
the_f[iz] += (df = dfcnd * (siginv ? siginv[dk + 2 * (dsig == Z) * iz] : 1));
if (fu) fu[iz] += siginvu[dku + 2 * (dsigu == Z) * iz] * df;
}

The key line number is 339 involving updating the_f at some $z$ position along $r = 0$.

For comparison, in step_generic.cpp we have:

meep/src/step_generic.cpp

Lines 197 to 204 in eb8ff3a

else { // no conductivity (other than PML conductivity)
if (g2) {
PLOOP_OVER_IVECS(gv, is, ie, i) {
DEF_k;
f[i] = ((kap[k] - sig[k]) * f[i] - dtdx * (g1[i + s1] - g1[i] + g2[i] - g2[i + s2])) *
siginv[k];
}
}

Only the second term matches step_db.cpp (although the sign is flipped). The first term in this equation does not appear in step_db.cpp.

(The step-curl equation for Br contains dsig = P which means it does not involve $z$-PML. Br is therefore omitted from this analysis.)

@oskooi
Copy link
Collaborator Author

oskooi commented Jun 17, 2023

I have tried to add the correct step-db update equations for $z$-PML and the absorber at $r = 0$ for $|m| = 1$ to step_db.cpp by simply copying and over the update equations from step_generic.cpp (with some modifications).

However, there seems to be a bug somewhere in this implementation since the fields blow up for the unit test for m = -1 in test_pml_cyl.py.

@oskooi
Copy link
Collaborator Author

oskooi commented Jun 22, 2023

step_db.cpp: In member function 'bool meep::fields_chunk::step_db(meep::field_type)':
step_db.cpp:349:29: error: operands to '?:' have different types 'const meep::realnum*' {aka 'const float*'} and 'meep::realnum' {aka 'float'}
  349 |           the_f[iz] = ((kap ? : kap[k] - sig[k] : 1) * the_f[iz] + dfcnd) * (siginv ? siginv[k] : 1);
      |                         ~~~~^~~~~~~~~~~~~~~~~~~
step_db.cpp:349:48: error: expected ')' before ':' token
  349 |           the_f[iz] = ((kap ? : kap[k] - sig[k] : 1) * the_f[iz] + dfcnd) * (siginv ? siginv[k] : 1);
      |                        ~                       ^~
      |                                                )
step_db.cpp:349:101: error: expected ')' before ';' token
  349 |           the_f[iz] = ((kap ? : kap[k] - sig[k] : 1) * the_f[iz] + dfcnd) * (siginv ? siginv[k] : 1);
      |                       ~                                                                             ^
      |                                                                                                     )
step_db.cpp:350:81: error: 'i' was not declared in this scope; did you mean 'pi'?
  350 |           if (fu) fu[iz] = siginvu[ku] * ((kapu ? kapu[ku] - sigu[ku] : 1) * fu[i] + the_f[iz] - fprev);
      |                                                                                 ^
      |                                                                                 pi
step_db.cpp:325:24: warning: unused variable 'siginv' [-Wunused-variable]
  325 |         const realnum *siginv = s->sigsize[dsig] > 1 ? s->siginv[dsig] : 0;

@oskooi
Copy link
Collaborator Author

oskooi commented Jun 23, 2023

There is a bug in the special step-db update equations at $r = 0$ for $|m| = 1$ introduced by the latest commit because the unit test (TestLDOS.test_ldos_ext_eff of test_ldos.py) which verifies that the extraction efficiency of an LED is identical in 3d and cylindrical coordinates is failing:

extraction efficiency (cyl):, 3.440227
extraction efficiency (3D):, 0.327121

Clearly, the results for the cylindrical coordinates is wrong (the extraction efficiency cannot be larger than one).

I went over the update equations again and compared them to the "most general case" of step_generic.cpp from which they were adapted. The update equations seem correct, however, and it is not obvious where the bug could be.

meep/src/step_generic.cpp

Lines 218 to 230 in 95ddacd

//////////////////// MOST GENERAL CASE //////////////////////
PLOOP_OVER_IVECS(gv, is, ie, i) {
DEF_k;
DEF_ku;
realnum fprev = fu[i];
realnum fcnd_prev = fcnd[i];
fcnd[i] =
((1 - dt2 * cnd[i]) * fcnd[i] - dtdx * (g1[i + s1] - g1[i] + g2[i] - g2[i + s2])) *
cndinv[i];
fu[i] = ((kap[k] - sig[k]) * fu[i] + (fcnd[i] - fcnd_prev)) * siginv[k];
f[i] = siginvu[ku] * ((kapu[ku] - sigu[ku]) * f[i] + fu[i] - fprev);
}
/////////////////////////////////////////////////////////////

@oskooi
Copy link
Collaborator Author

oskooi commented Jun 25, 2023

There was a bug in the sign of the curl terms and strides which involved a simple fix:

@@ -346,7 +346,7 @@ bool fields_chunk::step_db(field_type ft) {
         const int dku = gv.iyee_shift(cc).in_direction(dsigu);
         realnum *fu = siginvu && f_u[cc][cmp] ? f[cc][cmp] : 0;
         realnum *the_f = fu ? f_u[cc][cmp] : f[cc][cmp];
-        int sd = ft == D_stuff ? +1 : -1;
+        int sd = ft == D_stuff ? -1 : +1;
         realnum f_m_mult = ft == D_stuff ? 2 : (1 - 2 * cmp) * m;
         realnum dt2 = dt * 0.5;

@stevengj
Copy link
Collaborator

I think the old sd was correct, as we just verified by looking at the update equations and Yee grid.

If a test is failing now and was passing with the old code in step_db, what I would suggest is simply changing the r=0 update equations back into the old ones term-by-term. At some point the test will pass, and that will let us zoom in on the new term that is causing the problem.

@oskooi
Copy link
Collaborator Author

oskooi commented Jul 3, 2023

It seems there was a bug in the Dp update whereby the loop over the $z$ coordinates involving the index iz was one larger than it needed to be:

--- a/src/step_db.cpp
+++ b/src/step_db.cpp
@@ -351,7 +350,7 @@ bool fields_chunk::step_db(field_type ft) {
         realnum f_m_mult = ft == D_stuff ? 2 : (1 - 2 * cmp) * m;
         realnum dt2 = dt * 0.5;
 
-        for (int iz = (ft == D_stuff); iz < nz + (ft == D_stuff); ++iz) {
+        for (int iz = (ft == D_stuff); iz < nz; ++iz) {
           realnum fprev = the_f[iz];
           realnum dfcnd = (sd * Courant) * (f_p[iz] - f_p[iz - sd] - f_m_mult * f_m[iz]);
           if (fcnd) {

@oskooi
Copy link
Collaborator Author

oskooi commented Jul 3, 2023

With the latest commit, this PR finally fixes #2489.

The test for $m = 1$ involves an $E_r$ point source at $r = 0$ within a dielectric disc and two different sets of near-field monitors. The computed far fields are the same regardless of the near-field monitor configuration.

led_disc_cyl_discR1_L10 0_dpmlr1 0_dpmlz1 0_plot2D

led_disc_cyl_radpattern_rp0_res50 0_L10 0_dpmlr1 0_dpmlz1 0

@oskooi oskooi changed the title Specialized field updates for r = 0 in cylindrical coordinates Special field updates for r = 0 and m = 0, ±1 in cylindrical coordinates Jul 3, 2023
… Poynting flux of nearfields to avoid DFT interpolation to voxel center
@oskooi
Copy link
Collaborator Author

oskooi commented Aug 8, 2023

In the failing unit test for the extraction efficiency which involves computing the radiated flux using add_flux (which is based on interpolating the DFT fields to the center of the voxel that we have identified as containing a potential bug at $r = 0$), computing the radiated flux instead from the radiation pattern of the far fields via add_near2far (which does not involve interpolating the DFT fields to the center of the voxel) the test passes: the extraction efficiency computed in cylindrical coordinates matches the 3d Cartesian result.

I think the change from add_flux to add_near2far provides further evidence of the interpolation bug for the DFT fields at $r = 0$.

We can probably go ahead and merge this PR now and then file a bug report that we can address with a separate PR.

@mochen4
Copy link
Collaborator

mochen4 commented Aug 9, 2023

It looks like that the raw Dp fields at r=0 actually do increase with resolution. At t=5, the fields are
Screen Shot 2023-08-09 at 13 32 03

In addition, the fields oscillates twice as fast. For the same period of time=5, I have 500 snapshots of fields at resolution=25 and 1000 snapshots of fields at resolution=50. Therefore, frames 498-500 at resolution 25 should correspond to frames 996-1000 at resolution 50. However, plotting those frames, it looks like that the fields are oscillating at the rate of time stepping:

Screen Shot 2023-08-09 at 13 50 20 Screen Shot 2023-08-09 at 13 50 32

@mochen4
Copy link
Collaborator

mochen4 commented Aug 10, 2023

@oskooi pointed out that, when resolution is doubled, pixel size is halved, so time-domain fields getting doubled would make sense. The evolution of fields in time is reasonable and consistent with what we expect from a correct time stepping. The next step is to check that there is no jump in the raw field as r approaches 0.

@oskooi
Copy link
Collaborator Author

oskooi commented Aug 10, 2023

when resolution is doubled, pixel size is halved, so time-domain fields getting doubled

For reference, due to the finite discretization a $\delta$-function current source has an amplitude of $1 / \Delta x$ where $\Delta x$ is the voxel dimension. This is implemented in:

meep/src/sources.cpp

Lines 480 to 484 in dad3e52

data.amp = amp;
LOOP_OVER_DIRECTIONS(gv.dim, d) {
if (where.in_direction(d) == 0.0 && !nosize_direction(d)) // delta-fun
data.amp *= gv.a; // correct units for J delta-function amplitude
}

Doubling the grid resolution should therefore result in a doubling of the field amplitudes in the time domain. This is what we are observing here.

@mochen4
Copy link
Collaborator

mochen4 commented Aug 15, 2023

In addition, the fields oscillates twice as fast. For the same period of time=5, I have 500 snapshots of fields at resolution=25 and 1000 snapshots of fields at resolution=50. Therefore, frames 498-500 at resolution 25 should correspond to frames 996-1000 at resolution 50. However, plotting those frames, it looks like that the fields are oscillating at the rate of time stepping:

I realized that I was saving both real and imaginary parts of the fields together. If I focus on the real part (i.e. frames with odd number index), the fields evolution would make sense. Here is an animation for the real part of Dp at r=0 for t=0 to 5:

r0_anim.mp4

@mochen4
Copy link
Collaborator

mochen4 commented Aug 15, 2023

On the other hand, using get_array with snap=True to get the fields on Yee grid, I did find a discontinuity along r at r=0, consistent with the previous plot in #2538 (comment)
Dp_along_r

@oskooi
Copy link
Collaborator Author

oskooi commented Aug 21, 2023

Instead of a bug in the $z$-flux calculation at $r = 0$, I think the bug is in the "restriction" procedure used to define an $E_r$ point source at $r = 0$ since $E_r$ is not defined at $r = 0$ on the Yee grid. An $E_r$ point source defined at $r = 0$ should be "restricted" to its nearest Yee-grid locations at $r = 0.5 Δr$ but this does not seem to be happening correctly.

In the failing unit test of the extraction efficiency, if the position of the source is shifted by a voxel away from $r = 0$ the test passes using the flux calculation via add_flux. I modified this test in the latest commit and added this note in the comments:

	# Because (1) Er is not defined at r=0 on the Yee grid, and (2) there                  
	# seems to be a bug in the restriction of an Er point source at r = 0,                 
	# the source is placed at r = ~Δr (just outside the first voxel).                      
	# This incurs a small error which decreases linearly with resolution.                  
	src_pt = mp.Vector3(1.5 / self.resolution, 0, -0.5 * sz + h * dmat)

Note that this hack only seems to work if the source position in $r$ is placed outside of the first voxel. In this example, it is placed in the center of the second voxel (i.e., 1.5 * Δr).

@stevengj
Copy link
Collaborator

Might be worth trying an integrated source, since that adds the current in a completely different place. Might help to determine whether this is problem with the Er field update.

@stevengj
Copy link
Collaborator

stevengj commented Sep 7, 2023

One thing to make sure of is that we are doing the transformation across r=0 correctly when interpolating the source in src_vol_chunkloop. Two things to try:

  1. Around this line add a printf statement to see (1) the amplitude amps_array[idx_vol], (2) the coordinates iloc and loc, and (3) the shift_phase . Do it for a source at r=0 and at r=1.5Δr. In both cases the point source should be mapped to 4 grid points, at ± 1 pixel in Δz and Δr, but in the r=0 case the sources should be at ±Δr/2, which should both get mapped via r_to_minus_r symmetry to +Δr/2 (hopefully with the same sign).
  2. I have a suspicion that we may be subtracting currents from r < 0 instead of adding. You could check that forcing it to always add, by changing this line to remove the shift_phase factor (just to complex<double> amp = data->amp;) whether that helps. (This should have no effect for sources that are not near r = 0, i.e. for r > Δr, since they shouldn't get any contribution from r < 0 points.)

@stevengj
Copy link
Collaborator

If the problem is with sources that touch the r=0 pixel, would be good to check whether the problem is simply an overall normalization or something deeper.

One way to check this is to look at the spectrum, e.g. radiated power, for some nontrivial structure (e.g. a waveguide or sphere), and compare it for a source at r=0 and a source at r=1.5Δx. They should be the same, up to a first-order O(Δx) error, but if there is an overall normalization problem at r=0 they should still be nearly proportional to one another.

@oskooi
Copy link
Collaborator Author

oskooi commented Oct 12, 2023

Plot of the ratio of the radiated flux in air for the LED structure for two configurations of $E_r$ source at $r = 0$ and $r = 1.5 \Delta r$ across a broad bandwidth. Results shown at two resolutions: 200 and 400 pixels/μm. The ratio seems to be converging with resolution to a constant across the bandwidth.

flux_ratio_compare_r0_r1 5_res200

flux_ratio_compare_r0_r1 5_res400

led_cyl_plot2D

@stevengj
Copy link
Collaborator

This looks just like a scale factor (i.e. the ratio is nearly constant over most of the bandwidth). Presumably as you increase the resolution it should get more and more flat.

That means that it's not a problem with the timestepping or anything like that — it's just an issue with the normalization when the source is placed.

@mochen4
Copy link
Collaborator

mochen4 commented Oct 12, 2023

Here is a plot of Er field after 100 wavelength with source at r=0:

Figure_1

And here is a plot with source at r=1.5/Δr:

Figure2

@stevengj
Copy link
Collaborator

Some related problem might occur for the dft_ldos calculation if you have a point source at r=0, since again it involves calculating sources at the origin.

So, for example, if you did the extraction-efficiency tutorial but normalized by a dft_flux rather than dft_ldos, then it might be fine since a normalization error in the source would cancel.

@oskooi
Copy link
Collaborator Author

oskooi commented Oct 16, 2023

So, for example, if you did the extraction-efficiency tutorial but normalized by a dft_flux rather than dft_ldos, then it might be fine since a normalization error in the source would cancel.

I tried placing the $E_r$ source at $r = 0$ and redoing the LED extraction efficiency calculation by replacing the total flux in the denominator computed using dft_ldos with dft_flux. However, the results do not match those from a 3d calculation and therefore the test fails. (In fact, the extraction efficiency in cylindrical coordinates is significantly different than 3D: 0.8 vs. 0.3, respectively.)

This finding is unexpected because based on our discussions last week we had thought the bug in the $E_r$ source was simply introducing a constant scale factor. This scale factor would then be divided out when computing the extraction efficiency using dft_flux to obtain the radiated and total flux in the numerator and denominator, respectively. However, this does not appear to be the case. (As a check, the updated calculation of the extraction efficiency using dft_flux in the denominator does yield the correct result using the master branch. This proves the calculation itself is, in principle, correct.)

@mochen, would you update your field plots above for an $E_r$ source at $r = 0$ and $r = 1.5 \Delta r$ to show scale bars.

@mochen4
Copy link
Collaborator

mochen4 commented Oct 16, 2023

@mochen, would you update your field plots above for an Er source at r=0 and r=1.5Δr to show scale bars.

Here are the plots of fields with colorbars. I fixed their ranges to be the same:

Er source at r=0:
Figure_0

Er source at r=1.5Δr:
Figure_1

@oskooi
Copy link
Collaborator Author

oskooi commented Oct 21, 2023

One thing to make sure of is that we are doing the transformation across r=0 correctly when interpolating the source in src_vol_chunkloop. Two things to try:

  1. Around this line add a printf statement to see (1) the amplitude amps_array[idx_vol], (2) the coordinates iloc and loc, and (3) the shift_phase . Do it for a source at r=0 and at r=1.5Δr. In both cases the point source should be mapped to 4 grid points, at ± 1 pixel in Δz and Δr, but in the r=0 case the sources should be at ±Δr/2, which should both get mapped via r_to_minus_r symmetry to +Δr/2 (hopefully with the same sign).

I added the printf statement within src_vol_chunkloop of sources.cpp to output the relevant quantities described above.

The output for the $E_r$ source at $r = 1.5 \Delta r$ and $r = 0$ (of the parameter src_pt of the function ext_eff_cyl of python/tests/test_ldos.py) is shown below. I ran the test using:

$ mpirun -np 1 python3 test_ldos.py TestLDOS.test_ldos_ext_eff

For both cases, there are only 2 grid points at ± 1 pixel in Δz. There is no restriction of the $E_r$ point source occurring in $r$, regardless of its value. This seems like an obvious bug.

diff --git a/src/sources.cpp b/src/sources.cpp
index e4ef6d1b..6608c3d9 100644
--- a/src/sources.cpp
+++ b/src/sources.cpp
@@ -271,6 +271,9 @@ static void src_vol_chunkloop(fields_chunk *fc, int ichunk, component c, ivec is
 
     vec rel_loc = loc - data->center;
     amps_array[idx_vol] = IVEC_LOOP_WEIGHT(s0, s1, e0, e1, 1) * amp * data->A(rel_loc);
+    printf("src_vol_chunkloop:, %0.5f%+0.5fj, (%d, %d), (%0.5f, %0.5f),  %0.5f%+0.5fj\n",
+           real(amps_array[idx_vol]), imag(amps_array[idx_vol]), iloc.r(), iloc.z(), loc.r(), loc.z(),
+           real(shift_phase), imag(shift_phase));
 
     // check for invalid sources at r=0 in cylindrical coordinates
     if (fc->gv.dim == Dcyl && loc.r() == 0 && amps_array[idx_vol] != 0.0) 
  1. $r = 1.5 \Delta r$
src_vol_chunkloop:, 468.75000+0.00000j, (3, -38), (0.06000, -0.76000),  1.00000+0.00000j
src_vol_chunkloop:, 156.25000+0.00000j, (3, -36), (0.06000, -0.72000),  1.00000+0.00000j
  1. $r = 0$
src_vol_chunkloop:, 234.37500+0.00000j, (1, -38), (0.02000, -0.76000),  1.00000+0.00000j
src_vol_chunkloop:, 78.12500+0.00000j, (1, -36), (0.02000, -0.72000),  1.00000+0.00000j

The bug seems to affect only $E_r$ since if the source component is changed to $E_\phi$ (src_cmpt = mp.Ep), the output shows 4 grid points for the point source at $r = 1.5 \Delta r$ and 2 grid points $r = 0$. This is correct since $E_\phi$ is defined at $r = 0$ on the Yee grid.

  1. $r = 1.5 \Delta r$
src_vol_chunkloop:, 234.37500+0.00000j, (2, -38), (0.04000, -0.76000),  1.00000+0.00000j
src_vol_chunkloop:, 78.12500+0.00000j, (2, -36), (0.04000, -0.72000),  1.00000+0.00000j
src_vol_chunkloop:, 234.37500+0.00000j, (4, -38), (0.08000, -0.76000),  1.00000+0.00000j
src_vol_chunkloop:, 78.12500+0.00000j, (4, -36), (0.08000, -0.72000),  1.00000+0.00000j
  1. $r = 0$
src_vol_chunkloop:, 468.75000+0.00000j, (0, -38), (0.00000, -0.76000),  1.00000+0.00000j
src_vol_chunkloop:, 156.25000+0.00000j, (0, -36), (0.00000, -0.72000),  1.00000+0.00000j

@stevengj
Copy link
Collaborator

stevengj commented Oct 24, 2023

1.5Δr will be exactly on the Yee grid point for Er, so you expect only 2 current points. It would be good to try 2.0Δr, in which case we expect 4 points.

It would also be useful to add some print statements to loop_in_chunks (or step through with a debugger).

  1. After

    meep/src/loop_in_chunks.cpp

    Lines 358 to 359 in 20de9c7

    vec s0(gv.dim), e0(gv.dim), s1(gv.dim), e1(gv.dim);
    compute_boundary_weights(gv, where, is, ie, snap_empty_dimensions, s0, e0, s1, e1);
    to print out is, ie and the weights s0, e0, s1, e1 — this is the bounding box for the where volume (a single point in this case), which may extend outside the computational cell. In the r=0 case, this should include ±Δr/2 points.
  2. After
    volume gvS = S.transform(gv.surroundings(), sn);
    , to see gvS, the grid volume transformed by symmetry — here, the only symmetry should be r_to_minus_r so we should see a loop iteration where the grid is transformed to negative radii
  3. After

    meep/src/loop_in_chunks.cpp

    Lines 423 to 424 in 20de9c7

    ivec iscoS(max(user_volume.little_owned_corner(cgrid), min(_iscoS, _iecoS))),
    iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform
    to see the transformed volume iscoS, iecoS for each chunk. Note that @mochen4 added the intersection with user_volume here, but I forget what user_volume is for cylindrical coordinates — it would be good to check whether this includes negative r values?

(A quick hack to avoid losing the r<0 contributions for an Er source at r=0 would be to double the source amplitude. But we should really fix the user_volume intersection if that is the problem.)

@oskooi
Copy link
Collaborator Author

oskooi commented Oct 24, 2023

1.5Δr will be exactly on the Yee grid point for Er, so you expect only 2 current points. It would be good to try 2.0Δr, in which case we expect 4 points.

Findings:

  1. $E_r$ source placed at $r = \Delta r$ and $r = 2.0\Delta r$ (edge of the Yee-grid voxel) yields 4 restricted grid points.

  2. $E_r$ source at $r = 0.5\Delta r$ and $r = 2.5\Delta r$ (center of the Yee-grid voxel) yields 2 restricted grid points.

In these four cases (output shown below), the sum of the amplitudes of the grid points is always 625.00. This indicates "charge conservation." These results suggest that the restriction for $E_r$ is working correctly for sources placed at $r &gt; 0$.

However, the sum of the amplitudes for an $E_r$ source at $r = 0$ with 2 restricted grid points is 312.5 (output shown above). This is exactly half the expected value of 625.00 due to the two missing restricted grid points at $r &lt; 0$.

  1. $r = \Delta r$ (edge of voxel)
src_vol_chunkloop:, 234.37500+0.00000j, (1, -38), (0.02000, -0.76000), 1.00000+0.00000j
src_vol_chunkloop:, 78.12500+0.00000j, (1, -36), (0.02000, -0.72000), 1.00000+0.00000j
src_vol_chunkloop:, 234.37500+0.00000j, (3, -38), (0.06000, -0.76000), 1.00000+0.00000j
src_vol_chunkloop:, 78.12500+0.00000j, (3, -36), (0.06000, -0.72000), 1.00000+0.00000j
  1. $r = 2.0\Delta r$ (edge of voxel)
src_vol_chunkloop:, 234.37500+0.00000j, (3, -38), (0.06000, -0.76000), 1.00000+0.00000j
src_vol_chunkloop:, 78.12500+0.00000j, (3, -36), (0.06000, -0.72000), 1.00000+0.00000j
src_vol_chunkloop:, 234.37500+0.00000j, (5, -38), (0.10000, -0.76000), 1.00000+0.00000j
src_vol_chunkloop:, 78.12500+0.00000j, (5, -36), (0.10000, -0.72000), 1.00000+0.00000j
  1. $r = 0.5\Delta r$ (center of voxel)
src_vol_chunkloop:, 468.75000+0.00000j, (1, -38), (0.02000, -0.76000), 1.00000+0.00000j
src_vol_chunkloop:, 156.25000+0.00000j, (1, -36), (0.02000, -0.72000), 1.00000+0.00000j
  1. $r = 2.5\Delta r$ (center of voxel)
src_vol_chunkloop:, 468.75000+0.00000j, (5, -38), (0.10000, -0.76000), 1.00000+0.00000j
src_vol_chunkloop:, 156.25000+0.00000j, (5, -36), (0.10000, -0.72000), 1.00000+0.00000j

@oskooi
Copy link
Collaborator Author

oskooi commented Oct 26, 2023

Additional output from items 1-3 listed above. The main finding is that iscoS and iecoS do not contain negative values for the $r$ coordinate for an $E_r$ source at $r = 0$ as expected.

--- a/src/loop_in_chunks.cpp
+++ b/src/loop_in_chunks.cpp
@@ -357,6 +357,12 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con
 
   vec s0(gv.dim), e0(gv.dim), s1(gv.dim), e1(gv.dim);
   compute_boundary_weights(gv, where, is, ie, snap_empty_dimensions, s0, e0, s1, e1);
+  printf("is=(%d, %d), ie=(%d, %d)\n",
+         is.r(), is.z(), ie.r(), ie.z());
+  printf("s0=(%0.4f, %0.4f), e0=(%0.4f, %0.4f)\n",
+         s0.r(), s0.z(), e0.r(), e0.z());
+  printf("s1=(%0.4f, %0.4f), e1=(%0.4f, %0.4f)\n",
+         s1.r(), s1.z(), e1.r(), e1.z());

   int original_vol = 1;
   LOOP_OVER_DIRECTIONS(gv.dim, d) {
@@ -377,6 +383,11 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con
   for (int sn = 0; sn < (use_symmetry ? S.multiplicity() : 1); ++sn) {
     component cS = S.transform(cgrid, -sn);
     volume gvS = S.transform(gv.surroundings(), sn);
+
+    printf("gvS:, min_corner=(%0.5f, %0.5f), max_corner=(%0.5f, %0.5f)\n",
+           gvS.get_min_corner().r(), gvS.get_min_corner().z(),
+           gvS.get_max_corner().r(), gvS.get_max_corner().z());
     vec L(gv.dim);
     ivec iL(gv.dim);
 
@@ -422,6 +433,8 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con
         ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn));
         ivec iscoS(max(user_volume.little_owned_corner(cgrid), min(_iscoS, _iecoS))),
             iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform
+        printf("iscoS=(%d, %d), iecoS=(%d, %d)\n",
+               iscoS.r(), iscoS.z(), iecoS.r(), iecoS.z());
  1. $r = 0$
is=(-1, -38), ie=(1, -36)
s0=(0.5000, 0.7500), e0=(0.5000, 0.2500)
s1=(0.5000, 0.2500), e1=(0.5000, 0.7500)
  1. $r = 0.5\Delta r$
is=(1, -38), ie=(1, -36)
s0=(1.0000, 0.7500), e0=(1.0000, 0.2500)
s1=(1.0000, 0.2500), e1=(1.0000, 0.7500)
  1. $r = 1.0\Delta r$
is=(1, -38), ie=(3, -36)
s0=(0.5000, 0.7500), e0=(0.5000, 0.2500)
s1=(0.5000, 0.2500), e1=(0.5000, 0.7500)
  1. $r = 1.5\Delta r$
is=(3, -38), ie=(3, -36)
s0=(1.0000, 0.7500), e0=(1.0000, 0.2500)
s1=(1.0000, 0.2500), e1=(1.0000, 0.7500)
  1. $r = 2.0\Delta r$
is=(3, -38), ie=(5, -36)
s0=(0.5000, 0.7500), e0=(0.5000, 0.2500)
s1=(0.5000, 0.2500), e1=(0.5000, 0.7500)

For all five test cases, the output is always:

gvS:, min_corner=(0.00000, -0.84000), max_corner=(6.52000, 0.88000)

iscoS=(299, 18), iecoS=(325, 44)
iscoS=(299, -40), iecoS=(325, 16)
iscoS=(1, 18), iecoS=(297, 44)
iscoS=(1, -40), iecoS=(297, 16)

@oskooi
Copy link
Collaborator Author

oskooi commented Oct 29, 2023

Results from a separate experiment involving an $E_r$ point source at various positions along $r$ and investigating the convergence with resolution of the radiated flux via add_flux (column labeled out_flux), total flux via dft_ldos (column labeled total_flux), and extraction efficiency (column labeled ext_eff). The findings demonstrate that the extraction efficiency is incorrect whenever $E_r$ is restricted to any grid point within the first voxel.

The output is organized into four columns:

resolution, out_flux, total_flux, ext_eff

Resolutions tested were 25, 50, 100, and 200 pixels/μm.

  1. $r = 0$, 2 restricted points (should be 4 restricted points) [ incorrect result ]
25,  3.369926, 0.979566, 3.440224
50,  3.040429, 0.239534, 12.693084
100, 2.996429, 0.062598, 47.867953
200, 2.987436, 0.016272, 183.593285
  1. $r = 0.5\Delta r$, 2 restricted points [ incorrect result ]
25,  13.479703, 3.918263, 3.440224
50,  12.161716, 0.958137, 12.693084
100, 11.985716, 0.250391, 47.867953
  1. $r = 1.0\Delta r$, 4 restricted points [ incorrect result ]
25,  7.945538, 14.760318, 0.538304
50,  4.183601,  3.775316, 1.108146
100, 3.271576,  0.997740, 3.278987
200, 3.056390,  0.260119, 11.749961
  1. $r = 1.3\Delta r$, restricted points [ incorrect result ]
25,  8.698912, 24.393588, 0.356606
50,  2.534997,  6.344131, 0.399581
100, 0.973209,  1.683729, 0.578008
200, 0.601769,  0.439425, 1.369446
  1. $r = 1.4\Delta r$, 4 restricted points [ incorrect result ]
25,  9.629016, 28.103474, 0.342627
50,  2.508571,  7.345236, 0.341524
100, 0.695439,  1.951878, 0.356292
200, 0.263794,  0.509565, 0.517685
  1. $r = 1.5\Delta r$, 2 restricted points [ correct result ]
25,  10.898609, 32.056495, 0.339981
50,   2.743680,  8.418313, 0.325918
100,  0.661845,  2.239738, 0.295501
  1. $r = 1.8\Delta r$, 4 restricted points [ correct result ]
25, 14.878816, 42.740855, 0.348117
50,  3.897262, 11.874070, 0.328216
100, 0.949759,  3.207856, 0.296073
  1. $r = 2.0\Delta r$, 4 restricted points [ correct result ]
25, 17.881969, 50.531040, 0.353881
50,  4.778999, 14.487701, 0.329866
100, 1.170510,  3.948137, 0.296471

Cases 6-8 produce the correct result for the extraction efficiency because they do not involve restricting to $E_r$ grid points within the first voxel unlike Cases 1-5.

@oskooi
Copy link
Collaborator Author

oskooi commented Nov 2, 2023

Results for the experiment above but computing the total flux using add_flux rather than dft_ldos. The columns in the output for the various runs shown below are:

resolution, radiated flux, total flux, (radiated flux) / (total flux)

The ratio of the radiated to the total flux is converging with resolution to the same constant value for all dipole positions.

  1. $r = 0$
25,  3.369926, 4.007910, 0.840819
50,  3.040429, 3.201351, 0.949733
100, 2.996429, 3.040487, 0.985510
  1. $r = 0.5\Delta r$
25,  13.479703, 16.031640, 0.840819
50,  12.161716, 12.805405, 0.949733
100, 11.985716, 12.161949, 0.985510
  1. $r = 1.0\Delta r$
25,  7.945538, 17.276357, 0.459908
50,  4.183601,  6.702135, 0.624219
100, 3.271576,  3.972616, 0.823532
200, 3.056390,  3.242241, 0.942678

@stevengj
Copy link
Collaborator

stevengj commented Nov 3, 2023

Since the remaining problems are simply an overall normalization factor for Er delta function sources at (or near) r=0, I think we can go ahead an merge.

@stevengj stevengj merged commit 4cb2d36 into NanoComp:master Nov 3, 2023
@oskooi oskooi deleted the cyl_m0_r0 branch November 4, 2023 03:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Radiation pattern of a dipole emitter does not converge with resolution in cylindrical coordinates
3 participants