5. BSPM Evaluation Tutorial

  • Goal: Leverage capabilites of mach_eval to perform multi-physics evaluations on bearingless surface permanent magnet machines

  • Complexity: 4/5

  • Estimated Time: 30 - 60 min

This tutorial demonstrates how to perform a multi-physics evaluation of a BSPM_Machine by using the Analyzers available in eMach. By the end of this tutorial you will be able to:

  • create a digital BSPM design

  • use mach_eval analyzers together to perform multi-physics evaluations of BSPMs

5.1. Requirements

  1. Python packages installed on system per Pre-requisites

  2. Installation of JMAG v19 or above

  3. Personal repo using eMach as submodule established (see Rectangle Tutorial)

  4. (Recommended but not required) Review of mechanical and electromagnetic analyzers provided in mach_eval

5.2. Step 1: Create a BSPM Design

In the root folder of your repository, create a Python file named ecce_2020_bspm.py. The code required to create an example BSPM design will reside within this file. You can create this file by simply copying the ecce_2020_bspm.py file residing within examples/mach_eval_examples/bspm_eval and updating the import statements as shown below.

import os
import sys
from eMach.mach_eval.machines.materials.electric_steels import Arnon5
from eMach.mach_eval.machines.materials.jmag_library_magnets import N40H
from eMach.mach_eval.machines.materials.miscellaneous_materials import (
    CarbonFiber,
    Steel,
    Copper,
    Hub,
    Air,
)
from eMach.mach_eval.machines.bspm import BSPM_Machine
from eMach.mach_eval.machines.bspm.bspm_oper_pt import BSPM_Machine_Oper_Pt

5.3. Step 2: Create BSPM Evaluator

The main purpose of this tutorial is to showcase how multi-physics evaluations are handled by eMach using the analyzers provided within the repository. To demonstrate this, the structural, electromagnetic, and thermal performance of the example BSPM design created in ecce_2020_bspm.py are evaluated. AnalysisStep objects are created for each analyzer to ensure proper interface to and from the analyzers. The AnalysisSteps are then joined together to create an Evaluator, in a manner so as to ensure proper transfer of information and to facilitate computational efficiency during optimization. In this particular example, the thermal analyzers come after the electromagnetic analyzers as thermal performance is determined by motor losses. The structural analyzer is placed at the very beginning as this is a quick equation-based analyzer which determines whether an appropriate sized sleeve can be placed on an SPM rotor or not. Machines which are structurally unsound can be discarded prior to the computationally intensive electromagnetic AnalysisStep, thereby saving time. The steps involved in created a multi-physics BSPM Evaluator are discussed below. Readers can create a single Python script holding all the code snippets provided below or can separate them out into individual modules as done in examples/mach_eval_examples/bspm_eval.

5.3.1. Step 2.1: Create Structual AnalysisStep

In this step, we define the interface between a BSPM design and the SPM rotor retaining sleeve analyzer. The purpose of this analyzer is to determine if a retaining sleeve of sufficient strength can be applied on the SPM rotor to hold the magnets in place at rated speed. The code snip provided below shows how this analyzer can be interfaced to a BSPM design. After the analysis is completed, the design is either discarded if the analyzer was unable to find an appropriately sized sleeve, or a clone of the BSPM_Machine is created having the sleeve size required for magnet retention.

from copy import deepcopy

from eMach.mach_eval.analyzers.mechanical import rotor_structural as stra
from eMach.mach_eval import AnalysisStep, ProblemDefinition
from eMach.mach_opt import InvalidDesign

############################ Define Struct AnalysisStep ######################
stress_limits = {
    "rad_sleeve": -100e6,
    "tan_sleeve": 1300e6,
    "rad_magnets": 0,
    "tan_magnets": 80e6,
}

struct_ana = stra.SPM_RotorSleeveAnalyzer(stress_limits)

