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

Bug with 3D prism edges #400

Closed
DerekK44 opened this issue Jul 1, 2018 · 31 comments
Closed

Bug with 3D prism edges #400

DerekK44 opened this issue Jul 1, 2018 · 31 comments

Comments

@DerekK44
Copy link
Contributor

DerekK44 commented Jul 1, 2018

Hello,

I'd like to report what seems to be a bug I've noticed for the subpixel averaging of outer edges of prisms with large inner surfaces. For example, a prism with a narrow opening and large inner surface (such as the shape drawn below) will have the issue:

inner_surface

The issue seems to be that when meep (or libctl) builds the structure or does the subpixel averaging, some regions of the polygon have a shorter dimension than others, as shown by looking at slices:

meep_inner_surface_annotated

meep_3d_annotated

This issue becomes especially problematic when the prism thickness is on the order of a few pixels. I have attached a MWE to help reproduce the issue above, as well as shows another example with issues generating the following polygon (of width 2 pixels large):

thin_polygon

meep_thin_polygon_annotated

Let me know if I can provide any other information. Sorry I can't be of more help in finding the source of the issue. I'm still looking through meep/libctl to understand how things work.

@stevengj
Copy link
Collaborator

stevengj commented Jul 5, 2018

cc @HomerReid

@m9631m
Copy link

m9631m commented Jul 5, 2018

Just a suggestion, sometimes Paraview interpolates the points to make it nicer. What you are saying, is clearly an issue but for 2D outputs, I usually plot it with points (with large point size) to make sure it is not visualization issue.
capture

@DerekK44
Copy link
Contributor Author

DerekK44 commented Jul 6, 2018

Thanks for the suggestion, m9631m. Attached are the 3D images using the "points" rather than a "surface". We can see the features I reported above in both visualization modes:

meep_3d_points
meep_thin_polygon_points

@HomerReid
Copy link
Contributor

Thanks for the well-documented bug report and especially for being the first to do MEEP calculations involving recognizers from TRON, the greatest sci-fi film of all time.

Two questions:
(1) How did you make those nice plots in paraview, using points and using surfaces?
(2) Your prisms are extruded in the Y direction. Does the issue remain if you rotate so that the prisms are extruded in the Z direction?

For the time being, I haven't exactly reproduced your result, but I do observe a discrepancy in the epsilon grids with and without averaging. The following images show constant-Y slices at two heights: one at the lower boundary of the object, and one slicing through the object.

Lower boundary:

ymin

Slicing through object:

yminp1

@stevengj
Copy link
Collaborator

Thanks Homer, that's informative. It looks like there is a bug in the point_in_object routine for the prism.

@HomerReid
Copy link
Contributor

In libctl#18 I did find and fix a subtle issue in the point-in-prism algorithm that was responsible for the spurious narrow strips of purple in the figures above.

However, I'm not sure whether or not this fixes the original issue. Here's a threshold image, similar to the above, produced after fixing the above issue.

recognizer

It looks like the issue may still be there.

When you get a chance, could you double-check whether or not the updated version of libctl makes any difference on your geometries? It would also be useful to know if anything changes when the prism axis is the z-axis rather than the y-axis.

@DerekK44
Copy link
Contributor Author

Hi Homer,

Thanks for looking into this. To answer your questions above:

  1. First, I converted the epsilon .h5 to .vtk (using h5tovtk), then opened the .vtk in paraview. Then, with the .vtk file selected in the "Pipeline Browser" window, I applied a "Threshold". Under the Threshold options, I set a range (max and min of the data to be shown). This range is what I included in the text annotations above.

  2. I checked both y-extruded and z-extruded prisms before and after rebuilding with the changes you made to libctl. Unfortunately the problem seems to persist in all the cases I tested.

@DerekK44
Copy link
Contributor Author

I double checked the math in intersect_line_with_segment, and it seems right to me..

Is it possible there's a subtle bug somewhere in the intersect_line_with_prism or normal_to_prism routines of geom.c? Especially since the problem occurs at the surfaces of the prism, including the top/bottom prism surfaces. I'll keep looking

@HomerReid
Copy link
Contributor

Yes, it seems likely that your geometry exposes a bug in one of the new prism geometry routines in libctl (specifically in utils/geom.c) that wasn't hit by previous geometries. My strategy for pursuing it has been to instantiate your structure in two ways, as a prism and as a union of blocks, and look for differences in the results of calls to normal_to_object and other routines. Let me know if you come across any further insights.

@DerekK44
Copy link
Contributor Author

DerekK44 commented Jul 11, 2018

Ok, I think I have an idea of the problem.

In normal_to_prism (lines 2339 - 2369) the normal distance between each prism surface and a point is computed. It seems to assume the plane of the prism surface extends to infinity, which is where I think the problem is.

As a result, a prism surface that is far away from the point can be chosen as the "closest surface" if the normal of the infinite plane is sufficiently small. See image attached. For point xp that lies on the top surface (in the out-of-page direction) of a 3D 'recognizer' :) the top surface of the prism should be chosen, and the normal-direction should be out-of-page. However, the surface in blue could actually be selected (even though it is far away, the normal-distance from the plane is zero).

sketch

Perhaps the normal distance should be used only if the normal vector (containing the point) intersects with the plane on the true surface (i.e. the blue region shown above). However, if the normal vector does not intersect with the true surface, then the shortest Euclidean distance from the edges (like points v1 and v2 above) of the true surface could be used?

@DerekK44 DerekK44 changed the title Bug with subpixel averaging of 3D prism edges Bug with 3D prism edges Jul 11, 2018
@HomerReid
Copy link
Contributor

Many thanks for that brilliant piece of detective work! It would have taken me a very long time to identify this issue. I implemented a fix in https://github.com/stevengj/libctl/pull/19 that does seem to correct the problem.

recognizer

@DerekK44
Copy link
Contributor Author

Thanks for the fix! I rebuilt libctl using your specific branch and do get the same result for the MWE using -case=0. Could you see if -case=1 and -case=2 are also fixed? When I tried I seem to still be getting the same bug for those cases..

@HomerReid
Copy link
Contributor

I've updated libctl#19 with a revised implementation of normal_to_object for prisms. It now seems to be doing the right thing on your case-2 geometry:

case2

but I'm not sure if it's doing the right thing on the recognizers.

@HomerReid
Copy link
Contributor

Just FYI, the updates to point_in_prism and normal_to_object for prisms have now been merged into libctl. Here are pictures of the recognizer with resolution={30,60}:

  • Resolution 30:

case1_30

  • Resolution 60:

case1_60

@DerekK44
Copy link
Contributor Author

Hi Homer,

Thanks for the updates. I think in the -case=2 image above, there's an area actually missing (below they are side-by-side):
combined

I found one potential issue in the point_in_polygon routine. If the plumb line goes through a corner, it's always counted as an intersection. However, if the plumb line intersects a corner, but this does not cause the line to exit the polygon (like in the second image below), it mistakenly calls this an intersection. In the image below, closed circles denote points where the intersection is counted (t==0) and open circles are where intersections are not counted (t>1) when checking each edge.

corner_case

One potential fix is to first check if the line goes through a vertex (i.e. if t==0 at Line 2156), and then (if so) check if the vertices before and after lie on the same side of the plumb line. One way of doing so is checking the cross-products:

vp1 = vector3_minus(v[i+1], p1);
vm1 = vector3_minus(v[i-1], p1);
cp1 = vector3_cross(v1, vp1);
cm1 = vector3_cross(v1, vm1);
if (cp1.z * cm1.z < 0) {
   // count the intersection, vertices on opposite side of plumb-line
} else {
   // skip (this is a corner!)
}

I am not sure if this is the bug that is causing all the weird effects on the recognizers, but this seemed to cause issues when I rotate the recognizer by 45 degrees. I'll keep looking and see if I find anything else!

@DerekK44
Copy link
Contributor Author

Ok, so I suspect the second bug (that is causing the edges to be off in the 'recognizers') is happening in the intersect_line_with_prism routine (line 2230). Currently, slist appears to only hold the value of two intersections (the two with smallest s values). As a result, for shapes with inner surfaces (where the line can go in and out of the prism multiple times), overlap_integrand() (line 1240) is only over the first two intersection points.

Might this be the cause of the above issue?

@HomerReid
Copy link
Contributor

Thanks again for all the detective work and the extremely useful bug reports.

The issue with the plumb lines intersecting corners is subtle indeed. Thanks for identifying it, and for proposing a fix. I will patch the code to take this into account, but I suspect it's not the primary cause of the main issue you reported---it seems like it would only affect a small subset of points that happen to satisfy this geometrical coincidence, whereas the issue you report seems to extend over wider regions.

The second possibility you raise is also a plausible culprit. The idea of retaining only the nearest two intersections was proposed by @stevengj (see e.g. here), with the idea being that subpixel averaging is entirely local and cannot be affected by distant surface intersections. However, it seems possible that this reasoning only applies for star-shaped objects, or at any rate is invalid for objects with inner surfaces like in this case.

One observation is that the issue seems to be present for the recognizers only when the claw feet are present, and not for the simple inverted-U shape in which the claws are absent. In particular, the subpixel averaging at points on the top of recognizer seems to depend on the presence or absence of claw feet at the same horizontal position on the opposite side of the structure, even though those are far away and shouldn't affect local considerations. I've been working on trying to pinpoint what is going on with this.

@stevengj, as the designer of the sub-pixel averaging algorithm, do you have any guesses as to why subpixel averaging behaves differently for the recognizer and the simple inverted U?

@stevengj
Copy link
Collaborator

One of point-in-object, normal-to-object, or intersect-line-with-object must be giving a different result for the "U" and the "recognizer" shapes, up for a pixel at the top far from the claws where they really should be identical.

So,

  1. Find the coordinates of a pixel in the upper part where you are getting a different ε. (At worst, this could mean putting a printf(x,y,z, ε) statement to to dump out all of the pixels, perhaps only for sufficiently large x so that you don't look at the claws, and then using diff to find the first line that is different.

  2. For this pixel, output the results of all of the point-in-object, normal-to-object, or intersect-line-with-object calls for the two cases. The result that is different will be your culprit.

@stevengj
Copy link
Collaborator

stevengj commented Jul 19, 2018

@HomerReid, you said something today about box_overlap_with_object only returning a few possible values. In general, it should return a continuously varying result as you move the box continuously. (In a particular case like the "U" shape with flat surfaces, at a fixed resolution there will only be a few possible overlaps.)

For example, the following test program, which computes the overlap of a box and a sphere, gives a continuously varying result as I vary the input argument x:

#include <ctlgeom.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
    geom_box b = {{0,-2,-2},{1,2,2}};
    vector3 c = {0,0,0};
    geometric_object o = make_sphere(NULL, c, 1);
    double x = argc > 1 ? atof(argv[1]) : 0;
    b.low.x += x;
    b.high.x += x;
    printf("overlap = %g\n", box_overlap_with_object(b, o, 1e-10, 1000000));
    return 0;
}

