44
55import inspect
66import warnings
7- from typing import Any
7+ from typing import Any , Literal
88
99import gdsfactory as gf
1010import meep as mp
1111import numpy as np
1212from gdsfactory .component import Component
1313from gdsfactory .pdk import get_layer_stack
1414from gdsfactory .technology import LayerStack
15- from gdsfactory .typings import LayerSpecs
15+ from gdsfactory .typings import LayerSpecs , Float3
1616
1717from gplugins .common .base_models .component import move_polar_rad_copy
1818from gplugins .gmeep .get_material import get_material
2626settings_meep = set (sig .parameters .keys ())
2727
2828
29+ def is_point_in_plane (
30+ test_point : Float3 ,
31+ plane_support : Float3 ,
32+ plane_normal : Float3 ,
33+ tolerance : float = 1e-6 , # 1 pm for coordinates in µm
34+ ):
35+ a , b , c = plane_normal
36+ xt , yt , zt = test_point
37+ x0 , y0 , z0 = plane_support
38+ distance = (a * (xt - x0 ) + b * (yt - y0 ) + c * (zt - z0 )) / np .linalg .norm (plane_normal )
39+ return bool (abs (distance ) <= tolerance )
40+
41+
2942def get_simulation (
3043 component : Component ,
3144 resolution : int = 30 ,
@@ -36,6 +49,8 @@ def get_simulation(
3649 tpml : float = 1.5 ,
3750 clad_material : str = "SiO2" ,
3851 is_3d : bool = False ,
52+ normal_2d : Literal ['X' , 'Y' , 'Z' ] = 'Z' ,
53+ point_2d : tuple [float , float , float ] = (0 , 0 , 0 ),
3954 wavelength_start : float = 1.5 ,
4055 wavelength_stop : float = 1.6 ,
4156 wavelength_points : int = 50 ,
@@ -105,6 +120,8 @@ def get_simulation(
105120 tpml: PML thickness (um).
106121 clad_material: material for cladding.
107122 is_3d: if True runs in 3D.
123+ normal_2d: specified normal of 2D simulation plane
124+ point_2d: specifies support point for 2D simulation plane
108125 wavelength_start: wavelength min (um).
109126 wavelength_stop: wavelength max (um).
110127 wavelength_points: wavelength steps.
@@ -141,38 +158,27 @@ def get_simulation(
141158 for setting in settings :
142159 if setting not in settings_meep :
143160 raise ValueError (f"{ setting !r} not in { sorted (settings_meep )} " )
161+ normal_2d = normal_2d .upper ()
162+ normal_vec = {'X' : (1 , 0 , 0 ), 'Y' : (0 , 1 , 0 ), 'Z' : (0 , 0 , 1 )}[normal_2d ]
144163
145164 layer_stack = layer_stack or get_layer_stack ()
146165 layer_to_thickness = layer_stack .get_layer_to_thickness ()
147166
148- dummy_component = gf .Component ()
149- component_ref = dummy_component << component
150- component_ref .x = 0
151- component_ref .y = 0
152-
153167 wavelength = (wavelength_start + wavelength_stop ) / 2
154-
155168 wavelengths = np .linspace (wavelength_start , wavelength_stop , wavelength_points )
156- port_names = [port .name for port in component_ref .ports ]
157169
170+ port_names = [port .name for port in component .ports ]
158171 if port_source_name not in port_names :
159172 warnings .warn (f"port_source_name={ port_source_name !r} not in { port_names } " )
160- port_source = component_ref .ports [0 ]
173+ port_source = component .ports [0 ]
161174 port_source_name = port_source .name
162175 warnings .warn (f"Selecting port_source_name={ port_source_name !r} instead." )
163176
164177 assert isinstance (component , Component ), (
165178 f"component needs to be a gf.Component, got Type { type (component )} "
166179 )
167180
168- component_extended = (
169- gf .c .extend_ports (
170- component = component , length = extend_ports_length , centered = True
171- )
172- if extend_ports_length
173- else component
174- )
175-
181+ component_extended = gf .c .extend_ports (component = component , length = extend_ports_length )
176182 component_extended = component_extended .copy ()
177183 component_extended .flatten ()
178184
@@ -191,20 +197,19 @@ def get_simulation(
191197 t_core = sum (
192198 layers_thickness
193199 ) # This isn't exactly what we want but I think it's better than max
194- cell_thickness = tpml + zmargin_bot + t_core + zmargin_top + tpml if is_3d else 0
200+ cell_thickness = tpml + zmargin_bot + t_core + zmargin_top + tpml
195201
196202 cell_size = mp .Vector3 (
197- component .xsize + 2 * tpml ,
198- component .ysize + 2 * tpml ,
199- cell_thickness ,
203+ 0 if normal_2d == 'X' and not is_3d else component .xsize + 2 * tpml ,
204+ 0 if normal_2d == 'Y' and not is_3d else component .ysize + 2 * tpml ,
205+ 0 if normal_2d == 'Z' and not is_3d else cell_thickness ,
200206 )
201207
202208 geometry = get_meep_geometry_from_component (
203209 component = component_extended ,
204210 layer_stack = layer_stack ,
205211 material_name_to_meep = material_name_to_meep ,
206212 wavelength = wavelength ,
207- is_3d = is_3d ,
208213 dispersive = dispersive ,
209214 exclude_layers = exclude_layers ,
210215 )
@@ -214,19 +219,23 @@ def get_simulation(
214219 frequency_width = dfcen * fcen
215220
216221 # Add source
217- port = component_ref .ports [port_source_name ]
222+ port = component .ports [port_source_name ]
218223 angle_rad = np .radians (port .orientation )
219224 width = port .width + 2 * port_margin
220225 size_x = width * abs (np .sin (angle_rad ))
221226 size_y = width * abs (np .cos (angle_rad ))
222227 size_x = 0 if size_x < 0.001 else size_x
223228 size_y = 0 if size_y < 0.001 else size_y
224- size_z = cell_thickness - 2 * tpml if is_3d else 20
225- size = [size_x , size_y , size_z ]
229+ size_z = cell_thickness - 2 * tpml
230+ size = [
231+ 0 if normal_2d == 'X' and not is_3d else size_x ,
232+ 0 if normal_2d == 'Y' and not is_3d else size_y ,
233+ 0 if normal_2d == 'Z' and not is_3d else size_z ,
234+ ]
226235 xy_shifted = move_polar_rad_copy (
227236 np .array (port .center ), angle = angle_rad , length = port_source_offset
228237 )
229- center = xy_shifted .tolist () + [0 ] # (x, y, z=0)
238+ center = xy_shifted .round ( 6 ). tolist () + [0 ] # (x, y, z=0)
230239
231240 if np .isclose (port .orientation , 0 ):
232241 direction = mp .X
@@ -242,6 +251,11 @@ def get_simulation(
242251 "not 0, 90, 180, 270 degrees"
243252 )
244253
254+ if not (is_3d or is_point_in_plane (center , point_2d , normal_vec )):
255+ raise ValueError (
256+ f"Source '{ port_source_name } ' (center={ center } ) is not in { normal_2d } -normal 2D simulation domain around { point_2d } ."
257+ )
258+
245259 sources = [
246260 mp .EigenModeSource (
247261 src = mp .ContinuousSource (fcen )
@@ -250,15 +264,21 @@ def get_simulation(
250264 size = size ,
251265 center = center ,
252266 eig_band = port_source_mode + 1 ,
253- eig_parity = mp .NO_PARITY if is_3d else mp . EVEN_Y + mp . ODD_Z ,
267+ eig_parity = mp .NO_PARITY ,
254268 eig_match_freq = True ,
255269 eig_kpoint = - 1 * mp .Vector3 (x = 1 ).rotate (mp .Vector3 (z = 1 ), angle_rad ),
256270 direction = direction ,
257271 )
258272 ]
259273
274+ sim_center = mp .Vector3 (
275+ point_2d [0 ] if normal_2d == 'X' and not is_3d else component .x ,
276+ point_2d [1 ] if normal_2d == 'Y' and not is_3d else component .y ,
277+ point_2d [2 ] if normal_2d == 'Z' and not is_3d else 0 ,
278+ )
260279 sim = mp .Simulation (
261280 cell_size = cell_size ,
281+ geometry_center = sim_center ,
262282 boundary_layers = [mp .PML (tpml )],
263283 sources = sources ,
264284 geometry = geometry ,
@@ -273,16 +293,19 @@ def get_simulation(
273293
274294 # Add port monitors dict
275295 monitors = {}
276- for port in component_ref .ports :
296+ for port in component .ports :
277297 port_name = port .name
278298 angle_rad = np .radians (port .orientation )
279299 width = port .width + 2 * port_margin
280300 size_x = width * abs (np .sin (angle_rad ))
281301 size_y = width * abs (np .cos (angle_rad ))
282302 size_x = 0 if size_x < 0.001 else size_x
283303 size_y = 0 if size_y < 0.001 else size_y
284- size = mp .Vector3 (size_x , size_y , size_z )
285- size = [size_x , size_y , size_z ]
304+ size = mp .Vector3 (
305+ 0 if normal_2d == 'X' and not is_3d else size_x ,
306+ 0 if normal_2d == 'Y' and not is_3d else size_y ,
307+ 0 if normal_2d == 'Z' and not is_3d else size_z ,
308+ )
286309
287310 # if monitor has a source move monitor inwards
288311 length = (
@@ -293,10 +316,14 @@ def get_simulation(
293316 xy_shifted = move_polar_rad_copy (
294317 np .array (port .center ), angle = angle_rad , length = length
295318 )
296- center = xy_shifted .tolist () + [0 ] # (x, y, z=0)
297- m = sim .add_mode_monitor (freqs , mp .ModeRegion (center = center , size = size ))
298- m .z = 0
299- monitors [port_name ] = m
319+ center = xy_shifted .round (6 ).tolist () + [0 ] # (x, y, z=0)
320+ if is_3d or is_point_in_plane (center , point_2d , normal_vec ):
321+ m = sim .add_mode_monitor (freqs , mp .ModeRegion (center = center , size = size ))
322+ m .z = 0
323+ monitors [port_name ] = m
324+ else :
325+ warnings .warn (f"Monitor at port '{ port_name } ' ignored, "
326+ f"because it is not in the { normal_2d } -normal 2D simulation domain around { point_2d } ." )
300327 return dict (
301328 sim = sim ,
302329 cell_size = cell_size ,
0 commit comments