Motor-CAD Thermal Twin Builder ROM#

This example shows how to create the files needed to generate a Motor-CAD Thermal model using the Twin Builder Motor-CAD ROM component.

Background#

Important

The Twin Builder Motor-CAD ROM component is available in Twin Builder 2024 R2 or later.

Motor-CAD creates a thermal model of the motor using a Lumped Parameter Thermal Network (LPTN), which allows the machine temperatures to be calculated. This LPTN thermal model can be formulated as a set of thermal matrices. Motor-CAD provides the capability to export these, allowing the thermal model to be imported into other engineering software packages. In general, the exported model is accurate at a single speed, housing temperature, and airgap temperature (“operating point”).

In Twin Builder 2024 R2 and later, a Motor-CAD ROM component can be created which extends upon this idea, allowing for the creation of a Motor-CAD thermal model that is valid at a range of operating points. It does this by utilizing data exported from several discrete operating points to generate the component, and then automatically interpolating between these during a solve. The component also solves the coolant flow model, ensuring accuracy for Motor-CAD models with cooling systems enabled.

../../_images/Thermal_Twinbuilder_TwinBuilderROM.png

The Motor-CAD ROM component is quick to set up and provides a significantly more accurate model compared to the single operating point export. It has a user friendly interface with losses and RPM as input pins, and component temperatures as output pins. Once generated, the component is standalone (does not require Motor-CAD), thus allowing it to be shared/used in alternate systems whilst obscuring the underlying Motor-CAD geometry.

Data required to generate a Motor-CAD ROM component#

To generate the component, within Ansys Electronics Desktop, go to the menu bar and select Twin Builder > Add Component > Add Motor-CAD ROM Component…. This will present the following window:

../../_images/Thermal_Twinbuilder_GenerateROM_Blank.png

The Input Files must point to the folder which contains the Motor-CAD data at the appropriate operating points of interest, formatted in the appropriate manner. The script below is an example showing how this can be done.

Important

This script demonstrates how to obtain the data needed to generate a Motor-CAD ROM component, as well as how to generate the component in Twin Builder. For details on how the resulting Motor-CAD ROM component can be used, please consult the Twin Builder Help Manual.

Attention

This script is designed to be run using Motor-CAD template “e8”. For other models, modification of this script may be required.

Perform required imports#

import os
from pathlib import Path
import warnings

import matplotlib.pyplot as plt
import networkx as nx
import numpy as np

import ansys.motorcad.core as pymotorcad

Define the required Class#

A MotorCADTwinModel class has been created to encapsulate the required logic. The resulting object contains the data and functions to export the required Motor-CAD in the appropriate format. A summary of the operations performed is as follows:

  1. The Motor-CAD model calculation settings are configured

  2. The thermal node numbers, node names and other required node data is determined

  3. The cooling system nodes and flow path are identified and saved to the CoolingSystems.csv file

  4. For each desired speed, the thermal model is solved and thermal matrices exported and saved to the dpxxxxxx folders

  5. The distribution of the losses onto the individual nodes is determined and saved to the LossDistribution.csv file

  6. Natural convection cooling of the Housing is characterized and saved to the HousingTempDependency folder

  7. Temperature dependent Airgap heat transfer is characterized and saved to the AirGapTempDependency folder

class MotorCADTwinModel:
    # Store required constants for the Motor-CAD Cooling System Node Group names (provided in the
    # ``.nmf`` file), corresponding parameter names for varying flowrate and inlet temperature
    # the Motor-CAD loss names (for display in Twinbuilder), and the corresponding Motor-CAD
    # parameter names.
    coolingSystemData = {
        "End Space": [],
        "Ventilated": ["TVent_Flow_Rate", "TVent_Inlet_Temperature"],
        "Housing Water Jacket": ["WJ_Fluid_Volume_Flow_Rate", "HousingWJ_Inlet_Temperature"],
        "Shaft Spiral Groove": [
            "Shaft_Groove_Fluid_Volume_Flow_Rate",
            "Shaft_Groove_Fluid_Inlet_Temperature",
        ],
        "Wet Rotor": ["Wet_Rotor_Fluid_Volume_Flow_Rate", "Wet_Rotor_Inlet_Temp"],
        "Spray Cooling": ["Spray_Cooling_Fluid_Volume_Flow_Rate", "Spray_Cooling_Inlet_Temp"],
        "Rotor Water Jacket": ["Rotor_WJ_Fluid_Volume_Flow_Rate", "RotorWJ_Inlet_Temp"],
        "Slot Water Jacket": ["Slot_WJ_Fluid_Volume_Flow_Rate", "Slot_WJ_Fluid_inlet_temperature"],
        "Heat Exchanger": [],
    }

    lossNames = [
        "Armature_Copper_dc",
        "Armature_Copper_Freq_Comp",
        "Main_Winding",
        "Aux_Winding",
        "Stator_Back_Iron",
        "Stator_Tooth",
        "Windage",
        "Windage_Ext_Fan",
        "Friction_F_Bearing",
        "Friction_R_Bearing",
        "Magnet",
        "Rotor_Banding",
        "Stator_Sleeve",
        "Embedded_Magnet_Pole",
        "Encoder",
        "Rotor_Back_Iron",
        "Rotor_Tooth",
        "Rotor_Copper",
        "Stray_Load_Stator_Iron",
        "Stray_Load_Rotor_Iron",
        "Stray_Load_Stator_Copper",
        "Stray_Load_Rotor_Copper",
        "Brush_Friction",
        "Brush_VI",
    ]

    lossParameters = [
        "Armature_Copper_Loss_@Ref_Speed",
        "Armature_Copper_Freq_Component_Loss_@Ref_Speed",
        "Main_Winding_Copper_Loss_@Ref_Speed",
        "Aux_Winding_Copper_Loss_@Ref_Speed",
        "Stator_Iron_Loss_@Ref_Speed_[Back_Iron]",
        "Stator_Iron_Loss_@Ref_Speed_[Tooth]",
        "Windage_Loss_@Ref_Speed",
        "Windage_Loss_(Ext_Fan)@Ref_Speed",
        "Friction_Loss_[F]_@Ref_Speed",
        "Friction_Loss_[R]_@Ref_Speed",
        "Magnet_Iron_Loss_@Ref_Speed",
        "Magnet_Banding_Loss_@Ref_Speed",
        "Stator_Bore_Sleeve_Loss_@Ref_Speed",
        "Rotor_Iron_Loss_@Ref_Speed_[Embedded_Magnet_Pole]",
        "Encoder_Loss_@Ref_Speed",
        "Rotor_Iron_Loss_@Ref_Speed_[Back_Iron]",
        "Rotor_Iron_Loss_@Ref_Speed_[Tooth]",
        "Rotor_Copper_Loss_@Ref_Speed",
        "Stator_Iron_Stray_Load_Loss_@Ref_Speed",
        "Rotor_Iron_Stray_Load_Loss_@Ref_Speed",
        "Stator_Copper_Stray_Load_Loss_@Ref_Speed",
        "Rotor_Copper_Stray_Load_Loss_@Ref_Speed",
        "Brush_Friction_Loss_@Ref_Speed",
        "Brush_VI_Loss_@Ref_Speed",
    ]
    # Stator_Iron_Loss_@Ref_Speed_[Tooth_Tip] is not included as this is an unused Motor-CAD
    # parameter

    # Initialization function for objects of this class.
    def __init__(self, inputMotFilePath: str, outputDir: str):
        self.inputMotFilePath = inputMotFilePath
        self.outputDirectory = outputDir
        if not os.path.isdir(self.outputDirectory):
            os.makedirs(self.outputDirectory)

        print("Motor-CAD input file: " + self.inputMotFilePath)
        print("TB data output dir: " + self.outputDirectory)

        self.motFileName = None
        self.heatFlowMethod = None

        self.nodeNames = []
        self.nodeNumbers = []
        self.nodeGroupings = []
        self.nodeNumbers_fluid = []
        self.nodeNumbers_fluidInlet = []
        self.coolingSystems = None

        self.mcad = pymotorcad.MotorCAD()
        self.mcad.set_variable("MessageDisplayState", 2)
        # check which Motor-CAD version is being used as this affects the resistance matrix format
        self.motorcadV2025OrNewer = self.mcad.connection.check_version_at_least("2025.0")
        self.mcad.load_from_file(self.inputMotFilePath)

    # Main function to call which generates the required data for the Twin Builder export
    def generateTwinData(
        self,
        parameters: dict,
        housingAmbientTemperatures=None,
        airgapTemperatures=None,
        coolingSystemsInputs=None,
    ):
        self.updateMotfile()

        # calculate self.nodeNames, self.nodeNumbers, self.nodeGroupings, self.nodeNumbers_fluid,
        # and self.nodeNumbers_fluidInlet
        self.getNodeData()

        self.generateCoolingSystemNetwork()

        self.generateSamples(parameters)

        # this changes the losses in the file, so do after samples obtained
        self.generateLossDistribution()

        if housingAmbientTemperatures is not None:
            # uses fixed temperatures, so not dependent on losses
            self.generateHousingTempDependency(housingAmbientTemperatures)

        if airgapTemperatures is not None:
            # uses fixed temperatures, so not dependent on losses
            self.generateAirgapTempDependency(parameters["rpm"], airgapTemperatures)

        if coolingSystemsInputs is not None:
            self.generateCoolingSystemsInputsDependency(coolingSystemsInputs)

        # write config file
        with open(os.path.join(self.outputDirectory, "config.txt"), "w") as cf:
            if housingAmbientTemperatures is not None:
                cf.write("HousingTempDependency=1\n")
            else:
                cf.write("HousingTempDependency=0\n")
            if airgapTemperatures is not None:
                cf.write("AirGapTempDependency=1\n")
            else:
                cf.write("AirGapTempDependency=0\n")
            if self.heatFlowMethod == 1:
                cf.write("FluidHeatFlowMethod=1\n")
            else:
                cf.write("FluidHeatFlowMethod=0\n")
            if self.motorcadV2025OrNewer:
                cf.write("MCADVersion=20251\n")
            else:
                cf.write("MCADVersion=20242\n")
            if coolingSystemsInputs is not None:
                cf.write("CoolingSystemsInputs=1\n")
            else:
                cf.write("CoolingSystemsInputs=0\n")
            cf.write("CopperLossScaling=0\n")
            cf.write("SpeedDependentLosses=0\n")

    # Helper functions to parse the exported Motor-CAD matrices (``.cmf``, ``.nmf``, ``.pmf``,
    # ``.rmf`` and .``.tmf``)
    def unbracket(self, string):
        val = string.replace("(", "_").replace(")", "").replace(" ", "")
        return val

    def getExportedVector(self, file):
        with open(file, "r") as f:
            lines = f.readlines()[3:]
            vector = []
            for line in lines[:-1]:
                lineSplit = line.split(";")
                vector.append(float(lineSplit[1]))
        return vector

    def getExportedMatrix(self, file):
        with open(file, "r") as f:
            lines = f.readlines()[4:]
            matrix = []
            for line in lines[:-1]:
                row = []
                lineSplit = line.split(";")
                for ind in range(1, len(lineSplit) - 1):
                    row.append(float(lineSplit[ind]))
                matrix.append(row)
        return matrix

    def getPmfData(self, exportDirectory):
        pmfFile = os.path.join(exportDirectory, self.motFileName + ".pmf")
        # exported power vector does not contain ambient, so add on
        powerVector = [0.0] + self.getExportedVector(pmfFile)
        return powerVector

    def getTmfData(self, exportDirectory):
        tmfFile = os.path.join(exportDirectory, self.motFileName + ".tmf")
        temperatureVector = self.getExportedVector(tmfFile)
        return temperatureVector

    def getCmfData(self, exportDirectory):
        cmfFile = os.path.join(exportDirectory, self.motFileName + ".cmf")
        capacitanceMatrix = self.getExportedVector(cmfFile)
        return capacitanceMatrix

    def getRmfData(self, exportDirectory):
        rmfFile = os.path.join(exportDirectory, self.motFileName + ".rmf")
        resistanceMatrix = self.getExportedMatrix(rmfFile)

        # resistance matrix exported by v2025R1 and newer is transposed vs older versions
        if self.motorcadV2025OrNewer:
            resistanceMatrix = list(map(list, zip(*resistanceMatrix)))

        return resistanceMatrix

    def getNmfData(self, exportDirectory):
        # obtain the node numbers, node names, and node groupings from the nmf file
        nmfFile = os.path.join(exportDirectory, self.motFileName + ".nmf")
        nodeNumbers = []
        nodeNames = []
        nodeGroupings = []
        with open(nmfFile, "r") as fid:
            groupName = ""
            lines = fid.readlines()[2:]
            for line in lines:
                if not len(line.strip()) == 0:
                    if line[0] == "[":
                        # group name found
                        groupName = line[1:-2]
                    else:
                        # node number and node name found
                        lineSplit = line.split(" ", 1)

                        nodeNumbers.append(int(lineSplit[0]))
                        nodeNames.append(lineSplit[1][1:-2])
                        nodeGroupings.append(groupName)

        # sort based on the node numbers
        return (list(t) for t in zip(*sorted(zip(nodeNumbers, nodeNames, nodeGroupings))))

    # Functions to set and get the losses in the model, used to ensure the calculations are
    # performed with the correct losses and to determine the loss distribution
    def setLosses(self, lossVector):
        for index, lossParameter in enumerate(self.lossParameters):
            self.mcad.set_variable(lossParameter, lossVector[index])

    def getLosses(self):
        lossVector = []
        for lossParameter in self.lossParameters:
            lossVector.append(self.mcad.get_variable(lossParameter))
        return lossVector

    # Functions to update any mot file settings that need to be set appropriately
    # to ensure the correct calculations performed
    def updateMotfile(self):
        # update the model settings to those needed for the TB export
        # 1 rpm
        ## N/A no need to set RPM, this is done as required
        # 2 loss values
        ## use a very small loss value
        lossVector = 0.1 * np.ones(len(self.lossParameters))
        self.setLosses(lossVector)
        # 3 speed dep losses
        self.mcad.set_variable("Speed_Dependant_Losses", 0)
        # 4 copper loss variation x2
        ## turn off any temperature scaling losses as will affect loss distribution calculation
        self.mcad.set_variable("StatorCopperLossesVaryWithTemp", 0)
        self.mcad.set_variable("RotorCopperLossesVaryWithTemp", 0)
        # 5 calculation options x3
        ## set calculation type to steadystate thermal-only (no coupling)
        self.mcad.set_variable("ThermalCalcType", 0)
        self.mcad.set_variable("MagneticThermalCoupling", 0)
        self.mcad.set_variable("LabThermalCoupling", 0)
        # 6 matrix separator
        ## export assumes comma is being used TODO use the original separator
        self.mcad.set_variable("ExportTextSeparator", ";")
        # 7 windage losses
        ## TB model will not include this logic
        self.mcad.set_variable("Windage_Loss_Definition", 0)
        # 8 bearing losses
        ## TB model will not include this logic
        self.mcad.set_variable("BearingLossSource", 0)

        # detect heat flow method used (new option in 2024R2)
        try:
            self.heatFlowMethod = self.mcad.get_variable("FluidHeatFlowMethod")
        except:
            # variable does not exist due to using older version of Motor-CAD
            # set parameter to 0 which signifies use of the old method
            self.heatFlowMethod = 0

        # save the updated model so it is clear which Motor-CAD file can be used to validate
        # the Twin Builder Motor-CAD ROM component
        self.motFileName = Path(self.inputMotFilePath).stem + "_TwinModel"
        usedMotFilePath = os.path.join(self.outputDirectory, self.motFileName + ".mot")
        self.mcad.save_to_file(usedMotFilePath)

    # Helper function that solves the Motor-CAD thermal network and exports the matrices,
    # setting any operating-point specific required settings beforehand
    def computeMatrices(self, exportDirectory, rpm=None, lossVector=None):
        if not os.path.isdir(exportDirectory):
            os.makedirs(exportDirectory)

        if rpm is not None:
            self.mcad.set_variable("Shaft_Speed_[RPM]", rpm)

        if lossVector is not None:
            self.setLosses(lossVector)

        self.mcad.do_steady_state_analysis()
        self.mcad.export_matrices(exportDirectory)

    # Function that determines self.nodeNumbers, self.nodeNames, self.nodeGroupings,
    # self.nodeNumbers_fluidInlet and self.nodeNumbers_fluid
    def getNodeData(self):
        print("initialization : compute matrices")
        exportDirectory = os.path.join(self.outputDirectory, "tmp")
        self.computeMatrices(exportDirectory)

        self.nodeNumbers, self.nodeNames, self.nodeGroupings = self.getNmfData(exportDirectory)
        temperatureVector = self.getTmfData(exportDirectory)

        # determine which of the nodes is an inlet node
        for index, temperature in enumerate(temperatureVector):
            isInlet_check1 = "inlet".lower() in self.nodeNames[index].lower()
            isInlet_check2 = temperature > -10000000.0
            isInlet_check3 = self.nodeGroupings[index] in self.coolingSystemData

            if isInlet_check1 and isInlet_check2 and isInlet_check3:
                self.nodeNumbers_fluidInlet.append(self.nodeNumbers[index])

        self.nodeNumbers_fluid = [
            nodeNumber
            for (index, nodeNumber) in enumerate(self.nodeNumbers)
            if self.nodeGroupings[index] in self.coolingSystemData
        ]

    # Function that determines the nodes used for the cooling system and their connections. The
    # resulting data is required by Twin Builder to correctly model the fluid flow
    def generateCoolingSystemNetwork(self):
        print("initialization : identifying cooling systems")
        exportDirectory = os.path.join(self.outputDirectory, "tmp")
        self.computeMatrices(exportDirectory)

        if len(self.nodeNumbers_fluid) == 0:
            print("initialization : no cooling systems found")
        else:
            print("initialization : cooling systems found")
            self.coolingSystems = dict()

            resistanceMatrix = self.getRmfData(exportDirectory)
            graphNodes = []
            graphEdges = []

            for node in self.nodeNumbers_fluid:
                connectedNodes = self.returnConnectedNodes(
                    node, self.nodeNumbers_fluid, resistanceMatrix
                )
                if node not in graphNodes:
                    graphNodes.append(node)
                for connectedNode in connectedNodes:
                    graphEdges.append([node, connectedNode])

            G = nx.DiGraph()
            G.add_nodes_from(graphNodes)
            G.add_edges_from(graphEdges)
            M = nx.adjacency_matrix(G).todense()

            inletNodes = []
            connectedNodesLists = []

            for graphNode in graphNodes:
                if graphNode in self.nodeNumbers_fluidInlet:
                    inletNodes.append(graphNode)

            for index, graphNode in enumerate(inletNodes):
                connectedNodesList = []
                connectedNodesInd = []

                node = graphNode
                next = []
                next.append(node)
                covered = []
                curGraphEdges = []

                while len(next) > 0:
                    node = next[0]
                    line = M[graphNodes.index(node)]
                    for k in range(0, len(line)):
                        if line[k] > 0 and graphNodes[k] not in covered:
                            # don't consider first connection for the power correction
                            if node != graphNode:
                                connectedNodesList.append(
                                    [
                                        self.unbracket(
                                            self.nodeNames[self.nodeNumbers.index(node)]
                                        ),
                                        self.unbracket(
                                            self.nodeNames[self.nodeNumbers.index(graphNodes[k])]
                                        ),
                                    ]
                                )
                                connectedNodesInd.append([node, graphNodes[k]])

                            curGraphEdges.append([node, graphNodes[k]])
                            if graphNodes[k] not in next:
                                next.append(graphNodes[k])
                    next.remove(node)
                    covered.append(node)

                curG = nx.DiGraph()
                curG.add_nodes_from(graphNodes)
                curG.add_edges_from(curGraphEdges)
                connectedNodesLists.append(connectedNodesList)
                self.coolingSystems.update({graphNode: connectedNodesInd})

                plt.figure(index)
                nx.draw(curG, with_labels=True)
                plt.savefig(os.path.join(self.outputDirectory, str(graphNode) + "_cooling.png"))

            # write cooling systems config file
            if len(connectedNodesLists) > 0:
                with open(os.path.join(self.outputDirectory, "CoolingSystems.csv"), "w") as outfile:
                    k = 0
                    for connectedNodesList in connectedNodesLists:
                        outfile.write(
                            "inlet : "
                            + str(inletNodes[k])
                            + " - "
                            + str(self.nodeNames[self.nodeNumbers.index(inletNodes[k])])
                            + "\n"
                        )
                        for connectedNodes in connectedNodesList:
                            outfile.write(str(connectedNodes) + "\n")
                        k = k + 1

    def returnConnectedNodes(self, node, nodeList, resistanceMatrix):
        nodeIndex = self.nodeNumbers.index(node)
        resistanceRow = resistanceMatrix[nodeIndex]

        connectedNodesList = []

        for index, resistance in enumerate(resistanceRow):
            if (resistance > 0) and (resistance < 1000000000.0):
                # there is a connection
                connectedNode = self.nodeNumbers[index]
                if connectedNode in nodeList:
                    connectedNodesList.append(connectedNode)

        return connectedNodesList

    # Function that runs the thermal model at each desired speed, and exports the thermal matrices
    def generateSamples(self, parameters: dict):
        rpmSamples = parameters["rpm"]

        for index, rpm in enumerate(rpmSamples):
            print("DoE : computing sample point rpm = " + str(rpm))
            exportDirectory = os.path.join(self.outputDirectory, "dp" + str(index).zfill(6))
            self.computeMatrices(exportDirectory, rpm=rpm)

        # write doe file
        with open(os.path.join(self.outputDirectory, "doe.csv"), "w") as cf:
            cf.write("Name")
            parameterNames = list(parameters.keys())
            parameterValues = list(parameters.values())
            nbDPs = len(parameterValues[0])
            for parameterName in parameterNames:
                cf.write(", " + str(parameterName))
            cf.write("\n")
            for i in range(0, nbDPs):
                cf.write("dp" + str(i).zfill(6))
                for j in range(0, len(parameterNames)):
                    cf.write(", " + str(parameterValues[j][i]))
                cf.write("\n")

    # Function that extracts the per-node loss distribution for each loss type, allowing the user to
    # specify a loss value using a name (such as Armature Copper Loss) and have Twin Builder
    # automatically distribute this amongst appropriate nodes.
    def generateLossDistribution(self):
        numLossParameters = len(self.lossNames)
        lossDistributionMatrix = np.zeros((numLossParameters, len(self.nodeNames)))

        # use a small loss value of 1W
        inputLoss = 1

        for lossIndex in range(numLossParameters):
            print(
                "power distribution "
                + str(lossIndex + 1)
                + "/"
                + str(numLossParameters)
                + " : "
                + self.lossNames[lossIndex]
            )

            exportDirectory = os.path.join(self.outputDirectory, "tmp", "dis" + str(lossIndex))

            lossVector = np.zeros(numLossParameters)
            lossVector[lossIndex] = inputLoss

            self.computeMatrices(exportDirectory, lossVector=lossVector)

            powerVector = self.getPmfData(exportDirectory)
            for nodeIndex, nodePower in enumerate(powerVector):
                # ignore nodes with negative loss (cooling systems)
                if nodePower > 0:
                    lossDistributionMatrix[lossIndex, nodeIndex] = nodePower / inputLoss

        with open(os.path.join(self.outputDirectory, "LossDistribution.csv"), "w") as outfile:
            outfile.write(" ")
            for nodeName in self.nodeNames:
                outfile.write(", " + nodeName)
            outfile.write("\n")

            for index, lossName in enumerate(self.lossNames):
                outfile.write(str(lossName))
                for nodeLoss in lossDistributionMatrix[index]:
                    outfile.write(", " + str(nodeLoss))
                outfile.write("\n")

    # Function that determines the Housing to Ambient resistances at different housing temperatures,
    # the results of which are used by Twin Builder to take into account external Natural Convection
    # cooling.
    # The input parameter is a dict, format e.g.: {tAmbient1:[tHousing1, ..., tHousingx],
    # tAmbient2:[tHousing1,..., tHousingy], tAmbient3:[tHousing1, ..., tHousingx]}
    def generateHousingTempDependency(self, housingAmbientTemperatures):
        housingNodes = [
            nodeNumber
            for (index, nodeNumber) in enumerate(self.nodeNumbers)
            if self.nodeGroupings[index] == "Housing" or nodeNumber == 5
        ]

        fileInd = 0
        for ambientTemperature, fixedHousingTemperatures in housingAmbientTemperatures.items():
            fileInd = fileInd + 1

            print("Tamb: " + str(ambientTemperature))
            self.mcad.set_variable("T_Ambient", ambientTemperature)
            file_content, header_line = self.computeMatricesHousingTemps(
                housingNodes, ambientTemperature, fixedHousingTemperatures
            )

            exportDirectory = os.path.join(self.outputDirectory, "HousingTempDependency")
            if not os.path.isdir(exportDirectory):
                os.makedirs(os.path.join(exportDirectory))

            with open(
                os.path.join(exportDirectory, "Housing_Temp" + str(fileInd) + ".csv"), "w"
            ) as fout:
                fout.write(str(header_line[0]))
                for el in header_line[1:]:
                    fout.write("," + str(el))
                fout.write("\n")
                for key, item in file_content.items():
                    fout.write(str(key))
                    for el in item:
                        fout.write("," + str(el))
                    fout.write("\n")

    def computeMatricesHousingTemps(
        self, housingNodes, ambientTemperature, fixedHousingTemperatures
    ):
        exportDirectory = os.path.join(self.outputDirectory, "tmp")

        file_content = dict()

        for housingTemperature in fixedHousingTemperatures:
            print("HousingTemp: " + str(housingTemperature))

            # Set the fixed temperature
            for housingNode in housingNodes:
                name = "Housing Node " + str(housingNode)
                self.mcad.set_fixed_temperature_value(name, housingNode, housingTemperature, name)

            self.computeMatrices(exportDirectory)

            resistanceMatrix = self.getRmfData(exportDirectory)
            ambientResistances = resistanceMatrix[0]

            housingResistances = []
            for housingNode in housingNodes:
                index = self.nodeNumbers.index(housingNode)
                housingResistances.append(ambientResistances[index])

            file_content.update({housingTemperature + 273.15: housingResistances})

        header_line = []
        header_line.append(ambientTemperature + 273.15)
        for housingNode in housingNodes:
            index = self.nodeNumbers.index(housingNode)
            header_line.append(self.unbracket(self.nodeNames[index]))

        return file_content, header_line

    # Function that determines the Stator to Rotor airgap resistance at different housing
    # temperatures, the results of which are used by Twin Builder to take into account the
    # temperature dependent nature of this resistance
    def generateAirgapTempDependency(self, rpmSamples, airgapTemperatures):
        fileInd = 0

        for rpm in rpmSamples:
            fileInd = fileInd + 1
            print("RPM : " + str(rpm))

            file_content = self.computeMatricesAirgapTemp(airgapTemperatures, rpm)

            exportPath = os.path.join(self.outputDirectory, "AirGapTempDependency")
            if not os.path.isdir(exportPath):
                os.makedirs(os.path.join(exportPath))

            with open(os.path.join(exportPath, "AirGap_Temp" + str(fileInd) + ".csv"), "w") as fout:
                fout.write(str(rpm) + "\n")
                for key, item in file_content.items():
                    fout.write(str(key) + "," + str(item))
                    fout.write("\n")

    def computeMatricesAirgapTemp(self, airgapTemperatures, rpm):
        exportDirectory = os.path.join(self.outputDirectory, "tmp")

        # Airgap nodes between which there is a temperature dependent resistance
        airgapNodeStator, airgapNodeRotor = self.getAirgapNodes()

        file_content = dict()

        # Loop over each airgap average temperature
        for airgapTemperature in airgapTemperatures:
            print("Air Gap average temperature: " + str(airgapTemperature))

            # Set the fixed temperature
            name = "Airgap_Stator_Node_" + str(airgapNodeStator)
            self.mcad.set_fixed_temperature_value(name, airgapNodeStator, airgapTemperature, name)
            name = "Airgap_Rotor_Node_" + str(airgapNodeRotor)
            self.mcad.set_fixed_temperature_value(name, airgapNodeRotor, airgapTemperature, name)

            self.computeMatrices(exportDirectory, rpm=rpm)
            resistanceMatrix = self.getRmfData(exportDirectory)

            index1 = self.nodeNumbers.index(airgapNodeStator)
            index2 = self.nodeNumbers.index(airgapNodeRotor)
            airgapResistance = resistanceMatrix[index1][index2]

            file_content.update({airgapTemperature + 273.15: airgapResistance})

        return file_content

    def getAirgapNodes(self):
        tVent = self.mcad.get_variable("ThroughVentilation")
        sVent = self.mcad.get_variable("SelfVentilation")
        if tVent or sVent:
            statorCoolingOnly = self.mcad.get_variable("TVent_NoAirgapFlow")
            if statorCoolingOnly == False:
                warnings.warn(
                    "Temperature dependent airgap not supported for ventilated cooling with airgap"
                    "flow"
                )

        wetrotor = self.mcad.get_variable("Wet_Rotor")
        if wetrotor:
            warnings.warn("Temperature dependent airgap not supported for wet rotor")

        axialSlices = self.mcad.get_variable("AxialSliceDefinition")
        if axialSlices > 0:
            warnings.warn("Temperature dependent airgap only supported for middle axial slice")

        sleeveThickness = self.mcad.get_variable("Sleeve_Thickness")
        if sleeveThickness > 0:
            # sleeve node present on stator side
            airgapNodeStator = 61
        else:
            airgapNodeStator = 11

        airgapNodeRotor = 12

        return airgapNodeStator, airgapNodeRotor

    # Function that determines Cooling Systems nodes' resistances/capacitances at
    # different RPM, coolant flow rate and inlet temperatures, the results of which
    # are used by Twin Builder to take into account the Cooling Systems inputs
    # dependencies. coolingSystemsInputs is a dictionary with keys describing
    # the Cooling System name and value being another dictionary storing
    # the parameter (RPM, Flow Rate, Inlet Temperature) values to evaluate
    def generateCoolingSystemsInputsDependency(self, coolingSystemsInputs):
        for index, (coolingSystem, parameters) in enumerate(coolingSystemsInputs.items()):
            if coolingSystem not in self.coolingSystemData:
                warnings.warn(
                    "The Cooling System name {} is not part of the list of Cooling Systems "
                    "{}\n".format(coolingSystem, self.coolingSystemData)
                )
                return

            exportPath = os.path.join(self.outputDirectory, self.unbracket(coolingSystem))
            if not os.path.isdir(exportPath):
                os.makedirs(os.path.join(exportPath))

            rpms = parameters["rpm"]
            frs = parameters["FR"]
            inletTemps = parameters["inletTemp"]

            with open(os.path.join(exportPath, "dp_values.txt"), "w") as fout:
                fout.write("rpm=[" + str(rpms[0]))
                for el in rpms[1:]:
                    fout.write("," + str(el))
                fout.write("]\n")
                fout.write("FR=[" + str(frs[0]))
                for el in frs[1:]:
                    fout.write("," + str(el))
                fout.write("]\n")
                fout.write("inletTemp=[" + str(inletTemps[0] + 273.15))
                for el in inletTemps[1:]:
                    fout.write("," + str(el + 273.15))
                fout.write("]\n")

            # identify all the impacted resistances and capacitances
            exportDirectory = os.path.join(self.outputDirectory, "tmp")

            resistanceMatrix = self.getRmfData(exportDirectory)
            r_list = []
            c_list = []

            with open(os.path.join(exportPath, "c_nodes.txt"), "w") as fCout, open(
                os.path.join(exportPath, "r_nodes.txt"), "w"
            ) as fRout:
                covered_nodes = dict()
                for inNode, conList in self.coolingSystems.items():
                    if self.nodeGroupings[self.nodeNumbers.index(inNode)] == coolingSystem:
                        upnode = inNode
                        coolSys = conList

                connectedNodes = self.returnConnectedNodes(
                    upnode, self.nodeNumbers, resistanceMatrix
                )  # inlet node
                for i in range(0, len(connectedNodes)):
                    fRout.write(
                        self.unbracket(self.nodeNames[self.nodeNumbers.index(upnode)])
                        + " "
                        + self.unbracket(self.nodeNames[self.nodeNumbers.index(connectedNodes[i])])
                        + "\n"
                    )
                    r_list.append(
                        [
                            self.nodeNames[self.nodeNumbers.index(upnode)],
                            self.nodeNames[self.nodeNumbers.index(connectedNodes[i])],
                        ]
                    )
                    if upnode not in self.nodeNumbers_fluidInlet:
                        fCout.write(
                            self.unbracket(self.nodeNames[self.nodeNumbers.index(upnode)]) + "\n"
                        )
                        c_list.append([self.nodeNames[self.nodeNumbers.index(upnode)]])
                covered_nodes.update({upnode: connectedNodes})

                for item in coolSys:  # following downstream nodes of the cooling system
                    for upnode in item:
                        if upnode not in list(covered_nodes.keys()):
                            connectedNodes = self.returnConnectedNodes(
                                upnode, self.nodeNumbers, resistanceMatrix
                            )
                            for i in range(0, len(connectedNodes)):
                                if not (
                                    connectedNodes[i] in list(covered_nodes.keys())
                                    and upnode in covered_nodes[connectedNodes[i]]
                                ):  # avoid taking the symmetric counterpart of the resistance
                                    fRout.write(
                                        self.unbracket(
                                            self.nodeNames[self.nodeNumbers.index(upnode)]
                                        )
                                        + " "
                                        + self.unbracket(
                                            self.nodeNames[
                                                self.nodeNumbers.index(connectedNodes[i])
                                            ]
                                        )
                                        + "\n"
                                    )
                                    r_list.append(
                                        [
                                            self.nodeNames[self.nodeNumbers.index(upnode)],
                                            self.nodeNames[
                                                self.nodeNumbers.index(connectedNodes[i])
                                            ],
                                        ]
                                    )
                            if upnode not in self.nodeNumbers_fluidInlet:
                                fCout.write(
                                    self.unbracket(self.nodeNames[self.nodeNumbers.index(upnode)])
                                    + "\n"
                                )
                                c_list.append([self.nodeNames[self.nodeNumbers.index(upnode)]])
                            covered_nodes.update({upnode: connectedNodes})

                if (
                    len(coolSys) == 0
                ):  # particular case where the cooling system has only 2 nodes (inlet/outlet)
                    for upnode in connectedNodes:
                        if (
                            self.nodeGroupings[self.nodeNumbers.index(upnode)] == coolingSystem
                        ):  # make sure the connected node still belongs to cooling system
                            connectedNodes = self.returnConnectedNodes(
                                upnode, self.nodeNumbers, resistanceMatrix
                            )
                            for i in range(0, len(connectedNodes)):
                                if not (
                                    connectedNodes[i] in list(covered_nodes.keys())
                                    and upnode in covered_nodes[connectedNodes[i]]
                                ):  # avoid taking the symmetric counterpart of the resistance
                                    fRout.write(
                                        self.unbracket(
                                            self.nodeNames[self.nodeNumbers.index(upnode)]
                                        )
                                        + " "
                                        + self.unbracket(
                                            self.nodeNames[
                                                self.nodeNumbers.index(connectedNodes[i])
                                            ]
                                        )
                                        + "\n"
                                    )
                                    r_list.append(
                                        [
                                            self.nodeNames[self.nodeNumbers.index(upnode)],
                                            self.nodeNames[
                                                self.nodeNumbers.index(connectedNodes[i])
                                            ],
                                        ]
                                    )
                                if upnode not in self.nodeNumbers_fluidInlet:
                                    fCout.write(
                                        self.unbracket(
                                            self.nodeNames[self.nodeNumbers.index(upnode)]
                                        )
                                        + "\n"
                                    )
                                    c_list.append([self.nodeNames[self.nodeNumbers.index(upnode)]])
                            covered_nodes.update({upnode: connectedNodes})

            # run the DoE
            fileInd = 0
            nDps = len(frs) * len(inletTemps) * len(rpms)
            for flow in frs:
                for inTemp in inletTemps:
                    for speed in rpms:
                        fileInd = fileInd + 1
                        print(
                            (
                                "Run DP {}/{} for cooling system {} at parameters values flow rate "
                                "{}, inlet temperature {} and rpm {}"
                            ).format(fileInd, nDps, coolingSystem, flow, inTemp, speed)
                        )
                        [R, C] = self.computeMatricesCoolingSystemsInput(
                            coolingSystem, speed, inTemp, flow, r_list, c_list, fileInd
                        )
                        with open(
                            os.path.join(exportPath, "R" + str(fileInd) + ".csv"), "w"
                        ) as fout:
                            fout.write(str(speed) + "\n")
                            fout.write(str(flow) + "\n")
                            fout.write(str(inTemp + 273.15) + "\n")
                            for r in R:
                                fout.write(str(r) + "\n")
                        with open(
                            os.path.join(exportPath, "C" + str(fileInd) + ".csv"), "w"
                        ) as fout:
                            fout.write(str(speed) + "\n")
                            fout.write(str(flow) + "\n")
                            fout.write(str(inTemp + 273.15) + "\n")
                            for c in C:
                                fout.write(str(c) + "\n")

    def computeMatricesCoolingSystemsInput(
        self, coolingSystem, rpm, inTemp, fr, r_list, c_list, fileInd
    ):
        exportDirectory = os.path.join(self.outputDirectory, "tmp", "dp" + str(fileInd).zfill(6))
        if not os.path.isdir(exportDirectory):
            os.makedirs(exportDirectory)

        self.mcad.set_variable("Shaft_Speed_[RPM]", rpm)
        self.mcad.set_variable(self.coolingSystemData[coolingSystem][0], fr)
        self.mcad.set_variable(self.coolingSystemData[coolingSystem][1], inTemp)
        self.mcad.do_steady_state_analysis()
        self.mcad.export_matrices(exportDirectory)

        resistanceMatrix = self.getRmfData(exportDirectory)
        capacitanceMatrix = self.getCmfData(exportDirectory)

        R = []
        for r in r_list:
            index1 = self.nodeNames.index(r[0])
            index2 = self.nodeNames.index(r[1])
            resistance = resistanceMatrix[index1][index2]
            R.append(resistance)

        C = []
        for c in c_list:
            index = (
                self.nodeNames.index(c[0]) - 1
            )  # -1 since capacitance matrix does not have ambient node
            capacitance = capacitanceMatrix[index]
            C.append(capacitance)

        return [R, C]

