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

collapse empty dimensions of time-domain grid slices by default and remove snap argument from get_array_metadata #1456

Merged
merged 14 commits into from
Dec 31, 2020

Conversation

oskooi
Copy link
Collaborator

@oskooi oskooi commented Dec 20, 2020

Fixes #759.

Fixes #1431 based on the source of the bug described in #1431 (comment).

The snap parameter of get_array_metadata has been removed since it does not really serve a practical purpose compared with the argument collapse which does interpolation. Also, documentation has been added for the collapse argument which was missing.

The tests described in #1431 (comment) now produce the correct results:

import meep as mp
import numpy as np

sim = mp.Simulation(cell_size=mp.Vector3(5,5),
                    resolution=10)

sim.init_sim()

ds = np.linspace(0,0.1,11)

print("sweep grid slice along y")
for d in ds:
    (x,y,z,w) = sim.get_array_metadata(center=mp.Vector3(0,d), size=mp.Vector3(0.5,0), collapse=False)
    if (w.ndim == 2):
	print('{:.3f}, {:.3f}, {:.3f}'.format(d,np.sum(w[:,0]),np.sum(w[:,1])))
    elif (w.ndim == 1):
	print('{:.3f}, {:.3f}'.format(d,np.sum(w)))

print("sweep grid slice along x")
for d in ds:
    (x,y,z,w) = sim.get_array_metadata(center=mp.Vector3(d,0), size=mp.Vector3(0,0.5), collapse=False)
    if (w.ndim == 2):
	print('{:.3f}, {:.3f}, {:.3f}'.format(d,np.sum(w[0,:]),np.sum(w[1,:])))
    elif (w.ndim == 1):
	print('{:.3f}, {:.3f}'.format(d,np.sum(w)))
sweep grid slice along y
0.000, 0.250, 0.250
0.010, 0.200, 0.300
0.020, 0.150, 0.350
0.030, 0.100, 0.400
0.040, 0.050, 0.450
0.050, 0.500
0.060, 0.450, 0.050
0.070, 0.400, 0.100
0.080, 0.350, 0.150
0.090, 0.300, 0.200
0.100, 0.250, 0.250
sweep grid slice along x
0.000, 0.250, 0.250
0.010, 0.200, 0.300
0.020, 0.150, 0.350
0.030, 0.100, 0.400
0.040, 0.050, 0.450
0.050, 0.500
0.060, 0.450, 0.050
0.070, 0.400, 0.100
0.080, 0.350, 0.150
0.090, 0.300, 0.200
0.100, 0.250, 0.250

Note how the sum of the weights from each of the uncollapsed dimensions (second and third columns) changes as the grid slice is swept across a distance of one pixel (first column).

import meep as mp
import numpy as np

sim = mp.Simulation(cell_size=mp.Vector3(5,5,5),
                    resolution=10)

sim.init_sim()

ds = np.linspace(0,0.1,11)+0.058712

print("grid slice along z")
for d in ds:
    (x,y,z,w) = sim.get_array_metadata(center=mp.Vector3(0,0,d), size=mp.Vector3(0.5,0.5,0), collapse=False)
    if (w.ndim == 3):
        print('{:.3f}, {:.3f}, {:.3f}'.format(d,np.sum(w[:,:,0]),np.sum(w[:,:,1])))
    elif (w.ndim == 2):
        print('{:.3f}, {:.3f}'.format(d,np.sum(w)))

print("grid slice along y")
for d in ds:
    (x,y,z,w) = sim.get_array_metadata(center=mp.Vector3(0,d,0), size=mp.Vector3(0.5,0,0.5), collapse=False)
    if (w.ndim == 3):
        print('{:.3f}, {:.3f}, {:.3f}'.format(d,np.sum(w[:,0,:]),np.sum(w[:,1,:])))
    elif (w.ndim == 2):
        print('{:.3f}, {:.3f}'.format(d,np.sum(w)))

