From 4582204b68938e90a8e6a2e522881b19444c78c3 Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Mon, 13 May 2019 15:42:16 -0600 Subject: [PATCH 01/25] first stab --- src/meep.hpp | 34 ++++++++++++++++++++-------------- src/monitor.cpp | 4 ++-- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/meep.hpp b/src/meep.hpp index 94fd8f03b..83c606ac2 100644 --- a/src/meep.hpp +++ b/src/meep.hpp @@ -89,6 +89,9 @@ class susceptibility { int get_id() const { return id; } bool operator==(const susceptibility &s) const { return id == s.id; }; + // Returns the 1st order nonlinear susceptibility (generic) + virtual std::complex chi1(double freq, double sigma=1); + // update all of the internal polarization state given the W field // at the current time step, possibly the previous field W_prev, etc. virtual void update_P(realnum *W[NUM_FIELD_COMPONENTS][2], @@ -229,6 +232,9 @@ class lorentzian_susceptibility : public susceptibility { virtual susceptibility *clone() const { return new lorentzian_susceptibility(*this); } virtual ~lorentzian_susceptibility() {} + // Returns the 1st order nonlinear susceptibility + virtual std::complex chi1(double freq, double sigma=1); + virtual void update_P(realnum *W[NUM_FIELD_COMPONENTS][2], realnum *W_prev[NUM_FIELD_COMPONENTS][2], double dt, const grid_volume &gv, void *P_internal_data) const; @@ -555,9 +561,9 @@ class structure_chunk { void remove_susceptibilities(); // monitor.cpp - double get_chi1inv(component, direction, const ivec &iloc) const; - double get_inveps(component c, direction d, const ivec &iloc) const { - return get_chi1inv(c, d, iloc); + double get_chi1inv(component, direction, const ivec &iloc, double omega = 0) const; + double get_inveps(component c, direction d, const ivec &iloc, double omega = 0) const { + return get_chi1inv(c, d, iloc, omega); } double max_eps() const; @@ -721,15 +727,15 @@ class structure { void load_chunk_layout(const std::vector &gvs, boundary_region &br); // monitor.cpp - double get_chi1inv(component, direction, const ivec &origloc, bool parallel = true) const; - double get_chi1inv(component, direction, const vec &loc, bool parallel = true) const; - double get_inveps(component c, direction d, const ivec &origloc) const { - return get_chi1inv(c, d, origloc); + double get_chi1inv(component, direction, const ivec &origloc, double omega = 0, bool parallel = true) const; + double get_chi1inv(component, direction, const vec &loc, double omega = 0, bool parallel = true) const; + double get_inveps(component c, direction d, const ivec &origloc, double omega = 0) const { + return get_chi1inv(c, d, origloc, omega); } - double get_inveps(component c, direction d, const vec &loc) const { - return get_chi1inv(c, d, loc); + double get_inveps(component c, direction d, const vec &loc, double omega = 0) const { + return get_chi1inv(c, d, loc, omega); } - double get_eps(const vec &loc) const; + double get_eps(const vec &loc, double omega = 0) const; double get_mu(const vec &loc) const; double max_eps() const; @@ -1291,7 +1297,7 @@ class fields_chunk { // monitor.cpp std::complex get_field(component, const ivec &) const; - double get_chi1inv(component, direction, const ivec &iloc) const; + double get_chi1inv(component, direction, const ivec &iloc, double omega = 0) const; void backup_component(component c); void average_with_backup(component c); @@ -1736,9 +1742,9 @@ class fields { dft_near2far add_dft_near2far(const volume_list *where, double freq_min, double freq_max, int Nfreq, int Nperiods = 1); // monitor.cpp - double get_chi1inv(component, direction, const vec &loc, bool parallel = true) const; - double get_inveps(component c, direction d, const vec &loc) const { - return get_chi1inv(c, d, loc); + double get_chi1inv(component, direction, const vec &loc, double omega = 0, bool parallel = true) const; + double get_inveps(component c, direction d, const vec &loc, double omega = 0) const { + return get_chi1inv(c, d, loc, omega); } double get_eps(const vec &loc) const; double get_mu(const vec &loc) const; diff --git a/src/monitor.cpp b/src/monitor.cpp index 68a3089ef..e86a0367a 100644 --- a/src/monitor.cpp +++ b/src/monitor.cpp @@ -160,7 +160,7 @@ complex fields_chunk::get_field(component c, const ivec &iloc) const { return 0.0; } -double fields::get_chi1inv(component c, direction d, const ivec &origloc, bool parallel) const { +double fields::get_chi1inv(component c, direction d, const ivec &origloc, double omega, bool parallel) const { ivec iloc = origloc; complex aaack = 1.0; locate_point_in_user_volume(&iloc, &aaack); @@ -169,7 +169,7 @@ double fields::get_chi1inv(component c, direction d, const ivec &origloc, bool p if (chunks[i]->gv.owns(S.transform(iloc, sn))) { signed_direction ds = S.transform(d, sn); double val = chunks[i]->get_chi1inv(S.transform(c, sn), ds.d, S.transform(iloc, sn)) * - (ds.flipped ^ S.transform(component_direction(c), sn).flipped ? -1 : 1); + (ds.flipped ^ S.transform(component_direction(c), sn).flipped ? -1 : 1,omega); return parallel ? sum_to_all(val) : val; } return d == component_direction(c) ? 1.0 : 0; // default to vacuum outside computational cell From 8ede31fac650a87b58055d6fead453a6f366736e Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Wed, 22 May 2019 16:29:49 -0600 Subject: [PATCH 02/25] begin with test files --- python/simulation.py | 4 +- python/tests/dispersive_eigenmode.py | 66 +++++++++++++++++ src/meep.hpp | 8 +- src/monitor.cpp | 106 ++++++++++++++++++++------- src/mpb.cpp | 17 +++-- src/susceptibility.cpp | 15 ++++ 6 files changed, 176 insertions(+), 40 deletions(-) create mode 100644 python/tests/dispersive_eigenmode.py diff --git a/python/simulation.py b/python/simulation.py index 2724c2b93..715ddb023 100644 --- a/python/simulation.py +++ b/python/simulation.py @@ -1190,9 +1190,9 @@ def get_field_point(self, c, pt): v3 = py_v3_to_vec(self.dimensions, pt, self.is_cylindrical) return self.fields.get_field_from_comp(c, v3) - def get_epsilon_point(self, pt): + def get_epsilon_point(self, pt, omega = 0): v3 = py_v3_to_vec(self.dimensions, pt, self.is_cylindrical) - return self.fields.get_eps(v3) + return self.fields.get_eps(v3,omega) def get_filename_prefix(self): if isinstance(self.filename_prefix, str): diff --git a/python/tests/dispersive_eigenmode.py b/python/tests/dispersive_eigenmode.py new file mode 100644 index 000000000..90aac8eca --- /dev/null +++ b/python/tests/dispersive_eigenmode.py @@ -0,0 +1,66 @@ + +# dispersive_eigenmode.py - Tests the meep eigenmode features (eigenmode source, +# eigenmode decomposition, and get_eigenmode) with dispersive materials. +# TODO: +# * check materials with off diagonal components +# * check magnetic profiles +# * once imaginary component is supported, check that + +from __future__ import division + +import unittest +import meep as mp +import numpy as np + + +class TestDispersiveEigenmode(unittest.TestCase): + + def call_chi1(self,material,component,direction,omega): + + sim = mp.Simulation(cell_size=mp.Vector3(1,1,1), + default_material=material, + resolution=10) + + sim.init_sim() + print(component, direction) + v3 = mp.py_v3_to_vec(sim.dimensions, mp.Vector3(0,0,0), sim.is_cylindrical) + n = 1/np.sqrt(sim.fields.get_chi1inv(int(component),int(direction),v3,omega)) + return n + + def test_chi1_routine(self): + from meep.materials import Si, Ag, LiNbO3, fused_quartz + # Check Silicon + w0 = Si.valid_freq_range.min + w1 = Si.valid_freq_range.max + self.assertAlmostEqual(np.real(np.sqrt(Si.epsilon([w0,w1])[0,0,0])), self.call_chi1(Si,0,0,w0), places=6) + self.assertAlmostEqual(np.real(np.sqrt(Si.epsilon([w0,w1])[1,0,0])), self.call_chi1(Si,0,0,w1), places=6) + + # Check Silver + w0 = Ag.valid_freq_range.min + w1 = Ag.valid_freq_range.max + self.assertAlmostEqual(np.real(np.sqrt(Ag.epsilon([w0,w1])[0,0,0])), self.call_chi1(Ag,0,0,w0), places=6) + self.assertAlmostEqual(np.real(np.sqrt(Ag.epsilon([w0,w1])[1,0,0])), self.call_chi1(Ag,0,0,w1), places=6) + + # Check Lithium Niobate + w0 = LiNbO3.valid_freq_range.min + w1 = LiNbO3.valid_freq_range.max + self.assertAlmostEqual(np.real(np.sqrt(LiNbO3.epsilon([w0,w1])[0,0,0])), self.call_chi1(LiNbO3,0,0,w0), places=6) + self.assertAlmostEqual(np.real(np.sqrt(LiNbO3.epsilon([w0,w1])[1,0,0])), self.call_chi1(LiNbO3,0,0,w1), places=6) + self.assertAlmostEqual(np.real(np.sqrt(LiNbO3.epsilon([w0,w1])[0,2,2])), self.call_chi1(LiNbO3,mp.Z,mp.Z,w0), places=6) + self.assertAlmostEqual(np.real(np.sqrt(LiNbO3.epsilon([w0,w1])[1,2,2])), self.call_chi1(LiNbO3,mp.Z,mp.Z,w1), places=6) + + def test_eigenmode_source(self): + from meep.materials import Si, Ag, LiNbO3, fused_quartz + + def test_eigenmode_decomposition(self): + from meep.materials import Si, Ag, LiNbO3, fused_quartz + + def test_get_eigenmode(self): + from meep.materials import Si, Ag, LiNbO3, fused_quartz + + def test_everything(self): + from meep.materials import Si, Ag, LiNbO3, fused_quartz + + +if __name__ == '__main__': + unittest.main() diff --git a/src/meep.hpp b/src/meep.hpp index 83c606ac2..e4fe99615 100644 --- a/src/meep.hpp +++ b/src/meep.hpp @@ -736,7 +736,7 @@ class structure { return get_chi1inv(c, d, loc, omega); } double get_eps(const vec &loc, double omega = 0) const; - double get_mu(const vec &loc) const; + double get_mu(const vec &loc, double omega = 0) const; double max_eps() const; friend class boundary_region; @@ -1746,8 +1746,8 @@ class fields { double get_inveps(component c, direction d, const vec &loc, double omega = 0) const { return get_chi1inv(c, d, loc, omega); } - double get_eps(const vec &loc) const; - double get_mu(const vec &loc) const; + double get_eps(const vec &loc, double omega = 0) const; + double get_mu(const vec &loc, double omega = 0) const; void get_point(monitor_point *p, const vec &) const; monitor_point *get_new_point(const vec &, monitor_point *p = NULL) const; @@ -1829,7 +1829,7 @@ class fields { public: // monitor.cpp std::complex get_field(component c, const ivec &iloc, bool parallel = true) const; - double get_chi1inv(component, direction, const ivec &iloc, bool parallel = true) const; + double get_chi1inv(component, direction, const ivec &iloc, double omega = 0, bool parallel = true) const; // boundaries.cpp bool locate_component_point(component *, ivec *, std::complex *) const; // time.cpp diff --git a/src/monitor.cpp b/src/monitor.cpp index e86a0367a..03dfb5a24 100644 --- a/src/monitor.cpp +++ b/src/monitor.cpp @@ -168,100 +168,152 @@ double fields::get_chi1inv(component c, direction d, const ivec &origloc, double for (int i = 0; i < num_chunks; i++) if (chunks[i]->gv.owns(S.transform(iloc, sn))) { signed_direction ds = S.transform(d, sn); - double val = chunks[i]->get_chi1inv(S.transform(c, sn), ds.d, S.transform(iloc, sn)) * - (ds.flipped ^ S.transform(component_direction(c), sn).flipped ? -1 : 1,omega); + double val = chunks[i]->get_chi1inv(S.transform(c, sn), ds.d, S.transform(iloc, sn), omega) * + (ds.flipped ^ S.transform(component_direction(c), sn).flipped ? -1 : 1); return parallel ? sum_to_all(val) : val; } return d == component_direction(c) ? 1.0 : 0; // default to vacuum outside computational cell } -double fields_chunk::get_chi1inv(component c, direction d, const ivec &iloc) const { - if (is_mine()) - return s->chi1inv[c][d] ? s->chi1inv[c][d][gv.index(c, iloc)] - : (d == component_direction(c) ? 1.0 : 0); - return 0.0; +double fields_chunk::get_chi1inv(component c, direction d, const ivec &iloc, double omega) const { + double res = 0.0; + meep::master_printf("c %d d %d \n", c,d, (d == component_direction(c))); + if (is_mine()){ + res = s->has_chi1inv(c,d) ? s->chi1inv[c][d][gv.index(c, iloc)] + : (d == component_direction(c) ? 1.0 : 0); + if (res != 0){ + // Get instaneous dielectric (epsilon) + std::complex eps(1 / res,0); + // Loop through and add up susceptibility contributions + // locate correct susceptibility list + meep::master_printf("temp\n"); + susceptibility *Esus = s->chiP[E_stuff]; + while (Esus) { + double sigma = 0; + if (Esus->sigma[c][d]) sigma = Esus->sigma[c][d][gv.index(c, iloc)]; + meep::master_printf("c %d d %d | sigma %g\n", c,d,sigma); + eps += Esus->chi1(omega,sigma); + Esus = Esus->next; + } + // Account for conductivity term + if (s->conductivity[c][d]) { + double conductivityCur = s->conductivity[c][d][gv.index(c, iloc)]; + eps = std::complex(1.0, (conductivityCur/omega)) * eps; + } + // Return chi1 inverse, take the real part since no support for loss in mpb yet + // TODO: Add support for metals + res = 1 / (std::sqrt(eps).real() * std::sqrt(eps).real()); + } + } + + return broadcast(n_proc(), res); } -double fields::get_chi1inv(component c, direction d, const vec &loc, bool parallel) const { +double fields::get_chi1inv(component c, direction d, const vec &loc, double omega, bool parallel) const { ivec ilocs[8]; double w[8], res = 0.0; gv.interpolate(c, loc, ilocs, w); for (int argh = 0; argh < 8 && w[argh] != 0; argh++) - res += w[argh] * get_chi1inv(c, d, ilocs[argh], false); + res += w[argh] * get_chi1inv(c, d, ilocs[argh], omega, false); return parallel ? sum_to_all(res) : res; } -double fields::get_eps(const vec &loc) const { +double fields::get_eps(const vec &loc, double omega) const { double tr = 0; int nc = 0; FOR_ELECTRIC_COMPONENTS(c) { if (gv.has_field(c)) { - tr += get_chi1inv(c, component_direction(c), loc, false); + tr += get_chi1inv(c, component_direction(c), loc, omega, false); ++nc; } } return nc / sum_to_all(tr); } -double fields::get_mu(const vec &loc) const { +double fields::get_mu(const vec &loc, double omega) const { double tr = 0; int nc = 0; FOR_MAGNETIC_COMPONENTS(c) { if (gv.has_field(c)) { - tr += get_chi1inv(c, component_direction(c), loc, false); + tr += get_chi1inv(c, component_direction(c), loc, omega, false); ++nc; } } return nc / sum_to_all(tr); } -double structure::get_chi1inv(component c, direction d, const ivec &origloc, bool parallel) const { +double structure::get_chi1inv(component c, direction d, const ivec &origloc, double omega, bool parallel) const { ivec iloc = origloc; for (int sn = 0; sn < S.multiplicity(); sn++) for (int i = 0; i < num_chunks; i++) if (chunks[i]->gv.owns(S.transform(iloc, sn))) { signed_direction ds = S.transform(d, sn); - double val = chunks[i]->get_chi1inv(S.transform(c, sn), ds.d, S.transform(iloc, sn)) * + double val = chunks[i]->get_chi1inv(S.transform(c, sn), ds.d, S.transform(iloc, sn), omega) * (ds.flipped ^ S.transform(component_direction(c), sn).flipped ? -1 : 1); return parallel ? sum_to_all(val) : val; } return 0.0; } -double structure_chunk::get_chi1inv(component c, direction d, const ivec &iloc) const { - if (is_mine()) - return chi1inv[c][d] ? chi1inv[c][d][gv.index(c, iloc)] - : (d == component_direction(c) ? 1.0 : 0); - return 0.0; +double structure_chunk::get_chi1inv(component c, direction d, const ivec &iloc, double omega) const { + double res = 0.0; + if (is_mine()){ + res = + chi1inv[c][d] ? chi1inv[c][d][gv.index(c, iloc)] : (d == component_direction(c) ? 1.0 : 0); + + if (res != 0){ + // Get instaneous dielectric (epsilon) + std::complex eps(1 / res,0); + // Loop through and add up susceptibility contributions + // locate correct susceptibility list + susceptibility *Esus = chiP[E_stuff]; + while (Esus) { + double sigma = 0; + if (Esus->sigma[c][d]) sigma = Esus->sigma[c][d][gv.index(c, iloc)]; + eps += Esus->chi1(omega,sigma); + Esus = Esus->next; + } + // Account for conductivity term + if (conductivity[c][d]) { + double conductivityCur = conductivity[c][d][gv.index(c, iloc)]; + eps = std::complex(1.0, (conductivityCur/omega)) * eps; + } + // Return chi1 inverse, take the real part since no support for loss in mpb yet + // TODO: Add support for metals + res = 1 / (std::sqrt(eps).real() * std::sqrt(eps).real()); + } + } + + return broadcast(n_proc(), res); } -double structure::get_chi1inv(component c, direction d, const vec &loc, bool parallel) const { +double structure::get_chi1inv(component c, direction d, const vec &loc, double omega, bool parallel) const { ivec ilocs[8]; double w[8], res = 0.0; gv.interpolate(c, loc, ilocs, w); - for (int argh = 0; argh < 8 && w[argh]; argh++) - res += w[argh] * get_chi1inv(c, d, ilocs[argh], false); + for (int argh = 0; argh < 8 && w[argh] != 0; argh++) + res += w[argh] * get_chi1inv(c, d, ilocs[argh], omega, false); return parallel ? sum_to_all(res) : res; } -double structure::get_eps(const vec &loc) const { +double structure::get_eps(const vec &loc, double omega) const { double tr = 0; int nc = 0; FOR_ELECTRIC_COMPONENTS(c) { if (gv.has_field(c)) { - tr += get_chi1inv(c, component_direction(c), loc, false); + tr += get_chi1inv(c, component_direction(c), loc, omega, false); ++nc; } } return nc / sum_to_all(tr); } -double structure::get_mu(const vec &loc) const { +double structure::get_mu(const vec &loc, double omega) const { double tr = 0; int nc = 0; FOR_MAGNETIC_COMPONENTS(c) { if (gv.has_field(c)) { - tr += get_chi1inv(c, component_direction(c), loc, false); + tr += get_chi1inv(c, component_direction(c), loc, omega, false); ++nc; } } diff --git a/src/mpb.cpp b/src/mpb.cpp index cefdbeff1..16daeb10b 100644 --- a/src/mpb.cpp +++ b/src/mpb.cpp @@ -38,6 +38,7 @@ namespace meep { typedef struct { const double *s, *o; + double omega; ndim dim; const fields *f; } meep_mpb_eps_data; @@ -47,17 +48,18 @@ static void meep_mpb_eps(symmetric_matrix *eps, symmetric_matrix *eps_inv, const meep_mpb_eps_data *eps_data = (meep_mpb_eps_data *)eps_data_; const double *s = eps_data->s; const double *o = eps_data->o; + double omega = eps_data->omega; vec p(eps_data->dim == D3 ? vec(o[0] + r[0] * s[0], o[1] + r[1] * s[1], o[2] + r[2] * s[2]) : (eps_data->dim == D2 ? vec(o[0] + r[0] * s[0], o[1] + r[1] * s[1]) : /* D1 */ vec(o[2] + r[2] * s[2]))); const fields *f = eps_data->f; - eps_inv->m00 = f->get_chi1inv(Ex, X, p); - eps_inv->m11 = f->get_chi1inv(Ey, Y, p); - eps_inv->m22 = f->get_chi1inv(Ez, Z, p); + eps_inv->m00 = f->get_chi1inv(Ex, X, p, omega); + eps_inv->m11 = f->get_chi1inv(Ey, Y, p, omega); + eps_inv->m22 = f->get_chi1inv(Ez, Z, p, omega); // master_printf("eps_zz(%g,%g) = %g\n", p.x(), p.y(), 1/eps_inv->m00); - ASSIGN_ESCALAR(eps_inv->m01, f->get_chi1inv(Ex, Y, p), 0); - ASSIGN_ESCALAR(eps_inv->m02, f->get_chi1inv(Ex, Z, p), 0); - ASSIGN_ESCALAR(eps_inv->m12, f->get_chi1inv(Ey, Z, p), 0); + ASSIGN_ESCALAR(eps_inv->m01, f->get_chi1inv(Ex, Y, p, omega), 0); + ASSIGN_ESCALAR(eps_inv->m02, f->get_chi1inv(Ex, Z, p, omega), 0); + ASSIGN_ESCALAR(eps_inv->m12, f->get_chi1inv(Ey, Z, p, omega), 0); maxwell_sym_matrix_invert(eps, eps_inv); } @@ -355,6 +357,7 @@ void *fields::get_eigenmode(double omega_src, direction d, const volume where, c eps_data.o = o; eps_data.dim = gv.dim; eps_data.f = this; + eps_data.omega = omega_src; set_maxwell_dielectric(mdata, mesh_size, R, G, meep_mpb_eps, NULL, &eps_data); if (user_mdata) *user_mdata = (void *)mdata; } else { @@ -381,7 +384,7 @@ void *fields::get_eigenmode(double omega_src, direction d, const volume where, c // which we automatically pick if kmatch == 0. if (match_frequency && kmatch == 0) { vec cen = eig_vol.center(); - kmatch = omega_src * sqrt(get_eps(cen) * get_mu(cen)); + kmatch = omega_src * sqrt(get_eps(cen, omega_src) * get_mu(cen, omega_src)); if (d == NO_DIRECTION) { for (int i = 0; i < 3; ++i) k[i] = dot_product(R[i], kdir) * kmatch; // kdir*kmatch in reciprocal basis diff --git a/src/susceptibility.cpp b/src/susceptibility.cpp index d45034352..58cab7796 100644 --- a/src/susceptibility.cpp +++ b/src/susceptibility.cpp @@ -53,6 +53,11 @@ susceptibility *susceptibility::clone() const { return sus; } +// generic base class definition. +std::complex susceptibility::chi1(double freq, double sigma) { + return std::complex(0,0); +} + void susceptibility::delete_internal_data(void *data) const { free(data); } /* Return whether or not we need to allocate P[c][cmp]. (We don't need to @@ -281,6 +286,16 @@ realnum *lorentzian_susceptibility::cinternal_notowned_ptr(int inotowned, compon return d->P[c][cmp] + n; } +std::complex lorentzian_susceptibility::chi1(double freq, double sigma) { + if (no_omega_0_denominator){ + // Drude model + return sigma * omega_0*omega_0 / std::complex(-freq*freq, -gamma*freq); + }else{ + // Standard Lorentzian model + return sigma * omega_0*omega_0 / std::complex(omega_0*omega_0 - freq*freq, -gamma*freq); + } +} + void lorentzian_susceptibility::dump_params(h5file *h5f, size_t *start) { size_t num_params = 5; size_t params_dims[1] = {num_params}; From b546a9c6f493a25ade162d33291e295b59357266 Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Thu, 23 May 2019 10:37:58 -0600 Subject: [PATCH 03/25] bug fixes --- python/geom.py | 2 +- python/tests/dispersive_eigenmode.py | 138 ++++++++++++++++++++++----- src/monitor.cpp | 34 +------ 3 files changed, 119 insertions(+), 55 deletions(-) diff --git a/python/geom.py b/python/geom.py index 80c449110..d42ce681e 100755 --- a/python/geom.py +++ b/python/geom.py @@ -177,7 +177,7 @@ def __init__(self, epsilon_diag=Vector3(1, 1, 1), E_chi3=None, H_chi2=None, H_chi3=None, - valid_freq_range=None): + valid_freq_range=FreqRange(min=-mp.inf, max=mp.inf)): if epsilon: epsilon_diag = Vector3(epsilon, epsilon, epsilon) diff --git a/python/tests/dispersive_eigenmode.py b/python/tests/dispersive_eigenmode.py index 90aac8eca..e39bf2115 100644 --- a/python/tests/dispersive_eigenmode.py +++ b/python/tests/dispersive_eigenmode.py @@ -11,6 +11,7 @@ import unittest import meep as mp import numpy as np +from meep import mpb class TestDispersiveEigenmode(unittest.TestCase): @@ -22,44 +23,137 @@ def call_chi1(self,material,component,direction,omega): resolution=10) sim.init_sim() - print(component, direction) v3 = mp.py_v3_to_vec(sim.dimensions, mp.Vector3(0,0,0), sim.is_cylindrical) - n = 1/np.sqrt(sim.fields.get_chi1inv(int(component),int(direction),v3,omega)) + n = 1/np.sqrt(sim.structure.get_chi1inv(int(component),int(direction),v3,omega)) return n + def simulate_waveguide_meep(self,material,resolution): + sx = 1 + sy = 4 + sz = 0 + dpml = 1 + ww = 0.5 + + cell = mp.Vector3(sx,sy,sz) + + + + geometry = [mp.Block(size=mp.Vector3(mp.inf,ww,mp.inf), + center=mp.Vector3(), + material=material + )] + + fcen = 1/1.55 + df = 0.1*fcen + numFreqs = 25 + + sim = mp.Simulation(cell_size=cell, + geometry=geometry, + resolution=resolution, + sources = [mp.Source(mp.GaussianSource(fcen,df),center=mp.Vector3(),component=mp.Ez)] + ) + + flux1 = sim.add_flux(fcen,df,numFreqs,mp.FluxRegion(center=mp.Vector3(),size=mp.Vector3(y=sy))) + + sim.init_sim() + + res1 = sim.get_eigenmode_coefficients(flux1,[1]) + + freqs = np.array(mp.get_flux_freqs(flux1)) + + neff_meep = np.squeeze([a.x for a in res1.kpoints]) / freqs + + return freqs, neff_meep + + def simulate_waveguide_mpb(self,resolution,material,freqs): + sx = 0 + sy = 4 + sz = 0 + dpml = 1 + ww = 0.5 + + neff = [] + for ifreq in range(freqs.size): + omega = freqs[ifreq] + num_bands = 1 + geometry_lattice = mp.Lattice(size=mp.Vector3(0, sy, sz)) + geometry = [mp.Block(size=mp.Vector3(mp.inf,ww,mp.inf), + center=mp.Vector3(), + material=mp.Medium(index=np.real(np.sqrt(material.epsilon(omega)[0,0])))) + ] + ms = mpb.ModeSolver( + geometry_lattice=geometry_lattice, + geometry=geometry, + resolution=resolution, + num_bands=num_bands + ) + + k = ms.find_k(mp.NO_PARITY, omega, 1, num_bands, mp.Vector3(1), 1e-3, omega * 3.45, + omega * 0.1, omega * 4) + + neff.append(k/omega) + + neff_mpb = np.squeeze(neff) + + return neff_mpb + + def compare_meep_mpb(self,material): + resolution = 50 + freqs, neff_meep = self.simulate_waveguide_meep(material,resolution) + neff_mpb = self.simulate_waveguide_mpb(resolution,material,freqs) + + print('================================') + print(neff_meep) + print(neff_mpb) + + from matplotlib import pyplot as plt + plt.figure() + plt.plot(1/freqs,neff_meep) + plt.plot(1/freqs,neff_mpb) + plt.show() + + self.assertAlmostEqual(neff_meep[0],neff_mpb[0], places=2) + self.assertAlmostEqual(neff_meep[-1],neff_mpb[-1], places=2) + + def test_chi1_routine(self): - from meep.materials import Si, Ag, LiNbO3, fused_quartz + # This test chceks the newly implemented chi1inv routines within the + # fields and structure classes by comparing their output to the + # python epsilon output. + + from meep.materials import Si, Ag, LiNbO3 + # Check Silicon w0 = Si.valid_freq_range.min w1 = Si.valid_freq_range.max - self.assertAlmostEqual(np.real(np.sqrt(Si.epsilon([w0,w1])[0,0,0])), self.call_chi1(Si,0,0,w0), places=6) - self.assertAlmostEqual(np.real(np.sqrt(Si.epsilon([w0,w1])[1,0,0])), self.call_chi1(Si,0,0,w1), places=6) + self.assertAlmostEqual(np.real(np.sqrt(Si.epsilon([w0,w1])[0,0,0])), self.call_chi1(Si,mp.Ex,mp.X,w0), places=6) + self.assertAlmostEqual(np.real(np.sqrt(Si.epsilon([w0,w1])[1,0,0])), self.call_chi1(Si,mp.Ex,mp.X,w1), places=6) # Check Silver w0 = Ag.valid_freq_range.min w1 = Ag.valid_freq_range.max - self.assertAlmostEqual(np.real(np.sqrt(Ag.epsilon([w0,w1])[0,0,0])), self.call_chi1(Ag,0,0,w0), places=6) - self.assertAlmostEqual(np.real(np.sqrt(Ag.epsilon([w0,w1])[1,0,0])), self.call_chi1(Ag,0,0,w1), places=6) + self.assertAlmostEqual(np.real(np.sqrt(Ag.epsilon([w0,w1])[0,0,0])), self.call_chi1(Ag,mp.Ex,mp.X,w0), places=6) + self.assertAlmostEqual(np.real(np.sqrt(Ag.epsilon([w0,w1])[1,0,0])), self.call_chi1(Ag,mp.Ex,mp.X,w1), places=6) - # Check Lithium Niobate + # Check Lithium Niobate (X,X) w0 = LiNbO3.valid_freq_range.min w1 = LiNbO3.valid_freq_range.max - self.assertAlmostEqual(np.real(np.sqrt(LiNbO3.epsilon([w0,w1])[0,0,0])), self.call_chi1(LiNbO3,0,0,w0), places=6) - self.assertAlmostEqual(np.real(np.sqrt(LiNbO3.epsilon([w0,w1])[1,0,0])), self.call_chi1(LiNbO3,0,0,w1), places=6) - self.assertAlmostEqual(np.real(np.sqrt(LiNbO3.epsilon([w0,w1])[0,2,2])), self.call_chi1(LiNbO3,mp.Z,mp.Z,w0), places=6) - self.assertAlmostEqual(np.real(np.sqrt(LiNbO3.epsilon([w0,w1])[1,2,2])), self.call_chi1(LiNbO3,mp.Z,mp.Z,w1), places=6) + self.assertAlmostEqual(np.real(np.sqrt(LiNbO3.epsilon([w0,w1])[0,0,0])), self.call_chi1(LiNbO3,mp.Ex,mp.X,w0), places=6) + self.assertAlmostEqual(np.real(np.sqrt(LiNbO3.epsilon([w0,w1])[1,0,0])), self.call_chi1(LiNbO3,mp.Ex,mp.X,w1), places=6) - def test_eigenmode_source(self): - from meep.materials import Si, Ag, LiNbO3, fused_quartz - - def test_eigenmode_decomposition(self): - from meep.materials import Si, Ag, LiNbO3, fused_quartz - - def test_get_eigenmode(self): - from meep.materials import Si, Ag, LiNbO3, fused_quartz + # Check Lithium Niobate (Z,Z) + self.assertAlmostEqual(np.real(np.sqrt(LiNbO3.epsilon([w0,w1])[0,2,2])), self.call_chi1(LiNbO3,mp.Ez,mp.Z,w0), places=6) + self.assertAlmostEqual(np.real(np.sqrt(LiNbO3.epsilon([w0,w1])[1,2,2])), self.call_chi1(LiNbO3,mp.Ez,mp.Z,w1), places=6) + + def test_waveguide(self): + # This test simultaneously tests the eigenmode source, the eigenmode monitors, + # and the mode decomposition routines. + + from meep.materials import Si, Ag, LiNbO3 + + # Check Silicon + self.compare_meep_mpb(Si) - def test_everything(self): - from meep.materials import Si, Ag, LiNbO3, fused_quartz if __name__ == '__main__': diff --git a/src/monitor.cpp b/src/monitor.cpp index 03dfb5a24..ffcc98e35 100644 --- a/src/monitor.cpp +++ b/src/monitor.cpp @@ -176,37 +176,7 @@ double fields::get_chi1inv(component c, direction d, const ivec &origloc, double } double fields_chunk::get_chi1inv(component c, direction d, const ivec &iloc, double omega) const { - double res = 0.0; - meep::master_printf("c %d d %d \n", c,d, (d == component_direction(c))); - if (is_mine()){ - res = s->has_chi1inv(c,d) ? s->chi1inv[c][d][gv.index(c, iloc)] - : (d == component_direction(c) ? 1.0 : 0); - if (res != 0){ - // Get instaneous dielectric (epsilon) - std::complex eps(1 / res,0); - // Loop through and add up susceptibility contributions - // locate correct susceptibility list - meep::master_printf("temp\n"); - susceptibility *Esus = s->chiP[E_stuff]; - while (Esus) { - double sigma = 0; - if (Esus->sigma[c][d]) sigma = Esus->sigma[c][d][gv.index(c, iloc)]; - meep::master_printf("c %d d %d | sigma %g\n", c,d,sigma); - eps += Esus->chi1(omega,sigma); - Esus = Esus->next; - } - // Account for conductivity term - if (s->conductivity[c][d]) { - double conductivityCur = s->conductivity[c][d][gv.index(c, iloc)]; - eps = std::complex(1.0, (conductivityCur/omega)) * eps; - } - // Return chi1 inverse, take the real part since no support for loss in mpb yet - // TODO: Add support for metals - res = 1 / (std::sqrt(eps).real() * std::sqrt(eps).real()); - } - } - - return broadcast(n_proc(), res); + return s->get_chi1inv(c, d, iloc, omega); } double fields::get_chi1inv(component c, direction d, const vec &loc, double omega, bool parallel) const { @@ -280,7 +250,7 @@ double structure_chunk::get_chi1inv(component c, direction d, const ivec &iloc, } // Return chi1 inverse, take the real part since no support for loss in mpb yet // TODO: Add support for metals - res = 1 / (std::sqrt(eps).real() * std::sqrt(eps).real()); + res = 1 / (std::sqrt(eps).real() * std::sqrt(eps).real()); } } From 43b6adb7340a13c1c96a1219084e01d547eebc0c Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Thu, 13 Jun 2019 17:02:33 -0600 Subject: [PATCH 04/25] update test --- python/Makefile.am | 3 + python/tests/dispersive_eigenmode.py | 145 +++++++++------------------ 2 files changed, 50 insertions(+), 98 deletions(-) diff --git a/python/Makefile.am b/python/Makefile.am index 521f6b7da..4443c31c9 100644 --- a/python/Makefile.am +++ b/python/Makefile.am @@ -18,12 +18,14 @@ endif # WITH_MPI if WITH_MPB BINARY_GRATING_TEST = $(TEST_DIR)/binary_grating.py + DISPERSIVE_EIGENMODE_TEST = $(TEST_DIR)/dispersive_eigenmode.py KDOM_TEST = $(TEST_DIR)/kdom.py MODE_COEFFS_TEST = $(TEST_DIR)/mode_coeffs.py MODE_DECOMPOSITION_TEST = $(TEST_DIR)/mode_decomposition.py WVG_SRC_TEST = $(TEST_DIR)/wvg_src.py else BINARY_GRATING_TEST = + DISPERSIVE_EIGENMODE_TEST = KDOM_TEST = MODE_COEFFS_TEST = MODE_DECOMPOSITION_TEST = @@ -41,6 +43,7 @@ TESTS = \ $(TEST_DIR)/cavity_farfield.py \ $(TEST_DIR)/chunks.py \ $(TEST_DIR)/cyl_ellipsoid.py \ + ${DISPERSIVE_EIGENMODE_TEST} \ $(TEST_DIR)/dft_energy.py \ $(TEST_DIR)/dft_fields.py \ $(TEST_DIR)/field_functions.py \ diff --git a/python/tests/dispersive_eigenmode.py b/python/tests/dispersive_eigenmode.py index e39bf2115..df73fce2d 100644 --- a/python/tests/dispersive_eigenmode.py +++ b/python/tests/dispersive_eigenmode.py @@ -16,6 +16,7 @@ class TestDispersiveEigenmode(unittest.TestCase): + # Directly calss the C++ chi1 routine def call_chi1(self,material,component,direction,omega): sim = mp.Simulation(cell_size=mp.Vector3(1,1,1), @@ -27,94 +28,55 @@ def call_chi1(self,material,component,direction,omega): n = 1/np.sqrt(sim.structure.get_chi1inv(int(component),int(direction),v3,omega)) return n - def simulate_waveguide_meep(self,material,resolution): - sx = 1 - sy = 4 - sz = 0 - dpml = 1 - ww = 0.5 + # Pulls the "effective index" of a uniform, dispersive material + # (i.e. the refractive index) using meep's get_eigenmode + def simulate_meep(self,material,omega): - cell = mp.Vector3(sx,sy,sz) - - - - geometry = [mp.Block(size=mp.Vector3(mp.inf,ww,mp.inf), - center=mp.Vector3(), - material=material - )] - - fcen = 1/1.55 - df = 0.1*fcen - numFreqs = 25 - - sim = mp.Simulation(cell_size=cell, - geometry=geometry, - resolution=resolution, - sources = [mp.Source(mp.GaussianSource(fcen,df),center=mp.Vector3(),component=mp.Ez)] + sim = mp.Simulation(cell_size=mp.Vector3(2,2,2), + default_material=material, + resolution=20 ) - flux1 = sim.add_flux(fcen,df,numFreqs,mp.FluxRegion(center=mp.Vector3(),size=mp.Vector3(y=sy))) - + direction = mp.X + where = mp.Volume(center=mp.Vector3(0,0,0),size=mp.Vector3(0,1,1)) + band_num = 1 + kpoint = mp.Vector3(2,0,0) sim.init_sim() + em = sim.get_eigenmode(omega,direction,where,band_num,kpoint) + neff_meep = np.squeeze(em.k.x) / np.squeeze(em.freq) - res1 = sim.get_eigenmode_coefficients(flux1,[1]) - - freqs = np.array(mp.get_flux_freqs(flux1)) - - neff_meep = np.squeeze([a.x for a in res1.kpoints]) / freqs - - return freqs, neff_meep + return neff_meep - def simulate_waveguide_mpb(self,resolution,material,freqs): - sx = 0 - sy = 4 - sz = 0 - dpml = 1 - ww = 0.5 - - neff = [] - for ifreq in range(freqs.size): - omega = freqs[ifreq] - num_bands = 1 - geometry_lattice = mp.Lattice(size=mp.Vector3(0, sy, sz)) - geometry = [mp.Block(size=mp.Vector3(mp.inf,ww,mp.inf), - center=mp.Vector3(), - material=mp.Medium(index=np.real(np.sqrt(material.epsilon(omega)[0,0])))) - ] - ms = mpb.ModeSolver( - geometry_lattice=geometry_lattice, - geometry=geometry, - resolution=resolution, - num_bands=num_bands - ) - - k = ms.find_k(mp.NO_PARITY, omega, 1, num_bands, mp.Vector3(1), 1e-3, omega * 3.45, - omega * 0.1, omega * 4) - - neff.append(k/omega) - - neff_mpb = np.squeeze(neff) - + # Pulls the "effective index" of a uniform, dispersive material + # (i.e. the refractive index) using mpb + def simulate_mpb(self,material,omega): + ms = mpb.ModeSolver( + geometry_lattice=mp.Lattice(size=mp.Vector3(0,2,2)), + default_material=material, + resolution=10, + num_bands=1 + ) + k = ms.find_k(mp.NO_PARITY, omega, 1, 1, mp.Vector3(1), 1e-3, omega * 4, + omega * 0.1, omega * 6) + + neff_mpb = k[0]/omega return neff_mpb - def compare_meep_mpb(self,material): - resolution = 50 - freqs, neff_meep = self.simulate_waveguide_meep(material,resolution) - neff_mpb = self.simulate_waveguide_mpb(resolution,material,freqs) - - print('================================') - print(neff_meep) - print(neff_mpb) - - from matplotlib import pyplot as plt - plt.figure() - plt.plot(1/freqs,neff_meep) - plt.plot(1/freqs,neff_mpb) - plt.show() - - self.assertAlmostEqual(neff_meep[0],neff_mpb[0], places=2) - self.assertAlmostEqual(neff_meep[-1],neff_mpb[-1], places=2) + # main test bed to check the new features + def compare_meep_mpb(self,material,omega,component=0,direction=0): + n = np.real(np.sqrt(material.epsilon(omega)[component,direction])) + chi1 = self.call_chi1(material,mp.Ex,mp.X,omega) + n_meep = self.simulate_meep(material,omega) + n_mpb = self.simulate_mpb(material,omega) + + # Check that the chi1 value matches the refractive index + self.assertAlmostEqual(n,chi1) + + # Check that the chi1 value matches meep's get_eigenmode + self.assertAlmostEqual(n,n_meep) + # Check that the chi1 value matches mpb's get_eigenmode + #self.assertAlmostEqual(n,n_mpb) def test_chi1_routine(self): # This test chceks the newly implemented chi1inv routines within the @@ -126,33 +88,20 @@ def test_chi1_routine(self): # Check Silicon w0 = Si.valid_freq_range.min w1 = Si.valid_freq_range.max - self.assertAlmostEqual(np.real(np.sqrt(Si.epsilon([w0,w1])[0,0,0])), self.call_chi1(Si,mp.Ex,mp.X,w0), places=6) - self.assertAlmostEqual(np.real(np.sqrt(Si.epsilon([w0,w1])[1,0,0])), self.call_chi1(Si,mp.Ex,mp.X,w1), places=6) + self.compare_meep_mpb(Si,w0) + self.compare_meep_mpb(Si,w1) # Check Silver w0 = Ag.valid_freq_range.min w1 = Ag.valid_freq_range.max - self.assertAlmostEqual(np.real(np.sqrt(Ag.epsilon([w0,w1])[0,0,0])), self.call_chi1(Ag,mp.Ex,mp.X,w0), places=6) - self.assertAlmostEqual(np.real(np.sqrt(Ag.epsilon([w0,w1])[1,0,0])), self.call_chi1(Ag,mp.Ex,mp.X,w1), places=6) + self.compare_meep_mpb(Ag,w0) + self.compare_meep_mpb(Ag,w1) # Check Lithium Niobate (X,X) w0 = LiNbO3.valid_freq_range.min w1 = LiNbO3.valid_freq_range.max - self.assertAlmostEqual(np.real(np.sqrt(LiNbO3.epsilon([w0,w1])[0,0,0])), self.call_chi1(LiNbO3,mp.Ex,mp.X,w0), places=6) - self.assertAlmostEqual(np.real(np.sqrt(LiNbO3.epsilon([w0,w1])[1,0,0])), self.call_chi1(LiNbO3,mp.Ex,mp.X,w1), places=6) - - # Check Lithium Niobate (Z,Z) - self.assertAlmostEqual(np.real(np.sqrt(LiNbO3.epsilon([w0,w1])[0,2,2])), self.call_chi1(LiNbO3,mp.Ez,mp.Z,w0), places=6) - self.assertAlmostEqual(np.real(np.sqrt(LiNbO3.epsilon([w0,w1])[1,2,2])), self.call_chi1(LiNbO3,mp.Ez,mp.Z,w1), places=6) - - def test_waveguide(self): - # This test simultaneously tests the eigenmode source, the eigenmode monitors, - # and the mode decomposition routines. - - from meep.materials import Si, Ag, LiNbO3 - - # Check Silicon - self.compare_meep_mpb(Si) + self.compare_meep_mpb(LiNbO3,w0) + self.compare_meep_mpb(LiNbO3,w1) From 23cd5f6f2fcbb10ae24ee480afb7bbfb80508e39 Mon Sep 17 00:00:00 2001 From: smartalecH Date: Sat, 15 Jun 2019 14:31:33 -0600 Subject: [PATCH 05/25] found one bug --- src/monitor.cpp | 18 +++++++++++------- src/mpb.cpp | 3 ++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/monitor.cpp b/src/monitor.cpp index ffcc98e35..dc8d5dd89 100644 --- a/src/monitor.cpp +++ b/src/monitor.cpp @@ -230,17 +230,17 @@ double structure_chunk::get_chi1inv(component c, direction d, const ivec &iloc, if (is_mine()){ res = chi1inv[c][d] ? chi1inv[c][d][gv.index(c, iloc)] : (d == component_direction(c) ? 1.0 : 0); - if (res != 0){ // Get instaneous dielectric (epsilon) - std::complex eps(1 / res,0); + std::complex eps(1.0 / res,0.0); // Loop through and add up susceptibility contributions // locate correct susceptibility list susceptibility *Esus = chiP[E_stuff]; while (Esus) { - double sigma = 0; - if (Esus->sigma[c][d]) sigma = Esus->sigma[c][d][gv.index(c, iloc)]; - eps += Esus->chi1(omega,sigma); + if (Esus->sigma[c][d]) { + double sigma = Esus->sigma[c][d][gv.index(c, iloc)]; + eps += Esus->chi1(omega,sigma); + } Esus = Esus->next; } // Account for conductivity term @@ -250,11 +250,15 @@ double structure_chunk::get_chi1inv(component c, direction d, const ivec &iloc, } // Return chi1 inverse, take the real part since no support for loss in mpb yet // TODO: Add support for metals - res = 1 / (std::sqrt(eps).real() * std::sqrt(eps).real()); + if (eps.imag() == 0.0){ + res = 1.0 / eps.real(); + }else{ + res = 1.0 / (std::sqrt(eps).real() * std::sqrt(eps).real()); + } } } - return broadcast(n_proc(), res); + return res; } double structure::get_chi1inv(component c, direction d, const vec &loc, double omega, bool parallel) const { diff --git a/src/mpb.cpp b/src/mpb.cpp index 16daeb10b..ef2868f7b 100644 --- a/src/mpb.cpp +++ b/src/mpb.cpp @@ -56,10 +56,11 @@ static void meep_mpb_eps(symmetric_matrix *eps, symmetric_matrix *eps_inv, const eps_inv->m00 = f->get_chi1inv(Ex, X, p, omega); eps_inv->m11 = f->get_chi1inv(Ey, Y, p, omega); eps_inv->m22 = f->get_chi1inv(Ez, Z, p, omega); - // master_printf("eps_zz(%g,%g) = %g\n", p.x(), p.y(), 1/eps_inv->m00); + ASSIGN_ESCALAR(eps_inv->m01, f->get_chi1inv(Ex, Y, p, omega), 0); ASSIGN_ESCALAR(eps_inv->m02, f->get_chi1inv(Ex, Z, p, omega), 0); ASSIGN_ESCALAR(eps_inv->m12, f->get_chi1inv(Ey, Z, p, omega), 0); + //master_printf("eps_zz(%g,%g) = %g\n", p.x(), p.y(), eps_inv->m01); maxwell_sym_matrix_invert(eps, eps_inv); } From a4ac0f28a55e1c68fbc7f1250c345d360b6da759 Mon Sep 17 00:00:00 2001 From: smartalecH Date: Mon, 17 Jun 2019 16:41:18 -0600 Subject: [PATCH 06/25] bug fixes, h5 output, and array-slice output --- python/simulation.py | 18 +++---- python/tests/dispersive_eigenmode.py | 75 +++++++++++++++++++++++++--- python/tests/visualization.py | 58 +++++++++++++++------ python/visualization.py | 58 ++++++++++++--------- src/array_slice.cpp | 51 ++++++++++--------- src/h5fields.cpp | 55 ++++++++++---------- src/meep.hpp | 27 ++++++---- src/monitor.cpp | 15 ++++-- 8 files changed, 237 insertions(+), 120 deletions(-) diff --git a/python/simulation.py b/python/simulation.py index efcd70192..5f14fa190 100644 --- a/python/simulation.py +++ b/python/simulation.py @@ -1795,7 +1795,7 @@ def _add_fluxish_stuff(self, add_dft_stuff, fcen, df, nfreq, stufflist, *args): return stuff - def output_component(self, c, h5file=None): + def output_component(self, c, h5file=None, omega=0): if self.fields is None: raise RuntimeError("Fields must be initialized before calling output_component") @@ -1803,7 +1803,7 @@ def output_component(self, c, h5file=None): h5 = self.output_append_h5 if h5file is None else h5file append = h5file is None and self.output_append_h5 is not None - self.fields.output_hdf5(c, vol, h5, append, self.output_single_precision, self.get_filename_prefix()) + self.fields.output_hdf5(c, vol, h5, append, self.output_single_precision, self.get_filename_prefix(), omega) if h5file is None: nm = self.fields.h5file_name(mp.component_name(c), self.get_filename_prefix(), True) @@ -1833,7 +1833,7 @@ def h5topng(self, rm_h5, option, *step_funcs): cmd = re.sub(r'\$EPS', self.last_eps_filename, opts) return convert_h5(rm_h5, cmd, *step_funcs) - def get_array(self, component=None, vol=None, center=None, size=None, cmplx=None, arr=None): + def get_array(self, component=None, vol=None, center=None, size=None, cmplx=None, arr=None, omega = 0): if component is None: raise ValueError("component is required") if isinstance(component, mp.Volume) or isinstance(component, mp.volume): @@ -1868,9 +1868,9 @@ def get_array(self, component=None, vol=None, center=None, size=None, cmplx=None 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) + self.fields.get_complex_array_slice(v, component, arr, omega) else: - self.fields.get_array_slice(v, component, arr) + self.fields.get_array_slice(v, component, arr, omega) return arr @@ -2071,8 +2071,8 @@ def run(self, *step_funcs, **kwargs): else: raise ValueError("Invalid run configuration") - def get_epsilon(self): - return self.get_array(component=mp.Dielectric) + def get_epsilon(self,omega=0): + return self.get_array(component=mp.Dielectric,omega=omega) def get_mu(self): return self.get_array(component=mp.Permeability) @@ -2600,8 +2600,8 @@ def _output_png(sim, todo): return _output_png -def output_epsilon(sim): - sim.output_component(mp.Dielectric) +def output_epsilon(sim,omega=0): + sim.output_component(mp.Dielectric,omega=omega) def output_mu(sim): diff --git a/python/tests/dispersive_eigenmode.py b/python/tests/dispersive_eigenmode.py index df73fce2d..a9d2b9112 100644 --- a/python/tests/dispersive_eigenmode.py +++ b/python/tests/dispersive_eigenmode.py @@ -12,6 +12,8 @@ import meep as mp import numpy as np from meep import mpb +import h5py +import os class TestDispersiveEigenmode(unittest.TestCase): @@ -56,7 +58,7 @@ def simulate_mpb(self,material,omega): resolution=10, num_bands=1 ) - k = ms.find_k(mp.NO_PARITY, omega, 1, 1, mp.Vector3(1), 1e-3, omega * 4, + k = ms.find_k(mp.NO_PARITY, omega, 1, 1, mp.Vector3(1), 1e-3, omega * 1, omega * 0.1, omega * 6) neff_mpb = k[0]/omega @@ -67,23 +69,24 @@ def compare_meep_mpb(self,material,omega,component=0,direction=0): n = np.real(np.sqrt(material.epsilon(omega)[component,direction])) chi1 = self.call_chi1(material,mp.Ex,mp.X,omega) n_meep = self.simulate_meep(material,omega) - n_mpb = self.simulate_mpb(material,omega) + # Let's wait to check this until we enable dispersive materials in MPB... + #n_mpb = self.simulate_mpb(material,omega) # Check that the chi1 value matches the refractive index - self.assertAlmostEqual(n,chi1) + self.assertAlmostEqual(n,chi1, places=6) # Check that the chi1 value matches meep's get_eigenmode - self.assertAlmostEqual(n,n_meep) + self.assertAlmostEqual(n,n_meep, places=6) - # Check that the chi1 value matches mpb's get_eigenmode - #self.assertAlmostEqual(n,n_mpb) + # Check that the chi1 value matches mpb's get_eigenmode + #self.assertAlmostEqual(n,n_mpb, places=6) def test_chi1_routine(self): - # This test chceks the newly implemented chi1inv routines within the + # This test checks the newly implemented get_chi1inv routines within the # fields and structure classes by comparing their output to the # python epsilon output. - from meep.materials import Si, Ag, LiNbO3 + from meep.materials import Si, Ag, LiNbO3, Au # Check Silicon w0 = Si.valid_freq_range.min @@ -97,12 +100,68 @@ def test_chi1_routine(self): self.compare_meep_mpb(Ag,w0) self.compare_meep_mpb(Ag,w1) + # Check Gold + w0 = Au.valid_freq_range.min + w1 = Au.valid_freq_range.max + self.compare_meep_mpb(Au,w0) + self.compare_meep_mpb(Au,w1) + # Check Lithium Niobate (X,X) w0 = LiNbO3.valid_freq_range.min w1 = LiNbO3.valid_freq_range.max self.compare_meep_mpb(LiNbO3,w0) self.compare_meep_mpb(LiNbO3,w1) + def verify_output_and_slice(self,material,omega,component=0,direction=0): + filename = 'dispersive_eigenmode-eps-000000.00.h5' + n = np.real(np.sqrt(material.epsilon(omega)[component,direction])) + + sim = mp.Simulation(cell_size=mp.Vector3(2,2,2), + default_material=material, + resolution=20 + ) + sim.init_sim() + vol = mp.Volume(center=mp.Vector3(), size=mp.Vector3(1,1,1)) + + # Check to make sure the get_slice routine is working with omega + n_slice = np.sqrt(np.min(sim.get_epsilon(omega))) + self.assertAlmostEqual(n,n_slice, places=6) + + # Check to make sure h5 output is working with omega + mp.output_epsilon(sim,omega) + n_h5 = np.sqrt(np.min(h5py.File(filename, 'r')['eps'])) + self.assertAlmostEqual(n,n_h5, places=6) + os.remove(filename) + + def test_get_with_dispersion(self): + # This test checks the get_array_slice and output_fields method + # with dispersive materials. + + from meep.materials import Si, Ag, LiNbO3, Au + + # Check Silicon + w0 = Si.valid_freq_range.min + w1 = Si.valid_freq_range.max + self.verify_output_and_slice(Si,w0) + self.verify_output_and_slice(Si,w1) + + # Check Silver + w0 = Ag.valid_freq_range.min + w1 = Ag.valid_freq_range.max + self.verify_output_and_slice(Ag,w0) + self.verify_output_and_slice(Ag,w1) + + # Check Gold + w0 = Au.valid_freq_range.min + w1 = Au.valid_freq_range.max + self.verify_output_and_slice(Au,w0) + self.verify_output_and_slice(Au,w1) + + # Check Lithium Niobate (X,X) + w0 = LiNbO3.valid_freq_range.min + w1 = LiNbO3.valid_freq_range.max + #self.verify_output_and_slice(LiNbO3,w0) + #self.verify_output_and_slice(LiNbO3,w1) if __name__ == '__main__': diff --git a/python/tests/visualization.py b/python/tests/visualization.py index 42368e918..865278db2 100644 --- a/python/tests/visualization.py +++ b/python/tests/visualization.py @@ -31,40 +31,50 @@ def setup_sim(zDim=0): # A simple waveguide geometry = [mp.Block(mp.Vector3(mp.inf,1,1), - center=mp.Vector3(), - material=mp.Medium(epsilon=12))] - + center=mp.Vector3(), + material=mp.Medium(epsilon=12))] + # Add point sources sources = [mp.Source(mp.ContinuousSource(frequency=0.15), component=mp.Ez, - center=mp.Vector3(-5,0)), + center=mp.Vector3(-5,0), + size=mp.Vector3(0,0,2)), + mp.Source(mp.ContinuousSource(frequency=0.15), + component=mp.Ez, + center=mp.Vector3(0,2), + size=mp.Vector3(0,0,2)), + mp.Source(mp.ContinuousSource(frequency=0.15), + component=mp.Ez, + center=mp.Vector3(-1,1), + size=mp.Vector3(0,0,2)), mp.Source(mp.ContinuousSource(frequency=0.15), component=mp.Ez, - center=mp.Vector3(0,2)) + center=mp.Vector3(-2,-2,1), + size=mp.Vector3(0,0,0)), ] # Add line sources sources += [mp.Source(mp.ContinuousSource(frequency=0.15), component=mp.Ez, - size=mp.Vector3(0,2,0), + size=mp.Vector3(0,2,2), center=mp.Vector3(-6,0)), mp.Source(mp.ContinuousSource(frequency=0.15), component=mp.Ez, - size=mp.Vector3(2,0,0), + size=mp.Vector3(0,2,2), center=mp.Vector3(0,1))] # Add plane sources sources += [mp.Source(mp.ContinuousSource(frequency=0.15), component=mp.Ez, - size=mp.Vector3(2,2,0), + size=mp.Vector3(2,2,2), center=mp.Vector3(-3,0)), mp.Source(mp.ContinuousSource(frequency=0.15), component=mp.Ez, - size=mp.Vector3(2,2,0), + size=mp.Vector3(2,2,2), center=mp.Vector3(0,-2))] - + # Different pml layers - pml_layers = [mp.PML(2.0,mp.X),mp.PML(1.0,mp.Y,mp.Low),mp.PML(1.5,mp.Y,mp.High)] + pml_layers = [mp.PML(2.0,mp.X),mp.PML(1.0,mp.Y,mp.Low),mp.PML(1.5,mp.Y,mp.High),mp.PML(1.5,mp.Z)] resolution = 10 @@ -74,13 +84,33 @@ def setup_sim(zDim=0): sources=sources, resolution=resolution) # Line monitor - sim.add_flux(1,0,1,mp.FluxRegion(center=mp.Vector3(5,0,0),size=mp.Vector3(0,4), direction=mp.X)) + sim.add_flux(1,0,1,mp.FluxRegion(center=mp.Vector3(5,0,0),size=mp.Vector3(0,4,4), direction=mp.X)) # Plane monitor - sim.add_flux(1,0,1,mp.FluxRegion(center=mp.Vector3(2,0,0),size=mp.Vector3(4,4), direction=mp.X)) - + sim.add_flux(1,0,1,mp.FluxRegion(center=mp.Vector3(2,0,0),size=mp.Vector3(4,4,4), direction=mp.X)) + return sim +def view_sim(): + sim = setup_sim(8) + xy0 = mp.Volume(center=mp.Vector3(0,0,0), size=mp.Vector3(sim.cell_size.x,sim.cell_size.y,0)) + xy1 = mp.Volume(center=mp.Vector3(0,0,1), size=mp.Vector3(sim.cell_size.x,sim.cell_size.y,0)) + yz0 = mp.Volume(center=mp.Vector3(0,0,0), size=mp.Vector3(0,sim.cell_size.y,sim.cell_size.z)) + yz1 = mp.Volume(center=mp.Vector3(1,0,0), size=mp.Vector3(0,sim.cell_size.y,sim.cell_size.z)) + xz0 = mp.Volume(center=mp.Vector3(0,0,0), size=mp.Vector3(sim.cell_size.x,0,sim.cell_size.z)) + xz1 = mp.Volume(center=mp.Vector3(0,1,0), size=mp.Vector3(sim.cell_size.x,0,sim.cell_size.z)) + vols = [xy0,xy1,yz0,yz1,xz0,xz1] + titles = ['xy0','xy1','yz0','yz1','xz0','xz1'] + xlabel = ['x','x','y','y','x','x'] + ylabel = ['y','y','z','z','z','z'] + for k in range(len(vols)): + ax = plt.subplot(2,3,k+1) + sim.plot2D(ax=ax,output_plane=vols[k]) + ax.set_xlabel(xlabel[k]) + ax.set_ylabel(ylabel[k]) + ax.set_title(titles[k]) + plt.tight_layout() + plt.show() class TestVisualization(unittest.TestCase): def test_plot2D(self): diff --git a/python/visualization.py b/python/visualization.py index 737de0493..df3098ce7 100644 --- a/python/visualization.py +++ b/python/visualization.py @@ -119,7 +119,12 @@ def intersect_volume_volume(volume1,volume2): # Evaluate intersection U = np.min([U1,U2],axis=0) L = np.max([L1,L2],axis=0) - + + # For single points we have to check manually + if np.all(U-L == 0): + if (not volume1.pt_in_volume(Vector3(*U))) or (not volume2.pt_in_volume(Vector3(*U))): + return [] + # Check for two volumes that don't intersect if np.any(U-L < 0): return [] @@ -157,16 +162,10 @@ 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) - # Check if plane extends past domain, truncate, and issue warning if required. - if plane_volume.size.x == 0: - check_size = Vector3(0,sim.cell_size.y,sim.cell_size.z) - elif plane_volume.size.y == 0: - check_size = Vector3(sim.cell_size.x,0,sim.cell_size.z) - elif plane_volume.size.z == 0: - check_size = Vector3(sim.cell_size.x,sim.cell_size.y,0) - else: + 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=check_size) + + check_volume = Volume(center=sim.geometry_center,size=sim.cell_size) vertices = intersect_volume_volume(check_volume,plane_volume) @@ -234,13 +233,13 @@ def sort_points(xy): # Point volume if len(intersection) == 1: point_args = {key:value for key, value in plotting_parameters.items() if key in ['color','marker','alpha','linewidth']} - if sim_center.y == center.y and sim_size.y==0: + if sim_size.y==0: ax.scatter(center.x,center.z, **point_args) return ax - elif sim_center.x == center.x and sim_size.x==0: + elif sim_size.x==0: ax.scatter(center.y,center.z, **point_args) return ax - elif sim_center.z == center.z and sim_size.z==0: + elif sim_size.z==0: ax.scatter(center.x,center.y, **point_args) return ax else: @@ -250,15 +249,15 @@ 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_center.x == center.x and sim_size.x==0: + 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_center.y == center.y and sim_size.y==0: + 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_center.z == center.z and sim_size.z==0: + elif sim_size.z==0: ax.plot([a.x for a in intersection],[a.y for a in intersection], **line_args) return ax else: @@ -268,15 +267,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_center.x == center.x and 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 - elif sim_center.y == center.y and sim_size.y==0: + 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_center.z == center.z and 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: @@ -285,7 +284,7 @@ def sort_points(xy): return ax return ax -def plot_eps(sim,ax,output_plane=None,eps_parameters=None): +def plot_eps(sim,ax,output_plane=None,eps_parameters=None,omega=0): if sim.structure is None: sim.init_sim() @@ -324,7 +323,7 @@ def plot_eps(sim,ax,output_plane=None,eps_parameters=None): 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))) + eps_data = np.rot90(np.real(sim.get_array(center=center, size=cell_size, component=mp.Dielectric, omega=omega))) if mp.am_master(): ax.imshow(eps_data, extent=extent, **eps_parameters) ax.set_xlabel(xlabel) @@ -492,13 +491,24 @@ def plot_fields(sim,ax=None,fields=None,output_plane=None,field_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): + field_parameters=None, omega=None): + + # 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 omega is None: + try: + omega = sim.sources[0].frequency + except: + try: + omega = sim.sources[0].src.frequency + except: + omega = 0 # User incorrectly specified a 3D output plane if output_plane and (output_plane.size.x != 0) and (output_plane.size.y != 0) and (output_plane.size.z != 0): @@ -508,7 +518,7 @@ def plot2D(sim,ax=None, output_plane=None, fields=None, labels=False, raise ValueError("For 3D simulations, you must specify an output_plane.") # Plot geometry - ax = plot_eps(sim,ax,output_plane=output_plane,eps_parameters=eps_parameters) + ax = plot_eps(sim,ax,output_plane=output_plane,eps_parameters=eps_parameters,omega=omega) # Plot boundaries ax = plot_boundaries(sim,ax,output_plane=output_plane,boundary_parameters=boundary_parameters) diff --git a/src/array_slice.cpp b/src/array_slice.cpp index 2ecbc627b..db106935b 100644 --- a/src/array_slice.cpp +++ b/src/array_slice.cpp @@ -66,6 +66,8 @@ typedef struct { cdouble *fields; ptrdiff_t *offsets; + double omega; + int ninveps; component inveps_cs[3]; direction inveps_ds[3]; @@ -274,6 +276,7 @@ static void get_array_slice_chunkloop(fields_chunk *fc, int ichnk, component cgr ptrdiff_t *off = data->offsets; component *cS = data->cS; + double omega = data->omega; complex *fields = data->fields, *ph = data->ph; const component *iecs = data->inveps_cs; const direction *ieds = data->inveps_ds; @@ -315,24 +318,23 @@ static void get_array_slice_chunkloop(fields_chunk *fc, int ichnk, component cgr } else if (cS[i] == Dielectric) { double tr = 0.0; for (int k = 0; k < data->ninveps; ++k) { - const realnum *ie = fc->s->chi1inv[iecs[k]][ieds[k]]; - if (ie) - tr += (ie[idx] + ie[idx + ieos[2 * k]] + ie[idx + ieos[1 + 2 * k]] + - ie[idx + ieos[2 * k] + ieos[1 + 2 * k]]); - else - tr += 4; // default inveps == 1 + tr += (fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx,omega) + + fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[2 * k],omega) + + fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[1 + 2 * k],omega) + + fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[2 * k] + ieos[1 + 2 * k],omega)); } + if (tr == 0.0) tr = 4.0; // default inveps == 1 fields[i] = (4 * data->ninveps) / tr; + } else if (cS[i] == Permeability) { double tr = 0.0; for (int k = 0; k < data->ninvmu; ++k) { - const realnum *im = fc->s->chi1inv[imcs[k]][imds[k]]; - if (im) - tr += (im[idx] + im[idx + imos[2 * k]] + im[idx + imos[1 + 2 * k]] + - im[idx + imos[2 * k] + imos[1 + 2 * k]]); - else - tr += 4; // default invmu == 1 + tr += (fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx,omega) + + fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[2 * k],omega) + + fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[1 + 2 * k],omega) + + fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[2 * k] + imos[1 + 2 * k],omega)); } + if (tr == 0.0) tr = 4.0; // default invmu == 1 fields[i] = (4 * data->ninvmu) / tr; } else { double f[2]; @@ -462,7 +464,7 @@ int fields::get_array_slice_dimensions(const volume &where, size_t dims[3], dire /**********************************************************************/ void *fields::do_get_array_slice(const volume &where, std::vector components, field_function fun, field_rfunction rfun, void *fun_data, - void *vslice) { + void *vslice, double omega) { am_now_working_on(FieldOutput); /***************************************************************/ @@ -498,6 +500,7 @@ void *fields::do_get_array_slice(const volume &where, std::vector com data.rfun = rfun; data.fun_data = fun_data; data.components = components; + data.omega = omega; int num_components = components.size(); data.cS = new component[num_components]; data.ph = new cdouble[num_components]; @@ -555,33 +558,35 @@ 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) { - return (double *)do_get_array_slice(where, components, 0, rfun, fun_data, (void *)slice); + field_rfunction rfun, void *fun_data, double *slice, + double omega) { + return (double *)do_get_array_slice(where, components, 0, rfun, fun_data, (void *)slice, omega); } cdouble *fields::get_complex_array_slice(const volume &where, std::vector components, - field_function fun, void *fun_data, cdouble *slice) { - return (cdouble *)do_get_array_slice(where, components, fun, 0, fun_data, (void *)slice); + field_function fun, void *fun_data, cdouble *slice, + double omega) { + return (cdouble *)do_get_array_slice(where, components, fun, 0, fun_data, (void *)slice, omega); } -double *fields::get_array_slice(const volume &where, component c, double *slice) { +double *fields::get_array_slice(const volume &where, component c, double *slice, double omega) { std::vector components(1); components[0] = c; - return (double *)do_get_array_slice(where, components, 0, default_field_rfunc, 0, (void *)slice); + return (double *)do_get_array_slice(where, components, 0, default_field_rfunc, 0, (void *)slice, omega); } -double *fields::get_array_slice(const volume &where, derived_component c, double *slice) { +double *fields::get_array_slice(const volume &where, derived_component c, double *slice, double omega) { 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); + return (double *)do_get_array_slice(where, cs, 0, rfun, &nfields, (void *)slice, omega); } -cdouble *fields::get_complex_array_slice(const volume &where, component c, cdouble *slice) { +cdouble *fields::get_complex_array_slice(const volume &where, component c, cdouble *slice, double omega) { std::vector components(1); components[0] = c; - return (cdouble *)do_get_array_slice(where, components, default_field_func, 0, 0, (void *)slice); + return (cdouble *)do_get_array_slice(where, components, default_field_func, 0, 0, (void *)slice, omega); } cdouble *fields::get_source_slice(const volume &where, component source_slice_component, diff --git a/src/h5fields.cpp b/src/h5fields.cpp index e7da104e9..88b8503b3 100644 --- a/src/h5fields.cpp +++ b/src/h5fields.cpp @@ -50,6 +50,7 @@ typedef struct { complex *ph; complex *fields; ptrdiff_t *offsets; + double omega; int ninveps; component inveps_cs[3]; direction inveps_ds[3]; @@ -150,6 +151,8 @@ static void h5_output_chunkloop(fields_chunk *fc, int ichnk, component cgrid, iv const direction *imds = data->invmu_ds; ptrdiff_t imos[6]; + double omega = data->omega; + for (int i = 0; i < data->num_fields; ++i) { cS[i] = S.transform(data->components[i], -sn); if (cS[i] == Dielectric || cS[i] == Permeability) @@ -173,24 +176,22 @@ static void h5_output_chunkloop(fields_chunk *fc, int ichnk, component cgrid, iv if (cS[i] == Dielectric) { double tr = 0.0; for (int k = 0; k < data->ninveps; ++k) { - const realnum *ie = fc->s->chi1inv[iecs[k]][ieds[k]]; - if (ie) - tr += (ie[idx] + ie[idx + ieos[2 * k]] + ie[idx + ieos[1 + 2 * k]] + - ie[idx + ieos[2 * k] + ieos[1 + 2 * k]]); - else - tr += 4; // default inveps == 1 + tr += (fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx,omega) + + fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[2 * k],omega) + + fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[1 + 2 * k],omega) + + fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[2 * k] + ieos[1 + 2 * k],omega)); } + if (tr == 0.0) tr = 4.0; // default inveps == 1 fields[i] = (4 * data->ninveps) / tr; } else if (cS[i] == Permeability) { double tr = 0.0; for (int k = 0; k < data->ninvmu; ++k) { - const realnum *im = fc->s->chi1inv[imcs[k]][imds[k]]; - if (im) - tr += (im[idx] + im[idx + imos[2 * k]] + im[idx + imos[1 + 2 * k]] + - im[idx + imos[2 * k] + imos[1 + 2 * k]]); - else - tr += 4; // default invmu == 1 + tr += (fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx,omega) + + fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[2 * k],omega) + + fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[1 + 2 * k],omega) + + fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[2 * k] + imos[1 + 2 * k],omega)); } + if (tr == 0.0) tr = 4.0; // default invmu == 1 fields[i] = (4 * data->ninvmu) / tr; } else { double f[2]; @@ -219,7 +220,7 @@ static void h5_output_chunkloop(fields_chunk *fc, int ichnk, component cgrid, iv void fields::output_hdf5(h5file *file, const char *dataname, int num_fields, const component *components, field_function fun, void *fun_data_, int reim, - const volume &where, bool append_data, bool single_precision) { + const volume &where, bool append_data, bool single_precision, double omega) { am_now_working_on(FieldOutput); h5_output_data data; @@ -264,6 +265,8 @@ void fields::output_hdf5(h5file *file, const char *dataname, int num_fields, data.fun = fun; data.fun_data_ = fun_data_; + data.omega = omega; + /* compute inverse-epsilon directions for computing Dielectric fields */ data.ninveps = 0; bool needs_dielectric = false; @@ -314,22 +317,22 @@ void fields::output_hdf5(h5file *file, const char *dataname, int num_fields, void fields::output_hdf5(const char *dataname, int num_fields, const component *components, field_function fun, void *fun_data_, const volume &where, h5file *file, bool append_data, bool single_precision, const char *prefix, - bool real_part_only) { + bool real_part_only, double omega) { bool delete_file; if ((delete_file = !file)) file = open_h5file(dataname, h5file::WRITE, prefix, true); if (real_part_only) { output_hdf5(file, dataname, num_fields, components, fun, fun_data_, 0, where, append_data, - single_precision); + single_precision, omega); } else { int len = strlen(dataname) + 5; char *dataname2 = new char[len]; snprintf(dataname2, len, "%s%s", dataname, ".r"); output_hdf5(file, dataname2, num_fields, components, fun, fun_data_, 0, where, append_data, - single_precision); + single_precision, omega); snprintf(dataname2, len, "%s%s", dataname, ".i"); output_hdf5(file, dataname2, num_fields, components, fun, fun_data_, 1, where, append_data, - single_precision); + single_precision), omega; delete[] dataname2; } if (delete_file) delete file; @@ -349,7 +352,7 @@ static complex rintegrand_fun(const complex *fields, const vec & void fields::output_hdf5(const char *dataname, int num_fields, const component *components, field_rfunction fun, void *fun_data_, const volume &where, h5file *file, - bool append_data, bool single_precision, const char *prefix) { + bool append_data, bool single_precision, const char *prefix, double omega) { bool delete_file; if ((delete_file = !file)) file = open_h5file(dataname, h5file::WRITE, prefix, true); @@ -357,7 +360,7 @@ void fields::output_hdf5(const char *dataname, int num_fields, const component * data.fun = fun; data.fun_data_ = fun_data_; output_hdf5(file, dataname, num_fields, components, rintegrand_fun, (void *)&data, 0, where, - append_data, single_precision); + append_data, single_precision, omega); if (delete_file) delete file; } @@ -371,9 +374,9 @@ static complex component_fun(const complex *fields, const vec &l } void fields::output_hdf5(component c, const volume &where, h5file *file, bool append_data, - bool single_precision, const char *prefix) { + bool single_precision, const char *prefix, double omega) { if (is_derived(int(c))) { - output_hdf5(derived_component(c), where, file, append_data, single_precision, prefix); + output_hdf5(derived_component(c), where, file, append_data, single_precision, prefix, omega); return; } @@ -386,10 +389,10 @@ void fields::output_hdf5(component c, const volume &where, h5file *file, bool ap if ((delete_file = !file)) file = open_h5file(component_name(c), h5file::WRITE, prefix, true); snprintf(dataname, 256, "%s%s", component_name(c), has_imag ? ".r" : ""); - output_hdf5(file, dataname, 1, &c, component_fun, 0, 0, where, append_data, single_precision); + output_hdf5(file, dataname, 1, &c, component_fun, 0, 0, where, append_data, single_precision, omega); if (has_imag) { snprintf(dataname, 256, "%s.i", component_name(c)); - output_hdf5(file, dataname, 1, &c, component_fun, 0, 1, where, append_data, single_precision); + output_hdf5(file, dataname, 1, &c, component_fun, 0, 1, where, append_data, single_precision, omega); } if (delete_file) delete file; @@ -398,9 +401,9 @@ void fields::output_hdf5(component c, const volume &where, h5file *file, bool ap /***************************************************************************/ void fields::output_hdf5(derived_component c, const volume &where, h5file *file, bool append_data, - bool single_precision, const char *prefix) { + bool single_precision, const char *prefix, double omega) { if (!is_derived(int(c))) { - output_hdf5(component(c), where, file, append_data, single_precision, prefix); + output_hdf5(component(c), where, file, append_data, single_precision, prefix, omega); return; } @@ -411,7 +414,7 @@ void fields::output_hdf5(derived_component c, const volume &where, h5file *file, field_rfunction fun = derived_component_func(c, gv, nfields, cs); output_hdf5(component_name(c), nfields, cs, fun, &nfields, where, file, append_data, - single_precision, prefix); + single_precision, prefix, omega); } /***************************************************************************/ diff --git a/src/meep.hpp b/src/meep.hpp index 05af3eacf..992519605 100644 --- a/src/meep.hpp +++ b/src/meep.hpp @@ -600,6 +600,7 @@ class structure_chunk { void remove_susceptibilities(); // monitor.cpp + double get_chi1inv_at_pt(component, direction, int idx, double omega = 0) const; double get_chi1inv(component, direction, const ivec &iloc, double omega = 0) const; double get_inveps(component c, direction d, const ivec &iloc, double omega = 0) const { return get_chi1inv(c, d, iloc, omega); @@ -1525,23 +1526,23 @@ class fields { // low-level function: void output_hdf5(h5file *file, const char *dataname, int num_fields, const component *components, field_function fun, void *fun_data_, int reim, const volume &where, - bool append_data = false, bool single_precision = false); + bool append_data = false, bool single_precision = false, double omega = 0); // higher-level functions void output_hdf5(const char *dataname, // OUTPUT COMPLEX-VALUED FUNCTION int num_fields, const component *components, field_function fun, void *fun_data_, const volume &where, h5file *file = 0, bool append_data = false, bool single_precision = false, const char *prefix = 0, - bool real_part_only = false); + bool real_part_only = false, double omega = 0); void output_hdf5(const char *dataname, // OUTPUT REAL-VALUED FUNCTION int num_fields, const component *components, field_rfunction fun, void *fun_data_, const volume &where, h5file *file = 0, bool append_data = false, - bool single_precision = false, const char *prefix = 0); + bool single_precision = false, const char *prefix = 0, double omega = 0); void output_hdf5(component c, // OUTPUT FIELD COMPONENT (or Dielectric) const volume &where, h5file *file = 0, bool append_data = false, - bool single_precision = false, const char *prefix = 0); + bool single_precision = false, const char *prefix = 0, double omega = 0); void output_hdf5(derived_component c, // OUTPUT DERIVED FIELD COMPONENT const volume &where, h5file *file = 0, bool append_data = false, - bool single_precision = false, const char *prefix = 0); + bool single_precision = false, const char *prefix = 0, double omega = 0); h5file *open_h5file(const char *name, h5file::access_mode mode = h5file::WRITE, const char *prefix = NULL, bool timestamp = false); const char *h5file_name(const char *name, const char *prefix = NULL, bool timestamp = false); @@ -1582,20 +1583,23 @@ class fields { // 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); + field_rfunction rfun, void *fun_data, double *slice = 0, + double omega = 0); std::complex *get_complex_array_slice(const volume &where, std::vector components, field_function fun, void *fun_data, - std::complex *slice = 0); + std::complex *slice = 0, + double omega = 0); // 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 *get_array_slice(const volume &where, derived_component c, double *slice = 0); + double *get_array_slice(const volume &where, component c, double *slice = 0, double omega = 0); + double *get_array_slice(const volume &where, derived_component c, double *slice = 0, double omega = 0); std::complex *get_complex_array_slice(const volume &where, component c, - std::complex *slice = 0); + std::complex *slice = 0, + double omega = 0); // like get_array_slice, but for *sources* instead of fields std::complex *get_source_slice(const volume &where, component source_slice_component, @@ -1603,7 +1607,8 @@ class fields { // master routine for all above entry points void *do_get_array_slice(const volume &where, std::vector components, - field_function fun, field_rfunction rfun, void *fun_data, void *vslice); + field_function fun, field_rfunction rfun, void *fun_data, void *vslice, + double omega = 0); /* fetch and return coordinates and integration weights of grid points covered by an array slice, */ diff --git a/src/monitor.cpp b/src/monitor.cpp index dc8d5dd89..85036b1b3 100644 --- a/src/monitor.cpp +++ b/src/monitor.cpp @@ -224,12 +224,13 @@ double structure::get_chi1inv(component c, direction d, const ivec &origloc, dou } return 0.0; } - -double structure_chunk::get_chi1inv(component c, direction d, const ivec &iloc, double omega) const { +// Useful if you already know the exact location of the point you are interested in +// (i.e. you know what idx should be). This is used with the get_array() routines. +double structure_chunk::get_chi1inv_at_pt(component c, direction d, int idx, double omega) const { double res = 0.0; if (is_mine()){ res = - chi1inv[c][d] ? chi1inv[c][d][gv.index(c, iloc)] : (d == component_direction(c) ? 1.0 : 0); + chi1inv[c][d] ? chi1inv[c][d][idx] : (d == component_direction(c) ? 1.0 : 0); if (res != 0){ // Get instaneous dielectric (epsilon) std::complex eps(1.0 / res,0.0); @@ -238,14 +239,14 @@ double structure_chunk::get_chi1inv(component c, direction d, const ivec &iloc, susceptibility *Esus = chiP[E_stuff]; while (Esus) { if (Esus->sigma[c][d]) { - double sigma = Esus->sigma[c][d][gv.index(c, iloc)]; + double sigma = Esus->sigma[c][d][idx]; eps += Esus->chi1(omega,sigma); } Esus = Esus->next; } // Account for conductivity term if (conductivity[c][d]) { - double conductivityCur = conductivity[c][d][gv.index(c, iloc)]; + double conductivityCur = conductivity[c][d][idx]; eps = std::complex(1.0, (conductivityCur/omega)) * eps; } // Return chi1 inverse, take the real part since no support for loss in mpb yet @@ -261,6 +262,10 @@ double structure_chunk::get_chi1inv(component c, direction d, const ivec &iloc, return res; } +double structure_chunk::get_chi1inv(component c, direction d, const ivec &iloc, double omega) const { + return get_chi1inv_at_pt(c,d,gv.index(c, iloc),omega); +} + double structure::get_chi1inv(component c, direction d, const vec &loc, double omega, bool parallel) const { ivec ilocs[8]; double w[8], res = 0.0; From b0f65c3705165856b05639c9d2b48b89c16e2963 Mon Sep 17 00:00:00 2001 From: smartalecH Date: Tue, 18 Jun 2019 09:32:14 -0600 Subject: [PATCH 07/25] fix serial bugs --- python/simulation.py | 8 ++++---- python/tests/dispersive_eigenmode.py | 6 +++--- src/h5fields.cpp | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/python/simulation.py b/python/simulation.py index 5f14fa190..9d7c6fbb5 100644 --- a/python/simulation.py +++ b/python/simulation.py @@ -1803,7 +1803,7 @@ def output_component(self, c, h5file=None, omega=0): h5 = self.output_append_h5 if h5file is None else h5file append = h5file is None and self.output_append_h5 is not None - self.fields.output_hdf5(c, vol, h5, append, self.output_single_precision, self.get_filename_prefix(), omega) + self.fields.output_hdf5(c, vol, h5, append, self.output_single_precision,self.get_filename_prefix(),omega) if h5file is None: nm = self.fields.h5file_name(mp.component_name(c), self.get_filename_prefix(), True) @@ -2600,12 +2600,12 @@ def _output_png(sim, todo): return _output_png -def output_epsilon(sim,omega=0): +def output_epsilon(sim,*args,omega=0.0): sim.output_component(mp.Dielectric,omega=omega) -def output_mu(sim): - sim.output_component(mp.Permeability) +def output_mu(sim,*args,omega=0.0): + sim.output_component(mp.Permeability,omega=omega) def output_hpwr(sim): diff --git a/python/tests/dispersive_eigenmode.py b/python/tests/dispersive_eigenmode.py index a9d2b9112..405485baa 100644 --- a/python/tests/dispersive_eigenmode.py +++ b/python/tests/dispersive_eigenmode.py @@ -118,17 +118,17 @@ def verify_output_and_slice(self,material,omega,component=0,direction=0): sim = mp.Simulation(cell_size=mp.Vector3(2,2,2), default_material=material, - resolution=20 + resolution=20, + eps_averaging=False ) sim.init_sim() - vol = mp.Volume(center=mp.Vector3(), size=mp.Vector3(1,1,1)) # Check to make sure the get_slice routine is working with omega n_slice = np.sqrt(np.min(sim.get_epsilon(omega))) self.assertAlmostEqual(n,n_slice, places=6) # Check to make sure h5 output is working with omega - mp.output_epsilon(sim,omega) + mp.output_epsilon(sim,omega=omega) n_h5 = np.sqrt(np.min(h5py.File(filename, 'r')['eps'])) self.assertAlmostEqual(n,n_h5, places=6) os.remove(filename) diff --git a/src/h5fields.cpp b/src/h5fields.cpp index 88b8503b3..991cc6669 100644 --- a/src/h5fields.cpp +++ b/src/h5fields.cpp @@ -332,7 +332,7 @@ void fields::output_hdf5(const char *dataname, int num_fields, const component * single_precision, omega); snprintf(dataname2, len, "%s%s", dataname, ".i"); output_hdf5(file, dataname2, num_fields, components, fun, fun_data_, 1, where, append_data, - single_precision), omega; + single_precision,omega); delete[] dataname2; } if (delete_file) delete file; From 78d905394624e512b7fcc6f5939e3d5aa1c246e2 Mon Sep 17 00:00:00 2001 From: smartalecH Date: Tue, 18 Jun 2019 10:18:25 -0600 Subject: [PATCH 08/25] more serial fixes --- python/simulation.py | 3 ++- src/array_slice.cpp | 5 ++--- src/h5fields.cpp | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/python/simulation.py b/python/simulation.py index 9d7c6fbb5..2860ea1a5 100644 --- a/python/simulation.py +++ b/python/simulation.py @@ -2600,7 +2600,8 @@ def _output_png(sim, todo): return _output_png -def output_epsilon(sim,*args,omega=0.0): +def output_epsilon(sim,*step_func_args,**kwargs): + omega = kwargs.pop('omega', 0.0) sim.output_component(mp.Dielectric,omega=omega) diff --git a/src/array_slice.cpp b/src/array_slice.cpp index db106935b..553c6a87c 100644 --- a/src/array_slice.cpp +++ b/src/array_slice.cpp @@ -322,10 +322,9 @@ static void get_array_slice_chunkloop(fields_chunk *fc, int ichnk, component cgr fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[2 * k],omega) + fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[1 + 2 * k],omega) + fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[2 * k] + ieos[1 + 2 * k],omega)); + if (tr == 0.0) tr += 4.0; // default inveps == 1 } - if (tr == 0.0) tr = 4.0; // default inveps == 1 fields[i] = (4 * data->ninveps) / tr; - } else if (cS[i] == Permeability) { double tr = 0.0; for (int k = 0; k < data->ninvmu; ++k) { @@ -333,8 +332,8 @@ static void get_array_slice_chunkloop(fields_chunk *fc, int ichnk, component cgr fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[2 * k],omega) + fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[1 + 2 * k],omega) + fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[2 * k] + imos[1 + 2 * k],omega)); + if (tr == 0.0) tr += 4.0; // default invmu == 1 } - if (tr == 0.0) tr = 4.0; // default invmu == 1 fields[i] = (4 * data->ninvmu) / tr; } else { double f[2]; diff --git a/src/h5fields.cpp b/src/h5fields.cpp index 991cc6669..753dd0d39 100644 --- a/src/h5fields.cpp +++ b/src/h5fields.cpp @@ -179,9 +179,9 @@ static void h5_output_chunkloop(fields_chunk *fc, int ichnk, component cgrid, iv tr += (fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx,omega) + fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[2 * k],omega) + fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[1 + 2 * k],omega) + - fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[2 * k] + ieos[1 + 2 * k],omega)); + fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[2 * k] + ieos[1 + 2 * k],omega)); + if (tr == 0.0) tr += 4.0; // default inveps == 1 } - if (tr == 0.0) tr = 4.0; // default inveps == 1 fields[i] = (4 * data->ninveps) / tr; } else if (cS[i] == Permeability) { double tr = 0.0; @@ -190,8 +190,8 @@ static void h5_output_chunkloop(fields_chunk *fc, int ichnk, component cgrid, iv fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[2 * k],omega) + fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[1 + 2 * k],omega) + fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[2 * k] + imos[1 + 2 * k],omega)); + if (tr == 0.0) tr += 4.0; // default invmu == 1 } - if (tr == 0.0) tr = 4.0; // default invmu == 1 fields[i] = (4 * data->ninvmu) / tr; } else { double f[2]; From 939a99e00373b0cc67dad43190ce5270d8b03ffe Mon Sep 17 00:00:00 2001 From: smartalecH Date: Tue, 18 Jun 2019 10:57:01 -0600 Subject: [PATCH 09/25] fix mu issue --- python/simulation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/simulation.py b/python/simulation.py index 2860ea1a5..07533271f 100644 --- a/python/simulation.py +++ b/python/simulation.py @@ -2605,7 +2605,8 @@ def output_epsilon(sim,*step_func_args,**kwargs): sim.output_component(mp.Dielectric,omega=omega) -def output_mu(sim,*args,omega=0.0): +def output_mu(sim,*step_func_args,**kwargs): + omega = kwargs.pop('omega', 0.0) sim.output_component(mp.Permeability,omega=omega) From 6155d59493912c853d7b746375bef363976fbe46 Mon Sep 17 00:00:00 2001 From: smartalecH Date: Wed, 19 Jun 2019 16:41:41 -0600 Subject: [PATCH 10/25] disable h5 support --- python/simulation.py | 8 +-- python/tests/dispersive_eigenmode.py | 18 +++--- src/h5fields.cpp | 57 ++++++++--------- src/meep.hpp | 13 ++-- src/monitor.cpp | 95 +++++++++++++++++++--------- 5 files changed, 114 insertions(+), 77 deletions(-) diff --git a/python/simulation.py b/python/simulation.py index 07533271f..b07e72b97 100644 --- a/python/simulation.py +++ b/python/simulation.py @@ -1795,7 +1795,7 @@ def _add_fluxish_stuff(self, add_dft_stuff, fcen, df, nfreq, stufflist, *args): return stuff - def output_component(self, c, h5file=None, omega=0): + def output_component(self, c, h5file=None): if self.fields is None: raise RuntimeError("Fields must be initialized before calling output_component") @@ -1803,7 +1803,7 @@ def output_component(self, c, h5file=None, omega=0): h5 = self.output_append_h5 if h5file is None else h5file append = h5file is None and self.output_append_h5 is not None - self.fields.output_hdf5(c, vol, h5, append, self.output_single_precision,self.get_filename_prefix(),omega) + self.fields.output_hdf5(c, vol, h5, append, self.output_single_precision,self.get_filename_prefix()) if h5file is None: nm = self.fields.h5file_name(mp.component_name(c), self.get_filename_prefix(), True) @@ -2602,12 +2602,12 @@ def _output_png(sim, todo): def output_epsilon(sim,*step_func_args,**kwargs): omega = kwargs.pop('omega', 0.0) - sim.output_component(mp.Dielectric,omega=omega) + sim.output_component(mp.Dielectric) def output_mu(sim,*step_func_args,**kwargs): omega = kwargs.pop('omega', 0.0) - sim.output_component(mp.Permeability,omega=omega) + sim.output_component(mp.Permeability) def output_hpwr(sim): diff --git a/python/tests/dispersive_eigenmode.py b/python/tests/dispersive_eigenmode.py index 405485baa..8439fe763 100644 --- a/python/tests/dispersive_eigenmode.py +++ b/python/tests/dispersive_eigenmode.py @@ -112,9 +112,9 @@ def test_chi1_routine(self): self.compare_meep_mpb(LiNbO3,w0) self.compare_meep_mpb(LiNbO3,w1) - def verify_output_and_slice(self,material,omega,component=0,direction=0): - filename = 'dispersive_eigenmode-eps-000000.00.h5' - n = np.real(np.sqrt(material.epsilon(omega)[component,direction])) + def verify_output_and_slice(self,material,omega): + # Since the slice routines average the diagonals, we need to do that too: + n = np.mean(np.real(np.sqrt(material.epsilon(omega).diagonal()))) sim = mp.Simulation(cell_size=mp.Vector3(2,2,2), default_material=material, @@ -128,10 +128,12 @@ def verify_output_and_slice(self,material,omega,component=0,direction=0): self.assertAlmostEqual(n,n_slice, places=6) # Check to make sure h5 output is working with omega - mp.output_epsilon(sim,omega=omega) - n_h5 = np.sqrt(np.min(h5py.File(filename, 'r')['eps'])) - self.assertAlmostEqual(n,n_h5, places=6) - os.remove(filename) + # NOTE: We'll add this test once h5 support is added + #filename = 'dispersive_eigenmode-eps-000000.00.h5' + #mp.output_epsilon(sim,omega=omega) + #n_h5 = np.sqrt(np.min(h5py.File(filename, 'r')['eps'])) + #self.assertAlmostEqual(n,n_h5, places=6) + #os.remove(filename) def test_get_with_dispersion(self): # This test checks the get_array_slice and output_fields method @@ -157,7 +159,7 @@ def test_get_with_dispersion(self): self.verify_output_and_slice(Au,w0) self.verify_output_and_slice(Au,w1) - # Check Lithium Niobate (X,X) + # Check Lithium Niobate w0 = LiNbO3.valid_freq_range.min w1 = LiNbO3.valid_freq_range.max #self.verify_output_and_slice(LiNbO3,w0) diff --git a/src/h5fields.cpp b/src/h5fields.cpp index 753dd0d39..ab850a322 100644 --- a/src/h5fields.cpp +++ b/src/h5fields.cpp @@ -50,7 +50,6 @@ typedef struct { complex *ph; complex *fields; ptrdiff_t *offsets; - double omega; int ninveps; component inveps_cs[3]; direction inveps_ds[3]; @@ -151,8 +150,6 @@ static void h5_output_chunkloop(fields_chunk *fc, int ichnk, component cgrid, iv const direction *imds = data->invmu_ds; ptrdiff_t imos[6]; - double omega = data->omega; - for (int i = 0; i < data->num_fields; ++i) { cS[i] = S.transform(data->components[i], -sn); if (cS[i] == Dielectric || cS[i] == Permeability) @@ -176,21 +173,23 @@ static void h5_output_chunkloop(fields_chunk *fc, int ichnk, component cgrid, iv if (cS[i] == Dielectric) { double tr = 0.0; for (int k = 0; k < data->ninveps; ++k) { - tr += (fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx,omega) + - fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[2 * k],omega) + - fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[1 + 2 * k],omega) + - fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[2 * k] + ieos[1 + 2 * k],omega)); - if (tr == 0.0) tr += 4.0; // default inveps == 1 + const realnum *ie = fc->s->chi1inv[iecs[k]][ieds[k]]; + if (ie) + tr += (ie[idx] + ie[idx + ieos[2 * k]] + ie[idx + ieos[1 + 2 * k]] + + ie[idx + ieos[2 * k] + ieos[1 + 2 * k]]); + else + tr += 4; // default inveps == 1 } fields[i] = (4 * data->ninveps) / tr; } else if (cS[i] == Permeability) { double tr = 0.0; for (int k = 0; k < data->ninvmu; ++k) { - tr += (fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx,omega) + - fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[2 * k],omega) + - fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[1 + 2 * k],omega) + - fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[2 * k] + imos[1 + 2 * k],omega)); - if (tr == 0.0) tr += 4.0; // default invmu == 1 + const realnum *im = fc->s->chi1inv[imcs[k]][imds[k]]; + if (im) + tr += (im[idx] + im[idx + imos[2 * k]] + im[idx + imos[1 + 2 * k]] + + im[idx + imos[2 * k] + imos[1 + 2 * k]]); + else + tr += 4; // default invmu == 1 } fields[i] = (4 * data->ninvmu) / tr; } else { @@ -220,7 +219,7 @@ static void h5_output_chunkloop(fields_chunk *fc, int ichnk, component cgrid, iv void fields::output_hdf5(h5file *file, const char *dataname, int num_fields, const component *components, field_function fun, void *fun_data_, int reim, - const volume &where, bool append_data, bool single_precision, double omega) { + const volume &where, bool append_data, bool single_precision) { am_now_working_on(FieldOutput); h5_output_data data; @@ -265,8 +264,6 @@ void fields::output_hdf5(h5file *file, const char *dataname, int num_fields, data.fun = fun; data.fun_data_ = fun_data_; - data.omega = omega; - /* compute inverse-epsilon directions for computing Dielectric fields */ data.ninveps = 0; bool needs_dielectric = false; @@ -317,22 +314,22 @@ void fields::output_hdf5(h5file *file, const char *dataname, int num_fields, void fields::output_hdf5(const char *dataname, int num_fields, const component *components, field_function fun, void *fun_data_, const volume &where, h5file *file, bool append_data, bool single_precision, const char *prefix, - bool real_part_only, double omega) { + bool real_part_only) { bool delete_file; if ((delete_file = !file)) file = open_h5file(dataname, h5file::WRITE, prefix, true); if (real_part_only) { output_hdf5(file, dataname, num_fields, components, fun, fun_data_, 0, where, append_data, - single_precision, omega); + single_precision); } else { int len = strlen(dataname) + 5; char *dataname2 = new char[len]; snprintf(dataname2, len, "%s%s", dataname, ".r"); output_hdf5(file, dataname2, num_fields, components, fun, fun_data_, 0, where, append_data, - single_precision, omega); + single_precision); snprintf(dataname2, len, "%s%s", dataname, ".i"); output_hdf5(file, dataname2, num_fields, components, fun, fun_data_, 1, where, append_data, - single_precision,omega); + single_precision); delete[] dataname2; } if (delete_file) delete file; @@ -352,7 +349,7 @@ static complex rintegrand_fun(const complex *fields, const vec & void fields::output_hdf5(const char *dataname, int num_fields, const component *components, field_rfunction fun, void *fun_data_, const volume &where, h5file *file, - bool append_data, bool single_precision, const char *prefix, double omega) { + bool append_data, bool single_precision, const char *prefix) { bool delete_file; if ((delete_file = !file)) file = open_h5file(dataname, h5file::WRITE, prefix, true); @@ -360,7 +357,7 @@ void fields::output_hdf5(const char *dataname, int num_fields, const component * data.fun = fun; data.fun_data_ = fun_data_; output_hdf5(file, dataname, num_fields, components, rintegrand_fun, (void *)&data, 0, where, - append_data, single_precision, omega); + append_data, single_precision); if (delete_file) delete file; } @@ -374,9 +371,9 @@ static complex component_fun(const complex *fields, const vec &l } void fields::output_hdf5(component c, const volume &where, h5file *file, bool append_data, - bool single_precision, const char *prefix, double omega) { + bool single_precision, const char *prefix) { if (is_derived(int(c))) { - output_hdf5(derived_component(c), where, file, append_data, single_precision, prefix, omega); + output_hdf5(derived_component(c), where, file, append_data, single_precision, prefix); return; } @@ -389,10 +386,10 @@ void fields::output_hdf5(component c, const volume &where, h5file *file, bool ap if ((delete_file = !file)) file = open_h5file(component_name(c), h5file::WRITE, prefix, true); snprintf(dataname, 256, "%s%s", component_name(c), has_imag ? ".r" : ""); - output_hdf5(file, dataname, 1, &c, component_fun, 0, 0, where, append_data, single_precision, omega); + output_hdf5(file, dataname, 1, &c, component_fun, 0, 0, where, append_data, single_precision); if (has_imag) { snprintf(dataname, 256, "%s.i", component_name(c)); - output_hdf5(file, dataname, 1, &c, component_fun, 0, 1, where, append_data, single_precision, omega); + output_hdf5(file, dataname, 1, &c, component_fun, 0, 1, where, append_data, single_precision); } if (delete_file) delete file; @@ -401,9 +398,9 @@ void fields::output_hdf5(component c, const volume &where, h5file *file, bool ap /***************************************************************************/ void fields::output_hdf5(derived_component c, const volume &where, h5file *file, bool append_data, - bool single_precision, const char *prefix, double omega) { + bool single_precision, const char *prefix) { if (!is_derived(int(c))) { - output_hdf5(component(c), where, file, append_data, single_precision, prefix, omega); + output_hdf5(component(c), where, file, append_data, single_precision, prefix); return; } @@ -414,7 +411,7 @@ void fields::output_hdf5(derived_component c, const volume &where, h5file *file, field_rfunction fun = derived_component_func(c, gv, nfields, cs); output_hdf5(component_name(c), nfields, cs, fun, &nfields, where, file, append_data, - single_precision, prefix, omega); + single_precision, prefix); } /***************************************************************************/ @@ -448,4 +445,4 @@ h5file *fields::open_h5file(const char *name, h5file::access_mode mode, const ch return new h5file(filename, mode, true); } -} // namespace meep +} // namespace meep \ No newline at end of file diff --git a/src/meep.hpp b/src/meep.hpp index 992519605..91fd16a0c 100644 --- a/src/meep.hpp +++ b/src/meep.hpp @@ -600,7 +600,8 @@ class structure_chunk { void remove_susceptibilities(); // monitor.cpp - double get_chi1inv_at_pt(component, direction, int idx, double omega = 0) const; + double get_DC_chi1_at_pt(component c, direction d, int idx) const; + double get_chi1inv_at_pt(component, direction, int idx, double omega = 0) const; double get_chi1inv(component, direction, const ivec &iloc, double omega = 0) const; double get_inveps(component c, direction d, const ivec &iloc, double omega = 0) const { return get_chi1inv(c, d, iloc, omega); @@ -1526,23 +1527,23 @@ class fields { // low-level function: void output_hdf5(h5file *file, const char *dataname, int num_fields, const component *components, field_function fun, void *fun_data_, int reim, const volume &where, - bool append_data = false, bool single_precision = false, double omega = 0); + bool append_data = false, bool single_precision = false); // higher-level functions void output_hdf5(const char *dataname, // OUTPUT COMPLEX-VALUED FUNCTION int num_fields, const component *components, field_function fun, void *fun_data_, const volume &where, h5file *file = 0, bool append_data = false, bool single_precision = false, const char *prefix = 0, - bool real_part_only = false, double omega = 0); + bool real_part_only = false); void output_hdf5(const char *dataname, // OUTPUT REAL-VALUED FUNCTION int num_fields, const component *components, field_rfunction fun, void *fun_data_, const volume &where, h5file *file = 0, bool append_data = false, - bool single_precision = false, const char *prefix = 0, double omega = 0); + bool single_precision = false, const char *prefix = 0); void output_hdf5(component c, // OUTPUT FIELD COMPONENT (or Dielectric) const volume &where, h5file *file = 0, bool append_data = false, - bool single_precision = false, const char *prefix = 0, double omega = 0); + bool single_precision = false, const char *prefix = 0); void output_hdf5(derived_component c, // OUTPUT DERIVED FIELD COMPONENT const volume &where, h5file *file = 0, bool append_data = false, - bool single_precision = false, const char *prefix = 0, double omega = 0); + bool single_precision = false, const char *prefix = 0); h5file *open_h5file(const char *name, h5file::access_mode mode = h5file::WRITE, const char *prefix = NULL, bool timestamp = false); const char *h5file_name(const char *name, const char *prefix = NULL, bool timestamp = false); diff --git a/src/monitor.cpp b/src/monitor.cpp index 85036b1b3..539c49e4a 100644 --- a/src/monitor.cpp +++ b/src/monitor.cpp @@ -224,41 +224,78 @@ double structure::get_chi1inv(component c, direction d, const ivec &origloc, dou } return 0.0; } -// Useful if you already know the exact location of the point you are interested in -// (i.e. you know what idx should be). This is used with the get_array() routines. + +// This pulls the dc chi1 value from the chi1inv tensor by inverting it. +// So far only works for cartesian coordinates... +double structure_chunk::get_DC_chi1_at_pt(component c, direction d, int idx) const { + component comp_list[3]; + if (is_electric(c)) { + comp_list[0] = Ex; comp_list[1] = Ey; comp_list[2] = Ez; + }else if (is_magnetic(c)) { + comp_list[0] = Hx; comp_list[1] = Hy; comp_list[2] = Hz; + } else if (is_D(c)) { + comp_list[0] = Dx; comp_list[1] = Dy; comp_list[2] = Dz; + } else if (is_B(c)) { + comp_list[0] = Bx; comp_list[1] = By; comp_list[2] = Bz; + } + + // Set up chi1inv 3x3 symmetric matrix + double m00 = chi1inv[comp_list[0]][X] ? chi1inv[comp_list[0]][X][idx] : 1.0; + double m11 = chi1inv[comp_list[1]][Y] ? chi1inv[comp_list[1]][Y][idx] : 1.0; + double m22 = chi1inv[comp_list[2]][Z] ? chi1inv[comp_list[2]][Z][idx] : 1.0; + double m01 = chi1inv[comp_list[0]][Y] ? chi1inv[comp_list[0]][Y][idx] : 0; + double m02 = chi1inv[comp_list[0]][Z] ? chi1inv[comp_list[0]][Z][idx] : 0; + double m12 = chi1inv[comp_list[1]][Z] ? chi1inv[comp_list[1]][Z][idx] : 0; + + // Calculate determinant + double det = m00 * (m11*m22-m12*m12) - m01 * (m01*m22-m12*m02) + m02 * (m01*m12-m11*m02); + if (det==0) abort("Chi1 Inverse matrix is singular.\n"); + + // Set up chi1 3x3 symmetric matrix, as the inverse + double A[3][3]; + A[0][0] = (m11*m22 - m12*m12)/det; + A[1][1] = (m00*m22 - m02*m02)/det; + A[2][2] = (m00*m11 - m12*m12)/det; + A[0][1] = A[1][0] = (m02*m12 - m22*m01)/det; + A[0][2] = A[2][0] = (m01*m12 - m02*m11)/det; + A[1][2] = A[2][1] = (m01*m02 - m00*m12)/det; + + // Return the component we care about + return A[component_index(c)][d]; +} + double structure_chunk::get_chi1inv_at_pt(component c, direction d, int idx, double omega) const { double res = 0.0; if (is_mine()){ - res = - chi1inv[c][d] ? chi1inv[c][d][idx] : (d == component_direction(c) ? 1.0 : 0); - if (res != 0){ - // Get instaneous dielectric (epsilon) - std::complex eps(1.0 / res,0.0); - // Loop through and add up susceptibility contributions - // locate correct susceptibility list - susceptibility *Esus = chiP[E_stuff]; - while (Esus) { - if (Esus->sigma[c][d]) { - double sigma = Esus->sigma[c][d][idx]; - eps += Esus->chi1(omega,sigma); - } - Esus = Esus->next; - } - // Account for conductivity term - if (conductivity[c][d]) { - double conductivityCur = conductivity[c][d][idx]; - eps = std::complex(1.0, (conductivityCur/omega)) * eps; - } - // Return chi1 inverse, take the real part since no support for loss in mpb yet - // TODO: Add support for metals - if (eps.imag() == 0.0){ - res = 1.0 / eps.real(); - }else{ - res = 1.0 / (std::sqrt(eps).real() * std::sqrt(eps).real()); + // Get instantaneous chi1 value (i.e DC value) + res = get_DC_chi1_at_pt(c,d,idx); + std::complex eps(res,0.0); + // Loop through and add up susceptibility contributions + // locate correct susceptibility list + susceptibility *Esus = chiP[E_stuff]; + while (Esus) { + if (Esus->sigma[c][d]) { + double sigma = Esus->sigma[c][d][idx]; + eps += Esus->chi1(omega,sigma); + } + Esus = Esus->next; + } + // Account for conductivity term + if (conductivity[c][d]) { + double conductivityCur = conductivity[c][d][idx]; + eps = std::complex(1.0, (conductivityCur/omega)) * eps; } + // Return chi1 inverse, take the real part since no support for loss in mpb yet + // TODO: Add support for metals + if (eps.real() == 0.0 && eps.imag() == 0.0){ + res = 0.0; + }else if(eps.imag() == 0.0){ + res = 1.0 / eps.real(); + }else{ + res = 1.0 / (std::sqrt(eps).real() * std::sqrt(eps).real()); } } - + //master_printf("res: %g\n",res); return res; } From 6eac06640f001e12263d02a4e1793511dca0d50c Mon Sep 17 00:00:00 2001 From: smartalecH Date: Fri, 21 Jun 2019 14:37:58 -0600 Subject: [PATCH 11/25] big fix --- python/geom.py | 23 ++++ python/materials.py | 1 + python/simulation.py | 8 +- python/tests/dispersive_eigenmode.py | 172 +++++++++++++++------------ src/h5fields.cpp | 53 +++++---- src/meep.hpp | 22 ++-- src/monitor.cpp | 155 ++++++++++++++++-------- src/mpb.cpp | 9 +- 8 files changed, 276 insertions(+), 167 deletions(-) diff --git a/python/geom.py b/python/geom.py index 7d9249801..4fce49ccf 100755 --- a/python/geom.py +++ b/python/geom.py @@ -239,6 +239,29 @@ def transform(self, m): for s in self.H_susceptibilities: s.transform(m) + def rotate(self, rotations): + for rot in rotations: + if np.count_nonzero(rot) != 1: + raise ValueError("Each rotation vector should only have 1 coordinate.") + if rot.x != 0: # rotate about x axis + self.transform(Matrix( + Vector3(1,0,0), + Vector3(0,np.cos(rot.x),np.sin(rot.x)), + Vector3(0,-np.sin(rot.x),np.cos(rot.x)) + )) + elif rot.y != 0: # rotate about z axis + self.transform(Matrix( + Vector3(np.cos(rot.y),0,-np.sin(rot.y)), + Vector3(0,1,0), + Vector3(np.sin(rot.y),0,np.cos(np.y)) + )) + else: + self.transform(Matrix( + Vector3(np.cos(rot.z),np.sin(rot.z),0), + Vector3(-np.sin(np.z),np.cos(rot.z),0), + Vector3(0,0,1) + )) + def epsilon(self,freq): return self._get_epsmu(self.epsilon_diag, self.epsilon_offdiag, self.E_susceptibilities, self.D_conductivity_diag, self.D_conductivity_offdiag, freq) diff --git a/python/materials.py b/python/materials.py index 4b9e92838..028b6dcaa 100644 --- a/python/materials.py +++ b/python/materials.py @@ -2,6 +2,7 @@ # Materials Library import meep as mp +import numpy as np # default unit length is 1 um um_scale = 1.0 diff --git a/python/simulation.py b/python/simulation.py index b07e72b97..0ab46fd08 100644 --- a/python/simulation.py +++ b/python/simulation.py @@ -1795,7 +1795,7 @@ def _add_fluxish_stuff(self, add_dft_stuff, fcen, df, nfreq, stufflist, *args): return stuff - def output_component(self, c, h5file=None): + def output_component(self, c, h5file=None, omega=0): if self.fields is None: raise RuntimeError("Fields must be initialized before calling output_component") @@ -1803,7 +1803,7 @@ def output_component(self, c, h5file=None): h5 = self.output_append_h5 if h5file is None else h5file append = h5file is None and self.output_append_h5 is not None - self.fields.output_hdf5(c, vol, h5, append, self.output_single_precision,self.get_filename_prefix()) + self.fields.output_hdf5(c, vol, h5, append, self.output_single_precision,self.get_filename_prefix(), omega) if h5file is None: nm = self.fields.h5file_name(mp.component_name(c), self.get_filename_prefix(), True) @@ -2602,12 +2602,12 @@ def _output_png(sim, todo): def output_epsilon(sim,*step_func_args,**kwargs): omega = kwargs.pop('omega', 0.0) - sim.output_component(mp.Dielectric) + sim.output_component(mp.Dielectric,omega=omega) def output_mu(sim,*step_func_args,**kwargs): omega = kwargs.pop('omega', 0.0) - sim.output_component(mp.Permeability) + sim.output_component(mp.Permeability,omega=omega) def output_hpwr(sim): diff --git a/python/tests/dispersive_eigenmode.py b/python/tests/dispersive_eigenmode.py index 8439fe763..2c1bd4786 100644 --- a/python/tests/dispersive_eigenmode.py +++ b/python/tests/dispersive_eigenmode.py @@ -13,76 +13,94 @@ import numpy as np from meep import mpb import h5py -import os - - class TestDispersiveEigenmode(unittest.TestCase): - + # ----------------------------------------- # + # ----------- Helper Functions ------------ # + # ----------------------------------------- # # Directly calss the C++ chi1 routine - def call_chi1(self,material,component,direction,omega): + def call_chi1(self,material,omega): sim = mp.Simulation(cell_size=mp.Vector3(1,1,1), default_material=material, - resolution=10) + resolution=20) sim.init_sim() v3 = mp.py_v3_to_vec(sim.dimensions, mp.Vector3(0,0,0), sim.is_cylindrical) - n = 1/np.sqrt(sim.structure.get_chi1inv(int(component),int(direction),v3,omega)) - return n + chi1inv = np.zeros((3,3),dtype=np.float64) + for i, com in enumerate([mp.Ex,mp.Ey,mp.Ez]): + for k, dir in enumerate([mp.X,mp.Y,mp.Z]): + chi1inv[i,k] = sim.structure.get_chi1inv(com,dir,v3,omega) + n = np.real(np.sqrt(np.linalg.inv(chi1inv.astype(np.complex128)))) + + n_actual = np.real(np.sqrt(material.epsilon(omega).astype(np.complex128))) + + np.testing.assert_allclose(n,n_actual) # Pulls the "effective index" of a uniform, dispersive material # (i.e. the refractive index) using meep's get_eigenmode def simulate_meep(self,material,omega): - sim = mp.Simulation(cell_size=mp.Vector3(2,2,2), + sim = mp.Simulation(cell_size=mp.Vector3(1,1,1), default_material=material, - resolution=20 + resolution=1 ) - direction = mp.X - where = mp.Volume(center=mp.Vector3(0,0,0),size=mp.Vector3(0,1,1)) band_num = 1 - kpoint = mp.Vector3(2,0,0) sim.init_sim() - em = sim.get_eigenmode(omega,direction,where,band_num,kpoint) - neff_meep = np.squeeze(em.k.x) / np.squeeze(em.freq) - return neff_meep - - # Pulls the "effective index" of a uniform, dispersive material - # (i.e. the refractive index) using mpb - def simulate_mpb(self,material,omega): - ms = mpb.ModeSolver( - geometry_lattice=mp.Lattice(size=mp.Vector3(0,2,2)), - default_material=material, - resolution=10, - num_bands=1 - ) - k = ms.find_k(mp.NO_PARITY, omega, 1, 1, mp.Vector3(1), 1e-3, omega * 1, - omega * 0.1, omega * 6) + # Pull the x direction + direction = mp.X + where = mp.Volume(center=mp.Vector3(0,0,0),size=mp.Vector3(0,0,0)) + kpoint = mp.Vector3(1,0,0) + emx = sim.get_eigenmode(omega,direction,where,band_num,kpoint) + + # Pull the y direction + direction = mp.Y + where = mp.Volume(center=mp.Vector3(0,0,0),size=mp.Vector3(0,0,0)) + kpoint = mp.Vector3(0,1,0) + emy = sim.get_eigenmode(omega,direction,where,band_num,kpoint) + + # Pull the z direction + direction = mp.Z + where = mp.Volume(center=mp.Vector3(0,0,0),size=mp.Vector3(0,0,0)) + kpoint = mp.Vector3(0,0,1) + emz = sim.get_eigenmode(omega,direction,where,band_num,kpoint) - neff_mpb = k[0]/omega - return neff_mpb + # combine and return + k = np.array(mp.Matrix(emx.k,emy.k,emz.k)) + neff_meep = np.squeeze(k) / omega + + n = np.real(np.sqrt(material.epsilon(omega).astype(np.complex128))) + + np.testing.assert_allclose(n,neff_meep) - # main test bed to check the new features - def compare_meep_mpb(self,material,omega,component=0,direction=0): - n = np.real(np.sqrt(material.epsilon(omega)[component,direction])) - chi1 = self.call_chi1(material,mp.Ex,mp.X,omega) - n_meep = self.simulate_meep(material,omega) - # Let's wait to check this until we enable dispersive materials in MPB... - #n_mpb = self.simulate_mpb(material,omega) - - # Check that the chi1 value matches the refractive index - self.assertAlmostEqual(n,chi1, places=6) - - # Check that the chi1 value matches meep's get_eigenmode - self.assertAlmostEqual(n,n_meep, places=6) + def verify_output_and_slice(self,material,omega): + # Since the slice routines average the diagonals, we need to do that too: + n = np.real(np.sqrt(np.mean(np.linalg.eigvals(material.epsilon(omega))))) + + sim = mp.Simulation(cell_size=mp.Vector3(2,2,2), + default_material=material, + resolution=20, + eps_averaging=False + ) + sim.init_sim() - # Check that the chi1 value matches mpb's get_eigenmode - #self.assertAlmostEqual(n,n_mpb, places=6) + # Check to make sure the get_slice routine is working with omega + n_slice = np.sqrt(np.max(sim.get_epsilon(omega))) + self.assertAlmostEqual(n,n_slice, places=4) + # Check to make sure h5 output is working with omega + # NOTE: We'll add this test once h5 support is added + filename = 'dispersive_eigenmode-eps-000000.00.h5' + mp.output_epsilon(sim,omega=omega) + n_h5 = np.sqrt(np.mean(h5py.File(filename, 'r')['eps'])) + self.assertAlmostEqual(n,n_h5, places=4) + + # ----------------------------------------- # + # ----------- Test Routines --------------- # + # ----------------------------------------- # def test_chi1_routine(self): - # This test checks the newly implemented get_chi1inv routines within the + # Checks the newly implemented get_chi1inv routines within the # fields and structure classes by comparing their output to the # python epsilon output. @@ -91,52 +109,54 @@ def test_chi1_routine(self): # Check Silicon w0 = Si.valid_freq_range.min w1 = Si.valid_freq_range.max - self.compare_meep_mpb(Si,w0) - self.compare_meep_mpb(Si,w1) + self.call_chi1(Si,w0) + self.call_chi1(Si,w1) # Check Silver w0 = Ag.valid_freq_range.min w1 = Ag.valid_freq_range.max - self.compare_meep_mpb(Ag,w0) - self.compare_meep_mpb(Ag,w1) + self.call_chi1(Ag,w0) + self.call_chi1(Ag,w1) # Check Gold w0 = Au.valid_freq_range.min w1 = Au.valid_freq_range.max - self.compare_meep_mpb(Au,w0) - self.compare_meep_mpb(Au,w1) + self.call_chi1(Au,w0) + self.call_chi1(Au,w1) # Check Lithium Niobate (X,X) w0 = LiNbO3.valid_freq_range.min w1 = LiNbO3.valid_freq_range.max - self.compare_meep_mpb(LiNbO3,w0) - self.compare_meep_mpb(LiNbO3,w1) + self.call_chi1(LiNbO3,w0) + self.call_chi1(LiNbO3,w1) + + LiNbO3.rotate([mp.Vector3(x=np.radians(28)),mp.Vector3(np.radians(45))]) + self.call_chi1(LiNbO3,w0) + self.call_chi1(LiNbO3,w1) - def verify_output_and_slice(self,material,omega): - # Since the slice routines average the diagonals, we need to do that too: - n = np.mean(np.real(np.sqrt(material.epsilon(omega).diagonal()))) - - sim = mp.Simulation(cell_size=mp.Vector3(2,2,2), - default_material=material, - resolution=20, - eps_averaging=False - ) - sim.init_sim() - - # Check to make sure the get_slice routine is working with omega - n_slice = np.sqrt(np.min(sim.get_epsilon(omega))) - self.assertAlmostEqual(n,n_slice, places=6) + def test_meep_eigenmode(self): + # Checks the get_eigenmode features with dispersive materials + # NOTE: metals are not supported + from meep.materials import Si, Ag, LiNbO3, Au - # Check to make sure h5 output is working with omega - # NOTE: We'll add this test once h5 support is added - #filename = 'dispersive_eigenmode-eps-000000.00.h5' - #mp.output_epsilon(sim,omega=omega) - #n_h5 = np.sqrt(np.min(h5py.File(filename, 'r')['eps'])) - #self.assertAlmostEqual(n,n_h5, places=6) - #os.remove(filename) + # Check Silicon + w0 = Si.valid_freq_range.min + w1 = Si.valid_freq_range.max + self.simulate_meep(Si,w0) + self.simulate_meep(Si,w1) + + # Check Lithium Niobate + w0 = LiNbO3.valid_freq_range.min + w1 = LiNbO3.valid_freq_range.max + #self.simulate_meep(LiNbO3,w0) + #self.simulate_meep(LiNbO3,w1) + + LiNbO3.rotate([mp.Vector3(x=np.radians(28)),mp.Vector3(np.radians(45))]) + #self.simulate_meep(LiNbO3,w0) + #self.simulate_meep(LiNbO3,w1) def test_get_with_dispersion(self): - # This test checks the get_array_slice and output_fields method + # Checks the get_array_slice and output_fields method # with dispersive materials. from meep.materials import Si, Ag, LiNbO3, Au diff --git a/src/h5fields.cpp b/src/h5fields.cpp index ab850a322..fec91470c 100644 --- a/src/h5fields.cpp +++ b/src/h5fields.cpp @@ -50,6 +50,7 @@ typedef struct { complex *ph; complex *fields; ptrdiff_t *offsets; + double omega; int ninveps; component inveps_cs[3]; direction inveps_ds[3]; @@ -143,6 +144,7 @@ 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; + double omega = data->omega; const component *iecs = data->inveps_cs; const direction *ieds = data->inveps_ds; ptrdiff_t ieos[6]; @@ -173,23 +175,21 @@ static void h5_output_chunkloop(fields_chunk *fc, int ichnk, component cgrid, iv if (cS[i] == Dielectric) { double tr = 0.0; for (int k = 0; k < data->ninveps; ++k) { - const realnum *ie = fc->s->chi1inv[iecs[k]][ieds[k]]; - if (ie) - tr += (ie[idx] + ie[idx + ieos[2 * k]] + ie[idx + ieos[1 + 2 * k]] + - ie[idx + ieos[2 * k] + ieos[1 + 2 * k]]); - else - tr += 4; // default inveps == 1 + tr += (fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx,omega) + + fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[2 * k],omega) + + fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[1 + 2 * k],omega) + + fc->s->get_chi1inv_at_pt(iecs[k],ieds[k],idx + ieos[2 * k] + ieos[1 + 2 * k],omega)); + if (tr == 0.0) tr += 4.0; // default inveps == 1 } fields[i] = (4 * data->ninveps) / tr; } else if (cS[i] == Permeability) { double tr = 0.0; for (int k = 0; k < data->ninvmu; ++k) { - const realnum *im = fc->s->chi1inv[imcs[k]][imds[k]]; - if (im) - tr += (im[idx] + im[idx + imos[2 * k]] + im[idx + imos[1 + 2 * k]] + - im[idx + imos[2 * k] + imos[1 + 2 * k]]); - else - tr += 4; // default invmu == 1 + tr += (fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx,omega) + + fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[2 * k],omega) + + fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[1 + 2 * k],omega) + + fc->s->get_chi1inv_at_pt(imcs[k],imds[k],idx + imos[2 * k] + imos[1 + 2 * k],omega)); + if (tr == 0.0) tr += 4.0; // default invmu == 1 } fields[i] = (4 * data->ninvmu) / tr; } else { @@ -219,7 +219,7 @@ static void h5_output_chunkloop(fields_chunk *fc, int ichnk, component cgrid, iv void fields::output_hdf5(h5file *file, const char *dataname, int num_fields, const component *components, field_function fun, void *fun_data_, int reim, - const volume &where, bool append_data, bool single_precision) { + const volume &where, bool append_data, bool single_precision, double omega) { am_now_working_on(FieldOutput); h5_output_data data; @@ -265,6 +265,7 @@ void fields::output_hdf5(h5file *file, const char *dataname, int num_fields, data.fun_data_ = fun_data_; /* compute inverse-epsilon directions for computing Dielectric fields */ + data.omega = omega; data.ninveps = 0; bool needs_dielectric = false; for (int i = 0; i < num_fields; ++i) @@ -314,22 +315,22 @@ void fields::output_hdf5(h5file *file, const char *dataname, int num_fields, void fields::output_hdf5(const char *dataname, int num_fields, const component *components, field_function fun, void *fun_data_, const volume &where, h5file *file, bool append_data, bool single_precision, const char *prefix, - bool real_part_only) { + bool real_part_only, double omega) { bool delete_file; if ((delete_file = !file)) file = open_h5file(dataname, h5file::WRITE, prefix, true); if (real_part_only) { output_hdf5(file, dataname, num_fields, components, fun, fun_data_, 0, where, append_data, - single_precision); + single_precision, omega); } else { int len = strlen(dataname) + 5; char *dataname2 = new char[len]; snprintf(dataname2, len, "%s%s", dataname, ".r"); output_hdf5(file, dataname2, num_fields, components, fun, fun_data_, 0, where, append_data, - single_precision); + single_precision, omega); snprintf(dataname2, len, "%s%s", dataname, ".i"); output_hdf5(file, dataname2, num_fields, components, fun, fun_data_, 1, where, append_data, - single_precision); + single_precision, omega); delete[] dataname2; } if (delete_file) delete file; @@ -349,7 +350,7 @@ static complex rintegrand_fun(const complex *fields, const vec & void fields::output_hdf5(const char *dataname, int num_fields, const component *components, field_rfunction fun, void *fun_data_, const volume &where, h5file *file, - bool append_data, bool single_precision, const char *prefix) { + bool append_data, bool single_precision, const char *prefix, double omega) { bool delete_file; if ((delete_file = !file)) file = open_h5file(dataname, h5file::WRITE, prefix, true); @@ -357,7 +358,7 @@ void fields::output_hdf5(const char *dataname, int num_fields, const component * data.fun = fun; data.fun_data_ = fun_data_; output_hdf5(file, dataname, num_fields, components, rintegrand_fun, (void *)&data, 0, where, - append_data, single_precision); + append_data, single_precision, omega); if (delete_file) delete file; } @@ -371,9 +372,9 @@ static complex component_fun(const complex *fields, const vec &l } void fields::output_hdf5(component c, const volume &where, h5file *file, bool append_data, - bool single_precision, const char *prefix) { + bool single_precision, const char *prefix, double omega) { if (is_derived(int(c))) { - output_hdf5(derived_component(c), where, file, append_data, single_precision, prefix); + output_hdf5(derived_component(c), where, file, append_data, single_precision, prefix, omega); return; } @@ -386,10 +387,10 @@ void fields::output_hdf5(component c, const volume &where, h5file *file, bool ap if ((delete_file = !file)) file = open_h5file(component_name(c), h5file::WRITE, prefix, true); snprintf(dataname, 256, "%s%s", component_name(c), has_imag ? ".r" : ""); - output_hdf5(file, dataname, 1, &c, component_fun, 0, 0, where, append_data, single_precision); + output_hdf5(file, dataname, 1, &c, component_fun, 0, 0, where, append_data, single_precision, omega); if (has_imag) { snprintf(dataname, 256, "%s.i", component_name(c)); - output_hdf5(file, dataname, 1, &c, component_fun, 0, 1, where, append_data, single_precision); + output_hdf5(file, dataname, 1, &c, component_fun, 0, 1, where, append_data, single_precision, omega); } if (delete_file) delete file; @@ -398,9 +399,9 @@ void fields::output_hdf5(component c, const volume &where, h5file *file, bool ap /***************************************************************************/ void fields::output_hdf5(derived_component c, const volume &where, h5file *file, bool append_data, - bool single_precision, const char *prefix) { + bool single_precision, const char *prefix, double omega) { if (!is_derived(int(c))) { - output_hdf5(component(c), where, file, append_data, single_precision, prefix); + output_hdf5(component(c), where, file, append_data, single_precision, prefix, omega); return; } @@ -411,7 +412,7 @@ void fields::output_hdf5(derived_component c, const volume &where, h5file *file, field_rfunction fun = derived_component_func(c, gv, nfields, cs); output_hdf5(component_name(c), nfields, cs, fun, &nfields, where, file, append_data, - single_precision, prefix); + single_precision, prefix, omega); } /***************************************************************************/ diff --git a/src/meep.hpp b/src/meep.hpp index 91fd16a0c..8e4890f5a 100644 --- a/src/meep.hpp +++ b/src/meep.hpp @@ -58,6 +58,14 @@ const double nan = -7.0415659787563146e103; // ideally, a value never encountere class h5file; +// Defined in monitor.cpp +typedef struct { + double m00, m01, m02, m11, m12, m22; +} simple_symmetric_matrix; +void sym_matrix_invert(simple_symmetric_matrix *Vinv, const simple_symmetric_matrix *V); + +double pml_quadratic_profile(double, void *); + /* generic base class, only used by subclassing: represents susceptibility polarizability vector P = chi(omega) W (where W = E or H). */ class susceptibility { @@ -600,7 +608,7 @@ class structure_chunk { void remove_susceptibilities(); // monitor.cpp - double get_DC_chi1_at_pt(component c, direction d, int idx) const; + void get_chi1inv_tensor(simple_symmetric_matrix *chi1_inv_tensor, component c, direction d, int idx, double omega) const; double get_chi1inv_at_pt(component, direction, int idx, double omega = 0) const; double get_chi1inv(component, direction, const ivec &iloc, double omega = 0) const; double get_inveps(component c, direction d, const ivec &iloc, double omega = 0) const { @@ -614,8 +622,6 @@ class structure_chunk { int the_is_mine; }; -double pml_quadratic_profile(double, void *); - // linked list of descriptors for boundary regions (currently just for PML) class boundary_region { public: @@ -1527,23 +1533,23 @@ class fields { // low-level function: void output_hdf5(h5file *file, const char *dataname, int num_fields, const component *components, field_function fun, void *fun_data_, int reim, const volume &where, - bool append_data = false, bool single_precision = false); + bool append_data = false, bool single_precision = false, double omega = 0); // higher-level functions void output_hdf5(const char *dataname, // OUTPUT COMPLEX-VALUED FUNCTION int num_fields, const component *components, field_function fun, void *fun_data_, const volume &where, h5file *file = 0, bool append_data = false, bool single_precision = false, const char *prefix = 0, - bool real_part_only = false); + bool real_part_only = false, double omega = 0); void output_hdf5(const char *dataname, // OUTPUT REAL-VALUED FUNCTION int num_fields, const component *components, field_rfunction fun, void *fun_data_, const volume &where, h5file *file = 0, bool append_data = false, - bool single_precision = false, const char *prefix = 0); + bool single_precision = false, const char *prefix = 0, double = 0); void output_hdf5(component c, // OUTPUT FIELD COMPONENT (or Dielectric) const volume &where, h5file *file = 0, bool append_data = false, - bool single_precision = false, const char *prefix = 0); + bool single_precision = false, const char *prefix = 0, double omega = 0); void output_hdf5(derived_component c, // OUTPUT DERIVED FIELD COMPONENT const volume &where, h5file *file = 0, bool append_data = false, - bool single_precision = false, const char *prefix = 0); + bool single_precision = false, const char *prefix = 0, double omega = 0); h5file *open_h5file(const char *name, h5file::access_mode mode = h5file::WRITE, const char *prefix = NULL, bool timestamp = false); const char *h5file_name(const char *name, const char *prefix = NULL, bool timestamp = false); diff --git a/src/monitor.cpp b/src/monitor.cpp index 539c49e4a..12d4e8669 100644 --- a/src/monitor.cpp +++ b/src/monitor.cpp @@ -225,75 +225,126 @@ double structure::get_chi1inv(component c, direction d, const ivec &origloc, dou return 0.0; } -// This pulls the dc chi1 value from the chi1inv tensor by inverting it. -// So far only works for cartesian coordinates... -double structure_chunk::get_DC_chi1_at_pt(component c, direction d, int idx) const { +/* Set Vinv = inverse of V, where both V and Vinv are real-symmetric matrices.*/ +void sym_matrix_invert(simple_symmetric_matrix *Vinv, const simple_symmetric_matrix *V) { + double m00 = V->m00, m11 = V->m11, m22 = V->m22; + double m01 = V->m01, m02 = V->m02, m12 = V->m12; + + if (m01 == 0.0 && m02 == 0.0 && m12 == 0.0) { + /* optimize common case of a diagonal matrix: */ + Vinv->m00 = 1.0 / m00; + Vinv->m11 = 1.0 / m11; + Vinv->m22 = 1.0 / m22; + Vinv->m01 = Vinv->m02 = Vinv->m12 = 0.0; + } else { + double detinv; + + /* compute the determinant: */ + detinv = m00 * m11 * m22 - m02 * m11 * m02 + 2.0 * m01 * m12 * m02 - m01 * m01 * m22 - + m12 * m12 * m00; + + if (detinv == 0.0) meep::abort("singular 3x3 matrix"); + + detinv = 1.0 / detinv; + + Vinv->m00 = detinv * (m11 * m22 - m12 * m12); + Vinv->m11 = detinv * (m00 * m22 - m02 * m02); + Vinv->m22 = detinv * (m11 * m00 - m01 * m01); + + Vinv->m02 = detinv * (m01 * m12 - m11 * m02); + Vinv->m01 = detinv * (m12 * m02 - m01 * m22); + Vinv->m12 = detinv * (m01 * m02 - m00 * m12); + } +} + +void structure_chunk::get_chi1inv_tensor(simple_symmetric_matrix *chi1_inv_tensor, component c, direction d, int idx, double omega) const { + // ----------------------------------------------------------------- // + // ---- Step 1: Get instantaneous chi1 tensor ---------------------- + // ----------------------------------------------------------------- // + int my_stuff = E_stuff; component comp_list[3]; if (is_electric(c)) { comp_list[0] = Ex; comp_list[1] = Ey; comp_list[2] = Ez; + my_stuff = E_stuff; }else if (is_magnetic(c)) { comp_list[0] = Hx; comp_list[1] = Hy; comp_list[2] = Hz; + my_stuff = H_stuff; } else if (is_D(c)) { comp_list[0] = Dx; comp_list[1] = Dy; comp_list[2] = Dz; + my_stuff = D_stuff; } else if (is_B(c)) { comp_list[0] = Bx; comp_list[1] = By; comp_list[2] = Bz; + my_stuff = B_stuff; } - + simple_symmetric_matrix chi1_tensor; // Set up chi1inv 3x3 symmetric matrix - double m00 = chi1inv[comp_list[0]][X] ? chi1inv[comp_list[0]][X][idx] : 1.0; - double m11 = chi1inv[comp_list[1]][Y] ? chi1inv[comp_list[1]][Y][idx] : 1.0; - double m22 = chi1inv[comp_list[2]][Z] ? chi1inv[comp_list[2]][Z][idx] : 1.0; - double m01 = chi1inv[comp_list[0]][Y] ? chi1inv[comp_list[0]][Y][idx] : 0; - double m02 = chi1inv[comp_list[0]][Z] ? chi1inv[comp_list[0]][Z][idx] : 0; - double m12 = chi1inv[comp_list[1]][Z] ? chi1inv[comp_list[1]][Z][idx] : 0; - - // Calculate determinant - double det = m00 * (m11*m22-m12*m12) - m01 * (m01*m22-m12*m02) + m02 * (m01*m12-m11*m02); - if (det==0) abort("Chi1 Inverse matrix is singular.\n"); - - // Set up chi1 3x3 symmetric matrix, as the inverse - double A[3][3]; - A[0][0] = (m11*m22 - m12*m12)/det; - A[1][1] = (m00*m22 - m02*m02)/det; - A[2][2] = (m00*m11 - m12*m12)/det; - A[0][1] = A[1][0] = (m02*m12 - m22*m01)/det; - A[0][2] = A[2][0] = (m01*m12 - m02*m11)/det; - A[1][2] = A[2][1] = (m01*m02 - m00*m12)/det; - - // Return the component we care about - return A[component_index(c)][d]; -} - -double structure_chunk::get_chi1inv_at_pt(component c, direction d, int idx, double omega) const { - double res = 0.0; - if (is_mine()){ - // Get instantaneous chi1 value (i.e DC value) - res = get_DC_chi1_at_pt(c,d,idx); - std::complex eps(res,0.0); + chi1_inv_tensor->m00 = chi1inv[comp_list[0]][X] ? chi1inv[comp_list[0]][X][idx] : 1.0; + chi1_inv_tensor->m11 = chi1inv[comp_list[1]][Y] ? chi1inv[comp_list[1]][Y][idx] : 1.0; + chi1_inv_tensor->m22 = chi1inv[comp_list[2]][Z] ? chi1inv[comp_list[2]][Z][idx] : 1.0; + chi1_inv_tensor->m01 = chi1inv[comp_list[0]][Y] ? chi1inv[comp_list[0]][Y][idx] : 0; + chi1_inv_tensor->m02 = chi1inv[comp_list[0]][Z] ? chi1inv[comp_list[0]][Z][idx] : 0; + chi1_inv_tensor->m12 = chi1inv[comp_list[1]][Z] ? chi1inv[comp_list[1]][Z][idx] : 0; + + sym_matrix_invert(&chi1_tensor, chi1_inv_tensor); // We have the inverse, so let's invert it. + + // ----------------------------------------------------------------- // + // ---- Step 2: Evaluate susceptibilities of each tensor element --- + // ----------------------------------------------------------------- // + // loop over unique tensor elements + double *chi1_elements[6] = {&chi1_tensor.m00,&chi1_tensor.m11,&chi1_tensor.m22,&chi1_tensor.m01,&chi1_tensor.m02,&chi1_tensor.m12}; + component element_com[6] = {comp_list[0],comp_list[1],comp_list[2],comp_list[0],comp_list[0],comp_list[1]}; + direction element_dir[6] = {X,Y,Z,Y,Z,Z}; + for (int el_iter=0; el_iter<6; el_iter++){ + std::complex eps(*chi1_elements[el_iter],0.0); + component cc = element_com[el_iter]; + direction dd = element_dir[el_iter]; // Loop through and add up susceptibility contributions // locate correct susceptibility list - susceptibility *Esus = chiP[E_stuff]; - while (Esus) { - if (Esus->sigma[c][d]) { - double sigma = Esus->sigma[c][d][idx]; - eps += Esus->chi1(omega,sigma); + susceptibility *my_sus = chiP[my_stuff]; + while (my_sus) { + if (my_sus->sigma[cc][dd]) { + double sigma = my_sus->sigma[cc][dd][idx]; + eps += my_sus->chi1(omega,sigma); } - Esus = Esus->next; + my_sus = my_sus->next; } + // Account for conductivity term - if (conductivity[c][d]) { - double conductivityCur = conductivity[c][d][idx]; - eps = std::complex(1.0, (conductivityCur/omega)) * eps; - } - // Return chi1 inverse, take the real part since no support for loss in mpb yet - // TODO: Add support for metals - if (eps.real() == 0.0 && eps.imag() == 0.0){ - res = 0.0; - }else if(eps.imag() == 0.0){ - res = 1.0 / eps.real(); - }else{ - res = 1.0 / (std::sqrt(eps).real() * std::sqrt(eps).real()); + if (conductivity[cc][dd]) { + double conductivityCur = conductivity[cc][dd][idx]; + eps = std::complex(1.0, (conductivityCur/omega)) * eps; } + + // assign to eps tensor + if (eps.imag() == 0 ) + *chi1_elements[el_iter] = eps.real(); + else + *chi1_elements[el_iter] = std::sqrt(eps).real() * std::sqrt(eps).real(); // hack for metals + } + + // ----------------------------------------------------------------- // + // ---- Step 3: Invert chi1 matrix to get chi1inv matrix ----------- + // ----------------------------------------------------------------- // + sym_matrix_invert(chi1_inv_tensor, &chi1_tensor); // We have the inverse, so let's invert it. +} + +double structure_chunk::get_chi1inv_at_pt(component c, direction d, int idx, double omega) const { + double res = 0.0; + if (is_mine()){ + simple_symmetric_matrix chi1_inv_tensor; + get_chi1inv_tensor(&chi1_inv_tensor, c, d, idx, omega); + + // Set up chi1 3x3 symmetric matrix, as the inverse + double A[3][3]; + A[0][0] = chi1_inv_tensor.m00; + A[1][1] = chi1_inv_tensor.m11; + A[2][2] = chi1_inv_tensor.m22; + A[0][1] = A[1][0] = chi1_inv_tensor.m01; + A[0][2] = A[2][0] = chi1_inv_tensor.m02; + A[1][2] = A[2][1] = chi1_inv_tensor.m12; + + // Return the component we care about + res = A[component_index(c)][d]; } //master_printf("res: %g\n",res); return res; diff --git a/src/mpb.cpp b/src/mpb.cpp index ef2868f7b..ee6ff9f53 100644 --- a/src/mpb.cpp +++ b/src/mpb.cpp @@ -60,7 +60,14 @@ static void meep_mpb_eps(symmetric_matrix *eps, symmetric_matrix *eps_inv, const ASSIGN_ESCALAR(eps_inv->m01, f->get_chi1inv(Ex, Y, p, omega), 0); ASSIGN_ESCALAR(eps_inv->m02, f->get_chi1inv(Ex, Z, p, omega), 0); ASSIGN_ESCALAR(eps_inv->m12, f->get_chi1inv(Ey, Z, p, omega), 0); - //master_printf("eps_zz(%g,%g) = %g\n", p.x(), p.y(), eps_inv->m01); + /* + master_printf("m11(%g,%g) = %g\n", p.x(), p.y(), eps_inv->m00); + master_printf("m22(%g,%g) = %g\n", p.x(), p.y(), eps_inv->m11); + master_printf("m33(%g,%g) = %g\n", p.x(), p.y(), eps_inv->m22); + master_printf("m12(%g,%g) = %g\n", p.x(), p.y(), eps_inv->m01); + master_printf("m13(%g,%g) = %g\n", p.x(), p.y(), eps_inv->m02); + master_printf("m23(%g,%g) = %g\n", p.x(), p.y(), eps_inv->m12); + */ maxwell_sym_matrix_invert(eps, eps_inv); } From c4bc10be8cdb15f7a4630041b20c82af95709245 Mon Sep 17 00:00:00 2001 From: smartalecH Date: Fri, 21 Jun 2019 16:34:58 -0600 Subject: [PATCH 12/25] generalize matrix inverse and fix parallel bug --- src/meep.hpp | 7 +-- src/monitor.cpp | 152 ++++++++++++++++++++++-------------------------- 2 files changed, 70 insertions(+), 89 deletions(-) diff --git a/src/meep.hpp b/src/meep.hpp index 8e4890f5a..6721fac73 100644 --- a/src/meep.hpp +++ b/src/meep.hpp @@ -59,10 +59,7 @@ const double nan = -7.0415659787563146e103; // ideally, a value never encountere class h5file; // Defined in monitor.cpp -typedef struct { - double m00, m01, m02, m11, m12, m22; -} simple_symmetric_matrix; -void sym_matrix_invert(simple_symmetric_matrix *Vinv, const simple_symmetric_matrix *V); +void matrix_invert(double *Vinv, double *V); double pml_quadratic_profile(double, void *); @@ -608,7 +605,7 @@ class structure_chunk { void remove_susceptibilities(); // monitor.cpp - void get_chi1inv_tensor(simple_symmetric_matrix *chi1_inv_tensor, component c, direction d, int idx, double omega) const; + void get_chi1inv_tensor(double *chi1_inv_tensor, component c, direction d, int idx, double omega) const; double get_chi1inv_at_pt(component, direction, int idx, double omega = 0) const; double get_chi1inv(component, direction, const ivec &iloc, double omega = 0) const; double get_inveps(component c, direction d, const ivec &iloc, double omega = 0) const { diff --git a/src/monitor.cpp b/src/monitor.cpp index 12d4e8669..bbae21b39 100644 --- a/src/monitor.cpp +++ b/src/monitor.cpp @@ -226,38 +226,24 @@ double structure::get_chi1inv(component c, direction d, const ivec &origloc, dou } /* Set Vinv = inverse of V, where both V and Vinv are real-symmetric matrices.*/ -void sym_matrix_invert(simple_symmetric_matrix *Vinv, const simple_symmetric_matrix *V) { - double m00 = V->m00, m11 = V->m11, m22 = V->m22; - double m01 = V->m01, m02 = V->m02, m12 = V->m12; - - if (m01 == 0.0 && m02 == 0.0 && m12 == 0.0) { - /* optimize common case of a diagonal matrix: */ - Vinv->m00 = 1.0 / m00; - Vinv->m11 = 1.0 / m11; - Vinv->m22 = 1.0 / m22; - Vinv->m01 = Vinv->m02 = Vinv->m12 = 0.0; - } else { - double detinv; - - /* compute the determinant: */ - detinv = m00 * m11 * m22 - m02 * m11 * m02 + 2.0 * m01 * m12 * m02 - m01 * m01 * m22 - - m12 * m12 * m00; - - if (detinv == 0.0) meep::abort("singular 3x3 matrix"); - - detinv = 1.0 / detinv; - - Vinv->m00 = detinv * (m11 * m22 - m12 * m12); - Vinv->m11 = detinv * (m00 * m22 - m02 * m02); - Vinv->m22 = detinv * (m11 * m00 - m01 * m01); - - Vinv->m02 = detinv * (m01 * m12 - m11 * m02); - Vinv->m01 = detinv * (m12 * m02 - m01 * m22); - Vinv->m12 = detinv * (m01 * m02 - m00 * m12); - } +void matrix_invert(double *Vinv, double *V) { + + double det = (V[0 +3*0] * (V[1 + 3*1]*V[2 +3*2] - V[1 + 3*2]*V[2 +3*1]) - + V[0 + 3*1] * (V[0 + 3*1]*V[2 + 3*2] - V[1 + 3*2]*V[0 + 3*2]) + + V[0 + 3*2] * (V[0 + 3*1]*V[1 + 3*2] - V[1 + 3*1]*V[0 + 3*2])); + + Vinv[0 + 3*0] = 1/det * (V[1 + 3*1]*V[2 + 3*2] - V[1 + 3*2]*V[2 + 3*1]); + Vinv[0 + 3*1] = 1/det * (V[0 + 3*2]*V[2 + 3*1] - V[0 + 3*1]*V[2 + 3*2]); + Vinv[0 + 3*2] = 1/det * (V[0 + 3*1]*V[1 + 3*2] - V[0 + 3*2]*V[1 + 3*1]); + Vinv[1 + 3*0] = 1/det * (V[1 + 3*2]*V[2 + 3*0] - V[1 + 3*0]*V[2 + 3*2]); + Vinv[1 + 3*1] = 1/det * (V[0 + 3*0]*V[2 + 3*2] - V[0 + 3*2]*V[2 + 3*0]); + Vinv[1 + 3*2] = 1/det * (V[0 + 3*2]*V[1 + 3*0] - V[0 + 3*0]*V[1 + 3*2]); + Vinv[2 + 3*0] = 1/det * (V[1 + 3*0]*V[2 + 3*1] - V[1 + 3*1]*V[2 + 3*0]); + Vinv[2 + 3*1] = 1/det * (V[0 + 3*1]*V[2 + 3*0] - V[0 + 3*0]*V[2 + 3*1]); + Vinv[2 + 3*2] = 1/det * (V[0 + 3*0]*V[1 + 3*1] - V[0 + 3*1]*V[1 + 3*0]); } -void structure_chunk::get_chi1inv_tensor(simple_symmetric_matrix *chi1_inv_tensor, component c, direction d, int idx, double omega) const { +void structure_chunk::get_chi1inv_tensor(double *chi1_inv_tensor, component c, direction d, int idx, double omega) const { // ----------------------------------------------------------------- // // ---- Step 1: Get instantaneous chi1 tensor ---------------------- // ----------------------------------------------------------------- // @@ -276,77 +262,75 @@ void structure_chunk::get_chi1inv_tensor(simple_symmetric_matrix *chi1_inv_tenso comp_list[0] = Bx; comp_list[1] = By; comp_list[2] = Bz; my_stuff = B_stuff; } - simple_symmetric_matrix chi1_tensor; - // Set up chi1inv 3x3 symmetric matrix - chi1_inv_tensor->m00 = chi1inv[comp_list[0]][X] ? chi1inv[comp_list[0]][X][idx] : 1.0; - chi1_inv_tensor->m11 = chi1inv[comp_list[1]][Y] ? chi1inv[comp_list[1]][Y][idx] : 1.0; - chi1_inv_tensor->m22 = chi1inv[comp_list[2]][Z] ? chi1inv[comp_list[2]][Z][idx] : 1.0; - chi1_inv_tensor->m01 = chi1inv[comp_list[0]][Y] ? chi1inv[comp_list[0]][Y][idx] : 0; - chi1_inv_tensor->m02 = chi1inv[comp_list[0]][Z] ? chi1inv[comp_list[0]][Z][idx] : 0; - chi1_inv_tensor->m12 = chi1inv[comp_list[1]][Z] ? chi1inv[comp_list[1]][Z][idx] : 0; - - sym_matrix_invert(&chi1_tensor, chi1_inv_tensor); // We have the inverse, so let's invert it. + + double * chi1_tensor = new double[9]; + + // Set up the chi1inv tensor + for (int com_it=0; com_it<3;com_it++){ + for (int dir_int=0;dir_int<3;dir_int++){ + if (chi1inv[comp_list[com_it]][dir_int] ) + chi1_inv_tensor[com_it + 3*dir_int] = chi1inv[comp_list[0]][dir_int][idx]; + else if(dir_int == component_direction(comp_list[com_it])) + chi1_inv_tensor[com_it + 3*dir_int] = 1; + else + chi1_inv_tensor[com_it + 3*dir_int] = 0; + } + } + + matrix_invert(chi1_tensor, chi1_inv_tensor); // We have the inverse, so let's invert it. // ----------------------------------------------------------------- // // ---- Step 2: Evaluate susceptibilities of each tensor element --- // ----------------------------------------------------------------- // - // loop over unique tensor elements - double *chi1_elements[6] = {&chi1_tensor.m00,&chi1_tensor.m11,&chi1_tensor.m22,&chi1_tensor.m01,&chi1_tensor.m02,&chi1_tensor.m12}; - component element_com[6] = {comp_list[0],comp_list[1],comp_list[2],comp_list[0],comp_list[0],comp_list[1]}; - direction element_dir[6] = {X,Y,Z,Y,Z,Z}; - for (int el_iter=0; el_iter<6; el_iter++){ - std::complex eps(*chi1_elements[el_iter],0.0); - component cc = element_com[el_iter]; - direction dd = element_dir[el_iter]; - // Loop through and add up susceptibility contributions - // locate correct susceptibility list - susceptibility *my_sus = chiP[my_stuff]; - while (my_sus) { - if (my_sus->sigma[cc][dd]) { - double sigma = my_sus->sigma[cc][dd][idx]; - eps += my_sus->chi1(omega,sigma); - } - my_sus = my_sus->next; - } + // loop over tensor elements + for (int com_it=0; com_it<3;com_it++){ + for (int dir_int=0;dir_int<3;dir_int++){ + std::complex eps(chi1_tensor[com_it + 3*dir_int],0.0); + component cc = comp_list[com_it]; + direction dd = (direction)dir_int; + // Loop through and add up susceptibility contributions + // locate correct susceptibility list + susceptibility *my_sus = chiP[my_stuff]; + while (my_sus) { + if (my_sus->sigma[cc][dd]) { + double sigma = my_sus->sigma[cc][dd][idx]; + eps += my_sus->chi1(omega,sigma); + } + my_sus = my_sus->next; + } - // Account for conductivity term - if (conductivity[cc][dd]) { - double conductivityCur = conductivity[cc][dd][idx]; - eps = std::complex(1.0, (conductivityCur/omega)) * eps; - } + // Account for conductivity term + if (conductivity[cc][dd]) { + double conductivityCur = conductivity[cc][dd][idx]; + eps = std::complex(1.0, (conductivityCur/omega)) * eps; + } - // assign to eps tensor - if (eps.imag() == 0 ) - *chi1_elements[el_iter] = eps.real(); - else - *chi1_elements[el_iter] = std::sqrt(eps).real() * std::sqrt(eps).real(); // hack for metals + // assign to eps tensor + if (eps.imag() == 0 ) + chi1_tensor[com_it + 3*dir_int] = eps.real(); + else + chi1_tensor[com_it + 3*dir_int] = std::sqrt(eps).real() * std::sqrt(eps).real(); // hack for metals + } } // ----------------------------------------------------------------- // // ---- Step 3: Invert chi1 matrix to get chi1inv matrix ----------- // ----------------------------------------------------------------- // - sym_matrix_invert(chi1_inv_tensor, &chi1_tensor); // We have the inverse, so let's invert it. + matrix_invert(chi1_inv_tensor, chi1_tensor); // We have the inverse, so let's invert it. + + delete[] chi1_tensor; } double structure_chunk::get_chi1inv_at_pt(component c, direction d, int idx, double omega) const { double res = 0.0; if (is_mine()){ - simple_symmetric_matrix chi1_inv_tensor; - get_chi1inv_tensor(&chi1_inv_tensor, c, d, idx, omega); - - // Set up chi1 3x3 symmetric matrix, as the inverse - double A[3][3]; - A[0][0] = chi1_inv_tensor.m00; - A[1][1] = chi1_inv_tensor.m11; - A[2][2] = chi1_inv_tensor.m22; - A[0][1] = A[1][0] = chi1_inv_tensor.m01; - A[0][2] = A[2][0] = chi1_inv_tensor.m02; - A[1][2] = A[2][1] = chi1_inv_tensor.m12; - - // Return the component we care about - res = A[component_index(c)][d]; + double * chi1_inv_tensor = new double[9]; + get_chi1inv_tensor(chi1_inv_tensor, c, d, idx, omega); + + // Return the component we care about + res = chi1_inv_tensor[component_index(c) + 3*d]; + delete[] chi1_inv_tensor; } - //master_printf("res: %g\n",res); return res; } From 97a7b0ac6c778c004df14f26a0e3caea79a50074 Mon Sep 17 00:00:00 2001 From: smartalecH Date: Mon, 24 Jun 2019 10:25:33 -0600 Subject: [PATCH 13/25] fix memory issue --- python/tests/dispersive_eigenmode.py | 2 +- src/meep.hpp | 4 ++-- src/monitor.cpp | 21 ++++++++++----------- src/mpb.cpp | 1 + 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/python/tests/dispersive_eigenmode.py b/python/tests/dispersive_eigenmode.py index 2c1bd4786..237dfcce1 100644 --- a/python/tests/dispersive_eigenmode.py +++ b/python/tests/dispersive_eigenmode.py @@ -42,7 +42,7 @@ def simulate_meep(self,material,omega): sim = mp.Simulation(cell_size=mp.Vector3(1,1,1), default_material=material, - resolution=1 + resolution=10 ) band_num = 1 diff --git a/src/meep.hpp b/src/meep.hpp index 6721fac73..64a3ed394 100644 --- a/src/meep.hpp +++ b/src/meep.hpp @@ -59,7 +59,7 @@ const double nan = -7.0415659787563146e103; // ideally, a value never encountere class h5file; // Defined in monitor.cpp -void matrix_invert(double *Vinv, double *V); +void matrix_invert(std::vector &Vinv, std::vector &V); double pml_quadratic_profile(double, void *); @@ -605,7 +605,7 @@ class structure_chunk { void remove_susceptibilities(); // monitor.cpp - void get_chi1inv_tensor(double *chi1_inv_tensor, component c, direction d, int idx, double omega) const; + double get_chi1inv_tensor(std::vector &chi1_inv_tensor, component c, direction d, int idx, double omega) const; double get_chi1inv_at_pt(component, direction, int idx, double omega = 0) const; double get_chi1inv(component, direction, const ivec &iloc, double omega = 0) const; double get_inveps(component c, direction d, const ivec &iloc, double omega = 0) const { diff --git a/src/monitor.cpp b/src/monitor.cpp index bbae21b39..47a6602f8 100644 --- a/src/monitor.cpp +++ b/src/monitor.cpp @@ -226,12 +226,14 @@ double structure::get_chi1inv(component c, direction d, const ivec &origloc, dou } /* Set Vinv = inverse of V, where both V and Vinv are real-symmetric matrices.*/ -void matrix_invert(double *Vinv, double *V) { +void matrix_invert(std::vector &Vinv, std::vector &V) { double det = (V[0 +3*0] * (V[1 + 3*1]*V[2 +3*2] - V[1 + 3*2]*V[2 +3*1]) - V[0 + 3*1] * (V[0 + 3*1]*V[2 + 3*2] - V[1 + 3*2]*V[0 + 3*2]) + V[0 + 3*2] * (V[0 + 3*1]*V[1 + 3*2] - V[1 + 3*1]*V[0 + 3*2])); + if (det == 0) abort("meep: Matrix is singular, aborting.\n"); + Vinv[0 + 3*0] = 1/det * (V[1 + 3*1]*V[2 + 3*2] - V[1 + 3*2]*V[2 + 3*1]); Vinv[0 + 3*1] = 1/det * (V[0 + 3*2]*V[2 + 3*1] - V[0 + 3*1]*V[2 + 3*2]); Vinv[0 + 3*2] = 1/det * (V[0 + 3*1]*V[1 + 3*2] - V[0 + 3*2]*V[1 + 3*1]); @@ -243,7 +245,7 @@ void matrix_invert(double *Vinv, double *V) { Vinv[2 + 3*2] = 1/det * (V[0 + 3*0]*V[1 + 3*1] - V[0 + 3*1]*V[1 + 3*0]); } -void structure_chunk::get_chi1inv_tensor(double *chi1_inv_tensor, component c, direction d, int idx, double omega) const { +double structure_chunk::get_chi1inv_tensor(std::vector &chi1_inv_tensor, component c, direction d, int idx, double omega) const { // ----------------------------------------------------------------- // // ---- Step 1: Get instantaneous chi1 tensor ---------------------- // ----------------------------------------------------------------- // @@ -263,13 +265,13 @@ void structure_chunk::get_chi1inv_tensor(double *chi1_inv_tensor, component c, d my_stuff = B_stuff; } - double * chi1_tensor = new double[9]; + std::vector chi1_tensor(9,0); // Set up the chi1inv tensor for (int com_it=0; com_it<3;com_it++){ for (int dir_int=0;dir_int<3;dir_int++){ if (chi1inv[comp_list[com_it]][dir_int] ) - chi1_inv_tensor[com_it + 3*dir_int] = chi1inv[comp_list[0]][dir_int][idx]; + chi1_inv_tensor[com_it + 3*dir_int] = chi1inv[comp_list[com_it]][dir_int][idx]; else if(dir_int == component_direction(comp_list[com_it])) chi1_inv_tensor[com_it + 3*dir_int] = 1; else @@ -277,6 +279,7 @@ void structure_chunk::get_chi1inv_tensor(double *chi1_inv_tensor, component c, d } } + matrix_invert(chi1_tensor, chi1_inv_tensor); // We have the inverse, so let's invert it. // ----------------------------------------------------------------- // @@ -318,18 +321,14 @@ void structure_chunk::get_chi1inv_tensor(double *chi1_inv_tensor, component c, d // ----------------------------------------------------------------- // matrix_invert(chi1_inv_tensor, chi1_tensor); // We have the inverse, so let's invert it. - delete[] chi1_tensor; + return chi1_inv_tensor[component_index(c) + 3*d]; } double structure_chunk::get_chi1inv_at_pt(component c, direction d, int idx, double omega) const { double res = 0.0; if (is_mine()){ - double * chi1_inv_tensor = new double[9]; - get_chi1inv_tensor(chi1_inv_tensor, c, d, idx, omega); - - // Return the component we care about - res = chi1_inv_tensor[component_index(c) + 3*d]; - delete[] chi1_inv_tensor; + std::vector chi1_inv_tensor(9,0); + res = get_chi1inv_tensor(chi1_inv_tensor, c, d, idx, omega); } return res; } diff --git a/src/mpb.cpp b/src/mpb.cpp index ee6ff9f53..3e7ba92bc 100644 --- a/src/mpb.cpp +++ b/src/mpb.cpp @@ -53,6 +53,7 @@ static void meep_mpb_eps(symmetric_matrix *eps, symmetric_matrix *eps_inv, const : (eps_data->dim == D2 ? vec(o[0] + r[0] * s[0], o[1] + r[1] * s[1]) : /* D1 */ vec(o[2] + r[2] * s[2]))); const fields *f = eps_data->f; + eps_inv->m00 = f->get_chi1inv(Ex, X, p, omega); eps_inv->m11 = f->get_chi1inv(Ey, Y, p, omega); eps_inv->m22 = f->get_chi1inv(Ez, Z, p, omega); From 8a1e6353538a1d775d9de14990a17d620798cbf9 Mon Sep 17 00:00:00 2001 From: smartalecH Date: Mon, 24 Jun 2019 14:09:08 -0600 Subject: [PATCH 14/25] add tutorial --- python/examples/binary_grating.ipynb | 681 +++++++++++++++++---------- 1 file changed, 433 insertions(+), 248 deletions(-) diff --git a/python/examples/binary_grating.ipynb b/python/examples/binary_grating.ipynb index f346254e1..0354d97a0 100644 --- a/python/examples/binary_grating.ipynb +++ b/python/examples/binary_grating.ipynb @@ -1,5 +1,41 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Diffraction Spectrum of a Binary Grating" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The mode-decomposition feature can also be applied to planewaves in homogeneous media with scalar permittivity/permeability (i.e., no anisotropy). This will be demonstrated in this example to compute the diffraction spectrum of a binary phase grating. To compute the diffraction spectrum for a finite-length structure, see Tutorials/Near to Far Field Spectra/Diffraction Spectrum of a Finite Binary Grating. \n", + "\n", + "The unit cell geometry of the grating is shown in the schematic below. The grating is periodic in the `y` direction with periodicity `gp` and has a rectangular profile of height `gh` and duty cycle `gdc`. The grating parameters are `gh=0.5` μm, `gdc=0.5`, and `gp=10 μm`. There is a semi-infinite substrate of thickness `dsub` adjacent to the grating. The substrate and grating are glass with a refractive index of 1.5. The surrounding is air/vacuum. Perfectly matched layers (PML) of thickness `dpml` are used in the $\\pm x$ boundaries." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![geometry](https://meep.readthedocs.io/en/latest/images/grating.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Transmittance Spectra for Planewave at Normal Incidence\n", + "\n", + "A pulsed planewave with $E_z$ polarization spanning wavelengths of 0.4 to 0.6 μm is normally incident on the grating from the glass substrate. The eigenmode monitor is placed in the air region. We will use mode decomposition to compute the transmittance — the ratio of the power in the +x direction of the diffracted mode relative to that of the incident planewave — for the first ten diffraction orders. \n", + "\n", + "Two simulations are required: (1) an empty cell of homogeneous glass to obtain the incident power of the source, and (2) the grating structure to obtain the diffraction orders. At the end of the simulation, the wavelength, angle, and transmittance for each diffraction order are computed.\n", + "\n", + "First, we'll import our standard libraries, along with the `fused_quartz` material from MEEP's material library." + ] + }, { "cell_type": "code", "execution_count": 1, @@ -9,240 +45,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "-----------\n", - "Initializing structure...\n", - "field decay(t = 50.01): 0.10609306658233111 / 0.10609306658233111 = 1.0\n", - "field decay(t = 100.01): 8.493197174525773e-20 / 0.10609306658233111 = 8.005421511626135e-19\n", - "run 0 finished at t = 100.01 (10001 timesteps)\n", - "-----------\n", - "Initializing structure...\n", - "field decay(t = 50.01): 0.10313983544158939 / 0.10313983544158939 = 1.0\n", - "field decay(t = 100.01): 8.275841626039517e-06 / 0.10313983544158939 = 8.023904237006785e-05\n", - "field decay(t = 150.02): 7.578862246277832e-06 / 0.10313983544158939 = 7.348142658778942e-05\n", - "field decay(t = 200.03): 2.6331983132012556e-06 / 0.10313983544158939 = 2.55303714799167e-05\n", - "field decay(t = 250.04): 1.0595609940381617e-06 / 0.10313983544158939 = 1.0273052981921975e-05\n", - "field decay(t = 300.04): 4.182093600426132e-07 / 0.10313983544158939 = 4.054780175400371e-06\n", - "field decay(t = 350.05): 1.7897453529965382e-07 / 0.10313983544158939 = 1.7352610127152227e-06\n", - "field decay(t = 400.06): 7.323581231103283e-08 / 0.10313983544158939 = 7.100633038386808e-07\n", - "field decay(t = 450.07): 2.9341078575718368e-08 / 0.10313983544158939 = 2.8447862506368783e-07\n", - "field decay(t = 500.08): 1.184153513314788e-08 / 0.10313983544158939 = 1.1481049084913492e-07\n", - "field decay(t = 550.08): 4.99840667036108e-09 / 0.10313983544158939 = 4.846242626780028e-08\n", - "field decay(t = 600.09): 2.3507305572060455e-09 / 0.10313983544158939 = 2.2791684194002052e-08\n", - "field decay(t = 650.1): 1.1816026525465915e-09 / 0.10313983544158939 = 1.1456317023268492e-08\n", - "field decay(t = 700.11): 3.9576427414058525e-10 / 0.10313983544158939 = 3.837162163834518e-09\n", - "field decay(t = 750.12): 1.4213834548368875e-10 / 0.10313983544158939 = 1.378112975215916e-09\n", - "field decay(t = 800.13): 8.16132061585537e-11 / 0.10313983544158939 = 7.912869533786803e-10\n", - "run 0 finished at t = 800.13 (80013 timesteps)\n", - "grating0:, 0.60000, 0.00, 0.06566064\n", - "grating0:, 0.58537, 0.00, 0.05057571\n", - "grating0:, 0.57143, 0.00, 0.03752612\n", - "grating0:, 0.55814, 0.00, 0.02620881\n", - "grating0:, 0.54545, 0.00, 0.01693912\n", - "grating0:, 0.53333, 0.00, 0.00973341\n", - "grating0:, 0.52174, 0.00, 0.00458582\n", - "grating0:, 0.51064, 0.00, 0.00152102\n", - "grating0:, 0.50000, 0.00, 0.00059451\n", - "grating0:, 0.48980, 0.00, 0.00181994\n", - "grating0:, 0.48000, 0.00, 0.00521963\n", - "grating0:, 0.47059, 0.00, 0.01065881\n", - "grating0:, 0.46154, 0.00, 0.01826748\n", - "grating0:, 0.45283, 0.00, 0.02799911\n", - "grating0:, 0.44444, 0.00, 0.03976392\n", - "grating0:, 0.43636, 0.00, 0.05353118\n", - "grating0:, 0.42857, 0.00, 0.06923018\n", - "grating0:, 0.42105, 0.00, 0.08678683\n", - "grating0:, 0.41379, 0.00, 0.10606991\n", - "grating0:, 0.40678, 0.00, 0.12727449\n", - "grating0:, 0.40000, 0.00, 0.15011932\n", - "grating1:, 0.60000, 3.44, 0.36441833\n", - "grating1:, 0.58537, 3.36, 0.37026888\n", - "grating1:, 0.57143, 3.28, 0.37531227\n", - "grating1:, 0.55814, 3.20, 0.37950073\n", - "grating1:, 0.54545, 3.13, 0.38292703\n", - "grating1:, 0.53333, 3.06, 0.38544932\n", - "grating1:, 0.52174, 2.99, 0.38707714\n", - "grating1:, 0.51064, 2.93, 0.38788219\n", - "grating1:, 0.50000, 2.87, 0.38779242\n", - "grating1:, 0.48980, 2.81, 0.38676394\n", - "grating1:, 0.48000, 2.75, 0.38487068\n", - "grating1:, 0.47059, 2.70, 0.38213290\n", - "grating1:, 0.46154, 2.65, 0.37846383\n", - "grating1:, 0.45283, 2.60, 0.37394214\n", - "grating1:, 0.44444, 2.55, 0.36858855\n", - "grating1:, 0.43636, 2.50, 0.36237877\n", - "grating1:, 0.42857, 2.46, 0.35537418\n", - "grating1:, 0.42105, 2.41, 0.34760885\n", - "grating1:, 0.41379, 2.37, 0.33907896\n", - "grating1:, 0.40678, 2.33, 0.32983916\n", - "grating1:, 0.40000, 2.29, 0.31995793\n", - "grating2:, 0.60000, 6.89, 0.00060264\n", - "grating2:, 0.58537, 6.72, 0.00061980\n", - "grating2:, 0.57143, 6.56, 0.00066675\n", - "grating2:, 0.55814, 6.41, 0.00070498\n", - "grating2:, 0.54545, 6.26, 0.00073362\n", - "grating2:, 0.53333, 6.12, 0.00077073\n", - "grating2:, 0.52174, 5.99, 0.00081400\n", - "grating2:, 0.51064, 5.86, 0.00084271\n", - "grating2:, 0.50000, 5.74, 0.00087413\n", - "grating2:, 0.48980, 5.62, 0.00092390\n", - "grating2:, 0.48000, 5.51, 0.00094605\n", - "grating2:, 0.47059, 5.40, 0.00097020\n", - "grating2:, 0.46154, 5.30, 0.00101890\n", - "grating2:, 0.45283, 5.20, 0.00104340\n", - "grating2:, 0.44444, 5.10, 0.00107227\n", - "grating2:, 0.43636, 5.01, 0.00109732\n", - "grating2:, 0.42857, 4.92, 0.00113048\n", - "grating2:, 0.42105, 4.83, 0.00115295\n", - "grating2:, 0.41379, 4.75, 0.00119091\n", - "grating2:, 0.40678, 4.67, 0.00121934\n", - "grating2:, 0.40000, 4.59, 0.00121934\n", - "grating3:, 0.60000, 10.37, 0.04032187\n", - "grating3:, 0.58537, 10.11, 0.04096864\n", - "grating3:, 0.57143, 9.87, 0.04150771\n", - "grating3:, 0.55814, 9.64, 0.04191451\n", - "grating3:, 0.54545, 9.42, 0.04230647\n", - "grating3:, 0.53333, 9.21, 0.04255648\n", - "grating3:, 0.52174, 9.01, 0.04268330\n", - "grating3:, 0.51064, 8.81, 0.04277436\n", - "grating3:, 0.50000, 8.63, 0.04276314\n", - "grating3:, 0.48980, 8.45, 0.04260564\n", - "grating3:, 0.48000, 8.28, 0.04237879\n", - "grating3:, 0.47059, 8.12, 0.04209800\n", - "grating3:, 0.46154, 7.96, 0.04166668\n", - "grating3:, 0.45283, 7.81, 0.04115689\n", - "grating3:, 0.44444, 7.66, 0.04057382\n", - "grating3:, 0.43636, 7.52, 0.03987212\n", - "grating3:, 0.42857, 7.39, 0.03909019\n", - "grating3:, 0.42105, 7.26, 0.03823992\n", - "grating3:, 0.41379, 7.13, 0.03728341\n", - "grating3:, 0.40678, 7.01, 0.03625078\n", - "grating3:, 0.40000, 6.89, 0.03516630\n", - "grating4:, 0.60000, 13.89, 0.00062308\n", - "grating4:, 0.58537, 13.54, 0.00063845\n", - "grating4:, 0.57143, 13.21, 0.00068368\n", - "grating4:, 0.55814, 12.90, 0.00072240\n", - "grating4:, 0.54545, 12.60, 0.00075041\n", - "grating4:, 0.53333, 12.32, 0.00078515\n", - "grating4:, 0.52174, 12.05, 0.00082905\n", - "grating4:, 0.51064, 11.79, 0.00085870\n", - "grating4:, 0.50000, 11.54, 0.00088811\n", - "grating4:, 0.48980, 11.30, 0.00093827\n", - "grating4:, 0.48000, 11.07, 0.00096058\n", - "grating4:, 0.47059, 10.85, 0.00098445\n", - "grating4:, 0.46154, 10.64, 0.00103279\n", - "grating4:, 0.45283, 10.44, 0.00105781\n", - "grating4:, 0.44444, 10.24, 0.00108599\n", - "grating4:, 0.43636, 10.05, 0.00111000\n", - "grating4:, 0.42857, 9.87, 0.00114322\n", - "grating4:, 0.42105, 9.70, 0.00116469\n", - "grating4:, 0.41379, 9.53, 0.00120199\n", - "grating4:, 0.40678, 9.36, 0.00123025\n", - "grating4:, 0.40000, 9.21, 0.00122872\n", - "grating5:, 0.60000, 17.46, 0.01434617\n", - "grating5:, 0.58537, 17.02, 0.01458357\n", - "grating5:, 0.57143, 16.60, 0.01476756\n", - "grating5:, 0.55814, 16.20, 0.01486971\n", - "grating5:, 0.54545, 15.83, 0.01502474\n", - "grating5:, 0.53333, 15.47, 0.01509725\n", - "grating5:, 0.52174, 15.12, 0.01510229\n", - "grating5:, 0.51064, 14.79, 0.01513732\n", - "grating5:, 0.50000, 14.48, 0.01513738\n", - "grating5:, 0.48980, 14.18, 0.01504842\n", - "grating5:, 0.48000, 13.89, 0.01495349\n", - "grating5:, 0.47059, 13.61, 0.01487265\n", - "grating5:, 0.46154, 13.34, 0.01470122\n", - "grating5:, 0.45283, 13.09, 0.01451304\n", - "grating5:, 0.44444, 12.84, 0.01431250\n", - "grating5:, 0.43636, 12.60, 0.01405324\n", - "grating5:, 0.42857, 12.37, 0.01377062\n", - "grating5:, 0.42105, 12.15, 0.01347551\n", - "grating5:, 0.41379, 11.94, 0.01312754\n", - "grating5:, 0.40678, 11.74, 0.01275200\n", - "grating5:, 0.40000, 11.54, 0.01237396\n", - "grating6:, 0.60000, 21.10, 0.00065868\n", - "grating6:, 0.58537, 20.56, 0.00067104\n", - "grating6:, 0.57143, 20.05, 0.00071263\n", - "grating6:, 0.55814, 19.57, 0.00075198\n", - "grating6:, 0.54545, 19.10, 0.00077912\n", - "grating6:, 0.53333, 18.66, 0.00080894\n", - "grating6:, 0.52174, 18.24, 0.00085387\n", - "grating6:, 0.51064, 17.84, 0.00088503\n", - "grating6:, 0.50000, 17.46, 0.00091058\n", - "grating6:, 0.48980, 17.09, 0.00096069\n", - "grating6:, 0.48000, 16.74, 0.00098339\n", - "grating6:, 0.47059, 16.40, 0.00100748\n", - "grating6:, 0.46154, 16.08, 0.00105476\n", - "grating6:, 0.45283, 15.77, 0.00108059\n", - "grating6:, 0.44444, 15.47, 0.00110807\n", - "grating6:, 0.43636, 15.18, 0.00112967\n", - "grating6:, 0.42857, 14.90, 0.00116340\n", - "grating6:, 0.42105, 14.63, 0.00118366\n", - "grating6:, 0.41379, 14.38, 0.00121941\n", - "grating6:, 0.40678, 14.13, 0.00124712\n", - "grating6:, 0.40000, 13.89, 0.00124315\n", - "grating7:, 0.60000, 24.83, 0.00712106\n", - "grating7:, 0.58537, 24.19, 0.00725596\n", - "grating7:, 0.57143, 23.58, 0.00735079\n", - "grating7:, 0.55814, 23.00, 0.00736618\n", - "grating7:, 0.54545, 22.45, 0.00746403\n", - "grating7:, 0.53333, 21.92, 0.00749527\n", - "grating7:, 0.52174, 21.42, 0.00746526\n", - "grating7:, 0.51064, 20.94, 0.00748585\n", - "grating7:, 0.50000, 20.49, 0.00749678\n", - "grating7:, 0.48980, 20.05, 0.00742615\n", - "grating7:, 0.48000, 19.63, 0.00736557\n", - "grating7:, 0.47059, 19.23, 0.00734406\n", - "grating7:, 0.46154, 18.85, 0.00724584\n", - "grating7:, 0.45283, 18.48, 0.00714626\n", - "grating7:, 0.44444, 18.13, 0.00705266\n", - "grating7:, 0.43636, 17.79, 0.00691768\n", - "grating7:, 0.42857, 17.46, 0.00677418\n", - "grating7:, 0.42105, 17.14, 0.00663497\n", - "grating7:, 0.41379, 16.84, 0.00645740\n", - "grating7:, 0.40678, 16.54, 0.00626388\n", - "grating7:, 0.40000, 16.26, 0.00608317\n", - "grating8:, 0.60000, 28.69, 0.00071146\n", - "grating8:, 0.58537, 27.92, 0.00071967\n", - "grating8:, 0.57143, 27.20, 0.00075394\n", - "grating8:, 0.55814, 26.52, 0.00079416\n", - "grating8:, 0.54545, 25.87, 0.00082087\n", - "grating8:, 0.53333, 25.26, 0.00084132\n", - "grating8:, 0.52174, 24.67, 0.00088698\n", - "grating8:, 0.51064, 24.11, 0.00092106\n", - "grating8:, 0.50000, 23.58, 0.00094015\n", - "grating8:, 0.48980, 23.07, 0.00098910\n", - "grating8:, 0.48000, 22.58, 0.00101255\n", - "grating8:, 0.47059, 22.12, 0.00103723\n", - "grating8:, 0.46154, 21.67, 0.00108226\n", - "grating8:, 0.45283, 21.24, 0.00110965\n", - "grating8:, 0.44444, 20.83, 0.00113597\n", - "grating8:, 0.43636, 20.43, 0.00115414\n", - "grating8:, 0.42857, 20.05, 0.00118921\n", - "grating8:, 0.42105, 19.68, 0.00120781\n", - "grating8:, 0.41379, 19.33, 0.00124119\n", - "grating8:, 0.40678, 18.99, 0.00126834\n", - "grating8:, 0.40000, 18.66, 0.00126119\n", - "grating9:, 0.60000, 32.68, 0.00405749\n", - "grating9:, 0.58537, 31.79, 0.00416387\n", - "grating9:, 0.57143, 30.95, 0.00423625\n", - "grating9:, 0.55814, 30.15, 0.00421224\n", - "grating9:, 0.54545, 29.40, 0.00429611\n", - "grating9:, 0.53333, 28.69, 0.00432324\n", - "grating9:, 0.52174, 28.01, 0.00427886\n", - "grating9:, 0.51064, 27.36, 0.00429394\n", - "grating9:, 0.50000, 26.74, 0.00432027\n", - "grating9:, 0.48980, 26.16, 0.00425827\n", - "grating9:, 0.48000, 25.59, 0.00420924\n", - "grating9:, 0.47059, 25.06, 0.00421678\n", - "grating9:, 0.46154, 24.54, 0.00415124\n", - "grating9:, 0.45283, 24.05, 0.00408829\n", - "grating9:, 0.44444, 23.58, 0.00404009\n", - "grating9:, 0.43636, 23.12, 0.00395897\n", - "grating9:, 0.42857, 22.69, 0.00387386\n", - "grating9:, 0.42105, 22.27, 0.00380207\n", - "grating9:, 0.41379, 21.86, 0.00369822\n", - "grating9:, 0.40678, 21.48, 0.00358005\n", - "grating9:, 0.40000, 21.10, 0.00348359\n" + "Using MPI version 3.1, 1 processes\n" ] } ], @@ -250,10 +53,46 @@ "# -*- coding: utf-8 -*-\n", "\n", "import meep as mp\n", + "from meep.materials import fused_quartz\n", "import math\n", "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We first need to simulate the empty, homogenous glass (fuzed quartz)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-----------\n", + "Initializing structure...\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ "resolution = 50 # pixels/μm\n", "\n", "dpml = 1.0 # PML thickness\n", @@ -281,15 +120,13 @@ "\n", "k_point = mp.Vector3(0,0,0)\n", "\n", - "glass = mp.Medium(index=1.5)\n", - "\n", "symmetries=[mp.Mirror(mp.Y)]\n", "\n", "sim = mp.Simulation(resolution=resolution,\n", " cell_size=cell_size,\n", " boundary_layers=pml_layers,\n", " k_point=k_point,\n", - " default_material=glass,\n", + " default_material=fused_quartz,\n", " sources=sources,\n", " symmetries=symmetries)\n", "\n", @@ -297,14 +134,83 @@ "mon_pt = mp.Vector3(0.5*sx-dpml-0.5*dpad)\n", "flux_mon = sim.add_flux(fcen, df, nfreq, mp.FluxRegion(center=mon_pt, size=mp.Vector3(y=sy)))\n", "\n", - "sim.run(until_after_sources=mp.stop_when_fields_decayed(50, mp.Ez, mon_pt, 1e-9))\n", - "\n", + "f = plt.figure(dpi=120)\n", + "sim.plot2D(ax=f.gca())\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we'll run the simulation and record the fields." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "field decay(t = 50.01): 0.11139427530016409 / 0.11139427530016409 = 1.0\n", + "field decay(t = 100.01): 1.824305440068526e-15 / 0.11139427530016409 = 1.637701250941967e-14\n", + "run 0 finished at t = 100.01 (10001 timesteps)\n" + ] + } + ], + "source": [ + "sim.run(until_after_sources=mp.stop_when_fields_decayed(50, mp.Ez, mon_pt, 1e-9))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we'll simulate the actual grating." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-----------\n", + "Initializing structure...\n", + " block, center = (-2.25,0,0)\n", + " size (4,1e+20,1e+20)\n", + " axes (1,0,0), (0,1,0), (0,0,1)\n", + " block, center = (0,0,0)\n", + " size (0.5,5,1e+20)\n", + " axes (1,0,0), (0,1,0), (0,0,1)\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ "input_flux = mp.get_fluxes(flux_mon)\n", "\n", "sim.reset_meep()\n", "\n", - "geometry = [mp.Block(material=glass, size=mp.Vector3(dpml+dsub,mp.inf,mp.inf), center=mp.Vector3(-0.5*sx+0.5*(dpml+dsub))),\n", - " mp.Block(material=glass, size=mp.Vector3(gh,gdc*gp,mp.inf), center=mp.Vector3(-0.5*sx+dpml+dsub+0.5*gh))]\n", + "geometry = [mp.Block(material=fused_quartz, size=mp.Vector3(dpml+dsub,mp.inf,mp.inf), center=mp.Vector3(-0.5*sx+0.5*(dpml+dsub))),\n", + " mp.Block(material=fused_quartz, size=mp.Vector3(gh,gdc*gp,mp.inf), center=mp.Vector3(-0.5*sx+dpml+dsub+0.5*gh))]\n", "\n", "sim = mp.Simulation(resolution=resolution,\n", " cell_size=cell_size,\n", @@ -316,8 +222,267 @@ "\n", "mode_mon = sim.add_flux(fcen, df, nfreq, mp.FluxRegion(center=mon_pt, size=mp.Vector3(y=sy)))\n", "\n", - "sim.run(until_after_sources=mp.stop_when_fields_decayed(50, mp.Ez, mon_pt, 1e-9))\n", - "\n", + "f2 = plt.figure(dpi=120)\n", + "sim.plot2D(ax=f2.gca())\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "field decay(t = 50.01): 0.1082498119209954 / 0.1082498119209954 = 1.0\n", + "field decay(t = 100.01): 7.512926070352665e-06 / 0.1082498119209954 = 6.940359467632024e-05\n", + "field decay(t = 150.02): 6.732414916367269e-06 / 0.1082498119209954 = 6.219331744687766e-05\n", + "field decay(t = 200.03): 2.3712635292765586e-06 / 0.1082498119209954 = 2.1905474819736332e-05\n", + "field decay(t = 250.04): 9.494875348809632e-07 / 0.1082498119209954 = 8.771262675023706e-06\n", + "field decay(t = 300.04): 3.8220269083178343e-07 / 0.1082498119209954 = 3.530746927400933e-06\n", + "field decay(t = 350.05): 1.5689447663324306e-07 / 0.1082498119209954 = 1.4493741268368232e-06\n", + "field decay(t = 400.06): 6.492618040375044e-08 / 0.1082498119209954 = 5.997809996301509e-07\n", + "field decay(t = 450.07): 2.9338161615346314e-08 / 0.1082498119209954 = 2.710227490903943e-07\n", + "field decay(t = 500.08): 1.115652431764312e-08 / 0.1082498119209954 = 1.0306275936798441e-07\n", + "field decay(t = 550.08): 4.421515879496221e-09 / 0.1082498119209954 = 4.084548324872104e-08\n", + "field decay(t = 600.09): 1.6989786783603636e-09 / 0.1082498119209954 = 1.5694980418075362e-08\n", + "field decay(t = 650.1): 8.779199940057175e-10 / 0.1082498119209954 = 8.110129509014344e-09\n", + "field decay(t = 700.11): 2.64630974046326e-10 / 0.1082498119209954 = 2.444632183189964e-09\n", + "field decay(t = 750.12): 1.5056115102443512e-10 / 0.1082498119209954 = 1.39086755304776e-09\n", + "field decay(t = 800.13): 6.39132960275918e-11 / 0.1082498119209954 = 5.904240838241642e-10\n", + "run 0 finished at t = 800.13 (80013 timesteps)\n" + ] + } + ], + "source": [ + "sim.run(until_after_sources=mp.stop_when_fields_decayed(50, mp.Ez, mon_pt, 1e-9))" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "grating0:, 0.60000, 0.00, 0.12884455\n", + "grating0:, 0.58537, 0.00, 0.10877453\n", + "grating0:, 0.57143, 0.00, 0.09019427\n", + "grating0:, 0.55814, 0.00, 0.07312453\n", + "grating0:, 0.54545, 0.00, 0.05778349\n", + "grating0:, 0.53333, 0.00, 0.04394176\n", + "grating0:, 0.52174, 0.00, 0.03203070\n", + "grating0:, 0.51064, 0.00, 0.02188719\n", + "grating0:, 0.50000, 0.00, 0.01363406\n", + "grating0:, 0.48980, 0.00, 0.00741318\n", + "grating0:, 0.48000, 0.00, 0.00318863\n", + "grating0:, 0.47059, 0.00, 0.00103727\n", + "grating0:, 0.46154, 0.00, 0.00101454\n", + "grating0:, 0.45283, 0.00, 0.00313542\n", + "grating0:, 0.44444, 0.00, 0.00745115\n", + "grating0:, 0.43636, 0.00, 0.01396620\n", + "grating0:, 0.42857, 0.00, 0.02252241\n", + "grating0:, 0.42105, 0.00, 0.03341204\n", + "grating0:, 0.41379, 0.00, 0.04635643\n", + "grating0:, 0.40678, 0.00, 0.06143255\n", + "grating0:, 0.40000, 0.00, 0.07872147\n", + "grating1:, 0.60000, 3.44, 0.34156387\n", + "grating1:, 0.58537, 3.36, 0.34944613\n", + "grating1:, 0.57143, 3.28, 0.35680089\n", + "grating1:, 0.55814, 3.20, 0.36346042\n", + "grating1:, 0.54545, 3.13, 0.36942481\n", + "grating1:, 0.53333, 3.06, 0.37469297\n", + "grating1:, 0.52174, 2.99, 0.37921459\n", + "grating1:, 0.51064, 2.93, 0.38293085\n", + "grating1:, 0.50000, 2.87, 0.38587902\n", + "grating1:, 0.48980, 2.81, 0.38798872\n", + "grating1:, 0.48000, 2.75, 0.38921779\n", + "grating1:, 0.47059, 2.70, 0.38962522\n", + "grating1:, 0.46154, 2.65, 0.38915071\n", + "grating1:, 0.45283, 2.60, 0.38774251\n", + "grating1:, 0.44444, 2.55, 0.38545472\n", + "grating1:, 0.43636, 2.50, 0.38227119\n", + "grating1:, 0.42857, 2.46, 0.37815922\n", + "grating1:, 0.42105, 2.41, 0.37313255\n", + "grating1:, 0.41379, 2.37, 0.36725057\n", + "grating1:, 0.40678, 2.33, 0.36045351\n", + "grating1:, 0.40000, 2.29, 0.35278267\n", + "grating2:, 0.60000, 6.89, 0.00060165\n", + "grating2:, 0.58537, 6.72, 0.00063926\n", + "grating2:, 0.57143, 6.56, 0.00067111\n", + "grating2:, 0.55814, 6.41, 0.00070492\n", + "grating2:, 0.54545, 6.26, 0.00075251\n", + "grating2:, 0.53333, 6.12, 0.00078270\n", + "grating2:, 0.52174, 5.99, 0.00081829\n", + "grating2:, 0.51064, 5.86, 0.00086701\n", + "grating2:, 0.50000, 5.74, 0.00089301\n", + "grating2:, 0.48980, 5.62, 0.00093682\n", + "grating2:, 0.48000, 5.51, 0.00097960\n", + "grating2:, 0.47059, 5.40, 0.00100195\n", + "grating2:, 0.46154, 5.30, 0.00103900\n", + "grating2:, 0.45283, 5.20, 0.00108483\n", + "grating2:, 0.44444, 5.10, 0.00111937\n", + "grating2:, 0.43636, 5.01, 0.00113271\n", + "grating2:, 0.42857, 4.92, 0.00118188\n", + "grating2:, 0.42105, 4.83, 0.00121476\n", + "grating2:, 0.41379, 4.75, 0.00123868\n", + "grating2:, 0.40678, 4.67, 0.00127956\n", + "grating2:, 0.40000, 4.59, 0.00130116\n", + "grating3:, 0.60000, 10.37, 0.03778326\n", + "grating3:, 0.58537, 10.11, 0.03859258\n", + "grating3:, 0.57143, 9.87, 0.03942088\n", + "grating3:, 0.55814, 9.64, 0.04012895\n", + "grating3:, 0.54545, 9.42, 0.04074844\n", + "grating3:, 0.53333, 9.21, 0.04131042\n", + "grating3:, 0.52174, 9.01, 0.04179088\n", + "grating3:, 0.51064, 8.81, 0.04215646\n", + "grating3:, 0.50000, 8.63, 0.04247568\n", + "grating3:, 0.48980, 8.45, 0.04268933\n", + "grating3:, 0.48000, 8.28, 0.04278248\n", + "grating3:, 0.47059, 8.12, 0.04282523\n", + "grating3:, 0.46154, 7.96, 0.04277566\n", + "grating3:, 0.45283, 7.81, 0.04258704\n", + "grating3:, 0.44444, 7.66, 0.04232569\n", + "grating3:, 0.43636, 7.52, 0.04197688\n", + "grating3:, 0.42857, 7.39, 0.04150865\n", + "grating3:, 0.42105, 7.26, 0.04093166\n", + "grating3:, 0.41379, 7.13, 0.04029251\n", + "grating3:, 0.40678, 7.01, 0.03952707\n", + "grating3:, 0.40000, 6.89, 0.03865716\n", + "grating4:, 0.60000, 13.89, 0.00061845\n", + "grating4:, 0.58537, 13.54, 0.00065654\n", + "grating4:, 0.57143, 13.21, 0.00068831\n", + "grating4:, 0.55814, 12.90, 0.00071970\n", + "grating4:, 0.54545, 12.60, 0.00076721\n", + "grating4:, 0.53333, 12.32, 0.00079643\n", + "grating4:, 0.52174, 12.05, 0.00083103\n", + "grating4:, 0.51064, 11.79, 0.00088004\n", + "grating4:, 0.50000, 11.54, 0.00090551\n", + "grating4:, 0.48980, 11.30, 0.00094845\n", + "grating4:, 0.48000, 11.07, 0.00099106\n", + "grating4:, 0.47059, 10.85, 0.00101440\n", + "grating4:, 0.46154, 10.64, 0.00105076\n", + "grating4:, 0.45283, 10.44, 0.00109609\n", + "grating4:, 0.44444, 10.24, 0.00113118\n", + "grating4:, 0.43636, 10.05, 0.00114358\n", + "grating4:, 0.42857, 9.87, 0.00119209\n", + "grating4:, 0.42105, 9.70, 0.00122465\n", + "grating4:, 0.41379, 9.53, 0.00124821\n", + "grating4:, 0.40678, 9.36, 0.00128798\n", + "grating4:, 0.40000, 9.21, 0.00130878\n", + "grating5:, 0.60000, 17.46, 0.01343974\n", + "grating5:, 0.58537, 17.02, 0.01368150\n", + "grating5:, 0.57143, 16.60, 0.01399264\n", + "grating5:, 0.55814, 16.20, 0.01422852\n", + "grating5:, 0.54545, 15.83, 0.01442234\n", + "grating5:, 0.53333, 15.47, 0.01461010\n", + "grating5:, 0.52174, 15.12, 0.01477063\n", + "grating5:, 0.51064, 14.79, 0.01486755\n", + "grating5:, 0.50000, 14.48, 0.01497820\n", + "grating5:, 0.48980, 14.18, 0.01504258\n", + "grating5:, 0.48000, 13.89, 0.01504400\n", + "grating5:, 0.47059, 13.61, 0.01505723\n", + "grating5:, 0.46154, 13.34, 0.01504421\n", + "grating5:, 0.45283, 13.09, 0.01495468\n", + "grating5:, 0.44444, 12.84, 0.01485445\n", + "grating5:, 0.43636, 12.60, 0.01473364\n", + "grating5:, 0.42857, 12.37, 0.01456012\n", + "grating5:, 0.42105, 12.15, 0.01434018\n", + "grating5:, 0.41379, 11.94, 0.01412183\n", + "grating5:, 0.40678, 11.74, 0.01384159\n", + "grating5:, 0.40000, 11.54, 0.01351756\n", + "grating6:, 0.60000, 21.10, 0.00064734\n", + "grating6:, 0.58537, 20.56, 0.00068587\n", + "grating6:, 0.57143, 20.05, 0.00071786\n", + "grating6:, 0.55814, 19.57, 0.00074436\n", + "grating6:, 0.54545, 19.10, 0.00079178\n", + "grating6:, 0.53333, 18.66, 0.00081937\n", + "grating6:, 0.52174, 18.24, 0.00085160\n", + "grating6:, 0.51064, 17.84, 0.00090064\n", + "grating6:, 0.50000, 17.46, 0.00092589\n", + "grating6:, 0.48980, 17.09, 0.00096662\n", + "grating6:, 0.48000, 16.74, 0.00100832\n", + "grating6:, 0.47059, 16.40, 0.00103371\n", + "grating6:, 0.46154, 16.08, 0.00106905\n", + "grating6:, 0.45283, 15.77, 0.00111300\n", + "grating6:, 0.44444, 15.47, 0.00114934\n", + "grating6:, 0.43636, 15.18, 0.00116022\n", + "grating6:, 0.42857, 14.90, 0.00120761\n", + "grating6:, 0.42105, 14.63, 0.00123951\n", + "grating6:, 0.41379, 14.38, 0.00126300\n", + "grating6:, 0.40678, 14.13, 0.00130068\n", + "grating6:, 0.40000, 13.89, 0.00131970\n", + "grating7:, 0.60000, 24.83, 0.00667501\n", + "grating7:, 0.58537, 24.19, 0.00675842\n", + "grating7:, 0.57143, 23.58, 0.00693378\n", + "grating7:, 0.55814, 23.00, 0.00704600\n", + "grating7:, 0.54545, 22.45, 0.00712578\n", + "grating7:, 0.53333, 21.92, 0.00721375\n", + "grating7:, 0.52174, 21.42, 0.00729197\n", + "grating7:, 0.51064, 20.94, 0.00731446\n", + "grating7:, 0.50000, 20.49, 0.00737075\n", + "grating7:, 0.48980, 20.05, 0.00739756\n", + "grating7:, 0.48000, 19.63, 0.00737302\n", + "grating7:, 0.47059, 19.23, 0.00737750\n", + "grating7:, 0.46154, 18.85, 0.00737796\n", + "grating7:, 0.45283, 18.48, 0.00731808\n", + "grating7:, 0.44444, 18.13, 0.00726095\n", + "grating7:, 0.43636, 17.79, 0.00720401\n", + "grating7:, 0.42857, 17.46, 0.00711631\n", + "grating7:, 0.42105, 17.14, 0.00699564\n", + "grating7:, 0.41379, 16.84, 0.00689523\n", + "grating7:, 0.40678, 16.54, 0.00675169\n", + "grating7:, 0.40000, 16.26, 0.00658026\n", + "grating8:, 0.60000, 28.69, 0.00068832\n", + "grating8:, 0.58537, 27.92, 0.00072766\n", + "grating8:, 0.57143, 27.20, 0.00076099\n", + "grating8:, 0.55814, 26.52, 0.00077899\n", + "grating8:, 0.54545, 25.87, 0.00082616\n", + "grating8:, 0.53333, 25.26, 0.00085115\n", + "grating8:, 0.52174, 24.67, 0.00087912\n", + "grating8:, 0.51064, 24.11, 0.00092748\n", + "grating8:, 0.50000, 23.58, 0.00095268\n", + "grating8:, 0.48980, 23.07, 0.00098912\n", + "grating8:, 0.48000, 22.58, 0.00102872\n", + "grating8:, 0.47059, 22.12, 0.00105772\n", + "grating8:, 0.46154, 21.67, 0.00109142\n", + "grating8:, 0.45283, 21.24, 0.00113269\n", + "grating8:, 0.44444, 20.83, 0.00117105\n", + "grating8:, 0.43636, 20.43, 0.00118006\n", + "grating8:, 0.42857, 20.05, 0.00122584\n", + "grating8:, 0.42105, 19.68, 0.00125676\n", + "grating8:, 0.41379, 19.33, 0.00128083\n", + "grating8:, 0.40678, 18.99, 0.00131512\n", + "grating8:, 0.40000, 18.66, 0.00133206\n", + "grating9:, 0.60000, 32.68, 0.00381720\n", + "grating9:, 0.58537, 31.79, 0.00383343\n", + "grating9:, 0.57143, 30.95, 0.00396032\n", + "grating9:, 0.55814, 30.15, 0.00403152\n", + "grating9:, 0.54545, 29.40, 0.00406905\n", + "grating9:, 0.53333, 28.69, 0.00412086\n", + "grating9:, 0.52174, 28.01, 0.00417458\n", + "grating9:, 0.51064, 27.36, 0.00416693\n", + "grating9:, 0.50000, 26.74, 0.00420448\n", + "grating9:, 0.48980, 26.16, 0.00422216\n", + "grating9:, 0.48000, 25.59, 0.00418744\n", + "grating9:, 0.47059, 25.06, 0.00418665\n", + "grating9:, 0.46154, 24.54, 0.00419713\n", + "grating9:, 0.45283, 24.05, 0.00415325\n", + "grating9:, 0.44444, 23.58, 0.00411170\n", + "grating9:, 0.43636, 23.12, 0.00408157\n", + "grating9:, 0.42857, 22.69, 0.00403487\n", + "grating9:, 0.42105, 22.27, 0.00395612\n", + "grating9:, 0.41379, 21.86, 0.00390576\n", + "grating9:, 0.40678, 21.48, 0.00382198\n", + "grating9:, 0.40000, 21.10, 0.00371561\n" + ] + } + ], + "source": [ "freqs = mp.get_eigenmode_freqs(mode_mon)\n", "\n", "nmode = 10\n", @@ -342,12 +507,12 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 7, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -377,6 +542,13 @@ "cbar.set_ticks([t for t in np.arange(0,tran_max+0.1,0.1)])\n", "cbar.set_ticklabels([\"{:.1f}\".format(t) for t in np.arange(0,tran_max+0.1,0.1)])" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -395,7 +567,20 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.1" + "version": "3.6.8" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": true, + "toc_window_display": false } }, "nbformat": 4, From 81f3cc8b34b0a11c2ed1c21b45b4ea2514db5a6a Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Mon, 24 Jun 2019 14:54:52 -0600 Subject: [PATCH 15/25] minor fixes --- python/geom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/geom.py b/python/geom.py index 4fce49ccf..9c9a0a401 100755 --- a/python/geom.py +++ b/python/geom.py @@ -253,12 +253,12 @@ def rotate(self, rotations): self.transform(Matrix( Vector3(np.cos(rot.y),0,-np.sin(rot.y)), Vector3(0,1,0), - Vector3(np.sin(rot.y),0,np.cos(np.y)) + Vector3(np.sin(rot.y),0,np.cos(rot.y)) )) else: self.transform(Matrix( Vector3(np.cos(rot.z),np.sin(rot.z),0), - Vector3(-np.sin(np.z),np.cos(rot.z),0), + Vector3(-np.sin(rot.z),np.cos(rot.z),0), Vector3(0,0,1) )) From 041ea9c8947e96ef0f8b328e0c29915b5f39afc0 Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Mon, 24 Jun 2019 15:36:22 -0600 Subject: [PATCH 16/25] refactor --- src/meep.hpp | 1 - src/monitor.cpp | 152 ++++++++++++++++++++++++------------------------ 2 files changed, 75 insertions(+), 78 deletions(-) diff --git a/src/meep.hpp b/src/meep.hpp index 64a3ed394..a5dd213ce 100644 --- a/src/meep.hpp +++ b/src/meep.hpp @@ -605,7 +605,6 @@ class structure_chunk { void remove_susceptibilities(); // monitor.cpp - double get_chi1inv_tensor(std::vector &chi1_inv_tensor, component c, direction d, int idx, double omega) const; double get_chi1inv_at_pt(component, direction, int idx, double omega = 0) const; double get_chi1inv(component, direction, const ivec &iloc, double omega = 0) const; double get_inveps(component c, direction d, const ivec &iloc, double omega = 0) const { diff --git a/src/monitor.cpp b/src/monitor.cpp index a1f128c0c..7d785f9db 100644 --- a/src/monitor.cpp +++ b/src/monitor.cpp @@ -245,90 +245,88 @@ void matrix_invert(std::vector &Vinv, std::vector &V) { Vinv[2 + 3*2] = 1/det * (V[0 + 3*0]*V[1 + 3*1] - V[0 + 3*1]*V[1 + 3*0]); } -double structure_chunk::get_chi1inv_tensor(std::vector &chi1_inv_tensor, component c, direction d, int idx, double omega) const { - // ----------------------------------------------------------------- // - // ---- Step 1: Get instantaneous chi1 tensor ---------------------- - // ----------------------------------------------------------------- // - int my_stuff = E_stuff; - component comp_list[3]; - if (is_electric(c)) { - comp_list[0] = Ex; comp_list[1] = Ey; comp_list[2] = Ez; - my_stuff = E_stuff; - }else if (is_magnetic(c)) { - comp_list[0] = Hx; comp_list[1] = Hy; comp_list[2] = Hz; - my_stuff = H_stuff; - } else if (is_D(c)) { - comp_list[0] = Dx; comp_list[1] = Dy; comp_list[2] = Dz; - my_stuff = D_stuff; - } else if (is_B(c)) { - comp_list[0] = Bx; comp_list[1] = By; comp_list[2] = Bz; - my_stuff = B_stuff; - } - - std::vector chi1_tensor(9,0); - - // Set up the chi1inv tensor - for (int com_it=0; com_it<3;com_it++){ - for (int dir_int=0;dir_int<3;dir_int++){ - if (chi1inv[comp_list[com_it]][dir_int] ) - chi1_inv_tensor[com_it + 3*dir_int] = chi1inv[comp_list[com_it]][dir_int][idx]; - else if(dir_int == component_direction(comp_list[com_it])) - chi1_inv_tensor[com_it + 3*dir_int] = 1; - else - chi1_inv_tensor[com_it + 3*dir_int] = 0; +double structure_chunk::get_chi1inv_at_pt(component c, direction d, int idx, double omega) const { + double res = 0.0; + if (is_mine()){ + // ----------------------------------------------------------------- // + // ---- Step 1: Get instantaneous chi1 tensor ---------------------- + // ----------------------------------------------------------------- // + + int my_stuff = E_stuff; + component comp_list[3]; + if (is_electric(c)) { + comp_list[0] = Ex; comp_list[1] = Ey; comp_list[2] = Ez; + my_stuff = E_stuff; + }else if (is_magnetic(c)) { + comp_list[0] = Hx; comp_list[1] = Hy; comp_list[2] = Hz; + my_stuff = H_stuff; + } else if (is_D(c)) { + comp_list[0] = Dx; comp_list[1] = Dy; comp_list[2] = Dz; + my_stuff = D_stuff; + } else if (is_B(c)) { + comp_list[0] = Bx; comp_list[1] = By; comp_list[2] = Bz; + my_stuff = B_stuff; } - } - - - matrix_invert(chi1_tensor, chi1_inv_tensor); // We have the inverse, so let's invert it. - - // ----------------------------------------------------------------- // - // ---- Step 2: Evaluate susceptibilities of each tensor element --- - // ----------------------------------------------------------------- // - // loop over tensor elements - for (int com_it=0; com_it<3;com_it++){ - for (int dir_int=0;dir_int<3;dir_int++){ - std::complex eps(chi1_tensor[com_it + 3*dir_int],0.0); - component cc = comp_list[com_it]; - direction dd = (direction)dir_int; - // Loop through and add up susceptibility contributions - // locate correct susceptibility list - susceptibility *my_sus = chiP[my_stuff]; - while (my_sus) { - if (my_sus->sigma[cc][dd]) { - double sigma = my_sus->sigma[cc][dd][idx]; - eps += my_sus->chi1(omega,sigma); - } - my_sus = my_sus->next; - } - // Account for conductivity term - if (conductivity[cc][dd]) { - double conductivityCur = conductivity[cc][dd][idx]; - eps = std::complex(1.0, (conductivityCur/omega)) * eps; + std::vector chi1_inv_tensor(9,0); + std::vector chi1_tensor(9,0); + + // Set up the chi1inv tensor + for (int com_it=0; com_it<3;com_it++){ + for (int dir_int=0;dir_int<3;dir_int++){ + if (chi1inv[comp_list[com_it]][dir_int] ) + chi1_inv_tensor[com_it + 3*dir_int] = chi1inv[comp_list[com_it]][dir_int][idx]; + else if(dir_int == component_direction(comp_list[com_it])) + chi1_inv_tensor[com_it + 3*dir_int] = 1; + else + chi1_inv_tensor[com_it + 3*dir_int] = 0; } - - // assign to eps tensor - if (eps.imag() == 0 ) - chi1_tensor[com_it + 3*dir_int] = eps.real(); - else - chi1_tensor[com_it + 3*dir_int] = std::sqrt(eps).real() * std::sqrt(eps).real(); // hack for metals } - } + + + matrix_invert(chi1_tensor, chi1_inv_tensor); // We have the inverse, so let's invert it. + + // ----------------------------------------------------------------- // + // ---- Step 2: Evaluate susceptibilities of each tensor element --- + // ----------------------------------------------------------------- // + + // loop over tensor elements + for (int com_it=0; com_it<3;com_it++){ + for (int dir_int=0;dir_int<3;dir_int++){ + std::complex eps(chi1_tensor[com_it + 3*dir_int],0.0); + component cc = comp_list[com_it]; + direction dd = (direction)dir_int; + // Loop through and add up susceptibility contributions + // locate correct susceptibility list + susceptibility *my_sus = chiP[my_stuff]; + while (my_sus) { + if (my_sus->sigma[cc][dd]) { + double sigma = my_sus->sigma[cc][dd][idx]; + eps += my_sus->chi1(omega,sigma); + } + my_sus = my_sus->next; + } - // ----------------------------------------------------------------- // - // ---- Step 3: Invert chi1 matrix to get chi1inv matrix ----------- - // ----------------------------------------------------------------- // - matrix_invert(chi1_inv_tensor, chi1_tensor); // We have the inverse, so let's invert it. + // Account for conductivity term + if (conductivity[cc][dd]) { + double conductivityCur = conductivity[cc][dd][idx]; + eps = std::complex(1.0, (conductivityCur/omega)) * eps; + } - return chi1_inv_tensor[component_index(c) + 3*d]; -} + // assign to eps tensor + if (eps.imag() == 0 ) + chi1_tensor[com_it + 3*dir_int] = eps.real(); + else + chi1_tensor[com_it + 3*dir_int] = std::sqrt(eps).real() * std::sqrt(eps).real(); // hack for metals + } + } -double structure_chunk::get_chi1inv_at_pt(component c, direction d, int idx, double omega) const { - double res = 0.0; - if (is_mine()){ - std::vector chi1_inv_tensor(9,0); - res = get_chi1inv_tensor(chi1_inv_tensor, c, d, idx, omega); + // ----------------------------------------------------------------- // + // ---- Step 3: Invert chi1 matrix to get chi1inv matrix ----------- + // ----------------------------------------------------------------- // + + matrix_invert(chi1_inv_tensor, chi1_tensor); // We have the inverse, so let's invert it. + res = chi1_inv_tensor[component_index(c) + 3*d]; } return res; } From a3e4987f807885066246c992e0c66def55ba90fe Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Mon, 24 Jun 2019 16:08:58 -0600 Subject: [PATCH 17/25] check1 --- python/tests/dispersive_eigenmode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/tests/dispersive_eigenmode.py b/python/tests/dispersive_eigenmode.py index 237dfcce1..75d810b9a 100644 --- a/python/tests/dispersive_eigenmode.py +++ b/python/tests/dispersive_eigenmode.py @@ -134,7 +134,7 @@ def test_chi1_routine(self): self.call_chi1(LiNbO3,w0) self.call_chi1(LiNbO3,w1) - def test_meep_eigenmode(self): + def atest_meep_eigenmode(self): # Checks the get_eigenmode features with dispersive materials # NOTE: metals are not supported from meep.materials import Si, Ag, LiNbO3, Au @@ -155,7 +155,7 @@ def test_meep_eigenmode(self): #self.simulate_meep(LiNbO3,w0) #self.simulate_meep(LiNbO3,w1) - def test_get_with_dispersion(self): + def atest_get_with_dispersion(self): # Checks the get_array_slice and output_fields method # with dispersive materials. From 8eebb182d5c2047767c0ac1bc492156079276634 Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Tue, 25 Jun 2019 08:12:01 -0600 Subject: [PATCH 18/25] case2 --- python/tests/dispersive_eigenmode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/tests/dispersive_eigenmode.py b/python/tests/dispersive_eigenmode.py index 75d810b9a..3875d579b 100644 --- a/python/tests/dispersive_eigenmode.py +++ b/python/tests/dispersive_eigenmode.py @@ -155,7 +155,7 @@ def atest_meep_eigenmode(self): #self.simulate_meep(LiNbO3,w0) #self.simulate_meep(LiNbO3,w1) - def atest_get_with_dispersion(self): + def test_get_with_dispersion(self): # Checks the get_array_slice and output_fields method # with dispersive materials. From 9a33f4c110c67d20fbba206d10c7c551f9a0615e Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Tue, 25 Jun 2019 08:25:19 -0600 Subject: [PATCH 19/25] add docs --- doc/docs/Python_User_Interface.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/docs/Python_User_Interface.md b/doc/docs/Python_User_Interface.md index 7d51ceb12..9dda106d8 100644 --- a/doc/docs/Python_User_Interface.md +++ b/doc/docs/Python_User_Interface.md @@ -1017,9 +1017,9 @@ Sets the condition of the boundary on the specified side in the specified direct — Given a `component` or `derived_component` constant `c` and a `Vector3` `pt`, returns the value of that component at that point. -**`get_epsilon_point(pt)`** +**`get_epsilon_point(pt, omega=0)`** — -Equivalent to `get_field_point(mp.Dielectric, pt)`. +Given a frequency `omega` and a `Vector3` `pt`, returns the average eigenvalue of the permittivity tensor at that location and frequency. **`initialize_field(c, func)`** — @@ -1800,7 +1800,7 @@ See also [Field Functions](Field_Functions.md), and [Synchronizing the Magnetic The output functions described above write the data for the fields and materials for the entire cell to an HDF5 file. This is useful for post-processing as you can later read in the HDF5 file to obtain field/material data as a NumPy array. However, in some cases it is convenient to bypass the disk altogether to obtain the data *directly* in the form of a NumPy array without writing/reading HDF5 files. Additionally, you may want the field/material data on just a subregion (or slice) of the entire volume. This functionality is provided by the `get_array` method which takes as input a subregion of the cell and the field/material component. The method returns a NumPy array containing values of the field/material at the current simulation time. ```python - get_array(vol=None, center=None, size=None, component=mp.Ez, cmplx=False, arr=None) + get_array(vol=None, center=None, size=None, component=mp.Ez, cmplx=False, arr=None, omega=0) ``` with the following input parameters: @@ -1815,7 +1815,9 @@ with the following input parameters: + `arr`: optional field to pass a pre-allocated NumPy array of the correct size, 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 fetching the same slice repeatedly at different times). -For convenience, the following wrappers for `get_array` over the entire cell are available: `get_epsilon()`, `get_mu()`, `get_hpwr()`, `get_dpwr()`, `get_tot_pwr()`, `get_Xfield()`, `get_Xfield_x()`, `get_Xfield_y()`, `get_Xfield_z()`, `get_Xfield_r()`, `get_Xfield_p()` where `X` is one of `h`, `b`, `e`, `d`, or `s`. The routines `get_Xfield_*` all return an array type consistent with the fields (real or complex). ++ `omega`: optional frequency point over which the average eigenvalue of the dielectric and permeability tensors are evaluated (defaults to 0). + +For convenience, the following wrappers for `get_array` over the entire cell are available: `get_epsilon()`, `get_mu()`, `get_hpwr()`, `get_dpwr()`, `get_tot_pwr()`, `get_Xfield()`, `get_Xfield_x()`, `get_Xfield_y()`, `get_Xfield_z()`, `get_Xfield_r()`, `get_Xfield_p()` where `X` is one of `h`, `b`, `e`, `d`, or `s`. The routines `get_Xfield_*` all return an array type consistent with the fields (real or complex). The routines `get_epsilon()` and `get_mu()` accept the optional omega parameter (defaults to 0). **Note on array-slice dimensions:** The routines `get_epsilon`, `get_Xfield_z`, etc. use as default `size=meep.Simulation.fields.total_volume()` which for simulations involving Bloch-periodic boundaries (via `k_point`) will result in arrays that have slightly *different* dimensions than e.g. `get_array(center=meep.Vector3(), size=cell_size, component=meep.Dielectric`, etc. (i.e., the slice spans the entire cell volume `cell_size`). Neither of these approaches is "wrong", they are just slightly different methods of fetching the boundaries. The key point is that if you pass the same value for the `size` parameter, or use the default, the slicing routines always give you the same-size array for all components. You should *not* try to predict the exact size of these arrays; rather, you should simply rely on Meep's output. From 2d6aa85d19d65b25b84f87c4f3ee08c0d2b7cd91 Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Tue, 25 Jun 2019 09:36:21 -0600 Subject: [PATCH 20/25] cleanup --- python/tests/dispersive_eigenmode.py | 87 +++++++--------------------- 1 file changed, 21 insertions(+), 66 deletions(-) diff --git a/python/tests/dispersive_eigenmode.py b/python/tests/dispersive_eigenmode.py index 3875d579b..d3e558453 100644 --- a/python/tests/dispersive_eigenmode.py +++ b/python/tests/dispersive_eigenmode.py @@ -17,7 +17,7 @@ class TestDispersiveEigenmode(unittest.TestCase): # ----------------------------------------- # # ----------- Helper Functions ------------ # # ----------------------------------------- # - # Directly calss the C++ chi1 routine + # Directly cals the C++ chi1 routine def call_chi1(self,material,omega): sim = mp.Simulation(cell_size=mp.Vector3(1,1,1), @@ -35,48 +35,14 @@ def call_chi1(self,material,omega): n_actual = np.real(np.sqrt(material.epsilon(omega).astype(np.complex128))) np.testing.assert_allclose(n,n_actual) - - # Pulls the "effective index" of a uniform, dispersive material - # (i.e. the refractive index) using meep's get_eigenmode - def simulate_meep(self,material,omega): - - sim = mp.Simulation(cell_size=mp.Vector3(1,1,1), - default_material=material, - resolution=10 - ) - - band_num = 1 - sim.init_sim() - - # Pull the x direction - direction = mp.X - where = mp.Volume(center=mp.Vector3(0,0,0),size=mp.Vector3(0,0,0)) - kpoint = mp.Vector3(1,0,0) - emx = sim.get_eigenmode(omega,direction,where,band_num,kpoint) - - # Pull the y direction - direction = mp.Y - where = mp.Volume(center=mp.Vector3(0,0,0),size=mp.Vector3(0,0,0)) - kpoint = mp.Vector3(0,1,0) - emy = sim.get_eigenmode(omega,direction,where,band_num,kpoint) - - # Pull the z direction - direction = mp.Z - where = mp.Volume(center=mp.Vector3(0,0,0),size=mp.Vector3(0,0,0)) - kpoint = mp.Vector3(0,0,1) - emz = sim.get_eigenmode(omega,direction,where,band_num,kpoint) - - # combine and return - k = np.array(mp.Matrix(emx.k,emy.k,emz.k)) - neff_meep = np.squeeze(k) / omega - - n = np.real(np.sqrt(material.epsilon(omega).astype(np.complex128))) - - np.testing.assert_allclose(n,neff_meep) def verify_output_and_slice(self,material,omega): # Since the slice routines average the diagonals, we need to do that too: - n = np.real(np.sqrt(np.mean(np.linalg.eigvals(material.epsilon(omega))))) + chi1 = np.square(np.real(np.sqrt(material.epsilon(omega)))) + chi1inv = (np.linalg.inv(chi1)) + chi1inv = np.diag(chi1inv) + N = chi1inv.size + n = np.sqrt(N/np.sum(chi1inv)) sim = mp.Simulation(cell_size=mp.Vector3(2,2,2), default_material=material, @@ -130,30 +96,12 @@ def test_chi1_routine(self): self.call_chi1(LiNbO3,w0) self.call_chi1(LiNbO3,w1) - LiNbO3.rotate([mp.Vector3(x=np.radians(28)),mp.Vector3(np.radians(45))]) - self.call_chi1(LiNbO3,w0) - self.call_chi1(LiNbO3,w1) - - def atest_meep_eigenmode(self): - # Checks the get_eigenmode features with dispersive materials - # NOTE: metals are not supported - from meep.materials import Si, Ag, LiNbO3, Au - - # Check Silicon - w0 = Si.valid_freq_range.min - w1 = Si.valid_freq_range.max - self.simulate_meep(Si,w0) - self.simulate_meep(Si,w1) - - # Check Lithium Niobate - w0 = LiNbO3.valid_freq_range.min - w1 = LiNbO3.valid_freq_range.max - #self.simulate_meep(LiNbO3,w0) - #self.simulate_meep(LiNbO3,w1) - - LiNbO3.rotate([mp.Vector3(x=np.radians(28)),mp.Vector3(np.radians(45))]) - #self.simulate_meep(LiNbO3,w0) - #self.simulate_meep(LiNbO3,w1) + # Now let's rotate LN + import copy + rotLiNbO3 = copy.deepcopy(LiNbO3) + rotLiNbO3.rotate([mp.Vector3(x=np.radians(28)),mp.Vector3(np.radians(45))]) + self.call_chi1(rotLiNbO3,w0) + self.call_chi1(rotLiNbO3,w1) def test_get_with_dispersion(self): # Checks the get_array_slice and output_fields method @@ -182,8 +130,15 @@ def test_get_with_dispersion(self): # Check Lithium Niobate w0 = LiNbO3.valid_freq_range.min w1 = LiNbO3.valid_freq_range.max - #self.verify_output_and_slice(LiNbO3,w0) - #self.verify_output_and_slice(LiNbO3,w1) + self.verify_output_and_slice(LiNbO3,w0) + self.verify_output_and_slice(LiNbO3,w1) + + # Now let's rotate LN + import copy + rotLiNbO3 = copy.deepcopy(LiNbO3) + rotLiNbO3.rotate([mp.Vector3(x=np.radians(28)),mp.Vector3(np.radians(45))]) + self.verify_output_and_slice(rotLiNbO3,w0) + self.verify_output_and_slice(rotLiNbO3,w1) if __name__ == '__main__': From 6046280e68dae42c29bacc381fa6111f54acb003 Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Tue, 25 Jun 2019 10:12:37 -0600 Subject: [PATCH 21/25] check3 --- python/tests/dispersive_eigenmode.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/tests/dispersive_eigenmode.py b/python/tests/dispersive_eigenmode.py index d3e558453..5caafe9f6 100644 --- a/python/tests/dispersive_eigenmode.py +++ b/python/tests/dispersive_eigenmode.py @@ -57,10 +57,10 @@ def verify_output_and_slice(self,material,omega): # Check to make sure h5 output is working with omega # NOTE: We'll add this test once h5 support is added - filename = 'dispersive_eigenmode-eps-000000.00.h5' - mp.output_epsilon(sim,omega=omega) - n_h5 = np.sqrt(np.mean(h5py.File(filename, 'r')['eps'])) - self.assertAlmostEqual(n,n_h5, places=4) + #filename = 'dispersive_eigenmode-eps-000000.00.h5' + #mp.output_epsilon(sim,omega=omega) + #n_h5 = np.sqrt(np.mean(h5py.File(filename, 'r')['eps'])) + #self.assertAlmostEqual(n,n_h5, places=4) # ----------------------------------------- # # ----------- Test Routines --------------- # From 176834ab6d788e8a4d30d572948d201e3a54b7a2 Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Tue, 25 Jun 2019 10:55:48 -0600 Subject: [PATCH 22/25] fix test --- python/tests/dispersive_eigenmode.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/python/tests/dispersive_eigenmode.py b/python/tests/dispersive_eigenmode.py index 5caafe9f6..15880f9e5 100644 --- a/python/tests/dispersive_eigenmode.py +++ b/python/tests/dispersive_eigenmode.py @@ -56,11 +56,12 @@ def verify_output_and_slice(self,material,omega): self.assertAlmostEqual(n,n_slice, places=4) # Check to make sure h5 output is working with omega - # NOTE: We'll add this test once h5 support is added - #filename = 'dispersive_eigenmode-eps-000000.00.h5' - #mp.output_epsilon(sim,omega=omega) - #n_h5 = np.sqrt(np.mean(h5py.File(filename, 'r')['eps'])) - #self.assertAlmostEqual(n,n_h5, places=4) + filename = 'dispersive_eigenmode-eps-000000.00.h5' + mp.output_epsilon(sim,omega=omega) + n_h5 = 0 + with h5py.File(filename, 'r') as f: + n_h5 = np.sqrt(np.mean(f['eps'][()])) + self.assertAlmostEqual(n,n_h5, places=4) # ----------------------------------------- # # ----------- Test Routines --------------- # From 6ad454a6cb9adb55f90190568f023b00a2bc616c Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Tue, 25 Jun 2019 11:21:30 -0600 Subject: [PATCH 23/25] 1 more fix! --- python/tests/dispersive_eigenmode.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/tests/dispersive_eigenmode.py b/python/tests/dispersive_eigenmode.py index 15880f9e5..840478e27 100644 --- a/python/tests/dispersive_eigenmode.py +++ b/python/tests/dispersive_eigenmode.py @@ -59,6 +59,7 @@ def verify_output_and_slice(self,material,omega): filename = 'dispersive_eigenmode-eps-000000.00.h5' mp.output_epsilon(sim,omega=omega) n_h5 = 0 + mp.all_wait() with h5py.File(filename, 'r') as f: n_h5 = np.sqrt(np.mean(f['eps'][()])) self.assertAlmostEqual(n,n_h5, places=4) From e28c961c5b47075eb7c6e3977733444514fe4503 Mon Sep 17 00:00:00 2001 From: Alec Hammond Date: Tue, 25 Jun 2019 14:47:24 -0600 Subject: [PATCH 24/25] updates --- python/geom.py | 25 ++------------ python/tests/dispersive_eigenmode.py | 10 +++--- src/meep.hpp | 2 +- src/monitor.cpp | 51 +++++++++++++++------------- 4 files changed, 37 insertions(+), 51 deletions(-) diff --git a/python/geom.py b/python/geom.py index 9c9a0a401..97f46792a 100755 --- a/python/geom.py +++ b/python/geom.py @@ -239,28 +239,9 @@ def transform(self, m): for s in self.H_susceptibilities: s.transform(m) - def rotate(self, rotations): - for rot in rotations: - if np.count_nonzero(rot) != 1: - raise ValueError("Each rotation vector should only have 1 coordinate.") - if rot.x != 0: # rotate about x axis - self.transform(Matrix( - Vector3(1,0,0), - Vector3(0,np.cos(rot.x),np.sin(rot.x)), - Vector3(0,-np.sin(rot.x),np.cos(rot.x)) - )) - elif rot.y != 0: # rotate about z axis - self.transform(Matrix( - Vector3(np.cos(rot.y),0,-np.sin(rot.y)), - Vector3(0,1,0), - Vector3(np.sin(rot.y),0,np.cos(rot.y)) - )) - else: - self.transform(Matrix( - Vector3(np.cos(rot.z),np.sin(rot.z),0), - Vector3(-np.sin(rot.z),np.cos(rot.z),0), - Vector3(0,0,1) - )) + def rotate(self, axis, theta): + T = get_rotation_matrix(axis,theta) + self.transform(T) def epsilon(self,freq): return self._get_epsmu(self.epsilon_diag, self.epsilon_offdiag, self.E_susceptibilities, self.D_conductivity_diag, self.D_conductivity_offdiag, freq) diff --git a/python/tests/dispersive_eigenmode.py b/python/tests/dispersive_eigenmode.py index 840478e27..966f2a9a8 100644 --- a/python/tests/dispersive_eigenmode.py +++ b/python/tests/dispersive_eigenmode.py @@ -38,8 +38,10 @@ def call_chi1(self,material,omega): def verify_output_and_slice(self,material,omega): # Since the slice routines average the diagonals, we need to do that too: - chi1 = np.square(np.real(np.sqrt(material.epsilon(omega)))) - chi1inv = (np.linalg.inv(chi1)) + chi1 = material.epsilon(omega).astype(np.complex128) + if np.any(np.imag(chi1) != 0): + chi1 = np.square(np.real(np.sqrt(chi1))) + chi1inv = np.linalg.inv(chi1) chi1inv = np.diag(chi1inv) N = chi1inv.size n = np.sqrt(N/np.sum(chi1inv)) @@ -101,7 +103,7 @@ def test_chi1_routine(self): # Now let's rotate LN import copy rotLiNbO3 = copy.deepcopy(LiNbO3) - rotLiNbO3.rotate([mp.Vector3(x=np.radians(28)),mp.Vector3(np.radians(45))]) + rotLiNbO3.rotate(mp.Vector3(1,1,1),np.radians(34)) self.call_chi1(rotLiNbO3,w0) self.call_chi1(rotLiNbO3,w1) @@ -138,7 +140,7 @@ def test_get_with_dispersion(self): # Now let's rotate LN import copy rotLiNbO3 = copy.deepcopy(LiNbO3) - rotLiNbO3.rotate([mp.Vector3(x=np.radians(28)),mp.Vector3(np.radians(45))]) + rotLiNbO3.rotate(mp.Vector3(1,1,1),np.radians(34)) self.verify_output_and_slice(rotLiNbO3,w0) self.verify_output_and_slice(rotLiNbO3,w1) diff --git a/src/meep.hpp b/src/meep.hpp index a5dd213ce..647ad4a8d 100644 --- a/src/meep.hpp +++ b/src/meep.hpp @@ -59,7 +59,7 @@ const double nan = -7.0415659787563146e103; // ideally, a value never encountere class h5file; // Defined in monitor.cpp -void matrix_invert(std::vector &Vinv, std::vector &V); +void matrix_invert(std::complex (&Vinv)[9], std::complex (&V)[9]); double pml_quadratic_profile(double, void *); diff --git a/src/monitor.cpp b/src/monitor.cpp index 7d785f9db..6cb49f6c3 100644 --- a/src/monitor.cpp +++ b/src/monitor.cpp @@ -225,29 +225,31 @@ double structure::get_chi1inv(component c, direction d, const ivec &origloc, dou return 0.0; } -/* Set Vinv = inverse of V, where both V and Vinv are real-symmetric matrices.*/ -void matrix_invert(std::vector &Vinv, std::vector &V) { +/* Set Vinv = inverse of V, where both V and Vinv are complex matrices.*/ +void matrix_invert(std::complex (&Vinv)[9], std::complex (&V)[9]) { - double det = (V[0 +3*0] * (V[1 + 3*1]*V[2 +3*2] - V[1 + 3*2]*V[2 +3*1]) - + std::complex det = (V[0 +3*0] * (V[1 + 3*1]*V[2 +3*2] - V[1 + 3*2]*V[2 +3*1]) - V[0 + 3*1] * (V[0 + 3*1]*V[2 + 3*2] - V[1 + 3*2]*V[0 + 3*2]) + V[0 + 3*2] * (V[0 + 3*1]*V[1 + 3*2] - V[1 + 3*1]*V[0 + 3*2])); - if (det == 0) abort("meep: Matrix is singular, aborting.\n"); - - Vinv[0 + 3*0] = 1/det * (V[1 + 3*1]*V[2 + 3*2] - V[1 + 3*2]*V[2 + 3*1]); - Vinv[0 + 3*1] = 1/det * (V[0 + 3*2]*V[2 + 3*1] - V[0 + 3*1]*V[2 + 3*2]); - Vinv[0 + 3*2] = 1/det * (V[0 + 3*1]*V[1 + 3*2] - V[0 + 3*2]*V[1 + 3*1]); - Vinv[1 + 3*0] = 1/det * (V[1 + 3*2]*V[2 + 3*0] - V[1 + 3*0]*V[2 + 3*2]); - Vinv[1 + 3*1] = 1/det * (V[0 + 3*0]*V[2 + 3*2] - V[0 + 3*2]*V[2 + 3*0]); - Vinv[1 + 3*2] = 1/det * (V[0 + 3*2]*V[1 + 3*0] - V[0 + 3*0]*V[1 + 3*2]); - Vinv[2 + 3*0] = 1/det * (V[1 + 3*0]*V[2 + 3*1] - V[1 + 3*1]*V[2 + 3*0]); - Vinv[2 + 3*1] = 1/det * (V[0 + 3*1]*V[2 + 3*0] - V[0 + 3*0]*V[2 + 3*1]); - Vinv[2 + 3*2] = 1/det * (V[0 + 3*0]*V[1 + 3*1] - V[0 + 3*1]*V[1 + 3*0]); + if (det.real() == 0 && det.imag() == 0) abort("meep: Matrix is singular, aborting.\n"); + + Vinv[0 + 3*0] = 1.0/det * (V[1 + 3*1]*V[2 + 3*2] - V[1 + 3*2]*V[2 + 3*1]); + Vinv[0 + 3*1] = 1.0/det * (V[0 + 3*2]*V[2 + 3*1] - V[0 + 3*1]*V[2 + 3*2]); + Vinv[0 + 3*2] = 1.0/det * (V[0 + 3*1]*V[1 + 3*2] - V[0 + 3*2]*V[1 + 3*1]); + Vinv[1 + 3*0] = 1.0/det * (V[1 + 3*2]*V[2 + 3*0] - V[1 + 3*0]*V[2 + 3*2]); + Vinv[1 + 3*1] = 1.0/det * (V[0 + 3*0]*V[2 + 3*2] - V[0 + 3*2]*V[2 + 3*0]); + Vinv[1 + 3*2] = 1.0/det * (V[0 + 3*2]*V[1 + 3*0] - V[0 + 3*0]*V[1 + 3*2]); + Vinv[2 + 3*0] = 1.0/det * (V[1 + 3*0]*V[2 + 3*1] - V[1 + 3*1]*V[2 + 3*0]); + Vinv[2 + 3*1] = 1.0/det * (V[0 + 3*1]*V[2 + 3*0] - V[0 + 3*0]*V[2 + 3*1]); + Vinv[2 + 3*2] = 1.0/det * (V[0 + 3*0]*V[1 + 3*1] - V[0 + 3*1]*V[1 + 3*0]); } double structure_chunk::get_chi1inv_at_pt(component c, direction d, int idx, double omega) const { double res = 0.0; if (is_mine()){ + if (omega == 0) + return chi1inv[c][d] ? chi1inv[c][d][idx] : (d == component_direction(c) ? 1.0 : 0); // ----------------------------------------------------------------- // // ---- Step 1: Get instantaneous chi1 tensor ---------------------- // ----------------------------------------------------------------- // @@ -268,22 +270,23 @@ double structure_chunk::get_chi1inv_at_pt(component c, direction d, int idx, dou my_stuff = B_stuff; } - std::vector chi1_inv_tensor(9,0); - std::vector chi1_tensor(9,0); + std::complex chi1_inv_tensor[9] = {std::complex(1, 0),std::complex(0, 0),std::complex(0, 0), + std::complex(0, 0),std::complex(1, 0),std::complex(0, 0), + std::complex(0, 0),std::complex(0, 0),std::complex(1, 0) + }; + std::complex chi1_tensor[9] = {std::complex(1, 0),std::complex(0, 0),std::complex(0, 0), + std::complex(0, 0),std::complex(1, 0),std::complex(0, 0), + std::complex(0, 0),std::complex(0, 0),std::complex(1, 0) + }; - // Set up the chi1inv tensor + // Set up the chi1inv tensor with the DC components for (int com_it=0; com_it<3;com_it++){ for (int dir_int=0;dir_int<3;dir_int++){ if (chi1inv[comp_list[com_it]][dir_int] ) chi1_inv_tensor[com_it + 3*dir_int] = chi1inv[comp_list[com_it]][dir_int][idx]; - else if(dir_int == component_direction(comp_list[com_it])) - chi1_inv_tensor[com_it + 3*dir_int] = 1; - else - chi1_inv_tensor[com_it + 3*dir_int] = 0; } } - matrix_invert(chi1_tensor, chi1_inv_tensor); // We have the inverse, so let's invert it. // ----------------------------------------------------------------- // @@ -293,7 +296,7 @@ double structure_chunk::get_chi1inv_at_pt(component c, direction d, int idx, dou // loop over tensor elements for (int com_it=0; com_it<3;com_it++){ for (int dir_int=0;dir_int<3;dir_int++){ - std::complex eps(chi1_tensor[com_it + 3*dir_int],0.0); + std::complex eps = chi1_tensor[com_it + 3*dir_int]; component cc = comp_list[com_it]; direction dd = (direction)dir_int; // Loop through and add up susceptibility contributions @@ -326,7 +329,7 @@ double structure_chunk::get_chi1inv_at_pt(component c, direction d, int idx, dou // ----------------------------------------------------------------- // matrix_invert(chi1_inv_tensor, chi1_tensor); // We have the inverse, so let's invert it. - res = chi1_inv_tensor[component_index(c) + 3*d]; + res = chi1_inv_tensor[component_index(c) + 3*d].real(); } return res; } From 8590db1325257c98bd1b358b7fb203f606e16b8a Mon Sep 17 00:00:00 2001 From: smartalecH Date: Wed, 26 Jun 2019 12:00:05 -0600 Subject: [PATCH 25/25] det fix --- src/monitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monitor.cpp b/src/monitor.cpp index 6cb49f6c3..0996c77b9 100644 --- a/src/monitor.cpp +++ b/src/monitor.cpp @@ -232,7 +232,7 @@ void matrix_invert(std::complex (&Vinv)[9], std::complex (&V)[9] V[0 + 3*1] * (V[0 + 3*1]*V[2 + 3*2] - V[1 + 3*2]*V[0 + 3*2]) + V[0 + 3*2] * (V[0 + 3*1]*V[1 + 3*2] - V[1 + 3*1]*V[0 + 3*2])); - if (det.real() == 0 && det.imag() == 0) abort("meep: Matrix is singular, aborting.\n"); + if (det == 0.0) abort("meep: Matrix is singular, aborting.\n"); Vinv[0 + 3*0] = 1.0/det * (V[1 + 3*1]*V[2 + 3*2] - V[1 + 3*2]*V[2 + 3*1]); Vinv[0 + 3*1] = 1.0/det * (V[0 + 3*2]*V[2 + 3*1] - V[0 + 3*1]*V[2 + 3*2]);