class MySleeveProblemDef(ProblemDefinition):
    def get_problem(state):
        design = state.design
        material_dict = {}
        for key, value in design.machine.rotor_iron_mat.items():
            material_dict[key] = value
        for key, value in design.machine.magnet_mat.items():
            material_dict[key] = value
        for key, value in design.machine.rotor_sleeve_mat.items():
            material_dict[key] = value
        for key, value in design.machine.shaft_mat.items():
            material_dict[key] = value

        r_sh = design.machine.r_sh
        r_ro = design.machine.r_ro
        d_m = design.machine.d_m
        N = design.settings.speed
        deltaT = design.settings.rotor_temp_rise

        problem = stra.SPM_RotorSleeveProblem(r_sh, d_m, r_ro, deltaT, material_dict, N)
        return problem

class MyStructPostAnalyzer:
    """Converts a State into a problem"""

    def get_next_state(results, in_state):
        if results is False:
            raise InvalidDesign("Suitable sleeve not found")
        else:
            print("Suitable sleeve found!")
            machine = in_state.design.machine
            new_machine = machine.clone(dimensions_dict={"d_sl": results[0]})
        state_out = deepcopy(in_state)
        state_out.design.machine = new_machine
        return state_out

struct_step = AnalysisStep(MySleeveProblemDef, struct_ana, MyStructPostAnalyzer)

Note

If you get stuck at any point of the tutorial, the examples/mach_eval_examples/bspm_eval folder provides a working example of creating and evaluating the BSPM design discussed in this tutorial.

5.3.2. Step 2.2: Create Electromagnetic AnalysisStep

In this step, we define the interface between a BSPM design and the BSPM JMAG 2D FEA Analyzer. The purpose of this analyzer is to run a JMAG 2D FEA simulation of an input BSPM machine and return data relevant to the performance of this machine. The input provided to this analyzer is the BSPM machine and its operating point. The analyzer returns a set of dataframes extracted from JMAG 2D FEA solve which can be interpreted to determine the motor losses and torque and force performance. As interpreting this information can be challenging, readers are advised to copy the bpsm_em_post_analyzer.py script file in examples/mach_eval_examples/bspm_eval to post-process FEA results. Simply modify the import statements as shown below.

import copy
import numpy as np

from mach_eval.analyzers.force_vector_data import (
    ProcessForceDataProblem,
    ProcessForceDataAnalyzer,
)
from mach_eval.analyzers.torque_data import (
    ProcessTorqueDataProblem,
    ProcessTorqueDataAnalyzer,
)

The code snip provided below shows how this analyzer can be interfaced to a BSPM design. After the analysis is completed, relevant information is stored in State for future reference. It is worth noting that the losses obtained from this analysis are required by the next two thermal AnalysisSteps to determine the rotor and stator temperatures.

from eMach.mach_eval.analyzers.electromagnetic.bspm import jmag_2d as em
from eMach.mach_eval.analyzers.electromagnetic.bspm.jmag_2d_config import JMAG_2D_Config
from eMach.examples.mach_eval_examples.bspm_eval.bpsm_em_post_analyzer import BSPM_EM_PostAnalyzer
from eMach.mach_eval import AnalysisStep, ProblemDefinition


############################ Define EMAnalysisStep ###########################
class BSPM_EM_ProblemDefinition(ProblemDefinition):
    """Converts a State into a problem"""

    def __init__(self):
        pass

    def get_problem(state):
        problem = em.BSPM_EM_Problem(state.design.machine, state.design.settings)
        return problem


# initialize em analyzer class with FEA configuration
jmag_config = JMAG_2D_Config(
    no_of_rev_1TS=3,
    no_of_rev_2TS=0.5,
    no_of_steps_per_rev_1TS=8,
    no_of_steps_per_rev_2TS=64,
    mesh_size=4e-3,
    magnet_mesh_size=2e-3,
    airgap_mesh_radial_div=5,
    airgap_mesh_circum_div=720,
    mesh_air_region_scale=1.15,
    only_table_results=False,
    csv_results=(r"Torque;Force;FEMCoilFlux;LineCurrent;TerminalVoltage;JouleLoss;TotalDisplacementAngle;"
                "JouleLoss_IronLoss;IronLoss_IronLoss;HysteresisLoss_IronLoss"),
    del_results_after_calc=False,
    run_folder=os.path.dirname(__file__) + "/run_data/",
    jmag_csv_folder=os.path.dirname(__file__) + "/run_data/JMAG_csv/",
    max_nonlinear_iterations=50,
    multiple_cpus=True,
    num_cpus=4,
    jmag_scheduler=False,
    jmag_visible=False,
    jmag_version=None,
)
em_analysis = em.BSPM_EM_Analyzer(jmag_config)
# define AnalysysStep for EM evaluation
em_step = AnalysisStep(BSPM_EM_ProblemDefinition, em_analysis, BSPM_EM_PostAnalyzer)

