diff --git a/python/adjoint/objective.py b/python/adjoint/objective.py index 6a0316931..ac7d16d11 100644 --- a/python/adjoint/objective.py +++ b/python/adjoint/objective.py @@ -179,7 +179,7 @@ def place_adjoint_source(self, dJ): *(np.eye(3)[self._monitor.normal_direction] * np.abs(center_frequency))) eig_kpoint = -1 * direction if self.forward else direction - + if self._frequencies.size == 1: amp = da_dE * dJ * scale src = time_src diff --git a/python/adjoint/optimization_problem.py b/python/adjoint/optimization_problem.py index 509f8da27..e86fbe219 100644 --- a/python/adjoint/optimization_problem.py +++ b/python/adjoint/optimization_problem.py @@ -119,13 +119,11 @@ def __call__(self, rho_vector=None, need_value=True, need_gradient=True, beta=No print("Starting forward run...") self.forward_run() print("Starting adjoint run...") - self.D_a = [] self.adjoint_run() print("Calculating gradient...") self.calculate_gradient() elif self.current_state == "FWD": print("Starting adjoint run...") - self.D_a = [] self.adjoint_run() print("Calculating gradient...") self.calculate_gradient() @@ -168,7 +166,7 @@ def prepare_forward_run(self): self.forward_monitors.append(m.register_monitors(self.frequencies)) # register design region - self.design_region_monitors = utils.install_design_region_monitors( + self.forward_design_region_monitors = utils.install_design_region_monitors( self.sim, self.design_regions, self.frequencies, self.decimation_factor ) @@ -192,10 +190,7 @@ def forward_run(self): self.f0 = [fi(*self.results_list) for fi in self.objective_functions] if len(self.f0) == 1: self.f0 = self.f0[0] - - # Store forward fields for each set of design variables in array - self.D_f = utils.gather_design_region_fields(self.sim,self.design_region_monitors,self.frequencies) - + # store objective function evaluation in memory self.f_bank.append(self.f0) @@ -225,19 +220,22 @@ def adjoint_run(self): # flip the k point if self.sim.k_point: - self.sim.k_point *= -1 + self.sim.change_k_point(-1*self.sim.k_point) + self.adjoint_design_region_monitors = [] for ar in range(len(self.objective_functions)): # Reset the fields - self.sim.reset_meep() + self.sim.restart_fields() + self.sim.clear_dft_monitors() # Update the sources self.sim.change_sources(self.adjoint_sources[ar]) - # register design flux - self.design_region_monitors = utils.install_design_region_monitors( + # register design dft fields + self.adjoint_design_region_monitors.append(utils.install_design_region_monitors( self.sim, self.design_regions, self.frequencies, self.decimation_factor - ) + )) + self.sim._evaluate_dft_objects() # Adjoint run self.sim.run(until_after_sources=mp.stop_when_dft_decayed( @@ -246,9 +244,6 @@ def adjoint_run(self): self.maximum_run_time )) - # Store adjoint fields for each design set of design variables - self.D_a.append(utils.gather_design_region_fields(self.sim,self.design_region_monitors,self.frequencies)) - # reset the m number if utils._check_if_cylindrical(self.sim): self.sim.m = -self.sim.m @@ -264,8 +259,8 @@ def calculate_gradient(self): self.gradient = [[ dr.get_gradient( self.sim, - self.D_a[ar][dri], - self.D_f[dri], + self.adjoint_design_region_monitors[ar][dri], + self.forward_design_region_monitors[dri], self.frequencies, self.finite_difference_step ) for dri, dr in enumerate(self.design_regions) diff --git a/python/adjoint/utils.py b/python/adjoint/utils.py index e6a5d1581..ef4a94aee 100644 --- a/python/adjoint/utils.py +++ b/python/adjoint/utils.py @@ -43,53 +43,19 @@ def update_beta(self,beta): def get_gradient(self, sim, fields_a, fields_f, frequencies, finite_difference_step): num_freqs = onp.array(frequencies).size - shapes = [] '''We have the option to linearly scale the gradients up front using the scalegrad parameter (leftover from MPB API). Not currently needed for any existing feature (but available for future use)''' scalegrad = 1 - for component_idx, component in enumerate(_compute_components(sim)): - '''We need to correct for the rare cases that get_dft_array - returns a singleton element for the forward and adjoint fields. - This only occurs when we are in 2D and only working with a particular - polarization (as the other fields are never stored). For example, the - 2D in-plane polarization consists of a single scalar Ez field - (technically, meep doesn't store anything for these cases, but get_dft_array - still returns 0). - - Our get_gradient algorithm, however, requires we pass an array of - zeros with the proper shape as the design_region.''' - spatial_shape = sim.get_array_slice_dimensions(component, vol=self.volume)[0] - if (fields_a[component_idx][0,...].size == 1): - fields_a[component_idx] = onp.zeros(onp.insert(spatial_shape,0,num_freqs), - dtype=onp.complex64 if mp.is_single_precision() else onp.complex128) - fields_f[component_idx] = onp.zeros(onp.insert(spatial_shape,0,num_freqs), - dtype=onp.complex64 if mp.is_single_precision() else onp.complex128) - if _check_if_cylindrical(sim): - '''For some reason, get_dft_array returns the field - components in a different order than the convention used - throughout meep. So, we reorder them here so we can use - the same field macros later in our get_gradient function. - ''' - fields_a[component_idx] = onp.transpose(fields_a[component_idx],(_FREQ_AXIS,_RHO_AXIS,_PHI_AXIS,_Z_AXIS)) - fields_f[component_idx] = onp.transpose(fields_f[component_idx],(_FREQ_AXIS,_RHO_AXIS,_PHI_AXIS,_Z_AXIS)) - shapes.append(fields_a[component_idx].shape) - fields_a[component_idx] = fields_a[component_idx].flatten(order='C') - fields_f[component_idx] = fields_f[component_idx].flatten(order='C') - shapes = onp.asarray(shapes).flatten(order='C') - fields_a = onp.concatenate(fields_a) - fields_f = onp.concatenate(fields_f) - grad = onp.zeros((num_freqs, self.num_design_params)) # preallocate - geom_list = sim.geometry - f = sim.fields vol = sim._fit_volume_to_simulation(self.volume) - # compute the gradient - mp._get_gradient(grad,scalegrad,fields_a,fields_f, - sim.gv,vol.swigobj,onp.array(frequencies), - sim.geps,shapes,finite_difference_step) + mp._get_gradient(grad,scalegrad, + fields_a[0].swigobj,fields_a[1].swigobj,fields_a[2].swigobj, + fields_f[0].swigobj,fields_f[1].swigobj,fields_f[2].swigobj, + sim.gv,vol.swigobj,onp.array(frequencies), + sim.geps,finite_difference_step) return onp.squeeze(grad).T def _check_if_cylindrical(sim): @@ -149,18 +115,19 @@ def install_design_region_monitors( simulation: mp.Simulation, design_regions: List[DesignRegion], frequencies: List[float], - decimation_factor: int = 0 + decimation_factor: int = 0, ) -> List[mp.DftFields]: """Installs DFT field monitors at the design regions of the simulation.""" - design_region_monitors = [ + design_region_monitors = [[ simulation.add_dft_fields( - _compute_components(simulation), + [comp], frequencies, where=design_region.volume, yee_grid=True, - decimation_factor=decimation_factor - ) for design_region in design_regions - ] + decimation_factor=decimation_factor, + persist=True + ) for comp in _compute_components(simulation) + ] for design_region in design_regions ] return design_region_monitors diff --git a/python/adjoint/wrapper.py b/python/adjoint/wrapper.py index 3fad98c10..8ecd38a3f 100644 --- a/python/adjoint/wrapper.py +++ b/python/adjoint/wrapper.py @@ -143,7 +143,7 @@ def _run_fwd_simulation(self, design_variables): self.simulation.reset_meep() self.simulation.change_sources(self.sources) utils.register_monitors(self.monitors, self.frequencies) - design_region_monitors = utils.install_design_region_monitors( + fwd_design_region_monitors = utils.install_design_region_monitors( self.simulation, self.design_regions, self.frequencies, @@ -156,13 +156,8 @@ def _run_fwd_simulation(self, design_variables): self.simulation.run(**sim_run_args) monitor_values = utils.gather_monitor_values(self.monitors) - fwd_fields = utils.gather_design_region_fields( - self.simulation, - design_region_monitors, - self.frequencies, - ) return (jnp.asarray(monitor_values), - jax.tree_map(jnp.asarray, fwd_fields)) + fwd_design_region_monitors) def _run_adjoint_simulation(self, monitor_values_grad): """Runs adjoint simulation, returning design region fields.""" @@ -172,9 +167,12 @@ def _run_adjoint_simulation(self, monitor_values_grad): 'regions are present.') adjoint_sources = utils.create_adjoint_sources(self.monitors, monitor_values_grad) - self.simulation.reset_meep() + # TODO refactor with optimization_problem.py # + self.simulation.restart_fields() + self.simulation.clear_dft_monitors() self.simulation.change_sources(adjoint_sources) - design_region_monitors = utils.install_design_region_monitors( + # # + adj_design_region_monitors = utils.install_design_region_monitors( self.simulation, self.design_regions, self.frequencies, @@ -186,11 +184,7 @@ def _run_adjoint_simulation(self, monitor_values_grad): } self.simulation.run(**sim_run_args) - return utils.gather_design_region_fields( - self.simulation, - design_region_monitors, - self.frequencies, - ) + return adj_design_region_monitors def _calculate_vjps( self, @@ -221,20 +215,16 @@ def simulate(design_variables: List[jnp.ndarray]) -> jnp.ndarray: def _simulate_fwd(design_variables): """Runs forward simulation, returning monitor values and fields.""" - monitor_values, fwd_fields = self._run_fwd_simulation( + monitor_values, self.fwd_design_region_monitors = self._run_fwd_simulation( design_variables) design_variable_shapes = [x.shape for x in design_variables] - return monitor_values, (fwd_fields, design_variable_shapes) + return monitor_values, (design_variable_shapes) def _simulate_rev(res, monitor_values_grad): """Runs adjoint simulation, returning VJP of design wrt monitor values.""" - fwd_fields = jax.tree_map( - lambda x: onp.asarray(x, - dtype=onp.complex64 if mp.is_single_precision() else onp.complex128), - res[0]) - design_variable_shapes = res[1] - adj_fields = self._run_adjoint_simulation(monitor_values_grad) - vjps = self._calculate_vjps(fwd_fields, adj_fields, + design_variable_shapes = res + self.adj_design_region_monitors = self._run_adjoint_simulation(monitor_values_grad) + vjps = self._calculate_vjps(self.fwd_design_region_monitors, self.adj_design_region_monitors, design_variable_shapes) return ([jnp.asarray(vjp) for vjp in vjps], ) diff --git a/python/geom.py b/python/geom.py index 1b33436f8..908337089 100755 --- a/python/geom.py +++ b/python/geom.py @@ -539,7 +539,7 @@ def __init__(self, medium2, weights=None, grid_type="U_DEFAULT", - do_averaging=False, + do_averaging=True, beta=0, eta=0.5, damping=0): diff --git a/python/meep.i b/python/meep.i index de03a11f1..c629e67a8 100644 --- a/python/meep.i +++ b/python/meep.i @@ -843,9 +843,12 @@ meep::volume_list *make_volume_list(const meep::volume &v, int c, //-------------------------------------------------- %inline %{ -void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObject *fields_f, +void _get_gradient(PyObject *grad, double scalegrad, + meep::dft_fields *fields_a_0, meep::dft_fields *fields_a_1, meep::dft_fields *fields_a_2, + meep::dft_fields *fields_f_0, meep::dft_fields *fields_f_1, meep::dft_fields *fields_f_2, meep::grid_volume *grid_volume, meep::volume *where, PyObject *frequencies, - meep_geom::geom_epsilon *geps, PyObject *fields_shapes, double fd_step) { + meep_geom::geom_epsilon *geps, double fd_step) { + // clean the gradient array PyArrayObject *pao_grad = (PyArrayObject *)grad; if (!PyArray_Check(pao_grad)) meep::abort("grad parameter must be numpy array."); @@ -854,25 +857,11 @@ void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObjec double *grad_c = (double *)PyArray_DATA(pao_grad); npy_intp ng = PyArray_DIMS(pao_grad)[1]; // number of design parameters - // clean the adjoint fields array - PyArrayObject *pao_fields_a = (PyArrayObject *)fields_a; - if (!PyArray_Check(pao_fields_a)) meep::abort("adjoint fields parameter must be numpy array."); - if (!PyArray_ISCARRAY(pao_fields_a)) meep::abort("Numpy adjoint fields array must be C-style contiguous."); - if (PyArray_NDIM(pao_fields_a) !=1) {meep::abort("Numpy adjoint fields array must have 1 dimension.");} - std::complex *fields_a_c = (std::complex *)PyArray_DATA(pao_fields_a); - - // clean the forward fields array - PyArrayObject *pao_fields_f = (PyArrayObject *)fields_f; - if (!PyArray_Check(pao_fields_f)) meep::abort("forward fields parameter must be numpy array."); - if (!PyArray_ISCARRAY(pao_fields_f)) meep::abort("Numpy forward fields array must be C-style contiguous."); - if (PyArray_NDIM(pao_fields_f) !=1) {meep::abort("Numpy forward fields array must have 1 dimension.");} - std::complex *fields_f_c = (std::complex *)PyArray_DATA(pao_fields_f); - - // clean shapes array - PyArrayObject *pao_fields_shapes = (PyArrayObject *)fields_shapes; - if (!PyArray_Check(pao_fields_shapes)) meep::abort("fields shape parameter must be numpy array."); - if (!PyArray_ISCARRAY(pao_fields_shapes)) meep::abort("Numpy fields shape array must be C-style contiguous."); - size_t *fields_shapes_c = (size_t *)PyArray_DATA(pao_fields_shapes); + // clean the adjoint fields object + std::vector adjoint_fields = {fields_a_0,fields_a_1,fields_a_2}; + + // clean the forward fields object + std::vector forward_fields = {fields_f_0,fields_f_1,fields_f_2}; // clean the frequencies array PyArrayObject *pao_freqs = (PyArrayObject *)frequencies; @@ -883,7 +872,8 @@ void _get_gradient(PyObject *grad, double scalegrad, PyObject *fields_a, PyObjec if (PyArray_DIMS(pao_grad)[0] != nf) meep::abort("Numpy grad array is allocated for %td frequencies; it should be allocated for %td.",PyArray_DIMS(pao_grad)[0],nf); // calculate the gradient - meep_geom::material_grids_addgradient(grad_c,ng,fields_a_c,fields_f_c,fields_shapes_c,frequencies_c,scalegrad,*grid_volume,*where,geps,fd_step); + meep_geom::material_grids_addgradient(grad_c,ng,nf,adjoint_fields,forward_fields,frequencies_c,scalegrad,*grid_volume,*where,geps,fd_step); + } %} diff --git a/python/simulation.py b/python/simulation.py index ac36bb232..318dcf72d 100644 --- a/python/simulation.py +++ b/python/simulation.py @@ -2544,9 +2544,10 @@ def _evaluate_dft_objects(self): if dft.swigobj is None: dft.swigobj = dft.func(*dft.args) + def add_dft_fields(self, *args, **kwargs): """ - `add_dft_fields(cs, fcen, df, nfreq, freq, where=None, center=None, size=None, yee_grid=False, decimation_factor=0)` ##sig + `add_dft_fields(cs, fcen, df, nfreq, freq, where=None, center=None, size=None, yee_grid=False, decimation_factor=0, persist=False)` ##sig Given a list of field components `cs`, compute the Fourier transform of these fields for `nfreq` equally spaced frequencies covering the frequency range @@ -2573,18 +2574,19 @@ def add_dft_fields(self, *args, **kwargs): size = kwargs.get('size', None) yee_grid = kwargs.get('yee_grid', False) decimation_factor = kwargs.get('decimation_factor', 0) + persist = kwargs.get('persist', False) center_v3 = Vector3(*center) if center is not None else None size_v3 = Vector3(*size) if size is not None else None use_centered_grid = not yee_grid dftf = DftFields(self._add_dft_fields, [ components, where, center_v3, size_v3, freq, use_centered_grid, - decimation_factor + decimation_factor,persist ]) self.dft_objects.append(dftf) return dftf def _add_dft_fields(self, components, where, center, size, freq, - use_centered_grid, decimation_factor): + use_centered_grid, decimation_factor, persist): if self.fields is None: self.init_sim() try: @@ -2592,7 +2594,7 @@ def _add_dft_fields(self, components, where, center, size, freq, except ValueError: where = self.fields.total_volume() return self.fields.add_dft_fields(components, where, freq, - use_centered_grid, decimation_factor) + use_centered_grid, decimation_factor, persist) def output_dft(self, dft_fields, fname): """ @@ -3829,6 +3831,17 @@ def restart_fields(self): else: self._is_initialized = False self.init_sim() + + def clear_dft_monitors(self): + """ + Remove all of the dft monitors from the simulation. + """ + for m in self.dft_objects: + if not (isinstance(m,DftFields) and (m.chunks) and (m.chunks.persist)): + m.remove() + self.fields.clear_dft_monitors() + + self.dft_objects = [] def run(self, *step_funcs, **kwargs): """ diff --git a/python/tests/test_adjoint_cyl.py b/python/tests/test_adjoint_cyl.py index 84667b8ea..ab6a3307d 100644 --- a/python/tests/test_adjoint_cyl.py +++ b/python/tests/test_adjoint_cyl.py @@ -143,7 +143,7 @@ def test_adjoint_solver_cyl_n2f_fields(self): adj_scale = (dp[None,:]@adjsol_grad).flatten() fd_grad = S12_perturbed-S12_unperturbed print("Directional derivative -- adjoint solver: {}, FD: {}".format(adj_scale,fd_grad)) - tol = 0.1 if mp.is_single_precision() else 0.01 + tol = 0.2 if mp.is_single_precision() else 0.1 self.assertClose(adj_scale,fd_grad,epsilon=tol) diff --git a/python/tests/test_adjoint_jax.py b/python/tests/test_adjoint_jax.py index 89b073866..6513369a1 100644 --- a/python/tests/test_adjoint_jax.py +++ b/python/tests/test_adjoint_jax.py @@ -18,6 +18,9 @@ # The tolerance for the adjoint and finite difference gradient comparison _TOL = 0.1 if mp.is_single_precision() else 0.025 +# We expect 3 design region monitor pointers (one for each field component) +_NUM_DES_REG_MON = 3 + mp.verbosity(0) @@ -149,32 +152,15 @@ def test_mode_monitor_helpers(self): self.assertEqual(monitor_values.dtype, onp.complex128) self.assertEqual(monitor_values.shape, (len(self.monitors), len(self.frequencies))) - - def test_design_region_monitor_helpers(self): - design_region_monitors = mpa.utils.install_design_region_monitors( + + def test_dist_dft_pointers(self): + fwd_design_region_monitors = mpa.utils.install_design_region_monitors( self.simulation, self.design_regions, self.frequencies, ) - self.simulation.run(until=100) - design_region_fields = mpa.utils.gather_design_region_fields( - self.simulation, - design_region_monitors, - self.frequencies, - ) - - self.assertIsInstance(design_region_fields, list) - self.assertEqual(len(design_region_fields), len(self.design_regions)) - - self.assertIsInstance(design_region_fields[0], list) - self.assertEqual(len(design_region_fields[0]), - len(mpa.utils._ADJOINT_FIELD_COMPONENTS)) + self.assertEqual(len(fwd_design_region_monitors[0]),_NUM_DES_REG_MON) - for value in design_region_fields[0]: - self.assertIsInstance(value, onp.ndarray) - self.assertEqual(value.ndim, 4) # dims: freq, x, y, pad - self.assertEqual(value.dtype, - onp.complex64 if mp.is_single_precision() else onp.complex128) class WrapperTest(ApproxComparisonTestCase): diff --git a/python/tests/test_adjoint_solver.py b/python/tests/test_adjoint_solver.py index 082c152f2..c0edb934f 100644 --- a/python/tests/test_adjoint_solver.py +++ b/python/tests/test_adjoint_solver.py @@ -47,7 +47,7 @@ size=mp.Vector3(mp.inf,w,mp.inf))] fcen = 1/1.55 -df = 0.23*fcen +df = 0.2*fcen wvg_source = [mp.EigenModeSource(src=mp.GaussianSource(fcen,fwidth=df), center=mp.Vector3(-0.5*sxy+dpml+0.1,0), size=mp.Vector3(0,sxy-2*dpml), @@ -507,29 +507,38 @@ def test_damping(self): def test_offdiagonal(self): print("*** TESTING OFFDIAGONAL COMPONENTS ***") + filt = lambda x: mpa.conic_filter(x.reshape((Nx,Ny)),0.25,design_region_size.x,design_region_size.y,design_region_resolution).flatten() ## test the single frequency and multi frequency case for frequencies in [[fcen], [1/1.58, fcen, 1/1.53]]: ## compute gradient using adjoint solver - adjsol_obj, adjsol_grad = adjoint_solver(p, MonitorObject.EIGENMODE, frequencies, sapphire) + adjsol_obj, adjsol_grad = adjoint_solver(filt(p), MonitorObject.EIGENMODE, frequencies, sapphire) + + ## backpropagate the gradient + if len(frequencies) > 1: + bp_adjsol_grad = np.zeros(adjsol_grad.shape) + for i in range(len(frequencies)): + bp_adjsol_grad[:,i] = tensor_jacobian_product(filt,0)(p,adjsol_grad[:,i]) + else: + bp_adjsol_grad = tensor_jacobian_product(filt,0)(p,adjsol_grad) ## compute unperturbed S12 - S12_unperturbed = forward_simulation(p, MonitorObject.EIGENMODE, frequencies, sapphire) + S12_unperturbed = forward_simulation(filt(p), MonitorObject.EIGENMODE, frequencies, sapphire) ## compare objective results print("S12 -- adjoint solver: {}, traditional simulation: {}".format(adjsol_obj,S12_unperturbed)) self.assertClose(adjsol_obj,S12_unperturbed,epsilon=1e-6) ## compute perturbed S12 - S12_perturbed = forward_simulation(p+dp, MonitorObject.EIGENMODE, frequencies, sapphire) + S12_perturbed = forward_simulation(filt(p+dp), MonitorObject.EIGENMODE, frequencies, sapphire) ## compare gradients - if adjsol_grad.ndim < 2: - adjsol_grad = np.expand_dims(adjsol_grad,axis=1) - adj_scale = (dp[None,:]@adjsol_grad).flatten() + if bp_adjsol_grad.ndim < 2: + bp_adjsol_grad = np.expand_dims(bp_adjsol_grad,axis=1) + adj_scale = (dp[None,:]@bp_adjsol_grad).flatten() fd_grad = S12_perturbed-S12_unperturbed print("Directional derivative -- adjoint solver: {}, FD: {}".format(adj_scale,fd_grad)) - tol = 0.05 if mp.is_single_precision() else 0.04 + tol = 0.1 if mp.is_single_precision() else 0.04 self.assertClose(adj_scale,fd_grad,epsilon=tol) if __name__ == '__main__': unittest.main() diff --git a/python/visualization.py b/python/visualization.py index 7c37ac9b3..609cbb600 100644 --- a/python/visualization.py +++ b/python/visualization.py @@ -38,7 +38,7 @@ default_field_parameters = { 'interpolation':'spline36', 'cmap':'RdBu', - 'alpha':0.6, + 'alpha':0.8, 'post_process':np.real } @@ -86,7 +86,7 @@ def filter_dict(dict_to_filter, func_with_kwargs): try: # Python3 ... sig = inspect.signature(func_with_kwargs) - filter_keys = [param.name for param in sig.parameters.values() if param.kind == param.POSITIONAL_OR_KEYWORD] + filter_keys = [param.name for param in sig.parameters.values()] except: # Python2 ... filter_keys = inspect.getargspec(func_with_kwargs)[0] @@ -533,7 +533,7 @@ def plot_fields(sim, ax=None, fields=None, output_plane=None, field_parameters=N field_parameters = default_field_parameters if field_parameters is None else dict(default_field_parameters, **field_parameters) # user specifies a field component - if fields in [mp.Ex, mp.Ey, mp.Ez, mp.Er, mp.Ep, mp.Hx, mp.Hy, mp.Hz]: + if fields in [mp.Ex, mp.Ey, mp.Ez, mp.Er, mp.Ep, mp.Dx, mp.Dy, mp.Dz, mp.Hx, mp.Hy, mp.Hz]: # Get domain measurements sim_center, sim_size = get_2D_dimensions(sim, output_plane) diff --git a/src/dft.cpp b/src/dft.cpp index 8a4cade91..6b37de40f 100644 --- a/src/dft.cpp +++ b/src/dft.cpp @@ -51,6 +51,7 @@ struct dft_chunk_data { // for passing to field::loop_in_chunks as void* bool empty_dim[5]; dft_chunk *dft_chunks; int decimation_factor; + bool persist; }; dft_chunk::dft_chunk(fields_chunk *fc_, ivec is_, ivec ie_, vec s0_, vec s1_, vec e0_, vec e1_, @@ -70,8 +71,23 @@ dft_chunk::dft_chunk(fields_chunk *fc_, ivec is_, ivec ie_, vec s0_, vec s1_, ve dV0 = dV0_; dV1 = dV1_; + persist = data->persist; + c = c_; + /* for adjoint calculations, we want to pad + (or expand) the dimensions of the dft region + to account for boundary effects. We will pad + by 1 pixel in each dimension, while ensuring + we don't step outside of the chunk loop itself + */ + if(persist){ + is_old = is_; + ie_old = ie_; + is = max(is-one_ivec(fc->gv.dim)*2,fc->gv.little_corner()); + ie = min(ie+one_ivec(fc->gv.dim)*2,fc->gv.big_corner()); + } + if (use_centered_grid) fc->gv.yee2cent_offsets(c, avg1, avg2); else @@ -159,7 +175,7 @@ dft_chunk *fields::add_dft(component c, const volume &where, const double *freq, bool include_dV_and_interp_weights, complex stored_weight, dft_chunk *chunk_next, bool sqrt_dV_and_interp_weights, complex extra_weight, bool use_centered_grid, - int vc, int decimation_factor) { + int vc, int decimation_factor, bool persist) { if (coordinate_mismatch(gv.dim, c)) return NULL; /* If you call add_dft before adding sources, it will do nothing @@ -172,6 +188,7 @@ dft_chunk *fields::add_dft(component c, const volume &where, const double *freq, "add_dft"); dft_chunk_data data; + data.persist = persist; data.c = c; data.vc = vc; @@ -211,13 +228,13 @@ dft_chunk *fields::add_dft(component c, const volume &where, const double *freq, } dft_chunk *fields::add_dft(const volume_list *where, const std::vector &freq, - bool include_dV_and_interp_weights) { + bool include_dV_and_interp_weights, bool persist) { dft_chunk *chunks = 0; while (where) { if (is_derived(where->c)) meep::abort("derived_component invalid for dft"); complex stored_weight = where->weight; chunks = add_dft(component(where->c), where->v, freq, include_dV_and_interp_weights, - stored_weight, chunks); + stored_weight, chunks, persist); where = where->next; } return chunks; @@ -289,30 +306,50 @@ double fields::dft_norm() { am_now_working_on(Other); double sum = 0.0; for (int i = 0; i < num_chunks; i++) - if (chunks[i]->is_mine()) sum += chunks[i]->dft_norm2(); + if (chunks[i]->is_mine()) sum += chunks[i]->dft_norm2(gv); finished_working(); return std::sqrt(sum_to_all(sum)); } -double fields_chunk::dft_norm2() const { +double fields_chunk::dft_norm2(grid_volume fgv) const { double sum = 0.0; for (dft_chunk *cur = dft_chunks; cur; cur = cur->next_in_chunk) - sum += cur->norm2(); + sum += cur->norm2(fgv); return sum; } static double sqr(std::complex x) { return (x*std::conj(x)).real(); } -double dft_chunk::norm2() const { +double dft_chunk::norm2(grid_volume fgv) const { if (!fc->f[c][0]) return 0.0; double sum = 0.0; - size_t idx_dft = 0; - const int Nomega = omega.size(); - LOOP_OVER_IVECS(fc->gv, is, ie, idx) { - for (int i = 0; i < Nomega; ++i) - sum += sqr(dft[Nomega * idx_dft + i]); - idx_dft++; + size_t idx_dft; + const size_t Nomega = omega.size(); + /* looping over chunks that have been "expanded" + for adjoint calculations requires some care. Namely, + we want to make sure we don't double count the padding + and can replicate results with different chunk combinations. + */ + if (persist) { + grid_volume subgv = fgv.subvolume(is,ie,c); + LOOP_OVER_IVECS(subgv, is_old, ie_old, idx) { + for (size_t i = 0; i < Nomega; ++i) + sum += sqr(dft[Nomega * idx + i]); + } + } + /* note we place the if outside of the + loop to avoid branching. This routine gets + called a lot, so let's try to stay efficient + (at the expense of uglier code). + */ + else{ + LOOP_OVER_IVECS(fgv, is, ie, idx) { + idx_dft = IVEC_LOOP_COUNTER; + for (size_t i = 0; i < Nomega; ++i) + sum += sqr(dft[Nomega * idx_dft + i]); + } } + return sum; } @@ -842,7 +879,7 @@ void dft_fields::remove() { dft_fields fields::add_dft_fields(component *components, int num_components, const volume where, const double *freq, size_t Nfreq, bool use_centered_grid, - int decimation_factor) { + int decimation_factor, bool persist) { bool include_dV_and_interp_weights = false; bool sqrt_dV_and_interp_weights = false; // default option from meep.hpp (expose to user?) std::complex extra_weight = 1.0; // default option from meep.hpp (expose to user?) @@ -851,7 +888,7 @@ dft_fields fields::add_dft_fields(component *components, int num_components, con for (int nc = 0; nc < num_components; nc++) chunks = add_dft(components[nc], where, freq, Nfreq, include_dV_and_interp_weights, stored_weight, chunks, sqrt_dV_and_interp_weights, extra_weight, - use_centered_grid, 0, decimation_factor); + use_centered_grid, 0, decimation_factor, persist); return dft_fields(chunks, freq, Nfreq, where); } @@ -1382,6 +1419,16 @@ void fields::get_mode_mode_overlap(void *mode1_data, void *mode2_data, dft_flux get_overlap(mode1_data, mode2_data, flux, 0, overlaps); } +/* deregister all of the remaining dft monitors +from the fields object. Note that this does not +delete the underlying dft_chunks! (useful for +adjoint calculations, where we want to keep +the chunk data around) */ +void fields::clear_dft_monitors() { +for (int i = 0; i < num_chunks; i++) + if (chunks[i]->is_mine() && chunks[i]->dft_chunks) chunks[i]->dft_chunks = NULL; +} + // return the size of the dft monitor std::vector fields::dft_monitor_size(dft_fields fdft, const volume &where, component c){ ivec min_corner, max_corner; diff --git a/src/fields.cpp b/src/fields.cpp index e1b9f9318..966d5e5b1 100644 --- a/src/fields.cpp +++ b/src/fields.cpp @@ -181,7 +181,11 @@ fields_chunk::~fields_chunk() { delete[] f_rderiv_int; while (dft_chunks) { dft_chunk *nxt = dft_chunks->next_in_chunk; - delete dft_chunks; + // keep the dft chunk in memory for adjoint calculations + if (dft_chunks->persist) + dft_chunks->fc = NULL; + else + delete dft_chunks; dft_chunks = nxt; } FOR_FIELD_TYPES(ft) { diff --git a/src/meep.hpp b/src/meep.hpp index 54873e28b..3d09c6942 100644 --- a/src/meep.hpp +++ b/src/meep.hpp @@ -1127,7 +1127,7 @@ class dft_chunk { ~dft_chunk(); void update_dft(double time); - double norm2() const; + double norm2(grid_volume fgv) const; double maxomega() const; void scale_dft(std::complex scale); @@ -1148,6 +1148,10 @@ class dft_chunk { // the frequencies to loop_in_chunks std::vector omega; + // decide whether to "persist" after fields class + // is deleted (e.g. for adjoint calculations) + bool persist = false; + component c; // component to DFT (possibly transformed by symmetry) size_t N; // number of spatial points (on epsilon grid) @@ -1192,7 +1196,7 @@ class dft_chunk { // parameters passed from field_integrate: fields_chunk *fc; - ivec is, ie; + ivec is, ie, is_old, ie_old; vec s0, s1, e0, e1; double dV0, dV1; bool empty_dim[5]; // which directions correspond to empty dimensions in original volume @@ -1580,7 +1584,7 @@ class fields_chunk { void initialize_with_nth_tm(int n, double kz); // dft.cpp void update_dfts(double timeE, double timeH, int current_step); - double dft_norm2() const; + double dft_norm2(grid_volume fgv) const; double dft_maxfreq() const; int max_decimation() const; @@ -1977,26 +1981,26 @@ class fields { std::complex stored_weight = 1.0, dft_chunk *chunk_next = 0, bool sqrt_dV_and_interp_weights = false, std::complex extra_weight = 1.0, bool use_centered_grid = true, - int vc = 0, int decimation_factor = 0) { + int vc = 0, int decimation_factor = 0, bool persist = false) { return add_dft(c, where, linspace(freq_min, freq_max, Nfreq), include_dV_and_interp_weights, stored_weight, chunk_next, sqrt_dV_and_interp_weights, extra_weight, - use_centered_grid, vc, decimation_factor); + use_centered_grid, vc, decimation_factor, persist); } dft_chunk *add_dft(component c, const volume &where, const double *freq, size_t Nfreq, bool include_dV_and_interp_weights = true, std::complex stored_weight = 1.0, dft_chunk *chunk_next = 0, bool sqrt_dV_and_interp_weights = false, std::complex extra_weight = 1.0, bool use_centered_grid = true, - int vc = 0, int decimation_factor = 0); + int vc = 0, int decimation_factor = 0, bool persist = false); dft_chunk *add_dft(component c, const volume &where, const std::vector& freq, bool include_dV_and_interp_weights = true, std::complex stored_weight = 1.0, dft_chunk *chunk_next = 0, bool sqrt_dV_and_interp_weights = false, std::complex extra_weight = 1.0, bool use_centered_grid = true, - int vc = 0, int decimation_factor = 0) { + int vc = 0, int decimation_factor = 0, bool persist = false) { return add_dft(c, where, freq.data(), freq.size(), include_dV_and_interp_weights, stored_weight, chunk_next, sqrt_dV_and_interp_weights, extra_weight, use_centered_grid, vc, - decimation_factor); + decimation_factor, persist); } dft_chunk *add_dft_pt(component c, const vec &where, double freq_min, double freq_max, int Nfreq) { @@ -2006,15 +2010,16 @@ class fields { return add_dft(c, where, freq, false); } dft_chunk *add_dft(const volume_list *where, double freq_min, double freq_max, int Nfreq, - bool include_dV = true) { - return add_dft(where, linspace(freq_min, freq_max, Nfreq), include_dV); + bool include_dV = true, bool persist = false) { + return add_dft(where, linspace(freq_min, freq_max, Nfreq), include_dV, persist); } dft_chunk *add_dft(const volume_list *where, const std::vector &freq, - bool include_dV = true); + bool include_dV = true, bool persist = false); void update_dfts(); double dft_norm(); double dft_maxfreq() const; int max_decimation() const; + void clear_dft_monitors(); dft_flux add_dft_flux(const volume_list *where, const double *freq, size_t Nfreq, bool use_symmetry = true, bool centered_grid = true, @@ -2067,19 +2072,19 @@ class fields { dft_fields add_dft_fields(component *components, int num_components, const volume where, double freq_min, double freq_max, int Nfreq, - bool use_centered_grid = true, int decimation_factor = 0) { + bool use_centered_grid = true, int decimation_factor = 0, bool persist = false) { return add_dft_fields(components, num_components, where, linspace(freq_min, freq_max, Nfreq), - use_centered_grid, decimation_factor); + use_centered_grid, decimation_factor, persist); } dft_fields add_dft_fields(component *components, int num_components, const volume where, const std::vector &freq, bool use_centered_grid = true, - int decimation_factor = 0) { + int decimation_factor = 0, bool persist = false) { return add_dft_fields(components, num_components, where, freq.data(), freq.size(), - use_centered_grid, decimation_factor); + use_centered_grid, decimation_factor, persist); } dft_fields add_dft_fields(component *components, int num_components, const volume where, const double *freq, size_t Nfreq, bool use_centered_grid = true, - int decimation_factor = 0); + int decimation_factor = 0, bool persist = false); /********************************************************/ /* process_dft_component is an intermediate-level */ diff --git a/src/meep/vec.hpp b/src/meep/vec.hpp index 3077775da..cbf414ac1 100644 --- a/src/meep/vec.hpp +++ b/src/meep/vec.hpp @@ -349,16 +349,16 @@ _Pragma("omp parallel for collapse(2)") (loop_s3 != 0 && (loop_i3 == 0 || loop_i3 == loop_n3 - 1))) #define IVEC_LOOP_ILOC(gv, iloc) \ - ivec iloc((gv).dim); \ - iloc.set_direction(direction(loop_d1), loop_is1 + 2 * loop_i1); \ - iloc.set_direction(direction(loop_d2), loop_is2 + 2 * loop_i2); \ - iloc.set_direction(direction(loop_d3), loop_is3 + 2 * loop_i3) + meep::ivec iloc((gv).dim); \ + iloc.set_direction(meep::direction(loop_d1), loop_is1 + 2 * loop_i1); \ + iloc.set_direction(meep::direction(loop_d2), loop_is2 + 2 * loop_i2); \ + iloc.set_direction(meep::direction(loop_d3), loop_is3 + 2 * loop_i3) #define IVEC_LOOP_LOC(gv, loc) \ - vec loc((gv).dim); \ - loc.set_direction(direction(loop_d1), (0.5 * loop_is1 + loop_i1) * (gv).inva); \ - loc.set_direction(direction(loop_d2), (0.5 * loop_is2 + loop_i2) * (gv).inva); \ - loc.set_direction(direction(loop_d3), (0.5 * loop_is3 + loop_i3) * (gv).inva) + meep::vec loc((gv).dim); \ + loc.set_direction(meep::direction(loop_d1), (0.5 * loop_is1 + loop_i1) * (gv).inva); \ + loc.set_direction(meep::direction(loop_d2), (0.5 * loop_is2 + loop_i2) * (gv).inva); \ + loc.set_direction(meep::direction(loop_d3), (0.5 * loop_is3 + loop_i3) * (gv).inva) // integration weight for using LOOP_OVER_IVECS with field::integrate #define IVEC_LOOP_WEIGHT1x(s0, s1, e0, e1, i, n, dir) \ @@ -1006,8 +1006,8 @@ class grid_volume { public: grid_volume(){}; - grid_volume subvolume(ivec is, ivec ie); - void init_subvolume(ivec is, ivec ie); + grid_volume subvolume(ivec is, ivec ie, component c); + void init_subvolume(ivec is, ivec ie, component c); ndim dim; double a, inva /* = 1/a */; diff --git a/src/meepgeom.cpp b/src/meepgeom.cpp index 8d5b262f6..2c6dfe553 100644 --- a/src/meepgeom.cpp +++ b/src/meepgeom.cpp @@ -327,6 +327,18 @@ bool is_metal(meep::field_type ft, const material_type *material) { } } +bool has_offdiag(const medium_struct *material) { + if ((material->epsilon_offdiag.x.re != 0) || /* account for offdiagonal components */ + (material->epsilon_offdiag.y.re != 0) || + (material->epsilon_offdiag.z.re != 0) || + (material->epsilon_offdiag.x.im != 0) || + (material->epsilon_offdiag.y.im != 0) || + (material->epsilon_offdiag.z.im != 0)) + return true; + else + return false; +} + // computes the vector-Jacobian product of the gradient of the matgrid_val function v // with the Jacobian of the to_geom_box_coords function for geometric_object o vector3 to_geom_object_coords_VJP(vector3 v, const geometric_object *o) { @@ -776,7 +788,7 @@ static void material_epsmu(meep::field_type ft, material_type material, symm_mat symm_matrix *epsmu_inv) { material_data *md = material; - if (ft == meep::E_stuff) switch (md->which_subclass) { + if (ft == meep::E_stuff || ft == meep::D_stuff) switch (md->which_subclass) { case material_data::MEDIUM: case material_data::MATERIAL_FILE: @@ -2677,11 +2689,11 @@ std::complex get_material_gradient( geps->get_material_pt(md, r); int dir_idx = 0; - if (forward_c == meep::Ex || forward_c == meep::Er) + if (forward_c == meep::Dx || forward_c == meep::Dr) dir_idx = 0; - else if (forward_c == meep::Ey || forward_c == meep::Ep) + else if (forward_c == meep::Dy || forward_c == meep::Dp) dir_idx = 1; - else if (forward_c == meep::Ez) + else if (forward_c == meep::Dz) dir_idx = 2; else meep::abort("Invalid adjoint field component"); @@ -2853,188 +2865,170 @@ void material_grids_addgradient_point(double *v, vector3 p, double scalegrad, ge } } -/**********************************************************/ -/* Some gradient helper routines */ -/**********************************************************/ - -#define LOOP_OVER_DIRECTIONS_BACKWARDS(dim, d) \ - for (meep::direction d = (meep::direction)(meep::stop_at_direction(dim)-1), \ - loop_stop_directi = meep::start_at_direction(dim); \ - d >= loop_stop_directi; d = (meep::direction)(d - 1)) - -void set_strides(meep::ndim dim, ptrdiff_t *the_stride,const meep::ivec c1,const meep::ivec c2) { - FOR_DIRECTIONS(d) { the_stride[d] = 1;} - meep::ivec n_s = (c2 - c1)/2 + 1; - LOOP_OVER_DIRECTIONS_BACKWARDS(dim,d) { - ptrdiff_t current_stride = 1; - LOOP_OVER_DIRECTIONS_BACKWARDS(dim,d_i) { - if (d_i==d){ - the_stride[d] = current_stride; - break; - } - current_stride *= n_s.in_direction(d_i); - } - } -} - -ptrdiff_t get_idx_from_ivec(meep::ndim dim, meep::ivec c1, ptrdiff_t *the_stride, meep::ivec v){ - ptrdiff_t idx = 0; - meep::ivec diff = ((v-c1) / 2); - LOOP_OVER_DIRECTIONS(dim,d) { - idx += diff.in_direction(d)*the_stride[d]; - } - return idx; -} - -void material_grids_addgradient(double *v, size_t ng, std::complex *fields_a, - std::complex *fields_f, size_t fields_shapes[12], +void material_grids_addgradient(double *v, size_t ng, size_t nf, std::vector fields_a, std::vector fields_f, double *frequencies, double scalegrad, meep::grid_volume &gv, meep::volume &where, geom_epsilon *geps, double du) { - -// only loop over field components relevant to our simulation (in the proper order) -#define FOR_MY_COMPONENTS(c1) FOR_ELECTRIC_COMPONENTS(c1) if (!coordinate_mismatch(gv.dim, component_direction(c1))) - - /* poach some logic from loop_in_chunks - that ensures we loop over the same grid - points that the DFT points lie on. */ - meep::ivec *is = new meep::ivec[3]; - meep::ivec *ie = new meep::ivec[3]; - int c_i = 0; - FOR_MY_COMPONENTS(cgrid) { - meep::vec yee_c(gv.yee_shift(meep::Centered) - gv.yee_shift(cgrid)); - meep::ivec iyee_c(gv.iyee_shift(meep::Centered) - gv.iyee_shift(cgrid)); - meep::volume wherec(where + yee_c); - is[c_i] = meep::ivec(meep::fields::vec2diel_floor(wherec.get_min_corner(), gv.a, zero_ivec(gv.dim))); - ie[c_i] = meep::ivec(meep::fields::vec2diel_ceil(wherec.get_max_corner(), gv.a, zero_ivec(gv.dim))); - c_i++; - } - //calculate the number of elements in an entire block (x,y,z) for each component - size_t nf = fields_shapes[0]; - size_t stride_row[3] = {1,1,1}; //first entry is a dummy entry to simplify indexing - for (int i=0;i<3;i++) { - for (int j=1;j<4;j++){ - stride_row[i] *= fields_shapes[4*i+j]; + /* ------------------------------------------------------------ */ + // initialize local gradient array + /* ------------------------------------------------------------ */ + double *v_local = new double[ng*nf]; + for (int i = 0; i < ng*nf; i++) { + v_local[i] = 0; + } + + /* ------------------------------------------------------------ */ + // store chunk info in vectors for simplicity + /* ------------------------------------------------------------ */ + std::vector> adjoint_dft_chunks; + std::vector> forward_dft_chunks; + for (int i=0;i<3;i++){ + std::vector c_adjoint_dft_chunks; + std::vector c_forward_dft_chunks; + meep::dft_chunk *current_adjoint_chunk = fields_a[i]->chunks; + meep::dft_chunk *current_forward_chunk = fields_f[i]->chunks; + while(current_adjoint_chunk) { + if (current_adjoint_chunk->omega.size() != nf) meep::abort("Supplied frequencies %d don't match dft frequencies %d\n",nf,current_adjoint_chunk->omega.size()); + c_adjoint_dft_chunks.push_back(current_adjoint_chunk); + current_adjoint_chunk = current_adjoint_chunk->next_in_dft; } + while(current_forward_chunk) { + if (current_forward_chunk->omega.size() != nf) meep::abort("Supplied frequencies %d don't match dft frequencies %d\n",nf,current_forward_chunk->omega.size()); + c_forward_dft_chunks.push_back(current_forward_chunk); + current_forward_chunk = current_forward_chunk->next_in_dft; + } + if (c_adjoint_dft_chunks.size() != c_forward_dft_chunks.size()) + meep::abort("The number of adjoint chunks (%ld) is not equal to the number of forward chunks (%ld).\n", + c_adjoint_dft_chunks.size(),c_forward_dft_chunks.size()); + adjoint_dft_chunks.push_back(c_adjoint_dft_chunks); + forward_dft_chunks.push_back(c_forward_dft_chunks); } - size_t c_start[3]; - c_start[0] = 0; - c_start[1] = nf*stride_row[0]; - c_start[2] = nf*(stride_row[0]+stride_row[1]); -// fields dimensions are (components, nfreqs, x, y, z) -#define GET_FIELDS(fields,comp,freq,idx) fields[c_start[comp] + freq*stride_row[comp] + idx] + /* ------------------------------------------------------------ */ + // Begin looping + /* ------------------------------------------------------------ */ - meep::ivec start_ivec; // loop over frequency - for (size_t f_i = 0; f_i < nf; ++f_i) { - int ci_adjoint = 0; - FOR_MY_COMPONENTS(adjoint_c) { - LOOP_OVER_IVECS(gv, is[ci_adjoint], ie[ci_adjoint], idx) { - size_t idx_fields = IVEC_LOOP_COUNTER; - meep::ivec ip = gv.iloc(adjoint_c,idx); - meep::vec p = gv.loc(adjoint_c,idx); - std::complex adj = GET_FIELDS(fields_a, ci_adjoint, f_i, idx_fields); - material_type md; - geps->get_material_pt(md, p); - if (!md->trivial) adj *= cond_cmp(adjoint_c,p,frequencies[f_i], geps); - double cyl_scale; - int ci_forward = 0; - FOR_MY_COMPONENTS(forward_c) { - /* we need to calculate the bounds of - the forward fields (in space) so that we - can properly index into the fields array - later */ - meep::ivec isf = is[ci_forward]; - meep::ivec ief = ie[ci_forward]; - // the actual starting index, as if we were running another LOOP_OVER_IVECS - ptrdiff_t idx0_f = (isf - (gv).little_corner()).yucky_val(0) / 2 * loop_s1 + \ - (isf - (gv).little_corner()).yucky_val(1) / 2 * loop_s2 + \ - (isf - (gv).little_corner()).yucky_val(2) / 2 * loop_s3; - start_ivec = gv.iloc(forward_c,idx0_f); - ptrdiff_t the_stride[5]; - set_strides(gv.dim, the_stride, isf,ief); - /**************************************/ - /* Main Routine */ - /**************************************/ - /********* compute -λᵀAᵤx *************/ - - /* trivial case, no interpolation/restriction needed */ - if (forward_c == adjoint_c) { - std::complex fwd = GET_FIELDS(fields_f, ci_forward, f_i, idx_fields); - cyl_scale = (gv.dim == meep::Dcyl) ? 2*p.r() : 1; // the pi is already factored in near2far.cpp - material_grids_addgradient_point( - v+ng*f_i, vec_to_vector3(p), scalegrad*cyl_scale, geps, + for (size_t f_i = 0; f_i < nf; f_i++) { + + // loop over adjoint components + for (int ci_adjoint=0; ci_adjoint<3; ci_adjoint++){ + int num_chunks = adjoint_dft_chunks[ci_adjoint].size(); + if (num_chunks == 0) continue; + + // loop over each chunk + for (int cur_chunk=0;cur_chunkc; + meep::grid_volume gv_adj = gv.subvolume(adj_chunk->is,adj_chunk->ie,adjoint_c); + + // loop over forward components + for (int ci_forward=0; ci_forward<3; ci_forward++){ + size_t num_f_chunks = forward_dft_chunks[ci_forward].size(); + if ((num_f_chunks == 0) || (cur_chunk>=num_f_chunks)) continue; + meep::dft_chunk* fwd_chunk = forward_dft_chunks[ci_forward][cur_chunk]; + meep::component forward_c = fwd_chunk->c; + meep::grid_volume gv_fwd = gv.subvolume(fwd_chunk->is,fwd_chunk->ie,forward_c); + + // loop over each point of interest + LOOP_OVER_IVECS(gv_adj,adj_chunk->is_old,adj_chunk->ie_old,idx_adj){ + double cyl_scale; + IVEC_LOOP_ILOC(gv_adj, ip); + IVEC_LOOP_LOC(gv_adj, p); + std::complex adj = adj_chunk->dft[nf*idx_adj+f_i]; + material_type md; + geps->get_material_pt(md, p); + /* if we have conductivities (e.g. for damping) + then we need to make sure we correctly account + for that here */ + if (!md->trivial) adj *= cond_cmp(adjoint_c,p,frequencies[f_i], geps); + + /**************************************/ + /* Main Routine */ + /**************************************/ + + /********* compute -λᵀAᵤx *************/ + + /* trivial case, no interpolation/restriction needed */ + if (forward_c == adjoint_c) { + std::complex fwd = fwd_chunk->dft[nf*idx_adj+f_i]; + cyl_scale = (gv.dim == meep::Dcyl) ? 2*p.r() : 1; // the pi is already factored in near2far.cpp + material_grids_addgradient_point( + v_local+ng*f_i, vec_to_vector3(p), scalegrad*cyl_scale, geps, adjoint_c, forward_c, fwd, adj, frequencies[f_i], gv, du); - /* anisotropic materials require interpolation/restriction */ - } else if (md->do_averaging || - md->medium_1.epsilon_offdiag.x.re != 0 || - md->medium_1.epsilon_offdiag.y.re != 0 || - md->medium_1.epsilon_offdiag.z.re != 0 || - md->medium_2.epsilon_offdiag.x.re != 0 || - md->medium_2.epsilon_offdiag.y.re != 0 || - md->medium_2.epsilon_offdiag.z.re != 0) { - /* we need to restrict the adjoint fields to - the two nodes of interest (which requires a factor - of 0.5 to scale), and interpolate the forward fields - to the same two nodes (which requires another factor of 0.5). - Then we perform our inner product at these nodes. - */ - std::complex fwd_avg, fwd1, fwd2; - ptrdiff_t fwd1_idx, fwd2_idx; - - //identify the first corner of the forward fields - meep::ivec fwd_p = ip + gv.iyee_shift(forward_c) - gv.iyee_shift(adjoint_c); - - //identify the other three corners - meep::ivec unit_a = unit_ivec(gv.dim,component_direction(adjoint_c)); - meep::ivec unit_f = unit_ivec(gv.dim,component_direction(forward_c)); - meep::ivec fwd_pa = (fwd_p + unit_a*2); - meep::ivec fwd_pf = (fwd_p - unit_f*2); - meep::ivec fwd_paf = (fwd_p + unit_a*2 - unit_f*2); - - //identify the two eps points - meep::ivec ieps1 = (fwd_p + fwd_pf) / 2; - meep::ivec ieps2 = (fwd_pa + fwd_paf) / 2; - - //operate on the first eps node - fwd1_idx = get_idx_from_ivec(gv.dim, start_ivec, the_stride, fwd_p); - fwd1 = 0.5 * meep::cdouble(GET_FIELDS(fields_f, ci_forward, f_i, fwd1_idx)); - fwd2_idx = get_idx_from_ivec(gv.dim, start_ivec, the_stride, fwd_pf); - fwd2 = 0.5 * meep::cdouble(GET_FIELDS(fields_f, ci_forward, f_i, fwd2_idx)); - fwd_avg = fwd1 + fwd2; - meep::vec eps1 = gv[ieps1]; - cyl_scale = (gv.dim == meep::Dcyl) ? eps1.r() : 1; - material_grids_addgradient_point( - v+ng*f_i, vec_to_vector3(eps1), scalegrad*cyl_scale, geps, - adjoint_c, forward_c, fwd_avg, 0.5*adj, frequencies[f_i], gv, du); - - //operate on the second eps node - fwd1_idx = get_idx_from_ivec(gv.dim, start_ivec, the_stride, fwd_pa); - fwd1 = 0.5 * meep::cdouble(GET_FIELDS(fields_f, ci_forward, f_i, fwd1_idx)); - fwd2_idx = get_idx_from_ivec(gv.dim, start_ivec, the_stride, fwd_paf); - fwd2 = 0.5 * meep::cdouble(GET_FIELDS(fields_f, ci_forward, f_i, fwd2_idx)); - fwd_avg = fwd1 + fwd2; - meep::vec eps2 = gv[ieps2]; - cyl_scale = (gv.dim == meep::Dcyl) ? eps2.r() : 1; - material_grids_addgradient_point( - v+ng*f_i, vec_to_vector3(eps2), scalegrad*cyl_scale, geps, - adjoint_c, forward_c, fwd_avg, 0.5*adj, frequencies[f_i], gv, du); + /* more complicated case requires interpolation/restriction */ + } else if ((md->do_averaging) || /* account for subpixel smoothing */ + (!is_material_grid(md)) || /* account for edge effects of mg */ + (has_offdiag(&(md->medium_1))) || /* account for offdiagonal components */ + (has_offdiag(&(md->medium_2)))) { + /* we need to restrict the adjoint fields to + the two nodes of interest (which requires a factor + of 0.5 to scale), and interpolate the forward fields + to the same two nodes (which requires another factor of 0.5). + Then we perform our inner product at these nodes. + */ + std::complex fwd_avg, fwd1, fwd2; + ptrdiff_t fwd1_idx, fwd2_idx; + + //identify the first corner of the forward fields + meep::ivec fwd_p = ip + gv.iyee_shift(forward_c) - gv.iyee_shift(adjoint_c); + //identify the other three corners + meep::ivec unit_a = unit_ivec(gv.dim,component_direction(adjoint_c)); + meep::ivec unit_f = unit_ivec(gv.dim,component_direction(forward_c)); + meep::ivec fwd_pa = (fwd_p + unit_a*2); + meep::ivec fwd_pf = (fwd_p - unit_f*2); + meep::ivec fwd_paf = (fwd_p + unit_a*2 - unit_f*2); + + // store in vector for convenience + std::vector fwd_pl = {fwd_p, fwd_pa}; + std::vector fwd_pr = {fwd_pf, fwd_paf}; + + //identify the two eps points + std::vector ieps = {(fwd_p + fwd_pf) / 2, (fwd_pa + fwd_paf) / 2}; + + //operate on the each eps node + #pragma unroll + for (int node=0;node<2;node++) { // two nodes + fwd1_idx = gv_fwd.index(forward_c,fwd_pl[node]); + fwd1 = ((fwd1_idx >= fwd_chunk->N) || (fwd1_idx<0)) ? 0 : fwd_chunk->dft[nf*fwd1_idx+f_i]; + fwd2_idx = gv_fwd.index(forward_c,fwd_pr[node]); + fwd2 = ((fwd2_idx >= fwd_chunk->N) || (fwd2_idx<0)) ? 0 : fwd_chunk->dft[nf*fwd2_idx+f_i]; + fwd_avg = std::complex(0.5,0) * (fwd1 + fwd2); + meep::vec eps1 = gv[ieps[node]]; + cyl_scale = (gv.dim == meep::Dcyl) ? eps1.r() : 1; + material_grids_addgradient_point( + v_local+ng*f_i, vec_to_vector3(eps1), scalegrad*cyl_scale, geps, + adjoint_c, forward_c, fwd_avg, std::complex(0.5,0)*adj, frequencies[f_i], gv, du); + } + } + /********* compute λᵀbᵤ ***************/ + /* not yet implemented/needed */ + /**************************************/ } - ci_forward++; } - /********* compute λᵀbᵤ ***************/ - /* not yet implemented/needed */ - /**************************************/ - /**************************************/ } - ci_adjoint++; } } -#undef GET_FIELDS -#undef FOR_MY_COMPONENTS - delete[] is; - delete[] ie; -} + + /* ------------------------------------------------------------ */ + // Broadcast results + /* ------------------------------------------------------------ */ + meep::sum_to_all(v_local, v, ng*nf); + + /* ------------------------------------------------------------ */ + // cleanup + /* ------------------------------------------------------------ */ + // clear the array used for local sum to all + delete[] v_local; + + // clear all the dft data structures + for (int i=0;i<3;i++){ + for (int ii=0;i *fields_a, - std::complex *fields_f, size_t fields_shapes[12], +void material_grids_addgradient(double *v, size_t ng, size_t nf, std::vector fields_a, + std::vectorfields_f, double *frequencies, double scalegrad, meep::grid_volume &gv, meep::volume &where, geom_epsilon *geps, double du = 1e-6); diff --git a/src/vec.cpp b/src/vec.cpp index 5bf557e43..63b07f23d 100644 --- a/src/vec.cpp +++ b/src/vec.cpp @@ -1633,25 +1633,26 @@ const char *grid_volume::str(char *buffer, size_t buflen) { /********************************************************************/ /********************************************************************/ /********************************************************************/ -grid_volume grid_volume::subvolume(ivec is, ivec ie) { +grid_volume grid_volume::subvolume(ivec is, ivec ie, component c) { if (!(contains(is) && contains(ie))) meep::abort("invalid extents in subvolume"); grid_volume sub; sub.dim = dim; sub.a = a; sub.inva = inva; - sub.init_subvolume(is, ie); + sub.init_subvolume(is, ie, c); return sub; } -void grid_volume::init_subvolume(ivec is, ivec ie) { +void grid_volume::init_subvolume(ivec is, ivec ie, component c) { ivec origin(dim, 0); + for (int i=0;i<3;i++) num[i] = 0; LOOP_OVER_DIRECTIONS(dim, d) { - num[(int)d] = (ie - is).in_direction(d) / 2; - origin.set_direction(d, is.in_direction(d)); + num[(int)d] = (ie - is).in_direction(d)/2; + origin.set_direction(d, is.in_direction(d)-iyee_shift(c).in_direction(d)); } num_changed(); - center_origin(); - shift_origin(origin); + //center_origin(); + set_origin(origin); } } // namespace meep