{ "cells": [ { "cell_type": "markdown", "id": "3759f988", "metadata": {}, "source": [ "# Electro-Thermal-Structural Analysis\n", "\n", "This example uses PyAEDT to model a copper busbar in Q3D, run a stepped DC analysis,\n", "and pass the resulting losses to a transient thermal simulation with two-way coupling\n", "and restart enabled at each substep.\n", "The example includes the following steps:\n", "\n", "1. Import the required packages and load a Q3D project that contains a DC conduction design.\n", "2. Run the simulation for a parametric source value.\n", "3. Plot the mesh, current-density magnitude, and ohmic losses.\n", "4. Enable two-way coupling with temperature feedback from Icepak to Q3D at each substep.\n", "5. Complete the transient thermal simulation.\n", "6. Create a structural analysis using the final object-averaged temperatures as thermal loads.\n", "7. Run the structural analysis.\n", "8. Visualize the resulting deformation.\n", "\n", "Keywords: **Multiphysics**, **Q3D**, **Icepak**, **Icepak FEA Structural**." ] }, { "cell_type": "markdown", "id": "24b1b20e", "metadata": {}, "source": [ "## Perform imports and define constants\n", "\n", "Perform required imports." ] }, { "cell_type": "code", "execution_count": null, "id": "62458089", "metadata": {}, "outputs": [], "source": [ "import tempfile\n", "import time\n", "import numpy as np\n", "\n", "import ansys.aedt.core # Interface to Ansys Electronics Desktop\n", "from ansys.aedt.core.generic.aedt_constants import IcepakFeaConstants\n", "from ansys.aedt.core.examples.downloads import download_file" ] }, { "cell_type": "markdown", "id": "bdf9a84b", "metadata": {}, "source": [ "## Define constants\n", "\n", "Constants help ensure consistency and avoid repetition throughout the example." ] }, { "cell_type": "code", "execution_count": null, "id": "bd729ab8", "metadata": {}, "outputs": [], "source": [ "AEDT_VERSION = \"2026.1\"\n", "NUM_CORES = 4\n", "NG_MODE = False # Open AEDT UI when it is launched.\n", "Q3D_DESIGN_NAME = \"Q3DDesign\"\n", "ICEPAK_DESIGN_NAME = \"Icepak\"\n", "TIME = [0, 6, 7, 12] #s\n", "CURRENT = [100, 100, 50, 50] #A\n", "Q3D_DESIGN_VAR_NAME = 'I_DC'\n", "END_TIME = 4 #s, thermal transient end time\n", "STEPS = 2 #n. of substeps\n", "TH_TIME_STEP = 0.5 #s thermal transient time step" ] }, { "cell_type": "markdown", "id": "db1dd8e1", "metadata": {}, "source": [ "## Create temporary directory\n", "\n", "Create a temporary working directory and download the AEDT file.\n", "The name of the working folder is stored in ``temp_folder.name``.\n", "\n", "> **Note:** The final cell in the notebook cleans up the temporary folder. If you want to\n", "> retrieve the AEDT project and data, do so before executing the final cell in the notebook." ] }, { "cell_type": "code", "execution_count": null, "id": "da2ef989", "metadata": {}, "outputs": [], "source": [ "temp_folder = tempfile.TemporaryDirectory(suffix=\".ansys\")\n", "project_path = download_file(\n", " source=r\"pyaedt/busbar_3_physics_q3d_icepak_icepakfea\",\n", " name=\"Q3D_Busbar.aedt\",\n", " local_path=temp_folder.name)" ] }, { "cell_type": "markdown", "id": "edb9ae01", "metadata": {}, "source": [ "## Launch an instance AEDT\n", "\n", "Create an instance of the ``Q3d`` class.\n", "The Ansys Electronics Desktop will be launched with the active Q3d design.\n", "The ``q3d`` object is subsequently used to create and simulate the model." ] }, { "cell_type": "code", "execution_count": null, "id": "b8e44782", "metadata": {}, "outputs": [], "source": [ "q3d = ansys.aedt.core.Q3d(\n", " project=project_path,\n", " design=Q3D_DESIGN_NAME,\n", " version=AEDT_VERSION,\n", " non_graphical=NG_MODE,\n", ")" ] }, { "cell_type": "markdown", "id": "fc8735be", "metadata": {}, "source": [ "## Model Preparation - Materials\n", "\n", "Create temperature dependent materials.\n", "Duplicate copper and add temperature dependent electrical conductivity." ] }, { "cell_type": "code", "execution_count": null, "id": "98455c9b", "metadata": {}, "outputs": [], "source": [ "target_material = \"copper\"" ] }, { "cell_type": "code", "execution_count": null, "id": "28d9a5e4", "metadata": {}, "outputs": [], "source": [ "cu_temp = q3d.materials.duplicate_material(material=target_material, name=\"copper_temp_dep\")\n", "cu_temp.conductivity.add_thermal_modifier_free_form(\"1.0/(1.0+{}*(Temp-20))\".format(0.000001))" ] }, { "cell_type": "code", "execution_count": null, "id": "eb401d51", "metadata": {}, "outputs": [], "source": [ "matching_objects = [\n", " obj_name\n", " for obj_name in q3d.modeler.object_names\n", " if q3d.modeler[obj_name].material_name\n", " and q3d.modeler[obj_name].material_name.lower() == target_material.lower()\n", "]" ] }, { "cell_type": "code", "execution_count": null, "id": "ce2b829a", "metadata": {}, "outputs": [], "source": [ "q3d.assign_material(assignment=matching_objects, material=cu_temp.name)" ] }, { "cell_type": "markdown", "id": "93999137", "metadata": {}, "source": [ "## Set objects temperature and enable feedback" ] }, { "cell_type": "code", "execution_count": null, "id": "a0c5b329", "metadata": {}, "outputs": [], "source": [ "q3d.modeler.set_objects_temperature(assignment=matching_objects, ambient_temperature=20)" ] }, { "cell_type": "markdown", "id": "0d314ff8", "metadata": {}, "source": [ "## Define excitations" ] }, { "cell_type": "code", "execution_count": null, "id": "ab85f0db", "metadata": {}, "outputs": [], "source": [ "q3d[Q3D_DESIGN_VAR_NAME]= str(CURRENT[0])+\"A\"" ] }, { "cell_type": "markdown", "id": "f5d1954a", "metadata": {}, "source": [ "## Create Icepak target design" ] }, { "cell_type": "code", "execution_count": null, "id": "cc20dac5", "metadata": {}, "outputs": [], "source": [ "q3d.create_em_target_design(design=\"Icepak\", design_setup=\"Natural\")\n", "ipk = ansys.aedt.core.Icepak()\n", "ipk.design_name = ICEPAK_DESIGN_NAME" ] }, { "cell_type": "markdown", "id": "764d2db7", "metadata": {}, "source": [ "## Define solution setup\n", "\n", "Set the current value for the coupling-step time using linear interpolation" ] }, { "cell_type": "code", "execution_count": null, "id": "4f6c7638", "metadata": {}, "outputs": [], "source": [ "coupling_time = END_TIME / STEPS\n", "current_value = np.interp(coupling_time, TIME, CURRENT)\n", "ipk[Q3D_DESIGN_VAR_NAME] = f\"{current_value:.0f}A\"" ] }, { "cell_type": "markdown", "id": "4334918b", "metadata": {}, "source": [ "Enable desktop logging" ] }, { "cell_type": "code", "execution_count": null, "id": "e2fb652d", "metadata": {}, "outputs": [], "source": [ "ipk.logger.enable_desktop_log()\n", "ipk.logger.add_message(message_type=0, message_text=\"Solving substep n.1 of \" + str(STEPS), level=\"Project\", proj_name=ipk.project_name)" ] }, { "cell_type": "markdown", "id": "7c8cbd59", "metadata": {}, "source": [ "Icepak transient simulation and time stepping setup" ] }, { "cell_type": "code", "execution_count": null, "id": "e4bea569", "metadata": {}, "outputs": [], "source": [ "ipk.solution_type = \"Transient\"\n", "setup = ipk.setups[0]\n", "setup.props[\"Stop Time\"] = str(END_TIME/STEPS)+\"s\"\n", "setup.props[\"Time Step\"] = str(TH_TIME_STEP)+\"s\"\n", "setup.props[\"N Steps\"] = 1\n", "ipk.save_project()" ] }, { "cell_type": "markdown", "id": "07b02a12", "metadata": {}, "source": [ "Get the list of object names included in the EM Loss and delete the EM Loss boundary." ] }, { "cell_type": "code", "execution_count": null, "id": "0ab962fb", "metadata": {}, "outputs": [], "source": [ "em_loss = next(bound for bound in ipk.boundaries if bound.type == \"EM Loss\")\n", "objs = [ipk.modeler.objects[obj_id].name for obj_id in em_loss.props[\"Objects\"]]\n", "em_loss.delete()" ] }, { "cell_type": "markdown", "id": "371362e0", "metadata": {}, "source": [ "## Re-creation of the EM Loss\n", "\n", "Re-creation of the EM Loss with mapping of the current pulse parameter between Icepak and Maxwell.\n", "With this setting, the value of the current pulse to be used in the Maxwell simulation is driven from Icepak." ] }, { "cell_type": "code", "execution_count": null, "id": "56e80a33", "metadata": {}, "outputs": [], "source": [ "ipk.assign_em_losses(design=Q3D_DESIGN_NAME, setup=\"Setup1\", assignment=objs, parameters=[Q3D_DESIGN_VAR_NAME], sweep=\"LastAdaptive\", q3d_loss_type=\"DCVolOrACSurfLoss\", map_frequency=\"1kHz\")\n", "ipk.assign_2way_coupling(setup=setup.name, number_of_iterations=2)\n", "ipk.logger.add_message(0, \"Current value \" + str(ipk[Q3D_DESIGN_VAR_NAME]), level=\"Project\", proj_name=ipk.project_name)" ] }, { "cell_type": "markdown", "id": "8969446a", "metadata": {}, "source": [ "## Run analysis\n", "\n", "Run simulation for the current coupling step" ] }, { "cell_type": "code", "execution_count": null, "id": "f42a67bb", "metadata": {}, "outputs": [], "source": [ "ipk.analyze()\n", "ipk.logger.add_message(0, \"Substep n.1 of \" + str(STEPS) + \" completed\", level=\"Project\", proj_name=ipk.project_name)" ] }, { "cell_type": "markdown", "id": "a98c3d6b", "metadata": {}, "source": [ "Additional coupling steps loop definition" ] }, { "cell_type": "code", "execution_count": null, "id": "1784712d", "metadata": {}, "outputs": [], "source": [ "for n in range(STEPS-1):\n", " step = n + 2\n", " ipk.logger.add_message(0, \"Solving substep n.\" + str(step) + \" of \" + str(STEPS), level=\"Project\", proj_name=ipk.project_name)" ] }, { "cell_type": "markdown", "id": "9a7b4b86", "metadata": {}, "source": [ "### Duplicate Icepak design from previous substep and enable the restart\n", "\n", "Map ``Q3D_DESIGN_VAR_NAME`` to the value used in the previous substep to avoid\n", "re-solving the source Icepak design.\n", "``Copy Fields From Source`` transfers the results to the restarted design, so only\n", "the final Icepak design must be kept." ] }, { "cell_type": "code", "execution_count": null, "id": "683516da", "metadata": {}, "outputs": [], "source": [ " ipk_name = ipk.design_name\n", " ipk.duplicate_design(ipk_name)\n", " ipk = ansys.aedt.core.Icepak()\n", " ipk.cleanup_solution()\n", " ipk.save_project()\n", "\n", " setup = ipk.setups[0]\n", " setup.props[\"Import Start Time\"] = True\n", " setup.props[\"Copy Fields From Source\"] = True\n", " setup.start_continue_from_previous_setup(design=ipk_name, solution=setup.name + \" : Transient\", map_variables_by_name=False, parameters={Q3D_DESIGN_VAR_NAME:f\"{np.interp((step - 1) * END_TIME / STEPS, TIME, CURRENT):.0f}A\"})\n", " setup.props[\"Stop Time\"] = str(step*END_TIME/STEPS)+\"s\"" ] }, { "cell_type": "markdown", "id": "911d9646", "metadata": {}, "source": [ "Setup of the current pulse value for the current coupling step using linear interpolation" ] }, { "cell_type": "code", "execution_count": null, "id": "711372c8", "metadata": {}, "outputs": [], "source": [ " ipk[Q3D_DESIGN_VAR_NAME] = str(int(np.interp(step*END_TIME/STEPS, TIME, CURRENT))) + \"A\"\n", " ipk.logger.add_message(0, \"Current value \" + str(ipk[Q3D_DESIGN_VAR_NAME]), level=\"Project\", proj_name=ipk.project_name)" ] }, { "cell_type": "markdown", "id": "833de2b7", "metadata": {}, "source": [ "Simulation run for the current coupling step" ] }, { "cell_type": "code", "execution_count": null, "id": "fa507224", "metadata": {}, "outputs": [], "source": [ " ipk.analyze()\n", " ipk.logger.add_message(0, \"Substep n.\" + str(step) + \" of \" + str(STEPS) + \" completed\", level=\"Project\", proj_name=ipk.project_name)" ] }, { "cell_type": "markdown", "id": "ff96d876", "metadata": {}, "source": [ "## Remove all Icepak designs\n", "\n", "Remove all Icepak designs except the last one because all the field data are copied into the last Icepak design." ] }, { "cell_type": "code", "execution_count": null, "id": "531f4e74", "metadata": {}, "outputs": [], "source": [ "[ipk.delete_design(d) for d in ipk.design_list if d == ICEPAK_DESIGN_NAME]" ] }, { "cell_type": "markdown", "id": "6989ad67", "metadata": {}, "source": [ "## Icepak Thermal Transient Postprocess\n", "\n", "Access the FieldSummary functionality" ] }, { "cell_type": "code", "execution_count": null, "id": "17ee685b", "metadata": {}, "outputs": [], "source": [ "field_sum = ipk.post.create_field_summary()" ] }, { "cell_type": "markdown", "id": "1c70e88c", "metadata": {}, "source": [ "Compute time steps" ] }, { "cell_type": "code", "execution_count": null, "id": "7dc535e2", "metadata": {}, "outputs": [], "source": [ "substep_time = END_TIME / STEPS\n", "time_substeps_ns = range(\n", " 0,\n", " int((END_TIME + substep_time) * 1e9),\n", " int(substep_time * 1e9),\n", ")" ] }, { "cell_type": "markdown", "id": "6c8ca0c4", "metadata": {}, "source": [ "Convert time steps values to strings to be passed to field summary calculation" ] }, { "cell_type": "code", "execution_count": null, "id": "1fced5fb", "metadata": {}, "outputs": [], "source": [ "time_substeps_str = [f\"{time_ns}ns\" for time_ns in time_substeps_ns]\n", "for obj in objs:\n", " temp_avg_dict = {\n", " \"name\": \"temp_avg_\"+ obj,\n", " \"description\": f\"Average Temperature on {obj}\",\n", " \"design_type\": [\"Icepak\"],\n", " \"fields_type\": [\"Fields\"],\n", " \"solution_type\": \"Transient\",\n", " \"primary_sweep\": \"\",\n", " \"assignment\": \"\",\n", " \"assignment_type\": [\"Solid\"],\n", " \"operations\": [\n", " \"Fundamental_Quantity('Temp')\",\n", " \"EnterVolume('assignment')\",\n", " \"Operation('VolumeValue')\",\n", " \"Operation('Mean')\",\n", " ],\n", " \"report\": [\"Field3D\"]\n", " }\n", " expr_name = ipk.post.fields_calculator.add_expression(calculation=temp_avg_dict,\n", " assignment=obj,\n", " name=\"T_avg_\"+obj)\n", " report_temp = ipk.post.create_report(expressions=expr_name, primary_sweep_variable='Time')\n", "\n", " [field_sum.add_calculation(\n", " entity=\"Object\",\n", " geometry=\"Volume\",\n", " geometry_name=obj,\n", " quantity=\"Temperature\",\n", " time=t) for t in time_substeps_str]" ] }, { "cell_type": "code", "execution_count": null, "id": "9ce3648f", "metadata": {}, "outputs": [], "source": [ "temperature_data = field_sum.get_field_summary_data(\n", " pandas_output=True,\n", " intrinsics=\"All times\",\n", " variation=ipk.available_variations.variations(ipk.nominal_adaptive, True)[0]\n", ")" ] }, { "cell_type": "markdown", "id": "4a9a30af", "metadata": {}, "source": [ "The temperature results are grouped by object, with STEPS + 1 values per object.\n", "Select the temperatures of the object chosen as most critical, for which the time step corresponding to the maximum temperature will be selected.\n", "Averaged temperatures of all objects at this time step will be used as boundary conditions for the subsequent structural analysis." ] }, { "cell_type": "code", "execution_count": null, "id": "a765aac9", "metadata": {}, "outputs": [], "source": [ "object_pos = len(q3d.modeler.model_objects)\n", "reference_temperatures = temperature_data[\"Mean\"].to_numpy()[(object_pos-1)*(STEPS + 1):object_pos*(STEPS + 1)]" ] }, { "cell_type": "markdown", "id": "8af01731", "metadata": {}, "source": [ "Find the time step with the maximum of the average temperatures for the chosen object" ] }, { "cell_type": "code", "execution_count": null, "id": "df939ce6", "metadata": {}, "outputs": [], "source": [ "worst_step_index = np.argmax(reference_temperatures)" ] }, { "cell_type": "markdown", "id": "6e55dd54", "metadata": {}, "source": [ "Extract object temperatures at the selected worst-case time step" ] }, { "cell_type": "code", "execution_count": null, "id": "ce207bd2", "metadata": {}, "outputs": [], "source": [ "worst_case_data = temperature_data.iloc[worst_step_index::(STEPS+1)]\n", "temperature_by_object = dict(zip(worst_case_data[\"Entity\"], worst_case_data[\"Mean\"]))" ] }, { "cell_type": "markdown", "id": "02594bf3", "metadata": {}, "source": [ "## Create EM Target design\n", "\n", "Mechanical Structural Static simulation" ] }, { "cell_type": "code", "execution_count": null, "id": "f6547a96", "metadata": {}, "outputs": [], "source": [ "q3d.create_em_target_design(design=IcepakFeaConstants.NAME)\n", "design_list = q3d.design_list" ] }, { "cell_type": "markdown", "id": "9d185034", "metadata": {}, "source": [ "Connect to the newly created IcepakFEA design." ] }, { "cell_type": "code", "execution_count": null, "id": "887e7137", "metadata": {}, "outputs": [], "source": [ "mech = ansys.aedt.core.Mechanical(version=AEDT_VERSION)" ] }, { "cell_type": "markdown", "id": "20a91fd9", "metadata": {}, "source": [ "Change the solution type to Structural" ] }, { "cell_type": "code", "execution_count": null, "id": "366bb1f1", "metadata": {}, "outputs": [], "source": [ "mech.solution_type=\"Structural\"" ] }, { "cell_type": "markdown", "id": "dbd4b1ef", "metadata": {}, "source": [ "assign uniform temperature excitations" ] }, { "cell_type": "code", "execution_count": null, "id": "614e2b3d", "metadata": {}, "outputs": [], "source": [ "for obj, temp in temperature_by_object.items():\n", " mech.assign_thermal_condition_uniform(assignment=[obj], temperature =str(temp)+\"cel\", name=f\"ThermalCond_{obj}\")" ] }, { "cell_type": "markdown", "id": "86b0a9ef", "metadata": {}, "source": [ "## Retrieve from Q3D the Named selections face IDs\n", "\n", "Retrieve Q3D named selection face IDs.\n", "IcepakFEA EM target designs do not automatically inherit Q3D named selections.\n", "The IDs are remapped in IcepakFEA before applying structural constraints." ] }, { "cell_type": "code", "execution_count": null, "id": "273979b1", "metadata": {}, "outputs": [], "source": [ "q3d_face_ids = [face_id for ns in q3d.modeler.user_lists for face_id in ns.props[\"List\"]]" ] }, { "cell_type": "code", "execution_count": null, "id": "eeae85d1", "metadata": {}, "outputs": [], "source": [ "object_to_Save = {}\n", "mech_face_ids = []" ] }, { "cell_type": "code", "execution_count": null, "id": "c341f490", "metadata": {}, "outputs": [], "source": [ "for obj in q3d.modeler.object_list:\n", " matching_faces = [face.id for face in obj.faces if face.id in q3d_face_ids]\n", " if matching_faces:\n", " for face_id in matching_faces:\n", " fpos = q3d.modeler.get_face_center(assignment=face_id)\n", " mech_face_ids.append(mech.modeler.get_faceid_from_position(position=fpos, assignment=obj.name))" ] }, { "cell_type": "markdown", "id": "14950d6b", "metadata": {}, "source": [ "Assign ``Fixed support`` boundary condition" ] }, { "cell_type": "code", "execution_count": null, "id": "7d38a2e9", "metadata": {}, "outputs": [], "source": [ "mech.assign_fixed_support(assignment=mech_face_ids, name='Fixed')" ] }, { "cell_type": "markdown", "id": "e9f1dea4", "metadata": {}, "source": [ "## Create the solution setup\n", "\n", "Create a new setup, validate and analyze" ] }, { "cell_type": "code", "execution_count": null, "id": "9fbab7e6", "metadata": {}, "outputs": [], "source": [ "mech_setup = mech.create_setup()\n", "mech.validate_simple()\n", "mech.analyze()" ] }, { "cell_type": "markdown", "id": "e7bbb890", "metadata": {}, "source": [ "## Post-processing\n", "\n", "Create postprocessing surface plots of equivalent stress and displacement magnitude for all model objects." ] }, { "cell_type": "code", "execution_count": null, "id": "68e14f52", "metadata": {}, "outputs": [], "source": [ "plot_stress = mech.post.create_fieldplot_surface(\n", " assignment=mech.modeler.object_list, quantity=\"Equivalent Stress\", plot_name=\"Equivalent Stress\")" ] }, { "cell_type": "code", "execution_count": null, "id": "dafdda4b", "metadata": {}, "outputs": [], "source": [ "plot_displ = mech.post.create_fieldplot_surface(\n", " assignment=mech.modeler.object_list, quantity=\"Mag_Displacement\", plot_name=\"Mag_Displacement\")" ] }, { "cell_type": "markdown", "id": "638ff8a6", "metadata": {}, "source": [ "## Release AEDT" ] }, { "cell_type": "code", "execution_count": null, "id": "d2fed6c5", "metadata": {}, "outputs": [], "source": [ "ipk.save_project()\n", "ipk.release_desktop()" ] }, { "cell_type": "code", "execution_count": null, "id": "bf2bfa5e", "metadata": {}, "outputs": [], "source": [ "# Wait 3 seconds to allow AEDT to shut down before cleaning the temporary directory.\n", "time.sleep(3)" ] }, { "cell_type": "markdown", "id": "170d4f17", "metadata": {}, "source": [ "## Clean up\n", "\n", "All project files are saved in the folder ``temp_folder.name``.\n", "If you've run this example as a Jupyter notebook, you\n", "can retrieve those project files. The following cell\n", "removes all temporary files, including the project folder." ] }, { "cell_type": "code", "execution_count": null, "id": "98ded34e", "metadata": {}, "outputs": [], "source": [ "temp_folder.cleanup()" ] } ], "metadata": { "jupytext": { "cell_metadata_filter": "-all", "main_language": "python", "notebook_metadata_filter": "-all" } }, "nbformat": 4, "nbformat_minor": 5 }