@HomerReid
Copy link
Contributor

HomerReid commented Jul 23, 2018

In libctl#20 I have overhauled the intersect_line_segment_with_prism implementation to do the full, exact calculation, with no assumptions about proximity. (The unit test is currently failing, but I think this is because the new calculation is more accurate than the legacy calculation for blocks. I'm looking into it.) This is now fixed.

Meanwhile, images of the recognizer and pincer structures appear to be much improved. I'm continuing to investigate, but I think it's ready for more testing when you have a chance.

case1

case2

@oskooi
Copy link
Collaborator

oskooi commented Jul 30, 2018

There is still a bug in the prism objects which produces different results when compared to an identical structure made out of blocks. This is demonstrated in the simulation script below which involves the "claw" structure from above created using either a prism (use_prism = True) or three overlapping blocks (use_prism = False). A single dipole source is placed within the center of the computational and the field is output at a random position at every 5 time units. The results from the two runs are noticeably different. These results are based on the latest libctl which includes stevengj/libctl#21.

Simulation Script

import meep as mp

cell_size = mp.Vector3(5,5,5)

Si = mp.Medium(index=3.5)

h = 1.0

use_prism = True

if use_prism:
     v = [mp.Vector3(-1.0,0,-1),
          mp.Vector3(-1.0,0,-0.2),
          mp.Vector3(-0.5,0,-0.2),
          mp.Vector3(-0.5,0,-0.8),
          mp.Vector3(1.0,0,-0.8),
          mp.Vector3(1.0,0,0.8),
          mp.Vector3(-0.5,0,0.8),
          mp.Vector3(-0.5,0,0.2),
          mp.Vector3(-1.0,0,0.2),
          mp.Vector3(-1.0,0,1),
          mp.Vector3(1.5,0,1),
          mp.Vector3(1.5,0,-1)]
    geometry = [mp.Prism(v, axis=mp.Vector3(0,1,0), height=h, material=Si)]
else:
    geometry = [mp.Block(material=Si, size=mp.Vector3(2.5,h,2.0), center=mp.Vector3()),
                mp.Block(material=mp.air, size=mp.Vector3(1.5,h+0.1,1.6), center=mp.Vector3()),
                mp.Block(material=mp.air, size=mp.Vector3(0.5,h+0.1,0.4), center=mp.Vector3(-1.0,0,0))]

boundary_layers = [mp.PML(thickness=1.0)]

fcen = 1.0
sources = [mp.Source(mp.GaussianSource(fcen, fwidth=0.2*fcen), component=mp.Ey, center=mp.Vector3())]

sim = mp.Simulation(resolution=20,
                    geometry=geometry,
                    cell_size=cell_size,
                    boundary_layers=boundary_layers,
                    sources=sources)
                   
def print_stuff(sim):
    p = sim.get_field_point(mp.Ex, mp.Vector3(-0.21,0.37,0.18))
    print("ex:, {}, {}".format(sim.meep_time(), p.real))

sim.run(mp.at_beginning(mp.output_epsilon), mp.at_every(5,print_stuff), until_after_sources=0)

Output (blocks)

Using MPI version 3.0, 1 processes
-----------
Initializing structure...
Working in 3D dimensions.
Computational cell is 5 x 5 x 5 with resolution 20
     block, center = (0,0,0)
          size (2.5,1,2)
          axes (1,0,0), (0,1,0), (0,0,1)
          dielectric constant epsilon diagonal = (12.25,12.25,12.25)
     block, center = (0,0,0)
          size (1.5,1.1,1.6)
          axes (1,0,0), (0,1,0), (0,0,1)
          dielectric constant epsilon diagonal = (1,1,1)
     block, center = (-1,0,0)
          size (0.5,1.1,0.4)
          axes (1,0,0), (0,1,0), (0,0,1)
          dielectric constant epsilon diagonal = (1,1,1)
