{ "cells": [ { "cell_type": "markdown", "id": "6eabdeb0", "metadata": {}, "source": [ "# Circuit-HFSS-Icepak coupling workflow\n", "\n", "This example shows how to create a two-way coupling\n", "between HFSS and Icepak.\n", "\n", "Consider a design where some components are simulated in\n", "HFSS with a full 3D model, while others are simulated in Circuit as lumped elements.\n", "The electrical simulation is done by placing the HFSS design into a Circuit design as\n", "a subcomponent and connecting the lumped components to its ports.\n", "\n", "The purpose of the workflow is to perform a thermal simulation\n", "of the Circuit-HFSS design, creating a two-way coupling with Icepak\n", "that allows running multiple iterations. The losses from both designs\n", "are accounted for.\n", "- EM losses are evaluated by the HFSS solver and fed into Icepak via a direct link.\n", "- Losses from the lumped components in the Circuit design are evaluated\n", "analytically and must be manually set into the Icepak boundary.\n", "\n", "On the back of the coupling, temperature information\n", "is handled differently for HFSS and Circuit.\n", "\n", "- For HFSS, a temperature map is exported from the Icepak design and used to create a\n", "3D dataset. Then, the material properties in the HFSS design are updated based on this\n", "dataset.\n", "- For Circuit, the average temperature of the lumped components is extracted from the\n", "Icepak design and used to update the temperature-dependent characteristics of the\n", "lumped components in Circuit.\n", "\n", "The Circuit design in this example contains only a\n", "resistor component, with temperature-dependent resistance\n", "described by this formula: ``0.162*(1+0.004*(TempE-TempE0))``,\n", "where TempE is the current temperature and TempE0 is the\n", "ambient temperature. The HFSS design includes only a cylinder\n", "with temperature-dependent material conductivity, defined by\n", "a 2D dataset. The resistor and the cylinder have\n", "matching resistances.\n", "\n", "Keywords: **Multiphysics**, **HFSS**, **Icepak**, **Circuit**." ] }, { "cell_type": "markdown", "id": "93eb4693", "metadata": {}, "source": [ "## Perform imports and define constants\n", "\n", "Perform required imports." ] }, { "cell_type": "code", "execution_count": null, "id": "6a717240", "metadata": {}, "outputs": [], "source": [ "import os\n", "import tempfile\n", "import time" ] }, { "cell_type": "code", "execution_count": null, "id": "cbcef564", "metadata": {}, "outputs": [], "source": [ "import ansys.aedt.core as aedt" ] }, { "cell_type": "markdown", "id": "8ac91b49", "metadata": {}, "source": [ "Define constants." ] }, { "cell_type": "code", "execution_count": null, "id": "390a0e31", "metadata": {}, "outputs": [], "source": [ "AEDT_VERSION = \"2024.2\"\n", "NUM_CORES = 4\n", "NG_MODE = False # Open AEDT UI when it is launched." ] }, { "cell_type": "markdown", "id": "ec422332", "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": "613878ad", "metadata": {}, "outputs": [], "source": [ "temp_folder = tempfile.TemporaryDirectory(suffix=\".ansys\")" ] }, { "cell_type": "markdown", "id": "85540a10", "metadata": {}, "source": [ "## Download and open project\n", "\n", "Download and open the project. Save it to the temporary folder." ] }, { "cell_type": "code", "execution_count": null, "id": "e0f4cb04", "metadata": {}, "outputs": [], "source": [ "project_name = aedt.downloads.download_file(\n", " \"circuit_hfss_icepak\", \"Circuit-HFSS-Icepak-workflow.aedtz\", temp_folder.name\n", ")" ] }, { "cell_type": "markdown", "id": "d63583df", "metadata": {}, "source": [ "## Launch AEDT and initialize HFSS\n", "\n", "Launch AEDT and initialize HFSS. If there is an active HFSS design, the ``hfss``\n", "object is linked to it. Otherwise, a new design is created." ] }, { "cell_type": "code", "execution_count": null, "id": "e97e00b5", "metadata": {}, "outputs": [], "source": [ "circuit = aedt.Circuit(\n", " project=project_name,\n", " new_desktop=True,\n", " version=AEDT_VERSION,\n", " non_graphical=NG_MODE,\n", ")" ] }, { "cell_type": "markdown", "id": "a6e70d00", "metadata": {}, "source": [ "## Set variable names\n", "\n", "Set the name of the resistor in Circuit." ] }, { "cell_type": "code", "execution_count": null, "id": "d81728a1", "metadata": {}, "outputs": [], "source": [ "resistor_body_name = \"Circuit_Component\"" ] }, { "cell_type": "markdown", "id": "782c03db", "metadata": {}, "source": [ "Set the name of the cylinder body in HFSS." ] }, { "cell_type": "code", "execution_count": null, "id": "6fb61b3a", "metadata": {}, "outputs": [], "source": [ "device3D_body_name = \"Device_3D\"" ] }, { "cell_type": "markdown", "id": "2303b3aa", "metadata": {}, "source": [ "## Get HFSS design\n", "\n", "Get the HFSS design and prepare the material for the thermal link." ] }, { "cell_type": "code", "execution_count": null, "id": "e781b848", "metadata": {}, "outputs": [], "source": [ "hfss = aedt.Hfss(project=circuit.project_name)" ] }, { "cell_type": "markdown", "id": "a8438890", "metadata": {}, "source": [ "## Create a material\n", "\n", "Create a material to be used to set the temperature map on it.\n", "The material is created by duplicating the material assigned to the cylinder." ] }, { "cell_type": "code", "execution_count": null, "id": "93e59201", "metadata": {}, "outputs": [], "source": [ "material_name = hfss.modeler.objects_by_name[device3D_body_name].material_name\n", "new_material_name = material_name + \"_dataset\"\n", "new_material = hfss.materials.duplicate_material(\n", " material=material_name, name=new_material_name\n", ")" ] }, { "cell_type": "markdown", "id": "b500cd5c", "metadata": {}, "source": [ "## Modify material properties\n", "\n", "Save the conductivity value. It is used later in the iterations." ] }, { "cell_type": "code", "execution_count": null, "id": "fb80f19e", "metadata": {}, "outputs": [], "source": [ "old_conductivity = new_material.conductivity.value" ] }, { "cell_type": "markdown", "id": "e0052b0b", "metadata": {}, "source": [ "Assign the new material to the cylinder object in HFSS." ] }, { "cell_type": "code", "execution_count": null, "id": "69bcd5d3", "metadata": {}, "outputs": [], "source": [ "hfss.modeler.objects_by_name[device3D_body_name].material_name = new_material_name" ] }, { "cell_type": "markdown", "id": "856d8388", "metadata": {}, "source": [ "Because this material has a high conductivity, HFSS automatically deactivates ``solve_inside``.\n", "It must be turned back on to evaluate the losses inside the cylinder." ] }, { "cell_type": "code", "execution_count": null, "id": "85d09cf3", "metadata": {}, "outputs": [], "source": [ "hfss.modeler.objects_by_name[device3D_body_name].solve_inside = True" ] }, { "cell_type": "markdown", "id": "1468b7bd", "metadata": {}, "source": [ "## Get Icepak design" ] }, { "cell_type": "code", "execution_count": null, "id": "8783d8ef", "metadata": {}, "outputs": [], "source": [ "icepak = aedt.Icepak(project=circuit.project_name)" ] }, { "cell_type": "markdown", "id": "3a3bba61", "metadata": {}, "source": [ "## Set parameters for iterations\n", "\n", "Set the initial temperature to a value closer to the final one, to speed up the convergence." ] }, { "cell_type": "code", "execution_count": null, "id": "7c314b2d", "metadata": {}, "outputs": [], "source": [ "circuit[\"TempE\"] = \"300cel\"" ] }, { "cell_type": "markdown", "id": "c841b961", "metadata": {}, "source": [ "Set the maximum number of iterations." ] }, { "cell_type": "code", "execution_count": null, "id": "89adf7bd", "metadata": {}, "outputs": [], "source": [ "max_iter = 2" ] }, { "cell_type": "markdown", "id": "2503d03c", "metadata": {}, "source": [ "Set the residual convergence criteria to stop the iterations." ] }, { "cell_type": "code", "execution_count": null, "id": "0c85f4a3", "metadata": {}, "outputs": [], "source": [ "temp_residual_limit = 0.02\n", "loss_residual_limit = 0.02" ] }, { "cell_type": "markdown", "id": "5c19c977", "metadata": {}, "source": [ "This variable is to contain iteration statistics." ] }, { "cell_type": "code", "execution_count": null, "id": "702a75f4", "metadata": {}, "outputs": [], "source": [ "stats = {}" ] }, { "cell_type": "markdown", "id": "49ead8ee", "metadata": {}, "source": [ "## Start iterations\n", "\n", "Each ``for`` loop is a complete two-way iteration.\n", "The code is thoroughly commented.\n", "For a full understanding, read the inline comments carefully." ] }, { "cell_type": "code", "execution_count": null, "id": "21ba0145", "metadata": {}, "outputs": [], "source": [ "for cp_iter in range(1, max_iter + 1):\n", " stats[cp_iter] = {}\n", "\n", " # Step 1: Solve the HFSS design.\n", " #\n", " # Solve the HFSS design.\n", " hfss.analyze(cores=NUM_CORES)\n", "\n", " # Step 2: Refresh the dynamic link and solve the Circuit design.\n", " #\n", " # Find the HFSS subcomponent in Circuit.\n", " # This information is required by the ``refresh_dynamic_link()`` and ``push_excitations()`` methods.\n", " hfss_component_name = \"\"\n", " hfss_instance_name = \"\"\n", " for component in circuit.modeler.components.components.values():\n", " if (\n", " component.model_name is not None\n", " and hfss.design_name in component.model_name\n", " ):\n", " hfss_component_name = component.model_name\n", " hfss_instance_name = component.refdes\n", " break\n", " if not hfss_component_name or not hfss_instance_name:\n", " raise \"Hfss component not found in Circuit design\"\n", "\n", " # Refresh the dynamic link.\n", " circuit.modeler.schematic.refresh_dynamic_link(name=hfss_component_name)\n", "\n", " # Solve the Circuit design.\n", " circuit.analyze()\n", "\n", " # Step 3: Push the excitations. (HFSS design results are scaled automatically.)\n", " #\n", " # Push the excitations.\n", " circuit.push_excitations(instance=hfss_instance_name)\n", "\n", " # Step 4: Extract the resistor's power loss value from the Circuit design.\n", " #\n", " # Evaluate the power loss on the resistor.\n", " r_losses = circuit.post.get_solution_data(\n", " expressions=\"0.5*mag(I(I1)*V(V1))\"\n", " ).data_magnitude()[0]\n", "\n", " # Save the losses in the stats.\n", " stats[cp_iter][\"losses\"] = r_losses\n", "\n", " # Step 5: Set the resistor's power loss value in the Icepak design (block thermal condition)\n", " #\n", " # Find the solid block boundary in Icepak.\n", " boundaries = icepak.boundaries\n", " boundary = None\n", " for bb in boundaries:\n", " if bb.name == \"Block1\":\n", " boundary = bb\n", " break\n", " if not boundary:\n", " raise \"Block boundary not defined in Icepak design.\"\n", "\n", " # Set the resistor's power loss in the Block Boundary.\n", " boundary.props[\"Total Power\"] = str(r_losses) + \"W\"\n", "\n", " # Step 6: Solve the Icepak design.\n", " #\n", " # Clear linked data. Otherwise, Icepak continues to run the simulation with the initial losses.\n", " icepak.clear_linked_data()\n", "\n", " # Solve the Icepak design.\n", " icepak.analyze(cores=NUM_CORES)\n", "\n", " # Step 7: Export the temperature map from the Icepak design and create a new 3D dataset with it.\n", " #\n", " # Export the temperature map to a file.\n", " fld_filename = os.path.join(\n", " icepak.working_directory, f\"temperature_map_{cp_iter}.fld\"\n", " )\n", " icepak.post.export_field_file(\n", " quantity=\"Temp\",\n", " output_file=fld_filename,\n", " assignment=\"AllObjects\",\n", " objects_type=\"Vol\",\n", " )\n", "\n", " # Convert the FLD file into a dataset tab file compatible with the dataset import.\n", " # The existing header lines must be removed and replaced with a single header line\n", " # containing the value unit.\n", " with open(fld_filename, \"r\") as f:\n", " lines = f.readlines()\n", "\n", " _ = lines.pop(0)\n", " _ = lines.pop(0)\n", " lines.insert(0, '\"X\" \"Y\" \"Z\" \"cel\"\\n')\n", "\n", " basename, _ = os.path.splitext(fld_filename)\n", " tab_filename = basename + \"_dataset.tab\"\n", "\n", " with open(tab_filename, \"w\") as f:\n", " f.writelines(lines)\n", "\n", " # Import the 3D dataset.\n", " dataset_name = f\"temp_map_step_{cp_iter}\"\n", " hfss.import_dataset3d(\n", " input_file=tab_filename, name=dataset_name, is_project_dataset=True\n", " )\n", "\n", " # Step 8: Update material properties in the HFSS design based on the new dataset.\n", " #\n", " # Set the new conductivity value.\n", " new_material.conductivity.value = (\n", " f\"{old_conductivity}*Pwl($TempDepCond,clp(${dataset_name},X,Y,Z))\"\n", " )\n", "\n", " # Switch off the thermal modifier of the material, if any.\n", " new_material.conductivity.thermalmodifier = None\n", "\n", " # Step 9: Extract the average temperature of the resistor from the Icepak design.\n", " #\n", " # Get the mean temperature value on the high-resistivity object.\n", " mean_temp = icepak.post.get_scalar_field_value(\n", " quantity=\"Temp\", scalar_function=\"Mean\", object_name=resistor_body_name\n", " )\n", "\n", " # Save the temperature in the iteration statistics.\n", " stats[cp_iter][\"temp\"] = mean_temp\n", "\n", " # Step 10: Update the resistance value in the Circuit design.\n", " #\n", " # Set this temperature in Circuit in the ``TempE`` variable.\n", " circuit[\"TempE\"] = f\"{mean_temp}cel\"\n", "\n", " # Save the project\n", " circuit.save_project()\n", "\n", " # Check the convergence of the iteration\n", " #\n", " # Evaluate the relative residuals on temperature and losses.\n", " # If the residuals are smaller than the threshold, set the convergence flag to ``True```.\n", " # Residuals are calculated starting from the second iteration.\n", " converged = False\n", " stats[cp_iter][\"converged\"] = converged\n", " if cp_iter > 1:\n", " delta_temp = abs(stats[cp_iter][\"temp\"] - stats[cp_iter - 1][\"temp\"]) / abs(\n", " stats[cp_iter - 1][\"temp\"]\n", " )\n", " delta_losses = abs(\n", " stats[cp_iter][\"losses\"] - stats[cp_iter - 1][\"losses\"]\n", " ) / abs(stats[cp_iter - 1][\"losses\"])\n", " if delta_temp <= temp_residual_limit and delta_losses <= loss_residual_limit:\n", " converged = True\n", " stats[cp_iter][\"converged\"] = converged\n", " else:\n", " delta_temp = None\n", " delta_losses = None\n", "\n", " # Save the relative residuals in the iteration stats.\n", " stats[cp_iter][\"delta_temp\"] = delta_temp\n", " stats[cp_iter][\"delta_losses\"] = delta_losses\n", "\n", " # Exit from the loop if the convergence is reached.\n", " if converged:\n", " break" ] }, { "cell_type": "markdown", "id": "d634c599", "metadata": {}, "source": [ "## Print overall statistics\n", "\n", "Print the overall statistics for the multiphysics loop." ] }, { "cell_type": "code", "execution_count": null, "id": "ed724657", "metadata": {}, "outputs": [], "source": [ "for i in stats:\n", " txt = \"yes\" if stats[i][\"converged\"] else \"no\"\n", " delta_temp = (\n", " f\"{stats[i]['delta_temp']:.4f}\"\n", " if stats[i][\"delta_temp\"] is not None\n", " else \"None\"\n", " )\n", " delta_losses = (\n", " f\"{stats[i]['delta_losses']:.4f}\"\n", " if stats[i][\"delta_losses\"] is not None\n", " else \"None\"\n", " )\n", " print(\n", " f\"Step {i}: temp={stats[i]['temp']:.3f}, losses={stats[i]['losses']:.3f}, \"\n", " f\"delta_temp={delta_temp}, delta_losses={delta_losses}, \"\n", " f\"converged={txt}\"\n", " )" ] }, { "cell_type": "markdown", "id": "618501ca", "metadata": {}, "source": [ "## Release AEDT\n", "\n", "Release AEDT and close the example." ] }, { "cell_type": "code", "execution_count": null, "id": "2b9a1773", "metadata": {}, "outputs": [], "source": [ "icepak.save_project()\n", "icepak.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": "428d6e42", "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": "1bf6b30d", "metadata": {}, "outputs": [], "source": [ "temp_folder.cleanup()" ] } ], "metadata": { "jupytext": { "cell_metadata_filter": "-all", "main_language": "python", "notebook_metadata_filter": "-all" } }, "nbformat": 4, "nbformat_minor": 5 }