Skip to content

Commit 1fd61bf

Browse files
authored
Merge pull request #672 from gronniger/gr/2d_sims
Allow 2D simulations with meep in X- and Y- normal planes
2 parents 74da0bd + ddd2864 commit 1fd61bf

5 files changed

Lines changed: 75 additions & 53 deletions

File tree

gplugins/gmeep/get_meep_geometry.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ def get_meep_geometry_from_component(
1616
layer_stack: LayerStack | None = None,
1717
material_name_to_meep: dict[str, str | float] | None = None,
1818
wavelength: float = 1.55,
19-
is_3d: bool = False,
2019
dispersive: bool = False,
2120
exclude_layers: LayerSpecs | None = None,
2221
**kwargs,
@@ -28,7 +27,6 @@ def get_meep_geometry_from_component(
2827
layer_stack: for material layers.
2928
material_name_to_meep: maps layer_stack name to meep material name.
3029
wavelength: in um.
31-
is_3d: renders in 3D.
3230
dispersive: add dispersion.
3331
exclude_layers: these layers are ignored during geometry creation.
3432
kwargs: settings.
@@ -62,10 +60,9 @@ def get_meep_geometry_from_component(
6260
if layer_index in exclude_layers or layer_index not in polygons_per_layer:
6361
continue
6462

65-
zmin_um = level.zmin if is_3d else 0
66-
sw_angle = np.pi * level.sidewall_angle / 180 if is_3d else 0
63+
sw_angle = np.pi * level.sidewall_angle / 180
6764
for polygon in polygons_per_layer[layer_index]:
68-
vertices = [mp.Vector3(p[0], p[1], zmin_um) for p in polygon]
65+
vertices = [mp.Vector3(p[0], p[1], level.zmin) for p in polygon]
6966
material_name = level.material
7067

7168
if material_name:
@@ -79,7 +76,7 @@ def get_meep_geometry_from_component(
7976
mp.Prism(
8077
vertices=vertices,
8178
height=level.thickness,
82-
sidewall_angle=sw_angle,
79+
sidewall_angle=sw_angle, # TODO: libctl has issues with slanted prisms -> incorrect geometry
8380
material=material,
8481
)
8582
)

gplugins/gmeep/get_simulation.py

Lines changed: 62 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44

55
import inspect
66
import warnings
7-
from typing import Any
7+
from typing import Any, Literal
88

99
import gdsfactory as gf
1010
import meep as mp
1111
import numpy as np
1212
from gdsfactory.component import Component
1313
from gdsfactory.pdk import get_layer_stack
1414
from gdsfactory.technology import LayerStack
15-
from gdsfactory.typings import LayerSpecs
15+
from gdsfactory.typings import LayerSpecs, Float3
1616

1717
from gplugins.common.base_models.component import move_polar_rad_copy
1818
from gplugins.gmeep.get_material import get_material
@@ -26,6 +26,19 @@
2626
settings_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+
2942
def 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,

gplugins/gmeep/test_materials.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from gplugins.common.utils import optical_constants
44
from gplugins.gmeep.write_sparameters_meep import write_sparameters_meep
55

6+
gf.gpdk.PDK.activate()
7+
68

79
def test_materials_override() -> None:
810
"""Checks that materials are properly overridden if index is provided."""

gplugins/gmeep/test_write_sparameters_meep.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import gplugins as sim
99
import gplugins.gmeep as gm
1010

11+
gf.gpdk.PDK.activate()
1112
simulation_settings = dict(resolution=20, is_3d=False)
1213

1314

gplugins/gmeep/write_sparameters_meep.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from gdsfactory.pdk import get_layer_stack
2020
from gdsfactory.serialization import clean_value_json
2121
from gdsfactory.technology import LayerStack
22-
from gdsfactory.typings import ComponentSpec, PathType, Port, PortSymmetries, LayerSpec
22+
from gdsfactory.typings import ComponentSpec, PathType, Ports, PortSymmetries, LayerSpec
2323
from tqdm.auto import tqdm
2424

2525
from gplugins.common.utils import port_symmetries
@@ -52,14 +52,15 @@ def remove_simulation_kwargs(d: dict[str, Any]) -> dict[str, Any]:
5252

5353

5454
def parse_port_eigenmode_coeff(
55-
port_name: str, ports: dict[str, Port], sim_dict: dict, port_mode: int = 0
55+
port_name: str, ports: Ports, sim_dict: dict, port_mode: int = 0
5656
):
5757
"""Returns the coefficients relative to whether the wavevector is entering or exiting simulation.
5858
5959
Args:
60-
port_index: index of port.
60+
port_name: name of port.
6161
ports: component_ref.ports.
6262
sim_dict: simulation dict.
63+
port_mode: mode to get coefficient for
6364
6465
"""
6566
if port_name not in ports:
@@ -384,7 +385,7 @@ def write_sparameters_meep(
384385
sim.plot2D(
385386
output_plane=mp.Volume(
386387
size=mp.Vector3(sim.cell_size.x, sim.cell_size.y, 0),
387-
center=mp.Vector3(0, 0, z),
388+
center=mp.Vector3(component.x, component.y, z),
388389
),
389390
**plot_args,
390391
)
@@ -406,7 +407,6 @@ def sparameter_calculation(
406407
port_source_name: str,
407408
component: Component,
408409
port_symmetries: PortSymmetries | None = port_symmetries,
409-
port_names: list[str] = port_names,
410410
port_source_mode: int = 0,
411411
wavelength_start: float = wavelength_start,
412412
wavelength_stop: float = wavelength_stop,
@@ -438,11 +438,8 @@ def sparameter_calculation(
438438
)
439439

440440
sim = sim_dict["sim"]
441-
# freqs = sim_dict["freqs"]
442-
# wavelengths = 1 / freqs
443-
# print(sim.resolution)
444441

445-
# Terminate when the area in the whole area decayed
442+
# Terminate when the energy in the whole area decayed
446443
termination = [mp.stop_when_energy_decayed(dt=50, decay_by=decay_by)]
447444

448445
if animate:
@@ -493,7 +490,7 @@ def sparameter_calculation(
493490
port_source_name, component.ports, sim_dict, port_mode=port_source_mode
494491
)
495492
# Get coefficients
496-
for port_name in port_names:
493+
for port_name in sim_dict['monitors'].keys():
497494
for port_mode in port_modes:
498495
_, monitor_exiting = parse_port_eigenmode_coeff(
499496
port_name,
@@ -535,7 +532,6 @@ def sparameter_calculation(
535532
wavelength_stop=wavelength_stop,
536533
wavelength_points=wavelength_points,
537534
animate=animate,
538-
port_names=port_names,
539535
**settings,
540536
)
541537
# Synchronize dicts
@@ -568,7 +564,6 @@ def sparameter_calculation(
568564
wavelength_stop=wavelength_stop,
569565
wavelength_points=wavelength_points,
570566
animate=animate,
571-
port_names=port_names,
572567
**settings,
573568
)
574569
)

0 commit comments

Comments
 (0)