{ "cells": [ { "cell_type": "markdown", "id": "c8d99df0", "metadata": { "papermill": { "duration": 0.001442, "end_time": "2026-05-27T20:01:00.696743+00:00", "exception": false, "start_time": "2026-05-27T20:01:00.695301+00:00", "status": "completed" }, "tags": [] }, "source": [ "# Microstrip Modes\n", "\n", "This notebook builds a simple boxed microstrip cross-section (substrate + air + PEC strip conductor), solves eigenmodes with `WaveguideModeSolver`, and plots the first fields." ] }, { "cell_type": "code", "execution_count": 1, "id": "11dc12a7", "metadata": { "execution": { "iopub.execute_input": "2026-05-27T20:01:00.703144Z", "iopub.status.busy": "2026-05-27T20:01:00.702931Z", "iopub.status.idle": "2026-05-27T20:01:01.398253Z", "shell.execute_reply": "2026-05-27T20:01:01.397705Z" }, "papermill": { "duration": 0.698273, "end_time": "2026-05-27T20:01:01.399077+00:00", "exception": false, "start_time": "2026-05-27T20:01:00.700804+00:00", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "from palacetoolkit.mode_solver import WaveguideModeSolver\n", "from palacetoolkit.viz import view_mesh\n", "\n", "import importlib\n", "import inspect\n", "\n", "import gmsh\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import palacetoolkit.utils as ptk_utils\n", "\n", "importlib.reload(ptk_utils)\n", "view_fe_mesh_2d = ptk_utils.view_fe_mesh_2d\n", "view_fields_2d = ptk_utils.view_fields_2d\n", "write_and_finalize_gmsh = ptk_utils.write_and_finalize_gmsh" ] }, { "cell_type": "code", "execution_count": 2, "id": "5249965f", "metadata": { "execution": { "iopub.execute_input": "2026-05-27T20:01:01.405287Z", "iopub.status.busy": "2026-05-27T20:01:01.405029Z", "iopub.status.idle": "2026-05-27T20:01:01.408160Z", "shell.execute_reply": "2026-05-27T20:01:01.407688Z" }, "papermill": { "duration": 0.008402, "end_time": "2026-05-27T20:01:01.408821+00:00", "exception": false, "start_time": "2026-05-27T20:01:01.400419+00:00", "status": "completed" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WaveguideModeSolver.__init__ signature:\n", "(self, mesh, order=1, mu_inv=1.0, eps=1.0, pec_bdr='all')\n", "\n", "PEC conductor support via boundary attributes: True\n", "The solver enforces PEC by essential DOF elimination on selected boundary attributes.\n" ] } ], "source": [ "sig = inspect.signature(WaveguideModeSolver.__init__)\n", "print(\"WaveguideModeSolver.__init__ signature:\")\n", "print(sig)\n", "print(\"\\nPEC conductor support via boundary attributes:\", \"pec_bdr\" in sig.parameters)\n", "print(\"The solver enforces PEC by essential DOF elimination on selected boundary attributes.\")" ] }, { "cell_type": "code", "execution_count": 3, "id": "2b6408f2", "metadata": { "execution": { "iopub.execute_input": "2026-05-27T20:01:01.412661Z", "iopub.status.busy": "2026-05-27T20:01:01.412505Z", "iopub.status.idle": "2026-05-27T20:01:01.420138Z", "shell.execute_reply": "2026-05-27T20:01:01.419697Z" }, "papermill": { "duration": 0.01015, "end_time": "2026-05-27T20:01:01.420559+00:00", "exception": false, "start_time": "2026-05-27T20:01:01.410409+00:00", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "def make_microstrip_mesh(\n", " box_w=8.0,\n", " h_sub=1.0,\n", " h_air=3.0,\n", " strip_w=1.8,\n", " strip_t=0.06,\n", " lc_bulk=0.18,\n", " lc_strip=0.05,\n", " meshsize=1.0,\n", " filename=None,\n", "):\n", " gmsh.initialize()\n", " gmsh.option.setNumber(\"General.Verbosity\", 0)\n", " gmsh.model.add(\"microstrip_modes\")\n", "\n", " sub = gmsh.model.occ.addRectangle(-box_w / 2, -h_sub, 0, box_w, h_sub)\n", " air = gmsh.model.occ.addRectangle(-box_w / 2, 0.0, 0, box_w, h_air)\n", " strip = gmsh.model.occ.addRectangle(-strip_w / 2, 0.0, 0, strip_w, strip_t)\n", "\n", " _, outmap = gmsh.model.occ.fragment([(2, sub), (2, air), (2, strip)], [])\n", " strip_parts = list(outmap[2])\n", " gmsh.model.occ.remove(strip_parts, recursive=True)\n", " gmsh.model.occ.synchronize()\n", "\n", " all_surfs = [t for _, t in gmsh.model.getEntities(2)]\n", " substrate_surfs = []\n", " air_surfs = []\n", " for tag in all_surfs:\n", " _, cy, _ = gmsh.model.occ.getCenterOfMass(2, tag)\n", " if cy < -1e-9:\n", " substrate_surfs.append(tag)\n", " else:\n", " air_surfs.append(tag)\n", "\n", " if not substrate_surfs or not air_surfs:\n", " gmsh.finalize()\n", " raise RuntimeError(\"Failed to classify substrate/air surfaces for microstrip mesh\")\n", "\n", " gmsh.model.addPhysicalGroup(2, substrate_surfs, tag=1, name=\"substrate\")\n", " gmsh.model.addPhysicalGroup(2, air_surfs, tag=2, name=\"air\")\n", "\n", " bnd = gmsh.model.getBoundary([(2, t) for t in substrate_surfs + air_surfs], oriented=False, combined=False)\n", " edge_tags = sorted({abs(t) for _, t in bnd})\n", "\n", " strip_edges = []\n", " ground_edges = []\n", " open_edges = []\n", "\n", " for et in edge_tags:\n", " ex, ey, _ = gmsh.model.occ.getCenterOfMass(1, et)\n", " on_strip_x = abs(ex) <= strip_w / 2 + 1e-6\n", " on_strip_y = (-1e-6 <= ey <= strip_t + 1e-6)\n", " if on_strip_x and on_strip_y:\n", " strip_edges.append(et)\n", " continue\n", "\n", " # Ground plane is the lower boundary of the simulation box.\n", " if abs(ey + h_sub) <= 1e-6:\n", " ground_edges.append(et)\n", " else:\n", " open_edges.append(et)\n", "\n", " if ground_edges:\n", " gmsh.model.addPhysicalGroup(1, ground_edges, tag=1, name=\"ground_plane\")\n", " if strip_edges:\n", " gmsh.model.addPhysicalGroup(1, strip_edges, tag=2, name=\"strip_conductor\")\n", " if open_edges:\n", " gmsh.model.addPhysicalGroup(1, open_edges, tag=3, name=\"open_boundary\")\n", "\n", " if meshsize <= 0:\n", " gmsh.finalize()\n", " raise ValueError(\"meshsize must be positive\")\n", "\n", " lc_bulk_eff = lc_bulk * meshsize\n", " lc_strip_eff = lc_strip * meshsize\n", "\n", " # Use a distance-based field from all curves so refinement occurs\n", " # near any geometry boundary (PEC and domain boundaries).\n", " all_curves = sorted({t for _, t in gmsh.model.getEntities(1)})\n", " dist_field = gmsh.model.mesh.field.add(\"Distance\")\n", " gmsh.model.mesh.field.setNumbers(dist_field, \"CurvesList\", all_curves)\n", " gmsh.model.mesh.field.setNumber(dist_field, \"Sampling\", 200)\n", "\n", " threshold_field = gmsh.model.mesh.field.add(\"Threshold\")\n", " gmsh.model.mesh.field.setNumber(threshold_field, \"InField\", dist_field)\n", " gmsh.model.mesh.field.setNumber(threshold_field, \"SizeMin\", lc_strip_eff)\n", " gmsh.model.mesh.field.setNumber(threshold_field, \"SizeMax\", lc_bulk_eff)\n", " gmsh.model.mesh.field.setNumber(threshold_field, \"DistMin\", 0.0)\n", " gmsh.model.mesh.field.setNumber(threshold_field, \"DistMax\", 0.35 * h_sub)\n", "\n", " gmsh.model.mesh.field.setAsBackgroundMesh(threshold_field)\n", " gmsh.option.setNumber(\"Mesh.CharacteristicLengthFromPoints\", 0)\n", " gmsh.option.setNumber(\"Mesh.CharacteristicLengthFromCurvature\", 0)\n", " gmsh.option.setNumber(\"Mesh.CharacteristicLengthExtendFromBoundary\", 0)\n", " gmsh.option.setNumber(\"Mesh.CharacteristicLengthMin\", lc_strip_eff)\n", " gmsh.option.setNumber(\"Mesh.CharacteristicLengthMax\", lc_bulk_eff)\n", "\n", " gmsh.model.mesh.generate(2)\n", " return write_and_finalize_gmsh(filename, prefix=\"wg_microstrip_\")" ] }, { "cell_type": "code", "execution_count": 4, "id": "1f73ddb2", "metadata": { "execution": { "iopub.execute_input": "2026-05-27T20:01:01.423523Z", "iopub.status.busy": "2026-05-27T20:01:01.423383Z", "iopub.status.idle": "2026-05-27T20:01:01.426244Z", "shell.execute_reply": "2026-05-27T20:01:01.425735Z" }, "papermill": { "duration": 0.004855, "end_time": "2026-05-27T20:01:01.426595+00:00", "exception": false, "start_time": "2026-05-27T20:01:01.421740+00:00", "status": "completed" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Reference phase window (air to substrate): 1.0000 < kn < 2.1909\n" ] } ], "source": [ "eps_sub = 4.8\n", "eps_air = 1.0\n", "mu_r = 1.0\n", "\n", "omega = 1.0\n", "kn_air = omega * np.sqrt(mu_r * eps_air)\n", "kn_sub = omega * np.sqrt(mu_r * eps_sub)\n", "print(f\"Reference phase window (air to substrate): {kn_air:.4f} < kn < {kn_sub:.4f}\")" ] }, { "cell_type": "code", "execution_count": 5, "id": "d84a7361", "metadata": { "execution": { "iopub.execute_input": "2026-05-27T20:01:01.429366Z", "iopub.status.busy": "2026-05-27T20:01:01.429229Z", "iopub.status.idle": "2026-05-27T20:01:17.994464Z", "shell.execute_reply": "2026-05-27T20:01:17.994054Z" }, "papermill": { "duration": 16.567713, "end_time": "2026-05-27T20:01:17.995440+00:00", "exception": false, "start_time": "2026-05-27T20:01:01.427727+00:00", "status": "completed" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Loading mesh file: /tmp/wg_microstrip_ftoylzts.msh\n", "Groups to render transparent: ['air_none', 'air_plastic_enclosure']\n", "\n", "Mesh loaded successfully with 2 cell blocks\n", "Found 5778 triangles total\n", "Physical group tags in mesh: {1: 'substrate', 2: 'air'}\n" ] }, { "data": { "text/html": [ "
" ], "text/plain": [ "