time for set_epsilon = 1.55153 s
-----------
Meep progress: 2.725/50.0 = 5.5% done in 4.0s, 69.5s to go
on time step 109 (time=2.725), 0.0367454 s/step
ex:, 5.0, 9.865324182369947e-05
Meep progress: 7.300000000000001/50.0 = 14.6% done in 8.0s, 46.9s to go
on time step 292 (time=7.3), 0.0219486 s/step
ex:, 10.0, 0.0032943404168923694
Meep progress: 11.8/50.0 = 23.6% done in 12.0s, 39.0s to go
on time step 472 (time=11.8), 0.022331 s/step
ex:, 15.0, 0.03985741577315191
Meep progress: 16.375/50.0 = 32.8% done in 16.0s, 33.0s to go
on time step 655 (time=16.375), 0.0219037 s/step
ex:, 20.0, 0.1708871810594407
Meep progress: 20.950000000000003/50.0 = 41.9% done in 20.1s, 27.8s to go
on time step 838 (time=20.95), 0.0219113 s/step
ex:, 25.0, 0.24280444568935178
Meep progress: 25.525000000000002/50.0 = 51.1% done in 24.1s, 23.1s to go
on time step 1021 (time=25.525), 0.0219009 s/step
ex:, 30.0, 0.07210648702450055
Meep progress: 30.075000000000003/50.0 = 60.2% done in 28.1s, 18.6s to go
on time step 1203 (time=30.075), 0.022085 s/step
Meep progress: 34.550000000000004/50.0 = 69.1% done in 32.1s, 14.4s to go
on time step 1382 (time=34.55), 0.022456 s/step
ex:, 35.0, -0.05670679581798045
Meep progress: 39.075/50.0 = 78.2% done in 36.1s, 10.1s to go
on time step 1563 (time=39.075), 0.0221081 s/step
ex:, 40.0, -0.038714822218883
Meep progress: 43.650000000000006/50.0 = 87.3% done in 40.1s, 5.8s to go
on time step 1746 (time=43.65), 0.0219684 s/step
ex:, 45.0, 0.016713171093002025
Meep progress: 48.225/50.0 = 96.5% done in 44.1s, 1.6s to go
on time step 1929 (time=48.225), 0.0219006 s/step
ex:, 50.0, 0.04073459642769224
run 0 finished at t = 50.0 (2000 timesteps)

Elapsed run time = 47.2606 s

Geometry (blocks)

claw_blocks

Output (prism)

Using MPI version 3.0, 1 processes
-----------
Initializing structure...
Working in 3D dimensions.
Computational cell is 5 x 5 x 5 with resolution 20
     prism, center = (-0.0833333,0.5,-1.85037e-17)
          height 1, axis (1,0,0), 12 vertices:
          {-9.166667e-01,0.000000e+00,-1.000000e+00}
          {-9.166667e-01,0.000000e+00,-2.000000e-01}
          {-4.166667e-01,0.000000e+00,-2.000000e-01}
          {-4.166667e-01,0.000000e+00,-8.000000e-01}
          {1.083333e+00,0.000000e+00,-8.000000e-01}
          {1.083333e+00,0.000000e+00,8.000000e-01}
          {-4.166667e-01,0.000000e+00,8.000000e-01}
          {-4.166667e-01,0.000000e+00,2.000000e-01}
          {-9.166667e-01,0.000000e+00,2.000000e-01}
          {-9.166667e-01,0.000000e+00,1.000000e+00}
          {1.583333e+00,0.000000e+00,1.000000e+00}
          {1.583333e+00,0.000000e+00,-1.000000e+00}
          dielectric constant epsilon diagonal = (12.25,12.25,12.25)
time for set_epsilon = 2.07346 s
-----------
Meep progress: 2.725/50.0 = 5.5% done in 4.0s, 69.6s to go
on time step 109 (time=2.725), 0.0367798 s/step
ex:, 5.0, 9.732717883048765e-05
Meep progress: 7.275/50.0 = 14.6% done in 8.0s, 47.0s to go
on time step 291 (time=7.275), 0.0219883 s/step
ex:, 10.0, 0.003317982045276894
Meep progress: 11.675/50.0 = 23.4% done in 12.0s, 39.5s to go
on time step 467 (time=11.675), 0.0227789 s/step
ex:, 15.0, 0.04161624353753418
Meep progress: 16.25/50.0 = 32.5% done in 16.0s, 33.3s to go
on time step 650 (time=16.25), 0.0219516 s/step
ex:, 20.0, 0.19303261773676653
Meep progress: 20.825000000000003/50.0 = 41.7% done in 20.0s, 28.1s to go
on time step 833 (time=20.825), 0.0219172 s/step
ex:, 25.0, 0.33507522812004353
Meep progress: 25.400000000000002/50.0 = 50.8% done in 24.1s, 23.3s to go
on time step 1016 (time=25.4), 0.0219128 s/step
Meep progress: 29.975/50.0 = 60.0% done in 28.1s, 18.8s to go
on time step 1199 (time=29.975), 0.0219149 s/step
ex:, 30.0, 0.22382016144998923
Meep progress: 34.525/50.0 = 69.0% done in 32.1s, 14.4s to go
on time step 1381 (time=34.525), 0.0220515 s/step
ex:, 35.0, 0.06693557742000977
Meep progress: 39.1/50.0 = 78.2% done in 36.1s, 10.1s to go
on time step 1564 (time=39.1), 0.0219666 s/step
ex:, 40.0, 0.028284435301408044
Meep progress: 43.675000000000004/50.0 = 87.4% done in 40.1s, 5.8s to go
on time step 1747 (time=43.675), 0.021935 s/step
ex:, 45.0, 0.018579334381354083
Meep progress: 48.175000000000004/50.0 = 96.4% done in 44.1s, 1.7s to go
on time step 1927 (time=48.175), 0.022311 s/step
ex:, 50.0, 0.008328699044089125
run 0 finished at t = 50.0 (2000 timesteps)

Elapsed run time = 47.8274 s

Geometry (prism)
Note the small surface variations which were there previously.

claw_prism

@oskooi
Copy link
Collaborator

oskooi commented Jul 31, 2018

Even with a convex structure and the subpixel averaging turned off, the results are significantly different when using either a prism (use_prism = True) or a set of three overlapping blocks (use_prism = False) to represent the structure (a hexagonal prism). This is demonstrated in the following simulation script which is adapted from above. This might suggest that the remaining prism bug is not related to the particular shape of the prismatic structure (i.e., convex vs. non-convex) or to computing surface normals via subpixel averaging (i.e., for voxels spanning edges and corners) but rather to the way the prism itself is represented on the Yee grid.

