gsim 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. {gsim-0.2.0/src/gsim.egg-info → gsim-0.3.0}/PKG-INFO +4 -2
  2. {gsim-0.2.0 → gsim-0.3.0}/README.md +1 -1
  3. {gsim-0.2.0 → gsim-0.3.0}/pyproject.toml +3 -14
  4. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/__init__.py +1 -1
  5. gsim-0.3.0/src/gsim/common/materials.py +191 -0
  6. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/polygon.py +23 -6
  7. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/stack/extractor.py +17 -4
  8. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/meep/mode_solver.py +3 -1
  9. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/meep/models/results.py +2 -2
  10. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/meep/simulation.py +59 -4
  11. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/meep/viz.py +5 -35
  12. {gsim-0.2.0 → gsim-0.3.0/src/gsim.egg-info}/PKG-INFO +4 -2
  13. {gsim-0.2.0 → gsim-0.3.0}/src/gsim.egg-info/SOURCES.txt +1 -0
  14. {gsim-0.2.0 → gsim-0.3.0}/src/gsim.egg-info/requires.txt +1 -0
  15. {gsim-0.2.0 → gsim-0.3.0}/LICENSE +0 -0
  16. {gsim-0.2.0 → gsim-0.3.0}/setup.cfg +0 -0
  17. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/__init__.py +0 -0
  18. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/cross_section.py +0 -0
  19. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/geometry.py +0 -0
  20. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/geometry_model.py +0 -0
  21. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/layered_component.py +0 -0
  22. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/polygon_utils.py +0 -0
  23. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/stack/__init__.py +0 -0
  24. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/stack/_layer_utils.py +0 -0
  25. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/stack/materials.py +0 -0
  26. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/stack/overlays.py +0 -0
  27. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/stack/visualization.py +0 -0
  28. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/types.py +0 -0
  29. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/viz/__init__.py +0 -0
  30. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/viz/_colors.py +0 -0
  31. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/viz/_mesh_helpers.py +0 -0
  32. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/viz/render2d.py +0 -0
  33. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/viz/render2d_interactive.py +0 -0
  34. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/viz/render3d.py +0 -0
  35. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/viz/render3d_open3d.py +0 -0
  36. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/common/viz/render3d_pyvista.py +0 -0
  37. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/gcloud.py +0 -0
  38. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/hashing.py +0 -0
  39. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/meep/__init__.py +0 -0
  40. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/meep/materials.py +0 -0
  41. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/meep/models/__init__.py +0 -0
  42. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/meep/models/api.py +0 -0
  43. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/meep/models/config.py +0 -0
  44. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/meep/overlay.py +0 -0
  45. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/meep/ports.py +0 -0
  46. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/meep/results.py +0 -0
  47. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/meep/script.py +0 -0
  48. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/__init__.py +0 -0
  49. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/base.py +0 -0
  50. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/boundarymode.py +0 -0
  51. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/driven.py +0 -0
  52. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/eigenmode.py +0 -0
  53. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/electrostatic.py +0 -0
  54. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/field_viz.py +0 -0
  55. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/fields.py +0 -0
  56. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/materials.py +0 -0
  57. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/mesh/__init__.py +0 -0
  58. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/mesh/auto_size.py +0 -0
  59. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/mesh/config_generator.py +0 -0
  60. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/mesh/generator.py +0 -0
  61. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/mesh/geometry.py +0 -0
  62. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/mesh/gmsh_utils.py +0 -0
  63. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/mesh/groups.py +0 -0
  64. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/mesh/validation.py +0 -0
  65. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/models/__init__.py +0 -0
  66. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/models/cross_section.py +0 -0
  67. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/models/geometry.py +0 -0
  68. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/models/mesh.py +0 -0
  69. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/models/numerical.py +0 -0
  70. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/models/pec.py +0 -0
  71. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/models/ports.py +0 -0
  72. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/models/problems.py +0 -0
  73. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/models/results.py +0 -0
  74. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/models/stack.py +0 -0
  75. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/ports/__init__.py +0 -0
  76. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/ports/config.py +0 -0
  77. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/results.py +0 -0
  78. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/palace/runtime.py +0 -0
  79. {gsim-0.2.0 → gsim-0.3.0}/src/gsim/viz.py +0 -0
  80. {gsim-0.2.0 → gsim-0.3.0}/src/gsim.egg-info/dependency_links.txt +0 -0
  81. {gsim-0.2.0 → gsim-0.3.0}/src/gsim.egg-info/top_level.txt +0 -0
  82. {gsim-0.2.0 → gsim-0.3.0}/tests/test_gcloud_cache.py +0 -0
  83. {gsim-0.2.0 → gsim-0.3.0}/tests/test_gcloud_start.py +0 -0
  84. {gsim-0.2.0 → gsim-0.3.0}/tests/test_gsim.py +0 -0
  85. {gsim-0.2.0 → gsim-0.3.0}/tests/test_hashing.py +0 -0
  86. {gsim-0.2.0 → gsim-0.3.0}/tests/test_viz_mesh.py +0 -0