5.3.3. Step 2.3: Create Rotor Thermal AnalysisStep

In this step, we define the interface between a BSPM design and the SPM Airflow Analyzer. The purpose of this analyzer is evaluate the airflow required (under certain bounds) to prevent the BSPM machine magnets from overheating at the provided operating conditions. The code snip provided below shows how this analyzer can be interfaced to a BSPM design and the loss data obtained from FEA. After the analysis is completed, the design is discarded if the magnets get overheated. If the magnets are not at risk of de-magnetization from high temperatures, the required airflow and the corresponding magnet temperature rises are saved for future reference.

from copy import deepcopy
import numpy as np

from eMach.mach_eval.analyzers.mechanical import rotor_thermal as therm
from eMach.mach_eval import AnalysisStep, ProblemDefinition
from eMach.mach_opt import InvalidDesign


###################### Define Rotor Thermal AnalysisStep #####################
class MyAirflowProblemDef(ProblemDefinition):
    def get_problem(state):
        design = state.design
        material_dict = {}
        for key, value in design.machine.rotor_iron_mat.items():
            material_dict[key] = value
        for key, value in design.machine.magnet_mat.items():
            material_dict[key] = value
        for key, value in design.machine.rotor_sleeve_mat.items():
            material_dict[key] = value
        for key, value in design.machine.shaft_mat.items():
            material_dict[key] = value
        for key, value in design.machine.air_mat.items():
            material_dict[key] = value
        for key, value in design.machine.rotor_hub.items():
            material_dict[key] = value

        r_sh = design.machine.r_sh
        d_ri = design.machine.d_ri
        r_ro = design.machine.r_ro
        d_sl = design.machine.d_sl
        r_si = design.machine.r_si
        l_st = design.machine.l_st
        l_hub = 3e-3
        T_ref = design.settings.ambient_temp
        omega = design.settings.speed * 2 * np.pi / 60
        losses = state.conditions.em
        rotor_max_temp = material_dict["magnet_max_temperature"]
        prob = therm.AirflowProblem(
            r_sh=r_sh,
            d_ri=d_ri,
            r_ro=r_ro,
            d_sl=d_sl,
            r_si=r_si,
            l_st=l_st,
            l_hub=l_hub,
            T_ref=T_ref,
            losses=losses,
            omega=omega,
            max_temp=rotor_max_temp,
            mat_dict=material_dict,
        )
        return prob


class MyAirflowPostAnalyzer:
    """Converts a State into a problem"""

    def get_next_state(results, in_state):
        if results["valid"] is False:
            raise InvalidDesign("Magnet temperature beyond limits")
        else:
            state_out = deepcopy(in_state)
            state_out.conditions.airflow = results
        print("Magnet temperature is ", results["magnet Temp"])
        print("Required airflow is ", results["Required Airflow"])
        return state_out


rotor_therm_step = AnalysisStep(
    MyAirflowProblemDef, therm.AirflowAnalyzer(), MyAirflowPostAnalyzer
)

5.3.4. Step 2.4: Create Stator Thermal AnalysisStep

In this step, we define the interface between a BSPM design and the Stator Thermal Analyzer. The purpose of this analyzer is evaluate the stator winding temperature for a provided stator outer bore convection coefficient. The cooling rate is set at h = 200 W/m^2K for this evaluation. The code snip provided below shows how this analyzer can be interfaced to a BSPM design and the loss data obtained from FEA. After the analysis is completed, the stator winding and yoke temperatures are saved for future reference if the coil temperature is under 300 degC.