Simulation Script

import meep as mp
import math

cell_size = mp.Vector3(5,5,5)

Si = mp.Medium(index=3.5)

use_prism = True

if use_prism:
    vertices = [mp.Vector3(-1,0),
                mp.Vector3(-0.5,math.sqrt(3)/2),
                mp.Vector3(+0.5,math.sqrt(3)/2),
                mp.Vector3(+1,0),
                mp.Vector3(+0.5,-math.sqrt(3)/2),
                mp.Vector3(-0.5,-math.sqrt(3)/2)]

    geometry = [mp.Prism(vertices, height=1.0, material=Si)]
else:
    geometry = [mp.Block(material=Si, size=mp.Vector3(1,math.sqrt(3),1), center=mp.Vector3()),
                mp.Block(material=Si, size=mp.Vector3(1,math.sqrt(3),1), center=mp.Vector3(),
                         e1=mp.Vector3(0.5,+math.sqrt(3)/2,0), e2=mp.Vector3(-math.sqrt(3)/2,0.5,0), e3=mp.Vector3(0,0,1)),
                mp.Block(material=Si, size=mp.Vector3(1,math.sqrt(3),1), center=mp.Vector3(),
                         e1=mp.Vector3(0.5,-math.sqrt(3)/2,0), e2=mp.Vector3(+math.sqrt(3)/2,0.5,0), e3=mp.Vector3(0,0,1))]

boundary_layers = [mp.PML(thickness=1.0)]

fcen = 1.0
sources = [mp.Source(mp.GaussianSource(fcen, fwidth=0.2*fcen), component=mp.Ez, center=mp.Vector3())]

sim = mp.Simulation(resolution=20,
                    eps_averaging=False,
                    geometry=geometry,
                    cell_size=cell_size,
                    boundary_layers=boundary_layers,
                    sources=sources)

def print_stuff(sim):
    p = sim.get_field_point(mp.Ex, mp.Vector3(-0.21,0.37,0.18))
    print("ex:, {}, {}".format(sim.meep_time(), p.real))

sim.run(mp.at_beginning(mp.output_epsilon), mp.at_every(5,print_stuff), until_after_sources=0)

Output (blocks)

Using MPI version 3.0, 1 processes
-----------
Initializing structure...
Working in 3D dimensions.
Computational cell is 5 x 5 x 5 with resolution 20
     block, center = (0,0,0)
          size (1,1.73205,1)
          axes (1,0,0), (0,1,0), (0,0,1)
          dielectric constant epsilon diagonal = (12.25,12.25,12.25)
     block, center = (0,0,0)
          size (1,1.73205,1)
          axes (0.5,0.866025,0), (-0.866025,0.5,0), (0,0,1)
          dielectric constant epsilon diagonal = (12.25,12.25,12.25)
     block, center = (0,0,0)
          size (1,1.73205,1)
          axes (0.5,-0.866025,0), (0.866025,0.5,0), (0,0,1)
          dielectric constant epsilon diagonal = (12.25,12.25,12.25)
time for set_epsilon = 0.284494 s
-----------
Meep progress: 2.725/50.0 = 5.5% done in 4.0s, 69.4s to go
on time step 109 (time=2.725), 0.0367117 s/step
ex:, 5.0, 1.5134000477694057e-05
Meep progress: 7.300000000000001/50.0 = 14.6% done in 8.0s, 46.9s to go
on time step 292 (time=7.3), 0.0219284 s/step
ex:, 10.0, 0.0006954312408198126
Meep progress: 11.875/50.0 = 23.8% done in 12.0s, 38.6s to go
on time step 475 (time=11.875), 0.0219215 s/step
ex:, 15.0, 0.011882121748927858
Meep progress: 16.45/50.0 = 32.9% done in 16.0s, 32.7s to go
on time step 658 (time=16.45), 0.021934 s/step
ex:, 20.0, 0.07671705546362241
Meep progress: 21.025000000000002/50.0 = 42.1% done in 20.1s, 27.6s to go
on time step 841 (time=21.025), 0.02194 s/step
ex:, 25.0, 0.19213588524242717
Meep progress: 25.6/50.0 = 51.2% done in 24.1s, 22.9s to go
on time step 1024 (time=25.6), 0.0219239 s/step
ex:, 30.0, 0.16116783319546565
Meep progress: 30.150000000000002/50.0 = 60.3% done in 28.1s, 18.5s to go
on time step 1206 (time=30.15), 0.0220027 s/step
Meep progress: 34.7/50.0 = 69.4% done in 32.1s, 14.1s to go
on time step 1388 (time=34.7), 0.0220651 s/step
ex:, 35.0, -0.10359283960906834
Meep progress: 39.225/50.0 = 78.5% done in 36.1s, 9.9s to go
on time step 1569 (time=39.225), 0.0221598 s/step
ex:, 40.0, -0.2221164734612176
Meep progress: 43.725/50.0 = 87.5% done in 40.1s, 5.8s to go
on time step 1749 (time=43.725), 0.0222353 s/step
ex:, 45.0, -0.037386521008982175
Meep progress: 48.300000000000004/50.0 = 96.6% done in 44.1s, 1.6s to go
on time step 1932 (time=48.3), 0.0219241 s/step
ex:, 50.0, -0.08403968687554737
run 0 finished at t = 50.0 (2000 timesteps)

