{ "cells": [ { "cell_type": "markdown", "id": "5ed63800", "metadata": { "papermill": { "duration": 0.002025, "end_time": "2026-05-27T20:00:55.495376+00:00", "exception": false, "start_time": "2026-05-27T20:00:55.493351+00:00", "status": "completed" }, "tags": [] }, "source": [ "### L shaped antenna simulation.\n", "\n", "In this notebook we will generate the mesh of an L shaped antenna and generate a json for a transient\n", "simulation." ] }, { "cell_type": "code", "execution_count": 15, "id": "9437354e", "metadata": { "execution": { "iopub.execute_input": "2026-05-27T20:00:55.502114Z", "iopub.status.busy": "2026-05-27T20:00:55.501907Z", "iopub.status.idle": "2026-05-27T20:00:55.864001Z", "shell.execute_reply": "2026-05-27T20:00:55.863420Z" }, "papermill": { "duration": 0.365343, "end_time": "2026-05-27T20:00:55.864781+00:00", "exception": false, "start_time": "2026-05-27T20:00:55.499438+00:00", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "import gmsh\n", "import os\n", "import json\n", "\n", "from palacetoolkit.viz import view_mesh\n", "from palacetoolkit.mesh import (\n", " Entity, \n", " run_meshing_pipeline, \n", " generate_3d_mesh, \n", " refine_near_surfaces\n", ")" ] }, { "cell_type": "markdown", "id": "1d956d7d", "metadata": { "papermill": { "duration": 0.003929, "end_time": "2026-05-27T20:00:55.870459+00:00", "exception": false, "start_time": "2026-05-27T20:00:55.866530+00:00", "status": "completed" }, "tags": [] }, "source": [ "\n", "### Parameters:\n", "- h : Patch height along z-axis, specified as a scalar in meters. \n", "- l1 : Ground plane length along x-axis, specified as a scalar in meters\n", "- w1 : Ground plane width along y-axis, specified as a scalar in meters\n", "- w3 : Strip line width along y-axis, specified as a scalar in meters.\n", "- air_height : Air box height along z-axis, specified as a scalar in meters. \n", "- air_margin : Air box margin along x and y axes, specified as a scalar in meters.\n", "- freq : Simulation frequency in GHz, specified as a scalar.\n", "- filename : Output mesh filename, specified as a string." ] }, { "cell_type": "code", "execution_count": 16, "id": "4049c4db", "metadata": { "execution": { "iopub.execute_input": "2026-05-27T20:00:55.874959Z", "iopub.status.busy": "2026-05-27T20:00:55.874680Z", "iopub.status.idle": "2026-05-27T20:00:55.878448Z", "shell.execute_reply": "2026-05-27T20:00:55.877853Z" }, "papermill": { "duration": 0.006796, "end_time": "2026-05-27T20:00:55.878999+00:00", "exception": false, "start_time": "2026-05-27T20:00:55.872203+00:00", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "l1: float = 0.06\n", "w1: float = 0.06\n", "w3: float = 0.002\n", "h: float = 0.0013\n", "air_height: float = 0.025 \n", "air_margin: float = 0.025 \n", "freq: float = 3.3\n", "mesh_file: str = \"l_antenna.msh\"\n", "\n", "\n", "eps_r: float = 2.2\n", "loss_tan: float = 0.0009\n", "\n", "wavelength = 3e8 / (freq * 1e9)\n", "mesh_size = wavelength / 15" ] }, { "cell_type": "markdown", "id": "6d496609", "metadata": { "papermill": { "duration": 0.001629, "end_time": "2026-05-27T20:00:55.882261+00:00", "exception": false, "start_time": "2026-05-27T20:00:55.880632+00:00", "status": "completed" }, "tags": [] }, "source": [ "### Initialize the model" ] }, { "cell_type": "code", "execution_count": 17, "id": "e6a08089", "metadata": { "execution": { "iopub.execute_input": "2026-05-27T20:00:55.885728Z", "iopub.status.busy": "2026-05-27T20:00:55.885592Z", "iopub.status.idle": "2026-05-27T20:00:55.888774Z", "shell.execute_reply": "2026-05-27T20:00:55.888373Z" }, "papermill": { "duration": 0.005699, "end_time": "2026-05-27T20:00:55.889357+00:00", "exception": false, "start_time": "2026-05-27T20:00:55.883658+00:00", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "gmsh.initialize()\n", "gmsh.model.add(\"patch_antenna\")\n", "kernel = gmsh.model.occ" ] }, { "cell_type": "markdown", "id": "783686ab", "metadata": { "papermill": { "duration": 0.001329, "end_time": "2026-05-27T20:00:55.892238+00:00", "exception": false, "start_time": "2026-05-27T20:00:55.890909+00:00", "status": "completed" }, "tags": [] }, "source": [ "### Geometry Construction\n" ] }, { "cell_type": "code", "execution_count": 18, "id": "474cc986", "metadata": { "execution": { "iopub.execute_input": "2026-05-27T20:00:55.896136Z", "iopub.status.busy": "2026-05-27T20:00:55.895972Z", "iopub.status.idle": "2026-05-27T20:00:55.905951Z", "shell.execute_reply": "2026-05-27T20:00:55.905516Z" }, "papermill": { "duration": 0.01254, "end_time": "2026-05-27T20:00:55.906506+00:00", "exception": false, "start_time": "2026-05-27T20:00:55.893966+00:00", "status": "completed" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Info : Cannot bind existing OpenCASCADE surface 9 to second tag 10 \n", "Info : Could not preserve tag of 2D object 10 (->9)\n" ] } ], "source": [ "total_xmin = -l1/2 - air_margin\n", "total_xmax = l1/2 + air_margin\n", "total_ymin = -w1/2 - air_margin\n", "total_ymax = w1/2 + air_margin\n", "total_zmax = h + air_height\n", "\n", "\n", "# 1. Create volumes\n", "substrate = kernel.addBox(-l1/2, -w1/2, 0, l1, w1, h)\n", "airsphere_radius = max(abs(total_xmin), abs(total_xmax), abs(total_ymin), abs(total_ymax), total_zmax)\n", "air_sphere = kernel.addSphere(0.0, 0.0, 0.0, airsphere_radius)\n", "kernel.synchronize()\n", "\n", "# 2. Create 2D surfaces (ground, patch, ports)\n", "ground_plane = kernel.addRectangle(-l1/2, -w1/2, 0, l1, w1)\n", "\n", "strip_length_x = l1/2\n", "strip_length_y = w1/2 + w3/2\n", "\n", "feed_line_x = kernel.addRectangle(-l1/2, -w3/2, h, strip_length_x, w3)\n", "feed_line_y = kernel.addRectangle(0, -w3/2, h, w3, strip_length_y)\n", "\n", "top_conductor, top_map = kernel.fuse(\n", " [(2, feed_line_x)], [(2, feed_line_y)],\n", " removeObject=True, removeTool=True\n", ")\n", "kernel.synchronize()\n", "\n", "# Lumped ports - build directly in-plane, no rotation\n", "# Port 1: at x = -l1/2, vertical face\n", "p1 = kernel.addPoint(-l1/2, -w3/2, 0)\n", "p2 = kernel.addPoint(-l1/2, w3/2, 0)\n", "p3 = kernel.addPoint(-l1/2, w3/2, h)\n", "p4 = kernel.addPoint(-l1/2, -w3/2, h)\n", "\n", "lp1_a = kernel.addLine(p1, p2)\n", "lp1_b = kernel.addLine(p2, p3)\n", "lp1_c = kernel.addLine(p3, p4)\n", "lp1_d = kernel.addLine(p4, p1)\n", "loop1 = kernel.addCurveLoop([lp1_a, lp1_b, lp1_c, lp1_d])\n", "lumped_port_1 = kernel.addPlaneSurface([loop1])\n", "kernel.synchronize()\n", "\n", "# Port 2: at y = w1/2, vertical face\n", "p5 = kernel.addPoint(0, w1/2, 0)\n", "p6 = kernel.addPoint(w3, w1/2, 0)\n", "p7 = kernel.addPoint(w3, w1/2, h)\n", "p8 = kernel.addPoint(0, w1/2, h)\n", "\n", "lp2_a = kernel.addLine(p5, p6)\n", "lp2_b = kernel.addLine(p6, p7)\n", "lp2_c = kernel.addLine(p7, p8)\n", "lp2_d = kernel.addLine(p8, p5)\n", "loop2 = kernel.addCurveLoop([lp2_a, lp2_b, lp2_c, lp2_d])\n", "lumped_port_2 = kernel.addPlaneSurface([loop2])\n", "kernel.synchronize()\n", "\n", "# Define the entities which will be the physical groups.\n", "entities = [\n", " Entity(\"substrate\", dim=3, btype=\"dielectric\", mesh_order=1, tags=[substrate], loss_tan=loss_tan, eps_r=eps_r, mu_r=1.0),\n", " Entity(\"air_sphere\", dim=3, btype=\"dielectric\", mesh_order=2, tags=[air_sphere], loss_tan=0.0, eps_r=1.0, mu_r=1.0),\n", " Entity(\"top_conductor\", dim=2, btype=\"pec\", mesh_order=1, tags=[top_conductor[0][1]]),\n", " Entity(\"ground_plane\", dim=2, btype=\"pec\", mesh_order=1, tags=[ground_plane]),\n", " Entity(\"lumped_port_1\", dim=2, btype=\"lumped_port\", mesh_order=0, tags=[lumped_port_1], R=50.0, direction=\"+Z\", excitation=True),\n", " Entity(\"lumped_port_2\", dim=2, btype=\"lumped_port\", mesh_order=0, tags=[lumped_port_2], R=50.0, direction=\"+Z\", excitation=False),\n", "]\n" ] }, { "cell_type": "code", "execution_count": 19, "id": "ecf93353", "metadata": { "execution": { "iopub.execute_input": "2026-05-27T20:00:55.910655Z", "iopub.status.busy": "2026-05-27T20:00:55.910462Z", "iopub.status.idle": "2026-05-27T20:00:57.281541Z", "shell.execute_reply": "2026-05-27T20:00:57.280876Z" }, "papermill": { "duration": 1.37416, "end_time": "2026-05-27T20:00:57.282385+00:00", "exception": false, "start_time": "2026-05-27T20:00:55.908225+00:00", "status": "completed" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " Physical group 'substrate' (dim=3): pg=1, tags=[1] \n", " Physical group 'air_sphere' (dim=3): pg=2, tags=[2]\n", " Physical group 'top_conductor' (dim=2): pg=3, tags=[9]\n", " Physical group 'ground_plane' (dim=2): pg=4, tags=[8]\n", " Physical group 'lumped_port_1' (dim=2): pg=5, tags=[10]\n", " Physical group 'lumped_port_2' (dim=2): pg=6, tags=[11]\n", " Physical group 'air_sphere__substrate' (dim=2): pg=7, tags=[12, 13, 14, 15, 16, 17, 18, 19]\n", " Physical group 'air_sphere__None' (dim=2): pg=8, tags=[20]\n", " ppw_near=100 ppw_far=15\n", " SizeMax=0.0061 transition=0.0227\n", " global: 6 curves, SizeMin=0.0009\n", " local (2, 11): 4 curves, SizeMin=0.0009\n", " local (2, 10): 4 curves, SizeMin=0.0009\n", " Merged 3 fields with Min → field 7\n" ] } ], "source": [ "pg_map = run_meshing_pipeline(entities)\n", "\n", "lumped_port_1 = entities[-1].dimtags[0]\n", "lumped_port_2 = entities[-2].dimtags[0]\n", "\n", "# Refine near the top conductor and also locally refine the ports.\n", "refine_near_surfaces(entities[2].dimtags, \n", " wavelength, \n", " ppw_near=100, \n", " ppw_far=15, \n", " set_as_background=True,\n", " local_refinements = {lumped_port_1: 100, lumped_port_2 : 100})\n", "\n", "mesh_sizes = {\n", " \"substrate\": wavelength / 12,\n", " \"air_sphere\": wavelength / 4,\n", " \"lumped_port_1\": wavelength / 18,\n", " \"lumped_port_2\": wavelength / 18,\n", " \"top_conductor\": wavelength /10\n", "}\n", "\n", "generate_3d_mesh(entities, mesh_sizes, mesh_file, optimize=True, verbose=False)\n", "gmsh.finalize()" ] }, { "cell_type": "code", "execution_count": 20, "id": "f2fc1ec1", "metadata": { "execution": { "iopub.execute_input": "2026-05-27T20:00:57.286581Z", "iopub.status.busy": "2026-05-27T20:00:57.286452Z", "iopub.status.idle": "2026-05-27T20:00:58.038052Z", "shell.execute_reply": "2026-05-27T20:00:58.037446Z" }, "papermill": { "duration": 0.765281, "end_time": "2026-05-27T20:00:58.049563+00:00", "exception": false, "start_time": "2026-05-27T20:00:57.284282+00:00", "status": "completed" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Loading mesh file: l_antenna.msh\n", "Groups to render transparent: air_sphere__None\n", "\n", "Mesh loaded successfully with 2 cell blocks\n", "Found 5922 triangles total\n", "Physical group tags in mesh: {3: 'top_conductor', 4: 'ground_plane', 5: 'lumped_port_1', 6: 'lumped_port_2', 7: 'air_sphere__substrate', 8: 'air_sphere__None'}\n" ] }, { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "view_mesh(mesh_file, transparent_groups= \"air_sphere__None\")" ] }, { "cell_type": "markdown", "id": "cde1861e", "metadata": { "papermill": { "duration": 0.016444, "end_time": "2026-05-27T20:00:58.081535+00:00", "exception": false, "start_time": "2026-05-27T20:00:58.065091+00:00", "status": "completed" }, "tags": [] }, "source": [ "### Simulation Configuration\n", "We define the key parameters for the electromagnetic simulation here. These settings control the frequency sweep range, material properties (dielectric constant and loss tangent for the substrate), and solver-specific configurations like port impedance and mesh order.\n", "\n", "- output_file : output filename for the configuration JSON file\n", "- freq : frequency for the simulation (GHz) \n", "- eps_r: relative permittivity of the substrate\n", "- loss_tan: loss tangent of the substrate\n", "- port_impedance: characteristic impedance of the lumped port (Ohms)\n", "- solver_order: order of the finite element basis functions for the simulation (e.g., 1 for linear, 2 for quadratic)" ] }, { "cell_type": "code", "execution_count": 21, "id": "8a378602", "metadata": { "execution": { "iopub.execute_input": "2026-05-27T20:00:58.111890Z", "iopub.status.busy": "2026-05-27T20:00:58.111550Z", "iopub.status.idle": "2026-05-27T20:00:58.114463Z", "shell.execute_reply": "2026-05-27T20:00:58.114052Z" }, "papermill": { "duration": 0.018918, "end_time": "2026-05-27T20:00:58.115107+00:00", "exception": false, "start_time": "2026-05-27T20:00:58.096189+00:00", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "output_file_transient: str = \"l_antenna_transient.json\"\n", "output_file_driven: str = \"l_antenna_driven.json\"\n", "\n", "freq: float = 3.16\n", "eps_r: float = 2.2\n", "loss_tan: float = 0.0009\n", "port_impedance: float = 50.0\n", "solver_order: int = 2\n", "\n", "\n", "import numpy as np\n", "eps_0 = 8.8541878128e-12\n", "sigma = 2 * np.pi * freq * eps_0 * eps_r * loss_tan" ] }, { "cell_type": "markdown", "id": "a0161718", "metadata": { "papermill": { "duration": 0.014543, "end_time": "2026-05-27T20:00:58.144154+00:00", "exception": false, "start_time": "2026-05-27T20:00:58.129611+00:00", "status": "completed" }, "tags": [] }, "source": [ "### Generating the Palace Configuration File\n", "Finally, we assemble the simulation parameters into two JSON configuration. One for a transient simulation and one for a driven where we check the s-parameters." ] }, { "cell_type": "code", "execution_count": 22, "id": "87bca585", "metadata": { "execution": { "iopub.execute_input": "2026-05-27T20:00:58.173858Z", "iopub.status.busy": "2026-05-27T20:00:58.173692Z", "iopub.status.idle": "2026-05-27T20:00:58.179025Z", "shell.execute_reply": "2026-05-27T20:00:58.178528Z" }, "papermill": { "duration": 0.021375, "end_time": "2026-05-27T20:00:58.179702+00:00", "exception": false, "start_time": "2026-05-27T20:00:58.158327+00:00", "status": "completed" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Palace config written to /home/martin/Desktop/PalaceToolkit/docs/examples/l_antenna_transient.json\n" ] } ], "source": [ "def attr(name):\n", " return [pg_map[name]] if name in pg_map else []\n", "\n", "config = {\n", " \"Problem\": {\n", " \"Type\": \"Transient\",\n", " \"Verbose\": 2,\n", " \"Output\": \"/work/results_transient/l_antenna/\"\n", " },\n", "\n", " \"Model\": {\n", " \"Mesh\": f\"/work/{mesh_file}\",\n", " \"L0\": 1.0,\n", " \"Refinement\": {}\n", " },\n", "\n", " \"Domains\": {\n", " \"Materials\": [\n", " {\n", " \"Attributes\": attr(\"substrate\"),\n", " \"Permittivity\": eps_r,\n", " \"Permeability\": 1.0,\n", " \"Conductivity\": sigma # Replaced LossTan\n", " },\n", " {\n", " \"Attributes\": attr(\"air\"),\n", " \"Permittivity\": 1.0,\n", " \"Permeability\": 1.0\n", " }\n", " ]\n", "},\n", "\n", " \"Boundaries\": {\n", " \"PEC\": {\n", " \"Attributes\": attr(\"ground_plane\") + attr(\"patch\")\n", " },\n", "\n", " \"LumpedPort\": [\n", " {\n", " \"Index\": 1,\n", " \"Attributes\": attr(\"lumped_port_1\"),\n", " \"R\": port_impedance,\n", " \"Excitation\": True,\n", " \"Direction\": [0.0, 0.0, 1.0]\n", " },\n", " {\n", " \"Index\": 2,\n", " \"Attributes\": attr(\"lumped_port_2\"),\n", " \"R\": port_impedance,\n", " \"Excitation\": False, \n", " \"Direction\": [0.0, 0.0, 1.0] \n", " }\n", " ],\n", "\n", " \"Absorbing\": {\n", " \"Attributes\": attr(\"farfield\"),\n", " \"Order\": 1\n", " }\n", " },\n", "\n", " \"Solver\": {\n", " \"Order\": solver_order,\n", " \"Device\": \"CPU\",\n", " \"Transient\": {\n", " \"Type\": \"GeneralizedAlpha\",\n", " \"Excitation\": \"ModulatedGaussian\",\n", " \"ExcitationFreq\": freq, \n", " \"ExcitationWidth\": 0.05, \n", " \"MaxTime\": 1.0, \n", " \"TimeStep\": 0.005, \n", " \"SaveStep\": 10\n", " },\n", " \"Linear\": {\n", " \"Type\": \"AMS\",\n", " \"KSPType\": \"CG\",\n", " \"Tol\": 1.0e-8,\n", " \"MaxIts\": 100\n", " }\n", " }\n", "}\n", "\n", "\n", "\n", "script_dir = os.getcwd()\n", "config_path = os.path.join(script_dir, output_file_transient)\n", "with open(config_path, \"w\") as f:\n", " json.dump(config, f, indent=2)\n", "print(f\"Palace config written to {config_path}\")" ] }, { "cell_type": "code", "execution_count": 23, "id": "f937f58f", "metadata": { "execution": { "iopub.execute_input": "2026-05-27T20:00:58.211875Z", "iopub.status.busy": "2026-05-27T20:00:58.211672Z", "iopub.status.idle": "2026-05-27T20:00:58.216586Z", "shell.execute_reply": "2026-05-27T20:00:58.216146Z" }, "papermill": { "duration": 0.022153, "end_time": "2026-05-27T20:00:58.217147+00:00", "exception": false, "start_time": "2026-05-27T20:00:58.194994+00:00", "status": "completed" }, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Palace config written to /home/martin/Desktop/PalaceToolkit/docs/examples/l_antenna_driven.json\n" ] } ], "source": [ "config = {\n", " \"Problem\": {\n", " \"Type\": \"Driven\",\n", " \"Verbose\": 2,\n", " \"Output\": \"/work/results_driven/l_antenna/\"\n", " },\n", "\n", " \"Model\": {\n", " \"Mesh\": f\"/work/{mesh_file}\",\n", " \"L0\": 1.0,\n", " \"Refinement\": {}\n", " },\n", "\n", " \"Domains\": {\n", " \"Materials\": [\n", " {\n", " \"Attributes\": attr(\"substrate\"),\n", " \"Permittivity\": eps_r,\n", " \"Permeability\": 1.0,\n", " \"Conductivity\": loss_tan \n", " },\n", " {\n", " \"Attributes\": attr(\"air\"),\n", " \"Permittivity\": 1.0,\n", " \"Permeability\": 1.0\n", " }\n", " ]\n", "},\n", "\n", " \"Boundaries\": {\n", " \"PEC\": {\n", " \"Attributes\": attr(\"ground_plane\") + attr(\"patch\")\n", " },\n", "\n", " \"LumpedPort\": [\n", " {\n", " \"Index\": 1,\n", " \"Attributes\": attr(\"lumped_port_1\"),\n", " \"R\": port_impedance,\n", " \"Excitation\": True,\n", " \"Direction\": [0.0, 0.0, 1.0]\n", " },\n", " {\n", " \"Index\": 2,\n", " \"Attributes\": attr(\"lumped_port_2\"),\n", " \"R\": port_impedance,\n", " \"Excitation\": False, \n", " \"Direction\": [0.0, 0.0, 1.0] \n", " }\n", " ],\n", "\n", " \"Absorbing\": {\n", " \"Attributes\": attr(\"farfield\"),\n", " \"Order\": 1\n", " }\n", " },\n", "\n", " \"Solver\": {\n", " \"Order\": 2,\n", " \"Device\": \"CPU\",\n", " \"Driven\": {\n", " \"MinFreq\": 3.0,\n", " \"MaxFreq\": 3.5,\n", " \"FreqStep\": 0.1,\n", " \"SaveStep\": 1,\n", " \"AdaptiveTol\": 0.0001\n", " },\n", " \"Linear\": {\n", " \"Type\": \"Default\",\n", " \"KSPType\": \"GMRES\",\n", " \"Tol\": 1e-08,\n", " \"MaxIts\": 300,\n", " \"MaxSize\": 1000,\n", " \"ComplexCoarseSolve\": True\n", " }\n", " }\n", "}\n", "\n", "\n", "\n", "script_dir = os.getcwd()\n", "config_path = os.path.join(script_dir, output_file_driven)\n", "with open(config_path, \"w\") as f:\n", " json.dump(config, f, indent=2)\n", "print(f\"Palace config written to {config_path}\")" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "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", "version": "3.12.3" }, "papermill": { "default_parameters": {}, "duration": 3.845707, "end_time": "2026-05-27T20:00:58.647368+00:00", "environment_variables": {}, "exception": null, "input_path": "docs/examples/l_antenna.ipynb", "output_path": "docs/examples/l_antenna.ipynb", "parameters": {}, "start_time": "2026-05-27T20:00:54.801661+00:00", "version": "2.7.0" } }, "nbformat": 4, "nbformat_minor": 5 }