Example use case#

Below is an example of how the above MotorCADTwinModel class can be used using the e8_eMobility template .mot file.

The generateTwinData method accepts as an optional parameter a dictionary of Housing and Ambient temperatures to be investigated. This can be provided if Natural Convection cooling of the housing should be modelled in the Twin Builder Motor-CAD ROM component. For this example, a function has been defined to return this dictionary. As can be seen in the code comments, more data points are calculated when the housing and ambient temperatures are close together, as this is where the natural convection heat transfer coefficients vary the most.

def temperaturesHousingAmbient():
    # Consider ambient temperature as 20, 40, 65
    # For each ambient temperature run housing nodes sweep between 20 and 100 deg
    # 0<= abs(dT) <= 5 -> 0.5 deg => 10 points
    # 5 < abs(dT) <= 15 -> 1 deg => 10 points
    # 15 < abs(dT) <= 40 -> 5 deg => 5 points
    # 40 < abs(dT) -> 10 deg

    print("Determining Ambient and Housing temperatures to investigate:")
    temperatures = dict()

    ambientTemperatures = [20, 40]
    housingTemperatureMin = 20
    housingTemperatureMax = 100

    for ambientTemperature in ambientTemperatures:
        housingTemperatures = [housingTemperatureMin]
        curTemp = housingTemperatureMin
        while curTemp < housingTemperatureMax:
            curdT = abs(curTemp - ambientTemperature)
            if curdT < 5:
                dT = 0.5
            elif curdT < 15:
                dT = 1
            elif curdT < 40:
                dT = 5
            else:
                dT = 10
            curTemp = curTemp + dT
            housingTemperatures.append(curTemp)

        temperatures.update({ambientTemperature: housingTemperatures})

    print(temperatures)
    return temperatures