Elapsed run time = 45.9072 s

Geometry (blocks)
hexagonal_prism_blocks

Output (prism)

Using MPI version 3.0, 1 processes
-----------
Initializing structure...
Working in 3D dimensions.
Computational cell is 5 x 5 x 5 with resolution 20
     prism, center = (0,0,0.5)
          height 1, axis (0,0,1), 6 vertices:
          {-1.000000e+00,5.551115e-17,0.000000e+00}
          {-5.000000e-01,8.660254e-01,0.000000e+00}
          {5.000000e-01,8.660254e-01,0.000000e+00}
          {1.000000e+00,-5.551115e-17,0.000000e+00}
          {5.000000e-01,-8.660254e-01,0.000000e+00}
          {-5.000000e-01,-8.660254e-01,0.000000e+00}
          dielectric constant epsilon diagonal = (12.25,12.25,12.25)
time for set_epsilon = 0.292215 s
-----------
Meep progress: 2.725/50.0 = 5.5% done in 4.0s, 69.4s to go
on time step 109 (time=2.725), 0.0367039 s/step
ex:, 5.0, -1.8147795423358433e-05
Meep progress: 7.300000000000001/50.0 = 14.6% done in 8.0s, 46.8s to go
on time step 292 (time=7.3), 0.0218697 s/step
ex:, 10.0, -0.0008000210684242493
Meep progress: 11.875/50.0 = 23.8% done in 12.0s, 38.6s to go
on time step 475 (time=11.875), 0.0218903 s/step
ex:, 15.0, -0.013022116768411272
Meep progress: 16.45/50.0 = 32.9% done in 16.0s, 32.7s to go
on time step 658 (time=16.45), 0.0218766 s/step
ex:, 20.0, -0.0769859461681632
Meep progress: 21.025000000000002/50.0 = 42.1% done in 20.0s, 27.6s to go
on time step 841 (time=21.025), 0.0218742 s/step
ex:, 25.0, -0.15283194660871335
Meep progress: 25.6/50.0 = 51.2% done in 24.0s, 22.9s to go
on time step 1024 (time=25.6), 0.0218767 s/step
ex:, 30.0, -0.06558325518430419
Meep progress: 30.175/50.0 = 60.4% done in 28.0s, 18.4s to go
on time step 1207 (time=30.175), 0.0218751 s/step
Meep progress: 34.675000000000004/50.0 = 69.4% done in 32.0s, 14.2s to go
on time step 1387 (time=34.675), 0.0222726 s/step
ex:, 35.0, 0.028050318961152614
Meep progress: 39.150000000000006/50.0 = 78.3% done in 36.0s, 10.0s to go
on time step 1566 (time=39.15), 0.0224011 s/step
ex:, 40.0, 0.09121573643203618
Meep progress: 43.625/50.0 = 87.2% done in 40.1s, 5.9s to go
on time step 1745 (time=43.625), 0.022414 s/step
ex:, 45.0, 0.295926135590985
Meep progress: 48.125/50.0 = 96.2% done in 44.1s, 1.7s to go
on time step 1925 (time=48.125), 0.0222662 s/step
ex:, 50.0, 0.2770273206145171
run 0 finished at t = 50.0 (2000 timesteps)

Elapsed run time = 46.0336 s

Geometry (prism)
hexagonal_prism_prism

The geometry looks similar for the two cases. The difference in the results may be due to the prism being shifted or stretched by a pixel or two relative to the identical structure created using blocks. This can be investigated by comparing the epsilon HDF5 file output for both runs (e.g., hexagon_blocks.h5 for the blocks and hexagon_prism.h5 for the prism which is a 100x100x100 dataset) via h5math to compute the difference in the values:

h5math -e "d1-d2" hexagon_eps_diff.h5 hexagon_blocks.h5 hexagon_prism.h5

An isosurface plot of hexagon_eps_diff.h5 (first converted to VTK format via h5tovtk) reveals a hexagonal prism but with a smaller height. One way to validate the prism objects is to therefore compute this difference in the permittivities for increasing resolution and verify that it converges to 0 (i.e., the two representations - block vs. prism - become identical in the continuum limit).

hexagonal_prism_diff

@oskooi
Copy link
Collaborator

oskooi commented Aug 2, 2018

After adjusting the position of the "claw"-shaped prism with the new center parameter such that it aligns with the same shape created using blocks, there are still noticeable differences in the ε output. The following script outputs the permittivity file for the two structures.

Simulation Script

import meep as mp

cell_size = mp.Vector3(5,5,5)

Si = mp.Medium(index=3.5)

h = 1.0

use_prism = True

if use_prism:
    v = [mp.Vector3(-1.0,0,-1),
         mp.Vector3(-1.0,0,-0.2),
         mp.Vector3(-0.5,0,-0.2),
         mp.Vector3(-0.5,0,-0.8),
         mp.Vector3(1.0,0,-0.8),
         mp.Vector3(1.0,0,0.8),
         mp.Vector3(-0.5,0,0.8),
         mp.Vector3(-0.5,0,0.2),
         mp.Vector3(-1.0,0,0.2),
         mp.Vector3(-1.0,0,1),
         mp.Vector3(1.5,0,1),
         mp.Vector3(1.5,0,-1)]
    geometry = [mp.Prism(v, axis=mp.Vector3(0,1,0), center=mp.Vector3(), height=h, material=Si)]
