Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading