From 2a805fd0f6d868905a45d122ea171aa87b9a403a Mon Sep 17 00:00:00 2001 From: mochen4 Date: Wed, 21 Jun 2023 00:21:00 -0400 Subject: [PATCH 1/5] add adjoint doc --- python/adjoint/objective.py | 78 +++++++++++++++++++++++++- python/adjoint/optimization_problem.py | 53 ++++++++++++++--- 2 files changed, 121 insertions(+), 10 deletions(-) diff --git a/python/adjoint/objective.py b/python/adjoint/objective.py index bfee38983..f491bdc43 100644 --- a/python/adjoint/objective.py +++ b/python/adjoint/objective.py @@ -147,7 +147,7 @@ def _create_time_profile(self, fwidth_frac=0.1, adj_cutoff=5): class EigenmodeCoefficient(ObjectiveQuantity): - """A frequency-dependent eigenmode coefficient. + """A differentiable frequency-dependent eigenmode coefficient. Attributes: volume: the volume over which the eigenmode coefficient is calculated. mode: the eigenmode number. @@ -158,6 +158,10 @@ class EigenmodeCoefficient(ObjectiveQuantity): kpoint_func_overlap_idx: the index of the mode coefficient to return when specifying `kpoint_func`. When specified, this overrides the effect of `forward` and should have a value of either 0 or 1. + decimation_factor: an integer so that the DFT fields are updated at every + decimation_factor timesteps. The default is 0, at which the value is + automatically determined from the Nyquist rate of the bandwidth-limited + sources and the DFT monitor. It can be turned off by setting it to 1. subtracted_dft_fields: the DFT fields obtained using `get_flux_data` from a previous normalization run. This is subtracted from the DFT fields of this mode monitor in order to improve the accuracy of the @@ -257,6 +261,11 @@ def place_adjoint_source(self, dJ): return [source] def __call__(self): + """The values of eigenmode coefficient at each frequency + + Returns: + 1D array of eigenmode coefficients corresponding to each of self.frequencies + """ if self.kpoint_func: kpoint_func = self.kpoint_func overlap_idx = self.kpoint_func_overlap_idx @@ -284,11 +293,30 @@ def __call__(self): class FourierFields(ObjectiveQuantity): + """A differentiable frequency-dependent Fourier Fields (dft_fields) + Attributes: + volume: the volume over which the Fourier Fields are calculated. Due to an unsolved bug, + the size must not be zero in at least one direction. + component: field component (e.g. mp.Ex, mp.Hz, etc.) + yee_grid: if True, the Fourier fields evaluated at corresponding Yee grid point + are returned; otherwise, interpolated fields at the center of each voxel + are returned + decimation_factor: an integer so that the DFT fields are updated at every + decimation_factor timesteps. The default is 0, at which the value is + automatically determined from the Nyquist rate of the bandwidth-limited + sources and the DFT monitor. It can be turned off by setting it to 1. + subtracted_dft_fields: the DFT fields obtained using `get_flux_data` from + a previous normalization run. This is subtracted from the DFT fields + of this mode monitor in order to improve the accuracy of the + reflectance measurement (i.e., the $S_{11}$ scattering parameter). + Default is None. + """ + def __init__( self, sim: mp.Simulation, volume: mp.Volume, - component: List[int], + component: int, yee_grid: Optional[bool] = False, decimation_factor: Optional[int] = 0, subtracted_dft_fields: Optional[FluxData] = None, @@ -375,6 +403,13 @@ def place_adjoint_source(self, dJ): return sources def __call__(self): + """The values of Fourier Fields at each frequency + + Returns: + array of Fourier Fields with dimension k+1 where k is the dimension of self.volume + The first axis corresponds to the index of frequency, and the rest k axis are for + the spatial indices of points in the monitor + """ self._eval = np.array( [ self.sim.get_dft_array(self._monitor, self.component, i) @@ -385,6 +420,24 @@ def __call__(self): class Near2FarFields(ObjectiveQuantity): + """A differentiable near2far field transformation + Attributes: + Near2FarRegions: List of mp.Near2FarRegion over which the near fields are collected + far_pts: list of far points at which fields are computed + nperiods: If nperiods > 1, sum of 2*nperiods+1 Bloch-periodic copies of near fields + is computed to approximate the lattice sum with Bloch periodic condition. + Default is 1 (no sum). + decimation_factor: an integer so that the DFT fields are updated at every + decimation_factor timesteps. The default is 0, at which the value is + automatically determined from the Nyquist rate of the bandwidth-limited + sources and the DFT monitor. It can be turned off by setting it to 1. + norm_near_fields: the DFT fields obtained using `get_near2far_data` from + a previous normalization run. This is subtracted from the DFT fields + of this near2far monitor in order to improve the accuracy of the + reflectance measurement (i.e., the $S_{11}$ scattering parameter). + Default is None. + """ + def __init__( self, sim: mp.Simulation, @@ -461,6 +514,13 @@ def place_adjoint_source(self, dJ): return sources def __call__(self): + """The values of far fields at each points at each frequency + + Returns: + 3D array of far fields. The first axis is the index of far field points in self.far_pts; + the second axis is the index of frequency; and the third is the index of component in + [mp.Ex(mp.Er), mp.Ey(mp.Ep), mp.Ez, mp.Hx(mp.Hr), mp.Hy(mp.Hp), mp.Hz] + """ self._eval = np.array( [self.sim.get_farfield(self._monitor, far_pt) for far_pt in self.far_pts] ).reshape((self._nfar_pts, self.num_freq, 6)) @@ -468,6 +528,15 @@ def __call__(self): class LDOS(ObjectiveQuantity): + """A differentiable LDOS + + Attributes: + decimation_factor: an integer so that the DFT fields are updated at every + decimation_factor timesteps. The default is 0, at which the value is + automatically determined from the Nyquist rate of the bandwidth-limited + sources and the DFT monitor. It can be turned off by setting it to 1. + """ + def __init__( self, sim: mp.Simulation, decimation_factor: Optional[int] = 0, **kwargs ): @@ -530,6 +599,11 @@ def place_adjoint_source(self, dJ): return sources def __call__(self): + """The values of ldos_Jdata at each frequency + + Returns: + 1D array of LDOS corresponding to each of self.frequencies + """ self._eval = self.sim.ldos_data self.ldos_scale = self.sim.ldos_scale self.ldos_Jdata = self.sim.ldos_Jdata diff --git a/python/adjoint/optimization_problem.py b/python/adjoint/optimization_problem.py index 245b74717..c30611ca1 100644 --- a/python/adjoint/optimization_problem.py +++ b/python/adjoint/optimization_problem.py @@ -21,6 +21,27 @@ class OptimizationProblem: calculation) and optionally its gradient (adjoint calculation). This is done by the __call__ method. + Attributes: + simulation: The corresponding Meep `Simulation` object that describes + the problem (e.g. sources, geometry) + objective_functions: list of objective functions on objective_arguments + objective_arguments: list of differential objective quantities as arguments + of objective functions + design_regions: list of design regions to be optimized + frequencies: list of frequencies in the problem + fcen: center frequency + df: range of frequencies + nf: number of frequencies + decay_by: simulation stops once all the field components and frequencies of + every DFT object have decayed by at least this amount. Default is 1e-11. + decimation_factor: an integer so that the DFT fields are updated at every + decimation_factor timesteps. The default is 0, at which the value is + automatically determined from the Nyquist rate of the bandwidth-limited + sources and the DFT monitor. It can be turned off by setting it to 1 + minimum_run_time: minimum runtime for each simulation. Default is 0 + maximum_run_time: maximum runtime for each simulation + finite_difference_step: step size for calculation of finite difference gradients + step_funcs: list of step functions to be called at each timestep """ def __init__( @@ -40,13 +61,7 @@ def __init__( finite_difference_step: Optional[float] = utils.FD_DEFAULT, step_funcs: list = None, ): - """ - + **`simulation` [ `Simulation` ]** — The corresponding Meep - `Simulation` object that describes the problem (e.g. sources, - geometry) - + **`objective_functions` [ `list of ` ]** — - """ self.step_funcs = step_funcs if step_funcs is not None else [] self.sim = simulation @@ -123,7 +138,29 @@ def __call__( need_gradient: bool = True, beta: float = None, ) -> Tuple[List[np.ndarray], List[List[np.ndarray]]]: - """Evaluate value and/or gradient of objective function.""" + """Evaluate value and/or gradient of objective function. + + Args: + rho_vector: lists of design variables. Each list represents design variables + of one design region. The design is updated to the specified values; functions + and gradients will then be evaluated at this configuration of design variables. + need_value: whether forward evaluations are needed. Default is True. + need_gradient: whether adjoint and gradients evaluatiosn are needed. Default is True. + beta: the strength of projection of rho_vector. Default to None. + + Returns: + A tuple (f0, gradient) where: + f0 is the list of objective functions values when design variables + are set to rho_vector + gradient is the lists of gradients of objective functions with respect to + the design variables when they are set to rho_vector. There is a list of + gradients for each design region and for each objective functions. Generally, + the first axis is the index of objective functions and the second is the index + of design region; and each gradient is a 2d array where the first axis is for + the design variables, and the second is the index of frequency. Empty dimensions + are squeezed. + + """ if rho_vector: self.update_design(rho_vector=rho_vector, beta=beta) @@ -320,7 +357,7 @@ def calculate_gradient(self): elif len(self.gradient[0]) == 1: self.gradient = [ g[0] for g in self.gradient - ] # multiple objective functions bu one design region + ] # multiple objective functions but one design region # Return optimizer's state to initialization self.current_state = "INIT" From 1dc6624ae92b516b4c65b99f986605501ccab891 Mon Sep 17 00:00:00 2001 From: mochen4 Date: Thu, 22 Jun 2023 13:24:33 -0400 Subject: [PATCH 2/5] add args to adjoint doc --- python/adjoint/objective.py | 154 +++++++++++++++---------- python/adjoint/optimization_problem.py | 56 ++++++--- 2 files changed, 135 insertions(+), 75 deletions(-) diff --git a/python/adjoint/objective.py b/python/adjoint/objective.py index f491bdc43..8c3d9f42a 100644 --- a/python/adjoint/objective.py +++ b/python/adjoint/objective.py @@ -148,25 +148,18 @@ def _create_time_profile(self, fwidth_frac=0.1, adj_cutoff=5): class EigenmodeCoefficient(ObjectiveQuantity): """A differentiable frequency-dependent eigenmode coefficient. + Attributes: volume: the volume over which the eigenmode coefficient is calculated. mode: the eigenmode number. forward: whether the forward or backward mode coefficient is returned as the result of the evaluation. - kpoint_func: an optional k-point function to use when evaluating the eigenmode - coefficient. When specified, this overrides the effect of `forward`. + kpoint_func: an optional k-point function to use when evaluating the eigenmode coefficient. kpoint_func_overlap_idx: the index of the mode coefficient to return when - specifying `kpoint_func`. When specified, this overrides the effect of - `forward` and should have a value of either 0 or 1. - decimation_factor: an integer so that the DFT fields are updated at every - decimation_factor timesteps. The default is 0, at which the value is - automatically determined from the Nyquist rate of the bandwidth-limited - sources and the DFT monitor. It can be turned off by setting it to 1. - subtracted_dft_fields: the DFT fields obtained using `get_flux_data` from - a previous normalization run. This is subtracted from the DFT fields - of this mode monitor in order to improve the accuracy of the - reflectance measurement (i.e., the $S_{11}$ scattering parameter). - Default is None. + specifying `kpoint_func`. + decimation_factor: An integer used to specify the number of timesteps between updates of the DFT fields. + subtracted_dft_fields: the DFT fields obtained using `get_flux_data` from a previous normalization run. + eigenmode_kwargs: additional argument for EigenModeSource """ def __init__( @@ -181,8 +174,29 @@ def __init__( subtracted_dft_fields: Optional[FluxData] = None, **kwargs ): - """ - + **`sim` [ `Simulation` ]** — + """Initialize an instance of differentiable frequency-dependent eigenmode coefficient. + + Args: + sim: the Meep simulation object of the problem. + volume: the volume over which the eigenmode coefficient is calculated. + mode: the eigenmode number. + forward: whether the forward or backward mode coefficient is returned as + the result of the evaluation. Default is True. + kpoint_func: an optional k-point function to use when evaluating the eigenmode + coefficient. When specified, this overrides the effect of `forward`. + kpoint_func_overlap_idx: the index of the mode coefficient to return when + specifying `kpoint_func`. When specified, this overrides the effect of + `forward` and should have a value of either 0 or 1. + decimation_factor: An integer used to specify the number of timesteps between updates of + the DFT fields. The default is 0, at which the value is automatically determined from the + Nyquist rate of the bandwidth-limited sources and the DFT monitor. It can be turned off + by setting it to 1. + subtracted_dft_fields: the DFT fields obtained using `get_flux_data` from + a previous normalization run. This is subtracted from the DFT fields + of this mode monitor in order to improve the accuracy of the + reflectance measurement (i.e., the $S_{11}$ scattering parameter). + Default is None. + eigenmode_kwargs: additional argument for EigenModeSource """ super().__init__(sim) if kpoint_func_overlap_idx not in [0, 1]: @@ -293,23 +307,15 @@ def __call__(self): class FourierFields(ObjectiveQuantity): - """A differentiable frequency-dependent Fourier Fields (dft_fields) + """A differentiable frequency-dependent Fourier fields (dft_fields) + Attributes: - volume: the volume over which the Fourier Fields are calculated. Due to an unsolved bug, - the size must not be zero in at least one direction. - component: field component (e.g. mp.Ex, mp.Hz, etc.) - yee_grid: if True, the Fourier fields evaluated at corresponding Yee grid point - are returned; otherwise, interpolated fields at the center of each voxel - are returned - decimation_factor: an integer so that the DFT fields are updated at every - decimation_factor timesteps. The default is 0, at which the value is - automatically determined from the Nyquist rate of the bandwidth-limited - sources and the DFT monitor. It can be turned off by setting it to 1. - subtracted_dft_fields: the DFT fields obtained using `get_flux_data` from - a previous normalization run. This is subtracted from the DFT fields - of this mode monitor in order to improve the accuracy of the - reflectance measurement (i.e., the $S_{11}$ scattering parameter). - Default is None. + volume: the volume over which the Fourier fields are calculated. + component: field component (e.g. mp.Ex, mp.Hz, etc.) of the Fourier fields + yee_grid: whether the Fourier fields are evaluated at the corresponding Yee grid points + or interpolated fields at the center of each voxel + decimation_factor: An integer used to specify the number of timesteps between updates of the DFT fields. + subtracted_dft_fields: the DFT fields obtained using `get_flux_data` from a previous normalization run. """ def __init__( @@ -321,7 +327,24 @@ def __init__( decimation_factor: Optional[int] = 0, subtracted_dft_fields: Optional[FluxData] = None, ): - """ """ + """Initialize an instance of differentiable Fourier fields instance. + + Args: + sim: the Meep simulation object of the problem. + volume: the volume over which the eigenmode coefficient is calculated. Due to an unresolved bug, + the size must not be zero in at least one direction. + component: field component (e.g. mp.Ex, mp.Hz, etc.) of the Fourier fields + yee_grid: if True, the Fourier fields are evaluated at the corresponding Yee grid points; + otherwise, they are interpolated fields at the center of each voxel. Default is False + decimation_factor: An integer used to specify the number of timesteps between updates of + the DFT fields. The default is 0, at which the value is automatically determined from the + Nyquist rate of the bandwidth-limited sources and the DFT monitor. It can be turned off + by setting it to 1. + subtracted_dft_fields: the DFT fields obtained using `get_flux_data` from + a previous normalization run. This is subtracted from the DFT fields + of this mode monitor in order to improve the accuracy of the + reflectance measurement (i.e., the $S_{11}$ scattering parameter). Default is None. + """ super().__init__(sim) self.volume = sim._fit_volume_to_simulation(volume) self.component = component @@ -372,11 +395,11 @@ def place_adjoint_source(self, dJ): scale factor for each point (rather than a scale vector). """ - self.all_fouriersrcdata = self._monitor.swigobj.fourier_sourcedata( + all_fouriersrcdata = self._monitor.swigobj.fourier_sourcedata( self.volume.swigobj, self.component, self.sim.fields, dJ ) - for fourier_data in self.all_fouriersrcdata: + for fourier_data in all_fouriersrcdata: amp_arr = np.array(fourier_data.amp_arr).reshape(-1, self.num_freq) scale = amp_arr * self._adj_src_scale(include_resolution=False) @@ -421,21 +444,14 @@ def __call__(self): class Near2FarFields(ObjectiveQuantity): """A differentiable near2far field transformation + Attributes: Near2FarRegions: List of mp.Near2FarRegion over which the near fields are collected far_pts: list of far points at which fields are computed nperiods: If nperiods > 1, sum of 2*nperiods+1 Bloch-periodic copies of near fields - is computed to approximate the lattice sum with Bloch periodic condition. - Default is 1 (no sum). - decimation_factor: an integer so that the DFT fields are updated at every - decimation_factor timesteps. The default is 0, at which the value is - automatically determined from the Nyquist rate of the bandwidth-limited - sources and the DFT monitor. It can be turned off by setting it to 1. - norm_near_fields: the DFT fields obtained using `get_near2far_data` from - a previous normalization run. This is subtracted from the DFT fields - of this near2far monitor in order to improve the accuracy of the - reflectance measurement (i.e., the $S_{11}$ scattering parameter). - Default is None. + is computed to approximate the lattice sum from Bloch periodic boundary condition. + decimation_factor: An integer used to specify the number of timesteps between updates of the DFT fields. + norm_near_fields: the DFT fields obtained using `get_near2far_data` from a previous normalization run. """ def __init__( @@ -447,7 +463,25 @@ def __init__( decimation_factor: Optional[int] = 0, norm_near_fields: Optional[NearToFarData] = None, ): - """ """ + """Initialize an instance of differentiable Fourier fields instance. + + Args: + sim: the Meep simulation object of the problem. + Near2FarRegions: List of mp.Near2FarRegion over which the near fields are collected + far_pts: list of far points at which fields are computed + nperiods: If nperiods > 1, sum of 2*nperiods+1 Bloch-periodic copies of near fields + is computed to approximate the lattice sum from Bloch periodic boundary condition. + Default is 1 (no sum). + decimation_factor: An integer used to specify the number of timesteps between updates of + the DFT fields. The default is 0, at which the value is automatically determined from the + Nyquist rate of the bandwidth-limited sources and the DFT monitor. It can be turned off + by setting it to 1. + norm_near_fields: the DFT fields obtained using `get_near2far_data` from + a previous normalization run. This is subtracted from the DFT fields + of this near2far monitor in order to improve the accuracy of the + reflectance measurement (i.e., the $S_{11}$ scattering parameter). + Default is None. + """ super().__init__(sim) self.Near2FarRegions = Near2FarRegions self.far_pts = far_pts # list of far pts @@ -531,23 +565,21 @@ class LDOS(ObjectiveQuantity): """A differentiable LDOS Attributes: - decimation_factor: an integer so that the DFT fields are updated at every - decimation_factor timesteps. The default is 0, at which the value is - automatically determined from the Nyquist rate of the bandwidth-limited - sources and the DFT monitor. It can be turned off by setting it to 1. + srckwarg: additional arguments for the source """ - def __init__( - self, sim: mp.Simulation, decimation_factor: Optional[int] = 0, **kwargs - ): - """ """ + def __init__(self, sim: mp.Simulation, **kwargs): + """Initialize a differentiable LDOS instance + + Args: + sim: the Meep simulation object of the problem. + """ super().__init__(sim) - self.decimation_factor = decimation_factor self.srckwarg = kwargs def register_monitors(self, frequencies): self._frequencies = np.asarray(frequencies) - self.forward_src = self.sim.sources + self._forward_src = self.sim.sources return def place_adjoint_source(self, dJ): @@ -557,7 +589,7 @@ def place_adjoint_source(self, dJ): dJ = dJ.flatten() sources = [] forward_f_scale = np.array( - [self.ldos_scale / self.ldos_Jdata[k] for k in range(self.num_freq)] + [self._ldos_scale / self._ldos_Jdata[k] for k in range(self.num_freq)] ) if self._frequencies.size == 1: amp = (dJ * self._adj_src_scale(False) * forward_f_scale)[0] @@ -571,7 +603,7 @@ def place_adjoint_source(self, dJ): self.sim.fields.dt, ) amp = 1 - for forward_src_i in self.forward_src: + for forward_src_i in self._forward_src: if isinstance(forward_src_i, mp.EigenModeSource): src_i = mp.EigenModeSource( src, @@ -599,12 +631,12 @@ def place_adjoint_source(self, dJ): return sources def __call__(self): - """The values of ldos_Jdata at each frequency + """The values of LDOS at each frequency Returns: 1D array of LDOS corresponding to each of self.frequencies """ self._eval = self.sim.ldos_data - self.ldos_scale = self.sim.ldos_scale - self.ldos_Jdata = self.sim.ldos_Jdata + self._ldos_scale = self.sim.ldos_scale + self._ldos_Jdata = self.sim.ldos_Jdata return np.array(self._eval) diff --git a/python/adjoint/optimization_problem.py b/python/adjoint/optimization_problem.py index c30611ca1..888e10a9b 100644 --- a/python/adjoint/optimization_problem.py +++ b/python/adjoint/optimization_problem.py @@ -22,26 +22,25 @@ class OptimizationProblem: This is done by the __call__ method. Attributes: - simulation: The corresponding Meep `Simulation` object that describes - the problem (e.g. sources, geometry) + sim: the corresponding Meep `Simulation` object of the problem objective_functions: list of objective functions on objective_arguments - objective_arguments: list of differential objective quantities as arguments - of objective functions - design_regions: list of design regions to be optimized - frequencies: list of frequencies in the problem + objective_arguments: list of ObjectiveQuantity as arguments of objective functions + design_regions: list of DesignRegion under optimization + frequencies: list of frequencies of interest in the problem fcen: center frequency - df: range of frequencies + df: range of frequencies, i.e. maximum frequency - minimum frequency nf: number of frequencies - decay_by: simulation stops once all the field components and frequencies of - every DFT object have decayed by at least this amount. Default is 1e-11. - decimation_factor: an integer so that the DFT fields are updated at every - decimation_factor timesteps. The default is 0, at which the value is - automatically determined from the Nyquist rate of the bandwidth-limited - sources and the DFT monitor. It can be turned off by setting it to 1 - minimum_run_time: minimum runtime for each simulation. Default is 0 + fcen_idx: index of center frequency + decay_by: an number used to specify the amount by which every DFT object have to decay + before simulation stops. + decimation_factor: an integer used to specify the number of timesteps between updates of the DFT fields. + minimum_run_time: minimum runtime for each simulation. maximum_run_time: maximum runtime for each simulation finite_difference_step: step size for calculation of finite difference gradients step_funcs: list of step functions to be called at each timestep + f_bank: the history of objective functions evaluations + num_design_params: list of the numbers of design parameters for each design region + num_design_regions: number of design regions """ def __init__( @@ -62,6 +61,35 @@ def __init__( step_funcs: list = None, ): + """Initialize an instance of OptimizationProblem. + + Args: + sim: the corresponding Meep `Simulation` object that describes the + problem (e.g. sources, geometry) + objective_functions: list of objective functions on objective_arguments. The functions should + take all of objective_arguments as argument, even if not all of them are used in the functions. + For example, if we are interested in functions f(A,B) and g(B,C) of quantities A, B, C, then the + objective functions have to be lambda A, B, C: f(A,B) and lambda A, B, C: g(B,C); and we have to + specify arguments as [A,B,C] + objective_arguments: list of ObjectiveQuantity passed as arguments of objective functions + design_regions: list of DesignRegion to be optimized + frequencies: list of frequencies of interest in the problem. If not specified, then the list of frequencies + will be created from fcen, df, and nf: a list of size nf that goes from fcen-df/2 to fcen+df/2 + fcen: center frequency + df: range of frequencies, i.e. maximum frequency - minimum frequency + nf: number of frequencies + decay_by: an number used to specify the amount by which all the field components and frequencies + frequencies of every DFT object have to decay before simulation stops. Default is 1e-11. + decimation_factor: an integer used to specify the number of timesteps between updates of + updates of the DFT fields. The default is 0, at which the value is + automatically determined from the Nyquist rate of the bandwidth-limited + sources and the DFT monitor. It can be turned off by setting it to 1 + minimum_run_time: a number ensures the minimum runtime for each simulation. Default is 0 + maximum_run_time: a number caps the maximum runtime for each simulation + finite_difference_step: step size for calculation of finite difference gradients + step_funcs: list of step functions to be called at each timestep + """ + self.step_funcs = step_funcs if step_funcs is not None else [] self.sim = simulation From 02b4e810ba8c34efada927e16b5a8df7aacd61b2 Mon Sep 17 00:00:00 2001 From: mochen4 Date: Thu, 22 Jun 2023 15:34:52 -0400 Subject: [PATCH 3/5] fix args to adjoint doc --- python/adjoint/objective.py | 43 +++--------------------- python/adjoint/optimization_problem.py | 45 +++++++------------------- 2 files changed, 16 insertions(+), 72 deletions(-) diff --git a/python/adjoint/objective.py b/python/adjoint/objective.py index 8c3d9f42a..eda9f91e3 100644 --- a/python/adjoint/objective.py +++ b/python/adjoint/objective.py @@ -147,20 +147,7 @@ def _create_time_profile(self, fwidth_frac=0.1, adj_cutoff=5): class EigenmodeCoefficient(ObjectiveQuantity): - """A differentiable frequency-dependent eigenmode coefficient. - - Attributes: - volume: the volume over which the eigenmode coefficient is calculated. - mode: the eigenmode number. - forward: whether the forward or backward mode coefficient is returned as - the result of the evaluation. - kpoint_func: an optional k-point function to use when evaluating the eigenmode coefficient. - kpoint_func_overlap_idx: the index of the mode coefficient to return when - specifying `kpoint_func`. - decimation_factor: An integer used to specify the number of timesteps between updates of the DFT fields. - subtracted_dft_fields: the DFT fields obtained using `get_flux_data` from a previous normalization run. - eigenmode_kwargs: additional argument for EigenModeSource - """ + """A differentiable frequency-dependent eigenmode coefficient.""" def __init__( self, @@ -307,16 +294,7 @@ def __call__(self): class FourierFields(ObjectiveQuantity): - """A differentiable frequency-dependent Fourier fields (dft_fields) - - Attributes: - volume: the volume over which the Fourier fields are calculated. - component: field component (e.g. mp.Ex, mp.Hz, etc.) of the Fourier fields - yee_grid: whether the Fourier fields are evaluated at the corresponding Yee grid points - or interpolated fields at the center of each voxel - decimation_factor: An integer used to specify the number of timesteps between updates of the DFT fields. - subtracted_dft_fields: the DFT fields obtained using `get_flux_data` from a previous normalization run. - """ + """A differentiable frequency-dependent Fourier fields (dft_fields)""" def __init__( self, @@ -443,16 +421,7 @@ def __call__(self): class Near2FarFields(ObjectiveQuantity): - """A differentiable near2far field transformation - - Attributes: - Near2FarRegions: List of mp.Near2FarRegion over which the near fields are collected - far_pts: list of far points at which fields are computed - nperiods: If nperiods > 1, sum of 2*nperiods+1 Bloch-periodic copies of near fields - is computed to approximate the lattice sum from Bloch periodic boundary condition. - decimation_factor: An integer used to specify the number of timesteps between updates of the DFT fields. - norm_near_fields: the DFT fields obtained using `get_near2far_data` from a previous normalization run. - """ + """A differentiable near2far field transformation""" def __init__( self, @@ -562,11 +531,7 @@ def __call__(self): class LDOS(ObjectiveQuantity): - """A differentiable LDOS - - Attributes: - srckwarg: additional arguments for the source - """ + """A differentiable LDOS""" def __init__(self, sim: mp.Simulation, **kwargs): """Initialize a differentiable LDOS instance diff --git a/python/adjoint/optimization_problem.py b/python/adjoint/optimization_problem.py index 888e10a9b..d72d89db0 100644 --- a/python/adjoint/optimization_problem.py +++ b/python/adjoint/optimization_problem.py @@ -20,27 +20,6 @@ class OptimizationProblem: of design variables, compute the objective function value (forward calculation) and optionally its gradient (adjoint calculation). This is done by the __call__ method. - - Attributes: - sim: the corresponding Meep `Simulation` object of the problem - objective_functions: list of objective functions on objective_arguments - objective_arguments: list of ObjectiveQuantity as arguments of objective functions - design_regions: list of DesignRegion under optimization - frequencies: list of frequencies of interest in the problem - fcen: center frequency - df: range of frequencies, i.e. maximum frequency - minimum frequency - nf: number of frequencies - fcen_idx: index of center frequency - decay_by: an number used to specify the amount by which every DFT object have to decay - before simulation stops. - decimation_factor: an integer used to specify the number of timesteps between updates of the DFT fields. - minimum_run_time: minimum runtime for each simulation. - maximum_run_time: maximum runtime for each simulation - finite_difference_step: step size for calculation of finite difference gradients - step_funcs: list of step functions to be called at each timestep - f_bank: the history of objective functions evaluations - num_design_params: list of the numbers of design parameters for each design region - num_design_regions: number of design regions """ def __init__( @@ -66,11 +45,12 @@ def __init__( Args: sim: the corresponding Meep `Simulation` object that describes the problem (e.g. sources, geometry) - objective_functions: list of objective functions on objective_arguments. The functions should - take all of objective_arguments as argument, even if not all of them are used in the functions. + objective_functions: list of differentiable functions (callable objects) whose arguments are + given by objective_arguments. The functions should take all of objective_arguments + as argument, even if not all of them are used in the functions. For example, if we are interested in functions f(A,B) and g(B,C) of quantities A, B, C, then the - objective functions have to be lambda A, B, C: f(A,B) and lambda A, B, C: g(B,C); and we have to - specify arguments as [A,B,C] + objective_functions list has to be [f1, g1] where f1 = lambda A, B, C: f(A,B) and g1 = lambda A, B, C: g(B,C); + and we have to specify arguments as [A,B,C] objective_arguments: list of ObjectiveQuantity passed as arguments of objective functions design_regions: list of DesignRegion to be optimized frequencies: list of frequencies of interest in the problem. If not specified, then the list of frequencies @@ -180,14 +160,13 @@ def __call__( A tuple (f0, gradient) where: f0 is the list of objective functions values when design variables are set to rho_vector - gradient is the lists of gradients of objective functions with respect to - the design variables when they are set to rho_vector. There is a list of - gradients for each design region and for each objective functions. Generally, - the first axis is the index of objective functions and the second is the index - of design region; and each gradient is a 2d array where the first axis is for - the design variables, and the second is the index of frequency. Empty dimensions - are squeezed. - + gradient is a list (over objective functions) of lists (over design regions) of 2d arrays + (design variables by frequencies) of derivatives. If there is only a single objective function, + the outer 1-element list is replaced by just that element, and similarly if there is only one design region + then those 1-element list are replaced by just those elements. In addition, if there is only one frequency + then the innermost array is squeezed to a 1d array. + For example, if there is only a single objective function, a single design region, and a single frequency, + then gradient is simply a 1d array of the derivatives. """ if rho_vector: self.update_design(rho_vector=rho_vector, beta=beta) From a85debd0a27e7fb4c22812f48b28597764064fd1 Mon Sep 17 00:00:00 2001 From: mochen4 Date: Thu, 22 Jun 2023 15:39:47 -0400 Subject: [PATCH 4/5] minor fix --- python/adjoint/objective.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/python/adjoint/objective.py b/python/adjoint/objective.py index eda9f91e3..879175df4 100644 --- a/python/adjoint/objective.py +++ b/python/adjoint/objective.py @@ -533,11 +533,17 @@ def __call__(self): class LDOS(ObjectiveQuantity): """A differentiable LDOS""" - def __init__(self, sim: mp.Simulation, **kwargs): + def __init__( + self, sim: mp.Simulation, decimation_factor: Optional[int] = 0, **kwargs + ): """Initialize a differentiable LDOS instance Args: sim: the Meep simulation object of the problem. + decimation_factor: An integer used to specify the number of timesteps between updates of + the DFT fields. The default is 0, at which the value is automatically determined from the + Nyquist rate of the bandwidth-limited sources and the DFT monitor. It can be turned off + by setting it to 1. """ super().__init__(sim) self.srckwarg = kwargs From 499c8f830a5a2d1679bef5785dc30efac86ea6d5 Mon Sep 17 00:00:00 2001 From: mochen4 Date: Thu, 22 Jun 2023 15:44:33 -0400 Subject: [PATCH 5/5] minor fix --- python/adjoint/objective.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/python/adjoint/objective.py b/python/adjoint/objective.py index 879175df4..eda9f91e3 100644 --- a/python/adjoint/objective.py +++ b/python/adjoint/objective.py @@ -533,17 +533,11 @@ def __call__(self): class LDOS(ObjectiveQuantity): """A differentiable LDOS""" - def __init__( - self, sim: mp.Simulation, decimation_factor: Optional[int] = 0, **kwargs - ): + def __init__(self, sim: mp.Simulation, **kwargs): """Initialize a differentiable LDOS instance Args: sim: the Meep simulation object of the problem. - decimation_factor: An integer used to specify the number of timesteps between updates of - the DFT fields. The default is 0, at which the value is automatically determined from the - Nyquist rate of the bandwidth-limited sources and the DFT monitor. It can be turned off - by setting it to 1. """ super().__init__(sim) self.srckwarg = kwargs