{ "cells": [ { "cell_type": "markdown", "id": "ea546892", "metadata": {}, "source": [ "# HFSS-Mechanical MRI analysis\n", "\n", "This example uses a coil tuned to 63.8 MHz to determine the temperature\n", "rise in a gel phantom near an implant given a background SAR of 1 W/kg.\n", "\n", "Here is the workflow:" ] }, { "cell_type": "markdown", "id": "8a49fb75", "metadata": {}, "source": [ "Step 1: Simulate the coil loaded by the empty phantom:\n", "Scale input to coil ports to produce desired background SAR of 1 W/kg at the location\n", "that is to contain the implant." ] }, { "cell_type": "markdown", "id": "d48b6cec", "metadata": {}, "source": [ "Step 2: Simulate the coil loaded by the phantom containing the implant in the proper location:\n", "View SAR in the tissue surrounding implant." ] }, { "cell_type": "markdown", "id": "7fb98e82", "metadata": {}, "source": [ "Step 3: Thermal simulation:\n", "Link HFSS to the transient thermal solver to find the temperature rise in the tissue near the implant\n", "versus the time.\n", "\n", "Keywords: **multiphysics**, **HFSS**, **Mechanical AEDT**, **Circuit**." ] }, { "cell_type": "markdown", "id": "77eaf620", "metadata": {}, "source": [ "## Perform imports and define constants\n", "Perform required imports." ] }, { "cell_type": "code", "execution_count": null, "id": "07b6d00e", "metadata": {}, "outputs": [], "source": [ "import base64\n", "import os.path\n", "import tempfile\n", "import time\n", "\n", "from IPython.display import HTML, display\n", "from IPython.utils import io as ipio\n", "\n", "from ansys.aedt.core import Hfss, Icepak, Mechanical\n", "from ansys.aedt.core.examples import downloads\n" ] }, { "cell_type": "markdown", "id": "67a3488e", "metadata": {}, "source": [ "Define constants." ] }, { "cell_type": "code", "execution_count": null, "id": "75aaea2b", "metadata": { "lines_to_next_cell": 2 }, "outputs": [], "source": [ "AEDT_VERSION = \"2026.1\"\n", "NUM_CORES = 4\n", "NG_MODE = False # Open AEDT UI when it is launched." ] }, { "cell_type": "markdown", "id": "cafa546c", "metadata": {}, "source": [ "## Create temporary directory\n", "\n", "Create a temporary directory where downloaded data or\n", "dumped data can be stored.\n", "If you'd like to retrieve the project data for subsequent use,\n", "the temporary folder name is given by ``temp_folder.name``." ] }, { "cell_type": "code", "execution_count": null, "id": "e284d9e0", "metadata": { "lines_to_next_cell": 2 }, "outputs": [], "source": [ "temp_folder = tempfile.TemporaryDirectory(suffix=\".ansys\")" ] }, { "cell_type": "markdown", "id": "fbc972a6", "metadata": {}, "source": [ "## Load project\n", "\n", "Open AEDT and the ``background_SAR.aedt`` project. This project\n", "contains the phantom and airbox. The phantom consists of two objects: ``phantom`` and ``implant_box``.\n", "\n", "Separate objects are used to selectively assign mesh operations.\n", "Material properties defined in this project already contain electrical and thermal properties." ] }, { "cell_type": "code", "execution_count": null, "id": "d14f7325", "metadata": {}, "outputs": [], "source": [ "project_path = downloads.download_file(source=\"mri\", local_path=temp_folder.name)\n", "project_name = os.path.join(project_path, \"background_SAR.aedt\")\n", "hfss = Hfss(\n", " project=project_name,\n", " version=AEDT_VERSION,\n", " non_graphical=NG_MODE,\n", " new_desktop=True,\n", ")" ] }, { "cell_type": "markdown", "id": "eb35ef67", "metadata": {}, "source": [ "## Insert 3D component\n", "\n", "The MRI coil is saved as a separate 3D component." ] }, { "cell_type": "markdown", "id": "3d74f103", "metadata": {}, "source": [ "‒ 3D components store geometry (including parameters),\n", "material properties, boundary conditions, mesh assignments,\n", "and excitations.\n", "‒ 3D components make it easy to reuse and share parts of a simulation." ] }, { "cell_type": "code", "execution_count": null, "id": "460c5315", "metadata": {}, "outputs": [], "source": [ "component_file = os.path.join(project_path, \"coil.a3dcomp\")\n", "hfss.modeler.insert_3d_component(input_file=component_file)" ] }, { "cell_type": "markdown", "id": "9d95400e", "metadata": {}, "source": [ "## Define convergence criteria\n", "\n", " On the **Expression Cache** tab, define additional convergence criteria for self\n", " impedance of the four coil ports." ] }, { "cell_type": "markdown", "id": "cb17f278", "metadata": {}, "source": [ "Set each of these convergence criteria to 2.5 ohm.\n", "This example limits the number of passes to two to reduce simulation time." ] }, { "cell_type": "code", "execution_count": null, "id": "d8510bee", "metadata": {}, "outputs": [], "source": [ "im_traces = hfss.get_traces_for_plot(get_mutual_terms=False, category=\"im(Z\", first_element_filter=\"Coil1_p*\")\n", "\n", "hfss.setups[0].enable_expression_cache(\n", " report_type=\"Modal Solution Data\",\n", " expressions=im_traces,\n", " isconvergence=True,\n", " isrelativeconvergence=False,\n", " conv_criteria=2.5,\n", " use_cache_for_freq=False,\n", ")\n", "hfss.setups[0].props[\"MaximumPasses\"] = 1" ] }, { "cell_type": "markdown", "id": "3a5504f3", "metadata": {}, "source": [ "## Edit sources\n", "\n", "The 3D component of the MRI coil contains all the ports,\n", "but the sources for these ports are not yet defined.\n", "Browse to and select the ``sources.csv`` file.\n", "The sources in this file were determined by tuning the coil at 63.8 MHz.\n", "Notice that ``input_scale`` is a multiplier that lets you quickly adjust the coil excitation power." ] }, { "cell_type": "code", "execution_count": null, "id": "e1ddc9e1", "metadata": {}, "outputs": [], "source": [ "hfss.edit_source_from_file(os.path.join(project_path, \"sources.csv\"))" ] }, { "cell_type": "markdown", "id": "e43a2b23", "metadata": {}, "source": [ "## Run simulation\n", "\n", "Save and analyze the project." ] }, { "cell_type": "code", "execution_count": null, "id": "fb958bdc", "metadata": {}, "outputs": [], "source": [ "hfss.save_project(file_name=os.path.join(project_path, \"solved.aedt\"))\n", "hfss.analyze(cores=NUM_CORES)" ] }, { "cell_type": "markdown", "id": "c6db7da3", "metadata": {}, "source": [ "## Plot SAR on cut plane in phantom\n", "\n", "Ensure that the SAR averaging method is set to ``Gridless``.\n", "Plot ``Average_SAR`` on the global YZ plane.\n", "Draw ``Point1`` at the origin of the implant coordinate system." ] }, { "cell_type": "code", "execution_count": null, "id": "867b6c89", "metadata": {}, "outputs": [], "source": [ "hfss.sar_setup(\n", " assignment=-1,\n", " average_sar_method=1,\n", " tissue_mass=1,\n", " material_density=1,\n", ")\n", "hfss.post.create_fieldplot_cutplane(assignment=[\"implant:YZ\"], quantity=\"Average_SAR\", filter_objects=[\"implant_box\"])\n", "\n", "hfss.modeler.set_working_coordinate_system(\"implant\")\n", "hfss.modeler.create_point(position=[0, 0, 0], name=\"Point1\")\n", "\n", "plot = hfss.post.plot_field(\n", " quantity=\"Average_SAR\",\n", " assignment=\"implant:YZ\",\n", " plot_type=\"CutPlane\",\n", " show_legend=False,\n", " filter_objects=[\"implant_box\"],\n", " export_path=hfss.working_directory,\n", " show=False,\n", ")" ] }, { "cell_type": "markdown", "id": "6d98f9be", "metadata": {}, "source": [ "## Adjust input Power to MRI coil\n", "\n", "Adjust the MRI coil’s input power so that the average SAR at ``Point1`` is 1 W/kg.\n", "Note that the SAR and input power are linearly related.\n", "\n", "To determine the required input, calculate\n", "``input_scale = 1/AverageSAR`` at ``Point1``." ] }, { "cell_type": "code", "execution_count": null, "id": "a5ae819b", "metadata": {}, "outputs": [], "source": [ "sol_data = hfss.post.get_solution_data(\n", " expressions=\"Average_SAR\",\n", " primary_sweep_variable=\"Freq\",\n", " context=\"Point1\",\n", " report_category=\"Fields\",\n", ")\n", "\n", "hfss[\"input_scale\"] = 1 / sol_data.get_expression_data()[1][0]" ] }, { "cell_type": "markdown", "id": "7deade20", "metadata": {}, "source": [ "## Analyze phantom with implant\n", "\n", "Import the implant geometry.\n", "Subtract the rod from the implant box.\n", "Assign titanium to the imported object rod.\n", "Analyze the project." ] }, { "cell_type": "code", "execution_count": null, "id": "e70ec6c3", "metadata": {}, "outputs": [], "source": [ "hfss.modeler.import_3d_cad(os.path.join(project_path, \"implant_rod.sat\"))\n", "\n", "hfss.modeler[\"implant_box\"].subtract(tool_list=\"rod\", keep_originals=True)\n", "hfss.modeler[\"rod\"].material_name = \"titanium\"\n", "hfss.analyze(cores=NUM_CORES)\n", "hfss.save_project()" ] }, { "cell_type": "markdown", "id": "3ca35aed", "metadata": {}, "source": [ "## Run a thermal simulation\n", "\n", "Initialize a new Mechanical transient thermal analysis.\n", "This type of analysis is available in AEDT in 2023 R2 and later as a beta feature." ] }, { "cell_type": "code", "execution_count": null, "id": "1c3813da", "metadata": {}, "outputs": [], "source": [ "mech = Mechanical(solution_type=\"Transient Thermal\", version=AEDT_VERSION)" ] }, { "cell_type": "markdown", "id": "8ee970e3", "metadata": {}, "source": [ "## Copy geometries\n", "\n", "Copy bodies from the HFSS project. The 3D component is not copied." ] }, { "cell_type": "code", "execution_count": null, "id": "026e916c", "metadata": {}, "outputs": [], "source": [ "mech.copy_solid_bodies_from(hfss)" ] }, { "cell_type": "markdown", "id": "7b2401c3", "metadata": {}, "source": [ "## Link sources to EM losses\n", "\n", "Link sources to the EM losses.\n", "Assign external convection." ] }, { "cell_type": "code", "execution_count": null, "id": "6672c557", "metadata": {}, "outputs": [], "source": [ "exc = mech.assign_em_losses(\n", " design=hfss.design_name,\n", " setup=hfss.setups[0].name,\n", " sweep=\"LastAdaptive\",\n", " map_frequency=hfss.setups[0].props[\"Frequency\"],\n", " surface_objects=mech.get_all_conductors_names(),\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "031aaca5", "metadata": {}, "outputs": [], "source": [ "mech.assign_uniform_convection(assignment=mech.modeler[\"Region\"].faces, convection_value=1)" ] }, { "cell_type": "markdown", "id": "f99149b2", "metadata": {}, "source": [ "## Create setup\n", "\n", "Create a setup and edit properties." ] }, { "cell_type": "code", "execution_count": null, "id": "9490e96c", "metadata": {}, "outputs": [], "source": [ "setup = mech.create_setup()\n", "# setup.add_mesh_link(\"backgroundSAR\")\n", "# mech.create_dataset1d_design(\"PowerMap\", [0, 239, 240, 360], [1, 1, 0, 0])\n", "# exc.props[\"LossMultiplier\"] = \"pwl(PowerMap,Time)\"\n", "\n", "mech.modeler.set_working_coordinate_system(\"implant\")\n", "mech.modeler.create_point(position=[0, 0, 0], name=\"Point1\")\n", "setup.props[\"Stop Time\"] = 30\n", "setup.props[\"Time Step\"] = \"15s\"\n", "setup.props[\"SaveFieldsType\"] = \"Every N Steps\"\n", "setup.props[\"N Steps\"] = \"2\"" ] }, { "cell_type": "markdown", "id": "69ccee1d", "metadata": {}, "source": [ "## Analyze project\n", "\n", "Analyze the Mechanical project." ] }, { "cell_type": "code", "execution_count": null, "id": "fb6e750b", "metadata": {}, "outputs": [], "source": [ "mech.analyze(cores=NUM_CORES)" ] }, { "cell_type": "markdown", "id": "0aed576d", "metadata": {}, "source": [ "## Plot fields\n", "\n", "Plot the temperature on cut plane.\n", "Plot the temperature on the point." ] }, { "cell_type": "code", "execution_count": null, "id": "ff905cd8", "metadata": {}, "outputs": [], "source": [ "mech.post.create_fieldplot_cutplane(\n", " assignment=[\"implant:YZ\"],\n", " quantity=\"Temperature\",\n", " filter_objects=[\"implant_box\"],\n", " intrinsics={\"Time\": \"15s\"},\n", ")\n", "mech.save_project()\n", "\n", "data = mech.post.get_solution_data(\n", " expressions=\"Temperature\",\n", " primary_sweep_variable=\"Time\",\n", " context=\"Point1\",\n", " report_category=\"Fields\",\n", ")\n", "_ = data.plot()\n", "\n", "gif_file = os.path.join(tempfile.gettempdir(), \"mri_animated.gif\")\n", "\n", "with ipio.capture_output():\n", " anim = mech.post.plot_animated_field(\n", " quantity=\"Temperature\",\n", " assignment=\"implant:YZ\",\n", " plot_type=\"CutPlane\",\n", " intrinsics={\"Time\": \"15s\"},\n", " variation_variable=\"Time\",\n", " variations=[\"15s\", \"30s\"],\n", " filter_objects=[\"implant_box\"],\n", " show=False,\n", " )\n", " anim.is_notebook = False\n", " anim.off_screen = True\n", " anim.gif_file = gif_file\n", " anim.animate(show=False)\n", "\n", "with open(gif_file, \"rb\") as f:\n", " gif_b64 = base64.b64encode(f.read()).decode()\n", "display(HTML(f''))" ] }, { "cell_type": "markdown", "id": "9eac23ea", "metadata": {}, "source": [ "## Run a new thermal simulation\n", "\n", "Initialize a new Icepak transient thermal analysis." ] }, { "cell_type": "code", "execution_count": null, "id": "54615dc3", "metadata": {}, "outputs": [], "source": [ "ipk = Icepak(solution_type=\"Transient\", version=AEDT_VERSION)\n", "ipk.design_solutions.problem_type = \"TemperatureOnly\"" ] }, { "cell_type": "markdown", "id": "981bff3e", "metadata": {}, "source": [ "## Copy geometries\n", "\n", "Copy bodies from the HFSS project. The 3D component is not copied." ] }, { "cell_type": "code", "execution_count": null, "id": "e18e8f18", "metadata": {}, "outputs": [], "source": [ "ipk.modeler.delete(\"Region\")\n", "ipk.copy_solid_bodies_from(hfss)" ] }, { "cell_type": "markdown", "id": "0937c7d3", "metadata": {}, "source": [ "## Link sources to EM losses\n", "\n", "Link sources to the EM losses.\n", "Assign external convection." ] }, { "cell_type": "code", "execution_count": null, "id": "1b44a58f", "metadata": {}, "outputs": [], "source": [ "assignment = ipk.modeler.object_names" ] }, { "cell_type": "code", "execution_count": null, "id": "bfe2d923", "metadata": {}, "outputs": [], "source": [ "ipk.assign_em_losses(\n", " assignment=assignment,\n", " design=hfss.design_name,\n", " setup=hfss.setups[0].name,\n", " sweep=\"LastAdaptive\",\n", " map_frequency=hfss.setups[0].props[\"Frequency\"],\n", " surface_objects=ipk.get_all_conductors_names(),\n", ")" ] }, { "cell_type": "markdown", "id": "d094df1e", "metadata": {}, "source": [ "## Create setup\n", "\n", "Create a setup and edit properties.\n", "Simulation takes 30 seconds." ] }, { "cell_type": "code", "execution_count": null, "id": "10b5b551", "metadata": {}, "outputs": [], "source": [ "setup = ipk.create_setup()\n", "\n", "setup.props[\"Stop Time\"] = 30\n", "setup.props[\"N Steps\"] = 2\n", "setup.props[\"Time Step\"] = 15\n", "setup.props[\"Convergence Criteria - Energy\"] = 1e-12" ] }, { "cell_type": "markdown", "id": "55e784ca", "metadata": {}, "source": [ "## Create mesh region\n", "\n", "Create a mesh region and change the accuracy level to 4." ] }, { "cell_type": "code", "execution_count": null, "id": "639666cd", "metadata": {}, "outputs": [], "source": [ "bound = ipk.modeler[\"implant_box\"].bounding_box\n", "mesh_box = ipk.modeler.create_box(\n", " origin=bound[:3],\n", " sizes=[bound[3] - bound[0], bound[4] - bound[1], bound[5] - bound[2]],\n", ")\n", "mesh_box.is_model = False\n", "mesh_region = ipk.mesh.assign_mesh_region([mesh_box.name])\n", "mesh_region.UserSpecifiedSettings = False\n", "mesh_region.update()\n", "mesh_region.properties[\"Mesh Resolution\"] = 4" ] }, { "cell_type": "markdown", "id": "5fc8cbef", "metadata": {}, "source": [ "## Create point monitor\n", "\n", "Create a point monitor." ] }, { "cell_type": "code", "execution_count": null, "id": "a0c9f028", "metadata": {}, "outputs": [], "source": [ "ipk.modeler.set_working_coordinate_system(\"implant\")\n", "ipk.monitor.assign_point_monitor(point_position=[0, 0, 0], monitor_name=\"Point1\")\n", "ipk.assign_openings(ipk.modeler[\"Region\"].top_face_z)\n", "# -" ] }, { "cell_type": "markdown", "id": "75899f63", "metadata": {}, "source": [ "## Release AEDT\n", "\n", "Release AEDT and close the example." ] }, { "cell_type": "code", "execution_count": null, "id": "cf27ed84", "metadata": {}, "outputs": [], "source": [ "hfss.save_project()\n", "hfss.release_desktop()\n", "# Wait 3 seconds to allow AEDT to shut down before cleaning the temporary directory.\n", "time.sleep(3)" ] }, { "cell_type": "markdown", "id": "5093b9ca", "metadata": {}, "source": [ "## Clean up\n", "\n", "All project files are saved in the folder ``temp_folder.name``. If you've run this example as a Jupyter notebook, you\n", "can retrieve those project files. The following cell removes all temporary files, including the project folder." ] }, { "cell_type": "code", "execution_count": null, "id": "778e5a94", "metadata": {}, "outputs": [], "source": [ "temp_folder.cleanup()" ] } ], "metadata": { "jupytext": { "cell_metadata_filter": "-all", "main_language": "python", "notebook_metadata_filter": "-all" } }, "nbformat": 4, "nbformat_minor": 5 }