print("grid slice along x")
for d in ds:
    (x,y,z,w) = sim.get_array_metadata(center=mp.Vector3(d,0,0), size=mp.Vector3(0,0.5,0.5), collapse=False)
    if (w.ndim == 3):
	print('{:.3f}, {:.3f}, {:.3f}'.format(d,np.sum(w[0,:,:]),np.sum(w[1,:,:])))
    elif (w.ndim == 2):
	print('{:.3f}, {:.3f}'.format(d,np.sum(w)))
grid slice along z
0.059, 0.228, 0.022
0.069, 0.203, 0.047
0.079, 0.178, 0.072
0.089, 0.153, 0.097
0.099, 0.128, 0.122
0.109, 0.103, 0.147
0.119, 0.078, 0.172
0.129, 0.053, 0.197
0.139, 0.028, 0.222
0.149, 0.003, 0.247
0.159, 0.228, 0.022
grid slice along y
0.059, 0.228, 0.022
0.069, 0.203, 0.047
0.079, 0.178, 0.072
0.089, 0.153, 0.097
0.099, 0.128, 0.122
0.109, 0.103, 0.147
0.119, 0.078, 0.172
0.129, 0.053, 0.197
0.139, 0.028, 0.222
0.149, 0.003, 0.247
0.159, 0.228, 0.022
grid slice along x
0.059, 0.228, 0.022
0.069, 0.203, 0.047
0.079, 0.178, 0.072
0.089, 0.153, 0.097
0.099, 0.128, 0.122
0.109, 0.103, 0.147
0.119, 0.078, 0.172
0.129, 0.053, 0.197
0.139, 0.028, 0.222
0.149, 0.003, 0.247
0.159, 0.228, 0.022

Note: the documentation in Python_User_Interface.md has been regenerated using meep/doc/generate_py_api.py which is why an unrelated change due to plot2D (from #1446) appears.

"""
This routine provides geometric information useful for interpreting the arrays
returned by `get_array` or `get_dft_array` for the spatial region defined by `vol`
or `center`/`size`. In both cases, the return value is a tuple `(x,y,z,w)`, where:

+ `x,y,z` are 1d NumPy arrays storing the $x,y,z$ coordinates of the points in the
+ `x,y,z` are tuples storing the $x,y,z$ coordinates of the points in the
Copy link
Contributor

Choose a reason for hiding this comment

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

Just another drive by comment following this from the reference to #1431... x, y, and z look like they're NumPy arrays, not tuples.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for calling this out. The "tics" return values (x,y,z) should be NumPy arrays but in fact they are set up as tuples:

meep/python/simulation.py

Lines 3278 to 3289 in f5fc4cd

xyzw_vector = self.fields.get_array_metadata(v, collapse, snap)
offset, tics = 0, []
for n in range(3):
N = int(xyzw_vector[offset])
tics.append( xyzw_vector[offset+1:offset+1+N] )
offset += 1+N
wshape=[len(t) for t in tics if len(t)>1]
weights=np.reshape(xyzw_vector[offset:],wshape)
if return_pw:
points=[ mp.Vector3(x,y,z) for x in tics[0] for y in tics[1] for z in tics[2] ]
return points,weights
return tics + [weights]

I verified this using type(x), type(y), type(z) and the results was a tuple. (w though is a NumPy array.) If we really want these return values as 1d NumPy arrays then we will need to modify get_array_metadata in a separate PR. I just updated the documentation here to make it consistent with what is actually happening.

Copy link
Contributor

@ianwilliamson ianwilliamson Dec 20, 2020

Choose a reason for hiding this comment

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

Ah, good to know. If you wanted, you could also call np.asarray() on them to do the conversion in Python.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This looks like a SWIG problem — self.fields.get_array_metadata(v, collapse, snap) is nowadays returning a tuple, whereas the intention (and I think the original behavior) was to return a NumPy array.

We definitely want a numpy array here — a tuple is a terrible data structure for a large array. And we want to return a numpy array directly, not return a tuple and then convert to numpy.

Copy link
Collaborator

Choose a reason for hiding this comment

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

fields::get_array_metadata returns a std::vector<double>, which we want SWIG to convert to a NumPy array. I'm not sure why that isn't happening.

Copy link
Collaborator

Choose a reason for hiding this comment

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

According to the documentation for SWIG's std_vector.i (which we are using), it's supposed to be using a "list object in the target language" for output typemaps.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I was able to fix it by adding:

// use NumPy arrays for returning common std::vector types:
%typemap(out) std::vector<double> {
    npy_intp vec_len = (npy_intp) $1.size();
    $result = PyArray_SimpleNew(1, &vec_len, NPY_DOUBLE);
    memcpy(PyArray_DATA((PyArrayObject*) $result), &$1[0], vec_len * sizeof(double));
}

to meep.i (after %template(DoubleVector) std::vector<double>;).

Copy link
Collaborator

Choose a reason for hiding this comment

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

We can do that in a separate PR.

Copy link
Collaborator

Choose a reason for hiding this comment

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

See #1458.

@stevengj
Copy link
Collaborator

Note that until we get proper interpolation of the time-domain slices (#759), we may want to have a snap option here if you want the metadata for a time-domain slice.

@stevengj
Copy link
Collaborator

Seems like it should be easy to fix #759 nowadays, so that we can remove the snap option completely? For the time-domain get_slice, just set snap=false and call collapse_array on the result before returning it.

@ianwilliamson
Copy link
Contributor

Incorporating these changes does seem to eliminate the ASAN finding that I reported in #1431. ASAN now only returns the result reported in #1432, which is an issue in a separate function.

@oskooi
Copy link
Collaborator Author

oskooi commented Dec 27, 2020

The snap option has been removed and the time-domain slices are now all collapsed. The docs has also been updated with these changes.

All the tests in the make check suite are passing on my local machine. However, it seems that the Travis CI tests are disabled?

@oskooi
Copy link
Collaborator Author

oskooi commented Dec 27, 2020

The latest changes also affect the call to get_array_slice_dimensions in material_grids_add_gradient of meepgeom.cpp. Thus, this PR may also fix #1432?

@oskooi oskooi changed the title fix for get_array_metadata related to collapsing of empty dimensions of grid slice collapse empty dimensions of grid slices by default and remove snap argument Dec 27, 2020
python/simulation.py Outdated Show resolved Hide resolved
@stevengj
Copy link
Collaborator

Would be good to re-instate the snap option for get_array_slice (but not get_array_metadata), if only for comparison the the output_h5 feature (which snaps) in order to re-instate the tests (and also to ease migration if someone is migrating from an HDF5-based workflow to a Python get_array_slice workflow.

In the long run, I'm not sure that the current output_h5 implementation will support collapsing interpolated slices, because output_h5 is designed for saving huge datasets that may not fit into the memory of a single process — since it never loads the uncollapsed array into a single process's memory, implementing collapse_array could be difficult. However, we could still add a snap=false option to output_h5 — in that case, we could just call get_array_slice and then h5file::write, circumventing the whole parallel-I/O implementation entirely.

@oskooi
Copy link
Collaborator Author

oskooi commented Dec 29, 2020

Verifying types of return values of get_array_metadata:

import meep as mp

sim = mp.Simulation(cell_size=mp.Vector3(5,5,5),
                    resolution=20)

sim.init_sim()

(x,y,z,w) = sim.get_array_metadata(center=mp.Vector3(0,0,0.149), size=mp.Vector3(4.5,2.4,0))
print(len(x),len(y),len(z),w.shape)

print(type(x),type(y),type(z),type(w))

output

Using MPI version 3.1, 1 processes
-----------
Initializing structure...
time for choose_chunkdivision = 2.7325e-05 s
Working in 3D dimensions.
Computational cell is 5 x 5 x 5 with resolution 20
time for set_epsilon = 3.19013 s
-----------
92 50 1 (92, 50)
<class 'tuple'> <class 'tuple'> <class 'tuple'> <class 'numpy.ndarray'>

@stevengj
Copy link
Collaborator

Note that if snap==true you should omit the IVEC_LOOP_WEIGHT(s0, s1, e0, e1, 1.0) weights in get_array_slice.

@oskooi
Copy link
Collaborator Author

oskooi commented Dec 30, 2020

The snap argument to get_array has been re-instated as well as the tests which involve comparing the output from get_array with output_hdf5 (tests/array-slice-ll.cpp, python/tests/simulation.py). The make check suite is passing on my local machine.

@oskooi oskooi changed the title collapse empty dimensions of grid slices by default and remove snap argument collapse empty dimensions of time-domain grid slices by default and remove snap argument from get_array_metadata Dec 30, 2020
@oskooi
Copy link
Collaborator Author

oskooi commented Dec 30, 2020

All tests from the most recent commit (33dfce9) are passing on Travis (https://travis-ci.com/github/oskooi/meep/builds/211270487).

python/simulation.py Outdated Show resolved Hide resolved
@stevengj
Copy link
Collaborator

stevengj commented Dec 31, 2020

Note that the weights returned by get_array_metadata are somewhat inconsistent. Realize that these weights include three things:

  1. the volume element dV (≠ constant in cylindrical coords)
  2. an interpolation weight for slices
  3. a fractional weight at the edges of the region, corresponding to the fractional dV that those pixels contribute to an integral.

Currently, the get_array_metadata weights include 1 and 3 — weight (2) goes away when you collapse empty dimensions (because interpolation weights sum to 1). With this PR, the get_array_slice fields include weights (3). So, if you use sum(fields*weights) to compute an integral, you are including weights (3) twice, making a first-order error.

Note that the dft_fields already include weights 3 as well, so there is the same first-order error in the current release for computing integrals of DFT fields with these integration weights. And the current release makes a first-order error in get_array_slice when it snaps the field — so in some sense you are swapping one first-order error for another.

We can probably fix this in another PR. The fix is simple: this line should be changed to fields[i] = dV0 + dV1 * loop_i2; (computing weights 1 only). But some of the test results might change slightly.

  • Correction the dft_fields do not include weight 3, so there is no problem there. The only problem is with array slices?

python/simulation.py Outdated Show resolved Hide resolved
python/simulation.py Outdated Show resolved Hide resolved
src/array_slice.cpp Outdated Show resolved Hide resolved
src/array_slice.cpp Outdated Show resolved Hide resolved
src/array_slice.cpp Outdated Show resolved Hide resolved
@stevengj stevengj merged commit 27d4512 into NanoComp:master Dec 31, 2020
@oskooi oskooi deleted the get_array_metadata_snap branch January 1, 2021 01:09
@Chuban Chuban mentioned this pull request Jan 7, 2021
bencbartlett pushed a commit to bencbartlett/meep that referenced this pull request Sep 9, 2021
…emove snap argument from get_array_metadata (NanoComp#1456)

* fix for get_array_metadata related to collapsing of empty dimensions of grid slice

* remove snap_empty_dimensions and collapse arrays in get_array_

* fix get_source_slice and get_array_metadata

* update hard-coded validation binary files for cavity_arrayslice.py

* remove collapse_empty_dimensions argument from tests/array_metadata.cpp

* add array_to_all for parallel simulations and update docs

* remove tests/array-slice-ll.cpp from make check suite because output_hdf5 only snaps to grid

* add interpolation weights to slices and update hard-coded values of cavity_arrayslice.py test

* re-instate snap argument for do_get_array_slice

* fix bug in do_get_array_slice related to snapping

* re-instate snap_empty_dimensions argument in get_array_slice_dimensions

* include snap_empty_dimensions in meep.i

* fixes

* condense/simplify

Co-authored-by: Steven G. Johnson <stevenj@alum.mit.edu>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Heap buffer overflow and segmentation fault in collapse_array array-slice interpolation
3 participants