From 81117c03d2045f164840a2ca948a35aa930bec0e Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Mon, 24 May 2021 13:07:40 -0400 Subject: [PATCH 001/155] first draft of loop_in_chunks using grid_vol calculations --- src/dft.cpp | 2 +- src/loop_in_chunks.cpp | 31 +++++++++++-------------------- src/meep/vec.hpp | 6 +++++- src/stress.cpp | 2 +- src/structure.cpp | 2 +- src/vec.cpp | 37 ++++++++++++++++++++++++++++++++++++- tests/integrate.cpp | 13 ++++--------- 7 files changed, 59 insertions(+), 34 deletions(-) diff --git a/src/dft.cpp b/src/dft.cpp index 3cf95871a..265af4f02 100644 --- a/src/dft.cpp +++ b/src/dft.cpp @@ -744,7 +744,7 @@ complex dft_chunk::process_dft_component(int rank, direction *ds, ivec m int ic_conjugate, bool retain_interp_weights, fields *parent) { - if ((num_freq < 0) || (num_freq > omega.size()-1)) + if ((num_freq < 0) || (size_t(num_freq) > omega.size()-1)) abort("process_dft_component: frequency index %d is outside the range of the frequency array of size %lu",num_freq,omega.size()); /*****************************************************************/ diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 8a143c9dc..492890db7 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -375,8 +375,8 @@ 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); ivec iyee_cS(S.transform_unshifted(iyee_c, -sn)); - volume gvS = S.transform(gv.surroundings(), sn); + vec L(gv.dim); ivec iL(gv.dim); @@ -418,25 +418,16 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con for (int i = 0; i < num_chunks; ++i) { if (!chunks[i]->is_mine()) continue; - // Chunk looping boundaries: - volume vS(gv.dim); - - if (use_symmetry) - vS = S.transform(chunks[i]->v, sn); - else { - /* If we're not using symmetry, it's because (as in src_vol) - we don't care about correctly counting the points in the - grid_volume. Rather, we just want to make sure to get *all* - of the chunk points that intersect where. Hence, add a little - padding to make sure we don't miss any points due to rounding. */ - vec pad(one_ivec(gv.dim) * gv.inva * 1e-3); - vS = volume(chunks[i]->gv.loc(Centered, 0) - pad, - chunks[i]->gv.loc(Centered, chunks[i]->gv.ntot() - 1) + pad); - } - - ivec iscS(max(is - shifti, vec2diel_ceil(vS.get_min_corner(), gv.a, one_ivec(gv.dim) * 2))); - ivec iecS(min(ie - shifti, vec2diel_floor(vS.get_max_corner(), gv.a, zero_ivec(gv.dim)))); - if (iscS <= iecS) { + // Chunk looping boundaries for owned points, shifted to centered grid and transformed: + grid_volume gvu(chunks[i]->gv.unpad(gv)); + ivec _iscoS(S.transform(gvu.little_owned_corner(Centered), sn)); + ivec _iecoS(S.transform(gvu.big_owned_corner(Centered), sn)); + ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform + + // intersect the chunk points with is and ie volume (shifted): + ivec iscS(max(is - shifti, iscoS)); + ivec iecS(min(ie - shifti, iecoS)); + if (iscS <= iecS) { // non-empty intersection // Determine weights at chunk looping boundaries: ivec isc(S.transform(iscS, -sn)), iec(S.transform(iecS, -sn)); vec s0c(gv.dim, 1.0), s1c(gv.dim, 1.0), e0c(gv.dim, 1.0), e1c(gv.dim, 1.0); diff --git a/src/meep/vec.hpp b/src/meep/vec.hpp index c6b331ba7..0711b603e 100644 --- a/src/meep/vec.hpp +++ b/src/meep/vec.hpp @@ -53,7 +53,7 @@ enum component { Permeability, NO_COMPONENT }; -#define Centered Dielectric // better name for centered "dielectric" grid +const component Centered = Dielectric; // better name for centered "dielectric" grid enum derived_component { Sx = 100, Sy, @@ -976,6 +976,7 @@ class grid_volume { } ivec little_owned_corner(component c) const; + ivec big_owned_corner(component c) const { return big_corner() - iyee_shift(c); } bool owns(const ivec &) const; volume surroundings() const; volume interior() const; @@ -1000,6 +1001,8 @@ class grid_volume { gv.pad_self(d); return gv; } + grid_volume unpad() const; + grid_volume unpad(const grid_volume &gv0) const; ivec iyee_shift(component c) const { ivec out = zero_ivec(dim); LOOP_OVER_DIRECTIONS(dim, d) @@ -1045,6 +1048,7 @@ class grid_volume { } int num[3]; ptrdiff_t the_stride[5]; + bool is_padded[5]; size_t the_ntot; }; diff --git a/src/stress.cpp b/src/stress.cpp index 99d2ca06a..618bba16e 100644 --- a/src/stress.cpp +++ b/src/stress.cpp @@ -46,7 +46,7 @@ dft_force::dft_force(dft_chunk *offdiag1_, dft_chunk *offdiag2_, dft_chunk *diag dft_force::dft_force(dft_chunk *offdiag1_, dft_chunk *offdiag2_, dft_chunk *diag_, const double *freq_, size_t Nfreq, const volume &where_) - : where(where_), freq(Nfreq) { + : freq(Nfreq), where(where_) { for (size_t i = 0; i < Nfreq; ++i) freq[i] = freq_[i]; offdiag1 = offdiag1_; diff --git a/src/structure.cpp b/src/structure.cpp index dfbbb16e3..f532c2b92 100644 --- a/src/structure.cpp +++ b/src/structure.cpp @@ -181,7 +181,7 @@ std::vector choose_chunkdivision(grid_volume &gv, volume &v, int de v = gv.surroundings(); // Pad the little cell in any direction that we've shrunk: for (int d = 0; d < 3; d++) - if (break_this[d]) gv = gv.pad((direction)d); + if (break_this[d]) gv.pad_self((direction)d); } // Finally, create the chunks: diff --git a/src/vec.cpp b/src/vec.cpp index c60ac4b76..3c82d46be 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -310,6 +310,7 @@ grid_volume::grid_volume(ndim td, double ta, int na, int nb, int nc) { num[0] = na; num[1] = nb; num[2] = nc; + FOR_DIRECTIONS(d) is_padded[d] = false; num_changed(); set_origin(zero_vec(dim)); } @@ -863,7 +864,7 @@ ivec grid_volume::iloc(component c, ptrdiff_t ind) const { size_t grid_volume::surface_area() const { switch(dim) { case Dcyl: return 2*(nr()+nz()); - case D1: 2; + case D1: return 2; case D2: return 2*(nx()+ny()); case D3: return 2*(nx()*ny()+nx()*nz()+ny()*nz()); } @@ -1072,6 +1073,40 @@ void grid_volume::pad_self(direction d) { num[d % 3] += 2; // Pad in both directions by one grid point. num_changed(); shift_origin(d, -2); + is_padded[d] = true; +} + +// undoes all padding +grid_volume grid_volume::unpad() const { + grid_volume gv(*this); + LOOP_OVER_DIRECTIONS(dim, d) { + if (is_padded[d]) { // inverse of pad_self above + gv.num[d % 3] -= 2; + gv.shift_origin(d, +2); + gv.is_padded[d] = false; + } + } + gv.num_changed(); + return gv; +} + +// undoes padding in *this according when edges match padded sides of gv0 +grid_volume grid_volume::unpad(const grid_volume &gv0) const { + grid_volume gv(*this); + LOOP_OVER_DIRECTIONS(dim, d) { + if (gv0.is_padded[d]) { + if (little_corner().in_direction(d) == gv0.little_corner().in_direction(d)) { + gv.num[d % 3] -= 1; + gv.shift_origin(d, +2); + } + if (big_corner().in_direction(d) == gv0.big_corner().in_direction(d)) { + gv.num[d % 3] -= 1; + } + gv.is_padded[d] = false; + } + } + gv.num_changed(); + return gv; } ivec grid_volume::icenter() const { diff --git a/tests/integrate.cpp b/tests/integrate.cpp index 62760d77b..572e5cdf2 100644 --- a/tests/integrate.cpp +++ b/tests/integrate.cpp @@ -323,18 +323,13 @@ int main(int argc, char **argv) { const grid_volume v1d = vol1d(sz[0], a); const grid_volume vcyl = volcyl(sz[0], sz[1], a); - for (int ic = Ex; ic <= Dielectric; ++ic) { - component c = component(ic); - check_loop_vol(v1d, c); - check_loop_vol(v2d, c); - check_loop_vol(v3d, c); - check_loop_vol(vcyl, c); - check_loop_vol(v3d0, c); - check_loop_vol(v3d00, c); - } srand(0); // use fixed random sequence + check_splitsym(v3d, 0, identity(), "identity"); + check_splitsym(v3d, 0, mirror(X, v3d), "mirrorx"); + return 0; + for (int splitting = 0; splitting < 5; ++splitting) { check_splitsym(v3d, splitting, identity(), "identity"); check_splitsym(v3d, splitting, mirror(X, v3d), "mirrorx"); From dce89aeed4f9de678d30f6363c7e423a6f72883d Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Mon, 24 May 2021 13:10:32 -0400 Subject: [PATCH 002/155] try to only use owned points in loop_in_chunks --- src/loop_in_chunks.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 492890db7..cc8d5bd9b 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -420,8 +420,8 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (!chunks[i]->is_mine()) continue; // Chunk looping boundaries for owned points, shifted to centered grid and transformed: grid_volume gvu(chunks[i]->gv.unpad(gv)); - ivec _iscoS(S.transform(gvu.little_owned_corner(Centered), sn)); - ivec _iecoS(S.transform(gvu.big_owned_corner(Centered), sn)); + ivec _iscoS(S.transform(gvu.little_owned_corner(cS) + iyee_cS, sn)); + ivec _iecoS(S.transform(gvu.big_owned_corner(cS) + iyee_cS, sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform // intersect the chunk points with is and ie volume (shifted): From b273d36c9bbf410b9df670d783d93935395b4435 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Mon, 24 May 2021 13:45:22 -0400 Subject: [PATCH 003/155] don't unpad if !use_symmetry --- src/loop_in_chunks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index cc8d5bd9b..ea8be0fa5 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -419,7 +419,7 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con for (int i = 0; i < num_chunks; ++i) { if (!chunks[i]->is_mine()) continue; // Chunk looping boundaries for owned points, shifted to centered grid and transformed: - grid_volume gvu(chunks[i]->gv.unpad(gv)); + grid_volume gvu(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); ivec _iscoS(S.transform(gvu.little_owned_corner(cS) + iyee_cS, sn)); ivec _iecoS(S.transform(gvu.big_owned_corner(cS) + iyee_cS, sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform From 475ff3975c68b1b232de571993d2b7a540ef6f5f Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Mon, 24 May 2021 14:03:00 -0400 Subject: [PATCH 004/155] stop vscode from complaining about comments --- src/meep/vec.hpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/meep/vec.hpp b/src/meep/vec.hpp index 0711b603e..14f648972 100644 --- a/src/meep/vec.hpp +++ b/src/meep/vec.hpp @@ -1,20 +1,20 @@ // -*- C++ -*- -/* Copyright (C) 2005-2021 Massachusetts Institute of Technology -% -% This program is free software; you can redistribute it and/or modify -% it under the terms of the GNU General Public License as published by -% the Free Software Foundation; either version 2, or (at your option) -% any later version. -% -% This program is distributed in the hope that it will be useful, -% but WITHOUT ANY WARRANTY; without even the implied warranty of -% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -% GNU General Public License for more details. -% -% You should have received a copy of the GNU General Public License -% along with this program; if not, write to the Free Software Foundation, -% Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ +/* Copyright (C) 2005-2021 Massachusetts Institute of Technology. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ #ifndef MEEP_VEC_H #define MEEP_VEC_H From d8c51a62b3519b92d95f4ca63b91a79d2119b4af Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Thu, 3 Jun 2021 14:51:33 -0400 Subject: [PATCH 005/155] rm hack to loop over centered grid --- src/loop_in_chunks.cpp | 54 ++++++++++++++++------------------------ tests/array-metadata.cpp | 2 +- tests/near2far.cpp | 2 +- 3 files changed, 23 insertions(+), 35 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index ea8be0fa5..2c0036761 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -253,12 +253,12 @@ static inline int iabs(int i) { return (i < 0 ? -i : i); } /* Integration weights at boundaries (c.f. long comment at top). */ /* This code was formerly part of loop_in_chunks, now refactored */ /* as a separate routine so we can call it from get_array_metadata.*/ -void compute_boundary_weights(grid_volume gv, volume &wherec, ivec &is, ivec &ie, +void compute_boundary_weights(grid_volume gv, const volume &where, ivec &is, ivec &ie, bool snap_empty_dimensions, vec &s0, vec &e0, vec &s1, vec &e1) { LOOP_OVER_DIRECTIONS(gv.dim, d) { double w0, w1; - w0 = 1. - wherec.in_direction_min(d) * gv.a + 0.5 * is.in_direction(d); - w1 = 1. + wherec.in_direction_max(d) * gv.a - 0.5 * ie.in_direction(d); + w0 = 1. - where.in_direction_min(d) * gv.a + 0.5 * is.in_direction(d); + w1 = 1. + where.in_direction_max(d) * gv.a - 0.5 * ie.in_direction(d); if (ie.in_direction(d) >= is.in_direction(d) + 3 * 2) { s0.set_direction(d, w0 * w0 / 2); s1.set_direction(d, 1 - (1 - w0) * (1 - w0) / 2); @@ -271,14 +271,15 @@ void compute_boundary_weights(grid_volume gv, volume &wherec, ivec &is, ivec &ie e0.set_direction(d, w1 * w1 / 2); e1.set_direction(d, s1.in_direction(d)); } - else if (wherec.in_direction_min(d) == wherec.in_direction_max(d)) { + else if (where.in_direction_min(d) == where.in_direction_max(d)) { if (snap_empty_dimensions) { if (w0 > w1) ie.set_direction(d, is.in_direction(d)); else is.set_direction(d, ie.in_direction(d)); - wherec.set_direction_min(d, is.in_direction(d) * (0.5 * gv.inva)); - wherec.set_direction_max(d, is.in_direction(d) * (0.5 * gv.inva)); + // shouldn't be necessary to change where: + // where.set_direction_min(d, is.in_direction(d) * (0.5 * gv.inva)); + // where.set_direction_max(d, is.in_direction(d) * (0.5 * gv.inva)); w0 = w1 = 1.0; } s0.set_direction(d, w0); @@ -347,34 +348,21 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (cgrid == Permeability) cgrid = Centered; - /* - We handle looping on an arbitrary component grid by shifting - to the centered grid and then shifting back. The looping - coordinates are internally calculated on the odd-indexed - "centered grid", which has the virtue that it is disjoint for - each chunk and each chunk has enough information to interpolate all - of its field components onto this grid without communication. - Another virtue of this grid is that it is invariant under all of - our symmetry transformations, so we can uniquely decide which - transformed chunk gets to loop_in_chunks which grid point. - */ + /* Find the corners (is and ie) of the smallest bounding box for + wherec, on the grid of odd-coordinate ivecs (i.e. the + "dielectric/centered grid") and then shift back to the yee grid for c. */ vec yee_c(gv.yee_shift(Centered) - gv.yee_shift(cgrid)); ivec iyee_c(gv.iyee_shift(Centered) - gv.iyee_shift(cgrid)); volume wherec(where + yee_c); - - /* Find the corners (is and ie) of the smallest bounding box for - wherec, on the grid of odd-coordinate ivecs (i.e. the - "epsilon grid"). */ - ivec is(vec2diel_floor(wherec.get_min_corner(), gv.a, zero_ivec(gv.dim))); - ivec ie(vec2diel_ceil(wherec.get_max_corner(), gv.a, zero_ivec(gv.dim))); + ivec is(vec2diel_floor(wherec.get_min_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); + ivec ie(vec2diel_ceil(wherec.get_max_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); vec s0(gv.dim), e0(gv.dim), s1(gv.dim), e1(gv.dim); - compute_boundary_weights(gv, wherec, is, ie, snap_empty_dimensions, s0, e0, s1, e1); + compute_boundary_weights(gv, where, is, ie, snap_empty_dimensions, s0, e0, s1, e1); // loop over symmetry transformations of the chunks: for (int sn = 0; sn < (use_symmetry ? S.multiplicity() : 1); ++sn) { component cS = S.transform(cgrid, -sn); - ivec iyee_cS(S.transform_unshifted(iyee_c, -sn)); volume gvS = S.transform(gv.surroundings(), sn); vec L(gv.dim); @@ -387,16 +375,16 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con iL.set_direction(d, iabs(ilattice_vector(dS).in_direction(dS))); } - // figure out range of lattice shifts for which gvS intersects wherec: + // figure out range of lattice shifts for which gvS intersects where: ivec min_ishift(gv.dim), max_ishift(gv.dim); LOOP_OVER_DIRECTIONS(gv.dim, d) { if (boundaries[High][S.transform(d, -sn).d] == Periodic) { min_ishift.set_direction( d, - int(floor((wherec.in_direction_min(d) - gvS.in_direction_max(d)) / L.in_direction(d)))); + int(floor((where.in_direction_min(d) - gvS.in_direction_max(d)) / L.in_direction(d)))); max_ishift.set_direction( d, - int(ceil((wherec.in_direction_max(d) - gvS.in_direction_min(d)) / L.in_direction(d)))); + int(ceil((where.in_direction_max(d) - gvS.in_direction_min(d)) / L.in_direction(d)))); } else { min_ishift.set_direction(d, 0); @@ -420,8 +408,8 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (!chunks[i]->is_mine()) continue; // Chunk looping boundaries for owned points, shifted to centered grid and transformed: grid_volume gvu(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); - ivec _iscoS(S.transform(gvu.little_owned_corner(cS) + iyee_cS, sn)); - ivec _iecoS(S.transform(gvu.big_owned_corner(cS) + iyee_cS, sn)); + ivec _iscoS(S.transform(gvu.little_owned_corner(cS), sn)); + ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform // intersect the chunk points with is and ie volume (shifted): @@ -487,15 +475,15 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con // Determine integration "volumes" dV0 and dV1; double dV0 = 1.0, dV1 = 0.0; LOOP_OVER_DIRECTIONS(gv.dim, d) { - if (wherec.in_direction(d) > 0.0) dV0 *= gv.inva; + if (where.in_direction(d) > 0.0) dV0 *= gv.inva; } if (gv.dim == Dcyl) { dV1 = dV0 * 2 * pi * gv.inva; dV0 *= 2 * pi * - fabs((S.transform(chunks[i]->gv[isc], sn) + shift - yee_c).in_direction(R)); + fabs((S.transform(chunks[i]->gv[isc], sn) + shift).in_direction(R)); } - chunkloop(chunks[i], i, cS, isc - iyee_cS, iec - iyee_cS, s0c, s1c, e0c, e1c, dV0, dV1, + chunkloop(chunks[i], i, cS, isc, iec, s0c, s1c, e0c, e1c, dV0, dV1, shifti, ph, S, sn, chunkloop_data); } } diff --git a/tests/array-metadata.cpp b/tests/array-metadata.cpp index 1967ae719..2ca94a820 100644 --- a/tests/array-metadata.cpp +++ b/tests/array-metadata.cpp @@ -47,7 +47,7 @@ static ivec vec2diel_ceil(const vec &pt, double a, const ivec &equal_shift) { return ipt; } namespace meep { -void compute_boundary_weights(grid_volume gv, volume &wherec, ivec &is, ivec &ie, +void compute_boundary_weights(grid_volume gv, const volume &wherec, ivec &is, ivec &ie, bool snap_empty_dims, vec &s0, vec &e0, vec &s1, vec &e1); } diff --git a/tests/near2far.cpp b/tests/near2far.cpp index 298bada2c..ff010ece6 100644 --- a/tests/near2far.cpp +++ b/tests/near2far.cpp @@ -256,7 +256,7 @@ int main(int argc, char **argv) { // NOTE: see hack above -- we require sources to be odd in Z and even in X or vice-versa if (!check_2d_3d(D3, 4, a3d, Ez, Hx, false)) return 1; -#ifdef HAVE_LIBGSL +#if defined(HAVE_JN) || defined(HAVE_LIBGSL) // required for Hankel functions in 2d near2far if (!check_2d_3d(D2, 8, a2d, Ez, Hx, false)) return 1; if (!check_2d_3d(D2, 8, a2d, Ex, Hz, true)) return 1; #endif From 64f7c85f8af957819f924652ff8da16486de2a49 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 4 Jan 2022 19:14:16 -0500 Subject: [PATCH 006/155] fix and example --- python/adjoint/connectivity.py | 12 +- .../ConnectivityConstraint.ipynb | 272 ++++++++++++++++++ 2 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 python/examples/adjoint_optimization/ConnectivityConstraint.ipynb diff --git a/python/adjoint/connectivity.py b/python/adjoint/connectivity.py index 1f10b0648..dc2309e94 100644 --- a/python/adjoint/connectivity.py +++ b/python/adjoint/connectivity.py @@ -72,7 +72,10 @@ def forward(self, rho_vector): damping = self.k0*self.alpha**2*diags(1-rho_vector[:-self.nx*self.ny]) + diags([self.alpha0**2], shape=(self.m, self.m)) self.A = eq + damping self.damping = damping - self.T, sinfo = self.solver(csr_matrix(self.A), rhs) + if self.solver == spsolve: + self.T = self.solver(csr_matrix(self.A), rhs) + else: + self.T, sinfo = self.solver(csr_matrix(self.A), rhs) #exclude last row of rho and calculate weighted average of temperature self.rho_vec = rho_vector[:-self.nx*self.ny] @@ -83,7 +86,10 @@ def forward(self, rho_vector): def adjoint(self): T_p1 = -(self.T-1) ** (self.p-1) dg_dT = self.Td**(1-self.p) * (T_p1*self.rho_vec)/sum(self.rho_vec) - return self.solver(csr_matrix(self.A.transpose()), dg_dT) + if self.solver == spsolve: + return self.solver(csr_matrix(self.A.transpose()), dg_dT) + aT, _ = self.solver(csr_matrix(self.A.transpose()), dg_dT) + return aT def calculate_grad(self): dg_dp = np.zeros(self.n) @@ -109,7 +115,7 @@ def calculate_grad(self): dAz = (1-self.zeta)*self.k0*self.dz * drhoz.multiply(gzTz) d_damping = self.k0*self.alpha**2*diags(-self.T, shape=(self.m, self.n)) - self.grad = dg_dp + self.adjoint().reshape(1, -1) * csr_matrix( - dAz - dAx - dAy - d_damping) + self.grad = dg_dp + self.adjoint().reshape(1, -1) * csr_matrix(dAz + dAx + dAy + d_damping) return self.grad[0] def __call__(self, rho_vector): diff --git a/python/examples/adjoint_optimization/ConnectivityConstraint.ipynb b/python/examples/adjoint_optimization/ConnectivityConstraint.ipynb new file mode 100644 index 000000000..56279b124 --- /dev/null +++ b/python/examples/adjoint_optimization/ConnectivityConstraint.ipynb @@ -0,0 +1,272 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Connectivity Constraint in Meep adjoint\n", + "\n", + "For manufacturability, connectivity constraint is often desired. This is a simple tutorial example of the connectivity constraint in Meep adjoint. This feature is rather independent and may be used alone, when applicable." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using MPI version 3.1, 1 processes\n" + ] + } + ], + "source": [ + "import meep.adjoint as mpa\n", + "import numpy as np\n", + "from scipy.sparse.linalg import cg, spsolve\n", + "from matplotlib import pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The underlying idea (based on Li, Q. et al. https://doi.org/10.1007/s00158-016-1459-5) is briefly summerized below:\n", + "\n", + "Consider the heat equation, and regard the material as heat conductive and void as heat insulative. Solving the heat equation, we should expect the heat gets diffused into the connected component but not the disconnected component.\n", + "In practice, 3D-printed structure is often mounted on some substrate. This means the optimized structure should be connected to one side. For our heat equation, we impose Dirichlet boundary condition ($T=T_0$) on one side, (and Neumann on other sides,) the resulting temperature should be almost $T_0$ for all structures connected to that side. The p-norm weighted by material density $(\\frac{\\sum (T-T_0)^p \\rho}{\\sum \\rho})^\\frac1{p}$ measures how well the structure is connected. \n", + "\n", + "Additionally, damping terms are added so the heat can quickly decay away outside the material. The equation solved is thus $(-\\nabla \\cdot(k \\nabla) + \\alpha^2 (1-\\rho)k + \\alpha_0^2)T=0.$, where the conductivity $k=\\rho k_0$ for material density $\\rho \\in [0,1]$\n", + "\n", + "The solver assumes the side with Dirichlet boundary condition is the last slice ``rho[-nx*ny:]``. For 2D, as in the following example below, set ``ny=1``. In Meep, if we want the structure connect to the bottom, we can use ``rot90`` before we pass in. " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "foward value 0.6369810999723368\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "nz, ny, nx =25, 1, 25\n", + "c = mpa.ConnectivityConstraint(nx, ny, nz,sp_solver=cg)\n", + "\n", + "r = np.zeros((nz, ny,nx))+0.0001\n", + "r[:10,0, 8]=0.999\n", + "r[7,0,5:20]=0.999\n", + "r[5:,0, 18]=0.999\n", + "r[7,0,11:16]=0.0001\n", + "r[17:18,0,9:10]=0.999\n", + "\n", + "r=r.flatten()#flatten the structure before pass in\n", + "f = c.forward(r)\n", + "ag = c.calculate_grad()\n", + "\n", + "print(\"foward value\", f) \n", + "\n", + "plt.figure()\n", + "plt.pcolormesh(r.reshape((nz, nx)))\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The above structure has the following temperature distribution. As expected, component connected to the top (where Dirichlet BC is enforeced) has high value of temperature." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAWYAAAD6CAYAAACS9e2aAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAABWNklEQVR4nO29fZQs513f+fnVW3fP3HvnXknWSyxhK1klxAHWZBXDHiAxL86KTRbDJuvIPrB4l6zYXZRNQtgTQ3IMcQ4bkxwCnBOHg2IcDAkYxwlEuxGRWQNrSICVSBxA8hoUY2Mpkq8k675Ov1XVb/+ol36quqq6uru6p3vm+ZwzZ6arq6u6Z6a/9e3v83t+j6gqFovFYtkdnJN+AhaLxWIpYoXZYrFYdgwrzBaLxbJjWGG2WCyWHcMKs8VisewYVpgtFotlx7DCbLFYLC0QkQdE5BMi8oyIvKPi/teIyEdE5DdF5JdE5G7jvu8Tkd9Ov/7CwnNts45ZvL5K7/zWzmexWPYXPX7pJVV91TrHcI7uUcJRm3M9rqoP1N0vIi7wO8CbgGeBJ4C3qurTxj7/DPi/VPX9IvJVwP+gqt8kIn8G+CvA1wI94JeAr1bVa3Xn81q8ts5wBhc5+OJv3OYpLZZWhONh633jcLLBZ7IZHC9ova/XG2zwmbTnxi9//6fXPkg4wv/8r1+42+Tfvfe2Bbu8AXhGVT8JICIfAN4MPG3s8zrg29OffxH4WWP7R1U1BEIR+U3gAeCDdSfbqjC7fo8Lr/7D2zzlqSGatBcOS0K0pIBGNeIcTYqOKw4nhOPh3gi04wUEh0dz292gX7m/u6Iwu0uIfxtudHq0tXk18Bnj9rPAl5T2+Q/Afwv8EPANwHkRuTXd/t0i8v3AAfCVFAV9ju06Zs/n3G23b/OUlhMgnEZbPV8Uhu33bXDGptAWLoSpqGVCH42HOF7A5ObVnRfn4PAoEebzl/JtZQF1g6IQL3LXywq3621VZkpI208Lt4nIk8btR1T1kSVP9h3APxCRtwMfBZ4DIlX9sIj8CeDfAi8Cvwo0vkm265g9h/O37MbHJMv2icK40+OF08XHm7tInE/+/+KK55LtG4UhfibGhpD7pOJ9cMTk+CrATotzcHiEf3ABtzcgOEhejylSpsCa4un57tyxHK+6TqBq3zo8f6drDV5S1fsb7n8OuMe4fXe6LUdV/xOJY0ZEzgF/TlWvpPd9L/C96X0/SZJX17Jdx+w6nL9khdkCcbSeSC8S5aqLQJWTN/fLjpntF4cxDHpzjzUduhv0mR5fY3Lz6hLPfrM4XoDXG+AfXEjF+Qi3N6gV30x0s21lAXUrRLlKkKv2q2NbIi2O01Vm/gRwn4jcSyLIDwJvK5xL5Dbgc6oaA98JvC/d7gIXVfVlEfki4IuADzedbKvC3A8c7vsDF7Z5yjPDpGM3ui2Gk/YxRMa44bUOJ/PiOyltK18UMkGOwphwGtEbeLlgZ/eZjjEOXSB11IYD3YXcOcuTg/OXcL0A/+CI4PA8nu/OCXDyc7LN9Zx8uymwZQF13OLtIKh2zIOa7VX0lhD0k0JVQxF5GHgccIH3qepTIvIu4ElVfRR4I/B3RERJooxvSx/uA78sIgDXgG9MBwJr2aowB57Da2492OYpLSuwbZGvEtM66kR5WJNrZ6+lfAEwj5OdfzKJctEOpzFRGNMbUCvWrncrk5vX8+OcdO5s5snBQfrz4XmCgY/nuwURBgpCnN3nuE4utmVxNQV0EMykI2gQ1sESUcdGBFpaZ8wLUdXHgMdK295p/Pwh4EMVjxuRVGa0ZrvC7Dp83iUrzKeBcdjtAN80bldPXyXAlS55TniTSKIgyMaxJmHMcBLm9w8nUS7UvYFXcNWulzwucaBJXb4bDE40dy7nyf7hEa7nEQx8+gc+vYGfPM9clItCnIlwz3Ny0TUF1xTYTECrXHGVSC/jnpcR8tPMVoXZdx3uOt/b5iktW2S0htOetsicx1HMpVRgZo9TOCztZ1w0MsE3RTh3yCXxHod+vp8p1MNJBIOZoy6LG5w/sdy5Lk/uDXqpU3boDXx6g+StXifEgefkophsmwlkUCHE2b6+I3PPqefNi6vvzu9XR8/d/Whj02xXmB3hjnPd1jpa5olaus9Ns6xQN2XHMO+qq44/jWLOpQIyTsV+GinnUhc4DiMu9Lw5wQ48h0kY05s4+XPJt3mJQA8Cl+EkYsQU8BkPp3i+m1d4RF6A6wV5OZ3jBRt3zl5vgOMFyeBeMMgH+Zw0nnC95MsU5JnjnQmy6YIDQ5irBDgTXlNsq8TUL23rt4wqqsR+VUSc2nrtXWa7VRki9O3VcPNs+NNg1HIaf90bsS62ODQcWd3FxRTjc4E7J+bTeHZOP8yEOfk+jmJ812MaKdnntkwEBr7L527WiagHzBzxZBJBGm14vkM4dcBw5G5vMDcpZZsUB/eSbNkUZTOqyEQ5E+KjA5+B7zYKcSbCmfBW/Z3L4rooP24r2meFLQsznAvsH2AXaJvpVtPO0dSJa6/iY+2c+U21pXwR6HtO4bkfBu7ceTLxzsQhE+smoSaMuOUwYDiNKjPrwAvS6CMkCFziKCYK3XxA0PNdot6AOJwsPeNwXdygPzdpxPPdgls2RTmPJgxRzlzywHfzTxeLhDj7/ZZFtyyybo0D7tIZ1yEiK89kPEm2O8HECvPSTKPNxBJV4rgsqzy1cMEFYf6CkTxPU3x7rhSEPHKKj8mEIBPtNkLtu9Lyd+0xDmMmkygXPs93d6Jc0fGCQoyREZREuZwlZ6J8oefR81x8V1qJcbatLLxlwXWleLuNOa4T87PCVoVZAJ+T/wfeJ7Y9SB1Ldyesjjzm33CmIGYXjDqNNIV9JuJSEG5TCCJH8zd533MYhTE9kgzZD1ymsdL3HPww5sYk5FzgLXRyPc+Zc8272MokizEGgTuXJ2fRReA53HIY4DtCz3M5F7j4rlPrik0xzu5rEl5TYBf9Xr1NiLE4nffw2AbbncSuEc74+uL9LOvhrP5nXfbzjLp+7X1VEq8Vzy27+NRdFIoCP3O2PVcKAl4l2lE8E46ySENF7JHGHBeoz50nQeKaR8Npvs3xHFzPywcA3aAPJzAbMBHjYozR85w5UTajC98RzvU8eq6D7zqcNyo2oF6MMwFuEl9TbOs+pC1TsXFW2K4wxzEyvrnVU1rWpEF4oTlt1gqnMre/IdTZRaEs9i5FQffdmYibom2WOGeCEMZaK9LTWOcy6usTOBckYnFjHFbmzoGXiJ3jOsU4wxDqbZNVZGQUB/28xjw5iy7O97zUOSeOORPcOjHOtteJrym4ZVftqJnjz388knj5GaFViMhck6Z9YLtRhkY4I+uYTxR3yaiihdaoW/1RUabFz/dVbjnHEOPsLWwKe/62To8h6f6maLtG/Wwm2NNoXqSjWPEQXBEiVXxHcB3JneE0dmAcQs+rzJ3HYcwk8ApxxngY5gOA03SiidcbMNnCQKApyGa+nA36ZReSujz5XOAlcY4jHPW9ypjCc+bFONPdOgE2xVei0Pi54Z+qI0Hed7brmKMIbry81VOeNuQkazJr3LNQHbCWBXvOLZsXien8YzJhnxN010eYCbek9zvM3HYm2K7nzol0FnkkIp0eUoQRMUd9bxZv1OTOeTQQuHkfjrxsjrSN5pZijEK3OC+7QCUuPh/0891clOvy5HOBm7vkrKQ1E2NIXHNZjDMRzgTYdLkF8TW3112omsR6HcTpbEr2NtmqMGscEd+0jnktSr+/bQq1+M2xxtz+Tc/N9as70rqlja6LRLM3s7oBxCHqeLM3fyrG6gUzcchcNCGO4yXRh5t0k5lGs8FF35E06lB8neXP4zBOR7E8bkxCwGWQCvokjBmHcT4IONmBGtxMfMod4go1y4Gbi7LvSi7KfvppIXPEywhy9vvO/xZ1ImwIb11MYf6dzzpbHvyL0RMsvD+NdPX7bCPwTeeqerxOiy6oKOyzY5mPlWhadOapTmdOWqJhLtbZNq14rHqpAGRu2gGywUWXRJnLZV0x4CSleT3PgTBmRFLHO42ixDX7LkNv/oqSTdN2Pa9N+rNRMnEelAbx8ll8ZjlcmilnP7sijZGFK1JwyBJNF4px0UlXiG+0uYUVxHFsHfNC4oj4Zu36g5aOWcZN67S9WxF//qNhlWiXz2/uY96XCfhMuEdz+xREl/Yird4sQnGcZNDQFZmJM+QCHTlKFJHnzeMwTia0pHXO4DI1utQNAo8rx1M836G8MIrbGzA93u7/elWP5HKfi9ksvllJXOaWs8G9zCVDMpiXZciZKBdcchy2F+OSANc65E3FGnvEdqOMKLbCvASy5pVew9X+wcVrjizqnPOcEJfE3hT0KpHOtuW3KwRbgn61SJOcqyzSSRZdFOdkxyTamEaKK8mEa9eROdc8nUT4rpP33fAdKfWbcPOyuZOqzKiq0zX7JmeinNck17hlSH4HZVEuRxemS5Zw0kqMCyJcI7z20/SMLS/GpbCiWJxFVhXWRhaILoC2WDG66qJRfr5lga93zJM50S7cb9zW6TQV6pJIR0bcgZFJMy/OYEQb+dTv9LujEEvBNeM5TCMHiBkb8ajZ4tLzk8qMk8ac8Vfon+y7ebYMNLplKIpyVXRRJchtxLhKfMuRV9d0teagiDxAstCqC7xXVd9duv/zgPcDF9N93qGqj4mID7wX+OMkmvvjqvp3ms61XcccRkyu2sG/rnH7S4w6j8fLHbtX3aa19qJhiLEp8GUhNx8vnl/hllMHnAp2WaxNJH35ufCmIp0JdEGca3JnV5LM2U2rNnxHKl1zz0sGAQeBy5XUHZdX9TiJKoDi+n1FQQ7SAb6MbJp1nVtuK8rJtvmYYratKMZlAa5zyLvonNPlod4DvIlkhewnRORRVTVXu/6bwAdV9YdF5HUkTfVfC/x3QE9Vv1BEDoCnReSnVPVTdefbrjCrEk2sY+6adX6nbtDsoKNRffZceUEoCX8m7AUhLzvp8TAX7my/zG0X3XLirLNt+feSi4ZUiAmQaIi4LhoHiVBU5M6QilWUCnKFazYHATPMdpmzl+ayC7UF2cQSk2zQr1yJYbrlsihX5smmKJfdcYUzzgS5sK3SOXf/mxORpRaMbeANwDOq+sn0uB8A3gyYwqwkk0YhWXvsPxnbD0XEAwbAhGSJqVq2XC4XM7l+vM1Tnmlcf/Gft0l4C8eqEOGqC0JZ6LPjFx5viPeccGeCXBLrslCXRbqMBMYAIDXuOd1XHQ9XhCmaOEYnkXVfDdccaz4I6DtSuVKH5zsn2jPDSeuXTbKJJeagX7a9XB7nOTInynMDfCVRTr7P/g/K7ji/nW+f/3+rFOntR563iciTxu1HVPUR4/argc8Yt58FvqR0jO8BPiwif4lk+YavSbd/iETEnwcOgL+qqp9rejJbH/ybXrPCvApOsPyfqq3oZjRFItG0Oj8ti3/5nNkxTRE3xXtOuMfjolgbQg1JJGIKdUadi5bAcM8N4uw4Jdcca146BombzETtRvqYvGPbjtQyZ2SNi8qYg355/wujPC4ri6sU5VSQgTlRbivGbQS4zfjGMogUF9Jt4CVVvX/N070V+DFV/X4R+S+BnxCRLyBx2xHwB4BLJAuz/t+Z+65iy45ZCUfLZZyWlCV+b15/teW74ooVqxddECodsXn/NKwU78aLgHm/IdRQ1VVhMW3F2XV7BdecZc3l0rmel8yku3q827FcoVGRMehnZsuZZs0qMQSJlhPltoLcSoR3tzjgOeAe4/bd6TaTbwEeAFDVXxWRPnAb8DbgX6vqFLgsIv8GuB/YDWFGlbjGeVm6wfG9zi5+Xr9XKdbL4PaDWrdd+5jALwh+lEYfbq+XvHE9v9ZZNcUb+T7RvDjj+jiaDPJFYSL/njMb08oGAfcZc6mnqo5wviul5kJFuhDlNmIcLTlA3Uh3GfMTwH0ici+JID9IIrgmvw98NfBjIvJHgT7wYrr9q0gc9CHwpcAPNp1sy4N/MeGSH68ty9HVH3QZgV9WwMti7fpeUYjT2MMU6MLjIRdnM9ponpk4c81VjZwkms6aIQl5nAElEXMdqueS7wdmdYbZcMjsCtdYfVHB0qJc4YrLYrxsDLdpVDUUkYeBx0n+Bd+nqk+JyLuAJ1X1UeCvAf9IRP4qyXX/7aqqIvIe4B+LyFMk/4b/WFV/s+l8248yTrA14mnHG/idXfja/mM0CXiVYDuBt9KbLhPpzEFn0qowJ87Z4GCZxkgjyEQ5GQSE2YST7LbvCCMSZ2k2BhoELiN/dzLmRZhLP7lloW7Kiircsk5GKwlylSvehBiLFGu710FVHyMpgTO3vdP4+Wngyyoed4OkZK41W44yIJraFUw2RzcXvWUEvukfqMpDN+1vOuk6F13YP402Cm30DXFeijhMctTUTXt5c6OEfLJJ0/P3nKQyojfYyqoZTtqQ3w2SlbGzJvlZc3yzhrlnTMGGonPOqjEgrfOuKYvrQpRNQa4SYltOm7BQmEXkHuDHgTtIrqePqOoPicgtwE+TFFB/CniLqr7SdKzEMduMeRN4A6+Ti57rO60/1SwS8PI/V5W7XiXHLmTQLXJnkzrXTCqkVTlzVja3LLvYbtKsuzZn+kG5eX17GkW5JMhlMa4S4i6ds4jMlRDuA20ccwj8NVX9dyJyHvgNEfl54O3AR1T13SLyDuAdwF9vOpAqRNP9zefOBst8iKoX8CrRXiTU2f1m3FHnostU5c5lmtqWSjgBx6vNmYG8nvnGErpRFud4zcb5XYh9eaHTcr68jFvW6aS1S87+dm3EeNkB49PGwnehqj5PUhiNql4XkY+TFFu/GXhjutv7gV9ikTBbx7wREre8/gXP9d3Wf5/FDr345lsk1KZIt7k0FAYJa3LnMnlTpCrXnDZFkjjMy+bycxn1zJDUAy9aWLSOk3LRfmnaePk1ZWVyy9JGlKsEuTLGOONibLJUxiwirwW+GPh14I5UtAFeIIk6mlEl2vOSI8tiqkW7WajNf8Ts7WnGHJmLXlR+V5U7V5GLs5sKeNYxzegFbdYzV5EtcLqrzM36MyaWZJj5MpD8DpZwy9AuushE2RTk8t9xc4N/u/s3qqO1MIvIOeCfA39FVa+JUWqTloRU/veKyEPAQwB3+AHhyF4VV8XrV/+51r3YuekssTZu2Rskz6HOoVe57rJQl3NsU6S9fkA8DYsOupRDZ+KcTVyJJtPK3LkwKFjRQzp5cIQwySMMiaY4jlfImTPqBgDL/TJ2gXKfDBNzYkmGo1H9WnwNEcYqorxIkNetnT8NtBLmtG3dPwf+qar+i3TzZ0XkLlV9XkTuAi5XPTadb/4IwB85OFxl4pYlZZ2LWp2oQ7Owu6WpvVWiWziWIdiuXxT8majH6f1Oev8Ub5A41XA0ycUZZhGHOZux7JyzWCOb6h2VZgtCsUdDXdYs4SQvmytTzpvLlDvMnSRBxXTsKqrimKwnRsEtL6Bq0LWNKG9FkE/r4J8k1vhHgY+r6t837noU+Gbg3en3f7noWKoQTWy5XNe4weJ/vDaiXiXeVaJtinWVyy676jYCnTnozD176ZTsOfdMMdYAan+uw8yayXo4x2GeM5tk3ebMxTfKee0+41ZE5XVr8jVSVQ5XI8pVkUVZlM9664Y2jvnLgG8CfktEPpZu+y4SQf6giHwL8GngLRt5hpaFrHKxqxLzJvE2Rbss1nWuehmBLrvnKnHOiCdh3sOjKtIA8sHAfKmpul7O0SSdDTh/X7aySVt2Icusc4dmj4y5xyyaWGJQjjHalMRBvShvWpC7nGCyTdpUZfwKxcUfTL56qbPFajPmLdEUXUCzmLcR7TZCvYxAl91zU+6cxRrLuOa61VPmSCealCszymQDavuAObhXLpXLyCaW5NQM+i2icbDP+HmRIJ/1njpbb5Rvy+VWp5znNrHMBbAs4nWibQp2G6FeJNAwGywsu+e63DkXZ0r1zuZMQXMgsGIprfJqKZA6Z8fLJ5rU0fMcrqevr6q15km65rbOsFwqVyCatsqV5x7WIsKAkxBk2an8vy1bXvPPsg7rXNSaRL19hFEU7EVCbTppNyhWa5gibVZyZO55kThDu0gjq86oWmA2mbpdKpuroVHMdpimcj5vhddTFWNURRhtcuUmUT7rzc627JhhGtvBvy7xnXZuYJlSuMLjGp3xYqHO9m9y0Zk4Z9vL4pzsV8yd20QaJoU1Blv20fBdIVIaa5l3sVQuo8rRl1nU6rOqL0YVlZNISqKcueVFLrlLUXac9lUqu4R1zHvOOhe6sqg3iXcm2m2FOhPpNgJtzlw0o4263NmDxkgjOY+xYkrNat3lsjmJQ0inZjuOR0Q70W2qGd4W60Yo2VTsYkP8xT1TmiIM8+cqUd6kIO87W/2PioFhZEuZN8WgqvapgTpRr3LhVTEE1At1JtJtBLqYMUetcue6SAMqXHNNnJGXzQ18iKLKyowqyrPnyrjeyQt1eU3CbNZfq6nkUfOEpblqjJbTq9uKsm0NTEtLYNkLhpEu9VXHNI7zryrCYVjrrsNRWBDrctxRFvJMoKuOV3+OemfVNF27doHPFs7wNLFq+mI2LKqjTY3yNp2yiCTLay34anmsB0TkEyLyTNq4rXz/D4jIx9Kv3xGRK+n2rzS2f0xERiLy9U3n2nrGPFmhfaIlIeh48KkszlWO2xTnpuijnE+Ho7Dgnsv5c1U5nxlruKWP5lWuOTlWQ9Yc1Az41U3PXgJ/yU8nu8YyTZgW5cvQnC3DzC0vEuVddcsi4gLvAd5EskL2EyLyaNocHwBV/avG/n+JpK8QqvqLwOvT7bcAzwAfbjrfyX/msrSmq4tancCbQr2qSJdjjqZow4w1ypNUmiKNjKY4Izlu2n2uYnr20o30U/qew42K2ZBB4NLt+s6rU3aAvZpyMVeaJ5eYa/q1oY1bNmkS5a4W1HCkswHaNwDPZCtbi8gHSDpsPl2z/1uB767Y/ueBn1PV46aT2SjjDDKJde6rzKLYoy7qKMcc89FFXLgvu3/ZSCOscGP5OcpNciqWMWozWQJmfZnLTeV3iXVbic5NLoHWA3/RaNK62X1TOdwmRHlJbhORJ42vh0r3vxr4jHH72XTbHCLyGuBe4Bcq7n4Q+KlFT2bLg3/N2aalHcsO8rXBFOcqR5393crnzsS5ykGblRzl6o2m/h5NkUZyf/UgIDT0yginUOrRnMQaaR/maAKRm0wyaTn7b5dbfq5Ey8kl5sDf/CGaB/2S29sTZUekbdXMS6p6f0enfRD4kGqxDjFt9vaFJAu6NmKjjD2k64tbWWybRHoZgS6LM1CZO2fC3SbSKMcZJlVxhomOh5WVGavGGk24QfOElW2xisuvE2ezzWeZtjHGDjrltjwH3GPcvjvdVsWDwLdVbH8L8DOquvDjiB382wG6HtRblqZsuU6k2wq0Kc5QPyhYFufy46qPVT3hBGbOrTwAqOG8EOtklJTMWSoxm+K3YVGMkdE00NdV6waR+dLBFXkCuE9E7iUR5AeBt82fTz4fuAT8asUx3gp8Z5uT2Yx5B6jKfBd9dXl8k6ZcuWn/Mmb+XH6TNZXTJduKeXNVU37zTV1+0xcmOFTkn23z5WXZhb6/XXdSW6Uaw8SMMZpqlU23vIv9dFQ1BB4miSE+DnxQVZ8SkXeJyNcZuz4IfEBVC2+KdPWne4D/p835bJSxp3T5ySM7VtvYomr/qn2ncbyUc64rowMKefOycUY0mnRamWHStgZ2F1i218eiiox8GvaCGGOuRG6LouyIdDYWoKqPAY+Vtr2zdPt7ah77KWoGC6s4gZl/O50jdcZgDztaTWKtHfirGnCs2r+8b5M4t2FO0BvijG2xq9UZZao+wrdtxrTQKVdUukC1SDceZ7dz5RPDOuYNscoFaBfEvMk9ryrOJk3VGua2qoHARZRzZpNoPK7smbGJgb9dpFzLO7cI6zLUzaAs71bR9H4bubKJdFfHvFX27xmfYoZRXPl1EtTVNrfd16RNo6U2q7CUs+ZdnSW2TzQuwlpB1fp+0DwVHmyDomWxwrwH7KM4N5X0NU1AMWmadFL+CNw0ADg7WYMARdOFzXt2EbfXTVle3lmu4XdQ22ukRNtFVZtijLpV2M8KNsrYEzJx3nbcsUxUUd7X3M/MmhfRNAi4DNE0XLwwqzHJBOZbf9avqnaKqXDQq1aytF2RZFOVGE7axGjf2O7gn8LI1jHTX2PwaBjFJyLOMF+F0TZzrqIqa140I7DqscnjiwOATRNNqiaZNGE2y2fHZ62edLneooG/tpNJzrpbBuuYT4Sqi9MyYr0r7rlpkK9qn2Vcc0bThJNWj6/pMmfSdvafWdFQ1xhoV+hyunjT5JI6MV5mtetN1i13OMFkq+zfMz6ljGItfLXhJLLnNlly02Bg08QTk7rsedEAYBtBaJuVtsV843c9waML/DUuIo3LSWUrliw58Gfd8mKsY95RTHFuctMnEW3MP4dm59zGWS8bZyxDNJkuzJo3gRv04ebVrZ93G9Q55bYDfxmbnuW3rxnz7l3eLXMsctEnWVZXR9uZicu8Mcv7LqrMsKzHpqauWxaz3SZGLN/nYV/ZRGOiUaw74Z6XnVRSZpWsuWu6WsnkLFI3669M1YVy2zFGl1Oyt4mNMjbEogvQqsKdOec6gT6pgcFFLCPcZVaZBTh3jIp+GZZ6llm5BJaryJhtaxhj2MFGRtvECvMJURbuZYV6V9zzJumqntlydhH2p7eJyX6/c08Rq7T0bJM9b5NyhUbb6owybaZnz7cSLTq2ZQehLC1pUdGyTKlchq3GKGLtyA6yaJmnMk3ueR+cc1ajvG2H3FXrT0s9dT0ythVVJE2M9i9j3u13rKW1i25yz7tSsbHt9R7LuWfbQStLNXUNjNrStsXnrubLIvKAiHxCRJ4RkXfU7PMWEXlaRJ4SkZ80tn+eiHxYRD6e3v/apnNtfUr2rojEKpyk86xrx1mmzj13PSjYdur1Jigv0mouzgrN7T+7ZFezS7PN5Tbd4qrx0T7EGCLiAu8B3kSyQvYTIvKoqj5t7HMfydJRX6aqr4jI7cYhfhz4XlX9eRE5R9KevhYbZSzBqheVLgW9jSCeZLSxqbK5LiozThue0VnO2bFpx7tSUy6yRt/pIm8AnlHVT6bH/QDwZuBpY5//CXiPqr4CoKqX031fB3iq+vPp9huLTrZbf81TStc9ltvEG7sSbaxbt97UFnSX2MfZZWUkmiDRpHJiSe3q2C0EeG7a/G6Wyd0mIk8aXw+V7n818Bnj9rPMLxX1h4E/LCL/RkR+TUQeMLZfEZF/ISL/XkT+XurAa7GO+YQwxXFVB7uOey6L8yrPYdmlqBbdB91PzZ5r/RlOoaMexvuG7wiek3x3JWnKJFEIcVjZ6vM04NC64dRLqnr/mqfzgPuANwJ3Ax8VkS9Mt38F8MXA7wM/Dbwd+NGmA1lOmHVEuk32vGhSSvk5mGwrV2/bPa68X9PCrG3IlpfSyQiCw5WPU4dXcREIVxxEqzrWLrDsOn+Fx+5BvpzyHMkq1xl3p9tMngV+XVWnwO+JyO+QCPWzwMeMGORngS/FCvP+sKpItxXoZXtBLxo0bOPatzlQGE9DHH+1f2uJJijri5+3YArwrgpsHXU9M6oEeZUa5k0isl53PYMngPtE5F4SQX4QeFtpn58F3gr8YxG5jSTC+CRwBbgoIq9S1ReBrwKebDqZzZh3mFUy6TbZ86rPZRO0WQ9wm0hk16briqoa5h3NlxeiqiHwMPA48HHgg6r6lIi8S0S+Lt3tceBlEXka+EXgf1fVl1U1Ar4D+IiI/BbJhMR/1HS+hdZCRN4H/Fngsqp+Qbrte0hGIF9Md/suVX1suZdqWYZlqikWuec20cYyLNvUaNW+GbYyY3fIejHvOiJCv6OKlVTjHitte6fxswLfnn6VH/vzwBe1PVebZ/xjwAMV239AVV+ffllR3gKbcM/LOOiTqkGvq8xo67CWzkBP6UDYKjStXrIMVZNLmvLlbCHes8pCx6yqH100S6UtyvrlU7vONrLUZSaLbCp7rjpP0zlWyZmXrcwor/1XR3ndv/KCrEBSqXCK6Mo11rGrvUnOYhOjh0XkN0XkfSJyqW4nEXkoqw0ccvqvgmYzoqqvLjlJ99yGbU/B3ha9HZvMsW3WqcKoY5fz5ZNg1f+wHwb+EPB64Hng++t2VNVHVPV+Vb1/gM0HuxbpZeKNtj03Fp1vneO3YV8mlew6JzXpZVdm/QE4klxIF33tGis9I1X9rKpGqhqTjC6+odundTboWqC7omvnXEWVm17GNW2j/lVOWZyxLCe1tNRZz5dhRWEWkbuMm98A/HY3T+ds0pWLbiPOXVwI9rkRlWW7VK1cAns1seREaFMu91MkUwxvE5Fnge8G3igirycZz/sU8K2be4pni7Zd5OroqknRqgOCTYN8dfftwhqApwVnx3tvb5suy+W2SZuqjLdWbK6dSmjphnUFetGxT1sD/nLrz20R7OGbfh3W6WldF1XZgb95ztZ/1R6ySsTRZaSxbt6875UZrux+qZW7Z1O8t4kAriMLv3YNK8x7wibE+aTZpGiXezbsy0y1XcEO/J0stonRHrHsJI1FccO6kcauxRmbops+66efXWtgBGdzgonlBOh6ksomZmKettmde57GWPYQ65hPOSddpXFSbGPNP8tqbLNUTkT2YpygjHXMe8hpc6TbwA02X7HhrdGw32IxsY7ZslYj+9OeM0dqL4L7zj5WNO7hU7ZYLJbtIyIPiMgnROQZEXlHxf1vF5EXReRj6ddfNO6LjO2PLjqXdcxngDaudpvLP1ks+0a6qvV7gDeRrOH3hIg8qqpPl3b9aVV9uOIQQ1V9fdvzWWHeU6yQFjmJWX+W9dn0rL9sgkkHvAF4xlhQ9QPAm4GyMHeCjTIslhrUWd23ON7ihv2nmar1/nac27K+8enXQ6X7Xw18xrj9bLqtzJ9L+9R/SETMVbX76XF/TUS+ftGTsY75jGDjDMtZRKT1BJOXVPX+NU/3fwI/papjEflW4P0kK2IDvEZVnxORPwj8goj8lqr+x7oDWcdsac02+jRvAyn1lpCgP7/TGm7Zcip5DjAd8N3ptpx0Rexs+uN7gf/CuO+59PsngV8CvrjpZFaYLSfGKitlr4LbYh3A4gOa8+pxOOtDMgl3vyfJWUYAz5GFXy14ArhPRO4VkQB4EChUV5T61H8d8PF0+yUR6aU/3wZ8GQuyaWsLLHuJ688vm9RmIdYm1A1Q92xnw13h9YN9zJlrUdVQRB4GHgdc4H2q+pSIvAt4UlUfBf43Efk6IAQ+B7w9ffgfBX5ERGISM/zuimqOAlaYLWeaLMaQoM/pCGosBaS7JlSq+hjwWGnbO42fvxP4zorH/VvgC5c5l40yLGcLb3NldeH0bMcaq/Qn8QbWG1ZhfyuWvaXqTe34xW2ub//FV0GC/on1ZO4SB/D3sG+rdcyWM4v4Nk+27CbWTlgsllOMbftpsaxFFk14/eS7Gzil2/OVGO4SrTbdnu3RbNkPrDBbOmfZ2YO+0+2/YTYI1VS/XDmpxLIQe3HbDjbKsOwVVQN+VQ2MnKA0CNgk0v5qlRrTUzITctu4vru1VUwExdH9W+DVOuY9ZV96Wmxrdt8yyBIlc9NICWMlipVIk++7hLvBZkn2U8XJYR2zZWuYF5N1BHvZWX9Ny0rl4uP64Lqo46FegLo+sbhAdW3yODrbNcv7gyLxZluLbgLrmC2nhnINM8xHGOUGRl0xnOzfx2XL7mId8x6ySzHGptf7q6vIqJsx1jj7rBRhrPpR/axny24/IJpMT/pptEMVifbkuRpYx2zZSbJSucZ9akrlsoG/pll/dnKJZZexjtmyU2QOeV3KEUZTmde6g1ynufWn+EGrqdlO4BFP1s9y3cAl6jQWUrAZs8WyGqs2s1llrT9bbbBZzKzfrsW4GtYx7xmr5subzoJXZZnJJfW5cnMsYbrncqlcVsOc92J2fXA81PGS8jhNMuUwTr6PSu54Xwb9RmFM3+vuf8ANfKIl+y17A2/ji6/OoYqE+9cXejffrZZK1hn0G+5xeZc58FdVKgeJS6sa+DNL5VotKdWSaaQMGyZJxPskBo4Hrp9fnMq/F/sJI0FEHhCRT4jIMyLyjob9/pyIqIjcX9r+eSJyQ0S+Y9G5rDBbCky2UHHQtoa5Lm9uij2cwMsdciFnNpyy+CXxKdUwL6LsmneVsps3nX+kEKmutRL40kt21bDRnsyqEE0Xfy1ARFzgPcDXAq8D3ioir6vY7zzwl4FfrzjM3wd+rs3TtsK8J+xSidymaVuR0ZRfmhUZ5sCfKcji+5VuMJtcUjXrb9xClMMtTTfeJsvMljylvAF4RlU/qaoT4APAmyv2+9vA9wGFEVMR+Xrg94Cn2pzMCvMecBpEuc1raKrIaMqXqyaWZNTly6Ygr7PO3ziMGU5CJpOIKIzP5ComqzjnukjqBLlNRJ40vh4q3f9q4DPG7WfTbTki8seBe1T1X5W2nwP+OvC32j4ZO/hn2WmqWn1W4fV7842LavLluRgDKgf+6phGMeMw2puBv1UQ358rk5PeAA2LH/td3yOazgb0vH6PcDTeynNsR+sp2S+p6v2Ld6tGRBySqOLtFXd/D/ADqnpDWvaGXijMIvI+4M8Cl1X1C9JttwA/DbwW+BTwFlV9pdUZLUtxGtxyHVlFRmXHuAr33MZluf0A1/ca8+X85wWDWlUVGfs2628cRsDJxBCnbKXs54B7jNt3p9syzgNfAPxSKr53Ao+mq2Z/CfDnReTvAheBWERGqvoP6k7WJsr4MeCB0rZ3AB9R1fuAj6S3LR2z76K8ic5y3sBbqjm+STlfznF37mP1ysQdVt9UXrj2LGsWVSSaLPxqwRPAfSJyr4gEwIPAo9mdqnpVVW9T1deq6muBXwO+TlWfVNWvMLb/IPB/NIkytBBmVf0o8LnS5jcD709/fj/w9W1emaUdgSN7L8p1LBLrqoG/uh7Mi/JlmA38LcqXy13lsoG/KsbR/jnnk2bVi+muoKoh8DDwOPBx4IOq+pSIvCt1xZ2yasZ8h6o+n/78AnBH3Y5piP4QwDlOjzPZFLsgyJNYd+J5LJsvmzGGG/h5nFGVL0vQr8yXy5QrMsxSuUkYM5xGjMO4U6faNdM1npsEfXRadJRur1c5uWQ3p2UrRN0cS1UfAx4rbXtnzb5vrNn+PW3OtfZlTFUVqLUPqvqIqt6vqvcPrDA3smkx3OVJJot6ZKw8ip86ZQn6eb5szvbL6pczmmb8TWNlGsX55JLhJCr0yQin0U6UylWV9I3DuHCRCWNlGimxuKjrJ58WHC+Jddzy7MjNN3zaaC3zHrKqMH9WRO4CSL9f7u4pnU225VC7FudNiL0p0uYbdpl82e0HczGGBP1iY3yMKKMixqhyy2OjIiMTwOEkKpTJReHJN81pmpXYBYtK5BZFTLClkrnuMuatsqowPwp8c/rzNwP/spunc/Y4iTy5jZjWzQAcdZStrrMAq5kv18UYGVmMYbplCfpFt9yiP4bplqex5jFGVsMMEO3YjMDJGlUky07LbhJqcyKQdcbtWPjuEJGfAn4V+CMi8qyIfAvwbuBNIvK7wNekty1LcpI57q7EGuYb1Rz4a9Mfowm31yvEGOb3td3ydFbDnLnmTJTjHRNnWH2wsrxIbXn2X9OSXTtDR1Oyt83Cy5eqvrXmrq/u+LmcKXZhcG0YxY1d53ZlEBDqnVbToB8UY4zs+7JuuUwmylm+XDXwF02G673gDTAKY1xHcEWYxornCJEqjuMhWSOjOEBY77nv3iST/cN+rtgyuyJ0+0BZjLP+GGaMUf9gvxBjJIN/frNbDuPKErmqGMPMlzNOy3RsdYN8OSYJ+oUZgOXZf9lF0Jz9t1Ootmr0v2vsd3HhHrGvtcnb6DZnUq7OaBtjmG7ZHPQzB/xmUca8WzZpE2Nk+TIkgmxWY0Tj3XPL6zCXLy8xyWRRr2xLNVaYN0gmxrssyKtkzV0NAGbMiXHLhVfNGKNM1aCf2XOYrEQsddBtB/1MhzwO43zgL2Pb5XJROCEaD4nDOG+ilJXxZReQaVQdyTTirpYfl/uVWFbDCnPH7IMYL8si17yNgUSzTK4qxii45VJ5XLFMzqhbTt3yFIdRqAzDmHGojKKYm5OIG5OI6+OQG5OIG5OQz92ccPV4ypXhlKvHE67emDAZhYyHU8bDkMlwShSGTI+vMrl5lWjHPkK3qWU2KU5hb7+G4iIKA74bLplTVXQ6Xfi1a1hh7oB9F+OuhTUT8mHU3lmXp2I3TcMuPK7csIjELYvnN7vl4KBQiVEWZTPCKIvy5WsjLl8Z5aJ889qY42sjjq9cYfTKC1sTZaehqf9wEqYtSSPGYZS4/haNmPJJJilmZcayJXNtaplNbCndDPubWJN9FeMymTjXVWmUKzRGsdLf8GtvclPmSiVApVvOvpfdchZhRG4vFd/58rjrqUu+MZ4X5VeujhgPE1EeHU+ZDKdMbl5nenyV4Suf3dwvpAVRFq8cJL+HrJZ5HMWcN/bL4ppCZYZxf90AoHg+mmbobj8gmrR3m67vELUcIG07HX8hGtvBv7PEPjvkJlZ1z1247qpp2dUtQedjjAzTLZuVGHWLrY7T/DX7fnUUFkT52jhsFOXh9SGjqy8zuvbiiYuyWRUyTifAAIWcuRxnFDDW/gMKOXMh1iitnZjsaj1el1hhXoHTKMgmdSJbzprXHQRs+9G1XCY3226s79fgltUN0GAw10FuFGrqJutF+YUrozlRTqKLMcPrQ6Y3rzI5vsrkenU78qa4oUvMQcesrjobAMww44z8cUbOXCCNM5pyZmCubjyjsZRxm6SOedHXrmGFeQlOq0uuYhUH3IVrbvsRthxjQL1bziOM3iHqD/III8+VlxTlyXCa58nDV16oFeUMxwvmvpah6vF1x8gE2pyZmOXM42jWHc+sPskoNDMyKM8ABBpL5qoqM5rWZ7TMYz9/tOSsCLJJVe68jaw5aVbUnC8Dxb4YTW65FGGYufIoFausLG6xUx7lefI6g3ybcNLhNKI38JJIY5AMAB4N/DxnnqaDseMwpu85RI4Ckq+Y3SZnlqA/t7zUTqM617Z0H7COeQFnySXXsYwTzvbtYmJK1k2u0AQn/YicZZrZoF+lW4bCgF/sD5jiECm5OJfL4tqIcpYn70o5XBzOC09WX21OiClXZwB52VxO6parcuaMbJp7VcncKguz7gsi8oCIfEJEnhGRuVWbROR/FpHfEpGPiciviMjr0u1vSLd9TET+g4h8w6JzWWFu4KwLch3bng0IxdWw81x5kVsODmflcf4gz5WHxmDfOIxzUX7x5riVKC+KLk4Ss8Od2Zc5jzYq4gygmDO7s4saVDSBqqFuAPBEZ/91lDGLiAu8B/ha4HXAWzPhNfhJVf1CVX098HdJFmcF+G3g/nT7A8CPiEhjWmGjjBqsKBdpanjUFGcMI93I2n+FQT9odsvBQR5hhLEyTgf9ZrnyTJQvXx9z5XjCK1dH3Lw2ZjwMOb42YnT15cZBvpNicv0VXC9gcvM6pAVxrpf8na4CPW/2NxsELj3PTVf5TuIM3FnZHCQ5M14A0RSJJnmcka2aLX6QfK9YMXsZvIFHONzR/hrVvAF4RlU/CSAiHyBZYu/pbAdVvWbsf0i6gIiqHhvb+zQsLJJhhbkCK8rVmOLclDUv6lq3DubAUtPSUTPXl3yPxSXS5GN8pEmEMY2VG5OQF2+O+fRLx1wdTnn+ypAb18b5xJGs8mL4ygs7E12UGb7yWaJwkkYatwKJc47CmMukU8fDmEHgMvBdfFe4PonoeQ6uI3hpb+xItbDGUCLKzX0/slpmtx9ULjdVxw6K8m0i8qRx+xFVfcS4/WrgM8btZ0lWvy4gIt8GfDsQAF9lbP8S4H3Aa4BvStcQrMUKcwkrys2UxRlmv7OyczbFu8sWooWPzBWrk5iDfvlkEtXcLZsRxivDKS9cGfHC1SFXjqfcuDbm+HpSDjcejtPKi5OtT27D5PorefOkKDwq9IXOyueOBj5HBz6+I/RcJ3fNYZyVzYHvzlqAEqfa4frAaK7T3Cbpas0/VW3r7F9S1fs7ON97gPeIyNuAv0m6oIiq/jrwx0TkjwLvF5GfU9XaX6YVZsvSlB2xKbrbmBFYprACdqlEDpJpxlkTokgTYb4+iXj5eMILV0d8+uWbXL4yYnhjwng45fjamOMrV3ZiJt8yRJNR6uyHwJ1AsfNdz3OSaONin57n4rvJ7b7nMI2VnivE4hYHnlwXomxRVqNCw2z9GfhLzQDcU54D7jFu351uq+MDwA+XN6rqx0XkBvAFwJNzj0qxwmxg3XJ7yqV0ZXGGmXibObP5czgMW08yKTcucgO/UBWQ5csF0hK5KU7atjMR5RuTiFeGUz57fcynXz7ORXmfBvnqiCYjhqmrjcZHhNPzJLEmXHYdBoHHIHC50POYRi7jtJQumQ2YHKMuZ4bSqtmeD+Mdb4gfaz6FfE2eAO4TkXtJBPlB4G3mDiJyn6r+bnrzzwC/m26/F/iMqoYi8hrg84FPNZ3MCnOKFeXVaMqd67a1xZzxV4f0BsXZaaUYI/YHuVseRclEkpeOJ7x4c8zla2OevzJkeGPC1ZePmQynjK6+vNN5clvKuXM49fF8l+eDIYHncHTgF1yz7wj99BNFXc6cDQAug+N7xLvaRH8JUlF9GHgccIH3qepTIvIu4ElVfRR4WES+BpgCrzBbF/XLgXeIyBSIgf9VVV9qOp8VZqwor0uVOJdds3lfG+qctNsPKmtlq2b6QTLoN44ixmEy4Pe54TTPlT/98k1uXBtz9eXjpOfFFjvDbQPT8UfhEZ7v4noOVw98XrgyygcCzwduXtvcc6UxZ84wmxl1xWYGBBU6mhCjqo8Bj5W2vdP4+S/XPO4ngJ9Y5lxWmC2dsMg5l8vmpnFcu1J2mx69bj9Ilo8yy+RKM/3UH+Sz+kZRnPZVjnjhalIW98rVEcfXx7ko71Oe3JZsUDCaDHG9u+kNPC5fGeWRhu8I1wMvyZrdZPJNLC5ixhlpzpyRlcxZNseZF2brlrujagp3ss05kd+zOh5RCMfTmM8dT3npeMLvv3LM81dHfPryDW5eG3Pt5eNTK8oZ0WTEBPAPjrh5rRhpBJ5Dz3M5H7h55UoV6qYXv+l0v0Q5jol2PQev4EzP/LOivDkmsdZ2n1umgf4i8hVKyg2LvIBY3KQMLP26MQ6ZhDFXjyfEUcx4GBKFIZObVzt7PrtKNBkxPb5KHCZVGpN0+alkYdko/x1llSs5ZhtQy9Y40465y9paS5HAkZXL5qJp1LpaQycj1A/QoI8EU4gCxA0hnOBGYzzHp+86nAtcbj0IuDYOufNowJXjKYcXekyGUwaX7uTGZz+10nPdF9ygj39wRDDw6Q18js4FeV3zuZ6H70j+5Yrxd0srMySarNQiM56GhA0TT6INr5GosS418WVXONPCDFacu6Rqtt+qMwCTgSAPmM5VZUSjCVHg4/amaDhFp6lo+H6SjUaJa5ZoSt8POBc4ROoxDmPGUT9t6nPI704iwumAcJrMljsN1RhVBOcvERwccXDxIocXepy70OP2C31uv9DjVYc9bjsIuOXAp+cKPVdwNEqmYYcTJA4hiiCaDaDpdJLUMaeDam1qmMPhNF+9pGqQz9zW1eSSfebMC7OlG0wBLl/oyreX7Z0RjibJP2q/RzSazDXLqXPNGof4xLngHPW95OP6Jc0/xv9eFBca/+xiP4x1GFy6Az8V5fO3DBicC7jr4oC7jvrccb7HpYHPuXQQsO85+K4g8TSpxjDcMpAsXFpyzZkbjUYTomm4lDvdtFuGZObfPk5+scKMdc3rUuWKyzFGF42MwtGYIO2VEU2mROMxrjdMSrcqXLOEExxniO8f5lOPR0HMpYHPPZcGiXOeHORLMnm+y/GVxJ3vuzi7QZ/g8Ij+pTvpDXocXOhxeKHH7Rf73H6+x51Hfc71PM4HLoeBS88VvDTGmHPLMCfITaVy8SQkHI0r65cXlcRZt5xghTnFivNqlEXZ/B1uopFRPAmJpuGsljlM4oysj4PpmokS55e55qknHAbpbLeDgHtfdZjEG2HMi+nxHe8W3N4A1wv2tlLDDfoMLt2Jf3jE4PyA/oHPwfkel4763Hk04M6Lfc4FHrcdBBz1PfpuMi277yUxhumWAYim+XTsPMaAvNqhySXX5cumW97BhkYnjhVmAyvO7WkS5G0wy5l76HiIpvXMpmuWOBkEFD/EdwP8SOi7Th5pjKOY19x2kC9a+orv5C0z4U7cYLB3uXOWJ/ePbiUY+Bxe6NEb+HO5chZfJN3lMNxyOHPLkA/6AcWBv1K+3BRjmPlyE6ZbDkcdibXawT/LGWGRKJsxxjKCXf1mTAYAs5/KOXMSZ/j5IGDWbEeCtM9DOgjoO17umiNNqjQgYBopw1tnguC4Dp7vJrGGl5xjX8Q5OH+J/oVXERye5+BCn97A4/BCj/7AL+TK5wKX8z0vd8uuI7hCPuiXYwz6VYlzleBlMUYVmTO2bnkxVphLWNfcTFtRroox6n6viSB7uMHi6KOcM7v9IHFvvUE+CJi3pwwO80FAiRPXPFCHKFYO00VfLw18xmHSDGngu7zgOVxOz+X5iUg7XrDzTY3MQb6ZU/a4dNTn4kFQyJX9tHwwc8u+I/iuAFo76Jd8T2+X8uWoIktepz9GZ26ZdPBvD3t1WGGuwIpzNcs45bYs02HOJMuZo9EkjzPMQUAAJ5rkg4A4Hq7bwxXFdZJII/I06RNxMCvH63lJB7bngyE3rmWv9yLATubOdXly0Pe4/WKfo4OAu476Sb1y4NFzHc4HbpIpp26551bEGMagX+F7Tb5c5Z6r8uU6t2wH/YpYYa7BivPukOSTiSAEvkc8CQsrZkTjMS4z0VA/EVodXgc3QKaJw3Ndn77XS9tbxkTqQH/2Fuh5Li8Gs4/hz5O4ZtdzcDyH4fVkpZRdEme3N8DxAlzPy5sUeb7D0bmAQeBxceATeA4Xel7SsKjn5T2YTbecxximW04H/XQyKlzw6uqXq2KMtvlyvn+HbhnsBJNTiRXnGV0M9mW/z0XrAJbdk+mo42lISHGJqShdlNX1EgHOP3IHfZzJzXw/8QJc4NAvre6cirNvvMZB4BJ4DpevFbNl17t7JwcFvbTxk+e7OOnrCDwnWU4qXevvXJDM8DsMXFyRObdMGvlk2XJ5sVINp+h4SDQezy6K6aBfPJkJatVsv3KWbN1yM1aYF1BePslSzSoxRrnDXDSJgRCvX/y3NGcBwsw1Q7LMVObc3N4UBTRd0UQnI5IWSgkOSTNcF+h7AZDkzVB0zr4rDCo63Hm+y81rY7J19XZtMornz36XPc/JX8PAdzmXxhdH/UScPQcG6YSSfKbf+GZttqyTUZItG2657ETLtctlt5zFGHWibLrl5H+hA1QLF419wQpzS86ye97Uwqom4ahOkOfbgGauOQi8wsBObaRhTjoZJw468EnaWuLCJKIsziYD36XnOVy+MnPInn/7TkxGcb2gdNshCFwGaSvPIG2C77tOXh6XuWVXaOWWdTIqZMtNbhmSbDkczmIOU4jbiPKuIiIPAD9Ecm1/r6q+u3T/twN/EQiBF4H/UVU/LSKvJ1lm6gJJA9XvVdWfbjqXFeYlOGviXCfIXf8OygOA2ZvUDdzCPnWuOaMp0oDrCDPXDOD3PPAcqsS5n9Yz+44QeLPfw2US8Tu+7gAXd2IySpIzO3m+bDIIXM71vIJb7nky75bDSbFZkZEtA0u7ZaDSLee3G0S5M7dMkjHXle8tg4i4wHuAN5GskP2EiDyqqk8bu/174H5VPRaR/wX4u8BfAI6B/15Vf1dE/gDwGyLyuKpeqTvfWsIsIp8CrpNcBcIuVpnddc5KtLENl1xFNIkLZXOLXLMH+SzApkgDyGcEOhznEYffO5+LsxvGuZu8OYmAgJ5bigcCj+evpIOJnoNzzWHXJqNk+fggcBO3b5TH9dysg1zRLZtNioDCgF+VWwZyt2wKX1u3XEeXotwxbwCeUdVPAojIB4A3A7kwq+ovGvv/GvCN6fbfMfb5TyJyGXgVcKXuZF045q9ctH7VaeQ0u+dlRXmVfDkbADRz5nKcEU2i3DXPHFfRNcN8qVZjpAFoWt/M+CaO4+H7g1ScMz/tQKkFcbli40rg8srVRITNySgnmTtnA39Zvhx4TjLgl025TsvjvFIlRrmLnBrN8LMBP3N5pqxhkUl5wG8Vt7wRUVZtW1N9m4iYq1Y/oqqPGLdfDXzGuP0s8CUNx/sW4OfKG0XkDST/Xf+x6cnYKGMNTqN73rRTbntBM0W6rtbZdM0meaTBNTi8QHzzmim5Sd48AYIBDK8mzrmFOEM6ESV1pJDNFMwE6VYc72Ry56wqw8yXbzkM8jX9zMkkVW653HO5UB7HzC2XS+SqBvxmP8+75ba58glkzi919YlfRL4RuB/4U6Xtd5Gs/ffNqtp4FVpXmBX4sIgo8COlK8yZ4bS45zaivKnXaYqvGWfUuWZvUH8sUzxq8+bBeYjSeGRyXCvOfj9th5kNoBkVG1UzBY/T+7aZO7upW8/6fGQxhu8IvdRBL+uWswij7JahetCvrVuuY4cjjIzngHuM23en2wqkq2T/DeBPqerY2H4B+FfA31DVX1t0snWF+ctV9TkRuR34eRH5/1T1o6Un+hDwEMA5Fi+yua+cRvfcJVW1y4vijCbXnDkzDypdc0ZT3iwTZq44muLEIYE/wPV7uAK+k6wcXVWxYQ4KmjMFy5NRNrnqdnD+Ur4qyeGFZLbfXRcHXBz43HnU59aDgFsGPoeBu7RbzgmnBbdsxkZdueWyKHc7JTtuXEFlCZ4A7hORe0kE+UHgbeYOIvLFwI8AD6jqZWN7APwM8OOq+qE2J1tLmFX1ufT7ZRH5GZKA/KOlfR4BHgG4XXrdLfa2o5wW99wVVb+Put/Rsq7ZbG5UJ86Tq9cJjs7PxRkx4ByeL4pzWkqX1Dn3ynsXZwm6mSi7vJCW0ZVz501ORskaFp277RaObj1gcC7gNbef4/bzPe591SF3nutxy8DnlgOfc36SMffcxP270RiZDpHxTZzpMTIZosPrxDevE9+8VnDLZVGuc8uZKEfTuLJZUSbKiyowdrVsTlVDEXkYeJzkX+R9qvqUiLwLeFJVHwX+HnAO+GeSLM/1+6r6dcBbgD8J3Coib08P+XZV/Vjd+VYWZhE5BBxVvZ7+/KeBd616vNPEWRPnUaytBgCXdc3mhJPsje0NvLXEWdKP5ZI553RAEDdKMudUnL1oyoE/wHedpCWmUbHR8xxupM+n56WxRk3u7Pm3d94EaXDpDvqX7mRwftCZKDdFGFWinLnlsihnVE0mKbMNUdZYC05+rWOpPgY8Vtr2TuPnr6l53D8B/sky51rHMd8B/Ex6ZfCAn1TVf73G8U4Vp1Wcu3pdi1xz20jD7KPRRpyV1P8enCe+cQXn3EXyzHkC4rpJnbPrLxwULOfOQVoN8YLncCVwO693zhoW9Y9u5eBCn8MLvUZRzhrgN4lylivHN67kTfAzUc7dco0om5jr+ZVFucotm6JcJchnvR3oysKc1vP95x0+l1PHaRXnNgyjOB9MXPR7qHLNGYsijUyk22TObcRZSeqcNU7U1xRnVxx8R/HCRJRHxlqBvXRmHWT1zuNiE6S03hlWy53LvZabRDnrs9zzFg/2xTevF2uWy6LcsF5e5parFlndGVHWopPfF2y53IY5y+JchRlntHXNbSINtzTjzcSsuV0ozkE/qXNmtqxSPihYkTu7B/5ctJHVO5ebIK2aO2eifHDxIgcXehycT1a6vuvioFKUD303n93X9wSfGGc6RCbHiWOeDJHJzYXTrttGGNnfLPl1NVdhNInyWXfJJlaYt8BZEOe2OfMiMte8TKRRnq5dds35+oAYi7jCnDhL0E/qnAFcv3FQ0HOSmYLjUGujjReujmZ9NirqndvkzmaefJguqHrpqM/tF/rcddTnzqN+0Sl7TmtRLrhlM8JYIMomVYN9dW75JEQ5yZj3T/CtMG+JsyDOTZivv8o1L24FmkQaZh+N8nTtNnmz2ce5LM6kP8t0OqvYMAYFJZwgwQEH/oAps9zZc5y5aAPm+2xkuXObeudynpwtE7VQlEtTrp1JSZQremHMRRgNFRgwH2Ekv/tuRXka71/80CVWmC07R51rbpM3tx0MzDDFWTw/bxEak1ZsFAYFDxBjMorru7gC4yiX9zzaSETSoedl/ZBnuTPQ2Hy/bpDv9ovJKte3X+hxx/leXqdsinJ5sG+uVtmswDB7YdQM9mWYEYZJ02BfFdsWZdX2k1x2CSvMW+SsuWZzAHD+vuVcc9u8eZnBwJzxGDec5tO3ZTpBJqO53DkfFExzZ/EH9L0evqt5SV0WbVTNFjw68Dka+LxwMOTylVG+6Gtv8Br8gyNG117E9YLG6OLowOdVhz0uDXzOB26jKDvDq0WnbIhyoV755rVclCfXbhYy+XKunP1uswijqSyu7JYXifJZd8kmVpgtS1N3gVmUM5cf16auua5Pxkyo6wcDm8Q5moa4voc7meIavTXU85FwWp87V0QbrusXZgt6IYQxec3z+SBZPeTSwOeWw4CLBz53Hg144Woi0MMbE4KBz+TiRRzPydftMwf47rzYLwhyz3M4DNy8+qKQKU9u5PGFM7w666/cUpQzp7yKKNdFGKYo2+hiMVaYt8wuu+Ymh9vVMetef1UGvSjSWCTOCWnlwGhCPA1xfA9vEhaWpjIpRxuSTk+W6XQu2sjcs7h+nj1nE1LCeFZWN411TqBfGU4rBdr1nFaC7KctPFvVKaeinNcqp+KcxReTazfn4otFopyxKVEeRt1MEraDfxYLy1dnLCqfW0eczczZG/iEo8nCaCMaTXD7AW44RdPMOSaZKZi756Cfu2dToM1eG5HO4o0oVvquwyiKOep7XB3NC/TRQcDV4wmDwOP28z2ODvy5HDlb3dtzEjde55IL5XDmjL7phPj4ej7QN7l6fW6gD9qJcjSNGntgtBHlOpfclSjvM1aYLSuxjPNv65qLj5mPOdYV54xWuTOJe86mcYsp0pl7nhPoEE3jDccYHIwUwljpxcI41GQ1kTAuCPSdR30+dzMRwaqBPVeSlUf8tEOcKzS65LwUrjTI1yTKWfVFG1HO6FKUNyLIqnu52KsVZkvntHHNi8rnzO1VMwJXEee2uXN+23DP5YFBCfrVAh1NiFP37DgeB/6AWFwiVaaRMvBSkXaFQ99lFMXcnES5QPdch/M9j9sO/DymyNyxK8lCsT4xEofIZNrKJReaEt280diUqK4HRl2tcva3yH9nuyTKe4wV5hPgrOXMbY7bVpwLE0s2IM4ZmShnmAODWfZcJ9BOFCXu2fFygXZdn0BAXY8pieBGCsNQOOc7XOx7eR10NnMvy5ELYjydJgunhombdUZXW7tkc5AP5vsqLyvK0SRaW5Q3LcjJmn82Y7acIZouMMu65jasK84J1RUbjMZ4/V7tuXP3nN7WBQIt0QQNBrlAZ1Ud6gX0HI/A9VHX49BPBDkR6bhCjKdFMY6mSa8LSGqTq7rDLRDlrByuvGbfsqJc+P10KMpZb/OzjBVmy9aocs3LRBpQLc4Zi8TZ9bPZgkVxZjTB6we5e45LVRtF93wjiTayxvtNAp1WbKgbgOvCFNQNCkLteAHnHA91fc6n082d6TGEMyEGErccTSBK4pLkibV3yWZ0kb2mdUV51Uy5ySV3Lcqqe7E6yhxWmC1rsazrXTfSgOZ+GnXinGGKc8J8xQY0VG1MpriBkT23EGiYVXIIw2qhdn3jJEVXnItxKtJNOTLQKMpVlRdQ3ymuSZTrplmvIsr74JJF5AHgh0jGhd+rqu8u3f8ngR8Evgh40FytRET+NfClwK+o6p9ddC4rzJY5usqZ25bONYl7V+Js9tbIqKrYWOSeTQqDgw0CDUklBzDLomE2YAjguolgs1iMs9vASi4ZFldeQFGU2/a+MB+/E9FFRxmziLjAe4A3kayQ/YSIPKqqTxu7/T7wduA7Kg7x94AD4FvbnM8Ks2Vtls2al4k0mu5rK84ZmTg3DQqa7nmSTUhJH29GGtnPbRw0GCumpE4aqHTTycHbiXF2njaCDHQeXZjbzP2zv41JlSg3CfIOVmm8AXgm7UOPiHwAeDOQC7Oqfiq9by47UdWPiMgb257MCrOlkk1VZ7Q9flPfjEXiDMw1PSq7pqpBwYRiY/i2Nc9VAq3jYV7Foal71tRJA5VuGpgT43yFb0OMoT6yAOZii+R3UO2Sk59Xz5PN/WH96KJLUVZtPfPvNhF50rj9SLpeacargc8Yt58FvqSDp1iJFWZLJ6xboVF1jKZZgYsGBKs60i3OnaFKoMvuOYs3ctds1D8XBLrXSxxt6qLz9QbT29y8ljjoklCXxbggxEC2Fh/UN7WHoiBDO5cMmxXlHXbJL6nq/Sf5BEysMFtqWdY1dzEQuClxBmqjjfy2b0YdmcDMzxjMfi6TCXQ0mqTNkdLoY5wIo5sJrDcb6CuI9c1r+fY8ooBciKP0ODCLJ5oG9oBCY/u2A3zJcaOlogtYXZR3MLao4jngHuP23em2jWCF2bIVlumhsQlxBpaMNpoHBzMHndU/l100kAs0gBv4swb95sSVCrEuC7HZFzlbg68usigLstk/uWlq9UnkydsQZNXOOtc9AdwnIveSCPKDwNu6OHAVVpgtndJ1+dxsv+XFGViYO5ejjSqBbsqfgUqRjgIvjzmAwlRv11jg1A1mwmmurgLzIgzF9QvbCLIZWyS3F08Y6VKU99wl56hqKCIPA4+TlMu9T1WfEpF3AU+q6qMi8ieAnwEuAf+NiPwtVf1jACLyy8DnA+dE5FngW1T18brzWWG2NNLlIGCda24TaST7LSfOQKtooyp3XkagzXtyyRqN8dKMN3PSGW5p3bxctNOp0lAtwBnmUk/ldfiWFWSYd8nJtuXyZGgnym0FuasSuniJcy5CVR8DHitte6fx8xMkEUfVY79imXNZYbZ0ziq9QLYtzjBfUldV8zw/axCyt000Hc+vzp1GHVAU6eT4yZRvU1jLom0yv9beuHh/TVyRPLdqQS5sW9Elw2p5chuB3IeJJtvACvMJscuNjMpswzXXsa44A0tFGyZVk1JMga4aJMzqoGH+zVVVtNX0BqwT4tn9qwkyNLvk8vZt5MmbEmTV/RR7K8yWrbNMpFFHW3GG5aKN4vYo3T4/MaXooKEp5ijvYdJUYdskxMlrMcrmGgS5sH0Jl2w+DlYTZeuSV8MKs6UVm55w0nSeuk8XXYozUFlW1xRvZFTXQUOTSOcY0YdJWYST5z1/vLYOGZZzyeXHbyK62IYgx+jeDTSCFWbLhlgU1TRFGpsUZ2iONqBaoDPK8Uadiy7GHBS72bWgSYSL+7WLLPL7tuCS6/Zr2t9SxAqzZW9YVZyB1u4ZqK17Lscb2ePb0fRWW06Ei/stJ8jJfYsrLuB0iLLNmC2nnq7jjGVdM6wmzuX7oV6cgbnBQaiON0yqBgrz++by6CLhkLne0nX7z5fwVefH5uvIH9uy4gL2N7o4LVhhtpwoXYpzeR9gpWgjY5FAZ/dBURSbRLoNVeKbP6cq11wxoFe8v11sAbvjkodRN83t4w6PtU2sMFt2mmXEuaoj3SrRBiwWaKjOoMtuuq1IFyKVhv2rVnxeJMZV+3S57FOXoryPIroJrDBbNkbbWu1la5ubjt9GnKv2M90zLBZoYE6kzX2y/aqEtExVXr3ocW3EuGq/RbEFdNeAaBlRtoJcxAqzpTWbLJdbJdKoY1VxhqJ7hvn8GYoldhnlWuhsv4zy/sXHLhbv8vHmj9HskOFsinKsyf/WvmGF2dKKVUW5qxmO6wwGNu3bVpyh2T1nVAm0uX+ZJsFusyRSG5cMi6MLOH2ivM9YYbbsDF1GGrC+OAOt3DO0F+gyq6xHV7fq86ouGU6vKCu6l9UgVpgtC1k3wti0a246xzriDO3cMywWaJNFYr3o8WXaCjKsv0DqPonyPrPWO05EHhCRT4jIMyLyjq6elGV32MY0bJNFeWDTG3oZMamrLqjat8phhsOwUvzCUZh/1RFN4tZfTVSdp+55Vb2OsyDKsSbnW/TVhkV6JyI9Efnp9P5fF5HXGvd9Z7r9EyLyXy0618rvOmM5768FXge8VURet+rxLLtHl6J80h8n1/2oPo3jpQQa2on0MpjHW1aQz6Iod0lLvfsW4BVV/c+AHwC+L33s60hWPPljwAPAP0yPV8s677x8OW9VnQDZct6WU8C2nbLJJlzzsvvXCVHdMkWZMLYR6SaBbdq/jmUEGc6WKCvJc1z01YI2evdm4P3pzx8CvlpEJN3+AVUdq+rvAc+kx6tlnXdf1XLer17jeJYd4SRFuS1dRRqL9l/GPWc0CXTl/kuIcPkcdYN7dc/vLIlyx7TRu3wfVQ2Bq8CtLR9bYOODfyLyEPBQenP8D/XTv73pc54gtwEvtd67Xfnq9ql/Xsu9vv3iNL822M/X95p1D/Aik8f/oX76tha79kXkSeP2I6r6yLrnX5V1hLnVct7pi3sEQESeVNX71zjnTmNf3/5yml8bnP7XV4eqPtDRodroXbbPsyLiAUfAyy0fW2Cdz6z5ct4iEpCE24+ucTyLxWLZVdro3aPAN6c//3ngF1RV0+0PplUb9wL3Af9v08lWdsx1y3mvejyLxWLZVer0TkTeBTypqo8CPwr8hIg8A3yORLxJ9/sg8DTJamLfpqqNQaYkgr4dROShk8xtNo19ffvLaX5tcPpf32ljq8JssVgslsXsfl2UxWKxnDG2Isynfeq2iHxKRH5LRD5WKrnZS0TkfSJyWUR+29h2i4j8vIj8bvr90kk+x3WoeX3fIyLPpX/Dj4nIf32Sz3EdROQeEflFEXlaRJ4Skb+cbj81f8PTzsaF+QxN3f5KVX39KSlJ+jGSqaMm7wA+oqr3AR9Jb+8rP8b86wP4gfRv+HpVfWzLz6lLQuCvqerrgC8Fvi19z52mv+GpZhuO2U7d3jNU9aMko8om5nTT9wNfv83n1CU1r+/UoKrPq+q/S3++DnycZKbZqfkbnna2IcxnYeq2Ah8Wkd9IZzqeRu5Q1efTn18A7jjJJ7MhHhaR30yjjlPxMT/tcPbFwK9zNv6GpwI7+NcNX66qf5wkrvk2EfmTJ/2ENklaNH/aynl+GPhDwOuB54HvP9Fn0wEicg7458BfUdVr5n2n9G94atiGMC89HXHfUNXn0u+XgZ9hQeeoPeWzInIXQPr98gk/n05R1c+qaqSqMfCP2PO/oYj4JKL8T1X1X6SbT/Xf8DSxDWE+1VO3ReRQRM5nPwN/GjiNjZrM6abfDPzLE3wunZMJVso3sMd/w7TV5I8CH1fVv2/cdar/hqeJrUwwSUuPfpDZVMbv3fhJt4SI/EESlwzJFPef3PfXJyI/BbyRpCPZZ4HvBn4W+CDwecCngbeo6l4OoNW8vjeSxBgKfAr4ViOP3StE5MuBXwZ+C8j6bX4XSc58Kv6Gpx07889isVh2DDv4Z7FYLDuGFWaLxWLZMawwWywWy45hhdlisVh2DCvMFovFsmNYYbZYLJYdwwqzxWKx7BhWmC0Wi2XH+P8B8SE3iZQ/o90AAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "#padding to show the side with Dirichlet BC\n", + "fullT = np.pad(c.T, (0, c.nx*c.ny), 'constant', constant_values=1).reshape((nz, ny, nx)) \n", + "plt.figure()\n", + "plt.contourf(fullT[:,0,:], 100, cmap=\"RdBu\")\n", + "plt.colorbar()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The gradient across the structure is shown below. Note the part that can bridges one disconncted component to connected one has negative gradient. One the other hand, the island in the upper left corner has postive gradient itself, but also tries to connect to the top and right where the gradient is negative." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure()\n", + "plt.contourf(ag.reshape((nz, nx)), 100, cmap=\"RdBu\")\n", + "plt.colorbar()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For connected structure" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "foward value 0.06833918047665405\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "r = np.zeros((nz, ny,nx))+0.0001\n", + "r[:10,0, 8]=0.999\n", + "r[7,0,5:20]=0.999\n", + "r[5:,0, 18]=0.999\n", + "\n", + "r=r.flatten()#flatten the structure before pass in\n", + "f = c.forward(r)\n", + "ag = c.calculate_grad()\n", + "\n", + "print(\"foward value\", f) \n", + "\n", + "plt.figure()\n", + "plt.pcolormesh(r.reshape((nz, nx)))\n", + "plt.show()\n", + "\n", + "fullT = np.pad(c.T, (0, c.nx*c.ny), 'constant', constant_values=1).reshape((nz, ny, nx)) \n", + "plt.figure()\n", + "plt.contourf(fullT[:,0,:], 100,cmap=\"RdBu\")\n", + "plt.colorbar()\n", + "plt.show()\n", + "\n", + "plt.figure()\n", + "plt.contourf(ag.reshape((nz, nx)), 100,cmap=\"RdBu\")\n", + "plt.colorbar()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From a992eb014de7ba91587b938dd0a77717b08bfd50 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 11 Jan 2022 17:48:14 -0500 Subject: [PATCH 007/155] almost fix --- src/loop_in_chunks.cpp | 15 +++++++++++++++ tests/symmetry.cpp | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index edde9069b..337d75fa6 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -411,6 +411,21 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con ivec _iscoS(S.transform(gvu.little_owned_corner(cS), sn)); ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform + + if (sn>0){ + for (int sm = sn-1; sm >= 0; --sm){//for previous transformations + component cSm = S.transform(cgrid, -sm); + ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); + ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); + ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); + LOOP_OVER_DIRECTIONS(gvu.dim, d){ + if (iecoSm.in_direction(d) == iscoS.in_direction(d)) { + iscoS.set_direction(d, iecoSm.in_direction(d)+2); + iecoS.set_direction(d, iecoS.in_direction(d)+2); + } + } + } + } // intersect the chunk points with is and ie volume (shifted): ivec iscS(max(is - shifti, iscoS)); diff --git a/tests/symmetry.cpp b/tests/symmetry.cpp index 5a9d1d775..c6c4b52ca 100644 --- a/tests/symmetry.cpp +++ b/tests/symmetry.cpp @@ -927,11 +927,11 @@ int main(int argc, char **argv) { if (!test_yperiodic_ymirror(one)) meep::abort("error in test_yperiodic_ymirror vacuum\n"); if (!test_yperiodic_ymirror(rods_2d)) meep::abort("error in test_yperiodic_ymirror rods2d\n"); - if (!pml_twomirrors(one)) meep::abort("error in pml_twomirrors vacuum\n"); + //if (!pml_twomirrors(one)) meep::abort("error in pml_twomirrors vacuum\n"); if (!test_origin_shift()) meep::abort("error in test_origin_shift\n"); - if (!exact_pml_rot2x_tm(one)) meep::abort("error in exact_pml_rot2x_tm vacuum\n"); + //if (!exact_pml_rot2x_tm(one)) meep::abort("error in exact_pml_rot2x_tm vacuum\n"); if (!test_metal_xmirror(rods_2d)) meep::abort("error in test_metal_xmirror rods_2d\n"); From 6b044bf21579341c2ac6620f384d8e61e9bd3ed4 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 11 Jan 2022 18:01:10 -0500 Subject: [PATCH 008/155] before fix --- src/loop_in_chunks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 337d75fa6..85bceed18 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -412,7 +412,7 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform - if (sn>0){ + if (false && sn>0){ for (int sm = sn-1; sm >= 0; --sm){//for previous transformations component cSm = S.transform(cgrid, -sm); ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); From 2e21ddd294991335abacf7b31da3ebf2ed9beffe Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 11 Jan 2022 20:51:15 -0500 Subject: [PATCH 009/155] one more loop over num_chunk --- src/loop_in_chunks.cpp | 32 ++++++++++++++++++++++++-------- tests/symmetry.cpp | 4 ++-- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 85bceed18..26a6427da 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -412,20 +412,36 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform - if (false && sn>0){ + std::set overlap_d; + for (int j = 0; j < num_chunks; ++j) { + if (!chunks[j]->is_mine()) continue; + // Chunk looping boundaries for owned points, shifted to centered grid and transformed: + grid_volume gvuj(use_symmetry ? chunks[j]->gv.unpad(gv) : chunks[j]->gv); + ivec _iscoSj(S.transform(gvuj.little_owned_corner(cS), sn)); + ivec _iecoSj(S.transform(gvuj.big_owned_corner(cS), sn)); + ivec iscoSj(min(_iscoSj, _iecoSj)), iecoSj(max(_iscoSj, _iecoSj)); // fix ordering due to to transform + + if (sn>0){ for (int sm = sn-1; sm >= 0; --sm){//for previous transformations component cSm = S.transform(cgrid, -sm); - ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); - ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); + + ivec _iscoSm(S.transform(gvuj.little_owned_corner(cSm), sm)); + ivec _iecoSm(S.transform(gvuj.big_owned_corner(cSm), sm)); ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); - LOOP_OVER_DIRECTIONS(gvu.dim, d){ - if (iecoSm.in_direction(d) == iscoS.in_direction(d)) { - iscoS.set_direction(d, iecoSm.in_direction(d)+2); - iecoS.set_direction(d, iecoS.in_direction(d)+2); + + LOOP_OVER_DIRECTIONS(gvuj.dim, d){ + if (iecoSm.in_direction(d) == iscoSj.in_direction(d)) { + overlap_d.insert(d); } - } + } } } + } + for (std::set::iterator set_i=overlap_d.begin();set_i!=overlap_d.end();++set_i){ + iscoS.set_direction(*set_i, iscoS.in_direction(*set_i)+2); + iecoS.set_direction(*set_i, iecoS.in_direction(*set_i)+2); + } + overlap_d.clear(); // intersect the chunk points with is and ie volume (shifted): ivec iscS(max(is - shifti, iscoS)); diff --git a/tests/symmetry.cpp b/tests/symmetry.cpp index c6c4b52ca..5a9d1d775 100644 --- a/tests/symmetry.cpp +++ b/tests/symmetry.cpp @@ -927,11 +927,11 @@ int main(int argc, char **argv) { if (!test_yperiodic_ymirror(one)) meep::abort("error in test_yperiodic_ymirror vacuum\n"); if (!test_yperiodic_ymirror(rods_2d)) meep::abort("error in test_yperiodic_ymirror rods2d\n"); - //if (!pml_twomirrors(one)) meep::abort("error in pml_twomirrors vacuum\n"); + if (!pml_twomirrors(one)) meep::abort("error in pml_twomirrors vacuum\n"); if (!test_origin_shift()) meep::abort("error in test_origin_shift\n"); - //if (!exact_pml_rot2x_tm(one)) meep::abort("error in exact_pml_rot2x_tm vacuum\n"); + if (!exact_pml_rot2x_tm(one)) meep::abort("error in exact_pml_rot2x_tm vacuum\n"); if (!test_metal_xmirror(rods_2d)) meep::abort("error in test_metal_xmirror rods_2d\n"); From e284e4c4e719725f901f5846085ee426838efdd1 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 11 Jan 2022 20:57:14 -0500 Subject: [PATCH 010/155] include set --- src/loop_in_chunks.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 26a6427da..de93b188f 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "meep.hpp" #include "meep_internals.hpp" From 6d263144f98a229e0a9c056d685e81da2e453209 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:09:12 -0500 Subject: [PATCH 011/155] different approach --- src/loop_in_chunks.cpp | 143 +++++++++++++++++++++++++++++++++++++++-- src/vec.cpp | 11 +++- 2 files changed, 146 insertions(+), 8 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index de93b188f..3d8801f4d 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include "meep.hpp" #include "meep_internals.hpp" @@ -357,12 +356,14 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con volume wherec(where + yee_c); ivec is(vec2diel_floor(wherec.get_min_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); ivec ie(vec2diel_ceil(wherec.get_max_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); + //printf("is (%i, %i, %i), ie (%i, %i, %i), component %s \n",is.x(),is.y(),is.z(), ie.x(),ie.y(),ie.z(),component_name(cgrid)); 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); // loop over symmetry transformations of the chunks: for (int sn = 0; sn < (use_symmetry ? S.multiplicity() : 1); ++sn) { + //printf(" sym sn %i of %i \n", sn, (use_symmetry ? S.multiplicity() : 1)); component cS = S.transform(cgrid, -sn); volume gvS = S.transform(gv.surroundings(), sn); @@ -395,6 +396,31 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con // loop over lattice shifts ivec ishift(min_ishift); + + ivec lowerboundary(gv.dim, INT_MAX); + if (use_symmetry && S.multiplicity() > 1){ + for (int sm = 0; sm < (use_symmetry ? S.multiplicity() : 1); ++sm) { + component cSm = S.transform(cgrid, -sm); + for (int i = 0; i < num_chunks; ++i) { + if (!chunks[i]->is_mine()) continue; + // Chunk looping boundaries for owned points, shifted to centered grid and transformed: + grid_volume gvu2(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); + ivec _iscoS2(S.transform(gvu2.little_owned_corner(cSm), sm)); + ivec _iecoS2(S.transform(gvu2.big_owned_corner(cSm), sm)); + //printf("_iscoS2 (%i, %i, %i), _iecoS2 (%i, %i, %i), component %s\n", _iscoS2.x(), _iscoS2.y(),_iscoS2.z(),_iecoS2.x(), _iecoS2.y(),_iecoS2.z(), component_name(cSm)); + lowerboundary = min(lowerboundary, min(_iscoS2, _iecoS2)); + } + } + LOOP_OVER_DIRECTIONS(gv.dim, d){ + int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? 0 : 2); + //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); + if (ishift.in_direction(d) != 0) lowerboundary.set_direction(d, lowerboundary.in_direction(d) + off_sym_shift); + //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); + } + } + + + do { complex ph = 1.0; vec shift(gv.dim, 0.0); @@ -404,6 +430,7 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con shifti.set_direction(d, iL.in_direction(d) * ishift.in_direction(d)); ph *= pow(eikna[d], ishift.in_direction(d)); } + //printf(" ishift (%i, %i, %i), shifti (%i, %i, %i) \n",ishift.x(),ishift.y(),ishift.z(), shifti.x(),shifti.y(),shifti.z()); for (int i = 0; i < num_chunks; ++i) { if (!chunks[i]->is_mine()) continue; @@ -411,9 +438,33 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con grid_volume gvu(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); ivec _iscoS(S.transform(gvu.little_owned_corner(cS), sn)); ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); - ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform + ivec iscoS(max(lowerboundary, min(_iscoS, _iecoS))), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform + + - std::set overlap_d; + /* + if (false && sn>0){ + for (int sm = sn-1; sm >= 0; --sm){ + component cSm = S.transform(cgrid, -sm); + ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); + ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); + ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); + LOOP_OVER_DIRECTIONS(gvu.dim, d){ + if (iecoSm.in_direction(d) == iscoS.in_direction(d)) { + iscoS.set_direction(d, iecoSm.in_direction(d)+2); + //iecoS.set_direction(d, iecoS.in_direction(d)+2); + } + //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); + } + } + } + */ + + //if (!use_symmetry) printf("no symmetry, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); + //if (use_symmetry) printf("before: symmetry sn %i, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", sn, iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); + + + /*std::set overlap_d; for (int j = 0; j < num_chunks; ++j) { if (!chunks[j]->is_mine()) continue; // Chunk looping boundaries for owned points, shifted to centered grid and transformed: @@ -429,24 +480,104 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con ivec _iscoSm(S.transform(gvuj.little_owned_corner(cSm), sm)); ivec _iecoSm(S.transform(gvuj.big_owned_corner(cSm), sm)); ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); + //printf("symmetry sm %i, iscoSm %i, iecoSm %i, component %s\n", sm, iscoSm.z(), iecoSm.z(), component_name(cSm)); LOOP_OVER_DIRECTIONS(gvuj.dim, d){ if (iecoSm.in_direction(d) == iscoSj.in_direction(d)) { overlap_d.insert(d); + //iscoS.set_direction(d, iecoSm.in_direction(d)+2); + //iecoS.set_direction(d, iecoS.in_direction(d)+2); } - } + //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); + } + + } } } + for (std::set::iterator set_i=overlap_d.begin();set_i!=overlap_d.end();++set_i){ iscoS.set_direction(*set_i, iscoS.in_direction(*set_i)+2); iecoS.set_direction(*set_i, iecoS.in_direction(*set_i)+2); } overlap_d.clear(); - + + if (sn>0){ + for (int sm = sn-1; sm >= 0; --sm){//for previous transformations + component cSm = S.transform(cgrid, -sm); + + ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); + ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); + ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); + //printf("symmetry sm %i, iscoSm %i, iecoSm %i, component %s\n", sm, iscoSm.z(), iecoSm.z(), component_name(cSm)); + + LOOP_OVER_DIRECTIONS(gvu.dim, d){ + if (iscoSm.in_direction(d) <= iecoS.in_direction(d)) { + iecoS.set_direction(d, iscoSm.in_direction(d)-2); + } + //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); + } + } + } + */ + + //if (use_symmetry) printf(" after: symmetry sn %i, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", sn, iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); + + ivec isym(gvu.dim, INT_MAX); + //printf("infty %i", -meep::infinity); + //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); + bool gvu_is_halved[3] = {false, false, false}; + + bool break_this[3]; + for (int dd = 0; dd < 3; dd++) { + const direction d = (direction)dd; + break_this[dd] = false; + for (int n = 0; n < S.multiplicity(); n++) + if (has_direction(gv.dim, d) && + (S.transform(d, n).d != d || S.transform(d, n).flipped)) { + if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) + master_printf("Padding %s to even number of grid points.\n", direction_name(d)); + break_this[dd] = true; + } + } + int break_mult = 1; + for (int d = 0; d < 3; d++) { + if (break_mult == S.multiplicity()) break_this[d] = false; + if (break_this[d]) { + break_mult *= 2; + if (verbosity > 0) + master_printf("Halving computational cell along direction %s\n", + direction_name(direction(d))); + gvu_is_halved[d] = true; + } + } + if (sn!=0){ + LOOP_OVER_DIRECTIONS(gvu.dim, d){ + + int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? gv.iyee_shift(cgrid).in_direction(d)+2 : 2); + //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); + if (gvu_is_halved[d]) isym.set_direction(d, S.i_symmetry_point.in_direction(d) - off_sym_shift); + //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); + } + } + //ivec isym= (iyee!=0?yee:2) along half directions,-infty else // intersect the chunk points with is and ie volume (shifted): + //printf(" iscoS (%i, %i, %i), iecoS (%i, %i, %i)\n", iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z()); + //printf(" isym (%i, %i, %i)\n", isym.x(), isym.y(),isym.z()); + //printf("component %s, iyee (%i, %i, %i) \n",component_name(cgrid), gv.iyee_shift(cgrid).x(), gv.iyee_shift(cgrid).y(),gv.iyee_shift(cgrid).z()); + //ivec iscS(max(isym,max(is - shifti, iscoS))); ivec iscS(max(is - shifti, iscoS)); - ivec iecS(min(ie - shifti, iecoS)); + ivec iecS(min(isym, min(ie - shifti, iecoS))); + //printf(" use-isym: iscS (%i, %i, %i), iecS (%i, %i, %i)\n",iscS.x(), iscS.y(),iscS.z(), iecS.x(), iecS.y(),iecS.z()); + + + //max(iscS, sym) + //printf("symmetry? %i\n", (S.multiplicity()>1)); + //printf(" no-isym: iscS (%i, %i, %i), iecS (%i, %i, %i)\n",iscS0.x(), iscS0.y(),iscS0.z(), iecS.x(), iecS.y(),iecS.z()); + + //ivec iscS(max(is - shifti, iscoS)); + //ivec iecS(min(ie - shifti, iecoS)); + if (iscS <= iecS) { // non-empty intersection // Determine weights at chunk looping boundaries: ivec isc(S.transform(iscS, -sn)), iec(S.transform(iecS, -sn)); diff --git a/src/vec.cpp b/src/vec.cpp index 479a50422..24434932d 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -1072,9 +1072,16 @@ grid_volume grid_volume::split_at_fraction(bool side_high, int split_pt, int spl // Halve the grid_volume for symmetry exploitation...must contain icenter! grid_volume grid_volume::halve(direction d) const { - grid_volume retval(*this); // note that icenter-io is always even by construction of grid_volume::icenter - retval.set_num_direction(d, (icenter().in_direction(d) - io.in_direction(d)) / 2); + /*retval.set_num_direction(d, (icenter().in_direction(d) - io.in_direction(d)) / 2); + retval.is_halved[d]=true; + return retval; + */ + //printf("d %i, icenter %i, io %i \n", d, icenter().in_direction(d), io.in_direction(d)); + retval.set_num_direction(d, 1+(icenter().in_direction(d) - io.in_direction(d)) / 2); + //printf("d %i, icenter %i, io %i \n", d, icenter().in_direction(d), retval.io.in_direction(d)); + retval.set_origin(d, (icenter().in_direction(d) - io.in_direction(d))-2); + //printf("d %i, icenter %i, io %i \n", d, retval.icenter().in_direction(d), retval.io.in_direction(d)); return retval; } From 9a3e10ed651cab008c4d03bf195fe3efb6a0318c Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:20:57 -0500 Subject: [PATCH 012/155] minor --- src/meep/vec.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/meep/vec.hpp b/src/meep/vec.hpp index 4bf31bafa..0e76f50bb 100644 --- a/src/meep/vec.hpp +++ b/src/meep/vec.hpp @@ -1216,12 +1216,13 @@ class symmetry { void operator=(const symmetry &); bool operator==(const symmetry &) const; bool operator!=(const symmetry &S) const { return !(*this == S); }; + ivec i_symmetry_point; private: signed_direction S[5]; std::complex ph; vec symmetry_point; - ivec i_symmetry_point; + //ivec i_symmetry_point; int g; // g is the multiplicity of the symmetry. symmetry *next; friend symmetry r_to_minus_r_symmetry(double m); From a27303d7f0164fd5d43a6b986ae2d811210c8d98 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:30:24 -0500 Subject: [PATCH 013/155] include climits --- src/vec.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vec.cpp b/src/vec.cpp index 24434932d..ac60bba39 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "meep_internals.hpp" #include "meepgeom.hpp" From 75155a1f2f95993e81e3ed64fdff325c76d6dc31 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:38:16 -0500 Subject: [PATCH 014/155] include climits --- src/loop_in_chunks.cpp | 1 + src/vec.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 3d8801f4d..1ab2e2fb0 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "meep.hpp" #include "meep_internals.hpp" diff --git a/src/vec.cpp b/src/vec.cpp index ac60bba39..24434932d 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include "meep_internals.hpp" #include "meepgeom.hpp" From 620a52e9156b463561ab14a119b8f32e6be7681d Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:48:20 -0500 Subject: [PATCH 015/155] fix retval --- src/vec.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vec.cpp b/src/vec.cpp index 24434932d..c2d3bb3e1 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -1072,6 +1072,7 @@ grid_volume grid_volume::split_at_fraction(bool side_high, int split_pt, int spl // Halve the grid_volume for symmetry exploitation...must contain icenter! grid_volume grid_volume::halve(direction d) const { + grid_volume retval(*this); // note that icenter-io is always even by construction of grid_volume::icenter /*retval.set_num_direction(d, (icenter().in_direction(d) - io.in_direction(d)) / 2); retval.is_halved[d]=true; From 32d6035780ae88e6efbfca3e42adf4f6acc60861 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 14:56:48 -0500 Subject: [PATCH 016/155] clean up --- src/loop_in_chunks.cpp | 134 ++++------------------------------------- src/vec.cpp | 8 --- 2 files changed, 13 insertions(+), 129 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 1ab2e2fb0..273309b1d 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -408,15 +408,12 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con grid_volume gvu2(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); ivec _iscoS2(S.transform(gvu2.little_owned_corner(cSm), sm)); ivec _iecoS2(S.transform(gvu2.big_owned_corner(cSm), sm)); - //printf("_iscoS2 (%i, %i, %i), _iecoS2 (%i, %i, %i), component %s\n", _iscoS2.x(), _iscoS2.y(),_iscoS2.z(),_iecoS2.x(), _iecoS2.y(),_iecoS2.z(), component_name(cSm)); lowerboundary = min(lowerboundary, min(_iscoS2, _iecoS2)); } } LOOP_OVER_DIRECTIONS(gv.dim, d){ int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? 0 : 2); - //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); if (ishift.in_direction(d) != 0) lowerboundary.set_direction(d, lowerboundary.in_direction(d) + off_sym_shift); - //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); } } @@ -431,8 +428,7 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con shifti.set_direction(d, iL.in_direction(d) * ishift.in_direction(d)); ph *= pow(eikna[d], ishift.in_direction(d)); } - //printf(" ishift (%i, %i, %i), shifti (%i, %i, %i) \n",ishift.x(),ishift.y(),ishift.z(), shifti.x(),shifti.y(),shifti.z()); - + for (int i = 0; i < num_chunks; ++i) { if (!chunks[i]->is_mine()) continue; // Chunk looping boundaries for owned points, shifted to centered grid and transformed: @@ -441,104 +437,18 @@ 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(lowerboundary, min(_iscoS, _iecoS))), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform - - - /* - if (false && sn>0){ - for (int sm = sn-1; sm >= 0; --sm){ - component cSm = S.transform(cgrid, -sm); - ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); - ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); - ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); - LOOP_OVER_DIRECTIONS(gvu.dim, d){ - if (iecoSm.in_direction(d) == iscoS.in_direction(d)) { - iscoS.set_direction(d, iecoSm.in_direction(d)+2); - //iecoS.set_direction(d, iecoS.in_direction(d)+2); - } - //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); - } - } - } - */ - - //if (!use_symmetry) printf("no symmetry, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); - //if (use_symmetry) printf("before: symmetry sn %i, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", sn, iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); - - - /*std::set overlap_d; - for (int j = 0; j < num_chunks; ++j) { - if (!chunks[j]->is_mine()) continue; - // Chunk looping boundaries for owned points, shifted to centered grid and transformed: - grid_volume gvuj(use_symmetry ? chunks[j]->gv.unpad(gv) : chunks[j]->gv); - ivec _iscoSj(S.transform(gvuj.little_owned_corner(cS), sn)); - ivec _iecoSj(S.transform(gvuj.big_owned_corner(cS), sn)); - ivec iscoSj(min(_iscoSj, _iecoSj)), iecoSj(max(_iscoSj, _iecoSj)); // fix ordering due to to transform - - if (sn>0){ - for (int sm = sn-1; sm >= 0; --sm){//for previous transformations - component cSm = S.transform(cgrid, -sm); - - ivec _iscoSm(S.transform(gvuj.little_owned_corner(cSm), sm)); - ivec _iecoSm(S.transform(gvuj.big_owned_corner(cSm), sm)); - ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); - //printf("symmetry sm %i, iscoSm %i, iecoSm %i, component %s\n", sm, iscoSm.z(), iecoSm.z(), component_name(cSm)); - - LOOP_OVER_DIRECTIONS(gvuj.dim, d){ - if (iecoSm.in_direction(d) == iscoSj.in_direction(d)) { - overlap_d.insert(d); - //iscoS.set_direction(d, iecoSm.in_direction(d)+2); - //iecoS.set_direction(d, iecoS.in_direction(d)+2); - } - //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); - } - - - } - } - } - - for (std::set::iterator set_i=overlap_d.begin();set_i!=overlap_d.end();++set_i){ - iscoS.set_direction(*set_i, iscoS.in_direction(*set_i)+2); - iecoS.set_direction(*set_i, iecoS.in_direction(*set_i)+2); - } - overlap_d.clear(); - - if (sn>0){ - for (int sm = sn-1; sm >= 0; --sm){//for previous transformations - component cSm = S.transform(cgrid, -sm); - - ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); - ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); - ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); - //printf("symmetry sm %i, iscoSm %i, iecoSm %i, component %s\n", sm, iscoSm.z(), iecoSm.z(), component_name(cSm)); - - LOOP_OVER_DIRECTIONS(gvu.dim, d){ - if (iscoSm.in_direction(d) <= iecoS.in_direction(d)) { - iecoS.set_direction(d, iscoSm.in_direction(d)-2); - } - //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); - } - } - } - */ - - //if (use_symmetry) printf(" after: symmetry sn %i, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", sn, iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); - ivec isym(gvu.dim, INT_MAX); - //printf("infty %i", -meep::infinity); - //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); bool gvu_is_halved[3] = {false, false, false}; - bool break_this[3]; - for (int dd = 0; dd < 3; dd++) { - const direction d = (direction)dd; - break_this[dd] = false; - for (int n = 0; n < S.multiplicity(); n++) - if (has_direction(gv.dim, d) && - (S.transform(d, n).d != d || S.transform(d, n).flipped)) { - if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) - master_printf("Padding %s to even number of grid points.\n", direction_name(d)); - break_this[dd] = true; + bool break_this[3]; + for (int dd = 0; dd < 3; dd++) { + const direction d = (direction)dd; + break_this[dd] = false; + for (int n = 0; n < S.multiplicity(); n++) + if (has_direction(gv.dim, d) && (S.transform(d, n).d != d || S.transform(d, n).flipped)) { + if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) + master_printf("Padding %s to even number of grid points.\n", direction_name(d)); + break_this[dd] = true; } } int break_mult = 1; @@ -547,37 +457,19 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (break_this[d]) { break_mult *= 2; if (verbosity > 0) - master_printf("Halving computational cell along direction %s\n", - direction_name(direction(d))); + master_printf("Halving computational cell along direction %s\n",direction_name(direction(d))); gvu_is_halved[d] = true; } } if (sn!=0){ - LOOP_OVER_DIRECTIONS(gvu.dim, d){ - + LOOP_OVER_DIRECTIONS(gvu.dim, d){ int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? gv.iyee_shift(cgrid).in_direction(d)+2 : 2); - //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); if (gvu_is_halved[d]) isym.set_direction(d, S.i_symmetry_point.in_direction(d) - off_sym_shift); - //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); } } - //ivec isym= (iyee!=0?yee:2) along half directions,-infty else - // intersect the chunk points with is and ie volume (shifted): - //printf(" iscoS (%i, %i, %i), iecoS (%i, %i, %i)\n", iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z()); - //printf(" isym (%i, %i, %i)\n", isym.x(), isym.y(),isym.z()); - //printf("component %s, iyee (%i, %i, %i) \n",component_name(cgrid), gv.iyee_shift(cgrid).x(), gv.iyee_shift(cgrid).y(),gv.iyee_shift(cgrid).z()); - //ivec iscS(max(isym,max(is - shifti, iscoS))); + ivec iscS(max(is - shifti, iscoS)); ivec iecS(min(isym, min(ie - shifti, iecoS))); - //printf(" use-isym: iscS (%i, %i, %i), iecS (%i, %i, %i)\n",iscS.x(), iscS.y(),iscS.z(), iecS.x(), iecS.y(),iecS.z()); - - - //max(iscS, sym) - //printf("symmetry? %i\n", (S.multiplicity()>1)); - //printf(" no-isym: iscS (%i, %i, %i), iecS (%i, %i, %i)\n",iscS0.x(), iscS0.y(),iscS0.z(), iecS.x(), iecS.y(),iecS.z()); - - //ivec iscS(max(is - shifti, iscoS)); - //ivec iecS(min(ie - shifti, iecoS)); if (iscS <= iecS) { // non-empty intersection // Determine weights at chunk looping boundaries: diff --git a/src/vec.cpp b/src/vec.cpp index c2d3bb3e1..602c3bbf2 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -1073,16 +1073,8 @@ grid_volume grid_volume::split_at_fraction(bool side_high, int split_pt, int spl // Halve the grid_volume for symmetry exploitation...must contain icenter! grid_volume grid_volume::halve(direction d) const { grid_volume retval(*this); - // note that icenter-io is always even by construction of grid_volume::icenter - /*retval.set_num_direction(d, (icenter().in_direction(d) - io.in_direction(d)) / 2); - retval.is_halved[d]=true; - return retval; - */ - //printf("d %i, icenter %i, io %i \n", d, icenter().in_direction(d), io.in_direction(d)); retval.set_num_direction(d, 1+(icenter().in_direction(d) - io.in_direction(d)) / 2); - //printf("d %i, icenter %i, io %i \n", d, icenter().in_direction(d), retval.io.in_direction(d)); retval.set_origin(d, (icenter().in_direction(d) - io.in_direction(d))-2); - //printf("d %i, icenter %i, io %i \n", d, retval.icenter().in_direction(d), retval.io.in_direction(d)); return retval; } From 485d8f675a894dbfb4455a3fdfd75814a73acbeb Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 10 Feb 2022 18:35:11 -0500 Subject: [PATCH 017/155] using flipped --- src/loop_in_chunks.cpp | 61 +++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 39 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 273309b1d..77304a325 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -398,27 +398,6 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con // loop over lattice shifts ivec ishift(min_ishift); - ivec lowerboundary(gv.dim, INT_MAX); - if (use_symmetry && S.multiplicity() > 1){ - for (int sm = 0; sm < (use_symmetry ? S.multiplicity() : 1); ++sm) { - component cSm = S.transform(cgrid, -sm); - for (int i = 0; i < num_chunks; ++i) { - if (!chunks[i]->is_mine()) continue; - // Chunk looping boundaries for owned points, shifted to centered grid and transformed: - grid_volume gvu2(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); - ivec _iscoS2(S.transform(gvu2.little_owned_corner(cSm), sm)); - ivec _iecoS2(S.transform(gvu2.big_owned_corner(cSm), sm)); - lowerboundary = min(lowerboundary, min(_iscoS2, _iecoS2)); - } - } - LOOP_OVER_DIRECTIONS(gv.dim, d){ - int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? 0 : 2); - if (ishift.in_direction(d) != 0) lowerboundary.set_direction(d, lowerboundary.in_direction(d) + off_sym_shift); - } - } - - - do { complex ph = 1.0; vec shift(gv.dim, 0.0); @@ -428,27 +407,26 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con shifti.set_direction(d, iL.in_direction(d) * ishift.in_direction(d)); ph *= pow(eikna[d], ishift.in_direction(d)); } - for (int i = 0; i < num_chunks; ++i) { if (!chunks[i]->is_mine()) continue; - // Chunk looping boundaries for owned points, shifted to centered grid and transformed: - grid_volume gvu(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); + grid_volume gvu(chunks[i]->gv); ivec _iscoS(S.transform(gvu.little_owned_corner(cS), sn)); ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); - ivec iscoS(max(lowerboundary, min(_iscoS, _iecoS))), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform - + ivec iscoS(max(user_volume.little_owned_corner(cgrid), min(_iscoS, _iecoS))), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform + ivec isym(gvu.dim, INT_MAX); bool gvu_is_halved[3] = {false, false, false}; - bool break_this[3]; - for (int dd = 0; dd < 3; dd++) { - const direction d = (direction)dd; - break_this[dd] = false; - for (int n = 0; n < S.multiplicity(); n++) - if (has_direction(gv.dim, d) && (S.transform(d, n).d != d || S.transform(d, n).flipped)) { - if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) - master_printf("Padding %s to even number of grid points.\n", direction_name(d)); - break_this[dd] = true; + bool break_this[3]; + for (int dd = 0; dd < 3; dd++) { + const direction d = (direction)dd; + break_this[dd] = false; + for (int n = 0; n < S.multiplicity(); n++) + if (has_direction(gv.dim, d) && + (S.transform(d, n).d != d || S.transform(d, n).flipped)) { + if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) + master_printf("Padding %s to even number of grid points.\n", direction_name(d)); + break_this[dd] = true; } } int break_mult = 1; @@ -457,19 +435,24 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (break_this[d]) { break_mult *= 2; if (verbosity > 0) - master_printf("Halving computational cell along direction %s\n",direction_name(direction(d))); + master_printf("Halving computational cell along direction %s\n", + direction_name(direction(d))); gvu_is_halved[d] = true; } } - if (sn!=0){ - LOOP_OVER_DIRECTIONS(gvu.dim, d){ + if (use_symmetry && sn!=0){ + LOOP_OVER_DIRECTIONS(gvu.dim, d){ int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? gv.iyee_shift(cgrid).in_direction(d)+2 : 2); if (gvu_is_halved[d]) isym.set_direction(d, S.i_symmetry_point.in_direction(d) - off_sym_shift); } } ivec iscS(max(is - shifti, iscoS)); - ivec iecS(min(isym, min(ie - shifti, iecoS))); + ivec chunk_corner(gvu.little_owned_corner(cgrid)); + LOOP_OVER_DIRECTIONS(gv.dim, d) { + if ((S.transform(d, sn).d != d) != (S.transform(d, sn).flipped)) iecoS.set_direction(d, min(isym, iecoS).in_direction(d)); + } + ivec iecS( min(ie - shifti, iecoS)); if (iscS <= iecS) { // non-empty intersection // Determine weights at chunk looping boundaries: From 9a78ab780f57b8625cfa58bd2cfdc8e491420ee3 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 10 Feb 2022 19:36:45 -0500 Subject: [PATCH 018/155] add missing changes --- src/structure.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/structure.cpp b/src/structure.cpp index d0776b5a0..a8b18e9a6 100644 --- a/src/structure.cpp +++ b/src/structure.cpp @@ -186,8 +186,6 @@ std::unique_ptr choose_chunkdivision(grid_volume &gv, volume & // Before padding, find the corresponding geometric grid_volume. v = gv.surroundings(); // Pad the little cell in any direction that we've shrunk: - for (int d = 0; d < 3; d++) - if (break_this[d]) gv.pad_self((direction)d); } int proc_id = 0; @@ -517,13 +515,14 @@ void structure::use_pml(direction d, boundary_side b, double dx) { if (dx <= 0.0) return; grid_volume pml_volume = gv; pml_volume.set_num_direction(d, int(dx * user_volume.a + 1 + 0.5)); // FIXME: exact value? - if (b == High) + const int v_to_user_shift = + (user_volume.big_corner().in_direction(d) - gv.big_corner().in_direction(d)) / 2; + + if (b == High){ pml_volume.set_origin(d, user_volume.big_corner().in_direction(d) - pml_volume.num_direction(d) * 2); - const int v_to_user_shift = - (user_volume.little_corner().in_direction(d) - gv.little_corner().in_direction(d)) / 2; - if (b == Low && v_to_user_shift != 0) pml_volume.set_num_direction(d, pml_volume.num_direction(d) + v_to_user_shift); + } add_to_effort_volumes(pml_volume, 0.60); // FIXME: manual value for pml effort } From d975140f853a90ec9668b9ab06eab7a2a0659dab Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Sat, 12 Feb 2022 16:56:50 -0500 Subject: [PATCH 019/155] fix bug --- src/vec.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vec.cpp b/src/vec.cpp index 602c3bbf2..377a46956 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -1074,7 +1074,7 @@ grid_volume grid_volume::split_at_fraction(bool side_high, int split_pt, int spl grid_volume grid_volume::halve(direction d) const { grid_volume retval(*this); retval.set_num_direction(d, 1+(icenter().in_direction(d) - io.in_direction(d)) / 2); - retval.set_origin(d, (icenter().in_direction(d) - io.in_direction(d))-2); + retval.set_origin(d, icenter().in_direction(d)-2); return retval; } From 9ed741edf61e071424819c1a77ddc77f95c3bc6b Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Sat, 12 Feb 2022 18:24:57 -0500 Subject: [PATCH 020/155] fix pml --- src/structure.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structure.cpp b/src/structure.cpp index a8b18e9a6..96728bd93 100644 --- a/src/structure.cpp +++ b/src/structure.cpp @@ -516,7 +516,7 @@ void structure::use_pml(direction d, boundary_side b, double dx) { grid_volume pml_volume = gv; pml_volume.set_num_direction(d, int(dx * user_volume.a + 1 + 0.5)); // FIXME: exact value? const int v_to_user_shift = - (user_volume.big_corner().in_direction(d) - gv.big_corner().in_direction(d)) / 2; + (gv.big_corner().in_direction(d) - user_volume.big_corner().in_direction(d)) / 2; if (b == High){ pml_volume.set_origin(d, user_volume.big_corner().in_direction(d) - From 01b3c456af6f3685c85684057b88fa4dbc04a009 Mon Sep 17 00:00:00 2001 From: Krishna Gadepalli <34969407+kkg4theweb@users.noreply.github.com> Date: Thu, 18 Nov 2021 22:04:03 -0800 Subject: [PATCH 021/155] Fix memory leaks in SWIG wrappers. (#1826) * Fix memory leaks in SWIG wrappers. * move delete into material_free Co-authored-by: Steven G. Johnson --- libpympb/pympb.cpp | 1 - python/meep.i | 5 ++--- python/typemap_utils.cpp | 1 - src/meepgeom.cpp | 2 +- tests/array-slice-ll.cpp | 1 - 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/libpympb/pympb.cpp b/libpympb/pympb.cpp index cd2ff355b..6e4a00240 100644 --- a/libpympb/pympb.cpp +++ b/libpympb/pympb.cpp @@ -1631,7 +1631,6 @@ void mode_solver::clear_geometry_list() { if (geometry_list.num_items && geometry_list.items) { for(int i = 0; i < geometry_list.num_items; ++i) { material_free((meep_geom::material_data *)geometry_list.items[i].material); - delete (meep_geom::material_data *)geometry_list.items[i].material; geometric_object_destroy(geometry_list.items[i]); } delete[] geometry_list.items; diff --git a/python/meep.i b/python/meep.i index fe301cf58..948d6cc78 100644 --- a/python/meep.i +++ b/python/meep.i @@ -767,7 +767,6 @@ meep::volume_list *make_volume_list(const meep::volume &v, int c, %typemap(freearg) GEOMETRIC_OBJECT { if ($1.material) { material_free((material_data *)$1.material); - delete (material_data *)$1.material; geometric_object_destroy($1); } } @@ -983,7 +982,6 @@ void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObjec %typemap(freearg) material_type { if ($1) { material_free($1); - delete $1; } } @@ -1488,6 +1486,7 @@ void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObjec // it gets garbage collected and the file gets closed. %newobject meep::fields::open_h5file; +%newobject meep::make_output_directory; %newobject _get_eigenmode; %rename(_vec) meep::vec::vec; @@ -1989,7 +1988,7 @@ meep_geom::geom_epsilon* _set_materials(meep::structure * s, meep_geom::geom_epsilon *existing_geps, bool output_chunk_costs, const meep::binary_partition *my_bp) { - + meep_geom::geom_epsilon *geps; if (existing_geps) { geps = existing_geps; diff --git a/python/typemap_utils.cpp b/python/typemap_utils.cpp index e2182b3f8..4470cbc89 100644 --- a/python/typemap_utils.cpp +++ b/python/typemap_utils.cpp @@ -1127,7 +1127,6 @@ void gobj_list_freearg(geometric_object_list* objs) { SWIG_PYTHON_THREAD_SCOPED_BLOCK; for(int i = 0; i < objs->num_items; ++i) { material_free((material_data *)objs->items[i].material); - delete (material_data *)objs->items[i].material; geometric_object_destroy(objs->items[i]); } delete[] objs->items; diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index 077f2f52b..6ef7ff5fe 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -33,7 +33,6 @@ static void set_default_material(material_type _default_material) { if (default_material != NULL) { if (default_material == _default_material) return; material_free((material_type)default_material); - delete (material_type)default_material; default_material = NULL; } @@ -118,6 +117,7 @@ void material_free(material_type m) { delete[] m->weights; m->weights = NULL; + delete m; } bool material_type_equal(const material_type m1, const material_type m2) { diff --git a/tests/array-slice-ll.cpp b/tests/array-slice-ll.cpp index df7c55855..af754384b 100644 --- a/tests/array-slice-ll.cpp +++ b/tests/array-slice-ll.cpp @@ -139,7 +139,6 @@ int main(int argc, char *argv[]) { meep_geom::material_type vacuum = meep_geom::vacuum; auto material_deleter = [](meep_geom::material_data *m) { meep_geom::material_free(m); - delete m; }; std::unique_ptr dielectric( meep_geom::make_dielectric(eps), material_deleter); From 9e3c8d5017c5eb0925ef38e2cb7d867b0f5948ea Mon Sep 17 00:00:00 2001 From: mochen4 Date: Mon, 22 Nov 2021 11:24:46 -0500 Subject: [PATCH 022/155] Fix adjoint gradient with conductivities (#1830) * damp_fix * increase run time Co-authored-by: Mo Chen --- python/tests/test_adjoint_solver.py | 105 ++++++++++++++++++++++++++++ src/meepgeom.cpp | 26 ++++++- 2 files changed, 129 insertions(+), 2 deletions(-) diff --git a/python/tests/test_adjoint_solver.py b/python/tests/test_adjoint_solver.py index b9c3150a9..dae6e80f9 100644 --- a/python/tests/test_adjoint_solver.py +++ b/python/tests/test_adjoint_solver.py @@ -261,7 +261,86 @@ def J(dft_mon): sim.reset_meep() return f, dJ_du + +def forward_simulation_damping(design_params, frequencies=None, mat2=silicon): + matgrid = mp.MaterialGrid(mp.Vector3(Nx,Ny), + mp.air, + mat2, + weights=design_params.reshape(Nx,Ny), + damping = 3.14*fcen) + + matgrid_geometry = [mp.Block(center=mp.Vector3(), + size=mp.Vector3(design_region_size.x,design_region_size.y,0), + material=matgrid)] + + geometry = waveguide_geometry + matgrid_geometry + + sim = mp.Simulation(resolution=resolution, + cell_size=cell_size, + boundary_layers=pml_xy, + sources=wvg_source, + geometry=geometry) + + if not frequencies: + frequencies = [fcen] + + mode = sim.add_mode_monitor(frequencies, + mp.ModeRegion(center=mp.Vector3(0.5*sxy-dpml-0.1), + size=mp.Vector3(0,sxy-2*dpml,0)), + yee_grid=True, + eig_parity=eig_parity) + + sim.run(until_after_sources=mp.stop_when_dft_decayed()) + + + coeff = sim.get_eigenmode_coefficients(mode,[1],eig_parity).alpha[0,:,0] + S12 = np.power(np.abs(coeff),2) + sim.reset_meep() + return S12 + +def adjoint_solver_damping(design_params, frequencies=None, mat2=silicon): + matgrid = mp.MaterialGrid(mp.Vector3(Nx,Ny), + mp.air, + mat2, + weights=np.ones((Nx,Ny)), + damping = 3.14*fcen) + matgrid_region = mpa.DesignRegion(matgrid, + volume=mp.Volume(center=mp.Vector3(), size=mp.Vector3(design_region_size.x, design_region_size.y, 0))) + + matgrid_geometry = [mp.Block(center=matgrid_region.center, + size=matgrid_region.size, + material=matgrid)] + geometry = waveguide_geometry + matgrid_geometry + + sim = mp.Simulation(resolution=resolution, + cell_size=cell_size, + boundary_layers=pml_xy, + sources=wvg_source, + geometry=geometry) + + if not frequencies: + frequencies = [fcen] + + obj_list = [mpa.EigenmodeCoefficient(sim, mp.Volume(center=mp.Vector3(0.5*sxy-dpml-0.1), + size=mp.Vector3(0,sxy-2*dpml,0)), 1, eig_parity=eig_parity)] + + def J(mode_mon): + return npa.power(npa.abs(mode_mon),2) + + + opt = mpa.OptimizationProblem( + simulation=sim, + objective_functions=J, + objective_arguments=obj_list, + design_regions=[matgrid_region], + frequencies=frequencies, + minimum_run_time=150) + + f, dJ_du = opt([design_params]) + + sim.reset_meep() + return f, dJ_du def mapping(x,filter_radius,eta,beta): filtered_field = mpa.conic_filter(x, @@ -403,6 +482,32 @@ def test_complex_fields(self): print("Directional derivative -- adjoint solver: {}, FD: {}".format(adj_scale,fd_grad)) tol = 0.005 if mp.is_single_precision() else 0.0008 self.assertClose(adj_scale,fd_grad,epsilon=tol) + + def test_damping(self): + print("*** TESTING CONDUCTIVITIES ***") + + for frequencies in [[1/1.58, fcen, 1/1.53]]: + ## compute gradient using adjoint solver + adjsol_obj, adjsol_grad = adjoint_solver_damping(p, frequencies) + + ## compute unperturbed S12 + S12_unperturbed = forward_simulation_damping(p, frequencies) + + ## compare objective results + print("S12 -- adjoint solver: {}, traditional simulation: {}".format(adjsol_obj,S12_unperturbed)) + self.assertClose(adjsol_obj,S12_unperturbed,epsilon=1e-6) + + ## compute perturbed S12 + S12_perturbed = forward_simulation_damping(p+dp, frequencies) + + ## compare gradients + if adjsol_grad.ndim < 2: + adjsol_grad = np.expand_dims(adjsol_grad,axis=1) + adj_scale = (dp[None,:]@adjsol_grad).flatten() + fd_grad = S12_perturbed-S12_unperturbed + print("Directional derivative -- adjoint solver: {}, FD: {}".format(adj_scale,fd_grad)) + tol = 0.06 if mp.is_single_precision() else 0.03 + self.assertClose(adj_scale,fd_grad,epsilon=tol) def test_offdiagonal(self): print("*** TESTING OFFDIAGONAL COMPONENTS ***") diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index 6ef7ff5fe..dda2b5782 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -2572,7 +2572,7 @@ void eff_chi1inv_row_disp(meep::component c, std::complex chi1inv_row[3] vector3 dummy; dummy.x = dummy.y = dummy.z = 0.0; double conductivityCur = vec_to_value(mm->D_conductivity_diag, dummy, i); - a = std::complex(1.0, conductivityCur / (freq)); + a = std::complex(1.0, conductivityCur / (2*meep::pi*freq)); // compute lorentzian component b = cvec_to_value(mm->epsilon_diag, mm->epsilon_offdiag, i); @@ -2613,6 +2613,23 @@ void eff_chi1inv_row_disp(meep::component c, std::complex chi1inv_row[3] } } +std::complex cond_cmp(meep::component c, const meep::vec &r, double freq, geom_epsilon *geps) { + // locate the proper material + material_type md; + geps->get_material_pt(md, r); + const medium_struct *mm = &(md->medium); + + // get the row we care about + switch (component_direction(c)) { + case meep::X: + case meep::R: return std::complex(1.0, mm->D_conductivity_diag.x / (2*meep::pi*freq)); + case meep::Y: + case meep::P: return std::complex(1.0, mm->D_conductivity_diag.y / (2*meep::pi*freq)); + case meep::Z: return std::complex(1.0, mm->D_conductivity_diag.z / (2*meep::pi*freq)); + case meep::NO_DIRECTION: meep::abort("Invalid adjoint field component"); + } +} + std::complex get_material_gradient( const meep::vec &r, // current point const meep::component adjoint_c, // adjoint field component @@ -2686,7 +2703,7 @@ std::complex get_material_gradient( u[idx] = orig; for (int i=0;i<3;i++) dA_du[i] = (row_1[i] - row_2[i])/(2*du); - return dA_du[dir_idx] * fields_f; + return dA_du[dir_idx] * fields_f * cond_cmp(forward_c,r,freq,geps); } } @@ -2912,6 +2929,11 @@ void material_grids_addgradient(double *v, size_t ng, std::complex *fiel meep::ivec ip = gv.iloc(adjoint_c,idx); meep::vec p = gv.loc(adjoint_c,idx); std::complex adj = GET_FIELDS(fields_a,ci_adjoint,f_i,idx_fields); + + material_type md; + geps->get_material_pt(md, p); + if (!md->trivial) adj *= cond_cmp(adjoint_c,p,frequencies[f_i], geps); + double cyl_scale; int ci_forward = 0; FOR_MY_COMPONENTS(forward_c) { From d951cfdf0bb4835b19f73a87496c6781cfb14b40 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Tue, 23 Nov 2021 18:55:07 -0800 Subject: [PATCH 023/155] plot geometry for dispersive materials without initializing structure object (#1827) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * plot geometry without initializing structure class * update docstrings * rotate epsilon grid by 90 degrees to properly orient axes * add support for dispersive ε * update markdown pages from docstrings * make frequency and resolution parameters of plot2D dictionary keys of eps_parameters * reinstate frequency parameter of plot2D and add warning that it is deprecated * fix order of frequency check --- doc/docs/Python_User_Interface.md | 45 +++-- doc/docs/Python_User_Interface.md.in | 2 +- python/meep.i | 12 +- python/simulation.py | 73 ++++--- python/tests/test_visualization.py | 2 +- python/visualization.py | 291 ++++++++++++++++----------- src/meepgeom.cpp | 27 ++- src/meepgeom.hpp | 3 +- 8 files changed, 270 insertions(+), 185 deletions(-) diff --git a/doc/docs/Python_User_Interface.md b/doc/docs/Python_User_Interface.md index 1904b9fc7..581dd3382 100644 --- a/doc/docs/Python_User_Interface.md +++ b/doc/docs/Python_User_Interface.md @@ -676,16 +676,21 @@ frequency-independent part of $\mu$ (the $\omega\to\infty$ limit).
```python -def get_epsilon_grid(self, xtics=None, ytics=None, ztics=None): +def get_epsilon_grid(self, + xtics=None, + ytics=None, + ztics=None, + frequency=0): ```
Given three 1d NumPy arrays (`xtics`,`ytics`,`ztics`) which define the coordinates of a Cartesian -grid anywhere within the cell volume, compute the trace of the $\varepsilon$ tensor from the `geometry` -exactly at each grid point. (For [`MaterialGrid`](#materialgrid)s, the $\varepsilon$ at each grid -point is computed using bilinear interpolation from the nearest `MaterialGrid` points and possibly also -projected to form a level set.) Note that this is different from `get_epsilon_point` which computes +grid anywhere within the cell volume, compute the trace of the $\varepsilon(f)$ tensor at frequency +$f$ (in Meep units) from the `geometry` exactly at each grid point. `frequency` defaults to 0 which is +the instantaneous $\varepsilon$. (For [`MaterialGrid`](#materialgrid)s, the $\varepsilon$ at each +grid point is computed using bilinear interpolation from the nearest `MaterialGrid` points and possibly +also projected to form a level set.) Note that this is different from `get_epsilon_point` which computes $\varepsilon$ by bilinearly interpolating from the nearest Yee grid points. This function is useful for sampling the material geometry to any arbitrary resolution. The return value is a NumPy array with shape equivalent to `numpy.meshgrid(xtics,ytics,ztics)`. Empty dimensions are collapsed. @@ -2067,7 +2072,7 @@ including outside the cell and a `near2far` object, returns the computed of fields $(E_x^1,E_y^1,E_z^1,H_x^1,H_y^1,H_z^1,E_x^2,E_y^2,E_z^2,H_x^2,H_y^2,H_z^2,...)$ in Cartesian coordinates and $(E_r^1,E_\phi^1,E_z^1,H_r^1,H_\phi^1,H_z^1,E_r^2,E_\phi^2,E_z^2,H_r^2,H_\phi^2,H_z^2,...)$ -in cylindrical coordinates for the frequencies 1,2,…,`nfreq`. +in cylindrical coordinates for the frequencies 1,2,...,`nfreq`.
@@ -2609,7 +2614,7 @@ fr = mp.FluxRegion(volume=mp.GDSII_vol(fname, layer, zmin, zmax)) ### Data Visualization -This module provides basic visualization functionality for the simulation domain. The spirit of the module is to provide functions that can be called with *no customization options whatsoever* and will do useful relevant things by default, but which can also be customized in cases where you *do* want to take the time to spruce up the output. The `Simulation` class provides the following methods: +This module provides basic visualization functionality for the simulation domain. The intent of the module is to provide functions that can be called with *no customization options whatsoever* and will do useful relevant things by default, but which can also be customized in cases where you *do* want to take the time to spruce up the output. The `Simulation` class provides the following methods: @@ -2648,14 +2653,18 @@ sim.run(...) field_func = lambda x: 20*np.log10(np.abs(x)) import matplotlib.pyplot as plt sim.plot2D(fields=mp.Ez, - field_parameters={'alpha':0.8, 'cmap':'RdBu', 'interpolation':'none', 'post_process':field_func}, - boundary_parameters={'hatch':'o', 'linewidth':1.5, 'facecolor':'y', 'edgecolor':'b', 'alpha':0.3}) + field_parameters={'alpha':0.8, 'cmap':'RdBu', 'interpolation':'none', 'post_process':field_func}, + boundary_parameters={'hatch':'o', 'linewidth':1.5, 'facecolor':'y', 'edgecolor':'b', 'alpha':0.3}) plt.show() plt.savefig('sim_domain.png') ``` +If you just want to quickly visualize the simulation domain without the fields (i.e., when +setting up your simulation), there is no need to invoke the `run` function prior to calling +`plot2D`. Just define the `Simulation` object followed by any DFT monitors and then +invoke `plot2D`. -Note: When running a [parallel simulation](Parallel_Meep.md), the `plot2D` function expects to be called -on all processes, but only generates a plot on the master process. +Note: When running a [parallel simulation](Parallel_Meep.md), the `plot2D` function expects +to be called on all processes, but only generates a plot on the master process. **Parameters:** @@ -2677,6 +2686,12 @@ on all processes, but only generates a plot on the master process. - `alpha=1.0`: transparency of geometry - `contour=False`: if `True`, plot a contour of the geometry rather than its image - `contour_linewidth=1`: line width of the contour lines if `contour=True` + - `frequency=None`: for materials with a [frequency-dependent + permittivity](Materials.md#material-dispersion) $\varepsilon(f)$, specifies the + frequency $f$ (in Meep units) of the real part of the permittivity to use in the + plot. Defaults to the `frequency` parameter of the [Source](#source) object. + - `resolution=None`: the resolution of the $\varepsilon$ grid. Defaults to the + `resolution` of the `Simulation` object. * `boundary_parameters`: a `dict` of optional plotting parameters that override the default parameters for the boundary layers. - `alpha=1.0`: transparency of boundary layers @@ -2713,10 +2728,6 @@ on all processes, but only generates a plot on the master process. - `alpha=0.6`: transparency of fields - `post_process=np.real`: post processing function to apply to fields (must be a function object) -* `frequency`: for materials with a [frequency-dependent - permittivity](Materials.md#material-dispersion) $\varepsilon(f)$, specifies the - frequency $f$ (in Meep units) of the real part of the permittivity to use in the - plot. Defaults to the `frequency` parameter of the [Source](#source) object.
@@ -4401,7 +4412,7 @@ def __init__(self, medium2, weights=None, grid_type='U_DEFAULT', - do_averaging=False, + do_averaging=True, beta=0, eta=0.5, damping=0): @@ -6893,7 +6904,7 @@ A class used to record the fields during timestepping (i.e., a [`run`](#run-func function). The object is initialized prior to timestepping by specifying the simulation object and the field component. The object can then be passed to any [step-function modifier](#step-function-modifiers). For example, one can record the -Ez fields at every one time unit using: +$E_z$ fields at every one time unit using: ```py animate = mp.Animate2D(sim, diff --git a/doc/docs/Python_User_Interface.md.in b/doc/docs/Python_User_Interface.md.in index 362f652ec..2ed09ca3d 100644 --- a/doc/docs/Python_User_Interface.md.in +++ b/doc/docs/Python_User_Interface.md.in @@ -480,7 +480,7 @@ This feature is only available if Meep is built with [libGDSII](Build_From_Sourc ### Data Visualization -This module provides basic visualization functionality for the simulation domain. The spirit of the module is to provide functions that can be called with *no customization options whatsoever* and will do useful relevant things by default, but which can also be customized in cases where you *do* want to take the time to spruce up the output. The `Simulation` class provides the following methods: +This module provides basic visualization functionality for the simulation domain. The intent of the module is to provide functions that can be called with *no customization options whatsoever* and will do useful relevant things by default, but which can also be customized in cases where you *do* want to take the time to spruce up the output. The `Simulation` class provides the following methods: @@ Simulation.plot2D @@ @@ Simulation.plot3D @@ diff --git a/python/meep.i b/python/meep.i index 948d6cc78..f1dc8012b 100644 --- a/python/meep.i +++ b/python/meep.i @@ -1095,12 +1095,12 @@ void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObjec $1 = (double *)array_data($input); } -%typecheck(SWIG_TYPECHECK_POINTER, fragment="NumPy_Fragments") double* grid_vals { +%typecheck(SWIG_TYPECHECK_POINTER, fragment="NumPy_Fragments") std::complex* grid_vals { $1 = is_array($input); } -%typemap(in, fragment="NumPy_Macros") double* grid_vals { - $1 = (double *)array_data($input); +%typemap(in, fragment="NumPy_Macros") std::complex* grid_vals { + $1 = (std::complex *)array_data($input); } // typemap for solve_cw: @@ -2040,7 +2040,8 @@ void _get_epsilon_grid(geometric_object_list gobj_list, int nx, double *xtics, int ny, double *ytics, int nz, double *ztics, - double *grid_vals) { + std::complex *grid_vals, + double frequency) { meep_geom::get_epsilon_grid(gobj_list, mlist, _default_material, @@ -2051,7 +2052,8 @@ void _get_epsilon_grid(geometric_object_list gobj_list, nx, xtics, ny, ytics, nz, ztics, - grid_vals); + grid_vals, + frequency); } %} diff --git a/python/simulation.py b/python/simulation.py index 83742fc71..5fbf66155 100644 --- a/python/simulation.py +++ b/python/simulation.py @@ -2225,18 +2225,19 @@ def get_mu_point(self, pt, frequency=0): v3 = py_v3_to_vec(self.dimensions, pt, self.is_cylindrical) return self.fields.get_mu(v3,frequency) - def get_epsilon_grid(self, xtics=None, ytics=None, ztics=None): + def get_epsilon_grid(self, xtics=None, ytics=None, ztics=None, frequency=0): """ Given three 1d NumPy arrays (`xtics`,`ytics`,`ztics`) which define the coordinates of a Cartesian - grid anywhere within the cell volume, compute the trace of the $\\varepsilon$ tensor from the `geometry` - exactly at each grid point. (For [`MaterialGrid`](#materialgrid)s, the $\\varepsilon$ at each grid - point is computed using bilinear interpolation from the nearest `MaterialGrid` points and possibly also - projected to form a level set.) Note that this is different from `get_epsilon_point` which computes + grid anywhere within the cell volume, compute the trace of the $\\varepsilon(f)$ tensor at frequency + $f$ (in Meep units) from the `geometry` exactly at each grid point. `frequency` defaults to 0 which is + the instantaneous $\\varepsilon$. (For [`MaterialGrid`](#materialgrid)s, the $\\varepsilon$ at each + grid point is computed using bilinear interpolation from the nearest `MaterialGrid` points and possibly + also projected to form a level set.) Note that this is different from `get_epsilon_point` which computes $\\varepsilon$ by bilinearly interpolating from the nearest Yee grid points. This function is useful for sampling the material geometry to any arbitrary resolution. The return value is a NumPy array with shape equivalent to `numpy.meshgrid(xtics,ytics,ztics)`. Empty dimensions are collapsed. """ - grid_vals = np.squeeze(np.empty((len(xtics), len(ytics), len(ztics)))) + grid_vals = np.squeeze(np.empty((len(xtics), len(ytics), len(ztics)), dtype=np.complex128)) gv = self._create_grid_volume(False) mp._get_epsilon_grid(self.geometry, self.extra_materials, @@ -2247,7 +2248,8 @@ def get_epsilon_grid(self, xtics=None, ytics=None, ztics=None): len(xtics), xtics, len(ytics), ytics, len(ztics), ztics, - grid_vals) + grid_vals, + frequency) return grid_vals def get_filename_prefix(self): @@ -2755,8 +2757,8 @@ def get_farfield(self, near2far, x): (Fourier-transformed) "far" fields at `x` as list of length 6`nfreq`, consisting of fields $(E_x^1,E_y^1,E_z^1,H_x^1,H_y^1,H_z^1,E_x^2,E_y^2,E_z^2,H_x^2,H_y^2,H_z^2,...)$ in Cartesian coordinates and - $(E_r^1,E_\phi^1,E_z^1,H_r^1,H_\phi^1,H_z^1,E_r^2,E_\phi^2,E_z^2,H_r^2,H_\phi^2,H_z^2,...)$ - in cylindrical coordinates for the frequencies 1,2,…,`nfreq`. + $(E_r^1,E_\\phi^1,E_z^1,H_r^1,H_\\phi^1,H_z^1,E_r^2,E_\\phi^2,E_z^2,H_r^2,H_\\phi^2,H_z^2,...)$ + in cylindrical coordinates for the frequencies 1,2,...,`nfreq`. """ return mp._get_farfield(near2far.swigobj, py_v3_to_vec(self.dimensions, x, is_cylindrical=self.is_cylindrical)) @@ -4084,14 +4086,18 @@ def plot2D(self, ax=None, output_plane=None, fields=None, labels=False, field_func = lambda x: 20*np.log10(np.abs(x)) import matplotlib.pyplot as plt sim.plot2D(fields=mp.Ez, - field_parameters={'alpha':0.8, 'cmap':'RdBu', 'interpolation':'none', 'post_process':field_func}, - boundary_parameters={'hatch':'o', 'linewidth':1.5, 'facecolor':'y', 'edgecolor':'b', 'alpha':0.3}) + field_parameters={'alpha':0.8, 'cmap':'RdBu', 'interpolation':'none', 'post_process':field_func}, + boundary_parameters={'hatch':'o', 'linewidth':1.5, 'facecolor':'y', 'edgecolor':'b', 'alpha':0.3}) plt.show() plt.savefig('sim_domain.png') ``` + If you just want to quickly visualize the simulation domain without the fields (i.e., when + setting up your simulation), there is no need to invoke the `run` function prior to calling + `plot2D`. Just define the `Simulation` object followed by any DFT monitors and then + invoke `plot2D`. - Note: When running a [parallel simulation](Parallel_Meep.md), the `plot2D` function expects to be called - on all processes, but only generates a plot on the master process. + Note: When running a [parallel simulation](Parallel_Meep.md), the `plot2D` function expects + to be called on all processes, but only generates a plot on the master process. **Parameters:** @@ -4113,6 +4119,12 @@ def plot2D(self, ax=None, output_plane=None, fields=None, labels=False, - `alpha=1.0`: transparency of geometry - `contour=False`: if `True`, plot a contour of the geometry rather than its image - `contour_linewidth=1`: line width of the contour lines if `contour=True` + - `frequency=None`: for materials with a [frequency-dependent + permittivity](Materials.md#material-dispersion) $\\varepsilon(f)$, specifies the + frequency $f$ (in Meep units) of the real part of the permittivity to use in the + plot. Defaults to the `frequency` parameter of the [Source](#source) object. + - `resolution=None`: the resolution of the $\\varepsilon$ grid. Defaults to the + `resolution` of the `Simulation` object. * `boundary_parameters`: a `dict` of optional plotting parameters that override the default parameters for the boundary layers. - `alpha=1.0`: transparency of boundary layers @@ -4149,23 +4161,26 @@ def plot2D(self, ax=None, output_plane=None, fields=None, labels=False, - `alpha=0.6`: transparency of fields - `post_process=np.real`: post processing function to apply to fields (must be a function object) - * `frequency`: for materials with a [frequency-dependent - permittivity](Materials.md#material-dispersion) $\\varepsilon(f)$, specifies the - frequency $f$ (in Meep units) of the real part of the permittivity to use in the - plot. Defaults to the `frequency` parameter of the [Source](#source) object. - """ - return vis.plot2D(self, ax=ax, output_plane=output_plane, fields=fields, labels=labels, - eps_parameters=eps_parameters, boundary_parameters=boundary_parameters, - source_parameters=source_parameters,monitor_parameters=monitor_parameters, - field_parameters=field_parameters, frequency=frequency, - plot_eps_flag=plot_eps_flag, plot_sources_flag=plot_sources_flag, - plot_monitors_flag=plot_monitors_flag, plot_boundaries_flag=plot_boundaries_flag, - **kwargs) - - - def plot_fields(self,**kwargs): - return vis.plot_fields(self,**kwargs) + return vis.plot2D(self, + ax=ax, + output_plane=output_plane, + fields=fields, + labels=labels, + eps_parameters=eps_parameters, + boundary_parameters=boundary_parameters, + source_parameters=source_parameters, + monitor_parameters=monitor_parameters, + field_parameters=field_parameters, + frequency=frequency, + plot_eps_flag=plot_eps_flag, + plot_sources_flag=plot_sources_flag, + plot_monitors_flag=plot_monitors_flag, + plot_boundaries_flag=plot_boundaries_flag, + **kwargs) + + def plot_fields(self, **kwargs): + return vis.plot_fields(self, **kwargs) def plot3D(self): """ diff --git a/python/tests/test_visualization.py b/python/tests/test_visualization.py index e2b9d2846..1b030716e 100644 --- a/python/tests/test_visualization.py +++ b/python/tests/test_visualization.py @@ -161,7 +161,7 @@ def test_animation_output(self): sim = setup_sim() # generate 2D simulation Animate = mp.Animate2D(sim,fields=mp.Ez, realtime=False, normalize=False) # Check without normalization - Animate_norm = mp.Animate2D(sim,mp.Ez,realtime=False,normalize=True) # Check with normalization + Animate_norm = mp.Animate2D(sim, mp.Ez, realtime=False, normalize=True) # Check with normalization # test both animation objects during same run sim.run( diff --git a/python/visualization.py b/python/visualization.py index b42a3dcf7..1e17f6a08 100644 --- a/python/visualization.py +++ b/python/visualization.py @@ -47,7 +47,9 @@ 'cmap':'binary', 'alpha':1.0, 'contour':False, - 'contour_linewidth':1 + 'contour_linewidth':1, + 'frequency':None, + 'resolution':None } default_boundary_parameters = { @@ -95,7 +97,7 @@ def filter_dict(dict_to_filter, func_with_kwargs): # ------------------------------------------------------- # # Routines to add legends to plot -def place_label(ax,label_text,x,y,centerx,centery,label_parameters=None): +def place_label(ax, label_text, x, y, centerx, centery, label_parameters=None): label_parameters = default_label_parameters if label_parameters is None else dict(default_label_parameters, **label_parameters) @@ -112,29 +114,37 @@ def place_label(ax,label_text,x,y,centerx,centery,label_parameters=None): else: ytext = offset - ax.annotate(label_text, xy=(x, y), xytext=(xtext,ytext), - textcoords='offset points', ha='center', va='bottom', - bbox=dict(boxstyle='round,pad=0.2', fc=color, alpha=alpha), - arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.5', - color=color)) + ax.annotate(label_text, xy=(x, y), xytext=(xtext, ytext), + textcoords='offset points', ha='center', va='bottom', + bbox=dict(boxstyle='round,pad=0.2', fc=color, alpha=alpha), + arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.5', + color=color)) return ax # ------------------------------------------------------- # # Helper functions used to plot volumes on a 2D plane -# Returns the intersection points of 2 Volumes. +# Returns the intersection points of two Volumes. # Volumes must be a line, plane, or rectangular prism # (since they are volume objects) -def intersect_volume_volume(volume1,volume2): +def intersect_volume_volume(volume1, volume2): # volume1 ............... [volume] # volume2 ............... [volume] # Represent the volumes by an "upper" and "lower" coordinate - U1 = [volume1.center.x+volume1.size.x/2,volume1.center.y+volume1.size.y/2,volume1.center.z+volume1.size.z/2] - L1 = [volume1.center.x-volume1.size.x/2,volume1.center.y-volume1.size.y/2,volume1.center.z-volume1.size.z/2] - - U2 = [volume2.center.x+volume2.size.x/2,volume2.center.y+volume2.size.y/2,volume2.center.z+volume2.size.z/2] - L2 = [volume2.center.x-volume2.size.x/2,volume2.center.y-volume2.size.y/2,volume2.center.z-volume2.size.z/2] + U1 = [volume1.center.x+volume1.size.x/2, + volume1.center.y+volume1.size.y/2, + volume1.center.z+volume1.size.z/2] + L1 = [volume1.center.x-volume1.size.x/2, + volume1.center.y-volume1.size.y/2, + volume1.center.z-volume1.size.z/2] + + U2 = [volume2.center.x+volume2.size.x/2, + volume2.center.y+volume2.size.y/2, + volume2.center.z+volume2.size.z/2] + L2 = [volume2.center.x-volume2.size.x/2, + volume2.center.y-volume2.size.y/2, + volume2.center.z-volume2.size.z/2] # Evaluate intersection U = np.min([U1,U2],axis=0) @@ -164,13 +174,13 @@ def intersect_volume_volume(volume1,volume2): # All of the 2D plotting routines need an output plane over which to plot. # The user has many options to specify this output plane. They can pass # the output_plane parameter, which is a 2D volume object. They can specify -# a volume using in_volume, which stores the volume as a C volume, not a python +# a volume using in_volume, which stores the volume as a C volume, not a Python # volume. They can also do nothing and plot the XY plane through Z=0. # # Not only do we need to check for all of these possibilities, but we also need # to check if the user accidentally specifies a plane that stretches beyond the # simulation domain. -def get_2D_dimensions(sim,output_plane): +def get_2D_dimensions(sim, output_plane): from meep.simulation import Volume # Pull correct plane from user @@ -182,12 +192,12 @@ def get_2D_dimensions(sim,output_plane): plane_center, plane_size = (sim.geometry_center, sim.cell_size) plane_volume = Volume(center=plane_center,size=plane_size) - if plane_size.x!=0 and plane_size.y!=0 and plane_size.z!=0: + if plane_size.x != 0 and plane_size.y != 0 and plane_size.z != 0: raise ValueError("Plane volume must be 2D (a plane).") - check_volume = Volume(center=sim.geometry_center,size=sim.cell_size) + check_volume = Volume(center=sim.geometry_center, size=sim.cell_size) - vertices = intersect_volume_volume(check_volume,plane_volume) + vertices = intersect_volume_volume(check_volume, plane_volume) if len(vertices) == 0: raise ValueError("The specified user volume is completely outside of the simulation domain.") @@ -203,10 +213,7 @@ def get_2D_dimensions(sim,output_plane): # ------------------------------------------------------- # # actual plotting routines -def plot_volume(sim,ax,volume,output_plane=None,plotting_parameters=None,label=None): - if not sim._is_initialized: - sim.init_sim() - +def plot_volume(sim, ax, volume, output_plane=None, plotting_parameters=None, label=None): import matplotlib.patches as patches from matplotlib import pyplot as plt from meep.simulation import Volume @@ -215,39 +222,57 @@ def plot_volume(sim,ax,volume,output_plane=None,plotting_parameters=None,label=N plotting_parameters = default_volume_parameters if plotting_parameters is None else dict(default_volume_parameters, **plotting_parameters) # Get domain measurements - sim_center, sim_size = get_2D_dimensions(sim,output_plane) + sim_center, sim_size = get_2D_dimensions(sim, output_plane) - plane = Volume(center=sim_center,size=sim_size) + plane = Volume(center=sim_center, size=sim_size) # Pull volume parameters size = volume.size center = volume.center - xmax = center.x+size.x/2 - xmin = center.x-size.x/2 - ymax = center.y+size.y/2 - ymin = center.y-size.y/2 - zmax = center.z+size.z/2 - zmin = center.z-size.z/2 + xmax = center.x + size.x/2 + xmin = center.x - size.x/2 + ymax = center.y + size.y/2 + ymin = center.y - size.y/2 + zmax = center.z + size.z/2 + zmin = center.z - size.z/2 # Add labels if requested if label is not None and mp.am_master(): if sim_size.x == 0: - ax = place_label(ax,label,center.y,center.z,sim_center.y,sim_center.z,label_parameters=plotting_parameters) + ax = place_label(ax, + label, + center.y, + center.z, + sim_center.y, + sim_center.z, + label_parameters=plotting_parameters) elif sim_size.y == 0: - ax = place_label(ax,label,center.x,center.z,sim_center.x,sim_center.z,label_parameters=plotting_parameters) + ax = place_label(ax, + label, + center.x, + center.z, + sim_center.x, + sim_center.z, + label_parameters=plotting_parameters) elif sim_size.z == 0: - ax = place_label(ax,label,center.x,center.y,sim_center.x,sim_center.y,label_parameters=plotting_parameters) + ax = place_label(ax, + label, + center.x, + center.y, + sim_center.x, + sim_center.y, + label_parameters=plotting_parameters) # Intersect plane with volume - intersection = intersect_volume_volume(volume,plane) + intersection = intersect_volume_volume(volume, plane) # Sort the points in a counter clockwise manner to ensure convex polygon is formed def sort_points(xy): xy = np.squeeze(xy) - xy_mean = np.mean(xy,axis=0) - theta = np.arctan2(xy[:,1]-xy_mean[1],xy[:,0]-xy_mean[0]) - return xy[np.argsort(theta,axis=0),:] + xy_mean = np.mean(xy, axis=0) + theta = np.arctan2(xy[:,1] - xy_mean[1], xy[:,0] - xy_mean[0]) + return xy[np.argsort(theta, axis=0), :] if mp.am_master(): # Point volume @@ -269,16 +294,16 @@ def sort_points(xy): elif len(intersection) == 2: line_args = {key:value for key, value in plotting_parameters.items() if key in ['color','linestyle','linewidth','alpha']} # Plot YZ - if sim_size.x==0: - ax.plot([a.y for a in intersection],[a.z for a in intersection], **line_args) + if sim_size.x == 0: + ax.plot([a.y for a in intersection], [a.z for a in intersection], **line_args) return ax - #Plot XZ - elif sim_size.y==0: - ax.plot([a.x for a in intersection],[a.z for a in intersection], **line_args) + # Plot XZ + elif sim_size.y == 0: + ax.plot([a.x for a in intersection], [a.z for a in intersection], **line_args) return ax # Plot XY - elif sim_size.z==0: - ax.plot([a.x for a in intersection],[a.y for a in intersection], **line_args) + elif sim_size.z == 0: + ax.plot([a.x for a in intersection], [a.y for a in intersection], **line_args) return ax else: return ax @@ -287,15 +312,15 @@ def sort_points(xy): elif len(intersection) > 2: planar_args = {key:value for key, value in plotting_parameters.items() if key in ['edgecolor','linewidth','facecolor','hatch','alpha']} # Plot YZ - if sim_size.x==0: + if sim_size.x == 0: ax.add_patch(patches.Polygon(sort_points([[a.y,a.z] for a in intersection]), **planar_args)) return ax - #Plot XZ + # Plot XZ elif sim_size.y==0: ax.add_patch(patches.Polygon(sort_points([[a.x,a.z] for a in intersection]), **planar_args)) return ax # Plot XY - elif sim_size.z==0: + elif sim_size.z == 0: ax.add_patch(patches.Polygon(sort_points([[a.x,a.y] for a in intersection]), **planar_args)) return ax else: @@ -304,16 +329,32 @@ def sort_points(xy): return ax return ax -def plot_eps(sim,ax,output_plane=None,eps_parameters=None,frequency=0): - if sim.structure is None: - sim.init_sim() - - +def plot_eps(sim, ax, output_plane=None, eps_parameters=None, frequency=None): # consolidate plotting parameters eps_parameters = default_eps_parameters if eps_parameters is None else dict(default_eps_parameters, **eps_parameters) + # Determine a frequency to plot all epsilon + if frequency is not None: + warnings.warn('The frequency parameter of plot2D has been deprecated. Use the frequency key of the eps_parameters dictionary instead.') + eps_parameters['frequency'] = frequency + if eps_parameters['frequency'] is None: + try: + # Continuous sources + eps_parameters['frequency'] = sim.sources[0].frequency + except: + try: + # Gaussian sources + eps_parameters['frequency'] = sim.sources[0].src.frequency + except: + try: + # Custom sources + eps_parameters['frequency'] = sim.sources[0].src.center_frequency + except: + # No sources + eps_parameters['frequency'] = 0 + # Get domain measurements - sim_center, sim_size = get_2D_dimensions(sim,output_plane) + sim_center, sim_size = get_2D_dimensions(sim, output_plane) xmin = sim_center.x - sim_size.x/2 xmax = sim_center.x + sim_size.x/2 @@ -322,28 +363,43 @@ def plot_eps(sim,ax,output_plane=None,eps_parameters=None,frequency=0): zmin = sim_center.z - sim_size.z/2 zmax = sim_center.z + sim_size.z/2 - center = Vector3(sim_center.x,sim_center.y,sim_center.z) - cell_size = Vector3(sim_size.x,sim_size.y,sim_size.z) + center = Vector3(sim_center.x, sim_center.y, sim_center.z) + cell_size = Vector3(sim_size.x, sim_size.y, sim_size.z) + + grid_resolution = eps_parameters['resolution'] if eps_parameters['resolution'] else sim.resolution + Nx = int((xmax - xmin) * grid_resolution + 1) + Ny = int((ymax - ymin) * grid_resolution + 1) + Nz = int((zmax - zmin) * grid_resolution + 1) if sim_size.x == 0: # Plot y on x axis, z on y axis (YZ plane) - extent = [ymin,ymax,zmin,zmax] + extent = [ymin, ymax, zmin, zmax] xlabel = 'Y' ylabel = 'Z' + xtics = np.array([sim_center.x]) + ytics = np.linspace(ymin, ymax, Ny) + ztics = np.linspace(zmin, zmax, Nz) elif sim_size.y == 0: # Plot x on x axis, z on y axis (XZ plane) - extent = [xmin,xmax,zmin,zmax] + extent = [xmin, xmax, zmin, zmax] xlabel = 'X' ylabel = 'Z' + xtics = np.linspace(xmin, xmax, Nx) + ytics = np.array([sim_center.y]) + ztics = np.linspace(zmin, zmax, Nz) elif sim_size.z == 0: # Plot x on x axis, y on y axis (XY plane) - extent = [xmin,xmax,ymin,ymax] + extent = [xmin, xmax, ymin, ymax] xlabel = 'X' ylabel = 'Y' + xtics = np.linspace(xmin, xmax, Nx) + ytics = np.linspace(ymin, ymax, Ny) + ztics = np.array([sim_center.z]) else: raise ValueError("A 2D plane has not been specified...") - eps_data = np.rot90(np.real(sim.get_array(center=center, size=cell_size, component=mp.Dielectric, frequency=frequency))) + eps_data = np.rot90(np.real(sim.get_epsilon_grid(xtics, ytics, ztics, eps_parameters['frequency']))) + if mp.am_master(): if eps_parameters['contour']: ax.contour(eps_data, 0, colors='black', origin='upper', extent=extent, linewidths=eps_parameters['contour_linewidth']) @@ -354,14 +410,11 @@ def plot_eps(sim,ax,output_plane=None,eps_parameters=None,frequency=0): return ax -def plot_boundaries(sim,ax,output_plane=None,boundary_parameters=None): - if not sim._is_initialized: - sim.init_sim() - +def plot_boundaries(sim, ax, output_plane=None, boundary_parameters=None): # consolidate plotting parameters boundary_parameters = default_boundary_parameters if boundary_parameters is None else dict(default_boundary_parameters, **boundary_parameters) - def get_boundary_volumes(thickness,direction,side): + def get_boundary_volumes(thickness, direction, side): from meep.simulation import Volume thickness = boundary.thickness @@ -403,20 +456,20 @@ def get_boundary_volumes(thickness,direction,side): import itertools for boundary in sim.boundary_layers: - # All 4 side are the same + # All four sides are the same if boundary.direction == mp.ALL and boundary.side == mp.ALL: if sim.dimensions == 1: dims = [mp.X] elif sim.dimensions == 2: - dims = [mp.X,mp.Y] + dims = [mp.X, mp.Y] elif sim.dimensions == 3: - dims = [mp.X,mp.Y,mp.Z] + dims = [mp.X, mp.Y, mp.Z] else: raise ValueError("Invalid simulation dimensions") for permutation in itertools.product(dims, [mp.Low, mp.High]): vol = get_boundary_volumes(boundary.thickness,*permutation) ax = plot_volume(sim,ax,vol,output_plane,plotting_parameters=boundary_parameters) - # 2 sides are the same + # two sides are the same elif boundary.side == mp.ALL: for side in [mp.Low, mp.High]: vol = get_boundary_volumes(boundary.thickness,boundary.direction,side) @@ -427,10 +480,7 @@ def get_boundary_volumes(thickness,direction,side): ax = plot_volume(sim,ax,vol,output_plane,plotting_parameters=boundary_parameters) return ax -def plot_sources(sim,ax,output_plane=None,labels=False,source_parameters=None): - if not sim._is_initialized: - sim.init_sim() - +def plot_sources(sim, ax, output_plane=None, labels=False, source_parameters=None): from meep.simulation import Volume # consolidate plotting parameters @@ -443,13 +493,10 @@ def plot_sources(sim,ax,output_plane=None,labels=False,source_parameters=None): ax = plot_volume(sim,ax,vol,output_plane,plotting_parameters=source_parameters,label=label) return ax -def plot_monitors(sim,ax,output_plane=None,labels=False,monitor_parameters=None): - if not sim._is_initialized: - sim.init_sim() - +def plot_monitors(sim, ax, output_plane=None, labels=False, monitor_parameters=None): from meep.simulation import Volume - # consolidate plotting parameters + # consolidate plotting parameters monitor_parameters = default_monitor_parameters if monitor_parameters is None else dict(default_monitor_parameters, **monitor_parameters) label = 'monitor' if labels else None @@ -460,7 +507,7 @@ def plot_monitors(sim,ax,output_plane=None,labels=False,monitor_parameters=None) ax = plot_volume(sim,ax,vol,output_plane,plotting_parameters=monitor_parameters,label=label) return ax -def plot_fields(sim,ax=None,fields=None,output_plane=None,field_parameters=None): +def plot_fields(sim, ax=None, fields=None, output_plane=None, field_parameters=None): if not sim._is_initialized: sim.init_sim() @@ -472,7 +519,7 @@ def plot_fields(sim,ax=None,fields=None,output_plane=None,field_parameters=None) # user specifies a field component if fields in [mp.Ex, mp.Ey, mp.Ez, mp.Hx, mp.Hy, mp.Hz]: # Get domain measurements - sim_center, sim_size = get_2D_dimensions(sim,output_plane) + sim_center, sim_size = get_2D_dimensions(sim, output_plane) xmin = sim_center.x - sim_size.x/2 xmax = sim_center.x + sim_size.x/2 @@ -481,22 +528,22 @@ def plot_fields(sim,ax=None,fields=None,output_plane=None,field_parameters=None) zmin = sim_center.z - sim_size.z/2 zmax = sim_center.z + sim_size.z/2 - center = Vector3(sim_center.x,sim_center.y,sim_center.z) - cell_size = Vector3(sim_size.x,sim_size.y,sim_size.z) + center = Vector3(sim_center.x, sim_center.y, sim_center.z) + cell_size = Vector3(sim_size.x, sim_size.y, sim_size.z) if sim_size.x == 0: # Plot y on x axis, z on y axis (YZ plane) - extent = [ymin,ymax,zmin,zmax] + extent = [ymin, ymax, zmin, zmax] xlabel = 'Y' ylabel = 'Z' elif sim_size.y == 0: # Plot x on x axis, z on y axis (XZ plane) - extent = [xmin,xmax,zmin,zmax] + extent = [xmin, xmax, zmin, zmax] xlabel = 'X' ylabel = 'Z' elif sim_size.z == 0: # Plot x on x axis, y on y axis (XY plane) - extent = [xmin,xmax,ymin,ymax] + extent = [xmin, xmax, ymin, ymax] xlabel = 'X' ylabel = 'Y' fields = sim.get_array(center=center, size=cell_size, component=fields) @@ -515,73 +562,72 @@ def plot_fields(sim,ax=None,fields=None,output_plane=None,field_parameters=None) return np.rot90(fields) return ax -def plot2D(sim,ax=None, output_plane=None, fields=None, labels=False, - eps_parameters=None,boundary_parameters=None, - source_parameters=None,monitor_parameters=None, +def plot2D(sim, ax=None, output_plane=None, fields=None, labels=False, + eps_parameters=None, boundary_parameters=None, + source_parameters=None, monitor_parameters=None, field_parameters=None, frequency=None, plot_eps_flag=True, plot_sources_flag=True, plot_monitors_flag=True, plot_boundaries_flag=True): - # Initialize the simulation - if sim.structure is None: - sim.init_sim() # Ensure a figure axis exists if ax is None and mp.am_master(): from matplotlib import pyplot as plt ax = plt.gca() - # Determine a frequency to plot all epsilon - if frequency is None: - try: - # Continuous sources - frequency = sim.sources[0].frequency - except: - try: - # Gaussian sources - frequency = sim.sources[0].src.frequency - except: - try: - # Custom sources - frequency = sim.sources[0].src.center_frequency - except: - # No sources - frequency = 0 # validate the output plane to ensure proper 2D coordinates from meep.simulation import Volume - sim_center, sim_size = get_2D_dimensions(sim,output_plane) - output_plane = Volume(center=sim_center,size=sim_size) + sim_center, sim_size = get_2D_dimensions(sim, output_plane) + output_plane = Volume(center=sim_center, size=sim_size) # Plot geometry if plot_eps_flag: - ax = plot_eps(sim,ax,output_plane=output_plane,eps_parameters=eps_parameters,frequency=frequency) + ax = plot_eps(sim, ax, output_plane=output_plane, + eps_parameters=eps_parameters, frequency=frequency) # Plot boundaries if plot_boundaries_flag: - ax = plot_boundaries(sim,ax,output_plane=output_plane,boundary_parameters=boundary_parameters) + ax = plot_boundaries(sim, ax, output_plane=output_plane, + boundary_parameters=boundary_parameters) # Plot sources if plot_sources_flag: - ax = plot_sources(sim,ax,output_plane=output_plane,labels=labels,source_parameters=source_parameters) + ax = plot_sources(sim, ax, output_plane=output_plane, + labels=labels, source_parameters=source_parameters) # Plot monitors if plot_monitors_flag: - ax = plot_monitors(sim,ax,output_plane=output_plane,labels=labels,monitor_parameters=monitor_parameters) + ax = plot_monitors(sim, ax, output_plane=output_plane, + labels=labels, monitor_parameters=monitor_parameters) # Plot fields - ax = plot_fields(sim,ax,fields,output_plane=output_plane,field_parameters=field_parameters) + if fields: + ax = plot_fields(sim, ax, fields, output_plane=output_plane, + field_parameters=field_parameters) return ax def plot3D(sim): from mayavi import mlab - if not sim._is_initialized: - sim.init_sim() - if sim.dimensions < 3: raise ValueError("Simulation must have 3 dimensions to visualize 3D") - eps_data = sim.get_epsilon() + xmin = sim.geometry_center.x - 0.5*sim.cell_size.x + xmax = sim.geometry_center.x + 0.5*sim.cell_size.x + ymin = sim.geometry_center.y - 0.5*sim.cell_size.y + ymax = sim.geometry_center.y + 0.5*sim.cell_size.y + zmin = sim.geometry_center.z - 0.5*sim.cell_size.z + zmax = sim.geometry_center.z + 0.5*sim.cell_size.z + + Nx = int(sim.cell_size.x * sim.resolution) + 1 + Ny = int(sim.cell_size.y * sim.resolution) + 1 + Nz = int(sim.cell_size.z * sim.resolution) + 1 + + xtics = np.linspace(xmin, xmax, Nx) + ytics = np.linspace(ymin, ymax, Ny) + ztics = np.linspace(zmin, zmax, Nz) + + eps_data = sim.get_epsilon_grid(xtics, ytics, ztics) s = mlab.contour3d(eps_data, colormap="YlGnBu") return s @@ -711,7 +757,7 @@ class Animate2D(object): function). The object is initialized prior to timestepping by specifying the simulation object and the field component. The object can then be passed to any [step-function modifier](#step-function-modifiers). For example, one can record the - Ez fields at every one time unit using: + $E_z$ fields at every one time unit using: ```py animate = mp.Animate2D(sim, @@ -792,7 +838,6 @@ def mod1(ax): self.plot_modifiers = plot_modifiers self.customization_args = customization_args - self.cumulative_fields = [] self._saved_frames = [] @@ -813,7 +858,7 @@ def __call__(self,sim,todo): # Initialize the plot if not self.init: filtered_plot2D = filter_dict(self.customization_args, plot2D) - ax = sim.plot2D(ax=self.ax,fields=self.fields,**filtered_plot2D) + ax = sim.plot2D(ax=self.ax, fields=self.fields, **filtered_plot2D) # Run the plot modifier functions if self.plot_modifiers: for k in range(len(self.plot_modifiers)): @@ -827,7 +872,7 @@ def __call__(self,sim,todo): else: # Update the plot filtered_plot_fields= filter_dict(self.customization_args, plot_fields) - fields = sim.plot_fields(fields=self.fields,**filtered_plot_fields) + fields = sim.plot_fields(fields=self.fields, **filtered_plot_fields) if mp.am_master(): self.ax.images[-1].set_data(fields) self.ax.images[-1].set_clim(vmin=0.8*np.min(fields), vmax=0.8*np.max(fields)) @@ -851,7 +896,7 @@ def __call__(self,sim,todo): if self.normalize and mp.am_master(): if mp.verbosity.meep > 0: print("Normalizing field data...") - fields = np.array(self.cumulative_fields) / np.max(np.abs(self.cumulative_fields),axis=(0,1,2)) + fields = np.array(self.cumulative_fields) / np.max(np.abs(self.cumulative_fields), axis=(0,1,2)) for k in range(len(self.cumulative_fields)): self.ax.images[-1].set_data(fields[k,:,:]) self.ax.images[-1].set_clim(vmin=-0.8, vmax=0.8) diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index dda2b5782..a5fdfb56f 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -2557,13 +2557,11 @@ void invert_tensor(std::complex t_inv[9], std::complex t[9]) { #undef minv(x,y) } -void eff_chi1inv_row_disp(meep::component c, std::complex chi1inv_row[3], - const meep::vec &r, double freq, geom_epsilon *geps) { +void get_chi1_tensor_disp(std::complex tensor[9], const meep::vec &r, double freq, geom_epsilon *geps) { // locate the proper material material_type md; geps->get_material_pt(md, r); const medium_struct *mm = &(md->medium); - std::complex tensor[9], tensor_inv[9]; // loop over all the tensor components for (int i = 0; i < 9; i++) { @@ -2574,7 +2572,7 @@ void eff_chi1inv_row_disp(meep::component c, std::complex chi1inv_row[3] double conductivityCur = vec_to_value(mm->D_conductivity_diag, dummy, i); a = std::complex(1.0, conductivityCur / (2*meep::pi*freq)); - // compute lorentzian component + // compute lorentzian component including the instantaneous ε b = cvec_to_value(mm->epsilon_diag, mm->epsilon_offdiag, i); for (const auto &mm_susc: mm->E_susceptibilities) { meep::lorentzian_susceptibility sus = meep::lorentzian_susceptibility( @@ -2586,7 +2584,12 @@ void eff_chi1inv_row_disp(meep::component c, std::complex chi1inv_row[3] // elementwise multiply tensor[i] = a * b; } +} +void eff_chi1inv_row_disp(meep::component c, std::complex chi1inv_row[3], + const meep::vec &r, double freq, geom_epsilon *geps) { + std::complex tensor[9], tensor_inv[9]; + get_chi1_tensor_disp(tensor, r, freq, geps); // invert the matrix invert_tensor(tensor_inv, tensor); @@ -3050,7 +3053,8 @@ void get_epsilon_grid(geometric_object_list gobj_list, int nx, const double *x, int ny, const double *y, int nz, const double *z, - double *grid_vals) { + std::complex *grid_vals, + double frequency) { double min_val[3], max_val[3]; for (int n = 0; n < 3; ++n) { int ndir = (n == 0) ? nx : ((n == 1) ? ny : nz); @@ -3066,10 +3070,17 @@ void get_epsilon_grid(geometric_object_list gobj_list, geom_epsilon geps(gobj_list, mlist, vol); for (int i = 0; i < nx; ++i) for (int j = 0; j < ny; ++j) - for (int k = 0; k < nz; ++k) - /* obtain the trace of the \varepsilon tensor for each + for (int k = 0; k < nz; ++k) { + /* obtain the trace of the ε tensor (dispersive or non) for each grid point in row-major order (the order used by NumPy) */ - grid_vals[k + nz*(j + ny*i)] = geps.chi1p1(meep::E_stuff, meep::vec(x[i],y[j],z[k])); + if (frequency == 0) + grid_vals[k + nz*(j + ny*i)] = geps.chi1p1(meep::E_stuff, meep::vec(x[i],y[j],z[k])); + else { + std::complex tensor[9]; + get_chi1_tensor_disp(tensor, meep::vec(x[i],y[j],z[k]), frequency, &geps); + grid_vals[k + nz*(j + ny*i)] = (tensor[0] + tensor[4] + tensor[8]) / 3.0; + } + } } } // namespace meep_geom diff --git a/src/meepgeom.hpp b/src/meepgeom.hpp index d052d5b4a..f66c33e3b 100644 --- a/src/meepgeom.hpp +++ b/src/meepgeom.hpp @@ -281,7 +281,8 @@ void get_epsilon_grid(geometric_object_list gobj_list, int nx, const double *x, int ny, const double *y, int nz, const double *z, - double *grid_vals); + std::complex *grid_vals, + double frequency = 0); void init_libctl(material_type default_mat, bool ensure_per, meep::grid_volume *gv, vector3 cell_size, vector3 cell_center, geometric_object_list *geom_list); From cdffa97feb5729103066c9a3ba43ae56a21c9372 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Tue, 30 Nov 2021 08:41:05 -0800 Subject: [PATCH 024/155] unit test for `get_epsilon_grid` (#1835) * unit test for get_epsilon_grid * fix * limit test points to homogeneous regions (i.e. no material interfaces) and away from potential chunk boundaries --- python/Makefile.am | 1 + python/tests/test_get_epsilon_grid.py | 74 +++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 python/tests/test_get_epsilon_grid.py diff --git a/python/Makefile.am b/python/Makefile.am index 1f1fed41b..793eef539 100644 --- a/python/Makefile.am +++ b/python/Makefile.am @@ -65,6 +65,7 @@ TESTS = \ $(TEST_DIR)/test_gaussianbeam.py \ $(TEST_DIR)/test_geom.py \ $(TEST_DIR)/test_get_point.py \ + $(TEST_DIR)/test_get_epsilon_grid.py \ $(TEST_DIR)/test_holey_wvg_bands.py \ $(TEST_DIR)/test_holey_wvg_cavity.py \ $(KDOM_TEST) \ diff --git a/python/tests/test_get_epsilon_grid.py b/python/tests/test_get_epsilon_grid.py new file mode 100644 index 000000000..e9d24bd9c --- /dev/null +++ b/python/tests/test_get_epsilon_grid.py @@ -0,0 +1,74 @@ +import unittest +import parameterized +import numpy as np +import meep as mp +from meep.materials import SiN, Co + +class TestGetEpsilonGrid(unittest.TestCase): + + def setUp(self): + resolution = 60 + self.cell_size = mp.Vector3(1.0,1.0,0) + + matgrid_resolution = 200 + matgrid_size = mp.Vector3(1.0,1.0,mp.inf) + Nx = int(matgrid_resolution*matgrid_size.x) + 1 + Ny = int(matgrid_resolution*matgrid_size.y) + 1 + x = np.linspace(-0.5*matgrid_size.x,0.5*matgrid_size.x,Nx) + y = np.linspace(-0.5*matgrid_size.y,0.5*matgrid_size.y,Ny) + xv, yv = np.meshgrid(x,y) + rad = 0.201943 + w = 0.104283 + weights = np.logical_and(np.sqrt(np.square(xv) + np.square(yv)) > rad, + np.sqrt(np.square(xv) + np.square(yv)) < rad+w, + dtype=np.double) + + matgrid = mp.MaterialGrid(mp.Vector3(Nx,Ny), + mp.air, + mp.Medium(index=3.5), + weights=weights, + do_averaging=False, + beta=0, + eta=0.5) + + geometry = [mp.Cylinder(center=mp.Vector3(0.35,0.1), + radius=0.1, + height=mp.inf, + material=mp.Medium(index=1.5)), + mp.Block(center=mp.Vector3(-0.15,-0.2), + size=mp.Vector3(0.2,0.24,mp.inf), + material=SiN), + mp.Block(center=mp.Vector3(-0.2,0.2), + size=mp.Vector3(0.4,0.4,mp.inf), + material=matgrid), + mp.Prism(vertices=[mp.Vector3(0.05,0.45), + mp.Vector3(0.32,0.22), + mp.Vector3(0.15,0.10)], + height=0.5, + material=Co)] + + self.sim = mp.Simulation(resolution=resolution, + cell_size=self.cell_size, + geometry=geometry, + eps_averaging=False) + self.sim.init_sim() + + @parameterized.parameterized.expand([ + (mp.Vector3(0.2,0.2), 1.1), + (mp.Vector3(-0.2,0.1), 0.7), + (mp.Vector3(-0.2,-0.25), 0.55), + (mp.Vector3(0.4,0.1), 0) + ]) + def test_get_epsilon_grid(self, pt, freq): + eps_grid = self.sim.get_epsilon_grid(np.array([pt.x]), + np.array([pt.y]), + np.array([0]), + freq) + eps_pt = self.sim.get_epsilon_point(pt, freq) + print("eps:, ({},{}), {}, {}".format(pt.x,pt.y,eps_grid,eps_pt)) + self.assertAlmostEqual(np.real(eps_grid), np.real(eps_pt), places=6) + self.assertAlmostEqual(np.imag(eps_grid), np.imag(eps_pt), places=6) + + +if __name__ == '__main__': + unittest.main() From 005ef909026e1846daa131dc86417ef6585880a4 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Tue, 30 Nov 2021 18:05:19 -0800 Subject: [PATCH 025/155] support for single-precision floating point for fields array functions (#1833) * switch dft-related functions to using realnum from double * more fixes * more type conversions from double to realnum * adjust check tolerance of tests/integrate.cpp based on floating-point precision * more fixes * rebase from master from fix merge conflicts * slight adjustment to tolerances in unit tests and update docs * remove type check in test_adjoint_solver.py * revert return types of integration functions to double * revert return type of process_dft_component to double * cleanup --- doc/docs/Python_User_Interface.md | 7 +- python/adjoint/optimization_problem.py | 2 +- python/adjoint/utils.py | 33 ++++--- python/adjoint/wrapper.py | 4 +- python/meep.i | 40 ++++---- python/simulation.py | 14 ++- python/tests/test_adjoint_jax.py | 3 +- python/tests/test_adjoint_solver.py | 7 +- python/tests/test_cavity_arrayslice.py | 8 +- scheme/meep.i | 6 +- src/array_slice.cpp | 129 +++++++++++++------------ src/dft.cpp | 48 ++++----- src/energy_and_flux.cpp | 15 +-- src/h5fields.cpp | 13 +-- src/integrate.cpp | 20 ++-- src/integrate2.cpp | 14 +-- src/meep.hpp | 72 +++++++------- src/meep/mympi.hpp | 2 + src/meepgeom.cpp | 110 ++++++++++----------- src/meepgeom.hpp | 8 +- src/mympi.cpp | 12 +++ src/vec.cpp | 9 +- tests/array-slice-ll.cpp | 4 +- tests/dft-fields.cpp | 24 +++-- tests/integrate.cpp | 4 +- tests/pw-source-ll.cpp | 10 +- tests/ring-ll.cpp | 10 +- 27 files changed, 332 insertions(+), 296 deletions(-) diff --git a/doc/docs/Python_User_Interface.md b/doc/docs/Python_User_Interface.md index 581dd3382..811be2601 100644 --- a/doc/docs/Python_User_Interface.md +++ b/doc/docs/Python_User_Interface.md @@ -3602,7 +3602,9 @@ current simulation time. + `cmplx`: `boolean`; if `True`, return complex-valued data otherwise return real-valued data (default). -+ `arr`: optional field to pass a pre-allocated NumPy array of the correct size, ++ `arr`: optional parameter to pass a pre-allocated NumPy array of the correct size and + type (either `numpy.float32` or `numpy.float64` depending on the [floating-point precision + of the fields and materials](Build_From_Source.md#floating-point-precision-of-the-fields-and-materials-arrays)) which will be overwritten with the field/material data instead of allocating a new array. Normally, this will be the array returned from a previous call to `get_array` for a similar slice, allowing one to re-use `arr` (e.g., when @@ -3655,7 +3657,8 @@ def get_dft_array(self, dft_obj, component, num_freq):
-Returns the Fourier-transformed fields as a NumPy array. +Returns the Fourier-transformed fields as a NumPy array. The type is either `numpy.complex64` +or `numpy.complex128` depending on the [floating-point precision of the fields](Build_From_Source.md#floating-point-precision-of-the-fields-and-materials-arrays). **Parameters:** diff --git a/python/adjoint/optimization_problem.py b/python/adjoint/optimization_problem.py index 2fb45d5ca..bce235748 100644 --- a/python/adjoint/optimization_problem.py +++ b/python/adjoint/optimization_problem.py @@ -248,7 +248,7 @@ def adjoint_run(self): # Store adjoint fields for each design set of design variables self.D_a.append(utils.gather_design_region_fields(self.sim,self.design_region_monitors,self.frequencies)) - + # reset the m number if utils._check_if_cylindrical(self.sim): self.sim.m = -self.sim.m diff --git a/python/adjoint/utils.py b/python/adjoint/utils.py index 04b041fe7..38ffbd8f9 100644 --- a/python/adjoint/utils.py +++ b/python/adjoint/utils.py @@ -37,14 +37,14 @@ def __init__( def update_design_parameters(self, design_parameters): self.design_parameters.update_weights(design_parameters) - + def update_beta(self,beta): self.design_parameters.beta=beta def get_gradient(self, sim, fields_a, fields_f, frequencies, finite_difference_step): num_freqs = onp.array(frequencies).size shapes = [] - '''We have the option to linear scale the gradients up front + '''We have the option to linearly scale the gradients up front using the scalegrad parameter (leftover from MPB API). Not currently needed for any existing feature (but available for future use)''' @@ -54,16 +54,18 @@ def get_gradient(self, sim, fields_a, fields_f, frequencies, finite_difference_s returns a singleton element for the forward and adjoint fields. This only occurs when we are in 2D and only working with a particular polarization (as the other fields are never stored). For example, the - 2D in-plane polarization consists of a single scalar Ez field + 2D in-plane polarization consists of a single scalar Ez field (technically, meep doesn't store anything for these cases, but get_dft_array still returns 0). - - Our get_gradient algorithm, however, requires we pass an array of + + Our get_gradient algorithm, however, requires we pass an array of zeros with the proper shape as the design_region.''' spatial_shape = sim.get_array_slice_dimensions(component, vol=self.volume)[0] if (fields_a[component_idx][0,...].size == 1): - fields_a[component_idx] = onp.zeros(onp.insert(spatial_shape,0,num_freqs)) - fields_f[component_idx] = onp.zeros(onp.insert(spatial_shape,0,num_freqs)) + fields_a[component_idx] = onp.zeros(onp.insert(spatial_shape,0,num_freqs), + dtype=onp.float32 if mp.is_single_precision() else onp.float64) + fields_f[component_idx] = onp.zeros(onp.insert(spatial_shape,0,num_freqs), + dtype=onp.float32 if mp.is_single_precision() else onp.float64) if _check_if_cylindrical(sim): '''For some reason, get_dft_array returns the field components in a different order than the convention used @@ -78,15 +80,16 @@ def get_gradient(self, sim, fields_a, fields_f, frequencies, finite_difference_s shapes = onp.asarray(shapes).flatten(order='C') fields_a = onp.concatenate(fields_a) fields_f = onp.concatenate(fields_f) - + grad = onp.zeros((num_freqs, self.num_design_params)) # preallocate geom_list = sim.geometry f = sim.fields vol = sim._fit_volume_to_simulation(self.volume) - + # compute the gradient mp._get_gradient(grad,scalegrad,fields_a,fields_f, - sim.gv,vol.swigobj,onp.array(frequencies),sim.geps,shapes,finite_difference_step) + sim.gv,vol.swigobj,onp.array(frequencies), + sim.geps,shapes,finite_difference_step) return onp.squeeze(grad).T def _check_if_cylindrical(sim): @@ -203,7 +206,7 @@ def gather_design_region_fields( fairly awkward to inspect directly. Their primary use case is supporting gradient calculations. """ - fwd_fields = [] + design_region_fields = [] for monitor in design_region_monitors: fields_by_component = [] for component in _compute_components(simulation): @@ -212,8 +215,8 @@ def gather_design_region_fields( fields = simulation.get_dft_array(monitor, component, freq_idx) fields_by_freq.append(_make_at_least_nd(fields)) fields_by_component.append(onp.stack(fields_by_freq)) - fwd_fields.append(fields_by_component) - return fwd_fields + design_region_fields.append(fields_by_component) + return design_region_fields def validate_and_update_design( @@ -258,7 +261,7 @@ def create_adjoint_sources( monitors: Iterable[ObjectiveQuantity], monitor_values_grad: onp.ndarray) -> List[mp.Source]: monitor_values_grad = onp.asarray(monitor_values_grad, - dtype=onp.complex128) + dtype=onp.complex64 if mp.is_single_precision() else onp.complex128) if not onp.any(monitor_values_grad): raise RuntimeError( 'The gradient of all monitor values is zero, which ' @@ -273,7 +276,7 @@ def create_adjoint_sources( for monitor_idx, monitor in enumerate(monitors): # `dj` for each monitor will have a shape of (num frequencies,) dj = onp.asarray(monitor_values_grad[monitor_idx], - dtype=onp.complex128) + dtype=onp.complex64 if mp.is_single_precision() else onp.complex128) if onp.any(dj): adjoint_sources += monitor.place_adjoint_source(dj) assert adjoint_sources diff --git a/python/adjoint/wrapper.py b/python/adjoint/wrapper.py index be793573f..3fad98c10 100644 --- a/python/adjoint/wrapper.py +++ b/python/adjoint/wrapper.py @@ -229,7 +229,9 @@ def _simulate_fwd(design_variables): def _simulate_rev(res, monitor_values_grad): """Runs adjoint simulation, returning VJP of design wrt monitor values.""" fwd_fields = jax.tree_map( - lambda x: onp.asarray(x, dtype=onp.complex128), res[0]) + lambda x: onp.asarray(x, + dtype=onp.complex64 if mp.is_single_precision() else onp.complex128), + res[0]) design_variable_shapes = res[1] adj_fields = self._run_adjoint_simulation(monitor_values_grad) vjps = self._calculate_vjps(fwd_fields, adj_fields, diff --git a/python/meep.i b/python/meep.i index f1dc8012b..bb7f1c80c 100644 --- a/python/meep.i +++ b/python/meep.i @@ -150,7 +150,7 @@ static std::complex py_amp_func_wrap(const meep::vec &v) { return ret; } -static std::complex py_field_func_wrap(const std::complex *fields, +static std::complex py_field_func_wrap(const std::complex *fields, const meep::vec &loc, void *data_) { SWIG_PYTHON_THREAD_SCOPED_BLOCK; @@ -441,25 +441,25 @@ PyObject *_get_dft_array(meep::fields *f, dft_type dft, meep::component c, int n // Return value: New reference int rank; size_t dims[3]; - std::complex *dft_arr = f->get_dft_array(dft, c, num_freq, &rank, dims); + std::complex *dft_arr = f->get_dft_array(dft, c, num_freq, &rank, dims); - if (dft_arr==NULL){ // this can happen e.g. if component c vanishes by symmetry - std::complex d[1] = {std::complex(0,0)}; - return PyArray_SimpleNewFromData(0, 0, NPY_CDOUBLE, d); + if (dft_arr == NULL) { // this can happen e.g. if component c vanishes by symmetry + std::complex d[1] = {std::complex(0,0)}; + return PyArray_SimpleNewFromData(0, 0, sizeof(meep::realnum) == sizeof(float) ? NPY_CFLOAT : NPY_CDOUBLE, d); } if (rank == 0) // singleton results - return PyArray_SimpleNewFromData(0, 0, NPY_CDOUBLE, dft_arr); + return PyArray_SimpleNewFromData(0, 0, sizeof(meep::realnum) == sizeof(float) ? NPY_CFLOAT : NPY_CDOUBLE, dft_arr); size_t length = 1; npy_intp *arr_dims = new npy_intp[rank]; for (int i = 0; i < rank; ++i) { - arr_dims[i] = dims[i]; // implicit size_t -> int cast, presumed safe for individual array dimensions - length *= dims[i]; + arr_dims[i] = dims[i]; // implicit size_t -> int cast, presumed safe for individual array dimensions + length *= dims[i]; } - PyObject *py_arr = PyArray_SimpleNew(rank, arr_dims, NPY_CDOUBLE); - memcpy(PyArray_DATA((PyArrayObject*)py_arr), dft_arr, sizeof(std::complex) * length); + PyObject *py_arr = PyArray_SimpleNew(rank, arr_dims, sizeof(meep::realnum) == sizeof(float) ? NPY_CFLOAT : NPY_CDOUBLE); + memcpy(PyArray_DATA((PyArrayObject*)py_arr), dft_arr, sizeof(std::complex) * length); delete[] dft_arr; if (arr_dims) delete[] arr_dims; @@ -844,8 +844,8 @@ meep::volume_list *make_volume_list(const meep::volume &v, int c, %inline %{ void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObject *fields_f, - meep::grid_volume *grid_volume, meep::volume *where, PyObject *frequencies, - meep_geom::geom_epsilon *geps, PyObject *fields_shapes, double fd_step) { + meep::grid_volume *grid_volume, meep::volume *where, PyObject *frequencies, + meep_geom::geom_epsilon *geps, PyObject *fields_shapes, double fd_step) { // clean the gradient array PyArrayObject *pao_grad = (PyArrayObject *)grad; if (!PyArray_Check(pao_grad)) meep::abort("grad parameter must be numpy array."); @@ -859,14 +859,14 @@ void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObjec if (!PyArray_Check(pao_fields_a)) meep::abort("adjoint fields parameter must be numpy array."); if (!PyArray_ISCARRAY(pao_fields_a)) meep::abort("Numpy adjoint fields array must be C-style contiguous."); if (PyArray_NDIM(pao_fields_a) !=1) {meep::abort("Numpy adjoint fields array must have 1 dimension.");} - std::complex *fields_a_c = (std::complex *)PyArray_DATA(pao_fields_a); + std::complex *fields_a_c = (std::complex *)PyArray_DATA(pao_fields_a); // clean the forward fields array PyArrayObject *pao_fields_f = (PyArrayObject *)fields_f; if (!PyArray_Check(pao_fields_f)) meep::abort("forward fields parameter must be numpy array."); if (!PyArray_ISCARRAY(pao_fields_f)) meep::abort("Numpy forward fields array must be C-style contiguous."); if (PyArray_NDIM(pao_fields_f) !=1) {meep::abort("Numpy forward fields array must have 1 dimension.");} - std::complex *fields_f_c = (std::complex *)PyArray_DATA(pao_fields_f); + std::complex *fields_f_c = (std::complex *)PyArray_DATA(pao_fields_f); // clean shapes array PyArrayObject *pao_fields_shapes = (PyArrayObject *)fields_shapes; @@ -1025,20 +1025,20 @@ void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObjec $1 = (size_t *)array_data($input); } -%typecheck(SWIG_TYPECHECK_POINTER, fragment="NumPy_Fragments") double* slice { +%typecheck(SWIG_TYPECHECK_POINTER, fragment="NumPy_Fragments") meep::realnum* slice { $1 = is_array($input); } -%typemap(in, fragment="NumPy_Macros") double* slice { - $1 = (double *)array_data($input); +%typemap(in, fragment="NumPy_Macros") meep::realnum* slice { + $1 = (meep::realnum *)array_data($input); } -%typecheck(SWIG_TYPECHECK_POINTER, fragment="NumPy_Fragments") std::complex* slice { +%typecheck(SWIG_TYPECHECK_POINTER, fragment="NumPy_Fragments") std::complex* slice { $1 = is_array($input); } -%typemap(in) std::complex* slice { - $1 = (std::complex *)array_data($input); +%typemap(in) std::complex* slice { + $1 = (std::complex *)array_data($input); } %typecheck(SWIG_TYPECHECK_POINTER) meep::component { diff --git a/python/simulation.py b/python/simulation.py index 5fbf66155..b99dccf3b 100644 --- a/python/simulation.py +++ b/python/simulation.py @@ -3337,7 +3337,9 @@ def get_array(self, component=None, vol=None, center=None, size=None, cmplx=None + `cmplx`: `boolean`; if `True`, return complex-valued data otherwise return real-valued data (default). - + `arr`: optional field to pass a pre-allocated NumPy array of the correct size, + + `arr`: optional parameter to pass a pre-allocated NumPy array of the correct size and + type (either `numpy.float32` or `numpy.float64` depending on the [floating-point precision + of the fields and materials](Build_From_Source.md#floating-point-precision-of-the-fields-and-materials-arrays)) which will be overwritten with the field/material data instead of allocating a new array. Normally, this will be the array returned from a previous call to `get_array` for a similar slice, allowing one to re-use `arr` (e.g., when @@ -3407,7 +3409,10 @@ def get_array(self, component=None, vol=None, center=None, size=None, cmplx=None arr = np.require(arr, requirements=['C', 'W']) else: - arr = np.zeros(dims, dtype=np.complex128 if cmplx else np.float64) + if mp.is_single_precision(): + arr = np.zeros(dims, dtype=np.complex64 if cmplx else np.float32) + else: + arr = np.zeros(dims, dtype=np.complex128 if cmplx else np.float64) if np.iscomplexobj(arr): self.fields.get_complex_array_slice(v, component, arr, frequency, snap) @@ -3418,7 +3423,8 @@ def get_array(self, component=None, vol=None, center=None, size=None, cmplx=None def get_dft_array(self, dft_obj, component, num_freq): """ - Returns the Fourier-transformed fields as a NumPy array. + Returns the Fourier-transformed fields as a NumPy array. The type is either `numpy.complex64` + or `numpy.complex128` depending on the [floating-point precision of the fields](Build_From_Source.md#floating-point-precision-of-the-fields-and-materials-arrays). **Parameters:** @@ -3464,7 +3470,7 @@ def get_source(self, component, vol=None, center=None, size=None): dim_sizes = np.zeros(3, dtype=np.uintp) mp._get_array_slice_dimensions(self.fields, v, dim_sizes, True, False) dims = [s for s in dim_sizes if s != 0] - arr = np.zeros(dims, dtype=np.complex128) + arr = np.zeros(dims, dtype=np.complex64 if mp.is_single_precision() else np.complex128) self.fields.get_source_slice(v, component, arr) return arr diff --git a/python/tests/test_adjoint_jax.py b/python/tests/test_adjoint_jax.py index 768d7ac82..17994a8fa 100644 --- a/python/tests/test_adjoint_jax.py +++ b/python/tests/test_adjoint_jax.py @@ -175,7 +175,8 @@ def test_design_region_monitor_helpers(self): for value in design_region_fields[0]: self.assertIsInstance(value, onp.ndarray) self.assertEqual(value.ndim, 4) # dims: freq, x, y, pad - self.assertEqual(value.dtype, onp.complex128) + self.assertEqual(value.dtype, + onp.complex64 if mp.is_single_precision() else onp.complex128) class WrapperTest(ApproxComparisonTestCase): diff --git a/python/tests/test_adjoint_solver.py b/python/tests/test_adjoint_solver.py index dae6e80f9..1f4efa360 100644 --- a/python/tests/test_adjoint_solver.py +++ b/python/tests/test_adjoint_solver.py @@ -15,7 +15,8 @@ resolution = 30 silicon = mp.Medium(epsilon=12) -sapphire = mp.Medium(epsilon_diag=(10.225,10.225,9.95),epsilon_offdiag=(-0.825,-0.55*np.sqrt(3/2),0.55*np.sqrt(3/2))) +sapphire = mp.Medium(epsilon_diag=(10.225,10.225,9.95), + epsilon_offdiag=(-0.825,-0.55*np.sqrt(3/2),0.55*np.sqrt(3/2))) sxy = 5.0 cell_size = mp.Vector3(sxy,sxy,0) @@ -469,7 +470,7 @@ def test_complex_fields(self): ## compare objective results print("Ez2 -- adjoint solver: {}, traditional simulation: {}".format(adjsol_obj,Ez2_unperturbed)) - self.assertClose(adjsol_obj,Ez2_unperturbed,epsilon=1e-8) + self.assertClose(adjsol_obj,Ez2_unperturbed,epsilon=1e-6) ## compute perturbed |Ez|^2 Ez2_perturbed = forward_simulation_complex_fields(p+dp, frequencies) @@ -533,7 +534,7 @@ def test_offdiagonal(self): adj_scale = (dp[None,:]@adjsol_grad).flatten() fd_grad = S12_perturbed-S12_unperturbed print("Directional derivative -- adjoint solver: {}, FD: {}".format(adj_scale,fd_grad)) - tol = 0.04 + tol = 0.05 if mp.is_single_precision() else 0.04 self.assertClose(adj_scale,fd_grad,epsilon=tol) if __name__ == '__main__': unittest.main() diff --git a/python/tests/test_cavity_arrayslice.py b/python/tests/test_cavity_arrayslice.py index bbedbebe6..42f758b6f 100644 --- a/python/tests/test_cavity_arrayslice.py +++ b/python/tests/test_cavity_arrayslice.py @@ -70,7 +70,7 @@ def test_2d_slice(self): def test_1d_slice_user_array(self): self.sim.run(until_after_sources=0) - arr = np.zeros(126, dtype=np.float64) + arr = np.zeros(126, dtype=np.float32 if mp.is_single_precision() else np.float64) vol = mp.Volume(center=self.center_1d, size=self.size_1d) self.sim.get_array(mp.Hz, vol, arr=arr) tol = 1e-5 if mp.is_single_precision() else 1e-8 @@ -78,7 +78,7 @@ def test_1d_slice_user_array(self): def test_2d_slice_user_array(self): self.sim.run(until_after_sources=0) - arr = np.zeros((126, 38), dtype=np.float64) + arr = np.zeros((126, 38), dtype=np.float32 if mp.is_single_precision() else np.float64) vol = mp.Volume(center=self.center_2d, size=self.size_2d) self.sim.get_array(mp.Hz, vol, arr=arr) tol = 1e-5 if mp.is_single_precision() else 1e-8 @@ -106,14 +106,14 @@ def test_1d_complex_slice(self): self.sim.run(until_after_sources=0) vol = mp.Volume(center=self.center_1d, size=self.size_1d) hl_slice1d = self.sim.get_array(mp.Hz, vol, cmplx=True) - self.assertTrue(hl_slice1d.dtype == np.complex128) + self.assertTrue(hl_slice1d.dtype == np.complex64 if mp.is_single_precision() else np.complex128) self.assertTrue(hl_slice1d.shape[0] == 126) def test_2d_complex_slice(self): self.sim.run(until_after_sources=0) vol = mp.Volume(center=self.center_2d, size=self.size_2d) hl_slice2d = self.sim.get_array(mp.Hz, vol, cmplx=True) - self.assertTrue(hl_slice2d.dtype == np.complex128) + self.assertTrue(hl_slice2d.dtype == np.complex64 if mp.is_single_precision() else np.complex128) self.assertTrue(hl_slice2d.shape[0] == 126 and hl_slice2d.shape[1] == 38) diff --git a/scheme/meep.i b/scheme/meep.i index b88885e5d..9c8929ba8 100644 --- a/scheme/meep.i +++ b/scheme/meep.i @@ -33,9 +33,9 @@ static inline std::complex my_complex_func2(double t, void *f) { } typedef struct { SCM func; int nf; } my_field_func_data; -static inline std::complex my_field_func(const std::complex *fields, - const meep::vec &loc, - void *data_) { +static inline std::complex my_field_func(const std::complex *fields, + const meep::vec &loc, + void *data_) { my_field_func_data *data = (my_field_func_data *) data_; int num_items = data->nf; cnumber *items = new cnumber[num_items]; diff --git a/src/array_slice.cpp b/src/array_slice.cpp index b9905c16f..cb7d7aff3 100644 --- a/src/array_slice.cpp +++ b/src/array_slice.cpp @@ -43,17 +43,17 @@ constexpr size_t ARRAY_TO_ALL_BUFSIZE = 1 << 16; // Use (64k * 8 bytes) of buffe /* repeatedly call sum_to_all to consolidate all entries of */ /* an array on all cores. */ /* array: in/out ptr to the data */ -/* array_size: data size in multiples of sizeof(double) */ +/* array_size: data size in multiples of sizeof(realnum) */ /***************************************************************/ -double *array_to_all(double *array, size_t array_size) { +realnum *array_to_all(realnum *array, size_t array_size) { #ifdef HAVE_MPI - double *buffer = new double[ARRAY_TO_ALL_BUFSIZE]; + realnum *buffer = new realnum[ARRAY_TO_ALL_BUFSIZE]; ptrdiff_t offset = 0; size_t remaining = array_size; while (remaining != 0) { size_t xfer_size = (remaining > ARRAY_TO_ALL_BUFSIZE ? ARRAY_TO_ALL_BUFSIZE : remaining); sum_to_all(array + offset, buffer, xfer_size); - memcpy(array + offset, buffer, xfer_size * sizeof(double)); + memcpy(array + offset, buffer, xfer_size * sizeof(realnum)); remaining -= xfer_size; offset += xfer_size; } @@ -64,14 +64,22 @@ double *array_to_all(double *array, size_t array_size) { return array; } -complex *array_to_all(complex *array, size_t array_size) { - return (complex *)array_to_all((double *)array, 2 * array_size); +complex *array_to_all(complex *array, size_t array_size) { + return (complex *)array_to_all((realnum *)array, 2 * array_size); } } // namespace /***************************************************************************/ +std::complex cdouble(std::complex z) { + return std::complex(real(z), imag(z)); +} + +std::complex cdouble(std::complex z) { + return z; +} + typedef struct { // information related to the volume covered by the @@ -101,7 +109,7 @@ typedef struct { // temporary internal storage buffers component *cS; complex *ph; - complex *fields; + complex *fields; ptrdiff_t *offsets; double frequency; @@ -119,16 +127,16 @@ typedef struct { } array_slice_data; /* passthrough field function equivalent to component_fun in h5fields.cpp */ -static complex default_field_func(const complex *fields, const vec &loc, void *data_) { +static complex default_field_func(const complex *fields, const vec &loc, void *data_) { (void)loc; // unused (void)data_; // unused - return fields[0]; + return cdouble(fields[0]); } -static double default_field_rfunc(const complex *fields, const vec &loc, void *data_) { +static double default_field_rfunc(const complex *fields, const vec &loc, void *data_) { (void)loc; // unused (void)data_; // unused - return real(fields[0]); + return real(cdouble(fields[0])); } /***************************************************************/ @@ -174,7 +182,7 @@ static void get_array_slice_dimensions_chunkloop(fields_chunk *fc, int ichnk, co typedef struct { component source_component; ivec slice_imin, slice_imax; - complex *slice; + complex *slice; } source_slice_data; bool in_range(int imin, int i, int imax) { return (imin <= i && i <= imax); } @@ -310,18 +318,19 @@ static void get_array_slice_chunkloop(fields_chunk *fc, int ichnk, component cgr // Otherwise proceed to compute the function of field components to be // // tabulated on the slice, exactly as in fields::integrate. // //-----------------------------------------------------------------------// - double *slice = 0; - complex *zslice = 0; + realnum *slice = 0; + complex *zslice = 0; bool complex_data = (data->rfun == 0); if (complex_data) - zslice = (complex *)data->vslice; + zslice = (complex *)data->vslice; else - slice = (double *)data->vslice; + slice = (realnum *)data->vslice; ptrdiff_t *off = data->offsets; component *cS = data->cS; double frequency = data->frequency; - complex *fields = data->fields, *ph = data->ph; + complex *fields = data->fields; + complex *ph = data->ph; const component *iecs = data->inveps_cs; const direction *ieds = data->inveps_ds; ptrdiff_t ieos[6]; @@ -515,8 +524,8 @@ bool increment(size_t *n, size_t *nMax, int rank) { } // data_size = 1,2 for real,complex-valued array -double *collapse_array(double *array, int *rank, size_t dims[3], direction dirs[3], volume where, - int data_size = 1) { +realnum *collapse_array(realnum *array, int *rank, size_t dims[3], direction dirs[3], volume where, + int data_size = 1) { /*--------------------------------------------------------------*/ /*- detect empty dimensions and compute rank and strides for */ @@ -560,9 +569,9 @@ double *collapse_array(double *array, int *rank, size_t dims[3], direction dirs[ /*--------------------------------------------------------------*/ size_t reduced_grid_size = reduced_dims[0] * (reduced_rank == 2 ? reduced_dims[1] : 1); size_t reduced_array_size = data_size * reduced_grid_size; - double *reduced_array = new double[reduced_array_size]; + realnum *reduced_array = new realnum[reduced_array_size]; if (!reduced_array) meep::abort("%s:%i: out of memory (%zu)", __FILE__, __LINE__, reduced_array_size); - memset(reduced_array, 0, reduced_array_size * sizeof(double)); + memset(reduced_array, 0, reduced_array_size * sizeof(realnum)); size_t n[3] = {0, 0, 0}; do { @@ -581,9 +590,9 @@ double *collapse_array(double *array, int *rank, size_t dims[3], direction dirs[ return reduced_array; } -complex *collapse_array(complex *array, int *rank, size_t dims[3], direction dirs[3], - volume where) { - return (complex *)collapse_array((double *)array, rank, dims, dirs, where, 2); +complex *collapse_array(complex *array, int *rank, size_t dims[3], direction dirs[3], + volume where) { + return (complex *)collapse_array((realnum *)array, rank, dims, dirs, where, 2); } /**********************************************************************/ @@ -607,7 +616,7 @@ void *fields::do_get_array_slice(const volume &where, std::vector com int elem_size = complex_data ? 2 : 1; void *vslice_uncollapsed; - vslice_uncollapsed = memset(new double[slice_size * elem_size], 0, slice_size * elem_size * sizeof(double)); + vslice_uncollapsed = memset(new realnum[slice_size * elem_size], 0, slice_size * elem_size * sizeof(realnum)); data.vslice = vslice_uncollapsed; data.snap = snap; @@ -619,7 +628,7 @@ void *fields::do_get_array_slice(const volume &where, std::vector com int num_components = components.size(); data.cS = new component[num_components]; data.ph = new complex[num_components]; - data.fields = new complex[num_components]; + data.fields = new complex[num_components]; data.offsets = new ptrdiff_t[2 * num_components]; memset(data.offsets, 0, 2 * num_components * sizeof(ptrdiff_t)); data.empty_dim[0] = data.empty_dim[1] = data.empty_dim[2] = data.empty_dim[3] = @@ -659,19 +668,19 @@ void *fields::do_get_array_slice(const volume &where, std::vector com loop_in_chunks(get_array_slice_chunkloop, (void *)&data, where, Centered, true, snap); if (!snap) { - double *slice = collapse_array((double *)vslice_uncollapsed, &rank, dims, dirs, where, elem_size); + realnum *slice = collapse_array((realnum *)vslice_uncollapsed, &rank, dims, dirs, where, elem_size); rank = get_array_slice_dimensions(where, dims, dirs, true, false, 0, &data); slice_size = data.slice_size; - vslice_uncollapsed = (double*) slice; + vslice_uncollapsed = (realnum*) slice; } if (vslice) { - memcpy(vslice, vslice_uncollapsed, slice_size * elem_size * sizeof(double)); - delete[] (double*) vslice_uncollapsed; + memcpy(vslice, vslice_uncollapsed, slice_size * elem_size * sizeof(realnum)); + delete[] (realnum*) vslice_uncollapsed; } else vslice = vslice_uncollapsed; - array_to_all((double *)vslice, elem_size * slice_size); + array_to_all((realnum *)vslice, elem_size * slice_size); delete[] data.offsets; delete[] data.fields; @@ -685,48 +694,48 @@ void *fields::do_get_array_slice(const volume &where, std::vector com /***************************************************************/ /* entry points to get_array_slice */ /***************************************************************/ -double *fields::get_array_slice(const volume &where, std::vector components, - field_rfunction rfun, void *fun_data, double *slice, - double frequency, bool snap) { - return (double *)do_get_array_slice(where, components, 0, rfun, fun_data, (void *)slice, - frequency, snap); +realnum *fields::get_array_slice(const volume &where, std::vector components, + field_rfunction rfun, void *fun_data, realnum *slice, + double frequency, bool snap) { + return (realnum *)do_get_array_slice(where, components, 0, rfun, fun_data, (void *)slice, + frequency, snap); } -complex *fields::get_complex_array_slice(const volume &where, std::vector components, - field_function fun, void *fun_data, complex *slice, - double frequency, bool snap) { - return (complex *)do_get_array_slice(where, components, fun, 0, fun_data, (void *)slice, - frequency, snap); +complex *fields::get_complex_array_slice(const volume &where, std::vector components, + field_function fun, void *fun_data, complex *slice, + double frequency, bool snap) { + return (complex *)do_get_array_slice(where, components, fun, 0, fun_data, (void *)slice, + frequency, snap); } -double *fields::get_array_slice(const volume &where, component c, double *slice, double frequency, - bool snap) { +realnum *fields::get_array_slice(const volume &where, component c, realnum *slice, double frequency, + bool snap) { std::vector components(1); components[0] = c; - return (double *)do_get_array_slice(where, components, 0, default_field_rfunc, 0, (void *)slice, - frequency, snap); + return (realnum *)do_get_array_slice(where, components, 0, default_field_rfunc, 0, (void *)slice, + frequency, snap); } -double *fields::get_array_slice(const volume &where, derived_component c, double *slice, - double frequency, bool snap) { +realnum *fields::get_array_slice(const volume &where, derived_component c, realnum *slice, + double frequency, bool snap) { int nfields; component carray[12]; field_rfunction rfun = derived_component_func(c, gv, nfields, carray); std::vector cs(carray, carray + nfields); - return (double *)do_get_array_slice(where, cs, 0, rfun, &nfields, (void *)slice, - frequency, snap); + return (realnum *)do_get_array_slice(where, cs, 0, rfun, &nfields, (void *)slice, + frequency, snap); } -complex *fields::get_complex_array_slice(const volume &where, component c, complex *slice, - double frequency, bool snap) { +complex *fields::get_complex_array_slice(const volume &where, component c, complex *slice, + double frequency, bool snap) { std::vector components(1); components[0] = c; - return (complex *)do_get_array_slice(where, components, default_field_func, 0, 0, (void *)slice, - frequency, snap); + return (complex *)do_get_array_slice(where, components, default_field_func, 0, 0, (void *)slice, + frequency, snap); } -complex *fields::get_source_slice(const volume &where, component source_slice_component, - complex *slice) { +complex *fields::get_source_slice(const volume &where, component source_slice_component, + complex *slice) { size_t dims[3]; direction dirs[3]; vec min_max_loc[2]; @@ -737,18 +746,18 @@ complex *fields::get_source_slice(const volume &where, component source_ data.source_component = source_slice_component; data.slice_imin = gv.round_vec(min_max_loc[0]); data.slice_imax = gv.round_vec(min_max_loc[1]); - data.slice = new complex[slice_size]; + data.slice = new complex[slice_size]; if (!data.slice) meep::abort("%s:%i: out of memory (%zu)", __FILE__, __LINE__, slice_size); loop_in_chunks(get_source_slice_chunkloop, (void *)&data, where, Centered, true, false); - complex *slice_collapsed = collapse_array(data.slice, &rank, dims, dirs, where); + complex *slice_collapsed = collapse_array(data.slice, &rank, dims, dirs, where); rank = get_array_slice_dimensions(where, dims, dirs, true, false); slice_size = dims[0] * (rank >= 2 ? dims[1] : 1) * (rank == 3 ? dims[2] : 1); if (slice) { - memcpy(slice, slice_collapsed, 2 * slice_size * sizeof(double)); - delete[] (complex*) slice_collapsed; + memcpy(slice, slice_collapsed, 2 * slice_size * sizeof(realnum)); + delete[] (complex*) slice_collapsed; } else slice = slice_collapsed; @@ -769,7 +778,7 @@ std::vector fields::get_array_metadata(const volume &where) { vec min_max_loc[2]; // extremal points in subgrid get_array_slice_dimensions(where, dims, dirs, true, false, min_max_loc); - double *weights = get_array_slice(where, NO_COMPONENT); + realnum *weights = get_array_slice(where, NO_COMPONENT); /* get length and endpoints of x,y,z tics arrays */ size_t nxyz[3] = {1, 1, 1}; diff --git a/src/dft.cpp b/src/dft.cpp index cb6cd13ad..f97bbe730 100644 --- a/src/dft.cpp +++ b/src/dft.cpp @@ -860,8 +860,8 @@ dft_fields fields::add_dft_fields(component *components, int num_components, con /* chunk-level processing for fields::process_dft_component. */ /***************************************************************/ complex dft_chunk::process_dft_component(int rank, direction *ds, ivec min_corner, ivec max_corner, - int num_freq, h5file *file, double *buffer, int reim, - complex *field_array, void *mode1_data, void *mode2_data, + int num_freq, h5file *file, realnum *buffer, int reim, + complex *field_array, void *mode1_data, void *mode2_data, int ic_conjugate, bool retain_interp_weights, fields *parent) { @@ -1026,7 +1026,7 @@ complex dft_chunk::process_dft_component(int rank, direction *ds, ivec m /* are processed. */ /***************************************************************/ complex fields::process_dft_component(dft_chunk **chunklists, int num_chunklists, int num_freq, - component c, const char *HDF5FileName, complex **pfield_array, + component c, const char *HDF5FileName, complex **pfield_array, int *array_rank, size_t *array_dims, direction *array_dirs, void *mode1_data, void *mode2_data, component c_conjugate, bool *first_component, bool retain_interp_weights) { @@ -1099,15 +1099,15 @@ complex fields::process_dft_component(dft_chunk **chunklists, int num_ch /* buffer for process-local contributions to HDF5 output files,*/ /* like h5_output_data::buf in h5fields.cpp */ /***************************************************************/ - double *buffer = 0; - complex *field_array = 0; + realnum *buffer = 0; + complex *field_array = 0; int reim_max = 0; if (HDF5FileName) { - buffer = new double[bufsz]; + buffer = new realnum[bufsz]; reim_max = 1; } else if (pfield_array) - *pfield_array = field_array = (array_size ? new complex[array_size] : 0); + *pfield_array = field_array = (array_size ? new complex[array_size] : 0); complex overlap = 0.0; for (int reim = 0; reim <= reim_max; reim++) { @@ -1118,7 +1118,7 @@ complex fields::process_dft_component(dft_chunk **chunklists, int num_ch char dataname[100]; snprintf(dataname, 100, "%s_%i.%c", component_name(c), num_freq, reim ? 'i' : 'r'); file->create_or_extend_data(dataname, rank, dims, false /* append_data */, - false /* single_precision */); + sizeof(realnum) == sizeof(float) /* single_precision */); } for (int ncl = 0; ncl < num_chunklists; ncl++) @@ -1139,7 +1139,7 @@ complex fields::process_dft_component(dft_chunk **chunklists, int num_ch /* on all cores */ /***************************************************************/ #define BUFSIZE 1 << 20 // use 1M element (16 MB) buffer - complex *buf = new complex[BUFSIZE]; + complex *buf = new complex[BUFSIZE]; ptrdiff_t offset = 0; size_t remaining = array_size; while (remaining != 0) { @@ -1147,7 +1147,7 @@ complex fields::process_dft_component(dft_chunk **chunklists, int num_ch am_now_working_on(MpiAllTime); sum_to_all(field_array + offset, buf, size); finished_working(); - memcpy(field_array + offset, buf, size * sizeof(complex)); + memcpy(field_array + offset, buf, size * sizeof(complex)); remaining -= size; offset += size; } @@ -1169,46 +1169,46 @@ complex fields::process_dft_component(dft_chunk **chunklists, int num_ch /***************************************************************/ /* routines for fetching arrays of dft fields */ /***************************************************************/ -complex *collapse_array(complex *array, int *rank, size_t dims[3], direction dirs[3], volume where); +complex *collapse_array(complex *array, int *rank, size_t dims[3], direction dirs[3], volume where); -complex *fields::get_dft_array(dft_flux flux, component c, int num_freq, int *rank, - size_t dims[3]) { +complex *fields::get_dft_array(dft_flux flux, component c, int num_freq, int *rank, + size_t dims[3]) { dft_chunk *chunklists[2]; chunklists[0] = flux.E; chunklists[1] = flux.H; - complex *array; + complex *array; direction dirs[3]; process_dft_component(chunklists, 2, num_freq, c, 0, &array, rank, dims, dirs); return collapse_array(array, rank, dims, dirs, flux.where); } -complex *fields::get_dft_array(dft_force force, component c, int num_freq, int *rank, - size_t dims[3]) { +complex *fields::get_dft_array(dft_force force, component c, int num_freq, int *rank, + size_t dims[3]) { dft_chunk *chunklists[3]; chunklists[0] = force.offdiag1; chunklists[1] = force.offdiag2; chunklists[2] = force.diag; - complex *array; + complex *array; direction dirs[3]; process_dft_component(chunklists, 3, num_freq, c, 0, &array, rank, dims, dirs); return collapse_array(array, rank, dims, dirs, force.where); } -complex *fields::get_dft_array(dft_near2far n2f, component c, int num_freq, int *rank, - size_t dims[3]) { +complex *fields::get_dft_array(dft_near2far n2f, component c, int num_freq, int *rank, + size_t dims[3]) { dft_chunk *chunklists[1]; chunklists[0] = n2f.F; - complex *array; + complex *array; direction dirs[3]; process_dft_component(chunklists, 1, num_freq, c, 0, &array, rank, dims, dirs); return collapse_array(array, rank, dims, dirs, n2f.where); } -complex *fields::get_dft_array(dft_fields fdft, component c, int num_freq, int *rank, - size_t dims[3]) { +complex *fields::get_dft_array(dft_fields fdft, component c, int num_freq, int *rank, + size_t dims[3]) { dft_chunk *chunklists[1]; chunklists[0] = fdft.chunks; - complex *array; + complex *array; direction dirs[3]; process_dft_component(chunklists, 1, num_freq, c, 0, &array, rank, dims, dirs); return collapse_array(array, rank, dims, dirs, fdft.where); @@ -1255,7 +1255,7 @@ void fields::output_dft_components(dft_chunk **chunklists, int num_chunklists, v 0, Ex, &first_component); } else { - complex *array = 0; + complex *array = 0; int rank; size_t dims[3]; direction dirs[3]; diff --git a/src/energy_and_flux.cpp b/src/energy_and_flux.cpp index 4514e8553..889785d09 100644 --- a/src/energy_and_flux.cpp +++ b/src/energy_and_flux.cpp @@ -58,10 +58,10 @@ double fields::field_energy_in_box(const volume &where) { return electric_energy_in_box(where) + cur_step_magnetic_energy; } -static complex dot_integrand(const complex *fields, const vec &loc, void *data_) { +static complex dot_integrand(const complex *fields, const vec &loc, void *data_) { (void)loc; (void)data_; // unused; - return real(conj(fields[0]) * fields[1]); + return real(conj(cdouble(fields[0])) * cdouble(fields[1])); } double fields::field_energy_in_box(component c, const volume &where) { @@ -249,12 +249,13 @@ flux_vol *fields::add_flux_plane(const vec &p1, const vec &p2) { is more expensive and requires us to know the boundary orientation, and does not seem worth the trouble at this point. */ -static complex dot3_max_integrand(const complex *fields, const vec &loc, +static complex dot3_max_integrand(const complex *fields, const vec &loc, void *data_) { (void)loc; (void)data_; // unused; - return (real(conj(fields[0]) * fields[3]) + real(conj(fields[1]) * fields[4]) + - real(conj(fields[2]) * fields[5])); + return (real(conj(cdouble(fields[0])) * cdouble(fields[3])) + + real(conj(cdouble(fields[1])) * cdouble(fields[4])) + + real(conj(cdouble(fields[2])) * cdouble(fields[5]))); } double fields::electric_energy_max_in_box(const volume &where) { @@ -294,10 +295,10 @@ double fields::modal_volume_in_box(const volume &where) { typedef double (*fx_func)(const vec &); -static complex dot_fx_integrand(const complex *fields, const vec &loc, +static complex dot_fx_integrand(const complex *fields, const vec &loc, void *data_) { fx_func fx = (fx_func)data_; - return (real(conj(fields[0]) * fields[1]) * fx(loc)); + return (real(conj(cdouble(fields[0])) * cdouble(fields[1])) * fx(loc)); } /* computes integral of f(x) * |E|^2 / integral epsilon*|E|^2 */ diff --git a/src/h5fields.cpp b/src/h5fields.cpp index 672b273d1..d7840f89d 100644 --- a/src/h5fields.cpp +++ b/src/h5fields.cpp @@ -48,7 +48,7 @@ typedef struct { const component *components; component *cS; complex *ph; - complex *fields; + complex *fields; ptrdiff_t *offsets; double frequency; int ninveps; @@ -143,7 +143,8 @@ static void h5_output_chunkloop(fields_chunk *fc, int ichnk, component cgrid, iv ptrdiff_t *off = data->offsets; component *cS = data->cS; - complex *fields = data->fields, *ph = data->ph; + complex *fields = data->fields; + complex *ph = data->ph; double frequency = data->frequency; const component *iecs = data->inveps_cs; const direction *ieds = data->inveps_ds; @@ -267,7 +268,7 @@ void fields::output_hdf5(h5file *file, const char *dataname, int num_fields, data.components = components; data.cS = new component[num_fields]; data.ph = new complex[num_fields]; - data.fields = new complex[num_fields]; + data.fields = new complex[num_fields]; data.fun = fun; data.fun_data_ = fun_data_; @@ -351,7 +352,7 @@ typedef struct { void *fun_data_; } rintegrand_data; -static complex rintegrand_fun(const complex *fields, const vec &loc, void *data_) { +static complex rintegrand_fun(const complex *fields, const vec &loc, void *data_) { rintegrand_data *data = (rintegrand_data *)data_; return data->fun(fields, loc, data->fun_data_); } @@ -374,10 +375,10 @@ void fields::output_hdf5(const char *dataname, int num_fields, const component * /***************************************************************************/ -static complex component_fun(const complex *fields, const vec &loc, void *data_) { +static complex component_fun(const complex *fields, const vec &loc, void *data_) { (void)loc; // unused (void)data_; // unused - return fields[0]; + return cdouble(fields[0]); } void fields::output_hdf5(component c, const volume &where, h5file *file, bool append_data, diff --git a/src/integrate.cpp b/src/integrate.cpp index 7445df910..c94a63e09 100644 --- a/src/integrate.cpp +++ b/src/integrate.cpp @@ -29,7 +29,7 @@ struct integrate_data { const component *components; component *cS; complex *ph; - complex *fvals; + complex *fvals; ptrdiff_t *offsets; int ninveps; component inveps_cs[3]; @@ -37,7 +37,7 @@ struct integrate_data { int ninvmu; component invmu_cs[3]; direction invmu_ds[3]; - complex sum; + complex sum; double maxabs; field_function integrand; void *integrand_data_; @@ -51,7 +51,8 @@ static void integrate_chunkloop(fields_chunk *fc, int ichunk, component cgrid, i integrate_data *data = (integrate_data *)data_; ptrdiff_t *off = data->offsets; component *cS = data->cS; - complex *fvals = data->fvals, *ph = data->ph; + complex *fvals = data->fvals; + complex *ph = data->ph; complex sum = 0.0; double maxabs = 0; const component *iecs = data->inveps_cs; @@ -146,7 +147,7 @@ complex fields::integrate(int num_fvals, const component *components, data.components = components; data.cS = new component[num_fvals]; data.ph = new complex[num_fvals]; - data.fvals = new complex[num_fvals]; + data.fvals = new complex[num_fvals]; data.sum = 0; data.maxabs = 0; data.integrand = integrand; @@ -196,14 +197,15 @@ complex fields::integrate(int num_fvals, const component *components, if (maxabs) *maxabs = max_to_all(data.maxabs); data.sum = sum_to_all(data.sum); - return complex(real(data.sum), imag(data.sum)); + return cdouble(data.sum); } typedef struct { field_rfunction integrand; void *integrand_data; } rfun_wrap_data; -static complex rfun_wrap(const complex *fvals, const vec &loc, void *data_) { + +static complex rfun_wrap(const complex *fvals, const vec &loc, void *data_) { rfun_wrap_data *data = (rfun_wrap_data *)data_; return data->integrand(fvals, loc, data->integrand_data); } @@ -231,11 +233,11 @@ double fields::max_abs(int num_fvals, const component *components, field_rfuncti return max_abs(num_fvals, components, rfun_wrap, &data, where); } -static complex return_the_field(const complex *fields, const vec &loc, - void *integrand_data_) { +static complex return_the_field(const complex *fields, const vec &loc, + void *integrand_data_) { (void)integrand_data_; (void)loc; // unused - return fields[0]; + return cdouble(fields[0]); } double fields::max_abs(int c, const volume &where) { diff --git a/src/integrate2.cpp b/src/integrate2.cpp index 48e255d74..f8671987c 100644 --- a/src/integrate2.cpp +++ b/src/integrate2.cpp @@ -35,7 +35,7 @@ struct integrate_data { const component *components2; component *cS; complex *ph; - complex *fvals; + complex *fvals; ptrdiff_t *offsets; int ninveps; component inveps_cs[3]; @@ -43,7 +43,7 @@ struct integrate_data { int ninvmu; component invmu_cs[3]; direction invmu_ds[3]; - complex sum; + complex sum; double maxabs; field_function integrand; void *integrand_data_; @@ -57,7 +57,8 @@ static void integrate_chunkloop(fields_chunk *fc, int ichunk, component cgrid, i integrate_data *data = (integrate_data *)data_; ptrdiff_t *off = data->offsets; component *cS = data->cS; - complex *fvals = data->fvals, *ph = data->ph; + complex *fvals = data->fvals; + complex *ph = data->ph; complex sum = 0.0; double maxabs = 0; const component *iecs = data->inveps_cs; @@ -223,7 +224,7 @@ complex fields::integrate2(const fields &fields2, int num_fvals1, data.components2 = components2; data.cS = new component[num_fvals1 + num_fvals2]; data.ph = new complex[num_fvals1 + num_fvals2]; - data.fvals = new complex[num_fvals1 + num_fvals2]; + data.fvals = new complex[num_fvals1 + num_fvals2]; data.sum = 0; data.maxabs = 0; data.integrand = integrand; @@ -285,14 +286,15 @@ complex fields::integrate2(const fields &fields2, int num_fvals1, if (maxabs) *maxabs = max_to_all(data.maxabs); data.sum = sum_to_all(data.sum); - return complex(real(data.sum), imag(data.sum)); + return cdouble(data.sum); } typedef struct { field_rfunction integrand; void *integrand_data; } rfun_wrap_data; -static complex rfun_wrap(const complex *fields, const vec &loc, void *data_) { + +static complex rfun_wrap(const complex *fields, const vec &loc, void *data_) { rfun_wrap_data *data = (rfun_wrap_data *)data_; return data->integrand(fields, loc, data->integrand_data); } diff --git a/src/meep.hpp b/src/meep.hpp index 488c517b9..6c15378a2 100644 --- a/src/meep.hpp +++ b/src/meep.hpp @@ -60,6 +60,10 @@ const double nan = NAN; const double nan = -7.0415659787563146e103; // ideally, a value never encountered in practice #endif +// Defined in array_slice.cpp +std::complex cdouble(std::complex z); +std::complex cdouble(std::complex z); + class h5file; // Defined in monitor.cpp @@ -1126,8 +1130,8 @@ class dft_chunk { // fields::process_dft_component std::complex process_dft_component(int rank, direction *ds, ivec min_corner, ivec max_corner, int num_freq, h5file *file, - double *buffer, int reim, - std::complex *field_array, void *mode1_data, + realnum *buffer, int reim, + std::complex *field_array, void *mode1_data, void *mode2_data, int ic_conjugate, bool retain_interp_weights, fields *parent); @@ -1627,9 +1631,9 @@ typedef void (*field_chunkloop)(fields_chunk *fc, int ichunk, component cgrid, i vec s0, vec s1, vec e0, vec e1, double dV0, double dV1, ivec shift, std::complex shift_phase, const symmetry &S, int sn, void *chunkloop_data); -typedef std::complex (*field_function)(const std::complex *fields, const vec &loc, +typedef std::complex (*field_function)(const std::complex *fields, const vec &loc, void *integrand_data_); -typedef double (*field_rfunction)(const std::complex *fields, const vec &loc, +typedef double (*field_rfunction)(const std::complex *fields, const vec &loc, void *integrand_data_); field_rfunction derived_component_func(derived_component c, const grid_volume &gv, int &nfields, @@ -1812,33 +1816,33 @@ class fields { // of the correct size. // otherwise, a new buffer is allocated and returned; it // must eventually be caller-deallocated via delete[]. - double *get_array_slice(const volume &where, std::vector components, - field_rfunction rfun, void *fun_data, double *slice = 0, - double frequency = 0, - bool snap = false); - - std::complex *get_complex_array_slice(const volume &where, - std::vector components, - field_function fun, void *fun_data, - std::complex *slice = 0, - double frequency = 0, - bool snap = false); + realnum *get_array_slice(const volume &where, std::vector components, + field_rfunction rfun, void *fun_data, realnum *slice = 0, + double frequency = 0, + bool snap = false); + + std::complex *get_complex_array_slice(const volume &where, + std::vector components, + field_function fun, void *fun_data, + std::complex *slice = 0, + double frequency = 0, + bool snap = false); // alternative entry points for when you have no field // function, i.e. you want just a single component or // derived component.) - double *get_array_slice(const volume &where, component c, double *slice = 0, - double frequency = 0, bool snap = false); - double *get_array_slice(const volume &where, derived_component c, double *slice = 0, - double frequency = 0, bool snap = false); - std::complex *get_complex_array_slice(const volume &where, component c, - std::complex *slice = 0, - double frequency = 0, - bool snap = false); + realnum *get_array_slice(const volume &where, component c, realnum *slice = 0, + double frequency = 0, bool snap = false); + realnum *get_array_slice(const volume &where, derived_component c, realnum *slice = 0, + double frequency = 0, bool snap = false); + std::complex *get_complex_array_slice(const volume &where, component c, + std::complex *slice = 0, + double frequency = 0, + bool snap = false); // like get_array_slice, but for *sources* instead of fields - std::complex *get_source_slice(const volume &where, component source_slice_component, - std::complex *slice = 0); + std::complex *get_source_slice(const volume &where, component source_slice_component, + std::complex *slice = 0); // master routine for all above entry points void *do_get_array_slice(const volume &where, std::vector components, @@ -2079,7 +2083,7 @@ class fields { /********************************************************/ std::complex process_dft_component(dft_chunk **chunklists, int num_chunklists, int num_freq, component c, const char *HDF5FileName, - std::complex **field_array = 0, int *rank = 0, + std::complex **field_array = 0, int *rank = 0, size_t *dims = 0, direction *dirs = 0, void *mode1_data = 0, void *mode2_data = 0, component c_conjugate = Ex, bool *first_component = 0, @@ -2095,14 +2099,14 @@ class fields { void output_dft(dft_fields fdft, const char *HDF5FileName); // get array of DFT field values - std::complex *get_dft_array(dft_flux flux, component c, int num_freq, int *rank, - size_t dims[3]); - std::complex *get_dft_array(dft_fields fdft, component c, int num_freq, int *rank, - size_t dims[3]); - std::complex *get_dft_array(dft_force force, component c, int num_freq, int *rank, - size_t dims[3]); - std::complex *get_dft_array(dft_near2far n2f, component c, int num_freq, int *rank, - size_t dims[3]); + std::complex *get_dft_array(dft_flux flux, component c, int num_freq, int *rank, + size_t dims[3]); + std::complex *get_dft_array(dft_fields fdft, component c, int num_freq, int *rank, + size_t dims[3]); + std::complex *get_dft_array(dft_force force, component c, int num_freq, int *rank, + size_t dims[3]); + std::complex *get_dft_array(dft_near2far n2f, component c, int num_freq, int *rank, + size_t dims[3]); // overlap integrals between eigenmode fields and DFT flux fields void get_overlap(void *mode1_data, void *mode2_data, dft_flux flux, int num_freq, diff --git a/src/meep/mympi.hpp b/src/meep/mympi.hpp index ac71bd1ed..1f29200ea 100644 --- a/src/meep/mympi.hpp +++ b/src/meep/mympi.hpp @@ -76,12 +76,14 @@ int min_to_all(int); float sum_to_master(float); // Only returns the correct value to proc 0. double sum_to_master(double); // Only returns the correct value to proc 0. double sum_to_all(double); +void sum_to_all(const float *in, float *out, int size); void sum_to_all(const double *in, double *out, int size); void sum_to_master(const float *in, float *out, int size); void sum_to_master(const double *in, double *out, int size); void sum_to_all(const float *in, double *out, int size); void sum_to_all(const std::complex *in, std::complex *out, int size); void sum_to_all(const std::complex *in, std::complex *out, int size); +void sum_to_all(const std::complex *in, std::complex *out, int size); void sum_to_master(const std::complex *in, std::complex *out, int size); void sum_to_master(const std::complex *in, std::complex *out, int size); long double sum_to_all(long double); diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index a5fdfb56f..496012681 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -2678,10 +2678,10 @@ std::complex get_material_gradient( else meep::abort("Invalid adjoint field component"); - if (md->trivial){ + if (md->trivial) { const double sd = 1.0; // FIXME: make user-changable? meep::volume v(r); - LOOP_OVER_DIRECTIONS(dim, d){ + LOOP_OVER_DIRECTIONS(dim, d) { v.set_direction_min(d, r.in_direction(d) - 0.5*gv.inva*sd); v.set_direction_max(d, r.in_direction(d) + 0.5*gv.inva*sd); } @@ -2695,8 +2695,7 @@ std::complex get_material_gradient( for (int i=0;i<3;i++) dA_du[i] = (row_1[i] - row_2[i])/(2*du); return dA_du[dir_idx] * fields_f; - - }else{ + } else { double orig = u[idx]; std::complex row_1[3], row_2[3], dA_du[3]; u[idx] -= du; @@ -2707,7 +2706,7 @@ std::complex get_material_gradient( for (int i=0;i<3;i++) dA_du[i] = (row_1[i] - row_2[i])/(2*du); return dA_du[dir_idx] * fields_f * cond_cmp(forward_c,r,freq,geps); - } + } } /* A brute force approach to calculating Aᵤ using finite differences. @@ -2715,17 +2714,15 @@ Past versions of the code only calculated dA/dε using a finite difference, and then multiplied by the analytic vJp (dε/du). With the addition of subpixel smoothing, however, the vJp became much more complicated and it is easier to calculate the entire gradient -using finite differences (at the cost of slightly less accurate gradients -due to roundoff). - */ +using finite differences (at the cost of slightly less accurate gradients +due to floating-point roundoff errors). */ void add_interpolate_weights(double rx, double ry, double rz, double *data, int nx, int ny, int nz, int stride, double scaleby, double *udata, int ukind, double uval, - meep::vec r, geom_epsilon *geps, + meep::vec r, geom_epsilon *geps, meep::component adjoint_c, meep::component forward_c, - std::complex fwd, std::complex adj, - double freq, meep::grid_volume &gv, double du - ) { + std::complex fwd, std::complex adj, + double freq, meep::grid_volume &gv, double du) { int x1, y1, z1, x2, y2, z2; double dx, dy, dz, u; @@ -2773,8 +2770,7 @@ in row-major order (the order used by HDF5): */ void material_grids_addgradient_point(double *v, vector3 p, double scalegrad, geom_epsilon *geps, meep::component adjoint_c, meep::component forward_c, std::complex fwd, std::complex adj, - double freq, meep::grid_volume &gv, double tol - ) { + double freq, meep::grid_volume &gv, double tol) { geom_box_tree tp; int oi, ois; material_data *mg, *mg_sum; @@ -2827,12 +2823,10 @@ void material_grids_addgradient_point(double *v, vector3 p, double scalegrad, ge uval = tanh_projection(matgrid_val(p, tp, oi, mg), mg->beta, mg->eta); do { vector3 pb = to_geom_box_coords(p, &tp->objects[oi]); - add_interpolate_weights( - pb.x, pb.y, pb.z, vcur, sz.x, sz.y, sz.z, 1, - scalegrad,ucur, kind, uval, - vector3_to_vec(p),geps,adjoint_c,forward_c, - fwd,adj,freq,gv,tol - ); + add_interpolate_weights(pb.x, pb.y, pb.z, vcur, sz.x, sz.y, sz.z, 1, + scalegrad, ucur, kind, uval, + vector3_to_vec(p), geps, adjoint_c, forward_c, + fwd, adj, freq, gv, tol); if (kind == material_data::U_DEFAULT) break; tp = geom_tree_search_next(p, tp, &oi); } while (tp && is_material_grid((material_data *)tp->objects[oi].o->material)); @@ -2846,8 +2840,8 @@ void material_grids_addgradient_point(double *v, vector3 p, double scalegrad, ge uval = tanh_projection(material_grid_val(p, mg), mg->beta, mg->eta); add_interpolate_weights(p.x, p.y, p.z, vcur, sz.x, sz.y, sz.z, 1, scalegrad, ucur, kind, uval, - vector3_to_vec(p),geps,adjoint_c,forward_c, - fwd,adj,freq,gv,tol); + vector3_to_vec(p), geps, adjoint_c, forward_c, + fwd, adj, freq, gv, tol); } } @@ -2855,15 +2849,15 @@ void material_grids_addgradient_point(double *v, vector3 p, double scalegrad, ge /* Some gradient helper routines */ /**********************************************************/ -#define LOOP_OVER_DIRECTIONS_BACKWARDS(dim, d) \ - for (meep::direction d = (meep::direction)(meep::stop_at_direction(dim)-1), \ - loop_stop_directi = meep::start_at_direction(dim); \ +#define LOOP_OVER_DIRECTIONS_BACKWARDS(dim, d) \ + for (meep::direction d = (meep::direction)(meep::stop_at_direction(dim)-1), \ + loop_stop_directi = meep::start_at_direction(dim); \ d >= loop_stop_directi; d = (meep::direction)(d - 1)) void set_strides(meep::ndim dim, ptrdiff_t *the_stride,const meep::ivec c1,const meep::ivec c2) { FOR_DIRECTIONS(d) { the_stride[d] = 1;} meep::ivec n_s = (c2 - c1)/2 + 1; - LOOP_OVER_DIRECTIONS_BACKWARDS(dim,d) { + LOOP_OVER_DIRECTIONS_BACKWARDS(dim,d) { ptrdiff_t current_stride = 1; LOOP_OVER_DIRECTIONS_BACKWARDS(dim,d_i) { if (d_i==d){ @@ -2872,23 +2866,23 @@ void set_strides(meep::ndim dim, ptrdiff_t *the_stride,const meep::ivec c1,const } current_stride *= n_s.in_direction(d_i); } - } + } } ptrdiff_t get_idx_from_ivec(meep::ndim dim, meep::ivec c1, ptrdiff_t *the_stride, meep::ivec v){ ptrdiff_t idx = 0; meep::ivec diff = ((v-c1) / 2); - LOOP_OVER_DIRECTIONS(dim,d){ + LOOP_OVER_DIRECTIONS(dim,d) { idx += diff.in_direction(d)*the_stride[d]; } return idx; } -void material_grids_addgradient(double *v, size_t ng, std::complex *fields_a, - std::complex *fields_f, size_t fields_shapes[12], double *frequencies, - double scalegrad, meep::grid_volume &gv, +void material_grids_addgradient(double *v, size_t ng, std::complex *fields_a, + std::complex *fields_f, size_t fields_shapes[12], + double *frequencies, double scalegrad, meep::grid_volume &gv, meep::volume &where, geom_epsilon *geps, double du) { - + // only loop over field components relevant to our simulation (in the proper order) #define FOR_MY_COMPONENTS(c1) FOR_ELECTRIC_COMPONENTS(c1) if (!coordinate_mismatch(gv.dim, component_direction(c1))) @@ -2915,10 +2909,10 @@ void material_grids_addgradient(double *v, size_t ng, std::complex *fiel } } size_t c_start[3]; - c_start[0] = 0; - c_start[1] = nf*stride_row[0]; + c_start[0] = 0; + c_start[1] = nf*stride_row[0]; c_start[2] = nf*(stride_row[0]+stride_row[1]); - + // fields dimensions are (components, nfreqs, x, y, z) #define GET_FIELDS(fields,comp,freq,idx) fields[c_start[comp] + freq*stride_row[comp] + idx] @@ -2926,22 +2920,20 @@ void material_grids_addgradient(double *v, size_t ng, std::complex *fiel // loop over frequency for (size_t f_i = 0; f_i < nf; ++f_i) { int ci_adjoint = 0; - FOR_MY_COMPONENTS(adjoint_c) { + FOR_MY_COMPONENTS(adjoint_c) { LOOP_OVER_IVECS(gv, is[ci_adjoint], ie[ci_adjoint], idx) { size_t idx_fields = IVEC_LOOP_COUNTER; meep::ivec ip = gv.iloc(adjoint_c,idx); meep::vec p = gv.loc(adjoint_c,idx); - std::complex adj = GET_FIELDS(fields_a,ci_adjoint,f_i,idx_fields); - + std::complex adj = GET_FIELDS(fields_a, ci_adjoint, f_i, idx_fields); material_type md; geps->get_material_pt(md, p); if (!md->trivial) adj *= cond_cmp(adjoint_c,p,frequencies[f_i], geps); - double cyl_scale; int ci_forward = 0; FOR_MY_COMPONENTS(forward_c) { - /* we need to calculate the bounds of - the forward fields (in space) so that we + /* we need to calculate the bounds of + the forward fields (in space) so that we can properly index into the fields array later */ meep::ivec isf = is[ci_forward]; @@ -2959,20 +2951,20 @@ void material_grids_addgradient(double *v, size_t ng, std::complex *fiel /********* compute -λᵀAᵤx *************/ /* trivial case, no interpolation/restriction needed */ - if (forward_c == adjoint_c){ - std::complex fwd = GET_FIELDS(fields_f,ci_forward,f_i,idx_fields); + if (forward_c == adjoint_c) { + std::complex fwd = GET_FIELDS(fields_f, ci_forward, f_i, idx_fields); cyl_scale = (gv.dim == meep::Dcyl) ? 2*p.r() : 1; // the pi is already factored in near2far.cpp material_grids_addgradient_point( - v+ng*f_i, vec_to_vector3(p), scalegrad*cyl_scale, geps, - adjoint_c, forward_c, fwd, adj, frequencies[f_i], gv, du); + v+ng*f_i, vec_to_vector3(p), scalegrad*cyl_scale, geps, + adjoint_c, forward_c, fwd, adj, frequencies[f_i], gv, du); /* more complicated case requires interpolation/restriction */ - }else{ + } else { /* we need to restrict the adjoint fields to the two nodes of interest (which requires a factor - of 0.5 to scale), and interpolate the forward fields - to the same two nodes (which requires another factor of 0.5). + of 0.5 to scale), and interpolate the forward fields + to the same two nodes (which requires another factor of 0.5). Then we perform our inner product at these nodes. - */ + */ std::complex fwd_avg, fwd1, fwd2, prod; ptrdiff_t fwd1_idx, fwd2_idx; @@ -2985,34 +2977,34 @@ void material_grids_addgradient(double *v, size_t ng, std::complex *fiel meep::ivec fwd_pa = (fwd_p + unit_a*2); meep::ivec fwd_pf = (fwd_p - unit_f*2); meep::ivec fwd_paf = (fwd_p + unit_a*2 - unit_f*2); - + //identify the two eps points meep::ivec ieps1 = (fwd_p + fwd_pf) / 2; meep::ivec ieps2 = (fwd_pa + fwd_paf) / 2; //operate on the first eps node fwd1_idx = get_idx_from_ivec(gv.dim, start_ivec, the_stride, fwd_p); - fwd1 = 0.5 * GET_FIELDS(fields_f,ci_forward,f_i,fwd1_idx); + fwd1 = 0.5 * meep::cdouble(GET_FIELDS(fields_f, ci_forward, f_i, fwd1_idx)); fwd2_idx = get_idx_from_ivec(gv.dim, start_ivec, the_stride, fwd_pf); - fwd2 = 0.5 * GET_FIELDS(fields_f,ci_forward,f_i,fwd2_idx); + fwd2 = 0.5 * meep::cdouble(GET_FIELDS(fields_f, ci_forward, f_i, fwd2_idx)); fwd_avg = fwd1 + fwd2; meep::vec eps1 = gv[ieps1]; cyl_scale = (gv.dim == meep::Dcyl) ? eps1.r() : 1; material_grids_addgradient_point( - v+ng*f_i, vec_to_vector3(eps1), scalegrad*cyl_scale, geps, - adjoint_c, forward_c, fwd_avg, 0.5*adj, frequencies[f_i], gv, du); - + v+ng*f_i, vec_to_vector3(eps1), scalegrad*cyl_scale, geps, + adjoint_c, forward_c, fwd_avg, 0.5*adj, frequencies[f_i], gv, du); + //operate on the second eps node fwd1_idx = get_idx_from_ivec(gv.dim, start_ivec, the_stride, fwd_pa); - fwd1 = 0.5 * GET_FIELDS(fields_f,ci_forward,f_i,fwd1_idx); + fwd1 = 0.5 * meep::cdouble(GET_FIELDS(fields_f, ci_forward, f_i, fwd1_idx)); fwd2_idx = get_idx_from_ivec(gv.dim, start_ivec, the_stride, fwd_paf); - fwd2 = 0.5 * GET_FIELDS(fields_f,ci_forward,f_i,fwd2_idx); + fwd2 = 0.5 * meep::cdouble(GET_FIELDS(fields_f, ci_forward, f_i, fwd2_idx)); fwd_avg = fwd1 + fwd2; meep::vec eps2 = gv[ieps2]; cyl_scale = (gv.dim == meep::Dcyl) ? eps2.r() : 1; material_grids_addgradient_point( - v+ng*f_i, vec_to_vector3(eps2), scalegrad*cyl_scale, geps, - adjoint_c, forward_c, fwd_avg, 0.5*adj, frequencies[f_i], gv, du); + v+ng*f_i, vec_to_vector3(eps2), scalegrad*cyl_scale, geps, + adjoint_c, forward_c, fwd_avg, 0.5*adj, frequencies[f_i], gv, du); } ci_forward++; } diff --git a/src/meepgeom.hpp b/src/meepgeom.hpp index f66c33e3b..bb2f0d132 100644 --- a/src/meepgeom.hpp +++ b/src/meepgeom.hpp @@ -296,10 +296,10 @@ meep::vec material_grid_grad(vector3 p, material_data *md, const geometric_objec double matgrid_val(vector3 p, geom_box_tree tp, int oi, material_data *md); double material_grid_val(vector3 p, material_data *md); geom_box_tree calculate_tree(const meep::volume &v, geometric_object_list g); -void material_grids_addgradient(double *v, size_t ng, std::complex *fields_a, - std::complex *fields_f, size_t fields_shapes[12], - double *frequencies, double scalegrad, - meep::grid_volume &gv, meep::volume &where, geom_epsilon *geps,double du=1e-6); +void material_grids_addgradient(double *v, size_t ng, std::complex *fields_a, + std::complex *fields_f, size_t fields_shapes[12], + double *frequencies, double scalegrad, meep::grid_volume &gv, + meep::volume &where, geom_epsilon *geps, double du = 1e-6); /***************************************************************/ /* routines in GDSIIgeom.cc ************************************/ diff --git a/src/mympi.cpp b/src/mympi.cpp index 8ab321dbf..331def6e6 100644 --- a/src/mympi.cpp +++ b/src/mympi.cpp @@ -441,6 +441,14 @@ double sum_to_all(double in) { return out; } +void sum_to_all(const float *in, float *out, int size) { +#ifdef HAVE_MPI + MPI_Allreduce((void *)in, out, size, MPI_FLOAT, MPI_SUM, mycomm); +#else + memcpy(out, in, sizeof(float) * size); +#endif +} + void sum_to_all(const double *in, double *out, int size) { #ifdef HAVE_MPI MPI_Allreduce((void *)in, out, size, MPI_DOUBLE, MPI_SUM, mycomm); @@ -481,6 +489,10 @@ void sum_to_all(const complex *in, complex *out, int size) { sum_to_all((const float *)in, (double *)out, 2 * size); } +void sum_to_all(const complex *in, complex *out, int size) { + sum_to_all((const float *)in, (float *)out, 2 * size); +} + void sum_to_master(const complex *in, complex *out, int size) { sum_to_master((const float *)in, (float *)out, 2 * size); } diff --git a/src/vec.cpp b/src/vec.cpp index 69e809e02..e4bf55a92 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -1479,18 +1479,19 @@ volume_list *symmetry::reduce(const volume_list *gl) const { /***************************************************************************/ -static double poynting_fun(const complex *fields, const vec &loc, void *data_) { +static double poynting_fun(const complex *fields, const vec &loc, void *data_) { (void)loc; // unused (void)data_; // unused - return (real(conj(fields[0]) * fields[1]) - real(conj(fields[2]) * fields[3])); + return (real(conj(cdouble(fields[0])) * cdouble(fields[1])) - + real(conj(cdouble(fields[2])) * cdouble(fields[3]))); } -static double energy_fun(const complex *fields, const vec &loc, void *data_) { +static double energy_fun(const complex *fields, const vec &loc, void *data_) { (void)loc; // unused int nfields = *((int *)data_) / 2; double sum = 0; for (int k = 0; k < nfields; ++k) - sum += real(conj(fields[2 * k]) * fields[2 * k + 1]); + sum += real(conj(cdouble(fields[2 * k])) * cdouble(fields[2 * k + 1])); return sum * 0.5; } diff --git a/tests/array-slice-ll.cpp b/tests/array-slice-ll.cpp index af754384b..ea4e9aded 100644 --- a/tests/array-slice-ll.cpp +++ b/tests/array-slice-ll.cpp @@ -230,7 +230,7 @@ int main(int argc, char *argv[]) { // rank = f.get_array_slice_dimensions(v1d, dims1D, dirs1D, true, false); if (rank != 1 || dims1D[0] != NX) meep::abort("incorrect dimensions for 1D slice"); - std::unique_ptr []> slice1d(f.get_complex_array_slice(v1d, Hz, 0, 0, true)); + std::unique_ptr []> slice1d(f.get_complex_array_slice(v1d, Hz, 0, 0, true)); std::vector> slice1d_realnum; for (int i = 0; i < NX; ++i) slice1d_realnum.emplace_back(slice1d[i]); @@ -239,7 +239,7 @@ int main(int argc, char *argv[]) { rank = f.get_array_slice_dimensions(v2d, dims2D, dirs2D, true, false); if (rank != 2 || dims2D[0] != NX || dims2D[1] != NY) meep::abort("incorrect dimensions for 2D slice"); - std::unique_ptr slice2d(f.get_array_slice(v2d, Sy, 0, 0, true)); + std::unique_ptr slice2d(f.get_array_slice(v2d, Sy, 0, 0, true)); std::unique_ptr slice2d_realnum(new realnum[NX * NY]); for (int i = 0; i < NX * NY; ++i) slice2d_realnum[i] = static_cast(slice2d[i]); diff --git a/tests/dft-fields.cpp b/tests/dft-fields.cpp index 241866703..6e1e44746 100644 --- a/tests/dft-fields.cpp +++ b/tests/dft-fields.cpp @@ -15,8 +15,6 @@ using namespace meep; -typedef std::complex cdouble; - vector3 v3(double x, double y = 0.0, double z = 0.0) { vector3 v; v.x = x; @@ -35,7 +33,7 @@ double dummy_eps(const vec &) { return 1.0; } /***************************************************************/ /***************************************************************/ /***************************************************************/ -void Run(bool Pulse, double resolution, cdouble **field_array = 0, int *array_rank = 0, +void Run(bool Pulse, double resolution, std::complex **field_array = 0, int *array_rank = 0, size_t *array_dims = 0) { /***************************************************************/ /* initialize geometry */ @@ -104,7 +102,7 @@ void Run(bool Pulse, double resolution, cdouble **field_array = 0, int *array_ra /***************************************************************/ /* return L2 norm of error normalized by average of L2 norms */ /***************************************************************/ -double compare_array_to_dataset(cdouble *field_array, int array_rank, size_t *array_dims, +double compare_array_to_dataset(std::complex *field_array, int array_rank, size_t *array_dims, const char *file, const char *name) { int file_rank; size_t file_dims[3]; @@ -121,8 +119,8 @@ double compare_array_to_dataset(cdouble *field_array, int array_rank, size_t *ar double NormArray = 0.0, NormFile = 0.0, NormDelta = 0.0; for (size_t n = 0; n < file_dims[0] * file_dims[1]; n++) { - cdouble zArray = field_array[n]; - cdouble zFile = cdouble(rdata[n], idata[n]); + std::complex zArray = field_array[n]; + std::complex zFile = std::complex(rdata[n], idata[n]); NormArray += norm(zArray); NormFile += norm(zFile); NormDelta += norm(zArray - zFile); @@ -181,12 +179,12 @@ double compare_complex_hdf5_datasets(const char *file1, const char *name1, const double max_abs1 = 0.0, max_abs2 = 0.0; double max_arg1 = 0.0, max_arg2 = 0.0; for (size_t n = 0; n < length; n++) { - cdouble z1 = cdouble(rdata1[n], idata1[n]); + std::complex z1 = std::complex(rdata1[n], idata1[n]); if (abs(z1) > max_abs1) { max_abs1 = abs(z1); max_arg1 = arg(z1); } - cdouble z2 = cdouble(rdata2[n], idata2[n]); + std::complex z2 = std::complex(rdata2[n], idata2[n]); if (abs(z2) > max_abs2) { max_abs2 = abs(z2); max_arg2 = arg(z2); @@ -196,11 +194,11 @@ double compare_complex_hdf5_datasets(const char *file1, const char *name1, const // second pass to get L2 norm of difference between normalized data sets double norm1 = 0.0, norm2 = 0.0, normdiff = 0.0; - cdouble phase1 = exp(-cdouble(0, 1) * max_arg1); - cdouble phase2 = exp(-cdouble(0, 1) * max_arg2); + std::complex phase1 = exp(-std::complex(0, 1) * max_arg1); + std::complex phase2 = exp(-std::complex(0, 1) * max_arg2); for (size_t n = 0; n < length; n++) { - cdouble z1 = phase1 * cdouble(rdata1[n], idata1[n]) / max_abs1; - cdouble z2 = phase2 * cdouble(rdata2[n], idata2[n]) / max_abs2; + std::complex z1 = phase1 * std::complex(rdata1[n], idata1[n]) / max_abs1; + std::complex z2 = phase2 * std::complex(rdata2[n], idata2[n]) / max_abs2; norm1 += norm(z1); norm2 += norm(z2); normdiff += norm(z1 - z2); @@ -235,7 +233,7 @@ int main(int argc, char *argv[]) { meep::abort("unknown argument %s", argv[narg]); } - cdouble *field_array = 0; + std::complex *field_array = 0; int array_rank; size_t array_dims[3]; Run(true, resolution, &field_array, &array_rank, array_dims); diff --git a/tests/integrate.cpp b/tests/integrate.cpp index 8cada6a87..2bcf84549 100644 --- a/tests/integrate.cpp +++ b/tests/integrate.cpp @@ -41,7 +41,7 @@ typedef struct { } linear_integrand_data; /* integrand for integrating c + ax*x + ay*y + az*z. */ -static complex linear_integrand(const complex *fields, const vec &loc, +static complex linear_integrand(const complex *fields, const vec &loc, void *data_) { linear_integrand_data *data = (linear_integrand_data *)data_; @@ -190,7 +190,7 @@ void check_integral(fields &f, linear_integrand_data &d, const volume &v, compon double sum = real(f.integrate(0, 0, linear_integrand, (void *)&d, v)); if (fabs(sum - correct_integral(v, d)) > 1e-9 * fabs(sum)) - meep::abort("FAILED: %0.16g instead of %0.16g\n", (double)sum, correct_integral(v, d)); + meep::abort("FAILED: %0.16g instead of %0.16g\n", sum, correct_integral(v, d)); master_printf("...PASSED.\n"); } diff --git a/tests/pw-source-ll.cpp b/tests/pw-source-ll.cpp index dd61c8ae7..56b98007a 100644 --- a/tests/pw-source-ll.cpp +++ b/tests/pw-source-ll.cpp @@ -22,8 +22,6 @@ using namespace meep; -typedef std::complex cdouble; - /***************************************************************/ /* ; pw-amp is a function that returns the amplitude exp(ik(x+x0)) at a @@ -40,12 +38,12 @@ typedef struct pw_amp_data { vec x0; } pw_amp_data; -cdouble pw_amp(vec x, void *UserData) { +std::complex pw_amp(vec x, void *UserData) { pw_amp_data *data = (pw_amp_data *)UserData; vec k = data->k; vec x0 = data->x0; - const cdouble II(0.0, 1.0); + const std::complex II(0.0, 1.0); return exp(II * (k & (x + x0))); } @@ -56,10 +54,10 @@ cdouble pw_amp(vec x, void *UserData) { /* amplitude functions and global variables. */ /***************************************************************/ pw_amp_data pw_amp_data_left; -cdouble pw_amp_left(const vec &x) { return pw_amp(x, (void *)&pw_amp_data_left); } +std::complex pw_amp_left(const vec &x) { return pw_amp(x, (void *)&pw_amp_data_left); } pw_amp_data pw_amp_data_bottom; -cdouble pw_amp_bottom(const vec &x) { return pw_amp(x, (void *)&pw_amp_data_bottom); } +std::complex pw_amp_bottom(const vec &x) { return pw_amp(x, (void *)&pw_amp_data_bottom); } /***************************************************************/ /* dummy material function needed to pass to structure( ) */ diff --git a/tests/ring-ll.cpp b/tests/ring-ll.cpp index 321251cba..e28e6d07b 100644 --- a/tests/ring-ll.cpp +++ b/tests/ring-ll.cpp @@ -21,8 +21,6 @@ using namespace meep; -typedef std::complex cdouble; - vector3 v3(double x, double y = 0.0, double z = 0.0) { vector3 v; v.x = x; @@ -109,7 +107,7 @@ int main(int argc, char *argv[]) { double T = 300.0; double stop_time = f.round_time() + T; - std::vector fieldData; + std::vector> fieldData; vec eval_pt(r + 0.1, 0.0); while (f.round_time() < stop_time) { f.step(); @@ -117,7 +115,7 @@ int main(int argc, char *argv[]) { }; #define MAXBANDS 100 - cdouble amp[MAXBANDS]; + std::complex amp[MAXBANDS]; double freq_re[MAXBANDS]; double freq_im[MAXBANDS]; double err[MAXBANDS]; @@ -137,8 +135,8 @@ int main(int argc, char *argv[]) { int ref_bands = 3; double ref_freq_re[3] = {1.1807e-01, 1.4716e-01, 1.7525e-01}; double ref_freq_im[3] = {-7.6133e-04, -2.1156e-04, -5.2215e-05}; - cdouble ref_amp[3] = {cdouble(-8.28e-04, -1.34e-03), cdouble(1.23e-03, -1.25e-02), - cdouble(2.83e-03, -6.52e-04)}; + std::complex ref_amp[3] = {std::complex(-8.28e-04, -1.34e-03), std::complex(1.23e-03, -1.25e-02), + std::complex(2.83e-03, -6.52e-04)}; if (bands != 3) meep::abort("harminv found only %i/%i bands\n", bands, ref_bands); for (int nb = 0; nb < bands; nb++) if (fabs(freq_re[nb] - ref_freq_re[nb]) > 1.0e-2 * fabs(ref_freq_re[nb]) || From 5197f83fae0ad147cb67de6731d442772c9540b3 Mon Sep 17 00:00:00 2001 From: Krishna Gadepalli <34969407+kkg4theweb@users.noreply.github.com> Date: Thu, 2 Dec 2021 04:47:51 -0800 Subject: [PATCH 026/155] Fix memory leaks (#1839) * Fix memory leaks * Add kkg to authors list --- AUTHORS | 1 + src/GDSIIgeom.cpp | 9 +++++---- src/meepgeom.cpp | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 87d2d7607..33f82cca7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -16,3 +16,4 @@ Robin Dunn Ian Williamson Andreas Hoenselaar Ben Bartlett +Krishna Gadepalli diff --git a/src/GDSIIgeom.cpp b/src/GDSIIgeom.cpp index 536bb5ca5..6907970da 100644 --- a/src/GDSIIgeom.cpp +++ b/src/GDSIIgeom.cpp @@ -144,7 +144,7 @@ geometric_object_list get_GDSII_prisms(material_type material, const char *GDSII for (int np = 0; np < num_prisms; np++) { dVec polygon = polygons[np]; int num_vertices = polygon.size() / 2; - vector3 *vertices = new vector3[num_vertices]; + auto vertices = std::make_unique(num_vertices); for (int nv = 0; nv < num_vertices; nv++) { vertices[nv].x = polygon[2 * nv + 0]; vertices[nv].y = polygon[2 * nv + 1]; @@ -152,7 +152,8 @@ geometric_object_list get_GDSII_prisms(material_type material, const char *GDSII } double height = zmax - zmin; vector3 zaxis = {0, 0, 1}; - prisms.items[np] = make_prism(material, vertices, num_vertices, height, zaxis); + prisms.items[np] = + make_prism(material, vertices.get(), num_vertices, height, zaxis); } return prisms; } @@ -169,7 +170,7 @@ geometric_object get_GDSII_prism(material_type material, const char *GDSIIFile, dVec polygon = get_polygon(GDSIIFile, Text, Layer); int num_vertices = polygon.size() / 2; - vector3 *vertices = new vector3[num_vertices]; + auto vertices = std::make_unique(num_vertices); for (int nv = 0; nv < num_vertices; nv++) { vertices[nv].x = polygon[2 * nv + 0]; vertices[nv].y = polygon[2 * nv + 1]; @@ -178,7 +179,7 @@ geometric_object get_GDSII_prism(material_type material, const char *GDSIIFile, double height = zmax - zmin; vector3 zaxis = {0, 0, 1}; - return make_prism(material, vertices, num_vertices, height, zaxis); + return make_prism(material, vertices.get(), num_vertices, height, zaxis); } geometric_object get_GDSII_prism(material_type material, const char *GDSIIFile, int Layer, diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index 496012681..c96c8f879 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -716,7 +716,8 @@ geom_epsilon::geom_epsilon(const geom_epsilon &geps1) { geom_epsilon::~geom_epsilon() { int length = geometry.num_items; for (int i = 0; i < length; i++){ - delete geometry.items[i].material; + material_free((material_type)geometry.items[i].material); + geometric_object_destroy(geometry.items[i]); } delete[] geometry.items; unset_volume(); From 4d953289a4b6604d031b97870f9ff8eaf70f24bc Mon Sep 17 00:00:00 2001 From: Krishna Gadepalli <34969407+kkg4theweb@users.noreply.github.com> Date: Fri, 3 Dec 2021 05:47:11 -0800 Subject: [PATCH 027/155] Fix for Issue #1834 (#1840) * Fix memory leaks * Add kkg to authors list * Expose set_default_material and use it in libpympb/pympb.cpp --- libpympb/pympb.cpp | 6 ++++-- src/meepgeom.cpp | 2 +- src/meepgeom.hpp | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/libpympb/pympb.cpp b/libpympb/pympb.cpp index 6e4a00240..2c3885199 100644 --- a/libpympb/pympb.cpp +++ b/libpympb/pympb.cpp @@ -676,7 +676,7 @@ void mode_solver::init(int p, bool reset_fields, geometric_object_list *geometry meep_geom::material_data *_default_material) { int have_old_fields = 0; - default_material = _default_material; + set_default_material(_default_material); n[0] = grid_size.x; n[1] = grid_size.y; @@ -909,7 +909,9 @@ void mode_solver::reset_epsilon(geometric_object_list *geometry) { }; if (!epsilon_input_file.empty()) { - default_material = meep_geom::make_file_material(epsilon_input_file.c_str()); + meep_geom::material_type material = meep_geom::make_file_material(epsilon_input_file.c_str()); + set_default_material(material); + material_free(material); } // TODO: support mu_input_file diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index c96c8f879..eb7648395 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -29,7 +29,7 @@ namespace meep_geom { material_data vacuum_material_data; material_type vacuum = &vacuum_material_data; -static void set_default_material(material_type _default_material) { +void set_default_material(material_type _default_material) { if (default_material != NULL) { if (default_material == _default_material) return; material_free((material_type)default_material); diff --git a/src/meepgeom.hpp b/src/meepgeom.hpp index bb2f0d132..5802807e7 100644 --- a/src/meepgeom.hpp +++ b/src/meepgeom.hpp @@ -253,6 +253,7 @@ material_type make_material_grid(bool do_averaging, double beta, double eta, dou vector3 vec_to_vector3(const meep::vec &pt); meep::vec vector3_to_vec(const vector3 v3); +void set_default_material(material_type _default_material); void epsilon_material_grid(material_data *md, double u); void epsilon_file_material(material_data *md, vector3 p); bool susceptibility_equal(const susceptibility &s1, const susceptibility &s2); From b434e982f20579fa031f587d8181a9b285c2c1b8 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Tue, 7 Dec 2021 13:28:13 -0500 Subject: [PATCH 028/155] use unique_ptr (C++11) instead of make_unique (C++14) (#1844) --- src/GDSIIgeom.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GDSIIgeom.cpp b/src/GDSIIgeom.cpp index 6907970da..3c7d2960b 100644 --- a/src/GDSIIgeom.cpp +++ b/src/GDSIIgeom.cpp @@ -144,7 +144,7 @@ geometric_object_list get_GDSII_prisms(material_type material, const char *GDSII for (int np = 0; np < num_prisms; np++) { dVec polygon = polygons[np]; int num_vertices = polygon.size() / 2; - auto vertices = std::make_unique(num_vertices); + std::unique_ptr vertices(new vector3[num_vertices]); for (int nv = 0; nv < num_vertices; nv++) { vertices[nv].x = polygon[2 * nv + 0]; vertices[nv].y = polygon[2 * nv + 1]; @@ -170,7 +170,7 @@ geometric_object get_GDSII_prism(material_type material, const char *GDSIIFile, dVec polygon = get_polygon(GDSIIFile, Text, Layer); int num_vertices = polygon.size() / 2; - auto vertices = std::make_unique(num_vertices); + std::unique_ptr vertices(new vector3[num_vertices]); for (int nv = 0; nv < num_vertices; nv++) { vertices[nv].x = polygon[2 * nv + 0]; vertices[nv].y = polygon[2 * nv + 1]; From 6043fd50962c59e77fe24523cb1f5983a8db2053 Mon Sep 17 00:00:00 2001 From: mochen4 Date: Tue, 7 Dec 2021 16:10:15 -0500 Subject: [PATCH 029/155] Use None instead of empty list in constructors (#1846) * use None * minor fix Co-authored-by: Mo Chen --- doc/docs/Python_User_Interface.md | 18 +++++++++--------- python/geom.py | 14 +++++++------- python/simulation.py | 20 ++++++++++---------- python/solver.py | 8 ++++---- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/doc/docs/Python_User_Interface.md b/doc/docs/Python_User_Interface.md index 811be2601..d0711c5e3 100644 --- a/doc/docs/Python_User_Interface.md +++ b/doc/docs/Python_User_Interface.md @@ -89,18 +89,18 @@ control various parameters of the Meep computation. def __init__(self, cell_size, resolution, - geometry=[], - sources=[], + geometry=None, + sources=None, eps_averaging=True, dimensions=3, - boundary_layers=[], - symmetries=[], + boundary_layers=None, + symmetries=None, force_complex_fields=False, default_material=Medium(), m=0, k_point=False, kz_2d='complex', - extra_materials=[], + extra_materials=None, material_function=None, epsilon_func=None, epsilon_input_file='', @@ -4216,8 +4216,8 @@ def __init__(self, epsilon_offdiag=Vector3<0.0, 0.0, 0.0>, mu_diag=Vector3<1.0, 1.0, 1.0>, mu_offdiag=Vector3<0.0, 0.0, 0.0>, - E_susceptibilities=[], - H_susceptibilities=[], + E_susceptibilities=None, + H_susceptibilities=None, E_chi2_diag=Vector3<0.0, 0.0, 0.0>, E_chi3_diag=Vector3<0.0, 0.0, 0.0>, H_chi2_diag=Vector3<0.0, 0.0, 0.0>, @@ -4638,8 +4638,8 @@ Absorption](Materials.md#saturable-gain-and-absorption). ```python def __init__(self, - initial_populations=[], - transitions=[], + initial_populations=None, + transitions=None, **kwargs): ``` diff --git a/python/geom.py b/python/geom.py index 61a1fdb5c..19d89f48e 100755 --- a/python/geom.py +++ b/python/geom.py @@ -313,8 +313,8 @@ def __init__(self, epsilon_diag=Vector3(1, 1, 1), epsilon_offdiag=Vector3(), mu_diag=Vector3(1, 1, 1), mu_offdiag=Vector3(), - E_susceptibilities=[], - H_susceptibilities=[], + E_susceptibilities=None, + H_susceptibilities=None, E_chi2_diag=Vector3(), E_chi3_diag=Vector3(), H_chi2_diag=Vector3(), @@ -425,8 +425,8 @@ def __init__(self, epsilon_diag=Vector3(1, 1, 1), self.epsilon_offdiag = Vector3(*epsilon_offdiag) self.mu_diag = Vector3(*mu_diag) self.mu_offdiag = Vector3(*mu_offdiag) - self.E_susceptibilities = E_susceptibilities - self.H_susceptibilities = H_susceptibilities + self.E_susceptibilities = E_susceptibilities if E_susceptibilities else [] + self.H_susceptibilities = H_susceptibilities if H_susceptibilities else [] self.E_chi2_diag = Vector3(chi2, chi2, chi2) if chi2 else Vector3(*E_chi2_diag) self.E_chi3_diag = Vector3(chi3, chi3, chi3) if chi3 else Vector3(*E_chi3_diag) self.H_chi2_diag = Vector3(*H_chi2_diag) @@ -842,10 +842,10 @@ class MultilevelAtom(Susceptibility): atomic level. See [Materials/Saturable Gain and Absorption](Materials.md#saturable-gain-and-absorption). """ - def __init__(self, initial_populations=[], transitions=[], **kwargs): + def __init__(self, initial_populations=None, transitions=None, **kwargs): super(MultilevelAtom, self).__init__(**kwargs) - self.initial_populations = initial_populations - self.transitions = transitions + self.initial_populations = initial_populations if initial_populations else [] + self.transitions = transitions if transitions else [] class Transition(object): diff --git a/python/simulation.py b/python/simulation.py index b99dccf3b..acbe55ba0 100644 --- a/python/simulation.py +++ b/python/simulation.py @@ -935,18 +935,18 @@ class Simulation(object): def __init__(self, cell_size, resolution, - geometry=[], - sources=[], + geometry=None, + sources=None, eps_averaging=True, dimensions=3, - boundary_layers=[], - symmetries=[], + boundary_layers=None, + symmetries=None, force_complex_fields=False, default_material=mp.Medium(), m=0, k_point=False, kz_2d="complex", - extra_materials=[], + extra_materials=None, material_function=None, epsilon_func=None, epsilon_input_file='', @@ -1198,12 +1198,12 @@ def __init__(self, """ self.cell_size = Vector3(*cell_size) - self.geometry = geometry - self.sources = sources + self.geometry = geometry if geometry else [] + self.sources = sources if sources else [] self.resolution = resolution self.dimensions = dimensions - self.boundary_layers = boundary_layers - self.symmetries = symmetries + self.boundary_layers = boundary_layers if boundary_layers else [] + self.symmetries = symmetries if symmetries else [] self.geometry_center = Vector3(*geometry_center) self.eps_averaging = eps_averaging self.subpixel_tol = subpixel_tol @@ -1211,7 +1211,7 @@ def __init__(self, self.loop_tile_base_db = loop_tile_base_db self.loop_tile_base_eh = loop_tile_base_eh self.ensure_periodicity = ensure_periodicity - self.extra_materials = extra_materials + self.extra_materials = extra_materials if extra_materials else [] self.default_material = default_material self.epsilon_input_file = epsilon_input_file self.num_chunks = chunk_layout.numchunks() if isinstance(chunk_layout,mp.BinaryPartition) else num_chunks diff --git a/python/solver.py b/python/solver.py index 56a564a2b..3d94e0b66 100644 --- a/python/solver.py +++ b/python/solver.py @@ -86,9 +86,9 @@ def __init__(self, target_freq=0.0, tolerance=1.0e-7, num_bands=1, - k_points=[], + k_points=None, ensure_periodicity=True, - geometry=[], + geometry=None, geometry_lattice=mp.Lattice(), geometry_center=mp.Vector3(0, 0, 0), default_material=mp.Medium(epsilon=1), @@ -104,8 +104,8 @@ def __init__(self, self.mode_solver = None self.resolution = resolution self.eigensolver_flags = eigensolver_flags - self.k_points = k_points - self.geometry = geometry + self.k_points = k_points if k_points else [] + self.geometry = geometry if geometry else [] self.geometry_lattice = geometry_lattice self.geometry_center = mp.Vector3(*geometry_center) self.default_material = default_material From c696e31a4e02fc82fdb231f8b954c043c0cf51b7 Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Tue, 7 Dec 2021 21:05:00 -0500 Subject: [PATCH 030/155] =?UTF-8?q?Define=20what=20happens=20when=20`?= =?UTF-8?q?=CE=B2=3D=E2=88=9E`=20and=20`u=3D=CE=B7`=20(#1842)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * define what happens when beta=inf and u=0.5 * use eta not 0.5 * Update src/meepgeom.cpp Co-authored-by: Steven G. Johnson --- src/meepgeom.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index eb7648395..b88ccc5e9 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -480,6 +480,7 @@ double material_grid_val(vector3 p, material_data *md) { static double tanh_projection(double u, double beta, double eta) { if (beta == 0) return u; + if (u == eta) return 0.5; // avoid NaN when beta is Inf double tanh_beta_eta = tanh(beta*eta); return (tanh_beta_eta + tanh(beta*(u-eta))) / (tanh_beta_eta + tanh(beta*(1-eta))); From 87b3d98fd5bd672ab64c88b605473bba7801dc81 Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Tue, 7 Dec 2021 21:05:44 -0500 Subject: [PATCH 031/155] fix for arrays (#1845) --- python/adjoint/objective.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/adjoint/objective.py b/python/adjoint/objective.py index 9789d94b8..cd7d143d4 100644 --- a/python/adjoint/objective.py +++ b/python/adjoint/objective.py @@ -44,7 +44,7 @@ def place_adjoint_source(self, dJ): def get_evaluation(self): """Evaluates the objective quantity.""" - if self._eval: + if self._eval is not None: return self._eval else: raise RuntimeError( From f65576628951711b82b716a81a5027834e17cce2 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Tue, 7 Dec 2021 18:52:04 -0800 Subject: [PATCH 032/155] minor improvements to docs (#1848) --- doc/docs/FAQ.md | 22 +++++++++++----------- doc/docs/Introduction.md | 18 +++++++++--------- python/adjoint/utils.py | 4 ++-- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/doc/docs/FAQ.md b/doc/docs/FAQ.md index 2ea77e268..d19446253 100644 --- a/doc/docs/FAQ.md +++ b/doc/docs/FAQ.md @@ -103,7 +103,7 @@ Meep doesn't implement a frequency-independent complex $\varepsilon$. Not only i Maxwell's equations have exponentially growing solutions for a frequency-independent negative $\varepsilon$. For any physical medium with negative $\varepsilon$, there must be dispersion, and you must likewise use dispersive materials in Meep to obtain negative $\varepsilon$ at some desired frequency. The requirement of dispersion to obtain negative $\varepsilon$ follows from the [Kramers–Kronig relations](https://en.wikipedia.org/wiki/Kramers%E2%80%93Kronig_relations), and also follows from thermodynamic considerations that the energy in the electric field must be positive. For example, see [Electrodynamics of Continuous Media](https://www.amazon.com/Electrodynamics-Continuous-Media-Second-Theoretical/dp/0750626348) by Landau, Pitaevskii, and Lifshitz. At an even more fundamental level, it can be derived from passivity constraints as shown in [Physical Review A, Vol. 90, 023847 (2014)](http://arxiv.org/abs/arXiv:1405.0238). -If you solve Maxwell's equations in a homogeneous-epsilon material at some real wavevector $\mathbf{k}$, you get a dispersion relation $\omega^2 = c^2 |\mathbf{k}|^2 / \varepsilon$. If $\varepsilon$ is positive, there are two real solutions $\omega = \pm c |\mathbf{k}| / \sqrt{\varepsilon}$, giving oscillating solutions. If $\varepsilon$ is negative, there are two imaginary solutions corresponding to exponentially decaying and exponentially growing solutions from any current source. These solutions can always be spatially decomposed into a superposition of real-$\mathbf{k}$ values via a spatial Fourier transform. +If you solve Maxwell's equations in a homogeneous-$\varepsilon$ material at some real wavevector $\mathbf{k}$, you get a dispersion relation $\omega^2 = c^2 |\mathbf{k}|^2 / \varepsilon$. If $\varepsilon$ is positive, there are two real solutions $\omega = \pm c |\mathbf{k}| / \sqrt{\varepsilon}$, giving oscillating solutions. If $\varepsilon$ is negative, there are two imaginary solutions corresponding to exponentially decaying and exponentially growing solutions from any current source. These solutions can always be spatially decomposed into a superposition of real-$\mathbf{k}$ values via a spatial Fourier transform. If you do a simulation of any kind in the time domain (not just FDTD), you pretty much can't avoid exciting both the decaying and the growing solutions. This is *not* a numerical instability, it is a real solution of the underlying equations for an unphysical material. @@ -213,25 +213,25 @@ The "steady-state" response is defined as the exp(-iωt) response field (ω=2πf Meep contains a [mode-decomposition feature](Mode_Decomposition.md) which can be used to compute complex-valued [S-parameters](https://en.wikipedia.org/wiki/Scattering_parameters). An example is provided for a [two-port network](https://en.wikipedia.org/wiki/Two-port_network#Scattering_parameters_(S-parameters)) based on a silicon directional coupler in [Tutorial/GDSII Import](/Python_Tutorials/GDSII_Import/). Additional examples are available for a [waveguide mode converter](Python_Tutorials/Mode_Decomposition.md#reflectance-of-a-waveguide-taper) and [subwavelength grating](Python_Tutorials/Mode_Decomposition.md#phase-map-of-a-subwavelength-binary-grating). -### `Harminv` is unable to find the resonant modes of my structure +### Harminv is unable to find the resonant modes of my structure -There are six possible explanations for why [`Harminv`](Python_User_Interface.md#harminv) could not find the resonant modes: (1) the run time was not long enough and the decay rate of the mode is so small that the `Harminv` data was mainly noise, (2) the `Harminv` call was not wrapped in [`after_sources`](Python_User_Interface.md#controlling-when-a-step-function-executes); if `Harminv` overlaps sources turning on and off it will get confused because the sources are not exponentially decaying fields, (3) the `Harminv` monitor is near the mode's nodal point (e.g., in a symmetry plane), (4) there are field instabilities where the fields are actually [blowing up](#why-are-the-fields-blowing-up-in-my-simulation); this may result in `Harminv` returning a negative [quality factor](https://en.wikipedia.org/wiki/Q_factor), (5) the decay rate of the mode is too fast; `Harminv` discards any modes which have a quality factor less than 50 where the leaky-mode approximation of the modes as perfectly exponentially decaying (i.e. a Lorentzian lineshape) begins to break down (and thus `Harminv` won't likely find any modes inside a [metal](Materials.md#material-dispersion)), or (6) the PML overlaps the non-radiated/evanescent field and has introduced artificial absorption effects in the local density of states (LDOS). +There are six possible explanations for why [Harminv](Python_User_Interface.md#harminv) could not find the resonant modes: (1) the run time was not long enough and the decay rate of the mode is so small that the Harminv data was mainly noise, (2) the Harminv call was not wrapped in [`after_sources`](Python_User_Interface.md#controlling-when-a-step-function-executes); if Harminv overlaps sources turning on and off it will get confused because the sources are not exponentially decaying fields, (3) the Harminv monitor is near the mode's nodal point (e.g., in a symmetry plane), (4) there are field instabilities where the fields are actually [blowing up](#why-are-the-fields-blowing-up-in-my-simulation); this may result in Harminv returning a negative [quality factor](https://en.wikipedia.org/wiki/Q_factor), (5) the decay rate of the mode is too fast; Harminv discards any modes which have a quality factor less than 50 where the leaky-mode approximation of the modes as perfectly exponentially decaying (i.e. a Lorentzian lineshape) begins to break down (and thus Harminv won't likely find any modes inside a [metal](Materials.md#material-dispersion)), or (6) the PML overlaps the non-radiated/evanescent field and has introduced artificial absorption effects in the local density of states (LDOS). -`Harminv` will find modes in perfect-conductor cavities (i.e. with no loss) with a quality factor that is very large and has an arbitrary sign; it has no way to tell that the decay rate is zero, it just knows it is very small. +Harminv will find modes in perfect-conductor cavities (i.e. with no loss) with a quality factor that is very large and has an arbitrary sign; it has no way to tell that the decay rate is zero, it just knows it is very small. -`Harminv` becomes less effective as the frequency approaches zero, so you should specify a non-zero frequency range. +Harminv becomes less effective as the frequency approaches zero, so you should specify a non-zero frequency range. In order to resolve two closely-spaced modes, in general it is preferable to run with a narrow bandwidth source around the frequency of interest to excite/analyze as few modes as possible and/or increase the run time to improve the frequency resolution. If you want to analyze an arbitrary spectrum, just use the Fourier transform as computed by [`dft_fields`](Python_User_Interface.md#field-computations). -For a structure with two doubly-degenerate modes (e.g., a dipole-like mode or two counter-propagating modes in a ring resonator), the grid discretization will almost certainly break the degeneracy slightly. In this case, `Harminv` may find two *distinct* nearly-degenerate modes. +For a structure with two doubly-degenerate modes (e.g., a dipole-like mode or two counter-propagating modes in a ring resonator), the grid discretization will almost certainly break the degeneracy slightly. In this case, Harminv may find two *distinct* nearly-degenerate modes. -Note: any real-valued signal consists of both positive and negative frequency components (with complex-conjugate amplitudes) in a Fourier domain decomposition into complex exponentials. `Harminv` usually is set up to find just one sign of the frequency, but occasionally converges to a negative-frequency component as well; these are just as meaningful as the positive frequencies. +Note: any real-valued signal consists of both positive and negative frequency components (with complex-conjugate amplitudes) in a Fourier domain decomposition into complex exponentials. Harminv usually is set up to find just one sign of the frequency, but occasionally converges to a negative-frequency component as well; these are just as meaningful as the positive frequencies. ### How do I compute the effective index of an eigenmode of a lossy waveguide? To compute the [effective index](https://www.rp-photonics.com/effective_refractive_index.html), you will need to first compute the *complex* $\omega$ (the loss in time) for a *real* $\beta$ (the propagation constant) and then convert this quantity into a loss in space (*complex* $\beta$ at a *real* $\omega$) by dividing by the group velocity $v_g$. This procedure is described in more detail below. -To obtain the loss in time, you make your computational cell a cross-section of your waveguide (i.e. 2d for a waveguide with constant cross-section), and set Bloch-periodic boundary conditions via the `k_point` input variable — this specifies your (real) $\beta$. You then treat it exactly the same as a [resonant-cavity problem](Python_Tutorials/Resonant_Modes_and_Transmission_in_a_Waveguide_Cavity.md#resonant-modes): you excite the system with a short pulse source, monitor the field at some point, and then analyze the result with [`Harminv`](Python_User_Interface.md#harminv); all of which is done if you call `run_kpoints`. This will give you the complex $\omega$ at the given $\beta$, where the imaginary part is the loss rate in time. Note: the loss in a uniform waveguide, with no absorption or disorder, is zero, even in the discretized system. +To obtain the loss in time, you make your computational cell a cross-section of your waveguide (i.e. 2d for a waveguide with constant cross-section), and set Bloch-periodic boundary conditions via the `k_point` input variable — this specifies your (real) $\beta$. You then treat it exactly the same as a [resonant-cavity problem](Python_Tutorials/Resonant_Modes_and_Transmission_in_a_Waveguide_Cavity.md#resonant-modes): you excite the system with a short pulse source, monitor the field at some point, and then analyze the result with [Harminv](Python_User_Interface.md#harminv); all of which is done if you call `run_kpoints`. This will give you the complex $\omega$ at the given $\beta$, where the imaginary part is the loss rate in time. Note: the loss in a uniform waveguide, with no absorption or disorder, is zero, even in the discretized system. That is, you have $\omega(\beta_r) = \omega_r + i\omega_i$ where the subscripts $r$ and $i$ denote real and imaginary parts. Now, what you want to do is to get the complex $\beta$ at the real $\omega$ which is given by: $\beta(\omega_r) = \beta_r - i\omega_i/v_g + \mathcal{O}(\omega_i^2)$. That is, to first order in the loss, the imaginary part of $\beta$ (the propagation loss) at the real frequency $\omega_r$ is given just by dividing $\omega_i$ by the group velocity $v_g = \frac{d\omega}{d\beta}$, which you can [get from the dispersion relation in the absence of loss](#how-do-i-compute-the-group-velocity-of-a-mode). This relationship is just a consequence of the first-order Taylor expansion of the dispersion relation $\omega(\beta)$ in the complex plane. @@ -239,7 +239,7 @@ This analysis is only valid if the loss is small, i.e. $\omega_i \ll \omega_r$. ### How do I compute the group velocity of a mode? -There are two possible approaches for manually computing the [group velocity](https://en.wikipedia.org/wiki/Group_velocity) $\nabla_\textbf{k}\omega$: (1) compute the [dispersion relation](Python_Tutorials/Resonant_Modes_and_Transmission_in_a_Waveguide_Cavity.md#band-diagram) $\omega(\textbf{k})$ using [`Harminv`](Python_User_Interface.md#harminv), fit it to a polynomial, and calculate its derivative using a [finite difference](https://en.wikipedia.org/wiki/Finite_difference) (i.e. $[ \omega(\textbf{k} + \Delta \textbf{k}) - \omega(\textbf{k}-\Delta \textbf{k}) ] / (2\|\Delta \textbf{k}\|)$, or (2) excite the mode using a narrowband pulse and compute the ratio of the Poynting flux to electric-field energy density. +There are two possible approaches for manually computing the [group velocity](https://en.wikipedia.org/wiki/Group_velocity) $\nabla_\textbf{k}\omega$: (1) compute the [dispersion relation](Python_Tutorials/Resonant_Modes_and_Transmission_in_a_Waveguide_Cavity.md#band-diagram) $\omega(\textbf{k})$ using [Harminv](Python_User_Interface.md#harminv), fit it to a polynomial, and calculate its derivative using a [finite difference](https://en.wikipedia.org/wiki/Finite_difference) (i.e. $[ \omega(\textbf{k} + \Delta \textbf{k}) - \omega(\textbf{k}-\Delta \textbf{k}) ] / (2\|\Delta \textbf{k}\|)$, or (2) excite the mode using a narrowband pulse and compute the ratio of the Poynting flux to electric-field energy density. For eigenmodes obtained using [mode decomposition](Python_User_Interface.md#mode-decomposition), the group velocities are computed automatically along with the mode coefficients. @@ -257,7 +257,7 @@ The decay coefficient of the fields within any PML contains a $\cos(\theta)$ fac ### How do I compute the modes of a non-orthogonal (i.e., triangular) lattice? -Meep does not support non-rectangular unit cells. To model a triangular lattice, you have to use a supercell. This will cause the band structure to be [folded](#why-are-there-strange-peaks-in-my-reflectancetransmittance-spectrum-when-modeling-planar-or-periodic-structures). However, if you take your point source and replicate it according to the underlying triangular lattice vectors, with the right phase relationship according to the Bloch wavevector, then it should excite the folded bands only with very low amplitude as reported by [`Harminv`](Python_User_Interface.md#harminv). Also, for every `Harminv` point you put in, you should analyze the fields from the periodic copies of that point (with the periodicity of the underlying lattice). Then, reject any frequency that is not detected at *all* points, with an amplitude that is related by something close to the correct $\exp(i\vec{k}\cdot\vec{r})$ phase. +Meep does not support non-rectangular unit cells. To model a triangular lattice, you have to use a supercell. This will cause the band structure to be [folded](#why-are-there-strange-peaks-in-my-reflectancetransmittance-spectrum-when-modeling-planar-or-periodic-structures). However, if you take your point source and replicate it according to the underlying triangular lattice vectors, with the right phase relationship according to the Bloch wavevector, then it should excite the folded bands only with very low amplitude as reported by [Harminv](Python_User_Interface.md#harminv). Also, for every Harminv point you put in, you should analyze the fields from the periodic copies of that point (with the periodicity of the underlying lattice). Then, reject any frequency that is not detected at *all* points, with an amplitude that is related by something close to the correct $\exp(i\vec{k}\cdot\vec{r})$ phase. In principle, the excitation of the folded bands would be exactly zero if you place your sources correctly in the supercell. However, this doesn't happen in FDTD because the finite grid spoils the symmetry slightly. It also means that the detection of folded bands will vary with resolution. For an example, see Section 4.6 ("Sources in Supercells") in [Chapter 4](http://arxiv.org/abs/arXiv:1301.5366) ("Electromagnetic Wave Source Conditions") of [Advances in FDTD Computational Electrodynamics: Photonics and Nanotechnology](https://www.amazon.com/Advances-FDTD-Computational-Electrodynamics-Nanotechnology/dp/1608071707). @@ -471,7 +471,7 @@ To output the data to an HDF5 file, you can use the [`in_volume`](Python_User_In ### How do I compute the absorbed power in a local subregion of the cell? -To compute the absorbed power anywhere in the cell, you can use [Poynting's theorem](https://en.wikipedia.org/wiki/Poynting%27s_theorem): place a *closed* surface of [`dft`](Python_User_Interface.md#flux-spectra) flux monitors surrounding the subregion and specify the `weight` parameter of each [`FluxRegion`](Python_User_Interface.md#fluxregion) accordingly (i.e., ±1) in order to capture all incoming power. For a 2d example, see [Tutorial/Near-to-Far Field Spectra/Radiation Pattern of an Antenna](Python_Tutorials/Near_to_Far_Field_Spectra.md#radiation-pattern-of-an-antenna). There is also a 3d example for calculating the [light-extraction efficiency of an organic light-emitting diode (OLED)](http://www.simpetus.com/projects.html#meep_oled). +To compute the absorbed power anywhere in the cell, you can use [Poynting's theorem](https://en.wikipedia.org/wiki/Poynting%27s_theorem): place a *closed* surface of [DFT](Python_User_Interface.md#flux-spectra) flux monitors surrounding the subregion and specify the `weight` parameter of each [`FluxRegion`](Python_User_Interface.md#fluxregion) accordingly (i.e., ±1) in order to capture all incoming power. For a 2d example, see [Tutorial/Near-to-Far Field Spectra/Radiation Pattern of an Antenna](Python_Tutorials/Near_to_Far_Field_Spectra.md#radiation-pattern-of-an-antenna). There is also a 3d example for calculating the [light-extraction efficiency of an organic light-emitting diode (OLED)](http://www.simpetus.com/projects.html#meep_oled). ### What happens if I specify an output volume that extends beyond a cell with periodic boundaries? diff --git a/doc/docs/Introduction.md b/doc/docs/Introduction.md index d4466289c..c4c7cb91f 100644 --- a/doc/docs/Introduction.md +++ b/doc/docs/Introduction.md @@ -13,7 +13,7 @@ This introduction does not describe the [Python Interface](Python_User_Interface Maxwell's Equations ------------------- -Meep simulates [Maxwell's equations](https://en.wikipedia.org/wiki/Maxwell's_equations), which describe the interactions of electric (**E**) and magnetic (**H**) fields with one another and with matter and sources. In particular, the equations for the time evolution of the fields are: +Meep simulates [Maxwell's equations](https://en.wikipedia.org/wiki/Maxwell's_equations), which describe the interactions of electric ($\mathbf{E}$) and magnetic ($\mathbf{H}$) fields with one another and with matter and sources. In particular, the equations for the time evolution of the fields are:
@@ -27,7 +27,7 @@ $\mathbf{D} = \varepsilon \mathbf{E}$
-where **D** is the displacement field, ε is the dielectric constant, **J** is the current density (of electric charge), and **J***B* is the *magnetic-charge* current density. Magnetic currents are a convenient computational fiction in some situations. **B** is the magnetic flux density (often called the magnetic field), μ is the magnetic permeability, and **H** is the magnetic field. The σ$_B$ and σ$_D$ terms correspond to (frequency-independent) magnetic and electric conductivities, respectively. The divergence equations are implicitly: +where $\mathbf{D}$ is the displacement field, $\varepsilon$ is the dielectric constant, $\mathbf{J}$ is the current density (of electric charge), and $\mathbf{J}$*B* is the *magnetic-charge* current density. Magnetic currents are a convenient computational fiction in some situations. $\mathbf{B}$ is the magnetic flux density (often called the magnetic field), $\mu$ is the magnetic permeability, and $\mathbf{H}$ is the magnetic field. The $\sigma_B$ and $\sigma_D$ terms correspond to (frequency-independent) magnetic and electric conductivities, respectively. The divergence equations are implicitly:
@@ -37,7 +37,7 @@ $\nabla \cdot \mathbf{D} = - \int^t \nabla \cdot (\mathbf{J}(t') + \sigma_D \mat
-Generally, ε depends not only on position but also on frequency (material dispersion) and on the field **E** itself (nonlinearity), and may include loss or gain. These effects are supported in Meep and are described in [Materials](Materials.md). +Generally, $\varepsilon$ depends not only on position but also on frequency (material dispersion) and on the field $\mathbf{E}$ itself (nonlinearity), and may include loss or gain. These effects are supported in Meep and are described in [Materials](Materials.md). For rotationally symmetric geometries, Meep supports simulation in [Cylindrical Coordinates](Python_Tutorials/Cylindrical_Coordinates.md). @@ -75,7 +75,7 @@ FDTD methods divide space and time into a finite rectangular grid. As described Perhaps the most important thing you need to know is this: if the grid has some spatial resolution $\Delta x$, then our discrete time-step $\Delta t$ is given by $\Delta t = S \Delta x$, where $S$ is the [Courant factor](https://en.wikipedia.org/wiki/Courant%E2%80%93Friedrichs%E2%80%93Lewy_condition) and must satisfy $S < n_\textrm{min} / \sqrt{\mathrm{\# dimensions}}$, where $n_\textrm{min}$ is the minimum refractive index (usually 1), in order for the method to be stable (not diverge). In Meep, $S=0.5$ by default (which is sufficient for 1 to 3 dimensions), but [can be changed](Python_User_Interface.md#the-simulation-class) by the user. This means that **when you double the grid resolution, the number of time steps doubles as well** (for the same simulation period). Thus, in three dimensions, if you double the resolution, then the amount of memory increases by 8 and the amount of computational time increases by (at least) 2. -The second most important thing you should know is that, in order to discretize the equations with [second-order accuracy](https://en.wikipedia.org/wiki/Finite_difference_method#Accuracy_and_order), FDTD methods **store different field components at different grid locations**. This discretization is known as a [Yee lattice](Yee_Lattice.md). As a consequence, **Meep must interpolate the field components to a common point** whenever you want to combine, compare, or output the field components (e.g. in computing energy density or flux). Most of the time, you don't need to worry too much about this interpolation since it is automatic. However, because it is a simple linear interpolation, while **E** and **D** may be discontinuous across dielectric boundaries, it means that the interpolated **E** and **D** fields may be less accurate than you might expect right around dielectric interfaces. +The second most important thing you should know is that, in order to discretize the equations with [second-order accuracy](https://en.wikipedia.org/wiki/Finite_difference_method#Accuracy_and_order), FDTD methods **store different field components at different grid locations**. This discretization is known as a [Yee lattice](Yee_Lattice.md). As a consequence, **Meep must interpolate the field components to a common point** whenever you want to combine, compare, or output the field components (e.g. in computing energy density or flux). Most of the time, you don't need to worry too much about this interpolation since it is automatic. However, because it is a simple linear interpolation, while $\mathbf{E}$ and $\mathbf{D}$ may be discontinuous across dielectric boundaries, it means that the interpolated $\mathbf{E}$ and $\mathbf{D}$ fields may be less accurate than you might expect right around dielectric interfaces. Many references are available on FDTD methods for computational electromagnetics. See, for example: @@ -115,9 +115,9 @@ In this section, we sketch out a few of the basic ways in which FDTD can be used The most obvious thing that you can do with a time-domain simulation, of course, is to simply get a [picture of the field pattern](Python_User_Interface.md#data-visualization) resulting from a given source, or perhaps an [animation showing the field evolution in time](Python_User_Interface.md#animate2d). -The field pattern from a given localized source at a particular frequency ω is a form of the **Green's function** of the system. More specifically, one typically writes the "dyadic" Green's function +The field pattern from a given localized source at a particular frequency $\omega$ is a form of the **Green's function** of the system. More specifically, one typically writes the "dyadic" Green's function -$$G_{ij}(\omega; \mathbf{x}, \mathbf{x}')$$ which gives the $i$th component of (say) **E** at **x** from a point current source **J** at $\mathbf{x'}$, such that $\mathbf{J}(\mathbf{x})=\hat{\mathbf{e}_j} \cdot \exp(-i\omega t) \cdot \delta(\mathbf{x}-\mathbf{x}')$. To obtain this in FDTD, you simply place the requisite point source at $\mathbf{x'}$ and wait for a long enough time for all other frequency components to die out (noting that the mere act of turning on a current source at $t=0$ introduces a spectrum of frequencies). Alternatively, you can use Meep's frequency-domain solver to find the response directly (by solving the associated linear equation). For an example, see [Tutorial/Frequency Domain Solver](Python_Tutorials/Frequency_Domain_Solver.md). +$$G_{ij}(\omega; \mathbf{x}, \mathbf{x}')$$ which gives the $i$th component of (say) $\mathbf{E}$ at $\mathbf{x}$ from a point current source $\mathbf{J}$ at $\mathbf{x'}$, such that $\mathbf{J}(\mathbf{x})=\hat{\mathbf{e}_j} \cdot \exp(-i\omega t) \cdot \delta(\mathbf{x}-\mathbf{x}')$. To obtain this in FDTD, you simply place the requisite point source at $\mathbf{x'}$ and wait for a long enough time for all other frequency components to die out (noting that the mere act of turning on a current source at $t=0$ introduces a spectrum of frequencies). Alternatively, you can use Meep's frequency-domain solver to find the response directly (by solving the associated linear equation). For an example, see [Tutorial/Frequency Domain Solver](Python_Tutorials/Frequency_Domain_Solver.md). Given the Green's function, one can then compute a wide variety of useful things, from the radiated flux, to the local density of states (proportional to $\sum_i G_{ii}$), to Born approximations for small scatterers. Even more powerfully, one can compute many such quantities for multiple frequencies simultaneously using the Fourier transform of a short pulse as described below. @@ -147,12 +147,12 @@ Meep is designed to make these kinds of calculations easy, as long as you have s ### Resonant Modes -Another common task in FDTD is to compute resonant modes or eigenmodes of a given structure. For example, suppose you have a [diffraction grating](Python_Tutorials/Mode_Decomposition.md#diffraction-spectrum-of-a-binary-grating), photonic crystal (periodic dielectric structure), or a waveguide and you want to know its harmonic (definite-ω) modes at a given wavevector **k**. Or, suppose you have a resonant cavity that traps light in a small region for a long time, and you want to know the resonant frequency ω and the decay lifetime (quality factor) *Q*. And, of course, you may want the field patterns of these modes along with how a [given mode is decomposed into a linear superposition of its basis modes](Mode_Decomposition.md). +Another common task in FDTD is to compute resonant modes or eigenmodes of a given structure. For example, suppose you have a [diffraction grating](Python_Tutorials/Mode_Decomposition.md#diffraction-spectrum-of-a-binary-grating), photonic crystal (periodic dielectric structure), or a waveguide and you want to know its harmonic (definite-$\omega$) modes at a given wavevector $\mathbf{k}$. Or, suppose you have a resonant cavity that traps light in a small region for a long time, and you want to know the resonant frequency $\omega$ and the decay lifetime (quality factor) $Q$. And, of course, you may want the field patterns of these modes along with how a [given mode is decomposed into a linear superposition of its basis modes](Mode_Decomposition.md). In order to extract the frequencies and lifetimes (which may be infinite in a lossless system) with FDTD, the basic strategy is simple. You set up the structure with Bloch-periodic and/or absorbing boundaries, depending on whether it is a periodic or open system. Then you excite the mode(s) with a short pulse (broad bandwidth) from a current placed directly inside the cavity/waveguide/whatever. Finally, once the current source is turned off, you have some fields bouncing around inside the system, and you analyze them to extract the frequencies and decay rates. -The simplest form of harmonic analysis would be to compute the Fourier transform of the fields at some point — harmonic modes will yield sharp peaks in the spectrum. This method has serious drawbacks, however, in that high frequency resolution requires a very long running time, and moreover the problem of extracting the decay rates leads to a poorly-conditioned nonlinear fitting problem. Instead, Meep allows you to perform a more sophisticated signal processing algorithm borrowed from NMR spectroscopy — the algorithm is called *filter diagonalization* and is implemented by [Harminv](https://github.com/NanoComp/harminv/blob/master/README.md) package. Harminv extracts all of the frequencies and their decay rates (and amplitudes) within a short time to high accuracy; for example, we have used it to find lifetimes *Q* of 109 periods in a computational run of only a few hundred periods. +The simplest form of harmonic analysis would be to compute the Fourier transform of the fields at some point — harmonic modes will yield sharp peaks in the spectrum. This method has serious drawbacks, however, in that high frequency resolution requires a very long running time, and moreover the problem of extracting the decay rates leads to a poorly-conditioned nonlinear fitting problem. Instead, Meep allows you to perform a more sophisticated signal processing algorithm borrowed from NMR spectroscopy — the algorithm is called *filter diagonalization* and is implemented by [Harminv](https://github.com/NanoComp/harminv/blob/master/README.md) package. Harminv extracts all of the frequencies and their decay rates (and amplitudes) within a short time to high accuracy; for example, we have used it to find lifetimes $Q$ of 109 periods in a computational run of only a few hundred periods. -Once you know the frequencies of the modes, if you want the field patterns you will need to run the simulation again with a *narrow*-bandwidth (long-time) pulse to excite only the mode in question. Unless you want the longest-lifetime mode, in which case you can just run long enough for the other modes to decay away. Given the field patterns, you can then perform other analyses (e.g. decomposing the *Q* into decay rates into different directions via flux computations, finding modal volumes, etcetera). For an example, see [Tutorial/Resonant Modes and Transmission in a Waveguide Cavity](Python_Tutorials/Resonant_Modes_and_Transmission_in_a_Waveguide_Cavity.md#resonant-modes). +Once you know the frequencies of the modes, if you want the field patterns you will need to run the simulation again with a *narrow*-bandwidth (long-time) pulse to excite only the mode in question. Unless you want the longest-lifetime mode, in which case you can just run long enough for the other modes to decay away. Given the field patterns, you can then perform other analyses (e.g. decomposing the $Q$ into decay rates into different directions via flux computations, finding modal volumes, etcetera). For an example, see [Tutorial/Resonant Modes and Transmission in a Waveguide Cavity](Python_Tutorials/Resonant_Modes_and_Transmission_in_a_Waveguide_Cavity.md#resonant-modes). Why should you use Meep instead of [MPB](https://mpb.readthedocs.io) to compute the modes? Unlike MPB, Meep supports metallic and absorbing materials, can compute lossy resonant modes, can quickly compute large numbers of ω's at once by a single short pulse, and can efficiently extract modes in the interior of the spectrum (e.g. in a band gap). Why should you ever use MPB, then? MPB is quicker at computing the lowest-ω modes than Meep, gives you both the ω and the fields at once, and has no problem resolving closely-spaced frequencies. Moreover, computing modes in time domain is somewhat subtle and requires care — for example, one will occasionally miss a mode if the source happens to be nearly orthogonal to it or if it is too close to another mode; conversely, the signal processing will sometimes accidentally identify spurious peak frequencies. Also, studying periodic systems with non-rectangular unit cells is more subtle in Meep than in MPB. MPB is much more straightforward and reliable, albeit more limited in some ways. diff --git a/python/adjoint/utils.py b/python/adjoint/utils.py index 38ffbd8f9..e6a5d1581 100644 --- a/python/adjoint/utils.py +++ b/python/adjoint/utils.py @@ -63,9 +63,9 @@ def get_gradient(self, sim, fields_a, fields_f, frequencies, finite_difference_s spatial_shape = sim.get_array_slice_dimensions(component, vol=self.volume)[0] if (fields_a[component_idx][0,...].size == 1): fields_a[component_idx] = onp.zeros(onp.insert(spatial_shape,0,num_freqs), - dtype=onp.float32 if mp.is_single_precision() else onp.float64) + dtype=onp.complex64 if mp.is_single_precision() else onp.complex128) fields_f[component_idx] = onp.zeros(onp.insert(spatial_shape,0,num_freqs), - dtype=onp.float32 if mp.is_single_precision() else onp.float64) + dtype=onp.complex64 if mp.is_single_precision() else onp.complex128) if _check_if_cylindrical(sim): '''For some reason, get_dft_array returns the field components in a different order than the convention used From 6b5466b1c96791b6b350d6e7a5bbdc4d3773208a Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 11:01:01 -0500 Subject: [PATCH 033/155] update homebrew instructions for hdf5 and openblas (fixes #1850) --- doc/docs/Installation.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/docs/Installation.md b/doc/docs/Installation.md index 2783688ef..e564e2e4b 100644 --- a/doc/docs/Installation.md +++ b/doc/docs/Installation.md @@ -137,16 +137,20 @@ The first steps are: ```sh brew doctor -brew install homebrew/science/hdf5 homebrew/science/openblas guile fftw h5utils +brew install hdf5 guile fftw gsl libpng autoconf automake libtool swig +``` +If you don't have your own Python installation (e.g. via [miniforge](https://github.com/conda-forge/miniforge)), you should install `numpy` and `matplotlib`: +```sh +pip3 install numpy matplotlib ``` Now, install the Harminv, libctl, MPB, and Meep packages from source. Download [Harminv](https://github.com/NanoComp/harminv/blob/master/README.md) and, in the `harminv` directory, do: ```sh -./configure && make && make install +./configure CPPFLAGS="-I$(brew --prefix)/include" LDFLAGS="-L$(brew --prefix)/lib" && make && make install ``` -Use the same commands for [libctl](https://libctl.readthedocs.io), [MPB](https://mpb.readthedocs.io), and Meep. For more detailed information, see [Build From Source](Build_From_Source.md). +Use the same commands for [libctl](https://libctl.readthedocs.io), [MPB](https://mpb.readthedocs.io), (optionally) [h5utils](https://github.com/NanoComp/h5utils), (optionally) [libGDSII](https://github.com/HomerReid/libGDSII), and Meep. For more detailed information, see [Build From Source](Build_From_Source.md). You are done, and can now run Meep (Scheme interface) just by typing `meep`. You can run `make check` in the meep directory if you want to perform a self-test. From 6e0f047496e674546e40252422435b60230dc5f3 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 11:55:32 -0500 Subject: [PATCH 034/155] recommend python3 on macos --- doc/docs/Installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/docs/Installation.md b/doc/docs/Installation.md index e564e2e4b..67003b6b8 100644 --- a/doc/docs/Installation.md +++ b/doc/docs/Installation.md @@ -147,7 +147,7 @@ pip3 install numpy matplotlib Now, install the Harminv, libctl, MPB, and Meep packages from source. Download [Harminv](https://github.com/NanoComp/harminv/blob/master/README.md) and, in the `harminv` directory, do: ```sh -./configure CPPFLAGS="-I$(brew --prefix)/include" LDFLAGS="-L$(brew --prefix)/lib" && make && make install +./configure CPPFLAGS="-I$(brew --prefix)/include" LDFLAGS="-L$(brew --prefix)/lib" PYTHON=python3 && make && make install ``` Use the same commands for [libctl](https://libctl.readthedocs.io), [MPB](https://mpb.readthedocs.io), (optionally) [h5utils](https://github.com/NanoComp/h5utils), (optionally) [libGDSII](https://github.com/HomerReid/libGDSII), and Meep. For more detailed information, see [Build From Source](Build_From_Source.md). From 10d5fdef14899915edc1df58ed5eb865ba448b09 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 12:11:39 -0500 Subject: [PATCH 035/155] silence compiler warnings --- src/meepgeom.cpp | 24 +++++++++++------------- src/meepgeom.hpp | 5 ++--- src/mympi.cpp | 2 ++ 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index b88ccc5e9..8b9fa157c 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -563,7 +563,7 @@ void epsilon_material_grid(material_data *md, double u) { // Linearly interpolate dc epsilon values cinterp_tensors(m1->epsilon_diag, m1->epsilon_offdiag, m2->epsilon_diag, m2->epsilon_offdiag, &mm->epsilon_diag, &mm->epsilon_offdiag, u); - + // Interpolate resonant strength from d.p. vector3 zero_vec; zero_vec.x = zero_vec.y = zero_vec.z = 0; @@ -651,7 +651,7 @@ geom_epsilon::geom_epsilon(geometric_object_list g, material_type_list mlist, geometry.items[i].material = new material_data(); static_cast(geometry.items[i].material)->copy_from(*(material_data *)(g.items[i].material)); } - + extra_materials = mlist; current_pol = NULL; @@ -1551,8 +1551,6 @@ static bool has_conductivity(const material_type &md, meep::component c) { } bool geom_epsilon::has_conductivity(meep::component c) { - medium_struct *mm; - FOR_DIRECTIONS(d) FOR_SIDES(b) { if (cond[d][b].prof) return true; } @@ -1983,7 +1981,7 @@ void set_materials_from_geometry(meep::structure *s, geometric_object_list g, ve absorber_list alist, material_type_list extra_materials) { meep_geom::geom_epsilon *geps = meep_geom::make_geom_epsilon(s, &g, center, _ensure_periodicity, _default_material, extra_materials); - set_materials_from_geom_epsilon(s, geps, center, use_anisotropic_averaging, tol, + set_materials_from_geom_epsilon(s, geps, use_anisotropic_averaging, tol, maxeval, alist); delete geps; } @@ -1991,9 +1989,9 @@ void set_materials_from_geometry(meep::structure *s, geometric_object_list g, ve /* from a previously created geom_epsilon object, set the materials as specified */ void set_materials_from_geom_epsilon(meep::structure *s, geom_epsilon *geps, - vector3 center, bool use_anisotropic_averaging, + bool use_anisotropic_averaging, double tol, int maxeval, absorber_list alist) { - + // store for later use in gradient calculations geps->tol = tol; geps->maxeval = maxeval; @@ -2539,7 +2537,7 @@ double vec_to_value(vector3 diag, vector3 offdiag, int idx) { } void invert_tensor(std::complex t_inv[9], std::complex t[9]) { - + #define m(x,y) t[x*3+y] #define minv(x,y) t_inv[x*3+y] std::complex det = m(0, 0) * (m(1, 1) * m(2, 2) - m(2, 1) * m(1, 2)) - @@ -2555,8 +2553,8 @@ void invert_tensor(std::complex t_inv[9], std::complex t[9]) { minv(2, 0) = (m(1, 0) * m(2, 1) - m(2, 0) * m(1, 1)) * invdet; minv(2, 1) = (m(2, 0) * m(0, 1) - m(0, 0) * m(2, 1)) * invdet; minv(2, 2) = (m(0, 0) * m(1, 1) - m(1, 0) * m(0, 1)) * invdet; -#undef m(x,y) -#undef minv(x,y) +#undef m +#undef minv } void get_chi1_tensor_disp(std::complex tensor[9], const meep::vec &r, double freq, geom_epsilon *geps) { @@ -2649,7 +2647,7 @@ std::complex get_material_gradient( ) { /*Compute the Aᵤx product from the -λᵀAᵤx calculation. The current adjoint (λ) field component (adjoint_c) - determines which row of Aᵤ we care about. + determines which row of Aᵤ we care about. The current forward (x) field component (forward_c) determines which column of Aᵤ we care about. @@ -2661,7 +2659,7 @@ std::complex get_material_gradient( 2. We use eff_chi1inv_row_disp() for all other cases (at the expense of not accounting for subpixel smoothing, if there were any). - + For now we do a finite difference approach to estimate the gradient of the system matrix A since it's fairly accurate, cheap, and easy to generalize.*/ @@ -2967,7 +2965,7 @@ void material_grids_addgradient(double *v, size_t ng, std::complex fwd_avg, fwd1, fwd2, prod; + std::complex fwd_avg, fwd1, fwd2; ptrdiff_t fwd1_idx, fwd2_idx; //identify the first corner of the forward fields diff --git a/src/meepgeom.hpp b/src/meepgeom.hpp index 5802807e7..3be1e0c39 100644 --- a/src/meepgeom.hpp +++ b/src/meepgeom.hpp @@ -173,13 +173,13 @@ class geom_epsilon : public meep::material_function { public: double u_p = 0; - geom_box_tree geometry_tree; + geom_box_tree geometry_tree; geom_box_tree restricted_tree; geometric_object_list geometry; cond_profile cond[5][2]; // [direction][side] double tol=DEFAULT_SUBPIXEL_TOL; int maxeval=DEFAULT_SUBPIXEL_MAXEVAL; - + geom_epsilon(geometric_object_list g, material_type_list mlist, const meep::volume &v); geom_epsilon(const geom_epsilon &geps1); // copy constructor virtual ~geom_epsilon(); @@ -239,7 +239,6 @@ void set_materials_from_geometry(meep::structure *s, geometric_object_list g, material_type _default_material = vacuum, absorber_list alist = 0, material_type_list extra_materials = material_type_list()); void set_materials_from_geom_epsilon(meep::structure *s, geom_epsilon *geps, - vector3 center = make_vector3(), bool use_anisotropic_averaging = true, double tol = DEFAULT_SUBPIXEL_TOL, int maxeval = DEFAULT_SUBPIXEL_MAXEVAL, diff --git a/src/mympi.cpp b/src/mympi.cpp index 331def6e6..1f018166a 100644 --- a/src/mympi.cpp +++ b/src/mympi.cpp @@ -171,6 +171,8 @@ static void _set_zero_subnormals(bool iszero) else state &= ~flags; _mm_setcsr(state); +#else + (void) iszero; // unused #endif } void set_zero_subnormals(bool iszero) From a1ebf90f6d6f7e81261d185c1aad67d69297e782 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 12:14:05 -0500 Subject: [PATCH 036/155] whoops, missing commit --- python/meep.i | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/meep.i b/python/meep.i index bb7f1c80c..71a917aed 100644 --- a/python/meep.i +++ b/python/meep.i @@ -843,7 +843,7 @@ meep::volume_list *make_volume_list(const meep::volume &v, int c, //-------------------------------------------------- %inline %{ -void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObject *fields_f, +void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObject *fields_f, meep::grid_volume *grid_volume, meep::volume *where, PyObject *frequencies, meep_geom::geom_epsilon *geps, PyObject *fields_shapes, double fd_step) { // clean the gradient array @@ -1997,7 +1997,7 @@ meep_geom::geom_epsilon* _set_materials(meep::structure * s, extra_materials); } if (set_materials) { - meep_geom::set_materials_from_geom_epsilon(s, geps, center, use_anisotropic_averaging, tol, + meep_geom::set_materials_from_geom_epsilon(s, geps, use_anisotropic_averaging, tol, maxeval,alist); } From afab63b507fdd98afdc2790be2ea423086e8c7ba Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 12:28:43 -0500 Subject: [PATCH 037/155] tests need scipy and autograd --- doc/docs/Installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/docs/Installation.md b/doc/docs/Installation.md index 67003b6b8..3bc95df29 100644 --- a/doc/docs/Installation.md +++ b/doc/docs/Installation.md @@ -139,9 +139,9 @@ The first steps are: brew doctor brew install hdf5 guile fftw gsl libpng autoconf automake libtool swig ``` -If you don't have your own Python installation (e.g. via [miniforge](https://github.com/conda-forge/miniforge)), you should install `numpy` and `matplotlib`: +If you don't have your own Python installation (e.g. via [miniforge](https://github.com/conda-forge/miniforge)), you should install `numpy` and `matplotlib` and other packages used by Meep and its tests: ```sh -pip3 install numpy matplotlib +pip3 install numpy matplotlib scipy autograd ``` Now, install the Harminv, libctl, MPB, and Meep packages from source. Download [Harminv](https://github.com/NanoComp/harminv/blob/master/README.md) and, in the `harminv` directory, do: From 2e8a4d7ce13c2c9a0f8e5048c05f0eb377f04bf0 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 12:33:31 -0500 Subject: [PATCH 038/155] missing sudo --- doc/docs/Installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/docs/Installation.md b/doc/docs/Installation.md index 3bc95df29..e9616b2f4 100644 --- a/doc/docs/Installation.md +++ b/doc/docs/Installation.md @@ -147,7 +147,7 @@ pip3 install numpy matplotlib scipy autograd Now, install the Harminv, libctl, MPB, and Meep packages from source. Download [Harminv](https://github.com/NanoComp/harminv/blob/master/README.md) and, in the `harminv` directory, do: ```sh -./configure CPPFLAGS="-I$(brew --prefix)/include" LDFLAGS="-L$(brew --prefix)/lib" PYTHON=python3 && make && make install +./configure CPPFLAGS="-I$(brew --prefix)/include" LDFLAGS="-L$(brew --prefix)/lib" PYTHON=python3 && make && sudo make install ``` Use the same commands for [libctl](https://libctl.readthedocs.io), [MPB](https://mpb.readthedocs.io), (optionally) [h5utils](https://github.com/NanoComp/h5utils), (optionally) [libGDSII](https://github.com/HomerReid/libGDSII), and Meep. For more detailed information, see [Build From Source](Build_From_Source.md). From ab832db7828d395ea665ca6f2223ea49459b1c58 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 12:36:05 -0500 Subject: [PATCH 039/155] parameterized package is also used for tests --- doc/docs/Installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/docs/Installation.md b/doc/docs/Installation.md index e9616b2f4..39a1a9f7c 100644 --- a/doc/docs/Installation.md +++ b/doc/docs/Installation.md @@ -141,7 +141,7 @@ brew install hdf5 guile fftw gsl libpng autoconf automake libtool swig ``` If you don't have your own Python installation (e.g. via [miniforge](https://github.com/conda-forge/miniforge)), you should install `numpy` and `matplotlib` and other packages used by Meep and its tests: ```sh -pip3 install numpy matplotlib scipy autograd +pip3 install numpy matplotlib scipy autograd parameterized ``` Now, install the Harminv, libctl, MPB, and Meep packages from source. Download [Harminv](https://github.com/NanoComp/harminv/blob/master/README.md) and, in the `harminv` directory, do: From 361c03cb794311f7764954af23734af6996f75d2 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 15:11:10 -0500 Subject: [PATCH 040/155] h5py and jax on mac --- doc/docs/Installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/docs/Installation.md b/doc/docs/Installation.md index 39a1a9f7c..400754422 100644 --- a/doc/docs/Installation.md +++ b/doc/docs/Installation.md @@ -141,7 +141,7 @@ brew install hdf5 guile fftw gsl libpng autoconf automake libtool swig ``` If you don't have your own Python installation (e.g. via [miniforge](https://github.com/conda-forge/miniforge)), you should install `numpy` and `matplotlib` and other packages used by Meep and its tests: ```sh -pip3 install numpy matplotlib scipy autograd parameterized +HDF5_DIR="$(brew --prefix hdf5)" pip3 install numpy matplotlib scipy autograd jax parameterized h5py ``` Now, install the Harminv, libctl, MPB, and Meep packages from source. Download [Harminv](https://github.com/NanoComp/harminv/blob/master/README.md) and, in the `harminv` directory, do: From c9092ebdc442a9fae7377a9952d74e6aa713bc18 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 15:14:25 -0500 Subject: [PATCH 041/155] note on autogen.sh for git clone --- doc/docs/Installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/docs/Installation.md b/doc/docs/Installation.md index 400754422..d3137cf6b 100644 --- a/doc/docs/Installation.md +++ b/doc/docs/Installation.md @@ -150,7 +150,7 @@ Now, install the Harminv, libctl, MPB, and Meep packages from source. Download [ ./configure CPPFLAGS="-I$(brew --prefix)/include" LDFLAGS="-L$(brew --prefix)/lib" PYTHON=python3 && make && sudo make install ``` -Use the same commands for [libctl](https://libctl.readthedocs.io), [MPB](https://mpb.readthedocs.io), (optionally) [h5utils](https://github.com/NanoComp/h5utils), (optionally) [libGDSII](https://github.com/HomerReid/libGDSII), and Meep. For more detailed information, see [Build From Source](Build_From_Source.md). +Use the same commands for [libctl](https://libctl.readthedocs.io), [MPB](https://mpb.readthedocs.io), (optionally) [h5utils](https://github.com/NanoComp/h5utils), (optionally) [libGDSII](https://github.com/HomerReid/libGDSII), and Meep. For more detailed information, see [Build From Source](Build_From_Source.md). Note that if you are installing from a `git clone` rather than from a release `.tar.gz` file, you will need to first run `sh autogen.sh`, and you should add `--enable-maintainer-mode` to the `configure` arguments. You are done, and can now run Meep (Scheme interface) just by typing `meep`. You can run `make check` in the meep directory if you want to perform a self-test. From 7b65ab4728c0151211b490dbb53996fb6229f75e Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 15:21:27 -0500 Subject: [PATCH 042/155] xcode installation shortcut --- doc/docs/Installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/docs/Installation.md b/doc/docs/Installation.md index d3137cf6b..c1b35767a 100644 --- a/doc/docs/Installation.md +++ b/doc/docs/Installation.md @@ -131,7 +131,7 @@ Since [macOS](https://en.wikipedia.org/wiki/macOS) is, at its heart, a Unix syst The first steps are: -- Install [Xcode](https://en.wikipedia.org/wiki/Xcode), the development/compiler package from Apple, free from the [Apple Xcode web page](https://developer.apple.com/xcode/). +- Install [Xcode](https://en.wikipedia.org/wiki/Xcode), the development/compiler package from Apple: type `xcode-select --install` in the [Terminal](https://en.wikipedia.org/wiki/Terminal_(macOS)). - Install Homebrew: download from the [Homebrew site](http://brew.sh/) and follow the instructions there. - Run the following commands in the terminal to compile and install the prerequisites. This may take a while to complete because it will install lots of other stuff first From e35a0c7a0ab7ed22bd460ca5ad924e7ce577a46b Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Tue, 14 Dec 2021 19:15:21 -0800 Subject: [PATCH 043/155] bug fix for get_epsilon_point and cell boundary in parallel simulation (#1849) * bug fix for get_epsilon_point and cell boundary in parallel simulation * check for six digits in test_material_grid.py because of single precision --- python/tests/test_material_grid.py | 2 +- src/monitor.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/tests/test_material_grid.py b/python/tests/test_material_grid.py index d8750987a..25b6b4b50 100644 --- a/python/tests/test_material_grid.py +++ b/python/tests/test_material_grid.py @@ -159,7 +159,7 @@ def test_subpixel_smoothing(self): def test_symmetry(self): tran_nosym = compute_transmittance(False) tran_sym = compute_transmittance(True) - self.assertAlmostEqual(tran_nosym, tran_sym) + self.assertAlmostEqual(tran_nosym, tran_sym, places=6) if __name__ == '__main__': unittest.main() diff --git a/src/monitor.cpp b/src/monitor.cpp index 6ed10205c..46082332f 100644 --- a/src/monitor.cpp +++ b/src/monitor.cpp @@ -178,7 +178,7 @@ complex fields::get_chi1inv(component c, direction d, const ivec &origlo 0); return parallel ? sum_to_all(val) : val; } - return d == component_direction(c) ? 1.0 : 0; // default to vacuum outside computational cell + return d == component_direction(c) && (parallel || am_master()) ? 1.0 : 0; // default to vacuum outside computational cell } complex fields_chunk::get_chi1inv(component c, direction d, const ivec &iloc, From 806c404cca91ae38ce26d5503099d245f2725d22 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Mon, 20 Dec 2021 11:41:11 -0800 Subject: [PATCH 044/155] unit test for conductivity (#1857) * unit test for conductivity * describe in the docs how to model the attenutation coefficient using conductivity * Update python/tests/test_conductivity.py Co-authored-by: Steven G. Johnson --- doc/docs/Materials.md | 8 ++- python/Makefile.am | 3 +- python/tests/test_conductivity.py | 100 ++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 python/tests/test_conductivity.py diff --git a/doc/docs/Materials.md b/doc/docs/Materials.md index 226af4700..79b1912af 100644 --- a/doc/docs/Materials.md +++ b/doc/docs/Materials.md @@ -88,13 +88,15 @@ Meep can keep track of this energy for the Lorentzian polarizability terms but n Conductivity and Complex ε -------------------------- -Often, you only care about the absorption loss in a narrow bandwidth, where you just want to set the imaginary part of ε (or μ) to some known experimental value, in the same way that you often just care about setting a dispersionless real ε that is the correct value in your bandwidth of interest. +Often, you only care about the absorption loss in a narrow bandwidth, where you just want to set the imaginary part of $\varepsilon$ (or $\mu$) to some known experimental value, in the same way that you often just care about setting a dispersionless real $\varepsilon$ that is the correct value in your bandwidth of interest. One approach to this problem would be allowing you to specify a constant, frequency-independent, imaginary part of $\varepsilon$, but this has the disadvantage of requiring the simulation to employ complex fields which double the memory and time requirements, and also tends to be numerically unstable. Instead, the approach in Meep is for you to set the conductivity $\sigma_D$ (or $\sigma_B$ for an imaginary part of $\mu$), chosen so that $\mathrm{Im}\, \varepsilon = \varepsilon_\infty \sigma_D / \omega$ is the correct value at your frequency $\omega$ of interest. Note that, in Meep, you specify $f = \omega/2\pi$ instead of $\omega$ for the frequency, however, so you need to include the factor of $2\pi$ when computing the corresponding imaginary part of $\varepsilon$. Conductivities can be implemented with purely real fields, so they are not nearly as expensive as implementing a frequency-independent complex $\varepsilon$ or $\mu$. -For example, suppose you want to simulate a medium with $\varepsilon = 3.4 + 0.101i$ at a frequency 0.42 (in your Meep units), and you only care about the material in a narrow bandwidth around this frequency (i.e. you don't need to simulate the full experimental frequency-dependent permittivity). Then, in Meep, you could use `meep.Medium(epsilon=3.4, D_conductivity=2*math.pi*0.42*0.101/3.4)` in Python or `(make medium (epsilon 3.4) (D-conductivity (* 2 pi 0.42 0.101 (/ 3.4))))` in Scheme; i.e. $\varepsilon_\infty = \mathrm{Re}[\varepsilon] = 3.4$ and $\sigma_D = \omega \, \mathrm{Im}[\varepsilon / \varepsilon_\infty] = (2\pi \, 0.42) \, 0.101 / 3.4$. +For example, suppose you want to simulate a medium with $\varepsilon = 3.4 + 0.101i$ at a frequency 0.42 (in your Meep units), and you only care about the material in a narrow bandwidth around this frequency (i.e. you don't need to simulate the full experimental frequency-dependent permittivity). Then, in Meep, you could use `meep.Medium(epsilon=3.4, D_conductivity=2*math.pi*0.42*0.101/3.4)` in Python or `(make medium (epsilon 3.4) (D-conductivity (* 2 pi 0.42 0.101 (/ 3.4))))` in Scheme; i.e. $\varepsilon_\infty = \mathrm{Re}[\varepsilon] = 3.4$ and $\sigma_D = \omega \, \mathrm{Im}[\varepsilon] / \varepsilon_\infty = (2\pi \, 0.42) \, 0.101 / 3.4$. -**Note**: the "conductivity" in Meep is slightly different from the conductivity you might find in a textbook, because for computational convenience it appears as $\sigma_D \mathbf{D}$ in our Maxwell equations rather than the more-conventional $\sigma \mathbf{E}$; this just means that our definition is different from the usual electric conductivity by a factor of ε. Also, just as Meep uses the dimensionless relative permittivity for ε, it uses nondimensionalized units of 1/*a* (where *a* is your unit of distance) for the conductivities $\sigma_{D,B}$. If you have the electric conductivity $\sigma$ in SI units and want to convert to $\sigma_D$ in Meep units, you can simply use the formula: $\sigma_D = (a/c) \sigma / \varepsilon_r \varepsilon_0$ where *a* is your unit of distance in meters, *c* is the vacuum speed of light in m/s, $\varepsilon_0$ is the SI vacuum permittivity, and $\varepsilon_r$ is the real relative permittivity. +You can also use the $\sigma_D$ feature to model the [attenuation coefficient](https://en.wikipedia.org/wiki/Attenuation_coefficient) $\alpha$ (units of e.g. dB/cm) obtained from experimental measurements (i.e., ellipsometry). This involves first [converting $\alpha$ into a complex refractive index](https://en.wikipedia.org/wiki/Mathematical_descriptions_of_opacity#Complex_refractive_index) (which is then converted into a complex permittivity) with imaginary part given by $\lambda_0\alpha/(4\pi)$ where $\lambda_0$ is the vacuum wavelength. + +**Note**: the "conductivity" in Meep is slightly different from the conductivity you might find in a textbook, because for computational convenience it appears as $\sigma_D \mathbf{D}$ in our Maxwell equations rather than the more-conventional $\sigma \mathbf{E}$; this just means that our definition is different from the usual electric conductivity by a factor of $\varepsilon$. Also, just as Meep uses the dimensionless relative permittivity for $\varepsilon$, it uses nondimensionalized units of 1/*a* (where *a* is your unit of distance) for the conductivities $\sigma_{D,B}$. If you have the electric conductivity $\sigma$ in SI units and want to convert to $\sigma_D$ in Meep units, you can simply use the formula: $\sigma_D = (a/c) \sigma / \varepsilon_r \varepsilon_0$ where *a* is your unit of distance in meters, *c* is the vacuum speed of light in m/s, $\varepsilon_0$ is the SI vacuum permittivity, and $\varepsilon_r$ is the real relative permittivity. Nonlinearity ------------ diff --git a/python/Makefile.am b/python/Makefile.am index 793eef539..cac075de8 100644 --- a/python/Makefile.am +++ b/python/Makefile.am @@ -43,13 +43,14 @@ TESTS = \ $(TEST_DIR)/test_antenna_radiation.py \ $(TEST_DIR)/test_array_metadata.py \ $(TEST_DIR)/test_bend_flux.py \ - $(TEST_DIR)/test_binary_partition_utils.py \ + $(TEST_DIR)/test_binary_partition_utils.py \ $(BINARY_GRATING_TEST) \ $(TEST_DIR)/test_cavity_arrayslice.py \ $(TEST_DIR)/test_cavity_farfield.py \ $(TEST_DIR)/test_chunk_balancer.py \ $(TEST_DIR)/test_chunk_layout.py \ $(TEST_DIR)/test_chunks.py \ + $(TEST_DIR)/test_conductivity.py \ $(TEST_DIR)/test_cyl_ellipsoid.py \ $(TEST_DIR)/test_dft_energy.py \ $(TEST_DIR)/test_dft_fields.py \ diff --git a/python/tests/test_conductivity.py b/python/tests/test_conductivity.py new file mode 100644 index 000000000..2aa9a7616 --- /dev/null +++ b/python/tests/test_conductivity.py @@ -0,0 +1,100 @@ +import unittest +import numpy as np +import meep as mp + +dB_cm_to_dB_um = 1e-4 + +class TestConductivity(unittest.TestCase): + + def wvg_flux(self, res, att_coeff): + """ + Computes the Poynting flux in a single-mode waveguide at two + locations (5 and 10 μm) downstream from the source given the + grid resolution res (pixels/μm) and material attenuation + coefficient att_coeff (dB/cm). + """ + + cell_size = mp.Vector3(14.,14.) + + pml_layers = [mp.PML(thickness=2.)] + + w = 1. # width of waveguide + + fsrc = 0.15 # frequency (in vacuum) + + # note: MPB can only compute modes of lossless material systems. + # The imaginary part of ε is ignored and the launched + # waveguide mode is therefore inaccurate. For small values + # of imag(ε) (which is proportional to att_coeff), this + # inaccuracy tends to be insignificant. + sources = [mp.EigenModeSource(src=mp.GaussianSource(fsrc,fwidth=0.2*fsrc), + center=mp.Vector3(-5.), + size=mp.Vector3(y=10.), + eig_parity=mp.EVEN_Y+mp.ODD_Z)] + + # ref: https://en.wikipedia.org/wiki/Mathematical_descriptions_of_opacity + # Note that this is the loss of a planewave, which is only approximately + # the loss of a waveguide mode. In principle, we could compute the latter + # semi-analytically if we wanted to run this unit test to greater accuracy + # (e.g. to test convergence with resolution). + n_eff = np.sqrt(12.) + 1j * (1/fsrc) * (dB_cm_to_dB_um * att_coeff) / (4 * np.pi) + eps_eff = n_eff * n_eff + # ref: https://meep.readthedocs.io/en/latest/Materials/#conductivity-and-complex + sigma_D = 2 * np.pi * fsrc * np.imag(eps_eff) / np.real(eps_eff) + + geometry = [mp.Block(center=mp.Vector3(), + size=mp.Vector3(mp.inf,w,mp.inf), + material=mp.Medium(epsilon=np.real(eps_eff), + D_conductivity=sigma_D))] + + sim = mp.Simulation(cell_size=cell_size, + resolution=res, + boundary_layers=pml_layers, + sources=sources, + geometry=geometry, + symmetries=[mp.Mirror(mp.Y)]) + + tran1 = sim.add_flux(fsrc, + 0, + 1, + mp.FluxRegion(center=mp.Vector3(x=0.), size=mp.Vector3(y=10.))) + + tran2 = sim.add_flux(fsrc, + 0, + 1, + mp.FluxRegion(center=mp.Vector3(x=5.), size=mp.Vector3(y=10.))) + + sim.run(until_after_sources=20) + + return mp.get_fluxes(tran1)[0], mp.get_fluxes(tran2)[0] + + + def test_conductivity(self): + res = 25. # pixels/μm + + # compute the incident flux for a lossless waveguide + incident_flux1, incident_flux2 = self.wvg_flux(res, 0.) + self.assertAlmostEqual(incident_flux1/incident_flux2, 1., places=2) + print("incident_flux:, {} (measured), 1.0 (expected)".format(incident_flux2/incident_flux1)) + + # compute the flux for a lossy waveguide + att_coeff = 37.46 # dB/cm + attenuated_flux1, attenuated_flux2 = self.wvg_flux(res, att_coeff) + + L1 = 5. + expected_att1 = np.exp(-att_coeff * dB_cm_to_dB_um * L1) + self.assertAlmostEqual(attenuated_flux1/incident_flux2, expected_att1, places=2) + print("flux:, {}, {:.6f} (measured), {:.6f} (expected)".format(L1, + attenuated_flux1/incident_flux2, + expected_att1)) + + L2 = 10. + expected_att2 = np.exp(-att_coeff * dB_cm_to_dB_um * L2) + self.assertAlmostEqual(attenuated_flux2/incident_flux2, expected_att2, places=2) + print("flux:, {}, {:.6f} (measured), {:.6f} (expected)".format(L2, + attenuated_flux2/incident_flux2, + expected_att2)) + + +if __name__ == '__main__': + unittest.main() From 0b86ad0b7f1f3737797ffb9b5093c2949d0343f2 Mon Sep 17 00:00:00 2001 From: Andreas Hoenselaar Date: Mon, 20 Dec 2021 14:41:05 -0800 Subject: [PATCH 045/155] Fix the failure message for absorber 1D test (#1859) --- tests/absorber-1d-ll.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/absorber-1d-ll.cpp b/tests/absorber-1d-ll.cpp index 345f9791c..7c052052f 100644 --- a/tests/absorber-1d-ll.cpp +++ b/tests/absorber-1d-ll.cpp @@ -116,7 +116,7 @@ int main(int argc, char *argv[]) { fabs(fFinal - fFinal_ref) > 1.0e-6 * fabs(fFinal_ref)) { master_printf("{f50, tFinal, fFinal}={%e,%e,%e}\n", f50, tFinal, fFinal); master_printf(" should be:\n"); - master_printf("{f50, tFinal, fFinal}={%e,%e,%e}\n", f50, tFinal, fFinal); + master_printf("{f50, tFinal, fFinal}={%e,%e,%e}\n", f50_ref, tFinal_ref, fFinal_ref); meep::abort("Test failed."); } else if (verbose) From 3847a09fe8914b794b9f4d6a891ff5ab0eefdb33 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Tue, 21 Dec 2021 09:40:02 -0800 Subject: [PATCH 046/155] add missing fixed field phase to MPB unit test (#1860) --- python/tests/test_mpb.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/python/tests/test_mpb.py b/python/tests/test_mpb.py index 367e15675..ef6e3a4c4 100644 --- a/python/tests/test_mpb.py +++ b/python/tests/test_mpb.py @@ -408,6 +408,7 @@ def test_output_field_to_file(self): def test_compute_field_energy(self): ms = self.init_solver() ms.run_te() + mpb.fix_hfield_phase(ms, 8) ms.get_dfield(8) field_pt = ms.get_field_point(mp.Vector3(0.5, 0.5)) bloch_field_pt = ms.get_bloch_field_point(mp.Vector3(0.5, 0.5)) @@ -701,8 +702,10 @@ def test_diamond(self): def get_dpwr(ms, band): dpwr.append(ms.get_dpwr(band)) - ms.run(mpb.output_at_kpoint(mp.Vector3(0, 0.625, 0.375), mpb.fix_dfield_phase, - mpb.output_dpwr, get_dpwr)) + ms.run(mpb.output_at_kpoint(mp.Vector3(0, 0.625, 0.375), + mpb.fix_dfield_phase, + mpb.output_dpwr, + get_dpwr)) expected_brd = [ ((0.0, mp.Vector3(0.0, 0.0, 0.0)), @@ -812,7 +815,8 @@ def test_line_defect(self): ms.tolerance = 1e-12 ms.run_tm(mpb.output_at_kpoint(k_points[len(k_points) // 2]), - mpb.fix_efield_phase, mpb.output_efield_z) + mpb.fix_efield_phase, + mpb.output_efield_z) ref_fn = 'line-defect-e.k04.b12.z.tm.h5' ref_path = os.path.join(self.data_dir, ref_fn) @@ -973,8 +977,9 @@ def test_tri_rods(self): ms.tolerance = 1e-12 ms.filename_prefix = self.filename_prefix - ms.run_tm(mpb.output_at_kpoint(mp.Vector3(1 / -3, 1 / 3), mpb.fix_efield_phase, - mpb.output_efield_z)) + ms.run_tm(mpb.output_at_kpoint(mp.Vector3(1 / -3, 1 / 3), + mpb.fix_efield_phase, + mpb.output_efield_z)) ref_fn = 'tri-rods-e.k11.b08.z.tm.h5' ref_path = os.path.join(self.data_dir, ref_fn) From 358ecda427d27f1babc8329b7afea200c39ebbb4 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Thu, 23 Dec 2021 19:20:27 -0800 Subject: [PATCH 047/155] fix memory leak in array-slice-ll.cpp (#1865) * fix memory leak in array-slice-ll.cpp * reinstate line break --- tests/array-slice-ll.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/array-slice-ll.cpp b/tests/array-slice-ll.cpp index ea4e9aded..e13967aad 100644 --- a/tests/array-slice-ll.cpp +++ b/tests/array-slice-ll.cpp @@ -248,5 +248,9 @@ int main(int argc, char *argv[]) { }; // if (write_files) ... else ... + for (int n = 0; n < no; n++) { + geometric_object_destroy(objects[n]); + } + return 0; } From 5ad58a8b2bf5ed9bcb25c98546f0989ae80b2a9d Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Fri, 24 Dec 2021 12:52:34 -0800 Subject: [PATCH 048/155] fix memory leak in cyl-ellipsoid-ll.cpp (#1866) --- tests/cyl-ellipsoid-ll.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/cyl-ellipsoid-ll.cpp b/tests/cyl-ellipsoid-ll.cpp index 275a53a0d..c6c5ca597 100644 --- a/tests/cyl-ellipsoid-ll.cpp +++ b/tests/cyl-ellipsoid-ll.cpp @@ -129,7 +129,11 @@ int main(int argc, char *argv[]) { // (make ellipsoid (center 0 0 0) (size 1 2 infinity) // (material air)))) double n = 3.5; // index of refraction - meep_geom::material_type dielectric = meep_geom::make_dielectric(n * n); + auto material_deleter = [](meep_geom::material_data *m) { + meep_geom::material_free(m); + }; + std::unique_ptr dielectric( + meep_geom::make_dielectric(n * n), material_deleter); geometric_object objects[2]; vector3 center = {0.0, 0.0, 0.0}; double radius = 3.0; @@ -138,7 +142,7 @@ int main(int argc, char *argv[]) { vector3 yhat = {0.0, 1.0, 0.0}; vector3 zhat = {0.0, 0.0, 1.0}; vector3 size = {1.0, 2.0, 1.0e20}; - objects[0] = make_cylinder(dielectric, center, radius, height, zhat); + objects[0] = make_cylinder(dielectric.get(), center, radius, height, zhat); objects[1] = make_ellipsoid(meep_geom::vacuum, center, xhat, yhat, zhat, size); geometric_object_list g = {2, objects}; meep_geom::set_materials_from_geometry(&the_structure, g); @@ -191,5 +195,9 @@ int main(int argc, char *argv[]) { meep::abort("field output error in cyl-ellipsoid-ll"); }; + for (int n = 0; n < 2; n++) { + geometric_object_destroy(objects[n]); + } + return 0; } From e51079f0de0ee324b140e1cf57876461f299c200 Mon Sep 17 00:00:00 2001 From: simbilod <46427609+simbilod@users.noreply.github.com> Date: Sun, 26 Dec 2021 17:41:54 -0500 Subject: [PATCH 049/155] level parameter for contour plot calls epsilon data (#1869) --- python/visualization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/visualization.py b/python/visualization.py index 1e17f6a08..647882398 100644 --- a/python/visualization.py +++ b/python/visualization.py @@ -402,7 +402,7 @@ def plot_eps(sim, ax, output_plane=None, eps_parameters=None, frequency=None): if mp.am_master(): if eps_parameters['contour']: - ax.contour(eps_data, 0, colors='black', origin='upper', extent=extent, linewidths=eps_parameters['contour_linewidth']) + ax.contour(eps_data, 0, levels=np.unique(eps_data), colors='black', origin='upper', extent=extent, linewidths=eps_parameters['contour_linewidth']) else: ax.imshow(eps_data, extent=extent, **filter_dict(eps_parameters, ax.imshow)) ax.set_xlabel(xlabel) From 367e0ea82297ee464f1fe7ca945d4f6464a03016 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Tue, 28 Dec 2021 15:16:23 -0800 Subject: [PATCH 050/155] fix heap buffer overflow error for update E from D in cylindrical coordinates (#1871) --- src/update_eh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/update_eh.cpp b/src/update_eh.cpp index e59a25beb..efd7d3ffe 100644 --- a/src/update_eh.cpp +++ b/src/update_eh.cpp @@ -189,7 +189,7 @@ bool fields_chunk::update_eh(field_type ft, bool skip_w_components) { } if (f[ec][cmp] != f[dc][cmp]) - STEP_UPDATE_EDHB(f[ec][cmp], ec, gv, gvs_eh[ft][i].little_owned_corner(ec), gvs_eh[ft][i].big_corner(), + STEP_UPDATE_EDHB(f[ec][cmp], ec, gv, gvs_eh[ft][i].little_owned_corner0(ec), gvs_eh[ft][i].big_corner(), dmp[dc][cmp], dmp[dc_1][cmp], dmp[dc_2][cmp], s->chi1inv[ec][d_ec], dmp[dc_1][cmp] ? s->chi1inv[ec][d_1] : NULL, dmp[dc_2][cmp] ? s->chi1inv[ec][d_2] : NULL, s_ec, s_1, s_2, s->chi2[ec], From cd7a9100bc7097c163f7794d7fd1d38c7a3b96bf Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Thu, 30 Dec 2021 14:57:08 -0500 Subject: [PATCH 051/155] Add cylindrical coordinates support for `plot2d` (#1873) * add visualization support for plot2d * bug fix with cartesian plotting --- python/visualization.py | 56 ++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/python/visualization.py b/python/visualization.py index 647882398..f57621f65 100644 --- a/python/visualization.py +++ b/python/visualization.py @@ -189,14 +189,19 @@ def get_2D_dimensions(sim, output_plane): elif sim.output_volume: plane_center, plane_size = mp.get_center_and_size(sim.output_volume) else: - plane_center, plane_size = (sim.geometry_center, sim.cell_size) + if (sim.dimensions == mp.CYLINDRICAL) or sim.is_cylindrical: + plane_center, plane_size = (sim.geometry_center+mp.Vector3(sim.cell_size.x/2), sim.cell_size) + else: + plane_center, plane_size = (sim.geometry_center, sim.cell_size) plane_volume = Volume(center=plane_center,size=plane_size) if plane_size.x != 0 and plane_size.y != 0 and plane_size.z != 0: raise ValueError("Plane volume must be 2D (a plane).") - - check_volume = Volume(center=sim.geometry_center, size=sim.cell_size) - + if (sim.dimensions == mp.CYLINDRICAL) or sim.is_cylindrical: + center = sim.geometry_center+mp.Vector3(sim.cell_size.x/2) + check_volume = mp.Volume(center=center, size = sim.cell_size) + else: + check_volume = Volume(center=sim.geometry_center, size=sim.cell_size) vertices = intersect_volume_volume(check_volume, plane_volume) if len(vertices) == 0: @@ -382,7 +387,10 @@ def plot_eps(sim, ax, output_plane=None, eps_parameters=None, frequency=None): elif sim_size.y == 0: # Plot x on x axis, z on y axis (XZ plane) extent = [xmin, xmax, zmin, zmax] - xlabel = 'X' + if (sim.dimensions == mp.CYLINDRICAL) or sim.is_cylindrical: + xlabel = 'R' + else: + xlabel = "X" ylabel = 'Z' xtics = np.linspace(xmin, xmax, Nx) ytics = np.array([sim_center.y]) @@ -414,13 +422,13 @@ def plot_boundaries(sim, ax, output_plane=None, boundary_parameters=None): # consolidate plotting parameters boundary_parameters = default_boundary_parameters if boundary_parameters is None else dict(default_boundary_parameters, **boundary_parameters) - def get_boundary_volumes(thickness, direction, side): + def get_boundary_volumes(thickness, direction, side, cylindrical=False): from meep.simulation import Volume thickness = boundary.thickness # Get domain measurements - sim_center, sim_size = (sim.geometry_center, sim.cell_size) + sim_center, sim_size = get_2D_dimensions(sim, output_plane) xmin = sim_center.x - sim_size.x/2 xmax = sim_center.x + sim_size.x/2 @@ -434,22 +442,22 @@ def get_boundary_volumes(thickness, direction, side): cell_z = sim.cell_size.z if direction == mp.X and side == mp.Low: - return Volume(center=Vector3(xmin+thickness/2,sim.geometry_center.y,sim.geometry_center.z), + return Volume(center=Vector3(xmin+thickness/2,sim_center.y,sim_center.z), size=Vector3(thickness,cell_y,cell_z)) elif direction == mp.X and side == mp.High: - return Volume(center=Vector3(xmax-thickness/2,sim.geometry_center.y,sim.geometry_center.z), + return Volume(center=Vector3(xmax-thickness/2,sim_center.y,sim_center.z), size=Vector3(thickness,cell_y,cell_z)) elif direction == mp.Y and side == mp.Low: - return Volume(center=Vector3(sim.geometry_center.x,ymin+thickness/2,sim.geometry_center.z), + return Volume(center=Vector3(sim_center.x,ymin+thickness/2,sim_center.z), size=Vector3(cell_x,thickness,cell_z)) elif direction == mp.Y and side == mp.High: - return Volume(center=Vector3(sim.geometry_center.x,ymax-thickness/2,sim.geometry_center.z), + return Volume(center=Vector3(sim_center.x,ymax-thickness/2,sim_center.z), size=Vector3(cell_x,thickness,cell_z)) elif direction == mp.Z and side == mp.Low: - return Volume(center=Vector3(sim.geometry_center.x,sim.geometry_center.y,zmin+thickness/2), + return Volume(center=Vector3(sim_center.x,sim_center.y,zmin+thickness/2), size=Vector3(cell_x,cell_y,thickness)) elif direction == mp.Z and side == mp.High: - return Volume(center=Vector3(sim.geometry_center.x,sim.geometry_center.y,zmax-thickness/2), + return Volume(center=Vector3(sim_center.x,sim_center.y,zmax-thickness/2), size=Vector3(cell_x,cell_y,thickness)) else: raise ValueError("Invalid boundary type") @@ -460,6 +468,8 @@ def get_boundary_volumes(thickness, direction, side): if boundary.direction == mp.ALL and boundary.side == mp.ALL: if sim.dimensions == 1: dims = [mp.X] + elif sim.dimensions == mp.CYLINDRICAL or sim.is_cylindrical: + dims = [mp.X, mp.Z] elif sim.dimensions == 2: dims = [mp.X, mp.Y] elif sim.dimensions == 3: @@ -467,15 +477,21 @@ def get_boundary_volumes(thickness, direction, side): else: raise ValueError("Invalid simulation dimensions") for permutation in itertools.product(dims, [mp.Low, mp.High]): + if (permutation[0] == mp.X) and (permutation[1] == mp.Low): + continue vol = get_boundary_volumes(boundary.thickness,*permutation) ax = plot_volume(sim,ax,vol,output_plane,plotting_parameters=boundary_parameters) # two sides are the same elif boundary.side == mp.ALL: for side in [mp.Low, mp.High]: + if (boundary.direction == mp.X) and (side == mp.Low): + continue vol = get_boundary_volumes(boundary.thickness,boundary.direction,side) ax = plot_volume(sim,ax,vol,output_plane,plotting_parameters=boundary_parameters) # only one side - else: + else: + if (boundary.direction == mp.X) and (boundary.side == mp.Low): + continue vol = get_boundary_volumes(boundary.thickness,boundary.direction,boundary.side) ax = plot_volume(sim,ax,vol,output_plane,plotting_parameters=boundary_parameters) return ax @@ -517,7 +533,7 @@ def plot_fields(sim, ax=None, fields=None, output_plane=None, field_parameters=N field_parameters = default_field_parameters if field_parameters is None else dict(default_field_parameters, **field_parameters) # user specifies a field component - if fields in [mp.Ex, mp.Ey, mp.Ez, mp.Hx, mp.Hy, mp.Hz]: + if fields in [mp.Ex, mp.Ey, mp.Ez, mp.Er, mp.Ep, mp.Hx, mp.Hy, mp.Hz]: # Get domain measurements sim_center, sim_size = get_2D_dimensions(sim, output_plane) @@ -539,7 +555,10 @@ def plot_fields(sim, ax=None, fields=None, output_plane=None, field_parameters=N elif sim_size.y == 0: # Plot x on x axis, z on y axis (XZ plane) extent = [xmin, xmax, zmin, zmax] - xlabel = 'X' + if (sim.dimensions == mp.CYLINDRICAL) or sim.is_cylindrical: + xlabel = 'R' + else: + xlabel = "X" ylabel = 'Z' elif sim_size.z == 0: # Plot x on x axis, y on y axis (XY plane) @@ -552,14 +571,15 @@ def plot_fields(sim, ax=None, fields=None, output_plane=None, field_parameters=N fields = field_parameters['post_process'](fields) + fields = np.flipud(fields) if ((sim.dimensions == mp.CYLINDRICAL) or sim.is_cylindrical) else np.rot90(fields) # Either plot the field, or return the array if ax: if mp.am_master(): - ax.imshow(np.rot90(fields), extent=extent, **filter_dict(field_parameters,ax.imshow)) + ax.imshow(fields, extent=extent, **filter_dict(field_parameters,ax.imshow)) return ax else: - return np.rot90(fields) + return fields return ax def plot2D(sim, ax=None, output_plane=None, fields=None, labels=False, From 1ac33b480a7a99a858090e99fc36a076645db804 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Thu, 30 Dec 2021 19:35:47 -0800 Subject: [PATCH 052/155] fix near-field monitor position in cylindrical coordinate tutorial (#1874) --- .../Cylindrical_Coordinates.md | 25 +++++++++++++++---- python/examples/zone_plate.py | 25 +++++++++++++++---- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/doc/docs/Python_Tutorials/Cylindrical_Coordinates.md b/doc/docs/Python_Tutorials/Cylindrical_Coordinates.md index 6f558196f..ff081b8c3 100644 --- a/doc/docs/Python_Tutorials/Cylindrical_Coordinates.md +++ b/doc/docs/Python_Tutorials/Cylindrical_Coordinates.md @@ -536,14 +536,29 @@ sim = mp.Simulation(cell_size=cell_size, m=-1) ## near-field monitor -n2f_obj = sim.add_near2far(frq_cen, 0, 1, - mp.Near2FarRegion(center=mp.Vector3(0.5*(sr-dpml),0,0.5*sz-dpml),size=mp.Vector3(sr-dpml)), - mp.Near2FarRegion(center=mp.Vector3(sr-dpml,0,0.5*sz-0.5*(dsub+zh+dpad)),size=mp.Vector3(z=dsub+zh+dpad))) +n2f_obj = sim.add_near2far(frq_cen, + 0, + 1, + mp.Near2FarRegion(center=mp.Vector3(0.5*(sr-dpml),0,0.5*sz-dpml), + size=mp.Vector3(sr-dpml)), + mp.Near2FarRegion(center=mp.Vector3(sr-dpml,0,0.5*sz-dpml-0.5*(dsub+zh+dpad)), + size=mp.Vector3(z=dsub+zh+dpad))) + +sim.plot2D() +if mp.am_master(): + plt.savefig("zone_plate_epsilon.png",bbox_inches='tight',dpi=150) sim.run(until_after_sources=100) -ff_r = sim.get_farfields(n2f_obj, ff_res, center=mp.Vector3(0.5*(sr-dpml),0,-0.5*sz+dpml+dsub+zh+focal_length),size=mp.Vector3(sr-dpml)) -ff_z = sim.get_farfields(n2f_obj, ff_res, center=mp.Vector3(z=-0.5*sz+dpml+dsub+zh+focal_length),size=mp.Vector3(z=spot_length)) +ff_r = sim.get_farfields(n2f_obj, + ff_res, + center=mp.Vector3(0.5*(sr-dpml),0,-0.5*sz+dpml+dsub+zh+focal_length), + size=mp.Vector3(sr-dpml)) + +ff_z = sim.get_farfields(n2f_obj, + ff_res, + center=mp.Vector3(z=-0.5*sz+dpml+dsub+zh+focal_length), + size=mp.Vector3(z=spot_length)) E2_r = np.absolute(ff_r['Ex'])**2+np.absolute(ff_r['Ey'])**2+np.absolute(ff_r['Ez'])**2 E2_z = np.absolute(ff_z['Ex'])**2+np.absolute(ff_z['Ey'])**2+np.absolute(ff_z['Ez'])**2 diff --git a/python/examples/zone_plate.py b/python/examples/zone_plate.py index 11cca4ec3..9defcac9f 100644 --- a/python/examples/zone_plate.py +++ b/python/examples/zone_plate.py @@ -58,14 +58,29 @@ m=-1) ## near-field monitor -n2f_obj = sim.add_near2far(frq_cen, 0, 1, - mp.Near2FarRegion(center=mp.Vector3(0.5*(sr-dpml),0,0.5*sz-dpml),size=mp.Vector3(sr-dpml)), - mp.Near2FarRegion(center=mp.Vector3(sr-dpml,0,0.5*sz-0.5*(dsub+zh+dpad)),size=mp.Vector3(z=dsub+zh+dpad))) +n2f_obj = sim.add_near2far(frq_cen, + 0, + 1, + mp.Near2FarRegion(center=mp.Vector3(0.5*(sr-dpml),0,0.5*sz-dpml), + size=mp.Vector3(sr-dpml)), + mp.Near2FarRegion(center=mp.Vector3(sr-dpml,0,0.5*sz-dpml-0.5*(dsub+zh+dpad)), + size=mp.Vector3(z=dsub+zh+dpad))) + +sim.plot2D() +if mp.am_master(): + plt.savefig("zone_plate_epsilon.png",bbox_inches='tight',dpi=150) sim.run(until_after_sources=100) -ff_r = sim.get_farfields(n2f_obj, ff_res, center=mp.Vector3(0.5*(sr-dpml),0,-0.5*sz+dpml+dsub+zh+focal_length),size=mp.Vector3(sr-dpml)) -ff_z = sim.get_farfields(n2f_obj, ff_res, center=mp.Vector3(z=-0.5*sz+dpml+dsub+zh+focal_length),size=mp.Vector3(z=spot_length)) +ff_r = sim.get_farfields(n2f_obj, + ff_res, + center=mp.Vector3(0.5*(sr-dpml),0,-0.5*sz+dpml+dsub+zh+focal_length), + size=mp.Vector3(sr-dpml)) + +ff_z = sim.get_farfields(n2f_obj, + ff_res, + center=mp.Vector3(z=-0.5*sz+dpml+dsub+zh+focal_length), + size=mp.Vector3(z=spot_length)) E2_r = np.absolute(ff_r['Ex'])**2+np.absolute(ff_r['Ey'])**2+np.absolute(ff_r['Ez'])**2 E2_z = np.absolute(ff_z['Ex'])**2+np.absolute(ff_z['Ey'])**2+np.absolute(ff_z['Ez'])**2 From 238baca31c0b8da7eaf82a0fdf664700e143680c Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Fri, 31 Dec 2021 18:41:10 -0800 Subject: [PATCH 053/155] fix memory leaks in structure and fields load during checkpointing (#1872) * fix memory leaks in structure and fields load during checkpointing * delete the chi1inv and fields array if it exists and reallocate * in unit test, set gaussian source cutoff to 0 due to off-by-1 timestep counter bug * remove cutoff=0 from unit tests * lazily allocate H only if B is not NULL * allocate fields array for H in PML region --- src/fields_dump.cpp | 9 +++++++-- src/structure_dump.cpp | 2 +- tests/dump_load.cpp | 1 - 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/fields_dump.cpp b/src/fields_dump.cpp index 6fce54e2c..9ccc84e01 100644 --- a/src/fields_dump.cpp +++ b/src/fields_dump.cpp @@ -184,12 +184,17 @@ void fields::load_fields_chunk_field(h5file *h5f, bool single_parallel_file, size_t n = num_f[(chunk_i * NUM_FIELD_COMPONENTS + c) * 2 + d]; realnum **f = field_ptr_getter(chunks[i], c, d); if (n == 0) { - delete[] * f; + delete[] *f; *f = NULL; } else { if (n != ntot) meep::abort("grid size mismatch %zd vs %zd in fields::load", n, ntot); - *f = new realnum[ntot]; + // here we need to allocate the fields array for H in the PML region + // because of H = B in fields_chunk::alloc_f whereby H is lazily + // allocated in fields_chunk::update_eh during the first timestep + const direction d_c = component_direction(c); + if (!(*f) || (*f && is_magnetic(component(c)) && chunks[i]->s->sigsize[d_c] > 1)) + *f = new realnum[ntot]; my_ntot += ntot; } } diff --git a/src/structure_dump.cpp b/src/structure_dump.cpp index d0fcb91a5..e12cc88d0 100644 --- a/src/structure_dump.cpp +++ b/src/structure_dump.cpp @@ -604,7 +604,7 @@ void structure::load(const char *filename, bool single_parallel_file) { } else { if (n != ntot) meep::abort("grid size mismatch %zd vs %zd in structure::load", n, ntot); - chunks[i]->chi1inv[c][d] = new realnum[ntot]; + if (!chunks[i]->chi1inv[c][d]) chunks[i]->chi1inv[c][d] = new realnum[ntot]; my_ntot += ntot; } } diff --git a/tests/dump_load.cpp b/tests/dump_load.cpp index 78685a4e9..fef2006fd 100644 --- a/tests/dump_load.cpp +++ b/tests/dump_load.cpp @@ -169,7 +169,6 @@ int test_periodic(double eps(const vec &), int splitting, const char *tmpdir) { double ttot = 17.0; grid_volume gv = vol3d(1.5, 0.5, 1.0, a); - structure s1(gv, eps); structure s(gv, eps, no_pml(), identity(), splitting); std::string filename_prefix = From 67673d091c7507f96ee06d92af20f459637017b1 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Sun, 2 Jan 2022 11:59:26 -0800 Subject: [PATCH 054/155] fix two memory leaks in geom_epsilon class (#1877) * fix two memory leaks in geom_epsilon class * delete global variable default_material at the end of unit tests * add unset_default_material function to class meep_geom --- src/meepgeom.cpp | 24 ++++++++++++++++-------- src/meepgeom.hpp | 1 + tests/array-slice-ll.cpp | 1 + tests/cyl-ellipsoid-ll.cpp | 1 + 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index 8b9fa157c..12aa15933 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -43,6 +43,13 @@ void set_default_material(material_type _default_material) { } } +void unset_default_material(void) { + if (default_material != NULL) { + material_free((material_type)default_material); + default_material = NULL; + } +} + bool susceptibility_equal(const susceptibility &s1, const susceptibility &s2) { return (vector3_equal(s1.sigma_diag, s2.sigma_diag) && vector3_equal(s1.sigma_offdiag, s2.sigma_offdiag) && vector3_equal(s1.bias, s2.bias) && @@ -761,7 +768,8 @@ void geom_epsilon::set_volume(const meep::volume &v) { unset_volume(); geom_box box = gv2box(v); - restricted_tree = create_geom_box_tree0(geometry, box); + if (!restricted_tree) + restricted_tree = create_geom_box_tree0(geometry, box); } static void material_epsmu(meep::field_type ft, material_type material, symm_matrix *epsmu, @@ -1918,8 +1926,8 @@ void add_absorbing_layer(absorber_list alist, double thickness, int direction, i /* create a geom_epsilon object that can persist if needed */ geom_epsilon* make_geom_epsilon(meep::structure *s, geometric_object_list *g, vector3 center, - bool _ensure_periodicity, material_type _default_material, - material_type_list extra_materials) { + bool _ensure_periodicity, material_type _default_material, + material_type_list extra_materials) { // set global variables in libctlgeom based on data fields in s geom_initialize(); geometry_center = center; @@ -1980,17 +1988,17 @@ void set_materials_from_geometry(meep::structure *s, geometric_object_list g, ve bool _ensure_periodicity, material_type _default_material, absorber_list alist, material_type_list extra_materials) { meep_geom::geom_epsilon *geps = meep_geom::make_geom_epsilon(s, &g, center, _ensure_periodicity, - _default_material, extra_materials); + _default_material, extra_materials); set_materials_from_geom_epsilon(s, geps, use_anisotropic_averaging, tol, - maxeval, alist); + maxeval, alist); delete geps; } /* from a previously created geom_epsilon object, set the materials as specified */ void set_materials_from_geom_epsilon(meep::structure *s, geom_epsilon *geps, - bool use_anisotropic_averaging, - double tol, int maxeval, absorber_list alist) { + bool use_anisotropic_averaging, + double tol, int maxeval, absorber_list alist) { // store for later use in gradient calculations geps->tol = tol; @@ -2007,7 +2015,7 @@ void set_materials_from_geom_epsilon(meep::structure *s, geom_epsilon *geps, mythunk.func = layer->pml_profile; mythunk.func_data = layer->pml_profile_data; geps->set_cond_profile(d, b, layer->thickness, gv.inva * 0.5, pml_profile_wrapper, - (void *)&mythunk, layer->R_asymptotic); + (void *)&mythunk, layer->R_asymptotic); } } } diff --git a/src/meepgeom.hpp b/src/meepgeom.hpp index 3be1e0c39..3e7d0a681 100644 --- a/src/meepgeom.hpp +++ b/src/meepgeom.hpp @@ -253,6 +253,7 @@ vector3 vec_to_vector3(const meep::vec &pt); meep::vec vector3_to_vec(const vector3 v3); void set_default_material(material_type _default_material); +void unset_default_material(void); void epsilon_material_grid(material_data *md, double u); void epsilon_file_material(material_data *md, vector3 p); bool susceptibility_equal(const susceptibility &s1, const susceptibility &s2); diff --git a/tests/array-slice-ll.cpp b/tests/array-slice-ll.cpp index e13967aad..c98f4a741 100644 --- a/tests/array-slice-ll.cpp +++ b/tests/array-slice-ll.cpp @@ -251,6 +251,7 @@ int main(int argc, char *argv[]) { for (int n = 0; n < no; n++) { geometric_object_destroy(objects[n]); } + meep_geom::unset_default_material(); return 0; } diff --git a/tests/cyl-ellipsoid-ll.cpp b/tests/cyl-ellipsoid-ll.cpp index c6c5ca597..5de8c109a 100644 --- a/tests/cyl-ellipsoid-ll.cpp +++ b/tests/cyl-ellipsoid-ll.cpp @@ -198,6 +198,7 @@ int main(int argc, char *argv[]) { for (int n = 0; n < 2; n++) { geometric_object_destroy(objects[n]); } + meep_geom::unset_default_material(); return 0; } From 5d44864e08559a8593c042e90e9807da74d0e7df Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Mon, 3 Jan 2022 05:09:02 -0800 Subject: [PATCH 055/155] include ring-ll.cpp in C++ unit tests (#1878) * include ring-ll.cpp in C++ unit tests * only validate Harminv modes with error below some threshold --- tests/Makefile.am | 2 +- tests/cyl-ellipsoid-ll.cpp | 2 +- tests/pml.cpp | 3 +- tests/ring-ll.cpp | 58 +++++++++++++++++++++----------------- 4 files changed, 35 insertions(+), 30 deletions(-) diff --git a/tests/Makefile.am b/tests/Makefile.am index 1ce61b95b..ee118fea1 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -115,7 +115,7 @@ TESTS = aniso_disp bench bragg_transmission convergence_cyl_waveguide cylindrica if WITH_MPI LOG_COMPILER = $(RUNCODE) else - TESTS += cyl-ellipsoid-ll array-slice-ll + TESTS += cyl-ellipsoid-ll array-slice-ll ring-ll endif # Note: this requires GNU make diff --git a/tests/cyl-ellipsoid-ll.cpp b/tests/cyl-ellipsoid-ll.cpp index 5de8c109a..a6fe6f471 100644 --- a/tests/cyl-ellipsoid-ll.cpp +++ b/tests/cyl-ellipsoid-ll.cpp @@ -137,7 +137,7 @@ int main(int argc, char *argv[]) { geometric_object objects[2]; vector3 center = {0.0, 0.0, 0.0}; double radius = 3.0; - double height = 1.0e20; + double height = meep_geom::ENORMOUS; vector3 xhat = {1.0, 0.0, 0.0}; vector3 yhat = {0.0, 1.0, 0.0}; vector3 zhat = {0.0, 0.0, 1.0}; diff --git a/tests/pml.cpp b/tests/pml.cpp index 14ce007e5..6c3e1a8b4 100644 --- a/tests/pml.cpp +++ b/tests/pml.cpp @@ -210,7 +210,7 @@ int check_pml2d(double eps(const vec &), component c, double conductivity, bool int check_pmlcyl(double eps(const vec &)) { double freq = 1.0, dpml = 1.0; complex ft = 0.0, ft2 = 0.0; - double prev_refl_const = 0.0, refl_const = 0.0; + double refl_const = 0.0; double sr = 5.0 + dpml, sz = 1.0 + 2 * dpml; double sr2 = 5.0 + dpml * 2, sz2 = 1.0 + 2 * dpml * 2; vec fpt = veccyl(sr - dpml - 0.1, 0); @@ -239,7 +239,6 @@ int check_pmlcyl(double eps(const vec &)) { } refl_const = pow(abs(ft - ft2), 2.0) / pow(abs(ft2), 2.0); master_printf("reflcyl:, %g, %g\n", res, refl_const); - prev_refl_const = refl_const; } master_printf("passed cylindrical PML check.\n"); return 0; diff --git a/tests/ring-ll.cpp b/tests/ring-ll.cpp index e28e6d07b..a478d6728 100644 --- a/tests/ring-ll.cpp +++ b/tests/ring-ll.cpp @@ -45,24 +45,21 @@ int main(int argc, char *argv[]) { double n = 3.4; // index of waveguide double w = 1.0; // width of waveguide double r = 1.0; // inner radius of ring + double height = meep_geom::ENORMOUS; - double pad = 4; // padding between waveguide and edge of PML - double dpml = 2; // thickness of PML + double pad = 4.0; // padding between waveguide and edge of PML + double dpml = 2.0; // thickness of PML double sxy = 2.0 * (r + w + pad + dpml); // cell size double resolution = 10.0; // (set-param! resolution 10) // (set! geometry-lattice (make lattice (size sxy sxy no-size))) - geometry_lattice.size.x = sxy; - geometry_lattice.size.y = sxy; - geometry_lattice.size.z = 0.0; grid_volume gv = voltwo(sxy, sxy, resolution); gv.center_origin(); // (set! symmetries (list (make mirror-sym (direction Y)))) - // symmetry sym=mirror(Y, gv); - symmetry sym = identity(); + symmetry sym = mirror(Y, gv); // (set! pml-layers (list (make pml (thickness dpml)))) // ; exploit the mirror symmetry in structure+source: @@ -76,12 +73,16 @@ int main(int argc, char *argv[]) { // (radius (+ r w)) (material (make dielectric (index n)))) // (make cylinder (center 0 0) (height infinity) // (radius r) (material air)))) - meep_geom::material_type dielectric = meep_geom::make_dielectric(n * n); + auto material_deleter = [](meep_geom::material_data *m) { + meep_geom::material_free(m); + }; + std::unique_ptr dielectric( + meep_geom::make_dielectric(n*n), material_deleter); geometric_object objects[2]; vector3 v3zero = {0.0, 0.0, 0.0}; vector3 zaxis = {0.0, 0.0, 1.0}; - objects[0] = make_cylinder(dielectric, v3zero, r + w, meep_geom::ENORMOUS, zaxis); - objects[1] = make_cylinder(meep_geom::vacuum, v3zero, r, meep_geom::ENORMOUS, zaxis); + objects[0] = make_cylinder(dielectric.get(), v3zero, r + w, height, zaxis); + objects[1] = make_cylinder(meep_geom::vacuum, v3zero, r, height, zaxis); geometric_object_list g = {2, objects}; meep_geom::set_materials_from_geometry(&the_structure, g); fields f(&the_structure); @@ -96,8 +97,7 @@ int main(int argc, char *argv[]) { double fcen = 0.15; // ; pulse center frequency double df = 0.1; // ; df gaussian_src_time src(fcen, df); - volume v(vec(r + 0.1, 0.0), vec(0.0, 0.0)); - f.add_volume_source(Ez, src, v); + f.add_point_source(Ez, src, vec(r + 0.1, 0.0)); // (run-sources+ 300 // (at-beginning output-epsilon) @@ -132,22 +132,23 @@ int main(int argc, char *argv[]) { imag(amp[nb]), err[nb]); // test comparison with expected values - int ref_bands = 3; - double ref_freq_re[3] = {1.1807e-01, 1.4716e-01, 1.7525e-01}; - double ref_freq_im[3] = {-7.6133e-04, -2.1156e-04, -5.2215e-05}; - std::complex ref_amp[3] = {std::complex(-8.28e-04, -1.34e-03), std::complex(1.23e-03, -1.25e-02), - std::complex(2.83e-03, -6.52e-04)}; - if (bands != 3) meep::abort("harminv found only %i/%i bands\n", bands, ref_bands); + double err_tol = 1.0e-5; + int ref_bands = 4; + double ref_freq_re[4] = {1.1807e-01, 1.4470e-01, 1.4715e-01, 1.7525e-01}; + double ref_freq_im[4] = {-7.5657e-04, -8.9843e-04, -2.2172e-04, -5.0267e-05}; + std::complex ref_amp[4] = {std::complex(-6.40e-03,-2.81e-03), + std::complex(-1.42e-04,+6.78e-04), + std::complex(+3.99e-02,+4.09e-02), + std::complex(-1.98e-03,-1.43e-02)}; + if (bands != ref_bands) meep::abort("harminv found only %i/%i bands\n", bands, ref_bands); for (int nb = 0; nb < bands; nb++) - if (fabs(freq_re[nb] - ref_freq_re[nb]) > 1.0e-2 * fabs(ref_freq_re[nb]) || - fabs(freq_im[nb] - ref_freq_im[nb]) > 1.0e-2 * fabs(ref_freq_im[nb]) || - abs(amp[nb] - ref_amp[nb]) > 1.0e-2 * abs(ref_amp[nb]) - - ) + if ((fabs(freq_re[nb] - ref_freq_re[nb]) > 1.0e-2 * fabs(ref_freq_re[nb]) || + fabs(freq_im[nb] - ref_freq_im[nb]) > 1.0e-2 * fabs(ref_freq_im[nb]) || + abs(amp[nb] - ref_amp[nb]) > 1.0e-2 * abs(ref_amp[nb])) && (err[nb] < err_tol)) meep::abort("harminv band %i disagrees with ref: {re f, im f, re A, im A}={%e,%e,%e,%e}!= " - "{%e,%e,%e,%e}\n", - nb, freq_re[nb], freq_im[nb], real(amp[nb]), imag(amp[nb]), ref_freq_re[nb], - ref_freq_im[nb], real(ref_amp[nb]), imag(ref_amp[nb])); + "{%e,%e,%e,%e}\n", + nb, freq_re[nb], freq_im[nb], real(amp[nb]), imag(amp[nb]), ref_freq_re[nb], + ref_freq_im[nb], real(ref_amp[nb]), imag(ref_amp[nb])); master_printf("all harminv results match reference values\n"); @@ -168,6 +169,11 @@ int main(int argc, char *argv[]) { // this seems to be necessary to prevent failures all_wait(); + for (int n = 0; n < 2; n++) { + geometric_object_destroy(objects[n]); + } + meep_geom::unset_default_material(); + // success if we made it here return 0; } From f15b5057e79690ad8819b40e2b08e4749c37cb01 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 4 Jan 2022 19:14:16 -0500 Subject: [PATCH 056/155] fix and example --- python/adjoint/connectivity.py | 12 +- .../ConnectivityConstraint.ipynb | 272 ++++++++++++++++++ 2 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 python/examples/adjoint_optimization/ConnectivityConstraint.ipynb diff --git a/python/adjoint/connectivity.py b/python/adjoint/connectivity.py index 1f10b0648..dc2309e94 100644 --- a/python/adjoint/connectivity.py +++ b/python/adjoint/connectivity.py @@ -72,7 +72,10 @@ def forward(self, rho_vector): damping = self.k0*self.alpha**2*diags(1-rho_vector[:-self.nx*self.ny]) + diags([self.alpha0**2], shape=(self.m, self.m)) self.A = eq + damping self.damping = damping - self.T, sinfo = self.solver(csr_matrix(self.A), rhs) + if self.solver == spsolve: + self.T = self.solver(csr_matrix(self.A), rhs) + else: + self.T, sinfo = self.solver(csr_matrix(self.A), rhs) #exclude last row of rho and calculate weighted average of temperature self.rho_vec = rho_vector[:-self.nx*self.ny] @@ -83,7 +86,10 @@ def forward(self, rho_vector): def adjoint(self): T_p1 = -(self.T-1) ** (self.p-1) dg_dT = self.Td**(1-self.p) * (T_p1*self.rho_vec)/sum(self.rho_vec) - return self.solver(csr_matrix(self.A.transpose()), dg_dT) + if self.solver == spsolve: + return self.solver(csr_matrix(self.A.transpose()), dg_dT) + aT, _ = self.solver(csr_matrix(self.A.transpose()), dg_dT) + return aT def calculate_grad(self): dg_dp = np.zeros(self.n) @@ -109,7 +115,7 @@ def calculate_grad(self): dAz = (1-self.zeta)*self.k0*self.dz * drhoz.multiply(gzTz) d_damping = self.k0*self.alpha**2*diags(-self.T, shape=(self.m, self.n)) - self.grad = dg_dp + self.adjoint().reshape(1, -1) * csr_matrix( - dAz - dAx - dAy - d_damping) + self.grad = dg_dp + self.adjoint().reshape(1, -1) * csr_matrix(dAz + dAx + dAy + d_damping) return self.grad[0] def __call__(self, rho_vector): diff --git a/python/examples/adjoint_optimization/ConnectivityConstraint.ipynb b/python/examples/adjoint_optimization/ConnectivityConstraint.ipynb new file mode 100644 index 000000000..56279b124 --- /dev/null +++ b/python/examples/adjoint_optimization/ConnectivityConstraint.ipynb @@ -0,0 +1,272 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Connectivity Constraint in Meep adjoint\n", + "\n", + "For manufacturability, connectivity constraint is often desired. This is a simple tutorial example of the connectivity constraint in Meep adjoint. This feature is rather independent and may be used alone, when applicable." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using MPI version 3.1, 1 processes\n" + ] + } + ], + "source": [ + "import meep.adjoint as mpa\n", + "import numpy as np\n", + "from scipy.sparse.linalg import cg, spsolve\n", + "from matplotlib import pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The underlying idea (based on Li, Q. et al. https://doi.org/10.1007/s00158-016-1459-5) is briefly summerized below:\n", + "\n", + "Consider the heat equation, and regard the material as heat conductive and void as heat insulative. Solving the heat equation, we should expect the heat gets diffused into the connected component but not the disconnected component.\n", + "In practice, 3D-printed structure is often mounted on some substrate. This means the optimized structure should be connected to one side. For our heat equation, we impose Dirichlet boundary condition ($T=T_0$) on one side, (and Neumann on other sides,) the resulting temperature should be almost $T_0$ for all structures connected to that side. The p-norm weighted by material density $(\\frac{\\sum (T-T_0)^p \\rho}{\\sum \\rho})^\\frac1{p}$ measures how well the structure is connected. \n", + "\n", + "Additionally, damping terms are added so the heat can quickly decay away outside the material. The equation solved is thus $(-\\nabla \\cdot(k \\nabla) + \\alpha^2 (1-\\rho)k + \\alpha_0^2)T=0.$, where the conductivity $k=\\rho k_0$ for material density $\\rho \\in [0,1]$\n", + "\n", + "The solver assumes the side with Dirichlet boundary condition is the last slice ``rho[-nx*ny:]``. For 2D, as in the following example below, set ``ny=1``. In Meep, if we want the structure connect to the bottom, we can use ``rot90`` before we pass in. " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "foward value 0.6369810999723368\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "nz, ny, nx =25, 1, 25\n", + "c = mpa.ConnectivityConstraint(nx, ny, nz,sp_solver=cg)\n", + "\n", + "r = np.zeros((nz, ny,nx))+0.0001\n", + "r[:10,0, 8]=0.999\n", + "r[7,0,5:20]=0.999\n", + "r[5:,0, 18]=0.999\n", + "r[7,0,11:16]=0.0001\n", + "r[17:18,0,9:10]=0.999\n", + "\n", + "r=r.flatten()#flatten the structure before pass in\n", + "f = c.forward(r)\n", + "ag = c.calculate_grad()\n", + "\n", + "print(\"foward value\", f) \n", + "\n", + "plt.figure()\n", + "plt.pcolormesh(r.reshape((nz, nx)))\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The above structure has the following temperature distribution. As expected, component connected to the top (where Dirichlet BC is enforeced) has high value of temperature." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "#padding to show the side with Dirichlet BC\n", + "fullT = np.pad(c.T, (0, c.nx*c.ny), 'constant', constant_values=1).reshape((nz, ny, nx)) \n", + "plt.figure()\n", + "plt.contourf(fullT[:,0,:], 100, cmap=\"RdBu\")\n", + "plt.colorbar()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The gradient across the structure is shown below. Note the part that can bridges one disconncted component to connected one has negative gradient. One the other hand, the island in the upper left corner has postive gradient itself, but also tries to connect to the top and right where the gradient is negative." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure()\n", + "plt.contourf(ag.reshape((nz, nx)), 100, cmap=\"RdBu\")\n", + "plt.colorbar()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For connected structure" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "foward value 0.06833918047665405\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "r = np.zeros((nz, ny,nx))+0.0001\n", + "r[:10,0, 8]=0.999\n", + "r[7,0,5:20]=0.999\n", + "r[5:,0, 18]=0.999\n", + "\n", + "r=r.flatten()#flatten the structure before pass in\n", + "f = c.forward(r)\n", + "ag = c.calculate_grad()\n", + "\n", + "print(\"foward value\", f) \n", + "\n", + "plt.figure()\n", + "plt.pcolormesh(r.reshape((nz, nx)))\n", + "plt.show()\n", + "\n", + "fullT = np.pad(c.T, (0, c.nx*c.ny), 'constant', constant_values=1).reshape((nz, ny, nx)) \n", + "plt.figure()\n", + "plt.contourf(fullT[:,0,:], 100,cmap=\"RdBu\")\n", + "plt.colorbar()\n", + "plt.show()\n", + "\n", + "plt.figure()\n", + "plt.contourf(ag.reshape((nz, nx)), 100,cmap=\"RdBu\")\n", + "plt.colorbar()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 90e18be441f0db989b9da25c1317e86a6b9090b6 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Mon, 24 May 2021 13:07:40 -0400 Subject: [PATCH 057/155] first draft of loop_in_chunks using grid_vol calculations --- src/loop_in_chunks.cpp | 31 +++++++++++-------------------- src/meep/vec.hpp | 6 +++++- src/structure.cpp | 2 +- src/vec.cpp | 35 +++++++++++++++++++++++++++++++++++ tests/integrate.cpp | 13 ++++--------- 5 files changed, 56 insertions(+), 31 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 3c9bba764..13a4df864 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -375,8 +375,8 @@ 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); ivec iyee_cS(S.transform_unshifted(iyee_c, -sn)); - volume gvS = S.transform(gv.surroundings(), sn); + vec L(gv.dim); ivec iL(gv.dim); @@ -418,25 +418,16 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con for (int i = 0; i < num_chunks; ++i) { if (!chunks[i]->is_mine()) continue; - // Chunk looping boundaries: - volume vS(gv.dim); - - if (use_symmetry) - vS = S.transform(chunks[i]->v, sn); - else { - /* If we're not using symmetry, it's because (as in src_vol) - we don't care about correctly counting the points in the - grid_volume. Rather, we just want to make sure to get *all* - of the chunk points that intersect where. Hence, add a little - padding to make sure we don't miss any points due to rounding. */ - vec pad(one_ivec(gv.dim) * gv.inva * 1e-3); - vS = volume(chunks[i]->gv.loc(Centered, 0) - pad, - chunks[i]->gv.loc(Centered, chunks[i]->gv.ntot() - 1) + pad); - } - - ivec iscS(max(is - shifti, vec2diel_ceil(vS.get_min_corner(), gv.a, one_ivec(gv.dim) * 2))); - ivec iecS(min(ie - shifti, vec2diel_floor(vS.get_max_corner(), gv.a, zero_ivec(gv.dim)))); - if (iscS <= iecS) { + // Chunk looping boundaries for owned points, shifted to centered grid and transformed: + grid_volume gvu(chunks[i]->gv.unpad(gv)); + ivec _iscoS(S.transform(gvu.little_owned_corner(Centered), sn)); + ivec _iecoS(S.transform(gvu.big_owned_corner(Centered), sn)); + ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform + + // intersect the chunk points with is and ie volume (shifted): + ivec iscS(max(is - shifti, iscoS)); + ivec iecS(min(ie - shifti, iecoS)); + if (iscS <= iecS) { // non-empty intersection // Determine weights at chunk looping boundaries: ivec isc(S.transform(iscS, -sn)), iec(S.transform(iecS, -sn)); vec s0c(gv.dim, 1.0), s1c(gv.dim, 1.0), e0c(gv.dim, 1.0), e1c(gv.dim, 1.0); diff --git a/src/meep/vec.hpp b/src/meep/vec.hpp index 34ca5cc98..78732a238 100644 --- a/src/meep/vec.hpp +++ b/src/meep/vec.hpp @@ -53,7 +53,7 @@ enum component { Permeability, NO_COMPONENT }; -#define Centered Dielectric // better name for centered "dielectric" grid +const component Centered = Dielectric; // better name for centered "dielectric" grid enum derived_component { Sx = 100, Sy, @@ -1095,6 +1095,7 @@ class grid_volume { } ivec little_owned_corner(component c) const; + ivec big_owned_corner(component c) const { return big_corner() - iyee_shift(c); } bool owns(const ivec &) const; volume surroundings() const; volume interior() const; @@ -1119,6 +1120,8 @@ class grid_volume { gv.pad_self(d); return gv; } + grid_volume unpad() const; + grid_volume unpad(const grid_volume &gv0) const; ivec iyee_shift(component c) const { ivec out = zero_ivec(dim); LOOP_OVER_DIRECTIONS(dim, d) @@ -1166,6 +1169,7 @@ class grid_volume { } int num[3]; ptrdiff_t the_stride[5]; + bool is_padded[5]; size_t the_ntot; }; diff --git a/src/structure.cpp b/src/structure.cpp index ceb11f221..d0776b5a0 100644 --- a/src/structure.cpp +++ b/src/structure.cpp @@ -187,7 +187,7 @@ std::unique_ptr choose_chunkdivision(grid_volume &gv, volume & v = gv.surroundings(); // Pad the little cell in any direction that we've shrunk: for (int d = 0; d < 3; d++) - if (break_this[d]) gv = gv.pad((direction)d); + if (break_this[d]) gv.pad_self((direction)d); } int proc_id = 0; diff --git a/src/vec.cpp b/src/vec.cpp index e4bf55a92..479a50422 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -307,6 +307,7 @@ grid_volume::grid_volume(ndim td, double ta, int na, int nb, int nc) { num[0] = na; num[1] = nb; num[2] = nc; + FOR_DIRECTIONS(d) is_padded[d] = false; num_changed(); set_origin(zero_vec(dim)); } @@ -1087,6 +1088,40 @@ void grid_volume::pad_self(direction d) { num[d % 3] += 2; // Pad in both directions by one grid point. num_changed(); shift_origin(d, -2); + is_padded[d] = true; +} + +// undoes all padding +grid_volume grid_volume::unpad() const { + grid_volume gv(*this); + LOOP_OVER_DIRECTIONS(dim, d) { + if (is_padded[d]) { // inverse of pad_self above + gv.num[d % 3] -= 2; + gv.shift_origin(d, +2); + gv.is_padded[d] = false; + } + } + gv.num_changed(); + return gv; +} + +// undoes padding in *this according when edges match padded sides of gv0 +grid_volume grid_volume::unpad(const grid_volume &gv0) const { + grid_volume gv(*this); + LOOP_OVER_DIRECTIONS(dim, d) { + if (gv0.is_padded[d]) { + if (little_corner().in_direction(d) == gv0.little_corner().in_direction(d)) { + gv.num[d % 3] -= 1; + gv.shift_origin(d, +2); + } + if (big_corner().in_direction(d) == gv0.big_corner().in_direction(d)) { + gv.num[d % 3] -= 1; + } + gv.is_padded[d] = false; + } + } + gv.num_changed(); + return gv; } ivec grid_volume::icenter() const { diff --git a/tests/integrate.cpp b/tests/integrate.cpp index 2bcf84549..69a1c0242 100644 --- a/tests/integrate.cpp +++ b/tests/integrate.cpp @@ -323,18 +323,13 @@ int main(int argc, char **argv) { const grid_volume v1d = vol1d(sz[0], a); const grid_volume vcyl = volcyl(sz[0], sz[1], a); - for (int ic = Ex; ic <= Dielectric; ++ic) { - component c = component(ic); - check_loop_vol(v1d, c); - check_loop_vol(v2d, c); - check_loop_vol(v3d, c); - check_loop_vol(vcyl, c); - check_loop_vol(v3d0, c); - check_loop_vol(v3d00, c); - } srand(0); // use fixed random sequence + check_splitsym(v3d, 0, identity(), "identity"); + check_splitsym(v3d, 0, mirror(X, v3d), "mirrorx"); + return 0; + for (int splitting = 0; splitting < 5; ++splitting) { check_splitsym(v3d, splitting, identity(), "identity"); check_splitsym(v3d, splitting, mirror(X, v3d), "mirrorx"); From 39ceb8375a4f0ea00bf507bcc527c707306ba9e6 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Mon, 24 May 2021 13:10:32 -0400 Subject: [PATCH 058/155] try to only use owned points in loop_in_chunks --- src/loop_in_chunks.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 13a4df864..b2f014910 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -420,8 +420,8 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (!chunks[i]->is_mine()) continue; // Chunk looping boundaries for owned points, shifted to centered grid and transformed: grid_volume gvu(chunks[i]->gv.unpad(gv)); - ivec _iscoS(S.transform(gvu.little_owned_corner(Centered), sn)); - ivec _iecoS(S.transform(gvu.big_owned_corner(Centered), sn)); + ivec _iscoS(S.transform(gvu.little_owned_corner(cS) + iyee_cS, sn)); + ivec _iecoS(S.transform(gvu.big_owned_corner(cS) + iyee_cS, sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform // intersect the chunk points with is and ie volume (shifted): From d7437e29582ee27b95d83c517d1f0dbb24f8c7d5 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Mon, 24 May 2021 13:45:22 -0400 Subject: [PATCH 059/155] don't unpad if !use_symmetry --- src/loop_in_chunks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index b2f014910..a421837a0 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -419,7 +419,7 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con for (int i = 0; i < num_chunks; ++i) { if (!chunks[i]->is_mine()) continue; // Chunk looping boundaries for owned points, shifted to centered grid and transformed: - grid_volume gvu(chunks[i]->gv.unpad(gv)); + grid_volume gvu(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); ivec _iscoS(S.transform(gvu.little_owned_corner(cS) + iyee_cS, sn)); ivec _iecoS(S.transform(gvu.big_owned_corner(cS) + iyee_cS, sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform From 27f9d969663bf8404b4d2616c4a36b5bbaec6f5e Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Mon, 24 May 2021 14:03:00 -0400 Subject: [PATCH 060/155] stop vscode from complaining about comments --- src/meep/vec.hpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/meep/vec.hpp b/src/meep/vec.hpp index 78732a238..4bf31bafa 100644 --- a/src/meep/vec.hpp +++ b/src/meep/vec.hpp @@ -1,20 +1,20 @@ // -*- C++ -*- -/* Copyright (C) 2005-2021 Massachusetts Institute of Technology -% -% This program is free software; you can redistribute it and/or modify -% it under the terms of the GNU General Public License as published by -% the Free Software Foundation; either version 2, or (at your option) -% any later version. -% -% This program is distributed in the hope that it will be useful, -% but WITHOUT ANY WARRANTY; without even the implied warranty of -% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -% GNU General Public License for more details. -% -% You should have received a copy of the GNU General Public License -% along with this program; if not, write to the Free Software Foundation, -% Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ +/* Copyright (C) 2005-2021 Massachusetts Institute of Technology. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ #ifndef MEEP_VEC_H #define MEEP_VEC_H From 28ad0ae629bbddce56dc23d17359f266b8e6c42a Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Thu, 3 Jun 2021 14:51:33 -0400 Subject: [PATCH 061/155] rm hack to loop over centered grid --- src/loop_in_chunks.cpp | 54 ++++++++++++++++------------------------ tests/array-metadata.cpp | 2 +- 2 files changed, 22 insertions(+), 34 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index a421837a0..edde9069b 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -253,12 +253,12 @@ static inline int iabs(int i) { return (i < 0 ? -i : i); } /* Integration weights at boundaries (c.f. long comment at top). */ /* This code was formerly part of loop_in_chunks, now refactored */ /* as a separate routine so we can call it from get_array_metadata.*/ -void compute_boundary_weights(grid_volume gv, volume &wherec, ivec &is, ivec &ie, +void compute_boundary_weights(grid_volume gv, const volume &where, ivec &is, ivec &ie, bool snap_empty_dimensions, vec &s0, vec &e0, vec &s1, vec &e1) { LOOP_OVER_DIRECTIONS(gv.dim, d) { double w0, w1; - w0 = 1. - wherec.in_direction_min(d) * gv.a + 0.5 * is.in_direction(d); - w1 = 1. + wherec.in_direction_max(d) * gv.a - 0.5 * ie.in_direction(d); + w0 = 1. - where.in_direction_min(d) * gv.a + 0.5 * is.in_direction(d); + w1 = 1. + where.in_direction_max(d) * gv.a - 0.5 * ie.in_direction(d); if (ie.in_direction(d) >= is.in_direction(d) + 3 * 2) { s0.set_direction(d, w0 * w0 / 2); s1.set_direction(d, 1 - (1 - w0) * (1 - w0) / 2); @@ -271,14 +271,15 @@ void compute_boundary_weights(grid_volume gv, volume &wherec, ivec &is, ivec &ie e0.set_direction(d, w1 * w1 / 2); e1.set_direction(d, s1.in_direction(d)); } - else if (wherec.in_direction_min(d) == wherec.in_direction_max(d)) { + else if (where.in_direction_min(d) == where.in_direction_max(d)) { if (snap_empty_dimensions) { if (w0 > w1) ie.set_direction(d, is.in_direction(d)); else is.set_direction(d, ie.in_direction(d)); - wherec.set_direction_min(d, is.in_direction(d) * (0.5 * gv.inva)); - wherec.set_direction_max(d, is.in_direction(d) * (0.5 * gv.inva)); + // shouldn't be necessary to change where: + // where.set_direction_min(d, is.in_direction(d) * (0.5 * gv.inva)); + // where.set_direction_max(d, is.in_direction(d) * (0.5 * gv.inva)); w0 = w1 = 1.0; } s0.set_direction(d, w0); @@ -347,34 +348,21 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (cgrid == Permeability) cgrid = Centered; - /* - We handle looping on an arbitrary component grid by shifting - to the centered grid and then shifting back. The looping - coordinates are internally calculated on the odd-indexed - "centered grid", which has the virtue that it is disjoint for - each chunk and each chunk has enough information to interpolate all - of its field components onto this grid without communication. - Another virtue of this grid is that it is invariant under all of - our symmetry transformations, so we can uniquely decide which - transformed chunk gets to loop_in_chunks which grid point. - */ + /* Find the corners (is and ie) of the smallest bounding box for + wherec, on the grid of odd-coordinate ivecs (i.e. the + "dielectric/centered grid") and then shift back to the yee grid for c. */ vec yee_c(gv.yee_shift(Centered) - gv.yee_shift(cgrid)); ivec iyee_c(gv.iyee_shift(Centered) - gv.iyee_shift(cgrid)); volume wherec(where + yee_c); - - /* Find the corners (is and ie) of the smallest bounding box for - wherec, on the grid of odd-coordinate ivecs (i.e. the - "epsilon grid"). */ - ivec is(vec2diel_floor(wherec.get_min_corner(), gv.a, zero_ivec(gv.dim))); - ivec ie(vec2diel_ceil(wherec.get_max_corner(), gv.a, zero_ivec(gv.dim))); + ivec is(vec2diel_floor(wherec.get_min_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); + ivec ie(vec2diel_ceil(wherec.get_max_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); vec s0(gv.dim), e0(gv.dim), s1(gv.dim), e1(gv.dim); - compute_boundary_weights(gv, wherec, is, ie, snap_empty_dimensions, s0, e0, s1, e1); + compute_boundary_weights(gv, where, is, ie, snap_empty_dimensions, s0, e0, s1, e1); // loop over symmetry transformations of the chunks: for (int sn = 0; sn < (use_symmetry ? S.multiplicity() : 1); ++sn) { component cS = S.transform(cgrid, -sn); - ivec iyee_cS(S.transform_unshifted(iyee_c, -sn)); volume gvS = S.transform(gv.surroundings(), sn); vec L(gv.dim); @@ -387,16 +375,16 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con iL.set_direction(d, iabs(ilattice_vector(dS).in_direction(dS))); } - // figure out range of lattice shifts for which gvS intersects wherec: + // figure out range of lattice shifts for which gvS intersects where: ivec min_ishift(gv.dim), max_ishift(gv.dim); LOOP_OVER_DIRECTIONS(gv.dim, d) { if (boundaries[High][S.transform(d, -sn).d] == Periodic) { min_ishift.set_direction( d, - int(floor((wherec.in_direction_min(d) - gvS.in_direction_max(d)) / L.in_direction(d)))); + int(floor((where.in_direction_min(d) - gvS.in_direction_max(d)) / L.in_direction(d)))); max_ishift.set_direction( d, - int(ceil((wherec.in_direction_max(d) - gvS.in_direction_min(d)) / L.in_direction(d)))); + int(ceil((where.in_direction_max(d) - gvS.in_direction_min(d)) / L.in_direction(d)))); } else { min_ishift.set_direction(d, 0); @@ -420,8 +408,8 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (!chunks[i]->is_mine()) continue; // Chunk looping boundaries for owned points, shifted to centered grid and transformed: grid_volume gvu(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); - ivec _iscoS(S.transform(gvu.little_owned_corner(cS) + iyee_cS, sn)); - ivec _iecoS(S.transform(gvu.big_owned_corner(cS) + iyee_cS, sn)); + ivec _iscoS(S.transform(gvu.little_owned_corner(cS), sn)); + ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform // intersect the chunk points with is and ie volume (shifted): @@ -487,15 +475,15 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con // Determine integration "volumes" dV0 and dV1; double dV0 = 1.0, dV1 = 0.0; LOOP_OVER_DIRECTIONS(gv.dim, d) { - if (wherec.in_direction(d) > 0.0) dV0 *= gv.inva; + if (where.in_direction(d) > 0.0) dV0 *= gv.inva; } if (gv.dim == Dcyl) { dV1 = dV0 * 2 * pi * gv.inva; dV0 *= 2 * pi * - fabs((S.transform(chunks[i]->gv[isc], sn) + shift - yee_c).in_direction(R)); + fabs((S.transform(chunks[i]->gv[isc], sn) + shift).in_direction(R)); } - chunkloop(chunks[i], i, cS, isc - iyee_cS, iec - iyee_cS, s0c, s1c, e0c, e1c, dV0, dV1, + chunkloop(chunks[i], i, cS, isc, iec, s0c, s1c, e0c, e1c, dV0, dV1, shifti, ph, S, sn, chunkloop_data); } } diff --git a/tests/array-metadata.cpp b/tests/array-metadata.cpp index 1967ae719..2ca94a820 100644 --- a/tests/array-metadata.cpp +++ b/tests/array-metadata.cpp @@ -47,7 +47,7 @@ static ivec vec2diel_ceil(const vec &pt, double a, const ivec &equal_shift) { return ipt; } namespace meep { -void compute_boundary_weights(grid_volume gv, volume &wherec, ivec &is, ivec &ie, +void compute_boundary_weights(grid_volume gv, const volume &wherec, ivec &is, ivec &ie, bool snap_empty_dims, vec &s0, vec &e0, vec &s1, vec &e1); } From 92e3eae0a5a106633151cd5dea63cfb780e1221f Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 11 Jan 2022 17:48:14 -0500 Subject: [PATCH 062/155] almost fix --- src/loop_in_chunks.cpp | 15 +++++++++++++++ tests/symmetry.cpp | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index edde9069b..337d75fa6 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -411,6 +411,21 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con ivec _iscoS(S.transform(gvu.little_owned_corner(cS), sn)); ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform + + if (sn>0){ + for (int sm = sn-1; sm >= 0; --sm){//for previous transformations + component cSm = S.transform(cgrid, -sm); + ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); + ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); + ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); + LOOP_OVER_DIRECTIONS(gvu.dim, d){ + if (iecoSm.in_direction(d) == iscoS.in_direction(d)) { + iscoS.set_direction(d, iecoSm.in_direction(d)+2); + iecoS.set_direction(d, iecoS.in_direction(d)+2); + } + } + } + } // intersect the chunk points with is and ie volume (shifted): ivec iscS(max(is - shifti, iscoS)); diff --git a/tests/symmetry.cpp b/tests/symmetry.cpp index 5a9d1d775..c6c4b52ca 100644 --- a/tests/symmetry.cpp +++ b/tests/symmetry.cpp @@ -927,11 +927,11 @@ int main(int argc, char **argv) { if (!test_yperiodic_ymirror(one)) meep::abort("error in test_yperiodic_ymirror vacuum\n"); if (!test_yperiodic_ymirror(rods_2d)) meep::abort("error in test_yperiodic_ymirror rods2d\n"); - if (!pml_twomirrors(one)) meep::abort("error in pml_twomirrors vacuum\n"); + //if (!pml_twomirrors(one)) meep::abort("error in pml_twomirrors vacuum\n"); if (!test_origin_shift()) meep::abort("error in test_origin_shift\n"); - if (!exact_pml_rot2x_tm(one)) meep::abort("error in exact_pml_rot2x_tm vacuum\n"); + //if (!exact_pml_rot2x_tm(one)) meep::abort("error in exact_pml_rot2x_tm vacuum\n"); if (!test_metal_xmirror(rods_2d)) meep::abort("error in test_metal_xmirror rods_2d\n"); From c8ee02fe5c748d1db6dc6de58119803bef1faabd Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 11 Jan 2022 18:01:10 -0500 Subject: [PATCH 063/155] before fix --- src/loop_in_chunks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 337d75fa6..85bceed18 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -412,7 +412,7 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform - if (sn>0){ + if (false && sn>0){ for (int sm = sn-1; sm >= 0; --sm){//for previous transformations component cSm = S.transform(cgrid, -sm); ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); From c89a84fe9fe58d7199a954b9b40563f45e82f1cd Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 11 Jan 2022 20:51:15 -0500 Subject: [PATCH 064/155] one more loop over num_chunk --- src/loop_in_chunks.cpp | 32 ++++++++++++++++++++++++-------- tests/symmetry.cpp | 4 ++-- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 85bceed18..26a6427da 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -412,20 +412,36 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform - if (false && sn>0){ + std::set overlap_d; + for (int j = 0; j < num_chunks; ++j) { + if (!chunks[j]->is_mine()) continue; + // Chunk looping boundaries for owned points, shifted to centered grid and transformed: + grid_volume gvuj(use_symmetry ? chunks[j]->gv.unpad(gv) : chunks[j]->gv); + ivec _iscoSj(S.transform(gvuj.little_owned_corner(cS), sn)); + ivec _iecoSj(S.transform(gvuj.big_owned_corner(cS), sn)); + ivec iscoSj(min(_iscoSj, _iecoSj)), iecoSj(max(_iscoSj, _iecoSj)); // fix ordering due to to transform + + if (sn>0){ for (int sm = sn-1; sm >= 0; --sm){//for previous transformations component cSm = S.transform(cgrid, -sm); - ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); - ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); + + ivec _iscoSm(S.transform(gvuj.little_owned_corner(cSm), sm)); + ivec _iecoSm(S.transform(gvuj.big_owned_corner(cSm), sm)); ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); - LOOP_OVER_DIRECTIONS(gvu.dim, d){ - if (iecoSm.in_direction(d) == iscoS.in_direction(d)) { - iscoS.set_direction(d, iecoSm.in_direction(d)+2); - iecoS.set_direction(d, iecoS.in_direction(d)+2); + + LOOP_OVER_DIRECTIONS(gvuj.dim, d){ + if (iecoSm.in_direction(d) == iscoSj.in_direction(d)) { + overlap_d.insert(d); } - } + } } } + } + for (std::set::iterator set_i=overlap_d.begin();set_i!=overlap_d.end();++set_i){ + iscoS.set_direction(*set_i, iscoS.in_direction(*set_i)+2); + iecoS.set_direction(*set_i, iecoS.in_direction(*set_i)+2); + } + overlap_d.clear(); // intersect the chunk points with is and ie volume (shifted): ivec iscS(max(is - shifti, iscoS)); diff --git a/tests/symmetry.cpp b/tests/symmetry.cpp index c6c4b52ca..5a9d1d775 100644 --- a/tests/symmetry.cpp +++ b/tests/symmetry.cpp @@ -927,11 +927,11 @@ int main(int argc, char **argv) { if (!test_yperiodic_ymirror(one)) meep::abort("error in test_yperiodic_ymirror vacuum\n"); if (!test_yperiodic_ymirror(rods_2d)) meep::abort("error in test_yperiodic_ymirror rods2d\n"); - //if (!pml_twomirrors(one)) meep::abort("error in pml_twomirrors vacuum\n"); + if (!pml_twomirrors(one)) meep::abort("error in pml_twomirrors vacuum\n"); if (!test_origin_shift()) meep::abort("error in test_origin_shift\n"); - //if (!exact_pml_rot2x_tm(one)) meep::abort("error in exact_pml_rot2x_tm vacuum\n"); + if (!exact_pml_rot2x_tm(one)) meep::abort("error in exact_pml_rot2x_tm vacuum\n"); if (!test_metal_xmirror(rods_2d)) meep::abort("error in test_metal_xmirror rods_2d\n"); From fc22f588c19150601cb881ea16b10b68ceaeb4cb Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 11 Jan 2022 20:57:14 -0500 Subject: [PATCH 065/155] include set --- src/loop_in_chunks.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 26a6427da..de93b188f 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "meep.hpp" #include "meep_internals.hpp" From 05bd90ae400c7208481796e0ffd30d9894322a8a Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:09:12 -0500 Subject: [PATCH 066/155] different approach --- src/loop_in_chunks.cpp | 143 +++++++++++++++++++++++++++++++++++++++-- src/vec.cpp | 11 +++- 2 files changed, 146 insertions(+), 8 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index de93b188f..3d8801f4d 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include "meep.hpp" #include "meep_internals.hpp" @@ -357,12 +356,14 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con volume wherec(where + yee_c); ivec is(vec2diel_floor(wherec.get_min_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); ivec ie(vec2diel_ceil(wherec.get_max_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); + //printf("is (%i, %i, %i), ie (%i, %i, %i), component %s \n",is.x(),is.y(),is.z(), ie.x(),ie.y(),ie.z(),component_name(cgrid)); 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); // loop over symmetry transformations of the chunks: for (int sn = 0; sn < (use_symmetry ? S.multiplicity() : 1); ++sn) { + //printf(" sym sn %i of %i \n", sn, (use_symmetry ? S.multiplicity() : 1)); component cS = S.transform(cgrid, -sn); volume gvS = S.transform(gv.surroundings(), sn); @@ -395,6 +396,31 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con // loop over lattice shifts ivec ishift(min_ishift); + + ivec lowerboundary(gv.dim, INT_MAX); + if (use_symmetry && S.multiplicity() > 1){ + for (int sm = 0; sm < (use_symmetry ? S.multiplicity() : 1); ++sm) { + component cSm = S.transform(cgrid, -sm); + for (int i = 0; i < num_chunks; ++i) { + if (!chunks[i]->is_mine()) continue; + // Chunk looping boundaries for owned points, shifted to centered grid and transformed: + grid_volume gvu2(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); + ivec _iscoS2(S.transform(gvu2.little_owned_corner(cSm), sm)); + ivec _iecoS2(S.transform(gvu2.big_owned_corner(cSm), sm)); + //printf("_iscoS2 (%i, %i, %i), _iecoS2 (%i, %i, %i), component %s\n", _iscoS2.x(), _iscoS2.y(),_iscoS2.z(),_iecoS2.x(), _iecoS2.y(),_iecoS2.z(), component_name(cSm)); + lowerboundary = min(lowerboundary, min(_iscoS2, _iecoS2)); + } + } + LOOP_OVER_DIRECTIONS(gv.dim, d){ + int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? 0 : 2); + //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); + if (ishift.in_direction(d) != 0) lowerboundary.set_direction(d, lowerboundary.in_direction(d) + off_sym_shift); + //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); + } + } + + + do { complex ph = 1.0; vec shift(gv.dim, 0.0); @@ -404,6 +430,7 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con shifti.set_direction(d, iL.in_direction(d) * ishift.in_direction(d)); ph *= pow(eikna[d], ishift.in_direction(d)); } + //printf(" ishift (%i, %i, %i), shifti (%i, %i, %i) \n",ishift.x(),ishift.y(),ishift.z(), shifti.x(),shifti.y(),shifti.z()); for (int i = 0; i < num_chunks; ++i) { if (!chunks[i]->is_mine()) continue; @@ -411,9 +438,33 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con grid_volume gvu(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); ivec _iscoS(S.transform(gvu.little_owned_corner(cS), sn)); ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); - ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform + ivec iscoS(max(lowerboundary, min(_iscoS, _iecoS))), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform + + - std::set overlap_d; + /* + if (false && sn>0){ + for (int sm = sn-1; sm >= 0; --sm){ + component cSm = S.transform(cgrid, -sm); + ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); + ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); + ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); + LOOP_OVER_DIRECTIONS(gvu.dim, d){ + if (iecoSm.in_direction(d) == iscoS.in_direction(d)) { + iscoS.set_direction(d, iecoSm.in_direction(d)+2); + //iecoS.set_direction(d, iecoS.in_direction(d)+2); + } + //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); + } + } + } + */ + + //if (!use_symmetry) printf("no symmetry, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); + //if (use_symmetry) printf("before: symmetry sn %i, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", sn, iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); + + + /*std::set overlap_d; for (int j = 0; j < num_chunks; ++j) { if (!chunks[j]->is_mine()) continue; // Chunk looping boundaries for owned points, shifted to centered grid and transformed: @@ -429,24 +480,104 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con ivec _iscoSm(S.transform(gvuj.little_owned_corner(cSm), sm)); ivec _iecoSm(S.transform(gvuj.big_owned_corner(cSm), sm)); ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); + //printf("symmetry sm %i, iscoSm %i, iecoSm %i, component %s\n", sm, iscoSm.z(), iecoSm.z(), component_name(cSm)); LOOP_OVER_DIRECTIONS(gvuj.dim, d){ if (iecoSm.in_direction(d) == iscoSj.in_direction(d)) { overlap_d.insert(d); + //iscoS.set_direction(d, iecoSm.in_direction(d)+2); + //iecoS.set_direction(d, iecoS.in_direction(d)+2); } - } + //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); + } + + } } } + for (std::set::iterator set_i=overlap_d.begin();set_i!=overlap_d.end();++set_i){ iscoS.set_direction(*set_i, iscoS.in_direction(*set_i)+2); iecoS.set_direction(*set_i, iecoS.in_direction(*set_i)+2); } overlap_d.clear(); - + + if (sn>0){ + for (int sm = sn-1; sm >= 0; --sm){//for previous transformations + component cSm = S.transform(cgrid, -sm); + + ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); + ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); + ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); + //printf("symmetry sm %i, iscoSm %i, iecoSm %i, component %s\n", sm, iscoSm.z(), iecoSm.z(), component_name(cSm)); + + LOOP_OVER_DIRECTIONS(gvu.dim, d){ + if (iscoSm.in_direction(d) <= iecoS.in_direction(d)) { + iecoS.set_direction(d, iscoSm.in_direction(d)-2); + } + //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); + } + } + } + */ + + //if (use_symmetry) printf(" after: symmetry sn %i, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", sn, iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); + + ivec isym(gvu.dim, INT_MAX); + //printf("infty %i", -meep::infinity); + //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); + bool gvu_is_halved[3] = {false, false, false}; + + bool break_this[3]; + for (int dd = 0; dd < 3; dd++) { + const direction d = (direction)dd; + break_this[dd] = false; + for (int n = 0; n < S.multiplicity(); n++) + if (has_direction(gv.dim, d) && + (S.transform(d, n).d != d || S.transform(d, n).flipped)) { + if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) + master_printf("Padding %s to even number of grid points.\n", direction_name(d)); + break_this[dd] = true; + } + } + int break_mult = 1; + for (int d = 0; d < 3; d++) { + if (break_mult == S.multiplicity()) break_this[d] = false; + if (break_this[d]) { + break_mult *= 2; + if (verbosity > 0) + master_printf("Halving computational cell along direction %s\n", + direction_name(direction(d))); + gvu_is_halved[d] = true; + } + } + if (sn!=0){ + LOOP_OVER_DIRECTIONS(gvu.dim, d){ + + int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? gv.iyee_shift(cgrid).in_direction(d)+2 : 2); + //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); + if (gvu_is_halved[d]) isym.set_direction(d, S.i_symmetry_point.in_direction(d) - off_sym_shift); + //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); + } + } + //ivec isym= (iyee!=0?yee:2) along half directions,-infty else // intersect the chunk points with is and ie volume (shifted): + //printf(" iscoS (%i, %i, %i), iecoS (%i, %i, %i)\n", iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z()); + //printf(" isym (%i, %i, %i)\n", isym.x(), isym.y(),isym.z()); + //printf("component %s, iyee (%i, %i, %i) \n",component_name(cgrid), gv.iyee_shift(cgrid).x(), gv.iyee_shift(cgrid).y(),gv.iyee_shift(cgrid).z()); + //ivec iscS(max(isym,max(is - shifti, iscoS))); ivec iscS(max(is - shifti, iscoS)); - ivec iecS(min(ie - shifti, iecoS)); + ivec iecS(min(isym, min(ie - shifti, iecoS))); + //printf(" use-isym: iscS (%i, %i, %i), iecS (%i, %i, %i)\n",iscS.x(), iscS.y(),iscS.z(), iecS.x(), iecS.y(),iecS.z()); + + + //max(iscS, sym) + //printf("symmetry? %i\n", (S.multiplicity()>1)); + //printf(" no-isym: iscS (%i, %i, %i), iecS (%i, %i, %i)\n",iscS0.x(), iscS0.y(),iscS0.z(), iecS.x(), iecS.y(),iecS.z()); + + //ivec iscS(max(is - shifti, iscoS)); + //ivec iecS(min(ie - shifti, iecoS)); + if (iscS <= iecS) { // non-empty intersection // Determine weights at chunk looping boundaries: ivec isc(S.transform(iscS, -sn)), iec(S.transform(iecS, -sn)); diff --git a/src/vec.cpp b/src/vec.cpp index 479a50422..24434932d 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -1072,9 +1072,16 @@ grid_volume grid_volume::split_at_fraction(bool side_high, int split_pt, int spl // Halve the grid_volume for symmetry exploitation...must contain icenter! grid_volume grid_volume::halve(direction d) const { - grid_volume retval(*this); // note that icenter-io is always even by construction of grid_volume::icenter - retval.set_num_direction(d, (icenter().in_direction(d) - io.in_direction(d)) / 2); + /*retval.set_num_direction(d, (icenter().in_direction(d) - io.in_direction(d)) / 2); + retval.is_halved[d]=true; + return retval; + */ + //printf("d %i, icenter %i, io %i \n", d, icenter().in_direction(d), io.in_direction(d)); + retval.set_num_direction(d, 1+(icenter().in_direction(d) - io.in_direction(d)) / 2); + //printf("d %i, icenter %i, io %i \n", d, icenter().in_direction(d), retval.io.in_direction(d)); + retval.set_origin(d, (icenter().in_direction(d) - io.in_direction(d))-2); + //printf("d %i, icenter %i, io %i \n", d, retval.icenter().in_direction(d), retval.io.in_direction(d)); return retval; } From 776a9f6384f1eec34ff37d977bb256fb5a5be090 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:20:57 -0500 Subject: [PATCH 067/155] minor --- src/meep/vec.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/meep/vec.hpp b/src/meep/vec.hpp index 4bf31bafa..0e76f50bb 100644 --- a/src/meep/vec.hpp +++ b/src/meep/vec.hpp @@ -1216,12 +1216,13 @@ class symmetry { void operator=(const symmetry &); bool operator==(const symmetry &) const; bool operator!=(const symmetry &S) const { return !(*this == S); }; + ivec i_symmetry_point; private: signed_direction S[5]; std::complex ph; vec symmetry_point; - ivec i_symmetry_point; + //ivec i_symmetry_point; int g; // g is the multiplicity of the symmetry. symmetry *next; friend symmetry r_to_minus_r_symmetry(double m); From 3e0ab27954ab3fa1521458dc17a0f9ee2434f009 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:30:24 -0500 Subject: [PATCH 068/155] include climits --- src/vec.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vec.cpp b/src/vec.cpp index 24434932d..ac60bba39 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "meep_internals.hpp" #include "meepgeom.hpp" From 1826090d4ce104c9ee8ddddda0fe8f8c3d241246 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:38:16 -0500 Subject: [PATCH 069/155] include climits --- src/loop_in_chunks.cpp | 1 + src/vec.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 3d8801f4d..1ab2e2fb0 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "meep.hpp" #include "meep_internals.hpp" diff --git a/src/vec.cpp b/src/vec.cpp index ac60bba39..24434932d 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include "meep_internals.hpp" #include "meepgeom.hpp" From 02bb1a0053aa2c11883e1ae97d11d47237ffd284 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:48:20 -0500 Subject: [PATCH 070/155] fix retval --- src/vec.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vec.cpp b/src/vec.cpp index 24434932d..c2d3bb3e1 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -1072,6 +1072,7 @@ grid_volume grid_volume::split_at_fraction(bool side_high, int split_pt, int spl // Halve the grid_volume for symmetry exploitation...must contain icenter! grid_volume grid_volume::halve(direction d) const { + grid_volume retval(*this); // note that icenter-io is always even by construction of grid_volume::icenter /*retval.set_num_direction(d, (icenter().in_direction(d) - io.in_direction(d)) / 2); retval.is_halved[d]=true; From 939fae1e89913bc29f032971e0183fa53ba871b5 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 14:56:48 -0500 Subject: [PATCH 071/155] clean up --- src/loop_in_chunks.cpp | 134 ++++------------------------------------- src/vec.cpp | 8 --- 2 files changed, 13 insertions(+), 129 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 1ab2e2fb0..273309b1d 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -408,15 +408,12 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con grid_volume gvu2(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); ivec _iscoS2(S.transform(gvu2.little_owned_corner(cSm), sm)); ivec _iecoS2(S.transform(gvu2.big_owned_corner(cSm), sm)); - //printf("_iscoS2 (%i, %i, %i), _iecoS2 (%i, %i, %i), component %s\n", _iscoS2.x(), _iscoS2.y(),_iscoS2.z(),_iecoS2.x(), _iecoS2.y(),_iecoS2.z(), component_name(cSm)); lowerboundary = min(lowerboundary, min(_iscoS2, _iecoS2)); } } LOOP_OVER_DIRECTIONS(gv.dim, d){ int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? 0 : 2); - //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); if (ishift.in_direction(d) != 0) lowerboundary.set_direction(d, lowerboundary.in_direction(d) + off_sym_shift); - //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); } } @@ -431,8 +428,7 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con shifti.set_direction(d, iL.in_direction(d) * ishift.in_direction(d)); ph *= pow(eikna[d], ishift.in_direction(d)); } - //printf(" ishift (%i, %i, %i), shifti (%i, %i, %i) \n",ishift.x(),ishift.y(),ishift.z(), shifti.x(),shifti.y(),shifti.z()); - + for (int i = 0; i < num_chunks; ++i) { if (!chunks[i]->is_mine()) continue; // Chunk looping boundaries for owned points, shifted to centered grid and transformed: @@ -441,104 +437,18 @@ 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(lowerboundary, min(_iscoS, _iecoS))), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform - - - /* - if (false && sn>0){ - for (int sm = sn-1; sm >= 0; --sm){ - component cSm = S.transform(cgrid, -sm); - ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); - ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); - ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); - LOOP_OVER_DIRECTIONS(gvu.dim, d){ - if (iecoSm.in_direction(d) == iscoS.in_direction(d)) { - iscoS.set_direction(d, iecoSm.in_direction(d)+2); - //iecoS.set_direction(d, iecoS.in_direction(d)+2); - } - //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); - } - } - } - */ - - //if (!use_symmetry) printf("no symmetry, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); - //if (use_symmetry) printf("before: symmetry sn %i, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", sn, iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); - - - /*std::set overlap_d; - for (int j = 0; j < num_chunks; ++j) { - if (!chunks[j]->is_mine()) continue; - // Chunk looping boundaries for owned points, shifted to centered grid and transformed: - grid_volume gvuj(use_symmetry ? chunks[j]->gv.unpad(gv) : chunks[j]->gv); - ivec _iscoSj(S.transform(gvuj.little_owned_corner(cS), sn)); - ivec _iecoSj(S.transform(gvuj.big_owned_corner(cS), sn)); - ivec iscoSj(min(_iscoSj, _iecoSj)), iecoSj(max(_iscoSj, _iecoSj)); // fix ordering due to to transform - - if (sn>0){ - for (int sm = sn-1; sm >= 0; --sm){//for previous transformations - component cSm = S.transform(cgrid, -sm); - - ivec _iscoSm(S.transform(gvuj.little_owned_corner(cSm), sm)); - ivec _iecoSm(S.transform(gvuj.big_owned_corner(cSm), sm)); - ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); - //printf("symmetry sm %i, iscoSm %i, iecoSm %i, component %s\n", sm, iscoSm.z(), iecoSm.z(), component_name(cSm)); - - LOOP_OVER_DIRECTIONS(gvuj.dim, d){ - if (iecoSm.in_direction(d) == iscoSj.in_direction(d)) { - overlap_d.insert(d); - //iscoS.set_direction(d, iecoSm.in_direction(d)+2); - //iecoS.set_direction(d, iecoS.in_direction(d)+2); - } - //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); - } - - - } - } - } - - for (std::set::iterator set_i=overlap_d.begin();set_i!=overlap_d.end();++set_i){ - iscoS.set_direction(*set_i, iscoS.in_direction(*set_i)+2); - iecoS.set_direction(*set_i, iecoS.in_direction(*set_i)+2); - } - overlap_d.clear(); - - if (sn>0){ - for (int sm = sn-1; sm >= 0; --sm){//for previous transformations - component cSm = S.transform(cgrid, -sm); - - ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); - ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); - ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); - //printf("symmetry sm %i, iscoSm %i, iecoSm %i, component %s\n", sm, iscoSm.z(), iecoSm.z(), component_name(cSm)); - - LOOP_OVER_DIRECTIONS(gvu.dim, d){ - if (iscoSm.in_direction(d) <= iecoS.in_direction(d)) { - iecoS.set_direction(d, iscoSm.in_direction(d)-2); - } - //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); - } - } - } - */ - - //if (use_symmetry) printf(" after: symmetry sn %i, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", sn, iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); - ivec isym(gvu.dim, INT_MAX); - //printf("infty %i", -meep::infinity); - //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); bool gvu_is_halved[3] = {false, false, false}; - bool break_this[3]; - for (int dd = 0; dd < 3; dd++) { - const direction d = (direction)dd; - break_this[dd] = false; - for (int n = 0; n < S.multiplicity(); n++) - if (has_direction(gv.dim, d) && - (S.transform(d, n).d != d || S.transform(d, n).flipped)) { - if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) - master_printf("Padding %s to even number of grid points.\n", direction_name(d)); - break_this[dd] = true; + bool break_this[3]; + for (int dd = 0; dd < 3; dd++) { + const direction d = (direction)dd; + break_this[dd] = false; + for (int n = 0; n < S.multiplicity(); n++) + if (has_direction(gv.dim, d) && (S.transform(d, n).d != d || S.transform(d, n).flipped)) { + if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) + master_printf("Padding %s to even number of grid points.\n", direction_name(d)); + break_this[dd] = true; } } int break_mult = 1; @@ -547,37 +457,19 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (break_this[d]) { break_mult *= 2; if (verbosity > 0) - master_printf("Halving computational cell along direction %s\n", - direction_name(direction(d))); + master_printf("Halving computational cell along direction %s\n",direction_name(direction(d))); gvu_is_halved[d] = true; } } if (sn!=0){ - LOOP_OVER_DIRECTIONS(gvu.dim, d){ - + LOOP_OVER_DIRECTIONS(gvu.dim, d){ int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? gv.iyee_shift(cgrid).in_direction(d)+2 : 2); - //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); if (gvu_is_halved[d]) isym.set_direction(d, S.i_symmetry_point.in_direction(d) - off_sym_shift); - //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); } } - //ivec isym= (iyee!=0?yee:2) along half directions,-infty else - // intersect the chunk points with is and ie volume (shifted): - //printf(" iscoS (%i, %i, %i), iecoS (%i, %i, %i)\n", iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z()); - //printf(" isym (%i, %i, %i)\n", isym.x(), isym.y(),isym.z()); - //printf("component %s, iyee (%i, %i, %i) \n",component_name(cgrid), gv.iyee_shift(cgrid).x(), gv.iyee_shift(cgrid).y(),gv.iyee_shift(cgrid).z()); - //ivec iscS(max(isym,max(is - shifti, iscoS))); + ivec iscS(max(is - shifti, iscoS)); ivec iecS(min(isym, min(ie - shifti, iecoS))); - //printf(" use-isym: iscS (%i, %i, %i), iecS (%i, %i, %i)\n",iscS.x(), iscS.y(),iscS.z(), iecS.x(), iecS.y(),iecS.z()); - - - //max(iscS, sym) - //printf("symmetry? %i\n", (S.multiplicity()>1)); - //printf(" no-isym: iscS (%i, %i, %i), iecS (%i, %i, %i)\n",iscS0.x(), iscS0.y(),iscS0.z(), iecS.x(), iecS.y(),iecS.z()); - - //ivec iscS(max(is - shifti, iscoS)); - //ivec iecS(min(ie - shifti, iecoS)); if (iscS <= iecS) { // non-empty intersection // Determine weights at chunk looping boundaries: diff --git a/src/vec.cpp b/src/vec.cpp index c2d3bb3e1..602c3bbf2 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -1073,16 +1073,8 @@ grid_volume grid_volume::split_at_fraction(bool side_high, int split_pt, int spl // Halve the grid_volume for symmetry exploitation...must contain icenter! grid_volume grid_volume::halve(direction d) const { grid_volume retval(*this); - // note that icenter-io is always even by construction of grid_volume::icenter - /*retval.set_num_direction(d, (icenter().in_direction(d) - io.in_direction(d)) / 2); - retval.is_halved[d]=true; - return retval; - */ - //printf("d %i, icenter %i, io %i \n", d, icenter().in_direction(d), io.in_direction(d)); retval.set_num_direction(d, 1+(icenter().in_direction(d) - io.in_direction(d)) / 2); - //printf("d %i, icenter %i, io %i \n", d, icenter().in_direction(d), retval.io.in_direction(d)); retval.set_origin(d, (icenter().in_direction(d) - io.in_direction(d))-2); - //printf("d %i, icenter %i, io %i \n", d, retval.icenter().in_direction(d), retval.io.in_direction(d)); return retval; } From 748b349353478e971c8fe0b920aeafacb87421d2 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 10 Feb 2022 18:35:11 -0500 Subject: [PATCH 072/155] using flipped --- src/loop_in_chunks.cpp | 61 +++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 39 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 273309b1d..77304a325 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -398,27 +398,6 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con // loop over lattice shifts ivec ishift(min_ishift); - ivec lowerboundary(gv.dim, INT_MAX); - if (use_symmetry && S.multiplicity() > 1){ - for (int sm = 0; sm < (use_symmetry ? S.multiplicity() : 1); ++sm) { - component cSm = S.transform(cgrid, -sm); - for (int i = 0; i < num_chunks; ++i) { - if (!chunks[i]->is_mine()) continue; - // Chunk looping boundaries for owned points, shifted to centered grid and transformed: - grid_volume gvu2(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); - ivec _iscoS2(S.transform(gvu2.little_owned_corner(cSm), sm)); - ivec _iecoS2(S.transform(gvu2.big_owned_corner(cSm), sm)); - lowerboundary = min(lowerboundary, min(_iscoS2, _iecoS2)); - } - } - LOOP_OVER_DIRECTIONS(gv.dim, d){ - int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? 0 : 2); - if (ishift.in_direction(d) != 0) lowerboundary.set_direction(d, lowerboundary.in_direction(d) + off_sym_shift); - } - } - - - do { complex ph = 1.0; vec shift(gv.dim, 0.0); @@ -428,27 +407,26 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con shifti.set_direction(d, iL.in_direction(d) * ishift.in_direction(d)); ph *= pow(eikna[d], ishift.in_direction(d)); } - for (int i = 0; i < num_chunks; ++i) { if (!chunks[i]->is_mine()) continue; - // Chunk looping boundaries for owned points, shifted to centered grid and transformed: - grid_volume gvu(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); + grid_volume gvu(chunks[i]->gv); ivec _iscoS(S.transform(gvu.little_owned_corner(cS), sn)); ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); - ivec iscoS(max(lowerboundary, min(_iscoS, _iecoS))), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform - + ivec iscoS(max(user_volume.little_owned_corner(cgrid), min(_iscoS, _iecoS))), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform + ivec isym(gvu.dim, INT_MAX); bool gvu_is_halved[3] = {false, false, false}; - bool break_this[3]; - for (int dd = 0; dd < 3; dd++) { - const direction d = (direction)dd; - break_this[dd] = false; - for (int n = 0; n < S.multiplicity(); n++) - if (has_direction(gv.dim, d) && (S.transform(d, n).d != d || S.transform(d, n).flipped)) { - if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) - master_printf("Padding %s to even number of grid points.\n", direction_name(d)); - break_this[dd] = true; + bool break_this[3]; + for (int dd = 0; dd < 3; dd++) { + const direction d = (direction)dd; + break_this[dd] = false; + for (int n = 0; n < S.multiplicity(); n++) + if (has_direction(gv.dim, d) && + (S.transform(d, n).d != d || S.transform(d, n).flipped)) { + if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) + master_printf("Padding %s to even number of grid points.\n", direction_name(d)); + break_this[dd] = true; } } int break_mult = 1; @@ -457,19 +435,24 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (break_this[d]) { break_mult *= 2; if (verbosity > 0) - master_printf("Halving computational cell along direction %s\n",direction_name(direction(d))); + master_printf("Halving computational cell along direction %s\n", + direction_name(direction(d))); gvu_is_halved[d] = true; } } - if (sn!=0){ - LOOP_OVER_DIRECTIONS(gvu.dim, d){ + if (use_symmetry && sn!=0){ + LOOP_OVER_DIRECTIONS(gvu.dim, d){ int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? gv.iyee_shift(cgrid).in_direction(d)+2 : 2); if (gvu_is_halved[d]) isym.set_direction(d, S.i_symmetry_point.in_direction(d) - off_sym_shift); } } ivec iscS(max(is - shifti, iscoS)); - ivec iecS(min(isym, min(ie - shifti, iecoS))); + ivec chunk_corner(gvu.little_owned_corner(cgrid)); + LOOP_OVER_DIRECTIONS(gv.dim, d) { + if ((S.transform(d, sn).d != d) != (S.transform(d, sn).flipped)) iecoS.set_direction(d, min(isym, iecoS).in_direction(d)); + } + ivec iecS( min(ie - shifti, iecoS)); if (iscS <= iecS) { // non-empty intersection // Determine weights at chunk looping boundaries: From 0702f01b9d9e3decba051714fee8b9d204d53749 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 10 Feb 2022 19:36:45 -0500 Subject: [PATCH 073/155] add missing changes --- src/structure.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/structure.cpp b/src/structure.cpp index d0776b5a0..a8b18e9a6 100644 --- a/src/structure.cpp +++ b/src/structure.cpp @@ -186,8 +186,6 @@ std::unique_ptr choose_chunkdivision(grid_volume &gv, volume & // Before padding, find the corresponding geometric grid_volume. v = gv.surroundings(); // Pad the little cell in any direction that we've shrunk: - for (int d = 0; d < 3; d++) - if (break_this[d]) gv.pad_self((direction)d); } int proc_id = 0; @@ -517,13 +515,14 @@ void structure::use_pml(direction d, boundary_side b, double dx) { if (dx <= 0.0) return; grid_volume pml_volume = gv; pml_volume.set_num_direction(d, int(dx * user_volume.a + 1 + 0.5)); // FIXME: exact value? - if (b == High) + const int v_to_user_shift = + (user_volume.big_corner().in_direction(d) - gv.big_corner().in_direction(d)) / 2; + + if (b == High){ pml_volume.set_origin(d, user_volume.big_corner().in_direction(d) - pml_volume.num_direction(d) * 2); - const int v_to_user_shift = - (user_volume.little_corner().in_direction(d) - gv.little_corner().in_direction(d)) / 2; - if (b == Low && v_to_user_shift != 0) pml_volume.set_num_direction(d, pml_volume.num_direction(d) + v_to_user_shift); + } add_to_effort_volumes(pml_volume, 0.60); // FIXME: manual value for pml effort } From 508bb7aa93e21e2d4538c091893ea7b1e24e7efe Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Sat, 12 Feb 2022 16:56:50 -0500 Subject: [PATCH 074/155] fix bug --- src/vec.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vec.cpp b/src/vec.cpp index 602c3bbf2..377a46956 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -1074,7 +1074,7 @@ grid_volume grid_volume::split_at_fraction(bool side_high, int split_pt, int spl grid_volume grid_volume::halve(direction d) const { grid_volume retval(*this); retval.set_num_direction(d, 1+(icenter().in_direction(d) - io.in_direction(d)) / 2); - retval.set_origin(d, (icenter().in_direction(d) - io.in_direction(d))-2); + retval.set_origin(d, icenter().in_direction(d)-2); return retval; } From cad882300582b9f98fda24333c848ee2f6265a9f Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Sat, 12 Feb 2022 18:24:57 -0500 Subject: [PATCH 075/155] fix pml --- src/structure.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structure.cpp b/src/structure.cpp index a8b18e9a6..96728bd93 100644 --- a/src/structure.cpp +++ b/src/structure.cpp @@ -516,7 +516,7 @@ void structure::use_pml(direction d, boundary_side b, double dx) { grid_volume pml_volume = gv; pml_volume.set_num_direction(d, int(dx * user_volume.a + 1 + 0.5)); // FIXME: exact value? const int v_to_user_shift = - (user_volume.big_corner().in_direction(d) - gv.big_corner().in_direction(d)) / 2; + (gv.big_corner().in_direction(d) - user_volume.big_corner().in_direction(d)) / 2; if (b == High){ pml_volume.set_origin(d, user_volume.big_corner().in_direction(d) - From 418ad76fa0ce2f9049835883786ba56ea56f5122 Mon Sep 17 00:00:00 2001 From: Krishna Gadepalli <34969407+kkg4theweb@users.noreply.github.com> Date: Thu, 18 Nov 2021 22:04:03 -0800 Subject: [PATCH 076/155] Fix memory leaks in SWIG wrappers. (#1826) * Fix memory leaks in SWIG wrappers. * move delete into material_free Co-authored-by: Steven G. Johnson --- libpympb/pympb.cpp | 1 - python/meep.i | 5 ++--- python/typemap_utils.cpp | 1 - src/meepgeom.cpp | 2 +- tests/array-slice-ll.cpp | 1 - 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/libpympb/pympb.cpp b/libpympb/pympb.cpp index cd2ff355b..6e4a00240 100644 --- a/libpympb/pympb.cpp +++ b/libpympb/pympb.cpp @@ -1631,7 +1631,6 @@ void mode_solver::clear_geometry_list() { if (geometry_list.num_items && geometry_list.items) { for(int i = 0; i < geometry_list.num_items; ++i) { material_free((meep_geom::material_data *)geometry_list.items[i].material); - delete (meep_geom::material_data *)geometry_list.items[i].material; geometric_object_destroy(geometry_list.items[i]); } delete[] geometry_list.items; diff --git a/python/meep.i b/python/meep.i index fe301cf58..948d6cc78 100644 --- a/python/meep.i +++ b/python/meep.i @@ -767,7 +767,6 @@ meep::volume_list *make_volume_list(const meep::volume &v, int c, %typemap(freearg) GEOMETRIC_OBJECT { if ($1.material) { material_free((material_data *)$1.material); - delete (material_data *)$1.material; geometric_object_destroy($1); } } @@ -983,7 +982,6 @@ void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObjec %typemap(freearg) material_type { if ($1) { material_free($1); - delete $1; } } @@ -1488,6 +1486,7 @@ void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObjec // it gets garbage collected and the file gets closed. %newobject meep::fields::open_h5file; +%newobject meep::make_output_directory; %newobject _get_eigenmode; %rename(_vec) meep::vec::vec; @@ -1989,7 +1988,7 @@ meep_geom::geom_epsilon* _set_materials(meep::structure * s, meep_geom::geom_epsilon *existing_geps, bool output_chunk_costs, const meep::binary_partition *my_bp) { - + meep_geom::geom_epsilon *geps; if (existing_geps) { geps = existing_geps; diff --git a/python/typemap_utils.cpp b/python/typemap_utils.cpp index e2182b3f8..4470cbc89 100644 --- a/python/typemap_utils.cpp +++ b/python/typemap_utils.cpp @@ -1127,7 +1127,6 @@ void gobj_list_freearg(geometric_object_list* objs) { SWIG_PYTHON_THREAD_SCOPED_BLOCK; for(int i = 0; i < objs->num_items; ++i) { material_free((material_data *)objs->items[i].material); - delete (material_data *)objs->items[i].material; geometric_object_destroy(objs->items[i]); } delete[] objs->items; diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index 077f2f52b..6ef7ff5fe 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -33,7 +33,6 @@ static void set_default_material(material_type _default_material) { if (default_material != NULL) { if (default_material == _default_material) return; material_free((material_type)default_material); - delete (material_type)default_material; default_material = NULL; } @@ -118,6 +117,7 @@ void material_free(material_type m) { delete[] m->weights; m->weights = NULL; + delete m; } bool material_type_equal(const material_type m1, const material_type m2) { diff --git a/tests/array-slice-ll.cpp b/tests/array-slice-ll.cpp index df7c55855..af754384b 100644 --- a/tests/array-slice-ll.cpp +++ b/tests/array-slice-ll.cpp @@ -139,7 +139,6 @@ int main(int argc, char *argv[]) { meep_geom::material_type vacuum = meep_geom::vacuum; auto material_deleter = [](meep_geom::material_data *m) { meep_geom::material_free(m); - delete m; }; std::unique_ptr dielectric( meep_geom::make_dielectric(eps), material_deleter); From 4261746ddb1d13a2ec3eb942c7ce2c843a05354c Mon Sep 17 00:00:00 2001 From: mochen4 Date: Mon, 22 Nov 2021 11:24:46 -0500 Subject: [PATCH 077/155] Fix adjoint gradient with conductivities (#1830) * damp_fix * increase run time Co-authored-by: Mo Chen --- python/tests/test_adjoint_solver.py | 105 ++++++++++++++++++++++++++++ src/meepgeom.cpp | 26 ++++++- 2 files changed, 129 insertions(+), 2 deletions(-) diff --git a/python/tests/test_adjoint_solver.py b/python/tests/test_adjoint_solver.py index b9c3150a9..dae6e80f9 100644 --- a/python/tests/test_adjoint_solver.py +++ b/python/tests/test_adjoint_solver.py @@ -261,7 +261,86 @@ def J(dft_mon): sim.reset_meep() return f, dJ_du + +def forward_simulation_damping(design_params, frequencies=None, mat2=silicon): + matgrid = mp.MaterialGrid(mp.Vector3(Nx,Ny), + mp.air, + mat2, + weights=design_params.reshape(Nx,Ny), + damping = 3.14*fcen) + + matgrid_geometry = [mp.Block(center=mp.Vector3(), + size=mp.Vector3(design_region_size.x,design_region_size.y,0), + material=matgrid)] + + geometry = waveguide_geometry + matgrid_geometry + + sim = mp.Simulation(resolution=resolution, + cell_size=cell_size, + boundary_layers=pml_xy, + sources=wvg_source, + geometry=geometry) + + if not frequencies: + frequencies = [fcen] + + mode = sim.add_mode_monitor(frequencies, + mp.ModeRegion(center=mp.Vector3(0.5*sxy-dpml-0.1), + size=mp.Vector3(0,sxy-2*dpml,0)), + yee_grid=True, + eig_parity=eig_parity) + + sim.run(until_after_sources=mp.stop_when_dft_decayed()) + + + coeff = sim.get_eigenmode_coefficients(mode,[1],eig_parity).alpha[0,:,0] + S12 = np.power(np.abs(coeff),2) + sim.reset_meep() + return S12 + +def adjoint_solver_damping(design_params, frequencies=None, mat2=silicon): + matgrid = mp.MaterialGrid(mp.Vector3(Nx,Ny), + mp.air, + mat2, + weights=np.ones((Nx,Ny)), + damping = 3.14*fcen) + matgrid_region = mpa.DesignRegion(matgrid, + volume=mp.Volume(center=mp.Vector3(), size=mp.Vector3(design_region_size.x, design_region_size.y, 0))) + + matgrid_geometry = [mp.Block(center=matgrid_region.center, + size=matgrid_region.size, + material=matgrid)] + geometry = waveguide_geometry + matgrid_geometry + + sim = mp.Simulation(resolution=resolution, + cell_size=cell_size, + boundary_layers=pml_xy, + sources=wvg_source, + geometry=geometry) + + if not frequencies: + frequencies = [fcen] + + obj_list = [mpa.EigenmodeCoefficient(sim, mp.Volume(center=mp.Vector3(0.5*sxy-dpml-0.1), + size=mp.Vector3(0,sxy-2*dpml,0)), 1, eig_parity=eig_parity)] + + def J(mode_mon): + return npa.power(npa.abs(mode_mon),2) + + + opt = mpa.OptimizationProblem( + simulation=sim, + objective_functions=J, + objective_arguments=obj_list, + design_regions=[matgrid_region], + frequencies=frequencies, + minimum_run_time=150) + + f, dJ_du = opt([design_params]) + + sim.reset_meep() + return f, dJ_du def mapping(x,filter_radius,eta,beta): filtered_field = mpa.conic_filter(x, @@ -403,6 +482,32 @@ def test_complex_fields(self): print("Directional derivative -- adjoint solver: {}, FD: {}".format(adj_scale,fd_grad)) tol = 0.005 if mp.is_single_precision() else 0.0008 self.assertClose(adj_scale,fd_grad,epsilon=tol) + + def test_damping(self): + print("*** TESTING CONDUCTIVITIES ***") + + for frequencies in [[1/1.58, fcen, 1/1.53]]: + ## compute gradient using adjoint solver + adjsol_obj, adjsol_grad = adjoint_solver_damping(p, frequencies) + + ## compute unperturbed S12 + S12_unperturbed = forward_simulation_damping(p, frequencies) + + ## compare objective results + print("S12 -- adjoint solver: {}, traditional simulation: {}".format(adjsol_obj,S12_unperturbed)) + self.assertClose(adjsol_obj,S12_unperturbed,epsilon=1e-6) + + ## compute perturbed S12 + S12_perturbed = forward_simulation_damping(p+dp, frequencies) + + ## compare gradients + if adjsol_grad.ndim < 2: + adjsol_grad = np.expand_dims(adjsol_grad,axis=1) + adj_scale = (dp[None,:]@adjsol_grad).flatten() + fd_grad = S12_perturbed-S12_unperturbed + print("Directional derivative -- adjoint solver: {}, FD: {}".format(adj_scale,fd_grad)) + tol = 0.06 if mp.is_single_precision() else 0.03 + self.assertClose(adj_scale,fd_grad,epsilon=tol) def test_offdiagonal(self): print("*** TESTING OFFDIAGONAL COMPONENTS ***") diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index 6ef7ff5fe..dda2b5782 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -2572,7 +2572,7 @@ void eff_chi1inv_row_disp(meep::component c, std::complex chi1inv_row[3] vector3 dummy; dummy.x = dummy.y = dummy.z = 0.0; double conductivityCur = vec_to_value(mm->D_conductivity_diag, dummy, i); - a = std::complex(1.0, conductivityCur / (freq)); + a = std::complex(1.0, conductivityCur / (2*meep::pi*freq)); // compute lorentzian component b = cvec_to_value(mm->epsilon_diag, mm->epsilon_offdiag, i); @@ -2613,6 +2613,23 @@ void eff_chi1inv_row_disp(meep::component c, std::complex chi1inv_row[3] } } +std::complex cond_cmp(meep::component c, const meep::vec &r, double freq, geom_epsilon *geps) { + // locate the proper material + material_type md; + geps->get_material_pt(md, r); + const medium_struct *mm = &(md->medium); + + // get the row we care about + switch (component_direction(c)) { + case meep::X: + case meep::R: return std::complex(1.0, mm->D_conductivity_diag.x / (2*meep::pi*freq)); + case meep::Y: + case meep::P: return std::complex(1.0, mm->D_conductivity_diag.y / (2*meep::pi*freq)); + case meep::Z: return std::complex(1.0, mm->D_conductivity_diag.z / (2*meep::pi*freq)); + case meep::NO_DIRECTION: meep::abort("Invalid adjoint field component"); + } +} + std::complex get_material_gradient( const meep::vec &r, // current point const meep::component adjoint_c, // adjoint field component @@ -2686,7 +2703,7 @@ std::complex get_material_gradient( u[idx] = orig; for (int i=0;i<3;i++) dA_du[i] = (row_1[i] - row_2[i])/(2*du); - return dA_du[dir_idx] * fields_f; + return dA_du[dir_idx] * fields_f * cond_cmp(forward_c,r,freq,geps); } } @@ -2912,6 +2929,11 @@ void material_grids_addgradient(double *v, size_t ng, std::complex *fiel meep::ivec ip = gv.iloc(adjoint_c,idx); meep::vec p = gv.loc(adjoint_c,idx); std::complex adj = GET_FIELDS(fields_a,ci_adjoint,f_i,idx_fields); + + material_type md; + geps->get_material_pt(md, p); + if (!md->trivial) adj *= cond_cmp(adjoint_c,p,frequencies[f_i], geps); + double cyl_scale; int ci_forward = 0; FOR_MY_COMPONENTS(forward_c) { From 6dbc7198c88c3571ecf2a6845c3b2e99ceb882d0 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Tue, 23 Nov 2021 18:55:07 -0800 Subject: [PATCH 078/155] plot geometry for dispersive materials without initializing structure object (#1827) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * plot geometry without initializing structure class * update docstrings * rotate epsilon grid by 90 degrees to properly orient axes * add support for dispersive ε * update markdown pages from docstrings * make frequency and resolution parameters of plot2D dictionary keys of eps_parameters * reinstate frequency parameter of plot2D and add warning that it is deprecated * fix order of frequency check --- doc/docs/Python_User_Interface.md | 45 +++-- doc/docs/Python_User_Interface.md.in | 2 +- python/meep.i | 12 +- python/simulation.py | 73 ++++--- python/tests/test_visualization.py | 2 +- python/visualization.py | 291 ++++++++++++++++----------- src/meepgeom.cpp | 27 ++- src/meepgeom.hpp | 3 +- 8 files changed, 270 insertions(+), 185 deletions(-) diff --git a/doc/docs/Python_User_Interface.md b/doc/docs/Python_User_Interface.md index 1904b9fc7..581dd3382 100644 --- a/doc/docs/Python_User_Interface.md +++ b/doc/docs/Python_User_Interface.md @@ -676,16 +676,21 @@ frequency-independent part of $\mu$ (the $\omega\to\infty$ limit).
```python -def get_epsilon_grid(self, xtics=None, ytics=None, ztics=None): +def get_epsilon_grid(self, + xtics=None, + ytics=None, + ztics=None, + frequency=0): ```
Given three 1d NumPy arrays (`xtics`,`ytics`,`ztics`) which define the coordinates of a Cartesian -grid anywhere within the cell volume, compute the trace of the $\varepsilon$ tensor from the `geometry` -exactly at each grid point. (For [`MaterialGrid`](#materialgrid)s, the $\varepsilon$ at each grid -point is computed using bilinear interpolation from the nearest `MaterialGrid` points and possibly also -projected to form a level set.) Note that this is different from `get_epsilon_point` which computes +grid anywhere within the cell volume, compute the trace of the $\varepsilon(f)$ tensor at frequency +$f$ (in Meep units) from the `geometry` exactly at each grid point. `frequency` defaults to 0 which is +the instantaneous $\varepsilon$. (For [`MaterialGrid`](#materialgrid)s, the $\varepsilon$ at each +grid point is computed using bilinear interpolation from the nearest `MaterialGrid` points and possibly +also projected to form a level set.) Note that this is different from `get_epsilon_point` which computes $\varepsilon$ by bilinearly interpolating from the nearest Yee grid points. This function is useful for sampling the material geometry to any arbitrary resolution. The return value is a NumPy array with shape equivalent to `numpy.meshgrid(xtics,ytics,ztics)`. Empty dimensions are collapsed. @@ -2067,7 +2072,7 @@ including outside the cell and a `near2far` object, returns the computed of fields $(E_x^1,E_y^1,E_z^1,H_x^1,H_y^1,H_z^1,E_x^2,E_y^2,E_z^2,H_x^2,H_y^2,H_z^2,...)$ in Cartesian coordinates and $(E_r^1,E_\phi^1,E_z^1,H_r^1,H_\phi^1,H_z^1,E_r^2,E_\phi^2,E_z^2,H_r^2,H_\phi^2,H_z^2,...)$ -in cylindrical coordinates for the frequencies 1,2,…,`nfreq`. +in cylindrical coordinates for the frequencies 1,2,...,`nfreq`.
@@ -2609,7 +2614,7 @@ fr = mp.FluxRegion(volume=mp.GDSII_vol(fname, layer, zmin, zmax)) ### Data Visualization -This module provides basic visualization functionality for the simulation domain. The spirit of the module is to provide functions that can be called with *no customization options whatsoever* and will do useful relevant things by default, but which can also be customized in cases where you *do* want to take the time to spruce up the output. The `Simulation` class provides the following methods: +This module provides basic visualization functionality for the simulation domain. The intent of the module is to provide functions that can be called with *no customization options whatsoever* and will do useful relevant things by default, but which can also be customized in cases where you *do* want to take the time to spruce up the output. The `Simulation` class provides the following methods: @@ -2648,14 +2653,18 @@ sim.run(...) field_func = lambda x: 20*np.log10(np.abs(x)) import matplotlib.pyplot as plt sim.plot2D(fields=mp.Ez, - field_parameters={'alpha':0.8, 'cmap':'RdBu', 'interpolation':'none', 'post_process':field_func}, - boundary_parameters={'hatch':'o', 'linewidth':1.5, 'facecolor':'y', 'edgecolor':'b', 'alpha':0.3}) + field_parameters={'alpha':0.8, 'cmap':'RdBu', 'interpolation':'none', 'post_process':field_func}, + boundary_parameters={'hatch':'o', 'linewidth':1.5, 'facecolor':'y', 'edgecolor':'b', 'alpha':0.3}) plt.show() plt.savefig('sim_domain.png') ``` +If you just want to quickly visualize the simulation domain without the fields (i.e., when +setting up your simulation), there is no need to invoke the `run` function prior to calling +`plot2D`. Just define the `Simulation` object followed by any DFT monitors and then +invoke `plot2D`. -Note: When running a [parallel simulation](Parallel_Meep.md), the `plot2D` function expects to be called -on all processes, but only generates a plot on the master process. +Note: When running a [parallel simulation](Parallel_Meep.md), the `plot2D` function expects +to be called on all processes, but only generates a plot on the master process. **Parameters:** @@ -2677,6 +2686,12 @@ on all processes, but only generates a plot on the master process. - `alpha=1.0`: transparency of geometry - `contour=False`: if `True`, plot a contour of the geometry rather than its image - `contour_linewidth=1`: line width of the contour lines if `contour=True` + - `frequency=None`: for materials with a [frequency-dependent + permittivity](Materials.md#material-dispersion) $\varepsilon(f)$, specifies the + frequency $f$ (in Meep units) of the real part of the permittivity to use in the + plot. Defaults to the `frequency` parameter of the [Source](#source) object. + - `resolution=None`: the resolution of the $\varepsilon$ grid. Defaults to the + `resolution` of the `Simulation` object. * `boundary_parameters`: a `dict` of optional plotting parameters that override the default parameters for the boundary layers. - `alpha=1.0`: transparency of boundary layers @@ -2713,10 +2728,6 @@ on all processes, but only generates a plot on the master process. - `alpha=0.6`: transparency of fields - `post_process=np.real`: post processing function to apply to fields (must be a function object) -* `frequency`: for materials with a [frequency-dependent - permittivity](Materials.md#material-dispersion) $\varepsilon(f)$, specifies the - frequency $f$ (in Meep units) of the real part of the permittivity to use in the - plot. Defaults to the `frequency` parameter of the [Source](#source) object.
@@ -4401,7 +4412,7 @@ def __init__(self, medium2, weights=None, grid_type='U_DEFAULT', - do_averaging=False, + do_averaging=True, beta=0, eta=0.5, damping=0): @@ -6893,7 +6904,7 @@ A class used to record the fields during timestepping (i.e., a [`run`](#run-func function). The object is initialized prior to timestepping by specifying the simulation object and the field component. The object can then be passed to any [step-function modifier](#step-function-modifiers). For example, one can record the -Ez fields at every one time unit using: +$E_z$ fields at every one time unit using: ```py animate = mp.Animate2D(sim, diff --git a/doc/docs/Python_User_Interface.md.in b/doc/docs/Python_User_Interface.md.in index 362f652ec..2ed09ca3d 100644 --- a/doc/docs/Python_User_Interface.md.in +++ b/doc/docs/Python_User_Interface.md.in @@ -480,7 +480,7 @@ This feature is only available if Meep is built with [libGDSII](Build_From_Sourc ### Data Visualization -This module provides basic visualization functionality for the simulation domain. The spirit of the module is to provide functions that can be called with *no customization options whatsoever* and will do useful relevant things by default, but which can also be customized in cases where you *do* want to take the time to spruce up the output. The `Simulation` class provides the following methods: +This module provides basic visualization functionality for the simulation domain. The intent of the module is to provide functions that can be called with *no customization options whatsoever* and will do useful relevant things by default, but which can also be customized in cases where you *do* want to take the time to spruce up the output. The `Simulation` class provides the following methods: @@ Simulation.plot2D @@ @@ Simulation.plot3D @@ diff --git a/python/meep.i b/python/meep.i index 948d6cc78..f1dc8012b 100644 --- a/python/meep.i +++ b/python/meep.i @@ -1095,12 +1095,12 @@ void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObjec $1 = (double *)array_data($input); } -%typecheck(SWIG_TYPECHECK_POINTER, fragment="NumPy_Fragments") double* grid_vals { +%typecheck(SWIG_TYPECHECK_POINTER, fragment="NumPy_Fragments") std::complex* grid_vals { $1 = is_array($input); } -%typemap(in, fragment="NumPy_Macros") double* grid_vals { - $1 = (double *)array_data($input); +%typemap(in, fragment="NumPy_Macros") std::complex* grid_vals { + $1 = (std::complex *)array_data($input); } // typemap for solve_cw: @@ -2040,7 +2040,8 @@ void _get_epsilon_grid(geometric_object_list gobj_list, int nx, double *xtics, int ny, double *ytics, int nz, double *ztics, - double *grid_vals) { + std::complex *grid_vals, + double frequency) { meep_geom::get_epsilon_grid(gobj_list, mlist, _default_material, @@ -2051,7 +2052,8 @@ void _get_epsilon_grid(geometric_object_list gobj_list, nx, xtics, ny, ytics, nz, ztics, - grid_vals); + grid_vals, + frequency); } %} diff --git a/python/simulation.py b/python/simulation.py index 83742fc71..5fbf66155 100644 --- a/python/simulation.py +++ b/python/simulation.py @@ -2225,18 +2225,19 @@ def get_mu_point(self, pt, frequency=0): v3 = py_v3_to_vec(self.dimensions, pt, self.is_cylindrical) return self.fields.get_mu(v3,frequency) - def get_epsilon_grid(self, xtics=None, ytics=None, ztics=None): + def get_epsilon_grid(self, xtics=None, ytics=None, ztics=None, frequency=0): """ Given three 1d NumPy arrays (`xtics`,`ytics`,`ztics`) which define the coordinates of a Cartesian - grid anywhere within the cell volume, compute the trace of the $\\varepsilon$ tensor from the `geometry` - exactly at each grid point. (For [`MaterialGrid`](#materialgrid)s, the $\\varepsilon$ at each grid - point is computed using bilinear interpolation from the nearest `MaterialGrid` points and possibly also - projected to form a level set.) Note that this is different from `get_epsilon_point` which computes + grid anywhere within the cell volume, compute the trace of the $\\varepsilon(f)$ tensor at frequency + $f$ (in Meep units) from the `geometry` exactly at each grid point. `frequency` defaults to 0 which is + the instantaneous $\\varepsilon$. (For [`MaterialGrid`](#materialgrid)s, the $\\varepsilon$ at each + grid point is computed using bilinear interpolation from the nearest `MaterialGrid` points and possibly + also projected to form a level set.) Note that this is different from `get_epsilon_point` which computes $\\varepsilon$ by bilinearly interpolating from the nearest Yee grid points. This function is useful for sampling the material geometry to any arbitrary resolution. The return value is a NumPy array with shape equivalent to `numpy.meshgrid(xtics,ytics,ztics)`. Empty dimensions are collapsed. """ - grid_vals = np.squeeze(np.empty((len(xtics), len(ytics), len(ztics)))) + grid_vals = np.squeeze(np.empty((len(xtics), len(ytics), len(ztics)), dtype=np.complex128)) gv = self._create_grid_volume(False) mp._get_epsilon_grid(self.geometry, self.extra_materials, @@ -2247,7 +2248,8 @@ def get_epsilon_grid(self, xtics=None, ytics=None, ztics=None): len(xtics), xtics, len(ytics), ytics, len(ztics), ztics, - grid_vals) + grid_vals, + frequency) return grid_vals def get_filename_prefix(self): @@ -2755,8 +2757,8 @@ def get_farfield(self, near2far, x): (Fourier-transformed) "far" fields at `x` as list of length 6`nfreq`, consisting of fields $(E_x^1,E_y^1,E_z^1,H_x^1,H_y^1,H_z^1,E_x^2,E_y^2,E_z^2,H_x^2,H_y^2,H_z^2,...)$ in Cartesian coordinates and - $(E_r^1,E_\phi^1,E_z^1,H_r^1,H_\phi^1,H_z^1,E_r^2,E_\phi^2,E_z^2,H_r^2,H_\phi^2,H_z^2,...)$ - in cylindrical coordinates for the frequencies 1,2,…,`nfreq`. + $(E_r^1,E_\\phi^1,E_z^1,H_r^1,H_\\phi^1,H_z^1,E_r^2,E_\\phi^2,E_z^2,H_r^2,H_\\phi^2,H_z^2,...)$ + in cylindrical coordinates for the frequencies 1,2,...,`nfreq`. """ return mp._get_farfield(near2far.swigobj, py_v3_to_vec(self.dimensions, x, is_cylindrical=self.is_cylindrical)) @@ -4084,14 +4086,18 @@ def plot2D(self, ax=None, output_plane=None, fields=None, labels=False, field_func = lambda x: 20*np.log10(np.abs(x)) import matplotlib.pyplot as plt sim.plot2D(fields=mp.Ez, - field_parameters={'alpha':0.8, 'cmap':'RdBu', 'interpolation':'none', 'post_process':field_func}, - boundary_parameters={'hatch':'o', 'linewidth':1.5, 'facecolor':'y', 'edgecolor':'b', 'alpha':0.3}) + field_parameters={'alpha':0.8, 'cmap':'RdBu', 'interpolation':'none', 'post_process':field_func}, + boundary_parameters={'hatch':'o', 'linewidth':1.5, 'facecolor':'y', 'edgecolor':'b', 'alpha':0.3}) plt.show() plt.savefig('sim_domain.png') ``` + If you just want to quickly visualize the simulation domain without the fields (i.e., when + setting up your simulation), there is no need to invoke the `run` function prior to calling + `plot2D`. Just define the `Simulation` object followed by any DFT monitors and then + invoke `plot2D`. - Note: When running a [parallel simulation](Parallel_Meep.md), the `plot2D` function expects to be called - on all processes, but only generates a plot on the master process. + Note: When running a [parallel simulation](Parallel_Meep.md), the `plot2D` function expects + to be called on all processes, but only generates a plot on the master process. **Parameters:** @@ -4113,6 +4119,12 @@ def plot2D(self, ax=None, output_plane=None, fields=None, labels=False, - `alpha=1.0`: transparency of geometry - `contour=False`: if `True`, plot a contour of the geometry rather than its image - `contour_linewidth=1`: line width of the contour lines if `contour=True` + - `frequency=None`: for materials with a [frequency-dependent + permittivity](Materials.md#material-dispersion) $\\varepsilon(f)$, specifies the + frequency $f$ (in Meep units) of the real part of the permittivity to use in the + plot. Defaults to the `frequency` parameter of the [Source](#source) object. + - `resolution=None`: the resolution of the $\\varepsilon$ grid. Defaults to the + `resolution` of the `Simulation` object. * `boundary_parameters`: a `dict` of optional plotting parameters that override the default parameters for the boundary layers. - `alpha=1.0`: transparency of boundary layers @@ -4149,23 +4161,26 @@ def plot2D(self, ax=None, output_plane=None, fields=None, labels=False, - `alpha=0.6`: transparency of fields - `post_process=np.real`: post processing function to apply to fields (must be a function object) - * `frequency`: for materials with a [frequency-dependent - permittivity](Materials.md#material-dispersion) $\\varepsilon(f)$, specifies the - frequency $f$ (in Meep units) of the real part of the permittivity to use in the - plot. Defaults to the `frequency` parameter of the [Source](#source) object. - """ - return vis.plot2D(self, ax=ax, output_plane=output_plane, fields=fields, labels=labels, - eps_parameters=eps_parameters, boundary_parameters=boundary_parameters, - source_parameters=source_parameters,monitor_parameters=monitor_parameters, - field_parameters=field_parameters, frequency=frequency, - plot_eps_flag=plot_eps_flag, plot_sources_flag=plot_sources_flag, - plot_monitors_flag=plot_monitors_flag, plot_boundaries_flag=plot_boundaries_flag, - **kwargs) - - - def plot_fields(self,**kwargs): - return vis.plot_fields(self,**kwargs) + return vis.plot2D(self, + ax=ax, + output_plane=output_plane, + fields=fields, + labels=labels, + eps_parameters=eps_parameters, + boundary_parameters=boundary_parameters, + source_parameters=source_parameters, + monitor_parameters=monitor_parameters, + field_parameters=field_parameters, + frequency=frequency, + plot_eps_flag=plot_eps_flag, + plot_sources_flag=plot_sources_flag, + plot_monitors_flag=plot_monitors_flag, + plot_boundaries_flag=plot_boundaries_flag, + **kwargs) + + def plot_fields(self, **kwargs): + return vis.plot_fields(self, **kwargs) def plot3D(self): """ diff --git a/python/tests/test_visualization.py b/python/tests/test_visualization.py index e2b9d2846..1b030716e 100644 --- a/python/tests/test_visualization.py +++ b/python/tests/test_visualization.py @@ -161,7 +161,7 @@ def test_animation_output(self): sim = setup_sim() # generate 2D simulation Animate = mp.Animate2D(sim,fields=mp.Ez, realtime=False, normalize=False) # Check without normalization - Animate_norm = mp.Animate2D(sim,mp.Ez,realtime=False,normalize=True) # Check with normalization + Animate_norm = mp.Animate2D(sim, mp.Ez, realtime=False, normalize=True) # Check with normalization # test both animation objects during same run sim.run( diff --git a/python/visualization.py b/python/visualization.py index b42a3dcf7..1e17f6a08 100644 --- a/python/visualization.py +++ b/python/visualization.py @@ -47,7 +47,9 @@ 'cmap':'binary', 'alpha':1.0, 'contour':False, - 'contour_linewidth':1 + 'contour_linewidth':1, + 'frequency':None, + 'resolution':None } default_boundary_parameters = { @@ -95,7 +97,7 @@ def filter_dict(dict_to_filter, func_with_kwargs): # ------------------------------------------------------- # # Routines to add legends to plot -def place_label(ax,label_text,x,y,centerx,centery,label_parameters=None): +def place_label(ax, label_text, x, y, centerx, centery, label_parameters=None): label_parameters = default_label_parameters if label_parameters is None else dict(default_label_parameters, **label_parameters) @@ -112,29 +114,37 @@ def place_label(ax,label_text,x,y,centerx,centery,label_parameters=None): else: ytext = offset - ax.annotate(label_text, xy=(x, y), xytext=(xtext,ytext), - textcoords='offset points', ha='center', va='bottom', - bbox=dict(boxstyle='round,pad=0.2', fc=color, alpha=alpha), - arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.5', - color=color)) + ax.annotate(label_text, xy=(x, y), xytext=(xtext, ytext), + textcoords='offset points', ha='center', va='bottom', + bbox=dict(boxstyle='round,pad=0.2', fc=color, alpha=alpha), + arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.5', + color=color)) return ax # ------------------------------------------------------- # # Helper functions used to plot volumes on a 2D plane -# Returns the intersection points of 2 Volumes. +# Returns the intersection points of two Volumes. # Volumes must be a line, plane, or rectangular prism # (since they are volume objects) -def intersect_volume_volume(volume1,volume2): +def intersect_volume_volume(volume1, volume2): # volume1 ............... [volume] # volume2 ............... [volume] # Represent the volumes by an "upper" and "lower" coordinate - U1 = [volume1.center.x+volume1.size.x/2,volume1.center.y+volume1.size.y/2,volume1.center.z+volume1.size.z/2] - L1 = [volume1.center.x-volume1.size.x/2,volume1.center.y-volume1.size.y/2,volume1.center.z-volume1.size.z/2] - - U2 = [volume2.center.x+volume2.size.x/2,volume2.center.y+volume2.size.y/2,volume2.center.z+volume2.size.z/2] - L2 = [volume2.center.x-volume2.size.x/2,volume2.center.y-volume2.size.y/2,volume2.center.z-volume2.size.z/2] + U1 = [volume1.center.x+volume1.size.x/2, + volume1.center.y+volume1.size.y/2, + volume1.center.z+volume1.size.z/2] + L1 = [volume1.center.x-volume1.size.x/2, + volume1.center.y-volume1.size.y/2, + volume1.center.z-volume1.size.z/2] + + U2 = [volume2.center.x+volume2.size.x/2, + volume2.center.y+volume2.size.y/2, + volume2.center.z+volume2.size.z/2] + L2 = [volume2.center.x-volume2.size.x/2, + volume2.center.y-volume2.size.y/2, + volume2.center.z-volume2.size.z/2] # Evaluate intersection U = np.min([U1,U2],axis=0) @@ -164,13 +174,13 @@ def intersect_volume_volume(volume1,volume2): # All of the 2D plotting routines need an output plane over which to plot. # The user has many options to specify this output plane. They can pass # the output_plane parameter, which is a 2D volume object. They can specify -# a volume using in_volume, which stores the volume as a C volume, not a python +# a volume using in_volume, which stores the volume as a C volume, not a Python # volume. They can also do nothing and plot the XY plane through Z=0. # # Not only do we need to check for all of these possibilities, but we also need # to check if the user accidentally specifies a plane that stretches beyond the # simulation domain. -def get_2D_dimensions(sim,output_plane): +def get_2D_dimensions(sim, output_plane): from meep.simulation import Volume # Pull correct plane from user @@ -182,12 +192,12 @@ def get_2D_dimensions(sim,output_plane): plane_center, plane_size = (sim.geometry_center, sim.cell_size) plane_volume = Volume(center=plane_center,size=plane_size) - if plane_size.x!=0 and plane_size.y!=0 and plane_size.z!=0: + if plane_size.x != 0 and plane_size.y != 0 and plane_size.z != 0: raise ValueError("Plane volume must be 2D (a plane).") - check_volume = Volume(center=sim.geometry_center,size=sim.cell_size) + check_volume = Volume(center=sim.geometry_center, size=sim.cell_size) - vertices = intersect_volume_volume(check_volume,plane_volume) + vertices = intersect_volume_volume(check_volume, plane_volume) if len(vertices) == 0: raise ValueError("The specified user volume is completely outside of the simulation domain.") @@ -203,10 +213,7 @@ def get_2D_dimensions(sim,output_plane): # ------------------------------------------------------- # # actual plotting routines -def plot_volume(sim,ax,volume,output_plane=None,plotting_parameters=None,label=None): - if not sim._is_initialized: - sim.init_sim() - +def plot_volume(sim, ax, volume, output_plane=None, plotting_parameters=None, label=None): import matplotlib.patches as patches from matplotlib import pyplot as plt from meep.simulation import Volume @@ -215,39 +222,57 @@ def plot_volume(sim,ax,volume,output_plane=None,plotting_parameters=None,label=N plotting_parameters = default_volume_parameters if plotting_parameters is None else dict(default_volume_parameters, **plotting_parameters) # Get domain measurements - sim_center, sim_size = get_2D_dimensions(sim,output_plane) + sim_center, sim_size = get_2D_dimensions(sim, output_plane) - plane = Volume(center=sim_center,size=sim_size) + plane = Volume(center=sim_center, size=sim_size) # Pull volume parameters size = volume.size center = volume.center - xmax = center.x+size.x/2 - xmin = center.x-size.x/2 - ymax = center.y+size.y/2 - ymin = center.y-size.y/2 - zmax = center.z+size.z/2 - zmin = center.z-size.z/2 + xmax = center.x + size.x/2 + xmin = center.x - size.x/2 + ymax = center.y + size.y/2 + ymin = center.y - size.y/2 + zmax = center.z + size.z/2 + zmin = center.z - size.z/2 # Add labels if requested if label is not None and mp.am_master(): if sim_size.x == 0: - ax = place_label(ax,label,center.y,center.z,sim_center.y,sim_center.z,label_parameters=plotting_parameters) + ax = place_label(ax, + label, + center.y, + center.z, + sim_center.y, + sim_center.z, + label_parameters=plotting_parameters) elif sim_size.y == 0: - ax = place_label(ax,label,center.x,center.z,sim_center.x,sim_center.z,label_parameters=plotting_parameters) + ax = place_label(ax, + label, + center.x, + center.z, + sim_center.x, + sim_center.z, + label_parameters=plotting_parameters) elif sim_size.z == 0: - ax = place_label(ax,label,center.x,center.y,sim_center.x,sim_center.y,label_parameters=plotting_parameters) + ax = place_label(ax, + label, + center.x, + center.y, + sim_center.x, + sim_center.y, + label_parameters=plotting_parameters) # Intersect plane with volume - intersection = intersect_volume_volume(volume,plane) + intersection = intersect_volume_volume(volume, plane) # Sort the points in a counter clockwise manner to ensure convex polygon is formed def sort_points(xy): xy = np.squeeze(xy) - xy_mean = np.mean(xy,axis=0) - theta = np.arctan2(xy[:,1]-xy_mean[1],xy[:,0]-xy_mean[0]) - return xy[np.argsort(theta,axis=0),:] + xy_mean = np.mean(xy, axis=0) + theta = np.arctan2(xy[:,1] - xy_mean[1], xy[:,0] - xy_mean[0]) + return xy[np.argsort(theta, axis=0), :] if mp.am_master(): # Point volume @@ -269,16 +294,16 @@ def sort_points(xy): elif len(intersection) == 2: line_args = {key:value for key, value in plotting_parameters.items() if key in ['color','linestyle','linewidth','alpha']} # Plot YZ - if sim_size.x==0: - ax.plot([a.y for a in intersection],[a.z for a in intersection], **line_args) + if sim_size.x == 0: + ax.plot([a.y for a in intersection], [a.z for a in intersection], **line_args) return ax - #Plot XZ - elif sim_size.y==0: - ax.plot([a.x for a in intersection],[a.z for a in intersection], **line_args) + # Plot XZ + elif sim_size.y == 0: + ax.plot([a.x for a in intersection], [a.z for a in intersection], **line_args) return ax # Plot XY - elif sim_size.z==0: - ax.plot([a.x for a in intersection],[a.y for a in intersection], **line_args) + elif sim_size.z == 0: + ax.plot([a.x for a in intersection], [a.y for a in intersection], **line_args) return ax else: return ax @@ -287,15 +312,15 @@ def sort_points(xy): elif len(intersection) > 2: planar_args = {key:value for key, value in plotting_parameters.items() if key in ['edgecolor','linewidth','facecolor','hatch','alpha']} # Plot YZ - if sim_size.x==0: + if sim_size.x == 0: ax.add_patch(patches.Polygon(sort_points([[a.y,a.z] for a in intersection]), **planar_args)) return ax - #Plot XZ + # Plot XZ elif sim_size.y==0: ax.add_patch(patches.Polygon(sort_points([[a.x,a.z] for a in intersection]), **planar_args)) return ax # Plot XY - elif sim_size.z==0: + elif sim_size.z == 0: ax.add_patch(patches.Polygon(sort_points([[a.x,a.y] for a in intersection]), **planar_args)) return ax else: @@ -304,16 +329,32 @@ def sort_points(xy): return ax return ax -def plot_eps(sim,ax,output_plane=None,eps_parameters=None,frequency=0): - if sim.structure is None: - sim.init_sim() - - +def plot_eps(sim, ax, output_plane=None, eps_parameters=None, frequency=None): # consolidate plotting parameters eps_parameters = default_eps_parameters if eps_parameters is None else dict(default_eps_parameters, **eps_parameters) + # Determine a frequency to plot all epsilon + if frequency is not None: + warnings.warn('The frequency parameter of plot2D has been deprecated. Use the frequency key of the eps_parameters dictionary instead.') + eps_parameters['frequency'] = frequency + if eps_parameters['frequency'] is None: + try: + # Continuous sources + eps_parameters['frequency'] = sim.sources[0].frequency + except: + try: + # Gaussian sources + eps_parameters['frequency'] = sim.sources[0].src.frequency + except: + try: + # Custom sources + eps_parameters['frequency'] = sim.sources[0].src.center_frequency + except: + # No sources + eps_parameters['frequency'] = 0 + # Get domain measurements - sim_center, sim_size = get_2D_dimensions(sim,output_plane) + sim_center, sim_size = get_2D_dimensions(sim, output_plane) xmin = sim_center.x - sim_size.x/2 xmax = sim_center.x + sim_size.x/2 @@ -322,28 +363,43 @@ def plot_eps(sim,ax,output_plane=None,eps_parameters=None,frequency=0): zmin = sim_center.z - sim_size.z/2 zmax = sim_center.z + sim_size.z/2 - center = Vector3(sim_center.x,sim_center.y,sim_center.z) - cell_size = Vector3(sim_size.x,sim_size.y,sim_size.z) + center = Vector3(sim_center.x, sim_center.y, sim_center.z) + cell_size = Vector3(sim_size.x, sim_size.y, sim_size.z) + + grid_resolution = eps_parameters['resolution'] if eps_parameters['resolution'] else sim.resolution + Nx = int((xmax - xmin) * grid_resolution + 1) + Ny = int((ymax - ymin) * grid_resolution + 1) + Nz = int((zmax - zmin) * grid_resolution + 1) if sim_size.x == 0: # Plot y on x axis, z on y axis (YZ plane) - extent = [ymin,ymax,zmin,zmax] + extent = [ymin, ymax, zmin, zmax] xlabel = 'Y' ylabel = 'Z' + xtics = np.array([sim_center.x]) + ytics = np.linspace(ymin, ymax, Ny) + ztics = np.linspace(zmin, zmax, Nz) elif sim_size.y == 0: # Plot x on x axis, z on y axis (XZ plane) - extent = [xmin,xmax,zmin,zmax] + extent = [xmin, xmax, zmin, zmax] xlabel = 'X' ylabel = 'Z' + xtics = np.linspace(xmin, xmax, Nx) + ytics = np.array([sim_center.y]) + ztics = np.linspace(zmin, zmax, Nz) elif sim_size.z == 0: # Plot x on x axis, y on y axis (XY plane) - extent = [xmin,xmax,ymin,ymax] + extent = [xmin, xmax, ymin, ymax] xlabel = 'X' ylabel = 'Y' + xtics = np.linspace(xmin, xmax, Nx) + ytics = np.linspace(ymin, ymax, Ny) + ztics = np.array([sim_center.z]) else: raise ValueError("A 2D plane has not been specified...") - eps_data = np.rot90(np.real(sim.get_array(center=center, size=cell_size, component=mp.Dielectric, frequency=frequency))) + eps_data = np.rot90(np.real(sim.get_epsilon_grid(xtics, ytics, ztics, eps_parameters['frequency']))) + if mp.am_master(): if eps_parameters['contour']: ax.contour(eps_data, 0, colors='black', origin='upper', extent=extent, linewidths=eps_parameters['contour_linewidth']) @@ -354,14 +410,11 @@ def plot_eps(sim,ax,output_plane=None,eps_parameters=None,frequency=0): return ax -def plot_boundaries(sim,ax,output_plane=None,boundary_parameters=None): - if not sim._is_initialized: - sim.init_sim() - +def plot_boundaries(sim, ax, output_plane=None, boundary_parameters=None): # consolidate plotting parameters boundary_parameters = default_boundary_parameters if boundary_parameters is None else dict(default_boundary_parameters, **boundary_parameters) - def get_boundary_volumes(thickness,direction,side): + def get_boundary_volumes(thickness, direction, side): from meep.simulation import Volume thickness = boundary.thickness @@ -403,20 +456,20 @@ def get_boundary_volumes(thickness,direction,side): import itertools for boundary in sim.boundary_layers: - # All 4 side are the same + # All four sides are the same if boundary.direction == mp.ALL and boundary.side == mp.ALL: if sim.dimensions == 1: dims = [mp.X] elif sim.dimensions == 2: - dims = [mp.X,mp.Y] + dims = [mp.X, mp.Y] elif sim.dimensions == 3: - dims = [mp.X,mp.Y,mp.Z] + dims = [mp.X, mp.Y, mp.Z] else: raise ValueError("Invalid simulation dimensions") for permutation in itertools.product(dims, [mp.Low, mp.High]): vol = get_boundary_volumes(boundary.thickness,*permutation) ax = plot_volume(sim,ax,vol,output_plane,plotting_parameters=boundary_parameters) - # 2 sides are the same + # two sides are the same elif boundary.side == mp.ALL: for side in [mp.Low, mp.High]: vol = get_boundary_volumes(boundary.thickness,boundary.direction,side) @@ -427,10 +480,7 @@ def get_boundary_volumes(thickness,direction,side): ax = plot_volume(sim,ax,vol,output_plane,plotting_parameters=boundary_parameters) return ax -def plot_sources(sim,ax,output_plane=None,labels=False,source_parameters=None): - if not sim._is_initialized: - sim.init_sim() - +def plot_sources(sim, ax, output_plane=None, labels=False, source_parameters=None): from meep.simulation import Volume # consolidate plotting parameters @@ -443,13 +493,10 @@ def plot_sources(sim,ax,output_plane=None,labels=False,source_parameters=None): ax = plot_volume(sim,ax,vol,output_plane,plotting_parameters=source_parameters,label=label) return ax -def plot_monitors(sim,ax,output_plane=None,labels=False,monitor_parameters=None): - if not sim._is_initialized: - sim.init_sim() - +def plot_monitors(sim, ax, output_plane=None, labels=False, monitor_parameters=None): from meep.simulation import Volume - # consolidate plotting parameters + # consolidate plotting parameters monitor_parameters = default_monitor_parameters if monitor_parameters is None else dict(default_monitor_parameters, **monitor_parameters) label = 'monitor' if labels else None @@ -460,7 +507,7 @@ def plot_monitors(sim,ax,output_plane=None,labels=False,monitor_parameters=None) ax = plot_volume(sim,ax,vol,output_plane,plotting_parameters=monitor_parameters,label=label) return ax -def plot_fields(sim,ax=None,fields=None,output_plane=None,field_parameters=None): +def plot_fields(sim, ax=None, fields=None, output_plane=None, field_parameters=None): if not sim._is_initialized: sim.init_sim() @@ -472,7 +519,7 @@ def plot_fields(sim,ax=None,fields=None,output_plane=None,field_parameters=None) # user specifies a field component if fields in [mp.Ex, mp.Ey, mp.Ez, mp.Hx, mp.Hy, mp.Hz]: # Get domain measurements - sim_center, sim_size = get_2D_dimensions(sim,output_plane) + sim_center, sim_size = get_2D_dimensions(sim, output_plane) xmin = sim_center.x - sim_size.x/2 xmax = sim_center.x + sim_size.x/2 @@ -481,22 +528,22 @@ def plot_fields(sim,ax=None,fields=None,output_plane=None,field_parameters=None) zmin = sim_center.z - sim_size.z/2 zmax = sim_center.z + sim_size.z/2 - center = Vector3(sim_center.x,sim_center.y,sim_center.z) - cell_size = Vector3(sim_size.x,sim_size.y,sim_size.z) + center = Vector3(sim_center.x, sim_center.y, sim_center.z) + cell_size = Vector3(sim_size.x, sim_size.y, sim_size.z) if sim_size.x == 0: # Plot y on x axis, z on y axis (YZ plane) - extent = [ymin,ymax,zmin,zmax] + extent = [ymin, ymax, zmin, zmax] xlabel = 'Y' ylabel = 'Z' elif sim_size.y == 0: # Plot x on x axis, z on y axis (XZ plane) - extent = [xmin,xmax,zmin,zmax] + extent = [xmin, xmax, zmin, zmax] xlabel = 'X' ylabel = 'Z' elif sim_size.z == 0: # Plot x on x axis, y on y axis (XY plane) - extent = [xmin,xmax,ymin,ymax] + extent = [xmin, xmax, ymin, ymax] xlabel = 'X' ylabel = 'Y' fields = sim.get_array(center=center, size=cell_size, component=fields) @@ -515,73 +562,72 @@ def plot_fields(sim,ax=None,fields=None,output_plane=None,field_parameters=None) return np.rot90(fields) return ax -def plot2D(sim,ax=None, output_plane=None, fields=None, labels=False, - eps_parameters=None,boundary_parameters=None, - source_parameters=None,monitor_parameters=None, +def plot2D(sim, ax=None, output_plane=None, fields=None, labels=False, + eps_parameters=None, boundary_parameters=None, + source_parameters=None, monitor_parameters=None, field_parameters=None, frequency=None, plot_eps_flag=True, plot_sources_flag=True, plot_monitors_flag=True, plot_boundaries_flag=True): - # Initialize the simulation - if sim.structure is None: - sim.init_sim() # Ensure a figure axis exists if ax is None and mp.am_master(): from matplotlib import pyplot as plt ax = plt.gca() - # Determine a frequency to plot all epsilon - if frequency is None: - try: - # Continuous sources - frequency = sim.sources[0].frequency - except: - try: - # Gaussian sources - frequency = sim.sources[0].src.frequency - except: - try: - # Custom sources - frequency = sim.sources[0].src.center_frequency - except: - # No sources - frequency = 0 # validate the output plane to ensure proper 2D coordinates from meep.simulation import Volume - sim_center, sim_size = get_2D_dimensions(sim,output_plane) - output_plane = Volume(center=sim_center,size=sim_size) + sim_center, sim_size = get_2D_dimensions(sim, output_plane) + output_plane = Volume(center=sim_center, size=sim_size) # Plot geometry if plot_eps_flag: - ax = plot_eps(sim,ax,output_plane=output_plane,eps_parameters=eps_parameters,frequency=frequency) + ax = plot_eps(sim, ax, output_plane=output_plane, + eps_parameters=eps_parameters, frequency=frequency) # Plot boundaries if plot_boundaries_flag: - ax = plot_boundaries(sim,ax,output_plane=output_plane,boundary_parameters=boundary_parameters) + ax = plot_boundaries(sim, ax, output_plane=output_plane, + boundary_parameters=boundary_parameters) # Plot sources if plot_sources_flag: - ax = plot_sources(sim,ax,output_plane=output_plane,labels=labels,source_parameters=source_parameters) + ax = plot_sources(sim, ax, output_plane=output_plane, + labels=labels, source_parameters=source_parameters) # Plot monitors if plot_monitors_flag: - ax = plot_monitors(sim,ax,output_plane=output_plane,labels=labels,monitor_parameters=monitor_parameters) + ax = plot_monitors(sim, ax, output_plane=output_plane, + labels=labels, monitor_parameters=monitor_parameters) # Plot fields - ax = plot_fields(sim,ax,fields,output_plane=output_plane,field_parameters=field_parameters) + if fields: + ax = plot_fields(sim, ax, fields, output_plane=output_plane, + field_parameters=field_parameters) return ax def plot3D(sim): from mayavi import mlab - if not sim._is_initialized: - sim.init_sim() - if sim.dimensions < 3: raise ValueError("Simulation must have 3 dimensions to visualize 3D") - eps_data = sim.get_epsilon() + xmin = sim.geometry_center.x - 0.5*sim.cell_size.x + xmax = sim.geometry_center.x + 0.5*sim.cell_size.x + ymin = sim.geometry_center.y - 0.5*sim.cell_size.y + ymax = sim.geometry_center.y + 0.5*sim.cell_size.y + zmin = sim.geometry_center.z - 0.5*sim.cell_size.z + zmax = sim.geometry_center.z + 0.5*sim.cell_size.z + + Nx = int(sim.cell_size.x * sim.resolution) + 1 + Ny = int(sim.cell_size.y * sim.resolution) + 1 + Nz = int(sim.cell_size.z * sim.resolution) + 1 + + xtics = np.linspace(xmin, xmax, Nx) + ytics = np.linspace(ymin, ymax, Ny) + ztics = np.linspace(zmin, zmax, Nz) + + eps_data = sim.get_epsilon_grid(xtics, ytics, ztics) s = mlab.contour3d(eps_data, colormap="YlGnBu") return s @@ -711,7 +757,7 @@ class Animate2D(object): function). The object is initialized prior to timestepping by specifying the simulation object and the field component. The object can then be passed to any [step-function modifier](#step-function-modifiers). For example, one can record the - Ez fields at every one time unit using: + $E_z$ fields at every one time unit using: ```py animate = mp.Animate2D(sim, @@ -792,7 +838,6 @@ def mod1(ax): self.plot_modifiers = plot_modifiers self.customization_args = customization_args - self.cumulative_fields = [] self._saved_frames = [] @@ -813,7 +858,7 @@ def __call__(self,sim,todo): # Initialize the plot if not self.init: filtered_plot2D = filter_dict(self.customization_args, plot2D) - ax = sim.plot2D(ax=self.ax,fields=self.fields,**filtered_plot2D) + ax = sim.plot2D(ax=self.ax, fields=self.fields, **filtered_plot2D) # Run the plot modifier functions if self.plot_modifiers: for k in range(len(self.plot_modifiers)): @@ -827,7 +872,7 @@ def __call__(self,sim,todo): else: # Update the plot filtered_plot_fields= filter_dict(self.customization_args, plot_fields) - fields = sim.plot_fields(fields=self.fields,**filtered_plot_fields) + fields = sim.plot_fields(fields=self.fields, **filtered_plot_fields) if mp.am_master(): self.ax.images[-1].set_data(fields) self.ax.images[-1].set_clim(vmin=0.8*np.min(fields), vmax=0.8*np.max(fields)) @@ -851,7 +896,7 @@ def __call__(self,sim,todo): if self.normalize and mp.am_master(): if mp.verbosity.meep > 0: print("Normalizing field data...") - fields = np.array(self.cumulative_fields) / np.max(np.abs(self.cumulative_fields),axis=(0,1,2)) + fields = np.array(self.cumulative_fields) / np.max(np.abs(self.cumulative_fields), axis=(0,1,2)) for k in range(len(self.cumulative_fields)): self.ax.images[-1].set_data(fields[k,:,:]) self.ax.images[-1].set_clim(vmin=-0.8, vmax=0.8) diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index dda2b5782..a5fdfb56f 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -2557,13 +2557,11 @@ void invert_tensor(std::complex t_inv[9], std::complex t[9]) { #undef minv(x,y) } -void eff_chi1inv_row_disp(meep::component c, std::complex chi1inv_row[3], - const meep::vec &r, double freq, geom_epsilon *geps) { +void get_chi1_tensor_disp(std::complex tensor[9], const meep::vec &r, double freq, geom_epsilon *geps) { // locate the proper material material_type md; geps->get_material_pt(md, r); const medium_struct *mm = &(md->medium); - std::complex tensor[9], tensor_inv[9]; // loop over all the tensor components for (int i = 0; i < 9; i++) { @@ -2574,7 +2572,7 @@ void eff_chi1inv_row_disp(meep::component c, std::complex chi1inv_row[3] double conductivityCur = vec_to_value(mm->D_conductivity_diag, dummy, i); a = std::complex(1.0, conductivityCur / (2*meep::pi*freq)); - // compute lorentzian component + // compute lorentzian component including the instantaneous ε b = cvec_to_value(mm->epsilon_diag, mm->epsilon_offdiag, i); for (const auto &mm_susc: mm->E_susceptibilities) { meep::lorentzian_susceptibility sus = meep::lorentzian_susceptibility( @@ -2586,7 +2584,12 @@ void eff_chi1inv_row_disp(meep::component c, std::complex chi1inv_row[3] // elementwise multiply tensor[i] = a * b; } +} +void eff_chi1inv_row_disp(meep::component c, std::complex chi1inv_row[3], + const meep::vec &r, double freq, geom_epsilon *geps) { + std::complex tensor[9], tensor_inv[9]; + get_chi1_tensor_disp(tensor, r, freq, geps); // invert the matrix invert_tensor(tensor_inv, tensor); @@ -3050,7 +3053,8 @@ void get_epsilon_grid(geometric_object_list gobj_list, int nx, const double *x, int ny, const double *y, int nz, const double *z, - double *grid_vals) { + std::complex *grid_vals, + double frequency) { double min_val[3], max_val[3]; for (int n = 0; n < 3; ++n) { int ndir = (n == 0) ? nx : ((n == 1) ? ny : nz); @@ -3066,10 +3070,17 @@ void get_epsilon_grid(geometric_object_list gobj_list, geom_epsilon geps(gobj_list, mlist, vol); for (int i = 0; i < nx; ++i) for (int j = 0; j < ny; ++j) - for (int k = 0; k < nz; ++k) - /* obtain the trace of the \varepsilon tensor for each + for (int k = 0; k < nz; ++k) { + /* obtain the trace of the ε tensor (dispersive or non) for each grid point in row-major order (the order used by NumPy) */ - grid_vals[k + nz*(j + ny*i)] = geps.chi1p1(meep::E_stuff, meep::vec(x[i],y[j],z[k])); + if (frequency == 0) + grid_vals[k + nz*(j + ny*i)] = geps.chi1p1(meep::E_stuff, meep::vec(x[i],y[j],z[k])); + else { + std::complex tensor[9]; + get_chi1_tensor_disp(tensor, meep::vec(x[i],y[j],z[k]), frequency, &geps); + grid_vals[k + nz*(j + ny*i)] = (tensor[0] + tensor[4] + tensor[8]) / 3.0; + } + } } } // namespace meep_geom diff --git a/src/meepgeom.hpp b/src/meepgeom.hpp index d052d5b4a..f66c33e3b 100644 --- a/src/meepgeom.hpp +++ b/src/meepgeom.hpp @@ -281,7 +281,8 @@ void get_epsilon_grid(geometric_object_list gobj_list, int nx, const double *x, int ny, const double *y, int nz, const double *z, - double *grid_vals); + std::complex *grid_vals, + double frequency = 0); void init_libctl(material_type default_mat, bool ensure_per, meep::grid_volume *gv, vector3 cell_size, vector3 cell_center, geometric_object_list *geom_list); From 1106a587a3c8079e53b924e7a50f621051763a6e Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Tue, 30 Nov 2021 08:41:05 -0800 Subject: [PATCH 079/155] unit test for `get_epsilon_grid` (#1835) * unit test for get_epsilon_grid * fix * limit test points to homogeneous regions (i.e. no material interfaces) and away from potential chunk boundaries --- python/Makefile.am | 1 + python/tests/test_get_epsilon_grid.py | 74 +++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 python/tests/test_get_epsilon_grid.py diff --git a/python/Makefile.am b/python/Makefile.am index 1f1fed41b..793eef539 100644 --- a/python/Makefile.am +++ b/python/Makefile.am @@ -65,6 +65,7 @@ TESTS = \ $(TEST_DIR)/test_gaussianbeam.py \ $(TEST_DIR)/test_geom.py \ $(TEST_DIR)/test_get_point.py \ + $(TEST_DIR)/test_get_epsilon_grid.py \ $(TEST_DIR)/test_holey_wvg_bands.py \ $(TEST_DIR)/test_holey_wvg_cavity.py \ $(KDOM_TEST) \ diff --git a/python/tests/test_get_epsilon_grid.py b/python/tests/test_get_epsilon_grid.py new file mode 100644 index 000000000..e9d24bd9c --- /dev/null +++ b/python/tests/test_get_epsilon_grid.py @@ -0,0 +1,74 @@ +import unittest +import parameterized +import numpy as np +import meep as mp +from meep.materials import SiN, Co + +class TestGetEpsilonGrid(unittest.TestCase): + + def setUp(self): + resolution = 60 + self.cell_size = mp.Vector3(1.0,1.0,0) + + matgrid_resolution = 200 + matgrid_size = mp.Vector3(1.0,1.0,mp.inf) + Nx = int(matgrid_resolution*matgrid_size.x) + 1 + Ny = int(matgrid_resolution*matgrid_size.y) + 1 + x = np.linspace(-0.5*matgrid_size.x,0.5*matgrid_size.x,Nx) + y = np.linspace(-0.5*matgrid_size.y,0.5*matgrid_size.y,Ny) + xv, yv = np.meshgrid(x,y) + rad = 0.201943 + w = 0.104283 + weights = np.logical_and(np.sqrt(np.square(xv) + np.square(yv)) > rad, + np.sqrt(np.square(xv) + np.square(yv)) < rad+w, + dtype=np.double) + + matgrid = mp.MaterialGrid(mp.Vector3(Nx,Ny), + mp.air, + mp.Medium(index=3.5), + weights=weights, + do_averaging=False, + beta=0, + eta=0.5) + + geometry = [mp.Cylinder(center=mp.Vector3(0.35,0.1), + radius=0.1, + height=mp.inf, + material=mp.Medium(index=1.5)), + mp.Block(center=mp.Vector3(-0.15,-0.2), + size=mp.Vector3(0.2,0.24,mp.inf), + material=SiN), + mp.Block(center=mp.Vector3(-0.2,0.2), + size=mp.Vector3(0.4,0.4,mp.inf), + material=matgrid), + mp.Prism(vertices=[mp.Vector3(0.05,0.45), + mp.Vector3(0.32,0.22), + mp.Vector3(0.15,0.10)], + height=0.5, + material=Co)] + + self.sim = mp.Simulation(resolution=resolution, + cell_size=self.cell_size, + geometry=geometry, + eps_averaging=False) + self.sim.init_sim() + + @parameterized.parameterized.expand([ + (mp.Vector3(0.2,0.2), 1.1), + (mp.Vector3(-0.2,0.1), 0.7), + (mp.Vector3(-0.2,-0.25), 0.55), + (mp.Vector3(0.4,0.1), 0) + ]) + def test_get_epsilon_grid(self, pt, freq): + eps_grid = self.sim.get_epsilon_grid(np.array([pt.x]), + np.array([pt.y]), + np.array([0]), + freq) + eps_pt = self.sim.get_epsilon_point(pt, freq) + print("eps:, ({},{}), {}, {}".format(pt.x,pt.y,eps_grid,eps_pt)) + self.assertAlmostEqual(np.real(eps_grid), np.real(eps_pt), places=6) + self.assertAlmostEqual(np.imag(eps_grid), np.imag(eps_pt), places=6) + + +if __name__ == '__main__': + unittest.main() From 2aa8a300e290b4f6ce8657ffa35a73c3c1131720 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Tue, 30 Nov 2021 18:05:19 -0800 Subject: [PATCH 080/155] support for single-precision floating point for fields array functions (#1833) * switch dft-related functions to using realnum from double * more fixes * more type conversions from double to realnum * adjust check tolerance of tests/integrate.cpp based on floating-point precision * more fixes * rebase from master from fix merge conflicts * slight adjustment to tolerances in unit tests and update docs * remove type check in test_adjoint_solver.py * revert return types of integration functions to double * revert return type of process_dft_component to double * cleanup --- doc/docs/Python_User_Interface.md | 7 +- python/adjoint/optimization_problem.py | 2 +- python/adjoint/utils.py | 33 ++++--- python/adjoint/wrapper.py | 4 +- python/meep.i | 40 ++++---- python/simulation.py | 14 ++- python/tests/test_adjoint_jax.py | 3 +- python/tests/test_adjoint_solver.py | 7 +- python/tests/test_cavity_arrayslice.py | 8 +- scheme/meep.i | 6 +- src/array_slice.cpp | 129 +++++++++++++------------ src/dft.cpp | 48 ++++----- src/energy_and_flux.cpp | 15 +-- src/h5fields.cpp | 13 +-- src/integrate.cpp | 20 ++-- src/integrate2.cpp | 14 +-- src/meep.hpp | 72 +++++++------- src/meep/mympi.hpp | 2 + src/meepgeom.cpp | 110 ++++++++++----------- src/meepgeom.hpp | 8 +- src/mympi.cpp | 12 +++ src/vec.cpp | 9 +- tests/array-slice-ll.cpp | 4 +- tests/dft-fields.cpp | 24 +++-- tests/integrate.cpp | 4 +- tests/pw-source-ll.cpp | 10 +- tests/ring-ll.cpp | 10 +- 27 files changed, 332 insertions(+), 296 deletions(-) diff --git a/doc/docs/Python_User_Interface.md b/doc/docs/Python_User_Interface.md index 581dd3382..811be2601 100644 --- a/doc/docs/Python_User_Interface.md +++ b/doc/docs/Python_User_Interface.md @@ -3602,7 +3602,9 @@ current simulation time. + `cmplx`: `boolean`; if `True`, return complex-valued data otherwise return real-valued data (default). -+ `arr`: optional field to pass a pre-allocated NumPy array of the correct size, ++ `arr`: optional parameter to pass a pre-allocated NumPy array of the correct size and + type (either `numpy.float32` or `numpy.float64` depending on the [floating-point precision + of the fields and materials](Build_From_Source.md#floating-point-precision-of-the-fields-and-materials-arrays)) which will be overwritten with the field/material data instead of allocating a new array. Normally, this will be the array returned from a previous call to `get_array` for a similar slice, allowing one to re-use `arr` (e.g., when @@ -3655,7 +3657,8 @@ def get_dft_array(self, dft_obj, component, num_freq):
-Returns the Fourier-transformed fields as a NumPy array. +Returns the Fourier-transformed fields as a NumPy array. The type is either `numpy.complex64` +or `numpy.complex128` depending on the [floating-point precision of the fields](Build_From_Source.md#floating-point-precision-of-the-fields-and-materials-arrays). **Parameters:** diff --git a/python/adjoint/optimization_problem.py b/python/adjoint/optimization_problem.py index 2fb45d5ca..bce235748 100644 --- a/python/adjoint/optimization_problem.py +++ b/python/adjoint/optimization_problem.py @@ -248,7 +248,7 @@ def adjoint_run(self): # Store adjoint fields for each design set of design variables self.D_a.append(utils.gather_design_region_fields(self.sim,self.design_region_monitors,self.frequencies)) - + # reset the m number if utils._check_if_cylindrical(self.sim): self.sim.m = -self.sim.m diff --git a/python/adjoint/utils.py b/python/adjoint/utils.py index 04b041fe7..38ffbd8f9 100644 --- a/python/adjoint/utils.py +++ b/python/adjoint/utils.py @@ -37,14 +37,14 @@ def __init__( def update_design_parameters(self, design_parameters): self.design_parameters.update_weights(design_parameters) - + def update_beta(self,beta): self.design_parameters.beta=beta def get_gradient(self, sim, fields_a, fields_f, frequencies, finite_difference_step): num_freqs = onp.array(frequencies).size shapes = [] - '''We have the option to linear scale the gradients up front + '''We have the option to linearly scale the gradients up front using the scalegrad parameter (leftover from MPB API). Not currently needed for any existing feature (but available for future use)''' @@ -54,16 +54,18 @@ def get_gradient(self, sim, fields_a, fields_f, frequencies, finite_difference_s returns a singleton element for the forward and adjoint fields. This only occurs when we are in 2D and only working with a particular polarization (as the other fields are never stored). For example, the - 2D in-plane polarization consists of a single scalar Ez field + 2D in-plane polarization consists of a single scalar Ez field (technically, meep doesn't store anything for these cases, but get_dft_array still returns 0). - - Our get_gradient algorithm, however, requires we pass an array of + + Our get_gradient algorithm, however, requires we pass an array of zeros with the proper shape as the design_region.''' spatial_shape = sim.get_array_slice_dimensions(component, vol=self.volume)[0] if (fields_a[component_idx][0,...].size == 1): - fields_a[component_idx] = onp.zeros(onp.insert(spatial_shape,0,num_freqs)) - fields_f[component_idx] = onp.zeros(onp.insert(spatial_shape,0,num_freqs)) + fields_a[component_idx] = onp.zeros(onp.insert(spatial_shape,0,num_freqs), + dtype=onp.float32 if mp.is_single_precision() else onp.float64) + fields_f[component_idx] = onp.zeros(onp.insert(spatial_shape,0,num_freqs), + dtype=onp.float32 if mp.is_single_precision() else onp.float64) if _check_if_cylindrical(sim): '''For some reason, get_dft_array returns the field components in a different order than the convention used @@ -78,15 +80,16 @@ def get_gradient(self, sim, fields_a, fields_f, frequencies, finite_difference_s shapes = onp.asarray(shapes).flatten(order='C') fields_a = onp.concatenate(fields_a) fields_f = onp.concatenate(fields_f) - + grad = onp.zeros((num_freqs, self.num_design_params)) # preallocate geom_list = sim.geometry f = sim.fields vol = sim._fit_volume_to_simulation(self.volume) - + # compute the gradient mp._get_gradient(grad,scalegrad,fields_a,fields_f, - sim.gv,vol.swigobj,onp.array(frequencies),sim.geps,shapes,finite_difference_step) + sim.gv,vol.swigobj,onp.array(frequencies), + sim.geps,shapes,finite_difference_step) return onp.squeeze(grad).T def _check_if_cylindrical(sim): @@ -203,7 +206,7 @@ def gather_design_region_fields( fairly awkward to inspect directly. Their primary use case is supporting gradient calculations. """ - fwd_fields = [] + design_region_fields = [] for monitor in design_region_monitors: fields_by_component = [] for component in _compute_components(simulation): @@ -212,8 +215,8 @@ def gather_design_region_fields( fields = simulation.get_dft_array(monitor, component, freq_idx) fields_by_freq.append(_make_at_least_nd(fields)) fields_by_component.append(onp.stack(fields_by_freq)) - fwd_fields.append(fields_by_component) - return fwd_fields + design_region_fields.append(fields_by_component) + return design_region_fields def validate_and_update_design( @@ -258,7 +261,7 @@ def create_adjoint_sources( monitors: Iterable[ObjectiveQuantity], monitor_values_grad: onp.ndarray) -> List[mp.Source]: monitor_values_grad = onp.asarray(monitor_values_grad, - dtype=onp.complex128) + dtype=onp.complex64 if mp.is_single_precision() else onp.complex128) if not onp.any(monitor_values_grad): raise RuntimeError( 'The gradient of all monitor values is zero, which ' @@ -273,7 +276,7 @@ def create_adjoint_sources( for monitor_idx, monitor in enumerate(monitors): # `dj` for each monitor will have a shape of (num frequencies,) dj = onp.asarray(monitor_values_grad[monitor_idx], - dtype=onp.complex128) + dtype=onp.complex64 if mp.is_single_precision() else onp.complex128) if onp.any(dj): adjoint_sources += monitor.place_adjoint_source(dj) assert adjoint_sources diff --git a/python/adjoint/wrapper.py b/python/adjoint/wrapper.py index be793573f..3fad98c10 100644 --- a/python/adjoint/wrapper.py +++ b/python/adjoint/wrapper.py @@ -229,7 +229,9 @@ def _simulate_fwd(design_variables): def _simulate_rev(res, monitor_values_grad): """Runs adjoint simulation, returning VJP of design wrt monitor values.""" fwd_fields = jax.tree_map( - lambda x: onp.asarray(x, dtype=onp.complex128), res[0]) + lambda x: onp.asarray(x, + dtype=onp.complex64 if mp.is_single_precision() else onp.complex128), + res[0]) design_variable_shapes = res[1] adj_fields = self._run_adjoint_simulation(monitor_values_grad) vjps = self._calculate_vjps(fwd_fields, adj_fields, diff --git a/python/meep.i b/python/meep.i index f1dc8012b..bb7f1c80c 100644 --- a/python/meep.i +++ b/python/meep.i @@ -150,7 +150,7 @@ static std::complex py_amp_func_wrap(const meep::vec &v) { return ret; } -static std::complex py_field_func_wrap(const std::complex *fields, +static std::complex py_field_func_wrap(const std::complex *fields, const meep::vec &loc, void *data_) { SWIG_PYTHON_THREAD_SCOPED_BLOCK; @@ -441,25 +441,25 @@ PyObject *_get_dft_array(meep::fields *f, dft_type dft, meep::component c, int n // Return value: New reference int rank; size_t dims[3]; - std::complex *dft_arr = f->get_dft_array(dft, c, num_freq, &rank, dims); + std::complex *dft_arr = f->get_dft_array(dft, c, num_freq, &rank, dims); - if (dft_arr==NULL){ // this can happen e.g. if component c vanishes by symmetry - std::complex d[1] = {std::complex(0,0)}; - return PyArray_SimpleNewFromData(0, 0, NPY_CDOUBLE, d); + if (dft_arr == NULL) { // this can happen e.g. if component c vanishes by symmetry + std::complex d[1] = {std::complex(0,0)}; + return PyArray_SimpleNewFromData(0, 0, sizeof(meep::realnum) == sizeof(float) ? NPY_CFLOAT : NPY_CDOUBLE, d); } if (rank == 0) // singleton results - return PyArray_SimpleNewFromData(0, 0, NPY_CDOUBLE, dft_arr); + return PyArray_SimpleNewFromData(0, 0, sizeof(meep::realnum) == sizeof(float) ? NPY_CFLOAT : NPY_CDOUBLE, dft_arr); size_t length = 1; npy_intp *arr_dims = new npy_intp[rank]; for (int i = 0; i < rank; ++i) { - arr_dims[i] = dims[i]; // implicit size_t -> int cast, presumed safe for individual array dimensions - length *= dims[i]; + arr_dims[i] = dims[i]; // implicit size_t -> int cast, presumed safe for individual array dimensions + length *= dims[i]; } - PyObject *py_arr = PyArray_SimpleNew(rank, arr_dims, NPY_CDOUBLE); - memcpy(PyArray_DATA((PyArrayObject*)py_arr), dft_arr, sizeof(std::complex) * length); + PyObject *py_arr = PyArray_SimpleNew(rank, arr_dims, sizeof(meep::realnum) == sizeof(float) ? NPY_CFLOAT : NPY_CDOUBLE); + memcpy(PyArray_DATA((PyArrayObject*)py_arr), dft_arr, sizeof(std::complex) * length); delete[] dft_arr; if (arr_dims) delete[] arr_dims; @@ -844,8 +844,8 @@ meep::volume_list *make_volume_list(const meep::volume &v, int c, %inline %{ void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObject *fields_f, - meep::grid_volume *grid_volume, meep::volume *where, PyObject *frequencies, - meep_geom::geom_epsilon *geps, PyObject *fields_shapes, double fd_step) { + meep::grid_volume *grid_volume, meep::volume *where, PyObject *frequencies, + meep_geom::geom_epsilon *geps, PyObject *fields_shapes, double fd_step) { // clean the gradient array PyArrayObject *pao_grad = (PyArrayObject *)grad; if (!PyArray_Check(pao_grad)) meep::abort("grad parameter must be numpy array."); @@ -859,14 +859,14 @@ void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObjec if (!PyArray_Check(pao_fields_a)) meep::abort("adjoint fields parameter must be numpy array."); if (!PyArray_ISCARRAY(pao_fields_a)) meep::abort("Numpy adjoint fields array must be C-style contiguous."); if (PyArray_NDIM(pao_fields_a) !=1) {meep::abort("Numpy adjoint fields array must have 1 dimension.");} - std::complex *fields_a_c = (std::complex *)PyArray_DATA(pao_fields_a); + std::complex *fields_a_c = (std::complex *)PyArray_DATA(pao_fields_a); // clean the forward fields array PyArrayObject *pao_fields_f = (PyArrayObject *)fields_f; if (!PyArray_Check(pao_fields_f)) meep::abort("forward fields parameter must be numpy array."); if (!PyArray_ISCARRAY(pao_fields_f)) meep::abort("Numpy forward fields array must be C-style contiguous."); if (PyArray_NDIM(pao_fields_f) !=1) {meep::abort("Numpy forward fields array must have 1 dimension.");} - std::complex *fields_f_c = (std::complex *)PyArray_DATA(pao_fields_f); + std::complex *fields_f_c = (std::complex *)PyArray_DATA(pao_fields_f); // clean shapes array PyArrayObject *pao_fields_shapes = (PyArrayObject *)fields_shapes; @@ -1025,20 +1025,20 @@ void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObjec $1 = (size_t *)array_data($input); } -%typecheck(SWIG_TYPECHECK_POINTER, fragment="NumPy_Fragments") double* slice { +%typecheck(SWIG_TYPECHECK_POINTER, fragment="NumPy_Fragments") meep::realnum* slice { $1 = is_array($input); } -%typemap(in, fragment="NumPy_Macros") double* slice { - $1 = (double *)array_data($input); +%typemap(in, fragment="NumPy_Macros") meep::realnum* slice { + $1 = (meep::realnum *)array_data($input); } -%typecheck(SWIG_TYPECHECK_POINTER, fragment="NumPy_Fragments") std::complex* slice { +%typecheck(SWIG_TYPECHECK_POINTER, fragment="NumPy_Fragments") std::complex* slice { $1 = is_array($input); } -%typemap(in) std::complex* slice { - $1 = (std::complex *)array_data($input); +%typemap(in) std::complex* slice { + $1 = (std::complex *)array_data($input); } %typecheck(SWIG_TYPECHECK_POINTER) meep::component { diff --git a/python/simulation.py b/python/simulation.py index 5fbf66155..b99dccf3b 100644 --- a/python/simulation.py +++ b/python/simulation.py @@ -3337,7 +3337,9 @@ def get_array(self, component=None, vol=None, center=None, size=None, cmplx=None + `cmplx`: `boolean`; if `True`, return complex-valued data otherwise return real-valued data (default). - + `arr`: optional field to pass a pre-allocated NumPy array of the correct size, + + `arr`: optional parameter to pass a pre-allocated NumPy array of the correct size and + type (either `numpy.float32` or `numpy.float64` depending on the [floating-point precision + of the fields and materials](Build_From_Source.md#floating-point-precision-of-the-fields-and-materials-arrays)) which will be overwritten with the field/material data instead of allocating a new array. Normally, this will be the array returned from a previous call to `get_array` for a similar slice, allowing one to re-use `arr` (e.g., when @@ -3407,7 +3409,10 @@ def get_array(self, component=None, vol=None, center=None, size=None, cmplx=None arr = np.require(arr, requirements=['C', 'W']) else: - arr = np.zeros(dims, dtype=np.complex128 if cmplx else np.float64) + if mp.is_single_precision(): + arr = np.zeros(dims, dtype=np.complex64 if cmplx else np.float32) + else: + arr = np.zeros(dims, dtype=np.complex128 if cmplx else np.float64) if np.iscomplexobj(arr): self.fields.get_complex_array_slice(v, component, arr, frequency, snap) @@ -3418,7 +3423,8 @@ def get_array(self, component=None, vol=None, center=None, size=None, cmplx=None def get_dft_array(self, dft_obj, component, num_freq): """ - Returns the Fourier-transformed fields as a NumPy array. + Returns the Fourier-transformed fields as a NumPy array. The type is either `numpy.complex64` + or `numpy.complex128` depending on the [floating-point precision of the fields](Build_From_Source.md#floating-point-precision-of-the-fields-and-materials-arrays). **Parameters:** @@ -3464,7 +3470,7 @@ def get_source(self, component, vol=None, center=None, size=None): dim_sizes = np.zeros(3, dtype=np.uintp) mp._get_array_slice_dimensions(self.fields, v, dim_sizes, True, False) dims = [s for s in dim_sizes if s != 0] - arr = np.zeros(dims, dtype=np.complex128) + arr = np.zeros(dims, dtype=np.complex64 if mp.is_single_precision() else np.complex128) self.fields.get_source_slice(v, component, arr) return arr diff --git a/python/tests/test_adjoint_jax.py b/python/tests/test_adjoint_jax.py index 768d7ac82..17994a8fa 100644 --- a/python/tests/test_adjoint_jax.py +++ b/python/tests/test_adjoint_jax.py @@ -175,7 +175,8 @@ def test_design_region_monitor_helpers(self): for value in design_region_fields[0]: self.assertIsInstance(value, onp.ndarray) self.assertEqual(value.ndim, 4) # dims: freq, x, y, pad - self.assertEqual(value.dtype, onp.complex128) + self.assertEqual(value.dtype, + onp.complex64 if mp.is_single_precision() else onp.complex128) class WrapperTest(ApproxComparisonTestCase): diff --git a/python/tests/test_adjoint_solver.py b/python/tests/test_adjoint_solver.py index dae6e80f9..1f4efa360 100644 --- a/python/tests/test_adjoint_solver.py +++ b/python/tests/test_adjoint_solver.py @@ -15,7 +15,8 @@ resolution = 30 silicon = mp.Medium(epsilon=12) -sapphire = mp.Medium(epsilon_diag=(10.225,10.225,9.95),epsilon_offdiag=(-0.825,-0.55*np.sqrt(3/2),0.55*np.sqrt(3/2))) +sapphire = mp.Medium(epsilon_diag=(10.225,10.225,9.95), + epsilon_offdiag=(-0.825,-0.55*np.sqrt(3/2),0.55*np.sqrt(3/2))) sxy = 5.0 cell_size = mp.Vector3(sxy,sxy,0) @@ -469,7 +470,7 @@ def test_complex_fields(self): ## compare objective results print("Ez2 -- adjoint solver: {}, traditional simulation: {}".format(adjsol_obj,Ez2_unperturbed)) - self.assertClose(adjsol_obj,Ez2_unperturbed,epsilon=1e-8) + self.assertClose(adjsol_obj,Ez2_unperturbed,epsilon=1e-6) ## compute perturbed |Ez|^2 Ez2_perturbed = forward_simulation_complex_fields(p+dp, frequencies) @@ -533,7 +534,7 @@ def test_offdiagonal(self): adj_scale = (dp[None,:]@adjsol_grad).flatten() fd_grad = S12_perturbed-S12_unperturbed print("Directional derivative -- adjoint solver: {}, FD: {}".format(adj_scale,fd_grad)) - tol = 0.04 + tol = 0.05 if mp.is_single_precision() else 0.04 self.assertClose(adj_scale,fd_grad,epsilon=tol) if __name__ == '__main__': unittest.main() diff --git a/python/tests/test_cavity_arrayslice.py b/python/tests/test_cavity_arrayslice.py index bbedbebe6..42f758b6f 100644 --- a/python/tests/test_cavity_arrayslice.py +++ b/python/tests/test_cavity_arrayslice.py @@ -70,7 +70,7 @@ def test_2d_slice(self): def test_1d_slice_user_array(self): self.sim.run(until_after_sources=0) - arr = np.zeros(126, dtype=np.float64) + arr = np.zeros(126, dtype=np.float32 if mp.is_single_precision() else np.float64) vol = mp.Volume(center=self.center_1d, size=self.size_1d) self.sim.get_array(mp.Hz, vol, arr=arr) tol = 1e-5 if mp.is_single_precision() else 1e-8 @@ -78,7 +78,7 @@ def test_1d_slice_user_array(self): def test_2d_slice_user_array(self): self.sim.run(until_after_sources=0) - arr = np.zeros((126, 38), dtype=np.float64) + arr = np.zeros((126, 38), dtype=np.float32 if mp.is_single_precision() else np.float64) vol = mp.Volume(center=self.center_2d, size=self.size_2d) self.sim.get_array(mp.Hz, vol, arr=arr) tol = 1e-5 if mp.is_single_precision() else 1e-8 @@ -106,14 +106,14 @@ def test_1d_complex_slice(self): self.sim.run(until_after_sources=0) vol = mp.Volume(center=self.center_1d, size=self.size_1d) hl_slice1d = self.sim.get_array(mp.Hz, vol, cmplx=True) - self.assertTrue(hl_slice1d.dtype == np.complex128) + self.assertTrue(hl_slice1d.dtype == np.complex64 if mp.is_single_precision() else np.complex128) self.assertTrue(hl_slice1d.shape[0] == 126) def test_2d_complex_slice(self): self.sim.run(until_after_sources=0) vol = mp.Volume(center=self.center_2d, size=self.size_2d) hl_slice2d = self.sim.get_array(mp.Hz, vol, cmplx=True) - self.assertTrue(hl_slice2d.dtype == np.complex128) + self.assertTrue(hl_slice2d.dtype == np.complex64 if mp.is_single_precision() else np.complex128) self.assertTrue(hl_slice2d.shape[0] == 126 and hl_slice2d.shape[1] == 38) diff --git a/scheme/meep.i b/scheme/meep.i index b88885e5d..9c8929ba8 100644 --- a/scheme/meep.i +++ b/scheme/meep.i @@ -33,9 +33,9 @@ static inline std::complex my_complex_func2(double t, void *f) { } typedef struct { SCM func; int nf; } my_field_func_data; -static inline std::complex my_field_func(const std::complex *fields, - const meep::vec &loc, - void *data_) { +static inline std::complex my_field_func(const std::complex *fields, + const meep::vec &loc, + void *data_) { my_field_func_data *data = (my_field_func_data *) data_; int num_items = data->nf; cnumber *items = new cnumber[num_items]; diff --git a/src/array_slice.cpp b/src/array_slice.cpp index b9905c16f..cb7d7aff3 100644 --- a/src/array_slice.cpp +++ b/src/array_slice.cpp @@ -43,17 +43,17 @@ constexpr size_t ARRAY_TO_ALL_BUFSIZE = 1 << 16; // Use (64k * 8 bytes) of buffe /* repeatedly call sum_to_all to consolidate all entries of */ /* an array on all cores. */ /* array: in/out ptr to the data */ -/* array_size: data size in multiples of sizeof(double) */ +/* array_size: data size in multiples of sizeof(realnum) */ /***************************************************************/ -double *array_to_all(double *array, size_t array_size) { +realnum *array_to_all(realnum *array, size_t array_size) { #ifdef HAVE_MPI - double *buffer = new double[ARRAY_TO_ALL_BUFSIZE]; + realnum *buffer = new realnum[ARRAY_TO_ALL_BUFSIZE]; ptrdiff_t offset = 0; size_t remaining = array_size; while (remaining != 0) { size_t xfer_size = (remaining > ARRAY_TO_ALL_BUFSIZE ? ARRAY_TO_ALL_BUFSIZE : remaining); sum_to_all(array + offset, buffer, xfer_size); - memcpy(array + offset, buffer, xfer_size * sizeof(double)); + memcpy(array + offset, buffer, xfer_size * sizeof(realnum)); remaining -= xfer_size; offset += xfer_size; } @@ -64,14 +64,22 @@ double *array_to_all(double *array, size_t array_size) { return array; } -complex *array_to_all(complex *array, size_t array_size) { - return (complex *)array_to_all((double *)array, 2 * array_size); +complex *array_to_all(complex *array, size_t array_size) { + return (complex *)array_to_all((realnum *)array, 2 * array_size); } } // namespace /***************************************************************************/ +std::complex cdouble(std::complex z) { + return std::complex(real(z), imag(z)); +} + +std::complex cdouble(std::complex z) { + return z; +} + typedef struct { // information related to the volume covered by the @@ -101,7 +109,7 @@ typedef struct { // temporary internal storage buffers component *cS; complex *ph; - complex *fields; + complex *fields; ptrdiff_t *offsets; double frequency; @@ -119,16 +127,16 @@ typedef struct { } array_slice_data; /* passthrough field function equivalent to component_fun in h5fields.cpp */ -static complex default_field_func(const complex *fields, const vec &loc, void *data_) { +static complex default_field_func(const complex *fields, const vec &loc, void *data_) { (void)loc; // unused (void)data_; // unused - return fields[0]; + return cdouble(fields[0]); } -static double default_field_rfunc(const complex *fields, const vec &loc, void *data_) { +static double default_field_rfunc(const complex *fields, const vec &loc, void *data_) { (void)loc; // unused (void)data_; // unused - return real(fields[0]); + return real(cdouble(fields[0])); } /***************************************************************/ @@ -174,7 +182,7 @@ static void get_array_slice_dimensions_chunkloop(fields_chunk *fc, int ichnk, co typedef struct { component source_component; ivec slice_imin, slice_imax; - complex *slice; + complex *slice; } source_slice_data; bool in_range(int imin, int i, int imax) { return (imin <= i && i <= imax); } @@ -310,18 +318,19 @@ static void get_array_slice_chunkloop(fields_chunk *fc, int ichnk, component cgr // Otherwise proceed to compute the function of field components to be // // tabulated on the slice, exactly as in fields::integrate. // //-----------------------------------------------------------------------// - double *slice = 0; - complex *zslice = 0; + realnum *slice = 0; + complex *zslice = 0; bool complex_data = (data->rfun == 0); if (complex_data) - zslice = (complex *)data->vslice; + zslice = (complex *)data->vslice; else - slice = (double *)data->vslice; + slice = (realnum *)data->vslice; ptrdiff_t *off = data->offsets; component *cS = data->cS; double frequency = data->frequency; - complex *fields = data->fields, *ph = data->ph; + complex *fields = data->fields; + complex *ph = data->ph; const component *iecs = data->inveps_cs; const direction *ieds = data->inveps_ds; ptrdiff_t ieos[6]; @@ -515,8 +524,8 @@ bool increment(size_t *n, size_t *nMax, int rank) { } // data_size = 1,2 for real,complex-valued array -double *collapse_array(double *array, int *rank, size_t dims[3], direction dirs[3], volume where, - int data_size = 1) { +realnum *collapse_array(realnum *array, int *rank, size_t dims[3], direction dirs[3], volume where, + int data_size = 1) { /*--------------------------------------------------------------*/ /*- detect empty dimensions and compute rank and strides for */ @@ -560,9 +569,9 @@ double *collapse_array(double *array, int *rank, size_t dims[3], direction dirs[ /*--------------------------------------------------------------*/ size_t reduced_grid_size = reduced_dims[0] * (reduced_rank == 2 ? reduced_dims[1] : 1); size_t reduced_array_size = data_size * reduced_grid_size; - double *reduced_array = new double[reduced_array_size]; + realnum *reduced_array = new realnum[reduced_array_size]; if (!reduced_array) meep::abort("%s:%i: out of memory (%zu)", __FILE__, __LINE__, reduced_array_size); - memset(reduced_array, 0, reduced_array_size * sizeof(double)); + memset(reduced_array, 0, reduced_array_size * sizeof(realnum)); size_t n[3] = {0, 0, 0}; do { @@ -581,9 +590,9 @@ double *collapse_array(double *array, int *rank, size_t dims[3], direction dirs[ return reduced_array; } -complex *collapse_array(complex *array, int *rank, size_t dims[3], direction dirs[3], - volume where) { - return (complex *)collapse_array((double *)array, rank, dims, dirs, where, 2); +complex *collapse_array(complex *array, int *rank, size_t dims[3], direction dirs[3], + volume where) { + return (complex *)collapse_array((realnum *)array, rank, dims, dirs, where, 2); } /**********************************************************************/ @@ -607,7 +616,7 @@ void *fields::do_get_array_slice(const volume &where, std::vector com int elem_size = complex_data ? 2 : 1; void *vslice_uncollapsed; - vslice_uncollapsed = memset(new double[slice_size * elem_size], 0, slice_size * elem_size * sizeof(double)); + vslice_uncollapsed = memset(new realnum[slice_size * elem_size], 0, slice_size * elem_size * sizeof(realnum)); data.vslice = vslice_uncollapsed; data.snap = snap; @@ -619,7 +628,7 @@ void *fields::do_get_array_slice(const volume &where, std::vector com int num_components = components.size(); data.cS = new component[num_components]; data.ph = new complex[num_components]; - data.fields = new complex[num_components]; + data.fields = new complex[num_components]; data.offsets = new ptrdiff_t[2 * num_components]; memset(data.offsets, 0, 2 * num_components * sizeof(ptrdiff_t)); data.empty_dim[0] = data.empty_dim[1] = data.empty_dim[2] = data.empty_dim[3] = @@ -659,19 +668,19 @@ void *fields::do_get_array_slice(const volume &where, std::vector com loop_in_chunks(get_array_slice_chunkloop, (void *)&data, where, Centered, true, snap); if (!snap) { - double *slice = collapse_array((double *)vslice_uncollapsed, &rank, dims, dirs, where, elem_size); + realnum *slice = collapse_array((realnum *)vslice_uncollapsed, &rank, dims, dirs, where, elem_size); rank = get_array_slice_dimensions(where, dims, dirs, true, false, 0, &data); slice_size = data.slice_size; - vslice_uncollapsed = (double*) slice; + vslice_uncollapsed = (realnum*) slice; } if (vslice) { - memcpy(vslice, vslice_uncollapsed, slice_size * elem_size * sizeof(double)); - delete[] (double*) vslice_uncollapsed; + memcpy(vslice, vslice_uncollapsed, slice_size * elem_size * sizeof(realnum)); + delete[] (realnum*) vslice_uncollapsed; } else vslice = vslice_uncollapsed; - array_to_all((double *)vslice, elem_size * slice_size); + array_to_all((realnum *)vslice, elem_size * slice_size); delete[] data.offsets; delete[] data.fields; @@ -685,48 +694,48 @@ void *fields::do_get_array_slice(const volume &where, std::vector com /***************************************************************/ /* entry points to get_array_slice */ /***************************************************************/ -double *fields::get_array_slice(const volume &where, std::vector components, - field_rfunction rfun, void *fun_data, double *slice, - double frequency, bool snap) { - return (double *)do_get_array_slice(where, components, 0, rfun, fun_data, (void *)slice, - frequency, snap); +realnum *fields::get_array_slice(const volume &where, std::vector components, + field_rfunction rfun, void *fun_data, realnum *slice, + double frequency, bool snap) { + return (realnum *)do_get_array_slice(where, components, 0, rfun, fun_data, (void *)slice, + frequency, snap); } -complex *fields::get_complex_array_slice(const volume &where, std::vector components, - field_function fun, void *fun_data, complex *slice, - double frequency, bool snap) { - return (complex *)do_get_array_slice(where, components, fun, 0, fun_data, (void *)slice, - frequency, snap); +complex *fields::get_complex_array_slice(const volume &where, std::vector components, + field_function fun, void *fun_data, complex *slice, + double frequency, bool snap) { + return (complex *)do_get_array_slice(where, components, fun, 0, fun_data, (void *)slice, + frequency, snap); } -double *fields::get_array_slice(const volume &where, component c, double *slice, double frequency, - bool snap) { +realnum *fields::get_array_slice(const volume &where, component c, realnum *slice, double frequency, + bool snap) { std::vector components(1); components[0] = c; - return (double *)do_get_array_slice(where, components, 0, default_field_rfunc, 0, (void *)slice, - frequency, snap); + return (realnum *)do_get_array_slice(where, components, 0, default_field_rfunc, 0, (void *)slice, + frequency, snap); } -double *fields::get_array_slice(const volume &where, derived_component c, double *slice, - double frequency, bool snap) { +realnum *fields::get_array_slice(const volume &where, derived_component c, realnum *slice, + double frequency, bool snap) { int nfields; component carray[12]; field_rfunction rfun = derived_component_func(c, gv, nfields, carray); std::vector cs(carray, carray + nfields); - return (double *)do_get_array_slice(where, cs, 0, rfun, &nfields, (void *)slice, - frequency, snap); + return (realnum *)do_get_array_slice(where, cs, 0, rfun, &nfields, (void *)slice, + frequency, snap); } -complex *fields::get_complex_array_slice(const volume &where, component c, complex *slice, - double frequency, bool snap) { +complex *fields::get_complex_array_slice(const volume &where, component c, complex *slice, + double frequency, bool snap) { std::vector components(1); components[0] = c; - return (complex *)do_get_array_slice(where, components, default_field_func, 0, 0, (void *)slice, - frequency, snap); + return (complex *)do_get_array_slice(where, components, default_field_func, 0, 0, (void *)slice, + frequency, snap); } -complex *fields::get_source_slice(const volume &where, component source_slice_component, - complex *slice) { +complex *fields::get_source_slice(const volume &where, component source_slice_component, + complex *slice) { size_t dims[3]; direction dirs[3]; vec min_max_loc[2]; @@ -737,18 +746,18 @@ complex *fields::get_source_slice(const volume &where, component source_ data.source_component = source_slice_component; data.slice_imin = gv.round_vec(min_max_loc[0]); data.slice_imax = gv.round_vec(min_max_loc[1]); - data.slice = new complex[slice_size]; + data.slice = new complex[slice_size]; if (!data.slice) meep::abort("%s:%i: out of memory (%zu)", __FILE__, __LINE__, slice_size); loop_in_chunks(get_source_slice_chunkloop, (void *)&data, where, Centered, true, false); - complex *slice_collapsed = collapse_array(data.slice, &rank, dims, dirs, where); + complex *slice_collapsed = collapse_array(data.slice, &rank, dims, dirs, where); rank = get_array_slice_dimensions(where, dims, dirs, true, false); slice_size = dims[0] * (rank >= 2 ? dims[1] : 1) * (rank == 3 ? dims[2] : 1); if (slice) { - memcpy(slice, slice_collapsed, 2 * slice_size * sizeof(double)); - delete[] (complex*) slice_collapsed; + memcpy(slice, slice_collapsed, 2 * slice_size * sizeof(realnum)); + delete[] (complex*) slice_collapsed; } else slice = slice_collapsed; @@ -769,7 +778,7 @@ std::vector fields::get_array_metadata(const volume &where) { vec min_max_loc[2]; // extremal points in subgrid get_array_slice_dimensions(where, dims, dirs, true, false, min_max_loc); - double *weights = get_array_slice(where, NO_COMPONENT); + realnum *weights = get_array_slice(where, NO_COMPONENT); /* get length and endpoints of x,y,z tics arrays */ size_t nxyz[3] = {1, 1, 1}; diff --git a/src/dft.cpp b/src/dft.cpp index cb6cd13ad..f97bbe730 100644 --- a/src/dft.cpp +++ b/src/dft.cpp @@ -860,8 +860,8 @@ dft_fields fields::add_dft_fields(component *components, int num_components, con /* chunk-level processing for fields::process_dft_component. */ /***************************************************************/ complex dft_chunk::process_dft_component(int rank, direction *ds, ivec min_corner, ivec max_corner, - int num_freq, h5file *file, double *buffer, int reim, - complex *field_array, void *mode1_data, void *mode2_data, + int num_freq, h5file *file, realnum *buffer, int reim, + complex *field_array, void *mode1_data, void *mode2_data, int ic_conjugate, bool retain_interp_weights, fields *parent) { @@ -1026,7 +1026,7 @@ complex dft_chunk::process_dft_component(int rank, direction *ds, ivec m /* are processed. */ /***************************************************************/ complex fields::process_dft_component(dft_chunk **chunklists, int num_chunklists, int num_freq, - component c, const char *HDF5FileName, complex **pfield_array, + component c, const char *HDF5FileName, complex **pfield_array, int *array_rank, size_t *array_dims, direction *array_dirs, void *mode1_data, void *mode2_data, component c_conjugate, bool *first_component, bool retain_interp_weights) { @@ -1099,15 +1099,15 @@ complex fields::process_dft_component(dft_chunk **chunklists, int num_ch /* buffer for process-local contributions to HDF5 output files,*/ /* like h5_output_data::buf in h5fields.cpp */ /***************************************************************/ - double *buffer = 0; - complex *field_array = 0; + realnum *buffer = 0; + complex *field_array = 0; int reim_max = 0; if (HDF5FileName) { - buffer = new double[bufsz]; + buffer = new realnum[bufsz]; reim_max = 1; } else if (pfield_array) - *pfield_array = field_array = (array_size ? new complex[array_size] : 0); + *pfield_array = field_array = (array_size ? new complex[array_size] : 0); complex overlap = 0.0; for (int reim = 0; reim <= reim_max; reim++) { @@ -1118,7 +1118,7 @@ complex fields::process_dft_component(dft_chunk **chunklists, int num_ch char dataname[100]; snprintf(dataname, 100, "%s_%i.%c", component_name(c), num_freq, reim ? 'i' : 'r'); file->create_or_extend_data(dataname, rank, dims, false /* append_data */, - false /* single_precision */); + sizeof(realnum) == sizeof(float) /* single_precision */); } for (int ncl = 0; ncl < num_chunklists; ncl++) @@ -1139,7 +1139,7 @@ complex fields::process_dft_component(dft_chunk **chunklists, int num_ch /* on all cores */ /***************************************************************/ #define BUFSIZE 1 << 20 // use 1M element (16 MB) buffer - complex *buf = new complex[BUFSIZE]; + complex *buf = new complex[BUFSIZE]; ptrdiff_t offset = 0; size_t remaining = array_size; while (remaining != 0) { @@ -1147,7 +1147,7 @@ complex fields::process_dft_component(dft_chunk **chunklists, int num_ch am_now_working_on(MpiAllTime); sum_to_all(field_array + offset, buf, size); finished_working(); - memcpy(field_array + offset, buf, size * sizeof(complex)); + memcpy(field_array + offset, buf, size * sizeof(complex)); remaining -= size; offset += size; } @@ -1169,46 +1169,46 @@ complex fields::process_dft_component(dft_chunk **chunklists, int num_ch /***************************************************************/ /* routines for fetching arrays of dft fields */ /***************************************************************/ -complex *collapse_array(complex *array, int *rank, size_t dims[3], direction dirs[3], volume where); +complex *collapse_array(complex *array, int *rank, size_t dims[3], direction dirs[3], volume where); -complex *fields::get_dft_array(dft_flux flux, component c, int num_freq, int *rank, - size_t dims[3]) { +complex *fields::get_dft_array(dft_flux flux, component c, int num_freq, int *rank, + size_t dims[3]) { dft_chunk *chunklists[2]; chunklists[0] = flux.E; chunklists[1] = flux.H; - complex *array; + complex *array; direction dirs[3]; process_dft_component(chunklists, 2, num_freq, c, 0, &array, rank, dims, dirs); return collapse_array(array, rank, dims, dirs, flux.where); } -complex *fields::get_dft_array(dft_force force, component c, int num_freq, int *rank, - size_t dims[3]) { +complex *fields::get_dft_array(dft_force force, component c, int num_freq, int *rank, + size_t dims[3]) { dft_chunk *chunklists[3]; chunklists[0] = force.offdiag1; chunklists[1] = force.offdiag2; chunklists[2] = force.diag; - complex *array; + complex *array; direction dirs[3]; process_dft_component(chunklists, 3, num_freq, c, 0, &array, rank, dims, dirs); return collapse_array(array, rank, dims, dirs, force.where); } -complex *fields::get_dft_array(dft_near2far n2f, component c, int num_freq, int *rank, - size_t dims[3]) { +complex *fields::get_dft_array(dft_near2far n2f, component c, int num_freq, int *rank, + size_t dims[3]) { dft_chunk *chunklists[1]; chunklists[0] = n2f.F; - complex *array; + complex *array; direction dirs[3]; process_dft_component(chunklists, 1, num_freq, c, 0, &array, rank, dims, dirs); return collapse_array(array, rank, dims, dirs, n2f.where); } -complex *fields::get_dft_array(dft_fields fdft, component c, int num_freq, int *rank, - size_t dims[3]) { +complex *fields::get_dft_array(dft_fields fdft, component c, int num_freq, int *rank, + size_t dims[3]) { dft_chunk *chunklists[1]; chunklists[0] = fdft.chunks; - complex *array; + complex *array; direction dirs[3]; process_dft_component(chunklists, 1, num_freq, c, 0, &array, rank, dims, dirs); return collapse_array(array, rank, dims, dirs, fdft.where); @@ -1255,7 +1255,7 @@ void fields::output_dft_components(dft_chunk **chunklists, int num_chunklists, v 0, Ex, &first_component); } else { - complex *array = 0; + complex *array = 0; int rank; size_t dims[3]; direction dirs[3]; diff --git a/src/energy_and_flux.cpp b/src/energy_and_flux.cpp index 4514e8553..889785d09 100644 --- a/src/energy_and_flux.cpp +++ b/src/energy_and_flux.cpp @@ -58,10 +58,10 @@ double fields::field_energy_in_box(const volume &where) { return electric_energy_in_box(where) + cur_step_magnetic_energy; } -static complex dot_integrand(const complex *fields, const vec &loc, void *data_) { +static complex dot_integrand(const complex *fields, const vec &loc, void *data_) { (void)loc; (void)data_; // unused; - return real(conj(fields[0]) * fields[1]); + return real(conj(cdouble(fields[0])) * cdouble(fields[1])); } double fields::field_energy_in_box(component c, const volume &where) { @@ -249,12 +249,13 @@ flux_vol *fields::add_flux_plane(const vec &p1, const vec &p2) { is more expensive and requires us to know the boundary orientation, and does not seem worth the trouble at this point. */ -static complex dot3_max_integrand(const complex *fields, const vec &loc, +static complex dot3_max_integrand(const complex *fields, const vec &loc, void *data_) { (void)loc; (void)data_; // unused; - return (real(conj(fields[0]) * fields[3]) + real(conj(fields[1]) * fields[4]) + - real(conj(fields[2]) * fields[5])); + return (real(conj(cdouble(fields[0])) * cdouble(fields[3])) + + real(conj(cdouble(fields[1])) * cdouble(fields[4])) + + real(conj(cdouble(fields[2])) * cdouble(fields[5]))); } double fields::electric_energy_max_in_box(const volume &where) { @@ -294,10 +295,10 @@ double fields::modal_volume_in_box(const volume &where) { typedef double (*fx_func)(const vec &); -static complex dot_fx_integrand(const complex *fields, const vec &loc, +static complex dot_fx_integrand(const complex *fields, const vec &loc, void *data_) { fx_func fx = (fx_func)data_; - return (real(conj(fields[0]) * fields[1]) * fx(loc)); + return (real(conj(cdouble(fields[0])) * cdouble(fields[1])) * fx(loc)); } /* computes integral of f(x) * |E|^2 / integral epsilon*|E|^2 */ diff --git a/src/h5fields.cpp b/src/h5fields.cpp index 672b273d1..d7840f89d 100644 --- a/src/h5fields.cpp +++ b/src/h5fields.cpp @@ -48,7 +48,7 @@ typedef struct { const component *components; component *cS; complex *ph; - complex *fields; + complex *fields; ptrdiff_t *offsets; double frequency; int ninveps; @@ -143,7 +143,8 @@ static void h5_output_chunkloop(fields_chunk *fc, int ichnk, component cgrid, iv ptrdiff_t *off = data->offsets; component *cS = data->cS; - complex *fields = data->fields, *ph = data->ph; + complex *fields = data->fields; + complex *ph = data->ph; double frequency = data->frequency; const component *iecs = data->inveps_cs; const direction *ieds = data->inveps_ds; @@ -267,7 +268,7 @@ void fields::output_hdf5(h5file *file, const char *dataname, int num_fields, data.components = components; data.cS = new component[num_fields]; data.ph = new complex[num_fields]; - data.fields = new complex[num_fields]; + data.fields = new complex[num_fields]; data.fun = fun; data.fun_data_ = fun_data_; @@ -351,7 +352,7 @@ typedef struct { void *fun_data_; } rintegrand_data; -static complex rintegrand_fun(const complex *fields, const vec &loc, void *data_) { +static complex rintegrand_fun(const complex *fields, const vec &loc, void *data_) { rintegrand_data *data = (rintegrand_data *)data_; return data->fun(fields, loc, data->fun_data_); } @@ -374,10 +375,10 @@ void fields::output_hdf5(const char *dataname, int num_fields, const component * /***************************************************************************/ -static complex component_fun(const complex *fields, const vec &loc, void *data_) { +static complex component_fun(const complex *fields, const vec &loc, void *data_) { (void)loc; // unused (void)data_; // unused - return fields[0]; + return cdouble(fields[0]); } void fields::output_hdf5(component c, const volume &where, h5file *file, bool append_data, diff --git a/src/integrate.cpp b/src/integrate.cpp index 7445df910..c94a63e09 100644 --- a/src/integrate.cpp +++ b/src/integrate.cpp @@ -29,7 +29,7 @@ struct integrate_data { const component *components; component *cS; complex *ph; - complex *fvals; + complex *fvals; ptrdiff_t *offsets; int ninveps; component inveps_cs[3]; @@ -37,7 +37,7 @@ struct integrate_data { int ninvmu; component invmu_cs[3]; direction invmu_ds[3]; - complex sum; + complex sum; double maxabs; field_function integrand; void *integrand_data_; @@ -51,7 +51,8 @@ static void integrate_chunkloop(fields_chunk *fc, int ichunk, component cgrid, i integrate_data *data = (integrate_data *)data_; ptrdiff_t *off = data->offsets; component *cS = data->cS; - complex *fvals = data->fvals, *ph = data->ph; + complex *fvals = data->fvals; + complex *ph = data->ph; complex sum = 0.0; double maxabs = 0; const component *iecs = data->inveps_cs; @@ -146,7 +147,7 @@ complex fields::integrate(int num_fvals, const component *components, data.components = components; data.cS = new component[num_fvals]; data.ph = new complex[num_fvals]; - data.fvals = new complex[num_fvals]; + data.fvals = new complex[num_fvals]; data.sum = 0; data.maxabs = 0; data.integrand = integrand; @@ -196,14 +197,15 @@ complex fields::integrate(int num_fvals, const component *components, if (maxabs) *maxabs = max_to_all(data.maxabs); data.sum = sum_to_all(data.sum); - return complex(real(data.sum), imag(data.sum)); + return cdouble(data.sum); } typedef struct { field_rfunction integrand; void *integrand_data; } rfun_wrap_data; -static complex rfun_wrap(const complex *fvals, const vec &loc, void *data_) { + +static complex rfun_wrap(const complex *fvals, const vec &loc, void *data_) { rfun_wrap_data *data = (rfun_wrap_data *)data_; return data->integrand(fvals, loc, data->integrand_data); } @@ -231,11 +233,11 @@ double fields::max_abs(int num_fvals, const component *components, field_rfuncti return max_abs(num_fvals, components, rfun_wrap, &data, where); } -static complex return_the_field(const complex *fields, const vec &loc, - void *integrand_data_) { +static complex return_the_field(const complex *fields, const vec &loc, + void *integrand_data_) { (void)integrand_data_; (void)loc; // unused - return fields[0]; + return cdouble(fields[0]); } double fields::max_abs(int c, const volume &where) { diff --git a/src/integrate2.cpp b/src/integrate2.cpp index 48e255d74..f8671987c 100644 --- a/src/integrate2.cpp +++ b/src/integrate2.cpp @@ -35,7 +35,7 @@ struct integrate_data { const component *components2; component *cS; complex *ph; - complex *fvals; + complex *fvals; ptrdiff_t *offsets; int ninveps; component inveps_cs[3]; @@ -43,7 +43,7 @@ struct integrate_data { int ninvmu; component invmu_cs[3]; direction invmu_ds[3]; - complex sum; + complex sum; double maxabs; field_function integrand; void *integrand_data_; @@ -57,7 +57,8 @@ static void integrate_chunkloop(fields_chunk *fc, int ichunk, component cgrid, i integrate_data *data = (integrate_data *)data_; ptrdiff_t *off = data->offsets; component *cS = data->cS; - complex *fvals = data->fvals, *ph = data->ph; + complex *fvals = data->fvals; + complex *ph = data->ph; complex sum = 0.0; double maxabs = 0; const component *iecs = data->inveps_cs; @@ -223,7 +224,7 @@ complex fields::integrate2(const fields &fields2, int num_fvals1, data.components2 = components2; data.cS = new component[num_fvals1 + num_fvals2]; data.ph = new complex[num_fvals1 + num_fvals2]; - data.fvals = new complex[num_fvals1 + num_fvals2]; + data.fvals = new complex[num_fvals1 + num_fvals2]; data.sum = 0; data.maxabs = 0; data.integrand = integrand; @@ -285,14 +286,15 @@ complex fields::integrate2(const fields &fields2, int num_fvals1, if (maxabs) *maxabs = max_to_all(data.maxabs); data.sum = sum_to_all(data.sum); - return complex(real(data.sum), imag(data.sum)); + return cdouble(data.sum); } typedef struct { field_rfunction integrand; void *integrand_data; } rfun_wrap_data; -static complex rfun_wrap(const complex *fields, const vec &loc, void *data_) { + +static complex rfun_wrap(const complex *fields, const vec &loc, void *data_) { rfun_wrap_data *data = (rfun_wrap_data *)data_; return data->integrand(fields, loc, data->integrand_data); } diff --git a/src/meep.hpp b/src/meep.hpp index 488c517b9..6c15378a2 100644 --- a/src/meep.hpp +++ b/src/meep.hpp @@ -60,6 +60,10 @@ const double nan = NAN; const double nan = -7.0415659787563146e103; // ideally, a value never encountered in practice #endif +// Defined in array_slice.cpp +std::complex cdouble(std::complex z); +std::complex cdouble(std::complex z); + class h5file; // Defined in monitor.cpp @@ -1126,8 +1130,8 @@ class dft_chunk { // fields::process_dft_component std::complex process_dft_component(int rank, direction *ds, ivec min_corner, ivec max_corner, int num_freq, h5file *file, - double *buffer, int reim, - std::complex *field_array, void *mode1_data, + realnum *buffer, int reim, + std::complex *field_array, void *mode1_data, void *mode2_data, int ic_conjugate, bool retain_interp_weights, fields *parent); @@ -1627,9 +1631,9 @@ typedef void (*field_chunkloop)(fields_chunk *fc, int ichunk, component cgrid, i vec s0, vec s1, vec e0, vec e1, double dV0, double dV1, ivec shift, std::complex shift_phase, const symmetry &S, int sn, void *chunkloop_data); -typedef std::complex (*field_function)(const std::complex *fields, const vec &loc, +typedef std::complex (*field_function)(const std::complex *fields, const vec &loc, void *integrand_data_); -typedef double (*field_rfunction)(const std::complex *fields, const vec &loc, +typedef double (*field_rfunction)(const std::complex *fields, const vec &loc, void *integrand_data_); field_rfunction derived_component_func(derived_component c, const grid_volume &gv, int &nfields, @@ -1812,33 +1816,33 @@ class fields { // of the correct size. // otherwise, a new buffer is allocated and returned; it // must eventually be caller-deallocated via delete[]. - double *get_array_slice(const volume &where, std::vector components, - field_rfunction rfun, void *fun_data, double *slice = 0, - double frequency = 0, - bool snap = false); - - std::complex *get_complex_array_slice(const volume &where, - std::vector components, - field_function fun, void *fun_data, - std::complex *slice = 0, - double frequency = 0, - bool snap = false); + realnum *get_array_slice(const volume &where, std::vector components, + field_rfunction rfun, void *fun_data, realnum *slice = 0, + double frequency = 0, + bool snap = false); + + std::complex *get_complex_array_slice(const volume &where, + std::vector components, + field_function fun, void *fun_data, + std::complex *slice = 0, + double frequency = 0, + bool snap = false); // alternative entry points for when you have no field // function, i.e. you want just a single component or // derived component.) - double *get_array_slice(const volume &where, component c, double *slice = 0, - double frequency = 0, bool snap = false); - double *get_array_slice(const volume &where, derived_component c, double *slice = 0, - double frequency = 0, bool snap = false); - std::complex *get_complex_array_slice(const volume &where, component c, - std::complex *slice = 0, - double frequency = 0, - bool snap = false); + realnum *get_array_slice(const volume &where, component c, realnum *slice = 0, + double frequency = 0, bool snap = false); + realnum *get_array_slice(const volume &where, derived_component c, realnum *slice = 0, + double frequency = 0, bool snap = false); + std::complex *get_complex_array_slice(const volume &where, component c, + std::complex *slice = 0, + double frequency = 0, + bool snap = false); // like get_array_slice, but for *sources* instead of fields - std::complex *get_source_slice(const volume &where, component source_slice_component, - std::complex *slice = 0); + std::complex *get_source_slice(const volume &where, component source_slice_component, + std::complex *slice = 0); // master routine for all above entry points void *do_get_array_slice(const volume &where, std::vector components, @@ -2079,7 +2083,7 @@ class fields { /********************************************************/ std::complex process_dft_component(dft_chunk **chunklists, int num_chunklists, int num_freq, component c, const char *HDF5FileName, - std::complex **field_array = 0, int *rank = 0, + std::complex **field_array = 0, int *rank = 0, size_t *dims = 0, direction *dirs = 0, void *mode1_data = 0, void *mode2_data = 0, component c_conjugate = Ex, bool *first_component = 0, @@ -2095,14 +2099,14 @@ class fields { void output_dft(dft_fields fdft, const char *HDF5FileName); // get array of DFT field values - std::complex *get_dft_array(dft_flux flux, component c, int num_freq, int *rank, - size_t dims[3]); - std::complex *get_dft_array(dft_fields fdft, component c, int num_freq, int *rank, - size_t dims[3]); - std::complex *get_dft_array(dft_force force, component c, int num_freq, int *rank, - size_t dims[3]); - std::complex *get_dft_array(dft_near2far n2f, component c, int num_freq, int *rank, - size_t dims[3]); + std::complex *get_dft_array(dft_flux flux, component c, int num_freq, int *rank, + size_t dims[3]); + std::complex *get_dft_array(dft_fields fdft, component c, int num_freq, int *rank, + size_t dims[3]); + std::complex *get_dft_array(dft_force force, component c, int num_freq, int *rank, + size_t dims[3]); + std::complex *get_dft_array(dft_near2far n2f, component c, int num_freq, int *rank, + size_t dims[3]); // overlap integrals between eigenmode fields and DFT flux fields void get_overlap(void *mode1_data, void *mode2_data, dft_flux flux, int num_freq, diff --git a/src/meep/mympi.hpp b/src/meep/mympi.hpp index ac71bd1ed..1f29200ea 100644 --- a/src/meep/mympi.hpp +++ b/src/meep/mympi.hpp @@ -76,12 +76,14 @@ int min_to_all(int); float sum_to_master(float); // Only returns the correct value to proc 0. double sum_to_master(double); // Only returns the correct value to proc 0. double sum_to_all(double); +void sum_to_all(const float *in, float *out, int size); void sum_to_all(const double *in, double *out, int size); void sum_to_master(const float *in, float *out, int size); void sum_to_master(const double *in, double *out, int size); void sum_to_all(const float *in, double *out, int size); void sum_to_all(const std::complex *in, std::complex *out, int size); void sum_to_all(const std::complex *in, std::complex *out, int size); +void sum_to_all(const std::complex *in, std::complex *out, int size); void sum_to_master(const std::complex *in, std::complex *out, int size); void sum_to_master(const std::complex *in, std::complex *out, int size); long double sum_to_all(long double); diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index a5fdfb56f..496012681 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -2678,10 +2678,10 @@ std::complex get_material_gradient( else meep::abort("Invalid adjoint field component"); - if (md->trivial){ + if (md->trivial) { const double sd = 1.0; // FIXME: make user-changable? meep::volume v(r); - LOOP_OVER_DIRECTIONS(dim, d){ + LOOP_OVER_DIRECTIONS(dim, d) { v.set_direction_min(d, r.in_direction(d) - 0.5*gv.inva*sd); v.set_direction_max(d, r.in_direction(d) + 0.5*gv.inva*sd); } @@ -2695,8 +2695,7 @@ std::complex get_material_gradient( for (int i=0;i<3;i++) dA_du[i] = (row_1[i] - row_2[i])/(2*du); return dA_du[dir_idx] * fields_f; - - }else{ + } else { double orig = u[idx]; std::complex row_1[3], row_2[3], dA_du[3]; u[idx] -= du; @@ -2707,7 +2706,7 @@ std::complex get_material_gradient( for (int i=0;i<3;i++) dA_du[i] = (row_1[i] - row_2[i])/(2*du); return dA_du[dir_idx] * fields_f * cond_cmp(forward_c,r,freq,geps); - } + } } /* A brute force approach to calculating Aᵤ using finite differences. @@ -2715,17 +2714,15 @@ Past versions of the code only calculated dA/dε using a finite difference, and then multiplied by the analytic vJp (dε/du). With the addition of subpixel smoothing, however, the vJp became much more complicated and it is easier to calculate the entire gradient -using finite differences (at the cost of slightly less accurate gradients -due to roundoff). - */ +using finite differences (at the cost of slightly less accurate gradients +due to floating-point roundoff errors). */ void add_interpolate_weights(double rx, double ry, double rz, double *data, int nx, int ny, int nz, int stride, double scaleby, double *udata, int ukind, double uval, - meep::vec r, geom_epsilon *geps, + meep::vec r, geom_epsilon *geps, meep::component adjoint_c, meep::component forward_c, - std::complex fwd, std::complex adj, - double freq, meep::grid_volume &gv, double du - ) { + std::complex fwd, std::complex adj, + double freq, meep::grid_volume &gv, double du) { int x1, y1, z1, x2, y2, z2; double dx, dy, dz, u; @@ -2773,8 +2770,7 @@ in row-major order (the order used by HDF5): */ void material_grids_addgradient_point(double *v, vector3 p, double scalegrad, geom_epsilon *geps, meep::component adjoint_c, meep::component forward_c, std::complex fwd, std::complex adj, - double freq, meep::grid_volume &gv, double tol - ) { + double freq, meep::grid_volume &gv, double tol) { geom_box_tree tp; int oi, ois; material_data *mg, *mg_sum; @@ -2827,12 +2823,10 @@ void material_grids_addgradient_point(double *v, vector3 p, double scalegrad, ge uval = tanh_projection(matgrid_val(p, tp, oi, mg), mg->beta, mg->eta); do { vector3 pb = to_geom_box_coords(p, &tp->objects[oi]); - add_interpolate_weights( - pb.x, pb.y, pb.z, vcur, sz.x, sz.y, sz.z, 1, - scalegrad,ucur, kind, uval, - vector3_to_vec(p),geps,adjoint_c,forward_c, - fwd,adj,freq,gv,tol - ); + add_interpolate_weights(pb.x, pb.y, pb.z, vcur, sz.x, sz.y, sz.z, 1, + scalegrad, ucur, kind, uval, + vector3_to_vec(p), geps, adjoint_c, forward_c, + fwd, adj, freq, gv, tol); if (kind == material_data::U_DEFAULT) break; tp = geom_tree_search_next(p, tp, &oi); } while (tp && is_material_grid((material_data *)tp->objects[oi].o->material)); @@ -2846,8 +2840,8 @@ void material_grids_addgradient_point(double *v, vector3 p, double scalegrad, ge uval = tanh_projection(material_grid_val(p, mg), mg->beta, mg->eta); add_interpolate_weights(p.x, p.y, p.z, vcur, sz.x, sz.y, sz.z, 1, scalegrad, ucur, kind, uval, - vector3_to_vec(p),geps,adjoint_c,forward_c, - fwd,adj,freq,gv,tol); + vector3_to_vec(p), geps, adjoint_c, forward_c, + fwd, adj, freq, gv, tol); } } @@ -2855,15 +2849,15 @@ void material_grids_addgradient_point(double *v, vector3 p, double scalegrad, ge /* Some gradient helper routines */ /**********************************************************/ -#define LOOP_OVER_DIRECTIONS_BACKWARDS(dim, d) \ - for (meep::direction d = (meep::direction)(meep::stop_at_direction(dim)-1), \ - loop_stop_directi = meep::start_at_direction(dim); \ +#define LOOP_OVER_DIRECTIONS_BACKWARDS(dim, d) \ + for (meep::direction d = (meep::direction)(meep::stop_at_direction(dim)-1), \ + loop_stop_directi = meep::start_at_direction(dim); \ d >= loop_stop_directi; d = (meep::direction)(d - 1)) void set_strides(meep::ndim dim, ptrdiff_t *the_stride,const meep::ivec c1,const meep::ivec c2) { FOR_DIRECTIONS(d) { the_stride[d] = 1;} meep::ivec n_s = (c2 - c1)/2 + 1; - LOOP_OVER_DIRECTIONS_BACKWARDS(dim,d) { + LOOP_OVER_DIRECTIONS_BACKWARDS(dim,d) { ptrdiff_t current_stride = 1; LOOP_OVER_DIRECTIONS_BACKWARDS(dim,d_i) { if (d_i==d){ @@ -2872,23 +2866,23 @@ void set_strides(meep::ndim dim, ptrdiff_t *the_stride,const meep::ivec c1,const } current_stride *= n_s.in_direction(d_i); } - } + } } ptrdiff_t get_idx_from_ivec(meep::ndim dim, meep::ivec c1, ptrdiff_t *the_stride, meep::ivec v){ ptrdiff_t idx = 0; meep::ivec diff = ((v-c1) / 2); - LOOP_OVER_DIRECTIONS(dim,d){ + LOOP_OVER_DIRECTIONS(dim,d) { idx += diff.in_direction(d)*the_stride[d]; } return idx; } -void material_grids_addgradient(double *v, size_t ng, std::complex *fields_a, - std::complex *fields_f, size_t fields_shapes[12], double *frequencies, - double scalegrad, meep::grid_volume &gv, +void material_grids_addgradient(double *v, size_t ng, std::complex *fields_a, + std::complex *fields_f, size_t fields_shapes[12], + double *frequencies, double scalegrad, meep::grid_volume &gv, meep::volume &where, geom_epsilon *geps, double du) { - + // only loop over field components relevant to our simulation (in the proper order) #define FOR_MY_COMPONENTS(c1) FOR_ELECTRIC_COMPONENTS(c1) if (!coordinate_mismatch(gv.dim, component_direction(c1))) @@ -2915,10 +2909,10 @@ void material_grids_addgradient(double *v, size_t ng, std::complex *fiel } } size_t c_start[3]; - c_start[0] = 0; - c_start[1] = nf*stride_row[0]; + c_start[0] = 0; + c_start[1] = nf*stride_row[0]; c_start[2] = nf*(stride_row[0]+stride_row[1]); - + // fields dimensions are (components, nfreqs, x, y, z) #define GET_FIELDS(fields,comp,freq,idx) fields[c_start[comp] + freq*stride_row[comp] + idx] @@ -2926,22 +2920,20 @@ void material_grids_addgradient(double *v, size_t ng, std::complex *fiel // loop over frequency for (size_t f_i = 0; f_i < nf; ++f_i) { int ci_adjoint = 0; - FOR_MY_COMPONENTS(adjoint_c) { + FOR_MY_COMPONENTS(adjoint_c) { LOOP_OVER_IVECS(gv, is[ci_adjoint], ie[ci_adjoint], idx) { size_t idx_fields = IVEC_LOOP_COUNTER; meep::ivec ip = gv.iloc(adjoint_c,idx); meep::vec p = gv.loc(adjoint_c,idx); - std::complex adj = GET_FIELDS(fields_a,ci_adjoint,f_i,idx_fields); - + std::complex adj = GET_FIELDS(fields_a, ci_adjoint, f_i, idx_fields); material_type md; geps->get_material_pt(md, p); if (!md->trivial) adj *= cond_cmp(adjoint_c,p,frequencies[f_i], geps); - double cyl_scale; int ci_forward = 0; FOR_MY_COMPONENTS(forward_c) { - /* we need to calculate the bounds of - the forward fields (in space) so that we + /* we need to calculate the bounds of + the forward fields (in space) so that we can properly index into the fields array later */ meep::ivec isf = is[ci_forward]; @@ -2959,20 +2951,20 @@ void material_grids_addgradient(double *v, size_t ng, std::complex *fiel /********* compute -λᵀAᵤx *************/ /* trivial case, no interpolation/restriction needed */ - if (forward_c == adjoint_c){ - std::complex fwd = GET_FIELDS(fields_f,ci_forward,f_i,idx_fields); + if (forward_c == adjoint_c) { + std::complex fwd = GET_FIELDS(fields_f, ci_forward, f_i, idx_fields); cyl_scale = (gv.dim == meep::Dcyl) ? 2*p.r() : 1; // the pi is already factored in near2far.cpp material_grids_addgradient_point( - v+ng*f_i, vec_to_vector3(p), scalegrad*cyl_scale, geps, - adjoint_c, forward_c, fwd, adj, frequencies[f_i], gv, du); + v+ng*f_i, vec_to_vector3(p), scalegrad*cyl_scale, geps, + adjoint_c, forward_c, fwd, adj, frequencies[f_i], gv, du); /* more complicated case requires interpolation/restriction */ - }else{ + } else { /* we need to restrict the adjoint fields to the two nodes of interest (which requires a factor - of 0.5 to scale), and interpolate the forward fields - to the same two nodes (which requires another factor of 0.5). + of 0.5 to scale), and interpolate the forward fields + to the same two nodes (which requires another factor of 0.5). Then we perform our inner product at these nodes. - */ + */ std::complex fwd_avg, fwd1, fwd2, prod; ptrdiff_t fwd1_idx, fwd2_idx; @@ -2985,34 +2977,34 @@ void material_grids_addgradient(double *v, size_t ng, std::complex *fiel meep::ivec fwd_pa = (fwd_p + unit_a*2); meep::ivec fwd_pf = (fwd_p - unit_f*2); meep::ivec fwd_paf = (fwd_p + unit_a*2 - unit_f*2); - + //identify the two eps points meep::ivec ieps1 = (fwd_p + fwd_pf) / 2; meep::ivec ieps2 = (fwd_pa + fwd_paf) / 2; //operate on the first eps node fwd1_idx = get_idx_from_ivec(gv.dim, start_ivec, the_stride, fwd_p); - fwd1 = 0.5 * GET_FIELDS(fields_f,ci_forward,f_i,fwd1_idx); + fwd1 = 0.5 * meep::cdouble(GET_FIELDS(fields_f, ci_forward, f_i, fwd1_idx)); fwd2_idx = get_idx_from_ivec(gv.dim, start_ivec, the_stride, fwd_pf); - fwd2 = 0.5 * GET_FIELDS(fields_f,ci_forward,f_i,fwd2_idx); + fwd2 = 0.5 * meep::cdouble(GET_FIELDS(fields_f, ci_forward, f_i, fwd2_idx)); fwd_avg = fwd1 + fwd2; meep::vec eps1 = gv[ieps1]; cyl_scale = (gv.dim == meep::Dcyl) ? eps1.r() : 1; material_grids_addgradient_point( - v+ng*f_i, vec_to_vector3(eps1), scalegrad*cyl_scale, geps, - adjoint_c, forward_c, fwd_avg, 0.5*adj, frequencies[f_i], gv, du); - + v+ng*f_i, vec_to_vector3(eps1), scalegrad*cyl_scale, geps, + adjoint_c, forward_c, fwd_avg, 0.5*adj, frequencies[f_i], gv, du); + //operate on the second eps node fwd1_idx = get_idx_from_ivec(gv.dim, start_ivec, the_stride, fwd_pa); - fwd1 = 0.5 * GET_FIELDS(fields_f,ci_forward,f_i,fwd1_idx); + fwd1 = 0.5 * meep::cdouble(GET_FIELDS(fields_f, ci_forward, f_i, fwd1_idx)); fwd2_idx = get_idx_from_ivec(gv.dim, start_ivec, the_stride, fwd_paf); - fwd2 = 0.5 * GET_FIELDS(fields_f,ci_forward,f_i,fwd2_idx); + fwd2 = 0.5 * meep::cdouble(GET_FIELDS(fields_f, ci_forward, f_i, fwd2_idx)); fwd_avg = fwd1 + fwd2; meep::vec eps2 = gv[ieps2]; cyl_scale = (gv.dim == meep::Dcyl) ? eps2.r() : 1; material_grids_addgradient_point( - v+ng*f_i, vec_to_vector3(eps2), scalegrad*cyl_scale, geps, - adjoint_c, forward_c, fwd_avg, 0.5*adj, frequencies[f_i], gv, du); + v+ng*f_i, vec_to_vector3(eps2), scalegrad*cyl_scale, geps, + adjoint_c, forward_c, fwd_avg, 0.5*adj, frequencies[f_i], gv, du); } ci_forward++; } diff --git a/src/meepgeom.hpp b/src/meepgeom.hpp index f66c33e3b..bb2f0d132 100644 --- a/src/meepgeom.hpp +++ b/src/meepgeom.hpp @@ -296,10 +296,10 @@ meep::vec material_grid_grad(vector3 p, material_data *md, const geometric_objec double matgrid_val(vector3 p, geom_box_tree tp, int oi, material_data *md); double material_grid_val(vector3 p, material_data *md); geom_box_tree calculate_tree(const meep::volume &v, geometric_object_list g); -void material_grids_addgradient(double *v, size_t ng, std::complex *fields_a, - std::complex *fields_f, size_t fields_shapes[12], - double *frequencies, double scalegrad, - meep::grid_volume &gv, meep::volume &where, geom_epsilon *geps,double du=1e-6); +void material_grids_addgradient(double *v, size_t ng, std::complex *fields_a, + std::complex *fields_f, size_t fields_shapes[12], + double *frequencies, double scalegrad, meep::grid_volume &gv, + meep::volume &where, geom_epsilon *geps, double du = 1e-6); /***************************************************************/ /* routines in GDSIIgeom.cc ************************************/ diff --git a/src/mympi.cpp b/src/mympi.cpp index 8ab321dbf..331def6e6 100644 --- a/src/mympi.cpp +++ b/src/mympi.cpp @@ -441,6 +441,14 @@ double sum_to_all(double in) { return out; } +void sum_to_all(const float *in, float *out, int size) { +#ifdef HAVE_MPI + MPI_Allreduce((void *)in, out, size, MPI_FLOAT, MPI_SUM, mycomm); +#else + memcpy(out, in, sizeof(float) * size); +#endif +} + void sum_to_all(const double *in, double *out, int size) { #ifdef HAVE_MPI MPI_Allreduce((void *)in, out, size, MPI_DOUBLE, MPI_SUM, mycomm); @@ -481,6 +489,10 @@ void sum_to_all(const complex *in, complex *out, int size) { sum_to_all((const float *)in, (double *)out, 2 * size); } +void sum_to_all(const complex *in, complex *out, int size) { + sum_to_all((const float *)in, (float *)out, 2 * size); +} + void sum_to_master(const complex *in, complex *out, int size) { sum_to_master((const float *)in, (float *)out, 2 * size); } diff --git a/src/vec.cpp b/src/vec.cpp index 69e809e02..e4bf55a92 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -1479,18 +1479,19 @@ volume_list *symmetry::reduce(const volume_list *gl) const { /***************************************************************************/ -static double poynting_fun(const complex *fields, const vec &loc, void *data_) { +static double poynting_fun(const complex *fields, const vec &loc, void *data_) { (void)loc; // unused (void)data_; // unused - return (real(conj(fields[0]) * fields[1]) - real(conj(fields[2]) * fields[3])); + return (real(conj(cdouble(fields[0])) * cdouble(fields[1])) - + real(conj(cdouble(fields[2])) * cdouble(fields[3]))); } -static double energy_fun(const complex *fields, const vec &loc, void *data_) { +static double energy_fun(const complex *fields, const vec &loc, void *data_) { (void)loc; // unused int nfields = *((int *)data_) / 2; double sum = 0; for (int k = 0; k < nfields; ++k) - sum += real(conj(fields[2 * k]) * fields[2 * k + 1]); + sum += real(conj(cdouble(fields[2 * k])) * cdouble(fields[2 * k + 1])); return sum * 0.5; } diff --git a/tests/array-slice-ll.cpp b/tests/array-slice-ll.cpp index af754384b..ea4e9aded 100644 --- a/tests/array-slice-ll.cpp +++ b/tests/array-slice-ll.cpp @@ -230,7 +230,7 @@ int main(int argc, char *argv[]) { // rank = f.get_array_slice_dimensions(v1d, dims1D, dirs1D, true, false); if (rank != 1 || dims1D[0] != NX) meep::abort("incorrect dimensions for 1D slice"); - std::unique_ptr []> slice1d(f.get_complex_array_slice(v1d, Hz, 0, 0, true)); + std::unique_ptr []> slice1d(f.get_complex_array_slice(v1d, Hz, 0, 0, true)); std::vector> slice1d_realnum; for (int i = 0; i < NX; ++i) slice1d_realnum.emplace_back(slice1d[i]); @@ -239,7 +239,7 @@ int main(int argc, char *argv[]) { rank = f.get_array_slice_dimensions(v2d, dims2D, dirs2D, true, false); if (rank != 2 || dims2D[0] != NX || dims2D[1] != NY) meep::abort("incorrect dimensions for 2D slice"); - std::unique_ptr slice2d(f.get_array_slice(v2d, Sy, 0, 0, true)); + std::unique_ptr slice2d(f.get_array_slice(v2d, Sy, 0, 0, true)); std::unique_ptr slice2d_realnum(new realnum[NX * NY]); for (int i = 0; i < NX * NY; ++i) slice2d_realnum[i] = static_cast(slice2d[i]); diff --git a/tests/dft-fields.cpp b/tests/dft-fields.cpp index 241866703..6e1e44746 100644 --- a/tests/dft-fields.cpp +++ b/tests/dft-fields.cpp @@ -15,8 +15,6 @@ using namespace meep; -typedef std::complex cdouble; - vector3 v3(double x, double y = 0.0, double z = 0.0) { vector3 v; v.x = x; @@ -35,7 +33,7 @@ double dummy_eps(const vec &) { return 1.0; } /***************************************************************/ /***************************************************************/ /***************************************************************/ -void Run(bool Pulse, double resolution, cdouble **field_array = 0, int *array_rank = 0, +void Run(bool Pulse, double resolution, std::complex **field_array = 0, int *array_rank = 0, size_t *array_dims = 0) { /***************************************************************/ /* initialize geometry */ @@ -104,7 +102,7 @@ void Run(bool Pulse, double resolution, cdouble **field_array = 0, int *array_ra /***************************************************************/ /* return L2 norm of error normalized by average of L2 norms */ /***************************************************************/ -double compare_array_to_dataset(cdouble *field_array, int array_rank, size_t *array_dims, +double compare_array_to_dataset(std::complex *field_array, int array_rank, size_t *array_dims, const char *file, const char *name) { int file_rank; size_t file_dims[3]; @@ -121,8 +119,8 @@ double compare_array_to_dataset(cdouble *field_array, int array_rank, size_t *ar double NormArray = 0.0, NormFile = 0.0, NormDelta = 0.0; for (size_t n = 0; n < file_dims[0] * file_dims[1]; n++) { - cdouble zArray = field_array[n]; - cdouble zFile = cdouble(rdata[n], idata[n]); + std::complex zArray = field_array[n]; + std::complex zFile = std::complex(rdata[n], idata[n]); NormArray += norm(zArray); NormFile += norm(zFile); NormDelta += norm(zArray - zFile); @@ -181,12 +179,12 @@ double compare_complex_hdf5_datasets(const char *file1, const char *name1, const double max_abs1 = 0.0, max_abs2 = 0.0; double max_arg1 = 0.0, max_arg2 = 0.0; for (size_t n = 0; n < length; n++) { - cdouble z1 = cdouble(rdata1[n], idata1[n]); + std::complex z1 = std::complex(rdata1[n], idata1[n]); if (abs(z1) > max_abs1) { max_abs1 = abs(z1); max_arg1 = arg(z1); } - cdouble z2 = cdouble(rdata2[n], idata2[n]); + std::complex z2 = std::complex(rdata2[n], idata2[n]); if (abs(z2) > max_abs2) { max_abs2 = abs(z2); max_arg2 = arg(z2); @@ -196,11 +194,11 @@ double compare_complex_hdf5_datasets(const char *file1, const char *name1, const // second pass to get L2 norm of difference between normalized data sets double norm1 = 0.0, norm2 = 0.0, normdiff = 0.0; - cdouble phase1 = exp(-cdouble(0, 1) * max_arg1); - cdouble phase2 = exp(-cdouble(0, 1) * max_arg2); + std::complex phase1 = exp(-std::complex(0, 1) * max_arg1); + std::complex phase2 = exp(-std::complex(0, 1) * max_arg2); for (size_t n = 0; n < length; n++) { - cdouble z1 = phase1 * cdouble(rdata1[n], idata1[n]) / max_abs1; - cdouble z2 = phase2 * cdouble(rdata2[n], idata2[n]) / max_abs2; + std::complex z1 = phase1 * std::complex(rdata1[n], idata1[n]) / max_abs1; + std::complex z2 = phase2 * std::complex(rdata2[n], idata2[n]) / max_abs2; norm1 += norm(z1); norm2 += norm(z2); normdiff += norm(z1 - z2); @@ -235,7 +233,7 @@ int main(int argc, char *argv[]) { meep::abort("unknown argument %s", argv[narg]); } - cdouble *field_array = 0; + std::complex *field_array = 0; int array_rank; size_t array_dims[3]; Run(true, resolution, &field_array, &array_rank, array_dims); diff --git a/tests/integrate.cpp b/tests/integrate.cpp index 8cada6a87..2bcf84549 100644 --- a/tests/integrate.cpp +++ b/tests/integrate.cpp @@ -41,7 +41,7 @@ typedef struct { } linear_integrand_data; /* integrand for integrating c + ax*x + ay*y + az*z. */ -static complex linear_integrand(const complex *fields, const vec &loc, +static complex linear_integrand(const complex *fields, const vec &loc, void *data_) { linear_integrand_data *data = (linear_integrand_data *)data_; @@ -190,7 +190,7 @@ void check_integral(fields &f, linear_integrand_data &d, const volume &v, compon double sum = real(f.integrate(0, 0, linear_integrand, (void *)&d, v)); if (fabs(sum - correct_integral(v, d)) > 1e-9 * fabs(sum)) - meep::abort("FAILED: %0.16g instead of %0.16g\n", (double)sum, correct_integral(v, d)); + meep::abort("FAILED: %0.16g instead of %0.16g\n", sum, correct_integral(v, d)); master_printf("...PASSED.\n"); } diff --git a/tests/pw-source-ll.cpp b/tests/pw-source-ll.cpp index dd61c8ae7..56b98007a 100644 --- a/tests/pw-source-ll.cpp +++ b/tests/pw-source-ll.cpp @@ -22,8 +22,6 @@ using namespace meep; -typedef std::complex cdouble; - /***************************************************************/ /* ; pw-amp is a function that returns the amplitude exp(ik(x+x0)) at a @@ -40,12 +38,12 @@ typedef struct pw_amp_data { vec x0; } pw_amp_data; -cdouble pw_amp(vec x, void *UserData) { +std::complex pw_amp(vec x, void *UserData) { pw_amp_data *data = (pw_amp_data *)UserData; vec k = data->k; vec x0 = data->x0; - const cdouble II(0.0, 1.0); + const std::complex II(0.0, 1.0); return exp(II * (k & (x + x0))); } @@ -56,10 +54,10 @@ cdouble pw_amp(vec x, void *UserData) { /* amplitude functions and global variables. */ /***************************************************************/ pw_amp_data pw_amp_data_left; -cdouble pw_amp_left(const vec &x) { return pw_amp(x, (void *)&pw_amp_data_left); } +std::complex pw_amp_left(const vec &x) { return pw_amp(x, (void *)&pw_amp_data_left); } pw_amp_data pw_amp_data_bottom; -cdouble pw_amp_bottom(const vec &x) { return pw_amp(x, (void *)&pw_amp_data_bottom); } +std::complex pw_amp_bottom(const vec &x) { return pw_amp(x, (void *)&pw_amp_data_bottom); } /***************************************************************/ /* dummy material function needed to pass to structure( ) */ diff --git a/tests/ring-ll.cpp b/tests/ring-ll.cpp index 321251cba..e28e6d07b 100644 --- a/tests/ring-ll.cpp +++ b/tests/ring-ll.cpp @@ -21,8 +21,6 @@ using namespace meep; -typedef std::complex cdouble; - vector3 v3(double x, double y = 0.0, double z = 0.0) { vector3 v; v.x = x; @@ -109,7 +107,7 @@ int main(int argc, char *argv[]) { double T = 300.0; double stop_time = f.round_time() + T; - std::vector fieldData; + std::vector> fieldData; vec eval_pt(r + 0.1, 0.0); while (f.round_time() < stop_time) { f.step(); @@ -117,7 +115,7 @@ int main(int argc, char *argv[]) { }; #define MAXBANDS 100 - cdouble amp[MAXBANDS]; + std::complex amp[MAXBANDS]; double freq_re[MAXBANDS]; double freq_im[MAXBANDS]; double err[MAXBANDS]; @@ -137,8 +135,8 @@ int main(int argc, char *argv[]) { int ref_bands = 3; double ref_freq_re[3] = {1.1807e-01, 1.4716e-01, 1.7525e-01}; double ref_freq_im[3] = {-7.6133e-04, -2.1156e-04, -5.2215e-05}; - cdouble ref_amp[3] = {cdouble(-8.28e-04, -1.34e-03), cdouble(1.23e-03, -1.25e-02), - cdouble(2.83e-03, -6.52e-04)}; + std::complex ref_amp[3] = {std::complex(-8.28e-04, -1.34e-03), std::complex(1.23e-03, -1.25e-02), + std::complex(2.83e-03, -6.52e-04)}; if (bands != 3) meep::abort("harminv found only %i/%i bands\n", bands, ref_bands); for (int nb = 0; nb < bands; nb++) if (fabs(freq_re[nb] - ref_freq_re[nb]) > 1.0e-2 * fabs(ref_freq_re[nb]) || From 84dac0561e8c04a38acf78a8dbb62d0ab22be2fc Mon Sep 17 00:00:00 2001 From: Krishna Gadepalli <34969407+kkg4theweb@users.noreply.github.com> Date: Thu, 2 Dec 2021 04:47:51 -0800 Subject: [PATCH 081/155] Fix memory leaks (#1839) * Fix memory leaks * Add kkg to authors list --- AUTHORS | 1 + src/GDSIIgeom.cpp | 9 +++++---- src/meepgeom.cpp | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 87d2d7607..33f82cca7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -16,3 +16,4 @@ Robin Dunn Ian Williamson Andreas Hoenselaar Ben Bartlett +Krishna Gadepalli diff --git a/src/GDSIIgeom.cpp b/src/GDSIIgeom.cpp index 536bb5ca5..6907970da 100644 --- a/src/GDSIIgeom.cpp +++ b/src/GDSIIgeom.cpp @@ -144,7 +144,7 @@ geometric_object_list get_GDSII_prisms(material_type material, const char *GDSII for (int np = 0; np < num_prisms; np++) { dVec polygon = polygons[np]; int num_vertices = polygon.size() / 2; - vector3 *vertices = new vector3[num_vertices]; + auto vertices = std::make_unique(num_vertices); for (int nv = 0; nv < num_vertices; nv++) { vertices[nv].x = polygon[2 * nv + 0]; vertices[nv].y = polygon[2 * nv + 1]; @@ -152,7 +152,8 @@ geometric_object_list get_GDSII_prisms(material_type material, const char *GDSII } double height = zmax - zmin; vector3 zaxis = {0, 0, 1}; - prisms.items[np] = make_prism(material, vertices, num_vertices, height, zaxis); + prisms.items[np] = + make_prism(material, vertices.get(), num_vertices, height, zaxis); } return prisms; } @@ -169,7 +170,7 @@ geometric_object get_GDSII_prism(material_type material, const char *GDSIIFile, dVec polygon = get_polygon(GDSIIFile, Text, Layer); int num_vertices = polygon.size() / 2; - vector3 *vertices = new vector3[num_vertices]; + auto vertices = std::make_unique(num_vertices); for (int nv = 0; nv < num_vertices; nv++) { vertices[nv].x = polygon[2 * nv + 0]; vertices[nv].y = polygon[2 * nv + 1]; @@ -178,7 +179,7 @@ geometric_object get_GDSII_prism(material_type material, const char *GDSIIFile, double height = zmax - zmin; vector3 zaxis = {0, 0, 1}; - return make_prism(material, vertices, num_vertices, height, zaxis); + return make_prism(material, vertices.get(), num_vertices, height, zaxis); } geometric_object get_GDSII_prism(material_type material, const char *GDSIIFile, int Layer, diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index 496012681..c96c8f879 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -716,7 +716,8 @@ geom_epsilon::geom_epsilon(const geom_epsilon &geps1) { geom_epsilon::~geom_epsilon() { int length = geometry.num_items; for (int i = 0; i < length; i++){ - delete geometry.items[i].material; + material_free((material_type)geometry.items[i].material); + geometric_object_destroy(geometry.items[i]); } delete[] geometry.items; unset_volume(); From f9d0bb79fc13de754f10045158894ee689eef7ce Mon Sep 17 00:00:00 2001 From: Krishna Gadepalli <34969407+kkg4theweb@users.noreply.github.com> Date: Fri, 3 Dec 2021 05:47:11 -0800 Subject: [PATCH 082/155] Fix for Issue #1834 (#1840) * Fix memory leaks * Add kkg to authors list * Expose set_default_material and use it in libpympb/pympb.cpp --- libpympb/pympb.cpp | 6 ++++-- src/meepgeom.cpp | 2 +- src/meepgeom.hpp | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/libpympb/pympb.cpp b/libpympb/pympb.cpp index 6e4a00240..2c3885199 100644 --- a/libpympb/pympb.cpp +++ b/libpympb/pympb.cpp @@ -676,7 +676,7 @@ void mode_solver::init(int p, bool reset_fields, geometric_object_list *geometry meep_geom::material_data *_default_material) { int have_old_fields = 0; - default_material = _default_material; + set_default_material(_default_material); n[0] = grid_size.x; n[1] = grid_size.y; @@ -909,7 +909,9 @@ void mode_solver::reset_epsilon(geometric_object_list *geometry) { }; if (!epsilon_input_file.empty()) { - default_material = meep_geom::make_file_material(epsilon_input_file.c_str()); + meep_geom::material_type material = meep_geom::make_file_material(epsilon_input_file.c_str()); + set_default_material(material); + material_free(material); } // TODO: support mu_input_file diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index c96c8f879..eb7648395 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -29,7 +29,7 @@ namespace meep_geom { material_data vacuum_material_data; material_type vacuum = &vacuum_material_data; -static void set_default_material(material_type _default_material) { +void set_default_material(material_type _default_material) { if (default_material != NULL) { if (default_material == _default_material) return; material_free((material_type)default_material); diff --git a/src/meepgeom.hpp b/src/meepgeom.hpp index bb2f0d132..5802807e7 100644 --- a/src/meepgeom.hpp +++ b/src/meepgeom.hpp @@ -253,6 +253,7 @@ material_type make_material_grid(bool do_averaging, double beta, double eta, dou vector3 vec_to_vector3(const meep::vec &pt); meep::vec vector3_to_vec(const vector3 v3); +void set_default_material(material_type _default_material); void epsilon_material_grid(material_data *md, double u); void epsilon_file_material(material_data *md, vector3 p); bool susceptibility_equal(const susceptibility &s1, const susceptibility &s2); From 1141201c2ef7099496ff6a529b2400a18fe9a4cb Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Tue, 7 Dec 2021 13:28:13 -0500 Subject: [PATCH 083/155] use unique_ptr (C++11) instead of make_unique (C++14) (#1844) --- src/GDSIIgeom.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GDSIIgeom.cpp b/src/GDSIIgeom.cpp index 6907970da..3c7d2960b 100644 --- a/src/GDSIIgeom.cpp +++ b/src/GDSIIgeom.cpp @@ -144,7 +144,7 @@ geometric_object_list get_GDSII_prisms(material_type material, const char *GDSII for (int np = 0; np < num_prisms; np++) { dVec polygon = polygons[np]; int num_vertices = polygon.size() / 2; - auto vertices = std::make_unique(num_vertices); + std::unique_ptr vertices(new vector3[num_vertices]); for (int nv = 0; nv < num_vertices; nv++) { vertices[nv].x = polygon[2 * nv + 0]; vertices[nv].y = polygon[2 * nv + 1]; @@ -170,7 +170,7 @@ geometric_object get_GDSII_prism(material_type material, const char *GDSIIFile, dVec polygon = get_polygon(GDSIIFile, Text, Layer); int num_vertices = polygon.size() / 2; - auto vertices = std::make_unique(num_vertices); + std::unique_ptr vertices(new vector3[num_vertices]); for (int nv = 0; nv < num_vertices; nv++) { vertices[nv].x = polygon[2 * nv + 0]; vertices[nv].y = polygon[2 * nv + 1]; From 5186c66d13946161a4f4ca664a7def1d9c61890b Mon Sep 17 00:00:00 2001 From: mochen4 Date: Tue, 7 Dec 2021 16:10:15 -0500 Subject: [PATCH 084/155] Use None instead of empty list in constructors (#1846) * use None * minor fix Co-authored-by: Mo Chen --- doc/docs/Python_User_Interface.md | 18 +++++++++--------- python/geom.py | 14 +++++++------- python/simulation.py | 20 ++++++++++---------- python/solver.py | 8 ++++---- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/doc/docs/Python_User_Interface.md b/doc/docs/Python_User_Interface.md index 811be2601..d0711c5e3 100644 --- a/doc/docs/Python_User_Interface.md +++ b/doc/docs/Python_User_Interface.md @@ -89,18 +89,18 @@ control various parameters of the Meep computation. def __init__(self, cell_size, resolution, - geometry=[], - sources=[], + geometry=None, + sources=None, eps_averaging=True, dimensions=3, - boundary_layers=[], - symmetries=[], + boundary_layers=None, + symmetries=None, force_complex_fields=False, default_material=Medium(), m=0, k_point=False, kz_2d='complex', - extra_materials=[], + extra_materials=None, material_function=None, epsilon_func=None, epsilon_input_file='', @@ -4216,8 +4216,8 @@ def __init__(self, epsilon_offdiag=Vector3<0.0, 0.0, 0.0>, mu_diag=Vector3<1.0, 1.0, 1.0>, mu_offdiag=Vector3<0.0, 0.0, 0.0>, - E_susceptibilities=[], - H_susceptibilities=[], + E_susceptibilities=None, + H_susceptibilities=None, E_chi2_diag=Vector3<0.0, 0.0, 0.0>, E_chi3_diag=Vector3<0.0, 0.0, 0.0>, H_chi2_diag=Vector3<0.0, 0.0, 0.0>, @@ -4638,8 +4638,8 @@ Absorption](Materials.md#saturable-gain-and-absorption). ```python def __init__(self, - initial_populations=[], - transitions=[], + initial_populations=None, + transitions=None, **kwargs): ``` diff --git a/python/geom.py b/python/geom.py index 61a1fdb5c..19d89f48e 100755 --- a/python/geom.py +++ b/python/geom.py @@ -313,8 +313,8 @@ def __init__(self, epsilon_diag=Vector3(1, 1, 1), epsilon_offdiag=Vector3(), mu_diag=Vector3(1, 1, 1), mu_offdiag=Vector3(), - E_susceptibilities=[], - H_susceptibilities=[], + E_susceptibilities=None, + H_susceptibilities=None, E_chi2_diag=Vector3(), E_chi3_diag=Vector3(), H_chi2_diag=Vector3(), @@ -425,8 +425,8 @@ def __init__(self, epsilon_diag=Vector3(1, 1, 1), self.epsilon_offdiag = Vector3(*epsilon_offdiag) self.mu_diag = Vector3(*mu_diag) self.mu_offdiag = Vector3(*mu_offdiag) - self.E_susceptibilities = E_susceptibilities - self.H_susceptibilities = H_susceptibilities + self.E_susceptibilities = E_susceptibilities if E_susceptibilities else [] + self.H_susceptibilities = H_susceptibilities if H_susceptibilities else [] self.E_chi2_diag = Vector3(chi2, chi2, chi2) if chi2 else Vector3(*E_chi2_diag) self.E_chi3_diag = Vector3(chi3, chi3, chi3) if chi3 else Vector3(*E_chi3_diag) self.H_chi2_diag = Vector3(*H_chi2_diag) @@ -842,10 +842,10 @@ class MultilevelAtom(Susceptibility): atomic level. See [Materials/Saturable Gain and Absorption](Materials.md#saturable-gain-and-absorption). """ - def __init__(self, initial_populations=[], transitions=[], **kwargs): + def __init__(self, initial_populations=None, transitions=None, **kwargs): super(MultilevelAtom, self).__init__(**kwargs) - self.initial_populations = initial_populations - self.transitions = transitions + self.initial_populations = initial_populations if initial_populations else [] + self.transitions = transitions if transitions else [] class Transition(object): diff --git a/python/simulation.py b/python/simulation.py index b99dccf3b..acbe55ba0 100644 --- a/python/simulation.py +++ b/python/simulation.py @@ -935,18 +935,18 @@ class Simulation(object): def __init__(self, cell_size, resolution, - geometry=[], - sources=[], + geometry=None, + sources=None, eps_averaging=True, dimensions=3, - boundary_layers=[], - symmetries=[], + boundary_layers=None, + symmetries=None, force_complex_fields=False, default_material=mp.Medium(), m=0, k_point=False, kz_2d="complex", - extra_materials=[], + extra_materials=None, material_function=None, epsilon_func=None, epsilon_input_file='', @@ -1198,12 +1198,12 @@ def __init__(self, """ self.cell_size = Vector3(*cell_size) - self.geometry = geometry - self.sources = sources + self.geometry = geometry if geometry else [] + self.sources = sources if sources else [] self.resolution = resolution self.dimensions = dimensions - self.boundary_layers = boundary_layers - self.symmetries = symmetries + self.boundary_layers = boundary_layers if boundary_layers else [] + self.symmetries = symmetries if symmetries else [] self.geometry_center = Vector3(*geometry_center) self.eps_averaging = eps_averaging self.subpixel_tol = subpixel_tol @@ -1211,7 +1211,7 @@ def __init__(self, self.loop_tile_base_db = loop_tile_base_db self.loop_tile_base_eh = loop_tile_base_eh self.ensure_periodicity = ensure_periodicity - self.extra_materials = extra_materials + self.extra_materials = extra_materials if extra_materials else [] self.default_material = default_material self.epsilon_input_file = epsilon_input_file self.num_chunks = chunk_layout.numchunks() if isinstance(chunk_layout,mp.BinaryPartition) else num_chunks diff --git a/python/solver.py b/python/solver.py index 56a564a2b..3d94e0b66 100644 --- a/python/solver.py +++ b/python/solver.py @@ -86,9 +86,9 @@ def __init__(self, target_freq=0.0, tolerance=1.0e-7, num_bands=1, - k_points=[], + k_points=None, ensure_periodicity=True, - geometry=[], + geometry=None, geometry_lattice=mp.Lattice(), geometry_center=mp.Vector3(0, 0, 0), default_material=mp.Medium(epsilon=1), @@ -104,8 +104,8 @@ def __init__(self, self.mode_solver = None self.resolution = resolution self.eigensolver_flags = eigensolver_flags - self.k_points = k_points - self.geometry = geometry + self.k_points = k_points if k_points else [] + self.geometry = geometry if geometry else [] self.geometry_lattice = geometry_lattice self.geometry_center = mp.Vector3(*geometry_center) self.default_material = default_material From 3830874996230f86171410ed98935090b7ff3aa1 Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Tue, 7 Dec 2021 21:05:00 -0500 Subject: [PATCH 085/155] =?UTF-8?q?Define=20what=20happens=20when=20`?= =?UTF-8?q?=CE=B2=3D=E2=88=9E`=20and=20`u=3D=CE=B7`=20(#1842)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * define what happens when beta=inf and u=0.5 * use eta not 0.5 * Update src/meepgeom.cpp Co-authored-by: Steven G. Johnson --- src/meepgeom.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index eb7648395..b88ccc5e9 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -480,6 +480,7 @@ double material_grid_val(vector3 p, material_data *md) { static double tanh_projection(double u, double beta, double eta) { if (beta == 0) return u; + if (u == eta) return 0.5; // avoid NaN when beta is Inf double tanh_beta_eta = tanh(beta*eta); return (tanh_beta_eta + tanh(beta*(u-eta))) / (tanh_beta_eta + tanh(beta*(1-eta))); From d603830d69a48be456763243c546b9811caf5076 Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Tue, 7 Dec 2021 21:05:44 -0500 Subject: [PATCH 086/155] fix for arrays (#1845) --- python/adjoint/objective.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/adjoint/objective.py b/python/adjoint/objective.py index 9789d94b8..cd7d143d4 100644 --- a/python/adjoint/objective.py +++ b/python/adjoint/objective.py @@ -44,7 +44,7 @@ def place_adjoint_source(self, dJ): def get_evaluation(self): """Evaluates the objective quantity.""" - if self._eval: + if self._eval is not None: return self._eval else: raise RuntimeError( From de7fd416fb72a5bbc151a2ba02c7efd9d11be6d6 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Tue, 7 Dec 2021 18:52:04 -0800 Subject: [PATCH 087/155] minor improvements to docs (#1848) --- doc/docs/FAQ.md | 22 +++++++++++----------- doc/docs/Introduction.md | 18 +++++++++--------- python/adjoint/utils.py | 4 ++-- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/doc/docs/FAQ.md b/doc/docs/FAQ.md index 2ea77e268..d19446253 100644 --- a/doc/docs/FAQ.md +++ b/doc/docs/FAQ.md @@ -103,7 +103,7 @@ Meep doesn't implement a frequency-independent complex $\varepsilon$. Not only i Maxwell's equations have exponentially growing solutions for a frequency-independent negative $\varepsilon$. For any physical medium with negative $\varepsilon$, there must be dispersion, and you must likewise use dispersive materials in Meep to obtain negative $\varepsilon$ at some desired frequency. The requirement of dispersion to obtain negative $\varepsilon$ follows from the [Kramers–Kronig relations](https://en.wikipedia.org/wiki/Kramers%E2%80%93Kronig_relations), and also follows from thermodynamic considerations that the energy in the electric field must be positive. For example, see [Electrodynamics of Continuous Media](https://www.amazon.com/Electrodynamics-Continuous-Media-Second-Theoretical/dp/0750626348) by Landau, Pitaevskii, and Lifshitz. At an even more fundamental level, it can be derived from passivity constraints as shown in [Physical Review A, Vol. 90, 023847 (2014)](http://arxiv.org/abs/arXiv:1405.0238). -If you solve Maxwell's equations in a homogeneous-epsilon material at some real wavevector $\mathbf{k}$, you get a dispersion relation $\omega^2 = c^2 |\mathbf{k}|^2 / \varepsilon$. If $\varepsilon$ is positive, there are two real solutions $\omega = \pm c |\mathbf{k}| / \sqrt{\varepsilon}$, giving oscillating solutions. If $\varepsilon$ is negative, there are two imaginary solutions corresponding to exponentially decaying and exponentially growing solutions from any current source. These solutions can always be spatially decomposed into a superposition of real-$\mathbf{k}$ values via a spatial Fourier transform. +If you solve Maxwell's equations in a homogeneous-$\varepsilon$ material at some real wavevector $\mathbf{k}$, you get a dispersion relation $\omega^2 = c^2 |\mathbf{k}|^2 / \varepsilon$. If $\varepsilon$ is positive, there are two real solutions $\omega = \pm c |\mathbf{k}| / \sqrt{\varepsilon}$, giving oscillating solutions. If $\varepsilon$ is negative, there are two imaginary solutions corresponding to exponentially decaying and exponentially growing solutions from any current source. These solutions can always be spatially decomposed into a superposition of real-$\mathbf{k}$ values via a spatial Fourier transform. If you do a simulation of any kind in the time domain (not just FDTD), you pretty much can't avoid exciting both the decaying and the growing solutions. This is *not* a numerical instability, it is a real solution of the underlying equations for an unphysical material. @@ -213,25 +213,25 @@ The "steady-state" response is defined as the exp(-iωt) response field (ω=2πf Meep contains a [mode-decomposition feature](Mode_Decomposition.md) which can be used to compute complex-valued [S-parameters](https://en.wikipedia.org/wiki/Scattering_parameters). An example is provided for a [two-port network](https://en.wikipedia.org/wiki/Two-port_network#Scattering_parameters_(S-parameters)) based on a silicon directional coupler in [Tutorial/GDSII Import](/Python_Tutorials/GDSII_Import/). Additional examples are available for a [waveguide mode converter](Python_Tutorials/Mode_Decomposition.md#reflectance-of-a-waveguide-taper) and [subwavelength grating](Python_Tutorials/Mode_Decomposition.md#phase-map-of-a-subwavelength-binary-grating). -### `Harminv` is unable to find the resonant modes of my structure +### Harminv is unable to find the resonant modes of my structure -There are six possible explanations for why [`Harminv`](Python_User_Interface.md#harminv) could not find the resonant modes: (1) the run time was not long enough and the decay rate of the mode is so small that the `Harminv` data was mainly noise, (2) the `Harminv` call was not wrapped in [`after_sources`](Python_User_Interface.md#controlling-when-a-step-function-executes); if `Harminv` overlaps sources turning on and off it will get confused because the sources are not exponentially decaying fields, (3) the `Harminv` monitor is near the mode's nodal point (e.g., in a symmetry plane), (4) there are field instabilities where the fields are actually [blowing up](#why-are-the-fields-blowing-up-in-my-simulation); this may result in `Harminv` returning a negative [quality factor](https://en.wikipedia.org/wiki/Q_factor), (5) the decay rate of the mode is too fast; `Harminv` discards any modes which have a quality factor less than 50 where the leaky-mode approximation of the modes as perfectly exponentially decaying (i.e. a Lorentzian lineshape) begins to break down (and thus `Harminv` won't likely find any modes inside a [metal](Materials.md#material-dispersion)), or (6) the PML overlaps the non-radiated/evanescent field and has introduced artificial absorption effects in the local density of states (LDOS). +There are six possible explanations for why [Harminv](Python_User_Interface.md#harminv) could not find the resonant modes: (1) the run time was not long enough and the decay rate of the mode is so small that the Harminv data was mainly noise, (2) the Harminv call was not wrapped in [`after_sources`](Python_User_Interface.md#controlling-when-a-step-function-executes); if Harminv overlaps sources turning on and off it will get confused because the sources are not exponentially decaying fields, (3) the Harminv monitor is near the mode's nodal point (e.g., in a symmetry plane), (4) there are field instabilities where the fields are actually [blowing up](#why-are-the-fields-blowing-up-in-my-simulation); this may result in Harminv returning a negative [quality factor](https://en.wikipedia.org/wiki/Q_factor), (5) the decay rate of the mode is too fast; Harminv discards any modes which have a quality factor less than 50 where the leaky-mode approximation of the modes as perfectly exponentially decaying (i.e. a Lorentzian lineshape) begins to break down (and thus Harminv won't likely find any modes inside a [metal](Materials.md#material-dispersion)), or (6) the PML overlaps the non-radiated/evanescent field and has introduced artificial absorption effects in the local density of states (LDOS). -`Harminv` will find modes in perfect-conductor cavities (i.e. with no loss) with a quality factor that is very large and has an arbitrary sign; it has no way to tell that the decay rate is zero, it just knows it is very small. +Harminv will find modes in perfect-conductor cavities (i.e. with no loss) with a quality factor that is very large and has an arbitrary sign; it has no way to tell that the decay rate is zero, it just knows it is very small. -`Harminv` becomes less effective as the frequency approaches zero, so you should specify a non-zero frequency range. +Harminv becomes less effective as the frequency approaches zero, so you should specify a non-zero frequency range. In order to resolve two closely-spaced modes, in general it is preferable to run with a narrow bandwidth source around the frequency of interest to excite/analyze as few modes as possible and/or increase the run time to improve the frequency resolution. If you want to analyze an arbitrary spectrum, just use the Fourier transform as computed by [`dft_fields`](Python_User_Interface.md#field-computations). -For a structure with two doubly-degenerate modes (e.g., a dipole-like mode or two counter-propagating modes in a ring resonator), the grid discretization will almost certainly break the degeneracy slightly. In this case, `Harminv` may find two *distinct* nearly-degenerate modes. +For a structure with two doubly-degenerate modes (e.g., a dipole-like mode or two counter-propagating modes in a ring resonator), the grid discretization will almost certainly break the degeneracy slightly. In this case, Harminv may find two *distinct* nearly-degenerate modes. -Note: any real-valued signal consists of both positive and negative frequency components (with complex-conjugate amplitudes) in a Fourier domain decomposition into complex exponentials. `Harminv` usually is set up to find just one sign of the frequency, but occasionally converges to a negative-frequency component as well; these are just as meaningful as the positive frequencies. +Note: any real-valued signal consists of both positive and negative frequency components (with complex-conjugate amplitudes) in a Fourier domain decomposition into complex exponentials. Harminv usually is set up to find just one sign of the frequency, but occasionally converges to a negative-frequency component as well; these are just as meaningful as the positive frequencies. ### How do I compute the effective index of an eigenmode of a lossy waveguide? To compute the [effective index](https://www.rp-photonics.com/effective_refractive_index.html), you will need to first compute the *complex* $\omega$ (the loss in time) for a *real* $\beta$ (the propagation constant) and then convert this quantity into a loss in space (*complex* $\beta$ at a *real* $\omega$) by dividing by the group velocity $v_g$. This procedure is described in more detail below. -To obtain the loss in time, you make your computational cell a cross-section of your waveguide (i.e. 2d for a waveguide with constant cross-section), and set Bloch-periodic boundary conditions via the `k_point` input variable — this specifies your (real) $\beta$. You then treat it exactly the same as a [resonant-cavity problem](Python_Tutorials/Resonant_Modes_and_Transmission_in_a_Waveguide_Cavity.md#resonant-modes): you excite the system with a short pulse source, monitor the field at some point, and then analyze the result with [`Harminv`](Python_User_Interface.md#harminv); all of which is done if you call `run_kpoints`. This will give you the complex $\omega$ at the given $\beta$, where the imaginary part is the loss rate in time. Note: the loss in a uniform waveguide, with no absorption or disorder, is zero, even in the discretized system. +To obtain the loss in time, you make your computational cell a cross-section of your waveguide (i.e. 2d for a waveguide with constant cross-section), and set Bloch-periodic boundary conditions via the `k_point` input variable — this specifies your (real) $\beta$. You then treat it exactly the same as a [resonant-cavity problem](Python_Tutorials/Resonant_Modes_and_Transmission_in_a_Waveguide_Cavity.md#resonant-modes): you excite the system with a short pulse source, monitor the field at some point, and then analyze the result with [Harminv](Python_User_Interface.md#harminv); all of which is done if you call `run_kpoints`. This will give you the complex $\omega$ at the given $\beta$, where the imaginary part is the loss rate in time. Note: the loss in a uniform waveguide, with no absorption or disorder, is zero, even in the discretized system. That is, you have $\omega(\beta_r) = \omega_r + i\omega_i$ where the subscripts $r$ and $i$ denote real and imaginary parts. Now, what you want to do is to get the complex $\beta$ at the real $\omega$ which is given by: $\beta(\omega_r) = \beta_r - i\omega_i/v_g + \mathcal{O}(\omega_i^2)$. That is, to first order in the loss, the imaginary part of $\beta$ (the propagation loss) at the real frequency $\omega_r$ is given just by dividing $\omega_i$ by the group velocity $v_g = \frac{d\omega}{d\beta}$, which you can [get from the dispersion relation in the absence of loss](#how-do-i-compute-the-group-velocity-of-a-mode). This relationship is just a consequence of the first-order Taylor expansion of the dispersion relation $\omega(\beta)$ in the complex plane. @@ -239,7 +239,7 @@ This analysis is only valid if the loss is small, i.e. $\omega_i \ll \omega_r$. ### How do I compute the group velocity of a mode? -There are two possible approaches for manually computing the [group velocity](https://en.wikipedia.org/wiki/Group_velocity) $\nabla_\textbf{k}\omega$: (1) compute the [dispersion relation](Python_Tutorials/Resonant_Modes_and_Transmission_in_a_Waveguide_Cavity.md#band-diagram) $\omega(\textbf{k})$ using [`Harminv`](Python_User_Interface.md#harminv), fit it to a polynomial, and calculate its derivative using a [finite difference](https://en.wikipedia.org/wiki/Finite_difference) (i.e. $[ \omega(\textbf{k} + \Delta \textbf{k}) - \omega(\textbf{k}-\Delta \textbf{k}) ] / (2\|\Delta \textbf{k}\|)$, or (2) excite the mode using a narrowband pulse and compute the ratio of the Poynting flux to electric-field energy density. +There are two possible approaches for manually computing the [group velocity](https://en.wikipedia.org/wiki/Group_velocity) $\nabla_\textbf{k}\omega$: (1) compute the [dispersion relation](Python_Tutorials/Resonant_Modes_and_Transmission_in_a_Waveguide_Cavity.md#band-diagram) $\omega(\textbf{k})$ using [Harminv](Python_User_Interface.md#harminv), fit it to a polynomial, and calculate its derivative using a [finite difference](https://en.wikipedia.org/wiki/Finite_difference) (i.e. $[ \omega(\textbf{k} + \Delta \textbf{k}) - \omega(\textbf{k}-\Delta \textbf{k}) ] / (2\|\Delta \textbf{k}\|)$, or (2) excite the mode using a narrowband pulse and compute the ratio of the Poynting flux to electric-field energy density. For eigenmodes obtained using [mode decomposition](Python_User_Interface.md#mode-decomposition), the group velocities are computed automatically along with the mode coefficients. @@ -257,7 +257,7 @@ The decay coefficient of the fields within any PML contains a $\cos(\theta)$ fac ### How do I compute the modes of a non-orthogonal (i.e., triangular) lattice? -Meep does not support non-rectangular unit cells. To model a triangular lattice, you have to use a supercell. This will cause the band structure to be [folded](#why-are-there-strange-peaks-in-my-reflectancetransmittance-spectrum-when-modeling-planar-or-periodic-structures). However, if you take your point source and replicate it according to the underlying triangular lattice vectors, with the right phase relationship according to the Bloch wavevector, then it should excite the folded bands only with very low amplitude as reported by [`Harminv`](Python_User_Interface.md#harminv). Also, for every `Harminv` point you put in, you should analyze the fields from the periodic copies of that point (with the periodicity of the underlying lattice). Then, reject any frequency that is not detected at *all* points, with an amplitude that is related by something close to the correct $\exp(i\vec{k}\cdot\vec{r})$ phase. +Meep does not support non-rectangular unit cells. To model a triangular lattice, you have to use a supercell. This will cause the band structure to be [folded](#why-are-there-strange-peaks-in-my-reflectancetransmittance-spectrum-when-modeling-planar-or-periodic-structures). However, if you take your point source and replicate it according to the underlying triangular lattice vectors, with the right phase relationship according to the Bloch wavevector, then it should excite the folded bands only with very low amplitude as reported by [Harminv](Python_User_Interface.md#harminv). Also, for every Harminv point you put in, you should analyze the fields from the periodic copies of that point (with the periodicity of the underlying lattice). Then, reject any frequency that is not detected at *all* points, with an amplitude that is related by something close to the correct $\exp(i\vec{k}\cdot\vec{r})$ phase. In principle, the excitation of the folded bands would be exactly zero if you place your sources correctly in the supercell. However, this doesn't happen in FDTD because the finite grid spoils the symmetry slightly. It also means that the detection of folded bands will vary with resolution. For an example, see Section 4.6 ("Sources in Supercells") in [Chapter 4](http://arxiv.org/abs/arXiv:1301.5366) ("Electromagnetic Wave Source Conditions") of [Advances in FDTD Computational Electrodynamics: Photonics and Nanotechnology](https://www.amazon.com/Advances-FDTD-Computational-Electrodynamics-Nanotechnology/dp/1608071707). @@ -471,7 +471,7 @@ To output the data to an HDF5 file, you can use the [`in_volume`](Python_User_In ### How do I compute the absorbed power in a local subregion of the cell? -To compute the absorbed power anywhere in the cell, you can use [Poynting's theorem](https://en.wikipedia.org/wiki/Poynting%27s_theorem): place a *closed* surface of [`dft`](Python_User_Interface.md#flux-spectra) flux monitors surrounding the subregion and specify the `weight` parameter of each [`FluxRegion`](Python_User_Interface.md#fluxregion) accordingly (i.e., ±1) in order to capture all incoming power. For a 2d example, see [Tutorial/Near-to-Far Field Spectra/Radiation Pattern of an Antenna](Python_Tutorials/Near_to_Far_Field_Spectra.md#radiation-pattern-of-an-antenna). There is also a 3d example for calculating the [light-extraction efficiency of an organic light-emitting diode (OLED)](http://www.simpetus.com/projects.html#meep_oled). +To compute the absorbed power anywhere in the cell, you can use [Poynting's theorem](https://en.wikipedia.org/wiki/Poynting%27s_theorem): place a *closed* surface of [DFT](Python_User_Interface.md#flux-spectra) flux monitors surrounding the subregion and specify the `weight` parameter of each [`FluxRegion`](Python_User_Interface.md#fluxregion) accordingly (i.e., ±1) in order to capture all incoming power. For a 2d example, see [Tutorial/Near-to-Far Field Spectra/Radiation Pattern of an Antenna](Python_Tutorials/Near_to_Far_Field_Spectra.md#radiation-pattern-of-an-antenna). There is also a 3d example for calculating the [light-extraction efficiency of an organic light-emitting diode (OLED)](http://www.simpetus.com/projects.html#meep_oled). ### What happens if I specify an output volume that extends beyond a cell with periodic boundaries? diff --git a/doc/docs/Introduction.md b/doc/docs/Introduction.md index d4466289c..c4c7cb91f 100644 --- a/doc/docs/Introduction.md +++ b/doc/docs/Introduction.md @@ -13,7 +13,7 @@ This introduction does not describe the [Python Interface](Python_User_Interface Maxwell's Equations ------------------- -Meep simulates [Maxwell's equations](https://en.wikipedia.org/wiki/Maxwell's_equations), which describe the interactions of electric (**E**) and magnetic (**H**) fields with one another and with matter and sources. In particular, the equations for the time evolution of the fields are: +Meep simulates [Maxwell's equations](https://en.wikipedia.org/wiki/Maxwell's_equations), which describe the interactions of electric ($\mathbf{E}$) and magnetic ($\mathbf{H}$) fields with one another and with matter and sources. In particular, the equations for the time evolution of the fields are:
@@ -27,7 +27,7 @@ $\mathbf{D} = \varepsilon \mathbf{E}$
-where **D** is the displacement field, ε is the dielectric constant, **J** is the current density (of electric charge), and **J***B* is the *magnetic-charge* current density. Magnetic currents are a convenient computational fiction in some situations. **B** is the magnetic flux density (often called the magnetic field), μ is the magnetic permeability, and **H** is the magnetic field. The σ$_B$ and σ$_D$ terms correspond to (frequency-independent) magnetic and electric conductivities, respectively. The divergence equations are implicitly: +where $\mathbf{D}$ is the displacement field, $\varepsilon$ is the dielectric constant, $\mathbf{J}$ is the current density (of electric charge), and $\mathbf{J}$*B* is the *magnetic-charge* current density. Magnetic currents are a convenient computational fiction in some situations. $\mathbf{B}$ is the magnetic flux density (often called the magnetic field), $\mu$ is the magnetic permeability, and $\mathbf{H}$ is the magnetic field. The $\sigma_B$ and $\sigma_D$ terms correspond to (frequency-independent) magnetic and electric conductivities, respectively. The divergence equations are implicitly:
@@ -37,7 +37,7 @@ $\nabla \cdot \mathbf{D} = - \int^t \nabla \cdot (\mathbf{J}(t') + \sigma_D \mat
-Generally, ε depends not only on position but also on frequency (material dispersion) and on the field **E** itself (nonlinearity), and may include loss or gain. These effects are supported in Meep and are described in [Materials](Materials.md). +Generally, $\varepsilon$ depends not only on position but also on frequency (material dispersion) and on the field $\mathbf{E}$ itself (nonlinearity), and may include loss or gain. These effects are supported in Meep and are described in [Materials](Materials.md). For rotationally symmetric geometries, Meep supports simulation in [Cylindrical Coordinates](Python_Tutorials/Cylindrical_Coordinates.md). @@ -75,7 +75,7 @@ FDTD methods divide space and time into a finite rectangular grid. As described Perhaps the most important thing you need to know is this: if the grid has some spatial resolution $\Delta x$, then our discrete time-step $\Delta t$ is given by $\Delta t = S \Delta x$, where $S$ is the [Courant factor](https://en.wikipedia.org/wiki/Courant%E2%80%93Friedrichs%E2%80%93Lewy_condition) and must satisfy $S < n_\textrm{min} / \sqrt{\mathrm{\# dimensions}}$, where $n_\textrm{min}$ is the minimum refractive index (usually 1), in order for the method to be stable (not diverge). In Meep, $S=0.5$ by default (which is sufficient for 1 to 3 dimensions), but [can be changed](Python_User_Interface.md#the-simulation-class) by the user. This means that **when you double the grid resolution, the number of time steps doubles as well** (for the same simulation period). Thus, in three dimensions, if you double the resolution, then the amount of memory increases by 8 and the amount of computational time increases by (at least) 2. -The second most important thing you should know is that, in order to discretize the equations with [second-order accuracy](https://en.wikipedia.org/wiki/Finite_difference_method#Accuracy_and_order), FDTD methods **store different field components at different grid locations**. This discretization is known as a [Yee lattice](Yee_Lattice.md). As a consequence, **Meep must interpolate the field components to a common point** whenever you want to combine, compare, or output the field components (e.g. in computing energy density or flux). Most of the time, you don't need to worry too much about this interpolation since it is automatic. However, because it is a simple linear interpolation, while **E** and **D** may be discontinuous across dielectric boundaries, it means that the interpolated **E** and **D** fields may be less accurate than you might expect right around dielectric interfaces. +The second most important thing you should know is that, in order to discretize the equations with [second-order accuracy](https://en.wikipedia.org/wiki/Finite_difference_method#Accuracy_and_order), FDTD methods **store different field components at different grid locations**. This discretization is known as a [Yee lattice](Yee_Lattice.md). As a consequence, **Meep must interpolate the field components to a common point** whenever you want to combine, compare, or output the field components (e.g. in computing energy density or flux). Most of the time, you don't need to worry too much about this interpolation since it is automatic. However, because it is a simple linear interpolation, while $\mathbf{E}$ and $\mathbf{D}$ may be discontinuous across dielectric boundaries, it means that the interpolated $\mathbf{E}$ and $\mathbf{D}$ fields may be less accurate than you might expect right around dielectric interfaces. Many references are available on FDTD methods for computational electromagnetics. See, for example: @@ -115,9 +115,9 @@ In this section, we sketch out a few of the basic ways in which FDTD can be used The most obvious thing that you can do with a time-domain simulation, of course, is to simply get a [picture of the field pattern](Python_User_Interface.md#data-visualization) resulting from a given source, or perhaps an [animation showing the field evolution in time](Python_User_Interface.md#animate2d). -The field pattern from a given localized source at a particular frequency ω is a form of the **Green's function** of the system. More specifically, one typically writes the "dyadic" Green's function +The field pattern from a given localized source at a particular frequency $\omega$ is a form of the **Green's function** of the system. More specifically, one typically writes the "dyadic" Green's function -$$G_{ij}(\omega; \mathbf{x}, \mathbf{x}')$$ which gives the $i$th component of (say) **E** at **x** from a point current source **J** at $\mathbf{x'}$, such that $\mathbf{J}(\mathbf{x})=\hat{\mathbf{e}_j} \cdot \exp(-i\omega t) \cdot \delta(\mathbf{x}-\mathbf{x}')$. To obtain this in FDTD, you simply place the requisite point source at $\mathbf{x'}$ and wait for a long enough time for all other frequency components to die out (noting that the mere act of turning on a current source at $t=0$ introduces a spectrum of frequencies). Alternatively, you can use Meep's frequency-domain solver to find the response directly (by solving the associated linear equation). For an example, see [Tutorial/Frequency Domain Solver](Python_Tutorials/Frequency_Domain_Solver.md). +$$G_{ij}(\omega; \mathbf{x}, \mathbf{x}')$$ which gives the $i$th component of (say) $\mathbf{E}$ at $\mathbf{x}$ from a point current source $\mathbf{J}$ at $\mathbf{x'}$, such that $\mathbf{J}(\mathbf{x})=\hat{\mathbf{e}_j} \cdot \exp(-i\omega t) \cdot \delta(\mathbf{x}-\mathbf{x}')$. To obtain this in FDTD, you simply place the requisite point source at $\mathbf{x'}$ and wait for a long enough time for all other frequency components to die out (noting that the mere act of turning on a current source at $t=0$ introduces a spectrum of frequencies). Alternatively, you can use Meep's frequency-domain solver to find the response directly (by solving the associated linear equation). For an example, see [Tutorial/Frequency Domain Solver](Python_Tutorials/Frequency_Domain_Solver.md). Given the Green's function, one can then compute a wide variety of useful things, from the radiated flux, to the local density of states (proportional to $\sum_i G_{ii}$), to Born approximations for small scatterers. Even more powerfully, one can compute many such quantities for multiple frequencies simultaneously using the Fourier transform of a short pulse as described below. @@ -147,12 +147,12 @@ Meep is designed to make these kinds of calculations easy, as long as you have s ### Resonant Modes -Another common task in FDTD is to compute resonant modes or eigenmodes of a given structure. For example, suppose you have a [diffraction grating](Python_Tutorials/Mode_Decomposition.md#diffraction-spectrum-of-a-binary-grating), photonic crystal (periodic dielectric structure), or a waveguide and you want to know its harmonic (definite-ω) modes at a given wavevector **k**. Or, suppose you have a resonant cavity that traps light in a small region for a long time, and you want to know the resonant frequency ω and the decay lifetime (quality factor) *Q*. And, of course, you may want the field patterns of these modes along with how a [given mode is decomposed into a linear superposition of its basis modes](Mode_Decomposition.md). +Another common task in FDTD is to compute resonant modes or eigenmodes of a given structure. For example, suppose you have a [diffraction grating](Python_Tutorials/Mode_Decomposition.md#diffraction-spectrum-of-a-binary-grating), photonic crystal (periodic dielectric structure), or a waveguide and you want to know its harmonic (definite-$\omega$) modes at a given wavevector $\mathbf{k}$. Or, suppose you have a resonant cavity that traps light in a small region for a long time, and you want to know the resonant frequency $\omega$ and the decay lifetime (quality factor) $Q$. And, of course, you may want the field patterns of these modes along with how a [given mode is decomposed into a linear superposition of its basis modes](Mode_Decomposition.md). In order to extract the frequencies and lifetimes (which may be infinite in a lossless system) with FDTD, the basic strategy is simple. You set up the structure with Bloch-periodic and/or absorbing boundaries, depending on whether it is a periodic or open system. Then you excite the mode(s) with a short pulse (broad bandwidth) from a current placed directly inside the cavity/waveguide/whatever. Finally, once the current source is turned off, you have some fields bouncing around inside the system, and you analyze them to extract the frequencies and decay rates. -The simplest form of harmonic analysis would be to compute the Fourier transform of the fields at some point — harmonic modes will yield sharp peaks in the spectrum. This method has serious drawbacks, however, in that high frequency resolution requires a very long running time, and moreover the problem of extracting the decay rates leads to a poorly-conditioned nonlinear fitting problem. Instead, Meep allows you to perform a more sophisticated signal processing algorithm borrowed from NMR spectroscopy — the algorithm is called *filter diagonalization* and is implemented by [Harminv](https://github.com/NanoComp/harminv/blob/master/README.md) package. Harminv extracts all of the frequencies and their decay rates (and amplitudes) within a short time to high accuracy; for example, we have used it to find lifetimes *Q* of 109 periods in a computational run of only a few hundred periods. +The simplest form of harmonic analysis would be to compute the Fourier transform of the fields at some point — harmonic modes will yield sharp peaks in the spectrum. This method has serious drawbacks, however, in that high frequency resolution requires a very long running time, and moreover the problem of extracting the decay rates leads to a poorly-conditioned nonlinear fitting problem. Instead, Meep allows you to perform a more sophisticated signal processing algorithm borrowed from NMR spectroscopy — the algorithm is called *filter diagonalization* and is implemented by [Harminv](https://github.com/NanoComp/harminv/blob/master/README.md) package. Harminv extracts all of the frequencies and their decay rates (and amplitudes) within a short time to high accuracy; for example, we have used it to find lifetimes $Q$ of 109 periods in a computational run of only a few hundred periods. -Once you know the frequencies of the modes, if you want the field patterns you will need to run the simulation again with a *narrow*-bandwidth (long-time) pulse to excite only the mode in question. Unless you want the longest-lifetime mode, in which case you can just run long enough for the other modes to decay away. Given the field patterns, you can then perform other analyses (e.g. decomposing the *Q* into decay rates into different directions via flux computations, finding modal volumes, etcetera). For an example, see [Tutorial/Resonant Modes and Transmission in a Waveguide Cavity](Python_Tutorials/Resonant_Modes_and_Transmission_in_a_Waveguide_Cavity.md#resonant-modes). +Once you know the frequencies of the modes, if you want the field patterns you will need to run the simulation again with a *narrow*-bandwidth (long-time) pulse to excite only the mode in question. Unless you want the longest-lifetime mode, in which case you can just run long enough for the other modes to decay away. Given the field patterns, you can then perform other analyses (e.g. decomposing the $Q$ into decay rates into different directions via flux computations, finding modal volumes, etcetera). For an example, see [Tutorial/Resonant Modes and Transmission in a Waveguide Cavity](Python_Tutorials/Resonant_Modes_and_Transmission_in_a_Waveguide_Cavity.md#resonant-modes). Why should you use Meep instead of [MPB](https://mpb.readthedocs.io) to compute the modes? Unlike MPB, Meep supports metallic and absorbing materials, can compute lossy resonant modes, can quickly compute large numbers of ω's at once by a single short pulse, and can efficiently extract modes in the interior of the spectrum (e.g. in a band gap). Why should you ever use MPB, then? MPB is quicker at computing the lowest-ω modes than Meep, gives you both the ω and the fields at once, and has no problem resolving closely-spaced frequencies. Moreover, computing modes in time domain is somewhat subtle and requires care — for example, one will occasionally miss a mode if the source happens to be nearly orthogonal to it or if it is too close to another mode; conversely, the signal processing will sometimes accidentally identify spurious peak frequencies. Also, studying periodic systems with non-rectangular unit cells is more subtle in Meep than in MPB. MPB is much more straightforward and reliable, albeit more limited in some ways. diff --git a/python/adjoint/utils.py b/python/adjoint/utils.py index 38ffbd8f9..e6a5d1581 100644 --- a/python/adjoint/utils.py +++ b/python/adjoint/utils.py @@ -63,9 +63,9 @@ def get_gradient(self, sim, fields_a, fields_f, frequencies, finite_difference_s spatial_shape = sim.get_array_slice_dimensions(component, vol=self.volume)[0] if (fields_a[component_idx][0,...].size == 1): fields_a[component_idx] = onp.zeros(onp.insert(spatial_shape,0,num_freqs), - dtype=onp.float32 if mp.is_single_precision() else onp.float64) + dtype=onp.complex64 if mp.is_single_precision() else onp.complex128) fields_f[component_idx] = onp.zeros(onp.insert(spatial_shape,0,num_freqs), - dtype=onp.float32 if mp.is_single_precision() else onp.float64) + dtype=onp.complex64 if mp.is_single_precision() else onp.complex128) if _check_if_cylindrical(sim): '''For some reason, get_dft_array returns the field components in a different order than the convention used From 026c22293f7ad06d7237cf34cea275e5c2cbb63c Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 11:01:01 -0500 Subject: [PATCH 088/155] update homebrew instructions for hdf5 and openblas (fixes #1850) --- doc/docs/Installation.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/docs/Installation.md b/doc/docs/Installation.md index 2783688ef..e564e2e4b 100644 --- a/doc/docs/Installation.md +++ b/doc/docs/Installation.md @@ -137,16 +137,20 @@ The first steps are: ```sh brew doctor -brew install homebrew/science/hdf5 homebrew/science/openblas guile fftw h5utils +brew install hdf5 guile fftw gsl libpng autoconf automake libtool swig +``` +If you don't have your own Python installation (e.g. via [miniforge](https://github.com/conda-forge/miniforge)), you should install `numpy` and `matplotlib`: +```sh +pip3 install numpy matplotlib ``` Now, install the Harminv, libctl, MPB, and Meep packages from source. Download [Harminv](https://github.com/NanoComp/harminv/blob/master/README.md) and, in the `harminv` directory, do: ```sh -./configure && make && make install +./configure CPPFLAGS="-I$(brew --prefix)/include" LDFLAGS="-L$(brew --prefix)/lib" && make && make install ``` -Use the same commands for [libctl](https://libctl.readthedocs.io), [MPB](https://mpb.readthedocs.io), and Meep. For more detailed information, see [Build From Source](Build_From_Source.md). +Use the same commands for [libctl](https://libctl.readthedocs.io), [MPB](https://mpb.readthedocs.io), (optionally) [h5utils](https://github.com/NanoComp/h5utils), (optionally) [libGDSII](https://github.com/HomerReid/libGDSII), and Meep. For more detailed information, see [Build From Source](Build_From_Source.md). You are done, and can now run Meep (Scheme interface) just by typing `meep`. You can run `make check` in the meep directory if you want to perform a self-test. From 976f2c8021a88b6adb0bee8e13edf7289eaefc28 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 11:55:32 -0500 Subject: [PATCH 089/155] recommend python3 on macos --- doc/docs/Installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/docs/Installation.md b/doc/docs/Installation.md index e564e2e4b..67003b6b8 100644 --- a/doc/docs/Installation.md +++ b/doc/docs/Installation.md @@ -147,7 +147,7 @@ pip3 install numpy matplotlib Now, install the Harminv, libctl, MPB, and Meep packages from source. Download [Harminv](https://github.com/NanoComp/harminv/blob/master/README.md) and, in the `harminv` directory, do: ```sh -./configure CPPFLAGS="-I$(brew --prefix)/include" LDFLAGS="-L$(brew --prefix)/lib" && make && make install +./configure CPPFLAGS="-I$(brew --prefix)/include" LDFLAGS="-L$(brew --prefix)/lib" PYTHON=python3 && make && make install ``` Use the same commands for [libctl](https://libctl.readthedocs.io), [MPB](https://mpb.readthedocs.io), (optionally) [h5utils](https://github.com/NanoComp/h5utils), (optionally) [libGDSII](https://github.com/HomerReid/libGDSII), and Meep. For more detailed information, see [Build From Source](Build_From_Source.md). From 348e9f7c849ab8a55750ec5bef3702671222839b Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 12:11:39 -0500 Subject: [PATCH 090/155] silence compiler warnings --- src/meepgeom.cpp | 24 +++++++++++------------- src/meepgeom.hpp | 5 ++--- src/mympi.cpp | 2 ++ 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index b88ccc5e9..8b9fa157c 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -563,7 +563,7 @@ void epsilon_material_grid(material_data *md, double u) { // Linearly interpolate dc epsilon values cinterp_tensors(m1->epsilon_diag, m1->epsilon_offdiag, m2->epsilon_diag, m2->epsilon_offdiag, &mm->epsilon_diag, &mm->epsilon_offdiag, u); - + // Interpolate resonant strength from d.p. vector3 zero_vec; zero_vec.x = zero_vec.y = zero_vec.z = 0; @@ -651,7 +651,7 @@ geom_epsilon::geom_epsilon(geometric_object_list g, material_type_list mlist, geometry.items[i].material = new material_data(); static_cast(geometry.items[i].material)->copy_from(*(material_data *)(g.items[i].material)); } - + extra_materials = mlist; current_pol = NULL; @@ -1551,8 +1551,6 @@ static bool has_conductivity(const material_type &md, meep::component c) { } bool geom_epsilon::has_conductivity(meep::component c) { - medium_struct *mm; - FOR_DIRECTIONS(d) FOR_SIDES(b) { if (cond[d][b].prof) return true; } @@ -1983,7 +1981,7 @@ void set_materials_from_geometry(meep::structure *s, geometric_object_list g, ve absorber_list alist, material_type_list extra_materials) { meep_geom::geom_epsilon *geps = meep_geom::make_geom_epsilon(s, &g, center, _ensure_periodicity, _default_material, extra_materials); - set_materials_from_geom_epsilon(s, geps, center, use_anisotropic_averaging, tol, + set_materials_from_geom_epsilon(s, geps, use_anisotropic_averaging, tol, maxeval, alist); delete geps; } @@ -1991,9 +1989,9 @@ void set_materials_from_geometry(meep::structure *s, geometric_object_list g, ve /* from a previously created geom_epsilon object, set the materials as specified */ void set_materials_from_geom_epsilon(meep::structure *s, geom_epsilon *geps, - vector3 center, bool use_anisotropic_averaging, + bool use_anisotropic_averaging, double tol, int maxeval, absorber_list alist) { - + // store for later use in gradient calculations geps->tol = tol; geps->maxeval = maxeval; @@ -2539,7 +2537,7 @@ double vec_to_value(vector3 diag, vector3 offdiag, int idx) { } void invert_tensor(std::complex t_inv[9], std::complex t[9]) { - + #define m(x,y) t[x*3+y] #define minv(x,y) t_inv[x*3+y] std::complex det = m(0, 0) * (m(1, 1) * m(2, 2) - m(2, 1) * m(1, 2)) - @@ -2555,8 +2553,8 @@ void invert_tensor(std::complex t_inv[9], std::complex t[9]) { minv(2, 0) = (m(1, 0) * m(2, 1) - m(2, 0) * m(1, 1)) * invdet; minv(2, 1) = (m(2, 0) * m(0, 1) - m(0, 0) * m(2, 1)) * invdet; minv(2, 2) = (m(0, 0) * m(1, 1) - m(1, 0) * m(0, 1)) * invdet; -#undef m(x,y) -#undef minv(x,y) +#undef m +#undef minv } void get_chi1_tensor_disp(std::complex tensor[9], const meep::vec &r, double freq, geom_epsilon *geps) { @@ -2649,7 +2647,7 @@ std::complex get_material_gradient( ) { /*Compute the Aᵤx product from the -λᵀAᵤx calculation. The current adjoint (λ) field component (adjoint_c) - determines which row of Aᵤ we care about. + determines which row of Aᵤ we care about. The current forward (x) field component (forward_c) determines which column of Aᵤ we care about. @@ -2661,7 +2659,7 @@ std::complex get_material_gradient( 2. We use eff_chi1inv_row_disp() for all other cases (at the expense of not accounting for subpixel smoothing, if there were any). - + For now we do a finite difference approach to estimate the gradient of the system matrix A since it's fairly accurate, cheap, and easy to generalize.*/ @@ -2967,7 +2965,7 @@ void material_grids_addgradient(double *v, size_t ng, std::complex fwd_avg, fwd1, fwd2, prod; + std::complex fwd_avg, fwd1, fwd2; ptrdiff_t fwd1_idx, fwd2_idx; //identify the first corner of the forward fields diff --git a/src/meepgeom.hpp b/src/meepgeom.hpp index 5802807e7..3be1e0c39 100644 --- a/src/meepgeom.hpp +++ b/src/meepgeom.hpp @@ -173,13 +173,13 @@ class geom_epsilon : public meep::material_function { public: double u_p = 0; - geom_box_tree geometry_tree; + geom_box_tree geometry_tree; geom_box_tree restricted_tree; geometric_object_list geometry; cond_profile cond[5][2]; // [direction][side] double tol=DEFAULT_SUBPIXEL_TOL; int maxeval=DEFAULT_SUBPIXEL_MAXEVAL; - + geom_epsilon(geometric_object_list g, material_type_list mlist, const meep::volume &v); geom_epsilon(const geom_epsilon &geps1); // copy constructor virtual ~geom_epsilon(); @@ -239,7 +239,6 @@ void set_materials_from_geometry(meep::structure *s, geometric_object_list g, material_type _default_material = vacuum, absorber_list alist = 0, material_type_list extra_materials = material_type_list()); void set_materials_from_geom_epsilon(meep::structure *s, geom_epsilon *geps, - vector3 center = make_vector3(), bool use_anisotropic_averaging = true, double tol = DEFAULT_SUBPIXEL_TOL, int maxeval = DEFAULT_SUBPIXEL_MAXEVAL, diff --git a/src/mympi.cpp b/src/mympi.cpp index 331def6e6..1f018166a 100644 --- a/src/mympi.cpp +++ b/src/mympi.cpp @@ -171,6 +171,8 @@ static void _set_zero_subnormals(bool iszero) else state &= ~flags; _mm_setcsr(state); +#else + (void) iszero; // unused #endif } void set_zero_subnormals(bool iszero) From 5554aac79b166c94fbdc534643822b4222681c59 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 12:14:05 -0500 Subject: [PATCH 091/155] whoops, missing commit --- python/meep.i | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/meep.i b/python/meep.i index bb7f1c80c..71a917aed 100644 --- a/python/meep.i +++ b/python/meep.i @@ -843,7 +843,7 @@ meep::volume_list *make_volume_list(const meep::volume &v, int c, //-------------------------------------------------- %inline %{ -void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObject *fields_f, +void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObject *fields_f, meep::grid_volume *grid_volume, meep::volume *where, PyObject *frequencies, meep_geom::geom_epsilon *geps, PyObject *fields_shapes, double fd_step) { // clean the gradient array @@ -1997,7 +1997,7 @@ meep_geom::geom_epsilon* _set_materials(meep::structure * s, extra_materials); } if (set_materials) { - meep_geom::set_materials_from_geom_epsilon(s, geps, center, use_anisotropic_averaging, tol, + meep_geom::set_materials_from_geom_epsilon(s, geps, use_anisotropic_averaging, tol, maxeval,alist); } From 00d864bd5dd1cc4ab326781533055270280b9abd Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 12:28:43 -0500 Subject: [PATCH 092/155] tests need scipy and autograd --- doc/docs/Installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/docs/Installation.md b/doc/docs/Installation.md index 67003b6b8..3bc95df29 100644 --- a/doc/docs/Installation.md +++ b/doc/docs/Installation.md @@ -139,9 +139,9 @@ The first steps are: brew doctor brew install hdf5 guile fftw gsl libpng autoconf automake libtool swig ``` -If you don't have your own Python installation (e.g. via [miniforge](https://github.com/conda-forge/miniforge)), you should install `numpy` and `matplotlib`: +If you don't have your own Python installation (e.g. via [miniforge](https://github.com/conda-forge/miniforge)), you should install `numpy` and `matplotlib` and other packages used by Meep and its tests: ```sh -pip3 install numpy matplotlib +pip3 install numpy matplotlib scipy autograd ``` Now, install the Harminv, libctl, MPB, and Meep packages from source. Download [Harminv](https://github.com/NanoComp/harminv/blob/master/README.md) and, in the `harminv` directory, do: From f341e246c5f4a6cb57b5bd6fae84e2a694584cb8 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 12:33:31 -0500 Subject: [PATCH 093/155] missing sudo --- doc/docs/Installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/docs/Installation.md b/doc/docs/Installation.md index 3bc95df29..e9616b2f4 100644 --- a/doc/docs/Installation.md +++ b/doc/docs/Installation.md @@ -147,7 +147,7 @@ pip3 install numpy matplotlib scipy autograd Now, install the Harminv, libctl, MPB, and Meep packages from source. Download [Harminv](https://github.com/NanoComp/harminv/blob/master/README.md) and, in the `harminv` directory, do: ```sh -./configure CPPFLAGS="-I$(brew --prefix)/include" LDFLAGS="-L$(brew --prefix)/lib" PYTHON=python3 && make && make install +./configure CPPFLAGS="-I$(brew --prefix)/include" LDFLAGS="-L$(brew --prefix)/lib" PYTHON=python3 && make && sudo make install ``` Use the same commands for [libctl](https://libctl.readthedocs.io), [MPB](https://mpb.readthedocs.io), (optionally) [h5utils](https://github.com/NanoComp/h5utils), (optionally) [libGDSII](https://github.com/HomerReid/libGDSII), and Meep. For more detailed information, see [Build From Source](Build_From_Source.md). From 94f9094a748a444d8fa218a8c27389633e3ec878 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 12:36:05 -0500 Subject: [PATCH 094/155] parameterized package is also used for tests --- doc/docs/Installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/docs/Installation.md b/doc/docs/Installation.md index e9616b2f4..39a1a9f7c 100644 --- a/doc/docs/Installation.md +++ b/doc/docs/Installation.md @@ -141,7 +141,7 @@ brew install hdf5 guile fftw gsl libpng autoconf automake libtool swig ``` If you don't have your own Python installation (e.g. via [miniforge](https://github.com/conda-forge/miniforge)), you should install `numpy` and `matplotlib` and other packages used by Meep and its tests: ```sh -pip3 install numpy matplotlib scipy autograd +pip3 install numpy matplotlib scipy autograd parameterized ``` Now, install the Harminv, libctl, MPB, and Meep packages from source. Download [Harminv](https://github.com/NanoComp/harminv/blob/master/README.md) and, in the `harminv` directory, do: From e1beced096785b1469c5fa451e41d5ee21df1303 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 15:11:10 -0500 Subject: [PATCH 095/155] h5py and jax on mac --- doc/docs/Installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/docs/Installation.md b/doc/docs/Installation.md index 39a1a9f7c..400754422 100644 --- a/doc/docs/Installation.md +++ b/doc/docs/Installation.md @@ -141,7 +141,7 @@ brew install hdf5 guile fftw gsl libpng autoconf automake libtool swig ``` If you don't have your own Python installation (e.g. via [miniforge](https://github.com/conda-forge/miniforge)), you should install `numpy` and `matplotlib` and other packages used by Meep and its tests: ```sh -pip3 install numpy matplotlib scipy autograd parameterized +HDF5_DIR="$(brew --prefix hdf5)" pip3 install numpy matplotlib scipy autograd jax parameterized h5py ``` Now, install the Harminv, libctl, MPB, and Meep packages from source. Download [Harminv](https://github.com/NanoComp/harminv/blob/master/README.md) and, in the `harminv` directory, do: From c8047b37c6b52b7c6461aa53f56beeb2285cb169 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 15:14:25 -0500 Subject: [PATCH 096/155] note on autogen.sh for git clone --- doc/docs/Installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/docs/Installation.md b/doc/docs/Installation.md index 400754422..d3137cf6b 100644 --- a/doc/docs/Installation.md +++ b/doc/docs/Installation.md @@ -150,7 +150,7 @@ Now, install the Harminv, libctl, MPB, and Meep packages from source. Download [ ./configure CPPFLAGS="-I$(brew --prefix)/include" LDFLAGS="-L$(brew --prefix)/lib" PYTHON=python3 && make && sudo make install ``` -Use the same commands for [libctl](https://libctl.readthedocs.io), [MPB](https://mpb.readthedocs.io), (optionally) [h5utils](https://github.com/NanoComp/h5utils), (optionally) [libGDSII](https://github.com/HomerReid/libGDSII), and Meep. For more detailed information, see [Build From Source](Build_From_Source.md). +Use the same commands for [libctl](https://libctl.readthedocs.io), [MPB](https://mpb.readthedocs.io), (optionally) [h5utils](https://github.com/NanoComp/h5utils), (optionally) [libGDSII](https://github.com/HomerReid/libGDSII), and Meep. For more detailed information, see [Build From Source](Build_From_Source.md). Note that if you are installing from a `git clone` rather than from a release `.tar.gz` file, you will need to first run `sh autogen.sh`, and you should add `--enable-maintainer-mode` to the `configure` arguments. You are done, and can now run Meep (Scheme interface) just by typing `meep`. You can run `make check` in the meep directory if you want to perform a self-test. From ce3bb91019e2dfdf6f22a4dcd1eb3091a8062a29 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 15:21:27 -0500 Subject: [PATCH 097/155] xcode installation shortcut --- doc/docs/Installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/docs/Installation.md b/doc/docs/Installation.md index d3137cf6b..c1b35767a 100644 --- a/doc/docs/Installation.md +++ b/doc/docs/Installation.md @@ -131,7 +131,7 @@ Since [macOS](https://en.wikipedia.org/wiki/macOS) is, at its heart, a Unix syst The first steps are: -- Install [Xcode](https://en.wikipedia.org/wiki/Xcode), the development/compiler package from Apple, free from the [Apple Xcode web page](https://developer.apple.com/xcode/). +- Install [Xcode](https://en.wikipedia.org/wiki/Xcode), the development/compiler package from Apple: type `xcode-select --install` in the [Terminal](https://en.wikipedia.org/wiki/Terminal_(macOS)). - Install Homebrew: download from the [Homebrew site](http://brew.sh/) and follow the instructions there. - Run the following commands in the terminal to compile and install the prerequisites. This may take a while to complete because it will install lots of other stuff first From a7f78a37961b0b131b120e95a281e6b2c6fae806 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Tue, 14 Dec 2021 19:15:21 -0800 Subject: [PATCH 098/155] bug fix for get_epsilon_point and cell boundary in parallel simulation (#1849) * bug fix for get_epsilon_point and cell boundary in parallel simulation * check for six digits in test_material_grid.py because of single precision --- python/tests/test_material_grid.py | 2 +- src/monitor.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/tests/test_material_grid.py b/python/tests/test_material_grid.py index d8750987a..25b6b4b50 100644 --- a/python/tests/test_material_grid.py +++ b/python/tests/test_material_grid.py @@ -159,7 +159,7 @@ def test_subpixel_smoothing(self): def test_symmetry(self): tran_nosym = compute_transmittance(False) tran_sym = compute_transmittance(True) - self.assertAlmostEqual(tran_nosym, tran_sym) + self.assertAlmostEqual(tran_nosym, tran_sym, places=6) if __name__ == '__main__': unittest.main() diff --git a/src/monitor.cpp b/src/monitor.cpp index 6ed10205c..46082332f 100644 --- a/src/monitor.cpp +++ b/src/monitor.cpp @@ -178,7 +178,7 @@ complex fields::get_chi1inv(component c, direction d, const ivec &origlo 0); return parallel ? sum_to_all(val) : val; } - return d == component_direction(c) ? 1.0 : 0; // default to vacuum outside computational cell + return d == component_direction(c) && (parallel || am_master()) ? 1.0 : 0; // default to vacuum outside computational cell } complex fields_chunk::get_chi1inv(component c, direction d, const ivec &iloc, From 0062cf01176ac1e838002cbd355fbbd2bfdf7e33 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Mon, 20 Dec 2021 11:41:11 -0800 Subject: [PATCH 099/155] unit test for conductivity (#1857) * unit test for conductivity * describe in the docs how to model the attenutation coefficient using conductivity * Update python/tests/test_conductivity.py Co-authored-by: Steven G. Johnson --- doc/docs/Materials.md | 8 ++- python/Makefile.am | 3 +- python/tests/test_conductivity.py | 100 ++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 python/tests/test_conductivity.py diff --git a/doc/docs/Materials.md b/doc/docs/Materials.md index 226af4700..79b1912af 100644 --- a/doc/docs/Materials.md +++ b/doc/docs/Materials.md @@ -88,13 +88,15 @@ Meep can keep track of this energy for the Lorentzian polarizability terms but n Conductivity and Complex ε -------------------------- -Often, you only care about the absorption loss in a narrow bandwidth, where you just want to set the imaginary part of ε (or μ) to some known experimental value, in the same way that you often just care about setting a dispersionless real ε that is the correct value in your bandwidth of interest. +Often, you only care about the absorption loss in a narrow bandwidth, where you just want to set the imaginary part of $\varepsilon$ (or $\mu$) to some known experimental value, in the same way that you often just care about setting a dispersionless real $\varepsilon$ that is the correct value in your bandwidth of interest. One approach to this problem would be allowing you to specify a constant, frequency-independent, imaginary part of $\varepsilon$, but this has the disadvantage of requiring the simulation to employ complex fields which double the memory and time requirements, and also tends to be numerically unstable. Instead, the approach in Meep is for you to set the conductivity $\sigma_D$ (or $\sigma_B$ for an imaginary part of $\mu$), chosen so that $\mathrm{Im}\, \varepsilon = \varepsilon_\infty \sigma_D / \omega$ is the correct value at your frequency $\omega$ of interest. Note that, in Meep, you specify $f = \omega/2\pi$ instead of $\omega$ for the frequency, however, so you need to include the factor of $2\pi$ when computing the corresponding imaginary part of $\varepsilon$. Conductivities can be implemented with purely real fields, so they are not nearly as expensive as implementing a frequency-independent complex $\varepsilon$ or $\mu$. -For example, suppose you want to simulate a medium with $\varepsilon = 3.4 + 0.101i$ at a frequency 0.42 (in your Meep units), and you only care about the material in a narrow bandwidth around this frequency (i.e. you don't need to simulate the full experimental frequency-dependent permittivity). Then, in Meep, you could use `meep.Medium(epsilon=3.4, D_conductivity=2*math.pi*0.42*0.101/3.4)` in Python or `(make medium (epsilon 3.4) (D-conductivity (* 2 pi 0.42 0.101 (/ 3.4))))` in Scheme; i.e. $\varepsilon_\infty = \mathrm{Re}[\varepsilon] = 3.4$ and $\sigma_D = \omega \, \mathrm{Im}[\varepsilon / \varepsilon_\infty] = (2\pi \, 0.42) \, 0.101 / 3.4$. +For example, suppose you want to simulate a medium with $\varepsilon = 3.4 + 0.101i$ at a frequency 0.42 (in your Meep units), and you only care about the material in a narrow bandwidth around this frequency (i.e. you don't need to simulate the full experimental frequency-dependent permittivity). Then, in Meep, you could use `meep.Medium(epsilon=3.4, D_conductivity=2*math.pi*0.42*0.101/3.4)` in Python or `(make medium (epsilon 3.4) (D-conductivity (* 2 pi 0.42 0.101 (/ 3.4))))` in Scheme; i.e. $\varepsilon_\infty = \mathrm{Re}[\varepsilon] = 3.4$ and $\sigma_D = \omega \, \mathrm{Im}[\varepsilon] / \varepsilon_\infty = (2\pi \, 0.42) \, 0.101 / 3.4$. -**Note**: the "conductivity" in Meep is slightly different from the conductivity you might find in a textbook, because for computational convenience it appears as $\sigma_D \mathbf{D}$ in our Maxwell equations rather than the more-conventional $\sigma \mathbf{E}$; this just means that our definition is different from the usual electric conductivity by a factor of ε. Also, just as Meep uses the dimensionless relative permittivity for ε, it uses nondimensionalized units of 1/*a* (where *a* is your unit of distance) for the conductivities $\sigma_{D,B}$. If you have the electric conductivity $\sigma$ in SI units and want to convert to $\sigma_D$ in Meep units, you can simply use the formula: $\sigma_D = (a/c) \sigma / \varepsilon_r \varepsilon_0$ where *a* is your unit of distance in meters, *c* is the vacuum speed of light in m/s, $\varepsilon_0$ is the SI vacuum permittivity, and $\varepsilon_r$ is the real relative permittivity. +You can also use the $\sigma_D$ feature to model the [attenuation coefficient](https://en.wikipedia.org/wiki/Attenuation_coefficient) $\alpha$ (units of e.g. dB/cm) obtained from experimental measurements (i.e., ellipsometry). This involves first [converting $\alpha$ into a complex refractive index](https://en.wikipedia.org/wiki/Mathematical_descriptions_of_opacity#Complex_refractive_index) (which is then converted into a complex permittivity) with imaginary part given by $\lambda_0\alpha/(4\pi)$ where $\lambda_0$ is the vacuum wavelength. + +**Note**: the "conductivity" in Meep is slightly different from the conductivity you might find in a textbook, because for computational convenience it appears as $\sigma_D \mathbf{D}$ in our Maxwell equations rather than the more-conventional $\sigma \mathbf{E}$; this just means that our definition is different from the usual electric conductivity by a factor of $\varepsilon$. Also, just as Meep uses the dimensionless relative permittivity for $\varepsilon$, it uses nondimensionalized units of 1/*a* (where *a* is your unit of distance) for the conductivities $\sigma_{D,B}$. If you have the electric conductivity $\sigma$ in SI units and want to convert to $\sigma_D$ in Meep units, you can simply use the formula: $\sigma_D = (a/c) \sigma / \varepsilon_r \varepsilon_0$ where *a* is your unit of distance in meters, *c* is the vacuum speed of light in m/s, $\varepsilon_0$ is the SI vacuum permittivity, and $\varepsilon_r$ is the real relative permittivity. Nonlinearity ------------ diff --git a/python/Makefile.am b/python/Makefile.am index 793eef539..cac075de8 100644 --- a/python/Makefile.am +++ b/python/Makefile.am @@ -43,13 +43,14 @@ TESTS = \ $(TEST_DIR)/test_antenna_radiation.py \ $(TEST_DIR)/test_array_metadata.py \ $(TEST_DIR)/test_bend_flux.py \ - $(TEST_DIR)/test_binary_partition_utils.py \ + $(TEST_DIR)/test_binary_partition_utils.py \ $(BINARY_GRATING_TEST) \ $(TEST_DIR)/test_cavity_arrayslice.py \ $(TEST_DIR)/test_cavity_farfield.py \ $(TEST_DIR)/test_chunk_balancer.py \ $(TEST_DIR)/test_chunk_layout.py \ $(TEST_DIR)/test_chunks.py \ + $(TEST_DIR)/test_conductivity.py \ $(TEST_DIR)/test_cyl_ellipsoid.py \ $(TEST_DIR)/test_dft_energy.py \ $(TEST_DIR)/test_dft_fields.py \ diff --git a/python/tests/test_conductivity.py b/python/tests/test_conductivity.py new file mode 100644 index 000000000..2aa9a7616 --- /dev/null +++ b/python/tests/test_conductivity.py @@ -0,0 +1,100 @@ +import unittest +import numpy as np +import meep as mp + +dB_cm_to_dB_um = 1e-4 + +class TestConductivity(unittest.TestCase): + + def wvg_flux(self, res, att_coeff): + """ + Computes the Poynting flux in a single-mode waveguide at two + locations (5 and 10 μm) downstream from the source given the + grid resolution res (pixels/μm) and material attenuation + coefficient att_coeff (dB/cm). + """ + + cell_size = mp.Vector3(14.,14.) + + pml_layers = [mp.PML(thickness=2.)] + + w = 1. # width of waveguide + + fsrc = 0.15 # frequency (in vacuum) + + # note: MPB can only compute modes of lossless material systems. + # The imaginary part of ε is ignored and the launched + # waveguide mode is therefore inaccurate. For small values + # of imag(ε) (which is proportional to att_coeff), this + # inaccuracy tends to be insignificant. + sources = [mp.EigenModeSource(src=mp.GaussianSource(fsrc,fwidth=0.2*fsrc), + center=mp.Vector3(-5.), + size=mp.Vector3(y=10.), + eig_parity=mp.EVEN_Y+mp.ODD_Z)] + + # ref: https://en.wikipedia.org/wiki/Mathematical_descriptions_of_opacity + # Note that this is the loss of a planewave, which is only approximately + # the loss of a waveguide mode. In principle, we could compute the latter + # semi-analytically if we wanted to run this unit test to greater accuracy + # (e.g. to test convergence with resolution). + n_eff = np.sqrt(12.) + 1j * (1/fsrc) * (dB_cm_to_dB_um * att_coeff) / (4 * np.pi) + eps_eff = n_eff * n_eff + # ref: https://meep.readthedocs.io/en/latest/Materials/#conductivity-and-complex + sigma_D = 2 * np.pi * fsrc * np.imag(eps_eff) / np.real(eps_eff) + + geometry = [mp.Block(center=mp.Vector3(), + size=mp.Vector3(mp.inf,w,mp.inf), + material=mp.Medium(epsilon=np.real(eps_eff), + D_conductivity=sigma_D))] + + sim = mp.Simulation(cell_size=cell_size, + resolution=res, + boundary_layers=pml_layers, + sources=sources, + geometry=geometry, + symmetries=[mp.Mirror(mp.Y)]) + + tran1 = sim.add_flux(fsrc, + 0, + 1, + mp.FluxRegion(center=mp.Vector3(x=0.), size=mp.Vector3(y=10.))) + + tran2 = sim.add_flux(fsrc, + 0, + 1, + mp.FluxRegion(center=mp.Vector3(x=5.), size=mp.Vector3(y=10.))) + + sim.run(until_after_sources=20) + + return mp.get_fluxes(tran1)[0], mp.get_fluxes(tran2)[0] + + + def test_conductivity(self): + res = 25. # pixels/μm + + # compute the incident flux for a lossless waveguide + incident_flux1, incident_flux2 = self.wvg_flux(res, 0.) + self.assertAlmostEqual(incident_flux1/incident_flux2, 1., places=2) + print("incident_flux:, {} (measured), 1.0 (expected)".format(incident_flux2/incident_flux1)) + + # compute the flux for a lossy waveguide + att_coeff = 37.46 # dB/cm + attenuated_flux1, attenuated_flux2 = self.wvg_flux(res, att_coeff) + + L1 = 5. + expected_att1 = np.exp(-att_coeff * dB_cm_to_dB_um * L1) + self.assertAlmostEqual(attenuated_flux1/incident_flux2, expected_att1, places=2) + print("flux:, {}, {:.6f} (measured), {:.6f} (expected)".format(L1, + attenuated_flux1/incident_flux2, + expected_att1)) + + L2 = 10. + expected_att2 = np.exp(-att_coeff * dB_cm_to_dB_um * L2) + self.assertAlmostEqual(attenuated_flux2/incident_flux2, expected_att2, places=2) + print("flux:, {}, {:.6f} (measured), {:.6f} (expected)".format(L2, + attenuated_flux2/incident_flux2, + expected_att2)) + + +if __name__ == '__main__': + unittest.main() From f6b30e634b70c119120a63b27b8e806fb9c40b44 Mon Sep 17 00:00:00 2001 From: Andreas Hoenselaar Date: Mon, 20 Dec 2021 14:41:05 -0800 Subject: [PATCH 100/155] Fix the failure message for absorber 1D test (#1859) --- tests/absorber-1d-ll.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/absorber-1d-ll.cpp b/tests/absorber-1d-ll.cpp index 345f9791c..7c052052f 100644 --- a/tests/absorber-1d-ll.cpp +++ b/tests/absorber-1d-ll.cpp @@ -116,7 +116,7 @@ int main(int argc, char *argv[]) { fabs(fFinal - fFinal_ref) > 1.0e-6 * fabs(fFinal_ref)) { master_printf("{f50, tFinal, fFinal}={%e,%e,%e}\n", f50, tFinal, fFinal); master_printf(" should be:\n"); - master_printf("{f50, tFinal, fFinal}={%e,%e,%e}\n", f50, tFinal, fFinal); + master_printf("{f50, tFinal, fFinal}={%e,%e,%e}\n", f50_ref, tFinal_ref, fFinal_ref); meep::abort("Test failed."); } else if (verbose) From e505d530cc706a1554ce0894e7e1d573c1a2773b Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Tue, 21 Dec 2021 09:40:02 -0800 Subject: [PATCH 101/155] add missing fixed field phase to MPB unit test (#1860) --- python/tests/test_mpb.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/python/tests/test_mpb.py b/python/tests/test_mpb.py index 367e15675..ef6e3a4c4 100644 --- a/python/tests/test_mpb.py +++ b/python/tests/test_mpb.py @@ -408,6 +408,7 @@ def test_output_field_to_file(self): def test_compute_field_energy(self): ms = self.init_solver() ms.run_te() + mpb.fix_hfield_phase(ms, 8) ms.get_dfield(8) field_pt = ms.get_field_point(mp.Vector3(0.5, 0.5)) bloch_field_pt = ms.get_bloch_field_point(mp.Vector3(0.5, 0.5)) @@ -701,8 +702,10 @@ def test_diamond(self): def get_dpwr(ms, band): dpwr.append(ms.get_dpwr(band)) - ms.run(mpb.output_at_kpoint(mp.Vector3(0, 0.625, 0.375), mpb.fix_dfield_phase, - mpb.output_dpwr, get_dpwr)) + ms.run(mpb.output_at_kpoint(mp.Vector3(0, 0.625, 0.375), + mpb.fix_dfield_phase, + mpb.output_dpwr, + get_dpwr)) expected_brd = [ ((0.0, mp.Vector3(0.0, 0.0, 0.0)), @@ -812,7 +815,8 @@ def test_line_defect(self): ms.tolerance = 1e-12 ms.run_tm(mpb.output_at_kpoint(k_points[len(k_points) // 2]), - mpb.fix_efield_phase, mpb.output_efield_z) + mpb.fix_efield_phase, + mpb.output_efield_z) ref_fn = 'line-defect-e.k04.b12.z.tm.h5' ref_path = os.path.join(self.data_dir, ref_fn) @@ -973,8 +977,9 @@ def test_tri_rods(self): ms.tolerance = 1e-12 ms.filename_prefix = self.filename_prefix - ms.run_tm(mpb.output_at_kpoint(mp.Vector3(1 / -3, 1 / 3), mpb.fix_efield_phase, - mpb.output_efield_z)) + ms.run_tm(mpb.output_at_kpoint(mp.Vector3(1 / -3, 1 / 3), + mpb.fix_efield_phase, + mpb.output_efield_z)) ref_fn = 'tri-rods-e.k11.b08.z.tm.h5' ref_path = os.path.join(self.data_dir, ref_fn) From c48a4a2e0de38e77351393398ef08abbe7cdd4ea Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Thu, 23 Dec 2021 19:20:27 -0800 Subject: [PATCH 102/155] fix memory leak in array-slice-ll.cpp (#1865) * fix memory leak in array-slice-ll.cpp * reinstate line break --- tests/array-slice-ll.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/array-slice-ll.cpp b/tests/array-slice-ll.cpp index ea4e9aded..e13967aad 100644 --- a/tests/array-slice-ll.cpp +++ b/tests/array-slice-ll.cpp @@ -248,5 +248,9 @@ int main(int argc, char *argv[]) { }; // if (write_files) ... else ... + for (int n = 0; n < no; n++) { + geometric_object_destroy(objects[n]); + } + return 0; } From 405eb334361414fc7beb8005b6e683d4eedea7a5 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Fri, 24 Dec 2021 12:52:34 -0800 Subject: [PATCH 103/155] fix memory leak in cyl-ellipsoid-ll.cpp (#1866) --- tests/cyl-ellipsoid-ll.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/cyl-ellipsoid-ll.cpp b/tests/cyl-ellipsoid-ll.cpp index 275a53a0d..c6c5ca597 100644 --- a/tests/cyl-ellipsoid-ll.cpp +++ b/tests/cyl-ellipsoid-ll.cpp @@ -129,7 +129,11 @@ int main(int argc, char *argv[]) { // (make ellipsoid (center 0 0 0) (size 1 2 infinity) // (material air)))) double n = 3.5; // index of refraction - meep_geom::material_type dielectric = meep_geom::make_dielectric(n * n); + auto material_deleter = [](meep_geom::material_data *m) { + meep_geom::material_free(m); + }; + std::unique_ptr dielectric( + meep_geom::make_dielectric(n * n), material_deleter); geometric_object objects[2]; vector3 center = {0.0, 0.0, 0.0}; double radius = 3.0; @@ -138,7 +142,7 @@ int main(int argc, char *argv[]) { vector3 yhat = {0.0, 1.0, 0.0}; vector3 zhat = {0.0, 0.0, 1.0}; vector3 size = {1.0, 2.0, 1.0e20}; - objects[0] = make_cylinder(dielectric, center, radius, height, zhat); + objects[0] = make_cylinder(dielectric.get(), center, radius, height, zhat); objects[1] = make_ellipsoid(meep_geom::vacuum, center, xhat, yhat, zhat, size); geometric_object_list g = {2, objects}; meep_geom::set_materials_from_geometry(&the_structure, g); @@ -191,5 +195,9 @@ int main(int argc, char *argv[]) { meep::abort("field output error in cyl-ellipsoid-ll"); }; + for (int n = 0; n < 2; n++) { + geometric_object_destroy(objects[n]); + } + return 0; } From a886a543895082b61d8bafd7f979a0ac6fe7d6d6 Mon Sep 17 00:00:00 2001 From: simbilod <46427609+simbilod@users.noreply.github.com> Date: Sun, 26 Dec 2021 17:41:54 -0500 Subject: [PATCH 104/155] level parameter for contour plot calls epsilon data (#1869) --- python/visualization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/visualization.py b/python/visualization.py index 1e17f6a08..647882398 100644 --- a/python/visualization.py +++ b/python/visualization.py @@ -402,7 +402,7 @@ def plot_eps(sim, ax, output_plane=None, eps_parameters=None, frequency=None): if mp.am_master(): if eps_parameters['contour']: - ax.contour(eps_data, 0, colors='black', origin='upper', extent=extent, linewidths=eps_parameters['contour_linewidth']) + ax.contour(eps_data, 0, levels=np.unique(eps_data), colors='black', origin='upper', extent=extent, linewidths=eps_parameters['contour_linewidth']) else: ax.imshow(eps_data, extent=extent, **filter_dict(eps_parameters, ax.imshow)) ax.set_xlabel(xlabel) From 4117289ad4b44ffbec6977076f53e79b1d1de0df Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Tue, 28 Dec 2021 15:16:23 -0800 Subject: [PATCH 105/155] fix heap buffer overflow error for update E from D in cylindrical coordinates (#1871) --- src/update_eh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/update_eh.cpp b/src/update_eh.cpp index e59a25beb..efd7d3ffe 100644 --- a/src/update_eh.cpp +++ b/src/update_eh.cpp @@ -189,7 +189,7 @@ bool fields_chunk::update_eh(field_type ft, bool skip_w_components) { } if (f[ec][cmp] != f[dc][cmp]) - STEP_UPDATE_EDHB(f[ec][cmp], ec, gv, gvs_eh[ft][i].little_owned_corner(ec), gvs_eh[ft][i].big_corner(), + STEP_UPDATE_EDHB(f[ec][cmp], ec, gv, gvs_eh[ft][i].little_owned_corner0(ec), gvs_eh[ft][i].big_corner(), dmp[dc][cmp], dmp[dc_1][cmp], dmp[dc_2][cmp], s->chi1inv[ec][d_ec], dmp[dc_1][cmp] ? s->chi1inv[ec][d_1] : NULL, dmp[dc_2][cmp] ? s->chi1inv[ec][d_2] : NULL, s_ec, s_1, s_2, s->chi2[ec], From cab8df7f3877d4171392d5cddc798cfca38dfddd Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Thu, 30 Dec 2021 14:57:08 -0500 Subject: [PATCH 106/155] Add cylindrical coordinates support for `plot2d` (#1873) * add visualization support for plot2d * bug fix with cartesian plotting --- python/visualization.py | 56 ++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/python/visualization.py b/python/visualization.py index 647882398..f57621f65 100644 --- a/python/visualization.py +++ b/python/visualization.py @@ -189,14 +189,19 @@ def get_2D_dimensions(sim, output_plane): elif sim.output_volume: plane_center, plane_size = mp.get_center_and_size(sim.output_volume) else: - plane_center, plane_size = (sim.geometry_center, sim.cell_size) + if (sim.dimensions == mp.CYLINDRICAL) or sim.is_cylindrical: + plane_center, plane_size = (sim.geometry_center+mp.Vector3(sim.cell_size.x/2), sim.cell_size) + else: + plane_center, plane_size = (sim.geometry_center, sim.cell_size) plane_volume = Volume(center=plane_center,size=plane_size) if plane_size.x != 0 and plane_size.y != 0 and plane_size.z != 0: raise ValueError("Plane volume must be 2D (a plane).") - - check_volume = Volume(center=sim.geometry_center, size=sim.cell_size) - + if (sim.dimensions == mp.CYLINDRICAL) or sim.is_cylindrical: + center = sim.geometry_center+mp.Vector3(sim.cell_size.x/2) + check_volume = mp.Volume(center=center, size = sim.cell_size) + else: + check_volume = Volume(center=sim.geometry_center, size=sim.cell_size) vertices = intersect_volume_volume(check_volume, plane_volume) if len(vertices) == 0: @@ -382,7 +387,10 @@ def plot_eps(sim, ax, output_plane=None, eps_parameters=None, frequency=None): elif sim_size.y == 0: # Plot x on x axis, z on y axis (XZ plane) extent = [xmin, xmax, zmin, zmax] - xlabel = 'X' + if (sim.dimensions == mp.CYLINDRICAL) or sim.is_cylindrical: + xlabel = 'R' + else: + xlabel = "X" ylabel = 'Z' xtics = np.linspace(xmin, xmax, Nx) ytics = np.array([sim_center.y]) @@ -414,13 +422,13 @@ def plot_boundaries(sim, ax, output_plane=None, boundary_parameters=None): # consolidate plotting parameters boundary_parameters = default_boundary_parameters if boundary_parameters is None else dict(default_boundary_parameters, **boundary_parameters) - def get_boundary_volumes(thickness, direction, side): + def get_boundary_volumes(thickness, direction, side, cylindrical=False): from meep.simulation import Volume thickness = boundary.thickness # Get domain measurements - sim_center, sim_size = (sim.geometry_center, sim.cell_size) + sim_center, sim_size = get_2D_dimensions(sim, output_plane) xmin = sim_center.x - sim_size.x/2 xmax = sim_center.x + sim_size.x/2 @@ -434,22 +442,22 @@ def get_boundary_volumes(thickness, direction, side): cell_z = sim.cell_size.z if direction == mp.X and side == mp.Low: - return Volume(center=Vector3(xmin+thickness/2,sim.geometry_center.y,sim.geometry_center.z), + return Volume(center=Vector3(xmin+thickness/2,sim_center.y,sim_center.z), size=Vector3(thickness,cell_y,cell_z)) elif direction == mp.X and side == mp.High: - return Volume(center=Vector3(xmax-thickness/2,sim.geometry_center.y,sim.geometry_center.z), + return Volume(center=Vector3(xmax-thickness/2,sim_center.y,sim_center.z), size=Vector3(thickness,cell_y,cell_z)) elif direction == mp.Y and side == mp.Low: - return Volume(center=Vector3(sim.geometry_center.x,ymin+thickness/2,sim.geometry_center.z), + return Volume(center=Vector3(sim_center.x,ymin+thickness/2,sim_center.z), size=Vector3(cell_x,thickness,cell_z)) elif direction == mp.Y and side == mp.High: - return Volume(center=Vector3(sim.geometry_center.x,ymax-thickness/2,sim.geometry_center.z), + return Volume(center=Vector3(sim_center.x,ymax-thickness/2,sim_center.z), size=Vector3(cell_x,thickness,cell_z)) elif direction == mp.Z and side == mp.Low: - return Volume(center=Vector3(sim.geometry_center.x,sim.geometry_center.y,zmin+thickness/2), + return Volume(center=Vector3(sim_center.x,sim_center.y,zmin+thickness/2), size=Vector3(cell_x,cell_y,thickness)) elif direction == mp.Z and side == mp.High: - return Volume(center=Vector3(sim.geometry_center.x,sim.geometry_center.y,zmax-thickness/2), + return Volume(center=Vector3(sim_center.x,sim_center.y,zmax-thickness/2), size=Vector3(cell_x,cell_y,thickness)) else: raise ValueError("Invalid boundary type") @@ -460,6 +468,8 @@ def get_boundary_volumes(thickness, direction, side): if boundary.direction == mp.ALL and boundary.side == mp.ALL: if sim.dimensions == 1: dims = [mp.X] + elif sim.dimensions == mp.CYLINDRICAL or sim.is_cylindrical: + dims = [mp.X, mp.Z] elif sim.dimensions == 2: dims = [mp.X, mp.Y] elif sim.dimensions == 3: @@ -467,15 +477,21 @@ def get_boundary_volumes(thickness, direction, side): else: raise ValueError("Invalid simulation dimensions") for permutation in itertools.product(dims, [mp.Low, mp.High]): + if (permutation[0] == mp.X) and (permutation[1] == mp.Low): + continue vol = get_boundary_volumes(boundary.thickness,*permutation) ax = plot_volume(sim,ax,vol,output_plane,plotting_parameters=boundary_parameters) # two sides are the same elif boundary.side == mp.ALL: for side in [mp.Low, mp.High]: + if (boundary.direction == mp.X) and (side == mp.Low): + continue vol = get_boundary_volumes(boundary.thickness,boundary.direction,side) ax = plot_volume(sim,ax,vol,output_plane,plotting_parameters=boundary_parameters) # only one side - else: + else: + if (boundary.direction == mp.X) and (boundary.side == mp.Low): + continue vol = get_boundary_volumes(boundary.thickness,boundary.direction,boundary.side) ax = plot_volume(sim,ax,vol,output_plane,plotting_parameters=boundary_parameters) return ax @@ -517,7 +533,7 @@ def plot_fields(sim, ax=None, fields=None, output_plane=None, field_parameters=N field_parameters = default_field_parameters if field_parameters is None else dict(default_field_parameters, **field_parameters) # user specifies a field component - if fields in [mp.Ex, mp.Ey, mp.Ez, mp.Hx, mp.Hy, mp.Hz]: + if fields in [mp.Ex, mp.Ey, mp.Ez, mp.Er, mp.Ep, mp.Hx, mp.Hy, mp.Hz]: # Get domain measurements sim_center, sim_size = get_2D_dimensions(sim, output_plane) @@ -539,7 +555,10 @@ def plot_fields(sim, ax=None, fields=None, output_plane=None, field_parameters=N elif sim_size.y == 0: # Plot x on x axis, z on y axis (XZ plane) extent = [xmin, xmax, zmin, zmax] - xlabel = 'X' + if (sim.dimensions == mp.CYLINDRICAL) or sim.is_cylindrical: + xlabel = 'R' + else: + xlabel = "X" ylabel = 'Z' elif sim_size.z == 0: # Plot x on x axis, y on y axis (XY plane) @@ -552,14 +571,15 @@ def plot_fields(sim, ax=None, fields=None, output_plane=None, field_parameters=N fields = field_parameters['post_process'](fields) + fields = np.flipud(fields) if ((sim.dimensions == mp.CYLINDRICAL) or sim.is_cylindrical) else np.rot90(fields) # Either plot the field, or return the array if ax: if mp.am_master(): - ax.imshow(np.rot90(fields), extent=extent, **filter_dict(field_parameters,ax.imshow)) + ax.imshow(fields, extent=extent, **filter_dict(field_parameters,ax.imshow)) return ax else: - return np.rot90(fields) + return fields return ax def plot2D(sim, ax=None, output_plane=None, fields=None, labels=False, From 52bf1fd903b3a1e745e9dad513265ba7bad2e8f3 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Thu, 30 Dec 2021 19:35:47 -0800 Subject: [PATCH 107/155] fix near-field monitor position in cylindrical coordinate tutorial (#1874) --- .../Cylindrical_Coordinates.md | 25 +++++++++++++++---- python/examples/zone_plate.py | 25 +++++++++++++++---- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/doc/docs/Python_Tutorials/Cylindrical_Coordinates.md b/doc/docs/Python_Tutorials/Cylindrical_Coordinates.md index 6f558196f..ff081b8c3 100644 --- a/doc/docs/Python_Tutorials/Cylindrical_Coordinates.md +++ b/doc/docs/Python_Tutorials/Cylindrical_Coordinates.md @@ -536,14 +536,29 @@ sim = mp.Simulation(cell_size=cell_size, m=-1) ## near-field monitor -n2f_obj = sim.add_near2far(frq_cen, 0, 1, - mp.Near2FarRegion(center=mp.Vector3(0.5*(sr-dpml),0,0.5*sz-dpml),size=mp.Vector3(sr-dpml)), - mp.Near2FarRegion(center=mp.Vector3(sr-dpml,0,0.5*sz-0.5*(dsub+zh+dpad)),size=mp.Vector3(z=dsub+zh+dpad))) +n2f_obj = sim.add_near2far(frq_cen, + 0, + 1, + mp.Near2FarRegion(center=mp.Vector3(0.5*(sr-dpml),0,0.5*sz-dpml), + size=mp.Vector3(sr-dpml)), + mp.Near2FarRegion(center=mp.Vector3(sr-dpml,0,0.5*sz-dpml-0.5*(dsub+zh+dpad)), + size=mp.Vector3(z=dsub+zh+dpad))) + +sim.plot2D() +if mp.am_master(): + plt.savefig("zone_plate_epsilon.png",bbox_inches='tight',dpi=150) sim.run(until_after_sources=100) -ff_r = sim.get_farfields(n2f_obj, ff_res, center=mp.Vector3(0.5*(sr-dpml),0,-0.5*sz+dpml+dsub+zh+focal_length),size=mp.Vector3(sr-dpml)) -ff_z = sim.get_farfields(n2f_obj, ff_res, center=mp.Vector3(z=-0.5*sz+dpml+dsub+zh+focal_length),size=mp.Vector3(z=spot_length)) +ff_r = sim.get_farfields(n2f_obj, + ff_res, + center=mp.Vector3(0.5*(sr-dpml),0,-0.5*sz+dpml+dsub+zh+focal_length), + size=mp.Vector3(sr-dpml)) + +ff_z = sim.get_farfields(n2f_obj, + ff_res, + center=mp.Vector3(z=-0.5*sz+dpml+dsub+zh+focal_length), + size=mp.Vector3(z=spot_length)) E2_r = np.absolute(ff_r['Ex'])**2+np.absolute(ff_r['Ey'])**2+np.absolute(ff_r['Ez'])**2 E2_z = np.absolute(ff_z['Ex'])**2+np.absolute(ff_z['Ey'])**2+np.absolute(ff_z['Ez'])**2 diff --git a/python/examples/zone_plate.py b/python/examples/zone_plate.py index 11cca4ec3..9defcac9f 100644 --- a/python/examples/zone_plate.py +++ b/python/examples/zone_plate.py @@ -58,14 +58,29 @@ m=-1) ## near-field monitor -n2f_obj = sim.add_near2far(frq_cen, 0, 1, - mp.Near2FarRegion(center=mp.Vector3(0.5*(sr-dpml),0,0.5*sz-dpml),size=mp.Vector3(sr-dpml)), - mp.Near2FarRegion(center=mp.Vector3(sr-dpml,0,0.5*sz-0.5*(dsub+zh+dpad)),size=mp.Vector3(z=dsub+zh+dpad))) +n2f_obj = sim.add_near2far(frq_cen, + 0, + 1, + mp.Near2FarRegion(center=mp.Vector3(0.5*(sr-dpml),0,0.5*sz-dpml), + size=mp.Vector3(sr-dpml)), + mp.Near2FarRegion(center=mp.Vector3(sr-dpml,0,0.5*sz-dpml-0.5*(dsub+zh+dpad)), + size=mp.Vector3(z=dsub+zh+dpad))) + +sim.plot2D() +if mp.am_master(): + plt.savefig("zone_plate_epsilon.png",bbox_inches='tight',dpi=150) sim.run(until_after_sources=100) -ff_r = sim.get_farfields(n2f_obj, ff_res, center=mp.Vector3(0.5*(sr-dpml),0,-0.5*sz+dpml+dsub+zh+focal_length),size=mp.Vector3(sr-dpml)) -ff_z = sim.get_farfields(n2f_obj, ff_res, center=mp.Vector3(z=-0.5*sz+dpml+dsub+zh+focal_length),size=mp.Vector3(z=spot_length)) +ff_r = sim.get_farfields(n2f_obj, + ff_res, + center=mp.Vector3(0.5*(sr-dpml),0,-0.5*sz+dpml+dsub+zh+focal_length), + size=mp.Vector3(sr-dpml)) + +ff_z = sim.get_farfields(n2f_obj, + ff_res, + center=mp.Vector3(z=-0.5*sz+dpml+dsub+zh+focal_length), + size=mp.Vector3(z=spot_length)) E2_r = np.absolute(ff_r['Ex'])**2+np.absolute(ff_r['Ey'])**2+np.absolute(ff_r['Ez'])**2 E2_z = np.absolute(ff_z['Ex'])**2+np.absolute(ff_z['Ey'])**2+np.absolute(ff_z['Ez'])**2 From 61034a68fe68460d025e43244a2ec89fb6ec2c3c Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Fri, 31 Dec 2021 18:41:10 -0800 Subject: [PATCH 108/155] fix memory leaks in structure and fields load during checkpointing (#1872) * fix memory leaks in structure and fields load during checkpointing * delete the chi1inv and fields array if it exists and reallocate * in unit test, set gaussian source cutoff to 0 due to off-by-1 timestep counter bug * remove cutoff=0 from unit tests * lazily allocate H only if B is not NULL * allocate fields array for H in PML region --- src/fields_dump.cpp | 9 +++++++-- src/structure_dump.cpp | 2 +- tests/dump_load.cpp | 1 - 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/fields_dump.cpp b/src/fields_dump.cpp index 6fce54e2c..9ccc84e01 100644 --- a/src/fields_dump.cpp +++ b/src/fields_dump.cpp @@ -184,12 +184,17 @@ void fields::load_fields_chunk_field(h5file *h5f, bool single_parallel_file, size_t n = num_f[(chunk_i * NUM_FIELD_COMPONENTS + c) * 2 + d]; realnum **f = field_ptr_getter(chunks[i], c, d); if (n == 0) { - delete[] * f; + delete[] *f; *f = NULL; } else { if (n != ntot) meep::abort("grid size mismatch %zd vs %zd in fields::load", n, ntot); - *f = new realnum[ntot]; + // here we need to allocate the fields array for H in the PML region + // because of H = B in fields_chunk::alloc_f whereby H is lazily + // allocated in fields_chunk::update_eh during the first timestep + const direction d_c = component_direction(c); + if (!(*f) || (*f && is_magnetic(component(c)) && chunks[i]->s->sigsize[d_c] > 1)) + *f = new realnum[ntot]; my_ntot += ntot; } } diff --git a/src/structure_dump.cpp b/src/structure_dump.cpp index d0fcb91a5..e12cc88d0 100644 --- a/src/structure_dump.cpp +++ b/src/structure_dump.cpp @@ -604,7 +604,7 @@ void structure::load(const char *filename, bool single_parallel_file) { } else { if (n != ntot) meep::abort("grid size mismatch %zd vs %zd in structure::load", n, ntot); - chunks[i]->chi1inv[c][d] = new realnum[ntot]; + if (!chunks[i]->chi1inv[c][d]) chunks[i]->chi1inv[c][d] = new realnum[ntot]; my_ntot += ntot; } } diff --git a/tests/dump_load.cpp b/tests/dump_load.cpp index 78685a4e9..fef2006fd 100644 --- a/tests/dump_load.cpp +++ b/tests/dump_load.cpp @@ -169,7 +169,6 @@ int test_periodic(double eps(const vec &), int splitting, const char *tmpdir) { double ttot = 17.0; grid_volume gv = vol3d(1.5, 0.5, 1.0, a); - structure s1(gv, eps); structure s(gv, eps, no_pml(), identity(), splitting); std::string filename_prefix = From 8624c915ffebcc99486a658e4bce53463146e014 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Sun, 2 Jan 2022 11:59:26 -0800 Subject: [PATCH 109/155] fix two memory leaks in geom_epsilon class (#1877) * fix two memory leaks in geom_epsilon class * delete global variable default_material at the end of unit tests * add unset_default_material function to class meep_geom --- src/meepgeom.cpp | 24 ++++++++++++++++-------- src/meepgeom.hpp | 1 + tests/array-slice-ll.cpp | 1 + tests/cyl-ellipsoid-ll.cpp | 1 + 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index 8b9fa157c..12aa15933 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -43,6 +43,13 @@ void set_default_material(material_type _default_material) { } } +void unset_default_material(void) { + if (default_material != NULL) { + material_free((material_type)default_material); + default_material = NULL; + } +} + bool susceptibility_equal(const susceptibility &s1, const susceptibility &s2) { return (vector3_equal(s1.sigma_diag, s2.sigma_diag) && vector3_equal(s1.sigma_offdiag, s2.sigma_offdiag) && vector3_equal(s1.bias, s2.bias) && @@ -761,7 +768,8 @@ void geom_epsilon::set_volume(const meep::volume &v) { unset_volume(); geom_box box = gv2box(v); - restricted_tree = create_geom_box_tree0(geometry, box); + if (!restricted_tree) + restricted_tree = create_geom_box_tree0(geometry, box); } static void material_epsmu(meep::field_type ft, material_type material, symm_matrix *epsmu, @@ -1918,8 +1926,8 @@ void add_absorbing_layer(absorber_list alist, double thickness, int direction, i /* create a geom_epsilon object that can persist if needed */ geom_epsilon* make_geom_epsilon(meep::structure *s, geometric_object_list *g, vector3 center, - bool _ensure_periodicity, material_type _default_material, - material_type_list extra_materials) { + bool _ensure_periodicity, material_type _default_material, + material_type_list extra_materials) { // set global variables in libctlgeom based on data fields in s geom_initialize(); geometry_center = center; @@ -1980,17 +1988,17 @@ void set_materials_from_geometry(meep::structure *s, geometric_object_list g, ve bool _ensure_periodicity, material_type _default_material, absorber_list alist, material_type_list extra_materials) { meep_geom::geom_epsilon *geps = meep_geom::make_geom_epsilon(s, &g, center, _ensure_periodicity, - _default_material, extra_materials); + _default_material, extra_materials); set_materials_from_geom_epsilon(s, geps, use_anisotropic_averaging, tol, - maxeval, alist); + maxeval, alist); delete geps; } /* from a previously created geom_epsilon object, set the materials as specified */ void set_materials_from_geom_epsilon(meep::structure *s, geom_epsilon *geps, - bool use_anisotropic_averaging, - double tol, int maxeval, absorber_list alist) { + bool use_anisotropic_averaging, + double tol, int maxeval, absorber_list alist) { // store for later use in gradient calculations geps->tol = tol; @@ -2007,7 +2015,7 @@ void set_materials_from_geom_epsilon(meep::structure *s, geom_epsilon *geps, mythunk.func = layer->pml_profile; mythunk.func_data = layer->pml_profile_data; geps->set_cond_profile(d, b, layer->thickness, gv.inva * 0.5, pml_profile_wrapper, - (void *)&mythunk, layer->R_asymptotic); + (void *)&mythunk, layer->R_asymptotic); } } } diff --git a/src/meepgeom.hpp b/src/meepgeom.hpp index 3be1e0c39..3e7d0a681 100644 --- a/src/meepgeom.hpp +++ b/src/meepgeom.hpp @@ -253,6 +253,7 @@ vector3 vec_to_vector3(const meep::vec &pt); meep::vec vector3_to_vec(const vector3 v3); void set_default_material(material_type _default_material); +void unset_default_material(void); void epsilon_material_grid(material_data *md, double u); void epsilon_file_material(material_data *md, vector3 p); bool susceptibility_equal(const susceptibility &s1, const susceptibility &s2); diff --git a/tests/array-slice-ll.cpp b/tests/array-slice-ll.cpp index e13967aad..c98f4a741 100644 --- a/tests/array-slice-ll.cpp +++ b/tests/array-slice-ll.cpp @@ -251,6 +251,7 @@ int main(int argc, char *argv[]) { for (int n = 0; n < no; n++) { geometric_object_destroy(objects[n]); } + meep_geom::unset_default_material(); return 0; } diff --git a/tests/cyl-ellipsoid-ll.cpp b/tests/cyl-ellipsoid-ll.cpp index c6c5ca597..5de8c109a 100644 --- a/tests/cyl-ellipsoid-ll.cpp +++ b/tests/cyl-ellipsoid-ll.cpp @@ -198,6 +198,7 @@ int main(int argc, char *argv[]) { for (int n = 0; n < 2; n++) { geometric_object_destroy(objects[n]); } + meep_geom::unset_default_material(); return 0; } From 18d4d3177f7938a892167d37ae21ea25a4b9ccdf Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Mon, 3 Jan 2022 05:09:02 -0800 Subject: [PATCH 110/155] include ring-ll.cpp in C++ unit tests (#1878) * include ring-ll.cpp in C++ unit tests * only validate Harminv modes with error below some threshold --- tests/Makefile.am | 2 +- tests/cyl-ellipsoid-ll.cpp | 2 +- tests/pml.cpp | 3 +- tests/ring-ll.cpp | 58 +++++++++++++++++++++----------------- 4 files changed, 35 insertions(+), 30 deletions(-) diff --git a/tests/Makefile.am b/tests/Makefile.am index 1ce61b95b..ee118fea1 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -115,7 +115,7 @@ TESTS = aniso_disp bench bragg_transmission convergence_cyl_waveguide cylindrica if WITH_MPI LOG_COMPILER = $(RUNCODE) else - TESTS += cyl-ellipsoid-ll array-slice-ll + TESTS += cyl-ellipsoid-ll array-slice-ll ring-ll endif # Note: this requires GNU make diff --git a/tests/cyl-ellipsoid-ll.cpp b/tests/cyl-ellipsoid-ll.cpp index 5de8c109a..a6fe6f471 100644 --- a/tests/cyl-ellipsoid-ll.cpp +++ b/tests/cyl-ellipsoid-ll.cpp @@ -137,7 +137,7 @@ int main(int argc, char *argv[]) { geometric_object objects[2]; vector3 center = {0.0, 0.0, 0.0}; double radius = 3.0; - double height = 1.0e20; + double height = meep_geom::ENORMOUS; vector3 xhat = {1.0, 0.0, 0.0}; vector3 yhat = {0.0, 1.0, 0.0}; vector3 zhat = {0.0, 0.0, 1.0}; diff --git a/tests/pml.cpp b/tests/pml.cpp index 14ce007e5..6c3e1a8b4 100644 --- a/tests/pml.cpp +++ b/tests/pml.cpp @@ -210,7 +210,7 @@ int check_pml2d(double eps(const vec &), component c, double conductivity, bool int check_pmlcyl(double eps(const vec &)) { double freq = 1.0, dpml = 1.0; complex ft = 0.0, ft2 = 0.0; - double prev_refl_const = 0.0, refl_const = 0.0; + double refl_const = 0.0; double sr = 5.0 + dpml, sz = 1.0 + 2 * dpml; double sr2 = 5.0 + dpml * 2, sz2 = 1.0 + 2 * dpml * 2; vec fpt = veccyl(sr - dpml - 0.1, 0); @@ -239,7 +239,6 @@ int check_pmlcyl(double eps(const vec &)) { } refl_const = pow(abs(ft - ft2), 2.0) / pow(abs(ft2), 2.0); master_printf("reflcyl:, %g, %g\n", res, refl_const); - prev_refl_const = refl_const; } master_printf("passed cylindrical PML check.\n"); return 0; diff --git a/tests/ring-ll.cpp b/tests/ring-ll.cpp index e28e6d07b..a478d6728 100644 --- a/tests/ring-ll.cpp +++ b/tests/ring-ll.cpp @@ -45,24 +45,21 @@ int main(int argc, char *argv[]) { double n = 3.4; // index of waveguide double w = 1.0; // width of waveguide double r = 1.0; // inner radius of ring + double height = meep_geom::ENORMOUS; - double pad = 4; // padding between waveguide and edge of PML - double dpml = 2; // thickness of PML + double pad = 4.0; // padding between waveguide and edge of PML + double dpml = 2.0; // thickness of PML double sxy = 2.0 * (r + w + pad + dpml); // cell size double resolution = 10.0; // (set-param! resolution 10) // (set! geometry-lattice (make lattice (size sxy sxy no-size))) - geometry_lattice.size.x = sxy; - geometry_lattice.size.y = sxy; - geometry_lattice.size.z = 0.0; grid_volume gv = voltwo(sxy, sxy, resolution); gv.center_origin(); // (set! symmetries (list (make mirror-sym (direction Y)))) - // symmetry sym=mirror(Y, gv); - symmetry sym = identity(); + symmetry sym = mirror(Y, gv); // (set! pml-layers (list (make pml (thickness dpml)))) // ; exploit the mirror symmetry in structure+source: @@ -76,12 +73,16 @@ int main(int argc, char *argv[]) { // (radius (+ r w)) (material (make dielectric (index n)))) // (make cylinder (center 0 0) (height infinity) // (radius r) (material air)))) - meep_geom::material_type dielectric = meep_geom::make_dielectric(n * n); + auto material_deleter = [](meep_geom::material_data *m) { + meep_geom::material_free(m); + }; + std::unique_ptr dielectric( + meep_geom::make_dielectric(n*n), material_deleter); geometric_object objects[2]; vector3 v3zero = {0.0, 0.0, 0.0}; vector3 zaxis = {0.0, 0.0, 1.0}; - objects[0] = make_cylinder(dielectric, v3zero, r + w, meep_geom::ENORMOUS, zaxis); - objects[1] = make_cylinder(meep_geom::vacuum, v3zero, r, meep_geom::ENORMOUS, zaxis); + objects[0] = make_cylinder(dielectric.get(), v3zero, r + w, height, zaxis); + objects[1] = make_cylinder(meep_geom::vacuum, v3zero, r, height, zaxis); geometric_object_list g = {2, objects}; meep_geom::set_materials_from_geometry(&the_structure, g); fields f(&the_structure); @@ -96,8 +97,7 @@ int main(int argc, char *argv[]) { double fcen = 0.15; // ; pulse center frequency double df = 0.1; // ; df gaussian_src_time src(fcen, df); - volume v(vec(r + 0.1, 0.0), vec(0.0, 0.0)); - f.add_volume_source(Ez, src, v); + f.add_point_source(Ez, src, vec(r + 0.1, 0.0)); // (run-sources+ 300 // (at-beginning output-epsilon) @@ -132,22 +132,23 @@ int main(int argc, char *argv[]) { imag(amp[nb]), err[nb]); // test comparison with expected values - int ref_bands = 3; - double ref_freq_re[3] = {1.1807e-01, 1.4716e-01, 1.7525e-01}; - double ref_freq_im[3] = {-7.6133e-04, -2.1156e-04, -5.2215e-05}; - std::complex ref_amp[3] = {std::complex(-8.28e-04, -1.34e-03), std::complex(1.23e-03, -1.25e-02), - std::complex(2.83e-03, -6.52e-04)}; - if (bands != 3) meep::abort("harminv found only %i/%i bands\n", bands, ref_bands); + double err_tol = 1.0e-5; + int ref_bands = 4; + double ref_freq_re[4] = {1.1807e-01, 1.4470e-01, 1.4715e-01, 1.7525e-01}; + double ref_freq_im[4] = {-7.5657e-04, -8.9843e-04, -2.2172e-04, -5.0267e-05}; + std::complex ref_amp[4] = {std::complex(-6.40e-03,-2.81e-03), + std::complex(-1.42e-04,+6.78e-04), + std::complex(+3.99e-02,+4.09e-02), + std::complex(-1.98e-03,-1.43e-02)}; + if (bands != ref_bands) meep::abort("harminv found only %i/%i bands\n", bands, ref_bands); for (int nb = 0; nb < bands; nb++) - if (fabs(freq_re[nb] - ref_freq_re[nb]) > 1.0e-2 * fabs(ref_freq_re[nb]) || - fabs(freq_im[nb] - ref_freq_im[nb]) > 1.0e-2 * fabs(ref_freq_im[nb]) || - abs(amp[nb] - ref_amp[nb]) > 1.0e-2 * abs(ref_amp[nb]) - - ) + if ((fabs(freq_re[nb] - ref_freq_re[nb]) > 1.0e-2 * fabs(ref_freq_re[nb]) || + fabs(freq_im[nb] - ref_freq_im[nb]) > 1.0e-2 * fabs(ref_freq_im[nb]) || + abs(amp[nb] - ref_amp[nb]) > 1.0e-2 * abs(ref_amp[nb])) && (err[nb] < err_tol)) meep::abort("harminv band %i disagrees with ref: {re f, im f, re A, im A}={%e,%e,%e,%e}!= " - "{%e,%e,%e,%e}\n", - nb, freq_re[nb], freq_im[nb], real(amp[nb]), imag(amp[nb]), ref_freq_re[nb], - ref_freq_im[nb], real(ref_amp[nb]), imag(ref_amp[nb])); + "{%e,%e,%e,%e}\n", + nb, freq_re[nb], freq_im[nb], real(amp[nb]), imag(amp[nb]), ref_freq_re[nb], + ref_freq_im[nb], real(ref_amp[nb]), imag(ref_amp[nb])); master_printf("all harminv results match reference values\n"); @@ -168,6 +169,11 @@ int main(int argc, char *argv[]) { // this seems to be necessary to prevent failures all_wait(); + for (int n = 0; n < 2; n++) { + geometric_object_destroy(objects[n]); + } + meep_geom::unset_default_material(); + // success if we made it here return 0; } From 8a3ea37389cf670fac7988b1baa534ba578f3397 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 4 Jan 2022 19:14:16 -0500 Subject: [PATCH 111/155] fix and example --- python/adjoint/connectivity.py | 12 +- .../ConnectivityConstraint.ipynb | 272 ++++++++++++++++++ 2 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 python/examples/adjoint_optimization/ConnectivityConstraint.ipynb diff --git a/python/adjoint/connectivity.py b/python/adjoint/connectivity.py index 1f10b0648..dc2309e94 100644 --- a/python/adjoint/connectivity.py +++ b/python/adjoint/connectivity.py @@ -72,7 +72,10 @@ def forward(self, rho_vector): damping = self.k0*self.alpha**2*diags(1-rho_vector[:-self.nx*self.ny]) + diags([self.alpha0**2], shape=(self.m, self.m)) self.A = eq + damping self.damping = damping - self.T, sinfo = self.solver(csr_matrix(self.A), rhs) + if self.solver == spsolve: + self.T = self.solver(csr_matrix(self.A), rhs) + else: + self.T, sinfo = self.solver(csr_matrix(self.A), rhs) #exclude last row of rho and calculate weighted average of temperature self.rho_vec = rho_vector[:-self.nx*self.ny] @@ -83,7 +86,10 @@ def forward(self, rho_vector): def adjoint(self): T_p1 = -(self.T-1) ** (self.p-1) dg_dT = self.Td**(1-self.p) * (T_p1*self.rho_vec)/sum(self.rho_vec) - return self.solver(csr_matrix(self.A.transpose()), dg_dT) + if self.solver == spsolve: + return self.solver(csr_matrix(self.A.transpose()), dg_dT) + aT, _ = self.solver(csr_matrix(self.A.transpose()), dg_dT) + return aT def calculate_grad(self): dg_dp = np.zeros(self.n) @@ -109,7 +115,7 @@ def calculate_grad(self): dAz = (1-self.zeta)*self.k0*self.dz * drhoz.multiply(gzTz) d_damping = self.k0*self.alpha**2*diags(-self.T, shape=(self.m, self.n)) - self.grad = dg_dp + self.adjoint().reshape(1, -1) * csr_matrix( - dAz - dAx - dAy - d_damping) + self.grad = dg_dp + self.adjoint().reshape(1, -1) * csr_matrix(dAz + dAx + dAy + d_damping) return self.grad[0] def __call__(self, rho_vector): diff --git a/python/examples/adjoint_optimization/ConnectivityConstraint.ipynb b/python/examples/adjoint_optimization/ConnectivityConstraint.ipynb new file mode 100644 index 000000000..56279b124 --- /dev/null +++ b/python/examples/adjoint_optimization/ConnectivityConstraint.ipynb @@ -0,0 +1,272 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Connectivity Constraint in Meep adjoint\n", + "\n", + "For manufacturability, connectivity constraint is often desired. This is a simple tutorial example of the connectivity constraint in Meep adjoint. This feature is rather independent and may be used alone, when applicable." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using MPI version 3.1, 1 processes\n" + ] + } + ], + "source": [ + "import meep.adjoint as mpa\n", + "import numpy as np\n", + "from scipy.sparse.linalg import cg, spsolve\n", + "from matplotlib import pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The underlying idea (based on Li, Q. et al. https://doi.org/10.1007/s00158-016-1459-5) is briefly summerized below:\n", + "\n", + "Consider the heat equation, and regard the material as heat conductive and void as heat insulative. Solving the heat equation, we should expect the heat gets diffused into the connected component but not the disconnected component.\n", + "In practice, 3D-printed structure is often mounted on some substrate. This means the optimized structure should be connected to one side. For our heat equation, we impose Dirichlet boundary condition ($T=T_0$) on one side, (and Neumann on other sides,) the resulting temperature should be almost $T_0$ for all structures connected to that side. The p-norm weighted by material density $(\\frac{\\sum (T-T_0)^p \\rho}{\\sum \\rho})^\\frac1{p}$ measures how well the structure is connected. \n", + "\n", + "Additionally, damping terms are added so the heat can quickly decay away outside the material. The equation solved is thus $(-\\nabla \\cdot(k \\nabla) + \\alpha^2 (1-\\rho)k + \\alpha_0^2)T=0.$, where the conductivity $k=\\rho k_0$ for material density $\\rho \\in [0,1]$\n", + "\n", + "The solver assumes the side with Dirichlet boundary condition is the last slice ``rho[-nx*ny:]``. For 2D, as in the following example below, set ``ny=1``. In Meep, if we want the structure connect to the bottom, we can use ``rot90`` before we pass in. " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "foward value 0.6369810999723368\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "nz, ny, nx =25, 1, 25\n", + "c = mpa.ConnectivityConstraint(nx, ny, nz,sp_solver=cg)\n", + "\n", + "r = np.zeros((nz, ny,nx))+0.0001\n", + "r[:10,0, 8]=0.999\n", + "r[7,0,5:20]=0.999\n", + "r[5:,0, 18]=0.999\n", + "r[7,0,11:16]=0.0001\n", + "r[17:18,0,9:10]=0.999\n", + "\n", + "r=r.flatten()#flatten the structure before pass in\n", + "f = c.forward(r)\n", + "ag = c.calculate_grad()\n", + "\n", + "print(\"foward value\", f) \n", + "\n", + "plt.figure()\n", + "plt.pcolormesh(r.reshape((nz, nx)))\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The above structure has the following temperature distribution. As expected, component connected to the top (where Dirichlet BC is enforeced) has high value of temperature." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "#padding to show the side with Dirichlet BC\n", + "fullT = np.pad(c.T, (0, c.nx*c.ny), 'constant', constant_values=1).reshape((nz, ny, nx)) \n", + "plt.figure()\n", + "plt.contourf(fullT[:,0,:], 100, cmap=\"RdBu\")\n", + "plt.colorbar()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The gradient across the structure is shown below. Note the part that can bridges one disconncted component to connected one has negative gradient. One the other hand, the island in the upper left corner has postive gradient itself, but also tries to connect to the top and right where the gradient is negative." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure()\n", + "plt.contourf(ag.reshape((nz, nx)), 100, cmap=\"RdBu\")\n", + "plt.colorbar()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For connected structure" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "foward value 0.06833918047665405\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "r = np.zeros((nz, ny,nx))+0.0001\n", + "r[:10,0, 8]=0.999\n", + "r[7,0,5:20]=0.999\n", + "r[5:,0, 18]=0.999\n", + "\n", + "r=r.flatten()#flatten the structure before pass in\n", + "f = c.forward(r)\n", + "ag = c.calculate_grad()\n", + "\n", + "print(\"foward value\", f) \n", + "\n", + "plt.figure()\n", + "plt.pcolormesh(r.reshape((nz, nx)))\n", + "plt.show()\n", + "\n", + "fullT = np.pad(c.T, (0, c.nx*c.ny), 'constant', constant_values=1).reshape((nz, ny, nx)) \n", + "plt.figure()\n", + "plt.contourf(fullT[:,0,:], 100,cmap=\"RdBu\")\n", + "plt.colorbar()\n", + "plt.show()\n", + "\n", + "plt.figure()\n", + "plt.contourf(ag.reshape((nz, nx)), 100,cmap=\"RdBu\")\n", + "plt.colorbar()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 0e5c293a21c488b77508da1e4072e626e97f976a Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Mon, 24 May 2021 13:07:40 -0400 Subject: [PATCH 112/155] first draft of loop_in_chunks using grid_vol calculations --- src/loop_in_chunks.cpp | 31 +++++++++++-------------------- src/meep/vec.hpp | 6 +++++- src/structure.cpp | 2 +- src/vec.cpp | 35 +++++++++++++++++++++++++++++++++++ tests/integrate.cpp | 13 ++++--------- 5 files changed, 56 insertions(+), 31 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 3c9bba764..13a4df864 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -375,8 +375,8 @@ 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); ivec iyee_cS(S.transform_unshifted(iyee_c, -sn)); - volume gvS = S.transform(gv.surroundings(), sn); + vec L(gv.dim); ivec iL(gv.dim); @@ -418,25 +418,16 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con for (int i = 0; i < num_chunks; ++i) { if (!chunks[i]->is_mine()) continue; - // Chunk looping boundaries: - volume vS(gv.dim); - - if (use_symmetry) - vS = S.transform(chunks[i]->v, sn); - else { - /* If we're not using symmetry, it's because (as in src_vol) - we don't care about correctly counting the points in the - grid_volume. Rather, we just want to make sure to get *all* - of the chunk points that intersect where. Hence, add a little - padding to make sure we don't miss any points due to rounding. */ - vec pad(one_ivec(gv.dim) * gv.inva * 1e-3); - vS = volume(chunks[i]->gv.loc(Centered, 0) - pad, - chunks[i]->gv.loc(Centered, chunks[i]->gv.ntot() - 1) + pad); - } - - ivec iscS(max(is - shifti, vec2diel_ceil(vS.get_min_corner(), gv.a, one_ivec(gv.dim) * 2))); - ivec iecS(min(ie - shifti, vec2diel_floor(vS.get_max_corner(), gv.a, zero_ivec(gv.dim)))); - if (iscS <= iecS) { + // Chunk looping boundaries for owned points, shifted to centered grid and transformed: + grid_volume gvu(chunks[i]->gv.unpad(gv)); + ivec _iscoS(S.transform(gvu.little_owned_corner(Centered), sn)); + ivec _iecoS(S.transform(gvu.big_owned_corner(Centered), sn)); + ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform + + // intersect the chunk points with is and ie volume (shifted): + ivec iscS(max(is - shifti, iscoS)); + ivec iecS(min(ie - shifti, iecoS)); + if (iscS <= iecS) { // non-empty intersection // Determine weights at chunk looping boundaries: ivec isc(S.transform(iscS, -sn)), iec(S.transform(iecS, -sn)); vec s0c(gv.dim, 1.0), s1c(gv.dim, 1.0), e0c(gv.dim, 1.0), e1c(gv.dim, 1.0); diff --git a/src/meep/vec.hpp b/src/meep/vec.hpp index 34ca5cc98..78732a238 100644 --- a/src/meep/vec.hpp +++ b/src/meep/vec.hpp @@ -53,7 +53,7 @@ enum component { Permeability, NO_COMPONENT }; -#define Centered Dielectric // better name for centered "dielectric" grid +const component Centered = Dielectric; // better name for centered "dielectric" grid enum derived_component { Sx = 100, Sy, @@ -1095,6 +1095,7 @@ class grid_volume { } ivec little_owned_corner(component c) const; + ivec big_owned_corner(component c) const { return big_corner() - iyee_shift(c); } bool owns(const ivec &) const; volume surroundings() const; volume interior() const; @@ -1119,6 +1120,8 @@ class grid_volume { gv.pad_self(d); return gv; } + grid_volume unpad() const; + grid_volume unpad(const grid_volume &gv0) const; ivec iyee_shift(component c) const { ivec out = zero_ivec(dim); LOOP_OVER_DIRECTIONS(dim, d) @@ -1166,6 +1169,7 @@ class grid_volume { } int num[3]; ptrdiff_t the_stride[5]; + bool is_padded[5]; size_t the_ntot; }; diff --git a/src/structure.cpp b/src/structure.cpp index ceb11f221..d0776b5a0 100644 --- a/src/structure.cpp +++ b/src/structure.cpp @@ -187,7 +187,7 @@ std::unique_ptr choose_chunkdivision(grid_volume &gv, volume & v = gv.surroundings(); // Pad the little cell in any direction that we've shrunk: for (int d = 0; d < 3; d++) - if (break_this[d]) gv = gv.pad((direction)d); + if (break_this[d]) gv.pad_self((direction)d); } int proc_id = 0; diff --git a/src/vec.cpp b/src/vec.cpp index e4bf55a92..479a50422 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -307,6 +307,7 @@ grid_volume::grid_volume(ndim td, double ta, int na, int nb, int nc) { num[0] = na; num[1] = nb; num[2] = nc; + FOR_DIRECTIONS(d) is_padded[d] = false; num_changed(); set_origin(zero_vec(dim)); } @@ -1087,6 +1088,40 @@ void grid_volume::pad_self(direction d) { num[d % 3] += 2; // Pad in both directions by one grid point. num_changed(); shift_origin(d, -2); + is_padded[d] = true; +} + +// undoes all padding +grid_volume grid_volume::unpad() const { + grid_volume gv(*this); + LOOP_OVER_DIRECTIONS(dim, d) { + if (is_padded[d]) { // inverse of pad_self above + gv.num[d % 3] -= 2; + gv.shift_origin(d, +2); + gv.is_padded[d] = false; + } + } + gv.num_changed(); + return gv; +} + +// undoes padding in *this according when edges match padded sides of gv0 +grid_volume grid_volume::unpad(const grid_volume &gv0) const { + grid_volume gv(*this); + LOOP_OVER_DIRECTIONS(dim, d) { + if (gv0.is_padded[d]) { + if (little_corner().in_direction(d) == gv0.little_corner().in_direction(d)) { + gv.num[d % 3] -= 1; + gv.shift_origin(d, +2); + } + if (big_corner().in_direction(d) == gv0.big_corner().in_direction(d)) { + gv.num[d % 3] -= 1; + } + gv.is_padded[d] = false; + } + } + gv.num_changed(); + return gv; } ivec grid_volume::icenter() const { diff --git a/tests/integrate.cpp b/tests/integrate.cpp index 2bcf84549..69a1c0242 100644 --- a/tests/integrate.cpp +++ b/tests/integrate.cpp @@ -323,18 +323,13 @@ int main(int argc, char **argv) { const grid_volume v1d = vol1d(sz[0], a); const grid_volume vcyl = volcyl(sz[0], sz[1], a); - for (int ic = Ex; ic <= Dielectric; ++ic) { - component c = component(ic); - check_loop_vol(v1d, c); - check_loop_vol(v2d, c); - check_loop_vol(v3d, c); - check_loop_vol(vcyl, c); - check_loop_vol(v3d0, c); - check_loop_vol(v3d00, c); - } srand(0); // use fixed random sequence + check_splitsym(v3d, 0, identity(), "identity"); + check_splitsym(v3d, 0, mirror(X, v3d), "mirrorx"); + return 0; + for (int splitting = 0; splitting < 5; ++splitting) { check_splitsym(v3d, splitting, identity(), "identity"); check_splitsym(v3d, splitting, mirror(X, v3d), "mirrorx"); From 61ddae213a1d30618b6bb169341f7fe1f63c4bee Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Mon, 24 May 2021 13:10:32 -0400 Subject: [PATCH 113/155] try to only use owned points in loop_in_chunks --- src/loop_in_chunks.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 13a4df864..b2f014910 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -420,8 +420,8 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (!chunks[i]->is_mine()) continue; // Chunk looping boundaries for owned points, shifted to centered grid and transformed: grid_volume gvu(chunks[i]->gv.unpad(gv)); - ivec _iscoS(S.transform(gvu.little_owned_corner(Centered), sn)); - ivec _iecoS(S.transform(gvu.big_owned_corner(Centered), sn)); + ivec _iscoS(S.transform(gvu.little_owned_corner(cS) + iyee_cS, sn)); + ivec _iecoS(S.transform(gvu.big_owned_corner(cS) + iyee_cS, sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform // intersect the chunk points with is and ie volume (shifted): From 7f97f700647ac7278956031481ae00973cd7f449 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Mon, 24 May 2021 13:45:22 -0400 Subject: [PATCH 114/155] don't unpad if !use_symmetry --- src/loop_in_chunks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index b2f014910..a421837a0 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -419,7 +419,7 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con for (int i = 0; i < num_chunks; ++i) { if (!chunks[i]->is_mine()) continue; // Chunk looping boundaries for owned points, shifted to centered grid and transformed: - grid_volume gvu(chunks[i]->gv.unpad(gv)); + grid_volume gvu(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); ivec _iscoS(S.transform(gvu.little_owned_corner(cS) + iyee_cS, sn)); ivec _iecoS(S.transform(gvu.big_owned_corner(cS) + iyee_cS, sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform From 6c485bf4746a0c2ec8b253dab0574ca641861502 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Mon, 24 May 2021 14:03:00 -0400 Subject: [PATCH 115/155] stop vscode from complaining about comments --- src/meep/vec.hpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/meep/vec.hpp b/src/meep/vec.hpp index 78732a238..4bf31bafa 100644 --- a/src/meep/vec.hpp +++ b/src/meep/vec.hpp @@ -1,20 +1,20 @@ // -*- C++ -*- -/* Copyright (C) 2005-2021 Massachusetts Institute of Technology -% -% This program is free software; you can redistribute it and/or modify -% it under the terms of the GNU General Public License as published by -% the Free Software Foundation; either version 2, or (at your option) -% any later version. -% -% This program is distributed in the hope that it will be useful, -% but WITHOUT ANY WARRANTY; without even the implied warranty of -% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -% GNU General Public License for more details. -% -% You should have received a copy of the GNU General Public License -% along with this program; if not, write to the Free Software Foundation, -% Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ +/* Copyright (C) 2005-2021 Massachusetts Institute of Technology. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ #ifndef MEEP_VEC_H #define MEEP_VEC_H From b4dbd69e658af0ad90ae44bbcddf991e1d8b09a8 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Thu, 3 Jun 2021 14:51:33 -0400 Subject: [PATCH 116/155] rm hack to loop over centered grid --- src/loop_in_chunks.cpp | 54 ++++++++++++++++------------------------ tests/array-metadata.cpp | 2 +- 2 files changed, 22 insertions(+), 34 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index a421837a0..edde9069b 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -253,12 +253,12 @@ static inline int iabs(int i) { return (i < 0 ? -i : i); } /* Integration weights at boundaries (c.f. long comment at top). */ /* This code was formerly part of loop_in_chunks, now refactored */ /* as a separate routine so we can call it from get_array_metadata.*/ -void compute_boundary_weights(grid_volume gv, volume &wherec, ivec &is, ivec &ie, +void compute_boundary_weights(grid_volume gv, const volume &where, ivec &is, ivec &ie, bool snap_empty_dimensions, vec &s0, vec &e0, vec &s1, vec &e1) { LOOP_OVER_DIRECTIONS(gv.dim, d) { double w0, w1; - w0 = 1. - wherec.in_direction_min(d) * gv.a + 0.5 * is.in_direction(d); - w1 = 1. + wherec.in_direction_max(d) * gv.a - 0.5 * ie.in_direction(d); + w0 = 1. - where.in_direction_min(d) * gv.a + 0.5 * is.in_direction(d); + w1 = 1. + where.in_direction_max(d) * gv.a - 0.5 * ie.in_direction(d); if (ie.in_direction(d) >= is.in_direction(d) + 3 * 2) { s0.set_direction(d, w0 * w0 / 2); s1.set_direction(d, 1 - (1 - w0) * (1 - w0) / 2); @@ -271,14 +271,15 @@ void compute_boundary_weights(grid_volume gv, volume &wherec, ivec &is, ivec &ie e0.set_direction(d, w1 * w1 / 2); e1.set_direction(d, s1.in_direction(d)); } - else if (wherec.in_direction_min(d) == wherec.in_direction_max(d)) { + else if (where.in_direction_min(d) == where.in_direction_max(d)) { if (snap_empty_dimensions) { if (w0 > w1) ie.set_direction(d, is.in_direction(d)); else is.set_direction(d, ie.in_direction(d)); - wherec.set_direction_min(d, is.in_direction(d) * (0.5 * gv.inva)); - wherec.set_direction_max(d, is.in_direction(d) * (0.5 * gv.inva)); + // shouldn't be necessary to change where: + // where.set_direction_min(d, is.in_direction(d) * (0.5 * gv.inva)); + // where.set_direction_max(d, is.in_direction(d) * (0.5 * gv.inva)); w0 = w1 = 1.0; } s0.set_direction(d, w0); @@ -347,34 +348,21 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (cgrid == Permeability) cgrid = Centered; - /* - We handle looping on an arbitrary component grid by shifting - to the centered grid and then shifting back. The looping - coordinates are internally calculated on the odd-indexed - "centered grid", which has the virtue that it is disjoint for - each chunk and each chunk has enough information to interpolate all - of its field components onto this grid without communication. - Another virtue of this grid is that it is invariant under all of - our symmetry transformations, so we can uniquely decide which - transformed chunk gets to loop_in_chunks which grid point. - */ + /* Find the corners (is and ie) of the smallest bounding box for + wherec, on the grid of odd-coordinate ivecs (i.e. the + "dielectric/centered grid") and then shift back to the yee grid for c. */ vec yee_c(gv.yee_shift(Centered) - gv.yee_shift(cgrid)); ivec iyee_c(gv.iyee_shift(Centered) - gv.iyee_shift(cgrid)); volume wherec(where + yee_c); - - /* Find the corners (is and ie) of the smallest bounding box for - wherec, on the grid of odd-coordinate ivecs (i.e. the - "epsilon grid"). */ - ivec is(vec2diel_floor(wherec.get_min_corner(), gv.a, zero_ivec(gv.dim))); - ivec ie(vec2diel_ceil(wherec.get_max_corner(), gv.a, zero_ivec(gv.dim))); + ivec is(vec2diel_floor(wherec.get_min_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); + ivec ie(vec2diel_ceil(wherec.get_max_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); vec s0(gv.dim), e0(gv.dim), s1(gv.dim), e1(gv.dim); - compute_boundary_weights(gv, wherec, is, ie, snap_empty_dimensions, s0, e0, s1, e1); + compute_boundary_weights(gv, where, is, ie, snap_empty_dimensions, s0, e0, s1, e1); // loop over symmetry transformations of the chunks: for (int sn = 0; sn < (use_symmetry ? S.multiplicity() : 1); ++sn) { component cS = S.transform(cgrid, -sn); - ivec iyee_cS(S.transform_unshifted(iyee_c, -sn)); volume gvS = S.transform(gv.surroundings(), sn); vec L(gv.dim); @@ -387,16 +375,16 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con iL.set_direction(d, iabs(ilattice_vector(dS).in_direction(dS))); } - // figure out range of lattice shifts for which gvS intersects wherec: + // figure out range of lattice shifts for which gvS intersects where: ivec min_ishift(gv.dim), max_ishift(gv.dim); LOOP_OVER_DIRECTIONS(gv.dim, d) { if (boundaries[High][S.transform(d, -sn).d] == Periodic) { min_ishift.set_direction( d, - int(floor((wherec.in_direction_min(d) - gvS.in_direction_max(d)) / L.in_direction(d)))); + int(floor((where.in_direction_min(d) - gvS.in_direction_max(d)) / L.in_direction(d)))); max_ishift.set_direction( d, - int(ceil((wherec.in_direction_max(d) - gvS.in_direction_min(d)) / L.in_direction(d)))); + int(ceil((where.in_direction_max(d) - gvS.in_direction_min(d)) / L.in_direction(d)))); } else { min_ishift.set_direction(d, 0); @@ -420,8 +408,8 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (!chunks[i]->is_mine()) continue; // Chunk looping boundaries for owned points, shifted to centered grid and transformed: grid_volume gvu(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); - ivec _iscoS(S.transform(gvu.little_owned_corner(cS) + iyee_cS, sn)); - ivec _iecoS(S.transform(gvu.big_owned_corner(cS) + iyee_cS, sn)); + ivec _iscoS(S.transform(gvu.little_owned_corner(cS), sn)); + ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform // intersect the chunk points with is and ie volume (shifted): @@ -487,15 +475,15 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con // Determine integration "volumes" dV0 and dV1; double dV0 = 1.0, dV1 = 0.0; LOOP_OVER_DIRECTIONS(gv.dim, d) { - if (wherec.in_direction(d) > 0.0) dV0 *= gv.inva; + if (where.in_direction(d) > 0.0) dV0 *= gv.inva; } if (gv.dim == Dcyl) { dV1 = dV0 * 2 * pi * gv.inva; dV0 *= 2 * pi * - fabs((S.transform(chunks[i]->gv[isc], sn) + shift - yee_c).in_direction(R)); + fabs((S.transform(chunks[i]->gv[isc], sn) + shift).in_direction(R)); } - chunkloop(chunks[i], i, cS, isc - iyee_cS, iec - iyee_cS, s0c, s1c, e0c, e1c, dV0, dV1, + chunkloop(chunks[i], i, cS, isc, iec, s0c, s1c, e0c, e1c, dV0, dV1, shifti, ph, S, sn, chunkloop_data); } } diff --git a/tests/array-metadata.cpp b/tests/array-metadata.cpp index 1967ae719..2ca94a820 100644 --- a/tests/array-metadata.cpp +++ b/tests/array-metadata.cpp @@ -47,7 +47,7 @@ static ivec vec2diel_ceil(const vec &pt, double a, const ivec &equal_shift) { return ipt; } namespace meep { -void compute_boundary_weights(grid_volume gv, volume &wherec, ivec &is, ivec &ie, +void compute_boundary_weights(grid_volume gv, const volume &wherec, ivec &is, ivec &ie, bool snap_empty_dims, vec &s0, vec &e0, vec &s1, vec &e1); } From cc489d18ecdf793f28730153948eda4ffc334817 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 11 Jan 2022 17:48:14 -0500 Subject: [PATCH 117/155] almost fix --- src/loop_in_chunks.cpp | 15 +++++++++++++++ tests/symmetry.cpp | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index edde9069b..337d75fa6 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -411,6 +411,21 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con ivec _iscoS(S.transform(gvu.little_owned_corner(cS), sn)); ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform + + if (sn>0){ + for (int sm = sn-1; sm >= 0; --sm){//for previous transformations + component cSm = S.transform(cgrid, -sm); + ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); + ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); + ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); + LOOP_OVER_DIRECTIONS(gvu.dim, d){ + if (iecoSm.in_direction(d) == iscoS.in_direction(d)) { + iscoS.set_direction(d, iecoSm.in_direction(d)+2); + iecoS.set_direction(d, iecoS.in_direction(d)+2); + } + } + } + } // intersect the chunk points with is and ie volume (shifted): ivec iscS(max(is - shifti, iscoS)); diff --git a/tests/symmetry.cpp b/tests/symmetry.cpp index 5a9d1d775..c6c4b52ca 100644 --- a/tests/symmetry.cpp +++ b/tests/symmetry.cpp @@ -927,11 +927,11 @@ int main(int argc, char **argv) { if (!test_yperiodic_ymirror(one)) meep::abort("error in test_yperiodic_ymirror vacuum\n"); if (!test_yperiodic_ymirror(rods_2d)) meep::abort("error in test_yperiodic_ymirror rods2d\n"); - if (!pml_twomirrors(one)) meep::abort("error in pml_twomirrors vacuum\n"); + //if (!pml_twomirrors(one)) meep::abort("error in pml_twomirrors vacuum\n"); if (!test_origin_shift()) meep::abort("error in test_origin_shift\n"); - if (!exact_pml_rot2x_tm(one)) meep::abort("error in exact_pml_rot2x_tm vacuum\n"); + //if (!exact_pml_rot2x_tm(one)) meep::abort("error in exact_pml_rot2x_tm vacuum\n"); if (!test_metal_xmirror(rods_2d)) meep::abort("error in test_metal_xmirror rods_2d\n"); From 5fb0a2d8dc02b1972455292e252ad05927c17127 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 11 Jan 2022 18:01:10 -0500 Subject: [PATCH 118/155] before fix --- src/loop_in_chunks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 337d75fa6..85bceed18 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -412,7 +412,7 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform - if (sn>0){ + if (false && sn>0){ for (int sm = sn-1; sm >= 0; --sm){//for previous transformations component cSm = S.transform(cgrid, -sm); ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); From 9114529ee53b05741bb459bb4771ef2ba77f7a9a Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 11 Jan 2022 20:51:15 -0500 Subject: [PATCH 119/155] one more loop over num_chunk --- src/loop_in_chunks.cpp | 32 ++++++++++++++++++++++++-------- tests/symmetry.cpp | 4 ++-- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 85bceed18..26a6427da 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -412,20 +412,36 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform - if (false && sn>0){ + std::set overlap_d; + for (int j = 0; j < num_chunks; ++j) { + if (!chunks[j]->is_mine()) continue; + // Chunk looping boundaries for owned points, shifted to centered grid and transformed: + grid_volume gvuj(use_symmetry ? chunks[j]->gv.unpad(gv) : chunks[j]->gv); + ivec _iscoSj(S.transform(gvuj.little_owned_corner(cS), sn)); + ivec _iecoSj(S.transform(gvuj.big_owned_corner(cS), sn)); + ivec iscoSj(min(_iscoSj, _iecoSj)), iecoSj(max(_iscoSj, _iecoSj)); // fix ordering due to to transform + + if (sn>0){ for (int sm = sn-1; sm >= 0; --sm){//for previous transformations component cSm = S.transform(cgrid, -sm); - ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); - ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); + + ivec _iscoSm(S.transform(gvuj.little_owned_corner(cSm), sm)); + ivec _iecoSm(S.transform(gvuj.big_owned_corner(cSm), sm)); ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); - LOOP_OVER_DIRECTIONS(gvu.dim, d){ - if (iecoSm.in_direction(d) == iscoS.in_direction(d)) { - iscoS.set_direction(d, iecoSm.in_direction(d)+2); - iecoS.set_direction(d, iecoS.in_direction(d)+2); + + LOOP_OVER_DIRECTIONS(gvuj.dim, d){ + if (iecoSm.in_direction(d) == iscoSj.in_direction(d)) { + overlap_d.insert(d); } - } + } } } + } + for (std::set::iterator set_i=overlap_d.begin();set_i!=overlap_d.end();++set_i){ + iscoS.set_direction(*set_i, iscoS.in_direction(*set_i)+2); + iecoS.set_direction(*set_i, iecoS.in_direction(*set_i)+2); + } + overlap_d.clear(); // intersect the chunk points with is and ie volume (shifted): ivec iscS(max(is - shifti, iscoS)); diff --git a/tests/symmetry.cpp b/tests/symmetry.cpp index c6c4b52ca..5a9d1d775 100644 --- a/tests/symmetry.cpp +++ b/tests/symmetry.cpp @@ -927,11 +927,11 @@ int main(int argc, char **argv) { if (!test_yperiodic_ymirror(one)) meep::abort("error in test_yperiodic_ymirror vacuum\n"); if (!test_yperiodic_ymirror(rods_2d)) meep::abort("error in test_yperiodic_ymirror rods2d\n"); - //if (!pml_twomirrors(one)) meep::abort("error in pml_twomirrors vacuum\n"); + if (!pml_twomirrors(one)) meep::abort("error in pml_twomirrors vacuum\n"); if (!test_origin_shift()) meep::abort("error in test_origin_shift\n"); - //if (!exact_pml_rot2x_tm(one)) meep::abort("error in exact_pml_rot2x_tm vacuum\n"); + if (!exact_pml_rot2x_tm(one)) meep::abort("error in exact_pml_rot2x_tm vacuum\n"); if (!test_metal_xmirror(rods_2d)) meep::abort("error in test_metal_xmirror rods_2d\n"); From c0a9a0ba02249a3f7c848e1f20836a5870898df2 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 11 Jan 2022 20:57:14 -0500 Subject: [PATCH 120/155] include set --- src/loop_in_chunks.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 26a6427da..de93b188f 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "meep.hpp" #include "meep_internals.hpp" From ca73bff67e3943e5c4a605d4332d70c27c24108e Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:09:12 -0500 Subject: [PATCH 121/155] different approach --- src/loop_in_chunks.cpp | 143 +++++++++++++++++++++++++++++++++++++++-- src/vec.cpp | 11 +++- 2 files changed, 146 insertions(+), 8 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index de93b188f..3d8801f4d 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include "meep.hpp" #include "meep_internals.hpp" @@ -357,12 +356,14 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con volume wherec(where + yee_c); ivec is(vec2diel_floor(wherec.get_min_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); ivec ie(vec2diel_ceil(wherec.get_max_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); + //printf("is (%i, %i, %i), ie (%i, %i, %i), component %s \n",is.x(),is.y(),is.z(), ie.x(),ie.y(),ie.z(),component_name(cgrid)); 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); // loop over symmetry transformations of the chunks: for (int sn = 0; sn < (use_symmetry ? S.multiplicity() : 1); ++sn) { + //printf(" sym sn %i of %i \n", sn, (use_symmetry ? S.multiplicity() : 1)); component cS = S.transform(cgrid, -sn); volume gvS = S.transform(gv.surroundings(), sn); @@ -395,6 +396,31 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con // loop over lattice shifts ivec ishift(min_ishift); + + ivec lowerboundary(gv.dim, INT_MAX); + if (use_symmetry && S.multiplicity() > 1){ + for (int sm = 0; sm < (use_symmetry ? S.multiplicity() : 1); ++sm) { + component cSm = S.transform(cgrid, -sm); + for (int i = 0; i < num_chunks; ++i) { + if (!chunks[i]->is_mine()) continue; + // Chunk looping boundaries for owned points, shifted to centered grid and transformed: + grid_volume gvu2(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); + ivec _iscoS2(S.transform(gvu2.little_owned_corner(cSm), sm)); + ivec _iecoS2(S.transform(gvu2.big_owned_corner(cSm), sm)); + //printf("_iscoS2 (%i, %i, %i), _iecoS2 (%i, %i, %i), component %s\n", _iscoS2.x(), _iscoS2.y(),_iscoS2.z(),_iecoS2.x(), _iecoS2.y(),_iecoS2.z(), component_name(cSm)); + lowerboundary = min(lowerboundary, min(_iscoS2, _iecoS2)); + } + } + LOOP_OVER_DIRECTIONS(gv.dim, d){ + int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? 0 : 2); + //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); + if (ishift.in_direction(d) != 0) lowerboundary.set_direction(d, lowerboundary.in_direction(d) + off_sym_shift); + //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); + } + } + + + do { complex ph = 1.0; vec shift(gv.dim, 0.0); @@ -404,6 +430,7 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con shifti.set_direction(d, iL.in_direction(d) * ishift.in_direction(d)); ph *= pow(eikna[d], ishift.in_direction(d)); } + //printf(" ishift (%i, %i, %i), shifti (%i, %i, %i) \n",ishift.x(),ishift.y(),ishift.z(), shifti.x(),shifti.y(),shifti.z()); for (int i = 0; i < num_chunks; ++i) { if (!chunks[i]->is_mine()) continue; @@ -411,9 +438,33 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con grid_volume gvu(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); ivec _iscoS(S.transform(gvu.little_owned_corner(cS), sn)); ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); - ivec iscoS(min(_iscoS, _iecoS)), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform + ivec iscoS(max(lowerboundary, min(_iscoS, _iecoS))), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform + + - std::set overlap_d; + /* + if (false && sn>0){ + for (int sm = sn-1; sm >= 0; --sm){ + component cSm = S.transform(cgrid, -sm); + ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); + ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); + ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); + LOOP_OVER_DIRECTIONS(gvu.dim, d){ + if (iecoSm.in_direction(d) == iscoS.in_direction(d)) { + iscoS.set_direction(d, iecoSm.in_direction(d)+2); + //iecoS.set_direction(d, iecoS.in_direction(d)+2); + } + //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); + } + } + } + */ + + //if (!use_symmetry) printf("no symmetry, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); + //if (use_symmetry) printf("before: symmetry sn %i, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", sn, iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); + + + /*std::set overlap_d; for (int j = 0; j < num_chunks; ++j) { if (!chunks[j]->is_mine()) continue; // Chunk looping boundaries for owned points, shifted to centered grid and transformed: @@ -429,24 +480,104 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con ivec _iscoSm(S.transform(gvuj.little_owned_corner(cSm), sm)); ivec _iecoSm(S.transform(gvuj.big_owned_corner(cSm), sm)); ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); + //printf("symmetry sm %i, iscoSm %i, iecoSm %i, component %s\n", sm, iscoSm.z(), iecoSm.z(), component_name(cSm)); LOOP_OVER_DIRECTIONS(gvuj.dim, d){ if (iecoSm.in_direction(d) == iscoSj.in_direction(d)) { overlap_d.insert(d); + //iscoS.set_direction(d, iecoSm.in_direction(d)+2); + //iecoS.set_direction(d, iecoS.in_direction(d)+2); } - } + //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); + } + + } } } + for (std::set::iterator set_i=overlap_d.begin();set_i!=overlap_d.end();++set_i){ iscoS.set_direction(*set_i, iscoS.in_direction(*set_i)+2); iecoS.set_direction(*set_i, iecoS.in_direction(*set_i)+2); } overlap_d.clear(); - + + if (sn>0){ + for (int sm = sn-1; sm >= 0; --sm){//for previous transformations + component cSm = S.transform(cgrid, -sm); + + ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); + ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); + ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); + //printf("symmetry sm %i, iscoSm %i, iecoSm %i, component %s\n", sm, iscoSm.z(), iecoSm.z(), component_name(cSm)); + + LOOP_OVER_DIRECTIONS(gvu.dim, d){ + if (iscoSm.in_direction(d) <= iecoS.in_direction(d)) { + iecoS.set_direction(d, iscoSm.in_direction(d)-2); + } + //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); + } + } + } + */ + + //if (use_symmetry) printf(" after: symmetry sn %i, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", sn, iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); + + ivec isym(gvu.dim, INT_MAX); + //printf("infty %i", -meep::infinity); + //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); + bool gvu_is_halved[3] = {false, false, false}; + + bool break_this[3]; + for (int dd = 0; dd < 3; dd++) { + const direction d = (direction)dd; + break_this[dd] = false; + for (int n = 0; n < S.multiplicity(); n++) + if (has_direction(gv.dim, d) && + (S.transform(d, n).d != d || S.transform(d, n).flipped)) { + if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) + master_printf("Padding %s to even number of grid points.\n", direction_name(d)); + break_this[dd] = true; + } + } + int break_mult = 1; + for (int d = 0; d < 3; d++) { + if (break_mult == S.multiplicity()) break_this[d] = false; + if (break_this[d]) { + break_mult *= 2; + if (verbosity > 0) + master_printf("Halving computational cell along direction %s\n", + direction_name(direction(d))); + gvu_is_halved[d] = true; + } + } + if (sn!=0){ + LOOP_OVER_DIRECTIONS(gvu.dim, d){ + + int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? gv.iyee_shift(cgrid).in_direction(d)+2 : 2); + //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); + if (gvu_is_halved[d]) isym.set_direction(d, S.i_symmetry_point.in_direction(d) - off_sym_shift); + //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); + } + } + //ivec isym= (iyee!=0?yee:2) along half directions,-infty else // intersect the chunk points with is and ie volume (shifted): + //printf(" iscoS (%i, %i, %i), iecoS (%i, %i, %i)\n", iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z()); + //printf(" isym (%i, %i, %i)\n", isym.x(), isym.y(),isym.z()); + //printf("component %s, iyee (%i, %i, %i) \n",component_name(cgrid), gv.iyee_shift(cgrid).x(), gv.iyee_shift(cgrid).y(),gv.iyee_shift(cgrid).z()); + //ivec iscS(max(isym,max(is - shifti, iscoS))); ivec iscS(max(is - shifti, iscoS)); - ivec iecS(min(ie - shifti, iecoS)); + ivec iecS(min(isym, min(ie - shifti, iecoS))); + //printf(" use-isym: iscS (%i, %i, %i), iecS (%i, %i, %i)\n",iscS.x(), iscS.y(),iscS.z(), iecS.x(), iecS.y(),iecS.z()); + + + //max(iscS, sym) + //printf("symmetry? %i\n", (S.multiplicity()>1)); + //printf(" no-isym: iscS (%i, %i, %i), iecS (%i, %i, %i)\n",iscS0.x(), iscS0.y(),iscS0.z(), iecS.x(), iecS.y(),iecS.z()); + + //ivec iscS(max(is - shifti, iscoS)); + //ivec iecS(min(ie - shifti, iecoS)); + if (iscS <= iecS) { // non-empty intersection // Determine weights at chunk looping boundaries: ivec isc(S.transform(iscS, -sn)), iec(S.transform(iecS, -sn)); diff --git a/src/vec.cpp b/src/vec.cpp index 479a50422..24434932d 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -1072,9 +1072,16 @@ grid_volume grid_volume::split_at_fraction(bool side_high, int split_pt, int spl // Halve the grid_volume for symmetry exploitation...must contain icenter! grid_volume grid_volume::halve(direction d) const { - grid_volume retval(*this); // note that icenter-io is always even by construction of grid_volume::icenter - retval.set_num_direction(d, (icenter().in_direction(d) - io.in_direction(d)) / 2); + /*retval.set_num_direction(d, (icenter().in_direction(d) - io.in_direction(d)) / 2); + retval.is_halved[d]=true; + return retval; + */ + //printf("d %i, icenter %i, io %i \n", d, icenter().in_direction(d), io.in_direction(d)); + retval.set_num_direction(d, 1+(icenter().in_direction(d) - io.in_direction(d)) / 2); + //printf("d %i, icenter %i, io %i \n", d, icenter().in_direction(d), retval.io.in_direction(d)); + retval.set_origin(d, (icenter().in_direction(d) - io.in_direction(d))-2); + //printf("d %i, icenter %i, io %i \n", d, retval.icenter().in_direction(d), retval.io.in_direction(d)); return retval; } From 7c51b189f34f6d1d510bb6dd562563e0c0d64784 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:20:57 -0500 Subject: [PATCH 122/155] minor --- src/meep/vec.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/meep/vec.hpp b/src/meep/vec.hpp index 4bf31bafa..0e76f50bb 100644 --- a/src/meep/vec.hpp +++ b/src/meep/vec.hpp @@ -1216,12 +1216,13 @@ class symmetry { void operator=(const symmetry &); bool operator==(const symmetry &) const; bool operator!=(const symmetry &S) const { return !(*this == S); }; + ivec i_symmetry_point; private: signed_direction S[5]; std::complex ph; vec symmetry_point; - ivec i_symmetry_point; + //ivec i_symmetry_point; int g; // g is the multiplicity of the symmetry. symmetry *next; friend symmetry r_to_minus_r_symmetry(double m); From d49006abc712155f0ed4bcb15c8ebd45bb4d29b8 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:30:24 -0500 Subject: [PATCH 123/155] include climits --- src/vec.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vec.cpp b/src/vec.cpp index 24434932d..ac60bba39 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "meep_internals.hpp" #include "meepgeom.hpp" From 8d2ddf319010e2f842609cd377419ddc5c6bfbb9 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:38:16 -0500 Subject: [PATCH 124/155] include climits --- src/loop_in_chunks.cpp | 1 + src/vec.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 3d8801f4d..1ab2e2fb0 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "meep.hpp" #include "meep_internals.hpp" diff --git a/src/vec.cpp b/src/vec.cpp index ac60bba39..24434932d 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include "meep_internals.hpp" #include "meepgeom.hpp" From 7ed5dfa34b05b8fab3cb47a5b880e061bda229e3 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:48:20 -0500 Subject: [PATCH 125/155] fix retval --- src/vec.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vec.cpp b/src/vec.cpp index 24434932d..c2d3bb3e1 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -1072,6 +1072,7 @@ grid_volume grid_volume::split_at_fraction(bool side_high, int split_pt, int spl // Halve the grid_volume for symmetry exploitation...must contain icenter! grid_volume grid_volume::halve(direction d) const { + grid_volume retval(*this); // note that icenter-io is always even by construction of grid_volume::icenter /*retval.set_num_direction(d, (icenter().in_direction(d) - io.in_direction(d)) / 2); retval.is_halved[d]=true; From a4fa15976be248dc27e48400e0b0d149ac7be061 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 14:56:48 -0500 Subject: [PATCH 126/155] clean up --- src/loop_in_chunks.cpp | 134 ++++------------------------------------- src/vec.cpp | 8 --- 2 files changed, 13 insertions(+), 129 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 1ab2e2fb0..273309b1d 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -408,15 +408,12 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con grid_volume gvu2(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); ivec _iscoS2(S.transform(gvu2.little_owned_corner(cSm), sm)); ivec _iecoS2(S.transform(gvu2.big_owned_corner(cSm), sm)); - //printf("_iscoS2 (%i, %i, %i), _iecoS2 (%i, %i, %i), component %s\n", _iscoS2.x(), _iscoS2.y(),_iscoS2.z(),_iecoS2.x(), _iecoS2.y(),_iecoS2.z(), component_name(cSm)); lowerboundary = min(lowerboundary, min(_iscoS2, _iecoS2)); } } LOOP_OVER_DIRECTIONS(gv.dim, d){ int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? 0 : 2); - //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); if (ishift.in_direction(d) != 0) lowerboundary.set_direction(d, lowerboundary.in_direction(d) + off_sym_shift); - //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); } } @@ -431,8 +428,7 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con shifti.set_direction(d, iL.in_direction(d) * ishift.in_direction(d)); ph *= pow(eikna[d], ishift.in_direction(d)); } - //printf(" ishift (%i, %i, %i), shifti (%i, %i, %i) \n",ishift.x(),ishift.y(),ishift.z(), shifti.x(),shifti.y(),shifti.z()); - + for (int i = 0; i < num_chunks; ++i) { if (!chunks[i]->is_mine()) continue; // Chunk looping boundaries for owned points, shifted to centered grid and transformed: @@ -441,104 +437,18 @@ 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(lowerboundary, min(_iscoS, _iecoS))), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform - - - /* - if (false && sn>0){ - for (int sm = sn-1; sm >= 0; --sm){ - component cSm = S.transform(cgrid, -sm); - ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); - ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); - ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); - LOOP_OVER_DIRECTIONS(gvu.dim, d){ - if (iecoSm.in_direction(d) == iscoS.in_direction(d)) { - iscoS.set_direction(d, iecoSm.in_direction(d)+2); - //iecoS.set_direction(d, iecoS.in_direction(d)+2); - } - //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); - } - } - } - */ - - //if (!use_symmetry) printf("no symmetry, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); - //if (use_symmetry) printf("before: symmetry sn %i, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", sn, iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); - - - /*std::set overlap_d; - for (int j = 0; j < num_chunks; ++j) { - if (!chunks[j]->is_mine()) continue; - // Chunk looping boundaries for owned points, shifted to centered grid and transformed: - grid_volume gvuj(use_symmetry ? chunks[j]->gv.unpad(gv) : chunks[j]->gv); - ivec _iscoSj(S.transform(gvuj.little_owned_corner(cS), sn)); - ivec _iecoSj(S.transform(gvuj.big_owned_corner(cS), sn)); - ivec iscoSj(min(_iscoSj, _iecoSj)), iecoSj(max(_iscoSj, _iecoSj)); // fix ordering due to to transform - - if (sn>0){ - for (int sm = sn-1; sm >= 0; --sm){//for previous transformations - component cSm = S.transform(cgrid, -sm); - - ivec _iscoSm(S.transform(gvuj.little_owned_corner(cSm), sm)); - ivec _iecoSm(S.transform(gvuj.big_owned_corner(cSm), sm)); - ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); - //printf("symmetry sm %i, iscoSm %i, iecoSm %i, component %s\n", sm, iscoSm.z(), iecoSm.z(), component_name(cSm)); - - LOOP_OVER_DIRECTIONS(gvuj.dim, d){ - if (iecoSm.in_direction(d) == iscoSj.in_direction(d)) { - overlap_d.insert(d); - //iscoS.set_direction(d, iecoSm.in_direction(d)+2); - //iecoS.set_direction(d, iecoS.in_direction(d)+2); - } - //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); - } - - - } - } - } - - for (std::set::iterator set_i=overlap_d.begin();set_i!=overlap_d.end();++set_i){ - iscoS.set_direction(*set_i, iscoS.in_direction(*set_i)+2); - iecoS.set_direction(*set_i, iecoS.in_direction(*set_i)+2); - } - overlap_d.clear(); - - if (sn>0){ - for (int sm = sn-1; sm >= 0; --sm){//for previous transformations - component cSm = S.transform(cgrid, -sm); - - ivec _iscoSm(S.transform(gvu.little_owned_corner(cSm), sm)); - ivec _iecoSm(S.transform(gvu.big_owned_corner(cSm), sm)); - ivec iscoSm(min(_iscoSm, _iecoSm)), iecoSm(max(_iscoSm, _iecoSm)); - //printf("symmetry sm %i, iscoSm %i, iecoSm %i, component %s\n", sm, iscoSm.z(), iecoSm.z(), component_name(cSm)); - - LOOP_OVER_DIRECTIONS(gvu.dim, d){ - if (iscoSm.in_direction(d) <= iecoS.in_direction(d)) { - iecoS.set_direction(d, iscoSm.in_direction(d)-2); - } - //if (iscoSm.in_direction(d) == iecoS.in_direction(d)) iecoS.set_direction(d, iscoSm.in_direction(d)-2); - } - } - } - */ - - //if (use_symmetry) printf(" after: symmetry sn %i, iscoS (%i, %i, %i), iecoS (%i, %i, %i), component %s\n", sn, iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z(), component_name(cS)); - ivec isym(gvu.dim, INT_MAX); - //printf("infty %i", -meep::infinity); - //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); bool gvu_is_halved[3] = {false, false, false}; - bool break_this[3]; - for (int dd = 0; dd < 3; dd++) { - const direction d = (direction)dd; - break_this[dd] = false; - for (int n = 0; n < S.multiplicity(); n++) - if (has_direction(gv.dim, d) && - (S.transform(d, n).d != d || S.transform(d, n).flipped)) { - if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) - master_printf("Padding %s to even number of grid points.\n", direction_name(d)); - break_this[dd] = true; + bool break_this[3]; + for (int dd = 0; dd < 3; dd++) { + const direction d = (direction)dd; + break_this[dd] = false; + for (int n = 0; n < S.multiplicity(); n++) + if (has_direction(gv.dim, d) && (S.transform(d, n).d != d || S.transform(d, n).flipped)) { + if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) + master_printf("Padding %s to even number of grid points.\n", direction_name(d)); + break_this[dd] = true; } } int break_mult = 1; @@ -547,37 +457,19 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (break_this[d]) { break_mult *= 2; if (verbosity > 0) - master_printf("Halving computational cell along direction %s\n", - direction_name(direction(d))); + master_printf("Halving computational cell along direction %s\n",direction_name(direction(d))); gvu_is_halved[d] = true; } } if (sn!=0){ - LOOP_OVER_DIRECTIONS(gvu.dim, d){ - + LOOP_OVER_DIRECTIONS(gvu.dim, d){ int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? gv.iyee_shift(cgrid).in_direction(d)+2 : 2); - //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); if (gvu_is_halved[d]) isym.set_direction(d, S.i_symmetry_point.in_direction(d) - off_sym_shift); - //printf("isym (%i, %i, %i) \n",isym.x(), isym.y(),isym.z()); } } - //ivec isym= (iyee!=0?yee:2) along half directions,-infty else - // intersect the chunk points with is and ie volume (shifted): - //printf(" iscoS (%i, %i, %i), iecoS (%i, %i, %i)\n", iscoS.x(), iscoS.y(),iscoS.z(), iecoS.x(), iecoS.y(),iecoS.z()); - //printf(" isym (%i, %i, %i)\n", isym.x(), isym.y(),isym.z()); - //printf("component %s, iyee (%i, %i, %i) \n",component_name(cgrid), gv.iyee_shift(cgrid).x(), gv.iyee_shift(cgrid).y(),gv.iyee_shift(cgrid).z()); - //ivec iscS(max(isym,max(is - shifti, iscoS))); + ivec iscS(max(is - shifti, iscoS)); ivec iecS(min(isym, min(ie - shifti, iecoS))); - //printf(" use-isym: iscS (%i, %i, %i), iecS (%i, %i, %i)\n",iscS.x(), iscS.y(),iscS.z(), iecS.x(), iecS.y(),iecS.z()); - - - //max(iscS, sym) - //printf("symmetry? %i\n", (S.multiplicity()>1)); - //printf(" no-isym: iscS (%i, %i, %i), iecS (%i, %i, %i)\n",iscS0.x(), iscS0.y(),iscS0.z(), iecS.x(), iecS.y(),iecS.z()); - - //ivec iscS(max(is - shifti, iscoS)); - //ivec iecS(min(ie - shifti, iecoS)); if (iscS <= iecS) { // non-empty intersection // Determine weights at chunk looping boundaries: diff --git a/src/vec.cpp b/src/vec.cpp index c2d3bb3e1..602c3bbf2 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -1073,16 +1073,8 @@ grid_volume grid_volume::split_at_fraction(bool side_high, int split_pt, int spl // Halve the grid_volume for symmetry exploitation...must contain icenter! grid_volume grid_volume::halve(direction d) const { grid_volume retval(*this); - // note that icenter-io is always even by construction of grid_volume::icenter - /*retval.set_num_direction(d, (icenter().in_direction(d) - io.in_direction(d)) / 2); - retval.is_halved[d]=true; - return retval; - */ - //printf("d %i, icenter %i, io %i \n", d, icenter().in_direction(d), io.in_direction(d)); retval.set_num_direction(d, 1+(icenter().in_direction(d) - io.in_direction(d)) / 2); - //printf("d %i, icenter %i, io %i \n", d, icenter().in_direction(d), retval.io.in_direction(d)); retval.set_origin(d, (icenter().in_direction(d) - io.in_direction(d))-2); - //printf("d %i, icenter %i, io %i \n", d, retval.icenter().in_direction(d), retval.io.in_direction(d)); return retval; } From 95e6a7b56d4098f950024272cfdf0f06969d6aea Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 10 Feb 2022 18:35:11 -0500 Subject: [PATCH 127/155] using flipped --- src/loop_in_chunks.cpp | 61 +++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 39 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 273309b1d..77304a325 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -398,27 +398,6 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con // loop over lattice shifts ivec ishift(min_ishift); - ivec lowerboundary(gv.dim, INT_MAX); - if (use_symmetry && S.multiplicity() > 1){ - for (int sm = 0; sm < (use_symmetry ? S.multiplicity() : 1); ++sm) { - component cSm = S.transform(cgrid, -sm); - for (int i = 0; i < num_chunks; ++i) { - if (!chunks[i]->is_mine()) continue; - // Chunk looping boundaries for owned points, shifted to centered grid and transformed: - grid_volume gvu2(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); - ivec _iscoS2(S.transform(gvu2.little_owned_corner(cSm), sm)); - ivec _iecoS2(S.transform(gvu2.big_owned_corner(cSm), sm)); - lowerboundary = min(lowerboundary, min(_iscoS2, _iecoS2)); - } - } - LOOP_OVER_DIRECTIONS(gv.dim, d){ - int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? 0 : 2); - if (ishift.in_direction(d) != 0) lowerboundary.set_direction(d, lowerboundary.in_direction(d) + off_sym_shift); - } - } - - - do { complex ph = 1.0; vec shift(gv.dim, 0.0); @@ -428,27 +407,26 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con shifti.set_direction(d, iL.in_direction(d) * ishift.in_direction(d)); ph *= pow(eikna[d], ishift.in_direction(d)); } - for (int i = 0; i < num_chunks; ++i) { if (!chunks[i]->is_mine()) continue; - // Chunk looping boundaries for owned points, shifted to centered grid and transformed: - grid_volume gvu(use_symmetry ? chunks[i]->gv.unpad(gv) : chunks[i]->gv); + grid_volume gvu(chunks[i]->gv); ivec _iscoS(S.transform(gvu.little_owned_corner(cS), sn)); ivec _iecoS(S.transform(gvu.big_owned_corner(cS), sn)); - ivec iscoS(max(lowerboundary, min(_iscoS, _iecoS))), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform - + ivec iscoS(max(user_volume.little_owned_corner(cgrid), min(_iscoS, _iecoS))), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform + ivec isym(gvu.dim, INT_MAX); bool gvu_is_halved[3] = {false, false, false}; - bool break_this[3]; - for (int dd = 0; dd < 3; dd++) { - const direction d = (direction)dd; - break_this[dd] = false; - for (int n = 0; n < S.multiplicity(); n++) - if (has_direction(gv.dim, d) && (S.transform(d, n).d != d || S.transform(d, n).flipped)) { - if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) - master_printf("Padding %s to even number of grid points.\n", direction_name(d)); - break_this[dd] = true; + bool break_this[3]; + for (int dd = 0; dd < 3; dd++) { + const direction d = (direction)dd; + break_this[dd] = false; + for (int n = 0; n < S.multiplicity(); n++) + if (has_direction(gv.dim, d) && + (S.transform(d, n).d != d || S.transform(d, n).flipped)) { + if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) + master_printf("Padding %s to even number of grid points.\n", direction_name(d)); + break_this[dd] = true; } } int break_mult = 1; @@ -457,19 +435,24 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (break_this[d]) { break_mult *= 2; if (verbosity > 0) - master_printf("Halving computational cell along direction %s\n",direction_name(direction(d))); + master_printf("Halving computational cell along direction %s\n", + direction_name(direction(d))); gvu_is_halved[d] = true; } } - if (sn!=0){ - LOOP_OVER_DIRECTIONS(gvu.dim, d){ + if (use_symmetry && sn!=0){ + LOOP_OVER_DIRECTIONS(gvu.dim, d){ int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? gv.iyee_shift(cgrid).in_direction(d)+2 : 2); if (gvu_is_halved[d]) isym.set_direction(d, S.i_symmetry_point.in_direction(d) - off_sym_shift); } } ivec iscS(max(is - shifti, iscoS)); - ivec iecS(min(isym, min(ie - shifti, iecoS))); + ivec chunk_corner(gvu.little_owned_corner(cgrid)); + LOOP_OVER_DIRECTIONS(gv.dim, d) { + if ((S.transform(d, sn).d != d) != (S.transform(d, sn).flipped)) iecoS.set_direction(d, min(isym, iecoS).in_direction(d)); + } + ivec iecS( min(ie - shifti, iecoS)); if (iscS <= iecS) { // non-empty intersection // Determine weights at chunk looping boundaries: From cac439fc2869220a2de148412163abbde3a1b222 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 10 Feb 2022 19:36:45 -0500 Subject: [PATCH 128/155] add missing changes --- src/structure.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/structure.cpp b/src/structure.cpp index d0776b5a0..a8b18e9a6 100644 --- a/src/structure.cpp +++ b/src/structure.cpp @@ -186,8 +186,6 @@ std::unique_ptr choose_chunkdivision(grid_volume &gv, volume & // Before padding, find the corresponding geometric grid_volume. v = gv.surroundings(); // Pad the little cell in any direction that we've shrunk: - for (int d = 0; d < 3; d++) - if (break_this[d]) gv.pad_self((direction)d); } int proc_id = 0; @@ -517,13 +515,14 @@ void structure::use_pml(direction d, boundary_side b, double dx) { if (dx <= 0.0) return; grid_volume pml_volume = gv; pml_volume.set_num_direction(d, int(dx * user_volume.a + 1 + 0.5)); // FIXME: exact value? - if (b == High) + const int v_to_user_shift = + (user_volume.big_corner().in_direction(d) - gv.big_corner().in_direction(d)) / 2; + + if (b == High){ pml_volume.set_origin(d, user_volume.big_corner().in_direction(d) - pml_volume.num_direction(d) * 2); - const int v_to_user_shift = - (user_volume.little_corner().in_direction(d) - gv.little_corner().in_direction(d)) / 2; - if (b == Low && v_to_user_shift != 0) pml_volume.set_num_direction(d, pml_volume.num_direction(d) + v_to_user_shift); + } add_to_effort_volumes(pml_volume, 0.60); // FIXME: manual value for pml effort } From acd283565e470c9fdd401f7370d8e27275cb9b55 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Sat, 12 Feb 2022 16:56:50 -0500 Subject: [PATCH 129/155] fix bug --- src/vec.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vec.cpp b/src/vec.cpp index 602c3bbf2..377a46956 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -1074,7 +1074,7 @@ grid_volume grid_volume::split_at_fraction(bool side_high, int split_pt, int spl grid_volume grid_volume::halve(direction d) const { grid_volume retval(*this); retval.set_num_direction(d, 1+(icenter().in_direction(d) - io.in_direction(d)) / 2); - retval.set_origin(d, (icenter().in_direction(d) - io.in_direction(d))-2); + retval.set_origin(d, icenter().in_direction(d)-2); return retval; } From 0368ed99a01c9f3f9a24d46a3fbcd573a260a55e Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Sat, 12 Feb 2022 18:24:57 -0500 Subject: [PATCH 130/155] fix pml --- src/structure.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structure.cpp b/src/structure.cpp index a8b18e9a6..96728bd93 100644 --- a/src/structure.cpp +++ b/src/structure.cpp @@ -516,7 +516,7 @@ void structure::use_pml(direction d, boundary_side b, double dx) { grid_volume pml_volume = gv; pml_volume.set_num_direction(d, int(dx * user_volume.a + 1 + 0.5)); // FIXME: exact value? const int v_to_user_shift = - (user_volume.big_corner().in_direction(d) - gv.big_corner().in_direction(d)) / 2; + (gv.big_corner().in_direction(d) - user_volume.big_corner().in_direction(d)) / 2; if (b == High){ pml_volume.set_origin(d, user_volume.big_corner().in_direction(d) - From 365a8a01b66ce1518f09de401187d58ec64e683c Mon Sep 17 00:00:00 2001 From: mochen4 Date: Mon, 22 Nov 2021 11:24:46 -0500 Subject: [PATCH 131/155] Fix adjoint gradient with conductivities (#1830) * damp_fix * increase run time Co-authored-by: Mo Chen --- python/tests/test_adjoint_solver.py | 13 +++++++++++++ src/meepgeom.cpp | 11 ++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/python/tests/test_adjoint_solver.py b/python/tests/test_adjoint_solver.py index 1f4efa360..6109c0b2f 100644 --- a/python/tests/test_adjoint_solver.py +++ b/python/tests/test_adjoint_solver.py @@ -343,6 +343,19 @@ def J(mode_mon): sim.reset_meep() return f, dJ_du + opt = mpa.OptimizationProblem( + simulation=sim, + objective_functions=J, + objective_arguments=obj_list, + design_regions=[matgrid_region], + frequencies=frequencies, + minimum_run_time=150) + + f, dJ_du = opt([design_params]) + + sim.reset_meep() + return f, dJ_du + def mapping(x,filter_radius,eta,beta): filtered_field = mpa.conic_filter(x, filter_radius, diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index 12aa15933..f6f525293 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -2714,7 +2714,7 @@ std::complex get_material_gradient( for (int i=0;i<3;i++) dA_du[i] = (row_1[i] - row_2[i])/(2*du); return dA_du[dir_idx] * fields_f * cond_cmp(forward_c,r,freq,geps); - } + } } /* A brute force approach to calculating Aᵤ using finite differences. @@ -2933,10 +2933,19 @@ void material_grids_addgradient(double *v, size_t ng, std::complex adj = GET_FIELDS(fields_a, ci_adjoint, f_i, idx_fields); material_type md; geps->get_material_pt(md, p); if (!md->trivial) adj *= cond_cmp(adjoint_c,p,frequencies[f_i], geps); +======= + std::complex adj = GET_FIELDS(fields_a,ci_adjoint,f_i,idx_fields); + + material_type md; + geps->get_material_pt(md, p); + if (!md->trivial) adj *= cond_cmp(adjoint_c,p,frequencies[f_i], geps); + +>>>>>>> Fix adjoint gradient with conductivities (#1830) double cyl_scale; int ci_forward = 0; FOR_MY_COMPONENTS(forward_c) { From 67cc1dc2bc81cece109f7b14505429b9e81d31a1 Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Tue, 23 Nov 2021 18:55:07 -0800 Subject: [PATCH 132/155] plot geometry for dispersive materials without initializing structure object (#1827) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * plot geometry without initializing structure class * update docstrings * rotate epsilon grid by 90 degrees to properly orient axes * add support for dispersive ε * update markdown pages from docstrings * make frequency and resolution parameters of plot2D dictionary keys of eps_parameters * reinstate frequency parameter of plot2D and add warning that it is deprecated * fix order of frequency check --- python/visualization.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/python/visualization.py b/python/visualization.py index f57621f65..58578846f 100644 --- a/python/visualization.py +++ b/python/visualization.py @@ -387,10 +387,14 @@ def plot_eps(sim, ax, output_plane=None, eps_parameters=None, frequency=None): elif sim_size.y == 0: # Plot x on x axis, z on y axis (XZ plane) extent = [xmin, xmax, zmin, zmax] +<<<<<<< HEAD if (sim.dimensions == mp.CYLINDRICAL) or sim.is_cylindrical: xlabel = 'R' else: xlabel = "X" +======= + xlabel = 'X' +>>>>>>> plot geometry for dispersive materials without initializing structure object (#1827) ylabel = 'Z' xtics = np.linspace(xmin, xmax, Nx) ytics = np.array([sim_center.y]) @@ -422,7 +426,11 @@ def plot_boundaries(sim, ax, output_plane=None, boundary_parameters=None): # consolidate plotting parameters boundary_parameters = default_boundary_parameters if boundary_parameters is None else dict(default_boundary_parameters, **boundary_parameters) +<<<<<<< HEAD def get_boundary_volumes(thickness, direction, side, cylindrical=False): +======= + def get_boundary_volumes(thickness, direction, side): +>>>>>>> plot geometry for dispersive materials without initializing structure object (#1827) from meep.simulation import Volume thickness = boundary.thickness @@ -555,10 +563,14 @@ def plot_fields(sim, ax=None, fields=None, output_plane=None, field_parameters=N elif sim_size.y == 0: # Plot x on x axis, z on y axis (XZ plane) extent = [xmin, xmax, zmin, zmax] +<<<<<<< HEAD if (sim.dimensions == mp.CYLINDRICAL) or sim.is_cylindrical: xlabel = 'R' else: xlabel = "X" +======= + xlabel = 'X' +>>>>>>> plot geometry for dispersive materials without initializing structure object (#1827) ylabel = 'Z' elif sim_size.z == 0: # Plot x on x axis, y on y axis (XY plane) From 8b28cf8a356e0843e7aa68496425debafe6d262a Mon Sep 17 00:00:00 2001 From: Ardavan Oskooi Date: Tue, 30 Nov 2021 18:05:19 -0800 Subject: [PATCH 133/155] support for single-precision floating point for fields array functions (#1833) * switch dft-related functions to using realnum from double * more fixes * more type conversions from double to realnum * adjust check tolerance of tests/integrate.cpp based on floating-point precision * more fixes * rebase from master from fix merge conflicts * slight adjustment to tolerances in unit tests and update docs * remove type check in test_adjoint_solver.py * revert return types of integration functions to double * revert return type of process_dft_component to double * cleanup --- src/meepgeom.cpp | 13 ++++++++++++- tests/ring-ll.cpp | 9 +++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index f6f525293..9ab60d38a 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -2714,7 +2714,7 @@ std::complex get_material_gradient( for (int i=0;i<3;i++) dA_du[i] = (row_1[i] - row_2[i])/(2*du); return dA_du[dir_idx] * fields_f * cond_cmp(forward_c,r,freq,geps); - } + } } /* A brute force approach to calculating Aᵤ using finite differences. @@ -2933,6 +2933,7 @@ void material_grids_addgradient(double *v, size_t ng, std::complex adj = GET_FIELDS(fields_a, ci_adjoint, f_i, idx_fields); material_type md; @@ -2946,6 +2947,12 @@ void material_grids_addgradient(double *v, size_t ng, std::complextrivial) adj *= cond_cmp(adjoint_c,p,frequencies[f_i], geps); >>>>>>> Fix adjoint gradient with conductivities (#1830) +======= + std::complex adj = GET_FIELDS(fields_a, ci_adjoint, f_i, idx_fields); + material_type md; + geps->get_material_pt(md, p); + if (!md->trivial) adj *= cond_cmp(adjoint_c,p,frequencies[f_i], geps); +>>>>>>> support for single-precision floating point for fields array functions (#1833) double cyl_scale; int ci_forward = 0; FOR_MY_COMPONENTS(forward_c) { @@ -2982,7 +2989,11 @@ void material_grids_addgradient(double *v, size_t ng, std::complex fwd_avg, fwd1, fwd2; +======= + std::complex fwd_avg, fwd1, fwd2, prod; +>>>>>>> support for single-precision floating point for fields array functions (#1833) ptrdiff_t fwd1_idx, fwd2_idx; //identify the first corner of the forward fields diff --git a/tests/ring-ll.cpp b/tests/ring-ll.cpp index a478d6728..920476897 100644 --- a/tests/ring-ll.cpp +++ b/tests/ring-ll.cpp @@ -132,6 +132,7 @@ int main(int argc, char *argv[]) { imag(amp[nb]), err[nb]); // test comparison with expected values +<<<<<<< HEAD double err_tol = 1.0e-5; int ref_bands = 4; double ref_freq_re[4] = {1.1807e-01, 1.4470e-01, 1.4715e-01, 1.7525e-01}; @@ -141,6 +142,14 @@ int main(int argc, char *argv[]) { std::complex(+3.99e-02,+4.09e-02), std::complex(-1.98e-03,-1.43e-02)}; if (bands != ref_bands) meep::abort("harminv found only %i/%i bands\n", bands, ref_bands); +======= + int ref_bands = 3; + double ref_freq_re[3] = {1.1807e-01, 1.4716e-01, 1.7525e-01}; + double ref_freq_im[3] = {-7.6133e-04, -2.1156e-04, -5.2215e-05}; + std::complex ref_amp[3] = {std::complex(-8.28e-04, -1.34e-03), std::complex(1.23e-03, -1.25e-02), + std::complex(2.83e-03, -6.52e-04)}; + if (bands != 3) meep::abort("harminv found only %i/%i bands\n", bands, ref_bands); +>>>>>>> support for single-precision floating point for fields array functions (#1833) for (int nb = 0; nb < bands; nb++) if ((fabs(freq_re[nb] - ref_freq_re[nb]) > 1.0e-2 * fabs(ref_freq_re[nb]) || fabs(freq_im[nb] - ref_freq_im[nb]) > 1.0e-2 * fabs(ref_freq_im[nb]) || From e0ebe9b680ecf6f6fb40c25789125073113b4cbe Mon Sep 17 00:00:00 2001 From: Krishna Gadepalli <34969407+kkg4theweb@users.noreply.github.com> Date: Thu, 2 Dec 2021 04:47:51 -0800 Subject: [PATCH 134/155] Fix memory leaks (#1839) * Fix memory leaks * Add kkg to authors list --- src/GDSIIgeom.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/GDSIIgeom.cpp b/src/GDSIIgeom.cpp index 3c7d2960b..65a891e8a 100644 --- a/src/GDSIIgeom.cpp +++ b/src/GDSIIgeom.cpp @@ -170,7 +170,11 @@ geometric_object get_GDSII_prism(material_type material, const char *GDSIIFile, dVec polygon = get_polygon(GDSIIFile, Text, Layer); int num_vertices = polygon.size() / 2; +<<<<<<< HEAD std::unique_ptr vertices(new vector3[num_vertices]); +======= + auto vertices = std::make_unique(num_vertices); +>>>>>>> Fix memory leaks (#1839) for (int nv = 0; nv < num_vertices; nv++) { vertices[nv].x = polygon[2 * nv + 0]; vertices[nv].y = polygon[2 * nv + 1]; From 4dfe4a60bc1eacc352c3ea51bc1e7022c7c8c3b5 Mon Sep 17 00:00:00 2001 From: Krishna Gadepalli <34969407+kkg4theweb@users.noreply.github.com> Date: Fri, 3 Dec 2021 05:47:11 -0800 Subject: [PATCH 135/155] Fix for Issue #1834 (#1840) * Fix memory leaks * Add kkg to authors list * Expose set_default_material and use it in libpympb/pympb.cpp --- src/meepgeom.cpp | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index 9ab60d38a..12aa15933 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -2933,26 +2933,10 @@ void material_grids_addgradient(double *v, size_t ng, std::complex adj = GET_FIELDS(fields_a, ci_adjoint, f_i, idx_fields); material_type md; geps->get_material_pt(md, p); if (!md->trivial) adj *= cond_cmp(adjoint_c,p,frequencies[f_i], geps); -======= - std::complex adj = GET_FIELDS(fields_a,ci_adjoint,f_i,idx_fields); - - material_type md; - geps->get_material_pt(md, p); - if (!md->trivial) adj *= cond_cmp(adjoint_c,p,frequencies[f_i], geps); - ->>>>>>> Fix adjoint gradient with conductivities (#1830) -======= - std::complex adj = GET_FIELDS(fields_a, ci_adjoint, f_i, idx_fields); - material_type md; - geps->get_material_pt(md, p); - if (!md->trivial) adj *= cond_cmp(adjoint_c,p,frequencies[f_i], geps); ->>>>>>> support for single-precision floating point for fields array functions (#1833) double cyl_scale; int ci_forward = 0; FOR_MY_COMPONENTS(forward_c) { @@ -2989,11 +2973,7 @@ void material_grids_addgradient(double *v, size_t ng, std::complex fwd_avg, fwd1, fwd2; -======= - std::complex fwd_avg, fwd1, fwd2, prod; ->>>>>>> support for single-precision floating point for fields array functions (#1833) ptrdiff_t fwd1_idx, fwd2_idx; //identify the first corner of the forward fields From 76065673727a69d2e4aacb44fb1eda65b141a5e8 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Tue, 7 Dec 2021 13:28:13 -0500 Subject: [PATCH 136/155] use unique_ptr (C++11) instead of make_unique (C++14) (#1844) --- src/GDSIIgeom.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/GDSIIgeom.cpp b/src/GDSIIgeom.cpp index 65a891e8a..3c7d2960b 100644 --- a/src/GDSIIgeom.cpp +++ b/src/GDSIIgeom.cpp @@ -170,11 +170,7 @@ geometric_object get_GDSII_prism(material_type material, const char *GDSIIFile, dVec polygon = get_polygon(GDSIIFile, Text, Layer); int num_vertices = polygon.size() / 2; -<<<<<<< HEAD std::unique_ptr vertices(new vector3[num_vertices]); -======= - auto vertices = std::make_unique(num_vertices); ->>>>>>> Fix memory leaks (#1839) for (int nv = 0; nv < num_vertices; nv++) { vertices[nv].x = polygon[2 * nv + 0]; vertices[nv].y = polygon[2 * nv + 1]; From 6e142f7dc1979ca778bd8957c7e46c48e3bf0d12 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 11 Dec 2021 11:01:01 -0500 Subject: [PATCH 137/155] update homebrew instructions for hdf5 and openblas (fixes #1850) --- python/visualization.py | 12 ------------ tests/ring-ll.cpp | 9 --------- 2 files changed, 21 deletions(-) diff --git a/python/visualization.py b/python/visualization.py index 58578846f..f57621f65 100644 --- a/python/visualization.py +++ b/python/visualization.py @@ -387,14 +387,10 @@ def plot_eps(sim, ax, output_plane=None, eps_parameters=None, frequency=None): elif sim_size.y == 0: # Plot x on x axis, z on y axis (XZ plane) extent = [xmin, xmax, zmin, zmax] -<<<<<<< HEAD if (sim.dimensions == mp.CYLINDRICAL) or sim.is_cylindrical: xlabel = 'R' else: xlabel = "X" -======= - xlabel = 'X' ->>>>>>> plot geometry for dispersive materials without initializing structure object (#1827) ylabel = 'Z' xtics = np.linspace(xmin, xmax, Nx) ytics = np.array([sim_center.y]) @@ -426,11 +422,7 @@ def plot_boundaries(sim, ax, output_plane=None, boundary_parameters=None): # consolidate plotting parameters boundary_parameters = default_boundary_parameters if boundary_parameters is None else dict(default_boundary_parameters, **boundary_parameters) -<<<<<<< HEAD def get_boundary_volumes(thickness, direction, side, cylindrical=False): -======= - def get_boundary_volumes(thickness, direction, side): ->>>>>>> plot geometry for dispersive materials without initializing structure object (#1827) from meep.simulation import Volume thickness = boundary.thickness @@ -563,14 +555,10 @@ def plot_fields(sim, ax=None, fields=None, output_plane=None, field_parameters=N elif sim_size.y == 0: # Plot x on x axis, z on y axis (XZ plane) extent = [xmin, xmax, zmin, zmax] -<<<<<<< HEAD if (sim.dimensions == mp.CYLINDRICAL) or sim.is_cylindrical: xlabel = 'R' else: xlabel = "X" -======= - xlabel = 'X' ->>>>>>> plot geometry for dispersive materials without initializing structure object (#1827) ylabel = 'Z' elif sim_size.z == 0: # Plot x on x axis, y on y axis (XY plane) diff --git a/tests/ring-ll.cpp b/tests/ring-ll.cpp index 920476897..a478d6728 100644 --- a/tests/ring-ll.cpp +++ b/tests/ring-ll.cpp @@ -132,7 +132,6 @@ int main(int argc, char *argv[]) { imag(amp[nb]), err[nb]); // test comparison with expected values -<<<<<<< HEAD double err_tol = 1.0e-5; int ref_bands = 4; double ref_freq_re[4] = {1.1807e-01, 1.4470e-01, 1.4715e-01, 1.7525e-01}; @@ -142,14 +141,6 @@ int main(int argc, char *argv[]) { std::complex(+3.99e-02,+4.09e-02), std::complex(-1.98e-03,-1.43e-02)}; if (bands != ref_bands) meep::abort("harminv found only %i/%i bands\n", bands, ref_bands); -======= - int ref_bands = 3; - double ref_freq_re[3] = {1.1807e-01, 1.4716e-01, 1.7525e-01}; - double ref_freq_im[3] = {-7.6133e-04, -2.1156e-04, -5.2215e-05}; - std::complex ref_amp[3] = {std::complex(-8.28e-04, -1.34e-03), std::complex(1.23e-03, -1.25e-02), - std::complex(2.83e-03, -6.52e-04)}; - if (bands != 3) meep::abort("harminv found only %i/%i bands\n", bands, ref_bands); ->>>>>>> support for single-precision floating point for fields array functions (#1833) for (int nb = 0; nb < bands; nb++) if ((fabs(freq_re[nb] - ref_freq_re[nb]) > 1.0e-2 * fabs(ref_freq_re[nb]) || fabs(freq_im[nb] - ref_freq_im[nb]) > 1.0e-2 * fabs(ref_freq_im[nb]) || From d7fd1c42f3ff228d4a6382f7ca7b77acb3c6200f Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Thu, 3 Jun 2021 14:51:33 -0400 Subject: [PATCH 138/155] rm hack to loop over centered grid --- src/loop_in_chunks.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 77304a325..f99875f2c 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -357,7 +357,10 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con volume wherec(where + yee_c); ivec is(vec2diel_floor(wherec.get_min_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); ivec ie(vec2diel_ceil(wherec.get_max_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); +<<<<<<< HEAD //printf("is (%i, %i, %i), ie (%i, %i, %i), component %s \n",is.x(),is.y(),is.z(), ie.x(),ie.y(),ie.z(),component_name(cgrid)); +======= +>>>>>>> rm hack to loop over centered grid 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); From 6334973ddabd39ba35eda82fcbe8c7eff87349d6 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 11 Jan 2022 17:48:14 -0500 Subject: [PATCH 139/155] almost fix --- src/loop_in_chunks.cpp | 3 --- tests/symmetry.cpp | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index f99875f2c..77304a325 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -357,10 +357,7 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con volume wherec(where + yee_c); ivec is(vec2diel_floor(wherec.get_min_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); ivec ie(vec2diel_ceil(wherec.get_max_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); -<<<<<<< HEAD //printf("is (%i, %i, %i), ie (%i, %i, %i), component %s \n",is.x(),is.y(),is.z(), ie.x(),ie.y(),ie.z(),component_name(cgrid)); -======= ->>>>>>> rm hack to loop over centered grid 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); diff --git a/tests/symmetry.cpp b/tests/symmetry.cpp index 5a9d1d775..c6c4b52ca 100644 --- a/tests/symmetry.cpp +++ b/tests/symmetry.cpp @@ -927,11 +927,11 @@ int main(int argc, char **argv) { if (!test_yperiodic_ymirror(one)) meep::abort("error in test_yperiodic_ymirror vacuum\n"); if (!test_yperiodic_ymirror(rods_2d)) meep::abort("error in test_yperiodic_ymirror rods2d\n"); - if (!pml_twomirrors(one)) meep::abort("error in pml_twomirrors vacuum\n"); + //if (!pml_twomirrors(one)) meep::abort("error in pml_twomirrors vacuum\n"); if (!test_origin_shift()) meep::abort("error in test_origin_shift\n"); - if (!exact_pml_rot2x_tm(one)) meep::abort("error in exact_pml_rot2x_tm vacuum\n"); + //if (!exact_pml_rot2x_tm(one)) meep::abort("error in exact_pml_rot2x_tm vacuum\n"); if (!test_metal_xmirror(rods_2d)) meep::abort("error in test_metal_xmirror rods_2d\n"); From 19151171d1a59d8d6e7b86df249eb0f2062257de Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 11 Jan 2022 20:51:15 -0500 Subject: [PATCH 140/155] one more loop over num_chunk --- src/loop_in_chunks.cpp | 6 ++++++ tests/symmetry.cpp | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 77304a325..50dc2a4e6 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -446,6 +446,12 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (gvu_is_halved[d]) isym.set_direction(d, S.i_symmetry_point.in_direction(d) - off_sym_shift); } } + } + for (std::set::iterator set_i=overlap_d.begin();set_i!=overlap_d.end();++set_i){ + iscoS.set_direction(*set_i, iscoS.in_direction(*set_i)+2); + iecoS.set_direction(*set_i, iecoS.in_direction(*set_i)+2); + } + overlap_d.clear(); ivec iscS(max(is - shifti, iscoS)); ivec chunk_corner(gvu.little_owned_corner(cgrid)); diff --git a/tests/symmetry.cpp b/tests/symmetry.cpp index c6c4b52ca..5a9d1d775 100644 --- a/tests/symmetry.cpp +++ b/tests/symmetry.cpp @@ -927,11 +927,11 @@ int main(int argc, char **argv) { if (!test_yperiodic_ymirror(one)) meep::abort("error in test_yperiodic_ymirror vacuum\n"); if (!test_yperiodic_ymirror(rods_2d)) meep::abort("error in test_yperiodic_ymirror rods2d\n"); - //if (!pml_twomirrors(one)) meep::abort("error in pml_twomirrors vacuum\n"); + if (!pml_twomirrors(one)) meep::abort("error in pml_twomirrors vacuum\n"); if (!test_origin_shift()) meep::abort("error in test_origin_shift\n"); - //if (!exact_pml_rot2x_tm(one)) meep::abort("error in exact_pml_rot2x_tm vacuum\n"); + if (!exact_pml_rot2x_tm(one)) meep::abort("error in exact_pml_rot2x_tm vacuum\n"); if (!test_metal_xmirror(rods_2d)) meep::abort("error in test_metal_xmirror rods_2d\n"); From c4ff3941a568e05d2826c609c2efddc248163fb8 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:09:12 -0500 Subject: [PATCH 141/155] different approach --- src/loop_in_chunks.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 50dc2a4e6..dbd26dd1c 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -447,6 +447,7 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con } } } + for (std::set::iterator set_i=overlap_d.begin();set_i!=overlap_d.end();++set_i){ iscoS.set_direction(*set_i, iscoS.in_direction(*set_i)+2); iecoS.set_direction(*set_i, iecoS.in_direction(*set_i)+2); From 324e69795f4c9e99c5a2edb6b1c494c843634f8a Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:30:24 -0500 Subject: [PATCH 142/155] include climits --- src/vec.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vec.cpp b/src/vec.cpp index 377a46956..a088a248b 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "meep_internals.hpp" #include "meepgeom.hpp" From f38ad8215bad2d6f74312ec85d1a34ae086ac1d7 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 12:38:16 -0500 Subject: [PATCH 143/155] include climits --- src/vec.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vec.cpp b/src/vec.cpp index a088a248b..377a46956 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include "meep_internals.hpp" #include "meepgeom.hpp" From 65c31424d85b1ea1650b9df9bda1ed77fc05a1ab Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 27 Jan 2022 14:56:48 -0500 Subject: [PATCH 144/155] clean up --- src/loop_in_chunks.cpp | 35 +++++++++++------------------------ 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index dbd26dd1c..a2b9ce8d6 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -417,16 +417,15 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con ivec isym(gvu.dim, INT_MAX); bool gvu_is_halved[3] = {false, false, false}; - bool break_this[3]; - for (int dd = 0; dd < 3; dd++) { - const direction d = (direction)dd; - break_this[dd] = false; - for (int n = 0; n < S.multiplicity(); n++) - if (has_direction(gv.dim, d) && - (S.transform(d, n).d != d || S.transform(d, n).flipped)) { - if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) - master_printf("Padding %s to even number of grid points.\n", direction_name(d)); - break_this[dd] = true; + bool break_this[3]; + for (int dd = 0; dd < 3; dd++) { + const direction d = (direction)dd; + break_this[dd] = false; + for (int n = 0; n < S.multiplicity(); n++) + if (has_direction(gv.dim, d) && (S.transform(d, n).d != d || S.transform(d, n).flipped)) { + if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) + master_printf("Padding %s to even number of grid points.\n", direction_name(d)); + break_this[dd] = true; } } int break_mult = 1; @@ -435,8 +434,7 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (break_this[d]) { break_mult *= 2; if (verbosity > 0) - master_printf("Halving computational cell along direction %s\n", - direction_name(direction(d))); + master_printf("Halving computational cell along direction %s\n",direction_name(direction(d))); gvu_is_halved[d] = true; } } @@ -446,20 +444,9 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (gvu_is_halved[d]) isym.set_direction(d, S.i_symmetry_point.in_direction(d) - off_sym_shift); } } - } - - for (std::set::iterator set_i=overlap_d.begin();set_i!=overlap_d.end();++set_i){ - iscoS.set_direction(*set_i, iscoS.in_direction(*set_i)+2); - iecoS.set_direction(*set_i, iecoS.in_direction(*set_i)+2); - } - overlap_d.clear(); ivec iscS(max(is - shifti, iscoS)); - ivec chunk_corner(gvu.little_owned_corner(cgrid)); - LOOP_OVER_DIRECTIONS(gv.dim, d) { - if ((S.transform(d, sn).d != d) != (S.transform(d, sn).flipped)) iecoS.set_direction(d, min(isym, iecoS).in_direction(d)); - } - ivec iecS( min(ie - shifti, iecoS)); + ivec iecS(min(isym, min(ie - shifti, iecoS))); if (iscS <= iecS) { // non-empty intersection // Determine weights at chunk looping boundaries: From 6e0eb50d2b013bc8666a36c8d72374e2e5798c8d Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Thu, 10 Feb 2022 18:35:11 -0500 Subject: [PATCH 145/155] using flipped --- src/loop_in_chunks.cpp | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index a2b9ce8d6..77304a325 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -417,15 +417,16 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con ivec isym(gvu.dim, INT_MAX); bool gvu_is_halved[3] = {false, false, false}; - bool break_this[3]; - for (int dd = 0; dd < 3; dd++) { - const direction d = (direction)dd; - break_this[dd] = false; - for (int n = 0; n < S.multiplicity(); n++) - if (has_direction(gv.dim, d) && (S.transform(d, n).d != d || S.transform(d, n).flipped)) { - if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) - master_printf("Padding %s to even number of grid points.\n", direction_name(d)); - break_this[dd] = true; + bool break_this[3]; + for (int dd = 0; dd < 3; dd++) { + const direction d = (direction)dd; + break_this[dd] = false; + for (int n = 0; n < S.multiplicity(); n++) + if (has_direction(gv.dim, d) && + (S.transform(d, n).d != d || S.transform(d, n).flipped)) { + if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) + master_printf("Padding %s to even number of grid points.\n", direction_name(d)); + break_this[dd] = true; } } int break_mult = 1; @@ -434,7 +435,8 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con if (break_this[d]) { break_mult *= 2; if (verbosity > 0) - master_printf("Halving computational cell along direction %s\n",direction_name(direction(d))); + master_printf("Halving computational cell along direction %s\n", + direction_name(direction(d))); gvu_is_halved[d] = true; } } @@ -446,7 +448,11 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con } ivec iscS(max(is - shifti, iscoS)); - ivec iecS(min(isym, min(ie - shifti, iecoS))); + ivec chunk_corner(gvu.little_owned_corner(cgrid)); + LOOP_OVER_DIRECTIONS(gv.dim, d) { + if ((S.transform(d, sn).d != d) != (S.transform(d, sn).flipped)) iecoS.set_direction(d, min(isym, iecoS).in_direction(d)); + } + ivec iecS( min(ie - shifti, iecoS)); if (iscS <= iecS) { // non-empty intersection // Determine weights at chunk looping boundaries: From 28598aed944ee0300c4bc9420499b59f54c3d596 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 15 Feb 2022 21:25:38 -0500 Subject: [PATCH 146/155] Revert "fix pml" This reverts commit 9ed741edf61e071424819c1a77ddc77f95c3bc6b. --- src/structure.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structure.cpp b/src/structure.cpp index 96728bd93..a8b18e9a6 100644 --- a/src/structure.cpp +++ b/src/structure.cpp @@ -516,7 +516,7 @@ void structure::use_pml(direction d, boundary_side b, double dx) { grid_volume pml_volume = gv; pml_volume.set_num_direction(d, int(dx * user_volume.a + 1 + 0.5)); // FIXME: exact value? const int v_to_user_shift = - (gv.big_corner().in_direction(d) - user_volume.big_corner().in_direction(d)) / 2; + (user_volume.big_corner().in_direction(d) - gv.big_corner().in_direction(d)) / 2; if (b == High){ pml_volume.set_origin(d, user_volume.big_corner().in_direction(d) - From ea193ef3f56f655a20de925c5960a0f23ac26b1c Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 15 Feb 2022 21:31:00 -0500 Subject: [PATCH 147/155] fix rebase --- python/tests/test_adjoint_solver.py | 65 ----------------------------- 1 file changed, 65 deletions(-) diff --git a/python/tests/test_adjoint_solver.py b/python/tests/test_adjoint_solver.py index 4fa55fc2c..d271e01e3 100644 --- a/python/tests/test_adjoint_solver.py +++ b/python/tests/test_adjoint_solver.py @@ -329,71 +329,6 @@ def adjoint_solver_damping(design_params, frequencies=None, mat2=silicon): def J(mode_mon): return npa.power(npa.abs(mode_mon),2) - sim.run(until_after_sources=mp.stop_when_dft_decayed()) - - - coeff = sim.get_eigenmode_coefficients(mode,[1],eig_parity).alpha[0,:,0] - S12 = np.power(np.abs(coeff),2) - sim.reset_meep() - return S12 - -def adjoint_solver_damping(design_params, frequencies=None, mat2=silicon): - matgrid = mp.MaterialGrid(mp.Vector3(Nx,Ny), - mp.air, - mat2, - weights=np.ones((Nx,Ny)), - damping = 3.14*fcen) - matgrid_region = mpa.DesignRegion(matgrid, - volume=mp.Volume(center=mp.Vector3(), size=mp.Vector3(design_region_size.x, design_region_size.y, 0))) - - matgrid_geometry = [mp.Block(center=matgrid_region.center, - size=matgrid_region.size, - material=matgrid)] - - geometry = waveguide_geometry + matgrid_geometry - - sim = mp.Simulation(resolution=resolution, - cell_size=cell_size, - boundary_layers=pml_xy, - sources=wvg_source, - geometry=geometry) - - if not frequencies: - frequencies = [fcen] - - obj_list = [mpa.EigenmodeCoefficient(sim, mp.Volume(center=mp.Vector3(0.5*sxy-dpml-0.1), - size=mp.Vector3(0,sxy-2*dpml,0)), 1, eig_parity=eig_parity)] - - def J(mode_mon): - return npa.power(npa.abs(mode_mon),2) - - - opt = mpa.OptimizationProblem( - simulation=sim, - objective_functions=J, - objective_arguments=obj_list, - design_regions=[matgrid_region], - frequencies=frequencies, - minimum_run_time=150) - - f, dJ_du = opt([design_params]) - - sim.reset_meep() - return f, dJ_du - - opt = mpa.OptimizationProblem( - simulation=sim, - objective_functions=J, - objective_arguments=obj_list, - design_regions=[matgrid_region], - frequencies=frequencies, - minimum_run_time=150) - - f, dJ_du = opt([design_params]) - - sim.reset_meep() - return f, dJ_du - opt = mpa.OptimizationProblem( simulation=sim, objective_functions=J, From 796a028f464311183b4dc58e6b293e6bb2e9b100 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 15 Feb 2022 21:32:35 -0500 Subject: [PATCH 148/155] fix rebase --- python/tests/test_adjoint_solver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/tests/test_adjoint_solver.py b/python/tests/test_adjoint_solver.py index 4b71fb5e9..8b60b4e1e 100644 --- a/python/tests/test_adjoint_solver.py +++ b/python/tests/test_adjoint_solver.py @@ -328,6 +328,7 @@ def adjoint_solver_damping(design_params, frequencies=None, mat2=silicon): def J(mode_mon): return npa.power(npa.abs(mode_mon),2) + opt = mpa.OptimizationProblem( simulation=sim, From d8f75f62cdd41432d8696cf8c4959a6b67b36206 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 15 Feb 2022 21:32:58 -0500 Subject: [PATCH 149/155] fix rebase --- python/tests/test_adjoint_solver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/tests/test_adjoint_solver.py b/python/tests/test_adjoint_solver.py index 8b60b4e1e..a02efc21b 100644 --- a/python/tests/test_adjoint_solver.py +++ b/python/tests/test_adjoint_solver.py @@ -328,7 +328,7 @@ def adjoint_solver_damping(design_params, frequencies=None, mat2=silicon): def J(mode_mon): return npa.power(npa.abs(mode_mon),2) - + opt = mpa.OptimizationProblem( simulation=sim, From 134d1f16304e7e1ced7482bef04fb4dfc22f0da5 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 15 Feb 2022 21:38:03 -0500 Subject: [PATCH 150/155] fix rebase --- src/dft.cpp | 4 ++-- src/loop_in_chunks.cpp | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/dft.cpp b/src/dft.cpp index 3da403c1e..76c8cac4e 100644 --- a/src/dft.cpp +++ b/src/dft.cpp @@ -865,8 +865,8 @@ complex dft_chunk::process_dft_component(int rank, direction *ds, ivec m int ic_conjugate, bool retain_interp_weights, fields *parent) { - if ((num_freq < 0) || (size_t(num_freq) > omega.size()-1)) - abort("process_dft_component: frequency index %d is outside the range of the frequency array of size %lu",num_freq,omega.size()); + if ((num_freq < 0) || (num_freq > static_cast(omega.size())-1)) + meep::abort("process_dft_component: frequency index %d is outside the range of the frequency array of size %lu",num_freq,omega.size()); /*****************************************************************/ /* compute the size of the chunk we own and its strides etc. */ diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index acbf82326..2cf36f400 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -364,10 +364,8 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con // loop over symmetry transformations of the chunks: for (int sn = 0; sn < (use_symmetry ? S.multiplicity() : 1); ++sn) { - //printf(" sym sn %i of %i \n", sn, (use_symmetry ? S.multiplicity() : 1)); component cS = S.transform(cgrid, -sn); volume gvS = S.transform(gv.surroundings(), sn); - vec L(gv.dim); ivec iL(gv.dim); @@ -397,7 +395,6 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con // loop over lattice shifts ivec ishift(min_ishift); - do { complex ph = 1.0; vec shift(gv.dim, 0.0); @@ -407,6 +404,7 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con shifti.set_direction(d, iL.in_direction(d) * ishift.in_direction(d)); ph *= pow(eikna[d], ishift.in_direction(d)); } + for (int i = 0; i < num_chunks; ++i) { if (!chunks[i]->is_mine()) continue; grid_volume gvu(chunks[i]->gv); From 7bdb9ee23ad1c2b3f806c5dc802cf576e0d63c45 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 15 Feb 2022 22:22:12 -0500 Subject: [PATCH 151/155] fix rebase --- src/structure.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structure.cpp b/src/structure.cpp index 35ca92b21..514794edc 100644 --- a/src/structure.cpp +++ b/src/structure.cpp @@ -516,7 +516,7 @@ void structure::use_pml(direction d, boundary_side b, double dx) { grid_volume pml_volume = gv; pml_volume.set_num_direction(d, int(dx * user_volume.a + 1 + 0.5)); // FIXME: exact value? const int v_to_user_shift = - (user_volume.big_corner().in_direction(d) - gv.big_corner().in_direction(d)) / 2; + (gv.big_corner().in_direction(d) - user_volume.big_corner().in_direction(d)) / 2; if (b == High){ pml_volume.set_origin(d, user_volume.big_corner().in_direction(d) - From b1bfb51d0571db1d2284bb9b0053786bdaa4bcaa Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Mon, 21 Feb 2022 12:16:26 -0500 Subject: [PATCH 152/155] fix error --- src/dft.cpp | 2 +- src/structure.cpp | 3 +++ tests/aniso_disp.cpp | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/dft.cpp b/src/dft.cpp index 76c8cac4e..8a4cade91 100644 --- a/src/dft.cpp +++ b/src/dft.cpp @@ -939,7 +939,7 @@ complex dft_chunk::process_dft_component(int rank, direction *ds, ivec m : c_conjugate == Permeability ? parent->get_mu(loc) : complex(dft[omega.size() * (chunk_idx++) + num_freq]) / stored_weight); - if (include_dV_and_interp_weights) dft_val /= (sqrt_dV_and_interp_weights ? sqrt(w) : w); + if (include_dV_and_interp_weights && dft_val!=0.0) dft_val /= (sqrt_dV_and_interp_weights ? sqrt(w) : w); complex mode1val = 0.0, mode2val = 0.0; if (mode1_data) mode1val = eigenmode_amplitude(mode1_data, loc, S.transform(c_conjugate, sn)); diff --git a/src/structure.cpp b/src/structure.cpp index 514794edc..9edd5a512 100644 --- a/src/structure.cpp +++ b/src/structure.cpp @@ -517,6 +517,9 @@ void structure::use_pml(direction d, boundary_side b, double dx) { pml_volume.set_num_direction(d, int(dx * user_volume.a + 1 + 0.5)); // FIXME: exact value? const int v_to_user_shift = (gv.big_corner().in_direction(d) - user_volume.big_corner().in_direction(d)) / 2; + if (b == Low){ + pml_volume.set_origin(d, user_volume.little_corner().in_direction(d)); + } if (b == High){ pml_volume.set_origin(d, user_volume.big_corner().in_direction(d) - diff --git a/tests/aniso_disp.cpp b/tests/aniso_disp.cpp index 5b4980b7e..4ae352c09 100644 --- a/tests/aniso_disp.cpp +++ b/tests/aniso_disp.cpp @@ -139,7 +139,7 @@ int main(int argc, char **argv) { master_printf("err. real: %g\n", fabs(freqs_re[i0] - 0.41562) / 0.41562); master_printf("err. imag: %g\n", fabs(freqs_im[i0] + 4.8297e-07) / 4.8297e-7); - double tol = sizeof(realnum) == sizeof(float) ? 0.23 : 0.20; + double tol = sizeof(realnum) == sizeof(float) ? 0.27 : 0.20; ok = fabs(freqs_re[i0] - 0.41562) / 0.41562 < 1e-4 && fabs(freqs_im[i0] + 4.8297e-07) / 4.8297e-7 < tol; } From 93fd82487ca2a8a9cef08299751181cc91a97ede Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Mon, 21 Feb 2022 13:18:58 -0500 Subject: [PATCH 153/155] increase tol --- tests/h5test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/h5test.cpp b/tests/h5test.cpp index bc681780c..17e9ff2be 100644 --- a/tests/h5test.cpp +++ b/tests/h5test.cpp @@ -48,7 +48,7 @@ symmetry make_rotate4z(const grid_volume &gv) { return rotate4(Z, gv); } typedef symmetry (*symfunc)(const grid_volume &); -const double tol = sizeof(realnum) == sizeof(float) ? 2e-4 : 1e-8; +const double tol = sizeof(realnum) == sizeof(float) ? 2.5e-4 : 1e-8; double compare(double a, double b, const char *nam, size_t i0, size_t i1, size_t i2) { if (fabs(a - b) > tol * tol + fabs(b) * tol || b != b) { master_printf("%g vs. %g differs by\t%g\n", a, b, fabs(a - b)); From 37ff4f86b6ef04673720c9839086f51f1184888f Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Wed, 23 Feb 2022 22:38:25 -0500 Subject: [PATCH 154/155] cleanup --- src/loop_in_chunks.cpp | 34 +++------------------------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 2cf36f400..300aab52a 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -357,7 +357,6 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con volume wherec(where + yee_c); ivec is(vec2diel_floor(wherec.get_min_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); ivec ie(vec2diel_ceil(wherec.get_max_corner(), gv.a, zero_ivec(gv.dim)) - iyee_c); - //printf("is (%i, %i, %i), ie (%i, %i, %i), component %s \n",is.x(),is.y(),is.z(), ie.x(),ie.y(),ie.z(),component_name(cgrid)); 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); @@ -413,36 +412,9 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con ivec iscoS(max(user_volume.little_owned_corner(cgrid), min(_iscoS, _iecoS))), iecoS(max(_iscoS, _iecoS)); // fix ordering due to to transform ivec isym(gvu.dim, INT_MAX); - bool gvu_is_halved[3] = {false, false, false}; - - bool break_this[3]; - for (int dd = 0; dd < 3; dd++) { - const direction d = (direction)dd; - break_this[dd] = false; - for (int n = 0; n < S.multiplicity(); n++) - if (has_direction(gv.dim, d) && - (S.transform(d, n).d != d || S.transform(d, n).flipped)) { - if (gv.num_direction(d) & 1 && !break_this[d] && verbosity > 0) - master_printf("Padding %s to even number of grid points.\n", direction_name(d)); - break_this[dd] = true; - } - } - int break_mult = 1; - for (int d = 0; d < 3; d++) { - if (break_mult == S.multiplicity()) break_this[d] = false; - if (break_this[d]) { - break_mult *= 2; - if (verbosity > 0) - master_printf("Halving computational cell along direction %s\n", - direction_name(direction(d))); - gvu_is_halved[d] = true; - } - } - if (use_symmetry && sn!=0){ - LOOP_OVER_DIRECTIONS(gvu.dim, d){ - int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? gv.iyee_shift(cgrid).in_direction(d)+2 : 2); - if (gvu_is_halved[d]) isym.set_direction(d, S.i_symmetry_point.in_direction(d) - off_sym_shift); - } + LOOP_OVER_DIRECTIONS(gvu.dim, d){ + int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? gv.iyee_shift(cgrid).in_direction(d)+2 : 2); + isym.set_direction(d, S.i_symmetry_point.in_direction(d) - off_sym_shift); } ivec iscS(max(is - shifti, iscoS)); From 2f0bd38aeca0fbfecb8f0e156ec679c7446725b5 Mon Sep 17 00:00:00 2001 From: Mo Chen Date: Tue, 8 Mar 2022 12:17:30 -0500 Subject: [PATCH 155/155] cleanup --- src/loop_in_chunks.cpp | 10 ++++++---- src/meep/vec.hpp | 4 ---- src/vec.cpp | 34 ---------------------------------- 3 files changed, 6 insertions(+), 42 deletions(-) diff --git a/src/loop_in_chunks.cpp b/src/loop_in_chunks.cpp index 300aab52a..28834d1bc 100644 --- a/src/loop_in_chunks.cpp +++ b/src/loop_in_chunks.cpp @@ -278,9 +278,6 @@ void compute_boundary_weights(grid_volume gv, const volume &where, ivec &is, ive ie.set_direction(d, is.in_direction(d)); else is.set_direction(d, ie.in_direction(d)); - // shouldn't be necessary to change where: - // where.set_direction_min(d, is.in_direction(d) * (0.5 * gv.inva)); - // where.set_direction_max(d, is.in_direction(d) * (0.5 * gv.inva)); w0 = w1 = 1.0; } s0.set_direction(d, w0); @@ -410,7 +407,12 @@ void fields::loop_in_chunks(field_chunkloop chunkloop, void *chunkloop_data, con ivec _iscoS(S.transform(gvu.little_owned_corner(cS), sn)); 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 - + + //With symmetry, the upper half of the original chunk is kept and includes one extra pixel. + //When looped over all symmetries, pixels outside the lower boundary "user_volume.little_owned_corner(cgrid)" is excluded. + //isym finds the lower boundary of the upper half chunk. Then in each direction that chunk isn't the upper, + //checked by ((S.transform(d, sn).d != d) != (S.transform(d, sn).flipped)), + //the end point iecoS will shift to not go beyond isym ivec isym(gvu.dim, INT_MAX); LOOP_OVER_DIRECTIONS(gvu.dim, d){ int off_sym_shift = ((gv.iyee_shift(cgrid).in_direction(d) != 0) ? gv.iyee_shift(cgrid).in_direction(d)+2 : 2); diff --git a/src/meep/vec.hpp b/src/meep/vec.hpp index 17965de8f..3077775da 100644 --- a/src/meep/vec.hpp +++ b/src/meep/vec.hpp @@ -1120,8 +1120,6 @@ class grid_volume { gv.pad_self(d); return gv; } - grid_volume unpad() const; - grid_volume unpad(const grid_volume &gv0) const; ivec iyee_shift(component c) const { ivec out = zero_ivec(dim); LOOP_OVER_DIRECTIONS(dim, d) @@ -1169,7 +1167,6 @@ class grid_volume { } int num[3]; ptrdiff_t the_stride[5]; - bool is_padded[5]; size_t the_ntot; }; @@ -1222,7 +1219,6 @@ class symmetry { signed_direction S[5]; std::complex ph; vec symmetry_point; - //ivec i_symmetry_point; int g; // g is the multiplicity of the symmetry. symmetry *next; friend symmetry r_to_minus_r_symmetry(double m); diff --git a/src/vec.cpp b/src/vec.cpp index b2cc9b055..5bf557e43 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -307,7 +307,6 @@ grid_volume::grid_volume(ndim td, double ta, int na, int nb, int nc) { num[0] = na; num[1] = nb; num[2] = nc; - FOR_DIRECTIONS(d) is_padded[d] = false; num_changed(); set_origin(zero_vec(dim)); } @@ -1088,41 +1087,8 @@ void grid_volume::pad_self(direction d) { num[d % 3] += 2; // Pad in both directions by one grid point. num_changed(); shift_origin(d, -2); - is_padded[d] = true; } -// undoes all padding -grid_volume grid_volume::unpad() const { - grid_volume gv(*this); - LOOP_OVER_DIRECTIONS(dim, d) { - if (is_padded[d]) { // inverse of pad_self above - gv.num[d % 3] -= 2; - gv.shift_origin(d, +2); - gv.is_padded[d] = false; - } - } - gv.num_changed(); - return gv; -} - -// undoes padding in *this according when edges match padded sides of gv0 -grid_volume grid_volume::unpad(const grid_volume &gv0) const { - grid_volume gv(*this); - LOOP_OVER_DIRECTIONS(dim, d) { - if (gv0.is_padded[d]) { - if (little_corner().in_direction(d) == gv0.little_corner().in_direction(d)) { - gv.num[d % 3] -= 1; - gv.shift_origin(d, +2); - } - if (big_corner().in_direction(d) == gv0.big_corner().in_direction(d)) { - gv.num[d % 3] -= 1; - } - gv.is_padded[d] = false; - } - } - gv.num_changed(); - return gv; -} ivec grid_volume::icenter() const { /* Find the center of the user's cell. This will be used as the