Skip to content
Draft
2 changes: 2 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@

::: gsim.meep.Material

::: gsim.meep.MeepMeshSim

::: gsim.meep.ModeSource

::: gsim.meep.ResolutionConfig
Expand Down
268 changes: 268 additions & 0 deletions nbs/mesh_ybranch.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "0",
"metadata": {},
"source": [
"# GMSH Mesh Generation via MeepMeshSim\n",
"\n",
"This notebook demonstrates using `MeepMeshSim` to generate a GMSH mesh\n",
"from any gdsfactory component + LayerStack — mirroring Palace's\n",
"`mesh()` → `write_config()` workflow for photonic simulations.\n",
"\n",
"We use a UBC PDK Y-branch (photonic SOI) as an example."
]
},
{
"cell_type": "markdown",
"id": "1",
"metadata": {},
"source": [
"### Load component from UBC PDK"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2",
"metadata": {},
"outputs": [],
"source": [
"from ubcpdk import PDK, cells\n",
"\n",
"PDK.activate()\n",
"\n",
"# c = cells.ebeam_y_1550()\n",
"# c = cells.ring_single()\n",
"c = cells.coupler()\n",
"\n",
"dirname = \"./sim-data-mesh-----coupler\"\n",
"\n",
"c"
]
},
{
"cell_type": "markdown",
"id": "3",
"metadata": {},
"source": [
"### Configure MeepMeshSim"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4",
"metadata": {},
"outputs": [],
"source": [
"from gsim.common.stack.extractor import Layer, LayerStack\n",
"from gsim.meep import MeepMeshSim\n",
"\n",
"# Build a LayerStack for the SOI process\n",
"stack = LayerStack(pdk_name=\"ubcpdk\")\n",
"\n",
"stack.layers[\"core\"] = Layer(\n",
" name=\"core\",\n",
" gds_layer=(1, 0),\n",
" zmin=0.0,\n",
" zmax=0.22,\n",
" thickness=0.22,\n",
" material=\"si\",\n",
" layer_type=\"dielectric\",\n",
")\n",
"\n",
"stack.dielectrics = [\n",
" {\"name\": \"box\", \"material\": \"SiO2\", \"zmin\": -3.0, \"zmax\": 0.0},\n",
" {\"name\": \"clad\", \"material\": \"SiO2\", \"zmin\": 0.0, \"zmax\": 1.8},\n",
"]\n",
"\n",
"stack.materials = {\n",
" \"si\": {\"type\": \"dielectric\", \"permittivity\": 11.7},\n",
" \"SiO2\": {\"type\": \"dielectric\", \"permittivity\": 2.1},\n",
"}\n",
"\n",
"# Configure simulation\n",
"sim = MeepMeshSim()\n",
"sim.geometry(component=c, stack=stack)\n",
"sim.materials = {\"si\": 3.47, \"SiO2\": 1.44}\n",
"sim.source(port=\"o1\", wavelength=1.55, wavelength_span=0.1, num_freqs=11)\n",
"sim.monitors = [\"o1\", \"o2\", \"o3\"]\n",
"sim.domain(pml=1.0, margin=0.5)\n",
"sim.solver(resolution=32)"
]
},
{
"cell_type": "markdown",
"id": "5",
"metadata": {},
"source": [
"### Generate mesh and write config"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6",
"metadata": {},
"outputs": [],
"source": [
"result = sim.mesh(dirname, refined_mesh_size=0.2, max_mesh_size=1.0)\n",
"config_path = sim.write_config()\n",
"\n",
"print(f\"Mesh file: {result.mesh_path}\")\n",
"print(f\"File size: {result.mesh_path.stat().st_size / 1024:.0f} KB\")\n",
"print(f\"Config: {config_path}\")"
]
},
{
"cell_type": "markdown",
"id": "7",
"metadata": {},
"source": [
"### Mesh summary"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8",
"metadata": {},
"outputs": [],
"source": [
"import meshio\n",
"import numpy as np\n",
"\n",
"stats = result.mesh_stats\n",
"groups = result.groups\n",
"m = meshio.read(result.mesh_path)\n",
"tag_to_name = {tag: name for name, (tag, _) in m.field_data.items()}\n",
"\n",
"# --- Header ---\n",
"size_kb = result.mesh_path.stat().st_size / 1024\n",
"print(f\"Mesh: {result.mesh_path.name} ({size_kb:.0f} KB)\")\n",
"print(f\"Nodes: {stats.get('nodes', '?'):,} Tets: {stats.get('tetrahedra', '?'):,}\")\n",
"print()\n",
"\n",
"# --- Quality ---\n",
"q = stats.get(\"quality\", {})\n",
"sicn = stats.get(\"sicn\", {})\n",
"edge = stats.get(\"edge_length\", {})\n",
"print(\"Quality\")\n",
"print(\n",
" f\" Shape (gamma): min={q.get('min', '?')} mean={q.get('mean', '?')} max={q.get('max', '?')}\"\n",
")\n",
"print(\n",
" f\" SICN: min={sicn.get('min', '?')} mean={sicn.get('mean', '?')} invalid={sicn.get('invalid', '?')}\"\n",
")\n",
"print(f\" Edge length: min={edge.get('min', '?')} max={edge.get('max', '?')}\")\n",
"\n",
"# Edge ratio from actual mesh\n",
"for cells in m.cells:\n",
" if cells.type == \"tetra\":\n",
" pts = m.points[cells.data]\n",
" pairs = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]\n",
" all_edges = np.stack(\n",
" [np.linalg.norm(pts[:, i] - pts[:, j], axis=1) for i, j in pairs]\n",
" )\n",
" ratios = all_edges.max(axis=0) / np.maximum(all_edges.min(axis=0), 1e-15)\n",
" bad = int(np.sum(ratios > 20))\n",
" print(\n",
" f\" Edge ratio: mean={ratios.mean():.1f} max={ratios.max():.1f} bad(>20)={bad}\"\n",
" )\n",
"print()\n",
"\n",
"# --- Bounding box ---\n",
"bb = stats.get(\"bbox\", {})\n",
"print(f\"Bounding box\")\n",
"print(\n",
" f\" x: [{bb.get('xmin', 0):.2f}, {bb.get('xmax', 0):.2f}] ({bb.get('xmax', 0) - bb.get('xmin', 0):.2f} um)\"\n",
")\n",
"print(\n",
" f\" y: [{bb.get('ymin', 0):.2f}, {bb.get('ymax', 0):.2f}] ({bb.get('ymax', 0) - bb.get('ymin', 0):.2f} um)\"\n",
")\n",
"print(\n",
" f\" z: [{bb.get('zmin', 0):.2f}, {bb.get('zmax', 0):.2f}] ({bb.get('zmax', 0) - bb.get('zmin', 0):.2f} um)\"\n",
")\n",
"print()\n",
"\n",
"# --- Physical groups ---\n",
"print(\"Physical groups\")\n",
"for cells, phys in zip(m.cells, m.cell_data[\"gmsh:physical\"]):\n",
" if cells.type != \"tetra\":\n",
" continue\n",
" for tag, name in sorted(tag_to_name.items(), key=lambda x: x[1]):\n",
" mask = phys == tag\n",
" n = int(np.sum(mask))\n",
" if n == 0:\n",
" continue\n",
" pts = m.points[cells.data[mask].ravel()]\n",
" zlo, zhi = pts[:, 2].min(), pts[:, 2].max()\n",
" mat = \"\"\n",
" if name in groups.get(\"layer_volumes\", {}):\n",
" mat = f\" (material: {groups['layer_volumes'][name].get('material', '?')})\"\n",
" print(f\" {name:16s} {n:>7,} tets z=[{zlo:.3f}, {zhi:.3f}]{mat}\")\n",
"\n",
"if groups.get(\"port_surfaces\"):\n",
" print()\n",
" print(\"Port surfaces\")\n",
" for name, info in groups[\"port_surfaces\"].items():\n",
" c = info[\"center\"]\n",
" print(\n",
" f\" {name:16s} center=({c[0]:.2f}, {c[1]:.2f}) width={info['width']:.2f} z=[{info['z_range'][0]:.3f}, {info['z_range'][1]:.3f}]\"\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "9",
"metadata": {},
"source": [
"### Visualize the mesh"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "10",
"metadata": {},
"outputs": [],
"source": [
"# Full mesh wireframe\n",
"sim.plot_mesh(interactive=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "11",
"metadata": {},
"outputs": [],
"source": [
"# Show only the waveguide core\n",
"sim.plot_mesh(show_groups=[\"core\", \"port_\"], interactive=False)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "gsim",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading