{ "cells": [ { "cell_type": "markdown", "id": "2c4561cd", "metadata": {}, "source": [ "# Maxwell 3D-Icepak electrothermal analysis\n", "\n", "This example uses PyAEDT to set up a simple Maxwell design consisting of a coil and a ferrite core.\n", "Coil current is set to 100A, and coil resistance and ohmic loss are analyzed.\n", "Ohmic loss is mapped to Icepak, and a thermal analysis is performed.\n", "Icepak calculates a temperature distribution, and it is mapped back to Maxwell (2-way coupling).\n", "Coil resistance and ohmic loss are analyzed again in Maxwell. Results are printed in AEDT Message Manager.\n", "\n", "Keywords: **Multiphysics**, **Maxwell**, **Icepak**, **Wireless Charging**.\n", "\n", "## Perform imports and define constants\n", "\n", "Perform required imports." ] }, { "cell_type": "code", "execution_count": null, "id": "bd8c093f", "metadata": {}, "outputs": [], "source": [ "import os\n", "import tempfile\n", "import time\n", "\n", "import ansys.aedt.core\n", "from ansys.aedt.core.generic.constants import AXIS\n" ] }, { "cell_type": "markdown", "id": "68c335c0", "metadata": {}, "source": [ "Define constants." ] }, { "cell_type": "code", "execution_count": null, "id": "a65a0b83", "metadata": {}, "outputs": [], "source": [ "AEDT_VERSION = \"2024.2\"\n", "NG_MODE = False # Open AEDT UI when it is launched." ] }, { "cell_type": "markdown", "id": "05a5a500", "metadata": {}, "source": [ "## Create temporary directory\n", "\n", "Create a temporary working directory.\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": "e3f8155b", "metadata": {}, "outputs": [], "source": [ "temp_folder = tempfile.TemporaryDirectory(suffix=\".ansys\")" ] }, { "cell_type": "markdown", "id": "e7a7df95", "metadata": {}, "source": [ "## Launch application\n", "\n", "The syntax for different applications in AEDT differ\n", "only in the name of the class. This example demonstrates the use of the\n", "``Maxwell3d`` class.\n", "\n", "> **Note:** An AEDT _Project_ is created when the ``Maxwell3d`` class is instantiated. An instance of\n", "> the ``Icepak`` class will be used to insert and simulate an Icepak design and demonstrate\n", "> the coupled electrical-thermal workflow." ] }, { "cell_type": "code", "execution_count": null, "id": "41be8a7a", "metadata": {}, "outputs": [], "source": [ "project_name = os.path.join(temp_folder.name, \"Maxwell-Icepak-2way-Coupling\")\n", "maxwell_design_name = \"1 Maxwell\"\n", "icepak_design_name = \"2 Icepak\"\n", "\n", "m3d = ansys.aedt.core.Maxwell3d(\n", " project=project_name,\n", " design=maxwell_design_name,\n", " solution_type=\"EddyCurrent\",\n", " version=AEDT_VERSION,\n", " non_graphical=NG_MODE,\n", ")" ] }, { "cell_type": "markdown", "id": "c05c035c", "metadata": {}, "source": [ "### Units\n", "The default units are \"mm\". Model units can be queried or changed using the\n", "property ``m3d.modeler.model_units``." ] }, { "cell_type": "code", "execution_count": null, "id": "7275d12f", "metadata": {}, "outputs": [], "source": [ "print(f'Model units are \"{m3d.modeler.model_units}\"')" ] }, { "cell_type": "markdown", "id": "5f00f6fd", "metadata": {}, "source": [ "## Set up model\n", "\n", "Create the coil, coil terminal, core, and surrounding air region. The coil and core\n", "are created by drawing a rectangle and sweeping it about the z-axis." ] }, { "cell_type": "code", "execution_count": null, "id": "82014ff1", "metadata": {}, "outputs": [], "source": [ "coil_origin = [70, 0, -11] # [x, y, z] position of the rectangle origin.\n", "coil_xsection = [11, 110] # [z-size, x-size]\n", "core_origin = [45, 0, -18]\n", "core_xsection = [7, 160]\n", "\n", "coil = m3d.modeler.create_rectangle(\n", " orientation=\"XZ\", origin=coil_origin, sizes=coil_xsection, name=\"Coil\"\n", ")\n", "coil.sweep_around_axis(axis=AXIS.Z)\n", "coil_terminal = m3d.modeler.create_rectangle(\n", " orientation=\"XZ\", origin=coil_origin, sizes=coil_xsection, name=\"Coil_terminal\"\n", ")\n", "\n", "core = m3d.modeler.create_rectangle(\n", " orientation=\"XZ\", origin=core_origin, sizes=core_xsection, name=\"Core\"\n", ")\n", "core.sweep_around_axis(axis=AXIS.Z)\n", "\n", "# The air region should be sufficiently large to avoid interaction with the\n", "# coil magnetic field.\n", "\n", "region = m3d.modeler.create_region(pad_percent=[20, 20, 20, 20, 500, 100])" ] }, { "cell_type": "markdown", "id": "ff8c9867", "metadata": {}, "source": [ "### Restore view\n", "\n", "If you are using PyAEDT with an interactive desktop, you may want to fit the visible view to fit the model.\n", "PyAEDT uses the direct access to the native API for this command using the property `m3d.odesktop`.\n", "\n", "Uncomment and run the following cell if you are running PyAEDT interactively and would like to automatically fit the\n", "window to the model." ] }, { "cell_type": "code", "execution_count": null, "id": "8edfd1f3", "metadata": {}, "outputs": [], "source": [ "# desktop=m3d.odesktop.RestoreWindow() # Fit the active view" ] }, { "cell_type": "markdown", "id": "3fe037e6", "metadata": {}, "source": [ "### Create and assign material\n", "\n", "Define a new material for the AWG40 Litz wire copper strands:\n", "\n", "- Strand diameter = 0.08 mm\n", "- Number of parallel strands in the Litz wire = 24\n", "\n", "The built-in material \"ferrite\" will be assigned to the core.\n", "The material \"vacuum\" will be assigned to the outer region.\n", "\n", "You will also see the return value when\n", " ``True`` printed when material is successfully assigned." ] }, { "cell_type": "code", "execution_count": null, "id": "e2a1479f", "metadata": {}, "outputs": [], "source": [ "no_strands = 24\n", "strand_diameter = 0.08\n", "\n", "cu_litz = m3d.materials.duplicate_material(\"copper\", \"copper_litz\")\n", "cu_litz.stacking_type = \"Litz Wire\"\n", "cu_litz.wire_diameter = str(strand_diameter) + \"mm\"\n", "cu_litz.wire_type = \"Round\"\n", "cu_litz.strand_number = no_strands\n", "\n", "m3d.assign_material(region.name, \"vacuum\")\n", "m3d.assign_material(coil.name, \"copper_litz\")\n", "m3d.assign_material(core.name, \"ferrite\")" ] }, { "cell_type": "markdown", "id": "4ead00c5", "metadata": {}, "source": [ "The coil carries 0.5 A and 20 turns." ] }, { "cell_type": "code", "execution_count": null, "id": "c4b2c282", "metadata": {}, "outputs": [], "source": [ "turns = 20\n", "wire_current = 0.5\n", "m3d.assign_coil([\"Coil_terminal\"], conductors_number=turns, name=\"Coil_terminal\")\n", "m3d.assign_winding(is_solid=False, current=wire_current * turns, name=\"Winding1\")\n", "\n", "m3d.add_winding_coils(assignment=\"Winding1\", coils=[\"Coil_terminal\"])" ] }, { "cell_type": "markdown", "id": "ad162f3b", "metadata": {}, "source": [ "### Assign mesh operations\n", "\n", "Mesh \"seeding\" is used to retain solution accuracy and accelerate the auto-adaptive mesh refinement." ] }, { "cell_type": "code", "execution_count": null, "id": "e6ef2a71", "metadata": {}, "outputs": [], "source": [ "m3d.mesh.assign_length_mesh(\n", " [\"Core\"], maximum_length=15, maximum_elements=None, name=\"Inside_Core\"\n", ")\n", "m3d.mesh.assign_length_mesh(\n", " [\"Coil\"], maximum_length=30, maximum_elements=None, name=\"Inside_Coil\"\n", ")" ] }, { "cell_type": "markdown", "id": "d937c115", "metadata": {}, "source": [ "### Set object temperature and enable feedback\n", "\n", "The impact of Joule heating on conductivity can be considered\n", "by adding a \"thermal modifier\" to the ``cu_litz`` material definition.\n", "In this example, conductivity increases by 0.393% per $\\Delta$K. The temperature of the objects is set to the default value ($22^0$ C)." ] }, { "cell_type": "code", "execution_count": null, "id": "4e4f6eff", "metadata": {}, "outputs": [], "source": [ "cu_resistivity_temp_coefficient = 0.00393\n", "cu_litz.conductivity.add_thermal_modifier_free_form(\n", " \"1.0/(1.0+{}*(Temp-20))\".format(cu_resistivity_temp_coefficient)\n", ")\n", "m3d.modeler.set_objects_temperature([\"Coil\"], ambient_temperature=22)" ] }, { "cell_type": "markdown", "id": "2551dabe", "metadata": {}, "source": [ "### Assign matrix\n", "\n", "The resistance and inductance calculations for the coil are enabled by\n", "adding the matrix assignment." ] }, { "cell_type": "code", "execution_count": null, "id": "966075e8", "metadata": {}, "outputs": [], "source": [ "m3d.assign_matrix([\"Winding1\"], matrix_name=\"Matrix1\")" ] }, { "cell_type": "markdown", "id": "b621fe9a", "metadata": {}, "source": [ "### Create the simulation setup\n", "\n", "The simulation frequency is 150 kHz. You can query and modify the properties of the simulation setup using ``setup.props``. The \"PercentError\" establishes the minimum allowed change in energy due to the change in mesh size and ensure a small global solution error." ] }, { "cell_type": "code", "execution_count": null, "id": "c3229ab3", "metadata": {}, "outputs": [], "source": [ "setup = m3d.create_setup(name=\"Setup1\")\n", "setup.props[\"Frequency\"] = \"150kHz\"\n", "setup.props[\"MaximumPasses\"] = 4\n", "setup.props[\"PercentError\"] = 0.5\n", "setup.props[\"MinimumConvergedPasses\"] = 2" ] }, { "cell_type": "markdown", "id": "c08d01aa", "metadata": {}, "source": [ "## Run the Maxwell 3D analysis" ] }, { "cell_type": "code", "execution_count": null, "id": "8a798211", "metadata": {}, "outputs": [], "source": [ "m3d.analyze_setup(\"Setup1\")" ] }, { "cell_type": "markdown", "id": "0a501667", "metadata": {}, "source": [ "## Postprocessing\n", "\n", "The DC resistance of the coil can be calculated analyticially. The following cell compares the known\n", "DC resistance with the simulated coil\n", "resistance.\n", "\n", "The values can be displayed in the AEDT \"Message Manager\". The Ohmic loss in\n", "coil is calculated and displayed so we can see the change when Joule\n", "heating is considered." ] }, { "cell_type": "code", "execution_count": null, "id": "5aae5a09", "metadata": {}, "outputs": [], "source": [ "report = m3d.post.create_report(expressions=\"Matrix1.R(Winding1,Winding1)\")\n", "solution = report.get_solution_data()\n", "resistance = solution.data_magnitude()[0] # Resistance is the first matrix element.\n", "\n", "report_loss = m3d.post.create_report(expressions=\"StrandedLossAC\")\n", "solution_loss = report_loss.get_solution_data()\n", "em_loss = solution_loss.data_magnitude()[0]" ] }, { "cell_type": "code", "execution_count": null, "id": "f559c956", "metadata": {}, "outputs": [], "source": [ "# Analytical calculation of the DC resistance of the coil\n", "cu_cond = float(cu_litz.conductivity.value)\n", "\n", "# Average radius of a coil turn = 125 mm\n", "avg_coil_radius = (\n", " coil_xsection[1] / 2 + coil_origin[0] / 2\n", ") * 0.001 # Convert to meters\n", "l_conductor = turns * 2 * avg_coil_radius * 3.1415\n", "\n", "# R = resistivity * length / area / no_strand\n", "r_analytic_DC = (\n", " (1.0 / cu_cond)\n", " * l_conductor\n", " / (3.1415 * (strand_diameter * 0.001 / 2) ** 2)\n", " / no_strands\n", ")\n", "\n", "# Print results in AEDT Message Manager\n", "m3d.logger.info(f\"*******Coil analytical DC resistance = {r_analytic_DC:.2f}Ohm\")\n", "m3d.logger.info(\n", " f\"*******Coil resistance at 150kHz BEFORE temperature feedback = {resistance:.2f}Ohm\"\n", ")\n", "m3d.logger.info(\n", " f\"*******Ohmic loss in coil BEFORE temperature feedback = {em_loss / 1000:.2f}W\"\n", ")" ] }, { "cell_type": "markdown", "id": "7119c606", "metadata": {}, "source": [ "## Insert Icepak design\n", "\n", "The following commands insert an Icepak design into the AEDT project, copies the solid objects from Maxwell 3D, and modifies the region dimensions so they're suitable\n", "for thermal convection analysis." ] }, { "cell_type": "code", "execution_count": null, "id": "291d3f68", "metadata": {}, "outputs": [], "source": [ "ipk = ansys.aedt.core.Icepak(design=icepak_design_name, version=AEDT_VERSION)\n", "ipk.copy_solid_bodies_from(m3d, no_pec=False)\n", "\n", "# Set domain dimensions suitable for natural convection using the diameter of the coil\n", "ipk.modeler[\"Region\"].delete()\n", "coil_dim = coil.bounding_dimension[0]\n", "ipk.modeler.create_region(0, False)\n", "ipk.modeler.edit_region_dimensions(\n", " [coil_dim / 2, coil_dim / 2, coil_dim / 2, coil_dim / 2, coil_dim * 2, coil_dim]\n", ")" ] }, { "cell_type": "markdown", "id": "8e7bdf28", "metadata": {}, "source": [ "## Map coil losses\n", "\n", "Map ohmic losses from Maxwell 3D to the Icepak design." ] }, { "cell_type": "code", "execution_count": null, "id": "eac65703", "metadata": {}, "outputs": [], "source": [ "ipk.assign_em_losses(\n", " design=\"1 Maxwell\",\n", " setup=m3d.setups[0].name,\n", " sweep=\"LastAdaptive\",\n", " assignment=[\"Coil\"],\n", ")" ] }, { "cell_type": "markdown", "id": "373f5def", "metadata": {}, "source": [ "### Define boundary conditions\n", "\n", "Assign the opening in the Icepak model to allow free airflow." ] }, { "cell_type": "code", "execution_count": null, "id": "a52155c0", "metadata": {}, "outputs": [], "source": [ "faces = ipk.modeler[\"Region\"].faces\n", "face_names = [face.id for face in faces]\n", "ipk.assign_free_opening(face_names, boundary_name=\"Opening1\")" ] }, { "cell_type": "markdown", "id": "3aa05ef9", "metadata": {}, "source": [ "### Assign monitor\n", "\n", "Assign a temperature monitor on the coil surface." ] }, { "cell_type": "code", "execution_count": null, "id": "7bd9f8e2", "metadata": {}, "outputs": [], "source": [ "temp_monitor = ipk.assign_point_monitor([70, 0, 0], monitor_name=\"PointMonitor1\")" ] }, { "cell_type": "markdown", "id": "13ae793e", "metadata": {}, "source": [ "Set up Icepak solution" ] }, { "cell_type": "code", "execution_count": null, "id": "7a5f6751", "metadata": {}, "outputs": [], "source": [ "solution_setup = ipk.create_setup()\n", "solution_setup.props[\"Convergence Criteria - Max Iterations\"] = 50\n", "solution_setup.props[\"Flow Regime\"] = \"Turbulent\"\n", "solution_setup.props[\"Turbulent Model Eqn\"] = \"ZeroEquation\"\n", "solution_setup.props[\"Radiation Model\"] = \"Discrete Ordinates Model\"\n", "solution_setup.props[\"Include Flow\"] = True\n", "solution_setup.props[\"Include Gravity\"] = True\n", "solution_setup.props[\"Solution Initialization - Z Velocity\"] = \"0.0005m_per_sec\"\n", "solution_setup.props[\"Convergence Criteria - Flow\"] = 0.0005\n", "solution_setup.props[\"Flow Iteration Per Radiation Iteration\"] = \"5\"" ] }, { "cell_type": "markdown", "id": "694eb200", "metadata": {}, "source": [ "## Add two-way coupling and solve the project\n", "\n", "The temperature update from Icepak to Maxwell 3D is activated using the method ``assign_2way_coupling()``. The Ohmic\n", "loss in Maxwell will change due to the temperature increase, which in turn will change the results\n", "from the Icepak simulation. By default, this iteration occurs twice. However, the named argument\n", "``number_of_iterations`` can be passed to the ``assign_2way_coupling`` method to increase the number of iterations.\n", "\n", "The full electro-thermal analysis is run by calling the ``analyze_setup()`` method." ] }, { "cell_type": "code", "execution_count": null, "id": "7743ecc5", "metadata": {}, "outputs": [], "source": [ "ipk.assign_2way_coupling()\n", "ipk.analyze_setup(name=solution_setup.name)" ] }, { "cell_type": "markdown", "id": "775aaf0d", "metadata": {}, "source": [ "## Postprocess\n", "\n", "Plot the temperature on object surfaces." ] }, { "cell_type": "code", "execution_count": null, "id": "0583016d", "metadata": {}, "outputs": [], "source": [ "surface_list = []\n", "for name in [\"Coil\", \"Core\"]:\n", " surface_list.extend(ipk.modeler.get_object_faces(name))\n", "\n", "surf_temperature = ipk.post.create_fieldplot_surface(\n", " surface_list, quantity=\"SurfTemperature\", plot_name=\"Surface Temperature\"\n", ")\n", "\n", "velocity_cutplane = ipk.post.create_fieldplot_cutplane(\n", " assignment=[\"Global:XZ\"], quantity=\"Velocity Vectors\", plot_name=\"Velocity Vectors\"\n", ")\n", "\n", "surf_temperature.export_image()\n", "velocity_cutplane.export_image(orientation=\"right\")\n", "\n", "report_temp = ipk.post.create_report(\n", " expressions=\"PointMonitor1.Temperature\", primary_sweep_variable=\"X\"\n", ")\n", "solution_temp = report_temp.get_solution_data()\n", "temp = solution_temp.data_magnitude()[0]\n", "m3d.logger.info(\"*******Coil temperature = {:.2f}deg C\".format(temp))" ] }, { "cell_type": "markdown", "id": "9c81917a", "metadata": {}, "source": [ "### Get new resistance from Maxwell 3D\n", "\n", "The temperature of the coil increases, and consequently the coil resistance increases." ] }, { "cell_type": "code", "execution_count": null, "id": "f84e401c", "metadata": {}, "outputs": [], "source": [ "report_new = m3d.post.create_report(expressions=\"Matrix1.R(Winding1,Winding1)\")\n", "solution_new = report_new.get_solution_data()\n", "resistance_new = solution_new.data_magnitude()[0]\n", "resistance_increase = (resistance_new - resistance) / resistance * 100\n", "\n", "report_loss_new = m3d.post.create_report(expressions=\"StrandedLossAC\")\n", "solution_loss_new = report_loss_new.get_solution_data()\n", "em_loss_new = solution_loss_new.data_magnitude()[0]\n", "\n", "m3d.logger.info(\n", " \"*******Coil resistance at 150kHz AFTER temperature feedback = {:.2f}Ohm\".format(\n", " resistance_new\n", " )\n", ")\n", "m3d.logger.info(\n", " \"*******Coil resistance increased by {:.2f}%\".format(resistance_increase)\n", ")\n", "m3d.logger.info(\n", " \"*******Ohmic loss in coil AFTER temperature feedback = {:.2f}W\".format(\n", " em_loss_new / 1000\n", " )\n", ")" ] }, { "cell_type": "markdown", "id": "3cf5a611", "metadata": {}, "source": [ "### Save project" ] }, { "cell_type": "code", "execution_count": null, "id": "882dbdd2", "metadata": {}, "outputs": [], "source": [ "ipk.save_project()\n", "ipk.release_desktop()\n", "time.sleep(3) # Allow AEDT to shut down before cleaning the temporary project folder." ] }, { "cell_type": "markdown", "id": "34226511", "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 removes\n", "all temporary files, including the project folder." ] }, { "cell_type": "code", "execution_count": null, "id": "d138773c", "metadata": {}, "outputs": [], "source": [ "temp_folder.cleanup()" ] } ], "metadata": { "jupytext": { "cell_metadata_filter": "-all", "main_language": "python", "notebook_metadata_filter": "-all" } }, "nbformat": 4, "nbformat_minor": 5 }