from copy import deepcopy
import numpy as np

from mach_eval.analyzers.mechanical import thermal_stator as st_therm
from mach_eval import AnalysisStep, ProblemDefinition
from mach_opt import InvalidDesign


###################### Define Stator Thermal AnalysisStep #####################
class MyThermalProblemDefinition(ProblemDefinition):
    """Class converts input state into a problem"""

    def get_problem(state):
        """Returns Problem from Input State"""
        # TODO define problem definition
        g_sy = state.conditions.g_sy  # Volumetric loss in Stator Yoke [W/m^3]
        g_th = state.conditions.g_th  # Volumetric loss in Stator Tooth [W/m^3]
        w_st = state.design.machine.w_st  # Tooth width [m]
        l_st = state.design.machine.l_st  # Stack length [m]
        r_sy = state.design.machine.r_so - state.design.machine.d_sy
        alpha_q = 2 * np.pi / state.design.machine.Q  # [rad]
        r_so = state.design.machine.r_so  # outer stator radius [m]

        k_ins = 1  # thermal insulation conductivity (~1)
        w_ins = 0.5e-3  # insulation thickness [m] (.5mm)
        k_fe = state.design.machine.stator_iron_mat["core_therm_conductivity"]
        h = 200  # convection co-eff W/m^2K
        alpha_slot = alpha_q - 2 * np.arctan(
            w_st / (2 * r_sy)
        )  # span of back of stator slot [rad]
        T_ref = 20  # temperature of cooling liquid [K]

        r_si = state.design.machine.r_si  # inner stator radius
        Q_coil = state.conditions.Q_coil  # ohmic loss per coil
        h_slot = 0  # in slot convection coeff [W/m^2K] set to 0

        problem = st_therm.StatorThermalProblem(
            g_sy=g_sy,
            g_th=g_th,
            w_tooth=w_st,
            l_st=l_st,
            alpha_q=alpha_q,
            r_si=r_si,
            r_so=r_so,
            r_sy=r_sy,
            k_ins=k_ins,
            w_ins=w_ins,
            k_fe=k_fe,
            h=h,
            alpha_slot=alpha_slot,
            Q_coil=Q_coil,
            h_slot=h_slot,
            T_ref=T_ref,
        )
        return problem


class MyStatorThermalPostAnalyzer:
    """Converts input state into output state for TemplateAnalyzer"""

    def get_next_state(results, stateIn):
        if results["Coil temperature"] > 300 == True:
            raise InvalidDesign("Coil temperature beyond limits")
        else:
            stateOut = deepcopy(stateIn)
            stateOut.conditions.T_coil = results["Coil temperature"]
            stateOut.conditions.T_sy = results["Stator yoke temperature"]

        print("Coil Temp is ", results["Coil temperature"])
        print("Stator Temp is ", results["Stator yoke temperature"])
        return stateOut


stator_therm_step = AnalysisStep(
    MyThermalProblemDefinition,
    st_therm.StatorThermalAnalyzer(),
    MyStatorThermalPostAnalyzer,
)

5.3.5. Step 2.5: Create Windage Loss AnalysisStep

Finally, an AnalysisStep is created to define the interface between the BSPM design and the Windage Loss Analyzer. The purpose of this analyzer is evaluate the windage loss arising in a BSPM due to rotational speed of the machine. The code snip provided below shows how this analyzer can be interfaced to a BSPM design and the required rotor axial airflow for cooling the magnets. After the analysis is completed, the overall efficiency of the motor is calculated and saved.

from copy import deepcopy
import numpy as np

# add the directory 3 levels above this file's directory to path for module import
sys.path.append(os.path.dirname(__file__)+"../../..")

from mach_eval.analyzers.mechanical import windage_loss as wl
from mach_eval import AnalysisStep, ProblemDefinition