else:
    geometry = [mp.Block(material=Si, size=mp.Vector3(2.5,h,2.0), center=mp.Vector3()),
                mp.Block(material=mp.air, size=mp.Vector3(1.5,h+0.1,1.6), center=mp.Vector3()),
                mp.Block(material=mp.air, size=mp.Vector3(0.5,h+0.1,0.4), center=mp.Vector3(-1.0,0,0))]

sim = mp.Simulation(resolution=50,
                    eps_averaging=True,
                    geometry=geometry,
                    cell_size=cell_size)

sim.run(mp.at_beginning(mp.output_epsilon), until=0)

We can generate images (shown below for two different viewing angles) of the difference in the permittivity values using Mayavi:

h5math -e "d1-d2" claw-eps-diff.h5 claw-prism.h5 claw-blocks.h5
h5tovtk claw-eps-diff.h5
mayavi2 -d claw-eps-diff.vtk -m IsoSurface

Difference in Permittivity Values (with subpixel averaging)

claw_prism_blocks_diff

claw_prism_blocks_diff_z2

The results look different when subpixel smoothing is turned off.

Difference in Permittivity Values (without subpixel averaging)

claw_prism_blocks_diff_noepsavg_1

claw_prism_blocks_diff_noepsavg_2b

@stevengj
Copy link
Collaborator

stevengj commented Aug 2, 2018

Try it with center=mp.Vector3(1e-6,1e-6,1e-6) to see if the difference is due to grid points falling exactly on the edges.

@oskooi
Copy link
Collaborator

oskooi commented Aug 2, 2018

When the prism has center=mp.Vector3(1e-3,1e-3,1e-3) the resulting image becomes:

claw_prism_blocks_diff3b

@oskooi
Copy link
Collaborator

oskooi commented Aug 2, 2018

With the blocks shifted as well, the difference in permittivity is:

claw_prism_blocks_diff4b

@HomerReid
Copy link
Contributor

HomerReid commented Aug 15, 2018

Thanks for this useful test case. I believe the issues are resolved by libctl#23.

I have created an updated version of the python code, which instantiates the TRON recognizer in two ways---as a single prism and as a union of blocks. Instead of creating a large monolithic silicon block and subtracting from it by superposing air blocks, the new script creates the recognizer as a union of 5 nonoverlapping silicon blocks.

Here is the revised python script, PythonVsBlock.py:

import meep as mp
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--prism',  default=False, action='store_true')
parser.add_argument('--offset', type=float, default=0.01,  help='offset')
parser.add_argument('--res',    type=float, default=10,    help='resolution')
args = parser.parse_args()

use_prism = args.prism;
offset    = args.offset;
res       = args.res;

Si = mp.Medium(index=3.5)

X1=-1.0 + offset
X2=-0.5 + offset
X3= 1.0 + offset
X4= 1.5 + offset

Z1=-1.0 + offset
Z2=-0.8 + offset
Z3=-0.2 + offset
Z4=+0.2 + offset
Z5=+0.8 + offset
Z6=+1.0 + offset

YFloor = 0.0 + offset
Height = 1.0;

XCenter= 0.5*(X1+X4);
YCenter= 0.5*(YFloor + Height);
ZCenter= 0.5*(Z1+Z6);

cell_size = mp.Vector3(5,5,5)

if use_prism:
    v = [mp.Vector3(X1,YFloor,Z1),
         mp.Vector3(X1,YFloor,Z3),
         mp.Vector3(X2,YFloor,Z3),
         mp.Vector3(X2,YFloor,Z2),
         mp.Vector3(X3,YFloor,Z2),
         mp.Vector3(X3,YFloor,Z5),
         mp.Vector3(X2,YFloor,Z5),
         mp.Vector3(X2,YFloor,Z4),
         mp.Vector3(X1,YFloor,Z4),
         mp.Vector3(X1,YFloor,Z6),
         mp.Vector3(X4,YFloor,Z6),
         mp.Vector3(X4,YFloor,Z1)]
    geometry = [mp.Prism(v, axis=mp.Vector3(0,1,0), height=Height, material=Si)]
else:
    geometry = [mp.Block(material=Si,     size=mp.Vector3(X2-X1,Height,Z3-Z1), center=mp.Vector3(0.5*(X2+X1),YCenter,0.5*(Z3+Z1))),
                        mp.Block(material=Si,     size=mp.Vector3(X3-X2,Height,Z2-Z1), center=mp.Vector3(0.5*(X3+X2),YCenter,0.5*(Z2+Z1))),
                        mp.Block(material=Si,     size=mp.Vector3(X2-X1,Height,Z6-Z4), center=mp.Vector3(0.5*(X2+X1),YCenter,0.5*(Z6+Z4))),
                        mp.Block(material=Si,     size=mp.Vector3(X3-X2,Height,Z6-Z5), center=mp.Vector3(0.5*(X3+X2),YCenter,0.5*(Z6+Z5))),
                        mp.Block(material=Si,     size=mp.Vector3(X4-X3,Height,Z6-Z1), center=mp.Vector3(0.5*(X4+X3),YCenter,0.5*(Z6+Z1)))]

sim = mp.Simulation(resolution=res,
                    eps_averaging=False,
                    geometry=geometry,
                    cell_size=cell_size)

