@@ -243,6 +243,121 @@ def _solve_fn(self, eps_vec, entries_a, indices_a, Mz_vec):
243
243
244
244
return Ex_vec , Ey_vec , Hz_vec
245
245
246
+
247
+ class fdfd_mf_ez (fdfd ):
248
+ """ FDFD class for multifrequency linear Ez polarization. New variables:
249
+ omega_mod: angular frequency of modulation (rad/s)
250
+ delta: array of shape (Nfreq, Nx, Ny) containing pointwise modulation depth for each modulation harmonic (1,...,Nfreq)
251
+ phi: array of same shape as delta containing pointwise modulation phase for each modulation harmonic
252
+ Nsb: number of numerical sidebands to consider when solving for fields.
253
+ This is not the same as the number of modulation frequencies Nfreq. For physically meaningful results, Nsb >= Nfreq.
254
+ """
255
+
256
+ def __init__ (self , omega , dL , eps_r , omega_mod , delta , phi , Nsb , npml , bloch_phases = None ):
257
+ super ().__init__ (omega , dL , eps_r , npml , bloch_phases = bloch_phases )
258
+ self .omega_mod = omega_mod
259
+ self .delta = delta
260
+ self .phi = phi
261
+ self .Nsb = Nsb
262
+
263
+ def solve (self , source_z ):
264
+ """ Outward facing function (what gets called by user) that takes a source grid and returns the field components """
265
+ # flatten the permittivity and source grid
266
+ source_vec = self ._grid_to_vec (source_z )
267
+ eps_vec = self ._grid_to_vec (self .eps_r )
268
+ Nfreq = npa .shape (self .delta )[0 ]
269
+ delta_matrix = self .delta .reshape ([Nfreq , npa .prod (self .shape )])
270
+ phi_matrix = self .phi .reshape ([Nfreq , npa .prod (self .shape )])
271
+ # create the A matrix for this polarization
272
+ entries_a , indices_a = self ._make_A (eps_vec , delta_matrix , phi_matrix )
273
+
274
+ # solve field componets usng A and the source
275
+ Fx_vec , Fy_vec , Fz_vec = self ._solve_fn (eps_vec , entries_a , indices_a , source_vec )
276
+
277
+ # put all field components into a tuple, convert to grid shape and return them all
278
+ Fx = self ._vec_to_grid (Fx_vec )
279
+ Fy = self ._vec_to_grid (Fy_vec )
280
+ Fz = self ._vec_to_grid (Fz_vec )
281
+
282
+ return Fx , Fy , Fz
283
+
284
+ def _make_A (self , eps_vec , delta_matrix , phi_matrix ):
285
+ """ Builds the multi-frequency electromagnetic operator A in Ax = b """
286
+ M = 2 * self .Nsb + 1
287
+ N = self .Nx * self .Ny
288
+ W = self .omega + npa .arange (- self .Nsb ,self .Nsb + 1 )* self .omega_mod
289
+
290
+ C = sp .kron (sp .eye (M ), - 1 / MU_0 * self .Dxf .dot (self .Dxb ) - 1 / MU_0 * self .Dyf .dot (self .Dyb ))
291
+ entries_c , indices_c = get_entries_indices (C )
292
+
293
+ # diagonal entries representing static refractive index
294
+ # this part is just a block diagonal version of the single frequency fdfd_ez
295
+ entries_diag = - EPSILON_0 * npa .kron (W ** 2 , eps_vec )
296
+ indices_diag = npa .vstack ((npa .arange (M * N ), npa .arange (M * N )))
297
+
298
+ entries_a = npa .hstack ((entries_diag , entries_c ))
299
+ indices_a = npa .hstack ((indices_diag , indices_c ))
300
+
301
+ # off-diagonal entries representing dynamic modulation
302
+ # this part couples different frequencies due to modulation
303
+ # for a derivation of these entries, see Y. Shi, W. Shin, and S. Fan. Optica 3(11), 2016.
304
+ Nfreq = npa .shape (delta_matrix )[0 ]
305
+ for k in npa .arange (Nfreq ):
306
+ # super-diagonal entries (note the +1j phase)
307
+ mod_p = - 0.5 * EPSILON_0 * delta_matrix [k ,:] * npa .exp (1j * phi_matrix [k ,:])
308
+ entries_p = npa .kron (W [:- k - 1 ]** 2 , mod_p )
309
+ indices_p = npa .vstack ((npa .arange ((M - k - 1 )* N ), npa .arange ((k + 1 )* N , M * N )))
310
+ entries_a = npa .hstack ((entries_p , entries_a ))
311
+ indices_a = npa .hstack ((indices_p ,indices_a ))
312
+ # sub-diagonal entries (note the -1j phase)
313
+ mod_m = - 0.5 * EPSILON_0 * delta_matrix [k ,:] * npa .exp (- 1j * phi_matrix [k ,:])
314
+ entries_m = npa .kron (W [k + 1 :]** 2 , mod_m )
315
+ indices_m = npa .vstack ((npa .arange ((k + 1 )* N , M * N ), npa .arange ((M - k - 1 )* N )))
316
+ entries_a = npa .hstack ((entries_m , entries_a ))
317
+ indices_a = npa .hstack ((indices_m ,indices_a ))
318
+
319
+ return entries_a , indices_a
320
+
321
+ def _solve_fn (self , eps_vec , entries_a , indices_a , Jz_vec ):
322
+ """ Multi-frequency version of _solve_fn() defined in fdfd_ez """
323
+ M = 2 * self .Nsb + 1
324
+ N = self .Nx * self .Ny
325
+ W = self .omega + npa .arange (- self .Nsb ,self .Nsb + 1 )* self .omega_mod
326
+ P = sp .kron (sp .spdiags (W ,[0 ],M ,M ), sp .eye (N ))
327
+ entries_p , indices_p = get_entries_indices (P )
328
+ b_vec = 1j * sp_mult (entries_p ,indices_p ,Jz_vec )
329
+ Ez_vec = sp_solve (entries_a , indices_a , b_vec )
330
+ Hx_vec , Hy_vec = self ._Ez_to_Hx_Hy (Ez_vec )
331
+ return Hx_vec , Hy_vec , Ez_vec
332
+
333
+ def _Ez_to_Hx (self , Ez_vec ):
334
+ """ Multi-frequency version of _Ez_to_Hx() defined in fdfd """
335
+ M = 2 * self .Nsb + 1
336
+ Winv = 1 / (self .omega + npa .arange (- self .Nsb ,self .Nsb + 1 )* self .omega_mod )
337
+ Dyb_mf = sp .kron (sp .spdiags (Winv ,[0 ],M ,M ), self .Dyb )
338
+ entries_Dyb_mf , indices_Dyb_mf = get_entries_indices (Dyb_mf )
339
+ return - 1 / 1j / MU_0 * sp_mult (entries_Dyb_mf , indices_Dyb_mf , Ez_vec )
340
+
341
+ def _Ez_to_Hy (self , Ez_vec ):
342
+ """ Multi-frequency version of _Ez_to_Hy() defined in fdfd """
343
+ M = 2 * self .Nsb + 1
344
+ Winv = 1 / (self .omega + npa .arange (- self .Nsb ,self .Nsb + 1 )* self .omega_mod )
345
+ Dxb_mf = sp .kron (sp .spdiags (Winv ,[0 ],M ,M ), self .Dxb )
346
+ entries_Dxb_mf , indices_Dxb_mf = get_entries_indices (Dxb_mf )
347
+ return 1 / 1j / MU_0 * sp_mult (entries_Dxb_mf , indices_Dxb_mf , Ez_vec )
348
+
349
+ def _Ez_to_Hx_Hy (self , Ez_vec ):
350
+ """ Multi-frequency version of _Ez_to_Hx_Hy() defined in fdfd """
351
+ Hx_vec = self ._Ez_to_Hx (Ez_vec )
352
+ Hy_vec = self ._Ez_to_Hy (Ez_vec )
353
+ return Hx_vec , Hy_vec
354
+
355
+ def _vec_to_grid (self , vec ):
356
+ """ Multi-frequency version of _vec_to_grid() defined in fdfd """
357
+ # grid shape has Nx*Ny cells per frequency sideband
358
+ grid_shape = (2 * self .Nsb + 1 , self .Nx , self .Ny )
359
+ return npa .reshape (vec , grid_shape )
360
+
246
361
class fdfd_3d (fdfd ):
247
362
""" 3D FDFD class (work in progress) """
248
363
0 commit comments