Skip to content

Commit 550a5c3

Browse files
committed
fix: merge same-material dielectric boxes and align XY extents
Adjacent dielectric boxes of the same material are now merged into a single GMSH box (e.g. box+clad SiO2 → one volume), eliminating coincident faces and sliver tets. Different-material boxes sharing a z-boundary use removeAllDuplicates() to cleanly resolve the shared face. Also adds a regression test verifying box/clad XY consistency, improves the mesh summary in notebooks, and removes the plot_mesh_slice feature.
1 parent e33927b commit 550a5c3

4 files changed

Lines changed: 455 additions & 78 deletions

File tree

nbs/mesh_ybranch.ipynb

Lines changed: 80 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
"# c = cells.ring_single()\n",
3838
"c = cells.coupler()\n",
3939
"\n",
40+
"dirname = \"./sim-data-mesh-----coupler\"\n",
41+
"\n",
4042
"c"
4143
]
4244
},
@@ -106,7 +108,7 @@
106108
"metadata": {},
107109
"outputs": [],
108110
"source": [
109-
"result = sim.mesh(\"./mesh-ybranch\")\n",
111+
"result = sim.mesh(dirname, refined_mesh_size=0.2, max_mesh_size=1.0)\n",
110112
"config_path = sim.write_config()\n",
111113
"\n",
112114
"print(f\"Mesh file: {result.mesh_path}\")\n",
@@ -119,7 +121,7 @@
119121
"id": "7",
120122
"metadata": {},
121123
"source": [
122-
"### Inspect mesh statistics"
124+
"### Mesh summary"
123125
]
124126
},
125127
{
@@ -129,62 +131,92 @@
129131
"metadata": {},
130132
"outputs": [],
131133
"source": [
132-
"import json\n",
134+
"import meshio\n",
135+
"import numpy as np\n",
133136
"\n",
134-
"# Mesh statistics\n",
135137
"stats = result.mesh_stats\n",
136-
"print(\"=== Mesh Stats ===\")\n",
137-
"print(json.dumps(stats, indent=2))\n",
138-
"\n",
139-
"# Config contents\n",
140-
"print(\"\\n=== mesh_config.json ===\")\n",
141-
"print(config_path.read_text())"
142-
]
143-
},
144-
{
145-
"cell_type": "markdown",
146-
"id": "9",
147-
"metadata": {},
148-
"source": [
149-
"### Inspect physical groups"
150-
]
151-
},
152-
{
153-
"cell_type": "code",
154-
"execution_count": null,
155-
"id": "10",
156-
"metadata": {},
157-
"outputs": [],
158-
"source": [
159138
"groups = result.groups\n",
139+
"m = meshio.read(result.mesh_path)\n",
140+
"tag_to_name = {tag: name for name, (tag, _) in m.field_data.items()}\n",
160141
"\n",
161-
"print(\"Volume groups (materials):\")\n",
162-
"for name, info in groups[\"volumes\"].items():\n",
163-
" print(f\" {name}: phys_group={info['phys_group']}, tags={info['tags']}\")\n",
142+
"# --- Header ---\n",
143+
"size_kb = result.mesh_path.stat().st_size / 1024\n",
144+
"print(f\"Mesh: {result.mesh_path.name} ({size_kb:.0f} KB)\")\n",
145+
"print(f\"Nodes: {stats.get('nodes', '?'):,} Tets: {stats.get('tetrahedra', '?'):,}\")\n",
146+
"print()\n",
164147
"\n",
165-
"print(\"\\nLayer volumes:\")\n",
166-
"for name, info in groups[\"layer_volumes\"].items():\n",
167-
" print(\n",
168-
" f\" {name}: phys_group={info['phys_group']}, material={info['material']}, tags={info['tags']}\"\n",
169-
" )\n",
148+
"# --- Quality ---\n",
149+
"q = stats.get(\"quality\", {})\n",
150+
"sicn = stats.get(\"sicn\", {})\n",
151+
"edge = stats.get(\"edge_length\", {})\n",
152+
"print(\"Quality\")\n",
153+
"print(\n",
154+
" f\" Shape (gamma): min={q.get('min', '?')} mean={q.get('mean', '?')} max={q.get('max', '?')}\"\n",
155+
")\n",
156+
"print(\n",
157+
" f\" SICN: min={sicn.get('min', '?')} mean={sicn.get('mean', '?')} invalid={sicn.get('invalid', '?')}\"\n",
158+
")\n",
159+
"print(f\" Edge length: min={edge.get('min', '?')} max={edge.get('max', '?')}\")\n",
170160
"\n",
171-
"if groups.get(\"port_surfaces\"):\n",
172-
" print(\"\\nPort surfaces:\")\n",
173-
" for name, info in groups[\"port_surfaces\"].items():\n",
161+
"# Edge ratio from actual mesh\n",
162+
"for cells in m.cells:\n",
163+
" if cells.type == \"tetra\":\n",
164+
" pts = m.points[cells.data]\n",
165+
" pairs = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]\n",
166+
" all_edges = np.stack(\n",
167+
" [np.linalg.norm(pts[:, i] - pts[:, j], axis=1) for i, j in pairs]\n",
168+
" )\n",
169+
" ratios = all_edges.max(axis=0) / np.maximum(all_edges.min(axis=0), 1e-15)\n",
170+
" bad = int(np.sum(ratios > 20))\n",
174171
" print(\n",
175-
" f\" {name}: phys_group={info['phys_group']}, center={info['center']}, \"\n",
176-
" f\"width={info['width']}, layer={info['layer']}, z_range={info['z_range']}\"\n",
172+
" f\" Edge ratio: mean={ratios.mean():.1f} max={ratios.max():.1f} bad(>20)={bad}\"\n",
177173
" )\n",
174+
"print()\n",
175+
"\n",
176+
"# --- Bounding box ---\n",
177+
"bb = stats.get(\"bbox\", {})\n",
178+
"print(f\"Bounding box\")\n",
179+
"print(\n",
180+
" f\" x: [{bb.get('xmin', 0):.2f}, {bb.get('xmax', 0):.2f}] ({bb.get('xmax', 0) - bb.get('xmin', 0):.2f} um)\"\n",
181+
")\n",
182+
"print(\n",
183+
" f\" y: [{bb.get('ymin', 0):.2f}, {bb.get('ymax', 0):.2f}] ({bb.get('ymax', 0) - bb.get('ymin', 0):.2f} um)\"\n",
184+
")\n",
185+
"print(\n",
186+
" f\" z: [{bb.get('zmin', 0):.2f}, {bb.get('zmax', 0):.2f}] ({bb.get('zmax', 0) - bb.get('zmin', 0):.2f} um)\"\n",
187+
")\n",
188+
"print()\n",
178189
"\n",
179-
"if groups.get(\"outer_boundary\"):\n",
180-
" print(f\"\\nOuter boundary: phys_group={groups['outer_boundary']['phys_group']}\")\n",
181-
"else:\n",
182-
" print(\"\\nNo outer boundary (airbox not included)\")"
190+
"# --- Physical groups ---\n",
191+
"print(\"Physical groups\")\n",
192+
"for cells, phys in zip(m.cells, m.cell_data[\"gmsh:physical\"]):\n",
193+
" if cells.type != \"tetra\":\n",
194+
" continue\n",
195+
" for tag, name in sorted(tag_to_name.items(), key=lambda x: x[1]):\n",
196+
" mask = phys == tag\n",
197+
" n = int(np.sum(mask))\n",
198+
" if n == 0:\n",
199+
" continue\n",
200+
" pts = m.points[cells.data[mask].ravel()]\n",
201+
" zlo, zhi = pts[:, 2].min(), pts[:, 2].max()\n",
202+
" mat = \"\"\n",
203+
" if name in groups.get(\"layer_volumes\", {}):\n",
204+
" mat = f\" (material: {groups['layer_volumes'][name].get('material', '?')})\"\n",
205+
" print(f\" {name:16s} {n:>7,} tets z=[{zlo:.3f}, {zhi:.3f}]{mat}\")\n",
206+
"\n",
207+
"if groups.get(\"port_surfaces\"):\n",
208+
" print()\n",
209+
" print(\"Port surfaces\")\n",
210+
" for name, info in groups[\"port_surfaces\"].items():\n",
211+
" c = info[\"center\"]\n",
212+
" print(\n",
213+
" 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",
214+
" )"
183215
]
184216
},
185217
{
186218
"cell_type": "markdown",
187-
"id": "11",
219+
"id": "9",
188220
"metadata": {},
189221
"source": [
190222
"### Visualize the mesh"
@@ -193,18 +225,18 @@
193225
{
194226
"cell_type": "code",
195227
"execution_count": null,
196-
"id": "12",
228+
"id": "10",
197229
"metadata": {},
198230
"outputs": [],
199231
"source": [
200232
"# Full mesh wireframe\n",
201-
"sim.plot_mesh(interactive=False)"
233+
"sim.plot_mesh(interactive=True)"
202234
]
203235
},
204236
{
205237
"cell_type": "code",
206238
"execution_count": null,
207-
"id": "13",
239+
"id": "11",
208240
"metadata": {},
209241
"outputs": [],
210242
"source": [

0 commit comments

Comments
 (0)