From 8b5fa72eac7f97a289204859f3743591ec093afd Mon Sep 17 00:00:00 2001 From: Anson Chan Date: Wed, 12 Jul 2023 16:41:12 -0500 Subject: [PATCH 01/15] Initial commit --- .../BSPMMachineConstantsAnalyzer.py | 493 ++++++++++++++++++ 1 file changed, 493 insertions(+) create mode 100644 mach_eval/analyzers/electromagnetic/bspm/machine_constant/BSPMMachineConstantsAnalyzer.py diff --git a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/BSPMMachineConstantsAnalyzer.py b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/BSPMMachineConstantsAnalyzer.py new file mode 100644 index 00000000..8fdc453f --- /dev/null +++ b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/BSPMMachineConstantsAnalyzer.py @@ -0,0 +1,493 @@ +import numpy as np +import os +from tqdm.autonotebook import tqdm +import pandas as pd +import win32com.client + +from eMach.mach_eval.analyzers.force_vector_data import ( + ProcessForceDataProblem, + ProcessForceDataAnalyzer, +) +from eMach.mach_eval.analyzers.torque_data import ( + ProcessTorqueDataProblem, + ProcessTorqueDataAnalyzer, +) + +# change current working directory to file location +os.chdir(os.path.dirname(__file__)) +# add the directory immediately above this file's directory to path for module import +#sys.path.append("../../..") + +class BSPMMachineConstantAnalyzer(): + """Analyzer for determining machine constants of BSPM in JMAG. + + Attributes: + project_name (str): .jproj JMAG file to evaluate. + """ + def __init__(self, project_name:str) -> None: + self.project_name = project_name + + # open .jproj file and obtain initial model and study properties + toolJd = self._open_JMAG_file() + init_model,init_study,init_study_name,init_properties = self._get_init_study(toolJd) + + self.toolJd = toolJd + self.init_model = init_model + self.init_study = init_study + self.init_study_name = init_study_name + self.init_properties = init_properties + + # set result csv path + self.csv_path = self._set_csv_result_path() + + print("Project File Name: " + project_name+".jproj") + print("Initial Model Name: " + init_model.GetName()) + print("Initial Study Name: " + init_study_name) + + ###################################################################################### + #################################### Kf Kt methods ################################### + ###################################################################################### + + def get_Kf_Kt(self, Iq_pu:list, I_rms:float, angle_step:int=1, rev:int=3, rev_ignore:int=1): + """Run JMAG simulation and analyze force and torque data to obtain Kf and Kt value. + + Args: + Iq_pu (list): list of Iq per unit values to evaluate. Values should be between 0-1. + I_rms (float): machine rated RMS current. + angle_step (int, optional): step size of rotational angle. Defaults to 1. + rev (int, optional): number of revolution rotor will take in the simulation. Defaults to 3. + rev_ignore (int, optional): number of initial revolutions to ignore from simulation to ignore eddy current effect. Defaults to 1. + + Returns: + Kf, Kt + """ + + # Run simulations to obtain necessary data + force_df_list, torque_df_list = self.run_Kf_Kt_simulations(Iq_pu, I_rms, angle_step, rev) + + # calculate the number of initial data point to ignore + idx_ignore = int(360/angle_step*rev_ignore) + print(idx_ignore) + + # extract average force value from each run + force = [] + for force_df in force_df_list: + force_prob = ProcessForceDataProblem( + Fx=force_df["ForCon:1st"].iloc[idx_ignore:].to_numpy(), + Fy=force_df["ForCon:2nd"].iloc[idx_ignore:].to_numpy()) + force_ana = ProcessForceDataAnalyzer() + _,_,f_abs_avg,_,_ = force_ana.analyze(force_prob) + force.append(f_abs_avg) + + # extract average torque value from each run + torque = [] + for torque_df in torque_df_list: + torque_prob = ProcessTorqueDataProblem(torque_df["TorCon"].iloc[idx_ignore:].to_numpy()) + torque_analyzer = ProcessTorqueDataAnalyzer() + torque_avg,_ = torque_analyzer.analyze(torque_prob) + torque.append(torque_avg) + + # get fitted slope value + Iq = [np.sqrt(2)*I_rms*Iq_pu_val for Iq_pu_val in Iq_pu] + Kt,_ = np.polyfit(Iq,torque,deg=1) + + Is = [np.sqrt(2)*I_rms*(1-Iq_pu_val) for Iq_pu_val in Iq_pu] + Kf,_ = np.polyfit(Is,force,deg=1) + + print("Kt = "+str(Kt)) + print("Kf = "+str(Kf)) + return Kt, Kf + + def run_Kf_Kt_simulations(self, Iq_pu:list, I_rms:float, angle_step:int, rev:int): + """ + Run simulations in JMAG to obtain data used for determining Kf and Kt constant. + + Args: + Iq_pu (list): list of Iq per unit values to evaluate. Values should be between 0-1. + I_rms (float): machine rated RMS current. + angle_step (int): step size of rotational angle. + rev (int): number of revolution rotor will take in the simulation. + + Returns: + force_df_list, torque df_list (tuple): force and torque dataframe lists. + """ + print("Running force Kf and torqe Kt simulations...") + print("Angle_Step = "+str(angle_step)) + print("Number of revolution = "+str(rev)) + + # iterate through each specified coordinates + force_df_list = [] + torque_df_list = [] + tqdm_Iq_pu = tqdm(Iq_pu, desc='') + for idx, Iq_pu_val in enumerate(tqdm_Iq_pu): + tqdm_Iq_pu.set_description("Run "+ str(idx+1)+"/"+str(len(Iq_pu)) + +" , Iq_pu = "+str(round(Iq_pu_val,2))) + + # duplicate initial study + self.init_model.DuplicateStudyName(self.init_study_name, + self.init_study_name+'_Kf_Kt' + +'_Iq_'+str(round(Iq_pu_val,2)).replace(".", "_") + +'_Is_'+str(round(1-Iq_pu_val, 2)).replace(".", "_"),True) + + # set duplicated study as present study + present_study = self.toolJd.GetCurrentStudy() + + # rotor should rotate one full revolution over 1 second + # 1 rev/s = 60 rev/min + speed = 60 + self._set_study_speed(present_study,speed) + excitation_freq = self._get_elec_freq(present_study,speed) + + # set study step control + self._set_study_steps(present_study,angle_step,rev) + + # obtain handle to circuit for present study + circuit = present_study.GetCircuit() + + # set torque and suspension currents for all coils + ampT = 2*Iq_pu_val*I_rms*np.sqrt(2) + ampS = (1-Iq_pu_val)*I_rms*np.sqrt(2) + + func = self.toolJd.FunctionFactory().Composite() + f1 = self.toolJd.FunctionFactory().Sin(ampT, excitation_freq, 0) + # "freq" variable cannot be used here. So pay extra attension when you create new case of a different freq. + func.AddFunction(f1) + circuit.GetComponent("CS_t-1").SetFunction(func) + + func = self.toolJd.FunctionFactory().Composite() + f1 = self.toolJd.FunctionFactory().Sin(ampT, excitation_freq, -120) + func.AddFunction(f1) + circuit.GetComponent("CS_t-2").SetFunction(func) + + func = self.toolJd.FunctionFactory().Composite() + f1 = self.toolJd.FunctionFactory().Sin(ampT, excitation_freq, -240) + func.AddFunction(f1) + circuit.GetComponent("CS_t-3").SetFunction(func) + + func = self.toolJd.FunctionFactory().Composite() + f1 = self.toolJd.FunctionFactory().Sin(ampS, excitation_freq, 0) + f2 = self.toolJd.FunctionFactory().Sin(-ampT / 2, excitation_freq, 0) + func.AddFunction(f1) + func.AddFunction(f2) + circuit.GetComponent("CS_s-1").SetFunction(func) + + func = self.toolJd.FunctionFactory().Composite() + f1 = self.toolJd.FunctionFactory().Sin(ampS, excitation_freq, 120) + f2 = self.toolJd.FunctionFactory().Sin(-ampT / 2, excitation_freq, -120) + func.AddFunction(f1) + func.AddFunction(f2) + circuit.GetComponent("CS_s-2").SetFunction(func) + + func = self.toolJd.FunctionFactory().Composite() + f1 = self.toolJd.FunctionFactory().Sin(ampS, excitation_freq, 240) + f2 = self.toolJd.FunctionFactory().Sin(-ampT / 2, excitation_freq, -240) + func.AddFunction(f1) + func.AddFunction(f2) + circuit.GetComponent("CS_s-3").SetFunction(func) + + # run the study + present_study.RunAllCases() + + # extract FEA results from CSV + force_df = self._extract_csv_results(present_study.GetName(), "Force") + torque_df = self._extract_csv_results(present_study.GetName(),"Torque") + force_df_list.append(force_df) + torque_df_list.append(torque_df) + + self.force_df_list = force_df_list + return force_df_list, torque_df_list + + ###################################################################################### + #################################### Kdelta methods ################################## + ###################################################################################### + + def get_Kdelta(self, coordinates:list, angle_step:int=1, rev:int=3, rev_ignore:int=1): + """Run JMAG simulation and analyze force and displacement data to obtain Kdelta value. + + Args: + coordinates (list): list of coordinates to evaluate in simulation + angle_step (int, optional): step size of rotational angle. Defaults to 1. + rev (int, optional): number of revolution rotor will take in the simulation. Defaults to 1. + rev_ignore (int, optional): number of initial revolutions to ignore from simulation to ignore eddy current effect. Defaults to 1. + + Returns: + _type_: _description_ + """ + # run simualtions on all specified coordinates + force_df_list = self.run_Kdelta_simulations(coordinates,angle_step,rev) + + # calculate the number of initial data point to ignore + idx_ignore = int(360/angle_step*rev_ignore) + + force = [] + for force_df in force_df_list: + force_prob = ProcessForceDataProblem( + Fx=force_df["ForCon:1st"].iloc[idx_ignore:].to_numpy(), + Fy=force_df["ForCon:2nd"].iloc[idx_ignore:].to_numpy()) + force_ana = ProcessForceDataAnalyzer() + _,_,f_abs_avg,_,_ = force_ana.analyze(force_prob) + force.append(f_abs_avg) + disp = [np.linalg.norm(coord) for coord in coordinates] + + # combine and sort force and displacement data and unzip into + # two seperate list after sorting + disp, force = (list(t) for t in zip(*sorted(zip(disp,force)))) + + # get fitted slope value + K_delta,_ = np.polyfit(disp,force,deg=1) + + return K_delta + + def run_Kdelta_simulations(self, coordinates:list, angle_step:int, rev:int): + """Run simulations in JMAG to obtain data used for determining Kdelta constant""" + print("Running Kdelta rotor displacement simulations...") + print("Angle_Step = "+str(angle_step)) + print("Number of revolution = "+str(rev)) + + # determine if model is 2D or 3D + is_2D = self._is_2D_model(self.init_model) + + force_df_list = [] + tqdm_coord = tqdm(coordinates, desc='') + # iterate through each specified coordinates + for idx, coord in enumerate(tqdm_coord): + tqdm_coord.set_description("Run " + str(idx+1)+"/"+str(len(coordinates)) + +" , Co-ord = "+str(coord)) + x_coord = coord[0] + y_coord = coord[1] + + # set rotor offset depending on model type + if is_2D: + present_study = self._create_Kdelta_2D_study(x=x_coord,y=y_coord) + else: + present_study = self._create_Kdelta_3D_study(x=x_coord,y=y_coord) + + # set study step control + # rotor should rotate one full revolution over 1 second + self._set_study_steps(present_study, angle_step, rev) + + # set rotor angular velocity + # 60 RPM = 1 rev/sec + speed = 60 + self._set_study_speed(present_study, speed) + + # obtain handle to circuit for present study + circuit = present_study.GetCircuit() + + # zero current for all coils + function1 = self.toolJd.FunctionFactory().Constant(0) + circuit.GetComponent("CS_t-1").SetFunction(function1) + circuit.GetComponent("CS_t-2").SetFunction(function1) + circuit.GetComponent("CS_t-3").SetFunction(function1) + circuit.GetComponent("CS_s-1").SetFunction(function1) + circuit.GetComponent("CS_s-2").SetFunction(function1) + circuit.GetComponent("CS_s-3").SetFunction(function1) + + # run the study + present_study.RunAllCases() + + # extract FEA results from CSV + force_df = self._extract_csv_results(present_study.GetName(),"Force") + force_df_list.append(force_df) + + self.force_df_list = force_df_list + return force_df_list + + def _create_Kdelta_2D_study(self,x,y): + """Offset rotor by enabling eccentricity setting under RotCon condition""" + + # duplicate initial study + # replace all decimal in strings with underscore to avoid windows file naming error + self.init_model.DuplicateStudyName(self.init_study_name, + self.init_study_name+'_Kdelta' + +'_X'+str(x).replace(".", "_") + +'_Y'+str(y).replace(".", "_"),True) + + present_study = self.toolJd.GetCurrentStudy() + present_study.GetCondition(0).SetValue("UseEccentricity", 1) + present_study.GetCondition(0).SetXYZPoint("PartOffset",x,y,0) + present_study.GetCondition(0).SetXYZPoint("AxisOffset",x,y,0) + return present_study + + def _create_Kdelta_3D_study(self,x,y): + """Offset rotor by repositioning rotor in GeometryEditor""" + + # Open Geometry Editior and offset rotor from stator center + self.toolJd.SetCurrentModel(0) + self.init_model.RestoreCadLink() + geomApp = self.toolJd.CreateGeometryEditor() + + # select rotor parts + shaft = geomApp.GetDocument().GetAssembly().GetItem("Shaft") + rotmag = geomApp.GetDocument().GetAssembly().GetItem("RotorMagnet") + rotorCore = geomApp.GetDocument().GetAssembly().GetItem("NotchedRotor") + geomApp.GetDocument().GetSelection().Add(rotorCore) + geomApp.GetDocument().GetSelection().Add(shaft) + geomApp.GetDocument().GetSelection().Add(rotmag) + + # translate rotor + translation = geomApp.GetDocument().GetAssemblyManager().CreateMovePartParameter() + translation.SetProperty("MoveX", x) + translation.SetProperty("MoveY", y) + geomApp.GetDocument().GetAssemblyManager().Execute(translation) + + # update model and close geometry editor + # everytime model is updated, new model is created + self.toolJd.GetCurrentModel().UpdateCadModel() + geomApp.SaveCurrent() + geomApp.Quit() + + # set current study and renanme + present_study = self.toolJd.GetCurrentStudy() + present_study.SetName(self.init_study_name+'_displacement' + +'_X'+str(x).replace(".", "_") + +'_Y'+str(y).replace(".", "_")) + present_model = self.toolJd.GetCurrentModel() + present_model.SetName(self.init_study_name+'_displacement' + +'_X'+str(x).replace(".", "_") + +'_Y'+str(y).replace(".", "_")) + + # align axis of rotation to center of shaft + present_study.GetCondition("RotCon").SetXYZPoint("Origin", x, y, 1) + present_study.GetCondition("TorCon").SetXYZPoint("Origin", x, y, 1) + + # create face set for creating symmetry boundary + present_model.GetSetList().CreateFaceSet("Symmetry_Face_bottom") + symmetry_face1 = present_model.GetSetList().GetSet("Symmetry_Face_bottom") + symmetry_face1.SetUpdateByRelation(False) + symmetry_face1.SetMatcherType("OnPlane") + symmetry_face1.SetXYZPoint("direction", 0, 0, 1) + symmetry_face1.SetXYZPoint("origin", 0, 0, 0) + symmetry_face1.SetParameter("tolerance", 1e-6) + symmetry_face1.Rebuild() + + present_model.GetSetList().CreateFaceSet("Symmetry_Face_top") + symmetry_face2 = present_model.GetSetList().GetSet("Symmetry_Face_top") + symmetry_face2.SetUpdateByRelation(False) + symmetry_face2.SetMatcherType("OnPlane") + symmetry_face2.SetXYZPoint("direction", 0, 0, 1) + # FIND way to get coil height + symmetry_face2.SetXYZPoint("origin", 0, 0, 22.5) + symmetry_face2.SetParameter("tolerance", 1e-6) + symmetry_face2.Rebuild() + + # create new symmetry boundary condition + present_study.CreateCondition("SymmetryBoundary", "SymBound") + sym_bound = present_study.GetCondition("SymBound") + sym_bound.ClearParts() + sym_bound.AddSet(symmetry_face1, 0) + sym_bound.AddSet(symmetry_face2, 0) + + return present_study + + + ###################################################################################### + #################################### Kphi methods #################################### + ###################################################################################### + + def get_Kphi(self): + pass + + def run_Kphi_simulations(self): + pass + + ###################################################################################### + + def _get_elec_freq(self,study,speed): + """Calculate electric frequency to acheiev specified speed + + Args: + study (): instance of JMAG study to evaluate + speed (float): speed of rotor in RPM + + Returns: + freq: electric frequency in Hz + """ + + # obtain magnet pole pair value + p = study.GetMaterial("Magnet").GetValue("Poles")/2 + + # convert RPM to Hz + freq = speed/60*p + return freq + + def _set_study_speed(self, study, speed=60): + """Set rotor speed under RotCon""" + study.GetCondition(0).SetValue("AngularVelocity",speed) + + def _set_study_steps(self,study, + angle_step:int, + rev:int): + """Set the number of steps in transient study based on angle step size and number of revolutions. + + Args: + study (): instance of JMAG study to evaluate + angle_step (int): step size for rotational angle in deg + rev (int): number of revolutions to evaluate study + """ + + tran_steps = rev*360/angle_step + study.GetStep().SetValue("Step",tran_steps) + study.GetStep().SetValue("StepType",1) + study.GetStep().SetValue("StepDivision",tran_steps-1) + study.GetStep().SetValue("EndPoint", rev) + + def _is_2D_model(self,model): + """Check if model in JMAG project is 2D""" + if model.GetDimension() == 2: + print("2D model detected...") + return True + else: + print("3D model detected...") + return False + + def _get_init_study(self,toolJd): + """Obtain initial JMAG study and properties""" + toolJd.SetCurrentModel(0) + init_model = toolJd.GetModel(0) + init_study = toolJd.GetStudy(0) + init_study_name = init_study.GetName() + init_properties = init_study.GetStudyProperties() + return init_model,init_study,init_study_name,init_properties + + def _open_JMAG_file(self): + """Open and load specified JMAG file""" + toolJd = win32com.client.Dispatch("designer.Application") + toolJd.Show() + toolJd.load(os.path.dirname(__file__)+"/run_data/"+self.project_name+".jproj") + return toolJd + + def _set_csv_result_path(self): + """Set JMAG .csv result path and create one if not exist""" + csv_path = os.path.dirname(__file__)+"/run_data/"+self.project_name+"_mach_const_results_csv/" + + # create result folder if not exist + if not os.path.exists(csv_path): + os.mkdir(csv_path) + print("CSV result folder created at "+ csv_path) + + self.init_properties.SetValue("CsvOutputPath",csv_path) + return csv_path + + def _extract_csv_results(self, study_name, type:str): + """Extract coil flux linkage data from JMAG output .csv files""" + csv_type_dict = {'Force':'_force.csv', + 'Torque':'_torque.csv', + 'Flux':'_flux_of_fem_coil.csv'} + path = self.csv_path + study_name + csv_type_dict[type] + df = pd.read_csv(path, skiprows=6) + return df + +################################################################## +analyzer = BSPMMachineConstantAnalyzer("BP4") + +coord = [] +for x in np.linspace(-0.3,0.3,3): + for y in np.linspace(-0.3,0.3,3): + coord.append([x,y]) +coord.append([0,0.2]) +coord.append([0,0.4]) + +Iq_pu = np.linspace(0,1,11) +analyzer.get_Kf_Kt(Iq_pu, I_rms = 18) +analyzer.get_Kdelta(coord) \ No newline at end of file From 7ef992499512949ea6a38935234f099195860263 Mon Sep 17 00:00:00 2001 From: Anson Chan Date: Sat, 30 Dec 2023 23:38:02 -0600 Subject: [PATCH 02/15] clean up code --- .../analyzers/electromagnetic/bspm/jmag_2d.py | 13 +- .../bspm/machine_constant/_Kf_methods.py | 131 ++++++ .../machine_constant/bspm_mach_constants.py | 417 ++++++++++++++++++ ...lyzer.py => legacy_bspm_mach_constants.py} | 10 +- test.py | 156 +++++++ 5 files changed, 722 insertions(+), 5 deletions(-) create mode 100644 mach_eval/analyzers/electromagnetic/bspm/machine_constant/_Kf_methods.py create mode 100644 mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py rename mach_eval/analyzers/electromagnetic/bspm/machine_constant/{BSPMMachineConstantsAnalyzer.py => legacy_bspm_mach_constants.py} (98%) create mode 100644 test.py diff --git a/mach_eval/analyzers/electromagnetic/bspm/jmag_2d.py b/mach_eval/analyzers/electromagnetic/bspm/jmag_2d.py index 829f0c78..8b04fe81 100644 --- a/mach_eval/analyzers/electromagnetic/bspm/jmag_2d.py +++ b/mach_eval/analyzers/electromagnetic/bspm/jmag_2d.py @@ -4,11 +4,18 @@ import pandas as pd import sys -from eMach.mach_eval.analyzers.electromagnetic.bspm.electrical_analysis import ( + +from .electrical_analysis import ( CrossSectInnerNotchedRotor as CrossSectInnerNotchedRotor, ) -from eMach.mach_eval.analyzers.electromagnetic.bspm.electrical_analysis import CrossSectStator as CrossSectStator -from eMach.mach_eval.analyzers.electromagnetic.bspm.electrical_analysis.Location2D import Location2D +from .electrical_analysis import CrossSectStator as CrossSectStator +from .electrical_analysis.Location2D import Location2D + +# from eMach.mach_eval.analyzers.electromagnetic.bspm.electrical_analysis import ( +# CrossSectInnerNotchedRotor as CrossSectInnerNotchedRotor, +# ) +# from eMach.mach_eval.analyzers.electromagnetic.bspm.electrical_analysis import CrossSectStator as CrossSectStator +# from eMach.mach_eval.analyzers.electromagnetic.bspm.electrical_analysis.Location2D import Location2D sys.path.append(os.path.dirname(__file__) + "/../../../..") from mach_opt import InvalidDesign diff --git a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/_Kf_methods.py b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/_Kf_methods.py new file mode 100644 index 00000000..f2f3f651 --- /dev/null +++ b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/_Kf_methods.py @@ -0,0 +1,131 @@ +from functools import cached_property, lru_cache + +def testing(self): + return self.Iq_step + +# @cached_property +# def Kf(self): +# # Run simulations to obtain necessary data +# force_df_list, torque_df_list = self.run_Kf_Kt_simulations() + +# # calculate the number of initial data point to ignore +# idx_ignore = int(360/angle_step*rev_ignore) +# print(idx_ignore) + +# # extract average force value from each run +# force = [] +# for force_df in force_df_list: +# force_prob = ProcessForceDataProblem( +# Fx=force_df["ForCon:1st"].iloc[idx_ignore:].to_numpy(), +# Fy=force_df["ForCon:2nd"].iloc[idx_ignore:].to_numpy()) +# force_ana = ProcessForceDataAnalyzer() +# _,_,f_abs_avg,_,_ = force_ana.analyze(force_prob) +# force.append(f_abs_avg) + +# Is = [np.sqrt(2)*I_rms*(1-Iq_pu_val) for Iq_pu_val in Iq_pu] +# Kf,_ = np.polyfit(Is,force,deg=1) + +# return Kf + +# @cached_property +# def Kt(self): +# # extract average torque value from each run +# torque = np.zeros(len(self.torque_df_list)) +# for idx, torque_df in enumerate(self.torque_df_list): +# torque_prob = ProcessTorqueDataProblem(torque_df["TorCon"].iloc[idx_ignore:].to_numpy()) +# torque_analyzer = ProcessTorqueDataAnalyzer() +# torque_avg,_ = torque_analyzer.analyze(torque_prob) +# torque[idx] = torque_avg + +# Iq = [np.sqrt(2)*I_rms*Iq_pu_val for Iq_pu_val in Iq_pu] +# Kt,_ = np.polyfit(Iq,torque,deg=1) + +# return Kt + +# @lru_cache +# def run_Kf_Kt_simulations(self): + +# # use operating point rated current and result for speed +# force_df_list = [] +# torque_df_list = [] + +# Iq_pu_list = np.linspace(0,np.sqrt(2)*self.machine.Rated_current) +# tqdm_Iq = tqdm(self.Iq_list, desc='') +# for idx, Iq_val in enumerate(tqdm_Iq): +# tqdm_Iq.set_description("Run "+ str(idx+1)+"/"+str(len(Iq_val)) +# +" , Iq_pu = "+str(round(Iq_val,2))) + +# # duplicate initial study +# self.init_model.DuplicateStudyName(self.init_study_name, +# self.init_study_name+'_Kf_Kt' +# +'_Iq_'+str(round(Iq_val,2)).replace(".", "_") +# +'_Is_'+str(round(1-Iq_val, 2)).replace(".", "_"),True) + +# # set duplicated study as present study +# present_study = self.toolJd.GetCurrentStudy() + +# # rotor should rotate one full revolution over 1 second +# # 1 rev/s = 60 rev/min +# speed = 60 +# self._set_study_speed(present_study,speed) +# excitation_freq = self._get_elec_freq(present_study,speed) + +# # set study step control +# self._set_study_steps(present_study,angle_step,rev) + +# # obtain handle to circuit for present study +# circuit = present_study.GetCircuit() + +# # set torque and suspension currents for all coils +# ampT = 2*Iq_pu_val*I_rms*np.sqrt(2) +# ampS = (1-Iq_pu_val)*I_rms*np.sqrt(2) + +# func = self.toolJd.FunctionFactory().Composite() +# f1 = self.toolJd.FunctionFactory().Sin(ampT, excitation_freq, 0) +# # "freq" variable cannot be used here. So pay extra attension when you create new case of a different freq. +# func.AddFunction(f1) +# circuit.GetComponent("CS_t-1").SetFunction(func) + +# func = self.toolJd.FunctionFactory().Composite() +# f1 = self.toolJd.FunctionFactory().Sin(ampT, excitation_freq, -120) +# func.AddFunction(f1) +# circuit.GetComponent("CS_t-2").SetFunction(func) + +# func = self.toolJd.FunctionFactory().Composite() +# f1 = self.toolJd.FunctionFactory().Sin(ampT, excitation_freq, -240) +# func.AddFunction(f1) +# circuit.GetComponent("CS_t-3").SetFunction(func) + +# func = self.toolJd.FunctionFactory().Composite() +# f1 = self.toolJd.FunctionFactory().Sin(ampS, excitation_freq, 0) +# f2 = self.toolJd.FunctionFactory().Sin(-ampT / 2, excitation_freq, 0) +# func.AddFunction(f1) +# func.AddFunction(f2) +# circuit.GetComponent("CS_s-1").SetFunction(func) + +# func = self.toolJd.FunctionFactory().Composite() +# f1 = self.toolJd.FunctionFactory().Sin(ampS, excitation_freq, 120) +# f2 = self.toolJd.FunctionFactory().Sin(-ampT / 2, excitation_freq, -120) +# func.AddFunction(f1) +# func.AddFunction(f2) +# circuit.GetComponent("CS_s-2").SetFunction(func) + +# func = self.toolJd.FunctionFactory().Composite() +# f1 = self.toolJd.FunctionFactory().Sin(ampS, excitation_freq, 240) +# f2 = self.toolJd.FunctionFactory().Sin(-ampT / 2, excitation_freq, -240) +# func.AddFunction(f1) +# func.AddFunction(f2) +# circuit.GetComponent("CS_s-3").SetFunction(func) + +# # run the study +# present_study.RunAllCases() + +# # extract FEA results from CSV +# force_df = self._extract_csv_results(present_study.GetName(), "Force") +# torque_df = self._extract_csv_results(present_study.GetName(),"Torque") + +# force_df_list.append(force_df) +# torque_df_list.append(torque_df) + +# self.force_df_list = force_df_list +# return force_df_list, torque_df_list diff --git a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py new file mode 100644 index 00000000..bbe814a5 --- /dev/null +++ b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py @@ -0,0 +1,417 @@ +import numpy as np +import pandas as pd +from tqdm import tqdm +import win32com.client +from ..electrical_analysis.JMAG import JMAG +from .....machines.bspm import BSPM_Machine, BSPM_Machine_Oper_Pt +from ...bspm.jmag_2d_config import JMAG_2D_Config +from ...bspm.jmag_2d import BSPM_EM_Analyzer +from typing import Tuple +########################################################################### +from functools import cached_property, lru_cache +# @lru_cache decoractor saves the return value of the method, +# method will run once and the return value will be saved, from then everytime +# the method is called it will simply return the saved return value, +# sigificantly reducing computation time +########################################################################### + +from ....force_vector_data import ( + ProcessForceDataProblem, + ProcessForceDataAnalyzer +) + +from ....torque_data import ( + ProcessTorqueDataProblem, + ProcessTorqueDataAnalyzer +) + +class BSPMMachineConstantProblem: + def __init__( + self, + machine:BSPM_Machine, + operating_point: BSPM_Machine_Oper_Pt, + solve_Kf: bool = True, + solve_Kt: bool = True, + solve_Kdelta: bool = True, + solve_Kphi: bool = True, + ) -> 'BSPMMachineConstantProblem': + """BSPMMachineConstantProblem Class + + Args: + machine (BSPM_Machine): instance of `BSPM_Machine` + operating_point (BSPM_Machine_Oper_Pt): instance of `BSPM_Machine_Oper_Pt` + solve_Kf (bool, optional): solve force constant. Defaults to True. + solve_Kt (bool, optional): solve torque constant. Defaults to True. + solve_Kdelta (bool, optional): solve displacment constant. Defaults to True. + solve_Kphi (bool, optional): solve back-emf constant. Defaults to True. + + Returns: + BSPMMachineConstantProblem: instance of BSPMMachineConstantProblem + """ + self.machine = machine + self.operating_point = operating_point + self.solve_Kf = solve_Kf + self.solve_Kdelta = solve_Kdelta + self._validate_attr() + + def _validate_attr(self): + if not isinstance(self.machine,BSPM_Machine): + raise TypeError( + 'Invalid machine type, must be BSPM_Machine.' + ) + + if not isinstance(self.operating_point, BSPM_Machine_Oper_Pt): + raise TypeError( + 'Invalid settings type, must be BSPM_Machine_Oper_Pt.' + ) + +class BSPMMachineConstantAnalyzer(BSPM_EM_Analyzer): + def __init__( + self, + configuration: JMAG_2D_Config, + **kwargs + ): + self.configuration = configuration + super().__init__(self.configuration) + + ############# Simulation Steps ############# + # Unless kwargs are provided, analyzer will use default values + # kwargs.get(key, default_val) + self.Iq_step = kwargs.get('Iq_step',10) + + coord = [] + for x in np.linspace(-0.3,0.3,3): + for y in np.linspace(-0.3,0.3,3): + coord.append([x,y]) + self.Kdelta_coordinates = kwargs.get('Kdelta_coord',coord) + self.Kphi_speed = np.linspace(0,160000,11) + + def analyze(self, problem: BSPMMachineConstantProblem): + + self.problem = problem + self.machine = problem.machine + self._validate_attr() + + # Run initial analysis to build the model + print('Performing initial run...') + super().analyze(self.problem) + print('Initial run complete...') + + # open .jproj file and obtain initial model and study properties + print(f'Re-opening {self.project_name}.jproj') + self.toolJd = self._open_JMAG_file() + + (self.init_model, + self.init_study, + self.init_study_name, + self.init_properties) = self._get_init_study(self.toolJd) + + + @cached_property + def Kf(self)->float: + "Machine Force Constant" + Is_list, force = self.Kf_data + Kf,_ = np.polyfit(Is_list,force,deg=1) + return Kf + + @cached_property + def Kt(self)->float: + "Machine Torque Constant" + Iq_list, torque = self.Kt_data + Kt,_ = np.polyfit(Iq_list,torque,deg=1) + return Kt + + @cached_property + def Kdelta(self)->float: + "Machine Displacement Constant" + disp,force = self.Kdelta_data + Kdelta,_ = np.polyfit(disp,force,deg=1) + return Kdelta + + @cached_property + def Kphi(self)->float: + "Machine Back-EMF Constant" + speed, bemf = self.Kphi_data + Kphi,_ = np.polyfit(speed,bemf,deg=1) + return Kphi + + @cached_property + def Kf_data(self)->Tuple[list,list]: + # run simulations and extract data from JMAG + _,Is_list,_,force_df_list = self.run_Kf_Kt_simulations() + + # determine average torque in each simulation run + force = np.zeros(len(force_df_list)) + for idx, force_df in enumerate(force_df_list): + force_prob = ProcessForceDataProblem( + Fx=force_df["ForCon:1st"].iloc[self.idx_ignore:].to_numpy(), + Fy=force_df["ForCon:2nd"].iloc[self.idx_ignore:].to_numpy() + ) + force_ana = ProcessForceDataAnalyzer() + _,_,f_abs_avg,_,_ = force_ana.analyze(force_prob) + force[idx] = f_abs_avg + + return Is_list, force + + @cached_property + def Kt_data(self)->Tuple[list,list]: + # run simulations and extract data from JMAG + Iq_list, _, torque_df_list, _ = self.run_Kf_Kt_simulations() + + # determine average torque in each simulation run + torque = np.zeros(len(torque_df_list)) + for idx, torque_df in enumerate(torque_df_list): + torq_prob = ProcessTorqueDataProblem( + torque_df["TorCon"].iloc[self.idx_ignore:].to_numpy() + ) + torq_analyzer = ProcessTorqueDataAnalyzer() + torq_avg,_ = torq_analyzer.analyze(torq_prob) + torque[idx] = torq_avg + + return Iq_list, torque + + + @cached_property + def Kdelta_data(self)->list: + """Analyze force and displacement data""" + force_df_list = self.run_Kdelta_simulations() + + force = [] + for force_df in force_df_list: + force_prob = ProcessForceDataProblem( + Fx=force_df["ForCon:1st"].iloc[self.idx_ignore:].to_numpy(), + Fy=force_df["ForCon:2nd"].iloc[self.idx_ignore:].to_numpy()) + force_ana = ProcessForceDataAnalyzer() + _,_,f_abs_avg,_,_ = force_ana.analyze(force_prob) + force.append(f_abs_avg) + disp = [np.linalg.norm(coord) for coord in self.Kdelta_coordinates] + + # combine and sort force and displacement data and unzip into + # two seperate list after sorting + disp, force = (list(t) for t in zip(*sorted(zip(disp,force)))) + return disp, force + + @cached_property + def Kphi_data(self): + """Analyze back-EMF data from Kphi simulations""" + bemf_df_list = self.run_Kphi_simulations() + + bemf = [] + for bemf_df in bemf_df_list: + phase_voltage = bemf_df["Terminal_Wt"].iloc[self.idx_ignore:] + if len(phase_voltage) == 0: + rms_voltage = 0 + else: + rms_voltage = np.sqrt(sum(np.square(phase_voltage)) / len(phase_voltage)) + bemf.append(rms_voltage) + + return self.Kphi_speed, bemf + + @lru_cache + def run_Kf_Kt_simulations(self)->Tuple[list, list, list, list]: + """_summary_ + + Returns: + Tuple[list, list, list, list]: _description_ + """ + + # define torque and suspension current for simulation + Iq_list = np.linspace( + 0,np.sqrt(2)*self.machine.Rated_current,self.Iq_step) + Is_list = np.sqrt(2)*self.machine.Rated_current - Iq_list + + force_df_list = [] + torque_df_list = [] + + print('==============================================================') + print('Running Kf and Kt simulations ......') + for idx, (Iq_val,Is_val) in enumerate( + tqdm(zip(Iq_list,Is_list),total=len(Iq_list))): + + # duplicate initial study + self.init_model.DuplicateStudyName(self.init_study_name, + f"{self.init_study_name}_Kf_Kt_step{idx}",True) + + present_study = self.toolJd.GetCurrentStudy() + + # obtain handle to circuit for present study + circuit = present_study.GetCircuit() + self._set_circuit_current_value( + circuit, + ampT=2*Iq_val, + ampS=Is_val, + freq=super().excitation_freq) + present_study.RunAllCases() + + # extract FEA results from CSV + force_df = self._extract_csv_results(present_study.GetName(), "Force") + torque_df = self._extract_csv_results(present_study.GetName(),"Torque") + force_df_list.append(force_df) + torque_df_list.append(torque_df) + + return Iq_list, Is_list, torque_df_list, force_df_list + + @lru_cache + def run_Kdelta_simulations(self)->list: + """Script to perform Kdelta simulations in JMAG. + + When called, script will either use default list of coordinates or + user-defined kwargs `Kdelta_coord` if provided. + + Must follow the following format + + Returns: + list: list of pd.Dataframes containing force results from simulations + """ + + print('==============================================================') + print('Running Kdelta simulations ......') + + force_df_list = [] + for coord in tqdm(self.Kdelta_coordinates): + + study_name = f'{self.init_study_name}_Kdelta_X{coord[0]}_Y{coord[1]}' + study_name = study_name.replace(".", "_") + + # duplicate initial study + self.init_model.DuplicateStudyName( + self.init_study_name, + study_name, + True) + + # set rotor displacment + present_study = self.toolJd.GetCurrentStudy() + present_study.GetCondition(0).SetValue("UseEccentricity", 1) + present_study.GetCondition(0).SetXYZPoint( + "PartOffset",coord[0],coord[1],0) + present_study.GetCondition(0).SetXYZPoint( + "AxisOffset",coord[0],coord[1],0) + + # obtain handle to circuit for present study + circuit = present_study.GetCircuit() + self._set_circuit_current_value( + circuit,ampT=0,ampS=0, freq=0) + present_study.RunAllCases() + + # extract FEA results from CSV + force_df = self._extract_csv_results( + present_study.GetName(),"Force") + force_df_list.append(force_df) + + return force_df_list + + @lru_cache + def run_Kphi_simulations(self): + """Script to run Kdelta simulations in JMAG + + Script uses default list of coordinates or user-defined kwargs `Kdelta_speed` + + Returns: + _type_: _description_ + """ + + print('==============================================================') + print("Running Kphi simulations ......") + + bemf_df_list = [] + for speed in tqdm(self.Kphi_speed): + + # duplicate initial study + self.init_model.DuplicateStudyName( + self.init_study_name, + f'{self.init_study_name}_Kphi_{str(round(speed,6)).replace(".", "_")}', + True) + + # set rotor speed + present_study = self.toolJd.GetCurrentStudy() + present_study.GetCondition(0).SetValue("AngularVelocity",speed) + + # obtain handle to circuit for present study + circuit = present_study.GetCircuit() + self._set_circuit_current_value( + circuit,ampT=0,ampS=0,freq=0) + present_study.RunAllCases() + + # extract FEA results from CSV + bemf_df = self._extract_csv_results(present_study.GetName(),"Voltage") + bemf_df_list.append(bemf_df) + + return bemf_df_list + + @cached_property + def idx_ignore(self)->int: + """Number of initial data points to ignore for first time step 1TS""" + idx_ignore = int(self.configuration.no_of_rev_1TS + *self.configuration.no_of_steps_per_rev_1TS) + return idx_ignore + + @cached_property + def project_file_path(self)->str: + """Path to JMAG .jproj file""" + path = f'{self.configuration.run_folder}{self.project_name}.jproj' + return path + + def _set_circuit_current_value(self,circuit,ampT,ampS,freq): + """Set DPNV circuit current sources to specified values""" + + torq_current_source = ['CS_t-1', 'CS_t-2', 'CS_t-3'] + sus_current_source = ['CS_s-1', 'CS_s-2', 'CS_s-3'] + + for idx, source in enumerate(torq_current_source): + func = self.toolJd.FunctionFactory().Composite() + f1 = self.toolJd.FunctionFactory().Sin(ampT, freq, -120*idx) + func.AddFunction(f1) + circuit.GetComponent(source).SetFunction(func) + + for idx, source in enumerate(sus_current_source): + func = self.toolJd.FunctionFactory().Composite() + f1 = self.toolJd.FunctionFactory().Sin( + ampS, freq, 120*idx) + f2 = self.toolJd.FunctionFactory().Sin( + -ampT / 2, freq, -120*idx) + func.AddFunction(f1) + func.AddFunction(f2) + circuit.GetComponent(source).SetFunction(func) + + def _extract_csv_results(self, study_name, type:str): + """Extract JMAG output from .csv file""" + csv_type_dict = {'Force':'_force.csv', + 'Torque':'_torque.csv', + 'Flux':'_flux_of_fem_coil.csv'} + path = self.configuration.jmag_csv_folder + study_name + csv_type_dict[type] + df = pd.read_csv(path, skiprows=6) + return df + + def _open_JMAG_file(self): + """Open and load specified JMAG file""" + toolJd = win32com.client.Dispatch("designer.Application") + toolJd.Show() + toolJd.load(self.project_file_path) + return toolJd + + def _get_init_study(self,toolJd): + """Obtain initial JMAG study and properties""" + toolJd.SetCurrentModel(0) + model = toolJd.GetModel(0) + study = toolJd.GetStudy(0) + study_name = study.GetName() + properties = study.GetStudyProperties() + return model,study,study_name,properties + + def _validate_attr(self): + """Validate input attributes""" + if not isinstance(self.problem, BSPMMachineConstantProblem): + raise TypeError( + 'Invalid problem type, must be BSPMMachineConstantProblem.' + ) + + if not isinstance(self.configuration, JMAG_2D_Config): + raise TypeError( + 'Invalid configuration type, must be JMAG_2D_Config.' + ) + +class BSPMMachineConstantAnalyzer3D(BSPMMachineConstantAnalyzer): + def __init__(self, + problem: BSPMMachineConstantProblem, + configuration: JMAG_2D_Config): + super().__init__(problem, configuration) \ No newline at end of file diff --git a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/BSPMMachineConstantsAnalyzer.py b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/legacy_bspm_mach_constants.py similarity index 98% rename from mach_eval/analyzers/electromagnetic/bspm/machine_constant/BSPMMachineConstantsAnalyzer.py rename to mach_eval/analyzers/electromagnetic/bspm/machine_constant/legacy_bspm_mach_constants.py index 8fdc453f..53954e20 100644 --- a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/BSPMMachineConstantsAnalyzer.py +++ b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/legacy_bspm_mach_constants.py @@ -18,13 +18,19 @@ # add the directory immediately above this file's directory to path for module import #sys.path.append("../../..") -class BSPMMachineConstantAnalyzer(): +class BSPMMachineConstantProblem: + def __init__( + self + ) -> None: + pass + +class BSPMMachineConstantAnalyzer: """Analyzer for determining machine constants of BSPM in JMAG. Attributes: project_name (str): .jproj JMAG file to evaluate. """ - def __init__(self, project_name:str) -> None: + def __init__(self, project_name:str) -> 'BSPMMachineConstantAnalyzer': self.project_name = project_name # open .jproj file and obtain initial model and study properties diff --git a/test.py b/test.py new file mode 100644 index 00000000..3bc14525 --- /dev/null +++ b/test.py @@ -0,0 +1,156 @@ +import mach_eval.analyzers.electromagnetic.bspm.machine_constant.bspm_mach_constants as bmc +import mach_eval.analyzers.electromagnetic.bspm.machine_constant.bspm_mach_constants as bmc +from mach_eval.machines.materials.electric_steels import Arnon5 +from mach_eval.machines.materials.jmag_library_magnets import N40H +from mach_eval.machines.materials.miscellaneous_materials import ( + CarbonFiber, + Steel, + Copper, + Hub, + Air, +) +from mach_eval.machines.bspm import BSPM_Machine +from mach_eval.machines.bspm.bspm_oper_pt import BSPM_Machine_Oper_Pt +from mach_eval.analyzers.electromagnetic.bspm.jmag_2d_config import JMAG_2D_Config +import os +import numpy as np + +######################################################### +# CREATE BSPM MACHINE OBJECT +######################################################### + +# ** +# Actual machine does not have rotor core, only shaft. +# d_ri and r_sh are set to 0.1[mm] and 8.9[mm] respectively +# to avoid InvalidDesign error. Actual shaft radius is 9[mm]. +# Differences in results are negligble. + +################ DEFINE BP4 ################ +bspm_dimensions = { + "alpha_st": 31.7088, #[deg] + "d_so": 2.02334e-3, #[m] + "w_st": 5.95805e-3, #[m] + "d_st": 18.4967e-3, #[m] + "d_sy": 5.81374e-3, #[m] + "alpha_m": 180, #[m] + "d_m": 3e-3, #[m] + "d_mp": 0, #[m] + "d_ri": 1e-3, #[m]** 0.1e-3 + "alpha_so": 15.5, #[deg] + "d_sp": 2.05e-3, #[m] + "r_si": 16.9737e-3, #[m] + "alpha_ms": 180, #[deg] + "d_ms": 0, #[m] + "r_sh": 8e-3, #[m]** 8.9e-3 + "l_st": 25e-3, #[m] + "d_sl": 1e-3, #[m] + "delta_sl": 9.63e-5, #[m] +} + +bspm_parameters = { + "p": 1, # number of pole pairs + "ps": 2, # number of suspension pole pairs + "n_m": 1, # + "Q": 6, # number of slots + "rated_speed": 16755.16, #[rad/s] + "rated_power": 8e3, # [W] + "rated_voltage": 8e3/18, # [V_rms] + "rated_current": 10, # [I_rms] #18 + "name": "BP4" +} + +bspm_materials = { + "air_mat": Air, + "rotor_iron_mat": Arnon5, + "stator_iron_mat": Arnon5, + "magnet_mat": N40H, + "rotor_sleeve_mat": CarbonFiber, + "coil_mat": Copper, + "shaft_mat": Steel, + "rotor_hub": Hub, +} + +bspm_winding = { + "no_of_layers": 2, + # layer_phases is a list of lists, the number of lists = no_of_layers + # first list corresponds to coil sides in first layer + # second list corresponds to coil sides in second layer + # the index indicates the slot opening corresponding to the coil side + # string characters are used to represent the phases + "layer_phases": [["U", "W", "V", "U", "W", "V"], + ["V", "U", "W", "V", "U", "W"]], + # layer_polarity is a list of lists, the number of lists = no_of_layers + # first list corresponds to coil side direction in first layer + # second list corresponds to coil side direction in second layer + # the index indicates the slot opening corresponding to the coil side + # + indicates coil side goes into the page, - indicates coil side comes out of page + "layer_polarity": [["+", "-", "+", "-", "+", "-"], + ["+", "-", "+", "-", "+", "-"]], + # coil_groups are a unique property of DPNV windings + # coil group is assigned corresponding to the 1st winding layer + "coil_groups": ["b", "a", "b", "a", "b", "a"], + "pitch": 1, + "Z_q": 45, + "Kov": 1.8, + "Kcu": 0.5, + # add phase current offset to know relative rotor / current angle for creating Iq + "phase_current_offset": -30 +} + +bp4 = BSPM_Machine( + bspm_dimensions, bspm_parameters, bspm_materials, bspm_winding +) + +######################################################### +# DEFINE BSPM OPERATING POINT +######################################################### +bp4_op_pt = BSPM_Machine_Oper_Pt( + Id=0, # I_pu + Iq=0.95, # I_pu + Ix=0, # I_pu + Iy=0.05, # I_pu + speed=160000, # RPM + ambient_temp=25, # C + rotor_temp_rise=55, # K +) + +######################################################### +# DEFINE BSPM JMAG SETTINGS +######################################################### +jmag_config = JMAG_2D_Config( + no_of_rev_1TS=2, + no_of_rev_2TS=1, + no_of_steps_per_rev_1TS=36, + no_of_steps_per_rev_2TS=36, + mesh_size=2e-3, + magnet_mesh_size=1e-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.abspath("") + "/run_data/", + jmag_csv_folder=os.path.abspath("") + "/run_data/JMAG_csv/", + max_nonlinear_iterations=50, + multiple_cpus=True, + num_cpus=4, + jmag_scheduler=False, + jmag_visible=True, +) + +problem = bmc.BSPMMachineConstantProblem(bp4,bp4_op_pt) +analyzer = bmc.BSPMMachineConstantAnalyzer(jmag_config) +analyzer.analyze(problem) +print(analyzer.Kt) +print(analyzer.Kf) +print(analyzer.Kdelta) +print(analyzer.Kphi) + + + + + + + From 3db45ff1d260cfbc48d0babc28fc5aa7d61cc540 Mon Sep 17 00:00:00 2001 From: Anson Chan Date: Thu, 4 Jan 2024 19:47:11 -0600 Subject: [PATCH 03/15] modify code --- .../machine_constant/bspm_mach_constants.py | 209 ++++++++++++------ test.py | 38 +++- 2 files changed, 163 insertions(+), 84 deletions(-) diff --git a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py index bbe814a5..ccc6a3c5 100644 --- a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py +++ b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py @@ -2,11 +2,11 @@ import pandas as pd from tqdm import tqdm import win32com.client -from ..electrical_analysis.JMAG import JMAG from .....machines.bspm import BSPM_Machine, BSPM_Machine_Oper_Pt from ...bspm.jmag_2d_config import JMAG_2D_Config from ...bspm.jmag_2d import BSPM_EM_Analyzer -from typing import Tuple +from typing import Tuple, Union +from dataclasses import dataclass ########################################################################### from functools import cached_property, lru_cache # @lru_cache decoractor saves the return value of the method, @@ -32,8 +32,8 @@ def __init__( operating_point: BSPM_Machine_Oper_Pt, solve_Kf: bool = True, solve_Kt: bool = True, - solve_Kdelta: bool = True, solve_Kphi: bool = True, + solve_Kdelta: bool = True, ) -> 'BSPMMachineConstantProblem': """BSPMMachineConstantProblem Class @@ -51,6 +51,8 @@ def __init__( self.machine = machine self.operating_point = operating_point self.solve_Kf = solve_Kf + self.solve_Kt = solve_Kt + self.solve_Kphi = solve_Kphi self.solve_Kdelta = solve_Kdelta self._validate_attr() @@ -69,74 +71,111 @@ class BSPMMachineConstantAnalyzer(BSPM_EM_Analyzer): def __init__( self, configuration: JMAG_2D_Config, - **kwargs + Kf_Kt_step: int = 10, + Kdelta_coords: list = [[x, y] for x in np.linspace(-0.3,0.3,3) + for y in np.linspace(-0.3,0.3,3)], + Kphi_step: int = 10, ): self.configuration = configuration super().__init__(self.configuration) - - ############# Simulation Steps ############# - # Unless kwargs are provided, analyzer will use default values - # kwargs.get(key, default_val) - self.Iq_step = kwargs.get('Iq_step',10) - - coord = [] - for x in np.linspace(-0.3,0.3,3): - for y in np.linspace(-0.3,0.3,3): - coord.append([x,y]) - self.Kdelta_coordinates = kwargs.get('Kdelta_coord',coord) - self.Kphi_speed = np.linspace(0,160000,11) + self.Kf_Kt_step = Kf_Kt_step + self.Kdelta_coords = Kdelta_coords + self.Kphi_step = Kphi_step + + def __getstate__(self): + """Magic method for pickling""" + # Remove problematic objects to avoid pickling error + odict = self.__dict__.copy() + del_key_list = ['toolJd','init_model','init_study','init_properties'] + for key in del_key_list: + del odict[key] + return odict + + def __setstate__(self, state): + """Magic method for unpickling""" + self.__dict__ = state def analyze(self, problem: BSPMMachineConstantProblem): + """Analyze BSPMMachineConstantProblem + + Args: + problem (BSPMMachineConstantProblem): BSPMMachineConstantProblem + Returns: + BSPMMachineConstantResult: Result class of BSPMMachineConstantProblem + """ + self.problem = problem self.machine = problem.machine + self.machine_op_pt = problem.operating_point self._validate_attr() # Run initial analysis to build the model + # super().analyze == BSPM_EM_Analyzer.analyzer + print('==============================================================') print('Performing initial run...') super().analyze(self.problem) print('Initial run complete...') # open .jproj file and obtain initial model and study properties + print('==============================================================') print(f'Re-opening {self.project_name}.jproj') self.toolJd = self._open_JMAG_file() - (self.init_model, self.init_study, self.init_study_name, self.init_properties) = self._get_init_study(self.toolJd) + + return BSPMMachineConstantResult( + self.Kf,self.Kt,self.Kdelta,self.Kphi) - @cached_property - def Kf(self)->float: - "Machine Force Constant" - Is_list, force = self.Kf_data - Kf,_ = np.polyfit(Is_list,force,deg=1) - return Kf - + def Kf(self)-> Union[float, None]: + "Machine Force Constant [N/A]" + if self.problem.solve_Kf: + Is_list, force = self.Kf_data + Kf,_ = np.polyfit(Is_list,force,deg=1) + return Kf + else: + return None + @cached_property - def Kt(self)->float: - "Machine Torque Constant" - Iq_list, torque = self.Kt_data - Kt,_ = np.polyfit(Iq_list,torque,deg=1) - return Kt + def Kt(self)->Union[float, None]: + "Machine Torque Constant [N-m/A_pk]" + if self.problem.solve_Kt: + Iq_list, torque = self.Kt_data + Kt,_ = np.polyfit(Iq_list,torque,deg=1) + return Kt + else: + return None @cached_property - def Kdelta(self)->float: - "Machine Displacement Constant" - disp,force = self.Kdelta_data - Kdelta,_ = np.polyfit(disp,force,deg=1) - return Kdelta - + def Kdelta(self)->Union[float, None]: + "Machine Displacement Constant [N/m]" + if self.problem.solve_Kdelta: + disp,force = self.Kdelta_data + Kdelta,_ = np.polyfit(disp,force,deg=1) + return Kdelta + else: + return None + @cached_property - def Kphi(self)->float: - "Machine Back-EMF Constant" - speed, bemf = self.Kphi_data - Kphi,_ = np.polyfit(speed,bemf,deg=1) - return Kphi + def Kphi(self)->Union[float, None]: + "Machine Back-EMF Constant [V_rms/rad/s]" + if self.problem.solve_Kphi: + speed, bemf = self.Kphi_data + Kphi,_ = np.polyfit(speed,bemf,deg=1) + return Kphi + else: + return None @cached_property def Kf_data(self)->Tuple[list,list]: + """Data used in evaluating machine force constant Kf + + Returns: + Tuple[list,list]: (suspension currents [A], force values [N]) + """ # run simulations and extract data from JMAG _,Is_list,_,force_df_list = self.run_Kf_Kt_simulations() @@ -155,6 +194,11 @@ def Kf_data(self)->Tuple[list,list]: @cached_property def Kt_data(self)->Tuple[list,list]: + """Data used in evaluating machine torque constant Kt + + Returns: + Tuple[list,list]: (torque currents [A], torque values [N-m]) + """ # run simulations and extract data from JMAG Iq_list, _, torque_df_list, _ = self.run_Kf_Kt_simulations() @@ -170,10 +214,14 @@ def Kt_data(self)->Tuple[list,list]: return Iq_list, torque - @cached_property def Kdelta_data(self)->list: - """Analyze force and displacement data""" + """Data used in evaluating machine displacement constant Kdelta + + Returns: + Tuple[list,list]: (displacment magnitudes [m], force values [N]) + """ + # run simulations and extract data from JMAG force_df_list = self.run_Kdelta_simulations() force = [] @@ -184,7 +232,7 @@ def Kdelta_data(self)->list: force_ana = ProcessForceDataAnalyzer() _,_,f_abs_avg,_,_ = force_ana.analyze(force_prob) force.append(f_abs_avg) - disp = [np.linalg.norm(coord) for coord in self.Kdelta_coordinates] + disp = [np.linalg.norm(coord)/1000 for coord in self.Kdelta_coords] # combine and sort force and displacement data and unzip into # two seperate list after sorting @@ -193,7 +241,11 @@ def Kdelta_data(self)->list: @cached_property def Kphi_data(self): - """Analyze back-EMF data from Kphi simulations""" + """Data used in evaluating machine back-emf constant Kphi + + Returns: + Tuple[list,list]: (speed values [rad/s], back-emf voltage [V_rms]) + """ bemf_df_list = self.run_Kphi_simulations() bemf = [] @@ -202,22 +254,28 @@ def Kphi_data(self): if len(phase_voltage) == 0: rms_voltage = 0 else: - rms_voltage = np.sqrt(sum(np.square(phase_voltage)) / len(phase_voltage)) + rms_voltage = np.sqrt( + sum(np.square(phase_voltage)) / len(phase_voltage)) bemf.append(rms_voltage) - return self.Kphi_speed, bemf + return self.Kphi_speed*(2*np.pi/60), bemf + + @cached_property + def Kphi_speed(self): + return np.linspace(0,self.machine_op_pt.speed,self.Kphi_step) @lru_cache def run_Kf_Kt_simulations(self)->Tuple[list, list, list, list]: - """_summary_ - + """Script to perform Kf and Kt simulations in JMAG. + Returns: - Tuple[list, list, list, list]: _description_ + Tuple[list, list, list, list]: (torque currents [A], + suspension currents [A], torque_df_list, force_df_list) """ # define torque and suspension current for simulation Iq_list = np.linspace( - 0,np.sqrt(2)*self.machine.Rated_current,self.Iq_step) + 0,np.sqrt(2)*self.machine.Rated_current,self.Kf_Kt_step) Is_list = np.sqrt(2)*self.machine.Rated_current - Iq_list force_df_list = [] @@ -254,11 +312,6 @@ def run_Kf_Kt_simulations(self)->Tuple[list, list, list, list]: @lru_cache def run_Kdelta_simulations(self)->list: """Script to perform Kdelta simulations in JMAG. - - When called, script will either use default list of coordinates or - user-defined kwargs `Kdelta_coord` if provided. - - Must follow the following format Returns: list: list of pd.Dataframes containing force results from simulations @@ -268,7 +321,7 @@ def run_Kdelta_simulations(self)->list: print('Running Kdelta simulations ......') force_df_list = [] - for coord in tqdm(self.Kdelta_coordinates): + for coord in tqdm(self.Kdelta_coords): study_name = f'{self.init_study_name}_Kdelta_X{coord[0]}_Y{coord[1]}' study_name = study_name.replace(".", "_") @@ -278,9 +331,9 @@ def run_Kdelta_simulations(self)->list: self.init_study_name, study_name, True) + present_study = self.toolJd.GetCurrentStudy() # set rotor displacment - present_study = self.toolJd.GetCurrentStudy() present_study.GetCondition(0).SetValue("UseEccentricity", 1) present_study.GetCondition(0).SetXYZPoint( "PartOffset",coord[0],coord[1],0) @@ -289,8 +342,13 @@ def run_Kdelta_simulations(self)->list: # obtain handle to circuit for present study circuit = present_study.GetCircuit() - self._set_circuit_current_value( - circuit,ampT=0,ampS=0, freq=0) + function1 = self.toolJd.FunctionFactory().Constant(0) + circuit.GetComponent("CS_t-1").SetFunction(function1) + circuit.GetComponent("CS_t-2").SetFunction(function1) + circuit.GetComponent("CS_t-3").SetFunction(function1) + circuit.GetComponent("CS_s-1").SetFunction(function1) + circuit.GetComponent("CS_s-2").SetFunction(function1) + circuit.GetComponent("CS_s-3").SetFunction(function1) present_study.RunAllCases() # extract FEA results from CSV @@ -301,13 +359,11 @@ def run_Kdelta_simulations(self)->list: return force_df_list @lru_cache - def run_Kphi_simulations(self): + def run_Kphi_simulations(self)->list: """Script to run Kdelta simulations in JMAG - - Script uses default list of coordinates or user-defined kwargs `Kdelta_speed` Returns: - _type_: _description_ + list: list of pd.Dataframes containing voltage results from simulations """ print('==============================================================') @@ -328,8 +384,13 @@ def run_Kphi_simulations(self): # obtain handle to circuit for present study circuit = present_study.GetCircuit() - self._set_circuit_current_value( - circuit,ampT=0,ampS=0,freq=0) + function1 = self.toolJd.FunctionFactory().Constant(0) + circuit.GetComponent("CS_t-1").SetFunction(function1) + circuit.GetComponent("CS_t-2").SetFunction(function1) + circuit.GetComponent("CS_t-3").SetFunction(function1) + circuit.GetComponent("CS_s-1").SetFunction(function1) + circuit.GetComponent("CS_s-2").SetFunction(function1) + circuit.GetComponent("CS_s-3").SetFunction(function1) present_study.RunAllCases() # extract FEA results from CSV @@ -372,12 +433,14 @@ def _set_circuit_current_value(self,circuit,ampT,ampS,freq): func.AddFunction(f1) func.AddFunction(f2) circuit.GetComponent(source).SetFunction(func) - + def _extract_csv_results(self, study_name, type:str): """Extract JMAG output from .csv file""" csv_type_dict = {'Force':'_force.csv', 'Torque':'_torque.csv', - 'Flux':'_flux_of_fem_coil.csv'} + 'Flux':'_flux_of_fem_coil.csv', + 'Voltage':'_circuit_voltage.csv' + } path = self.configuration.jmag_csv_folder + study_name + csv_type_dict[type] df = pd.read_csv(path, skiprows=6) return df @@ -409,9 +472,11 @@ def _validate_attr(self): raise TypeError( 'Invalid configuration type, must be JMAG_2D_Config.' ) + +@dataclass +class BSPMMachineConstantResult: + Kf: float + Kt: float + Kdelta: float + Kphi: float -class BSPMMachineConstantAnalyzer3D(BSPMMachineConstantAnalyzer): - def __init__(self, - problem: BSPMMachineConstantProblem, - configuration: JMAG_2D_Config): - super().__init__(problem, configuration) \ No newline at end of file diff --git a/test.py b/test.py index 3bc14525..648adebc 100644 --- a/test.py +++ b/test.py @@ -35,16 +35,16 @@ "alpha_m": 180, #[m] "d_m": 3e-3, #[m] "d_mp": 0, #[m] - "d_ri": 1e-3, #[m]** 0.1e-3 + "d_ri": 0.1e-3, #[m]** 0.1e-3 "alpha_so": 15.5, #[deg] "d_sp": 2.05e-3, #[m] "r_si": 16.9737e-3, #[m] "alpha_ms": 180, #[deg] "d_ms": 0, #[m] - "r_sh": 8e-3, #[m]** 8.9e-3 + "r_sh": 8.9e-3, #[m]** 8.9e-3 "l_st": 25e-3, #[m] "d_sl": 1e-3, #[m] - "delta_sl": 9.63e-5, #[m] + "delta_sl": 0.00011 #9.63e-5, #[m] } bspm_parameters = { @@ -55,7 +55,7 @@ "rated_speed": 16755.16, #[rad/s] "rated_power": 8e3, # [W] "rated_voltage": 8e3/18, # [V_rms] - "rated_current": 10, # [I_rms] #18 + "rated_current": 18, # [I_rms] #18 "name": "BP4" } @@ -118,10 +118,10 @@ # DEFINE BSPM JMAG SETTINGS ######################################################### jmag_config = JMAG_2D_Config( - no_of_rev_1TS=2, + no_of_rev_1TS=1, no_of_rev_2TS=1, no_of_steps_per_rev_1TS=36, - no_of_steps_per_rev_2TS=36, + no_of_steps_per_rev_2TS=720, mesh_size=2e-3, magnet_mesh_size=1e-3, airgap_mesh_radial_div=5, @@ -141,12 +141,26 @@ ) problem = bmc.BSPMMachineConstantProblem(bp4,bp4_op_pt) -analyzer = bmc.BSPMMachineConstantAnalyzer(jmag_config) -analyzer.analyze(problem) -print(analyzer.Kt) -print(analyzer.Kf) -print(analyzer.Kdelta) -print(analyzer.Kphi) + +coord = [] +for x in np.linspace(-0.3,0.3,3): + for y in np.linspace(-0.3,0.3,3): + coord.append([x,y]) +coord.append([0,0.2]) +coord.append([0,0.4]) + +analyzer = bmc.BSPMMachineConstantAnalyzer( + jmag_config, + Kf_Kt_step=11, + Kdelta_coords=coord, + Kphi_step=11) + +result = analyzer.analyze(problem) +print(result.Kt) +print(result.Kf) +print(result.Kdelta) +print(result.Kphi) + From 3d6f5a4515096c7fecb97a9ff2a062a21e4df3f8 Mon Sep 17 00:00:00 2001 From: Anson Chan Date: Fri, 5 Jan 2024 13:32:05 -0600 Subject: [PATCH 04/15] add rst and csv --- .../bspm_mach_constants_analyzer.rst | 199 ++++++++++++++++++ .../result_bspm_mach_constants.csv | 5 + 2 files changed, 204 insertions(+) create mode 100644 docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst create mode 100644 docs/source/EM_analyzers/result_bspm_mach_constants.csv diff --git a/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst b/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst new file mode 100644 index 00000000..c143bd5f --- /dev/null +++ b/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst @@ -0,0 +1,199 @@ +BSPM Machine Constants Analyzer +######################################################################## + +This analyzer determines the machine constants (:math:`K_t, K_f, K_\delta` and :math:`K_\Phi`) of a given BSPM machine design. + +Model Background +**************** + +This analyzer utilizes scripts within eMach to generate `BSPM_Machine` and `BSPM_Machine_Oper_Pt` objects for performing machine constant analysis. + + + +Input from User +********************************* + +In order to define the problem class, user must specify the geometry as well as the operating point of the BSPM machine. +For defining the `BSPM_Machine` and `BSPM_Machine_Oper_Pt` objects, user can refer to :doc:`../machines/bspm/index.rst` + +.. code-block:: python + + import mach_eval.analyzers.electromagnetic.bspm.machine_constant.bspm_mach_constants as bmc + import mach_eval.analyzers.electromagnetic.bspm.machine_constant.bspm_mach_constants as bmc + from mach_eval.machines.materials.electric_steels import Arnon5 + from mach_eval.machines.materials.jmag_library_magnets import N40H + from mach_eval.machines.materials.miscellaneous_materials import ( + CarbonFiber, + Steel, + Copper, + Hub, + Air, + ) + from mach_eval.machines.bspm import BSPM_Machine + from mach_eval.machines.bspm.bspm_oper_pt import BSPM_Machine_Oper_Pt + from mach_eval.analyzers.electromagnetic.bspm.jmag_2d_config import JMAG_2D_Config + import os + import numpy as np + + ######################################################### + # CREATE BSPM MACHINE OBJECT + ######################################################### + + ################ DEFINE BP4 ################ + bspm_dimensions = { + "alpha_st": 31.7088, #[deg] + "d_so": 2.02334e-3, #[m] + "w_st": 5.95805e-3, #[m] + "d_st": 18.4967e-3, #[m] + "d_sy": 5.81374e-3, #[m] + "alpha_m": 180, #[m] + "d_m": 3e-3, #[m] + "d_mp": 0, #[m] + "d_ri": 0.1e-3, #[m] + "alpha_so": 15.5, #[deg] + "d_sp": 2.05e-3, #[m] + "r_si": 16.9737e-3, #[m] + "alpha_ms": 180, #[deg] + "d_ms": 0, #[m] + "r_sh": 8.9e-3, #[m] + "l_st": 25e-3, #[m] + "d_sl": 1e-3, #[m] + "delta_sl": 9.63e-5, #[m] + } + + bspm_parameters = { + "p": 1, # number of pole pairs + "ps": 2, # number of suspension pole pairs + "n_m": 1, # + "Q": 6, # number of slots + "rated_speed": 16755.16, #[rad/s] + "rated_power": 8e3, # [W] + "rated_voltage": 8e3/18, # [V_rms] + "rated_current": 18, # [I_rms] + "name": "BP4" + } + + bspm_materials = { + "air_mat": Air, + "rotor_iron_mat": Arnon5, + "stator_iron_mat": Arnon5, + "magnet_mat": N40H, + "rotor_sleeve_mat": CarbonFiber, + "coil_mat": Copper, + "shaft_mat": Steel, + "rotor_hub": Hub, + } + + bspm_winding = { + "no_of_layers": 2, + # layer_phases is a list of lists, the number of lists = no_of_layers + # first list corresponds to coil sides in first layer + # second list corresponds to coil sides in second layer + # the index indicates the slot opening corresponding to the coil side + # string characters are used to represent the phases + "layer_phases": [["U", "W", "V", "U", "W", "V"], + ["V", "U", "W", "V", "U", "W"]], + # layer_polarity is a list of lists, the number of lists = no_of_layers + # first list corresponds to coil side direction in first layer + # second list corresponds to coil side direction in second layer + # the index indicates the slot opening corresponding to the coil side + # + indicates coil side goes into the page, - indicates coil side comes out of page + "layer_polarity": [["+", "-", "+", "-", "+", "-"], + ["+", "-", "+", "-", "+", "-"]], + # coil_groups are a unique property of DPNV windings + # coil group is assigned corresponding to the 1st winding layer + "coil_groups": ["b", "a", "b", "a", "b", "a"], + "pitch": 1, + "Z_q": 45, + "Kov": 1.8, + "Kcu": 0.5, + # add phase current offset to know relative rotor / current angle for creating Iq + "phase_current_offset": -30 + } + + bp4 = BSPM_Machine( + bspm_dimensions, bspm_parameters, bspm_materials, bspm_winding + ) + + ######################################################### + # DEFINE BSPM OPERATING POINT + ######################################################### + bp4_op_pt = BSPM_Machine_Oper_Pt( + Id=0, # I_pu + Iq=0.95, # I_pu + Ix=0, # I_pu + Iy=0.05, # I_pu + speed=160000, # RPM + ambient_temp=25, # C + rotor_temp_rise=55, # K + ) + + ######################################################### + # DEFINE BSPM JMAG SETTINGS + ######################################################### + jmag_config = JMAG_2D_Config( + no_of_rev_1TS=1, + no_of_rev_2TS=2, + no_of_steps_per_rev_1TS=36, + no_of_steps_per_rev_2TS=360, + mesh_size=2e-3, + magnet_mesh_size=1e-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.abspath("") + "/run_data/", + jmag_csv_folder=os.path.abspath("") + "/run_data/JMAG_csv/", + max_nonlinear_iterations=50, + multiple_cpus=True, + num_cpus=4, + jmag_scheduler=False, + jmag_visible=True, + ) + + ######################################################### + # DEFINE BSPM MACHINE CONSTANTS PROBLEM + ######################################################### + problem = bmc.BSPMMachineConstantProblem(bp4,bp4_op_pt) + + ######################################################### + # DEFINE BSPM MACHINE CONSTANTS ANALYZER + ######################################################### + analyzer = bmc.BSPMMachineConstantAnalyzer(jmag_config) + + +Output to User +********************************** + +The attributes of the results class can be summarized in the table below: + +.. csv-table:: results of bspm machine constant analyzer + :file: result_bspm_mach_constants.csv + :widths: 30, 70, 30 + :header-rows: 1 + +Use the following code to run the example analysis: + +.. code-block:: python + + ######################################################### + # SOLVE BSPM MACHINE CONSTANTS PROBLEM + ######################################################### + result = analyzer.analyze(problem) + print(result.Kf) + print(result.Kt) + print(result.Kdelta) + print(result.Kphi) + +Running the example case returns the following: + +.. code-block:: python + + +The results indicate that the example BSPM machine design has suspension force constant of xxx [N/A], +torque constant of xxx [N-m/A_pk], displacement stiffness constant of xxx [N/m] and back-EMF constant of xxx [V_rms/rad/s]. + + \ No newline at end of file diff --git a/docs/source/EM_analyzers/result_bspm_mach_constants.csv b/docs/source/EM_analyzers/result_bspm_mach_constants.csv new file mode 100644 index 00000000..0e5009c5 --- /dev/null +++ b/docs/source/EM_analyzers/result_bspm_mach_constants.csv @@ -0,0 +1,5 @@ +Attribute,Description,Units +Kf, Suspension force constant, N/A +Kt, Torque constant, N-m/A_pk +Kdelta, Displacement stiffness constant, N/m +Kphi, Back-EMF constant , V_rms/rad/s From 512e258c61c5047721252fe87ac0457e0ffb76f3 Mon Sep 17 00:00:00 2001 From: Anson Chan Date: Fri, 5 Jan 2024 14:35:19 -0600 Subject: [PATCH 05/15] add input csv and update rsy --- .../bspm_mach_constants_analyzer.rst | 48 +++++++++++++++++-- .../input_bspm_mach_constants_analyzer.csv | 7 +++ 2 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 docs/source/EM_analyzers/input_bspm_mach_constants_analyzer.csv diff --git a/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst b/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst index c143bd5f..1cb7dfc2 100644 --- a/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst +++ b/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst @@ -1,13 +1,45 @@ BSPM Machine Constants Analyzer ######################################################################## -This analyzer determines the machine constants (:math:`K_t, K_f, K_\delta` and :math:`K_\Phi`) of a given BSPM machine design. +This analyzer determines the machine constants (:math:`k_t, k_f, k_\delta` and :math:`k_\Phi`) of a given BSPM machine design. Model Background **************** This analyzer utilizes scripts within eMach to generate `BSPM_Machine` and `BSPM_Machine_Oper_Pt` objects for performing machine constant analysis. +Torque Contant :math:`k_t` +------------------------------------ +The machine torque constant :math:`k_t` can be expressed using the following equaiton, + +.. math:: + + \tau = k_t i_q + +where 𝜏 is the torque and :math:`i_q` is the injected torque current. + +Suspension Force :math:`k_f` & Displacement Stiffness Constant :math:`k_\delta` +-------------------------------------------------------------------------------------------------- +The suspension force constant :math:`k_f` and displacement stiffness constant :math:`k_\delta` can be expressed using the following equaiton, + +.. math:: + + \vec{F} = k_f \vec{i_s}+k_\delta \vec{\delta} + +where 𝛿 is the displacement of the rotor from magnetic center, :math:`F_c` is the force created by current :math:`\vec{i_s}`, +:math:`k_f` is the force constant and :math:`k_\delta` is the displacement stiffness constant + + +Back-EMF Constant :math:`k_\Phi` +------------------------------------ +The machine back-EMF constant :math:`k_\Phi` can be expressed using the following equaiton, + +.. math:: + + \vec{v_m} = k_\Phi\omega + +where, 𝜔 is angular velocity in rad/s and :math:`\vec{v_m}` is the RMS value of the induced phase voltage. (???) + Input from User @@ -16,6 +48,11 @@ Input from User In order to define the problem class, user must specify the geometry as well as the operating point of the BSPM machine. For defining the `BSPM_Machine` and `BSPM_Machine_Oper_Pt` objects, user can refer to :doc:`../machines/bspm/index.rst` +.. csv-table:: results of bspm machine constant analyzer + :file: input_bspm_mach_constants_analyzer.csv + :widths: 20, 20, 70 + :header-rows: 1 + .. code-block:: python import mach_eval.analyzers.electromagnetic.bspm.machine_constant.bspm_mach_constants as bmc @@ -192,8 +229,13 @@ Running the example case returns the following: .. code-block:: python + 1.8019710307171688 + 0.0203730830815381 + 6935.763575553156 + 0.00456017028983404 + -The results indicate that the example BSPM machine design has suspension force constant of xxx [N/A], -torque constant of xxx [N-m/A_pk], displacement stiffness constant of xxx [N/m] and back-EMF constant of xxx [V_rms/rad/s]. +The results indicate that the example BSPM machine design has suspension force constant of 1.802 [N/A], +torque constant of 0.0204 [N-m/A_pk], displacement stiffness constant of 6935.76 [N/m] and back-EMF constant of 0.00456 [V_rms/rad/s]. \ No newline at end of file diff --git a/docs/source/EM_analyzers/input_bspm_mach_constants_analyzer.csv b/docs/source/EM_analyzers/input_bspm_mach_constants_analyzer.csv new file mode 100644 index 00000000..cdcb3200 --- /dev/null +++ b/docs/source/EM_analyzers/input_bspm_mach_constants_analyzer.csv @@ -0,0 +1,7 @@ +Arguments, Type, Description +machine, 'BSPM_machine', object of type BSPM_machine describing a bearingless spm machine +operating_point,'BSPM_Machine_Oper_Pt', object of type BSPM_Machine_Oper_Pt describing BSPM machine operating point +solve_Kf,bool, +solve_Kt,bool, +solve_Kphi,bool, +solve_Kdelta,bool, \ No newline at end of file From fd6420a3d634b5e186b5fd48a88cf48b6cc85868 Mon Sep 17 00:00:00 2001 From: Anson Chan Date: Fri, 5 Jan 2024 19:11:14 -0600 Subject: [PATCH 06/15] edit script --- .../bspm/machine_constant/bspm_mach_constants.py | 12 +++++++++--- test.py | 10 ++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py index ccc6a3c5..dd55f2bc 100644 --- a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py +++ b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py @@ -1,12 +1,18 @@ import numpy as np import pandas as pd -from tqdm import tqdm import win32com.client from .....machines.bspm import BSPM_Machine, BSPM_Machine_Oper_Pt from ...bspm.jmag_2d_config import JMAG_2D_Config from ...bspm.jmag_2d import BSPM_EM_Analyzer from typing import Tuple, Union from dataclasses import dataclass + +try: + from tqdm import tqdm +except ImportError: + def tqdm(iterator, *args, **kwargs): + return iterator + ########################################################################### from functools import cached_property, lru_cache # @lru_cache decoractor saves the return value of the method, @@ -131,7 +137,7 @@ def analyze(self, problem: BSPMMachineConstantProblem): @cached_property def Kf(self)-> Union[float, None]: - "Machine Force Constant [N/A]" + "Machine Suspension Force Constant [N/A]" if self.problem.solve_Kf: Is_list, force = self.Kf_data Kf,_ = np.polyfit(Is_list,force,deg=1) @@ -151,7 +157,7 @@ def Kt(self)->Union[float, None]: @cached_property def Kdelta(self)->Union[float, None]: - "Machine Displacement Constant [N/m]" + "Machine Displacement Stiffness Constant [N/m]" if self.problem.solve_Kdelta: disp,force = self.Kdelta_data Kdelta,_ = np.polyfit(disp,force,deg=1) diff --git a/test.py b/test.py index 648adebc..3e62125a 100644 --- a/test.py +++ b/test.py @@ -44,7 +44,7 @@ "r_sh": 8.9e-3, #[m]** 8.9e-3 "l_st": 25e-3, #[m] "d_sl": 1e-3, #[m] - "delta_sl": 0.00011 #9.63e-5, #[m] + "delta_sl": 9.63e-5, #[m] } bspm_parameters = { @@ -119,9 +119,9 @@ ######################################################### jmag_config = JMAG_2D_Config( no_of_rev_1TS=1, - no_of_rev_2TS=1, + no_of_rev_2TS=2, no_of_steps_per_rev_1TS=36, - no_of_steps_per_rev_2TS=720, + no_of_steps_per_rev_2TS=360, mesh_size=2e-3, magnet_mesh_size=1e-3, airgap_mesh_radial_div=5, @@ -151,9 +151,7 @@ analyzer = bmc.BSPMMachineConstantAnalyzer( jmag_config, - Kf_Kt_step=11, - Kdelta_coords=coord, - Kphi_step=11) +) result = analyzer.analyze(problem) print(result.Kt) From 1f50ee611cac5bd047c54a09e2f94d7c5ecc2334 Mon Sep 17 00:00:00 2001 From: Anson Chan Date: Tue, 9 Jan 2024 20:04:54 -0600 Subject: [PATCH 07/15] remove legacy code and unused python code --- .../bspm/machine_constant/_Kf_methods.py | 131 ----- .../legacy_bspm_mach_constants.py | 499 ------------------ 2 files changed, 630 deletions(-) delete mode 100644 mach_eval/analyzers/electromagnetic/bspm/machine_constant/_Kf_methods.py delete mode 100644 mach_eval/analyzers/electromagnetic/bspm/machine_constant/legacy_bspm_mach_constants.py diff --git a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/_Kf_methods.py b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/_Kf_methods.py deleted file mode 100644 index f2f3f651..00000000 --- a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/_Kf_methods.py +++ /dev/null @@ -1,131 +0,0 @@ -from functools import cached_property, lru_cache - -def testing(self): - return self.Iq_step - -# @cached_property -# def Kf(self): -# # Run simulations to obtain necessary data -# force_df_list, torque_df_list = self.run_Kf_Kt_simulations() - -# # calculate the number of initial data point to ignore -# idx_ignore = int(360/angle_step*rev_ignore) -# print(idx_ignore) - -# # extract average force value from each run -# force = [] -# for force_df in force_df_list: -# force_prob = ProcessForceDataProblem( -# Fx=force_df["ForCon:1st"].iloc[idx_ignore:].to_numpy(), -# Fy=force_df["ForCon:2nd"].iloc[idx_ignore:].to_numpy()) -# force_ana = ProcessForceDataAnalyzer() -# _,_,f_abs_avg,_,_ = force_ana.analyze(force_prob) -# force.append(f_abs_avg) - -# Is = [np.sqrt(2)*I_rms*(1-Iq_pu_val) for Iq_pu_val in Iq_pu] -# Kf,_ = np.polyfit(Is,force,deg=1) - -# return Kf - -# @cached_property -# def Kt(self): -# # extract average torque value from each run -# torque = np.zeros(len(self.torque_df_list)) -# for idx, torque_df in enumerate(self.torque_df_list): -# torque_prob = ProcessTorqueDataProblem(torque_df["TorCon"].iloc[idx_ignore:].to_numpy()) -# torque_analyzer = ProcessTorqueDataAnalyzer() -# torque_avg,_ = torque_analyzer.analyze(torque_prob) -# torque[idx] = torque_avg - -# Iq = [np.sqrt(2)*I_rms*Iq_pu_val for Iq_pu_val in Iq_pu] -# Kt,_ = np.polyfit(Iq,torque,deg=1) - -# return Kt - -# @lru_cache -# def run_Kf_Kt_simulations(self): - -# # use operating point rated current and result for speed -# force_df_list = [] -# torque_df_list = [] - -# Iq_pu_list = np.linspace(0,np.sqrt(2)*self.machine.Rated_current) -# tqdm_Iq = tqdm(self.Iq_list, desc='') -# for idx, Iq_val in enumerate(tqdm_Iq): -# tqdm_Iq.set_description("Run "+ str(idx+1)+"/"+str(len(Iq_val)) -# +" , Iq_pu = "+str(round(Iq_val,2))) - -# # duplicate initial study -# self.init_model.DuplicateStudyName(self.init_study_name, -# self.init_study_name+'_Kf_Kt' -# +'_Iq_'+str(round(Iq_val,2)).replace(".", "_") -# +'_Is_'+str(round(1-Iq_val, 2)).replace(".", "_"),True) - -# # set duplicated study as present study -# present_study = self.toolJd.GetCurrentStudy() - -# # rotor should rotate one full revolution over 1 second -# # 1 rev/s = 60 rev/min -# speed = 60 -# self._set_study_speed(present_study,speed) -# excitation_freq = self._get_elec_freq(present_study,speed) - -# # set study step control -# self._set_study_steps(present_study,angle_step,rev) - -# # obtain handle to circuit for present study -# circuit = present_study.GetCircuit() - -# # set torque and suspension currents for all coils -# ampT = 2*Iq_pu_val*I_rms*np.sqrt(2) -# ampS = (1-Iq_pu_val)*I_rms*np.sqrt(2) - -# func = self.toolJd.FunctionFactory().Composite() -# f1 = self.toolJd.FunctionFactory().Sin(ampT, excitation_freq, 0) -# # "freq" variable cannot be used here. So pay extra attension when you create new case of a different freq. -# func.AddFunction(f1) -# circuit.GetComponent("CS_t-1").SetFunction(func) - -# func = self.toolJd.FunctionFactory().Composite() -# f1 = self.toolJd.FunctionFactory().Sin(ampT, excitation_freq, -120) -# func.AddFunction(f1) -# circuit.GetComponent("CS_t-2").SetFunction(func) - -# func = self.toolJd.FunctionFactory().Composite() -# f1 = self.toolJd.FunctionFactory().Sin(ampT, excitation_freq, -240) -# func.AddFunction(f1) -# circuit.GetComponent("CS_t-3").SetFunction(func) - -# func = self.toolJd.FunctionFactory().Composite() -# f1 = self.toolJd.FunctionFactory().Sin(ampS, excitation_freq, 0) -# f2 = self.toolJd.FunctionFactory().Sin(-ampT / 2, excitation_freq, 0) -# func.AddFunction(f1) -# func.AddFunction(f2) -# circuit.GetComponent("CS_s-1").SetFunction(func) - -# func = self.toolJd.FunctionFactory().Composite() -# f1 = self.toolJd.FunctionFactory().Sin(ampS, excitation_freq, 120) -# f2 = self.toolJd.FunctionFactory().Sin(-ampT / 2, excitation_freq, -120) -# func.AddFunction(f1) -# func.AddFunction(f2) -# circuit.GetComponent("CS_s-2").SetFunction(func) - -# func = self.toolJd.FunctionFactory().Composite() -# f1 = self.toolJd.FunctionFactory().Sin(ampS, excitation_freq, 240) -# f2 = self.toolJd.FunctionFactory().Sin(-ampT / 2, excitation_freq, -240) -# func.AddFunction(f1) -# func.AddFunction(f2) -# circuit.GetComponent("CS_s-3").SetFunction(func) - -# # run the study -# present_study.RunAllCases() - -# # extract FEA results from CSV -# force_df = self._extract_csv_results(present_study.GetName(), "Force") -# torque_df = self._extract_csv_results(present_study.GetName(),"Torque") - -# force_df_list.append(force_df) -# torque_df_list.append(torque_df) - -# self.force_df_list = force_df_list -# return force_df_list, torque_df_list diff --git a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/legacy_bspm_mach_constants.py b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/legacy_bspm_mach_constants.py deleted file mode 100644 index 53954e20..00000000 --- a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/legacy_bspm_mach_constants.py +++ /dev/null @@ -1,499 +0,0 @@ -import numpy as np -import os -from tqdm.autonotebook import tqdm -import pandas as pd -import win32com.client - -from eMach.mach_eval.analyzers.force_vector_data import ( - ProcessForceDataProblem, - ProcessForceDataAnalyzer, -) -from eMach.mach_eval.analyzers.torque_data import ( - ProcessTorqueDataProblem, - ProcessTorqueDataAnalyzer, -) - -# change current working directory to file location -os.chdir(os.path.dirname(__file__)) -# add the directory immediately above this file's directory to path for module import -#sys.path.append("../../..") - -class BSPMMachineConstantProblem: - def __init__( - self - ) -> None: - pass - -class BSPMMachineConstantAnalyzer: - """Analyzer for determining machine constants of BSPM in JMAG. - - Attributes: - project_name (str): .jproj JMAG file to evaluate. - """ - def __init__(self, project_name:str) -> 'BSPMMachineConstantAnalyzer': - self.project_name = project_name - - # open .jproj file and obtain initial model and study properties - toolJd = self._open_JMAG_file() - init_model,init_study,init_study_name,init_properties = self._get_init_study(toolJd) - - self.toolJd = toolJd - self.init_model = init_model - self.init_study = init_study - self.init_study_name = init_study_name - self.init_properties = init_properties - - # set result csv path - self.csv_path = self._set_csv_result_path() - - print("Project File Name: " + project_name+".jproj") - print("Initial Model Name: " + init_model.GetName()) - print("Initial Study Name: " + init_study_name) - - ###################################################################################### - #################################### Kf Kt methods ################################### - ###################################################################################### - - def get_Kf_Kt(self, Iq_pu:list, I_rms:float, angle_step:int=1, rev:int=3, rev_ignore:int=1): - """Run JMAG simulation and analyze force and torque data to obtain Kf and Kt value. - - Args: - Iq_pu (list): list of Iq per unit values to evaluate. Values should be between 0-1. - I_rms (float): machine rated RMS current. - angle_step (int, optional): step size of rotational angle. Defaults to 1. - rev (int, optional): number of revolution rotor will take in the simulation. Defaults to 3. - rev_ignore (int, optional): number of initial revolutions to ignore from simulation to ignore eddy current effect. Defaults to 1. - - Returns: - Kf, Kt - """ - - # Run simulations to obtain necessary data - force_df_list, torque_df_list = self.run_Kf_Kt_simulations(Iq_pu, I_rms, angle_step, rev) - - # calculate the number of initial data point to ignore - idx_ignore = int(360/angle_step*rev_ignore) - print(idx_ignore) - - # extract average force value from each run - force = [] - for force_df in force_df_list: - force_prob = ProcessForceDataProblem( - Fx=force_df["ForCon:1st"].iloc[idx_ignore:].to_numpy(), - Fy=force_df["ForCon:2nd"].iloc[idx_ignore:].to_numpy()) - force_ana = ProcessForceDataAnalyzer() - _,_,f_abs_avg,_,_ = force_ana.analyze(force_prob) - force.append(f_abs_avg) - - # extract average torque value from each run - torque = [] - for torque_df in torque_df_list: - torque_prob = ProcessTorqueDataProblem(torque_df["TorCon"].iloc[idx_ignore:].to_numpy()) - torque_analyzer = ProcessTorqueDataAnalyzer() - torque_avg,_ = torque_analyzer.analyze(torque_prob) - torque.append(torque_avg) - - # get fitted slope value - Iq = [np.sqrt(2)*I_rms*Iq_pu_val for Iq_pu_val in Iq_pu] - Kt,_ = np.polyfit(Iq,torque,deg=1) - - Is = [np.sqrt(2)*I_rms*(1-Iq_pu_val) for Iq_pu_val in Iq_pu] - Kf,_ = np.polyfit(Is,force,deg=1) - - print("Kt = "+str(Kt)) - print("Kf = "+str(Kf)) - return Kt, Kf - - def run_Kf_Kt_simulations(self, Iq_pu:list, I_rms:float, angle_step:int, rev:int): - """ - Run simulations in JMAG to obtain data used for determining Kf and Kt constant. - - Args: - Iq_pu (list): list of Iq per unit values to evaluate. Values should be between 0-1. - I_rms (float): machine rated RMS current. - angle_step (int): step size of rotational angle. - rev (int): number of revolution rotor will take in the simulation. - - Returns: - force_df_list, torque df_list (tuple): force and torque dataframe lists. - """ - print("Running force Kf and torqe Kt simulations...") - print("Angle_Step = "+str(angle_step)) - print("Number of revolution = "+str(rev)) - - # iterate through each specified coordinates - force_df_list = [] - torque_df_list = [] - tqdm_Iq_pu = tqdm(Iq_pu, desc='') - for idx, Iq_pu_val in enumerate(tqdm_Iq_pu): - tqdm_Iq_pu.set_description("Run "+ str(idx+1)+"/"+str(len(Iq_pu)) - +" , Iq_pu = "+str(round(Iq_pu_val,2))) - - # duplicate initial study - self.init_model.DuplicateStudyName(self.init_study_name, - self.init_study_name+'_Kf_Kt' - +'_Iq_'+str(round(Iq_pu_val,2)).replace(".", "_") - +'_Is_'+str(round(1-Iq_pu_val, 2)).replace(".", "_"),True) - - # set duplicated study as present study - present_study = self.toolJd.GetCurrentStudy() - - # rotor should rotate one full revolution over 1 second - # 1 rev/s = 60 rev/min - speed = 60 - self._set_study_speed(present_study,speed) - excitation_freq = self._get_elec_freq(present_study,speed) - - # set study step control - self._set_study_steps(present_study,angle_step,rev) - - # obtain handle to circuit for present study - circuit = present_study.GetCircuit() - - # set torque and suspension currents for all coils - ampT = 2*Iq_pu_val*I_rms*np.sqrt(2) - ampS = (1-Iq_pu_val)*I_rms*np.sqrt(2) - - func = self.toolJd.FunctionFactory().Composite() - f1 = self.toolJd.FunctionFactory().Sin(ampT, excitation_freq, 0) - # "freq" variable cannot be used here. So pay extra attension when you create new case of a different freq. - func.AddFunction(f1) - circuit.GetComponent("CS_t-1").SetFunction(func) - - func = self.toolJd.FunctionFactory().Composite() - f1 = self.toolJd.FunctionFactory().Sin(ampT, excitation_freq, -120) - func.AddFunction(f1) - circuit.GetComponent("CS_t-2").SetFunction(func) - - func = self.toolJd.FunctionFactory().Composite() - f1 = self.toolJd.FunctionFactory().Sin(ampT, excitation_freq, -240) - func.AddFunction(f1) - circuit.GetComponent("CS_t-3").SetFunction(func) - - func = self.toolJd.FunctionFactory().Composite() - f1 = self.toolJd.FunctionFactory().Sin(ampS, excitation_freq, 0) - f2 = self.toolJd.FunctionFactory().Sin(-ampT / 2, excitation_freq, 0) - func.AddFunction(f1) - func.AddFunction(f2) - circuit.GetComponent("CS_s-1").SetFunction(func) - - func = self.toolJd.FunctionFactory().Composite() - f1 = self.toolJd.FunctionFactory().Sin(ampS, excitation_freq, 120) - f2 = self.toolJd.FunctionFactory().Sin(-ampT / 2, excitation_freq, -120) - func.AddFunction(f1) - func.AddFunction(f2) - circuit.GetComponent("CS_s-2").SetFunction(func) - - func = self.toolJd.FunctionFactory().Composite() - f1 = self.toolJd.FunctionFactory().Sin(ampS, excitation_freq, 240) - f2 = self.toolJd.FunctionFactory().Sin(-ampT / 2, excitation_freq, -240) - func.AddFunction(f1) - func.AddFunction(f2) - circuit.GetComponent("CS_s-3").SetFunction(func) - - # run the study - present_study.RunAllCases() - - # extract FEA results from CSV - force_df = self._extract_csv_results(present_study.GetName(), "Force") - torque_df = self._extract_csv_results(present_study.GetName(),"Torque") - force_df_list.append(force_df) - torque_df_list.append(torque_df) - - self.force_df_list = force_df_list - return force_df_list, torque_df_list - - ###################################################################################### - #################################### Kdelta methods ################################## - ###################################################################################### - - def get_Kdelta(self, coordinates:list, angle_step:int=1, rev:int=3, rev_ignore:int=1): - """Run JMAG simulation and analyze force and displacement data to obtain Kdelta value. - - Args: - coordinates (list): list of coordinates to evaluate in simulation - angle_step (int, optional): step size of rotational angle. Defaults to 1. - rev (int, optional): number of revolution rotor will take in the simulation. Defaults to 1. - rev_ignore (int, optional): number of initial revolutions to ignore from simulation to ignore eddy current effect. Defaults to 1. - - Returns: - _type_: _description_ - """ - # run simualtions on all specified coordinates - force_df_list = self.run_Kdelta_simulations(coordinates,angle_step,rev) - - # calculate the number of initial data point to ignore - idx_ignore = int(360/angle_step*rev_ignore) - - force = [] - for force_df in force_df_list: - force_prob = ProcessForceDataProblem( - Fx=force_df["ForCon:1st"].iloc[idx_ignore:].to_numpy(), - Fy=force_df["ForCon:2nd"].iloc[idx_ignore:].to_numpy()) - force_ana = ProcessForceDataAnalyzer() - _,_,f_abs_avg,_,_ = force_ana.analyze(force_prob) - force.append(f_abs_avg) - disp = [np.linalg.norm(coord) for coord in coordinates] - - # combine and sort force and displacement data and unzip into - # two seperate list after sorting - disp, force = (list(t) for t in zip(*sorted(zip(disp,force)))) - - # get fitted slope value - K_delta,_ = np.polyfit(disp,force,deg=1) - - return K_delta - - def run_Kdelta_simulations(self, coordinates:list, angle_step:int, rev:int): - """Run simulations in JMAG to obtain data used for determining Kdelta constant""" - print("Running Kdelta rotor displacement simulations...") - print("Angle_Step = "+str(angle_step)) - print("Number of revolution = "+str(rev)) - - # determine if model is 2D or 3D - is_2D = self._is_2D_model(self.init_model) - - force_df_list = [] - tqdm_coord = tqdm(coordinates, desc='') - # iterate through each specified coordinates - for idx, coord in enumerate(tqdm_coord): - tqdm_coord.set_description("Run " + str(idx+1)+"/"+str(len(coordinates)) - +" , Co-ord = "+str(coord)) - x_coord = coord[0] - y_coord = coord[1] - - # set rotor offset depending on model type - if is_2D: - present_study = self._create_Kdelta_2D_study(x=x_coord,y=y_coord) - else: - present_study = self._create_Kdelta_3D_study(x=x_coord,y=y_coord) - - # set study step control - # rotor should rotate one full revolution over 1 second - self._set_study_steps(present_study, angle_step, rev) - - # set rotor angular velocity - # 60 RPM = 1 rev/sec - speed = 60 - self._set_study_speed(present_study, speed) - - # obtain handle to circuit for present study - circuit = present_study.GetCircuit() - - # zero current for all coils - function1 = self.toolJd.FunctionFactory().Constant(0) - circuit.GetComponent("CS_t-1").SetFunction(function1) - circuit.GetComponent("CS_t-2").SetFunction(function1) - circuit.GetComponent("CS_t-3").SetFunction(function1) - circuit.GetComponent("CS_s-1").SetFunction(function1) - circuit.GetComponent("CS_s-2").SetFunction(function1) - circuit.GetComponent("CS_s-3").SetFunction(function1) - - # run the study - present_study.RunAllCases() - - # extract FEA results from CSV - force_df = self._extract_csv_results(present_study.GetName(),"Force") - force_df_list.append(force_df) - - self.force_df_list = force_df_list - return force_df_list - - def _create_Kdelta_2D_study(self,x,y): - """Offset rotor by enabling eccentricity setting under RotCon condition""" - - # duplicate initial study - # replace all decimal in strings with underscore to avoid windows file naming error - self.init_model.DuplicateStudyName(self.init_study_name, - self.init_study_name+'_Kdelta' - +'_X'+str(x).replace(".", "_") - +'_Y'+str(y).replace(".", "_"),True) - - present_study = self.toolJd.GetCurrentStudy() - present_study.GetCondition(0).SetValue("UseEccentricity", 1) - present_study.GetCondition(0).SetXYZPoint("PartOffset",x,y,0) - present_study.GetCondition(0).SetXYZPoint("AxisOffset",x,y,0) - return present_study - - def _create_Kdelta_3D_study(self,x,y): - """Offset rotor by repositioning rotor in GeometryEditor""" - - # Open Geometry Editior and offset rotor from stator center - self.toolJd.SetCurrentModel(0) - self.init_model.RestoreCadLink() - geomApp = self.toolJd.CreateGeometryEditor() - - # select rotor parts - shaft = geomApp.GetDocument().GetAssembly().GetItem("Shaft") - rotmag = geomApp.GetDocument().GetAssembly().GetItem("RotorMagnet") - rotorCore = geomApp.GetDocument().GetAssembly().GetItem("NotchedRotor") - geomApp.GetDocument().GetSelection().Add(rotorCore) - geomApp.GetDocument().GetSelection().Add(shaft) - geomApp.GetDocument().GetSelection().Add(rotmag) - - # translate rotor - translation = geomApp.GetDocument().GetAssemblyManager().CreateMovePartParameter() - translation.SetProperty("MoveX", x) - translation.SetProperty("MoveY", y) - geomApp.GetDocument().GetAssemblyManager().Execute(translation) - - # update model and close geometry editor - # everytime model is updated, new model is created - self.toolJd.GetCurrentModel().UpdateCadModel() - geomApp.SaveCurrent() - geomApp.Quit() - - # set current study and renanme - present_study = self.toolJd.GetCurrentStudy() - present_study.SetName(self.init_study_name+'_displacement' - +'_X'+str(x).replace(".", "_") - +'_Y'+str(y).replace(".", "_")) - present_model = self.toolJd.GetCurrentModel() - present_model.SetName(self.init_study_name+'_displacement' - +'_X'+str(x).replace(".", "_") - +'_Y'+str(y).replace(".", "_")) - - # align axis of rotation to center of shaft - present_study.GetCondition("RotCon").SetXYZPoint("Origin", x, y, 1) - present_study.GetCondition("TorCon").SetXYZPoint("Origin", x, y, 1) - - # create face set for creating symmetry boundary - present_model.GetSetList().CreateFaceSet("Symmetry_Face_bottom") - symmetry_face1 = present_model.GetSetList().GetSet("Symmetry_Face_bottom") - symmetry_face1.SetUpdateByRelation(False) - symmetry_face1.SetMatcherType("OnPlane") - symmetry_face1.SetXYZPoint("direction", 0, 0, 1) - symmetry_face1.SetXYZPoint("origin", 0, 0, 0) - symmetry_face1.SetParameter("tolerance", 1e-6) - symmetry_face1.Rebuild() - - present_model.GetSetList().CreateFaceSet("Symmetry_Face_top") - symmetry_face2 = present_model.GetSetList().GetSet("Symmetry_Face_top") - symmetry_face2.SetUpdateByRelation(False) - symmetry_face2.SetMatcherType("OnPlane") - symmetry_face2.SetXYZPoint("direction", 0, 0, 1) - # FIND way to get coil height - symmetry_face2.SetXYZPoint("origin", 0, 0, 22.5) - symmetry_face2.SetParameter("tolerance", 1e-6) - symmetry_face2.Rebuild() - - # create new symmetry boundary condition - present_study.CreateCondition("SymmetryBoundary", "SymBound") - sym_bound = present_study.GetCondition("SymBound") - sym_bound.ClearParts() - sym_bound.AddSet(symmetry_face1, 0) - sym_bound.AddSet(symmetry_face2, 0) - - return present_study - - - ###################################################################################### - #################################### Kphi methods #################################### - ###################################################################################### - - def get_Kphi(self): - pass - - def run_Kphi_simulations(self): - pass - - ###################################################################################### - - def _get_elec_freq(self,study,speed): - """Calculate electric frequency to acheiev specified speed - - Args: - study (): instance of JMAG study to evaluate - speed (float): speed of rotor in RPM - - Returns: - freq: electric frequency in Hz - """ - - # obtain magnet pole pair value - p = study.GetMaterial("Magnet").GetValue("Poles")/2 - - # convert RPM to Hz - freq = speed/60*p - return freq - - def _set_study_speed(self, study, speed=60): - """Set rotor speed under RotCon""" - study.GetCondition(0).SetValue("AngularVelocity",speed) - - def _set_study_steps(self,study, - angle_step:int, - rev:int): - """Set the number of steps in transient study based on angle step size and number of revolutions. - - Args: - study (): instance of JMAG study to evaluate - angle_step (int): step size for rotational angle in deg - rev (int): number of revolutions to evaluate study - """ - - tran_steps = rev*360/angle_step - study.GetStep().SetValue("Step",tran_steps) - study.GetStep().SetValue("StepType",1) - study.GetStep().SetValue("StepDivision",tran_steps-1) - study.GetStep().SetValue("EndPoint", rev) - - def _is_2D_model(self,model): - """Check if model in JMAG project is 2D""" - if model.GetDimension() == 2: - print("2D model detected...") - return True - else: - print("3D model detected...") - return False - - def _get_init_study(self,toolJd): - """Obtain initial JMAG study and properties""" - toolJd.SetCurrentModel(0) - init_model = toolJd.GetModel(0) - init_study = toolJd.GetStudy(0) - init_study_name = init_study.GetName() - init_properties = init_study.GetStudyProperties() - return init_model,init_study,init_study_name,init_properties - - def _open_JMAG_file(self): - """Open and load specified JMAG file""" - toolJd = win32com.client.Dispatch("designer.Application") - toolJd.Show() - toolJd.load(os.path.dirname(__file__)+"/run_data/"+self.project_name+".jproj") - return toolJd - - def _set_csv_result_path(self): - """Set JMAG .csv result path and create one if not exist""" - csv_path = os.path.dirname(__file__)+"/run_data/"+self.project_name+"_mach_const_results_csv/" - - # create result folder if not exist - if not os.path.exists(csv_path): - os.mkdir(csv_path) - print("CSV result folder created at "+ csv_path) - - self.init_properties.SetValue("CsvOutputPath",csv_path) - return csv_path - - def _extract_csv_results(self, study_name, type:str): - """Extract coil flux linkage data from JMAG output .csv files""" - csv_type_dict = {'Force':'_force.csv', - 'Torque':'_torque.csv', - 'Flux':'_flux_of_fem_coil.csv'} - path = self.csv_path + study_name + csv_type_dict[type] - df = pd.read_csv(path, skiprows=6) - return df - -################################################################## -analyzer = BSPMMachineConstantAnalyzer("BP4") - -coord = [] -for x in np.linspace(-0.3,0.3,3): - for y in np.linspace(-0.3,0.3,3): - coord.append([x,y]) -coord.append([0,0.2]) -coord.append([0,0.4]) - -Iq_pu = np.linspace(0,1,11) -analyzer.get_Kf_Kt(Iq_pu, I_rms = 18) -analyzer.get_Kdelta(coord) \ No newline at end of file From e1c19296c27d316650b7de8c020f259c922c2acc Mon Sep 17 00:00:00 2001 From: Anson Chan Date: Fri, 12 Jan 2024 15:15:03 -0600 Subject: [PATCH 08/15] edit rst docs --- .../bspm_mach_constants_analyzer.rst | 77 ++++++++++++++++--- docs/source/EM_analyzers/index.rst | 1 + .../input_bspm_mach_constants_analyzer.csv | 10 +-- .../input_bspm_mach_constants_problem.csv | 7 ++ 4 files changed, 77 insertions(+), 18 deletions(-) create mode 100644 docs/source/EM_analyzers/input_bspm_mach_constants_problem.csv diff --git a/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst b/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst index 1cb7dfc2..d7aebb5c 100644 --- a/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst +++ b/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst @@ -6,21 +6,21 @@ This analyzer determines the machine constants (:math:`k_t, k_f, k_\delta` and : Model Background **************** -This analyzer utilizes scripts within eMach to generate `BSPM_Machine` and `BSPM_Machine_Oper_Pt` objects for performing machine constant analysis. +This analyzer utilizes scripts within eMach to generate ``BSPM_Machine`` and ``BSPM_Machine_Oper_Pt`` objects for performing machine constant analysis. Torque Contant :math:`k_t` ------------------------------------ -The machine torque constant :math:`k_t` can be expressed using the following equaiton, +The machine torque constant :math:`k_t` can be computed using the following expression, .. math:: \tau = k_t i_q -where 𝜏 is the torque and :math:`i_q` is the injected torque current. +where 𝜏 is torque and :math:`i_q` is the torque current. Suspension Force :math:`k_f` & Displacement Stiffness Constant :math:`k_\delta` -------------------------------------------------------------------------------------------------- -The suspension force constant :math:`k_f` and displacement stiffness constant :math:`k_\delta` can be expressed using the following equaiton, +The suspension force constant :math:`k_f` and displacement stiffness constant :math:`k_\delta` can be computed using the following expression, .. math:: @@ -45,14 +45,31 @@ where, 𝜔 is angular velocity in rad/s and :math:`\vec{v_m}` is the RMS value Input from User ********************************* -In order to define the problem class, user must specify the geometry as well as the operating point of the BSPM machine. -For defining the `BSPM_Machine` and `BSPM_Machine_Oper_Pt` objects, user can refer to :doc:`../machines/bspm/index.rst` +To define the problem class, the user needs to provide the ``BSPM_Machine`` and ``BSPM_Machine_Oper_Pt`` objects, which specify both the properties and the operating point of the BSPM machine intended for evaluation. +For defining these objects, user can refer to the :doc:`BSPM Design <../machines/bspm/index>` page. -.. csv-table:: results of bspm machine constant analyzer +.. csv-table:: Input for BSPM machine constants problem class + :file: input_bspm_mach_constants_problem.csv + :widths: 20, 20, 30 + :header-rows: 1 + +.. csv-table:: Input for BSPM machine constants analyzer class :file: input_bspm_mach_constants_analyzer.csv - :widths: 20, 20, 70 + :widths: 20, 20, 30 :header-rows: 1 +The default value of ``Kdelta_coords`` is shown below. + +.. code-block:: python + + Kdelta_coords: list = [[x, y] for x in np.linspace(-0.3,0.3,3) + for y in np.linspace(-0.3,0.3,3)], + + +Import modules +------------------------------------ +The following code imports all the required modules for performing BSPM machine constants analysis. Users can paste this code into their scripts and execute it to ensure the modules can imported properly. + .. code-block:: python import mach_eval.analyzers.electromagnetic.bspm.machine_constant.bspm_mach_constants as bmc @@ -72,6 +89,12 @@ For defining the `BSPM_Machine` and `BSPM_Machine_Oper_Pt` objects, user can ref import os import numpy as np +Define and create ``BSPM Machine`` object +------------------------------------ +User can paste the following sample BSPM machine design to create the ``BSPM_machine`` object. + +.. code-block:: python + ######################################################### # CREATE BSPM MACHINE OBJECT ######################################################### @@ -152,6 +175,12 @@ For defining the `BSPM_Machine` and `BSPM_Machine_Oper_Pt` objects, user can ref bspm_dimensions, bspm_parameters, bspm_materials, bspm_winding ) +Define and create ``BSPM_Machine_Oper_Pt`` object +------------------------------------ +Users can paste the provided sample BSPM operating point code to instantiate the ``BSPM_Machine_Oper_Pt`` object. + +.. code-block:: python + ######################################################### # DEFINE BSPM OPERATING POINT ######################################################### @@ -165,6 +194,13 @@ For defining the `BSPM_Machine` and `BSPM_Machine_Oper_Pt` objects, user can ref rotor_temp_rise=55, # K ) +Define and create ``JMAG_2D_Config`` object +------------------------------------ +For performing simualtion in JMAG, an instance of ``JMAG_2D_Config`` must be provided (For more information, see :doc:`BSPM JMAG 2D FEA Analyzer `.) +Users can paste the provided sample JMAG configuration code to instantiate the ``JMAG_2D_Config`` object. + +.. code-block:: python + ######################################################### # DEFINE BSPM JMAG SETTINGS ######################################################### @@ -191,6 +227,16 @@ For defining the `BSPM_Machine` and `BSPM_Machine_Oper_Pt` objects, user can ref jmag_visible=True, ) +.. note:: + + The step and mesh size could sigificantly affect the results. User should consider making these values to be more fine. + +Define problem and analyzer object +------------------------------------ +Use the following code to create the problem and analyzer object. + +.. code-block:: python + ######################################################### # DEFINE BSPM MACHINE CONSTANTS PROBLEM ######################################################### @@ -207,7 +253,7 @@ Output to User The attributes of the results class can be summarized in the table below: -.. csv-table:: results of bspm machine constant analyzer +.. csv-table:: results of BSPM machine constants analyzer :file: result_bspm_mach_constants.csv :widths: 30, 70, 30 :header-rows: 1 @@ -225,6 +271,14 @@ Use the following code to run the example analysis: print(result.Kdelta) print(result.Kphi) +.. note:: + + User can install the ``tqdm`` library for a visual progress bar on your terminal when the simulations are running. + +.. note:: + + Depending on the number of evaluation steps specified, a full analysis could take upwards of **one to two hours** to complete. + Running the example case returns the following: .. code-block:: python @@ -234,8 +288,7 @@ Running the example case returns the following: 6935.763575553156 0.00456017028983404 - -The results indicate that the example BSPM machine design has suspension force constant of 1.802 [N/A], -torque constant of 0.0204 [N-m/A_pk], displacement stiffness constant of 6935.76 [N/m] and back-EMF constant of 0.00456 [V_rms/rad/s]. +The results indicate that the example BSPM machine design has a suspension force constant of :math:`k_f = 1.802\; [\frac{N}{A}]`, +torque constant of :math:`k_t = 0.0204 \; [\frac{Nm}{A_{pk}}]`, displacement stiffness constant of :math:`k_\delta = 6935.76\; [\frac{N}{m}]` and back-EMF constant of :math:`k_\phi = 0.00456\; [\frac{V_{rms}}{rad/s}]`. \ No newline at end of file diff --git a/docs/source/EM_analyzers/index.rst b/docs/source/EM_analyzers/index.rst index 26384741..2442790b 100644 --- a/docs/source/EM_analyzers/index.rst +++ b/docs/source/EM_analyzers/index.rst @@ -13,6 +13,7 @@ This section provides documentation for the electromagnetic analyzers supported force_vector_data stator_wdg_res bspm_jmag2d_analyzer + bspm_mach_constants_analyzer winding_factors diff --git a/docs/source/EM_analyzers/input_bspm_mach_constants_analyzer.csv b/docs/source/EM_analyzers/input_bspm_mach_constants_analyzer.csv index cdcb3200..0c5d5366 100644 --- a/docs/source/EM_analyzers/input_bspm_mach_constants_analyzer.csv +++ b/docs/source/EM_analyzers/input_bspm_mach_constants_analyzer.csv @@ -1,7 +1,5 @@ Arguments, Type, Description -machine, 'BSPM_machine', object of type BSPM_machine describing a bearingless spm machine -operating_point,'BSPM_Machine_Oper_Pt', object of type BSPM_Machine_Oper_Pt describing BSPM machine operating point -solve_Kf,bool, -solve_Kt,bool, -solve_Kphi,bool, -solve_Kdelta,bool, \ No newline at end of file +config, ``JMAG_2D_Config``, object of type JMAG_2D_Config describing time step and mesh setting etc +Kf_Kt_step, int (default - *10*), number of steps for evaluating suspension force and torque constant +Kdelta_coords, list (default - *see below*), list of coordinate points [x y] in mm +Kphi_step, int (default - *10*), number of steps for evaluating back-EMF constant diff --git a/docs/source/EM_analyzers/input_bspm_mach_constants_problem.csv b/docs/source/EM_analyzers/input_bspm_mach_constants_problem.csv new file mode 100644 index 00000000..56e791d4 --- /dev/null +++ b/docs/source/EM_analyzers/input_bspm_mach_constants_problem.csv @@ -0,0 +1,7 @@ +Arguments, Type, Description +machine, ``BSPM_machine``, instance of ``BSPM_machine`` +operating_point,``BSPM_Machine_Oper_Pt``, instance of ``BSPM_Machine_Oper_Pt`` +solve_Kf,bool (default - *True*), boolean input to check whether to solve Kf value or not +solve_Kt,bool (default - *True*),boolean input to check whether to solve Kt value or not +solve_Kphi,bool (default - *True*),boolean input to check whether to solve Kphi value or not +solve_Kdelta,bool (default - *True*),boolean input to check whether to solve Kdelta value or not \ No newline at end of file From d8c3e210407af7a511eee177b039b7d17fdb5252 Mon Sep 17 00:00:00 2001 From: Anson Chan Date: Fri, 12 Jan 2024 15:17:06 -0600 Subject: [PATCH 09/15] remove test.py --- .../machine_constant/bspm_mach_constants.py | 1 + test.py | 168 ------------------ 2 files changed, 1 insertion(+), 168 deletions(-) delete mode 100644 test.py diff --git a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py index dd55f2bc..6476d13a 100644 --- a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py +++ b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py @@ -172,6 +172,7 @@ def Kphi(self)->Union[float, None]: speed, bemf = self.Kphi_data Kphi,_ = np.polyfit(speed,bemf,deg=1) return Kphi + # AC else: return None diff --git a/test.py b/test.py deleted file mode 100644 index 3e62125a..00000000 --- a/test.py +++ /dev/null @@ -1,168 +0,0 @@ -import mach_eval.analyzers.electromagnetic.bspm.machine_constant.bspm_mach_constants as bmc -import mach_eval.analyzers.electromagnetic.bspm.machine_constant.bspm_mach_constants as bmc -from mach_eval.machines.materials.electric_steels import Arnon5 -from mach_eval.machines.materials.jmag_library_magnets import N40H -from mach_eval.machines.materials.miscellaneous_materials import ( - CarbonFiber, - Steel, - Copper, - Hub, - Air, -) -from mach_eval.machines.bspm import BSPM_Machine -from mach_eval.machines.bspm.bspm_oper_pt import BSPM_Machine_Oper_Pt -from mach_eval.analyzers.electromagnetic.bspm.jmag_2d_config import JMAG_2D_Config -import os -import numpy as np - -######################################################### -# CREATE BSPM MACHINE OBJECT -######################################################### - -# ** -# Actual machine does not have rotor core, only shaft. -# d_ri and r_sh are set to 0.1[mm] and 8.9[mm] respectively -# to avoid InvalidDesign error. Actual shaft radius is 9[mm]. -# Differences in results are negligble. - -################ DEFINE BP4 ################ -bspm_dimensions = { - "alpha_st": 31.7088, #[deg] - "d_so": 2.02334e-3, #[m] - "w_st": 5.95805e-3, #[m] - "d_st": 18.4967e-3, #[m] - "d_sy": 5.81374e-3, #[m] - "alpha_m": 180, #[m] - "d_m": 3e-3, #[m] - "d_mp": 0, #[m] - "d_ri": 0.1e-3, #[m]** 0.1e-3 - "alpha_so": 15.5, #[deg] - "d_sp": 2.05e-3, #[m] - "r_si": 16.9737e-3, #[m] - "alpha_ms": 180, #[deg] - "d_ms": 0, #[m] - "r_sh": 8.9e-3, #[m]** 8.9e-3 - "l_st": 25e-3, #[m] - "d_sl": 1e-3, #[m] - "delta_sl": 9.63e-5, #[m] -} - -bspm_parameters = { - "p": 1, # number of pole pairs - "ps": 2, # number of suspension pole pairs - "n_m": 1, # - "Q": 6, # number of slots - "rated_speed": 16755.16, #[rad/s] - "rated_power": 8e3, # [W] - "rated_voltage": 8e3/18, # [V_rms] - "rated_current": 18, # [I_rms] #18 - "name": "BP4" -} - -bspm_materials = { - "air_mat": Air, - "rotor_iron_mat": Arnon5, - "stator_iron_mat": Arnon5, - "magnet_mat": N40H, - "rotor_sleeve_mat": CarbonFiber, - "coil_mat": Copper, - "shaft_mat": Steel, - "rotor_hub": Hub, -} - -bspm_winding = { - "no_of_layers": 2, - # layer_phases is a list of lists, the number of lists = no_of_layers - # first list corresponds to coil sides in first layer - # second list corresponds to coil sides in second layer - # the index indicates the slot opening corresponding to the coil side - # string characters are used to represent the phases - "layer_phases": [["U", "W", "V", "U", "W", "V"], - ["V", "U", "W", "V", "U", "W"]], - # layer_polarity is a list of lists, the number of lists = no_of_layers - # first list corresponds to coil side direction in first layer - # second list corresponds to coil side direction in second layer - # the index indicates the slot opening corresponding to the coil side - # + indicates coil side goes into the page, - indicates coil side comes out of page - "layer_polarity": [["+", "-", "+", "-", "+", "-"], - ["+", "-", "+", "-", "+", "-"]], - # coil_groups are a unique property of DPNV windings - # coil group is assigned corresponding to the 1st winding layer - "coil_groups": ["b", "a", "b", "a", "b", "a"], - "pitch": 1, - "Z_q": 45, - "Kov": 1.8, - "Kcu": 0.5, - # add phase current offset to know relative rotor / current angle for creating Iq - "phase_current_offset": -30 -} - -bp4 = BSPM_Machine( - bspm_dimensions, bspm_parameters, bspm_materials, bspm_winding -) - -######################################################### -# DEFINE BSPM OPERATING POINT -######################################################### -bp4_op_pt = BSPM_Machine_Oper_Pt( - Id=0, # I_pu - Iq=0.95, # I_pu - Ix=0, # I_pu - Iy=0.05, # I_pu - speed=160000, # RPM - ambient_temp=25, # C - rotor_temp_rise=55, # K -) - -######################################################### -# DEFINE BSPM JMAG SETTINGS -######################################################### -jmag_config = JMAG_2D_Config( - no_of_rev_1TS=1, - no_of_rev_2TS=2, - no_of_steps_per_rev_1TS=36, - no_of_steps_per_rev_2TS=360, - mesh_size=2e-3, - magnet_mesh_size=1e-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.abspath("") + "/run_data/", - jmag_csv_folder=os.path.abspath("") + "/run_data/JMAG_csv/", - max_nonlinear_iterations=50, - multiple_cpus=True, - num_cpus=4, - jmag_scheduler=False, - jmag_visible=True, -) - -problem = bmc.BSPMMachineConstantProblem(bp4,bp4_op_pt) - -coord = [] -for x in np.linspace(-0.3,0.3,3): - for y in np.linspace(-0.3,0.3,3): - coord.append([x,y]) -coord.append([0,0.2]) -coord.append([0,0.4]) - -analyzer = bmc.BSPMMachineConstantAnalyzer( - jmag_config, -) - -result = analyzer.analyze(problem) -print(result.Kt) -print(result.Kf) -print(result.Kdelta) -print(result.Kphi) - - - - - - - - From 48b66de75f3b112d44107fa65283e6759d0ae95b Mon Sep 17 00:00:00 2001 From: Dante Newman Date: Tue, 16 Jan 2024 19:16:59 -0600 Subject: [PATCH 10/15] Update language of .rst file --- .../bspm_mach_constants_analyzer.rst | 71 ++++++++++--------- docs/source/EM_analyzers/index.rst | 2 +- .../input_bspm_mach_constants_analyzer.csv | 2 +- 3 files changed, 39 insertions(+), 36 deletions(-) diff --git a/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst b/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst index d7aebb5c..de2539e7 100644 --- a/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst +++ b/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst @@ -1,52 +1,51 @@ BSPM Machine Constants Analyzer ######################################################################## -This analyzer determines the machine constants (:math:`k_t, k_f, k_\delta` and :math:`k_\Phi`) of a given BSPM machine design. +This analyzer determines the machine constants (:math:`k_t, k_f, k_\delta,` and :math:`k_\Phi`) of a given BSPM machine design. Model Background **************** -This analyzer utilizes scripts within eMach to generate ``BSPM_Machine`` and ``BSPM_Machine_Oper_Pt`` objects for performing machine constant analysis. +This analyzer utilizes scripts within eMach to generate ``BSPM_Machine`` and ``BSPM_Machine_Oper_Pt`` objects for performing a machine constant analysis. Torque Contant :math:`k_t` ------------------------------------ -The machine torque constant :math:`k_t` can be computed using the following expression, +The machine torque constant, :math:`k_t`, can be computed using the following expression: .. math:: \tau = k_t i_q -where 𝜏 is torque and :math:`i_q` is the torque current. +where :math:`\tau` is torque and :math:`i_q` is the torque current. Suspension Force :math:`k_f` & Displacement Stiffness Constant :math:`k_\delta` -------------------------------------------------------------------------------------------------- -The suspension force constant :math:`k_f` and displacement stiffness constant :math:`k_\delta` can be computed using the following expression, +The suspension force constant, :math:`k_f`, and displacement stiffness constant, :math:`k_\delta`, can be computed using the following expression: .. math:: \vec{F} = k_f \vec{i_s}+k_\delta \vec{\delta} -where 𝛿 is the displacement of the rotor from magnetic center, :math:`F_c` is the force created by current :math:`\vec{i_s}`, -:math:`k_f` is the force constant and :math:`k_\delta` is the displacement stiffness constant +where :math:`\delta` is the displacement of the rotor from the magnetic center, :math:`F_c` is the force created by the current, :math:`\vec{i_s}` is the suspension +current, :math:`k_f` is the force constant, and :math:`k_\delta` is the displacement stiffness constant. Back-EMF Constant :math:`k_\Phi` ------------------------------------ -The machine back-EMF constant :math:`k_\Phi` can be expressed using the following equaiton, +The machine back-EMF constant :math:`k_\Phi` can be expressed using the following equation: .. math:: \vec{v_m} = k_\Phi\omega -where, 𝜔 is angular velocity in rad/s and :math:`\vec{v_m}` is the RMS value of the induced phase voltage. (???) - - +where, :math:`\omega` is angular velocity in rad/s and :math:`\vec{v_m}` is the RMS value of the induced phase voltage. Input from User ********************************* -To define the problem class, the user needs to provide the ``BSPM_Machine`` and ``BSPM_Machine_Oper_Pt`` objects, which specify both the properties and the operating point of the BSPM machine intended for evaluation. -For defining these objects, user can refer to the :doc:`BSPM Design <../machines/bspm/index>` page. +To define the problem class, the user needs to provide the ``BSPM_Machine`` and ``BSPM_Machine_Oper_Pt`` objects, which specify both the properties and the operating +point of the BSPM machine intended for evaluation. For defining these objects, the user can refer to the :doc:`BSPM Design <../machines/bspm/index>` page. The inputs +for each are summarized below: .. csv-table:: Input for BSPM machine constants problem class :file: input_bspm_mach_constants_problem.csv @@ -58,7 +57,7 @@ For defining these objects, user can refer to the :doc:`BSPM Design <../machines :widths: 20, 20, 30 :header-rows: 1 -The default value of ``Kdelta_coords`` is shown below. +The default value of ``Kdelta_coords`` is shown below: .. code-block:: python @@ -66,9 +65,10 @@ The default value of ``Kdelta_coords`` is shown below. for y in np.linspace(-0.3,0.3,3)], -Import modules +Import Modules ------------------------------------ -The following code imports all the required modules for performing BSPM machine constants analysis. Users can paste this code into their scripts and execute it to ensure the modules can imported properly. +The following code imports all the required modules for performing a BSPM machine constants analysis. Users can paste this code into their scripts and execute it +to ensure the modules can imported properly: .. code-block:: python @@ -89,9 +89,10 @@ The following code imports all the required modules for performing BSPM machine import os import numpy as np -Define and create ``BSPM Machine`` object ------------------------------------- -User can paste the following sample BSPM machine design to create the ``BSPM_machine`` object. +Define and Create ``BSPM Machine`` Object +------------------------------------------ + +The user can paste the following sample BSPM machine design to create the ``BSPM_machine`` object: .. code-block:: python @@ -175,9 +176,10 @@ User can paste the following sample BSPM machine design to create the ``BSPM_mac bspm_dimensions, bspm_parameters, bspm_materials, bspm_winding ) -Define and create ``BSPM_Machine_Oper_Pt`` object ------------------------------------- -Users can paste the provided sample BSPM operating point code to instantiate the ``BSPM_Machine_Oper_Pt`` object. +Define and Create ``BSPM_Machine_Oper_Pt`` Object +------------------------------------------------- + +The users can paste the provided sample BSPM operating point code to instantiate the ``BSPM_Machine_Oper_Pt`` object: .. code-block:: python @@ -194,10 +196,11 @@ Users can paste the provided sample BSPM operating point code to instantiate the rotor_temp_rise=55, # K ) -Define and create ``JMAG_2D_Config`` object ------------------------------------- +Define and Create ``JMAG_2D_Config`` Object +------------------------------------------- + For performing simualtion in JMAG, an instance of ``JMAG_2D_Config`` must be provided (For more information, see :doc:`BSPM JMAG 2D FEA Analyzer `.) -Users can paste the provided sample JMAG configuration code to instantiate the ``JMAG_2D_Config`` object. +Users can paste the provided sample pf the JMAG configuration code to instantiate the ``JMAG_2D_Config`` object: .. code-block:: python @@ -229,11 +232,12 @@ Users can paste the provided sample JMAG configuration code to instantiate the ` .. note:: - The step and mesh size could sigificantly affect the results. User should consider making these values to be more fine. + The step and mesh size could significantly affect the results. The user should consider making these values to be more fine. -Define problem and analyzer object +Define Problem and Analyzer Object ------------------------------------ -Use the following code to create the problem and analyzer object. + +Use the following code to create the problem and analyzer object: .. code-block:: python @@ -253,7 +257,7 @@ Output to User The attributes of the results class can be summarized in the table below: -.. csv-table:: results of BSPM machine constants analyzer +.. csv-table:: Results of BSPM machine constants analyzer :file: result_bspm_mach_constants.csv :widths: 30, 70, 30 :header-rows: 1 @@ -273,7 +277,7 @@ Use the following code to run the example analysis: .. note:: - User can install the ``tqdm`` library for a visual progress bar on your terminal when the simulations are running. + The user can install the ``tqdm`` library for a visual progress bar on your terminal when the simulations are running. .. note:: @@ -288,7 +292,6 @@ Running the example case returns the following: 6935.763575553156 0.00456017028983404 -The results indicate that the example BSPM machine design has a suspension force constant of :math:`k_f = 1.802\; [\frac{N}{A}]`, -torque constant of :math:`k_t = 0.0204 \; [\frac{Nm}{A_{pk}}]`, displacement stiffness constant of :math:`k_\delta = 6935.76\; [\frac{N}{m}]` and back-EMF constant of :math:`k_\phi = 0.00456\; [\frac{V_{rms}}{rad/s}]`. - - \ No newline at end of file +The results indicate that the example BSPM machine design has a suspension force constant of :math:`k_f = 1.802\; [\frac{N}{A}]`, a torque constant of +:math:`k_t = 0.0204 \; [\frac{Nm}{A_{pk}}]`, a displacement stiffness constant of :math:`k_\delta = 6935.76\; [\frac{N}{m}]`, and back-EMF constant of +:math:`k_\phi = 0.00456\; [\frac{V_{rms}}{rad/s}]`. \ No newline at end of file diff --git a/docs/source/EM_analyzers/index.rst b/docs/source/EM_analyzers/index.rst index 7a92c35b..cd7547a8 100644 --- a/docs/source/EM_analyzers/index.rst +++ b/docs/source/EM_analyzers/index.rst @@ -13,7 +13,7 @@ This section provides documentation for the electromagnetic analyzers supported Force Data Stator Winding Resistance BSPM JMAG 2D FEA - BSPM Machine Constants + BSPM Machine Constants Winding Factors SynR JMAG 2D FEA Inductance/Saliency diff --git a/docs/source/EM_analyzers/input_bspm_mach_constants_analyzer.csv b/docs/source/EM_analyzers/input_bspm_mach_constants_analyzer.csv index 0c5d5366..885162b6 100644 --- a/docs/source/EM_analyzers/input_bspm_mach_constants_analyzer.csv +++ b/docs/source/EM_analyzers/input_bspm_mach_constants_analyzer.csv @@ -1,5 +1,5 @@ Arguments, Type, Description -config, ``JMAG_2D_Config``, object of type JMAG_2D_Config describing time step and mesh setting etc +config, ``JMAG_2D_Config``," object of type JMAG_2D_Config describing time step and mesh setting, etc." Kf_Kt_step, int (default - *10*), number of steps for evaluating suspension force and torque constant Kdelta_coords, list (default - *see below*), list of coordinate points [x y] in mm Kphi_step, int (default - *10*), number of steps for evaluating back-EMF constant From 75092d9df60b62d22cd43470cbb1f2a0bb148bff Mon Sep 17 00:00:00 2001 From: Takahiro Noguchi Date: Thu, 30 May 2024 16:14:41 -0500 Subject: [PATCH 11/15] Update jmag_2d to fix naming issue --- mach_eval/analyzers/electromagnetic/bspm/jmag_2d.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mach_eval/analyzers/electromagnetic/bspm/jmag_2d.py b/mach_eval/analyzers/electromagnetic/bspm/jmag_2d.py index e0b49590..d6c00eea 100644 --- a/mach_eval/analyzers/electromagnetic/bspm/jmag_2d.py +++ b/mach_eval/analyzers/electromagnetic/bspm/jmag_2d.py @@ -312,7 +312,7 @@ def group(name, id_list): id_shaft = part_ID_list[1] partIDRange_Magnet = part_ID_list[2 : int(2 + self.machine_variant.p * 2)] # id_sleeve = part_ID_list[int(2 + self.machine_variant.p * 2)] - id_statorCore = part_ID_list[int(2 + self.machine_variant.p * 2) + 1] + id_statorCore = part_ID_list[int(2 + self.machine_variant.p * 2)] partIDRange_Coil = part_ID_list[ int(1 + self.machine_variant.p * 2) + 2 : int(2 + self.machine_variant.p * 2) @@ -325,6 +325,12 @@ def group(name, id_list): group("Magnet", partIDRange_Magnet) group("Coils", partIDRange_Coil) + # """ Set Parts names """ + + app.GetModel(0).SetPartName(id_backiron, u"NotchedRotor") + app.GetModel(0).SetPartName(id_shaft, u"Shaft") + app.GetModel(0).SetPartName(id_statorCore, u"StatorCore") + """ Add Part to Set for later references """ def add_part_to_set(name, x, y, ID=None): From ebac9929a3ae8bf8cd1790912ba66f40fbde2d30 Mon Sep 17 00:00:00 2001 From: Anson Chan Date: Mon, 10 Jun 2024 12:07:33 -0500 Subject: [PATCH 12/15] address taka comment --- .../EM_analyzers/bspm_mach_constants_analyzer.rst | 10 ++++------ .../bspm/machine_constant/bspm_mach_constants.py | 14 +++++++------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst b/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst index de2539e7..27b356b2 100644 --- a/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst +++ b/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst @@ -72,7 +72,6 @@ to ensure the modules can imported properly: .. code-block:: python - import mach_eval.analyzers.electromagnetic.bspm.machine_constant.bspm_mach_constants as bmc import mach_eval.analyzers.electromagnetic.bspm.machine_constant.bspm_mach_constants as bmc from mach_eval.machines.materials.electric_steels import Arnon5 from mach_eval.machines.materials.jmag_library_magnets import N40H @@ -87,7 +86,6 @@ to ensure the modules can imported properly: from mach_eval.machines.bspm.bspm_oper_pt import BSPM_Machine_Oper_Pt from mach_eval.analyzers.electromagnetic.bspm.jmag_2d_config import JMAG_2D_Config import os - import numpy as np Define and Create ``BSPM Machine`` Object ------------------------------------------ @@ -270,10 +268,10 @@ Use the following code to run the example analysis: # SOLVE BSPM MACHINE CONSTANTS PROBLEM ######################################################### result = analyzer.analyze(problem) - print(result.Kf) - print(result.Kt) - print(result.Kdelta) - print(result.Kphi) + print(f"Kf = {result.Kf}") + print(f"Kt = {result.Kt}") + print(f"Kdelta = {result.Kdelta}") + print(f"Kphi = {result.Kphi}") .. note:: diff --git a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py index 6476d13a..d8c800d9 100644 --- a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py +++ b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py @@ -77,16 +77,16 @@ class BSPMMachineConstantAnalyzer(BSPM_EM_Analyzer): def __init__( self, configuration: JMAG_2D_Config, - Kf_Kt_step: int = 10, + Kf_Kt_case: int = 10, Kdelta_coords: list = [[x, y] for x in np.linspace(-0.3,0.3,3) for y in np.linspace(-0.3,0.3,3)], - Kphi_step: int = 10, + Kphi_case: int = 10, ): self.configuration = configuration super().__init__(self.configuration) - self.Kf_Kt_step = Kf_Kt_step + self.Kf_Kt_case = Kf_Kt_case self.Kdelta_coords = Kdelta_coords - self.Kphi_step = Kphi_step + self.Kphi_case = Kphi_case def __getstate__(self): """Magic method for pickling""" @@ -269,7 +269,7 @@ def Kphi_data(self): @cached_property def Kphi_speed(self): - return np.linspace(0,self.machine_op_pt.speed,self.Kphi_step) + return np.linspace(0,self.machine_op_pt.speed,self.Kphi_case) @lru_cache def run_Kf_Kt_simulations(self)->Tuple[list, list, list, list]: @@ -282,7 +282,7 @@ def run_Kf_Kt_simulations(self)->Tuple[list, list, list, list]: # define torque and suspension current for simulation Iq_list = np.linspace( - 0,np.sqrt(2)*self.machine.Rated_current,self.Kf_Kt_step) + 0,np.sqrt(2)*self.machine.Rated_current,self.Kf_Kt_case) Is_list = np.sqrt(2)*self.machine.Rated_current - Iq_list force_df_list = [] @@ -295,7 +295,7 @@ def run_Kf_Kt_simulations(self)->Tuple[list, list, list, list]: # duplicate initial study self.init_model.DuplicateStudyName(self.init_study_name, - f"{self.init_study_name}_Kf_Kt_step{idx}",True) + f"{self.init_study_name}_Kf_Kt_case{idx}",True) present_study = self.toolJd.GetCurrentStudy() From 202eb35339b7245e5130f244817c48c47087c5f5 Mon Sep 17 00:00:00 2001 From: Anson Chan Date: Thu, 27 Jun 2024 17:33:31 -0500 Subject: [PATCH 13/15] change operating point variable name --- .../bspm/machine_constant/bspm_mach_constants.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py index d8c800d9..c184febb 100644 --- a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py +++ b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py @@ -35,7 +35,7 @@ class BSPMMachineConstantProblem: def __init__( self, machine:BSPM_Machine, - operating_point: BSPM_Machine_Oper_Pt, + nominal_op_pt: BSPM_Machine_Oper_Pt, solve_Kf: bool = True, solve_Kt: bool = True, solve_Kphi: bool = True, @@ -45,7 +45,7 @@ def __init__( Args: machine (BSPM_Machine): instance of `BSPM_Machine` - operating_point (BSPM_Machine_Oper_Pt): instance of `BSPM_Machine_Oper_Pt` + nominial_op_pt (BSPM_Machine_Oper_Pt): instance of `BSPM_Machine_Oper_Pt` solve_Kf (bool, optional): solve force constant. Defaults to True. solve_Kt (bool, optional): solve torque constant. Defaults to True. solve_Kdelta (bool, optional): solve displacment constant. Defaults to True. @@ -55,7 +55,7 @@ def __init__( BSPMMachineConstantProblem: instance of BSPMMachineConstantProblem """ self.machine = machine - self.operating_point = operating_point + self.nominial_op_pt = nominal_op_pt self.solve_Kf = solve_Kf self.solve_Kt = solve_Kt self.solve_Kphi = solve_Kphi @@ -68,7 +68,7 @@ def _validate_attr(self): 'Invalid machine type, must be BSPM_Machine.' ) - if not isinstance(self.operating_point, BSPM_Machine_Oper_Pt): + if not isinstance(self.nominial_op_pt, BSPM_Machine_Oper_Pt): raise TypeError( 'Invalid settings type, must be BSPM_Machine_Oper_Pt.' ) @@ -113,7 +113,7 @@ def analyze(self, problem: BSPMMachineConstantProblem): self.problem = problem self.machine = problem.machine - self.machine_op_pt = problem.operating_point + self.nominial_op_pt = problem.nominial_op_pt self._validate_attr() # Run initial analysis to build the model @@ -269,7 +269,7 @@ def Kphi_data(self): @cached_property def Kphi_speed(self): - return np.linspace(0,self.machine_op_pt.speed,self.Kphi_case) + return np.linspace(0,self.nominial_op_pt.speed,self.Kphi_case) @lru_cache def run_Kf_Kt_simulations(self)->Tuple[list, list, list, list]: From 0daf3575d64567a44e44c7a0753a5e5ac1dcb563 Mon Sep 17 00:00:00 2001 From: Anson Chan Date: Thu, 27 Jun 2024 23:13:57 -0500 Subject: [PATCH 14/15] update analyzer input and docs --- .../bspm_mach_constants_analyzer.rst | 107 ++++++++--- .../input_bspm_mach_constants_analyzer.csv | 4 +- .../input_bspm_mach_constants_problem.csv | 10 +- .../result_bspm_mach_constants.csv | 6 +- .../machine_constant/bspm_mach_constants.py | 175 ++++++++++++------ 5 files changed, 204 insertions(+), 98 deletions(-) diff --git a/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst b/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst index 27b356b2..9b1b5f7c 100644 --- a/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst +++ b/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst @@ -6,7 +6,8 @@ This analyzer determines the machine constants (:math:`k_t, k_f, k_\delta,` and Model Background **************** -This analyzer utilizes scripts within eMach to generate ``BSPM_Machine`` and ``BSPM_Machine_Oper_Pt`` objects for performing a machine constant analysis. +This analyzer utilizes scripts within eMach to generate ``BSPM_Machine`` and ``BSPM_Machine_Oper_Pt`` objects for performing machine constant analysis. +In each analysis, linear polynomials are fitted to torque (or force) data as the current (or displacement) varies in order to obtain the corresponding machine constant. Torque Contant :math:`k_t` ------------------------------------ @@ -26,8 +27,7 @@ The suspension force constant, :math:`k_f`, and displacement stiffness constant, \vec{F} = k_f \vec{i_s}+k_\delta \vec{\delta} -where :math:`\delta` is the displacement of the rotor from the magnetic center, :math:`F_c` is the force created by the current, :math:`\vec{i_s}` is the suspension -current, :math:`k_f` is the force constant, and :math:`k_\delta` is the displacement stiffness constant. +where :math:`\vec{i_s}` is the suspension current, and :math:`\delta` is the displacement of the rotor from the magnetic center. Back-EMF Constant :math:`k_\Phi` @@ -38,7 +38,7 @@ The machine back-EMF constant :math:`k_\Phi` can be expressed using the followin \vec{v_m} = k_\Phi\omega -where, :math:`\omega` is angular velocity in rad/s and :math:`\vec{v_m}` is the RMS value of the induced phase voltage. +where, :math:`\omega` is angular velocity in rad/s and :math:`\vec{v_m}` is the peak value of the induced phase voltage. Input from User ********************************* @@ -57,14 +57,6 @@ for each are summarized below: :widths: 20, 20, 30 :header-rows: 1 -The default value of ``Kdelta_coords`` is shown below: - -.. code-block:: python - - Kdelta_coords: list = [[x, y] for x in np.linspace(-0.3,0.3,3) - for y in np.linspace(-0.3,0.3,3)], - - Import Modules ------------------------------------ The following code imports all the required modules for performing a BSPM machine constants analysis. Users can paste this code into their scripts and execute it @@ -212,20 +204,20 @@ Users can paste the provided sample pf the JMAG configuration code to instantiat no_of_steps_per_rev_2TS=360, mesh_size=2e-3, magnet_mesh_size=1e-3, - airgap_mesh_radial_div=5, + airgap_mesh_radial_div=7, 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"), + 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.abspath("") + "/run_data/", - jmag_csv_folder=os.path.abspath("") + "/run_data/JMAG_csv/", + 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=True, + jmag_visible=False, + jmag_version = '23', ) .. note:: @@ -235,14 +227,75 @@ Users can paste the provided sample pf the JMAG configuration code to instantiat Define Problem and Analyzer Object ------------------------------------ -Use the following code to create the problem and analyzer object: +Use the following code to define the problem and analyzer object: .. code-block:: python + ######################################################### + # DEFINE BSPM OPERATING POINTS + ######################################################### + + # List of BSPM operating points for Kf evaluation + Kf_op_pt = [ + BSPM_Machine_Oper_Pt( + Id=0, + Iq=0, + Ix=0, + Iy=Is_pu, + speed=160000, + ambient_temp=25, + rotor_temp_rise=55, + ) + + for Is_pu in np.linspace(0,1,10) + ] + + # List of BSPM operating points for Kt evaluation + Kt_op_pt = [ + BSPM_Machine_Oper_Pt( + Id=0, + Iq=Iq_pu, + Ix=0, + Iy=0, + speed=160000, + ambient_temp=25, + rotor_temp_rise=55, + ) + for Iq_pu in np.linspace(0,1,10) + ] + + # List of BSPM operating points for Kphi evaluation + Kphi_op_pt = [ + BSPM_Machine_Oper_Pt( + Id=0, + Iq=0, + Ix=0, + Iy=0, + speed=speed, + ambient_temp=25, + rotor_temp_rise=55, + ) + for speed in np.linspace(0,160000,10) + ] + + # List of coordinates for Kdelta evaluation + Kdelta_coords = [ + [x, y] + for x in np.linspace(-0.3,0.3,3) + for y in np.linspace(-0.3,0.3,3) + ] + ######################################################### # DEFINE BSPM MACHINE CONSTANTS PROBLEM ######################################################### - problem = bmc.BSPMMachineConstantProblem(bp4,bp4_op_pt) + problem = BSPMMachineConstantProblem( + machine=bp4, + nominal_op_pt=bp4_op_pt, + Kf_op_pt, + Kt_op_pt, + Kphi_op_pt, + Kdelta_coords + ) ######################################################### # DEFINE BSPM MACHINE CONSTANTS ANALYZER @@ -285,11 +338,11 @@ Running the example case returns the following: .. code-block:: python - 1.8019710307171688 - 0.0203730830815381 - 6935.763575553156 - 0.00456017028983404 + 1.8052182451902197 + 0.01911529534112125 + 6935.763575553303 + 0.006449054670613704 -The results indicate that the example BSPM machine design has a suspension force constant of :math:`k_f = 1.802\; [\frac{N}{A}]`, a torque constant of -:math:`k_t = 0.0204 \; [\frac{Nm}{A_{pk}}]`, a displacement stiffness constant of :math:`k_\delta = 6935.76\; [\frac{N}{m}]`, and back-EMF constant of -:math:`k_\phi = 0.00456\; [\frac{V_{rms}}{rad/s}]`. \ No newline at end of file +The results indicate that the example BSPM machine design has a suspension force constant of :math:`k_f = 1.805\; [\frac{N}{A_{pk}}]`, a torque constant of +:math:`k_t = 0.0191 \; [\frac{Nm}{A_{pk}}]`, a displacement stiffness constant of :math:`k_\delta = 6935.76\; [\frac{N}{m}]`, and back-EMF constant of +:math:`k_\phi = 0.00645\; [\frac{V_{pk}}{rad/s}]`. \ No newline at end of file diff --git a/docs/source/EM_analyzers/input_bspm_mach_constants_analyzer.csv b/docs/source/EM_analyzers/input_bspm_mach_constants_analyzer.csv index 885162b6..8459835c 100644 --- a/docs/source/EM_analyzers/input_bspm_mach_constants_analyzer.csv +++ b/docs/source/EM_analyzers/input_bspm_mach_constants_analyzer.csv @@ -1,5 +1,3 @@ Arguments, Type, Description config, ``JMAG_2D_Config``," object of type JMAG_2D_Config describing time step and mesh setting, etc." -Kf_Kt_step, int (default - *10*), number of steps for evaluating suspension force and torque constant -Kdelta_coords, list (default - *see below*), list of coordinate points [x y] in mm -Kphi_step, int (default - *10*), number of steps for evaluating back-EMF constant + diff --git a/docs/source/EM_analyzers/input_bspm_mach_constants_problem.csv b/docs/source/EM_analyzers/input_bspm_mach_constants_problem.csv index 56e791d4..90680322 100644 --- a/docs/source/EM_analyzers/input_bspm_mach_constants_problem.csv +++ b/docs/source/EM_analyzers/input_bspm_mach_constants_problem.csv @@ -1,7 +1,7 @@ Arguments, Type, Description machine, ``BSPM_machine``, instance of ``BSPM_machine`` -operating_point,``BSPM_Machine_Oper_Pt``, instance of ``BSPM_Machine_Oper_Pt`` -solve_Kf,bool (default - *True*), boolean input to check whether to solve Kf value or not -solve_Kt,bool (default - *True*),boolean input to check whether to solve Kt value or not -solve_Kphi,bool (default - *True*),boolean input to check whether to solve Kphi value or not -solve_Kdelta,bool (default - *True*),boolean input to check whether to solve Kdelta value or not \ No newline at end of file +nominial_op_pt,``BSPM_Machine_Oper_Pt``, nominal operating point of BSPM Machine under evaluation +Kf_op_pt, list (default - *None*), list containing instances of `BSPM_Machine_Oper_Pt` for Kf simulations +Kt_op_pt, list (default - *None*), list containing instances of `BSPM_Machine_Oper_Pt` for Kt simulations +Kphi_op_pt, list (default - *None*), list containing instances of `BSPM_Machine_Oper_Pt` for Kphi simulations +Kdelta_coords, list (default - *None*),list of [x y] coordinates in mm to run Kdelta simulations \ No newline at end of file diff --git a/docs/source/EM_analyzers/result_bspm_mach_constants.csv b/docs/source/EM_analyzers/result_bspm_mach_constants.csv index 0e5009c5..ffde9030 100644 --- a/docs/source/EM_analyzers/result_bspm_mach_constants.csv +++ b/docs/source/EM_analyzers/result_bspm_mach_constants.csv @@ -1,5 +1,5 @@ Attribute,Description,Units -Kf, Suspension force constant, N/A -Kt, Torque constant, N-m/A_pk +Kf, Suspension force constant, N/A_pk +Kt, Torque constant, Nm/A_pk Kdelta, Displacement stiffness constant, N/m -Kphi, Back-EMF constant , V_rms/rad/s +Kphi, Back-EMF constant , V_pk/rad/s diff --git a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py index c184febb..eb6e2eea 100644 --- a/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py +++ b/mach_eval/analyzers/electromagnetic/bspm/machine_constant/bspm_mach_constants.py @@ -36,30 +36,30 @@ def __init__( self, machine:BSPM_Machine, nominal_op_pt: BSPM_Machine_Oper_Pt, - solve_Kf: bool = True, - solve_Kt: bool = True, - solve_Kphi: bool = True, - solve_Kdelta: bool = True, + Kf_op_pt: list = None, + Kt_op_pt: list = None, + Kphi_op_pt: list = None, + Kdelta_coords: list = None, ) -> 'BSPMMachineConstantProblem': """BSPMMachineConstantProblem Class Args: machine (BSPM_Machine): instance of `BSPM_Machine` - nominial_op_pt (BSPM_Machine_Oper_Pt): instance of `BSPM_Machine_Oper_Pt` - solve_Kf (bool, optional): solve force constant. Defaults to True. - solve_Kt (bool, optional): solve torque constant. Defaults to True. - solve_Kdelta (bool, optional): solve displacment constant. Defaults to True. - solve_Kphi (bool, optional): solve back-emf constant. Defaults to True. + nominial_op_pt (BSPM_Machine_Oper_Pt): instance of `BSPM_Machine_Oper_Pt` at nominial + Kf_op_pt (List): list containing instances of `BSPM_Machine_Oper_Pt` for Kf simulations + Kt_op_pt (List): list containing instances of `BSPM_Machine_Oper_Pt` for Kt simulations + Kphi_op_pt (List): list containing instances of `BSPM_Machine_Oper_Pt` for Kphi simulations + Kdelta_coords (List): list of [x,y] coordinates to run Kdelta simulations Returns: BSPMMachineConstantProblem: instance of BSPMMachineConstantProblem """ self.machine = machine - self.nominial_op_pt = nominal_op_pt - self.solve_Kf = solve_Kf - self.solve_Kt = solve_Kt - self.solve_Kphi = solve_Kphi - self.solve_Kdelta = solve_Kdelta + self.operating_point = nominal_op_pt + self.Kf_op_pt = Kf_op_pt + self.Kt_op_pt = Kt_op_pt + self.Kphi_op_pt = Kphi_op_pt + self.Kdelta_coords = Kdelta_coords self._validate_attr() def _validate_attr(self): @@ -68,25 +68,28 @@ def _validate_attr(self): 'Invalid machine type, must be BSPM_Machine.' ) - if not isinstance(self.nominial_op_pt, BSPM_Machine_Oper_Pt): + if not isinstance(self.operating_point, BSPM_Machine_Oper_Pt): raise TypeError( 'Invalid settings type, must be BSPM_Machine_Oper_Pt.' ) + + for attr_name in ['Kf_op_pt', 'Kt_op_pt', 'Kphi_op_pt']: + attr_value = getattr(self, attr_name) + if attr_value is not None: + if not isinstance(attr_value, list): + raise TypeError(f'{attr_name} must be a list or None.') + if not all(isinstance(item, BSPM_Machine_Oper_Pt) for item in attr_value): + raise TypeError(f'All elements in {attr_name} must be instances of BSPM_Machine_Oper_Pt.') + class BSPMMachineConstantAnalyzer(BSPM_EM_Analyzer): - def __init__( - self, - configuration: JMAG_2D_Config, - Kf_Kt_case: int = 10, - Kdelta_coords: list = [[x, y] for x in np.linspace(-0.3,0.3,3) - for y in np.linspace(-0.3,0.3,3)], - Kphi_case: int = 10, - ): + def __init__(self, configuration: JMAG_2D_Config): + # Kf_Kt_case: int = 10, + # Kdelta_coords: list = [[x, y] for x in np.linspace(-0.3,0.3,3) + # for y in np.linspace(-0.3,0.3,3)], + # Kphi_case: int = 10, self.configuration = configuration super().__init__(self.configuration) - self.Kf_Kt_case = Kf_Kt_case - self.Kdelta_coords = Kdelta_coords - self.Kphi_case = Kphi_case def __getstate__(self): """Magic method for pickling""" @@ -113,8 +116,11 @@ def analyze(self, problem: BSPMMachineConstantProblem): self.problem = problem self.machine = problem.machine - self.nominial_op_pt = problem.nominial_op_pt - self._validate_attr() + self.nominial_op_pt = problem.operating_point + self.Kf_op_pt = problem.Kf_op_pt + self.Kt_op_pt = problem.Kt_op_pt + self.Kphi_op_pt = problem.Kphi_op_pt + self.Kdelta_coords = problem.Kdelta_coords # Run initial analysis to build the model # super().analyze == BSPM_EM_Analyzer.analyzer @@ -133,12 +139,16 @@ def analyze(self, problem: BSPMMachineConstantProblem): self.init_properties) = self._get_init_study(self.toolJd) return BSPMMachineConstantResult( - self.Kf,self.Kt,self.Kdelta,self.Kphi) + Kf=self.Kf, + Kt=self.Kt, + Kphi=self.Kphi, + Kdelta=self.Kdelta + ) @cached_property def Kf(self)-> Union[float, None]: "Machine Suspension Force Constant [N/A]" - if self.problem.solve_Kf: + if self.Kf_op_pt is not None: Is_list, force = self.Kf_data Kf,_ = np.polyfit(Is_list,force,deg=1) return Kf @@ -148,33 +158,33 @@ def Kf(self)-> Union[float, None]: @cached_property def Kt(self)->Union[float, None]: "Machine Torque Constant [N-m/A_pk]" - if self.problem.solve_Kt: + if self.Kt_op_pt is not None: Iq_list, torque = self.Kt_data Kt,_ = np.polyfit(Iq_list,torque,deg=1) return Kt else: return None - - @cached_property - def Kdelta(self)->Union[float, None]: - "Machine Displacement Stiffness Constant [N/m]" - if self.problem.solve_Kdelta: - disp,force = self.Kdelta_data - Kdelta,_ = np.polyfit(disp,force,deg=1) - return Kdelta - else: - return None @cached_property def Kphi(self)->Union[float, None]: "Machine Back-EMF Constant [V_rms/rad/s]" - if self.problem.solve_Kphi: + if self.Kphi_op_pt is not None: speed, bemf = self.Kphi_data Kphi,_ = np.polyfit(speed,bemf,deg=1) return Kphi # AC else: return None + + @cached_property + def Kdelta(self)->Union[float, None]: + "Machine Displacement Stiffness Constant [N/m]" + if self.Kdelta_coords is not None: + disp, force = self.Kdelta_data + Kdelta,_ = np.polyfit(disp,force,deg=1) + return Kdelta + else: + return None @cached_property def Kf_data(self)->Tuple[list,list]: @@ -184,7 +194,7 @@ def Kf_data(self)->Tuple[list,list]: Tuple[list,list]: (suspension currents [A], force values [N]) """ # run simulations and extract data from JMAG - _,Is_list,_,force_df_list = self.run_Kf_Kt_simulations() + _,Is_list,force_df_list = self.run_Kf_simulations() # determine average torque in each simulation run force = np.zeros(len(force_df_list)) @@ -207,7 +217,7 @@ def Kt_data(self)->Tuple[list,list]: Tuple[list,list]: (torque currents [A], torque values [N-m]) """ # run simulations and extract data from JMAG - Iq_list, _, torque_df_list, _ = self.run_Kf_Kt_simulations() + Iq_list, _, torque_df_list = self.run_Kt_simulations() # determine average torque in each simulation run torque = np.zeros(len(torque_df_list)) @@ -263,39 +273,40 @@ def Kphi_data(self): else: rms_voltage = np.sqrt( sum(np.square(phase_voltage)) / len(phase_voltage)) - bemf.append(rms_voltage) + bemf.append(rms_voltage*np.sqrt(2)) - return self.Kphi_speed*(2*np.pi/60), bemf + return [speed*(2*np.pi/60) for speed in self.Kphi_speed], bemf @cached_property def Kphi_speed(self): - return np.linspace(0,self.nominial_op_pt.speed,self.Kphi_case) + print([op_pt.speed for op_pt in self.Kphi_op_pt]) + return [op_pt.speed for op_pt in self.Kphi_op_pt] @lru_cache - def run_Kf_Kt_simulations(self)->Tuple[list, list, list, list]: + def run_Kf_simulations(self)->Tuple[list, list, list]: """Script to perform Kf and Kt simulations in JMAG. Returns: - Tuple[list, list, list, list]: (torque currents [A], - suspension currents [A], torque_df_list, force_df_list) + Tuple[list, list, list]: ( + torque currents [A], + suspension currents [A], + force_df_list + ) """ # define torque and suspension current for simulation - Iq_list = np.linspace( - 0,np.sqrt(2)*self.machine.Rated_current,self.Kf_Kt_case) - Is_list = np.sqrt(2)*self.machine.Rated_current - Iq_list - + Iq_list = [np.sqrt(2)*self.machine.Rated_current*op_pt.Iq for op_pt in self.Kf_op_pt] + Is_list = [np.sqrt(2)*self.machine.Rated_current*op_pt.Iy for op_pt in self.Kf_op_pt] force_df_list = [] - torque_df_list = [] print('==============================================================') - print('Running Kf and Kt simulations ......') + print('Running Kf simulations ......') for idx, (Iq_val,Is_val) in enumerate( tqdm(zip(Iq_list,Is_list),total=len(Iq_list))): # duplicate initial study self.init_model.DuplicateStudyName(self.init_study_name, - f"{self.init_study_name}_Kf_Kt_case{idx}",True) + f"{self.init_study_name}_Kf_case{idx}",True) present_study = self.toolJd.GetCurrentStudy() @@ -310,11 +321,52 @@ def run_Kf_Kt_simulations(self)->Tuple[list, list, list, list]: # extract FEA results from CSV force_df = self._extract_csv_results(present_study.GetName(), "Force") - torque_df = self._extract_csv_results(present_study.GetName(),"Torque") force_df_list.append(force_df) + + return Iq_list, Is_list, force_df_list + + @lru_cache + def run_Kt_simulations(self)->Tuple[list, list, list]: + """Script to perform Kt simulations in JMAG. + + Returns: + Tuple[list, list, list]: ( + torque currents [A], + suspension currents [A], + torque_df_list + ) + """ + + # define torque and suspension current for simulation + Iq_list = [np.sqrt(2)*self.machine.Rated_current*op_pt.Iq for op_pt in self.Kt_op_pt] + Is_list = [np.sqrt(2)*self.machine.Rated_current*op_pt.Iy for op_pt in self.Kt_op_pt] + torque_df_list = [] + + print('==============================================================') + print('Running Kt simulations ......') + for idx, (Iq_val,Is_val) in enumerate( + tqdm(zip(Iq_list,Is_list),total=len(Iq_list))): + + # duplicate initial study + self.init_model.DuplicateStudyName(self.init_study_name, + f"{self.init_study_name}_Kt_case{idx}",True) + + present_study = self.toolJd.GetCurrentStudy() + + # obtain handle to circuit for present study + circuit = present_study.GetCircuit() + self._set_circuit_current_value( + circuit, + ampT=2*Iq_val, + ampS=Is_val, + freq=super().excitation_freq) + present_study.RunAllCases() + + # extract FEA results from CSV + torque_df = self._extract_csv_results(present_study.GetName(),"Torque") torque_df_list.append(torque_df) - return Iq_list, Is_list, torque_df_list, force_df_list + return Iq_list, Is_list, torque_df_list @lru_cache def run_Kdelta_simulations(self)->list: @@ -484,6 +536,9 @@ def _validate_attr(self): class BSPMMachineConstantResult: Kf: float Kt: float - Kdelta: float Kphi: float + Kdelta: float + + + From f73446ad923b10755d70a0e9c1359b7ff02df25a Mon Sep 17 00:00:00 2001 From: Anson Chan Date: Fri, 28 Jun 2024 15:35:09 -0500 Subject: [PATCH 15/15] type on delta symbol --- docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst b/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst index 9b1b5f7c..d2ab207a 100644 --- a/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst +++ b/docs/source/EM_analyzers/bspm_mach_constants_analyzer.rst @@ -27,7 +27,7 @@ The suspension force constant, :math:`k_f`, and displacement stiffness constant, \vec{F} = k_f \vec{i_s}+k_\delta \vec{\delta} -where :math:`\vec{i_s}` is the suspension current, and :math:`\delta` is the displacement of the rotor from the magnetic center. +where :math:`\vec{i_s}` is the suspension current, and :math:`\vec{\delta}` is the displacement of the rotor from the magnetic center. Back-EMF Constant :math:`k_\Phi`