.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "examples\links\thermal_twinbuilder.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_examples_links_thermal_twinbuilder.py: 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. .. GENERATED FROM PYTHON SOURCE LINES 30-56 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. .. image:: ../../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. .. GENERATED FROM PYTHON SOURCE LINES 58-77 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: .. image:: ../../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. .. GENERATED FROM PYTHON SOURCE LINES 79-81 Perform required imports ------------------------ .. GENERATED FROM PYTHON SOURCE LINES 81-92 .. code-block:: Python 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 .. GENERATED FROM PYTHON SOURCE LINES 94-99 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: .. GENERATED FROM PYTHON SOURCE LINES 101-113 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 .. GENERATED FROM PYTHON SOURCE LINES 113-1014 .. code-block:: Python 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] .. GENERATED FROM PYTHON SOURCE LINES 1015-1019 Example use case ---------------- Below is an example of how the above ``MotorCADTwinModel`` class can be used using the ``e8_eMobility`` template .mot file. .. GENERATED FROM PYTHON SOURCE LINES 1022-1028 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. .. GENERATED FROM PYTHON SOURCE LINES 1028-1065 .. code-block:: Python 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 .. GENERATED FROM PYTHON SOURCE LINES 1066-1067 Specify the input .mot file and the directory to save the output data to. .. GENERATED FROM PYTHON SOURCE LINES 1067-1072 .. code-block:: Python working_folder = os.getcwd() mcad_name = "e8_mobility" inputMotFilePath = os.path.join(working_folder, mcad_name + ".mot") outputDir = os.path.join(working_folder, "thermal_twinbuilder_" + mcad_name) .. GENERATED FROM PYTHON SOURCE LINES 1073-1074 Create the e8 input file if it does not exist already. .. GENERATED FROM PYTHON SOURCE LINES 1074-1080 .. code-block:: Python if Path(inputMotFilePath).exists() == False: motorcad = pymotorcad.MotorCAD() motorcad.load_template("e8") motorcad.save_to_file(inputMotFilePath) motorcad.quit() .. GENERATED FROM PYTHON SOURCE LINES 1081-1085 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. .. GENERATED FROM PYTHON SOURCE LINES 1085-1087 .. code-block:: Python rpms = [200, 500, 1000] .. GENERATED FROM PYTHON SOURCE LINES 1088-1093 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. .. GENERATED FROM PYTHON SOURCE LINES 1093-1095 .. code-block:: Python airgapTemps = [40, 50, 65] .. GENERATED FROM PYTHON SOURCE LINES 1096-1101 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. .. GENERATED FROM PYTHON SOURCE LINES 1101-1103 .. code-block:: Python housingAmbientTemps = temperaturesHousingAmbient() .. rst-class:: sphx-glr-script-out .. code-block:: none 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]} .. GENERATED FROM PYTHON SOURCE LINES 1104-1107 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. .. GENERATED FROM PYTHON SOURCE LINES 1107-1115 .. code-block:: Python coolingSystemsInputs = { "Housing Water Jacket": { "rpm": rpms, "FR": [9.7695e-05, 0.000103122499999999, 0.00010855, 0.0001139775, 0.000119405], "inletTemp": [40, 65], } } .. GENERATED FROM PYTHON SOURCE LINES 1116-1118 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. .. GENERATED FROM PYTHON SOURCE LINES 1118-1120 .. code-block:: Python MotorCADTwin = MotorCADTwinModel(inputMotFilePath, outputDir) .. rst-class:: sphx-glr-script-out .. code-block:: none 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 .. GENERATED FROM PYTHON SOURCE LINES 1121-1123 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. .. GENERATED FROM PYTHON SOURCE LINES 1123-1131 .. code-block:: Python MotorCADTwin.generateTwinData( parameters={"rpm": rpms}, housingAmbientTemperatures=housingAmbientTemps, airgapTemperatures=airgapTemps, coolingSystemsInputs=coolingSystemsInputs, ) .. image-sg:: /examples/links/images/sphx_glr_thermal_twinbuilder_001.png :alt: thermal twinbuilder :srcset: /examples/links/images/sphx_glr_thermal_twinbuilder_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none 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 .. GENERATED FROM PYTHON SOURCE LINES 1132-1150 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. .. image:: ../../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. .. image:: ../../images/Thermal_Twinbuilder_TwinBuilderROM.png .. seealso:: For informtation on how to use the *Motor-CAD ROM* component, please consult the Twin Builder Help Manual. .. rst-class:: sphx-glr-timing **Total running time of the script:** (16 minutes 9.939 seconds) .. _sphx_glr_download_examples_links_thermal_twinbuilder.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: thermal_twinbuilder.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: thermal_twinbuilder.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: thermal_twinbuilder.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_