############################ Define Windage AnalysisStep #####################
class MyWindageProblemDef(ProblemDefinition):
    def get_problem(state):
        design = state.design
        omega = design.settings.speed * 2 * np.pi / 60
        r_ro = design.machine.r_ro + design.machine.d_sl
        l_st = design.machine.l_st
        r_si = design.machine.r_si
        m_dot_air = state.conditions.airflow["Required Airflow"]
        T_air = design.settings.ambient_temp

        prob = wl.WindageLossProblem(omega, r_ro, l_st, r_si, m_dot_air, T_air)
        return prob


class MyWindageLossPostAnalyzer:
    """Converts a State into a problem"""

    def get_next_state(results, in_state):
        state_out = deepcopy(in_state)
        omega = state_out.design.settings.speed * 2 * np.pi / 60
        Pout = state_out.conditions.em["torque_avg"] * omega
        eff = (
            100
            * Pout
            / (
                Pout
                + results[0]
                + results[1]
                + results[2]
                + state_out.conditions.em["copper_loss"]
                + state_out.conditions.em["rotor_iron_loss"]
                + state_out.conditions.em["stator_iron_loss"]
                + state_out.conditions.em["magnet_loss"]
            )
        )
        state_out.conditions.windage = {"loss": results, "efficiency": eff}
        print("Efficiency is ", eff)
        return state_out

windage_step = AnalysisStep(
    MyWindageProblemDef, wl.WindageLossAnalyzer, MyWindageLossPostAnalyzer
)

5.3.6. Step 2.6: Create the Multi-Physics Evaluator

Simply merge all the AnalysisStep s in the order in which they were defined above to create the Evaluator. The code provided below assumes each AnalysisStep was defined in a separate Python file / module. Readers are advised to name this file bspm_evaluator.py.

from mach_eval import MachineEvaluator
from examples.mach_eval_examples.bspm_eval.structural_step import struct_step
from examples.mach_eval_examples.bspm_eval.electromagnetic_step import em_step
from examples.mach_eval_examples.bspm_eval.rotor_thermal_step import rotor_therm_step
from examples.mach_eval_examples.bspm_eval.stator_thermal_step import stator_therm_step
from examples.mach_eval_examples.bspm_eval.windage_loss_step import windage_step

############################ Create Evaluator ########################
bspm_evaluator = MachineEvaluator(
    [
        struct_step,
        em_step,
        rotor_therm_step,
        stator_therm_step,
        windage_step,
    ]
)

5.4. Step 3: Evaluate BSPM Design

To evaluate the BSPM machine created in Step 1 at the defined operating point, we need to instantiate a MachineDesign object and pass it as an argument to the evaluate method of the bspm_evaluator created in the preceding step. The code below is provided in a manner such that the BSPM design is evaluated only when users try to run the bspm_evaluator.py file.

if __name__ == "__main__":
    from ecce_2020_bspm import ecce_2020_machine, ecce_2020_op_pt
    from mach_eval import MachineDesign

    ecce_2020_design = MachineDesign(ecce_2020_machine, ecce_2020_op_pt)
    results = bspm_evaluator.evaluate(ecce_2020_design)

Upon running this script you should get the following results:

  • Suitable sleeve found! Thickness = 0.00093 m

  • Torque = 0.33 Nm

  • Torque Density = 70260.15 Nm/m3

  • Power = 5519.5 W

  • Force = 0.63 N

  • Force per rotor wieght = 1.81 pu

  • Force error angle = 1.06 deg

  • Magnet temperature = 54.7 degC

  • Coil temperature = 169.5 degC

  • Stator temperature = 161.5 degC

  • Efficiency = 97.2%

Note

The examples/mach_eval_examples/bspm_eval folder provides working code for the tutorial discussed here. You can run the bspm_evaluator.py script here to evaluate the ecce_2020_design and compare the results against those obtained from your own Evaluator.

5.5. Conclusion

Congratulations! You have successfully used eMach to create a digital BSPM design and a multi-physics BSPM evaluator as well! You can now attempt evaluating other BSPM designs using this evaluator and see what results you end up with.