The script accepts three options:

  • --prism (use prism instead of default blocks)
  • --res (resolution)
  • --offset to specify a small (fraction of a pixel size) displacement to study the effect of prism faces lying precisely on grid points

Here's a shell script that runs the python code with both prism and block implementations and plots the two structures, plus their difference, in mayavi:

#!/bin/bash

rm *.h5 *.vtk *.out

# run meep-python script to generate Prism.h5, Block.h5
python PrismVsBlock.py "$@" --prism > Prism.out
/bin/mv PrismVsBlock-eps-000000.00.h5 Prism.h5

python PrismVsBlock.py "$@" > Block.out
/bin/mv PrismVsBlock-eps-000000.00.h5 Block.h5

# compute difference of h5 datasets and convert to vtk
h5math -e "d1-d2" PrismVsBlock.h5 Prism.h5 Block.h5
h5tovtk PrismVsBlock.h5
h5tovtk Prism.h5
h5tovtk Block.h5

# plot in mayavi
MAYAVI_ARGS=""
MAYAVI_ARGS="${MAYAVI_ARGS} -d Prism.vtk -m IsoSurface"
MAYAVI_ARGS="${MAYAVI_ARGS} -d Block.vtk -m IsoSurface"
MAYAVI_ARGS="${MAYAVI_ARGS} -d PrismVsBlock.vtk -m IsoSurface"

mayavi2 ${MAYAVI_ARGS} 

As far as I can tell, the two structure plots are identical and the difference plot is all zeros.

@oskooi
Copy link
Collaborator

oskooi commented Aug 16, 2018

stevengj/libctl#23 does indeed produce a difference plot with all zeros when comparing the prism structure with a superposition of 5 blocks. However, there is still a non-zero difference when comparing with the structure of 3 blocks via this slightly modified example.

import meep as mp

cell_size = mp.Vector3(5,5,5)

Si = mp.Medium(index=3.5)

h = 1.0

use_prism = False

if use_prism:
    v = [mp.Vector3(-1.25,0,-1),
         mp.Vector3(-1.25,0,-0.2),
         mp.Vector3(-0.75,0,-0.2),
         mp.Vector3(-0.75,0,-0.8),
         mp.Vector3(+0.75,0,-0.8),
         mp.Vector3(+0.75,0,+0.8),
         mp.Vector3(-0.75,0,+0.8),
         mp.Vector3(-0.75,0,+0.2),
         mp.Vector3(-1.25,0,+0.2),
         mp.Vector3(-1.25,0,+1),
         mp.Vector3(+1.25,0,+1),
         mp.Vector3(+1.25,0,-1)]
    geometry = [mp.Prism(v, axis=mp.Vector3(0,1,0), center=mp.Vector3(), height=h, material=Si)]
else:
    geometry = [mp.Block(material=Si, size=mp.Vector3(2.5,h,2.0), center=mp.Vector3()),
		mp.Block(material=mp.air, size=mp.Vector3(1.5,h,1.6), center=mp.Vector3()),
		mp.Block(material=mp.air, size=mp.Vector3(0.5,h,0.4), center=mp.Vector3(-1.0,0,0))]

sim = mp.Simulation(resolution=50,
                    geometry=geometry,
                    cell_size=cell_size,
                    eps_averaging=False)

sim.run(mp.at_beginning(mp.output_epsilon), until=0)

prism_vertices

Difference Plot of Prism vs. Blocks

prism_blocks_diff

@HomerReid
Copy link
Contributor

Just to clarify, the issue being reported here concerns a difference between two different ways of representing the same structure with the legacy block implementation, not anything to do with prisms, correct?

I noted in the comments on libctl#23 that the legacy implementation does not appear to be meticulous in adjudicating questions regarding points lying on object boundaries, and perhaps what you are observing is a manifestation of that.

In either case, the question is only relevant for cases in which grid points happen to lie on object boundaries---a matter of coincidence that is purely an artifact of specific discretization and should vanish (even at the same resolution!) upon slight (subpixel) displacements of the objects to shift boundaries off of grid points. My interpretation of the bedrock philosophy of meep is that the code guarantees convergence to correct results with increasing resolution, but doesn't guarantee equality of results obtained on two shifted grids of the same resolution. Do we want to revise that policy to offer new, stronger, promises regarding effects like the ones you've observed? Note that the inconsistency you've observed is in the legacy block implementation, not in any newer code, so it's been there for years and would presumably have been noted as problematic if it actually were.

Either way, since the original issue seems to have been resolved and the discussion seems to have morphed into larger questions, I propose closing the issue, though I'd like to give the original poster a chance to determine whether the issue has been resolved to his satisfaction.

@DerekK44
Copy link
Contributor Author

Thanks for the libctl update Homer! I've rerun my test suite using libctl#23, and can confirm that the volume errors on the "recognizer" shapes are no longer present. I also ran the other test case above (the strip to slot waveguide converter) and it looks good and without the previous errors, so I agree that closing it would be appropriate.

I do notice some strange discretization effects (in terms of surface pixels) in other examples with multiple prisms coincident at the top/bottom surfaces, but I think perhaps this is out of the scope of the current bug report. Thanks again!

@HomerReid HomerReid reopened this Aug 17, 2018
@stevengj
Copy link
Collaborator

Let's close this since the original problem is gone. If additional problems appear, let's open new issues for those.

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

No branches or pull requests

5 participants