Specify the input .mot file and the directory to save the output data to.

Create the e8 input file if it does not exist already.

if Path(inputMotFilePath).exists() == False:
    motorcad = pymotorcad.MotorCAD()
    motorcad.load_template("e8")
    motorcad.save_to_file(inputMotFilePath)
    motorcad.quit()

Choose the speed points that the model should be solved at. The generated Motor-CAD ROM component will interpolate between these, so it is important to cover the complete speed range with the appropriate sampling in order to maintain accuracy. Three points have been chosen here to reduce calculation time, but in real use it is recommended that this be greater.

rpms = [200, 500, 1000]

Specify the airgap temperatures to investigate, in order for the temperature dependent nature of the airgap heat transfer to be included in the Motor-CAD ROM component. The generated Motor-CAD ROM component will interpolate between these, so it is important to cover the complete expected airgap temperature range with the appropriate sampling in order to maintain accuracy. This parameter can be set to None should this not be required.

airgapTemps = [40, 50, 65]

Specify the housing and ambient temperatures to investigate, in order for the natural convection cooling of the housing to be be included in the Motor-CAD ROM component. The generated Motor-CAD ROM component will interpolate between these, so it is important to cover the complete expected housing and ambient temperature range with the appropriate sampling in order to maintain accuracy. This parameter can be set to None should this not be required.