@@ -1,6 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gsim
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
+ Summary: Electromagnetic simulation for photonics and electronics, powered by GDSFactory.
4
5
  Author-email: GDSFactory <contact@gdsfactory.com>
5
6
  License: Apache-2.0
6
7
  Classifier: License :: OSI Approved :: Apache Software License
@@ -15,6 +16,7 @@ Requires-Dist: gmsh
15
16
  Requires-Dist: meshio>=5.0.0
16
17
  Requires-Dist: nbformat>=4.2.0
17
18
  Requires-Dist: nest_asyncio2
19
+ Requires-Dist: pdk-schema==0.0.2
18
20
  Requires-Dist: plotly
19
21
  Requires-Dist: pyvista>=0.43.0
20
22
  Requires-Dist: trame
@@ -22,7 +24,7 @@ Requires-Dist: trame-vtk
22
24
  Requires-Dist: trame-vuetify
23
25
  Dynamic: license-file
24
26
 
25
- # gsim 0.2.0
27
+ # gsim 0.3.0
26
28
 
27
29
  > Electromagnetic simulation for photonics and electronics, powered by [GDSFactory+](https://gdsfactory.com)
28
30
 
@@ -1,4 +1,4 @@
1
- # gsim 0.2.0
1
+ # gsim 0.3.0
2
2
 
3
3
  > Electromagnetic simulation for photonics and electronics, powered by [GDSFactory+](https://gdsfactory.com)
4
4
 
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "gsim"
3
- version = "0.2.0"
4
- description = ""
3
+ version = "0.3.0"
4
+ description = "Electromagnetic simulation for photonics and electronics, powered by GDSFactory."
5
5
  readme = "README.md"
6
6
  requires-python = "~=3.12.0"
7
7
  license = { text = "Apache-2.0" }
@@ -18,6 +18,7 @@ dependencies = [
18
18
  "meshio>=5.0.0",
19
19
  "nbformat>=4.2.0",
20
20
  "nest_asyncio2",
21
+ "pdk-schema==0.0.2",
21
22
  "plotly",
22
23
  "pyvista>=0.43.0",
23
24
  "trame",
@@ -69,18 +70,6 @@ test = [
69
70
  requires = ["build", "setuptools>=61", "uv", "wheel"]
70
71
  build-backend = "setuptools.build_meta"
71
72
 
72
- [[tool.bver.file]]
73
- src = "README.md"
74
-
75
- [[tool.bver.file]]
76
- src = "pyproject.toml"
77
-
78
- [[tool.bver.file]]
79
- src = "src/gsim/__init__.py"
80
-
81
- [tool.bver.git]
82
- commit-template = "Release {new-version}"
83
-
84
73
  [tool.codespell]
85
74
  ignore-words-list = "doubleclick,euclidian,te"
86
75
 
@@ -12,7 +12,7 @@ from __future__ import annotations
12
12
 
13
13
  from gsim.gcloud import get_status, wait_for_results
14
14
 
15
- __version__ = "0.2.0"
15
+ __version__ = "0.3.0"
16
16
 
17
17
  __all__ = [
18
18
  "__version__",
@@ -0,0 +1,191 @@
1
+ """Built-in optical material cards."""
2
+
3
+ from collections.abc import Mapping
4
+
5
+ import gdsfactory as gf
6
+ from pdk_schema import (
7
+ Band,
8
+ Coord,
9
+ Index,
10
+ MaterialCard,
11
+ Provenance,
12
+ Regime,
13
+ Sellmeier,
14
+ SellmeierTerm,
15
+ TableData,
16
+ TabulatedValue,
17
+ Validity,
18
+ )
19
+
20
+ _LI_293K_DATA = (
21
+ (1.20, 3.5167),
22
+ (1.22, 3.5133),
23
+ (1.24, 3.5102),
24
+ (1.26, 3.5072),
25
+ (1.28, 3.5043),
26
+ (1.30, 3.5016),
27
+ (1.32, 3.4990),
28
+ (1.34, 3.4965),
29
+ (1.36, 3.4941),
30
+ (1.38, 3.4918),
31
+ (1.40, 3.4896),
32
+ (1.45, 3.4845),
33
+ (1.50, 3.4799),
34
+ (1.55, 3.4757),
35
+ (1.60, 3.4719),
36
+ (1.65, 3.4684),
37
+ (1.70, 3.4653),
38
+ (1.80, 3.4597),
39
+ (1.90, 3.4550),
40
+ (2.00, 3.4510),
41
+ (2.25, 3.4431),
42
+ (2.50, 3.4375),
43
+ (2.75, 3.4334),
44
+ (3.00, 3.4302),
45
+ (4.00, 3.4229),
46
+ (5.00, 3.4195),
47
+ (6.00, 3.4177),
48
+ (7.00, 3.4165),
49
+ (8.00, 3.4158),
50
+ (9.00, 3.4153),
51
+ (10.0, 3.4150),
52
+ (11.0, 3.4147),
53
+ (12.0, 3.4145),
54
+ (13.0, 3.4144),
55
+ (14.0, 3.4142),
56
+ )
57
+
58
+
59
+ def _validity(minimum_um: float, maximum_um: float) -> Validity:
60
+ """Return a strict wavelength validity range in micrometers."""
61
+ return Validity(
62
+ at=None,
63
+ over={
64
+ "wavelength": Band(
65
+ min=minimum_um,
66
+ max=maximum_um,
67
+ unit="um",
68
+ label=None,
69
+ )
70
+ },
71
+ on_out_of_range="raise",
72
+ )
73
+
74
+
75
+ def _provenance(label: str) -> Provenance:
76
+ """Return the minimum required literature provenance."""
77
+ return Provenance(
78
+ source="literature",
79
+ label=label,
80
+ maturity="empirical",
81
+ citations=[],
82
+ comment=None,
83
+ url=None,
84
+ data_url=None,
85
+ info={},
86
+ )
87
+
88
+
89
+ def _card(
90
+ name: str,
91
+ permittivity: Index | Sellmeier,
92
+ temperature_ref: float,
93
+ ) -> MaterialCard:
94
+ """Build an optical material card."""
95
+ return MaterialCard(
96
+ name=name,
97
+ optical=Regime(
98
+ temperature_ref=temperature_ref,
99
+ provenance=_provenance(name),
100
+ permittivity=permittivity,
101
+ conductivity=None,
102
+ permeability=None,
103
+ perturbations=[],
104
+ info={},
105
+ ),
106
+ rf=None,
107
+ info={},
108
+ )
109
+
110
+
111
+ _SI_SALZBERG = _card(
112
+ name="Si-Salzberg",
113
+ temperature_ref=299.15,
114
+ permittivity=Sellmeier(
115
+ validity=_validity(1.357, 11.04),
116
+ variation=None,
117
+ conductivity=None,
118
+ terms=(
119
+ SellmeierTerm(b=10.6684293, c_um=0.301516485),
120
+ SellmeierTerm(b=0.0030434748, c_um=1.13475115),
121
+ SellmeierTerm(b=1.54133408, c_um=1104.0),
122
+ ),
123
+ offset=0.0,
124
+ ),
125
+ )
126
+
127
+ _SI_LI_293K = _card(
128
+ name="Si-Li-293K",
129
+ temperature_ref=293.0,
130
+ permittivity=Index(
131
+ validity=_validity(1.2, 14.0),
132
+ variation=None,
133
+ conductivity=None,
134
+ n=TabulatedValue(
135
+ unit="",
136
+ data=TableData(
137
+ dims=("wavelength",),
138
+ coords={
139
+ "wavelength": Coord(
140
+ values=[row[0] for row in _LI_293K_DATA],
141
+ unit="um",
142
+ )
143
+ },
144
+ values=[row[1] for row in _LI_293K_DATA],
145
+ attrs={},
146
+ interp="linear",
147
+ ),
148
+ ),
149
+ k=None,
150
+ ),
151
+ )
152
+
153
+ _SIO2_MALITSON = _card(
154
+ name="SiO2-Malitson",
155
+ temperature_ref=293.0,
156
+ permittivity=Sellmeier(
157
+ validity=_validity(0.21, 6.7),
158
+ variation=None,
159
+ conductivity=None,
160
+ terms=(
161
+ SellmeierTerm(b=0.6961663, c_um=0.0684043),
162
+ SellmeierTerm(b=0.4079426, c_um=0.1162414),
163
+ SellmeierTerm(b=0.8974794, c_um=9.896161),
164
+ ),
165
+ offset=0.0,
166
+ ),
167
+ )
168
+
169
+ GSIM_MATERIAL_CARDS: dict[str, MaterialCard] = {
170
+ "Si": _SI_SALZBERG.model_copy(update={"name": "Si"}),
171
+ "Si-Salzberg": _SI_SALZBERG,
172
+ "Si-Li-293K": _SI_LI_293K,
173
+ "SiO2": _SIO2_MALITSON.model_copy(update={"name": "SiO2"}),
174
+ "SiO2-Malitson": _SIO2_MALITSON,
175
+ }
176
+
177
+
178
+ def get_material_card(
179
+ material_name: str,
180
+ project_material_cards: Mapping[str, MaterialCard] | None = None,
181
+ ) -> MaterialCard:
182
+ """Return an active-PDK card when present, otherwise a built-in card."""
183
+ if project_material_cards is None:
184
+ active_pdk = gf.get_active_pdk()
185
+ project_material_cards = getattr(active_pdk, "material_cards", None)
186
+ if project_material_cards and material_name in project_material_cards:
187
+ return project_material_cards[material_name]
188
+ return GSIM_MATERIAL_CARDS[material_name]
189
+
190
+
191
+ __all__ = ["GSIM_MATERIAL_CARDS", "get_material_card"]
@@ -55,7 +55,26 @@ def fuse_polygons(
55
55
  Returns:
56
56
  Merged Shapely Polygon or MultiPolygon
57
57
  """
58
- layer_region = layer.get_shapes(component)
58
+ source_layer = getattr(layer, "layer", layer)
59
+ derived_layer = getattr(layer, "derived_layer", None)
60
+
61
+ # Simulation export materializes derived layers onto their GDS targets.
62
+ # Prefer those polygons when present: re-evaluating the source Boolean
63
+ # expression loses LayerLevel.background semantics (notably full-height
64
+ # grating teeth). Fall back to evaluating the source expression for the
65
+ # ordinary, unmaterialized visualization path.
66
+ layer_region = None
67
+ if derived_layer is not None:
68
+ target = tuple(derived_layer.layer)
69
+ layer_index = component.kcl.layer(*target)
70
+ target_region = component.kdb_cell.begin_shapes_rec(layer_index)
71
+ if not target_region.at_end():
72
+ from kfactory import kdb
73
+
74
+ layer_region = kdb.Region(target_region)
75
+
76
+ if layer_region is None:
77
+ layer_region = source_layer.get_shapes(component)
59
78
 
60
79
  shapely_polygons = []
61
80
  for klayout_polygon in layer_region.each_merged():
@@ -100,15 +119,13 @@ def cleanup_component(
100
119
  Returns:
101
120
  Dict mapping layer name to merged Shapely polygon
102
121
  """
103
- layer_stack_dict = layer_stack.to_dict()
104
-
105
122
  return {
106
123
  layername: fuse_polygons(
107
124
  component,
108
- layer["layer"],
125
+ layer,
109
126
  round_tol=round_tol,
110
127
  simplify_tol=simplify_tol,
111
128
  )
112
- for layername, layer in layer_stack_dict.items()
113
- if layer["layer"] is not None
129
+ for layername, layer in layer_stack.layers.items()
130
+ if layer.layer is not None
114
131
  }
@@ -491,6 +491,13 @@ def extract_layer_stack(
491
491
  if not _is_dielectric_material(material):
492
492
  continue
493
493
  if not _looks_like_passivation(layer_name, material):
494
+ if material.strip().lower() in {
495
+ "si",
496
+ "silicon",
497
+ "ge",
498
+ "germanium",
499
+ }:
500
+ continue
494
501
  bulk_material = material
495
502
  break
496
503
 
@@ -502,13 +509,19 @@ def extract_layer_stack(
502
509
 
503
510
  if bulk_material is None and sorted_dielectric_layers:
504
511
  for _zmin, _zmax, material, _layer_name in sorted_dielectric_layers:
505
- if _is_dielectric_material(material):
512
+ # Silicon/Ge layers can be classified as dielectrics for geometry
513
+ # purposes, but they are not a suitable blanket cladding. If a
514
+ # PDK has no oxide-like layer, use the explicit SiO2 fallback
515
+ # below rather than turning the synthetic BOX/cladding into Si.
516
+ if _is_dielectric_material(material) and material.strip().lower() not in {
517
+ "si",
518
+ "silicon",
519
+ "ge",
520
+ "germanium",
521
+ }:
506
522
  bulk_material = material
507
523
  break
508
524
 
509
- if bulk_material is None and sorted_dielectric_layers:
510
- bulk_material = sorted_dielectric_layers[0][2]
511
-
512
525
  if bulk_material is None:
513
526
  # Rare fallback when a PDK stack defines no dielectric layers.
514
527
  bulk_material = "SiO2"
@@ -1270,7 +1270,7 @@ def refractive_index_profile(
1270
1270
  if layer.material == "air":
1271
1271
  continue
1272
1272
  gds_layer = getattr(layer, "gds_layer", None)
1273
- if gds_layer is None:
1273
+ if not isinstance(gds_layer, tuple):
1274
1274
  continue
1275
1275
  intervals = interval_func(component, layer, cut_coord)
1276
1276
  if not intervals and not _layer_has_any_polygon(component, layer):
@@ -1292,6 +1292,8 @@ def refractive_index_profile(
1292
1292
  if eps <= 0:
1293
1293
  continue
1294
1294
  gds_layer = getattr(layer, "gds_layer", None)
1295
+ if not isinstance(gds_layer, tuple):
1296
+ continue
1295
1297
  h_mask = layer_h_masks.get(gds_layer)
1296
1298
  if h_mask is None:
1297
1299
  continue
@@ -475,9 +475,9 @@ class ModeResult(BaseModel):
475
475
  raise ValueError(
476
476
  "Ambiguous geometry: both x_grid and y_grid are populated."
477
477
  )
478
- if has_y:
478
+ if self.y_grid is not None:
479
479
  return self.y_grid, "y (µm)"
480
- if has_x:
480
+ if self.x_grid is not None:
481
481
  return self.x_grid, "x (µm)"
482
482
  raise ValueError(
483
483
  "No horizontal grid available. Set x_grid or y_grid on ModeResult."
@@ -553,7 +553,52 @@ class Simulation(BaseModel):
553
553
  if mz_high < needed:
554
554
  self.domain.margin_z = (mz_low, needed)
555
555
 
556
- def _resolve_z_ref_extent(self) -> tuple[float, float, str, float | None, bool]:
556
+ def _component_with_derived_layers(self, component: Any) -> Any:
557
+ """Return *component* augmented with any active-PDK derived layers.
558
+
559
+ GDS stores source mask layers, whereas a gdsfactory ``LayerStack`` may
560
+ describe physical layers using boolean expressions and ``derived_layer``
561
+ targets. MEEP needs both: the evaluated layers for the physical stack
562
+ and the original layers for stack entries that are not derived.
563
+ """
564
+ import gdsfactory as gf
565
+
566
+ pdk_layer_stack = gf.get_active_pdk().layer_stack
567
+ if pdk_layer_stack is None:
568
+ return component.copy()
569
+ if not any(
570
+ level.derived_layer is not None for level in pdk_layer_stack.layers.values()
571
+ ):
572
+ return component.copy()
573
+
574
+ derived = pdk_layer_stack.get_component_with_derived_layers(component)
575
+ # ``get_component_with_derived_layers`` evaluates each layer's
576
+ # Boolean expression but does not apply gdsfactory's ``background``
577
+ # semantics. Recreate those physical layers from the component bbox
578
+ # and the declared etch exclusions, matching ``Component.to_3d()``.
579
+ # This keeps the hierarchy intact; no flattening is required.
580
+ from kfactory import kdb
581
+
582
+ for level in pdk_layer_stack.layers.values():
583
+ if not getattr(level, "background", False) or level.derived_layer is None:
584
+ continue
585
+
586
+ target_layer = gf.get_layer_tuple(level.derived_layer.layer)
587
+ background = kdb.Region(component.kdb_cell.bbox())
588
+ for excluded_layer in getattr(level, "background_exclude_layers", ()):
589
+ layer_index = component.kcl.layer(*tuple(excluded_layer))
590
+ background -= kdb.Region(
591
+ component.kdb_cell.begin_shapes_rec(layer_index)
592
+ )
593
+ derived.remove_layers([target_layer])
594
+ derived.add_polygon(background, layer=target_layer)
595
+
596
+ derived.add_ref(component)
597
+ return derived
598
+
599
+ def _resolve_z_ref_extent(
600
+ self, component: Any | None = None
601
+ ) -> tuple[float, float, str, float | None, bool]:
557
602
  """Resolve ``domain.z_ref`` to a vertical reference window.
558
603
 
559
604
  Single source of truth for both the fiber-margin expansion and the
@@ -588,7 +633,8 @@ class Simulation(BaseModel):
588
633
 
589
634
  if z_ref is None:
590
635
  layer, n = _find_highest_n_layer_in_component(
591
- self.geometry.component, stack
636
+ component if component is not None else self.geometry.component,
637
+ stack,
592
638
  )
593
639
  if layer is None:
594
640
  extent = self._stack_material_extent()
@@ -1139,11 +1185,21 @@ class Simulation(BaseModel):
1139
1185
  if self.geometry.component is None:
1140
1186
  raise ValueError("No geometry set.")
1141
1187
 
1188
+ # Materialize derived PDK layers before inspecting or exporting the
1189
+ # geometry. A number of PDKs define the physical core as a boolean
1190
+ # expression of fabrication-mask layers; those derived polygons are
1191
+ # not present in an imported GDS until the layer stack evaluates them.
1192
+ original_component = self._component_with_derived_layers(
1193
+ self.geometry.component
1194
+ )
1195
+
1142
1196
  # Apply z-crop for 3D and XZ 2D (both use the vertical dimension).
1143
1197
  # XY 2D collapses z entirely so cropping is meaningless. The crop
1144
1198
  # window comes from domain.z_ref (default: the drawn photonic core).
1145
1199
  if (is_3d or plane == "xz") and not self._z_cropped:
1146
- ref_zmin, ref_zmax, ref_name, ref_n, is_auto = self._resolve_z_ref_extent()
1200
+ ref_zmin, ref_zmax, ref_name, ref_n, is_auto = self._resolve_z_ref_extent(
1201
+ component=original_component
1202
+ )
1147
1203
  # When a fiber source is configured in XZ mode, expand the
1148
1204
  # above (high) side of margin_z so the cropped stack still
1149
1205
  # contains the beam plane (and PML headroom) — from the ref top.
@@ -1152,7 +1208,6 @@ class Simulation(BaseModel):
1152
1208
 
1153
1209
  import gdsfactory as gf
1154
1210
 
1155
- original_component = self.geometry.component.copy()
1156
1211
  stack = self.geometry.stack
1157
1212
 
1158
1213
  # Resolve the XZ cut Y-coordinate ('auto'/None -> bbox center).
@@ -254,9 +254,8 @@ def build_cross_section_rectangles(
254
254
  directly so cross-section geometry for custom material stacks
255
255
  (e.g. TFLN) is rendered correctly.
256
256
 
257
- Layers that have no GDS polygons at all (``_layer_has_any_polygon``
258
- returns ``False``) are skipped, since their boundaries are already
259
- implicit from adjacent layers.
257
+ Layers that have no GDS polygons are skipped, since their boundaries are
258
+ already implicit from adjacent layers.
260
259
 
261
260
  Args:
262
261
  component: gdsfactory Component with GDS polygons.
@@ -275,23 +274,6 @@ def build_cross_section_rectangles(
275
274
  if not stack.layers:
276
275
  return []
277
276
 
278
- def _has_any_polygon(layer: object) -> bool:
279
- gds = getattr(layer, "gds_layer", None)
280
- if gds is None:
281
- return False
282
- try:
283
- polys = component.get_polygons_points(layers=(gds,), merge=True)
284
- except Exception:
285
- return False
286
- else:
287
- if not isinstance(polys, dict):
288
- return False
289
- for v in polys.values():
290
- items = v if isinstance(v, list) else [v]
291
- if len(items) > 0:
292
- return True
293
- return False
294
-
295
277
  if slice_axis == "y":
296
278
  cut_line = LineString([(-1e6, slice_coord), (1e6, slice_coord)])
297
279
  elif slice_axis == "x":
@@ -327,21 +309,9 @@ def build_cross_section_rectangles(
327
309
  poly_items.extend(items)
328
310
 
329
311
  if not poly_items:
330
- if _has_any_polygon(layer):
331
- continue
332
- rectangles.append(
333
- {
334
- "h_min": -float("inf"),
335
- "h_max": float("inf"),
336
- "z_min": layer.zmin,
337
- "z_max": layer.zmax,
338
- "layer_name": layer.name,
339
- "material": layer.material,
340
- "sidewall_angle": float(
341
- getattr(layer, "sidewall_angle", 0.0) or 0.0
342
- ),
343
- }
344
- )
312
+ # The component does not draw this stack layer. Do not turn it
313
+ # into an infinite slab: the runner likewise emits no geometry
314
+ # for a layer with no GDS polygons.
345
315
  continue
346
316
 
347
317
  for poly in poly_items:
@@ -1,6 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gsim
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
+ Summary: Electromagnetic simulation for photonics and electronics, powered by GDSFactory.
4
5
  Author-email: GDSFactory <contact@gdsfactory.com>
5
6
  License: Apache-2.0
6
7
  Classifier: License :: OSI Approved :: Apache Software License
@@ -15,6 +16,7 @@ Requires-Dist: gmsh
15
16
  Requires-Dist: meshio>=5.0.0
16
17
  Requires-Dist: nbformat>=4.2.0
17
18
  Requires-Dist: nest_asyncio2
19
+ Requires-Dist: pdk-schema==0.0.2
18
20
  Requires-Dist: plotly
19
21
  Requires-Dist: pyvista>=0.43.0
20
22
  Requires-Dist: trame
@@ -22,7 +24,7 @@ Requires-Dist: trame-vtk
22
24
  Requires-Dist: trame-vuetify
23
25
  Dynamic: license-file
24
26
 
25
- # gsim 0.2.0
27
+ # gsim 0.3.0
26
28
 
27
29
  > Electromagnetic simulation for photonics and electronics, powered by [GDSFactory+](https://gdsfactory.com)
28
30
 
@@ -15,6 +15,7 @@ src/gsim/common/cross_section.py
15
15
  src/gsim/common/geometry.py
16
16
  src/gsim/common/geometry_model.py
17
17
  src/gsim/common/layered_component.py
18
+ src/gsim/common/materials.py
18
19
  src/gsim/common/polygon.py
19
20
  src/gsim/common/polygon_utils.py
20
21
  src/gsim/common/types.py
@@ -4,6 +4,7 @@ gmsh
4
4
  meshio>=5.0.0
5
5
  nbformat>=4.2.0
6
6
  nest_asyncio2
7
+ pdk-schema==0.0.2
7
8
  plotly
8
9
  pyvista>=0.43.0
9
10
  trame
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes