Download this example
Download this example as a Jupyter Notebook or as a Python script.
Electro-Thermal-Structural Analysis#
This example uses PyAEDT to model a copper busbar in Q3D, run a stepped DC analysis, and pass the resulting losses to a transient thermal simulation with two-way coupling and restart enabled at each substep. The example includes the following steps:
Import the required packages and load a Q3D project that contains a DC conduction design.
Run the simulation for a parametric source value.
Plot the mesh, current-density magnitude, and ohmic losses.
Enable two-way coupling with temperature feedback from Icepak to Q3D at each substep.
Complete the transient thermal simulation.
Create a structural analysis using the final object-averaged temperatures as thermal loads.
Run the structural analysis.
Visualize the resulting deformation.
Keywords: Multiphysics, Q3D, Icepak, Icepak FEA Structural.
Perform imports and define constants#
Perform required imports.
[1]:
import tempfile
import time
import numpy as np
import ansys.aedt.core # Interface to Ansys Electronics Desktop
from ansys.aedt.core.generic.aedt_constants import IcepakFeaConstants
from ansys.aedt.core.examples.downloads import download_file
Define constants#
Constants help ensure consistency and avoid repetition throughout the example.
[2]:
AEDT_VERSION = "2026.1"
NUM_CORES = 4
NG_MODE = False # Open AEDT UI when it is launched.
Q3D_DESIGN_NAME = "Q3DDesign"
ICEPAK_DESIGN_NAME = "Icepak"
TIME = [0, 6, 7, 12] #s
CURRENT = [100, 100, 50, 50] #A
Q3D_DESIGN_VAR_NAME = 'I_DC'
END_TIME = 4 #s, thermal transient end time
STEPS = 2 #n. of substeps
TH_TIME_STEP = 0.5 #s thermal transient time step
Create temporary directory#
Create a temporary working directory and download the AEDT file. The name of the working folder is stored in temp_folder.name.
Note: The final cell in the notebook cleans up the temporary folder. If you want to retrieve the AEDT project and data, do so before executing the final cell in the notebook.
[3]:
temp_folder = tempfile.TemporaryDirectory(suffix=".ansys")
project_path = download_file(
source=r"pyaedt/busbar_3_physics_q3d_icepak_icepakfea",
name="Q3D_Busbar.aedt",
local_path=temp_folder.name)
Launch an instance AEDT#
Create an instance of the Q3d class. The Ansys Electronics Desktop will be launched with the active Q3d design. The q3d object is subsequently used to create and simulate the model.
[4]:
q3d = ansys.aedt.core.Q3d(
project=project_path,
design=Q3D_DESIGN_NAME,
version=AEDT_VERSION,
non_graphical=NG_MODE,
)
PyAEDT INFO: Parsing C:\Users\ansys\AppData\Local\Temp\tmp5g0t0y_3.ansys\pyaedt\busbar_3_physics_q3d_icepak_icepakfea\Q3D_Busbar.aedt.
PyAEDT INFO: Python version 3.10.11 (tags/v3.10.11:7d4cc5a, Apr 5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)].
PyAEDT INFO: PyAEDT version 1.3.dev0.
PyAEDT INFO: Initializing Desktop session.
PyAEDT INFO: File C:\Users\ansys\AppData\Local\Temp\tmp5g0t0y_3.ansys\pyaedt\busbar_3_physics_q3d_icepak_icepakfea\Q3D_Busbar.aedt correctly loaded. Elapsed time: 0m 0sec
PyAEDT INFO: AEDT version 2026.1.
PyAEDT INFO: New AEDT session is starting on gRPC port 53573.
PyAEDT INFO: Starting new AEDT gRPC session on port 53573.
PyAEDT INFO: Launching AEDT server with gRPC transport mode: wnua
PyAEDT INFO: Electronics Desktop started on gRPC port 53573 after 11.5 seconds.
PyAEDT INFO: AEDT installation Path C:\Program Files\ANSYS Inc\v261\AnsysEM
PyAEDT INFO: 2026.1 version started with process ID 7692.
PyAEDT INFO: Non-graphical mode detected. Disabling Desktop logs.
PyAEDT INFO: Project Q3D_Busbar has been opened.
PyAEDT INFO: Active Design set to Q3DDesign
PyAEDT INFO: AEDT objects correctly read
Model Preparation - Materials#
Create temperature dependent materials. Duplicate copper and add temperature dependent electrical conductivity.
[5]:
target_material = "copper"
[6]:
cu_temp = q3d.materials.duplicate_material(material=target_material, name="copper_temp_dep")
cu_temp.conductivity.add_thermal_modifier_free_form("1.0/(1.0+{}*(Temp-20))".format(0.000001))
PyAEDT INFO: Materials class has been initialized! Elapsed time: 0m 0sec
[6]:
True
[7]:
matching_objects = [
obj_name
for obj_name in q3d.modeler.object_names
if q3d.modeler[obj_name].material_name
and q3d.modeler[obj_name].material_name.lower() == target_material.lower()
]
PyAEDT INFO: Modeler class has been initialized! Elapsed time: 0m 0sec
[8]:
q3d.assign_material(assignment=matching_objects, material=cu_temp.name)
[8]:
True
Set objects temperature and enable feedback#
[9]:
q3d.modeler.set_objects_temperature(assignment=matching_objects, ambient_temperature=20)
PyAEDT INFO: Set model temperature and enabling Thermal Feedback
PyAEDT INFO: Assigned Objects Temperature
[9]:
True
Define excitations#
[10]:
q3d[Q3D_DESIGN_VAR_NAME]= str(CURRENT[0])+"A"
Create Icepak target design#
[11]:
q3d.create_em_target_design(design="Icepak", design_setup="Natural")
ipk = ansys.aedt.core.Icepak()
ipk.design_name = ICEPAK_DESIGN_NAME
PyAEDT INFO: Python version 3.10.11 (tags/v3.10.11:7d4cc5a, Apr 5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)].
PyAEDT INFO: PyAEDT version 1.3.dev0.
PyAEDT INFO: Returning found Desktop session with PID 7692!
PyAEDT INFO: No project is defined. Project Q3D_Busbar exists and has been read.
PyAEDT INFO: Active Design set to IcepakDesign1
PyAEDT INFO: AEDT objects correctly read
Define solution setup#
Set the current value for the coupling-step time using linear interpolation
[12]:
coupling_time = END_TIME / STEPS
current_value = np.interp(coupling_time, TIME, CURRENT)
ipk[Q3D_DESIGN_VAR_NAME] = f"{current_value:.0f}A"
Enable desktop logging
[13]:
ipk.logger.enable_desktop_log()
ipk.logger.add_message(message_type=0, message_text="Solving substep n.1 of " + str(STEPS), level="Project", proj_name=ipk.project_name)
PyAEDT INFO: Solving substep n.1 of 2
Icepak transient simulation and time stepping setup
[14]:
ipk.solution_type = "Transient"
setup = ipk.setups[0]
setup.props["Stop Time"] = str(END_TIME/STEPS)+"s"
setup.props["Time Step"] = str(TH_TIME_STEP)+"s"
setup.props["N Steps"] = 1
ipk.save_project()
PyAEDT INFO: Project Q3D_Busbar Saved correctly
[14]:
True
Get the list of object names included in the EM Loss and delete the EM Loss boundary.
[15]:
em_loss = next(bound for bound in ipk.boundaries if bound.type == "EM Loss")
objs = [ipk.modeler.objects[obj_id].name for obj_id in em_loss.props["Objects"]]
em_loss.delete()
PyAEDT INFO: Modeler class has been initialized! Elapsed time: 0m 0sec
PyAEDT INFO: Parsing C:\Users\ansys\AppData\Local\Temp\tmp5g0t0y_3.ansys\pyaedt\busbar_3_physics_q3d_icepak_icepakfea\Q3D_Busbar.aedt.
PyAEDT INFO: File C:\Users\ansys\AppData\Local\Temp\tmp5g0t0y_3.ansys\pyaedt\busbar_3_physics_q3d_icepak_icepakfea\Q3D_Busbar.aedt correctly loaded. Elapsed time: 0m 0sec
PyAEDT INFO: aedt file load time 0.18264484405517578
[15]:
True
Re-creation of the EM Loss#
Re-creation of the EM Loss with mapping of the current pulse parameter between Icepak and Maxwell. With this setting, the value of the current pulse to be used in the Maxwell simulation is driven from Icepak.
[16]:
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")
ipk.assign_2way_coupling(setup=setup.name, number_of_iterations=2)
ipk.logger.add_message(0, "Current value " + str(ipk[Q3D_DESIGN_VAR_NAME]), level="Project", proj_name=ipk.project_name)
PyAEDT INFO: Boundary EMLoss EMLoss_YB4GL6 has been created.
PyAEDT INFO: Current value 100A
Run analysis#
Run simulation for the current coupling step
[17]:
ipk.analyze()
ipk.logger.add_message(0, "Substep n.1 of " + str(STEPS) + " completed", level="Project", proj_name=ipk.project_name)
PyAEDT INFO: Project Q3D_Busbar Saved correctly
PyAEDT INFO: Solving all design setups. Analysis started...
PyAEDT INFO: Design setup None solved correctly in 0.0h 7.0m 43.0s
PyAEDT INFO: Substep n.1 of 2 completed
Additional coupling steps loop definition
[18]:
for n in range(STEPS-1):
step = n + 2
ipk.logger.add_message(0, "Solving substep n." + str(step) + " of " + str(STEPS), level="Project", proj_name=ipk.project_name)
PyAEDT INFO: Solving substep n.2 of 2
Duplicate Icepak design from previous substep and enable the restart#
Map Q3D_DESIGN_VAR_NAME to the value used in the previous substep to avoid re-solving the source Icepak design. Copy Fields From Source transfers the results to the restarted design, so only the final Icepak design must be kept.
[19]:
ipk_name = ipk.design_name
ipk.duplicate_design(ipk_name)
ipk = ansys.aedt.core.Icepak()
ipk.cleanup_solution()
ipk.save_project()
setup = ipk.setups[0]
setup.props["Import Start Time"] = True
setup.props["Copy Fields From Source"] = True
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"})
setup.props["Stop Time"] = str(step*END_TIME/STEPS)+"s"
PyAEDT INFO: Active Design set to Icepak1
PyAEDT INFO: Python version 3.10.11 (tags/v3.10.11:7d4cc5a, Apr 5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)].
PyAEDT INFO: PyAEDT version 1.3.dev0.
PyAEDT INFO: Returning found Desktop session with PID 7692!
PyAEDT INFO: No project is defined. Project Q3D_Busbar exists and has been read.
PyAEDT INFO: Active Design set to Icepak_1
PyAEDT INFO: AEDT objects correctly read
PyAEDT INFO: Project Q3D_Busbar Saved correctly
PyAEDT INFO: Parsing C:\Users\ansys\AppData\Local\Temp\tmp5g0t0y_3.ansys\pyaedt\busbar_3_physics_q3d_icepak_icepakfea\Q3D_Busbar.aedt.
PyAEDT INFO: File C:\Users\ansys\AppData\Local\Temp\tmp5g0t0y_3.ansys\pyaedt\busbar_3_physics_q3d_icepak_icepakfea\Q3D_Busbar.aedt correctly loaded. Elapsed time: 0m 0sec
PyAEDT INFO: aedt file load time 0.22790932655334473
Setup of the current pulse value for the current coupling step using linear interpolation
[20]:
ipk[Q3D_DESIGN_VAR_NAME] = str(int(np.interp(step*END_TIME/STEPS, TIME, CURRENT))) + "A"
ipk.logger.add_message(0, "Current value " + str(ipk[Q3D_DESIGN_VAR_NAME]), level="Project", proj_name=ipk.project_name)
PyAEDT INFO: Current value 100A
Simulation run for the current coupling step
[21]:
ipk.analyze()
ipk.logger.add_message(0, "Substep n." + str(step) + " of " + str(STEPS) + " completed", level="Project", proj_name=ipk.project_name)
PyAEDT INFO: Project Q3D_Busbar Saved correctly
PyAEDT INFO: Solving all design setups. Analysis started...
PyAEDT INFO: Design setup None solved correctly in 0.0h 6.0m 44.0s
PyAEDT INFO: Substep n.2 of 2 completed
Remove all Icepak designs#
Remove all Icepak designs except the last one because all the field data are copied into the last Icepak design.
[22]:
[ipk.delete_design(d) for d in ipk.design_list if d == ICEPAK_DESIGN_NAME]
[22]:
[True]
Icepak Thermal Transient Postprocess#
Access the FieldSummary functionality
[23]:
field_sum = ipk.post.create_field_summary()
PyAEDT INFO: Parsing C:\Users\ansys\AppData\Local\Temp\tmp5g0t0y_3.ansys\pyaedt\busbar_3_physics_q3d_icepak_icepakfea\Q3D_Busbar.aedt.
PyAEDT INFO: File C:\Users\ansys\AppData\Local\Temp\tmp5g0t0y_3.ansys\pyaedt\busbar_3_physics_q3d_icepak_icepakfea\Q3D_Busbar.aedt correctly loaded. Elapsed time: 0m 0sec
PyAEDT INFO: aedt file load time 0.22215533256530762
PyAEDT INFO: PostProcessor class has been initialized! Elapsed time: 0m 0sec
PyAEDT INFO: Post class has been initialized! Elapsed time: 0m 0sec
Compute time steps
[24]:
substep_time = END_TIME / STEPS
time_substeps_ns = range(
0,
int((END_TIME + substep_time) * 1e9),
int(substep_time * 1e9),
)
Convert time steps values to strings to be passed to field summary calculation
[25]:
time_substeps_str = [f"{time_ns}ns" for time_ns in time_substeps_ns]
for obj in objs:
temp_avg_dict = {
"name": "temp_avg_"+ obj,
"description": f"Average Temperature on {obj}",
"design_type": ["Icepak"],
"fields_type": ["Fields"],
"solution_type": "Transient",
"primary_sweep": "",
"assignment": "",
"assignment_type": ["Solid"],
"operations": [
"Fundamental_Quantity('Temp')",
"EnterVolume('assignment')",
"Operation('VolumeValue')",
"Operation('Mean')",
],
"report": ["Field3D"]
}
expr_name = ipk.post.fields_calculator.add_expression(calculation=temp_avg_dict,
assignment=obj,
name="T_avg_"+obj)
report_temp = ipk.post.create_report(expressions=expr_name, primary_sweep_variable='Time')
[field_sum.add_calculation(
entity="Object",
geometry="Volume",
geometry_name=obj,
quantity="Temperature",
time=t) for t in time_substeps_str]
PyAEDT INFO: Modeler class has been initialized! Elapsed time: 0m 0sec
PyAEDT INFO: Parsing design objects. This operation can take time
PyAEDT INFO: Refreshing bodies from Object Info
PyAEDT INFO: Bodies Info Refreshed Elapsed time: 0m 0sec
PyAEDT INFO: 3D Modeler objects parsed. Elapsed time: 0m 0sec
PyAEDT WARNING: No report category provided. Automatically identified Fields
PyAEDT WARNING: No context provided for Fields. Assigned object: None
PyAEDT WARNING: No report category provided. Automatically identified Fields
PyAEDT WARNING: No context provided for Fields. Assigned object: None
PyAEDT WARNING: No report category provided. Automatically identified Fields
PyAEDT WARNING: No context provided for Fields. Assigned object: None
PyAEDT WARNING: No report category provided. Automatically identified Fields
PyAEDT WARNING: No context provided for Fields. Assigned object: None
PyAEDT WARNING: No report category provided. Automatically identified Fields
PyAEDT WARNING: No context provided for Fields. Assigned object: None
PyAEDT WARNING: No report category provided. Automatically identified Fields
PyAEDT WARNING: No context provided for Fields. Assigned object: None
[26]:
temperature_data = field_sum.get_field_summary_data(
pandas_output=True,
intrinsics="All times",
variation=ipk.available_variations.variations(ipk.nominal_adaptive, True)[0]
)
The temperature results are grouped by object, with STEPS + 1 values per object. Select the temperatures of the object chosen as most critical, for which the time step corresponding to the maximum temperature will be selected. Averaged temperatures of all objects at this time step will be used as boundary conditions for the subsequent structural analysis.
[27]:
object_pos = len(q3d.modeler.model_objects)
reference_temperatures = temperature_data["Mean"].to_numpy()[(object_pos-1)*(STEPS + 1):object_pos*(STEPS + 1)]
Find the time step with the maximum of the average temperatures for the chosen object
[28]:
worst_step_index = np.argmax(reference_temperatures)
Extract object temperatures at the selected worst-case time step
[29]:
worst_case_data = temperature_data.iloc[worst_step_index::(STEPS+1)]
temperature_by_object = dict(zip(worst_case_data["Entity"], worst_case_data["Mean"]))
Create EM Target design#
Mechanical Structural Static simulation
[30]:
q3d.create_em_target_design(design=IcepakFeaConstants.NAME)
design_list = q3d.design_list
Connect to the newly created IcepakFEA design.
[31]:
mech = ansys.aedt.core.Mechanical(version=AEDT_VERSION)
PyAEDT INFO: Python version 3.10.11 (tags/v3.10.11:7d4cc5a, Apr 5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)].
PyAEDT INFO: PyAEDT version 1.3.dev0.
PyAEDT INFO: Returning found Desktop session with PID 7692!
PyAEDT INFO: No project is defined. Project Q3D_Busbar exists and has been read.
PyAEDT INFO: Active Design set to IcepakFEADesign1
PyAEDT INFO: AEDT objects correctly read
Change the solution type to Structural
[32]:
mech.solution_type="Structural"
assign uniform temperature excitations
[33]:
for obj, temp in temperature_by_object.items():
mech.assign_thermal_condition_uniform(assignment=[obj], temperature =str(temp)+"cel", name=f"ThermalCond_{obj}")
PyAEDT INFO: Modeler class has been initialized! Elapsed time: 0m 0sec
PyAEDT INFO: Boundary ThermalCondition ThermalCond_FUSE_DIELECTRIC2 has been created.
PyAEDT INFO: Boundary ThermalCondition ThermalCond_BB2 has been created.
PyAEDT INFO: Boundary ThermalCondition ThermalCond_STUD2_1 has been created.
PyAEDT INFO: Boundary ThermalCondition ThermalCond_STUD2_2 has been created.
PyAEDT INFO: Boundary ThermalCondition ThermalCond_BB2_DS has been created.
PyAEDT INFO: Boundary ThermalCondition ThermalCond_Fuse_METAL2_1 has been created.
Retrieve from Q3D the Named selections face IDs#
Retrieve Q3D named selection face IDs. IcepakFEA EM target designs do not automatically inherit Q3D named selections. The IDs are remapped in IcepakFEA before applying structural constraints.
[34]:
q3d_face_ids = [face_id for ns in q3d.modeler.user_lists for face_id in ns.props["List"]]
[35]:
object_to_Save = {}
mech_face_ids = []
[36]:
for obj in q3d.modeler.object_list:
matching_faces = [face.id for face in obj.faces if face.id in q3d_face_ids]
if matching_faces:
for face_id in matching_faces:
fpos = q3d.modeler.get_face_center(assignment=face_id)
mech_face_ids.append(mech.modeler.get_faceid_from_position(position=fpos, assignment=obj.name))
Assign Fixed support boundary condition
[37]:
mech.assign_fixed_support(assignment=mech_face_ids, name='Fixed')
PyAEDT INFO: Parsing design objects. This operation can take time
PyAEDT INFO: Refreshing bodies from Object Info
PyAEDT INFO: Bodies Info Refreshed Elapsed time: 0m 0sec
PyAEDT INFO: 3D Modeler objects parsed. Elapsed time: 0m 0sec
PyAEDT INFO: Boundary FixedSupport Fixed has been created.
[37]:
Fixed
Create the solution setup#
Create a new setup, validate and analyze
[38]:
mech_setup = mech.create_setup()
mech.validate_simple()
mech.analyze()
PyAEDT INFO: Project Q3D_Busbar Saved correctly
PyAEDT INFO: Solving all design setups. Analysis started...
PyAEDT INFO: Design setup None solved correctly in 0.0h 1.0m 38.0s
[38]:
True
Post-processing#
Create postprocessing surface plots of equivalent stress and displacement magnitude for all model objects.
[39]:
plot_stress = mech.post.create_fieldplot_surface(
assignment=mech.modeler.object_list, quantity="Equivalent Stress", plot_name="Equivalent Stress")
PyAEDT INFO: Parsing C:\Users\ansys\AppData\Local\Temp\tmp5g0t0y_3.ansys\pyaedt\busbar_3_physics_q3d_icepak_icepakfea\Q3D_Busbar.aedt.
PyAEDT INFO: File C:\Users\ansys\AppData\Local\Temp\tmp5g0t0y_3.ansys\pyaedt\busbar_3_physics_q3d_icepak_icepakfea\Q3D_Busbar.aedt correctly loaded. Elapsed time: 0m 0sec
PyAEDT INFO: aedt file load time 0.303189754486084
PyAEDT INFO: PostProcessor class has been initialized! Elapsed time: 0m 1sec
PyAEDT INFO: Post class has been initialized! Elapsed time: 0m 1sec
PyAEDT INFO: Active Design set to IcepakFEADesign1
[40]:
plot_displ = mech.post.create_fieldplot_surface(
assignment=mech.modeler.object_list, quantity="Mag_Displacement", plot_name="Mag_Displacement")
PyAEDT INFO: Active Design set to IcepakFEADesign1
Release AEDT#
[41]:
ipk.save_project()
ipk.release_desktop()
PyAEDT INFO: Project Q3D_Busbar Saved correctly
PyAEDT INFO: Desktop has been released and closed.
[41]:
True
[42]:
# Wait 3 seconds to allow AEDT to shut down before cleaning the temporary directory.
time.sleep(3)
Clean up#
All project files are saved in the folder temp_folder.name. If you’ve run this example as a Jupyter notebook, you can retrieve those project files. The following cell removes all temporary files, including the project folder.
[43]:
temp_folder.cleanup()
Download this example
Download this example as a Jupyter Notebook or as a Python script.