housingAmbientTemps = temperaturesHousingAmbient()
Determining Ambient and Housing temperatures to investigate:
{20: [20, 20.5, 21.0, 21.5, 22.0, 22.5, 23.0, 23.5, 24.0, 24.5, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 40.0, 45.0, 50.0, 55.0, 60.0, 70.0, 80.0, 90.0, 100.0], 40: [20, 25, 30, 31, 32, 33, 34, 35, 36, 36.5, 37.0, 37.5, 38.0, 38.5, 39.0, 39.5, 40.0, 40.5, 41.0, 41.5, 42.0, 42.5, 43.0, 43.5, 44.0, 44.5, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 60.0, 65.0, 70.0, 75.0, 80.0, 90.0, 100.0]}

Specify the cooling systems for which input dependencies need to be taken into account. For each cooling system involved, define the parameters values to sweep to extract the corresponding training data.

coolingSystemsInputs = {
    "Housing Water Jacket": {
        "rpm": rpms,
        "FR": [9.7695e-05, 0.000103122499999999, 0.00010855, 0.0001139775, 0.000119405],
        "inletTemp": [40, 65],
    }
}

Create a MotorCADTwinModel object, passing as arguments the path to the input .mot file as well as the directory to which the results should be saved.

MotorCADTwin = MotorCADTwinModel(inputMotFilePath, outputDir)
Motor-CAD input file: C:\actions-runner-docs\_work\pymotorcad\pymotorcad\examples\links\e8_mobility.mot
TB data output dir: C:\actions-runner-docs\_work\pymotorcad\pymotorcad\examples\links\thermal_twinbuilder_e8_mobility

Finally, generate the required data. This function will write the data to the directory specified above. The identified cooling system node flow path is automatically plotted.

MotorCADTwin.generateTwinData(
    parameters={"rpm": rpms},
    housingAmbientTemperatures=housingAmbientTemps,
    airgapTemperatures=airgapTemps,
    coolingSystemsInputs=coolingSystemsInputs,
)
thermal twinbuilder
initialization : compute matrices
initialization : identifying cooling systems
initialization : cooling systems found
DoE : computing sample point rpm = 200
DoE : computing sample point rpm = 500
DoE : computing sample point rpm = 1000
power distribution 1/24 : Armature_Copper_dc
power distribution 2/24 : Armature_Copper_Freq_Comp
power distribution 3/24 : Main_Winding
power distribution 4/24 : Aux_Winding
power distribution 5/24 : Stator_Back_Iron
power distribution 6/24 : Stator_Tooth
power distribution 7/24 : Windage
power distribution 8/24 : Windage_Ext_Fan
power distribution 9/24 : Friction_F_Bearing
power distribution 10/24 : Friction_R_Bearing
power distribution 11/24 : Magnet
power distribution 12/24 : Rotor_Banding
power distribution 13/24 : Stator_Sleeve
power distribution 14/24 : Embedded_Magnet_Pole
power distribution 15/24 : Encoder
power distribution 16/24 : Rotor_Back_Iron
power distribution 17/24 : Rotor_Tooth
power distribution 18/24 : Rotor_Copper
power distribution 19/24 : Stray_Load_Stator_Iron
power distribution 20/24 : Stray_Load_Rotor_Iron
power distribution 21/24 : Stray_Load_Stator_Copper
power distribution 22/24 : Stray_Load_Rotor_Copper
power distribution 23/24 : Brush_Friction
power distribution 24/24 : Brush_VI
Tamb: 20
HousingTemp: 20
HousingTemp: 20.5
HousingTemp: 21.0
HousingTemp: 21.5
HousingTemp: 22.0
HousingTemp: 22.5
HousingTemp: 23.0
HousingTemp: 23.5
HousingTemp: 24.0
HousingTemp: 24.5
HousingTemp: 25.0
HousingTemp: 26.0
HousingTemp: 27.0
HousingTemp: 28.0
HousingTemp: 29.0
HousingTemp: 30.0
HousingTemp: 31.0
HousingTemp: 32.0
HousingTemp: 33.0
HousingTemp: 34.0
HousingTemp: 35.0
HousingTemp: 40.0
HousingTemp: 45.0
HousingTemp: 50.0
HousingTemp: 55.0
HousingTemp: 60.0
HousingTemp: 70.0
HousingTemp: 80.0
HousingTemp: 90.0
HousingTemp: 100.0
Tamb: 40
HousingTemp: 20
HousingTemp: 25
HousingTemp: 30
HousingTemp: 31
HousingTemp: 32
HousingTemp: 33
HousingTemp: 34
HousingTemp: 35
HousingTemp: 36
HousingTemp: 36.5
HousingTemp: 37.0
HousingTemp: 37.5
HousingTemp: 38.0
HousingTemp: 38.5
HousingTemp: 39.0
HousingTemp: 39.5
HousingTemp: 40.0
HousingTemp: 40.5
HousingTemp: 41.0
HousingTemp: 41.5
HousingTemp: 42.0
HousingTemp: 42.5
HousingTemp: 43.0
HousingTemp: 43.5
HousingTemp: 44.0
HousingTemp: 44.5
HousingTemp: 45.0
HousingTemp: 46.0
HousingTemp: 47.0
HousingTemp: 48.0
HousingTemp: 49.0
HousingTemp: 50.0
HousingTemp: 51.0
HousingTemp: 52.0
HousingTemp: 53.0
HousingTemp: 54.0
HousingTemp: 55.0
HousingTemp: 60.0
HousingTemp: 65.0
HousingTemp: 70.0
HousingTemp: 75.0
HousingTemp: 80.0
HousingTemp: 90.0
HousingTemp: 100.0
RPM : 200
Air Gap average temperature: 40
Air Gap average temperature: 50
Air Gap average temperature: 65
RPM : 500
Air Gap average temperature: 40
Air Gap average temperature: 50
Air Gap average temperature: 65
RPM : 1000
Air Gap average temperature: 40
Air Gap average temperature: 50
Air Gap average temperature: 65
Run DP 1/30 for cooling system Housing Water Jacket at parameters values flow rate 9.7695e-05, inlet temperature 40 and rpm 200
Run DP 2/30 for cooling system Housing Water Jacket at parameters values flow rate 9.7695e-05, inlet temperature 40 and rpm 500
Run DP 3/30 for cooling system Housing Water Jacket at parameters values flow rate 9.7695e-05, inlet temperature 40 and rpm 1000
Run DP 4/30 for cooling system Housing Water Jacket at parameters values flow rate 9.7695e-05, inlet temperature 65 and rpm 200
Run DP 5/30 for cooling system Housing Water Jacket at parameters values flow rate 9.7695e-05, inlet temperature 65 and rpm 500
Run DP 6/30 for cooling system Housing Water Jacket at parameters values flow rate 9.7695e-05, inlet temperature 65 and rpm 1000
Run DP 7/30 for cooling system Housing Water Jacket at parameters values flow rate 0.000103122499999999, inlet temperature 40 and rpm 200
Run DP 8/30 for cooling system Housing Water Jacket at parameters values flow rate 0.000103122499999999, inlet temperature 40 and rpm 500
Run DP 9/30 for cooling system Housing Water Jacket at parameters values flow rate 0.000103122499999999, inlet temperature 40 and rpm 1000
Run DP 10/30 for cooling system Housing Water Jacket at parameters values flow rate 0.000103122499999999, inlet temperature 65 and rpm 200
Run DP 11/30 for cooling system Housing Water Jacket at parameters values flow rate 0.000103122499999999, inlet temperature 65 and rpm 500
Run DP 12/30 for cooling system Housing Water Jacket at parameters values flow rate 0.000103122499999999, inlet temperature 65 and rpm 1000
Run DP 13/30 for cooling system Housing Water Jacket at parameters values flow rate 0.00010855, inlet temperature 40 and rpm 200
Run DP 14/30 for cooling system Housing Water Jacket at parameters values flow rate 0.00010855, inlet temperature 40 and rpm 500
Run DP 15/30 for cooling system Housing Water Jacket at parameters values flow rate 0.00010855, inlet temperature 40 and rpm 1000
Run DP 16/30 for cooling system Housing Water Jacket at parameters values flow rate 0.00010855, inlet temperature 65 and rpm 200
Run DP 17/30 for cooling system Housing Water Jacket at parameters values flow rate 0.00010855, inlet temperature 65 and rpm 500
Run DP 18/30 for cooling system Housing Water Jacket at parameters values flow rate 0.00010855, inlet temperature 65 and rpm 1000
Run DP 19/30 for cooling system Housing Water Jacket at parameters values flow rate 0.0001139775, inlet temperature 40 and rpm 200
Run DP 20/30 for cooling system Housing Water Jacket at parameters values flow rate 0.0001139775, inlet temperature 40 and rpm 500
Run DP 21/30 for cooling system Housing Water Jacket at parameters values flow rate 0.0001139775, inlet temperature 40 and rpm 1000
Run DP 22/30 for cooling system Housing Water Jacket at parameters values flow rate 0.0001139775, inlet temperature 65 and rpm 200
Run DP 23/30 for cooling system Housing Water Jacket at parameters values flow rate 0.0001139775, inlet temperature 65 and rpm 500
Run DP 24/30 for cooling system Housing Water Jacket at parameters values flow rate 0.0001139775, inlet temperature 65 and rpm 1000
Run DP 25/30 for cooling system Housing Water Jacket at parameters values flow rate 0.000119405, inlet temperature 40 and rpm 200
Run DP 26/30 for cooling system Housing Water Jacket at parameters values flow rate 0.000119405, inlet temperature 40 and rpm 500
Run DP 27/30 for cooling system Housing Water Jacket at parameters values flow rate 0.000119405, inlet temperature 40 and rpm 1000
Run DP 28/30 for cooling system Housing Water Jacket at parameters values flow rate 0.000119405, inlet temperature 65 and rpm 200
Run DP 29/30 for cooling system Housing Water Jacket at parameters values flow rate 0.000119405, inlet temperature 65 and rpm 500
Run DP 30/30 for cooling system Housing Water Jacket at parameters values flow rate 0.000119405, inlet temperature 65 and rpm 1000

Generating the Motor-CAD ROM component#

To generate the component, within Ansys Electronics Desktop, go to the menu bar and select Twin Builder > Add Component > Add Motor-CAD ROM Component…. Under Input Files, press the ... icon and choose the outputDir as specified in the previous step. Then press the Generate button.

../../_images/Thermal_Twinbuilder_GenerateROM_Filled.png

Should the generation be successful, the Log will indicate that the SML model has been written and the Select Interfaces table will be populated.

The resulting Motor-CAD ROM component will then be available to use.

../../_images/Thermal_Twinbuilder_TwinBuilderROM.png

See also

For informtation on how to use the Motor-CAD ROM component, please consult the Twin Builder Help Manual.

Total running time of the script: (14 minutes 17.937 seconds)

Gallery generated by Sphinx-Gallery