From af2c4ddfe3422d35310f942c28692d05015f8386 Mon Sep 17 00:00:00 2001 From: Jean Le Besnerais Date: Mon, 26 Apr 2021 14:47:39 +0200 Subject: [PATCH 001/167] [CC] added Jean line --- test.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test.txt b/test.txt index 793aa682b..811d67bb3 100644 --- a/test.txt +++ b/test.txt @@ -1 +1,3 @@ -This is a test \ No newline at end of file +This is a test + +Coucou c'est Jean à la troisième ligne \ No newline at end of file From 4640629a8dd48cfc649b15b9c5d27bd8c489cabb Mon Sep 17 00:00:00 2001 From: Paul GNING Date: Mon, 26 Apr 2021 14:47:45 +0200 Subject: [PATCH 002/167] [WP] Added my name --- test.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test.txt b/test.txt index 793aa682b..5b4a2ceda 100644 --- a/test.txt +++ b/test.txt @@ -1 +1,2 @@ -This is a test \ No newline at end of file +This is a test +Paul \ No newline at end of file From 3e89cbb87dd6a3210314564a390f43cb87fb1f51 Mon Sep 17 00:00:00 2001 From: Paul GNING Date: Thu, 29 Apr 2021 15:56:32 +0200 Subject: [PATCH 003/167] [WP] Skin effect computation for stator winding (square and round wires) --- Tests/Classes/test_classes.py | 4 +- Tests/excel_report_post_process.py | 67 ++++++++------- pyleecan/Classes/Class_Dict.json | 16 +++- pyleecan/Classes/Conductor.py | 82 +++++++++++++++++++ pyleecan/Classes/Electrical.py | 36 +++++++- .../ClassesRef/Machine/Conductor.csv | 8 +- .../ClassesRef/Simulation/Electrical.csv | 2 +- .../Simulation/EEC_PMSM/comp_parameters.py | 7 ++ .../Simulation/EEC_SCIM/comp_parameters.py | 7 +- 9 files changed, 193 insertions(+), 36 deletions(-) diff --git a/Tests/Classes/test_classes.py b/Tests/Classes/test_classes.py index 3526fb095..abb0a0df6 100644 --- a/Tests/Classes/test_classes.py +++ b/Tests/Classes/test_classes.py @@ -331,7 +331,9 @@ def test_class_type_float(class_dict): assert test_obj.__getattribute__(prop["name"]) == value, msg else: # CheckTypeError expected - with pytest.raises(CheckTypeError,): + with pytest.raises( + CheckTypeError, + ): # print(msg) test_obj.__setattr__(prop["name"], value) diff --git a/Tests/excel_report_post_process.py b/Tests/excel_report_post_process.py index 1b9581ab9..7770fb2e7 100644 --- a/Tests/excel_report_post_process.py +++ b/Tests/excel_report_post_process.py @@ -22,38 +22,38 @@ """ df = pd.read_excel(join(TEST_DIR, "report.xlsx")) -df.to_csv(join(TEST_DIR, "report.csv")) +df.to_csv(join(TEST_DIR, "report.csv")) # Additionnal tolerance time in seconds for the time marker verification DELTA_TIME = 1 dict_markers = { - "Machine":["IPMSM","SCIM", "SPMSM","SIPMSM", "SynRM", "MachineUD"], - "VarSimu":["VarLoadCurrent", "VarParam", "SingleOP"], - "Elec":["EEC_PMSM", "EEC_SCIM"], - "Magnetic":["MagFEMM", "MagElmer"], - "Force":["ForceMT", "ForceTensor"], - "Loss":["Loss"], - "Structurel":["StructElmer"], - "Topology":["HoleUD", "SlotUD", "outer_rotor"], - "Long":["long_5s", "long_1m", "long_10m"], - "Skip":["skip"], - "Other":["MeshSol", "FEMM", "GMSH", "GMSH2D", "periodicity", "parallel"] - } - -dict_time = { - "long_5s":5, - "long_1m":60, - "long_10m":600 - } + "Machine": ["IPMSM", "SCIM", "SPMSM", "SIPMSM", "SynRM", "MachineUD"], + "VarSimu": ["VarLoadCurrent", "VarParam", "SingleOP"], + "Elec": ["EEC_PMSM", "EEC_SCIM"], + "Magnetic": ["MagFEMM", "MagElmer"], + "Force": ["ForceMT", "ForceTensor"], + "Loss": ["Loss"], + "Structurel": ["StructElmer"], + "Topology": ["HoleUD", "SlotUD", "outer_rotor"], + "Long": ["long_5s", "long_1m", "long_10m"], + "Skip": ["skip"], + "Other": ["MeshSol", "FEMM", "GMSH", "GMSH2D", "periodicity", "parallel"], +} + +dict_time = {"long_5s": 5, "long_1m": 60, "long_10m": 600} if __name__ == "__main__": nb_row = 0 with open(join(TEST_DIR, "report.csv"), "r", encoding="utf8") as source: - reader = csv.reader(source) # Return n independent iterators from a single iterable. - with open(join(TEST_DIR, "converted_report.csv"), "w", newline="", encoding="utf8") as result: - writer = csv.writer(result, delimiter=';') + reader = csv.reader( + source + ) # Return n independent iterators from a single iterable. + with open( + join(TEST_DIR, "converted_report.csv"), "w", newline="", encoding="utf8" + ) as result: + writer = csv.writer(result, delimiter=";") for row in reader: # Copy Headers @@ -64,8 +64,10 @@ # Copy other values else: markers = row[9].split(", ") - markers_entry = ["None" for idx in range(len(list(dict_markers.keys())))] - last_item = len(list(dict_markers.keys())) - 1 + markers_entry = [ + "None" for idx in range(len(list(dict_markers.keys()))) + ] + last_item = len(list(dict_markers.keys())) - 1 # Searching where to put the marker for marker in markers: @@ -80,7 +82,7 @@ # If not, just add the marker to the current marker else: markers_entry[index] += ", " + marker - index += 1 + index += 1 # If not found, put the marker in OTHER column if not found: @@ -92,10 +94,19 @@ # If time marker is inferior than the duration of the test, a message is sent if marker in dict_time: if int(dict_time[marker]) + DELTA_TIME < int(row[5][0]): - print("Time marker can be updated for : " + row[1] + ". Line in excel : " + row[0] + ". Duration : " + row[5][0] + "s with marker : " + marker + ".") - + print( + "Time marker can be updated for : " + + row[1] + + ". Line in excel : " + + row[0] + + ". Duration : " + + row[5][0] + + "s with marker : " + + marker + + "." + ) + entry = row[1:] + markers_entry writer.writerow(entry) nb_row = nb_row + 1 - diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 494d9e513..0bb66bd13 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -700,7 +700,12 @@ "desc": "abstact class for conductors", "is_internal": false, "methods": [ - "check" + "check", + "comp_phi_skin", + "comp_psi_skin", + "comp_phip_skin", + "comp_psip_skin", + "comp_skin_effect" ], "mother": "", "name": "Conductor", @@ -1156,6 +1161,15 @@ "type": "str", "unit": "-", "value": "Pyleecan.Electrical" + }, + { + "desc": "Skin effect for resistance and inductance", + "max": "", + "min": "", + "name": "type_skin_effect", + "type": "int", + "unit": "-", + "value": 0 } ] }, diff --git a/pyleecan/Classes/Conductor.py b/pyleecan/Classes/Conductor.py index bbb35142b..1ecc5ae4f 100644 --- a/pyleecan/Classes/Conductor.py +++ b/pyleecan/Classes/Conductor.py @@ -22,6 +22,31 @@ except ImportError as error: check = error +try: + from ..Methods.Machine.Conductor.comp_phi_skin import comp_phi_skin +except ImportError as error: + comp_phi_skin = error + +try: + from ..Methods.Machine.Conductor.comp_psi_skin import comp_psi_skin +except ImportError as error: + comp_psi_skin = error + +try: + from ..Methods.Machine.Conductor.comp_phip_skin import comp_phip_skin +except ImportError as error: + comp_phip_skin = error + +try: + from ..Methods.Machine.Conductor.comp_psip_skin import comp_psip_skin +except ImportError as error: + comp_psip_skin = error + +try: + from ..Methods.Machine.Conductor.comp_skin_effect import comp_skin_effect +except ImportError as error: + comp_skin_effect = error + from ._check import InitUnKnowClassError from .Material import Material @@ -32,6 +57,7 @@ class Conductor(FrozenClass): VERSION = 1 + # Check ImportError to remove unnecessary dependencies in unused method # cf Methods.Machine.Conductor.check if isinstance(check, ImportError): check = property( @@ -41,6 +67,62 @@ class Conductor(FrozenClass): ) else: check = check + # cf Methods.Machine.Conductor.comp_phi_skin + if isinstance(comp_phi_skin, ImportError): + comp_phi_skin = property( + fget=lambda x: raise_( + ImportError( + "Can't use Conductor method comp_phi_skin: " + str(comp_phi_skin) + ) + ) + ) + else: + comp_phi_skin = comp_phi_skin + # cf Methods.Machine.Conductor.comp_psi_skin + if isinstance(comp_psi_skin, ImportError): + comp_psi_skin = property( + fget=lambda x: raise_( + ImportError( + "Can't use Conductor method comp_psi_skin: " + str(comp_psi_skin) + ) + ) + ) + else: + comp_psi_skin = comp_psi_skin + # cf Methods.Machine.Conductor.comp_phip_skin + if isinstance(comp_phip_skin, ImportError): + comp_phip_skin = property( + fget=lambda x: raise_( + ImportError( + "Can't use Conductor method comp_phip_skin: " + str(comp_phip_skin) + ) + ) + ) + else: + comp_phip_skin = comp_phip_skin + # cf Methods.Machine.Conductor.comp_psip_skin + if isinstance(comp_psip_skin, ImportError): + comp_psip_skin = property( + fget=lambda x: raise_( + ImportError( + "Can't use Conductor method comp_psip_skin: " + str(comp_psip_skin) + ) + ) + ) + else: + comp_psip_skin = comp_psip_skin + # cf Methods.Machine.Conductor.comp_skin_effect + if isinstance(comp_skin_effect, ImportError): + comp_skin_effect = property( + fget=lambda x: raise_( + ImportError( + "Can't use Conductor method comp_skin_effect: " + + str(comp_skin_effect) + ) + ) + ) + else: + comp_skin_effect = comp_skin_effect # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/Electrical.py b/pyleecan/Classes/Electrical.py index 0811d863b..496e1cff9 100644 --- a/pyleecan/Classes/Electrical.py +++ b/pyleecan/Classes/Electrical.py @@ -81,7 +81,12 @@ class Electrical(FrozenClass): get_logger = get_logger def __init__( - self, eec=None, logger_name="Pyleecan.Electrical", init_dict=None, init_str=None + self, + eec=None, + logger_name="Pyleecan.Electrical", + type_skin_effect=0, + init_dict=None, + init_str=None, ): """Constructor of the class. Can be use in three ways : - __init__ (arg1 = 1, arg3 = 5) every parameters have name and default values @@ -102,10 +107,13 @@ def __init__( eec = init_dict["eec"] if "logger_name" in list(init_dict.keys()): logger_name = init_dict["logger_name"] + if "type_skin_effect" in list(init_dict.keys()): + type_skin_effect = init_dict["type_skin_effect"] # Set the properties (value check and convertion are done in setter) self.parent = None self.eec = eec self.logger_name = logger_name + self.type_skin_effect = type_skin_effect # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -124,6 +132,7 @@ def __str__(self): else: Electrical_str += "eec = None" + linesep + linesep Electrical_str += 'logger_name = "' + str(self.logger_name) + '"' + linesep + Electrical_str += "type_skin_effect = " + str(self.type_skin_effect) + linesep return Electrical_str def __eq__(self, other): @@ -135,6 +144,8 @@ def __eq__(self, other): return False if other.logger_name != self.logger_name: return False + if other.type_skin_effect != self.type_skin_effect: + return False return True def compare(self, other, name="self"): @@ -151,6 +162,8 @@ def compare(self, other, name="self"): diff_list.extend(self.eec.compare(other.eec, name=name + ".eec")) if other._logger_name != self._logger_name: diff_list.append(name + ".logger_name") + if other._type_skin_effect != self._type_skin_effect: + diff_list.append(name + ".type_skin_effect") return diff_list def __sizeof__(self): @@ -159,6 +172,7 @@ def __sizeof__(self): S = 0 # Full size of the object S += getsizeof(self.eec) S += getsizeof(self.logger_name) + S += getsizeof(self.type_skin_effect) return S def as_dict(self, **kwargs): @@ -174,6 +188,7 @@ def as_dict(self, **kwargs): else: Electrical_dict["eec"] = self.eec.as_dict(**kwargs) Electrical_dict["logger_name"] = self.logger_name + Electrical_dict["type_skin_effect"] = self.type_skin_effect # The class name is added to the dict for deserialisation purpose Electrical_dict["__class__"] = "Electrical" return Electrical_dict @@ -184,6 +199,7 @@ def _set_None(self): if self.eec is not None: self.eec._set_None() self.logger_name = None + self.type_skin_effect = None def _get_eec(self): """getter of eec""" @@ -230,3 +246,21 @@ def _set_logger_name(self, value): :Type: str """, ) + + def _get_type_skin_effect(self): + """getter of type_skin_effect""" + return self._type_skin_effect + + def _set_type_skin_effect(self, value): + """setter of type_skin_effect""" + check_var("type_skin_effect", value, "int") + self._type_skin_effect = value + + type_skin_effect = property( + fget=_get_type_skin_effect, + fset=_set_type_skin_effect, + doc=u"""Skin effect for resistance and inductance + + :Type: int + """, + ) diff --git a/pyleecan/Generator/ClassesRef/Machine/Conductor.csv b/pyleecan/Generator/ClassesRef/Machine/Conductor.csv index da3375981..aa7a43a86 100644 --- a/pyleecan/Generator/ClassesRef/Machine/Conductor.csv +++ b/pyleecan/Generator/ClassesRef/Machine/Conductor.csv @@ -1,5 +1,7 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description cond_mat,-,Material of the conductor,0,Material,,,,,Machine,,check,VERSION,1,abstact class for conductors -ins_mat,-,Material of the insulation,0,Material,,,,,,,,,, -,,,,,,,,,,,,,, -,,,,,,,,,,,,,, +ins_mat,-,Material of the insulation,0,Material,,,,,,,comp_phi_skin,,, +,,,,,,,,,,,comp_psi_skin,,, +,,,,,,,,,,,comp_phip_skin,,, +,,,,,,,,,,,comp_psip_skin,,, +,,,,,,,,,,,comp_skin_effect,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/Electrical.csv b/pyleecan/Generator/ClassesRef/Simulation/Electrical.csv index f0e3b3273..2227ac115 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/Electrical.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/Electrical.csv @@ -1,4 +1,4 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille eec,-,Electrical Equivalent Circuit,,EEC,None,,,,Simulation,,run,VERSION,1,Electric module object for electrical equivalent circuit simulation, logger_name,-,Name of the logger to use,0,str,Pyleecan.Electrical,,,,,,comp_power,,,, -,,,,,,,,,,,comp_torque,,,, +type_skin_effect,-,Skin effect for resistance and inductance,1,int,0,,,,,,comp_torque,,,, diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py index 0b16839c1..ed8ae3e2d 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py @@ -14,10 +14,16 @@ def comp_parameters(self, output): # TODO maybe set currents to small value if I is 0 to compute inductance PAR = self.parameters + Cond = self.parent.parent.machine.stator.winding.conductor + # compute skin_effect + Xkr_skinS, Xke_skinS = Cond.comp_skin_effect(T=20) + # Xkr_skinS=1 + # Xke_skinS=1 # Parameters to compute only once if "R20" not in PAR: PAR["R20"] = output.simu.machine.stator.comp_resistance_wind() + PAR["R20"] = PAR["R20"] * Xkr_skinS if "phi" not in PAR: PAR["phi"] = self.fluxlink.comp_fluxlinkage(output) @@ -40,6 +46,7 @@ def comp_parameters(self, output): # compute inductance if necessary if is_comp_ind: (phid, phiq) = self.indmag.comp_inductance(output) + (phid, phiq) = tuple([z * Xke_skinS for z in (phid, phiq)]) if PAR["Id"] != 0: PAR["Ld"] = (phid - PAR["phi"]) / PAR["Id"] else: diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py index 7daa000c3..191c4c530 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py @@ -27,6 +27,10 @@ def comp_parameters(self, output): self.parameters["norm"] = norm + Cond = self.parent.parent.machine.stator.winding.conductor + # compute skin_effect + Xkr_skinS, Xke_skinS = Cond.comp_skin_effect(T=20) + # get temperatures TODO remove/replace, since this is a temp. solution only Tws = 20 if "Tws" not in self.parameters else self.parameter["Tws"] Twr = 20 if "Twr" not in self.parameters else self.parameter["Twr"] @@ -34,6 +38,7 @@ def comp_parameters(self, output): # Parameters to compute only if they are not set if "Rs" not in self.parameters or self.parameters["Rs"] is None: self.parameters["Rs"] = machine.stator.comp_resistance_wind(T=Tws) + self.parameters["Rs"] = self.parameters["Rs"] * Xkr_skinS if "Rr_norm" not in self.parameters or self.parameters["Rr_norm"] is None: # 3 phase equivalent rotor resistance @@ -136,7 +141,7 @@ def comp_parameters(self, output): self.parameters["Lm"] = (Phi_r * norm * Zsr / 3) / self.I self.parameters["Ls"] = (Phi_s - (Phi_r * norm * Zsr / 3)) / self.I - + self.parameters["Ls"] = self.parameters["Ls"] * Xke_skinS # --- compute the main inductance and rotor stray inductance --- # set new output out = Output(simu=simu) From 5b24f38c4d0f9e7733a120462106a0c4ac44b5ff Mon Sep 17 00:00:00 2001 From: Paul GNING Date: Thu, 29 Apr 2021 17:44:23 +0200 Subject: [PATCH 004/167] [NF] Computation of skin effect for rectangular and round wires --- .../Validation/Electrical/test_skin_effect.py | 90 +++++++++++++++++++ .../Machine/Conductor/comp_phi_skin.py | 23 +++++ .../Machine/Conductor/comp_phip_skin.py | 23 +++++ .../Methods/Machine/Conductor/comp_power.py | 24 +++++ .../Machine/Conductor/comp_psi_skin.py | 23 +++++ .../Machine/Conductor/comp_psip_skin.py | 21 +++++ .../Machine/Conductor/comp_skin_effect.py | 72 +++++++++++++++ .../Simulation/EEC_PMSM/comp_parameters.py | 3 +- 8 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 Tests/Validation/Electrical/test_skin_effect.py create mode 100644 pyleecan/Methods/Machine/Conductor/comp_phi_skin.py create mode 100644 pyleecan/Methods/Machine/Conductor/comp_phip_skin.py create mode 100644 pyleecan/Methods/Machine/Conductor/comp_power.py create mode 100644 pyleecan/Methods/Machine/Conductor/comp_psi_skin.py create mode 100644 pyleecan/Methods/Machine/Conductor/comp_psip_skin.py create mode 100644 pyleecan/Methods/Machine/Conductor/comp_skin_effect.py diff --git a/Tests/Validation/Electrical/test_skin_effect.py b/Tests/Validation/Electrical/test_skin_effect.py new file mode 100644 index 000000000..e25951380 --- /dev/null +++ b/Tests/Validation/Electrical/test_skin_effect.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Apr 28 11:15:05 2021 + +@author: Paul +""" + +from os.path import join +from numpy.testing import assert_almost_equal + +from Tests import save_validation_path as save_path +from numpy import sqrt, pi +from multiprocessing import cpu_count + +import pytest + +from pyleecan.Classes.Simu1 import Simu1 +from pyleecan.Classes.InputElec import InputElec +from pyleecan.Classes.Electrical import Electrical +from pyleecan.Classes.EEC_PMSM import EEC_PMSM +from pyleecan.Classes.FluxLinkFEMM import FluxLinkFEMM +from pyleecan.Classes.IndMagFEMM import IndMagFEMM +from pyleecan.Classes.MagFEMM import MagFEMM +from pyleecan.Classes.Output import Output +from pyleecan.Functions.load import load + +# from pyleecan.Functions.Plot import dict_2D +from pyleecan.definitions import DATA_DIR + + +# @pytest.mark.long_5s +# @pytest.mark.MagFEMM +# @pytest.mark.EEC_PMSM +# @pytest.mark.IPMSM +# @pytest.mark.periodicity +# @pytest.mark.SingleOP +# def test_skin_effect(): +"""Validation of the PMSM Electrical Equivalent Circuit with the Prius machine +Compute Torque from EEC results and compare with Yang et al, 2013 +""" + +Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) +simu = Simu1(name="test_skin_effect", machine=Toyota_Prius) + +# Definition of the input +simu.input = InputElec(N0=2000, Nt_tot=10, Na_tot=2048) +simu.input.set_Id_Iq(I0=250 / sqrt(2), Phi0=60 * pi / 180) + +# # Define second simu for FEMM comparison +# simu2 = simu.copy() +# simu2.name = "test_EEC_PMSM_FEMM" + +# Definition of the electrical simulation (FEMM) +simu.elec = Electrical(type_skin_effect=1) +simu.elec.eec = EEC_PMSM( + indmag=IndMagFEMM(is_periodicity_a=True, Nt_tot=10), + fluxlink=FluxLinkFEMM(is_periodicity_a=True, Nt_tot=10), +) + +simu.mag = None +simu.force = None +simu.struct = None + +out = Output(simu=simu) +#%% +simu.run() + +# # Definition of the magnetic simulation (FEMM) +# simu2.mag = MagFEMM( +# type_BH_stator=0, +# type_BH_rotor=0, +# is_periodicity_a=True, +# nb_worker=cpu_count(), +# ) + +# out2 = Output(simu=simu2) +# simu2.run() + +# Plot 3-phase current function of time +# out.elec.get_Is().plot_2D_Data( +# "time", +# "phase", +# save_path=join(save_path, "EEC_FEMM_IPMSM_currents.png"), +# is_show_fig=False, +# **dict_2D +# ) + +# from Yang et al, 2013 +# assert_almost_equal(out.elec.Tem_av_ref, 81.81, decimal=1) +# assert_almost_equal(out2.mag.Tem_av, 81.70, decimal=1) diff --git a/pyleecan/Methods/Machine/Conductor/comp_phi_skin.py b/pyleecan/Methods/Machine/Conductor/comp_phi_skin.py new file mode 100644 index 000000000..daa85ae96 --- /dev/null +++ b/pyleecan/Methods/Machine/Conductor/comp_phi_skin.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from numpy import sinh, cosh, sin, cos + + +def comp_phi_skin(self, u): + """phi_skin for skin effect computation + + Parameters + ---------- + self : Conductor + An Conductor object + + Returns + ------- + None + """ + + y = ( + u * (sinh(2 * u) + sin(2 * u)) / (cosh(2 * u) - cos(2 * u)) + ) # Trickey model cf 5.26 p271 Pyrhonen + # y[u==0]=1 + + return y diff --git a/pyleecan/Methods/Machine/Conductor/comp_phip_skin.py b/pyleecan/Methods/Machine/Conductor/comp_phip_skin.py new file mode 100644 index 000000000..2fbfbc3db --- /dev/null +++ b/pyleecan/Methods/Machine/Conductor/comp_phip_skin.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from numpy import sinh, cosh, sin, cos + + +def comp_phip_skin(self, u): + """phip_skin for skin effect computation + + Parameters + ---------- + self : Conductor + An Conductor object + + Returns + ------- + None + """ + + y = ( + (3 / (2 * u)) * (sinh(2 * u) - sin(2 * u)) / (cosh(2 * u) - cos(2 * u)) + ) # p257 Pyrhonen + # y[u==0]=1 + + return y diff --git a/pyleecan/Methods/Machine/Conductor/comp_power.py b/pyleecan/Methods/Machine/Conductor/comp_power.py new file mode 100644 index 000000000..249ba0e2f --- /dev/null +++ b/pyleecan/Methods/Machine/Conductor/comp_power.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + + +def comp_power(self, output): + """Compute the electrical average power + + Parameters + ---------- + self : Electrical + an Electrical object + output : Output + an Output object + """ + + qs = output.simu.machine.stator.winding.qs + Id = output.elec.Id_ref + Iq = output.elec.Iq_ref + Ud = output.elec.Ud_ref + Uq = output.elec.Uq_ref + + # All quantities are in RMS + Pem_av_ref = qs * (Ud * Id + Uq * Iq) + + output.elec.Pem_av_ref = Pem_av_ref diff --git a/pyleecan/Methods/Machine/Conductor/comp_psi_skin.py b/pyleecan/Methods/Machine/Conductor/comp_psi_skin.py new file mode 100644 index 000000000..ba06a4b68 --- /dev/null +++ b/pyleecan/Methods/Machine/Conductor/comp_psi_skin.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from numpy import sinh, cosh, sin, cos + + +def comp_psi_skin(self, u): + """psi_skin for skin effect computation + + Parameters + ---------- + self : Conductor + An Conductor object + + Returns + ------- + None + """ + + y = ( + 2 * u * (sinh(u) - sin(u)) / (cosh(u) + cos(u)) + ) # Trickey model cf 5.26 p271 Pyrhonen + # y[u==0]=1 + + return y diff --git a/pyleecan/Methods/Machine/Conductor/comp_psip_skin.py b/pyleecan/Methods/Machine/Conductor/comp_psip_skin.py new file mode 100644 index 000000000..b73379850 --- /dev/null +++ b/pyleecan/Methods/Machine/Conductor/comp_psip_skin.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from numpy import sinh, cosh, sin, cos + + +def comp_psip_skin(self, u): + """psip_skin for skin effect computation + + Parameters + ---------- + self : Conductor + An Conductor object + + Returns + ------- + None + """ + + y = (1 / u) * (sinh(u) + sin(u)) / (cosh(u) + cos(u)) # p257 Pyrhonen + # y[u==0]=1 + + return y diff --git a/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py b/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py new file mode 100644 index 000000000..7804263eb --- /dev/null +++ b/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# from ....Classes.InputElec import InputElec +# from ....Classes.Slot import Slot +# from ....Classes.Conductor import Conductor +# from ....Classes.CondType11 import CondType11 +# from ....Classes.CondType12 import CondType12 + +from numpy import ones, pi, sqrt + + +def comp_skin_effect(self, T=20): + """Compute the electrical average power + + Parameters + ---------- + self : Conductor + an Conductor object + output : + Xkr_skinS, Xke_skinS + """ + + rhosw20 = self.cond_mat.elec.rho + alphasw = self.cond_mat.elec.alpha + rho = rhosw20 * (1 + alphasw * (T - 20)) + sigmar = 1 / rho + mu0 = 4 * pi * 1e-7 + ws = 2 * pi * self.parent.parent.parent.parent.input.comp_felec() + Slot = self.parent.parent.parent.stator.slot + type_skin_effect = self.parent.parent.parent.parent.elec.type_skin_effect + # nsw = len(ws) + + Xkr_skinS = 1 + Xke_skinS = 1 + + if type_skin_effect == 1: # analytical calculations based on Pyrhonen + # case of preformed rectangular wire CondType11 + if hasattr(self, "Wwire") and hasattr(self, "Hwire"): + Hwire = self.Hwire + Wwire = self.Wwire + Nwppc_rad = self.Nwppc_rad + Nwppc_tan = self.Nwppc_tan + W2s = Slot.W2 + # ksi=Hwire*sqrt((1/2)*ws*mu0*sigmar*Nwppc_tan*Wwire/W2s) + + # case of round wire CondType12 - approximation based on rectangular wire formula + elif hasattr(self, "Wwire") and not hasattr(self, "Hwire"): + Hwire = self.Wwire + Wwire = self.Wwire + Nwppc_tan = self.Nwppc + Nwppc_rad = self.Nwppc + W2s = Slot.W2 + # ksi=Hwire*sqrt((1/2)*ws*mu0*sigmar*Nwppc_tan*Wwire/W2s) + + # nsw=length(ws) + # kr_skin=ones(self.Nwppc_rad,nsw) + + # for k in self.Nwppc_rad + # kr_skin(k,:)=phi_skin(ksi)+k*(k-1)*psi_skin(ksi) + + # average resistance factor over the slot + ksi = Hwire * sqrt((1 / 2) * ws * mu0 * sigmar * Nwppc_tan * Wwire / W2s) + phi_skin = self.comp_phi_skin(ksi) + psi_skin = self.comp_psi_skin(ksi) + phip_skin = self.comp_phip_skin(ksi) + psip_skin = self.comp_psip_skin(ksi) + + Xkr_skinS = phi_skin + ((Nwppc_rad ^ 2 - 1) / 3) * psi_skin + Xke_skinS = (1 / Nwppc_rad ** 2) * phip_skin + ( + 1 - 1 / Nwppc_rad ** 2 + ) * psip_skin + + return Xkr_skinS, Xke_skinS diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py index ed8ae3e2d..e8b877f91 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py @@ -18,8 +18,7 @@ def comp_parameters(self, output): # compute skin_effect Xkr_skinS, Xke_skinS = Cond.comp_skin_effect(T=20) - # Xkr_skinS=1 - # Xke_skinS=1 + # Parameters to compute only once if "R20" not in PAR: PAR["R20"] = output.simu.machine.stator.comp_resistance_wind() From ef8d335cbb2bcc8ee9747070cda3a31575a54537 Mon Sep 17 00:00:00 2001 From: Paul GNING Date: Thu, 29 Apr 2021 17:50:46 +0200 Subject: [PATCH 005/167] [BC] Multiplication mistake --- pyleecan/Methods/Machine/Conductor/comp_skin_effect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py b/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py index 7804263eb..e2983d51a 100644 --- a/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py +++ b/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py @@ -64,7 +64,7 @@ def comp_skin_effect(self, T=20): phip_skin = self.comp_phip_skin(ksi) psip_skin = self.comp_psip_skin(ksi) - Xkr_skinS = phi_skin + ((Nwppc_rad ^ 2 - 1) / 3) * psi_skin + Xkr_skinS = phi_skin + ((Nwppc_rad **2 2 - 1) / 3) * psi_skin Xke_skinS = (1 / Nwppc_rad ** 2) * phip_skin + ( 1 - 1 / Nwppc_rad ** 2 ) * psip_skin From 1f884acb57ef2cb736097e72817d90a5e309e448 Mon Sep 17 00:00:00 2001 From: Paul GNING Date: Thu, 29 Apr 2021 17:52:42 +0200 Subject: [PATCH 006/167] [BC] power error solved --- pyleecan/Methods/Machine/Conductor/comp_skin_effect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py b/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py index e2983d51a..f6c1744be 100644 --- a/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py +++ b/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py @@ -64,7 +64,7 @@ def comp_skin_effect(self, T=20): phip_skin = self.comp_phip_skin(ksi) psip_skin = self.comp_psip_skin(ksi) - Xkr_skinS = phi_skin + ((Nwppc_rad **2 2 - 1) / 3) * psi_skin + Xkr_skinS = phi_skin + ((Nwppc_rad ** 2 - 1) / 3) * psi_skin Xke_skinS = (1 / Nwppc_rad ** 2) * phip_skin + ( 1 - 1 / Nwppc_rad ** 2 ) * psip_skin From b7810bf2978ed20225bc4ced72c4792105fbe01a Mon Sep 17 00:00:00 2001 From: Paul GNING Date: Fri, 30 Apr 2021 16:58:10 +0200 Subject: [PATCH 007/167] [CC] cleaning code for better comprehension --- pyleecan/Methods/Machine/Conductor/comp_skin_effect.py | 7 ------- pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py | 4 ++-- pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py | 8 ++++---- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py b/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py index f6c1744be..b45440c59 100644 --- a/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py +++ b/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py @@ -49,13 +49,6 @@ def comp_skin_effect(self, T=20): Nwppc_tan = self.Nwppc Nwppc_rad = self.Nwppc W2s = Slot.W2 - # ksi=Hwire*sqrt((1/2)*ws*mu0*sigmar*Nwppc_tan*Wwire/W2s) - - # nsw=length(ws) - # kr_skin=ones(self.Nwppc_rad,nsw) - - # for k in self.Nwppc_rad - # kr_skin(k,:)=phi_skin(ksi)+k*(k-1)*psi_skin(ksi) # average resistance factor over the slot ksi = Hwire * sqrt((1 / 2) * ws * mu0 * sigmar * Nwppc_tan * Wwire / W2s) diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py index e8b877f91..b2be72a79 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py @@ -21,8 +21,8 @@ def comp_parameters(self, output): # Parameters to compute only once if "R20" not in PAR: - PAR["R20"] = output.simu.machine.stator.comp_resistance_wind() - PAR["R20"] = PAR["R20"] * Xkr_skinS + R20 = output.simu.machine.stator.comp_resistance_wind() + PAR["R20"] = R20 * Xkr_skinS if "phi" not in PAR: PAR["phi"] = self.fluxlink.comp_fluxlinkage(output) diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py index 191c4c530..363e94419 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py @@ -37,8 +37,8 @@ def comp_parameters(self, output): # Parameters to compute only if they are not set if "Rs" not in self.parameters or self.parameters["Rs"] is None: - self.parameters["Rs"] = machine.stator.comp_resistance_wind(T=Tws) - self.parameters["Rs"] = self.parameters["Rs"] * Xkr_skinS + Rs = machine.stator.comp_resistance_wind(T=Tws) + self.parameters["Rs"] = Rs * Xkr_skinS if "Rr_norm" not in self.parameters or self.parameters["Rr_norm"] is None: # 3 phase equivalent rotor resistance @@ -140,8 +140,8 @@ def comp_parameters(self, output): Phi_s, Phi_r = _comp_flux_mean(self, out) self.parameters["Lm"] = (Phi_r * norm * Zsr / 3) / self.I - self.parameters["Ls"] = (Phi_s - (Phi_r * norm * Zsr / 3)) / self.I - self.parameters["Ls"] = self.parameters["Ls"] * Xke_skinS + Ls = (Phi_s - (Phi_r * norm * Zsr / 3)) / self.I + self.parameters["Ls"] = Ls * Xke_skinS # --- compute the main inductance and rotor stray inductance --- # set new output out = Output(simu=simu) From 2e91a4efb935d59895f042d3a56dcb12114973f1 Mon Sep 17 00:00:00 2001 From: Sijie-NI Date: Fri, 28 May 2021 14:28:50 +0200 Subject: [PATCH 008/167] [WP] EEC file LSRPM --- Tests/Validation/Electrical/test_EEC_LSRPM.py | 87 +++++ pyleecan/Classes/EEC_LSRPM.py | 334 ++++++++++++++++++ .../ClassesRef/Simulation/EEC_LSRPM.csv | 5 + .../Methods/Simulation/EEC_LSRPM/__init__.py | 1 + .../Simulation/EEC_LSRPM/comp_joule_losses.py | 27 ++ .../Simulation/EEC_LSRPM/comp_parameters.py | 113 ++++++ .../Methods/Simulation/EEC_LSRPM/gen_drive.py | 10 + .../Methods/Simulation/EEC_LSRPM/solve_EEC.py | 107 ++++++ 8 files changed, 684 insertions(+) create mode 100644 Tests/Validation/Electrical/test_EEC_LSRPM.py create mode 100644 pyleecan/Classes/EEC_LSRPM.py create mode 100644 pyleecan/Generator/ClassesRef/Simulation/EEC_LSRPM.csv create mode 100644 pyleecan/Methods/Simulation/EEC_LSRPM/__init__.py create mode 100644 pyleecan/Methods/Simulation/EEC_LSRPM/comp_joule_losses.py create mode 100644 pyleecan/Methods/Simulation/EEC_LSRPM/comp_parameters.py create mode 100644 pyleecan/Methods/Simulation/EEC_LSRPM/gen_drive.py create mode 100644 pyleecan/Methods/Simulation/EEC_LSRPM/solve_EEC.py diff --git a/Tests/Validation/Electrical/test_EEC_LSRPM.py b/Tests/Validation/Electrical/test_EEC_LSRPM.py new file mode 100644 index 000000000..85bbf7460 --- /dev/null +++ b/Tests/Validation/Electrical/test_EEC_LSRPM.py @@ -0,0 +1,87 @@ +from os.path import join +from numpy.testing import assert_almost_equal + +from Tests import save_validation_path as save_path +from numpy import sqrt, pi +from multiprocessing import cpu_count + +import pytest + +from pyleecan.Classes.Simu1 import Simu1 +from pyleecan.Classes.InputElec import InputElec +from pyleecan.Classes.Electrical import Electrical +from pyleecan.Classes.EEC_PMSM import EEC_PMSM +from pyleecan.Classes.FluxLinkFEMM import FluxLinkFEMM +from pyleecan.Classes.IndMagFEMM import IndMagFEMM +from pyleecan.Classes.MagFEMM import MagFEMM +from pyleecan.Classes.Output import Output +from pyleecan.Functions.load import load +from pyleecan.Functions.Plot import dict_2D +from pyleecan.definitions import DATA_DIR + + +@pytest.mark.long_5s +@pytest.mark.MagFEMM +@pytest.mark.EEC_PMSM +@pytest.mark.IPMSM +@pytest.mark.periodicity +@pytest.mark.SingleOP +def test_EEC_PMSM(): + """Validation of LSRPM EEC from Sijie's PhD thesis + """ + + LSRPM = load("LSRPM.json")) + simu = Simu1(name="test_EEC_LSRPM", machine=LSRPM) + + # Definition of the input + simu.input = InputElec(N0=2000, Nt_tot=10, Na_tot=2048) + simu.input.set_Id_Iq(I0=250 / sqrt(2), Phi0=60 * pi / 180) + + # Define second simu for FEMM comparison + simu2 = simu.copy() + simu2.name = "test_EEC_LSRPM_FEMM" + + # Definition of the electrical simulation (FEMM) + simu.elec = Electrical() + simu.elec.eec = EEC_LPMSM( + indmag=IndMagFEMM(is_periodicity_a=True, Nt_tot=10), + fluxlink=FluxLinkFEMM(is_periodicity_a=True, Nt_tot=10), + ) + + simu.mag = None + simu.force = None + simu.struct = None + + out = Output(simu=simu) + simu.run() + + # Definition of the magnetic simulation (FEMM) + simu2.mag = MagFEMM( + type_BH_stator=0, + type_BH_rotor=0, + is_periodicity_a=True, + nb_worker=cpu_count(), + ) + + out2 = Output(simu=simu2) + simu2.run() + + # Plot 3-phase current function of time + out.elec.get_Is().plot_2D_Data( + "time", + "phase", + save_path=join(save_path, "EEC_FEMM_IPMSM_currents.png"), + is_show_fig=False, + **dict_2D + ) + + # from Yang et al, 2013 + assert_almost_equal(out.elec.Tem_av_ref, 81.81, decimal=1) + assert_almost_equal(out2.mag.Tem_av, 81.70, decimal=1) + + return out, out2 + + +# To run it without pytest +if __name__ == "__main__": + out, out2 = test_EEC_PMSM() diff --git a/pyleecan/Classes/EEC_LSRPM.py b/pyleecan/Classes/EEC_LSRPM.py new file mode 100644 index 000000000..fa9c37bce --- /dev/null +++ b/pyleecan/Classes/EEC_LSRPM.py @@ -0,0 +1,334 @@ +# -*- coding: utf-8 -*- +# File generated according to Generator/ClassesRef/Simulation/EEC_LSRPM.csv +# WARNING! All changes made in this file will be lost! +"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Simulation/EEC_LSRPM +""" + +from os import linesep +from sys import getsizeof +from logging import getLogger +from ._check import check_var, raise_ +from ..Functions.get_logger import get_logger +from ..Functions.save import save +from ..Functions.copy import copy +from ..Functions.load import load_init_dict +from ..Functions.Load.import_class import import_class +from .EEC import EEC + +# Import all class method +# Try/catch to remove unnecessary dependencies in unused method +try: + from ..Methods.Simulation.EEC_LSRPM.comp_parameters import comp_parameters +except ImportError as error: + comp_parameters = error + +try: + from ..Methods.Simulation.EEC_LSRPM.solve_EEC import solve_EEC +except ImportError as error: + solve_EEC = error + +try: + from ..Methods.Simulation.EEC_LSRPM.gen_drive import gen_drive +except ImportError as error: + gen_drive = error + +try: + from ..Methods.Simulation.EEC_LSRPM.comp_joule_losses import comp_joule_losses +except ImportError as error: + comp_joule_losses = error + + +from ._check import InitUnKnowClassError +from .FluxLink import FluxLink + + +class EEC_LSRPM(EEC): + """Electric module: Electrical Equivalent Circuit for Squirrel Cage Induction Machine""" + + VERSION = 1 + + # Check ImportError to remove unnecessary dependencies in unused method + # cf Methods.Simulation.EEC_LSRPM.comp_parameters + if isinstance(comp_parameters, ImportError): + comp_parameters = property( + fget=lambda x: raise_( + ImportError( + "Can't use EEC_LSRPM method comp_parameters: " + + str(comp_parameters) + ) + ) + ) + else: + comp_parameters = comp_parameters + # cf Methods.Simulation.EEC_LSRPM.solve_EEC + if isinstance(solve_EEC, ImportError): + solve_EEC = property( + fget=lambda x: raise_( + ImportError("Can't use EEC_LSRPM method solve_EEC: " + str(solve_EEC)) + ) + ) + else: + solve_EEC = solve_EEC + # cf Methods.Simulation.EEC_LSRPM.gen_drive + if isinstance(gen_drive, ImportError): + gen_drive = property( + fget=lambda x: raise_( + ImportError("Can't use EEC_LSRPM method gen_drive: " + str(gen_drive)) + ) + ) + else: + gen_drive = gen_drive + # cf Methods.Simulation.EEC_LSRPM.comp_joule_losses + if isinstance(comp_joule_losses, ImportError): + comp_joule_losses = property( + fget=lambda x: raise_( + ImportError( + "Can't use EEC_LSRPM method comp_joule_losses: " + + str(comp_joule_losses) + ) + ) + ) + else: + comp_joule_losses = comp_joule_losses + # save and copy methods are available in all object + save = save + copy = copy + # get_logger method is available in all object + get_logger = get_logger + + def __init__( + self, + parameters=-1, + N0=1500, + felec=100, + fluxlink=None, + init_dict=None, + init_str=None, + ): + """Constructor of the class. Can be use in three ways : + - __init__ (arg1 = 1, arg3 = 5) every parameters have name and default values + for pyleecan type, -1 will call the default constructor + - __init__ (init_dict = d) d must be a dictionnary with property names as keys + - __init__ (init_str = s) s must be a string + s is the file path to load + + ndarray or list can be given for Vector and Matrix + object or dict can be given for pyleecan Object""" + + if init_str is not None: # Load from a file + init_dict = load_init_dict(init_str)[1] + if init_dict is not None: # Initialisation by dict + assert type(init_dict) is dict + # Overwrite default value with init_dict content + if "parameters" in list(init_dict.keys()): + parameters = init_dict["parameters"] + if "N0" in list(init_dict.keys()): + N0 = init_dict["N0"] + if "felec" in list(init_dict.keys()): + felec = init_dict["felec"] + if "fluxlink" in list(init_dict.keys()): + fluxlink = init_dict["fluxlink"] + # Set the properties (value check and convertion are done in setter) + self.parameters = parameters + self.N0 = N0 + self.felec = felec + self.fluxlink = fluxlink + # Call EEC init + super(EEC_LSRPM, self).__init__() + # The class is frozen (in EEC init), for now it's impossible to + # add new properties + + def __str__(self): + """Convert this object in a readeable string (for print)""" + + EEC_LSRPM_str = "" + # Get the properties inherited from EEC + EEC_LSRPM_str += super(EEC_LSRPM, self).__str__() + EEC_LSRPM_str += "parameters = " + str(self.parameters) + linesep + EEC_LSRPM_str += "N0 = " + str(self.N0) + linesep + EEC_LSRPM_str += "felec = " + str(self.felec) + linesep + if self.fluxlink is not None: + tmp = self.fluxlink.__str__().replace(linesep, linesep + "\t").rstrip("\t") + EEC_LSRPM_str += "fluxlink = " + tmp + else: + EEC_LSRPM_str += "fluxlink = None" + linesep + linesep + return EEC_LSRPM_str + + def __eq__(self, other): + """Compare two objects (skip parent)""" + + if type(other) != type(self): + return False + + # Check the properties inherited from EEC + if not super(EEC_LSRPM, self).__eq__(other): + return False + if other.parameters != self.parameters: + return False + if other.N0 != self.N0: + return False + if other.felec != self.felec: + return False + if other.fluxlink != self.fluxlink: + return False + return True + + def compare(self, other, name="self"): + """Compare two objects and return list of differences""" + + if type(other) != type(self): + return ["type(" + name + ")"] + diff_list = list() + + # Check the properties inherited from EEC + diff_list.extend(super(EEC_LSRPM, self).compare(other, name=name)) + if other._parameters != self._parameters: + diff_list.append(name + ".parameters") + if other._N0 != self._N0: + diff_list.append(name + ".N0") + if other._felec != self._felec: + diff_list.append(name + ".felec") + if (other.fluxlink is None and self.fluxlink is not None) or ( + other.fluxlink is not None and self.fluxlink is None + ): + diff_list.append(name + ".fluxlink None mismatch") + elif self.fluxlink is not None: + diff_list.extend( + self.fluxlink.compare(other.fluxlink, name=name + ".fluxlink") + ) + return diff_list + + def __sizeof__(self): + """Return the size in memory of the object (including all subobject)""" + + S = 0 # Full size of the object + + # Get size of the properties inherited from EEC + S += super(EEC_LSRPM, self).__sizeof__() + if self.parameters is not None: + for key, value in self.parameters.items(): + S += getsizeof(value) + getsizeof(key) + S += getsizeof(self.N0) + S += getsizeof(self.felec) + S += getsizeof(self.fluxlink) + return S + + def as_dict(self, **kwargs): + """ + Convert this object in a json serializable dict (can be use in __init__). + Optional keyword input parameter is for internal use only + and may prevent json serializability. + """ + + # Get the properties inherited from EEC + EEC_LSRPM_dict = super(EEC_LSRPM, self).as_dict(**kwargs) + EEC_LSRPM_dict["parameters"] = ( + self.parameters.copy() if self.parameters is not None else None + ) + EEC_LSRPM_dict["N0"] = self.N0 + EEC_LSRPM_dict["felec"] = self.felec + if self.fluxlink is None: + EEC_LSRPM_dict["fluxlink"] = None + else: + EEC_LSRPM_dict["fluxlink"] = self.fluxlink.as_dict(**kwargs) + # The class name is added to the dict for deserialisation purpose + # Overwrite the mother class name + EEC_LSRPM_dict["__class__"] = "EEC_LSRPM" + return EEC_LSRPM_dict + + def _set_None(self): + """Set all the properties to None (except pyleecan object)""" + + self.parameters = None + self.N0 = None + self.felec = None + if self.fluxlink is not None: + self.fluxlink._set_None() + # Set to None the properties inherited from EEC + super(EEC_LSRPM, self)._set_None() + + def _get_parameters(self): + """getter of parameters""" + return self._parameters + + def _set_parameters(self, value): + """setter of parameters""" + if type(value) is int and value == -1: + value = dict() + check_var("parameters", value, "dict") + self._parameters = value + + parameters = property( + fget=_get_parameters, + fset=_set_parameters, + doc=u"""Parameters of the EEC: computed if empty, or enforced + + :Type: dict + """, + ) + + def _get_N0(self): + """getter of N0""" + return self._N0 + + def _set_N0(self, value): + """setter of N0""" + check_var("N0", value, "int") + self._N0 = value + + N0 = property( + fget=_get_N0, + fset=_set_N0, + doc=u"""Rotation speed + + :Type: int + """, + ) + + def _get_felec(self): + """getter of felec""" + return self._felec + + def _set_felec(self, value): + """setter of felec""" + check_var("felec", value, "int") + self._felec = value + + felec = property( + fget=_get_felec, + fset=_set_felec, + doc=u"""frequency + + :Type: int + """, + ) + + def _get_fluxlink(self): + """getter of fluxlink""" + return self._fluxlink + + def _set_fluxlink(self, value): + """setter of fluxlink""" + if isinstance(value, str): # Load from file + value = load_init_dict(value)[1] + if isinstance(value, dict) and "__class__" in value: + class_obj = import_class( + "pyleecan.Classes", value.get("__class__"), "fluxlink" + ) + value = class_obj(init_dict=value) + elif type(value) is int and value == -1: # Default constructor + value = FluxLink() + check_var("fluxlink", value, "FluxLink") + self._fluxlink = value + + if self._fluxlink is not None: + self._fluxlink.parent = self + + fluxlink = property( + fget=_get_fluxlink, + fset=_set_fluxlink, + doc=u"""Flux Linkage + + :Type: FluxLink + """, + ) diff --git a/pyleecan/Generator/ClassesRef/Simulation/EEC_LSRPM.csv b/pyleecan/Generator/ClassesRef/Simulation/EEC_LSRPM.csv new file mode 100644 index 000000000..6a1099166 --- /dev/null +++ b/pyleecan/Generator/ClassesRef/Simulation/EEC_LSRPM.csv @@ -0,0 +1,5 @@ +Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille +parameters,-,"Parameters of the EEC: computed if empty, or enforced",,dict,{},,,,Simulation,EEC,comp_parameters,VERSION,1,Electric module: Electrical Equivalent Circuit for Squirrel Cage Induction Machine, +N0,RPM,Rotation speed,,int,1500,,,,,,solve_EEC,,,, +felec,Hz,frequency,,int,100,,,,,,gen_drive,,,, +fluxlink,-,Flux Linkage,,FluxLink,None,,,,,,comp_joule_losses,,,, diff --git a/pyleecan/Methods/Simulation/EEC_LSRPM/__init__.py b/pyleecan/Methods/Simulation/EEC_LSRPM/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyleecan/Methods/Simulation/EEC_LSRPM/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyleecan/Methods/Simulation/EEC_LSRPM/comp_joule_losses.py b/pyleecan/Methods/Simulation/EEC_LSRPM/comp_joule_losses.py new file mode 100644 index 000000000..3590777b3 --- /dev/null +++ b/pyleecan/Methods/Simulation/EEC_LSRPM/comp_joule_losses.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + + +def comp_joule_losses(self, output): + """Compute the electrical Joule losses + + Parameters + ---------- + self : Electrical + an Electrical object + output : Output + an Output object + """ + + qs = output.simu.machine.stator.winding.qs + I_ds = output.elec.Ids_ref + I_qs = output.elec.Iqs_ref + I_da = output.elec.Ida_ref + I_qa = output.elec.Iqa_ref + + R_s = self.parameters["R_s"] + R_a = self.parameters["R_a"] + + # Id and Iq are in RMS + Pj_losses = qs * (R_s * (I_ds ** 2 + I_qs ** 2) + R_a * (I_da ** 2 + I_qa ** 2)) + + output.elec.Pj_losses = Pj_losses diff --git a/pyleecan/Methods/Simulation/EEC_LSRPM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_LSRPM/comp_parameters.py new file mode 100644 index 000000000..00c0baf3c --- /dev/null +++ b/pyleecan/Methods/Simulation/EEC_LSRPM/comp_parameters.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- + + +def comp_parameters(self, output): + """Compute the parameters dict for the equivalent electrical circuit: + resistance, inductance and back electromotive force + Parameters + ---------- + self : EEC_LSRPM + an EEC_LSRPM object + output : Output + an Output object + """ + machine = output.simu.machine + p = machine.rotor.winding.p + PAR = self.parameters + + if self.N0 is None and self.felec is None: + N0 = 100 * 60 / p + felec = 100 + elif self.N0 is None and self.felec is not None: + N0 = self.felec * 60 / p + elif self.N0 is not None and self.felec is None: + N0 = self.N0 + felec = N0 / 60 * p + else: + N0 = self.N0 + felec = self.felec + + N_s = machine.stator.winding.comp_Ntspc() # stator number of turns + # TODO defind the method "comp_N_auxiliary" + # N_a = machine.stator.winding.comp_N_auxiliary() # damper winding number of turns + norm = 1 # rotor - stator transformation factor N_a=N_s + + self.parameters["norm"] = norm + + Tws = 20 if "Tws" not in self.parameters else self.parameter["Tws"] + Twr = 20 if "Twr" not in self.parameters else self.parameter["Twr"] + + # TODO all methods should be verfied for PMSM with damper winding + if "R_s" not in self.parameters or self.parameters["Rs"] is None: + # 3 phase equivalent stator resistance + self.parameters["Rs"] = machine.stator.comp_resistance_wind(T=Tws) + + if "R_a" not in self.parameters or self.parameters["R_a"] is None: + # 3 phase equivalent damper resistance + # TODO have to defind the method"comp_resistance_damper_winding" + R_a = machine.rotor.comp_resistance_damper_winding(T=Twr, qs=3) + self.parameters["R_a"] = norm ** 2 * R_a + + if "Phi_m" not in PAR: + PAR["Phi_m"] = self.fluxlink.comp_fluxlinkage(output) + + if "C_a" not in PAR: + self.parameters["C_a"] = 0 # Not sure + + is_comp_ind = False + # check for complete parameter set + # (there may be some redundancy here but it seems simplier to implement) + # if not all(k in PAR for k in ("Phid", "Phiq", "Ld", "Lq")): + # is_comp_ind = True + + # check for d- and q-current (change) + if "I_ds" not in PAR or PAR["I_ds"] != output.elec.Ids_ref: + PAR["I_ds"] = output.elec.Ids_ref + is_comp_ind = True + + if "I_qs" not in PAR or PAR["I_qs"] != output.elec.Iqs_ref: + PAR["I_qs"] = output.elec.Iqs_ref + is_comp_ind = True + + if "I_da" not in PAR or PAR["I_da"] != output.elec.Ida_ref: + PAR["I_da"] = output.elec.Ida_ref + is_comp_ind = True + + if "I_qa" not in PAR or PAR["I_qa"] != output.elec.Iqa_ref: + PAR["I_qa"] = output.elec.Iqa_ref + is_comp_ind = True + + if "L_dss" not in self.parameters or self.parameters["L_dss"] is None: + is_comp_ind = True + + if "L_dmu" not in self.parameters or self.parameters["L_dmu"] is None: + is_comp_ind = True + + if "L_dsa" not in self.parameters or self.parameters["L_dsa"] is None: + is_comp_ind = True + + if "L_daa" not in self.parameters or self.parameters["L_daa"] is None: + is_comp_ind = True + + if "L_qss" not in self.parameters or self.parameters["L_qss"] is None: + is_comp_ind = True + + if "L_qmu" not in self.parameters or self.parameters["L_qmu"] is None: + is_comp_ind = True + + if "Lr_qaa" not in self.parameters or self.parameters["Lr_qaa"] is None: + is_comp_ind = True + + # compute inductance if necessary + if is_comp_ind: + print("We will return soon") + print("Please input all variables") + + # TODO It is complexe to calculate + # (Phi_ds, Phi_qs) = self.indmag.comp_inductance(output) #Have to calculate stator flux by FEA + # (phi_da, phi_qa) = self.indmag.comp_inductance(output) #Have to calculate damper flux bu FEA + + # PAR["Phi_ds"] = Phi_ds + # PAR["Phi_qs"] = Phi_qs + # PAR["Phi_da"] = Phi_da + # PAR["Phi_qa"] = Phi_qa diff --git a/pyleecan/Methods/Simulation/EEC_LSRPM/gen_drive.py b/pyleecan/Methods/Simulation/EEC_LSRPM/gen_drive.py new file mode 100644 index 000000000..b43d04f85 --- /dev/null +++ b/pyleecan/Methods/Simulation/EEC_LSRPM/gen_drive.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- + +from ....Functions.Electrical.coordinate_transformation import n2dq +from numpy import split, transpose, mean, pi + +import matplotlib.pyplot as plt + + +def gen_drive(self, output): + pass diff --git a/pyleecan/Methods/Simulation/EEC_LSRPM/solve_EEC.py b/pyleecan/Methods/Simulation/EEC_LSRPM/solve_EEC.py new file mode 100644 index 000000000..f6c43c5c2 --- /dev/null +++ b/pyleecan/Methods/Simulation/EEC_LSRPM/solve_EEC.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +from numpy import array, pi +from scipy.linalg import solve + + +def solve_EEC(self, output): + """Compute the parameters dict for the equivalent electrical circuit + cf PhD thesis of SIJIE NI + "Damper winding for noise and vibration reductionof Permanent Magnet Synchronous Machine" + + + -----R_s--L_dss-------------------------------- -----R_s-----L_qss---------------------------------------------------- + | | | | | | + ^ | L_dmu L_daa ^ | | Lr_qaa + | | | | | | | | + | Uds L_dsa R_a | Uqs L_qmu Rr_a + | | | | | | | | + | | C_a | | C_a + | | | | | | + --wsL_qssI_qs---wsL_qmu(I_qs-Ir_qa)------------ ---wsPhi_m---wsL_dssI_ds---ws(L_dss+L_dmu)(I_ds-Ir_da)---------------- + <-------- <---------------- <------ <---------- <------------------------- + <---------------------------- <------------------------------------------------- + wsPhi_qs wsPhi_ds + + Parameters + ---------- + self : EEC_LSRPM + an EEC_LSRPM object + output : Output + an Output object + """ + + felec = output.elec.felec + ws = 2 * pi * felec + + R_s = self.parameters["R_s"] + Rr_a = self.parameters["Rr_a"] + C_a = self.parameters["C_a"] + norm = self.parameters["norm"] + Phi_m = self.parametrs["Phi_m"] + + # d-axis + L_dss = self.parameters["L_dss"] + L_dmu = self.parameters["L_dmu"] + L_dsa = self.parameters["L_dsa"] + L_daa = self.parameters["L_daa"] + U_ds = self.parameters["U_ds"] + I_ds = self.parameters["I_ds"] + I_da = self.parameters["I_da"] + + # q-axis + L_qss = self.parameters["L_qss"] + L_qmu = self.parameters["L_qmu"] + Lr_qaa = self.parameters["Lr_qaa"] + U_qs = self.parameters["U_qs"] + I_qs = self.parameters["I_qs"] + I_qa = self.parameters["I_qa"] + + # Prepare linear system + + # Solve system + if "U_ds" in self.parameters: + XR = array( + [ + [R_s, -ws * (L_qss + L_qmu), 0, -ws * L_qmu], + [ws * (L_dss + L_dmu + L_dsa), R_s, -ws * (L_dmu + L_dsa), 0], + [0, -ws * L_qmu, Rr_a + 1 / (ws * C_a), ws * (Lr_qaa + L_qmu)], + [ + ws * (L_dmu + L_dsa), + 0, + -ws * (L_daa + L_dmu + L_dsa), + Rr_a + 1 / (ws * C_a), + ], + ] + ) + XE = array([0, ws * Phi_m, 0, ws * Phi_m]) + XU = array([U_ds, U_qs, 0, 0]) + XI = solve(XR, XU - XE) + + output.elec.Ids_ref = XI[0] + output.elec.Iqs_ref = XI[1] + output.elec.Ida_ref = XI[2] + output.elec.Iqa_ref = XI[3] + else: + + output.elec.Uds_ref = ( + I_ds * (R_s + ws * L_dss) + + (I_ds - I_da) * ws * (L_dmu + L_dsa) + - ws * L_qmu * (I_qs - I_qa) + - ws * L_qss * I_qs + ) + output.elec.Uqs_ref = ( + I_qs * (R_s + ws * L_qss) + + (I_qs - I_qa) * ws * L_qmu + + ws * Phi_m + + ws * L_dss * I_ds + + ws * (L_dss + L_dmu) * (I_ds - I_da) + ) + + # Compute currents + output.elec.Is = None + output.elec.Is = output.elec.get_Is() + + # Compute voltage + output.elec.Us = None + output.elec.Us = output.elec.get_Us() From 5994e780ebc35205665dba080c845e81af0bfb18 Mon Sep 17 00:00:00 2001 From: Sijie-NI Date: Tue, 27 Jul 2021 14:40:19 +0200 Subject: [PATCH 009/167] [WP] Two new random switching frequency strategies are added in Pyleecan (Random fswi and Symetrical random fswi) --- Tests/Methods/Import/test_ImportGenPWM.py | 26 +++++-- pyleecan/Functions/Electrical/comp_PWM.py | 77 +++++++++++-------- .../Methods/Import/ImportGenPWM/get_data.py | 4 +- 3 files changed, 64 insertions(+), 43 deletions(-) diff --git a/Tests/Methods/Import/test_ImportGenPWM.py b/Tests/Methods/Import/test_ImportGenPWM.py index b95747758..fa68e27ad 100644 --- a/Tests/Methods/Import/test_ImportGenPWM.py +++ b/Tests/Methods/Import/test_ImportGenPWM.py @@ -18,7 +18,7 @@ def testSPWM(): """Check """ # fs, duration, f,fmax,fmode, fswimode,fswi, fswi_max,typePWM, Vdc1, U0, type_carrier for ii in range(2): - for jj in range(2): + for jj in range(4): for hh in range(4): test_obj = ImportGenPWM( fs=96000, @@ -31,16 +31,23 @@ def testSPWM(): fswi_max=30, typePWM=8, Vdc1=2, - U0=0.77, + U0=0.70, type_carrier=hh, ) # Generate the signal time = linspace(start=0, stop=2, num=2 * 96000, endpoint=True) - result = test_obj.get_data() + Triphase = test_obj.get_data()[0] + Vas=test_obj.get_data()[1] + Triangle=test_obj.get_data()[3] + + + # Plot/save the result plt.close("all") - plt.plot(time, result[:, 1]) + plt.plot(time, Triphase[:, 1]) + plt.plot(time, Vas) + plt.plot(time, Triangle) fig = plt.gcf() fig.savefig( join( @@ -71,16 +78,21 @@ def testDPWM(): fswi_max=30, typePWM=ii, Vdc1=2, - U0=0.77, + U0=0.70, type_carrier=0, ) # Generate the signal time = linspace(start=0, stop=2, num=2 * 96000, endpoint=True) - result = test_obj.get_data() + Triphase = test_obj.get_data()[0] + Vas=test_obj.get_data()[1] + Triangle=test_obj.get_data()[3] + # Plot/save the result plt.close("all") - plt.plot(time, result[:, 1]) + plt.plot(time, Triphase[:, 1]) + plt.plot(time, Vas) + plt.plot(time, Triangle) fig = plt.gcf() fig.savefig(join(save_path, "test_ImportGenPWM_" + str(ii) + ".png")) diff --git a/pyleecan/Functions/Electrical/comp_PWM.py b/pyleecan/Functions/Electrical/comp_PWM.py index 14b2426c9..1cb7155ce 100644 --- a/pyleecan/Functions/Electrical/comp_PWM.py +++ b/pyleecan/Functions/Electrical/comp_PWM.py @@ -1,5 +1,6 @@ import numpy as np -from scipy import signal +from numpy.core.numeric import ones +from scipy import signal, integrate def comp_volt_PWM_NUM( @@ -63,10 +64,12 @@ def comp_volt_PWM_NUM( fswimode: int 0: Fixed fswi 1: Variable fswi + 2: Random fswi + 3: Symetrical random fswi """ Npsim = len(Tpwmu) - + triangle=np.ones(len(Tpwmu)) if fmode == 0: # Fixed speed: ws = 2 * np.pi * freq0 elif fmode == 1: # Variable speed: @@ -85,7 +88,7 @@ def comp_volt_PWM_NUM( triangle = Vdc1 / 2 * comp_carrier(Tpwmu, fswi, type_carrier) else: Th = 1 / fswi - elif fswimode == 1: # Variable fswi: + elif fswimode == 1: # Variable fswi (ramp): if type_DPWM == 8: wswiT = ( np.pi * (fswi_max - fswi) / Tpwmu[-1] * Tpwmu ** 2 @@ -94,6 +97,41 @@ def comp_volt_PWM_NUM( triangle = Vdc1 / 2 * signal.sawtooth(wswiT, 0.5) else: print("ERROR:only SPWM supports the varaible switching frequency") + elif fswimode == 2 or fswimode== 3: # Random fswi & Symetrical random fswi + t1=round(Tpwmu[-1]*5000000) + if fswimode=='Symetrical random fswi': + num_slice=round((fswi_max+fswi)/2*Tpwmu[-1]) + delta_fswi=np.random.randint(fswi, high=fswi_max+1, size=num_slice*2,dtype=int) + delta_fswi[1::2]=delta_fswi[0::2]*-1 + # elif fswimode=='Random carrier amplitude': + # num_slice=round(fswiMin*duration) + # delta_fswi=np.random.randint(fswiMin, high=fswiMin+1, size=num_slice*2,dtype=int) + # delta_fswi[1::2]=delta_fswi[0::2]*-1 + + else: + + num_slice=round((fswi_max+fswi)/2*Tpwmu[-1]) + delta_fswi=np.random.randint(fswi, high=fswi_max+1, size=num_slice*2,dtype=int) + delta_fswi[1::2]=delta_fswi[1::2]*-1 + + fswi_base=np.array(np.ones(t1)) + S_delta=1 + delta_t=S_delta/abs(delta_fswi) + time=sum(delta_t) + delta_point=delta_t[:-1]/time*t1 + delta_point=np.array(delta_point) + delta_point=np.append(delta_point,t1-sum(delta_point)) + fswi=np.concatenate([fswi_base[0: round(delta_point[ii])]*delta_fswi[ii] for ii in range(len(delta_fswi))]) + if len(fswi) ((n + 1) * Th - T3)] = -Vdc1 / 2 - if is_plot: - fig, axs = plt.subplots(2) - axs[0].plot(v_pwm[0]) - axs[0].plot(Tpwmu, v_pwm[1]) - axs[0].plot(Tpwmu, v_pwm[2]) - axs[1].plot(Tpwmu, Van) - axs[1].plot(Tpwmu, V_offset) - axs[1].plot(Tpwmu, Vas) - fig.show() - plt.show() - - if type_DPWM == 8: - fig, axs = plt.subplots(3) - axs[0].plot(Tpwmu, Vas, "red", label="Sine wave") - axs[0].plot(Tpwmu, triangle, "green", label="Carrier wave") - axs[1].plot(Tpwmu, v_pwm[0], "blue", label="Square wave") - axs[2].plot(Tpwmu, ws, "blue", label="Square wave") - - axs[0].set_title("SPWM generation") - axs[0].set_ylabel("Frequency [Hz]") - axs[0].legend() - axs[1].set_xlabel("Time [s]") - axs[1].set_ylabel("Frequency [Hz]") - axs[1].legend() - - fig.show() - plt.show() - - return v_pwm, Vas, M_I + return v_pwm, Vas, M_I, triangle def comp_carrier(time, fswi, type_carrier): @@ -291,7 +301,7 @@ def comp_carrier(time, fswi, type_carrier): fswi : array Switching frequency type_carrier : int - 1: forward toothsaw carrier + 1: forward toothsaw carrier 2: backwards toothsaw carrier 3: toothsaw carrier else: symetrical toothsaw carrier @@ -334,7 +344,6 @@ def comp_carrier(time, fswi, type_carrier): / (-t1 + 0.5 * T) + np.where(time >= t2, 1, 0) * (time - T) / (T - t2) ) - else: wswiT = 2 * np.pi * time * fswi Y = signal.sawtooth(wswiT, 0.5) diff --git a/pyleecan/Methods/Import/ImportGenPWM/get_data.py b/pyleecan/Methods/Import/ImportGenPWM/get_data.py index 8f3eb71b9..0d359469c 100644 --- a/pyleecan/Methods/Import/ImportGenPWM/get_data.py +++ b/pyleecan/Methods/Import/ImportGenPWM/get_data.py @@ -26,7 +26,7 @@ def get_data(self): """ # Tpwmu=np.arange(fs*duration)/fs, Tpwmu = np.linspace(0, self.duration, self.fs * self.duration, endpoint=True) - v_pwm, Vas, M_I = comp_volt_PWM_NUM( + v_pwm, Vas, MI, triangle= comp_volt_PWM_NUM( Tpwmu=Tpwmu, freq0=self.f, freq0_max=self.fmax, @@ -49,4 +49,4 @@ def get_data(self): PWM2 = np.where(v_pwm[1] < ref, -1, 1) # .astype(np.float32) PWM3 = np.where(v_pwm[2] < ref, -1, 1) # .astype(np.float32) Triphase = np.column_stack([PWM1, PWM2, PWM3]) - return Triphase + return Triphase, Vas, MI,triangle From c981ecf2c0b391157b9fd421f490f0b911676d73 Mon Sep 17 00:00:00 2001 From: Sijie-NI Date: Tue, 27 Jul 2021 14:57:44 +0200 Subject: [PATCH 010/167] [WP] Fix bugs of random PWM --- Tests/Methods/Import/test_ImportGenPWM.py | 4 ++-- pyleecan/Functions/Electrical/comp_PWM.py | 12 ++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Tests/Methods/Import/test_ImportGenPWM.py b/Tests/Methods/Import/test_ImportGenPWM.py index fa68e27ad..f9ea4b6c5 100644 --- a/Tests/Methods/Import/test_ImportGenPWM.py +++ b/Tests/Methods/Import/test_ImportGenPWM.py @@ -27,8 +27,8 @@ def testSPWM(): fmax=5, fmode=ii, fswimode=jj, - fswi=10, - fswi_max=30, + fswi=5, + fswi_max=15, typePWM=8, Vdc1=2, U0=0.70, diff --git a/pyleecan/Functions/Electrical/comp_PWM.py b/pyleecan/Functions/Electrical/comp_PWM.py index 1cb7155ce..93c3817f0 100644 --- a/pyleecan/Functions/Electrical/comp_PWM.py +++ b/pyleecan/Functions/Electrical/comp_PWM.py @@ -65,7 +65,7 @@ def comp_volt_PWM_NUM( 0: Fixed fswi 1: Variable fswi 2: Random fswi - 3: Symetrical random fswi + 3: Symmetrical random fswi """ Npsim = len(Tpwmu) @@ -97,16 +97,12 @@ def comp_volt_PWM_NUM( triangle = Vdc1 / 2 * signal.sawtooth(wswiT, 0.5) else: print("ERROR:only SPWM supports the varaible switching frequency") - elif fswimode == 2 or fswimode== 3: # Random fswi & Symetrical random fswi + elif fswimode == 2 or fswimode== 3: # Random fswi & Symmetrical random fswi t1=round(Tpwmu[-1]*5000000) - if fswimode=='Symetrical random fswi': + if fswimode== 3: num_slice=round((fswi_max+fswi)/2*Tpwmu[-1]) delta_fswi=np.random.randint(fswi, high=fswi_max+1, size=num_slice*2,dtype=int) delta_fswi[1::2]=delta_fswi[0::2]*-1 - # elif fswimode=='Random carrier amplitude': - # num_slice=round(fswiMin*duration) - # delta_fswi=np.random.randint(fswiMin, high=fswiMin+1, size=num_slice*2,dtype=int) - # delta_fswi[1::2]=delta_fswi[0::2]*-1 else: @@ -304,7 +300,7 @@ def comp_carrier(time, fswi, type_carrier): 1: forward toothsaw carrier 2: backwards toothsaw carrier 3: toothsaw carrier - else: symetrical toothsaw carrier + else: Symmetrical toothsaw carrier Returns ------- From 8fac2cf72829b22791753467dbb228d623eacf16 Mon Sep 17 00:00:00 2001 From: Sijie-NI Date: Wed, 28 Jul 2021 16:41:38 +0200 Subject: [PATCH 011/167] [WP] Random amplitude modulation (one type of random PWM) --- Tests/Methods/Import/test_ImportGenPWM.py | 60 ++++++++-- pyleecan/Classes/ImportGenPWM.py | 30 +++++ pyleecan/Classes/import_all.py | 1 + pyleecan/Functions/Electrical/comp_PWM.py | 104 +++++++++++++----- .../ClassesRef/Import/ImportGenPWM.csv | 1 + .../Methods/Import/ImportGenPWM/get_data.py | 5 +- 6 files changed, 159 insertions(+), 42 deletions(-) diff --git a/Tests/Methods/Import/test_ImportGenPWM.py b/Tests/Methods/Import/test_ImportGenPWM.py index f9ea4b6c5..a6c4ff0ef 100644 --- a/Tests/Methods/Import/test_ImportGenPWM.py +++ b/Tests/Methods/Import/test_ImportGenPWM.py @@ -18,7 +18,7 @@ def testSPWM(): """Check """ # fs, duration, f,fmax,fmode, fswimode,fswi, fswi_max,typePWM, Vdc1, U0, type_carrier for ii in range(2): - for jj in range(4): + for jj in range(5): for hh in range(4): test_obj = ImportGenPWM( fs=96000, @@ -33,15 +33,13 @@ def testSPWM(): Vdc1=2, U0=0.70, type_carrier=hh, + var_amp=20, ) # Generate the signal time = linspace(start=0, stop=2, num=2 * 96000, endpoint=True) Triphase = test_obj.get_data()[0] - Vas=test_obj.get_data()[1] - Triangle=test_obj.get_data()[3] - - - + Vas = test_obj.get_data()[1] + Triangle = test_obj.get_data()[3] # Plot/save the result plt.close("all") @@ -63,6 +61,45 @@ def testSPWM(): ) +# def testSPWM(): +# """Check """ +# # fs, duration, f,fmax,fmode, fswimode,fswi, fswi_max,typePWM, Vdc1, U0, type_carrier + +# test_obj = ImportGenPWM( +# fs=96000, +# duration=2, +# f=1, +# fmax=5, +# fmode=0, +# fswimode=4, +# fswi=5, +# fswi_max=15, +# typePWM=8, +# Vdc1=2, +# U0=0.70, +# type_carrier=6, +# var_amp=20, +# ) +# # Generate the signal +# time = linspace(start=0, stop=2, num=2 * 96000, endpoint=True) +# Triphase = test_obj.get_data()[0] +# Vas = test_obj.get_data()[1] +# Triangle = test_obj.get_data()[3] + +# # Plot/save the result +# plt.close("all") +# plt.plot(time, Triphase[:, 1]) +# plt.plot(time, Vas) +# plt.plot(time, Triangle) +# fig = plt.gcf() +# fig.savefig( +# join( +# save_path, +# "test_ImportGenPWM_" + str(0) + "_" + str(4) + "_" + str(6) + "_SPWM.png", +# ) +# ) + + def testDPWM(): """Check """ for ii in range(9): @@ -84,9 +121,8 @@ def testDPWM(): # Generate the signal time = linspace(start=0, stop=2, num=2 * 96000, endpoint=True) Triphase = test_obj.get_data()[0] - Vas=test_obj.get_data()[1] - Triangle=test_obj.get_data()[3] - + Vas = test_obj.get_data()[1] + Triangle = test_obj.get_data()[3] # Plot/save the result plt.close("all") @@ -98,5 +134,7 @@ def testDPWM(): if __name__ == "__main__": - testDPWM() - testSPWM() \ No newline at end of file + # testDPWM() + # testSPWM() + + testSPWM() diff --git a/pyleecan/Classes/ImportGenPWM.py b/pyleecan/Classes/ImportGenPWM.py index 428ed93f4..599206968 100644 --- a/pyleecan/Classes/ImportGenPWM.py +++ b/pyleecan/Classes/ImportGenPWM.py @@ -60,6 +60,7 @@ def __init__( Vdc1=2, U0=1, type_carrier=0, + var_amp=20, is_transpose=False, init_dict=None, init_str=None, @@ -103,6 +104,8 @@ def __init__( U0 = init_dict["U0"] if "type_carrier" in list(init_dict.keys()): type_carrier = init_dict["type_carrier"] + if "var_amp" in list(init_dict.keys()): + var_amp = init_dict["var_amp"] if "is_transpose" in list(init_dict.keys()): is_transpose = init_dict["is_transpose"] # Set the properties (value check and convertion are done in setter) @@ -118,6 +121,7 @@ def __init__( self.Vdc1 = Vdc1 self.U0 = U0 self.type_carrier = type_carrier + self.var_amp = var_amp # Call ImportMatrix init super(ImportGenPWM, self).__init__(is_transpose=is_transpose) # The class is frozen (in ImportMatrix init), for now it's impossible to @@ -141,6 +145,7 @@ def __str__(self): ImportGenPWM_str += "Vdc1 = " + str(self.Vdc1) + linesep ImportGenPWM_str += "U0 = " + str(self.U0) + linesep ImportGenPWM_str += "type_carrier = " + str(self.type_carrier) + linesep + ImportGenPWM_str += "var_amp = " + str(self.var_amp) + linesep return ImportGenPWM_str def __eq__(self, other): @@ -176,6 +181,8 @@ def __eq__(self, other): return False if other.type_carrier != self.type_carrier: return False + if other.var_amp != self.var_amp: + return False return True def compare(self, other, name="self"): @@ -211,6 +218,8 @@ def compare(self, other, name="self"): diff_list.append(name + ".U0") if other._type_carrier != self._type_carrier: diff_list.append(name + ".type_carrier") + if other._var_amp != self._var_amp: + diff_list.append(name + ".var_amp") return diff_list def __sizeof__(self): @@ -232,6 +241,7 @@ def __sizeof__(self): S += getsizeof(self.Vdc1) S += getsizeof(self.U0) S += getsizeof(self.type_carrier) + S += getsizeof(self.var_amp) return S def as_dict(self, **kwargs): @@ -255,6 +265,7 @@ def as_dict(self, **kwargs): ImportGenPWM_dict["Vdc1"] = self.Vdc1 ImportGenPWM_dict["U0"] = self.U0 ImportGenPWM_dict["type_carrier"] = self.type_carrier + ImportGenPWM_dict["var_amp"] = self.var_amp # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name ImportGenPWM_dict["__class__"] = "ImportGenPWM" @@ -275,6 +286,7 @@ def _set_None(self): self.Vdc1 = None self.U0 = None self.type_carrier = None + self.var_amp = None # Set to None the properties inherited from ImportMatrix super(ImportGenPWM, self)._set_None() @@ -498,3 +510,21 @@ def _set_type_carrier(self, value): :Type: int """, ) + + def _get_var_amp(self): + """getter of var_amp""" + return self._var_amp + + def _set_var_amp(self, value): + """setter of var_amp""" + check_var("var_amp", value, "int") + self._var_amp = value + + var_amp = property( + fget=_get_var_amp, + fset=_set_var_amp, + doc=u"""percentage of variation of carrier amplitude + + :Type: int + """, + ) diff --git a/pyleecan/Classes/import_all.py b/pyleecan/Classes/import_all.py index 8925ae226..085b544a0 100644 --- a/pyleecan/Classes/import_all.py +++ b/pyleecan/Classes/import_all.py @@ -23,6 +23,7 @@ from ..Classes.Drive import Drive from ..Classes.DriveWave import DriveWave from ..Classes.EEC import EEC +from ..Classes.EEC_LSRPM import EEC_LSRPM from ..Classes.EEC_PMSM import EEC_PMSM from ..Classes.EEC_SCIM import EEC_SCIM from ..Classes.Electrical import Electrical diff --git a/pyleecan/Functions/Electrical/comp_PWM.py b/pyleecan/Functions/Electrical/comp_PWM.py index 93c3817f0..ebb3e9d3a 100644 --- a/pyleecan/Functions/Electrical/comp_PWM.py +++ b/pyleecan/Functions/Electrical/comp_PWM.py @@ -20,6 +20,8 @@ def comp_volt_PWM_NUM( fswi_max=0, freq0_max=0, type_carrier=0, + var_amp=0, + ): """ Generalized DPWM using numerical method according to @@ -66,10 +68,14 @@ def comp_volt_PWM_NUM( 1: Variable fswi 2: Random fswi 3: Symmetrical random fswi + 4: Random amplitude carrier wave + + var_amp: int + precentage of variation of the carrier amplitude """ Npsim = len(Tpwmu) - triangle=np.ones(len(Tpwmu)) + triangle = np.ones(len(Tpwmu)) if fmode == 0: # Fixed speed: ws = 2 * np.pi * freq0 elif fmode == 1: # Variable speed: @@ -85,6 +91,7 @@ def comp_volt_PWM_NUM( if fswimode == 0: # Fixed fswi: if type_DPWM == 8: + triangle = Vdc1 / 2 * comp_carrier(Tpwmu, fswi, type_carrier) else: Th = 1 / fswi @@ -97,37 +104,77 @@ def comp_volt_PWM_NUM( triangle = Vdc1 / 2 * signal.sawtooth(wswiT, 0.5) else: print("ERROR:only SPWM supports the varaible switching frequency") - elif fswimode == 2 or fswimode== 3: # Random fswi & Symmetrical random fswi - t1=round(Tpwmu[-1]*5000000) - if fswimode== 3: - num_slice=round((fswi_max+fswi)/2*Tpwmu[-1]) - delta_fswi=np.random.randint(fswi, high=fswi_max+1, size=num_slice*2,dtype=int) - delta_fswi[1::2]=delta_fswi[0::2]*-1 + elif fswimode == 2 or fswimode == 3: # Random fswi & Symmetrical random fswi + t1 = round(Tpwmu[-1] * 5000000) + if fswimode == 3: + num_slice = round((fswi_max + fswi) / 2 * Tpwmu[-1]) + delta_fswi = np.random.randint( + fswi, high=fswi_max + 1, size=num_slice * 2, dtype=int + ) + delta_fswi[1::2] = delta_fswi[0::2] * -1 else: - num_slice=round((fswi_max+fswi)/2*Tpwmu[-1]) - delta_fswi=np.random.randint(fswi, high=fswi_max+1, size=num_slice*2,dtype=int) - delta_fswi[1::2]=delta_fswi[1::2]*-1 - - fswi_base=np.array(np.ones(t1)) - S_delta=1 - delta_t=S_delta/abs(delta_fswi) - time=sum(delta_t) - delta_point=delta_t[:-1]/time*t1 - delta_point=np.array(delta_point) - delta_point=np.append(delta_point,t1-sum(delta_point)) - fswi=np.concatenate([fswi_base[0: round(delta_point[ii])]*delta_fswi[ii] for ii in range(len(delta_fswi))]) - if len(fswi) ((n + 1) * Th - T3)] = -Vdc1 / 2 - return v_pwm, Vas, M_I, triangle @@ -344,4 +390,4 @@ def comp_carrier(time, fswi, type_carrier): wswiT = 2 * np.pi * time * fswi Y = signal.sawtooth(wswiT, 0.5) - return Y \ No newline at end of file + return Y diff --git a/pyleecan/Generator/ClassesRef/Import/ImportGenPWM.csv b/pyleecan/Generator/ClassesRef/Import/ImportGenPWM.csv index 679d3da4b..bf06f3bb5 100644 --- a/pyleecan/Generator/ClassesRef/Import/ImportGenPWM.csv +++ b/pyleecan/Generator/ClassesRef/Import/ImportGenPWM.csv @@ -11,3 +11,4 @@ typePWM,,0: GDPWM 1: DPWMMIN 2: DPWMMAX 3: DPWM0 4: DPWM1 5: DPWM2 6: DPWM3 7: S Vdc1,V,DC BUS voltage,0,float,2,,,,,,,,, U0,V,reference voltage,0,float,1,,,,,,,,, type_carrier,,1: forward toothsaw carrier 2: backwards toothsaw carrier 3: toothsaw carrier else: symetrical toothsaw carrier,0,int,0,,,,,,,,, +var_amp,%,percentage of variation of carrier amplitude,0,int,20,,,,,,,,, diff --git a/pyleecan/Methods/Import/ImportGenPWM/get_data.py b/pyleecan/Methods/Import/ImportGenPWM/get_data.py index 0d359469c..0abaa6b12 100644 --- a/pyleecan/Methods/Import/ImportGenPWM/get_data.py +++ b/pyleecan/Methods/Import/ImportGenPWM/get_data.py @@ -26,7 +26,7 @@ def get_data(self): """ # Tpwmu=np.arange(fs*duration)/fs, Tpwmu = np.linspace(0, self.duration, self.fs * self.duration, endpoint=True) - v_pwm, Vas, MI, triangle= comp_volt_PWM_NUM( + v_pwm, Vas, MI, triangle = comp_volt_PWM_NUM( Tpwmu=Tpwmu, freq0=self.f, freq0_max=self.fmax, @@ -42,6 +42,7 @@ def get_data(self): type_DPWM=self.typePWM, PF_angle=0, is_plot=False, + var_amp=self.var_amp, ) ref = np.zeros(np.size(v_pwm[0])).astype(np.float32) @@ -49,4 +50,4 @@ def get_data(self): PWM2 = np.where(v_pwm[1] < ref, -1, 1) # .astype(np.float32) PWM3 = np.where(v_pwm[2] < ref, -1, 1) # .astype(np.float32) Triphase = np.column_stack([PWM1, PWM2, PWM3]) - return Triphase, Vas, MI,triangle + return Triphase, Vas, MI, triangle From 525b6ea38bf05510cdab4e8ea2c6750dceb1ecbb Mon Sep 17 00:00:00 2001 From: Pierre Bonneel Date: Mon, 2 Aug 2021 17:16:20 +0200 Subject: [PATCH 012/167] [CC] Test structure --- .../Validation/Electrical/test_skin_effect.py | 111 +++++++++--------- 1 file changed, 57 insertions(+), 54 deletions(-) diff --git a/Tests/Validation/Electrical/test_skin_effect.py b/Tests/Validation/Electrical/test_skin_effect.py index e25951380..880cb4181 100644 --- a/Tests/Validation/Electrical/test_skin_effect.py +++ b/Tests/Validation/Electrical/test_skin_effect.py @@ -34,57 +34,60 @@ # @pytest.mark.IPMSM # @pytest.mark.periodicity # @pytest.mark.SingleOP -# def test_skin_effect(): -"""Validation of the PMSM Electrical Equivalent Circuit with the Prius machine -Compute Torque from EEC results and compare with Yang et al, 2013 -""" - -Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) -simu = Simu1(name="test_skin_effect", machine=Toyota_Prius) - -# Definition of the input -simu.input = InputElec(N0=2000, Nt_tot=10, Na_tot=2048) -simu.input.set_Id_Iq(I0=250 / sqrt(2), Phi0=60 * pi / 180) - -# # Define second simu for FEMM comparison -# simu2 = simu.copy() -# simu2.name = "test_EEC_PMSM_FEMM" - -# Definition of the electrical simulation (FEMM) -simu.elec = Electrical(type_skin_effect=1) -simu.elec.eec = EEC_PMSM( - indmag=IndMagFEMM(is_periodicity_a=True, Nt_tot=10), - fluxlink=FluxLinkFEMM(is_periodicity_a=True, Nt_tot=10), -) - -simu.mag = None -simu.force = None -simu.struct = None - -out = Output(simu=simu) -#%% -simu.run() - -# # Definition of the magnetic simulation (FEMM) -# simu2.mag = MagFEMM( -# type_BH_stator=0, -# type_BH_rotor=0, -# is_periodicity_a=True, -# nb_worker=cpu_count(), -# ) - -# out2 = Output(simu=simu2) -# simu2.run() - -# Plot 3-phase current function of time -# out.elec.get_Is().plot_2D_Data( -# "time", -# "phase", -# save_path=join(save_path, "EEC_FEMM_IPMSM_currents.png"), -# is_show_fig=False, -# **dict_2D -# ) - -# from Yang et al, 2013 -# assert_almost_equal(out.elec.Tem_av_ref, 81.81, decimal=1) -# assert_almost_equal(out2.mag.Tem_av, 81.70, decimal=1) +@pytest.skip(reason="Not finished yet") +def test_skin_effect(): + """Validation of the PMSM Electrical Equivalent Circuit with the Prius machine + Compute Torque from EEC results and compare with Yang et al, 2013 + """ + + Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) + simu = Simu1(name="test_skin_effect", machine=Toyota_Prius) + + # Definition of the input + simu.input = InputElec(N0=2000, Nt_tot=10, Na_tot=2048) + simu.input.set_Id_Iq(I0=250 / sqrt(2), Phi0=60 * pi / 180) + + # # Define second simu for FEMM comparison + # simu2 = simu.copy() + # simu2.name = "test_EEC_PMSM_FEMM" + + # Definition of the electrical simulation (FEMM) + simu.elec = Electrical(type_skin_effect=1) + simu.elec.eec = EEC_PMSM( + indmag=IndMagFEMM(is_periodicity_a=True, Nt_tot=10), + fluxlink=FluxLinkFEMM(is_periodicity_a=True, Nt_tot=10), + ) + + simu.mag = None + simu.force = None + simu.struct = None + + out = Output(simu=simu) + #%% + simu.run() + + # # Definition of the magnetic simulation (FEMM) + # simu2.mag = MagFEMM( + # type_BH_stator=0, + # type_BH_rotor=0, + # is_periodicity_a=True, + # nb_worker=cpu_count(), + # ) + + # out2 = Output(simu=simu2) + # simu2.run() + + # Plot 3-phase current function of time + # out.elec.get_Is().plot_2D_Data( + # "time", + # "phase", + # save_path=join(save_path, "EEC_FEMM_IPMSM_currents.png"), + # is_show_fig=False, + # **dict_2D + # ) + + # from Yang et al, 2013 + # assert_almost_equal(out.elec.Tem_av_ref, 81.81, decimal=1) + # assert_almost_equal(out2.mag.Tem_av, 81.70, decimal=1) +if __name__ == "__main__": + test_skin_effect() From e9918ff6323fe78855086636873aa3153a3aa278 Mon Sep 17 00:00:00 2001 From: Sijie-NI Date: Wed, 4 Aug 2021 18:02:57 +0200 Subject: [PATCH 013/167] [NF] comp_BEMF_harmonics and comp_skin_effect_round_wire --- .../Conductor/comp_skin_effect_round_wire.py | 64 ++++++++ .../EEC_PMSM/comp_BEMF_harmonics.py | 140 ++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 pyleecan/Methods/Machine/Conductor/comp_skin_effect_round_wire.py create mode 100644 pyleecan/Methods/Simulation/EEC_PMSM/comp_BEMF_harmonics.py diff --git a/pyleecan/Methods/Machine/Conductor/comp_skin_effect_round_wire.py b/pyleecan/Methods/Machine/Conductor/comp_skin_effect_round_wire.py new file mode 100644 index 000000000..355d93d19 --- /dev/null +++ b/pyleecan/Methods/Machine/Conductor/comp_skin_effect_round_wire.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +import numpy as np + +def comp_skin_effect_round_wire(self,f, rho=None): + + """ Compute skin effect factor for round wires + + + Parameters + ---------- + Inputs: + f: float + Frequency (Hz) + rho: float + Resistivity of wire material (Ohm meter) + self : Conductor + an Conductor object + + Outputs: + K_R: float + Skin effect resistance factor of round wires + + K_I: float + Skin effect inductance factor of round wires + """ + + + + if rho is None: + rho = self.cond_mat.elec.rho + # Wire diameter + d_w=self.Wwire + # Vaccum or air permeability + mu0 = 4 * np.pi * 1e-7 + # Wire material magnetic permeability (mu*mu0) + mu=self.cond_mat.mag.mur_lin*mu0 + # Thickness of skin + delta=np.sqrt(rho/(np.pi*f*mu)) + + + """ + cf. SKIN EFFECT, PROXIMITY EFFECT AND THE RESISTANCE OF CIRCULAR AND RECTANGULAR + CONDUCTORS, page 6 + + """ + # # factor of skin effect (R_AC=R_DC*K) + # K_R = 0.25*(d_w)**2/(d_w*delta-delta**2) + + """ + cf. A simple derivation for the skin effect in a round wire, page 8-9 eqa 30-31 + + """ + # Radius of wire + r_w=d_w/2 + + # Resistance factor of skin effect (R_AC=R_DC*K) + K_R=(1+1/48(r_w/delta)**4) + # Inductance factor of skin effect (I_AC=I_DC*K) + K_I=(1-1/96(r_w/delta)**4) + + return K_R, K_I + + + diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/comp_BEMF_harmonics.py b/pyleecan/Methods/Simulation/EEC_PMSM/comp_BEMF_harmonics.py new file mode 100644 index 000000000..450c7ae68 --- /dev/null +++ b/pyleecan/Methods/Simulation/EEC_PMSM/comp_BEMF_harmonics.py @@ -0,0 +1,140 @@ + +# -*- coding: utf-8 -*- +from numpy import sqrt, cos , sin, pi +import numpy as np +from SciDataTool import DataLinspace, DataTime +import matplotlib.pyplot as plt + + + +def comp_BEMF_harmonics(Phi_A,Phi_B,Phi_C, delta, time): + """ + Compute the back electromotive force harmonics from magnet fluxes + + Parameters + ---------- + Phi_A: Magnetic flux of phase A + Phi_B: Magnetic flux of phase B + Phi_C: Magnetic flux of phase C + delta: Rotor angular position + time: time vector + + + """ + + #Park transformation (keep the amplitude factor=2/3) + Phi_d=2/3*(Phi_A*cos(delta)+Phi_B*cos(delta-2*pi/3)+Phi_C*cos(delta+2*pi/3)) + Phi_q=2/3*(-Phi_A*sin(delta)-Phi_B*sin(delta-2*pi/3)-Phi_C*sin(delta+2*pi/3)) + Phi_h=2/3*1/2*(Phi_A+Phi_B+Phi_C) + + # Create time vector in form of DataLinspace + time_axis = DataLinspace( + name="time", + unit="s", + initial=0, + final=time[-1], + number=len(time), + include_endpoint=True, + ) + + # Load Phi into DataTime + Phi_A_data = DataTime( + name="Phi_A", + symbol="Phi_A", + unit="Wb", + normalizations=None, + axes=[time_axis], + values=Phi_A, + ) + + Phi_B_data = DataTime( + name="Phi_B", + symbol="Phi_B", + unit="Wb", + normalizations=None, + axes=[time_axis], + values=Phi_B, + ) + Phi_C_data = DataTime( + name="Phi_C", + symbol="Phi_C", + unit="Wb", + normalizations=None, + axes=[time_axis], + values=Phi_C, + ) + + + Phi_d_data = DataTime( + name="Phi_d", + symbol="Phi_d", + unit="Wb", + normalizations=None, + axes=[time_axis], + values=Phi_d, + ) + + Phi_q_data = DataTime( + name="Phi_q", + symbol="Phi_q", + unit="Wb", + normalizations=None, + axes=[time_axis], + values=Phi_q, + ) + Phi_h_data = DataTime( + name="Phi_h", + symbol="Phi_h", + unit="Wb", + normalizations=None, + axes=[time_axis], + values=Phi_h, + ) + + # Phi_q_data.plot_2D_Data("time") + # Phi_q_data.plot_2D_Data("freqs", type_plot='curve') + # plt.show() + + + # Calculate FFT for Phi on the dq0 frame + d = Phi_d_data.get_along("freqs") + freqs_d = d['freqs'] + complx_d = d['Phi_d'] + + q = Phi_q_data.get_along("freqs") + freqs_q = q['freqs'] + complx_q = q['Phi_q'] + + h = Phi_h_data.get_along("freqs") + freqs_h = h['freqs'] + complx_h = h['Phi_h'] + + # Calculate back-emf (E) on dq0 frame + E_d=-2*pi*freqs_q*complx_q+2*pi*freqs_d*complx_d*1j + E_q=2*pi*freqs_d*complx_d+2*pi*freqs_q*complx_q*1j + E_h=2*pi*freqs_h*complx_h*1j + + + + return E_d, E_q, E_h, freqs_d, freqs_q, freqs_h + + +if __name__ == "__main__": + + #Example: + N = 10000 + # sample spacing + T = 1.0 / 20000 + time = np.linspace(0.0, N*T, N, endpoint=False) + Phi_A = 0.5*np.cos(50.0 * 2.0*np.pi*time)+0.001*np.cos(100 * 2.0*np.pi*time)+0.0009*np.cos(150 * 2.0*np.pi*time) + Phi_B = 0.5*np.cos(50.0 * 2.0*np.pi*time-2*pi/3)+0.001*np.cos(100 * 2.0*np.pi*time-2*pi/3)+0.0009*np.cos(150 * 2.0*np.pi*time-2*pi/3) + Phi_C = 0.5*np.cos(50.0 * 2.0*np.pi*time+2*pi/3)+0.001*np.cos(100 * 2.0*np.pi*time+2*pi/3)+0.0009*np.cos(150 * 2.0*np.pi*time+2*pi/3) + delta=50.0 * 2.0*np.pi*time + + E_d, E_q, E_h, freqs_d, freqs_q, freqs_h=comp_BEMF_harmonics(Phi_A=Phi_A, Phi_B=Phi_B, Phi_C=Phi_C,delta=delta, time=time) + + fig, axs=plt.subplots(3) + axs[0].plot(freqs_d,abs(E_d)) + axs[1].plot(freqs_q,abs(E_q)) + axs[2].plot(freqs_h,abs(E_h)) + plt.show() \ No newline at end of file From 934685bcb58a55b38ee567b0835e24b5ed329a14 Mon Sep 17 00:00:00 2001 From: Jean Le Besnerais Date: Thu, 5 Aug 2021 11:04:58 +0200 Subject: [PATCH 014/167] [WP] building of ELUT classes --- pyleecan/Classes/ELUT.py | 309 +++++++++++ pyleecan/Classes/ELUT_PMSM.py | 519 ++++++++++++++++++ pyleecan/Classes/ELUT_SCIM.py | 373 +++++++++++++ .../Generator/ClassesRef/Simulation/ELUT.csv | 6 + .../ClassesRef/Simulation/ELUT_PMSM.csv | 10 + .../ClassesRef/Simulation/ELUT_SCIM.csv | 5 + pyleecan/Methods/Output/OutElec/store.py | 128 +++++ .../Simulation/EEC_SCIM/solve_EEC_freq.py | 133 +++++ pyleecan/Methods/Simulation/ELUT/__init__.py | 0 .../Methods/Simulation/ELUT/get_parameters.py | 50 ++ .../Methods/Simulation/ELUT_PMSM/__init__.py | 0 .../Methods/Simulation/ELUT_PMSM/get_bemf.py | 23 + .../Simulation/ELUT_PMSM/get_parameters.py | 34 ++ .../Methods/Simulation/ELUT_SCIM/__init__.py | 0 14 files changed, 1590 insertions(+) create mode 100644 pyleecan/Classes/ELUT.py create mode 100644 pyleecan/Classes/ELUT_PMSM.py create mode 100644 pyleecan/Classes/ELUT_SCIM.py create mode 100644 pyleecan/Generator/ClassesRef/Simulation/ELUT.csv create mode 100644 pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv create mode 100644 pyleecan/Generator/ClassesRef/Simulation/ELUT_SCIM.csv create mode 100644 pyleecan/Methods/Output/OutElec/store.py create mode 100644 pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC_freq.py create mode 100644 pyleecan/Methods/Simulation/ELUT/__init__.py create mode 100644 pyleecan/Methods/Simulation/ELUT/get_parameters.py create mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/__init__.py create mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/get_bemf.py create mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/get_parameters.py create mode 100644 pyleecan/Methods/Simulation/ELUT_SCIM/__init__.py diff --git a/pyleecan/Classes/ELUT.py b/pyleecan/Classes/ELUT.py new file mode 100644 index 000000000..44d6e8a6c --- /dev/null +++ b/pyleecan/Classes/ELUT.py @@ -0,0 +1,309 @@ +# -*- coding: utf-8 -*- +# File generated according to Generator/ClassesRef/Simulation/ELUT.csv +# WARNING! All changes made in this file will be lost! +"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Simulation/ELUT +""" + +from os import linesep +from sys import getsizeof +from logging import getLogger +from ._check import set_array, check_var, raise_ +from ..Functions.get_logger import get_logger +from ..Functions.save import save +from ..Functions.copy import copy +from ..Functions.load import load_init_dict +from ..Functions.Load.import_class import import_class +from ._frozen import FrozenClass + +# Import all class method +# Try/catch to remove unnecessary dependencies in unused method +try: + from ..Methods.Simulation.ELUT.get_parameters import get_parameters +except ImportError as error: + get_parameters = error + + +from numpy import array, array_equal +from ._check import InitUnKnowClassError + + +class ELUT(FrozenClass): + """Abstract class for Electrical Look Up Table (ELUT)""" + + VERSION = 1 + + # cf Methods.Simulation.ELUT.get_parameters + if isinstance(get_parameters, ImportError): + get_parameters = property( + fget=lambda x: raise_( + ImportError( + "Can't use ELUT method get_parameters: " + str(get_parameters) + ) + ) + ) + else: + get_parameters = get_parameters + # save and copy methods are available in all object + save = save + copy = copy + # get_logger method is available in all object + get_logger = get_logger + + def __init__( + self, + Rs=None, + Ls=None, + Tsta_ref=20, + K_RSE_sta=None, + K_ISE_sta=None, + init_dict=None, + init_str=None, + ): + """Constructor of the class. Can be use in three ways : + - __init__ (arg1 = 1, arg3 = 5) every parameters have name and default values + for pyleecan type, -1 will call the default constructor + - __init__ (init_dict = d) d must be a dictionary with property names as keys + - __init__ (init_str = s) s must be a string + s is the file path to load + + ndarray or list can be given for Vector and Matrix + object or dict can be given for pyleecan Object""" + + if init_str is not None: # Load from a file + init_dict = load_init_dict(init_str)[1] + if init_dict is not None: # Initialisation by dict + assert type(init_dict) is dict + # Overwrite default value with init_dict content + if "Rs" in list(init_dict.keys()): + Rs = init_dict["Rs"] + if "Ls" in list(init_dict.keys()): + Ls = init_dict["Ls"] + if "Tsta_ref" in list(init_dict.keys()): + Tsta_ref = init_dict["Tsta_ref"] + if "K_RSE_sta" in list(init_dict.keys()): + K_RSE_sta = init_dict["K_RSE_sta"] + if "K_ISE_sta" in list(init_dict.keys()): + K_ISE_sta = init_dict["K_ISE_sta"] + # Set the properties (value check and convertion are done in setter) + self.parent = None + self.Rs = Rs + self.Ls = Ls + self.Tsta_ref = Tsta_ref + self.K_RSE_sta = K_RSE_sta + self.K_ISE_sta = K_ISE_sta + + # The class is frozen, for now it's impossible to add new properties + self._freeze() + + def __str__(self): + """Convert this object in a readeable string (for print)""" + + ELUT_str = "" + if self.parent is None: + ELUT_str += "parent = None " + linesep + else: + ELUT_str += "parent = " + str(type(self.parent)) + " object" + linesep + ELUT_str += "Rs = " + str(self.Rs) + linesep + ELUT_str += "Ls = " + str(self.Ls) + linesep + ELUT_str += "Tsta_ref = " + str(self.Tsta_ref) + linesep + ELUT_str += ( + "K_RSE_sta = " + + linesep + + str(self.K_RSE_sta).replace(linesep, linesep + "\t") + + linesep + + linesep + ) + ELUT_str += ( + "K_ISE_sta = " + + linesep + + str(self.K_ISE_sta).replace(linesep, linesep + "\t") + + linesep + + linesep + ) + return ELUT_str + + def __eq__(self, other): + """Compare two objects (skip parent)""" + + if type(other) != type(self): + return False + if other.Rs != self.Rs: + return False + if other.Ls != self.Ls: + return False + if other.Tsta_ref != self.Tsta_ref: + return False + if not array_equal(other.K_RSE_sta, self.K_RSE_sta): + return False + if not array_equal(other.K_ISE_sta, self.K_ISE_sta): + return False + return True + + def compare(self, other, name="self", ignore_list=None): + """Compare two objects and return list of differences""" + + if ignore_list is None: + ignore_list = list() + if type(other) != type(self): + return ["type(" + name + ")"] + diff_list = list() + if other._Rs != self._Rs: + diff_list.append(name + ".Rs") + if other._Ls != self._Ls: + diff_list.append(name + ".Ls") + if other._Tsta_ref != self._Tsta_ref: + diff_list.append(name + ".Tsta_ref") + if not array_equal(other.K_RSE_sta, self.K_RSE_sta): + diff_list.append(name + ".K_RSE_sta") + if not array_equal(other.K_ISE_sta, self.K_ISE_sta): + diff_list.append(name + ".K_ISE_sta") + # Filter ignore differences + diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) + return diff_list + + def __sizeof__(self): + """Return the size in memory of the object (including all subobject)""" + + S = 0 # Full size of the object + S += getsizeof(self.Rs) + S += getsizeof(self.Ls) + S += getsizeof(self.Tsta_ref) + S += getsizeof(self.K_RSE_sta) + S += getsizeof(self.K_ISE_sta) + return S + + def as_dict(self, **kwargs): + """ + Convert this object in a json serializable dict (can be use in __init__). + Optional keyword input parameter is for internal use only + and may prevent json serializability. + """ + + ELUT_dict = dict() + ELUT_dict["Rs"] = self.Rs + ELUT_dict["Ls"] = self.Ls + ELUT_dict["Tsta_ref"] = self.Tsta_ref + if self.K_RSE_sta is None: + ELUT_dict["K_RSE_sta"] = None + else: + ELUT_dict["K_RSE_sta"] = self.K_RSE_sta.tolist() + if self.K_ISE_sta is None: + ELUT_dict["K_ISE_sta"] = None + else: + ELUT_dict["K_ISE_sta"] = self.K_ISE_sta.tolist() + # The class name is added to the dict for deserialisation purpose + ELUT_dict["__class__"] = "ELUT" + return ELUT_dict + + def _set_None(self): + """Set all the properties to None (except pyleecan object)""" + + self.Rs = None + self.Ls = None + self.Tsta_ref = None + self.K_RSE_sta = None + self.K_ISE_sta = None + + def _get_Rs(self): + """getter of Rs""" + return self._Rs + + def _set_Rs(self, value): + """setter of Rs""" + check_var("Rs", value, "float") + self._Rs = value + + Rs = property( + fget=_get_Rs, + fset=_set_Rs, + doc=u"""DC phase winding resistance at Tsta_ref + + :Type: float + """, + ) + + def _get_Ls(self): + """getter of Ls""" + return self._Ls + + def _set_Ls(self, value): + """setter of Ls""" + check_var("Ls", value, "float") + self._Ls = value + + Ls = property( + fget=_get_Ls, + fset=_set_Ls, + doc=u"""Phase winding leakage inductance + + :Type: float + """, + ) + + def _get_Tsta_ref(self): + """getter of Tsta_ref""" + return self._Tsta_ref + + def _set_Tsta_ref(self, value): + """setter of Tsta_ref""" + check_var("Tsta_ref", value, "float") + self._Tsta_ref = value + + Tsta_ref = property( + fget=_get_Tsta_ref, + fset=_set_Tsta_ref, + doc=u"""Stator winding average temperature associated to Rs, Ls parameters + + :Type: float + """, + ) + + def _get_K_RSE_sta(self): + """getter of K_RSE_sta""" + return self._K_RSE_sta + + def _set_K_RSE_sta(self, value): + """setter of K_RSE_sta""" + if type(value) is int and value == -1: + value = array([]) + elif type(value) is list: + try: + value = array(value) + except: + pass + check_var("K_RSE_sta", value, "ndarray") + self._K_RSE_sta = value + + K_RSE_sta = property( + fget=_get_K_RSE_sta, + fset=_set_K_RSE_sta, + doc=u"""Stator winding Resistance Skin Effect factor function of frequency + + :Type: ndarray + """, + ) + + def _get_K_ISE_sta(self): + """getter of K_ISE_sta""" + return self._K_ISE_sta + + def _set_K_ISE_sta(self, value): + """setter of K_ISE_sta""" + if type(value) is int and value == -1: + value = array([]) + elif type(value) is list: + try: + value = array(value) + except: + pass + check_var("K_ISE_sta", value, "ndarray") + self._K_ISE_sta = value + + K_ISE_sta = property( + fget=_get_K_ISE_sta, + fset=_set_K_ISE_sta, + doc=u"""Stator winding Inductance Skin Effect factor function of frequency + + :Type: ndarray + """, + ) diff --git a/pyleecan/Classes/ELUT_PMSM.py b/pyleecan/Classes/ELUT_PMSM.py new file mode 100644 index 000000000..5acefd7f4 --- /dev/null +++ b/pyleecan/Classes/ELUT_PMSM.py @@ -0,0 +1,519 @@ +# -*- coding: utf-8 -*- +# File generated according to Generator/ClassesRef/Simulation/ELUT_PMSM.csv +# WARNING! All changes made in this file will be lost! +"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Simulation/ELUT_PMSM +""" + +from os import linesep +from sys import getsizeof +from logging import getLogger +from ._check import set_array, check_var, raise_ +from ..Functions.get_logger import get_logger +from ..Functions.save import save +from ..Functions.copy import copy +from ..Functions.load import load_init_dict +from ..Functions.Load.import_class import import_class +from .ELUT import ELUT + +# Import all class method +# Try/catch to remove unnecessary dependencies in unused method +try: + from ..Methods.Simulation.ELUT_PMSM.get_parameters import get_parameters +except ImportError as error: + get_parameters = error + +try: + from ..Methods.Simulation.ELUT_PMSM.get_Lq import get_Lq +except ImportError as error: + get_Lq = error + +try: + from ..Methods.Simulation.ELUT_PMSM.get_bemf import get_bemf +except ImportError as error: + get_bemf = error + +try: + from ..Methods.Simulation.ELUT_PMSM.get_Ld import get_Ld +except ImportError as error: + get_Ld = error + +try: + from ..Methods.Simulation.ELUT_PMSM.get_Lmd import get_Lmd +except ImportError as error: + get_Lmd = error + +try: + from ..Methods.Simulation.ELUT_PMSM.get_Lmq import get_Lmq +except ImportError as error: + get_Lmq = error + +try: + from ..Methods.Simulation.ELUT_PMSM.comp_Ldqh_from_Phidqh import ( + comp_Ldqh_from_Phidqh, + ) +except ImportError as error: + comp_Ldqh_from_Phidqh = error + +try: + from ..Methods.Simulation.ELUT_PMSM.comp_Phidqh_from_Phiwind import ( + comp_Phidqh_from_Phiwind, + ) +except ImportError as error: + comp_Phidqh_from_Phiwind = error + +try: + from ..Methods.Simulation.ELUT_PMSM.import_from_data import import_from_data +except ImportError as error: + import_from_data = error + + +from numpy import array, array_equal +from ._check import InitUnKnowClassError + + +class ELUT_PMSM(ELUT): + """ELUT class for PMSM""" + + VERSION = 1 + + # Check ImportError to remove unnecessary dependencies in unused method + # cf Methods.Simulation.ELUT_PMSM.get_parameters + if isinstance(get_parameters, ImportError): + get_parameters = property( + fget=lambda x: raise_( + ImportError( + "Can't use ELUT_PMSM method get_parameters: " + str(get_parameters) + ) + ) + ) + else: + get_parameters = get_parameters + # cf Methods.Simulation.ELUT_PMSM.get_Lq + if isinstance(get_Lq, ImportError): + get_Lq = property( + fget=lambda x: raise_( + ImportError("Can't use ELUT_PMSM method get_Lq: " + str(get_Lq)) + ) + ) + else: + get_Lq = get_Lq + # cf Methods.Simulation.ELUT_PMSM.get_bemf + if isinstance(get_bemf, ImportError): + get_bemf = property( + fget=lambda x: raise_( + ImportError("Can't use ELUT_PMSM method get_bemf: " + str(get_bemf)) + ) + ) + else: + get_bemf = get_bemf + # cf Methods.Simulation.ELUT_PMSM.get_Ld + if isinstance(get_Ld, ImportError): + get_Ld = property( + fget=lambda x: raise_( + ImportError("Can't use ELUT_PMSM method get_Ld: " + str(get_Ld)) + ) + ) + else: + get_Ld = get_Ld + # cf Methods.Simulation.ELUT_PMSM.get_Lmd + if isinstance(get_Lmd, ImportError): + get_Lmd = property( + fget=lambda x: raise_( + ImportError("Can't use ELUT_PMSM method get_Lmd: " + str(get_Lmd)) + ) + ) + else: + get_Lmd = get_Lmd + # cf Methods.Simulation.ELUT_PMSM.get_Lmq + if isinstance(get_Lmq, ImportError): + get_Lmq = property( + fget=lambda x: raise_( + ImportError("Can't use ELUT_PMSM method get_Lmq: " + str(get_Lmq)) + ) + ) + else: + get_Lmq = get_Lmq + # cf Methods.Simulation.ELUT_PMSM.comp_Ldqh_from_Phidqh + if isinstance(comp_Ldqh_from_Phidqh, ImportError): + comp_Ldqh_from_Phidqh = property( + fget=lambda x: raise_( + ImportError( + "Can't use ELUT_PMSM method comp_Ldqh_from_Phidqh: " + + str(comp_Ldqh_from_Phidqh) + ) + ) + ) + else: + comp_Ldqh_from_Phidqh = comp_Ldqh_from_Phidqh + # cf Methods.Simulation.ELUT_PMSM.comp_Phidqh_from_Phiwind + if isinstance(comp_Phidqh_from_Phiwind, ImportError): + comp_Phidqh_from_Phiwind = property( + fget=lambda x: raise_( + ImportError( + "Can't use ELUT_PMSM method comp_Phidqh_from_Phiwind: " + + str(comp_Phidqh_from_Phiwind) + ) + ) + ) + else: + comp_Phidqh_from_Phiwind = comp_Phidqh_from_Phiwind + # cf Methods.Simulation.ELUT_PMSM.import_from_data + if isinstance(import_from_data, ImportError): + import_from_data = property( + fget=lambda x: raise_( + ImportError( + "Can't use ELUT_PMSM method import_from_data: " + + str(import_from_data) + ) + ) + ) + else: + import_from_data = import_from_data + # save and copy methods are available in all object + save = save + copy = copy + # get_logger method is available in all object + get_logger = get_logger + + def __init__( + self, + Phi_dqh=None, + I_dqh=None, + Tmag_ref=20, + E0=None, + E_dqh=None, + orders_dqh=None, + Rs=None, + Ls=None, + Tsta_ref=20, + K_RSE_sta=None, + K_ISE_sta=None, + init_dict=None, + init_str=None, + ): + """Constructor of the class. Can be use in three ways : + - __init__ (arg1 = 1, arg3 = 5) every parameters have name and default values + for pyleecan type, -1 will call the default constructor + - __init__ (init_dict = d) d must be a dictionary with property names as keys + - __init__ (init_str = s) s must be a string + s is the file path to load + + ndarray or list can be given for Vector and Matrix + object or dict can be given for pyleecan Object""" + + if init_str is not None: # Load from a file + init_dict = load_init_dict(init_str)[1] + if init_dict is not None: # Initialisation by dict + assert type(init_dict) is dict + # Overwrite default value with init_dict content + if "Phi_dqh" in list(init_dict.keys()): + Phi_dqh = init_dict["Phi_dqh"] + if "I_dqh" in list(init_dict.keys()): + I_dqh = init_dict["I_dqh"] + if "Tmag_ref" in list(init_dict.keys()): + Tmag_ref = init_dict["Tmag_ref"] + if "E0" in list(init_dict.keys()): + E0 = init_dict["E0"] + if "E_dqh" in list(init_dict.keys()): + E_dqh = init_dict["E_dqh"] + if "orders_dqh" in list(init_dict.keys()): + orders_dqh = init_dict["orders_dqh"] + if "Rs" in list(init_dict.keys()): + Rs = init_dict["Rs"] + if "Ls" in list(init_dict.keys()): + Ls = init_dict["Ls"] + if "Tsta_ref" in list(init_dict.keys()): + Tsta_ref = init_dict["Tsta_ref"] + if "K_RSE_sta" in list(init_dict.keys()): + K_RSE_sta = init_dict["K_RSE_sta"] + if "K_ISE_sta" in list(init_dict.keys()): + K_ISE_sta = init_dict["K_ISE_sta"] + # Set the properties (value check and convertion are done in setter) + self.Phi_dqh = Phi_dqh + self.I_dqh = I_dqh + self.Tmag_ref = Tmag_ref + self.E0 = E0 + self.E_dqh = E_dqh + self.orders_dqh = orders_dqh + # Call ELUT init + super(ELUT_PMSM, self).__init__( + Rs=Rs, Ls=Ls, Tsta_ref=Tsta_ref, K_RSE_sta=K_RSE_sta, K_ISE_sta=K_ISE_sta + ) + # The class is frozen (in ELUT init), for now it's impossible to + # add new properties + + def __str__(self): + """Convert this object in a readeable string (for print)""" + + ELUT_PMSM_str = "" + # Get the properties inherited from ELUT + ELUT_PMSM_str += super(ELUT_PMSM, self).__str__() + ELUT_PMSM_str += ( + "Phi_dqh = " + + linesep + + str(self.Phi_dqh).replace(linesep, linesep + "\t") + + linesep + + linesep + ) + ELUT_PMSM_str += ( + "I_dqh = " + + linesep + + str(self.I_dqh).replace(linesep, linesep + "\t") + + linesep + ) + ELUT_PMSM_str += "Tmag_ref = " + str(self.Tmag_ref) + linesep + ELUT_PMSM_str += "E0 = " + str(self.E0) + linesep + ELUT_PMSM_str += ( + "E_dqh = " + + linesep + + str(self.E_dqh).replace(linesep, linesep + "\t") + + linesep + + linesep + ) + ELUT_PMSM_str += ( + "orders_dqh = " + + linesep + + str(self.orders_dqh).replace(linesep, linesep + "\t") + + linesep + + linesep + ) + return ELUT_PMSM_str + + def __eq__(self, other): + """Compare two objects (skip parent)""" + + if type(other) != type(self): + return False + + # Check the properties inherited from ELUT + if not super(ELUT_PMSM, self).__eq__(other): + return False + if not array_equal(other.Phi_dqh, self.Phi_dqh): + return False + if other.I_dqh != self.I_dqh: + return False + if other.Tmag_ref != self.Tmag_ref: + return False + if other.E0 != self.E0: + return False + if not array_equal(other.E_dqh, self.E_dqh): + return False + if not array_equal(other.orders_dqh, self.orders_dqh): + return False + return True + + def compare(self, other, name="self", ignore_list=None): + """Compare two objects and return list of differences""" + + if ignore_list is None: + ignore_list = list() + if type(other) != type(self): + return ["type(" + name + ")"] + diff_list = list() + + # Check the properties inherited from ELUT + diff_list.extend(super(ELUT_PMSM, self).compare(other, name=name)) + if not array_equal(other.Phi_dqh, self.Phi_dqh): + diff_list.append(name + ".Phi_dqh") + if other._I_dqh != self._I_dqh: + diff_list.append(name + ".I_dqh") + if other._Tmag_ref != self._Tmag_ref: + diff_list.append(name + ".Tmag_ref") + if other._E0 != self._E0: + diff_list.append(name + ".E0") + if not array_equal(other.E_dqh, self.E_dqh): + diff_list.append(name + ".E_dqh") + if not array_equal(other.orders_dqh, self.orders_dqh): + diff_list.append(name + ".orders_dqh") + # Filter ignore differences + diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) + return diff_list + + def __sizeof__(self): + """Return the size in memory of the object (including all subobject)""" + + S = 0 # Full size of the object + + # Get size of the properties inherited from ELUT + S += super(ELUT_PMSM, self).__sizeof__() + S += getsizeof(self.Phi_dqh) + if self.I_dqh is not None: + for value in self.I_dqh: + S += getsizeof(value) + S += getsizeof(self.Tmag_ref) + S += getsizeof(self.E0) + S += getsizeof(self.E_dqh) + S += getsizeof(self.orders_dqh) + return S + + def as_dict(self, **kwargs): + """ + Convert this object in a json serializable dict (can be use in __init__). + Optional keyword input parameter is for internal use only + and may prevent json serializability. + """ + + # Get the properties inherited from ELUT + ELUT_PMSM_dict = super(ELUT_PMSM, self).as_dict(**kwargs) + if self.Phi_dqh is None: + ELUT_PMSM_dict["Phi_dqh"] = None + else: + ELUT_PMSM_dict["Phi_dqh"] = self.Phi_dqh.tolist() + ELUT_PMSM_dict["I_dqh"] = self.I_dqh.copy() if self.I_dqh is not None else None + ELUT_PMSM_dict["Tmag_ref"] = self.Tmag_ref + ELUT_PMSM_dict["E0"] = self.E0 + if self.E_dqh is None: + ELUT_PMSM_dict["E_dqh"] = None + else: + ELUT_PMSM_dict["E_dqh"] = self.E_dqh.tolist() + if self.orders_dqh is None: + ELUT_PMSM_dict["orders_dqh"] = None + else: + ELUT_PMSM_dict["orders_dqh"] = self.orders_dqh.tolist() + # The class name is added to the dict for deserialisation purpose + # Overwrite the mother class name + ELUT_PMSM_dict["__class__"] = "ELUT_PMSM" + return ELUT_PMSM_dict + + def _set_None(self): + """Set all the properties to None (except pyleecan object)""" + + self.Phi_dqh = None + self.I_dqh = None + self.Tmag_ref = None + self.E0 = None + self.E_dqh = None + self.orders_dqh = None + # Set to None the properties inherited from ELUT + super(ELUT_PMSM, self)._set_None() + + def _get_Phi_dqh(self): + """getter of Phi_dqh""" + return self._Phi_dqh + + def _set_Phi_dqh(self, value): + """setter of Phi_dqh""" + if type(value) is int and value == -1: + value = array([]) + elif type(value) is list: + try: + value = array(value) + except: + pass + check_var("Phi_dqh", value, "ndarray") + self._Phi_dqh = value + + Phi_dqh = property( + fget=_get_Phi_dqh, + fset=_set_Phi_dqh, + doc=u"""Stator winding flux llinkage fundamental calculated from user-input inductance tables + + :Type: ndarray + """, + ) + + def _get_I_dqh(self): + """getter of I_dqh""" + return self._I_dqh + + def _set_I_dqh(self, value): + """setter of I_dqh""" + if type(value) is int and value == -1: + value = list() + check_var("I_dqh", value, "list") + self._I_dqh = value + + I_dqh = property( + fget=_get_I_dqh, + fset=_set_I_dqh, + doc=u"""Id Iq Ih table corresponding to flux linkage data given in Phi_dqh + + :Type: list + """, + ) + + def _get_Tmag_ref(self): + """getter of Tmag_ref""" + return self._Tmag_ref + + def _set_Tmag_ref(self, value): + """setter of Tmag_ref""" + check_var("Tmag_ref", value, "float") + self._Tmag_ref = value + + Tmag_ref = property( + fget=_get_Tmag_ref, + fset=_set_Tmag_ref, + doc=u"""Magnet average temperature at which Phi_dqh is given + + :Type: float + """, + ) + + def _get_E0(self): + """getter of E0""" + return self._E0 + + def _set_E0(self, value): + """setter of E0""" + check_var("E0", value, "float") + self._E0 = value + + E0 = property( + fget=_get_E0, + fset=_set_E0, + doc=u"""RMS fundamental back electromotive force (bemf) along Q-axis + + :Type: float + """, + ) + + def _get_E_dqh(self): + """getter of E_dqh""" + return self._E_dqh + + def _set_E_dqh(self, value): + """setter of E_dqh""" + if type(value) is int and value == -1: + value = array([]) + elif type(value) is list: + try: + value = array(value) + except: + pass + check_var("E_dqh", value, "ndarray") + self._E_dqh = value + + E_dqh = property( + fget=_get_E_dqh, + fset=_set_E_dqh, + doc=u"""Back emf harmonics along DQH axis + + :Type: ndarray + """, + ) + + def _get_orders_dqh(self): + """getter of orders_dqh""" + return self._orders_dqh + + def _set_orders_dqh(self, value): + """setter of orders_dqh""" + if type(value) is int and value == -1: + value = array([]) + elif type(value) is list: + try: + value = array(value) + except: + pass + check_var("orders_dqh", value, "ndarray") + self._orders_dqh = value + + orders_dqh = property( + fget=_get_orders_dqh, + fset=_set_orders_dqh, + doc=u"""Back harmonic orders (multiple of fundamental electrical frequency) corresponding to Edqh spectrum + + :Type: ndarray + """, + ) diff --git a/pyleecan/Classes/ELUT_SCIM.py b/pyleecan/Classes/ELUT_SCIM.py new file mode 100644 index 000000000..4f413361b --- /dev/null +++ b/pyleecan/Classes/ELUT_SCIM.py @@ -0,0 +1,373 @@ +# -*- coding: utf-8 -*- +# File generated according to Generator/ClassesRef/Simulation/ELUT_SCIM.csv +# WARNING! All changes made in this file will be lost! +"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Simulation/ELUT_SCIM +""" + +from os import linesep +from sys import getsizeof +from logging import getLogger +from ._check import set_array, check_var, raise_ +from ..Functions.get_logger import get_logger +from ..Functions.save import save +from ..Functions.copy import copy +from ..Functions.load import load_init_dict +from ..Functions.Load.import_class import import_class +from .ELUT import ELUT + +# Import all class method +# Try/catch to remove unnecessary dependencies in unused method +try: + from ..Methods.Simulation.ELUT_SCIM.get_parameters import get_parameters +except ImportError as error: + get_parameters = error + +try: + from ..Methods.Simulation.ELUT_SCIM.get_Lm import get_Lm +except ImportError as error: + get_Lm = error + +try: + from ..Methods.Simulation.ELUT_SCIM.comp_Lm_from_Phim import comp_Lm_from_Phim +except ImportError as error: + comp_Lm_from_Phim = error + +try: + from ..Methods.Simulation.ELUT_SCIM.import_from_data import import_from_data +except ImportError as error: + import_from_data = error + + +from numpy import array, array_equal +from ._check import InitUnKnowClassError + + +class ELUT_SCIM(ELUT): + """ELUT class for SCIM""" + + VERSION = 1 + + # Check ImportError to remove unnecessary dependencies in unused method + # cf Methods.Simulation.ELUT_SCIM.get_parameters + if isinstance(get_parameters, ImportError): + get_parameters = property( + fget=lambda x: raise_( + ImportError( + "Can't use ELUT_SCIM method get_parameters: " + str(get_parameters) + ) + ) + ) + else: + get_parameters = get_parameters + # cf Methods.Simulation.ELUT_SCIM.get_Lm + if isinstance(get_Lm, ImportError): + get_Lm = property( + fget=lambda x: raise_( + ImportError("Can't use ELUT_SCIM method get_Lm: " + str(get_Lm)) + ) + ) + else: + get_Lm = get_Lm + # cf Methods.Simulation.ELUT_SCIM.comp_Lm_from_Phim + if isinstance(comp_Lm_from_Phim, ImportError): + comp_Lm_from_Phim = property( + fget=lambda x: raise_( + ImportError( + "Can't use ELUT_SCIM method comp_Lm_from_Phim: " + + str(comp_Lm_from_Phim) + ) + ) + ) + else: + comp_Lm_from_Phim = comp_Lm_from_Phim + # cf Methods.Simulation.ELUT_SCIM.import_from_data + if isinstance(import_from_data, ImportError): + import_from_data = property( + fget=lambda x: raise_( + ImportError( + "Can't use ELUT_SCIM method import_from_data: " + + str(import_from_data) + ) + ) + ) + else: + import_from_data = import_from_data + # save and copy methods are available in all object + save = save + copy = copy + # get_logger method is available in all object + get_logger = get_logger + + def __init__( + self, + Phi_m=None, + Trot_ref=20, + K_RSE_rot=None, + K_ISE_rot=None, + Rs=None, + Ls=None, + Tsta_ref=20, + K_RSE_sta=None, + K_ISE_sta=None, + init_dict=None, + init_str=None, + ): + """Constructor of the class. Can be use in three ways : + - __init__ (arg1 = 1, arg3 = 5) every parameters have name and default values + for pyleecan type, -1 will call the default constructor + - __init__ (init_dict = d) d must be a dictionary with property names as keys + - __init__ (init_str = s) s must be a string + s is the file path to load + + ndarray or list can be given for Vector and Matrix + object or dict can be given for pyleecan Object""" + + if init_str is not None: # Load from a file + init_dict = load_init_dict(init_str)[1] + if init_dict is not None: # Initialisation by dict + assert type(init_dict) is dict + # Overwrite default value with init_dict content + if "Phi_m" in list(init_dict.keys()): + Phi_m = init_dict["Phi_m"] + if "Trot_ref" in list(init_dict.keys()): + Trot_ref = init_dict["Trot_ref"] + if "K_RSE_rot" in list(init_dict.keys()): + K_RSE_rot = init_dict["K_RSE_rot"] + if "K_ISE_rot" in list(init_dict.keys()): + K_ISE_rot = init_dict["K_ISE_rot"] + if "Rs" in list(init_dict.keys()): + Rs = init_dict["Rs"] + if "Ls" in list(init_dict.keys()): + Ls = init_dict["Ls"] + if "Tsta_ref" in list(init_dict.keys()): + Tsta_ref = init_dict["Tsta_ref"] + if "K_RSE_sta" in list(init_dict.keys()): + K_RSE_sta = init_dict["K_RSE_sta"] + if "K_ISE_sta" in list(init_dict.keys()): + K_ISE_sta = init_dict["K_ISE_sta"] + # Set the properties (value check and convertion are done in setter) + self.Phi_m = Phi_m + self.Trot_ref = Trot_ref + self.K_RSE_rot = K_RSE_rot + self.K_ISE_rot = K_ISE_rot + # Call ELUT init + super(ELUT_SCIM, self).__init__( + Rs=Rs, Ls=Ls, Tsta_ref=Tsta_ref, K_RSE_sta=K_RSE_sta, K_ISE_sta=K_ISE_sta + ) + # The class is frozen (in ELUT init), for now it's impossible to + # add new properties + + def __str__(self): + """Convert this object in a readeable string (for print)""" + + ELUT_SCIM_str = "" + # Get the properties inherited from ELUT + ELUT_SCIM_str += super(ELUT_SCIM, self).__str__() + ELUT_SCIM_str += ( + "Phi_m = " + + linesep + + str(self.Phi_m).replace(linesep, linesep + "\t") + + linesep + + linesep + ) + ELUT_SCIM_str += "Trot_ref = " + str(self.Trot_ref) + linesep + ELUT_SCIM_str += ( + "K_RSE_rot = " + + linesep + + str(self.K_RSE_rot).replace(linesep, linesep + "\t") + + linesep + + linesep + ) + ELUT_SCIM_str += ( + "K_ISE_rot = " + + linesep + + str(self.K_ISE_rot).replace(linesep, linesep + "\t") + + linesep + + linesep + ) + return ELUT_SCIM_str + + def __eq__(self, other): + """Compare two objects (skip parent)""" + + if type(other) != type(self): + return False + + # Check the properties inherited from ELUT + if not super(ELUT_SCIM, self).__eq__(other): + return False + if not array_equal(other.Phi_m, self.Phi_m): + return False + if other.Trot_ref != self.Trot_ref: + return False + if not array_equal(other.K_RSE_rot, self.K_RSE_rot): + return False + if not array_equal(other.K_ISE_rot, self.K_ISE_rot): + return False + return True + + def compare(self, other, name="self", ignore_list=None): + """Compare two objects and return list of differences""" + + if ignore_list is None: + ignore_list = list() + if type(other) != type(self): + return ["type(" + name + ")"] + diff_list = list() + + # Check the properties inherited from ELUT + diff_list.extend(super(ELUT_SCIM, self).compare(other, name=name)) + if not array_equal(other.Phi_m, self.Phi_m): + diff_list.append(name + ".Phi_m") + if other._Trot_ref != self._Trot_ref: + diff_list.append(name + ".Trot_ref") + if not array_equal(other.K_RSE_rot, self.K_RSE_rot): + diff_list.append(name + ".K_RSE_rot") + if not array_equal(other.K_ISE_rot, self.K_ISE_rot): + diff_list.append(name + ".K_ISE_rot") + # Filter ignore differences + diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) + return diff_list + + def __sizeof__(self): + """Return the size in memory of the object (including all subobject)""" + + S = 0 # Full size of the object + + # Get size of the properties inherited from ELUT + S += super(ELUT_SCIM, self).__sizeof__() + S += getsizeof(self.Phi_m) + S += getsizeof(self.Trot_ref) + S += getsizeof(self.K_RSE_rot) + S += getsizeof(self.K_ISE_rot) + return S + + def as_dict(self, **kwargs): + """ + Convert this object in a json serializable dict (can be use in __init__). + Optional keyword input parameter is for internal use only + and may prevent json serializability. + """ + + # Get the properties inherited from ELUT + ELUT_SCIM_dict = super(ELUT_SCIM, self).as_dict(**kwargs) + if self.Phi_m is None: + ELUT_SCIM_dict["Phi_m"] = None + else: + ELUT_SCIM_dict["Phi_m"] = self.Phi_m.tolist() + ELUT_SCIM_dict["Trot_ref"] = self.Trot_ref + if self.K_RSE_rot is None: + ELUT_SCIM_dict["K_RSE_rot"] = None + else: + ELUT_SCIM_dict["K_RSE_rot"] = self.K_RSE_rot.tolist() + if self.K_ISE_rot is None: + ELUT_SCIM_dict["K_ISE_rot"] = None + else: + ELUT_SCIM_dict["K_ISE_rot"] = self.K_ISE_rot.tolist() + # The class name is added to the dict for deserialisation purpose + # Overwrite the mother class name + ELUT_SCIM_dict["__class__"] = "ELUT_SCIM" + return ELUT_SCIM_dict + + def _set_None(self): + """Set all the properties to None (except pyleecan object)""" + + self.Phi_m = None + self.Trot_ref = None + self.K_RSE_rot = None + self.K_ISE_rot = None + # Set to None the properties inherited from ELUT + super(ELUT_SCIM, self)._set_None() + + def _get_Phi_m(self): + """getter of Phi_m""" + return self._Phi_m + + def _set_Phi_m(self, value): + """setter of Phi_m""" + if type(value) is int and value == -1: + value = array([]) + elif type(value) is list: + try: + value = array(value) + except: + pass + check_var("Phi_m", value, "ndarray") + self._Phi_m = value + + Phi_m = property( + fget=_get_Phi_m, + fset=_set_Phi_m, + doc=u"""Stator winding flux look-up table: list of DataTime objects whose (Id,Iq) is given by Idq list + + :Type: ndarray + """, + ) + + def _get_Trot_ref(self): + """getter of Trot_ref""" + return self._Trot_ref + + def _set_Trot_ref(self, value): + """setter of Trot_ref""" + check_var("Trot_ref", value, "float") + self._Trot_ref = value + + Trot_ref = property( + fget=_get_Trot_ref, + fset=_set_Trot_ref, + doc=u"""Rotor bar average temperature at which Phi_m is given + + :Type: float + """, + ) + + def _get_K_RSE_rot(self): + """getter of K_RSE_rot""" + return self._K_RSE_rot + + def _set_K_RSE_rot(self, value): + """setter of K_RSE_rot""" + if type(value) is int and value == -1: + value = array([]) + elif type(value) is list: + try: + value = array(value) + except: + pass + check_var("K_RSE_rot", value, "ndarray") + self._K_RSE_rot = value + + K_RSE_rot = property( + fget=_get_K_RSE_rot, + fset=_set_K_RSE_rot, + doc=u"""Rotor winding Resistance Skin Effect factor function of frequency + + :Type: ndarray + """, + ) + + def _get_K_ISE_rot(self): + """getter of K_ISE_rot""" + return self._K_ISE_rot + + def _set_K_ISE_rot(self, value): + """setter of K_ISE_rot""" + if type(value) is int and value == -1: + value = array([]) + elif type(value) is list: + try: + value = array(value) + except: + pass + check_var("K_ISE_rot", value, "ndarray") + self._K_ISE_rot = value + + K_ISE_rot = property( + fget=_get_K_ISE_rot, + fset=_set_K_ISE_rot, + doc=u"""Rotor winding Inductance Skin Effect factor function of frequency + + :Type: ndarray + """, + ) diff --git a/pyleecan/Generator/ClassesRef/Simulation/ELUT.csv b/pyleecan/Generator/ClassesRef/Simulation/ELUT.csv new file mode 100644 index 000000000..a54648aa3 --- /dev/null +++ b/pyleecan/Generator/ClassesRef/Simulation/ELUT.csv @@ -0,0 +1,6 @@ +Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille +Rs,Ohm,DC phase winding resistance at Tsta_ref ,0,float,None,,,,Simulation,,get_parameters,VERSION,1,Abstract class for Electrical Look Up Table (ELUT), +Ls,H,Phase winding leakage inductance,0,float,None,,,,,,,,,, +Tsta_ref,degC,"Stator winding average temperature associated to Rs, Ls parameters",0,float,20,,,,,,,,,, +K_RSE_sta,,Stator winding Resistance Skin Effect factor function of frequency,,ndarray,None,,,,,,,,,, +K_ISE_sta,,Stator winding Inductance Skin Effect factor function of frequency,,ndarray,None,,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv b/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv new file mode 100644 index 000000000..761cebdc9 --- /dev/null +++ b/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv @@ -0,0 +1,10 @@ +Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille +Phi_dqh,Wb,Stator winding flux llinkage fundamental calculated from user-input inductance tables,,ndarray,None,,,,Simulation,ELUT,get_parameters,VERSION,1,ELUT class for PMSM, +I_dqh,Arms,Id Iq Ih table corresponding to flux linkage data given in Phi_dqh,,list,None,,,,,,get_Lq,,,, +Tmag_ref,degC,Magnet average temperature at which Phi_dqh is given,,float,20,,,,,,get_bemf,,,, +E0,V,RMS fundamental back electromotive force (bemf) along Q-axis,,float,None,,,,,,get_Ld,,,, +E_dqh,V,Back emf harmonics along DQH axis,,ndarray,None,,,,,,get_Lmd,,,, +orders_dqh,,Back harmonic orders (multiple of fundamental electrical frequency) corresponding to Edqh spectrum,,ndarray,None,,,,,,get_Lmq,,,, +,,,,,,,,,,,comp_Ldqh_from_Phidqh,,,, +,,,,,,,,,,,comp_Phidqh_from_Phiwind,,,, +,,,,,,,,,,,import_from_data,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/ELUT_SCIM.csv b/pyleecan/Generator/ClassesRef/Simulation/ELUT_SCIM.csv new file mode 100644 index 000000000..0f4ffd41f --- /dev/null +++ b/pyleecan/Generator/ClassesRef/Simulation/ELUT_SCIM.csv @@ -0,0 +1,5 @@ +Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille +Phi_m,Wb,"Stator winding flux look-up table: list of DataTime objects whose (Id,Iq) is given by Idq list",,ndarray,None,,,,Simulation,ELUT,get_parameters,VERSION,1,ELUT class for SCIM, +Trot_ref,degC,Rotor bar average temperature at which Phi_m is given,0,float,20,,,,,,get_Lm,,,, +K_RSE_rot,,Rotor winding Resistance Skin Effect factor function of frequency,,ndarray,None,,,,,,comp_Lm_from_Phim,,,, +K_ISE_rot,,Rotor winding Inductance Skin Effect factor function of frequency,,ndarray,None,,,,,,import_from_data,,,, diff --git a/pyleecan/Methods/Output/OutElec/store.py b/pyleecan/Methods/Output/OutElec/store.py new file mode 100644 index 000000000..bb07099cc --- /dev/null +++ b/pyleecan/Methods/Output/OutElec/store.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +from numpy import mean, max as np_max, min as np_min + +from SciDataTool import DataTime, VectorField, Data1D + +from ....Functions.Winding.gen_phase_list import gen_name + + +def store(self, out_dict, axes_dict): + """Store the standard outputs of Electrical that are temporarily in out_dict as arrays into OutElec as Data object + + Parameters + ---------- + self : OutElec + the OutElec object to update + out_dict : dict + Dict containing all magnetic quantities that have been calculated in comp_parameters of EEC + axes_dict: {Data} + Dict of axes used for electrical calculation + + """ + + # Get time axis + Time = axes_dict["Time"] + + # Store airgap flux as VectorField object + # Axes for each airgap flux component + # axis_list = [Time, axes_dict["Angle"]] + + # Create VectorField with empty components + self.B = VectorField( + name="Airgap flux density", + symbol="B", + ) + # Radial flux component + if "Br" in out_dict: + self.B.components["radial"] = DataTime( + name="Airgap radial flux density", + unit="T", + symbol="B_r", + axes=axis_list, + values=out_dict.pop("Br"), + ) + # Tangential flux component + if "Bt" in out_dict: + self.B.components["tangential"] = DataTime( + name="Airgap tangential flux density", + unit="T", + symbol="B_t", + axes=axis_list, + values=out_dict.pop("Bt"), + ) + # Axial flux component + if "Bz" in out_dict: + self.B.components["axial"] = DataTime( + name="Airgap axial flux density", + unit="T", + symbol="B_z", + axes=axis_list, + values=out_dict.pop("Bz"), + ) + + # Store electromagnetic torque over time, and global values: average, peak to peak and ripple + if "Tem" in out_dict: + + Tem = out_dict.pop("Tem") + + self.Tem = DataTime( + name="Electromagnetic torque", + unit="Nm", + symbol="T_{em}", + axes=[axes_dict["Time_Tem"]], + values=Tem, + ) + + # Calculate average torque in Nm + self.Tem_av = mean(Tem) + self.get_logger().debug("Average Torque: " + str(self.Tem_av) + " N.m") + + # Calculate peak to peak torque in absolute value Nm + self.Tem_rip_pp = abs(np_max(Tem) - np_min(Tem)) # [N.m] + + # Calculate torque ripple in percentage + if self.Tem_av != 0: + self.Tem_rip_norm = self.Tem_rip_pp / self.Tem_av # [] + else: + self.Tem_rip_norm = None + + # Store list of winding fluxlinkage, stator winding fluxlinkage + # and calculate electromotive force + if "Phi_wind" in out_dict: + machine = self.parent.simu.machine + self.Phi_wind = {} + for key in out_dict["Phi_wind"].keys(): + # Store stator winding flux + lam = machine.get_lam_by_label(key) + qs = lam.winding.qs + + Phase = Data1D( + name="phase", + unit="", + values=gen_name(qs), + is_components=True, + ) + prefix = "Stator" if lam.is_stator else "Rotor" + self.Phi_wind[key] = DataTime( + name=prefix + " Winding Flux", + unit="Wb", + symbol="Phi_{wind}", + axes=[Time, Phase], + values=out_dict["Phi_wind"][key], + ) + + if "Stator_0" in out_dict["Phi_wind"].keys(): # TODO fix for multi stator + self.Phi_wind_stator = self.Phi_wind["Stator_0"] + + # Electromotive force computation + self.comp_emf() + + # remove from out_dict + out_dict.pop("Phi_wind") + + # Store MeshSolution object + if "meshsolution" in out_dict: + self.meshsolution = out_dict.pop("meshsolution") + + if "Rag" in out_dict: + self.Rag = out_dict.pop("Rag") diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC_freq.py b/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC_freq.py new file mode 100644 index 000000000..3ae46f593 --- /dev/null +++ b/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC_freq.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- + +from ....Functions.Electrical.coordinate_transformation import dq2n +from ....Functions.Winding.gen_phase_list import gen_name + +from numpy import array, pi, real, imag, tile +from scipy.linalg import solve +from SciDataTool import Data1D, DataTime + + +def solve_EEC_freq(self, output): + """Solves the equivalent electrical circuit for each frequency (not only fundamental) + TODO find ref. to cite + cf "Title" + Autor, Publisher + + ---> ----> + -----Rs------XsIs---- --- -----Rr'----Xr'Ir'---- + | | | | + | Rfe Xm Rr'*(s-1)/s + | | | | + ---------Is---------- --- ---------Ir------------ + + ---> + Us + + Parameters + ---------- + self : EEC_SCIM + an EEC_SCIM object, including ELUT + output : Output + an Output object + """ + ELUT = self.ELUT #Electrical Look Up Table containing Lm(Im) + k_skin = self.k_skin #skin effect factor array + + # initial calculation of unsaturated magnetization current + Lm0 = ELUT.phim[0] + EEC.solve_EEC() + Im0 = EEC. + + + + Rr_s = Rr / slip if slip != 0 else 1e16 # TODO modify system instead + + # Prepare linear system + + # Solve system + if "Ud" in self.parameters: + Us = self.parameters["Ud"] + 1j * self.parameters["Uq"] + # input vector + b = array([real(Us), imag(Us), 0, 0, 0, 0, 0, 0, 0, 0]) + # system matrix (unknowns order: Um, Is, Im, Ir', Ife each real and imagine parts) + # TODO simplify system for less unknows (only calculate them afterwards, e.g. Um, Im, Ife) + # fmt: off + A = array( + [ + # sum of (real and imagine) voltages equals the input voltage Us + [ 1, 0, Rs, -Xs, 0, 0, 0, 0, 0, 0, ], + [ 0, 1, Xs, Rs, 0, 0, 0, 0, 0, 0, ], + # sum of (real and imagine) currents are zeros + [ 0, 0, -1, 0, 1, 0, 1, 0, 1, 0, ], + [ 0, 0, 0, -1, 0, 1, 0, 1, 0, 1, ], + # j*Xm*Im = Um + [-1, 0, 0, 0, 0, -Xm, 0, 0, 0, 0, ], + [ 0, -1, 0, 0, Xm, 0, 0, 0, 0, 0, ], + # (Rr'/s + j*Xr')*Ir' = Um + [-1, 0, 0, 0, 0, 0, Rr_s, -Xr, 0, 0, ], + [ 0, -1, 0, 0, 0, 0, Xr, Rr_s, 0, 0, ], + # Rfe*Ife = Um + [-1, 0, 0, 0, 0, 0, 0, 0, Rfe, 0, ], + [ 0, -1, 0, 0, 0, 0, 0, 0, 0, Rfe, ], + ] + ) + # fmt: on + # delete last row and column if Rfe is None + if Rfe is None: + A = A[:-2, :-2] + b = b[:-2] + + # print(b) + # print(A) + X = solve(A.astype(float), b.astype(float)) + + Ir_norm = array([X[6], X[7]]) + + # TODO use logger for output of some quantities + + output.elec.Id_ref = X[2] # use Id_ref / Iq_ref for now + output.elec.Iq_ref = X[3] + else: + pass + # TODO + + # Compute stator currents + output.elec.Is = None + output.elec.Is = output.elec.get_Is() + + # Compute stator voltage + output.elec.Us = None + output.elec.Us = output.elec.get_Us() + + # Compute rotor currents + time = output.elec.Time.get_values(is_oneperiod=True) + Nt = time.size + qsr = output.simu.machine.rotor.winding.qs + sym = output.simu.machine.comp_periodicity()[0] + + Ir_ = tile(Ir_norm, (Nt, 1)) * norm + + w_slip = ws * slip + + # Get rotation direction + rot_dir = output.get_rot_dir() + + # compute actual rotor bar currents + # TODO fix: initial rotor pos. is disregarded for now + Ir = dq2n(Ir_, w_slip * time, n=qsr // sym, rot_dir=rot_dir, is_n_rms=False) + Ir = tile(Ir, (1, sym)) + + Phase = Data1D( + name="phase", + unit="", + values=gen_name(qsr), + is_components=True, + ) + output.elec.Ir = DataTime( + name="Rotor current", + unit="A", + symbol="Ir", + axes=[Phase, output.elec.Time.copy()], + values=Ir.T, + ) diff --git a/pyleecan/Methods/Simulation/ELUT/__init__.py b/pyleecan/Methods/Simulation/ELUT/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pyleecan/Methods/Simulation/ELUT/get_parameters.py b/pyleecan/Methods/Simulation/ELUT/get_parameters.py new file mode 100644 index 000000000..c8ffba5ee --- /dev/null +++ b/pyleecan/Methods/Simulation/ELUT/get_parameters.py @@ -0,0 +1,50 @@ +from numpy import interp + + +def get_parameters(self, Tsta=None, felec=None): + """Get the parameters dict for the ELUT at the operationnal temperature and frequency + Parameters + ---------- + self : ELUT + an ELUT object + + Returns + ---------- + param_dict : dict + a Dict object + """ + + +param_dict = dict() + +alphasw = self.cond_mat.elec.alpha + +# stator winding phase resistance, skin effect correction +if felec is None: + Rs_freq = self.Rs +else: + Rs_dc = self.Rs # DC resistance at Tsta_ref + K_RSE_sta = self.K_RSE_sta # skin effect factor for resistance + Rs_freq = Rs_dc * interp(K_RSE_sta[0, :], K_RSE_sta[1, :], felec) + +# stator winding phase resistance, temperature correction +if Tsta is not None: + Rs_freq_temp = Rs_freq +else: + Tsta_ref = self.Tsta_ref # ref temperature + Rs_freq_temp = Rs_freq * (1 + alphasw * (Tsta - Tsta_ref)) + +param_dict["Rs"] = Rs_freq_temp + + +# stator winding phase leakage inductance, skin effect correction +if felec is None: + Ls_freq = self.Ls +else: + Ls_dc = self.Ls # DC resistance + K_ISE_sta = self.K_ISE_sta # skin effect factor for leakage inductance + Ls_freq = Ls_dc * interp(K_ISE_sta[0, :], K_ISE_sta[1, :], felec) + +param_dict["Ls"] = Ls_freq + +return param_dict diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/__init__.py b/pyleecan/Methods/Simulation/ELUT_PMSM/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_bemf.py b/pyleecan/Methods/Simulation/ELUT_PMSM/get_bemf.py new file mode 100644 index 000000000..76e9d419c --- /dev/null +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/get_bemf.py @@ -0,0 +1,23 @@ +def get_bemf(self): + """Get the phase to phase back electromotive force magnitude [V] from ELUT flux linkage data + Parameters + ---------- + self : ELUT + an ELUT_PMSM object + + Returns + ---------- + bemf : ndarray + (0,:) back emf waveform magnitude as a function of rotor position [V] + (1,:) rotor position [rad] + """ + + +# calculating bemf from MLUT +Phi_dqh = self.Phi_dqh +I_dqh = self.I_dqh + +# Phi0_dqh = Phi_dqh[I_dqh.index([0,0,0]),:] + + +return bemf diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_parameters.py b/pyleecan/Methods/Simulation/ELUT_PMSM/get_parameters.py new file mode 100644 index 000000000..86cb5db7a --- /dev/null +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/get_parameters.py @@ -0,0 +1,34 @@ +from numpy import interp + + +def get_parameters(self, Tsta, Tmag, felec): + """Get the parameters dict for the ELUT of PMSM at the operationnal temperature and frequency + Parameters + ---------- + self : ELUT + an ELUT_PMSM object + + Returns + ---------- + param_dict : dict + a Dict object + """ + + +# getting parameters of the abstract class ELUT (stator parameters) +param_dict = super(type(self), self).get_parameters(Tsta=Tsta, felec=felec) + +Brm20 = self.magnet_0.mat_type.mag.Brm20 +alpha_Br = self.magnet_0.mat_type.mag.alpha_Br + +# back emf [V] update with temperature +if Tmag is None: + E0_temp = self.E0 +else: + Tmag_ref = self.Tmag_ref + E0_temp = self.E0 * (1 + alpha_Br * (Tmag - Tmag_ref)) + +param_dict["E0"] = E0_temp + + +return param_dict diff --git a/pyleecan/Methods/Simulation/ELUT_SCIM/__init__.py b/pyleecan/Methods/Simulation/ELUT_SCIM/__init__.py new file mode 100644 index 000000000..e69de29bb From ee9da62e45989edd91af40df6bc6f05369c41bd1 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Thu, 5 Aug 2021 16:20:45 +0200 Subject: [PATCH 015/167] [WiP] Add InputVoltage and transform InputCurrent to children of InputVoltage [WiP] Add test_EEC_ELUT_SCIM_001.py to test EEC_SCIM using ELUT import --- .../Electrical/test_EEC_ELUT_SCIM_001.py | 228 ++++++++ .../Validation/Electrical/test_skin_effect.py | 2 + pyleecan/Classes/Class_Dict.json | 240 ++++++--- pyleecan/Classes/ELUT.py | 118 +---- pyleecan/Classes/ELUT_PMSM.py | 24 +- pyleecan/Classes/ELUT_SCIM.py | 188 +++---- pyleecan/Classes/InputCurrent.py | 227 ++------ pyleecan/Classes/InputVoltage.py | 499 ++++++++++++++++++ pyleecan/Classes/OutElec.py | 68 ++- pyleecan/Classes/import_all.py | 1 + pyleecan/Functions/load_switch.py | 1 + .../Generator/ClassesRef/Output/OutElec.csv | 10 +- .../Generator/ClassesRef/Simulation/ELUT.csv | 4 +- .../ClassesRef/Simulation/ELUT_PMSM.csv | 2 +- .../ClassesRef/Simulation/ELUT_SCIM.csv | 9 +- .../ClassesRef/Simulation/InputCurrent.csv | 9 +- .../ClassesRef/Simulation/InputVoltage.csv | 10 + .../Simulation/EEC_SCIM/comp_parameters.py | 29 +- .../Methods/Simulation/ELUT/get_param_dict.py | 20 + .../Methods/Simulation/ELUT/get_parameters.py | 50 -- .../Simulation/ELUT_PMSM/get_param_dict.py | 32 ++ .../Simulation/ELUT_PMSM/get_parameters.py | 34 -- .../Simulation/ELUT_SCIM/get_param_dict.py | 29 + pyleecan/Methods/Simulation/Electrical/run.py | 53 +- .../Simulation/InputCurrent/gen_input.py | 61 +-- .../Simulation/InputVoltage/__init__.py | 7 + .../Simulation/InputVoltage/comp_felec.py | 30 ++ .../Simulation/InputVoltage/gen_input.py | 130 +++++ .../InputVoltage/set_OP_from_array.py | 29 + 29 files changed, 1479 insertions(+), 665 deletions(-) create mode 100644 Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py create mode 100644 pyleecan/Classes/InputVoltage.py create mode 100644 pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv create mode 100644 pyleecan/Methods/Simulation/ELUT/get_param_dict.py delete mode 100644 pyleecan/Methods/Simulation/ELUT/get_parameters.py create mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/get_param_dict.py delete mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/get_parameters.py create mode 100644 pyleecan/Methods/Simulation/ELUT_SCIM/get_param_dict.py create mode 100644 pyleecan/Methods/Simulation/InputVoltage/__init__.py create mode 100644 pyleecan/Methods/Simulation/InputVoltage/comp_felec.py create mode 100644 pyleecan/Methods/Simulation/InputVoltage/gen_input.py create mode 100644 pyleecan/Methods/Simulation/InputVoltage/set_OP_from_array.py diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py b/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py new file mode 100644 index 000000000..8506efe85 --- /dev/null +++ b/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py @@ -0,0 +1,228 @@ +from os.path import join, isfile + +import pytest + +from numpy import zeros, squeeze, abs as np_abs, array, pi, sum as np_sum, cos + +import matplotlib.pyplot as plt + +from Tests import save_validation_path as save_path + +from pyleecan.Classes.Electrical import Electrical +from pyleecan.Classes.EEC_SCIM import EEC_SCIM +from pyleecan.Classes.ELUT_SCIM import ELUT_SCIM +from pyleecan.Classes.ImportMatrixVal import ImportMatrixVal +from pyleecan.Classes.InputVoltage import InputVoltage +from pyleecan.Classes.Output import Output +from pyleecan.Classes.Simu1 import Simu1 + +from pyleecan.Functions.load import load +from pyleecan.definitions import DATA_DIR +from pyleecan.Classes.ImportMatlab import ImportMatlab +from pyleecan.definitions import config_dict +from pyleecan.Functions.Plot import dict_2D, dict_3D + +from SciDataTool import Data1D, DataLinspace, DataTime, DataFreq, VectorField + +color_list = config_dict["PLOT"]["COLOR_DICT"]["CURVE_COLORS"] + +NAS_path = "//192.168.1.168/eomys/IT_data/Validation_data/Manatee_v2/" + +is_show_fig = False + + +@pytest.mark.SCIM +@pytest.mark.Electrical +def test_EEC_ELUT_SCIM_001(): + """Validation of the structural/acoustic module for default_machine machine + Comparison with MANATEE V1 results""" + + #%% Load MANATEE V1 results + # matlab_path = ( + # NAS_path + "Manatee_v1_results/default_proj_nl/default_proj_nl_results.mat" + # ) + # matlab_path = "D:/Manatee_V1_trunk/Manatee_1.0/Results/default_proj_nl/default_proj_nl_results.mat" + matlab_path = "D:/Manatee_V1_trunk/Manatee_1.0/ELUT_SCIM_001.mat" + + assert isfile(matlab_path) + + param_dict = dict() + param_list = [ + "slip", + "N0", + "Im", + "Lm", + "R10", + "R20", + "R1_20", + "R2_20", + "L10", + "L20", + "I10", + "U0", + "I20", + "Tswind", + "Trwind", + ] + + for param in param_list: + value = ImportMatlab(file_path=matlab_path, var_name=param).get_data() + if value.size == 1: + if value.dtype == complex: + param_dict[param] = complex(value) + else: + param_dict[param] = float(value) + else: + param_dict[param] = value + + # Prepare simulation + SCIM_001 = load(join(DATA_DIR, "Machine", "SCIM_001.json")) + + simu = Simu1(name="test_EEC_ELUT_SCIM_001", machine=SCIM_001) + + simu.input = InputVoltage( + U0_ref=param_dict["U0"], + Na_tot=2016, + Nt_tot=2016, + N0=param_dict["N0"], + slip_ref=param_dict["slip"], + ) + + ELUT_SCIM_001 = ELUT_SCIM( + Rs=param_dict["R1_20"], + Ls=param_dict["L10"], + Tsta_ref=20, + Rr=param_dict["R2_20"], + Lr=param_dict["L20"], + Trot_ref=20, + Phi_m=np_abs(param_dict["L_m"] * param_dict["I_m"]), + I_m=np_abs(param_dict["I_m"]), + ) + + # Configure simulation + simu.elec = Electrical( + ELUT_enforced=ELUT_SCIM_001, + Tsta=param_dict["Tswind"], + Trot=param_dict["Trwind"], + ) + + # Run simulation + #%% + out = simu.run() + #%% + + # #%% Prepare MANATEE V1 data for comparisons + # Angle = Data1D( + # name="angle", + # unit="rad", + # values=angle, + # ) + # MMF_space = DataTime( + # symbol="MMF", + # unit="At", + # values=MMF_space_v1, + # axes=[Angle], + # ) + + # Per_space = DataTime( + # symbol="Per", + # unit="H/m^2", + # values=Per_space_v1, + # axes=[Angle], + # ) + # Br_space = DataTime( + # symbol="B_r", + # unit="T", + # values=B_r_space, + # axes=[Angle], + # ) + # B_space = VectorField(components={"radial": Br_space}) + # AGSFr_space = DataTime( + # symbol="AGSF_r", + # unit="N/m^2", + # values=AGSF_space_v1, + # axes=[Angle], + # ) + # AGSF_space = VectorField(components={"radial": AGSFr_space}) + + # Time = Data1D( + # name="time", + # unit="s", + # values=time, + # normalizations={"elec_order": 1188 / 60 * 3}, + # ) + # MMF_time = DataTime( + # symbol="MMF", + # unit="At", + # values=MMF_time_v1, + # axes=[Time], + # ) + + # Per_time = DataTime( + # symbol="Per", + # unit="H/m^2", + # values=Per_time_v1, + # axes=[Time], + # ) + # Br_time = DataTime( + # symbol="B_r", + # unit="T", + # values=B_r_time, + # axes=[Time], + # ) + # B_time = VectorField(components={"radial": Br_time}) + + # # Plots + # if is_show_fig: + # legend_list = ["Manatee v2", "Manatee v1"] + # out.mag.MMF.plot_2D_Data( + # "angle{°}", + # data_list=[MMF_space], + # legend_list=legend_list, + # save_path=join(save_path, "plot_MMF_angle"), + # linestyles=["solid", "dashed"], + # is_show_fig=is_show_fig, + # **dict_2D + # ) + # out.mag.MMF.plot_2D_Data( + # "time", + # data_list=[MMF_time], + # legend_list=legend_list, + # save_path=join(save_path, "plot_MMF_time"), + # is_show_fig=is_show_fig, + # linestyles=["solid", "dashed"], + # **dict_2D + # ) + # out.mag.Per.plot_2D_Data( + # "angle{°}", + # data_list=[Per_space], + # legend_list=legend_list, + # save_path=join(save_path, "plot_Per_angle"), + # is_show_fig=is_show_fig, + # linestyles=["solid", "dashed"], + # **dict_2D + # ) + # out.mag.Per.plot_2D_Data( + # "time", + # data_list=[Per_time], + # legend_list=legend_list, + # save_path=join(save_path, "plot_Per_time"), + # is_show_fig=is_show_fig, + # **dict_2D + # ) + # out.mag.B.plot_2D_Data( + # "angle{°}", + # component_list=["radial"], + # data_list=[B_space], + # legend_list=legend_list, + # save_path=join(save_path, "plot_Br_angle"), + # is_show_fig=is_show_fig, + # **dict_2D + # ) + + return out + + +if __name__ == "__main__": + + out = test_EEC_ELUT_SCIM_001() diff --git a/Tests/Validation/Electrical/test_skin_effect.py b/Tests/Validation/Electrical/test_skin_effect.py index 880cb4181..80235951f 100644 --- a/Tests/Validation/Electrical/test_skin_effect.py +++ b/Tests/Validation/Electrical/test_skin_effect.py @@ -89,5 +89,7 @@ def test_skin_effect(): # from Yang et al, 2013 # assert_almost_equal(out.elec.Tem_av_ref, 81.81, decimal=1) # assert_almost_equal(out2.mag.Tem_av, 81.70, decimal=1) + + if __name__ == "__main__": test_skin_effect() diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 4abe7afb5..c70fb52bb 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -1255,7 +1255,7 @@ "desc": "Abstract class for Electrical Look Up Table (ELUT)", "is_internal": false, "methods": [ - "get_parameters" + "get_param_dict" ], "mother": "", "name": "ELUT", @@ -1288,24 +1288,6 @@ "type": "float", "unit": "degC", "value": 20 - }, - { - "desc": "Stator winding Resistance Skin Effect factor function of frequency", - "max": "", - "min": "", - "name": "K_RSE_sta", - "type": "ndarray", - "unit": "", - "value": null - }, - { - "desc": "Stator winding Inductance Skin Effect factor function of frequency", - "max": "", - "min": "", - "name": "K_ISE_sta", - "type": "ndarray", - "unit": "", - "value": null } ] }, @@ -1320,7 +1302,7 @@ "desc": "ELUT class for PMSM", "is_internal": false, "methods": [ - "get_parameters", + "get_param_dict", "get_Lq", "get_bemf", "get_Ld", @@ -1402,7 +1384,7 @@ "desc": "ELUT class for SCIM", "is_internal": false, "methods": [ - "get_parameters", + "get_param_dict", "get_Lm", "comp_Lm_from_Phim", "import_from_data" @@ -1413,7 +1395,7 @@ "path": "pyleecan/Generator/ClassesRef/Simulation/ELUT_SCIM.csv", "properties": [ { - "desc": "Stator winding flux look-up table: list of DataTime objects whose (Id,Iq) is given by Idq list", + "desc": "Magnetizing flux for a given magnetizing current I_m", "max": "", "min": "", "name": "Phi_m", @@ -1421,6 +1403,15 @@ "unit": "Wb", "value": null }, + { + "desc": "Stator magnetizing current", + "max": "", + "min": "", + "name": "I_m", + "type": "ndarray", + "unit": "Arms", + "value": null + }, { "desc": "Rotor bar average temperature at which Phi_m is given", "max": "", @@ -1431,21 +1422,21 @@ "value": 20 }, { - "desc": "Rotor winding Resistance Skin Effect factor function of frequency", + "desc": "DC rotor winding resistance at Tsta_ref ", "max": "", "min": "", - "name": "K_RSE_rot", - "type": "ndarray", - "unit": "", + "name": "Rr", + "type": "float", + "unit": "Ohm", "value": null }, { - "desc": "Rotor winding Inductance Skin Effect factor function of frequency", + "desc": "Rotor winding leakage inductance", "max": "", "min": "", - "name": "K_ISE_rot", - "type": "ndarray", - "unit": "", + "name": "Lr", + "type": "float", + "unit": "H", "value": null } ] @@ -4055,7 +4046,8 @@ "InputCurrent", "InputElec", "InputFlux", - "InputForce" + "InputForce", + "InputVoltage" ], "desc": "Starting data of the simulation", "is_internal": false, @@ -4140,7 +4132,7 @@ "set_Id_Iq", "set_OP_from_array" ], - "mother": "Input", + "mother": "InputVoltage", "name": "InputCurrent", "package": "Simulation", "path": "pyleecan/Generator/ClassesRef/Simulation/InputCurrent.csv", @@ -4163,42 +4155,6 @@ "unit": "A", "value": null }, - { - "desc": "Rotor angular position as a function of time (if None computed according to Nr) to import", - "max": "", - "min": "", - "name": "angle_rotor", - "type": "Import", - "unit": "rad", - "value": null - }, - { - "desc": "Rotation direction of the rotor 1 trigo, -1 clockwise", - "max": "1", - "min": "-1", - "name": "rot_dir", - "type": "float", - "unit": "-", - "value": null - }, - { - "desc": "Initial angular position of the rotor at t=0", - "max": "", - "min": "", - "name": "angle_rotor_initial", - "type": "float", - "unit": "", - "value": 0 - }, - { - "desc": "Theorical Average Electromagnetic torque", - "max": "", - "min": "", - "name": "Tem_av_ref", - "type": "float", - "unit": "N.m", - "value": null - }, { "desc": "d-axis current RMS magnitude", "max": "", @@ -4216,15 +4172,6 @@ "type": "float", "unit": "A", "value": null - }, - { - "desc": "electrical frequency", - "max": "", - "min": "", - "name": "felec", - "type": "float", - "unit": "Hz", - "value": null } ] }, @@ -4418,6 +4365,111 @@ } ] }, + "InputVoltage": { + "constants": [ + { + "name": "VERSION", + "value": "1" + } + ], + "daughters": [ + "InputCurrent" + ], + "desc": "Input to start the electrical module with voltage input", + "is_internal": false, + "methods": [ + "gen_input", + "set_OP_from_array", + "comp_felec" + ], + "mother": "Input", + "name": "InputVoltage", + "package": "Simulation", + "path": "pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv", + "properties": [ + { + "desc": "Rotor angular position as a function of time (if None computed according to Nr) to import", + "max": "", + "min": "", + "name": "angle_rotor", + "type": "Import", + "unit": "rad", + "value": null + }, + { + "desc": "Rotation direction of the rotor 1 trigo, -1 clockwise", + "max": "1", + "min": "-1", + "name": "rot_dir", + "type": "float", + "unit": "-", + "value": null + }, + { + "desc": "Initial angular position of the rotor at t=0", + "max": "", + "min": "", + "name": "angle_rotor_initial", + "type": "float", + "unit": "", + "value": 0 + }, + { + "desc": "Theorical Average Electromagnetic torque", + "max": "", + "min": "", + "name": "Tem_av_ref", + "type": "float", + "unit": "N.m", + "value": null + }, + { + "desc": "d-axis current RMS magnitude (phase to neutral)", + "max": "", + "min": "", + "name": "Ud_ref", + "type": "float", + "unit": "Vrms", + "value": null + }, + { + "desc": "q-axis current RMS magnitude (phase to neutral)", + "max": "", + "min": "", + "name": "Uq_ref", + "type": "float", + "unit": "Vrms", + "value": null + }, + { + "desc": "electrical frequency", + "max": "", + "min": "", + "name": "felec", + "type": "float", + "unit": "Hz", + "value": null + }, + { + "desc": "Rotor mechanical slip", + "max": "", + "min": "", + "name": "slip_ref", + "type": "float", + "unit": "", + "value": 0 + }, + { + "desc": "stator voltage (phase to neutral)", + "max": "", + "min": "", + "name": "U0_ref", + "type": "float", + "unit": "Vrms", + "value": null + } + ] + }, "Interpolation": { "constants": [ { @@ -7877,21 +7929,21 @@ "value": null }, { - "desc": "Electrical time vector (no symmetry)", + "desc": "d-axis current rms value", "max": "", "min": "", "name": "Id_ref", "type": "float", - "unit": "A", + "unit": "Arms", "value": null }, { - "desc": "q-axis current magnitude", + "desc": "q-axis current rms value", "max": "", "min": "", "name": "Iq_ref", "type": "float", - "unit": "A", + "unit": "Arms", "value": null }, { @@ -7904,21 +7956,21 @@ "value": null }, { - "desc": "d-axis voltage magnitude", + "desc": "d-axis voltage rms value", "max": "", "min": "", "name": "Ud_ref", "type": "float", - "unit": "V", + "unit": "Vrms", "value": null }, { - "desc": "q-axis voltage magnitude", + "desc": "q-axis voltage rms value", "max": "", "min": "", "name": "Uq_ref", "type": "float", - "unit": "V", + "unit": "Vrms", "value": null }, { @@ -7956,6 +8008,24 @@ "type": "OutInternal", "unit": "-", "value": null + }, + { + "desc": "Rotor mechanical slip", + "max": "", + "min": "", + "name": "slip_ref", + "type": "float", + "unit": "", + "value": 0 + }, + { + "desc": "stator voltage (phase to neutral)", + "max": "", + "min": "", + "name": "U0_ref", + "type": "float", + "unit": "Vrms", + "value": null } ] }, diff --git a/pyleecan/Classes/ELUT.py b/pyleecan/Classes/ELUT.py index 44d6e8a6c..43c7e3038 100644 --- a/pyleecan/Classes/ELUT.py +++ b/pyleecan/Classes/ELUT.py @@ -7,7 +7,7 @@ from os import linesep from sys import getsizeof from logging import getLogger -from ._check import set_array, check_var, raise_ +from ._check import check_var, raise_ from ..Functions.get_logger import get_logger from ..Functions.save import save from ..Functions.copy import copy @@ -18,12 +18,11 @@ # Import all class method # Try/catch to remove unnecessary dependencies in unused method try: - from ..Methods.Simulation.ELUT.get_parameters import get_parameters + from ..Methods.Simulation.ELUT.get_param_dict import get_param_dict except ImportError as error: - get_parameters = error + get_param_dict = error -from numpy import array, array_equal from ._check import InitUnKnowClassError @@ -32,33 +31,24 @@ class ELUT(FrozenClass): VERSION = 1 - # cf Methods.Simulation.ELUT.get_parameters - if isinstance(get_parameters, ImportError): - get_parameters = property( + # cf Methods.Simulation.ELUT.get_param_dict + if isinstance(get_param_dict, ImportError): + get_param_dict = property( fget=lambda x: raise_( ImportError( - "Can't use ELUT method get_parameters: " + str(get_parameters) + "Can't use ELUT method get_param_dict: " + str(get_param_dict) ) ) ) else: - get_parameters = get_parameters + get_param_dict = get_param_dict # save and copy methods are available in all object save = save copy = copy # get_logger method is available in all object get_logger = get_logger - def __init__( - self, - Rs=None, - Ls=None, - Tsta_ref=20, - K_RSE_sta=None, - K_ISE_sta=None, - init_dict=None, - init_str=None, - ): + def __init__(self, Rs=None, Ls=None, Tsta_ref=20, init_dict=None, init_str=None): """Constructor of the class. Can be use in three ways : - __init__ (arg1 = 1, arg3 = 5) every parameters have name and default values for pyleecan type, -1 will call the default constructor @@ -80,17 +70,11 @@ def __init__( Ls = init_dict["Ls"] if "Tsta_ref" in list(init_dict.keys()): Tsta_ref = init_dict["Tsta_ref"] - if "K_RSE_sta" in list(init_dict.keys()): - K_RSE_sta = init_dict["K_RSE_sta"] - if "K_ISE_sta" in list(init_dict.keys()): - K_ISE_sta = init_dict["K_ISE_sta"] # Set the properties (value check and convertion are done in setter) self.parent = None self.Rs = Rs self.Ls = Ls self.Tsta_ref = Tsta_ref - self.K_RSE_sta = K_RSE_sta - self.K_ISE_sta = K_ISE_sta # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -106,20 +90,6 @@ def __str__(self): ELUT_str += "Rs = " + str(self.Rs) + linesep ELUT_str += "Ls = " + str(self.Ls) + linesep ELUT_str += "Tsta_ref = " + str(self.Tsta_ref) + linesep - ELUT_str += ( - "K_RSE_sta = " - + linesep - + str(self.K_RSE_sta).replace(linesep, linesep + "\t") - + linesep - + linesep - ) - ELUT_str += ( - "K_ISE_sta = " - + linesep - + str(self.K_ISE_sta).replace(linesep, linesep + "\t") - + linesep - + linesep - ) return ELUT_str def __eq__(self, other): @@ -133,10 +103,6 @@ def __eq__(self, other): return False if other.Tsta_ref != self.Tsta_ref: return False - if not array_equal(other.K_RSE_sta, self.K_RSE_sta): - return False - if not array_equal(other.K_ISE_sta, self.K_ISE_sta): - return False return True def compare(self, other, name="self", ignore_list=None): @@ -153,10 +119,6 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".Ls") if other._Tsta_ref != self._Tsta_ref: diff_list.append(name + ".Tsta_ref") - if not array_equal(other.K_RSE_sta, self.K_RSE_sta): - diff_list.append(name + ".K_RSE_sta") - if not array_equal(other.K_ISE_sta, self.K_ISE_sta): - diff_list.append(name + ".K_ISE_sta") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -168,8 +130,6 @@ def __sizeof__(self): S += getsizeof(self.Rs) S += getsizeof(self.Ls) S += getsizeof(self.Tsta_ref) - S += getsizeof(self.K_RSE_sta) - S += getsizeof(self.K_ISE_sta) return S def as_dict(self, **kwargs): @@ -183,14 +143,6 @@ def as_dict(self, **kwargs): ELUT_dict["Rs"] = self.Rs ELUT_dict["Ls"] = self.Ls ELUT_dict["Tsta_ref"] = self.Tsta_ref - if self.K_RSE_sta is None: - ELUT_dict["K_RSE_sta"] = None - else: - ELUT_dict["K_RSE_sta"] = self.K_RSE_sta.tolist() - if self.K_ISE_sta is None: - ELUT_dict["K_ISE_sta"] = None - else: - ELUT_dict["K_ISE_sta"] = self.K_ISE_sta.tolist() # The class name is added to the dict for deserialisation purpose ELUT_dict["__class__"] = "ELUT" return ELUT_dict @@ -201,8 +153,6 @@ def _set_None(self): self.Rs = None self.Ls = None self.Tsta_ref = None - self.K_RSE_sta = None - self.K_ISE_sta = None def _get_Rs(self): """getter of Rs""" @@ -257,53 +207,3 @@ def _set_Tsta_ref(self, value): :Type: float """, ) - - def _get_K_RSE_sta(self): - """getter of K_RSE_sta""" - return self._K_RSE_sta - - def _set_K_RSE_sta(self, value): - """setter of K_RSE_sta""" - if type(value) is int and value == -1: - value = array([]) - elif type(value) is list: - try: - value = array(value) - except: - pass - check_var("K_RSE_sta", value, "ndarray") - self._K_RSE_sta = value - - K_RSE_sta = property( - fget=_get_K_RSE_sta, - fset=_set_K_RSE_sta, - doc=u"""Stator winding Resistance Skin Effect factor function of frequency - - :Type: ndarray - """, - ) - - def _get_K_ISE_sta(self): - """getter of K_ISE_sta""" - return self._K_ISE_sta - - def _set_K_ISE_sta(self, value): - """setter of K_ISE_sta""" - if type(value) is int and value == -1: - value = array([]) - elif type(value) is list: - try: - value = array(value) - except: - pass - check_var("K_ISE_sta", value, "ndarray") - self._K_ISE_sta = value - - K_ISE_sta = property( - fget=_get_K_ISE_sta, - fset=_set_K_ISE_sta, - doc=u"""Stator winding Inductance Skin Effect factor function of frequency - - :Type: ndarray - """, - ) diff --git a/pyleecan/Classes/ELUT_PMSM.py b/pyleecan/Classes/ELUT_PMSM.py index 5acefd7f4..7f9909e1f 100644 --- a/pyleecan/Classes/ELUT_PMSM.py +++ b/pyleecan/Classes/ELUT_PMSM.py @@ -18,9 +18,9 @@ # Import all class method # Try/catch to remove unnecessary dependencies in unused method try: - from ..Methods.Simulation.ELUT_PMSM.get_parameters import get_parameters + from ..Methods.Simulation.ELUT_PMSM.get_param_dict import get_param_dict except ImportError as error: - get_parameters = error + get_param_dict = error try: from ..Methods.Simulation.ELUT_PMSM.get_Lq import get_Lq @@ -77,17 +77,17 @@ class ELUT_PMSM(ELUT): VERSION = 1 # Check ImportError to remove unnecessary dependencies in unused method - # cf Methods.Simulation.ELUT_PMSM.get_parameters - if isinstance(get_parameters, ImportError): - get_parameters = property( + # cf Methods.Simulation.ELUT_PMSM.get_param_dict + if isinstance(get_param_dict, ImportError): + get_param_dict = property( fget=lambda x: raise_( ImportError( - "Can't use ELUT_PMSM method get_parameters: " + str(get_parameters) + "Can't use ELUT_PMSM method get_param_dict: " + str(get_param_dict) ) ) ) else: - get_parameters = get_parameters + get_param_dict = get_param_dict # cf Methods.Simulation.ELUT_PMSM.get_Lq if isinstance(get_Lq, ImportError): get_Lq = property( @@ -186,8 +186,6 @@ def __init__( Rs=None, Ls=None, Tsta_ref=20, - K_RSE_sta=None, - K_ISE_sta=None, init_dict=None, init_str=None, ): @@ -224,10 +222,6 @@ def __init__( Ls = init_dict["Ls"] if "Tsta_ref" in list(init_dict.keys()): Tsta_ref = init_dict["Tsta_ref"] - if "K_RSE_sta" in list(init_dict.keys()): - K_RSE_sta = init_dict["K_RSE_sta"] - if "K_ISE_sta" in list(init_dict.keys()): - K_ISE_sta = init_dict["K_ISE_sta"] # Set the properties (value check and convertion are done in setter) self.Phi_dqh = Phi_dqh self.I_dqh = I_dqh @@ -236,9 +230,7 @@ def __init__( self.E_dqh = E_dqh self.orders_dqh = orders_dqh # Call ELUT init - super(ELUT_PMSM, self).__init__( - Rs=Rs, Ls=Ls, Tsta_ref=Tsta_ref, K_RSE_sta=K_RSE_sta, K_ISE_sta=K_ISE_sta - ) + super(ELUT_PMSM, self).__init__(Rs=Rs, Ls=Ls, Tsta_ref=Tsta_ref) # The class is frozen (in ELUT init), for now it's impossible to # add new properties diff --git a/pyleecan/Classes/ELUT_SCIM.py b/pyleecan/Classes/ELUT_SCIM.py index 4f413361b..2f65ad46c 100644 --- a/pyleecan/Classes/ELUT_SCIM.py +++ b/pyleecan/Classes/ELUT_SCIM.py @@ -18,9 +18,9 @@ # Import all class method # Try/catch to remove unnecessary dependencies in unused method try: - from ..Methods.Simulation.ELUT_SCIM.get_parameters import get_parameters + from ..Methods.Simulation.ELUT_SCIM.get_param_dict import get_param_dict except ImportError as error: - get_parameters = error + get_param_dict = error try: from ..Methods.Simulation.ELUT_SCIM.get_Lm import get_Lm @@ -48,17 +48,17 @@ class ELUT_SCIM(ELUT): VERSION = 1 # Check ImportError to remove unnecessary dependencies in unused method - # cf Methods.Simulation.ELUT_SCIM.get_parameters - if isinstance(get_parameters, ImportError): - get_parameters = property( + # cf Methods.Simulation.ELUT_SCIM.get_param_dict + if isinstance(get_param_dict, ImportError): + get_param_dict = property( fget=lambda x: raise_( ImportError( - "Can't use ELUT_SCIM method get_parameters: " + str(get_parameters) + "Can't use ELUT_SCIM method get_param_dict: " + str(get_param_dict) ) ) ) else: - get_parameters = get_parameters + get_param_dict = get_param_dict # cf Methods.Simulation.ELUT_SCIM.get_Lm if isinstance(get_Lm, ImportError): get_Lm = property( @@ -101,14 +101,13 @@ class ELUT_SCIM(ELUT): def __init__( self, Phi_m=None, + I_m=None, Trot_ref=20, - K_RSE_rot=None, - K_ISE_rot=None, + Rr=None, + Lr=None, Rs=None, Ls=None, Tsta_ref=20, - K_RSE_sta=None, - K_ISE_sta=None, init_dict=None, init_str=None, ): @@ -129,31 +128,28 @@ def __init__( # Overwrite default value with init_dict content if "Phi_m" in list(init_dict.keys()): Phi_m = init_dict["Phi_m"] + if "I_m" in list(init_dict.keys()): + I_m = init_dict["I_m"] if "Trot_ref" in list(init_dict.keys()): Trot_ref = init_dict["Trot_ref"] - if "K_RSE_rot" in list(init_dict.keys()): - K_RSE_rot = init_dict["K_RSE_rot"] - if "K_ISE_rot" in list(init_dict.keys()): - K_ISE_rot = init_dict["K_ISE_rot"] + if "Rr" in list(init_dict.keys()): + Rr = init_dict["Rr"] + if "Lr" in list(init_dict.keys()): + Lr = init_dict["Lr"] if "Rs" in list(init_dict.keys()): Rs = init_dict["Rs"] if "Ls" in list(init_dict.keys()): Ls = init_dict["Ls"] if "Tsta_ref" in list(init_dict.keys()): Tsta_ref = init_dict["Tsta_ref"] - if "K_RSE_sta" in list(init_dict.keys()): - K_RSE_sta = init_dict["K_RSE_sta"] - if "K_ISE_sta" in list(init_dict.keys()): - K_ISE_sta = init_dict["K_ISE_sta"] # Set the properties (value check and convertion are done in setter) self.Phi_m = Phi_m + self.I_m = I_m self.Trot_ref = Trot_ref - self.K_RSE_rot = K_RSE_rot - self.K_ISE_rot = K_ISE_rot + self.Rr = Rr + self.Lr = Lr # Call ELUT init - super(ELUT_SCIM, self).__init__( - Rs=Rs, Ls=Ls, Tsta_ref=Tsta_ref, K_RSE_sta=K_RSE_sta, K_ISE_sta=K_ISE_sta - ) + super(ELUT_SCIM, self).__init__(Rs=Rs, Ls=Ls, Tsta_ref=Tsta_ref) # The class is frozen (in ELUT init), for now it's impossible to # add new properties @@ -170,21 +166,16 @@ def __str__(self): + linesep + linesep ) - ELUT_SCIM_str += "Trot_ref = " + str(self.Trot_ref) + linesep ELUT_SCIM_str += ( - "K_RSE_rot = " + "I_m = " + linesep - + str(self.K_RSE_rot).replace(linesep, linesep + "\t") - + linesep - + linesep - ) - ELUT_SCIM_str += ( - "K_ISE_rot = " - + linesep - + str(self.K_ISE_rot).replace(linesep, linesep + "\t") + + str(self.I_m).replace(linesep, linesep + "\t") + linesep + linesep ) + ELUT_SCIM_str += "Trot_ref = " + str(self.Trot_ref) + linesep + ELUT_SCIM_str += "Rr = " + str(self.Rr) + linesep + ELUT_SCIM_str += "Lr = " + str(self.Lr) + linesep return ELUT_SCIM_str def __eq__(self, other): @@ -198,11 +189,13 @@ def __eq__(self, other): return False if not array_equal(other.Phi_m, self.Phi_m): return False + if not array_equal(other.I_m, self.I_m): + return False if other.Trot_ref != self.Trot_ref: return False - if not array_equal(other.K_RSE_rot, self.K_RSE_rot): + if other.Rr != self.Rr: return False - if not array_equal(other.K_ISE_rot, self.K_ISE_rot): + if other.Lr != self.Lr: return False return True @@ -219,12 +212,14 @@ def compare(self, other, name="self", ignore_list=None): diff_list.extend(super(ELUT_SCIM, self).compare(other, name=name)) if not array_equal(other.Phi_m, self.Phi_m): diff_list.append(name + ".Phi_m") + if not array_equal(other.I_m, self.I_m): + diff_list.append(name + ".I_m") if other._Trot_ref != self._Trot_ref: diff_list.append(name + ".Trot_ref") - if not array_equal(other.K_RSE_rot, self.K_RSE_rot): - diff_list.append(name + ".K_RSE_rot") - if not array_equal(other.K_ISE_rot, self.K_ISE_rot): - diff_list.append(name + ".K_ISE_rot") + if other._Rr != self._Rr: + diff_list.append(name + ".Rr") + if other._Lr != self._Lr: + diff_list.append(name + ".Lr") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -237,9 +232,10 @@ def __sizeof__(self): # Get size of the properties inherited from ELUT S += super(ELUT_SCIM, self).__sizeof__() S += getsizeof(self.Phi_m) + S += getsizeof(self.I_m) S += getsizeof(self.Trot_ref) - S += getsizeof(self.K_RSE_rot) - S += getsizeof(self.K_ISE_rot) + S += getsizeof(self.Rr) + S += getsizeof(self.Lr) return S def as_dict(self, **kwargs): @@ -255,15 +251,13 @@ def as_dict(self, **kwargs): ELUT_SCIM_dict["Phi_m"] = None else: ELUT_SCIM_dict["Phi_m"] = self.Phi_m.tolist() - ELUT_SCIM_dict["Trot_ref"] = self.Trot_ref - if self.K_RSE_rot is None: - ELUT_SCIM_dict["K_RSE_rot"] = None - else: - ELUT_SCIM_dict["K_RSE_rot"] = self.K_RSE_rot.tolist() - if self.K_ISE_rot is None: - ELUT_SCIM_dict["K_ISE_rot"] = None + if self.I_m is None: + ELUT_SCIM_dict["I_m"] = None else: - ELUT_SCIM_dict["K_ISE_rot"] = self.K_ISE_rot.tolist() + ELUT_SCIM_dict["I_m"] = self.I_m.tolist() + ELUT_SCIM_dict["Trot_ref"] = self.Trot_ref + ELUT_SCIM_dict["Rr"] = self.Rr + ELUT_SCIM_dict["Lr"] = self.Lr # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name ELUT_SCIM_dict["__class__"] = "ELUT_SCIM" @@ -273,9 +267,10 @@ def _set_None(self): """Set all the properties to None (except pyleecan object)""" self.Phi_m = None + self.I_m = None self.Trot_ref = None - self.K_RSE_rot = None - self.K_ISE_rot = None + self.Rr = None + self.Lr = None # Set to None the properties inherited from ELUT super(ELUT_SCIM, self)._set_None() @@ -298,7 +293,32 @@ def _set_Phi_m(self, value): Phi_m = property( fget=_get_Phi_m, fset=_set_Phi_m, - doc=u"""Stator winding flux look-up table: list of DataTime objects whose (Id,Iq) is given by Idq list + doc=u"""Magnetizing flux for a given magnetizing current I_m + + :Type: ndarray + """, + ) + + def _get_I_m(self): + """getter of I_m""" + return self._I_m + + def _set_I_m(self, value): + """setter of I_m""" + if type(value) is int and value == -1: + value = array([]) + elif type(value) is list: + try: + value = array(value) + except: + pass + check_var("I_m", value, "ndarray") + self._I_m = value + + I_m = property( + fget=_get_I_m, + fset=_set_I_m, + doc=u"""Stator magnetizing current :Type: ndarray """, @@ -322,52 +342,38 @@ def _set_Trot_ref(self, value): """, ) - def _get_K_RSE_rot(self): - """getter of K_RSE_rot""" - return self._K_RSE_rot + def _get_Rr(self): + """getter of Rr""" + return self._Rr - def _set_K_RSE_rot(self, value): - """setter of K_RSE_rot""" - if type(value) is int and value == -1: - value = array([]) - elif type(value) is list: - try: - value = array(value) - except: - pass - check_var("K_RSE_rot", value, "ndarray") - self._K_RSE_rot = value + def _set_Rr(self, value): + """setter of Rr""" + check_var("Rr", value, "float") + self._Rr = value - K_RSE_rot = property( - fget=_get_K_RSE_rot, - fset=_set_K_RSE_rot, - doc=u"""Rotor winding Resistance Skin Effect factor function of frequency + Rr = property( + fget=_get_Rr, + fset=_set_Rr, + doc=u"""DC rotor winding resistance at Tsta_ref - :Type: ndarray + :Type: float """, ) - def _get_K_ISE_rot(self): - """getter of K_ISE_rot""" - return self._K_ISE_rot + def _get_Lr(self): + """getter of Lr""" + return self._Lr - def _set_K_ISE_rot(self, value): - """setter of K_ISE_rot""" - if type(value) is int and value == -1: - value = array([]) - elif type(value) is list: - try: - value = array(value) - except: - pass - check_var("K_ISE_rot", value, "ndarray") - self._K_ISE_rot = value + def _set_Lr(self, value): + """setter of Lr""" + check_var("Lr", value, "float") + self._Lr = value - K_ISE_rot = property( - fget=_get_K_ISE_rot, - fset=_set_K_ISE_rot, - doc=u"""Rotor winding Inductance Skin Effect factor function of frequency + Lr = property( + fget=_get_Lr, + fset=_set_Lr, + doc=u"""Rotor winding leakage inductance - :Type: ndarray + :Type: float """, ) diff --git a/pyleecan/Classes/InputCurrent.py b/pyleecan/Classes/InputCurrent.py index bb7962dc8..00516030d 100644 --- a/pyleecan/Classes/InputCurrent.py +++ b/pyleecan/Classes/InputCurrent.py @@ -13,7 +13,7 @@ from ..Functions.copy import copy from ..Functions.load import load_init_dict from ..Functions.Load.import_class import import_class -from .Input import Input +from .InputVoltage import InputVoltage # Import all class method # Try/catch to remove unnecessary dependencies in unused method @@ -41,7 +41,7 @@ from .Import import Import -class InputCurrent(Input): +class InputCurrent(InputVoltage): """Input to skip the electrical module and start with the magnetic one""" VERSION = 1 @@ -91,13 +91,17 @@ def __init__( self, Is=None, Ir=None, + Id_ref=None, + Iq_ref=None, angle_rotor=None, rot_dir=None, angle_rotor_initial=0, Tem_av_ref=None, - Id_ref=None, - Iq_ref=None, + Ud_ref=None, + Uq_ref=None, felec=None, + slip_ref=0, + U0_ref=None, time=None, angle=None, Nt_tot=2048, @@ -126,6 +130,10 @@ def __init__( Is = init_dict["Is"] if "Ir" in list(init_dict.keys()): Ir = init_dict["Ir"] + if "Id_ref" in list(init_dict.keys()): + Id_ref = init_dict["Id_ref"] + if "Iq_ref" in list(init_dict.keys()): + Iq_ref = init_dict["Iq_ref"] if "angle_rotor" in list(init_dict.keys()): angle_rotor = init_dict["angle_rotor"] if "rot_dir" in list(init_dict.keys()): @@ -134,12 +142,16 @@ def __init__( angle_rotor_initial = init_dict["angle_rotor_initial"] if "Tem_av_ref" in list(init_dict.keys()): Tem_av_ref = init_dict["Tem_av_ref"] - if "Id_ref" in list(init_dict.keys()): - Id_ref = init_dict["Id_ref"] - if "Iq_ref" in list(init_dict.keys()): - Iq_ref = init_dict["Iq_ref"] + if "Ud_ref" in list(init_dict.keys()): + Ud_ref = init_dict["Ud_ref"] + if "Uq_ref" in list(init_dict.keys()): + Uq_ref = init_dict["Uq_ref"] if "felec" in list(init_dict.keys()): felec = init_dict["felec"] + if "slip_ref" in list(init_dict.keys()): + slip_ref = init_dict["slip_ref"] + if "U0_ref" in list(init_dict.keys()): + U0_ref = init_dict["U0_ref"] if "time" in list(init_dict.keys()): time = init_dict["time"] if "angle" in list(init_dict.keys()): @@ -155,25 +167,34 @@ def __init__( # Set the properties (value check and convertion are done in setter) self.Is = Is self.Ir = Ir - self.angle_rotor = angle_rotor - self.rot_dir = rot_dir - self.angle_rotor_initial = angle_rotor_initial - self.Tem_av_ref = Tem_av_ref self.Id_ref = Id_ref self.Iq_ref = Iq_ref - self.felec = felec - # Call Input init + # Call InputVoltage init super(InputCurrent, self).__init__( - time=time, angle=angle, Nt_tot=Nt_tot, Nrev=Nrev, Na_tot=Na_tot, N0=N0 + angle_rotor=angle_rotor, + rot_dir=rot_dir, + angle_rotor_initial=angle_rotor_initial, + Tem_av_ref=Tem_av_ref, + Ud_ref=Ud_ref, + Uq_ref=Uq_ref, + felec=felec, + slip_ref=slip_ref, + U0_ref=U0_ref, + time=time, + angle=angle, + Nt_tot=Nt_tot, + Nrev=Nrev, + Na_tot=Na_tot, + N0=N0, ) - # The class is frozen (in Input init), for now it's impossible to + # The class is frozen (in InputVoltage init), for now it's impossible to # add new properties def __str__(self): """Convert this object in a readeable string (for print)""" InputCurrent_str = "" - # Get the properties inherited from Input + # Get the properties inherited from InputVoltage InputCurrent_str += super(InputCurrent, self).__str__() if self.Is is not None: tmp = self.Is.__str__().replace(linesep, linesep + "\t").rstrip("\t") @@ -185,21 +206,8 @@ def __str__(self): InputCurrent_str += "Ir = " + tmp else: InputCurrent_str += "Ir = None" + linesep + linesep - if self.angle_rotor is not None: - tmp = ( - self.angle_rotor.__str__().replace(linesep, linesep + "\t").rstrip("\t") - ) - InputCurrent_str += "angle_rotor = " + tmp - else: - InputCurrent_str += "angle_rotor = None" + linesep + linesep - InputCurrent_str += "rot_dir = " + str(self.rot_dir) + linesep - InputCurrent_str += ( - "angle_rotor_initial = " + str(self.angle_rotor_initial) + linesep - ) - InputCurrent_str += "Tem_av_ref = " + str(self.Tem_av_ref) + linesep InputCurrent_str += "Id_ref = " + str(self.Id_ref) + linesep InputCurrent_str += "Iq_ref = " + str(self.Iq_ref) + linesep - InputCurrent_str += "felec = " + str(self.felec) + linesep return InputCurrent_str def __eq__(self, other): @@ -208,27 +216,17 @@ def __eq__(self, other): if type(other) != type(self): return False - # Check the properties inherited from Input + # Check the properties inherited from InputVoltage if not super(InputCurrent, self).__eq__(other): return False if other.Is != self.Is: return False if other.Ir != self.Ir: return False - if other.angle_rotor != self.angle_rotor: - return False - if other.rot_dir != self.rot_dir: - return False - if other.angle_rotor_initial != self.angle_rotor_initial: - return False - if other.Tem_av_ref != self.Tem_av_ref: - return False if other.Id_ref != self.Id_ref: return False if other.Iq_ref != self.Iq_ref: return False - if other.felec != self.felec: - return False return True def compare(self, other, name="self", ignore_list=None): @@ -240,7 +238,7 @@ def compare(self, other, name="self", ignore_list=None): return ["type(" + name + ")"] diff_list = list() - # Check the properties inherited from Input + # Check the properties inherited from InputVoltage diff_list.extend(super(InputCurrent, self).compare(other, name=name)) if (other.Is is None and self.Is is not None) or ( other.Is is not None and self.Is is None @@ -254,26 +252,10 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".Ir None mismatch") elif self.Ir is not None: diff_list.extend(self.Ir.compare(other.Ir, name=name + ".Ir")) - if (other.angle_rotor is None and self.angle_rotor is not None) or ( - other.angle_rotor is not None and self.angle_rotor is None - ): - diff_list.append(name + ".angle_rotor None mismatch") - elif self.angle_rotor is not None: - diff_list.extend( - self.angle_rotor.compare(other.angle_rotor, name=name + ".angle_rotor") - ) - if other._rot_dir != self._rot_dir: - diff_list.append(name + ".rot_dir") - if other._angle_rotor_initial != self._angle_rotor_initial: - diff_list.append(name + ".angle_rotor_initial") - if other._Tem_av_ref != self._Tem_av_ref: - diff_list.append(name + ".Tem_av_ref") if other._Id_ref != self._Id_ref: diff_list.append(name + ".Id_ref") if other._Iq_ref != self._Iq_ref: diff_list.append(name + ".Iq_ref") - if other._felec != self._felec: - diff_list.append(name + ".felec") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -283,17 +265,12 @@ def __sizeof__(self): S = 0 # Full size of the object - # Get size of the properties inherited from Input + # Get size of the properties inherited from InputVoltage S += super(InputCurrent, self).__sizeof__() S += getsizeof(self.Is) S += getsizeof(self.Ir) - S += getsizeof(self.angle_rotor) - S += getsizeof(self.rot_dir) - S += getsizeof(self.angle_rotor_initial) - S += getsizeof(self.Tem_av_ref) S += getsizeof(self.Id_ref) S += getsizeof(self.Iq_ref) - S += getsizeof(self.felec) return S def as_dict(self, **kwargs): @@ -303,7 +280,7 @@ def as_dict(self, **kwargs): and may prevent json serializability. """ - # Get the properties inherited from Input + # Get the properties inherited from InputVoltage InputCurrent_dict = super(InputCurrent, self).as_dict(**kwargs) if self.Is is None: InputCurrent_dict["Is"] = None @@ -313,16 +290,8 @@ def as_dict(self, **kwargs): InputCurrent_dict["Ir"] = None else: InputCurrent_dict["Ir"] = self.Ir.as_dict(**kwargs) - if self.angle_rotor is None: - InputCurrent_dict["angle_rotor"] = None - else: - InputCurrent_dict["angle_rotor"] = self.angle_rotor.as_dict(**kwargs) - InputCurrent_dict["rot_dir"] = self.rot_dir - InputCurrent_dict["angle_rotor_initial"] = self.angle_rotor_initial - InputCurrent_dict["Tem_av_ref"] = self.Tem_av_ref InputCurrent_dict["Id_ref"] = self.Id_ref InputCurrent_dict["Iq_ref"] = self.Iq_ref - InputCurrent_dict["felec"] = self.felec # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name InputCurrent_dict["__class__"] = "InputCurrent" @@ -335,15 +304,9 @@ def _set_None(self): self.Is._set_None() if self.Ir is not None: self.Ir._set_None() - if self.angle_rotor is not None: - self.angle_rotor._set_None() - self.rot_dir = None - self.angle_rotor_initial = None - self.Tem_av_ref = None self.Id_ref = None self.Iq_ref = None - self.felec = None - # Set to None the properties inherited from Input + # Set to None the properties inherited from InputVoltage super(InputCurrent, self)._set_None() def _get_Is(self): @@ -410,92 +373,6 @@ def _set_Ir(self, value): """, ) - def _get_angle_rotor(self): - """getter of angle_rotor""" - return self._angle_rotor - - def _set_angle_rotor(self, value): - """setter of angle_rotor""" - if isinstance(value, str): # Load from file - value = load_init_dict(value)[1] - if isinstance(value, dict) and "__class__" in value: - class_obj = import_class( - "pyleecan.Classes", value.get("__class__"), "angle_rotor" - ) - value = class_obj(init_dict=value) - elif type(value) is int and value == -1: # Default constructor - value = Import() - check_var("angle_rotor", value, "Import") - self._angle_rotor = value - - if self._angle_rotor is not None: - self._angle_rotor.parent = self - - angle_rotor = property( - fget=_get_angle_rotor, - fset=_set_angle_rotor, - doc=u"""Rotor angular position as a function of time (if None computed according to Nr) to import - - :Type: Import - """, - ) - - def _get_rot_dir(self): - """getter of rot_dir""" - return self._rot_dir - - def _set_rot_dir(self, value): - """setter of rot_dir""" - check_var("rot_dir", value, "float", Vmin=-1, Vmax=1) - self._rot_dir = value - - rot_dir = property( - fget=_get_rot_dir, - fset=_set_rot_dir, - doc=u"""Rotation direction of the rotor 1 trigo, -1 clockwise - - :Type: float - :min: -1 - :max: 1 - """, - ) - - def _get_angle_rotor_initial(self): - """getter of angle_rotor_initial""" - return self._angle_rotor_initial - - def _set_angle_rotor_initial(self, value): - """setter of angle_rotor_initial""" - check_var("angle_rotor_initial", value, "float") - self._angle_rotor_initial = value - - angle_rotor_initial = property( - fget=_get_angle_rotor_initial, - fset=_set_angle_rotor_initial, - doc=u"""Initial angular position of the rotor at t=0 - - :Type: float - """, - ) - - def _get_Tem_av_ref(self): - """getter of Tem_av_ref""" - return self._Tem_av_ref - - def _set_Tem_av_ref(self, value): - """setter of Tem_av_ref""" - check_var("Tem_av_ref", value, "float") - self._Tem_av_ref = value - - Tem_av_ref = property( - fget=_get_Tem_av_ref, - fset=_set_Tem_av_ref, - doc=u"""Theorical Average Electromagnetic torque - - :Type: float - """, - ) - def _get_Id_ref(self): """getter of Id_ref""" return self._Id_ref @@ -531,21 +408,3 @@ def _set_Iq_ref(self, value): :Type: float """, ) - - def _get_felec(self): - """getter of felec""" - return self._felec - - def _set_felec(self, value): - """setter of felec""" - check_var("felec", value, "float") - self._felec = value - - felec = property( - fget=_get_felec, - fset=_set_felec, - doc=u"""electrical frequency - - :Type: float - """, - ) diff --git a/pyleecan/Classes/InputVoltage.py b/pyleecan/Classes/InputVoltage.py new file mode 100644 index 000000000..a4b38ffcf --- /dev/null +++ b/pyleecan/Classes/InputVoltage.py @@ -0,0 +1,499 @@ +# -*- coding: utf-8 -*- +# File generated according to Generator/ClassesRef/Simulation/InputVoltage.csv +# WARNING! All changes made in this file will be lost! +"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Simulation/InputVoltage +""" + +from os import linesep +from sys import getsizeof +from logging import getLogger +from ._check import check_var, raise_ +from ..Functions.get_logger import get_logger +from ..Functions.save import save +from ..Functions.copy import copy +from ..Functions.load import load_init_dict +from ..Functions.Load.import_class import import_class +from .Input import Input + +# Import all class method +# Try/catch to remove unnecessary dependencies in unused method +try: + from ..Methods.Simulation.InputVoltage.gen_input import gen_input +except ImportError as error: + gen_input = error + +try: + from ..Methods.Simulation.InputVoltage.set_OP_from_array import set_OP_from_array +except ImportError as error: + set_OP_from_array = error + +try: + from ..Methods.Simulation.InputVoltage.comp_felec import comp_felec +except ImportError as error: + comp_felec = error + + +from ..Classes.ImportMatrixVal import ImportMatrixVal +from numpy import ndarray +from numpy import array, array_equal +from ._check import InitUnKnowClassError +from .Import import Import +from .ImportMatrix import ImportMatrix + + +class InputVoltage(Input): + """Input to start the electrical module with voltage input""" + + VERSION = 1 + + # Check ImportError to remove unnecessary dependencies in unused method + # cf Methods.Simulation.InputVoltage.gen_input + if isinstance(gen_input, ImportError): + gen_input = property( + fget=lambda x: raise_( + ImportError( + "Can't use InputVoltage method gen_input: " + str(gen_input) + ) + ) + ) + else: + gen_input = gen_input + # cf Methods.Simulation.InputVoltage.set_OP_from_array + if isinstance(set_OP_from_array, ImportError): + set_OP_from_array = property( + fget=lambda x: raise_( + ImportError( + "Can't use InputVoltage method set_OP_from_array: " + + str(set_OP_from_array) + ) + ) + ) + else: + set_OP_from_array = set_OP_from_array + # cf Methods.Simulation.InputVoltage.comp_felec + if isinstance(comp_felec, ImportError): + comp_felec = property( + fget=lambda x: raise_( + ImportError( + "Can't use InputVoltage method comp_felec: " + str(comp_felec) + ) + ) + ) + else: + comp_felec = comp_felec + # save and copy methods are available in all object + save = save + copy = copy + # get_logger method is available in all object + get_logger = get_logger + + def __init__( + self, + angle_rotor=None, + rot_dir=None, + angle_rotor_initial=0, + Tem_av_ref=None, + Ud_ref=None, + Uq_ref=None, + felec=None, + slip_ref=0, + U0_ref=None, + time=None, + angle=None, + Nt_tot=2048, + Nrev=1, + Na_tot=2048, + N0=None, + init_dict=None, + init_str=None, + ): + """Constructor of the class. Can be use in three ways : + - __init__ (arg1 = 1, arg3 = 5) every parameters have name and default values + for pyleecan type, -1 will call the default constructor + - __init__ (init_dict = d) d must be a dictionary with property names as keys + - __init__ (init_str = s) s must be a string + s is the file path to load + + ndarray or list can be given for Vector and Matrix + object or dict can be given for pyleecan Object""" + + if init_str is not None: # Load from a file + init_dict = load_init_dict(init_str)[1] + if init_dict is not None: # Initialisation by dict + assert type(init_dict) is dict + # Overwrite default value with init_dict content + if "angle_rotor" in list(init_dict.keys()): + angle_rotor = init_dict["angle_rotor"] + if "rot_dir" in list(init_dict.keys()): + rot_dir = init_dict["rot_dir"] + if "angle_rotor_initial" in list(init_dict.keys()): + angle_rotor_initial = init_dict["angle_rotor_initial"] + if "Tem_av_ref" in list(init_dict.keys()): + Tem_av_ref = init_dict["Tem_av_ref"] + if "Ud_ref" in list(init_dict.keys()): + Ud_ref = init_dict["Ud_ref"] + if "Uq_ref" in list(init_dict.keys()): + Uq_ref = init_dict["Uq_ref"] + if "felec" in list(init_dict.keys()): + felec = init_dict["felec"] + if "slip_ref" in list(init_dict.keys()): + slip_ref = init_dict["slip_ref"] + if "U0_ref" in list(init_dict.keys()): + U0_ref = init_dict["U0_ref"] + if "time" in list(init_dict.keys()): + time = init_dict["time"] + if "angle" in list(init_dict.keys()): + angle = init_dict["angle"] + if "Nt_tot" in list(init_dict.keys()): + Nt_tot = init_dict["Nt_tot"] + if "Nrev" in list(init_dict.keys()): + Nrev = init_dict["Nrev"] + if "Na_tot" in list(init_dict.keys()): + Na_tot = init_dict["Na_tot"] + if "N0" in list(init_dict.keys()): + N0 = init_dict["N0"] + # Set the properties (value check and convertion are done in setter) + self.angle_rotor = angle_rotor + self.rot_dir = rot_dir + self.angle_rotor_initial = angle_rotor_initial + self.Tem_av_ref = Tem_av_ref + self.Ud_ref = Ud_ref + self.Uq_ref = Uq_ref + self.felec = felec + self.slip_ref = slip_ref + self.U0_ref = U0_ref + # Call Input init + super(InputVoltage, self).__init__( + time=time, angle=angle, Nt_tot=Nt_tot, Nrev=Nrev, Na_tot=Na_tot, N0=N0 + ) + # The class is frozen (in Input init), for now it's impossible to + # add new properties + + def __str__(self): + """Convert this object in a readeable string (for print)""" + + InputVoltage_str = "" + # Get the properties inherited from Input + InputVoltage_str += super(InputVoltage, self).__str__() + if self.angle_rotor is not None: + tmp = ( + self.angle_rotor.__str__().replace(linesep, linesep + "\t").rstrip("\t") + ) + InputVoltage_str += "angle_rotor = " + tmp + else: + InputVoltage_str += "angle_rotor = None" + linesep + linesep + InputVoltage_str += "rot_dir = " + str(self.rot_dir) + linesep + InputVoltage_str += ( + "angle_rotor_initial = " + str(self.angle_rotor_initial) + linesep + ) + InputVoltage_str += "Tem_av_ref = " + str(self.Tem_av_ref) + linesep + InputVoltage_str += "Ud_ref = " + str(self.Ud_ref) + linesep + InputVoltage_str += "Uq_ref = " + str(self.Uq_ref) + linesep + InputVoltage_str += "felec = " + str(self.felec) + linesep + InputVoltage_str += "slip_ref = " + str(self.slip_ref) + linesep + InputVoltage_str += "U0_ref = " + str(self.U0_ref) + linesep + return InputVoltage_str + + def __eq__(self, other): + """Compare two objects (skip parent)""" + + if type(other) != type(self): + return False + + # Check the properties inherited from Input + if not super(InputVoltage, self).__eq__(other): + return False + if other.angle_rotor != self.angle_rotor: + return False + if other.rot_dir != self.rot_dir: + return False + if other.angle_rotor_initial != self.angle_rotor_initial: + return False + if other.Tem_av_ref != self.Tem_av_ref: + return False + if other.Ud_ref != self.Ud_ref: + return False + if other.Uq_ref != self.Uq_ref: + return False + if other.felec != self.felec: + return False + if other.slip_ref != self.slip_ref: + return False + if other.U0_ref != self.U0_ref: + return False + return True + + def compare(self, other, name="self", ignore_list=None): + """Compare two objects and return list of differences""" + + if ignore_list is None: + ignore_list = list() + if type(other) != type(self): + return ["type(" + name + ")"] + diff_list = list() + + # Check the properties inherited from Input + diff_list.extend(super(InputVoltage, self).compare(other, name=name)) + if (other.angle_rotor is None and self.angle_rotor is not None) or ( + other.angle_rotor is not None and self.angle_rotor is None + ): + diff_list.append(name + ".angle_rotor None mismatch") + elif self.angle_rotor is not None: + diff_list.extend( + self.angle_rotor.compare(other.angle_rotor, name=name + ".angle_rotor") + ) + if other._rot_dir != self._rot_dir: + diff_list.append(name + ".rot_dir") + if other._angle_rotor_initial != self._angle_rotor_initial: + diff_list.append(name + ".angle_rotor_initial") + if other._Tem_av_ref != self._Tem_av_ref: + diff_list.append(name + ".Tem_av_ref") + if other._Ud_ref != self._Ud_ref: + diff_list.append(name + ".Ud_ref") + if other._Uq_ref != self._Uq_ref: + diff_list.append(name + ".Uq_ref") + if other._felec != self._felec: + diff_list.append(name + ".felec") + if other._slip_ref != self._slip_ref: + diff_list.append(name + ".slip_ref") + if other._U0_ref != self._U0_ref: + diff_list.append(name + ".U0_ref") + # Filter ignore differences + diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) + return diff_list + + def __sizeof__(self): + """Return the size in memory of the object (including all subobject)""" + + S = 0 # Full size of the object + + # Get size of the properties inherited from Input + S += super(InputVoltage, self).__sizeof__() + S += getsizeof(self.angle_rotor) + S += getsizeof(self.rot_dir) + S += getsizeof(self.angle_rotor_initial) + S += getsizeof(self.Tem_av_ref) + S += getsizeof(self.Ud_ref) + S += getsizeof(self.Uq_ref) + S += getsizeof(self.felec) + S += getsizeof(self.slip_ref) + S += getsizeof(self.U0_ref) + return S + + def as_dict(self, **kwargs): + """ + Convert this object in a json serializable dict (can be use in __init__). + Optional keyword input parameter is for internal use only + and may prevent json serializability. + """ + + # Get the properties inherited from Input + InputVoltage_dict = super(InputVoltage, self).as_dict(**kwargs) + if self.angle_rotor is None: + InputVoltage_dict["angle_rotor"] = None + else: + InputVoltage_dict["angle_rotor"] = self.angle_rotor.as_dict(**kwargs) + InputVoltage_dict["rot_dir"] = self.rot_dir + InputVoltage_dict["angle_rotor_initial"] = self.angle_rotor_initial + InputVoltage_dict["Tem_av_ref"] = self.Tem_av_ref + InputVoltage_dict["Ud_ref"] = self.Ud_ref + InputVoltage_dict["Uq_ref"] = self.Uq_ref + InputVoltage_dict["felec"] = self.felec + InputVoltage_dict["slip_ref"] = self.slip_ref + InputVoltage_dict["U0_ref"] = self.U0_ref + # The class name is added to the dict for deserialisation purpose + # Overwrite the mother class name + InputVoltage_dict["__class__"] = "InputVoltage" + return InputVoltage_dict + + def _set_None(self): + """Set all the properties to None (except pyleecan object)""" + + if self.angle_rotor is not None: + self.angle_rotor._set_None() + self.rot_dir = None + self.angle_rotor_initial = None + self.Tem_av_ref = None + self.Ud_ref = None + self.Uq_ref = None + self.felec = None + self.slip_ref = None + self.U0_ref = None + # Set to None the properties inherited from Input + super(InputVoltage, self)._set_None() + + def _get_angle_rotor(self): + """getter of angle_rotor""" + return self._angle_rotor + + def _set_angle_rotor(self, value): + """setter of angle_rotor""" + if isinstance(value, str): # Load from file + value = load_init_dict(value)[1] + if isinstance(value, dict) and "__class__" in value: + class_obj = import_class( + "pyleecan.Classes", value.get("__class__"), "angle_rotor" + ) + value = class_obj(init_dict=value) + elif type(value) is int and value == -1: # Default constructor + value = Import() + check_var("angle_rotor", value, "Import") + self._angle_rotor = value + + if self._angle_rotor is not None: + self._angle_rotor.parent = self + + angle_rotor = property( + fget=_get_angle_rotor, + fset=_set_angle_rotor, + doc=u"""Rotor angular position as a function of time (if None computed according to Nr) to import + + :Type: Import + """, + ) + + def _get_rot_dir(self): + """getter of rot_dir""" + return self._rot_dir + + def _set_rot_dir(self, value): + """setter of rot_dir""" + check_var("rot_dir", value, "float", Vmin=-1, Vmax=1) + self._rot_dir = value + + rot_dir = property( + fget=_get_rot_dir, + fset=_set_rot_dir, + doc=u"""Rotation direction of the rotor 1 trigo, -1 clockwise + + :Type: float + :min: -1 + :max: 1 + """, + ) + + def _get_angle_rotor_initial(self): + """getter of angle_rotor_initial""" + return self._angle_rotor_initial + + def _set_angle_rotor_initial(self, value): + """setter of angle_rotor_initial""" + check_var("angle_rotor_initial", value, "float") + self._angle_rotor_initial = value + + angle_rotor_initial = property( + fget=_get_angle_rotor_initial, + fset=_set_angle_rotor_initial, + doc=u"""Initial angular position of the rotor at t=0 + + :Type: float + """, + ) + + def _get_Tem_av_ref(self): + """getter of Tem_av_ref""" + return self._Tem_av_ref + + def _set_Tem_av_ref(self, value): + """setter of Tem_av_ref""" + check_var("Tem_av_ref", value, "float") + self._Tem_av_ref = value + + Tem_av_ref = property( + fget=_get_Tem_av_ref, + fset=_set_Tem_av_ref, + doc=u"""Theorical Average Electromagnetic torque + + :Type: float + """, + ) + + def _get_Ud_ref(self): + """getter of Ud_ref""" + return self._Ud_ref + + def _set_Ud_ref(self, value): + """setter of Ud_ref""" + check_var("Ud_ref", value, "float") + self._Ud_ref = value + + Ud_ref = property( + fget=_get_Ud_ref, + fset=_set_Ud_ref, + doc=u"""d-axis current RMS magnitude (phase to neutral) + + :Type: float + """, + ) + + def _get_Uq_ref(self): + """getter of Uq_ref""" + return self._Uq_ref + + def _set_Uq_ref(self, value): + """setter of Uq_ref""" + check_var("Uq_ref", value, "float") + self._Uq_ref = value + + Uq_ref = property( + fget=_get_Uq_ref, + fset=_set_Uq_ref, + doc=u"""q-axis current RMS magnitude (phase to neutral) + + :Type: float + """, + ) + + def _get_felec(self): + """getter of felec""" + return self._felec + + def _set_felec(self, value): + """setter of felec""" + check_var("felec", value, "float") + self._felec = value + + felec = property( + fget=_get_felec, + fset=_set_felec, + doc=u"""electrical frequency + + :Type: float + """, + ) + + def _get_slip_ref(self): + """getter of slip_ref""" + return self._slip_ref + + def _set_slip_ref(self, value): + """setter of slip_ref""" + check_var("slip_ref", value, "float") + self._slip_ref = value + + slip_ref = property( + fget=_get_slip_ref, + fset=_set_slip_ref, + doc=u"""Rotor mechanical slip + + :Type: float + """, + ) + + def _get_U0_ref(self): + """getter of U0_ref""" + return self._U0_ref + + def _set_U0_ref(self, value): + """setter of U0_ref""" + check_var("U0_ref", value, "float") + self._U0_ref = value + + U0_ref = property( + fget=_get_U0_ref, + fset=_set_U0_ref, + doc=u"""stator voltage (phase to neutral) + + :Type: float + """, + ) diff --git a/pyleecan/Classes/OutElec.py b/pyleecan/Classes/OutElec.py index 7fb5d5767..5b2e86177 100644 --- a/pyleecan/Classes/OutElec.py +++ b/pyleecan/Classes/OutElec.py @@ -111,6 +111,8 @@ def __init__( Pem_av_ref=None, Us=None, internal=None, + slip_ref=0, + U0_ref=None, init_dict=None, init_str=None, ): @@ -165,6 +167,10 @@ def __init__( Us = init_dict["Us"] if "internal" in list(init_dict.keys()): internal = init_dict["internal"] + if "slip_ref" in list(init_dict.keys()): + slip_ref = init_dict["slip_ref"] + if "U0_ref" in list(init_dict.keys()): + U0_ref = init_dict["U0_ref"] # Set the properties (value check and convertion are done in setter) self.parent = None self.Time = Time @@ -185,6 +191,8 @@ def __init__( self.Pem_av_ref = Pem_av_ref self.Us = Us self.internal = internal + self.slip_ref = slip_ref + self.U0_ref = U0_ref # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -227,6 +235,8 @@ def __str__(self): OutElec_str += "internal = " + tmp else: OutElec_str += "internal = None" + linesep + linesep + OutElec_str += "slip_ref = " + str(self.slip_ref) + linesep + OutElec_str += "U0_ref = " + str(self.U0_ref) + linesep return OutElec_str def __eq__(self, other): @@ -270,6 +280,10 @@ def __eq__(self, other): return False if other.internal != self.internal: return False + if other.slip_ref != self.slip_ref: + return False + if other.U0_ref != self.U0_ref: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -342,6 +356,10 @@ def compare(self, other, name="self", ignore_list=None): diff_list.extend( self.internal.compare(other.internal, name=name + ".internal") ) + if other._slip_ref != self._slip_ref: + diff_list.append(name + ".slip_ref") + if other._U0_ref != self._U0_ref: + diff_list.append(name + ".U0_ref") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -368,6 +386,8 @@ def __sizeof__(self): S += getsizeof(self.Pem_av_ref) S += getsizeof(self.Us) S += getsizeof(self.internal) + S += getsizeof(self.slip_ref) + S += getsizeof(self.U0_ref) return S def as_dict(self, **kwargs): @@ -417,6 +437,8 @@ def as_dict(self, **kwargs): OutElec_dict["internal"] = None else: OutElec_dict["internal"] = self.internal.as_dict(**kwargs) + OutElec_dict["slip_ref"] = self.slip_ref + OutElec_dict["U0_ref"] = self.U0_ref # The class name is added to the dict for deserialisation purpose OutElec_dict["__class__"] = "OutElec" return OutElec_dict @@ -443,6 +465,8 @@ def _set_None(self): self.Us = None if self.internal is not None: self.internal._set_None() + self.slip_ref = None + self.U0_ref = None def _get_Time(self): """getter of Time""" @@ -661,7 +685,7 @@ def _set_Id_ref(self, value): Id_ref = property( fget=_get_Id_ref, fset=_set_Id_ref, - doc=u"""Electrical time vector (no symmetry) + doc=u"""d-axis current rms value :Type: float """, @@ -679,7 +703,7 @@ def _set_Iq_ref(self, value): Iq_ref = property( fget=_get_Iq_ref, fset=_set_Iq_ref, - doc=u"""q-axis current magnitude + doc=u"""q-axis current rms value :Type: float """, @@ -715,7 +739,7 @@ def _set_Ud_ref(self, value): Ud_ref = property( fget=_get_Ud_ref, fset=_set_Ud_ref, - doc=u"""d-axis voltage magnitude + doc=u"""d-axis voltage rms value :Type: float """, @@ -733,7 +757,7 @@ def _set_Uq_ref(self, value): Uq_ref = property( fget=_get_Uq_ref, fset=_set_Uq_ref, - doc=u"""q-axis voltage magnitude + doc=u"""q-axis voltage rms value :Type: float """, @@ -831,3 +855,39 @@ def _set_internal(self, value): :Type: OutInternal """, ) + + def _get_slip_ref(self): + """getter of slip_ref""" + return self._slip_ref + + def _set_slip_ref(self, value): + """setter of slip_ref""" + check_var("slip_ref", value, "float") + self._slip_ref = value + + slip_ref = property( + fget=_get_slip_ref, + fset=_set_slip_ref, + doc=u"""Rotor mechanical slip + + :Type: float + """, + ) + + def _get_U0_ref(self): + """getter of U0_ref""" + return self._U0_ref + + def _set_U0_ref(self, value): + """setter of U0_ref""" + check_var("U0_ref", value, "float") + self._U0_ref = value + + U0_ref = property( + fget=_get_U0_ref, + fset=_set_U0_ref, + doc=u"""stator voltage (phase to neutral) + + :Type: float + """, + ) diff --git a/pyleecan/Classes/import_all.py b/pyleecan/Classes/import_all.py index d03bd5a95..f51e05aba 100644 --- a/pyleecan/Classes/import_all.py +++ b/pyleecan/Classes/import_all.py @@ -78,6 +78,7 @@ from ..Classes.InputElec import InputElec from ..Classes.InputFlux import InputFlux from ..Classes.InputForce import InputForce +from ..Classes.InputVoltage import InputVoltage from ..Classes.Interpolation import Interpolation from ..Classes.LamHole import LamHole from ..Classes.LamSlot import LamSlot diff --git a/pyleecan/Functions/load_switch.py b/pyleecan/Functions/load_switch.py index ba39d0b06..f55af4e6c 100644 --- a/pyleecan/Functions/load_switch.py +++ b/pyleecan/Functions/load_switch.py @@ -80,6 +80,7 @@ "InputElec": InputElec, "InputFlux": InputFlux, "InputForce": InputForce, + "InputVoltage": InputVoltage, "Interpolation": Interpolation, "LamHole": LamHole, "LamSlot": LamSlot, diff --git a/pyleecan/Generator/ClassesRef/Output/OutElec.csv b/pyleecan/Generator/ClassesRef/Output/OutElec.csv index 0513e2e69..ee92c4dde 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutElec.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutElec.csv @@ -8,12 +8,14 @@ N0,rpm,Rotor speed,1,float,None,,,,,,,,, angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,,,, logger_name,-,Name of the logger to use,0,str,Pyleecan.Electrical,,,,,,,,, Tem_av_ref,N.m,Theorical Average Electromagnetic torque,,float,None,,,,,,,,, -Id_ref,A,Electrical time vector (no symmetry),1,float,None,,,,,,,,, -Iq_ref,A,q-axis current magnitude,1,float,None,,,,,,,,, +Id_ref,Arms,d-axis current rms value,1,float,None,,,,,,,,, +Iq_ref,Arms,q-axis current rms value,1,float,None,,,,,,,,, felec,Hz,Electrical Frequency,1,float,None,,,,,,,,, -Ud_ref,V,d-axis voltage magnitude,1,float,None,,,,,,,,, -Uq_ref,V,q-axis voltage magnitude,1,float,None,,,,,,,,, +Ud_ref,Vrms,d-axis voltage rms value,1,float,None,,,,,,,,, +Uq_ref,Vrms,q-axis voltage rms value,1,float,None,,,,,,,,, Pj_losses,W,Electrical Joule losses,,float,None,,,,,,,,, Pem_av_ref,W,Average Electromagnetic power,,float,None,,,,,,,,, Us,V,Stator voltage as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,,,, internal,-,OutInternal object containg outputs related to a specific model,0,OutInternal,None,,,,,,,,, +slip_ref,,Rotor mechanical slip,,float,0,,,,,,,,, +U0_ref,Vrms,stator voltage (phase to neutral),,float,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/ELUT.csv b/pyleecan/Generator/ClassesRef/Simulation/ELUT.csv index a54648aa3..a583d13b4 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/ELUT.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/ELUT.csv @@ -1,6 +1,4 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille -Rs,Ohm,DC phase winding resistance at Tsta_ref ,0,float,None,,,,Simulation,,get_parameters,VERSION,1,Abstract class for Electrical Look Up Table (ELUT), +Rs,Ohm,DC phase winding resistance at Tsta_ref ,0,float,None,,,,Simulation,,get_param_dict,VERSION,1,Abstract class for Electrical Look Up Table (ELUT), Ls,H,Phase winding leakage inductance,0,float,None,,,,,,,,,, Tsta_ref,degC,"Stator winding average temperature associated to Rs, Ls parameters",0,float,20,,,,,,,,,, -K_RSE_sta,,Stator winding Resistance Skin Effect factor function of frequency,,ndarray,None,,,,,,,,,, -K_ISE_sta,,Stator winding Inductance Skin Effect factor function of frequency,,ndarray,None,,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv b/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv index 761cebdc9..6ca6a8247 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv @@ -1,5 +1,5 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille -Phi_dqh,Wb,Stator winding flux llinkage fundamental calculated from user-input inductance tables,,ndarray,None,,,,Simulation,ELUT,get_parameters,VERSION,1,ELUT class for PMSM, +Phi_dqh,Wb,Stator winding flux llinkage fundamental calculated from user-input inductance tables,,ndarray,None,,,,Simulation,ELUT,get_param_dict,VERSION,1,ELUT class for PMSM, I_dqh,Arms,Id Iq Ih table corresponding to flux linkage data given in Phi_dqh,,list,None,,,,,,get_Lq,,,, Tmag_ref,degC,Magnet average temperature at which Phi_dqh is given,,float,20,,,,,,get_bemf,,,, E0,V,RMS fundamental back electromotive force (bemf) along Q-axis,,float,None,,,,,,get_Ld,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/ELUT_SCIM.csv b/pyleecan/Generator/ClassesRef/Simulation/ELUT_SCIM.csv index 0f4ffd41f..bdba7ecd7 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/ELUT_SCIM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/ELUT_SCIM.csv @@ -1,5 +1,6 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille -Phi_m,Wb,"Stator winding flux look-up table: list of DataTime objects whose (Id,Iq) is given by Idq list",,ndarray,None,,,,Simulation,ELUT,get_parameters,VERSION,1,ELUT class for SCIM, -Trot_ref,degC,Rotor bar average temperature at which Phi_m is given,0,float,20,,,,,,get_Lm,,,, -K_RSE_rot,,Rotor winding Resistance Skin Effect factor function of frequency,,ndarray,None,,,,,,comp_Lm_from_Phim,,,, -K_ISE_rot,,Rotor winding Inductance Skin Effect factor function of frequency,,ndarray,None,,,,,,import_from_data,,,, +Phi_m,Wb,Magnetizing flux for a given magnetizing current I_m,,ndarray,None,,,,Simulation,ELUT,get_param_dict,VERSION,1,ELUT class for SCIM, +I_m,Arms,Stator magnetizing current,,ndarray,None,,,,,,get_Lm,,,, +Trot_ref,degC,Rotor bar average temperature at which Phi_m is given,0,float,20,,,,,,comp_Lm_from_Phim,,,, +Rr,Ohm,DC rotor winding resistance at Tsta_ref ,0,float,None,,,,,,import_from_data,,,, +Lr,H,Rotor winding leakage inductance,0,float,None,,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/InputCurrent.csv b/pyleecan/Generator/ClassesRef/Simulation/InputCurrent.csv index c1099bd8d..c2b644148 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/InputCurrent.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/InputCurrent.csv @@ -1,10 +1,5 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -Is,A,Stator currents as a function of time (each column correspond to one phase) to import,"(Nt_tot, qs)",ImportMatrix,None,,,,Simulation,Input,gen_input,VERSION,1,Input to skip the electrical module and start with the magnetic one +Is,A,Stator currents as a function of time (each column correspond to one phase) to import,"(Nt_tot, qs)",ImportMatrix,None,,,,Simulation,InputVoltage,gen_input,VERSION,1,Input to skip the electrical module and start with the magnetic one Ir,A,Rotor currents as a function of time (each column correspond to one phase) to import,"(Nt_tot, qs)",ImportMatrix,None,,,,,,set_Id_Iq,,, -angle_rotor,rad,Rotor angular position as a function of time (if None computed according to Nr) to import,Nt_tot,Import,None,,,,,,set_OP_from_array,,, -rot_dir,-,"Rotation direction of the rotor 1 trigo, -1 clockwise",,float,None,-1,1,,,,,,, -angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,,,, -Tem_av_ref,N.m,Theorical Average Electromagnetic torque,,float,None,,,,,,,,, -Id_ref,A,d-axis current RMS magnitude,1,float,None,,,,,,,,, +Id_ref,A,d-axis current RMS magnitude,1,float,None,,,,,,set_OP_from_array,,, Iq_ref,A,q-axis current RMS magnitude,1,float,None,,,,,,,,, -felec,Hz,electrical frequency,1,float,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv new file mode 100644 index 000000000..897538945 --- /dev/null +++ b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv @@ -0,0 +1,10 @@ +Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description +angle_rotor,rad,Rotor angular position as a function of time (if None computed according to Nr) to import,Nt_tot,Import,None,,,,Simulation,Input,gen_input,VERSION,1,Input to start the electrical module with voltage input +rot_dir,-,"Rotation direction of the rotor 1 trigo, -1 clockwise",,float,None,-1,1,,,,set_OP_from_array,,, +angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,comp_felec,,, +Tem_av_ref,N.m,Theorical Average Electromagnetic torque,,float,None,,,,,,,,, +Ud_ref,Vrms,d-axis current RMS magnitude (phase to neutral),1,float,None,,,,,,,,, +Uq_ref,Vrms,q-axis current RMS magnitude (phase to neutral),1,float,None,,,,,,,,, +felec,Hz,electrical frequency,1,float,None,,,,,,,,, +slip_ref,,Rotor mechanical slip,,float,0,,,,,,,,, +U0_ref,Vrms,stator voltage (phase to neutral),,float,None,,,,,,,,, diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py index 919d236d6..937d0f9a7 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py @@ -1,8 +1,8 @@ -# -*- coding: utf-8 -*- from numpy import zeros, sqrt, pi, tile, isnan from multiprocessing import cpu_count from ....Functions.Electrical.coordinate_transformation import n2ab, ab2n +from ....Functions.load import import_class def comp_parameters(self, output): @@ -33,6 +33,33 @@ def comp_parameters(self, output): # compute skin_effect Xkr_skinS, Xke_skinS = Cond.comp_skin_effect(T=20) + alphasw = self.cond_mat.elec.alpha + + # alphasw = self.cond_mat.elec.alpha + + # # stator winding phase resistance, skin effect correction + # if felec is None: + # Rs_freq = self.Rs + # else: + # Rs_dc = self.Rs # DC resistance at Tsta_ref + # K_RSE_sta = self.K_RSE_sta # skin effect factor for resistance + # Rs_freq = Rs_dc * interp(K_RSE_sta[0, :], K_RSE_sta[1, :], felec) + + # # stator winding phase resistance, temperature correction + # if Tsta is not None: + # Rs_freq_temp = Rs_freq + # else: + # Tsta_ref = self.Tsta_ref # ref temperature + # Rs_freq_temp = Rs_freq * (1 + alphasw * (Tsta - Tsta_ref)) + + # # stator winding phase leakage inductance, skin effect correction + # if felec is None: + # Ls_freq = self.Ls + # else: + # Ls_dc = self.Ls # DC resistance + # K_ISE_sta = self.K_ISE_sta # skin effect factor for leakage inductance + # Ls_freq = Ls_dc * interp(K_ISE_sta[0, :], K_ISE_sta[1, :], felec) + # get temperatures TODO remove/replace, since this is a temp. solution only Tws = 20 if "Tws" not in self.parameters else self.parameter["Tws"] Twr = 20 if "Twr" not in self.parameters else self.parameter["Twr"] diff --git a/pyleecan/Methods/Simulation/ELUT/get_param_dict.py b/pyleecan/Methods/Simulation/ELUT/get_param_dict.py new file mode 100644 index 000000000..631c28f31 --- /dev/null +++ b/pyleecan/Methods/Simulation/ELUT/get_param_dict.py @@ -0,0 +1,20 @@ +from numpy import interp + + +def get_param_dict(self): + """Get the parameters dict from ELUT + + Parameters + ---------- + self : ELUT + an ELUT object + + Returns + ---------- + param_dict : dict + a Dict object + """ + + param_dict = {"Rs": self.Rs, "Ls": self.Ls, "Tsta_ref": self.Tsta_ref} + + return param_dict diff --git a/pyleecan/Methods/Simulation/ELUT/get_parameters.py b/pyleecan/Methods/Simulation/ELUT/get_parameters.py deleted file mode 100644 index c8ffba5ee..000000000 --- a/pyleecan/Methods/Simulation/ELUT/get_parameters.py +++ /dev/null @@ -1,50 +0,0 @@ -from numpy import interp - - -def get_parameters(self, Tsta=None, felec=None): - """Get the parameters dict for the ELUT at the operationnal temperature and frequency - Parameters - ---------- - self : ELUT - an ELUT object - - Returns - ---------- - param_dict : dict - a Dict object - """ - - -param_dict = dict() - -alphasw = self.cond_mat.elec.alpha - -# stator winding phase resistance, skin effect correction -if felec is None: - Rs_freq = self.Rs -else: - Rs_dc = self.Rs # DC resistance at Tsta_ref - K_RSE_sta = self.K_RSE_sta # skin effect factor for resistance - Rs_freq = Rs_dc * interp(K_RSE_sta[0, :], K_RSE_sta[1, :], felec) - -# stator winding phase resistance, temperature correction -if Tsta is not None: - Rs_freq_temp = Rs_freq -else: - Tsta_ref = self.Tsta_ref # ref temperature - Rs_freq_temp = Rs_freq * (1 + alphasw * (Tsta - Tsta_ref)) - -param_dict["Rs"] = Rs_freq_temp - - -# stator winding phase leakage inductance, skin effect correction -if felec is None: - Ls_freq = self.Ls -else: - Ls_dc = self.Ls # DC resistance - K_ISE_sta = self.K_ISE_sta # skin effect factor for leakage inductance - Ls_freq = Ls_dc * interp(K_ISE_sta[0, :], K_ISE_sta[1, :], felec) - -param_dict["Ls"] = Ls_freq - -return param_dict diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_param_dict.py b/pyleecan/Methods/Simulation/ELUT_PMSM/get_param_dict.py new file mode 100644 index 000000000..dbad16944 --- /dev/null +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/get_param_dict.py @@ -0,0 +1,32 @@ +from numpy import interp + + +def get_param_dict(self): + """Get the parameters dict for the ELUT of PMSM at the operationnal temperature and frequency + Parameters + ---------- + self : ELUT + an ELUT_PMSM object + + Returns + ---------- + param_dict : dict + a Dict object + """ + + # getting parameters of the abstract class ELUT (stator parameters) + param_dict = super(type(self), self).get_parameters(Tsta=Tsta, felec=felec) + + Brm20 = self.magnet_0.mat_type.mag.Brm20 + alpha_Br = self.magnet_0.mat_type.mag.alpha_Br + + # back emf [V] update with temperature + if Tmag is None: + E0_temp = self.E0 + else: + Tmag_ref = self.Tmag_ref + E0_temp = self.E0 * (1 + alpha_Br * (Tmag - Tmag_ref)) + + param_dict["E0"] = E0_temp + + return param_dict diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_parameters.py b/pyleecan/Methods/Simulation/ELUT_PMSM/get_parameters.py deleted file mode 100644 index 86cb5db7a..000000000 --- a/pyleecan/Methods/Simulation/ELUT_PMSM/get_parameters.py +++ /dev/null @@ -1,34 +0,0 @@ -from numpy import interp - - -def get_parameters(self, Tsta, Tmag, felec): - """Get the parameters dict for the ELUT of PMSM at the operationnal temperature and frequency - Parameters - ---------- - self : ELUT - an ELUT_PMSM object - - Returns - ---------- - param_dict : dict - a Dict object - """ - - -# getting parameters of the abstract class ELUT (stator parameters) -param_dict = super(type(self), self).get_parameters(Tsta=Tsta, felec=felec) - -Brm20 = self.magnet_0.mat_type.mag.Brm20 -alpha_Br = self.magnet_0.mat_type.mag.alpha_Br - -# back emf [V] update with temperature -if Tmag is None: - E0_temp = self.E0 -else: - Tmag_ref = self.Tmag_ref - E0_temp = self.E0 * (1 + alpha_Br * (Tmag - Tmag_ref)) - -param_dict["E0"] = E0_temp - - -return param_dict diff --git a/pyleecan/Methods/Simulation/ELUT_SCIM/get_param_dict.py b/pyleecan/Methods/Simulation/ELUT_SCIM/get_param_dict.py new file mode 100644 index 000000000..d852076d3 --- /dev/null +++ b/pyleecan/Methods/Simulation/ELUT_SCIM/get_param_dict.py @@ -0,0 +1,29 @@ +from numpy import interp + + +def get_param_dict(self): + """Get the parameters dict for the ELUT of PMSM at the operationnal temperature and frequency + Parameters + ---------- + self : ELUT + an ELUT_PMSM object + + Returns + ---------- + param_dict : dict + a Dict object + """ + + # getting parameters of the abstract class ELUT (stator parameters) + param_dict = super(type(self), self).get_param_dict() + + param_dict["Rr"] = self.Rr + param_dict["Lr"] = self.Lr + + param_dict["Trot_ref"] = self.Trot_ref + + param_dict["Phi_m"] = self.Phi_m + + param_dict["I_m"] = self.I_m + + return param_dict diff --git a/pyleecan/Methods/Simulation/Electrical/run.py b/pyleecan/Methods/Simulation/Electrical/run.py index f36ecac11..32aa4488f 100644 --- a/pyleecan/Methods/Simulation/Electrical/run.py +++ b/pyleecan/Methods/Simulation/Electrical/run.py @@ -1,42 +1,61 @@ -# -*- coding: utf-8 -*- - from numpy.lib.shape_base import expand_dims from ....Methods.Simulation.Input import InputError +from ....Classes.EEC_SCIM import EEC_SCIM +from ....Classes.EEC_PMSM import EEC_PMSM +from ....Classes.MachineSCIM import MachineSCIM +from ....Classes.MachineSIPMSM import MachineSIPMSM +from ....Classes.MachineIPMSM import MachineIPMSM def run(self): """Run the Electrical module""" if self.parent is None: - raise InputError( - "ERROR: The Electrical object must be in a Simulation object to run" - ) + raise InputError("The Electrical object must be in a Simulation object to run") if self.parent.parent is None: - raise InputError( - "ERROR: The Simulation object must be in an Output object to run" - ) + raise InputError("The Simulation object must be in an Output object to run") self.get_logger().info("Starting Electric module") output = self.parent.parent - # taking simulation fundamental frequency & temperatures #TODO - felec = 50 + machine = output.simu.machine + + if self.eec is None: + # Init EEC depending on machine type + if isinstance(machine, MachineSCIM): + self.eec = EEC_SCIM() + elif isinstance(machine, (MachineSIPMSM, MachineIPMSM)): + self.eec = EEC_PMSM() + + else: + # Check that EEC is consistent with machine type + if isinstance(machine, MachineSCIM) and not isinstance(self.eec, EEC_SCIM): + raise Exception( + "Cannot run Electrical model if machine is SCIM and eec is not EEC_SCIM" + ) + elif isinstance(machine, (MachineSIPMSM, MachineIPMSM)) and not isinstance( + self.eec, EEC_PMSM + ): + raise Exception( + "Cannot run Electrical model if machine is PMSM and eec is not EEC_PMSM" + ) # Compute and store time and angle axes from elec output # and returns additional axes in axes_dict - axes_dict = self.comp_axes(output) + if output.elec.Time is not None and output.elec.Angle is not None: + axes_dict = {"time": output.elec.Time, "angle": output.elec.Angle} + else: + raise Exception("Time and Angle must not be None in Electrical.run()") # Generate drive # self.eec.gen_drive(output) if self.ELUT_enforced is not None: # enforce parameters of EEC coming from enforced ELUT at right frequency & temperatures - self.eec.parameters = self.ELUT_enforced.get_parameters( - felec=felec, Tsta=self.Tsta, Trot=self.Trot - ) - else: - # Compute parameters of the electrical equivalent circuit if ELUT not given - out_dict = self.eec.comp_parameters(output, self.ELUT_enforced) + self.eec.parameters = self.ELUT_enforced.get_param_dict() + + # Compute parameters of the electrical equivalent circuit if ELUT not given + out_dict = self.eec.comp_parameters(output, Tsta=self.Tsta, Trot=self.Trot) # Solve the electrical equivalent circuit out_dict = self.eec.solve_EEC(output) diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index 6a0d4a82a..6f533e0b7 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -24,17 +24,10 @@ def gen_input(self): elif isinstance(self.parent.parent, Simulation): simu = self.parent.parent else: - raise InputError( - "ERROR: InputCurrent object should be inside a Simulation object" - ) - - # Create the correct Output object - output = OutElec() + raise InputError("InputCurrent object should be inside a Simulation object") - # Set discretization - Time, Angle = self.comp_axes(simu.machine, self.N0) - output.Time = Time - output.Angle = Angle + # Call InputVoltage.gen_input() + super(type(self), self).gen_input() # Number of winding phases for stator/rotor qs = len(simu.machine.stator.get_name_phase()) @@ -48,7 +41,7 @@ def gen_input(self): if self.Is is None: if self.Id_ref is None and self.Iq_ref is None: raise InputError( - "ERROR: InputCurrent.Is, InputCurrent.Id_ref, and InputCurrent.Iq_ref missing" + "InputCurrent.Is, InputCurrent.Id_ref, and InputCurrent.Iq_ref missing" ) else: output.Id_ref = self.Id_ref @@ -58,7 +51,7 @@ def gen_input(self): Is = self.Is.get_data() if not isinstance(Is, ndarray) or Is.shape != (self.Nt_tot, qs): raise InputError( - "ERROR: InputCurrent.Is must be a matrix with the shape " + "InputCurrent.Is must be a matrix with the shape " + str((self.Nt_tot, qs)) + " (len(time), stator phase number), " + str(Is.shape) @@ -96,7 +89,7 @@ def gen_input(self): Ir = self.Ir.get_data() if not isinstance(Ir, ndarray) or Ir.shape != (self.Nt_tot, qr): raise InputError( - "ERROR: InputCurrent.Ir must be a matrix with the shape " + "InputCurrent.Ir must be a matrix with the shape " + str((self.Nt_tot, qr)) + " (len(time), rotor phase number), " + str(Ir.shape) @@ -117,47 +110,5 @@ def gen_input(self): values=transpose(Ir), ) - # Load and check alpha_rotor and N0 - if self.angle_rotor is None and self.N0 is None: - raise InputError( - "ERROR: InputCurrent.angle_rotor and InputCurrent.N0 can't be None at the same time" - ) - if self.angle_rotor is not None: - output.angle_rotor = self.angle_rotor.get_data() - if ( - not isinstance(output.angle_rotor, ndarray) - or len(output.angle_rotor.shape) != 1 - or output.angle_rotor.size != self.Nt_tot - ): - # angle_rotor should be a vector of same length as time - raise InputError( - "ERROR: InputCurrent.angle_rotor should be a vector of the same length as time, " - + str(output.angle_rotor.shape) - + " shape found, " - + str(self.Nt_tot) - + " expected" - ) - - if self.rot_dir is None or self.rot_dir not in [-1, 1]: - # Enforce default rotation direction - # simu.parent.geo.rot_dir = None - pass # None is already the default value - else: - simu.parent.geo.rot_dir = self.rot_dir - - if self.angle_rotor_initial is None: - # Enforce default initial position - output.angle_rotor_initial = 0 - else: - output.angle_rotor_initial = self.angle_rotor_initial - - if self.Tem_av_ref is not None: - output.Tem_av_ref = self.Tem_av_ref - - if simu.parent is None: - raise InputError( - "ERROR: The Simulation object must be in an Output object to run" - ) - # Save the Output in the correct place simu.parent.elec = output diff --git a/pyleecan/Methods/Simulation/InputVoltage/__init__.py b/pyleecan/Methods/Simulation/InputVoltage/__init__.py new file mode 100644 index 000000000..e8315aaa1 --- /dev/null +++ b/pyleecan/Methods/Simulation/InputVoltage/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- + + +class ImportError(Exception): + """Raised when the data import or generation is not possible""" + + pass diff --git a/pyleecan/Methods/Simulation/InputVoltage/comp_felec.py b/pyleecan/Methods/Simulation/InputVoltage/comp_felec.py new file mode 100644 index 000000000..41714bcd3 --- /dev/null +++ b/pyleecan/Methods/Simulation/InputVoltage/comp_felec.py @@ -0,0 +1,30 @@ +from ....Methods.Simulation.Input import InputError + + +def comp_felec(self): + """Compute the electrical frequency + + Parameters + ---------- + self : InputCurrent + An InputCurrent object + """ + + if hasattr(self, "felec") and self.felec is not None: + return self.felec # TODO maybe add some checks? + elif self.N0 is not None: + # Get the phase number for verifications + if self.parent is None: + raise InputError("InputCurrent object should be inside a Simulation object") + # get the pole pair number + if hasattr(self.parent, "machine"): + zp = self.parent.machine.stator.get_pole_pair_number() + elif hasattr(self.parent.parent, "machine"): + zp = self.parent.parent.machine.stator.get_pole_pair_number() + else: + logger = self.get_logger() + logger.warning("Input.comp_felec(): Machine was not found.") + zp = 1 + return self.N0 * zp / (60 * (1 - self.slip_ref)) + else: + raise InputError("InputCurrent object can't have felec and N0 at None") diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py new file mode 100644 index 000000000..fd3093d2c --- /dev/null +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -0,0 +1,130 @@ +from ....Classes.OutElec import OutElec +from ....Classes.Simulation import Simulation +from ....Methods.Simulation.Input import InputError +from numpy import ndarray, pi, mean, transpose, zeros +from ....Functions.Electrical.coordinate_transformation import n2dq +from SciDataTool import Data1D, DataTime +from ....Functions.Winding.gen_phase_list import gen_name + + +def gen_input(self): + """Generate the input for the electrical module (electrical output filled with voltage) + + Parameters + ---------- + self : InputVoltage + An InputVoltage object + """ + + # Get the simulation + if isinstance(self.parent, Simulation): + simu = self.parent + elif isinstance(self.parent.parent, Simulation): + simu = self.parent.parent + else: + raise InputError("InputVoltage object should be inside a Simulation object") + + # Create the correct Output object + output = OutElec() + + # Set discretization + Time, Angle = self.comp_axes(simu.machine, self.N0) + output.Time = Time + output.Angle = Angle + + output.N0 = self.N0 + output.felec = self.comp_felec() # TODO introduce set_felec(slip) + + if self.U0_ref is None and self.Ud_ref and self.Uq_ref: + raise Exception("U0_ref, Ud_ref, and Uq_refcannot be all None in InputVoltage") + + # Generate Us + # if qs > 0: + # TODO + # if self.Is is None: + # if self.Id_ref is None and self.Iq_ref is None: + # raise InputError( + # "ERROR: InputVoltage.Is, InputVoltage.Id_ref, and InputVoltage.Iq_ref missing" + # ) + # else: + # output.Id_ref = self.Id_ref + # output.Iq_ref = self.Iq_ref + # output.Is = None + # else: + # Is = self.Is.get_data() + # if not isinstance(Is, ndarray) or Is.shape != (self.Nt_tot, qs): + # raise InputError( + # "ERROR: InputVoltage.Is must be a matrix with the shape " + # + str((self.Nt_tot, qs)) + # + " (len(time), stator phase number), " + # + str(Is.shape) + # + " returned" + # ) + # # Creating the data object + # Phase = Data1D( + # name="phase", + # unit="", + # values=gen_name(qs), + # is_components=True, + # ) + # output.Is = DataTime( + # name="Stator current", + # unit="A", + # symbol="Is", + # axes=[Phase, Time], + # values=transpose(Is), + # ) + # # Compute corresponding Id/Iq reference + # Idq = n2dq( + # transpose(output.Is.values), + # 2 * pi * output.felec * output.Time.get_values(is_oneperiod=False), + # n=qs, + # is_dq_rms=True, + # ) + # output.Id_ref = mean(Idq[:, 0]) + # output.Iq_ref = mean(Idq[:, 1]) # TODO use of mean has to be documented + + # Load and check alpha_rotor and N0 + if self.angle_rotor is None and self.N0 is None: + raise InputError( + "InputVoltage.angle_rotor and InputVoltage.N0 can't be None at the same time" + ) + if self.angle_rotor is not None: + output.angle_rotor = self.angle_rotor.get_data() + if ( + not isinstance(output.angle_rotor, ndarray) + or len(output.angle_rotor.shape) != 1 + or output.angle_rotor.size != self.Nt_tot + ): + # angle_rotor should be a vector of same length as time + raise InputError( + "InputVoltage.angle_rotor should be a vector of the same length as time, " + + str(output.angle_rotor.shape) + + " shape found, " + + str(self.Nt_tot) + + " expected" + ) + + simu.parent.elec.slip_ref = self.slip_ref + + if self.rot_dir is None or self.rot_dir not in [-1, 1]: + # Enforce default rotation direction + # simu.parent.geo.rot_dir = None + pass # None is already the default value + else: + simu.parent.geo.rot_dir = self.rot_dir + + if self.angle_rotor_initial is None: + # Enforce default initial position + output.angle_rotor_initial = 0 + else: + output.angle_rotor_initial = self.angle_rotor_initial + + if self.Tem_av_ref is not None: + output.Tem_av_ref = self.Tem_av_ref + + if simu.parent is None: + raise InputError("The Simulation object must be in an Output object to run") + + # Save the Output in the correct place + simu.parent.elec = output diff --git a/pyleecan/Methods/Simulation/InputVoltage/set_OP_from_array.py b/pyleecan/Methods/Simulation/InputVoltage/set_OP_from_array.py new file mode 100644 index 000000000..ab198efdf --- /dev/null +++ b/pyleecan/Methods/Simulation/InputVoltage/set_OP_from_array.py @@ -0,0 +1,29 @@ +def set_OP_from_array(self, OP_matrix, type_OP_matrix=1, index=0): + """Extract the Operating Point from an OP_matrix + + Parameters + ---------- + self : InputCurrent + An InputCurrent object + OP_matrix : ndarray + Operating Point matrix (cf VarLoadCurrent) + type_OP_matrix : int + Select which kind of OP_matrix is used 0: (N0,I0,Phi0,T,P), 1:(N0,Id,Iq,T,P) + index : int + To select the line of the OP_matrix to use (default=0) + """ + + # Check OP_matrix + assert len(OP_matrix.shape) == 2 + assert OP_matrix.shape[1] <= 5 + assert index < OP_matrix.shape[0] + assert type_OP_matrix in [0, 1] + + self.N0 = OP_matrix[index, 0] + if type_OP_matrix == 1: + self.Id_ref = OP_matrix[index, 1] + self.Iq_ref = OP_matrix[index, 2] + else: + self.set_Id_Iq(I0=OP_matrix[index, 1], Phi0=OP_matrix[index, 2]) + if OP_matrix.shape[1] > 3: + self.Tem_av_ref = OP_matrix[index, 3] From e4f7f85425bd647d76dfc710f91fe2da4212027f Mon Sep 17 00:00:00 2001 From: Jean Le Besnerais Date: Thu, 5 Aug 2021 17:09:59 +0200 Subject: [PATCH 016/167] [CC] remove solve_EEC_freq.py --- .../Simulation/EEC_SCIM/solve_EEC_freq.py | 133 ------------------ 1 file changed, 133 deletions(-) delete mode 100644 pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC_freq.py diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC_freq.py b/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC_freq.py deleted file mode 100644 index 3ae46f593..000000000 --- a/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC_freq.py +++ /dev/null @@ -1,133 +0,0 @@ -# -*- coding: utf-8 -*- - -from ....Functions.Electrical.coordinate_transformation import dq2n -from ....Functions.Winding.gen_phase_list import gen_name - -from numpy import array, pi, real, imag, tile -from scipy.linalg import solve -from SciDataTool import Data1D, DataTime - - -def solve_EEC_freq(self, output): - """Solves the equivalent electrical circuit for each frequency (not only fundamental) - TODO find ref. to cite - cf "Title" - Autor, Publisher - - ---> ----> - -----Rs------XsIs---- --- -----Rr'----Xr'Ir'---- - | | | | - | Rfe Xm Rr'*(s-1)/s - | | | | - ---------Is---------- --- ---------Ir------------ - - ---> - Us - - Parameters - ---------- - self : EEC_SCIM - an EEC_SCIM object, including ELUT - output : Output - an Output object - """ - ELUT = self.ELUT #Electrical Look Up Table containing Lm(Im) - k_skin = self.k_skin #skin effect factor array - - # initial calculation of unsaturated magnetization current - Lm0 = ELUT.phim[0] - EEC.solve_EEC() - Im0 = EEC. - - - - Rr_s = Rr / slip if slip != 0 else 1e16 # TODO modify system instead - - # Prepare linear system - - # Solve system - if "Ud" in self.parameters: - Us = self.parameters["Ud"] + 1j * self.parameters["Uq"] - # input vector - b = array([real(Us), imag(Us), 0, 0, 0, 0, 0, 0, 0, 0]) - # system matrix (unknowns order: Um, Is, Im, Ir', Ife each real and imagine parts) - # TODO simplify system for less unknows (only calculate them afterwards, e.g. Um, Im, Ife) - # fmt: off - A = array( - [ - # sum of (real and imagine) voltages equals the input voltage Us - [ 1, 0, Rs, -Xs, 0, 0, 0, 0, 0, 0, ], - [ 0, 1, Xs, Rs, 0, 0, 0, 0, 0, 0, ], - # sum of (real and imagine) currents are zeros - [ 0, 0, -1, 0, 1, 0, 1, 0, 1, 0, ], - [ 0, 0, 0, -1, 0, 1, 0, 1, 0, 1, ], - # j*Xm*Im = Um - [-1, 0, 0, 0, 0, -Xm, 0, 0, 0, 0, ], - [ 0, -1, 0, 0, Xm, 0, 0, 0, 0, 0, ], - # (Rr'/s + j*Xr')*Ir' = Um - [-1, 0, 0, 0, 0, 0, Rr_s, -Xr, 0, 0, ], - [ 0, -1, 0, 0, 0, 0, Xr, Rr_s, 0, 0, ], - # Rfe*Ife = Um - [-1, 0, 0, 0, 0, 0, 0, 0, Rfe, 0, ], - [ 0, -1, 0, 0, 0, 0, 0, 0, 0, Rfe, ], - ] - ) - # fmt: on - # delete last row and column if Rfe is None - if Rfe is None: - A = A[:-2, :-2] - b = b[:-2] - - # print(b) - # print(A) - X = solve(A.astype(float), b.astype(float)) - - Ir_norm = array([X[6], X[7]]) - - # TODO use logger for output of some quantities - - output.elec.Id_ref = X[2] # use Id_ref / Iq_ref for now - output.elec.Iq_ref = X[3] - else: - pass - # TODO - - # Compute stator currents - output.elec.Is = None - output.elec.Is = output.elec.get_Is() - - # Compute stator voltage - output.elec.Us = None - output.elec.Us = output.elec.get_Us() - - # Compute rotor currents - time = output.elec.Time.get_values(is_oneperiod=True) - Nt = time.size - qsr = output.simu.machine.rotor.winding.qs - sym = output.simu.machine.comp_periodicity()[0] - - Ir_ = tile(Ir_norm, (Nt, 1)) * norm - - w_slip = ws * slip - - # Get rotation direction - rot_dir = output.get_rot_dir() - - # compute actual rotor bar currents - # TODO fix: initial rotor pos. is disregarded for now - Ir = dq2n(Ir_, w_slip * time, n=qsr // sym, rot_dir=rot_dir, is_n_rms=False) - Ir = tile(Ir, (1, sym)) - - Phase = Data1D( - name="phase", - unit="", - values=gen_name(qsr), - is_components=True, - ) - output.elec.Ir = DataTime( - name="Rotor current", - unit="A", - symbol="Ir", - axes=[Phase, output.elec.Time.copy()], - values=Ir.T, - ) From 9b7d7c96618a61d0236f8113a82bc62759bb672f Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Thu, 5 Aug 2021 17:35:33 +0200 Subject: [PATCH 017/167] [BC] Various corrections in gen_input and run() --- .../Methods/Simulation/ELUT/get_param_dict.py | 3 --- .../Simulation/ELUT_SCIM/get_param_dict.py | 3 --- pyleecan/Methods/Simulation/Electrical/run.py | 4 +++- .../Simulation/InputCurrent/gen_input.py | 19 ++++++++++--------- .../Simulation/InputVoltage/gen_input.py | 10 ++++++---- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/pyleecan/Methods/Simulation/ELUT/get_param_dict.py b/pyleecan/Methods/Simulation/ELUT/get_param_dict.py index 631c28f31..d2875fe03 100644 --- a/pyleecan/Methods/Simulation/ELUT/get_param_dict.py +++ b/pyleecan/Methods/Simulation/ELUT/get_param_dict.py @@ -1,6 +1,3 @@ -from numpy import interp - - def get_param_dict(self): """Get the parameters dict from ELUT diff --git a/pyleecan/Methods/Simulation/ELUT_SCIM/get_param_dict.py b/pyleecan/Methods/Simulation/ELUT_SCIM/get_param_dict.py index d852076d3..7eb5585b5 100644 --- a/pyleecan/Methods/Simulation/ELUT_SCIM/get_param_dict.py +++ b/pyleecan/Methods/Simulation/ELUT_SCIM/get_param_dict.py @@ -1,6 +1,3 @@ -from numpy import interp - - def get_param_dict(self): """Get the parameters dict for the ELUT of PMSM at the operationnal temperature and frequency Parameters diff --git a/pyleecan/Methods/Simulation/Electrical/run.py b/pyleecan/Methods/Simulation/Electrical/run.py index 32aa4488f..553c3f549 100644 --- a/pyleecan/Methods/Simulation/Electrical/run.py +++ b/pyleecan/Methods/Simulation/Electrical/run.py @@ -52,7 +52,9 @@ def run(self): if self.ELUT_enforced is not None: # enforce parameters of EEC coming from enforced ELUT at right frequency & temperatures - self.eec.parameters = self.ELUT_enforced.get_param_dict() + if self.eec.parameters is None: + self.eec.parameters = dict() + self.eec.parameters.update(self.ELUT_enforced.get_param_dict()) # Compute parameters of the electrical equivalent circuit if ELUT not given out_dict = self.eec.comp_parameters(output, Tsta=self.Tsta, Trot=self.Trot) diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index 6f533e0b7..1359e4b56 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -1,13 +1,14 @@ -# -*- coding: utf-8 -*- +from numpy import ndarray, pi, mean, transpose, zeros -from ....Classes.OutElec import OutElec from ....Classes.Simulation import Simulation + from ....Methods.Simulation.Input import InputError -from numpy import ndarray, pi, mean, transpose, zeros + from ....Functions.Electrical.coordinate_transformation import n2dq -from SciDataTool import Data1D, DataTime from ....Functions.Winding.gen_phase_list import gen_name +from SciDataTool import Data1D, DataTime + def gen_input(self): """Generate the input for the magnetic module (electrical output) @@ -29,13 +30,13 @@ def gen_input(self): # Call InputVoltage.gen_input() super(type(self), self).gen_input() + # Get electrical output + output = simu.parent.elec + # Number of winding phases for stator/rotor qs = len(simu.machine.stator.get_name_phase()) qr = len(simu.machine.rotor.get_name_phase()) - output.N0 = self.N0 - output.felec = self.comp_felec() # TODO introduce set_felec(slip) - # Load and check Is if qs > 0: if self.Is is None: @@ -68,7 +69,7 @@ def gen_input(self): name="Stator current", unit="A", symbol="Is", - axes=[Phase, Time], + axes=[Phase, output.Time], values=transpose(Is), ) # Compute corresponding Id/Iq reference @@ -106,7 +107,7 @@ def gen_input(self): name="Rotor current", unit="A", symbol="Ir", - axes=[Phase, Time], + axes=[Phase, output.Time], values=transpose(Ir), ) diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index fd3093d2c..90d27e657 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -1,10 +1,12 @@ +from numpy import ndarray + from ....Classes.OutElec import OutElec from ....Classes.Simulation import Simulation from ....Methods.Simulation.Input import InputError -from numpy import ndarray, pi, mean, transpose, zeros -from ....Functions.Electrical.coordinate_transformation import n2dq -from SciDataTool import Data1D, DataTime -from ....Functions.Winding.gen_phase_list import gen_name + +# from ....Functions.Electrical.coordinate_transformation import n2dq +# from SciDataTool import Data1D, DataTime +# from ....Functions.Winding.gen_phase_list import gen_name def gen_input(self): From 56ae915fdfd033fc81ad4ba17d6a8a8f9ec82037 Mon Sep 17 00:00:00 2001 From: Sijie-NI Date: Fri, 6 Aug 2021 09:11:15 +0200 Subject: [PATCH 018/167] [WP]comp_BEMF_harmonics fix bugs --- pyleecan/Methods/Simulation/EEC_PMSM/comp_BEMF_harmonics.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/comp_BEMF_harmonics.py b/pyleecan/Methods/Simulation/EEC_PMSM/comp_BEMF_harmonics.py index 450c7ae68..8a2bfa539 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/comp_BEMF_harmonics.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/comp_BEMF_harmonics.py @@ -9,7 +9,7 @@ def comp_BEMF_harmonics(Phi_A,Phi_B,Phi_C, delta, time): """ - Compute the back electromotive force harmonics from magnet fluxes + Compute the back electromotive force harmonics from magnet fluxes (PMSM) Parameters ---------- @@ -19,7 +19,6 @@ def comp_BEMF_harmonics(Phi_A,Phi_B,Phi_C, delta, time): delta: Rotor angular position time: time vector - """ #Park transformation (keep the amplitude factor=2/3) @@ -114,8 +113,6 @@ def comp_BEMF_harmonics(Phi_A,Phi_B,Phi_C, delta, time): E_q=2*pi*freqs_d*complx_d+2*pi*freqs_q*complx_q*1j E_h=2*pi*freqs_h*complx_h*1j - - return E_d, E_q, E_h, freqs_d, freqs_q, freqs_h From 4e8319c8003e94178c4d30039bf1184f62ead0a5 Mon Sep 17 00:00:00 2001 From: Sijie-NI Date: Fri, 6 Aug 2021 09:32:29 +0200 Subject: [PATCH 019/167] [WP]fix bug comp_skin_effect_round_wire --- .../Machine/Conductor/comp_skin_effect_round_wire.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pyleecan/Methods/Machine/Conductor/comp_skin_effect_round_wire.py b/pyleecan/Methods/Machine/Conductor/comp_skin_effect_round_wire.py index 355d93d19..a00413156 100644 --- a/pyleecan/Methods/Machine/Conductor/comp_skin_effect_round_wire.py +++ b/pyleecan/Methods/Machine/Conductor/comp_skin_effect_round_wire.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import numpy as np -def comp_skin_effect_round_wire(self,f, rho=None): +def comp_skin_effect_round_wire(self,f,rho=None, mu=None): """ Compute skin effect factor for round wires @@ -13,6 +13,8 @@ def comp_skin_effect_round_wire(self,f, rho=None): Frequency (Hz) rho: float Resistivity of wire material (Ohm meter) + mu: float + Relative permeability of wire material self : Conductor an Conductor object @@ -25,7 +27,7 @@ def comp_skin_effect_round_wire(self,f, rho=None): """ - + # Resistivity of wire material (Ohm meter) if rho is None: rho = self.cond_mat.elec.rho # Wire diameter @@ -33,7 +35,10 @@ def comp_skin_effect_round_wire(self,f, rho=None): # Vaccum or air permeability mu0 = 4 * np.pi * 1e-7 # Wire material magnetic permeability (mu*mu0) - mu=self.cond_mat.mag.mur_lin*mu0 + if mu is None: + mu=self.cond_mat.mag.mur_lin*mu0 + else: + mu=mu*mu0 # Thickness of skin delta=np.sqrt(rho/(np.pi*f*mu)) From 44cdc9e45e608834989ea26e88eccdf179ca02b1 Mon Sep 17 00:00:00 2001 From: Jean Le Besnerais Date: Fri, 6 Aug 2021 12:22:16 +0200 Subject: [PATCH 020/167] [WP] test of the SCIM equivalent circuit (fundamental) --- .../Electrical/test_EEC_ELUT_SCIM_001.py | 10 +- .../Machine/Conductor/comp_skin_effect.py | 15 +- .../Simulation/EEC_SCIM/comp_parameters.py | 343 ++++++++++-------- .../Methods/Simulation/EEC_SCIM/solve_EEC.py | 15 +- pyleecan/Methods/Simulation/Electrical/run.py | 4 +- 5 files changed, 227 insertions(+), 160 deletions(-) diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py b/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py index 8506efe85..1c604ea22 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py @@ -42,7 +42,8 @@ def test_EEC_ELUT_SCIM_001(): # NAS_path + "Manatee_v1_results/default_proj_nl/default_proj_nl_results.mat" # ) # matlab_path = "D:/Manatee_V1_trunk/Manatee_1.0/Results/default_proj_nl/default_proj_nl_results.mat" - matlab_path = "D:/Manatee_V1_trunk/Manatee_1.0/ELUT_SCIM_001.mat" + # matlab_path = "D:/Manatee_V1_trunk/Manatee_1.0/ELUT_SCIM_001.mat" + matlab_path = "C:/Users/Jean/Documents/EOMYS_JLB/MANATEEV2_JLB/ELUT_SCIM_001.mat" assert isfile(matlab_path) @@ -75,6 +76,9 @@ def test_EEC_ELUT_SCIM_001(): else: param_dict[param] = value + # add Rfe + # param_dict["Rfe"] = 1e12 + # Prepare simulation SCIM_001 = load(join(DATA_DIR, "Machine", "SCIM_001.json")) @@ -95,8 +99,8 @@ def test_EEC_ELUT_SCIM_001(): Rr=param_dict["R2_20"], Lr=param_dict["L20"], Trot_ref=20, - Phi_m=np_abs(param_dict["L_m"] * param_dict["I_m"]), - I_m=np_abs(param_dict["I_m"]), + Phi_m=np_abs(param_dict["Lm"] * param_dict["Im"]), + I_m=np_abs(param_dict["Im"]), ) # Configure simulation diff --git a/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py b/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py index b45440c59..2c9e5742c 100644 --- a/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py +++ b/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py @@ -8,15 +8,19 @@ from numpy import ones, pi, sqrt -def comp_skin_effect(self, T=20): - """Compute the electrical average power +def comp_skin_effect(self, freq, T=20): + """Compute the skin effect factor for the conductor Parameters ---------- self : Conductor an Conductor object - output : - Xkr_skinS, Xke_skinS + Returns + ---------- + Xkr_skinS : float + skin effect coeff for resistance at freq + Xke_skinS : float + skin effect coeff for inductance at freq """ rhosw20 = self.cond_mat.elec.rho @@ -24,11 +28,12 @@ def comp_skin_effect(self, T=20): rho = rhosw20 * (1 + alphasw * (T - 20)) sigmar = 1 / rho mu0 = 4 * pi * 1e-7 - ws = 2 * pi * self.parent.parent.parent.parent.input.comp_felec() + ws = 2 * pi * freq Slot = self.parent.parent.parent.stator.slot type_skin_effect = self.parent.parent.parent.parent.elec.type_skin_effect # nsw = len(ws) + # initialization Xkr_skinS = 1 Xke_skinS = 1 diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py index 937d0f9a7..2ff00ee2c 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py @@ -5,7 +5,7 @@ from ....Functions.load import import_class -def comp_parameters(self, output): +def comp_parameters(self, output, Tsta, Trot): """Compute the parameters dict for the equivalent electrical circuit: resistance, inductance Parameters @@ -21,22 +21,93 @@ def comp_parameters(self, output): Zsr = machine.rotor.slot.Zs qsr = machine.rotor.winding.qs p = machine.rotor.winding.p - + felec = ( + output.simu.input.comp_felec() + ) # XXX felec should be at same level that Tsta Trot (simulation param) + + # simulation type for magnetizing inductance when missing (0: FEA, 1: Analytical) + type_comp_Lm = 0 + # simulation type for rotor slot leakage inductance when missing (0: FEA, 1: Analytical) + type_comp_Lr = 0 + # simulation type for stator slot leakage inductance when missing (0: FEA, 1: Analytical) + type_comp_Ls = 0 + + # change from rotor frame to stator frame xi = machine.stator.winding.comp_winding_factor() Ntspc = machine.stator.winding.comp_Ntsp() - norm = (xi[0] * Ntspc) / (Zsr / 6) # rotor - stator transformation factor + K21 = (xi[0] * Ntspc) / (Zsr / 6) # rotor - stator transformation factor + self.parameters["K21"] = K21 - self.parameters["norm"] = norm + # check that parameters are in ELUT, otherwise compute missing ones -> to be put in check_ELUT method? + if "slip" not in self.parameters or self.parameters["slip"] is None: + Nr = output.elec.N0 + Ns = output.elec.felec / p * 60 + slip = (Ns - Nr) / Ns + self.parameters["slip"] = slip - Cond = self.parent.parent.machine.stator.winding.conductor + if "Rs" not in self.parameters or self.parameters["Rs"] is None: + CondS = self.parent.parent.machine.stator.winding.conductor + # get resistance calculated analytically at simulation temperature + Rs = machine.stator.comp_resistance_wind(T=Tsta) + # compute skin_effect on stator side + Xkr_skinS, Xke_skinS = CondS.comp_skin_effect(freq=felec, T=Tsta) + # update resistance value including skin effect + self.parameters["Rs"] = Rs * Xkr_skinS - # compute skin_effect - Xkr_skinS, Xke_skinS = Cond.comp_skin_effect(T=20) + if "Rr" not in self.parameters or self.parameters["Rr"] is None: + # get resistance calculated analytically at simulation temperature + R2 = machine.rotor.comp_resistance_wind(T=Trot, qs=3) + # putting resistance on stator side in EEC + Rr = K21 ** 2 * R2 - alphasw = self.cond_mat.elec.alpha + # compute skin_effect on rotor side + if Xkr_skinR is None: + CondR = self.parent.parent.machine.rotor.winding.conductor + Xkr_skinR, Xke_skinR = CondR.comp_skin_effect( + freq=felec * (self.parameters["slip"]), T=Trot + ) - # alphasw = self.cond_mat.elec.alpha + # update resistance value including skin effect + self.parameters["Rr"] = Rr * Xkr_skinR + if "Rfe" not in self.parameters: + self.parameters["Rfe"] = 1e12 # TODO calculate (or estimate at least) + + if "Lr" not in self.parameters or self.parameters["Lr"] is None: + if type_comp_Lr == 1: + # analytic calculation + # Lr = machine.rotor.slot.comp_inductance_leakage_ANL() #TODO + Lr0 = 0 + if Xke_skinR is None: + CondR = self.parent.parent.machine.rotor.winding.conductor + Xkr_skinR, Xke_skinR = CondR.comp_skin_effect( + freq=felec * (self.parameters["slip"]), T=Trot + ) + Lr = Lr0 * Xke_skinR + else: + # FEA calculation + # Lr = machine.rotor.slot.comp_inductance_leakage_FEA() #TODO + Lr = 0 + + self.parameters["Lr"] = Lr + + if "Ls" not in self.parameters or self.parameters["Ls"] is None: + if type_comp_Ls == 1: + # analytic calculation + # Ls = machine.stator.slot.comp_inductance_leakage_ANL() #TODO + Ls0 = 0 + if Xke_skinS is None: + CondS = self.parent.parent.machine.stator.winding.conductor + Xkr_skinS, Xke_skinS = CondS.comp_skin_effect(freq=felec, T=Tsta) + Ls = Ls0 * Xke_skinS + else: + # FEA calculation + # Lr = machine.rotor.slot.comp_inductance_leakage_FEA() #TODO + Ls = 0 + + self.parameters["Ls"] = Ls + + # alphasw = self.cond_mat.elec.alpha # # stator winding phase resistance, skin effect correction # if felec is None: # Rs_freq = self.Rs @@ -60,141 +131,22 @@ def comp_parameters(self, output): # K_ISE_sta = self.K_ISE_sta # skin effect factor for leakage inductance # Ls_freq = Ls_dc * interp(K_ISE_sta[0, :], K_ISE_sta[1, :], felec) - # get temperatures TODO remove/replace, since this is a temp. solution only - Tws = 20 if "Tws" not in self.parameters else self.parameter["Tws"] - Twr = 20 if "Twr" not in self.parameters else self.parameter["Twr"] - - # Parameters to compute only if they are not set - if "Rs" not in self.parameters or self.parameters["Rs"] is None: - Rs = machine.stator.comp_resistance_wind(T=Tws) - self.parameters["Rs"] = Rs * Xkr_skinS - - if "Rr_norm" not in self.parameters or self.parameters["Rr_norm"] is None: - # 3 phase equivalent rotor resistance - Rr = machine.rotor.comp_resistance_wind(T=Twr, qs=3) - self.parameters["Rr_norm"] = norm ** 2 * Rr - - if "slip" not in self.parameters or self.parameters["slip"] is None: - zp = output.simu.machine.stator.get_pole_pair_number() - Nr = output.elec.N0 - Ns = output.elec.felec / zp * 60 - self.parameters["slip"] = (Ns - Nr) / Ns - # print(f"slip = {(Ns - Nr) / Ns}") - # check if inductances have to be calculated - is_comp_ind = False - - if "Lm" not in self.parameters or self.parameters["Lm"] is None: - is_comp_ind = True + if "Phi_m" not in self.parameters or self.parameters["Phi_m"] is None: + if type_comp_Lm == 1: + # analytic calculation + # Phi_m, I_m = machine.comp_inductance_magnetization_ANL() #TODO + Phi_m = None + I_m = None - if "Ls" not in self.parameters or self.parameters["Ls"] is None: - is_comp_ind = True - - if "Lr_norm" not in self.parameters or self.parameters["Lr_norm"] is None: - is_comp_ind = True - - if "Rfe" not in self.parameters: - self.parameters["Rfe"] = None # TODO calculate (or estimate at least) - - if is_comp_ind: - # setup a MagFEMM simulation to get the parameters - # TODO maybe use IndMagFEMM or FluxlinkageFEMM - # but for now they are not suitable so I utilize 'normal' MagFEMM simu - from ....Classes.Simu1 import Simu1 - from ....Classes.InputCurrent import InputCurrent - from ....Classes.MagFEMM import MagFEMM - from ....Classes.Output import Output - from ....Classes.ImportGenVectLin import ImportGenVectLin - from ....Classes.ImportMatrixVal import ImportMatrixVal - - # set frequency, time and number of revolutions - # TODO what will be the best settings to get a good average with min. samples - if self.N0 is None and self.felec is None: - N0 = 50 * 60 / p - felec = 50 - elif self.N0 is None and self.felec is not None: - N0 = self.felec * 60 / p - elif self.N0 is not None and self.felec is None: - N0 = self.N0 - felec = N0 / 60 * p else: - N0 = self.N0 - felec = self.felec - - Nrev = self.Nrev - - T = Nrev / (N0 / 60) - Ir = ImportMatrixVal(value=zeros((self.Nt_tot, qsr))) - time = ImportGenVectLin(start=0, stop=T, num=self.Nt_tot, endpoint=False) - - # TODO estimate magnetizing current if self.I == None - # TODO compute magnetizing curve as function of I - - # setup the simu object - simu = Simu1(name="EEC_comp_parameter", machine=machine.copy()) - # Definition of the enforced output of the electrical module - simu.input = InputCurrent( - Is=None, - Id_ref=self.I, - Iq_ref=0, - Ir=Ir, # zero current for the rotor - N0=N0, - angle_rotor=None, # Will be computed - time=time, - felec=felec, - ) - - # Definition of the magnetic simulation (no symmetry) - nb_worker = self.nb_worker if self.nb_worker else cpu_count() - - simu.mag = MagFEMM( - type_BH_stator=0, - type_BH_rotor=0, - is_periodicity_a=self.is_periodicity_a, - is_periodicity_t=False, - Kgeo_fineness=0.5, - Kmesh_fineness=0.5, - nb_worker=nb_worker, - ) - simu.force = None - simu.struct = None - - # --- compute the main inductance and stator stray inductance --- - # set output and run first simulation - out = Output(simu=simu) - out.simu.run() - - # compute average rotor and stator fluxlinkage - # TODO check wind_mat that the i-th bars is in the i-th slots - Phi_s, Phi_r = _comp_flux_mean(self, out) - - self.parameters["Lm"] = (Phi_r * norm * Zsr / 3) / self.I - Ls = (Phi_s - (Phi_r * norm * Zsr / 3)) / self.I - self.parameters["Ls"] = Ls * Xke_skinS - # --- compute the main inductance and rotor stray inductance --- - # set new output - out = Output(simu=simu) - - # set current values - Ir_ = zeros([self.Nt_tot, 2]) - Ir_[:, 0] = self.I * norm * sqrt(2) - Ir = ab2n(Ir_, n=qsr // p) # TODO no rotation for now - - Ir = ImportMatrixVal(value=tile(Ir, (1, p))) - - simu.input.Is = None - simu.input.Id_ref = 0 - simu.input.Iq_ref = 0 - simu.input.Ir = Ir - - out.simu.run() - - # compute average rotor and stator fluxlinkage - # TODO check wind_mat that the i-th bars is in the i-th slots - Phi_s, Phi_r = _comp_flux_mean(self, out) - - self.parameters["Lm_"] = Phi_s / self.I - self.parameters["Lr_norm"] = ((Phi_r * norm * Zsr / 3) - Phi_s) / self.I + # FEA calculation + # Lm, Im =comp_Lm_FEA(self) #TODO + Phi_m = None + I_m = None + + self.parameters["Phi_m"] = Phi_m + self.parameters["I_m"] = I_m def _comp_flux_mean(self, out): @@ -240,7 +192,7 @@ def _comp_flux_mean(self, out): # rescale Phi = Phi / (sym * (1 + is_anti_per)) - # compute mean value of periodic bar fluxlinkage + # compute mean value of periodic bar flux linkage Phi_ab = zeros([Phi.shape[0], 2]) if (qsr % p) == 0: qsr_per_pole = qsr // p @@ -251,9 +203,114 @@ def _comp_flux_mean(self, out): else: logger.warning(f"{type(self).__name__}: " + "Not Implemented Yet") - # compute rotor and stator fluxlinkage + # compute rotor and stator flux linkage Phi_r = abs(Phi_ab[:, 0] + 1j * Phi_ab[:, 1]).mean() / sqrt(2) Phi_ab = n2ab(out.mag.Phi_wind["Stator_0"].get_along("time", "phase")["Phi_{wind}"]) Phi_s = abs(Phi_ab[:, 0] + 1j * Phi_ab[:, 1]).mean() / sqrt(2) return Phi_s, Phi_r + + +def _comp_Lm_FEA(self): + + # setup a MagFEMM simulation to get the parameters + # TODO maybe use IndMagFEMM or FluxlinkageFEMM + # but for now they are not suitable so I utilize 'normal' MagFEMM simu + from ....Classes.Simu1 import Simu1 + from ....Classes.InputCurrent import InputCurrent + from ....Classes.MagFEMM import MagFEMM + from ....Classes.Output import Output + from ....Classes.ImportGenVectLin import ImportGenVectLin + from ....Classes.ImportMatrixVal import ImportMatrixVal + + # set frequency, time and number of revolutions + # TODO what will be the best settings to get a good average with min. samples + if self.N0 is None and self.felec is None: + N0 = 50 * 60 / p + felec = 50 + elif self.N0 is None and self.felec is not None: + N0 = self.felec * 60 / p + elif self.N0 is not None and self.felec is None: + N0 = self.N0 + felec = N0 / 60 * p + else: + N0 = self.N0 + felec = self.felec + + Nrev = self.Nrev + + T = Nrev / (N0 / 60) + Ir = ImportMatrixVal(value=zeros((self.Nt_tot, qsr))) + time = ImportGenVectLin(start=0, stop=T, num=self.Nt_tot, endpoint=False) + + # TODO estimate magnetizing current if self.I == None + # TODO compute magnetizing curve as function of I + + # setup the simu object + simu = Simu1(name="EEC_comp_parameter", machine=machine.copy()) + # Definition of the enforced output of the electrical module + simu.input = InputCurrent( + Is=None, + Id_ref=self.I, + Iq_ref=0, + Ir=Ir, # zero current for the rotor + N0=N0, + angle_rotor=None, # Will be computed + time=time, + felec=felec, + ) + + # Definition of the magnetic simulation (no symmetry) + nb_worker = self.nb_worker if self.nb_worker else cpu_count() + + simu.mag = MagFEMM( + type_BH_stator=0, + type_BH_rotor=0, + is_periodicity_a=self.is_periodicity_a, + is_periodicity_t=False, + Kgeo_fineness=0.5, + Kmesh_fineness=0.5, + nb_worker=nb_worker, + ) + simu.force = None + simu.struct = None + + # --- compute the main inductance and stator stray inductance --- + # set output and run first simulation + out = Output(simu=simu) + out.simu.run() + + # compute average rotor and stator fluxlinkage + # TODO check wind_mat that the i-th bars is in the i-th slots + Phi_s, Phi_r = _comp_flux_mean(self, out) + + self.parameters["Lm"] = (Phi_r * K21 * Zsr / 3) / self.I + Ls = (Phi_s - (Phi_r * K21 * Zsr / 3)) / self.I + self.parameters["Ls"] = Ls * Xke_skinS + # --- compute the main inductance and rotor stray inductance --- + # set new output + out = Output(simu=simu) + + # set current values + Ir_ = zeros([self.Nt_tot, 2]) + Ir_[:, 0] = self.I * K21 * sqrt(2) + Ir = ab2n(Ir_, n=qsr // p) # TODO no rotation for now + + Ir = ImportMatrixVal(value=tile(Ir, (1, p))) + + simu.input.Is = None + simu.input.Id_ref = 0 + simu.input.Iq_ref = 0 + simu.input.Ir = Ir + + out.simu.run() + + # compute average rotor and stator fluxlinkage + # TODO check wind_mat that the i-th bars is in the i-th slots + Phi_s, Phi_r = _comp_flux_mean(self, out) + K21 = self.parameters["K21"] + I_m = self.I + Lm = Phi_s / I_m + L2 = ((Phi_r * K21 * Zsr / 3) - Phi_s) / self.I + + return Phi_s, I_m diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC.py b/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC.py index 80906bdba..6fac35709 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC.py @@ -15,9 +15,9 @@ def solve_EEC(self, output): Autor, Publisher ---> ----> - -----Rs------XsIs---- --- -----Rr'----Xr'Ir'---- + -----Rs------XsIs---- --- -----Rr'----XrIr---- | | | | - | Rfe Xm Rr'*(s-1)/s + | Rfe Xm Rr*(s-1)/s | | | | ---------Is---------- --- ---------Ir------------ @@ -32,15 +32,16 @@ def solve_EEC(self, output): an Output object """ Rs = self.parameters["Rs"] - Rr = self.parameters["Rr_norm"] + Rr = self.parameters["Rr"] Rfe = self.parameters["Rfe"] Ls = self.parameters["Ls"] - Lr = self.parameters["Lr_norm"] - Lm = self.parameters["Lm"] - norm = self.parameters["norm"] - + Lr = self.parameters["Lr"] + Phi_m = self.parameters["Phi_m"] + I_m = self.parameters["I_m"] + K21 = self.parameters["K21"] slip = self.parameters["slip"] + Lm = Phi_m[0] / I_m[0] felec = output.elec.felec ws = 2 * pi * felec diff --git a/pyleecan/Methods/Simulation/Electrical/run.py b/pyleecan/Methods/Simulation/Electrical/run.py index 32aa4488f..5b00d7f7f 100644 --- a/pyleecan/Methods/Simulation/Electrical/run.py +++ b/pyleecan/Methods/Simulation/Electrical/run.py @@ -51,10 +51,10 @@ def run(self): # self.eec.gen_drive(output) if self.ELUT_enforced is not None: - # enforce parameters of EEC coming from enforced ELUT at right frequency & temperatures + # enforce parameters of EEC coming from enforced ELUT at right temperatures self.eec.parameters = self.ELUT_enforced.get_param_dict() - # Compute parameters of the electrical equivalent circuit if ELUT not given + # Compute parameters of the electrical equivalent circuit if some parameters are missing in ELUT out_dict = self.eec.comp_parameters(output, Tsta=self.Tsta, Trot=self.Trot) # Solve the electrical equivalent circuit From 90d819c05604e7bbfff7ba309c5429cdd079763e Mon Sep 17 00:00:00 2001 From: Paul GNING Date: Tue, 17 Aug 2021 10:50:29 +0200 Subject: [PATCH 021/167] [WP] skin effect PG --- pyleecan/Methods/Machine/Conductor/comp_skin_effect.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py b/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py index b45440c59..389d31488 100644 --- a/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py +++ b/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py @@ -5,7 +5,7 @@ # from ....Classes.CondType11 import CondType11 # from ....Classes.CondType12 import CondType12 -from numpy import ones, pi, sqrt +from numpy import ones, pi, sqrt, sin def comp_skin_effect(self, T=20): @@ -48,7 +48,9 @@ def comp_skin_effect(self, T=20): Wwire = self.Wwire Nwppc_tan = self.Nwppc Nwppc_rad = self.Nwppc - W2s = Slot.W2 + Alpha_wind = Slot.comp_angle_active_eq() + R_wind = Slot.comp_radius_mid_active() + W2s = 2 * R_wind * sin(Alpha_wind) # average resistance factor over the slot ksi = Hwire * sqrt((1 / 2) * ws * mu0 * sigmar * Nwppc_tan * Wwire / W2s) From d22d0d628577b6fa0df72319f5eb08e4243e9e50 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 25 Aug 2021 09:56:33 +0200 Subject: [PATCH 022/167] [CO] Rework periodicities cf issue #418 --- Tests/Methods/Slot/test_comp_periodicity.py | 4 +-- Tests/Plot/Schematics/test_plot_axis.py | 2 +- .../Validation/Magnetics/test_FEMM_compare.py | 12 +++---- .../Magnetics/test_FEMM_periodicity.py | 6 ++-- .../Magnetics/test_meshsolution_plots.py | 2 +- Tests/Validation/Magnetics/test_torque.py | 2 +- pyleecan/Classes/Class_Dict.json | 15 ++++---- pyleecan/Classes/LamHole.py | 18 +++++----- pyleecan/Classes/LamSlotMag.py | 18 +++++----- pyleecan/Classes/LamSlotMulti.py | 18 +++++----- pyleecan/Classes/LamSlotWind.py | 18 +++++----- pyleecan/Classes/LamSquirrelCage.py | 18 +++++----- pyleecan/Classes/Machine.py | 35 ++++++++++++++----- .../Generator/ClassesRef/Machine/LamHole.csv | 2 +- .../ClassesRef/Machine/LamSlotMag.csv | 2 +- .../ClassesRef/Machine/LamSlotMulti.csv | 2 +- .../ClassesRef/Machine/LamSlotWind.csv | 2 +- .../ClassesRef/Machine/LamSquirrelCage.csv | 2 +- .../Generator/ClassesRef/Machine/Machine.csv | 3 +- ...odicity.py => comp_periodicity_spatial.py} | 11 ++---- .../Machine/LamSlot/comp_periodicity.py | 25 ------------- .../LamSlot/comp_periodicity_spatial.py | 20 +++++++++++ .../Machine/LamSlotMag/comp_periodicity.py | 24 ------------- .../LamSlotMag/comp_periodicity_spatial.py | 17 +++++++++ ...odicity.py => comp_periodicity_spatial.py} | 16 ++------- .../Machine/LamSlotMultiWind/comp_mmf_unit.py | 2 +- .../Machine/LamSlotWind/comp_mmf_unit.py | 2 +- ...odicity.py => comp_periodicity_spatial.py} | 15 ++------ .../LamSquirrelCage/comp_periodicity.py | 24 ------------- .../comp_periodicity_spatial.py | 19 ++++++++++ .../Machine/Machine/comp_periodicity.py | 26 -------------- .../Machine/comp_periodicity_spatial.py | 34 ++++++++++++++++++ .../Machine/Machine/comp_periodicity_time.py | 35 +++++++++++++++++++ .../Machine/Winding/comp_connection_mat.py | 2 +- .../Machine/Winding/comp_periodicity.py | 2 +- .../Machine/Winding/get_periodicity.py | 2 +- .../Output/getter/get_machine_periodicity.py | 9 +++-- .../Simulation/EEC_SCIM/comp_parameters.py | 2 +- .../Methods/Simulation/EEC_SCIM/solve_EEC.py | 12 ++----- .../Simulation/InputCurrent/gen_input.py | 2 +- .../Simulation/StructElmer/gen_mesh.py | 2 +- 41 files changed, 253 insertions(+), 231 deletions(-) rename pyleecan/Methods/Machine/LamHole/{comp_periodicity.py => comp_periodicity_spatial.py} (52%) delete mode 100644 pyleecan/Methods/Machine/LamSlot/comp_periodicity.py create mode 100644 pyleecan/Methods/Machine/LamSlot/comp_periodicity_spatial.py delete mode 100644 pyleecan/Methods/Machine/LamSlotMag/comp_periodicity.py create mode 100644 pyleecan/Methods/Machine/LamSlotMag/comp_periodicity_spatial.py rename pyleecan/Methods/Machine/LamSlotMulti/{comp_periodicity.py => comp_periodicity_spatial.py} (64%) rename pyleecan/Methods/Machine/LamSlotWind/{comp_periodicity.py => comp_periodicity_spatial.py} (54%) delete mode 100644 pyleecan/Methods/Machine/LamSquirrelCage/comp_periodicity.py create mode 100644 pyleecan/Methods/Machine/LamSquirrelCage/comp_periodicity_spatial.py delete mode 100644 pyleecan/Methods/Machine/Machine/comp_periodicity.py create mode 100644 pyleecan/Methods/Machine/Machine/comp_periodicity_spatial.py create mode 100644 pyleecan/Methods/Machine/Machine/comp_periodicity_time.py diff --git a/Tests/Methods/Slot/test_comp_periodicity.py b/Tests/Methods/Slot/test_comp_periodicity.py index a34260ad0..1cf92579d 100644 --- a/Tests/Methods/Slot/test_comp_periodicity.py +++ b/Tests/Methods/Slot/test_comp_periodicity.py @@ -8,7 +8,7 @@ @pytest.mark.periodicity -def test_comp_periodicity(): +def test_comp_periodicity_spatial(): rotor = LamSlotWind( Rint=0.2, Rext=0.5, @@ -19,4 +19,4 @@ def test_comp_periodicity(): Wrvd=0.05, ) rotor.winding = None - assert rotor.comp_periodicity() == (1, False, 1, False) + assert rotor.comp_periodicity_spatial() == (1, False) diff --git a/Tests/Plot/Schematics/test_plot_axis.py b/Tests/Plot/Schematics/test_plot_axis.py index 6b3567a4c..3809eae9a 100644 --- a/Tests/Plot/Schematics/test_plot_axis.py +++ b/Tests/Plot/Schematics/test_plot_axis.py @@ -239,7 +239,7 @@ def test_axis_LamWind(CURVE_COLORS): mmf_waveform = magmax * cos(p * angle_rotor + phimax) ind_max = argmax(mmf_waveform) d_angle = angle_rotor[ind_max] - (per_a, _, _, _) = SCIM_001.stator.comp_periodicity() + per_a, _ = SCIM_001.stator.comp_periodicity_spatial() d_angle = d_angle % (2 * pi / per_a) fig = plt.figure("MMF fundamental") diff --git a/Tests/Validation/Magnetics/test_FEMM_compare.py b/Tests/Validation/Magnetics/test_FEMM_compare.py index 261d1b985..eded297b9 100644 --- a/Tests/Validation/Magnetics/test_FEMM_compare.py +++ b/Tests/Validation/Magnetics/test_FEMM_compare.py @@ -60,7 +60,7 @@ def test_IPMSM_xxx(): simu.force = None simu.struct = None - assert IPMSM_xxx.comp_periodicity() == (4, True, 4, True) + assert IPMSM_xxx.comp_periodicity_spatial() == (4, True) # Copy the simu and activate the symmetry simu_sym = Simu1(init_dict=simu.as_dict()) simu_sym.mag.is_periodicity_a = True @@ -129,7 +129,7 @@ def test_Toyota_Prius(): simu.struct = None # simu.struct.force = ForceMT() # Copy the simu and activate the symmetry - assert Toyota_Prius.comp_periodicity() == (4, True, 4, True) + assert Toyota_Prius.comp_periodicity_spatial() == (4, True) simu_sym = Simu1(init_dict=simu.as_dict()) simu_sym.mag.is_periodicity_a = True @@ -199,7 +199,7 @@ def test_SCIM(): simu.force = None simu.struct = None # Copy the simu and activate the symmetry - assert SCIM_006.comp_periodicity() == (2, True, 28, False) + assert SCIM_006.comp_periodicity_spatial() == (2, True) simu_sym = Simu1(init_dict=simu.as_dict()) simu_sym.mag.is_periodicity_a = True @@ -286,7 +286,7 @@ def test_SIPMSM(): ) # Definition of the magnetic simulation (is_mmfr=False => no flux from the magnets) - assert SIPMSM_001.comp_periodicity() == (1, False, 2, True) + assert SIPMSM_001.comp_periodicity_spatial() == (1, False) simu.mag = MagFEMM( type_BH_stator=2, type_BH_rotor=2, @@ -378,7 +378,7 @@ def test_SPMSM_load(): simu.force = None simu.struct = None # Copy the simu and activate the symmetry - assert SPMSM_003.comp_periodicity() == (1, True, 1, True) + assert SPMSM_003.comp_periodicity_spatial() == (1, True) simu_sym = Simu1(init_dict=simu.as_dict()) simu_sym.mag.is_periodicity_a = True @@ -460,7 +460,7 @@ def test_SPMSM_noload(): ) # Definition of the magnetic simulation (is_mmfr=False => no flux from the magnets) - assert SPMSM_015.comp_periodicity() == (9, False, 9, True) + assert SPMSM_015.comp_periodicity_spatial() == (9, False) simu.mag = MagFEMM( type_BH_stator=0, type_BH_rotor=0, diff --git a/Tests/Validation/Magnetics/test_FEMM_periodicity.py b/Tests/Validation/Magnetics/test_FEMM_periodicity.py index b8f3b5564..e3e594b57 100644 --- a/Tests/Validation/Magnetics/test_FEMM_periodicity.py +++ b/Tests/Validation/Magnetics/test_FEMM_periodicity.py @@ -32,7 +32,7 @@ def test_FEMM_periodicity_time_no_periodicity_a(): SPMSM_015 = load(join(DATA_DIR, "Machine", "SPMSM_015.json")) - assert SPMSM_015.comp_periodicity() == (9, False, 9, True) + assert SPMSM_015.comp_periodicity_spatial() == (9, False) simu = Simu1(name="test_FEMM_periodicity_time_no_periodicity_a", machine=SPMSM_015) @@ -179,7 +179,7 @@ def test_FEMM_periodicity_time(): SPMSM_015 = load(join(DATA_DIR, "Machine", "SPMSM_015.json")) - assert SPMSM_015.comp_periodicity() == (9, False, 9, True) + assert SPMSM_015.comp_periodicity_spatial() == (9, False) simu = Simu1(name="test_FEMM_periodicity_time", machine=SPMSM_015) @@ -326,7 +326,7 @@ def test_FEMM_periodicity_angle(): SPMSM_015 = load(join(DATA_DIR, "Machine", "SPMSM_015.json")) - assert SPMSM_015.comp_periodicity() == (9, False, 9, True) + assert SPMSM_015.comp_periodicity_spatial() == (9, False) simu = Simu1(name="test_FEMM_periodicity_angle", machine=SPMSM_015) diff --git a/Tests/Validation/Magnetics/test_meshsolution_plots.py b/Tests/Validation/Magnetics/test_meshsolution_plots.py index e372b8261..3f2ee3b8e 100644 --- a/Tests/Validation/Magnetics/test_meshsolution_plots.py +++ b/Tests/Validation/Magnetics/test_meshsolution_plots.py @@ -73,7 +73,7 @@ def test_SPMSM(): simu.force = None simu.struct = None # Copy the simu and activate the symmetry - assert SPMSM_003.comp_periodicity() == (1, True, 1, True) + assert SPMSM_003.comp_periodicity_spatial() == (1, True) simu_sym = Simu1(init_dict=simu.as_dict()) simu_sym.mag.is_periodicity_a = True diff --git a/Tests/Validation/Magnetics/test_torque.py b/Tests/Validation/Magnetics/test_torque.py index e3b8edbcf..990473c8f 100644 --- a/Tests/Validation/Magnetics/test_torque.py +++ b/Tests/Validation/Magnetics/test_torque.py @@ -110,7 +110,7 @@ def test_torque(): simu.input.set_OP_from_array(OP_matrix, type_OP_matrix=varload.type_OP_matrix) # Definition of the magnetic simulation (1/2 symmetry) - assert SynRM_001.comp_periodicity() == (2, True, 2, True) + assert SynRM_001.comp_periodicity_spatial() == (2, True) simu.mag = MagFEMM( type_BH_stator=0, type_BH_rotor=0, diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index ac0ee37f4..3007aeb9c 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4536,7 +4536,7 @@ "comp_radius_mid_yoke", "has_magnet", "comp_angle_d_axis", - "comp_periodicity", + "comp_periodicity_spatial", "set_pole_pair_number" ], "mother": "Lamination", @@ -4618,7 +4618,7 @@ "comp_volumes", "plot", "comp_angle_d_axis", - "comp_periodicity" + "comp_periodicity_spatial" ], "mother": "LamSlot", "name": "LamSlotMag", @@ -4657,7 +4657,7 @@ "comp_height_yoke", "get_Zs", "get_bore_desc", - "comp_periodicity" + "comp_periodicity_spatial" ], "mother": "Lamination", "name": "LamSlotMulti", @@ -4771,7 +4771,7 @@ "comp_rot_dir", "comp_lengths_winding", "comp_number_phase_eq", - "comp_periodicity", + "comp_periodicity_spatial", "set_pole_pair_number" ], "mother": "LamSlot", @@ -4817,7 +4817,7 @@ "comp_length_ring", "plot", "comp_number_phase_eq", - "comp_periodicity", + "comp_periodicity_spatial", "comp_surface_ring", "comp_resistance_wind" ], @@ -5342,7 +5342,7 @@ "comp_masses", "comp_output_geo", "comp_Rgap_mec", - "comp_periodicity", + "comp_periodicity_spatial", "comp_width_airgap_mag", "comp_width_airgap_mec", "get_material_dict", @@ -5354,7 +5354,8 @@ "get_lam_by_label", "get_lam_index", "get_pole_pair_number", - "set_pole_pair_number" + "set_pole_pair_number", + "comp_periodicity_time" ], "mother": "", "name": "Machine", diff --git a/pyleecan/Classes/LamHole.py b/pyleecan/Classes/LamHole.py index 7ea1aa750..d261cda26 100644 --- a/pyleecan/Classes/LamHole.py +++ b/pyleecan/Classes/LamHole.py @@ -68,9 +68,11 @@ comp_angle_d_axis = error try: - from ..Methods.Machine.LamHole.comp_periodicity import comp_periodicity + from ..Methods.Machine.LamHole.comp_periodicity_spatial import ( + comp_periodicity_spatial, + ) except ImportError as error: - comp_periodicity = error + comp_periodicity_spatial = error try: from ..Methods.Machine.LamHole.set_pole_pair_number import set_pole_pair_number @@ -199,18 +201,18 @@ class LamHole(Lamination): ) else: comp_angle_d_axis = comp_angle_d_axis - # cf Methods.Machine.LamHole.comp_periodicity - if isinstance(comp_periodicity, ImportError): - comp_periodicity = property( + # cf Methods.Machine.LamHole.comp_periodicity_spatial + if isinstance(comp_periodicity_spatial, ImportError): + comp_periodicity_spatial = property( fget=lambda x: raise_( ImportError( - "Can't use LamHole method comp_periodicity: " - + str(comp_periodicity) + "Can't use LamHole method comp_periodicity_spatial: " + + str(comp_periodicity_spatial) ) ) ) else: - comp_periodicity = comp_periodicity + comp_periodicity_spatial = comp_periodicity_spatial # cf Methods.Machine.LamHole.set_pole_pair_number if isinstance(set_pole_pair_number, ImportError): set_pole_pair_number = property( diff --git a/pyleecan/Classes/LamSlotMag.py b/pyleecan/Classes/LamSlotMag.py index 1c00c82f9..56ac7522f 100644 --- a/pyleecan/Classes/LamSlotMag.py +++ b/pyleecan/Classes/LamSlotMag.py @@ -58,9 +58,11 @@ comp_angle_d_axis = error try: - from ..Methods.Machine.LamSlotMag.comp_periodicity import comp_periodicity + from ..Methods.Machine.LamSlotMag.comp_periodicity_spatial import ( + comp_periodicity_spatial, + ) except ImportError as error: - comp_periodicity = error + comp_periodicity_spatial = error from ._check import InitUnKnowClassError @@ -164,18 +166,18 @@ class LamSlotMag(LamSlot): ) else: comp_angle_d_axis = comp_angle_d_axis - # cf Methods.Machine.LamSlotMag.comp_periodicity - if isinstance(comp_periodicity, ImportError): - comp_periodicity = property( + # cf Methods.Machine.LamSlotMag.comp_periodicity_spatial + if isinstance(comp_periodicity_spatial, ImportError): + comp_periodicity_spatial = property( fget=lambda x: raise_( ImportError( - "Can't use LamSlotMag method comp_periodicity: " - + str(comp_periodicity) + "Can't use LamSlotMag method comp_periodicity_spatial: " + + str(comp_periodicity_spatial) ) ) ) else: - comp_periodicity = comp_periodicity + comp_periodicity_spatial = comp_periodicity_spatial # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/LamSlotMulti.py b/pyleecan/Classes/LamSlotMulti.py index 9e34b59cd..7a193fe51 100644 --- a/pyleecan/Classes/LamSlotMulti.py +++ b/pyleecan/Classes/LamSlotMulti.py @@ -58,9 +58,11 @@ get_bore_desc = error try: - from ..Methods.Machine.LamSlotMulti.comp_periodicity import comp_periodicity + from ..Methods.Machine.LamSlotMulti.comp_periodicity_spatial import ( + comp_periodicity_spatial, + ) except ImportError as error: - comp_periodicity = error + comp_periodicity_spatial = error from numpy import array, array_equal @@ -163,18 +165,18 @@ class LamSlotMulti(Lamination): ) else: get_bore_desc = get_bore_desc - # cf Methods.Machine.LamSlotMulti.comp_periodicity - if isinstance(comp_periodicity, ImportError): - comp_periodicity = property( + # cf Methods.Machine.LamSlotMulti.comp_periodicity_spatial + if isinstance(comp_periodicity_spatial, ImportError): + comp_periodicity_spatial = property( fget=lambda x: raise_( ImportError( - "Can't use LamSlotMulti method comp_periodicity: " - + str(comp_periodicity) + "Can't use LamSlotMulti method comp_periodicity_spatial: " + + str(comp_periodicity_spatial) ) ) ) else: - comp_periodicity = comp_periodicity + comp_periodicity_spatial = comp_periodicity_spatial # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/LamSlotWind.py b/pyleecan/Classes/LamSlotWind.py index a789a5395..d0010ada0 100644 --- a/pyleecan/Classes/LamSlotWind.py +++ b/pyleecan/Classes/LamSlotWind.py @@ -113,9 +113,11 @@ comp_number_phase_eq = error try: - from ..Methods.Machine.LamSlotWind.comp_periodicity import comp_periodicity + from ..Methods.Machine.LamSlotWind.comp_periodicity_spatial import ( + comp_periodicity_spatial, + ) except ImportError as error: - comp_periodicity = error + comp_periodicity_spatial = error try: from ..Methods.Machine.LamSlotWind.set_pole_pair_number import set_pole_pair_number @@ -352,18 +354,18 @@ class LamSlotWind(LamSlot): ) else: comp_number_phase_eq = comp_number_phase_eq - # cf Methods.Machine.LamSlotWind.comp_periodicity - if isinstance(comp_periodicity, ImportError): - comp_periodicity = property( + # cf Methods.Machine.LamSlotWind.comp_periodicity_spatial + if isinstance(comp_periodicity_spatial, ImportError): + comp_periodicity_spatial = property( fget=lambda x: raise_( ImportError( - "Can't use LamSlotWind method comp_periodicity: " - + str(comp_periodicity) + "Can't use LamSlotWind method comp_periodicity_spatial: " + + str(comp_periodicity_spatial) ) ) ) else: - comp_periodicity = comp_periodicity + comp_periodicity_spatial = comp_periodicity_spatial # cf Methods.Machine.LamSlotWind.set_pole_pair_number if isinstance(set_pole_pair_number, ImportError): set_pole_pair_number = property( diff --git a/pyleecan/Classes/LamSquirrelCage.py b/pyleecan/Classes/LamSquirrelCage.py index bbee85015..09c74d8eb 100644 --- a/pyleecan/Classes/LamSquirrelCage.py +++ b/pyleecan/Classes/LamSquirrelCage.py @@ -45,9 +45,11 @@ comp_number_phase_eq = error try: - from ..Methods.Machine.LamSquirrelCage.comp_periodicity import comp_periodicity + from ..Methods.Machine.LamSquirrelCage.comp_periodicity_spatial import ( + comp_periodicity_spatial, + ) except ImportError as error: - comp_periodicity = error + comp_periodicity_spatial = error try: from ..Methods.Machine.LamSquirrelCage.comp_surface_ring import comp_surface_ring @@ -131,18 +133,18 @@ class LamSquirrelCage(LamSlotWind): ) else: comp_number_phase_eq = comp_number_phase_eq - # cf Methods.Machine.LamSquirrelCage.comp_periodicity - if isinstance(comp_periodicity, ImportError): - comp_periodicity = property( + # cf Methods.Machine.LamSquirrelCage.comp_periodicity_spatial + if isinstance(comp_periodicity_spatial, ImportError): + comp_periodicity_spatial = property( fget=lambda x: raise_( ImportError( - "Can't use LamSquirrelCage method comp_periodicity: " - + str(comp_periodicity) + "Can't use LamSquirrelCage method comp_periodicity_spatial: " + + str(comp_periodicity_spatial) ) ) ) else: - comp_periodicity = comp_periodicity + comp_periodicity_spatial = comp_periodicity_spatial # cf Methods.Machine.LamSquirrelCage.comp_surface_ring if isinstance(comp_surface_ring, ImportError): comp_surface_ring = property( diff --git a/pyleecan/Classes/Machine.py b/pyleecan/Classes/Machine.py index a2b24c07f..49e1b04ac 100644 --- a/pyleecan/Classes/Machine.py +++ b/pyleecan/Classes/Machine.py @@ -62,9 +62,11 @@ comp_Rgap_mec = error try: - from ..Methods.Machine.Machine.comp_periodicity import comp_periodicity + from ..Methods.Machine.Machine.comp_periodicity_spatial import ( + comp_periodicity_spatial, + ) except ImportError as error: - comp_periodicity = error + comp_periodicity_spatial = error try: from ..Methods.Machine.Machine.comp_width_airgap_mag import comp_width_airgap_mag @@ -126,6 +128,11 @@ except ImportError as error: set_pole_pair_number = error +try: + from ..Methods.Machine.Machine.comp_periodicity_time import comp_periodicity_time +except ImportError as error: + comp_periodicity_time = error + from ._check import InitUnKnowClassError from .Frame import Frame @@ -224,18 +231,18 @@ class Machine(FrozenClass): ) else: comp_Rgap_mec = comp_Rgap_mec - # cf Methods.Machine.Machine.comp_periodicity - if isinstance(comp_periodicity, ImportError): - comp_periodicity = property( + # cf Methods.Machine.Machine.comp_periodicity_spatial + if isinstance(comp_periodicity_spatial, ImportError): + comp_periodicity_spatial = property( fget=lambda x: raise_( ImportError( - "Can't use Machine method comp_periodicity: " - + str(comp_periodicity) + "Can't use Machine method comp_periodicity_spatial: " + + str(comp_periodicity_spatial) ) ) ) else: - comp_periodicity = comp_periodicity + comp_periodicity_spatial = comp_periodicity_spatial # cf Methods.Machine.Machine.comp_width_airgap_mag if isinstance(comp_width_airgap_mag, ImportError): comp_width_airgap_mag = property( @@ -373,6 +380,18 @@ class Machine(FrozenClass): ) else: set_pole_pair_number = set_pole_pair_number + # cf Methods.Machine.Machine.comp_periodicity_time + if isinstance(comp_periodicity_time, ImportError): + comp_periodicity_time = property( + fget=lambda x: raise_( + ImportError( + "Can't use Machine method comp_periodicity_time: " + + str(comp_periodicity_time) + ) + ) + ) + else: + comp_periodicity_time = comp_periodicity_time # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Generator/ClassesRef/Machine/LamHole.csv b/pyleecan/Generator/ClassesRef/Machine/LamHole.csv index c25f02738..f85a325b1 100644 --- a/pyleecan/Generator/ClassesRef/Machine/LamHole.csv +++ b/pyleecan/Generator/ClassesRef/Machine/LamHole.csv @@ -9,5 +9,5 @@ hole,,lamination Hole,1,[Hole],,,,,Machine,Lamination,build_geometry,VERSION,1,L ,,,,,,,,,,,comp_radius_mid_yoke,,, ,,,,,,,,,,,has_magnet,,, ,,,,,,,,,,,comp_angle_d_axis,,, -,,,,,,,,,,,comp_periodicity,,, +,,,,,,,,,,,comp_periodicity_spatial,,, ,,,,,,,,,,,set_pole_pair_number,,, diff --git a/pyleecan/Generator/ClassesRef/Machine/LamSlotMag.csv b/pyleecan/Generator/ClassesRef/Machine/LamSlotMag.csv index 4bc7fae2b..8ce06c1ab 100644 --- a/pyleecan/Generator/ClassesRef/Machine/LamSlotMag.csv +++ b/pyleecan/Generator/ClassesRef/Machine/LamSlotMag.csv @@ -7,4 +7,4 @@ magnet,-,Magnet of the lamination,,Magnet,,,,,Machine,LamSlot,build_geometry,VER ,,,,,,,,,,,comp_volumes,,, ,,,,,,,,,,,plot,,, ,,,,,,,,,,,comp_angle_d_axis,,, -,,,,,,,,,,,comp_periodicity,,, +,,,,,,,,,,,comp_periodicity_spatial,,, diff --git a/pyleecan/Generator/ClassesRef/Machine/LamSlotMulti.csv b/pyleecan/Generator/ClassesRef/Machine/LamSlotMulti.csv index 60d23a9f8..b1efe6473 100644 --- a/pyleecan/Generator/ClassesRef/Machine/LamSlotMulti.csv +++ b/pyleecan/Generator/ClassesRef/Machine/LamSlotMulti.csv @@ -7,4 +7,4 @@ sym_dict_enforced,,Dictionary to enforce the lamination symmetry,,dict,None,,,,, ,,,,,,,,,,,comp_height_yoke,,,, ,,,,,,,,,,,get_Zs,,,, ,,,,,,,,,,,get_bore_desc,,,, -,,,,,,,,,,,comp_periodicity,,,, +,,,,,,,,,,,comp_periodicity_spatial,,,, diff --git a/pyleecan/Generator/ClassesRef/Machine/LamSlotWind.csv b/pyleecan/Generator/ClassesRef/Machine/LamSlotWind.csv index ef7b5f88a..40a0e192d 100644 --- a/pyleecan/Generator/ClassesRef/Machine/LamSlotWind.csv +++ b/pyleecan/Generator/ClassesRef/Machine/LamSlotWind.csv @@ -18,5 +18,5 @@ winding,,Lamination's Winding,,Winding,,,,,,,check,,, ,,,,,,,,,,,comp_rot_dir,,, ,,,,,,,,,,,comp_lengths_winding,,, ,,,,,,,,,,,comp_number_phase_eq,,, -,,,,,,,,,,,comp_periodicity,,, +,,,,,,,,,,,comp_periodicity_spatial,,, ,,,,,,,,,,,set_pole_pair_number,,, diff --git a/pyleecan/Generator/ClassesRef/Machine/LamSquirrelCage.csv b/pyleecan/Generator/ClassesRef/Machine/LamSquirrelCage.csv index 85afda592..213abff96 100644 --- a/pyleecan/Generator/ClassesRef/Machine/LamSquirrelCage.csv +++ b/pyleecan/Generator/ClassesRef/Machine/LamSquirrelCage.csv @@ -4,6 +4,6 @@ Lscr,m,short circuit ring section axial length,0,float,1.50E-02,0,,,,,check,,, ring_mat,,Material of the Rotor short circuit ring,,Material,,,,,,,comp_length_ring,,, ,,,,,,,,,,,plot,,, ,,,,,,,,,,,comp_number_phase_eq,,, -,,,,,,,,,,,comp_periodicity,,, +,,,,,,,,,,,comp_periodicity_spatial,,, ,,,,,,,,,,,comp_surface_ring,,, ,,,,,,,,,,,comp_resistance_wind,,, diff --git a/pyleecan/Generator/ClassesRef/Machine/Machine.csv b/pyleecan/Generator/ClassesRef/Machine/Machine.csv index 8eb7b8aff..ba76af78b 100644 --- a/pyleecan/Generator/ClassesRef/Machine/Machine.csv +++ b/pyleecan/Generator/ClassesRef/Machine/Machine.csv @@ -7,7 +7,7 @@ type_machine,,"Integer to store the machine type (for the GUI, should be replace logger_name,-,Name of the logger to use,0,str,Pyleecan.Machine,,,,,,comp_masses,,, ,,,,,,,,,,,comp_output_geo,,, ,,,,,,,,,,,comp_Rgap_mec,,, -,,,,,,,,,,,comp_periodicity,,, +,,,,,,,,,,,comp_periodicity_spatial,,, ,,,,,,,,,,,comp_width_airgap_mag,,, ,,,,,,,,,,,comp_width_airgap_mec,,, ,,,,,,,,,,,get_material_dict,,, @@ -20,3 +20,4 @@ logger_name,-,Name of the logger to use,0,str,Pyleecan.Machine,,,,,,comp_masses, ,,,,,,,,,,,get_lam_index,,, ,,,,,,,,,,,get_pole_pair_number,,, ,,,,,,,,,,,set_pole_pair_number,,, +,,,,,,,,,,,comp_periodicity_time,,, diff --git a/pyleecan/Methods/Machine/LamHole/comp_periodicity.py b/pyleecan/Methods/Machine/LamHole/comp_periodicity_spatial.py similarity index 52% rename from pyleecan/Methods/Machine/LamHole/comp_periodicity.py rename to pyleecan/Methods/Machine/LamHole/comp_periodicity_spatial.py index a807985cf..9087a74f6 100644 --- a/pyleecan/Methods/Machine/LamHole/comp_periodicity.py +++ b/pyleecan/Methods/Machine/LamHole/comp_periodicity_spatial.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- - - -def comp_periodicity(self): +def comp_periodicity_spatial(self): """Compute the periodicity factor of the lamination Parameters @@ -15,10 +12,6 @@ def comp_periodicity(self): Number of spatial periodicities of the lamination is_antiper_a : bool True if an spatial anti-periodicity is possible after the periodicities - per_t : int - Number of time periodicities of the lamination - is_antiper_t : bool - True if an time anti-periodicity is possible after the periodicities """ - return self.get_pole_pair_number(), True, self.get_pole_pair_number(), True + return self.get_pole_pair_number(), True diff --git a/pyleecan/Methods/Machine/LamSlot/comp_periodicity.py b/pyleecan/Methods/Machine/LamSlot/comp_periodicity.py deleted file mode 100644 index 0b4b338b9..000000000 --- a/pyleecan/Methods/Machine/LamSlot/comp_periodicity.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- - - -def comp_periodicity(self): - """Compute the periodicity factor of the lamination - - Parameters - ---------- - self : LamSlot - A LamSlot object - - Returns - ------- - per_a : int - Number of spatial periodicities of the lamination - is_antiper_a : bool - True if an spatial anti-periodicity is possible after the periodicities - per_t : int - Number of time periodicities of the lamination - is_antiper_t : bool - True if an time anti-periodicity is possible after the periodicities - - """ - - return self.get_Zs(), False, self.get_Zs(), False diff --git a/pyleecan/Methods/Machine/LamSlot/comp_periodicity_spatial.py b/pyleecan/Methods/Machine/LamSlot/comp_periodicity_spatial.py new file mode 100644 index 000000000..be778679b --- /dev/null +++ b/pyleecan/Methods/Machine/LamSlot/comp_periodicity_spatial.py @@ -0,0 +1,20 @@ +def comp_periodicity_spatial(self): + """Compute the periodicity factor of the lamination + + Parameters + ---------- + self : LamSlot + A LamSlot object + + Returns + ------- + per_a : int + Number of spatial periodicities of the lamination over 2*pi + is_antiper_a : bool + True if an spatial anti-periodicity is possible after the periodicities + + """ + + Zs = self.get_Zs() + + return Zs, bool(Zs % 2 == 0) diff --git a/pyleecan/Methods/Machine/LamSlotMag/comp_periodicity.py b/pyleecan/Methods/Machine/LamSlotMag/comp_periodicity.py deleted file mode 100644 index 637a99607..000000000 --- a/pyleecan/Methods/Machine/LamSlotMag/comp_periodicity.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- - - -def comp_periodicity(self): - """Compute the periodicity factor of the lamination - - Parameters - ---------- - self : LamSlotMag - A LamSlotMag object - - Returns - ------- - per_a : int - Number of spatial periodicities of the lamination - is_antiper_a : bool - True if an spatial anti-periodicity is possible after the periodicities - per_t : int - Number of time periodicities of the lamination - is_antiper_t : bool - True if an time anti-periodicity is possible after the periodicities - """ - - return self.get_pole_pair_number(), True, self.get_pole_pair_number(), True diff --git a/pyleecan/Methods/Machine/LamSlotMag/comp_periodicity_spatial.py b/pyleecan/Methods/Machine/LamSlotMag/comp_periodicity_spatial.py new file mode 100644 index 000000000..144c9ee8b --- /dev/null +++ b/pyleecan/Methods/Machine/LamSlotMag/comp_periodicity_spatial.py @@ -0,0 +1,17 @@ +def comp_periodicity_spatial(self): + """Compute the periodicity factor of the lamination + + Parameters + ---------- + self : LamSlotMag + A LamSlotMag object + + Returns + ------- + per_a : int + Number of spatial periodicities of the lamination over 2*pi + is_antiper_a : bool + True if an spatial anti-periodicity is possible after the periodicities + """ + + return self.get_pole_pair_number(), True diff --git a/pyleecan/Methods/Machine/LamSlotMulti/comp_periodicity.py b/pyleecan/Methods/Machine/LamSlotMulti/comp_periodicity_spatial.py similarity index 64% rename from pyleecan/Methods/Machine/LamSlotMulti/comp_periodicity.py rename to pyleecan/Methods/Machine/LamSlotMulti/comp_periodicity_spatial.py index 7a55ee775..23c3f7419 100644 --- a/pyleecan/Methods/Machine/LamSlotMulti/comp_periodicity.py +++ b/pyleecan/Methods/Machine/LamSlotMulti/comp_periodicity_spatial.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- - - -def comp_periodicity(self, p=None): +def comp_periodicity_spatial(self): """Compute the periodicity factor of the lamination Parameters @@ -15,11 +12,6 @@ def comp_periodicity(self, p=None): Number of spatial periodicities of the lamination is_antiper_a : bool True if an spatial anti-periodicity is possible after the periodicities - per_t : int - Number of time periodicities of the lamination - is_antiper_t : bool - True if an time anti-periodicity is possible after the periodicities - """ if self.sym_dict_enforced is not None: @@ -27,12 +19,10 @@ def comp_periodicity(self, p=None): return ( self.sym_dict_enforced["per_a"], self.sym_dict_enforced["is_antiper_a"], - self.sym_dict_enforced["per_t"], - self.sym_dict_enforced["is_antiper_t"], ) else: - Zs = self.get_Zs() + # Zs = self.get_Zs() is_aper = False # TODO compute it self.get_logger().debug("Symmetry not available yet for LamSlotMulti") - return 1, is_aper, 1, is_aper + return 1, is_aper diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py index af42e409f..43dd641f2 100644 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py @@ -36,7 +36,7 @@ def comp_mmf_unit(self, Na=None, Nt=None, freq=1): p = self.get_pole_pair_number() # Get spatial symmetry - per_a, _, _, _ = self.comp_periodicity(p=p) + per_a, _ = self.comp_periodicity_spatial() # Define the space dicretization angle = linspace(0, 2 * pi / per_a, Na, endpoint=False) diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py index d37addbde..ca3b4edae 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py @@ -33,7 +33,7 @@ def comp_mmf_unit(self, Na=None, Nt=None, freq=1): qs = self.winding.qs # Get spatial symmetry - per_a, _, _, _ = self.comp_periodicity() + per_a, _ = self.comp_periodicity_spatial() # Define the space dicretization angle = linspace(0, 2 * pi / per_a, Na, endpoint=False) diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_periodicity.py b/pyleecan/Methods/Machine/LamSlotWind/comp_periodicity_spatial.py similarity index 54% rename from pyleecan/Methods/Machine/LamSlotWind/comp_periodicity.py rename to pyleecan/Methods/Machine/LamSlotWind/comp_periodicity_spatial.py index 19a74b1a3..8e47866eb 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_periodicity.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_periodicity_spatial.py @@ -1,9 +1,4 @@ -# -*- coding: utf-8 -*- -from ....Classes.Winding import Winding -from ....Classes.Conductor import Conductor - - -def comp_periodicity(self): +def comp_periodicity_spatial(self): """Compute the periodicity factor of the lamination Parameters @@ -14,13 +9,9 @@ def comp_periodicity(self): Returns ------- per_a : int - Number of spatial periodicities of the lamination + Number of spatial periodicities of the lamination over 2*pi is_antiper_a : bool True if an spatial anti-periodicity is possible after the periodicities - per_t : int - Number of time periodicities of the lamination - is_antiper_t : bool - True if an time anti-periodicity is possible after the periodicities """ if self.winding is not None and self.winding.conductor is not None: @@ -31,4 +22,4 @@ def comp_periodicity(self): if is_antisym_a: sym_a /= 2 - return (int(sym_a), is_antisym_a, sym_a, is_antisym_a) + return int(sym_a), is_antisym_a diff --git a/pyleecan/Methods/Machine/LamSquirrelCage/comp_periodicity.py b/pyleecan/Methods/Machine/LamSquirrelCage/comp_periodicity.py deleted file mode 100644 index 8737b1151..000000000 --- a/pyleecan/Methods/Machine/LamSquirrelCage/comp_periodicity.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- - - -def comp_periodicity(self): - """Compute the periodicity factor of the lamination - - Parameters - ---------- - self : LamSquirrelCage - A LamSquirrelCage object - - Returns - ------- - per_a : int - Number of spatial periodicities of the lamination - is_antiper_a : bool - True if an spatial anti-periodicity is possible after the periodicities - per_t : int - Number of time periodicities of the lamination - is_antiper_t : bool - True if an time anti-periodicity is possible after the periodicities - """ - - return self.slot.Zs, self.slot.Zs % 2 == 0, self.slot.Zs, False diff --git a/pyleecan/Methods/Machine/LamSquirrelCage/comp_periodicity_spatial.py b/pyleecan/Methods/Machine/LamSquirrelCage/comp_periodicity_spatial.py new file mode 100644 index 000000000..f255463d9 --- /dev/null +++ b/pyleecan/Methods/Machine/LamSquirrelCage/comp_periodicity_spatial.py @@ -0,0 +1,19 @@ +def comp_periodicity_spatial(self): + """Compute the periodicity factor of the lamination + + Parameters + ---------- + self : LamSquirrelCage + A LamSquirrelCage object + + Returns + ------- + per_a : int + Number of spatial periodicities of the lamination over 2*pi + is_antiper_a : bool + True if an spatial anti-periodicity is possible after the periodicities + """ + + Zs = self.get_Zs() + + return Zs, bool(Zs % 2 == 0) diff --git a/pyleecan/Methods/Machine/Machine/comp_periodicity.py b/pyleecan/Methods/Machine/Machine/comp_periodicity.py deleted file mode 100644 index 2895654e9..000000000 --- a/pyleecan/Methods/Machine/Machine/comp_periodicity.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -from math import gcd - - -def comp_periodicity(self): - """Compute the (anti)-periodicities of the machine in time and space domain - - Parameters - ---------- - self : Machine - A Machine object - - Returns - ------- - per : int - Number of periodicities of the machine - is_antisym : bool - True if an anti-periodicity is possible after the periodicities - """ - # Get stator (anti)-periodicity in spatial domain only (because stator is not moving regarding time) - pera_s, is_antipera_s, _, _ = self.stator.comp_periodicity() - - # Get rotor (anti)-periodicities both in time and spatial domains - pera_r, is_antipera_r, pert_r, is_antipert_r = self.rotor.comp_periodicity() - - return (gcd(pera_s, pera_r), is_antipera_s and is_antipera_r, pert_r, is_antipert_r) diff --git a/pyleecan/Methods/Machine/Machine/comp_periodicity_spatial.py b/pyleecan/Methods/Machine/Machine/comp_periodicity_spatial.py new file mode 100644 index 000000000..868f95d67 --- /dev/null +++ b/pyleecan/Methods/Machine/Machine/comp_periodicity_spatial.py @@ -0,0 +1,34 @@ +from numpy import gcd + + +def comp_periodicity_spatial(self): + """Compute the (anti)-periodicities of the machine in space domain + + Parameters + ---------- + self : Machine + A Machine object + + Returns + ------- + pera : int + Number of spatial periodicities of the machine over 2*pi + is_apera : bool + True if an anti-periodicity is possible after the periodicities + """ + + p = self.get_pole_pair_number() + + # Get stator (anti)-periodicity in spatial domain + pera_s, is_antipera_s = self.stator.comp_periodicity_spatial() + + # Get rotor (anti)-periodicities in spatial domain + pera_r, is_antipera_r = self.rotor.comp_periodicity_spatial() + + # Get machine spatial periodicity + pera = int(gcd(gcd(pera_s, pera_r), p)) + + # Get machine time and spatial anti-periodicities + is_apera = bool(is_antipera_s and is_antipera_r) + + return pera, is_apera diff --git a/pyleecan/Methods/Machine/Machine/comp_periodicity_time.py b/pyleecan/Methods/Machine/Machine/comp_periodicity_time.py new file mode 100644 index 000000000..f979c65a0 --- /dev/null +++ b/pyleecan/Methods/Machine/Machine/comp_periodicity_time.py @@ -0,0 +1,35 @@ +from numpy import gcd + + +def comp_periodicity_time(self): + """Compute the (anti)-periodicities of the machine in time domain + + Parameters + ---------- + self : Machine + A Machine object + + Returns + ------- + pert : int + Number of periodicities of the machine + is_apert : bool + True if an anti-periodicity is possible after the periodicities + """ + # TODO + + # p = self.get_pole_pair_number() + + # # Get stator (anti)-periodicity in spatial domain + # pera_s, is_antipera_s = self.stator.comp_periodicity_spatial() + + # # Get rotor (anti)-periodicities in spatial domain + # pera_r, is_antipera_r = self.rotor.comp_periodicity_spatial() + + # # Get machine spatial periodicity + # pera = int(gcd(gcd(pera_s, pera_r), p)) + + # # Get machine time and spatial anti-periodicities + # is_apera = bool(is_antipera_s and is_antipera_r) + + return pert, is_apert diff --git a/pyleecan/Methods/Machine/Winding/comp_connection_mat.py b/pyleecan/Methods/Machine/Winding/comp_connection_mat.py index 5a912fc9b..0a6568684 100644 --- a/pyleecan/Methods/Machine/Winding/comp_connection_mat.py +++ b/pyleecan/Methods/Machine/Winding/comp_connection_mat.py @@ -141,7 +141,7 @@ def comp_connection_mat(self, Zs=None, p=None): # self.is_aper_a = wdg.get_is_symmetric() # To check periodicities swat-em / pyleecan definitions - self.per_a, self.is_aper_a = self.comp_periodicity(wind_mat=wind_mat) + self.per_a, self.is_aper_a = self.comp_periodicity_spatial(wind_mat=wind_mat) # if is_aper_a: # Different def for Anti per # per_a = per_a / 2 # if self.per_a != per_a or self.is_aper_a != is_aper_a: diff --git a/pyleecan/Methods/Machine/Winding/comp_periodicity.py b/pyleecan/Methods/Machine/Winding/comp_periodicity.py index 41722e095..b4cf4a3b9 100644 --- a/pyleecan/Methods/Machine/Winding/comp_periodicity.py +++ b/pyleecan/Methods/Machine/Winding/comp_periodicity.py @@ -2,7 +2,7 @@ from numpy.linalg import norm -def comp_periodicity(self, wind_mat=None): +def comp_periodicity_spatial(self, wind_mat=None): """Computes the winding matrix (anti-)periodicity Parameters diff --git a/pyleecan/Methods/Machine/Winding/get_periodicity.py b/pyleecan/Methods/Machine/Winding/get_periodicity.py index b0878e66e..b0e86a426 100644 --- a/pyleecan/Methods/Machine/Winding/get_periodicity.py +++ b/pyleecan/Methods/Machine/Winding/get_periodicity.py @@ -17,6 +17,6 @@ def get_periodicity(self): if self.per_a is None or self.is_aper_a is None: - self.per_a, self.is_aper_a = self.comp_periodicity() + self.per_a, self.is_aper_a = self.comp_periodicity_spatial() return self.per_a, self.is_aper_a diff --git a/pyleecan/Methods/Output/Output/getter/get_machine_periodicity.py b/pyleecan/Methods/Output/Output/getter/get_machine_periodicity.py index bcd130212..f862e9ad0 100644 --- a/pyleecan/Methods/Output/Output/getter/get_machine_periodicity.py +++ b/pyleecan/Methods/Output/Output/getter/get_machine_periodicity.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- -from math import gcd - - def get_machine_periodicity(self): """Return / Compute the (anti)-periodicities of the machine in time and space domain @@ -30,9 +26,12 @@ def get_machine_periodicity(self): ( self.geo.per_a, self.geo.is_antiper_a, + ) = self.simu.machine.comp_periodicity_spatial() + + ( self.geo.per_t, self.geo.is_antiper_t, - ) = self.simu.machine.comp_periodicity() + ) = self.simu.machine.comp_periodicity_time() return ( self.geo.per_a, diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py index 00f99900a..30026efd3 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py @@ -166,7 +166,7 @@ def _comp_flux_mean(self, out): machine = out.simu.machine p = machine.rotor.winding.p qsr = machine.rotor.winding.qs - sym, is_anti_per, _, _ = machine.comp_periodicity() + sym, is_anti_per = machine.comp_periodicity_spatial() # get the fluxlinkages Phi = out.mag.Phi_wind["Rotor_0"].get_along("time", "phase")["Phi_{wind}"] diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC.py b/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC.py index 07bd2ddcc..3df14d17f 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC.py @@ -1,12 +1,4 @@ -# -*- coding: utf-8 -*- - -from pickle import EMPTY_SET -from ....Functions.Electrical.coordinate_transformation import dq2n -from ....Functions.Winding.gen_phase_list import gen_name - -from numpy import array, pi, real, imag, tile, interp, complex, shape -from scipy.linalg import solve -from SciDataTool import Data1D, DataTime +from numpy import pi, interp def solve_EEC(self, output): @@ -81,7 +73,7 @@ def solve_EEC(self, output): # time = output.elec.Time.get_values(is_oneperiod=True) # Nt = time.size # qsr = output.simu.machine.rotor.winding.qs - # sym = output.simu.machine.comp_periodicity()[0] + # sym = output.simu.machine.comp_periodicity_spatial()[0] # Ir_ = tile(Ir_norm, (Nt, 1)) * norm diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index 1359e4b56..25b09e86f 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -80,7 +80,7 @@ def gen_input(self): is_dq_rms=True, ) output.Id_ref = mean(Idq[:, 0]) - output.Iq_ref = mean(Idq[:, 1]) # TODO use of mean has to be documented + output.Iq_ref = mean(Idq[:, 1]) # Load and check Ir is needed if qr > 0: diff --git a/pyleecan/Methods/Simulation/StructElmer/gen_mesh.py b/pyleecan/Methods/Simulation/StructElmer/gen_mesh.py index 418a64b22..8d9cdf89a 100644 --- a/pyleecan/Methods/Simulation/StructElmer/gen_mesh.py +++ b/pyleecan/Methods/Simulation/StructElmer/gen_mesh.py @@ -27,7 +27,7 @@ def gen_mesh(self, output): """ # readability machine = output.simu.machine - _, _, sym_r, is_antipert_r = machine.comp_periodicity() + sym_r, is_antipert_r = machine.comp_periodicity_spatial() sym_r = sym_r * (1 + is_antipert_r) From bbe53962e497bf0a146826e6c01070b1c316f9bd Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 25 Aug 2021 10:05:09 +0200 Subject: [PATCH 023/167] [CO] Set ending time at p/felec if Nrev is None cf issue #418 --- pyleecan/Methods/Simulation/Input/comp_axes.py | 17 +++++++++++------ pyleecan/Methods/Simulation/Input/comp_felec.py | 4 ++-- pyleecan/Methods/Simulation/Input/gen_input.py | 9 +++------ .../Simulation/InputVoltage/gen_input.py | 2 +- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/pyleecan/Methods/Simulation/Input/comp_axes.py b/pyleecan/Methods/Simulation/Input/comp_axes.py index 497593daf..8a1cb7049 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axes.py +++ b/pyleecan/Methods/Simulation/Input/comp_axes.py @@ -3,7 +3,7 @@ from ....Methods.Simulation.Input import InputError -def comp_axes(self, machine, N0=None): +def comp_axes(self, machine): """Compute simulation axes, i.e. space DataObject including (anti)-periodicity and time DataObject including (anti)-periodicity and accounting for rotating speed and number of revolutions @@ -14,9 +14,6 @@ def comp_axes(self, machine, N0=None): an Input object machine : Machine a Machine object - N0 : float - rotating speed [rpm] - Returns ------- @@ -26,8 +23,11 @@ def comp_axes(self, machine, N0=None): Angle axis including (anti)-periodicity """ + + N0 = self.N0 + if self.time is None and N0 is None: - raise InputError("ERROR: time and N0 can't be both None") + raise InputError("time and N0 can't be both None") # Get machine pole pair number p = machine.get_pole_pair_number() @@ -51,11 +51,16 @@ def comp_axes(self, machine, N0=None): # Create time axis if self.time is None: # Create time axis as a DataLinspace + if self.Nrev is not None: + t_final = 60 / N0 * self.Nrev + else: + t_final = p / f_elec + # Create time axis as a DataLinspace Time = DataLinspace( name="time", unit="s", initial=0, - final=60 / N0 * self.Nrev, + final=t_final, number=self.Nt_tot, include_endpoint=False, normalizations=norm_time, diff --git a/pyleecan/Methods/Simulation/Input/comp_felec.py b/pyleecan/Methods/Simulation/Input/comp_felec.py index 271578f36..a91c73323 100644 --- a/pyleecan/Methods/Simulation/Input/comp_felec.py +++ b/pyleecan/Methods/Simulation/Input/comp_felec.py @@ -16,7 +16,7 @@ def comp_felec(self): # Get the phase number for verifications if self.parent is None: raise InputError( - "ERROR: InputCurrent object should be inside a Simulation object" + "InputCurrent object should be inside a Simulation object" ) # get the pole pair number if hasattr(self.parent, "machine"): @@ -29,4 +29,4 @@ def comp_felec(self): zp = 1 return self.N0 * zp / 60 else: - raise InputError("ERROR: InputCurrent object can't have felec and N0 at None") + raise InputError("InputCurrent object can't have felec and N0 at None") diff --git a/pyleecan/Methods/Simulation/Input/gen_input.py b/pyleecan/Methods/Simulation/Input/gen_input.py index 10dd71bee..5fa002d1d 100644 --- a/pyleecan/Methods/Simulation/Input/gen_input.py +++ b/pyleecan/Methods/Simulation/Input/gen_input.py @@ -1,8 +1,5 @@ -# -*- coding: utf-8 -*- - from ....Classes.OutElec import OutElec from ....Methods.Simulation.Input import InputError -from numpy import ndarray def gen_input(self): @@ -15,18 +12,18 @@ def gen_input(self): """ # Get the simulation and check output if self.parent is None: - raise InputError("ERROR: Input object should be inside a Simulation object") + raise InputError("Input object should be inside a Simulation object") simu = self.parent if self.parent.parent is None: raise InputError( - "ERROR: Input parent error:" + "Input parent error:" + " The Simulation object must be in an Output object to run" ) # Create the correct Output object output = OutElec() - self.comp_axes(machine=simu.machine, N0=self.N0) + self.comp_axes(machine=simu.machine) # Save the Output in the correct place self.parent.parent.elec = output diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index a3ee2a5c6..6041ec1f2 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -30,7 +30,7 @@ def gen_input(self): output = OutElec() # Set discretization - Time, Angle = self.comp_axes(simu.machine, self.N0) + Time, Angle = self.comp_axes(simu.machine) output.Time = Time output.Angle = Angle From 7b8c8f76b7dd9b716bb5d12a022add2f1301bd12 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 25 Aug 2021 11:20:29 +0200 Subject: [PATCH 024/167] [CO] Introduce axes_dict as explained in issue #418 --- .../Methods/Simulation/test_InCurrent_meth.py | 4 +- pyleecan/Classes/Class_Dict.json | 132 ++----- pyleecan/Classes/EEC_SCIM.py | 2 +- pyleecan/Classes/Force.py | 4 +- pyleecan/Classes/Magnetics.py | 4 +- pyleecan/Classes/OutElec.py | 142 +++----- pyleecan/Classes/OutForce.py | 146 ++++---- pyleecan/Classes/OutGeo.py | 66 ++++ pyleecan/Classes/OutMag.py | 140 +++----- pyleecan/Classes/OutStruct.py | 340 +++--------------- .../Functions/Electrical/comp_fluxlinkage.py | 4 +- pyleecan/Functions/Electrical/solve_FEMM.py | 8 +- .../Generator/ClassesRef/Output/OutElec.csv | 9 +- .../Generator/ClassesRef/Output/OutForce.csv | 3 +- .../Generator/ClassesRef/Output/OutGeo.csv | 1 + .../Generator/ClassesRef/Output/OutMag.csv | 9 +- .../Generator/ClassesRef/Output/OutStruct.csv | 10 +- .../ClassesRef/Simulation/EEC_SCIM.csv | 2 +- .../Generator/ClassesRef/Simulation/Force.csv | 4 +- .../ClassesRef/Simulation/Magnetics.csv | 4 +- pyleecan/Methods/Output/OutElec/get_Is.py | 4 +- pyleecan/Methods/Output/OutElec/get_Nr.py | 4 +- pyleecan/Methods/Output/OutElec/get_Us.py | 4 +- pyleecan/Methods/Output/OutElec/store.py | 107 +----- pyleecan/Methods/Output/OutForce/store.py | 4 +- pyleecan/Methods/Output/OutMag/clean.py | 3 +- pyleecan/Methods/Output/OutMag/store.py | 3 + .../Output/Output/getter/get_angle_rotor.py | 2 +- .../Methods/Simulation/EEC_PMSM/gen_drive.py | 2 +- .../Methods/Simulation/EEC_SCIM/solve_EEC.py | 4 +- pyleecan/Methods/Simulation/Electrical/run.py | 9 +- .../Methods/Simulation/Force/comp_axes.py | 12 +- .../Methods/Simulation/Input/comp_axes.py | 2 +- .../Methods/Simulation/Input/comp_felec.py | 4 +- .../Simulation/InputCurrent/gen_input.py | 29 +- .../Methods/Simulation/InputElec/gen_input.py | 78 ++-- .../Simulation/InputVoltage/gen_input.py | 81 +++-- .../Simulation/LossModelBertotti/comp_loss.py | 2 +- .../Methods/Simulation/Magnetics/comp_axes.py | 11 +- .../Simulation/Structural/comp_axes.py | 24 +- 40 files changed, 504 insertions(+), 919 deletions(-) diff --git a/Tests/Methods/Simulation/test_InCurrent_meth.py b/Tests/Methods/Simulation/test_InCurrent_meth.py index f5f869ca3..e61b50c67 100644 --- a/Tests/Methods/Simulation/test_InCurrent_meth.py +++ b/Tests/Methods/Simulation/test_InCurrent_meth.py @@ -255,11 +255,11 @@ def test_InputCurrent_DQ(self, test_dict): # Generate Is according to Id/Iq test_obj.input.gen_input() assert_array_almost_equal( - output.elec.Time.get_values(is_oneperiod=False), + output.elec.axes_dict["time"].get_values(is_oneperiod=False), time_exp, ) assert_array_almost_equal( - output.elec.Angle.get_values(is_oneperiod=False), + output.elec.axes_dict["angle"].get_values(is_oneperiod=False), linspace(0, 2 * pi, Na_tot, endpoint=False), ) assert_array_almost_equal(output.elec.get_Is().values, Is_exp) diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 3007aeb9c..ece410860 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -1186,7 +1186,7 @@ "value": {} }, { - "desc": "True to compute only on one angle periodicity (use periodicities defined in output.mag.Angle)", + "desc": "True to compute only on one angle periodicity (use periodicities defined in output.mag.axes_dict[angle])", "max": "", "min": "", "name": "is_periodicity_a", @@ -1898,7 +1898,7 @@ "path": "pyleecan/Generator/ClassesRef/Simulation/Force.csv", "properties": [ { - "desc": "True to compute only on one time periodicity (use periodicities defined in output.force.Time). If None, automatically calculated based on Magnetics periodicities.", + "desc": "True to compute only on one time periodicity (use periodicities defined in output.force.axes_dict[time]). If None, automatically calculated based on Magnetics periodicities.", "max": "", "min": "", "name": "is_periodicity_t", @@ -1907,7 +1907,7 @@ "value": null }, { - "desc": "True to compute only on one angle periodicity (use periodicities defined in output.force.Angle). If None, automatically calculated based on Magnetics periodicities.", + "desc": "True to compute only on one angle periodicity (use periodicities defined in output.force.axes_dict[angle]). If None, automatically calculated based on Magnetics periodicities.", "max": "", "min": "", "name": "is_periodicity_a", @@ -6217,7 +6217,7 @@ "value": 0 }, { - "desc": "True to compute only on one time periodicity (use periodicities defined in output.mag.Time)", + "desc": "True to compute only on one time periodicity (use periodicities defined in output.mag.axes_dict[time])", "max": "", "min": "", "name": "is_periodicity_t", @@ -6226,7 +6226,7 @@ "value": 0 }, { - "desc": "True to compute only on one angle periodicity (use periodicities defined in output.mag.Angle)", + "desc": "True to compute only on one angle periodicity (use periodicities defined in output.mag.axes_dict[angle])", "max": "", "min": "", "name": "is_periodicity_a", @@ -7849,21 +7849,12 @@ "path": "pyleecan/Generator/ClassesRef/Output/OutElec.csv", "properties": [ { - "desc": "Electrical time Data object", + "desc": "Dict containing axes data used for Electrical", "max": "", "min": "", - "name": "Time", - "type": "SciDataTool.Classes.DataND.Data", - "unit": "s", - "value": "None" - }, - { - "desc": "Electrical position Data object", - "max": "", - "min": "", - "name": "Angle", - "type": "SciDataTool.Classes.DataND.Data", - "unit": "rad", + "name": "axes_dict", + "type": "{SciDataTool.Classes.DataND.Data}", + "unit": "", "value": "None" }, { @@ -8049,21 +8040,12 @@ "path": "pyleecan/Generator/ClassesRef/Output/OutForce.csv", "properties": [ { - "desc": "Force time Data object", + "desc": "Dict containing axes data used for Force", "max": "", "min": "", - "name": "Time", - "type": "SciDataTool.Classes.DataND.Data", - "unit": "s", - "value": "None" - }, - { - "desc": "Force position Data object", - "max": "", - "min": "", - "name": "Angle", - "type": "SciDataTool.Classes.DataND.Data", - "unit": "rad", + "name": "axes_dict", + "type": "{SciDataTool.Classes.DataND.Data}", + "unit": "", "value": "None" }, { @@ -8236,6 +8218,15 @@ "type": "bool", "unit": "-", "value": null + }, + { + "desc": "Dict containing axes data without periodicities used for plots and to have simulation full time/angle vectors", + "max": "", + "min": "", + "name": "axes_dict", + "type": "{SciDataTool.Classes.DataND.Data}", + "unit": "", + "value": "None" } ] }, @@ -8446,21 +8437,12 @@ "path": "pyleecan/Generator/ClassesRef/Output/OutMag.csv", "properties": [ { - "desc": "Magnetic time Data object", - "max": "", - "min": "", - "name": "Time", - "type": "SciDataTool.Classes.DataND.Data", - "unit": "s", - "value": "None" - }, - { - "desc": "Magnetic position Data object", + "desc": "Dict containing axes data used for Magnetics", "max": "", "min": "", - "name": "Angle", - "type": "SciDataTool.Classes.DataND.Data", - "unit": "rad", + "name": "axes_dict", + "type": "{SciDataTool.Classes.DataND.Data}", + "unit": "", "value": "None" }, { @@ -8693,41 +8675,14 @@ "path": "pyleecan/Generator/ClassesRef/Output/OutStruct.csv", "properties": [ { - "desc": "Structural time Data object", + "desc": "Dict containing axes data used for Structural", "max": "", "min": "", - "name": "Time", - "type": "SciDataTool.Classes.DataND.Data", - "unit": "s", - "value": "None" - }, - { - "desc": "Structural position Data object", - "max": "", - "min": "", - "name": "Angle", - "type": "SciDataTool.Classes.DataND.Data", - "unit": "rad", + "name": "axes_dict", + "type": "{SciDataTool.Classes.DataND.Data}", + "unit": "", "value": "None" }, - { - "desc": "Length of the time vector", - "max": "", - "min": "", - "name": "Nt_tot", - "type": "int", - "unit": "-", - "value": null - }, - { - "desc": "Length of the angle vector", - "max": "", - "min": "", - "name": "Na_tot", - "type": "int", - "unit": "-", - "value": null - }, { "desc": "Name of the logger to use", "max": "", @@ -8737,33 +8692,6 @@ "unit": "-", "value": "Pyleecan.Structural" }, - { - "desc": "Displacement output", - "max": "", - "min": "", - "name": "Yr", - "type": "SciDataTool.Classes.DataND.DataND", - "unit": "m", - "value": "None" - }, - { - "desc": "Velocity output", - "max": "", - "min": "", - "name": "Vr", - "type": "SciDataTool.Classes.DataND.DataND", - "unit": "m/s", - "value": "None" - }, - { - "desc": "Acceleration output", - "max": "", - "min": "", - "name": "Ar", - "type": "SciDataTool.Classes.DataND.DataND", - "unit": "m/s^2", - "value": "None" - }, { "desc": "FEA software mesh and solution", "max": "", diff --git a/pyleecan/Classes/EEC_SCIM.py b/pyleecan/Classes/EEC_SCIM.py index 12e0e7ead..d6bce86ea 100644 --- a/pyleecan/Classes/EEC_SCIM.py +++ b/pyleecan/Classes/EEC_SCIM.py @@ -333,7 +333,7 @@ def _set_is_periodicity_a(self, value): is_periodicity_a = property( fget=_get_is_periodicity_a, fset=_set_is_periodicity_a, - doc=u"""True to compute only on one angle periodicity (use periodicities defined in output.mag.Angle) + doc=u"""True to compute only on one angle periodicity (use periodicities defined in output.mag.axes_dict[angle]) :Type: bool """, diff --git a/pyleecan/Classes/Force.py b/pyleecan/Classes/Force.py index 5debb19c4..5b94b79f6 100644 --- a/pyleecan/Classes/Force.py +++ b/pyleecan/Classes/Force.py @@ -243,7 +243,7 @@ def _set_is_periodicity_t(self, value): is_periodicity_t = property( fget=_get_is_periodicity_t, fset=_set_is_periodicity_t, - doc=u"""True to compute only on one time periodicity (use periodicities defined in output.force.Time). If None, automatically calculated based on Magnetics periodicities. + doc=u"""True to compute only on one time periodicity (use periodicities defined in output.force.axes_dict[time]). If None, automatically calculated based on Magnetics periodicities. :Type: bool """, @@ -261,7 +261,7 @@ def _set_is_periodicity_a(self, value): is_periodicity_a = property( fget=_get_is_periodicity_a, fset=_set_is_periodicity_a, - doc=u"""True to compute only on one angle periodicity (use periodicities defined in output.force.Angle). If None, automatically calculated based on Magnetics periodicities. + doc=u"""True to compute only on one angle periodicity (use periodicities defined in output.force.axes_dict[angle]). If None, automatically calculated based on Magnetics periodicities. :Type: bool """, diff --git a/pyleecan/Classes/Magnetics.py b/pyleecan/Classes/Magnetics.py index 30344b0d6..9117ca5b9 100644 --- a/pyleecan/Classes/Magnetics.py +++ b/pyleecan/Classes/Magnetics.py @@ -426,7 +426,7 @@ def _set_is_periodicity_t(self, value): is_periodicity_t = property( fget=_get_is_periodicity_t, fset=_set_is_periodicity_t, - doc=u"""True to compute only on one time periodicity (use periodicities defined in output.mag.Time) + doc=u"""True to compute only on one time periodicity (use periodicities defined in output.mag.axes_dict[time]) :Type: bool """, @@ -444,7 +444,7 @@ def _set_is_periodicity_a(self, value): is_periodicity_a = property( fget=_get_is_periodicity_a, fset=_set_is_periodicity_a, - doc=u"""True to compute only on one angle periodicity (use periodicities defined in output.mag.Angle) + doc=u"""True to compute only on one angle periodicity (use periodicities defined in output.mag.axes_dict[angle]) :Type: bool """, diff --git a/pyleecan/Classes/OutElec.py b/pyleecan/Classes/OutElec.py index 5b2e86177..581112cea 100644 --- a/pyleecan/Classes/OutElec.py +++ b/pyleecan/Classes/OutElec.py @@ -93,8 +93,7 @@ class OutElec(FrozenClass): def __init__( self, - Time=None, - Angle=None, + axes_dict=None, Is=None, Ir=None, angle_rotor=None, @@ -131,10 +130,8 @@ def __init__( if init_dict is not None: # Initialisation by dict assert type(init_dict) is dict # Overwrite default value with init_dict content - if "Time" in list(init_dict.keys()): - Time = init_dict["Time"] - if "Angle" in list(init_dict.keys()): - Angle = init_dict["Angle"] + if "axes_dict" in list(init_dict.keys()): + axes_dict = init_dict["axes_dict"] if "Is" in list(init_dict.keys()): Is = init_dict["Is"] if "Ir" in list(init_dict.keys()): @@ -173,8 +170,7 @@ def __init__( U0_ref = init_dict["U0_ref"] # Set the properties (value check and convertion are done in setter) self.parent = None - self.Time = Time - self.Angle = Angle + self.axes_dict = axes_dict self.Is = Is self.Ir = Ir self.angle_rotor = angle_rotor @@ -205,8 +201,7 @@ def __str__(self): OutElec_str += "parent = None " + linesep else: OutElec_str += "parent = " + str(type(self.parent)) + " object" + linesep - OutElec_str += "Time = " + str(self.Time) + linesep + linesep - OutElec_str += "Angle = " + str(self.Angle) + linesep + linesep + OutElec_str += "axes_dict = " + str(self.axes_dict) + linesep + linesep OutElec_str += "Is = " + str(self.Is) + linesep + linesep OutElec_str += "Ir = " + str(self.Ir) + linesep + linesep OutElec_str += ( @@ -244,9 +239,7 @@ def __eq__(self, other): if type(other) != type(self): return False - if other.Time != self.Time: - return False - if other.Angle != self.Angle: + if other.axes_dict != self.axes_dict: return False if other.Is != self.Is: return False @@ -294,18 +287,21 @@ def compare(self, other, name="self", ignore_list=None): if type(other) != type(self): return ["type(" + name + ")"] diff_list = list() - if (other.Time is None and self.Time is not None) or ( - other.Time is not None and self.Time is None - ): - diff_list.append(name + ".Time None mismatch") - elif self.Time is not None: - diff_list.extend(self.Time.compare(other.Time, name=name + ".Time")) - if (other.Angle is None and self.Angle is not None) or ( - other.Angle is not None and self.Angle is None + if (other.axes_dict is None and self.axes_dict is not None) or ( + other.axes_dict is not None and self.axes_dict is None ): - diff_list.append(name + ".Angle None mismatch") - elif self.Angle is not None: - diff_list.extend(self.Angle.compare(other.Angle, name=name + ".Angle")) + diff_list.append(name + ".axes_dict None mismatch") + elif self.axes_dict is None: + pass + elif len(other.axes_dict) != len(self.axes_dict): + diff_list.append("len(" + name + "axes_dict)") + else: + for key in self.axes_dict: + diff_list.extend( + self.axes_dict[key].compare( + other.axes_dict[key], name=name + ".axes_dict" + ) + ) if (other.Is is None and self.Is is not None) or ( other.Is is not None and self.Is is None ): @@ -368,8 +364,9 @@ def __sizeof__(self): """Return the size in memory of the object (including all subobject)""" S = 0 # Full size of the object - S += getsizeof(self.Time) - S += getsizeof(self.Angle) + if self.axes_dict is not None: + for key, value in self.axes_dict.items(): + S += getsizeof(value) + getsizeof(key) S += getsizeof(self.Is) S += getsizeof(self.Ir) S += getsizeof(self.angle_rotor) @@ -398,14 +395,15 @@ def as_dict(self, **kwargs): """ OutElec_dict = dict() - if self.Time is None: - OutElec_dict["Time"] = None - else: - OutElec_dict["Time"] = self.Time.as_dict() - if self.Angle is None: - OutElec_dict["Angle"] = None + if self.axes_dict is None: + OutElec_dict["axes_dict"] = None else: - OutElec_dict["Angle"] = self.Angle.as_dict() + OutElec_dict["axes_dict"] = dict() + for key, obj in self.axes_dict.items(): + if obj is not None: + OutElec_dict["axes_dict"][key] = obj.as_dict() + else: + OutElec_dict["axes_dict"][key] = None if self.Is is None: OutElec_dict["Is"] = None else: @@ -446,8 +444,7 @@ def as_dict(self, **kwargs): def _set_None(self): """Set all the properties to None (except pyleecan object)""" - self.Time = None - self.Angle = None + self.axes_dict = None self.Is = None self.Ir = None self.angle_rotor = None @@ -468,57 +465,34 @@ def _set_None(self): self.slip_ref = None self.U0_ref = None - def _get_Time(self): - """getter of Time""" - return self._Time - - def _set_Time(self, value): - """setter of Time""" - if isinstance(value, str): # Load from file - value = load_init_dict(value)[1] - if isinstance(value, dict) and "__class__" in value: - class_obj = import_class( - "SciDataTool.Classes", value.get("__class__"), "Time" - ) - value = class_obj(init_dict=value) - elif type(value) is int and value == -1: # Default constructor - value = Data() - check_var("Time", value, "Data") - self._Time = value - - Time = property( - fget=_get_Time, - fset=_set_Time, - doc=u"""Electrical time Data object - - :Type: SciDataTool.Classes.DataND.Data - """, - ) - - def _get_Angle(self): - """getter of Angle""" - return self._Angle - - def _set_Angle(self, value): - """setter of Angle""" - if isinstance(value, str): # Load from file - value = load_init_dict(value)[1] - if isinstance(value, dict) and "__class__" in value: - class_obj = import_class( - "SciDataTool.Classes", value.get("__class__"), "Angle" - ) - value = class_obj(init_dict=value) - elif type(value) is int and value == -1: # Default constructor - value = Data() - check_var("Angle", value, "Data") - self._Angle = value + def _get_axes_dict(self): + """getter of axes_dict""" + if self._axes_dict is not None: + for key, obj in self._axes_dict.items(): + if obj is not None: + obj.parent = self + return self._axes_dict + + def _set_axes_dict(self, value): + """setter of axes_dict""" + if type(value) is dict: + for key, obj in value.items(): + if type(obj) is dict: + class_obj = import_class( + "SciDataTool.Classes", obj.get("__class__"), "axes_dict" + ) + value[key] = class_obj(init_dict=obj) + if type(value) is int and value == -1: + value = dict() + check_var("axes_dict", value, "{Data}") + self._axes_dict = value - Angle = property( - fget=_get_Angle, - fset=_set_Angle, - doc=u"""Electrical position Data object + axes_dict = property( + fget=_get_axes_dict, + fset=_set_axes_dict, + doc=u"""Dict containing axes data used for Electrical - :Type: SciDataTool.Classes.DataND.Data + :Type: {SciDataTool.Classes.DataND.Data} """, ) diff --git a/pyleecan/Classes/OutForce.py b/pyleecan/Classes/OutForce.py index 31ebe610a..c1bfd87c2 100644 --- a/pyleecan/Classes/OutForce.py +++ b/pyleecan/Classes/OutForce.py @@ -49,8 +49,7 @@ class OutForce(FrozenClass): def __init__( self, - Time=None, - Angle=None, + axes_dict=None, AGSF=None, logger_name="Pyleecan.Force", Rag=None, @@ -73,10 +72,8 @@ def __init__( if init_dict is not None: # Initialisation by dict assert type(init_dict) is dict # Overwrite default value with init_dict content - if "Time" in list(init_dict.keys()): - Time = init_dict["Time"] - if "Angle" in list(init_dict.keys()): - Angle = init_dict["Angle"] + if "axes_dict" in list(init_dict.keys()): + axes_dict = init_dict["axes_dict"] if "AGSF" in list(init_dict.keys()): AGSF = init_dict["AGSF"] if "logger_name" in list(init_dict.keys()): @@ -87,8 +84,7 @@ def __init__( meshsolution = init_dict["meshsolution"] # Set the properties (value check and convertion are done in setter) self.parent = None - self.Time = Time - self.Angle = Angle + self.axes_dict = axes_dict self.AGSF = AGSF self.logger_name = logger_name self.Rag = Rag @@ -105,8 +101,7 @@ def __str__(self): OutForce_str += "parent = None " + linesep else: OutForce_str += "parent = " + str(type(self.parent)) + " object" + linesep - OutForce_str += "Time = " + str(self.Time) + linesep + linesep - OutForce_str += "Angle = " + str(self.Angle) + linesep + linesep + OutForce_str += "axes_dict = " + str(self.axes_dict) + linesep + linesep OutForce_str += "AGSF = " + str(self.AGSF) + linesep + linesep OutForce_str += 'logger_name = "' + str(self.logger_name) + '"' + linesep OutForce_str += "Rag = " + str(self.Rag) + linesep @@ -126,9 +121,7 @@ def __eq__(self, other): if type(other) != type(self): return False - if other.Time != self.Time: - return False - if other.Angle != self.Angle: + if other.axes_dict != self.axes_dict: return False if other.AGSF != self.AGSF: return False @@ -148,18 +141,21 @@ def compare(self, other, name="self", ignore_list=None): if type(other) != type(self): return ["type(" + name + ")"] diff_list = list() - if (other.Time is None and self.Time is not None) or ( - other.Time is not None and self.Time is None - ): - diff_list.append(name + ".Time None mismatch") - elif self.Time is not None: - diff_list.extend(self.Time.compare(other.Time, name=name + ".Time")) - if (other.Angle is None and self.Angle is not None) or ( - other.Angle is not None and self.Angle is None + if (other.axes_dict is None and self.axes_dict is not None) or ( + other.axes_dict is not None and self.axes_dict is None ): - diff_list.append(name + ".Angle None mismatch") - elif self.Angle is not None: - diff_list.extend(self.Angle.compare(other.Angle, name=name + ".Angle")) + diff_list.append(name + ".axes_dict None mismatch") + elif self.axes_dict is None: + pass + elif len(other.axes_dict) != len(self.axes_dict): + diff_list.append("len(" + name + "axes_dict)") + else: + for key in self.axes_dict: + diff_list.extend( + self.axes_dict[key].compare( + other.axes_dict[key], name=name + ".axes_dict" + ) + ) if (other.AGSF is None and self.AGSF is not None) or ( other.AGSF is not None and self.AGSF is None ): @@ -188,8 +184,9 @@ def __sizeof__(self): """Return the size in memory of the object (including all subobject)""" S = 0 # Full size of the object - S += getsizeof(self.Time) - S += getsizeof(self.Angle) + if self.axes_dict is not None: + for key, value in self.axes_dict.items(): + S += getsizeof(value) + getsizeof(key) S += getsizeof(self.AGSF) S += getsizeof(self.logger_name) S += getsizeof(self.Rag) @@ -204,14 +201,15 @@ def as_dict(self, **kwargs): """ OutForce_dict = dict() - if self.Time is None: - OutForce_dict["Time"] = None + if self.axes_dict is None: + OutForce_dict["axes_dict"] = None else: - OutForce_dict["Time"] = self.Time.as_dict() - if self.Angle is None: - OutForce_dict["Angle"] = None - else: - OutForce_dict["Angle"] = self.Angle.as_dict() + OutForce_dict["axes_dict"] = dict() + for key, obj in self.axes_dict.items(): + if obj is not None: + OutForce_dict["axes_dict"][key] = obj.as_dict() + else: + OutForce_dict["axes_dict"][key] = None if self.AGSF is None: OutForce_dict["AGSF"] = None else: @@ -229,65 +227,41 @@ def as_dict(self, **kwargs): def _set_None(self): """Set all the properties to None (except pyleecan object)""" - self.Time = None - self.Angle = None + self.axes_dict = None self.AGSF = None self.logger_name = None self.Rag = None if self.meshsolution is not None: self.meshsolution._set_None() - def _get_Time(self): - """getter of Time""" - return self._Time - - def _set_Time(self, value): - """setter of Time""" - if isinstance(value, str): # Load from file - value = load_init_dict(value)[1] - if isinstance(value, dict) and "__class__" in value: - class_obj = import_class( - "SciDataTool.Classes", value.get("__class__"), "Time" - ) - value = class_obj(init_dict=value) - elif type(value) is int and value == -1: # Default constructor - value = Data() - check_var("Time", value, "Data") - self._Time = value - - Time = property( - fget=_get_Time, - fset=_set_Time, - doc=u"""Force time Data object - - :Type: SciDataTool.Classes.DataND.Data - """, - ) - - def _get_Angle(self): - """getter of Angle""" - return self._Angle - - def _set_Angle(self, value): - """setter of Angle""" - if isinstance(value, str): # Load from file - value = load_init_dict(value)[1] - if isinstance(value, dict) and "__class__" in value: - class_obj = import_class( - "SciDataTool.Classes", value.get("__class__"), "Angle" - ) - value = class_obj(init_dict=value) - elif type(value) is int and value == -1: # Default constructor - value = Data() - check_var("Angle", value, "Data") - self._Angle = value - - Angle = property( - fget=_get_Angle, - fset=_set_Angle, - doc=u"""Force position Data object - - :Type: SciDataTool.Classes.DataND.Data + def _get_axes_dict(self): + """getter of axes_dict""" + if self._axes_dict is not None: + for key, obj in self._axes_dict.items(): + if obj is not None: + obj.parent = self + return self._axes_dict + + def _set_axes_dict(self, value): + """setter of axes_dict""" + if type(value) is dict: + for key, obj in value.items(): + if type(obj) is dict: + class_obj = import_class( + "SciDataTool.Classes", obj.get("__class__"), "axes_dict" + ) + value[key] = class_obj(init_dict=obj) + if type(value) is int and value == -1: + value = dict() + check_var("axes_dict", value, "{Data}") + self._axes_dict = value + + axes_dict = property( + fget=_get_axes_dict, + fset=_set_axes_dict, + doc=u"""Dict containing axes data used for Force + + :Type: {SciDataTool.Classes.DataND.Data} """, ) diff --git a/pyleecan/Classes/OutGeo.py b/pyleecan/Classes/OutGeo.py index 5a1e9ff13..b67003f2c 100644 --- a/pyleecan/Classes/OutGeo.py +++ b/pyleecan/Classes/OutGeo.py @@ -45,6 +45,7 @@ def __init__( is_antiper_a=None, per_t=None, is_antiper_t=None, + axes_dict=None, init_dict=None, init_str=None, ): @@ -89,6 +90,8 @@ def __init__( per_t = init_dict["per_t"] if "is_antiper_t" in list(init_dict.keys()): is_antiper_t = init_dict["is_antiper_t"] + if "axes_dict" in list(init_dict.keys()): + axes_dict = init_dict["axes_dict"] # Set the properties (value check and convertion are done in setter) self.parent = None self.stator = stator @@ -104,6 +107,7 @@ def __init__( self.is_antiper_a = is_antiper_a self.per_t = per_t self.is_antiper_t = is_antiper_t + self.axes_dict = axes_dict # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -139,6 +143,7 @@ def __str__(self): OutGeo_str += "is_antiper_a = " + str(self.is_antiper_a) + linesep OutGeo_str += "per_t = " + str(self.per_t) + linesep OutGeo_str += "is_antiper_t = " + str(self.is_antiper_t) + linesep + OutGeo_str += "axes_dict = " + str(self.axes_dict) + linesep + linesep return OutGeo_str def __eq__(self, other): @@ -172,6 +177,8 @@ def __eq__(self, other): return False if other.is_antiper_t != self.is_antiper_t: return False + if other.axes_dict != self.axes_dict: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -216,6 +223,21 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".per_t") if other._is_antiper_t != self._is_antiper_t: diff_list.append(name + ".is_antiper_t") + if (other.axes_dict is None and self.axes_dict is not None) or ( + other.axes_dict is not None and self.axes_dict is None + ): + diff_list.append(name + ".axes_dict None mismatch") + elif self.axes_dict is None: + pass + elif len(other.axes_dict) != len(self.axes_dict): + diff_list.append("len(" + name + "axes_dict)") + else: + for key in self.axes_dict: + diff_list.extend( + self.axes_dict[key].compare( + other.axes_dict[key], name=name + ".axes_dict" + ) + ) # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -237,6 +259,9 @@ def __sizeof__(self): S += getsizeof(self.is_antiper_a) S += getsizeof(self.per_t) S += getsizeof(self.is_antiper_t) + if self.axes_dict is not None: + for key, value in self.axes_dict.items(): + S += getsizeof(value) + getsizeof(key) return S def as_dict(self, **kwargs): @@ -266,6 +291,15 @@ def as_dict(self, **kwargs): OutGeo_dict["is_antiper_a"] = self.is_antiper_a OutGeo_dict["per_t"] = self.per_t OutGeo_dict["is_antiper_t"] = self.is_antiper_t + if self.axes_dict is None: + OutGeo_dict["axes_dict"] = None + else: + OutGeo_dict["axes_dict"] = dict() + for key, obj in self.axes_dict.items(): + if obj is not None: + OutGeo_dict["axes_dict"][key] = obj.as_dict() + else: + OutGeo_dict["axes_dict"][key] = None # The class name is added to the dict for deserialisation purpose OutGeo_dict["__class__"] = "OutGeo" return OutGeo_dict @@ -288,6 +322,7 @@ def _set_None(self): self.is_antiper_a = None self.per_t = None self.is_antiper_t = None + self.axes_dict = None def _get_stator(self): """getter of stator""" @@ -548,3 +583,34 @@ def _set_is_antiper_t(self, value): :Type: bool """, ) + + def _get_axes_dict(self): + """getter of axes_dict""" + if self._axes_dict is not None: + for key, obj in self._axes_dict.items(): + if obj is not None: + obj.parent = self + return self._axes_dict + + def _set_axes_dict(self, value): + """setter of axes_dict""" + if type(value) is dict: + for key, obj in value.items(): + if type(obj) is dict: + class_obj = import_class( + "SciDataTool.Classes", obj.get("__class__"), "axes_dict" + ) + value[key] = class_obj(init_dict=obj) + if type(value) is int and value == -1: + value = dict() + check_var("axes_dict", value, "{Data}") + self._axes_dict = value + + axes_dict = property( + fget=_get_axes_dict, + fset=_set_axes_dict, + doc=u"""Dict containing axes data without periodicities used for plots and to have simulation full time/angle vectors + + :Type: {SciDataTool.Classes.DataND.Data} + """, + ) diff --git a/pyleecan/Classes/OutMag.py b/pyleecan/Classes/OutMag.py index 86c537508..110abffed 100644 --- a/pyleecan/Classes/OutMag.py +++ b/pyleecan/Classes/OutMag.py @@ -93,8 +93,7 @@ class OutMag(FrozenClass): def __init__( self, - Time=None, - Angle=None, + axes_dict=None, B=None, Tem=None, Tem_av=None, @@ -125,10 +124,8 @@ def __init__( if init_dict is not None: # Initialisation by dict assert type(init_dict) is dict # Overwrite default value with init_dict content - if "Time" in list(init_dict.keys()): - Time = init_dict["Time"] - if "Angle" in list(init_dict.keys()): - Angle = init_dict["Angle"] + if "axes_dict" in list(init_dict.keys()): + axes_dict = init_dict["axes_dict"] if "B" in list(init_dict.keys()): B = init_dict["B"] if "Tem" in list(init_dict.keys()): @@ -155,8 +152,7 @@ def __init__( Rag = init_dict["Rag"] # Set the properties (value check and convertion are done in setter) self.parent = None - self.Time = Time - self.Angle = Angle + self.axes_dict = axes_dict self.B = B self.Tem = Tem self.Tem_av = Tem_av @@ -181,8 +177,7 @@ def __str__(self): OutMag_str += "parent = None " + linesep else: OutMag_str += "parent = " + str(type(self.parent)) + " object" + linesep - OutMag_str += "Time = " + str(self.Time) + linesep + linesep - OutMag_str += "Angle = " + str(self.Angle) + linesep + linesep + OutMag_str += "axes_dict = " + str(self.axes_dict) + linesep + linesep OutMag_str += "B = " + str(self.B) + linesep + linesep OutMag_str += "Tem = " + str(self.Tem) + linesep + linesep OutMag_str += "Tem_av = " + str(self.Tem_av) + linesep @@ -216,9 +211,7 @@ def __eq__(self, other): if type(other) != type(self): return False - if other.Time != self.Time: - return False - if other.Angle != self.Angle: + if other.axes_dict != self.axes_dict: return False if other.B != self.B: return False @@ -254,18 +247,21 @@ def compare(self, other, name="self", ignore_list=None): if type(other) != type(self): return ["type(" + name + ")"] diff_list = list() - if (other.Time is None and self.Time is not None) or ( - other.Time is not None and self.Time is None - ): - diff_list.append(name + ".Time None mismatch") - elif self.Time is not None: - diff_list.extend(self.Time.compare(other.Time, name=name + ".Time")) - if (other.Angle is None and self.Angle is not None) or ( - other.Angle is not None and self.Angle is None + if (other.axes_dict is None and self.axes_dict is not None) or ( + other.axes_dict is not None and self.axes_dict is None ): - diff_list.append(name + ".Angle None mismatch") - elif self.Angle is not None: - diff_list.extend(self.Angle.compare(other.Angle, name=name + ".Angle")) + diff_list.append(name + ".axes_dict None mismatch") + elif self.axes_dict is None: + pass + elif len(other.axes_dict) != len(self.axes_dict): + diff_list.append("len(" + name + "axes_dict)") + else: + for key in self.axes_dict: + diff_list.extend( + self.axes_dict[key].compare( + other.axes_dict[key], name=name + ".axes_dict" + ) + ) if (other.B is None and self.B is not None) or ( other.B is not None and self.B is None ): @@ -345,8 +341,9 @@ def __sizeof__(self): """Return the size in memory of the object (including all subobject)""" S = 0 # Full size of the object - S += getsizeof(self.Time) - S += getsizeof(self.Angle) + if self.axes_dict is not None: + for key, value in self.axes_dict.items(): + S += getsizeof(value) + getsizeof(key) S += getsizeof(self.B) S += getsizeof(self.Tem) S += getsizeof(self.Tem_av) @@ -371,14 +368,15 @@ def as_dict(self, **kwargs): """ OutMag_dict = dict() - if self.Time is None: - OutMag_dict["Time"] = None - else: - OutMag_dict["Time"] = self.Time.as_dict() - if self.Angle is None: - OutMag_dict["Angle"] = None + if self.axes_dict is None: + OutMag_dict["axes_dict"] = None else: - OutMag_dict["Angle"] = self.Angle.as_dict() + OutMag_dict["axes_dict"] = dict() + for key, obj in self.axes_dict.items(): + if obj is not None: + OutMag_dict["axes_dict"][key] = obj.as_dict() + else: + OutMag_dict["axes_dict"][key] = None if self.B is None: OutMag_dict["B"] = None else: @@ -424,8 +422,7 @@ def as_dict(self, **kwargs): def _set_None(self): """Set all the properties to None (except pyleecan object)""" - self.Time = None - self.Angle = None + self.axes_dict = None self.B = None self.Tem = None self.Tem_av = None @@ -441,57 +438,34 @@ def _set_None(self): self.internal._set_None() self.Rag = None - def _get_Time(self): - """getter of Time""" - return self._Time - - def _set_Time(self, value): - """setter of Time""" - if isinstance(value, str): # Load from file - value = load_init_dict(value)[1] - if isinstance(value, dict) and "__class__" in value: - class_obj = import_class( - "SciDataTool.Classes", value.get("__class__"), "Time" - ) - value = class_obj(init_dict=value) - elif type(value) is int and value == -1: # Default constructor - value = Data() - check_var("Time", value, "Data") - self._Time = value - - Time = property( - fget=_get_Time, - fset=_set_Time, - doc=u"""Magnetic time Data object - - :Type: SciDataTool.Classes.DataND.Data - """, - ) - - def _get_Angle(self): - """getter of Angle""" - return self._Angle + def _get_axes_dict(self): + """getter of axes_dict""" + if self._axes_dict is not None: + for key, obj in self._axes_dict.items(): + if obj is not None: + obj.parent = self + return self._axes_dict - def _set_Angle(self, value): - """setter of Angle""" - if isinstance(value, str): # Load from file - value = load_init_dict(value)[1] - if isinstance(value, dict) and "__class__" in value: - class_obj = import_class( - "SciDataTool.Classes", value.get("__class__"), "Angle" - ) - value = class_obj(init_dict=value) - elif type(value) is int and value == -1: # Default constructor - value = Data() - check_var("Angle", value, "Data") - self._Angle = value + def _set_axes_dict(self, value): + """setter of axes_dict""" + if type(value) is dict: + for key, obj in value.items(): + if type(obj) is dict: + class_obj = import_class( + "SciDataTool.Classes", obj.get("__class__"), "axes_dict" + ) + value[key] = class_obj(init_dict=obj) + if type(value) is int and value == -1: + value = dict() + check_var("axes_dict", value, "{Data}") + self._axes_dict = value - Angle = property( - fget=_get_Angle, - fset=_set_Angle, - doc=u"""Magnetic position Data object + axes_dict = property( + fget=_get_axes_dict, + fset=_set_axes_dict, + doc=u"""Dict containing axes data used for Magnetics - :Type: SciDataTool.Classes.DataND.Data + :Type: {SciDataTool.Classes.DataND.Data} """, ) diff --git a/pyleecan/Classes/OutStruct.py b/pyleecan/Classes/OutStruct.py index 7d42b603f..8824056c8 100644 --- a/pyleecan/Classes/OutStruct.py +++ b/pyleecan/Classes/OutStruct.py @@ -32,14 +32,8 @@ class OutStruct(FrozenClass): def __init__( self, - Time=None, - Angle=None, - Nt_tot=None, - Na_tot=None, + axes_dict=None, logger_name="Pyleecan.Structural", - Yr=None, - Vr=None, - Ar=None, meshsolution=-1, FEA_dict=None, init_dict=None, @@ -60,36 +54,18 @@ def __init__( if init_dict is not None: # Initialisation by dict assert type(init_dict) is dict # Overwrite default value with init_dict content - if "Time" in list(init_dict.keys()): - Time = init_dict["Time"] - if "Angle" in list(init_dict.keys()): - Angle = init_dict["Angle"] - if "Nt_tot" in list(init_dict.keys()): - Nt_tot = init_dict["Nt_tot"] - if "Na_tot" in list(init_dict.keys()): - Na_tot = init_dict["Na_tot"] + if "axes_dict" in list(init_dict.keys()): + axes_dict = init_dict["axes_dict"] if "logger_name" in list(init_dict.keys()): logger_name = init_dict["logger_name"] - if "Yr" in list(init_dict.keys()): - Yr = init_dict["Yr"] - if "Vr" in list(init_dict.keys()): - Vr = init_dict["Vr"] - if "Ar" in list(init_dict.keys()): - Ar = init_dict["Ar"] if "meshsolution" in list(init_dict.keys()): meshsolution = init_dict["meshsolution"] if "FEA_dict" in list(init_dict.keys()): FEA_dict = init_dict["FEA_dict"] # Set the properties (value check and convertion are done in setter) self.parent = None - self.Time = Time - self.Angle = Angle - self.Nt_tot = Nt_tot - self.Na_tot = Na_tot + self.axes_dict = axes_dict self.logger_name = logger_name - self.Yr = Yr - self.Vr = Vr - self.Ar = Ar self.meshsolution = meshsolution self.FEA_dict = FEA_dict @@ -104,14 +80,8 @@ def __str__(self): OutStruct_str += "parent = None " + linesep else: OutStruct_str += "parent = " + str(type(self.parent)) + " object" + linesep - OutStruct_str += "Time = " + str(self.Time) + linesep + linesep - OutStruct_str += "Angle = " + str(self.Angle) + linesep + linesep - OutStruct_str += "Nt_tot = " + str(self.Nt_tot) + linesep - OutStruct_str += "Na_tot = " + str(self.Na_tot) + linesep + OutStruct_str += "axes_dict = " + str(self.axes_dict) + linesep + linesep OutStruct_str += 'logger_name = "' + str(self.logger_name) + '"' + linesep - OutStruct_str += "Yr = " + str(self.Yr) + linesep + linesep - OutStruct_str += "Vr = " + str(self.Vr) + linesep + linesep - OutStruct_str += "Ar = " + str(self.Ar) + linesep + linesep if self.meshsolution is not None: tmp = ( self.meshsolution.__str__() @@ -129,22 +99,10 @@ def __eq__(self, other): if type(other) != type(self): return False - if other.Time != self.Time: - return False - if other.Angle != self.Angle: - return False - if other.Nt_tot != self.Nt_tot: - return False - if other.Na_tot != self.Na_tot: + if other.axes_dict != self.axes_dict: return False if other.logger_name != self.logger_name: return False - if other.Yr != self.Yr: - return False - if other.Vr != self.Vr: - return False - if other.Ar != self.Ar: - return False if other.meshsolution != self.meshsolution: return False if other.FEA_dict != self.FEA_dict: @@ -159,42 +117,23 @@ def compare(self, other, name="self", ignore_list=None): if type(other) != type(self): return ["type(" + name + ")"] diff_list = list() - if (other.Time is None and self.Time is not None) or ( - other.Time is not None and self.Time is None + if (other.axes_dict is None and self.axes_dict is not None) or ( + other.axes_dict is not None and self.axes_dict is None ): - diff_list.append(name + ".Time None mismatch") - elif self.Time is not None: - diff_list.extend(self.Time.compare(other.Time, name=name + ".Time")) - if (other.Angle is None and self.Angle is not None) or ( - other.Angle is not None and self.Angle is None - ): - diff_list.append(name + ".Angle None mismatch") - elif self.Angle is not None: - diff_list.extend(self.Angle.compare(other.Angle, name=name + ".Angle")) - if other._Nt_tot != self._Nt_tot: - diff_list.append(name + ".Nt_tot") - if other._Na_tot != self._Na_tot: - diff_list.append(name + ".Na_tot") + diff_list.append(name + ".axes_dict None mismatch") + elif self.axes_dict is None: + pass + elif len(other.axes_dict) != len(self.axes_dict): + diff_list.append("len(" + name + "axes_dict)") + else: + for key in self.axes_dict: + diff_list.extend( + self.axes_dict[key].compare( + other.axes_dict[key], name=name + ".axes_dict" + ) + ) if other._logger_name != self._logger_name: diff_list.append(name + ".logger_name") - if (other.Yr is None and self.Yr is not None) or ( - other.Yr is not None and self.Yr is None - ): - diff_list.append(name + ".Yr None mismatch") - elif self.Yr is not None: - diff_list.extend(self.Yr.compare(other.Yr, name=name + ".Yr")) - if (other.Vr is None and self.Vr is not None) or ( - other.Vr is not None and self.Vr is None - ): - diff_list.append(name + ".Vr None mismatch") - elif self.Vr is not None: - diff_list.extend(self.Vr.compare(other.Vr, name=name + ".Vr")) - if (other.Ar is None and self.Ar is not None) or ( - other.Ar is not None and self.Ar is None - ): - diff_list.append(name + ".Ar None mismatch") - elif self.Ar is not None: - diff_list.extend(self.Ar.compare(other.Ar, name=name + ".Ar")) if (other.meshsolution is None and self.meshsolution is not None) or ( other.meshsolution is not None and self.meshsolution is None ): @@ -215,14 +154,10 @@ def __sizeof__(self): """Return the size in memory of the object (including all subobject)""" S = 0 # Full size of the object - S += getsizeof(self.Time) - S += getsizeof(self.Angle) - S += getsizeof(self.Nt_tot) - S += getsizeof(self.Na_tot) + if self.axes_dict is not None: + for key, value in self.axes_dict.items(): + S += getsizeof(value) + getsizeof(key) S += getsizeof(self.logger_name) - S += getsizeof(self.Yr) - S += getsizeof(self.Vr) - S += getsizeof(self.Ar) S += getsizeof(self.meshsolution) if self.FEA_dict is not None: for key, value in self.FEA_dict.items(): @@ -237,29 +172,16 @@ def as_dict(self, **kwargs): """ OutStruct_dict = dict() - if self.Time is None: - OutStruct_dict["Time"] = None - else: - OutStruct_dict["Time"] = self.Time.as_dict() - if self.Angle is None: - OutStruct_dict["Angle"] = None + if self.axes_dict is None: + OutStruct_dict["axes_dict"] = None else: - OutStruct_dict["Angle"] = self.Angle.as_dict() - OutStruct_dict["Nt_tot"] = self.Nt_tot - OutStruct_dict["Na_tot"] = self.Na_tot + OutStruct_dict["axes_dict"] = dict() + for key, obj in self.axes_dict.items(): + if obj is not None: + OutStruct_dict["axes_dict"][key] = obj.as_dict() + else: + OutStruct_dict["axes_dict"][key] = None OutStruct_dict["logger_name"] = self.logger_name - if self.Yr is None: - OutStruct_dict["Yr"] = None - else: - OutStruct_dict["Yr"] = self.Yr.as_dict() - if self.Vr is None: - OutStruct_dict["Vr"] = None - else: - OutStruct_dict["Vr"] = self.Vr.as_dict() - if self.Ar is None: - OutStruct_dict["Ar"] = None - else: - OutStruct_dict["Ar"] = self.Ar.as_dict() if self.meshsolution is None: OutStruct_dict["meshsolution"] = None else: @@ -274,105 +196,40 @@ def as_dict(self, **kwargs): def _set_None(self): """Set all the properties to None (except pyleecan object)""" - self.Time = None - self.Angle = None - self.Nt_tot = None - self.Na_tot = None + self.axes_dict = None self.logger_name = None - self.Yr = None - self.Vr = None - self.Ar = None if self.meshsolution is not None: self.meshsolution._set_None() self.FEA_dict = None - def _get_Time(self): - """getter of Time""" - return self._Time - - def _set_Time(self, value): - """setter of Time""" - if isinstance(value, str): # Load from file - value = load_init_dict(value)[1] - if isinstance(value, dict) and "__class__" in value: - class_obj = import_class( - "SciDataTool.Classes", value.get("__class__"), "Time" - ) - value = class_obj(init_dict=value) - elif type(value) is int and value == -1: # Default constructor - value = Data() - check_var("Time", value, "Data") - self._Time = value - - Time = property( - fget=_get_Time, - fset=_set_Time, - doc=u"""Structural time Data object - - :Type: SciDataTool.Classes.DataND.Data - """, - ) - - def _get_Angle(self): - """getter of Angle""" - return self._Angle - - def _set_Angle(self, value): - """setter of Angle""" - if isinstance(value, str): # Load from file - value = load_init_dict(value)[1] - if isinstance(value, dict) and "__class__" in value: - class_obj = import_class( - "SciDataTool.Classes", value.get("__class__"), "Angle" - ) - value = class_obj(init_dict=value) - elif type(value) is int and value == -1: # Default constructor - value = Data() - check_var("Angle", value, "Data") - self._Angle = value - - Angle = property( - fget=_get_Angle, - fset=_set_Angle, - doc=u"""Structural position Data object - - :Type: SciDataTool.Classes.DataND.Data - """, - ) - - def _get_Nt_tot(self): - """getter of Nt_tot""" - return self._Nt_tot - - def _set_Nt_tot(self, value): - """setter of Nt_tot""" - check_var("Nt_tot", value, "int") - self._Nt_tot = value - - Nt_tot = property( - fget=_get_Nt_tot, - fset=_set_Nt_tot, - doc=u"""Length of the time vector - - :Type: int - """, - ) - - def _get_Na_tot(self): - """getter of Na_tot""" - return self._Na_tot - - def _set_Na_tot(self, value): - """setter of Na_tot""" - check_var("Na_tot", value, "int") - self._Na_tot = value + def _get_axes_dict(self): + """getter of axes_dict""" + if self._axes_dict is not None: + for key, obj in self._axes_dict.items(): + if obj is not None: + obj.parent = self + return self._axes_dict + + def _set_axes_dict(self, value): + """setter of axes_dict""" + if type(value) is dict: + for key, obj in value.items(): + if type(obj) is dict: + class_obj = import_class( + "SciDataTool.Classes", obj.get("__class__"), "axes_dict" + ) + value[key] = class_obj(init_dict=obj) + if type(value) is int and value == -1: + value = dict() + check_var("axes_dict", value, "{Data}") + self._axes_dict = value - Na_tot = property( - fget=_get_Na_tot, - fset=_set_Na_tot, - doc=u"""Length of the angle vector + axes_dict = property( + fget=_get_axes_dict, + fset=_set_axes_dict, + doc=u"""Dict containing axes data used for Structural - :Type: int + :Type: {SciDataTool.Classes.DataND.Data} """, ) @@ -394,87 +251,6 @@ def _set_logger_name(self, value): """, ) - def _get_Yr(self): - """getter of Yr""" - return self._Yr - - def _set_Yr(self, value): - """setter of Yr""" - if isinstance(value, str): # Load from file - value = load_init_dict(value)[1] - if isinstance(value, dict) and "__class__" in value: - class_obj = import_class( - "SciDataTool.Classes", value.get("__class__"), "Yr" - ) - value = class_obj(init_dict=value) - elif type(value) is int and value == -1: # Default constructor - value = DataND() - check_var("Yr", value, "DataND") - self._Yr = value - - Yr = property( - fget=_get_Yr, - fset=_set_Yr, - doc=u"""Displacement output - - :Type: SciDataTool.Classes.DataND.DataND - """, - ) - - def _get_Vr(self): - """getter of Vr""" - return self._Vr - - def _set_Vr(self, value): - """setter of Vr""" - if isinstance(value, str): # Load from file - value = load_init_dict(value)[1] - if isinstance(value, dict) and "__class__" in value: - class_obj = import_class( - "SciDataTool.Classes", value.get("__class__"), "Vr" - ) - value = class_obj(init_dict=value) - elif type(value) is int and value == -1: # Default constructor - value = DataND() - check_var("Vr", value, "DataND") - self._Vr = value - - Vr = property( - fget=_get_Vr, - fset=_set_Vr, - doc=u"""Velocity output - - :Type: SciDataTool.Classes.DataND.DataND - """, - ) - - def _get_Ar(self): - """getter of Ar""" - return self._Ar - - def _set_Ar(self, value): - """setter of Ar""" - if isinstance(value, str): # Load from file - value = load_init_dict(value)[1] - if isinstance(value, dict) and "__class__" in value: - class_obj = import_class( - "SciDataTool.Classes", value.get("__class__"), "Ar" - ) - value = class_obj(init_dict=value) - elif type(value) is int and value == -1: # Default constructor - value = DataND() - check_var("Ar", value, "DataND") - self._Ar = value - - Ar = property( - fget=_get_Ar, - fset=_set_Ar, - doc=u"""Acceleration output - - :Type: SciDataTool.Classes.DataND.DataND - """, - ) - def _get_meshsolution(self): """getter of meshsolution""" return self._meshsolution diff --git a/pyleecan/Functions/Electrical/comp_fluxlinkage.py b/pyleecan/Functions/Electrical/comp_fluxlinkage.py index 7588519cd..7c81488f0 100644 --- a/pyleecan/Functions/Electrical/comp_fluxlinkage.py +++ b/pyleecan/Functions/Electrical/comp_fluxlinkage.py @@ -74,7 +74,9 @@ def comp_fluxlinkage(obj, output): ) # modify some quantities - output.elec.Time = Data1D( + if output.elec.axes_dict is None: + output.elec.axes_dict = dict() + output.elec.axes_dict["time"] = Data1D( name="time", unit="s", values=(angle_rotor - angle_rotor[0]) / (2 * pi * output.elec.N0 / 60), diff --git a/pyleecan/Functions/Electrical/solve_FEMM.py b/pyleecan/Functions/Electrical/solve_FEMM.py index 1a89de319..70f2718b8 100644 --- a/pyleecan/Functions/Electrical/solve_FEMM.py +++ b/pyleecan/Functions/Electrical/solve_FEMM.py @@ -22,10 +22,14 @@ def solve_FEMM(obj, femm, output, sym, FEMM_dict): # Interpolate current on electric model time axis # Get stator current from elec out - Is = output.elec.comp_I_mag(time=output.elec.Time.values, is_stator=True) + Is = output.elec.comp_I_mag( + time=output.elec.axes_dict["time"].values, is_stator=True + ) # Get rotor current from elec out - Ir = output.elec.comp_I_mag(time=output.elec.Time.values, is_stator=False) + Ir = output.elec.comp_I_mag( + time=output.elec.axes_dict["time"].values, is_stator=False + ) # Get rotor angular position angle_rotor = output.get_angle_rotor()[0:Nt_tot] diff --git a/pyleecan/Generator/ClassesRef/Output/OutElec.csv b/pyleecan/Generator/ClassesRef/Output/OutElec.csv index ee92c4dde..f9ebb6896 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutElec.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutElec.csv @@ -1,9 +1,8 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -Time,s,Electrical time Data object,Nt_tot,SciDataTool.Classes.DataND.Data,None,,,,Output,,get_Nr,VERSION,1,Gather the electric module outputs -Angle,rad,Electrical position Data object,Na_tot,SciDataTool.Classes.DataND.Data,None,,,,,,get_Is,,, -Is,A,Stator currents DataTime object,"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Us,,, -Ir,A,Rotor currents as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,comp_I_mag,,, -angle_rotor,rad,Rotor angular position as a function of time (if None computed according to Nr),Nt_tot,ndarray,None,,,,,,,,, +axes_dict,,Dict containing axes data used for Electrical,,{SciDataTool.Classes.DataND.Data},None,,,,Output,,get_Nr,VERSION,1,Gather the electric module outputs +Is,A,Stator currents DataTime object,"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Is,,, +Ir,A,Rotor currents as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Us,,, +angle_rotor,rad,Rotor angular position as a function of time (if None computed according to Nr),Nt_tot,ndarray,None,,,,,,comp_I_mag,,, N0,rpm,Rotor speed,1,float,None,,,,,,,,, angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,,,, logger_name,-,Name of the logger to use,0,str,Pyleecan.Electrical,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Output/OutForce.csv b/pyleecan/Generator/ClassesRef/Output/OutForce.csv index b182b4e89..997229286 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutForce.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutForce.csv @@ -1,6 +1,5 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -Time,s,Force time Data object,Nt_tot,SciDataTool.Classes.DataND.Data,None,,,,Output,,store,VERSION,1,Gather the structural module outputs -Angle,rad,Force position Data object,Na_tot,SciDataTool.Classes.DataND.Data,None,,,,,,,,, +axes_dict,,Dict containing axes data used for Force,,{SciDataTool.Classes.DataND.Data},None,,,,Output,,store,VERSION,1,Gather the structural module outputs AGSF,N.m^2,Air Gap Surface Force (mainly computed with Maxwell stress tensor),"(Nt_tot ,Na_tot)",SciDataTool.Classes.VectorField.VectorField,None,,,,,,,,, logger_name,-,Name of the logger to use,0,str,Pyleecan.Force,,,,,,,,, Rag,-,Radius value for air-gap computation,0,float,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Output/OutGeo.csv b/pyleecan/Generator/ClassesRef/Output/OutGeo.csv index 36301373c..1f775986c 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutGeo.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutGeo.csv @@ -12,3 +12,4 @@ per_a,-,Number of spatial periodicities of the machine,0,int,None,,,,,,,,, is_antiper_a,-,True if an spatial anti-periodicity is possible after the periodicities,0,bool,None,,,,,,,,, per_t,-,Number of time periodicities of the machine,0,int,None,,,,,,,,, is_antiper_t,-,True if an time anti-periodicity is possible after the periodicities,0,bool,None,,,,,,,,, +axes_dict,,Dict containing axes data without periodicities used for plots and to have simulation full time/angle vectors,,{SciDataTool.Classes.DataND.Data},None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Output/OutMag.csv b/pyleecan/Generator/ClassesRef/Output/OutMag.csv index d07467170..49a0794b9 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutMag.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutMag.csv @@ -1,9 +1,8 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -Time,s,Magnetic time Data object,Nt_tot,SciDataTool.Classes.DataND.Data,None,,,,Output,,store,VERSION,1,Gather the magnetic module outputs -Angle,rad,Magnetic position Data object,Na_tot,SciDataTool.Classes.DataND.Data,None,,,,,,clean,,, -B,H,Airgap flux density VectorField object,"(Nt_tot,Na_tot)",SciDataTool.Classes.VectorField.VectorField,None,,,,,,comp_emf,,, -Tem,N.m,Electromagnetic torque DataTime object,Nt_tot,SciDataTool.Classes.DataND.DataND,None,,,,,,get_demag,,, -Tem_av,N.m,Average Electromagnetic torque,,float,None,,,,,,,,, +axes_dict,,Dict containing axes data used for Magnetics,,{SciDataTool.Classes.DataND.Data},None,,,,Output,,store,VERSION,1,Gather the magnetic module outputs +B,H,Airgap flux density VectorField object,"(Nt_tot,Na_tot)",SciDataTool.Classes.VectorField.VectorField,None,,,,,,clean,,, +Tem,N.m,Electromagnetic torque DataTime object,Nt_tot,SciDataTool.Classes.DataND.DataND,None,,,,,,comp_emf,,, +Tem_av,N.m,Average Electromagnetic torque,,float,None,,,,,,get_demag,,, Tem_rip_norm,-,Peak to Peak Torque ripple normalized according to average torque (None if average torque=0),,float,None,,,,,,,,, Tem_rip_pp,N.m,Peak to Peak Torque ripple,,float,None,,,,,,,,, Phi_wind_stator,Wb,Stator winding flux DataTime object,"(Nt_tot, qs)",SciDataTool.Classes.DataTime.DataTime,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Output/OutStruct.csv b/pyleecan/Generator/ClassesRef/Output/OutStruct.csv index cdb192566..449150014 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutStruct.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutStruct.csv @@ -1,11 +1,5 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -Time,s,Structural time Data object,Nt_tot,SciDataTool.Classes.DataND.Data,None,,,,Output,,,VERSION,1,Gather the structural module outputs -Angle,rad,Structural position Data object,Na_tot,SciDataTool.Classes.DataND.Data,None,,,,,,,,, -Nt_tot,-,Length of the time vector,,int,None,,,,,,,,, -Na_tot,-,Length of the angle vector,,int,None,,,,,,,,, +axes_dict,,Dict containing axes data used for Structural,,{SciDataTool.Classes.DataND.Data},None,,,,Output,,,VERSION,1,Gather the structural module outputs logger_name,-,Name of the logger to use,0,str,Pyleecan.Structural,,,,,,,,, -Yr,m,Displacement output,,SciDataTool.Classes.DataND.DataND,None,,,,,,,,, -Vr,m/s,Velocity output,,SciDataTool.Classes.DataND.DataND,None,,,,,,,,, -Ar,m/s^2,Acceleration output,,SciDataTool.Classes.DataND.DataND,None,,,,,,,,, meshsolution,,FEA software mesh and solution,,MeshSolution,,,,,,,,,, -FEA_dict,,dictionary containing the main FEA parameter,,dict,None,,,,,,,,, \ No newline at end of file +FEA_dict,,dictionary containing the main FEA parameter,,dict,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/EEC_SCIM.csv b/pyleecan/Generator/ClassesRef/Simulation/EEC_SCIM.csv index 939072537..f92cc79cb 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/EEC_SCIM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/EEC_SCIM.csv @@ -1,7 +1,7 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille I,-,RMS current for parameter estimation,,float,1,,,,Simulation,EEC,comp_parameters,VERSION,1,Electric module: Electrical Equivalent Circuit for Squirrel Cage Induction Machine, parameters,-,"Parameters of the EEC: computed if empty, or enforced",,dict,{},,,,,,solve_EEC,,,, -is_periodicity_a,,True to compute only on one angle periodicity (use periodicities defined in output.mag.Angle),,bool,1,,,,,,gen_drive,,,, +is_periodicity_a,,True to compute only on one angle periodicity (use periodicities defined in output.mag.axes_dict[angle]),,bool,1,,,,,,gen_drive,,,, nb_worker,,To run FEMM in parallel (the parallelization is on the time loop),,int,None,,,,,,comp_joule_losses,,,, N0,rpm,Rotor speed,1,float,None,,,,,,,,, felec,Hz,electrical frequency,1,float,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/Force.csv b/pyleecan/Generator/ClassesRef/Simulation/Force.csv index d1bd2bd2d..a63299c2d 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/Force.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/Force.csv @@ -1,6 +1,6 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe file -is_periodicity_t,-,"True to compute only on one time periodicity (use periodicities defined in output.force.Time). If None, automatically calculated based on Magnetics periodicities.",0,bool,None,,,,Simulation,,run,VERSION,1,Forces module abstract object,ForceMT -is_periodicity_a,-,"True to compute only on one angle periodicity (use periodicities defined in output.force.Angle). If None, automatically calculated based on Magnetics periodicities.",0,bool,None,,,,,,comp_axes,,,, +is_periodicity_t,-,"True to compute only on one time periodicity (use periodicities defined in output.force.axes_dict[time]). If None, automatically calculated based on Magnetics periodicities.",0,bool,None,,,,Simulation,,run,VERSION,1,Forces module abstract object,ForceMT +is_periodicity_a,-,"True to compute only on one angle periodicity (use periodicities defined in output.force.axes_dict[angle]). If None, automatically calculated based on Magnetics periodicities.",0,bool,None,,,,,,comp_axes,,,, is_agsf_transfer,-,True to compute the AGSF transfer from air-gap to stator bore radius.,0,bool,0,,,,,,comp_AGSF_transfer,,,, max_wavenumber_transfer,-,Maximum value to apply agsf transfer (to be used with FEA to avoid numerical noise amplification),0,int,None,,,,,,,,,, Rsbo_enforced_transfer,-,To enforce the value of the radius for AGSF transfer,0,float,None,,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/Magnetics.csv b/pyleecan/Generator/ClassesRef/Simulation/Magnetics.csv index 8d8967bdb..45680b206 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/Magnetics.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/Magnetics.csv @@ -6,8 +6,8 @@ is_mmfs,-,1 to compute the stator magnetomotive force / stator armature magnetic is_mmfr,-,1 to compute the rotor magnetomotive force / rotor magnetic field,0,bool,1,,,,,,,,,, type_BH_stator,-,"0 to use the B(H) curve, 1 to use linear B(H) curve according to mur_lin, 2 to enforce infinite permeability (mur_lin =100000)",0,int,0,0,2,,,,,,,, type_BH_rotor,-,"0 to use the B(H) curve, 1 to use linear B(H) curve according to mur_lin, 2 to enforce infinite permeability (mur_lin =100000)",0,int,0,0,2,,,,,,,, -is_periodicity_t,-,True to compute only on one time periodicity (use periodicities defined in output.mag.Time),0,bool,0,,,,,,,,,, -is_periodicity_a,-,True to compute only on one angle periodicity (use periodicities defined in output.mag.Angle),0,bool,0,,,,,,,,,, +is_periodicity_t,-,True to compute only on one time periodicity (use periodicities defined in output.mag.axes_dict[time]),0,bool,0,,,,,,,,,, +is_periodicity_a,-,True to compute only on one angle periodicity (use periodicities defined in output.mag.axes_dict[angle]),0,bool,0,,,,,,,,,, angle_stator_shift,rad,Shift angle to appy to the stator in magnetic model,0,float,0,,,,,,,,,, angle_rotor_shift,rad,Shift angle to appy to the rotor in magnetic model,0,float,0,,,,,,,,,, logger_name,-,Name of the logger to use,0,str,Pyleecan.Magnetics,,,,,,,,,, diff --git a/pyleecan/Methods/Output/OutElec/get_Is.py b/pyleecan/Methods/Output/OutElec/get_Is.py index 4e20d9a54..cdb280438 100644 --- a/pyleecan/Methods/Output/OutElec/get_Is.py +++ b/pyleecan/Methods/Output/OutElec/get_Is.py @@ -19,7 +19,7 @@ def get_Is(self): if self.Is is None: # Generate current according to Id/Iq Isdq = array([self.Id_ref, self.Iq_ref]) - time = self.Time.get_values(is_oneperiod=True) + time = self.axes_dict["time"].get_values(is_oneperiod=True) qs = self.parent.simu.machine.stator.winding.qs felec = self.felec @@ -39,7 +39,7 @@ def get_Is(self): name="Stator current", unit="A", symbol="Is", - axes=[Phase, self.Time.copy()], + axes=[Phase, self.axes_dict["time"].copy()], values=transpose(Is), ) return self.Is diff --git a/pyleecan/Methods/Output/OutElec/get_Nr.py b/pyleecan/Methods/Output/OutElec/get_Nr.py index fa08f71a4..5c412913f 100644 --- a/pyleecan/Methods/Output/OutElec/get_Nr.py +++ b/pyleecan/Methods/Output/OutElec/get_Nr.py @@ -18,11 +18,11 @@ def get_Nr(self): Nr: ndarray speed in function of time """ - if self.Time is None: + if self.axes_dict is None or "time" not in self.axes_dict: raise OutElecError('You must define "time" property before calling get_Nr') if self.N0 is None: raise OutElecError('You must define "N0" before calling get_Nr.') # Same speed for every timestep - Nr = self.N0 * ones(self.Time.get_length(is_oneperiod=False)) + Nr = self.N0 * ones(self.axes_dict["time"].get_length(is_oneperiod=False)) return Nr diff --git a/pyleecan/Methods/Output/OutElec/get_Us.py b/pyleecan/Methods/Output/OutElec/get_Us.py index 50357ab5b..f3dda2e9a 100644 --- a/pyleecan/Methods/Output/OutElec/get_Us.py +++ b/pyleecan/Methods/Output/OutElec/get_Us.py @@ -9,7 +9,7 @@ def get_Us(self): if self.Us is None: # Generate current according to Ud/Uq Usdq = array([self.Ud_ref, self.Uq_ref]) - time = self.Time.get_values(is_oneperiod=True) + time = self.axes_dict["time"].get_values(is_oneperiod=True) qs = self.parent.simu.machine.stator.winding.qs felec = self.felec @@ -25,7 +25,7 @@ def get_Us(self): name="Stator voltage", unit="V", symbol="Us", - axes=[Phase, self.Time.copy()], + axes=[Phase, self.axes_dict["time"].copy()], values=transpose(Us), ) return self.Us diff --git a/pyleecan/Methods/Output/OutElec/store.py b/pyleecan/Methods/Output/OutElec/store.py index bb07099cc..aef10c47f 100644 --- a/pyleecan/Methods/Output/OutElec/store.py +++ b/pyleecan/Methods/Output/OutElec/store.py @@ -20,109 +20,4 @@ def store(self, out_dict, axes_dict): """ - # Get time axis - Time = axes_dict["Time"] - - # Store airgap flux as VectorField object - # Axes for each airgap flux component - # axis_list = [Time, axes_dict["Angle"]] - - # Create VectorField with empty components - self.B = VectorField( - name="Airgap flux density", - symbol="B", - ) - # Radial flux component - if "Br" in out_dict: - self.B.components["radial"] = DataTime( - name="Airgap radial flux density", - unit="T", - symbol="B_r", - axes=axis_list, - values=out_dict.pop("Br"), - ) - # Tangential flux component - if "Bt" in out_dict: - self.B.components["tangential"] = DataTime( - name="Airgap tangential flux density", - unit="T", - symbol="B_t", - axes=axis_list, - values=out_dict.pop("Bt"), - ) - # Axial flux component - if "Bz" in out_dict: - self.B.components["axial"] = DataTime( - name="Airgap axial flux density", - unit="T", - symbol="B_z", - axes=axis_list, - values=out_dict.pop("Bz"), - ) - - # Store electromagnetic torque over time, and global values: average, peak to peak and ripple - if "Tem" in out_dict: - - Tem = out_dict.pop("Tem") - - self.Tem = DataTime( - name="Electromagnetic torque", - unit="Nm", - symbol="T_{em}", - axes=[axes_dict["Time_Tem"]], - values=Tem, - ) - - # Calculate average torque in Nm - self.Tem_av = mean(Tem) - self.get_logger().debug("Average Torque: " + str(self.Tem_av) + " N.m") - - # Calculate peak to peak torque in absolute value Nm - self.Tem_rip_pp = abs(np_max(Tem) - np_min(Tem)) # [N.m] - - # Calculate torque ripple in percentage - if self.Tem_av != 0: - self.Tem_rip_norm = self.Tem_rip_pp / self.Tem_av # [] - else: - self.Tem_rip_norm = None - - # Store list of winding fluxlinkage, stator winding fluxlinkage - # and calculate electromotive force - if "Phi_wind" in out_dict: - machine = self.parent.simu.machine - self.Phi_wind = {} - for key in out_dict["Phi_wind"].keys(): - # Store stator winding flux - lam = machine.get_lam_by_label(key) - qs = lam.winding.qs - - Phase = Data1D( - name="phase", - unit="", - values=gen_name(qs), - is_components=True, - ) - prefix = "Stator" if lam.is_stator else "Rotor" - self.Phi_wind[key] = DataTime( - name=prefix + " Winding Flux", - unit="Wb", - symbol="Phi_{wind}", - axes=[Time, Phase], - values=out_dict["Phi_wind"][key], - ) - - if "Stator_0" in out_dict["Phi_wind"].keys(): # TODO fix for multi stator - self.Phi_wind_stator = self.Phi_wind["Stator_0"] - - # Electromotive force computation - self.comp_emf() - - # remove from out_dict - out_dict.pop("Phi_wind") - - # Store MeshSolution object - if "meshsolution" in out_dict: - self.meshsolution = out_dict.pop("meshsolution") - - if "Rag" in out_dict: - self.Rag = out_dict.pop("Rag") + # TODO \ No newline at end of file diff --git a/pyleecan/Methods/Output/OutForce/store.py b/pyleecan/Methods/Output/OutForce/store.py index 3f430c24c..6c9693384 100644 --- a/pyleecan/Methods/Output/OutForce/store.py +++ b/pyleecan/Methods/Output/OutForce/store.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from SciDataTool import DataTime, VectorField @@ -16,6 +15,9 @@ def store(self, out_dict, axes_dict): """ + # Store axes_dict + self.axes_dict = axes_dict + # Store air-gap surface force as VectorField object # Axes for each component diff --git a/pyleecan/Methods/Output/OutMag/clean.py b/pyleecan/Methods/Output/OutMag/clean.py index b815820ea..88c8f1236 100644 --- a/pyleecan/Methods/Output/OutMag/clean.py +++ b/pyleecan/Methods/Output/OutMag/clean.py @@ -34,8 +34,7 @@ def clean(self, clean_level=1): if clean_level > 3: # clean all internal outputs self.internal = None - self.Angle = None - self.Time = None + self.axes_dict = None else: # clean internal depending on log_level diff --git a/pyleecan/Methods/Output/OutMag/store.py b/pyleecan/Methods/Output/OutMag/store.py index 699ba6db6..d6e3faccc 100644 --- a/pyleecan/Methods/Output/OutMag/store.py +++ b/pyleecan/Methods/Output/OutMag/store.py @@ -20,6 +20,9 @@ def store(self, out_dict, axes_dict): """ + # Store axes_dict + self.axes_dict = axes_dict + # Get time axis Time = axes_dict["Time"] diff --git a/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py b/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py index 48e966b81..47619f471 100644 --- a/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py +++ b/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py @@ -35,7 +35,7 @@ def get_angle_rotor(self): # Compute rotor initial angle (for synchronous machines, to align rotor d-axis and stator alpha-axis) A0 = self.get_angle_offset_initial() - time = self.elec.Time.get_values(is_oneperiod=False) + time = self.elec.axes_dict["time"].get_values(is_oneperiod=False) if time.size == 1: # Only one time step, no need to compute the position return ones(1) * A0 diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/gen_drive.py b/pyleecan/Methods/Simulation/EEC_PMSM/gen_drive.py index 03bba170f..22945aa89 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/gen_drive.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/gen_drive.py @@ -19,7 +19,7 @@ def gen_drive(self, output): qs = output.simu.machine.stator.winding.qs felec = output.elec.felec - time = output.elec.Time.get_values() + time = output.elec.axes_dict["time"].get_values() # Compute voltage Voltage = self.drive.get_wave() diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC.py b/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC.py index 3df14d17f..0e3619168 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC.py @@ -70,7 +70,7 @@ def solve_EEC(self, output): # output.elec.Us = output.elec.get_Us() # # Compute rotor currents - # time = output.elec.Time.get_values(is_oneperiod=True) + # time = output.elec.axes_dict["time"].get_values(is_oneperiod=True) # Nt = time.size # qsr = output.simu.machine.rotor.winding.qs # sym = output.simu.machine.comp_periodicity_spatial()[0] @@ -97,7 +97,7 @@ def solve_EEC(self, output): # name="Rotor current", # unit="A", # symbol="Ir", - # axes=[Phase, output.elec.Time.copy()], + # axes=[Phase, output.elec.axes_dict["time"].copy()], # values=Ir.T, # ) diff --git a/pyleecan/Methods/Simulation/Electrical/run.py b/pyleecan/Methods/Simulation/Electrical/run.py index 25e0001ad..83ad374f7 100644 --- a/pyleecan/Methods/Simulation/Electrical/run.py +++ b/pyleecan/Methods/Simulation/Electrical/run.py @@ -40,13 +40,6 @@ def run(self): "Cannot run Electrical model if machine is PMSM and eec is not EEC_PMSM" ) - # Compute and store time and angle axes from elec output - # and returns additional axes in axes_dict - if output.elec.Time is not None and output.elec.Angle is not None: - axes_dict = {"time": output.elec.Time, "angle": output.elec.Angle} - else: - raise Exception("Time and Angle must not be None in Electrical.run()") - if self.ELUT_enforced is not None: # enforce parameters of EEC coming from enforced ELUT at right temperatures if self.eec.parameters is None: @@ -75,4 +68,4 @@ def run(self): out_dict = self.comp_torque(output) # Store electrical quantities contained in out_dict in OutElec, as Data object if necessary - out_dict.store(out_dict, axes_dict) + out_dict.store(out_dict) diff --git a/pyleecan/Methods/Simulation/Force/comp_axes.py b/pyleecan/Methods/Simulation/Force/comp_axes.py index e0147f53d..098fd2bdc 100644 --- a/pyleecan/Methods/Simulation/Force/comp_axes.py +++ b/pyleecan/Methods/Simulation/Force/comp_axes.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ....Functions.Simulation.create_from_axis import create_from_axis @@ -18,11 +17,8 @@ def comp_axes(self, output): Dict containing Time and Angle axes including (anti-)periodicties used in any Force module """ - # Store Time axis in OutMag - output.force.Time = output.mag.Time.copy() - - # Store Angle axis in OutMag - output.force.Angle = output.mag.Angle.copy() + # Get axis dict from OutMag + axes_dict_mag = output.mag.axes_dict # Get time and space (anti-)periodicities of the machine ( @@ -34,7 +30,7 @@ def comp_axes(self, output): # Compute Time axis based on the one stored in OutMag and removing anti-periodicty Time, is_periodicity_t = create_from_axis( - axis_in=output.force.Time, + axis_in=axes_dict_mag["time"], per=per_t, is_aper=is_antiper_t, is_include_per=self.is_periodicity_t, @@ -55,7 +51,7 @@ def comp_axes(self, output): # Compute Angle axis based on the one stored in OutMag and removing anti-periodicty Angle, is_periodicity_a = create_from_axis( - axis_in=output.force.Angle, + axis_in=axes_dict_mag["angle"], per=per_a, is_aper=is_antiper_a, is_include_per=self.is_periodicity_a, diff --git a/pyleecan/Methods/Simulation/Input/comp_axes.py b/pyleecan/Methods/Simulation/Input/comp_axes.py index 8a1cb7049..f214ec4d4 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axes.py +++ b/pyleecan/Methods/Simulation/Input/comp_axes.py @@ -23,7 +23,7 @@ def comp_axes(self, machine): Angle axis including (anti)-periodicity """ - + N0 = self.N0 if self.time is None and N0 is None: diff --git a/pyleecan/Methods/Simulation/Input/comp_felec.py b/pyleecan/Methods/Simulation/Input/comp_felec.py index a91c73323..1786f7ae1 100644 --- a/pyleecan/Methods/Simulation/Input/comp_felec.py +++ b/pyleecan/Methods/Simulation/Input/comp_felec.py @@ -15,9 +15,7 @@ def comp_felec(self): elif self.N0 is not None: # Get the phase number for verifications if self.parent is None: - raise InputError( - "InputCurrent object should be inside a Simulation object" - ) + raise InputError("InputCurrent object should be inside a Simulation object") # get the pole pair number if hasattr(self.parent, "machine"): zp = self.parent.machine.stator.get_pole_pair_number() diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index 25b09e86f..5a0e21005 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -31,12 +31,15 @@ def gen_input(self): super(type(self), self).gen_input() # Get electrical output - output = simu.parent.elec + outelec = simu.parent.elec # Number of winding phases for stator/rotor qs = len(simu.machine.stator.get_name_phase()) qr = len(simu.machine.rotor.get_name_phase()) + # Get time axis + Time = outelec.axes_dict["time"] + # Load and check Is if qs > 0: if self.Is is None: @@ -45,9 +48,9 @@ def gen_input(self): "InputCurrent.Is, InputCurrent.Id_ref, and InputCurrent.Iq_ref missing" ) else: - output.Id_ref = self.Id_ref - output.Iq_ref = self.Iq_ref - output.Is = None + outelec.Id_ref = self.Id_ref + outelec.Iq_ref = self.Iq_ref + outelec.Is = None else: Is = self.Is.get_data() if not isinstance(Is, ndarray) or Is.shape != (self.Nt_tot, qs): @@ -65,22 +68,22 @@ def gen_input(self): values=gen_name(qs), is_components=True, ) - output.Is = DataTime( + outelec.Is = DataTime( name="Stator current", unit="A", symbol="Is", - axes=[Phase, output.Time], + axes=[Phase, Time], values=transpose(Is), ) # Compute corresponding Id/Iq reference Idq = n2dq( - transpose(output.Is.values), - 2 * pi * output.felec * output.Time.get_values(is_oneperiod=False), + transpose(outelec.Is.values), + 2 * pi * outelec.felec * Time.get_values(is_oneperiod=False), n=qs, is_dq_rms=True, ) - output.Id_ref = mean(Idq[:, 0]) - output.Iq_ref = mean(Idq[:, 1]) + outelec.Id_ref = mean(Idq[:, 0]) + outelec.Iq_ref = mean(Idq[:, 1]) # Load and check Ir is needed if qr > 0: @@ -103,13 +106,13 @@ def gen_input(self): values=gen_name(qr), is_components=True, ) - output.Ir = DataTime( + outelec.Ir = DataTime( name="Rotor current", unit="A", symbol="Ir", - axes=[Phase, output.Time], + axes=[Phase, Time], values=transpose(Ir), ) # Save the Output in the correct place - simu.parent.elec = output + simu.parent.elec = outelec diff --git a/pyleecan/Methods/Simulation/InputElec/gen_input.py b/pyleecan/Methods/Simulation/InputElec/gen_input.py index d211d6ca2..bebf5ff2e 100644 --- a/pyleecan/Methods/Simulation/InputElec/gen_input.py +++ b/pyleecan/Methods/Simulation/InputElec/gen_input.py @@ -1,7 +1,8 @@ -# -*- coding: utf-8 -*- - from ....Classes.OutElec import OutElec from ....Classes.Simulation import Simulation + +from ....Functions.Simulation.create_from_axis import create_from_axis + from ....Methods.Simulation.Input import InputError @@ -14,63 +15,70 @@ def gen_input(self): An InputCurrent object """ - output = OutElec() - # get the simulation if isinstance(self.parent, Simulation): simu = self.parent elif isinstance(self.parent.parent, Simulation): simu = self.parent.parent else: - raise InputError( - "ERROR: InputCurrent object should be inside a Simulation object" - ) + raise InputError("InputCurrent object should be inside a Simulation object") + + outelec = OutElec() + outgeo = simu.parent.geo + + outelec.N0 = self.N0 + outelec.felec = self.comp_felec() - output.N0 = self.N0 - output.felec = self.comp_felec() + # Set time and angle full axes in geometry output + Time, Angle = self.comp_axes(simu.machine) + outgeo.axes_dict = {"Time": Time, "Angle": Angle} - # Set discretization - Time, Angle = self.comp_axes(simu.machine, self.N0) - output.Time = Time - output.Angle = Angle + # Create time axis in electrical output accounting for pole periodicity + Time_elec = Time.copy() + Time_elec, _ = create_from_axis( + axis_in=Time, + per=int(2 * simu.machine.get_pole_pair_number()), + is_aper=True, + is_include_per=True, + is_remove_aper=False, + ) + outelec.axes_dict = {"Time": Time_elec} - # Initialize output at None - output.Id_ref = None - output.Iq_ref = None - output.Ud_ref = None - output.Uq_ref = None - output.Is = None - output.Ir = None + # Initialize outelec at None + outelec.Id_ref = None + outelec.Iq_ref = None + outelec.Ud_ref = None + outelec.Uq_ref = None + outelec.Is = None + outelec.Ir = None # Load and check voltage and currents if self.Ud_ref is not None and self.Uq_ref is not None: - output.Ud_ref = self.Ud_ref - output.Uq_ref = self.Uq_ref + outelec.Ud_ref = self.Ud_ref + outelec.Uq_ref = self.Uq_ref simu.elec.eec.parameters["Ud"] = self.Ud_ref simu.elec.eec.parameters["Uq"] = self.Uq_ref if self.Id_ref is not None and self.Iq_ref is not None: - output.Id_ref = self.Id_ref - output.Iq_ref = self.Iq_ref + outelec.Id_ref = self.Id_ref + outelec.Iq_ref = self.Iq_ref else: - output.Id_ref = 1 - output.Iq_ref = 1 + outelec.Id_ref = 1 + outelec.Iq_ref = 1 elif self.Id_ref is not None and self.Iq_ref is not None: - output.Id_ref = self.Id_ref - output.Iq_ref = self.Iq_ref + outelec.Id_ref = self.Id_ref + outelec.Iq_ref = self.Iq_ref else: - raise InputError("ERROR: Id/Iq or Ud/Uq missing") + raise InputError("Id/Iq or Ud/Uq missing") # Load and check rot_dir if self.rot_dir is None or self.rot_dir not in [-1, 1]: # Enforce default rotation direction - simu.parent.geo.rot_dir = -1 + outgeo.rot_dir = -1 else: - simu.parent.geo.rot_dir = self.rot_dir + outgeo.rot_dir = self.rot_dir if simu.parent is None: - raise InputError( - "ERROR: The Simulation object must be in an Output object to run" - ) + raise InputError("The Simulation object must be in an outelec object to run") # Save the Output in the correct place - simu.parent.elec = output + simu.parent.elec = outelec diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 6041ec1f2..5b7f4d7f6 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -2,6 +2,9 @@ from ....Classes.OutElec import OutElec from ....Classes.Simulation import Simulation + +from ....Functions.Simulation.create_from_axis import create_from_axis + from ....Methods.Simulation.Input import InputError # from ....Functions.Electrical.coordinate_transformation import n2dq @@ -27,20 +30,33 @@ def gen_input(self): raise InputError("InputVoltage object should be inside a Simulation object") # Create the correct Output object - output = OutElec() + outelec = OutElec() + outgeo = simu.parent.geo - # Set discretization + # Set time and angle full axes in geometry output Time, Angle = self.comp_axes(simu.machine) - output.Time = Time - output.Angle = Angle - - output.N0 = self.N0 - output.felec = self.comp_felec() # TODO introduce set_felec(slip) + outgeo.axes_dict = {"Time": Time, "Angle": Angle} + + # Create time axis in electrical output accounting for pole periodicity + Time_elec = Time.copy() + Time_elec, _ = create_from_axis( + axis_in=Time, + per=int(2 * simu.machine.get_pole_pair_number()), + is_aper=True, + is_include_per=True, + is_remove_aper=False, + ) + outelec.axes_dict = {"Time": Time_elec} + + outelec.N0 = self.N0 + outelec.felec = self.comp_felec() # TODO introduce set_felec(slip) if self.U0_ref is None and self.Ud_ref and self.Uq_ref: raise Exception("U0_ref, Ud_ref, and Uq_refcannot be all None in InputVoltage") - output.U0_ref = self.U0_ref + outelec.U0_ref = self.U0_ref + + outelec.slip_ref = self.slip_ref # Generate Us # if qs > 0: @@ -51,9 +67,9 @@ def gen_input(self): # "ERROR: InputVoltage.Is, InputVoltage.Id_ref, and InputVoltage.Iq_ref missing" # ) # else: - # output.Id_ref = self.Id_ref - # output.Iq_ref = self.Iq_ref - # output.Is = None + # outelec.Id_ref = self.Id_ref + # outelec.Iq_ref = self.Iq_ref + # outelec.Is = None # else: # Is = self.Is.get_data() # if not isinstance(Is, ndarray) or Is.shape != (self.Nt_tot, qs): @@ -71,7 +87,7 @@ def gen_input(self): # values=gen_name(qs), # is_components=True, # ) - # output.Is = DataTime( + # outelec.Is = DataTime( # name="Stator current", # unit="A", # symbol="Is", @@ -80,13 +96,13 @@ def gen_input(self): # ) # # Compute corresponding Id/Iq reference # Idq = n2dq( - # transpose(output.Is.values), - # 2 * pi * output.felec * output.Time.get_values(is_oneperiod=False), + # transpose(outelec.Is.values), + # 2 * pi * outelec.felec * outelec.axes_dict["time"].get_values(is_oneperiod=False), # n=qs, # is_dq_rms=True, # ) - # output.Id_ref = mean(Idq[:, 0]) - # output.Iq_ref = mean(Idq[:, 1]) # TODO use of mean has to be documented + # outelec.Id_ref = mean(Idq[:, 0]) + # outelec.Iq_ref = mean(Idq[:, 1]) # TODO use of mean has to be documented # Load and check alpha_rotor and N0 if self.angle_rotor is None and self.N0 is None: @@ -94,41 +110,40 @@ def gen_input(self): "InputVoltage.angle_rotor and InputVoltage.N0 can't be None at the same time" ) if self.angle_rotor is not None: - output.angle_rotor = self.angle_rotor.get_data() + outelec.angle_rotor = self.angle_rotor.get_data() if ( - not isinstance(output.angle_rotor, ndarray) - or len(output.angle_rotor.shape) != 1 - or output.angle_rotor.size != self.Nt_tot + not isinstance(outelec.angle_rotor, ndarray) + or len(outelec.angle_rotor.shape) != 1 + or outelec.angle_rotor.size != self.Nt_tot ): # angle_rotor should be a vector of same length as time raise InputError( "InputVoltage.angle_rotor should be a vector of the same length as time, " - + str(output.angle_rotor.shape) + + str(outelec.angle_rotor.shape) + " shape found, " + str(self.Nt_tot) + " expected" ) - simu.parent.elec.slip_ref = self.slip_ref - - if self.rot_dir is None or self.rot_dir not in [-1, 1]: - # Enforce default rotation direction - # simu.parent.geo.rot_dir = None - pass # None is already the default value - else: - simu.parent.geo.rot_dir = self.rot_dir + if self.rot_dir is not None: + if self.rot_dir in [-1, 1]: + # Enforce user-defined rotation direction + outgeo.rot_dir = self.rot_dir + else: + # Enforce calculation of rotation direction + outgeo.rot_dir = None if self.angle_rotor_initial is None: # Enforce default initial position - output.angle_rotor_initial = 0 + outelec.angle_rotor_initial = 0 else: - output.angle_rotor_initial = self.angle_rotor_initial + outelec.angle_rotor_initial = self.angle_rotor_initial if self.Tem_av_ref is not None: - output.Tem_av_ref = self.Tem_av_ref + outelec.Tem_av_ref = self.Tem_av_ref if simu.parent is None: raise InputError("The Simulation object must be in an Output object to run") # Save the Output in the correct place - simu.parent.elec = output + simu.parent.elec = outelec diff --git a/pyleecan/Methods/Simulation/LossModelBertotti/comp_loss.py b/pyleecan/Methods/Simulation/LossModelBertotti/comp_loss.py index 1cfade2bb..cc432676c 100644 --- a/pyleecan/Methods/Simulation/LossModelBertotti/comp_loss.py +++ b/pyleecan/Methods/Simulation/LossModelBertotti/comp_loss.py @@ -113,7 +113,7 @@ def comp_loss(self, output, part_label): N0_list = self.N0 if self.N0 else [N0] k_freq = [n / N0 for n in N0_list] - Time = output.elec.Time + Time = output.elec.axes_dict["time"] Speed = Data1D(name="speed", unit="rpm", symbol="N0", values=N0_list) loss_sum = _comp_loss_sum(self, LossDensComps, area, k_freq)[newaxis, :] diff --git a/pyleecan/Methods/Simulation/Magnetics/comp_axes.py b/pyleecan/Methods/Simulation/Magnetics/comp_axes.py index edf18fd1a..a8b6c3dbb 100644 --- a/pyleecan/Methods/Simulation/Magnetics/comp_axes.py +++ b/pyleecan/Methods/Simulation/Magnetics/comp_axes.py @@ -19,11 +19,8 @@ def comp_axes(self, output): """ - # Store Time axis in OutMag - output.mag.Time = output.elec.Time.copy() - - # Store Angle axis in OutMag - output.mag.Angle = output.elec.Angle.copy() + # Get axis dict from OutElec + axes_dict_elec = output.elec.axes_dict # Calculate axes for Magnetics module calculation # Get time and space (anti-)periodicities of the machine @@ -36,7 +33,7 @@ def comp_axes(self, output): # Compute Time axis based on the one stored in OutElec Time, is_periodicity_t = create_from_axis( - axis_in=output.mag.Time, + axis_in=axes_dict_elec["time"], per=per_t, is_aper=is_antiper_t, is_include_per=self.is_periodicity_t, @@ -57,7 +54,7 @@ def comp_axes(self, output): # Compute Angle axis based on the one stored in OutElec Angle, is_periodicity_a = create_from_axis( - axis_in=output.mag.Angle, + axis_in=axes_dict_elec["angle"], per=per_a, is_aper=is_antiper_a, is_include_per=self.is_periodicity_a, diff --git a/pyleecan/Methods/Simulation/Structural/comp_axes.py b/pyleecan/Methods/Simulation/Structural/comp_axes.py index 73f5df0f2..66b5fa039 100644 --- a/pyleecan/Methods/Simulation/Structural/comp_axes.py +++ b/pyleecan/Methods/Simulation/Structural/comp_axes.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ....Methods.Simulation.Input import InputError from ....Classes.OutStruct import OutStruct @@ -14,28 +13,15 @@ def comp_axes(self, output): an Output object (to update) """ if self.parent is None: - raise InputError( - "ERROR: The Structural object must be in a Simulation object to run" - ) + raise InputError("The Structural object must be in a Simulation object to run") if self.parent.parent is None: - raise InputError( - "ERROR: The Simulation object must be in an Output object to run" - ) + raise InputError("The Simulation object must be in an Output object to run") # setup OutStruct if None if self.parent.parent.struct is None: self.parent.parent.struct = OutStruct() - # readability - N0 = getattr(self.parent.input, "N0", None) - machine = self.parent.machine + output.struct.axes_dict = dict() - Time, Angle = self.parent.input.comp_axes(machine, N0=N0) - - # TODO maybe remove periodicity ? - - output.struct.Time = Time - # output.struct.Nt_tot = len(output.struct.time) - - output.struct.Angle = Angle - # output.struct.Na_tot = len(output.struct.angle) + for key, val in output.geo.axes_dict.items(): + output.struct.axes_dict[key] = val From c68e634e1a49627369722a317d8081d68547a4af Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 25 Aug 2021 12:38:32 +0200 Subject: [PATCH 025/167] [CO] Factorize comp_periodicity_spatial in Lamination class with a more generic method [NF] New method comp_periodicity_time.py that calculated time periodicities in both static and rotating referential [CO] Store time periodicities in both static and rotating referential in OutGeo --- pyleecan/Classes/Class_Dict.json | 39 +++-- pyleecan/Classes/Input.py | 2 +- pyleecan/Classes/InputCurrent.py | 2 +- pyleecan/Classes/InputElec.py | 2 +- pyleecan/Classes/InputFlux.py | 2 +- pyleecan/Classes/InputForce.py | 2 +- pyleecan/Classes/InputVoltage.py | 2 +- pyleecan/Classes/LamHole.py | 31 ++-- pyleecan/Classes/LamSlotMag.py | 19 --- pyleecan/Classes/LamSquirrelCage.py | 19 --- pyleecan/Classes/Lamination.py | 19 +++ pyleecan/Classes/OutGeo.py | 148 ++++++++++++------ .../Generator/ClassesRef/Machine/LamHole.csv | 2 +- .../ClassesRef/Machine/LamSlotMag.csv | 1 - .../ClassesRef/Machine/LamSquirrelCage.csv | 1 - .../ClassesRef/Machine/Lamination.csv | 3 +- .../Generator/ClassesRef/Output/OutGeo.csv | 6 +- .../Generator/ClassesRef/Simulation/Input.csv | 2 +- .../LamHole/comp_periodicity_spatial.py | 17 -- pyleecan/Methods/Machine/LamHole/get_Zs.py | 16 ++ .../Machine/LamHole/get_pole_pair_number.py | 3 - .../LamSlotMag/comp_periodicity_spatial.py | 17 -- .../Lamination/comp_periodicity_spatial.py | 35 +++++ .../Machine/Machine/comp_periodicity_time.py | 45 +++--- pyleecan/Methods/Output/OutElec/store.py | 2 +- .../Output/getter/get_machine_periodicity.py | 46 ++++-- 26 files changed, 286 insertions(+), 197 deletions(-) delete mode 100644 pyleecan/Methods/Machine/LamHole/comp_periodicity_spatial.py create mode 100644 pyleecan/Methods/Machine/LamHole/get_Zs.py delete mode 100644 pyleecan/Methods/Machine/LamSlotMag/comp_periodicity_spatial.py create mode 100644 pyleecan/Methods/Machine/Lamination/comp_periodicity_spatial.py diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index ece410860..3989c358b 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4095,7 +4095,7 @@ "name": "Nrev", "type": "float", "unit": "-", - "value": 1 + "value": null }, { "desc": "Angular discretization", @@ -4536,8 +4536,8 @@ "comp_radius_mid_yoke", "has_magnet", "comp_angle_d_axis", - "comp_periodicity_spatial", - "set_pole_pair_number" + "set_pole_pair_number", + "get_Zs" ], "mother": "Lamination", "name": "LamHole", @@ -4617,8 +4617,7 @@ "comp_surfaces", "comp_volumes", "plot", - "comp_angle_d_axis", - "comp_periodicity_spatial" + "comp_angle_d_axis" ], "mother": "LamSlot", "name": "LamSlotMag", @@ -4817,7 +4816,6 @@ "comp_length_ring", "plot", "comp_number_phase_eq", - "comp_periodicity_spatial", "comp_surface_ring", "comp_resistance_wind" ], @@ -4931,7 +4929,8 @@ "comp_radius_mid_yoke", "get_yoke_desc", "get_bore_desc", - "comp_point_ref" + "comp_point_ref", + "comp_periodicity_spatial" ], "mother": "", "name": "Lamination", @@ -8202,19 +8201,19 @@ "value": null }, { - "desc": "Number of time periodicities of the machine", + "desc": "Number of time periodicities of the machine in static referential", "max": "", "min": "", - "name": "per_t", + "name": "per_t_S", "type": "int", "unit": "-", "value": null }, { - "desc": "True if an time anti-periodicity is possible after the periodicities", + "desc": "True if an time anti-periodicity is possible after the periodicities in static referential", "max": "", "min": "", - "name": "is_antiper_t", + "name": "is_antiper_t_S", "type": "bool", "unit": "-", "value": null @@ -8227,6 +8226,24 @@ "type": "{SciDataTool.Classes.DataND.Data}", "unit": "", "value": "None" + }, + { + "desc": "Number of time periodicities of the machine in rotating referential", + "max": "", + "min": "", + "name": "per_t_R", + "type": "int", + "unit": "-", + "value": null + }, + { + "desc": "True if an time anti-periodicity is possible after the periodicities in rotating referential", + "max": "", + "min": "", + "name": "is_antiper_t_R", + "type": "bool", + "unit": "-", + "value": null } ] }, diff --git a/pyleecan/Classes/Input.py b/pyleecan/Classes/Input.py index 0edb44ee9..11e242989 100644 --- a/pyleecan/Classes/Input.py +++ b/pyleecan/Classes/Input.py @@ -84,7 +84,7 @@ def __init__( time=None, angle=None, Nt_tot=2048, - Nrev=1, + Nrev=None, Na_tot=2048, N0=None, init_dict=None, diff --git a/pyleecan/Classes/InputCurrent.py b/pyleecan/Classes/InputCurrent.py index 00516030d..775ebba2c 100644 --- a/pyleecan/Classes/InputCurrent.py +++ b/pyleecan/Classes/InputCurrent.py @@ -105,7 +105,7 @@ def __init__( time=None, angle=None, Nt_tot=2048, - Nrev=1, + Nrev=None, Na_tot=2048, N0=None, init_dict=None, diff --git a/pyleecan/Classes/InputElec.py b/pyleecan/Classes/InputElec.py index dfe66db94..4808b1ac9 100644 --- a/pyleecan/Classes/InputElec.py +++ b/pyleecan/Classes/InputElec.py @@ -90,7 +90,7 @@ def __init__( time=None, angle=None, Nt_tot=2048, - Nrev=1, + Nrev=None, Na_tot=2048, N0=None, init_dict=None, diff --git a/pyleecan/Classes/InputFlux.py b/pyleecan/Classes/InputFlux.py index cd5130f0e..8f106dfdd 100644 --- a/pyleecan/Classes/InputFlux.py +++ b/pyleecan/Classes/InputFlux.py @@ -92,7 +92,7 @@ def __init__( time=None, angle=None, Nt_tot=2048, - Nrev=1, + Nrev=None, Na_tot=2048, N0=None, init_dict=None, diff --git a/pyleecan/Classes/InputForce.py b/pyleecan/Classes/InputForce.py index 2f4abdb8a..9fa62d9a5 100644 --- a/pyleecan/Classes/InputForce.py +++ b/pyleecan/Classes/InputForce.py @@ -57,7 +57,7 @@ def __init__( time=None, angle=None, Nt_tot=2048, - Nrev=1, + Nrev=None, Na_tot=2048, N0=None, init_dict=None, diff --git a/pyleecan/Classes/InputVoltage.py b/pyleecan/Classes/InputVoltage.py index a4b38ffcf..8e3bf427c 100644 --- a/pyleecan/Classes/InputVoltage.py +++ b/pyleecan/Classes/InputVoltage.py @@ -101,7 +101,7 @@ def __init__( time=None, angle=None, Nt_tot=2048, - Nrev=1, + Nrev=None, Na_tot=2048, N0=None, init_dict=None, diff --git a/pyleecan/Classes/LamHole.py b/pyleecan/Classes/LamHole.py index d261cda26..3ed5159a8 100644 --- a/pyleecan/Classes/LamHole.py +++ b/pyleecan/Classes/LamHole.py @@ -68,16 +68,14 @@ comp_angle_d_axis = error try: - from ..Methods.Machine.LamHole.comp_periodicity_spatial import ( - comp_periodicity_spatial, - ) + from ..Methods.Machine.LamHole.set_pole_pair_number import set_pole_pair_number except ImportError as error: - comp_periodicity_spatial = error + set_pole_pair_number = error try: - from ..Methods.Machine.LamHole.set_pole_pair_number import set_pole_pair_number + from ..Methods.Machine.LamHole.get_Zs import get_Zs except ImportError as error: - set_pole_pair_number = error + get_Zs = error from ._check import InitUnKnowClassError @@ -201,18 +199,6 @@ class LamHole(Lamination): ) else: comp_angle_d_axis = comp_angle_d_axis - # cf Methods.Machine.LamHole.comp_periodicity_spatial - if isinstance(comp_periodicity_spatial, ImportError): - comp_periodicity_spatial = property( - fget=lambda x: raise_( - ImportError( - "Can't use LamHole method comp_periodicity_spatial: " - + str(comp_periodicity_spatial) - ) - ) - ) - else: - comp_periodicity_spatial = comp_periodicity_spatial # cf Methods.Machine.LamHole.set_pole_pair_number if isinstance(set_pole_pair_number, ImportError): set_pole_pair_number = property( @@ -225,6 +211,15 @@ class LamHole(Lamination): ) else: set_pole_pair_number = set_pole_pair_number + # cf Methods.Machine.LamHole.get_Zs + if isinstance(get_Zs, ImportError): + get_Zs = property( + fget=lambda x: raise_( + ImportError("Can't use LamHole method get_Zs: " + str(get_Zs)) + ) + ) + else: + get_Zs = get_Zs # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/LamSlotMag.py b/pyleecan/Classes/LamSlotMag.py index 56ac7522f..26fdc9bde 100644 --- a/pyleecan/Classes/LamSlotMag.py +++ b/pyleecan/Classes/LamSlotMag.py @@ -57,13 +57,6 @@ except ImportError as error: comp_angle_d_axis = error -try: - from ..Methods.Machine.LamSlotMag.comp_periodicity_spatial import ( - comp_periodicity_spatial, - ) -except ImportError as error: - comp_periodicity_spatial = error - from ._check import InitUnKnowClassError from .Magnet import Magnet @@ -166,18 +159,6 @@ class LamSlotMag(LamSlot): ) else: comp_angle_d_axis = comp_angle_d_axis - # cf Methods.Machine.LamSlotMag.comp_periodicity_spatial - if isinstance(comp_periodicity_spatial, ImportError): - comp_periodicity_spatial = property( - fget=lambda x: raise_( - ImportError( - "Can't use LamSlotMag method comp_periodicity_spatial: " - + str(comp_periodicity_spatial) - ) - ) - ) - else: - comp_periodicity_spatial = comp_periodicity_spatial # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/LamSquirrelCage.py b/pyleecan/Classes/LamSquirrelCage.py index 09c74d8eb..b22d847b8 100644 --- a/pyleecan/Classes/LamSquirrelCage.py +++ b/pyleecan/Classes/LamSquirrelCage.py @@ -44,13 +44,6 @@ except ImportError as error: comp_number_phase_eq = error -try: - from ..Methods.Machine.LamSquirrelCage.comp_periodicity_spatial import ( - comp_periodicity_spatial, - ) -except ImportError as error: - comp_periodicity_spatial = error - try: from ..Methods.Machine.LamSquirrelCage.comp_surface_ring import comp_surface_ring except ImportError as error: @@ -133,18 +126,6 @@ class LamSquirrelCage(LamSlotWind): ) else: comp_number_phase_eq = comp_number_phase_eq - # cf Methods.Machine.LamSquirrelCage.comp_periodicity_spatial - if isinstance(comp_periodicity_spatial, ImportError): - comp_periodicity_spatial = property( - fget=lambda x: raise_( - ImportError( - "Can't use LamSquirrelCage method comp_periodicity_spatial: " - + str(comp_periodicity_spatial) - ) - ) - ) - else: - comp_periodicity_spatial = comp_periodicity_spatial # cf Methods.Machine.LamSquirrelCage.comp_surface_ring if isinstance(comp_surface_ring, ImportError): comp_surface_ring = property( diff --git a/pyleecan/Classes/Lamination.py b/pyleecan/Classes/Lamination.py index b9e5b7a2b..a5a22c5fb 100644 --- a/pyleecan/Classes/Lamination.py +++ b/pyleecan/Classes/Lamination.py @@ -134,6 +134,13 @@ except ImportError as error: comp_point_ref = error +try: + from ..Methods.Machine.Lamination.comp_periodicity_spatial import ( + comp_periodicity_spatial, + ) +except ImportError as error: + comp_periodicity_spatial = error + from ._check import InitUnKnowClassError from .Material import Material @@ -399,6 +406,18 @@ class Lamination(FrozenClass): ) else: comp_point_ref = comp_point_ref + # cf Methods.Machine.Lamination.comp_periodicity_spatial + if isinstance(comp_periodicity_spatial, ImportError): + comp_periodicity_spatial = property( + fget=lambda x: raise_( + ImportError( + "Can't use Lamination method comp_periodicity_spatial: " + + str(comp_periodicity_spatial) + ) + ) + ) + else: + comp_periodicity_spatial = comp_periodicity_spatial # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/OutGeo.py b/pyleecan/Classes/OutGeo.py index b67003f2c..b01d3671b 100644 --- a/pyleecan/Classes/OutGeo.py +++ b/pyleecan/Classes/OutGeo.py @@ -43,9 +43,11 @@ def __init__( rot_dir=None, per_a=None, is_antiper_a=None, - per_t=None, - is_antiper_t=None, + per_t_S=None, + is_antiper_t_S=None, axes_dict=None, + per_t_R=None, + is_antiper_t_R=None, init_dict=None, init_str=None, ): @@ -86,12 +88,16 @@ def __init__( per_a = init_dict["per_a"] if "is_antiper_a" in list(init_dict.keys()): is_antiper_a = init_dict["is_antiper_a"] - if "per_t" in list(init_dict.keys()): - per_t = init_dict["per_t"] - if "is_antiper_t" in list(init_dict.keys()): - is_antiper_t = init_dict["is_antiper_t"] + if "per_t_S" in list(init_dict.keys()): + per_t_S = init_dict["per_t_S"] + if "is_antiper_t_S" in list(init_dict.keys()): + is_antiper_t_S = init_dict["is_antiper_t_S"] if "axes_dict" in list(init_dict.keys()): axes_dict = init_dict["axes_dict"] + if "per_t_R" in list(init_dict.keys()): + per_t_R = init_dict["per_t_R"] + if "is_antiper_t_R" in list(init_dict.keys()): + is_antiper_t_R = init_dict["is_antiper_t_R"] # Set the properties (value check and convertion are done in setter) self.parent = None self.stator = stator @@ -105,9 +111,11 @@ def __init__( self.rot_dir = rot_dir self.per_a = per_a self.is_antiper_a = is_antiper_a - self.per_t = per_t - self.is_antiper_t = is_antiper_t + self.per_t_S = per_t_S + self.is_antiper_t_S = is_antiper_t_S self.axes_dict = axes_dict + self.per_t_R = per_t_R + self.is_antiper_t_R = is_antiper_t_R # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -141,9 +149,11 @@ def __str__(self): OutGeo_str += "rot_dir = " + str(self.rot_dir) + linesep OutGeo_str += "per_a = " + str(self.per_a) + linesep OutGeo_str += "is_antiper_a = " + str(self.is_antiper_a) + linesep - OutGeo_str += "per_t = " + str(self.per_t) + linesep - OutGeo_str += "is_antiper_t = " + str(self.is_antiper_t) + linesep + OutGeo_str += "per_t_S = " + str(self.per_t_S) + linesep + OutGeo_str += "is_antiper_t_S = " + str(self.is_antiper_t_S) + linesep OutGeo_str += "axes_dict = " + str(self.axes_dict) + linesep + linesep + OutGeo_str += "per_t_R = " + str(self.per_t_R) + linesep + OutGeo_str += "is_antiper_t_R = " + str(self.is_antiper_t_R) + linesep return OutGeo_str def __eq__(self, other): @@ -173,12 +183,16 @@ def __eq__(self, other): return False if other.is_antiper_a != self.is_antiper_a: return False - if other.per_t != self.per_t: + if other.per_t_S != self.per_t_S: return False - if other.is_antiper_t != self.is_antiper_t: + if other.is_antiper_t_S != self.is_antiper_t_S: return False if other.axes_dict != self.axes_dict: return False + if other.per_t_R != self.per_t_R: + return False + if other.is_antiper_t_R != self.is_antiper_t_R: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -219,10 +233,10 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".per_a") if other._is_antiper_a != self._is_antiper_a: diff_list.append(name + ".is_antiper_a") - if other._per_t != self._per_t: - diff_list.append(name + ".per_t") - if other._is_antiper_t != self._is_antiper_t: - diff_list.append(name + ".is_antiper_t") + if other._per_t_S != self._per_t_S: + diff_list.append(name + ".per_t_S") + if other._is_antiper_t_S != self._is_antiper_t_S: + diff_list.append(name + ".is_antiper_t_S") if (other.axes_dict is None and self.axes_dict is not None) or ( other.axes_dict is not None and self.axes_dict is None ): @@ -238,6 +252,10 @@ def compare(self, other, name="self", ignore_list=None): other.axes_dict[key], name=name + ".axes_dict" ) ) + if other._per_t_R != self._per_t_R: + diff_list.append(name + ".per_t_R") + if other._is_antiper_t_R != self._is_antiper_t_R: + diff_list.append(name + ".is_antiper_t_R") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -257,11 +275,13 @@ def __sizeof__(self): S += getsizeof(self.rot_dir) S += getsizeof(self.per_a) S += getsizeof(self.is_antiper_a) - S += getsizeof(self.per_t) - S += getsizeof(self.is_antiper_t) + S += getsizeof(self.per_t_S) + S += getsizeof(self.is_antiper_t_S) if self.axes_dict is not None: for key, value in self.axes_dict.items(): S += getsizeof(value) + getsizeof(key) + S += getsizeof(self.per_t_R) + S += getsizeof(self.is_antiper_t_R) return S def as_dict(self, **kwargs): @@ -289,8 +309,8 @@ def as_dict(self, **kwargs): OutGeo_dict["rot_dir"] = self.rot_dir OutGeo_dict["per_a"] = self.per_a OutGeo_dict["is_antiper_a"] = self.is_antiper_a - OutGeo_dict["per_t"] = self.per_t - OutGeo_dict["is_antiper_t"] = self.is_antiper_t + OutGeo_dict["per_t_S"] = self.per_t_S + OutGeo_dict["is_antiper_t_S"] = self.is_antiper_t_S if self.axes_dict is None: OutGeo_dict["axes_dict"] = None else: @@ -300,6 +320,8 @@ def as_dict(self, **kwargs): OutGeo_dict["axes_dict"][key] = obj.as_dict() else: OutGeo_dict["axes_dict"][key] = None + OutGeo_dict["per_t_R"] = self.per_t_R + OutGeo_dict["is_antiper_t_R"] = self.is_antiper_t_R # The class name is added to the dict for deserialisation purpose OutGeo_dict["__class__"] = "OutGeo" return OutGeo_dict @@ -320,9 +342,11 @@ def _set_None(self): self.rot_dir = None self.per_a = None self.is_antiper_a = None - self.per_t = None - self.is_antiper_t = None + self.per_t_S = None + self.is_antiper_t_S = None self.axes_dict = None + self.per_t_R = None + self.is_antiper_t_R = None def _get_stator(self): """getter of stator""" @@ -548,37 +572,37 @@ def _set_is_antiper_a(self, value): """, ) - def _get_per_t(self): - """getter of per_t""" - return self._per_t + def _get_per_t_S(self): + """getter of per_t_S""" + return self._per_t_S - def _set_per_t(self, value): - """setter of per_t""" - check_var("per_t", value, "int") - self._per_t = value + def _set_per_t_S(self, value): + """setter of per_t_S""" + check_var("per_t_S", value, "int") + self._per_t_S = value - per_t = property( - fget=_get_per_t, - fset=_set_per_t, - doc=u"""Number of time periodicities of the machine + per_t_S = property( + fget=_get_per_t_S, + fset=_set_per_t_S, + doc=u"""Number of time periodicities of the machine in static referential :Type: int """, ) - def _get_is_antiper_t(self): - """getter of is_antiper_t""" - return self._is_antiper_t + def _get_is_antiper_t_S(self): + """getter of is_antiper_t_S""" + return self._is_antiper_t_S - def _set_is_antiper_t(self, value): - """setter of is_antiper_t""" - check_var("is_antiper_t", value, "bool") - self._is_antiper_t = value + def _set_is_antiper_t_S(self, value): + """setter of is_antiper_t_S""" + check_var("is_antiper_t_S", value, "bool") + self._is_antiper_t_S = value - is_antiper_t = property( - fget=_get_is_antiper_t, - fset=_set_is_antiper_t, - doc=u"""True if an time anti-periodicity is possible after the periodicities + is_antiper_t_S = property( + fget=_get_is_antiper_t_S, + fset=_set_is_antiper_t_S, + doc=u"""True if an time anti-periodicity is possible after the periodicities in static referential :Type: bool """, @@ -614,3 +638,39 @@ def _set_axes_dict(self, value): :Type: {SciDataTool.Classes.DataND.Data} """, ) + + def _get_per_t_R(self): + """getter of per_t_R""" + return self._per_t_R + + def _set_per_t_R(self, value): + """setter of per_t_R""" + check_var("per_t_R", value, "int") + self._per_t_R = value + + per_t_R = property( + fget=_get_per_t_R, + fset=_set_per_t_R, + doc=u"""Number of time periodicities of the machine in rotating referential + + :Type: int + """, + ) + + def _get_is_antiper_t_R(self): + """getter of is_antiper_t_R""" + return self._is_antiper_t_R + + def _set_is_antiper_t_R(self, value): + """setter of is_antiper_t_R""" + check_var("is_antiper_t_R", value, "bool") + self._is_antiper_t_R = value + + is_antiper_t_R = property( + fget=_get_is_antiper_t_R, + fset=_set_is_antiper_t_R, + doc=u"""True if an time anti-periodicity is possible after the periodicities in rotating referential + + :Type: bool + """, + ) diff --git a/pyleecan/Generator/ClassesRef/Machine/LamHole.csv b/pyleecan/Generator/ClassesRef/Machine/LamHole.csv index f85a325b1..bfde8a728 100644 --- a/pyleecan/Generator/ClassesRef/Machine/LamHole.csv +++ b/pyleecan/Generator/ClassesRef/Machine/LamHole.csv @@ -9,5 +9,5 @@ hole,,lamination Hole,1,[Hole],,,,,Machine,Lamination,build_geometry,VERSION,1,L ,,,,,,,,,,,comp_radius_mid_yoke,,, ,,,,,,,,,,,has_magnet,,, ,,,,,,,,,,,comp_angle_d_axis,,, -,,,,,,,,,,,comp_periodicity_spatial,,, ,,,,,,,,,,,set_pole_pair_number,,, +,,,,,,,,,,,get_Zs,,, diff --git a/pyleecan/Generator/ClassesRef/Machine/LamSlotMag.csv b/pyleecan/Generator/ClassesRef/Machine/LamSlotMag.csv index 8ce06c1ab..a50ec3a77 100644 --- a/pyleecan/Generator/ClassesRef/Machine/LamSlotMag.csv +++ b/pyleecan/Generator/ClassesRef/Machine/LamSlotMag.csv @@ -7,4 +7,3 @@ magnet,-,Magnet of the lamination,,Magnet,,,,,Machine,LamSlot,build_geometry,VER ,,,,,,,,,,,comp_volumes,,, ,,,,,,,,,,,plot,,, ,,,,,,,,,,,comp_angle_d_axis,,, -,,,,,,,,,,,comp_periodicity_spatial,,, diff --git a/pyleecan/Generator/ClassesRef/Machine/LamSquirrelCage.csv b/pyleecan/Generator/ClassesRef/Machine/LamSquirrelCage.csv index 213abff96..b75f3b326 100644 --- a/pyleecan/Generator/ClassesRef/Machine/LamSquirrelCage.csv +++ b/pyleecan/Generator/ClassesRef/Machine/LamSquirrelCage.csv @@ -4,6 +4,5 @@ Lscr,m,short circuit ring section axial length,0,float,1.50E-02,0,,,,,check,,, ring_mat,,Material of the Rotor short circuit ring,,Material,,,,,,,comp_length_ring,,, ,,,,,,,,,,,plot,,, ,,,,,,,,,,,comp_number_phase_eq,,, -,,,,,,,,,,,comp_periodicity_spatial,,, ,,,,,,,,,,,comp_surface_ring,,, ,,,,,,,,,,,comp_resistance_wind,,, diff --git a/pyleecan/Generator/ClassesRef/Machine/Lamination.csv b/pyleecan/Generator/ClassesRef/Machine/Lamination.csv index 8ba97b5e7..08a1a4084 100644 --- a/pyleecan/Generator/ClassesRef/Machine/Lamination.csv +++ b/pyleecan/Generator/ClassesRef/Machine/Lamination.csv @@ -1,4 +1,4 @@ -Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description +Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description L1,m,Lamination stack active length [m] without radial ventilation airducts but including insulation layers between lamination sheets,0,float,3.50E-01,0,100,,Machine,,build_geometry,VERSION,1,abstract class for lamination mat_type,,Lamination's material,,Material,,,,,,,check,,, Nrvd,-,number of radial air ventilation ducts in lamination,0,int,0,0,,,,,comp_length,,, @@ -22,3 +22,4 @@ bore,,Bore Shape,,Bore,None,,,,,,plot,,, ,,,,,,,,,,,get_yoke_desc,,, ,,,,,,,,,,,get_bore_desc,,, ,,,,,,,,,,,comp_point_ref,,, +,,,,,,,,,,,comp_periodicity_spatial,,, diff --git a/pyleecan/Generator/ClassesRef/Output/OutGeo.csv b/pyleecan/Generator/ClassesRef/Output/OutGeo.csv index 1f775986c..4f6a0c0f8 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutGeo.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutGeo.csv @@ -10,6 +10,8 @@ angle_offset_initial,rad,Difference between the d axis angle of the stator and t rot_dir,-,"rotation direction of the magnetic field fundamental !! WARNING: rot_dir = -1 to have positive rotor rotating direction, i.e. rotor position moves towards positive angle",0,int,None,-1,1,,,,,,, per_a,-,Number of spatial periodicities of the machine,0,int,None,,,,,,,,, is_antiper_a,-,True if an spatial anti-periodicity is possible after the periodicities,0,bool,None,,,,,,,,, -per_t,-,Number of time periodicities of the machine,0,int,None,,,,,,,,, -is_antiper_t,-,True if an time anti-periodicity is possible after the periodicities,0,bool,None,,,,,,,,, +per_t_S,-,Number of time periodicities of the machine in static referential,0,int,None,,,,,,,,, +is_antiper_t_S,-,True if an time anti-periodicity is possible after the periodicities in static referential,0,bool,None,,,,,,,,, axes_dict,,Dict containing axes data without periodicities used for plots and to have simulation full time/angle vectors,,{SciDataTool.Classes.DataND.Data},None,,,,,,,,, +per_t_R,-,Number of time periodicities of the machine in rotating referential,0,int,None,,,,,,,,, +is_antiper_t_R,-,True if an time anti-periodicity is possible after the periodicities in rotating referential,0,bool,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/Input.csv b/pyleecan/Generator/ClassesRef/Simulation/Input.csv index 78957bba0..6fb2e9d19 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/Input.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/Input.csv @@ -2,6 +2,6 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximu time,s,Electrical time vector (no symmetry) to import,Nt_tot,ImportMatrix,None,,,,Simulation,,gen_input,VERSION,1,Starting data of the simulation angle,rad,Electrical position vector (no symmetry) to import,Na_tot,ImportMatrix,None,,,,,,comp_axes,,, Nt_tot,-,Time discretization,0,int,2048,1,,,,,comp_felec,,, -Nrev,-,Number of rotor revolution (to compute the final time),,float,1,0,,,,,,,, +Nrev,-,Number of rotor revolution (to compute the final time),,float,None,0,,,,,,,, Na_tot,-,Angular discretization,,int,2048,1,,,,,,,, N0,rpm,Rotor speed,1,float,None,,,,,,,,, diff --git a/pyleecan/Methods/Machine/LamHole/comp_periodicity_spatial.py b/pyleecan/Methods/Machine/LamHole/comp_periodicity_spatial.py deleted file mode 100644 index 9087a74f6..000000000 --- a/pyleecan/Methods/Machine/LamHole/comp_periodicity_spatial.py +++ /dev/null @@ -1,17 +0,0 @@ -def comp_periodicity_spatial(self): - """Compute the periodicity factor of the lamination - - Parameters - ---------- - self : LamHole - A LamHole object - - Returns - ------- - per_a : int - Number of spatial periodicities of the lamination - is_antiper_a : bool - True if an spatial anti-periodicity is possible after the periodicities - """ - - return self.get_pole_pair_number(), True diff --git a/pyleecan/Methods/Machine/LamHole/get_Zs.py b/pyleecan/Methods/Machine/LamHole/get_Zs.py new file mode 100644 index 000000000..5b24f5ef6 --- /dev/null +++ b/pyleecan/Methods/Machine/LamHole/get_Zs.py @@ -0,0 +1,16 @@ +def get_Zs(self): + """Return the number of holes in the lamination + + Parameters + ---------- + self : LamHole + A LamHole object + + Returns + ------- + p: int + Number of pair of pole + + """ + + return self.hole[0].Zh diff --git a/pyleecan/Methods/Machine/LamHole/get_pole_pair_number.py b/pyleecan/Methods/Machine/LamHole/get_pole_pair_number.py index 98e116255..0d2e2c97e 100644 --- a/pyleecan/Methods/Machine/LamHole/get_pole_pair_number.py +++ b/pyleecan/Methods/Machine/LamHole/get_pole_pair_number.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - def get_pole_pair_number(self): """Return the number of pair of pole of the Lamination diff --git a/pyleecan/Methods/Machine/LamSlotMag/comp_periodicity_spatial.py b/pyleecan/Methods/Machine/LamSlotMag/comp_periodicity_spatial.py deleted file mode 100644 index 144c9ee8b..000000000 --- a/pyleecan/Methods/Machine/LamSlotMag/comp_periodicity_spatial.py +++ /dev/null @@ -1,17 +0,0 @@ -def comp_periodicity_spatial(self): - """Compute the periodicity factor of the lamination - - Parameters - ---------- - self : LamSlotMag - A LamSlotMag object - - Returns - ------- - per_a : int - Number of spatial periodicities of the lamination over 2*pi - is_antiper_a : bool - True if an spatial anti-periodicity is possible after the periodicities - """ - - return self.get_pole_pair_number(), True diff --git a/pyleecan/Methods/Machine/Lamination/comp_periodicity_spatial.py b/pyleecan/Methods/Machine/Lamination/comp_periodicity_spatial.py new file mode 100644 index 000000000..f4e3fcb76 --- /dev/null +++ b/pyleecan/Methods/Machine/Lamination/comp_periodicity_spatial.py @@ -0,0 +1,35 @@ +from numpy import gcd + + +def comp_periodicity_spatial(self): + """Compute the periodicity factor of the lamination + + Parameters + ---------- + self : lamination + A lamination object + + Returns + ------- + per_a : int + Number of spatial periodicities of the lamination + is_antiper_a : bool + True if an spatial anti-periodicity is possible after the periodicities + """ + + if hasattr(self, "get_Zs") and hasattr(self, "get_pole_pair_number"): + Zs = self.get_Zs() + p = self.get_pole_pair_number() + + per = int(gcd(Zs, p)) + + if per == 1: + is_aper = bool(Zs % 2 == 0) + else: + is_aper = bool(Zs / p % 2 == 0) + + else: + per = None + is_aper = False + + return per, is_aper diff --git a/pyleecan/Methods/Machine/Machine/comp_periodicity_time.py b/pyleecan/Methods/Machine/Machine/comp_periodicity_time.py index f979c65a0..202fd4b71 100644 --- a/pyleecan/Methods/Machine/Machine/comp_periodicity_time.py +++ b/pyleecan/Methods/Machine/Machine/comp_periodicity_time.py @@ -1,35 +1,40 @@ -from numpy import gcd - - -def comp_periodicity_time(self): +def comp_periodicity_time(self, slip=0): """Compute the (anti)-periodicities of the machine in time domain Parameters ---------- self : Machine A Machine object + slip: float + Rotor asynchronous slip Returns ------- - pert : int - Number of periodicities of the machine - is_apert : bool - True if an anti-periodicity is possible after the periodicities + pert_S : int + Number of periodicities of the machine over time period (p/felec by default if Nrev is None) in static referential + is_apert_S : bool + True if an anti-periodicity is possible after the periodicities (in static referential) + pert_R : int + Number of periodicities of the machine over time period (p/felec by default if Nrev is None) in rotating referential + is_apert_R : bool + True if an anti-periodicity is possible after the periodicities (in rotating referential) """ - # TODO - - # p = self.get_pole_pair_number() - # # Get stator (anti)-periodicity in spatial domain - # pera_s, is_antipera_s = self.stator.comp_periodicity_spatial() + if slip == 0: + # Rotor and fundamental field rotate synchronously - # # Get rotor (anti)-periodicities in spatial domain - # pera_r, is_antipera_r = self.rotor.comp_periodicity_spatial() + # In static referential (stator), rotor (anti)-periodicity in spatial domain + # becomes (anti)-periodicity in time domain + pert_S, is_apert_S = self.rotor.comp_periodicity_spatial() - # # Get machine spatial periodicity - # pera = int(gcd(gcd(pera_s, pera_r), p)) + # In rotating referential (rotor), fundamental field is static so there is no anti-periodicity + # and periodicity is given by stator spatial periodicity + pert_R, _ = self.stator.comp_periodicity_spatial() + is_apert_R = False - # # Get machine time and spatial anti-periodicities - # is_apera = bool(is_antipera_s and is_antipera_r) + else: + # In case of non-zero slip, rotor and fundamental field rotates asynchronously + # so there is no (anti)-periodicity in time domain + pert_S, is_apert_S, pert_R, is_apert_R = None, False, None, False - return pert, is_apert + return pert_S, is_apert_S, pert_R, is_apert_R diff --git a/pyleecan/Methods/Output/OutElec/store.py b/pyleecan/Methods/Output/OutElec/store.py index aef10c47f..d334f59e5 100644 --- a/pyleecan/Methods/Output/OutElec/store.py +++ b/pyleecan/Methods/Output/OutElec/store.py @@ -20,4 +20,4 @@ def store(self, out_dict, axes_dict): """ - # TODO \ No newline at end of file + # TODO diff --git a/pyleecan/Methods/Output/Output/getter/get_machine_periodicity.py b/pyleecan/Methods/Output/Output/getter/get_machine_periodicity.py index f862e9ad0..8043e7d94 100644 --- a/pyleecan/Methods/Output/Output/getter/get_machine_periodicity.py +++ b/pyleecan/Methods/Output/Output/getter/get_machine_periodicity.py @@ -1,4 +1,4 @@ -def get_machine_periodicity(self): +def get_machine_periodicity(self, is_rotor_ref=False): """Return / Compute the (anti)-periodicities of the machine in time and space domain Parameters @@ -9,33 +9,49 @@ def get_machine_periodicity(self): Returns ------- per_a : int - Number of space periodicities of the machine + Number of space periodicities of the machine over 2*pi is_antisym_a : bool True if an anti-periodicity is possible after the space periodicities per_t : int - Number of time periodicities of the machine + Number of time periodicities of the machine over time period (p/felec by default if Nrev is None) in static or rotating referential is_antisym_t : bool - True if an anti-periodicity is possible after the time periodicities + True if an anti-periodicity is possible after the time periodicities (in static or rotating referential) """ + if ( self.geo.per_a is None or self.geo.is_antiper_a is None - or self.geo.per_t is None - or self.geo.is_antiper_t is None + or self.geo.per_t_S is None + or self.geo.is_antiper_t_S is None + or self.geo.per_t_R is None + or self.geo.is_antiper_t_R is None ): + # Spatial periodicities ( self.geo.per_a, self.geo.is_antiper_a, ) = self.simu.machine.comp_periodicity_spatial() + # Time periodicities in both static and rotating referentials ( - self.geo.per_t, - self.geo.is_antiper_t, - ) = self.simu.machine.comp_periodicity_time() + self.geo.per_t_S, + self.geo.is_antiper_t_S, + self.geo.per_t_R, + self.geo.is_antiper_t_R, + ) = self.simu.machine.comp_periodicity_time(slip=self.elec.slip_ref) + + if is_rotor_ref: + return ( + self.geo.per_a, + self.geo.is_antiper_a, + self.geo.per_t_R, + self.geo.is_antiper_t_R, + ) - return ( - self.geo.per_a, - self.geo.is_antiper_a, - self.geo.per_t, - self.geo.is_antiper_t, - ) + else: + return ( + self.geo.per_a, + self.geo.is_antiper_a, + self.geo.per_t_S, + self.geo.is_antiper_t_S, + ) From fe340393dceb06eb0361d45275ec439fcc49cc0a Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 25 Aug 2021 14:56:01 +0200 Subject: [PATCH 026/167] [BC] Solve bugs due to new comp_periodicity methods [NF] New test to check periodicity in space and time [NF] Add comp_periodicity to WindingSC to be able to reuse LamSlotWind.comp_periodicity() for LamSquirrelCage --- Tests/Methods/Import/test_ImportGenPWM.py | 45 +------- .../Methods/Machine/test_comp_periodicity.py | 109 ++++++++++++++++++ Tests/Methods/Slot/test_comp_periodicity.py | 22 ---- .../Validation/Electrical/test_skin_effect.py | 2 +- pyleecan/Classes/Class_Dict.json | 3 +- pyleecan/Classes/WindingSC.py | 17 +++ .../ClassesRef/Machine/WindingSC.csv | 1 + .../comp_periodicity_spatial.py | 19 --- .../Machine/Winding/comp_connection_mat.py | 2 +- .../Machine/Winding/comp_periodicity.py | 2 +- .../Machine/Winding/get_periodicity.py | 2 +- .../Machine/WindingSC/comp_periodicity.py | 37 ++++++ .../Methods/Simulation/ELUT_PMSM/get_bemf.py | 12 +- 13 files changed, 180 insertions(+), 93 deletions(-) create mode 100644 Tests/Methods/Machine/test_comp_periodicity.py delete mode 100644 Tests/Methods/Slot/test_comp_periodicity.py delete mode 100644 pyleecan/Methods/Machine/LamSquirrelCage/comp_periodicity_spatial.py create mode 100644 pyleecan/Methods/Machine/WindingSC/comp_periodicity.py diff --git a/Tests/Methods/Import/test_ImportGenPWM.py b/Tests/Methods/Import/test_ImportGenPWM.py index 8c7e4859c..392ef1702 100644 --- a/Tests/Methods/Import/test_ImportGenPWM.py +++ b/Tests/Methods/Import/test_ImportGenPWM.py @@ -14,6 +14,8 @@ mkdir(save_path) +@pytest.mark.long_5s +@pytest.mark.long_1m def testSPWM(): """Check""" # fs, duration, f,fmax,fmode, fswimode,fswi, fswi_max,typePWM, Vdc1, U0, type_carrier @@ -61,45 +63,8 @@ def testSPWM(): ) -# def testSPWM(): -# """Check """ -# # fs, duration, f,fmax,fmode, fswimode,fswi, fswi_max,typePWM, Vdc1, U0, type_carrier - -# test_obj = ImportGenPWM( -# fs=96000, -# duration=2, -# f=1, -# fmax=5, -# fmode=0, -# fswimode=4, -# fswi=5, -# fswi_max=15, -# typePWM=8, -# Vdc1=2, -# U0=0.70, -# type_carrier=6, -# var_amp=20, -# ) -# # Generate the signal -# time = linspace(start=0, stop=2, num=2 * 96000, endpoint=True) -# Triphase = test_obj.get_data()[0] -# Vas = test_obj.get_data()[1] -# Triangle = test_obj.get_data()[3] - -# # Plot/save the result -# plt.close("all") -# plt.plot(time, Triphase[:, 1]) -# plt.plot(time, Vas) -# plt.plot(time, Triangle) -# fig = plt.gcf() -# fig.savefig( -# join( -# save_path, -# "test_ImportGenPWM_" + str(0) + "_" + str(4) + "_" + str(6) + "_SPWM.png", -# ) -# ) - - +@pytest.mark.long_5s +@pytest.mark.long_1m def testDPWM(): """Check""" for ii in range(9): @@ -135,6 +100,4 @@ def testDPWM(): if __name__ == "__main__": # testDPWM() - # testSPWM() - testSPWM() diff --git a/Tests/Methods/Machine/test_comp_periodicity.py b/Tests/Methods/Machine/test_comp_periodicity.py new file mode 100644 index 000000000..b5966544d --- /dev/null +++ b/Tests/Methods/Machine/test_comp_periodicity.py @@ -0,0 +1,109 @@ +from os.path import join + +from pyleecan.Classes.LamSlotWind import LamSlotWind + +import pytest + +from pyleecan.Functions.load import load +from pyleecan.definitions import DATA_DIR + + +"""pytest for comp_periodicity""" + + +@pytest.mark.periodicity +def test_comp_periodicity_spatial(): + rotor = LamSlotWind( + Rint=0.2, + Rext=0.5, + is_internal=True, + is_stator=False, + L1=0.95, + Nrvd=1, + Wrvd=0.05, + ) + rotor.winding = None + assert rotor.comp_periodicity_spatial() == (1, False) + + +@pytest.mark.periodicity +def test_comp_periodicity(): + + machine = load(join(DATA_DIR, "Machine", "Benchmark.json")) + + # Spatial periodicities + ( + per_a, + is_antiper_a, + ) = machine.comp_periodicity_spatial() + + assert per_a, is_antiper_a == (1, True) + + # Time periodicities in both static and rotating referentials + ( + per_t_S, + is_antiper_t_S, + per_t_R, + is_antiper_t_R, + ) = machine.comp_periodicity_time(slip=0) + + assert (per_t_S, is_antiper_t_S, per_t_R, is_antiper_t_R) == ( + 5, + True, + 1, + False, + ) + + machine = load(join(DATA_DIR, "Machine", "SPMSM_015.json")) + + # Spatial periodicities + ( + per_a, + is_antiper_a, + ) = machine.comp_periodicity_spatial() + + assert per_a, is_antiper_a == (9, False) + + # Time periodicities in both static and rotating referentials + ( + per_t_S, + is_antiper_t_S, + per_t_R, + is_antiper_t_R, + ) = machine.comp_periodicity_time(slip=0) + + assert (per_t_S, is_antiper_t_S, per_t_R, is_antiper_t_R) == ( + 9, + True, + 9, + False, + ) + + machine = load(join(DATA_DIR, "Machine", "Audi_eTron.json")) + + # Spatial periodicities + ( + per_a, + is_antiper_a, + ) = machine.comp_periodicity_spatial() + + assert per_a, is_antiper_a == (2, False) + + # Time periodicities in both static and rotating referentials + ( + per_t_S, + is_antiper_t_S, + per_t_R, + is_antiper_t_R, + ) = machine.comp_periodicity_time(slip=0) + + assert (per_t_S, is_antiper_t_S, per_t_R, is_antiper_t_R) == ( + 2, + False, + 2, + False, + ) + + +if __name__ == "__main__": + test_comp_periodicity() diff --git a/Tests/Methods/Slot/test_comp_periodicity.py b/Tests/Methods/Slot/test_comp_periodicity.py deleted file mode 100644 index 1cf92579d..000000000 --- a/Tests/Methods/Slot/test_comp_periodicity.py +++ /dev/null @@ -1,22 +0,0 @@ -from pyleecan.Classes.LamSlotWind import LamSlotWind -from pyleecan.Classes.Winding import Winding - -import pytest - - -"""pytest for comp_periodicity""" - - -@pytest.mark.periodicity -def test_comp_periodicity_spatial(): - rotor = LamSlotWind( - Rint=0.2, - Rext=0.5, - is_internal=True, - is_stator=False, - L1=0.95, - Nrvd=1, - Wrvd=0.05, - ) - rotor.winding = None - assert rotor.comp_periodicity_spatial() == (1, False) diff --git a/Tests/Validation/Electrical/test_skin_effect.py b/Tests/Validation/Electrical/test_skin_effect.py index 80235951f..52b11284a 100644 --- a/Tests/Validation/Electrical/test_skin_effect.py +++ b/Tests/Validation/Electrical/test_skin_effect.py @@ -34,7 +34,7 @@ # @pytest.mark.IPMSM # @pytest.mark.periodicity # @pytest.mark.SingleOP -@pytest.skip(reason="Not finished yet") +@pytest.mark.skip(reason="Not finished yet") def test_skin_effect(): """Validation of the PMSM Electrical Equivalent Circuit with the Prius machine Compute Torque from EEC results and compare with Yang et al, 2013 diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 3989c358b..43d146cd5 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -13639,7 +13639,8 @@ "is_internal": false, "methods": [ "comp_connection_mat", - "get_dim_wind" + "get_dim_wind", + "comp_periodicity" ], "mother": "Winding", "name": "WindingSC", diff --git a/pyleecan/Classes/WindingSC.py b/pyleecan/Classes/WindingSC.py index c15e14c3b..6cc360524 100644 --- a/pyleecan/Classes/WindingSC.py +++ b/pyleecan/Classes/WindingSC.py @@ -27,6 +27,11 @@ except ImportError as error: get_dim_wind = error +try: + from ..Methods.Machine.WindingSC.comp_periodicity import comp_periodicity +except ImportError as error: + comp_periodicity = error + from numpy import array, array_equal from ._check import InitUnKnowClassError @@ -64,6 +69,18 @@ class WindingSC(Winding): ) else: get_dim_wind = get_dim_wind + # cf Methods.Machine.WindingSC.comp_periodicity + if isinstance(comp_periodicity, ImportError): + comp_periodicity = property( + fget=lambda x: raise_( + ImportError( + "Can't use WindingSC method comp_periodicity: " + + str(comp_periodicity) + ) + ) + ) + else: + comp_periodicity = comp_periodicity # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Generator/ClassesRef/Machine/WindingSC.csv b/pyleecan/Generator/ClassesRef/Machine/WindingSC.csv index 8744c0b6e..0adfb3b35 100644 --- a/pyleecan/Generator/ClassesRef/Machine/WindingSC.csv +++ b/pyleecan/Generator/ClassesRef/Machine/WindingSC.csv @@ -1,3 +1,4 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description ,,,,,,,,,Machine,Winding,comp_connection_mat,VERSION,1,short-circuit winding (e.g. squirrel cage type) ,,,,,,,,,,,get_dim_wind,NAME,"""short-circuit""", +,,,,,,,,,,,comp_periodicity,,, diff --git a/pyleecan/Methods/Machine/LamSquirrelCage/comp_periodicity_spatial.py b/pyleecan/Methods/Machine/LamSquirrelCage/comp_periodicity_spatial.py deleted file mode 100644 index f255463d9..000000000 --- a/pyleecan/Methods/Machine/LamSquirrelCage/comp_periodicity_spatial.py +++ /dev/null @@ -1,19 +0,0 @@ -def comp_periodicity_spatial(self): - """Compute the periodicity factor of the lamination - - Parameters - ---------- - self : LamSquirrelCage - A LamSquirrelCage object - - Returns - ------- - per_a : int - Number of spatial periodicities of the lamination over 2*pi - is_antiper_a : bool - True if an spatial anti-periodicity is possible after the periodicities - """ - - Zs = self.get_Zs() - - return Zs, bool(Zs % 2 == 0) diff --git a/pyleecan/Methods/Machine/Winding/comp_connection_mat.py b/pyleecan/Methods/Machine/Winding/comp_connection_mat.py index 0a6568684..5a912fc9b 100644 --- a/pyleecan/Methods/Machine/Winding/comp_connection_mat.py +++ b/pyleecan/Methods/Machine/Winding/comp_connection_mat.py @@ -141,7 +141,7 @@ def comp_connection_mat(self, Zs=None, p=None): # self.is_aper_a = wdg.get_is_symmetric() # To check periodicities swat-em / pyleecan definitions - self.per_a, self.is_aper_a = self.comp_periodicity_spatial(wind_mat=wind_mat) + self.per_a, self.is_aper_a = self.comp_periodicity(wind_mat=wind_mat) # if is_aper_a: # Different def for Anti per # per_a = per_a / 2 # if self.per_a != per_a or self.is_aper_a != is_aper_a: diff --git a/pyleecan/Methods/Machine/Winding/comp_periodicity.py b/pyleecan/Methods/Machine/Winding/comp_periodicity.py index b4cf4a3b9..41722e095 100644 --- a/pyleecan/Methods/Machine/Winding/comp_periodicity.py +++ b/pyleecan/Methods/Machine/Winding/comp_periodicity.py @@ -2,7 +2,7 @@ from numpy.linalg import norm -def comp_periodicity_spatial(self, wind_mat=None): +def comp_periodicity(self, wind_mat=None): """Computes the winding matrix (anti-)periodicity Parameters diff --git a/pyleecan/Methods/Machine/Winding/get_periodicity.py b/pyleecan/Methods/Machine/Winding/get_periodicity.py index b0e86a426..b0878e66e 100644 --- a/pyleecan/Methods/Machine/Winding/get_periodicity.py +++ b/pyleecan/Methods/Machine/Winding/get_periodicity.py @@ -17,6 +17,6 @@ def get_periodicity(self): if self.per_a is None or self.is_aper_a is None: - self.per_a, self.is_aper_a = self.comp_periodicity_spatial() + self.per_a, self.is_aper_a = self.comp_periodicity() return self.per_a, self.is_aper_a diff --git a/pyleecan/Methods/Machine/WindingSC/comp_periodicity.py b/pyleecan/Methods/Machine/WindingSC/comp_periodicity.py new file mode 100644 index 000000000..b6996dab5 --- /dev/null +++ b/pyleecan/Methods/Machine/WindingSC/comp_periodicity.py @@ -0,0 +1,37 @@ +from ....Classes.Lamination import Lamination + + +def comp_periodicity(self): + """Computes the winding matrix (anti-)periodicity + + Parameters + ---------- + self : WindingSC + A WindingSC object + + Returns + ------- + per_a: int + Number of spatial periods of the winding + is_aper_a: bool + True if the winding is anti-periodic over space + + """ + + lamination = self.parent + + if lamination is not None: + + # Call comp_periodicity of Lamination class since squirrel cage periodicity + # depends on lamination properties: number of pole pairs and number of slots + per_a, is_aper_a = Lamination.comp_periodicity_spatial(lamination) + + if is_aper_a: + # Multiply periodicity by 2 to be compliant with Winding.comp_periodicity() + per_a = int(2 * per_a) + + return per_a, is_aper_a + + else: + + return None, False diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_bemf.py b/pyleecan/Methods/Simulation/ELUT_PMSM/get_bemf.py index 76e9d419c..065306926 100644 --- a/pyleecan/Methods/Simulation/ELUT_PMSM/get_bemf.py +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/get_bemf.py @@ -12,12 +12,12 @@ def get_bemf(self): (1,:) rotor position [rad] """ + # TODO -# calculating bemf from MLUT -Phi_dqh = self.Phi_dqh -I_dqh = self.I_dqh + # # calculating bemf from MLUT + # Phi_dqh = self.Phi_dqh + # I_dqh = self.I_dqh -# Phi0_dqh = Phi_dqh[I_dqh.index([0,0,0]),:] + # # Phi0_dqh = Phi_dqh[I_dqh.index([0,0,0]),:] - -return bemf + # return bemf From 572c9ee68b4d766c0a146dcb25a9732a88ed6c2f Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 25 Aug 2021 15:01:11 +0200 Subject: [PATCH 027/167] [BC] Correct upper letter in keys in axes_dict [CC] Remove unused method comp_axes of MagElmer.csv [CO] Calculate time and angle periodicity from OutMag.axes_dict in Force.comp_axes if is_periodicity_XX are None in Force model --- .../Magnetics/test_FEMM_periodicity.py | 38 ++++++++++++++++--- pyleecan/Classes/Class_Dict.json | 1 - pyleecan/Classes/MagElmer.py | 14 ------- .../ClassesRef/Simulation/MagElmer.csv | 10 ++--- .../Machine/WindingSC/comp_periodicity.py | 2 +- pyleecan/Methods/Output/OutForce/store.py | 2 +- pyleecan/Methods/Output/OutMag/store.py | 6 +-- .../Methods/Simulation/Force/comp_axes.py | 19 ++++++++-- .../Methods/Simulation/ForceMT/comp_force.py | 4 +- .../ForceTensor/comp_force_nodal.py | 2 +- .../Methods/Simulation/InputElec/gen_input.py | 15 ++++---- .../Methods/Simulation/InputFlux/comp_axes.py | 2 +- .../Simulation/InputVoltage/gen_input.py | 15 ++++---- .../Methods/Simulation/MagElmer/comp_axes.py | 26 ------------- .../Simulation/MagElmer/comp_flux_airgap.py | 6 +-- .../Simulation/MagFEMM/comp_flux_airgap.py | 4 +- .../Methods/Simulation/Magnetics/comp_axes.py | 10 ++--- 17 files changed, 88 insertions(+), 88 deletions(-) delete mode 100644 pyleecan/Methods/Simulation/MagElmer/comp_axes.py diff --git a/Tests/Validation/Magnetics/test_FEMM_periodicity.py b/Tests/Validation/Magnetics/test_FEMM_periodicity.py index e3e594b57..42ea2c5a1 100644 --- a/Tests/Validation/Magnetics/test_FEMM_periodicity.py +++ b/Tests/Validation/Magnetics/test_FEMM_periodicity.py @@ -34,7 +34,9 @@ def test_FEMM_periodicity_time_no_periodicity_a(): assert SPMSM_015.comp_periodicity_spatial() == (9, False) - simu = Simu1(name="test_FEMM_periodicity_time_no_periodicity_a", machine=SPMSM_015) + name = "test_FEMM_periodicity_time_no_periodicity_a" + + simu = Simu1(name=name + "_1", machine=SPMSM_015) # Definition of the enforced output of the electrical module I0_rms = 250 / sqrt(2) @@ -64,6 +66,7 @@ def test_FEMM_periodicity_time_no_periodicity_a(): # Definition of the magnetic simulation: no periodicity simu2 = simu.copy() + simu2.name = name + "_2" simu2.mag.is_periodicity_t = False # Run simulations @@ -181,7 +184,9 @@ def test_FEMM_periodicity_time(): assert SPMSM_015.comp_periodicity_spatial() == (9, False) - simu = Simu1(name="test_FEMM_periodicity_time", machine=SPMSM_015) + name = "test_FEMM_periodicity_time" + + simu = Simu1(name=name + "_1", machine=SPMSM_015) # Definition of the enforced output of the electrical module I0_rms = 250 / sqrt(2) @@ -211,6 +216,7 @@ def test_FEMM_periodicity_time(): # Definition of the magnetic simulation: no periodicity simu2 = simu.copy() + simu2.name = name + "_2" simu2.mag.is_periodicity_t = False # Run simulations @@ -256,7 +262,27 @@ def test_FEMM_periodicity_time(): "angle[0]", data_list=[out2.force.AGSF], legend_list=["Periodic", "Full"], - save_path=join(save_path, simu.name + "_P_fft2.png"), + save_path=join(save_path, simu.name + "_P_freqs.png"), + is_show_fig=False, + **dict_2D + ) + + out.force.AGSF.plot_2D_Data( + "time", + "angle[0]", + data_list=[out2.force.AGSF], + legend_list=["Periodic", "Full"], + save_path=join(save_path, simu.name + "_P_time.png"), + is_show_fig=False, + **dict_2D + ) + + out.force.AGSF.plot_2D_Data( + "angle", + "time[0]", + data_list=[out2.force.AGSF], + legend_list=["Periodic", "Full"], + save_path=join(save_path, simu.name + "_P_angle.png"), is_show_fig=False, **dict_2D ) @@ -309,7 +335,7 @@ def test_FEMM_periodicity_time(): Prad2 = result_AGSF2["radial"] time = result_AGSF2["time"] - assert_array_almost_equal(Prad / 1000, Prad2 / 1000, decimal=0) + assert_array_almost_equal(Prad / 100000, Prad2 / 100000, decimal=2) return out, out2 @@ -450,6 +476,6 @@ def test_FEMM_periodicity_angle(): # To run it without pytest if __name__ == "__main__": - out, out2 = test_FEMM_periodicity_angle() + # out5, out6 = test_FEMM_periodicity_time_no_periodicity_a() out3, out4 = test_FEMM_periodicity_time() - out5, out6 = test_FEMM_periodicity_time_no_periodicity_a() + # out, out2 = test_FEMM_periodicity_angle() diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 43d146cd5..bda1dd76f 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -5816,7 +5816,6 @@ "get_meshsolution", "get_path_save_fea", "get_path_save", - "comp_axes", "gen_elmer_mesh" ], "mother": "Magnetics", diff --git a/pyleecan/Classes/MagElmer.py b/pyleecan/Classes/MagElmer.py index 040ffb35a..b3c895bde 100644 --- a/pyleecan/Classes/MagElmer.py +++ b/pyleecan/Classes/MagElmer.py @@ -42,11 +42,6 @@ except ImportError as error: get_path_save = error -try: - from ..Methods.Simulation.MagElmer.comp_axes import comp_axes -except ImportError as error: - comp_axes = error - try: from ..Methods.Simulation.MagElmer.gen_elmer_mesh import gen_elmer_mesh except ImportError as error: @@ -119,15 +114,6 @@ class MagElmer(Magnetics): ) else: get_path_save = get_path_save - # cf Methods.Simulation.MagElmer.comp_axes - if isinstance(comp_axes, ImportError): - comp_axes = property( - fget=lambda x: raise_( - ImportError("Can't use MagElmer method comp_axes: " + str(comp_axes)) - ) - ) - else: - comp_axes = comp_axes # cf Methods.Simulation.MagElmer.gen_elmer_mesh if isinstance(gen_elmer_mesh, ImportError): gen_elmer_mesh = property( diff --git a/pyleecan/Generator/ClassesRef/Simulation/MagElmer.csv b/pyleecan/Generator/ClassesRef/Simulation/MagElmer.csv index 8ce7d7da8..eecbc56e5 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/MagElmer.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/MagElmer.csv @@ -2,11 +2,11 @@ Variable name,Unit,Description,Size,Type,Default value,Minimum value,Maximum val Kmesh_fineness,,"global coefficient to adjust mesh fineness (1 : default , > 1 : finner , < 1 : less fine)",0,float,1,,,,Simulation,Magnetics,comp_flux_airgap,VERSION,1,Magnetic module: Finite Element model with Elmer, Kgeo_fineness,,"global coefficient to adjust geometry fineness (1 : default , > 1 : finner , < 1 : less fine)",0,float,1,,,,,,solve_FEA,,,, file_name,,Name of the file to save the Elmer model,0,str,,,,,,,get_meshsolution,,,, -FEA_dict,,"To enforce user-defined values for Elmer main parameters ",0,dict,,,,,,,get_path_save_fea,,,, -is_get_mesh,,"To save FEA mesh for latter post-procesing ",0,bool,0,,,,,,get_path_save,,,, -is_save_FEA,,To save FEA mesh and solution in a file,0,bool,0,,,,,,comp_axes,,,, -transform_list,,"List of dictionary to apply transformation on the machine surfaces. Key: label (to select the surface), type (rotate or translate), value (alpha or delta)",0,list,[],,,,,,gen_elmer_mesh,,,, +FEA_dict,,To enforce user-defined values for Elmer main parameters ,0,dict,,,,,,,get_path_save_fea,,,, +is_get_mesh,,To save FEA mesh for latter post-procesing ,0,bool,0,,,,,,get_path_save,,,, +is_save_FEA,,To save FEA mesh and solution in a file,0,bool,0,,,,,,gen_elmer_mesh,,,, +transform_list,,"List of dictionary to apply transformation on the machine surfaces. Key: label (to select the surface), type (rotate or translate), value (alpha or delta)",0,list,[],,,,,,,,,, rotor_dxf,,To use a dxf version of the rotor instead of build_geometry,,DXFImport,None,,,,,,,,,, stator_dxf,,To use a dxf version of the rotor instead of build_geometry,,DXFImport,None,,,,,,,,,, import_file,,To import an existing simulation file,0,str,,,,,,,,,,, -nb_worker,,To run Elmer in parallel (the parallelization is on the time loop),,int,1,,,,,,,,,, \ No newline at end of file +nb_worker,,To run Elmer in parallel (the parallelization is on the time loop),,int,1,,,,,,,,,, diff --git a/pyleecan/Methods/Machine/WindingSC/comp_periodicity.py b/pyleecan/Methods/Machine/WindingSC/comp_periodicity.py index b6996dab5..9c9f9d125 100644 --- a/pyleecan/Methods/Machine/WindingSC/comp_periodicity.py +++ b/pyleecan/Methods/Machine/WindingSC/comp_periodicity.py @@ -22,7 +22,7 @@ def comp_periodicity(self): if lamination is not None: - # Call comp_periodicity of Lamination class since squirrel cage periodicity + # Call comp_periodicity of Lamination class since squirrel cage periodicity # depends on lamination properties: number of pole pairs and number of slots per_a, is_aper_a = Lamination.comp_periodicity_spatial(lamination) diff --git a/pyleecan/Methods/Output/OutForce/store.py b/pyleecan/Methods/Output/OutForce/store.py index 6c9693384..01556060f 100644 --- a/pyleecan/Methods/Output/OutForce/store.py +++ b/pyleecan/Methods/Output/OutForce/store.py @@ -21,7 +21,7 @@ def store(self, out_dict, axes_dict): # Store air-gap surface force as VectorField object # Axes for each component - axis_list = [axes_dict["Time"], axes_dict["Angle"]] + axis_list = [axes_dict["time"], axes_dict["angle"]] # Create VectorField with empty components self.AGSF = VectorField( diff --git a/pyleecan/Methods/Output/OutMag/store.py b/pyleecan/Methods/Output/OutMag/store.py index d6e3faccc..4ee8ea47a 100644 --- a/pyleecan/Methods/Output/OutMag/store.py +++ b/pyleecan/Methods/Output/OutMag/store.py @@ -24,11 +24,11 @@ def store(self, out_dict, axes_dict): self.axes_dict = axes_dict # Get time axis - Time = axes_dict["Time"] + Time = axes_dict["time"] # Store airgap flux as VectorField object # Axes for each airgap flux component - axis_list = [Time, axes_dict["Angle"]] + axis_list = [Time, axes_dict["angle"]] # Create VectorField with empty components self.B = VectorField( name="Airgap flux density", @@ -71,7 +71,7 @@ def store(self, out_dict, axes_dict): name="Electromagnetic torque", unit="Nm", symbol="T_{em}", - axes=[axes_dict["Time_Tem"]], + axes=[axes_dict["time_Tem"]], values=Tem, ) diff --git a/pyleecan/Methods/Simulation/Force/comp_axes.py b/pyleecan/Methods/Simulation/Force/comp_axes.py index 098fd2bdc..22342b918 100644 --- a/pyleecan/Methods/Simulation/Force/comp_axes.py +++ b/pyleecan/Methods/Simulation/Force/comp_axes.py @@ -20,6 +20,19 @@ def comp_axes(self, output): # Get axis dict from OutMag axes_dict_mag = output.mag.axes_dict + # Get time periodicity + is_include_per_t = len(axes_dict_mag["time"].symmetries) > 0 + + # Get angle periodicity + is_include_per_a = len(axes_dict_mag["angle"].symmetries) > 0 + + # Init periodicities if they are None + if self.is_periodicity_t is None: + self.is_periodicity_t = is_include_per_t + + if self.is_periodicity_a is None: + self.is_periodicity_a = is_include_per_a + # Get time and space (anti-)periodicities of the machine ( per_a, @@ -33,7 +46,7 @@ def comp_axes(self, output): axis_in=axes_dict_mag["time"], per=per_t, is_aper=is_antiper_t, - is_include_per=self.is_periodicity_t, + is_include_per=is_include_per_t and self.is_periodicity_t, is_remove_aper=True, ) @@ -54,7 +67,7 @@ def comp_axes(self, output): axis_in=axes_dict_mag["angle"], per=per_a, is_aper=is_antiper_a, - is_include_per=self.is_periodicity_a, + is_include_per=is_include_per_a and self.is_periodicity_a, is_remove_aper=True, ) @@ -70,6 +83,6 @@ def comp_axes(self, output): + "). Angular periodicity removed" ) - axes_dict = {"Time": Time, "Angle": Angle} + axes_dict = {"time": Time, "angle": Angle} return axes_dict diff --git a/pyleecan/Methods/Simulation/ForceMT/comp_force.py b/pyleecan/Methods/Simulation/ForceMT/comp_force.py index 01158ebd8..f278839cb 100644 --- a/pyleecan/Methods/Simulation/ForceMT/comp_force.py +++ b/pyleecan/Methods/Simulation/ForceMT/comp_force.py @@ -33,8 +33,8 @@ def comp_force(self, output, axes_dict): out_dict["Rag"] = Rag # Get time and angular axes - Angle = axes_dict["Angle"] - Time = axes_dict["Time"] + Angle = axes_dict["angle"] + Time = axes_dict["time"] # Import angular vector from Angle Data object is_periodicity_a, is_antiper_a = Angle.get_periodicity() diff --git a/pyleecan/Methods/Simulation/ForceTensor/comp_force_nodal.py b/pyleecan/Methods/Simulation/ForceTensor/comp_force_nodal.py index 1e1f5dd91..b2b8eb672 100644 --- a/pyleecan/Methods/Simulation/ForceTensor/comp_force_nodal.py +++ b/pyleecan/Methods/Simulation/ForceTensor/comp_force_nodal.py @@ -23,7 +23,7 @@ def comp_force_nodal(self, output, axes_dict): """ dim = 2 - Time = axes_dict["Time"] + Time = axes_dict["time"] Nt_tot = Time.get_length() # Number of time step meshsolution_mag = output.mag.meshsolution # Comes from FEMM simulation diff --git a/pyleecan/Methods/Simulation/InputElec/gen_input.py b/pyleecan/Methods/Simulation/InputElec/gen_input.py index bebf5ff2e..d502edd4f 100644 --- a/pyleecan/Methods/Simulation/InputElec/gen_input.py +++ b/pyleecan/Methods/Simulation/InputElec/gen_input.py @@ -31,18 +31,19 @@ def gen_input(self): # Set time and angle full axes in geometry output Time, Angle = self.comp_axes(simu.machine) - outgeo.axes_dict = {"Time": Time, "Angle": Angle} + outgeo.axes_dict = {"time": Time, "angle": Angle} - # Create time axis in electrical output accounting for pole periodicity + # Create time axis in electrical output without periodicity + # TODO: account for pole periodicity Time_elec = Time.copy() Time_elec, _ = create_from_axis( axis_in=Time, - per=int(2 * simu.machine.get_pole_pair_number()), - is_aper=True, - is_include_per=True, - is_remove_aper=False, + per=1, # int(2 * simu.machine.get_pole_pair_number()), + is_aper=False, # True, + is_include_per=False, # True, + is_remove_aper=True, # False, ) - outelec.axes_dict = {"Time": Time_elec} + outelec.axes_dict = {"time": Time_elec} # Initialize outelec at None outelec.Id_ref = None diff --git a/pyleecan/Methods/Simulation/InputFlux/comp_axes.py b/pyleecan/Methods/Simulation/InputFlux/comp_axes.py index 9168c9523..3e5687d33 100644 --- a/pyleecan/Methods/Simulation/InputFlux/comp_axes.py +++ b/pyleecan/Methods/Simulation/InputFlux/comp_axes.py @@ -139,6 +139,6 @@ def comp_axes( ) # Store in axes_dict - axes_dict = {"Time": Time, "Angle": Angle} + axes_dict = {"time": Time, "angle": Angle} return axes_dict diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 5b7f4d7f6..c4283b417 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -35,18 +35,19 @@ def gen_input(self): # Set time and angle full axes in geometry output Time, Angle = self.comp_axes(simu.machine) - outgeo.axes_dict = {"Time": Time, "Angle": Angle} + outgeo.axes_dict = {"time": Time, "angle": Angle} - # Create time axis in electrical output accounting for pole periodicity + # Create time axis in electrical output without periodicity + # TODO: account for pole periodicity Time_elec = Time.copy() Time_elec, _ = create_from_axis( axis_in=Time, - per=int(2 * simu.machine.get_pole_pair_number()), - is_aper=True, - is_include_per=True, - is_remove_aper=False, + per=1, # int(2 * simu.machine.get_pole_pair_number()), + is_aper=False, # True, + is_include_per=False, # True, + is_remove_aper=True, # False, ) - outelec.axes_dict = {"Time": Time_elec} + outelec.axes_dict = {"time": Time_elec} outelec.N0 = self.N0 outelec.felec = self.comp_felec() # TODO introduce set_felec(slip) diff --git a/pyleecan/Methods/Simulation/MagElmer/comp_axes.py b/pyleecan/Methods/Simulation/MagElmer/comp_axes.py deleted file mode 100644 index 2bb28510b..000000000 --- a/pyleecan/Methods/Simulation/MagElmer/comp_axes.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -from ....Classes.Magnetics import Magnetics - - -def comp_axes(self, output): - """Compute the additional axes required in the MagElmer module - - Parameters - ---------- - self : Magnetic - a Magnetic object - output : Output - an Output object (to update) - - Returns - ------- - axes_dict: {Data} - Dict containing Time_Tem axis used in MagFEMM to store torque result - """ - # Calculate standard axes from Magnetics model - axes_dict = Magnetics.comp_axes(self, output) - - # Add other axes if requested by Elmer - # TODO - - return axes_dict diff --git a/pyleecan/Methods/Simulation/MagElmer/comp_flux_airgap.py b/pyleecan/Methods/Simulation/MagElmer/comp_flux_airgap.py index 845f07a57..0ea8b75dc 100644 --- a/pyleecan/Methods/Simulation/MagElmer/comp_flux_airgap.py +++ b/pyleecan/Methods/Simulation/MagElmer/comp_flux_airgap.py @@ -26,8 +26,8 @@ def comp_flux_airgap(self, output, axes_dict): output.mag.internal = OutMagElmer() # Get time and angular axes - Angle = axes_dict["Angle"] - Time = axes_dict["Time"] + Angle = axes_dict["angle"] + Time = axes_dict["time"] # Set the angular symmetry factor according to the machine and check if it is anti-periodic sym, is_antiper_a = Angle.get_periodicity() @@ -37,7 +37,7 @@ def comp_flux_airgap(self, output, axes_dict): is_oneperiod=self.is_periodicity_a, is_antiperiod=is_antiper_a and self.is_periodicity_a, ) - Na = angle.size + # Na = angle.size # Check if the time axis is anti-periodic _, is_antiper_t = Time.get_periodicity() diff --git a/pyleecan/Methods/Simulation/MagFEMM/comp_flux_airgap.py b/pyleecan/Methods/Simulation/MagFEMM/comp_flux_airgap.py index 27742ca27..b8f2996f5 100644 --- a/pyleecan/Methods/Simulation/MagFEMM/comp_flux_airgap.py +++ b/pyleecan/Methods/Simulation/MagFEMM/comp_flux_airgap.py @@ -48,8 +48,8 @@ def comp_flux_airgap(self, output, axes_dict): output.mag.internal = OutMagFEMM() # Get time and angular axes - Angle = axes_dict["Angle"] - Time = axes_dict["Time"] + Angle = axes_dict["angle"] + Time = axes_dict["time"] # Set the angular symmetry factor according to the machine and check if it is anti-periodic sym, is_antiper_a = Angle.get_periodicity() diff --git a/pyleecan/Methods/Simulation/Magnetics/comp_axes.py b/pyleecan/Methods/Simulation/Magnetics/comp_axes.py index a8b6c3dbb..13e44f63a 100644 --- a/pyleecan/Methods/Simulation/Magnetics/comp_axes.py +++ b/pyleecan/Methods/Simulation/Magnetics/comp_axes.py @@ -19,8 +19,8 @@ def comp_axes(self, output): """ - # Get axis dict from OutElec - axes_dict_elec = output.elec.axes_dict + # Get axis dict from OutGeo + axes_dict_geo = output.geo.axes_dict # Calculate axes for Magnetics module calculation # Get time and space (anti-)periodicities of the machine @@ -33,7 +33,7 @@ def comp_axes(self, output): # Compute Time axis based on the one stored in OutElec Time, is_periodicity_t = create_from_axis( - axis_in=axes_dict_elec["time"], + axis_in=axes_dict_geo["time"], per=per_t, is_aper=is_antiper_t, is_include_per=self.is_periodicity_t, @@ -54,7 +54,7 @@ def comp_axes(self, output): # Compute Angle axis based on the one stored in OutElec Angle, is_periodicity_a = create_from_axis( - axis_in=axes_dict_elec["angle"], + axis_in=axes_dict_geo["angle"], per=per_a, is_aper=is_antiper_a, is_include_per=self.is_periodicity_a, @@ -82,6 +82,6 @@ def comp_axes(self, output): Time_Tem.symmetries["period"] = Time_Tem.symmetries.pop("antiperiod") # Store in axis dict - axes_dict = {"Time": Time, "Angle": Angle, "Time_Tem": Time_Tem} + axes_dict = {"time": Time, "angle": Angle, "time_Tem": Time_Tem} return axes_dict From 37f3a4beed25b77b9a0e98185e8e5864bccabc02 Mon Sep 17 00:00:00 2001 From: BenjaminGabet Date: Wed, 1 Sep 2021 16:28:17 +0200 Subject: [PATCH 028/167] [BC] Adding methods --- pyleecan/Methods/Simulation/ELUT_PMSM/comp_Ldqh_from_Phidqh.py | 3 +++ .../Methods/Simulation/ELUT_PMSM/comp_Phidqh_from_Phiwind.py | 3 +++ pyleecan/Methods/Simulation/ELUT_PMSM/get_Ld.py | 3 +++ pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmd.py | 3 +++ pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmq.py | 3 +++ pyleecan/Methods/Simulation/ELUT_PMSM/get_Lq.py | 3 +++ pyleecan/Methods/Simulation/ELUT_PMSM/import_from_data.py | 3 +++ pyleecan/Methods/Simulation/ELUT_SCIM/comp_Lm_from_Phim.py | 3 +++ pyleecan/Methods/Simulation/ELUT_SCIM/get_Lm.py | 3 +++ pyleecan/Methods/Simulation/ELUT_SCIM/import_from_data.py | 3 +++ 10 files changed, 30 insertions(+) create mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/comp_Ldqh_from_Phidqh.py create mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/comp_Phidqh_from_Phiwind.py create mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/get_Ld.py create mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmd.py create mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmq.py create mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/get_Lq.py create mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/import_from_data.py create mode 100644 pyleecan/Methods/Simulation/ELUT_SCIM/comp_Lm_from_Phim.py create mode 100644 pyleecan/Methods/Simulation/ELUT_SCIM/get_Lm.py create mode 100644 pyleecan/Methods/Simulation/ELUT_SCIM/import_from_data.py diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Ldqh_from_Phidqh.py b/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Ldqh_from_Phidqh.py new file mode 100644 index 000000000..c3509c277 --- /dev/null +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Ldqh_from_Phidqh.py @@ -0,0 +1,3 @@ +def comp_Ldqh_from_Phidqh(self): + pass + # TODO \ No newline at end of file diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Phidqh_from_Phiwind.py b/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Phidqh_from_Phiwind.py new file mode 100644 index 000000000..5f57381d7 --- /dev/null +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Phidqh_from_Phiwind.py @@ -0,0 +1,3 @@ +def comp_Phidqh_from_Phiwind(self): + pass + # TODO \ No newline at end of file diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Ld.py b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Ld.py new file mode 100644 index 000000000..e7a4c4d0c --- /dev/null +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Ld.py @@ -0,0 +1,3 @@ +def get_Ld(self): + pass + # TODO \ No newline at end of file diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmd.py b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmd.py new file mode 100644 index 000000000..85e5c5044 --- /dev/null +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmd.py @@ -0,0 +1,3 @@ +def get_Lmd(self): + pass + # TODO \ No newline at end of file diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmq.py b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmq.py new file mode 100644 index 000000000..3edea786d --- /dev/null +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmq.py @@ -0,0 +1,3 @@ +def get_Lmq(self): + pass + # TODO \ No newline at end of file diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lq.py b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lq.py new file mode 100644 index 000000000..8be1d23f1 --- /dev/null +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lq.py @@ -0,0 +1,3 @@ +def get_Lq(self): + pass + # TODO \ No newline at end of file diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/import_from_data.py b/pyleecan/Methods/Simulation/ELUT_PMSM/import_from_data.py new file mode 100644 index 000000000..0cb591cd3 --- /dev/null +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/import_from_data.py @@ -0,0 +1,3 @@ +def import_from_data(self): + pass + # TODO \ No newline at end of file diff --git a/pyleecan/Methods/Simulation/ELUT_SCIM/comp_Lm_from_Phim.py b/pyleecan/Methods/Simulation/ELUT_SCIM/comp_Lm_from_Phim.py new file mode 100644 index 000000000..92a1f0320 --- /dev/null +++ b/pyleecan/Methods/Simulation/ELUT_SCIM/comp_Lm_from_Phim.py @@ -0,0 +1,3 @@ +def comp_Lm_from_Phim(self): + pass + # TODO \ No newline at end of file diff --git a/pyleecan/Methods/Simulation/ELUT_SCIM/get_Lm.py b/pyleecan/Methods/Simulation/ELUT_SCIM/get_Lm.py new file mode 100644 index 000000000..6a82a556c --- /dev/null +++ b/pyleecan/Methods/Simulation/ELUT_SCIM/get_Lm.py @@ -0,0 +1,3 @@ +def get_Lm(self): + pass + # TODO \ No newline at end of file diff --git a/pyleecan/Methods/Simulation/ELUT_SCIM/import_from_data.py b/pyleecan/Methods/Simulation/ELUT_SCIM/import_from_data.py new file mode 100644 index 000000000..0cb591cd3 --- /dev/null +++ b/pyleecan/Methods/Simulation/ELUT_SCIM/import_from_data.py @@ -0,0 +1,3 @@ +def import_from_data(self): + pass + # TODO \ No newline at end of file From 13d6bcd2f23249e32f254dff3f49bc6999ff09af Mon Sep 17 00:00:00 2001 From: BenjaminGabet Date: Thu, 2 Sep 2021 10:29:19 +0200 Subject: [PATCH 029/167] Adding methods to csv --- pyleecan/Classes/Class_Dict.json | 6 ++++-- pyleecan/Classes/EEC_PMSM.py | 17 +++++++++++++++++ pyleecan/Classes/OutElec.py | 14 ++++++++++++++ .../Generator/ClassesRef/Output/OutElec.csv | 2 +- .../ClassesRef/Simulation/EEC_PMSM.csv | 2 +- .../ELUT_PMSM/comp_Ldqh_from_Phidqh.py | 2 +- .../ELUT_PMSM/comp_Phidqh_from_Phiwind.py | 2 +- pyleecan/Methods/Simulation/ELUT_PMSM/get_Ld.py | 2 +- .../Methods/Simulation/ELUT_PMSM/get_Lmd.py | 2 +- .../Methods/Simulation/ELUT_PMSM/get_Lmq.py | 2 +- pyleecan/Methods/Simulation/ELUT_PMSM/get_Lq.py | 2 +- .../Simulation/ELUT_PMSM/import_from_data.py | 2 +- .../Simulation/ELUT_SCIM/comp_Lm_from_Phim.py | 2 +- pyleecan/Methods/Simulation/ELUT_SCIM/get_Lm.py | 2 +- .../Simulation/ELUT_SCIM/import_from_data.py | 2 +- 15 files changed, 47 insertions(+), 14 deletions(-) diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index bda1dd76f..58b726eba 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -1092,7 +1092,8 @@ "comp_parameters", "solve_EEC", "gen_drive", - "comp_joule_losses" + "comp_joule_losses", + "comp_BEMF_harmonics" ], "mother": "EEC", "name": "EEC_PMSM", @@ -7839,7 +7840,8 @@ "get_Nr", "get_Is", "get_Us", - "comp_I_mag" + "comp_I_mag", + "store" ], "mother": "", "name": "OutElec", diff --git a/pyleecan/Classes/EEC_PMSM.py b/pyleecan/Classes/EEC_PMSM.py index 0a784a519..dcea923fd 100644 --- a/pyleecan/Classes/EEC_PMSM.py +++ b/pyleecan/Classes/EEC_PMSM.py @@ -37,6 +37,11 @@ except ImportError as error: comp_joule_losses = error +try: + from ..Methods.Simulation.EEC_PMSM.comp_BEMF_harmonics import comp_BEMF_harmonics +except ImportError as error: + comp_BEMF_harmonics = error + from ._check import InitUnKnowClassError from .IndMag import IndMag @@ -91,6 +96,18 @@ class EEC_PMSM(EEC): ) else: comp_joule_losses = comp_joule_losses + # cf Methods.Simulation.EEC_PMSM.comp_BEMF_harmonics + if isinstance(comp_BEMF_harmonics, ImportError): + comp_BEMF_harmonics = property( + fget=lambda x: raise_( + ImportError( + "Can't use EEC_PMSM method comp_BEMF_harmonics: " + + str(comp_BEMF_harmonics) + ) + ) + ) + else: + comp_BEMF_harmonics = comp_BEMF_harmonics # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/OutElec.py b/pyleecan/Classes/OutElec.py index 581112cea..9b75befd8 100644 --- a/pyleecan/Classes/OutElec.py +++ b/pyleecan/Classes/OutElec.py @@ -37,6 +37,11 @@ except ImportError as error: comp_I_mag = error +try: + from ..Methods.Output.OutElec.store import store +except ImportError as error: + store = error + from numpy import array, array_equal from ._check import InitUnKnowClassError @@ -85,6 +90,15 @@ class OutElec(FrozenClass): ) else: comp_I_mag = comp_I_mag + # cf Methods.Output.OutElec.store + if isinstance(store, ImportError): + store = property( + fget=lambda x: raise_( + ImportError("Can't use OutElec method store: " + str(store)) + ) + ) + else: + store = store # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Generator/ClassesRef/Output/OutElec.csv b/pyleecan/Generator/ClassesRef/Output/OutElec.csv index f9ebb6896..a18db6666 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutElec.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutElec.csv @@ -3,7 +3,7 @@ axes_dict,,Dict containing axes data used for Electrical,,{SciDataTool.Classes.D Is,A,Stator currents DataTime object,"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Is,,, Ir,A,Rotor currents as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Us,,, angle_rotor,rad,Rotor angular position as a function of time (if None computed according to Nr),Nt_tot,ndarray,None,,,,,,comp_I_mag,,, -N0,rpm,Rotor speed,1,float,None,,,,,,,,, +N0,rpm,Rotor speed,1,float,None,,,,,,store,,, angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,,,, logger_name,-,Name of the logger to use,0,str,Pyleecan.Electrical,,,,,,,,, Tem_av_ref,N.m,Theorical Average Electromagnetic torque,,float,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/EEC_PMSM.csv b/pyleecan/Generator/ClassesRef/Simulation/EEC_PMSM.csv index 8d1a21102..40fb7d696 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/EEC_PMSM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/EEC_PMSM.csv @@ -3,4 +3,4 @@ indmag,-,Magnetic inductance,,IndMag,None,,,,Simulation,EEC,comp_parameters,VERS fluxlink,-,Flux Linkage,,FluxLink,None,,,,,,solve_EEC,,,, parameters,-,"Parameters of the EEC: computed if empty, or enforced",,dict,{},,,,,,gen_drive,,,, freq0,Hz,Frequency,,float,None,,,,,,comp_joule_losses,,,, -drive,-,Drive,,Drive,None,,,,,,,,,, +drive,-,Drive,,Drive,None,,,,,,comp_BEMF_harmonics,,,, diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Ldqh_from_Phidqh.py b/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Ldqh_from_Phidqh.py index c3509c277..bc721911f 100644 --- a/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Ldqh_from_Phidqh.py +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Ldqh_from_Phidqh.py @@ -1,3 +1,3 @@ def comp_Ldqh_from_Phidqh(self): pass - # TODO \ No newline at end of file + # TODO diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Phidqh_from_Phiwind.py b/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Phidqh_from_Phiwind.py index 5f57381d7..8b871bdb7 100644 --- a/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Phidqh_from_Phiwind.py +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Phidqh_from_Phiwind.py @@ -1,3 +1,3 @@ def comp_Phidqh_from_Phiwind(self): pass - # TODO \ No newline at end of file + # TODO diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Ld.py b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Ld.py index e7a4c4d0c..062c2b9eb 100644 --- a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Ld.py +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Ld.py @@ -1,3 +1,3 @@ def get_Ld(self): pass - # TODO \ No newline at end of file + # TODO diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmd.py b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmd.py index 85e5c5044..d46680a90 100644 --- a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmd.py +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmd.py @@ -1,3 +1,3 @@ def get_Lmd(self): pass - # TODO \ No newline at end of file + # TODO diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmq.py b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmq.py index 3edea786d..982a723e9 100644 --- a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmq.py +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmq.py @@ -1,3 +1,3 @@ def get_Lmq(self): pass - # TODO \ No newline at end of file + # TODO diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lq.py b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lq.py index 8be1d23f1..edd1b173c 100644 --- a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lq.py +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lq.py @@ -1,3 +1,3 @@ def get_Lq(self): pass - # TODO \ No newline at end of file + # TODO diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/import_from_data.py b/pyleecan/Methods/Simulation/ELUT_PMSM/import_from_data.py index 0cb591cd3..c8001d4e0 100644 --- a/pyleecan/Methods/Simulation/ELUT_PMSM/import_from_data.py +++ b/pyleecan/Methods/Simulation/ELUT_PMSM/import_from_data.py @@ -1,3 +1,3 @@ def import_from_data(self): pass - # TODO \ No newline at end of file + # TODO diff --git a/pyleecan/Methods/Simulation/ELUT_SCIM/comp_Lm_from_Phim.py b/pyleecan/Methods/Simulation/ELUT_SCIM/comp_Lm_from_Phim.py index 92a1f0320..cf71fba0a 100644 --- a/pyleecan/Methods/Simulation/ELUT_SCIM/comp_Lm_from_Phim.py +++ b/pyleecan/Methods/Simulation/ELUT_SCIM/comp_Lm_from_Phim.py @@ -1,3 +1,3 @@ def comp_Lm_from_Phim(self): pass - # TODO \ No newline at end of file + # TODO diff --git a/pyleecan/Methods/Simulation/ELUT_SCIM/get_Lm.py b/pyleecan/Methods/Simulation/ELUT_SCIM/get_Lm.py index 6a82a556c..bcbb88bbe 100644 --- a/pyleecan/Methods/Simulation/ELUT_SCIM/get_Lm.py +++ b/pyleecan/Methods/Simulation/ELUT_SCIM/get_Lm.py @@ -1,3 +1,3 @@ def get_Lm(self): pass - # TODO \ No newline at end of file + # TODO diff --git a/pyleecan/Methods/Simulation/ELUT_SCIM/import_from_data.py b/pyleecan/Methods/Simulation/ELUT_SCIM/import_from_data.py index 0cb591cd3..c8001d4e0 100644 --- a/pyleecan/Methods/Simulation/ELUT_SCIM/import_from_data.py +++ b/pyleecan/Methods/Simulation/ELUT_SCIM/import_from_data.py @@ -1,3 +1,3 @@ def import_from_data(self): pass - # TODO \ No newline at end of file + # TODO From 3e795d008460a31f49b4ab4d3f460b7b9aba7f27 Mon Sep 17 00:00:00 2001 From: BenjaminGabet Date: Thu, 2 Sep 2021 11:00:11 +0200 Subject: [PATCH 030/167] Updating csv --- pyleecan/Classes/Class_Dict.json | 1 - pyleecan/Classes/LamSlot.py | 17 ---------------- .../Generator/ClassesRef/Machine/LamSlot.csv | 1 - .../LamSlot/comp_periodicity_spatial.py | 20 ------------------- 4 files changed, 39 deletions(-) delete mode 100644 pyleecan/Methods/Machine/LamSlot/comp_periodicity_spatial.py diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 58b726eba..e909ef78d 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4580,7 +4580,6 @@ "comp_height_yoke", "get_Zs", "comp_radius_mid_yoke", - "comp_periodicity", "get_bore_desc", "set_pole_pair_number" ], diff --git a/pyleecan/Classes/LamSlot.py b/pyleecan/Classes/LamSlot.py index 5b3b27e16..bc348fa9e 100644 --- a/pyleecan/Classes/LamSlot.py +++ b/pyleecan/Classes/LamSlot.py @@ -57,11 +57,6 @@ except ImportError as error: comp_radius_mid_yoke = error -try: - from ..Methods.Machine.LamSlot.comp_periodicity import comp_periodicity -except ImportError as error: - comp_periodicity = error - try: from ..Methods.Machine.LamSlot.get_bore_desc import get_bore_desc except ImportError as error: @@ -172,18 +167,6 @@ class LamSlot(Lamination): ) else: comp_radius_mid_yoke = comp_radius_mid_yoke - # cf Methods.Machine.LamSlot.comp_periodicity - if isinstance(comp_periodicity, ImportError): - comp_periodicity = property( - fget=lambda x: raise_( - ImportError( - "Can't use LamSlot method comp_periodicity: " - + str(comp_periodicity) - ) - ) - ) - else: - comp_periodicity = comp_periodicity # cf Methods.Machine.LamSlot.get_bore_desc if isinstance(get_bore_desc, ImportError): get_bore_desc = property( diff --git a/pyleecan/Generator/ClassesRef/Machine/LamSlot.csv b/pyleecan/Generator/ClassesRef/Machine/LamSlot.csv index e337391df..eae23dd84 100644 --- a/pyleecan/Generator/ClassesRef/Machine/LamSlot.csv +++ b/pyleecan/Generator/ClassesRef/Machine/LamSlot.csv @@ -7,6 +7,5 @@ slot,,lamination Slot,,Slot,,,,,Machine,Lamination,check,VERSION,1,Lamination wi ,,,,,,,,,,,comp_height_yoke,,, ,,,,,,,,,,,get_Zs,,, ,,,,,,,,,,,comp_radius_mid_yoke,,, -,,,,,,,,,,,comp_periodicity,,, ,,,,,,,,,,,get_bore_desc,,, ,,,,,,,,,,,set_pole_pair_number,,, diff --git a/pyleecan/Methods/Machine/LamSlot/comp_periodicity_spatial.py b/pyleecan/Methods/Machine/LamSlot/comp_periodicity_spatial.py deleted file mode 100644 index be778679b..000000000 --- a/pyleecan/Methods/Machine/LamSlot/comp_periodicity_spatial.py +++ /dev/null @@ -1,20 +0,0 @@ -def comp_periodicity_spatial(self): - """Compute the periodicity factor of the lamination - - Parameters - ---------- - self : LamSlot - A LamSlot object - - Returns - ------- - per_a : int - Number of spatial periodicities of the lamination over 2*pi - is_antiper_a : bool - True if an spatial anti-periodicity is possible after the periodicities - - """ - - Zs = self.get_Zs() - - return Zs, bool(Zs % 2 == 0) From 7f1fd9bffbddb0f491fc10a72838193d63a29b8e Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Thu, 2 Sep 2021 15:51:45 +0200 Subject: [PATCH 031/167] [BC] Correct bug in comp_connection_mat if Nt_coil is None, dont pass it to swat_em [CO] Request smallest period to get_Is [CC] Edit description of axes_dict in csv --- pyleecan/Classes/Class_Dict.json | 10 ++++---- pyleecan/Classes/EEC_SCIM.py | 2 +- pyleecan/Classes/Force.py | 4 ++-- pyleecan/Classes/Magnetics.py | 4 ++-- pyleecan/Data/Machine/SPMSM_002.json | 2 +- .../ClassesRef/Simulation/EEC_SCIM.csv | 2 +- .../Generator/ClassesRef/Simulation/Force.csv | 4 ++-- .../ClassesRef/Simulation/Magnetics.csv | 4 ++-- .../Machine/Winding/comp_connection_mat.py | 24 +++++++------------ pyleecan/Methods/Output/OutElec/get_Is.py | 2 +- 10 files changed, 26 insertions(+), 32 deletions(-) diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index e909ef78d..f842b263c 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -1187,7 +1187,7 @@ "value": {} }, { - "desc": "True to compute only on one angle periodicity (use periodicities defined in output.mag.axes_dict[angle])", + "desc": "True to compute only on one angle periodicity (use periodicities defined in axes_dict[angle])", "max": "", "min": "", "name": "is_periodicity_a", @@ -1899,7 +1899,7 @@ "path": "pyleecan/Generator/ClassesRef/Simulation/Force.csv", "properties": [ { - "desc": "True to compute only on one time periodicity (use periodicities defined in output.force.axes_dict[time]). If None, automatically calculated based on Magnetics periodicities.", + "desc": "True to compute only on one time periodicity (use periodicities defined in axes_dict[time]). If None, automatically calculated based on Magnetics periodicities.", "max": "", "min": "", "name": "is_periodicity_t", @@ -1908,7 +1908,7 @@ "value": null }, { - "desc": "True to compute only on one angle periodicity (use periodicities defined in output.force.axes_dict[angle]). If None, automatically calculated based on Magnetics periodicities.", + "desc": "True to compute only on one angle periodicity (use periodicities defined in axes_dict[angle]). If None, automatically calculated based on Magnetics periodicities.", "max": "", "min": "", "name": "is_periodicity_a", @@ -6215,7 +6215,7 @@ "value": 0 }, { - "desc": "True to compute only on one time periodicity (use periodicities defined in output.mag.axes_dict[time])", + "desc": "True to compute only on one time periodicity (use periodicities defined in axes_dict[time])", "max": "", "min": "", "name": "is_periodicity_t", @@ -6224,7 +6224,7 @@ "value": 0 }, { - "desc": "True to compute only on one angle periodicity (use periodicities defined in output.mag.axes_dict[angle])", + "desc": "True to compute only on one angle periodicity (use periodicities defined in axes_dict[angle])", "max": "", "min": "", "name": "is_periodicity_a", diff --git a/pyleecan/Classes/EEC_SCIM.py b/pyleecan/Classes/EEC_SCIM.py index d6bce86ea..a890c5d66 100644 --- a/pyleecan/Classes/EEC_SCIM.py +++ b/pyleecan/Classes/EEC_SCIM.py @@ -333,7 +333,7 @@ def _set_is_periodicity_a(self, value): is_periodicity_a = property( fget=_get_is_periodicity_a, fset=_set_is_periodicity_a, - doc=u"""True to compute only on one angle periodicity (use periodicities defined in output.mag.axes_dict[angle]) + doc=u"""True to compute only on one angle periodicity (use periodicities defined in axes_dict[angle]) :Type: bool """, diff --git a/pyleecan/Classes/Force.py b/pyleecan/Classes/Force.py index 5b94b79f6..b5e0c2e68 100644 --- a/pyleecan/Classes/Force.py +++ b/pyleecan/Classes/Force.py @@ -243,7 +243,7 @@ def _set_is_periodicity_t(self, value): is_periodicity_t = property( fget=_get_is_periodicity_t, fset=_set_is_periodicity_t, - doc=u"""True to compute only on one time periodicity (use periodicities defined in output.force.axes_dict[time]). If None, automatically calculated based on Magnetics periodicities. + doc=u"""True to compute only on one time periodicity (use periodicities defined in axes_dict[time]). If None, automatically calculated based on Magnetics periodicities. :Type: bool """, @@ -261,7 +261,7 @@ def _set_is_periodicity_a(self, value): is_periodicity_a = property( fget=_get_is_periodicity_a, fset=_set_is_periodicity_a, - doc=u"""True to compute only on one angle periodicity (use periodicities defined in output.force.axes_dict[angle]). If None, automatically calculated based on Magnetics periodicities. + doc=u"""True to compute only on one angle periodicity (use periodicities defined in axes_dict[angle]). If None, automatically calculated based on Magnetics periodicities. :Type: bool """, diff --git a/pyleecan/Classes/Magnetics.py b/pyleecan/Classes/Magnetics.py index 9117ca5b9..ef6a93047 100644 --- a/pyleecan/Classes/Magnetics.py +++ b/pyleecan/Classes/Magnetics.py @@ -426,7 +426,7 @@ def _set_is_periodicity_t(self, value): is_periodicity_t = property( fget=_get_is_periodicity_t, fset=_set_is_periodicity_t, - doc=u"""True to compute only on one time periodicity (use periodicities defined in output.mag.axes_dict[time]) + doc=u"""True to compute only on one time periodicity (use periodicities defined in axes_dict[time]) :Type: bool """, @@ -444,7 +444,7 @@ def _set_is_periodicity_a(self, value): is_periodicity_a = property( fget=_get_is_periodicity_a, fset=_set_is_periodicity_a, - doc=u"""True to compute only on one angle periodicity (use periodicities defined in output.mag.axes_dict[angle]) + doc=u"""True to compute only on one angle periodicity (use periodicities defined in axes_dict[angle]) :Type: bool """, diff --git a/pyleecan/Data/Machine/SPMSM_002.json b/pyleecan/Data/Machine/SPMSM_002.json index 36b116d5b..1109fedaa 100644 --- a/pyleecan/Data/Machine/SPMSM_002.json +++ b/pyleecan/Data/Machine/SPMSM_002.json @@ -601,7 +601,7 @@ "Lewout": 0, "Nlayer": 1, "Npcp": 1, - "Nslot_shift_wind": 0, + "Nslot_shift_wind": 1, "Ntcoil": 1, "__class__": "Winding", "coil_pitch": 0, diff --git a/pyleecan/Generator/ClassesRef/Simulation/EEC_SCIM.csv b/pyleecan/Generator/ClassesRef/Simulation/EEC_SCIM.csv index f92cc79cb..3d0f1fda2 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/EEC_SCIM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/EEC_SCIM.csv @@ -1,7 +1,7 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille I,-,RMS current for parameter estimation,,float,1,,,,Simulation,EEC,comp_parameters,VERSION,1,Electric module: Electrical Equivalent Circuit for Squirrel Cage Induction Machine, parameters,-,"Parameters of the EEC: computed if empty, or enforced",,dict,{},,,,,,solve_EEC,,,, -is_periodicity_a,,True to compute only on one angle periodicity (use periodicities defined in output.mag.axes_dict[angle]),,bool,1,,,,,,gen_drive,,,, +is_periodicity_a,,True to compute only on one angle periodicity (use periodicities defined in axes_dict[angle]),,bool,1,,,,,,gen_drive,,,, nb_worker,,To run FEMM in parallel (the parallelization is on the time loop),,int,None,,,,,,comp_joule_losses,,,, N0,rpm,Rotor speed,1,float,None,,,,,,,,, felec,Hz,electrical frequency,1,float,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/Force.csv b/pyleecan/Generator/ClassesRef/Simulation/Force.csv index a63299c2d..98f4193aa 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/Force.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/Force.csv @@ -1,6 +1,6 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe file -is_periodicity_t,-,"True to compute only on one time periodicity (use periodicities defined in output.force.axes_dict[time]). If None, automatically calculated based on Magnetics periodicities.",0,bool,None,,,,Simulation,,run,VERSION,1,Forces module abstract object,ForceMT -is_periodicity_a,-,"True to compute only on one angle periodicity (use periodicities defined in output.force.axes_dict[angle]). If None, automatically calculated based on Magnetics periodicities.",0,bool,None,,,,,,comp_axes,,,, +is_periodicity_t,-,"True to compute only on one time periodicity (use periodicities defined in axes_dict[time]). If None, automatically calculated based on Magnetics periodicities.",0,bool,None,,,,Simulation,,run,VERSION,1,Forces module abstract object,ForceMT +is_periodicity_a,-,"True to compute only on one angle periodicity (use periodicities defined in axes_dict[angle]). If None, automatically calculated based on Magnetics periodicities.",0,bool,None,,,,,,comp_axes,,,, is_agsf_transfer,-,True to compute the AGSF transfer from air-gap to stator bore radius.,0,bool,0,,,,,,comp_AGSF_transfer,,,, max_wavenumber_transfer,-,Maximum value to apply agsf transfer (to be used with FEA to avoid numerical noise amplification),0,int,None,,,,,,,,,, Rsbo_enforced_transfer,-,To enforce the value of the radius for AGSF transfer,0,float,None,,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/Magnetics.csv b/pyleecan/Generator/ClassesRef/Simulation/Magnetics.csv index 45680b206..69e6e0e1c 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/Magnetics.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/Magnetics.csv @@ -6,8 +6,8 @@ is_mmfs,-,1 to compute the stator magnetomotive force / stator armature magnetic is_mmfr,-,1 to compute the rotor magnetomotive force / rotor magnetic field,0,bool,1,,,,,,,,,, type_BH_stator,-,"0 to use the B(H) curve, 1 to use linear B(H) curve according to mur_lin, 2 to enforce infinite permeability (mur_lin =100000)",0,int,0,0,2,,,,,,,, type_BH_rotor,-,"0 to use the B(H) curve, 1 to use linear B(H) curve according to mur_lin, 2 to enforce infinite permeability (mur_lin =100000)",0,int,0,0,2,,,,,,,, -is_periodicity_t,-,True to compute only on one time periodicity (use periodicities defined in output.mag.axes_dict[time]),0,bool,0,,,,,,,,,, -is_periodicity_a,-,True to compute only on one angle periodicity (use periodicities defined in output.mag.axes_dict[angle]),0,bool,0,,,,,,,,,, +is_periodicity_t,-,True to compute only on one time periodicity (use periodicities defined in axes_dict[time]),0,bool,0,,,,,,,,,, +is_periodicity_a,-,True to compute only on one angle periodicity (use periodicities defined in axes_dict[angle]),0,bool,0,,,,,,,,,, angle_stator_shift,rad,Shift angle to appy to the stator in magnetic model,0,float,0,,,,,,,,,, angle_rotor_shift,rad,Shift angle to appy to the rotor in magnetic model,0,float,0,,,,,,,,,, logger_name,-,Name of the logger to use,0,str,Pyleecan.Magnetics,,,,,,,,,, diff --git a/pyleecan/Methods/Machine/Winding/comp_connection_mat.py b/pyleecan/Methods/Machine/Winding/comp_connection_mat.py index 5a912fc9b..af7175567 100644 --- a/pyleecan/Methods/Machine/Winding/comp_connection_mat.py +++ b/pyleecan/Methods/Machine/Winding/comp_connection_mat.py @@ -1,10 +1,7 @@ -# -*- coding: utf-8 -*- - from numpy import zeros, swapaxes, sign from ....Methods.Machine.Winding import WindingError - from swat_em import datamodel @@ -33,23 +30,19 @@ def comp_connection_mat(self, Zs=None, p=None): """ if Zs is None: - if self.parent is None: - raise WindingError( - "ERROR: The Winding object must be in a Lamination object." - ) + if not hasattr(self.parent, "slot") and not hasattr(self.parent, "slot_list"): + raise WindingError("The Winding object must be in a Lamination object.") if not hasattr(self.parent, "slot") and not hasattr(self.parent, "slot_list"): raise WindingError( - "ERROR: The Winding object must be in a Lamination object with Slot." + "The Winding object must be in a Lamination object with Slot." ) Zs = self.parent.get_Zs() if p is None: if self.parent is None: - raise WindingError( - "ERROR: The Winding object must be in a Lamination object." - ) + raise WindingError("The Winding object must be in a Lamination object.") p = self.parent.get_pole_pair_number() @@ -65,13 +58,14 @@ def comp_connection_mat(self, Zs=None, p=None): Nlayer = self.Nlayer # number of layers - coil_pitch = self.coil_pitch # coil pitch (coil span) - # generate a datamodel for the winding wdg = datamodel() - # generate winding from inputs - wdg.genwdg(Q=Zs, P=2 * p, m=qs, layers=Nlayer, turns=Ntcoil, w=coil_pitch) + # generate winding from inputs depending on coil pitch (or coil span) + if self.coil_pitch in [0, None]: + wdg.genwdg(Q=Zs, P=2 * p, m=qs, layers=Nlayer, turns=Ntcoil, w=5) + else: + wdg.genwdg(Q=Zs, P=2 * p, m=qs, layers=Nlayer, turns=Ntcoil, w=self.coil_pitch) # init connexion matrix wind_mat = zeros((Nlayer, 1, Zs, qs)) diff --git a/pyleecan/Methods/Output/OutElec/get_Is.py b/pyleecan/Methods/Output/OutElec/get_Is.py index cdb280438..75f3c538a 100644 --- a/pyleecan/Methods/Output/OutElec/get_Is.py +++ b/pyleecan/Methods/Output/OutElec/get_Is.py @@ -19,7 +19,7 @@ def get_Is(self): if self.Is is None: # Generate current according to Id/Iq Isdq = array([self.Id_ref, self.Iq_ref]) - time = self.axes_dict["time"].get_values(is_oneperiod=True) + time = self.axes_dict["time"].get_values(is_smallestperiod=True) qs = self.parent.simu.machine.stator.winding.qs felec = self.felec From e72b6c3e36a8694690d0aa710d35bd35fe5858b5 Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 30 Sep 2021 16:41:26 +0200 Subject: [PATCH 032/167] [BC] fixing bugs due to merge --- .../Methods/Simulation/test_InCurrent_meth.py | 2 +- Tests/Plot/test_PostPlot.py | 2 +- Tests/Validation/Electrical/test_EEC_LSRPM.py | 2 +- Tests/Validation/Electrical/test_EEC_PMSM.py | 2 +- .../Magnetics/test_FEMM_parallelization.py | 2 +- .../Magnetics/test_FEMM_periodicity.py | 6 ++-- pyleecan/Classes/Class_Dict.json | 8 ++--- pyleecan/Classes/Conductor.py | 33 ++++++++++++++++++ pyleecan/Classes/LamSlot.py | 34 ------------------- pyleecan/Classes/Lamination.py | 3 +- .../ClassesRef/Machine/Conductor.csv | 2 ++ .../Generator/ClassesRef/Machine/LamSlot.csv | 2 -- .../ClassesRef/Machine/Lamination.csv | 2 +- .../Machine/LamSlotMultiWind/plot_mmf_unit.py | 2 +- .../Machine/LamSlotWind/plot_mmf_unit.py | 2 +- .../Simulation/InputCurrent/gen_input.py | 18 +++++----- 16 files changed, 60 insertions(+), 62 deletions(-) diff --git a/Tests/Methods/Simulation/test_InCurrent_meth.py b/Tests/Methods/Simulation/test_InCurrent_meth.py index e61b50c67..6d1ffb0c3 100644 --- a/Tests/Methods/Simulation/test_InCurrent_meth.py +++ b/Tests/Methods/Simulation/test_InCurrent_meth.py @@ -286,7 +286,7 @@ def test_InputCurrent_DQ(self, test_dict): # Plot 3-phase current function of time # out.plot_2D_Data("elec.Is", "time", "phase", is_show_fig=False) - out.elec.get_Is().plot_2D_Data("time", "phase", is_show_fig=False, **dict_2D) + out.elec.get_Is().plot_2D_Data("time", "phase[]", is_show_fig=False, **dict_2D) # Save picture title = "Id=" + str(test_dict["Id"]) + " Iq=" + str(test_dict["Iq"]) diff --git a/Tests/Plot/test_PostPlot.py b/Tests/Plot/test_PostPlot.py index 037605b13..6382af6ca 100644 --- a/Tests/Plot/test_PostPlot.py +++ b/Tests/Plot/test_PostPlot.py @@ -135,7 +135,7 @@ def test_PostPlot(): plot_Is = PostPlot( method="plot_2D_Data", quantity="elec.get_Is", - param_list=["time", "phase"], + param_list=["time", "phase[]"], param_dict=dict( { "is_show_fig": False, diff --git a/Tests/Validation/Electrical/test_EEC_LSRPM.py b/Tests/Validation/Electrical/test_EEC_LSRPM.py index 0efb14a04..175af41e4 100644 --- a/Tests/Validation/Electrical/test_EEC_LSRPM.py +++ b/Tests/Validation/Electrical/test_EEC_LSRPM.py @@ -68,7 +68,7 @@ def test_EEC_PMSM(): # Plot 3-phase current function of time out.elec.get_Is().plot_2D_Data( "time", - "phase", + "phase[]", save_path=join(save_path, "EEC_FEMM_IPMSM_currents.png"), is_show_fig=False, **dict_2D diff --git a/Tests/Validation/Electrical/test_EEC_PMSM.py b/Tests/Validation/Electrical/test_EEC_PMSM.py index a08ebbc28..a999230a1 100644 --- a/Tests/Validation/Electrical/test_EEC_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_PMSM.py @@ -70,7 +70,7 @@ def test_EEC_PMSM(): # Plot 3-phase current function of time out.elec.get_Is().plot_2D_Data( "time", - "phase", + "phase[]", save_path=join(save_path, "EEC_FEMM_IPMSM_currents.png"), is_show_fig=False, **dict_2D diff --git a/Tests/Validation/Magnetics/test_FEMM_parallelization.py b/Tests/Validation/Magnetics/test_FEMM_parallelization.py index 8ccd6ee1c..c17efd882 100644 --- a/Tests/Validation/Magnetics/test_FEMM_parallelization.py +++ b/Tests/Validation/Magnetics/test_FEMM_parallelization.py @@ -99,7 +99,7 @@ def test_FEMM_parallelization_mag(): out.mag.Phi_wind_stator.plot_2D_Data( "time", - "phase", + "phase[]", data_list=[out2.mag.Phi_wind_stator], legend_list=["Periodic", "Full"], save_path=join(save_path, simu.name + "_Phi_wind_stator.png"), diff --git a/Tests/Validation/Magnetics/test_FEMM_periodicity.py b/Tests/Validation/Magnetics/test_FEMM_periodicity.py index f2401661a..47e294227 100644 --- a/Tests/Validation/Magnetics/test_FEMM_periodicity.py +++ b/Tests/Validation/Magnetics/test_FEMM_periodicity.py @@ -129,7 +129,7 @@ def test_FEMM_periodicity_time_no_periodicity_a(): out.mag.Phi_wind_stator.plot_2D_Data( "time", - "phase", + "phase[]", data_list=[out2.mag.Phi_wind_stator], legend_list=["Periodic", "Full"], save_path=join(save_path, simu.name + "_Phi_wind_stator_time.png"), @@ -299,7 +299,7 @@ def test_FEMM_periodicity_time(): out.mag.Phi_wind_stator.plot_2D_Data( "time", - "phase", + "phase[]", data_list=[out2.mag.Phi_wind_stator], legend_list=["Periodic", "Full"], save_path=join(save_path, simu.name + "_Phi_wind_stator_time.png"), @@ -478,7 +478,7 @@ def test_FEMM_periodicity_angle(): out.mag.Phi_wind_stator.plot_2D_Data( "time", - "phase", + "phase[]", data_list=[out2.mag.Phi_wind_stator], legend_list=["Periodic", "Full"], save_path=join(save_path, simu.name + "_Phi_wind_stator_time.png"), diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index a10247bef..4339146c5 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -763,7 +763,9 @@ "comp_psi_skin", "comp_phip_skin", "comp_psip_skin", - "comp_skin_effect" + "comp_skin_effect", + "comp_power", + "comp_skin_effect_round_wire" ], "mother": "", "name": "Conductor", @@ -4650,8 +4652,6 @@ "plot", "comp_height_yoke", "get_Zs", - "comp_radius_mid_yoke", - "comp_periodicity", "get_bore_desc", "set_pole_pair_number", "comp_angle_d_axis" @@ -5014,7 +5014,7 @@ "properties": [ { "desc": "Lamination stack active length [m] without radial ventilation airducts but including insulation layers between lamination sheets", - "max": "100", + "max": "", "min": "0", "name": "L1", "type": "float", diff --git a/pyleecan/Classes/Conductor.py b/pyleecan/Classes/Conductor.py index 1a2975d24..b863cf546 100644 --- a/pyleecan/Classes/Conductor.py +++ b/pyleecan/Classes/Conductor.py @@ -47,6 +47,18 @@ except ImportError as error: comp_skin_effect = error +try: + from ..Methods.Machine.Conductor.comp_power import comp_power +except ImportError as error: + comp_power = error + +try: + from ..Methods.Machine.Conductor.comp_skin_effect_round_wire import ( + comp_skin_effect_round_wire, + ) +except ImportError as error: + comp_skin_effect_round_wire = error + from ._check import InitUnKnowClassError from .Material import Material @@ -123,6 +135,27 @@ class Conductor(FrozenClass): ) else: comp_skin_effect = comp_skin_effect + # cf Methods.Machine.Conductor.comp_power + if isinstance(comp_power, ImportError): + comp_power = property( + fget=lambda x: raise_( + ImportError("Can't use Conductor method comp_power: " + str(comp_power)) + ) + ) + else: + comp_power = comp_power + # cf Methods.Machine.Conductor.comp_skin_effect_round_wire + if isinstance(comp_skin_effect_round_wire, ImportError): + comp_skin_effect_round_wire = property( + fget=lambda x: raise_( + ImportError( + "Can't use Conductor method comp_skin_effect_round_wire: " + + str(comp_skin_effect_round_wire) + ) + ) + ) + else: + comp_skin_effect_round_wire = comp_skin_effect_round_wire # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/LamSlot.py b/pyleecan/Classes/LamSlot.py index 48fe1730c..cfb52ae10 100644 --- a/pyleecan/Classes/LamSlot.py +++ b/pyleecan/Classes/LamSlot.py @@ -52,16 +52,6 @@ except ImportError as error: get_Zs = error -try: - from ..Methods.Machine.LamSlot.comp_radius_mid_yoke import comp_radius_mid_yoke -except ImportError as error: - comp_radius_mid_yoke = error - -try: - from ..Methods.Machine.LamSlot.comp_periodicity import comp_periodicity -except ImportError as error: - comp_periodicity = error - try: from ..Methods.Machine.LamSlot.get_bore_desc import get_bore_desc except ImportError as error: @@ -165,30 +155,6 @@ class LamSlot(Lamination): ) else: get_Zs = get_Zs - # cf Methods.Machine.LamSlot.comp_radius_mid_yoke - if isinstance(comp_radius_mid_yoke, ImportError): - comp_radius_mid_yoke = property( - fget=lambda x: raise_( - ImportError( - "Can't use LamSlot method comp_radius_mid_yoke: " - + str(comp_radius_mid_yoke) - ) - ) - ) - else: - comp_radius_mid_yoke = comp_radius_mid_yoke - # cf Methods.Machine.LamSlot.comp_periodicity - if isinstance(comp_periodicity, ImportError): - comp_periodicity = property( - fget=lambda x: raise_( - ImportError( - "Can't use LamSlot method comp_periodicity: " - + str(comp_periodicity) - ) - ) - ) - else: - comp_periodicity = comp_periodicity # cf Methods.Machine.LamSlot.get_bore_desc if isinstance(get_bore_desc, ImportError): get_bore_desc = property( diff --git a/pyleecan/Classes/Lamination.py b/pyleecan/Classes/Lamination.py index 02cd803a1..290e84fa4 100644 --- a/pyleecan/Classes/Lamination.py +++ b/pyleecan/Classes/Lamination.py @@ -805,7 +805,7 @@ def _get_L1(self): def _set_L1(self, value): """setter of L1""" - check_var("L1", value, "float", Vmin=0, Vmax=100) + check_var("L1", value, "float", Vmin=0) self._L1 = value L1 = property( @@ -815,7 +815,6 @@ def _set_L1(self, value): :Type: float :min: 0 - :max: 100 """, ) diff --git a/pyleecan/Generator/ClassesRef/Machine/Conductor.csv b/pyleecan/Generator/ClassesRef/Machine/Conductor.csv index aa7a43a86..731e9b703 100644 --- a/pyleecan/Generator/ClassesRef/Machine/Conductor.csv +++ b/pyleecan/Generator/ClassesRef/Machine/Conductor.csv @@ -5,3 +5,5 @@ ins_mat,-,Material of the insulation,0,Material,,,,,,,comp_phi_skin,,, ,,,,,,,,,,,comp_phip_skin,,, ,,,,,,,,,,,comp_psip_skin,,, ,,,,,,,,,,,comp_skin_effect,,, +,,,,,,,,,,,comp_power,,, +,,,,,,,,,,,comp_skin_effect_round_wire,,, diff --git a/pyleecan/Generator/ClassesRef/Machine/LamSlot.csv b/pyleecan/Generator/ClassesRef/Machine/LamSlot.csv index 72e2d60cb..b749fda4c 100644 --- a/pyleecan/Generator/ClassesRef/Machine/LamSlot.csv +++ b/pyleecan/Generator/ClassesRef/Machine/LamSlot.csv @@ -6,8 +6,6 @@ slot,,lamination Slot,,Slot,,,,,Machine,Lamination,check,VERSION,1,Lamination wi ,,,,,,,,,,,plot,,, ,,,,,,,,,,,comp_height_yoke,,, ,,,,,,,,,,,get_Zs,,, -,,,,,,,,,,,comp_radius_mid_yoke,,, -,,,,,,,,,,,comp_periodicity,,, ,,,,,,,,,,,get_bore_desc,,, ,,,,,,,,,,,set_pole_pair_number,,, ,,,,,,,,,,,comp_angle_d_axis,,, diff --git a/pyleecan/Generator/ClassesRef/Machine/Lamination.csv b/pyleecan/Generator/ClassesRef/Machine/Lamination.csv index 686390bc6..504219307 100644 --- a/pyleecan/Generator/ClassesRef/Machine/Lamination.csv +++ b/pyleecan/Generator/ClassesRef/Machine/Lamination.csv @@ -1,5 +1,5 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -L1,m,Lamination stack active length [m] without radial ventilation airducts but including insulation layers between lamination sheets,0,float,3.50E-01,0,100,,Machine,,build_geometry,VERSION,1,abstract class for lamination +L1,m,Lamination stack active length [m] without radial ventilation airducts but including insulation layers between lamination sheets,0,float,3.50E-01,0,,,Machine,,build_geometry,VERSION,1,abstract class for lamination mat_type,,Lamination's material,,Material,,,,,,,check,,, Nrvd,-,number of radial air ventilation ducts in lamination,0,int,0,0,,,,,comp_length,,, Wrvd,m,axial width of ventilation ducts in lamination,0,float,0,0,,,,,comp_masses,,, diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/plot_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotMultiWind/plot_mmf_unit.py index 8416315d7..584d83a47 100644 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/plot_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/plot_mmf_unit.py @@ -47,7 +47,7 @@ def plot_mmf_unit(self, r_max=100, fig=None, is_show_fig=True): WF.plot_2D_Data( "angle{°}", - "phase", + "phase[]", data_list=[MMF_U], fig=fig, ax=axs[0], diff --git a/pyleecan/Methods/Machine/LamSlotWind/plot_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotWind/plot_mmf_unit.py index 940c1a44d..58962ca8d 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/plot_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotWind/plot_mmf_unit.py @@ -42,7 +42,7 @@ def plot_mmf_unit(self, r_max=100, fig=None, is_show_fig=True): WF.plot_2D_Data( "angle{°}", - "phase", + "phase[]", data_list=[MMF_U], fig=fig, ax=axs[0], diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index d52c93fd1..571bd18c4 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -120,16 +120,16 @@ def gen_input(self): "ERROR: InputCurrent.angle_rotor and InputCurrent.N0 can't be None at the same time" ) if self.angle_rotor is not None: - output.angle_rotor = self.angle_rotor.get_data() + outelec.angle_rotor = self.angle_rotor.get_data() if ( - not isinstance(output.angle_rotor, ndarray) - or len(output.angle_rotor.shape) != 1 - or output.angle_rotor.size != self.Nt_tot + not isinstance(outelec.angle_rotor, ndarray) + or len(outelec.angle_rotor.shape) != 1 + or outelec.angle_rotor.size != self.Nt_tot ): # angle_rotor should be a vector of same length as time raise InputError( "ERROR: InputCurrent.angle_rotor should be a vector of the same length as time, " - + str(output.angle_rotor.shape) + + str(outelec.angle_rotor.shape) + " shape found, " + str(self.Nt_tot) + " expected" @@ -144,14 +144,14 @@ def gen_input(self): if self.angle_rotor_initial is None: # Enforce default initial position - output.angle_rotor_initial = 0 + outelec.angle_rotor_initial = 0 else: - output.angle_rotor_initial = self.angle_rotor_initial + outelec.angle_rotor_initial = self.angle_rotor_initial if self.Tem_av_ref is not None: - output.Tem_av_ref = self.Tem_av_ref + outelec.Tem_av_ref = self.Tem_av_ref if self.Pem_av_ref is not None: - output.Pem_av_ref = self.Pem_av_ref + outelec.Pem_av_ref = self.Pem_av_ref if simu.parent is None: raise InputError( "ERROR: The Simulation object must be in an Output object to run" From 934dfb6e54d9aea3e02fe6980510ac8fbc59c5b1 Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 30 Sep 2021 17:01:14 +0200 Subject: [PATCH 033/167] [WP] fixing bugs due to merge --- Tests/Validation/Electrical/test_EEC_LSRPM.py | 8 ++++---- .../Machine/LamSquirrelCage/comp_resistance_wind.py | 2 +- pyleecan/Methods/Simulation/EEC_LSRPM/comp_parameters.py | 2 +- pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Tests/Validation/Electrical/test_EEC_LSRPM.py b/Tests/Validation/Electrical/test_EEC_LSRPM.py index 175af41e4..a1a76fe36 100644 --- a/Tests/Validation/Electrical/test_EEC_LSRPM.py +++ b/Tests/Validation/Electrical/test_EEC_LSRPM.py @@ -10,7 +10,7 @@ from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.InputElec import InputElec from pyleecan.Classes.Electrical import Electrical -from pyleecan.Classes.EEC_PMSM import EEC_PMSM +from pyleecan.Classes.EEC_LSRPM import EEC_LSRPM from pyleecan.Classes.FluxLinkFEMM import FluxLinkFEMM from pyleecan.Classes.IndMagFEMM import IndMagFEMM from pyleecan.Classes.MagFEMM import MagFEMM @@ -22,14 +22,14 @@ @pytest.mark.long_5s @pytest.mark.MagFEMM -@pytest.mark.EEC_PMSM +@pytest.mark.EEC_LSRPM @pytest.mark.IPMSM @pytest.mark.periodicity @pytest.mark.SingleOP def test_EEC_PMSM(): """Validation of LSRPM EEC from Sijie's PhD thesis""" - LSRPM = load("LSRPM.json") + LSRPM = load("LSRPM_001.json") simu = Simu1(name="test_EEC_LSRPM", machine=LSRPM) # Definition of the input @@ -42,7 +42,7 @@ def test_EEC_PMSM(): # Definition of the electrical simulation (FEMM) simu.elec = Electrical() - simu.elec.eec = EEC_LPMSM( + simu.elec.eec = EEC_LSRPM( indmag=IndMagFEMM(is_periodicity_a=True, Nt_tot=10), fluxlink=FluxLinkFEMM(is_periodicity_a=True, Nt_tot=10), ) diff --git a/pyleecan/Methods/Machine/LamSquirrelCage/comp_resistance_wind.py b/pyleecan/Methods/Machine/LamSquirrelCage/comp_resistance_wind.py index ce4ba6c3a..35f548a20 100644 --- a/pyleecan/Methods/Machine/LamSquirrelCage/comp_resistance_wind.py +++ b/pyleecan/Methods/Machine/LamSquirrelCage/comp_resistance_wind.py @@ -24,7 +24,7 @@ def comp_resistance_wind(self, T=20, qs=3): rho20 = self.ring_mat.elec.rho rho = rho20 * (1 + alpha * (T - 20)) - Sring = self.comp_surfacef_ring() + Sring = self.comp_surface_ring() lring = self.comp_length_ring() Zr = self.get_Zs() P = self.get_pole_pair_number() diff --git a/pyleecan/Methods/Simulation/EEC_LSRPM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_LSRPM/comp_parameters.py index 00c0baf3c..aa397d541 100644 --- a/pyleecan/Methods/Simulation/EEC_LSRPM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_LSRPM/comp_parameters.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -def comp_parameters(self, output): +def comp_parameters(self, output, Tsta=None, Trot=None): """Compute the parameters dict for the equivalent electrical circuit: resistance, inductance and back electromotive force Parameters diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py index b2be72a79..e31fe4a03 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -def comp_parameters(self, output): +def comp_parameters(self, output, Tsta=None, Trot=None): """Compute the parameters dict for the equivalent electrical circuit: resistance, inductance and back electromotive force Parameters From 408a001984948fc6d4dc8d35562b999ed46f5175 Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 30 Sep 2021 17:19:07 +0200 Subject: [PATCH 034/167] [WP] enhancing methods to compute axes --- pyleecan/Classes/Class_Dict.json | 6 +- pyleecan/Classes/Input.py | 14 +- pyleecan/Classes/InputFlux.py | 29 ---- .../Generator/ClassesRef/Simulation/Input.csv | 2 +- .../ClassesRef/Simulation/InputFlux.csv | 4 +- .../Methods/Simulation/Input/comp_axes.py | 49 +++++- .../Methods/Simulation/Input/gen_input.py | 15 +- pyleecan/Methods/Simulation/Input/set_disc.py | 40 +++++ .../{Input => InputCurrent}/comp_felec.py | 6 +- .../Simulation/InputCurrent/gen_input.py | 56 +----- .../Methods/Simulation/InputElec/gen_input.py | 3 +- .../Methods/Simulation/InputFlux/comp_axes.py | 144 ---------------- .../Simulation/InputFlux/comp_felec.py | 37 ---- .../Methods/Simulation/InputFlux/gen_input.py | 160 ++++++++++++------ .../Simulation/InputForce/gen_input.py | 41 +++-- .../Simulation/InputVoltage/gen_input.py | 84 ++++++--- 16 files changed, 319 insertions(+), 371 deletions(-) create mode 100644 pyleecan/Methods/Simulation/Input/set_disc.py rename pyleecan/Methods/Simulation/{Input => InputCurrent}/comp_felec.py (89%) delete mode 100644 pyleecan/Methods/Simulation/InputFlux/comp_axes.py delete mode 100644 pyleecan/Methods/Simulation/InputFlux/comp_felec.py diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 4339146c5..5ece8370c 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4119,7 +4119,7 @@ "methods": [ "gen_input", "comp_axes", - "comp_felec" + "set_disc" ], "mother": "", "name": "Input", @@ -4327,9 +4327,7 @@ "desc": "Input to skip the magnetic module and start with the structural one", "is_internal": false, "methods": [ - "gen_input", - "comp_felec", - "comp_axes" + "gen_input" ], "mother": "Input", "name": "InputFlux", diff --git a/pyleecan/Classes/Input.py b/pyleecan/Classes/Input.py index 11e242989..5d8a5f93e 100644 --- a/pyleecan/Classes/Input.py +++ b/pyleecan/Classes/Input.py @@ -28,9 +28,9 @@ comp_axes = error try: - from ..Methods.Simulation.Input.comp_felec import comp_felec + from ..Methods.Simulation.Input.set_disc import set_disc except ImportError as error: - comp_felec = error + set_disc = error from ..Classes.ImportMatrixVal import ImportMatrixVal @@ -64,15 +64,15 @@ class Input(FrozenClass): ) else: comp_axes = comp_axes - # cf Methods.Simulation.Input.comp_felec - if isinstance(comp_felec, ImportError): - comp_felec = property( + # cf Methods.Simulation.Input.set_disc + if isinstance(set_disc, ImportError): + set_disc = property( fget=lambda x: raise_( - ImportError("Can't use Input method comp_felec: " + str(comp_felec)) + ImportError("Can't use Input method set_disc: " + str(set_disc)) ) ) else: - comp_felec = comp_felec + set_disc = set_disc # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/InputFlux.py b/pyleecan/Classes/InputFlux.py index 8f106dfdd..2a69d03d7 100644 --- a/pyleecan/Classes/InputFlux.py +++ b/pyleecan/Classes/InputFlux.py @@ -22,16 +22,6 @@ except ImportError as error: gen_input = error -try: - from ..Methods.Simulation.InputFlux.comp_felec import comp_felec -except ImportError as error: - comp_felec = error - -try: - from ..Methods.Simulation.InputFlux.comp_axes import comp_axes -except ImportError as error: - comp_axes = error - from ..Classes.ImportMatrixVal import ImportMatrixVal from numpy import ndarray @@ -46,7 +36,6 @@ class InputFlux(Input): VERSION = 1 - # Check ImportError to remove unnecessary dependencies in unused method # cf Methods.Simulation.InputFlux.gen_input if isinstance(gen_input, ImportError): gen_input = property( @@ -56,24 +45,6 @@ class InputFlux(Input): ) else: gen_input = gen_input - # cf Methods.Simulation.InputFlux.comp_felec - if isinstance(comp_felec, ImportError): - comp_felec = property( - fget=lambda x: raise_( - ImportError("Can't use InputFlux method comp_felec: " + str(comp_felec)) - ) - ) - else: - comp_felec = comp_felec - # cf Methods.Simulation.InputFlux.comp_axes - if isinstance(comp_axes, ImportError): - comp_axes = property( - fget=lambda x: raise_( - ImportError("Can't use InputFlux method comp_axes: " + str(comp_axes)) - ) - ) - else: - comp_axes = comp_axes # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Generator/ClassesRef/Simulation/Input.csv b/pyleecan/Generator/ClassesRef/Simulation/Input.csv index 6fb2e9d19..d5adbe56a 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/Input.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/Input.csv @@ -1,7 +1,7 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description time,s,Electrical time vector (no symmetry) to import,Nt_tot,ImportMatrix,None,,,,Simulation,,gen_input,VERSION,1,Starting data of the simulation angle,rad,Electrical position vector (no symmetry) to import,Na_tot,ImportMatrix,None,,,,,,comp_axes,,, -Nt_tot,-,Time discretization,0,int,2048,1,,,,,comp_felec,,, +Nt_tot,-,Time discretization,0,int,2048,1,,,,,set_disc,,, Nrev,-,Number of rotor revolution (to compute the final time),,float,None,0,,,,,,,, Na_tot,-,Angular discretization,,int,2048,1,,,,,,,, N0,rpm,Rotor speed,1,float,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/InputFlux.csv b/pyleecan/Generator/ClassesRef/Simulation/InputFlux.csv index 9a4366a92..ae9060b26 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/InputFlux.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/InputFlux.csv @@ -1,7 +1,7 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description per_a,-,Angle periodicity,,int,1,,,,Simulation,Input,gen_input,VERSION,1,Input to skip the magnetic module and start with the structural one -per_t,-,Time periodicity,,int,1,,,,,,comp_felec,,, -is_antiper_a,-,If angle is antiperiodic,,bool,False,,,,,,comp_axes,,, +per_t,-,Time periodicity,,int,1,,,,,,,,, +is_antiper_a,-,If angle is antiperiodic,,bool,False,,,,,,,,, is_antiper_t,-,If time is antiperiodic,,bool,False,,,,,,,,, B_dict,-,Dict of Import objects or lists for each component of the flux,,dict,None,,,,,,,,, unit,-,Unit of the flux if not T,,str,None,,,,,,,,, diff --git a/pyleecan/Methods/Simulation/Input/comp_axes.py b/pyleecan/Methods/Simulation/Input/comp_axes.py index f214ec4d4..7eab4705e 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axes.py +++ b/pyleecan/Methods/Simulation/Input/comp_axes.py @@ -3,7 +3,14 @@ from ....Methods.Simulation.Input import InputError -def comp_axes(self, machine): +def comp_axes( + self, + machine=None, + per_a=1, + is_antiper_a=False, + per_t=1, + is_antiper_t=False, +): """Compute simulation axes, i.e. space DataObject including (anti)-periodicity and time DataObject including (anti)-periodicity and accounting for rotating speed and number of revolutions @@ -14,13 +21,19 @@ def comp_axes(self, machine): an Input object machine : Machine a Machine object + per_a : int + angle periodicity + is_antiper_a : bool + if the angle axis is antiperiodic + per_t : int + time periodicity + is_antiper_t : bool + if the time axis is antiperiodic Returns ------- - Time : DataLinspace - Time axis including (anti)-periodicity and accounting for rotating speed and number of revolutions - Angle : DataLinspace - Angle axis including (anti)-periodicity + axes_dict: {Data} + dict of axes containing time and angle axes (with or without (anti-)periodicity) """ @@ -29,6 +42,15 @@ def comp_axes(self, machine): if self.time is None and N0 is None: raise InputError("time and N0 can't be both None") + if machine is None: + # Fetch machine from input + if ( + self.parent is not None + and hasattr(self.parent, "machine") + and self.parent.machine is not None + ): + machine = self.parent.machine + # Get machine pole pair number p = machine.get_pole_pair_number() @@ -43,8 +65,6 @@ def comp_axes(self, machine): "elec_order": f_elec, "mech_order": f_elec / p, } - if N0 is not None: - norm_time["angle_rotor"] = 1 / (360 * N0 / 60) norm_angle = {"space_order": p, "distance": 1 / Rag} @@ -91,4 +111,17 @@ def comp_axes(self, machine): name="angle", unit="rad", values=angle, normalizations=norm_angle ) - return Time, Angle + # Add time (anti-)periodicity + if per_t > 1 or is_antiper_t: + Time = Time.get_axis_periodic(per_t, is_antiper_t) + + # Add angle (anti-)periodicity + if per_a > 1 or is_antiper_a: + Angle = Angle.get_axis_periodic(per_a, is_antiper_a) + + # Compute angle_rotor (added to time normalizations) + self.parent.parent.comp_angle_rotor(Time) + + axes_dict = {"time": Time, "angle": Angle} + + return axes_dict diff --git a/pyleecan/Methods/Simulation/Input/gen_input.py b/pyleecan/Methods/Simulation/Input/gen_input.py index 5fa002d1d..6959475c4 100644 --- a/pyleecan/Methods/Simulation/Input/gen_input.py +++ b/pyleecan/Methods/Simulation/Input/gen_input.py @@ -1,3 +1,5 @@ +from numpy import ndarray + from ....Classes.OutElec import OutElec from ....Methods.Simulation.Input import InputError @@ -10,10 +12,19 @@ def gen_input(self): self : Input An Input object """ - # Get the simulation and check output + + output = OutElec() + simu = self.parent + + # Replace N0=0 by 0.1 rpm + if self.N0 == 0: + self.N0 = 0.1 + self.get_logger().debug("Updating N0 from 0 [rpm] to 0.1 [rpm]") + self.comp_axes(machine=simu.machine, N0=self.N0) + + # Get the phase number for verifications if self.parent is None: raise InputError("Input object should be inside a Simulation object") - simu = self.parent if self.parent.parent is None: raise InputError( diff --git a/pyleecan/Methods/Simulation/Input/set_disc.py b/pyleecan/Methods/Simulation/Input/set_disc.py new file mode 100644 index 000000000..e50b746b0 --- /dev/null +++ b/pyleecan/Methods/Simulation/Input/set_disc.py @@ -0,0 +1,40 @@ +NT_MAX = 4096 +NA_MAX = 2048 + + +def set_disc(self, machine): + """Set the value of Nt_tot and Na_tot according to a machine + + Parameters + ---------- + self : Input + An Input object + machine : Machine + A machine object to adapt the discretization + """ + + pert, is_apert, _, _ = machine.comp_periodicity_time() # Stator + if is_apert: + pert = pert * 2 + + self.Nt_tot = int(round(NT_MAX / pert)) * pert + + pera, is_apera = machine.comp_periodicity_spatial() + + if is_apera: + pera = pera * 2 + + if hasattr(machine.stator, "get_Zs"): + # Na_tot/pera must be multiple of Zs for lumped force calculation in time space domain + Zs = machine.stator.get_Zs() + else: + Zs = 1 + + self.Na_tot = int(round(NA_MAX / Zs / pera) * Zs * pera) + + self.get_logger().debug( + "Setting automatic discretization: Nt_tot=" + + str(self.Nt_tot) + + ", Na_tot=" + + str(self.Na_tot) + ) diff --git a/pyleecan/Methods/Simulation/Input/comp_felec.py b/pyleecan/Methods/Simulation/InputCurrent/comp_felec.py similarity index 89% rename from pyleecan/Methods/Simulation/Input/comp_felec.py rename to pyleecan/Methods/Simulation/InputCurrent/comp_felec.py index ea5c5894b..85cc0bffa 100644 --- a/pyleecan/Methods/Simulation/Input/comp_felec.py +++ b/pyleecan/Methods/Simulation/InputCurrent/comp_felec.py @@ -6,8 +6,8 @@ def comp_felec(self): Parameters ---------- - felec : float - Electrical frequency [Hz] + self : InputCurrent + An InputCurrent object """ if hasattr(self, "felec") and self.felec is not None: @@ -25,6 +25,6 @@ def comp_felec(self): logger = self.get_logger() logger.warning("Input.comp_felec(): Machine was not found.") zp = 1 - return self.N0 * zp / 60 + return self.N0 * zp / (60 * (1 - self.slip_ref)) else: raise InputError("InputCurrent object can't have felec and N0 at None") diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index 571bd18c4..4bfaf8e65 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -6,6 +6,7 @@ from ....Functions.Electrical.coordinate_transformation import n2dq from ....Functions.Winding.gen_phase_list import gen_name +from ....Classes.InputVoltage import InputVoltage from SciDataTool import Data1D, DataTime @@ -28,14 +29,18 @@ def gen_input(self): raise InputError("InputCurrent object should be inside a Simulation object") # Call InputVoltage.gen_input() - super(type(self), self).gen_input() + InputVoltage.gen_input(self) # Get electrical output outelec = simu.parent.elec # Number of winding phases for stator/rotor - qs = len(simu.machine.stator.get_name_phase()) - qr = len(simu.machine.rotor.get_name_phase()) + if simu.machine is not None: + qs = len(simu.machine.stator.get_name_phase()) + qr = len(simu.machine.rotor.get_name_phase()) + else: + qs = 0 + qr = 0 # Get time axis Time = outelec.axes_dict["time"] @@ -71,7 +76,7 @@ def gen_input(self): outelec.Is = DataTime( name="Stator current", unit="A", - symbol="Is", + symbol="I_s", axes=[Phase, Time], values=transpose(Is), ) @@ -114,48 +119,5 @@ def gen_input(self): values=transpose(Ir), ) - # Load and check alpha_rotor and N0 - if self.angle_rotor is None and self.N0 is None: - raise InputError( - "ERROR: InputCurrent.angle_rotor and InputCurrent.N0 can't be None at the same time" - ) - if self.angle_rotor is not None: - outelec.angle_rotor = self.angle_rotor.get_data() - if ( - not isinstance(outelec.angle_rotor, ndarray) - or len(outelec.angle_rotor.shape) != 1 - or outelec.angle_rotor.size != self.Nt_tot - ): - # angle_rotor should be a vector of same length as time - raise InputError( - "ERROR: InputCurrent.angle_rotor should be a vector of the same length as time, " - + str(outelec.angle_rotor.shape) - + " shape found, " - + str(self.Nt_tot) - + " expected" - ) - - if self.rot_dir is None or self.rot_dir not in [-1, 1]: - # Enforce default rotation direction - # simu.parent.geo.rot_dir = None - pass # None is already the default value - else: - simu.parent.geo.rot_dir = self.rot_dir - - if self.angle_rotor_initial is None: - # Enforce default initial position - outelec.angle_rotor_initial = 0 - else: - outelec.angle_rotor_initial = self.angle_rotor_initial - - if self.Tem_av_ref is not None: - outelec.Tem_av_ref = self.Tem_av_ref - if self.Pem_av_ref is not None: - outelec.Pem_av_ref = self.Pem_av_ref - if simu.parent is None: - raise InputError( - "ERROR: The Simulation object must be in an Output object to run" - ) - # Save the Output in the correct place simu.parent.elec = outelec diff --git a/pyleecan/Methods/Simulation/InputElec/gen_input.py b/pyleecan/Methods/Simulation/InputElec/gen_input.py index d502edd4f..757e9cdf9 100644 --- a/pyleecan/Methods/Simulation/InputElec/gen_input.py +++ b/pyleecan/Methods/Simulation/InputElec/gen_input.py @@ -1,3 +1,5 @@ +from numpy import ndarray, pi, linspace + from ....Classes.OutElec import OutElec from ....Classes.Simulation import Simulation @@ -41,7 +43,6 @@ def gen_input(self): per=1, # int(2 * simu.machine.get_pole_pair_number()), is_aper=False, # True, is_include_per=False, # True, - is_remove_aper=True, # False, ) outelec.axes_dict = {"time": Time_elec} diff --git a/pyleecan/Methods/Simulation/InputFlux/comp_axes.py b/pyleecan/Methods/Simulation/InputFlux/comp_axes.py deleted file mode 100644 index 3e5687d33..000000000 --- a/pyleecan/Methods/Simulation/InputFlux/comp_axes.py +++ /dev/null @@ -1,144 +0,0 @@ -from numpy import pi -from SciDataTool import Data1D, DataLinspace -from ....Methods.Simulation.Input import InputError - - -def comp_axes( - self, - axes_values, - machine=None, - N0=None, - per_a=1, - is_antiper_a=False, - per_t=1, - is_antiper_t=False, -): - """Compute simulation axes, i.e. space DataObject including (anti)-periodicity - and time DataObject including (anti)-periodicity and accounting for rotating speed - and number of revolutions -> overrides Input comp_axes method - - Parameters - ---------- - self : InputFlux - an InputFlux object - axes_values : {ndarray} - dict of axe values - machine : Machine - a Machine object - N0 : float - rotating speed [rpm] - per_a : int - angle periodicity - is_antiper_a : bool - if the angle axis is antiperiodic - per_t : int - time periodicity - is_antiper_t : bool - if the time axis is antiperiodic - - - Returns - ------- - axes_dict : {Data} - dict of Data objects for each axis - - """ - - norm_time = {} - norm_angle = {} - - if machine is not None: - # Get machine pole pair number - p = machine.get_pole_pair_number() - - # Get electrical fundamental frequency - f_elec = self.comp_felec() - - # Airgap radius - Rag = machine.comp_Rgap_mec() - - # Setup normalizations for time and angle axes - norm_time["elec_order"] = f_elec - norm_time["mech_order"] = f_elec / p - if N0 is not None: - norm_time["angle_rotor"] = 1 / (360 * N0 / 60) - - norm_angle["space_order"] = p - norm_angle["distance"] = 1 / Rag - - sym_t = {} - if is_antiper_t: - sym_t["antiperiod"] = per_t - else: - sym_t["period"] = per_t - - if self.time is not None: - Time = Data1D( - name="time", - unit="s", - values=self.time.get_data(), - normalizations=norm_time, - symmetries=sym_t, - ) - elif "time" in axes_values: - Time = Data1D( - name="time", - unit="s", - values=axes_values["time"], - normalizations=norm_time, - symmetries=sym_t, - ) - elif N0 is None: - raise InputError("ERROR: time and N0 can't be both None") - else: - # Create time axis as a DataLinspace - Time = DataLinspace( - name="time", - unit="s", - initial=0, - final=60 / N0 * self.Nrev, - number=self.Nt_tot, - include_endpoint=False, - normalizations=norm_time, - symmetries=sym_t, - ) - - sym_a = {} - if is_antiper_a: - sym_a["antiperiod"] = per_a - else: - sym_a["period"] = per_a - - if self.angle is not None: - Angle = Data1D( - name="angle", - unit="rad", - values=self.angle.get_data(), - normalizations=norm_angle, - symmetries=sym_a, - ) - elif "angle" in axes_values: - Angle = Data1D( - name="angle", - unit="rad", - values=axes_values["angle"], - normalizations=norm_angle, - symmetries=sym_a, - ) - else: - # Create angle axis as a DataLinspace - Angle = DataLinspace( - name="angle", - unit="rad", - initial=0, - final=2 * pi, - number=self.Na_tot, - include_endpoint=False, - normalizations=norm_angle, - symmetries=sym_a, - ) - - # Store in axes_dict - axes_dict = {"time": Time, "angle": Angle} - - return axes_dict diff --git a/pyleecan/Methods/Simulation/InputFlux/comp_felec.py b/pyleecan/Methods/Simulation/InputFlux/comp_felec.py deleted file mode 100644 index 35744b84e..000000000 --- a/pyleecan/Methods/Simulation/InputFlux/comp_felec.py +++ /dev/null @@ -1,37 +0,0 @@ -from ....Classes.Simulation import Simulation -from ....Methods.Simulation.Input import InputError - - -def comp_felec(self): - """Compute the electrical frequency - - Parameters - ---------- - felec : float - Electrical frequency [Hz] - """ - - if self.OP.felec is not None: - return self.OP.felec # TODO maybe add some checks? - elif self.OP.N0 is not None: - # Get the phase number for verifications - if self.parent is None: - raise InputError( - "ERROR: InputFlux object should be inside a Simulation object" - ) - # get the simulation - if isinstance(self.parent, Simulation): - simu = self.parent - elif isinstance(self.parent.parent, Simulation): - simu = self.parent.parent - else: - raise InputError("Cannot find InputFlux simulation.") - - zp = simu.machine.get_pole_pair_number() - zp = zp if zp is not None else 1 - - return self.OP.N0 * zp / 60 - else: - raise InputError( - "ERROR: InputFlux object can't have OP.felec and OP.N0 at None" - ) diff --git a/pyleecan/Methods/Simulation/InputFlux/gen_input.py b/pyleecan/Methods/Simulation/InputFlux/gen_input.py index baa89f313..152ce4427 100644 --- a/pyleecan/Methods/Simulation/InputFlux/gen_input.py +++ b/pyleecan/Methods/Simulation/InputFlux/gen_input.py @@ -1,23 +1,31 @@ -# -*- coding: utf-8 -*- +from numpy import arange, pi, array -from numpy import arange +from SciDataTool import DataPattern from ....Classes.OutMag import OutMag -from ....Classes.Simulation import Simulation from ....Classes.ImportMatrixXls import ImportMatrixXls +from ....Classes.ImportMatrixVal import ImportMatrixVal +from ....Classes.Input import Input +from ....Classes.InputCurrent import InputCurrent + from ....Methods.Simulation.Input import InputError +from ....Functions.load import import_class + + +VERBOSE_KEY = {"Br": "Radial", "Bt": "Tangential", "Bz": "Axial"} + def gen_input(self): - """Generate the input for the structural module (magnetic output) + """Generate the input for the force/stress module (magnetic output) Parameters ---------- - self : InFlux - An InFlux object + self : InputFlux + An InputFlux object """ - output = OutMag() + Simulation = import_class("pyleecan.Classes", "Simulation") # get the simulation if isinstance(self.parent, Simulation): @@ -25,54 +33,100 @@ def gen_input(self): elif isinstance(self.parent.parent, Simulation): simu = self.parent.parent else: - raise InputError( - "ERROR: InputCurrent object should be inside a Simulation object" - ) + raise InputError("InputCurrent object should be inside a Simulation object") - # Set discretization - if self.N0 is None: - if self.OP is None: - N0 = None # N0 can be None if time isn't - else: - N0 = self.OP.N0 - else: - N0 = self.N0 + if simu.parent is None: + raise InputError("The Simulation object must be in an Output object to run") + + logger = simu.get_logger() + + # Call InputCurrent.gen_input() + InputCurrent.gen_input(self) # Import flux components - per_a = self.per_a - per_t = self.per_t - is_antiper_a = self.is_antiper_a - is_antiper_t = self.is_antiper_t - out_dict = {} - for key in self.B_dict: - comp = self.B_dict[key] - if isinstance(comp, ImportMatrixXls) and comp.axes_colrows is not None: - B_comp, axes_values = comp.get_data() - else: - B_comp = comp.get_data() - axes_values = {} - out_dict[key] = B_comp - - axes_dict = self.comp_axes( - axes_values, - N0=N0, - per_a=per_a, - is_antiper_a=is_antiper_a, - per_t=per_t, - is_antiper_t=is_antiper_t, - ) + out_mag = OutMag() + if self.B_enforced is None: + per_a = self.per_a + per_t = self.per_t + is_antiper_a = self.is_antiper_a + is_antiper_t = self.is_antiper_t + out_dict = {} + for key in self.B_dict: + comp = self.B_dict[key] + if isinstance(comp, list): + for i, B_import in enumerate(comp): + if i == 0: + values, axes_values = B_import.get_data() + B_comp = values[..., None] + else: + values, _ = B_import.get_data() + B_comp.append(B_comp, values[..., None], axis=-1) + if self.slice is not None: + axes_values["slice"] = self.slice + else: + axes_values["slice"] = arange(len(comp)) + elif isinstance(comp, ImportMatrixXls): + # Only this one is used to import flux from excel files + logger.info( + "Importing " + + VERBOSE_KEY[key].lower() + + " flux from " + + comp.file_path + ) + B_comp, axes_values = comp.get_data(logger=logger) - if simu.parent is None: - raise InputError( - "ERROR: The Simulation object must be in an Output object to run" + else: + B_comp = comp.get_data() + axes_values = {} + if len(B_comp.shape) < 3: + B_comp = B_comp[..., None] + + out_dict[key] = B_comp + + self.time = ImportMatrixVal(value=axes_values["time"]) + self.angle = ImportMatrixVal(value=axes_values["angle"]) + + axes_dict = Input.comp_axes( + self, + per_a=per_a, + is_antiper_a=is_antiper_a, + per_t=per_t, + is_antiper_t=is_antiper_t, ) - # Save the Output in the correct place - if N0 is not None: - simu.parent.elec.N0 = N0 - output = OutMag() - output.store(out_dict=out_dict, axes_dict=axes_dict) - simu.parent.mag = output - - # Define the electrical Output to set the Operating Point - if self.OP is not None: - self.OP.gen_input() + + # Compute slices and angles + if "slice" in axes_values: + Slice = DataPattern( + name="z", + unit="m", + values=axes_values["z"], + rebuild_indices=axes_values["slice"], + unique_indices=axes_values["slice"], + values_whole=axes_values["z"], + ) + else: + # Single slice + Slice = DataPattern( + name="z", + unit="m", + values=array([0], dtype=float), + rebuild_indices=[0], + unique_indices=[0], + values_whole=array([0], dtype=float), + ) + + # Store in axes_dict + axes_dict["z"] = Slice + + # Save the Output in the correct place + out_mag.store(out_dict=out_dict, axes_dict=axes_dict) + + else: + # Enforce input VectorField + out_mag.B = self.B_enforced + out_mag.axes_dict = dict() + axes_list = self.B_enforced.get_axes() + for ax in axes_list: + out_mag.axes_dict[ax.name] = ax.copy() + + simu.parent.mag = out_mag diff --git a/pyleecan/Methods/Simulation/InputForce/gen_input.py b/pyleecan/Methods/Simulation/InputForce/gen_input.py index 58e121498..ccfa5e27d 100644 --- a/pyleecan/Methods/Simulation/InputForce/gen_input.py +++ b/pyleecan/Methods/Simulation/InputForce/gen_input.py @@ -13,20 +13,35 @@ def gen_input(self): An InputForce object """ - output = OutForce() - - if self.P is None: - raise InputError("ERROR: InForce.P missing") - if self.P.name is None: - self.P.name = "Magnetic airgap surface force" - if self.P.symbol is None: - self.P.symbol = "P" - P = self.P.get_data() - output.P = P - if self.parent.parent is None: raise InputError( "ERROR: The Simulation object must be in an Output object to run" ) - # Save the Output in the correct place - self.parent.parent.struct = output + + if self.AGSF is None and self.AGSF_enforced is None: + raise InputError("ERROR: Input AGSF are missing") + else: + # generate OutElec from parent InputCurrent + super(type(self), self).gen_input() + + # generate OutForce + outforce = OutForce() + + if self.AGSF_enforced is not None: + outforce.AGSF = self.AGSF_enforced + else: + if self.AGSF.name is None: + self.AGSF.name = "Magnetic airgap surface force" + if self.AGSF.symbol is None: + self.AGSF.symbol = "AGSF" + AGSF = self.AGSF.get_data() + outforce.AGSF = AGSF + + # Update axes_dict + outforce.axes_dict = dict() + axes_list = outforce.AGSF.get_axes() + for ax in axes_list: + outforce.axes_dict[ax.name] = ax + + # Save the Output in the correct place + self.parent.parent.force = outforce diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index c4283b417..751481dc8 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -7,10 +7,6 @@ from ....Methods.Simulation.Input import InputError -# from ....Functions.Electrical.coordinate_transformation import n2dq -# from SciDataTool import Data1D, DataTime -# from ....Functions.Winding.gen_phase_list import gen_name - def gen_input(self): """Generate the input for the electrical module (electrical output filled with voltage) @@ -29,36 +25,84 @@ def gen_input(self): else: raise InputError("InputVoltage object should be inside a Simulation object") + if simu.parent is None: + raise InputError("The Simulation object must be in an Output object to run") + + output = simu.parent + # Create the correct Output object outelec = OutElec() - outgeo = simu.parent.geo + output.elec = outelec + outgeo = output.geo + + # Replace N0=0 by 0.1 rpm + if self.N0 == 0: + self.N0 = 0.1 + self.get_logger().debug("Updating N0 from 0 [rpm] to 0.1 [rpm] in gen_input") + + outelec.N0 = self.N0 + outelec.felec = self.comp_felec() + + if self.U0_ref is None and self.Ud_ref and self.Uq_ref: + raise Exception("U0_ref, Ud_ref, and Uq_ref cannot be all None in InputVoltage") + + outelec.U0_ref = self.U0_ref + outelec.slip_ref = self.slip_ref + + # Load and check alpha_rotor and N0 + if self.angle_rotor is None and self.N0 is None: + raise InputError( + "InputVoltage.angle_rotor and InputVoltage.N0 can't be None at the same time" + ) + if self.angle_rotor is not None: + outelec.angle_rotor = self.angle_rotor.get_data() + if ( + not isinstance(outelec.angle_rotor, ndarray) + or len(outelec.angle_rotor.shape) != 1 + or outelec.angle_rotor.size != self.Nt_tot + ): + # angle_rotor should be a vector of same length as time + raise InputError( + "InputVoltage.angle_rotor should be a vector of the same length as time, " + + str(outelec.angle_rotor.shape) + + " shape found, " + + str(self.Nt_tot) + + " expected" + ) + + if self.rot_dir is not None: + if self.rot_dir in [-1, 1]: + # Enforce user-defined rotation direction + outgeo.rot_dir = self.rot_dir + else: + # Enforce calculation of rotation direction + outgeo.rot_dir = None + + if self.angle_rotor_initial is None: + # Enforce default initial position + outelec.angle_rotor_initial = 0 + else: + outelec.angle_rotor_initial = self.angle_rotor_initial + + if self.Tem_av_ref is not None: + outelec.Tem_av_ref = self.Tem_av_ref # Set time and angle full axes in geometry output - Time, Angle = self.comp_axes(simu.machine) - outgeo.axes_dict = {"time": Time, "angle": Angle} + axes_dict = self.comp_axes() + # Store in axes_dict + outgeo.axes_dict = axes_dict # Create time axis in electrical output without periodicity # TODO: account for pole periodicity - Time_elec = Time.copy() Time_elec, _ = create_from_axis( - axis_in=Time, + axis_in=axes_dict["time"], per=1, # int(2 * simu.machine.get_pole_pair_number()), is_aper=False, # True, is_include_per=False, # True, - is_remove_aper=True, # False, ) + # Store in axes_dict outelec.axes_dict = {"time": Time_elec} - outelec.N0 = self.N0 - outelec.felec = self.comp_felec() # TODO introduce set_felec(slip) - - if self.U0_ref is None and self.Ud_ref and self.Uq_ref: - raise Exception("U0_ref, Ud_ref, and Uq_refcannot be all None in InputVoltage") - - outelec.U0_ref = self.U0_ref - - outelec.slip_ref = self.slip_ref - # Generate Us # if qs > 0: # TODO From 5f7b57519077d271434b042fe5695912f8acb49d Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 30 Sep 2021 17:38:16 +0200 Subject: [PATCH 035/167] [WP] debugging after merge --- pyleecan/Classes/Class_Dict.json | 4 +- pyleecan/Classes/Input.py | 14 ----- pyleecan/Classes/Output.py | 16 ++++++ .../Generator/ClassesRef/Output/Output.csv | 21 +++---- .../Generator/ClassesRef/Simulation/Input.csv | 2 +- pyleecan/Methods/Output/OutElec/get_Nr.py | 24 ++++---- .../Output/Output/getter/comp_angle_rotor.py | 56 +++++++++++++++++++ pyleecan/Methods/Simulation/Input/set_disc.py | 40 ------------- 8 files changed, 100 insertions(+), 77 deletions(-) create mode 100644 pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py delete mode 100644 pyleecan/Methods/Simulation/Input/set_disc.py diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 5ece8370c..920409d73 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4118,8 +4118,7 @@ "is_internal": false, "methods": [ "gen_input", - "comp_axes", - "set_disc" + "comp_axes" ], "mother": "", "name": "Input", @@ -8836,6 +8835,7 @@ "desc": "Main Output object: gather all the outputs of all the modules", "is_internal": false, "methods": [ + "getter.comp_angle_rotor", "getter.get_angle_offset_initial", "getter.get_angle_rotor", "getter.get_BH_rotor", diff --git a/pyleecan/Classes/Input.py b/pyleecan/Classes/Input.py index 5d8a5f93e..f319f0937 100644 --- a/pyleecan/Classes/Input.py +++ b/pyleecan/Classes/Input.py @@ -27,11 +27,6 @@ except ImportError as error: comp_axes = error -try: - from ..Methods.Simulation.Input.set_disc import set_disc -except ImportError as error: - set_disc = error - from ..Classes.ImportMatrixVal import ImportMatrixVal from numpy import ndarray @@ -64,15 +59,6 @@ class Input(FrozenClass): ) else: comp_axes = comp_axes - # cf Methods.Simulation.Input.set_disc - if isinstance(set_disc, ImportError): - set_disc = property( - fget=lambda x: raise_( - ImportError("Can't use Input method set_disc: " + str(set_disc)) - ) - ) - else: - set_disc = set_disc # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/Output.py b/pyleecan/Classes/Output.py index 50ca6fdde..2a19884b2 100644 --- a/pyleecan/Classes/Output.py +++ b/pyleecan/Classes/Output.py @@ -17,6 +17,11 @@ # Import all class method # Try/catch to remove unnecessary dependencies in unused method +try: + from ..Methods.Output.Output.getter.comp_angle_rotor import comp_angle_rotor +except ImportError as error: + comp_angle_rotor = error + try: from ..Methods.Output.Output.getter.get_angle_offset_initial import ( get_angle_offset_initial, @@ -89,6 +94,17 @@ class Output(FrozenClass): VERSION = 1 # Check ImportError to remove unnecessary dependencies in unused method + # cf Methods.Output.Output.getter.comp_angle_rotor + if isinstance(comp_angle_rotor, ImportError): + comp_angle_rotor = property( + fget=lambda x: raise_( + ImportError( + "Can't use Output method comp_angle_rotor: " + str(comp_angle_rotor) + ) + ) + ) + else: + comp_angle_rotor = comp_angle_rotor # cf Methods.Output.Output.getter.get_angle_offset_initial if isinstance(get_angle_offset_initial, ImportError): get_angle_offset_initial = property( diff --git a/pyleecan/Generator/ClassesRef/Output/Output.csv b/pyleecan/Generator/ClassesRef/Output/Output.csv index 63d346bd2..01e5ea128 100644 --- a/pyleecan/Generator/ClassesRef/Output/Output.csv +++ b/pyleecan/Generator/ClassesRef/Output/Output.csv @@ -1,11 +1,12 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -simu,-,Simulation object that generated the Output,0,Simulation,,,,,Output,,getter.get_angle_offset_initial,VERSION,1,Main Output object: gather all the outputs of all the modules -path_result,-,Path to the folder to same the results,0,str,,,,,,,getter.get_angle_rotor,,, -geo,-,Geometry output,0,OutGeo,,,,,,,getter.get_BH_rotor,,, -elec,-,Electrical module output,0,OutElec,,,,,,,getter.get_BH_stator,,, -mag,-,Magnetic module output,0,OutMag,,,,,,,getter.get_path_result,,, -struct,-,Structural module output,0,OutStruct,,,,,,,getter.get_machine_periodicity,,, -post,-,Post-Processing settings,0,OutPost,,,,,,,getter.get_rot_dir,,, -logger_name,-,Name of the logger to use,0,str,Pyleecan.Output,,,,,,getter.get_fund_harm,,, -force,-,Force module output,0,OutForce,,,,,,,getter.get_data_from_str,,, -loss,-,Loss module output,0,OutLoss,,,,,,,print_memory,,, +simu,-,Simulation object that generated the Output,0,Simulation,,,,,Output,,getter.comp_angle_rotor,VERSION,1,Main Output object: gather all the outputs of all the modules +path_result,-,Path to the folder to same the results,0,str,,,,,,,getter.get_angle_offset_initial,,, +geo,-,Geometry output,0,OutGeo,,,,,,,getter.get_angle_rotor,,, +elec,-,Electrical module output,0,OutElec,,,,,,,getter.get_BH_rotor,,, +mag,-,Magnetic module output,0,OutMag,,,,,,,getter.get_BH_stator,,, +struct,-,Structural module output,0,OutStruct,,,,,,,getter.get_path_result,,, +post,-,Post-Processing settings,0,OutPost,,,,,,,getter.get_machine_periodicity,,, +logger_name,-,Name of the logger to use,0,str,Pyleecan.Output,,,,,,getter.get_rot_dir,,, +force,-,Force module output,0,OutForce,,,,,,,getter.get_fund_harm,,, +loss,-,Loss module output,0,OutLoss,,,,,,,getter.get_data_from_str,,, +,,,,,,,,,,,print_memory,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/Input.csv b/pyleecan/Generator/ClassesRef/Simulation/Input.csv index d5adbe56a..16bb2efda 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/Input.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/Input.csv @@ -1,7 +1,7 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description time,s,Electrical time vector (no symmetry) to import,Nt_tot,ImportMatrix,None,,,,Simulation,,gen_input,VERSION,1,Starting data of the simulation angle,rad,Electrical position vector (no symmetry) to import,Na_tot,ImportMatrix,None,,,,,,comp_axes,,, -Nt_tot,-,Time discretization,0,int,2048,1,,,,,set_disc,,, +Nt_tot,-,Time discretization,0,int,2048,1,,,,,,,, Nrev,-,Number of rotor revolution (to compute the final time),,float,None,0,,,,,,,, Na_tot,-,Angular discretization,,int,2048,1,,,,,,,, N0,rpm,Rotor speed,1,float,None,,,,,,,,, diff --git a/pyleecan/Methods/Output/OutElec/get_Nr.py b/pyleecan/Methods/Output/OutElec/get_Nr.py index 5c412913f..90cfd122f 100644 --- a/pyleecan/Methods/Output/OutElec/get_Nr.py +++ b/pyleecan/Methods/Output/OutElec/get_Nr.py @@ -1,28 +1,32 @@ from numpy import ones -class OutElecError(Exception): - pass - - -def get_Nr(self): +def get_Nr(self, Time=None): """Create speed in function of time vector Nr Parameters ---------- self : OutElec An OutElec object + Time : Data + a time axis (SciDataTool Data object) Returns ------- Nr: ndarray speed in function of time """ - if self.axes_dict is None or "time" not in self.axes_dict: - raise OutElecError('You must define "time" property before calling get_Nr') + + if Time is None: + if self.axes_dict is not None and "time" in self.axes_dict: + Time = self.axes_dict["time"] + else: + raise Exception('You must define "time" property before calling get_Nr') + if self.N0 is None: - raise OutElecError('You must define "N0" before calling get_Nr.') + raise Exception('You must define "N0" before calling get_Nr') # Same speed for every timestep - Nr = self.N0 * ones(self.axes_dict["time"].get_length(is_oneperiod=False)) - return Nr + Nr = self.N0 * ones(Time.get_length(is_oneperiod=False)) + + return Nr \ No newline at end of file diff --git a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py new file mode 100644 index 000000000..ef2a6e4c9 --- /dev/null +++ b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py @@ -0,0 +1,56 @@ +from numpy import pi, cumsum, roll, ones, unique + + +def comp_angle_rotor(self, Time): + """ + Computes the angular position of the rotor as a function of time + and set the Output.elec.angle_rotor attribute if it is None + + Parameters + ---------- + self : Output + an Output object + Time : Data + a time axis (SciDataTool Data object) + + Returns + ------- + alpha_rotor: numpy.ndarray + angular position of the rotor as a function of time (vector) [rad] + + """ + + # Compute according to the speed + Nr = self.elec.get_Nr(Time=Time) + + # Get rotor rotating direction + # rotor rotating is the opposite of rot_dir which is fundamental field rotation direction + # so that rotor moves in positive angles + rot_dir = -self.get_rot_dir() + + # Compute rotor initial angle (for synchronous machines, to align rotor d-axis and stator alpha-axis) + A0 = self.get_angle_offset_initial() + + # Case where normalization is a constant + if A0 == 0 and unique(Nr).size == 1: + angle_rotor = rot_dir * Nr[0] * 2 * pi / 60 + + else: + + time = Time.get_values(is_oneperiod=False) + if time.size == 1: + # Only one time step, no need to compute the position + angle_rotor = ones(1) * A0 + else: + deltaT = time[1] - time[0] + # Convert Nr from [rpm] to [rad/s] (time in [s] and angle_rotor in [rad]) + Ar = cumsum(rot_dir * deltaT * Nr * 2 * pi / 60) + # Enforce first position to 0 + Ar = roll(Ar, 1) + Ar[0] = 0 + angle_rotor = Ar + A0 + + # Store in time axis normalizations + Time.normalizations["angle_rotor"] = angle_rotor + + return angle_rotor diff --git a/pyleecan/Methods/Simulation/Input/set_disc.py b/pyleecan/Methods/Simulation/Input/set_disc.py deleted file mode 100644 index e50b746b0..000000000 --- a/pyleecan/Methods/Simulation/Input/set_disc.py +++ /dev/null @@ -1,40 +0,0 @@ -NT_MAX = 4096 -NA_MAX = 2048 - - -def set_disc(self, machine): - """Set the value of Nt_tot and Na_tot according to a machine - - Parameters - ---------- - self : Input - An Input object - machine : Machine - A machine object to adapt the discretization - """ - - pert, is_apert, _, _ = machine.comp_periodicity_time() # Stator - if is_apert: - pert = pert * 2 - - self.Nt_tot = int(round(NT_MAX / pert)) * pert - - pera, is_apera = machine.comp_periodicity_spatial() - - if is_apera: - pera = pera * 2 - - if hasattr(machine.stator, "get_Zs"): - # Na_tot/pera must be multiple of Zs for lumped force calculation in time space domain - Zs = machine.stator.get_Zs() - else: - Zs = 1 - - self.Na_tot = int(round(NA_MAX / Zs / pera) * Zs * pera) - - self.get_logger().debug( - "Setting automatic discretization: Nt_tot=" - + str(self.Nt_tot) - + ", Na_tot=" - + str(self.Na_tot) - ) From fde1a197fc53922f54d3ec85365b786f3c55a48b Mon Sep 17 00:00:00 2001 From: helene-t Date: Fri, 1 Oct 2021 10:33:37 +0200 Subject: [PATCH 036/167] [WiP] rework of comp_axes methods --- pyleecan/Classes/Class_Dict.json | 78 ---- pyleecan/Classes/Input.py | 15 - pyleecan/Classes/InputElec.py | 371 ------------------ pyleecan/Classes/import_all.py | 1 - pyleecan/Functions/load_switch.py | 1 - .../Generator/ClassesRef/Simulation/Input.csv | 4 +- .../ClassesRef/Simulation/InputElec.csv | 7 - pyleecan/Methods/Output/OutElec/get_Nr.py | 2 +- .../Output/Output/getter/comp_angle_rotor.py | 4 +- .../Output/Output/getter/get_angle_rotor.py | 48 +-- .../Methods/Simulation/Input/comp_axes.py | 29 +- .../Methods/Simulation/Input/gen_input.py | 40 -- .../Simulation/InputCurrent/comp_felec.py | 30 -- .../Methods/Simulation/InputElec/__init__.py | 7 - .../Simulation/InputElec/comp_felec.py | 35 -- .../Methods/Simulation/InputElec/gen_input.py | 86 ---- .../Methods/Simulation/InputElec/set_Id_Iq.py | 12 - .../Methods/Simulation/InputFlux/gen_input.py | 10 +- .../Simulation/InputForce/gen_input.py | 12 +- .../Simulation/InputVoltage/comp_felec.py | 44 ++- .../Simulation/InputVoltage/gen_input.py | 48 +-- 21 files changed, 91 insertions(+), 793 deletions(-) delete mode 100644 pyleecan/Classes/InputElec.py delete mode 100644 pyleecan/Generator/ClassesRef/Simulation/InputElec.csv delete mode 100644 pyleecan/Methods/Simulation/Input/gen_input.py delete mode 100644 pyleecan/Methods/Simulation/InputCurrent/comp_felec.py delete mode 100644 pyleecan/Methods/Simulation/InputElec/__init__.py delete mode 100644 pyleecan/Methods/Simulation/InputElec/comp_felec.py delete mode 100644 pyleecan/Methods/Simulation/InputElec/gen_input.py delete mode 100644 pyleecan/Methods/Simulation/InputElec/set_Id_Iq.py diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 920409d73..dec2afb6b 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4109,7 +4109,6 @@ ], "daughters": [ "InputCurrent", - "InputElec", "InputFlux", "InputForce", "InputVoltage" @@ -4117,7 +4116,6 @@ "desc": "Starting data of the simulation", "is_internal": false, "methods": [ - "gen_input", "comp_axes" ], "mother": "", @@ -4239,82 +4237,6 @@ } ] }, - "InputElec": { - "constants": [ - { - "name": "VERSION", - "value": "1" - } - ], - "daughters": [], - "desc": "Input to skip the electrical module and start with the magnetic one", - "is_internal": false, - "methods": [ - "gen_input", - "comp_felec", - "set_Id_Iq" - ], - "mother": "Input", - "name": "InputElec", - "package": "Simulation", - "path": "pyleecan/Generator/ClassesRef/Simulation/InputElec.csv", - "properties": [ - { - "desc": "Rotation direction of the rotor 1 trigo, -1 clockwise", - "max": "1", - "min": "-1", - "name": "rot_dir", - "type": "float", - "unit": "-", - "value": -1 - }, - { - "desc": "d-axis current magnitude", - "max": "", - "min": "", - "name": "Id_ref", - "type": "float", - "unit": "A", - "value": null - }, - { - "desc": "q-axis current magnitude", - "max": "", - "min": "", - "name": "Iq_ref", - "type": "float", - "unit": "A", - "value": null - }, - { - "desc": "d-axis voltage magnitude", - "max": "", - "min": "", - "name": "Ud_ref", - "type": "float", - "unit": "V", - "value": null - }, - { - "desc": "q-axis voltage magnitude", - "max": "", - "min": "", - "name": "Uq_ref", - "type": "float", - "unit": "V", - "value": null - }, - { - "desc": "electrical frequency", - "max": "", - "min": "", - "name": "felec", - "type": "float", - "unit": "Hz", - "value": null - } - ] - }, "InputFlux": { "constants": [ { diff --git a/pyleecan/Classes/Input.py b/pyleecan/Classes/Input.py index f319f0937..0c32dc850 100644 --- a/pyleecan/Classes/Input.py +++ b/pyleecan/Classes/Input.py @@ -17,11 +17,6 @@ # Import all class method # Try/catch to remove unnecessary dependencies in unused method -try: - from ..Methods.Simulation.Input.gen_input import gen_input -except ImportError as error: - gen_input = error - try: from ..Methods.Simulation.Input.comp_axes import comp_axes except ImportError as error: @@ -40,16 +35,6 @@ class Input(FrozenClass): VERSION = 1 - # Check ImportError to remove unnecessary dependencies in unused method - # cf Methods.Simulation.Input.gen_input - if isinstance(gen_input, ImportError): - gen_input = property( - fget=lambda x: raise_( - ImportError("Can't use Input method gen_input: " + str(gen_input)) - ) - ) - else: - gen_input = gen_input # cf Methods.Simulation.Input.comp_axes if isinstance(comp_axes, ImportError): comp_axes = property( diff --git a/pyleecan/Classes/InputElec.py b/pyleecan/Classes/InputElec.py deleted file mode 100644 index 4808b1ac9..000000000 --- a/pyleecan/Classes/InputElec.py +++ /dev/null @@ -1,371 +0,0 @@ -# -*- coding: utf-8 -*- -# File generated according to Generator/ClassesRef/Simulation/InputElec.csv -# WARNING! All changes made in this file will be lost! -"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Simulation/InputElec -""" - -from os import linesep -from sys import getsizeof -from logging import getLogger -from ._check import check_var, raise_ -from ..Functions.get_logger import get_logger -from ..Functions.save import save -from ..Functions.copy import copy -from ..Functions.load import load_init_dict -from ..Functions.Load.import_class import import_class -from .Input import Input - -# Import all class method -# Try/catch to remove unnecessary dependencies in unused method -try: - from ..Methods.Simulation.InputElec.gen_input import gen_input -except ImportError as error: - gen_input = error - -try: - from ..Methods.Simulation.InputElec.comp_felec import comp_felec -except ImportError as error: - comp_felec = error - -try: - from ..Methods.Simulation.InputElec.set_Id_Iq import set_Id_Iq -except ImportError as error: - set_Id_Iq = error - - -from ..Classes.ImportMatrixVal import ImportMatrixVal -from numpy import ndarray -from numpy import array, array_equal -from ._check import InitUnKnowClassError -from .ImportMatrix import ImportMatrix - - -class InputElec(Input): - """Input to skip the electrical module and start with the magnetic one""" - - VERSION = 1 - - # Check ImportError to remove unnecessary dependencies in unused method - # cf Methods.Simulation.InputElec.gen_input - if isinstance(gen_input, ImportError): - gen_input = property( - fget=lambda x: raise_( - ImportError("Can't use InputElec method gen_input: " + str(gen_input)) - ) - ) - else: - gen_input = gen_input - # cf Methods.Simulation.InputElec.comp_felec - if isinstance(comp_felec, ImportError): - comp_felec = property( - fget=lambda x: raise_( - ImportError("Can't use InputElec method comp_felec: " + str(comp_felec)) - ) - ) - else: - comp_felec = comp_felec - # cf Methods.Simulation.InputElec.set_Id_Iq - if isinstance(set_Id_Iq, ImportError): - set_Id_Iq = property( - fget=lambda x: raise_( - ImportError("Can't use InputElec method set_Id_Iq: " + str(set_Id_Iq)) - ) - ) - else: - set_Id_Iq = set_Id_Iq - # save and copy methods are available in all object - save = save - copy = copy - # get_logger method is available in all object - get_logger = get_logger - - def __init__( - self, - rot_dir=-1, - Id_ref=None, - Iq_ref=None, - Ud_ref=None, - Uq_ref=None, - felec=None, - time=None, - angle=None, - Nt_tot=2048, - Nrev=None, - Na_tot=2048, - N0=None, - init_dict=None, - init_str=None, - ): - """Constructor of the class. Can be use in three ways : - - __init__ (arg1 = 1, arg3 = 5) every parameters have name and default values - for pyleecan type, -1 will call the default constructor - - __init__ (init_dict = d) d must be a dictionary with property names as keys - - __init__ (init_str = s) s must be a string - s is the file path to load - - ndarray or list can be given for Vector and Matrix - object or dict can be given for pyleecan Object""" - - if init_str is not None: # Load from a file - init_dict = load_init_dict(init_str)[1] - if init_dict is not None: # Initialisation by dict - assert type(init_dict) is dict - # Overwrite default value with init_dict content - if "rot_dir" in list(init_dict.keys()): - rot_dir = init_dict["rot_dir"] - if "Id_ref" in list(init_dict.keys()): - Id_ref = init_dict["Id_ref"] - if "Iq_ref" in list(init_dict.keys()): - Iq_ref = init_dict["Iq_ref"] - if "Ud_ref" in list(init_dict.keys()): - Ud_ref = init_dict["Ud_ref"] - if "Uq_ref" in list(init_dict.keys()): - Uq_ref = init_dict["Uq_ref"] - if "felec" in list(init_dict.keys()): - felec = init_dict["felec"] - if "time" in list(init_dict.keys()): - time = init_dict["time"] - if "angle" in list(init_dict.keys()): - angle = init_dict["angle"] - if "Nt_tot" in list(init_dict.keys()): - Nt_tot = init_dict["Nt_tot"] - if "Nrev" in list(init_dict.keys()): - Nrev = init_dict["Nrev"] - if "Na_tot" in list(init_dict.keys()): - Na_tot = init_dict["Na_tot"] - if "N0" in list(init_dict.keys()): - N0 = init_dict["N0"] - # Set the properties (value check and convertion are done in setter) - self.rot_dir = rot_dir - self.Id_ref = Id_ref - self.Iq_ref = Iq_ref - self.Ud_ref = Ud_ref - self.Uq_ref = Uq_ref - self.felec = felec - # Call Input init - super(InputElec, self).__init__( - time=time, angle=angle, Nt_tot=Nt_tot, Nrev=Nrev, Na_tot=Na_tot, N0=N0 - ) - # The class is frozen (in Input init), for now it's impossible to - # add new properties - - def __str__(self): - """Convert this object in a readeable string (for print)""" - - InputElec_str = "" - # Get the properties inherited from Input - InputElec_str += super(InputElec, self).__str__() - InputElec_str += "rot_dir = " + str(self.rot_dir) + linesep - InputElec_str += "Id_ref = " + str(self.Id_ref) + linesep - InputElec_str += "Iq_ref = " + str(self.Iq_ref) + linesep - InputElec_str += "Ud_ref = " + str(self.Ud_ref) + linesep - InputElec_str += "Uq_ref = " + str(self.Uq_ref) + linesep - InputElec_str += "felec = " + str(self.felec) + linesep - return InputElec_str - - def __eq__(self, other): - """Compare two objects (skip parent)""" - - if type(other) != type(self): - return False - - # Check the properties inherited from Input - if not super(InputElec, self).__eq__(other): - return False - if other.rot_dir != self.rot_dir: - return False - if other.Id_ref != self.Id_ref: - return False - if other.Iq_ref != self.Iq_ref: - return False - if other.Ud_ref != self.Ud_ref: - return False - if other.Uq_ref != self.Uq_ref: - return False - if other.felec != self.felec: - return False - return True - - def compare(self, other, name="self", ignore_list=None): - """Compare two objects and return list of differences""" - - if ignore_list is None: - ignore_list = list() - if type(other) != type(self): - return ["type(" + name + ")"] - diff_list = list() - - # Check the properties inherited from Input - diff_list.extend(super(InputElec, self).compare(other, name=name)) - if other._rot_dir != self._rot_dir: - diff_list.append(name + ".rot_dir") - if other._Id_ref != self._Id_ref: - diff_list.append(name + ".Id_ref") - if other._Iq_ref != self._Iq_ref: - diff_list.append(name + ".Iq_ref") - if other._Ud_ref != self._Ud_ref: - diff_list.append(name + ".Ud_ref") - if other._Uq_ref != self._Uq_ref: - diff_list.append(name + ".Uq_ref") - if other._felec != self._felec: - diff_list.append(name + ".felec") - # Filter ignore differences - diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) - return diff_list - - def __sizeof__(self): - """Return the size in memory of the object (including all subobject)""" - - S = 0 # Full size of the object - - # Get size of the properties inherited from Input - S += super(InputElec, self).__sizeof__() - S += getsizeof(self.rot_dir) - S += getsizeof(self.Id_ref) - S += getsizeof(self.Iq_ref) - S += getsizeof(self.Ud_ref) - S += getsizeof(self.Uq_ref) - S += getsizeof(self.felec) - return S - - def as_dict(self, **kwargs): - """ - Convert this object in a json serializable dict (can be use in __init__). - Optional keyword input parameter is for internal use only - and may prevent json serializability. - """ - - # Get the properties inherited from Input - InputElec_dict = super(InputElec, self).as_dict(**kwargs) - InputElec_dict["rot_dir"] = self.rot_dir - InputElec_dict["Id_ref"] = self.Id_ref - InputElec_dict["Iq_ref"] = self.Iq_ref - InputElec_dict["Ud_ref"] = self.Ud_ref - InputElec_dict["Uq_ref"] = self.Uq_ref - InputElec_dict["felec"] = self.felec - # The class name is added to the dict for deserialisation purpose - # Overwrite the mother class name - InputElec_dict["__class__"] = "InputElec" - return InputElec_dict - - def _set_None(self): - """Set all the properties to None (except pyleecan object)""" - - self.rot_dir = None - self.Id_ref = None - self.Iq_ref = None - self.Ud_ref = None - self.Uq_ref = None - self.felec = None - # Set to None the properties inherited from Input - super(InputElec, self)._set_None() - - def _get_rot_dir(self): - """getter of rot_dir""" - return self._rot_dir - - def _set_rot_dir(self, value): - """setter of rot_dir""" - check_var("rot_dir", value, "float", Vmin=-1, Vmax=1) - self._rot_dir = value - - rot_dir = property( - fget=_get_rot_dir, - fset=_set_rot_dir, - doc=u"""Rotation direction of the rotor 1 trigo, -1 clockwise - - :Type: float - :min: -1 - :max: 1 - """, - ) - - def _get_Id_ref(self): - """getter of Id_ref""" - return self._Id_ref - - def _set_Id_ref(self, value): - """setter of Id_ref""" - check_var("Id_ref", value, "float") - self._Id_ref = value - - Id_ref = property( - fget=_get_Id_ref, - fset=_set_Id_ref, - doc=u"""d-axis current magnitude - - :Type: float - """, - ) - - def _get_Iq_ref(self): - """getter of Iq_ref""" - return self._Iq_ref - - def _set_Iq_ref(self, value): - """setter of Iq_ref""" - check_var("Iq_ref", value, "float") - self._Iq_ref = value - - Iq_ref = property( - fget=_get_Iq_ref, - fset=_set_Iq_ref, - doc=u"""q-axis current magnitude - - :Type: float - """, - ) - - def _get_Ud_ref(self): - """getter of Ud_ref""" - return self._Ud_ref - - def _set_Ud_ref(self, value): - """setter of Ud_ref""" - check_var("Ud_ref", value, "float") - self._Ud_ref = value - - Ud_ref = property( - fget=_get_Ud_ref, - fset=_set_Ud_ref, - doc=u"""d-axis voltage magnitude - - :Type: float - """, - ) - - def _get_Uq_ref(self): - """getter of Uq_ref""" - return self._Uq_ref - - def _set_Uq_ref(self, value): - """setter of Uq_ref""" - check_var("Uq_ref", value, "float") - self._Uq_ref = value - - Uq_ref = property( - fget=_get_Uq_ref, - fset=_set_Uq_ref, - doc=u"""q-axis voltage magnitude - - :Type: float - """, - ) - - def _get_felec(self): - """getter of felec""" - return self._felec - - def _set_felec(self, value): - """setter of felec""" - check_var("felec", value, "float") - self._felec = value - - felec = property( - fget=_get_felec, - fset=_set_felec, - doc=u"""electrical frequency - - :Type: float - """, - ) diff --git a/pyleecan/Classes/import_all.py b/pyleecan/Classes/import_all.py index 557e7f983..f049cbc52 100644 --- a/pyleecan/Classes/import_all.py +++ b/pyleecan/Classes/import_all.py @@ -76,7 +76,6 @@ from ..Classes.IndMagFEMM import IndMagFEMM from ..Classes.Input import Input from ..Classes.InputCurrent import InputCurrent -from ..Classes.InputElec import InputElec from ..Classes.InputFlux import InputFlux from ..Classes.InputForce import InputForce from ..Classes.InputVoltage import InputVoltage diff --git a/pyleecan/Functions/load_switch.py b/pyleecan/Functions/load_switch.py index 788bdd87a..0c600218a 100644 --- a/pyleecan/Functions/load_switch.py +++ b/pyleecan/Functions/load_switch.py @@ -78,7 +78,6 @@ "IndMagFEMM": IndMagFEMM, "Input": Input, "InputCurrent": InputCurrent, - "InputElec": InputElec, "InputFlux": InputFlux, "InputForce": InputForce, "InputVoltage": InputVoltage, diff --git a/pyleecan/Generator/ClassesRef/Simulation/Input.csv b/pyleecan/Generator/ClassesRef/Simulation/Input.csv index 16bb2efda..546dfbfee 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/Input.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/Input.csv @@ -1,6 +1,6 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -time,s,Electrical time vector (no symmetry) to import,Nt_tot,ImportMatrix,None,,,,Simulation,,gen_input,VERSION,1,Starting data of the simulation -angle,rad,Electrical position vector (no symmetry) to import,Na_tot,ImportMatrix,None,,,,,,comp_axes,,, +time,s,Electrical time vector (no symmetry) to import,Nt_tot,ImportMatrix,None,,,,Simulation,,comp_axes,VERSION,1,Starting data of the simulation +angle,rad,Electrical position vector (no symmetry) to import,Na_tot,ImportMatrix,None,,,,,,,,, Nt_tot,-,Time discretization,0,int,2048,1,,,,,,,, Nrev,-,Number of rotor revolution (to compute the final time),,float,None,0,,,,,,,, Na_tot,-,Angular discretization,,int,2048,1,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/InputElec.csv b/pyleecan/Generator/ClassesRef/Simulation/InputElec.csv deleted file mode 100644 index a9e978ce2..000000000 --- a/pyleecan/Generator/ClassesRef/Simulation/InputElec.csv +++ /dev/null @@ -1,7 +0,0 @@ -Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -rot_dir,-,"Rotation direction of the rotor 1 trigo, -1 clockwise",,float,-1,-1,1,,Simulation,Input,gen_input,VERSION,1,Input to skip the electrical module and start with the magnetic one -Id_ref,A,d-axis current magnitude,1,float,None,,,,,,comp_felec,,, -Iq_ref,A,q-axis current magnitude,1,float,None,,,,,,set_Id_Iq,,, -Ud_ref,V,d-axis voltage magnitude,1,float,None,,,,,,,,, -Uq_ref,V,q-axis voltage magnitude,1,float,None,,,,,,,,, -felec,Hz,electrical frequency,1,float,None,,,,,,,,, \ No newline at end of file diff --git a/pyleecan/Methods/Output/OutElec/get_Nr.py b/pyleecan/Methods/Output/OutElec/get_Nr.py index 90cfd122f..d33752748 100644 --- a/pyleecan/Methods/Output/OutElec/get_Nr.py +++ b/pyleecan/Methods/Output/OutElec/get_Nr.py @@ -29,4 +29,4 @@ def get_Nr(self, Time=None): # Same speed for every timestep Nr = self.N0 * ones(Time.get_length(is_oneperiod=False)) - return Nr \ No newline at end of file + return Nr diff --git a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py index ef2a6e4c9..af7615f9a 100644 --- a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py +++ b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py @@ -50,7 +50,7 @@ def comp_angle_rotor(self, Time): Ar[0] = 0 angle_rotor = Ar + A0 - # Store in time axis normalizations - Time.normalizations["angle_rotor"] = angle_rotor + # Store in time axis normalizations (#TODO array) + Time.normalizations["angle_rotor"] = 1 / (360 * Nr[0] / 60) return angle_rotor diff --git a/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py b/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py index 47619f471..c36523b69 100644 --- a/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py +++ b/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py @@ -4,7 +4,7 @@ from numpy import pi, cumsum, roll, size, ones -def get_angle_rotor(self): +def get_angle_rotor(self, Time=None): """ Return the angular position of the rotor as a function of time and set the Output.elec.angle_rotor attribute if it is None @@ -13,6 +13,8 @@ def get_angle_rotor(self): ---------- self : Output an Output object + Time: Data + a time axis (SciDataTool Data object) Returns ------- @@ -21,30 +23,22 @@ def get_angle_rotor(self): """ - # Already available => Return - if self.elec.angle_rotor is not None and self.elec.angle_rotor.size > 0: - return self.elec.angle_rotor - else: # Compute according to the speed - Nr = self.elec.get_Nr() - - # Get rotor rotating direction - rot_dir = ( - -self.get_rot_dir() - ) # rotor rotating is the opposite of rot_dir which is fundamental field rotation direction so that rotor moves in positive angles - - # Compute rotor initial angle (for synchronous machines, to align rotor d-axis and stator alpha-axis) - A0 = self.get_angle_offset_initial() - - time = self.elec.axes_dict["time"].get_values(is_oneperiod=False) - if time.size == 1: - # Only one time step, no need to compute the position - return ones(1) * A0 + # time axis is not provided -> use elec or mag time axis + if Time is None: + if self.elec.axes_dict is not None and "time" in self.elec.axes_dict: + Time = self.elec.axes_dict["time"] + elif self.mag.axes_dict is not None and "time" in self.mag.axes_dict: + Time = self.mag.axes_dict["time"] else: - deltaT = time[1] - time[0] - # Convert Nr from [rpm] to [rad/s] (time in [s] and angle_rotor in [rad]) - Ar = cumsum(rot_dir * deltaT * Nr * 2 * pi / 60) - # Enforce first position to 0 - Ar = roll(Ar, 1) - Ar[0] = 0 - self.elec.angle_rotor = Ar + A0 - return self.elec.angle_rotor + logger = self.get_logger() + logger.error("No time axis, cannot compute rotor angle") + + # TODO: debug with normalizations as array + if False: # "angle_rotor" in Time.normalizations: + # angle rotor is stored as normalization of Time axis + angle_rotor = Time.normalizations["angle_rotor"] + else: + # compute angle rotor from time axis + angle_rotor = self.comp_angle_rotor(Time) + + return angle_rotor diff --git a/pyleecan/Methods/Simulation/Input/comp_axes.py b/pyleecan/Methods/Simulation/Input/comp_axes.py index 7eab4705e..604efad19 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axes.py +++ b/pyleecan/Methods/Simulation/Input/comp_axes.py @@ -85,11 +85,21 @@ def comp_axes( include_endpoint=False, normalizations=norm_time, ) + # Add time (anti-)periodicity + if per_t > 1 or is_antiper_t: + Time = Time.get_axis_periodic(per_t, is_antiper_t) else: # Load time data time = self.time.get_data() self.Nt_tot = len(time) Time = Data1D(name="time", unit="s", values=time, normalizations=norm_time) + # Add time (anti-)periodicity + sym_t = dict() + if is_antiper_t: + sym_t["antiperiod"] = per_t + else: + sym_t["period"] = per_t + Time.symmetries = sym_t # Create angle axis if self.angle is None: @@ -103,6 +113,10 @@ def comp_axes( include_endpoint=False, normalizations=norm_angle, ) + # Add angle (anti-)periodicity + if per_a > 1 or is_antiper_a: + Angle = Angle.get_axis_periodic(per_a, is_antiper_a) + else: # Load angle data angle = self.angle.get_data() @@ -110,14 +124,13 @@ def comp_axes( Angle = Data1D( name="angle", unit="rad", values=angle, normalizations=norm_angle ) - - # Add time (anti-)periodicity - if per_t > 1 or is_antiper_t: - Time = Time.get_axis_periodic(per_t, is_antiper_t) - - # Add angle (anti-)periodicity - if per_a > 1 or is_antiper_a: - Angle = Angle.get_axis_periodic(per_a, is_antiper_a) + # Add angle (anti-)periodicity + sym_a = dict() + if is_antiper_a: + sym_a["antiperiod"] = per_a + else: + sym_a["period"] = per_a + Angle.symmetries = sym_a # Compute angle_rotor (added to time normalizations) self.parent.parent.comp_angle_rotor(Time) diff --git a/pyleecan/Methods/Simulation/Input/gen_input.py b/pyleecan/Methods/Simulation/Input/gen_input.py deleted file mode 100644 index 6959475c4..000000000 --- a/pyleecan/Methods/Simulation/Input/gen_input.py +++ /dev/null @@ -1,40 +0,0 @@ -from numpy import ndarray - -from ....Classes.OutElec import OutElec -from ....Methods.Simulation.Input import InputError - - -def gen_input(self): - """Generate the input for the electrical module (time/space discretization) - - Parameters - ---------- - self : Input - An Input object - """ - - output = OutElec() - simu = self.parent - - # Replace N0=0 by 0.1 rpm - if self.N0 == 0: - self.N0 = 0.1 - self.get_logger().debug("Updating N0 from 0 [rpm] to 0.1 [rpm]") - self.comp_axes(machine=simu.machine, N0=self.N0) - - # Get the phase number for verifications - if self.parent is None: - raise InputError("Input object should be inside a Simulation object") - - if self.parent.parent is None: - raise InputError( - "Input parent error:" - + " The Simulation object must be in an Output object to run" - ) - - # Create the correct Output object - output = OutElec() - self.comp_axes(machine=simu.machine) - - # Save the Output in the correct place - self.parent.parent.elec = output diff --git a/pyleecan/Methods/Simulation/InputCurrent/comp_felec.py b/pyleecan/Methods/Simulation/InputCurrent/comp_felec.py deleted file mode 100644 index 85cc0bffa..000000000 --- a/pyleecan/Methods/Simulation/InputCurrent/comp_felec.py +++ /dev/null @@ -1,30 +0,0 @@ -from ....Methods.Simulation.Input import InputError - - -def comp_felec(self): - """Compute the electrical frequency - - Parameters - ---------- - self : InputCurrent - An InputCurrent object - """ - - if hasattr(self, "felec") and self.felec is not None: - return self.felec # TODO maybe add some checks? - elif self.N0 is not None: - # Get the phase number for verifications - if self.parent is None: - raise InputError("InputCurrent object should be inside a Simulation object") - # get the pole pair number - if hasattr(self.parent, "machine"): - zp = self.parent.machine.get_pole_pair_number() - elif hasattr(self.parent.parent, "machine"): - zp = self.parent.parent.machine.get_pole_pair_number() - else: - logger = self.get_logger() - logger.warning("Input.comp_felec(): Machine was not found.") - zp = 1 - return self.N0 * zp / (60 * (1 - self.slip_ref)) - else: - raise InputError("InputCurrent object can't have felec and N0 at None") diff --git a/pyleecan/Methods/Simulation/InputElec/__init__.py b/pyleecan/Methods/Simulation/InputElec/__init__.py deleted file mode 100644 index e8315aaa1..000000000 --- a/pyleecan/Methods/Simulation/InputElec/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- - - -class ImportError(Exception): - """Raised when the data import or generation is not possible""" - - pass diff --git a/pyleecan/Methods/Simulation/InputElec/comp_felec.py b/pyleecan/Methods/Simulation/InputElec/comp_felec.py deleted file mode 100644 index f651a65e1..000000000 --- a/pyleecan/Methods/Simulation/InputElec/comp_felec.py +++ /dev/null @@ -1,35 +0,0 @@ -from ....Classes.Simulation import Simulation -from ....Methods.Simulation.Input import InputError - - -def comp_felec(self): - """Compute the electrical frequency - - Parameters - ---------- - felec : float - Electrical frequency [Hz] - """ - if self.felec is not None: - return self.felec - elif self.N0 is not None: - # Get the phase number for verifications - if self.parent is None: - raise InputError( - "ERROR: InputCurrent object should be inside a Simulation object" - ) - # get the simulation - if isinstance(self.parent, Simulation): - simu = self.parent - elif isinstance(self.parent.parent, Simulation): - simu = self.parent.parent - else: - raise InputError("Cannot find InputCurrent simulation.") - - zp = simu.machine.get_pole_pair_number() - if zp is None: - return self.N0 / 60 - else: - return zp * self.N0 / 60 - else: - return 50 diff --git a/pyleecan/Methods/Simulation/InputElec/gen_input.py b/pyleecan/Methods/Simulation/InputElec/gen_input.py deleted file mode 100644 index 757e9cdf9..000000000 --- a/pyleecan/Methods/Simulation/InputElec/gen_input.py +++ /dev/null @@ -1,86 +0,0 @@ -from numpy import ndarray, pi, linspace - -from ....Classes.OutElec import OutElec -from ....Classes.Simulation import Simulation - -from ....Functions.Simulation.create_from_axis import create_from_axis - -from ....Methods.Simulation.Input import InputError - - -def gen_input(self): - """Generate the input for the magnetic module (electrical output) - - Parameters - ---------- - self : InputCurrent - An InputCurrent object - """ - - # get the simulation - if isinstance(self.parent, Simulation): - simu = self.parent - elif isinstance(self.parent.parent, Simulation): - simu = self.parent.parent - else: - raise InputError("InputCurrent object should be inside a Simulation object") - - outelec = OutElec() - outgeo = simu.parent.geo - - outelec.N0 = self.N0 - outelec.felec = self.comp_felec() - - # Set time and angle full axes in geometry output - Time, Angle = self.comp_axes(simu.machine) - outgeo.axes_dict = {"time": Time, "angle": Angle} - - # Create time axis in electrical output without periodicity - # TODO: account for pole periodicity - Time_elec = Time.copy() - Time_elec, _ = create_from_axis( - axis_in=Time, - per=1, # int(2 * simu.machine.get_pole_pair_number()), - is_aper=False, # True, - is_include_per=False, # True, - ) - outelec.axes_dict = {"time": Time_elec} - - # Initialize outelec at None - outelec.Id_ref = None - outelec.Iq_ref = None - outelec.Ud_ref = None - outelec.Uq_ref = None - outelec.Is = None - outelec.Ir = None - - # Load and check voltage and currents - if self.Ud_ref is not None and self.Uq_ref is not None: - outelec.Ud_ref = self.Ud_ref - outelec.Uq_ref = self.Uq_ref - simu.elec.eec.parameters["Ud"] = self.Ud_ref - simu.elec.eec.parameters["Uq"] = self.Uq_ref - if self.Id_ref is not None and self.Iq_ref is not None: - outelec.Id_ref = self.Id_ref - outelec.Iq_ref = self.Iq_ref - else: - outelec.Id_ref = 1 - outelec.Iq_ref = 1 - elif self.Id_ref is not None and self.Iq_ref is not None: - outelec.Id_ref = self.Id_ref - outelec.Iq_ref = self.Iq_ref - else: - raise InputError("Id/Iq or Ud/Uq missing") - - # Load and check rot_dir - if self.rot_dir is None or self.rot_dir not in [-1, 1]: - # Enforce default rotation direction - outgeo.rot_dir = -1 - else: - outgeo.rot_dir = self.rot_dir - - if simu.parent is None: - raise InputError("The Simulation object must be in an outelec object to run") - - # Save the Output in the correct place - simu.parent.elec = outelec diff --git a/pyleecan/Methods/Simulation/InputElec/set_Id_Iq.py b/pyleecan/Methods/Simulation/InputElec/set_Id_Iq.py deleted file mode 100644 index 36fb4e6f7..000000000 --- a/pyleecan/Methods/Simulation/InputElec/set_Id_Iq.py +++ /dev/null @@ -1,12 +0,0 @@ -from numpy import cos, sin - - -def set_Id_Iq(self, I0, Phi0): - """Set Id_ref and Iq_ref according to I0, Phi0""" - - self.Id_ref = I0 * cos(Phi0) - self.Iq_ref = I0 * sin(Phi0) - if abs(self.Id_ref) < 1e-10: - self.Id_ref = 0 - if abs(self.Iq_ref) < 1e-10: - self.Iq_ref = 0 diff --git a/pyleecan/Methods/Simulation/InputFlux/gen_input.py b/pyleecan/Methods/Simulation/InputFlux/gen_input.py index 152ce4427..69cdd868f 100644 --- a/pyleecan/Methods/Simulation/InputFlux/gen_input.py +++ b/pyleecan/Methods/Simulation/InputFlux/gen_input.py @@ -83,9 +83,15 @@ def gen_input(self): out_dict[key] = B_comp - self.time = ImportMatrixVal(value=axes_values["time"]) - self.angle = ImportMatrixVal(value=axes_values["angle"]) + # Create import object for time values + if self.time is None and "time" in axes_values: + self.time = ImportMatrixVal(value=axes_values["time"]) + # Create import object for angle values + if self.angle is None and "angle" in axes_values: + self.angle = ImportMatrixVal(value=axes_values["angle"]) + + # Calculate time and angle axes axes_dict = Input.comp_axes( self, per_a=per_a, diff --git a/pyleecan/Methods/Simulation/InputForce/gen_input.py b/pyleecan/Methods/Simulation/InputForce/gen_input.py index ccfa5e27d..afa93338a 100644 --- a/pyleecan/Methods/Simulation/InputForce/gen_input.py +++ b/pyleecan/Methods/Simulation/InputForce/gen_input.py @@ -1,6 +1,5 @@ -# -*- coding: utf-8 -*- - from ....Classes.OutForce import OutForce +from ....Classes.InputCurrent import InputCurrent from ....Methods.Simulation.Input import InputError @@ -14,15 +13,14 @@ def gen_input(self): """ if self.parent.parent is None: - raise InputError( - "ERROR: The Simulation object must be in an Output object to run" - ) + raise InputError("The Simulation object must be in an Output object to run") if self.AGSF is None and self.AGSF_enforced is None: - raise InputError("ERROR: Input AGSF are missing") + raise InputError("Input AGSF are missing") else: # generate OutElec from parent InputCurrent - super(type(self), self).gen_input() + # Call InputCurrent.gen_input() + InputCurrent.gen_input(self) # generate OutForce outforce = OutForce() diff --git a/pyleecan/Methods/Simulation/InputVoltage/comp_felec.py b/pyleecan/Methods/Simulation/InputVoltage/comp_felec.py index 41714bcd3..3044ea0ae 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/comp_felec.py +++ b/pyleecan/Methods/Simulation/InputVoltage/comp_felec.py @@ -6,25 +6,37 @@ def comp_felec(self): Parameters ---------- - self : InputCurrent - An InputCurrent object + self : InputVoltage + An InputVoltage object """ + name = self.__class__.__name__ + + if self.parent is None: + raise InputError(name + " object should be inside a Simulation object") + + # get the pole pair number + if hasattr(self.parent, "machine"): + p = self.parent.machine.stator.get_pole_pair_number() + elif hasattr(self.parent.parent, "machine"): + p = self.parent.parent.machine.stator.get_pole_pair_number() + else: + logger = self.get_logger() + logger.warning("Input.comp_felec(): Machine was not found.") + p = 1 + if hasattr(self, "felec") and self.felec is not None: - return self.felec # TODO maybe add some checks? + if self.N0 is None: + self.N0 = 60 * (1 - self.slip_ref) * self.felec / p + else: + assert self.felec == self.N0 * p / ( + 60 * (1 - self.slip_ref) + ), "Input speed and frequency are not consistent regarding N0=60*(1-slip)*f_elec/p" + + return self.felec + elif self.N0 is not None: # Get the phase number for verifications - if self.parent is None: - raise InputError("InputCurrent object should be inside a Simulation object") - # get the pole pair number - if hasattr(self.parent, "machine"): - zp = self.parent.machine.stator.get_pole_pair_number() - elif hasattr(self.parent.parent, "machine"): - zp = self.parent.parent.machine.stator.get_pole_pair_number() - else: - logger = self.get_logger() - logger.warning("Input.comp_felec(): Machine was not found.") - zp = 1 - return self.N0 * zp / (60 * (1 - self.slip_ref)) + return self.N0 * p / (60 * (1 - self.slip_ref)) else: - raise InputError("InputCurrent object can't have felec and N0 at None") + raise InputError(name + " object can't have felec and N0 at None") diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 751481dc8..a00ef7464 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -35,13 +35,15 @@ def gen_input(self): output.elec = outelec outgeo = output.geo + # Calculate electrical frequency and/or speed depending on inputs + outelec.felec = self.comp_felec() + # Replace N0=0 by 0.1 rpm if self.N0 == 0: self.N0 = 0.1 self.get_logger().debug("Updating N0 from 0 [rpm] to 0.1 [rpm] in gen_input") outelec.N0 = self.N0 - outelec.felec = self.comp_felec() if self.U0_ref is None and self.Ud_ref and self.Uq_ref: raise Exception("U0_ref, Ud_ref, and Uq_ref cannot be all None in InputVoltage") @@ -148,47 +150,3 @@ def gen_input(self): # ) # outelec.Id_ref = mean(Idq[:, 0]) # outelec.Iq_ref = mean(Idq[:, 1]) # TODO use of mean has to be documented - - # Load and check alpha_rotor and N0 - if self.angle_rotor is None and self.N0 is None: - raise InputError( - "InputVoltage.angle_rotor and InputVoltage.N0 can't be None at the same time" - ) - if self.angle_rotor is not None: - outelec.angle_rotor = self.angle_rotor.get_data() - if ( - not isinstance(outelec.angle_rotor, ndarray) - or len(outelec.angle_rotor.shape) != 1 - or outelec.angle_rotor.size != self.Nt_tot - ): - # angle_rotor should be a vector of same length as time - raise InputError( - "InputVoltage.angle_rotor should be a vector of the same length as time, " - + str(outelec.angle_rotor.shape) - + " shape found, " - + str(self.Nt_tot) - + " expected" - ) - - if self.rot_dir is not None: - if self.rot_dir in [-1, 1]: - # Enforce user-defined rotation direction - outgeo.rot_dir = self.rot_dir - else: - # Enforce calculation of rotation direction - outgeo.rot_dir = None - - if self.angle_rotor_initial is None: - # Enforce default initial position - outelec.angle_rotor_initial = 0 - else: - outelec.angle_rotor_initial = self.angle_rotor_initial - - if self.Tem_av_ref is not None: - outelec.Tem_av_ref = self.Tem_av_ref - - if simu.parent is None: - raise InputError("The Simulation object must be in an Output object to run") - - # Save the Output in the correct place - simu.parent.elec = outelec From 3ce68907fbd6b31473eaee933a02df0a710f33ee Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Fri, 1 Oct 2021 10:39:08 +0200 Subject: [PATCH 037/167] [CC] Update tests with latest changes --- Tests/Simulation/test_post_simu.py | 9 +++++-- Tests/Simulation/test_post_var_simu.py | 24 +++++++++---------- Tests/Validation/Electrical/test_EEC_LSRPM.py | 5 ++-- Tests/Validation/Electrical/test_EEC_PMSM.py | 5 ++-- Tests/Validation/Electrical/test_EEC_SCIM.py | 4 ++-- .../Validation/Electrical/test_skin_effect.py | 16 ++----------- Tests/Validation/Force/test_AGSF_SynRM.py | 1 + .../Validation/Magnetics/test_FEMM_compare.py | 13 ++++++---- .../Magnetics/test_FEMM_slotless.py | 4 ++-- 9 files changed, 39 insertions(+), 42 deletions(-) diff --git a/Tests/Simulation/test_post_simu.py b/Tests/Simulation/test_post_simu.py index 0a3a06d3f..e6e5717d5 100644 --- a/Tests/Simulation/test_post_simu.py +++ b/Tests/Simulation/test_post_simu.py @@ -6,7 +6,7 @@ from pyleecan.Classes.PostFunction import PostFunction from pyleecan.Classes.PostMethod import PostMethod from pyleecan.Classes.Simu1 import Simu1 -from pyleecan.Classes.InputElec import InputElec +from pyleecan.Classes.InputCurrent import InputCurrent class ExamplePostMethod(PostMethod): @@ -24,7 +24,7 @@ def test_post_simu(): # simu1, simu without postprocessing simu1 = Simu1(name="test_post_simu", machine=Toyota_Prius) # Definition of the input - simu1.input = InputElec( + simu1.input = InputCurrent( N0=2000, Id_ref=-100, Iq_ref=200, Nt_tot=10, Na_tot=2048, rot_dir=1 ) @@ -49,3 +49,8 @@ def example_post(output): assert out1.simu.machine.stator.slot.W0 + 2 == out2.simu.machine.stator.slot.W0 assert out1.simu.machine.stator.slot.H0 + 1 == out2.simu.machine.stator.slot.H0 + + +# To run it without pytest +if __name__ == "__main__": + out = test_post_simu() diff --git a/Tests/Simulation/test_post_var_simu.py b/Tests/Simulation/test_post_var_simu.py index c109215b9..aae3381be 100644 --- a/Tests/Simulation/test_post_var_simu.py +++ b/Tests/Simulation/test_post_var_simu.py @@ -1,25 +1,25 @@ import pytest from os.path import join -import sys -from os.path import dirname, abspath, normpath, join, realpath -from os import listdir, remove, system -import json +from copy import copy + from numpy import sqrt -from pyleecan.Functions.load import load -from pyleecan.definitions import DATA_DIR + +from pyleecan.Classes.HoleM51 import HoleM51 +from pyleecan.Classes.HoleM52 import HoleM52 +from pyleecan.Classes.HoleM53 import HoleM53 from pyleecan.Classes.PostFunction import PostFunction from pyleecan.Classes.PostMethod import PostMethod from pyleecan.Classes.Simu1 import Simu1 -from pyleecan.Classes.InputElec import InputElec +from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.VarParam import VarParam from pyleecan.Classes.ParamExplorerSet import ParamExplorerSet from pyleecan.Classes.DataKeeper import DataKeeper -from copy import copy + +from pyleecan.Functions.load import load +from pyleecan.definitions import DATA_DIR + from Tests import TEST_DATA_DIR -from pyleecan.Classes.HoleM51 import HoleM51 -from pyleecan.Classes.HoleM52 import HoleM52 -from pyleecan.Classes.HoleM53 import HoleM53 class ExamplePostMethod(PostMethod): @@ -46,7 +46,7 @@ def test_post_var_simu(): # simu1, simu without postprocessing simu1 = Simu1(name="test_post_simu", machine=Toyota_Prius) # Definition of the input - simu1.input = InputElec( + simu1.input = InputCurrent( N0=2000, Id_ref=-100, Iq_ref=200, Nt_tot=10, Na_tot=2048, rot_dir=1 ) diff --git a/Tests/Validation/Electrical/test_EEC_LSRPM.py b/Tests/Validation/Electrical/test_EEC_LSRPM.py index a1a76fe36..d4cceba93 100644 --- a/Tests/Validation/Electrical/test_EEC_LSRPM.py +++ b/Tests/Validation/Electrical/test_EEC_LSRPM.py @@ -8,7 +8,7 @@ import pytest from pyleecan.Classes.Simu1 import Simu1 -from pyleecan.Classes.InputElec import InputElec +from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.Electrical import Electrical from pyleecan.Classes.EEC_LSRPM import EEC_LSRPM from pyleecan.Classes.FluxLinkFEMM import FluxLinkFEMM @@ -17,7 +17,6 @@ from pyleecan.Classes.Output import Output from pyleecan.Functions.load import load from pyleecan.Functions.Plot import dict_2D -from pyleecan.definitions import DATA_DIR @pytest.mark.long_5s @@ -33,7 +32,7 @@ def test_EEC_PMSM(): simu = Simu1(name="test_EEC_LSRPM", machine=LSRPM) # Definition of the input - simu.input = InputElec(N0=2000, Nt_tot=10, Na_tot=2048) + simu.input = InputCurrent(N0=2000, Nt_tot=10, Na_tot=2048) simu.input.set_Id_Iq(I0=250 / sqrt(2), Phi0=60 * pi / 180) # Define second simu for FEMM comparison diff --git a/Tests/Validation/Electrical/test_EEC_PMSM.py b/Tests/Validation/Electrical/test_EEC_PMSM.py index a999230a1..a14514b9f 100644 --- a/Tests/Validation/Electrical/test_EEC_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_PMSM.py @@ -1,5 +1,4 @@ from os.path import join -from numpy.testing import assert_almost_equal from Tests import save_validation_path as save_path from numpy import sqrt, pi @@ -8,7 +7,7 @@ import pytest from pyleecan.Classes.Simu1 import Simu1 -from pyleecan.Classes.InputElec import InputElec +from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.Electrical import Electrical from pyleecan.Classes.EEC_PMSM import EEC_PMSM from pyleecan.Classes.FluxLinkFEMM import FluxLinkFEMM @@ -35,7 +34,7 @@ def test_EEC_PMSM(): simu = Simu1(name="test_EEC_PMSM", machine=Toyota_Prius) # Definition of the input - simu.input = InputElec(N0=2000, Nt_tot=10, Na_tot=2048) + simu.input = InputCurrent(N0=2000, Nt_tot=10, Na_tot=2048) simu.input.set_Id_Iq(I0=250 / sqrt(2), Phi0=60 * pi / 180) # Define second simu for FEMM comparison diff --git a/Tests/Validation/Electrical/test_EEC_SCIM.py b/Tests/Validation/Electrical/test_EEC_SCIM.py index c2a9279d7..29131ef52 100644 --- a/Tests/Validation/Electrical/test_EEC_SCIM.py +++ b/Tests/Validation/Electrical/test_EEC_SCIM.py @@ -7,7 +7,7 @@ from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.Electrical import Electrical from pyleecan.Classes.EEC_SCIM import EEC_SCIM -from pyleecan.Classes.InputElec import InputElec +from pyleecan.Classes.InputCurrent import InputCurrent from numpy import angle, cos import pytest @@ -59,7 +59,7 @@ def test_EEC_SCIM(): simu.struct = None # Definition of a sinusoidal current - simu.input = InputElec() + simu.input = InputCurrent() simu.input.felec = 50 # [Hz] simu.input.Id_ref = None # [A] simu.input.Iq_ref = None # [A] diff --git a/Tests/Validation/Electrical/test_skin_effect.py b/Tests/Validation/Electrical/test_skin_effect.py index 52b11284a..f434100df 100644 --- a/Tests/Validation/Electrical/test_skin_effect.py +++ b/Tests/Validation/Electrical/test_skin_effect.py @@ -1,30 +1,18 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Apr 28 11:15:05 2021 - -@author: Paul -""" - from os.path import join -from numpy.testing import assert_almost_equal -from Tests import save_validation_path as save_path from numpy import sqrt, pi -from multiprocessing import cpu_count import pytest from pyleecan.Classes.Simu1 import Simu1 -from pyleecan.Classes.InputElec import InputElec +from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.Electrical import Electrical from pyleecan.Classes.EEC_PMSM import EEC_PMSM from pyleecan.Classes.FluxLinkFEMM import FluxLinkFEMM from pyleecan.Classes.IndMagFEMM import IndMagFEMM -from pyleecan.Classes.MagFEMM import MagFEMM from pyleecan.Classes.Output import Output from pyleecan.Functions.load import load -# from pyleecan.Functions.Plot import dict_2D from pyleecan.definitions import DATA_DIR @@ -44,7 +32,7 @@ def test_skin_effect(): simu = Simu1(name="test_skin_effect", machine=Toyota_Prius) # Definition of the input - simu.input = InputElec(N0=2000, Nt_tot=10, Na_tot=2048) + simu.input = InputCurrent(N0=2000, Nt_tot=10, Na_tot=2048) simu.input.set_Id_Iq(I0=250 / sqrt(2), Phi0=60 * pi / 180) # # Define second simu for FEMM comparison diff --git a/Tests/Validation/Force/test_AGSF_SynRM.py b/Tests/Validation/Force/test_AGSF_SynRM.py index ac735f33f..80b1dd6b7 100644 --- a/Tests/Validation/Force/test_AGSF_SynRM.py +++ b/Tests/Validation/Force/test_AGSF_SynRM.py @@ -64,6 +64,7 @@ def test_AGSF_SynRM(): angle_rotor=alpha_rotor, time=time_obj, Na_tot=Na_tot, + Nt_tot=Nt_tot, angle_rotor_initial=0, felec=freq0, ) diff --git a/Tests/Validation/Magnetics/test_FEMM_compare.py b/Tests/Validation/Magnetics/test_FEMM_compare.py index 28d0c096b..a2a523aae 100644 --- a/Tests/Validation/Magnetics/test_FEMM_compare.py +++ b/Tests/Validation/Magnetics/test_FEMM_compare.py @@ -1,4 +1,4 @@ -from numpy import ones, pi, array, linspace, zeros +from numpy import pi, array, linspace, zeros from os.path import join import matplotlib.pyplot as plt from multiprocessing import cpu_count @@ -9,7 +9,6 @@ from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.InputFlux import InputFlux from pyleecan.Classes.ImportMatlab import ImportMatlab -from pyleecan.Classes.ImportData import ImportData from pyleecan.Classes.ImportGenVectLin import ImportGenVectLin from pyleecan.Classes.ImportMatrixVal import ImportMatrixVal from pyleecan.Classes.MagFEMM import MagFEMM @@ -271,7 +270,8 @@ def test_FEMM_compare_SIPMSM(): Is = ImportMatrixVal( value=array([[14.1421, -7.0711, -7.0711], [-14.1421, 7.0711, 7.0711]]) ) - time = ImportGenVectLin(start=0, stop=0.1, num=2, endpoint=True) + Nt_tot = 2 + time = ImportGenVectLin(start=0, stop=0.1, num=Nt_tot, endpoint=True) Na_tot = 1024 Ar = ImportMatrixVal(value=array([2.5219, 0.9511]) + pi / 6) @@ -281,6 +281,7 @@ def test_FEMM_compare_SIPMSM(): N0=N0, angle_rotor=Ar, # Will be computed time=time, + Nt_tot=Nt_tot, Na_tot=Na_tot, angle_rotor_initial=0, ) @@ -305,7 +306,11 @@ def test_FEMM_compare_SIPMSM(): Br = ImportMatlab(file_path=mat_file, var_name="XBr") Bt = ImportMatlab(file_path=mat_file, var_name="XBt") simu_load.input = InputFlux( - time=time, Na_tot=Na_tot, B_dict={"Br": Br, "Bt": Bt}, OP=simu.input.copy() + time=time, + Na_tot=Na_tot, + Nt_tot=Nt_tot, + B_dict={"Br": Br, "Bt": Bt}, + OP=simu.input.copy(), ) out = Output(simu=simu) diff --git a/Tests/Validation/Magnetics/test_FEMM_slotless.py b/Tests/Validation/Magnetics/test_FEMM_slotless.py index 45f8d7852..fcb2c88eb 100644 --- a/Tests/Validation/Magnetics/test_FEMM_slotless.py +++ b/Tests/Validation/Magnetics/test_FEMM_slotless.py @@ -1,11 +1,9 @@ -# -*- coding: utf-8 -*- import pytest from os.path import join from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.MagFEMM import MagFEMM -from pyleecan.Classes.Output import Output from pyleecan.Functions.load import load from pyleecan.definitions import DATA_DIR @@ -43,6 +41,8 @@ def test_FEMM_slotless(): out = simu.run() + return out + # To run it without pytest if __name__ == "__main__": From 8ef80e75d8f273a96fd38b652a49a4c7af8dd842 Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 5 Oct 2021 10:06:41 +0200 Subject: [PATCH 038/167] [WP] integrate new normalization syntax from SciDataTool --- Tests/Plot/test_plots.py | 8 +++++--- .../Electrical/test_EEC_ELUT_SCIM_001.py | 4 ++-- .../Elmer/ElmerResultsVTU/build_meshsolution.py | 4 ++-- .../Machine/LamSlotMultiWind/comp_mmf_unit.py | 4 ++-- .../Methods/Machine/LamSlotWind/comp_mmf_unit.py | 4 ++-- .../Output/Output/getter/comp_angle_rotor.py | 5 +++-- .../Methods/Output/Output/getter/get_fund_harm.py | 4 ++-- pyleecan/Methods/Simulation/Force/comp_axes.py | 13 ++++--------- pyleecan/Methods/Simulation/Input/comp_axes.py | 8 ++++---- .../Methods/Simulation/InputVoltage/gen_input.py | 7 ++----- .../Simulation/MagElmer/get_meshsolution.py | 4 ++-- pyleecan/Methods/Simulation/Magnetics/comp_axes.py | 14 ++++---------- 12 files changed, 34 insertions(+), 45 deletions(-) diff --git a/Tests/Plot/test_plots.py b/Tests/Plot/test_plots.py index 97db72359..97f179a7c 100644 --- a/Tests/Plot/test_plots.py +++ b/Tests/Plot/test_plots.py @@ -3,7 +3,7 @@ import matplotlib.pyplot as plt import pytest -from SciDataTool import DataTime, Data1D, DataLinspace, VectorField +from SciDataTool import DataTime, Data1D, DataLinspace, VectorField, Norm_ref from numpy import linspace, sin, squeeze from Tests import TEST_DATA_DIR @@ -82,7 +82,7 @@ def test_default_proj_Br_time_space(self, import_data): time_arr = squeeze(time.get_data()) angle_arr = squeeze(angle.get_data()) flux_arr = flux.get_data() - norm_angle = {"space_order": 3} + norm_angle = {"space_order": Norm_ref(ref=3)} simu = Simu1(name="test_default_proj_Br_time_space", machine=SCIM_006) simu.mag = None @@ -197,7 +197,9 @@ def test_default_proj_Br_time_space(self, import_data): is_show_fig=False, **dict_2D, ) - out.mag.B.components["radial"].axes[1].normalizations["space_order"] = 3 + out.mag.B.components["radial"].axes[1].normalizations["space_order"] = Norm_ref( + ref=3 + ) out.mag.B.plot_2D_Data( "wavenumber->space_order=[0,100]", data_list=[out3.mag.B], diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py b/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py index 055852a9d..415a63815 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py @@ -22,7 +22,7 @@ from pyleecan.definitions import config_dict from pyleecan.Functions.Plot import dict_2D, dict_3D -from SciDataTool import Data1D, DataLinspace, DataTime, DataFreq, VectorField +from SciDataTool import Data1D, DataLinspace, DataTime, DataFreq, VectorField, Norm_ref color_list = config_dict["PLOT"]["COLOR_DICT"]["CURVE_COLORS"] @@ -161,7 +161,7 @@ def test_EEC_ELUT_SCIM_001(): # name="time", # unit="s", # values=time, - # normalizations={"elec_order": 1188 / 60 * 3}, + # normalizations={"elec_order": Norm_ref(ref=1188 / 60 * 3)}, # ) # MMF_time = DataTime( # symbol="MMF", diff --git a/pyleecan/Methods/Elmer/ElmerResultsVTU/build_meshsolution.py b/pyleecan/Methods/Elmer/ElmerResultsVTU/build_meshsolution.py index 6d4566c8a..fae7dc74d 100644 --- a/pyleecan/Methods/Elmer/ElmerResultsVTU/build_meshsolution.py +++ b/pyleecan/Methods/Elmer/ElmerResultsVTU/build_meshsolution.py @@ -3,7 +3,7 @@ from meshio import read from os.path import join, split, splitext -from SciDataTool import DataTime, Data1D, VectorField +from SciDataTool import DataTime, Data1D, VectorField, Norm_ref from ....Classes.SolutionData import SolutionData from ....Classes.SolutionVector import SolutionVector @@ -87,7 +87,7 @@ def build_meshsolution(self): symbol=self.store_dict[key]["symbol"] + ext, axes=[Indices], values=value[:, i], - normalizations={"ref": self.store_dict[key]["norm"]}, + normalizations={"ref": Norm_ref(ref=self.store_dict[key]["norm"])}, ) components.append(data) comp_name.append("comp_" + ext) diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py index 43dd641f2..7f86100c3 100644 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from numpy import pi, linspace, zeros, ones, dot, squeeze -from SciDataTool import Data1D, DataTime +from SciDataTool import Data1D, DataTime, Norm_ref from ....Functions.Electrical.coordinate_transformation import dq2n from ....Functions.Winding.gen_phase_list import gen_name from pyleecan.Classes.Winding import Winding @@ -65,7 +65,7 @@ def comp_mmf_unit(self, Na=None, Nt=None, freq=1): unit="rad", symmetries={"period": per_a}, values=angle, - normalizations={"space_order": self.get_pole_pair_number()}, + normalizations={"space_order": Norm_ref(ref=self.get_pole_pair_number())}, ) Phase = Data1D( name="phase", diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py index 45a474f5c..26a217be7 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from numpy import pi, linspace, zeros, ones, dot, squeeze -from SciDataTool import Data1D, DataTime +from SciDataTool import Data1D, DataTime, Norm_ref from ....Functions.Electrical.coordinate_transformation import dq2n from ....Functions.Winding.gen_phase_list import gen_name from pyleecan.Classes.Winding import Winding @@ -65,7 +65,7 @@ def comp_mmf_unit(self, Na=None, Nt=None, freq=1): unit="rad", symmetries={"period": per_a}, values=angle, - normalizations={"space_order": self.get_pole_pair_number()}, + normalizations={"space_order": Norm_ref(ref=self.get_pole_pair_number())}, ) Phase = Data1D( name="phase", diff --git a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py index af7615f9a..9d9a4b39f 100644 --- a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py +++ b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py @@ -1,4 +1,5 @@ from numpy import pi, cumsum, roll, ones, unique +from SciDataTool import Norm_vector def comp_angle_rotor(self, Time): @@ -50,7 +51,7 @@ def comp_angle_rotor(self, Time): Ar[0] = 0 angle_rotor = Ar + A0 - # Store in time axis normalizations (#TODO array) - Time.normalizations["angle_rotor"] = 1 / (360 * Nr[0] / 60) + # Store in time axis normalizations + Time.normalizations["angle_rotor"] = Norm_vector(vector=angle_rotor) return angle_rotor diff --git a/pyleecan/Methods/Output/Output/getter/get_fund_harm.py b/pyleecan/Methods/Output/Output/getter/get_fund_harm.py index 62d758f4f..ea6af2f21 100644 --- a/pyleecan/Methods/Output/Output/getter/get_fund_harm.py +++ b/pyleecan/Methods/Output/Output/getter/get_fund_harm.py @@ -66,8 +66,8 @@ def get_fund_harm(self, data): fund_harm[axe_fft] = coeff # Add also normalizations in dict - for key, val in axe.normalizations.items(): - fund_harm[key] = fund_harm[axe_fft] / val + for key, norm in axe.normalizations.items(): + fund_harm[key] = norm.normalize(fund_harm[axe_fft]) # Cannot calculate dict of fundamental harmonic values else: diff --git a/pyleecan/Methods/Simulation/Force/comp_axes.py b/pyleecan/Methods/Simulation/Force/comp_axes.py index 22342b918..f0e518b88 100644 --- a/pyleecan/Methods/Simulation/Force/comp_axes.py +++ b/pyleecan/Methods/Simulation/Force/comp_axes.py @@ -1,6 +1,3 @@ -from ....Functions.Simulation.create_from_axis import create_from_axis - - def comp_axes(self, output): """Compute the axes required in any Force module @@ -42,9 +39,8 @@ def comp_axes(self, output): ) = output.get_machine_periodicity() # Compute Time axis based on the one stored in OutMag and removing anti-periodicty - Time, is_periodicity_t = create_from_axis( - axis_in=axes_dict_mag["time"], - per=per_t, + Time, is_periodicity_t = axes_dict_mag["time"].get_axis_periodic( + Nper=per_t, is_aper=is_antiper_t, is_include_per=is_include_per_t and self.is_periodicity_t, is_remove_aper=True, @@ -63,9 +59,8 @@ def comp_axes(self, output): ) # Compute Angle axis based on the one stored in OutMag and removing anti-periodicty - Angle, is_periodicity_a = create_from_axis( - axis_in=axes_dict_mag["angle"], - per=per_a, + Angle, is_periodicity_a = axes_dict_mag["angle"].get_axis_periodic( + Nper=per_a, is_aper=is_antiper_a, is_include_per=is_include_per_a and self.is_periodicity_a, is_remove_aper=True, diff --git a/pyleecan/Methods/Simulation/Input/comp_axes.py b/pyleecan/Methods/Simulation/Input/comp_axes.py index 604efad19..39d71a904 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axes.py +++ b/pyleecan/Methods/Simulation/Input/comp_axes.py @@ -1,5 +1,5 @@ from numpy import pi -from SciDataTool import Data1D, DataLinspace +from SciDataTool import Data1D, DataLinspace, Norm_ref from ....Methods.Simulation.Input import InputError @@ -62,11 +62,11 @@ def comp_axes( # Setup normalizations for time and angle axes norm_time = { - "elec_order": f_elec, - "mech_order": f_elec / p, + "elec_order": Norm_ref(ref=f_elec), + "mech_order": Norm_ref(ref=f_elec / p), } - norm_angle = {"space_order": p, "distance": 1 / Rag} + norm_angle = {"space_order": Norm_ref(ref=p), "distance": Norm_ref(ref=1 / Rag)} # Create time axis if self.time is None: diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index a00ef7464..3e07350b0 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -3,8 +3,6 @@ from ....Classes.OutElec import OutElec from ....Classes.Simulation import Simulation -from ....Functions.Simulation.create_from_axis import create_from_axis - from ....Methods.Simulation.Input import InputError @@ -96,9 +94,8 @@ def gen_input(self): # Create time axis in electrical output without periodicity # TODO: account for pole periodicity - Time_elec, _ = create_from_axis( - axis_in=axes_dict["time"], - per=1, # int(2 * simu.machine.get_pole_pair_number()), + Time_elec, _ = axes_dict["time"].get_axis_periodic( + Nper=1, # int(2 * simu.machine.get_pole_pair_number()), is_aper=False, # True, is_include_per=False, # True, ) diff --git a/pyleecan/Methods/Simulation/MagElmer/get_meshsolution.py b/pyleecan/Methods/Simulation/MagElmer/get_meshsolution.py index f537e4491..592e67b3f 100644 --- a/pyleecan/Methods/Simulation/MagElmer/get_meshsolution.py +++ b/pyleecan/Methods/Simulation/MagElmer/get_meshsolution.py @@ -3,7 +3,7 @@ from ....Classes.SolutionData import SolutionData from ....Classes.SolutionVector import SolutionVector from meshio import read -from SciDataTool import Data1D, VectorField, DataTime +from SciDataTool import Data1D, VectorField, DataTime, Norm_ref from numpy import arange, append as np_append from os.path import join @@ -122,7 +122,7 @@ def get_meshsolution(self, output): axes=[Indices], # values=value[:, i], values=values[:, i], - normalizations={"ref": store_dict[key]["norm"]}, + normalizations={"ref": Norm_ref(ref=store_dict[key]["norm"])}, ) components.append(data) diff --git a/pyleecan/Methods/Simulation/Magnetics/comp_axes.py b/pyleecan/Methods/Simulation/Magnetics/comp_axes.py index 13e44f63a..76b36a833 100644 --- a/pyleecan/Methods/Simulation/Magnetics/comp_axes.py +++ b/pyleecan/Methods/Simulation/Magnetics/comp_axes.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- -from ....Functions.Simulation.create_from_axis import create_from_axis - - def comp_axes(self, output): """Compute the axes required in any Magnetics module @@ -32,9 +28,8 @@ def comp_axes(self, output): ) = output.get_machine_periodicity() # Compute Time axis based on the one stored in OutElec - Time, is_periodicity_t = create_from_axis( - axis_in=axes_dict_geo["time"], - per=per_t, + Time, is_periodicity_t = axes_dict_geo["time"].get_axis_periodic( + Nper=per_t, is_aper=is_antiper_t, is_include_per=self.is_periodicity_t, is_remove_aper=False, @@ -53,9 +48,8 @@ def comp_axes(self, output): ) # Compute Angle axis based on the one stored in OutElec - Angle, is_periodicity_a = create_from_axis( - axis_in=axes_dict_geo["angle"], - per=per_a, + Angle, is_periodicity_a = axes_dict_geo["angle"].get_axis_periodic( + Nper=per_a, is_aper=is_antiper_a, is_include_per=self.is_periodicity_a, is_remove_aper=False, From d895f5359042c3227b5acf8b431a58fa1f2ec301 Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 5 Oct 2021 10:31:38 +0200 Subject: [PATCH 039/167] [BC] axes_dict is now stored in Outgeo --- Tests/Methods/Simulation/test_InCurrent_meth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Methods/Simulation/test_InCurrent_meth.py b/Tests/Methods/Simulation/test_InCurrent_meth.py index 6d1ffb0c3..35d4099af 100644 --- a/Tests/Methods/Simulation/test_InCurrent_meth.py +++ b/Tests/Methods/Simulation/test_InCurrent_meth.py @@ -255,11 +255,11 @@ def test_InputCurrent_DQ(self, test_dict): # Generate Is according to Id/Iq test_obj.input.gen_input() assert_array_almost_equal( - output.elec.axes_dict["time"].get_values(is_oneperiod=False), + output.geo.axes_dict["time"].get_values(is_oneperiod=False), time_exp, ) assert_array_almost_equal( - output.elec.axes_dict["angle"].get_values(is_oneperiod=False), + output.geo.axes_dict["angle"].get_values(is_oneperiod=False), linspace(0, 2 * pi, Na_tot, endpoint=False), ) assert_array_almost_equal(output.elec.get_Is().values, Is_exp) From b6b00d9e49938bd88a3cc06bffbafbff3d8b2fe2 Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 5 Oct 2021 11:27:55 +0200 Subject: [PATCH 040/167] [BC] fix Nslot_shift_wind value --- pyleecan/Data/Machine/SPMSM_002.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyleecan/Data/Machine/SPMSM_002.json b/pyleecan/Data/Machine/SPMSM_002.json index 1109fedaa..36b116d5b 100644 --- a/pyleecan/Data/Machine/SPMSM_002.json +++ b/pyleecan/Data/Machine/SPMSM_002.json @@ -601,7 +601,7 @@ "Lewout": 0, "Nlayer": 1, "Npcp": 1, - "Nslot_shift_wind": 1, + "Nslot_shift_wind": 0, "Ntcoil": 1, "__class__": "Winding", "coil_pitch": 0, From cc5fb5bbb2ed266f2808e757bdb8a17364943bb3 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Tue, 5 Oct 2021 11:33:09 +0200 Subject: [PATCH 041/167] [BC] Correct tests + InputFlux now inherits from InputCurrent --- .../Validation/Magnetics/test_FEMM_compare.py | 71 +++--- pyleecan/Classes/Class_Dict.json | 30 ++- pyleecan/Classes/InputFlux.py | 205 +++++++++++++----- .../ClassesRef/Simulation/InputFlux.csv | 9 +- .../Machine/LamSlotWind/comp_angle_d_axis.py | 23 +- .../Output/getter/get_angle_offset_initial.py | 5 +- .../Methods/Simulation/InputFlux/gen_input.py | 2 +- 7 files changed, 234 insertions(+), 111 deletions(-) diff --git a/Tests/Validation/Magnetics/test_FEMM_compare.py b/Tests/Validation/Magnetics/test_FEMM_compare.py index a2a523aae..43b6a045e 100644 --- a/Tests/Validation/Magnetics/test_FEMM_compare.py +++ b/Tests/Validation/Magnetics/test_FEMM_compare.py @@ -12,7 +12,6 @@ from pyleecan.Classes.ImportGenVectLin import ImportGenVectLin from pyleecan.Classes.ImportMatrixVal import ImportMatrixVal from pyleecan.Classes.MagFEMM import MagFEMM -from pyleecan.Classes.Output import Output from pyleecan.Functions.load import load from pyleecan.Functions.Plot import dict_2D from pyleecan.definitions import DATA_DIR @@ -64,11 +63,9 @@ def test_FEMM_compare_IPMSM_xxx(): simu_sym = Simu1(init_dict=simu.as_dict()) simu_sym.mag.is_periodicity_a = True - out = Output(simu=simu) - simu.run() + out = simu.run() - out2 = Output(simu=simu_sym) - simu_sym.run() + out2 = simu_sym.run() # Plot the result by comparing the two simulation plt.close("all") @@ -82,6 +79,7 @@ def test_FEMM_compare_IPMSM_xxx(): ) +@pytest.mark.long_5s @pytest.mark.MagFEMM @pytest.mark.IPMSM @pytest.mark.periodicity @@ -132,11 +130,9 @@ def test_FEMM_compare_Prius(): simu_sym = Simu1(init_dict=simu.as_dict()) simu_sym.mag.is_periodicity_a = True - out = Output(simu=simu) - simu.run() + out = simu.run() - out2 = Output(simu=simu_sym) - simu_sym.run() + out2 = simu_sym.run() # Plot the result by comparing the two simulation plt.close("all") @@ -209,17 +205,19 @@ def test_FEMM_compare_SCIM(): Br = ImportMatlab(file_path=mat_file, var_name="XBr") angle2 = ImportGenVectLin(start=0, stop=pi, num=4096 / 2, endpoint=False) simu_load.input = InputFlux( - time=time, angle=angle2, B_dict={"Br": Br}, OP=simu.input.copy() + time=time, + angle=angle2, + B_dict={"Br": Br}, + Is=Is, + Ir=Ir, + N0=simu.input.N0, ) - out = Output(simu=simu) - simu.run() + out = simu.run() - out2 = Output(simu=simu_sym) - simu_sym.run() + out2 = simu_sym.run() - out3 = Output(simu=simu_load) - simu_load.run() + out3 = simu_load.run() # Plot the result by comparing the two simulation (sym / no sym) plt.close("all") @@ -247,6 +245,7 @@ def test_FEMM_compare_SCIM(): ) +@pytest.mark.long_5s @pytest.mark.MagFEMM @pytest.mark.SIPMSM @pytest.mark.periodicity @@ -310,14 +309,14 @@ def test_FEMM_compare_SIPMSM(): Na_tot=Na_tot, Nt_tot=Nt_tot, B_dict={"Br": Br, "Bt": Bt}, - OP=simu.input.copy(), + Is=Is, + Ir=None, # No winding on the rotor + N0=simu.input.N0, ) - out = Output(simu=simu) - simu.run() + out = simu.run() - out3 = Output(simu=simu_load) - simu_load.run() + out3 = simu_load.run() # Plot the result by comparing the two simulation (no sym / MANATEE SDM) plt.close("all") @@ -394,16 +393,18 @@ def test_SPMSM_load(): Br = ImportMatlab(file_path=mat_file, var_name="XBr") Bt = ImportMatlab(file_path=mat_file, var_name="XBt") simu_load.input = InputFlux( - time=time, Na_tot=Na_tot, B_dict={"Br": Br, "Bt": Bt}, OP=simu.input.copy() + time=time, + Na_tot=Na_tot, + B_dict={"Br": Br, "Bt": Bt}, + Is=Is, + Ir=None, # No winding on the rotor + N0=simu.input.N0, ) - out = Output(simu=simu) - simu.run() + out = simu.run() - out2 = Output(simu=simu_sym) - simu_sym.run() + out2 = simu_sym.run() - out3 = Output(simu=simu_load) - simu_load.run() + out3 = simu_load.run() # Plot the result by comparing the two simulation (sym / no sym) plt.close("all") @@ -430,6 +431,7 @@ def test_SPMSM_load(): ) +@pytest.mark.long_5s @pytest.mark.MagFEMM @pytest.mark.SPMSM @pytest.mark.periodicity @@ -483,14 +485,17 @@ def test_SPMSM_noload(): Bt = ImportMatlab(file_path=mat_file, var_name="Bt") angle2 = ImportGenVectLin(start=0, stop=2 * pi / 9, num=2048 / 9, endpoint=False) simu_load.input = InputFlux( - time=time, angle=angle2, B_dict={"Br": Br, "Bt": Bt}, OP=simu.input.copy() + time=time, + angle=angle2, + B_dict={"Br": Br, "Bt": Bt}, + Is=Is, + Ir=None, # No winding on the rotor + N0=simu.input.N0, ) - out = Output(simu=simu) - simu.run() + out = simu.run() - out3 = Output(simu=simu_load) - simu_load.run() + out3 = simu_load.run() plt.close("all") diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index dec2afb6b..765c6251a 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4186,7 +4186,9 @@ "value": "1" } ], - "daughters": [], + "daughters": [ + "InputFlux" + ], "desc": "Input to skip the electrical module and start with the magnetic one", "is_internal": false, "methods": [ @@ -4250,7 +4252,7 @@ "methods": [ "gen_input" ], - "mother": "Input", + "mother": "InputCurrent", "name": "InputFlux", "package": "Simulation", "path": "pyleecan/Generator/ClassesRef/Simulation/InputFlux.csv", @@ -4280,7 +4282,7 @@ "name": "is_antiper_a", "type": "bool", "unit": "-", - "value": false + "value": 0 }, { "desc": "If time is antiperiodic", @@ -4289,7 +4291,7 @@ "name": "is_antiper_t", "type": "bool", "unit": "-", - "value": false + "value": 0 }, { "desc": "Dict of Import objects or lists for each component of the flux", @@ -4310,13 +4312,22 @@ "value": "None" }, { - "desc": "InputCurrent to define Operating Point (not mandatory)", + "desc": "Slice axis values", "max": "", "min": "", - "name": "OP", - "type": "Input", - "unit": "-", + "name": "slice", + "type": "ndarray", + "unit": "m", "value": null + }, + { + "desc": "Airgap flux density as VectorField object", + "max": "", + "min": "", + "name": "B_enforced", + "type": "SciDataTool.Classes.VectorField.VectorField", + "unit": "", + "value": "None" } ] }, @@ -4357,7 +4368,8 @@ } ], "daughters": [ - "InputCurrent" + "InputCurrent", + "InputFlux" ], "desc": "Input to start the electrical module with voltage input", "is_internal": false, diff --git a/pyleecan/Classes/InputFlux.py b/pyleecan/Classes/InputFlux.py index 2a69d03d7..a86a3e5e6 100644 --- a/pyleecan/Classes/InputFlux.py +++ b/pyleecan/Classes/InputFlux.py @@ -7,13 +7,13 @@ from os import linesep from sys import getsizeof from logging import getLogger -from ._check import check_var, raise_ +from ._check import set_array, check_var, raise_ from ..Functions.get_logger import get_logger from ..Functions.save import save from ..Functions.copy import copy from ..Functions.load import load_init_dict from ..Functions.Load.import_class import import_class -from .Input import Input +from .InputCurrent import InputCurrent # Import all class method # Try/catch to remove unnecessary dependencies in unused method @@ -27,11 +27,11 @@ from numpy import ndarray from numpy import array, array_equal from ._check import InitUnKnowClassError -from .Input import Input from .ImportMatrix import ImportMatrix +from .Import import Import -class InputFlux(Input): +class InputFlux(InputCurrent): """Input to skip the magnetic module and start with the structural one""" VERSION = 1 @@ -59,7 +59,22 @@ def __init__( is_antiper_t=False, B_dict=None, unit=None, - OP=None, + slice=None, + B_enforced=None, + Is=None, + Ir=None, + Id_ref=None, + Iq_ref=None, + angle_rotor=None, + rot_dir=None, + angle_rotor_initial=0, + Tem_av_ref=None, + Ud_ref=None, + Uq_ref=None, + felec=None, + slip_ref=0, + U0_ref=None, + Pem_av_ref=None, time=None, angle=None, Nt_tot=2048, @@ -96,8 +111,38 @@ def __init__( B_dict = init_dict["B_dict"] if "unit" in list(init_dict.keys()): unit = init_dict["unit"] - if "OP" in list(init_dict.keys()): - OP = init_dict["OP"] + if "slice" in list(init_dict.keys()): + slice = init_dict["slice"] + if "B_enforced" in list(init_dict.keys()): + B_enforced = init_dict["B_enforced"] + if "Is" in list(init_dict.keys()): + Is = init_dict["Is"] + if "Ir" in list(init_dict.keys()): + Ir = init_dict["Ir"] + if "Id_ref" in list(init_dict.keys()): + Id_ref = init_dict["Id_ref"] + if "Iq_ref" in list(init_dict.keys()): + Iq_ref = init_dict["Iq_ref"] + if "angle_rotor" in list(init_dict.keys()): + angle_rotor = init_dict["angle_rotor"] + if "rot_dir" in list(init_dict.keys()): + rot_dir = init_dict["rot_dir"] + if "angle_rotor_initial" in list(init_dict.keys()): + angle_rotor_initial = init_dict["angle_rotor_initial"] + if "Tem_av_ref" in list(init_dict.keys()): + Tem_av_ref = init_dict["Tem_av_ref"] + if "Ud_ref" in list(init_dict.keys()): + Ud_ref = init_dict["Ud_ref"] + if "Uq_ref" in list(init_dict.keys()): + Uq_ref = init_dict["Uq_ref"] + if "felec" in list(init_dict.keys()): + felec = init_dict["felec"] + if "slip_ref" in list(init_dict.keys()): + slip_ref = init_dict["slip_ref"] + if "U0_ref" in list(init_dict.keys()): + U0_ref = init_dict["U0_ref"] + if "Pem_av_ref" in list(init_dict.keys()): + Pem_av_ref = init_dict["Pem_av_ref"] if "time" in list(init_dict.keys()): time = init_dict["time"] if "angle" in list(init_dict.keys()): @@ -117,19 +162,39 @@ def __init__( self.is_antiper_t = is_antiper_t self.B_dict = B_dict self.unit = unit - self.OP = OP - # Call Input init + self.slice = slice + self.B_enforced = B_enforced + # Call InputCurrent init super(InputFlux, self).__init__( - time=time, angle=angle, Nt_tot=Nt_tot, Nrev=Nrev, Na_tot=Na_tot, N0=N0 + Is=Is, + Ir=Ir, + Id_ref=Id_ref, + Iq_ref=Iq_ref, + angle_rotor=angle_rotor, + rot_dir=rot_dir, + angle_rotor_initial=angle_rotor_initial, + Tem_av_ref=Tem_av_ref, + Ud_ref=Ud_ref, + Uq_ref=Uq_ref, + felec=felec, + slip_ref=slip_ref, + U0_ref=U0_ref, + Pem_av_ref=Pem_av_ref, + time=time, + angle=angle, + Nt_tot=Nt_tot, + Nrev=Nrev, + Na_tot=Na_tot, + N0=N0, ) - # The class is frozen (in Input init), for now it's impossible to + # The class is frozen (in InputCurrent init), for now it's impossible to # add new properties def __str__(self): """Convert this object in a readeable string (for print)""" InputFlux_str = "" - # Get the properties inherited from Input + # Get the properties inherited from InputCurrent InputFlux_str += super(InputFlux, self).__str__() InputFlux_str += "per_a = " + str(self.per_a) + linesep InputFlux_str += "per_t = " + str(self.per_t) + linesep @@ -137,11 +202,14 @@ def __str__(self): InputFlux_str += "is_antiper_t = " + str(self.is_antiper_t) + linesep InputFlux_str += "B_dict = " + str(self.B_dict) + linesep InputFlux_str += 'unit = "' + str(self.unit) + '"' + linesep - if self.OP is not None: - tmp = self.OP.__str__().replace(linesep, linesep + "\t").rstrip("\t") - InputFlux_str += "OP = " + tmp - else: - InputFlux_str += "OP = None" + linesep + linesep + InputFlux_str += ( + "slice = " + + linesep + + str(self.slice).replace(linesep, linesep + "\t") + + linesep + + linesep + ) + InputFlux_str += "B_enforced = " + str(self.B_enforced) + linesep + linesep return InputFlux_str def __eq__(self, other): @@ -150,7 +218,7 @@ def __eq__(self, other): if type(other) != type(self): return False - # Check the properties inherited from Input + # Check the properties inherited from InputCurrent if not super(InputFlux, self).__eq__(other): return False if other.per_a != self.per_a: @@ -165,7 +233,9 @@ def __eq__(self, other): return False if other.unit != self.unit: return False - if other.OP != self.OP: + if not array_equal(other.slice, self.slice): + return False + if other.B_enforced != self.B_enforced: return False return True @@ -178,7 +248,7 @@ def compare(self, other, name="self", ignore_list=None): return ["type(" + name + ")"] diff_list = list() - # Check the properties inherited from Input + # Check the properties inherited from InputCurrent diff_list.extend(super(InputFlux, self).compare(other, name=name)) if other._per_a != self._per_a: diff_list.append(name + ".per_a") @@ -192,12 +262,16 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".B_dict") if other._unit != self._unit: diff_list.append(name + ".unit") - if (other.OP is None and self.OP is not None) or ( - other.OP is not None and self.OP is None + if not array_equal(other.slice, self.slice): + diff_list.append(name + ".slice") + if (other.B_enforced is None and self.B_enforced is not None) or ( + other.B_enforced is not None and self.B_enforced is None ): - diff_list.append(name + ".OP None mismatch") - elif self.OP is not None: - diff_list.extend(self.OP.compare(other.OP, name=name + ".OP")) + diff_list.append(name + ".B_enforced None mismatch") + elif self.B_enforced is not None: + diff_list.extend( + self.B_enforced.compare(other.B_enforced, name=name + ".B_enforced") + ) # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -207,7 +281,7 @@ def __sizeof__(self): S = 0 # Full size of the object - # Get size of the properties inherited from Input + # Get size of the properties inherited from InputCurrent S += super(InputFlux, self).__sizeof__() S += getsizeof(self.per_a) S += getsizeof(self.per_t) @@ -217,7 +291,8 @@ def __sizeof__(self): for key, value in self.B_dict.items(): S += getsizeof(value) + getsizeof(key) S += getsizeof(self.unit) - S += getsizeof(self.OP) + S += getsizeof(self.slice) + S += getsizeof(self.B_enforced) return S def as_dict(self, **kwargs): @@ -227,7 +302,7 @@ def as_dict(self, **kwargs): and may prevent json serializability. """ - # Get the properties inherited from Input + # Get the properties inherited from InputCurrent InputFlux_dict = super(InputFlux, self).as_dict(**kwargs) InputFlux_dict["per_a"] = self.per_a InputFlux_dict["per_t"] = self.per_t @@ -237,10 +312,14 @@ def as_dict(self, **kwargs): self.B_dict.copy() if self.B_dict is not None else None ) InputFlux_dict["unit"] = self.unit - if self.OP is None: - InputFlux_dict["OP"] = None + if self.slice is None: + InputFlux_dict["slice"] = None + else: + InputFlux_dict["slice"] = self.slice.tolist() + if self.B_enforced is None: + InputFlux_dict["B_enforced"] = None else: - InputFlux_dict["OP"] = self.OP.as_dict(**kwargs) + InputFlux_dict["B_enforced"] = self.B_enforced.as_dict() # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name InputFlux_dict["__class__"] = "InputFlux" @@ -255,9 +334,9 @@ def _set_None(self): self.is_antiper_t = None self.B_dict = None self.unit = None - if self.OP is not None: - self.OP._set_None() - # Set to None the properties inherited from Input + self.slice = None + self.B_enforced = None + # Set to None the properties inherited from InputCurrent super(InputFlux, self)._set_None() def _get_per_a(self): @@ -370,30 +449,54 @@ def _set_unit(self, value): """, ) - def _get_OP(self): - """getter of OP""" - return self._OP + def _get_slice(self): + """getter of slice""" + return self._slice + + def _set_slice(self, value): + """setter of slice""" + if type(value) is int and value == -1: + value = array([]) + elif type(value) is list: + try: + value = array(value) + except: + pass + check_var("slice", value, "ndarray") + self._slice = value + + slice = property( + fget=_get_slice, + fset=_set_slice, + doc=u"""Slice axis values + + :Type: ndarray + """, + ) + + def _get_B_enforced(self): + """getter of B_enforced""" + return self._B_enforced - def _set_OP(self, value): - """setter of OP""" + def _set_B_enforced(self, value): + """setter of B_enforced""" if isinstance(value, str): # Load from file value = load_init_dict(value)[1] if isinstance(value, dict) and "__class__" in value: - class_obj = import_class("pyleecan.Classes", value.get("__class__"), "OP") + class_obj = import_class( + "SciDataTool.Classes", value.get("__class__"), "B_enforced" + ) value = class_obj(init_dict=value) elif type(value) is int and value == -1: # Default constructor - value = Input() - check_var("OP", value, "Input") - self._OP = value - - if self._OP is not None: - self._OP.parent = self + value = VectorField() + check_var("B_enforced", value, "VectorField") + self._B_enforced = value - OP = property( - fget=_get_OP, - fset=_set_OP, - doc=u"""InputCurrent to define Operating Point (not mandatory) + B_enforced = property( + fget=_get_B_enforced, + fset=_set_B_enforced, + doc=u"""Airgap flux density as VectorField object - :Type: Input + :Type: SciDataTool.Classes.VectorField.VectorField """, ) diff --git a/pyleecan/Generator/ClassesRef/Simulation/InputFlux.csv b/pyleecan/Generator/ClassesRef/Simulation/InputFlux.csv index ae9060b26..546398ec8 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/InputFlux.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/InputFlux.csv @@ -1,8 +1,9 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -per_a,-,Angle periodicity,,int,1,,,,Simulation,Input,gen_input,VERSION,1,Input to skip the magnetic module and start with the structural one +per_a,-,Angle periodicity,,int,1,,,,Simulation,InputCurrent,gen_input,VERSION,1,Input to skip the magnetic module and start with the structural one per_t,-,Time periodicity,,int,1,,,,,,,,, -is_antiper_a,-,If angle is antiperiodic,,bool,False,,,,,,,,, -is_antiper_t,-,If time is antiperiodic,,bool,False,,,,,,,,, +is_antiper_a,-,If angle is antiperiodic,,bool,0,,,,,,,,, +is_antiper_t,-,If time is antiperiodic,,bool,0,,,,,,,,, B_dict,-,Dict of Import objects or lists for each component of the flux,,dict,None,,,,,,,,, unit,-,Unit of the flux if not T,,str,None,,,,,,,,, -OP,-,InputCurrent to define Operating Point (not mandatory),,Input,None,,,,,,,,, +slice,m,Slice axis values,,ndarray,None,,,,,,,,, +B_enforced,,Airgap flux density as VectorField object,,SciDataTool.Classes.VectorField.VectorField,None,,,,,,,,, diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_angle_d_axis.py b/pyleecan/Methods/Machine/LamSlotWind/comp_angle_d_axis.py index bcb58ef41..8c0cdf34e 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_angle_d_axis.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_angle_d_axis.py @@ -1,7 +1,7 @@ from numpy import argmax, cos, abs as np_abs, angle as np_angle -def comp_angle_d_axis(self): +def comp_angle_d_axis(self, is_plot=False): """Compute the angle between the X axis and the first d+ axis By convention a "Tooth" is centered on the X axis @@ -9,6 +9,8 @@ def comp_angle_d_axis(self): ---------- self : LamSlotWind A LamSlotWind object + is_plot : bool + True to plot d axis position regarding unit mmf Returns ------- @@ -43,13 +45,16 @@ def comp_angle_d_axis(self): # Get the first angle where mmf is max d_angle = angle_stator[argmax(mmf_waveform)] - # import matplotlib.pyplot as plt - # from numpy import squeeze - - # fig, ax = plt.subplots() - # ax.plot(angle_stator, squeeze(MMF.get_along("angle[oneperiod]")[MMF.symbol]), "k") - # ax.plot(angle_stator, mmf_waveform, "r") - # ax.plot([d_angle, d_angle], [-magmax, magmax], "--k") - # plt.show() + if is_plot: + import matplotlib.pyplot as plt + from numpy import squeeze + + fig, ax = plt.subplots() + ax.plot( + angle_stator, squeeze(MMF.get_along("angle[oneperiod]")[MMF.symbol]), "k" + ) + ax.plot(angle_stator, mmf_waveform, "r") + ax.plot([d_angle, d_angle], [-magmax, magmax], "--k") + plt.show() return d_angle diff --git a/pyleecan/Methods/Output/Output/getter/get_angle_offset_initial.py b/pyleecan/Methods/Output/Output/getter/get_angle_offset_initial.py index 7f436f8eb..ebf8d2091 100644 --- a/pyleecan/Methods/Output/Output/getter/get_angle_offset_initial.py +++ b/pyleecan/Methods/Output/Output/getter/get_angle_offset_initial.py @@ -17,10 +17,7 @@ def get_angle_offset_initial(self): """ # Already available => Return - if ( - self.geo.angle_offset_initial is not None - and self.geo.angle_offset_initial.size > 0 - ): + if self.geo.angle_offset_initial is not None: return self.geo.angle_offset_initial else: # Compute self.geo.angle_offset_initial = self.simu.machine.comp_angle_offset_initial() diff --git a/pyleecan/Methods/Simulation/InputFlux/gen_input.py b/pyleecan/Methods/Simulation/InputFlux/gen_input.py index 69cdd868f..113b70f8b 100644 --- a/pyleecan/Methods/Simulation/InputFlux/gen_input.py +++ b/pyleecan/Methods/Simulation/InputFlux/gen_input.py @@ -1,4 +1,4 @@ -from numpy import arange, pi, array +from numpy import arange, array from SciDataTool import DataPattern From 73e3228e9c38a14cddad4a1ab2d1cd5cc2e41ed4 Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 5 Oct 2021 15:59:16 +0200 Subject: [PATCH 042/167] [BC] fix tolerance in test (more accurate) --- Tests/Validation/Force/test_AGSF_spectrum.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Tests/Validation/Force/test_AGSF_spectrum.py b/Tests/Validation/Force/test_AGSF_spectrum.py index 5a996a511..e7995d1bc 100644 --- a/Tests/Validation/Force/test_AGSF_spectrum.py +++ b/Tests/Validation/Force/test_AGSF_spectrum.py @@ -181,9 +181,8 @@ def test_IPMSM_AGSF_spectrum_sym(): Prad_wr[ifrq, ir] * exp(1j * 2 * pi * frq * Xtime + 1j * r * Xangle) ) - # assert_array_almost_equal(XP_rad1, Prad, decimal=3) - test = abs(XP_rad1 - Prad) / mean(XP_rad1) - assert_array_almost_equal(test, 0, decimal=1) # Less than 10% error + test = abs(XP_rad1 - Prad) / XP_rad1 + assert_array_almost_equal(test, 0, decimal=5) return out From 4dffc3339cad766537b2d7bff2c037ed8dd6b124 Mon Sep 17 00:00:00 2001 From: helene-t Date: Wed, 6 Oct 2021 09:51:47 +0200 Subject: [PATCH 043/167] [CO] activate normalization to compute angle rotor --- pyleecan/Methods/Output/Output/getter/get_angle_rotor.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py b/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py index c36523b69..68a891c96 100644 --- a/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py +++ b/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py @@ -33,10 +33,9 @@ def get_angle_rotor(self, Time=None): logger = self.get_logger() logger.error("No time axis, cannot compute rotor angle") - # TODO: debug with normalizations as array - if False: # "angle_rotor" in Time.normalizations: + if "angle_rotor" in Time.normalizations: # angle rotor is stored as normalization of Time axis - angle_rotor = Time.normalizations["angle_rotor"] + angle_rotor = Time.get_values(normalization="angle_rotor") else: # compute angle rotor from time axis angle_rotor = self.comp_angle_rotor(Time) From dd21b3f4e6d52f0b18c9c6e15a9c9970f2b3e3b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl?= Date: Wed, 6 Oct 2021 11:38:59 +0200 Subject: [PATCH 044/167] [CO] Precompute the current before comp_flux_airgap --- pyleecan/Methods/Output/OutElec/comp_I_mag.py | 20 ++++-- pyleecan/Methods/Output/OutElec/get_I_fund.py | 70 +++++++++++++++++++ pyleecan/Methods/Output/OutElec/get_I_harm.py | 37 ++++++++++ pyleecan/Methods/Output/OutElec/get_Is.py | 31 ++------ .../Simulation/MagElmer/comp_flux_airgap.py | 14 +--- .../Simulation/MagFEMM/comp_flux_airgap.py | 11 +-- pyleecan/Methods/Simulation/Magnetics/run.py | 14 +++- 7 files changed, 140 insertions(+), 57 deletions(-) create mode 100644 pyleecan/Methods/Output/OutElec/get_I_fund.py create mode 100644 pyleecan/Methods/Output/OutElec/get_I_harm.py diff --git a/pyleecan/Methods/Output/OutElec/comp_I_mag.py b/pyleecan/Methods/Output/OutElec/comp_I_mag.py index 4495d0bbc..1ba6f6389 100644 --- a/pyleecan/Methods/Output/OutElec/comp_I_mag.py +++ b/pyleecan/Methods/Output/OutElec/comp_I_mag.py @@ -2,7 +2,7 @@ from numpy import array -def comp_I_mag(self, time, is_stator, phase=None): +def comp_I_mag(self, Time, is_stator, phase=None, I_data=None, is_periodicity_t=True): """Compute the current on the given lamination and time vector to use it in Magnetics model Phase currents are divided by the number of parallel circuits per pole and per phase to account for actual current in slot conductors @@ -11,7 +11,7 @@ def comp_I_mag(self, time, is_stator, phase=None): ---------- self : OutElec an OutElec object - time : ndarray + Time : Data1D Time vector on which to interpolate currents stored in OutElec is_stator: bool True if lamination is stator @@ -23,6 +23,13 @@ def comp_I_mag(self, time, is_stator, phase=None): I: ndarray Current matrix accounting for periodicities [q_pera,len(time)] """ + _, is_antiper_t = Time.get_periodicity() + + # Number of time steps + time = Time.get_values( + is_oneperiod=is_periodicity_t, + is_antiperiod=is_antiper_t and is_periodicity_t, + ) # Get lamination if is_stator: @@ -43,10 +50,11 @@ def comp_I_mag(self, time, is_stator, phase=None): Npcp = 1 # Get current DataTime - if is_stator: - I_data = self.get_Is() - else: - I_data = self.Ir + if I_data is None: + if is_stator: + I_data = self.get_Is() + else: + I_data = self.Ir if phase is None: # Take all phases that are in the I_data Data object diff --git a/pyleecan/Methods/Output/OutElec/get_I_fund.py b/pyleecan/Methods/Output/OutElec/get_I_fund.py new file mode 100644 index 000000000..c14fe17fd --- /dev/null +++ b/pyleecan/Methods/Output/OutElec/get_I_fund.py @@ -0,0 +1,70 @@ +from numpy import pi, array, transpose, where + +from SciDataTool import Data1D, DataTime, DataFreq + +from ....Functions.Electrical.coordinate_transformation import dq2n +from ....Functions.Winding.gen_phase_list import gen_name + + +def get_I_fund(self, Time=None): + """Return the stator current DataTime object + + Parameters + ---------- + self : OutElec + an OutElec object + + """ + if Time is None: + Time = self.axes_dict["time"] + time = Time.get_values(is_smallestperiod=True) + qs = self.parent.simu.machine.stator.winding.qs + felec = self.felec + + Phase = Data1D( + name="phase", + unit="", + values=gen_name(qs), + is_components=True, + ) + + if self.Is is None: + # Generate current according to Id/Iq + Isdq = array([self.Id_ref, self.Iq_ref]) + + # Get rotation direction of the fundamental magnetic field created by the winding + rot_dir = self.parent.get_rot_dir() + + # Get stator current function of time + Is = dq2n(Isdq, 2 * pi * felec * time, n=qs, rot_dir=rot_dir, is_n_rms=False) + + I_fund = DataTime( + name="Stator current", + unit="A", + symbol="I_s", + axes=[Phase, Time], + values=transpose(Is), + ) + + else: + result = self.Is.get_along("freqs", "phase") + Is_val = result["I_s"] + freqs = result["freqs"] + ifund = where(freqs == felec) + Is_fund = Is_val[:, ifund] + + Freq = Data1D( + name="freqs", + unit="Hz", + values=freqs[ifund], + ) + + I_fund = DataFreq( + name="Stator current", + unit="A", + symbol="I_s", + axes=[Phase, Freq], + values=Is_fund, + ) + + return I_fund diff --git a/pyleecan/Methods/Output/OutElec/get_I_harm.py b/pyleecan/Methods/Output/OutElec/get_I_harm.py new file mode 100644 index 000000000..db6e2639a --- /dev/null +++ b/pyleecan/Methods/Output/OutElec/get_I_harm.py @@ -0,0 +1,37 @@ +from numpy import pi, array, transpose, where + +from SciDataTool import Data1D, DataTime + +from ....Functions.Electrical.coordinate_transformation import dq2n +from ....Functions.Winding.gen_phase_list import gen_name + + +def get_I_harm(self): + """Return the stator current DataTime object + + Parameters + ---------- + self : OutElec + an OutElec object + + """ + + # Generate current according to Id/Iq + + felec = self.felec + I_fund_freq = self.Is.time_to_freq() + + results = I_fund_freq.get_along("freqs", "phase") + + # Remove fundamental value + freqs = results["freqs"] + ifund = where(freqs != self.felec)[0] + + Freqs = I_fund_freq.axes[1].copy() + Freqs.values = results["freqs"][ifund] + + I_harm = I_fund_freq.copy() + I_harm.axes = [I_fund_freq.axes[0], Freqs] + I_harm.values = results["I_s"][:, ifund] + + return I_harm diff --git a/pyleecan/Methods/Output/OutElec/get_Is.py b/pyleecan/Methods/Output/OutElec/get_Is.py index 75f3c538a..87d15eb95 100644 --- a/pyleecan/Methods/Output/OutElec/get_Is.py +++ b/pyleecan/Methods/Output/OutElec/get_Is.py @@ -6,7 +6,7 @@ from ....Functions.Winding.gen_phase_list import gen_name -def get_Is(self): +def get_Is(self, Time=None, is_current_harm=False): """Return the stator current DataTime object Parameters @@ -16,30 +16,7 @@ def get_Is(self): """ # Calculate stator currents if Is is not in OutElec - if self.Is is None: - # Generate current according to Id/Iq - Isdq = array([self.Id_ref, self.Iq_ref]) - time = self.axes_dict["time"].get_values(is_smallestperiod=True) - qs = self.parent.simu.machine.stator.winding.qs - felec = self.felec - - # Get rotation direction of the fundamental magnetic field created by the winding - rot_dir = self.parent.get_rot_dir() - - # Get stator current function of time - Is = dq2n(Isdq, 2 * pi * felec * time, n=qs, rot_dir=rot_dir, is_n_rms=False) - - Phase = Data1D( - name="phase", - unit="", - values=gen_name(qs), - is_components=True, - ) - self.Is = DataTime( - name="Stator current", - unit="A", - symbol="Is", - axes=[Phase, self.axes_dict["time"].copy()], - values=transpose(Is), - ) + if self.Is is None or is_current_harm: + self.Is = self.get_I_fund(Time=Time) + return self.Is diff --git a/pyleecan/Methods/Simulation/MagElmer/comp_flux_airgap.py b/pyleecan/Methods/Simulation/MagElmer/comp_flux_airgap.py index 0cba962f4..4b3077ccf 100644 --- a/pyleecan/Methods/Simulation/MagElmer/comp_flux_airgap.py +++ b/pyleecan/Methods/Simulation/MagElmer/comp_flux_airgap.py @@ -7,7 +7,7 @@ from ....Methods.Simulation.MagElmer import MagElmer_BP_dict -def comp_flux_airgap(self, output, axes_dict): +def comp_flux_airgap(self, output, axes_dict, Is=None, Ir=None): """Build and solve Elmer model to calculate and store magnetic quantities Parameters @@ -52,18 +52,6 @@ def comp_flux_airgap(self, output, axes_dict): # Get rotor angular position angle_rotor = output.get_angle_rotor()[0:Nt] - # Interpolate current on magnetic model time axis - # Get stator current from elec out - if self.is_mmfs: - Is = output.elec.comp_I_mag(time, is_stator=True) - else: - Is = None - # Get rotor current from elec out - if self.is_mmfr: - Ir = output.elec.comp_I_mag(time, is_stator=False) - else: - Ir = None - # Setup the Elmer simulation # Geometry building gmsh_filename = self.get_path_save_fea(output) + ".msh" diff --git a/pyleecan/Methods/Simulation/MagFEMM/comp_flux_airgap.py b/pyleecan/Methods/Simulation/MagFEMM/comp_flux_airgap.py index ec6dee00a..d286b748d 100644 --- a/pyleecan/Methods/Simulation/MagFEMM/comp_flux_airgap.py +++ b/pyleecan/Methods/Simulation/MagFEMM/comp_flux_airgap.py @@ -13,7 +13,7 @@ from SciDataTool import Data1D -def comp_flux_airgap(self, output, axes_dict): +def comp_flux_airgap(self, output, axes_dict, Is=None, Ir=None): """Build and solve FEMM model to calculate and store magnetic quantities Parameters @@ -77,15 +77,6 @@ def comp_flux_airgap(self, output, axes_dict): # Interpolate current on magnetic model time axis # Get stator current from elec out - if self.is_mmfs: - Is = output.elec.comp_I_mag(time, is_stator=True) - else: - Is = None - # Get rotor current from elec out - if self.is_mmfr: - Ir = output.elec.comp_I_mag(time, is_stator=False) - else: - Ir = None # Setup the FEMM simulation # Geometry building and assigning property in FEMM diff --git a/pyleecan/Methods/Simulation/Magnetics/run.py b/pyleecan/Methods/Simulation/Magnetics/run.py index f07dc0be4..d0f599c35 100644 --- a/pyleecan/Methods/Simulation/Magnetics/run.py +++ b/pyleecan/Methods/Simulation/Magnetics/run.py @@ -20,8 +20,20 @@ def run(self): # and returns additional axes in axes_dict axes_dict = self.comp_axes(output) + if self.is_mmfs: + Is = output.elec.get_Is(Time=axes_dict["time"], is_current_harm=self.is_current_harm) + Is_val = output.elec.comp_I_mag(Time=axes_dict["time"], is_stator=True, I_data=Is, is_periodicity_t=self.is_periodicity_t) + else: + Is_val = None + # Get rotor current from elec out + if self.is_mmfr: + # Ir = output.elec.get_Ir(Time=axes_dict["time"]) TODO + Ir_val = output.elec.comp_I_mag(Time=axes_dict["time"], is_stator=False, is_periodicity_t=self.is_periodicity_t) + else: + Ir_val = None + # Calculate airgap flux - out_dict = self.comp_flux_airgap(output, axes_dict) + out_dict = self.comp_flux_airgap(output, axes_dict, Is=Is_val, Ir=Ir_val) # Store magnetic quantities contained in out_dict in OutMag, as Data object if necessary output.mag.store(out_dict, axes_dict) From 59ee70ba646d0125bac73a4701e962d4bf53ef40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl?= Date: Wed, 6 Oct 2021 12:23:41 +0200 Subject: [PATCH 045/167] [BC] Correct condition --- pyleecan/Methods/Output/OutElec/get_Is.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyleecan/Methods/Output/OutElec/get_Is.py b/pyleecan/Methods/Output/OutElec/get_Is.py index 87d15eb95..98df9f075 100644 --- a/pyleecan/Methods/Output/OutElec/get_Is.py +++ b/pyleecan/Methods/Output/OutElec/get_Is.py @@ -16,7 +16,7 @@ def get_Is(self, Time=None, is_current_harm=False): """ # Calculate stator currents if Is is not in OutElec - if self.Is is None or is_current_harm: + if self.Is is None or not is_current_harm: self.Is = self.get_I_fund(Time=Time) - + return self.Is From f5e865f136ef3d6b293cec77c4949edfe1bc8a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl?= Date: Wed, 6 Oct 2021 12:24:01 +0200 Subject: [PATCH 046/167] [CC] --- pyleecan/Classes/Class_Dict.json | 19 ++++-- pyleecan/Classes/MagElmer.py | 4 ++ pyleecan/Classes/MagFEMM.py | 4 ++ pyleecan/Classes/Magnetics.py | 30 ++++++++ pyleecan/Classes/OutElec.py | 68 +++++++++++++------ pyleecan/Classes/OutMag.py | 42 ++++++------ .../Generator/ClassesRef/Output/OutElec.csv | 14 ++-- .../Generator/ClassesRef/Output/OutMag.csv | 8 +-- .../ClassesRef/Simulation/Magnetics.csv | 1 + pyleecan/Methods/Output/OutElec/get_I_fund.py | 2 +- pyleecan/Methods/Simulation/Magnetics/run.py | 17 ++++- 11 files changed, 149 insertions(+), 60 deletions(-) diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index dec2afb6b..3968bf4f4 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -6254,6 +6254,15 @@ "type": "str", "unit": "-", "value": "Pyleecan.Magnetics" + }, + { + "desc": "0 To compute only the airgap flux from fundamental current harmonics", + "max": "", + "min": "", + "name": "is_current_harm", + "type": "bool", + "unit": "", + "value": 1 } ] }, @@ -7832,10 +7841,12 @@ "desc": "Gather the electric module outputs", "is_internal": false, "methods": [ - "get_Nr", + "comp_I_mag", + "get_I_fund", + "get_I_harm", "get_Is", + "get_Nr", "get_Us", - "comp_I_mag", "store" ], "mother": "", @@ -8439,11 +8450,11 @@ "desc": "Gather the magnetic module outputs", "is_internal": false, "methods": [ - "store", "clean", "comp_emf", + "comp_power", "get_demag", - "comp_power" + "store" ], "mother": "", "name": "OutMag", diff --git a/pyleecan/Classes/MagElmer.py b/pyleecan/Classes/MagElmer.py index b3c895bde..47650a38b 100644 --- a/pyleecan/Classes/MagElmer.py +++ b/pyleecan/Classes/MagElmer.py @@ -156,6 +156,7 @@ def __init__( angle_stator_shift=0, angle_rotor_shift=0, logger_name="Pyleecan.Magnetics", + is_current_harm=True, init_dict=None, init_str=None, ): @@ -220,6 +221,8 @@ def __init__( angle_rotor_shift = init_dict["angle_rotor_shift"] if "logger_name" in list(init_dict.keys()): logger_name = init_dict["logger_name"] + if "is_current_harm" in list(init_dict.keys()): + is_current_harm = init_dict["is_current_harm"] # Set the properties (value check and convertion are done in setter) self.Kmesh_fineness = Kmesh_fineness self.Kgeo_fineness = Kgeo_fineness @@ -246,6 +249,7 @@ def __init__( angle_stator_shift=angle_stator_shift, angle_rotor_shift=angle_rotor_shift, logger_name=logger_name, + is_current_harm=is_current_harm, ) # The class is frozen (in Magnetics init), for now it's impossible to # add new properties diff --git a/pyleecan/Classes/MagFEMM.py b/pyleecan/Classes/MagFEMM.py index f766bcfb3..24a03eca8 100644 --- a/pyleecan/Classes/MagFEMM.py +++ b/pyleecan/Classes/MagFEMM.py @@ -161,6 +161,7 @@ def __init__( angle_stator_shift=0, angle_rotor_shift=0, logger_name="Pyleecan.Magnetics", + is_current_harm=True, init_dict=None, init_str=None, ): @@ -233,6 +234,8 @@ def __init__( angle_rotor_shift = init_dict["angle_rotor_shift"] if "logger_name" in list(init_dict.keys()): logger_name = init_dict["logger_name"] + if "is_current_harm" in list(init_dict.keys()): + is_current_harm = init_dict["is_current_harm"] # Set the properties (value check and convertion are done in setter) self.Kmesh_fineness = Kmesh_fineness self.Kgeo_fineness = Kgeo_fineness @@ -263,6 +266,7 @@ def __init__( angle_stator_shift=angle_stator_shift, angle_rotor_shift=angle_rotor_shift, logger_name=logger_name, + is_current_harm=is_current_harm, ) # The class is frozen (in Magnetics init), for now it's impossible to # add new properties diff --git a/pyleecan/Classes/Magnetics.py b/pyleecan/Classes/Magnetics.py index ef6a93047..5b718e251 100644 --- a/pyleecan/Classes/Magnetics.py +++ b/pyleecan/Classes/Magnetics.py @@ -75,6 +75,7 @@ def __init__( angle_stator_shift=0, angle_rotor_shift=0, logger_name="Pyleecan.Magnetics", + is_current_harm=True, init_dict=None, init_str=None, ): @@ -117,6 +118,8 @@ def __init__( angle_rotor_shift = init_dict["angle_rotor_shift"] if "logger_name" in list(init_dict.keys()): logger_name = init_dict["logger_name"] + if "is_current_harm" in list(init_dict.keys()): + is_current_harm = init_dict["is_current_harm"] # Set the properties (value check and convertion are done in setter) self.parent = None self.is_remove_slotS = is_remove_slotS @@ -131,6 +134,7 @@ def __init__( self.angle_stator_shift = angle_stator_shift self.angle_rotor_shift = angle_rotor_shift self.logger_name = logger_name + self.is_current_harm = is_current_harm # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -157,6 +161,7 @@ def __str__(self): ) Magnetics_str += "angle_rotor_shift = " + str(self.angle_rotor_shift) + linesep Magnetics_str += 'logger_name = "' + str(self.logger_name) + '"' + linesep + Magnetics_str += "is_current_harm = " + str(self.is_current_harm) + linesep return Magnetics_str def __eq__(self, other): @@ -188,6 +193,8 @@ def __eq__(self, other): return False if other.logger_name != self.logger_name: return False + if other.is_current_harm != self.is_current_harm: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -222,6 +229,8 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".angle_rotor_shift") if other._logger_name != self._logger_name: diff_list.append(name + ".logger_name") + if other._is_current_harm != self._is_current_harm: + diff_list.append(name + ".is_current_harm") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -242,6 +251,7 @@ def __sizeof__(self): S += getsizeof(self.angle_stator_shift) S += getsizeof(self.angle_rotor_shift) S += getsizeof(self.logger_name) + S += getsizeof(self.is_current_harm) return S def as_dict(self, **kwargs): @@ -264,6 +274,7 @@ def as_dict(self, **kwargs): Magnetics_dict["angle_stator_shift"] = self.angle_stator_shift Magnetics_dict["angle_rotor_shift"] = self.angle_rotor_shift Magnetics_dict["logger_name"] = self.logger_name + Magnetics_dict["is_current_harm"] = self.is_current_harm # The class name is added to the dict for deserialisation purpose Magnetics_dict["__class__"] = "Magnetics" return Magnetics_dict @@ -283,6 +294,7 @@ def _set_None(self): self.angle_stator_shift = None self.angle_rotor_shift = None self.logger_name = None + self.is_current_harm = None def _get_is_remove_slotS(self): """getter of is_remove_slotS""" @@ -503,3 +515,21 @@ def _set_logger_name(self, value): :Type: str """, ) + + def _get_is_current_harm(self): + """getter of is_current_harm""" + return self._is_current_harm + + def _set_is_current_harm(self, value): + """setter of is_current_harm""" + check_var("is_current_harm", value, "bool") + self._is_current_harm = value + + is_current_harm = property( + fget=_get_is_current_harm, + fset=_set_is_current_harm, + doc=u"""0 To compute only the airgap flux from fundamental current harmonics + + :Type: bool + """, + ) diff --git a/pyleecan/Classes/OutElec.py b/pyleecan/Classes/OutElec.py index 7c7317ed7..8e6618df5 100644 --- a/pyleecan/Classes/OutElec.py +++ b/pyleecan/Classes/OutElec.py @@ -18,9 +18,19 @@ # Import all class method # Try/catch to remove unnecessary dependencies in unused method try: - from ..Methods.Output.OutElec.get_Nr import get_Nr + from ..Methods.Output.OutElec.comp_I_mag import comp_I_mag except ImportError as error: - get_Nr = error + comp_I_mag = error + +try: + from ..Methods.Output.OutElec.get_I_fund import get_I_fund +except ImportError as error: + get_I_fund = error + +try: + from ..Methods.Output.OutElec.get_I_harm import get_I_harm +except ImportError as error: + get_I_harm = error try: from ..Methods.Output.OutElec.get_Is import get_Is @@ -28,14 +38,14 @@ get_Is = error try: - from ..Methods.Output.OutElec.get_Us import get_Us + from ..Methods.Output.OutElec.get_Nr import get_Nr except ImportError as error: - get_Us = error + get_Nr = error try: - from ..Methods.Output.OutElec.comp_I_mag import comp_I_mag + from ..Methods.Output.OutElec.get_Us import get_Us except ImportError as error: - comp_I_mag = error + get_Us = error try: from ..Methods.Output.OutElec.store import store @@ -54,15 +64,33 @@ class OutElec(FrozenClass): VERSION = 1 # Check ImportError to remove unnecessary dependencies in unused method - # cf Methods.Output.OutElec.get_Nr - if isinstance(get_Nr, ImportError): - get_Nr = property( + # cf Methods.Output.OutElec.comp_I_mag + if isinstance(comp_I_mag, ImportError): + comp_I_mag = property( fget=lambda x: raise_( - ImportError("Can't use OutElec method get_Nr: " + str(get_Nr)) + ImportError("Can't use OutElec method comp_I_mag: " + str(comp_I_mag)) ) ) else: - get_Nr = get_Nr + comp_I_mag = comp_I_mag + # cf Methods.Output.OutElec.get_I_fund + if isinstance(get_I_fund, ImportError): + get_I_fund = property( + fget=lambda x: raise_( + ImportError("Can't use OutElec method get_I_fund: " + str(get_I_fund)) + ) + ) + else: + get_I_fund = get_I_fund + # cf Methods.Output.OutElec.get_I_harm + if isinstance(get_I_harm, ImportError): + get_I_harm = property( + fget=lambda x: raise_( + ImportError("Can't use OutElec method get_I_harm: " + str(get_I_harm)) + ) + ) + else: + get_I_harm = get_I_harm # cf Methods.Output.OutElec.get_Is if isinstance(get_Is, ImportError): get_Is = property( @@ -72,6 +100,15 @@ class OutElec(FrozenClass): ) else: get_Is = get_Is + # cf Methods.Output.OutElec.get_Nr + if isinstance(get_Nr, ImportError): + get_Nr = property( + fget=lambda x: raise_( + ImportError("Can't use OutElec method get_Nr: " + str(get_Nr)) + ) + ) + else: + get_Nr = get_Nr # cf Methods.Output.OutElec.get_Us if isinstance(get_Us, ImportError): get_Us = property( @@ -81,15 +118,6 @@ class OutElec(FrozenClass): ) else: get_Us = get_Us - # cf Methods.Output.OutElec.comp_I_mag - if isinstance(comp_I_mag, ImportError): - comp_I_mag = property( - fget=lambda x: raise_( - ImportError("Can't use OutElec method comp_I_mag: " + str(comp_I_mag)) - ) - ) - else: - comp_I_mag = comp_I_mag # cf Methods.Output.OutElec.store if isinstance(store, ImportError): store = property( diff --git a/pyleecan/Classes/OutMag.py b/pyleecan/Classes/OutMag.py index 4c5073deb..ecc7174fe 100644 --- a/pyleecan/Classes/OutMag.py +++ b/pyleecan/Classes/OutMag.py @@ -17,11 +17,6 @@ # Import all class method # Try/catch to remove unnecessary dependencies in unused method -try: - from ..Methods.Output.OutMag.store import store -except ImportError as error: - store = error - try: from ..Methods.Output.OutMag.clean import clean except ImportError as error: @@ -32,15 +27,20 @@ except ImportError as error: comp_emf = error +try: + from ..Methods.Output.OutMag.comp_power import comp_power +except ImportError as error: + comp_power = error + try: from ..Methods.Output.OutMag.get_demag import get_demag except ImportError as error: get_demag = error try: - from ..Methods.Output.OutMag.comp_power import comp_power + from ..Methods.Output.OutMag.store import store except ImportError as error: - comp_power = error + store = error from ._check import InitUnKnowClassError @@ -54,15 +54,6 @@ class OutMag(FrozenClass): VERSION = 1 # Check ImportError to remove unnecessary dependencies in unused method - # cf Methods.Output.OutMag.store - if isinstance(store, ImportError): - store = property( - fget=lambda x: raise_( - ImportError("Can't use OutMag method store: " + str(store)) - ) - ) - else: - store = store # cf Methods.Output.OutMag.clean if isinstance(clean, ImportError): clean = property( @@ -81,6 +72,15 @@ class OutMag(FrozenClass): ) else: comp_emf = comp_emf + # cf Methods.Output.OutMag.comp_power + if isinstance(comp_power, ImportError): + comp_power = property( + fget=lambda x: raise_( + ImportError("Can't use OutMag method comp_power: " + str(comp_power)) + ) + ) + else: + comp_power = comp_power # cf Methods.Output.OutMag.get_demag if isinstance(get_demag, ImportError): get_demag = property( @@ -90,15 +90,15 @@ class OutMag(FrozenClass): ) else: get_demag = get_demag - # cf Methods.Output.OutMag.comp_power - if isinstance(comp_power, ImportError): - comp_power = property( + # cf Methods.Output.OutMag.store + if isinstance(store, ImportError): + store = property( fget=lambda x: raise_( - ImportError("Can't use OutMag method comp_power: " + str(comp_power)) + ImportError("Can't use OutMag method store: " + str(store)) ) ) else: - comp_power = comp_power + store = store # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Generator/ClassesRef/Output/OutElec.csv b/pyleecan/Generator/ClassesRef/Output/OutElec.csv index d758d5efc..7bbcc058f 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutElec.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutElec.csv @@ -1,11 +1,11 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -axes_dict,,Dict containing axes data used for Electrical,,{SciDataTool.Classes.DataND.Data},None,,,,Output,,get_Nr,VERSION,1,Gather the electric module outputs -Is,A,Stator currents DataTime object,"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Is,,, -Ir,A,Rotor currents as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Us,,, -angle_rotor,rad,Rotor angular position as a function of time (if None computed according to Nr),Nt_tot,ndarray,None,,,,,,comp_I_mag,,, -N0,rpm,Rotor speed,1,float,None,,,,,,store,,, -angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,,,, -logger_name,-,Name of the logger to use,0,str,Pyleecan.Electrical,,,,,,,,, +axes_dict,,Dict containing axes data used for Electrical,,{SciDataTool.Classes.DataND.Data},None,,,,Output,,comp_I_mag,VERSION,1,Gather the electric module outputs +Is,A,Stator currents DataTime object,"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_I_fund,,, +Ir,A,Rotor currents as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_I_harm,,, +angle_rotor,rad,Rotor angular position as a function of time (if None computed according to Nr),Nt_tot,ndarray,None,,,,,,get_Is,,, +N0,rpm,Rotor speed,1,float,None,,,,,,get_Nr,,, +angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,get_Us,,, +logger_name,-,Name of the logger to use,0,str,Pyleecan.Electrical,,,,,,store,,, Tem_av_ref,N.m,Theorical Average Electromagnetic torque,,float,None,,,,,,,,, Id_ref,Arms,d-axis current rms value,1,float,None,,,,,,,,, Iq_ref,Arms,q-axis current rms value,1,float,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Output/OutMag.csv b/pyleecan/Generator/ClassesRef/Output/OutMag.csv index 82c690ef4..fb7e5ae28 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutMag.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutMag.csv @@ -1,9 +1,9 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -Time,s,Magnetic time Data object,Nt_tot,SciDataTool.Classes.DataND.Data,None,,,,Output,,store,VERSION,1,Gather the magnetic module outputs -axes_dict,,Dict containing axes data used for Magnetics,,{SciDataTool.Classes.DataND.Data},None,,,,,,clean,,, -B,H,Airgap flux density VectorField object,"(Nt_tot,Na_tot)",SciDataTool.Classes.VectorField.VectorField,None,,,,,,comp_emf,,, +Time,s,Magnetic time Data object,Nt_tot,SciDataTool.Classes.DataND.Data,None,,,,Output,,clean,VERSION,1,Gather the magnetic module outputs +axes_dict,,Dict containing axes data used for Magnetics,,{SciDataTool.Classes.DataND.Data},None,,,,,,comp_emf,,, +B,H,Airgap flux density VectorField object,"(Nt_tot,Na_tot)",SciDataTool.Classes.VectorField.VectorField,None,,,,,,comp_power,,, Tem,N.m,Electromagnetic torque DataTime object,Nt_tot,SciDataTool.Classes.DataND.DataND,None,,,,,,get_demag,,, -Tem_av,N.m,Average Electromagnetic torque,,float,None,,,,,,comp_power,,, +Tem_av,N.m,Average Electromagnetic torque,,float,None,,,,,,store,,, Tem_rip_norm,-,Peak to Peak Torque ripple normalized according to average torque (None if average torque=0),,float,None,,,,,,,,, Tem_rip_pp,N.m,Peak to Peak Torque ripple,,float,None,,,,,,,,, Phi_wind_stator,Wb,Stator winding flux DataTime object,"(Nt_tot, qs)",SciDataTool.Classes.DataTime.DataTime,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/Magnetics.csv b/pyleecan/Generator/ClassesRef/Simulation/Magnetics.csv index 69e6e0e1c..be6fb759a 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/Magnetics.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/Magnetics.csv @@ -11,3 +11,4 @@ is_periodicity_a,-,True to compute only on one angle periodicity (use periodicit angle_stator_shift,rad,Shift angle to appy to the stator in magnetic model,0,float,0,,,,,,,,,, angle_rotor_shift,rad,Shift angle to appy to the rotor in magnetic model,0,float,0,,,,,,,,,, logger_name,-,Name of the logger to use,0,str,Pyleecan.Magnetics,,,,,,,,,, +is_current_harm,,0 To compute only the airgap flux from fundamental current harmonics,0,bool,1,,,,,,,,,, diff --git a/pyleecan/Methods/Output/OutElec/get_I_fund.py b/pyleecan/Methods/Output/OutElec/get_I_fund.py index c14fe17fd..ec72565b7 100644 --- a/pyleecan/Methods/Output/OutElec/get_I_fund.py +++ b/pyleecan/Methods/Output/OutElec/get_I_fund.py @@ -52,7 +52,7 @@ def get_I_fund(self, Time=None): freqs = result["freqs"] ifund = where(freqs == felec) Is_fund = Is_val[:, ifund] - + Freq = Data1D( name="freqs", unit="Hz", diff --git a/pyleecan/Methods/Simulation/Magnetics/run.py b/pyleecan/Methods/Simulation/Magnetics/run.py index d0f599c35..a42a5a6f3 100644 --- a/pyleecan/Methods/Simulation/Magnetics/run.py +++ b/pyleecan/Methods/Simulation/Magnetics/run.py @@ -21,14 +21,25 @@ def run(self): axes_dict = self.comp_axes(output) if self.is_mmfs: - Is = output.elec.get_Is(Time=axes_dict["time"], is_current_harm=self.is_current_harm) - Is_val = output.elec.comp_I_mag(Time=axes_dict["time"], is_stator=True, I_data=Is, is_periodicity_t=self.is_periodicity_t) + Is = output.elec.get_Is( + Time=axes_dict["time"], is_current_harm=self.is_current_harm + ) + Is_val = output.elec.comp_I_mag( + Time=axes_dict["time"], + is_stator=True, + I_data=Is, + is_periodicity_t=self.is_periodicity_t, + ) else: Is_val = None # Get rotor current from elec out if self.is_mmfr: # Ir = output.elec.get_Ir(Time=axes_dict["time"]) TODO - Ir_val = output.elec.comp_I_mag(Time=axes_dict["time"], is_stator=False, is_periodicity_t=self.is_periodicity_t) + Ir_val = output.elec.comp_I_mag( + Time=axes_dict["time"], + is_stator=False, + is_periodicity_t=self.is_periodicity_t, + ) else: Ir_val = None From e0a0900707dfdf713b9241d3f68866bfcbfb5775 Mon Sep 17 00:00:00 2001 From: helene-t Date: Wed, 6 Oct 2021 15:21:01 +0200 Subject: [PATCH 047/167] [CC] remove Time from Outmag --- pyleecan/Classes/Class_Dict.json | 9 ---- pyleecan/Classes/OutMag.py | 46 ------------------- .../Generator/ClassesRef/Output/OutMag.csv | 11 ++--- 3 files changed, 5 insertions(+), 61 deletions(-) diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 765c6251a..8f059ebd5 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -8462,15 +8462,6 @@ "package": "Output", "path": "pyleecan/Generator/ClassesRef/Output/OutMag.csv", "properties": [ - { - "desc": "Magnetic time Data object", - "max": "", - "min": "", - "name": "Time", - "type": "SciDataTool.Classes.DataND.Data", - "unit": "s", - "value": "None" - }, { "desc": "Dict containing axes data used for Magnetics", "max": "", diff --git a/pyleecan/Classes/OutMag.py b/pyleecan/Classes/OutMag.py index 4c5073deb..a5d358820 100644 --- a/pyleecan/Classes/OutMag.py +++ b/pyleecan/Classes/OutMag.py @@ -107,7 +107,6 @@ class OutMag(FrozenClass): def __init__( self, - Time=None, axes_dict=None, B=None, Tem=None, @@ -140,8 +139,6 @@ def __init__( if init_dict is not None: # Initialisation by dict assert type(init_dict) is dict # Overwrite default value with init_dict content - if "Time" in list(init_dict.keys()): - Time = init_dict["Time"] if "axes_dict" in list(init_dict.keys()): axes_dict = init_dict["axes_dict"] if "B" in list(init_dict.keys()): @@ -172,7 +169,6 @@ def __init__( Pem_av = init_dict["Pem_av"] # Set the properties (value check and convertion are done in setter) self.parent = None - self.Time = Time self.axes_dict = axes_dict self.B = B self.Tem = Tem @@ -199,7 +195,6 @@ def __str__(self): OutMag_str += "parent = None " + linesep else: OutMag_str += "parent = " + str(type(self.parent)) + " object" + linesep - OutMag_str += "Time = " + str(self.Time) + linesep + linesep OutMag_str += "axes_dict = " + str(self.axes_dict) + linesep + linesep OutMag_str += "B = " + str(self.B) + linesep + linesep OutMag_str += "Tem = " + str(self.Tem) + linesep + linesep @@ -235,8 +230,6 @@ def __eq__(self, other): if type(other) != type(self): return False - if other.Time != self.Time: - return False if other.axes_dict != self.axes_dict: return False if other.B != self.B: @@ -275,12 +268,6 @@ def compare(self, other, name="self", ignore_list=None): if type(other) != type(self): return ["type(" + name + ")"] diff_list = list() - if (other.Time is None and self.Time is not None) or ( - other.Time is not None and self.Time is None - ): - diff_list.append(name + ".Time None mismatch") - elif self.Time is not None: - diff_list.extend(self.Time.compare(other.Time, name=name + ".Time")) if (other.axes_dict is None and self.axes_dict is not None) or ( other.axes_dict is not None and self.axes_dict is None ): @@ -377,7 +364,6 @@ def __sizeof__(self): """Return the size in memory of the object (including all subobject)""" S = 0 # Full size of the object - S += getsizeof(self.Time) if self.axes_dict is not None: for key, value in self.axes_dict.items(): S += getsizeof(value) + getsizeof(key) @@ -406,10 +392,6 @@ def as_dict(self, **kwargs): """ OutMag_dict = dict() - if self.Time is None: - OutMag_dict["Time"] = None - else: - OutMag_dict["Time"] = self.Time.as_dict() if self.axes_dict is None: OutMag_dict["axes_dict"] = None else: @@ -465,7 +447,6 @@ def as_dict(self, **kwargs): def _set_None(self): """Set all the properties to None (except pyleecan object)""" - self.Time = None self.axes_dict = None self.B = None self.Tem = None @@ -483,33 +464,6 @@ def _set_None(self): self.Rag = None self.Pem_av = None - def _get_Time(self): - """getter of Time""" - return self._Time - - def _set_Time(self, value): - """setter of Time""" - if isinstance(value, str): # Load from file - value = load_init_dict(value)[1] - if isinstance(value, dict) and "__class__" in value: - class_obj = import_class( - "SciDataTool.Classes", value.get("__class__"), "Time" - ) - value = class_obj(init_dict=value) - elif type(value) is int and value == -1: # Default constructor - value = Data() - check_var("Time", value, "Data") - self._Time = value - - Time = property( - fget=_get_Time, - fset=_set_Time, - doc=u"""Magnetic time Data object - - :Type: SciDataTool.Classes.DataND.Data - """, - ) - def _get_axes_dict(self): """getter of axes_dict""" if self._axes_dict is not None: diff --git a/pyleecan/Generator/ClassesRef/Output/OutMag.csv b/pyleecan/Generator/ClassesRef/Output/OutMag.csv index 82c690ef4..8f7b33e17 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutMag.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutMag.csv @@ -1,10 +1,9 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -Time,s,Magnetic time Data object,Nt_tot,SciDataTool.Classes.DataND.Data,None,,,,Output,,store,VERSION,1,Gather the magnetic module outputs -axes_dict,,Dict containing axes data used for Magnetics,,{SciDataTool.Classes.DataND.Data},None,,,,,,clean,,, -B,H,Airgap flux density VectorField object,"(Nt_tot,Na_tot)",SciDataTool.Classes.VectorField.VectorField,None,,,,,,comp_emf,,, -Tem,N.m,Electromagnetic torque DataTime object,Nt_tot,SciDataTool.Classes.DataND.DataND,None,,,,,,get_demag,,, -Tem_av,N.m,Average Electromagnetic torque,,float,None,,,,,,comp_power,,, -Tem_rip_norm,-,Peak to Peak Torque ripple normalized according to average torque (None if average torque=0),,float,None,,,,,,,,, +axes_dict,,Dict containing axes data used for Magnetics,,{SciDataTool.Classes.DataND.Data},None,,,,Output,,store,VERSION,1,Gather the magnetic module outputs +B,H,Airgap flux density VectorField object,"(Nt_tot,Na_tot)",SciDataTool.Classes.VectorField.VectorField,None,,,,,,clean,,, +Tem,N.m,Electromagnetic torque DataTime object,Nt_tot,SciDataTool.Classes.DataND.DataND,None,,,,,,comp_emf,,, +Tem_av,N.m,Average Electromagnetic torque,,float,None,,,,,,get_demag,,, +Tem_rip_norm,-,Peak to Peak Torque ripple normalized according to average torque (None if average torque=0),,float,None,,,,,,comp_power,,, Tem_rip_pp,N.m,Peak to Peak Torque ripple,,float,None,,,,,,,,, Phi_wind_stator,Wb,Stator winding flux DataTime object,"(Nt_tot, qs)",SciDataTool.Classes.DataTime.DataTime,None,,,,,,,,, Phi_wind,Wb,Dict of lamination winding fluxlinkage DataTime objects,"(Nt_tot, qs)",{SciDataTool.Classes.DataTime.DataTime},None,,,,,,,,, From 665ce6b160f48987b19736825d59df4c3a80bd9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl?= Date: Wed, 6 Oct 2021 15:32:15 +0200 Subject: [PATCH 048/167] [CO] Add small tolerance --- pyleecan/Methods/Output/OutElec/get_I_fund.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyleecan/Methods/Output/OutElec/get_I_fund.py b/pyleecan/Methods/Output/OutElec/get_I_fund.py index ec72565b7..17d3c7a3f 100644 --- a/pyleecan/Methods/Output/OutElec/get_I_fund.py +++ b/pyleecan/Methods/Output/OutElec/get_I_fund.py @@ -1,4 +1,4 @@ -from numpy import pi, array, transpose, where +from numpy import pi, array, transpose, where, isclose from SciDataTool import Data1D, DataTime, DataFreq @@ -50,7 +50,7 @@ def get_I_fund(self, Time=None): result = self.Is.get_along("freqs", "phase") Is_val = result["I_s"] freqs = result["freqs"] - ifund = where(freqs == felec) + ifund = where(isclose(freqs, felec)) Is_fund = Is_val[:, ifund] Freq = Data1D( From 134cd6234e71238745852c64af88d5d20b12187d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl?= Date: Wed, 6 Oct 2021 15:33:07 +0200 Subject: [PATCH 049/167] [CO] Add small tolerance + [BC] Now robust to all type of SDT object --- pyleecan/Methods/Output/OutElec/get_I_harm.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pyleecan/Methods/Output/OutElec/get_I_harm.py b/pyleecan/Methods/Output/OutElec/get_I_harm.py index db6e2639a..e8b4db5a4 100644 --- a/pyleecan/Methods/Output/OutElec/get_I_harm.py +++ b/pyleecan/Methods/Output/OutElec/get_I_harm.py @@ -1,9 +1,5 @@ -from numpy import pi, array, transpose, where - -from SciDataTool import Data1D, DataTime - -from ....Functions.Electrical.coordinate_transformation import dq2n -from ....Functions.Winding.gen_phase_list import gen_name +from numpy import where, isclose, logical_not +from SciDataTool import Data1D def get_I_harm(self): @@ -25,10 +21,13 @@ def get_I_harm(self): # Remove fundamental value freqs = results["freqs"] - ifund = where(freqs != self.felec)[0] + ifund = where(logical_not(isclose(freqs, felec, rtol=1e-05)))[0] - Freqs = I_fund_freq.axes[1].copy() - Freqs.values = results["freqs"][ifund] + Freqs = Data1D( + name="freqs", + unit="Hz", + values=results["freqs"][ifund], + ) I_harm = I_fund_freq.copy() I_harm.axes = [I_fund_freq.axes[0], Freqs] From b65a17e9246c5430f81918c7828bc371a64e885a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl?= Date: Wed, 6 Oct 2021 15:33:28 +0200 Subject: [PATCH 050/167] [BC] Do not replace Is if enforced --- pyleecan/Methods/Output/OutElec/get_Is.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/pyleecan/Methods/Output/OutElec/get_Is.py b/pyleecan/Methods/Output/OutElec/get_Is.py index 98df9f075..d88a1cb10 100644 --- a/pyleecan/Methods/Output/OutElec/get_Is.py +++ b/pyleecan/Methods/Output/OutElec/get_Is.py @@ -1,11 +1,3 @@ -from numpy import pi, array, transpose - -from SciDataTool import Data1D, DataTime - -from ....Functions.Electrical.coordinate_transformation import dq2n -from ....Functions.Winding.gen_phase_list import gen_name - - def get_Is(self, Time=None, is_current_harm=False): """Return the stator current DataTime object @@ -17,6 +9,9 @@ def get_Is(self, Time=None, is_current_harm=False): """ # Calculate stator currents if Is is not in OutElec if self.Is is None or not is_current_harm: - self.Is = self.get_I_fund(Time=Time) + Is = self.get_I_fund(Time=Time) + + if self.Is is None: + self.Is = Is return self.Is From dc433d15b1c4910c4dfee66326adea38640e8c7f Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 6 Oct 2021 16:30:45 +0200 Subject: [PATCH 051/167] [WiP] Test to generate ELUT for PMSM --- .../Electrical/test_EEC_ELUT_PMSM.py | 93 +++++++++++++++++++ .../ClassesRef/Simulation/ELUT_PMSM.csv | 3 +- 2 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py new file mode 100644 index 000000000..1c43603df --- /dev/null +++ b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py @@ -0,0 +1,93 @@ +from os.path import join + +from Tests import save_validation_path as save_path +from numpy import sqrt, pi +from multiprocessing import cpu_count + +import pytest + +from pyleecan.Classes.Simu1 import Simu1 +from pyleecan.Classes.InputCurrent import InputCurrent +from pyleecan.Classes.VarLoadCurrent import VarLoadCurrent +from pyleecan.Classes.MagFEMM import MagFEMM +from pyleecan.Classes.PostELUT import PostELUT +from pyleecan.Classes.DataKeeper import DataKeeper + +from pyleecan.Functions.load import load +from pyleecan.Functions.Plot import dict_2D +from pyleecan.definitions import DATA_DIR + + +@pytest.mark.long_5s +@pytest.mark.MagFEMM +@pytest.mark.EEC_PMSM +@pytest.mark.IPMSM +@pytest.mark.periodicity +def test_ELUT_PMSM(): + """Validation of the PMSM Electrical Equivalent Circuit with the Prius machine + Compute Torque from EEC results and compare with Yang et al, 2013 + """ + + Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) + simu = Simu1(name="test_ELUT_PMSM", machine=Toyota_Prius) + + # Definition of the input + simu.input = InputCurrent( + N0=1000, Nt_tot=8 * 80, Na_tot=8 * 200, Id_ref=0, Iq_ref=0 + ) + + # Set varspeed simulation + simu.var_simu = VarLoadCurrent(type_OP_matrix=1, OP_matrix=OP_matrix) + + # Define second simu for FEMM comparison + simu.mag = MagFEMM(is_periodicity_a=True, is_periodicity_t=True, nb_worker=4) + + # Datakeepers + # Stator Winding Flux Datakeeper + Phi_wind_stator_dk = DataKeeper( + name="Stator Winding Flux", + symbol="Phi_{wind}", + unit="Wb", + keeper="lambda out: out.mag.Phi_wind_stator", + ) + + # Instanteneous torque Datakeeper + Tem_dk = DataKeeper( + name="Electromagnetic torque", + symbol="T_{em}", + unit="N.m", + keeper="lambda out: out.mag.Tem", + ) + + # Store Datakeepers + simu.var_simu.datakeeper_list = [Phi_wind_stator_dk, Tem_dk] + + # Postprocessing + simu.var_simu.postproc_list = [PostELUT()] + + out = simu.run() + + # Definition of the magnetic simulation (FEMM) + + # out2 = Output(simu=simu2) + # simu2.run() + + # # Plot 3-phase current function of time + # out.elec.get_Is().plot_2D_Data( + # "time", + # "phase[]", + # save_path=join(save_path, "EEC_FEMM_IPMSM_currents.png"), + # is_show_fig=False, + # **dict_2D + # ) + + # # from Yang et al, 2013 + # assert out.elec.Tem_av_ref == pytest.approx(81.81, rel=0.1) + # assert out2.mag.Tem_av == pytest.approx(81.70, rel=0.1) + + return out, out2 + + +# To run it without pytest +if __name__ == "__main__": + out, out2 = test_ELUT_PMSM() diff --git a/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv b/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv index 6ca6a8247..63d345cd5 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv @@ -5,6 +5,5 @@ Tmag_ref,degC,Magnet average temperature at which Phi_dqh is given,,float,20,,,, E0,V,RMS fundamental back electromotive force (bemf) along Q-axis,,float,None,,,,,,get_Ld,,,, E_dqh,V,Back emf harmonics along DQH axis,,ndarray,None,,,,,,get_Lmd,,,, orders_dqh,,Back harmonic orders (multiple of fundamental electrical frequency) corresponding to Edqh spectrum,,ndarray,None,,,,,,get_Lmq,,,, -,,,,,,,,,,,comp_Ldqh_from_Phidqh,,,, -,,,,,,,,,,,comp_Phidqh_from_Phiwind,,,, +bemf,V,Back electromotive force DataTime object,"(Nt_tot, qs)",SciDataTool.Classes.DataND.DataND,None,,,,,,comp_Ldqh_from_Phidqh,,,, ,,,,,,,,,,,import_from_data,,,, From 0c65340cb1a6d913ac946f2dd026a716966da800 Mon Sep 17 00:00:00 2001 From: helene-t Date: Wed, 6 Oct 2021 17:20:55 +0200 Subject: [PATCH 052/167] [WP] methods to compute ELUT from simu --- Tests/Data/OP_ELUT_PMSM.xlsx | Bin 0 -> 9738 bytes .../Electrical/test_EEC_ELUT_PMSM.py | 30 ++- pyleecan/Classes/Class_Dict.json | 60 ++++- pyleecan/Classes/ELUT_PMSM.py | 65 +++-- pyleecan/Classes/OutMag.py | 14 + pyleecan/Classes/PostELUT.py | 249 ++++++++++++++++++ pyleecan/Classes/import_all.py | 1 + pyleecan/Functions/load_switch.py | 1 + .../Generator/ClassesRef/Output/OutMag.csv | 8 +- .../Generator/ClassesRef/Post/PostELUT.csv | 4 + pyleecan/Methods/Output/OutMag/comp_Phi_dq.py | 42 +++ pyleecan/Methods/Output/OutMag/comp_emf.py | 27 +- 12 files changed, 466 insertions(+), 35 deletions(-) create mode 100644 Tests/Data/OP_ELUT_PMSM.xlsx create mode 100644 pyleecan/Classes/PostELUT.py create mode 100644 pyleecan/Generator/ClassesRef/Post/PostELUT.csv create mode 100644 pyleecan/Methods/Output/OutMag/comp_Phi_dq.py diff --git a/Tests/Data/OP_ELUT_PMSM.xlsx b/Tests/Data/OP_ELUT_PMSM.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..e16d4f2aee484c7454b790cb701364885937f169 GIT binary patch literal 9738 zcmeHt1y@|l()M73yIZgTgKKbt26uONx8OQhaEB1w-2=g0LvSa!Ymnds_(<+K-#I7u zzQ5q!p0&F7?%q$WJ-eT(>aJ=9X((tc01yBN0077U`O1A`au5Ij95euc34nvt7O}T; zF|~8iSMhK#b=GBcx3wY7g@y!W10bKD|6lnp{sQF*!*V^$Xm741eu!)_gfG_$!o4~M z4`4KdNDMY4jb@lUmL?zhF|TU`bFi&AYmCO0ocY-@zi`ZAS-&f8E1Q2GqXAKQCDqbk zloj#mJP!MPzVf6yEcRE(+J%cNv)XxJC%Beatz4Rh4?f^6tQz?>1W^Y2Ypv8h&Ia;9 zPa18s>%57;;x_3urBqL3j*M183Uw;%D#FDT?l6N`?6H>4OjQk@aX&NVs=G3&<<;%o8?f*Jl@&y=K1hFr2qr#+9YRGrO>UX zX$d5e2y{s`l*H_5s}o&4@6GG3u`bX#YkO_nqp#*oWQ^zoFs>s-dyv)K|9u1SbkTGy zw`g#>%HsmU(uv|nmM`AO>lUP!&@7f*D7y*?F`^&tBB~G{T9mSZ^W+_y`>4+hdU}Ea zDEvbW>s6U4E}lnZp0y0|Sq=4_Ol_Q*7=PCPQ@{UWo%`EgFOQd%?_ov?Ig$7gI(R*^ z9E&C_?ItAALZ;&DFS&$TAC>o#aJhq;2u+0`5K7#y&G*OWugiQ<2ZLl+8|>u~7+Aa% z4Q>@7DR+)3IQ*#rTK%$`D@He!5HrHc#}y&fpE$D*62{W}=8_BdkpHc+CrhE%4Nq15}EA>Cfx{(w9U0M zXScECzE8HATro11jBmiFsWC)U#a|jVsP}d}cTi&UkkRpJ@(th@S)$r?eIJ}^;tmXH zwgaI6!qgzn!f<~1sq`e#tf6rD)AD@UFbL%eeq9ZQE#m`b=7Q!f&lLGe+)76_r==QzVq;g*zd@iUh2N(?@+PNZwoTLcG=7ec$=}C_m zzHw{Abih5%hf?mzLCy^+QWi;BHL;olj_dQ~HwM~6&lC|Fin!fLl=f(@25pg1%T+Ff z)FvS4JRy1>izb*Q$|j_chVDtVPC6L`Q*i0@s=ul=qm-2D=YLG1ja5doEyU-fmsTK= zX)+W?RngU*X$9D$Wx}GLGom`_fvAn?lyG+lf|j~O%ta@d_ZB#X@bbEuDc>0y(;)3R zkj$CCnlp%ThDUJot?PKDjzoZX0aMd0Wg(O7DJ1i4x>RDEOU|A>C~E}|trEynFV!+T zzOP|MuX}{h4?@45+UKgc67xjfAKwqci*HwA;kHeE8fVGj;C^9J6qViPmqr68+Lxh* zE5_|0koCjg^MNczfqz`0!TuX}B5c+wVyTh$6MLH8Wk?>NrN@`cp$9#)xd%V9(Vh8& zq4x{jDB&MSUB#fn_-U$D#GdELg2qm7^o_UO?ef+6eiYoF(9xmO(xJ(-T8BIz-Kjmw zarZ`e)}_Hs$HWN=;rso#c*|(757JJd)zF|~4Zp|;+Y(mIePldXSY&>r zeUEe0?o|H~>OWCDDs<6d?U~vP&on0hz(G7y{9mr%??nI8EkHc4%AOVFzq^#j4O{(M zl>|QqzW3pGMJPxdTu2md=z9Q=#gOzS2bsms`suHOmjxv_7T_v@%g!%t-2^tX<*=Hr zU9D#26hU6i-w>Q-f`CSq`y}BeC1vmo)JaMNXgAI8T z4m(gy?3Ug{QTceL5X7e4Y+-r#}&5dQ3(!4xBwp(RyD{!-qq2O-v z7fiO4z*R^JV+afBNT(F<9KIRhKa0-{R=Xo|)Qhl2>EkDE_u+F8e_1wA+s7r3gU@%r zb&mU=_yIbvq6mNHh6M}&fcHG{f0#gL3sX}UXQp2b%THUFo-l0J{d29BbcdRJ-3zav zLN2UYFKnuQ+2ooIUvo-PJ0h+6)V8URjX^jKa*TJ}`J!`g=JaVMyu8c9QdX$$WdU?# zbrO37xV>Y<^HbtYr)*!+D}`}lcTdi?_767Y+IK)ww3m4L<5WQ$=3gR!?AKwoU28J7 z89NaA%i!Bth^pq76&eDv;xf*bdzrB<(s&J(1^(i6Y6LR#B1E7U)qbmkvm_Q;I^d}# z26bC`jb-xOM5z#6*aFcA?{?rCCWG4iA_+S6Q3;{y;_->OqjiZ*^)}5`$Wl+#Iqt5o zJ$S;Zb+m7uTrjvJwQHbqy|^)uxq(~2bGL#}I0CdILDy3Lg3L7mC{;q9pw`NE( znIbRwQO9S2r2Qz(9z$vkQhD?ME*as}31w(`S zFYfV!DDUAFZMoj|i!`|L|MIoJ-nW)ULPq{lmR@S?3!U1aq%2*HD(g6#x)SKK%$R?T z^f_W@ZlM%{0n^-z&d9{jf0K zbgtTc>x76n!}((W%fxMjo6!NWAtRi$mhY|3(6s}rk#W3?_G8p~_V_73nIfxLB4y(? zQ}rp6-px(kcMEATuKpK;)JBN)KO{acsMUbPm?L|+IeOrKa2WE5VZ6Bg$hEVFchUb* zgC@zU4==9w8UrhE(ePpbHT&qmL|AOTqk4L!i1`h9Fae%odzH(kOmAm2>M#NsPOfy5 zTad+YXjuP|`@0i~)jSD_o1E>Gi!A+ChxT_3tYoyDg9J`c4=F>YkefZ>nF;c!Y6(Cg zyy-={2Li!uo=W|bxx4zL*T{l-=;iG7eY2WXE`dna{*zTG%Wa-jS`DWmj|U${jum|9 zTY&|4fv7#?}sCQplOrZtDXp+95oywuYwe zW3pDBF82dr2Tj_pvnYSB5!hgRLA>ik|H@>p7*BOK=85#U3G^Xv4J_Ls6XaM%d$jcCP@?_|6 z!A~M@^~G=;>;sAs=eimY?VSwpaZe|IG?*Ux_U^`WnYCl|;W(tQEE+ED`_ox5_Vo04 zu)dWa-KF}*BP?Fo)B>u3T*?~r*9((2(jWXAlrcAo*S~lWJ0E# zCea${ZM42=mm8G$xZ%ME7?RY+_E@xin!+!KF*Rt6sbxg7*3b_K%#plDEiUL^B^ZZt zq<2w_zKCAU8*&PteX(SdB*(lD;|juhv+{~c_f*&H1nm;ZQE;f=VfE!a!gX2L8ZfrZ zQYn-4Acfnl_Z-9Q%2#I`zMC=G^838e90&F2(cYILHLDW?nF$sGe@g^rZ=IlI?vfwq%osTrE%mN^^uWtzu06A{cR}DqK+$I%WHx=7U$_U4n z4aG(-@Wn4R#Zs>XOsOdbsN13HO>6t&`N>Z9(-BrwDumZkqcC`LWJ1}uZ^ym=|_!H z80k7v2FeiG=gO^vGo=of+@aA)*!xWM@bNt0EU^NgsQFuWUg}}8rD6r2d90bQ znO1qOoGfQ*dxywMHLZXsLz&XS?8!9wZu3t3A*o_UQ7@g`@*Ec$($x77{ZwM#A$vI& zFJZkbZy&hxRMa+bRn0tT3e(=}JaB2KTotyrwsJbG>g3CawymoJ;b0!@b*M9KFbd_8 zrZe;#eNU*=NuW&}FKZe4dK%|${`M+E@Nu>_d`qwr#St#pN`aQW+qHfirN^p?Bw4ym zZu z+Hh8sroMTa#jr_Iyv3ggAF_Rz=!^}vX?#rD0oPV7j6eS4MkAYh2hQ#}1RNm*03iR% z5j(qh*qA#1Ts`*cEZcqMz-lL{c0+?cnw;sdg=vVG5A%oARv=xd*48W}uDoM;$>Yp% zr0e;0&O+ZNG?6~OCSyFzh}^=OX2b1u=E-R+A1m)&1evn^xpbB?92eU*u^`#{s}Eg-quneVu4XP|eBXA;NOKC-0uPmYw6e?# z{A5d$p}rzjy~Hy=Nl({xgCKmBgP4e zmoaB(MGgZj6+U84?F0x#O?=14ZxTbrqwu4x|+1*>*Gp75dq!*TTYg@$krBl2h0SKb<03$XIk zzXd@K&v@I2p>F{P8c;va8sc&Ecc&s>jhfNprB|5iZM_MU^P=j@MI(u#D-mKkm`q~A zi)4gVH+MF_?vK8#l#sq(i z+=_!aE4Ut2$n-^5@Pv1}mrl=gMh|OL^Gs=$*ZRIHz5ownTKhDC%A0W9#6GKj9Hf!z ztjnsrg;#ZS%*v*Y(Cp!6fo_1d%#gZosiWQ1)(V{9tY{%7sJkhTYoXFn??ohy4!U@T zOd)1n+^-}+Lp+p>M+Yt>pRqHaoh=W{2`>F?2Cc{uUvs-fb2LEUIAMqn?z{QN*i;@m zC{#EkumK)d&S4Kpa_(xD(A;ZendMRhB=!EacU9ABGw zuXJw+W2NnCJ>xm5m0{sDV(;dIE7*4aQ>ah+_BK4M896X3CiiXDwj|T2OAE0$sV$ZQal!We8GF)EM@)2pMs?a8Sb1sEOZyVpJ z9=9&!RtE=~GN{eP413DhyOqK_9VihzrhRlTeiWFM_;7}EU;Rm-8208(na?{mhBH9R zMuvbOBujDD=9H;uH;UZ=Ky-_9K0Dw$n1MrNA_G$MAh+t|bye9=V zN>)GVQIQQ0vEN3w@g@7_44dPBi;lG?8*fTm-)LL%rtdqDVgVm9Pc|a1ZEzzS%pRM4 zc8=cmwnagYXLMM55y0+{S&ZexI6obC7L=V`BEbohotFkK+@C`?3NWKvwi{=MiZ}zw zc8yVoG2#l7!&pkarhs9=%fFS1AcHdQWR=XuL4ce#KtUcvmRqOf=_~Atv!QiodR1Z1QN)rd4y8Y9!6(-^R8LW@ zKPL--r+y_Q5*!kI)K?TDO*D*DrCF}FSoLeiq zSorB(A-gGpuX3awev~z>cvTTz+p`#zXuU;8?2aJ|9cAv&n{aMXvCR*kxqH>Wko_D~ zBU4aWw;W9x@Gs*6T47b@5Z#|1ds9};fpmDecli6>S1C4{IhEgs*XZRrRBXv?(wTiA z2;Nm%XlXBXTgRjAQiY*LbDkWHW~JaNE8a5Zs(EiSi6e?a(9EWc|ANhZvsp4OZi8f1 zA%ZeO?wXTUV~(2!2Fs&$a$gu`OIfcgZq5pSlXizOejud`Pk_@x;A_yl+4;B}R4WkM0ORuF%HblPDi zl35?Kb>qjTZ<1@E!ju;9sYU@Y8kI8_M8R~HfUh?lOFA-a!FubZ9R$;TNM$GLZODnZ z!Ep8bIg?{`obX=i+pEiHS4;?qCc$C%g!q=L6ypTH$0(nDWGt)9yGRm*Zqk0Lr!S*i zfd{70GEutA4hN5skN2y0y+C`xT^FUeH+B0ns#uGa{P*%) z+)p9S-;c5dj+%&`a=|s=&xLi!x7xn;HVC+{e6Fy`vl*OFm)m{txm=mfZHjiUHx2FQ zufcj)T;Ehzv-DsUNgYu2JlaBj9$PdBdLYss7Zsp1{gg122;2E>_gX)#LH=ZK8 zxBUKNgkbj6ypa1jcp&l|&IZI5QdBJDL8Gj{aZZ^ZC<7#*N59kzt8l zQ?>U5Js^$GmYRf6SvA*&h1s%ygIj9o4ImTDxH>MFaxE7?Oxe2UO826n)>bwrSqrqY z3APYY0IG3FeN_#{`EKbOSc?{{nrla|VgU;wd$aw9hBzTAlzNrxR8eFQV+cs48$8Rx z6XFL1I(ffkIRxW>=y#*a=MCv_$9>Fh160)_}O z&<5!}4wm0r3U|!M)^SR6*}Tosg4F2dT^mYN!_^hwGVa#~hDF1oxF0mW8T_x^@^%)# zxlux#J@{IoZ_rmbl?W{#-;f_@!J->(O8?+xOge&2y@-{fm6op!T%+;8UlI6H^bU2< zes z#mR0RM`-}{g$1El$l!6=8s5+kq>Hx&XN3D$@g88kKEhHW;8?;9o?!m&*#Q;m{l1U_ z1r|C*WQA@FT&_x_expCJboha(y()$hLi$W2dDbG>Mf%pr<#Yn~Js&ZC+xvQl_!_w4Hs=HQ!1H6_3*6=Tt)uzSF#lnuJ8{ZSb7{OiOFc}IBSJ*TuSLyOJaUyHHvkfC$t`#Bf`)sz7CQIw5@}0F>kJEN(nUc2OoB6Wy9q5zdBeXW$VcHsRPjW(=l=^d4OC zrcisEBR0#-m-7JE&%UFcDDrM!S1M+q1m5?y^PKR{Fe(Hj1K|HUxAW&D{n`G9Q#=aN z{}k}g69<0+f3~5|n((*t2fqV<&%*u%Eq~6-{+5&d9sJMy)?ZKnAO`*y`2Wau{VwPC zJjh>?su2JGOZ;0_i~dW%4fZbqf9u1N>K=JeL{{b0b4)p*4 literal 0 HcmV?d00001 diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py index 1c43603df..fc5a79f82 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py @@ -1,6 +1,6 @@ from os.path import join -from Tests import save_validation_path as save_path +from Tests import save_validation_path as save_path, TEST_DATA_DIR from numpy import sqrt, pi from multiprocessing import cpu_count @@ -12,6 +12,7 @@ from pyleecan.Classes.MagFEMM import MagFEMM from pyleecan.Classes.PostELUT import PostELUT from pyleecan.Classes.DataKeeper import DataKeeper +from pyleecan.Classes.ImportMatrixXls import ImportMatrixXls from pyleecan.Functions.load import load from pyleecan.Functions.Plot import dict_2D @@ -36,6 +37,11 @@ def test_ELUT_PMSM(): N0=1000, Nt_tot=8 * 80, Na_tot=8 * 200, Id_ref=0, Iq_ref=0 ) + # Load OP_matrix + OP_matrix = ImportMatrixXls( + file_path=join(TEST_DATA_DIR, "OP_ELUT_PMSM.xlsx"), sheet="Feuil1" + ).get_data() + # Set varspeed simulation simu.var_simu = VarLoadCurrent(type_OP_matrix=1, OP_matrix=OP_matrix) @@ -59,8 +65,24 @@ def test_ELUT_PMSM(): keeper="lambda out: out.mag.Tem", ) + # Stator Winding Flux along dq Datakeeper + Phi_wind_dq_dk = DataKeeper( + name="Stator Winding Flux along dq axes", + symbol="Phi_{dq}", + unit="Wb", + keeper="lambda out: out.mag.comp_Phi_dq()", + ) + + # Electromotive force Datakeeper + EMF_dk = DataKeeper( + name="Stator Winding Electromotive Force", + symbol="EMF", + unit="V", + keeper="lambda out: out.mag.comp_emf()", + ) + # Store Datakeepers - simu.var_simu.datakeeper_list = [Phi_wind_stator_dk, Tem_dk] + simu.var_simu.datakeeper_list = [Phi_wind_stator_dk, Tem_dk, Phi_wind_dq_dk, EMF_dk] # Postprocessing simu.var_simu.postproc_list = [PostELUT()] @@ -85,9 +107,9 @@ def test_ELUT_PMSM(): # assert out.elec.Tem_av_ref == pytest.approx(81.81, rel=0.1) # assert out2.mag.Tem_av == pytest.approx(81.70, rel=0.1) - return out, out2 + return out # To run it without pytest if __name__ == "__main__": - out, out2 = test_ELUT_PMSM() + out = test_ELUT_PMSM() diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index b0d12f648..4bcfea721 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -1313,7 +1313,6 @@ "get_Lmd", "get_Lmq", "comp_Ldqh_from_Phidqh", - "comp_Phidqh_from_Phiwind", "import_from_data" ], "mother": "ELUT", @@ -1374,6 +1373,15 @@ "type": "ndarray", "unit": "", "value": null + }, + { + "desc": "Back electromotive force DataTime object", + "max": "", + "min": "", + "name": "bemf", + "type": "SciDataTool.Classes.DataND.DataND", + "unit": "V", + "value": "None" } ] }, @@ -8464,6 +8472,7 @@ "methods": [ "clean", "comp_emf", + "comp_Phi_dq", "comp_power", "get_demag", "store" @@ -9107,6 +9116,7 @@ } ], "daughters": [ + "PostELUT", "PostFunction", "PostMethod", "PostPlot" @@ -9120,6 +9130,53 @@ "path": "pyleecan/Generator/ClassesRef/Post/Post.csv", "properties": [] }, + "PostELUT": { + "constants": [ + { + "name": "VERSION", + "value": "1" + } + ], + "daughters": [], + "desc": "Class to generate an ELUT after the corresponding simulation", + "is_internal": false, + "methods": [ + "run" + ], + "mother": "PostMethod", + "name": "PostELUT", + "package": "Post", + "path": "pyleecan/Generator/ClassesRef/Post/PostELUT.csv", + "properties": [ + { + "desc": "Electrical Look-Up Table to enforce", + "max": "", + "min": "", + "name": "ELUT", + "type": "ELUT", + "unit": "", + "value": null + }, + { + "desc": "True to save ELUT in PostELUT", + "max": "", + "min": "", + "name": "is_save_ELUT", + "type": "bool", + "unit": "", + "value": 1 + }, + { + "desc": "True to store ELUT in PostELUT", + "max": "", + "min": "", + "name": "is_store_ELUT", + "type": "bool", + "unit": "", + "value": 1 + } + ] + }, "PostFunction": { "constants": [ { @@ -9155,6 +9212,7 @@ } ], "daughters": [ + "PostELUT", "PostPlot" ], "desc": "Abstract class for post-processing defined in the method run", diff --git a/pyleecan/Classes/ELUT_PMSM.py b/pyleecan/Classes/ELUT_PMSM.py index d55b95a5e..2fdb9f413 100644 --- a/pyleecan/Classes/ELUT_PMSM.py +++ b/pyleecan/Classes/ELUT_PMSM.py @@ -54,13 +54,6 @@ except ImportError as error: comp_Ldqh_from_Phidqh = error -try: - from ..Methods.Simulation.ELUT_PMSM.comp_Phidqh_from_Phiwind import ( - comp_Phidqh_from_Phiwind, - ) -except ImportError as error: - comp_Phidqh_from_Phiwind = error - try: from ..Methods.Simulation.ELUT_PMSM.import_from_data import import_from_data except ImportError as error: @@ -145,18 +138,6 @@ class ELUT_PMSM(ELUT): ) else: comp_Ldqh_from_Phidqh = comp_Ldqh_from_Phidqh - # cf Methods.Simulation.ELUT_PMSM.comp_Phidqh_from_Phiwind - if isinstance(comp_Phidqh_from_Phiwind, ImportError): - comp_Phidqh_from_Phiwind = property( - fget=lambda x: raise_( - ImportError( - "Can't use ELUT_PMSM method comp_Phidqh_from_Phiwind: " - + str(comp_Phidqh_from_Phiwind) - ) - ) - ) - else: - comp_Phidqh_from_Phiwind = comp_Phidqh_from_Phiwind # cf Methods.Simulation.ELUT_PMSM.import_from_data if isinstance(import_from_data, ImportError): import_from_data = property( @@ -183,6 +164,7 @@ def __init__( E0=None, E_dqh=None, orders_dqh=None, + bemf=None, R1=None, L1=None, T1_ref=20, @@ -216,6 +198,8 @@ def __init__( E_dqh = init_dict["E_dqh"] if "orders_dqh" in list(init_dict.keys()): orders_dqh = init_dict["orders_dqh"] + if "bemf" in list(init_dict.keys()): + bemf = init_dict["bemf"] if "R1" in list(init_dict.keys()): R1 = init_dict["R1"] if "L1" in list(init_dict.keys()): @@ -229,6 +213,7 @@ def __init__( self.E0 = E0 self.E_dqh = E_dqh self.orders_dqh = orders_dqh + self.bemf = bemf # Call ELUT init super(ELUT_PMSM, self).__init__(R1=R1, L1=L1, T1_ref=T1_ref) # The class is frozen (in ELUT init), for now it's impossible to @@ -269,6 +254,7 @@ def __str__(self): + linesep + linesep ) + ELUT_PMSM_str += "bemf = " + str(self.bemf) + linesep + linesep return ELUT_PMSM_str def __eq__(self, other): @@ -292,6 +278,8 @@ def __eq__(self, other): return False if not array_equal(other.orders_dqh, self.orders_dqh): return False + if other.bemf != self.bemf: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -317,6 +305,12 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".E_dqh") if not array_equal(other.orders_dqh, self.orders_dqh): diff_list.append(name + ".orders_dqh") + if (other.bemf is None and self.bemf is not None) or ( + other.bemf is not None and self.bemf is None + ): + diff_list.append(name + ".bemf None mismatch") + elif self.bemf is not None: + diff_list.extend(self.bemf.compare(other.bemf, name=name + ".bemf")) # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -336,6 +330,7 @@ def __sizeof__(self): S += getsizeof(self.E0) S += getsizeof(self.E_dqh) S += getsizeof(self.orders_dqh) + S += getsizeof(self.bemf) return S def as_dict(self, **kwargs): @@ -362,6 +357,10 @@ def as_dict(self, **kwargs): ELUT_PMSM_dict["orders_dqh"] = None else: ELUT_PMSM_dict["orders_dqh"] = self.orders_dqh.tolist() + if self.bemf is None: + ELUT_PMSM_dict["bemf"] = None + else: + ELUT_PMSM_dict["bemf"] = self.bemf.as_dict() # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name ELUT_PMSM_dict["__class__"] = "ELUT_PMSM" @@ -376,6 +375,7 @@ def _set_None(self): self.E0 = None self.E_dqh = None self.orders_dqh = None + self.bemf = None # Set to None the properties inherited from ELUT super(ELUT_PMSM, self)._set_None() @@ -509,3 +509,30 @@ def _set_orders_dqh(self, value): :Type: ndarray """, ) + + def _get_bemf(self): + """getter of bemf""" + return self._bemf + + def _set_bemf(self, value): + """setter of bemf""" + if isinstance(value, str): # Load from file + value = load_init_dict(value)[1] + if isinstance(value, dict) and "__class__" in value: + class_obj = import_class( + "SciDataTool.Classes", value.get("__class__"), "bemf" + ) + value = class_obj(init_dict=value) + elif type(value) is int and value == -1: # Default constructor + value = DataND() + check_var("bemf", value, "DataND") + self._bemf = value + + bemf = property( + fget=_get_bemf, + fset=_set_bemf, + doc=u"""Back electromotive force DataTime object + + :Type: SciDataTool.Classes.DataND.DataND + """, + ) diff --git a/pyleecan/Classes/OutMag.py b/pyleecan/Classes/OutMag.py index 3c73635af..c92e1db04 100644 --- a/pyleecan/Classes/OutMag.py +++ b/pyleecan/Classes/OutMag.py @@ -27,6 +27,11 @@ except ImportError as error: comp_emf = error +try: + from ..Methods.Output.OutMag.comp_Phi_dq import comp_Phi_dq +except ImportError as error: + comp_Phi_dq = error + try: from ..Methods.Output.OutMag.comp_power import comp_power except ImportError as error: @@ -72,6 +77,15 @@ class OutMag(FrozenClass): ) else: comp_emf = comp_emf + # cf Methods.Output.OutMag.comp_Phi_dq + if isinstance(comp_Phi_dq, ImportError): + comp_Phi_dq = property( + fget=lambda x: raise_( + ImportError("Can't use OutMag method comp_Phi_dq: " + str(comp_Phi_dq)) + ) + ) + else: + comp_Phi_dq = comp_Phi_dq # cf Methods.Output.OutMag.comp_power if isinstance(comp_power, ImportError): comp_power = property( diff --git a/pyleecan/Classes/PostELUT.py b/pyleecan/Classes/PostELUT.py new file mode 100644 index 000000000..8154e7cff --- /dev/null +++ b/pyleecan/Classes/PostELUT.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- +# File generated according to Generator/ClassesRef/Post/PostELUT.csv +# WARNING! All changes made in this file will be lost! +"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Post/PostELUT +""" + +from os import linesep +from sys import getsizeof +from logging import getLogger +from ._check import check_var, raise_ +from ..Functions.get_logger import get_logger +from ..Functions.save import save +from ..Functions.copy import copy +from ..Functions.load import load_init_dict +from ..Functions.Load.import_class import import_class +from .PostMethod import PostMethod + +# Import all class method +# Try/catch to remove unnecessary dependencies in unused method +try: + from ..Methods.Post.PostELUT.run import run +except ImportError as error: + run = error + + +from ._check import InitUnKnowClassError +from .ELUT import ELUT + + +class PostELUT(PostMethod): + """Class to generate an ELUT after the corresponding simulation""" + + VERSION = 1 + + # cf Methods.Post.PostELUT.run + if isinstance(run, ImportError): + run = property( + fget=lambda x: raise_( + ImportError("Can't use PostELUT method run: " + str(run)) + ) + ) + else: + run = run + # save and copy methods are available in all object + save = save + copy = copy + # get_logger method is available in all object + get_logger = get_logger + + def __init__( + self, + ELUT=None, + is_save_ELUT=True, + is_store_ELUT=True, + init_dict=None, + init_str=None, + ): + """Constructor of the class. Can be use in three ways : + - __init__ (arg1 = 1, arg3 = 5) every parameters have name and default values + for pyleecan type, -1 will call the default constructor + - __init__ (init_dict = d) d must be a dictionary with property names as keys + - __init__ (init_str = s) s must be a string + s is the file path to load + + ndarray or list can be given for Vector and Matrix + object or dict can be given for pyleecan Object""" + + if init_str is not None: # Load from a file + init_dict = load_init_dict(init_str)[1] + if init_dict is not None: # Initialisation by dict + assert type(init_dict) is dict + # Overwrite default value with init_dict content + if "ELUT" in list(init_dict.keys()): + ELUT = init_dict["ELUT"] + if "is_save_ELUT" in list(init_dict.keys()): + is_save_ELUT = init_dict["is_save_ELUT"] + if "is_store_ELUT" in list(init_dict.keys()): + is_store_ELUT = init_dict["is_store_ELUT"] + # Set the properties (value check and convertion are done in setter) + self.ELUT = ELUT + self.is_save_ELUT = is_save_ELUT + self.is_store_ELUT = is_store_ELUT + # Call PostMethod init + super(PostELUT, self).__init__() + # The class is frozen (in PostMethod init), for now it's impossible to + # add new properties + + def __str__(self): + """Convert this object in a readeable string (for print)""" + + PostELUT_str = "" + # Get the properties inherited from PostMethod + PostELUT_str += super(PostELUT, self).__str__() + if self.ELUT is not None: + tmp = self.ELUT.__str__().replace(linesep, linesep + "\t").rstrip("\t") + PostELUT_str += "ELUT = " + tmp + else: + PostELUT_str += "ELUT = None" + linesep + linesep + PostELUT_str += "is_save_ELUT = " + str(self.is_save_ELUT) + linesep + PostELUT_str += "is_store_ELUT = " + str(self.is_store_ELUT) + linesep + return PostELUT_str + + def __eq__(self, other): + """Compare two objects (skip parent)""" + + if type(other) != type(self): + return False + + # Check the properties inherited from PostMethod + if not super(PostELUT, self).__eq__(other): + return False + if other.ELUT != self.ELUT: + return False + if other.is_save_ELUT != self.is_save_ELUT: + return False + if other.is_store_ELUT != self.is_store_ELUT: + return False + return True + + def compare(self, other, name="self", ignore_list=None): + """Compare two objects and return list of differences""" + + if ignore_list is None: + ignore_list = list() + if type(other) != type(self): + return ["type(" + name + ")"] + diff_list = list() + + # Check the properties inherited from PostMethod + diff_list.extend(super(PostELUT, self).compare(other, name=name)) + if (other.ELUT is None and self.ELUT is not None) or ( + other.ELUT is not None and self.ELUT is None + ): + diff_list.append(name + ".ELUT None mismatch") + elif self.ELUT is not None: + diff_list.extend(self.ELUT.compare(other.ELUT, name=name + ".ELUT")) + if other._is_save_ELUT != self._is_save_ELUT: + diff_list.append(name + ".is_save_ELUT") + if other._is_store_ELUT != self._is_store_ELUT: + diff_list.append(name + ".is_store_ELUT") + # Filter ignore differences + diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) + return diff_list + + def __sizeof__(self): + """Return the size in memory of the object (including all subobject)""" + + S = 0 # Full size of the object + + # Get size of the properties inherited from PostMethod + S += super(PostELUT, self).__sizeof__() + S += getsizeof(self.ELUT) + S += getsizeof(self.is_save_ELUT) + S += getsizeof(self.is_store_ELUT) + return S + + def as_dict(self, **kwargs): + """ + Convert this object in a json serializable dict (can be use in __init__). + Optional keyword input parameter is for internal use only + and may prevent json serializability. + """ + + # Get the properties inherited from PostMethod + PostELUT_dict = super(PostELUT, self).as_dict(**kwargs) + if self.ELUT is None: + PostELUT_dict["ELUT"] = None + else: + PostELUT_dict["ELUT"] = self.ELUT.as_dict(**kwargs) + PostELUT_dict["is_save_ELUT"] = self.is_save_ELUT + PostELUT_dict["is_store_ELUT"] = self.is_store_ELUT + # The class name is added to the dict for deserialisation purpose + # Overwrite the mother class name + PostELUT_dict["__class__"] = "PostELUT" + return PostELUT_dict + + def _set_None(self): + """Set all the properties to None (except pyleecan object)""" + + if self.ELUT is not None: + self.ELUT._set_None() + self.is_save_ELUT = None + self.is_store_ELUT = None + # Set to None the properties inherited from PostMethod + super(PostELUT, self)._set_None() + + def _get_ELUT(self): + """getter of ELUT""" + return self._ELUT + + def _set_ELUT(self, value): + """setter of ELUT""" + if isinstance(value, str): # Load from file + value = load_init_dict(value)[1] + if isinstance(value, dict) and "__class__" in value: + class_obj = import_class("pyleecan.Classes", value.get("__class__"), "ELUT") + value = class_obj(init_dict=value) + elif type(value) is int and value == -1: # Default constructor + value = ELUT() + check_var("ELUT", value, "ELUT") + self._ELUT = value + + if self._ELUT is not None: + self._ELUT.parent = self + + ELUT = property( + fget=_get_ELUT, + fset=_set_ELUT, + doc=u"""Electrical Look-Up Table to enforce + + :Type: ELUT + """, + ) + + def _get_is_save_ELUT(self): + """getter of is_save_ELUT""" + return self._is_save_ELUT + + def _set_is_save_ELUT(self, value): + """setter of is_save_ELUT""" + check_var("is_save_ELUT", value, "bool") + self._is_save_ELUT = value + + is_save_ELUT = property( + fget=_get_is_save_ELUT, + fset=_set_is_save_ELUT, + doc=u"""True to save ELUT in PostELUT + + :Type: bool + """, + ) + + def _get_is_store_ELUT(self): + """getter of is_store_ELUT""" + return self._is_store_ELUT + + def _set_is_store_ELUT(self, value): + """setter of is_store_ELUT""" + check_var("is_store_ELUT", value, "bool") + self._is_store_ELUT = value + + is_store_ELUT = property( + fget=_get_is_store_ELUT, + fset=_set_is_store_ELUT, + doc=u"""True to store ELUT in PostELUT + + :Type: bool + """, + ) diff --git a/pyleecan/Classes/import_all.py b/pyleecan/Classes/import_all.py index f049cbc52..865c5d252 100644 --- a/pyleecan/Classes/import_all.py +++ b/pyleecan/Classes/import_all.py @@ -153,6 +153,7 @@ from ..Classes.ParamExplorerSet import ParamExplorerSet from ..Classes.PolarArc import PolarArc from ..Classes.Post import Post +from ..Classes.PostELUT import PostELUT from ..Classes.PostFunction import PostFunction from ..Classes.PostMethod import PostMethod from ..Classes.PostPlot import PostPlot diff --git a/pyleecan/Functions/load_switch.py b/pyleecan/Functions/load_switch.py index 0c600218a..b75da0127 100644 --- a/pyleecan/Functions/load_switch.py +++ b/pyleecan/Functions/load_switch.py @@ -155,6 +155,7 @@ "ParamExplorerSet": ParamExplorerSet, "PolarArc": PolarArc, "Post": Post, + "PostELUT": PostELUT, "PostFunction": PostFunction, "PostMethod": PostMethod, "PostPlot": PostPlot, diff --git a/pyleecan/Generator/ClassesRef/Output/OutMag.csv b/pyleecan/Generator/ClassesRef/Output/OutMag.csv index 1603ff3f7..3f68baca9 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutMag.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutMag.csv @@ -1,10 +1,10 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description axes_dict,,Dict containing axes data used for Magnetics,,{SciDataTool.Classes.DataND.Data},None,,,,Output,,clean,VERSION,1,Gather the magnetic module outputs B,H,Airgap flux density VectorField object,"(Nt_tot,Na_tot)",SciDataTool.Classes.VectorField.VectorField,None,,,,,,comp_emf,,, -Tem,N.m,Electromagnetic torque DataTime object,Nt_tot,SciDataTool.Classes.DataND.DataND,None,,,,,,comp_power,,, -Tem_av,N.m,Average Electromagnetic torque,,float,None,,,,,,get_demag,,, -Tem_rip_norm,-,Peak to Peak Torque ripple normalized according to average torque (None if average torque=0),,float,None,,,,,,store,,, -Tem_rip_pp,N.m,Peak to Peak Torque ripple,,float,None,,,,,,,,, +Tem,N.m,Electromagnetic torque DataTime object,Nt_tot,SciDataTool.Classes.DataND.DataND,None,,,,,,comp_Phi_dq,,, +Tem_av,N.m,Average Electromagnetic torque,,float,None,,,,,,comp_power,,, +Tem_rip_norm,-,Peak to Peak Torque ripple normalized according to average torque (None if average torque=0),,float,None,,,,,,get_demag,,, +Tem_rip_pp,N.m,Peak to Peak Torque ripple,,float,None,,,,,,store,,, Phi_wind_stator,Wb,Stator winding flux DataTime object,"(Nt_tot, qs)",SciDataTool.Classes.DataTime.DataTime,None,,,,,,,,, Phi_wind,Wb,Dict of lamination winding fluxlinkage DataTime objects,"(Nt_tot, qs)",{SciDataTool.Classes.DataTime.DataTime},None,,,,,,,,, emf,V,Electromotive force DataTime object,"(Nt_tot, qs)",SciDataTool.Classes.DataTime.DataTime,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Post/PostELUT.csv b/pyleecan/Generator/ClassesRef/Post/PostELUT.csv new file mode 100644 index 000000000..6c4981b9c --- /dev/null +++ b/pyleecan/Generator/ClassesRef/Post/PostELUT.csv @@ -0,0 +1,4 @@ +Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description +ELUT,,Electrical Look-Up Table to enforce,,ELUT,None,,,,Post,PostMethod,run,VERSION,1,Class to generate an ELUT after the corresponding simulation +is_save_ELUT,,True to save ELUT in PostELUT,,bool,1,,,,,,,,, +is_store_ELUT,,True to store ELUT in PostELUT,,bool,1,,,,,,,,, diff --git a/pyleecan/Methods/Output/OutMag/comp_Phi_dq.py b/pyleecan/Methods/Output/OutMag/comp_Phi_dq.py new file mode 100644 index 000000000..c23bb1d28 --- /dev/null +++ b/pyleecan/Methods/Output/OutMag/comp_Phi_dq.py @@ -0,0 +1,42 @@ +from numpy import mean, pi + +from ....Functions.Electrical.coordinate_transformation import n2dq + + +def comp_Phi_dq(self): + """Compute the stator flux linkage along dq axes + + Parameters + ---------- + self : OutMag + an OutMag object + + Returns + ------- + Phi_dqh : ndarray + Stator flux linkage along dq axes [Wb] + + """ + + output = self.parent + + qs = output.simu.machine.stator.winding.qs + felec = output.elec.felec + + # Get rotation direction of the fundamental magnetic field created by the winding + rot_dir = output.get_rot_dir() + + result = self.Phi_wind_stator.get_along("time[oneperiod]", "phase") + + Phi = result["Phi_{wind}"] + + time = result["time"] + + # Get stator current function of time + Phi_dq_time = n2dq( + Phi, 2 * pi * felec * time, n=qs, rot_dir=rot_dir, is_dq_rms=True + ) + + Phi_dq = mean(Phi_dq_time, axis=0) + + return Phi_dq diff --git a/pyleecan/Methods/Output/OutMag/comp_emf.py b/pyleecan/Methods/Output/OutMag/comp_emf.py index 158ee4fcc..cc9696049 100644 --- a/pyleecan/Methods/Output/OutMag/comp_emf.py +++ b/pyleecan/Methods/Output/OutMag/comp_emf.py @@ -1,19 +1,25 @@ -from numpy import diff, zeros, newaxis +from numpy import diff, zeros, newaxis, pi +from ....Functions.Electrical.coordinate_transformation import n2dq -def comp_emf(self): + +def comp_emf(self, is_dq=False): """Compute the Electromotive force [V] Parameters ---------- self : OutMag an OutMag object + is_dq : bool + rotate to dq axes if true """ # Get stator winding flux Phi_wind = self.Phi_wind_stator - phi_wind = Phi_wind.get_along("time[smallestperiod]", "phase")[Phi_wind.symbol] + result = Phi_wind.get_along("time[smallestperiod]", "phase") + phi_wind = result[Phi_wind.symbol] + time = result["time"] # Get time axis for axe in Phi_wind.axes: @@ -22,10 +28,6 @@ def comp_emf(self): # Get time values on the smallest period _, is_antiper_t = Time.get_periodicity() - time = Time.get_values( - is_oneperiod=True, - is_antiperiod=is_antiper_t, - ) # Calculate EMF and store it in OutMag if time.size > 1: @@ -43,6 +45,17 @@ def comp_emf(self): ) emf[-1, :] = (sign0 * phi_wind[0, :] - phi_wind[-1, :]) / (time[1] - time[0]) + if is_dq: + output = self.parent + qs = output.simu.machine.stator.winding.qs + felec = output.elec.felec + # Get rotation direction of the fundamental magnetic field created by the winding + rot_dir = output.get_rot_dir() + # Get stator current function of time + emf = n2dq( + emf, 2 * pi * felec * time, n=qs, rot_dir=rot_dir, is_dq_rms=True + ) + EMF = Phi_wind.copy() EMF.values = emf EMF.name = "Stator Winding Electromotive Force" From 66f3f0f964d878f234c3a260fa305834f50427f4 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 6 Oct 2021 18:07:18 +0200 Subject: [PATCH 053/167] [CO] Rework comp_axes, Input.comp_axes() can now generate periodic axes --- .../Functions/Simulation/create_from_axis.py | 47 --- .../Methods/Simulation/Force/comp_axes.py | 75 ++--- .../Methods/Simulation/Input/comp_axes.py | 298 ++++++++++++------ .../Simulation/InputVoltage/gen_input.py | 19 +- .../Methods/Simulation/Magnetics/comp_axes.py | 55 ++-- 5 files changed, 267 insertions(+), 227 deletions(-) delete mode 100644 pyleecan/Functions/Simulation/create_from_axis.py diff --git a/pyleecan/Functions/Simulation/create_from_axis.py b/pyleecan/Functions/Simulation/create_from_axis.py deleted file mode 100644 index 1beda3db3..000000000 --- a/pyleecan/Functions/Simulation/create_from_axis.py +++ /dev/null @@ -1,47 +0,0 @@ -from SciDataTool.Functions import AxisError - - -def create_from_axis(axis_in, per, is_aper, is_include_per, is_remove_aper=False): - """ - Create axis input axis accounting for (anti-)periodicity changes imposed - by physics and model inputs - - Parameters - ---------- - axis_in : Data - The input axis coming from previous output (a Data object such as Data1D or DataLinspace) - per: int - machine periodicity along current axis - is_aper: bool - True if the machine is anti-periodic along current axis - is_include_per: bool - True if the model requires to include periodicity - is_remove_aper: bool - True if the model requires to remove anti-periodicity - - Returns - ------- - axis_out : Data - The output axis (a Data object such as Data1D or DataLinspace) - is_include_per : bool - Returns is_include_per in case periodicity is activated in the model but cannot be applied - - """ - - # Getting the computation axes (with or without periodicity) - if is_include_per: - try: - # Reduce axis to the machine periodicity - per = per * 2 if is_aper else per - axis_out = axis_in.get_axis_periodic(per, is_aper and not is_remove_aper) - - except AxisError: - # Periodicity cannot be applied, return full axis - axis_out = axis_in.copy() - is_include_per = False - - else: - # Return full axis - axis_out = axis_in.copy() - - return axis_out, is_include_per diff --git a/pyleecan/Methods/Simulation/Force/comp_axes.py b/pyleecan/Methods/Simulation/Force/comp_axes.py index f0e518b88..e5c5653a9 100644 --- a/pyleecan/Methods/Simulation/Force/comp_axes.py +++ b/pyleecan/Methods/Simulation/Force/comp_axes.py @@ -17,67 +17,54 @@ def comp_axes(self, output): # Get axis dict from OutMag axes_dict_mag = output.mag.axes_dict - # Get time periodicity - is_include_per_t = len(axes_dict_mag["time"].symmetries) > 0 - - # Get angle periodicity - is_include_per_a = len(axes_dict_mag["angle"].symmetries) > 0 - - # Init periodicities if they are None - if self.is_periodicity_t is None: - self.is_periodicity_t = is_include_per_t - - if self.is_periodicity_a is None: - self.is_periodicity_a = is_include_per_a - - # Get time and space (anti-)periodicities of the machine - ( - per_a, - is_antiper_a, - per_t, - is_antiper_t, - ) = output.get_machine_periodicity() - - # Compute Time axis based on the one stored in OutMag and removing anti-periodicty - Time, is_periodicity_t = axes_dict_mag["time"].get_axis_periodic( - Nper=per_t, - is_aper=is_antiper_t, - is_include_per=is_include_per_t and self.is_periodicity_t, - is_remove_aper=True, + # Add periodicities to time and angle axes + axes_dict = self.parent.input.comp_axes( + axes_list=["time", "angle"], + axes_dict=axes_dict_mag, + is_periodicity_a=self.is_periodicity_a, + is_periodicity_t=self.is_periodicity_t, ) - if is_periodicity_t != self.is_periodicity_t: + # Remove time anti-periodicity if any + if "antiperiod" in axes_dict["time"].symmetries: + axes_dict["time"].symmetries["period"] = axes_dict["time"].symmetries.pop( + "antiperiod" + ) + + # Check Time periodicities regarding Force model input + per_t0, is_antiper_t0 = axes_dict["time"].get_periodicity() + is_periodicity_t0 = per_t0 > 1 or is_antiper_t0 + if is_periodicity_t0 != self.is_periodicity_t: # Remove time periodicity in Force model self.is_periodicity_t = False - Nt_tot = Time.get_length(is_oneperiod=False) + Nt_tot = axes_dict["time"].get_length(is_oneperiod=False) self.get_logger().warning( - "WARNING: In Force model, Nt_tot=" + "In Force model, Nt_tot=" + str(Nt_tot) + " is not divisible by the machine time periodicity (" - + str(per_t) + + str(output.geo.per_t_S) + "). Time periodicity removed" ) - # Compute Angle axis based on the one stored in OutMag and removing anti-periodicty - Angle, is_periodicity_a = axes_dict_mag["angle"].get_axis_periodic( - Nper=per_a, - is_aper=is_antiper_a, - is_include_per=is_include_per_a and self.is_periodicity_a, - is_remove_aper=True, - ) + # Remove angular anti-periodicity if any + if "antiperiod" in axes_dict["angle"].symmetries: + axes_dict["angle"].symmetries["period"] = axes_dict["angle"].symmetries.pop( + "antiperiod" + ) - if is_periodicity_a != self.is_periodicity_a: + # Check Angle periodicities regarding Force model input + per_a0, is_antiper_a0 = axes_dict["angle"].get_periodicity() + is_periodicity_a0 = per_a0 > 1 or is_antiper_a0 + if is_periodicity_a0 != self.is_periodicity_a: # Remove time periodicity in Magnetic model self.is_periodicity_a = False - Na_tot = Angle.get_length(is_oneperiod=False) + Na_tot = axes_dict["angle"].get_length(is_oneperiod=False) self.get_logger().warning( - "WARNING: In Force model, Na_tot=" + "In Force model, Na_tot=" + str(Na_tot) + " is not divisible by the machine angular periodicity (" - + str(per_a) + + str(output.geo.per_a) + "). Angular periodicity removed" ) - axes_dict = {"time": Time, "angle": Angle} - return axes_dict diff --git a/pyleecan/Methods/Simulation/Input/comp_axes.py b/pyleecan/Methods/Simulation/Input/comp_axes.py index 39d71a904..a605a5a6f 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axes.py +++ b/pyleecan/Methods/Simulation/Input/comp_axes.py @@ -1,19 +1,26 @@ from numpy import pi + from SciDataTool import Data1D, DataLinspace, Norm_ref + from ....Methods.Simulation.Input import InputError +from ....Functions.Winding.gen_phase_list import gen_name + def comp_axes( self, + axes_list, machine=None, - per_a=1, - is_antiper_a=False, - per_t=1, - is_antiper_t=False, + axes_dict=None, + is_periodicity_a=None, + is_periodicity_t=None, + per_a=None, + is_antiper_a=None, + per_t=None, + is_antiper_t=None, ): - """Compute simulation axes, i.e. space DataObject including (anti)-periodicity - and time DataObject including (anti)-periodicity and accounting for rotating speed - and number of revolutions + """Compute simulation axes such as time / angle / phase axes, with or without periodicities + and including normalizations Parameters ---------- @@ -21,6 +28,14 @@ def comp_axes( an Input object machine : Machine a Machine object + axes_list: list + List of axes name to return in axes dict + axes_dict: {Data} + dict of axes containing time and angle axes (with or without (anti-)periodicity) + is_periodicity_a: bool + True if spatial periodicity is requested + is_periodicity_t: bool + True if time periodicity is requested per_a : int angle periodicity is_antiper_a : bool @@ -33,108 +48,211 @@ def comp_axes( Returns ------- axes_dict: {Data} - dict of axes containing time and angle axes (with or without (anti-)periodicity) + dict of axes containing requested axes """ - N0 = self.N0 + if self.parent is not None: + simu = self.parent + else: + raise Exception("Cannot calculate axes if parent simu is None") + + if hasattr(self.parent, "parent") and self.parent.parent is not None: + output = simu.parent + else: + raise Exception("Cannot calculate axes if parent output is None") + + if (axes_list is None or len(axes_list) == 0) and ( + axes_dict is None or len(axes_dict) == 0 + ): + raise Exception( + "Cannot calculate axes if both axes list and axes dict are None" + ) + + if len(axes_list) == 0: + raise Exception("axes_list should not be empty") + + if axes_dict is not None: + # Get list of axes name from dict of axes + axes_dict_list = list(axes_dict.keys()) + for ax in axes_list: + if ax not in axes_dict_list: + raise Exception("Axis " + ax + " is requested but is not in axes_dict") - if self.time is None and N0 is None: - raise InputError("time and N0 can't be both None") + if axes_dict is None or len(axes_dict) == 0: + # Init axes_dict + axes_dict = dict() if machine is None: # Fetch machine from input - if ( - self.parent is not None - and hasattr(self.parent, "machine") - and self.parent.machine is not None - ): - machine = self.parent.machine + if hasattr(simu, "machine") and simu.machine is not None: + machine = simu.machine + else: + raise Exception("Cannot calculate axes if simu.machine is None") # Get machine pole pair number p = machine.get_pole_pair_number() - # Get electrical fundamental frequency - f_elec = self.comp_felec() + if ("time" in axes_list and is_periodicity_t) or ( + "angle" in axes_list and is_periodicity_a + ): + # Fill periodicity parameters that are None + if ( + per_a is None + or is_antiper_a is None + or per_t is None + or is_antiper_t is None + ): + # Get time and space (anti-)periodicities of the machine + ( + per_a_0, + is_antiper_a_0, + per_t_0, + is_antiper_t_0, + ) = output.get_machine_periodicity() + # Enforce None values to machine periodicity + per_a = per_a_0 if per_a is None else per_a + is_antiper_a = is_antiper_a_0 if is_antiper_a is None else is_antiper_a + per_t = per_t_0 if per_t is None else per_t + is_antiper_t = is_antiper_t_0 if is_antiper_t is None else is_antiper_t - # Airgap radius - Rag = machine.comp_Rgap_mec() + if "time" in axes_list: - # Setup normalizations for time and angle axes - norm_time = { - "elec_order": Norm_ref(ref=f_elec), - "mech_order": Norm_ref(ref=f_elec / p), - } + if is_periodicity_t is None: + # Enforce requested time periodicity + is_periodicity_t = per_t > 1 or is_antiper_t + elif not is_periodicity_t: + # Remove time periodicity + per_t = 1 + is_antiper_t = False - norm_angle = {"space_order": Norm_ref(ref=p), "distance": Norm_ref(ref=1 / Rag)} + # Get electrical fundamental frequency + f_elec = self.comp_felec() - # Create time axis - if self.time is None: - # Create time axis as a DataLinspace - if self.Nrev is not None: - t_final = 60 / N0 * self.Nrev - else: - t_final = p / f_elec - # Create time axis as a DataLinspace - Time = DataLinspace( - name="time", - unit="s", - initial=0, - final=t_final, - number=self.Nt_tot, - include_endpoint=False, - normalizations=norm_time, - ) - # Add time (anti-)periodicity - if per_t > 1 or is_antiper_t: - Time = Time.get_axis_periodic(per_t, is_antiper_t) - else: - # Load time data - time = self.time.get_data() - self.Nt_tot = len(time) - Time = Data1D(name="time", unit="s", values=time, normalizations=norm_time) - # Add time (anti-)periodicity - sym_t = dict() - if is_antiper_t: - sym_t["antiperiod"] = per_t + # Setup normalizations for time and angle axes + norm_time = { + "elec_order": Norm_ref(ref=f_elec), + "mech_order": Norm_ref(ref=f_elec / p), + } + + if "time" in axes_dict: + # Compute Time axis based on the one stored in OutElec + Time = axes_dict["time"].get_axis_periodic(Nper=per_t, is_aper=is_antiper_t) + Time.normalizations = norm_time + + # Create time axis + elif self.time is None: + # Create time axis as a DataLinspace + if self.Nrev is not None: + if self.N0 is not None: + t_final = 60 / self.N0 * self.Nrev + else: + raise InputError("time and N0 can't be both None") + else: + t_final = p / f_elec + # Create time axis as a DataLinspace + Time = DataLinspace( + name="time", + unit="s", + initial=0, + final=t_final, + number=self.Nt_tot, + include_endpoint=False, + normalizations=norm_time, + ) + # Add time (anti-)periodicity + if per_t > 1 or is_antiper_t: + Time = Time.get_axis_periodic(per_t, is_antiper_t) else: - sym_t["period"] = per_t - Time.symmetries = sym_t - - # Create angle axis - if self.angle is None: - # Create angle axis as a DataLinspace - Angle = DataLinspace( - name="angle", - unit="rad", - initial=0, - final=2 * pi, - number=self.Na_tot, - include_endpoint=False, - normalizations=norm_angle, - ) - # Add angle (anti-)periodicity - if per_a > 1 or is_antiper_a: - Angle = Angle.get_axis_periodic(per_a, is_antiper_a) + # Load time data + time = self.time.get_data() + self.Nt_tot = len(time) + Time = Data1D(name="time", unit="s", values=time, normalizations=norm_time) + # Add time (anti-)periodicity + sym_t = dict() + if is_antiper_t: + sym_t["antiperiod"] = per_t + else: + sym_t["period"] = per_t + Time.symmetries = sym_t + Time = Time.to_linspace() + + # Compute angle_rotor (added to time normalizations) + output.comp_angle_rotor(Time) + + # Store time axis in dict + axes_dict["time"] = Time + + if "angle" in axes_list: + + if is_periodicity_a is None: + # Enforce requested angle periodicity + is_periodicity_a = per_a > 1 or is_antiper_a + elif not is_periodicity_a: + # Remove angle periodicity + per_a = 1 + is_antiper_a = False + + # Airgap radius + Rag = machine.comp_Rgap_mec() + + norm_angle = {"space_order": Norm_ref(ref=p), "distance": Norm_ref(ref=1 / Rag)} + + if "angle" in axes_dict: + # Compute Angle axis based on the one stored in OutElec + Angle = axes_dict["angle"].get_axis_periodic( + Nper=per_a, is_aper=is_antiper_a + ) + + # Create angle axis + elif self.angle is None: + + # Create angle axis as a DataLinspace + Angle = DataLinspace( + name="angle", + unit="rad", + initial=0, + final=2 * pi, + number=self.Na_tot, + include_endpoint=False, + normalizations=norm_angle, + ) + # Add angle (anti-)periodicity + if per_a > 1 or is_antiper_a: + Angle = Angle.get_axis_periodic(per_a, is_antiper_a) - else: - # Load angle data - angle = self.angle.get_data() - self.Na_tot = len(angle) - Angle = Data1D( - name="angle", unit="rad", values=angle, normalizations=norm_angle - ) - # Add angle (anti-)periodicity - sym_a = dict() - if is_antiper_a: - sym_a["antiperiod"] = per_a else: - sym_a["period"] = per_a - Angle.symmetries = sym_a + # Load angle data + angle = self.angle.get_data() + self.Na_tot = len(angle) + Angle = Data1D( + name="angle", unit="rad", values=angle, normalizations=norm_angle + ) + # Add angle (anti-)periodicity + sym_a = dict() + if is_antiper_a: + sym_a["antiperiod"] = per_a + else: + sym_a["period"] = per_a + Angle.symmetries = sym_a + Angle = Angle.to_linspace() - # Compute angle_rotor (added to time normalizations) - self.parent.parent.comp_angle_rotor(Time) + # Store angle axis in dict + axes_dict["angle"] = Angle + + if "phase" in axes_list: + + qs = machine.stator.winding.qs + + # Creating the data object + Phase = Data1D( + name="phase", + unit="", + values=gen_name(qs), + is_components=True, + ) - axes_dict = {"time": Time, "angle": Angle} + # Store phase axis in dict + axes_dict["phase"] = Phase return axes_dict diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 3e07350b0..9b724e314 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -87,20 +87,13 @@ def gen_input(self): if self.Tem_av_ref is not None: outelec.Tem_av_ref = self.Tem_av_ref - # Set time and angle full axes in geometry output - axes_dict = self.comp_axes() - # Store in axes_dict - outgeo.axes_dict = axes_dict - - # Create time axis in electrical output without periodicity - # TODO: account for pole periodicity - Time_elec, _ = axes_dict["time"].get_axis_periodic( - Nper=1, # int(2 * simu.machine.get_pole_pair_number()), - is_aper=False, # True, - is_include_per=False, # True, + # Calculate time, angle and phase axes and store them in OutGeo + outgeo.axes_dict = self.comp_axes(axes_list=["time", "angle", "phase"]) + + # Create time axis for electrical model including periodicity + outelec.axes_dict = self.comp_axes( + axes_list=["time"], axes_dict=outgeo.axes_dict, is_periodicity_t=True ) - # Store in axes_dict - outelec.axes_dict = {"time": Time_elec} # Generate Us # if qs > 0: diff --git a/pyleecan/Methods/Simulation/Magnetics/comp_axes.py b/pyleecan/Methods/Simulation/Magnetics/comp_axes.py index 76b36a833..693419fdc 100644 --- a/pyleecan/Methods/Simulation/Magnetics/comp_axes.py +++ b/pyleecan/Methods/Simulation/Magnetics/comp_axes.py @@ -18,64 +18,53 @@ def comp_axes(self, output): # Get axis dict from OutGeo axes_dict_geo = output.geo.axes_dict - # Calculate axes for Magnetics module calculation - # Get time and space (anti-)periodicities of the machine - ( - per_a, - is_antiper_a, - per_t, - is_antiper_t, - ) = output.get_machine_periodicity() - - # Compute Time axis based on the one stored in OutElec - Time, is_periodicity_t = axes_dict_geo["time"].get_axis_periodic( - Nper=per_t, - is_aper=is_antiper_t, - is_include_per=self.is_periodicity_t, - is_remove_aper=False, + # Add periodicities to time and angle axes + axes_dict = self.parent.input.comp_axes( + axes_list=["time", "angle"], + axes_dict=axes_dict_geo, + is_periodicity_a=self.is_periodicity_a, + is_periodicity_t=self.is_periodicity_t, ) - if is_periodicity_t != self.is_periodicity_t: + # Check Time periodicities regarding Magnetics model input + per_t0, is_antiper_t0 = axes_dict["time"].get_periodicity() + is_periodicity_t0 = per_t0 > 1 or is_antiper_t0 + if is_periodicity_t0 != self.is_periodicity_t: # Remove time periodicity in Magnetic model self.is_periodicity_t = False - Nt_tot = Time.get_length(is_oneperiod=False) + Nt_tot = axes_dict["time"].get_length(is_oneperiod=False) self.get_logger().warning( - "WARNING: In Magnetic model, Nt_tot=" + "In Magnetic model, Nt_tot=" + str(Nt_tot) + " is not divisible by the machine time periodicity (" - + str(per_t) + + str(output.geo.per_t_S) + "). Time periodicity removed" ) - # Compute Angle axis based on the one stored in OutElec - Angle, is_periodicity_a = axes_dict_geo["angle"].get_axis_periodic( - Nper=per_a, - is_aper=is_antiper_a, - is_include_per=self.is_periodicity_a, - is_remove_aper=False, - ) - - if is_periodicity_a != self.is_periodicity_a: + # Check Angle periodicities regarding Magnetics model input + per_a0, is_antiper_a0 = axes_dict["angle"].get_periodicity() + is_periodicity_a0 = per_a0 > 1 or is_antiper_a0 + if is_periodicity_a0 != self.is_periodicity_a: # Remove time periodicity in Magnetic model self.is_periodicity_a = False - Na_tot = Angle.get_length(is_oneperiod=False) + Na_tot = axes_dict["angle"].get_length(is_oneperiod=False) self.get_logger().warning( - "WARNING: In Magnetic model, Na_tot=" + "In Magnetic model, Na_tot=" + str(Na_tot) + " is not divisible by the machine angular periodicity (" - + str(per_a) + + str(output.geo.per_a) + "). Angular periodicity removed" ) # Add Time axis on which to calculate torque # Copy from standard Time axis - Time_Tem = Time.copy() + Time_Tem = axes_dict["time"].copy() # Remove anti-periodicity if any if "antiperiod" in Time_Tem.symmetries: Time_Tem.symmetries["period"] = Time_Tem.symmetries.pop("antiperiod") # Store in axis dict - axes_dict = {"time": Time, "angle": Angle, "time_Tem": Time_Tem} + axes_dict["time_Tem"] = Time_Tem return axes_dict From e959a6dd14f46d0dca72d234e98e50965db95f91 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 6 Oct 2021 19:41:13 +0200 Subject: [PATCH 054/167] [BC] better check periodicity --- .../Methods/Simulation/Input/comp_axes.py | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/pyleecan/Methods/Simulation/Input/comp_axes.py b/pyleecan/Methods/Simulation/Input/comp_axes.py index a605a5a6f..4a03f4e72 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axes.py +++ b/pyleecan/Methods/Simulation/Input/comp_axes.py @@ -93,34 +93,25 @@ def comp_axes( # Get machine pole pair number p = machine.get_pole_pair_number() - if ("time" in axes_list and is_periodicity_t) or ( - "angle" in axes_list and is_periodicity_a - ): - # Fill periodicity parameters that are None - if ( - per_a is None - or is_antiper_a is None - or per_t is None - or is_antiper_t is None - ): - # Get time and space (anti-)periodicities of the machine - ( - per_a_0, - is_antiper_a_0, - per_t_0, - is_antiper_t_0, - ) = output.get_machine_periodicity() - # Enforce None values to machine periodicity - per_a = per_a_0 if per_a is None else per_a - is_antiper_a = is_antiper_a_0 if is_antiper_a is None else is_antiper_a - per_t = per_t_0 if per_t is None else per_t - is_antiper_t = is_antiper_t_0 if is_antiper_t is None else is_antiper_t + # Fill periodicity parameters that are None + if per_a is None or is_antiper_a is None or per_t is None or is_antiper_t is None: + # Get time and space (anti-)periodicities of the machine + ( + per_a_0, + is_antiper_a_0, + per_t_0, + is_antiper_t_0, + ) = output.get_machine_periodicity() if "time" in axes_list: - if is_periodicity_t is None: - # Enforce requested time periodicity - is_periodicity_t = per_t > 1 or is_antiper_t + if is_periodicity_t is None or is_periodicity_t: + # Enforce None values to machine time periodicity + per_t = per_t_0 if per_t is None else per_t + is_antiper_t = is_antiper_t_0 if is_antiper_t is None else is_antiper_t + if is_periodicity_t is None: + # Check time periodicity is included + is_periodicity_t = per_t > 1 or is_antiper_t elif not is_periodicity_t: # Remove time periodicity per_t = 1 @@ -185,9 +176,13 @@ def comp_axes( if "angle" in axes_list: - if is_periodicity_a is None: - # Enforce requested angle periodicity - is_periodicity_a = per_a > 1 or is_antiper_a + if is_periodicity_a is None or is_periodicity_a: + # Enforce None values to machine periodicity + per_a = per_a_0 if per_a is None else per_a + is_antiper_a = is_antiper_a_0 if is_antiper_a is None else is_antiper_a + if is_periodicity_a is None: + # Enforce requested angle periodicity + is_periodicity_a = per_a > 1 or is_antiper_a elif not is_periodicity_a: # Remove angle periodicity per_a = 1 From 7a58ed1cccb09c0f64ae1450bc1f63070ca63b0a Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Thu, 7 Oct 2021 09:16:43 +0200 Subject: [PATCH 055/167] [BC] Correct tests --- Tests/Simulation/test_StructElmer.py | 19 +++++++++---------- .../Simulation/InputVoltage/gen_input.py | 8 ++++++-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Tests/Simulation/test_StructElmer.py b/Tests/Simulation/test_StructElmer.py index 04adfdd44..fc0861e44 100644 --- a/Tests/Simulation/test_StructElmer.py +++ b/Tests/Simulation/test_StructElmer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from os.path import join from numpy import pi import pytest @@ -10,8 +9,8 @@ from pyleecan.Classes.MachineSIPMSM import MachineSIPMSM from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.StructElmer import StructElmer +from pyleecan.Classes.InputVoltage import InputVoltage from pyleecan.Classes.Output import Output -from pyleecan.Classes.MeshSolution import MeshSolution from pyleecan.Functions.load import load @@ -69,7 +68,7 @@ def test_HoleM50(self): simu.struct.is_get_mesh = True # set rotor speed and run simulation - simu.input.N0 = 10000 # rpm + simu.input = InputVoltage(N0=10000) # rpm simu.run() return output @@ -95,7 +94,7 @@ def test_HoleM50_no_magnets(self): simu.struct.is_get_mesh = True # set rotor speed and run simulation - simu.input.N0 = 10000 # rpm + simu.input = InputVoltage(N0=10000) # rpm simu.run() return output @@ -128,7 +127,7 @@ def test_disk_geometry(self): simu.struct.is_get_mesh = True # set rotor speed and run simulation - simu.input.N0 = 10000 # rpm + simu.input = InputVoltage(N0=10000) # rpm simu.run() return output @@ -140,12 +139,12 @@ def test_disk_geometry(self): obj = Test_StructElmer() # test Toyota_Prius (HoleM50-Rotor) with minor modification out = obj.test_HoleM50() - # out = obj.test_HoleM50_wo_magnets() + out = obj.test_HoleM50_wo_magnets() # test centrifugal force on a disc - # out = obj.test_disk_geometry() + out = obj.test_disk_geometry() - # plot some results - out.struct.meshsolution.plot_deflection(label="disp", factor=20) - # out.struct.meshsolution.plot_contour(label='disp') + # # plot some results + # out.struct.meshsolution.plot_deflection(label="disp", factor=20) + # out.struct.meshsolution.plot_contour(label="disp") # out.struct.meshsolution.plot_mesh() diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 9b724e314..a9adebdfc 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -88,11 +88,15 @@ def gen_input(self): outelec.Tem_av_ref = self.Tem_av_ref # Calculate time, angle and phase axes and store them in OutGeo - outgeo.axes_dict = self.comp_axes(axes_list=["time", "angle", "phase"]) + outgeo.axes_dict = self.comp_axes( + axes_list=["time", "angle", "phase"], + is_periodicity_a=False, + is_periodicity_t=False, + ) # Create time axis for electrical model including periodicity outelec.axes_dict = self.comp_axes( - axes_list=["time"], axes_dict=outgeo.axes_dict, is_periodicity_t=True + axes_list=["time"], axes_dict=outgeo.axes_dict, is_periodicity_t=False ) # Generate Us From ba74a6fe084253a6dd99688d14f521d529ca31a0 Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 7 Oct 2021 11:02:44 +0200 Subject: [PATCH 056/167] [BC] compute angle rotor on smallest period, then use SciDataTool to get whole signal --- pyleecan/Methods/Output/OutElec/get_Nr.py | 2 +- pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyleecan/Methods/Output/OutElec/get_Nr.py b/pyleecan/Methods/Output/OutElec/get_Nr.py index d33752748..f1e8ba799 100644 --- a/pyleecan/Methods/Output/OutElec/get_Nr.py +++ b/pyleecan/Methods/Output/OutElec/get_Nr.py @@ -27,6 +27,6 @@ def get_Nr(self, Time=None): raise Exception('You must define "N0" before calling get_Nr') # Same speed for every timestep - Nr = self.N0 * ones(Time.get_length(is_oneperiod=False)) + Nr = self.N0 * ones(Time.get_length(is_smallestperiod=True)) return Nr diff --git a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py index 9d9a4b39f..4e871b493 100644 --- a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py +++ b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py @@ -38,7 +38,7 @@ def comp_angle_rotor(self, Time): else: - time = Time.get_values(is_oneperiod=False) + time = Time.get_values(is_smallestperiod=True) if time.size == 1: # Only one time step, no need to compute the position angle_rotor = ones(1) * A0 @@ -54,4 +54,4 @@ def comp_angle_rotor(self, Time): # Store in time axis normalizations Time.normalizations["angle_rotor"] = Norm_vector(vector=angle_rotor) - return angle_rotor + return Time.get_values(normalization="angle_rotor") From 47c8fcbe77a3e6adce78a25e3612f9568ab816ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl?= Date: Thu, 7 Oct 2021 13:05:03 +0200 Subject: [PATCH 057/167] [CO] Move comp_I_mag as Magnetics method --- pyleecan/Classes/Class_Dict.json | 4 ++-- pyleecan/Classes/Magnetics.py | 14 ++++++++++++++ pyleecan/Classes/OutElec.py | 14 -------------- pyleecan/Generator/ClassesRef/Output/OutElec.csv | 14 +++++++------- .../Generator/ClassesRef/Simulation/Magnetics.csv | 2 +- pyleecan/Methods/Output/OutElec/get_Is.py | 2 +- .../OutElec => Simulation/Magnetics}/comp_I_mag.py | 12 +++++++----- pyleecan/Methods/Simulation/Magnetics/run.py | 6 ++++-- 8 files changed, 36 insertions(+), 32 deletions(-) rename pyleecan/Methods/{Output/OutElec => Simulation/Magnetics}/comp_I_mag.py (89%) diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 4bcfea721..305733747 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -6160,7 +6160,8 @@ "is_internal": false, "methods": [ "run", - "comp_axes" + "comp_axes", + "comp_I_mag" ], "mother": "", "name": "Magnetics", @@ -7861,7 +7862,6 @@ "desc": "Gather the electric module outputs", "is_internal": false, "methods": [ - "comp_I_mag", "get_I_fund", "get_I_harm", "get_Is", diff --git a/pyleecan/Classes/Magnetics.py b/pyleecan/Classes/Magnetics.py index 5b718e251..0ee7862cd 100644 --- a/pyleecan/Classes/Magnetics.py +++ b/pyleecan/Classes/Magnetics.py @@ -27,6 +27,11 @@ except ImportError as error: comp_axes = error +try: + from ..Methods.Simulation.Magnetics.comp_I_mag import comp_I_mag +except ImportError as error: + comp_I_mag = error + from ._check import InitUnKnowClassError @@ -55,6 +60,15 @@ class Magnetics(FrozenClass): ) else: comp_axes = comp_axes + # cf Methods.Simulation.Magnetics.comp_I_mag + if isinstance(comp_I_mag, ImportError): + comp_I_mag = property( + fget=lambda x: raise_( + ImportError("Can't use Magnetics method comp_I_mag: " + str(comp_I_mag)) + ) + ) + else: + comp_I_mag = comp_I_mag # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/OutElec.py b/pyleecan/Classes/OutElec.py index 8e6618df5..aacb9da8f 100644 --- a/pyleecan/Classes/OutElec.py +++ b/pyleecan/Classes/OutElec.py @@ -17,11 +17,6 @@ # Import all class method # Try/catch to remove unnecessary dependencies in unused method -try: - from ..Methods.Output.OutElec.comp_I_mag import comp_I_mag -except ImportError as error: - comp_I_mag = error - try: from ..Methods.Output.OutElec.get_I_fund import get_I_fund except ImportError as error: @@ -64,15 +59,6 @@ class OutElec(FrozenClass): VERSION = 1 # Check ImportError to remove unnecessary dependencies in unused method - # cf Methods.Output.OutElec.comp_I_mag - if isinstance(comp_I_mag, ImportError): - comp_I_mag = property( - fget=lambda x: raise_( - ImportError("Can't use OutElec method comp_I_mag: " + str(comp_I_mag)) - ) - ) - else: - comp_I_mag = comp_I_mag # cf Methods.Output.OutElec.get_I_fund if isinstance(get_I_fund, ImportError): get_I_fund = property( diff --git a/pyleecan/Generator/ClassesRef/Output/OutElec.csv b/pyleecan/Generator/ClassesRef/Output/OutElec.csv index 7bbcc058f..4ba0c1a25 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutElec.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutElec.csv @@ -1,11 +1,11 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -axes_dict,,Dict containing axes data used for Electrical,,{SciDataTool.Classes.DataND.Data},None,,,,Output,,comp_I_mag,VERSION,1,Gather the electric module outputs -Is,A,Stator currents DataTime object,"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_I_fund,,, -Ir,A,Rotor currents as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_I_harm,,, -angle_rotor,rad,Rotor angular position as a function of time (if None computed according to Nr),Nt_tot,ndarray,None,,,,,,get_Is,,, -N0,rpm,Rotor speed,1,float,None,,,,,,get_Nr,,, -angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,get_Us,,, -logger_name,-,Name of the logger to use,0,str,Pyleecan.Electrical,,,,,,store,,, +axes_dict,,Dict containing axes data used for Electrical,,{SciDataTool.Classes.DataND.Data},None,,,,Output,,get_I_fund,VERSION,1,Gather the electric module outputs +Is,A,Stator currents DataTime object,"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_I_harm,,, +Ir,A,Rotor currents as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Is,,, +angle_rotor,rad,Rotor angular position as a function of time (if None computed according to Nr),Nt_tot,ndarray,None,,,,,,get_Nr,,, +N0,rpm,Rotor speed,1,float,None,,,,,,get_Us,,, +angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,store,,, +logger_name,-,Name of the logger to use,0,str,Pyleecan.Electrical,,,,,,,,, Tem_av_ref,N.m,Theorical Average Electromagnetic torque,,float,None,,,,,,,,, Id_ref,Arms,d-axis current rms value,1,float,None,,,,,,,,, Iq_ref,Arms,q-axis current rms value,1,float,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/Magnetics.csv b/pyleecan/Generator/ClassesRef/Simulation/Magnetics.csv index be6fb759a..5be62c26f 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/Magnetics.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/Magnetics.csv @@ -1,7 +1,7 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille is_remove_slotS,-,1 to artificially remove stator slotting effects in permeance mmf calculations,0,bool,0,,,,Simulation,,run,VERSION,1,Magnetic module abstract object,MagFEMM is_remove_slotR,-,1 to artificially remove rotor slotting effects in permeance mmf calculations,0,bool,0,,,,,,comp_axes,,,,MagElmer -is_remove_vent,-,1 to artificially remove the ventilations duct,0,bool,0,,,,,,,,,, +is_remove_vent,-,1 to artificially remove the ventilations duct,0,bool,0,,,,,,comp_I_mag,,,, is_mmfs,-,1 to compute the stator magnetomotive force / stator armature magnetic field,0,bool,1,,,,,,,,,, is_mmfr,-,1 to compute the rotor magnetomotive force / rotor magnetic field,0,bool,1,,,,,,,,,, type_BH_stator,-,"0 to use the B(H) curve, 1 to use linear B(H) curve according to mur_lin, 2 to enforce infinite permeability (mur_lin =100000)",0,int,0,0,2,,,,,,,, diff --git a/pyleecan/Methods/Output/OutElec/get_Is.py b/pyleecan/Methods/Output/OutElec/get_Is.py index d88a1cb10..deacab066 100644 --- a/pyleecan/Methods/Output/OutElec/get_Is.py +++ b/pyleecan/Methods/Output/OutElec/get_Is.py @@ -10,7 +10,7 @@ def get_Is(self, Time=None, is_current_harm=False): # Calculate stator currents if Is is not in OutElec if self.Is is None or not is_current_harm: Is = self.get_I_fund(Time=Time) - + if self.Is is None: self.Is = Is diff --git a/pyleecan/Methods/Output/OutElec/comp_I_mag.py b/pyleecan/Methods/Simulation/Magnetics/comp_I_mag.py similarity index 89% rename from pyleecan/Methods/Output/OutElec/comp_I_mag.py rename to pyleecan/Methods/Simulation/Magnetics/comp_I_mag.py index 1ba6f6389..8ed6c4354 100644 --- a/pyleecan/Methods/Output/OutElec/comp_I_mag.py +++ b/pyleecan/Methods/Simulation/Magnetics/comp_I_mag.py @@ -2,15 +2,17 @@ from numpy import array -def comp_I_mag(self, Time, is_stator, phase=None, I_data=None, is_periodicity_t=True): +def comp_I_mag( + self, output, Time, is_stator, phase=None, I_data=None, is_periodicity_t=True +): """Compute the current on the given lamination and time vector to use it in Magnetics model Phase currents are divided by the number of parallel circuits per pole and per phase to account for actual current in slot conductors Parameters ---------- - self : OutElec - an OutElec object + self : Magnetics + an Magnetics object Time : Data1D Time vector on which to interpolate currents stored in OutElec is_stator: bool @@ -33,9 +35,9 @@ def comp_I_mag(self, Time, is_stator, phase=None, I_data=None, is_periodicity_t= # Get lamination if is_stator: - lam = self.parent.simu.machine.stator + lam = output.simu.machine.stator else: - lam = self.parent.simu.machine.rotor + lam = output.simu.machine.rotor if ( hasattr(lam, "winding") diff --git a/pyleecan/Methods/Simulation/Magnetics/run.py b/pyleecan/Methods/Simulation/Magnetics/run.py index a42a5a6f3..c62503bcc 100644 --- a/pyleecan/Methods/Simulation/Magnetics/run.py +++ b/pyleecan/Methods/Simulation/Magnetics/run.py @@ -24,7 +24,8 @@ def run(self): Is = output.elec.get_Is( Time=axes_dict["time"], is_current_harm=self.is_current_harm ) - Is_val = output.elec.comp_I_mag( + Is_val = self.comp_I_mag( + output=output, Time=axes_dict["time"], is_stator=True, I_data=Is, @@ -35,7 +36,8 @@ def run(self): # Get rotor current from elec out if self.is_mmfr: # Ir = output.elec.get_Ir(Time=axes_dict["time"]) TODO - Ir_val = output.elec.comp_I_mag( + Ir_val = self.comp_I_mag( + output=output, Time=axes_dict["time"], is_stator=False, is_periodicity_t=self.is_periodicity_t, From a833ae2eb9d31d2abc6fc00a6c70b8a094a330bd Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 7 Oct 2021 15:05:05 +0200 Subject: [PATCH 058/167] [CO] use Norm_affine if Nr is constant --- .../Methods/Output/Output/getter/comp_angle_rotor.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py index 4e871b493..341845dfc 100644 --- a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py +++ b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py @@ -1,5 +1,5 @@ from numpy import pi, cumsum, roll, ones, unique -from SciDataTool import Norm_vector +from SciDataTool import Norm_vector, Norm_affine def comp_angle_rotor(self, Time): @@ -33,8 +33,8 @@ def comp_angle_rotor(self, Time): A0 = self.get_angle_offset_initial() # Case where normalization is a constant - if A0 == 0 and unique(Nr).size == 1: - angle_rotor = rot_dir * Nr[0] * 2 * pi / 60 + if unique(Nr).size == 1: + norm = Norm_affine(slope=rot_dir * Nr[0] * 360 / 60, offset=A0 * 180 / pi) else: @@ -50,8 +50,9 @@ def comp_angle_rotor(self, Time): Ar = roll(Ar, 1) Ar[0] = 0 angle_rotor = Ar + A0 + norm = Norm_vector(vector=angle_rotor) # Store in time axis normalizations - Time.normalizations["angle_rotor"] = Norm_vector(vector=angle_rotor) + Time.normalizations["angle_rotor"] = norm return Time.get_values(normalization="angle_rotor") From b75a7b1647806f7851724906b7d93a3338f1b6b1 Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 7 Oct 2021 15:05:53 +0200 Subject: [PATCH 059/167] [WiP] PostELUT object --- pyleecan/Classes/Class_Dict.json | 36 +++++ pyleecan/Classes/ELUT.py | 124 ++++++++++++++- pyleecan/Classes/ELUT_PMSM.py | 149 +++++++++++++++++- pyleecan/Classes/ELUT_SCIM.py | 10 +- .../Generator/ClassesRef/Simulation/ELUT.csv | 2 + .../ClassesRef/Simulation/ELUT_PMSM.csv | 3 +- pyleecan/Methods/Post/PostELUT/__init__.py | 0 pyleecan/Methods/Post/PostELUT/run.py | 95 +++++++++++ 8 files changed, 414 insertions(+), 5 deletions(-) create mode 100644 pyleecan/Methods/Post/PostELUT/__init__.py create mode 100644 pyleecan/Methods/Post/PostELUT/run.py diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 4bcfea721..022e3f39a 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -1292,6 +1292,24 @@ "type": "float", "unit": "degC", "value": 20 + }, + { + "desc": "Array of operating point values of size (N,5) with N the number of operating points (Speed, Id, Iq, Torque, Power). OP values are set to nan if they are not given.", + "max": "", + "min": "", + "name": "OP_matrix", + "type": "ndarray", + "unit": "", + "value": null + }, + { + "desc": "Dict containing axes data used for Magnetics", + "max": "", + "min": "", + "name": "axes_dict", + "type": "{SciDataTool.Classes.DataND.Data}", + "unit": "", + "value": "None" } ] }, @@ -1382,6 +1400,24 @@ "type": "SciDataTool.Classes.DataND.DataND", "unit": "V", "value": "None" + }, + { + "desc": "Stator winding flux look-up table: list of DataTime objects whose (Id,Iq) is given by Idq list", + "max": "", + "min": "", + "name": "Phi_wind_stator", + "type": "[SciDataTool.Classes.DataND.DataND]", + "unit": "Wb", + "value": "None" + }, + { + "desc": "Instaneous torque look-up table: list of DataTime objects whose (Id,Iq) is given by Idq list", + "max": "", + "min": "", + "name": "Tem", + "type": "[SciDataTool.Classes.DataND.DataND]", + "unit": "Nm", + "value": "None" } ] }, diff --git a/pyleecan/Classes/ELUT.py b/pyleecan/Classes/ELUT.py index 991db59d9..a3bba62de 100644 --- a/pyleecan/Classes/ELUT.py +++ b/pyleecan/Classes/ELUT.py @@ -7,7 +7,7 @@ from os import linesep from sys import getsizeof from logging import getLogger -from ._check import check_var, raise_ +from ._check import set_array, check_var, raise_ from ..Functions.get_logger import get_logger from ..Functions.save import save from ..Functions.copy import copy @@ -23,6 +23,7 @@ get_param_dict = error +from numpy import array, array_equal from ._check import InitUnKnowClassError @@ -48,7 +49,16 @@ class ELUT(FrozenClass): # get_logger method is available in all object get_logger = get_logger - def __init__(self, R1=None, L1=None, T1_ref=20, init_dict=None, init_str=None): + def __init__( + self, + R1=None, + L1=None, + T1_ref=20, + OP_matrix=None, + axes_dict=None, + init_dict=None, + init_str=None, + ): """Constructor of the class. Can be use in three ways : - __init__ (arg1 = 1, arg3 = 5) every parameters have name and default values for pyleecan type, -1 will call the default constructor @@ -70,11 +80,17 @@ def __init__(self, R1=None, L1=None, T1_ref=20, init_dict=None, init_str=None): L1 = init_dict["L1"] if "T1_ref" in list(init_dict.keys()): T1_ref = init_dict["T1_ref"] + if "OP_matrix" in list(init_dict.keys()): + OP_matrix = init_dict["OP_matrix"] + if "axes_dict" in list(init_dict.keys()): + axes_dict = init_dict["axes_dict"] # Set the properties (value check and convertion are done in setter) self.parent = None self.R1 = R1 self.L1 = L1 self.T1_ref = T1_ref + self.OP_matrix = OP_matrix + self.axes_dict = axes_dict # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -90,6 +106,14 @@ def __str__(self): ELUT_str += "R1 = " + str(self.R1) + linesep ELUT_str += "L1 = " + str(self.L1) + linesep ELUT_str += "T1_ref = " + str(self.T1_ref) + linesep + ELUT_str += ( + "OP_matrix = " + + linesep + + str(self.OP_matrix).replace(linesep, linesep + "\t") + + linesep + + linesep + ) + ELUT_str += "axes_dict = " + str(self.axes_dict) + linesep + linesep return ELUT_str def __eq__(self, other): @@ -103,6 +127,10 @@ def __eq__(self, other): return False if other.T1_ref != self.T1_ref: return False + if not array_equal(other.OP_matrix, self.OP_matrix): + return False + if other.axes_dict != self.axes_dict: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -119,6 +147,23 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".L1") if other._T1_ref != self._T1_ref: diff_list.append(name + ".T1_ref") + if not array_equal(other.OP_matrix, self.OP_matrix): + diff_list.append(name + ".OP_matrix") + if (other.axes_dict is None and self.axes_dict is not None) or ( + other.axes_dict is not None and self.axes_dict is None + ): + diff_list.append(name + ".axes_dict None mismatch") + elif self.axes_dict is None: + pass + elif len(other.axes_dict) != len(self.axes_dict): + diff_list.append("len(" + name + "axes_dict)") + else: + for key in self.axes_dict: + diff_list.extend( + self.axes_dict[key].compare( + other.axes_dict[key], name=name + ".axes_dict" + ) + ) # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -130,6 +175,10 @@ def __sizeof__(self): S += getsizeof(self.R1) S += getsizeof(self.L1) S += getsizeof(self.T1_ref) + S += getsizeof(self.OP_matrix) + if self.axes_dict is not None: + for key, value in self.axes_dict.items(): + S += getsizeof(value) + getsizeof(key) return S def as_dict(self, **kwargs): @@ -143,6 +192,19 @@ def as_dict(self, **kwargs): ELUT_dict["R1"] = self.R1 ELUT_dict["L1"] = self.L1 ELUT_dict["T1_ref"] = self.T1_ref + if self.OP_matrix is None: + ELUT_dict["OP_matrix"] = None + else: + ELUT_dict["OP_matrix"] = self.OP_matrix.tolist() + if self.axes_dict is None: + ELUT_dict["axes_dict"] = None + else: + ELUT_dict["axes_dict"] = dict() + for key, obj in self.axes_dict.items(): + if obj is not None: + ELUT_dict["axes_dict"][key] = obj.as_dict() + else: + ELUT_dict["axes_dict"][key] = None # The class name is added to the dict for deserialisation purpose ELUT_dict["__class__"] = "ELUT" return ELUT_dict @@ -153,6 +215,8 @@ def _set_None(self): self.R1 = None self.L1 = None self.T1_ref = None + self.OP_matrix = None + self.axes_dict = None def _get_R1(self): """getter of R1""" @@ -207,3 +271,59 @@ def _set_T1_ref(self, value): :Type: float """, ) + + def _get_OP_matrix(self): + """getter of OP_matrix""" + return self._OP_matrix + + def _set_OP_matrix(self, value): + """setter of OP_matrix""" + if type(value) is int and value == -1: + value = array([]) + elif type(value) is list: + try: + value = array(value) + except: + pass + check_var("OP_matrix", value, "ndarray") + self._OP_matrix = value + + OP_matrix = property( + fget=_get_OP_matrix, + fset=_set_OP_matrix, + doc=u"""Array of operating point values of size (N,5) with N the number of operating points (Speed, Id, Iq, Torque, Power). OP values are set to nan if they are not given. + + :Type: ndarray + """, + ) + + def _get_axes_dict(self): + """getter of axes_dict""" + if self._axes_dict is not None: + for key, obj in self._axes_dict.items(): + if obj is not None: + obj.parent = self + return self._axes_dict + + def _set_axes_dict(self, value): + """setter of axes_dict""" + if type(value) is dict: + for key, obj in value.items(): + if type(obj) is dict: + class_obj = import_class( + "SciDataTool.Classes", obj.get("__class__"), "axes_dict" + ) + value[key] = class_obj(init_dict=obj) + if type(value) is int and value == -1: + value = dict() + check_var("axes_dict", value, "{Data}") + self._axes_dict = value + + axes_dict = property( + fget=_get_axes_dict, + fset=_set_axes_dict, + doc=u"""Dict containing axes data used for Magnetics + + :Type: {SciDataTool.Classes.DataND.Data} + """, + ) diff --git a/pyleecan/Classes/ELUT_PMSM.py b/pyleecan/Classes/ELUT_PMSM.py index 2fdb9f413..fc20ab0bd 100644 --- a/pyleecan/Classes/ELUT_PMSM.py +++ b/pyleecan/Classes/ELUT_PMSM.py @@ -165,9 +165,13 @@ def __init__( E_dqh=None, orders_dqh=None, bemf=None, + Phi_wind_stator=None, + Tem=None, R1=None, L1=None, T1_ref=20, + OP_matrix=None, + axes_dict=None, init_dict=None, init_str=None, ): @@ -200,12 +204,20 @@ def __init__( orders_dqh = init_dict["orders_dqh"] if "bemf" in list(init_dict.keys()): bemf = init_dict["bemf"] + if "Phi_wind_stator" in list(init_dict.keys()): + Phi_wind_stator = init_dict["Phi_wind_stator"] + if "Tem" in list(init_dict.keys()): + Tem = init_dict["Tem"] if "R1" in list(init_dict.keys()): R1 = init_dict["R1"] if "L1" in list(init_dict.keys()): L1 = init_dict["L1"] if "T1_ref" in list(init_dict.keys()): T1_ref = init_dict["T1_ref"] + if "OP_matrix" in list(init_dict.keys()): + OP_matrix = init_dict["OP_matrix"] + if "axes_dict" in list(init_dict.keys()): + axes_dict = init_dict["axes_dict"] # Set the properties (value check and convertion are done in setter) self.Phi_dqh = Phi_dqh self.I_dqh = I_dqh @@ -214,8 +226,12 @@ def __init__( self.E_dqh = E_dqh self.orders_dqh = orders_dqh self.bemf = bemf + self.Phi_wind_stator = Phi_wind_stator + self.Tem = Tem # Call ELUT init - super(ELUT_PMSM, self).__init__(R1=R1, L1=L1, T1_ref=T1_ref) + super(ELUT_PMSM, self).__init__( + R1=R1, L1=L1, T1_ref=T1_ref, OP_matrix=OP_matrix, axes_dict=axes_dict + ) # The class is frozen (in ELUT init), for now it's impossible to # add new properties @@ -255,6 +271,10 @@ def __str__(self): + linesep ) ELUT_PMSM_str += "bemf = " + str(self.bemf) + linesep + linesep + ELUT_PMSM_str += ( + "Phi_wind_stator = " + str(self.Phi_wind_stator) + linesep + linesep + ) + ELUT_PMSM_str += "Tem = " + str(self.Tem) + linesep + linesep return ELUT_PMSM_str def __eq__(self, other): @@ -280,6 +300,10 @@ def __eq__(self, other): return False if other.bemf != self.bemf: return False + if other.Phi_wind_stator != self.Phi_wind_stator: + return False + if other.Tem != self.Tem: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -311,6 +335,37 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".bemf None mismatch") elif self.bemf is not None: diff_list.extend(self.bemf.compare(other.bemf, name=name + ".bemf")) + if (other.Phi_wind_stator is None and self.Phi_wind_stator is not None) or ( + other.Phi_wind_stator is not None and self.Phi_wind_stator is None + ): + diff_list.append(name + ".Phi_wind_stator None mismatch") + elif self.Phi_wind_stator is None: + pass + elif len(other.Phi_wind_stator) != len(self.Phi_wind_stator): + diff_list.append("len(" + name + ".Phi_wind_stator)") + else: + for ii in range(len(other.Phi_wind_stator)): + diff_list.extend( + self.Phi_wind_stator[ii].compare( + other.Phi_wind_stator[ii], + name=name + ".Phi_wind_stator[" + str(ii) + "]", + ) + ) + if (other.Tem is None and self.Tem is not None) or ( + other.Tem is not None and self.Tem is None + ): + diff_list.append(name + ".Tem None mismatch") + elif self.Tem is None: + pass + elif len(other.Tem) != len(self.Tem): + diff_list.append("len(" + name + ".Tem)") + else: + for ii in range(len(other.Tem)): + diff_list.extend( + self.Tem[ii].compare( + other.Tem[ii], name=name + ".Tem[" + str(ii) + "]" + ) + ) # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -331,6 +386,12 @@ def __sizeof__(self): S += getsizeof(self.E_dqh) S += getsizeof(self.orders_dqh) S += getsizeof(self.bemf) + if self.Phi_wind_stator is not None: + for value in self.Phi_wind_stator: + S += getsizeof(value) + if self.Tem is not None: + for value in self.Tem: + S += getsizeof(value) return S def as_dict(self, **kwargs): @@ -361,6 +422,24 @@ def as_dict(self, **kwargs): ELUT_PMSM_dict["bemf"] = None else: ELUT_PMSM_dict["bemf"] = self.bemf.as_dict() + if self.Phi_wind_stator is None: + ELUT_PMSM_dict["Phi_wind_stator"] = None + else: + ELUT_PMSM_dict["Phi_wind_stator"] = list() + for obj in self.Phi_wind_stator: + if obj is not None: + ELUT_PMSM_dict["Phi_wind_stator"].append(obj.as_dict()) + else: + ELUT_PMSM_dict["Phi_wind_stator"].append(None) + if self.Tem is None: + ELUT_PMSM_dict["Tem"] = None + else: + ELUT_PMSM_dict["Tem"] = list() + for obj in self.Tem: + if obj is not None: + ELUT_PMSM_dict["Tem"].append(obj.as_dict()) + else: + ELUT_PMSM_dict["Tem"].append(None) # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name ELUT_PMSM_dict["__class__"] = "ELUT_PMSM" @@ -376,6 +455,8 @@ def _set_None(self): self.E_dqh = None self.orders_dqh = None self.bemf = None + self.Phi_wind_stator = None + self.Tem = None # Set to None the properties inherited from ELUT super(ELUT_PMSM, self)._set_None() @@ -536,3 +617,69 @@ def _set_bemf(self, value): :Type: SciDataTool.Classes.DataND.DataND """, ) + + def _get_Phi_wind_stator(self): + """getter of Phi_wind_stator""" + if self._Phi_wind_stator is not None: + for obj in self._Phi_wind_stator: + if obj is not None: + obj.parent = self + return self._Phi_wind_stator + + def _set_Phi_wind_stator(self, value): + """setter of Phi_wind_stator""" + if type(value) is list: + for ii, obj in enumerate(value): + if type(obj) is dict: + class_obj = import_class( + "SciDataTool.Classes", obj.get("__class__"), "Phi_wind_stator" + ) + value[ii] = class_obj(init_dict=obj) + if value[ii] is not None: + value[ii].parent = self + if value == -1: + value = list() + check_var("Phi_wind_stator", value, "[DataND]") + self._Phi_wind_stator = value + + Phi_wind_stator = property( + fget=_get_Phi_wind_stator, + fset=_set_Phi_wind_stator, + doc=u"""Stator winding flux look-up table: list of DataTime objects whose (Id,Iq) is given by Idq list + + :Type: [SciDataTool.Classes.DataND.DataND] + """, + ) + + def _get_Tem(self): + """getter of Tem""" + if self._Tem is not None: + for obj in self._Tem: + if obj is not None: + obj.parent = self + return self._Tem + + def _set_Tem(self, value): + """setter of Tem""" + if type(value) is list: + for ii, obj in enumerate(value): + if type(obj) is dict: + class_obj = import_class( + "SciDataTool.Classes", obj.get("__class__"), "Tem" + ) + value[ii] = class_obj(init_dict=obj) + if value[ii] is not None: + value[ii].parent = self + if value == -1: + value = list() + check_var("Tem", value, "[DataND]") + self._Tem = value + + Tem = property( + fget=_get_Tem, + fset=_set_Tem, + doc=u"""Instaneous torque look-up table: list of DataTime objects whose (Id,Iq) is given by Idq list + + :Type: [SciDataTool.Classes.DataND.DataND] + """, + ) diff --git a/pyleecan/Classes/ELUT_SCIM.py b/pyleecan/Classes/ELUT_SCIM.py index 995201614..d353bc931 100644 --- a/pyleecan/Classes/ELUT_SCIM.py +++ b/pyleecan/Classes/ELUT_SCIM.py @@ -108,6 +108,8 @@ def __init__( R1=None, L1=None, T1_ref=20, + OP_matrix=None, + axes_dict=None, init_dict=None, init_str=None, ): @@ -142,6 +144,10 @@ def __init__( L1 = init_dict["L1"] if "T1_ref" in list(init_dict.keys()): T1_ref = init_dict["T1_ref"] + if "OP_matrix" in list(init_dict.keys()): + OP_matrix = init_dict["OP_matrix"] + if "axes_dict" in list(init_dict.keys()): + axes_dict = init_dict["axes_dict"] # Set the properties (value check and convertion are done in setter) self.Phi_m = Phi_m self.I_m = I_m @@ -149,7 +155,9 @@ def __init__( self.R2 = R2 self.L2 = L2 # Call ELUT init - super(ELUT_SCIM, self).__init__(R1=R1, L1=L1, T1_ref=T1_ref) + super(ELUT_SCIM, self).__init__( + R1=R1, L1=L1, T1_ref=T1_ref, OP_matrix=OP_matrix, axes_dict=axes_dict + ) # The class is frozen (in ELUT init), for now it's impossible to # add new properties diff --git a/pyleecan/Generator/ClassesRef/Simulation/ELUT.csv b/pyleecan/Generator/ClassesRef/Simulation/ELUT.csv index 29282b6af..2523d05f4 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/ELUT.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/ELUT.csv @@ -2,3 +2,5 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximu R1,Ohm,DC phase winding resistance at T1_ref per phase ,0,float,None,,,,Simulation,,get_param_dict,VERSION,1,Abstract class for Electrical Look Up Table (ELUT), L1,H,Phase winding leakage inductance ,0,float,None,,,,,,,,,, T1_ref,degC,"Stator winding average temperature associated to R1, L1 parameters",0,float,20,,,,,,,,,, +OP_matrix,,"Array of operating point values of size (N,5) with N the number of operating points (Speed, Id, Iq, Torque, Power). OP values are set to nan if they are not given.",,ndarray,None,,,,,,,,,, +axes_dict,,Dict containing axes data used for Magnetics,,{SciDataTool.Classes.DataND.Data},None,,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv b/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv index 63d345cd5..4e537c3a1 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv @@ -6,4 +6,5 @@ E0,V,RMS fundamental back electromotive force (bemf) along Q-axis,,float,None,,, E_dqh,V,Back emf harmonics along DQH axis,,ndarray,None,,,,,,get_Lmd,,,, orders_dqh,,Back harmonic orders (multiple of fundamental electrical frequency) corresponding to Edqh spectrum,,ndarray,None,,,,,,get_Lmq,,,, bemf,V,Back electromotive force DataTime object,"(Nt_tot, qs)",SciDataTool.Classes.DataND.DataND,None,,,,,,comp_Ldqh_from_Phidqh,,,, -,,,,,,,,,,,import_from_data,,,, +Phi_wind_stator,Wb,"Stator winding flux look-up table: list of DataTime objects whose (Id,Iq) is given by Idq list",,[SciDataTool.Classes.DataND.DataND],None,,,,,,import_from_data,,,, +Tem,Nm,"Instaneous torque look-up table: list of DataTime objects whose (Id,Iq) is given by Idq list",,[SciDataTool.Classes.DataND.DataND],None,,,,,,,,,, diff --git a/pyleecan/Methods/Post/PostELUT/__init__.py b/pyleecan/Methods/Post/PostELUT/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pyleecan/Methods/Post/PostELUT/run.py b/pyleecan/Methods/Post/PostELUT/run.py new file mode 100644 index 000000000..114f05eb2 --- /dev/null +++ b/pyleecan/Methods/Post/PostELUT/run.py @@ -0,0 +1,95 @@ +from numpy import array, zeros, nan + +from ....Classes.ELUT_PMSM import ELUT_PMSM +from ....Functions.Load.import_class import import_class + + +def run(self, out): + """PostProcessing to generate an ELUT after the corresponding simulation + + Parameters + ---------- + self : PostELUT + A PostELUT object + out: Output + Output object coming from ELUT calculation workflow + + """ + + # Init ELUT object + ELUT = ELUT_PMSM() + + XOutput = import_class("pyleecan.Classes", "XOutput") + + # Store force for each OP + if isinstance(out, XOutput): + + # Number of columns in OP_matrix + ndim = out.simu.var_simu.OP_matrix.shape[1] + + # Store operating point matrix in VarLoadCurrent object + ELUT.OP_matrix = nan * zeros((out.nb_simu, 5)) + ELUT.OP_matrix[:, 0] = array(out["N0"].result) + ELUT.OP_matrix[:, 1] = array(out["Id"].result) + ELUT.OP_matrix[:, 2] = array(out["Iq"].result) + if ndim > 3: + for ii in range(3, ndim): + ELUT.OP_matrix[:, ii] = array(out.simu.var_simu.OP_matrix[:, ii]) + + # Fill ELUT variables + dk_list = list(out.keys()) + if "T_{em}" in dk_list and None not in out["T_{em}"].result: + ELUT.Tem = out["T_{em}"].result + if "Phi_{wind}" in dk_list and None not in out["Phi_{wind}"].result: + ELUT.Phi_wind_stator = out["Phi_{wind}"].result + ELUT.Phi_dqh = out["Phi_{dq}"].result + if "EMF" in dk_list and None not in out["EMF"].result: + ELUT.bemf = out["EMF"].result + else: + # Store operating point matrix in OutElec + ELUT.OP_matrix = nan * zeros((1, 5)) + ELUT.OP_matrix[:, 0] = out.elec.N0 + ELUT.OP_matrix[:, 1] = out.elec.Id_ref + ELUT.OP_matrix[:, 2] = out.elec.Iq_ref + if out.elec.Tem_av_ref is not None: + ELUT.OP_matrix[:, 3] = out.elec.Tem_av_ref + + # Fill ELUT variables + if out.mag.Tem is not None: + ELUT.Tem = [out.mag.Tem] + if out.mag.Phi_wind_stator is not None: + ELUT.Phi_wind_stator = [out.mag.Phi_wind_stator] + ELUT.Phi_dqh = [out.mag.comp_Phi_dq()] + if out.mag.emf is not None: + ELUT.bemf = [out.mag.emf] + + # # Set if the interpolation must be made along a curve + # ELUT.set_is_interp_along_curve() + + # Store axes_dict + if ELUT.Tem is not None: + axes_dict = dict() + axes_list = ELUT.Tem[0].get_axes() + # if MLUT.AGSF is not None and self.is_fft_AGSF: + # axes_list.extend(MLUT.AGSF[0].get_axes()) + for axis in axes_list: + axes_dict[axis.name] = axis + ELUT.axes_dict = axes_dict + + # if self.is_save_ELUT: + + # # Save ELUT object + # ELUT.save_partial( + # folder_path=out.simu.get_machine_data_path( + # data_name="ELUT", is_create_folder=True + # ), + # file_name="ELUT", + # is_Tem=ELUT.Tem is not None, + # is_Phi_wind_stator=ELUT.Phi_wind_stator is not None, + # is_Phi_dq=ELUT.Phi_dq is not None, + # is_EMF=ELUT.bemf is not None, + # ) + + if self.is_store_ELUT: + # Store ELUT in PostELUT object + self.ELUT = ELUT From fcb3dc01073030fda9eda96e8104324d8c279868 Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 7 Oct 2021 15:25:53 +0200 Subject: [PATCH 060/167] [CO] split PMSM/SCIM cases --- pyleecan/Methods/Post/PostELUT/run.py | 156 +++++++++++++------------- 1 file changed, 80 insertions(+), 76 deletions(-) diff --git a/pyleecan/Methods/Post/PostELUT/run.py b/pyleecan/Methods/Post/PostELUT/run.py index 114f05eb2..e7a723cb2 100644 --- a/pyleecan/Methods/Post/PostELUT/run.py +++ b/pyleecan/Methods/Post/PostELUT/run.py @@ -16,80 +16,84 @@ def run(self, out): """ - # Init ELUT object - ELUT = ELUT_PMSM() - - XOutput = import_class("pyleecan.Classes", "XOutput") - - # Store force for each OP - if isinstance(out, XOutput): - - # Number of columns in OP_matrix - ndim = out.simu.var_simu.OP_matrix.shape[1] - - # Store operating point matrix in VarLoadCurrent object - ELUT.OP_matrix = nan * zeros((out.nb_simu, 5)) - ELUT.OP_matrix[:, 0] = array(out["N0"].result) - ELUT.OP_matrix[:, 1] = array(out["Id"].result) - ELUT.OP_matrix[:, 2] = array(out["Iq"].result) - if ndim > 3: - for ii in range(3, ndim): - ELUT.OP_matrix[:, ii] = array(out.simu.var_simu.OP_matrix[:, ii]) - - # Fill ELUT variables - dk_list = list(out.keys()) - if "T_{em}" in dk_list and None not in out["T_{em}"].result: - ELUT.Tem = out["T_{em}"].result - if "Phi_{wind}" in dk_list and None not in out["Phi_{wind}"].result: - ELUT.Phi_wind_stator = out["Phi_{wind}"].result - ELUT.Phi_dqh = out["Phi_{dq}"].result - if "EMF" in dk_list and None not in out["EMF"].result: - ELUT.bemf = out["EMF"].result + if out.simu.machine.is_synchronous(): + # Init ELUT object + ELUT = ELUT_PMSM() + + XOutput = import_class("pyleecan.Classes", "XOutput") + + # Store data for each OP + if isinstance(out, XOutput): + + # Number of columns in OP_matrix + ndim = out.simu.var_simu.OP_matrix.shape[1] + + # Store operating point matrix in VarLoadCurrent object + ELUT.OP_matrix = nan * zeros((out.nb_simu, 5)) + ELUT.OP_matrix[:, 0] = array(out["N0"].result) + ELUT.OP_matrix[:, 1] = array(out["Id"].result) + ELUT.OP_matrix[:, 2] = array(out["Iq"].result) + if ndim > 3: + for ii in range(3, ndim): + ELUT.OP_matrix[:, ii] = array(out.simu.var_simu.OP_matrix[:, ii]) + + # Fill ELUT variables + dk_list = list(out.keys()) + if "T_{em}" in dk_list and None not in out["T_{em}"].result: + ELUT.Tem = out["T_{em}"].result + if "Phi_{wind}" in dk_list and None not in out["Phi_{wind}"].result: + ELUT.Phi_wind_stator = out["Phi_{wind}"].result + ELUT.Phi_dqh = out["Phi_{dq}"].result + if "EMF" in dk_list and None not in out["EMF"].result: + ELUT.bemf = out["EMF"].result + else: + # Store operating point matrix in OutElec + ELUT.OP_matrix = nan * zeros((1, 5)) + ELUT.OP_matrix[:, 0] = out.elec.N0 + ELUT.OP_matrix[:, 1] = out.elec.Id_ref + ELUT.OP_matrix[:, 2] = out.elec.Iq_ref + if out.elec.Tem_av_ref is not None: + ELUT.OP_matrix[:, 3] = out.elec.Tem_av_ref + + # Fill ELUT variables + if out.mag.Tem is not None: + ELUT.Tem = [out.mag.Tem] + if out.mag.Phi_wind_stator is not None: + ELUT.Phi_wind_stator = [out.mag.Phi_wind_stator] + ELUT.Phi_dqh = [out.mag.comp_Phi_dq()] + if out.mag.emf is not None: + ELUT.bemf = [out.mag.emf] + + # # Set if the interpolation must be made along a curve + # ELUT.set_is_interp_along_curve() + + # Store axes_dict + if ELUT.Tem is not None: + axes_dict = dict() + axes_list = ELUT.Tem[0].get_axes() + # if MLUT.AGSF is not None and self.is_fft_AGSF: + # axes_list.extend(MLUT.AGSF[0].get_axes()) + for axis in axes_list: + axes_dict[axis.name] = axis + ELUT.axes_dict = axes_dict + + # if self.is_save_ELUT: + + # # Save ELUT object + # ELUT.save_partial( + # folder_path=out.simu.get_machine_data_path( + # data_name="ELUT", is_create_folder=True + # ), + # file_name="ELUT", + # is_Tem=ELUT.Tem is not None, + # is_Phi_wind_stator=ELUT.Phi_wind_stator is not None, + # is_Phi_dq=ELUT.Phi_dq is not None, + # is_EMF=ELUT.bemf is not None, + # ) + + if self.is_store_ELUT: + # Store ELUT in PostELUT object + self.ELUT = ELUT + else: - # Store operating point matrix in OutElec - ELUT.OP_matrix = nan * zeros((1, 5)) - ELUT.OP_matrix[:, 0] = out.elec.N0 - ELUT.OP_matrix[:, 1] = out.elec.Id_ref - ELUT.OP_matrix[:, 2] = out.elec.Iq_ref - if out.elec.Tem_av_ref is not None: - ELUT.OP_matrix[:, 3] = out.elec.Tem_av_ref - - # Fill ELUT variables - if out.mag.Tem is not None: - ELUT.Tem = [out.mag.Tem] - if out.mag.Phi_wind_stator is not None: - ELUT.Phi_wind_stator = [out.mag.Phi_wind_stator] - ELUT.Phi_dqh = [out.mag.comp_Phi_dq()] - if out.mag.emf is not None: - ELUT.bemf = [out.mag.emf] - - # # Set if the interpolation must be made along a curve - # ELUT.set_is_interp_along_curve() - - # Store axes_dict - if ELUT.Tem is not None: - axes_dict = dict() - axes_list = ELUT.Tem[0].get_axes() - # if MLUT.AGSF is not None and self.is_fft_AGSF: - # axes_list.extend(MLUT.AGSF[0].get_axes()) - for axis in axes_list: - axes_dict[axis.name] = axis - ELUT.axes_dict = axes_dict - - # if self.is_save_ELUT: - - # # Save ELUT object - # ELUT.save_partial( - # folder_path=out.simu.get_machine_data_path( - # data_name="ELUT", is_create_folder=True - # ), - # file_name="ELUT", - # is_Tem=ELUT.Tem is not None, - # is_Phi_wind_stator=ELUT.Phi_wind_stator is not None, - # is_Phi_dq=ELUT.Phi_dq is not None, - # is_EMF=ELUT.bemf is not None, - # ) - - if self.is_store_ELUT: - # Store ELUT in PostELUT object - self.ELUT = ELUT + raise Exception("PostELUT for asynchronous machines not developed yet") From 71e53f6004e94ba69645458ec5bc09538acce3fe Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 7 Oct 2021 16:22:29 +0200 Subject: [PATCH 061/167] [WiP] PostELUT methods --- .../Electrical/test_EEC_ELUT_PMSM.py | 43 +++--- pyleecan/Classes/Class_Dict.json | 27 ---- pyleecan/Classes/ELUT.py | 74 +-------- pyleecan/Classes/ELUT_PMSM.py | 144 +----------------- pyleecan/Classes/ELUT_SCIM.py | 5 +- .../Generator/ClassesRef/Simulation/ELUT.csv | 1 - .../ClassesRef/Simulation/ELUT_PMSM.csv | 3 +- pyleecan/Methods/Post/PostELUT/run.py | 87 ++++------- 8 files changed, 54 insertions(+), 330 deletions(-) diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py index fc5a79f82..fe08eabb4 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py @@ -43,27 +43,29 @@ def test_ELUT_PMSM(): ).get_data() # Set varspeed simulation - simu.var_simu = VarLoadCurrent(type_OP_matrix=1, OP_matrix=OP_matrix) + simu.var_simu = VarLoadCurrent( + type_OP_matrix=1, OP_matrix=OP_matrix, is_keep_all_output=True + ) # Define second simu for FEMM comparison simu.mag = MagFEMM(is_periodicity_a=True, is_periodicity_t=True, nb_worker=4) # Datakeepers - # Stator Winding Flux Datakeeper - Phi_wind_stator_dk = DataKeeper( - name="Stator Winding Flux", - symbol="Phi_{wind}", - unit="Wb", - keeper="lambda out: out.mag.Phi_wind_stator", - ) + # # Stator Winding Flux Datakeeper + # Phi_wind_stator_dk = DataKeeper( + # name="Stator Winding Flux", + # symbol="Phi_{wind}", + # unit="Wb", + # keeper="lambda out: out.mag.Phi_wind_stator", + # ) - # Instanteneous torque Datakeeper - Tem_dk = DataKeeper( - name="Electromagnetic torque", - symbol="T_{em}", - unit="N.m", - keeper="lambda out: out.mag.Tem", - ) + # # Instanteneous torque Datakeeper + # Tem_dk = DataKeeper( + # name="Electromagnetic torque", + # symbol="T_{em}", + # unit="N.m", + # keeper="lambda out: out.mag.Tem", + # ) # Stator Winding Flux along dq Datakeeper Phi_wind_dq_dk = DataKeeper( @@ -73,16 +75,9 @@ def test_ELUT_PMSM(): keeper="lambda out: out.mag.comp_Phi_dq()", ) - # Electromotive force Datakeeper - EMF_dk = DataKeeper( - name="Stator Winding Electromotive Force", - symbol="EMF", - unit="V", - keeper="lambda out: out.mag.comp_emf()", - ) - # Store Datakeepers - simu.var_simu.datakeeper_list = [Phi_wind_stator_dk, Tem_dk, Phi_wind_dq_dk, EMF_dk] + # simu.var_simu.datakeeper_list = [Phi_wind_stator_dk, Tem_dk, Phi_wind_dq_dk] + simu.var_simu.datakeeper_list = [Phi_wind_dq_dk] # Postprocessing simu.var_simu.postproc_list = [PostELUT()] diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 6e5f47b85..f22875df4 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -1301,15 +1301,6 @@ "type": "ndarray", "unit": "", "value": null - }, - { - "desc": "Dict containing axes data used for Magnetics", - "max": "", - "min": "", - "name": "axes_dict", - "type": "{SciDataTool.Classes.DataND.Data}", - "unit": "", - "value": "None" } ] }, @@ -1400,24 +1391,6 @@ "type": "SciDataTool.Classes.DataND.DataND", "unit": "V", "value": "None" - }, - { - "desc": "Stator winding flux look-up table: list of DataTime objects whose (Id,Iq) is given by Idq list", - "max": "", - "min": "", - "name": "Phi_wind_stator", - "type": "[SciDataTool.Classes.DataND.DataND]", - "unit": "Wb", - "value": "None" - }, - { - "desc": "Instaneous torque look-up table: list of DataTime objects whose (Id,Iq) is given by Idq list", - "max": "", - "min": "", - "name": "Tem", - "type": "[SciDataTool.Classes.DataND.DataND]", - "unit": "Nm", - "value": "None" } ] }, diff --git a/pyleecan/Classes/ELUT.py b/pyleecan/Classes/ELUT.py index a3bba62de..ad9b45f4b 100644 --- a/pyleecan/Classes/ELUT.py +++ b/pyleecan/Classes/ELUT.py @@ -50,14 +50,7 @@ class ELUT(FrozenClass): get_logger = get_logger def __init__( - self, - R1=None, - L1=None, - T1_ref=20, - OP_matrix=None, - axes_dict=None, - init_dict=None, - init_str=None, + self, R1=None, L1=None, T1_ref=20, OP_matrix=None, init_dict=None, init_str=None ): """Constructor of the class. Can be use in three ways : - __init__ (arg1 = 1, arg3 = 5) every parameters have name and default values @@ -82,15 +75,12 @@ def __init__( T1_ref = init_dict["T1_ref"] if "OP_matrix" in list(init_dict.keys()): OP_matrix = init_dict["OP_matrix"] - if "axes_dict" in list(init_dict.keys()): - axes_dict = init_dict["axes_dict"] # Set the properties (value check and convertion are done in setter) self.parent = None self.R1 = R1 self.L1 = L1 self.T1_ref = T1_ref self.OP_matrix = OP_matrix - self.axes_dict = axes_dict # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -113,7 +103,6 @@ def __str__(self): + linesep + linesep ) - ELUT_str += "axes_dict = " + str(self.axes_dict) + linesep + linesep return ELUT_str def __eq__(self, other): @@ -129,8 +118,6 @@ def __eq__(self, other): return False if not array_equal(other.OP_matrix, self.OP_matrix): return False - if other.axes_dict != self.axes_dict: - return False return True def compare(self, other, name="self", ignore_list=None): @@ -149,21 +136,6 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".T1_ref") if not array_equal(other.OP_matrix, self.OP_matrix): diff_list.append(name + ".OP_matrix") - if (other.axes_dict is None and self.axes_dict is not None) or ( - other.axes_dict is not None and self.axes_dict is None - ): - diff_list.append(name + ".axes_dict None mismatch") - elif self.axes_dict is None: - pass - elif len(other.axes_dict) != len(self.axes_dict): - diff_list.append("len(" + name + "axes_dict)") - else: - for key in self.axes_dict: - diff_list.extend( - self.axes_dict[key].compare( - other.axes_dict[key], name=name + ".axes_dict" - ) - ) # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -176,9 +148,6 @@ def __sizeof__(self): S += getsizeof(self.L1) S += getsizeof(self.T1_ref) S += getsizeof(self.OP_matrix) - if self.axes_dict is not None: - for key, value in self.axes_dict.items(): - S += getsizeof(value) + getsizeof(key) return S def as_dict(self, **kwargs): @@ -196,15 +165,6 @@ def as_dict(self, **kwargs): ELUT_dict["OP_matrix"] = None else: ELUT_dict["OP_matrix"] = self.OP_matrix.tolist() - if self.axes_dict is None: - ELUT_dict["axes_dict"] = None - else: - ELUT_dict["axes_dict"] = dict() - for key, obj in self.axes_dict.items(): - if obj is not None: - ELUT_dict["axes_dict"][key] = obj.as_dict() - else: - ELUT_dict["axes_dict"][key] = None # The class name is added to the dict for deserialisation purpose ELUT_dict["__class__"] = "ELUT" return ELUT_dict @@ -216,7 +176,6 @@ def _set_None(self): self.L1 = None self.T1_ref = None self.OP_matrix = None - self.axes_dict = None def _get_R1(self): """getter of R1""" @@ -296,34 +255,3 @@ def _set_OP_matrix(self, value): :Type: ndarray """, ) - - def _get_axes_dict(self): - """getter of axes_dict""" - if self._axes_dict is not None: - for key, obj in self._axes_dict.items(): - if obj is not None: - obj.parent = self - return self._axes_dict - - def _set_axes_dict(self, value): - """setter of axes_dict""" - if type(value) is dict: - for key, obj in value.items(): - if type(obj) is dict: - class_obj = import_class( - "SciDataTool.Classes", obj.get("__class__"), "axes_dict" - ) - value[key] = class_obj(init_dict=obj) - if type(value) is int and value == -1: - value = dict() - check_var("axes_dict", value, "{Data}") - self._axes_dict = value - - axes_dict = property( - fget=_get_axes_dict, - fset=_set_axes_dict, - doc=u"""Dict containing axes data used for Magnetics - - :Type: {SciDataTool.Classes.DataND.Data} - """, - ) diff --git a/pyleecan/Classes/ELUT_PMSM.py b/pyleecan/Classes/ELUT_PMSM.py index fc20ab0bd..cbab93f16 100644 --- a/pyleecan/Classes/ELUT_PMSM.py +++ b/pyleecan/Classes/ELUT_PMSM.py @@ -165,13 +165,10 @@ def __init__( E_dqh=None, orders_dqh=None, bemf=None, - Phi_wind_stator=None, - Tem=None, R1=None, L1=None, T1_ref=20, OP_matrix=None, - axes_dict=None, init_dict=None, init_str=None, ): @@ -204,10 +201,6 @@ def __init__( orders_dqh = init_dict["orders_dqh"] if "bemf" in list(init_dict.keys()): bemf = init_dict["bemf"] - if "Phi_wind_stator" in list(init_dict.keys()): - Phi_wind_stator = init_dict["Phi_wind_stator"] - if "Tem" in list(init_dict.keys()): - Tem = init_dict["Tem"] if "R1" in list(init_dict.keys()): R1 = init_dict["R1"] if "L1" in list(init_dict.keys()): @@ -216,8 +209,6 @@ def __init__( T1_ref = init_dict["T1_ref"] if "OP_matrix" in list(init_dict.keys()): OP_matrix = init_dict["OP_matrix"] - if "axes_dict" in list(init_dict.keys()): - axes_dict = init_dict["axes_dict"] # Set the properties (value check and convertion are done in setter) self.Phi_dqh = Phi_dqh self.I_dqh = I_dqh @@ -226,11 +217,9 @@ def __init__( self.E_dqh = E_dqh self.orders_dqh = orders_dqh self.bemf = bemf - self.Phi_wind_stator = Phi_wind_stator - self.Tem = Tem # Call ELUT init super(ELUT_PMSM, self).__init__( - R1=R1, L1=L1, T1_ref=T1_ref, OP_matrix=OP_matrix, axes_dict=axes_dict + R1=R1, L1=L1, T1_ref=T1_ref, OP_matrix=OP_matrix ) # The class is frozen (in ELUT init), for now it's impossible to # add new properties @@ -271,10 +260,6 @@ def __str__(self): + linesep ) ELUT_PMSM_str += "bemf = " + str(self.bemf) + linesep + linesep - ELUT_PMSM_str += ( - "Phi_wind_stator = " + str(self.Phi_wind_stator) + linesep + linesep - ) - ELUT_PMSM_str += "Tem = " + str(self.Tem) + linesep + linesep return ELUT_PMSM_str def __eq__(self, other): @@ -300,10 +285,6 @@ def __eq__(self, other): return False if other.bemf != self.bemf: return False - if other.Phi_wind_stator != self.Phi_wind_stator: - return False - if other.Tem != self.Tem: - return False return True def compare(self, other, name="self", ignore_list=None): @@ -335,37 +316,6 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".bemf None mismatch") elif self.bemf is not None: diff_list.extend(self.bemf.compare(other.bemf, name=name + ".bemf")) - if (other.Phi_wind_stator is None and self.Phi_wind_stator is not None) or ( - other.Phi_wind_stator is not None and self.Phi_wind_stator is None - ): - diff_list.append(name + ".Phi_wind_stator None mismatch") - elif self.Phi_wind_stator is None: - pass - elif len(other.Phi_wind_stator) != len(self.Phi_wind_stator): - diff_list.append("len(" + name + ".Phi_wind_stator)") - else: - for ii in range(len(other.Phi_wind_stator)): - diff_list.extend( - self.Phi_wind_stator[ii].compare( - other.Phi_wind_stator[ii], - name=name + ".Phi_wind_stator[" + str(ii) + "]", - ) - ) - if (other.Tem is None and self.Tem is not None) or ( - other.Tem is not None and self.Tem is None - ): - diff_list.append(name + ".Tem None mismatch") - elif self.Tem is None: - pass - elif len(other.Tem) != len(self.Tem): - diff_list.append("len(" + name + ".Tem)") - else: - for ii in range(len(other.Tem)): - diff_list.extend( - self.Tem[ii].compare( - other.Tem[ii], name=name + ".Tem[" + str(ii) + "]" - ) - ) # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -386,12 +336,6 @@ def __sizeof__(self): S += getsizeof(self.E_dqh) S += getsizeof(self.orders_dqh) S += getsizeof(self.bemf) - if self.Phi_wind_stator is not None: - for value in self.Phi_wind_stator: - S += getsizeof(value) - if self.Tem is not None: - for value in self.Tem: - S += getsizeof(value) return S def as_dict(self, **kwargs): @@ -422,24 +366,6 @@ def as_dict(self, **kwargs): ELUT_PMSM_dict["bemf"] = None else: ELUT_PMSM_dict["bemf"] = self.bemf.as_dict() - if self.Phi_wind_stator is None: - ELUT_PMSM_dict["Phi_wind_stator"] = None - else: - ELUT_PMSM_dict["Phi_wind_stator"] = list() - for obj in self.Phi_wind_stator: - if obj is not None: - ELUT_PMSM_dict["Phi_wind_stator"].append(obj.as_dict()) - else: - ELUT_PMSM_dict["Phi_wind_stator"].append(None) - if self.Tem is None: - ELUT_PMSM_dict["Tem"] = None - else: - ELUT_PMSM_dict["Tem"] = list() - for obj in self.Tem: - if obj is not None: - ELUT_PMSM_dict["Tem"].append(obj.as_dict()) - else: - ELUT_PMSM_dict["Tem"].append(None) # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name ELUT_PMSM_dict["__class__"] = "ELUT_PMSM" @@ -455,8 +381,6 @@ def _set_None(self): self.E_dqh = None self.orders_dqh = None self.bemf = None - self.Phi_wind_stator = None - self.Tem = None # Set to None the properties inherited from ELUT super(ELUT_PMSM, self)._set_None() @@ -617,69 +541,3 @@ def _set_bemf(self, value): :Type: SciDataTool.Classes.DataND.DataND """, ) - - def _get_Phi_wind_stator(self): - """getter of Phi_wind_stator""" - if self._Phi_wind_stator is not None: - for obj in self._Phi_wind_stator: - if obj is not None: - obj.parent = self - return self._Phi_wind_stator - - def _set_Phi_wind_stator(self, value): - """setter of Phi_wind_stator""" - if type(value) is list: - for ii, obj in enumerate(value): - if type(obj) is dict: - class_obj = import_class( - "SciDataTool.Classes", obj.get("__class__"), "Phi_wind_stator" - ) - value[ii] = class_obj(init_dict=obj) - if value[ii] is not None: - value[ii].parent = self - if value == -1: - value = list() - check_var("Phi_wind_stator", value, "[DataND]") - self._Phi_wind_stator = value - - Phi_wind_stator = property( - fget=_get_Phi_wind_stator, - fset=_set_Phi_wind_stator, - doc=u"""Stator winding flux look-up table: list of DataTime objects whose (Id,Iq) is given by Idq list - - :Type: [SciDataTool.Classes.DataND.DataND] - """, - ) - - def _get_Tem(self): - """getter of Tem""" - if self._Tem is not None: - for obj in self._Tem: - if obj is not None: - obj.parent = self - return self._Tem - - def _set_Tem(self, value): - """setter of Tem""" - if type(value) is list: - for ii, obj in enumerate(value): - if type(obj) is dict: - class_obj = import_class( - "SciDataTool.Classes", obj.get("__class__"), "Tem" - ) - value[ii] = class_obj(init_dict=obj) - if value[ii] is not None: - value[ii].parent = self - if value == -1: - value = list() - check_var("Tem", value, "[DataND]") - self._Tem = value - - Tem = property( - fget=_get_Tem, - fset=_set_Tem, - doc=u"""Instaneous torque look-up table: list of DataTime objects whose (Id,Iq) is given by Idq list - - :Type: [SciDataTool.Classes.DataND.DataND] - """, - ) diff --git a/pyleecan/Classes/ELUT_SCIM.py b/pyleecan/Classes/ELUT_SCIM.py index d353bc931..58762321e 100644 --- a/pyleecan/Classes/ELUT_SCIM.py +++ b/pyleecan/Classes/ELUT_SCIM.py @@ -109,7 +109,6 @@ def __init__( L1=None, T1_ref=20, OP_matrix=None, - axes_dict=None, init_dict=None, init_str=None, ): @@ -146,8 +145,6 @@ def __init__( T1_ref = init_dict["T1_ref"] if "OP_matrix" in list(init_dict.keys()): OP_matrix = init_dict["OP_matrix"] - if "axes_dict" in list(init_dict.keys()): - axes_dict = init_dict["axes_dict"] # Set the properties (value check and convertion are done in setter) self.Phi_m = Phi_m self.I_m = I_m @@ -156,7 +153,7 @@ def __init__( self.L2 = L2 # Call ELUT init super(ELUT_SCIM, self).__init__( - R1=R1, L1=L1, T1_ref=T1_ref, OP_matrix=OP_matrix, axes_dict=axes_dict + R1=R1, L1=L1, T1_ref=T1_ref, OP_matrix=OP_matrix ) # The class is frozen (in ELUT init), for now it's impossible to # add new properties diff --git a/pyleecan/Generator/ClassesRef/Simulation/ELUT.csv b/pyleecan/Generator/ClassesRef/Simulation/ELUT.csv index 2523d05f4..f071da89c 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/ELUT.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/ELUT.csv @@ -3,4 +3,3 @@ R1,Ohm,DC phase winding resistance at T1_ref per phase ,0,float,None,,,,Simulati L1,H,Phase winding leakage inductance ,0,float,None,,,,,,,,,, T1_ref,degC,"Stator winding average temperature associated to R1, L1 parameters",0,float,20,,,,,,,,,, OP_matrix,,"Array of operating point values of size (N,5) with N the number of operating points (Speed, Id, Iq, Torque, Power). OP values are set to nan if they are not given.",,ndarray,None,,,,,,,,,, -axes_dict,,Dict containing axes data used for Magnetics,,{SciDataTool.Classes.DataND.Data},None,,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv b/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv index 4e537c3a1..63d345cd5 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv @@ -6,5 +6,4 @@ E0,V,RMS fundamental back electromotive force (bemf) along Q-axis,,float,None,,, E_dqh,V,Back emf harmonics along DQH axis,,ndarray,None,,,,,,get_Lmd,,,, orders_dqh,,Back harmonic orders (multiple of fundamental electrical frequency) corresponding to Edqh spectrum,,ndarray,None,,,,,,get_Lmq,,,, bemf,V,Back electromotive force DataTime object,"(Nt_tot, qs)",SciDataTool.Classes.DataND.DataND,None,,,,,,comp_Ldqh_from_Phidqh,,,, -Phi_wind_stator,Wb,"Stator winding flux look-up table: list of DataTime objects whose (Id,Iq) is given by Idq list",,[SciDataTool.Classes.DataND.DataND],None,,,,,,import_from_data,,,, -Tem,Nm,"Instaneous torque look-up table: list of DataTime objects whose (Id,Iq) is given by Idq list",,[SciDataTool.Classes.DataND.DataND],None,,,,,,,,,, +,,,,,,,,,,,import_from_data,,,, diff --git a/pyleecan/Methods/Post/PostELUT/run.py b/pyleecan/Methods/Post/PostELUT/run.py index e7a723cb2..5c50fb8db 100644 --- a/pyleecan/Methods/Post/PostELUT/run.py +++ b/pyleecan/Methods/Post/PostELUT/run.py @@ -1,4 +1,5 @@ from numpy import array, zeros, nan +from os.path import join from ....Classes.ELUT_PMSM import ELUT_PMSM from ....Functions.Load.import_class import import_class @@ -22,9 +23,10 @@ def run(self, out): XOutput = import_class("pyleecan.Classes", "XOutput") - # Store data for each OP - if isinstance(out, XOutput): - + if not isinstance(out, XOutput): + raise Exception("Need an XOutput to compute ELUT") + else: + # Store data for each OP # Number of columns in OP_matrix ndim = out.simu.var_simu.OP_matrix.shape[1] @@ -39,61 +41,34 @@ def run(self, out): # Fill ELUT variables dk_list = list(out.keys()) - if "T_{em}" in dk_list and None not in out["T_{em}"].result: - ELUT.Tem = out["T_{em}"].result - if "Phi_{wind}" in dk_list and None not in out["Phi_{wind}"].result: - ELUT.Phi_wind_stator = out["Phi_{wind}"].result + if "Phi_{dq}" in dk_list: ELUT.Phi_dqh = out["Phi_{dq}"].result - if "EMF" in dk_list and None not in out["EMF"].result: - ELUT.bemf = out["EMF"].result - else: - # Store operating point matrix in OutElec - ELUT.OP_matrix = nan * zeros((1, 5)) - ELUT.OP_matrix[:, 0] = out.elec.N0 - ELUT.OP_matrix[:, 1] = out.elec.Id_ref - ELUT.OP_matrix[:, 2] = out.elec.Iq_ref - if out.elec.Tem_av_ref is not None: - ELUT.OP_matrix[:, 3] = out.elec.Tem_av_ref - - # Fill ELUT variables - if out.mag.Tem is not None: - ELUT.Tem = [out.mag.Tem] - if out.mag.Phi_wind_stator is not None: - ELUT.Phi_wind_stator = [out.mag.Phi_wind_stator] - ELUT.Phi_dqh = [out.mag.comp_Phi_dq()] - if out.mag.emf is not None: - ELUT.bemf = [out.mag.emf] - - # # Set if the interpolation must be made along a curve - # ELUT.set_is_interp_along_curve() - - # Store axes_dict - if ELUT.Tem is not None: - axes_dict = dict() - axes_list = ELUT.Tem[0].get_axes() - # if MLUT.AGSF is not None and self.is_fft_AGSF: - # axes_list.extend(MLUT.AGSF[0].get_axes()) - for axis in axes_list: - axes_dict[axis.name] = axis - ELUT.axes_dict = axes_dict - - # if self.is_save_ELUT: - - # # Save ELUT object - # ELUT.save_partial( - # folder_path=out.simu.get_machine_data_path( - # data_name="ELUT", is_create_folder=True - # ), - # file_name="ELUT", - # is_Tem=ELUT.Tem is not None, - # is_Phi_wind_stator=ELUT.Phi_wind_stator is not None, - # is_Phi_dq=ELUT.Phi_dq is not None, - # is_EMF=ELUT.bemf is not None, - # ) - if self.is_store_ELUT: - # Store ELUT in PostELUT object - self.ELUT = ELUT + # Find Id=Iq=0 + OP_list = ELUT.OP_matrix[:, 1:3].tolist() + if [0, 0] in OP_list: + ii = OP_list.index([0, 0]) + else: + raise Exception("Operating Point Id=Iq=0 is required to compute ELUT") + # Compute back electromotive force + out.output_list[ii].mag.comp_emf() + BEMF = out.output_list[ii].mag.emf + BEMF.name = "Stator Winding Back Electromotive Force" + BEMF.symbol = "BEMF" + ELUT.bemf = BEMF + + # # Set if the interpolation must be made along a curve + # ELUT.set_is_interp_along_curve() + + if self.is_save_ELUT: + # Save ELUT object + ELUT.save( + save_path=join(out.get_path_result(), "ELUT.h5"), + ) + + if self.is_store_ELUT: + # Store ELUT in PostELUT object + self.ELUT = ELUT else: raise Exception("PostELUT for asynchronous machines not developed yet") From 82c4e5256ccbfccaefc521d347109912edbe6a37 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Thu, 7 Oct 2021 16:41:31 +0200 Subject: [PATCH 062/167] [CC] gen classes --- pyleecan/Classes/Class_Dict.json | 294 ++++++++++++++++++++++++++++++- pyleecan/Classes/EEC_LSRPM.py | 18 +- pyleecan/Classes/ELUT.py | 23 ++- pyleecan/Classes/ELUT_PMSM.py | 67 ++++++- pyleecan/Classes/ELUT_SCIM.py | 34 +++- pyleecan/Classes/InputCurrent.py | 24 ++- pyleecan/Classes/InputFlux.py | 29 ++- pyleecan/Classes/InputVoltage.py | 18 +- pyleecan/Classes/MagElmer.py | 37 +++- pyleecan/Classes/MagFEMM.py | 67 ++++++- pyleecan/Classes/Magnetics.py | 155 +++++++++++++++- pyleecan/Classes/OutElec.py | 47 ++++- pyleecan/Classes/OutForce.py | 24 ++- pyleecan/Classes/OutMag.py | 251 ++++++++++++++++++++++++-- pyleecan/Classes/OutStruct.py | 18 +- pyleecan/Classes/PostELUT.py | 18 +- 16 files changed, 1049 insertions(+), 75 deletions(-) diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 6e5f47b85..7b6c4ee3d 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -3287,6 +3287,15 @@ "type": "{Magnet}", "unit": "", "value": "" + }, + { + "desc": "Name of the hole (for save)", + "max": "", + "min": "", + "name": "name", + "type": "str", + "unit": "-", + "value": "" } ] }, @@ -5086,6 +5095,15 @@ "unit": "-", "value": "" }, + { + "desc": "Skew object", + "max": "", + "min": "", + "name": "skew", + "type": "Skew", + "unit": "-", + "value": null + }, { "desc": "Lamination yoke notches", "max": "", @@ -6133,6 +6151,15 @@ "type": "float", "unit": "m", "value": null + }, + { + "desc": "True set previous .ans result file in current .fem to use it as initialization and speed up calculation time", + "max": "", + "min": "", + "name": "is_set_previous", + "type": "bool", + "unit": "", + "value": 1 } ] }, @@ -6197,6 +6224,7 @@ "methods": [ "run", "comp_axes", + "get_slice_model", "comp_I_mag" ], "mother": "", @@ -6312,6 +6340,33 @@ "unit": "-", "value": "Pyleecan.Magnetics" }, + { + "desc": "Enforce slice model to account for skew", + "max": "", + "min": "", + "name": "Slice_enforced", + "type": "SliceModel", + "unit": "-", + "value": null + }, + { + "desc": "To enforce number of slices in slice model", + "max": "", + "min": "", + "name": "Nslices_enforced", + "type": "int", + "unit": "", + "value": null + }, + { + "desc": "To enforce type of slice distribution to use for rotor skew if linear and continuous (\"uniform\", \"gauss\", \"user-defined\")", + "max": "", + "min": "", + "name": "type_distribution_enforced", + "type": "str", + "unit": "-", + "value": "None" + }, { "desc": "0 To compute only the airgap flux from fundamental current harmonics", "max": "", @@ -8577,7 +8632,7 @@ "max": "", "min": "", "name": "Phi_wind_stator", - "type": "SciDataTool.Classes.DataTime.DataTime", + "type": "SciDataTool.Classes.DataND.DataND", "unit": "Wb", "value": "None" }, @@ -8586,7 +8641,7 @@ "max": "", "min": "", "name": "Phi_wind", - "type": "{SciDataTool.Classes.DataTime.DataTime}", + "type": "{SciDataTool.Classes.DataND.DataND}", "unit": "Wb", "value": "None" }, @@ -8595,7 +8650,7 @@ "max": "", "min": "", "name": "emf", - "type": "SciDataTool.Classes.DataTime.DataTime", + "type": "SciDataTool.Classes.DataND.DataND", "unit": "V", "value": "None" }, @@ -8643,6 +8698,33 @@ "type": "float", "unit": "W", "value": null + }, + { + "desc": "Slice model to account for skew", + "max": "", + "min": "", + "name": "Slice", + "type": "SliceModel", + "unit": "-", + "value": null + }, + { + "desc": "Electromagnetic torque DataTime object including torque per slice", + "max": "", + "min": "", + "name": "Tem_slice", + "type": "SciDataTool.Classes.DataND.DataND", + "unit": "N", + "value": "None" + }, + { + "desc": "Dict of lamination winding fluxlinkage DataTime objects per slice", + "max": "", + "min": "", + "name": "Phi_wind_slice", + "type": "{SciDataTool.Classes.DataND.DataND}", + "unit": "Wb/m", + "value": "None" } ] }, @@ -9921,6 +10003,194 @@ } ] }, + "Skew": { + "constants": [ + { + "name": "VERSION", + "value": "1" + } + ], + "daughters": [], + "desc": "Class for the skew (rotor or stator)", + "is_internal": false, + "methods": [ + "comp_angle", + "comp_pattern", + "plot" + ], + "mother": "", + "name": "Skew", + "package": "Machine", + "path": "pyleecan/Generator/ClassesRef/Machine/Skew.csv", + "properties": [ + { + "desc": "Type of skew (\"linear\", \"vshape\", \"function\", \"user-defined\")", + "max": "", + "min": "", + "name": "type_skew", + "type": "str", + "unit": "-", + "value": "None" + }, + { + "desc": "Skew rate expressed in terms of slot pitch (stator slot pitch for SCIM, rotor slot pitch for PMSM)", + "max": "", + "min": "", + "name": "rate", + "type": "float", + "unit": "-", + "value": 1 + }, + { + "desc": "True to define skew as steps", + "max": "", + "min": "", + "name": "is_step", + "type": "bool", + "unit": "-", + "value": true + }, + { + "desc": "Function which describes skew pattern", + "max": "", + "min": "", + "name": "function", + "type": "function", + "unit": "-", + "value": null + }, + { + "desc": "List of skew angles", + "max": "", + "min": "", + "name": "angle_list", + "type": "list", + "unit": "rad", + "value": null + }, + { + "desc": "List of z axis positions for which skew angles are given", + "max": "", + "min": "", + "name": "z_list", + "type": "list", + "unit": "m", + "value": null + }, + { + "desc": "Number of steps if step skew", + "max": "", + "min": "2", + "name": "Nstep", + "type": "int", + "unit": "", + "value": null + }, + { + "desc": "Overall skewing angle", + "max": "", + "min": "0", + "name": "angle_overall", + "type": "float", + "unit": "rad", + "value": null + } + ] + }, + "SliceModel": { + "constants": [ + { + "name": "VERSION", + "value": "1" + } + ], + "daughters": [], + "desc": "Class to hande slices in magnetics model", + "is_internal": false, + "methods": [ + "get_distribution", + "get_data", + "plot" + ], + "mother": "", + "name": "SliceModel", + "package": "Simulation", + "path": "pyleecan/Generator/ClassesRef/Simulation/SliceModel.csv", + "properties": [ + { + "desc": "Type of slice distribution to use for rotor skew if linear and continuous (\"uniform\", \"gauss\", \"user-defined\")", + "max": "", + "min": "", + "name": "type_distribution", + "type": "str", + "unit": "-", + "value": "None" + }, + { + "desc": "Number of slices", + "max": "", + "min": "", + "name": "Nslices", + "type": "int", + "unit": "-", + "value": 5 + }, + { + "desc": "List of slice positions", + "max": "", + "min": "", + "name": "z_list", + "type": "list", + "unit": "m", + "value": null + }, + { + "desc": "Array of rotor skew angles in case of skew", + "max": "", + "min": "", + "name": "angle_rotor", + "type": "ndarray", + "unit": "rad", + "value": null + }, + { + "desc": "Array of stator skew angles in case of skew", + "max": "", + "min": "", + "name": "angle_stator", + "type": "ndarray", + "unit": "rad", + "value": null + }, + { + "desc": "Machine length (mean of rotor/stator lengths)", + "max": "", + "min": "", + "name": "L", + "type": "float", + "unit": "m", + "value": null + }, + { + "desc": "True to define slices as steps", + "max": "", + "min": "", + "name": "is_step", + "type": "bool", + "unit": "-", + "value": null + }, + { + "desc": "True if slices account for skew", + "max": "", + "min": "", + "name": "is_skew", + "type": "bool", + "unit": "", + "value": null + } + ] + }, "Slot": { "constants": [ { @@ -10820,6 +11090,15 @@ "type": "int", "unit": "-", "value": 0 + }, + { + "desc": "Name of the slot (for save)", + "max": "", + "min": "", + "name": "name", + "type": "str", + "unit": "-", + "value": "" } ] }, @@ -10871,6 +11150,15 @@ "type": "dict", "unit": "-", "value": null + }, + { + "desc": "Name of the slot (for save)", + "max": "", + "min": "", + "name": "name", + "type": "str", + "unit": "-", + "value": "" } ] }, diff --git a/pyleecan/Classes/EEC_LSRPM.py b/pyleecan/Classes/EEC_LSRPM.py index 74265c338..8253cd6d6 100644 --- a/pyleecan/Classes/EEC_LSRPM.py +++ b/pyleecan/Classes/EEC_LSRPM.py @@ -217,15 +217,23 @@ def __sizeof__(self): S += getsizeof(self.fluxlink) return S - def as_dict(self, **kwargs): + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): """ Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str Optional keyword input parameter is for internal use only and may prevent json serializability. """ # Get the properties inherited from EEC - EEC_LSRPM_dict = super(EEC_LSRPM, self).as_dict(**kwargs) + EEC_LSRPM_dict = super(EEC_LSRPM, self).as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) EEC_LSRPM_dict["parameters"] = ( self.parameters.copy() if self.parameters is not None else None ) @@ -234,7 +242,11 @@ def as_dict(self, **kwargs): if self.fluxlink is None: EEC_LSRPM_dict["fluxlink"] = None else: - EEC_LSRPM_dict["fluxlink"] = self.fluxlink.as_dict(**kwargs) + EEC_LSRPM_dict["fluxlink"] = self.fluxlink.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name EEC_LSRPM_dict["__class__"] = "EEC_LSRPM" diff --git a/pyleecan/Classes/ELUT.py b/pyleecan/Classes/ELUT.py index a3bba62de..a3c318a70 100644 --- a/pyleecan/Classes/ELUT.py +++ b/pyleecan/Classes/ELUT.py @@ -181,9 +181,13 @@ def __sizeof__(self): S += getsizeof(value) + getsizeof(key) return S - def as_dict(self, **kwargs): + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): """ Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str Optional keyword input parameter is for internal use only and may prevent json serializability. """ @@ -195,14 +199,27 @@ def as_dict(self, **kwargs): if self.OP_matrix is None: ELUT_dict["OP_matrix"] = None else: - ELUT_dict["OP_matrix"] = self.OP_matrix.tolist() + if type_handle_ndarray == 0: + ELUT_dict["OP_matrix"] = self.OP_matrix.tolist() + elif type_handle_ndarray == 1: + ELUT_dict["OP_matrix"] = self.OP_matrix.copy() + elif type_handle_ndarray == 2: + ELUT_dict["OP_matrix"] = self.OP_matrix + else: + raise Exception( + "Unknown type_handle_ndarray: " + str(type_handle_ndarray) + ) if self.axes_dict is None: ELUT_dict["axes_dict"] = None else: ELUT_dict["axes_dict"] = dict() for key, obj in self.axes_dict.items(): if obj is not None: - ELUT_dict["axes_dict"][key] = obj.as_dict() + ELUT_dict["axes_dict"][key] = obj.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) else: ELUT_dict["axes_dict"][key] = None # The class name is added to the dict for deserialisation purpose diff --git a/pyleecan/Classes/ELUT_PMSM.py b/pyleecan/Classes/ELUT_PMSM.py index fc20ab0bd..2699cf12d 100644 --- a/pyleecan/Classes/ELUT_PMSM.py +++ b/pyleecan/Classes/ELUT_PMSM.py @@ -394,41 +394,86 @@ def __sizeof__(self): S += getsizeof(value) return S - def as_dict(self, **kwargs): + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): """ Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str Optional keyword input parameter is for internal use only and may prevent json serializability. """ # Get the properties inherited from ELUT - ELUT_PMSM_dict = super(ELUT_PMSM, self).as_dict(**kwargs) + ELUT_PMSM_dict = super(ELUT_PMSM, self).as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) if self.Phi_dqh is None: ELUT_PMSM_dict["Phi_dqh"] = None else: - ELUT_PMSM_dict["Phi_dqh"] = self.Phi_dqh.tolist() + if type_handle_ndarray == 0: + ELUT_PMSM_dict["Phi_dqh"] = self.Phi_dqh.tolist() + elif type_handle_ndarray == 1: + ELUT_PMSM_dict["Phi_dqh"] = self.Phi_dqh.copy() + elif type_handle_ndarray == 2: + ELUT_PMSM_dict["Phi_dqh"] = self.Phi_dqh + else: + raise Exception( + "Unknown type_handle_ndarray: " + str(type_handle_ndarray) + ) ELUT_PMSM_dict["I_dqh"] = self.I_dqh.copy() if self.I_dqh is not None else None ELUT_PMSM_dict["Tmag_ref"] = self.Tmag_ref ELUT_PMSM_dict["E0"] = self.E0 if self.E_dqh is None: ELUT_PMSM_dict["E_dqh"] = None else: - ELUT_PMSM_dict["E_dqh"] = self.E_dqh.tolist() + if type_handle_ndarray == 0: + ELUT_PMSM_dict["E_dqh"] = self.E_dqh.tolist() + elif type_handle_ndarray == 1: + ELUT_PMSM_dict["E_dqh"] = self.E_dqh.copy() + elif type_handle_ndarray == 2: + ELUT_PMSM_dict["E_dqh"] = self.E_dqh + else: + raise Exception( + "Unknown type_handle_ndarray: " + str(type_handle_ndarray) + ) if self.orders_dqh is None: ELUT_PMSM_dict["orders_dqh"] = None else: - ELUT_PMSM_dict["orders_dqh"] = self.orders_dqh.tolist() + if type_handle_ndarray == 0: + ELUT_PMSM_dict["orders_dqh"] = self.orders_dqh.tolist() + elif type_handle_ndarray == 1: + ELUT_PMSM_dict["orders_dqh"] = self.orders_dqh.copy() + elif type_handle_ndarray == 2: + ELUT_PMSM_dict["orders_dqh"] = self.orders_dqh + else: + raise Exception( + "Unknown type_handle_ndarray: " + str(type_handle_ndarray) + ) if self.bemf is None: ELUT_PMSM_dict["bemf"] = None else: - ELUT_PMSM_dict["bemf"] = self.bemf.as_dict() + ELUT_PMSM_dict["bemf"] = self.bemf.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) if self.Phi_wind_stator is None: ELUT_PMSM_dict["Phi_wind_stator"] = None else: ELUT_PMSM_dict["Phi_wind_stator"] = list() for obj in self.Phi_wind_stator: if obj is not None: - ELUT_PMSM_dict["Phi_wind_stator"].append(obj.as_dict()) + ELUT_PMSM_dict["Phi_wind_stator"].append( + obj.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) + ) else: ELUT_PMSM_dict["Phi_wind_stator"].append(None) if self.Tem is None: @@ -437,7 +482,13 @@ def as_dict(self, **kwargs): ELUT_PMSM_dict["Tem"] = list() for obj in self.Tem: if obj is not None: - ELUT_PMSM_dict["Tem"].append(obj.as_dict()) + ELUT_PMSM_dict["Tem"].append( + obj.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) + ) else: ELUT_PMSM_dict["Tem"].append(None) # The class name is added to the dict for deserialisation purpose diff --git a/pyleecan/Classes/ELUT_SCIM.py b/pyleecan/Classes/ELUT_SCIM.py index d353bc931..8bc24ab5c 100644 --- a/pyleecan/Classes/ELUT_SCIM.py +++ b/pyleecan/Classes/ELUT_SCIM.py @@ -246,23 +246,49 @@ def __sizeof__(self): S += getsizeof(self.L2) return S - def as_dict(self, **kwargs): + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): """ Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str Optional keyword input parameter is for internal use only and may prevent json serializability. """ # Get the properties inherited from ELUT - ELUT_SCIM_dict = super(ELUT_SCIM, self).as_dict(**kwargs) + ELUT_SCIM_dict = super(ELUT_SCIM, self).as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) if self.Phi_m is None: ELUT_SCIM_dict["Phi_m"] = None else: - ELUT_SCIM_dict["Phi_m"] = self.Phi_m.tolist() + if type_handle_ndarray == 0: + ELUT_SCIM_dict["Phi_m"] = self.Phi_m.tolist() + elif type_handle_ndarray == 1: + ELUT_SCIM_dict["Phi_m"] = self.Phi_m.copy() + elif type_handle_ndarray == 2: + ELUT_SCIM_dict["Phi_m"] = self.Phi_m + else: + raise Exception( + "Unknown type_handle_ndarray: " + str(type_handle_ndarray) + ) if self.I_m is None: ELUT_SCIM_dict["I_m"] = None else: - ELUT_SCIM_dict["I_m"] = self.I_m.tolist() + if type_handle_ndarray == 0: + ELUT_SCIM_dict["I_m"] = self.I_m.tolist() + elif type_handle_ndarray == 1: + ELUT_SCIM_dict["I_m"] = self.I_m.copy() + elif type_handle_ndarray == 2: + ELUT_SCIM_dict["I_m"] = self.I_m + else: + raise Exception( + "Unknown type_handle_ndarray: " + str(type_handle_ndarray) + ) ELUT_SCIM_dict["T2_ref"] = self.T2_ref ELUT_SCIM_dict["R2"] = self.R2 ELUT_SCIM_dict["L2"] = self.L2 diff --git a/pyleecan/Classes/InputCurrent.py b/pyleecan/Classes/InputCurrent.py index cd56c0937..852a02669 100644 --- a/pyleecan/Classes/InputCurrent.py +++ b/pyleecan/Classes/InputCurrent.py @@ -277,23 +277,39 @@ def __sizeof__(self): S += getsizeof(self.Iq_ref) return S - def as_dict(self, **kwargs): + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): """ Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str Optional keyword input parameter is for internal use only and may prevent json serializability. """ # Get the properties inherited from InputVoltage - InputCurrent_dict = super(InputCurrent, self).as_dict(**kwargs) + InputCurrent_dict = super(InputCurrent, self).as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) if self.Is is None: InputCurrent_dict["Is"] = None else: - InputCurrent_dict["Is"] = self.Is.as_dict(**kwargs) + InputCurrent_dict["Is"] = self.Is.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) if self.Ir is None: InputCurrent_dict["Ir"] = None else: - InputCurrent_dict["Ir"] = self.Ir.as_dict(**kwargs) + InputCurrent_dict["Ir"] = self.Ir.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) InputCurrent_dict["Id_ref"] = self.Id_ref InputCurrent_dict["Iq_ref"] = self.Iq_ref # The class name is added to the dict for deserialisation purpose diff --git a/pyleecan/Classes/InputFlux.py b/pyleecan/Classes/InputFlux.py index a86a3e5e6..5ae4732b5 100644 --- a/pyleecan/Classes/InputFlux.py +++ b/pyleecan/Classes/InputFlux.py @@ -295,15 +295,23 @@ def __sizeof__(self): S += getsizeof(self.B_enforced) return S - def as_dict(self, **kwargs): + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): """ Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str Optional keyword input parameter is for internal use only and may prevent json serializability. """ # Get the properties inherited from InputCurrent - InputFlux_dict = super(InputFlux, self).as_dict(**kwargs) + InputFlux_dict = super(InputFlux, self).as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) InputFlux_dict["per_a"] = self.per_a InputFlux_dict["per_t"] = self.per_t InputFlux_dict["is_antiper_a"] = self.is_antiper_a @@ -315,11 +323,24 @@ def as_dict(self, **kwargs): if self.slice is None: InputFlux_dict["slice"] = None else: - InputFlux_dict["slice"] = self.slice.tolist() + if type_handle_ndarray == 0: + InputFlux_dict["slice"] = self.slice.tolist() + elif type_handle_ndarray == 1: + InputFlux_dict["slice"] = self.slice.copy() + elif type_handle_ndarray == 2: + InputFlux_dict["slice"] = self.slice + else: + raise Exception( + "Unknown type_handle_ndarray: " + str(type_handle_ndarray) + ) if self.B_enforced is None: InputFlux_dict["B_enforced"] = None else: - InputFlux_dict["B_enforced"] = self.B_enforced.as_dict() + InputFlux_dict["B_enforced"] = self.B_enforced.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name InputFlux_dict["__class__"] = "InputFlux" diff --git a/pyleecan/Classes/InputVoltage.py b/pyleecan/Classes/InputVoltage.py index 470612ecf..7957fb544 100644 --- a/pyleecan/Classes/InputVoltage.py +++ b/pyleecan/Classes/InputVoltage.py @@ -290,19 +290,31 @@ def __sizeof__(self): S += getsizeof(self.Pem_av_ref) return S - def as_dict(self, **kwargs): + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): """ Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str Optional keyword input parameter is for internal use only and may prevent json serializability. """ # Get the properties inherited from Input - InputVoltage_dict = super(InputVoltage, self).as_dict(**kwargs) + InputVoltage_dict = super(InputVoltage, self).as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) if self.angle_rotor is None: InputVoltage_dict["angle_rotor"] = None else: - InputVoltage_dict["angle_rotor"] = self.angle_rotor.as_dict(**kwargs) + InputVoltage_dict["angle_rotor"] = self.angle_rotor.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) InputVoltage_dict["rot_dir"] = self.rot_dir InputVoltage_dict["angle_rotor_initial"] = self.angle_rotor_initial InputVoltage_dict["Tem_av_ref"] = self.Tem_av_ref diff --git a/pyleecan/Classes/MagElmer.py b/pyleecan/Classes/MagElmer.py index 47650a38b..e4f502b98 100644 --- a/pyleecan/Classes/MagElmer.py +++ b/pyleecan/Classes/MagElmer.py @@ -50,6 +50,7 @@ from ._check import InitUnKnowClassError from .DXFImport import DXFImport +from .SliceModel import SliceModel class MagElmer(Magnetics): @@ -156,6 +157,9 @@ def __init__( angle_stator_shift=0, angle_rotor_shift=0, logger_name="Pyleecan.Magnetics", + Slice_enforced=None, + Nslices_enforced=None, + type_distribution_enforced=None, is_current_harm=True, init_dict=None, init_str=None, @@ -221,6 +225,12 @@ def __init__( angle_rotor_shift = init_dict["angle_rotor_shift"] if "logger_name" in list(init_dict.keys()): logger_name = init_dict["logger_name"] + if "Slice_enforced" in list(init_dict.keys()): + Slice_enforced = init_dict["Slice_enforced"] + if "Nslices_enforced" in list(init_dict.keys()): + Nslices_enforced = init_dict["Nslices_enforced"] + if "type_distribution_enforced" in list(init_dict.keys()): + type_distribution_enforced = init_dict["type_distribution_enforced"] if "is_current_harm" in list(init_dict.keys()): is_current_harm = init_dict["is_current_harm"] # Set the properties (value check and convertion are done in setter) @@ -249,6 +259,9 @@ def __init__( angle_stator_shift=angle_stator_shift, angle_rotor_shift=angle_rotor_shift, logger_name=logger_name, + Slice_enforced=Slice_enforced, + Nslices_enforced=Nslices_enforced, + type_distribution_enforced=type_distribution_enforced, is_current_harm=is_current_harm, ) # The class is frozen (in Magnetics init), for now it's impossible to @@ -394,15 +407,23 @@ def __sizeof__(self): S += getsizeof(self.nb_worker) return S - def as_dict(self, **kwargs): + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): """ Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str Optional keyword input parameter is for internal use only and may prevent json serializability. """ # Get the properties inherited from Magnetics - MagElmer_dict = super(MagElmer, self).as_dict(**kwargs) + MagElmer_dict = super(MagElmer, self).as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) MagElmer_dict["Kmesh_fineness"] = self.Kmesh_fineness MagElmer_dict["Kgeo_fineness"] = self.Kgeo_fineness MagElmer_dict["file_name"] = self.file_name @@ -417,11 +438,19 @@ def as_dict(self, **kwargs): if self.rotor_dxf is None: MagElmer_dict["rotor_dxf"] = None else: - MagElmer_dict["rotor_dxf"] = self.rotor_dxf.as_dict(**kwargs) + MagElmer_dict["rotor_dxf"] = self.rotor_dxf.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) if self.stator_dxf is None: MagElmer_dict["stator_dxf"] = None else: - MagElmer_dict["stator_dxf"] = self.stator_dxf.as_dict(**kwargs) + MagElmer_dict["stator_dxf"] = self.stator_dxf.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) MagElmer_dict["import_file"] = self.import_file MagElmer_dict["nb_worker"] = self.nb_worker # The class name is added to the dict for deserialisation purpose diff --git a/pyleecan/Classes/MagFEMM.py b/pyleecan/Classes/MagFEMM.py index 24a03eca8..d8d678a4d 100644 --- a/pyleecan/Classes/MagFEMM.py +++ b/pyleecan/Classes/MagFEMM.py @@ -50,6 +50,7 @@ from ._check import InitUnKnowClassError from .DXFImport import DXFImport +from .SliceModel import SliceModel class MagFEMM(Magnetics): @@ -149,6 +150,7 @@ def __init__( is_close_femm=True, nb_worker=1, Rag_enforced=None, + is_set_previous=True, is_remove_slotS=False, is_remove_slotR=False, is_remove_vent=False, @@ -161,6 +163,9 @@ def __init__( angle_stator_shift=0, angle_rotor_shift=0, logger_name="Pyleecan.Magnetics", + Slice_enforced=None, + Nslices_enforced=None, + type_distribution_enforced=None, is_current_harm=True, init_dict=None, init_str=None, @@ -210,6 +215,8 @@ def __init__( nb_worker = init_dict["nb_worker"] if "Rag_enforced" in list(init_dict.keys()): Rag_enforced = init_dict["Rag_enforced"] + if "is_set_previous" in list(init_dict.keys()): + is_set_previous = init_dict["is_set_previous"] if "is_remove_slotS" in list(init_dict.keys()): is_remove_slotS = init_dict["is_remove_slotS"] if "is_remove_slotR" in list(init_dict.keys()): @@ -234,6 +241,12 @@ def __init__( angle_rotor_shift = init_dict["angle_rotor_shift"] if "logger_name" in list(init_dict.keys()): logger_name = init_dict["logger_name"] + if "Slice_enforced" in list(init_dict.keys()): + Slice_enforced = init_dict["Slice_enforced"] + if "Nslices_enforced" in list(init_dict.keys()): + Nslices_enforced = init_dict["Nslices_enforced"] + if "type_distribution_enforced" in list(init_dict.keys()): + type_distribution_enforced = init_dict["type_distribution_enforced"] if "is_current_harm" in list(init_dict.keys()): is_current_harm = init_dict["is_current_harm"] # Set the properties (value check and convertion are done in setter) @@ -252,6 +265,7 @@ def __init__( self.is_close_femm = is_close_femm self.nb_worker = nb_worker self.Rag_enforced = Rag_enforced + self.is_set_previous = is_set_previous # Call Magnetics init super(MagFEMM, self).__init__( is_remove_slotS=is_remove_slotS, @@ -266,6 +280,9 @@ def __init__( angle_stator_shift=angle_stator_shift, angle_rotor_shift=angle_rotor_shift, logger_name=logger_name, + Slice_enforced=Slice_enforced, + Nslices_enforced=Nslices_enforced, + type_distribution_enforced=type_distribution_enforced, is_current_harm=is_current_harm, ) # The class is frozen (in Magnetics init), for now it's impossible to @@ -313,6 +330,7 @@ def __str__(self): MagFEMM_str += "is_close_femm = " + str(self.is_close_femm) + linesep MagFEMM_str += "nb_worker = " + str(self.nb_worker) + linesep MagFEMM_str += "Rag_enforced = " + str(self.Rag_enforced) + linesep + MagFEMM_str += "is_set_previous = " + str(self.is_set_previous) + linesep return MagFEMM_str def __eq__(self, other): @@ -354,6 +372,8 @@ def __eq__(self, other): return False if other.Rag_enforced != self.Rag_enforced: return False + if other.is_set_previous != self.is_set_previous: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -409,6 +429,8 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".nb_worker") if other._Rag_enforced != self._Rag_enforced: diff_list.append(name + ".Rag_enforced") + if other._is_set_previous != self._is_set_previous: + diff_list.append(name + ".is_set_previous") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -439,17 +461,26 @@ def __sizeof__(self): S += getsizeof(self.is_close_femm) S += getsizeof(self.nb_worker) S += getsizeof(self.Rag_enforced) + S += getsizeof(self.is_set_previous) return S - def as_dict(self, **kwargs): + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): """ Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str Optional keyword input parameter is for internal use only and may prevent json serializability. """ # Get the properties inherited from Magnetics - MagFEMM_dict = super(MagFEMM, self).as_dict(**kwargs) + MagFEMM_dict = super(MagFEMM, self).as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) MagFEMM_dict["Kmesh_fineness"] = self.Kmesh_fineness MagFEMM_dict["Kgeo_fineness"] = self.Kgeo_fineness MagFEMM_dict["type_calc_leakage"] = self.type_calc_leakage @@ -468,15 +499,24 @@ def as_dict(self, **kwargs): if self.rotor_dxf is None: MagFEMM_dict["rotor_dxf"] = None else: - MagFEMM_dict["rotor_dxf"] = self.rotor_dxf.as_dict(**kwargs) + MagFEMM_dict["rotor_dxf"] = self.rotor_dxf.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) if self.stator_dxf is None: MagFEMM_dict["stator_dxf"] = None else: - MagFEMM_dict["stator_dxf"] = self.stator_dxf.as_dict(**kwargs) + MagFEMM_dict["stator_dxf"] = self.stator_dxf.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) MagFEMM_dict["import_file"] = self.import_file MagFEMM_dict["is_close_femm"] = self.is_close_femm MagFEMM_dict["nb_worker"] = self.nb_worker MagFEMM_dict["Rag_enforced"] = self.Rag_enforced + MagFEMM_dict["is_set_previous"] = self.is_set_previous # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name MagFEMM_dict["__class__"] = "MagFEMM" @@ -502,6 +542,7 @@ def _set_None(self): self.is_close_femm = None self.nb_worker = None self.Rag_enforced = None + self.is_set_previous = None # Set to None the properties inherited from Magnetics super(MagFEMM, self)._set_None() @@ -804,3 +845,21 @@ def _set_Rag_enforced(self, value): :Type: float """, ) + + def _get_is_set_previous(self): + """getter of is_set_previous""" + return self._is_set_previous + + def _set_is_set_previous(self, value): + """setter of is_set_previous""" + check_var("is_set_previous", value, "bool") + self._is_set_previous = value + + is_set_previous = property( + fget=_get_is_set_previous, + fset=_set_is_set_previous, + doc=u"""True set previous .ans result file in current .fem to use it as initialization and speed up calculation time + + :Type: bool + """, + ) diff --git a/pyleecan/Classes/Magnetics.py b/pyleecan/Classes/Magnetics.py index 0ee7862cd..866ad98d6 100644 --- a/pyleecan/Classes/Magnetics.py +++ b/pyleecan/Classes/Magnetics.py @@ -27,6 +27,11 @@ except ImportError as error: comp_axes = error +try: + from ..Methods.Simulation.Magnetics.get_slice_model import get_slice_model +except ImportError as error: + get_slice_model = error + try: from ..Methods.Simulation.Magnetics.comp_I_mag import comp_I_mag except ImportError as error: @@ -34,6 +39,7 @@ from ._check import InitUnKnowClassError +from .SliceModel import SliceModel class Magnetics(FrozenClass): @@ -60,6 +66,18 @@ class Magnetics(FrozenClass): ) else: comp_axes = comp_axes + # cf Methods.Simulation.Magnetics.get_slice_model + if isinstance(get_slice_model, ImportError): + get_slice_model = property( + fget=lambda x: raise_( + ImportError( + "Can't use Magnetics method get_slice_model: " + + str(get_slice_model) + ) + ) + ) + else: + get_slice_model = get_slice_model # cf Methods.Simulation.Magnetics.comp_I_mag if isinstance(comp_I_mag, ImportError): comp_I_mag = property( @@ -89,6 +107,9 @@ def __init__( angle_stator_shift=0, angle_rotor_shift=0, logger_name="Pyleecan.Magnetics", + Slice_enforced=None, + Nslices_enforced=None, + type_distribution_enforced=None, is_current_harm=True, init_dict=None, init_str=None, @@ -132,6 +153,12 @@ def __init__( angle_rotor_shift = init_dict["angle_rotor_shift"] if "logger_name" in list(init_dict.keys()): logger_name = init_dict["logger_name"] + if "Slice_enforced" in list(init_dict.keys()): + Slice_enforced = init_dict["Slice_enforced"] + if "Nslices_enforced" in list(init_dict.keys()): + Nslices_enforced = init_dict["Nslices_enforced"] + if "type_distribution_enforced" in list(init_dict.keys()): + type_distribution_enforced = init_dict["type_distribution_enforced"] if "is_current_harm" in list(init_dict.keys()): is_current_harm = init_dict["is_current_harm"] # Set the properties (value check and convertion are done in setter) @@ -148,6 +175,9 @@ def __init__( self.angle_stator_shift = angle_stator_shift self.angle_rotor_shift = angle_rotor_shift self.logger_name = logger_name + self.Slice_enforced = Slice_enforced + self.Nslices_enforced = Nslices_enforced + self.type_distribution_enforced = type_distribution_enforced self.is_current_harm = is_current_harm # The class is frozen, for now it's impossible to add new properties @@ -175,6 +205,22 @@ def __str__(self): ) Magnetics_str += "angle_rotor_shift = " + str(self.angle_rotor_shift) + linesep Magnetics_str += 'logger_name = "' + str(self.logger_name) + '"' + linesep + if self.Slice_enforced is not None: + tmp = ( + self.Slice_enforced.__str__() + .replace(linesep, linesep + "\t") + .rstrip("\t") + ) + Magnetics_str += "Slice_enforced = " + tmp + else: + Magnetics_str += "Slice_enforced = None" + linesep + linesep + Magnetics_str += "Nslices_enforced = " + str(self.Nslices_enforced) + linesep + Magnetics_str += ( + 'type_distribution_enforced = "' + + str(self.type_distribution_enforced) + + '"' + + linesep + ) Magnetics_str += "is_current_harm = " + str(self.is_current_harm) + linesep return Magnetics_str @@ -207,6 +253,12 @@ def __eq__(self, other): return False if other.logger_name != self.logger_name: return False + if other.Slice_enforced != self.Slice_enforced: + return False + if other.Nslices_enforced != self.Nslices_enforced: + return False + if other.type_distribution_enforced != self.type_distribution_enforced: + return False if other.is_current_harm != self.is_current_harm: return False return True @@ -243,6 +295,20 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".angle_rotor_shift") if other._logger_name != self._logger_name: diff_list.append(name + ".logger_name") + if (other.Slice_enforced is None and self.Slice_enforced is not None) or ( + other.Slice_enforced is not None and self.Slice_enforced is None + ): + diff_list.append(name + ".Slice_enforced None mismatch") + elif self.Slice_enforced is not None: + diff_list.extend( + self.Slice_enforced.compare( + other.Slice_enforced, name=name + ".Slice_enforced" + ) + ) + if other._Nslices_enforced != self._Nslices_enforced: + diff_list.append(name + ".Nslices_enforced") + if other._type_distribution_enforced != self._type_distribution_enforced: + diff_list.append(name + ".type_distribution_enforced") if other._is_current_harm != self._is_current_harm: diff_list.append(name + ".is_current_harm") # Filter ignore differences @@ -265,12 +331,19 @@ def __sizeof__(self): S += getsizeof(self.angle_stator_shift) S += getsizeof(self.angle_rotor_shift) S += getsizeof(self.logger_name) + S += getsizeof(self.Slice_enforced) + S += getsizeof(self.Nslices_enforced) + S += getsizeof(self.type_distribution_enforced) S += getsizeof(self.is_current_harm) return S - def as_dict(self, **kwargs): + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): """ Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str Optional keyword input parameter is for internal use only and may prevent json serializability. """ @@ -288,6 +361,16 @@ def as_dict(self, **kwargs): Magnetics_dict["angle_stator_shift"] = self.angle_stator_shift Magnetics_dict["angle_rotor_shift"] = self.angle_rotor_shift Magnetics_dict["logger_name"] = self.logger_name + if self.Slice_enforced is None: + Magnetics_dict["Slice_enforced"] = None + else: + Magnetics_dict["Slice_enforced"] = self.Slice_enforced.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) + Magnetics_dict["Nslices_enforced"] = self.Nslices_enforced + Magnetics_dict["type_distribution_enforced"] = self.type_distribution_enforced Magnetics_dict["is_current_harm"] = self.is_current_harm # The class name is added to the dict for deserialisation purpose Magnetics_dict["__class__"] = "Magnetics" @@ -308,6 +391,10 @@ def _set_None(self): self.angle_stator_shift = None self.angle_rotor_shift = None self.logger_name = None + if self.Slice_enforced is not None: + self.Slice_enforced._set_None() + self.Nslices_enforced = None + self.type_distribution_enforced = None self.is_current_harm = None def _get_is_remove_slotS(self): @@ -530,6 +617,72 @@ def _set_logger_name(self, value): """, ) + def _get_Slice_enforced(self): + """getter of Slice_enforced""" + return self._Slice_enforced + + def _set_Slice_enforced(self, value): + """setter of Slice_enforced""" + if isinstance(value, str): # Load from file + value = load_init_dict(value)[1] + if isinstance(value, dict) and "__class__" in value: + class_obj = import_class( + "pyleecan.Classes", value.get("__class__"), "Slice_enforced" + ) + value = class_obj(init_dict=value) + elif type(value) is int and value == -1: # Default constructor + value = SliceModel() + check_var("Slice_enforced", value, "SliceModel") + self._Slice_enforced = value + + if self._Slice_enforced is not None: + self._Slice_enforced.parent = self + + Slice_enforced = property( + fget=_get_Slice_enforced, + fset=_set_Slice_enforced, + doc=u"""Enforce slice model to account for skew + + :Type: SliceModel + """, + ) + + def _get_Nslices_enforced(self): + """getter of Nslices_enforced""" + return self._Nslices_enforced + + def _set_Nslices_enforced(self, value): + """setter of Nslices_enforced""" + check_var("Nslices_enforced", value, "int") + self._Nslices_enforced = value + + Nslices_enforced = property( + fget=_get_Nslices_enforced, + fset=_set_Nslices_enforced, + doc=u"""To enforce number of slices in slice model + + :Type: int + """, + ) + + def _get_type_distribution_enforced(self): + """getter of type_distribution_enforced""" + return self._type_distribution_enforced + + def _set_type_distribution_enforced(self, value): + """setter of type_distribution_enforced""" + check_var("type_distribution_enforced", value, "str") + self._type_distribution_enforced = value + + type_distribution_enforced = property( + fget=_get_type_distribution_enforced, + fset=_set_type_distribution_enforced, + doc=u"""To enforce type of slice distribution to use for rotor skew if linear and continuous ("uniform", "gauss", "user-defined") + + :Type: str + """, + ) + def _get_is_current_harm(self): """getter of is_current_harm""" return self._is_current_harm diff --git a/pyleecan/Classes/OutElec.py b/pyleecan/Classes/OutElec.py index aacb9da8f..e5d6bcf2e 100644 --- a/pyleecan/Classes/OutElec.py +++ b/pyleecan/Classes/OutElec.py @@ -415,9 +415,13 @@ def __sizeof__(self): S += getsizeof(self.U0_ref) return S - def as_dict(self, **kwargs): + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): """ Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str Optional keyword input parameter is for internal use only and may prevent json serializability. """ @@ -429,21 +433,42 @@ def as_dict(self, **kwargs): OutElec_dict["axes_dict"] = dict() for key, obj in self.axes_dict.items(): if obj is not None: - OutElec_dict["axes_dict"][key] = obj.as_dict() + OutElec_dict["axes_dict"][key] = obj.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) else: OutElec_dict["axes_dict"][key] = None if self.Is is None: OutElec_dict["Is"] = None else: - OutElec_dict["Is"] = self.Is.as_dict() + OutElec_dict["Is"] = self.Is.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) if self.Ir is None: OutElec_dict["Ir"] = None else: - OutElec_dict["Ir"] = self.Ir.as_dict() + OutElec_dict["Ir"] = self.Ir.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) if self.angle_rotor is None: OutElec_dict["angle_rotor"] = None else: - OutElec_dict["angle_rotor"] = self.angle_rotor.tolist() + if type_handle_ndarray == 0: + OutElec_dict["angle_rotor"] = self.angle_rotor.tolist() + elif type_handle_ndarray == 1: + OutElec_dict["angle_rotor"] = self.angle_rotor.copy() + elif type_handle_ndarray == 2: + OutElec_dict["angle_rotor"] = self.angle_rotor + else: + raise Exception( + "Unknown type_handle_ndarray: " + str(type_handle_ndarray) + ) OutElec_dict["N0"] = self.N0 OutElec_dict["angle_rotor_initial"] = self.angle_rotor_initial OutElec_dict["logger_name"] = self.logger_name @@ -458,11 +483,19 @@ def as_dict(self, **kwargs): if self.Us is None: OutElec_dict["Us"] = None else: - OutElec_dict["Us"] = self.Us.as_dict() + OutElec_dict["Us"] = self.Us.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) if self.internal is None: OutElec_dict["internal"] = None else: - OutElec_dict["internal"] = self.internal.as_dict(**kwargs) + OutElec_dict["internal"] = self.internal.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) OutElec_dict["slip_ref"] = self.slip_ref OutElec_dict["U0_ref"] = self.U0_ref # The class name is added to the dict for deserialisation purpose diff --git a/pyleecan/Classes/OutForce.py b/pyleecan/Classes/OutForce.py index c1bfd87c2..84921a479 100644 --- a/pyleecan/Classes/OutForce.py +++ b/pyleecan/Classes/OutForce.py @@ -193,9 +193,13 @@ def __sizeof__(self): S += getsizeof(self.meshsolution) return S - def as_dict(self, **kwargs): + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): """ Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str Optional keyword input parameter is for internal use only and may prevent json serializability. """ @@ -207,19 +211,31 @@ def as_dict(self, **kwargs): OutForce_dict["axes_dict"] = dict() for key, obj in self.axes_dict.items(): if obj is not None: - OutForce_dict["axes_dict"][key] = obj.as_dict() + OutForce_dict["axes_dict"][key] = obj.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) else: OutForce_dict["axes_dict"][key] = None if self.AGSF is None: OutForce_dict["AGSF"] = None else: - OutForce_dict["AGSF"] = self.AGSF.as_dict() + OutForce_dict["AGSF"] = self.AGSF.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) OutForce_dict["logger_name"] = self.logger_name OutForce_dict["Rag"] = self.Rag if self.meshsolution is None: OutForce_dict["meshsolution"] = None else: - OutForce_dict["meshsolution"] = self.meshsolution.as_dict(**kwargs) + OutForce_dict["meshsolution"] = self.meshsolution.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) # The class name is added to the dict for deserialisation purpose OutForce_dict["__class__"] = "OutForce" return OutForce_dict diff --git a/pyleecan/Classes/OutMag.py b/pyleecan/Classes/OutMag.py index c92e1db04..686d2a57e 100644 --- a/pyleecan/Classes/OutMag.py +++ b/pyleecan/Classes/OutMag.py @@ -51,6 +51,7 @@ from ._check import InitUnKnowClassError from .MeshSolution import MeshSolution from .OutInternal import OutInternal +from .SliceModel import SliceModel class OutMag(FrozenClass): @@ -135,6 +136,9 @@ def __init__( internal=None, Rag=None, Pem_av=None, + Slice=None, + Tem_slice=None, + Phi_wind_slice=None, init_dict=None, init_str=None, ): @@ -181,6 +185,12 @@ def __init__( Rag = init_dict["Rag"] if "Pem_av" in list(init_dict.keys()): Pem_av = init_dict["Pem_av"] + if "Slice" in list(init_dict.keys()): + Slice = init_dict["Slice"] + if "Tem_slice" in list(init_dict.keys()): + Tem_slice = init_dict["Tem_slice"] + if "Phi_wind_slice" in list(init_dict.keys()): + Phi_wind_slice = init_dict["Phi_wind_slice"] # Set the properties (value check and convertion are done in setter) self.parent = None self.axes_dict = axes_dict @@ -197,6 +207,9 @@ def __init__( self.internal = internal self.Rag = Rag self.Pem_av = Pem_av + self.Slice = Slice + self.Tem_slice = Tem_slice + self.Phi_wind_slice = Phi_wind_slice # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -237,6 +250,13 @@ def __str__(self): OutMag_str += "internal = None" + linesep + linesep OutMag_str += "Rag = " + str(self.Rag) + linesep OutMag_str += "Pem_av = " + str(self.Pem_av) + linesep + if self.Slice is not None: + tmp = self.Slice.__str__().replace(linesep, linesep + "\t").rstrip("\t") + OutMag_str += "Slice = " + tmp + else: + OutMag_str += "Slice = None" + linesep + linesep + OutMag_str += "Tem_slice = " + str(self.Tem_slice) + linesep + linesep + OutMag_str += "Phi_wind_slice = " + str(self.Phi_wind_slice) + linesep + linesep return OutMag_str def __eq__(self, other): @@ -272,6 +292,12 @@ def __eq__(self, other): return False if other.Pem_av != self.Pem_av: return False + if other.Slice != self.Slice: + return False + if other.Tem_slice != self.Tem_slice: + return False + if other.Phi_wind_slice != self.Phi_wind_slice: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -370,6 +396,35 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".Rag") if other._Pem_av != self._Pem_av: diff_list.append(name + ".Pem_av") + if (other.Slice is None and self.Slice is not None) or ( + other.Slice is not None and self.Slice is None + ): + diff_list.append(name + ".Slice None mismatch") + elif self.Slice is not None: + diff_list.extend(self.Slice.compare(other.Slice, name=name + ".Slice")) + if (other.Tem_slice is None and self.Tem_slice is not None) or ( + other.Tem_slice is not None and self.Tem_slice is None + ): + diff_list.append(name + ".Tem_slice None mismatch") + elif self.Tem_slice is not None: + diff_list.extend( + self.Tem_slice.compare(other.Tem_slice, name=name + ".Tem_slice") + ) + if (other.Phi_wind_slice is None and self.Phi_wind_slice is not None) or ( + other.Phi_wind_slice is not None and self.Phi_wind_slice is None + ): + diff_list.append(name + ".Phi_wind_slice None mismatch") + elif self.Phi_wind_slice is None: + pass + elif len(other.Phi_wind_slice) != len(self.Phi_wind_slice): + diff_list.append("len(" + name + "Phi_wind_slice)") + else: + for key in self.Phi_wind_slice: + diff_list.extend( + self.Phi_wind_slice[key].compare( + other.Phi_wind_slice[key], name=name + ".Phi_wind_slice" + ) + ) # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -396,11 +451,20 @@ def __sizeof__(self): S += getsizeof(self.internal) S += getsizeof(self.Rag) S += getsizeof(self.Pem_av) + S += getsizeof(self.Slice) + S += getsizeof(self.Tem_slice) + if self.Phi_wind_slice is not None: + for key, value in self.Phi_wind_slice.items(): + S += getsizeof(value) + getsizeof(key) return S - def as_dict(self, **kwargs): + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): """ Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str Optional keyword input parameter is for internal use only and may prevent json serializability. """ @@ -412,48 +476,109 @@ def as_dict(self, **kwargs): OutMag_dict["axes_dict"] = dict() for key, obj in self.axes_dict.items(): if obj is not None: - OutMag_dict["axes_dict"][key] = obj.as_dict() + OutMag_dict["axes_dict"][key] = obj.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) else: OutMag_dict["axes_dict"][key] = None if self.B is None: OutMag_dict["B"] = None else: - OutMag_dict["B"] = self.B.as_dict() + OutMag_dict["B"] = self.B.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) if self.Tem is None: OutMag_dict["Tem"] = None else: - OutMag_dict["Tem"] = self.Tem.as_dict() + OutMag_dict["Tem"] = self.Tem.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) OutMag_dict["Tem_av"] = self.Tem_av OutMag_dict["Tem_rip_norm"] = self.Tem_rip_norm OutMag_dict["Tem_rip_pp"] = self.Tem_rip_pp if self.Phi_wind_stator is None: OutMag_dict["Phi_wind_stator"] = None else: - OutMag_dict["Phi_wind_stator"] = self.Phi_wind_stator.as_dict() + OutMag_dict["Phi_wind_stator"] = self.Phi_wind_stator.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) if self.Phi_wind is None: OutMag_dict["Phi_wind"] = None else: OutMag_dict["Phi_wind"] = dict() for key, obj in self.Phi_wind.items(): if obj is not None: - OutMag_dict["Phi_wind"][key] = obj.as_dict() + OutMag_dict["Phi_wind"][key] = obj.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) else: OutMag_dict["Phi_wind"][key] = None if self.emf is None: OutMag_dict["emf"] = None else: - OutMag_dict["emf"] = self.emf.as_dict() + OutMag_dict["emf"] = self.emf.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) if self.meshsolution is None: OutMag_dict["meshsolution"] = None else: - OutMag_dict["meshsolution"] = self.meshsolution.as_dict(**kwargs) + OutMag_dict["meshsolution"] = self.meshsolution.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) OutMag_dict["logger_name"] = self.logger_name if self.internal is None: OutMag_dict["internal"] = None else: - OutMag_dict["internal"] = self.internal.as_dict(**kwargs) + OutMag_dict["internal"] = self.internal.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) OutMag_dict["Rag"] = self.Rag OutMag_dict["Pem_av"] = self.Pem_av + if self.Slice is None: + OutMag_dict["Slice"] = None + else: + OutMag_dict["Slice"] = self.Slice.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) + if self.Tem_slice is None: + OutMag_dict["Tem_slice"] = None + else: + OutMag_dict["Tem_slice"] = self.Tem_slice.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) + if self.Phi_wind_slice is None: + OutMag_dict["Phi_wind_slice"] = None + else: + OutMag_dict["Phi_wind_slice"] = dict() + for key, obj in self.Phi_wind_slice.items(): + if obj is not None: + OutMag_dict["Phi_wind_slice"][key] = obj.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) + else: + OutMag_dict["Phi_wind_slice"][key] = None # The class name is added to the dict for deserialisation purpose OutMag_dict["__class__"] = "OutMag" return OutMag_dict @@ -477,6 +602,10 @@ def _set_None(self): self.internal._set_None() self.Rag = None self.Pem_av = None + if self.Slice is not None: + self.Slice._set_None() + self.Tem_slice = None + self.Phi_wind_slice = None def _get_axes_dict(self): """getter of axes_dict""" @@ -629,8 +758,8 @@ def _set_Phi_wind_stator(self, value): ) value = class_obj(init_dict=value) elif type(value) is int and value == -1: # Default constructor - value = DataTime() - check_var("Phi_wind_stator", value, "DataTime") + value = DataND() + check_var("Phi_wind_stator", value, "DataND") self._Phi_wind_stator = value Phi_wind_stator = property( @@ -638,7 +767,7 @@ def _set_Phi_wind_stator(self, value): fset=_set_Phi_wind_stator, doc=u"""Stator winding flux DataTime object - :Type: SciDataTool.Classes.DataTime.DataTime + :Type: SciDataTool.Classes.DataND.DataND """, ) @@ -661,7 +790,7 @@ def _set_Phi_wind(self, value): value[key] = class_obj(init_dict=obj) if type(value) is int and value == -1: value = dict() - check_var("Phi_wind", value, "{DataTime}") + check_var("Phi_wind", value, "{DataND}") self._Phi_wind = value Phi_wind = property( @@ -669,7 +798,7 @@ def _set_Phi_wind(self, value): fset=_set_Phi_wind, doc=u"""Dict of lamination winding fluxlinkage DataTime objects - :Type: {SciDataTool.Classes.DataTime.DataTime} + :Type: {SciDataTool.Classes.DataND.DataND} """, ) @@ -687,8 +816,8 @@ def _set_emf(self, value): ) value = class_obj(init_dict=value) elif type(value) is int and value == -1: # Default constructor - value = DataTime() - check_var("emf", value, "DataTime") + value = DataND() + check_var("emf", value, "DataND") self._emf = value emf = property( @@ -696,7 +825,7 @@ def _set_emf(self, value): fset=_set_emf, doc=u"""Electromotive force DataTime object - :Type: SciDataTool.Classes.DataTime.DataTime + :Type: SciDataTool.Classes.DataND.DataND """, ) @@ -813,3 +942,91 @@ def _set_Pem_av(self, value): :Type: float """, ) + + def _get_Slice(self): + """getter of Slice""" + return self._Slice + + def _set_Slice(self, value): + """setter of Slice""" + if isinstance(value, str): # Load from file + value = load_init_dict(value)[1] + if isinstance(value, dict) and "__class__" in value: + class_obj = import_class( + "pyleecan.Classes", value.get("__class__"), "Slice" + ) + value = class_obj(init_dict=value) + elif type(value) is int and value == -1: # Default constructor + value = SliceModel() + check_var("Slice", value, "SliceModel") + self._Slice = value + + if self._Slice is not None: + self._Slice.parent = self + + Slice = property( + fget=_get_Slice, + fset=_set_Slice, + doc=u"""Slice model to account for skew + + :Type: SliceModel + """, + ) + + def _get_Tem_slice(self): + """getter of Tem_slice""" + return self._Tem_slice + + def _set_Tem_slice(self, value): + """setter of Tem_slice""" + if isinstance(value, str): # Load from file + value = load_init_dict(value)[1] + if isinstance(value, dict) and "__class__" in value: + class_obj = import_class( + "SciDataTool.Classes", value.get("__class__"), "Tem_slice" + ) + value = class_obj(init_dict=value) + elif type(value) is int and value == -1: # Default constructor + value = DataND() + check_var("Tem_slice", value, "DataND") + self._Tem_slice = value + + Tem_slice = property( + fget=_get_Tem_slice, + fset=_set_Tem_slice, + doc=u"""Electromagnetic torque DataTime object including torque per slice + + :Type: SciDataTool.Classes.DataND.DataND + """, + ) + + def _get_Phi_wind_slice(self): + """getter of Phi_wind_slice""" + if self._Phi_wind_slice is not None: + for key, obj in self._Phi_wind_slice.items(): + if obj is not None: + obj.parent = self + return self._Phi_wind_slice + + def _set_Phi_wind_slice(self, value): + """setter of Phi_wind_slice""" + if type(value) is dict: + for key, obj in value.items(): + if type(obj) is dict: + class_obj = import_class( + "SciDataTool.Classes", obj.get("__class__"), "Phi_wind_slice" + ) + value[key] = class_obj(init_dict=obj) + if type(value) is int and value == -1: + value = dict() + check_var("Phi_wind_slice", value, "{DataND}") + self._Phi_wind_slice = value + + Phi_wind_slice = property( + fget=_get_Phi_wind_slice, + fset=_set_Phi_wind_slice, + doc=u"""Dict of lamination winding fluxlinkage DataTime objects per slice + + :Type: {SciDataTool.Classes.DataND.DataND} + """, + ) diff --git a/pyleecan/Classes/OutStruct.py b/pyleecan/Classes/OutStruct.py index 8824056c8..c7247e584 100644 --- a/pyleecan/Classes/OutStruct.py +++ b/pyleecan/Classes/OutStruct.py @@ -164,9 +164,13 @@ def __sizeof__(self): S += getsizeof(value) + getsizeof(key) return S - def as_dict(self, **kwargs): + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): """ Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str Optional keyword input parameter is for internal use only and may prevent json serializability. """ @@ -178,14 +182,22 @@ def as_dict(self, **kwargs): OutStruct_dict["axes_dict"] = dict() for key, obj in self.axes_dict.items(): if obj is not None: - OutStruct_dict["axes_dict"][key] = obj.as_dict() + OutStruct_dict["axes_dict"][key] = obj.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) else: OutStruct_dict["axes_dict"][key] = None OutStruct_dict["logger_name"] = self.logger_name if self.meshsolution is None: OutStruct_dict["meshsolution"] = None else: - OutStruct_dict["meshsolution"] = self.meshsolution.as_dict(**kwargs) + OutStruct_dict["meshsolution"] = self.meshsolution.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) OutStruct_dict["FEA_dict"] = ( self.FEA_dict.copy() if self.FEA_dict is not None else None ) diff --git a/pyleecan/Classes/PostELUT.py b/pyleecan/Classes/PostELUT.py index 8154e7cff..8f07fdc9d 100644 --- a/pyleecan/Classes/PostELUT.py +++ b/pyleecan/Classes/PostELUT.py @@ -154,19 +154,31 @@ def __sizeof__(self): S += getsizeof(self.is_store_ELUT) return S - def as_dict(self, **kwargs): + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): """ Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str Optional keyword input parameter is for internal use only and may prevent json serializability. """ # Get the properties inherited from PostMethod - PostELUT_dict = super(PostELUT, self).as_dict(**kwargs) + PostELUT_dict = super(PostELUT, self).as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) if self.ELUT is None: PostELUT_dict["ELUT"] = None else: - PostELUT_dict["ELUT"] = self.ELUT.as_dict(**kwargs) + PostELUT_dict["ELUT"] = self.ELUT.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) PostELUT_dict["is_save_ELUT"] = self.is_save_ELUT PostELUT_dict["is_store_ELUT"] = self.is_store_ELUT # The class name is added to the dict for deserialisation purpose From d491f091c2d52315e3220d0297c0dba31f09fb9c Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Thu, 7 Oct 2021 16:54:19 +0200 Subject: [PATCH 063/167] [BC] Correct periodicity test --- Tests/Methods/Machine/test_comp_periodicity.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Tests/Methods/Machine/test_comp_periodicity.py b/Tests/Methods/Machine/test_comp_periodicity.py index fefd8ba3e..0924a423b 100644 --- a/Tests/Methods/Machine/test_comp_periodicity.py +++ b/Tests/Methods/Machine/test_comp_periodicity.py @@ -28,11 +28,9 @@ def test_comp_periodicity(machine): machine_obj = load(join(DATA_DIR, "Machine", machine[0] + ".json")) - # per_a, aper_a = machine_obj.comp_periodicity_spatial() + per_a, aper_a = machine_obj.comp_periodicity_spatial() - # per_t, aper_t, _, _ = machine_obj.comp_periodicity_time() - - per_a, aper_a, per_t, aper_t = machine_obj.comp_periodicity() + per_t, aper_t, _, _ = machine_obj.comp_periodicity_time() msg = ( "Wrong periodicity calculation for " From efda7d213b38b14c62bb2c9fd5b36562dcea7a74 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Thu, 7 Oct 2021 17:10:09 +0200 Subject: [PATCH 064/167] [BC] Correct angle rotor methods to return angle_rotor in radians --- .../Output/Output/getter/comp_angle_rotor.py | 23 +++++++++++-------- .../Output/Output/getter/get_angle_rotor.py | 11 ++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py index 341845dfc..867ef0aa4 100644 --- a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py +++ b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py @@ -1,4 +1,4 @@ -from numpy import pi, cumsum, roll, ones, unique +from numpy import pi, cumsum, roll, array, unique from SciDataTool import Norm_vector, Norm_affine @@ -16,7 +16,7 @@ def comp_angle_rotor(self, Time): Returns ------- - alpha_rotor: numpy.ndarray + angle_rotor: ndarray angular position of the rotor as a function of time (vector) [rad] """ @@ -34,14 +34,19 @@ def comp_angle_rotor(self, Time): # Case where normalization is a constant if unique(Nr).size == 1: - norm = Norm_affine(slope=rot_dir * Nr[0] * 360 / 60, offset=A0 * 180 / pi) + # Define affine normalization between time and rotor angle + Time.normalizations["angle_rotor"] = Norm_affine( + slope=rot_dir * Nr[0] * 360 / 60, offset=A0 * 180 / pi + ) + # Compute rotor angle array from normalization + angle_rotor = Time.get_values(normalization="angle_rotor") * pi / 180 else: - + # Calculate rotor angle from spee time = Time.get_values(is_smallestperiod=True) if time.size == 1: # Only one time step, no need to compute the position - angle_rotor = ones(1) * A0 + angle_rotor = array([A0]) else: deltaT = time[1] - time[0] # Convert Nr from [rpm] to [rad/s] (time in [s] and angle_rotor in [rad]) @@ -50,9 +55,7 @@ def comp_angle_rotor(self, Time): Ar = roll(Ar, 1) Ar[0] = 0 angle_rotor = Ar + A0 - norm = Norm_vector(vector=angle_rotor) - - # Store in time axis normalizations - Time.normalizations["angle_rotor"] = norm + # Define vector normalization between time and rotor angle + Time.normalizations["angle_rotor"] = Norm_vector(vector=angle_rotor) - return Time.get_values(normalization="angle_rotor") + return angle_rotor diff --git a/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py b/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py index 68a891c96..b62c566af 100644 --- a/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py +++ b/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- - -from .....Methods.Output.Output.getter import GetOutError -from numpy import pi, cumsum, roll, size, ones +from numpy import pi def get_angle_rotor(self, Time=None): @@ -18,7 +15,7 @@ def get_angle_rotor(self, Time=None): Returns ------- - alpha_rotor: numpy.ndarray + angle_rotor: ndarray angular position of the rotor as a function of time (vector) [rad] """ @@ -34,8 +31,8 @@ def get_angle_rotor(self, Time=None): logger.error("No time axis, cannot compute rotor angle") if "angle_rotor" in Time.normalizations: - # angle rotor is stored as normalization of Time axis - angle_rotor = Time.get_values(normalization="angle_rotor") + # angle rotor is stored in degrees as normalization of Time axis + angle_rotor = Time.get_values(normalization="angle_rotor") * pi / 180 else: # compute angle rotor from time axis angle_rotor = self.comp_angle_rotor(Time) From bac31f3b465eec9501405687c6ebbcd17178b1d7 Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 7 Oct 2021 17:33:59 +0200 Subject: [PATCH 065/167] [CC] add missing method to csv --- pyleecan/Classes/Class_Dict.json | 3 ++- pyleecan/Classes/ELUT_PMSM.py | 19 +++++++++++++++++++ .../ClassesRef/Simulation/ELUT_PMSM.csv | 1 + 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 0c140546b..506d4e6e4 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -1322,7 +1322,8 @@ "get_Lmd", "get_Lmq", "comp_Ldqh_from_Phidqh", - "import_from_data" + "import_from_data", + "comp_Phidqh_from_Phiwind" ], "mother": "ELUT", "name": "ELUT_PMSM", diff --git a/pyleecan/Classes/ELUT_PMSM.py b/pyleecan/Classes/ELUT_PMSM.py index dbdc2a270..e3b54f419 100644 --- a/pyleecan/Classes/ELUT_PMSM.py +++ b/pyleecan/Classes/ELUT_PMSM.py @@ -59,6 +59,13 @@ except ImportError as error: import_from_data = error +try: + from ..Methods.Simulation.ELUT_PMSM.comp_Phidqh_from_Phiwind import ( + comp_Phidqh_from_Phiwind, + ) +except ImportError as error: + comp_Phidqh_from_Phiwind = error + from numpy import array, array_equal from ._check import InitUnKnowClassError @@ -150,6 +157,18 @@ class ELUT_PMSM(ELUT): ) else: import_from_data = import_from_data + # cf Methods.Simulation.ELUT_PMSM.comp_Phidqh_from_Phiwind + if isinstance(comp_Phidqh_from_Phiwind, ImportError): + comp_Phidqh_from_Phiwind = property( + fget=lambda x: raise_( + ImportError( + "Can't use ELUT_PMSM method comp_Phidqh_from_Phiwind: " + + str(comp_Phidqh_from_Phiwind) + ) + ) + ) + else: + comp_Phidqh_from_Phiwind = comp_Phidqh_from_Phiwind # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv b/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv index 63d345cd5..0a59469f6 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv @@ -7,3 +7,4 @@ E_dqh,V,Back emf harmonics along DQH axis,,ndarray,None,,,,,,get_Lmd,,,, orders_dqh,,Back harmonic orders (multiple of fundamental electrical frequency) corresponding to Edqh spectrum,,ndarray,None,,,,,,get_Lmq,,,, bemf,V,Back electromotive force DataTime object,"(Nt_tot, qs)",SciDataTool.Classes.DataND.DataND,None,,,,,,comp_Ldqh_from_Phidqh,,,, ,,,,,,,,,,,import_from_data,,,, +,,,,,,,,,,,comp_Phidqh_from_Phiwind,,,, From c8b51fc283178588d74eebc6c58bf70438f4a93a Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Thu, 7 Oct 2021 17:43:09 +0200 Subject: [PATCH 066/167] [WiP] Test for ELUT PMSM calculation --- .../Electrical/test_EEC_ELUT_PMSM.py | 43 ++++--------------- 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py index fe08eabb4..a4ce73c84 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py @@ -50,23 +50,6 @@ def test_ELUT_PMSM(): # Define second simu for FEMM comparison simu.mag = MagFEMM(is_periodicity_a=True, is_periodicity_t=True, nb_worker=4) - # Datakeepers - # # Stator Winding Flux Datakeeper - # Phi_wind_stator_dk = DataKeeper( - # name="Stator Winding Flux", - # symbol="Phi_{wind}", - # unit="Wb", - # keeper="lambda out: out.mag.Phi_wind_stator", - # ) - - # # Instanteneous torque Datakeeper - # Tem_dk = DataKeeper( - # name="Electromagnetic torque", - # symbol="T_{em}", - # unit="N.m", - # keeper="lambda out: out.mag.Tem", - # ) - # Stator Winding Flux along dq Datakeeper Phi_wind_dq_dk = DataKeeper( name="Stator Winding Flux along dq axes", @@ -76,7 +59,6 @@ def test_ELUT_PMSM(): ) # Store Datakeepers - # simu.var_simu.datakeeper_list = [Phi_wind_stator_dk, Tem_dk, Phi_wind_dq_dk] simu.var_simu.datakeeper_list = [Phi_wind_dq_dk] # Postprocessing @@ -84,23 +66,14 @@ def test_ELUT_PMSM(): out = simu.run() - # Definition of the magnetic simulation (FEMM) - - # out2 = Output(simu=simu2) - # simu2.run() - - # # Plot 3-phase current function of time - # out.elec.get_Is().plot_2D_Data( - # "time", - # "phase[]", - # save_path=join(save_path, "EEC_FEMM_IPMSM_currents.png"), - # is_show_fig=False, - # **dict_2D - # ) - - # # from Yang et al, 2013 - # assert out.elec.Tem_av_ref == pytest.approx(81.81, rel=0.1) - # assert out2.mag.Tem_av == pytest.approx(81.70, rel=0.1) + # Plot 3-phase current function of time + out.mag.Phi_wind_stator.plot_2D_Data( + "time", + "phase[]", + # save_path=join(save_path, "EEC_FEMM_IPMSM_currents.png"), + # is_show_fig=False, + **dict_2D + ) return out From fdff1bcabc87db7bb296bb4fbe39ef1b65d0e6e0 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Thu, 7 Oct 2021 18:03:18 +0200 Subject: [PATCH 067/167] [BC] Correct tests --- Tests/Plot/test_ICEM_2020.py | 1 + Tests/Plot/test_plots.py | 111 +++++++++++++----- .../Simulation/Magnetics/comp_I_mag.py | 4 +- 3 files changed, 86 insertions(+), 30 deletions(-) diff --git a/Tests/Plot/test_ICEM_2020.py b/Tests/Plot/test_ICEM_2020.py index e1d32dc0b..e8720b6e5 100644 --- a/Tests/Plot/test_ICEM_2020.py +++ b/Tests/Plot/test_ICEM_2020.py @@ -842,4 +842,5 @@ def test_Optimization_problem(): if __name__ == "__main__": + test_FEMM_sym() test_WindingUD_layer() diff --git a/Tests/Plot/test_plots.py b/Tests/Plot/test_plots.py index 97f179a7c..a5200efb8 100644 --- a/Tests/Plot/test_plots.py +++ b/Tests/Plot/test_plots.py @@ -12,7 +12,6 @@ from pyleecan.Classes.InputFlux import InputFlux from pyleecan.Classes.Output import Output from pyleecan.Classes.Simu1 import Simu1 -from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Functions.load import load from pyleecan.Functions.Plot import dict_2D, dict_3D from pyleecan.definitions import DATA_DIR @@ -20,6 +19,11 @@ @pytest.fixture(scope="module") def import_data(): + data = import_data_func() + return data + + +def import_data_func(): SCIM_006 = load(join(DATA_DIR, "Machine", "SCIM_006.json")) simu = Simu1(name="test_plots", machine=SCIM_006) @@ -55,7 +59,9 @@ def import_data(): data["flux_FT"] = ImportMatlab(mat_file_Br_cfft2, var_name="Fwr") data["freqs"] = ImportMatlab(mat_file_Brfreqs, var_name="freqs") data["wavenumber"] = ImportMatlab(mat_file_Brwavenumber, var_name="orders") - data["OP"] = InputCurrent(N0=2000, Id_ref=10, Iq_ref=-10) + data["N0"] = 2000 + data["Id_ref"] = 10 + data["Iq_ref"] = -10 # Plot parameters data["freq_max"] = 2000 data["r_max"] = 78 @@ -72,12 +78,10 @@ def test_default_proj_Br_time_space(self, import_data): time = import_data["time"] angle = import_data["angle"] flux = import_data["flux"] - flux_FT = import_data["flux_FT"] - freqs = import_data["freqs"] - wavenumber = import_data["wavenumber"] freq_max = import_data["freq_max"] - r_max = import_data["r_max"] - OP = import_data["OP"] + N0 = import_data["N0"] + Id_ref = import_data["Id_ref"] + Iq_ref = import_data["Iq_ref"] time_arr = squeeze(time.get_data()) angle_arr = squeeze(angle.get_data()) @@ -88,7 +92,14 @@ def test_default_proj_Br_time_space(self, import_data): simu.mag = None simu.force = None simu.struct = None - simu.input = InputFlux(B_dict={"Br": flux}, time=time, angle=angle, OP=OP) + simu.input = InputFlux( + B_dict={"Br": flux}, + time=time, + angle=angle, + N0=N0, + Id_ref=Id_ref, + Iq_ref=Iq_ref, + ) out = Output(simu=simu) simu.run() @@ -214,7 +225,14 @@ def test_default_proj_Br_time_space(self, import_data): simu4.mag = None simu4.force = None simu4.struct = None - simu4.input = InputFlux(B_dict={"Br": flux}, time=time, angle=angle, OP=OP) + simu4.input = InputFlux( + B_dict={"Br": flux}, + time=time, + angle=angle, + N0=N0, + Id_ref=Id_ref, + Iq_ref=Iq_ref, + ) out4 = Output(simu=simu4) simu4.run() out4.post.legend_name = "Inverse FT" @@ -294,17 +312,23 @@ def test_default_proj_Br_cfft2(self, import_data): time = import_data["time"] angle = import_data["angle"] flux = import_data["flux"] - flux_FT = import_data["flux_FT"] - freqs = import_data["freqs"] - wavenumber = import_data["wavenumber"] freq_max = import_data["freq_max"] r_max = import_data["r_max"] - OP = import_data["OP"] + N0 = import_data["N0"] + Id_ref = import_data["Id_ref"] + Iq_ref = import_data["Iq_ref"] N_stem = 100 simu = Simu1(name="test_default_proj_Br_cfft2", machine=SCIM_006) - simu.input = InputFlux(B_dict={"Br": flux}, time=time, angle=angle, OP=OP) + simu.input = InputFlux( + B_dict={"Br": flux}, + time=time, + angle=angle, + N0=N0, + Id_ref=Id_ref, + Iq_ref=Iq_ref, + ) simu.mag = None simu.force = None simu.struct = None @@ -336,13 +360,22 @@ def test_default_proj_surf(self, import_data): wavenumber = import_data["wavenumber"] freq_max = import_data["freq_max"] r_max = import_data["r_max"] - OP = import_data["OP"] + N0 = import_data["N0"] + Id_ref = import_data["Id_ref"] + Iq_ref = import_data["Iq_ref"] simu = Simu1(name="test_default_proj_surf", machine=SCIM_006) simu.mag = None simu.force = None simu.struct = None - simu.input = InputFlux(B_dict={"Br": flux}, time=time, angle=angle, OP=OP) + simu.input = InputFlux( + B_dict={"Br": flux}, + time=time, + angle=angle, + N0=N0, + Id_ref=Id_ref, + Iq_ref=Iq_ref, + ) out = Output(simu=simu) simu.run() @@ -366,18 +399,24 @@ def test_default_proj_fft2(self, import_data): time = import_data["time"] angle = import_data["angle"] flux = import_data["flux"] - flux_FT = import_data["flux_FT"] - freqs = import_data["freqs"] - wavenumber = import_data["wavenumber"] freq_max = import_data["freq_max"] r_max = import_data["r_max"] - OP = import_data["OP"] + N0 = import_data["N0"] + Id_ref = import_data["Id_ref"] + Iq_ref = import_data["Iq_ref"] simu = Simu1(name="test_default_proj_fft2", machine=SCIM_006) simu.mag = None simu.force = None simu.struct = None - simu.input = InputFlux(B_dict={"Br": flux}, time=time, angle=angle, OP=OP) + simu.input = InputFlux( + B_dict={"Br": flux}, + time=time, + angle=angle, + N0=N0, + Id_ref=Id_ref, + Iq_ref=Iq_ref, + ) out = Output(simu=simu) simu.run() @@ -403,18 +442,22 @@ def test_default_proj_time_space(self, import_data): time = import_data["time"] angle = import_data["angle"] flux = import_data["flux"] - flux_FT = import_data["flux_FT"] - freqs = import_data["freqs"] - wavenumber = import_data["wavenumber"] - freq_max = import_data["freq_max"] - r_max = import_data["r_max"] - OP = import_data["OP"] + N0 = import_data["N0"] + Id_ref = import_data["Id_ref"] + Iq_ref = import_data["Iq_ref"] simu = Simu1(name="test_default_proj_time_space", machine=SCIM_006) simu.mag = None simu.force = None simu.struct = None - simu.input = InputFlux(B_dict={"Br": flux}, time=time, angle=angle, OP=OP) + simu.input = InputFlux( + B_dict={"Br": flux}, + time=time, + angle=angle, + N0=N0, + Id_ref=Id_ref, + Iq_ref=Iq_ref, + ) out = Output(simu=simu) simu.run() @@ -428,3 +471,15 @@ def test_default_proj_time_space(self, import_data): is_show_fig=False, **dict_3D, ) + + +if __name__ == "__main__": + + data = import_data_func() + test_plot_class = Test_plots() + + test_plot_class.test_default_proj_Br_time_space(data) + test_plot_class.test_default_proj_Br_cfft2(data) + test_plot_class.test_default_proj_surf(data) + test_plot_class.test_default_proj_fft2(data) + test_plot_class.test_default_proj_time_space(data) diff --git a/pyleecan/Methods/Simulation/Magnetics/comp_I_mag.py b/pyleecan/Methods/Simulation/Magnetics/comp_I_mag.py index 8ed6c4354..3cf72f2d9 100644 --- a/pyleecan/Methods/Simulation/Magnetics/comp_I_mag.py +++ b/pyleecan/Methods/Simulation/Magnetics/comp_I_mag.py @@ -54,9 +54,9 @@ def comp_I_mag( # Get current DataTime if I_data is None: if is_stator: - I_data = self.get_Is() + I_data = output.elec.get_Is() else: - I_data = self.Ir + I_data = output.elec.Ir if phase is None: # Take all phases that are in the I_data Data object From 431f8fa94a8a9ea5b771bf4ec1fb06bd6f1efb7b Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Thu, 7 Oct 2021 18:46:13 +0200 Subject: [PATCH 068/167] [CC] Skip EEC tests --- Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py | 1 + Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py | 1 + Tests/Validation/Electrical/test_EEC_LSRPM.py | 5 +++-- Tests/Validation/Electrical/test_EEC_PMSM.py | 1 + Tests/Validation/Electrical/test_EEC_SCIM.py | 1 + Tests/Validation/Electrical/test_skin_effect.py | 2 +- 6 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py index a4ce73c84..cf66daf65 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py @@ -24,6 +24,7 @@ @pytest.mark.EEC_PMSM @pytest.mark.IPMSM @pytest.mark.periodicity +@pytest.mark.skip(reason="Work in progress") def test_ELUT_PMSM(): """Validation of the PMSM Electrical Equivalent Circuit with the Prius machine Compute Torque from EEC results and compare with Yang et al, 2013 diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py b/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py index 415a63815..189d3dbca 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py @@ -33,6 +33,7 @@ @pytest.mark.SCIM @pytest.mark.Electrical +@pytest.mark.skip(reason="Work in progress") def test_EEC_ELUT_SCIM_001(): """Validation of the structural/acoustic module for default_machine machine Comparison with MANATEE V1 results""" diff --git a/Tests/Validation/Electrical/test_EEC_LSRPM.py b/Tests/Validation/Electrical/test_EEC_LSRPM.py index d4cceba93..0fa60d116 100644 --- a/Tests/Validation/Electrical/test_EEC_LSRPM.py +++ b/Tests/Validation/Electrical/test_EEC_LSRPM.py @@ -25,7 +25,8 @@ @pytest.mark.IPMSM @pytest.mark.periodicity @pytest.mark.SingleOP -def test_EEC_PMSM(): +@pytest.mark.skip(reason="Work in progress") +def test_EEC_LSRPM(): """Validation of LSRPM EEC from Sijie's PhD thesis""" LSRPM = load("LSRPM_001.json") @@ -82,4 +83,4 @@ def test_EEC_PMSM(): # To run it without pytest if __name__ == "__main__": - out, out2 = test_EEC_PMSM() + out, out2 = test_EEC_LSRPM() diff --git a/Tests/Validation/Electrical/test_EEC_PMSM.py b/Tests/Validation/Electrical/test_EEC_PMSM.py index 9a70b662e..573678a94 100644 --- a/Tests/Validation/Electrical/test_EEC_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_PMSM.py @@ -25,6 +25,7 @@ @pytest.mark.IPMSM @pytest.mark.periodicity @pytest.mark.SingleOP +@pytest.mark.skip(reason="Work in progress") def test_EEC_PMSM(): """Validation of the PMSM Electrical Equivalent Circuit with the Prius machine Compute Torque from EEC results and compare with Yang et al, 2013 diff --git a/Tests/Validation/Electrical/test_EEC_SCIM.py b/Tests/Validation/Electrical/test_EEC_SCIM.py index 29131ef52..00531377c 100644 --- a/Tests/Validation/Electrical/test_EEC_SCIM.py +++ b/Tests/Validation/Electrical/test_EEC_SCIM.py @@ -18,6 +18,7 @@ @pytest.mark.SCIM @pytest.mark.periodicity @pytest.mark.SingleOP +@pytest.mark.skip(reason="Work in progress") def test_EEC_SCIM(): """Validation of the SCIM Electrical Equivalent Circuit with the 3kW SCIM from 'Berechnung elektrischer Maschinen' (ISBN: 978-3-527-40525-1) diff --git a/Tests/Validation/Electrical/test_skin_effect.py b/Tests/Validation/Electrical/test_skin_effect.py index f434100df..963b39bbf 100644 --- a/Tests/Validation/Electrical/test_skin_effect.py +++ b/Tests/Validation/Electrical/test_skin_effect.py @@ -22,7 +22,7 @@ # @pytest.mark.IPMSM # @pytest.mark.periodicity # @pytest.mark.SingleOP -@pytest.mark.skip(reason="Not finished yet") +@pytest.mark.skip(reason="Work in progress") def test_skin_effect(): """Validation of the PMSM Electrical Equivalent Circuit with the Prius machine Compute Torque from EEC results and compare with Yang et al, 2013 From 723d48267044e93909005645fd752bdfffa2b8cc Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Thu, 7 Oct 2021 18:46:40 +0200 Subject: [PATCH 069/167] [CC] Clean import in test --- Tests/Validation/Force/test_AGSF_slotless.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Tests/Validation/Force/test_AGSF_slotless.py b/Tests/Validation/Force/test_AGSF_slotless.py index 21efdebf9..3d770f0ff 100644 --- a/Tests/Validation/Force/test_AGSF_slotless.py +++ b/Tests/Validation/Force/test_AGSF_slotless.py @@ -1,23 +1,15 @@ -# -*- coding: utf-8 -*- from pyleecan.Functions.load import load from pyleecan.definitions import DATA_DIR from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.InputCurrent import InputCurrent -from pyleecan.Classes.ImportGenVectLin import ImportGenVectLin -from pyleecan.Classes.ImportMatrixVal import ImportMatrixVal from pyleecan.Classes.MagFEMM import MagFEMM -from pyleecan.Classes.Output import Output from pyleecan.Classes.ForceMT import ForceMT from pyleecan.Functions.Plot import dict_2D from Tests import save_plot_path from os.path import join -from numpy import zeros, ones, pi, array -import matplotlib.pyplot as plt -import json -import numpy as np import pytest From 34ccf3bd43f6426d4a97c6ea58b4501083a569b9 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Thu, 7 Oct 2021 19:26:55 +0200 Subject: [PATCH 070/167] [CO] Permute phase and Time axes in current data objects [CC] Put stator/rotor currents calculation in comp_I_mag method --- pyleecan/Methods/Output/OutElec/get_I_fund.py | 32 +++-- .../Simulation/InputCurrent/gen_input.py | 24 ++-- .../Methods/Simulation/MagFEMM/solve_FEMM.py | 6 +- .../Simulation/Magnetics/comp_I_mag.py | 117 ++++++++++-------- pyleecan/Methods/Simulation/Magnetics/run.py | 31 +---- 5 files changed, 105 insertions(+), 105 deletions(-) diff --git a/pyleecan/Methods/Output/OutElec/get_I_fund.py b/pyleecan/Methods/Output/OutElec/get_I_fund.py index 17d3c7a3f..329316618 100644 --- a/pyleecan/Methods/Output/OutElec/get_I_fund.py +++ b/pyleecan/Methods/Output/OutElec/get_I_fund.py @@ -1,4 +1,4 @@ -from numpy import pi, array, transpose, where, isclose +from numpy import pi, array, where, isclose, zeros from SciDataTool import Data1D, DataTime, DataFreq @@ -29,21 +29,31 @@ def get_I_fund(self, Time=None): ) if self.Is is None: - # Generate current according to Id/Iq - Isdq = array([self.Id_ref, self.Iq_ref]) + if ( + self.Id_ref is not None + and self.Iq_ref is not None + and self.Id_ref != 0 + and self.Iq_ref != 0 + ): + # Generate current according to Id/Iq + Isdq = array([self.Id_ref, self.Iq_ref]) - # Get rotation direction of the fundamental magnetic field created by the winding - rot_dir = self.parent.get_rot_dir() + # Get rotation direction of the fundamental magnetic field created by the winding + rot_dir = self.parent.get_rot_dir() - # Get stator current function of time - Is = dq2n(Isdq, 2 * pi * felec * time, n=qs, rot_dir=rot_dir, is_n_rms=False) + # Get stator current function of time + Is = dq2n( + Isdq, 2 * pi * felec * time, n=qs, rot_dir=rot_dir, is_n_rms=False + ) + else: + Is = zeros((time.size, qs)) I_fund = DataTime( name="Stator current", unit="A", symbol="I_s", - axes=[Phase, Time], - values=transpose(Is), + axes=[Time, Phase], + values=Is, ) else: @@ -51,7 +61,7 @@ def get_I_fund(self, Time=None): Is_val = result["I_s"] freqs = result["freqs"] ifund = where(isclose(freqs, felec)) - Is_fund = Is_val[:, ifund] + Is_fund = Is_val[ifund, :] Freq = Data1D( name="freqs", @@ -63,7 +73,7 @@ def get_I_fund(self, Time=None): name="Stator current", unit="A", symbol="I_s", - axes=[Phase, Freq], + axes=[Freq, Phase], values=Is_fund, ) diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index 4bfaf8e65..7bf3a86af 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -28,11 +28,18 @@ def gen_input(self): else: raise InputError("InputCurrent object should be inside a Simulation object") + # Get output + if simu.parent is not None: + output = simu.parent + else: + raise InputError("Simulation object should be inside an Output object") + # Call InputVoltage.gen_input() InputVoltage.gen_input(self) - # Get electrical output - outelec = simu.parent.elec + # Get outputs + outgeo = output.geo + outelec = output.elec # Number of winding phases for stator/rotor if simu.machine is not None: @@ -67,18 +74,12 @@ def gen_input(self): + " returned" ) # Creating the data object - Phase = Data1D( - name="phase", - unit="", - values=gen_name(qs), - is_components=True, - ) outelec.Is = DataTime( name="Stator current", unit="A", symbol="I_s", - axes=[Phase, Time], - values=transpose(Is), + axes=[Time, outgeo.axes_dict["phase"]], + values=Is, ) # Compute corresponding Id/Iq reference Idq = n2dq( @@ -118,6 +119,3 @@ def gen_input(self): axes=[Phase, Time], values=transpose(Ir), ) - - # Save the Output in the correct place - simu.parent.elec = outelec diff --git a/pyleecan/Methods/Simulation/MagFEMM/solve_FEMM.py b/pyleecan/Methods/Simulation/MagFEMM/solve_FEMM.py index 2d89e757b..686ef41c0 100644 --- a/pyleecan/Methods/Simulation/MagFEMM/solve_FEMM.py +++ b/pyleecan/Methods/Simulation/MagFEMM/solve_FEMM.py @@ -1,5 +1,5 @@ from os import remove -from os.path import basename, splitext, isfile +from os.path import splitext, isfile from numpy import zeros, pi, roll, cos, sin @@ -105,7 +105,7 @@ def solve_FEMM( femm.opendocument(filename) else: # FEMM instance and file is already open, get filename from output - filename = basename(self.get_path_save_fem(output)) + filename = self.get_path_save_fem(output) if is_sliding_band and self.is_set_previous: # Check result .ans file existence and delete it if it exists @@ -113,7 +113,7 @@ def solve_FEMM( (splitext(filename)[0] + ".ans").replace("\\", "/").replace("//", "/") ) if isfile(ans_file): - logger.info("Delete existing result .ans file at: " + ans_file) + logger.debug("Delete existing result .ans file at: " + ans_file) remove(ans_file) # Take last time step at Nt by default diff --git a/pyleecan/Methods/Simulation/Magnetics/comp_I_mag.py b/pyleecan/Methods/Simulation/Magnetics/comp_I_mag.py index 3cf72f2d9..4aba8a1a8 100644 --- a/pyleecan/Methods/Simulation/Magnetics/comp_I_mag.py +++ b/pyleecan/Methods/Simulation/Magnetics/comp_I_mag.py @@ -1,84 +1,99 @@ -from ....Classes.WindingSC import WindingSC -from numpy import array +from numpy import transpose -def comp_I_mag( - self, output, Time, is_stator, phase=None, I_data=None, is_periodicity_t=True -): - """Compute the current on the given lamination and time vector to use it in Magnetics model - Phase currents are divided by the number of parallel circuits per pole - and per phase to account for actual current in slot conductors +def comp_I_mag(self, output, Time): + """Compute the currents on both stator and rotor laminations for given Magnetics time axis + Phase currents are divided by the number of parallel circuits per pole and per phase to account + for actual current in slot conductors Parameters ---------- self : Magnetics an Magnetics object - Time : Data1D + output: Output + An Output object + Time : Data Time vector on which to interpolate currents stored in OutElec - is_stator: bool - True if lamination is stator - per_a: int - (Anti-)periodicity factor Returns ------- - I: ndarray - Current matrix accounting for periodicities [q_pera,len(time)] + Is_val: ndarray + Stator current matrix accounting for magnetic periodicities [qs_pera,len(time)] + Ir_val: ndarray + Rotor current matrix accounting for magnetic periodicities [qr_pera,len(time)] """ - _, is_antiper_t = Time.get_periodicity() - # Number of time steps - time = Time.get_values( - is_oneperiod=is_periodicity_t, - is_antiperiod=is_antiper_t and is_periodicity_t, - ) + logger = self.get_logger() - # Get lamination - if is_stator: - lam = output.simu.machine.stator - else: - lam = output.simu.machine.rotor + # Extract time vector + time = Time.get_values(is_smallestperiod=True) + + # Get laminations + stator = output.simu.machine.stator + rotor = output.simu.machine.rotor + # Get stator current from electrical output if ( - hasattr(lam, "winding") - and lam.winding is not None - and lam.winding.conductor is not None + self.is_mmfs + and hasattr(stator, "winding") + and stator.winding is not None + and stator.winding.conductor is not None ): + # Get Data object on magnetic Time axis + Is = output.elec.get_Is(Time=Time, is_current_harm=self.is_current_harm) # Get the number of parallel circuit per phase of winding - if hasattr(lam.winding, "Npcp") and lam.winding.Npcp is not None: - Npcp = lam.winding.Npcp + if hasattr(stator.winding, "Npcp") and stator.winding.Npcp is not None: + Npcp = stator.winding.Npcp else: + logger.warning("Enforcing Npcp=1 at stator side") Npcp = 1 - # Get current DataTime - if I_data is None: - if is_stator: - I_data = output.elec.get_Is() - else: - I_data = output.elec.Ir + # Interpolate stator currents on input time vector + Is_val = transpose( + Is.get_along( + "time=axis_data", + "phase", + axis_data={"time": time}, + is_squeeze=False, + )[Is.symbol] + / Npcp + ) + + else: + Is_val = None + + # Get rotor current from electrical output + if ( + self.is_mmfr + and output.elec.Ir is not None + and hasattr(rotor, "winding") + and rotor.winding is not None + and rotor.winding.conductor is not None + ): - if phase is None: - # Take all phases that are in the I_data Data object - str_phase = "phase" + # Get Data object + Ir = output.elec.Ir + + # Get the number of parallel circuit per phase of winding + if hasattr(rotor.winding, "Npcp") and rotor.winding.Npcp is not None: + Npcp = rotor.winding.Npcp else: - str_phase = "phase" + str(phase) + logger.warning("Enforcing Npcp=1 at rotor side") + Npcp = 1 # Interpolate stator currents on input time vector - I = ( - I_data.get_along( + Ir_val = transpose( + Ir.get_along( "time=axis_data", - str_phase, + "phase", axis_data={"time": time}, - )[I_data.symbol] + is_squeeze=False, + )[Ir.symbol] / Npcp ) - # Add time dimension if Is is calculated only for one time step - if len(I.shape) == 1: - I = I[:, None] - else: - I = None + Ir_val = None - return I + return Is_val, Ir_val diff --git a/pyleecan/Methods/Simulation/Magnetics/run.py b/pyleecan/Methods/Simulation/Magnetics/run.py index 3ef7118d7..863dd924c 100644 --- a/pyleecan/Methods/Simulation/Magnetics/run.py +++ b/pyleecan/Methods/Simulation/Magnetics/run.py @@ -23,36 +23,13 @@ def run(self): # and returns additional axes in axes_dict axes_dict = self.comp_axes(output) - if self.is_mmfs: - Is = output.elec.get_Is( - Time=axes_dict["time"], is_current_harm=self.is_current_harm - ) - Is_val = self.comp_I_mag( - output=output, - Time=axes_dict["time"], - is_stator=True, - I_data=Is, - is_periodicity_t=self.is_periodicity_t, - ) - else: - Is_val = None - # Get rotor current from elec out - if self.is_mmfr: - # Ir = output.elec.get_Ir(Time=axes_dict["time"]) TODO - Ir_val = self.comp_I_mag( - output=output, - Time=axes_dict["time"], - is_stator=False, - is_periodicity_t=self.is_periodicity_t, - ) - else: - Ir_val = None - - # Calculate airgap flux - # Loop over skew axis + # Get z axis Slice_axis = axes_dict["z"] unique_indices = Slice_axis.unique_indices + # Get stator and rotor currents if requested + Is_val, Ir_val = self.comp_I_mag(output, Time=axes_dict["time"]) + # First iteration to check dimensions # Assign stator and rotor angle shifts self.angle_stator_shift = float(slice_model.angle_stator[unique_indices[0]]) From 86333ac9fce23cfd77f711b0c7887aa852f9dc62 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Thu, 7 Oct 2021 19:30:00 +0200 Subject: [PATCH 071/167] [CC] Check if machine is periodic before warning that periodicty has been removed --- pyleecan/Methods/Simulation/Force/comp_axes.py | 15 ++++++++++----- .../Methods/Simulation/Magnetics/comp_axes.py | 17 +++++++++++------ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/pyleecan/Methods/Simulation/Force/comp_axes.py b/pyleecan/Methods/Simulation/Force/comp_axes.py index e5c5653a9..3ddeb228f 100644 --- a/pyleecan/Methods/Simulation/Force/comp_axes.py +++ b/pyleecan/Methods/Simulation/Force/comp_axes.py @@ -14,11 +14,14 @@ def comp_axes(self, output): Dict containing Time and Angle axes including (anti-)periodicties used in any Force module """ + # Get geometry output + outgeo = output.geo + # Get axis dict from OutMag axes_dict_mag = output.mag.axes_dict # Add periodicities to time and angle axes - axes_dict = self.parent.input.comp_axes( + axes_dict = output.simu.input.comp_axes( axes_list=["time", "angle"], axes_dict=axes_dict_mag, is_periodicity_a=self.is_periodicity_a, @@ -34,7 +37,8 @@ def comp_axes(self, output): # Check Time periodicities regarding Force model input per_t0, is_antiper_t0 = axes_dict["time"].get_periodicity() is_periodicity_t0 = per_t0 > 1 or is_antiper_t0 - if is_periodicity_t0 != self.is_periodicity_t: + is_periodic_machine_t = outgeo.per_t_S > 1 or outgeo.is_antiper_t_S + if is_periodicity_t0 != self.is_periodicity_t and is_periodic_machine_t: # Remove time periodicity in Force model self.is_periodicity_t = False Nt_tot = axes_dict["time"].get_length(is_oneperiod=False) @@ -42,7 +46,7 @@ def comp_axes(self, output): "In Force model, Nt_tot=" + str(Nt_tot) + " is not divisible by the machine time periodicity (" - + str(output.geo.per_t_S) + + str(outgeo.per_t_S) + "). Time periodicity removed" ) @@ -55,7 +59,8 @@ def comp_axes(self, output): # Check Angle periodicities regarding Force model input per_a0, is_antiper_a0 = axes_dict["angle"].get_periodicity() is_periodicity_a0 = per_a0 > 1 or is_antiper_a0 - if is_periodicity_a0 != self.is_periodicity_a: + is_periodic_machine_a = outgeo.per_a > 1 or outgeo.is_antiper_a + if is_periodicity_a0 != self.is_periodicity_a and is_periodic_machine_a: # Remove time periodicity in Magnetic model self.is_periodicity_a = False Na_tot = axes_dict["angle"].get_length(is_oneperiod=False) @@ -63,7 +68,7 @@ def comp_axes(self, output): "In Force model, Na_tot=" + str(Na_tot) + " is not divisible by the machine angular periodicity (" - + str(output.geo.per_a) + + str(outgeo.per_a) + "). Angular periodicity removed" ) diff --git a/pyleecan/Methods/Simulation/Magnetics/comp_axes.py b/pyleecan/Methods/Simulation/Magnetics/comp_axes.py index 68f25c8ee..33bab412a 100644 --- a/pyleecan/Methods/Simulation/Magnetics/comp_axes.py +++ b/pyleecan/Methods/Simulation/Magnetics/comp_axes.py @@ -15,11 +15,14 @@ def comp_axes(self, output): """ + # Get geometry output + outgeo = output.geo + # Get axis dict from OutGeo - axes_dict_geo = output.geo.axes_dict + axes_dict_geo = outgeo.axes_dict # Add periodicities to time and angle axes - axes_dict = self.parent.input.comp_axes( + axes_dict = output.simu.input.comp_axes( axes_list=["time", "angle"], axes_dict=axes_dict_geo, is_periodicity_a=self.is_periodicity_a, @@ -29,7 +32,8 @@ def comp_axes(self, output): # Check Time periodicities regarding Magnetics model input per_t0, is_antiper_t0 = axes_dict["time"].get_periodicity() is_periodicity_t0 = per_t0 > 1 or is_antiper_t0 - if is_periodicity_t0 != self.is_periodicity_t: + is_periodic_machine_t = outgeo.per_t_S > 1 or outgeo.is_antiper_t_S + if is_periodicity_t0 != self.is_periodicity_t and is_periodic_machine_t: # Remove time periodicity in Magnetic model self.is_periodicity_t = False Nt_tot = axes_dict["time"].get_length(is_oneperiod=False) @@ -37,14 +41,15 @@ def comp_axes(self, output): "In Magnetic model, Nt_tot=" + str(Nt_tot) + " is not divisible by the machine time periodicity (" - + str(output.geo.per_t_S) + + str(outgeo.per_t_S) + "). Time periodicity removed" ) # Check Angle periodicities regarding Magnetics model input per_a0, is_antiper_a0 = axes_dict["angle"].get_periodicity() is_periodicity_a0 = per_a0 > 1 or is_antiper_a0 - if is_periodicity_a0 != self.is_periodicity_a: + is_periodic_machine_a = outgeo.per_a > 1 or outgeo.is_antiper_a + if is_periodicity_a0 != self.is_periodicity_a and is_periodic_machine_a: # Remove time periodicity in Magnetic model self.is_periodicity_a = False Na_tot = axes_dict["angle"].get_length(is_oneperiod=False) @@ -52,7 +57,7 @@ def comp_axes(self, output): "In Magnetic model, Na_tot=" + str(Na_tot) + " is not divisible by the machine angular periodicity (" - + str(output.geo.per_a) + + str(outgeo.per_a) + "). Angular periodicity removed" ) From d0b8cc4295bd5998d12f2cfbb6a8fbcc0c12a387 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Fri, 8 Oct 2021 09:33:26 +0200 Subject: [PATCH 072/167] [BC] solve bug due to phase/time inversion in current object --- pyleecan/Methods/Simulation/InputCurrent/gen_input.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index 7bf3a86af..bc0a83b6a 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -1,4 +1,4 @@ -from numpy import ndarray, pi, mean, transpose, zeros +from numpy import ndarray, pi, mean, zeros from ....Classes.Simulation import Simulation @@ -83,7 +83,7 @@ def gen_input(self): ) # Compute corresponding Id/Iq reference Idq = n2dq( - transpose(outelec.Is.values), + outelec.Is.values, 2 * pi * outelec.felec * Time.get_values(is_oneperiod=False), n=qs, is_dq_rms=True, @@ -116,6 +116,6 @@ def gen_input(self): name="Rotor current", unit="A", symbol="Ir", - axes=[Phase, Time], - values=transpose(Ir), + axes=[Time, Phase], + values=Ir, ) From 6f91b007298a8e4fe304afbf243ef9ccd0bfa9cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl?= Date: Fri, 8 Oct 2021 09:57:31 +0200 Subject: [PATCH 073/167] [BC] axes order --- pyleecan/Methods/Output/OutElec/get_I_fund.py | 4 ++-- pyleecan/Methods/Output/OutElec/get_I_harm.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyleecan/Methods/Output/OutElec/get_I_fund.py b/pyleecan/Methods/Output/OutElec/get_I_fund.py index 17d3c7a3f..eaa61af21 100644 --- a/pyleecan/Methods/Output/OutElec/get_I_fund.py +++ b/pyleecan/Methods/Output/OutElec/get_I_fund.py @@ -51,7 +51,7 @@ def get_I_fund(self, Time=None): Is_val = result["I_s"] freqs = result["freqs"] ifund = where(isclose(freqs, felec)) - Is_fund = Is_val[:, ifund] + Is_fund = Is_val[ifund] Freq = Data1D( name="freqs", @@ -63,7 +63,7 @@ def get_I_fund(self, Time=None): name="Stator current", unit="A", symbol="I_s", - axes=[Phase, Freq], + axes=[Freq, Phase], values=Is_fund, ) diff --git a/pyleecan/Methods/Output/OutElec/get_I_harm.py b/pyleecan/Methods/Output/OutElec/get_I_harm.py index e8b4db5a4..7115a1548 100644 --- a/pyleecan/Methods/Output/OutElec/get_I_harm.py +++ b/pyleecan/Methods/Output/OutElec/get_I_harm.py @@ -30,7 +30,7 @@ def get_I_harm(self): ) I_harm = I_fund_freq.copy() - I_harm.axes = [I_fund_freq.axes[0], Freqs] - I_harm.values = results["I_s"][:, ifund] + I_harm.axes = [Freqs, I_fund_freq.axes[0]] + I_harm.values = results["I_s"][ifund] return I_harm From d8eece8b60dde6d63c8d5f37d00333b9dbaf67a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl?= Date: Fri, 8 Oct 2021 10:08:49 +0200 Subject: [PATCH 074/167] [BC] axes order --- pyleecan/Methods/Output/OutElec/get_I_harm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyleecan/Methods/Output/OutElec/get_I_harm.py b/pyleecan/Methods/Output/OutElec/get_I_harm.py index 7115a1548..d742d3fcc 100644 --- a/pyleecan/Methods/Output/OutElec/get_I_harm.py +++ b/pyleecan/Methods/Output/OutElec/get_I_harm.py @@ -30,7 +30,7 @@ def get_I_harm(self): ) I_harm = I_fund_freq.copy() - I_harm.axes = [Freqs, I_fund_freq.axes[0]] + I_harm.axes = [Freqs, I_fund_freq.axes[1]] I_harm.values = results["I_s"][ifund] return I_harm From 48c54adeb0776fb4cec7e4f1e639545037ce262e Mon Sep 17 00:00:00 2001 From: Martin Glesser Date: Fri, 8 Oct 2021 16:44:52 +0200 Subject: [PATCH 075/167] [CC] Argument qs added to ImportGenPWM class + better docstring for comp_PWM function --- Tests/Methods/Import/test_ImportGenPWM.py | 2 + pyleecan/Classes/Class_Dict.json | 9 ++ pyleecan/Classes/ImportGenPWM.py | 30 ++++++ pyleecan/Functions/Electrical/comp_PWM.py | 96 +++++++++++-------- .../ClassesRef/Import/ImportGenPWM.csv | 1 + .../Methods/Import/ImportGenPWM/get_data.py | 19 ++-- 6 files changed, 111 insertions(+), 46 deletions(-) diff --git a/Tests/Methods/Import/test_ImportGenPWM.py b/Tests/Methods/Import/test_ImportGenPWM.py index 6b4ad453c..6a1d7bbc8 100644 --- a/Tests/Methods/Import/test_ImportGenPWM.py +++ b/Tests/Methods/Import/test_ImportGenPWM.py @@ -34,6 +34,7 @@ def testSPWM(): U0=0.70, type_carrier=hh, var_amp=20, + qs=3, ) # Generate the signal time = linspace(start=0, stop=2, num=2 * 96000, endpoint=True) @@ -80,6 +81,7 @@ def testDPWM(): Vdc1=2, U0=0.70, type_carrier=0, + qs=3, ) # Generate the signal time = linspace(start=0, stop=2, num=2 * 96000, endpoint=True) diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 506d4e6e4..ae53a22ff 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -3549,6 +3549,15 @@ "type": "int", "unit": "%", "value": 20 + }, + { + "desc": "number of phase", + "max": "", + "min": "", + "name": "qs", + "type": "int", + "unit": "", + "value": 3 } ] }, diff --git a/pyleecan/Classes/ImportGenPWM.py b/pyleecan/Classes/ImportGenPWM.py index dc0489e73..21616fb09 100644 --- a/pyleecan/Classes/ImportGenPWM.py +++ b/pyleecan/Classes/ImportGenPWM.py @@ -61,6 +61,7 @@ def __init__( U0=1, type_carrier=0, var_amp=20, + qs=3, is_transpose=False, init_dict=None, init_str=None, @@ -106,6 +107,8 @@ def __init__( type_carrier = init_dict["type_carrier"] if "var_amp" in list(init_dict.keys()): var_amp = init_dict["var_amp"] + if "qs" in list(init_dict.keys()): + qs = init_dict["qs"] if "is_transpose" in list(init_dict.keys()): is_transpose = init_dict["is_transpose"] # Set the properties (value check and convertion are done in setter) @@ -122,6 +125,7 @@ def __init__( self.U0 = U0 self.type_carrier = type_carrier self.var_amp = var_amp + self.qs = qs # Call ImportMatrix init super(ImportGenPWM, self).__init__(is_transpose=is_transpose) # The class is frozen (in ImportMatrix init), for now it's impossible to @@ -146,6 +150,7 @@ def __str__(self): ImportGenPWM_str += "U0 = " + str(self.U0) + linesep ImportGenPWM_str += "type_carrier = " + str(self.type_carrier) + linesep ImportGenPWM_str += "var_amp = " + str(self.var_amp) + linesep + ImportGenPWM_str += "qs = " + str(self.qs) + linesep return ImportGenPWM_str def __eq__(self, other): @@ -183,6 +188,8 @@ def __eq__(self, other): return False if other.var_amp != self.var_amp: return False + if other.qs != self.qs: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -222,6 +229,8 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".type_carrier") if other._var_amp != self._var_amp: diff_list.append(name + ".var_amp") + if other._qs != self._qs: + diff_list.append(name + ".qs") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -246,6 +255,7 @@ def __sizeof__(self): S += getsizeof(self.U0) S += getsizeof(self.type_carrier) S += getsizeof(self.var_amp) + S += getsizeof(self.qs) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -278,6 +288,7 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): ImportGenPWM_dict["U0"] = self.U0 ImportGenPWM_dict["type_carrier"] = self.type_carrier ImportGenPWM_dict["var_amp"] = self.var_amp + ImportGenPWM_dict["qs"] = self.qs # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name ImportGenPWM_dict["__class__"] = "ImportGenPWM" @@ -299,6 +310,7 @@ def _set_None(self): self.U0 = None self.type_carrier = None self.var_amp = None + self.qs = None # Set to None the properties inherited from ImportMatrix super(ImportGenPWM, self)._set_None() @@ -540,3 +552,21 @@ def _set_var_amp(self, value): :Type: int """, ) + + def _get_qs(self): + """getter of qs""" + return self._qs + + def _set_qs(self, value): + """setter of qs""" + check_var("qs", value, "int") + self._qs = value + + qs = property( + fget=_get_qs, + fset=_set_qs, + doc=u"""number of phase + + :Type: int + """, + ) diff --git a/pyleecan/Functions/Electrical/comp_PWM.py b/pyleecan/Functions/Electrical/comp_PWM.py index 611e314a0..26e62f67b 100644 --- a/pyleecan/Functions/Electrical/comp_PWM.py +++ b/pyleecan/Functions/Electrical/comp_PWM.py @@ -7,15 +7,14 @@ def comp_volt_PWM_NUM( Tpwmu, freq0, fmode, - fswimode, fswi, qs, Vdc1, U0, rot_dir, type_DPWM: int, + fswimode=0, PF_angle=0, - is_plot: bool = False, is_sin=True, fswi_max=0, freq0_max=0, @@ -25,21 +24,29 @@ def comp_volt_PWM_NUM( """ Generalized DPWM using numerical method according to 'Impact of Modulation Schemes on DC-Link Capacitor of VSI in HEV Applications' - Tpwmu : vector - TIME VECTOR + + Parameters + ---------- + Tpwmu : ndarray + time vector freq0: float fundamental frequency + fmode: int, optional + type of control + 0: Fixed speed + 1: Variable speed fswi: float switching frequency qs: int number of phases Vdc1: float - bus voltage + bus voltage [VDC] U0: float - Phase Voltage + Phase Voltage [Vrms] rot_dir: int rotation direction type_DPWM : int + type of modulation waveform 0: GDPWM 1: DPWMMIN 2: DPWMMAX @@ -49,32 +56,43 @@ def comp_volt_PWM_NUM( 6: DPWM3 7: SVPWM 8: SPWM - type_carrier: int - type of carrier waveform - PF_angle: float - power factor angle - is_plot: bool - to plot the pwm - fswi_max: int - Maximal switching frequency - freq0_max: int - maximal fundamental frequency - fmode: int - 0: Fixed speed - 1: Varaible speed - fswimode: int - 0: Fixed fswi + fswimode: int, optional + mode of the switching frequency evolution + 0: Fixed fswi [default] 1: Variable fswi 2: Random fswi 3: Symmetrical random fswi 4: Random amplitude carrier wave - + PF_angle: float, optional + power factor angle, default to 0 + fswi_max: int, optional + Maximal switching frequency, default to 0 + freq0_max: int, optional + maximal fundamental frequency, default to 0 + type_carrier: int + type of carrier waveform + 1: forward toothsaw carrier + 2: backwards toothsaw carrier + 3: toothsaw carrier + else: Symmetrical toothsaw carrier [default] var_amp: int - precentage of variation of the carrier amplitude + precentage of variation of the carrier amplitude, default to 0 + + Returns + ------- + v_pwm : ndarray + n-phase PWM voltage waveform + Vas : ndarray + modulation waveform + M_I : float + modulation index + carrier : ndarray + carrier waveform + """ Npsim = len(Tpwmu) - triangle = np.ones(len(Tpwmu)) + carrier = np.ones(len(Tpwmu)) if fmode == 0: # Fixed speed: ws = 2 * np.pi * freq0 elif fmode == 1: # Variable speed: @@ -84,14 +102,14 @@ def comp_volt_PWM_NUM( ) ws = np.pi * freq0_array else: - print("ERROR:only SPWM supports the varaible fundamental frequency") + print("ERROR:only SPWM supports the variable fundamental frequency") else: pass if fswimode == 0: # Fixed fswi: if type_DPWM == 8: - triangle = Vdc1 / 2 * comp_carrier(Tpwmu, fswi, type_carrier) + carrier = Vdc1 / 2 * comp_carrier(Tpwmu, fswi, type_carrier) else: Th = 1 / fswi elif fswimode == 1: # Variable fswi (ramp): @@ -100,9 +118,9 @@ def comp_volt_PWM_NUM( np.pi * (fswi_max - fswi) / Tpwmu[-1] * Tpwmu ** 2 + 2 * np.pi * fswi * Tpwmu ) - triangle = Vdc1 / 2 * signal.sawtooth(wswiT, 0.5) + carrier = Vdc1 / 2 * signal.sawtooth(wswiT, 0.5) else: - print("ERROR:only SPWM supports the varaible switching frequency") + print("ERROR:only SPWM supports the variable switching frequency") elif fswimode == 2 or fswimode == 3: # Random fswi & Symmetrical random fswi t1 = round(Tpwmu[-1] * 5000000) if fswimode == 3: @@ -141,13 +159,13 @@ def comp_volt_PWM_NUM( Tpwmu_10 = np.linspace( 0, round(Tpwmu[-1]), round(Tpwmu[-1]) * 5000000, endpoint=True ) - triangle = integrate.cumtrapz(fswi, Tpwmu_10, initial=0) - Aml_tri = max(triangle) - triangle = triangle / Aml_tri * Vdc1 - Vdc1 / 2 * np.ones(np.size(Tpwmu_10)) - triangle = signal.resample(triangle, len(Tpwmu)) + carrier = integrate.cumtrapz(fswi, Tpwmu_10, initial=0) + Aml_tri = max(carrier) + carrier = carrier / Aml_tri * Vdc1 - Vdc1 / 2 * np.ones(np.size(Tpwmu_10)) + carrier = signal.resample(carrier, len(Tpwmu)) elif fswimode == 4: # Random amplitude carrier wave if type_DPWM == 8: - triangle = Vdc1 / 2 * (comp_carrier(Tpwmu, fswi, type_carrier)) + carrier = Vdc1 / 2 * (comp_carrier(Tpwmu, fswi, type_carrier)) num_slice = int(fswi * Tpwmu[-1]) delta_amp = np.random.randint( -var_amp, high=var_amp + 1, size=int(num_slice), dtype=int @@ -170,9 +188,9 @@ def comp_volt_PWM_NUM( amp = np.concatenate((amp, amp[-1] * np.ones(len(Tpwmu) - len(amp)))) else: amp = amp[: len(Tpwmu)] - triangle = triangle * amp + carrier = carrier * amp else: - print("ERROR:only SPWM supports the varaible switching frequency") + print("ERROR:only SPWM supports the variable switching frequency") else: pass @@ -311,9 +329,9 @@ def comp_volt_PWM_NUM( if type_DPWM == 8: v_pwm = np.ones((qs, Npsim)) - v_pwm[0] = np.where(Vas < triangle, -1, 1) - v_pwm[1] = np.where(Vbs < triangle, -1, 1) - v_pwm[2] = np.where(Vcs < triangle, -1, 1) + v_pwm[0] = np.where(Vas < carrier, -1, 1) + v_pwm[1] = np.where(Vbs < carrier, -1, 1) + v_pwm[2] = np.where(Vcs < carrier, -1, 1) else: @@ -329,7 +347,7 @@ def comp_volt_PWM_NUM( v_pwm[2, Tpwmu < (T3 + n * Th)] = -Vdc1 / 2 v_pwm[2, Tpwmu > ((n + 1) * Th - T3)] = -Vdc1 / 2 - return v_pwm, Vas, M_I, triangle + return v_pwm, Vas, M_I, carrier def comp_carrier(time, fswi, type_carrier): diff --git a/pyleecan/Generator/ClassesRef/Import/ImportGenPWM.csv b/pyleecan/Generator/ClassesRef/Import/ImportGenPWM.csv index bf06f3bb5..b08ed48a1 100644 --- a/pyleecan/Generator/ClassesRef/Import/ImportGenPWM.csv +++ b/pyleecan/Generator/ClassesRef/Import/ImportGenPWM.csv @@ -12,3 +12,4 @@ Vdc1,V,DC BUS voltage,0,float,2,,,,,,,,, U0,V,reference voltage,0,float,1,,,,,,,,, type_carrier,,1: forward toothsaw carrier 2: backwards toothsaw carrier 3: toothsaw carrier else: symetrical toothsaw carrier,0,int,0,,,,,,,,, var_amp,%,percentage of variation of carrier amplitude,0,int,20,,,,,,,,, +qs,,number of phase,0,int,3,,,,,,,,, diff --git a/pyleecan/Methods/Import/ImportGenPWM/get_data.py b/pyleecan/Methods/Import/ImportGenPWM/get_data.py index 0abaa6b12..861f42ed2 100644 --- a/pyleecan/Methods/Import/ImportGenPWM/get_data.py +++ b/pyleecan/Methods/Import/ImportGenPWM/get_data.py @@ -20,13 +20,19 @@ def get_data(self): Returns ------- - matrix: ndarray - The generated PWM matrix + vpwm : ndarray + n-phase PWM voltage waveform + Vas : ndarray + modulation waveform + MI : float + modulation index + carrier : ndarray + carrier waveform """ # Tpwmu=np.arange(fs*duration)/fs, Tpwmu = np.linspace(0, self.duration, self.fs * self.duration, endpoint=True) - v_pwm, Vas, MI, triangle = comp_volt_PWM_NUM( + v_pwm, Vas, MI, carrier = comp_volt_PWM_NUM( Tpwmu=Tpwmu, freq0=self.f, freq0_max=self.fmax, @@ -34,14 +40,13 @@ def get_data(self): fswimode=self.fswimode, fswi=self.fswi, fswi_max=self.fswi_max, - qs=3, + qs=self.qs, Vdc1=self.Vdc1, U0=self.U0, type_carrier=self.type_carrier, rot_dir=-1, type_DPWM=self.typePWM, PF_angle=0, - is_plot=False, var_amp=self.var_amp, ) @@ -49,5 +54,5 @@ def get_data(self): PWM1 = np.where(v_pwm[0] < ref, -1, 1) # .astype(np.float32) PWM2 = np.where(v_pwm[1] < ref, -1, 1) # .astype(np.float32) PWM3 = np.where(v_pwm[2] < ref, -1, 1) # .astype(np.float32) - Triphase = np.column_stack([PWM1, PWM2, PWM3]) - return Triphase, Vas, MI, triangle + Vpwm = np.column_stack([PWM1, PWM2, PWM3]) + return Vpwm, Vas, MI, carrier From 025f9b718a14317d012d23718ad95059155ed924 Mon Sep 17 00:00:00 2001 From: Martin Glesser Date: Fri, 8 Oct 2021 16:59:35 +0200 Subject: [PATCH 076/167] [CC] get_data method from ImportGenPWM returns time vector --- Tests/Methods/Import/test_ImportGenPWM.py | 18 ++++++------------ .../Methods/Import/ImportGenPWM/get_data.py | 5 +++-- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/Tests/Methods/Import/test_ImportGenPWM.py b/Tests/Methods/Import/test_ImportGenPWM.py index 6a1d7bbc8..cd1bf975a 100644 --- a/Tests/Methods/Import/test_ImportGenPWM.py +++ b/Tests/Methods/Import/test_ImportGenPWM.py @@ -37,16 +37,13 @@ def testSPWM(): qs=3, ) # Generate the signal - time = linspace(start=0, stop=2, num=2 * 96000, endpoint=True) - Triphase = test_obj.get_data()[0] - Vas = test_obj.get_data()[1] - Triangle = test_obj.get_data()[3] + Uabc, Vas, _, carrier, time = test_obj.get_data() # Plot/save the result plt.close("all") - plt.plot(time, Triphase[:, 1]) + plt.plot(time, Uabc[:, 1]) plt.plot(time, Vas) - plt.plot(time, Triangle) + plt.plot(time, carrier) fig = plt.gcf() fig.savefig( join( @@ -84,16 +81,13 @@ def testDPWM(): qs=3, ) # Generate the signal - time = linspace(start=0, stop=2, num=2 * 96000, endpoint=True) - Triphase = test_obj.get_data()[0] - Vas = test_obj.get_data()[1] - Triangle = test_obj.get_data()[3] + Uabc, Vas, _, carrier, time = test_obj.get_data() # Plot/save the result plt.close("all") - plt.plot(time, Triphase[:, 1]) + plt.plot(time, Uabc[:, 1]) plt.plot(time, Vas) - plt.plot(time, Triangle) + plt.plot(time, carrier) fig = plt.gcf() fig.savefig(join(save_path, "test_ImportGenPWM_" + str(ii) + ".png")) diff --git a/pyleecan/Methods/Import/ImportGenPWM/get_data.py b/pyleecan/Methods/Import/ImportGenPWM/get_data.py index 861f42ed2..8eca066b0 100644 --- a/pyleecan/Methods/Import/ImportGenPWM/get_data.py +++ b/pyleecan/Methods/Import/ImportGenPWM/get_data.py @@ -28,7 +28,8 @@ def get_data(self): modulation index carrier : ndarray carrier waveform - + Tpwmu : ndarray + time vector """ # Tpwmu=np.arange(fs*duration)/fs, Tpwmu = np.linspace(0, self.duration, self.fs * self.duration, endpoint=True) @@ -55,4 +56,4 @@ def get_data(self): PWM2 = np.where(v_pwm[1] < ref, -1, 1) # .astype(np.float32) PWM3 = np.where(v_pwm[2] < ref, -1, 1) # .astype(np.float32) Vpwm = np.column_stack([PWM1, PWM2, PWM3]) - return Vpwm, Vas, MI, carrier + return Vpwm, Vas, MI, carrier, Tpwmu From 35c447a9c7252a8e754f9a953b5512eba4d6ae01 Mon Sep 17 00:00:00 2001 From: Martin Glesser Date: Fri, 8 Oct 2021 17:38:44 +0200 Subject: [PATCH 077/167] [BC] type of argument corrected for ImportGenPWM --- pyleecan/Classes/Class_Dict.json | 4 ++-- pyleecan/Classes/ImportGenPWM.py | 8 ++++---- pyleecan/Generator/ClassesRef/Import/ImportGenPWM.csv | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index ae53a22ff..e65c9e11d 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -3447,7 +3447,7 @@ "max": "", "min": "0", "name": "duration", - "type": "int", + "type": "float", "unit": "s", "value": 10 }, @@ -3501,7 +3501,7 @@ "max": "", "min": "", "name": "fswi_max", - "type": "int", + "type": "float", "unit": "Hz", "value": 3000 }, diff --git a/pyleecan/Classes/ImportGenPWM.py b/pyleecan/Classes/ImportGenPWM.py index 21616fb09..f15e288cd 100644 --- a/pyleecan/Classes/ImportGenPWM.py +++ b/pyleecan/Classes/ImportGenPWM.py @@ -339,7 +339,7 @@ def _get_duration(self): def _set_duration(self, value): """setter of duration""" - check_var("duration", value, "int", Vmin=0) + check_var("duration", value, "float", Vmin=0) self._duration = value duration = property( @@ -347,7 +347,7 @@ def _set_duration(self, value): fset=_set_duration, doc=u"""duration - :Type: int + :Type: float :min: 0 """, ) @@ -451,7 +451,7 @@ def _get_fswi_max(self): def _set_fswi_max(self, value): """setter of fswi_max""" - check_var("fswi_max", value, "int") + check_var("fswi_max", value, "float") self._fswi_max = value fswi_max = property( @@ -459,7 +459,7 @@ def _set_fswi_max(self, value): fset=_set_fswi_max, doc=u"""maximal switching frequency - :Type: int + :Type: float """, ) diff --git a/pyleecan/Generator/ClassesRef/Import/ImportGenPWM.csv b/pyleecan/Generator/ClassesRef/Import/ImportGenPWM.csv index b08ed48a1..196bdba98 100644 --- a/pyleecan/Generator/ClassesRef/Import/ImportGenPWM.csv +++ b/pyleecan/Generator/ClassesRef/Import/ImportGenPWM.csv @@ -1,12 +1,12 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description fs,Hz,sample frequency,0,float,96000,0,,,Import,ImportMatrix,get_data,VERSION,1,To generate a PWM voltage matrix -duration,s,duration,0,int,10,0,,,,,,,, +duration,s,duration,0,float,10,0,,,,,,,, f,Hz,fundamental frequency,0,float,50,0,,,,,,,, fmax,Hz,maximal fundamental frequency,0,float,0,0,,,,,,,, fmode,,"speed mode: 0: Fixed speed, 1: Variable speed",0,int,0,0,,,,,,,, fswimode,,"switch mode: 0:Fixed fswi, 1:Variable fswi",0,int,0,,,,,,,,, fswi,Hz,switching frequency,0,float,1000,,,,,,,,, -fswi_max,Hz,maximal switching frequency,0,int,3000,,,,,,,,, +fswi_max,Hz,maximal switching frequency,0,float,3000,,,,,,,,, typePWM,,0: GDPWM 1: DPWMMIN 2: DPWMMAX 3: DPWM0 4: DPWM1 5: DPWM2 6: DPWM3 7: SVPWM 8: SPWM,0,int,8,,,,,,,,, Vdc1,V,DC BUS voltage,0,float,2,,,,,,,,, U0,V,reference voltage,0,float,1,,,,,,,,, From bf00c06fe0557fb9d818e97f0f57bd24902ec3a1 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Sun, 10 Oct 2021 22:05:35 +0200 Subject: [PATCH 078/167] [CO] Split comp_axes i nseveral methods --- pyleecan/Classes/Class_Dict.json | 55 +-- pyleecan/Classes/ELUT_PMSM.py | 376 +++++++++--------- pyleecan/Classes/Input.py | 49 +++ pyleecan/Classes/OutMag.py | 14 - .../Functions/Electrical/comp_fluxlinkage.py | 4 +- .../Generator/ClassesRef/Output/OutMag.csv | 8 +- .../ClassesRef/Simulation/ELUT_PMSM.csv | 15 +- .../Generator/ClassesRef/Simulation/Input.csv | 6 +- pyleecan/Methods/Output/OutMag/comp_Phi_dq.py | 42 -- .../Methods/Simulation/Force/comp_axes.py | 2 +- .../Methods/Simulation/Input/comp_axes.py | 220 ++++------ .../Simulation/Input/comp_axis_angle.py | 70 ++++ .../Simulation/Input/comp_axis_phase.py | 42 ++ .../Simulation/Input/comp_axis_time.py | 89 +++++ .../Simulation/InputCurrent/gen_input.py | 43 +- .../Methods/Simulation/InputFlux/gen_input.py | 25 +- .../Simulation/InputVoltage/gen_input.py | 72 +--- .../Methods/Simulation/Magnetics/comp_axes.py | 2 +- 18 files changed, 574 insertions(+), 560 deletions(-) delete mode 100644 pyleecan/Methods/Output/OutMag/comp_Phi_dq.py create mode 100644 pyleecan/Methods/Simulation/Input/comp_axis_angle.py create mode 100644 pyleecan/Methods/Simulation/Input/comp_axis_phase.py create mode 100644 pyleecan/Methods/Simulation/Input/comp_axis_time.py diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 506d4e6e4..8f29c369c 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -1323,7 +1323,10 @@ "get_Lmq", "comp_Ldqh_from_Phidqh", "import_from_data", - "comp_Phidqh_from_Phiwind" + "comp_Phidqh_from_Phiwind", + "get_Phid_mag_mean", + "get_Phid_mag_harm", + "get_orders_dqh" ], "mother": "ELUT", "name": "ELUT_PMSM", @@ -1331,16 +1334,16 @@ "path": "pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv", "properties": [ { - "desc": "Stator winding flux llinkage fundamental calculated from user-input inductance tables", + "desc": "RMS stator winding flux table in dqh frame (including magnets and currents given by I_dqh)", "max": "", "min": "", - "name": "Phi_dqh", + "name": "Phi_dqh_mean", "type": "ndarray", - "unit": "Wb", + "unit": "Wbrms", "value": null }, { - "desc": "Id Iq Ih table corresponding to flux linkage data given in Phi_dqh", + "desc": "RMS Id Iq Ih table corresponding to flux linkage table given in Phi_dqh_mean", "max": "", "min": "", "name": "I_dqh", @@ -1358,39 +1361,21 @@ "value": 20 }, { - "desc": "RMS fundamental back electromotive force (bemf) along Q-axis", - "max": "", - "min": "", - "name": "E0", - "type": "float", - "unit": "V", - "value": null - }, - { - "desc": "Back emf harmonics along DQH axis", + "desc": "RMS stator winding flux linkage in dqh frame including harmonics (only magnets)", "max": "", "min": "", - "name": "E_dqh", - "type": "ndarray", - "unit": "V", - "value": null - }, - { - "desc": "Back harmonic orders (multiple of fundamental electrical frequency) corresponding to Edqh spectrum", - "max": "", - "min": "", - "name": "orders_dqh", - "type": "ndarray", - "unit": "", - "value": null + "name": "Phi_dqh_mag", + "type": "SciDataTool.Classes.DataND.DataND", + "unit": "Wbrms", + "value": "None" }, { - "desc": "Back electromotive force DataTime object", + "desc": "Stator winding flux function of time and phases", "max": "", "min": "", - "name": "bemf", - "type": "SciDataTool.Classes.DataND.DataND", - "unit": "V", + "name": "Phi_wind", + "type": "[SciDataTool.Classes.DataND.DataND]", + "unit": "Wb", "value": "None" } ] @@ -4143,7 +4128,10 @@ "desc": "Starting data of the simulation", "is_internal": false, "methods": [ - "comp_axes" + "comp_axes", + "comp_axis_time", + "comp_axis_angle", + "comp_axis_phase" ], "mother": "", "name": "Input", @@ -8537,7 +8525,6 @@ "methods": [ "clean", "comp_emf", - "comp_Phi_dq", "comp_power", "get_demag", "store" diff --git a/pyleecan/Classes/ELUT_PMSM.py b/pyleecan/Classes/ELUT_PMSM.py index e3b54f419..66f0d7475 100644 --- a/pyleecan/Classes/ELUT_PMSM.py +++ b/pyleecan/Classes/ELUT_PMSM.py @@ -66,6 +66,21 @@ except ImportError as error: comp_Phidqh_from_Phiwind = error +try: + from ..Methods.Simulation.ELUT_PMSM.get_Phid_mag_mean import get_Phid_mag_mean +except ImportError as error: + get_Phid_mag_mean = error + +try: + from ..Methods.Simulation.ELUT_PMSM.get_Phid_mag_harm import get_Phid_mag_harm +except ImportError as error: + get_Phid_mag_harm = error + +try: + from ..Methods.Simulation.ELUT_PMSM.get_orders_dqh import get_orders_dqh +except ImportError as error: + get_orders_dqh = error + from numpy import array, array_equal from ._check import InitUnKnowClassError @@ -169,6 +184,41 @@ class ELUT_PMSM(ELUT): ) else: comp_Phidqh_from_Phiwind = comp_Phidqh_from_Phiwind + # cf Methods.Simulation.ELUT_PMSM.get_Phid_mag_mean + if isinstance(get_Phid_mag_mean, ImportError): + get_Phid_mag_mean = property( + fget=lambda x: raise_( + ImportError( + "Can't use ELUT_PMSM method get_Phid_mag_mean: " + + str(get_Phid_mag_mean) + ) + ) + ) + else: + get_Phid_mag_mean = get_Phid_mag_mean + # cf Methods.Simulation.ELUT_PMSM.get_Phid_mag_harm + if isinstance(get_Phid_mag_harm, ImportError): + get_Phid_mag_harm = property( + fget=lambda x: raise_( + ImportError( + "Can't use ELUT_PMSM method get_Phid_mag_harm: " + + str(get_Phid_mag_harm) + ) + ) + ) + else: + get_Phid_mag_harm = get_Phid_mag_harm + # cf Methods.Simulation.ELUT_PMSM.get_orders_dqh + if isinstance(get_orders_dqh, ImportError): + get_orders_dqh = property( + fget=lambda x: raise_( + ImportError( + "Can't use ELUT_PMSM method get_orders_dqh: " + str(get_orders_dqh) + ) + ) + ) + else: + get_orders_dqh = get_orders_dqh # save and copy methods are available in all object save = save copy = copy @@ -177,13 +227,11 @@ class ELUT_PMSM(ELUT): def __init__( self, - Phi_dqh=None, + Phi_dqh_mean=None, I_dqh=None, Tmag_ref=20, - E0=None, - E_dqh=None, - orders_dqh=None, - bemf=None, + Phi_dqh_mag=None, + Phi_wind=None, R1=None, L1=None, T1_ref=20, @@ -206,20 +254,16 @@ def __init__( if init_dict is not None: # Initialisation by dict assert type(init_dict) is dict # Overwrite default value with init_dict content - if "Phi_dqh" in list(init_dict.keys()): - Phi_dqh = init_dict["Phi_dqh"] + if "Phi_dqh_mean" in list(init_dict.keys()): + Phi_dqh_mean = init_dict["Phi_dqh_mean"] if "I_dqh" in list(init_dict.keys()): I_dqh = init_dict["I_dqh"] if "Tmag_ref" in list(init_dict.keys()): Tmag_ref = init_dict["Tmag_ref"] - if "E0" in list(init_dict.keys()): - E0 = init_dict["E0"] - if "E_dqh" in list(init_dict.keys()): - E_dqh = init_dict["E_dqh"] - if "orders_dqh" in list(init_dict.keys()): - orders_dqh = init_dict["orders_dqh"] - if "bemf" in list(init_dict.keys()): - bemf = init_dict["bemf"] + if "Phi_dqh_mag" in list(init_dict.keys()): + Phi_dqh_mag = init_dict["Phi_dqh_mag"] + if "Phi_wind" in list(init_dict.keys()): + Phi_wind = init_dict["Phi_wind"] if "R1" in list(init_dict.keys()): R1 = init_dict["R1"] if "L1" in list(init_dict.keys()): @@ -229,13 +273,11 @@ def __init__( if "OP_matrix" in list(init_dict.keys()): OP_matrix = init_dict["OP_matrix"] # Set the properties (value check and convertion are done in setter) - self.Phi_dqh = Phi_dqh + self.Phi_dqh_mean = Phi_dqh_mean self.I_dqh = I_dqh self.Tmag_ref = Tmag_ref - self.E0 = E0 - self.E_dqh = E_dqh - self.orders_dqh = orders_dqh - self.bemf = bemf + self.Phi_dqh_mag = Phi_dqh_mag + self.Phi_wind = Phi_wind # Call ELUT init super(ELUT_PMSM, self).__init__( R1=R1, L1=L1, T1_ref=T1_ref, OP_matrix=OP_matrix @@ -250,9 +292,9 @@ def __str__(self): # Get the properties inherited from ELUT ELUT_PMSM_str += super(ELUT_PMSM, self).__str__() ELUT_PMSM_str += ( - "Phi_dqh = " + "Phi_dqh_mean = " + linesep - + str(self.Phi_dqh).replace(linesep, linesep + "\t") + + str(self.Phi_dqh_mean).replace(linesep, linesep + "\t") + linesep + linesep ) @@ -263,22 +305,8 @@ def __str__(self): + linesep ) ELUT_PMSM_str += "Tmag_ref = " + str(self.Tmag_ref) + linesep - ELUT_PMSM_str += "E0 = " + str(self.E0) + linesep - ELUT_PMSM_str += ( - "E_dqh = " - + linesep - + str(self.E_dqh).replace(linesep, linesep + "\t") - + linesep - + linesep - ) - ELUT_PMSM_str += ( - "orders_dqh = " - + linesep - + str(self.orders_dqh).replace(linesep, linesep + "\t") - + linesep - + linesep - ) - ELUT_PMSM_str += "bemf = " + str(self.bemf) + linesep + linesep + ELUT_PMSM_str += "Phi_dqh_mag = " + str(self.Phi_dqh_mag) + linesep + linesep + ELUT_PMSM_str += "Phi_wind = " + str(self.Phi_wind) + linesep + linesep return ELUT_PMSM_str def __eq__(self, other): @@ -290,19 +318,15 @@ def __eq__(self, other): # Check the properties inherited from ELUT if not super(ELUT_PMSM, self).__eq__(other): return False - if not array_equal(other.Phi_dqh, self.Phi_dqh): + if not array_equal(other.Phi_dqh_mean, self.Phi_dqh_mean): return False if other.I_dqh != self.I_dqh: return False if other.Tmag_ref != self.Tmag_ref: return False - if other.E0 != self.E0: - return False - if not array_equal(other.E_dqh, self.E_dqh): - return False - if not array_equal(other.orders_dqh, self.orders_dqh): + if other.Phi_dqh_mag != self.Phi_dqh_mag: return False - if other.bemf != self.bemf: + if other.Phi_wind != self.Phi_wind: return False return True @@ -317,24 +341,35 @@ def compare(self, other, name="self", ignore_list=None): # Check the properties inherited from ELUT diff_list.extend(super(ELUT_PMSM, self).compare(other, name=name)) - if not array_equal(other.Phi_dqh, self.Phi_dqh): - diff_list.append(name + ".Phi_dqh") + if not array_equal(other.Phi_dqh_mean, self.Phi_dqh_mean): + diff_list.append(name + ".Phi_dqh_mean") if other._I_dqh != self._I_dqh: diff_list.append(name + ".I_dqh") if other._Tmag_ref != self._Tmag_ref: diff_list.append(name + ".Tmag_ref") - if other._E0 != self._E0: - diff_list.append(name + ".E0") - if not array_equal(other.E_dqh, self.E_dqh): - diff_list.append(name + ".E_dqh") - if not array_equal(other.orders_dqh, self.orders_dqh): - diff_list.append(name + ".orders_dqh") - if (other.bemf is None and self.bemf is not None) or ( - other.bemf is not None and self.bemf is None + if (other.Phi_dqh_mag is None and self.Phi_dqh_mag is not None) or ( + other.Phi_dqh_mag is not None and self.Phi_dqh_mag is None ): - diff_list.append(name + ".bemf None mismatch") - elif self.bemf is not None: - diff_list.extend(self.bemf.compare(other.bemf, name=name + ".bemf")) + diff_list.append(name + ".Phi_dqh_mag None mismatch") + elif self.Phi_dqh_mag is not None: + diff_list.extend( + self.Phi_dqh_mag.compare(other.Phi_dqh_mag, name=name + ".Phi_dqh_mag") + ) + if (other.Phi_wind is None and self.Phi_wind is not None) or ( + other.Phi_wind is not None and self.Phi_wind is None + ): + diff_list.append(name + ".Phi_wind None mismatch") + elif self.Phi_wind is None: + pass + elif len(other.Phi_wind) != len(self.Phi_wind): + diff_list.append("len(" + name + ".Phi_wind)") + else: + for ii in range(len(other.Phi_wind)): + diff_list.extend( + self.Phi_wind[ii].compare( + other.Phi_wind[ii], name=name + ".Phi_wind[" + str(ii) + "]" + ) + ) # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -346,15 +381,15 @@ def __sizeof__(self): # Get size of the properties inherited from ELUT S += super(ELUT_PMSM, self).__sizeof__() - S += getsizeof(self.Phi_dqh) + S += getsizeof(self.Phi_dqh_mean) if self.I_dqh is not None: for value in self.I_dqh: S += getsizeof(value) S += getsizeof(self.Tmag_ref) - S += getsizeof(self.E0) - S += getsizeof(self.E_dqh) - S += getsizeof(self.orders_dqh) - S += getsizeof(self.bemf) + S += getsizeof(self.Phi_dqh_mag) + if self.Phi_wind is not None: + for value in self.Phi_wind: + S += getsizeof(value) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -374,56 +409,44 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): keep_function=keep_function, **kwargs ) - if self.Phi_dqh is None: - ELUT_PMSM_dict["Phi_dqh"] = None + if self.Phi_dqh_mean is None: + ELUT_PMSM_dict["Phi_dqh_mean"] = None else: if type_handle_ndarray == 0: - ELUT_PMSM_dict["Phi_dqh"] = self.Phi_dqh.tolist() + ELUT_PMSM_dict["Phi_dqh_mean"] = self.Phi_dqh_mean.tolist() elif type_handle_ndarray == 1: - ELUT_PMSM_dict["Phi_dqh"] = self.Phi_dqh.copy() + ELUT_PMSM_dict["Phi_dqh_mean"] = self.Phi_dqh_mean.copy() elif type_handle_ndarray == 2: - ELUT_PMSM_dict["Phi_dqh"] = self.Phi_dqh + ELUT_PMSM_dict["Phi_dqh_mean"] = self.Phi_dqh_mean else: raise Exception( "Unknown type_handle_ndarray: " + str(type_handle_ndarray) ) ELUT_PMSM_dict["I_dqh"] = self.I_dqh.copy() if self.I_dqh is not None else None ELUT_PMSM_dict["Tmag_ref"] = self.Tmag_ref - ELUT_PMSM_dict["E0"] = self.E0 - if self.E_dqh is None: - ELUT_PMSM_dict["E_dqh"] = None - else: - if type_handle_ndarray == 0: - ELUT_PMSM_dict["E_dqh"] = self.E_dqh.tolist() - elif type_handle_ndarray == 1: - ELUT_PMSM_dict["E_dqh"] = self.E_dqh.copy() - elif type_handle_ndarray == 2: - ELUT_PMSM_dict["E_dqh"] = self.E_dqh - else: - raise Exception( - "Unknown type_handle_ndarray: " + str(type_handle_ndarray) - ) - if self.orders_dqh is None: - ELUT_PMSM_dict["orders_dqh"] = None + if self.Phi_dqh_mag is None: + ELUT_PMSM_dict["Phi_dqh_mag"] = None else: - if type_handle_ndarray == 0: - ELUT_PMSM_dict["orders_dqh"] = self.orders_dqh.tolist() - elif type_handle_ndarray == 1: - ELUT_PMSM_dict["orders_dqh"] = self.orders_dqh.copy() - elif type_handle_ndarray == 2: - ELUT_PMSM_dict["orders_dqh"] = self.orders_dqh - else: - raise Exception( - "Unknown type_handle_ndarray: " + str(type_handle_ndarray) - ) - if self.bemf is None: - ELUT_PMSM_dict["bemf"] = None - else: - ELUT_PMSM_dict["bemf"] = self.bemf.as_dict( + ELUT_PMSM_dict["Phi_dqh_mag"] = self.Phi_dqh_mag.as_dict( type_handle_ndarray=type_handle_ndarray, keep_function=keep_function, **kwargs ) + if self.Phi_wind is None: + ELUT_PMSM_dict["Phi_wind"] = None + else: + ELUT_PMSM_dict["Phi_wind"] = list() + for obj in self.Phi_wind: + if obj is not None: + ELUT_PMSM_dict["Phi_wind"].append( + obj.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) + ) + else: + ELUT_PMSM_dict["Phi_wind"].append(None) # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name ELUT_PMSM_dict["__class__"] = "ELUT_PMSM" @@ -432,22 +455,20 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): def _set_None(self): """Set all the properties to None (except pyleecan object)""" - self.Phi_dqh = None + self.Phi_dqh_mean = None self.I_dqh = None self.Tmag_ref = None - self.E0 = None - self.E_dqh = None - self.orders_dqh = None - self.bemf = None + self.Phi_dqh_mag = None + self.Phi_wind = None # Set to None the properties inherited from ELUT super(ELUT_PMSM, self)._set_None() - def _get_Phi_dqh(self): - """getter of Phi_dqh""" - return self._Phi_dqh + def _get_Phi_dqh_mean(self): + """getter of Phi_dqh_mean""" + return self._Phi_dqh_mean - def _set_Phi_dqh(self, value): - """setter of Phi_dqh""" + def _set_Phi_dqh_mean(self, value): + """setter of Phi_dqh_mean""" if type(value) is int and value == -1: value = array([]) elif type(value) is list: @@ -455,13 +476,13 @@ def _set_Phi_dqh(self, value): value = array(value) except: pass - check_var("Phi_dqh", value, "ndarray") - self._Phi_dqh = value + check_var("Phi_dqh_mean", value, "ndarray") + self._Phi_dqh_mean = value - Phi_dqh = property( - fget=_get_Phi_dqh, - fset=_set_Phi_dqh, - doc=u"""Stator winding flux llinkage fundamental calculated from user-input inductance tables + Phi_dqh_mean = property( + fget=_get_Phi_dqh_mean, + fset=_set_Phi_dqh_mean, + doc=u"""RMS stator winding flux table in dqh frame (including magnets and currents given by I_dqh) :Type: ndarray """, @@ -481,7 +502,7 @@ def _set_I_dqh(self, value): I_dqh = property( fget=_get_I_dqh, fset=_set_I_dqh, - doc=u"""Id Iq Ih table corresponding to flux linkage data given in Phi_dqh + doc=u"""RMS Id Iq Ih table corresponding to flux linkage table given in Phi_dqh_mean :Type: list """, @@ -505,97 +526,62 @@ def _set_Tmag_ref(self, value): """, ) - def _get_E0(self): - """getter of E0""" - return self._E0 - - def _set_E0(self, value): - """setter of E0""" - check_var("E0", value, "float") - self._E0 = value - - E0 = property( - fget=_get_E0, - fset=_set_E0, - doc=u"""RMS fundamental back electromotive force (bemf) along Q-axis - - :Type: float - """, - ) - - def _get_E_dqh(self): - """getter of E_dqh""" - return self._E_dqh - - def _set_E_dqh(self, value): - """setter of E_dqh""" - if type(value) is int and value == -1: - value = array([]) - elif type(value) is list: - try: - value = array(value) - except: - pass - check_var("E_dqh", value, "ndarray") - self._E_dqh = value - - E_dqh = property( - fget=_get_E_dqh, - fset=_set_E_dqh, - doc=u"""Back emf harmonics along DQH axis + def _get_Phi_dqh_mag(self): + """getter of Phi_dqh_mag""" + return self._Phi_dqh_mag - :Type: ndarray - """, - ) - - def _get_orders_dqh(self): - """getter of orders_dqh""" - return self._orders_dqh - - def _set_orders_dqh(self, value): - """setter of orders_dqh""" - if type(value) is int and value == -1: - value = array([]) - elif type(value) is list: - try: - value = array(value) - except: - pass - check_var("orders_dqh", value, "ndarray") - self._orders_dqh = value - - orders_dqh = property( - fget=_get_orders_dqh, - fset=_set_orders_dqh, - doc=u"""Back harmonic orders (multiple of fundamental electrical frequency) corresponding to Edqh spectrum - - :Type: ndarray - """, - ) - - def _get_bemf(self): - """getter of bemf""" - return self._bemf - - def _set_bemf(self, value): - """setter of bemf""" + def _set_Phi_dqh_mag(self, value): + """setter of Phi_dqh_mag""" if isinstance(value, str): # Load from file value = load_init_dict(value)[1] if isinstance(value, dict) and "__class__" in value: class_obj = import_class( - "SciDataTool.Classes", value.get("__class__"), "bemf" + "SciDataTool.Classes", value.get("__class__"), "Phi_dqh_mag" ) value = class_obj(init_dict=value) elif type(value) is int and value == -1: # Default constructor value = DataND() - check_var("bemf", value, "DataND") - self._bemf = value + check_var("Phi_dqh_mag", value, "DataND") + self._Phi_dqh_mag = value - bemf = property( - fget=_get_bemf, - fset=_set_bemf, - doc=u"""Back electromotive force DataTime object + Phi_dqh_mag = property( + fget=_get_Phi_dqh_mag, + fset=_set_Phi_dqh_mag, + doc=u"""RMS stator winding flux linkage in dqh frame including harmonics (only magnets) :Type: SciDataTool.Classes.DataND.DataND """, ) + + def _get_Phi_wind(self): + """getter of Phi_wind""" + if self._Phi_wind is not None: + for obj in self._Phi_wind: + if obj is not None: + obj.parent = self + return self._Phi_wind + + def _set_Phi_wind(self, value): + """setter of Phi_wind""" + if type(value) is list: + for ii, obj in enumerate(value): + if type(obj) is dict: + class_obj = import_class( + "SciDataTool.Classes", obj.get("__class__"), "Phi_wind" + ) + value[ii] = class_obj(init_dict=obj) + if value[ii] is not None: + value[ii].parent = self + if value == -1: + value = list() + check_var("Phi_wind", value, "[DataND]") + self._Phi_wind = value + + Phi_wind = property( + fget=_get_Phi_wind, + fset=_set_Phi_wind, + doc=u"""Stator winding flux function of time and phases + + :Type: [SciDataTool.Classes.DataND.DataND] + """, + ) diff --git a/pyleecan/Classes/Input.py b/pyleecan/Classes/Input.py index a7a321ad4..44b350d04 100644 --- a/pyleecan/Classes/Input.py +++ b/pyleecan/Classes/Input.py @@ -22,6 +22,21 @@ except ImportError as error: comp_axes = error +try: + from ..Methods.Simulation.Input.comp_axis_time import comp_axis_time +except ImportError as error: + comp_axis_time = error + +try: + from ..Methods.Simulation.Input.comp_axis_angle import comp_axis_angle +except ImportError as error: + comp_axis_angle = error + +try: + from ..Methods.Simulation.Input.comp_axis_phase import comp_axis_phase +except ImportError as error: + comp_axis_phase = error + from ..Classes.ImportMatrixVal import ImportMatrixVal from numpy import ndarray @@ -35,6 +50,7 @@ class Input(FrozenClass): VERSION = 1 + # Check ImportError to remove unnecessary dependencies in unused method # cf Methods.Simulation.Input.comp_axes if isinstance(comp_axes, ImportError): comp_axes = property( @@ -44,6 +60,39 @@ class Input(FrozenClass): ) else: comp_axes = comp_axes + # cf Methods.Simulation.Input.comp_axis_time + if isinstance(comp_axis_time, ImportError): + comp_axis_time = property( + fget=lambda x: raise_( + ImportError( + "Can't use Input method comp_axis_time: " + str(comp_axis_time) + ) + ) + ) + else: + comp_axis_time = comp_axis_time + # cf Methods.Simulation.Input.comp_axis_angle + if isinstance(comp_axis_angle, ImportError): + comp_axis_angle = property( + fget=lambda x: raise_( + ImportError( + "Can't use Input method comp_axis_angle: " + str(comp_axis_angle) + ) + ) + ) + else: + comp_axis_angle = comp_axis_angle + # cf Methods.Simulation.Input.comp_axis_phase + if isinstance(comp_axis_phase, ImportError): + comp_axis_phase = property( + fget=lambda x: raise_( + ImportError( + "Can't use Input method comp_axis_phase: " + str(comp_axis_phase) + ) + ) + ) + else: + comp_axis_phase = comp_axis_phase # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/OutMag.py b/pyleecan/Classes/OutMag.py index 686d2a57e..8ee99b1b6 100644 --- a/pyleecan/Classes/OutMag.py +++ b/pyleecan/Classes/OutMag.py @@ -27,11 +27,6 @@ except ImportError as error: comp_emf = error -try: - from ..Methods.Output.OutMag.comp_Phi_dq import comp_Phi_dq -except ImportError as error: - comp_Phi_dq = error - try: from ..Methods.Output.OutMag.comp_power import comp_power except ImportError as error: @@ -78,15 +73,6 @@ class OutMag(FrozenClass): ) else: comp_emf = comp_emf - # cf Methods.Output.OutMag.comp_Phi_dq - if isinstance(comp_Phi_dq, ImportError): - comp_Phi_dq = property( - fget=lambda x: raise_( - ImportError("Can't use OutMag method comp_Phi_dq: " + str(comp_Phi_dq)) - ) - ) - else: - comp_Phi_dq = comp_Phi_dq # cf Methods.Output.OutMag.comp_power if isinstance(comp_power, ImportError): comp_power = property( diff --git a/pyleecan/Functions/Electrical/comp_fluxlinkage.py b/pyleecan/Functions/Electrical/comp_fluxlinkage.py index 4c8c02359..553e6b4f7 100644 --- a/pyleecan/Functions/Electrical/comp_fluxlinkage.py +++ b/pyleecan/Functions/Electrical/comp_fluxlinkage.py @@ -2,7 +2,7 @@ from os.path import join from ...Functions.FEMM.draw_FEMM import draw_FEMM -from ...Functions.Electrical.coordinate_transformation import n2dq +from ...Functions.Electrical.coordinate_transformation import n2dqh from ...Classes._FEMMHandler import _FEMMHandler from ...Classes.OutMagFEMM import OutMagFEMM from numpy import linspace, pi, split @@ -114,7 +114,7 @@ def comp_fluxlinkage(obj, output): # Define d axis angle for the d,q transform d_angle = (angle_rotor - angle_offset_initial) * zp - fluxdq = split(n2dq(Phi_wind, d_angle, n=qs), 2, axis=1) + fluxdq = split(n2dqh(Phi_wind, d_angle), 2, axis=1) # restore the original elec output.elec = elec diff --git a/pyleecan/Generator/ClassesRef/Output/OutMag.csv b/pyleecan/Generator/ClassesRef/Output/OutMag.csv index 2899fed93..b95068fea 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutMag.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutMag.csv @@ -1,10 +1,10 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description axes_dict,,Dict containing axes data used for Magnetics,,{SciDataTool.Classes.DataND.Data},None,,,,Output,,clean,VERSION,1,Gather the magnetic module outputs B,H,Airgap flux density VectorField object,"(Nt_tot,Na_tot)",SciDataTool.Classes.VectorField.VectorField,None,,,,,,comp_emf,,, -Tem,N.m,Electromagnetic torque DataTime object,Nt_tot,SciDataTool.Classes.DataND.DataND,None,,,,,,comp_Phi_dq,,, -Tem_av,N.m,Average Electromagnetic torque,,float,None,,,,,,comp_power,,, -Tem_rip_norm,-,Peak to Peak Torque ripple normalized according to average torque (None if average torque=0),,float,None,,,,,,get_demag,,, -Tem_rip_pp,N.m,Peak to Peak Torque ripple,,float,None,,,,,,store,,, +Tem,N.m,Electromagnetic torque DataTime object,Nt_tot,SciDataTool.Classes.DataND.DataND,None,,,,,,comp_power,,, +Tem_av,N.m,Average Electromagnetic torque,,float,None,,,,,,get_demag,,, +Tem_rip_norm,-,Peak to Peak Torque ripple normalized according to average torque (None if average torque=0),,float,None,,,,,,store,,, +Tem_rip_pp,N.m,Peak to Peak Torque ripple,,float,None,,,,,,,,, Phi_wind_stator,Wb,Stator winding flux DataTime object,"(Nt_tot, qs)",SciDataTool.Classes.DataND.DataND,None,,,,,,,,, Phi_wind,Wb,Dict of lamination winding fluxlinkage DataTime objects,"(Nt_tot, qs)",{SciDataTool.Classes.DataND.DataND},None,,,,,,,,, emf,V,Electromotive force DataTime object,"(Nt_tot, qs)",SciDataTool.Classes.DataND.DataND,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv b/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv index 0a59469f6..a851848c4 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv @@ -1,10 +1,13 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille -Phi_dqh,Wb,Stator winding flux llinkage fundamental calculated from user-input inductance tables,,ndarray,None,,,,Simulation,ELUT,get_param_dict,VERSION,1,ELUT class for PMSM, -I_dqh,Arms,Id Iq Ih table corresponding to flux linkage data given in Phi_dqh,,list,None,,,,,,get_Lq,,,, +Phi_dqh_mean,Wbrms,RMS stator winding flux table in dqh frame (including magnets and currents given by I_dqh),"(N_dq, 3)",ndarray,None,,,,Simulation,ELUT,get_param_dict,VERSION,1,ELUT class for PMSM, +I_dqh,Arms,RMS Id Iq Ih table corresponding to flux linkage table given in Phi_dqh_mean,"(N_dq, 3)",list,None,,,,,,get_Lq,,,, Tmag_ref,degC,Magnet average temperature at which Phi_dqh is given,,float,20,,,,,,get_bemf,,,, -E0,V,RMS fundamental back electromotive force (bemf) along Q-axis,,float,None,,,,,,get_Ld,,,, -E_dqh,V,Back emf harmonics along DQH axis,,ndarray,None,,,,,,get_Lmd,,,, -orders_dqh,,Back harmonic orders (multiple of fundamental electrical frequency) corresponding to Edqh spectrum,,ndarray,None,,,,,,get_Lmq,,,, -bemf,V,Back electromotive force DataTime object,"(Nt_tot, qs)",SciDataTool.Classes.DataND.DataND,None,,,,,,comp_Ldqh_from_Phidqh,,,, +Phi_dqh_mag,Wbrms,RMS stator winding flux linkage in dqh frame including harmonics (only magnets),"(Nharm, 3)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Ld,,,, +Phi_wind,Wb,Stator winding flux function of time and phases,"(Nt_tot, qs)",[SciDataTool.Classes.DataND.DataND],None,,,,,,get_Lmd,,,, +,,,,,,,,,,,get_Lmq,,,, +,,,,,,,,,,,comp_Ldqh_from_Phidqh,,,, ,,,,,,,,,,,import_from_data,,,, ,,,,,,,,,,,comp_Phidqh_from_Phiwind,,,, +,,,,,,,,,,,get_Phid_mag_mean,,,, +,,,,,,,,,,,get_Phid_mag_harm,,,, +,,,,,,,,,,,get_orders_dqh,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/Input.csv b/pyleecan/Generator/ClassesRef/Simulation/Input.csv index 546dfbfee..3b6111b0e 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/Input.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/Input.csv @@ -1,7 +1,7 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description time,s,Electrical time vector (no symmetry) to import,Nt_tot,ImportMatrix,None,,,,Simulation,,comp_axes,VERSION,1,Starting data of the simulation -angle,rad,Electrical position vector (no symmetry) to import,Na_tot,ImportMatrix,None,,,,,,,,, -Nt_tot,-,Time discretization,0,int,2048,1,,,,,,,, -Nrev,-,Number of rotor revolution (to compute the final time),,float,None,0,,,,,,,, +angle,rad,Electrical position vector (no symmetry) to import,Na_tot,ImportMatrix,None,,,,,,comp_axis_time,,, +Nt_tot,-,Time discretization,0,int,2048,1,,,,,comp_axis_angle,,, +Nrev,-,Number of rotor revolution (to compute the final time),,float,None,0,,,,,comp_axis_phase,,, Na_tot,-,Angular discretization,,int,2048,1,,,,,,,, N0,rpm,Rotor speed,1,float,None,,,,,,,,, diff --git a/pyleecan/Methods/Output/OutMag/comp_Phi_dq.py b/pyleecan/Methods/Output/OutMag/comp_Phi_dq.py deleted file mode 100644 index c23bb1d28..000000000 --- a/pyleecan/Methods/Output/OutMag/comp_Phi_dq.py +++ /dev/null @@ -1,42 +0,0 @@ -from numpy import mean, pi - -from ....Functions.Electrical.coordinate_transformation import n2dq - - -def comp_Phi_dq(self): - """Compute the stator flux linkage along dq axes - - Parameters - ---------- - self : OutMag - an OutMag object - - Returns - ------- - Phi_dqh : ndarray - Stator flux linkage along dq axes [Wb] - - """ - - output = self.parent - - qs = output.simu.machine.stator.winding.qs - felec = output.elec.felec - - # Get rotation direction of the fundamental magnetic field created by the winding - rot_dir = output.get_rot_dir() - - result = self.Phi_wind_stator.get_along("time[oneperiod]", "phase") - - Phi = result["Phi_{wind}"] - - time = result["time"] - - # Get stator current function of time - Phi_dq_time = n2dq( - Phi, 2 * pi * felec * time, n=qs, rot_dir=rot_dir, is_dq_rms=True - ) - - Phi_dq = mean(Phi_dq_time, axis=0) - - return Phi_dq diff --git a/pyleecan/Methods/Simulation/Force/comp_axes.py b/pyleecan/Methods/Simulation/Force/comp_axes.py index 3ddeb228f..700cc6b29 100644 --- a/pyleecan/Methods/Simulation/Force/comp_axes.py +++ b/pyleecan/Methods/Simulation/Force/comp_axes.py @@ -23,7 +23,7 @@ def comp_axes(self, output): # Add periodicities to time and angle axes axes_dict = output.simu.input.comp_axes( axes_list=["time", "angle"], - axes_dict=axes_dict_mag, + axes_dict_in=axes_dict_mag, is_periodicity_a=self.is_periodicity_a, is_periodicity_t=self.is_periodicity_t, ) diff --git a/pyleecan/Methods/Simulation/Input/comp_axes.py b/pyleecan/Methods/Simulation/Input/comp_axes.py index 4a03f4e72..5e3de548d 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axes.py +++ b/pyleecan/Methods/Simulation/Input/comp_axes.py @@ -1,17 +1,8 @@ -from numpy import pi - -from SciDataTool import Data1D, DataLinspace, Norm_ref - -from ....Methods.Simulation.Input import InputError - -from ....Functions.Winding.gen_phase_list import gen_name - - def comp_axes( self, axes_list, machine=None, - axes_dict=None, + axes_dict_in=None, is_periodicity_a=None, is_periodicity_t=None, per_a=None, @@ -63,7 +54,7 @@ def comp_axes( raise Exception("Cannot calculate axes if parent output is None") if (axes_list is None or len(axes_list) == 0) and ( - axes_dict is None or len(axes_dict) == 0 + axes_dict_in is None or len(axes_dict_in) == 0 ): raise Exception( "Cannot calculate axes if both axes list and axes dict are None" @@ -72,17 +63,6 @@ def comp_axes( if len(axes_list) == 0: raise Exception("axes_list should not be empty") - if axes_dict is not None: - # Get list of axes name from dict of axes - axes_dict_list = list(axes_dict.keys()) - for ax in axes_list: - if ax not in axes_dict_list: - raise Exception("Axis " + ax + " is requested but is not in axes_dict") - - if axes_dict is None or len(axes_dict) == 0: - # Init axes_dict - axes_dict = dict() - if machine is None: # Fetch machine from input if hasattr(simu, "machine") and simu.machine is not None: @@ -103,151 +83,95 @@ def comp_axes( is_antiper_t_0, ) = output.get_machine_periodicity() + if is_periodicity_t is None or is_periodicity_t: + # Enforce None values to machine time periodicity + per_t = per_t_0 if per_t is None else per_t + is_antiper_t = is_antiper_t_0 if is_antiper_t is None else is_antiper_t + if is_periodicity_t is None: + # Check time periodicity is included + is_periodicity_t = per_t > 1 or is_antiper_t + elif not is_periodicity_t: + # Remove time periodicity + per_t = 1 + is_antiper_t = False + + if is_periodicity_a is None or is_periodicity_a: + # Enforce None values to machine periodicity + per_a = per_a_0 if per_a is None else per_a + is_antiper_a = is_antiper_a_0 if is_antiper_a is None else is_antiper_a + if is_periodicity_a is None: + # Enforce requested angle periodicity + is_periodicity_a = per_a > 1 or is_antiper_a + elif not is_periodicity_a: + # Remove angle periodicity + per_a = 1 + is_antiper_a = False + + # Init axes_dict + axes_dict = dict() + + # Get time axis if "time" in axes_list: - if is_periodicity_t is None or is_periodicity_t: - # Enforce None values to machine time periodicity - per_t = per_t_0 if per_t is None else per_t - is_antiper_t = is_antiper_t_0 if is_antiper_t is None else is_antiper_t - if is_periodicity_t is None: - # Check time periodicity is included - is_periodicity_t = per_t > 1 or is_antiper_t - elif not is_periodicity_t: - # Remove time periodicity - per_t = 1 - is_antiper_t = False - - # Get electrical fundamental frequency - f_elec = self.comp_felec() - - # Setup normalizations for time and angle axes - norm_time = { - "elec_order": Norm_ref(ref=f_elec), - "mech_order": Norm_ref(ref=f_elec / p), - } - - if "time" in axes_dict: - # Compute Time axis based on the one stored in OutElec - Time = axes_dict["time"].get_axis_periodic(Nper=per_t, is_aper=is_antiper_t) - Time.normalizations = norm_time - - # Create time axis - elif self.time is None: - # Create time axis as a DataLinspace - if self.Nrev is not None: - if self.N0 is not None: - t_final = 60 / self.N0 * self.Nrev - else: - raise InputError("time and N0 can't be both None") - else: - t_final = p / f_elec - # Create time axis as a DataLinspace - Time = DataLinspace( - name="time", - unit="s", - initial=0, - final=t_final, - number=self.Nt_tot, - include_endpoint=False, - normalizations=norm_time, - ) - # Add time (anti-)periodicity - if per_t > 1 or is_antiper_t: - Time = Time.get_axis_periodic(per_t, is_antiper_t) + # Check if Time is already in input dict of axes + if axes_dict_in is not None and "time" in axes_dict_in: + Time_in = axes_dict_in["time"] else: - # Load time data - time = self.time.get_data() - self.Nt_tot = len(time) - Time = Data1D(name="time", unit="s", values=time, normalizations=norm_time) - # Add time (anti-)periodicity - sym_t = dict() - if is_antiper_t: - sym_t["antiperiod"] = per_t - else: - sym_t["period"] = per_t - Time.symmetries = sym_t - Time = Time.to_linspace() - - # Compute angle_rotor (added to time normalizations) - output.comp_angle_rotor(Time) + Time_in = None + + # Calculate time axis + Time = self.comp_axis_time(output, p, per_t, is_antiper_t, Time_in) # Store time axis in dict axes_dict["time"] = Time + # Get angle axis if "angle" in axes_list: - if is_periodicity_a is None or is_periodicity_a: - # Enforce None values to machine periodicity - per_a = per_a_0 if per_a is None else per_a - is_antiper_a = is_antiper_a_0 if is_antiper_a is None else is_antiper_a - if is_periodicity_a is None: - # Enforce requested angle periodicity - is_periodicity_a = per_a > 1 or is_antiper_a - elif not is_periodicity_a: - # Remove angle periodicity - per_a = 1 - is_antiper_a = False - # Airgap radius Rag = machine.comp_Rgap_mec() - norm_angle = {"space_order": Norm_ref(ref=p), "distance": Norm_ref(ref=1 / Rag)} - - if "angle" in axes_dict: - # Compute Angle axis based on the one stored in OutElec - Angle = axes_dict["angle"].get_axis_periodic( - Nper=per_a, is_aper=is_antiper_a - ) - - # Create angle axis - elif self.angle is None: - - # Create angle axis as a DataLinspace - Angle = DataLinspace( - name="angle", - unit="rad", - initial=0, - final=2 * pi, - number=self.Na_tot, - include_endpoint=False, - normalizations=norm_angle, - ) - # Add angle (anti-)periodicity - if per_a > 1 or is_antiper_a: - Angle = Angle.get_axis_periodic(per_a, is_antiper_a) - + # Check if Angle is already in input dict of axes + if axes_dict_in is not None and "angle" in axes_dict_in: + Angle_in = axes_dict_in["angle"] else: - # Load angle data - angle = self.angle.get_data() - self.Na_tot = len(angle) - Angle = Data1D( - name="angle", unit="rad", values=angle, normalizations=norm_angle - ) - # Add angle (anti-)periodicity - sym_a = dict() - if is_antiper_a: - sym_a["antiperiod"] = per_a - else: - sym_a["period"] = per_a - Angle.symmetries = sym_a - Angle = Angle.to_linspace() + Angle_in = None + + # Calculate angle axis + Angle = self.comp_axis_angle(p, Rag, per_a, is_antiper_a, Angle_in) # Store angle axis in dict axes_dict["angle"] = Angle - if "phase" in axes_list: + if "phase_S" in axes_list: - qs = machine.stator.winding.qs + # Check if Phase is already in input dict of axes + if axes_dict_in is not None and "phase_S" in axes_dict_in: + Phase_in = axes_dict_in["phase_S"] + else: + Phase_in = None - # Creating the data object - Phase = Data1D( - name="phase", - unit="", - values=gen_name(qs), - is_components=True, - ) + # Calculate stator phase axis + Phase = self.comp_axis_phase(machine.stator, Phase_in) + + if Phase is not None: + # Store phase axis in dict + axes_dict["phase_S"] = Phase + + if "phase_R" in axes_list: + + # Check if Phase is already in input dict of axes + if axes_dict_in is not None and "phase_R" in axes_dict_in: + Phase_in = axes_dict_in["phase_R"] + else: + Phase_in = None + + # Calculate rotor phase axis + per_a_phase = 2 * per_a if is_antiper_a else per_a + Phase = self.comp_axis_phase(machine.rotor, per_a_phase, Phase_in) - # Store phase axis in dict - axes_dict["phase"] = Phase + if Phase is not None: + # Store phase axis in dict + axes_dict["phase_R"] = Phase return axes_dict diff --git a/pyleecan/Methods/Simulation/Input/comp_axis_angle.py b/pyleecan/Methods/Simulation/Input/comp_axis_angle.py new file mode 100644 index 000000000..84f229997 --- /dev/null +++ b/pyleecan/Methods/Simulation/Input/comp_axis_angle.py @@ -0,0 +1,70 @@ +from numpy import pi + +from SciDataTool import Data1D, DataLinspace, Norm_ref + + +def comp_axis_angle(self, p, Rag, per_a, is_antiper_a, Angle_in=None): + """Compute angle axis with or without periodicities and including normalizations + + Parameters + ---------- + self : Input + an Input object + p : int + Machine pole pair number + Rag: float + Airgap mean radius [m] + per_a : int + angle periodicity + is_antiper_a : bool + if the angle axis is antiperiodic + Angle_in: Data + Input axis angle + + Returns + ------- + Timee_in: Data + Requested axis angle + + """ + + norm_angle = {"space_order": Norm_ref(ref=p), "distance": Norm_ref(ref=1 / Rag)} + + if Angle_in is not None: + # Compute Angle axis based on the one stored in OutElec + Angle = Angle_in.get_axis_periodic(Nper=per_a, is_aper=is_antiper_a) + + # Create angle axis + elif self.angle is None: + + # Create angle axis as a DataLinspace + Angle = DataLinspace( + name="angle", + unit="rad", + initial=0, + final=2 * pi, + number=self.Na_tot, + include_endpoint=False, + normalizations=norm_angle, + ) + # Add angle (anti-)periodicity + if per_a > 1 or is_antiper_a: + Angle = Angle.get_axis_periodic(per_a, is_antiper_a) + + else: + # Load angle data + angle = self.angle.get_data() + self.Na_tot = angle.size + Angle = Data1D( + name="angle", unit="rad", values=angle, normalizations=norm_angle + ) + # Add angle (anti-)periodicity + sym_a = dict() + if is_antiper_a: + sym_a["antiperiod"] = per_a + else: + sym_a["period"] = per_a + Angle.symmetries = sym_a + Angle = Angle.to_linspace() + + return Angle diff --git a/pyleecan/Methods/Simulation/Input/comp_axis_phase.py b/pyleecan/Methods/Simulation/Input/comp_axis_phase.py new file mode 100644 index 000000000..194ee756f --- /dev/null +++ b/pyleecan/Methods/Simulation/Input/comp_axis_phase.py @@ -0,0 +1,42 @@ +from SciDataTool import Data1D + + +def comp_axis_phase(self, lamination, per_a=None, Phase_in=None): + """Compute phase axes for given lamination + + Parameters + ---------- + self : Input + an Input object + lamination: Lamination + a Lamination object + per_a : int + time periodicity + Phase_in: Data + Input phase axis + + Returns + ------- + Phase: Data + Requested phase axis + """ + + Phase = None + + if Phase_in is not None: + Phase = Phase_in.copy() + + else: + name_phase = lamination.get_name_phase() + + if len(name_phase) > 0: + + # Creating the data object + Phase = Data1D( + name="phase", + unit="", + values=name_phase, + is_components=True, + ) + + return Phase diff --git a/pyleecan/Methods/Simulation/Input/comp_axis_time.py b/pyleecan/Methods/Simulation/Input/comp_axis_time.py new file mode 100644 index 000000000..e576d39c5 --- /dev/null +++ b/pyleecan/Methods/Simulation/Input/comp_axis_time.py @@ -0,0 +1,89 @@ +from numpy import pi + +from SciDataTool import Data1D, DataLinspace, Norm_ref + +from ....Methods.Simulation.Input import InputError + + +def comp_axis_time(self, output, p, per_t, is_antiper_t, Time_in=None): + """Compute time axis, with or without periodicities and including normalizations + + Parameters + ---------- + self : Input + an Input object + output: Output + An output object to calculate angle_rotor normalization + p: int + Number of pole pairs + per_t : int + time periodicity + is_antiper_t : bool + if the time axis is antiperiodic + Time_in: Data + Input time axis + + Returns + ------- + Time: Data + Requested Time axis + """ + # Get electrical fundamental frequency + f_elec = self.comp_felec() + + # Get magnetic field rotation direction + rot_dir = output.get_rot_dir() + + # Setup normalizations for time and angle axes + norm_time = { + "elec_order": Norm_ref(ref=f_elec), + "mech_order": Norm_ref(ref=f_elec / p), + "angle_elec": Norm_ref(ref=rot_dir / (2 * pi * f_elec)), + } + + if Time_in is not None: + # Compute Time axis based on the one stored in OutElec + Time = Time_in.get_axis_periodic(Nper=per_t, is_aper=is_antiper_t) + Time.normalizations = norm_time + + # Create time axis + elif self.time is None: + # Create time axis as a DataLinspace + if self.Nrev is not None: + if self.N0 is not None: + t_final = 60 / self.N0 * self.Nrev + else: + raise InputError("time and N0 can't be both None") + else: + t_final = p / f_elec + # Create time axis as a DataLinspace + Time = DataLinspace( + name="time", + unit="s", + initial=0, + final=t_final, + number=self.Nt_tot, + include_endpoint=False, + normalizations=norm_time, + ) + # Add time (anti-)periodicity + if per_t > 1 or is_antiper_t: + Time = Time.get_axis_periodic(per_t, is_antiper_t) + else: + # Load time data + time = self.time.get_data() + self.Nt_tot = time.size + Time = Data1D(name="time", unit="s", values=time, normalizations=norm_time) + # Add time (anti-)periodicity + sym_t = dict() + if is_antiper_t: + sym_t["antiperiod"] = per_t + else: + sym_t["period"] = per_t + Time.symmetries = sym_t + Time = Time.to_linspace() + + # Compute angle_rotor (added to time normalizations) + output.comp_angle_rotor(Time) + + return Time diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index bc0a83b6a..eab908362 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -1,11 +1,8 @@ -from numpy import ndarray, pi, mean, zeros - -from ....Classes.Simulation import Simulation +from numpy import ndarray, mean, zeros from ....Methods.Simulation.Input import InputError -from ....Functions.Electrical.coordinate_transformation import n2dq -from ....Functions.Winding.gen_phase_list import gen_name +from ....Functions.Electrical.coordinate_transformation import n2dqh from ....Classes.InputVoltage import InputVoltage from SciDataTool import Data1D, DataTime @@ -20,25 +17,12 @@ def gen_input(self): An InputCurrent object """ - # Get the simulation - if isinstance(self.parent, Simulation): - simu = self.parent - elif isinstance(self.parent.parent, Simulation): - simu = self.parent.parent - else: - raise InputError("InputCurrent object should be inside a Simulation object") - - # Get output - if simu.parent is not None: - output = simu.parent - else: - raise InputError("Simulation object should be inside an Output object") - # Call InputVoltage.gen_input() InputVoltage.gen_input(self) - # Get outputs - outgeo = output.geo + # Get simulation and outputs + simu = self.parent + output = simu.parent outelec = output.elec # Number of winding phases for stator/rotor @@ -78,14 +62,13 @@ def gen_input(self): name="Stator current", unit="A", symbol="I_s", - axes=[Time, outgeo.axes_dict["phase"]], + axes=[Time, outelec.axes_dict["phase_S"]], values=Is, ) # Compute corresponding Id/Iq reference - Idq = n2dq( + Idq = n2dqh( outelec.Is.values, - 2 * pi * outelec.felec * Time.get_values(is_oneperiod=False), - n=qs, + Time.get_values(is_oneperiod=False, normalization="angle_elec"), is_dq_rms=True, ) outelec.Id_ref = mean(Idq[:, 0]) @@ -105,17 +88,11 @@ def gen_input(self): + str(Ir.shape) + " returned" ) - # Creating the data object - Phase = Data1D( - name="phase", - unit="", - values=gen_name(qr), - is_components=True, - ) + outelec.Ir = DataTime( name="Rotor current", unit="A", symbol="Ir", - axes=[Time, Phase], + axes=[Time, outelec.axes_dict["phase_R"]], values=Ir, ) diff --git a/pyleecan/Methods/Simulation/InputFlux/gen_input.py b/pyleecan/Methods/Simulation/InputFlux/gen_input.py index e9192548f..9ec106e87 100644 --- a/pyleecan/Methods/Simulation/InputFlux/gen_input.py +++ b/pyleecan/Methods/Simulation/InputFlux/gen_input.py @@ -8,10 +8,6 @@ from ....Classes.Input import Input from ....Classes.InputCurrent import InputCurrent -from ....Methods.Simulation.Input import InputError - -from ....Functions.load import import_class - VERBOSE_KEY = {"Br": "Radial", "Bt": "Tangential", "Bz": "Axial"} @@ -25,24 +21,15 @@ def gen_input(self): An InputFlux object """ - Simulation = import_class("pyleecan.Classes", "Simulation") - - # get the simulation - if isinstance(self.parent, Simulation): - simu = self.parent - elif isinstance(self.parent.parent, Simulation): - simu = self.parent.parent - else: - raise InputError("InputFlux object should be inside a Simulation object") + # Call InputCurrent.gen_input() + InputCurrent.gen_input(self) - if simu.parent is None: - raise InputError("The Simulation object must be in an Output object to run") + # Get simulation and outputs + simu = self.parent + output = simu.parent logger = simu.get_logger() - # Call InputCurrent.gen_input() - InputCurrent.gen_input(self) - # Import flux components out_mag = OutMag() if self.B_enforced is None: @@ -136,4 +123,4 @@ def gen_input(self): for ax in axes_list: out_mag.axes_dict[ax.name] = ax.copy() - simu.parent.mag = out_mag + output.mag = out_mag diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index a9adebdfc..41a180588 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -18,15 +18,16 @@ def gen_input(self): # Get the simulation if isinstance(self.parent, Simulation): simu = self.parent - elif isinstance(self.parent.parent, Simulation): - simu = self.parent.parent else: - raise InputError("InputVoltage object should be inside a Simulation object") - - if simu.parent is None: - raise InputError("The Simulation object must be in an Output object to run") + raise InputError( + self.__class__.__name__ + " object should be inside a Simulation object" + ) - output = simu.parent + # Get output + if simu.parent is not None: + output = simu.parent + else: + raise InputError("Simulation object should be inside an Output object") # Create the correct Output object outelec = OutElec() @@ -51,9 +52,8 @@ def gen_input(self): # Load and check alpha_rotor and N0 if self.angle_rotor is None and self.N0 is None: - raise InputError( - "InputVoltage.angle_rotor and InputVoltage.N0 can't be None at the same time" - ) + raise InputError("angle_rotor and N0 can't be None at the same time") + if self.angle_rotor is not None: outelec.angle_rotor = self.angle_rotor.get_data() if ( @@ -89,58 +89,14 @@ def gen_input(self): # Calculate time, angle and phase axes and store them in OutGeo outgeo.axes_dict = self.comp_axes( - axes_list=["time", "angle", "phase"], + axes_list=["time", "angle"], is_periodicity_a=False, is_periodicity_t=False, ) # Create time axis for electrical model including periodicity outelec.axes_dict = self.comp_axes( - axes_list=["time"], axes_dict=outgeo.axes_dict, is_periodicity_t=False + axes_list=["time", "phase_S", "phase_R"], + axes_dict_in=outgeo.axes_dict, + is_periodicity_t=False, ) - - # Generate Us - # if qs > 0: - # TODO - # if self.Is is None: - # if self.Id_ref is None and self.Iq_ref is None: - # raise InputError( - # "ERROR: InputVoltage.Is, InputVoltage.Id_ref, and InputVoltage.Iq_ref missing" - # ) - # else: - # outelec.Id_ref = self.Id_ref - # outelec.Iq_ref = self.Iq_ref - # outelec.Is = None - # else: - # Is = self.Is.get_data() - # if not isinstance(Is, ndarray) or Is.shape != (self.Nt_tot, qs): - # raise InputError( - # "ERROR: InputVoltage.Is must be a matrix with the shape " - # + str((self.Nt_tot, qs)) - # + " (len(time), stator phase number), " - # + str(Is.shape) - # + " returned" - # ) - # # Creating the data object - # Phase = Data1D( - # name="phase", - # unit="", - # values=gen_name(qs), - # is_components=True, - # ) - # outelec.Is = DataTime( - # name="Stator current", - # unit="A", - # symbol="Is", - # axes=[Phase, Time], - # values=transpose(Is), - # ) - # # Compute corresponding Id/Iq reference - # Idq = n2dq( - # transpose(outelec.Is.values), - # 2 * pi * outelec.felec * outelec.axes_dict["time"].get_values(is_oneperiod=False), - # n=qs, - # is_dq_rms=True, - # ) - # outelec.Id_ref = mean(Idq[:, 0]) - # outelec.Iq_ref = mean(Idq[:, 1]) # TODO use of mean has to be documented diff --git a/pyleecan/Methods/Simulation/Magnetics/comp_axes.py b/pyleecan/Methods/Simulation/Magnetics/comp_axes.py index 33bab412a..49a9c83cc 100644 --- a/pyleecan/Methods/Simulation/Magnetics/comp_axes.py +++ b/pyleecan/Methods/Simulation/Magnetics/comp_axes.py @@ -24,7 +24,7 @@ def comp_axes(self, output): # Add periodicities to time and angle axes axes_dict = output.simu.input.comp_axes( axes_list=["time", "angle"], - axes_dict=axes_dict_geo, + axes_dict_in=axes_dict_geo, is_periodicity_a=self.is_periodicity_a, is_periodicity_t=self.is_periodicity_t, ) From 18d6422c3c8e9056d7c0b7d5afcb5698a91c5e2e Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Sun, 10 Oct 2021 22:06:08 +0200 Subject: [PATCH 079/167] [NF] Extend DQ transform to DQH --- .../test_coordinate_transformation.py | 114 +++-- .../Electrical/coordinate_transformation.py | 400 ++++++++++++------ .../Machine/LamSlotMultiWind/comp_mmf_unit.py | 2 - .../Machine/LamSlotWind/comp_mmf_unit.py | 8 +- .../Machine/Lamination/get_name_phase.py | 1 - pyleecan/Methods/Output/OutElec/get_I_fund.py | 31 +- pyleecan/Methods/Output/OutElec/get_Us.py | 4 +- pyleecan/Methods/Output/OutMag/comp_emf.py | 8 +- .../Methods/Simulation/EEC_LSRPM/gen_drive.py | 2 +- .../Methods/Simulation/EEC_PMSM/gen_drive.py | 6 +- .../Simulation/EEC_SCIM/comp_joule_losses.py | 4 +- .../Simulation/EEC_SCIM/comp_parameters.py | 8 +- .../Methods/Simulation/EEC_SCIM/gen_drive.py | 4 +- .../Methods/Simulation/EEC_SCIM/solve_EEC.py | 2 +- 14 files changed, 391 insertions(+), 203 deletions(-) diff --git a/Tests/Functions/test_coordinate_transformation.py b/Tests/Functions/test_coordinate_transformation.py index 2e0187e6c..38b47bda1 100644 --- a/Tests/Functions/test_coordinate_transformation.py +++ b/Tests/Functions/test_coordinate_transformation.py @@ -1,46 +1,100 @@ -# -*- coding: utf-8 -*- -""" -Package: -File Created: Thursday, 5th March 2020 11:32:19 am -Last Modified: Thursday, 5th March 2020 11:33:16 am -Author: Sebastian Guenther -""" - +import pytest -from numpy import pi, array +from numpy import pi, array, linspace, zeros, cos, mean, sqrt, sin from numpy.testing import assert_array_almost_equal +from SciDataTool import DataTime, Data1D, Norm_ref + from pyleecan.Functions.Electrical.coordinate_transformation import ( - ab2n, - n2ab, - dq2ab, - ab2dq, + n2dqh, + dqh2n, + n2dqh_DataTime, + dqh2n_DataTime, ) +from pyleecan.Functions.Winding.gen_phase_list import gen_name -import pytest +param_list = [ + {"qs": 3, "rot_dir": -1}, + {"qs": 3, "rot_dir": 1}, + {"qs": 6, "rot_dir": -1}, + {"qs": 11, "rot_dir": -1}, +] -"""unittest for coordinate transformation functions""" +is_show_fig = False -def test_coordinate_transformation_Ok(): +@pytest.mark.parametrize("param_dict", param_list) +def test_coordinate_transformation(param_dict): """Check that the coordinate transformations can return a correct output""" - X_uvw = array([[1, -0.5, -0.5], [-1, 0.5, 0.5]]) - X_ab = array([[1, 0], [0, 1]]) - X_ab_wrong = array([1, 0]) + Nt = 100 + f_elec = 1 + p = 1 + + qs = param_dict["qs"] + rot_dir = param_dict["rot_dir"] + + time = linspace(0, 1 / f_elec, Nt, endpoint=False) + angle_elec = rot_dir * 2 * pi * time + + # Time axis for plots + norm_time = { + "elec_order": Norm_ref(ref=f_elec), + "mech_order": Norm_ref(ref=f_elec / p), + "angle_elec": Norm_ref(ref=rot_dir / (2 * pi * f_elec)), + } + Time = Data1D(name="time", unit="s", values=time, normalizations=norm_time) + + # Phase axis for plots + Phase = Data1D( + name="phase", + unit="", + values=gen_name(qs), + is_components=True, + ) + + phase_list = array([0, 45, 90, 180]) * pi / 180 + for phase in phase_list: + In = zeros((Nt, qs)) + for ii in range(qs): + In[:, ii] = cos(angle_elec + phase + rot_dir * 2 * ii * pi / qs) + + Idqh_rms = n2dqh(In, angle_elec, is_dqh_rms=True) + Idqh_amp = n2dqh(In, angle_elec, is_dqh_rms=False) + In_check_rms = dqh2n(Idqh_rms, angle_elec, n=qs, is_n_rms=False) + In_check_amp = dqh2n(Idqh_amp, angle_elec, n=qs, is_n_rms=True) + + assert_array_almost_equal( + mean(Idqh_rms, axis=0), + array([cos(phase) / sqrt(2), sin(phase) / sqrt(2), 0]), + ) + + assert_array_almost_equal(Idqh_rms * sqrt(2), Idqh_amp) + + assert_array_almost_equal(In, In_check_rms) + assert_array_almost_equal(In, In_check_amp) + + In_data = DataTime( + name="Stator current", + unit="A", + symbol="I_s", + axes=[Time, Phase], + values=In, + ) + + I_dqh_data = n2dqh_DataTime(In_data) + In_data_check = dqh2n_DataTime(I_dqh_data, n=qs) + + if is_show_fig: + In_data.plot_2D_Data("time", "phase[]") + I_dqh_data.plot_2D_Data("time", "phase[]") - th_90 = pi / 2 - th_180 = pi + pass - X_dq90 = array([[0, -1], [1, 0]]) - X_dq90_wrong = array([[0, -1]]) - X_dq180 = array([[-1, 0], [0, -1]]) - assert_array_almost_equal(ab2n(n2ab(X_uvw)), X_uvw) - assert_array_almost_equal(dq2ab(ab2dq(X_ab, 0), 0), X_ab) - assert_array_almost_equal(dq2ab(ab2dq(X_ab, th_90), th_90), X_ab) +if __name__ == "__main__": - assert_array_almost_equal(ab2dq(X_ab, th_90), X_dq90) - assert_array_almost_equal(ab2dq(X_ab, th_180), X_dq180) + for param_dict in param_list: + test_coordinate_transformation(param_dict) - assert_array_almost_equal(ab2dq(X_ab_wrong, th_90), X_dq90_wrong) + # test_coordinate_transformation(param_list[2]) diff --git a/pyleecan/Functions/Electrical/coordinate_transformation.py b/pyleecan/Functions/Electrical/coordinate_transformation.py index f04f9043a..4d0191a6f 100644 --- a/pyleecan/Functions/Electrical/coordinate_transformation.py +++ b/pyleecan/Functions/Electrical/coordinate_transformation.py @@ -1,210 +1,362 @@ -from numpy import ( - array, - matmul, - sqrt, - cos, - sin, - reshape, - newaxis, - finfo, - log10, - floor, - pi, - linspace, - column_stack, - vstack, -) - -EPS = int(floor(-log10(finfo(float).eps))) - -# TODO: add homopolar component - - -def ab2n(Z_ab, n=3, rot_dir=-1): +import numpy as np + +from SciDataTool import Data1D, DataTime + +from ...Functions.Winding.gen_phase_list import gen_name + + +def n2dqh_DataTime(data_n, is_dqh_rms=True): + """n phases to dqh equivalent coordinate transformation of DataTime object + + Parameters + ---------- + data_n : DataTime + data object containing values over time and phase axes + is_dqh_rms : boolean + True to return dq currents in rms value (Pyleecan convention), False to return peak values + + Returns + ------- + data_dqh : DataTime + data object transformed in dqh frame + """ - 2 phase equivalent to n phase coordinate transformation, i.e. Clarke transformation + + if not isinstance(data_n, DataTime): + raise Exception("Input object should be a DataTime object") + if len(data_n.axes) != 2: + raise Exception("DataTime object should contain two axes: time and phase") + if data_n.axes[0].name != "time": + raise Exception("DataTime object should contain time as first axis") + if data_n.axes[1].name != "phase": + raise Exception("DataTime object should contain phase as second axis") + + # Get values for one time period converted in electrical angle and for all phases + result = data_n.get_along("time[oneperiod]->angle_elec", "phase") + data_n_val = result[data_n.symbol] + angle_elec = result["time"] + + # Convert values to dqh frame + data_dqh_val = n2dqh(data_n_val, angle_elec, is_dqh_rms=is_dqh_rms) + + # Get time axis on one period + per_t, is_aper_t = data_n.axes[0].get_periodicity() + per_t = int(per_t / 2) if is_aper_t else per_t + Time = data_n.axes[0].get_axis_periodic(per_t, is_aper=False) + + # Create DQH axis + axis_dq = Data1D( + name="phase", + unit="", + values=["direct", "quadrature", "homopolar"], + is_components=True, + ) + + # Get normalizations + if data_n.normalizations is None or len(data_n.normalizations) == 0: + normalizations = dict() + else: + normalizations = data_n.normalizations.copy() + + # Create DataTime object in dqh frame + data_dqh = DataTime( + name=data_n.name + " in DQH frame", + unit=data_n.unit, + symbol=data_n.symbol, + values=data_dqh_val, + axes=[Time, axis_dq], + normalizations=normalizations, + is_real=data_n.is_real, + ) + + return data_dqh + + +def dqh2n_DataTime(data_dqh, n, is_n_rms=False): + """dqh to n phase coordinate transformation of DataTime object Parameters ---------- - Z_ab : ndarray - matrix (N x 2) of 2 phase equivalent values - n : integer + data_dqh : DataTime + data object containing values over time in dqh frame + n: int number of phases - rot_dir : integer - rotation direction of the fundamental of magnetic field (rot_dir = +/- 1) + is_n_rms : boolean + True to return n currents in rms value, False to return peak values (Pyleecan convention) Returns ------- + data_n : DataTime + data object containing values over time and phase axes + """ + + if not isinstance(data_dqh, DataTime): + raise Exception("Input object should be a DataTime object") + if len(data_dqh.axes) != 2: + raise Exception("DataTime object should contain two axes: time and phase") + if data_dqh.axes[0].name != "time": + raise Exception("DataTime object should contain time as first axis") + if data_dqh.axes[1].name != "phase": + raise Exception("DataTime object should contain phase as second axis") + + # Get values for one time period converted in electrical angle and for all phases + result = data_dqh.get_along("time[oneperiod]->angle_elec", "phase") + data_dqh_val = result[data_dqh.symbol] + angle_elec = result["time"] + + # Convert values to dqh frame + data_n_val = dqh2n(data_dqh_val, angle_elec, n=n, is_n_rms=is_n_rms) + + # Get time axis on one period + per_t, is_aper_t = data_dqh.axes[0].get_periodicity() + per_t = int(per_t / 2) if is_aper_t else per_t + Time = data_dqh.axes[0].get_axis_periodic(per_t, is_aper=False) + + # Create DQH axis + Phase = Data1D( + name="phase", + unit="", + values=gen_name(n), + is_components=True, + ) + + # Get normalizations + if data_dqh.normalizations is None or len(data_dqh.normalizations) == 0: + normalizations = dict() + else: + normalizations = data_dqh.normalizations.copy() + + # Create DataTime object in dqh frame + data_n = DataTime( + name=data_dqh.name.replace(" in DQH frame", ""), + unit=data_dqh.unit, + symbol=data_dqh.symbol, + values=data_n_val, + axes=[Time, Phase], + normalizations=normalizations, + is_real=data_dqh.is_real, + ) + + return data_n + + +def n2dqh(Z_n, angle_elec, is_dqh_rms=True): + """n phases to dqh equivalent coordinate transformation + + Parameters + ---------- Z_n : ndarray - transformed matrix (N x n) of n phase values + matrix (N x n) of n phases values + angle_elec : ndarray + angle of the rotor coordinate system + is_dqh_rms : boolean + True to return dq currents in rms value (Pyleecan convention), False to return peak values + Returns + ------- + Z_dqh : ndarray + transformed matrix (N x 3) of dqh equivalent values """ - ii = linspace(0, n - 1, n) - alpha = ( - rot_dir * 2 * ii * pi / n - ) # Phasor depending on fundamental field rotation direction - # Transformation matrix - ab_2_n = vstack((cos(alpha).round(decimals=EPS), -sin(alpha).round(decimals=EPS))) + if angle_elec.size > 1: + # Get rotating direction by looking at sign of angle_elec derivate + rot_dir = np.mean(np.sign(np.diff(angle_elec))) + else: + # Default rotating direction in pyleecan + rot_dir = -1 - Z_n = matmul(Z_ab, ab_2_n) + Z_dqh = abc2dqh(n2abc(Z_n, rot_dir), angle_elec) - return Z_n + if is_dqh_rms: + # Divide by sqrt(2) to go from (Id_peak, Iq_peak) to (Id_rms, Iq_rms) + Z_dqh = Z_dqh / np.sqrt(2) + + return Z_dqh -def n2ab(Z_n, n=3, rot_dir=-1): - """n phase to 2 phase equivalent coordinate transformation, i.e. Clarke transformation +def dqh2n(Z_dqh, angle_elec, n, is_n_rms=False): + """dqh to n phase coordinate transformation Parameters ---------- + Z_dqh : ndarray + matrix (N x 3) of dqh phase values + angle_elec : ndarray + angle of the rotor coordinate system + n: int + number of phases + is_n_rms : boolean + True to return n currents in rms value, False to return peak values (Pyleecan convention) + + Returns + ------- Z_n : ndarray - matrix (N x n) of n phase values - n : integer + transformed matrix (N x n) of n phase values + """ + + if angle_elec.size > 1: + # Get rotating direction by looking at sign of angle_elec derivate + rot_dir = np.mean(np.sign(np.diff(angle_elec))) + else: + # Default rotating direction in pyleecan + rot_dir = -1 + + Z_n = abc2n(dqh2abc(Z_dqh, angle_elec), n, rot_dir) + + if not is_n_rms: + # Multiply by sqrt(2) to from (I_n_rms) to (I_n_peak) + Z_n = Z_n * np.sqrt(2) + + return Z_n + + +def abc2n(Z_abc, n=3, rot_dir=-1): + """3 phase equivalent to n phase coordinate transformation, i.e. Clarke transformation + + Parameters + ---------- + Z_abc : ndarray + matrix (N x 3) of 3 phase equivalent values in alpha-beta-gamma frame + n: int number of phases rot_dir : integer rotation direction of the fundamental of magnetic field (rot_dir = +/- 1) Returns ------- - Z_ab : ndarray - transformed matrix (N x 2) of 2 phase equivalent values + Z_n : ndarray + transformed matrix (N x n) of n phase values """ - ii = linspace(0, n - 1, n) - alpha = ( - rot_dir * 2 * ii * pi / n - ) # Phasor depending on fundamental field rotation direction - - # Transformation matrix - n_2_ab = ( - 2 - / n - * column_stack( - (cos(alpha).round(decimals=EPS), -sin(alpha).round(decimals=EPS)) - ) - ) - Z_ab = matmul(Z_n, n_2_ab) + # Inverse of Clarke transformation matrix + ab_2_n = comp_Clarke_transform(n, rot_dir, is_inv=True) - return Z_ab + Z_n = np.matmul(Z_abc, ab_2_n) + return Z_n -def ab2dq(Z_ab, theta): - """ - alpha-beta to dq coordinate transformation - NOTE: sin/cos values are rounded to avoid numerical errors + +def n2abc(Z_n, rot_dir=-1): + """n phase to 3 phases equivalent coordinate transformation, i.e. Clarke transformation Parameters ---------- - Z_ab : ndarray - matrix (N x 2) of alpha-beta - reference frame values - theta : ndarray - angle of the rotor coordinate system + Z_n : ndarray + matrix (N x n) of n phase values + rot_dir : integer + rotation direction of the fundamental of magnetic field (rot_dir = +/- 1) Returns ------- - Z_dq : ndarray - transformed (dq) values + Z_abc : ndarray + transformed matrix (N x 3) of 3 phases equivalent values (alpha-beta-gamma) """ - if len(Z_ab.shape) == 1: - Z_ab = Z_ab[newaxis, :] - sin_theta = sin(theta).round(decimals=EPS) - cos_theta = cos(theta).round(decimals=EPS) + n = Z_n.shape[1] - Z_d = Z_ab[:, 0] * cos_theta + Z_ab[:, 1] * sin_theta - Z_q = -Z_ab[:, 0] * sin_theta + Z_ab[:, 1] * cos_theta + n_2_abc = comp_Clarke_transform(n, rot_dir, is_inv=False) - return reshape([Z_d, Z_q], (2, -1)).transpose() + Z_abc = np.matmul(Z_n, n_2_abc) + return Z_abc -def dq2ab(Z_dq, theta): - """ - dq to alpha-beta coordinate transformation - NOTE: sin/cos values are rounded to avoid numerical errors + +def abc2dqh(Z_abc, angle_elec): + """alpha-beta-gamma to dqh coordinate transformation Parameters ---------- - Z_dq : ndarray - matrix (N x 2) of dq - reference frame values - theta : ndarray + Z_abc : ndarray + matrix (N x 3) of alpha-beta-gamma - reference frame values + angle_elec : ndarray angle of the rotor coordinate system Returns ------- - Z_ab : ndarray - transformed array + Z_dqh : ndarray + transformed (dqh) values """ - if len(Z_dq.shape) == 1: - Z_dq = Z_dq[newaxis, :] - sin_theta = sin(theta).round(decimals=EPS) - cos_theta = cos(theta).round(decimals=EPS) + if Z_abc.ndim == 1: + Z_abc = Z_abc[None, :] + + sin_angle_elec = np.sin(angle_elec) + cos_angle_elec = np.cos(angle_elec) - # Multiply by sqrt(2) to go from (Id_rms, Iq_rms) in to I_ab in amplitude - Z_a = Z_dq[:, 0] * cos_theta - Z_dq[:, 1] * sin_theta - Z_b = Z_dq[:, 0] * sin_theta + Z_dq[:, 1] * cos_theta + Z_dqh = np.zeros((angle_elec.size, 3)) - return reshape([Z_a, Z_b], (2, -1)).transpose() + # d-axis + Z_dqh[:, 0] = Z_abc[:, 0] * cos_angle_elec + Z_abc[:, 1] * sin_angle_elec + # q-axis + Z_dqh[:, 1] = -Z_abc[:, 0] * sin_angle_elec + Z_abc[:, 1] * cos_angle_elec + # Homopolar axis + Z_dqh[:, 2] = Z_abc[:, 2] + return Z_dqh -def n2dq(Z_n, theta, n=3, rot_dir=-1, is_dq_rms=True): - """n phase to dq equivalent coordinate transformation + +def dqh2abc(Z_dqh, angle_elec): + """dqh to alpha-beta-gamma coordinate transformation Parameters ---------- - Z_n : ndarray - matrix (N x n) of n phase values - n : integer - number of phases - rot_dir : integer - rotation direction of the fundamental of magnetic field (rot_dir = +/- 1) - is_dq_rms : boolean - True to return dq currents in rms value (Pyleecan convention), False to return peak values + Z_dqh : ndarray + matrix (N x 3) of dqh - reference frame values + angle_elec : ndarray + angle of the rotor coordinate system Returns ------- - Z_dq : ndarray - transformed matrix (N x 2) of dq equivalent values + Z_abc : ndarray + transformed array """ - Z_dq = ab2dq(n2ab(Z_n, n=n, rot_dir=rot_dir), theta) + if Z_dqh.ndim == 1: + Z_dqh = Z_dqh[None, :] - if is_dq_rms == True: - # Divide by sqrt(2) to go from (Id_peak, Iq_peak) to (Id_rms, Iq_rms) - Z_dq = Z_dq / sqrt(2) + sin_angle_elec = np.sin(angle_elec) + cos_angle_elec = np.cos(angle_elec) + + Z_abc = np.zeros((angle_elec.size, 3)) + + Z_abc[:, 0] = Z_dqh[:, 0] * cos_angle_elec - Z_dqh[:, 1] * sin_angle_elec + Z_abc[:, 1] = Z_dqh[:, 0] * sin_angle_elec + Z_dqh[:, 1] * cos_angle_elec + Z_abc[:, 2] = Z_dqh[:, 2] - return Z_dq + return Z_abc -def dq2n(Z_dq, theta, n=3, rot_dir=-1, is_n_rms=False): - """n phase to dq equivalent coordinate transformation +def comp_Clarke_transform(n, rot_dir, is_inv=False): + """Compute Clarke transformation for given number of phases and rotating direction of phases Parameters ---------- - Z_dq : ndarray - matrix (N x 2) of dq phase values - n : integer + n : int number of phases rot_dir : integer rotation direction of the fundamental of magnetic field (rot_dir = +/- 1) - is_n_rms : boolean - True to return n currents in rms value, False to return peak values (Pyleecan convention) + is_inv: bool + False to return Clarke transform, True to return inverse of Clarke transform Returns ------- - Z_n : ndarray - transformed matrix (N x n) of n phase values + mat : ndarray + Clarke transform matrix of size (n, 3) """ - Z_n = ab2n(dq2ab(Z_dq, theta), n=n, rot_dir=rot_dir) + # Phasor depending on fundamental field rotation direction + ii = np.linspace(0, n - 1, n) + phasor = rot_dir * 2 * ii * np.pi / n - if is_n_rms == False: - # Multiply by sqrt(2) to from (I_n_rms) to (I_n_peak) - Z_n = Z_n * sqrt(2) + # Clarke transformation matrix + if is_inv: + mat = np.vstack((np.cos(phasor), -np.sin(phasor), np.ones(n))) + else: + mat = 2 / n * np.column_stack((np.cos(phasor), -np.sin(phasor), np.ones(n) / 2)) - return Z_n + return mat diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py index 7f86100c3..8a0043001 100644 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py @@ -1,9 +1,7 @@ -# -*- coding: utf-8 -*- from numpy import pi, linspace, zeros, ones, dot, squeeze from SciDataTool import Data1D, DataTime, Norm_ref from ....Functions.Electrical.coordinate_transformation import dq2n from ....Functions.Winding.gen_phase_list import gen_name -from pyleecan.Classes.Winding import Winding def comp_mmf_unit(self, Na=None, Nt=None, freq=1): diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py index 26a217be7..c76dcbf38 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from numpy import pi, linspace, zeros, ones, dot, squeeze from SciDataTool import Data1D, DataTime, Norm_ref -from ....Functions.Electrical.coordinate_transformation import dq2n +from ....Functions.Electrical.coordinate_transformation import dqh2n from ....Functions.Winding.gen_phase_list import gen_name from pyleecan.Classes.Winding import Winding @@ -50,10 +50,10 @@ def comp_mmf_unit(self, Na=None, Nt=None, freq=1): else: wf = self.comp_wind_function(angle=angle, per_a=per_a) - # Compute unit current function of time applying constant Id=1 Arms, Iq=0 - Idq = zeros((Nt, 2)) + # Compute unit current function of time applying constant Id=1 Arms, Iq=0, Ih=0 + Idq = zeros((Nt, 3)) Idq[:, 0] = ones(Nt) - I = dq2n(Idq, 2 * pi * freq * time, n=qs, is_n_rms=False) + I = dqh2n(Idq, 2 * pi * freq * time, n=qs, is_n_rms=False) # Compute unit mmf mmf_u = squeeze(dot(I, wf)) diff --git a/pyleecan/Methods/Machine/Lamination/get_name_phase.py b/pyleecan/Methods/Machine/Lamination/get_name_phase.py index 396ac5fe1..e2fed8fc9 100644 --- a/pyleecan/Methods/Machine/Lamination/get_name_phase.py +++ b/pyleecan/Methods/Machine/Lamination/get_name_phase.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ....Functions.Winding.gen_phase_list import gen_name diff --git a/pyleecan/Methods/Output/OutElec/get_I_fund.py b/pyleecan/Methods/Output/OutElec/get_I_fund.py index 329316618..685d6e90d 100644 --- a/pyleecan/Methods/Output/OutElec/get_I_fund.py +++ b/pyleecan/Methods/Output/OutElec/get_I_fund.py @@ -1,9 +1,8 @@ -from numpy import pi, array, where, isclose, zeros +from numpy import array, where, isclose, zeros from SciDataTool import Data1D, DataTime, DataFreq -from ....Functions.Electrical.coordinate_transformation import dq2n -from ....Functions.Winding.gen_phase_list import gen_name +from ....Functions.Electrical.coordinate_transformation import dqh2n def get_I_fund(self, Time=None): @@ -17,16 +16,11 @@ def get_I_fund(self, Time=None): """ if Time is None: Time = self.axes_dict["time"] - time = Time.get_values(is_smallestperiod=True) + angle_elec = Time.get_values(is_smallestperiod=True, normalization="angle_elec") qs = self.parent.simu.machine.stator.winding.qs felec = self.felec - Phase = Data1D( - name="phase", - unit="", - values=gen_name(qs), - is_components=True, - ) + Phase = self.axes_dict["phase_S"] if self.Is is None: if ( @@ -35,18 +29,15 @@ def get_I_fund(self, Time=None): and self.Id_ref != 0 and self.Iq_ref != 0 ): - # Generate current according to Id/Iq - Isdq = array([self.Id_ref, self.Iq_ref]) - - # Get rotation direction of the fundamental magnetic field created by the winding - rot_dir = self.parent.get_rot_dir() + # Generate current according to Id/Iq, Ih=0 + Is_dqh = zeros((angle_elec.size, 3)) + Is_dqh[:, 0] = self.Id_ref + Is_dqh[:, 1] = self.Iq_ref # Get stator current function of time - Is = dq2n( - Isdq, 2 * pi * felec * time, n=qs, rot_dir=rot_dir, is_n_rms=False - ) + Is = dqh2n(Is_dqh, angle_elec, n=qs, is_n_rms=False) else: - Is = zeros((time.size, qs)) + Is = zeros((angle_elec.size, qs)) I_fund = DataTime( name="Stator current", @@ -58,7 +49,7 @@ def get_I_fund(self, Time=None): else: result = self.Is.get_along("freqs", "phase") - Is_val = result["I_s"] + Is_val = result[self.Is.symbol] freqs = result["freqs"] ifund = where(isclose(freqs, felec)) Is_fund = Is_val[ifund, :] diff --git a/pyleecan/Methods/Output/OutElec/get_Us.py b/pyleecan/Methods/Output/OutElec/get_Us.py index f3dda2e9a..0aad2ac2c 100644 --- a/pyleecan/Methods/Output/OutElec/get_Us.py +++ b/pyleecan/Methods/Output/OutElec/get_Us.py @@ -1,4 +1,4 @@ -from ....Functions.Electrical.coordinate_transformation import dq2n +from ....Functions.Electrical.coordinate_transformation import dqh2n from numpy import pi, array, transpose from SciDataTool import Data1D, DataTime from ....Functions.Winding.gen_phase_list import gen_name @@ -14,7 +14,7 @@ def get_Us(self): felec = self.felec # add stator current - Us = dq2n(Usdq, 2 * pi * felec * time, n=qs) + Us = dqh2n(Usdq, 2 * pi * felec * time, n=qs) Phase = Data1D( name="phase", unit="", diff --git a/pyleecan/Methods/Output/OutMag/comp_emf.py b/pyleecan/Methods/Output/OutMag/comp_emf.py index cc9696049..c41681898 100644 --- a/pyleecan/Methods/Output/OutMag/comp_emf.py +++ b/pyleecan/Methods/Output/OutMag/comp_emf.py @@ -1,6 +1,6 @@ from numpy import diff, zeros, newaxis, pi -from ....Functions.Electrical.coordinate_transformation import n2dq +from ....Functions.Electrical.coordinate_transformation import n2dqh def comp_emf(self, is_dq=False): @@ -49,12 +49,8 @@ def comp_emf(self, is_dq=False): output = self.parent qs = output.simu.machine.stator.winding.qs felec = output.elec.felec - # Get rotation direction of the fundamental magnetic field created by the winding - rot_dir = output.get_rot_dir() # Get stator current function of time - emf = n2dq( - emf, 2 * pi * felec * time, n=qs, rot_dir=rot_dir, is_dq_rms=True - ) + emf = n2dqh(emf, 2 * pi * felec * time, is_dq_rms=True) EMF = Phi_wind.copy() EMF.values = emf diff --git a/pyleecan/Methods/Simulation/EEC_LSRPM/gen_drive.py b/pyleecan/Methods/Simulation/EEC_LSRPM/gen_drive.py index b43d04f85..087921e6c 100644 --- a/pyleecan/Methods/Simulation/EEC_LSRPM/gen_drive.py +++ b/pyleecan/Methods/Simulation/EEC_LSRPM/gen_drive.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from ....Functions.Electrical.coordinate_transformation import n2dq +from ....Functions.Electrical.coordinate_transformation import n2dqh from numpy import split, transpose, mean, pi import matplotlib.pyplot as plt diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/gen_drive.py b/pyleecan/Methods/Simulation/EEC_PMSM/gen_drive.py index 22945aa89..56bdf55be 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/gen_drive.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/gen_drive.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from ....Functions.Electrical.coordinate_transformation import n2dq +from ....Functions.Electrical.coordinate_transformation import n2dqh from numpy import split, transpose, mean, pi import matplotlib.pyplot as plt @@ -26,9 +26,7 @@ def gen_drive(self, output): # d,q transform voltage = Voltage.values - voltage_dq = split( - n2dq(transpose(voltage), -2 * pi * felec * time, n=qs), 2, axis=1 - ) + voltage_dq = split(n2dqh(transpose(voltage), -2 * pi * felec * time), 2, axis=1) fig = plt.figure() plt.plot(time[:50], voltage[0, :50], color="tab:blue", label="A") diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py b/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py index f89b5a205..fee0e203b 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from numpy import mean, zeros -from ....Functions.Electrical.coordinate_transformation import n2ab +from ....Functions.Electrical.coordinate_transformation import n2abc def comp_joule_losses(self, output): @@ -36,7 +36,7 @@ def comp_joule_losses(self, output): for ii in range(p): id0 = qr_eff * ii id1 = qr_eff * (ii + 1) - Ir_2ph += n2ab(Ir[:, id0:id1], n=qr_eff) / p + Ir_2ph += n2abc(Ir[:, id0:id1], n=qr_eff) / p Ir_mag = abs(Ir_2ph[:, 0] + 1j * Ir_2ph[:, 1]) diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py index 45dfdbe60..b45ebd440 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py @@ -1,7 +1,7 @@ from numpy import zeros, sqrt, pi, tile, isnan from multiprocessing import cpu_count -from ....Functions.Electrical.coordinate_transformation import n2ab, ab2n +from ....Functions.Electrical.coordinate_transformation import n2abc, abc2n from ....Functions.labels import STATOR_LAB, ROTOR_LAB from ....Functions.load import import_class @@ -208,13 +208,13 @@ def _comp_flux_mean(self, out): for ii in range(p): id0 = qsr_per_pole * ii id1 = qsr_per_pole * (ii + 1) - Phi_ab += n2ab(Phi[:, id0:id1], n=qsr_per_pole) / p + Phi_ab += n2abc(Phi[:, id0:id1], n=qsr_per_pole) / p else: logger.warning(f"{type(self).__name__}: " + "Not Implemented Yet") # compute rotor and stator flux linkage Phi_r = abs(Phi_ab[:, 0] + 1j * Phi_ab[:, 1]).mean() / sqrt(2) - Phi_ab = n2ab( + Phi_ab = n2abc( out.mag.Phi_wind[STATOR_LAB + "-0"].get_along("time", "phase")["Phi_{wind}"] ) Phi_s = abs(Phi_ab[:, 0] + 1j * Phi_ab[:, 1]).mean() / sqrt(2) @@ -305,7 +305,7 @@ def _comp_Lm_FEA(self): # set current values Ir_ = zeros([self.Nt_tot, 2]) Ir_[:, 0] = self.I * K21Z * sqrt(2) - Ir = ab2n(Ir_, n=qsr // p) # TODO no rotation for now + Ir = abc2n(Ir_, n=qsr // p) # TODO no rotation for now Ir = ImportMatrixVal(value=tile(Ir, (1, p))) diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/gen_drive.py b/pyleecan/Methods/Simulation/EEC_SCIM/gen_drive.py index 76e01e345..77ce5ffda 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/gen_drive.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/gen_drive.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from ....Functions.Electrical.coordinate_transformation import n2dq +from ....Functions.Electrical.coordinate_transformation import n2dqh from numpy import split, transpose, mean, pi import matplotlib.pyplot as plt @@ -30,7 +30,7 @@ def gen_drive(self, output): # d,q transform voltage = Voltage.values voltage_dq = split( - n2dq(transpose(voltage), -2 * pi * felec * time, n=qs), 2, axis=1 + n2dqh(transpose(voltage), -2 * pi * felec * time, n=qs), 2, axis=1 ) fig = plt.figure() diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC.py b/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC.py index 0e3619168..115d4ba49 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/solve_EEC.py @@ -84,7 +84,7 @@ def solve_EEC(self, output): # # compute actual rotor bar currents # # TODO fix: initial rotor pos. is disregarded for now - # Ir = dq2n(Ir_, w_slip * time, n=qsr // sym, rot_dir=rot_dir, is_n_rms=False) + # Ir = dqh2n(Ir_, w_slip * time, n=qsr // sym, rot_dir=rot_dir, is_n_rms=False) # Ir = tile(Ir, (1, sym)) # Phase = Data1D( From 59ca5e0f69d95d12ed14f62d75e7b5dc9e05f0db Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Mon, 11 Oct 2021 11:14:21 +0200 Subject: [PATCH 080/167] [WiP] Modifications in ELUT_PMSM to prepare interpolation of phi_dqh --- .../test_coordinate_transformation.py | 9 ++- .../Electrical/coordinate_transformation.py | 4 ++ .../ClassesRef/Simulation/ELUT_PMSM.csv | 13 +++-- pyleecan/Methods/Output/OutMag/comp_emf.py | 56 ++++--------------- 4 files changed, 28 insertions(+), 54 deletions(-) diff --git a/Tests/Functions/test_coordinate_transformation.py b/Tests/Functions/test_coordinate_transformation.py index 38b47bda1..ea021f5f0 100644 --- a/Tests/Functions/test_coordinate_transformation.py +++ b/Tests/Functions/test_coordinate_transformation.py @@ -35,7 +35,7 @@ def test_coordinate_transformation(param_dict): rot_dir = param_dict["rot_dir"] time = linspace(0, 1 / f_elec, Nt, endpoint=False) - angle_elec = rot_dir * 2 * pi * time + angle_elec = rot_dir * 2 * pi * f_elec * time # Time axis for plots norm_time = { @@ -45,6 +45,9 @@ def test_coordinate_transformation(param_dict): } Time = Data1D(name="time", unit="s", values=time, normalizations=norm_time) + # TODO: test angle_elec + angle_elec_bis = Time.get_values(is_smallestperiod=True, normalization="angle_elec") + # Phase axis for plots Phase = Data1D( name="phase", @@ -53,7 +56,7 @@ def test_coordinate_transformation(param_dict): is_components=True, ) - phase_list = array([0, 45, 90, 180]) * pi / 180 + phase_list = array([0, 45, 90, 180, 270]) * pi / 180 for phase in phase_list: In = zeros((Nt, qs)) for ii in range(qs): @@ -82,6 +85,8 @@ def test_coordinate_transformation(param_dict): values=In, ) + In_data.plot_2D_Data("time", "phase[]") + I_dqh_data = n2dqh_DataTime(In_data) In_data_check = dqh2n_DataTime(I_dqh_data, n=qs) diff --git a/pyleecan/Functions/Electrical/coordinate_transformation.py b/pyleecan/Functions/Electrical/coordinate_transformation.py index 4d0191a6f..93d2d6918 100644 --- a/pyleecan/Functions/Electrical/coordinate_transformation.py +++ b/pyleecan/Functions/Electrical/coordinate_transformation.py @@ -31,6 +31,8 @@ def n2dqh_DataTime(data_n, is_dqh_rms=True): if data_n.axes[1].name != "phase": raise Exception("DataTime object should contain phase as second axis") + # TODO: add check on angle_elec normalization + # Get values for one time period converted in electrical angle and for all phases result = data_n.get_along("time[oneperiod]->angle_elec", "phase") data_n_val = result[data_n.symbol] @@ -99,6 +101,8 @@ def dqh2n_DataTime(data_dqh, n, is_n_rms=False): if data_dqh.axes[1].name != "phase": raise Exception("DataTime object should contain phase as second axis") + # TODO: add check on angle_elec normalization + # Get values for one time period converted in electrical angle and for all phases result = data_dqh.get_along("time[oneperiod]->angle_elec", "phase") data_dqh_val = result[data_dqh.symbol] diff --git a/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv b/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv index a851848c4..f374d5a5a 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv @@ -2,12 +2,13 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximu Phi_dqh_mean,Wbrms,RMS stator winding flux table in dqh frame (including magnets and currents given by I_dqh),"(N_dq, 3)",ndarray,None,,,,Simulation,ELUT,get_param_dict,VERSION,1,ELUT class for PMSM, I_dqh,Arms,RMS Id Iq Ih table corresponding to flux linkage table given in Phi_dqh_mean,"(N_dq, 3)",list,None,,,,,,get_Lq,,,, Tmag_ref,degC,Magnet average temperature at which Phi_dqh is given,,float,20,,,,,,get_bemf,,,, -Phi_dqh_mag,Wbrms,RMS stator winding flux linkage in dqh frame including harmonics (only magnets),"(Nharm, 3)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Ld,,,, +Phi_dqh_mag,Wbrms,RMS stator winding flux linkage spectrum in dqh frame including harmonics (only magnets),"(Nharm, 3)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Ld,,,, Phi_wind,Wb,Stator winding flux function of time and phases,"(Nt_tot, qs)",[SciDataTool.Classes.DataND.DataND],None,,,,,,get_Lmd,,,, -,,,,,,,,,,,get_Lmq,,,, -,,,,,,,,,,,comp_Ldqh_from_Phidqh,,,, +Phi_dqh_interp,,Interpolant function of Phi_dqh,,scipy.interp,None,,,,,,get_Lmq,,,, ,,,,,,,,,,,import_from_data,,,, -,,,,,,,,,,,comp_Phidqh_from_Phiwind,,,, -,,,,,,,,,,,get_Phid_mag_mean,,,, -,,,,,,,,,,,get_Phid_mag_harm,,,, +,,,,,,,,,,,get_Phidqh_mean,,,, +,,,,,,,,,,,get_Phidqh_mag,,,, +,,,,,,,,,,,get_Phidqh_mag_mean,,,, +,,,,,,,,,,,get_Phidqh_mag_harm,,,, ,,,,,,,,,,,get_orders_dqh,,,, +,,,,,,,,,,,interp_Phi_dqh,,,, diff --git a/pyleecan/Methods/Output/OutMag/comp_emf.py b/pyleecan/Methods/Output/OutMag/comp_emf.py index c41681898..412ff8aa9 100644 --- a/pyleecan/Methods/Output/OutMag/comp_emf.py +++ b/pyleecan/Methods/Output/OutMag/comp_emf.py @@ -3,59 +3,23 @@ from ....Functions.Electrical.coordinate_transformation import n2dqh -def comp_emf(self, is_dq=False): +def comp_emf(self): """Compute the Electromotive force [V] Parameters ---------- self : OutMag an OutMag object - is_dq : bool - rotate to dq axes if true """ # Get stator winding flux - Phi_wind = self.Phi_wind_stator - result = Phi_wind.get_along("time[smallestperiod]", "phase") - phi_wind = result[Phi_wind.symbol] - time = result["time"] - - # Get time axis - for axe in Phi_wind.axes: - if axe.name == "time": - Time = axe - - # Get time values on the smallest period - _, is_antiper_t = Time.get_periodicity() - - # Calculate EMF and store it in OutMag - if time.size > 1: - emf = zeros(phi_wind.shape) - emf[:-1, :] = diff(phi_wind, 1, 0) / diff(time, 1, 0)[:, newaxis] - # We assume phi_wind to be periodic to compute the last value - # and we assume time to be a linspace - if is_antiper_t: - sign0 = ( - -1 - ) # The opposite of the first value of the anti-period is after the last value of the anti-period - else: - sign0 = ( - 1 # The first value of the period is after the last value of the period - ) - emf[-1, :] = (sign0 * phi_wind[0, :] - phi_wind[-1, :]) / (time[1] - time[0]) - - if is_dq: - output = self.parent - qs = output.simu.machine.stator.winding.qs - felec = output.elec.felec - # Get stator current function of time - emf = n2dqh(emf, 2 * pi * felec * time, is_dq_rms=True) - - EMF = Phi_wind.copy() - EMF.values = emf - EMF.name = "Stator Winding Electromotive Force" - EMF.unit = "V" - EMF.symbol = "EMF" - - self.emf = EMF + Phi_wind = self.Phi_wind[self.parent.simu.machine.stator.get_label()] + + EMF = Phi_wind.get_data_along("time=derivate", "phase") + + EMF.name = "Stator Winding Electromotive Force" + EMF.unit = "V" + EMF.symbol = "EMF" + + self.emf = EMF From 0374b8ea14ca4d7cb985777825ef775b3e6b645e Mon Sep 17 00:00:00 2001 From: Martin Glesser Date: Mon, 11 Oct 2021 14:09:18 +0200 Subject: [PATCH 081/167] [BC] better handling of time vector in ImportGenPWM --- pyleecan/Functions/Electrical/comp_PWM.py | 7 +++---- pyleecan/Methods/Import/ImportGenPWM/get_data.py | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pyleecan/Functions/Electrical/comp_PWM.py b/pyleecan/Functions/Electrical/comp_PWM.py index 26e62f67b..0b547fdec 100644 --- a/pyleecan/Functions/Electrical/comp_PWM.py +++ b/pyleecan/Functions/Electrical/comp_PWM.py @@ -122,7 +122,7 @@ def comp_volt_PWM_NUM( else: print("ERROR:only SPWM supports the variable switching frequency") elif fswimode == 2 or fswimode == 3: # Random fswi & Symmetrical random fswi - t1 = round(Tpwmu[-1] * 5000000) + t1 = round(Tpwmu[-1] * 5000000) # Nombre de points if fswimode == 3: num_slice = round((fswi_max + fswi) / 2 * Tpwmu[-1]) delta_fswi = np.random.randint( @@ -156,9 +156,8 @@ def comp_volt_PWM_NUM( else: fswi = fswi[:t1] - Tpwmu_10 = np.linspace( - 0, round(Tpwmu[-1]), round(Tpwmu[-1]) * 5000000, endpoint=True - ) + Tpwmu_10 = np.linspace(0, (t1 - 1) / 5000000, t1, endpoint=True) + np.linspace(0, (t1 - 1) / 5000000, t1, endpoint=True) carrier = integrate.cumtrapz(fswi, Tpwmu_10, initial=0) Aml_tri = max(carrier) carrier = carrier / Aml_tri * Vdc1 - Vdc1 / 2 * np.ones(np.size(Tpwmu_10)) diff --git a/pyleecan/Methods/Import/ImportGenPWM/get_data.py b/pyleecan/Methods/Import/ImportGenPWM/get_data.py index 8eca066b0..ff7451020 100644 --- a/pyleecan/Methods/Import/ImportGenPWM/get_data.py +++ b/pyleecan/Methods/Import/ImportGenPWM/get_data.py @@ -31,8 +31,8 @@ def get_data(self): Tpwmu : ndarray time vector """ - # Tpwmu=np.arange(fs*duration)/fs, - Tpwmu = np.linspace(0, self.duration, self.fs * self.duration, endpoint=True) + N = int(self.fs * self.duration) # Number of points + Tpwmu = np.linspace(0, (N - 1) / self.fs, N, endpoint=True) # Time vector v_pwm, Vas, MI, carrier = comp_volt_PWM_NUM( Tpwmu=Tpwmu, freq0=self.f, From acab1db93d71bc8d520f7df7d5036471c5f1a0f0 Mon Sep 17 00:00:00 2001 From: helene-t Date: Mon, 11 Oct 2021 15:07:55 +0200 Subject: [PATCH 082/167] [WiP] rework LUT architecture --- Tests/Methods/Simulation/test_Electrical.py | 6 + .../Electrical/test_EEC_ELUT_PMSM.py | 12 +- .../Electrical/test_EEC_ELUT_SCIM_001.py | 4 +- pyleecan/Classes/Class_Dict.json | 498 +++++++++--------- pyleecan/Classes/Electrical.py | 8 +- pyleecan/Classes/{ELUT.py => LUT.py} | 50 +- pyleecan/Classes/{ELUT_PMSM.py => LUTdq.py} | 278 ++++++---- pyleecan/Classes/{ELUT_SCIM.py => LUTslip.py} | 106 ++-- pyleecan/Classes/{PostELUT.py => PostLUT.py} | 198 +++---- pyleecan/Classes/import_all.py | 8 +- pyleecan/Functions/load_switch.py | 8 +- .../Generator/ClassesRef/Post/PostELUT.csv | 4 - .../Generator/ClassesRef/Post/PostLUT.csv | 4 + .../ClassesRef/Simulation/Electrical.csv | 2 +- .../Simulation/{ELUT.csv => LUT.csv} | 2 +- .../Simulation/{ELUT_PMSM.csv => LUTdq.csv} | 2 +- .../Simulation/{ELUT_SCIM.csv => LUTslip.csv} | 2 +- .../Machine/LamSlotMultiWind/comp_mmf_unit.py | 10 +- .../Machine/LamSlotWind/comp_mmf_unit.py | 4 +- pyleecan/Methods/Post/PostELUT/run.py | 74 --- .../Post/{PostELUT => PostLUT}/__init__.py | 0 pyleecan/Methods/Post/PostLUT/run.py | 74 +++ .../ELUT_PMSM/comp_Ldqh_from_Phidqh.py | 3 - .../ELUT_PMSM/comp_Phidqh_from_Phiwind.py | 3 - .../Methods/Simulation/ELUT_PMSM/get_Ld.py | 3 - .../Methods/Simulation/ELUT_PMSM/get_Lmd.py | 3 - .../Methods/Simulation/ELUT_PMSM/get_Lmq.py | 3 - .../Methods/Simulation/ELUT_PMSM/get_Lq.py | 3 - .../Simulation/InputCurrent/gen_input.py | 2 +- .../Simulation/{ELUT => LUT}/__init__.py | 0 .../{ELUT => LUT}/get_param_dict.py | 0 .../{ELUT_PMSM => LUTdq}/__init__.py | 0 pyleecan/Methods/Simulation/LUTdq/get_Ld.py | 18 + pyleecan/Methods/Simulation/LUTdq/get_Lmd.py | 20 + pyleecan/Methods/Simulation/LUTdq/get_Lmq.py | 20 + pyleecan/Methods/Simulation/LUTdq/get_Lq.py | 18 + .../Simulation/LUTdq/get_Phidqh_mag.py | 3 + .../Simulation/LUTdq/get_Phidqh_mag_harm.py | 3 + .../Simulation/LUTdq/get_Phidqh_mag_mean.py | 3 + .../Simulation/LUTdq/get_Phidqh_mean.py | 3 + .../{ELUT_PMSM => LUTdq}/get_bemf.py | 4 +- .../Simulation/LUTdq/get_orders_dqh.py | 3 + .../{ELUT_PMSM => LUTdq}/get_param_dict.py | 4 +- .../{ELUT_PMSM => LUTdq}/import_from_data.py | 0 .../Simulation/LUTdq/interp_Phi_dqh.py | 3 + .../{ELUT_SCIM => LUTslip}/__init__.py | 0 .../comp_Lm_from_Phim.py | 0 .../{ELUT_SCIM => LUTslip}/get_Lm.py | 0 .../{ELUT_SCIM => LUTslip}/get_param_dict.py | 0 .../import_from_data.py | 0 50 files changed, 814 insertions(+), 662 deletions(-) rename pyleecan/Classes/{ELUT.py => LUT.py} (86%) rename pyleecan/Classes/{ELUT_PMSM.py => LUTdq.py} (65%) rename pyleecan/Classes/{ELUT_SCIM.py => LUTslip.py} (78%) rename pyleecan/Classes/{PostELUT.py => PostLUT.py} (53%) delete mode 100644 pyleecan/Generator/ClassesRef/Post/PostELUT.csv create mode 100644 pyleecan/Generator/ClassesRef/Post/PostLUT.csv rename pyleecan/Generator/ClassesRef/Simulation/{ELUT.csv => LUT.csv} (95%) rename pyleecan/Generator/ClassesRef/Simulation/{ELUT_PMSM.csv => LUTdq.csv} (92%) rename pyleecan/Generator/ClassesRef/Simulation/{ELUT_SCIM.csv => LUTslip.csv} (85%) delete mode 100644 pyleecan/Methods/Post/PostELUT/run.py rename pyleecan/Methods/Post/{PostELUT => PostLUT}/__init__.py (100%) create mode 100644 pyleecan/Methods/Post/PostLUT/run.py delete mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/comp_Ldqh_from_Phidqh.py delete mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/comp_Phidqh_from_Phiwind.py delete mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/get_Ld.py delete mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmd.py delete mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmq.py delete mode 100644 pyleecan/Methods/Simulation/ELUT_PMSM/get_Lq.py rename pyleecan/Methods/Simulation/{ELUT => LUT}/__init__.py (100%) rename pyleecan/Methods/Simulation/{ELUT => LUT}/get_param_dict.py (100%) rename pyleecan/Methods/Simulation/{ELUT_PMSM => LUTdq}/__init__.py (100%) create mode 100644 pyleecan/Methods/Simulation/LUTdq/get_Ld.py create mode 100644 pyleecan/Methods/Simulation/LUTdq/get_Lmd.py create mode 100644 pyleecan/Methods/Simulation/LUTdq/get_Lmq.py create mode 100644 pyleecan/Methods/Simulation/LUTdq/get_Lq.py create mode 100644 pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py create mode 100644 pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_harm.py create mode 100644 pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_mean.py create mode 100644 pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py rename pyleecan/Methods/Simulation/{ELUT_PMSM => LUTdq}/get_bemf.py (91%) create mode 100644 pyleecan/Methods/Simulation/LUTdq/get_orders_dqh.py rename pyleecan/Methods/Simulation/{ELUT_PMSM => LUTdq}/get_param_dict.py (94%) rename pyleecan/Methods/Simulation/{ELUT_PMSM => LUTdq}/import_from_data.py (100%) create mode 100644 pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py rename pyleecan/Methods/Simulation/{ELUT_SCIM => LUTslip}/__init__.py (100%) rename pyleecan/Methods/Simulation/{ELUT_SCIM => LUTslip}/comp_Lm_from_Phim.py (100%) rename pyleecan/Methods/Simulation/{ELUT_SCIM => LUTslip}/get_Lm.py (100%) rename pyleecan/Methods/Simulation/{ELUT_SCIM => LUTslip}/get_param_dict.py (100%) rename pyleecan/Methods/Simulation/{ELUT_SCIM => LUTslip}/import_from_data.py (100%) diff --git a/Tests/Methods/Simulation/test_Electrical.py b/Tests/Methods/Simulation/test_Electrical.py index 3a285a7d6..52661caed 100644 --- a/Tests/Methods/Simulation/test_Electrical.py +++ b/Tests/Methods/Simulation/test_Electrical.py @@ -52,3 +52,9 @@ def test_comp_rot_dir_reverse_wind(self, Toyota_Prius): ) rot_dir = IPMSM_B.stator.comp_rot_dir() assert rot_dir == 1 + + +if __name__ == "__main__": + a = Test_Electrical() + toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) + a.test_comp_rot_dir(toyota_Prius) diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py index cf66daf65..205d8777d 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py @@ -10,7 +10,7 @@ from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.VarLoadCurrent import VarLoadCurrent from pyleecan.Classes.MagFEMM import MagFEMM -from pyleecan.Classes.PostELUT import PostELUT +from pyleecan.Classes.PostLUT import PostLUT from pyleecan.Classes.DataKeeper import DataKeeper from pyleecan.Classes.ImportMatrixXls import ImportMatrixXls @@ -25,13 +25,13 @@ @pytest.mark.IPMSM @pytest.mark.periodicity @pytest.mark.skip(reason="Work in progress") -def test_ELUT_PMSM(): +def test_LUT_PMSM(): """Validation of the PMSM Electrical Equivalent Circuit with the Prius machine Compute Torque from EEC results and compare with Yang et al, 2013 """ Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) - simu = Simu1(name="test_ELUT_PMSM", machine=Toyota_Prius) + simu = Simu1(name="test_LUT_PMSM", machine=Toyota_Prius) # Definition of the input simu.input = InputCurrent( @@ -40,7 +40,7 @@ def test_ELUT_PMSM(): # Load OP_matrix OP_matrix = ImportMatrixXls( - file_path=join(TEST_DATA_DIR, "OP_ELUT_PMSM.xlsx"), sheet="Feuil1" + file_path=join(TEST_DATA_DIR, "OP_LUT_PMSM.xlsx"), sheet="Feuil1" ).get_data() # Set varspeed simulation @@ -63,7 +63,7 @@ def test_ELUT_PMSM(): simu.var_simu.datakeeper_list = [Phi_wind_dq_dk] # Postprocessing - simu.var_simu.postproc_list = [PostELUT()] + simu.var_simu.postproc_list = [PostLUT()] out = simu.run() @@ -81,4 +81,4 @@ def test_ELUT_PMSM(): # To run it without pytest if __name__ == "__main__": - out = test_ELUT_PMSM() + out = test_LUT_PMSM() diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py b/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py index 189d3dbca..c15fc66ed 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_SCIM_001.py @@ -10,7 +10,7 @@ from pyleecan.Classes.Electrical import Electrical from pyleecan.Classes.EEC_SCIM import EEC_SCIM -from pyleecan.Classes.ELUT_SCIM import ELUT_SCIM +from pyleecan.Classes.LUTslip import LUTslip from pyleecan.Classes.ImportMatrixVal import ImportMatrixVal from pyleecan.Classes.InputVoltage import InputVoltage from pyleecan.Classes.Output import Output @@ -101,7 +101,7 @@ def test_EEC_ELUT_SCIM_001(): slip_ref=param_dict["slip"], ) - ELUT_SCIM_001 = ELUT_SCIM( + ELUT_SCIM_001 = LUTslip( R1=param_dict["R1_20"], L1=param_dict["L10"], T1_ref=20, diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 3e260bad4..ceee4d940 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -1245,209 +1245,6 @@ } ] }, - "ELUT": { - "constants": [ - { - "name": "VERSION", - "value": "1" - } - ], - "daughters": [ - "ELUT_PMSM", - "ELUT_SCIM" - ], - "desc": "Abstract class for Electrical Look Up Table (ELUT)", - "is_internal": false, - "methods": [ - "get_param_dict" - ], - "mother": "", - "name": "ELUT", - "package": "Simulation", - "path": "pyleecan/Generator/ClassesRef/Simulation/ELUT.csv", - "properties": [ - { - "desc": "DC phase winding resistance at T1_ref per phase ", - "max": "", - "min": "", - "name": "R1", - "type": "float", - "unit": "Ohm", - "value": null - }, - { - "desc": "Phase winding leakage inductance ", - "max": "", - "min": "", - "name": "L1", - "type": "float", - "unit": "H", - "value": null - }, - { - "desc": "Stator winding average temperature associated to R1, L1 parameters", - "max": "", - "min": "", - "name": "T1_ref", - "type": "float", - "unit": "degC", - "value": 20 - }, - { - "desc": "Array of operating point values of size (N,5) with N the number of operating points (Speed, Id, Iq, Torque, Power). OP values are set to nan if they are not given.", - "max": "", - "min": "", - "name": "OP_matrix", - "type": "ndarray", - "unit": "", - "value": null - } - ] - }, - "ELUT_PMSM": { - "constants": [ - { - "name": "VERSION", - "value": "1" - } - ], - "daughters": [], - "desc": "ELUT class for PMSM", - "is_internal": false, - "methods": [ - "get_param_dict", - "get_Lq", - "get_bemf", - "get_Ld", - "get_Lmd", - "get_Lmq", - "comp_Ldqh_from_Phidqh", - "import_from_data", - "comp_Phidqh_from_Phiwind", - "get_Phid_mag_mean", - "get_Phid_mag_harm", - "get_orders_dqh" - ], - "mother": "ELUT", - "name": "ELUT_PMSM", - "package": "Simulation", - "path": "pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv", - "properties": [ - { - "desc": "RMS stator winding flux table in dqh frame (including magnets and currents given by I_dqh)", - "max": "", - "min": "", - "name": "Phi_dqh_mean", - "type": "ndarray", - "unit": "Wbrms", - "value": null - }, - { - "desc": "RMS Id Iq Ih table corresponding to flux linkage table given in Phi_dqh_mean", - "max": "", - "min": "", - "name": "I_dqh", - "type": "list", - "unit": "Arms", - "value": null - }, - { - "desc": "Magnet average temperature at which Phi_dqh is given", - "max": "", - "min": "", - "name": "Tmag_ref", - "type": "float", - "unit": "degC", - "value": 20 - }, - { - "desc": "RMS stator winding flux linkage in dqh frame including harmonics (only magnets)", - "max": "", - "min": "", - "name": "Phi_dqh_mag", - "type": "SciDataTool.Classes.DataND.DataND", - "unit": "Wbrms", - "value": "None" - }, - { - "desc": "Stator winding flux function of time and phases", - "max": "", - "min": "", - "name": "Phi_wind", - "type": "[SciDataTool.Classes.DataND.DataND]", - "unit": "Wb", - "value": "None" - } - ] - }, - "ELUT_SCIM": { - "constants": [ - { - "name": "VERSION", - "value": "1" - } - ], - "daughters": [], - "desc": "ELUT class for SCIM", - "is_internal": false, - "methods": [ - "get_param_dict", - "get_Lm", - "comp_Lm_from_Phim", - "import_from_data" - ], - "mother": "ELUT", - "name": "ELUT_SCIM", - "package": "Simulation", - "path": "pyleecan/Generator/ClassesRef/Simulation/ELUT_SCIM.csv", - "properties": [ - { - "desc": "Magnetizing flux for a given magnetizing current I_m", - "max": "", - "min": "", - "name": "Phi_m", - "type": "ndarray", - "unit": "Wb", - "value": null - }, - { - "desc": "Stator magnetizing current", - "max": "", - "min": "", - "name": "I_m", - "type": "ndarray", - "unit": "Arms", - "value": null - }, - { - "desc": "Rotor bar average temperature at which Phi_m is given", - "max": "", - "min": "", - "name": "T2_ref", - "type": "float", - "unit": "degC", - "value": 20 - }, - { - "desc": "DC rotor winding resistance at T2_ref already expressed per phase in stator frame ", - "max": "", - "min": "", - "name": "R2", - "type": "float", - "unit": "Ohm", - "value": null - }, - { - "desc": "Rotor winding leakage inductance", - "max": "", - "min": "", - "name": "L2", - "type": "float", - "unit": "H", - "value": null - } - ] - }, "Electrical": { "constants": [ { @@ -1527,7 +1324,7 @@ "max": "", "min": "", "name": "ELUT_enforced", - "type": "ELUT", + "type": "LUT", "unit": "-", "value": null } @@ -4544,6 +4341,219 @@ } ] }, + "LUT": { + "constants": [ + { + "name": "VERSION", + "value": "1" + } + ], + "daughters": [ + "LUTdq", + "LUTslip" + ], + "desc": "Abstract class for Look Up Table (LUT)", + "is_internal": false, + "methods": [ + "get_param_dict" + ], + "mother": "", + "name": "LUT", + "package": "Simulation", + "path": "pyleecan/Generator/ClassesRef/Simulation/LUT.csv", + "properties": [ + { + "desc": "DC phase winding resistance at T1_ref per phase ", + "max": "", + "min": "", + "name": "R1", + "type": "float", + "unit": "Ohm", + "value": null + }, + { + "desc": "Phase winding leakage inductance ", + "max": "", + "min": "", + "name": "L1", + "type": "float", + "unit": "H", + "value": null + }, + { + "desc": "Stator winding average temperature associated to R1, L1 parameters", + "max": "", + "min": "", + "name": "T1_ref", + "type": "float", + "unit": "degC", + "value": 20 + }, + { + "desc": "Array of operating point values of size (N,5) with N the number of operating points (Speed, Id, Iq, Torque, Power). OP values are set to nan if they are not given.", + "max": "", + "min": "", + "name": "OP_matrix", + "type": "ndarray", + "unit": "", + "value": null + } + ] + }, + "LUTdq": { + "constants": [ + { + "name": "VERSION", + "value": "1" + } + ], + "daughters": [], + "desc": "Look Up Table class for dq OP matrix", + "is_internal": false, + "methods": [ + "get_param_dict", + "get_Lq", + "get_bemf", + "get_Ld", + "get_Lmd", + "get_Lmq", + "import_from_data", + "get_Phidqh_mean", + "get_Phidqh_mag", + "get_Phidqh_mag_mean", + "get_Phidqh_mag_harm", + "get_orders_dqh", + "interp_Phi_dqh" + ], + "mother": "LUT", + "name": "LUTdq", + "package": "Simulation", + "path": "pyleecan/Generator/ClassesRef/Simulation/LUTdq.csv", + "properties": [ + { + "desc": "RMS stator winding flux table in dqh frame (including magnets and currents given by I_dqh)", + "max": "", + "min": "", + "name": "Phi_dqh_mean", + "type": "ndarray", + "unit": "Wbrms", + "value": null + }, + { + "desc": "RMS Id Iq Ih table corresponding to flux linkage table given in Phi_dqh_mean", + "max": "", + "min": "", + "name": "I_dqh", + "type": "list", + "unit": "Arms", + "value": null + }, + { + "desc": "Magnet average temperature at which Phi_dqh is given", + "max": "", + "min": "", + "name": "Tmag_ref", + "type": "float", + "unit": "degC", + "value": 20 + }, + { + "desc": "RMS stator winding flux linkage spectrum in dqh frame including harmonics (only magnets)", + "max": "", + "min": "", + "name": "Phi_dqh_mag", + "type": "SciDataTool.Classes.DataND.DataND", + "unit": "Wbrms", + "value": "None" + }, + { + "desc": "Stator winding flux function of time and phases", + "max": "", + "min": "", + "name": "Phi_wind", + "type": "[SciDataTool.Classes.DataND.DataND]", + "unit": "Wb", + "value": "None" + }, + { + "desc": "Interpolant function of Phi_dqh", + "max": "", + "min": "", + "name": "Phi_dqh_interp", + "type": "scipy.interp", + "unit": "", + "value": "None" + } + ] + }, + "LUTslip": { + "constants": [ + { + "name": "VERSION", + "value": "1" + } + ], + "daughters": [], + "desc": "Look Up Table class for u0/slip OP matrix", + "is_internal": false, + "methods": [ + "get_param_dict", + "get_Lm", + "comp_Lm_from_Phim", + "import_from_data" + ], + "mother": "LUT", + "name": "LUTslip", + "package": "Simulation", + "path": "pyleecan/Generator/ClassesRef/Simulation/LUTslip.csv", + "properties": [ + { + "desc": "Magnetizing flux for a given magnetizing current I_m", + "max": "", + "min": "", + "name": "Phi_m", + "type": "ndarray", + "unit": "Wb", + "value": null + }, + { + "desc": "Stator magnetizing current", + "max": "", + "min": "", + "name": "I_m", + "type": "ndarray", + "unit": "Arms", + "value": null + }, + { + "desc": "Rotor bar average temperature at which Phi_m is given", + "max": "", + "min": "", + "name": "T2_ref", + "type": "float", + "unit": "degC", + "value": 20 + }, + { + "desc": "DC rotor winding resistance at T2_ref already expressed per phase in stator frame ", + "max": "", + "min": "", + "name": "R2", + "type": "float", + "unit": "Ohm", + "value": null + }, + { + "desc": "Rotor winding leakage inductance", + "max": "", + "min": "", + "name": "L2", + "type": "float", + "unit": "H", + "value": null + } + ] + }, "LamHole": { "constants": [ { @@ -9204,8 +9214,8 @@ } ], "daughters": [ - "PostELUT", "PostFunction", + "PostLUT", "PostMethod", "PostPlot" ], @@ -9218,7 +9228,7 @@ "path": "pyleecan/Generator/ClassesRef/Post/Post.csv", "properties": [] }, - "PostELUT": { + "PostFunction": { "constants": [ { "name": "VERSION", @@ -9226,46 +9236,26 @@ } ], "daughters": [], - "desc": "Class to generate an ELUT after the corresponding simulation", + "desc": "Post-processing from a user-defined function", "is_internal": false, - "methods": [ - "run" - ], - "mother": "PostMethod", - "name": "PostELUT", + "methods": [], + "mother": "Post", + "name": "PostFunction", "package": "Post", - "path": "pyleecan/Generator/ClassesRef/Post/PostELUT.csv", + "path": "pyleecan/Generator/ClassesRef/Post/PostFunction.csv", "properties": [ { - "desc": "Electrical Look-Up Table to enforce", + "desc": "Post-processing that takes an output in argument", "max": "", "min": "", - "name": "ELUT", - "type": "ELUT", + "name": "run", + "type": "function", "unit": "", "value": null - }, - { - "desc": "True to save ELUT in PostELUT", - "max": "", - "min": "", - "name": "is_save_ELUT", - "type": "bool", - "unit": "", - "value": 1 - }, - { - "desc": "True to store ELUT in PostELUT", - "max": "", - "min": "", - "name": "is_store_ELUT", - "type": "bool", - "unit": "", - "value": 1 } ] }, - "PostFunction": { + "PostLUT": { "constants": [ { "name": "VERSION", @@ -9273,22 +9263,42 @@ } ], "daughters": [], - "desc": "Post-processing from a user-defined function", + "desc": "Class to generate a LUT after the corresponding simulation", "is_internal": false, - "methods": [], - "mother": "Post", - "name": "PostFunction", + "methods": [ + "run" + ], + "mother": "PostMethod", + "name": "PostLUT", "package": "Post", - "path": "pyleecan/Generator/ClassesRef/Post/PostFunction.csv", + "path": "pyleecan/Generator/ClassesRef/Post/PostLUT.csv", "properties": [ { - "desc": "Post-processing that takes an output in argument", + "desc": "Look-Up Table to enforce", "max": "", "min": "", - "name": "run", - "type": "function", + "name": "LUT", + "type": "LUT", "unit": "", "value": null + }, + { + "desc": "True to save LUT in PostLUT", + "max": "", + "min": "", + "name": "is_save_LUT", + "type": "bool", + "unit": "", + "value": 1 + }, + { + "desc": "True to store LUT in PostLUT", + "max": "", + "min": "", + "name": "is_store_LUT", + "type": "bool", + "unit": "", + "value": 1 } ] }, @@ -9300,7 +9310,7 @@ } ], "daughters": [ - "PostELUT", + "PostLUT", "PostPlot" ], "desc": "Abstract class for post-processing defined in the method run", diff --git a/pyleecan/Classes/Electrical.py b/pyleecan/Classes/Electrical.py index 9618a7660..30df9ed38 100644 --- a/pyleecan/Classes/Electrical.py +++ b/pyleecan/Classes/Electrical.py @@ -35,7 +35,7 @@ from ._check import InitUnKnowClassError from .EEC import EEC -from .ELUT import ELUT +from .LUT import LUT class Electrical(FrozenClass): @@ -418,8 +418,8 @@ def _set_ELUT_enforced(self, value): ) value = class_obj(init_dict=value) elif type(value) is int and value == -1: # Default constructor - value = ELUT() - check_var("ELUT_enforced", value, "ELUT") + value = LUT() + check_var("ELUT_enforced", value, "LUT") self._ELUT_enforced = value if self._ELUT_enforced is not None: @@ -430,6 +430,6 @@ def _set_ELUT_enforced(self, value): fset=_set_ELUT_enforced, doc=u"""Electrical Look Up Table to be enforced - :Type: ELUT + :Type: LUT """, ) diff --git a/pyleecan/Classes/ELUT.py b/pyleecan/Classes/LUT.py similarity index 86% rename from pyleecan/Classes/ELUT.py rename to pyleecan/Classes/LUT.py index 6a506f3cf..e058442dd 100644 --- a/pyleecan/Classes/ELUT.py +++ b/pyleecan/Classes/LUT.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -# File generated according to Generator/ClassesRef/Simulation/ELUT.csv +# File generated according to Generator/ClassesRef/Simulation/LUT.csv # WARNING! All changes made in this file will be lost! -"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Simulation/ELUT +"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Simulation/LUT """ from os import linesep @@ -18,7 +18,7 @@ # Import all class method # Try/catch to remove unnecessary dependencies in unused method try: - from ..Methods.Simulation.ELUT.get_param_dict import get_param_dict + from ..Methods.Simulation.LUT.get_param_dict import get_param_dict except ImportError as error: get_param_dict = error @@ -27,17 +27,17 @@ from ._check import InitUnKnowClassError -class ELUT(FrozenClass): - """Abstract class for Electrical Look Up Table (ELUT)""" +class LUT(FrozenClass): + """Abstract class for Look Up Table (LUT)""" VERSION = 1 - # cf Methods.Simulation.ELUT.get_param_dict + # cf Methods.Simulation.LUT.get_param_dict if isinstance(get_param_dict, ImportError): get_param_dict = property( fget=lambda x: raise_( ImportError( - "Can't use ELUT method get_param_dict: " + str(get_param_dict) + "Can't use LUT method get_param_dict: " + str(get_param_dict) ) ) ) @@ -88,22 +88,22 @@ def __init__( def __str__(self): """Convert this object in a readeable string (for print)""" - ELUT_str = "" + LUT_str = "" if self.parent is None: - ELUT_str += "parent = None " + linesep + LUT_str += "parent = None " + linesep else: - ELUT_str += "parent = " + str(type(self.parent)) + " object" + linesep - ELUT_str += "R1 = " + str(self.R1) + linesep - ELUT_str += "L1 = " + str(self.L1) + linesep - ELUT_str += "T1_ref = " + str(self.T1_ref) + linesep - ELUT_str += ( + LUT_str += "parent = " + str(type(self.parent)) + " object" + linesep + LUT_str += "R1 = " + str(self.R1) + linesep + LUT_str += "L1 = " + str(self.L1) + linesep + LUT_str += "T1_ref = " + str(self.T1_ref) + linesep + LUT_str += ( "OP_matrix = " + linesep + str(self.OP_matrix).replace(linesep, linesep + "\t") + linesep + linesep ) - return ELUT_str + return LUT_str def __eq__(self, other): """Compare two objects (skip parent)""" @@ -161,26 +161,26 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): and may prevent json serializability. """ - ELUT_dict = dict() - ELUT_dict["R1"] = self.R1 - ELUT_dict["L1"] = self.L1 - ELUT_dict["T1_ref"] = self.T1_ref + LUT_dict = dict() + LUT_dict["R1"] = self.R1 + LUT_dict["L1"] = self.L1 + LUT_dict["T1_ref"] = self.T1_ref if self.OP_matrix is None: - ELUT_dict["OP_matrix"] = None + LUT_dict["OP_matrix"] = None else: if type_handle_ndarray == 0: - ELUT_dict["OP_matrix"] = self.OP_matrix.tolist() + LUT_dict["OP_matrix"] = self.OP_matrix.tolist() elif type_handle_ndarray == 1: - ELUT_dict["OP_matrix"] = self.OP_matrix.copy() + LUT_dict["OP_matrix"] = self.OP_matrix.copy() elif type_handle_ndarray == 2: - ELUT_dict["OP_matrix"] = self.OP_matrix + LUT_dict["OP_matrix"] = self.OP_matrix else: raise Exception( "Unknown type_handle_ndarray: " + str(type_handle_ndarray) ) # The class name is added to the dict for deserialisation purpose - ELUT_dict["__class__"] = "ELUT" - return ELUT_dict + LUT_dict["__class__"] = "LUT" + return LUT_dict def _set_None(self): """Set all the properties to None (except pyleecan object)""" diff --git a/pyleecan/Classes/ELUT_PMSM.py b/pyleecan/Classes/LUTdq.py similarity index 65% rename from pyleecan/Classes/ELUT_PMSM.py rename to pyleecan/Classes/LUTdq.py index 66f0d7475..5076d986a 100644 --- a/pyleecan/Classes/ELUT_PMSM.py +++ b/pyleecan/Classes/LUTdq.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -# File generated according to Generator/ClassesRef/Simulation/ELUT_PMSM.csv +# File generated according to Generator/ClassesRef/Simulation/LUTdq.csv # WARNING! All changes made in this file will be lost! -"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Simulation/ELUT_PMSM +"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Simulation/LUTdq """ from os import linesep @@ -13,212 +13,228 @@ from ..Functions.copy import copy from ..Functions.load import load_init_dict from ..Functions.Load.import_class import import_class -from .ELUT import ELUT +from .LUT import LUT # Import all class method # Try/catch to remove unnecessary dependencies in unused method try: - from ..Methods.Simulation.ELUT_PMSM.get_param_dict import get_param_dict + from ..Methods.Simulation.LUTdq.get_param_dict import get_param_dict except ImportError as error: get_param_dict = error try: - from ..Methods.Simulation.ELUT_PMSM.get_Lq import get_Lq + from ..Methods.Simulation.LUTdq.get_Lq import get_Lq except ImportError as error: get_Lq = error try: - from ..Methods.Simulation.ELUT_PMSM.get_bemf import get_bemf + from ..Methods.Simulation.LUTdq.get_bemf import get_bemf except ImportError as error: get_bemf = error try: - from ..Methods.Simulation.ELUT_PMSM.get_Ld import get_Ld + from ..Methods.Simulation.LUTdq.get_Ld import get_Ld except ImportError as error: get_Ld = error try: - from ..Methods.Simulation.ELUT_PMSM.get_Lmd import get_Lmd + from ..Methods.Simulation.LUTdq.get_Lmd import get_Lmd except ImportError as error: get_Lmd = error try: - from ..Methods.Simulation.ELUT_PMSM.get_Lmq import get_Lmq + from ..Methods.Simulation.LUTdq.get_Lmq import get_Lmq except ImportError as error: get_Lmq = error try: - from ..Methods.Simulation.ELUT_PMSM.comp_Ldqh_from_Phidqh import ( - comp_Ldqh_from_Phidqh, - ) + from ..Methods.Simulation.LUTdq.import_from_data import import_from_data except ImportError as error: - comp_Ldqh_from_Phidqh = error + import_from_data = error try: - from ..Methods.Simulation.ELUT_PMSM.import_from_data import import_from_data + from ..Methods.Simulation.LUTdq.get_Phidqh_mean import get_Phidqh_mean except ImportError as error: - import_from_data = error + get_Phidqh_mean = error try: - from ..Methods.Simulation.ELUT_PMSM.comp_Phidqh_from_Phiwind import ( - comp_Phidqh_from_Phiwind, - ) + from ..Methods.Simulation.LUTdq.get_Phidqh_mag import get_Phidqh_mag except ImportError as error: - comp_Phidqh_from_Phiwind = error + get_Phidqh_mag = error try: - from ..Methods.Simulation.ELUT_PMSM.get_Phid_mag_mean import get_Phid_mag_mean + from ..Methods.Simulation.LUTdq.get_Phidqh_mag_mean import get_Phidqh_mag_mean except ImportError as error: - get_Phid_mag_mean = error + get_Phidqh_mag_mean = error try: - from ..Methods.Simulation.ELUT_PMSM.get_Phid_mag_harm import get_Phid_mag_harm + from ..Methods.Simulation.LUTdq.get_Phidqh_mag_harm import get_Phidqh_mag_harm except ImportError as error: - get_Phid_mag_harm = error + get_Phidqh_mag_harm = error try: - from ..Methods.Simulation.ELUT_PMSM.get_orders_dqh import get_orders_dqh + from ..Methods.Simulation.LUTdq.get_orders_dqh import get_orders_dqh except ImportError as error: get_orders_dqh = error +try: + from ..Methods.Simulation.LUTdq.interp_Phi_dqh import interp_Phi_dqh +except ImportError as error: + interp_Phi_dqh = error + from numpy import array, array_equal +from cloudpickle import dumps, loads +from ._check import CheckTypeError + +try: + from scipy import interp +except ImportError: + interp = ImportError from ._check import InitUnKnowClassError -class ELUT_PMSM(ELUT): - """ELUT class for PMSM""" +class LUTdq(LUT): + """Look Up Table class for dq OP matrix""" VERSION = 1 # Check ImportError to remove unnecessary dependencies in unused method - # cf Methods.Simulation.ELUT_PMSM.get_param_dict + # cf Methods.Simulation.LUTdq.get_param_dict if isinstance(get_param_dict, ImportError): get_param_dict = property( fget=lambda x: raise_( ImportError( - "Can't use ELUT_PMSM method get_param_dict: " + str(get_param_dict) + "Can't use LUTdq method get_param_dict: " + str(get_param_dict) ) ) ) else: get_param_dict = get_param_dict - # cf Methods.Simulation.ELUT_PMSM.get_Lq + # cf Methods.Simulation.LUTdq.get_Lq if isinstance(get_Lq, ImportError): get_Lq = property( fget=lambda x: raise_( - ImportError("Can't use ELUT_PMSM method get_Lq: " + str(get_Lq)) + ImportError("Can't use LUTdq method get_Lq: " + str(get_Lq)) ) ) else: get_Lq = get_Lq - # cf Methods.Simulation.ELUT_PMSM.get_bemf + # cf Methods.Simulation.LUTdq.get_bemf if isinstance(get_bemf, ImportError): get_bemf = property( fget=lambda x: raise_( - ImportError("Can't use ELUT_PMSM method get_bemf: " + str(get_bemf)) + ImportError("Can't use LUTdq method get_bemf: " + str(get_bemf)) ) ) else: get_bemf = get_bemf - # cf Methods.Simulation.ELUT_PMSM.get_Ld + # cf Methods.Simulation.LUTdq.get_Ld if isinstance(get_Ld, ImportError): get_Ld = property( fget=lambda x: raise_( - ImportError("Can't use ELUT_PMSM method get_Ld: " + str(get_Ld)) + ImportError("Can't use LUTdq method get_Ld: " + str(get_Ld)) ) ) else: get_Ld = get_Ld - # cf Methods.Simulation.ELUT_PMSM.get_Lmd + # cf Methods.Simulation.LUTdq.get_Lmd if isinstance(get_Lmd, ImportError): get_Lmd = property( fget=lambda x: raise_( - ImportError("Can't use ELUT_PMSM method get_Lmd: " + str(get_Lmd)) + ImportError("Can't use LUTdq method get_Lmd: " + str(get_Lmd)) ) ) else: get_Lmd = get_Lmd - # cf Methods.Simulation.ELUT_PMSM.get_Lmq + # cf Methods.Simulation.LUTdq.get_Lmq if isinstance(get_Lmq, ImportError): get_Lmq = property( fget=lambda x: raise_( - ImportError("Can't use ELUT_PMSM method get_Lmq: " + str(get_Lmq)) + ImportError("Can't use LUTdq method get_Lmq: " + str(get_Lmq)) ) ) else: get_Lmq = get_Lmq - # cf Methods.Simulation.ELUT_PMSM.comp_Ldqh_from_Phidqh - if isinstance(comp_Ldqh_from_Phidqh, ImportError): - comp_Ldqh_from_Phidqh = property( + # cf Methods.Simulation.LUTdq.import_from_data + if isinstance(import_from_data, ImportError): + import_from_data = property( fget=lambda x: raise_( ImportError( - "Can't use ELUT_PMSM method comp_Ldqh_from_Phidqh: " - + str(comp_Ldqh_from_Phidqh) + "Can't use LUTdq method import_from_data: " + str(import_from_data) ) ) ) else: - comp_Ldqh_from_Phidqh = comp_Ldqh_from_Phidqh - # cf Methods.Simulation.ELUT_PMSM.import_from_data - if isinstance(import_from_data, ImportError): - import_from_data = property( + import_from_data = import_from_data + # cf Methods.Simulation.LUTdq.get_Phidqh_mean + if isinstance(get_Phidqh_mean, ImportError): + get_Phidqh_mean = property( fget=lambda x: raise_( ImportError( - "Can't use ELUT_PMSM method import_from_data: " - + str(import_from_data) + "Can't use LUTdq method get_Phidqh_mean: " + str(get_Phidqh_mean) ) ) ) else: - import_from_data = import_from_data - # cf Methods.Simulation.ELUT_PMSM.comp_Phidqh_from_Phiwind - if isinstance(comp_Phidqh_from_Phiwind, ImportError): - comp_Phidqh_from_Phiwind = property( + get_Phidqh_mean = get_Phidqh_mean + # cf Methods.Simulation.LUTdq.get_Phidqh_mag + if isinstance(get_Phidqh_mag, ImportError): + get_Phidqh_mag = property( fget=lambda x: raise_( ImportError( - "Can't use ELUT_PMSM method comp_Phidqh_from_Phiwind: " - + str(comp_Phidqh_from_Phiwind) + "Can't use LUTdq method get_Phidqh_mag: " + str(get_Phidqh_mag) ) ) ) else: - comp_Phidqh_from_Phiwind = comp_Phidqh_from_Phiwind - # cf Methods.Simulation.ELUT_PMSM.get_Phid_mag_mean - if isinstance(get_Phid_mag_mean, ImportError): - get_Phid_mag_mean = property( + get_Phidqh_mag = get_Phidqh_mag + # cf Methods.Simulation.LUTdq.get_Phidqh_mag_mean + if isinstance(get_Phidqh_mag_mean, ImportError): + get_Phidqh_mag_mean = property( fget=lambda x: raise_( ImportError( - "Can't use ELUT_PMSM method get_Phid_mag_mean: " - + str(get_Phid_mag_mean) + "Can't use LUTdq method get_Phidqh_mag_mean: " + + str(get_Phidqh_mag_mean) ) ) ) else: - get_Phid_mag_mean = get_Phid_mag_mean - # cf Methods.Simulation.ELUT_PMSM.get_Phid_mag_harm - if isinstance(get_Phid_mag_harm, ImportError): - get_Phid_mag_harm = property( + get_Phidqh_mag_mean = get_Phidqh_mag_mean + # cf Methods.Simulation.LUTdq.get_Phidqh_mag_harm + if isinstance(get_Phidqh_mag_harm, ImportError): + get_Phidqh_mag_harm = property( fget=lambda x: raise_( ImportError( - "Can't use ELUT_PMSM method get_Phid_mag_harm: " - + str(get_Phid_mag_harm) + "Can't use LUTdq method get_Phidqh_mag_harm: " + + str(get_Phidqh_mag_harm) ) ) ) else: - get_Phid_mag_harm = get_Phid_mag_harm - # cf Methods.Simulation.ELUT_PMSM.get_orders_dqh + get_Phidqh_mag_harm = get_Phidqh_mag_harm + # cf Methods.Simulation.LUTdq.get_orders_dqh if isinstance(get_orders_dqh, ImportError): get_orders_dqh = property( fget=lambda x: raise_( ImportError( - "Can't use ELUT_PMSM method get_orders_dqh: " + str(get_orders_dqh) + "Can't use LUTdq method get_orders_dqh: " + str(get_orders_dqh) ) ) ) else: get_orders_dqh = get_orders_dqh + # cf Methods.Simulation.LUTdq.interp_Phi_dqh + if isinstance(interp_Phi_dqh, ImportError): + interp_Phi_dqh = property( + fget=lambda x: raise_( + ImportError( + "Can't use LUTdq method interp_Phi_dqh: " + str(interp_Phi_dqh) + ) + ) + ) + else: + interp_Phi_dqh = interp_Phi_dqh # save and copy methods are available in all object save = save copy = copy @@ -232,6 +248,7 @@ def __init__( Tmag_ref=20, Phi_dqh_mag=None, Phi_wind=None, + Phi_dqh_interp=None, R1=None, L1=None, T1_ref=20, @@ -264,6 +281,8 @@ def __init__( Phi_dqh_mag = init_dict["Phi_dqh_mag"] if "Phi_wind" in list(init_dict.keys()): Phi_wind = init_dict["Phi_wind"] + if "Phi_dqh_interp" in list(init_dict.keys()): + Phi_dqh_interp = init_dict["Phi_dqh_interp"] if "R1" in list(init_dict.keys()): R1 = init_dict["R1"] if "L1" in list(init_dict.keys()): @@ -278,36 +297,36 @@ def __init__( self.Tmag_ref = Tmag_ref self.Phi_dqh_mag = Phi_dqh_mag self.Phi_wind = Phi_wind - # Call ELUT init - super(ELUT_PMSM, self).__init__( - R1=R1, L1=L1, T1_ref=T1_ref, OP_matrix=OP_matrix - ) - # The class is frozen (in ELUT init), for now it's impossible to + self.Phi_dqh_interp = Phi_dqh_interp + # Call LUT init + super(LUTdq, self).__init__(R1=R1, L1=L1, T1_ref=T1_ref, OP_matrix=OP_matrix) + # The class is frozen (in LUT init), for now it's impossible to # add new properties def __str__(self): """Convert this object in a readeable string (for print)""" - ELUT_PMSM_str = "" - # Get the properties inherited from ELUT - ELUT_PMSM_str += super(ELUT_PMSM, self).__str__() - ELUT_PMSM_str += ( + LUTdq_str = "" + # Get the properties inherited from LUT + LUTdq_str += super(LUTdq, self).__str__() + LUTdq_str += ( "Phi_dqh_mean = " + linesep + str(self.Phi_dqh_mean).replace(linesep, linesep + "\t") + linesep + linesep ) - ELUT_PMSM_str += ( + LUTdq_str += ( "I_dqh = " + linesep + str(self.I_dqh).replace(linesep, linesep + "\t") + linesep ) - ELUT_PMSM_str += "Tmag_ref = " + str(self.Tmag_ref) + linesep - ELUT_PMSM_str += "Phi_dqh_mag = " + str(self.Phi_dqh_mag) + linesep + linesep - ELUT_PMSM_str += "Phi_wind = " + str(self.Phi_wind) + linesep + linesep - return ELUT_PMSM_str + LUTdq_str += "Tmag_ref = " + str(self.Tmag_ref) + linesep + LUTdq_str += "Phi_dqh_mag = " + str(self.Phi_dqh_mag) + linesep + linesep + LUTdq_str += "Phi_wind = " + str(self.Phi_wind) + linesep + linesep + LUTdq_str += "Phi_dqh_interp = " + str(self.Phi_dqh_interp) + linesep + linesep + return LUTdq_str def __eq__(self, other): """Compare two objects (skip parent)""" @@ -315,8 +334,8 @@ def __eq__(self, other): if type(other) != type(self): return False - # Check the properties inherited from ELUT - if not super(ELUT_PMSM, self).__eq__(other): + # Check the properties inherited from LUT + if not super(LUTdq, self).__eq__(other): return False if not array_equal(other.Phi_dqh_mean, self.Phi_dqh_mean): return False @@ -328,6 +347,8 @@ def __eq__(self, other): return False if other.Phi_wind != self.Phi_wind: return False + if other.Phi_dqh_interp != self.Phi_dqh_interp: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -339,8 +360,8 @@ def compare(self, other, name="self", ignore_list=None): return ["type(" + name + ")"] diff_list = list() - # Check the properties inherited from ELUT - diff_list.extend(super(ELUT_PMSM, self).compare(other, name=name)) + # Check the properties inherited from LUT + diff_list.extend(super(LUTdq, self).compare(other, name=name)) if not array_equal(other.Phi_dqh_mean, self.Phi_dqh_mean): diff_list.append(name + ".Phi_dqh_mean") if other._I_dqh != self._I_dqh: @@ -370,6 +391,15 @@ def compare(self, other, name="self", ignore_list=None): other.Phi_wind[ii], name=name + ".Phi_wind[" + str(ii) + "]" ) ) + if (other.Phi_dqh_interp is None and self.Phi_dqh_interp is not None) or ( + other.Phi_dqh_interp is not None and self.Phi_dqh_interp is None + ): + diff_list.append(name + ".Phi_dqh_interp None mismatch") + elif ( + self.Phi_dqh_interp is not None + and self.Phi_dqh_interp != other.Phi_dqh_interp + ): + diff_list.append(name + ".Phi_dqh_interp") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -379,8 +409,8 @@ def __sizeof__(self): S = 0 # Full size of the object - # Get size of the properties inherited from ELUT - S += super(ELUT_PMSM, self).__sizeof__() + # Get size of the properties inherited from LUT + S += super(LUTdq, self).__sizeof__() S += getsizeof(self.Phi_dqh_mean) if self.I_dqh is not None: for value in self.I_dqh: @@ -390,6 +420,7 @@ def __sizeof__(self): if self.Phi_wind is not None: for value in self.Phi_wind: S += getsizeof(value) + S += getsizeof(self.Phi_dqh_interp) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -403,42 +434,42 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): and may prevent json serializability. """ - # Get the properties inherited from ELUT - ELUT_PMSM_dict = super(ELUT_PMSM, self).as_dict( + # Get the properties inherited from LUT + LUTdq_dict = super(LUTdq, self).as_dict( type_handle_ndarray=type_handle_ndarray, keep_function=keep_function, **kwargs ) if self.Phi_dqh_mean is None: - ELUT_PMSM_dict["Phi_dqh_mean"] = None + LUTdq_dict["Phi_dqh_mean"] = None else: if type_handle_ndarray == 0: - ELUT_PMSM_dict["Phi_dqh_mean"] = self.Phi_dqh_mean.tolist() + LUTdq_dict["Phi_dqh_mean"] = self.Phi_dqh_mean.tolist() elif type_handle_ndarray == 1: - ELUT_PMSM_dict["Phi_dqh_mean"] = self.Phi_dqh_mean.copy() + LUTdq_dict["Phi_dqh_mean"] = self.Phi_dqh_mean.copy() elif type_handle_ndarray == 2: - ELUT_PMSM_dict["Phi_dqh_mean"] = self.Phi_dqh_mean + LUTdq_dict["Phi_dqh_mean"] = self.Phi_dqh_mean else: raise Exception( "Unknown type_handle_ndarray: " + str(type_handle_ndarray) ) - ELUT_PMSM_dict["I_dqh"] = self.I_dqh.copy() if self.I_dqh is not None else None - ELUT_PMSM_dict["Tmag_ref"] = self.Tmag_ref + LUTdq_dict["I_dqh"] = self.I_dqh.copy() if self.I_dqh is not None else None + LUTdq_dict["Tmag_ref"] = self.Tmag_ref if self.Phi_dqh_mag is None: - ELUT_PMSM_dict["Phi_dqh_mag"] = None + LUTdq_dict["Phi_dqh_mag"] = None else: - ELUT_PMSM_dict["Phi_dqh_mag"] = self.Phi_dqh_mag.as_dict( + LUTdq_dict["Phi_dqh_mag"] = self.Phi_dqh_mag.as_dict( type_handle_ndarray=type_handle_ndarray, keep_function=keep_function, **kwargs ) if self.Phi_wind is None: - ELUT_PMSM_dict["Phi_wind"] = None + LUTdq_dict["Phi_wind"] = None else: - ELUT_PMSM_dict["Phi_wind"] = list() + LUTdq_dict["Phi_wind"] = list() for obj in self.Phi_wind: if obj is not None: - ELUT_PMSM_dict["Phi_wind"].append( + LUTdq_dict["Phi_wind"].append( obj.as_dict( type_handle_ndarray=type_handle_ndarray, keep_function=keep_function, @@ -446,11 +477,21 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): ) ) else: - ELUT_PMSM_dict["Phi_wind"].append(None) + LUTdq_dict["Phi_wind"].append(None) + if self.Phi_dqh_interp is None: + LUTdq_dict["Phi_dqh_interp"] = None + else: + # Store serialized data (using cloudpickle) and str + # to read it in json save files + LUTdq_dict["Phi_dqh_interp"] = { + "__class__": str(type(self._Phi_dqh_interp)), + "__repr__": str(self._Phi_dqh_interp.__repr__()), + "serialized": dumps(self._Phi_dqh_interp).decode("ISO-8859-2"), + } # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name - ELUT_PMSM_dict["__class__"] = "ELUT_PMSM" - return ELUT_PMSM_dict + LUTdq_dict["__class__"] = "LUTdq" + return LUTdq_dict def _set_None(self): """Set all the properties to None (except pyleecan object)""" @@ -460,8 +501,9 @@ def _set_None(self): self.Tmag_ref = None self.Phi_dqh_mag = None self.Phi_wind = None - # Set to None the properties inherited from ELUT - super(ELUT_PMSM, self)._set_None() + self.Phi_dqh_interp = None + # Set to None the properties inherited from LUT + super(LUTdq, self)._set_None() def _get_Phi_dqh_mean(self): """getter of Phi_dqh_mean""" @@ -547,7 +589,7 @@ def _set_Phi_dqh_mag(self, value): Phi_dqh_mag = property( fget=_get_Phi_dqh_mag, fset=_set_Phi_dqh_mag, - doc=u"""RMS stator winding flux linkage in dqh frame including harmonics (only magnets) + doc=u"""RMS stator winding flux linkage spectrum in dqh frame including harmonics (only magnets) :Type: SciDataTool.Classes.DataND.DataND """, @@ -585,3 +627,21 @@ def _set_Phi_wind(self, value): :Type: [SciDataTool.Classes.DataND.DataND] """, ) + + def _get_Phi_dqh_interp(self): + """getter of Phi_dqh_interp""" + return self._Phi_dqh_interp + + def _set_Phi_dqh_interp(self, value): + """setter of Phi_dqh_interp""" + check_var("Phi_dqh_interp", value, "interp") + self._Phi_dqh_interp = value + + Phi_dqh_interp = property( + fget=_get_Phi_dqh_interp, + fset=_set_Phi_dqh_interp, + doc=u"""Interpolant function of Phi_dqh + + :Type: scipy.interp + """, + ) diff --git a/pyleecan/Classes/ELUT_SCIM.py b/pyleecan/Classes/LUTslip.py similarity index 78% rename from pyleecan/Classes/ELUT_SCIM.py rename to pyleecan/Classes/LUTslip.py index 6f727673e..03634f15c 100644 --- a/pyleecan/Classes/ELUT_SCIM.py +++ b/pyleecan/Classes/LUTslip.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -# File generated according to Generator/ClassesRef/Simulation/ELUT_SCIM.csv +# File generated according to Generator/ClassesRef/Simulation/LUTslip.csv # WARNING! All changes made in this file will be lost! -"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Simulation/ELUT_SCIM +"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Simulation/LUTslip """ from os import linesep @@ -13,27 +13,27 @@ from ..Functions.copy import copy from ..Functions.load import load_init_dict from ..Functions.Load.import_class import import_class -from .ELUT import ELUT +from .LUT import LUT # Import all class method # Try/catch to remove unnecessary dependencies in unused method try: - from ..Methods.Simulation.ELUT_SCIM.get_param_dict import get_param_dict + from ..Methods.Simulation.LUTslip.get_param_dict import get_param_dict except ImportError as error: get_param_dict = error try: - from ..Methods.Simulation.ELUT_SCIM.get_Lm import get_Lm + from ..Methods.Simulation.LUTslip.get_Lm import get_Lm except ImportError as error: get_Lm = error try: - from ..Methods.Simulation.ELUT_SCIM.comp_Lm_from_Phim import comp_Lm_from_Phim + from ..Methods.Simulation.LUTslip.comp_Lm_from_Phim import comp_Lm_from_Phim except ImportError as error: comp_Lm_from_Phim = error try: - from ..Methods.Simulation.ELUT_SCIM.import_from_data import import_from_data + from ..Methods.Simulation.LUTslip.import_from_data import import_from_data except ImportError as error: import_from_data = error @@ -42,50 +42,50 @@ from ._check import InitUnKnowClassError -class ELUT_SCIM(ELUT): - """ELUT class for SCIM""" +class LUTslip(LUT): + """Look Up Table class for u0/slip OP matrix""" VERSION = 1 # Check ImportError to remove unnecessary dependencies in unused method - # cf Methods.Simulation.ELUT_SCIM.get_param_dict + # cf Methods.Simulation.LUTslip.get_param_dict if isinstance(get_param_dict, ImportError): get_param_dict = property( fget=lambda x: raise_( ImportError( - "Can't use ELUT_SCIM method get_param_dict: " + str(get_param_dict) + "Can't use LUTslip method get_param_dict: " + str(get_param_dict) ) ) ) else: get_param_dict = get_param_dict - # cf Methods.Simulation.ELUT_SCIM.get_Lm + # cf Methods.Simulation.LUTslip.get_Lm if isinstance(get_Lm, ImportError): get_Lm = property( fget=lambda x: raise_( - ImportError("Can't use ELUT_SCIM method get_Lm: " + str(get_Lm)) + ImportError("Can't use LUTslip method get_Lm: " + str(get_Lm)) ) ) else: get_Lm = get_Lm - # cf Methods.Simulation.ELUT_SCIM.comp_Lm_from_Phim + # cf Methods.Simulation.LUTslip.comp_Lm_from_Phim if isinstance(comp_Lm_from_Phim, ImportError): comp_Lm_from_Phim = property( fget=lambda x: raise_( ImportError( - "Can't use ELUT_SCIM method comp_Lm_from_Phim: " + "Can't use LUTslip method comp_Lm_from_Phim: " + str(comp_Lm_from_Phim) ) ) ) else: comp_Lm_from_Phim = comp_Lm_from_Phim - # cf Methods.Simulation.ELUT_SCIM.import_from_data + # cf Methods.Simulation.LUTslip.import_from_data if isinstance(import_from_data, ImportError): import_from_data = property( fget=lambda x: raise_( ImportError( - "Can't use ELUT_SCIM method import_from_data: " + "Can't use LUTslip method import_from_data: " + str(import_from_data) ) ) @@ -151,37 +151,35 @@ def __init__( self.T2_ref = T2_ref self.R2 = R2 self.L2 = L2 - # Call ELUT init - super(ELUT_SCIM, self).__init__( - R1=R1, L1=L1, T1_ref=T1_ref, OP_matrix=OP_matrix - ) - # The class is frozen (in ELUT init), for now it's impossible to + # Call LUT init + super(LUTslip, self).__init__(R1=R1, L1=L1, T1_ref=T1_ref, OP_matrix=OP_matrix) + # The class is frozen (in LUT init), for now it's impossible to # add new properties def __str__(self): """Convert this object in a readeable string (for print)""" - ELUT_SCIM_str = "" - # Get the properties inherited from ELUT - ELUT_SCIM_str += super(ELUT_SCIM, self).__str__() - ELUT_SCIM_str += ( + LUTslip_str = "" + # Get the properties inherited from LUT + LUTslip_str += super(LUTslip, self).__str__() + LUTslip_str += ( "Phi_m = " + linesep + str(self.Phi_m).replace(linesep, linesep + "\t") + linesep + linesep ) - ELUT_SCIM_str += ( + LUTslip_str += ( "I_m = " + linesep + str(self.I_m).replace(linesep, linesep + "\t") + linesep + linesep ) - ELUT_SCIM_str += "T2_ref = " + str(self.T2_ref) + linesep - ELUT_SCIM_str += "R2 = " + str(self.R2) + linesep - ELUT_SCIM_str += "L2 = " + str(self.L2) + linesep - return ELUT_SCIM_str + LUTslip_str += "T2_ref = " + str(self.T2_ref) + linesep + LUTslip_str += "R2 = " + str(self.R2) + linesep + LUTslip_str += "L2 = " + str(self.L2) + linesep + return LUTslip_str def __eq__(self, other): """Compare two objects (skip parent)""" @@ -189,8 +187,8 @@ def __eq__(self, other): if type(other) != type(self): return False - # Check the properties inherited from ELUT - if not super(ELUT_SCIM, self).__eq__(other): + # Check the properties inherited from LUT + if not super(LUTslip, self).__eq__(other): return False if not array_equal(other.Phi_m, self.Phi_m): return False @@ -213,8 +211,8 @@ def compare(self, other, name="self", ignore_list=None): return ["type(" + name + ")"] diff_list = list() - # Check the properties inherited from ELUT - diff_list.extend(super(ELUT_SCIM, self).compare(other, name=name)) + # Check the properties inherited from LUT + diff_list.extend(super(LUTslip, self).compare(other, name=name)) if not array_equal(other.Phi_m, self.Phi_m): diff_list.append(name + ".Phi_m") if not array_equal(other.I_m, self.I_m): @@ -234,8 +232,8 @@ def __sizeof__(self): S = 0 # Full size of the object - # Get size of the properties inherited from ELUT - S += super(ELUT_SCIM, self).__sizeof__() + # Get size of the properties inherited from LUT + S += super(LUTslip, self).__sizeof__() S += getsizeof(self.Phi_m) S += getsizeof(self.I_m) S += getsizeof(self.T2_ref) @@ -254,45 +252,45 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): and may prevent json serializability. """ - # Get the properties inherited from ELUT - ELUT_SCIM_dict = super(ELUT_SCIM, self).as_dict( + # Get the properties inherited from LUT + LUTslip_dict = super(LUTslip, self).as_dict( type_handle_ndarray=type_handle_ndarray, keep_function=keep_function, **kwargs ) if self.Phi_m is None: - ELUT_SCIM_dict["Phi_m"] = None + LUTslip_dict["Phi_m"] = None else: if type_handle_ndarray == 0: - ELUT_SCIM_dict["Phi_m"] = self.Phi_m.tolist() + LUTslip_dict["Phi_m"] = self.Phi_m.tolist() elif type_handle_ndarray == 1: - ELUT_SCIM_dict["Phi_m"] = self.Phi_m.copy() + LUTslip_dict["Phi_m"] = self.Phi_m.copy() elif type_handle_ndarray == 2: - ELUT_SCIM_dict["Phi_m"] = self.Phi_m + LUTslip_dict["Phi_m"] = self.Phi_m else: raise Exception( "Unknown type_handle_ndarray: " + str(type_handle_ndarray) ) if self.I_m is None: - ELUT_SCIM_dict["I_m"] = None + LUTslip_dict["I_m"] = None else: if type_handle_ndarray == 0: - ELUT_SCIM_dict["I_m"] = self.I_m.tolist() + LUTslip_dict["I_m"] = self.I_m.tolist() elif type_handle_ndarray == 1: - ELUT_SCIM_dict["I_m"] = self.I_m.copy() + LUTslip_dict["I_m"] = self.I_m.copy() elif type_handle_ndarray == 2: - ELUT_SCIM_dict["I_m"] = self.I_m + LUTslip_dict["I_m"] = self.I_m else: raise Exception( "Unknown type_handle_ndarray: " + str(type_handle_ndarray) ) - ELUT_SCIM_dict["T2_ref"] = self.T2_ref - ELUT_SCIM_dict["R2"] = self.R2 - ELUT_SCIM_dict["L2"] = self.L2 + LUTslip_dict["T2_ref"] = self.T2_ref + LUTslip_dict["R2"] = self.R2 + LUTslip_dict["L2"] = self.L2 # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name - ELUT_SCIM_dict["__class__"] = "ELUT_SCIM" - return ELUT_SCIM_dict + LUTslip_dict["__class__"] = "LUTslip" + return LUTslip_dict def _set_None(self): """Set all the properties to None (except pyleecan object)""" @@ -302,8 +300,8 @@ def _set_None(self): self.T2_ref = None self.R2 = None self.L2 = None - # Set to None the properties inherited from ELUT - super(ELUT_SCIM, self)._set_None() + # Set to None the properties inherited from LUT + super(LUTslip, self)._set_None() def _get_Phi_m(self): """getter of Phi_m""" diff --git a/pyleecan/Classes/PostELUT.py b/pyleecan/Classes/PostLUT.py similarity index 53% rename from pyleecan/Classes/PostELUT.py rename to pyleecan/Classes/PostLUT.py index 8f07fdc9d..434b434e4 100644 --- a/pyleecan/Classes/PostELUT.py +++ b/pyleecan/Classes/PostLUT.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -# File generated according to Generator/ClassesRef/Post/PostELUT.csv +# File generated according to Generator/ClassesRef/Post/PostLUT.csv # WARNING! All changes made in this file will be lost! -"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Post/PostELUT +"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Post/PostLUT """ from os import linesep @@ -18,25 +18,25 @@ # Import all class method # Try/catch to remove unnecessary dependencies in unused method try: - from ..Methods.Post.PostELUT.run import run + from ..Methods.Post.PostLUT.run import run except ImportError as error: run = error from ._check import InitUnKnowClassError -from .ELUT import ELUT +from .LUT import LUT -class PostELUT(PostMethod): - """Class to generate an ELUT after the corresponding simulation""" +class PostLUT(PostMethod): + """Class to generate a LUT after the corresponding simulation""" VERSION = 1 - # cf Methods.Post.PostELUT.run + # cf Methods.Post.PostLUT.run if isinstance(run, ImportError): run = property( fget=lambda x: raise_( - ImportError("Can't use PostELUT method run: " + str(run)) + ImportError("Can't use PostLUT method run: " + str(run)) ) ) else: @@ -49,9 +49,9 @@ class PostELUT(PostMethod): def __init__( self, - ELUT=None, - is_save_ELUT=True, - is_store_ELUT=True, + LUT=None, + is_save_LUT=True, + is_store_LUT=True, init_dict=None, init_str=None, ): @@ -70,35 +70,35 @@ def __init__( if init_dict is not None: # Initialisation by dict assert type(init_dict) is dict # Overwrite default value with init_dict content - if "ELUT" in list(init_dict.keys()): - ELUT = init_dict["ELUT"] - if "is_save_ELUT" in list(init_dict.keys()): - is_save_ELUT = init_dict["is_save_ELUT"] - if "is_store_ELUT" in list(init_dict.keys()): - is_store_ELUT = init_dict["is_store_ELUT"] + if "LUT" in list(init_dict.keys()): + LUT = init_dict["LUT"] + if "is_save_LUT" in list(init_dict.keys()): + is_save_LUT = init_dict["is_save_LUT"] + if "is_store_LUT" in list(init_dict.keys()): + is_store_LUT = init_dict["is_store_LUT"] # Set the properties (value check and convertion are done in setter) - self.ELUT = ELUT - self.is_save_ELUT = is_save_ELUT - self.is_store_ELUT = is_store_ELUT + self.LUT = LUT + self.is_save_LUT = is_save_LUT + self.is_store_LUT = is_store_LUT # Call PostMethod init - super(PostELUT, self).__init__() + super(PostLUT, self).__init__() # The class is frozen (in PostMethod init), for now it's impossible to # add new properties def __str__(self): """Convert this object in a readeable string (for print)""" - PostELUT_str = "" + PostLUT_str = "" # Get the properties inherited from PostMethod - PostELUT_str += super(PostELUT, self).__str__() - if self.ELUT is not None: - tmp = self.ELUT.__str__().replace(linesep, linesep + "\t").rstrip("\t") - PostELUT_str += "ELUT = " + tmp + PostLUT_str += super(PostLUT, self).__str__() + if self.LUT is not None: + tmp = self.LUT.__str__().replace(linesep, linesep + "\t").rstrip("\t") + PostLUT_str += "LUT = " + tmp else: - PostELUT_str += "ELUT = None" + linesep + linesep - PostELUT_str += "is_save_ELUT = " + str(self.is_save_ELUT) + linesep - PostELUT_str += "is_store_ELUT = " + str(self.is_store_ELUT) + linesep - return PostELUT_str + PostLUT_str += "LUT = None" + linesep + linesep + PostLUT_str += "is_save_LUT = " + str(self.is_save_LUT) + linesep + PostLUT_str += "is_store_LUT = " + str(self.is_store_LUT) + linesep + return PostLUT_str def __eq__(self, other): """Compare two objects (skip parent)""" @@ -107,13 +107,13 @@ def __eq__(self, other): return False # Check the properties inherited from PostMethod - if not super(PostELUT, self).__eq__(other): + if not super(PostLUT, self).__eq__(other): return False - if other.ELUT != self.ELUT: + if other.LUT != self.LUT: return False - if other.is_save_ELUT != self.is_save_ELUT: + if other.is_save_LUT != self.is_save_LUT: return False - if other.is_store_ELUT != self.is_store_ELUT: + if other.is_store_LUT != self.is_store_LUT: return False return True @@ -127,17 +127,17 @@ def compare(self, other, name="self", ignore_list=None): diff_list = list() # Check the properties inherited from PostMethod - diff_list.extend(super(PostELUT, self).compare(other, name=name)) - if (other.ELUT is None and self.ELUT is not None) or ( - other.ELUT is not None and self.ELUT is None + diff_list.extend(super(PostLUT, self).compare(other, name=name)) + if (other.LUT is None and self.LUT is not None) or ( + other.LUT is not None and self.LUT is None ): - diff_list.append(name + ".ELUT None mismatch") - elif self.ELUT is not None: - diff_list.extend(self.ELUT.compare(other.ELUT, name=name + ".ELUT")) - if other._is_save_ELUT != self._is_save_ELUT: - diff_list.append(name + ".is_save_ELUT") - if other._is_store_ELUT != self._is_store_ELUT: - diff_list.append(name + ".is_store_ELUT") + diff_list.append(name + ".LUT None mismatch") + elif self.LUT is not None: + diff_list.extend(self.LUT.compare(other.LUT, name=name + ".LUT")) + if other._is_save_LUT != self._is_save_LUT: + diff_list.append(name + ".is_save_LUT") + if other._is_store_LUT != self._is_store_LUT: + diff_list.append(name + ".is_store_LUT") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -148,10 +148,10 @@ def __sizeof__(self): S = 0 # Full size of the object # Get size of the properties inherited from PostMethod - S += super(PostELUT, self).__sizeof__() - S += getsizeof(self.ELUT) - S += getsizeof(self.is_save_ELUT) - S += getsizeof(self.is_store_ELUT) + S += super(PostLUT, self).__sizeof__() + S += getsizeof(self.LUT) + S += getsizeof(self.is_save_LUT) + S += getsizeof(self.is_store_LUT) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -166,95 +166,95 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): """ # Get the properties inherited from PostMethod - PostELUT_dict = super(PostELUT, self).as_dict( + PostLUT_dict = super(PostLUT, self).as_dict( type_handle_ndarray=type_handle_ndarray, keep_function=keep_function, **kwargs ) - if self.ELUT is None: - PostELUT_dict["ELUT"] = None + if self.LUT is None: + PostLUT_dict["LUT"] = None else: - PostELUT_dict["ELUT"] = self.ELUT.as_dict( + PostLUT_dict["LUT"] = self.LUT.as_dict( type_handle_ndarray=type_handle_ndarray, keep_function=keep_function, **kwargs ) - PostELUT_dict["is_save_ELUT"] = self.is_save_ELUT - PostELUT_dict["is_store_ELUT"] = self.is_store_ELUT + PostLUT_dict["is_save_LUT"] = self.is_save_LUT + PostLUT_dict["is_store_LUT"] = self.is_store_LUT # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name - PostELUT_dict["__class__"] = "PostELUT" - return PostELUT_dict + PostLUT_dict["__class__"] = "PostLUT" + return PostLUT_dict def _set_None(self): """Set all the properties to None (except pyleecan object)""" - if self.ELUT is not None: - self.ELUT._set_None() - self.is_save_ELUT = None - self.is_store_ELUT = None + if self.LUT is not None: + self.LUT._set_None() + self.is_save_LUT = None + self.is_store_LUT = None # Set to None the properties inherited from PostMethod - super(PostELUT, self)._set_None() + super(PostLUT, self)._set_None() - def _get_ELUT(self): - """getter of ELUT""" - return self._ELUT + def _get_LUT(self): + """getter of LUT""" + return self._LUT - def _set_ELUT(self, value): - """setter of ELUT""" + def _set_LUT(self, value): + """setter of LUT""" if isinstance(value, str): # Load from file value = load_init_dict(value)[1] if isinstance(value, dict) and "__class__" in value: - class_obj = import_class("pyleecan.Classes", value.get("__class__"), "ELUT") + class_obj = import_class("pyleecan.Classes", value.get("__class__"), "LUT") value = class_obj(init_dict=value) elif type(value) is int and value == -1: # Default constructor - value = ELUT() - check_var("ELUT", value, "ELUT") - self._ELUT = value + value = LUT() + check_var("LUT", value, "LUT") + self._LUT = value - if self._ELUT is not None: - self._ELUT.parent = self + if self._LUT is not None: + self._LUT.parent = self - ELUT = property( - fget=_get_ELUT, - fset=_set_ELUT, - doc=u"""Electrical Look-Up Table to enforce + LUT = property( + fget=_get_LUT, + fset=_set_LUT, + doc=u"""Look-Up Table to enforce - :Type: ELUT + :Type: LUT """, ) - def _get_is_save_ELUT(self): - """getter of is_save_ELUT""" - return self._is_save_ELUT + def _get_is_save_LUT(self): + """getter of is_save_LUT""" + return self._is_save_LUT - def _set_is_save_ELUT(self, value): - """setter of is_save_ELUT""" - check_var("is_save_ELUT", value, "bool") - self._is_save_ELUT = value + def _set_is_save_LUT(self, value): + """setter of is_save_LUT""" + check_var("is_save_LUT", value, "bool") + self._is_save_LUT = value - is_save_ELUT = property( - fget=_get_is_save_ELUT, - fset=_set_is_save_ELUT, - doc=u"""True to save ELUT in PostELUT + is_save_LUT = property( + fget=_get_is_save_LUT, + fset=_set_is_save_LUT, + doc=u"""True to save LUT in PostLUT :Type: bool """, ) - def _get_is_store_ELUT(self): - """getter of is_store_ELUT""" - return self._is_store_ELUT + def _get_is_store_LUT(self): + """getter of is_store_LUT""" + return self._is_store_LUT - def _set_is_store_ELUT(self, value): - """setter of is_store_ELUT""" - check_var("is_store_ELUT", value, "bool") - self._is_store_ELUT = value + def _set_is_store_LUT(self, value): + """setter of is_store_LUT""" + check_var("is_store_LUT", value, "bool") + self._is_store_LUT = value - is_store_ELUT = property( - fget=_get_is_store_ELUT, - fset=_set_is_store_ELUT, - doc=u"""True to store ELUT in PostELUT + is_store_LUT = property( + fget=_get_is_store_LUT, + fset=_set_is_store_LUT, + doc=u"""True to store LUT in PostLUT :Type: bool """, diff --git a/pyleecan/Classes/import_all.py b/pyleecan/Classes/import_all.py index a175e8c87..93078bf54 100644 --- a/pyleecan/Classes/import_all.py +++ b/pyleecan/Classes/import_all.py @@ -27,9 +27,6 @@ from ..Classes.EEC_LSRPM import EEC_LSRPM from ..Classes.EEC_PMSM import EEC_PMSM from ..Classes.EEC_SCIM import EEC_SCIM -from ..Classes.ELUT import ELUT -from ..Classes.ELUT_PMSM import ELUT_PMSM -from ..Classes.ELUT_SCIM import ELUT_SCIM from ..Classes.Electrical import Electrical from ..Classes.Elmer import Elmer from ..Classes.ElmerResults import ElmerResults @@ -80,6 +77,9 @@ from ..Classes.InputForce import InputForce from ..Classes.InputVoltage import InputVoltage from ..Classes.Interpolation import Interpolation +from ..Classes.LUT import LUT +from ..Classes.LUTdq import LUTdq +from ..Classes.LUTslip import LUTslip from ..Classes.LamHole import LamHole from ..Classes.LamSlot import LamSlot from ..Classes.LamSlotMag import LamSlotMag @@ -153,8 +153,8 @@ from ..Classes.ParamExplorerSet import ParamExplorerSet from ..Classes.PolarArc import PolarArc from ..Classes.Post import Post -from ..Classes.PostELUT import PostELUT from ..Classes.PostFunction import PostFunction +from ..Classes.PostLUT import PostLUT from ..Classes.PostMethod import PostMethod from ..Classes.PostPlot import PostPlot from ..Classes.RefCell import RefCell diff --git a/pyleecan/Functions/load_switch.py b/pyleecan/Functions/load_switch.py index 158fce79e..1179ff4cd 100644 --- a/pyleecan/Functions/load_switch.py +++ b/pyleecan/Functions/load_switch.py @@ -29,9 +29,6 @@ "EEC_LSRPM": EEC_LSRPM, "EEC_PMSM": EEC_PMSM, "EEC_SCIM": EEC_SCIM, - "ELUT": ELUT, - "ELUT_PMSM": ELUT_PMSM, - "ELUT_SCIM": ELUT_SCIM, "Electrical": Electrical, "Elmer": Elmer, "ElmerResults": ElmerResults, @@ -82,6 +79,9 @@ "InputForce": InputForce, "InputVoltage": InputVoltage, "Interpolation": Interpolation, + "LUT": LUT, + "LUTdq": LUTdq, + "LUTslip": LUTslip, "LamHole": LamHole, "LamSlot": LamSlot, "LamSlotMag": LamSlotMag, @@ -155,8 +155,8 @@ "ParamExplorerSet": ParamExplorerSet, "PolarArc": PolarArc, "Post": Post, - "PostELUT": PostELUT, "PostFunction": PostFunction, + "PostLUT": PostLUT, "PostMethod": PostMethod, "PostPlot": PostPlot, "RefCell": RefCell, diff --git a/pyleecan/Generator/ClassesRef/Post/PostELUT.csv b/pyleecan/Generator/ClassesRef/Post/PostELUT.csv deleted file mode 100644 index 6c4981b9c..000000000 --- a/pyleecan/Generator/ClassesRef/Post/PostELUT.csv +++ /dev/null @@ -1,4 +0,0 @@ -Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -ELUT,,Electrical Look-Up Table to enforce,,ELUT,None,,,,Post,PostMethod,run,VERSION,1,Class to generate an ELUT after the corresponding simulation -is_save_ELUT,,True to save ELUT in PostELUT,,bool,1,,,,,,,,, -is_store_ELUT,,True to store ELUT in PostELUT,,bool,1,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Post/PostLUT.csv b/pyleecan/Generator/ClassesRef/Post/PostLUT.csv new file mode 100644 index 000000000..39b7de8a4 --- /dev/null +++ b/pyleecan/Generator/ClassesRef/Post/PostLUT.csv @@ -0,0 +1,4 @@ +Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description +LUT,,Look-Up Table to enforce,,LUT,None,,,,Post,PostMethod,run,VERSION,1,Class to generate a LUT after the corresponding simulation +is_save_LUT,,True to save LUT in PostLUT,,bool,1,,,,,,,,, +is_store_LUT,,True to store LUT in PostLUT,,bool,1,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/Electrical.csv b/pyleecan/Generator/ClassesRef/Simulation/Electrical.csv index 2104fb065..3174e2f7c 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/Electrical.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/Electrical.csv @@ -5,4 +5,4 @@ type_skin_effect,-,Skin effect for resistance and inductance,1,int,0,,,,,,comp_t Tsta,deg Celsius,Average stator temperature for operational EEC calculation,0,float,20,,,,,,,,,, Trot,deg Celsius,Average rotor temperature for operational EEC calculation,0,float,20,,,,,,,,,, Tmag,deg Celsius,Average magnet temperature for operational EEC calculation,0,float,20,,,,,,,,,, -ELUT_enforced,-,Electrical Look Up Table to be enforced ,,ELUT,None,,,,,,,,,, +ELUT_enforced,-,Electrical Look Up Table to be enforced ,,LUT,None,,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/ELUT.csv b/pyleecan/Generator/ClassesRef/Simulation/LUT.csv similarity index 95% rename from pyleecan/Generator/ClassesRef/Simulation/ELUT.csv rename to pyleecan/Generator/ClassesRef/Simulation/LUT.csv index f071da89c..3c0180f1a 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/ELUT.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/LUT.csv @@ -1,5 +1,5 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille -R1,Ohm,DC phase winding resistance at T1_ref per phase ,0,float,None,,,,Simulation,,get_param_dict,VERSION,1,Abstract class for Electrical Look Up Table (ELUT), +R1,Ohm,DC phase winding resistance at T1_ref per phase ,0,float,None,,,,Simulation,,get_param_dict,VERSION,1,Abstract class for Look Up Table (LUT), L1,H,Phase winding leakage inductance ,0,float,None,,,,,,,,,, T1_ref,degC,"Stator winding average temperature associated to R1, L1 parameters",0,float,20,,,,,,,,,, OP_matrix,,"Array of operating point values of size (N,5) with N the number of operating points (Speed, Id, Iq, Torque, Power). OP values are set to nan if they are not given.",,ndarray,None,,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv b/pyleecan/Generator/ClassesRef/Simulation/LUTdq.csv similarity index 92% rename from pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv rename to pyleecan/Generator/ClassesRef/Simulation/LUTdq.csv index f374d5a5a..1878c15aa 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/ELUT_PMSM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/LUTdq.csv @@ -1,5 +1,5 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille -Phi_dqh_mean,Wbrms,RMS stator winding flux table in dqh frame (including magnets and currents given by I_dqh),"(N_dq, 3)",ndarray,None,,,,Simulation,ELUT,get_param_dict,VERSION,1,ELUT class for PMSM, +Phi_dqh_mean,Wbrms,RMS stator winding flux table in dqh frame (including magnets and currents given by I_dqh),"(N_dq, 3)",ndarray,None,,,,Simulation,LUT,get_param_dict,VERSION,1,Look Up Table class for dq OP matrix, I_dqh,Arms,RMS Id Iq Ih table corresponding to flux linkage table given in Phi_dqh_mean,"(N_dq, 3)",list,None,,,,,,get_Lq,,,, Tmag_ref,degC,Magnet average temperature at which Phi_dqh is given,,float,20,,,,,,get_bemf,,,, Phi_dqh_mag,Wbrms,RMS stator winding flux linkage spectrum in dqh frame including harmonics (only magnets),"(Nharm, 3)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Ld,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/ELUT_SCIM.csv b/pyleecan/Generator/ClassesRef/Simulation/LUTslip.csv similarity index 85% rename from pyleecan/Generator/ClassesRef/Simulation/ELUT_SCIM.csv rename to pyleecan/Generator/ClassesRef/Simulation/LUTslip.csv index d71aa6265..1dfd2f1c7 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/ELUT_SCIM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/LUTslip.csv @@ -1,5 +1,5 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille -Phi_m,Wb,Magnetizing flux for a given magnetizing current I_m,,ndarray,None,,,,Simulation,ELUT,get_param_dict,VERSION,1,ELUT class for SCIM, +Phi_m,Wb,Magnetizing flux for a given magnetizing current I_m,,ndarray,None,,,,Simulation,LUT,get_param_dict,VERSION,1,Look Up Table class for u0/slip OP matrix, I_m,Arms,Stator magnetizing current,,ndarray,None,,,,,,get_Lm,,,, T2_ref,degC,Rotor bar average temperature at which Phi_m is given,0,float,20,,,,,,comp_Lm_from_Phim,,,, R2,Ohm,DC rotor winding resistance at T2_ref already expressed per phase in stator frame ,0,float,None,,,,,,import_from_data,,,, diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py index 8a0043001..f360090f8 100644 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py @@ -1,10 +1,10 @@ from numpy import pi, linspace, zeros, ones, dot, squeeze from SciDataTool import Data1D, DataTime, Norm_ref -from ....Functions.Electrical.coordinate_transformation import dq2n +from ....Functions.Electrical.coordinate_transformation import dqh2n from ....Functions.Winding.gen_phase_list import gen_name -def comp_mmf_unit(self, Na=None, Nt=None, freq=1): +def comp_mmf_unit(self, Na=None, Nt=None, freq=1, rot_dir=-1): """Compute the winding Unit magnetomotive force Parameters @@ -17,6 +17,8 @@ def comp_mmf_unit(self, Na=None, Nt=None, freq=1): Time discretization for offline computation (otherwise use out.elec.time) freq : float Stator current frequency to consider + rot_dir : int + Rotation direction (+/- 1) Returns ------- @@ -49,9 +51,9 @@ def comp_mmf_unit(self, Na=None, Nt=None, freq=1): wf = self.comp_wind_function(angle=angle, per_a=per_a) # Compute unit current function of time applying constant Id=1 Arms, Iq=0 - Idq = zeros((Nt, 2)) + Idq = zeros((Nt, 3)) Idq[:, 0] = ones(Nt) - I = dq2n(Idq, 2 * pi * freq * time, n=qs, is_n_rms=False) + I = dqh2n(Idq, rot_dir * 2 * pi * freq * time, n=qs, is_n_rms=False) # Compute unit mmf mmf_u = squeeze(dot(I, wf)) diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py index c76dcbf38..a350e0a7b 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py @@ -6,7 +6,7 @@ from pyleecan.Classes.Winding import Winding -def comp_mmf_unit(self, Na=None, Nt=None, freq=1): +def comp_mmf_unit(self, Na=None, Nt=None, freq=1, rot_dir=-1): """Compute the winding Unit magnetomotive force Parameters @@ -53,7 +53,7 @@ def comp_mmf_unit(self, Na=None, Nt=None, freq=1): # Compute unit current function of time applying constant Id=1 Arms, Iq=0, Ih=0 Idq = zeros((Nt, 3)) Idq[:, 0] = ones(Nt) - I = dqh2n(Idq, 2 * pi * freq * time, n=qs, is_n_rms=False) + I = dqh2n(Idq, rot_dir * 2 * pi * freq * time, n=qs, is_n_rms=False) # Compute unit mmf mmf_u = squeeze(dot(I, wf)) diff --git a/pyleecan/Methods/Post/PostELUT/run.py b/pyleecan/Methods/Post/PostELUT/run.py deleted file mode 100644 index 5c50fb8db..000000000 --- a/pyleecan/Methods/Post/PostELUT/run.py +++ /dev/null @@ -1,74 +0,0 @@ -from numpy import array, zeros, nan -from os.path import join - -from ....Classes.ELUT_PMSM import ELUT_PMSM -from ....Functions.Load.import_class import import_class - - -def run(self, out): - """PostProcessing to generate an ELUT after the corresponding simulation - - Parameters - ---------- - self : PostELUT - A PostELUT object - out: Output - Output object coming from ELUT calculation workflow - - """ - - if out.simu.machine.is_synchronous(): - # Init ELUT object - ELUT = ELUT_PMSM() - - XOutput = import_class("pyleecan.Classes", "XOutput") - - if not isinstance(out, XOutput): - raise Exception("Need an XOutput to compute ELUT") - else: - # Store data for each OP - # Number of columns in OP_matrix - ndim = out.simu.var_simu.OP_matrix.shape[1] - - # Store operating point matrix in VarLoadCurrent object - ELUT.OP_matrix = nan * zeros((out.nb_simu, 5)) - ELUT.OP_matrix[:, 0] = array(out["N0"].result) - ELUT.OP_matrix[:, 1] = array(out["Id"].result) - ELUT.OP_matrix[:, 2] = array(out["Iq"].result) - if ndim > 3: - for ii in range(3, ndim): - ELUT.OP_matrix[:, ii] = array(out.simu.var_simu.OP_matrix[:, ii]) - - # Fill ELUT variables - dk_list = list(out.keys()) - if "Phi_{dq}" in dk_list: - ELUT.Phi_dqh = out["Phi_{dq}"].result - - # Find Id=Iq=0 - OP_list = ELUT.OP_matrix[:, 1:3].tolist() - if [0, 0] in OP_list: - ii = OP_list.index([0, 0]) - else: - raise Exception("Operating Point Id=Iq=0 is required to compute ELUT") - # Compute back electromotive force - out.output_list[ii].mag.comp_emf() - BEMF = out.output_list[ii].mag.emf - BEMF.name = "Stator Winding Back Electromotive Force" - BEMF.symbol = "BEMF" - ELUT.bemf = BEMF - - # # Set if the interpolation must be made along a curve - # ELUT.set_is_interp_along_curve() - - if self.is_save_ELUT: - # Save ELUT object - ELUT.save( - save_path=join(out.get_path_result(), "ELUT.h5"), - ) - - if self.is_store_ELUT: - # Store ELUT in PostELUT object - self.ELUT = ELUT - - else: - raise Exception("PostELUT for asynchronous machines not developed yet") diff --git a/pyleecan/Methods/Post/PostELUT/__init__.py b/pyleecan/Methods/Post/PostLUT/__init__.py similarity index 100% rename from pyleecan/Methods/Post/PostELUT/__init__.py rename to pyleecan/Methods/Post/PostLUT/__init__.py diff --git a/pyleecan/Methods/Post/PostLUT/run.py b/pyleecan/Methods/Post/PostLUT/run.py new file mode 100644 index 000000000..24f628bbb --- /dev/null +++ b/pyleecan/Methods/Post/PostLUT/run.py @@ -0,0 +1,74 @@ +from numpy import array, zeros, nan +from os.path import join + +from ....Classes.LUTdq import LUTdq +from ....Functions.Load.import_class import import_class + + +def run(self, out): + """PostProcessing to generate a LUT after the corresponding simulation + + Parameters + ---------- + self : PostLUT + A PostLUT object + out: Output + Output object coming from LUT calculation workflow + + """ + + if out.simu.machine.is_synchronous(): + # Init LUT object + LUT = LUTdq() + + XOutput = import_class("pyleecan.Classes", "XOutput") + + if not isinstance(out, XOutput): + raise Exception("Need an XOutput to compute LUT") + else: + # Store data for each OP + # Number of columns in OP_matrix + ndim = out.simu.var_simu.OP_matrix.shape[1] + + # Store operating point matrix in VarLoadCurrent object + LUT.OP_matrix = nan * zeros((out.nb_simu, 5)) + LUT.OP_matrix[:, 0] = array(out["N0"].result) + LUT.OP_matrix[:, 1] = array(out["Id"].result) + LUT.OP_matrix[:, 2] = array(out["Iq"].result) + if ndim > 3: + for ii in range(3, ndim): + LUT.OP_matrix[:, ii] = array(out.simu.var_simu.OP_matrix[:, ii]) + + # Fill LUT variables + dk_list = list(out.keys()) + if "Phi_{dq}" in dk_list: + LUT.Phi_dqh = out["Phi_{dq}"].result + + # Find Id=Iq=0 + OP_list = LUT.OP_matrix[:, 1:3].tolist() + if [0, 0] in OP_list: + ii = OP_list.index([0, 0]) + else: + raise Exception("Operating Point Id=Iq=0 is required to compute LUT") + # Compute back electromotive force + out.output_list[ii].mag.comp_emf() + BEMF = out.output_list[ii].mag.emf + BEMF.name = "Stator Winding Back Electromotive Force" + BEMF.symbol = "BEMF" + LUT.bemf = BEMF + + # # Set if the interpolation must be made along a curve + # LUT.set_is_interp_along_curve() + + if self.is_save_LUT: + # Save LUT object + LUT.save( + save_path=join(out.get_path_result(), "LUT.h5"), + ) + + if self.is_store_LUT: + # Store LUT in PostLUT object + self.LUT = LUT + + else: + raise Exception("PostLUT for asynchronous machines not developed yet") diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Ldqh_from_Phidqh.py b/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Ldqh_from_Phidqh.py deleted file mode 100644 index bc721911f..000000000 --- a/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Ldqh_from_Phidqh.py +++ /dev/null @@ -1,3 +0,0 @@ -def comp_Ldqh_from_Phidqh(self): - pass - # TODO diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Phidqh_from_Phiwind.py b/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Phidqh_from_Phiwind.py deleted file mode 100644 index 8b871bdb7..000000000 --- a/pyleecan/Methods/Simulation/ELUT_PMSM/comp_Phidqh_from_Phiwind.py +++ /dev/null @@ -1,3 +0,0 @@ -def comp_Phidqh_from_Phiwind(self): - pass - # TODO diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Ld.py b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Ld.py deleted file mode 100644 index 062c2b9eb..000000000 --- a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Ld.py +++ /dev/null @@ -1,3 +0,0 @@ -def get_Ld(self): - pass - # TODO diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmd.py b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmd.py deleted file mode 100644 index d46680a90..000000000 --- a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmd.py +++ /dev/null @@ -1,3 +0,0 @@ -def get_Lmd(self): - pass - # TODO diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmq.py b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmq.py deleted file mode 100644 index 982a723e9..000000000 --- a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lmq.py +++ /dev/null @@ -1,3 +0,0 @@ -def get_Lmq(self): - pass - # TODO diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lq.py b/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lq.py deleted file mode 100644 index edd1b173c..000000000 --- a/pyleecan/Methods/Simulation/ELUT_PMSM/get_Lq.py +++ /dev/null @@ -1,3 +0,0 @@ -def get_Lq(self): - pass - # TODO diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index eab908362..1d0d623a5 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -69,7 +69,7 @@ def gen_input(self): Idq = n2dqh( outelec.Is.values, Time.get_values(is_oneperiod=False, normalization="angle_elec"), - is_dq_rms=True, + is_dqh_rms=True, ) outelec.Id_ref = mean(Idq[:, 0]) outelec.Iq_ref = mean(Idq[:, 1]) diff --git a/pyleecan/Methods/Simulation/ELUT/__init__.py b/pyleecan/Methods/Simulation/LUT/__init__.py similarity index 100% rename from pyleecan/Methods/Simulation/ELUT/__init__.py rename to pyleecan/Methods/Simulation/LUT/__init__.py diff --git a/pyleecan/Methods/Simulation/ELUT/get_param_dict.py b/pyleecan/Methods/Simulation/LUT/get_param_dict.py similarity index 100% rename from pyleecan/Methods/Simulation/ELUT/get_param_dict.py rename to pyleecan/Methods/Simulation/LUT/get_param_dict.py diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/__init__.py b/pyleecan/Methods/Simulation/LUTdq/__init__.py similarity index 100% rename from pyleecan/Methods/Simulation/ELUT_PMSM/__init__.py rename to pyleecan/Methods/Simulation/LUTdq/__init__.py diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Ld.py b/pyleecan/Methods/Simulation/LUTdq/get_Ld.py new file mode 100644 index 000000000..11f29b37c --- /dev/null +++ b/pyleecan/Methods/Simulation/LUTdq/get_Ld.py @@ -0,0 +1,18 @@ +def get_Ld(self, L_endwinding): + """Get the total d-axis inductance + Parameters + ---------- + self : LUTdq + a LUTdq object + L_endwinding : float + end winding inductance provided by user + + Returns + ---------- + Ld : ndarray + d-axis inductance + """ + + Lmd = self.get_Lmd() + + return Lmd + L_endwinding diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Lmd.py b/pyleecan/Methods/Simulation/LUTdq/get_Lmd.py new file mode 100644 index 000000000..09f660476 --- /dev/null +++ b/pyleecan/Methods/Simulation/LUTdq/get_Lmd.py @@ -0,0 +1,20 @@ +def get_Lmd(self): + """Get the magnets d-axis inductance + Parameters + ---------- + self : LUTdq + a LUTdq object + + Returns + ---------- + Lmd : ndarray + magnets d-axis inductance + """ + + Phi_d = self.get_Phidqh_mean()[:, 0] + Phi_mag = self.get_Phidqh_mag_mean()[0] + Id = self.Idqh[:, 0] + + Lmd = (Phi_d - Phi_mag) / Id + + return Lmd diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Lmq.py b/pyleecan/Methods/Simulation/LUTdq/get_Lmq.py new file mode 100644 index 000000000..75e63da7f --- /dev/null +++ b/pyleecan/Methods/Simulation/LUTdq/get_Lmq.py @@ -0,0 +1,20 @@ +def get_Lmq(self): + """Get the magnets q-axis inductance + Parameters + ---------- + self : LUTdq + a LUTdq object + + Returns + ---------- + Lmq : ndarray + magnets q-axis inductance + """ + + Phi_q = self.get_Phidqh_mean()[:, 1] + Phi_mag = self.get_Phidqh_mag_mean()[1] + Iq = self.Idqh[:, 1] + + Lmq = (Phi_q - Phi_mag) / Iq + + return Lmq diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Lq.py b/pyleecan/Methods/Simulation/LUTdq/get_Lq.py new file mode 100644 index 000000000..e215ea5a4 --- /dev/null +++ b/pyleecan/Methods/Simulation/LUTdq/get_Lq.py @@ -0,0 +1,18 @@ +def get_Lq(self, L_endwinding): + """Get the total q-axis inductance + Parameters + ---------- + self : LUTdq + a LUTdq object + L_endwinding : float + end winding inductance provided by user + + Returns + ---------- + Lq : ndarray + q-axis inductance + """ + + Lmq = self.get_Lmq() + + return Lmq + L_endwinding diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py new file mode 100644 index 000000000..ade58ac0b --- /dev/null +++ b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py @@ -0,0 +1,3 @@ +def get_Phidqh_mag(self): + pass + # TODO diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_harm.py b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_harm.py new file mode 100644 index 000000000..17ab718a3 --- /dev/null +++ b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_harm.py @@ -0,0 +1,3 @@ +def get_Phidqh_mag_harm(self): + pass + # TODO diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_mean.py b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_mean.py new file mode 100644 index 000000000..a60920cce --- /dev/null +++ b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_mean.py @@ -0,0 +1,3 @@ +def get_Phidqh_mag_mean(self): + pass + # TODO diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py new file mode 100644 index 000000000..4b60aceeb --- /dev/null +++ b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py @@ -0,0 +1,3 @@ +def get_Phidqh_mean(self): + pass + # TODO diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_bemf.py b/pyleecan/Methods/Simulation/LUTdq/get_bemf.py similarity index 91% rename from pyleecan/Methods/Simulation/ELUT_PMSM/get_bemf.py rename to pyleecan/Methods/Simulation/LUTdq/get_bemf.py index 065306926..7c196e15b 100644 --- a/pyleecan/Methods/Simulation/ELUT_PMSM/get_bemf.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_bemf.py @@ -2,8 +2,8 @@ def get_bemf(self): """Get the phase to phase back electromotive force magnitude [V] from ELUT flux linkage data Parameters ---------- - self : ELUT - an ELUT_PMSM object + self : LUTdq + a LUTdq object Returns ---------- diff --git a/pyleecan/Methods/Simulation/LUTdq/get_orders_dqh.py b/pyleecan/Methods/Simulation/LUTdq/get_orders_dqh.py new file mode 100644 index 000000000..70da0e9f6 --- /dev/null +++ b/pyleecan/Methods/Simulation/LUTdq/get_orders_dqh.py @@ -0,0 +1,3 @@ +def get_orders_dqh(self): + pass + # TODO diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/get_param_dict.py b/pyleecan/Methods/Simulation/LUTdq/get_param_dict.py similarity index 94% rename from pyleecan/Methods/Simulation/ELUT_PMSM/get_param_dict.py rename to pyleecan/Methods/Simulation/LUTdq/get_param_dict.py index dbad16944..f19babef0 100644 --- a/pyleecan/Methods/Simulation/ELUT_PMSM/get_param_dict.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_param_dict.py @@ -5,8 +5,8 @@ def get_param_dict(self): """Get the parameters dict for the ELUT of PMSM at the operationnal temperature and frequency Parameters ---------- - self : ELUT - an ELUT_PMSM object + self : LUTdq + a LUTdq object Returns ---------- diff --git a/pyleecan/Methods/Simulation/ELUT_PMSM/import_from_data.py b/pyleecan/Methods/Simulation/LUTdq/import_from_data.py similarity index 100% rename from pyleecan/Methods/Simulation/ELUT_PMSM/import_from_data.py rename to pyleecan/Methods/Simulation/LUTdq/import_from_data.py diff --git a/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py b/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py new file mode 100644 index 000000000..a1a6e7012 --- /dev/null +++ b/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py @@ -0,0 +1,3 @@ +def interp_Phi_dqh(self): + pass + # TODO diff --git a/pyleecan/Methods/Simulation/ELUT_SCIM/__init__.py b/pyleecan/Methods/Simulation/LUTslip/__init__.py similarity index 100% rename from pyleecan/Methods/Simulation/ELUT_SCIM/__init__.py rename to pyleecan/Methods/Simulation/LUTslip/__init__.py diff --git a/pyleecan/Methods/Simulation/ELUT_SCIM/comp_Lm_from_Phim.py b/pyleecan/Methods/Simulation/LUTslip/comp_Lm_from_Phim.py similarity index 100% rename from pyleecan/Methods/Simulation/ELUT_SCIM/comp_Lm_from_Phim.py rename to pyleecan/Methods/Simulation/LUTslip/comp_Lm_from_Phim.py diff --git a/pyleecan/Methods/Simulation/ELUT_SCIM/get_Lm.py b/pyleecan/Methods/Simulation/LUTslip/get_Lm.py similarity index 100% rename from pyleecan/Methods/Simulation/ELUT_SCIM/get_Lm.py rename to pyleecan/Methods/Simulation/LUTslip/get_Lm.py diff --git a/pyleecan/Methods/Simulation/ELUT_SCIM/get_param_dict.py b/pyleecan/Methods/Simulation/LUTslip/get_param_dict.py similarity index 100% rename from pyleecan/Methods/Simulation/ELUT_SCIM/get_param_dict.py rename to pyleecan/Methods/Simulation/LUTslip/get_param_dict.py diff --git a/pyleecan/Methods/Simulation/ELUT_SCIM/import_from_data.py b/pyleecan/Methods/Simulation/LUTslip/import_from_data.py similarity index 100% rename from pyleecan/Methods/Simulation/ELUT_SCIM/import_from_data.py rename to pyleecan/Methods/Simulation/LUTslip/import_from_data.py From 211028f70619200f63a373d31740b01924c9f210 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Mon, 11 Oct 2021 17:37:14 +0200 Subject: [PATCH 083/167] [BC] Remove rot_dir from coordinate_transformation.py as rot_dir is already included in angle_elec [CO] Rework comp_mmf_unit using new Input.comp_axes method and normalizations [CO] Enable comp_axes and comp_felec method without simu parent of Input [CO] Store phase axis in axes_dict with generic key "phase_" + lamination.get_label() --- .../test_coordinate_transformation.py | 2 +- Tests/Methods/Simulation/test_Electrical.py | 15 ++- .../Electrical/coordinate_transformation.py | 38 ++----- pyleecan/Functions/Load/import_class.py | 2 +- .../Machine/LamSlotWind/comp_mmf_unit.py | 102 +++++++++--------- .../Machine/LamSlotWind/comp_rot_dir.py | 7 +- .../Machine/LamSlotWind/comp_wind_function.py | 3 +- .../Machine/LamSlotWind/plot_mmf_unit.py | 9 +- pyleecan/Methods/Output/OutElec/get_I_fund.py | 3 +- .../Methods/Simulation/Input/comp_axes.py | 46 ++++---- .../Simulation/Input/comp_axis_time.py | 37 ++++--- .../Simulation/InputCurrent/gen_input.py | 3 +- .../Simulation/InputVoltage/comp_felec.py | 32 +++--- 13 files changed, 145 insertions(+), 154 deletions(-) diff --git a/Tests/Functions/test_coordinate_transformation.py b/Tests/Functions/test_coordinate_transformation.py index ea021f5f0..875f66788 100644 --- a/Tests/Functions/test_coordinate_transformation.py +++ b/Tests/Functions/test_coordinate_transformation.py @@ -60,7 +60,7 @@ def test_coordinate_transformation(param_dict): for phase in phase_list: In = zeros((Nt, qs)) for ii in range(qs): - In[:, ii] = cos(angle_elec + phase + rot_dir * 2 * ii * pi / qs) + In[:, ii] = cos(angle_elec + phase + 2 * ii * pi / qs) Idqh_rms = n2dqh(In, angle_elec, is_dqh_rms=True) Idqh_amp = n2dqh(In, angle_elec, is_dqh_rms=False) diff --git a/Tests/Methods/Simulation/test_Electrical.py b/Tests/Methods/Simulation/test_Electrical.py index 52661caed..149fd2922 100644 --- a/Tests/Methods/Simulation/test_Electrical.py +++ b/Tests/Methods/Simulation/test_Electrical.py @@ -1,11 +1,6 @@ -# -*- coding: utf-8 -*- - import pytest from numpy import pi from os.path import join -from pyleecan.Classes.LamSlotWind import LamSlotWind -from pyleecan.Classes.SlotW11 import SlotW11 -from pyleecan.Classes.CondType12 import CondType12 from pyleecan.Functions.load import load from pyleecan.definitions import DATA_DIR @@ -34,15 +29,15 @@ def test_DQ_axis_rotor(self, Toyota_Prius): def test_DQ_axis_stator(self, Toyota_Prius): """Check that the DQ axis are correct for the stator""" d_axis = Toyota_Prius.stator.comp_angle_d_axis() - assert d_axis == pytest.approx(1.3096, abs=0.001) + assert d_axis == pytest.approx(1.3076, abs=0.001) q_axis = Toyota_Prius.stator.comp_angle_q_axis() - assert q_axis == pytest.approx(1.3096 + pi / 8, abs=0.001) + assert q_axis == pytest.approx(1.3076 + pi / 8, abs=0.001) def test_comp_rot_dir(self, Toyota_Prius): """Check that the computation of the rot dir is correct""" rot_dir = Toyota_Prius.stator.comp_rot_dir() - assert rot_dir == -1 + assert rot_dir == -1, "rot_dir=" + str(rot_dir) + " instead of -1" def test_comp_rot_dir_reverse_wind(self, Toyota_Prius): """Check that the computation of the rot dir is correct when reversing the winding""" @@ -57,4 +52,8 @@ def test_comp_rot_dir_reverse_wind(self, Toyota_Prius): if __name__ == "__main__": a = Test_Electrical() toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) + a.test_resistance(toyota_Prius) + a.test_DQ_axis_rotor(toyota_Prius) + a.test_DQ_axis_stator(toyota_Prius) a.test_comp_rot_dir(toyota_Prius) + a.test_comp_rot_dir_reverse_wind(toyota_Prius) diff --git a/pyleecan/Functions/Electrical/coordinate_transformation.py b/pyleecan/Functions/Electrical/coordinate_transformation.py index 93d2d6918..ee5e96e13 100644 --- a/pyleecan/Functions/Electrical/coordinate_transformation.py +++ b/pyleecan/Functions/Electrical/coordinate_transformation.py @@ -161,15 +161,7 @@ def n2dqh(Z_n, angle_elec, is_dqh_rms=True): Z_dqh : ndarray transformed matrix (N x 3) of dqh equivalent values """ - - if angle_elec.size > 1: - # Get rotating direction by looking at sign of angle_elec derivate - rot_dir = np.mean(np.sign(np.diff(angle_elec))) - else: - # Default rotating direction in pyleecan - rot_dir = -1 - - Z_dqh = abc2dqh(n2abc(Z_n, rot_dir), angle_elec) + Z_dqh = abc2dqh(n2abc(Z_n), angle_elec) if is_dqh_rms: # Divide by sqrt(2) to go from (Id_peak, Iq_peak) to (Id_rms, Iq_rms) @@ -198,14 +190,7 @@ def dqh2n(Z_dqh, angle_elec, n, is_n_rms=False): transformed matrix (N x n) of n phase values """ - if angle_elec.size > 1: - # Get rotating direction by looking at sign of angle_elec derivate - rot_dir = np.mean(np.sign(np.diff(angle_elec))) - else: - # Default rotating direction in pyleecan - rot_dir = -1 - - Z_n = abc2n(dqh2abc(Z_dqh, angle_elec), n, rot_dir) + Z_n = abc2n(dqh2abc(Z_dqh, angle_elec), n) if not is_n_rms: # Multiply by sqrt(2) to from (I_n_rms) to (I_n_peak) @@ -214,7 +199,7 @@ def dqh2n(Z_dqh, angle_elec, n, is_n_rms=False): return Z_n -def abc2n(Z_abc, n=3, rot_dir=-1): +def abc2n(Z_abc, n=3): """3 phase equivalent to n phase coordinate transformation, i.e. Clarke transformation Parameters @@ -223,8 +208,6 @@ def abc2n(Z_abc, n=3, rot_dir=-1): matrix (N x 3) of 3 phase equivalent values in alpha-beta-gamma frame n: int number of phases - rot_dir : integer - rotation direction of the fundamental of magnetic field (rot_dir = +/- 1) Returns ------- @@ -234,22 +217,20 @@ def abc2n(Z_abc, n=3, rot_dir=-1): """ # Inverse of Clarke transformation matrix - ab_2_n = comp_Clarke_transform(n, rot_dir, is_inv=True) + ab_2_n = comp_Clarke_transform(n, is_inv=True) Z_n = np.matmul(Z_abc, ab_2_n) return Z_n -def n2abc(Z_n, rot_dir=-1): +def n2abc(Z_n): """n phase to 3 phases equivalent coordinate transformation, i.e. Clarke transformation Parameters ---------- Z_n : ndarray matrix (N x n) of n phase values - rot_dir : integer - rotation direction of the fundamental of magnetic field (rot_dir = +/- 1) Returns ------- @@ -260,7 +241,7 @@ def n2abc(Z_n, rot_dir=-1): n = Z_n.shape[1] - n_2_abc = comp_Clarke_transform(n, rot_dir, is_inv=False) + n_2_abc = comp_Clarke_transform(n, is_inv=False) Z_abc = np.matmul(Z_n, n_2_abc) @@ -334,15 +315,13 @@ def dqh2abc(Z_dqh, angle_elec): return Z_abc -def comp_Clarke_transform(n, rot_dir, is_inv=False): +def comp_Clarke_transform(n, is_inv=False): """Compute Clarke transformation for given number of phases and rotating direction of phases Parameters ---------- n : int number of phases - rot_dir : integer - rotation direction of the fundamental of magnetic field (rot_dir = +/- 1) is_inv: bool False to return Clarke transform, True to return inverse of Clarke transform @@ -354,8 +333,7 @@ def comp_Clarke_transform(n, rot_dir, is_inv=False): """ # Phasor depending on fundamental field rotation direction - ii = np.linspace(0, n - 1, n) - phasor = rot_dir * 2 * ii * np.pi / n + phasor = np.linspace(0, 2 * np.pi * (n - 1) / n, n) # Clarke transformation matrix if is_inv: diff --git a/pyleecan/Functions/Load/import_class.py b/pyleecan/Functions/Load/import_class.py index 226006623..bc7a83a4d 100644 --- a/pyleecan/Functions/Load/import_class.py +++ b/pyleecan/Functions/Load/import_class.py @@ -7,7 +7,7 @@ def import_class(mod_name, class_name, prop_name=""): try: module = __import__(mod_name + "." + class_name, fromlist=[class_name]) return getattr(module, class_name) - except: + except Exception: if prop_name != "": raise InitUnKnowClassError( "Unknow class name " diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py index a350e0a7b..ecd081912 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py @@ -1,92 +1,90 @@ -# -*- coding: utf-8 -*- -from numpy import pi, linspace, zeros, ones, dot, squeeze -from SciDataTool import Data1D, DataTime, Norm_ref +from numpy import zeros, ones, dot, squeeze + +from SciDataTool import DataTime + from ....Functions.Electrical.coordinate_transformation import dqh2n -from ....Functions.Winding.gen_phase_list import gen_name -from pyleecan.Classes.Winding import Winding +from ....Functions.Load.import_class import import_class -def comp_mmf_unit(self, Na=None, Nt=None, freq=1, rot_dir=-1): - """Compute the winding Unit magnetomotive force +def comp_mmf_unit(self, Na, Nt, felec=1, rot_dir=-1): + """Compute the winding unit magnetomotive force Parameters ---------- self : LamSlotWind an LamSlotWind object Na : int - Space discretization for offline computation (otherwise use out.elec.angle) + Space discretization for offline computation Nt : int - Time discretization for offline computation (otherwise use out.elec.time) + Time discretization for offline computation freq : float Stator current frequency to consider Returns ------- - MMF_U : SciDataTool.Classes.DataND.DataND + MMF_U : DataTime Unit magnetomotive force (Na,Nt) - WF : SciDataTool.Classes.DataND.DataND + WF : DataTime Winding functions (qs,Na) """ - # Get stator winding number of phases - qs = self.winding.qs - - # Get number of pole pairs - p = self.get_pole_pair_number() - - # Get spatial symmetry - per_a, _ = self.comp_periodicity_spatial() - - # Define the space dicretization - angle = linspace(0, 2 * pi / per_a, Na, endpoint=False) - - # Define the time dicretization - time = linspace(0, 1 / freq, Nt, endpoint=False) + # Check that parent machine is not None + if self.parent is None: + raise Exception("Cannot calculate mmf unit if parent machine is None") + else: + machine = self.parent # Compute the winding function and mmf if self.winding is None or self.winding.conductor is None: - wf = zeros((qs, Na)) + raise Exception("Cannot calculate mmf unit if winding or conductor is None") else: - wf = self.comp_wind_function(angle=angle, per_a=per_a) + # Get stator winding number of phases + qs = self.winding.qs + + InputCurrent = import_class("pyleecan.Classes", "InputCurrent") + input = InputCurrent(Na_tot=Na, Nt_tot=Nt, felec=felec, rot_dir=rot_dir) + + axes_dict = input.comp_axes( + axes_list=["time", "angle", "phase_S"], + machine=machine, + is_periodicity_t=True, + is_periodicity_a=True, + is_antiper_t=False, + is_antiper_a=False, + ) + + # Compute winding function + angle = axes_dict["angle"].get_values(is_oneperiod=True) + per_a, _ = axes_dict["angle"].get_periodicity() + wf = self.comp_wind_function(angle=angle, per_a=per_a) - # Compute unit current function of time applying constant Id=1 Arms, Iq=0, Ih=0 - Idq = zeros((Nt, 3)) - Idq[:, 0] = ones(Nt) - I = dqh2n(Idq, rot_dir * 2 * pi * freq * time, n=qs, is_n_rms=False) + # Compute unit current function of time applying constant Id=1 Arms, Iq=Ih=0 + angle_elec = axes_dict["time"].get_values( + is_oneperiod=True, normalization="angle_elec" + ) + Idq = zeros((angle_elec.size, 3)) + Idq[:, 0] = ones(angle_elec.size) + I = dqh2n(Idq, angle_elec, n=qs, is_n_rms=False) # Compute unit mmf mmf_u = squeeze(dot(I, wf)) # Create a Data object - Time = Data1D(name="time", unit="s", values=time) - Angle = Data1D( - name="angle", - unit="rad", - symmetries={"period": per_a}, - values=angle, - normalizations={"space_order": Norm_ref(ref=self.get_pole_pair_number())}, - ) - Phase = Data1D( - name="phase", - unit="", - values=gen_name(qs), - is_components=True, - ) MMF_U = DataTime( - name="Unit MMF", + name="Overall MMF", unit="A", - symbol="Magnitude", - axes=[Time, Angle], + symbol="MMF", + axes=[axes_dict["time"], axes_dict["angle"]], values=mmf_u, ) WF = DataTime( - name="Winding Functions", + name="Phase MMF", unit="A", - symbol="Magnitude", - axes=[Phase, Angle], - values=wf, + symbol="MMF", + axes=[axes_dict["angle"], axes_dict["phase_" + self.get_label()]], + values=wf.T, ) return MMF_U, WF diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py b/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py index 5ae6d335d..362eada48 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py @@ -18,12 +18,11 @@ def comp_rot_dir(self): p = self.get_pole_pair_number() # Compute unit mmf - MMF, _ = self.comp_mmf_unit( - Nt=20 * p, Na=20 * p - ) # 20 points per pole over time and space is enough to capture rotating direction of fundamental mmf + # 20 points per pole over time and space is enough to capture rotating direction of fundamental mmf + MMF, _ = self.comp_mmf_unit(Nt=20 * p, Na=20 * p) # Extract fundamental from unit mmf - results = MMF.get_harmonics(1, "freqs", "wavenumber") + results = MMF.get_harmonics(1, "freqs>0", "wavenumber") # Get frequency and wavenumber of fundamental f = results["freqs"][0] diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_wind_function.py b/pyleecan/Methods/Machine/LamSlotWind/comp_wind_function.py index e2cbbc361..3d282adc8 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_wind_function.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_wind_function.py @@ -1,6 +1,5 @@ -# -*- coding: utf-8 -*- - from numpy import linspace, meshgrid, pi, repeat, sum as np_sum, zeros, mean + from ....Functions.Winding.comp_cond_function import comp_cond_function diff --git a/pyleecan/Methods/Machine/LamSlotWind/plot_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotWind/plot_mmf_unit.py index 58962ca8d..af2fb5675 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/plot_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotWind/plot_mmf_unit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import matplotlib.pyplot as plt from ....Functions.Plot import dict_2D @@ -29,9 +28,9 @@ def plot_mmf_unit(self, r_max=100, fig=None, is_show_fig=True): name += "Rotor " # Compute the winding function and mmf - wf = self.comp_wind_function(per_a=1) qs = self.winding.qs - MMF_U, WF = self.comp_mmf_unit(Nt=1, Na=wf.shape[1]) + p = self.get_pole_pair_number() + MMF_U, WF = self.comp_mmf_unit(Nt=1, Na=400 * p) color_list = config_dict["PLOT"]["COLOR_DICT"]["COLOR_LIST"][:qs] @@ -47,7 +46,7 @@ def plot_mmf_unit(self, r_max=100, fig=None, is_show_fig=True): fig=fig, ax=axs[0], is_show_fig=is_show_fig, - win_title=name + "Winding functions", + win_title=name + "phase MMF", **dict_2D_0, ) @@ -59,6 +58,6 @@ def plot_mmf_unit(self, r_max=100, fig=None, is_show_fig=True): fig=fig, ax=axs[1], is_show_fig=is_show_fig, - win_title=name + "Winding functions & MMF FFT", + win_title=name + "phase MMF FFT", **dict_2D_0, ) diff --git a/pyleecan/Methods/Output/OutElec/get_I_fund.py b/pyleecan/Methods/Output/OutElec/get_I_fund.py index 685d6e90d..f5f0e593f 100644 --- a/pyleecan/Methods/Output/OutElec/get_I_fund.py +++ b/pyleecan/Methods/Output/OutElec/get_I_fund.py @@ -18,9 +18,10 @@ def get_I_fund(self, Time=None): Time = self.axes_dict["time"] angle_elec = Time.get_values(is_smallestperiod=True, normalization="angle_elec") qs = self.parent.simu.machine.stator.winding.qs + stator_label = "phase_" + self.parent.simu.machine.stator.get_label() felec = self.felec - Phase = self.axes_dict["phase_S"] + Phase = self.axes_dict[stator_label] if self.Is is None: if ( diff --git a/pyleecan/Methods/Simulation/Input/comp_axes.py b/pyleecan/Methods/Simulation/Input/comp_axes.py index 5e3de548d..a3d1eb56b 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axes.py +++ b/pyleecan/Methods/Simulation/Input/comp_axes.py @@ -42,16 +42,18 @@ def comp_axes( dict of axes containing requested axes """ + if len(axes_list) == 0: + raise Exception("axes_list should not be empty") if self.parent is not None: simu = self.parent else: - raise Exception("Cannot calculate axes if parent simu is None") + simu = None - if hasattr(self.parent, "parent") and self.parent.parent is not None: + if hasattr(simu, "parent") and simu.parent is not None: output = simu.parent else: - raise Exception("Cannot calculate axes if parent output is None") + output = None if (axes_list is None or len(axes_list) == 0) and ( axes_dict_in is None or len(axes_dict_in) == 0 @@ -60,9 +62,6 @@ def comp_axes( "Cannot calculate axes if both axes list and axes dict are None" ) - if len(axes_list) == 0: - raise Exception("axes_list should not be empty") - if machine is None: # Fetch machine from input if hasattr(simu, "machine") and simu.machine is not None: @@ -75,13 +74,18 @@ def comp_axes( # Fill periodicity parameters that are None if per_a is None or is_antiper_a is None or per_t is None or is_antiper_t is None: - # Get time and space (anti-)periodicities of the machine - ( - per_a_0, - is_antiper_a_0, - per_t_0, - is_antiper_t_0, - ) = output.get_machine_periodicity() + if output is not None: + # Get time and space (anti-)periodicities from the output + ( + per_a_0, + is_antiper_a_0, + per_t_0, + is_antiper_t_0, + ) = output.get_machine_periodicity() + else: + # Compute time and space (anti-)periodicities from the machine + per_a_0, is_antiper_a_0 = machine.comp_periodicity_spatial() + per_t_0, is_antiper_t_0, _, _ = machine.comp_periodicity_time() if is_periodicity_t is None or is_periodicity_t: # Enforce None values to machine time periodicity @@ -120,7 +124,7 @@ def comp_axes( Time_in = None # Calculate time axis - Time = self.comp_axis_time(output, p, per_t, is_antiper_t, Time_in) + Time = self.comp_axis_time(p, per_t, is_antiper_t, Time_in, output) # Store time axis in dict axes_dict["time"] = Time @@ -146,8 +150,9 @@ def comp_axes( if "phase_S" in axes_list: # Check if Phase is already in input dict of axes - if axes_dict_in is not None and "phase_S" in axes_dict_in: - Phase_in = axes_dict_in["phase_S"] + stator_label = "phase_" + machine.stator.get_label() + if axes_dict_in is not None and stator_label in axes_dict_in: + Phase_in = axes_dict_in[stator_label] else: Phase_in = None @@ -156,13 +161,14 @@ def comp_axes( if Phase is not None: # Store phase axis in dict - axes_dict["phase_S"] = Phase + axes_dict[stator_label] = Phase if "phase_R" in axes_list: # Check if Phase is already in input dict of axes - if axes_dict_in is not None and "phase_R" in axes_dict_in: - Phase_in = axes_dict_in["phase_R"] + rotor_label = "phase_" + machine.rotor.get_label() + if axes_dict_in is not None and rotor_label in axes_dict_in: + Phase_in = axes_dict_in[rotor_label] else: Phase_in = None @@ -172,6 +178,6 @@ def comp_axes( if Phase is not None: # Store phase axis in dict - axes_dict["phase_R"] = Phase + axes_dict[rotor_label] = Phase return axes_dict diff --git a/pyleecan/Methods/Simulation/Input/comp_axis_time.py b/pyleecan/Methods/Simulation/Input/comp_axis_time.py index e576d39c5..f5df46c4b 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axis_time.py +++ b/pyleecan/Methods/Simulation/Input/comp_axis_time.py @@ -2,18 +2,14 @@ from SciDataTool import Data1D, DataLinspace, Norm_ref -from ....Methods.Simulation.Input import InputError - -def comp_axis_time(self, output, p, per_t, is_antiper_t, Time_in=None): +def comp_axis_time(self, p, per_t, is_antiper_t, Time_in=None, output=None): """Compute time axis, with or without periodicities and including normalizations Parameters ---------- self : Input an Input object - output: Output - An output object to calculate angle_rotor normalization p: int Number of pole pairs per_t : int @@ -22,17 +18,28 @@ def comp_axis_time(self, output, p, per_t, is_antiper_t, Time_in=None): if the time axis is antiperiodic Time_in: Data Input time axis + output: Output + An output object to calculate angle_rotor normalization Returns ------- Time: Data Requested Time axis """ + + logger = self.get_logger() + # Get electrical fundamental frequency - f_elec = self.comp_felec() + f_elec = self.comp_felec(p=p) # Get magnetic field rotation direction - rot_dir = output.get_rot_dir() + if output is not None: + rot_dir = output.get_rot_dir() + else: + if self.rot_dir not in [-1, 1]: + self.rot_dir = -1 + logger.debug("Enforcing input.rot_dir=-1") + rot_dir = self.rot_dir # Setup normalizations for time and angle axes norm_time = { @@ -50,11 +57,10 @@ def comp_axis_time(self, output, p, per_t, is_antiper_t, Time_in=None): elif self.time is None: # Create time axis as a DataLinspace if self.Nrev is not None: - if self.N0 is not None: - t_final = 60 / self.N0 * self.Nrev - else: - raise InputError("time and N0 can't be both None") + # Set final time depending on rotor speed and number of revolutions + t_final = 60 / self.N0 * self.Nrev else: + # Set final time to p times the number of electrical periods t_final = p / f_elec # Create time axis as a DataLinspace Time = DataLinspace( @@ -83,7 +89,12 @@ def comp_axis_time(self, output, p, per_t, is_antiper_t, Time_in=None): Time.symmetries = sym_t Time = Time.to_linspace() - # Compute angle_rotor (added to time normalizations) - output.comp_angle_rotor(Time) + if output is not None: + # Compute angle_rotor (added to time normalizations) + output.comp_angle_rotor(Time) + else: + # Add default normalization + Time.normalizations["angle_rotor"] = Norm_ref(ref=rot_dir * self.N0 * 360 / 60) + logger.debug("Enforcing default angle_rotor normalization to time axis") return Time diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index 1d0d623a5..7d0726670 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -58,11 +58,12 @@ def gen_input(self): + " returned" ) # Creating the data object + stator_label = "phase_" + simu.machine.get_label() outelec.Is = DataTime( name="Stator current", unit="A", symbol="I_s", - axes=[Time, outelec.axes_dict["phase_S"]], + axes=[Time, outelec.axes_dict[stator_label]], values=Is, ) # Compute corresponding Id/Iq reference diff --git a/pyleecan/Methods/Simulation/InputVoltage/comp_felec.py b/pyleecan/Methods/Simulation/InputVoltage/comp_felec.py index 3044ea0ae..9db0099a8 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/comp_felec.py +++ b/pyleecan/Methods/Simulation/InputVoltage/comp_felec.py @@ -1,7 +1,7 @@ from ....Methods.Simulation.Input import InputError -def comp_felec(self): +def comp_felec(self, p=None): """Compute the electrical frequency Parameters @@ -12,18 +12,19 @@ def comp_felec(self): name = self.__class__.__name__ - if self.parent is None: - raise InputError(name + " object should be inside a Simulation object") + if p is None: + if self.parent is None: + raise InputError(name + " object should be inside a Simulation object") + # get the pole pair number + elif hasattr(self.parent, "machine"): + p = self.parent.machine.get_pole_pair_number() + elif hasattr(self.parent.parent, "machine"): + p = self.parent.parent.machine.get_pole_pair_number() + else: + raise Exception("Cannot calculate pole pair number if machine is not found") - # get the pole pair number - if hasattr(self.parent, "machine"): - p = self.parent.machine.stator.get_pole_pair_number() - elif hasattr(self.parent.parent, "machine"): - p = self.parent.parent.machine.stator.get_pole_pair_number() - else: - logger = self.get_logger() - logger.warning("Input.comp_felec(): Machine was not found.") - p = 1 + if self.N0 is None and self.felec is None: + raise InputError(name + " object can't have felec and N0 both None") if hasattr(self, "felec") and self.felec is not None: if self.N0 is None: @@ -33,10 +34,9 @@ def comp_felec(self): 60 * (1 - self.slip_ref) ), "Input speed and frequency are not consistent regarding N0=60*(1-slip)*f_elec/p" - return self.felec - elif self.N0 is not None: + # Get the phase number for verifications return self.N0 * p / (60 * (1 - self.slip_ref)) - else: - raise InputError(name + " object can't have felec and N0 at None") + + return self.felec From 526a5f0347e17e8dcf8c35e0c9e23df3e5a45cf6 Mon Sep 17 00:00:00 2001 From: helene-t Date: Mon, 11 Oct 2021 17:52:15 +0200 Subject: [PATCH 084/167] [WiP] ELUT methods --- Tests/Data/OP_ELUT_PMSM.xlsx | Bin 9738 -> 9744 bytes .../Electrical/test_EEC_ELUT_PMSM.py | 28 +++++++------ .../Generator/ClassesRef/Simulation/LUTdq.csv | 10 ++--- pyleecan/Methods/Output/OutMag/store.py | 2 +- pyleecan/Methods/Post/PostLUT/run.py | 16 ++++---- pyleecan/Methods/Simulation/LUTdq/get_Lmd.py | 9 +++-- pyleecan/Methods/Simulation/LUTdq/get_Lmq.py | 9 +++-- .../Simulation/LUTdq/get_Phidqh_mag.py | 37 ++++++++++++++++- .../Simulation/LUTdq/get_Phidqh_mag_harm.py | 1 + .../Simulation/LUTdq/get_Phidqh_mag_mean.py | 22 +++++++++- .../Simulation/LUTdq/get_Phidqh_mean.py | 38 +++++++++++++++++- .../Simulation/LUTdq/interp_Phi_dqh.py | 35 ++++++++++++++-- 12 files changed, 166 insertions(+), 41 deletions(-) diff --git a/Tests/Data/OP_ELUT_PMSM.xlsx b/Tests/Data/OP_ELUT_PMSM.xlsx index e16d4f2aee484c7454b790cb701364885937f169..df2c82f0944b01a640bb44b910a69a8c4a41c3aa 100644 GIT binary patch delta 1749 zcmV;`1}gcAOpr{lx&{URSY&~-lfMQWe_e0dFcf{CwEtlFohA+;rBOrG0o9PU32ps& ziBuWK5UYuu*-imf{qMU@2rcEMYa{SSu5*0uz31NZi%nUeN3M-fGQs4;$B0X&G9gz9 zzPY~Y4KXs7%8V9D@&s?W!55>iU(VM`KP;4bKmZ_3f^%!D*z*j_c}dNQs<;Hpe^Tj^ zTG;B9XDZEUW^&GLS$Ki(M_x&V#4SRs--Dr+OTqX=vAX2awji1p)B=5zi^_CpC3_EC zN%ccr^_VIvfLMq^*lh!fQOV-jN-9kk1-QK-1GLfbi{MXu7=rF5Fdbc03Z|7&OM3#) zUQ7DOf8u+D9E4zV9E0~Eg`Vb*f5PQ(&q_xhWEJhPq8BjYe;SQ|v>L6%5WEkf4ffmu zJUU;Bg5S2%MYO7Z(b8qAz^I_cPBUS7mf%y^sx?1&q3h~fEefy(VLynlH`>d2u2II9 zv@Yy52)ToY%7~(~VBk)IFfI$rwWOAhm9(J4onN2TT!TH%6(}72=6`FUe>oHu=nH&> zk+PUB%$(XB)w)RVI8JX2xS!rw5Uv3_wNAUzFzHxjJe_X~&KZ^W)9E;^cANAX!eP=0 zwKRRDVtQOM|Dj{bTyQ*yPU}Itej6!pM8}<0=T;+F&n7=0Uw`N$WDhiw?TUK_=^_1l z$#hKapQ7nBBE#skcNSiTf4wjyQ&{zfy~%hujUsX}M;Eax9rf6;RoiH49frwa8v*Jo&q)=jJyP@gwM{ z>&AE|GMj^w!(p(0_nnJX4vgVk7&_8{W0v3pMw6D&6+AsR95C`;FHvfmEl_J_B%1=c z;vjtjUEMX}Cde4+*xjLL8EKN(ea2|Ppdj4j4*HF;Yy6{slYj#ivz-Yb0uJsz(_N4P z007?w000;Oli?&9f1H!cZi6roMfa8Z50>}AkkG0@AyJhmYIjwiYh#9B#SdybFID~b z4x~vcWfK*F8OG+^8GIQ{wpx)jI4`ZqX%fYhfDyKoW|7mk*Lm-b5+ArJxv~awx&u!q zGRMu%8@X;-pe{US{vU^D@){o;`rnxk9 zPsrRQ3|m!Fz|4x31{3xW2Z{%*-`BG72bva_;52v3RnrqzH;5>tl3~|DDbXT(To~(k zp|J05l5#<|4$nSbgM&<$KU30DIO}Z{A|$hY&FAe8*^qH@B%Swv5hh8QgSBio@Pz9B zucYMW$m*Y9e}jL*ZjZ3`bGd9K%bb42zef-6Njt=+;V}12$D>l>4!2zq2USk*lWdw& zHXe2Ud6%&9e_|4N@d64&0Mtk*Y3*59yKEOeVk$ z06QxDjSp(`&d^Ve6x_qqst;L)8YS_^DWT#k6`t+pe*xs84wy|Nq<0?4%H0gSK+|F| z(Ex>h00030{{R30|NoScK?=hl5Jj&dbAYOet1uDTokYDBLOE zu93`q`|}R~vl0%C5C!EI!e%_Pq#h;#e_e0FAQXo0m+U_@+_yk=W+QD)w#BQNELn}q z?g$)}tWW~D>c1aW?cB%3F3J1y@SH<2-RUOz0OwU}N<=e)5-_5b%B)MYsAuUdN_^l( z@}@OVqCI#tt*);MVVP(hJiFF{3krM^Nyal_OSB2WGK{_0faX4n4HLsv>ogDXf9TfO za`DdBfOA47SVQ2F2aXTG)DA>+LP{|p?bbCtKne^EXfVNJno<1K4dAr@^Fp6oG_?wQ z8)rJ{Ub3Y)hC_Sj)llrZF6&0UM)arnb^i3ykD4lT*p&cO9rn|3ALenDD+Tg?UoC=a zl;;7quE4+JLW*9H2?wzxllUY%HVU4T<>=wDo}nryIZY`^$)u)ZHl8qY_g3H^_p1S@ zPY(YeQd-j+mJ>!t7sNB5>b<{zd-VyEfddq?4<|-tqB^F zwImQ5?mp99kOBYz-vs~w7ytkO00000000000069$;3P!>a+4+{Kmq%cXC)vTz^+dC?+mQ?&Kv z3sU76Cs+lW*``TU^}p{7A#Iv3U5gT%R~D6g989UCnz_jiak%$oRvhM$%+Z^f2^b` zi2+lsJzXhAGMzJK%EAkLKk`b#1!@su^&SklT5-xIlGY^?rUg-~AO`5`oL9O-E9raS zN}?X>%B8Zb0Ak4tZgveQa!MM{*Fq|?EFkPH?m1fpzX<-=hb8Dv0@uk=C8tVixiV(} z?X{#&;>W&+@lgu4r#W~ZQs^o6f5>eO53E%5K~~WLD|!LL{-@C}NUPCG49WW-TJOLu zK$jOQUa;F%x(=zTU!=5^Dv(nUZKfGFEK5)yOu1o44^&-U)w}?25S|AS@-7c@UMMGH zD^eHc8id@zLuW+MFzDGgK^UXLFeQj#V<`;ia39xaHP>K|a|sG}ezU(de^(3|3-krC z!b)gNmU=-*3ju#W-MLo3dSfA_tWV(t@cKG4e8M7 zgcy>(QZYGgng7r+p*A@lWT%awy?>i22t>u5Ru@J&a6g~?fO`EQk5E0(NVae8Ig|&V z-wUc@eE&3?UiF5f0K3C}e>iZ%5uUok8J@U!Fq&a}9$k$>{0zaUD5jFsCf^IgLMJE$ z6`Z(!l5OX~eq8hHg~pS=zg)Y&Svudd4cLyrTh2E6MMBnews%})asvwU`_Mt)rzdad1*~Svm~VijIfn7i-Nwt&3d<#_`prYl{HY%9e8>-y}p`k ztXuj9AP^)NU(hCmHfPL>1~m7HZNcD7ZJp);U)_TF)&Z}7x+JYwKTStWb7|q3&%q z#2pkL|l(ZhQdhx9ZY=Dz84Qc2w5sERnK3wodB4+F}klg>XM z5;p$NGYPzW1qC7i*2pM{_AISk#t-KhN@FTJ%bj)he0HSd9v-avlvQXDfWJWnHD9Ul zVmD797frzEFd3qd=!Cr5J%Sf#K}099uO9#a0RR7g00030|CEu@3d0}_MSrFEUNvf2 zEmqjt9}y?)sT)<=?eAxCYpHCo7A$F4nN~t1~R>Z!9QxdXimrqHwTz%OezU-TC5=!6#Q_kuL zmJ0SQeBJv%g{7oiuC=c1&|(ZfN3w+TF_I*dP+?%T%y^Q>4wwm1Z8&wtL$-H40kahj zjSvOa@Moquv!@;=0e{U-10fVf_a?r>g#8TA)~aNNCThB}iHU7A?g-qr#DO6|o%Zde z({`*s3zy{kIGlSaCtFizAK-&-Y=vk+PzF{uO50V17V~L-gR&5XRibVzRA>hQP59+S zDIJrIhbP}S@KHm^63GT8U4_;$I)-tOYcL`dsbN#NY`hUM9e@1_J0ai23h;oCF*Xo| z5>eniFn2u>9g#}*NVoBI2T&424F+rsm=+X&bt8Ba{=Cp7XHBEy&ZU`-y60>q4`JWl z2Hh9iwk_IWr;+?Aew{r&ccbRo?sp{t@%?^k5#lVZa;ZVx?f4?oKDoanYgEQbMc^Te6%%_MC$bjaQelec_order>1" pass # TODO diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_mean.py b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_mean.py index a60920cce..029abc48f 100644 --- a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_mean.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_mean.py @@ -1,3 +1,21 @@ def get_Phidqh_mag_mean(self): - pass - # TODO + """Get the total d-axis inductance + Parameters + ---------- + self : LUTdq + a LUTdq object + + Returns + ---------- + Phi_dqh_mag_mean : ndarray + mean magnets flux linkage in dqh frame (3,) + """ + + # Find Id=Iq=0 + OP_list = self.OP_matrix[:, 1:3].tolist() + if [0, 0] in OP_list: + ii = OP_list.index([0, 0]) + else: + raise Exception("Operating Point Id=Iq=0 is required to compute LUT") + + return self.get_Phidqh_mean()[ii, :] diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py index 4b60aceeb..187f2f323 100644 --- a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py @@ -1,3 +1,37 @@ +from numpy import zeros + +from ....Functions.Electrical.coordinate_transformation import n2dqh_DataTime + + def get_Phidqh_mean(self): - pass - # TODO + """Get the total d-axis inductance + Parameters + ---------- + self : LUTdq + a LUTdq object + + Returns + ---------- + Phi_dqh_mean : ndarray + mean flux linkage in dqh frame (N_dq, 3) + """ + + if self.Phi_dqh_mean is None: + + Phi_dqh_mean = zeros((len(self.Phi_wind), 3)) + + for i, Phi_wind in enumerate(self.Phi_wind): + # dqh transform + Phi_dqh = n2dqh_DataTime( + Phi_wind, + is_dqh_rms=True, + ) + # mean over time axis + Phi_dqh_mean[i, :] = Phi_dqh.get_along("time=mean", "phases")[ + Phi_dqh.symbol + ] + + # Store for next call + self.Phi_dqh_mean = Phi_dqh_mean + + return self.Phi_dqh_mean diff --git a/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py b/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py index a1a6e7012..233aad32c 100644 --- a/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py +++ b/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py @@ -1,3 +1,32 @@ -def interp_Phi_dqh(self): - pass - # TODO +import scipy.interpolate as scp_int +import numpy as np + + +def interp_Phi_dqh(self, Id, Iq): + """Get the magnets d-axis inductance + Parameters + ---------- + self : LUTdq + a LUTdq object + Id : float + current Id + Iq : float + current Iq + + Returns + ---------- + Phi_dqh : ndarray + interpolated flux in dqh frame (3) + """ + + # Compute interpolant at first call + if self.Phi_dqh_interp is None: + # Flatten Id/Iq grid columnwise + XId, XIq = self.OP_matrix[:, 1], self.OP_matrix[:, 2] + # Sort in ascending order + self.Phi_dqh_interp = scp_int.RegularGridInterpolator( + (XId, XIq), self.get_Phidqh_mean(), method="linear" + ) + + # Perform 2D interpolation + return self.Phi_dqh_interp((Id, Iq)) From ca4fdc454a50b163c10f7a09f58863d99907d41b Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Mon, 11 Oct 2021 18:44:23 +0200 Subject: [PATCH 085/167] [CC] Call methods of LamSlotWind in LamSlotMultiWind --- .../Machine/LamSlotMultiWind/comp_mmf_unit.py | 68 ++----------------- .../Machine/LamSlotMultiWind/plot_mmf_unit.py | 55 ++------------- 2 files changed, 10 insertions(+), 113 deletions(-) diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py index f360090f8..165f48e67 100644 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py @@ -1,10 +1,7 @@ -from numpy import pi, linspace, zeros, ones, dot, squeeze -from SciDataTool import Data1D, DataTime, Norm_ref -from ....Functions.Electrical.coordinate_transformation import dqh2n -from ....Functions.Winding.gen_phase_list import gen_name +from ....Functions.Load.import_class import import_class -def comp_mmf_unit(self, Na=None, Nt=None, freq=1, rot_dir=-1): +def comp_mmf_unit(self, Na, Nt, felec=1, rot_dir=-1): """Compute the winding Unit magnetomotive force Parameters @@ -29,64 +26,11 @@ def comp_mmf_unit(self, Na=None, Nt=None, freq=1, rot_dir=-1): """ - # Get stator winding number of phases - qs = self.winding.qs + # Call method of LamSlotWind + LamSlotWind = import_class("pyleecan.Classes", "LamSlotWind") - # Get number of pole pairs - p = self.get_pole_pair_number() - - # Get spatial symmetry - per_a, _ = self.comp_periodicity_spatial() - - # Define the space dicretization - angle = linspace(0, 2 * pi / per_a, Na, endpoint=False) - - # Define the time dicretization - time = linspace(0, 1 / freq, Nt, endpoint=False) - - # Compute the winding function and mmf - if self.winding is None or self.winding.conductor is None: - wf = zeros((qs, Na)) - else: - wf = self.comp_wind_function(angle=angle, per_a=per_a) - - # Compute unit current function of time applying constant Id=1 Arms, Iq=0 - Idq = zeros((Nt, 3)) - Idq[:, 0] = ones(Nt) - I = dqh2n(Idq, rot_dir * 2 * pi * freq * time, n=qs, is_n_rms=False) - - # Compute unit mmf - mmf_u = squeeze(dot(I, wf)) - - # Create a Data object - Time = Data1D(name="time", unit="s", values=time) - Angle = Data1D( - name="angle", - unit="rad", - symmetries={"period": per_a}, - values=angle, - normalizations={"space_order": Norm_ref(ref=self.get_pole_pair_number())}, - ) - Phase = Data1D( - name="phase", - unit="", - values=gen_name(qs), - is_components=True, - ) - MMF_U = DataTime( - name="Total MMF", - unit="A", - symbol="Magnitude", - axes=[Time, Angle], - values=mmf_u, - ) - - WF = DataTime( - name="Phase MMF", - unit="A", - symbol="Magnitude", - axes=[Phase, Angle], - values=wf, + MMF_U, WF = LamSlotWind.comp_mmf_unit( + self, Na=Na, Nt=Nt, felec=felec, rot_dir=rot_dir ) return MMF_U, WF diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/plot_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotMultiWind/plot_mmf_unit.py index 584d83a47..b78d0e594 100644 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/plot_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/plot_mmf_unit.py @@ -1,13 +1,4 @@ -# -*- coding: utf-8 -*- -import matplotlib.pyplot as plt - -from PySide2.QtCore import * -from PySide2.QtGui import * -from PySide2.QtWidgets import * - -from pyleecan.GUI.Resources import pyleecan_rc -from ....Functions.Plot import dict_2D -from ....definitions import config_dict +from ....Functions.Load.import_class import import_class def plot_mmf_unit(self, r_max=100, fig=None, is_show_fig=True): @@ -25,45 +16,7 @@ def plot_mmf_unit(self, r_max=100, fig=None, is_show_fig=True): To call show at the end of the method """ - name = "" - if self.parent is not None and self.parent.name not in [None, ""]: - name += self.parent.name + " " - if self.is_stator: - name += "Stator " - else: - name += "Rotor " - - # Compute the winding function and mmf - wf = self.comp_wind_function(per_a=1) - qs = self.winding.qs - MMF_U, WF = self.comp_mmf_unit(Nt=1, Na=wf.shape[1]) - - color_list = config_dict["PLOT"]["COLOR_DICT"]["COLOR_LIST"][:qs] - - dict_2D_0 = dict_2D.copy() - dict_2D_0["color_list"] = color_list + ["k"] - - fig, axs = plt.subplots(2, 1, tight_layout=True, figsize=(8, 8)) - - WF.plot_2D_Data( - "angle{°}", - "phase[]", - data_list=[MMF_U], - fig=fig, - ax=axs[0], - is_show_fig=is_show_fig, - win_title=name + "Phase MMF over space", - **dict_2D_0, - ) - - dict_2D_0["color_list"] = [color_list[0], "k"] + # Call method of LamSlotWind + LamSlotWind = import_class("pyleecan.Classes", "LamSlotWind") - WF.plot_2D_Data( - "wavenumber=[0," + str(r_max) + "]", - data_list=[MMF_U], - fig=fig, - ax=axs[1], - is_show_fig=is_show_fig, - win_title=name + "Phase MMF FFT", - **dict_2D_0, - ) + LamSlotWind.plot_mmf_unit(self, r_max=r_max, fig=fig, is_show_fig=is_show_fig) From 9db552aaadf2f0c02362b17862d25665e91e0f51 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Mon, 11 Oct 2021 19:27:58 +0200 Subject: [PATCH 086/167] [BC] Don't put symmetries if period=1 and not anti periodic --- pyleecan/Methods/Simulation/Input/comp_axis_angle.py | 2 +- pyleecan/Methods/Simulation/Input/comp_axis_time.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyleecan/Methods/Simulation/Input/comp_axis_angle.py b/pyleecan/Methods/Simulation/Input/comp_axis_angle.py index 84f229997..8d527d7d8 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axis_angle.py +++ b/pyleecan/Methods/Simulation/Input/comp_axis_angle.py @@ -62,7 +62,7 @@ def comp_axis_angle(self, p, Rag, per_a, is_antiper_a, Angle_in=None): sym_a = dict() if is_antiper_a: sym_a["antiperiod"] = per_a - else: + elif per_a > 1: sym_a["period"] = per_a Angle.symmetries = sym_a Angle = Angle.to_linspace() diff --git a/pyleecan/Methods/Simulation/Input/comp_axis_time.py b/pyleecan/Methods/Simulation/Input/comp_axis_time.py index f5df46c4b..0fa0fb9f7 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axis_time.py +++ b/pyleecan/Methods/Simulation/Input/comp_axis_time.py @@ -84,7 +84,7 @@ def comp_axis_time(self, p, per_t, is_antiper_t, Time_in=None, output=None): sym_t = dict() if is_antiper_t: sym_t["antiperiod"] = per_t - else: + elif per_t > 1: sym_t["period"] = per_t Time.symmetries = sym_t Time = Time.to_linspace() From 1bc8cc58cf7bc1dfe3ff40c41ed3bb819975ea36 Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 12 Oct 2021 10:05:30 +0200 Subject: [PATCH 087/167] [WiP] LUT interpolation --- Tests/Data/OP_ELUT_PMSM.xlsx | Bin 9744 -> 9748 bytes pyleecan/Classes/Class_Dict.json | 11 +--- pyleecan/Classes/LUTdq.py | 47 ++---------------- .../Generator/ClassesRef/Simulation/LUTdq.csv | 2 +- .../Simulation/LUTdq/interp_Phi_dqh.py | 8 +-- 5 files changed, 11 insertions(+), 57 deletions(-) diff --git a/Tests/Data/OP_ELUT_PMSM.xlsx b/Tests/Data/OP_ELUT_PMSM.xlsx index df2c82f0944b01a640bb44b910a69a8c4a41c3aa..7531f2d15cb5b4a842207a4e8897a5f3b954cdfe 100644 GIT binary patch delta 1714 zcmV;j22J^pOq5Kpx&{UFvWP>qlfMQWf8TGLFc5y9wEw~KI|Y-3HmIb^NTNmBrfBPr z7o^GoCs+lW*``TU^?%Pj)1>6|fB7OwAkfm>28aElPC_h87?il=NM)4F8Bv>=KV)Bt^*^GbJUW%?et zlB$Qga#C4V0I}o+H@gNDqcRQWYata~7U1@l4A54=9l%3882s)eFr6G#@>EGJSLO_$ z-Inx;|HN|%ISRq{GzRZO>br_Pe{!3{11lALkX3NN3SPj7_h~c&(rUC4LhwF_HaKwe z@#u2J3wGN|7tyNvMN6Bh0;7UzGtIbRS%lAFDL3rsgQ}}^aZ}c zNYjum^@5rl)vAba940p!+)r){2v-B0S|wd+=p>dIOBTC=v6PDY=`>EN{UW)BaOh-0 z4NYFDn4Xr*f9RN|HaIRsr}dzHe;X-qM1`GJ7e*m?pHF^3zW&fh$R21U+ZXp7(nEUp zB2^)|e~SI_Bpwg?PS6|Af1LjGJaC4?Nza)Mhv(lWac>%s=`%Q^f-se|Hu+u{7CORx zP{E1mC*5`y;)ONOUT8dd`^&NOo1wEkTZe56yya}8UnFFgXnV&+CN~l8!FG&x-I90# zbkt2_yyKb4!O4pY|M2ZQnq2?KBlGy#0(jtX|u#?^BHO8*-kNyFZfddq?nh78R1tVg! zv5}L%3nhPDPlGTNhVM=M4^8h!VVlMgm@zX(?~I?KwLzzADPGtcS!+r!|75+ArJxv~awx&=>frGRMu%8@YT(kZybN{vfHE;8^-Z9(_9+5Bc$#WhOH_o z;NFU*1`~D=2Z{&G-`BG7Jxz;KaGJZtvKa`g8$=XR$*}F9lxUGY&5d=uP+0dS8FN84 z4(}m8qh6+qA1P@mob|Q}5t7+1=i~AxY{IzMlaA{@36qSOgOzL#@PHbgmr`=IXAKXq z(V2g+>piUfTrOS8GN<41-!;HKX`A@a^m9LSnw1hyxUGsfsB(Ijq_Z())2#E)hlI8N zHzt7>ub@B#K#hcw)}Dp6i+14?hGHc8Zb3`rBFGiYK&hM}mH4ktV<*7&3e)T`?$sVT zLq9uGa1S%9K4lqdl*AvWgo-a!c(Ln8kn?{!V00WIz4J&~ZtuYhG%Y3*jj)nm00030 z{{R30|NoScTMEM<5Jk5lvw-@DUtuD&$wtHq{Yp!ewApz+O^Y*It*w2j|DwfjBi_(5+9;GEK9QLI~uuLGC`qg^D#I z?aP!WWag>^Tvg*zQF19&U%%XuljYyWtNTymTZ@l4nftwXR3V=vaAxzA$5#BkX<%|kr8e-*Y|yz>>{oRA6D z5V+)l<2^970}&mOQVdADaZL}90z(5DOz@ay6n}LCIPL$u&?je2t-{X6nU1>WY$*=m z(B67A6uYj=x>2tY{V9H(KR)-PrpoMhB>+|X{WRQ%d0gdEfxO#Qi=Z0id4P>8@b9>g zq8DVsK`hB6KFJP(HmBrebpKG#P?eLMrj(>)QqwUTPZ+sL1@p3qL$#AjBvJv6lb$3k1tVg!v5}L|BozU!ljbBv z0dVXOpr{lx&{URSY&~-lfMQWe_e0dFcf{CwEtlFohA+;rBOrG0o9PU32ps& ziBuWK5UYuu*-imf{qMU@2rcEMYa{SSu5*0uz31NZi%nUeN3M-fGQs4;$B0X&G9gz9 zzPY~Y4KXs7%8V9D@&s?W!55>iU(VM`KP;4bKmZ_3f^%!D*z*j_c}dNQs<;Hpe^Tj^ zTG;B9XDZEUW^&GLS$Ki(M_x&V#4SRs--Dr+OTqX=vAX2awji1p)B=5zi^_CpC3_EC zN%ccr^_VIvfLMq^*lh!fQOV-jN-9kk1-QK-1GLfbi{MXu7=rF5Fdbc03Z|7&OM3#) zUQ7DOf8u+D9E4zV9E0~Eg`Vb*f5PQ(&q_xhWEJhPq8BjYe;SQ|v>L6%5WEkf4ffmu zJUU;Bg5S2%MYO7Z(b8qAz^I_cPBUS7mf%y^sx?1&q3h~fEefy(VLynlH`>d2u2II9 zv@Yy52)ToY%7~(~VBk)IFfI$rwWOAhm9(J4onN2TT!TH%6(}72=6`FUe>oHu=nH&> zk+PUB%$(XB)w)RVI8JX2xS!rw5Uv3_wNAUzFzHxjJe_X~&KZ^W)9E;^cANAX!eP=0 zwKRRDVtQOM|Dj{bTyQ*yPU}Itej6!pM8}<0=T;+F&n7=0Uw`N$WDhiw?TUK_=^_1l z$#hKapQ7nBBE#skcNSiTf4wjyQ&{zfy~%hujUsX}M;Eax9rf6;RoiH49frwa8v*Jo&q)=jJyP@gwM{ z>&AE|GMj^w!(p(0_nnJX4vgVk7&_8{W0v3pMw6D&6+AsR95C`;FHvfmEl_J_B%1=c z;vjtjUEMX}Cde4+*xjLL8EKN(ea2|Ppdj4j4*HF;Yy6{slYj#ivz!Sa0tN0q(_N60 z!3!mS%Wi`(5JmTu`VW@(!I03ZK_O9f; z+!=fsO}1K*H8?M=$!QYBlz!+a=HUgC*$j@(Z;%^uK@x< zg7G=6LufL_yr@BQAK3;B&Q#WE9`MmEm~R|^@UnYJE7p(WTc)`*bWh0KB@A0tQoziL zl?D^`5C@6}tl!tN@dug~m*6ya%T?18RyT+!q>^FRK`GH9dt4anc%iWGZIW_9whqre zUW0>7mp@a|QaI~v6(S_Fea+|X5804$aU`Ahe-S20nS-@#H}Hh&|F5Lv=E&-wV1s{u z!fubS_H(&xCCi+C#lJ@n?@2qvr{OU7O~<2B;tsc65eHRH?~`nrQZ^oS{&|8L@t6{!xWUt=`2Nue|8!eq5wN8{EZK4^Ulyu zjuhO()T$3zh8iXD$0?!WD;1vY<^kk?q7Il%Bcyj8$;#afyg<`pG0^~pegFUf|Nj60 z0RR7#kwFT>AP_~bB6EPMiK{RX+T6Mu|w*0&C+Wlu^0{JDneV)113AZ|O6DZs%->#9&eEahcvlkAI z5C!EI!e%_Psvai+e{aGd6o&7Y>_0Tzw?K7fBW+E##jBYtS&hr?2ppBHPy)E>zaLiZ z+{eW($@}u~oI^3)=_dIA=T&P;L^FaCFrt;ptV^`0XX!0UeBeg%rZrHaJ$N*&uCEGV znP?q6yVimW3Vaet#xr3{vqfms^r!fB{`At1nksYHl>k&7_S0}5=5duP1@eAhErM#4=K;2^z`x@{ie8Wj z2eBlR_#`_DHJ+2@=;5)Rp(-aiO({vqq^4swo-lIvR^T7^s{yD_4*wuhTGJbr6GlfD z#517ky}y5Z^$C-K0~E6vAu$65|5#*!vy)FGQUr_%005J$2^y2XBoG_!KGR*00ssKt z1pojT0000000000000000IZYhBt-#olQ1Pf0sE72B_JE+7s6&d0RR9)0ssIJ00000 e00000000000F;xgB|ZTMlj|iT2D~Ex0000&c{3LP diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index ceee4d940..441b9add6 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4439,15 +4439,6 @@ "unit": "Wbrms", "value": null }, - { - "desc": "RMS Id Iq Ih table corresponding to flux linkage table given in Phi_dqh_mean", - "max": "", - "min": "", - "name": "I_dqh", - "type": "list", - "unit": "Arms", - "value": null - }, { "desc": "Magnet average temperature at which Phi_dqh is given", "max": "", @@ -4480,7 +4471,7 @@ "max": "", "min": "", "name": "Phi_dqh_interp", - "type": "scipy.interp", + "type": "scipy.interpolate.interpolate.RegularGridInterpolator", "unit": "", "value": "None" } diff --git a/pyleecan/Classes/LUTdq.py b/pyleecan/Classes/LUTdq.py index 5076d986a..8e71f8295 100644 --- a/pyleecan/Classes/LUTdq.py +++ b/pyleecan/Classes/LUTdq.py @@ -88,9 +88,9 @@ from ._check import CheckTypeError try: - from scipy import interp + from scipy.interpolate.interpolate import RegularGridInterpolator except ImportError: - interp = ImportError + RegularGridInterpolator = ImportError from ._check import InitUnKnowClassError @@ -244,7 +244,6 @@ class LUTdq(LUT): def __init__( self, Phi_dqh_mean=None, - I_dqh=None, Tmag_ref=20, Phi_dqh_mag=None, Phi_wind=None, @@ -273,8 +272,6 @@ def __init__( # Overwrite default value with init_dict content if "Phi_dqh_mean" in list(init_dict.keys()): Phi_dqh_mean = init_dict["Phi_dqh_mean"] - if "I_dqh" in list(init_dict.keys()): - I_dqh = init_dict["I_dqh"] if "Tmag_ref" in list(init_dict.keys()): Tmag_ref = init_dict["Tmag_ref"] if "Phi_dqh_mag" in list(init_dict.keys()): @@ -293,7 +290,6 @@ def __init__( OP_matrix = init_dict["OP_matrix"] # Set the properties (value check and convertion are done in setter) self.Phi_dqh_mean = Phi_dqh_mean - self.I_dqh = I_dqh self.Tmag_ref = Tmag_ref self.Phi_dqh_mag = Phi_dqh_mag self.Phi_wind = Phi_wind @@ -316,12 +312,6 @@ def __str__(self): + linesep + linesep ) - LUTdq_str += ( - "I_dqh = " - + linesep - + str(self.I_dqh).replace(linesep, linesep + "\t") - + linesep - ) LUTdq_str += "Tmag_ref = " + str(self.Tmag_ref) + linesep LUTdq_str += "Phi_dqh_mag = " + str(self.Phi_dqh_mag) + linesep + linesep LUTdq_str += "Phi_wind = " + str(self.Phi_wind) + linesep + linesep @@ -339,8 +329,6 @@ def __eq__(self, other): return False if not array_equal(other.Phi_dqh_mean, self.Phi_dqh_mean): return False - if other.I_dqh != self.I_dqh: - return False if other.Tmag_ref != self.Tmag_ref: return False if other.Phi_dqh_mag != self.Phi_dqh_mag: @@ -364,8 +352,6 @@ def compare(self, other, name="self", ignore_list=None): diff_list.extend(super(LUTdq, self).compare(other, name=name)) if not array_equal(other.Phi_dqh_mean, self.Phi_dqh_mean): diff_list.append(name + ".Phi_dqh_mean") - if other._I_dqh != self._I_dqh: - diff_list.append(name + ".I_dqh") if other._Tmag_ref != self._Tmag_ref: diff_list.append(name + ".Tmag_ref") if (other.Phi_dqh_mag is None and self.Phi_dqh_mag is not None) or ( @@ -412,9 +398,6 @@ def __sizeof__(self): # Get size of the properties inherited from LUT S += super(LUTdq, self).__sizeof__() S += getsizeof(self.Phi_dqh_mean) - if self.I_dqh is not None: - for value in self.I_dqh: - S += getsizeof(value) S += getsizeof(self.Tmag_ref) S += getsizeof(self.Phi_dqh_mag) if self.Phi_wind is not None: @@ -453,7 +436,6 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): raise Exception( "Unknown type_handle_ndarray: " + str(type_handle_ndarray) ) - LUTdq_dict["I_dqh"] = self.I_dqh.copy() if self.I_dqh is not None else None LUTdq_dict["Tmag_ref"] = self.Tmag_ref if self.Phi_dqh_mag is None: LUTdq_dict["Phi_dqh_mag"] = None @@ -497,7 +479,6 @@ def _set_None(self): """Set all the properties to None (except pyleecan object)""" self.Phi_dqh_mean = None - self.I_dqh = None self.Tmag_ref = None self.Phi_dqh_mag = None self.Phi_wind = None @@ -530,26 +511,6 @@ def _set_Phi_dqh_mean(self, value): """, ) - def _get_I_dqh(self): - """getter of I_dqh""" - return self._I_dqh - - def _set_I_dqh(self, value): - """setter of I_dqh""" - if type(value) is int and value == -1: - value = list() - check_var("I_dqh", value, "list") - self._I_dqh = value - - I_dqh = property( - fget=_get_I_dqh, - fset=_set_I_dqh, - doc=u"""RMS Id Iq Ih table corresponding to flux linkage table given in Phi_dqh_mean - - :Type: list - """, - ) - def _get_Tmag_ref(self): """getter of Tmag_ref""" return self._Tmag_ref @@ -634,7 +595,7 @@ def _get_Phi_dqh_interp(self): def _set_Phi_dqh_interp(self, value): """setter of Phi_dqh_interp""" - check_var("Phi_dqh_interp", value, "interp") + check_var("Phi_dqh_interp", value, "RegularGridInterpolator") self._Phi_dqh_interp = value Phi_dqh_interp = property( @@ -642,6 +603,6 @@ def _set_Phi_dqh_interp(self, value): fset=_set_Phi_dqh_interp, doc=u"""Interpolant function of Phi_dqh - :Type: scipy.interp + :Type: scipy.interpolate.interpolate.RegularGridInterpolator """, ) diff --git a/pyleecan/Generator/ClassesRef/Simulation/LUTdq.csv b/pyleecan/Generator/ClassesRef/Simulation/LUTdq.csv index 5f44e2733..c4cbbedca 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/LUTdq.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/LUTdq.csv @@ -3,7 +3,7 @@ Phi_dqh_mean,Wbrms,RMS stator winding flux table in dqh frame (including magnets Tmag_ref,degC,Magnet average temperature at which Phi_dqh is given,,float,20,,,,,,get_Lq,,,, Phi_dqh_mag,Wbrms,RMS stator winding flux linkage spectrum in dqh frame including harmonics (only magnets),"(Nharm, 3)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_bemf,,,, Phi_wind,Wb,Stator winding flux function of time and phases,"(Nt_tot, qs)",[SciDataTool.Classes.DataND.DataND],None,,,,,,get_Ld,,,, -Phi_dqh_interp,,Interpolant function of Phi_dqh,,scipy.interp,None,,,,,,get_Lmd,,,, +Phi_dqh_interp,,Interpolant function of Phi_dqh,,scipy.interpolate.interpolate.RegularGridInterpolator,None,,,,,,get_Lmd,,,, ,,,,,,,,,,,get_Lmq,,,, ,,,,,,,,,,,import_from_data,,,, ,,,,,,,,,,,get_Phidqh_mean,,,, diff --git a/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py b/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py index 233aad32c..fb98832c8 100644 --- a/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py +++ b/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py @@ -21,11 +21,13 @@ def interp_Phi_dqh(self, Id, Iq): # Compute interpolant at first call if self.Phi_dqh_interp is None: - # Flatten Id/Iq grid columnwise - XId, XIq = self.OP_matrix[:, 1], self.OP_matrix[:, 2] + # Get unique Id, Iq, assuming regular grid + XId, XIq = np.unique(self.OP_matrix[:, 1]), np.unique(self.OP_matrix[:, 2]) # Sort in ascending order self.Phi_dqh_interp = scp_int.RegularGridInterpolator( - (XId, XIq), self.get_Phidqh_mean(), method="linear" + (XId, XIq), + self.get_Phidqh_mean()[:, 0:2].reshape((len(XId), len(XIq), 2)), + method="linear", ) # Perform 2D interpolation From d4bbea7e77a7a72571507ae6df09622df866a5b5 Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 12 Oct 2021 10:24:34 +0200 Subject: [PATCH 088/167] [BC] need machine to compute unit mmf -> test on Prius --- Tests/Plot/test_Winding_plot.py | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/Tests/Plot/test_Winding_plot.py b/Tests/Plot/test_Winding_plot.py index e2d19cdac..919968c93 100644 --- a/Tests/Plot/test_Winding_plot.py +++ b/Tests/Plot/test_Winding_plot.py @@ -14,6 +14,8 @@ from pyleecan.Classes.SlotW21 import SlotW21 from pyleecan.Classes.SlotW22 import SlotW22 from Tests import save_plot_path as save_path +from pyleecan.Functions.load import load +from pyleecan.definitions import DATA_DIR class Test_Winding_plot(object): @@ -181,23 +183,8 @@ def test_type_wind_CW2LR(self): def test_plot_mmf_unit(self): """Test plot unit mmf""" - stator = LamSlotWind( - Rint=0.1325, - Rext=0.2, - Nrvd=0, - L1=0.35, - Kf1=0.95, - is_internal=False, - is_stator=True, - ) - stator.slot = SlotW10( - Zs=36, H0=1e-3, H1=1.5e-3, H2=30e-3, W0=12e-3, W1=14e-3, W2=12e-3 - ) - stator.winding = WindingUD( - qs=3, Lewout=15e-3, p=3, coil_pitch=5, Ntcoil=7, Npcp=2 - ) - stator.winding.init_as_DWL(nlay=2) - stator.plot_mmf_unit(is_show_fig=False) + Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) + Toyota_Prius.stator.plot_mmf_unit(is_show_fig=False) fig = plt.gcf() fig.savefig(join(save_path, "test_unit_mmf.png")) From 537bc7bffa6f8598490562df8ee8f93a88728a18 Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 12 Oct 2021 11:03:48 +0200 Subject: [PATCH 089/167] [BC] fix get_Is tests --- Tests/Methods/Simulation/test_InCurrent_meth.py | 8 ++++---- pyleecan/Methods/Output/OutElec/get_I_fund.py | 3 +-- pyleecan/Methods/Simulation/InputCurrent/gen_input.py | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Tests/Methods/Simulation/test_InCurrent_meth.py b/Tests/Methods/Simulation/test_InCurrent_meth.py index cde9b2a4e..fb02e27bb 100644 --- a/Tests/Methods/Simulation/test_InCurrent_meth.py +++ b/Tests/Methods/Simulation/test_InCurrent_meth.py @@ -217,17 +217,17 @@ def test_InputCurrent_DQ(self, test_dict): Ia = ( A_rms * sqrt(2) - * cos(2 * pi * felec * time_exp + 0 * rot_dir * 2 * pi / qs + Phi0) + * cos(rot_dir * 2 * pi * felec * time_exp + 0 * 2 * pi / qs + Phi0) ) Ib = ( A_rms * sqrt(2) - * cos(2 * pi * felec * time_exp + 1 * rot_dir * 2 * pi / qs + Phi0) + * cos(rot_dir * 2 * pi * felec * time_exp + 1 * 2 * pi / qs + Phi0) ) Ic = ( A_rms * sqrt(2) - * cos(2 * pi * felec * time_exp + 2 * rot_dir * 2 * pi / qs + Phi0) + * cos(rot_dir * 2 * pi * felec * time_exp + 2 * 2 * pi / qs + Phi0) ) Is_exp = array([Ia, Ib, Ic]) @@ -262,7 +262,7 @@ def test_InputCurrent_DQ(self, test_dict): output.geo.axes_dict["angle"].get_values(is_oneperiod=False), linspace(0, 2 * pi, Na_tot, endpoint=False), ) - assert_array_almost_equal(output.elec.get_Is().values, Is_exp) + assert_array_almost_equal(output.elec.get_Is().values, Is_exp.T) assert_array_almost_equal(output.get_angle_rotor(), angle_rotor_exp) assert_array_almost_equal(output.elec.N0, N0) assert_array_almost_equal(output.geo.rot_dir, rot_dir) diff --git a/pyleecan/Methods/Output/OutElec/get_I_fund.py b/pyleecan/Methods/Output/OutElec/get_I_fund.py index f5f0e593f..b601750ba 100644 --- a/pyleecan/Methods/Output/OutElec/get_I_fund.py +++ b/pyleecan/Methods/Output/OutElec/get_I_fund.py @@ -27,8 +27,7 @@ def get_I_fund(self, Time=None): if ( self.Id_ref is not None and self.Iq_ref is not None - and self.Id_ref != 0 - and self.Iq_ref != 0 + and (self.Id_ref != 0 or self.Iq_ref != 0) ): # Generate current according to Id/Iq, Ih=0 Is_dqh = zeros((angle_elec.size, 3)) diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index 7d0726670..8ebd69f2d 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -58,7 +58,7 @@ def gen_input(self): + " returned" ) # Creating the data object - stator_label = "phase_" + simu.machine.get_label() + stator_label = "phase_" + simu.machine.stator.get_label() outelec.Is = DataTime( name="Stator current", unit="A", From c1f3d1b24070aceecd91c7149ace27fa5f537c8d Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 12 Oct 2021 11:39:13 +0200 Subject: [PATCH 090/167] [BC] fix mmf unit computation for async machines --- pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py | 2 +- pyleecan/Methods/Simulation/InputCurrent/gen_input.py | 2 +- pyleecan/Methods/Simulation/InputFlux/gen_input.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py index ecd081912..e1a595ed5 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py @@ -46,7 +46,7 @@ def comp_mmf_unit(self, Na, Nt, felec=1, rot_dir=-1): input = InputCurrent(Na_tot=Na, Nt_tot=Nt, felec=felec, rot_dir=rot_dir) axes_dict = input.comp_axes( - axes_list=["time", "angle", "phase_S"], + axes_list=["time", "angle", "phase_S", "phase_R"], machine=machine, is_periodicity_t=True, is_periodicity_a=True, diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index 8ebd69f2d..ac0088e08 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -94,6 +94,6 @@ def gen_input(self): name="Rotor current", unit="A", symbol="Ir", - axes=[Time, outelec.axes_dict["phase_R"]], + axes=[Time, outelec.axes_dict["phase_" + simu.machine.rotor.get_label()]], values=Ir, ) diff --git a/pyleecan/Methods/Simulation/InputFlux/gen_input.py b/pyleecan/Methods/Simulation/InputFlux/gen_input.py index 9ec106e87..68a94ddf5 100644 --- a/pyleecan/Methods/Simulation/InputFlux/gen_input.py +++ b/pyleecan/Methods/Simulation/InputFlux/gen_input.py @@ -81,6 +81,7 @@ def gen_input(self): # Calculate time and angle axes axes_dict = Input.comp_axes( self, + axes_list=["time", "angle"], per_a=per_a, is_antiper_a=is_antiper_a, per_t=per_t, From b4cb7ddf0cf475b9d2e5cc754b06e1567653b1a9 Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 12 Oct 2021 11:42:38 +0200 Subject: [PATCH 091/167] [BC] use MMF symbol to get result --- pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py index cbd080d98..193e283ed 100644 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py @@ -26,7 +26,7 @@ def comp_rot_dir(self): result_p = MMF.get_harmonics(1, "freqs", "wavenumber=" + str(p)) result_n = MMF.get_harmonics(1, "freqs", "wavenumber=" + str(-p)) - if result_p["Magnitude"][0] > result_n["Magnitude"][0]: + if result_p[MMF.symbol][0] > result_n[MMF.symbol][0]: result = result_p else: result = result_n From ecf3c4b752ceed3f99da15f861336a3814eef1ec Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 12 Oct 2021 12:23:36 +0200 Subject: [PATCH 092/167] [BC] remove condition on conductor --- pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py | 4 ++-- pyleecan/Methods/Machine/Lamination/get_name_phase.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py index e1a595ed5..39cd4916d 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py @@ -36,8 +36,8 @@ def comp_mmf_unit(self, Na, Nt, felec=1, rot_dir=-1): machine = self.parent # Compute the winding function and mmf - if self.winding is None or self.winding.conductor is None: - raise Exception("Cannot calculate mmf unit if winding or conductor is None") + if self.winding is None: + raise Exception("Cannot calculate mmf unit if winding is None") else: # Get stator winding number of phases qs = self.winding.qs diff --git a/pyleecan/Methods/Machine/Lamination/get_name_phase.py b/pyleecan/Methods/Machine/Lamination/get_name_phase.py index e2fed8fc9..5fe1e3fcc 100644 --- a/pyleecan/Methods/Machine/Lamination/get_name_phase.py +++ b/pyleecan/Methods/Machine/Lamination/get_name_phase.py @@ -18,7 +18,7 @@ def get_name_phase(self): if ( not hasattr(self, "winding") or self.winding is None - or self.winding.conductor is None + # or self.winding.conductor is None ): return list() return gen_name(self.winding.qs) From acc3304d38152c79760a4dcc45aeb089ce28442e Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 12 Oct 2021 12:40:35 +0200 Subject: [PATCH 093/167] [BC] fill periodicities if None --- pyleecan/Methods/Simulation/Force/comp_axes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyleecan/Methods/Simulation/Force/comp_axes.py b/pyleecan/Methods/Simulation/Force/comp_axes.py index 700cc6b29..1e0175dbe 100644 --- a/pyleecan/Methods/Simulation/Force/comp_axes.py +++ b/pyleecan/Methods/Simulation/Force/comp_axes.py @@ -37,6 +37,8 @@ def comp_axes(self, output): # Check Time periodicities regarding Force model input per_t0, is_antiper_t0 = axes_dict["time"].get_periodicity() is_periodicity_t0 = per_t0 > 1 or is_antiper_t0 + if self.is_periodicity_t is None: + self.is_periodicity_t = is_periodicity_t0 is_periodic_machine_t = outgeo.per_t_S > 1 or outgeo.is_antiper_t_S if is_periodicity_t0 != self.is_periodicity_t and is_periodic_machine_t: # Remove time periodicity in Force model @@ -59,6 +61,8 @@ def comp_axes(self, output): # Check Angle periodicities regarding Force model input per_a0, is_antiper_a0 = axes_dict["angle"].get_periodicity() is_periodicity_a0 = per_a0 > 1 or is_antiper_a0 + if self.is_periodicity_a is None: + self.is_periodicity_a = is_periodicity_a0 is_periodic_machine_a = outgeo.per_a > 1 or outgeo.is_antiper_a if is_periodicity_a0 != self.is_periodicity_a and is_periodic_machine_a: # Remove time periodicity in Magnetic model From cba21e2fbef78e4a7ce2a727db6346fb7c9c9bd8 Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 12 Oct 2021 14:17:13 +0200 Subject: [PATCH 094/167] [BC] restore previous test --- Tests/Validation/Force/test_AGSF_spectrum.py | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Tests/Validation/Force/test_AGSF_spectrum.py b/Tests/Validation/Force/test_AGSF_spectrum.py index e7995d1bc..339a89d94 100644 --- a/Tests/Validation/Force/test_AGSF_spectrum.py +++ b/Tests/Validation/Force/test_AGSF_spectrum.py @@ -181,8 +181,8 @@ def test_IPMSM_AGSF_spectrum_sym(): Prad_wr[ifrq, ir] * exp(1j * 2 * pi * frq * Xtime + 1j * r * Xangle) ) - test = abs(XP_rad1 - Prad) / XP_rad1 - assert_array_almost_equal(test, 0, decimal=5) + test = abs(XP_rad1 - Prad) / mean(XP_rad1) + assert_array_almost_equal(test, 0, decimal=1) # Less than 10% error return out @@ -190,14 +190,14 @@ def test_IPMSM_AGSF_spectrum_sym(): if __name__ == "__main__": out = test_IPMSM_AGSF_spectrum_sym() - out2 = test_IPMSM_AGSF_spectrum_no_sym() - - out.force.AGSF.plot_2D_Data( - "wavenumber", - "freqs=160", - data_list=[out2.force.AGSF], - legend_list=["Periodic", "Full"], - save_path=join(save_path, simu.name + "_space_fft_freq160.png"), - is_show_fig=False, - **dict_2D - ) + # out2 = test_IPMSM_AGSF_spectrum_no_sym() + + # out.force.AGSF.plot_2D_Data( + # "wavenumber", + # "freqs=160", + # data_list=[out2.force.AGSF], + # legend_list=["Periodic", "Full"], + # save_path=join(save_path, simu.name + "_space_fft_freq160.png"), + # is_show_fig=False, + # **dict_2D + # ) From af17fdc52b2a4428bb47e9364c60dbf81049c10a Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 12 Oct 2021 15:06:18 +0200 Subject: [PATCH 095/167] [CC] more robust way to compute rot_dir --- .../Methods/Machine/LamSlotMultiWind/comp_rot_dir.py | 4 ++-- pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py index 193e283ed..4ceb55e28 100644 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py @@ -23,8 +23,8 @@ def comp_rot_dir(self): ) # 20 points per pole over time and space is enough to capture rotating direction of fundamental mmf # Extract fundamental from unit mmf - result_p = MMF.get_harmonics(1, "freqs", "wavenumber=" + str(p)) - result_n = MMF.get_harmonics(1, "freqs", "wavenumber=" + str(-p)) + result_p = MMF.get_harmonics(1, "freqs>0", "wavenumber=" + str(p)) + result_n = MMF.get_harmonics(1, "freqs>0", "wavenumber=" + str(-p)) if result_p[MMF.symbol][0] > result_n[MMF.symbol][0]: result = result_p diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py b/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py index 362eada48..08c6feca8 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py @@ -22,11 +22,17 @@ def comp_rot_dir(self): MMF, _ = self.comp_mmf_unit(Nt=20 * p, Na=20 * p) # Extract fundamental from unit mmf - results = MMF.get_harmonics(1, "freqs>0", "wavenumber") + result_p = MMF.get_harmonics(1, "freqs>0", "wavenumber=" + str(p)) + result_n = MMF.get_harmonics(1, "freqs>0", "wavenumber=" + str(-p)) + + if result_p["Magnitude"][0] > result_n["Magnitude"][0]: + result = result_p + else: + result = result_n # Get frequency and wavenumber of fundamental - f = results["freqs"][0] - r = results["wavenumber"][0] + f = result["freqs"][0] + r = result["wavenumber"][0] # Rotating direction is the sign of the mechanical speed of the magnetic field fundamental, i.e frequency over wavenumber rot_dir = int(sign(f / r)) From dec3a0f4ae664c06d2c01eb69a1fb241ea651789 Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 12 Oct 2021 15:35:40 +0200 Subject: [PATCH 096/167] [BC] fix plot command --- Tests/Validation/Force/AGSF_Transfer/test_compare_Rag_var.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Validation/Force/AGSF_Transfer/test_compare_Rag_var.py b/Tests/Validation/Force/AGSF_Transfer/test_compare_Rag_var.py index 82f6fce52..8257dfaad 100644 --- a/Tests/Validation/Force/AGSF_Transfer/test_compare_Rag_var.py +++ b/Tests/Validation/Force/AGSF_Transfer/test_compare_Rag_var.py @@ -93,7 +93,7 @@ def test_compare_Rag_variation(): AGSF_list2.append(out_list2[ik].force.AGSF) out_list[ik].force.AGSF.plot_2D_Data( - "angle=[0,3.14]", + "angle", "time=0", data_list=[AGSF_list2[ik]], legend_list=["Direct", "Transfer"], @@ -188,7 +188,7 @@ def test_compare_Rag_variation_Nmax_sensitivity(): AGSF_list.append(out_tmp.force.AGSF) out.force.AGSF.plot_2D_Data( - "angle=[0,3.14]", + "angle", "time=0", data_list=AGSF_list, legend_list=legend_list, From 0a167d64e6b69981b3c6087e3d0d3b21cc8787ed Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 12 Oct 2021 15:36:01 +0200 Subject: [PATCH 097/167] [BC] use MMF.symbol --- pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py b/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py index 08c6feca8..e38baf3e4 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py @@ -25,7 +25,7 @@ def comp_rot_dir(self): result_p = MMF.get_harmonics(1, "freqs>0", "wavenumber=" + str(p)) result_n = MMF.get_harmonics(1, "freqs>0", "wavenumber=" + str(-p)) - if result_p["Magnitude"][0] > result_n["Magnitude"][0]: + if result_p[MMF.symbol][0] > result_n[MMF.symbol][0]: result = result_p else: result = result_n From 980e41a402e94459d76c174e15004851ed196c54 Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 12 Oct 2021 17:24:40 +0200 Subject: [PATCH 098/167] [NF] compute harmonic flux linkage --- Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py | 1 + pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py index df58b1565..1e2b1cb46 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py @@ -79,6 +79,7 @@ def test_LUT_PMSM(): ) out.simu.var_simu.postproc_list[0].LUT.get_Lmd(Id=50, Iq=50) + out.simu.var_simu.postproc_list[0].LUT.get_Phidqh_mag_harm() return out diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py index 5e64539ff..115cddebf 100644 --- a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py @@ -10,7 +10,7 @@ def get_Phidqh_mag(self): Returns ---------- - Phi_dqh_mag : ndarray + Phi_dqh_mag : DataND magnets flux linkage in dqh frame (Nt_tot, 3) """ From 516ce4643a55503db3967c41a4a9b15ffef7f244 Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 12 Oct 2021 17:24:52 +0200 Subject: [PATCH 099/167] [CC] docstrings --- .../Simulation/LUTdq/get_Phidqh_mag_harm.py | 22 ++++++++++++++++--- .../Simulation/LUTdq/get_Phidqh_mag_mean.py | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_harm.py b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_harm.py index b04fef94f..e72bab554 100644 --- a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_harm.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_harm.py @@ -1,4 +1,20 @@ def get_Phidqh_mag_harm(self): - # "freqs->elec_order>1" - pass - # TODO + """Get the harmonic magnets flux linkage in DQH frame + Parameters + ---------- + self : LUTdq + a LUTdq object + + Returns + ---------- + Phi_dqh_mag_harm : ndarray + mean magnets flux linkage in dqh frame (3,) + """ + + Phidqh_mag = self.get_Phidqh_mag() + Phidqh_mag_freq = Phidqh_mag.time_to_freq() + + # Filter out fondamental + Phidqh_mag_freq.values[0, :] = 0 + + return Phidqh_mag_freq diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_mean.py b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_mean.py index 029abc48f..850e0c57a 100644 --- a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_mean.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag_mean.py @@ -1,5 +1,5 @@ def get_Phidqh_mag_mean(self): - """Get the total d-axis inductance + """Get the mean magnets flux linkage in DQH frame Parameters ---------- self : LUTdq From 6abad7173a701ae515df67eee1544ca4ed23717a Mon Sep 17 00:00:00 2001 From: helene-t Date: Wed, 13 Oct 2021 11:45:23 +0200 Subject: [PATCH 100/167] [WiP] create PWM signal in InputVoltage --- pyleecan/Classes/Class_Dict.json | 18 ++++++ pyleecan/Classes/InputCurrent.py | 5 ++ pyleecan/Classes/InputFlux.py | 5 ++ pyleecan/Classes/InputVoltage.py | 57 +++++++++++++++++++ pyleecan/Classes/OutElec.py | 52 +++++++++++++++++ .../Generator/ClassesRef/Output/OutElec.csv | 1 + .../ClassesRef/Simulation/InputVoltage.csv | 1 + .../Simulation/InputVoltage/gen_input.py | 48 +++++++++++++++- 8 files changed, 186 insertions(+), 1 deletion(-) diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 441b9add6..04c4e9d0e 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4293,6 +4293,15 @@ "type": "float", "unit": "W", "value": null + }, + { + "desc": "Object to generate PWM signal", + "max": "", + "min": "", + "name": "PWM", + "type": "ImportGenPWM", + "unit": "", + "value": null } ] }, @@ -8107,6 +8116,15 @@ "type": "float", "unit": "Vrms", "value": null + }, + { + "desc": "Harmonic stator voltage as a function of time (each column correspond to one phase)", + "max": "", + "min": "", + "name": "Us_harm", + "type": "SciDataTool.Classes.DataND.DataND", + "unit": "V", + "value": "None" } ] }, diff --git a/pyleecan/Classes/InputCurrent.py b/pyleecan/Classes/InputCurrent.py index 852a02669..b2a8e1cd2 100644 --- a/pyleecan/Classes/InputCurrent.py +++ b/pyleecan/Classes/InputCurrent.py @@ -39,6 +39,7 @@ from ._check import InitUnKnowClassError from .ImportMatrix import ImportMatrix from .Import import Import +from .ImportGenPWM import ImportGenPWM class InputCurrent(InputVoltage): @@ -103,6 +104,7 @@ def __init__( slip_ref=0, U0_ref=None, Pem_av_ref=None, + PWM=None, time=None, angle=None, Nt_tot=2048, @@ -155,6 +157,8 @@ def __init__( U0_ref = init_dict["U0_ref"] if "Pem_av_ref" in list(init_dict.keys()): Pem_av_ref = init_dict["Pem_av_ref"] + if "PWM" in list(init_dict.keys()): + PWM = init_dict["PWM"] if "time" in list(init_dict.keys()): time = init_dict["time"] if "angle" in list(init_dict.keys()): @@ -184,6 +188,7 @@ def __init__( slip_ref=slip_ref, U0_ref=U0_ref, Pem_av_ref=Pem_av_ref, + PWM=PWM, time=time, angle=angle, Nt_tot=Nt_tot, diff --git a/pyleecan/Classes/InputFlux.py b/pyleecan/Classes/InputFlux.py index 5ae4732b5..5e58f2abb 100644 --- a/pyleecan/Classes/InputFlux.py +++ b/pyleecan/Classes/InputFlux.py @@ -29,6 +29,7 @@ from ._check import InitUnKnowClassError from .ImportMatrix import ImportMatrix from .Import import Import +from .ImportGenPWM import ImportGenPWM class InputFlux(InputCurrent): @@ -75,6 +76,7 @@ def __init__( slip_ref=0, U0_ref=None, Pem_av_ref=None, + PWM=None, time=None, angle=None, Nt_tot=2048, @@ -143,6 +145,8 @@ def __init__( U0_ref = init_dict["U0_ref"] if "Pem_av_ref" in list(init_dict.keys()): Pem_av_ref = init_dict["Pem_av_ref"] + if "PWM" in list(init_dict.keys()): + PWM = init_dict["PWM"] if "time" in list(init_dict.keys()): time = init_dict["time"] if "angle" in list(init_dict.keys()): @@ -180,6 +184,7 @@ def __init__( slip_ref=slip_ref, U0_ref=U0_ref, Pem_av_ref=Pem_av_ref, + PWM=PWM, time=time, angle=angle, Nt_tot=Nt_tot, diff --git a/pyleecan/Classes/InputVoltage.py b/pyleecan/Classes/InputVoltage.py index 7957fb544..4cdfbaf50 100644 --- a/pyleecan/Classes/InputVoltage.py +++ b/pyleecan/Classes/InputVoltage.py @@ -38,6 +38,7 @@ from numpy import array, array_equal from ._check import InitUnKnowClassError from .Import import Import +from .ImportGenPWM import ImportGenPWM from .ImportMatrix import ImportMatrix @@ -99,6 +100,7 @@ def __init__( slip_ref=0, U0_ref=None, Pem_av_ref=None, + PWM=None, time=None, angle=None, Nt_tot=2048, @@ -143,6 +145,8 @@ def __init__( U0_ref = init_dict["U0_ref"] if "Pem_av_ref" in list(init_dict.keys()): Pem_av_ref = init_dict["Pem_av_ref"] + if "PWM" in list(init_dict.keys()): + PWM = init_dict["PWM"] if "time" in list(init_dict.keys()): time = init_dict["time"] if "angle" in list(init_dict.keys()): @@ -166,6 +170,7 @@ def __init__( self.slip_ref = slip_ref self.U0_ref = U0_ref self.Pem_av_ref = Pem_av_ref + self.PWM = PWM # Call Input init super(InputVoltage, self).__init__( time=time, angle=angle, Nt_tot=Nt_tot, Nrev=Nrev, Na_tot=Na_tot, N0=N0 @@ -197,6 +202,11 @@ def __str__(self): InputVoltage_str += "slip_ref = " + str(self.slip_ref) + linesep InputVoltage_str += "U0_ref = " + str(self.U0_ref) + linesep InputVoltage_str += "Pem_av_ref = " + str(self.Pem_av_ref) + linesep + if self.PWM is not None: + tmp = self.PWM.__str__().replace(linesep, linesep + "\t").rstrip("\t") + InputVoltage_str += "PWM = " + tmp + else: + InputVoltage_str += "PWM = None" + linesep + linesep return InputVoltage_str def __eq__(self, other): @@ -228,6 +238,8 @@ def __eq__(self, other): return False if other.Pem_av_ref != self.Pem_av_ref: return False + if other.PWM != self.PWM: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -267,6 +279,12 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".U0_ref") if other._Pem_av_ref != self._Pem_av_ref: diff_list.append(name + ".Pem_av_ref") + if (other.PWM is None and self.PWM is not None) or ( + other.PWM is not None and self.PWM is None + ): + diff_list.append(name + ".PWM None mismatch") + elif self.PWM is not None: + diff_list.extend(self.PWM.compare(other.PWM, name=name + ".PWM")) # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -288,6 +306,7 @@ def __sizeof__(self): S += getsizeof(self.slip_ref) S += getsizeof(self.U0_ref) S += getsizeof(self.Pem_av_ref) + S += getsizeof(self.PWM) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -324,6 +343,14 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): InputVoltage_dict["slip_ref"] = self.slip_ref InputVoltage_dict["U0_ref"] = self.U0_ref InputVoltage_dict["Pem_av_ref"] = self.Pem_av_ref + if self.PWM is None: + InputVoltage_dict["PWM"] = None + else: + InputVoltage_dict["PWM"] = self.PWM.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name InputVoltage_dict["__class__"] = "InputVoltage" @@ -343,6 +370,8 @@ def _set_None(self): self.slip_ref = None self.U0_ref = None self.Pem_av_ref = None + if self.PWM is not None: + self.PWM._set_None() # Set to None the properties inherited from Input super(InputVoltage, self)._set_None() @@ -539,3 +568,31 @@ def _set_Pem_av_ref(self, value): :Type: float """, ) + + def _get_PWM(self): + """getter of PWM""" + return self._PWM + + def _set_PWM(self, value): + """setter of PWM""" + if isinstance(value, str): # Load from file + value = load_init_dict(value)[1] + if isinstance(value, dict) and "__class__" in value: + class_obj = import_class("pyleecan.Classes", value.get("__class__"), "PWM") + value = class_obj(init_dict=value) + elif type(value) is int and value == -1: # Default constructor + value = ImportGenPWM() + check_var("PWM", value, "ImportGenPWM") + self._PWM = value + + if self._PWM is not None: + self._PWM.parent = self + + PWM = property( + fget=_get_PWM, + fset=_set_PWM, + doc=u"""Object to generate PWM signal + + :Type: ImportGenPWM + """, + ) diff --git a/pyleecan/Classes/OutElec.py b/pyleecan/Classes/OutElec.py index e5d6bcf2e..409414b8b 100644 --- a/pyleecan/Classes/OutElec.py +++ b/pyleecan/Classes/OutElec.py @@ -140,6 +140,7 @@ def __init__( internal=None, slip_ref=0, U0_ref=None, + Us_harm=None, init_dict=None, init_str=None, ): @@ -196,6 +197,8 @@ def __init__( slip_ref = init_dict["slip_ref"] if "U0_ref" in list(init_dict.keys()): U0_ref = init_dict["U0_ref"] + if "Us_harm" in list(init_dict.keys()): + Us_harm = init_dict["Us_harm"] # Set the properties (value check and convertion are done in setter) self.parent = None self.axes_dict = axes_dict @@ -217,6 +220,7 @@ def __init__( self.internal = internal self.slip_ref = slip_ref self.U0_ref = U0_ref + self.Us_harm = Us_harm # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -260,6 +264,7 @@ def __str__(self): OutElec_str += "internal = None" + linesep + linesep OutElec_str += "slip_ref = " + str(self.slip_ref) + linesep OutElec_str += "U0_ref = " + str(self.U0_ref) + linesep + OutElec_str += "Us_harm = " + str(self.Us_harm) + linesep + linesep return OutElec_str def __eq__(self, other): @@ -305,6 +310,8 @@ def __eq__(self, other): return False if other.U0_ref != self.U0_ref: return False + if other.Us_harm != self.Us_harm: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -384,6 +391,14 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".slip_ref") if other._U0_ref != self._U0_ref: diff_list.append(name + ".U0_ref") + if (other.Us_harm is None and self.Us_harm is not None) or ( + other.Us_harm is not None and self.Us_harm is None + ): + diff_list.append(name + ".Us_harm None mismatch") + elif self.Us_harm is not None: + diff_list.extend( + self.Us_harm.compare(other.Us_harm, name=name + ".Us_harm") + ) # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -413,6 +428,7 @@ def __sizeof__(self): S += getsizeof(self.internal) S += getsizeof(self.slip_ref) S += getsizeof(self.U0_ref) + S += getsizeof(self.Us_harm) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -498,6 +514,14 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): ) OutElec_dict["slip_ref"] = self.slip_ref OutElec_dict["U0_ref"] = self.U0_ref + if self.Us_harm is None: + OutElec_dict["Us_harm"] = None + else: + OutElec_dict["Us_harm"] = self.Us_harm.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) # The class name is added to the dict for deserialisation purpose OutElec_dict["__class__"] = "OutElec" return OutElec_dict @@ -525,6 +549,7 @@ def _set_None(self): self.internal._set_None() self.slip_ref = None self.U0_ref = None + self.Us_harm = None def _get_axes_dict(self): """getter of axes_dict""" @@ -926,3 +951,30 @@ def _set_U0_ref(self, value): :Type: float """, ) + + def _get_Us_harm(self): + """getter of Us_harm""" + return self._Us_harm + + def _set_Us_harm(self, value): + """setter of Us_harm""" + if isinstance(value, str): # Load from file + value = load_init_dict(value)[1] + if isinstance(value, dict) and "__class__" in value: + class_obj = import_class( + "SciDataTool.Classes", value.get("__class__"), "Us_harm" + ) + value = class_obj(init_dict=value) + elif type(value) is int and value == -1: # Default constructor + value = DataND() + check_var("Us_harm", value, "DataND") + self._Us_harm = value + + Us_harm = property( + fget=_get_Us_harm, + fset=_set_Us_harm, + doc=u"""Harmonic stator voltage as a function of time (each column correspond to one phase) + + :Type: SciDataTool.Classes.DataND.DataND + """, + ) diff --git a/pyleecan/Generator/ClassesRef/Output/OutElec.csv b/pyleecan/Generator/ClassesRef/Output/OutElec.csv index 4ba0c1a25..14f5c5430 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutElec.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutElec.csv @@ -18,3 +18,4 @@ Us,V,Stator voltage as a function of time (each column correspond to one phase), internal,-,OutInternal object containg outputs related to a specific model,0,OutInternal,None,,,,,,,,, slip_ref,,Rotor mechanical slip,,float,0,,,,,,,,, U0_ref,Vrms,stator voltage (phase to neutral),,float,None,,,,,,,,, +Us_harm,V,Harmonic stator voltage as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv index d4fc65f2d..8f8e79811 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv @@ -9,3 +9,4 @@ felec,Hz,electrical frequency,1,float,None,,,,,,,,, slip_ref,,Rotor mechanical slip,,float,0,,,,,,,,, U0_ref,Vrms,stator voltage (phase to neutral),,float,None,,,,,,,,, Pem_av_ref,W,Theorical Average Electromagnetic Power,1,float,None,,,,,,,,, +PWM,,Object to generate PWM signal,,ImportGenPWM,None,,,,,,,,, diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 41a180588..62d7c76ce 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -1,9 +1,13 @@ -from numpy import ndarray +from numpy import ndarray, pi from ....Classes.OutElec import OutElec from ....Classes.Simulation import Simulation from ....Methods.Simulation.Input import InputError +from ....Functions.Electrical.coordinate_transformation import n2dqh, n2dqh_DataTime +from ....Functions.Winding.gen_phase_list import gen_name + +from SciDataTool import Data1D, DataLinspace, DataTime def gen_input(self): @@ -100,3 +104,45 @@ def gen_input(self): axes_dict_in=outgeo.axes_dict, is_periodicity_t=False, ) + + # Generate PWM signal + if self.PWM is not None: + # Fill generator with simu data + felec = self.comp_felec + rot_dir = output.get_rot_dir() + qs = simu.machine.stator.winding.qs + self.PWM.f = felec + self.fs = self.PWM.fmax * 2.56 # Shanon based sampling frequency (with margin) + self.PWM.duration = 1 / felec + # Generate PWM signal + Uabc, modulation, _, carrier, time = self.PWM.get_data() + # Create DataTime object + Time = DataLinspace( + name="time", + unit="s", + initial=0, + final=time[-1], + number=len(time), + include_endpoint=True, + ) + Phase = Data1D( + name="phase", + unit="", + values=gen_name(qs), + is_components=True, + ) + Uabc_data = DataTime( + name="Stator voltage", + symbol="U_{abc}", + unit="V", + axes=[Time, Phase], + values=Uabc, + ) + # Rotate to DQH frame + Udqh = n2dqh_DataTime( + Uabc_data, 2 * pi * felec * time, n=qs, rot_dir=rot_dir, is_dq_rms=True + ) + # fft + Udqh_freq = Udqh.time_to_freq() + # Store + outelec.Us_harm = Udqh_freq From f05db57affabfcb861cea1a3b1376d3ad72d69a3 Mon Sep 17 00:00:00 2001 From: helene-t Date: Wed, 13 Oct 2021 13:39:18 +0200 Subject: [PATCH 101/167] [CO] store Ud_ref and Uq_ref in outelec --- pyleecan/Methods/Simulation/InputVoltage/gen_input.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 62d7c76ce..13df6610b 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -52,6 +52,8 @@ def gen_input(self): raise Exception("U0_ref, Ud_ref, and Uq_ref cannot be all None in InputVoltage") outelec.U0_ref = self.U0_ref + outelec.Ud_ref = self.Ud_ref + outelec.Uq_ref = self.Uq_ref outelec.slip_ref = self.slip_ref # Load and check alpha_rotor and N0 From ef2b742907132ef653e1eef1a8df8e7f164c8176 Mon Sep 17 00:00:00 2001 From: helene-t Date: Wed, 13 Oct 2021 13:41:39 +0200 Subject: [PATCH 102/167] [CC] check angle_elec + remove plot --- Tests/Functions/test_coordinate_transformation.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Tests/Functions/test_coordinate_transformation.py b/Tests/Functions/test_coordinate_transformation.py index 875f66788..18cab3736 100644 --- a/Tests/Functions/test_coordinate_transformation.py +++ b/Tests/Functions/test_coordinate_transformation.py @@ -45,8 +45,8 @@ def test_coordinate_transformation(param_dict): } Time = Data1D(name="time", unit="s", values=time, normalizations=norm_time) - # TODO: test angle_elec angle_elec_bis = Time.get_values(is_smallestperiod=True, normalization="angle_elec") + assert_array_almost_equal(angle_elec, angle_elec_bis) # Phase axis for plots Phase = Data1D( @@ -85,8 +85,6 @@ def test_coordinate_transformation(param_dict): values=In, ) - In_data.plot_2D_Data("time", "phase[]") - I_dqh_data = n2dqh_DataTime(In_data) In_data_check = dqh2n_DataTime(I_dqh_data, n=qs) From 57d48e363267be0fb894585fe577739a15937f21 Mon Sep 17 00:00:00 2001 From: helene-t Date: Wed, 13 Oct 2021 14:05:50 +0200 Subject: [PATCH 103/167] [BC] fix Us_harm computation --- .../Methods/Simulation/InputVoltage/gen_input.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 13df6610b..fe0f9cd59 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -1,3 +1,4 @@ +from SciDataTool import Norm_ref from numpy import ndarray, pi from ....Classes.OutElec import OutElec @@ -110,11 +111,13 @@ def gen_input(self): # Generate PWM signal if self.PWM is not None: # Fill generator with simu data - felec = self.comp_felec + felec = self.comp_felec() rot_dir = output.get_rot_dir() qs = simu.machine.stator.winding.qs self.PWM.f = felec - self.fs = self.PWM.fmax * 2.56 # Shanon based sampling frequency (with margin) + self.PWM.fs = ( + self.PWM.fmax * 2.56 + ) # Shanon based sampling frequency (with margin) self.PWM.duration = 1 / felec # Generate PWM signal Uabc, modulation, _, carrier, time = self.PWM.get_data() @@ -126,6 +129,7 @@ def gen_input(self): final=time[-1], number=len(time), include_endpoint=True, + normalizations={"angle_elec": Norm_ref(ref=rot_dir / (2 * pi * felec))}, ) Phase = Data1D( name="phase", @@ -141,9 +145,7 @@ def gen_input(self): values=Uabc, ) # Rotate to DQH frame - Udqh = n2dqh_DataTime( - Uabc_data, 2 * pi * felec * time, n=qs, rot_dir=rot_dir, is_dq_rms=True - ) + Udqh = n2dqh_DataTime(Uabc_data, is_dqh_rms=True) # fft Udqh_freq = Udqh.time_to_freq() # Store From 34a6dab21670fda149a92d3328945f3b23a2f407 Mon Sep 17 00:00:00 2001 From: helene-t Date: Wed, 13 Oct 2021 14:06:11 +0200 Subject: [PATCH 104/167] [NF] add test to Us_harm computation (PWM) --- .../Methods/Simulation/test_InVoltage_PWM.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 Tests/Methods/Simulation/test_InVoltage_PWM.py diff --git a/Tests/Methods/Simulation/test_InVoltage_PWM.py b/Tests/Methods/Simulation/test_InVoltage_PWM.py new file mode 100644 index 000000000..a2f812cc7 --- /dev/null +++ b/Tests/Methods/Simulation/test_InVoltage_PWM.py @@ -0,0 +1,47 @@ +from os.path import join + +from pyleecan.Classes.ImportGenPWM import ImportGenPWM +from pyleecan.Classes.InputVoltage import InputVoltage +from pyleecan.Classes.Simu1 import Simu1 +from pyleecan.definitions import DATA_DIR +from pyleecan.Functions.load import load +from pyleecan.Functions.Plot import dict_2D +from Tests import save_plot_path as save_path + +is_show_fig = True + + +def test_InVoltage_PWM(): + + fmax = 20000 + fswi = 7000 + Vdc1 = 2 # Bus voltage + U0 = 0.5 # Phase voltage + + Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) + + simu = Simu1(name="test_InVoltage_PWM", machine=Toyota_Prius) + + simu.input = InputVoltage( + N0=2000, + Na_tot=1024, + Nt_tot=1024, + PWM=ImportGenPWM(fmax=fmax, fswi=fswi, Vdc1=Vdc1, U0=U0), + ) + + out = simu.run() + + if is_show_fig: + out.elec.Us_harm.plot_2D_Data( + "freqs", + is_auto_ticks=False, + save_path=join(save_path, "test_InVoltage_PWM.png"), + **dict_2D + ) + + return out + + +# To run it without pytest +if __name__ == "__main__": + out = test_InVoltage_PWM() From 07fed825543840bd2641a267b3a95449392cf4fd Mon Sep 17 00:00:00 2001 From: helene-t Date: Wed, 13 Oct 2021 15:52:20 +0200 Subject: [PATCH 105/167] [CC] Us symbol --- pyleecan/Methods/Simulation/InputVoltage/gen_input.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index fe0f9cd59..9dbccc00f 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -139,7 +139,7 @@ def gen_input(self): ) Uabc_data = DataTime( name="Stator voltage", - symbol="U_{abc}", + symbol="U_s", unit="V", axes=[Time, Phase], values=Uabc, From 3b31cc3e8a0235750c62ebf6a7ee52f11d9d85a9 Mon Sep 17 00:00:00 2001 From: Pierre Bonneel Date: Wed, 13 Oct 2021 16:06:32 +0200 Subject: [PATCH 106/167] [WIP] Using Simulation/MagFEMM for FluxLinkFEMM.csv and IndMagFEMM.csv --- Tests/Validation/Electrical/test_EEC_SCIM.py | 2 +- .../Functions/Electrical/comp_fluxlinkage.py | 4 - .../ClassesRef/Simulation/FluxLinkFEMM.csv | 2 +- .../ClassesRef/Simulation/IndMagFEMM.csv | 2 +- .../Simulation/EEC_PMSM/comp_parameters.py | 26 ++++-- .../Methods/Simulation/EEC_PMSM/solve_EEC.py | 4 +- .../Simulation/EEC_SCIM/comp_parameters.py | 18 ++-- pyleecan/Methods/Simulation/Electrical/run.py | 14 ++- .../FluxLinkFEMM/comp_fluxlinkage.py | 85 ++++++++++++------- .../Simulation/FluxLinkFEMM/solve_FEMM.py | 5 -- .../Simulation/IndMagFEMM/comp_inductance.py | 61 +++++++++++-- .../Simulation/IndMagFEMM/solve_FEMM.py | 5 -- 12 files changed, 148 insertions(+), 80 deletions(-) delete mode 100644 pyleecan/Methods/Simulation/FluxLinkFEMM/solve_FEMM.py delete mode 100644 pyleecan/Methods/Simulation/IndMagFEMM/solve_FEMM.py diff --git a/Tests/Validation/Electrical/test_EEC_SCIM.py b/Tests/Validation/Electrical/test_EEC_SCIM.py index 00531377c..46ba82fd2 100644 --- a/Tests/Validation/Electrical/test_EEC_SCIM.py +++ b/Tests/Validation/Electrical/test_EEC_SCIM.py @@ -23,7 +23,7 @@ def test_EEC_SCIM(): """Validation of the SCIM Electrical Equivalent Circuit with the 3kW SCIM from 'Berechnung elektrischer Maschinen' (ISBN: 978-3-527-40525-1) Note: conductor properties have been set to operation point temperature condition, - stator end winding length is adapted to ref. lenght + stator end winding length is adapted to ref. lenght """ SCIM = load(join(DATA_DIR, "Machine", "SCIM_010.json")) diff --git a/pyleecan/Functions/Electrical/comp_fluxlinkage.py b/pyleecan/Functions/Electrical/comp_fluxlinkage.py index 553e6b4f7..bfb8fbe68 100644 --- a/pyleecan/Functions/Electrical/comp_fluxlinkage.py +++ b/pyleecan/Functions/Electrical/comp_fluxlinkage.py @@ -112,10 +112,6 @@ def comp_fluxlinkage(obj, output): femm.closefemm() output.elec.internal.handler_list.remove(femm) - # Define d axis angle for the d,q transform - d_angle = (angle_rotor - angle_offset_initial) * zp - fluxdq = split(n2dqh(Phi_wind, d_angle), 2, axis=1) - # restore the original elec output.elec = elec diff --git a/pyleecan/Generator/ClassesRef/Simulation/FluxLinkFEMM.csv b/pyleecan/Generator/ClassesRef/Simulation/FluxLinkFEMM.csv index 662c71d51..33c47bcdf 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/FluxLinkFEMM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/FluxLinkFEMM.csv @@ -1,6 +1,6 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille FEMM_dict,,To enforce user-defined values for FEMM main parameters ,0,dict,,,,,Simulation,FluxLink,comp_fluxlinkage,VERSION,1,Electric module: Flux Linkage with FEMM, -type_calc_leakage,,0 no leakage calculation / 1 calculation using single slot ,0,int,0,0,1,,,,solve_FEMM,,,, +type_calc_leakage,,0 no leakage calculation / 1 calculation using single slot ,0,int,0,0,1,,,,,,,, is_sliding_band,,0 to desactivate the sliding band,0,bool,1,,,,,,,,,, is_periodicity_a,,True to take into account the spatial periodicity of the machine,0,bool,0,,,,,,,,,, Nt_tot,-,Number of time steps for the FEMM simulation,0,int,5,,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/IndMagFEMM.csv b/pyleecan/Generator/ClassesRef/Simulation/IndMagFEMM.csv index da57b98cf..9007ad768 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/IndMagFEMM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/IndMagFEMM.csv @@ -1,6 +1,6 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille FEMM_dict,,To enforce user-defined values for FEMM main parameters ,0,dict,,,,,Simulation,IndMag,comp_inductance,VERSION,1,Electric module: Magnetic Inductance with FEMM, -type_calc_leakage,,0 no leakage calculation / 1 calculation using single slot ,0,int,0,0,1,,,,solve_FEMM,,,, +type_calc_leakage,,0 no leakage calculation / 1 calculation using single slot ,0,int,0,0,1,,,,,,,, is_sliding_band,,0 to desactivate the sliding band,0,bool,1,,,,,,,,,, is_periodicity_a,,True to take into account the spatial periodicity of the machine,0,bool,0,,,,,,,,,, Nt_tot,-,Number of time steps for the FEMM simulation,0,int,5,,,,,,,,,, diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py index e31fe4a03..b2a9a82e9 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py @@ -1,7 +1,10 @@ # -*- coding: utf-8 -*- -def comp_parameters(self, output, Tsta=None, Trot=None): +from enum import unique + + +def comp_parameters(self, machine, N0, felec, Id_ref, Iq_ref, Tsta=None, Trot=None): """Compute the parameters dict for the equivalent electrical circuit: resistance, inductance and back electromotive force Parameters @@ -13,18 +16,21 @@ def comp_parameters(self, output, Tsta=None, Trot=None): """ # TODO maybe set currents to small value if I is 0 to compute inductance + # OPdq = N0, felec, Id, Iq, Ud, Uq, Tem, Pem + # OPslip = N0, felec, U0, slip, I0, Phi0, Tem, Pem + PAR = self.parameters - Cond = self.parent.parent.machine.stator.winding.conductor + Cond = machine.stator.winding.conductor # compute skin_effect Xkr_skinS, Xke_skinS = Cond.comp_skin_effect(T=20) # Parameters to compute only once if "R20" not in PAR: - R20 = output.simu.machine.stator.comp_resistance_wind() + R20 = machine.stator.comp_resistance_wind() PAR["R20"] = R20 * Xkr_skinS if "phi" not in PAR: - PAR["phi"] = self.fluxlink.comp_fluxlinkage(output) + PAR["phi"] = self.fluxlink.comp_fluxlinkage(machine) # Parameters which may vary for each simulation is_comp_ind = False @@ -34,17 +40,19 @@ def comp_parameters(self, output, Tsta=None, Trot=None): is_comp_ind = True # check for d- and q-current (change) - if "Id" not in PAR or PAR["Id"] != output.elec.Id_ref: - PAR["Id"] = output.elec.Id_ref + if "Id" not in PAR or PAR["Id"] != Id_ref: + PAR["Id"] = Id_ref is_comp_ind = True - if "Iq" not in PAR or PAR["Iq"] != output.elec.Iq_ref: - PAR["Iq"] = output.elec.Iq_ref + if "Iq" not in PAR or PAR["Iq"] != Iq_ref: + PAR["Iq"] = Iq_ref is_comp_ind = True # compute inductance if necessary if is_comp_ind: - (phid, phiq) = self.indmag.comp_inductance(output) + (phid, phiq) = self.indmag.comp_inductance( + machine=machine, Id_ref=Id_ref, Iq_ref=Iq_ref + ) (phid, phiq) = tuple([z * Xke_skinS for z in (phid, phiq)]) if PAR["Id"] != 0: PAR["Ld"] = (phid - PAR["phi"]) / PAR["Id"] diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/solve_EEC.py b/pyleecan/Methods/Simulation/EEC_PMSM/solve_EEC.py index 35800bd0f..56bb4d764 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/solve_EEC.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/solve_EEC.py @@ -33,7 +33,7 @@ def solve_EEC(self, output): # Prepare linear system # Solve system - if "Ud" in self.parameters: + if "Ud" in self.parameters: # Voltage driven XR = array( [ [self.parameters["R20"], -ws * self.parameters["Lq"]], @@ -45,7 +45,7 @@ def solve_EEC(self, output): XI = solve(XR, XU - XE) output.elec.Id_ref = XI[0] output.elec.Iq_ref = XI[1] - else: + else: # Current Driven output.elec.Ud_ref = ( self.parameters["R20"] * self.parameters["Id"] - ws * self.parameters["Phiq"] diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py index b45ebd440..17e65cf21 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py @@ -6,7 +6,7 @@ from ....Functions.load import import_class -def comp_parameters(self, output, Tsta, Trot): +def comp_parameters(self, machine, N0, felec, Id_ref, Iq_ref, Tsta, Trot): """Compute the parameters dict for the equivalent electrical circuit: resistance, inductance Parameters @@ -18,14 +18,10 @@ def comp_parameters(self, output, Tsta, Trot): """ # get some machine parameters - machine = output.simu.machine Zsr = machine.rotor.slot.Zs qsr = machine.rotor.winding.qs qs = machine.stator.winding.qs p = machine.rotor.winding.p - felec = ( - output.simu.input.comp_felec() - ) # XXX felec should be at same level that Tsta Trot (simulation param) # simulation type for magnetizing inductance when missing (0: FEA, 1: Analytical) type_comp_Lm = 0 @@ -49,13 +45,13 @@ def comp_parameters(self, output, Tsta, Trot): # check that parameters are in ELUT, otherwise compute missing ones -> to be put in check_ELUT method? if "slip" not in self.parameters or self.parameters["slip"] is None: - Nr = output.elec.N0 - Ns = output.elec.felec / p * 60 + Nr = N0 + Ns = felec / p * 60 slip = (Ns - Nr) / Ns self.parameters["slip"] = slip if "R1" not in self.parameters or self.parameters["R1"] is None: - CondS = self.parent.parent.machine.stator.winding.conductor + CondS = machine.stator.winding.conductor # get resistance calculated analytically at simulation temperature R1 = machine.stator.comp_resistance_wind(T=Tsta) # compute skin_effect on stator side @@ -71,7 +67,7 @@ def comp_parameters(self, output, Tsta, Trot): # compute skin_effect on rotor side if Xkr_skinR is None: - CondR = self.parent.parent.machine.rotor.winding.conductor + CondR = machine.rotor.winding.conductor Xkr_skinR, Xke_skinR = CondR.comp_skin_effect( freq=felec * (self.parameters["slip"]), T=Trot ) @@ -88,7 +84,7 @@ def comp_parameters(self, output, Tsta, Trot): # L2 = machine.rotor.slot.comp_inductance_leakage_ANL() #TODO L20 = 0 if Xke_skinR is None: - CondR = self.parent.parent.machine.rotor.winding.conductor + CondR = machine.rotor.winding.conductor Xkr_skinR, Xke_skinR = CondR.comp_skin_effect( freq=felec * (self.parameters["slip"]), T=Trot ) @@ -106,7 +102,7 @@ def comp_parameters(self, output, Tsta, Trot): # L10 = machine.stator.slot.comp_inductance_leakage_ANL() #TODO L10 = 0 if Xke_skinS is None: - CondS = self.parent.parent.machine.stator.winding.conductor + CondS = machine.stator.winding.conductor Xkr_skinS, Xke_skinS = CondS.comp_skin_effect(freq=felec, T=Tsta) L1 = L10 * Xke_skinS else: diff --git a/pyleecan/Methods/Simulation/Electrical/run.py b/pyleecan/Methods/Simulation/Electrical/run.py index 83ad374f7..f84b9c201 100644 --- a/pyleecan/Methods/Simulation/Electrical/run.py +++ b/pyleecan/Methods/Simulation/Electrical/run.py @@ -26,7 +26,6 @@ def run(self): self.eec = EEC_SCIM() elif isinstance(machine, (MachineSIPMSM, MachineIPMSM)): self.eec = EEC_PMSM() - else: # Check that EEC is consistent with machine type if isinstance(machine, MachineSCIM) and not isinstance(self.eec, EEC_SCIM): @@ -52,8 +51,19 @@ def run(self): self.eec.parameters["Ud_ref"] = output.elec.Ud_ref self.eec.parameters["Uq_ref"] = output.elec.Uq_ref + # Ud_ref, Ud_ref, fe => OP_fund + # for harm + # Ud, Uq, Fharm => Op_harm # Compute parameters of the electrical equivalent circuit if some parameters are missing in ELUT - out_dict = self.eec.comp_parameters(output, Tsta=self.Tsta, Trot=self.Trot) + out_dict = self.eec.comp_parameters( + machine, + output.elec.N0, + output.elec.felec, + output.elec.Id_ref, + output.elec.Iq_ref, + Tsta=self.Tsta, + Trot=self.Trot, + ) # Solve the electrical equivalent circuit out_dict = self.eec.solve_EEC(output) diff --git a/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py b/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py index ccdbd148e..b161b648d 100644 --- a/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py +++ b/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py @@ -1,11 +1,14 @@ -# -*- coding: utf-8 -*- +from os.path import join -from ....Functions.Electrical.comp_fluxlinkage import comp_fluxlinkage as comp_flx -from numpy import zeros, split, mean -import matplotlib.pyplot as plt +from numpy import mean, split +from pyleecan.Classes.InputCurrent import InputCurrent +from pyleecan.Classes.MagFEMM import MagFEMM +from pyleecan.Classes.Simulation import Simulation +from ....Functions.Electrical.coordinate_transformation import n2dqh -def comp_fluxlinkage(self, output): + +def comp_fluxlinkage(self, machine): """Compute using FEMM the flux linkage Parameters @@ -18,32 +21,50 @@ def comp_fluxlinkage(self, output): self.get_logger().info("INFO: Compute flux linkage with FEMM") - # store orignal currents - Is = output.elec.Is - Id_ref = output.elec.Id_ref - Iq_ref = output.elec.Iq_ref - - # Set currents at 0A for the FEMM simulation - output.elec.Is = None - output.elec.Id_ref = 0 - output.elec.Iq_ref = 0 - - # compute the fluxlinkage - fluxdq = comp_flx(self, output) - - # flux = split(Phi_wind, 3, axis=1) - # fig = plt.figure() - # plt.plot(angle, flux[0], color="tab:blue", label="A") - # plt.plot(angle, flux[1], color="tab:red", label="B") - # plt.plot(angle, flux[2], color="tab:olive", label="C") - # plt.plot(angle, fluxdq[0], color="k", label="D") - # plt.plot(angle, fluxdq[1], color="g", label="Q") - # plt.legend() - # fig.savefig("test_fluxlink.png") - - # restore orignal currents - output.elec.Is = Is - output.elec.Id_ref = Id_ref - output.elec.Iq_ref = Iq_ref + # Remove skew + machine_fl = machine.copy() + machine_fl.rotor.skew = None + machine_fl.stator.skew = None + + # Get simulation name and result path + if isinstance(machine.parent, Simulation) and machine.parent.name not in [None, ""]: + simu_name = machine.parent.name + "_FluxLinkage" + path_result = ( + join(machine.parent.path_result, "FluxLinkage") + if machine.parent.path_result not in [None, ""] + else None + ) + elif machine.name not in [None, ""]: + simu_name = machine.name + "_FluxLinkage" + path_result = None + else: + simu_name = "FluxLinkage" + path_result = None + + # Define simulation + simu_fl = Simulation( + elec=None, name=simu_name, path_result=path_result, machine=machine_fl + ) + simu_fl.input = InputCurrent( + N0=2000, Id_ref=0, Iq_ref=0, Nt_tot=self.Nt_tot, Na_tot=2048 + ) + simu_fl.mag = MagFEMM( + is_periodicity_t=True, + is_periodicity_a=True, + is_sliding_band=self.is_sliding_band, + Kgeo_fineness=self.Kgeo_fineness, + type_calc_leakage=self.type_calc_leakage, + ) + + # Run Simulation + out_fl = simu_fl.run() + # Post-Process + angle_rotor = out_fl.get_angle_rotor() + angle_offset_initial = out_fl.get_angle_offset_initial() + zp = machine.get_pole_pair_number() + Phi_wind = out_fl.mag.Phi_wind_stator + # Define d axis angle for the d,q transform + d_angle = (angle_rotor - angle_offset_initial) * zp + fluxdq = split(n2dqh(Phi_wind, d_angle), 2, axis=1) return mean(fluxdq[0]) diff --git a/pyleecan/Methods/Simulation/FluxLinkFEMM/solve_FEMM.py b/pyleecan/Methods/Simulation/FluxLinkFEMM/solve_FEMM.py deleted file mode 100644 index d2534339e..000000000 --- a/pyleecan/Methods/Simulation/FluxLinkFEMM/solve_FEMM.py +++ /dev/null @@ -1,5 +0,0 @@ -from ....Functions.Electrical.solve_FEMM import solve_FEMM as solve_FEMM_ - - -def solve_FEMM(self, femm, output, sym, FEMM_dict): - return solve_FEMM_(self, femm, output, sym, FEMM_dict) diff --git a/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py b/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py index f667e35e5..01aa8d344 100644 --- a/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py +++ b/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py @@ -1,11 +1,15 @@ -# -*- coding: utf-8 -*- +from os.path import join -from ....Functions.Electrical.comp_fluxlinkage import comp_fluxlinkage as comp_flx -from numpy import mean +from numpy import mean, split +from pyleecan.Classes.InputCurrent import InputCurrent +from pyleecan.Classes.MagFEMM import MagFEMM +from pyleecan.Classes.Simulation import Simulation +from ....Functions.Electrical.coordinate_transformation import n2dqh -def comp_inductance(self, output): - """Compute using FEMM the inductance + +def comp_inductance(self, machine, Id_ref, Iq_ref): + """Compute using FEMM the inductance (Current driven only) Parameters ---------- @@ -17,7 +21,50 @@ def comp_inductance(self, output): self.get_logger().info("INFO: Compute dq inductances with FEMM") - # compute the fluxlinkage - fluxdq = comp_flx(self, output) + # Remove skew + machine_fl = machine.copy() + machine_fl.rotor.skew = None + machine_fl.stator.skew = None + + # Get simulation name and result path + if isinstance(machine.parent, Simulation) and machine.parent.name not in [None, ""]: + simu_name = machine.parent.name + "_FluxLinkage" + path_result = ( + join(machine.parent.path_result, "FluxLinkage") + if machine.parent.path_result not in [None, ""] + else None + ) + elif machine.name not in [None, ""]: + simu_name = machine.name + "_FluxLinkage" + path_result = None + else: + simu_name = "FluxLinkage" + path_result = None + + # Define simulation + simu_ind = Simulation( + elec=None, name=simu_name, path_result=path_result, machine=machine_fl + ) + simu_ind.input = InputCurrent( + N0=2000, Id_ref=0, Iq_ref=0, Nt_tot=self.Nt_tot, Na_tot=2048 + ) + simu_ind.mag = MagFEMM( + is_periodicity_t=True, + is_periodicity_a=True, + is_sliding_band=self.is_sliding_band, + Kgeo_fineness=self.Kgeo_fineness, + type_calc_leakage=self.type_calc_leakage, + ) + + # Run Simulation + out_ind = simu_ind.run() + # Post-Process + angle_rotor = out_ind.get_angle_rotor() + angle_offset_initial = out_ind.get_angle_offset_initial() + zp = machine.get_pole_pair_number() + Phi_wind = out_ind.mag.Phi_wind_stator + # Define d axis angle for the d,q transform + d_angle = (angle_rotor - angle_offset_initial) * zp + fluxdq = split(n2dqh(Phi_wind, d_angle), 2, axis=1) return (mean(fluxdq[0]), mean(fluxdq[1])) diff --git a/pyleecan/Methods/Simulation/IndMagFEMM/solve_FEMM.py b/pyleecan/Methods/Simulation/IndMagFEMM/solve_FEMM.py deleted file mode 100644 index d2534339e..000000000 --- a/pyleecan/Methods/Simulation/IndMagFEMM/solve_FEMM.py +++ /dev/null @@ -1,5 +0,0 @@ -from ....Functions.Electrical.solve_FEMM import solve_FEMM as solve_FEMM_ - - -def solve_FEMM(self, femm, output, sym, FEMM_dict): - return solve_FEMM_(self, femm, output, sym, FEMM_dict) From 1d5472e1fd447bf34feb32f4ca59e4a32b1e3398 Mon Sep 17 00:00:00 2001 From: Pierre Bonneel Date: Wed, 13 Oct 2021 17:22:52 +0200 Subject: [PATCH 107/167] [BC] Correcting tests --- Tests/Validation/Electrical/test_EEC_PMSM.py | 4 ++-- .../Simulation/EEC_PMSM/comp_parameters.py | 2 +- .../FluxLinkFEMM/comp_fluxlinkage.py | 17 ++++++---------- .../Simulation/IndMagFEMM/comp_inductance.py | 20 ++++++++----------- 4 files changed, 17 insertions(+), 26 deletions(-) diff --git a/Tests/Validation/Electrical/test_EEC_PMSM.py b/Tests/Validation/Electrical/test_EEC_PMSM.py index 573678a94..a08a7e0a9 100644 --- a/Tests/Validation/Electrical/test_EEC_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_PMSM.py @@ -45,8 +45,8 @@ def test_EEC_PMSM(): # Definition of the electrical simulation (FEMM) simu.elec = Electrical() simu.elec.eec = EEC_PMSM( - indmag=IndMagFEMM(is_periodicity_a=True, Nt_tot=10), - fluxlink=FluxLinkFEMM(is_periodicity_a=True, Nt_tot=10), + indmag=IndMagFEMM(is_periodicity_a=True, Nt_tot=16), + fluxlink=FluxLinkFEMM(is_periodicity_a=True, Nt_tot=16), ) simu.mag = None diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py index b2a9a82e9..d6d99683e 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py @@ -23,7 +23,7 @@ def comp_parameters(self, machine, N0, felec, Id_ref, Iq_ref, Tsta=None, Trot=No Cond = machine.stator.winding.conductor # compute skin_effect - Xkr_skinS, Xke_skinS = Cond.comp_skin_effect(T=20) + Xkr_skinS, Xke_skinS = Cond.comp_skin_effect(T=20, freq=felec) # Parameters to compute only once if "R20" not in PAR: diff --git a/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py b/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py index b161b648d..99f2767cf 100644 --- a/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py +++ b/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py @@ -3,9 +3,9 @@ from numpy import mean, split from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.MagFEMM import MagFEMM +from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.Simulation import Simulation - -from ....Functions.Electrical.coordinate_transformation import n2dqh +from ....Functions.Electrical.coordinate_transformation import n2dqh_DataTime def comp_fluxlinkage(self, machine): @@ -42,7 +42,7 @@ def comp_fluxlinkage(self, machine): path_result = None # Define simulation - simu_fl = Simulation( + simu_fl = Simu1( elec=None, name=simu_name, path_result=path_result, machine=machine_fl ) simu_fl.input = InputCurrent( @@ -60,11 +60,6 @@ def comp_fluxlinkage(self, machine): out_fl = simu_fl.run() # Post-Process - angle_rotor = out_fl.get_angle_rotor() - angle_offset_initial = out_fl.get_angle_offset_initial() - zp = machine.get_pole_pair_number() - Phi_wind = out_fl.mag.Phi_wind_stator - # Define d axis angle for the d,q transform - d_angle = (angle_rotor - angle_offset_initial) * zp - fluxdq = split(n2dqh(Phi_wind, d_angle), 2, axis=1) - return mean(fluxdq[0]) + Phidqh = n2dqh_DataTime(out_fl.mag.Phi_wind_stator) + Phi_d_mean = float(Phidqh.get_along("time=mean", "phase[0]")["Phi_{wind}"]) + return Phi_d_mean diff --git a/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py b/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py index 01aa8d344..d1f9e1aff 100644 --- a/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py +++ b/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py @@ -4,8 +4,8 @@ from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.MagFEMM import MagFEMM from pyleecan.Classes.Simulation import Simulation - -from ....Functions.Electrical.coordinate_transformation import n2dqh +from pyleecan.Classes.Simu1 import Simu1 +from ....Functions.Electrical.coordinate_transformation import n2dqh_DataTime def comp_inductance(self, machine, Id_ref, Iq_ref): @@ -42,11 +42,11 @@ def comp_inductance(self, machine, Id_ref, Iq_ref): path_result = None # Define simulation - simu_ind = Simulation( + simu_ind = Simu1( elec=None, name=simu_name, path_result=path_result, machine=machine_fl ) simu_ind.input = InputCurrent( - N0=2000, Id_ref=0, Iq_ref=0, Nt_tot=self.Nt_tot, Na_tot=2048 + N0=2000, Id_ref=Id_ref, Iq_ref=Iq_ref, Nt_tot=self.Nt_tot, Na_tot=2048 ) simu_ind.mag = MagFEMM( is_periodicity_t=True, @@ -60,11 +60,7 @@ def comp_inductance(self, machine, Id_ref, Iq_ref): out_ind = simu_ind.run() # Post-Process - angle_rotor = out_ind.get_angle_rotor() - angle_offset_initial = out_ind.get_angle_offset_initial() - zp = machine.get_pole_pair_number() - Phi_wind = out_ind.mag.Phi_wind_stator - # Define d axis angle for the d,q transform - d_angle = (angle_rotor - angle_offset_initial) * zp - fluxdq = split(n2dqh(Phi_wind, d_angle), 2, axis=1) - return (mean(fluxdq[0]), mean(fluxdq[1])) + Phidqh = n2dqh_DataTime(out_ind.mag.Phi_wind_stator) + Phi_d_mean = float(Phidqh.get_along("time=mean", "phase[0]")["Phi_{wind}"]) + Phi_q_mean = float(Phidqh.get_along("time=mean", "phase[1]")["Phi_{wind}"]) + return (Phi_d_mean, Phi_q_mean) From c2dcb2ac71929734e1967f694e50e9b707655e37 Mon Sep 17 00:00:00 2001 From: Pierre Bonneel Date: Thu, 14 Oct 2021 11:28:18 +0200 Subject: [PATCH 108/167] [WIP] OP object --- pyleecan/Classes/Class_Dict.json | 291 +++++++++----- pyleecan/Classes/FluxLinkFEMM.py | 17 - pyleecan/Classes/IndMagFEMM.py | 17 - pyleecan/Classes/OP.py | 251 ++++++++++++ pyleecan/Classes/OPdq.py | 321 ++++++++++++++++ pyleecan/Classes/OPslip.py | 351 +++++++++++++++++ pyleecan/Classes/OutElec.py | 357 +++--------------- pyleecan/Classes/import_all.py | 3 + pyleecan/Functions/load_switch.py | 3 + .../Generator/ClassesRef/Output/OutElec.csv | 15 +- .../Generator/ClassesRef/Simulation/OP.csv | 5 + .../Generator/ClassesRef/Simulation/OPdq.csv | 5 + .../ClassesRef/Simulation/OPslip.csv | 6 + pyleecan/Methods/Simulation/OP/__init__.py | 0 .../Simulation/OP/get_machine_from_parent.py | 22 ++ pyleecan/Methods/Simulation/OPdq/__init__.py | 0 pyleecan/Methods/Simulation/OPdq/get_Id_Iq.py | 15 + pyleecan/Methods/Simulation/OPdq/get_N0.py | 31 ++ pyleecan/Methods/Simulation/OPdq/get_Ud_Uq.py | 15 + pyleecan/Methods/Simulation/OPdq/get_felec.py | 31 ++ .../Methods/Simulation/OPslip/__init__.py | 0 .../Methods/Simulation/OPslip/get_Id_Iq.py | 19 + pyleecan/Methods/Simulation/OPslip/get_N0.py | 31 ++ .../Methods/Simulation/OPslip/get_Ud_Uq.py | 19 + .../Methods/Simulation/OPslip/get_felec.py | 31 ++ 25 files changed, 1416 insertions(+), 440 deletions(-) create mode 100644 pyleecan/Classes/OP.py create mode 100644 pyleecan/Classes/OPdq.py create mode 100644 pyleecan/Classes/OPslip.py create mode 100644 pyleecan/Generator/ClassesRef/Simulation/OP.csv create mode 100644 pyleecan/Generator/ClassesRef/Simulation/OPdq.csv create mode 100644 pyleecan/Generator/ClassesRef/Simulation/OPslip.csv create mode 100644 pyleecan/Methods/Simulation/OP/__init__.py create mode 100644 pyleecan/Methods/Simulation/OP/get_machine_from_parent.py create mode 100644 pyleecan/Methods/Simulation/OPdq/__init__.py create mode 100644 pyleecan/Methods/Simulation/OPdq/get_Id_Iq.py create mode 100644 pyleecan/Methods/Simulation/OPdq/get_N0.py create mode 100644 pyleecan/Methods/Simulation/OPdq/get_Ud_Uq.py create mode 100644 pyleecan/Methods/Simulation/OPdq/get_felec.py create mode 100644 pyleecan/Methods/Simulation/OPslip/__init__.py create mode 100644 pyleecan/Methods/Simulation/OPslip/get_Id_Iq.py create mode 100644 pyleecan/Methods/Simulation/OPslip/get_N0.py create mode 100644 pyleecan/Methods/Simulation/OPslip/get_Ud_Uq.py create mode 100644 pyleecan/Methods/Simulation/OPslip/get_felec.py diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 064eeccad..fd0562cf3 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -1614,8 +1614,7 @@ "desc": "Electric module: Flux Linkage with FEMM", "is_internal": false, "methods": [ - "comp_fluxlinkage", - "solve_FEMM" + "comp_fluxlinkage" ], "mother": "FluxLink", "name": "FluxLinkFEMM", @@ -3854,8 +3853,7 @@ "desc": "Electric module: Magnetic Inductance with FEMM", "is_internal": false, "methods": [ - "comp_inductance", - "solve_FEMM" + "comp_inductance" ], "mother": "IndMag", "name": "IndMagFEMM", @@ -7549,6 +7547,192 @@ } ] }, + "OP": { + "constants": [ + { + "name": "VERSION", + "value": "1" + } + ], + "daughters": [ + "OPdq", + "OPslip" + ], + "desc": "Define the Operating Point of the simulation", + "is_internal": false, + "methods": [ + "get_machine_from_parent" + ], + "mother": "", + "name": "OP", + "package": "Output", + "path": "pyleecan/Generator/ClassesRef/Simulation/OP.csv", + "properties": [ + { + "desc": "Rotor speed", + "max": "", + "min": "", + "name": "N0", + "type": "float", + "unit": "rpm", + "value": null + }, + { + "desc": "Electrical Frequency", + "max": "", + "min": "", + "name": "felec", + "type": "float", + "unit": "Hz", + "value": null + }, + { + "desc": "Theorical Average Electromagnetic torque", + "max": "", + "min": "", + "name": "Tem_av_ref", + "type": "float", + "unit": "N.m", + "value": null + }, + { + "desc": "Theorical Average Electromagnetic Power", + "max": "", + "min": "", + "name": "Pem_av_ref", + "type": "float", + "unit": "W", + "value": null + } + ] + }, + "OPdq": { + "constants": [ + { + "name": "VERSION", + "value": "1" + } + ], + "daughters": [], + "desc": "Operating Point defined in DQH frame", + "is_internal": false, + "methods": [ + "get_Id_Iq", + "get_felec", + "get_N0", + "get_Ud_Uq" + ], + "mother": "OP", + "name": "OPdq", + "package": "Output", + "path": "pyleecan/Generator/ClassesRef/Simulation/OPdq.csv", + "properties": [ + { + "desc": "d-axis current rms value", + "max": "", + "min": "", + "name": "Id_ref", + "type": "float", + "unit": "Arms", + "value": null + }, + { + "desc": "q-axis current rms value", + "max": "", + "min": "", + "name": "Iq_ref", + "type": "float", + "unit": "Arms", + "value": null + }, + { + "desc": "d-axis voltage rms value", + "max": "", + "min": "", + "name": "Ud_ref", + "type": "float", + "unit": "Vrms", + "value": null + }, + { + "desc": "q-axis voltage rms value", + "max": "", + "min": "", + "name": "Uq_ref", + "type": "float", + "unit": "Vrms", + "value": null + } + ] + }, + "OPslip": { + "constants": [ + { + "name": "VERSION", + "value": "1" + } + ], + "daughters": [], + "desc": "Operating Point defined with slip, I0", + "is_internal": false, + "methods": [ + "get_Id_Iq", + "get_felec", + "get_N0", + "get_Ud_Uq" + ], + "mother": "OP", + "name": "OPslip", + "package": "Output", + "path": "pyleecan/Generator/ClassesRef/Simulation/OPslip.csv", + "properties": [ + { + "desc": "Current rms value", + "max": "", + "min": "", + "name": "I0_ref", + "type": "float", + "unit": "Arms", + "value": null + }, + { + "desc": "Current phase", + "max": "", + "min": "", + "name": "IPhi0_ref", + "type": "float", + "unit": "rad", + "value": null + }, + { + "desc": "Rotor mechanical slip", + "max": "", + "min": "", + "name": "slip_ref", + "type": "float", + "unit": "", + "value": 0 + }, + { + "desc": "stator voltage (phase to neutral)", + "max": "", + "min": "", + "name": "U0_ref", + "type": "float", + "unit": "Vrms", + "value": null + }, + { + "desc": "Voltage phase", + "max": "", + "min": "", + "name": "UPhi0_ref", + "type": "float", + "unit": "rad", + "value": null + } + ] + }, "OptiConstraint": { "constants": [ { @@ -7982,15 +8166,6 @@ "unit": "rad", "value": null }, - { - "desc": "Rotor speed", - "max": "", - "min": "", - "name": "N0", - "type": "float", - "unit": "rpm", - "value": null - }, { "desc": "Initial angular position of the rotor at t=0", "max": "", @@ -8009,60 +8184,6 @@ "unit": "-", "value": "Pyleecan.Electrical" }, - { - "desc": "Theorical Average Electromagnetic torque", - "max": "", - "min": "", - "name": "Tem_av_ref", - "type": "float", - "unit": "N.m", - "value": null - }, - { - "desc": "d-axis current rms value", - "max": "", - "min": "", - "name": "Id_ref", - "type": "float", - "unit": "Arms", - "value": null - }, - { - "desc": "q-axis current rms value", - "max": "", - "min": "", - "name": "Iq_ref", - "type": "float", - "unit": "Arms", - "value": null - }, - { - "desc": "Electrical Frequency", - "max": "", - "min": "", - "name": "felec", - "type": "float", - "unit": "Hz", - "value": null - }, - { - "desc": "d-axis voltage rms value", - "max": "", - "min": "", - "name": "Ud_ref", - "type": "float", - "unit": "Vrms", - "value": null - }, - { - "desc": "q-axis voltage rms value", - "max": "", - "min": "", - "name": "Uq_ref", - "type": "float", - "unit": "Vrms", - "value": null - }, { "desc": "Electrical Joule losses", "max": "", @@ -8072,15 +8193,6 @@ "unit": "W", "value": null }, - { - "desc": "Theorical Average Electromagnetic Power", - "max": "", - "min": "", - "name": "Pem_av_ref", - "type": "float", - "unit": "W", - "value": null - }, { "desc": "Stator voltage as a function of time (each column correspond to one phase)", "max": "", @@ -8099,24 +8211,6 @@ "unit": "-", "value": null }, - { - "desc": "Rotor mechanical slip", - "max": "", - "min": "", - "name": "slip_ref", - "type": "float", - "unit": "", - "value": 0 - }, - { - "desc": "stator voltage (phase to neutral)", - "max": "", - "min": "", - "name": "U0_ref", - "type": "float", - "unit": "Vrms", - "value": null - }, { "desc": "Harmonic stator voltage as a function of time (each column correspond to one phase)", "max": "", @@ -8125,6 +8219,15 @@ "type": "SciDataTool.Classes.DataND.DataND", "unit": "V", "value": "None" + }, + { + "desc": "Operating Point", + "max": "", + "min": "", + "name": "OP", + "type": "OP", + "unit": "-", + "value": null } ] }, diff --git a/pyleecan/Classes/FluxLinkFEMM.py b/pyleecan/Classes/FluxLinkFEMM.py index b37ee85e4..a59899263 100644 --- a/pyleecan/Classes/FluxLinkFEMM.py +++ b/pyleecan/Classes/FluxLinkFEMM.py @@ -22,11 +22,6 @@ except ImportError as error: comp_fluxlinkage = error -try: - from ..Methods.Simulation.FluxLinkFEMM.solve_FEMM import solve_FEMM -except ImportError as error: - solve_FEMM = error - from ._check import InitUnKnowClassError @@ -36,7 +31,6 @@ class FluxLinkFEMM(FluxLink): VERSION = 1 - # Check ImportError to remove unnecessary dependencies in unused method # cf Methods.Simulation.FluxLinkFEMM.comp_fluxlinkage if isinstance(comp_fluxlinkage, ImportError): comp_fluxlinkage = property( @@ -49,17 +43,6 @@ class FluxLinkFEMM(FluxLink): ) else: comp_fluxlinkage = comp_fluxlinkage - # cf Methods.Simulation.FluxLinkFEMM.solve_FEMM - if isinstance(solve_FEMM, ImportError): - solve_FEMM = property( - fget=lambda x: raise_( - ImportError( - "Can't use FluxLinkFEMM method solve_FEMM: " + str(solve_FEMM) - ) - ) - ) - else: - solve_FEMM = solve_FEMM # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/IndMagFEMM.py b/pyleecan/Classes/IndMagFEMM.py index d4a796144..b2707cc3e 100644 --- a/pyleecan/Classes/IndMagFEMM.py +++ b/pyleecan/Classes/IndMagFEMM.py @@ -22,11 +22,6 @@ except ImportError as error: comp_inductance = error -try: - from ..Methods.Simulation.IndMagFEMM.solve_FEMM import solve_FEMM -except ImportError as error: - solve_FEMM = error - from ._check import InitUnKnowClassError @@ -36,7 +31,6 @@ class IndMagFEMM(IndMag): VERSION = 1 - # Check ImportError to remove unnecessary dependencies in unused method # cf Methods.Simulation.IndMagFEMM.comp_inductance if isinstance(comp_inductance, ImportError): comp_inductance = property( @@ -49,17 +43,6 @@ class IndMagFEMM(IndMag): ) else: comp_inductance = comp_inductance - # cf Methods.Simulation.IndMagFEMM.solve_FEMM - if isinstance(solve_FEMM, ImportError): - solve_FEMM = property( - fget=lambda x: raise_( - ImportError( - "Can't use IndMagFEMM method solve_FEMM: " + str(solve_FEMM) - ) - ) - ) - else: - solve_FEMM = solve_FEMM # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/OP.py b/pyleecan/Classes/OP.py new file mode 100644 index 000000000..a7bd8e236 --- /dev/null +++ b/pyleecan/Classes/OP.py @@ -0,0 +1,251 @@ +# -*- coding: utf-8 -*- +# File generated according to Generator/ClassesRef/Simulation/OP.csv +# WARNING! All changes made in this file will be lost! +"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Output/OP +""" + +from os import linesep +from sys import getsizeof +from logging import getLogger +from ._check import check_var, raise_ +from ..Functions.get_logger import get_logger +from ..Functions.save import save +from ..Functions.copy import copy +from ..Functions.load import load_init_dict +from ..Functions.Load.import_class import import_class +from ._frozen import FrozenClass + +# Import all class method +# Try/catch to remove unnecessary dependencies in unused method +try: + from ..Methods.Output.OP.get_machine_from_parent import get_machine_from_parent +except ImportError as error: + get_machine_from_parent = error + + +from ._check import InitUnKnowClassError + + +class OP(FrozenClass): + """Define the Operating Point of the simulation""" + + VERSION = 1 + + # cf Methods.Output.OP.get_machine_from_parent + if isinstance(get_machine_from_parent, ImportError): + get_machine_from_parent = property( + fget=lambda x: raise_( + ImportError( + "Can't use OP method get_machine_from_parent: " + + str(get_machine_from_parent) + ) + ) + ) + else: + get_machine_from_parent = get_machine_from_parent + # save and copy methods are available in all object + save = save + copy = copy + # get_logger method is available in all object + get_logger = get_logger + + def __init__( + self, + N0=None, + felec=None, + Tem_av_ref=None, + Pem_av_ref=None, + init_dict=None, + init_str=None, + ): + """Constructor of the class. Can be use in three ways : + - __init__ (arg1 = 1, arg3 = 5) every parameters have name and default values + for pyleecan type, -1 will call the default constructor + - __init__ (init_dict = d) d must be a dictionary with property names as keys + - __init__ (init_str = s) s must be a string + s is the file path to load + + ndarray or list can be given for Vector and Matrix + object or dict can be given for pyleecan Object""" + + if init_str is not None: # Load from a file + init_dict = load_init_dict(init_str)[1] + if init_dict is not None: # Initialisation by dict + assert type(init_dict) is dict + # Overwrite default value with init_dict content + if "N0" in list(init_dict.keys()): + N0 = init_dict["N0"] + if "felec" in list(init_dict.keys()): + felec = init_dict["felec"] + if "Tem_av_ref" in list(init_dict.keys()): + Tem_av_ref = init_dict["Tem_av_ref"] + if "Pem_av_ref" in list(init_dict.keys()): + Pem_av_ref = init_dict["Pem_av_ref"] + # Set the properties (value check and convertion are done in setter) + self.parent = None + self.N0 = N0 + self.felec = felec + self.Tem_av_ref = Tem_av_ref + self.Pem_av_ref = Pem_av_ref + + # The class is frozen, for now it's impossible to add new properties + self._freeze() + + def __str__(self): + """Convert this object in a readeable string (for print)""" + + OP_str = "" + if self.parent is None: + OP_str += "parent = None " + linesep + else: + OP_str += "parent = " + str(type(self.parent)) + " object" + linesep + OP_str += "N0 = " + str(self.N0) + linesep + OP_str += "felec = " + str(self.felec) + linesep + OP_str += "Tem_av_ref = " + str(self.Tem_av_ref) + linesep + OP_str += "Pem_av_ref = " + str(self.Pem_av_ref) + linesep + return OP_str + + def __eq__(self, other): + """Compare two objects (skip parent)""" + + if type(other) != type(self): + return False + if other.N0 != self.N0: + return False + if other.felec != self.felec: + return False + if other.Tem_av_ref != self.Tem_av_ref: + return False + if other.Pem_av_ref != self.Pem_av_ref: + return False + return True + + def compare(self, other, name="self", ignore_list=None): + """Compare two objects and return list of differences""" + + if ignore_list is None: + ignore_list = list() + if type(other) != type(self): + return ["type(" + name + ")"] + diff_list = list() + if other._N0 != self._N0: + diff_list.append(name + ".N0") + if other._felec != self._felec: + diff_list.append(name + ".felec") + if other._Tem_av_ref != self._Tem_av_ref: + diff_list.append(name + ".Tem_av_ref") + if other._Pem_av_ref != self._Pem_av_ref: + diff_list.append(name + ".Pem_av_ref") + # Filter ignore differences + diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) + return diff_list + + def __sizeof__(self): + """Return the size in memory of the object (including all subobject)""" + + S = 0 # Full size of the object + S += getsizeof(self.N0) + S += getsizeof(self.felec) + S += getsizeof(self.Tem_av_ref) + S += getsizeof(self.Pem_av_ref) + return S + + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): + """ + Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str + Optional keyword input parameter is for internal use only + and may prevent json serializability. + """ + + OP_dict = dict() + OP_dict["N0"] = self.N0 + OP_dict["felec"] = self.felec + OP_dict["Tem_av_ref"] = self.Tem_av_ref + OP_dict["Pem_av_ref"] = self.Pem_av_ref + # The class name is added to the dict for deserialisation purpose + OP_dict["__class__"] = "OP" + return OP_dict + + def _set_None(self): + """Set all the properties to None (except pyleecan object)""" + + self.N0 = None + self.felec = None + self.Tem_av_ref = None + self.Pem_av_ref = None + + def _get_N0(self): + """getter of N0""" + return self._N0 + + def _set_N0(self, value): + """setter of N0""" + check_var("N0", value, "float") + self._N0 = value + + N0 = property( + fget=_get_N0, + fset=_set_N0, + doc=u"""Rotor speed + + :Type: float + """, + ) + + def _get_felec(self): + """getter of felec""" + return self._felec + + def _set_felec(self, value): + """setter of felec""" + check_var("felec", value, "float") + self._felec = value + + felec = property( + fget=_get_felec, + fset=_set_felec, + doc=u"""Electrical Frequency + + :Type: float + """, + ) + + def _get_Tem_av_ref(self): + """getter of Tem_av_ref""" + return self._Tem_av_ref + + def _set_Tem_av_ref(self, value): + """setter of Tem_av_ref""" + check_var("Tem_av_ref", value, "float") + self._Tem_av_ref = value + + Tem_av_ref = property( + fget=_get_Tem_av_ref, + fset=_set_Tem_av_ref, + doc=u"""Theorical Average Electromagnetic torque + + :Type: float + """, + ) + + def _get_Pem_av_ref(self): + """getter of Pem_av_ref""" + return self._Pem_av_ref + + def _set_Pem_av_ref(self, value): + """setter of Pem_av_ref""" + check_var("Pem_av_ref", value, "float") + self._Pem_av_ref = value + + Pem_av_ref = property( + fget=_get_Pem_av_ref, + fset=_set_Pem_av_ref, + doc=u"""Theorical Average Electromagnetic Power + + :Type: float + """, + ) diff --git a/pyleecan/Classes/OPdq.py b/pyleecan/Classes/OPdq.py new file mode 100644 index 000000000..2c615518d --- /dev/null +++ b/pyleecan/Classes/OPdq.py @@ -0,0 +1,321 @@ +# -*- coding: utf-8 -*- +# File generated according to Generator/ClassesRef/Simulation/OPdq.csv +# WARNING! All changes made in this file will be lost! +"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Output/OPdq +""" + +from os import linesep +from sys import getsizeof +from logging import getLogger +from ._check import check_var, raise_ +from ..Functions.get_logger import get_logger +from ..Functions.save import save +from ..Functions.copy import copy +from ..Functions.load import load_init_dict +from ..Functions.Load.import_class import import_class +from .OP import OP + +# Import all class method +# Try/catch to remove unnecessary dependencies in unused method +try: + from ..Methods.Output.OPdq.get_Id_Iq import get_Id_Iq +except ImportError as error: + get_Id_Iq = error + +try: + from ..Methods.Output.OPdq.get_felec import get_felec +except ImportError as error: + get_felec = error + +try: + from ..Methods.Output.OPdq.get_N0 import get_N0 +except ImportError as error: + get_N0 = error + +try: + from ..Methods.Output.OPdq.get_Ud_Uq import get_Ud_Uq +except ImportError as error: + get_Ud_Uq = error + + +from ._check import InitUnKnowClassError + + +class OPdq(OP): + """Operating Point defined in DQH frame""" + + VERSION = 1 + + # Check ImportError to remove unnecessary dependencies in unused method + # cf Methods.Output.OPdq.get_Id_Iq + if isinstance(get_Id_Iq, ImportError): + get_Id_Iq = property( + fget=lambda x: raise_( + ImportError("Can't use OPdq method get_Id_Iq: " + str(get_Id_Iq)) + ) + ) + else: + get_Id_Iq = get_Id_Iq + # cf Methods.Output.OPdq.get_felec + if isinstance(get_felec, ImportError): + get_felec = property( + fget=lambda x: raise_( + ImportError("Can't use OPdq method get_felec: " + str(get_felec)) + ) + ) + else: + get_felec = get_felec + # cf Methods.Output.OPdq.get_N0 + if isinstance(get_N0, ImportError): + get_N0 = property( + fget=lambda x: raise_( + ImportError("Can't use OPdq method get_N0: " + str(get_N0)) + ) + ) + else: + get_N0 = get_N0 + # cf Methods.Output.OPdq.get_Ud_Uq + if isinstance(get_Ud_Uq, ImportError): + get_Ud_Uq = property( + fget=lambda x: raise_( + ImportError("Can't use OPdq method get_Ud_Uq: " + str(get_Ud_Uq)) + ) + ) + else: + get_Ud_Uq = get_Ud_Uq + # save and copy methods are available in all object + save = save + copy = copy + # get_logger method is available in all object + get_logger = get_logger + + def __init__( + self, + Id_ref=None, + Iq_ref=None, + Ud_ref=None, + Uq_ref=None, + N0=None, + felec=None, + Tem_av_ref=None, + Pem_av_ref=None, + init_dict=None, + init_str=None, + ): + """Constructor of the class. Can be use in three ways : + - __init__ (arg1 = 1, arg3 = 5) every parameters have name and default values + for pyleecan type, -1 will call the default constructor + - __init__ (init_dict = d) d must be a dictionary with property names as keys + - __init__ (init_str = s) s must be a string + s is the file path to load + + ndarray or list can be given for Vector and Matrix + object or dict can be given for pyleecan Object""" + + if init_str is not None: # Load from a file + init_dict = load_init_dict(init_str)[1] + if init_dict is not None: # Initialisation by dict + assert type(init_dict) is dict + # Overwrite default value with init_dict content + if "Id_ref" in list(init_dict.keys()): + Id_ref = init_dict["Id_ref"] + if "Iq_ref" in list(init_dict.keys()): + Iq_ref = init_dict["Iq_ref"] + if "Ud_ref" in list(init_dict.keys()): + Ud_ref = init_dict["Ud_ref"] + if "Uq_ref" in list(init_dict.keys()): + Uq_ref = init_dict["Uq_ref"] + if "N0" in list(init_dict.keys()): + N0 = init_dict["N0"] + if "felec" in list(init_dict.keys()): + felec = init_dict["felec"] + if "Tem_av_ref" in list(init_dict.keys()): + Tem_av_ref = init_dict["Tem_av_ref"] + if "Pem_av_ref" in list(init_dict.keys()): + Pem_av_ref = init_dict["Pem_av_ref"] + # Set the properties (value check and convertion are done in setter) + self.Id_ref = Id_ref + self.Iq_ref = Iq_ref + self.Ud_ref = Ud_ref + self.Uq_ref = Uq_ref + # Call OP init + super(OPdq, self).__init__( + N0=N0, felec=felec, Tem_av_ref=Tem_av_ref, Pem_av_ref=Pem_av_ref + ) + # The class is frozen (in OP init), for now it's impossible to + # add new properties + + def __str__(self): + """Convert this object in a readeable string (for print)""" + + OPdq_str = "" + # Get the properties inherited from OP + OPdq_str += super(OPdq, self).__str__() + OPdq_str += "Id_ref = " + str(self.Id_ref) + linesep + OPdq_str += "Iq_ref = " + str(self.Iq_ref) + linesep + OPdq_str += "Ud_ref = " + str(self.Ud_ref) + linesep + OPdq_str += "Uq_ref = " + str(self.Uq_ref) + linesep + return OPdq_str + + def __eq__(self, other): + """Compare two objects (skip parent)""" + + if type(other) != type(self): + return False + + # Check the properties inherited from OP + if not super(OPdq, self).__eq__(other): + return False + if other.Id_ref != self.Id_ref: + return False + if other.Iq_ref != self.Iq_ref: + return False + if other.Ud_ref != self.Ud_ref: + return False + if other.Uq_ref != self.Uq_ref: + return False + return True + + def compare(self, other, name="self", ignore_list=None): + """Compare two objects and return list of differences""" + + if ignore_list is None: + ignore_list = list() + if type(other) != type(self): + return ["type(" + name + ")"] + diff_list = list() + + # Check the properties inherited from OP + diff_list.extend(super(OPdq, self).compare(other, name=name)) + if other._Id_ref != self._Id_ref: + diff_list.append(name + ".Id_ref") + if other._Iq_ref != self._Iq_ref: + diff_list.append(name + ".Iq_ref") + if other._Ud_ref != self._Ud_ref: + diff_list.append(name + ".Ud_ref") + if other._Uq_ref != self._Uq_ref: + diff_list.append(name + ".Uq_ref") + # Filter ignore differences + diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) + return diff_list + + def __sizeof__(self): + """Return the size in memory of the object (including all subobject)""" + + S = 0 # Full size of the object + + # Get size of the properties inherited from OP + S += super(OPdq, self).__sizeof__() + S += getsizeof(self.Id_ref) + S += getsizeof(self.Iq_ref) + S += getsizeof(self.Ud_ref) + S += getsizeof(self.Uq_ref) + return S + + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): + """ + Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str + Optional keyword input parameter is for internal use only + and may prevent json serializability. + """ + + # Get the properties inherited from OP + OPdq_dict = super(OPdq, self).as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) + OPdq_dict["Id_ref"] = self.Id_ref + OPdq_dict["Iq_ref"] = self.Iq_ref + OPdq_dict["Ud_ref"] = self.Ud_ref + OPdq_dict["Uq_ref"] = self.Uq_ref + # The class name is added to the dict for deserialisation purpose + # Overwrite the mother class name + OPdq_dict["__class__"] = "OPdq" + return OPdq_dict + + def _set_None(self): + """Set all the properties to None (except pyleecan object)""" + + self.Id_ref = None + self.Iq_ref = None + self.Ud_ref = None + self.Uq_ref = None + # Set to None the properties inherited from OP + super(OPdq, self)._set_None() + + def _get_Id_ref(self): + """getter of Id_ref""" + return self._Id_ref + + def _set_Id_ref(self, value): + """setter of Id_ref""" + check_var("Id_ref", value, "float") + self._Id_ref = value + + Id_ref = property( + fget=_get_Id_ref, + fset=_set_Id_ref, + doc=u"""d-axis current rms value + + :Type: float + """, + ) + + def _get_Iq_ref(self): + """getter of Iq_ref""" + return self._Iq_ref + + def _set_Iq_ref(self, value): + """setter of Iq_ref""" + check_var("Iq_ref", value, "float") + self._Iq_ref = value + + Iq_ref = property( + fget=_get_Iq_ref, + fset=_set_Iq_ref, + doc=u"""q-axis current rms value + + :Type: float + """, + ) + + def _get_Ud_ref(self): + """getter of Ud_ref""" + return self._Ud_ref + + def _set_Ud_ref(self, value): + """setter of Ud_ref""" + check_var("Ud_ref", value, "float") + self._Ud_ref = value + + Ud_ref = property( + fget=_get_Ud_ref, + fset=_set_Ud_ref, + doc=u"""d-axis voltage rms value + + :Type: float + """, + ) + + def _get_Uq_ref(self): + """getter of Uq_ref""" + return self._Uq_ref + + def _set_Uq_ref(self, value): + """setter of Uq_ref""" + check_var("Uq_ref", value, "float") + self._Uq_ref = value + + Uq_ref = property( + fget=_get_Uq_ref, + fset=_set_Uq_ref, + doc=u"""q-axis voltage rms value + + :Type: float + """, + ) diff --git a/pyleecan/Classes/OPslip.py b/pyleecan/Classes/OPslip.py new file mode 100644 index 000000000..660696b35 --- /dev/null +++ b/pyleecan/Classes/OPslip.py @@ -0,0 +1,351 @@ +# -*- coding: utf-8 -*- +# File generated according to Generator/ClassesRef/Simulation/OPslip.csv +# WARNING! All changes made in this file will be lost! +"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Output/OPslip +""" + +from os import linesep +from sys import getsizeof +from logging import getLogger +from ._check import check_var, raise_ +from ..Functions.get_logger import get_logger +from ..Functions.save import save +from ..Functions.copy import copy +from ..Functions.load import load_init_dict +from ..Functions.Load.import_class import import_class +from .OP import OP + +# Import all class method +# Try/catch to remove unnecessary dependencies in unused method +try: + from ..Methods.Output.OPslip.get_Id_Iq import get_Id_Iq +except ImportError as error: + get_Id_Iq = error + +try: + from ..Methods.Output.OPslip.get_felec import get_felec +except ImportError as error: + get_felec = error + +try: + from ..Methods.Output.OPslip.get_N0 import get_N0 +except ImportError as error: + get_N0 = error + +try: + from ..Methods.Output.OPslip.get_Ud_Uq import get_Ud_Uq +except ImportError as error: + get_Ud_Uq = error + + +from ._check import InitUnKnowClassError + + +class OPslip(OP): + """Operating Point defined with slip, I0""" + + VERSION = 1 + + # Check ImportError to remove unnecessary dependencies in unused method + # cf Methods.Output.OPslip.get_Id_Iq + if isinstance(get_Id_Iq, ImportError): + get_Id_Iq = property( + fget=lambda x: raise_( + ImportError("Can't use OPslip method get_Id_Iq: " + str(get_Id_Iq)) + ) + ) + else: + get_Id_Iq = get_Id_Iq + # cf Methods.Output.OPslip.get_felec + if isinstance(get_felec, ImportError): + get_felec = property( + fget=lambda x: raise_( + ImportError("Can't use OPslip method get_felec: " + str(get_felec)) + ) + ) + else: + get_felec = get_felec + # cf Methods.Output.OPslip.get_N0 + if isinstance(get_N0, ImportError): + get_N0 = property( + fget=lambda x: raise_( + ImportError("Can't use OPslip method get_N0: " + str(get_N0)) + ) + ) + else: + get_N0 = get_N0 + # cf Methods.Output.OPslip.get_Ud_Uq + if isinstance(get_Ud_Uq, ImportError): + get_Ud_Uq = property( + fget=lambda x: raise_( + ImportError("Can't use OPslip method get_Ud_Uq: " + str(get_Ud_Uq)) + ) + ) + else: + get_Ud_Uq = get_Ud_Uq + # save and copy methods are available in all object + save = save + copy = copy + # get_logger method is available in all object + get_logger = get_logger + + def __init__( + self, + I0_ref=None, + IPhi0_ref=None, + slip_ref=0, + U0_ref=None, + UPhi0_ref=None, + N0=None, + felec=None, + Tem_av_ref=None, + Pem_av_ref=None, + init_dict=None, + init_str=None, + ): + """Constructor of the class. Can be use in three ways : + - __init__ (arg1 = 1, arg3 = 5) every parameters have name and default values + for pyleecan type, -1 will call the default constructor + - __init__ (init_dict = d) d must be a dictionary with property names as keys + - __init__ (init_str = s) s must be a string + s is the file path to load + + ndarray or list can be given for Vector and Matrix + object or dict can be given for pyleecan Object""" + + if init_str is not None: # Load from a file + init_dict = load_init_dict(init_str)[1] + if init_dict is not None: # Initialisation by dict + assert type(init_dict) is dict + # Overwrite default value with init_dict content + if "I0_ref" in list(init_dict.keys()): + I0_ref = init_dict["I0_ref"] + if "IPhi0_ref" in list(init_dict.keys()): + IPhi0_ref = init_dict["IPhi0_ref"] + if "slip_ref" in list(init_dict.keys()): + slip_ref = init_dict["slip_ref"] + if "U0_ref" in list(init_dict.keys()): + U0_ref = init_dict["U0_ref"] + if "UPhi0_ref" in list(init_dict.keys()): + UPhi0_ref = init_dict["UPhi0_ref"] + if "N0" in list(init_dict.keys()): + N0 = init_dict["N0"] + if "felec" in list(init_dict.keys()): + felec = init_dict["felec"] + if "Tem_av_ref" in list(init_dict.keys()): + Tem_av_ref = init_dict["Tem_av_ref"] + if "Pem_av_ref" in list(init_dict.keys()): + Pem_av_ref = init_dict["Pem_av_ref"] + # Set the properties (value check and convertion are done in setter) + self.I0_ref = I0_ref + self.IPhi0_ref = IPhi0_ref + self.slip_ref = slip_ref + self.U0_ref = U0_ref + self.UPhi0_ref = UPhi0_ref + # Call OP init + super(OPslip, self).__init__( + N0=N0, felec=felec, Tem_av_ref=Tem_av_ref, Pem_av_ref=Pem_av_ref + ) + # The class is frozen (in OP init), for now it's impossible to + # add new properties + + def __str__(self): + """Convert this object in a readeable string (for print)""" + + OPslip_str = "" + # Get the properties inherited from OP + OPslip_str += super(OPslip, self).__str__() + OPslip_str += "I0_ref = " + str(self.I0_ref) + linesep + OPslip_str += "IPhi0_ref = " + str(self.IPhi0_ref) + linesep + OPslip_str += "slip_ref = " + str(self.slip_ref) + linesep + OPslip_str += "U0_ref = " + str(self.U0_ref) + linesep + OPslip_str += "UPhi0_ref = " + str(self.UPhi0_ref) + linesep + return OPslip_str + + def __eq__(self, other): + """Compare two objects (skip parent)""" + + if type(other) != type(self): + return False + + # Check the properties inherited from OP + if not super(OPslip, self).__eq__(other): + return False + if other.I0_ref != self.I0_ref: + return False + if other.IPhi0_ref != self.IPhi0_ref: + return False + if other.slip_ref != self.slip_ref: + return False + if other.U0_ref != self.U0_ref: + return False + if other.UPhi0_ref != self.UPhi0_ref: + return False + return True + + def compare(self, other, name="self", ignore_list=None): + """Compare two objects and return list of differences""" + + if ignore_list is None: + ignore_list = list() + if type(other) != type(self): + return ["type(" + name + ")"] + diff_list = list() + + # Check the properties inherited from OP + diff_list.extend(super(OPslip, self).compare(other, name=name)) + if other._I0_ref != self._I0_ref: + diff_list.append(name + ".I0_ref") + if other._IPhi0_ref != self._IPhi0_ref: + diff_list.append(name + ".IPhi0_ref") + if other._slip_ref != self._slip_ref: + diff_list.append(name + ".slip_ref") + if other._U0_ref != self._U0_ref: + diff_list.append(name + ".U0_ref") + if other._UPhi0_ref != self._UPhi0_ref: + diff_list.append(name + ".UPhi0_ref") + # Filter ignore differences + diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) + return diff_list + + def __sizeof__(self): + """Return the size in memory of the object (including all subobject)""" + + S = 0 # Full size of the object + + # Get size of the properties inherited from OP + S += super(OPslip, self).__sizeof__() + S += getsizeof(self.I0_ref) + S += getsizeof(self.IPhi0_ref) + S += getsizeof(self.slip_ref) + S += getsizeof(self.U0_ref) + S += getsizeof(self.UPhi0_ref) + return S + + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): + """ + Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str + Optional keyword input parameter is for internal use only + and may prevent json serializability. + """ + + # Get the properties inherited from OP + OPslip_dict = super(OPslip, self).as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) + OPslip_dict["I0_ref"] = self.I0_ref + OPslip_dict["IPhi0_ref"] = self.IPhi0_ref + OPslip_dict["slip_ref"] = self.slip_ref + OPslip_dict["U0_ref"] = self.U0_ref + OPslip_dict["UPhi0_ref"] = self.UPhi0_ref + # The class name is added to the dict for deserialisation purpose + # Overwrite the mother class name + OPslip_dict["__class__"] = "OPslip" + return OPslip_dict + + def _set_None(self): + """Set all the properties to None (except pyleecan object)""" + + self.I0_ref = None + self.IPhi0_ref = None + self.slip_ref = None + self.U0_ref = None + self.UPhi0_ref = None + # Set to None the properties inherited from OP + super(OPslip, self)._set_None() + + def _get_I0_ref(self): + """getter of I0_ref""" + return self._I0_ref + + def _set_I0_ref(self, value): + """setter of I0_ref""" + check_var("I0_ref", value, "float") + self._I0_ref = value + + I0_ref = property( + fget=_get_I0_ref, + fset=_set_I0_ref, + doc=u"""Current rms value + + :Type: float + """, + ) + + def _get_IPhi0_ref(self): + """getter of IPhi0_ref""" + return self._IPhi0_ref + + def _set_IPhi0_ref(self, value): + """setter of IPhi0_ref""" + check_var("IPhi0_ref", value, "float") + self._IPhi0_ref = value + + IPhi0_ref = property( + fget=_get_IPhi0_ref, + fset=_set_IPhi0_ref, + doc=u"""Current phase + + :Type: float + """, + ) + + def _get_slip_ref(self): + """getter of slip_ref""" + return self._slip_ref + + def _set_slip_ref(self, value): + """setter of slip_ref""" + check_var("slip_ref", value, "float") + self._slip_ref = value + + slip_ref = property( + fget=_get_slip_ref, + fset=_set_slip_ref, + doc=u"""Rotor mechanical slip + + :Type: float + """, + ) + + def _get_U0_ref(self): + """getter of U0_ref""" + return self._U0_ref + + def _set_U0_ref(self, value): + """setter of U0_ref""" + check_var("U0_ref", value, "float") + self._U0_ref = value + + U0_ref = property( + fget=_get_U0_ref, + fset=_set_U0_ref, + doc=u"""stator voltage (phase to neutral) + + :Type: float + """, + ) + + def _get_UPhi0_ref(self): + """getter of UPhi0_ref""" + return self._UPhi0_ref + + def _set_UPhi0_ref(self, value): + """setter of UPhi0_ref""" + check_var("UPhi0_ref", value, "float") + self._UPhi0_ref = value + + UPhi0_ref = property( + fget=_get_UPhi0_ref, + fset=_set_UPhi0_ref, + doc=u"""Voltage phase + + :Type: float + """, + ) diff --git a/pyleecan/Classes/OutElec.py b/pyleecan/Classes/OutElec.py index 409414b8b..4e1b4af9b 100644 --- a/pyleecan/Classes/OutElec.py +++ b/pyleecan/Classes/OutElec.py @@ -51,6 +51,7 @@ from numpy import array, array_equal from ._check import InitUnKnowClassError from .OutInternal import OutInternal +from .OP import OP class OutElec(FrozenClass): @@ -125,22 +126,13 @@ def __init__( Is=None, Ir=None, angle_rotor=None, - N0=None, angle_rotor_initial=0, logger_name="Pyleecan.Electrical", - Tem_av_ref=None, - Id_ref=None, - Iq_ref=None, - felec=None, - Ud_ref=None, - Uq_ref=None, Pj_losses=None, - Pem_av_ref=None, Us=None, internal=None, - slip_ref=0, - U0_ref=None, Us_harm=None, + OP=None, init_dict=None, init_str=None, ): @@ -167,60 +159,33 @@ def __init__( Ir = init_dict["Ir"] if "angle_rotor" in list(init_dict.keys()): angle_rotor = init_dict["angle_rotor"] - if "N0" in list(init_dict.keys()): - N0 = init_dict["N0"] if "angle_rotor_initial" in list(init_dict.keys()): angle_rotor_initial = init_dict["angle_rotor_initial"] if "logger_name" in list(init_dict.keys()): logger_name = init_dict["logger_name"] - if "Tem_av_ref" in list(init_dict.keys()): - Tem_av_ref = init_dict["Tem_av_ref"] - if "Id_ref" in list(init_dict.keys()): - Id_ref = init_dict["Id_ref"] - if "Iq_ref" in list(init_dict.keys()): - Iq_ref = init_dict["Iq_ref"] - if "felec" in list(init_dict.keys()): - felec = init_dict["felec"] - if "Ud_ref" in list(init_dict.keys()): - Ud_ref = init_dict["Ud_ref"] - if "Uq_ref" in list(init_dict.keys()): - Uq_ref = init_dict["Uq_ref"] if "Pj_losses" in list(init_dict.keys()): Pj_losses = init_dict["Pj_losses"] - if "Pem_av_ref" in list(init_dict.keys()): - Pem_av_ref = init_dict["Pem_av_ref"] if "Us" in list(init_dict.keys()): Us = init_dict["Us"] if "internal" in list(init_dict.keys()): internal = init_dict["internal"] - if "slip_ref" in list(init_dict.keys()): - slip_ref = init_dict["slip_ref"] - if "U0_ref" in list(init_dict.keys()): - U0_ref = init_dict["U0_ref"] if "Us_harm" in list(init_dict.keys()): Us_harm = init_dict["Us_harm"] + if "OP" in list(init_dict.keys()): + OP = init_dict["OP"] # Set the properties (value check and convertion are done in setter) self.parent = None self.axes_dict = axes_dict self.Is = Is self.Ir = Ir self.angle_rotor = angle_rotor - self.N0 = N0 self.angle_rotor_initial = angle_rotor_initial self.logger_name = logger_name - self.Tem_av_ref = Tem_av_ref - self.Id_ref = Id_ref - self.Iq_ref = Iq_ref - self.felec = felec - self.Ud_ref = Ud_ref - self.Uq_ref = Uq_ref self.Pj_losses = Pj_losses - self.Pem_av_ref = Pem_av_ref self.Us = Us self.internal = internal - self.slip_ref = slip_ref - self.U0_ref = U0_ref self.Us_harm = Us_harm + self.OP = OP # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -243,28 +208,23 @@ def __str__(self): + linesep + linesep ) - OutElec_str += "N0 = " + str(self.N0) + linesep OutElec_str += ( "angle_rotor_initial = " + str(self.angle_rotor_initial) + linesep ) OutElec_str += 'logger_name = "' + str(self.logger_name) + '"' + linesep - OutElec_str += "Tem_av_ref = " + str(self.Tem_av_ref) + linesep - OutElec_str += "Id_ref = " + str(self.Id_ref) + linesep - OutElec_str += "Iq_ref = " + str(self.Iq_ref) + linesep - OutElec_str += "felec = " + str(self.felec) + linesep - OutElec_str += "Ud_ref = " + str(self.Ud_ref) + linesep - OutElec_str += "Uq_ref = " + str(self.Uq_ref) + linesep OutElec_str += "Pj_losses = " + str(self.Pj_losses) + linesep - OutElec_str += "Pem_av_ref = " + str(self.Pem_av_ref) + linesep OutElec_str += "Us = " + str(self.Us) + linesep + linesep if self.internal is not None: tmp = self.internal.__str__().replace(linesep, linesep + "\t").rstrip("\t") OutElec_str += "internal = " + tmp else: OutElec_str += "internal = None" + linesep + linesep - OutElec_str += "slip_ref = " + str(self.slip_ref) + linesep - OutElec_str += "U0_ref = " + str(self.U0_ref) + linesep OutElec_str += "Us_harm = " + str(self.Us_harm) + linesep + linesep + if self.OP is not None: + tmp = self.OP.__str__().replace(linesep, linesep + "\t").rstrip("\t") + OutElec_str += "OP = " + tmp + else: + OutElec_str += "OP = None" + linesep + linesep return OutElec_str def __eq__(self, other): @@ -280,38 +240,20 @@ def __eq__(self, other): return False if not array_equal(other.angle_rotor, self.angle_rotor): return False - if other.N0 != self.N0: - return False if other.angle_rotor_initial != self.angle_rotor_initial: return False if other.logger_name != self.logger_name: return False - if other.Tem_av_ref != self.Tem_av_ref: - return False - if other.Id_ref != self.Id_ref: - return False - if other.Iq_ref != self.Iq_ref: - return False - if other.felec != self.felec: - return False - if other.Ud_ref != self.Ud_ref: - return False - if other.Uq_ref != self.Uq_ref: - return False if other.Pj_losses != self.Pj_losses: return False - if other.Pem_av_ref != self.Pem_av_ref: - return False if other.Us != self.Us: return False if other.internal != self.internal: return False - if other.slip_ref != self.slip_ref: - return False - if other.U0_ref != self.U0_ref: - return False if other.Us_harm != self.Us_harm: return False + if other.OP != self.OP: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -351,28 +293,12 @@ def compare(self, other, name="self", ignore_list=None): diff_list.extend(self.Ir.compare(other.Ir, name=name + ".Ir")) if not array_equal(other.angle_rotor, self.angle_rotor): diff_list.append(name + ".angle_rotor") - if other._N0 != self._N0: - diff_list.append(name + ".N0") if other._angle_rotor_initial != self._angle_rotor_initial: diff_list.append(name + ".angle_rotor_initial") if other._logger_name != self._logger_name: diff_list.append(name + ".logger_name") - if other._Tem_av_ref != self._Tem_av_ref: - diff_list.append(name + ".Tem_av_ref") - if other._Id_ref != self._Id_ref: - diff_list.append(name + ".Id_ref") - if other._Iq_ref != self._Iq_ref: - diff_list.append(name + ".Iq_ref") - if other._felec != self._felec: - diff_list.append(name + ".felec") - if other._Ud_ref != self._Ud_ref: - diff_list.append(name + ".Ud_ref") - if other._Uq_ref != self._Uq_ref: - diff_list.append(name + ".Uq_ref") if other._Pj_losses != self._Pj_losses: diff_list.append(name + ".Pj_losses") - if other._Pem_av_ref != self._Pem_av_ref: - diff_list.append(name + ".Pem_av_ref") if (other.Us is None and self.Us is not None) or ( other.Us is not None and self.Us is None ): @@ -387,10 +313,6 @@ def compare(self, other, name="self", ignore_list=None): diff_list.extend( self.internal.compare(other.internal, name=name + ".internal") ) - if other._slip_ref != self._slip_ref: - diff_list.append(name + ".slip_ref") - if other._U0_ref != self._U0_ref: - diff_list.append(name + ".U0_ref") if (other.Us_harm is None and self.Us_harm is not None) or ( other.Us_harm is not None and self.Us_harm is None ): @@ -399,6 +321,12 @@ def compare(self, other, name="self", ignore_list=None): diff_list.extend( self.Us_harm.compare(other.Us_harm, name=name + ".Us_harm") ) + if (other.OP is None and self.OP is not None) or ( + other.OP is not None and self.OP is None + ): + diff_list.append(name + ".OP None mismatch") + elif self.OP is not None: + diff_list.extend(self.OP.compare(other.OP, name=name + ".OP")) # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -413,22 +341,13 @@ def __sizeof__(self): S += getsizeof(self.Is) S += getsizeof(self.Ir) S += getsizeof(self.angle_rotor) - S += getsizeof(self.N0) S += getsizeof(self.angle_rotor_initial) S += getsizeof(self.logger_name) - S += getsizeof(self.Tem_av_ref) - S += getsizeof(self.Id_ref) - S += getsizeof(self.Iq_ref) - S += getsizeof(self.felec) - S += getsizeof(self.Ud_ref) - S += getsizeof(self.Uq_ref) S += getsizeof(self.Pj_losses) - S += getsizeof(self.Pem_av_ref) S += getsizeof(self.Us) S += getsizeof(self.internal) - S += getsizeof(self.slip_ref) - S += getsizeof(self.U0_ref) S += getsizeof(self.Us_harm) + S += getsizeof(self.OP) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -485,17 +404,9 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): raise Exception( "Unknown type_handle_ndarray: " + str(type_handle_ndarray) ) - OutElec_dict["N0"] = self.N0 OutElec_dict["angle_rotor_initial"] = self.angle_rotor_initial OutElec_dict["logger_name"] = self.logger_name - OutElec_dict["Tem_av_ref"] = self.Tem_av_ref - OutElec_dict["Id_ref"] = self.Id_ref - OutElec_dict["Iq_ref"] = self.Iq_ref - OutElec_dict["felec"] = self.felec - OutElec_dict["Ud_ref"] = self.Ud_ref - OutElec_dict["Uq_ref"] = self.Uq_ref OutElec_dict["Pj_losses"] = self.Pj_losses - OutElec_dict["Pem_av_ref"] = self.Pem_av_ref if self.Us is None: OutElec_dict["Us"] = None else: @@ -512,8 +423,6 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): keep_function=keep_function, **kwargs ) - OutElec_dict["slip_ref"] = self.slip_ref - OutElec_dict["U0_ref"] = self.U0_ref if self.Us_harm is None: OutElec_dict["Us_harm"] = None else: @@ -522,6 +431,14 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): keep_function=keep_function, **kwargs ) + if self.OP is None: + OutElec_dict["OP"] = None + else: + OutElec_dict["OP"] = self.OP.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) # The class name is added to the dict for deserialisation purpose OutElec_dict["__class__"] = "OutElec" return OutElec_dict @@ -533,23 +450,15 @@ def _set_None(self): self.Is = None self.Ir = None self.angle_rotor = None - self.N0 = None self.angle_rotor_initial = None self.logger_name = None - self.Tem_av_ref = None - self.Id_ref = None - self.Iq_ref = None - self.felec = None - self.Ud_ref = None - self.Uq_ref = None self.Pj_losses = None - self.Pem_av_ref = None self.Us = None if self.internal is not None: self.internal._set_None() - self.slip_ref = None - self.U0_ref = None self.Us_harm = None + if self.OP is not None: + self.OP._set_None() def _get_axes_dict(self): """getter of axes_dict""" @@ -661,24 +570,6 @@ def _set_angle_rotor(self, value): """, ) - def _get_N0(self): - """getter of N0""" - return self._N0 - - def _set_N0(self, value): - """setter of N0""" - check_var("N0", value, "float") - self._N0 = value - - N0 = property( - fget=_get_N0, - fset=_set_N0, - doc=u"""Rotor speed - - :Type: float - """, - ) - def _get_angle_rotor_initial(self): """getter of angle_rotor_initial""" return self._angle_rotor_initial @@ -715,114 +606,6 @@ def _set_logger_name(self, value): """, ) - def _get_Tem_av_ref(self): - """getter of Tem_av_ref""" - return self._Tem_av_ref - - def _set_Tem_av_ref(self, value): - """setter of Tem_av_ref""" - check_var("Tem_av_ref", value, "float") - self._Tem_av_ref = value - - Tem_av_ref = property( - fget=_get_Tem_av_ref, - fset=_set_Tem_av_ref, - doc=u"""Theorical Average Electromagnetic torque - - :Type: float - """, - ) - - def _get_Id_ref(self): - """getter of Id_ref""" - return self._Id_ref - - def _set_Id_ref(self, value): - """setter of Id_ref""" - check_var("Id_ref", value, "float") - self._Id_ref = value - - Id_ref = property( - fget=_get_Id_ref, - fset=_set_Id_ref, - doc=u"""d-axis current rms value - - :Type: float - """, - ) - - def _get_Iq_ref(self): - """getter of Iq_ref""" - return self._Iq_ref - - def _set_Iq_ref(self, value): - """setter of Iq_ref""" - check_var("Iq_ref", value, "float") - self._Iq_ref = value - - Iq_ref = property( - fget=_get_Iq_ref, - fset=_set_Iq_ref, - doc=u"""q-axis current rms value - - :Type: float - """, - ) - - def _get_felec(self): - """getter of felec""" - return self._felec - - def _set_felec(self, value): - """setter of felec""" - check_var("felec", value, "float") - self._felec = value - - felec = property( - fget=_get_felec, - fset=_set_felec, - doc=u"""Electrical Frequency - - :Type: float - """, - ) - - def _get_Ud_ref(self): - """getter of Ud_ref""" - return self._Ud_ref - - def _set_Ud_ref(self, value): - """setter of Ud_ref""" - check_var("Ud_ref", value, "float") - self._Ud_ref = value - - Ud_ref = property( - fget=_get_Ud_ref, - fset=_set_Ud_ref, - doc=u"""d-axis voltage rms value - - :Type: float - """, - ) - - def _get_Uq_ref(self): - """getter of Uq_ref""" - return self._Uq_ref - - def _set_Uq_ref(self, value): - """setter of Uq_ref""" - check_var("Uq_ref", value, "float") - self._Uq_ref = value - - Uq_ref = property( - fget=_get_Uq_ref, - fset=_set_Uq_ref, - doc=u"""q-axis voltage rms value - - :Type: float - """, - ) - def _get_Pj_losses(self): """getter of Pj_losses""" return self._Pj_losses @@ -841,24 +624,6 @@ def _set_Pj_losses(self, value): """, ) - def _get_Pem_av_ref(self): - """getter of Pem_av_ref""" - return self._Pem_av_ref - - def _set_Pem_av_ref(self, value): - """setter of Pem_av_ref""" - check_var("Pem_av_ref", value, "float") - self._Pem_av_ref = value - - Pem_av_ref = property( - fget=_get_Pem_av_ref, - fset=_set_Pem_av_ref, - doc=u"""Theorical Average Electromagnetic Power - - :Type: float - """, - ) - def _get_Us(self): """getter of Us""" return self._Us @@ -916,42 +681,6 @@ def _set_internal(self, value): """, ) - def _get_slip_ref(self): - """getter of slip_ref""" - return self._slip_ref - - def _set_slip_ref(self, value): - """setter of slip_ref""" - check_var("slip_ref", value, "float") - self._slip_ref = value - - slip_ref = property( - fget=_get_slip_ref, - fset=_set_slip_ref, - doc=u"""Rotor mechanical slip - - :Type: float - """, - ) - - def _get_U0_ref(self): - """getter of U0_ref""" - return self._U0_ref - - def _set_U0_ref(self, value): - """setter of U0_ref""" - check_var("U0_ref", value, "float") - self._U0_ref = value - - U0_ref = property( - fget=_get_U0_ref, - fset=_set_U0_ref, - doc=u"""stator voltage (phase to neutral) - - :Type: float - """, - ) - def _get_Us_harm(self): """getter of Us_harm""" return self._Us_harm @@ -978,3 +707,31 @@ def _set_Us_harm(self, value): :Type: SciDataTool.Classes.DataND.DataND """, ) + + def _get_OP(self): + """getter of OP""" + return self._OP + + def _set_OP(self, value): + """setter of OP""" + if isinstance(value, str): # Load from file + value = load_init_dict(value)[1] + if isinstance(value, dict) and "__class__" in value: + class_obj = import_class("pyleecan.Classes", value.get("__class__"), "OP") + value = class_obj(init_dict=value) + elif type(value) is int and value == -1: # Default constructor + value = OP() + check_var("OP", value, "OP") + self._OP = value + + if self._OP is not None: + self._OP.parent = self + + OP = property( + fget=_get_OP, + fset=_set_OP, + doc=u"""Operating Point + + :Type: OP + """, + ) diff --git a/pyleecan/Classes/import_all.py b/pyleecan/Classes/import_all.py index 93078bf54..15acb4478 100644 --- a/pyleecan/Classes/import_all.py +++ b/pyleecan/Classes/import_all.py @@ -129,6 +129,9 @@ from ..Classes.NodeMat import NodeMat from ..Classes.Notch import Notch from ..Classes.NotchEvenDist import NotchEvenDist +from ..Classes.OP import OP +from ..Classes.OPdq import OPdq +from ..Classes.OPslip import OPslip from ..Classes.OptiConstraint import OptiConstraint from ..Classes.OptiDesignVar import OptiDesignVar from ..Classes.OptiGenAlg import OptiGenAlg diff --git a/pyleecan/Functions/load_switch.py b/pyleecan/Functions/load_switch.py index 1179ff4cd..47b97d88e 100644 --- a/pyleecan/Functions/load_switch.py +++ b/pyleecan/Functions/load_switch.py @@ -131,6 +131,9 @@ "NodeMat": NodeMat, "Notch": Notch, "NotchEvenDist": NotchEvenDist, + "OP": OP, + "OPdq": OPdq, + "OPslip": OPslip, "OptiConstraint": OptiConstraint, "OptiDesignVar": OptiDesignVar, "OptiGenAlg": OptiGenAlg, diff --git a/pyleecan/Generator/ClassesRef/Output/OutElec.csv b/pyleecan/Generator/ClassesRef/Output/OutElec.csv index 14f5c5430..608fb2aae 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutElec.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutElec.csv @@ -3,19 +3,10 @@ axes_dict,,Dict containing axes data used for Electrical,,{SciDataTool.Classes.D Is,A,Stator currents DataTime object,"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_I_harm,,, Ir,A,Rotor currents as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Is,,, angle_rotor,rad,Rotor angular position as a function of time (if None computed according to Nr),Nt_tot,ndarray,None,,,,,,get_Nr,,, -N0,rpm,Rotor speed,1,float,None,,,,,,get_Us,,, -angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,store,,, -logger_name,-,Name of the logger to use,0,str,Pyleecan.Electrical,,,,,,,,, -Tem_av_ref,N.m,Theorical Average Electromagnetic torque,,float,None,,,,,,,,, -Id_ref,Arms,d-axis current rms value,1,float,None,,,,,,,,, -Iq_ref,Arms,q-axis current rms value,1,float,None,,,,,,,,, -felec,Hz,Electrical Frequency,1,float,None,,,,,,,,, -Ud_ref,Vrms,d-axis voltage rms value,1,float,None,,,,,,,,, -Uq_ref,Vrms,q-axis voltage rms value,1,float,None,,,,,,,,, +angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,get_Us,,, +logger_name,-,Name of the logger to use,0,str,Pyleecan.Electrical,,,,,,store,,, Pj_losses,W,Electrical Joule losses,,float,None,,,,,,,,, -Pem_av_ref,W,Theorical Average Electromagnetic Power,,float,None,,,,,,,,, Us,V,Stator voltage as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,,,, internal,-,OutInternal object containg outputs related to a specific model,0,OutInternal,None,,,,,,,,, -slip_ref,,Rotor mechanical slip,,float,0,,,,,,,,, -U0_ref,Vrms,stator voltage (phase to neutral),,float,None,,,,,,,,, Us_harm,V,Harmonic stator voltage as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,,,, +OP,-,Operating Point,,OP,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/OP.csv b/pyleecan/Generator/ClassesRef/Simulation/OP.csv new file mode 100644 index 000000000..f18c0073e --- /dev/null +++ b/pyleecan/Generator/ClassesRef/Simulation/OP.csv @@ -0,0 +1,5 @@ +Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description +N0,rpm,Rotor speed,1,float,None,,,,Output,,get_machine_from_parent,VERSION,1,Define the Operating Point of the simulation +felec,Hz,Electrical Frequency,1,float,None,,,,,,,,, +Tem_av_ref,N.m,Theorical Average Electromagnetic torque,,float,None,,,,,,,,, +Pem_av_ref,W,Theorical Average Electromagnetic Power,,float,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv b/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv new file mode 100644 index 000000000..55970c6da --- /dev/null +++ b/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv @@ -0,0 +1,5 @@ +Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description +Id_ref,Arms,d-axis current rms value,1,float,None,,,,Output,OP,get_Id_Iq,VERSION,1,Operating Point defined in DQH frame +Iq_ref,Arms,q-axis current rms value,1,float,None,,,,,,get_felec,,, +Ud_ref,Vrms,d-axis voltage rms value,1,float,None,,,,,,get_N0,,, +Uq_ref,Vrms,q-axis voltage rms value,1,float,None,,,,,,get_Ud_Uq,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv b/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv new file mode 100644 index 000000000..488a7f101 --- /dev/null +++ b/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv @@ -0,0 +1,6 @@ +Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description +I0_ref,Arms,Current rms value,1,float,None,,,,Output,OP,get_Id_Iq,VERSION,1,"Operating Point defined with slip, I0" +IPhi0_ref,rad,Current phase,1,float,None,,,,,,get_felec,,, +slip_ref,,Rotor mechanical slip,,float,0,,,,,,get_N0,,, +U0_ref,Vrms,stator voltage (phase to neutral),,float,None,,,,,,get_Ud_Uq,,, +UPhi0_ref,rad,Voltage phase,,float,None,,,,,,,,, diff --git a/pyleecan/Methods/Simulation/OP/__init__.py b/pyleecan/Methods/Simulation/OP/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pyleecan/Methods/Simulation/OP/get_machine_from_parent.py b/pyleecan/Methods/Simulation/OP/get_machine_from_parent.py new file mode 100644 index 000000000..1add96125 --- /dev/null +++ b/pyleecan/Methods/Simulation/OP/get_machine_from_parent.py @@ -0,0 +1,22 @@ +def get_machine_from_parent(self): + """Search in the parent to find the machine + + Parameters + ---------- + self : OP + An OP object + + Returns + ------- + machine : Machine + Machine from the parent (or None) + """ + + parent = self.parent + while parent is not None and not hasattr(parent, "machine"): + parent = parent.parent + + if parent is not None: + return parent.machine + else: + return None diff --git a/pyleecan/Methods/Simulation/OPdq/__init__.py b/pyleecan/Methods/Simulation/OPdq/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pyleecan/Methods/Simulation/OPdq/get_Id_Iq.py b/pyleecan/Methods/Simulation/OPdq/get_Id_Iq.py new file mode 100644 index 000000000..09538223f --- /dev/null +++ b/pyleecan/Methods/Simulation/OPdq/get_Id_Iq.py @@ -0,0 +1,15 @@ +def get_Id_Iq(self): + """Return Id and Iq + + Parameters + ---------- + self : OPdq + An OPdq object + + Returns + ------- + I_dict : dict + Dict with key "Id", "Iq" + """ + + return {"Id": self.Id_ref, "Iq": self.Iq_ref} diff --git a/pyleecan/Methods/Simulation/OPdq/get_N0.py b/pyleecan/Methods/Simulation/OPdq/get_N0.py new file mode 100644 index 000000000..e52fe4c0e --- /dev/null +++ b/pyleecan/Methods/Simulation/OPdq/get_N0.py @@ -0,0 +1,31 @@ +from ....Methods.Simulation.Input import InputError + + +def get_felec(self): + """Returns the Rotor speed + + Parameters + ---------- + self : OPdq + An OPdq object + + Returns + ------- + N0 : float + Rotor speed [rpm] + """ + + if self.N0 is not None: + return self.N0 + + # Compute N0 if possible + if self.felec is None: + raise InputError("OPdq object can't have felec and N0 both None") + + machine = self.get_machine_from_parent() + if machine is None: + raise InputError("OPdq object can't find machine parent to compute N0") + + p = machine.get_pole_pair_number() + self.N0 = self.felec * 60 / p + return self.N0 diff --git a/pyleecan/Methods/Simulation/OPdq/get_Ud_Uq.py b/pyleecan/Methods/Simulation/OPdq/get_Ud_Uq.py new file mode 100644 index 000000000..850c77ef8 --- /dev/null +++ b/pyleecan/Methods/Simulation/OPdq/get_Ud_Uq.py @@ -0,0 +1,15 @@ +def get_Ud_Uq(self): + """Return Ud and Uq + + Parameters + ---------- + self : OPdq + An OPdq object + + Returns + ------- + U_dict : dict + Dict with key "Ud", "Uq" + """ + + return {"Ud": self.Ud_ref, "Uq": self.Uq_ref} diff --git a/pyleecan/Methods/Simulation/OPdq/get_felec.py b/pyleecan/Methods/Simulation/OPdq/get_felec.py new file mode 100644 index 000000000..12fd346d5 --- /dev/null +++ b/pyleecan/Methods/Simulation/OPdq/get_felec.py @@ -0,0 +1,31 @@ +from ....Methods.Simulation.Input import InputError + + +def get_felec(self): + """Returns the electrical frequency + + Parameters + ---------- + self : OPdq + An OPdq object + + Returns + ------- + felec : float + Electrical Frequency [Hz] + """ + + if self.felec is not None: + return self.felec + + # Compute felec if possible + if self.N0 is None: + raise InputError("OPdq object can't have felec and N0 both None") + + machine = self.get_machine_from_parent() + if machine is None: + raise InputError("OPdq object can't find machine parent to compute felec") + + p = machine.get_pole_pair_number() + self.felec = self.N0 * p / 60 + return self.felec diff --git a/pyleecan/Methods/Simulation/OPslip/__init__.py b/pyleecan/Methods/Simulation/OPslip/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pyleecan/Methods/Simulation/OPslip/get_Id_Iq.py b/pyleecan/Methods/Simulation/OPslip/get_Id_Iq.py new file mode 100644 index 000000000..bcbff207d --- /dev/null +++ b/pyleecan/Methods/Simulation/OPslip/get_Id_Iq.py @@ -0,0 +1,19 @@ +from numpy import exp + + +def get_Id_Iq(self): + """Return Id and Iq + + Parameters + ---------- + self : OPslip + An OPslip object + + Returns + ------- + I_dict : dict + Dict with key "Id", "Iq" + """ + + Z = self.I0 * exp(1j * self.IPhi0) + return {"Id": Z.real, "Iq": Z.imag} diff --git a/pyleecan/Methods/Simulation/OPslip/get_N0.py b/pyleecan/Methods/Simulation/OPslip/get_N0.py new file mode 100644 index 000000000..32a3f3273 --- /dev/null +++ b/pyleecan/Methods/Simulation/OPslip/get_N0.py @@ -0,0 +1,31 @@ +from ....Methods.Simulation.Input import InputError + + +def get_felec(self): + """Returns the Rotor speed + + Parameters + ---------- + self : OPslip + An OPslip object + + Returns + ------- + N0 : float + Rotor speed [rpm] + """ + + if self.N0 is not None: + return self.N0 + + # Compute N0 if possible + if self.felec is None: + raise InputError("OPslip object can't have felec and N0 both None") + + machine = self.get_machine_from_parent() + if machine is None: + raise InputError("OPslip object can't find machine parent to compute N0") + + p = machine.get_pole_pair_number() + self.N0 = 60 * (1 - self.slip_ref) * self.felec / p + return self.N0 diff --git a/pyleecan/Methods/Simulation/OPslip/get_Ud_Uq.py b/pyleecan/Methods/Simulation/OPslip/get_Ud_Uq.py new file mode 100644 index 000000000..ab68a04f1 --- /dev/null +++ b/pyleecan/Methods/Simulation/OPslip/get_Ud_Uq.py @@ -0,0 +1,19 @@ +from numpy import exp + + +def get_Ud_Uq(self): + """Return Ud and Uq + + Parameters + ---------- + self : OPslip + An OPslip object + + Returns + ------- + U_dict : dict + Dict with key "Ud", "Uq" + """ + + Z = self.U0 * exp(1j * self.UPhi0) + return {"Ud": Z.real, "Uq": Z.imag} diff --git a/pyleecan/Methods/Simulation/OPslip/get_felec.py b/pyleecan/Methods/Simulation/OPslip/get_felec.py new file mode 100644 index 000000000..e797947e4 --- /dev/null +++ b/pyleecan/Methods/Simulation/OPslip/get_felec.py @@ -0,0 +1,31 @@ +from ....Methods.Simulation.Input import InputError + + +def get_felec(self): + """Returns the electrical frequency + + Parameters + ---------- + self : OPslip + An OPslip object + + Returns + ------- + felec : float + Electrical Frequency [Hz] + """ + + if self.felec is not None: + return self.felec + + # Compute felec if possible + if self.N0 is None: + raise InputError("OPslip object can't have felec and N0 both None") + + machine = self.get_machine_from_parent() + if machine is None: + raise InputError("OPslip object can't find machine parent to compute felec") + + p = machine.get_pole_pair_number() + self.felec = self.N0 * p / (60 * (1 - self.slip_ref)) + return self.felec From 3ea330140e9715b92b0d8abddf1d21ad4b9d1964 Mon Sep 17 00:00:00 2001 From: Pierre Bonneel Date: Thu, 14 Oct 2021 12:04:13 +0200 Subject: [PATCH 109/167] [WIP] OP in gen_input --- pyleecan/Classes/Class_Dict.json | 18 +++++-- pyleecan/Classes/InputCurrent.py | 4 ++ pyleecan/Classes/InputFlux.py | 4 ++ pyleecan/Classes/InputVoltage.py | 46 ++++++++++------ pyleecan/Classes/OPdq.py | 14 +++++ pyleecan/Classes/OPslip.py | 14 +++++ .../ClassesRef/Simulation/InputVoltage.csv | 3 +- .../Generator/ClassesRef/Simulation/OPdq.csv | 1 + .../ClassesRef/Simulation/OPslip.csv | 2 +- .../Simulation/InputCurrent/gen_input.py | 6 +-- .../Simulation/InputVoltage/comp_felec.py | 42 --------------- .../Simulation/InputVoltage/gen_input.py | 52 ++++++++++--------- pyleecan/Methods/Simulation/OPdq/set_Id_Iq.py | 15 ++++++ .../Methods/Simulation/OPslip/set_Id_Iq.py | 23 ++++++++ 14 files changed, 151 insertions(+), 93 deletions(-) delete mode 100644 pyleecan/Methods/Simulation/InputVoltage/comp_felec.py create mode 100644 pyleecan/Methods/Simulation/OPdq/set_Id_Iq.py create mode 100644 pyleecan/Methods/Simulation/OPslip/set_Id_Iq.py diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index fd0562cf3..60ff72da2 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4194,8 +4194,7 @@ "is_internal": false, "methods": [ "gen_input", - "set_OP_from_array", - "comp_felec" + "set_OP_from_array" ], "mother": "Input", "name": "InputVoltage", @@ -4283,6 +4282,15 @@ "unit": "Vrms", "value": null }, + { + "desc": "stator voltage phase", + "max": "", + "min": "", + "name": "Phi0_ref", + "type": "float", + "unit": "rad", + "value": null + }, { "desc": "Theorical Average Electromagnetic Power", "max": "", @@ -7620,7 +7628,8 @@ "get_Id_Iq", "get_felec", "get_N0", - "get_Ud_Uq" + "get_Ud_Uq", + "set_Id_Iq" ], "mother": "OP", "name": "OPdq", @@ -7679,7 +7688,8 @@ "get_Id_Iq", "get_felec", "get_N0", - "get_Ud_Uq" + "get_Ud_Uq", + "set_Id_Iq" ], "mother": "OP", "name": "OPslip", diff --git a/pyleecan/Classes/InputCurrent.py b/pyleecan/Classes/InputCurrent.py index b2a8e1cd2..2208af700 100644 --- a/pyleecan/Classes/InputCurrent.py +++ b/pyleecan/Classes/InputCurrent.py @@ -103,6 +103,7 @@ def __init__( felec=None, slip_ref=0, U0_ref=None, + Phi0_ref=None, Pem_av_ref=None, PWM=None, time=None, @@ -155,6 +156,8 @@ def __init__( slip_ref = init_dict["slip_ref"] if "U0_ref" in list(init_dict.keys()): U0_ref = init_dict["U0_ref"] + if "Phi0_ref" in list(init_dict.keys()): + Phi0_ref = init_dict["Phi0_ref"] if "Pem_av_ref" in list(init_dict.keys()): Pem_av_ref = init_dict["Pem_av_ref"] if "PWM" in list(init_dict.keys()): @@ -187,6 +190,7 @@ def __init__( felec=felec, slip_ref=slip_ref, U0_ref=U0_ref, + Phi0_ref=Phi0_ref, Pem_av_ref=Pem_av_ref, PWM=PWM, time=time, diff --git a/pyleecan/Classes/InputFlux.py b/pyleecan/Classes/InputFlux.py index 5e58f2abb..29987a199 100644 --- a/pyleecan/Classes/InputFlux.py +++ b/pyleecan/Classes/InputFlux.py @@ -75,6 +75,7 @@ def __init__( felec=None, slip_ref=0, U0_ref=None, + Phi0_ref=None, Pem_av_ref=None, PWM=None, time=None, @@ -143,6 +144,8 @@ def __init__( slip_ref = init_dict["slip_ref"] if "U0_ref" in list(init_dict.keys()): U0_ref = init_dict["U0_ref"] + if "Phi0_ref" in list(init_dict.keys()): + Phi0_ref = init_dict["Phi0_ref"] if "Pem_av_ref" in list(init_dict.keys()): Pem_av_ref = init_dict["Pem_av_ref"] if "PWM" in list(init_dict.keys()): @@ -183,6 +186,7 @@ def __init__( felec=felec, slip_ref=slip_ref, U0_ref=U0_ref, + Phi0_ref=Phi0_ref, Pem_av_ref=Pem_av_ref, PWM=PWM, time=time, diff --git a/pyleecan/Classes/InputVoltage.py b/pyleecan/Classes/InputVoltage.py index 4cdfbaf50..9f210a925 100644 --- a/pyleecan/Classes/InputVoltage.py +++ b/pyleecan/Classes/InputVoltage.py @@ -27,11 +27,6 @@ except ImportError as error: set_OP_from_array = error -try: - from ..Methods.Simulation.InputVoltage.comp_felec import comp_felec -except ImportError as error: - comp_felec = error - from ..Classes.ImportMatrixVal import ImportMatrixVal from numpy import ndarray @@ -71,17 +66,6 @@ class InputVoltage(Input): ) else: set_OP_from_array = set_OP_from_array - # cf Methods.Simulation.InputVoltage.comp_felec - if isinstance(comp_felec, ImportError): - comp_felec = property( - fget=lambda x: raise_( - ImportError( - "Can't use InputVoltage method comp_felec: " + str(comp_felec) - ) - ) - ) - else: - comp_felec = comp_felec # save and copy methods are available in all object save = save copy = copy @@ -99,6 +83,7 @@ def __init__( felec=None, slip_ref=0, U0_ref=None, + Phi0_ref=None, Pem_av_ref=None, PWM=None, time=None, @@ -143,6 +128,8 @@ def __init__( slip_ref = init_dict["slip_ref"] if "U0_ref" in list(init_dict.keys()): U0_ref = init_dict["U0_ref"] + if "Phi0_ref" in list(init_dict.keys()): + Phi0_ref = init_dict["Phi0_ref"] if "Pem_av_ref" in list(init_dict.keys()): Pem_av_ref = init_dict["Pem_av_ref"] if "PWM" in list(init_dict.keys()): @@ -169,6 +156,7 @@ def __init__( self.felec = felec self.slip_ref = slip_ref self.U0_ref = U0_ref + self.Phi0_ref = Phi0_ref self.Pem_av_ref = Pem_av_ref self.PWM = PWM # Call Input init @@ -201,6 +189,7 @@ def __str__(self): InputVoltage_str += "felec = " + str(self.felec) + linesep InputVoltage_str += "slip_ref = " + str(self.slip_ref) + linesep InputVoltage_str += "U0_ref = " + str(self.U0_ref) + linesep + InputVoltage_str += "Phi0_ref = " + str(self.Phi0_ref) + linesep InputVoltage_str += "Pem_av_ref = " + str(self.Pem_av_ref) + linesep if self.PWM is not None: tmp = self.PWM.__str__().replace(linesep, linesep + "\t").rstrip("\t") @@ -236,6 +225,8 @@ def __eq__(self, other): return False if other.U0_ref != self.U0_ref: return False + if other.Phi0_ref != self.Phi0_ref: + return False if other.Pem_av_ref != self.Pem_av_ref: return False if other.PWM != self.PWM: @@ -277,6 +268,8 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".slip_ref") if other._U0_ref != self._U0_ref: diff_list.append(name + ".U0_ref") + if other._Phi0_ref != self._Phi0_ref: + diff_list.append(name + ".Phi0_ref") if other._Pem_av_ref != self._Pem_av_ref: diff_list.append(name + ".Pem_av_ref") if (other.PWM is None and self.PWM is not None) or ( @@ -305,6 +298,7 @@ def __sizeof__(self): S += getsizeof(self.felec) S += getsizeof(self.slip_ref) S += getsizeof(self.U0_ref) + S += getsizeof(self.Phi0_ref) S += getsizeof(self.Pem_av_ref) S += getsizeof(self.PWM) return S @@ -342,6 +336,7 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): InputVoltage_dict["felec"] = self.felec InputVoltage_dict["slip_ref"] = self.slip_ref InputVoltage_dict["U0_ref"] = self.U0_ref + InputVoltage_dict["Phi0_ref"] = self.Phi0_ref InputVoltage_dict["Pem_av_ref"] = self.Pem_av_ref if self.PWM is None: InputVoltage_dict["PWM"] = None @@ -369,6 +364,7 @@ def _set_None(self): self.felec = None self.slip_ref = None self.U0_ref = None + self.Phi0_ref = None self.Pem_av_ref = None if self.PWM is not None: self.PWM._set_None() @@ -551,6 +547,24 @@ def _set_U0_ref(self, value): """, ) + def _get_Phi0_ref(self): + """getter of Phi0_ref""" + return self._Phi0_ref + + def _set_Phi0_ref(self, value): + """setter of Phi0_ref""" + check_var("Phi0_ref", value, "float") + self._Phi0_ref = value + + Phi0_ref = property( + fget=_get_Phi0_ref, + fset=_set_Phi0_ref, + doc=u"""stator voltage phase + + :Type: float + """, + ) + def _get_Pem_av_ref(self): """getter of Pem_av_ref""" return self._Pem_av_ref diff --git a/pyleecan/Classes/OPdq.py b/pyleecan/Classes/OPdq.py index 2c615518d..d8717e5fa 100644 --- a/pyleecan/Classes/OPdq.py +++ b/pyleecan/Classes/OPdq.py @@ -37,6 +37,11 @@ except ImportError as error: get_Ud_Uq = error +try: + from ..Methods.Output.OPdq.set_Id_Iq import set_Id_Iq +except ImportError as error: + set_Id_Iq = error + from ._check import InitUnKnowClassError @@ -83,6 +88,15 @@ class OPdq(OP): ) else: get_Ud_Uq = get_Ud_Uq + # cf Methods.Output.OPdq.set_Id_Iq + if isinstance(set_Id_Iq, ImportError): + set_Id_Iq = property( + fget=lambda x: raise_( + ImportError("Can't use OPdq method set_Id_Iq: " + str(set_Id_Iq)) + ) + ) + else: + set_Id_Iq = set_Id_Iq # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/OPslip.py b/pyleecan/Classes/OPslip.py index 660696b35..eb5a7cbbd 100644 --- a/pyleecan/Classes/OPslip.py +++ b/pyleecan/Classes/OPslip.py @@ -37,6 +37,11 @@ except ImportError as error: get_Ud_Uq = error +try: + from ..Methods.Output.OPslip.set_Id_Iq import set_Id_Iq +except ImportError as error: + set_Id_Iq = error + from ._check import InitUnKnowClassError @@ -83,6 +88,15 @@ class OPslip(OP): ) else: get_Ud_Uq = get_Ud_Uq + # cf Methods.Output.OPslip.set_Id_Iq + if isinstance(set_Id_Iq, ImportError): + set_Id_Iq = property( + fget=lambda x: raise_( + ImportError("Can't use OPslip method set_Id_Iq: " + str(set_Id_Iq)) + ) + ) + else: + set_Id_Iq = set_Id_Iq # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv index 8f8e79811..08542797e 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv @@ -1,12 +1,13 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description angle_rotor,rad,Rotor angular position as a function of time (if None computed according to Nr) to import,Nt_tot,Import,None,,,,Simulation,Input,gen_input,VERSION,1,Input to start the electrical module with voltage input rot_dir,-,"Rotation direction of the rotor 1 trigo, -1 clockwise",,float,None,-1,1,,,,set_OP_from_array,,, -angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,comp_felec,,, +angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,,,, Tem_av_ref,N.m,Theorical Average Electromagnetic torque,,float,None,,,,,,,,, Ud_ref,Vrms,d-axis current RMS magnitude (phase to neutral),1,float,None,,,,,,,,, Uq_ref,Vrms,q-axis current RMS magnitude (phase to neutral),1,float,None,,,,,,,,, felec,Hz,electrical frequency,1,float,None,,,,,,,,, slip_ref,,Rotor mechanical slip,,float,0,,,,,,,,, U0_ref,Vrms,stator voltage (phase to neutral),,float,None,,,,,,,,, +Phi0_ref,rad,stator voltage phase,,float,None,,,,,,,,, Pem_av_ref,W,Theorical Average Electromagnetic Power,1,float,None,,,,,,,,, PWM,,Object to generate PWM signal,,ImportGenPWM,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv b/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv index 55970c6da..183ef6eb7 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv @@ -3,3 +3,4 @@ Id_ref,Arms,d-axis current rms value,1,float,None,,,,Output,OP,get_Id_Iq,VERSION Iq_ref,Arms,q-axis current rms value,1,float,None,,,,,,get_felec,,, Ud_ref,Vrms,d-axis voltage rms value,1,float,None,,,,,,get_N0,,, Uq_ref,Vrms,q-axis voltage rms value,1,float,None,,,,,,get_Ud_Uq,,, +,,,,,,,,,,,set_Id_Iq,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv b/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv index 488a7f101..77f1f4a56 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv @@ -3,4 +3,4 @@ I0_ref,Arms,Current rms value,1,float,None,,,,Output,OP,get_Id_Iq,VERSION,1,"Ope IPhi0_ref,rad,Current phase,1,float,None,,,,,,get_felec,,, slip_ref,,Rotor mechanical slip,,float,0,,,,,,get_N0,,, U0_ref,Vrms,stator voltage (phase to neutral),,float,None,,,,,,get_Ud_Uq,,, -UPhi0_ref,rad,Voltage phase,,float,None,,,,,,,,, +UPhi0_ref,rad,Voltage phase,,float,None,,,,,,set_Id_Iq,,, diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index ac0088e08..0c173b2ee 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -44,8 +44,7 @@ def gen_input(self): "InputCurrent.Is, InputCurrent.Id_ref, and InputCurrent.Iq_ref missing" ) else: - outelec.Id_ref = self.Id_ref - outelec.Iq_ref = self.Iq_ref + outelec.OP.set_Id_Iq(self.Id_ref, self.Iq_ref) outelec.Is = None else: Is = self.Is.get_data() @@ -72,8 +71,7 @@ def gen_input(self): Time.get_values(is_oneperiod=False, normalization="angle_elec"), is_dqh_rms=True, ) - outelec.Id_ref = mean(Idq[:, 0]) - outelec.Iq_ref = mean(Idq[:, 1]) + outelec.OP.set_Id_Iq(mean(Idq[:, 0], mean(Idq[:, 1]))) # Load and check Ir is needed if qr > 0: diff --git a/pyleecan/Methods/Simulation/InputVoltage/comp_felec.py b/pyleecan/Methods/Simulation/InputVoltage/comp_felec.py deleted file mode 100644 index 9db0099a8..000000000 --- a/pyleecan/Methods/Simulation/InputVoltage/comp_felec.py +++ /dev/null @@ -1,42 +0,0 @@ -from ....Methods.Simulation.Input import InputError - - -def comp_felec(self, p=None): - """Compute the electrical frequency - - Parameters - ---------- - self : InputVoltage - An InputVoltage object - """ - - name = self.__class__.__name__ - - if p is None: - if self.parent is None: - raise InputError(name + " object should be inside a Simulation object") - # get the pole pair number - elif hasattr(self.parent, "machine"): - p = self.parent.machine.get_pole_pair_number() - elif hasattr(self.parent.parent, "machine"): - p = self.parent.parent.machine.get_pole_pair_number() - else: - raise Exception("Cannot calculate pole pair number if machine is not found") - - if self.N0 is None and self.felec is None: - raise InputError(name + " object can't have felec and N0 both None") - - if hasattr(self, "felec") and self.felec is not None: - if self.N0 is None: - self.N0 = 60 * (1 - self.slip_ref) * self.felec / p - else: - assert self.felec == self.N0 * p / ( - 60 * (1 - self.slip_ref) - ), "Input speed and frequency are not consistent regarding N0=60*(1-slip)*f_elec/p" - - elif self.N0 is not None: - - # Get the phase number for verifications - return self.N0 * p / (60 * (1 - self.slip_ref)) - - return self.felec diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 9dbccc00f..6ec4e5be2 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -3,7 +3,8 @@ from ....Classes.OutElec import OutElec from ....Classes.Simulation import Simulation - +from ....Classes.OPdq import OPdq +from ....Classes.OPslip import OPslip from ....Methods.Simulation.Input import InputError from ....Functions.Electrical.coordinate_transformation import n2dqh, n2dqh_DataTime from ....Functions.Winding.gen_phase_list import gen_name @@ -38,28 +39,32 @@ def gen_input(self): outelec = OutElec() output.elec = outelec outgeo = output.geo - - # Calculate electrical frequency and/or speed depending on inputs - outelec.felec = self.comp_felec() - + if simu.machine.is_synchronous(): + output.elec.OP = OPdq( + N0=self.N0, + felec=self.felec, + Ud_ref=self.Ud_ref, + Uq_ref=self.Uq_ref, + Tem_av_ref=self.Tem_av_ref, + Pem_av_ref=self.Pem_av_ref, + ) + else: + output.elec.OP = OPslip( + N0=self.N0, + felec=self.felec, + slip_ref=self.slip_ref, + U0_ref=self.U0_ref, + UPhi0_ref=self.Phi0_ref, + Tem_av_ref=self.Tem_av_ref, + Pem_av_ref=self.Pem_av_ref, + ) + OP = output.elec.OP # Replace N0=0 by 0.1 rpm - if self.N0 == 0: - self.N0 = 0.1 + if OP.N0 == 0: + OP.N0 = 0.1 self.get_logger().debug("Updating N0 from 0 [rpm] to 0.1 [rpm] in gen_input") - - outelec.N0 = self.N0 - - if self.U0_ref is None and self.Ud_ref and self.Uq_ref: - raise Exception("U0_ref, Ud_ref, and Uq_ref cannot be all None in InputVoltage") - - outelec.U0_ref = self.U0_ref - outelec.Ud_ref = self.Ud_ref - outelec.Uq_ref = self.Uq_ref - outelec.slip_ref = self.slip_ref - - # Load and check alpha_rotor and N0 - if self.angle_rotor is None and self.N0 is None: - raise InputError("angle_rotor and N0 can't be None at the same time") + # Check that felec/N0 can be computed + OP.get_felec() if self.angle_rotor is not None: outelec.angle_rotor = self.angle_rotor.get_data() @@ -91,9 +96,6 @@ def gen_input(self): else: outelec.angle_rotor_initial = self.angle_rotor_initial - if self.Tem_av_ref is not None: - outelec.Tem_av_ref = self.Tem_av_ref - # Calculate time, angle and phase axes and store them in OutGeo outgeo.axes_dict = self.comp_axes( axes_list=["time", "angle"], @@ -111,7 +113,7 @@ def gen_input(self): # Generate PWM signal if self.PWM is not None: # Fill generator with simu data - felec = self.comp_felec() + felec = OP.get_felec() rot_dir = output.get_rot_dir() qs = simu.machine.stator.winding.qs self.PWM.f = felec diff --git a/pyleecan/Methods/Simulation/OPdq/set_Id_Iq.py b/pyleecan/Methods/Simulation/OPdq/set_Id_Iq.py new file mode 100644 index 000000000..ffb0a77d6 --- /dev/null +++ b/pyleecan/Methods/Simulation/OPdq/set_Id_Iq.py @@ -0,0 +1,15 @@ +def set_Id_Iq(self, Id, Iq): + """Set the value for Id and Iq + + Parameters + ---------- + self : OPdq + An OPdq object + Id : float + Id value to set [Arms] + Iq : float + Iq value to set [Arms] + """ + + self.Id_ref = Id + self.Iq_ref = Iq diff --git a/pyleecan/Methods/Simulation/OPslip/set_Id_Iq.py b/pyleecan/Methods/Simulation/OPslip/set_Id_Iq.py new file mode 100644 index 000000000..6baceaaea --- /dev/null +++ b/pyleecan/Methods/Simulation/OPslip/set_Id_Iq.py @@ -0,0 +1,23 @@ +from numpy import angle + + +def set_Id_Iq(self, Id, Iq): + """Set the value for Id and Iq + + Parameters + ---------- + self : OPdq + An OPdq object + Id : float + Id value to set [Arms] + Iq : float + Iq value to set [Arms] + """ + + if Id == 0 and Iq == 0: + self.I0_ref = 0 + self.IPhi0_ref = 0 + else: + Z = Id + 1j * Iq + self.I0_ref = abs(Z) + self.IPhi0_ref = angle(Z) From eb82e0ab317f4061b969146983c450b83c85ccb1 Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 14 Oct 2021 14:06:26 +0200 Subject: [PATCH 110/167] [WiP] replace N0 with get_N0 + fix class problems --- Tests/Classes/test_compare.py | 2 +- .../Methods/Simulation/test_InCurrent_meth.py | 2 +- pyleecan/Classes/Class_Dict.json | 6 ++--- pyleecan/Classes/OP.py | 6 ++--- pyleecan/Classes/OPdq.py | 22 +++++++++---------- pyleecan/Classes/OPslip.py | 22 +++++++++---------- .../Functions/Electrical/comp_fluxlinkage.py | 2 +- .../Generator/ClassesRef/Simulation/OP.csv | 2 +- .../Generator/ClassesRef/Simulation/OPdq.csv | 2 +- .../ClassesRef/Simulation/OPslip.csv | 2 +- pyleecan/Methods/Output/OutElec/get_Nr.py | 4 ++-- pyleecan/Methods/Output/OutMag/comp_power.py | 4 ++-- .../Simulation/Electrical/comp_torque.py | 2 +- pyleecan/Methods/Simulation/Electrical/run.py | 2 +- .../Simulation/LossModelBertotti/comp_loss.py | 2 +- .../Methods/Simulation/MagElmer/solve_FEA.py | 2 +- .../Simulation/OP/get_machine_from_parent.py | 9 +++++++- pyleecan/Methods/Simulation/OPdq/get_N0.py | 2 +- pyleecan/Methods/Simulation/OPslip/get_N0.py | 2 +- .../Simulation/VarSimu/gen_datakeeper_list.py | 2 +- 20 files changed, 53 insertions(+), 46 deletions(-) diff --git a/Tests/Classes/test_compare.py b/Tests/Classes/test_compare.py index b42605127..7d4558352 100644 --- a/Tests/Classes/test_compare.py +++ b/Tests/Classes/test_compare.py @@ -54,7 +54,7 @@ def test_compare(): simu.struct = None simu.postproc_list = [ PostPlot(param_list=[1, 2], param_dict={"test": 2}), - PostFunction(run="lambda out: out.elec.N0"), + PostFunction(run="lambda out: out.elec.OP.get_N0()"), ] # Create the differences diff --git a/Tests/Methods/Simulation/test_InCurrent_meth.py b/Tests/Methods/Simulation/test_InCurrent_meth.py index fb02e27bb..aab7ace5b 100644 --- a/Tests/Methods/Simulation/test_InCurrent_meth.py +++ b/Tests/Methods/Simulation/test_InCurrent_meth.py @@ -264,7 +264,7 @@ def test_InputCurrent_DQ(self, test_dict): ) assert_array_almost_equal(output.elec.get_Is().values, Is_exp.T) assert_array_almost_equal(output.get_angle_rotor(), angle_rotor_exp) - assert_array_almost_equal(output.elec.N0, N0) + assert_array_almost_equal(output.elec.OP.get_N0(), N0) assert_array_almost_equal(output.geo.rot_dir, rot_dir) # Check Id/Iq by enforcing Is diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 60ff72da2..846ceb5ea 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -7573,7 +7573,7 @@ ], "mother": "", "name": "OP", - "package": "Output", + "package": "Simulation", "path": "pyleecan/Generator/ClassesRef/Simulation/OP.csv", "properties": [ { @@ -7633,7 +7633,7 @@ ], "mother": "OP", "name": "OPdq", - "package": "Output", + "package": "Simulation", "path": "pyleecan/Generator/ClassesRef/Simulation/OPdq.csv", "properties": [ { @@ -7693,7 +7693,7 @@ ], "mother": "OP", "name": "OPslip", - "package": "Output", + "package": "Simulation", "path": "pyleecan/Generator/ClassesRef/Simulation/OPslip.csv", "properties": [ { diff --git a/pyleecan/Classes/OP.py b/pyleecan/Classes/OP.py index a7bd8e236..6a042eba5 100644 --- a/pyleecan/Classes/OP.py +++ b/pyleecan/Classes/OP.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # File generated according to Generator/ClassesRef/Simulation/OP.csv # WARNING! All changes made in this file will be lost! -"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Output/OP +"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Simulation/OP """ from os import linesep @@ -18,7 +18,7 @@ # Import all class method # Try/catch to remove unnecessary dependencies in unused method try: - from ..Methods.Output.OP.get_machine_from_parent import get_machine_from_parent + from ..Methods.Simulation.OP.get_machine_from_parent import get_machine_from_parent except ImportError as error: get_machine_from_parent = error @@ -31,7 +31,7 @@ class OP(FrozenClass): VERSION = 1 - # cf Methods.Output.OP.get_machine_from_parent + # cf Methods.Simulation.OP.get_machine_from_parent if isinstance(get_machine_from_parent, ImportError): get_machine_from_parent = property( fget=lambda x: raise_( diff --git a/pyleecan/Classes/OPdq.py b/pyleecan/Classes/OPdq.py index d8717e5fa..5451fa29f 100644 --- a/pyleecan/Classes/OPdq.py +++ b/pyleecan/Classes/OPdq.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # File generated according to Generator/ClassesRef/Simulation/OPdq.csv # WARNING! All changes made in this file will be lost! -"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Output/OPdq +"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Simulation/OPdq """ from os import linesep @@ -18,27 +18,27 @@ # Import all class method # Try/catch to remove unnecessary dependencies in unused method try: - from ..Methods.Output.OPdq.get_Id_Iq import get_Id_Iq + from ..Methods.Simulation.OPdq.get_Id_Iq import get_Id_Iq except ImportError as error: get_Id_Iq = error try: - from ..Methods.Output.OPdq.get_felec import get_felec + from ..Methods.Simulation.OPdq.get_felec import get_felec except ImportError as error: get_felec = error try: - from ..Methods.Output.OPdq.get_N0 import get_N0 + from ..Methods.Simulation.OPdq.get_N0 import get_N0 except ImportError as error: get_N0 = error try: - from ..Methods.Output.OPdq.get_Ud_Uq import get_Ud_Uq + from ..Methods.Simulation.OPdq.get_Ud_Uq import get_Ud_Uq except ImportError as error: get_Ud_Uq = error try: - from ..Methods.Output.OPdq.set_Id_Iq import set_Id_Iq + from ..Methods.Simulation.OPdq.set_Id_Iq import set_Id_Iq except ImportError as error: set_Id_Iq = error @@ -52,7 +52,7 @@ class OPdq(OP): VERSION = 1 # Check ImportError to remove unnecessary dependencies in unused method - # cf Methods.Output.OPdq.get_Id_Iq + # cf Methods.Simulation.OPdq.get_Id_Iq if isinstance(get_Id_Iq, ImportError): get_Id_Iq = property( fget=lambda x: raise_( @@ -61,7 +61,7 @@ class OPdq(OP): ) else: get_Id_Iq = get_Id_Iq - # cf Methods.Output.OPdq.get_felec + # cf Methods.Simulation.OPdq.get_felec if isinstance(get_felec, ImportError): get_felec = property( fget=lambda x: raise_( @@ -70,7 +70,7 @@ class OPdq(OP): ) else: get_felec = get_felec - # cf Methods.Output.OPdq.get_N0 + # cf Methods.Simulation.OPdq.get_N0 if isinstance(get_N0, ImportError): get_N0 = property( fget=lambda x: raise_( @@ -79,7 +79,7 @@ class OPdq(OP): ) else: get_N0 = get_N0 - # cf Methods.Output.OPdq.get_Ud_Uq + # cf Methods.Simulation.OPdq.get_Ud_Uq if isinstance(get_Ud_Uq, ImportError): get_Ud_Uq = property( fget=lambda x: raise_( @@ -88,7 +88,7 @@ class OPdq(OP): ) else: get_Ud_Uq = get_Ud_Uq - # cf Methods.Output.OPdq.set_Id_Iq + # cf Methods.Simulation.OPdq.set_Id_Iq if isinstance(set_Id_Iq, ImportError): set_Id_Iq = property( fget=lambda x: raise_( diff --git a/pyleecan/Classes/OPslip.py b/pyleecan/Classes/OPslip.py index eb5a7cbbd..e9c0d882d 100644 --- a/pyleecan/Classes/OPslip.py +++ b/pyleecan/Classes/OPslip.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # File generated according to Generator/ClassesRef/Simulation/OPslip.csv # WARNING! All changes made in this file will be lost! -"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Output/OPslip +"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Simulation/OPslip """ from os import linesep @@ -18,27 +18,27 @@ # Import all class method # Try/catch to remove unnecessary dependencies in unused method try: - from ..Methods.Output.OPslip.get_Id_Iq import get_Id_Iq + from ..Methods.Simulation.OPslip.get_Id_Iq import get_Id_Iq except ImportError as error: get_Id_Iq = error try: - from ..Methods.Output.OPslip.get_felec import get_felec + from ..Methods.Simulation.OPslip.get_felec import get_felec except ImportError as error: get_felec = error try: - from ..Methods.Output.OPslip.get_N0 import get_N0 + from ..Methods.Simulation.OPslip.get_N0 import get_N0 except ImportError as error: get_N0 = error try: - from ..Methods.Output.OPslip.get_Ud_Uq import get_Ud_Uq + from ..Methods.Simulation.OPslip.get_Ud_Uq import get_Ud_Uq except ImportError as error: get_Ud_Uq = error try: - from ..Methods.Output.OPslip.set_Id_Iq import set_Id_Iq + from ..Methods.Simulation.OPslip.set_Id_Iq import set_Id_Iq except ImportError as error: set_Id_Iq = error @@ -52,7 +52,7 @@ class OPslip(OP): VERSION = 1 # Check ImportError to remove unnecessary dependencies in unused method - # cf Methods.Output.OPslip.get_Id_Iq + # cf Methods.Simulation.OPslip.get_Id_Iq if isinstance(get_Id_Iq, ImportError): get_Id_Iq = property( fget=lambda x: raise_( @@ -61,7 +61,7 @@ class OPslip(OP): ) else: get_Id_Iq = get_Id_Iq - # cf Methods.Output.OPslip.get_felec + # cf Methods.Simulation.OPslip.get_felec if isinstance(get_felec, ImportError): get_felec = property( fget=lambda x: raise_( @@ -70,7 +70,7 @@ class OPslip(OP): ) else: get_felec = get_felec - # cf Methods.Output.OPslip.get_N0 + # cf Methods.Simulation.OPslip.get_N0 if isinstance(get_N0, ImportError): get_N0 = property( fget=lambda x: raise_( @@ -79,7 +79,7 @@ class OPslip(OP): ) else: get_N0 = get_N0 - # cf Methods.Output.OPslip.get_Ud_Uq + # cf Methods.Simulation.OPslip.get_Ud_Uq if isinstance(get_Ud_Uq, ImportError): get_Ud_Uq = property( fget=lambda x: raise_( @@ -88,7 +88,7 @@ class OPslip(OP): ) else: get_Ud_Uq = get_Ud_Uq - # cf Methods.Output.OPslip.set_Id_Iq + # cf Methods.Simulation.OPslip.set_Id_Iq if isinstance(set_Id_Iq, ImportError): set_Id_Iq = property( fget=lambda x: raise_( diff --git a/pyleecan/Functions/Electrical/comp_fluxlinkage.py b/pyleecan/Functions/Electrical/comp_fluxlinkage.py index bfb8fbe68..7527f2d28 100644 --- a/pyleecan/Functions/Electrical/comp_fluxlinkage.py +++ b/pyleecan/Functions/Electrical/comp_fluxlinkage.py @@ -80,7 +80,7 @@ def comp_fluxlinkage(obj, output): output.elec.axes_dict["time"] = Data1D( name="time", unit="s", - values=(angle_rotor - angle_rotor[0]) / (2 * pi * output.elec.N0 / 60), + values=(angle_rotor - angle_rotor[0]) / (2 * pi * output.elec.OP.get_N0() / 60), ) output.elec.Is = None # to compute Is from Id_ref and Iq_ref (that are mean val.) output.elec.Is = output.elec.get_Is() # TODO get_Is disregards initial rotor angle diff --git a/pyleecan/Generator/ClassesRef/Simulation/OP.csv b/pyleecan/Generator/ClassesRef/Simulation/OP.csv index f18c0073e..102935a7e 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/OP.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/OP.csv @@ -1,5 +1,5 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -N0,rpm,Rotor speed,1,float,None,,,,Output,,get_machine_from_parent,VERSION,1,Define the Operating Point of the simulation +N0,rpm,Rotor speed,1,float,None,,,,Simulation,,get_machine_from_parent,VERSION,1,Define the Operating Point of the simulation felec,Hz,Electrical Frequency,1,float,None,,,,,,,,, Tem_av_ref,N.m,Theorical Average Electromagnetic torque,,float,None,,,,,,,,, Pem_av_ref,W,Theorical Average Electromagnetic Power,,float,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv b/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv index 183ef6eb7..889f7bb9e 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv @@ -1,5 +1,5 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -Id_ref,Arms,d-axis current rms value,1,float,None,,,,Output,OP,get_Id_Iq,VERSION,1,Operating Point defined in DQH frame +Id_ref,Arms,d-axis current rms value,1,float,None,,,,Simulation,OP,get_Id_Iq,VERSION,1,Operating Point defined in DQH frame Iq_ref,Arms,q-axis current rms value,1,float,None,,,,,,get_felec,,, Ud_ref,Vrms,d-axis voltage rms value,1,float,None,,,,,,get_N0,,, Uq_ref,Vrms,q-axis voltage rms value,1,float,None,,,,,,get_Ud_Uq,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv b/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv index 77f1f4a56..199508ec3 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv @@ -1,5 +1,5 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -I0_ref,Arms,Current rms value,1,float,None,,,,Output,OP,get_Id_Iq,VERSION,1,"Operating Point defined with slip, I0" +I0_ref,Arms,Current rms value,1,float,None,,,,Simulation,OP,get_Id_Iq,VERSION,1,"Operating Point defined with slip, I0" IPhi0_ref,rad,Current phase,1,float,None,,,,,,get_felec,,, slip_ref,,Rotor mechanical slip,,float,0,,,,,,get_N0,,, U0_ref,Vrms,stator voltage (phase to neutral),,float,None,,,,,,get_Ud_Uq,,, diff --git a/pyleecan/Methods/Output/OutElec/get_Nr.py b/pyleecan/Methods/Output/OutElec/get_Nr.py index f1e8ba799..f7259138e 100644 --- a/pyleecan/Methods/Output/OutElec/get_Nr.py +++ b/pyleecan/Methods/Output/OutElec/get_Nr.py @@ -23,10 +23,10 @@ def get_Nr(self, Time=None): else: raise Exception('You must define "time" property before calling get_Nr') - if self.N0 is None: + if self.OP.get_N0() is None: raise Exception('You must define "N0" before calling get_Nr') # Same speed for every timestep - Nr = self.N0 * ones(Time.get_length(is_smallestperiod=True)) + Nr = self.OP.get_N0() * ones(Time.get_length(is_smallestperiod=True)) return Nr diff --git a/pyleecan/Methods/Output/OutMag/comp_power.py b/pyleecan/Methods/Output/OutMag/comp_power.py index aa66612ea..ae36cfdf3 100644 --- a/pyleecan/Methods/Output/OutMag/comp_power.py +++ b/pyleecan/Methods/Output/OutMag/comp_power.py @@ -13,9 +13,9 @@ def comp_power(self): if ( self.parent is not None and self.parent.elec is not None - and self.parent.elec.N0 is not None + and self.parent.elec.OP.get_N0() is not None and self.Tem_av is not None ): - self.Pem_av = 2 * pi * self.parent.elec.N0 / 60 * self.Tem_av + self.Pem_av = 2 * pi * self.parent.elec.OP.get_N0() / 60 * self.Tem_av else: self.Pem_av = None diff --git a/pyleecan/Methods/Simulation/Electrical/comp_torque.py b/pyleecan/Methods/Simulation/Electrical/comp_torque.py index 2004e08f1..7ad4f7292 100644 --- a/pyleecan/Methods/Simulation/Electrical/comp_torque.py +++ b/pyleecan/Methods/Simulation/Electrical/comp_torque.py @@ -14,7 +14,7 @@ def comp_torque(self, output): an Output object """ - N0 = output.elec.N0 + N0 = output.elec.OP.get_N0() omega = 2 * pi * N0 / 60 P = output.elec.Pem_av_ref diff --git a/pyleecan/Methods/Simulation/Electrical/run.py b/pyleecan/Methods/Simulation/Electrical/run.py index f84b9c201..89f9ac6a5 100644 --- a/pyleecan/Methods/Simulation/Electrical/run.py +++ b/pyleecan/Methods/Simulation/Electrical/run.py @@ -57,7 +57,7 @@ def run(self): # Compute parameters of the electrical equivalent circuit if some parameters are missing in ELUT out_dict = self.eec.comp_parameters( machine, - output.elec.N0, + output.elec.OP.get_N0(), output.elec.felec, output.elec.Id_ref, output.elec.Iq_ref, diff --git a/pyleecan/Methods/Simulation/LossModelBertotti/comp_loss.py b/pyleecan/Methods/Simulation/LossModelBertotti/comp_loss.py index cc432676c..d032eb257 100644 --- a/pyleecan/Methods/Simulation/LossModelBertotti/comp_loss.py +++ b/pyleecan/Methods/Simulation/LossModelBertotti/comp_loss.py @@ -64,7 +64,7 @@ def comp_loss(self, output, part_label): L1 = lam.L1 mat_type = lam.mat_type rho = mat_type.struct.rho - N0 = output.elec.N0 + N0 = output.elec.OP.get_N0() group_name = part_label.lower() + " " + self.group # TODO unifiy FEA names # setup meshsolution and solution list diff --git a/pyleecan/Methods/Simulation/MagElmer/solve_FEA.py b/pyleecan/Methods/Simulation/MagElmer/solve_FEA.py index a67c6993b..605b1a5bc 100644 --- a/pyleecan/Methods/Simulation/MagElmer/solve_FEA.py +++ b/pyleecan/Methods/Simulation/MagElmer/solve_FEA.py @@ -84,7 +84,7 @@ def solve_FEA(self, output, sym, angle, time, angle_rotor, Is, Ir): BHr = output.geo.rotor.BH_curve # Rotor B(H) curve # Is = output.elec.Is # Stator currents waveforms # Ir = output.elec.Ir # Rotor currents waveforms - Speed = output.elec.N0 + Speed = output.elec.OP.get_N0() rotor_mat_file = join(project_name, "rotor_material.pmf") stator_mat_file = join(project_name, "stator_material.pmf") diff --git a/pyleecan/Methods/Simulation/OP/get_machine_from_parent.py b/pyleecan/Methods/Simulation/OP/get_machine_from_parent.py index 1add96125..0ba3769b5 100644 --- a/pyleecan/Methods/Simulation/OP/get_machine_from_parent.py +++ b/pyleecan/Methods/Simulation/OP/get_machine_from_parent.py @@ -19,4 +19,11 @@ def get_machine_from_parent(self): if parent is not None: return parent.machine else: - return None + # Try to import machine from simu object + parent = self.parent + while parent is not None and not hasattr(parent, "simu"): + parent = parent.parent + if parent is not None: + return parent.simu.machine + else: + return None diff --git a/pyleecan/Methods/Simulation/OPdq/get_N0.py b/pyleecan/Methods/Simulation/OPdq/get_N0.py index e52fe4c0e..4536ff257 100644 --- a/pyleecan/Methods/Simulation/OPdq/get_N0.py +++ b/pyleecan/Methods/Simulation/OPdq/get_N0.py @@ -1,7 +1,7 @@ from ....Methods.Simulation.Input import InputError -def get_felec(self): +def get_N0(self): """Returns the Rotor speed Parameters diff --git a/pyleecan/Methods/Simulation/OPslip/get_N0.py b/pyleecan/Methods/Simulation/OPslip/get_N0.py index 32a3f3273..4d4656570 100644 --- a/pyleecan/Methods/Simulation/OPslip/get_N0.py +++ b/pyleecan/Methods/Simulation/OPslip/get_N0.py @@ -1,7 +1,7 @@ from ....Methods.Simulation.Input import InputError -def get_felec(self): +def get_N0(self): """Returns the Rotor speed Parameters diff --git a/pyleecan/Methods/Simulation/VarSimu/gen_datakeeper_list.py b/pyleecan/Methods/Simulation/VarSimu/gen_datakeeper_list.py index 9786c44d8..08242c95c 100644 --- a/pyleecan/Methods/Simulation/VarSimu/gen_datakeeper_list.py +++ b/pyleecan/Methods/Simulation/VarSimu/gen_datakeeper_list.py @@ -20,7 +20,7 @@ def gen_datakeeper_list(self, ref_simu): name="Speed", symbol="N0", unit="rpm", - keeper="lambda output: output.elec.N0", + keeper="lambda output: output.elec.OP.get_N0()", ) ) From b4787f4a3d53845133ff6140a3fcecddf7a82ddc Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 14 Oct 2021 14:07:46 +0200 Subject: [PATCH 111/167] [BC] slip_ref is now in OP --- .../Methods/Output/Output/getter/get_machine_periodicity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyleecan/Methods/Output/Output/getter/get_machine_periodicity.py b/pyleecan/Methods/Output/Output/getter/get_machine_periodicity.py index 8043e7d94..e14fc236c 100644 --- a/pyleecan/Methods/Output/Output/getter/get_machine_periodicity.py +++ b/pyleecan/Methods/Output/Output/getter/get_machine_periodicity.py @@ -38,7 +38,7 @@ def get_machine_periodicity(self, is_rotor_ref=False): self.geo.is_antiper_t_S, self.geo.per_t_R, self.geo.is_antiper_t_R, - ) = self.simu.machine.comp_periodicity_time(slip=self.elec.slip_ref) + ) = self.simu.machine.comp_periodicity_time(slip=self.elec.OP.slip_ref) if is_rotor_ref: return ( From e3c29f72eda4e72ea2d7de87cba77e9dfdf3fbb9 Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 14 Oct 2021 14:19:09 +0200 Subject: [PATCH 112/167] [NF] add get_slip method --- pyleecan/Classes/Class_Dict.json | 2 ++ pyleecan/Classes/OPdq.py | 14 ++++++++++++++ pyleecan/Classes/OPslip.py | 14 ++++++++++++++ .../Generator/ClassesRef/Simulation/OPdq.csv | 3 ++- .../Generator/ClassesRef/Simulation/OPslip.csv | 5 +++-- pyleecan/Methods/Simulation/OPdq/get_slip.py | 18 ++++++++++++++++++ pyleecan/Methods/Simulation/OPslip/get_slip.py | 18 ++++++++++++++++++ 7 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 pyleecan/Methods/Simulation/OPdq/get_slip.py create mode 100644 pyleecan/Methods/Simulation/OPslip/get_slip.py diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 846ceb5ea..052f16d3b 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -7628,6 +7628,7 @@ "get_Id_Iq", "get_felec", "get_N0", + "get_slip", "get_Ud_Uq", "set_Id_Iq" ], @@ -7688,6 +7689,7 @@ "get_Id_Iq", "get_felec", "get_N0", + "get_slip", "get_Ud_Uq", "set_Id_Iq" ], diff --git a/pyleecan/Classes/OPdq.py b/pyleecan/Classes/OPdq.py index 5451fa29f..b1bd77e0a 100644 --- a/pyleecan/Classes/OPdq.py +++ b/pyleecan/Classes/OPdq.py @@ -32,6 +32,11 @@ except ImportError as error: get_N0 = error +try: + from ..Methods.Simulation.OPdq.get_slip import get_slip +except ImportError as error: + get_slip = error + try: from ..Methods.Simulation.OPdq.get_Ud_Uq import get_Ud_Uq except ImportError as error: @@ -79,6 +84,15 @@ class OPdq(OP): ) else: get_N0 = get_N0 + # cf Methods.Simulation.OPdq.get_slip + if isinstance(get_slip, ImportError): + get_slip = property( + fget=lambda x: raise_( + ImportError("Can't use OPdq method get_slip: " + str(get_slip)) + ) + ) + else: + get_slip = get_slip # cf Methods.Simulation.OPdq.get_Ud_Uq if isinstance(get_Ud_Uq, ImportError): get_Ud_Uq = property( diff --git a/pyleecan/Classes/OPslip.py b/pyleecan/Classes/OPslip.py index e9c0d882d..a8efcb97d 100644 --- a/pyleecan/Classes/OPslip.py +++ b/pyleecan/Classes/OPslip.py @@ -32,6 +32,11 @@ except ImportError as error: get_N0 = error +try: + from ..Methods.Simulation.OPslip.get_slip import get_slip +except ImportError as error: + get_slip = error + try: from ..Methods.Simulation.OPslip.get_Ud_Uq import get_Ud_Uq except ImportError as error: @@ -79,6 +84,15 @@ class OPslip(OP): ) else: get_N0 = get_N0 + # cf Methods.Simulation.OPslip.get_slip + if isinstance(get_slip, ImportError): + get_slip = property( + fget=lambda x: raise_( + ImportError("Can't use OPslip method get_slip: " + str(get_slip)) + ) + ) + else: + get_slip = get_slip # cf Methods.Simulation.OPslip.get_Ud_Uq if isinstance(get_Ud_Uq, ImportError): get_Ud_Uq = property( diff --git a/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv b/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv index 889f7bb9e..19994ec23 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv @@ -2,5 +2,6 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximu Id_ref,Arms,d-axis current rms value,1,float,None,,,,Simulation,OP,get_Id_Iq,VERSION,1,Operating Point defined in DQH frame Iq_ref,Arms,q-axis current rms value,1,float,None,,,,,,get_felec,,, Ud_ref,Vrms,d-axis voltage rms value,1,float,None,,,,,,get_N0,,, -Uq_ref,Vrms,q-axis voltage rms value,1,float,None,,,,,,get_Ud_Uq,,, +Uq_ref,Vrms,q-axis voltage rms value,1,float,None,,,,,,get_slip,,, +,,,,,,,,,,,get_Ud_Uq,,, ,,,,,,,,,,,set_Id_Iq,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv b/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv index 199508ec3..70163b966 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv @@ -2,5 +2,6 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximu I0_ref,Arms,Current rms value,1,float,None,,,,Simulation,OP,get_Id_Iq,VERSION,1,"Operating Point defined with slip, I0" IPhi0_ref,rad,Current phase,1,float,None,,,,,,get_felec,,, slip_ref,,Rotor mechanical slip,,float,0,,,,,,get_N0,,, -U0_ref,Vrms,stator voltage (phase to neutral),,float,None,,,,,,get_Ud_Uq,,, -UPhi0_ref,rad,Voltage phase,,float,None,,,,,,set_Id_Iq,,, +U0_ref,Vrms,stator voltage (phase to neutral),,float,None,,,,,,get_slip,,, +UPhi0_ref,rad,Voltage phase,,float,None,,,,,,get_Ud_Uq,,, +,,,,,,,,,,,set_Id_Iq,,, diff --git a/pyleecan/Methods/Simulation/OPdq/get_slip.py b/pyleecan/Methods/Simulation/OPdq/get_slip.py new file mode 100644 index 000000000..be32ab8a7 --- /dev/null +++ b/pyleecan/Methods/Simulation/OPdq/get_slip.py @@ -0,0 +1,18 @@ +from ....Methods.Simulation.Input import InputError + + +def get_slip(self): + """Returns the Rotor mechanical slip + + Parameters + ---------- + self : OPdq + An OPdq object + + Returns + ------- + slip : float + Rotor mechanical slip (0 for synchronous machines) + """ + + return 0 diff --git a/pyleecan/Methods/Simulation/OPslip/get_slip.py b/pyleecan/Methods/Simulation/OPslip/get_slip.py new file mode 100644 index 000000000..a740fe29a --- /dev/null +++ b/pyleecan/Methods/Simulation/OPslip/get_slip.py @@ -0,0 +1,18 @@ +from ....Methods.Simulation.Input import InputError + + +def get_slip(self): + """Returns the Rotor mechanical slip + + Parameters + ---------- + self : OPslip + An OPslip object + + Returns + ------- + slip : float + Rotor mechanical slip + """ + + return self.slip_ref From 8e6d2574ea47950e3c8390b33e1078922d07f0e7 Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 14 Oct 2021 14:19:34 +0200 Subject: [PATCH 113/167] [NF] add get_slip method --- .../Methods/Output/Output/getter/get_machine_periodicity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyleecan/Methods/Output/Output/getter/get_machine_periodicity.py b/pyleecan/Methods/Output/Output/getter/get_machine_periodicity.py index e14fc236c..6a156ed6b 100644 --- a/pyleecan/Methods/Output/Output/getter/get_machine_periodicity.py +++ b/pyleecan/Methods/Output/Output/getter/get_machine_periodicity.py @@ -38,7 +38,7 @@ def get_machine_periodicity(self, is_rotor_ref=False): self.geo.is_antiper_t_S, self.geo.per_t_R, self.geo.is_antiper_t_R, - ) = self.simu.machine.comp_periodicity_time(slip=self.elec.OP.slip_ref) + ) = self.simu.machine.comp_periodicity_time(slip=self.elec.OP.get_slip()) if is_rotor_ref: return ( From e8b74975573406c7e06b79e1b6c34cf9f9e50c26 Mon Sep 17 00:00:00 2001 From: Pierre Bonneel Date: Thu, 14 Oct 2021 14:30:35 +0200 Subject: [PATCH 114/167] [WIP] Using OP in the code --- Tests/Classes/test_compare.py | 2 +- pyleecan/Classes/Class_Dict.json | 6 ++++-- pyleecan/Classes/OPdq.py | 14 ++++++++++++++ pyleecan/Classes/OPslip.py | 14 ++++++++++++++ .../SPreview/WMachineTable/WMachineTable.py | 10 +++++++--- .../Generator/ClassesRef/Simulation/OPdq.csv | 1 + .../ClassesRef/Simulation/OPslip.csv | 1 + .../Methods/Machine/Conductor/comp_power.py | 11 ++++++----- pyleecan/Methods/Output/OutElec/get_I_fund.py | 12 +++++------- .../Simulation/EEC_PMSM/comp_joule_losses.py | 4 ++-- .../Simulation/EEC_PMSM/comp_parameters.py | 5 ++++- .../Methods/Simulation/EEC_PMSM/solve_EEC.py | 3 +-- .../Simulation/EEC_SCIM/comp_joule_losses.py | 4 ++-- .../Simulation/EEC_SCIM/comp_parameters.py | 4 +++- .../Simulation/Electrical/comp_power.py | 9 +++++---- pyleecan/Methods/Simulation/Electrical/run.py | 5 +---- .../Methods/Simulation/OPdq/get_I0_Phi0.py | 19 +++++++++++++++++++ .../Methods/Simulation/OPslip/get_I0_Phi0.py | 18 ++++++++++++++++++ .../VarLoadCurrent/get_elec_datakeeper.py | 4 ++-- .../Simulation/VarSimu/get_elec_datakeeper.py | 4 ++-- pyleecan/Methods/Simulation/VarSimu/run.py | 6 +++--- 21 files changed, 115 insertions(+), 41 deletions(-) create mode 100644 pyleecan/Methods/Simulation/OPdq/get_I0_Phi0.py create mode 100644 pyleecan/Methods/Simulation/OPslip/get_I0_Phi0.py diff --git a/Tests/Classes/test_compare.py b/Tests/Classes/test_compare.py index b42605127..1bb4c6ce7 100644 --- a/Tests/Classes/test_compare.py +++ b/Tests/Classes/test_compare.py @@ -71,7 +71,7 @@ def test_compare(): simu2.mag.transform_list = ["bla"] # len(list) simu2.postproc_list[0].param_list = [1, 3] # list diff simu2.postproc_list[0].param_dict["test2"] = 3 # len(dict) - simu2.postproc_list[1].run = "lambda out: out.elec.Id_ref" # function + simu2.postproc_list[1].run = "lambda out: out.elec.OP.N0" # function # dict diff simu2.machine.stator.winding = WindingUD() # pyleecan type diff # pyleecan dict diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 60ff72da2..40bac49dd 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -7629,7 +7629,8 @@ "get_felec", "get_N0", "get_Ud_Uq", - "set_Id_Iq" + "set_Id_Iq", + "get_I0_Phi0" ], "mother": "OP", "name": "OPdq", @@ -7689,7 +7690,8 @@ "get_felec", "get_N0", "get_Ud_Uq", - "set_Id_Iq" + "set_Id_Iq", + "get_I0_Phi0" ], "mother": "OP", "name": "OPslip", diff --git a/pyleecan/Classes/OPdq.py b/pyleecan/Classes/OPdq.py index d8717e5fa..c6bf5e838 100644 --- a/pyleecan/Classes/OPdq.py +++ b/pyleecan/Classes/OPdq.py @@ -42,6 +42,11 @@ except ImportError as error: set_Id_Iq = error +try: + from ..Methods.Output.OPdq.get_I0_Phi0 import get_I0_Phi0 +except ImportError as error: + get_I0_Phi0 = error + from ._check import InitUnKnowClassError @@ -97,6 +102,15 @@ class OPdq(OP): ) else: set_Id_Iq = set_Id_Iq + # cf Methods.Output.OPdq.get_I0_Phi0 + if isinstance(get_I0_Phi0, ImportError): + get_I0_Phi0 = property( + fget=lambda x: raise_( + ImportError("Can't use OPdq method get_I0_Phi0: " + str(get_I0_Phi0)) + ) + ) + else: + get_I0_Phi0 = get_I0_Phi0 # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/OPslip.py b/pyleecan/Classes/OPslip.py index eb5a7cbbd..5d4310da2 100644 --- a/pyleecan/Classes/OPslip.py +++ b/pyleecan/Classes/OPslip.py @@ -42,6 +42,11 @@ except ImportError as error: set_Id_Iq = error +try: + from ..Methods.Output.OPslip.get_I0_Phi0 import get_I0_Phi0 +except ImportError as error: + get_I0_Phi0 = error + from ._check import InitUnKnowClassError @@ -97,6 +102,15 @@ class OPslip(OP): ) else: set_Id_Iq = set_Id_Iq + # cf Methods.Output.OPslip.get_I0_Phi0 + if isinstance(get_I0_Phi0, ImportError): + get_I0_Phi0 = property( + fget=lambda x: raise_( + ImportError("Can't use OPslip method get_I0_Phi0: " + str(get_I0_Phi0)) + ) + ) + else: + get_I0_Phi0 = get_I0_Phi0 # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/GUI/Dialog/DMachineSetup/SPreview/WMachineTable/WMachineTable.py b/pyleecan/GUI/Dialog/DMachineSetup/SPreview/WMachineTable/WMachineTable.py index 63c1066be..6b31fff29 100644 --- a/pyleecan/GUI/Dialog/DMachineSetup/SPreview/WMachineTable/WMachineTable.py +++ b/pyleecan/GUI/Dialog/DMachineSetup/SPreview/WMachineTable/WMachineTable.py @@ -9,6 +9,8 @@ from ......Classes._FEMMHandler import _FEMMHandler from ......Classes.Output import Output from ......Classes.Simu1 import Simu1 +from ......Classes.OPdq import OPdq +from ......Classes.OPslip import OPslip from ......definitions import config_dict from ......loggers import GUI_LOG_NAME from ......Functions.FEMM.update_FEMM_simulation import update_FEMM_simulation @@ -130,9 +132,11 @@ def draw_FEMM(self): Ntcoil = self.machine.stator.winding.Ntcoil Sphase = S_slot / (Nrad * Ntan) J = 5e6 - output.elec.Id_ref = J * Sphase / Ntcoil - output.elec.Iq_ref = 0 - output.elec.felec = 60 + if self.machine.is_synchronous(): + output.elec.OP = OPdq(felec=60) + else: + output.elec.OP = OPslip(felec=60) + output.elec.OP.set_Id_Iq(Id=J * Sphase / Ntcoil, Iq=0) output.elec.Time = DataLinspace( name="time", unit="s", diff --git a/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv b/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv index 183ef6eb7..4edaec682 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv @@ -4,3 +4,4 @@ Iq_ref,Arms,q-axis current rms value,1,float,None,,,,,,get_felec,,, Ud_ref,Vrms,d-axis voltage rms value,1,float,None,,,,,,get_N0,,, Uq_ref,Vrms,q-axis voltage rms value,1,float,None,,,,,,get_Ud_Uq,,, ,,,,,,,,,,,set_Id_Iq,,, +,,,,,,,,,,,get_I0_Phi0,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv b/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv index 77f1f4a56..9641f85b2 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv @@ -4,3 +4,4 @@ IPhi0_ref,rad,Current phase,1,float,None,,,,,,get_felec,,, slip_ref,,Rotor mechanical slip,,float,0,,,,,,get_N0,,, U0_ref,Vrms,stator voltage (phase to neutral),,float,None,,,,,,get_Ud_Uq,,, UPhi0_ref,rad,Voltage phase,,float,None,,,,,,set_Id_Iq,,, +,,,,,,,,,,,get_I0_Phi0,,, diff --git a/pyleecan/Methods/Machine/Conductor/comp_power.py b/pyleecan/Methods/Machine/Conductor/comp_power.py index 249ba0e2f..d359f8c7b 100644 --- a/pyleecan/Methods/Machine/Conductor/comp_power.py +++ b/pyleecan/Methods/Machine/Conductor/comp_power.py @@ -13,12 +13,13 @@ def comp_power(self, output): """ qs = output.simu.machine.stator.winding.qs - Id = output.elec.Id_ref - Iq = output.elec.Iq_ref - Ud = output.elec.Ud_ref - Uq = output.elec.Uq_ref + I_dict = output.elec.OP.get_Id_Iq() + Id, Iq = I_dict["Id"], I_dict["Iq"] + + U_dict = output.elec.OP.get_Ud_Uq() + Ud, Uq = U_dict["Ud"], U_dict["Uq"] # All quantities are in RMS Pem_av_ref = qs * (Ud * Id + Uq * Iq) - output.elec.Pem_av_ref = Pem_av_ref + output.elec.OP.Pem_av_ref = Pem_av_ref diff --git a/pyleecan/Methods/Output/OutElec/get_I_fund.py b/pyleecan/Methods/Output/OutElec/get_I_fund.py index b601750ba..c61e875e1 100644 --- a/pyleecan/Methods/Output/OutElec/get_I_fund.py +++ b/pyleecan/Methods/Output/OutElec/get_I_fund.py @@ -24,15 +24,13 @@ def get_I_fund(self, Time=None): Phase = self.axes_dict[stator_label] if self.Is is None: - if ( - self.Id_ref is not None - and self.Iq_ref is not None - and (self.Id_ref != 0 or self.Iq_ref != 0) - ): + I_dict = self.OP.get_Id_Iq() + Id, Iq = I_dict["Id"], I_dict["Iq"] + if Id is not None and Iq is not None and (Id != 0 or Iq != 0): # Generate current according to Id/Iq, Ih=0 Is_dqh = zeros((angle_elec.size, 3)) - Is_dqh[:, 0] = self.Id_ref - Is_dqh[:, 1] = self.Iq_ref + Is_dqh[:, 0] = Id + Is_dqh[:, 1] = Iq # Get stator current function of time Is = dqh2n(Is_dqh, angle_elec, n=qs, is_n_rms=False) diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/comp_joule_losses.py b/pyleecan/Methods/Simulation/EEC_PMSM/comp_joule_losses.py index 01ee7d4e7..e1fb09b06 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/comp_joule_losses.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/comp_joule_losses.py @@ -13,8 +13,8 @@ def comp_joule_losses(self, output): """ qs = output.simu.machine.stator.winding.qs - Id = output.elec.Id_ref - Iq = output.elec.Iq_ref + I_dict = self.OP.get_Id_Iq() + Id, Iq = I_dict["Id"], I_dict["Iq"] R = self.parameters["R20"] # Id and Iq are in RMS diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py index d6d99683e..93b09655c 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py @@ -4,7 +4,7 @@ from enum import unique -def comp_parameters(self, machine, N0, felec, Id_ref, Iq_ref, Tsta=None, Trot=None): +def comp_parameters(self, machine, OP, Tsta=None, Trot=None): """Compute the parameters dict for the equivalent electrical circuit: resistance, inductance and back electromotive force Parameters @@ -21,6 +21,9 @@ def comp_parameters(self, machine, N0, felec, Id_ref, Iq_ref, Tsta=None, Trot=No PAR = self.parameters Cond = machine.stator.winding.conductor + I_dict = OP.get_Id_Iq() + Id_ref, Iq_ref = I_dict["Id"], I_dict["Iq"] + felec = OP.get_felec() # compute skin_effect Xkr_skinS, Xke_skinS = Cond.comp_skin_effect(T=20, freq=felec) diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/solve_EEC.py b/pyleecan/Methods/Simulation/EEC_PMSM/solve_EEC.py index 56bb4d764..ec8071c17 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/solve_EEC.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/solve_EEC.py @@ -43,8 +43,7 @@ def solve_EEC(self, output): XE = array([0, ws * self.parameters["phi"]]) XU = array([self.parameters["Ud"], self.parameters["Uq"]]) XI = solve(XR, XU - XE) - output.elec.Id_ref = XI[0] - output.elec.Iq_ref = XI[1] + output.elec.OP.set_Id_Iq(Id=XI[0], Iq=XI[1]) else: # Current Driven output.elec.Ud_ref = ( self.parameters["R20"] * self.parameters["Id"] diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py b/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py index fee0e203b..ab200ec82 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py @@ -16,8 +16,8 @@ def comp_joule_losses(self, output): # TODO utilize loss models instead here # compute stator joule losses qs = output.simu.machine.stator.winding.qs - Id = output.elec.Id_ref - Iq = output.elec.Iq_ref + I_dict = self.OP.get_Id_Iq() + Id, Iq = I_dict["Id"], I_dict["Iq"] Rs = self.parameters["Rs"] P_joule_s = qs * Rs * (Id ** 2 + Iq ** 2) diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py index 17e65cf21..5e56dc580 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py @@ -6,7 +6,7 @@ from ....Functions.load import import_class -def comp_parameters(self, machine, N0, felec, Id_ref, Iq_ref, Tsta, Trot): +def comp_parameters(self, machine, OP, Tsta, Trot): """Compute the parameters dict for the equivalent electrical circuit: resistance, inductance Parameters @@ -22,6 +22,8 @@ def comp_parameters(self, machine, N0, felec, Id_ref, Iq_ref, Tsta, Trot): qsr = machine.rotor.winding.qs qs = machine.stator.winding.qs p = machine.rotor.winding.p + N0 = OP.get_N0() + felec = OP.get_felec() # simulation type for magnetizing inductance when missing (0: FEA, 1: Analytical) type_comp_Lm = 0 diff --git a/pyleecan/Methods/Simulation/Electrical/comp_power.py b/pyleecan/Methods/Simulation/Electrical/comp_power.py index 249ba0e2f..439f5d5cf 100644 --- a/pyleecan/Methods/Simulation/Electrical/comp_power.py +++ b/pyleecan/Methods/Simulation/Electrical/comp_power.py @@ -13,10 +13,11 @@ def comp_power(self, output): """ qs = output.simu.machine.stator.winding.qs - Id = output.elec.Id_ref - Iq = output.elec.Iq_ref - Ud = output.elec.Ud_ref - Uq = output.elec.Uq_ref + I_dict = output.elec.OP.get_Id_Iq() + Id, Iq = I_dict["Id"], I_dict["Iq"] + + U_dict = output.elec.OP.get_Ud_Uq() + Ud, Uq = U_dict["Ud"], U_dict["Uq"] # All quantities are in RMS Pem_av_ref = qs * (Ud * Id + Uq * Iq) diff --git a/pyleecan/Methods/Simulation/Electrical/run.py b/pyleecan/Methods/Simulation/Electrical/run.py index f84b9c201..d48263195 100644 --- a/pyleecan/Methods/Simulation/Electrical/run.py +++ b/pyleecan/Methods/Simulation/Electrical/run.py @@ -57,10 +57,7 @@ def run(self): # Compute parameters of the electrical equivalent circuit if some parameters are missing in ELUT out_dict = self.eec.comp_parameters( machine, - output.elec.N0, - output.elec.felec, - output.elec.Id_ref, - output.elec.Iq_ref, + OP=output.elec.OP, Tsta=self.Tsta, Trot=self.Trot, ) diff --git a/pyleecan/Methods/Simulation/OPdq/get_I0_Phi0.py b/pyleecan/Methods/Simulation/OPdq/get_I0_Phi0.py new file mode 100644 index 000000000..eb5b9a3d2 --- /dev/null +++ b/pyleecan/Methods/Simulation/OPdq/get_I0_Phi0.py @@ -0,0 +1,19 @@ +from numpy import angle, pi + + +def get_I0_Phi0(self): + """Return I0 and Phi0 + + Parameters + ---------- + self : OPdq + An OPdq object + + Returns + ------- + I_dict : dict + Dict with key "I0", "Phi0" + """ + + Z = self.Id_ref + 1j * self.Iq_ref + return {"I0": abs(Z), "Phi0": angle(Z) % (2 * pi)} diff --git a/pyleecan/Methods/Simulation/OPslip/get_I0_Phi0.py b/pyleecan/Methods/Simulation/OPslip/get_I0_Phi0.py new file mode 100644 index 000000000..4992f8e9c --- /dev/null +++ b/pyleecan/Methods/Simulation/OPslip/get_I0_Phi0.py @@ -0,0 +1,18 @@ +from numpy import angle + + +def get_I0_Phi0(self): + """Return I0 and Phi0 + + Parameters + ---------- + self : OPslip + An OPslip object + + Returns + ------- + I_dict : dict + Dict with key "I0", "Phi0" + """ + + return {"I0": self.I0_ref, "Phi0": self.IPhi0_ref} diff --git a/pyleecan/Methods/Simulation/VarLoadCurrent/get_elec_datakeeper.py b/pyleecan/Methods/Simulation/VarLoadCurrent/get_elec_datakeeper.py index a38c9077c..a86df1c23 100644 --- a/pyleecan/Methods/Simulation/VarLoadCurrent/get_elec_datakeeper.py +++ b/pyleecan/Methods/Simulation/VarLoadCurrent/get_elec_datakeeper.py @@ -29,7 +29,7 @@ def get_elec_datakeeper(self, symbol_list, is_multi=False): name="I0", symbol="I0", unit="A", - keeper="lambda output: np.abs(output.elec.Id_ref + 1j * output.elec.Iq_ref)", + keeper="lambda output: output.elec.OP.get_I0_Phi0()['I0']", ) ) # Save Phi0 @@ -39,7 +39,7 @@ def get_elec_datakeeper(self, symbol_list, is_multi=False): name="Phi0", symbol="Phi0", unit="", - keeper="lambda output: np.angle(output.elec.Id_ref + 1j * output.elec.Iq_ref) % (2 * np.pi)", + keeper="lambda output: output.elec.OP.get_I0_Phi0()['Phi0']", ) ) # Keep torque diff --git a/pyleecan/Methods/Simulation/VarSimu/get_elec_datakeeper.py b/pyleecan/Methods/Simulation/VarSimu/get_elec_datakeeper.py index adb626a3e..9d722a60a 100644 --- a/pyleecan/Methods/Simulation/VarSimu/get_elec_datakeeper.py +++ b/pyleecan/Methods/Simulation/VarSimu/get_elec_datakeeper.py @@ -27,7 +27,7 @@ def get_elec_datakeeper(self, symbol_list, is_multi=False): name="Id", symbol="Id", unit="Arms", - keeper="lambda output: output.elec.Id_ref", + keeper="lambda output: output.elec.OP.get_Id_Iq()['Id']", ) ) # Save Iq @@ -37,7 +37,7 @@ def get_elec_datakeeper(self, symbol_list, is_multi=False): name="Iq", symbol="Iq", unit="Arms", - keeper="lambda output: output.elec.Iq_ref", + keeper="lambda output: output.elec.OP.get_Id_Iq()['Iq']", ) ) diff --git a/pyleecan/Methods/Simulation/VarSimu/run.py b/pyleecan/Methods/Simulation/VarSimu/run.py index 513fee415..ff621b641 100644 --- a/pyleecan/Methods/Simulation/VarSimu/run.py +++ b/pyleecan/Methods/Simulation/VarSimu/run.py @@ -176,9 +176,9 @@ def log_step_simu(index, nb_simu, paramexplorer_list, logger, layer): for param_exp in paramexplorer_list: value = param_exp.get_value()[index] if isinstance(value, InputCurrent): - msg += "N0=" + format(value.N0, ".6g") + " [rpm]" - msg += ", Id=" + format(value.Id_ref, ".4g") + " [Arms]" - msg += ", Iq=" + format(value.Iq_ref, ".4g") + " [Arms], " + msg += "N0=" + format(value.get_N0(), ".6g") + " [rpm]" + msg += ", Id=" + format(value.get_Id_Iq()["Id"], ".4g") + " [Arms]" + msg += ", Iq=" + format(value.get_Id_Iq()["Iq"], ".4g") + " [Arms], " elif isinstance(value, (list, np.ndarray)): msg += param_exp.symbol msg += "=" From 25b3fd43b216d04b31045563334fe6c6ba4dd6e5 Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 14 Oct 2021 14:43:10 +0200 Subject: [PATCH 115/167] [CC] regenerated classes --- pyleecan/Classes/Class_Dict.json | 4 ++-- pyleecan/Classes/OPdq.py | 4 ++-- pyleecan/Classes/OPslip.py | 32 ++++++++++++++++---------------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index a69ba773c..7bc40c050 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -7690,10 +7690,10 @@ "get_Id_Iq", "get_felec", "get_N0", - "get_slip", "get_Ud_Uq", "set_Id_Iq", - "get_I0_Phi0" + "get_I0_Phi0", + "get_slip" ], "mother": "OP", "name": "OPslip", diff --git a/pyleecan/Classes/OPdq.py b/pyleecan/Classes/OPdq.py index eb475ed9f..df4211008 100644 --- a/pyleecan/Classes/OPdq.py +++ b/pyleecan/Classes/OPdq.py @@ -48,7 +48,7 @@ set_Id_Iq = error try: - from ..Methods.Output.OPdq.get_I0_Phi0 import get_I0_Phi0 + from ..Methods.Simulation.OPdq.get_I0_Phi0 import get_I0_Phi0 except ImportError as error: get_I0_Phi0 = error @@ -116,7 +116,7 @@ class OPdq(OP): ) else: set_Id_Iq = set_Id_Iq - # cf Methods.Output.OPdq.get_I0_Phi0 + # cf Methods.Simulation.OPdq.get_I0_Phi0 if isinstance(get_I0_Phi0, ImportError): get_I0_Phi0 = property( fget=lambda x: raise_( diff --git a/pyleecan/Classes/OPslip.py b/pyleecan/Classes/OPslip.py index 3b37b56ca..03d470fae 100644 --- a/pyleecan/Classes/OPslip.py +++ b/pyleecan/Classes/OPslip.py @@ -32,11 +32,6 @@ except ImportError as error: get_N0 = error -try: - from ..Methods.Simulation.OPslip.get_slip import get_slip -except ImportError as error: - get_slip = error - try: from ..Methods.Simulation.OPslip.get_Ud_Uq import get_Ud_Uq except ImportError as error: @@ -48,10 +43,15 @@ set_Id_Iq = error try: - from ..Methods.Output.OPslip.get_I0_Phi0 import get_I0_Phi0 + from ..Methods.Simulation.OPslip.get_I0_Phi0 import get_I0_Phi0 except ImportError as error: get_I0_Phi0 = error +try: + from ..Methods.Simulation.OPslip.get_slip import get_slip +except ImportError as error: + get_slip = error + from ._check import InitUnKnowClassError @@ -89,15 +89,6 @@ class OPslip(OP): ) else: get_N0 = get_N0 - # cf Methods.Simulation.OPslip.get_slip - if isinstance(get_slip, ImportError): - get_slip = property( - fget=lambda x: raise_( - ImportError("Can't use OPslip method get_slip: " + str(get_slip)) - ) - ) - else: - get_slip = get_slip # cf Methods.Simulation.OPslip.get_Ud_Uq if isinstance(get_Ud_Uq, ImportError): get_Ud_Uq = property( @@ -116,7 +107,7 @@ class OPslip(OP): ) else: set_Id_Iq = set_Id_Iq - # cf Methods.Output.OPslip.get_I0_Phi0 + # cf Methods.Simulation.OPslip.get_I0_Phi0 if isinstance(get_I0_Phi0, ImportError): get_I0_Phi0 = property( fget=lambda x: raise_( @@ -125,6 +116,15 @@ class OPslip(OP): ) else: get_I0_Phi0 = get_I0_Phi0 + # cf Methods.Simulation.OPslip.get_slip + if isinstance(get_slip, ImportError): + get_slip = property( + fget=lambda x: raise_( + ImportError("Can't use OPslip method get_slip: " + str(get_slip)) + ) + ) + else: + get_slip = get_slip # save and copy methods are available in all object save = save copy = copy From 2a41f66625277afaa26b4ff656464d11c155450d Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 14 Oct 2021 16:47:43 +0200 Subject: [PATCH 116/167] [CC] do not show fig --- Tests/Methods/Simulation/test_InVoltage_PWM.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Methods/Simulation/test_InVoltage_PWM.py b/Tests/Methods/Simulation/test_InVoltage_PWM.py index a2f812cc7..d309826a8 100644 --- a/Tests/Methods/Simulation/test_InVoltage_PWM.py +++ b/Tests/Methods/Simulation/test_InVoltage_PWM.py @@ -8,7 +8,7 @@ from pyleecan.Functions.Plot import dict_2D from Tests import save_plot_path as save_path -is_show_fig = True +is_show_fig = False def test_InVoltage_PWM(): From 710df15446d02d03690e546c9ef742449be27b65 Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 14 Oct 2021 16:48:00 +0200 Subject: [PATCH 117/167] [BC] use new get_Id_Iq method --- Tests/Methods/Simulation/test_InCurrent_meth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Methods/Simulation/test_InCurrent_meth.py b/Tests/Methods/Simulation/test_InCurrent_meth.py index aab7ace5b..4401e9ae7 100644 --- a/Tests/Methods/Simulation/test_InCurrent_meth.py +++ b/Tests/Methods/Simulation/test_InCurrent_meth.py @@ -281,8 +281,8 @@ def test_InputCurrent_DQ(self, test_dict): ) out = Output(simu=test_obj) test_obj.input.gen_input() - assert out.elec.Id_ref == pytest.approx(test_dict["Id"], abs=0.01) - assert out.elec.Iq_ref == pytest.approx(test_dict["Iq"], abs=0.01) + assert out.elec.OP.get_Id_Iq()["Id"] == pytest.approx(test_dict["Id"], abs=0.01) + assert out.elec.OP.get_Id_Iq()["Iq"] == pytest.approx(test_dict["Iq"], abs=0.01) # Plot 3-phase current function of time # out.plot_2D_Data("elec.Is", "time", "phase", is_show_fig=False) From 1d655b94d13ef49bb471ec6f5c925e7fd6ef53cf Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 14 Oct 2021 16:48:43 +0200 Subject: [PATCH 118/167] [BC] for offline mmf unit computation --- pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py | 7 +++++-- pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py | 4 ++-- pyleecan/Methods/Output/OutElec/get_I_fund.py | 2 +- pyleecan/Methods/Output/Output/getter/get_fund_harm.py | 2 +- pyleecan/Methods/Output/Output/getter/get_rot_dir.py | 4 +++- pyleecan/Methods/Simulation/Input/comp_axis_time.py | 6 +++--- pyleecan/Methods/Simulation/InputCurrent/gen_input.py | 2 +- 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py index 39cd4916d..89279b2fc 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py @@ -6,7 +6,7 @@ from ....Functions.Load.import_class import import_class -def comp_mmf_unit(self, Na, Nt, felec=1, rot_dir=-1): +def comp_mmf_unit(self, Na=None, Nt=None, felec=1, rot_dir=-1, N0=1000): """Compute the winding unit magnetomotive force Parameters @@ -42,8 +42,11 @@ def comp_mmf_unit(self, Na, Nt, felec=1, rot_dir=-1): # Get stator winding number of phases qs = self.winding.qs + # Output = import_class("pyleecan.Classes", "Output") + # OutElec = import_class("pyleecan.Classes", "OutElec") + # Simu1 = import_class("pyleecan.Classes", "Simu1") InputCurrent = import_class("pyleecan.Classes", "InputCurrent") - input = InputCurrent(Na_tot=Na, Nt_tot=Nt, felec=felec, rot_dir=rot_dir) + input = InputCurrent(Na_tot=Na, Nt_tot=Nt, felec=felec, rot_dir=rot_dir, N0=N0) axes_dict = input.comp_axes( axes_list=["time", "angle", "phase_S", "phase_R"], diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py b/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py index e38baf3e4..490a61c41 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py @@ -1,7 +1,7 @@ from numpy import sign -def comp_rot_dir(self): +def comp_rot_dir(self, N0=1000, felec=1): """Compute the rotation direction of the fundamental magnetic field induced by the winding WARNING: rot_dir = -1 to have positive rotor rotating direction, i.e. rotor position moves towards positive angle @@ -19,7 +19,7 @@ def comp_rot_dir(self): # Compute unit mmf # 20 points per pole over time and space is enough to capture rotating direction of fundamental mmf - MMF, _ = self.comp_mmf_unit(Nt=20 * p, Na=20 * p) + MMF, _ = self.comp_mmf_unit(Nt=20 * p, Na=20 * p, felec=felec, N0=N0) # Extract fundamental from unit mmf result_p = MMF.get_harmonics(1, "freqs>0", "wavenumber=" + str(p)) diff --git a/pyleecan/Methods/Output/OutElec/get_I_fund.py b/pyleecan/Methods/Output/OutElec/get_I_fund.py index c61e875e1..3c6cb0d3f 100644 --- a/pyleecan/Methods/Output/OutElec/get_I_fund.py +++ b/pyleecan/Methods/Output/OutElec/get_I_fund.py @@ -19,7 +19,7 @@ def get_I_fund(self, Time=None): angle_elec = Time.get_values(is_smallestperiod=True, normalization="angle_elec") qs = self.parent.simu.machine.stator.winding.qs stator_label = "phase_" + self.parent.simu.machine.stator.get_label() - felec = self.felec + felec = self.OP.get_felec() Phase = self.axes_dict[stator_label] diff --git a/pyleecan/Methods/Output/Output/getter/get_fund_harm.py b/pyleecan/Methods/Output/Output/getter/get_fund_harm.py index ea6af2f21..6dd75cced 100644 --- a/pyleecan/Methods/Output/Output/getter/get_fund_harm.py +++ b/pyleecan/Methods/Output/Output/getter/get_fund_harm.py @@ -31,7 +31,7 @@ def get_fund_harm(self, data): p = self.simu.machine.get_pole_pair_number() # Get electrical fundamental frequency - f_elec = self.simu.input.comp_felec() + f_elec = self.elec.OP.get_felec() # Loop on axes to express the fundamental harmonic of the Data object # including normalizations diff --git a/pyleecan/Methods/Output/Output/getter/get_rot_dir.py b/pyleecan/Methods/Output/Output/getter/get_rot_dir.py index 44132823d..9586359eb 100644 --- a/pyleecan/Methods/Output/Output/getter/get_rot_dir.py +++ b/pyleecan/Methods/Output/Output/getter/get_rot_dir.py @@ -29,7 +29,9 @@ def get_rot_dir(self): ): rot_dir = self.simu.input.rot_dir else: # Compute from stator winding - rot_dir = self.simu.machine.stator.comp_rot_dir() + rot_dir = self.simu.machine.stator.comp_rot_dir( + N0=self.elec.OP.get_N0(), felec=self.elec.OP.get_felec() + ) self.geo.rot_dir = rot_dir return rot_dir diff --git a/pyleecan/Methods/Simulation/Input/comp_axis_time.py b/pyleecan/Methods/Simulation/Input/comp_axis_time.py index 0fa0fb9f7..a67c89331 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axis_time.py +++ b/pyleecan/Methods/Simulation/Input/comp_axis_time.py @@ -29,17 +29,17 @@ def comp_axis_time(self, p, per_t, is_antiper_t, Time_in=None, output=None): logger = self.get_logger() - # Get electrical fundamental frequency - f_elec = self.comp_felec(p=p) - # Get magnetic field rotation direction if output is not None: rot_dir = output.get_rot_dir() + # Get electrical fundamental frequency + f_elec = output.elec.OP.get_felec() else: if self.rot_dir not in [-1, 1]: self.rot_dir = -1 logger.debug("Enforcing input.rot_dir=-1") rot_dir = self.rot_dir + f_elec = self.felec # Setup normalizations for time and angle axes norm_time = { diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index 0c173b2ee..b0174b38a 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -71,7 +71,7 @@ def gen_input(self): Time.get_values(is_oneperiod=False, normalization="angle_elec"), is_dqh_rms=True, ) - outelec.OP.set_Id_Iq(mean(Idq[:, 0], mean(Idq[:, 1]))) + outelec.OP.set_Id_Iq(mean(Idq[:, 0]), mean(Idq[:, 1])) # Load and check Ir is needed if qr > 0: From 11c92a3fb00fb5f8e69b06640b8fe155163732be Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 14 Oct 2021 17:17:00 +0200 Subject: [PATCH 119/167] [WiP] rework Electrical module run --- .../Generator/ClassesRef/Output/OutElec.csv | 2 ++ pyleecan/Methods/Output/OutElec/store.py | 4 +++- .../Simulation/EEC_PMSM/comp_joule_losses.py | 13 +++++------ .../Simulation/Electrical/comp_power.py | 17 ++++++-------- .../Simulation/Electrical/comp_torque.py | 14 +++++------- pyleecan/Methods/Simulation/Electrical/run.py | 22 +++++++++++++------ 6 files changed, 39 insertions(+), 33 deletions(-) diff --git a/pyleecan/Generator/ClassesRef/Output/OutElec.csv b/pyleecan/Generator/ClassesRef/Output/OutElec.csv index 608fb2aae..6e2e3da5e 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutElec.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutElec.csv @@ -10,3 +10,5 @@ Us,V,Stator voltage as a function of time (each column correspond to one phase), internal,-,OutInternal object containg outputs related to a specific model,0,OutInternal,None,,,,,,,,, Us_harm,V,Harmonic stator voltage as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,,,, OP,-,Operating Point,,OP,None,,,,,,,,, +Pem_av_ref,W,Theorical Average Electromagnetic Power,1,float,None,,,,,,,,, +Tem_av_ref,N.m,Theorical Average Electromagnetic torque,,float,None,,,,,,,,, diff --git a/pyleecan/Methods/Output/OutElec/store.py b/pyleecan/Methods/Output/OutElec/store.py index d334f59e5..43f075dc7 100644 --- a/pyleecan/Methods/Output/OutElec/store.py +++ b/pyleecan/Methods/Output/OutElec/store.py @@ -20,4 +20,6 @@ def store(self, out_dict, axes_dict): """ - # TODO + self.Pj_losses = out_dict["Pj_losses"] + self.Tem_av_ref = out_dict["Tem_av_ref"] + self.Pem_av_ref = out_dict["Pem_av_ref"] diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/comp_joule_losses.py b/pyleecan/Methods/Simulation/EEC_PMSM/comp_joule_losses.py index e1fb09b06..ff78e890c 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/comp_joule_losses.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/comp_joule_losses.py @@ -1,23 +1,22 @@ # -*- coding: utf-8 -*- -def comp_joule_losses(self, output): +def comp_joule_losses(self, out_dict, machine): """Compute the electrical Joule losses Parameters ---------- self : Electrical an Electrical object - output : Output - an Output object + out_dict : dict + Dict containing all magnetic quantities that have been calculated in comp_parameters of EEC """ - qs = output.simu.machine.stator.winding.qs - I_dict = self.OP.get_Id_Iq() - Id, Iq = I_dict["Id"], I_dict["Iq"] + qs = machine.stator.winding.qs + Id, Iq = self.eec.parameters["Id"], self.eec.parameters["Iq"] R = self.parameters["R20"] # Id and Iq are in RMS Pj_losses = qs * R * (Id ** 2 + Iq ** 2) - output.elec.Pj_losses = Pj_losses + out_dict["Pj_losses"] = Pj_losses diff --git a/pyleecan/Methods/Simulation/Electrical/comp_power.py b/pyleecan/Methods/Simulation/Electrical/comp_power.py index 439f5d5cf..5615b62de 100644 --- a/pyleecan/Methods/Simulation/Electrical/comp_power.py +++ b/pyleecan/Methods/Simulation/Electrical/comp_power.py @@ -1,25 +1,22 @@ # -*- coding: utf-8 -*- -def comp_power(self, output): +def comp_power(self, out_dict, machine): """Compute the electrical average power Parameters ---------- self : Electrical an Electrical object - output : Output - an Output object + out_dict : dict + Dict containing all magnetic quantities that have been calculated in comp_parameters of EEC """ - qs = output.simu.machine.stator.winding.qs - I_dict = output.elec.OP.get_Id_Iq() - Id, Iq = I_dict["Id"], I_dict["Iq"] - - U_dict = output.elec.OP.get_Ud_Uq() - Ud, Uq = U_dict["Ud"], U_dict["Uq"] + qs = machine.stator.winding.qs + Id, Iq = self.eec.parameters["Id"], self.eec.parameters["Iq"] + Ud, Uq = self.eec.parameters["Ud"], self.eec.parameters["Uq"] # All quantities are in RMS Pem_av_ref = qs * (Ud * Id + Uq * Iq) - output.elec.Pem_av_ref = Pem_av_ref + out_dict["Pem_av_ref"] = Pem_av_ref diff --git a/pyleecan/Methods/Simulation/Electrical/comp_torque.py b/pyleecan/Methods/Simulation/Electrical/comp_torque.py index 7ad4f7292..93fb014cd 100644 --- a/pyleecan/Methods/Simulation/Electrical/comp_torque.py +++ b/pyleecan/Methods/Simulation/Electrical/comp_torque.py @@ -3,23 +3,21 @@ from numpy import pi -def comp_torque(self, output): +def comp_torque(self, out_dict, N0): """Compute the electrical average torque Parameters ---------- self : Electrical an Electrical object - output : Output - an Output object + out_dict : dict + Dict containing all magnetic quantities that have been calculated in comp_parameters of EEC """ - N0 = output.elec.OP.get_N0() omega = 2 * pi * N0 / 60 - P = output.elec.Pem_av_ref - losses = output.elec.Pj_losses # TODO update since there may also be other losses + P = out_dict["Pem_av_ref"] - Tem_av_ref = (P - losses) / omega + Tem_av_ref = (P - out_dict["Pj_losses"]) / omega - output.elec.Tem_av_ref = Tem_av_ref + out_dict["Tem_av_ref"] = Tem_av_ref diff --git a/pyleecan/Methods/Simulation/Electrical/run.py b/pyleecan/Methods/Simulation/Electrical/run.py index d48263195..c4394e505 100644 --- a/pyleecan/Methods/Simulation/Electrical/run.py +++ b/pyleecan/Methods/Simulation/Electrical/run.py @@ -47,9 +47,9 @@ def run(self): # Generate drive # self.eec.gen_drive(output) - self.eec.parameters["U0_ref"] = output.elec.U0_ref - self.eec.parameters["Ud_ref"] = output.elec.Ud_ref - self.eec.parameters["Uq_ref"] = output.elec.Uq_ref + # self.eec.parameters["U0_ref"] = output.elec.U0_ref + # self.eec.parameters["Ud_ref"] = output.elec.OP.get_Ud_Uq()["Ud"] + # self.eec.parameters["Uq_ref"] = output.elec.OP.get_Ud_Uq()["Uq"] # Ud_ref, Ud_ref, fe => OP_fund # for harm @@ -65,14 +65,22 @@ def run(self): # Solve the electrical equivalent circuit out_dict = self.eec.solve_EEC(output) + # Solve for each harmonic in case of Us_harm + if output.elec.Us_harm is not None: + freqs = output.elec.Us_harm.get_axes("freqs")[0].get_values().tolist() + for f in freqs: + out_dict_harm = self.eec.solve_EEC(output) + else: + out_dict_harm = None + # Compute losses due to Joule effects - out_dict = self.eec.comp_joule_losses(output) + out_dict = self.eec.comp_joule_losses(out_dict, machine) # Compute electromagnetic power - out_dict = self.comp_power(output) + out_dict = self.comp_power(out_dict, machine) # Compute torque - out_dict = self.comp_torque(output) + self.comp_torque(out_dict, output.elec.OP.get_N0()) # Store electrical quantities contained in out_dict in OutElec, as Data object if necessary - out_dict.store(out_dict) + output.elec.store(out_dict, out_dict_harm) From c09fffaa8fc7ad15ec068115e6ea890f259180e7 Mon Sep 17 00:00:00 2001 From: helene-t Date: Fri, 15 Oct 2021 10:22:58 +0200 Subject: [PATCH 120/167] [WiP] new analytical EEC --- .../Electrical/test_EEC_ELUT_PMSM.py | 33 +- pyleecan/Classes/Class_Dict.json | 68 ++++ pyleecan/Classes/EEC_ANL.py | 295 ++++++++++++++++++ pyleecan/Classes/OutElec.py | 60 ++++ pyleecan/Classes/import_all.py | 1 + pyleecan/Functions/load_switch.py | 1 + .../ClassesRef/Simulation/EEC_ANL.csv | 4 + pyleecan/Methods/Output/OutElec/store.py | 33 +- .../Methods/Simulation/EEC_ANL/__init__.py | 1 + .../Simulation/EEC_ANL/comp_joule_losses.py | 22 ++ .../Simulation/EEC_ANL/comp_parameters.py | 72 +++++ .../Methods/Simulation/EEC_ANL/solve_EEC.py | 42 +++ .../Simulation/EEC_PMSM/comp_joule_losses.py | 4 +- .../Simulation/EEC_PMSM/comp_parameters.py | 19 +- .../Methods/Simulation/EEC_PMSM/solve_EEC.py | 36 ++- .../Simulation/EEC_SCIM/comp_joule_losses.py | 18 +- .../Simulation/EEC_SCIM/comp_parameters.py | 10 +- .../Simulation/Electrical/comp_power.py | 4 +- pyleecan/Methods/Simulation/Electrical/run.py | 34 +- pyleecan/Methods/Simulation/LUTdq/get_Ld.py | 8 +- pyleecan/Methods/Simulation/LUTdq/get_Lq.py | 8 +- .../Simulation/LUTdq/get_param_dict.py | 26 +- pyleecan/Methods/Simulation/VarSimu/run.py | 6 +- 23 files changed, 725 insertions(+), 80 deletions(-) create mode 100644 pyleecan/Classes/EEC_ANL.py create mode 100644 pyleecan/Generator/ClassesRef/Simulation/EEC_ANL.csv create mode 100644 pyleecan/Methods/Simulation/EEC_ANL/__init__.py create mode 100644 pyleecan/Methods/Simulation/EEC_ANL/comp_joule_losses.py create mode 100644 pyleecan/Methods/Simulation/EEC_ANL/comp_parameters.py create mode 100644 pyleecan/Methods/Simulation/EEC_ANL/solve_EEC.py diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py index 1e2b1cb46..37d5e20cc 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py @@ -13,11 +13,15 @@ from pyleecan.Classes.PostLUT import PostLUT from pyleecan.Classes.DataKeeper import DataKeeper from pyleecan.Classes.ImportMatrixXls import ImportMatrixXls +from pyleecan.Classes.Electrical import Electrical +from pyleecan.Classes.EEC_ANL import EEC_ANL from pyleecan.Functions.load import load from pyleecan.Functions.Plot import dict_2D from pyleecan.definitions import DATA_DIR +is_show_fig = False + @pytest.mark.long_5s @pytest.mark.MagFEMM @@ -25,10 +29,12 @@ @pytest.mark.IPMSM @pytest.mark.periodicity @pytest.mark.skip(reason="Work in progress") -def test_LUT_PMSM(): +def test_EEC_ELUT_PMSM(): """Validation of the PMSM Electrical Equivalent Circuit with the Prius machine""" Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) + + # Generate ELUT simu = Simu1(name="test_LUT_PMSM", machine=Toyota_Prius) # Definition of the input @@ -69,21 +75,32 @@ def test_LUT_PMSM(): out = simu.run() + ELUT = out.simu.var_simu.postproc_list[0].LUT + + # Simu with EEC using ELUT + simu_EEC = Simu1(name="test_LUT_PMSM", machine=Toyota_Prius) + + # Definition of the input + simu_EEC.input = InputCurrent( + N0=1000, Nt_tot=8 * 10, Na_tot=8 * 200, Id_ref=50, Iq_ref=50 + ) + + simu_EEC.elec = Electrical(eec=EEC_ANL(), ELUT_enforced=ELUT) + + out_EEC = simu_EEC.run() + # Plot 3-phase current function of time out.mag.Phi_wind_stator.plot_2D_Data( "time", "phase[]", - # save_path=join(save_path, "EEC_FEMM_IPMSM_currents.png"), - # is_show_fig=False, + save_path=join(save_path, "EEC_FEMM_IPMSM_currents.png"), + is_show_fig=is_show_fig, **dict_2D ) - out.simu.var_simu.postproc_list[0].LUT.get_Lmd(Id=50, Iq=50) - out.simu.var_simu.postproc_list[0].LUT.get_Phidqh_mag_harm() - - return out + return out, out_EEC # To run it without pytest if __name__ == "__main__": - out = test_LUT_PMSM() + out, out_EEC = test_EEC_ELUT_PMSM() diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 7bc40c050..950868700 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -1009,6 +1009,7 @@ } ], "daughters": [ + "EEC_ANL", "EEC_LSRPM", "EEC_PMSM", "EEC_SCIM" @@ -1022,6 +1023,55 @@ "path": "pyleecan/Generator/ClassesRef/Simulation/EEC.csv", "properties": [] }, + "EEC_ANL": { + "constants": [ + { + "name": "VERSION", + "value": "1" + } + ], + "daughters": [], + "desc": "Electric module: Analytical Electrical Equivalent Circuit", + "is_internal": false, + "methods": [ + "comp_parameters", + "solve_EEC", + "gen_drive" + ], + "mother": "EEC", + "name": "EEC_ANL", + "package": "Simulation", + "path": "pyleecan/Generator/ClassesRef/Simulation/EEC_ANL.csv", + "properties": [ + { + "desc": "Parameters of the EEC: computed if empty, or enforced", + "max": "", + "min": "", + "name": "parameters", + "type": "dict", + "unit": "-", + "value": {} + }, + { + "desc": "Frequency", + "max": "", + "min": "", + "name": "freq0", + "type": "float", + "unit": "Hz", + "value": null + }, + { + "desc": "Drive", + "max": "", + "min": "", + "name": "drive", + "type": "Drive", + "unit": "-", + "value": null + } + ] + }, "EEC_LSRPM": { "constants": [ { @@ -8242,6 +8292,24 @@ "type": "OP", "unit": "-", "value": null + }, + { + "desc": "Theorical Average Electromagnetic Power", + "max": "", + "min": "", + "name": "Pem_av_ref", + "type": "float", + "unit": "W", + "value": null + }, + { + "desc": "Theorical Average Electromagnetic torque", + "max": "", + "min": "", + "name": "Tem_av_ref", + "type": "float", + "unit": "N.m", + "value": null } ] }, diff --git a/pyleecan/Classes/EEC_ANL.py b/pyleecan/Classes/EEC_ANL.py new file mode 100644 index 000000000..b66ff2179 --- /dev/null +++ b/pyleecan/Classes/EEC_ANL.py @@ -0,0 +1,295 @@ +# -*- coding: utf-8 -*- +# File generated according to Generator/ClassesRef/Simulation/EEC_ANL.csv +# WARNING! All changes made in this file will be lost! +"""Method code available at https://github.com/Eomys/pyleecan/tree/master/pyleecan/Methods/Simulation/EEC_ANL +""" + +from os import linesep +from sys import getsizeof +from logging import getLogger +from ._check import check_var, raise_ +from ..Functions.get_logger import get_logger +from ..Functions.save import save +from ..Functions.copy import copy +from ..Functions.load import load_init_dict +from ..Functions.Load.import_class import import_class +from .EEC import EEC + +# Import all class method +# Try/catch to remove unnecessary dependencies in unused method +try: + from ..Methods.Simulation.EEC_ANL.comp_parameters import comp_parameters +except ImportError as error: + comp_parameters = error + +try: + from ..Methods.Simulation.EEC_ANL.solve_EEC import solve_EEC +except ImportError as error: + solve_EEC = error + +try: + from ..Methods.Simulation.EEC_ANL.gen_drive import gen_drive +except ImportError as error: + gen_drive = error + + +from ._check import InitUnKnowClassError +from .Drive import Drive + + +class EEC_ANL(EEC): + """Electric module: Analytical Electrical Equivalent Circuit""" + + VERSION = 1 + + # Check ImportError to remove unnecessary dependencies in unused method + # cf Methods.Simulation.EEC_ANL.comp_parameters + if isinstance(comp_parameters, ImportError): + comp_parameters = property( + fget=lambda x: raise_( + ImportError( + "Can't use EEC_ANL method comp_parameters: " + str(comp_parameters) + ) + ) + ) + else: + comp_parameters = comp_parameters + # cf Methods.Simulation.EEC_ANL.solve_EEC + if isinstance(solve_EEC, ImportError): + solve_EEC = property( + fget=lambda x: raise_( + ImportError("Can't use EEC_ANL method solve_EEC: " + str(solve_EEC)) + ) + ) + else: + solve_EEC = solve_EEC + # cf Methods.Simulation.EEC_ANL.gen_drive + if isinstance(gen_drive, ImportError): + gen_drive = property( + fget=lambda x: raise_( + ImportError("Can't use EEC_ANL method gen_drive: " + str(gen_drive)) + ) + ) + else: + gen_drive = gen_drive + # save and copy methods are available in all object + save = save + copy = copy + # get_logger method is available in all object + get_logger = get_logger + + def __init__( + self, parameters=-1, freq0=None, drive=None, init_dict=None, init_str=None + ): + """Constructor of the class. Can be use in three ways : + - __init__ (arg1 = 1, arg3 = 5) every parameters have name and default values + for pyleecan type, -1 will call the default constructor + - __init__ (init_dict = d) d must be a dictionary with property names as keys + - __init__ (init_str = s) s must be a string + s is the file path to load + + ndarray or list can be given for Vector and Matrix + object or dict can be given for pyleecan Object""" + + if init_str is not None: # Load from a file + init_dict = load_init_dict(init_str)[1] + if init_dict is not None: # Initialisation by dict + assert type(init_dict) is dict + # Overwrite default value with init_dict content + if "parameters" in list(init_dict.keys()): + parameters = init_dict["parameters"] + if "freq0" in list(init_dict.keys()): + freq0 = init_dict["freq0"] + if "drive" in list(init_dict.keys()): + drive = init_dict["drive"] + # Set the properties (value check and convertion are done in setter) + self.parameters = parameters + self.freq0 = freq0 + self.drive = drive + # Call EEC init + super(EEC_ANL, self).__init__() + # The class is frozen (in EEC init), for now it's impossible to + # add new properties + + def __str__(self): + """Convert this object in a readeable string (for print)""" + + EEC_ANL_str = "" + # Get the properties inherited from EEC + EEC_ANL_str += super(EEC_ANL, self).__str__() + EEC_ANL_str += "parameters = " + str(self.parameters) + linesep + EEC_ANL_str += "freq0 = " + str(self.freq0) + linesep + if self.drive is not None: + tmp = self.drive.__str__().replace(linesep, linesep + "\t").rstrip("\t") + EEC_ANL_str += "drive = " + tmp + else: + EEC_ANL_str += "drive = None" + linesep + linesep + return EEC_ANL_str + + def __eq__(self, other): + """Compare two objects (skip parent)""" + + if type(other) != type(self): + return False + + # Check the properties inherited from EEC + if not super(EEC_ANL, self).__eq__(other): + return False + if other.parameters != self.parameters: + return False + if other.freq0 != self.freq0: + return False + if other.drive != self.drive: + return False + return True + + def compare(self, other, name="self", ignore_list=None): + """Compare two objects and return list of differences""" + + if ignore_list is None: + ignore_list = list() + if type(other) != type(self): + return ["type(" + name + ")"] + diff_list = list() + + # Check the properties inherited from EEC + diff_list.extend(super(EEC_ANL, self).compare(other, name=name)) + if other._parameters != self._parameters: + diff_list.append(name + ".parameters") + if other._freq0 != self._freq0: + diff_list.append(name + ".freq0") + if (other.drive is None and self.drive is not None) or ( + other.drive is not None and self.drive is None + ): + diff_list.append(name + ".drive None mismatch") + elif self.drive is not None: + diff_list.extend(self.drive.compare(other.drive, name=name + ".drive")) + # Filter ignore differences + diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) + return diff_list + + def __sizeof__(self): + """Return the size in memory of the object (including all subobject)""" + + S = 0 # Full size of the object + + # Get size of the properties inherited from EEC + S += super(EEC_ANL, self).__sizeof__() + if self.parameters is not None: + for key, value in self.parameters.items(): + S += getsizeof(value) + getsizeof(key) + S += getsizeof(self.freq0) + S += getsizeof(self.drive) + return S + + def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): + """ + Convert this object in a json serializable dict (can be use in __init__). + type_handle_ndarray: int + How to handle ndarray (0: tolist, 1: copy, 2: nothing) + keep_function : bool + True to keep the function object, else return str + Optional keyword input parameter is for internal use only + and may prevent json serializability. + """ + + # Get the properties inherited from EEC + EEC_ANL_dict = super(EEC_ANL, self).as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) + EEC_ANL_dict["parameters"] = ( + self.parameters.copy() if self.parameters is not None else None + ) + EEC_ANL_dict["freq0"] = self.freq0 + if self.drive is None: + EEC_ANL_dict["drive"] = None + else: + EEC_ANL_dict["drive"] = self.drive.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) + # The class name is added to the dict for deserialisation purpose + # Overwrite the mother class name + EEC_ANL_dict["__class__"] = "EEC_ANL" + return EEC_ANL_dict + + def _set_None(self): + """Set all the properties to None (except pyleecan object)""" + + self.parameters = None + self.freq0 = None + if self.drive is not None: + self.drive._set_None() + # Set to None the properties inherited from EEC + super(EEC_ANL, self)._set_None() + + def _get_parameters(self): + """getter of parameters""" + return self._parameters + + def _set_parameters(self, value): + """setter of parameters""" + if type(value) is int and value == -1: + value = dict() + check_var("parameters", value, "dict") + self._parameters = value + + parameters = property( + fget=_get_parameters, + fset=_set_parameters, + doc=u"""Parameters of the EEC: computed if empty, or enforced + + :Type: dict + """, + ) + + def _get_freq0(self): + """getter of freq0""" + return self._freq0 + + def _set_freq0(self, value): + """setter of freq0""" + check_var("freq0", value, "float") + self._freq0 = value + + freq0 = property( + fget=_get_freq0, + fset=_set_freq0, + doc=u"""Frequency + + :Type: float + """, + ) + + def _get_drive(self): + """getter of drive""" + return self._drive + + def _set_drive(self, value): + """setter of drive""" + if isinstance(value, str): # Load from file + value = load_init_dict(value)[1] + if isinstance(value, dict) and "__class__" in value: + class_obj = import_class( + "pyleecan.Classes", value.get("__class__"), "drive" + ) + value = class_obj(init_dict=value) + elif type(value) is int and value == -1: # Default constructor + value = Drive() + check_var("drive", value, "Drive") + self._drive = value + + if self._drive is not None: + self._drive.parent = self + + drive = property( + fget=_get_drive, + fset=_set_drive, + doc=u"""Drive + + :Type: Drive + """, + ) diff --git a/pyleecan/Classes/OutElec.py b/pyleecan/Classes/OutElec.py index 4e1b4af9b..9ce098960 100644 --- a/pyleecan/Classes/OutElec.py +++ b/pyleecan/Classes/OutElec.py @@ -133,6 +133,8 @@ def __init__( internal=None, Us_harm=None, OP=None, + Pem_av_ref=None, + Tem_av_ref=None, init_dict=None, init_str=None, ): @@ -173,6 +175,10 @@ def __init__( Us_harm = init_dict["Us_harm"] if "OP" in list(init_dict.keys()): OP = init_dict["OP"] + if "Pem_av_ref" in list(init_dict.keys()): + Pem_av_ref = init_dict["Pem_av_ref"] + if "Tem_av_ref" in list(init_dict.keys()): + Tem_av_ref = init_dict["Tem_av_ref"] # Set the properties (value check and convertion are done in setter) self.parent = None self.axes_dict = axes_dict @@ -186,6 +192,8 @@ def __init__( self.internal = internal self.Us_harm = Us_harm self.OP = OP + self.Pem_av_ref = Pem_av_ref + self.Tem_av_ref = Tem_av_ref # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -225,6 +233,8 @@ def __str__(self): OutElec_str += "OP = " + tmp else: OutElec_str += "OP = None" + linesep + linesep + OutElec_str += "Pem_av_ref = " + str(self.Pem_av_ref) + linesep + OutElec_str += "Tem_av_ref = " + str(self.Tem_av_ref) + linesep return OutElec_str def __eq__(self, other): @@ -254,6 +264,10 @@ def __eq__(self, other): return False if other.OP != self.OP: return False + if other.Pem_av_ref != self.Pem_av_ref: + return False + if other.Tem_av_ref != self.Tem_av_ref: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -327,6 +341,10 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".OP None mismatch") elif self.OP is not None: diff_list.extend(self.OP.compare(other.OP, name=name + ".OP")) + if other._Pem_av_ref != self._Pem_av_ref: + diff_list.append(name + ".Pem_av_ref") + if other._Tem_av_ref != self._Tem_av_ref: + diff_list.append(name + ".Tem_av_ref") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -348,6 +366,8 @@ def __sizeof__(self): S += getsizeof(self.internal) S += getsizeof(self.Us_harm) S += getsizeof(self.OP) + S += getsizeof(self.Pem_av_ref) + S += getsizeof(self.Tem_av_ref) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -439,6 +459,8 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): keep_function=keep_function, **kwargs ) + OutElec_dict["Pem_av_ref"] = self.Pem_av_ref + OutElec_dict["Tem_av_ref"] = self.Tem_av_ref # The class name is added to the dict for deserialisation purpose OutElec_dict["__class__"] = "OutElec" return OutElec_dict @@ -459,6 +481,8 @@ def _set_None(self): self.Us_harm = None if self.OP is not None: self.OP._set_None() + self.Pem_av_ref = None + self.Tem_av_ref = None def _get_axes_dict(self): """getter of axes_dict""" @@ -735,3 +759,39 @@ def _set_OP(self, value): :Type: OP """, ) + + def _get_Pem_av_ref(self): + """getter of Pem_av_ref""" + return self._Pem_av_ref + + def _set_Pem_av_ref(self, value): + """setter of Pem_av_ref""" + check_var("Pem_av_ref", value, "float") + self._Pem_av_ref = value + + Pem_av_ref = property( + fget=_get_Pem_av_ref, + fset=_set_Pem_av_ref, + doc=u"""Theorical Average Electromagnetic Power + + :Type: float + """, + ) + + def _get_Tem_av_ref(self): + """getter of Tem_av_ref""" + return self._Tem_av_ref + + def _set_Tem_av_ref(self, value): + """setter of Tem_av_ref""" + check_var("Tem_av_ref", value, "float") + self._Tem_av_ref = value + + Tem_av_ref = property( + fget=_get_Tem_av_ref, + fset=_set_Tem_av_ref, + doc=u"""Theorical Average Electromagnetic torque + + :Type: float + """, + ) diff --git a/pyleecan/Classes/import_all.py b/pyleecan/Classes/import_all.py index 15acb4478..d9e2da13c 100644 --- a/pyleecan/Classes/import_all.py +++ b/pyleecan/Classes/import_all.py @@ -24,6 +24,7 @@ from ..Classes.Drive import Drive from ..Classes.DriveWave import DriveWave from ..Classes.EEC import EEC +from ..Classes.EEC_ANL import EEC_ANL from ..Classes.EEC_LSRPM import EEC_LSRPM from ..Classes.EEC_PMSM import EEC_PMSM from ..Classes.EEC_SCIM import EEC_SCIM diff --git a/pyleecan/Functions/load_switch.py b/pyleecan/Functions/load_switch.py index 47b97d88e..ea3d3e77a 100644 --- a/pyleecan/Functions/load_switch.py +++ b/pyleecan/Functions/load_switch.py @@ -26,6 +26,7 @@ "Drive": Drive, "DriveWave": DriveWave, "EEC": EEC, + "EEC_ANL": EEC_ANL, "EEC_LSRPM": EEC_LSRPM, "EEC_PMSM": EEC_PMSM, "EEC_SCIM": EEC_SCIM, diff --git a/pyleecan/Generator/ClassesRef/Simulation/EEC_ANL.csv b/pyleecan/Generator/ClassesRef/Simulation/EEC_ANL.csv new file mode 100644 index 000000000..c3d342ed3 --- /dev/null +++ b/pyleecan/Generator/ClassesRef/Simulation/EEC_ANL.csv @@ -0,0 +1,4 @@ +Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille +parameters,-,"Parameters of the EEC: computed if empty, or enforced",,dict,{},,,,Simulation,EEC,comp_parameters,VERSION,1,Electric module: Analytical Electrical Equivalent Circuit, +freq0,Hz,Frequency,,float,None,,,,,,solve_EEC,,,, +drive,-,Drive,,Drive,None,,,,,,gen_drive,,,, diff --git a/pyleecan/Methods/Output/OutElec/store.py b/pyleecan/Methods/Output/OutElec/store.py index 43f075dc7..a8aff0463 100644 --- a/pyleecan/Methods/Output/OutElec/store.py +++ b/pyleecan/Methods/Output/OutElec/store.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- from numpy import mean, max as np_max, min as np_min -from SciDataTool import DataTime, VectorField, Data1D +from SciDataTool import DataFreq from ....Functions.Winding.gen_phase_list import gen_name -def store(self, out_dict, axes_dict): +def store(self, out_dict, out_dict_harm, axes_dict): """Store the standard outputs of Electrical that are temporarily in out_dict as arrays into OutElec as Data object Parameters @@ -14,12 +14,39 @@ def store(self, out_dict, axes_dict): self : OutElec the OutElec object to update out_dict : dict - Dict containing all magnetic quantities that have been calculated in comp_parameters of EEC + Dict containing all electrical quantities that have been calculated in EEC + out_dict_harm : dict + Dict containing harmonic quantities that have been calculated in EEC axes_dict: {Data} Dict of axes used for electrical calculation """ + # Store Id, Iq, Ud, Uq + self.OP.Id_ref = out_dict["Id"] + self.OP.Iq_ref = out_dict["Iq"] + self.OP.Ud_ref = out_dict["Ud"] + self.OP.Uq_ref = out_dict["Uq"] + + # Compute currents + self.Is = None + self.Is = self.get_Is() + + # Compute voltage + self.Us = None + self.Us = self.get_Us() + self.Pj_losses = out_dict["Pj_losses"] self.Tem_av_ref = out_dict["Tem_av_ref"] self.Pem_av_ref = out_dict["Pem_av_ref"] + + if "Is_harm" in out_dict_harm: + # Create Data object + axes_list = self.Us_harm.get_axes() + self.Is_harm = DataFreq( + name="Harmonic stator current", + unit="A", + symbol="I_s^{harm}", + axes=axes_list, + values=out_dict_harm["Is_harm"], + ) diff --git a/pyleecan/Methods/Simulation/EEC_ANL/__init__.py b/pyleecan/Methods/Simulation/EEC_ANL/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyleecan/Methods/Simulation/EEC_ANL/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyleecan/Methods/Simulation/EEC_ANL/comp_joule_losses.py b/pyleecan/Methods/Simulation/EEC_ANL/comp_joule_losses.py new file mode 100644 index 000000000..d49b402e9 --- /dev/null +++ b/pyleecan/Methods/Simulation/EEC_ANL/comp_joule_losses.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + + +def comp_joule_losses(self, out_dict, machine): + """Compute the electrical Joule losses + + Parameters + ---------- + self : EEC_PMSM + an EEC_PMSM object + out_dict : dict + Dict containing all magnetic quantities that have been calculated in comp_parameters of EEC + """ + + qs = machine.stator.winding.qs + Id, Iq = out_dict["Id"], out_dict["Iq"] + R = self.parameters["R20"] + + # Id and Iq are in RMS + Pj_losses = qs * R * (Id ** 2 + Iq ** 2) + + out_dict["Pj_losses"] = Pj_losses diff --git a/pyleecan/Methods/Simulation/EEC_ANL/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_ANL/comp_parameters.py new file mode 100644 index 000000000..2c4cb3e2d --- /dev/null +++ b/pyleecan/Methods/Simulation/EEC_ANL/comp_parameters.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- + + +from enum import unique + + +def comp_parameters(self, machine, OP, Tsta=None, Trot=None): + """Compute the parameters dict for the equivalent electrical circuit: + resistance, inductance and back electromotive force + Parameters + ---------- + self : EEC_ANL + an EEC_ANL object + machine : Machine + a Machine object + OP : OP + an OP object + Tsta : float + Average stator temperature + Trot : float + Average rotor temperature + """ + # TODO maybe set currents to small value if I is 0 to compute inductance + + PAR = self.parameters + Cond = machine.stator.winding.conductor + I_dict = OP.get_Id_Iq() + Id_ref, Iq_ref = I_dict["Id"], I_dict["Iq"] + U_dict = OP.get_Ud_Uq() + Ud_ref, Uq_ref = U_dict["Ud"], U_dict["Uq"] + self.freq0 = OP.get_felec() + + if "R20" not in PAR: + PAR["R20"] = machine.stator.comp_resistance_wind() + + # Parameters which may vary for each simulation + is_comp_ind = False + # check for complete parameter set + # (there may be some redundancy here but it seems simplier to implement) + if not all(k in PAR for k in ("Ld", "Lq")): + is_comp_ind = True + + # check for d- and q-current (change) + if "Id" not in PAR or PAR["Id"] != Id_ref: + PAR["Id"] = Id_ref + is_comp_ind = True + + if "Iq" not in PAR or PAR["Iq"] != Iq_ref: + PAR["Iq"] = Iq_ref + is_comp_ind = True + + # check for d- and q-voltage (change) + if "Ud" not in PAR or PAR["Ud"] != Ud_ref: + PAR["Ud"] = Ud_ref + + if "Uq" not in PAR or PAR["Uq"] != Uq_ref: + PAR["Uq"] = Uq_ref + + # compute inductance if necessary + if is_comp_ind: + (phid, phiq) = self.indmag.comp_inductance( + machine=machine, Id_ref=Id_ref, Iq_ref=Iq_ref + ) + if PAR["Id"] != 0: + PAR["Ld"] = (phid - PAR["phi"]) / PAR["Id"] + else: + PAR["Ld"] = None # to have the parameters complete though + + if PAR["Iq"] != 0: + PAR["Lq"] = phiq / PAR["Iq"] + else: + PAR["Lq"] = None # to have the parameters complete though diff --git a/pyleecan/Methods/Simulation/EEC_ANL/solve_EEC.py b/pyleecan/Methods/Simulation/EEC_ANL/solve_EEC.py new file mode 100644 index 000000000..6df998511 --- /dev/null +++ b/pyleecan/Methods/Simulation/EEC_ANL/solve_EEC.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +from numpy import array, pi +from scipy.linalg import solve + + +def solve_EEC(self): + """Compute the parameters dict for the analytical equivalent electrical circuit + cf "Influence of the Number of Pole Pairs on the Audible + Noise of Inverter-Fed Induction Motors: Radial + Force Waves and Mechanical Resonances" + I. P. Tsoumas, H. Tischmacher, B. Eichinger + + Parameters + ---------- + self : EEC_ANL + an EEC_ANL object + Return + ------ + out_dict : dict + Dict containing all magnetic quantities that have been calculated in EEC + """ + + f = self.freq0 + ws = 2 * pi * f + PAR = self.parameters + + out_dict = dict() + + # Solve system + if "Ud" in PAR: # Voltage driven + out_dict["Id"] = PAR["Ud"] / (1j * ws * PAR["Ld"]) + out_dict["Iq"] = PAR["Uq"] / (1j * ws * PAR["Lq"]) + out_dict["Ud"] = PAR["Ud"] + out_dict["Uq"] = PAR["Uq"] + else: # Current Driven + out_dict["Ud"] = PAR["Id"] * (1j * ws * PAR["Ld"]) + out_dict["Uq"] = PAR["Iq"] * (1j * ws * PAR["Lq"]) + out_dict["Id"] = PAR["Id"] + out_dict["Iq"] = PAR["Iq"] + + return out_dict diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/comp_joule_losses.py b/pyleecan/Methods/Simulation/EEC_PMSM/comp_joule_losses.py index ff78e890c..d9f749929 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/comp_joule_losses.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/comp_joule_losses.py @@ -10,10 +10,12 @@ def comp_joule_losses(self, out_dict, machine): an Electrical object out_dict : dict Dict containing all magnetic quantities that have been calculated in comp_parameters of EEC + machine : Machine + a Machine object """ qs = machine.stator.winding.qs - Id, Iq = self.eec.parameters["Id"], self.eec.parameters["Iq"] + Id, Iq = out_dict["Id"], out_dict["Iq"] R = self.parameters["R20"] # Id and Iq are in RMS diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py index 93b09655c..3a729a037 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py @@ -11,8 +11,14 @@ def comp_parameters(self, machine, OP, Tsta=None, Trot=None): ---------- self : EEC_PMSM an EEC_PMSM object - output : Output - an Output object + machine : Machine + a Machine object + OP : OP + an OP object + Tsta : float + Average stator temperature + Trot : float + Average rotor temperature """ # TODO maybe set currents to small value if I is 0 to compute inductance @@ -23,6 +29,8 @@ def comp_parameters(self, machine, OP, Tsta=None, Trot=None): Cond = machine.stator.winding.conductor I_dict = OP.get_Id_Iq() Id_ref, Iq_ref = I_dict["Id"], I_dict["Iq"] + U_dict = OP.get_Ud_Uq() + Ud_ref, Uq_ref = U_dict["Ud"], U_dict["Uq"] felec = OP.get_felec() # compute skin_effect @@ -51,6 +59,13 @@ def comp_parameters(self, machine, OP, Tsta=None, Trot=None): PAR["Iq"] = Iq_ref is_comp_ind = True + # check for d- and q-voltage (change) + if "Ud" not in PAR or PAR["Ud"] != Ud_ref: + PAR["Ud"] = Ud_ref + + if "Uq" not in PAR or PAR["Uq"] != Uq_ref: + PAR["Uq"] = Uq_ref + # compute inductance if necessary if is_comp_ind: (phid, phiq) = self.indmag.comp_inductance( diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/solve_EEC.py b/pyleecan/Methods/Simulation/EEC_PMSM/solve_EEC.py index ec8071c17..084060194 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/solve_EEC.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/solve_EEC.py @@ -4,7 +4,7 @@ from scipy.linalg import solve -def solve_EEC(self, output): +def solve_EEC(self): """Compute the parameters dict for the equivalent electrical circuit cf "Advanced Electrical Drives, analysis, modeling, control" Rik de doncker, Duco W.J. Pulle, Andre Veltman, Springer edition @@ -23,17 +23,19 @@ def solve_EEC(self, output): ---------- self : EEC_PMSM an EEC_PMSM object - output : Output - an Output object + Return + ------ + out_dict : dict + Dict containing all magnetic quantities that have been calculated in EEC """ - felec = output.elec.felec + felec = self.freq0 ws = 2 * pi * felec - # Prepare linear system + out_dict = dict() - # Solve system if "Ud" in self.parameters: # Voltage driven + # Prepare linear system XR = array( [ [self.parameters["R20"], -ws * self.parameters["Lq"]], @@ -42,22 +44,24 @@ def solve_EEC(self, output): ) XE = array([0, ws * self.parameters["phi"]]) XU = array([self.parameters["Ud"], self.parameters["Uq"]]) + # Solve system XI = solve(XR, XU - XE) - output.elec.OP.set_Id_Iq(Id=XI[0], Iq=XI[1]) + out_dict["Id"] = XI[0] + out_dict["Iq"] = XI[1] + out_dict["Ud"] = self.parameters["Ud"] + out_dict["Uq"] = self.parameters["Uq"] else: # Current Driven - output.elec.Ud_ref = ( + Ud = ( self.parameters["R20"] * self.parameters["Id"] - ws * self.parameters["Phiq"] ) - output.elec.Uq_ref = ( + Uq = ( self.parameters["R20"] * self.parameters["Iq"] + ws * self.parameters["Phid"] ) + out_dict["Ud"] = Ud + out_dict["Uq"] = Uq + out_dict["Id"] = self.parameters["Id"] + out_dict["Iq"] = self.parameters["Iq"] - # Compute currents - output.elec.Is = None - output.elec.Is = output.elec.get_Is() - - # Compute voltage - output.elec.Us = None - output.elec.Us = output.elec.get_Us() + return out_dict diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py b/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py index ab200ec82..afb53dc4e 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py @@ -3,19 +3,21 @@ from ....Functions.Electrical.coordinate_transformation import n2abc -def comp_joule_losses(self, output): +def comp_joule_losses(self, out_dict, machine): """Compute the electrical Joule losses Parameters ---------- self : Electrical an Electrical object - output : Output - an Output object + out_dict : dict + Dict containing all magnetic quantities that have been calculated in comp_parameters of EEC + machine : Machine + a Machine object """ # TODO utilize loss models instead here # compute stator joule losses - qs = output.simu.machine.stator.winding.qs + qs = machine.stator.winding.qs I_dict = self.OP.get_Id_Iq() Id, Iq = I_dict["Id"], I_dict["Iq"] Rs = self.parameters["Rs"] @@ -23,12 +25,12 @@ def comp_joule_losses(self, output): P_joule_s = qs * Rs * (Id ** 2 + Iq ** 2) # compute rotor joule losses - qr = output.simu.machine.rotor.winding.qs - p = output.simu.machine.rotor.winding.p + qr = machine.rotor.winding.qs + p = machine.rotor.winding.p Rr = self.parameters["Rr_norm"] / self.parameters["norm"] ** 2 # get the bar currents - Ir = output.elec.Ir.get_along("time", "phase")["Ir"].T + Ir = machine.parent.parent.elec.Ir.get_along("time", "phase")["Ir"].T # transform rotor current to 2 phase equivalent qr_eff = qr // p @@ -42,4 +44,4 @@ def comp_joule_losses(self, output): P_joule_r = 3 * Rr * mean(Ir_mag ** 2) / 2 - output.elec.Pj_losses = P_joule_s + P_joule_r + out_dict["Pj_losses"] = P_joule_s + P_joule_r diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py index 5e56dc580..a47c9668b 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py @@ -13,8 +13,14 @@ def comp_parameters(self, machine, OP, Tsta, Trot): ---------- self : EEC_PMSM an EEC_PMSM object - output : Output - an Output object + machine : Machine + a Machine object + OP : OP + an OP object + Tsta : float + Average stator temperature + Trot : float + Average rotor temperature """ # get some machine parameters diff --git a/pyleecan/Methods/Simulation/Electrical/comp_power.py b/pyleecan/Methods/Simulation/Electrical/comp_power.py index 5615b62de..6a23811c5 100644 --- a/pyleecan/Methods/Simulation/Electrical/comp_power.py +++ b/pyleecan/Methods/Simulation/Electrical/comp_power.py @@ -13,8 +13,8 @@ def comp_power(self, out_dict, machine): """ qs = machine.stator.winding.qs - Id, Iq = self.eec.parameters["Id"], self.eec.parameters["Iq"] - Ud, Uq = self.eec.parameters["Ud"], self.eec.parameters["Uq"] + Id, Iq = out_dict["Id"], out_dict["Iq"] + Ud, Uq = out_dict["Ud"], out_dict["Uq"] # All quantities are in RMS Pem_av_ref = qs * (Ud * Id + Uq * Iq) diff --git a/pyleecan/Methods/Simulation/Electrical/run.py b/pyleecan/Methods/Simulation/Electrical/run.py index c4394e505..358da0820 100644 --- a/pyleecan/Methods/Simulation/Electrical/run.py +++ b/pyleecan/Methods/Simulation/Electrical/run.py @@ -1,7 +1,9 @@ from numpy.lib.shape_base import expand_dims +from numpy import zeros, array from ....Methods.Simulation.Input import InputError from ....Classes.EEC_SCIM import EEC_SCIM from ....Classes.EEC_PMSM import EEC_PMSM +from ....Classes.EEC_ANL import EEC_ANL from ....Classes.MachineSCIM import MachineSCIM from ....Classes.MachineSIPMSM import MachineSIPMSM from ....Classes.MachineIPMSM import MachineIPMSM @@ -28,22 +30,24 @@ def run(self): self.eec = EEC_PMSM() else: # Check that EEC is consistent with machine type - if isinstance(machine, MachineSCIM) and not isinstance(self.eec, EEC_SCIM): + if isinstance(machine, MachineSCIM) and ( + not isinstance(self.eec, EEC_SCIM) and not isinstance(self.eec, EEC_ANL) + ): raise Exception( - "Cannot run Electrical model if machine is SCIM and eec is not EEC_SCIM" + "Cannot run Electrical model if machine is SCIM and eec is not EEC_SCIM or EEC_ANL" ) - elif isinstance(machine, (MachineSIPMSM, MachineIPMSM)) and not isinstance( - self.eec, EEC_PMSM + elif isinstance(machine, (MachineSIPMSM, MachineIPMSM)) and ( + not isinstance(self.eec, EEC_PMSM) and not isinstance(self.eec, EEC_ANL) ): raise Exception( - "Cannot run Electrical model if machine is PMSM and eec is not EEC_PMSM" + "Cannot run Electrical model if machine is PMSM and eec is not EEC_PMSM or EEC_ANL" ) if self.ELUT_enforced is not None: # enforce parameters of EEC coming from enforced ELUT at right temperatures if self.eec.parameters is None: self.eec.parameters = dict() - self.eec.parameters.update(self.ELUT_enforced.get_param_dict()) + self.eec.parameters.update(self.ELUT_enforced.get_param_dict(OP=output.elec.OP)) # Generate drive # self.eec.gen_drive(output) @@ -51,11 +55,8 @@ def run(self): # self.eec.parameters["Ud_ref"] = output.elec.OP.get_Ud_Uq()["Ud"] # self.eec.parameters["Uq_ref"] = output.elec.OP.get_Ud_Uq()["Uq"] - # Ud_ref, Ud_ref, fe => OP_fund - # for harm - # Ud, Uq, Fharm => Op_harm # Compute parameters of the electrical equivalent circuit if some parameters are missing in ELUT - out_dict = self.eec.comp_parameters( + self.eec.comp_parameters( machine, OP=output.elec.OP, Tsta=self.Tsta, @@ -63,15 +64,18 @@ def run(self): ) # Solve the electrical equivalent circuit - out_dict = self.eec.solve_EEC(output) + out_dict = self.eec.solve_EEC() # Solve for each harmonic in case of Us_harm + out_dict_harm = dict() if output.elec.Us_harm is not None: freqs = output.elec.Us_harm.get_axes("freqs")[0].get_values().tolist() - for f in freqs: - out_dict_harm = self.eec.solve_EEC(output) - else: - out_dict_harm = None + Is_harm = zeros((len(freqs), machine.winding.qs)) + for i, f in enumerate(freqs): + self.eec.freq0 = f + out_dict_i = self.eec.solve_EEC() + Is_harm[i, :] = array([out_dict_i["Id"], out_dict_i["Iq"], 0]) + out_dict_harm["Is_harm"] = Is_harm # Compute losses due to Joule effects out_dict = self.eec.comp_joule_losses(out_dict, machine) diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Ld.py b/pyleecan/Methods/Simulation/LUTdq/get_Ld.py index 11f29b37c..9108bcd22 100644 --- a/pyleecan/Methods/Simulation/LUTdq/get_Ld.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_Ld.py @@ -1,9 +1,13 @@ -def get_Ld(self, L_endwinding): +def get_Ld(self, Id, Iq, L_endwinding=0): """Get the total d-axis inductance Parameters ---------- self : LUTdq a LUTdq object + Id : float + current Id + Iq : float + current Iq L_endwinding : float end winding inductance provided by user @@ -13,6 +17,6 @@ def get_Ld(self, L_endwinding): d-axis inductance """ - Lmd = self.get_Lmd() + Lmd = self.get_Lmd(Id=Id, Iq=Iq) return Lmd + L_endwinding diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Lq.py b/pyleecan/Methods/Simulation/LUTdq/get_Lq.py index e215ea5a4..559996084 100644 --- a/pyleecan/Methods/Simulation/LUTdq/get_Lq.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_Lq.py @@ -1,9 +1,13 @@ -def get_Lq(self, L_endwinding): +def get_Lq(self, Id, Iq, L_endwinding=0): """Get the total q-axis inductance Parameters ---------- self : LUTdq a LUTdq object + Id : float + current Id + Iq : float + current Iq L_endwinding : float end winding inductance provided by user @@ -13,6 +17,6 @@ def get_Lq(self, L_endwinding): q-axis inductance """ - Lmq = self.get_Lmq() + Lmq = self.get_Lmq(Id=Id, Iq=Iq) return Lmq + L_endwinding diff --git a/pyleecan/Methods/Simulation/LUTdq/get_param_dict.py b/pyleecan/Methods/Simulation/LUTdq/get_param_dict.py index f19babef0..e01dea143 100644 --- a/pyleecan/Methods/Simulation/LUTdq/get_param_dict.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_param_dict.py @@ -1,12 +1,14 @@ from numpy import interp -def get_param_dict(self): - """Get the parameters dict for the ELUT of PMSM at the operationnal temperature and frequency +def get_param_dict(self, OP): + """Get the parameters dict for the ELUT of PMSM Parameters ---------- self : LUTdq a LUTdq object + OP : OP + an OP object Returns ---------- @@ -14,19 +16,11 @@ def get_param_dict(self): a Dict object """ - # getting parameters of the abstract class ELUT (stator parameters) - param_dict = super(type(self), self).get_parameters(Tsta=Tsta, felec=felec) - - Brm20 = self.magnet_0.mat_type.mag.Brm20 - alpha_Br = self.magnet_0.mat_type.mag.alpha_Br - - # back emf [V] update with temperature - if Tmag is None: - E0_temp = self.E0 - else: - Tmag_ref = self.Tmag_ref - E0_temp = self.E0 * (1 + alpha_Br * (Tmag - Tmag_ref)) - - param_dict["E0"] = E0_temp + param_dict = dict() + Id, Iq = OP.get_Id_Iq()["Id"], OP.get_Id_Iq()["Iq"] + param_dict["Ld"] = self.get_Ld(Id=Id, Iq=Iq) + param_dict["Lq"] = self.get_Lq(Id=Id, Iq=Iq) + param_dict["Id"] = Id + param_dict["Iq"] = Iq return param_dict diff --git a/pyleecan/Methods/Simulation/VarSimu/run.py b/pyleecan/Methods/Simulation/VarSimu/run.py index ff621b641..513fee415 100644 --- a/pyleecan/Methods/Simulation/VarSimu/run.py +++ b/pyleecan/Methods/Simulation/VarSimu/run.py @@ -176,9 +176,9 @@ def log_step_simu(index, nb_simu, paramexplorer_list, logger, layer): for param_exp in paramexplorer_list: value = param_exp.get_value()[index] if isinstance(value, InputCurrent): - msg += "N0=" + format(value.get_N0(), ".6g") + " [rpm]" - msg += ", Id=" + format(value.get_Id_Iq()["Id"], ".4g") + " [Arms]" - msg += ", Iq=" + format(value.get_Id_Iq()["Iq"], ".4g") + " [Arms], " + msg += "N0=" + format(value.N0, ".6g") + " [rpm]" + msg += ", Id=" + format(value.Id_ref, ".4g") + " [Arms]" + msg += ", Iq=" + format(value.Iq_ref, ".4g") + " [Arms], " elif isinstance(value, (list, np.ndarray)): msg += param_exp.symbol msg += "=" From 14abc73e11a927651c7acbe54ecfac1263f42212 Mon Sep 17 00:00:00 2001 From: helene-t Date: Fri, 15 Oct 2021 13:45:20 +0200 Subject: [PATCH 121/167] [WiP] introducing OP into Input objects --- Tests/Classes/test_compare.py | 4 +- Tests/Methods/Simulation/test_FEMM_clean.py | 5 +- .../Methods/Simulation/test_InCurrent_meth.py | 30 ++- .../Methods/Simulation/test_InVoltage_PWM.py | 3 +- Tests/Methods/Simulation/test_magelmer.py | 13 +- Tests/Methods/Slot/test_HoleUD_meth.py | 5 +- Tests/Plot/Schematics/test_plot_axis.py | 3 +- Tests/Plot/test_ICEM_2020.py | 6 +- Tests/Plot/test_PostPlot.py | 3 +- Tests/Plot/test_plots.py | 25 +- Tests/Simulation/test_LSRPM_simulation.py | 7 +- Tests/Simulation/test_StructElmer.py | 7 +- Tests/Simulation/test_post_simu.py | 3 +- Tests/Simulation/test_post_var_simu.py | 3 +- .../Electrical/test_EEC_ELUT_PMSM.py | 31 ++- Tests/Validation/Electrical/test_EEC_PMSM.py | 3 +- Tests/Validation/Electrical/test_EEC_SCIM.py | 10 +- .../Force/AGSF_Transfer/test_compare_Kmesh.py | 6 +- .../AGSF_Transfer/test_compare_Rag_var.py | 11 +- .../AGSF_Transfer/test_compare_transfer.py | 16 +- .../Force/test_AGSF_Rag_variation.py | 6 +- Tests/Validation/Force/test_AGSF_SynRM.py | 4 +- Tests/Validation/Force/test_AGSF_slotless.py | 3 +- Tests/Validation/Force/test_AGSF_spectrum.py | 6 +- Tests/Validation/Force/test_ForceTensor.py | 6 +- Tests/Validation/Loss/test_FEMM_Loss.py | 6 +- .../Magnetics/test_EM_BH_Model_001.py | 3 +- .../Magnetics/test_FEMM_LamSlotMultiWind.py | 6 +- .../Validation/Magnetics/test_FEMM_compare.py | 21 +- .../Magnetics/test_FEMM_import_dxf.py | 5 +- .../Magnetics/test_FEMM_import_model.py | 3 +- .../Magnetics/test_FEMM_meshsolution_plots.py | 10 +- .../Magnetics/test_FEMM_parallelization.py | 7 +- .../Magnetics/test_FEMM_periodicity.py | 13 +- .../Magnetics/test_FEMM_set_previous.py | 5 +- Tests/Validation/Magnetics/test_FEMM_skew.py | 6 +- .../Magnetics/test_FEMM_slotless.py | 6 +- .../Validation/Magnetics/test_FEMM_torque.py | 3 +- Tests/Validation/Multisimulation/plot_save.py | 4 +- .../Multisimulation/test_multi_multi.py | 5 +- .../Multisimulation/test_slot_scale.py | 3 +- .../Multisimulation/test_varload.py | 4 +- .../Optimization/test_Binh_and_Korn.py | 3 +- Tests/Validation/Optimization/test_zdt3.py | 3 +- pyleecan/Classes/Class_Dict.json | 109 +------- pyleecan/Classes/EEC_ANL.py | 17 +- pyleecan/Classes/Input.py | 73 +++-- pyleecan/Classes/InputCurrent.py | 101 +------ pyleecan/Classes/InputFlux.py | 49 +--- pyleecan/Classes/InputForce.py | 9 +- pyleecan/Classes/InputVoltage.py | 249 +----------------- pyleecan/Classes/OutElec.py | 52 ++++ .../Generator/ClassesRef/Output/OutElec.csv | 1 + .../ClassesRef/Simulation/EEC_ANL.csv | 2 +- .../Generator/ClassesRef/Simulation/Input.csv | 2 +- .../ClassesRef/Simulation/InputCurrent.csv | 3 +- .../ClassesRef/Simulation/InputVoltage.csv | 8 - .../Machine/LamSlotWind/comp_mmf_unit.py | 14 +- pyleecan/Methods/Output/OutElec/get_Us.py | 6 +- pyleecan/Methods/Output/OutElec/store.py | 4 +- .../Simulation/EEC_ANL/comp_joule_losses.py | 2 + .../Methods/Simulation/EEC_ANL/solve_EEC.py | 9 +- .../Simulation/EEC_PMSM/comp_joule_losses.py | 2 + .../Simulation/EEC_SCIM/comp_joule_losses.py | 2 + .../Simulation/Electrical/comp_power.py | 2 + .../Simulation/Electrical/comp_torque.py | 2 + pyleecan/Methods/Simulation/Electrical/run.py | 13 +- .../Simulation/Input/comp_axis_time.py | 8 +- .../Simulation/InputCurrent/gen_input.py | 6 +- .../Simulation/InputCurrent/set_Id_Iq.py | 12 +- .../InputCurrent/set_OP_from_array.py | 8 +- .../Simulation/InputVoltage/gen_input.py | 33 +-- .../Simulation/StructElmer/gen_case.py | 2 +- .../VarLoadCurrent/get_input_list.py | 17 +- pyleecan/Methods/Simulation/VarSimu/run.py | 6 +- 75 files changed, 439 insertions(+), 719 deletions(-) diff --git a/Tests/Classes/test_compare.py b/Tests/Classes/test_compare.py index 5e6ff54a5..d1e23d66a 100644 --- a/Tests/Classes/test_compare.py +++ b/Tests/Classes/test_compare.py @@ -10,7 +10,7 @@ from pyleecan.Classes.MagFEMM import MagFEMM from pyleecan.Classes.PostPlot import PostPlot from pyleecan.Classes.PostFunction import PostFunction -from pyleecan.Classes.Output import Output +from pyleecan.Classes.OPdq import OPdq from pyleecan.Functions.load import load from pyleecan.definitions import DATA_DIR @@ -45,7 +45,7 @@ def test_compare(): ) ) simu.input.Ir = None # SPMSM machine => no rotor currents to define - simu.input.N0 = 3000 # Rotor speed [rpm] + simu.input.OP = OPdq(N0=3000) # Rotor speed [rpm] simu.input.angle_rotor_initial = 0.5216 + pi # Rotor position at t=0 [rad] # Definition of the magnetic simulation (no symmetry) diff --git a/Tests/Methods/Simulation/test_FEMM_clean.py b/Tests/Methods/Simulation/test_FEMM_clean.py index 419d4428e..2f3774994 100644 --- a/Tests/Methods/Simulation/test_FEMM_clean.py +++ b/Tests/Methods/Simulation/test_FEMM_clean.py @@ -4,6 +4,7 @@ from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.MagFEMM import MagFEMM +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Functions.load import load from pyleecan.definitions import DATA_DIR @@ -23,11 +24,9 @@ def test_FEMM_clean(): # Definition of a sinusoidal current simu.input = InputCurrent() - simu.input.Id_ref = -100 # [A] - simu.input.Iq_ref = 200 # [A] + simu.input.OP = OPdq(Id_ref=-100, Iq_ref=200, N0=2000) simu.input.Nt_tot = 16 # Number of time step simu.input.Na_tot = 1024 # Spatial discretization - simu.input.N0 = 2000 # Rotor speed [rpm] # Definition of the magnetic simulation simu.mag = MagFEMM( diff --git a/Tests/Methods/Simulation/test_InCurrent_meth.py b/Tests/Methods/Simulation/test_InCurrent_meth.py index 4401e9ae7..886654295 100644 --- a/Tests/Methods/Simulation/test_InCurrent_meth.py +++ b/Tests/Methods/Simulation/test_InCurrent_meth.py @@ -25,6 +25,7 @@ from pyleecan.Classes.MachineDFIM import MachineDFIM from pyleecan.Classes.Output import Output from pyleecan.Classes.Simulation import Simulation +from pyleecan.Classes.OPdq import OPdq from pyleecan.definitions import DATA_DIR from pyleecan.Functions.load import load from pyleecan.Functions.Plot import dict_2D @@ -147,7 +148,12 @@ # Wrong N0, alpha_rotor test_obj = Simulation(machine=M3) test_obj.input = InputCurrent( - time=time, angle=angle, Is=I_1, Ir=I_2, angle_rotor=None, N0=None + time=time, + angle=angle, + Is=I_1, + Ir=I_2, + angle_rotor=None, + OP=OPdq(N0=None), ) InputCurrent_Error_test.append( { @@ -157,7 +163,12 @@ ) test_obj = Simulation(machine=M3) test_obj.input = InputCurrent( - time=time, angle=angle, Is=I_1, Ir=I_2, angle_rotor=angle_rotor_wrong, N0=None + time=time, + angle=angle, + Is=I_1, + Ir=I_2, + angle_rotor=angle_rotor_wrong, + OP=OPdq(N0=None), ) InputCurrent_Error_test.append( { @@ -167,7 +178,12 @@ ) test_obj = Simulation(machine=M3) test_obj.input = InputCurrent( - time=time, angle=angle, Is=I_1, Ir=I_2, angle_rotor=angle_rotor_wrong2, N0=None + time=time, + angle=angle, + Is=I_1, + Ir=I_2, + angle_rotor=angle_rotor_wrong2, + OP=OPdq(N0=None), ) InputCurrent_Error_test.append( { @@ -244,11 +260,9 @@ def test_InputCurrent_DQ(self, test_dict): Nt_tot=Nt_tot, Na_tot=Na_tot, Is=None, - Iq_ref=Iq_ref, - Id_ref=Id_ref, + OP=OPdq(Id_ref=Id_ref, Iq_ref=Iq_ref, N0=N0), Ir=None, angle_rotor=None, # Will be computed according to N0 and rot_dir - N0=N0, rot_dir=None, ) @@ -272,11 +286,9 @@ def test_InputCurrent_DQ(self, test_dict): Nt_tot=Nt_tot, Na_tot=Na_tot, Is=Is_exp.transpose(), - Iq_ref=None, - Id_ref=None, + OP=OPdq(Id_ref=None, Iq_ref=None, N0=N0), Ir=None, angle_rotor=None, # Will be computed according to N0 and rot_dir - N0=N0, rot_dir=None, ) out = Output(simu=test_obj) diff --git a/Tests/Methods/Simulation/test_InVoltage_PWM.py b/Tests/Methods/Simulation/test_InVoltage_PWM.py index d309826a8..40c0937ad 100644 --- a/Tests/Methods/Simulation/test_InVoltage_PWM.py +++ b/Tests/Methods/Simulation/test_InVoltage_PWM.py @@ -3,6 +3,7 @@ from pyleecan.Classes.ImportGenPWM import ImportGenPWM from pyleecan.Classes.InputVoltage import InputVoltage from pyleecan.Classes.Simu1 import Simu1 +from pyleecan.Classes.OPdq import OPdq from pyleecan.definitions import DATA_DIR from pyleecan.Functions.load import load from pyleecan.Functions.Plot import dict_2D @@ -23,7 +24,7 @@ def test_InVoltage_PWM(): simu = Simu1(name="test_InVoltage_PWM", machine=Toyota_Prius) simu.input = InputVoltage( - N0=2000, + OP=OPdq(N0=2000), Na_tot=1024, Nt_tot=1024, PWM=ImportGenPWM(fmax=fmax, fswi=fswi, Vdc1=Vdc1, U0=U0), diff --git a/Tests/Methods/Simulation/test_magelmer.py b/Tests/Methods/Simulation/test_magelmer.py index 4461c2278..b71c7852d 100644 --- a/Tests/Methods/Simulation/test_magelmer.py +++ b/Tests/Methods/Simulation/test_magelmer.py @@ -7,6 +7,7 @@ from pyleecan.Classes.MagElmer import MagElmer from pyleecan.Classes.SlotM10 import SlotM10 from pyleecan.Classes.Simu1 import Simu1 +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Output import Output from pyleecan.Functions.load import load from pyleecan.Functions.Plot import dict_2D @@ -72,13 +73,13 @@ def test_ipm_Elmer(): # simu.input.Iq_ref = 250 # [A] # simu.input.Nt_tot = 32 * 8 # Number of time step # simu.input.Na_tot = 2048 # Spatial discretization - simu.input.N0 = 2000 # Rotor speed [rpm] + simu.input.OP = OPdq(N0=2000) # Rotor speed [rpm] p = Toyota_Prius.stator.winding.p - time = linspace(0, 60 / simu.input.N0, num=32 * p, endpoint=False) + time = linspace(0, 60 / simu.input.OP.N0, num=32 * p, endpoint=False) simu.input.time = time simu.input.angle = linspace(0, 2 * pi, num=2048, endpoint=False) I0 = 250 - felec = p * simu.input.N0 / 60 + felec = p * simu.input.OP.N0 / 60 rot_dir = simu.machine.stator.comp_rot_dir() Phi0 = 140 * pi / 180 Ia = I0 * cos(2 * pi * felec * time + 0 * rot_dir * 2 * pi / 3 + Phi0) @@ -135,13 +136,13 @@ def test_spm_Elmer(): # simu.input.Iq_ref = 250 # [A] # simu.input.Nt_tot = 32 * 8 # Number of time step # simu.input.Na_tot = 2048 # Spatial discretization - simu.input.N0 = 2000 # Rotor speed [rpm] + simu.input.OP = OPdq(N0=2000) # Rotor speed [rpm] p = PMSM_A.stator.winding.p - time = linspace(0, 60 / simu.input.N0, num=32 * p, endpoint=False) + time = linspace(0, 60 / simu.input.OP.N0, num=32 * p, endpoint=False) simu.input.time = time simu.input.angle = linspace(0, 2 * pi, num=2048, endpoint=False) I0 = 150 - felec = p * simu.input.N0 / 60 + felec = p * simu.input.OP.N0 / 60 rot_dir = simu.machine.stator.comp_rot_dir() Phi0 = 140 * pi / 180 Ia = I0 * cos(2 * pi * felec * time + 0 * rot_dir * 2 * pi / 3 + Phi0) diff --git a/Tests/Methods/Slot/test_HoleUD_meth.py b/Tests/Methods/Slot/test_HoleUD_meth.py index 244482e01..6457087e2 100644 --- a/Tests/Methods/Slot/test_HoleUD_meth.py +++ b/Tests/Methods/Slot/test_HoleUD_meth.py @@ -4,6 +4,7 @@ from pyleecan.Classes.HoleUD import HoleUD from pyleecan.Classes.LamHole import LamHole +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.SurfLine import SurfLine from pyleecan.definitions import DATA_DIR from pyleecan.Functions.load import load @@ -130,11 +131,9 @@ def test_convert_UD(self): simu.input = InputCurrent( Is=None, Ir=None, # No winding on the rotor - N0=N0, + OP=OPdq(N0=N0, Id_ref=50, Iq_ref=0), Nt_tot=Nt_tot, Na_tot=Na_tot, - Id_ref=50, - Iq_ref=0, ) # Definition of the magnetic simulation (no symmetry) diff --git a/Tests/Plot/Schematics/test_plot_axis.py b/Tests/Plot/Schematics/test_plot_axis.py index 7267c78cd..799adfdf9 100644 --- a/Tests/Plot/Schematics/test_plot_axis.py +++ b/Tests/Plot/Schematics/test_plot_axis.py @@ -4,6 +4,7 @@ import matplotlib.pyplot as plt from numpy import exp, pi, ones, array, zeros from numpy import argmax, cos, abs as np_abs, angle as np_angle +from pyleecan.Classes.OPdq import OPdq from pyleecan.definitions import config_dict from Tests import save_plot_path as save_path @@ -264,7 +265,7 @@ def test_axis_LamWind(): simu.input = InputCurrent( Is=Is, Ir=Ir, # zero current for the rotor - N0=N0, + OP=OPdq(N0=N0), angle_rotor=None, # Will be computed Nt_tot=Nt_tot, Na_tot=Na_tot, diff --git a/Tests/Plot/test_ICEM_2020.py b/Tests/Plot/test_ICEM_2020.py index e8720b6e5..7706f1913 100644 --- a/Tests/Plot/test_ICEM_2020.py +++ b/Tests/Plot/test_ICEM_2020.py @@ -65,7 +65,7 @@ def test_FEMM_sym(): simu.input = InputCurrent( Is=Is, Ir=Ir, # zero current for the rotor - N0=N0, + OP=OPdq(N0=N0), angle_rotor=None, # Will be computed Nt_tot=Nt_tot, Na_tot=Na_tot, @@ -552,7 +552,7 @@ def test_ecc_FEMM(): simu.input = InputCurrent( Is=Is, Ir=None, # No winding on the rotor - N0=N0, + OP=OPdq(N0=N0), angle_rotor=None, time=time, angle=angle, @@ -680,7 +680,7 @@ def test_Optimization_problem(): simu.input = InputCurrent( Is=Is, Ir=Ir, # zero current for the rotor - N0=N0, + OP=OPdq(N0=N0), angle_rotor=None, # Will be computed Nt_tot=Nt_tot, Na_tot=Na_tot, diff --git a/Tests/Plot/test_PostPlot.py b/Tests/Plot/test_PostPlot.py index 6382af6ca..279cd7fa1 100644 --- a/Tests/Plot/test_PostPlot.py +++ b/Tests/Plot/test_PostPlot.py @@ -5,6 +5,7 @@ from numpy import exp, sqrt, pi import matplotlib.pyplot as plt +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 @@ -45,7 +46,7 @@ def test_PostPlot(): Iq_ref=Iq_ref, Na_tot=252 * 8, Nt_tot=20 * 8, - N0=1000, + OP=OPdq(N0=1000), ) # Definition of the magnetic simulation: with periodicity diff --git a/Tests/Plot/test_plots.py b/Tests/Plot/test_plots.py index a5200efb8..7916fff10 100644 --- a/Tests/Plot/test_plots.py +++ b/Tests/Plot/test_plots.py @@ -10,6 +10,7 @@ from Tests import save_plot_path as save_path from pyleecan.Classes.ImportMatlab import ImportMatlab from pyleecan.Classes.InputFlux import InputFlux +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Output import Output from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Functions.load import load @@ -96,9 +97,7 @@ def test_default_proj_Br_time_space(self, import_data): B_dict={"Br": flux}, time=time, angle=angle, - N0=N0, - Id_ref=Id_ref, - Iq_ref=Iq_ref, + OP=OPdq(N0=N0, Id_ref=Id_ref, Iq_ref=Iq_ref), ) out = Output(simu=simu) simu.run() @@ -229,9 +228,7 @@ def test_default_proj_Br_time_space(self, import_data): B_dict={"Br": flux}, time=time, angle=angle, - N0=N0, - Id_ref=Id_ref, - Iq_ref=Iq_ref, + OP=OPdq(N0=N0, Id_ref=Id_ref, Iq_ref=Iq_ref), ) out4 = Output(simu=simu4) simu4.run() @@ -325,9 +322,7 @@ def test_default_proj_Br_cfft2(self, import_data): B_dict={"Br": flux}, time=time, angle=angle, - N0=N0, - Id_ref=Id_ref, - Iq_ref=Iq_ref, + OP=OPdq(N0=N0, Id_ref=Id_ref, Iq_ref=Iq_ref), ) simu.mag = None simu.force = None @@ -372,9 +367,7 @@ def test_default_proj_surf(self, import_data): B_dict={"Br": flux}, time=time, angle=angle, - N0=N0, - Id_ref=Id_ref, - Iq_ref=Iq_ref, + OP=OPdq(N0=N0, Id_ref=Id_ref, Iq_ref=Iq_ref), ) out = Output(simu=simu) simu.run() @@ -413,9 +406,7 @@ def test_default_proj_fft2(self, import_data): B_dict={"Br": flux}, time=time, angle=angle, - N0=N0, - Id_ref=Id_ref, - Iq_ref=Iq_ref, + OP=OPdq(N0=N0, Id_ref=Id_ref, Iq_ref=Iq_ref), ) out = Output(simu=simu) simu.run() @@ -454,9 +445,7 @@ def test_default_proj_time_space(self, import_data): B_dict={"Br": flux}, time=time, angle=angle, - N0=N0, - Id_ref=Id_ref, - Iq_ref=Iq_ref, + OP=OPdq(N0=N0, Id_ref=Id_ref, Iq_ref=Iq_ref), ) out = Output(simu=simu) simu.run() diff --git a/Tests/Simulation/test_LSRPM_simulation.py b/Tests/Simulation/test_LSRPM_simulation.py index cdc954ab8..9591f42d9 100644 --- a/Tests/Simulation/test_LSRPM_simulation.py +++ b/Tests/Simulation/test_LSRPM_simulation.py @@ -1,5 +1,6 @@ # Load the machine from os.path import join +from pyleecan.Classes.OPdq import OPdq from pyleecan.Functions.load import load from pyleecan.definitions import DATA_DIR import matplotlib.pyplot as plt @@ -29,11 +30,11 @@ def test_LSRPM_simulation(): simu_femm.input = InputCurrent() # Rotor speed [rpm] - simu_femm.input.N0 = 750 + simu_femm.input.OP = OPdq(N0=750) # time discretization [s] time = linspace( - start=0, stop=60 / simu_femm.input.N0, num=32 * p, endpoint=False + start=0, stop=60 / simu_femm.input.OP.N0, num=32 * p, endpoint=False ) # 32*p timesteps simu_femm.input.time = time @@ -44,7 +45,7 @@ def test_LSRPM_simulation(): # Stator currents as a function of time, each column correspond to one phase [A] I0_rms = 6.85 - felec = p * simu_femm.input.N0 / 60 # [Hz] + felec = p * simu_femm.input.OP.N0 / 60 # [Hz] rot_dir = simu_femm.machine.stator.comp_rot_dir() Phi0 = 140 * pi / 180 # Maximum Torque Per Amp diff --git a/Tests/Simulation/test_StructElmer.py b/Tests/Simulation/test_StructElmer.py index fc0861e44..99cf164c8 100644 --- a/Tests/Simulation/test_StructElmer.py +++ b/Tests/Simulation/test_StructElmer.py @@ -2,6 +2,7 @@ from numpy import pi import pytest from Tests import save_path, TEST_DATA_DIR +from pyleecan.Classes.OPdq import OPdq from pyleecan.definitions import DATA_DIR from pyleecan.Classes.LamSlotMag import LamSlotMag @@ -68,7 +69,7 @@ def test_HoleM50(self): simu.struct.is_get_mesh = True # set rotor speed and run simulation - simu.input = InputVoltage(N0=10000) # rpm + simu.input = InputVoltage(OP=OPdq(N0=10000)) # rpm simu.run() return output @@ -94,7 +95,7 @@ def test_HoleM50_no_magnets(self): simu.struct.is_get_mesh = True # set rotor speed and run simulation - simu.input = InputVoltage(N0=10000) # rpm + simu.input = InputVoltage(OP=OPdq(N0=10000)) # rpm simu.run() return output @@ -127,7 +128,7 @@ def test_disk_geometry(self): simu.struct.is_get_mesh = True # set rotor speed and run simulation - simu.input = InputVoltage(N0=10000) # rpm + simu.input = InputVoltage(OP=OPdq(N0=10000)) # rpm simu.run() return output diff --git a/Tests/Simulation/test_post_simu.py b/Tests/Simulation/test_post_simu.py index e6e5717d5..45e212a19 100644 --- a/Tests/Simulation/test_post_simu.py +++ b/Tests/Simulation/test_post_simu.py @@ -1,5 +1,6 @@ import pytest from os.path import join +from pyleecan.Classes.OPdq import OPdq from pyleecan.Functions.load import load from pyleecan.definitions import DATA_DIR @@ -25,7 +26,7 @@ def test_post_simu(): simu1 = Simu1(name="test_post_simu", machine=Toyota_Prius) # Definition of the input simu1.input = InputCurrent( - N0=2000, Id_ref=-100, Iq_ref=200, Nt_tot=10, Na_tot=2048, rot_dir=1 + OP=OPdq(N0=2000, Id_ref=-100, Iq_ref=200), Nt_tot=10, Na_tot=2048, rot_dir=1 ) # simu2, postprocessing 1 PostFunction, 1 PostMethod diff --git a/Tests/Simulation/test_post_var_simu.py b/Tests/Simulation/test_post_var_simu.py index aae3381be..4b3f334be 100644 --- a/Tests/Simulation/test_post_var_simu.py +++ b/Tests/Simulation/test_post_var_simu.py @@ -8,6 +8,7 @@ from pyleecan.Classes.HoleM51 import HoleM51 from pyleecan.Classes.HoleM52 import HoleM52 from pyleecan.Classes.HoleM53 import HoleM53 +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.PostFunction import PostFunction from pyleecan.Classes.PostMethod import PostMethod from pyleecan.Classes.Simu1 import Simu1 @@ -47,7 +48,7 @@ def test_post_var_simu(): simu1 = Simu1(name="test_post_simu", machine=Toyota_Prius) # Definition of the input simu1.input = InputCurrent( - N0=2000, Id_ref=-100, Iq_ref=200, Nt_tot=10, Na_tot=2048, rot_dir=1 + OP=OPdq(N0=2000, Id_ref=-100, Iq_ref=200), Nt_tot=10, Na_tot=2048, rot_dir=1 ) # Vary Stator slot H0 diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py index 37d5e20cc..4a7fa2f8d 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py @@ -5,7 +5,9 @@ from multiprocessing import cpu_count import pytest - +from pyleecan.Classes.ImportGenPWM import ImportGenPWM +from pyleecan.Classes.InputVoltage import InputVoltage +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.VarLoadCurrent import VarLoadCurrent @@ -39,7 +41,9 @@ def test_EEC_ELUT_PMSM(): # Definition of the input simu.input = InputCurrent( - N0=1000, Nt_tot=8 * 10, Na_tot=8 * 200, Id_ref=0, Iq_ref=0 + Nt_tot=8 * 10, + Na_tot=8 * 200, + OP=OPdq(N0=1000, Id_ref=0, Iq_ref=0), ) # Load OP_matrix @@ -53,7 +57,10 @@ def test_EEC_ELUT_PMSM(): # Set varspeed simulation simu.var_simu = VarLoadCurrent( - type_OP_matrix=1, OP_matrix=OP_matrix, is_keep_all_output=True + type_OP_matrix=1, + OP_matrix=OP_matrix, + is_keep_all_output=True, + stop_if_error=True, ) # Define second simu for FEMM comparison @@ -78,11 +85,18 @@ def test_EEC_ELUT_PMSM(): ELUT = out.simu.var_simu.postproc_list[0].LUT # Simu with EEC using ELUT + fmax = 20000 + fswi = 7000 + Vdc1 = 1000 # Bus voltage + U0 = 800 # Phase voltage simu_EEC = Simu1(name="test_LUT_PMSM", machine=Toyota_Prius) # Definition of the input - simu_EEC.input = InputCurrent( - N0=1000, Nt_tot=8 * 10, Na_tot=8 * 200, Id_ref=50, Iq_ref=50 + simu_EEC.input = InputVoltage( + Na_tot=1024, + Nt_tot=1024, + PWM=ImportGenPWM(fmax=fmax, fswi=fswi, Vdc1=Vdc1, U0=U0), + OP=OPdq(N0=1000, Id_ref=50, Iq_ref=100, Ud_ref=200, Uq_ref=300), ) simu_EEC.elec = Electrical(eec=EEC_ANL(), ELUT_enforced=ELUT) @@ -98,6 +112,13 @@ def test_EEC_ELUT_PMSM(): **dict_2D ) + out_EEC.elec.Is_harm.plot_2D_Data( + "freqs", + save_path=join(save_path, "EEC_FEMM_IPMSM_Is_harm.png"), + is_show_fig=is_show_fig, + **dict_2D + ) + return out, out_EEC diff --git a/Tests/Validation/Electrical/test_EEC_PMSM.py b/Tests/Validation/Electrical/test_EEC_PMSM.py index a08a7e0a9..0e34c6e5e 100644 --- a/Tests/Validation/Electrical/test_EEC_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_PMSM.py @@ -5,6 +5,7 @@ from multiprocessing import cpu_count import pytest +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.InputCurrent import InputCurrent @@ -35,7 +36,7 @@ def test_EEC_PMSM(): simu = Simu1(name="test_EEC_PMSM", machine=Toyota_Prius) # Definition of the input - simu.input = InputCurrent(N0=2000, Nt_tot=10, Na_tot=2048) + simu.input = InputCurrent(OP=OPdq(N0=2000), Nt_tot=10, Na_tot=2048) simu.input.set_Id_Iq(I0=250 / sqrt(2), Phi0=60 * pi / 180) # Define second simu for FEMM comparison diff --git a/Tests/Validation/Electrical/test_EEC_SCIM.py b/Tests/Validation/Electrical/test_EEC_SCIM.py index 46ba82fd2..be365f230 100644 --- a/Tests/Validation/Electrical/test_EEC_SCIM.py +++ b/Tests/Validation/Electrical/test_EEC_SCIM.py @@ -5,6 +5,7 @@ from pyleecan.definitions import DATA_DIR from pyleecan.Classes.Simu1 import Simu1 +from pyleecan.Classes.OPslip import OPslip from pyleecan.Classes.Electrical import Electrical from pyleecan.Classes.EEC_SCIM import EEC_SCIM from pyleecan.Classes.InputCurrent import InputCurrent @@ -61,14 +62,11 @@ def test_EEC_SCIM(): # Definition of a sinusoidal current simu.input = InputCurrent() - simu.input.felec = 50 # [Hz] - simu.input.Id_ref = None # [A] - simu.input.Iq_ref = None # [A] - simu.input.Ud_ref = 400 # [V] - simu.input.Uq_ref = 0 # [V] + simu.input.OP = OPslip( + felec=50, Id_ref=None, Iq_ref=None, Ud_ref=400, Uq_ref=0, N0=1418 + ) simu.input.Nt_tot = 360 # Number of time steps simu.input.Na_tot = 2048 # Spatial discretization - simu.input.N0 = 1418 # Rotor speed [rpm] simu.input.rot_dir = 1 # To enforce the rotation direction simu.input.Nrev = 5 diff --git a/Tests/Validation/Force/AGSF_Transfer/test_compare_Kmesh.py b/Tests/Validation/Force/AGSF_Transfer/test_compare_Kmesh.py index 17dde7ac0..9a7df3b20 100644 --- a/Tests/Validation/Force/AGSF_Transfer/test_compare_Kmesh.py +++ b/Tests/Validation/Force/AGSF_Transfer/test_compare_Kmesh.py @@ -4,6 +4,7 @@ from os.path import join from pyleecan.Classes.ForceMT import ForceMT +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.MagFEMM import MagFEMM from pyleecan.Classes.InputCurrent import InputCurrent @@ -31,7 +32,10 @@ def test_compare_Kmesh(): simu = Simu1(name="test_compare_Kmesh_direct", machine=Benchmark) simu.input = InputCurrent( - Id_ref=0, Iq_ref=0, Ir=None, Na_tot=5 * 2 ** 8, Nt_tot=2, N0=1200 + OP=OPdq(N0=1200, Id_ref=0, Iq_ref=0), + Ir=None, + Na_tot=5 * 2 ** 8, + Nt_tot=2, ) # Configure simulation diff --git a/Tests/Validation/Force/AGSF_Transfer/test_compare_Rag_var.py b/Tests/Validation/Force/AGSF_Transfer/test_compare_Rag_var.py index 8257dfaad..cf6c722e8 100644 --- a/Tests/Validation/Force/AGSF_Transfer/test_compare_Rag_var.py +++ b/Tests/Validation/Force/AGSF_Transfer/test_compare_Rag_var.py @@ -5,6 +5,7 @@ from multiprocessing import cpu_count from pyleecan.Classes.ForceMT import ForceMT +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.MagFEMM import MagFEMM from pyleecan.Classes.InputCurrent import InputCurrent @@ -32,7 +33,10 @@ def test_compare_Rag_variation(): simu = Simu1(name="test_compare_Rag_variation_direct", machine=Benchmark) simu.input = InputCurrent( - Id_ref=0, Iq_ref=0, Ir=None, Na_tot=5 * 2 ** 8, Nt_tot=2, N0=1200 + OP=OPdq(N0=1200, Id_ref=0, Iq_ref=0), + Ir=None, + Na_tot=5 * 2 ** 8, + Nt_tot=2, ) # Configure simulation @@ -138,7 +142,10 @@ def test_compare_Rag_variation_Nmax_sensitivity(): ) simu.input = InputCurrent( - Id_ref=0, Iq_ref=0, Ir=None, Na_tot=5 * 2 ** 8, Nt_tot=2, N0=1200 + OP=OPdq(N0=1200, Id_ref=0, Iq_ref=0), + Ir=None, + Na_tot=5 * 2 ** 8, + Nt_tot=2, ) # Configure simulation diff --git a/Tests/Validation/Force/AGSF_Transfer/test_compare_transfer.py b/Tests/Validation/Force/AGSF_Transfer/test_compare_transfer.py index 38d10e8e3..c58ced439 100644 --- a/Tests/Validation/Force/AGSF_Transfer/test_compare_transfer.py +++ b/Tests/Validation/Force/AGSF_Transfer/test_compare_transfer.py @@ -5,6 +5,7 @@ from multiprocessing import cpu_count from pyleecan.Classes.ForceMT import ForceMT +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.MagFEMM import MagFEMM from pyleecan.Classes.InputCurrent import InputCurrent @@ -33,7 +34,10 @@ def test_IPMSM(): simu = Simu1(name="test_compare_transfer_IPMSM_no_transfer", machine=Toyota_Prius) simu.input = InputCurrent( - Id_ref=0, Iq_ref=0, Ir=None, Na_tot=2 ** 11, Nt_tot=2 ** 6, N0=1200 + OP=OPdq(N0=1200, Id_ref=0, Iq_ref=0), + Ir=None, + Na_tot=2 ** 11, + Nt_tot=2 ** 6, ) # Configure simulation @@ -55,7 +59,10 @@ def test_IPMSM(): simu2.name = "test_compare_transfer_IPMSM_with_transfer" simu2.input = InputCurrent( - Id_ref=0, Iq_ref=0, Ir=None, Na_tot=2 ** 11, Nt_tot=2 ** 6, N0=1200 + OP=OPdq(N0=1200, Id_ref=0, Iq_ref=0), + Ir=None, + Na_tot=2 ** 11, + Nt_tot=2 ** 6, ) simu2.mag = MagFEMM( @@ -121,7 +128,10 @@ def test_Benchmark(): simu = Simu1(name="test_compare_transfer_Benchmark_Rag", machine=Benchmark) simu.input = InputCurrent( - Id_ref=0, Iq_ref=0, Ir=None, Na_tot=5 * 2 ** 9, Nt_tot=2, N0=1200 + OP=OPdq(N0=1200, Id_ref=0, Iq_ref=0), + Ir=None, + Na_tot=5 * 2 ** 9, + Nt_tot=2, ) # Configure simulation diff --git a/Tests/Validation/Force/test_AGSF_Rag_variation.py b/Tests/Validation/Force/test_AGSF_Rag_variation.py index 3cf8edc1b..8a865aaa9 100644 --- a/Tests/Validation/Force/test_AGSF_Rag_variation.py +++ b/Tests/Validation/Force/test_AGSF_Rag_variation.py @@ -5,6 +5,7 @@ from multiprocessing import cpu_count from pyleecan.Classes.ForceMT import ForceMT +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.MagFEMM import MagFEMM from pyleecan.Classes.InputCurrent import InputCurrent @@ -42,7 +43,10 @@ def test_Benchmark_AGSF_Rag(): simu = Simu1(name="AC_IPMSM_plot", machine=Benchmark) simu.input = InputCurrent( - Id_ref=0, Iq_ref=0, Ir=None, Na_tot=5 * 2 ** 8, Nt_tot=2, N0=1200 + OP=OPdq(N0=1200, Id_ref=0, Iq_ref=0), + Ir=None, + Na_tot=5 * 2 ** 8, + Nt_tot=2, ) # Configure simulation diff --git a/Tests/Validation/Force/test_AGSF_SynRM.py b/Tests/Validation/Force/test_AGSF_SynRM.py index 80b1dd6b7..763d86458 100644 --- a/Tests/Validation/Force/test_AGSF_SynRM.py +++ b/Tests/Validation/Force/test_AGSF_SynRM.py @@ -2,6 +2,7 @@ from os.path import join from Tests import save_validation_path as save_path +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 @@ -60,13 +61,12 @@ def test_AGSF_SynRM(): simu.input = InputCurrent( Is=None, Ir=None, # No winding on the rotor - N0=None, + OP=OPdq(N0=None, felec=freq0), angle_rotor=alpha_rotor, time=time_obj, Na_tot=Na_tot, Nt_tot=Nt_tot, angle_rotor_initial=0, - felec=freq0, ) # Definition of the magnetic simulation (1/2 symmetry) diff --git a/Tests/Validation/Force/test_AGSF_slotless.py b/Tests/Validation/Force/test_AGSF_slotless.py index 3d770f0ff..79244d180 100644 --- a/Tests/Validation/Force/test_AGSF_slotless.py +++ b/Tests/Validation/Force/test_AGSF_slotless.py @@ -1,3 +1,4 @@ +from pyleecan.Classes.OPdq import OPdq from pyleecan.Functions.load import load from pyleecan.definitions import DATA_DIR @@ -32,7 +33,7 @@ def test_AGSF_slotless(): simu = Simu1(name="test_AGSF_slotless", machine=Slotless_CEFC) simu.input = InputCurrent( - Id_ref=0, Iq_ref=0, Ir=None, Na_tot=2 ** 6, Nt_tot=2, N0=1200 + OP=OPdq(N0=1200, Id_ref=0, Iq_ref=0), Ir=None, Na_tot=2 ** 6, Nt_tot=2 ) simu.mag = MagFEMM( diff --git a/Tests/Validation/Force/test_AGSF_spectrum.py b/Tests/Validation/Force/test_AGSF_spectrum.py index 339a89d94..08129a8e5 100644 --- a/Tests/Validation/Force/test_AGSF_spectrum.py +++ b/Tests/Validation/Force/test_AGSF_spectrum.py @@ -13,6 +13,7 @@ from numpy.testing import assert_array_almost_equal from pyleecan.Classes.ForceMT import ForceMT +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.MagFEMM import MagFEMM from pyleecan.Classes.InputCurrent import InputCurrent @@ -30,7 +31,10 @@ simu = Simu1(name="test_AGSF_spectrum", machine=Toyota_Prius) simu.input = InputCurrent( - Id_ref=0, Iq_ref=0, Ir=None, Na_tot=2 ** 6, Nt_tot=4 * 2 ** 4, N0=1200 + OP=OPdq(Id_ref=0, Iq_ref=0, N0=1200), + Ir=None, + Na_tot=2 ** 6, + Nt_tot=4 * 2 ** 4, ) simu.elec = None diff --git a/Tests/Validation/Force/test_ForceTensor.py b/Tests/Validation/Force/test_ForceTensor.py index 2967ef052..c6bc8c473 100644 --- a/Tests/Validation/Force/test_ForceTensor.py +++ b/Tests/Validation/Force/test_ForceTensor.py @@ -11,6 +11,7 @@ import matplotlib.pyplot as plt from pyleecan.Classes.ForceTensor import ForceTensor +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.MagFEMM import MagFEMM from pyleecan.Classes.InputCurrent import InputCurrent @@ -38,7 +39,10 @@ def test_Benchmark_Tensor(): simu = Simu1(name="Benchmark_Tensor", machine=Benchmark) simu.input = InputCurrent( - Id_ref=0, Iq_ref=0, Ir=None, Na_tot=2 ** 6, Nt_tot=1, N0=1200 + OP=OPdq(N0=1200, Id_ref=0, Iq_ref=0), + Ir=None, + Na_tot=2 ** 6, + Nt_tot=1, ) simu.elec = None diff --git a/Tests/Validation/Loss/test_FEMM_Loss.py b/Tests/Validation/Loss/test_FEMM_Loss.py index 273c4fa9c..2046f123b 100644 --- a/Tests/Validation/Loss/test_FEMM_Loss.py +++ b/Tests/Validation/Loss/test_FEMM_Loss.py @@ -2,6 +2,7 @@ import pytest from Tests import save_validation_path as save_path +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.InputCurrent import InputCurrent @@ -57,7 +58,10 @@ def test_FEMM_Loss(): simu = Simu1(name="test_FEMM_Loss", machine=machine) # Definition of the enforced output of the electrical module - simu.input = InputCurrent(Id_ref=Id_ref, Iq_ref=Iq_ref, Na_tot=2048, N0=rotor_speed) + simu.input = InputCurrent( + OP=OPdq(N0=rotor_speed, Id_ref=Id_ref, Iq_ref=Iq_ref), + Na_tot=2048, + ) # time discretization [s] # TODO without explicit time def. there is an error diff --git a/Tests/Validation/Magnetics/test_EM_BH_Model_001.py b/Tests/Validation/Magnetics/test_EM_BH_Model_001.py index 165be8368..2238671b0 100644 --- a/Tests/Validation/Magnetics/test_EM_BH_Model_001.py +++ b/Tests/Validation/Magnetics/test_EM_BH_Model_001.py @@ -2,6 +2,7 @@ from os.path import join import matplotlib.pyplot as plt from Tests import save_validation_path as save_path +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 @@ -51,7 +52,7 @@ def test_EM_BH_Model_001_Toyota_Prius(): simu.input = InputCurrent( Is=Is, Ir=None, # No winding on the rotor - N0=N0, + OP=OPdq(N0=N0), angle_rotor=None, # Will be computed Nt_tot=Nt_tot, Na_tot=Na_tot, diff --git a/Tests/Validation/Magnetics/test_FEMM_LamSlotMultiWind.py b/Tests/Validation/Magnetics/test_FEMM_LamSlotMultiWind.py index 45a4274dd..fce90582a 100644 --- a/Tests/Validation/Magnetics/test_FEMM_LamSlotMultiWind.py +++ b/Tests/Validation/Magnetics/test_FEMM_LamSlotMultiWind.py @@ -1,5 +1,6 @@ import pytest from os.path import join +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.InputCurrent import InputCurrent @@ -27,7 +28,10 @@ def test_FEMM_LamSlotMultiWind(): # "is_antiper_t": False, # } simu.input = InputCurrent( - Id_ref=0, Iq_ref=0, Ir=None, Na_tot=2 ** 6, Nt_tot=1, N0=1200 + OP=OPdq(N0=1200, Id_ref=0, Iq_ref=0), + Ir=None, + Na_tot=2 ** 6, + Nt_tot=1, ) simu.mag = MagFEMM( diff --git a/Tests/Validation/Magnetics/test_FEMM_compare.py b/Tests/Validation/Magnetics/test_FEMM_compare.py index 43b6a045e..39ff1fd54 100644 --- a/Tests/Validation/Magnetics/test_FEMM_compare.py +++ b/Tests/Validation/Magnetics/test_FEMM_compare.py @@ -5,6 +5,7 @@ import pytest from Tests import save_validation_path as save_path +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.InputFlux import InputFlux @@ -48,7 +49,7 @@ def test_FEMM_compare_IPMSM_xxx(): ) ) simu.input.Ir = None # SPMSM machine => no rotor currents to define - simu.input.N0 = 3000 # Rotor speed [rpm] + simu.input.OP = OPdq(N0=3000) # Rotor speed [rpm] simu.input.angle_rotor_initial = 0.5216 + pi # Rotor position at t=0 [rad] # Definition of the magnetic simulation (no symmetry) @@ -108,7 +109,7 @@ def test_FEMM_compare_Prius(): simu.input = InputCurrent( Is=Is, Ir=None, # No winding on the rotor - N0=N0, + OP=OPdq(N0=N0), angle_rotor=None, # Will be computed Nt_tot=Nt_tot, Na_tot=Na_tot, @@ -177,7 +178,7 @@ def test_FEMM_compare_SCIM(): simu.input = InputCurrent( Is=Is, Ir=Ir, # zero current for the rotor - N0=N0, + OP=OPdq(N0=N0), angle_rotor=None, # Will be computed time=time, Na_tot=Na_tot, @@ -210,7 +211,7 @@ def test_FEMM_compare_SCIM(): B_dict={"Br": Br}, Is=Is, Ir=Ir, - N0=simu.input.N0, + OP=OPdq(N0=simu.input.OP.N0), ) out = simu.run() @@ -277,7 +278,7 @@ def test_FEMM_compare_SIPMSM(): simu.input = InputCurrent( Is=Is, Ir=None, # No winding on the rotor - N0=N0, + OP=OPdq(N0=N0), angle_rotor=Ar, # Will be computed time=time, Nt_tot=Nt_tot, @@ -311,7 +312,7 @@ def test_FEMM_compare_SIPMSM(): B_dict={"Br": Br, "Bt": Bt}, Is=Is, Ir=None, # No winding on the rotor - N0=simu.input.N0, + OP=OPdq(N0=simu.input.OP.N0), ) out = simu.run() @@ -368,7 +369,7 @@ def test_SPMSM_load(): simu.input = InputCurrent( Is=Is, Ir=None, # No winding on the rotor - N0=N0, + OP=OPdq(N0=N0), angle_rotor=None, # Will be computed time=time, Na_tot=Na_tot, @@ -398,7 +399,7 @@ def test_SPMSM_load(): B_dict={"Br": Br, "Bt": Bt}, Is=Is, Ir=None, # No winding on the rotor - N0=simu.input.N0, + OP=OPdq(N0=simu.input.OP.N0), ) out = simu.run() @@ -459,7 +460,7 @@ def test_SPMSM_noload(): simu.input = InputCurrent( Is=Is, Ir=None, # No winding on the rotor - N0=N0, + OP=OPdq(N0=N0), angle_rotor=None, time=time, angle=angle, @@ -490,7 +491,7 @@ def test_SPMSM_noload(): B_dict={"Br": Br, "Bt": Bt}, Is=Is, Ir=None, # No winding on the rotor - N0=simu.input.N0, + OP=OPdq(N0=simu.input.OP.N0), ) out = simu.run() diff --git a/Tests/Validation/Magnetics/test_FEMM_import_dxf.py b/Tests/Validation/Magnetics/test_FEMM_import_dxf.py index 1c9ca09c5..422d0f04a 100644 --- a/Tests/Validation/Magnetics/test_FEMM_import_dxf.py +++ b/Tests/Validation/Magnetics/test_FEMM_import_dxf.py @@ -2,6 +2,7 @@ import matplotlib.pyplot as plt from Tests import save_validation_path as save_path from Tests import TEST_DATA_DIR +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.DXFImport import DXFImport @@ -40,11 +41,9 @@ def test_FEMM_import_dxf(): simu.struct = None simu.input = InputCurrent() - simu.input.Id_ref = -100 # [A] - simu.input.Iq_ref = 200 # [A] + simu.input.OP = OPdq(Id_ref=-100, Iq_ref=200, N0=2000) simu.input.Nt_tot = 1 # Number of time step simu.input.Na_tot = 2048 # Spatial discretization - simu.input.N0 = 2000 # Rotor speed [rpm] simu.input.rot_dir = 1 # To enforce the rotation direction # DXF import setup diff --git a/Tests/Validation/Magnetics/test_FEMM_import_model.py b/Tests/Validation/Magnetics/test_FEMM_import_model.py index 2d13f6f3e..2d4a3e0ab 100644 --- a/Tests/Validation/Magnetics/test_FEMM_import_model.py +++ b/Tests/Validation/Magnetics/test_FEMM_import_model.py @@ -3,6 +3,7 @@ import matplotlib.pyplot as plt from Tests import save_validation_path as save_path from numpy.testing import assert_array_almost_equal +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.InputCurrent import InputCurrent @@ -46,7 +47,7 @@ def test_FEMM_import_model(): ) ) simu.input.Ir = None # SPMSM machine => no rotor currents to define - simu.input.N0 = 3000 # Rotor speed [rpm] + simu.input.OP = OPdq(N0=3000) # Rotor speed [rpm] # Definition of the magnetic simulation simu.mag = MagFEMM( diff --git a/Tests/Validation/Magnetics/test_FEMM_meshsolution_plots.py b/Tests/Validation/Magnetics/test_FEMM_meshsolution_plots.py index 0380ee09b..a1e0d6fa0 100644 --- a/Tests/Validation/Magnetics/test_FEMM_meshsolution_plots.py +++ b/Tests/Validation/Magnetics/test_FEMM_meshsolution_plots.py @@ -7,6 +7,7 @@ from pyleecan.Classes.ImportMatrixVal import ImportMatrixVal from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.MagFEMM import MagFEMM +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Output import Output from pyleecan.Classes.Simu1 import Simu1 from pyleecan.definitions import DATA_DIR @@ -52,7 +53,7 @@ def test_FEMM_meshsolution_plots_SPMSM(): simu.input = InputCurrent( Is=Is, Ir=None, # No winding on the rotor - N0=N0, + OP=OPdq(N0=N0), angle_rotor=None, # Will be computed time=time, Na_tot=Na_tot, @@ -154,7 +155,10 @@ def test_FEMM_meshsolution_plots_slotless(): simu = Simu1(name="test_meshsolution_plots_slotless", machine=Slotless_CEFC) simu.input = InputCurrent( - Id_ref=0, Iq_ref=0, Ir=None, Na_tot=2 ** 6, Nt_tot=1, N0=1200 + OP=OPdq(N0=1200, Id_ref=0, Iq_ref=0), + Ir=None, + Na_tot=2 ** 6, + Nt_tot=1, ) simu.mag = MagFEMM( @@ -284,7 +288,7 @@ def test_FEMM_meshsolution_plots_Prius(): simu.input = InputCurrent( Is=Is, Ir=None, # No winding on the rotor - N0=N0, + OP=OPdq(N0=N0), angle_rotor=None, # Will be computed Nt_tot=Nt_tot, Na_tot=Na_tot, diff --git a/Tests/Validation/Magnetics/test_FEMM_parallelization.py b/Tests/Validation/Magnetics/test_FEMM_parallelization.py index f02890ddf..93e854b27 100644 --- a/Tests/Validation/Magnetics/test_FEMM_parallelization.py +++ b/Tests/Validation/Magnetics/test_FEMM_parallelization.py @@ -10,6 +10,7 @@ from pyleecan.Classes.ImportMatrixVal import ImportMatrixVal from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.MagFEMM import MagFEMM +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Functions.load import load from pyleecan.Functions.Plot import dict_2D @@ -31,11 +32,9 @@ def test_FEMM_parallelization_mag(): # Definition of a sinusoidal current simu.input = InputCurrent() - simu.input.Id_ref = -100 # [A] - simu.input.Iq_ref = 200 # [A] + simu.input.OP = OPdq(Id_ref=-100, Iq_ref=200, N0=2000) simu.input.Nt_tot = 16 # Number of time step simu.input.Na_tot = 1024 # Spatial discretization - simu.input.N0 = 2000 # Rotor speed [rpm] # Definition of the magnetic simulation simu.mag = MagFEMM( @@ -156,7 +155,7 @@ def test_FEMM_parallelization_meshsolution(): simu.input = InputCurrent( Is=Is, Ir=None, # No winding on the rotor - N0=N0, + OP=OPdq(N0=N0), angle_rotor=None, # Will be computed time=time, angle=angle, diff --git a/Tests/Validation/Magnetics/test_FEMM_periodicity.py b/Tests/Validation/Magnetics/test_FEMM_periodicity.py index 47e294227..3ebe5a8f6 100644 --- a/Tests/Validation/Magnetics/test_FEMM_periodicity.py +++ b/Tests/Validation/Magnetics/test_FEMM_periodicity.py @@ -6,6 +6,7 @@ from numpy import exp, sqrt, pi, meshgrid, zeros, real from numpy.testing import assert_array_almost_equal +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 import matplotlib.pyplot as plt @@ -47,11 +48,9 @@ def test_FEMM_periodicity_time_no_periodicity_a(): Iq_ref = (I0_rms * exp(1j * Phi0)).imag simu.input = InputCurrent( - Id_ref=Id_ref, - Iq_ref=Iq_ref, + OP=OPdq(N0=1000, Id_ref=Id_ref, Iq_ref=Iq_ref), Na_tot=252 * 9, Nt_tot=4 * 9, - N0=1000, ) # Definition of the magnetic simulation: with periodicity @@ -197,11 +196,9 @@ def test_FEMM_periodicity_time(): Iq_ref = (I0_rms * exp(1j * Phi0)).imag simu.input = InputCurrent( - Id_ref=Id_ref, - Iq_ref=Iq_ref, + OP=OPdq(N0=1000, Id_ref=Id_ref, Iq_ref=Iq_ref), Na_tot=252 * 9, Nt_tot=4 * 9, - N0=1000, ) # Definition of the magnetic simulation: with periodicity @@ -394,11 +391,9 @@ def test_FEMM_periodicity_angle(): Iq_ref = (I0_rms * exp(1j * Phi0)).imag simu.input = InputCurrent( - Id_ref=Id_ref, - Iq_ref=Iq_ref, + OP=OPdq(N0=1000, Id_ref=Id_ref, Iq_ref=Iq_ref), Na_tot=252 * 9, Nt_tot=4 * 9, - N0=1000, ) # Definition of the magnetic simulation: with periodicity diff --git a/Tests/Validation/Magnetics/test_FEMM_set_previous.py b/Tests/Validation/Magnetics/test_FEMM_set_previous.py index 9c16e5815..9b9d42fa1 100644 --- a/Tests/Validation/Magnetics/test_FEMM_set_previous.py +++ b/Tests/Validation/Magnetics/test_FEMM_set_previous.py @@ -7,6 +7,7 @@ from Tests import save_validation_path as save_path from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.MagFEMM import MagFEMM +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Functions.load import load from pyleecan.Functions.Plot import dict_2D @@ -32,11 +33,9 @@ def test_FEMM_set_previous(): # Definition of a sinusoidal current simu.input = InputCurrent() - simu.input.Id_ref = -100 # [A] - simu.input.Iq_ref = 200 # [A] + simu.input.OP = OPdq(Id_ref=-100, Iq_ref=200, N0=2000) simu.input.Nt_tot = 32 * 4 # Number of time step simu.input.Na_tot = 1024 # Spatial discretization - simu.input.N0 = 2000 # Rotor speed [rpm] # Definition of the magnetic simulation with previous ans file simu.mag = MagFEMM( diff --git a/Tests/Validation/Magnetics/test_FEMM_skew.py b/Tests/Validation/Magnetics/test_FEMM_skew.py index 431aa2053..f5b8ac0c6 100644 --- a/Tests/Validation/Magnetics/test_FEMM_skew.py +++ b/Tests/Validation/Magnetics/test_FEMM_skew.py @@ -6,6 +6,7 @@ from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.MagFEMM import MagFEMM +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Skew import Skew from pyleecan.Classes.Simu1 import Simu1 @@ -46,10 +47,7 @@ def comp_skew_angle(Zs, p, Nstep): simu_no_skew = Simu1(name=name + "_none", machine=SPMSM_no_skew) simu_no_skew.input = InputCurrent( - N0=1200, - Id_ref=0, - Iq_ref=0, - Tem_av_ref=0, + OP=OPdq(N0=1200, Id_ref=0, Iq_ref=0, Tem_av_ref=0), Na_tot=400 * 4, Nt_tot=40 * 4, ) diff --git a/Tests/Validation/Magnetics/test_FEMM_slotless.py b/Tests/Validation/Magnetics/test_FEMM_slotless.py index fcb2c88eb..4d4710b55 100644 --- a/Tests/Validation/Magnetics/test_FEMM_slotless.py +++ b/Tests/Validation/Magnetics/test_FEMM_slotless.py @@ -1,5 +1,6 @@ import pytest from os.path import join +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.InputCurrent import InputCurrent @@ -27,7 +28,10 @@ def test_FEMM_slotless(): simu = Simu1(name="test_FEMM_slotless", machine=Slotless_CEFC) simu.input = InputCurrent( - Id_ref=0, Iq_ref=0, Ir=None, Na_tot=2 ** 6, Nt_tot=1, N0=1200 + OP=OPdq(N0=1200, Id_ref=0, Iq_ref=0), + Ir=None, + Na_tot=2 ** 6, + Nt_tot=1, ) simu.mag = MagFEMM( diff --git a/Tests/Validation/Magnetics/test_FEMM_torque.py b/Tests/Validation/Magnetics/test_FEMM_torque.py index 233eeb708..9f299e7d2 100644 --- a/Tests/Validation/Magnetics/test_FEMM_torque.py +++ b/Tests/Validation/Magnetics/test_FEMM_torque.py @@ -4,6 +4,7 @@ from numpy import array, ones, pi, zeros, sqrt from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.MagFEMM import MagFEMM +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.VarLoadCurrent import VarLoadCurrent from pyleecan.definitions import DATA_DIR @@ -98,7 +99,7 @@ def test_FEMM_torque(): simu.input = InputCurrent( Is=None, Ir=None, # No winding on the rotor - N0=N0, + OP=OPdq(N0=N0), Nt_tot=Nt_tot, Nrev=1 / 6, Na_tot=Na_tot, diff --git a/Tests/Validation/Multisimulation/plot_save.py b/Tests/Validation/Multisimulation/plot_save.py index a6f7854b7..3b16c26db 100644 --- a/Tests/Validation/Multisimulation/plot_save.py +++ b/Tests/Validation/Multisimulation/plot_save.py @@ -48,7 +48,7 @@ def plot_save(output): "Simulation W0=" + format(output.simu.machine.stator.slot.W0, ".4g") + " N0=" - + format(output.simu.input.N0, ".4g"), + + format(output.simu.input.OP.N0, ".4g"), fontsize=20, ) fig.savefig( @@ -57,7 +57,7 @@ def plot_save(output): "machine_W0=" + format(output.simu.machine.stator.slot.W0, ".4g") + "_N0=" - + format(output.simu.input.N0, ".4g") + + format(output.simu.input.OP.N0, ".4g") + ".png", ), dpi=100, diff --git a/Tests/Validation/Multisimulation/test_multi_multi.py b/Tests/Validation/Multisimulation/test_multi_multi.py index 41979d0bc..57c334b99 100644 --- a/Tests/Validation/Multisimulation/test_multi_multi.py +++ b/Tests/Validation/Multisimulation/test_multi_multi.py @@ -16,6 +16,7 @@ from pyleecan.Classes.IndMagFEMM import IndMagFEMM from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.MagFEMM import MagFEMM +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Output import Output from pyleecan.Classes.ParamExplorerInterval import ParamExplorerInterval from pyleecan.Classes.ParamExplorerSet import ParamExplorerSet @@ -130,9 +131,7 @@ def test_multi_multi(): simu.input = InputCurrent( Is=None, Ir=None, # No winding on the rotor - N0=N0_MTPA[0], - Id_ref=Id_MTPA[0], - Iq_ref=Iq_MTPA[0], + OP=OPdq(N0=N0_MTPA[0], Id_ref=Id_MTPA[0], Iq_ref=Iq_MTPA[0]), angle_rotor=None, # Will be computed Nt_tot=Nt_tot, Na_tot=2048, diff --git a/Tests/Validation/Multisimulation/test_slot_scale.py b/Tests/Validation/Multisimulation/test_slot_scale.py index d7f34be9b..b64f8f3fb 100644 --- a/Tests/Validation/Multisimulation/test_slot_scale.py +++ b/Tests/Validation/Multisimulation/test_slot_scale.py @@ -1,4 +1,5 @@ # Multisimulation objects +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.VarParam import VarParam from pyleecan.Classes.ParamExplorerSet import ParamExplorerSet from pyleecan.Classes.DataKeeper import DataKeeper @@ -53,7 +54,7 @@ def test_slot_scale(): ref_simu.input = InputCurrent( Is=Is, Ir=None, # No winding on the rotor - N0=2504, + OP=OPdq(N0=2504), angle_rotor=None, # Will be computed time=time, Na_tot=Na_tot, diff --git a/Tests/Validation/Multisimulation/test_varload.py b/Tests/Validation/Multisimulation/test_varload.py index ac172d2dd..6e2fad42f 100644 --- a/Tests/Validation/Multisimulation/test_varload.py +++ b/Tests/Validation/Multisimulation/test_varload.py @@ -2,6 +2,8 @@ import sys from os.path import dirname, abspath, normpath, join +from pyleecan.Classes.OPdq import OPdq + sys.path.insert(0, normpath(abspath(join(dirname(__file__), "..", "..", "..")))) sys.path.insert(0, normpath(abspath(dirname(__file__)))) @@ -68,7 +70,7 @@ def test_varload(): simu.input.Nt_tot = 8 * 5 # Number of time step simu.input.Na_tot = 2048 # Spatial discretization - simu.input.N0 = 2000 # Rotor speed [rpm] + simu.input.OP = OPdq(N0=2000) varload = VarLoadCurrent(is_torque=True) varload.type_OP_matrix = 0 # Matrix N0, I0, Phi0, Tem_ref diff --git a/Tests/Validation/Optimization/test_Binh_and_Korn.py b/Tests/Validation/Optimization/test_Binh_and_Korn.py index cd894dfad..621326df5 100644 --- a/Tests/Validation/Optimization/test_Binh_and_Korn.py +++ b/Tests/Validation/Optimization/test_Binh_and_Korn.py @@ -13,6 +13,7 @@ from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.MagFEMM import MagFEMM from pyleecan.Classes.Simu1 import Simu1 +from pyleecan.Classes.OPslip import OPslip from pyleecan.Classes.Output import Output from pyleecan.Classes.OptiDesignVar import OptiDesignVar from pyleecan.Classes.OptiObjective import OptiObjective @@ -64,7 +65,7 @@ def test_Binh_and_Korn(): simu.input = InputCurrent( Is=Is, Ir=Ir, # zero current for the rotor - N0=N0, + OP=OPslip(N0=N0), angle_rotor=None, # Will be computed time=time, Na_tot=Na_tot, diff --git a/Tests/Validation/Optimization/test_zdt3.py b/Tests/Validation/Optimization/test_zdt3.py index 1af96d3a8..f51e9839c 100644 --- a/Tests/Validation/Optimization/test_zdt3.py +++ b/Tests/Validation/Optimization/test_zdt3.py @@ -5,6 +5,7 @@ """ from os.path import join import pytest +from pyleecan.Classes.OPslip import OPslip from pyleecan.definitions import PACKAGE_NAME from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.MagFEMM import MagFEMM @@ -59,7 +60,7 @@ def test_zdt3(): simu.input = InputCurrent( Is=Is, Ir=Ir, # zero current for the rotor - N0=N0, + OP=OPslip(N0=N0), angle_rotor=None, # Will be computed time=time, Na_tot=Na_tot, diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 950868700..f1e809e42 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -1036,7 +1036,7 @@ "methods": [ "comp_parameters", "solve_EEC", - "gen_drive" + "comp_joule_losses" ], "mother": "EEC", "name": "EEC_ANL", @@ -4038,12 +4038,12 @@ "value": 2048 }, { - "desc": "Rotor speed", + "desc": "Operating Point", "max": "", "min": "", - "name": "N0", - "type": "float", - "unit": "rpm", + "name": "OP", + "type": "OP", + "unit": "-", "value": null } ] @@ -4087,24 +4087,6 @@ "type": "ImportMatrix", "unit": "A", "value": null - }, - { - "desc": "d-axis current RMS magnitude", - "max": "", - "min": "", - "name": "Id_ref", - "type": "float", - "unit": "A", - "value": null - }, - { - "desc": "q-axis current RMS magnitude", - "max": "", - "min": "", - "name": "Iq_ref", - "type": "float", - "unit": "A", - "value": null } ] }, @@ -4278,78 +4260,6 @@ "unit": "", "value": 0 }, - { - "desc": "Theorical Average Electromagnetic torque", - "max": "", - "min": "", - "name": "Tem_av_ref", - "type": "float", - "unit": "N.m", - "value": null - }, - { - "desc": "d-axis current RMS magnitude (phase to neutral)", - "max": "", - "min": "", - "name": "Ud_ref", - "type": "float", - "unit": "Vrms", - "value": null - }, - { - "desc": "q-axis current RMS magnitude (phase to neutral)", - "max": "", - "min": "", - "name": "Uq_ref", - "type": "float", - "unit": "Vrms", - "value": null - }, - { - "desc": "electrical frequency", - "max": "", - "min": "", - "name": "felec", - "type": "float", - "unit": "Hz", - "value": null - }, - { - "desc": "Rotor mechanical slip", - "max": "", - "min": "", - "name": "slip_ref", - "type": "float", - "unit": "", - "value": 0 - }, - { - "desc": "stator voltage (phase to neutral)", - "max": "", - "min": "", - "name": "U0_ref", - "type": "float", - "unit": "Vrms", - "value": null - }, - { - "desc": "stator voltage phase", - "max": "", - "min": "", - "name": "Phi0_ref", - "type": "float", - "unit": "rad", - "value": null - }, - { - "desc": "Theorical Average Electromagnetic Power", - "max": "", - "min": "", - "name": "Pem_av_ref", - "type": "float", - "unit": "W", - "value": null - }, { "desc": "Object to generate PWM signal", "max": "", @@ -8310,6 +8220,15 @@ "type": "float", "unit": "N.m", "value": null + }, + { + "desc": "Harmonic stator current ", + "max": "", + "min": "", + "name": "Is_harm", + "type": "SciDataTool.Classes.DataND.DataND", + "unit": "A", + "value": "None" } ] }, diff --git a/pyleecan/Classes/EEC_ANL.py b/pyleecan/Classes/EEC_ANL.py index b66ff2179..e124a57bf 100644 --- a/pyleecan/Classes/EEC_ANL.py +++ b/pyleecan/Classes/EEC_ANL.py @@ -28,9 +28,9 @@ solve_EEC = error try: - from ..Methods.Simulation.EEC_ANL.gen_drive import gen_drive + from ..Methods.Simulation.EEC_ANL.comp_joule_losses import comp_joule_losses except ImportError as error: - gen_drive = error + comp_joule_losses = error from ._check import InitUnKnowClassError @@ -63,15 +63,18 @@ class EEC_ANL(EEC): ) else: solve_EEC = solve_EEC - # cf Methods.Simulation.EEC_ANL.gen_drive - if isinstance(gen_drive, ImportError): - gen_drive = property( + # cf Methods.Simulation.EEC_ANL.comp_joule_losses + if isinstance(comp_joule_losses, ImportError): + comp_joule_losses = property( fget=lambda x: raise_( - ImportError("Can't use EEC_ANL method gen_drive: " + str(gen_drive)) + ImportError( + "Can't use EEC_ANL method comp_joule_losses: " + + str(comp_joule_losses) + ) ) ) else: - gen_drive = gen_drive + comp_joule_losses = comp_joule_losses # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/Input.py b/pyleecan/Classes/Input.py index 44b350d04..481a474e6 100644 --- a/pyleecan/Classes/Input.py +++ b/pyleecan/Classes/Input.py @@ -43,6 +43,7 @@ from numpy import array, array_equal from ._check import InitUnKnowClassError from .ImportMatrix import ImportMatrix +from .OP import OP class Input(FrozenClass): @@ -106,7 +107,7 @@ def __init__( Nt_tot=2048, Nrev=None, Na_tot=2048, - N0=None, + OP=None, init_dict=None, init_str=None, ): @@ -135,8 +136,8 @@ def __init__( Nrev = init_dict["Nrev"] if "Na_tot" in list(init_dict.keys()): Na_tot = init_dict["Na_tot"] - if "N0" in list(init_dict.keys()): - N0 = init_dict["N0"] + if "OP" in list(init_dict.keys()): + OP = init_dict["OP"] # Set the properties (value check and convertion are done in setter) self.parent = None self.time = time @@ -144,7 +145,7 @@ def __init__( self.Nt_tot = Nt_tot self.Nrev = Nrev self.Na_tot = Na_tot - self.N0 = N0 + self.OP = OP # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -170,7 +171,11 @@ def __str__(self): Input_str += "Nt_tot = " + str(self.Nt_tot) + linesep Input_str += "Nrev = " + str(self.Nrev) + linesep Input_str += "Na_tot = " + str(self.Na_tot) + linesep - Input_str += "N0 = " + str(self.N0) + linesep + if self.OP is not None: + tmp = self.OP.__str__().replace(linesep, linesep + "\t").rstrip("\t") + Input_str += "OP = " + tmp + else: + Input_str += "OP = None" + linesep + linesep return Input_str def __eq__(self, other): @@ -188,7 +193,7 @@ def __eq__(self, other): return False if other.Na_tot != self.Na_tot: return False - if other.N0 != self.N0: + if other.OP != self.OP: return False return True @@ -218,8 +223,12 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".Nrev") if other._Na_tot != self._Na_tot: diff_list.append(name + ".Na_tot") - if other._N0 != self._N0: - diff_list.append(name + ".N0") + if (other.OP is None and self.OP is not None) or ( + other.OP is not None and self.OP is None + ): + diff_list.append(name + ".OP None mismatch") + elif self.OP is not None: + diff_list.extend(self.OP.compare(other.OP, name=name + ".OP")) # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -233,7 +242,7 @@ def __sizeof__(self): S += getsizeof(self.Nt_tot) S += getsizeof(self.Nrev) S += getsizeof(self.Na_tot) - S += getsizeof(self.N0) + S += getsizeof(self.OP) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -267,7 +276,14 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): Input_dict["Nt_tot"] = self.Nt_tot Input_dict["Nrev"] = self.Nrev Input_dict["Na_tot"] = self.Na_tot - Input_dict["N0"] = self.N0 + if self.OP is None: + Input_dict["OP"] = None + else: + Input_dict["OP"] = self.OP.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) # The class name is added to the dict for deserialisation purpose Input_dict["__class__"] = "Input" return Input_dict @@ -282,7 +298,8 @@ def _set_None(self): self.Nt_tot = None self.Nrev = None self.Na_tot = None - self.N0 = None + if self.OP is not None: + self.OP._set_None() def _get_time(self): """getter of time""" @@ -407,20 +424,30 @@ def _set_Na_tot(self, value): """, ) - def _get_N0(self): - """getter of N0""" - return self._N0 + def _get_OP(self): + """getter of OP""" + return self._OP + + def _set_OP(self, value): + """setter of OP""" + if isinstance(value, str): # Load from file + value = load_init_dict(value)[1] + if isinstance(value, dict) and "__class__" in value: + class_obj = import_class("pyleecan.Classes", value.get("__class__"), "OP") + value = class_obj(init_dict=value) + elif type(value) is int and value == -1: # Default constructor + value = OP() + check_var("OP", value, "OP") + self._OP = value - def _set_N0(self, value): - """setter of N0""" - check_var("N0", value, "float") - self._N0 = value + if self._OP is not None: + self._OP.parent = self - N0 = property( - fget=_get_N0, - fset=_set_N0, - doc=u"""Rotor speed + OP = property( + fget=_get_OP, + fset=_set_OP, + doc=u"""Operating Point - :Type: float + :Type: OP """, ) diff --git a/pyleecan/Classes/InputCurrent.py b/pyleecan/Classes/InputCurrent.py index 2208af700..fb76c2e68 100644 --- a/pyleecan/Classes/InputCurrent.py +++ b/pyleecan/Classes/InputCurrent.py @@ -40,6 +40,7 @@ from .ImportMatrix import ImportMatrix from .Import import Import from .ImportGenPWM import ImportGenPWM +from .OP import OP class InputCurrent(InputVoltage): @@ -92,26 +93,16 @@ def __init__( self, Is=None, Ir=None, - Id_ref=None, - Iq_ref=None, angle_rotor=None, rot_dir=None, angle_rotor_initial=0, - Tem_av_ref=None, - Ud_ref=None, - Uq_ref=None, - felec=None, - slip_ref=0, - U0_ref=None, - Phi0_ref=None, - Pem_av_ref=None, PWM=None, time=None, angle=None, Nt_tot=2048, Nrev=None, Na_tot=2048, - N0=None, + OP=None, init_dict=None, init_str=None, ): @@ -134,32 +125,12 @@ def __init__( Is = init_dict["Is"] if "Ir" in list(init_dict.keys()): Ir = init_dict["Ir"] - if "Id_ref" in list(init_dict.keys()): - Id_ref = init_dict["Id_ref"] - if "Iq_ref" in list(init_dict.keys()): - Iq_ref = init_dict["Iq_ref"] if "angle_rotor" in list(init_dict.keys()): angle_rotor = init_dict["angle_rotor"] if "rot_dir" in list(init_dict.keys()): rot_dir = init_dict["rot_dir"] if "angle_rotor_initial" in list(init_dict.keys()): angle_rotor_initial = init_dict["angle_rotor_initial"] - if "Tem_av_ref" in list(init_dict.keys()): - Tem_av_ref = init_dict["Tem_av_ref"] - if "Ud_ref" in list(init_dict.keys()): - Ud_ref = init_dict["Ud_ref"] - if "Uq_ref" in list(init_dict.keys()): - Uq_ref = init_dict["Uq_ref"] - if "felec" in list(init_dict.keys()): - felec = init_dict["felec"] - if "slip_ref" in list(init_dict.keys()): - slip_ref = init_dict["slip_ref"] - if "U0_ref" in list(init_dict.keys()): - U0_ref = init_dict["U0_ref"] - if "Phi0_ref" in list(init_dict.keys()): - Phi0_ref = init_dict["Phi0_ref"] - if "Pem_av_ref" in list(init_dict.keys()): - Pem_av_ref = init_dict["Pem_av_ref"] if "PWM" in list(init_dict.keys()): PWM = init_dict["PWM"] if "time" in list(init_dict.keys()): @@ -172,33 +143,23 @@ def __init__( Nrev = init_dict["Nrev"] if "Na_tot" in list(init_dict.keys()): Na_tot = init_dict["Na_tot"] - if "N0" in list(init_dict.keys()): - N0 = init_dict["N0"] + if "OP" in list(init_dict.keys()): + OP = init_dict["OP"] # Set the properties (value check and convertion are done in setter) self.Is = Is self.Ir = Ir - self.Id_ref = Id_ref - self.Iq_ref = Iq_ref # Call InputVoltage init super(InputCurrent, self).__init__( angle_rotor=angle_rotor, rot_dir=rot_dir, angle_rotor_initial=angle_rotor_initial, - Tem_av_ref=Tem_av_ref, - Ud_ref=Ud_ref, - Uq_ref=Uq_ref, - felec=felec, - slip_ref=slip_ref, - U0_ref=U0_ref, - Phi0_ref=Phi0_ref, - Pem_av_ref=Pem_av_ref, PWM=PWM, time=time, angle=angle, Nt_tot=Nt_tot, Nrev=Nrev, Na_tot=Na_tot, - N0=N0, + OP=OP, ) # The class is frozen (in InputVoltage init), for now it's impossible to # add new properties @@ -219,8 +180,6 @@ def __str__(self): InputCurrent_str += "Ir = " + tmp else: InputCurrent_str += "Ir = None" + linesep + linesep - InputCurrent_str += "Id_ref = " + str(self.Id_ref) + linesep - InputCurrent_str += "Iq_ref = " + str(self.Iq_ref) + linesep return InputCurrent_str def __eq__(self, other): @@ -236,10 +195,6 @@ def __eq__(self, other): return False if other.Ir != self.Ir: return False - if other.Id_ref != self.Id_ref: - return False - if other.Iq_ref != self.Iq_ref: - return False return True def compare(self, other, name="self", ignore_list=None): @@ -265,10 +220,6 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".Ir None mismatch") elif self.Ir is not None: diff_list.extend(self.Ir.compare(other.Ir, name=name + ".Ir")) - if other._Id_ref != self._Id_ref: - diff_list.append(name + ".Id_ref") - if other._Iq_ref != self._Iq_ref: - diff_list.append(name + ".Iq_ref") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -282,8 +233,6 @@ def __sizeof__(self): S += super(InputCurrent, self).__sizeof__() S += getsizeof(self.Is) S += getsizeof(self.Ir) - S += getsizeof(self.Id_ref) - S += getsizeof(self.Iq_ref) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -319,8 +268,6 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): keep_function=keep_function, **kwargs ) - InputCurrent_dict["Id_ref"] = self.Id_ref - InputCurrent_dict["Iq_ref"] = self.Iq_ref # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name InputCurrent_dict["__class__"] = "InputCurrent" @@ -333,8 +280,6 @@ def _set_None(self): self.Is._set_None() if self.Ir is not None: self.Ir._set_None() - self.Id_ref = None - self.Iq_ref = None # Set to None the properties inherited from InputVoltage super(InputCurrent, self)._set_None() @@ -401,39 +346,3 @@ def _set_Ir(self, value): :Type: ImportMatrix """, ) - - def _get_Id_ref(self): - """getter of Id_ref""" - return self._Id_ref - - def _set_Id_ref(self, value): - """setter of Id_ref""" - check_var("Id_ref", value, "float") - self._Id_ref = value - - Id_ref = property( - fget=_get_Id_ref, - fset=_set_Id_ref, - doc=u"""d-axis current RMS magnitude - - :Type: float - """, - ) - - def _get_Iq_ref(self): - """getter of Iq_ref""" - return self._Iq_ref - - def _set_Iq_ref(self, value): - """setter of Iq_ref""" - check_var("Iq_ref", value, "float") - self._Iq_ref = value - - Iq_ref = property( - fget=_get_Iq_ref, - fset=_set_Iq_ref, - doc=u"""q-axis current RMS magnitude - - :Type: float - """, - ) diff --git a/pyleecan/Classes/InputFlux.py b/pyleecan/Classes/InputFlux.py index 29987a199..b1c7a167c 100644 --- a/pyleecan/Classes/InputFlux.py +++ b/pyleecan/Classes/InputFlux.py @@ -30,6 +30,7 @@ from .ImportMatrix import ImportMatrix from .Import import Import from .ImportGenPWM import ImportGenPWM +from .OP import OP class InputFlux(InputCurrent): @@ -64,26 +65,16 @@ def __init__( B_enforced=None, Is=None, Ir=None, - Id_ref=None, - Iq_ref=None, angle_rotor=None, rot_dir=None, angle_rotor_initial=0, - Tem_av_ref=None, - Ud_ref=None, - Uq_ref=None, - felec=None, - slip_ref=0, - U0_ref=None, - Phi0_ref=None, - Pem_av_ref=None, PWM=None, time=None, angle=None, Nt_tot=2048, Nrev=None, Na_tot=2048, - N0=None, + OP=None, init_dict=None, init_str=None, ): @@ -122,32 +113,12 @@ def __init__( Is = init_dict["Is"] if "Ir" in list(init_dict.keys()): Ir = init_dict["Ir"] - if "Id_ref" in list(init_dict.keys()): - Id_ref = init_dict["Id_ref"] - if "Iq_ref" in list(init_dict.keys()): - Iq_ref = init_dict["Iq_ref"] if "angle_rotor" in list(init_dict.keys()): angle_rotor = init_dict["angle_rotor"] if "rot_dir" in list(init_dict.keys()): rot_dir = init_dict["rot_dir"] if "angle_rotor_initial" in list(init_dict.keys()): angle_rotor_initial = init_dict["angle_rotor_initial"] - if "Tem_av_ref" in list(init_dict.keys()): - Tem_av_ref = init_dict["Tem_av_ref"] - if "Ud_ref" in list(init_dict.keys()): - Ud_ref = init_dict["Ud_ref"] - if "Uq_ref" in list(init_dict.keys()): - Uq_ref = init_dict["Uq_ref"] - if "felec" in list(init_dict.keys()): - felec = init_dict["felec"] - if "slip_ref" in list(init_dict.keys()): - slip_ref = init_dict["slip_ref"] - if "U0_ref" in list(init_dict.keys()): - U0_ref = init_dict["U0_ref"] - if "Phi0_ref" in list(init_dict.keys()): - Phi0_ref = init_dict["Phi0_ref"] - if "Pem_av_ref" in list(init_dict.keys()): - Pem_av_ref = init_dict["Pem_av_ref"] if "PWM" in list(init_dict.keys()): PWM = init_dict["PWM"] if "time" in list(init_dict.keys()): @@ -160,8 +131,8 @@ def __init__( Nrev = init_dict["Nrev"] if "Na_tot" in list(init_dict.keys()): Na_tot = init_dict["Na_tot"] - if "N0" in list(init_dict.keys()): - N0 = init_dict["N0"] + if "OP" in list(init_dict.keys()): + OP = init_dict["OP"] # Set the properties (value check and convertion are done in setter) self.per_a = per_a self.per_t = per_t @@ -175,26 +146,16 @@ def __init__( super(InputFlux, self).__init__( Is=Is, Ir=Ir, - Id_ref=Id_ref, - Iq_ref=Iq_ref, angle_rotor=angle_rotor, rot_dir=rot_dir, angle_rotor_initial=angle_rotor_initial, - Tem_av_ref=Tem_av_ref, - Ud_ref=Ud_ref, - Uq_ref=Uq_ref, - felec=felec, - slip_ref=slip_ref, - U0_ref=U0_ref, - Phi0_ref=Phi0_ref, - Pem_av_ref=Pem_av_ref, PWM=PWM, time=time, angle=angle, Nt_tot=Nt_tot, Nrev=Nrev, Na_tot=Na_tot, - N0=N0, + OP=OP, ) # The class is frozen (in InputCurrent init), for now it's impossible to # add new properties diff --git a/pyleecan/Classes/InputForce.py b/pyleecan/Classes/InputForce.py index 2f92b4346..81d944de3 100644 --- a/pyleecan/Classes/InputForce.py +++ b/pyleecan/Classes/InputForce.py @@ -29,6 +29,7 @@ from ._check import InitUnKnowClassError from .ImportVectorField import ImportVectorField from .ImportMatrix import ImportMatrix +from .OP import OP class InputForce(Input): @@ -59,7 +60,7 @@ def __init__( Nt_tot=2048, Nrev=None, Na_tot=2048, - N0=None, + OP=None, init_dict=None, init_str=None, ): @@ -90,13 +91,13 @@ def __init__( Nrev = init_dict["Nrev"] if "Na_tot" in list(init_dict.keys()): Na_tot = init_dict["Na_tot"] - if "N0" in list(init_dict.keys()): - N0 = init_dict["N0"] + if "OP" in list(init_dict.keys()): + OP = init_dict["OP"] # Set the properties (value check and convertion are done in setter) self.P = P # Call Input init super(InputForce, self).__init__( - time=time, angle=angle, Nt_tot=Nt_tot, Nrev=Nrev, Na_tot=Na_tot, N0=N0 + time=time, angle=angle, Nt_tot=Nt_tot, Nrev=Nrev, Na_tot=Na_tot, OP=OP ) # The class is frozen (in Input init), for now it's impossible to # add new properties diff --git a/pyleecan/Classes/InputVoltage.py b/pyleecan/Classes/InputVoltage.py index 9f210a925..9f20ce7b0 100644 --- a/pyleecan/Classes/InputVoltage.py +++ b/pyleecan/Classes/InputVoltage.py @@ -35,6 +35,7 @@ from .Import import Import from .ImportGenPWM import ImportGenPWM from .ImportMatrix import ImportMatrix +from .OP import OP class InputVoltage(Input): @@ -77,21 +78,13 @@ def __init__( angle_rotor=None, rot_dir=None, angle_rotor_initial=0, - Tem_av_ref=None, - Ud_ref=None, - Uq_ref=None, - felec=None, - slip_ref=0, - U0_ref=None, - Phi0_ref=None, - Pem_av_ref=None, PWM=None, time=None, angle=None, Nt_tot=2048, Nrev=None, Na_tot=2048, - N0=None, + OP=None, init_dict=None, init_str=None, ): @@ -116,22 +109,6 @@ def __init__( rot_dir = init_dict["rot_dir"] if "angle_rotor_initial" in list(init_dict.keys()): angle_rotor_initial = init_dict["angle_rotor_initial"] - if "Tem_av_ref" in list(init_dict.keys()): - Tem_av_ref = init_dict["Tem_av_ref"] - if "Ud_ref" in list(init_dict.keys()): - Ud_ref = init_dict["Ud_ref"] - if "Uq_ref" in list(init_dict.keys()): - Uq_ref = init_dict["Uq_ref"] - if "felec" in list(init_dict.keys()): - felec = init_dict["felec"] - if "slip_ref" in list(init_dict.keys()): - slip_ref = init_dict["slip_ref"] - if "U0_ref" in list(init_dict.keys()): - U0_ref = init_dict["U0_ref"] - if "Phi0_ref" in list(init_dict.keys()): - Phi0_ref = init_dict["Phi0_ref"] - if "Pem_av_ref" in list(init_dict.keys()): - Pem_av_ref = init_dict["Pem_av_ref"] if "PWM" in list(init_dict.keys()): PWM = init_dict["PWM"] if "time" in list(init_dict.keys()): @@ -144,24 +121,16 @@ def __init__( Nrev = init_dict["Nrev"] if "Na_tot" in list(init_dict.keys()): Na_tot = init_dict["Na_tot"] - if "N0" in list(init_dict.keys()): - N0 = init_dict["N0"] + if "OP" in list(init_dict.keys()): + OP = init_dict["OP"] # Set the properties (value check and convertion are done in setter) self.angle_rotor = angle_rotor self.rot_dir = rot_dir self.angle_rotor_initial = angle_rotor_initial - self.Tem_av_ref = Tem_av_ref - self.Ud_ref = Ud_ref - self.Uq_ref = Uq_ref - self.felec = felec - self.slip_ref = slip_ref - self.U0_ref = U0_ref - self.Phi0_ref = Phi0_ref - self.Pem_av_ref = Pem_av_ref self.PWM = PWM # Call Input init super(InputVoltage, self).__init__( - time=time, angle=angle, Nt_tot=Nt_tot, Nrev=Nrev, Na_tot=Na_tot, N0=N0 + time=time, angle=angle, Nt_tot=Nt_tot, Nrev=Nrev, Na_tot=Na_tot, OP=OP ) # The class is frozen (in Input init), for now it's impossible to # add new properties @@ -183,14 +152,6 @@ def __str__(self): InputVoltage_str += ( "angle_rotor_initial = " + str(self.angle_rotor_initial) + linesep ) - InputVoltage_str += "Tem_av_ref = " + str(self.Tem_av_ref) + linesep - InputVoltage_str += "Ud_ref = " + str(self.Ud_ref) + linesep - InputVoltage_str += "Uq_ref = " + str(self.Uq_ref) + linesep - InputVoltage_str += "felec = " + str(self.felec) + linesep - InputVoltage_str += "slip_ref = " + str(self.slip_ref) + linesep - InputVoltage_str += "U0_ref = " + str(self.U0_ref) + linesep - InputVoltage_str += "Phi0_ref = " + str(self.Phi0_ref) + linesep - InputVoltage_str += "Pem_av_ref = " + str(self.Pem_av_ref) + linesep if self.PWM is not None: tmp = self.PWM.__str__().replace(linesep, linesep + "\t").rstrip("\t") InputVoltage_str += "PWM = " + tmp @@ -213,22 +174,6 @@ def __eq__(self, other): return False if other.angle_rotor_initial != self.angle_rotor_initial: return False - if other.Tem_av_ref != self.Tem_av_ref: - return False - if other.Ud_ref != self.Ud_ref: - return False - if other.Uq_ref != self.Uq_ref: - return False - if other.felec != self.felec: - return False - if other.slip_ref != self.slip_ref: - return False - if other.U0_ref != self.U0_ref: - return False - if other.Phi0_ref != self.Phi0_ref: - return False - if other.Pem_av_ref != self.Pem_av_ref: - return False if other.PWM != self.PWM: return False return True @@ -256,22 +201,6 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".rot_dir") if other._angle_rotor_initial != self._angle_rotor_initial: diff_list.append(name + ".angle_rotor_initial") - if other._Tem_av_ref != self._Tem_av_ref: - diff_list.append(name + ".Tem_av_ref") - if other._Ud_ref != self._Ud_ref: - diff_list.append(name + ".Ud_ref") - if other._Uq_ref != self._Uq_ref: - diff_list.append(name + ".Uq_ref") - if other._felec != self._felec: - diff_list.append(name + ".felec") - if other._slip_ref != self._slip_ref: - diff_list.append(name + ".slip_ref") - if other._U0_ref != self._U0_ref: - diff_list.append(name + ".U0_ref") - if other._Phi0_ref != self._Phi0_ref: - diff_list.append(name + ".Phi0_ref") - if other._Pem_av_ref != self._Pem_av_ref: - diff_list.append(name + ".Pem_av_ref") if (other.PWM is None and self.PWM is not None) or ( other.PWM is not None and self.PWM is None ): @@ -292,14 +221,6 @@ def __sizeof__(self): S += getsizeof(self.angle_rotor) S += getsizeof(self.rot_dir) S += getsizeof(self.angle_rotor_initial) - S += getsizeof(self.Tem_av_ref) - S += getsizeof(self.Ud_ref) - S += getsizeof(self.Uq_ref) - S += getsizeof(self.felec) - S += getsizeof(self.slip_ref) - S += getsizeof(self.U0_ref) - S += getsizeof(self.Phi0_ref) - S += getsizeof(self.Pem_av_ref) S += getsizeof(self.PWM) return S @@ -330,14 +251,6 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): ) InputVoltage_dict["rot_dir"] = self.rot_dir InputVoltage_dict["angle_rotor_initial"] = self.angle_rotor_initial - InputVoltage_dict["Tem_av_ref"] = self.Tem_av_ref - InputVoltage_dict["Ud_ref"] = self.Ud_ref - InputVoltage_dict["Uq_ref"] = self.Uq_ref - InputVoltage_dict["felec"] = self.felec - InputVoltage_dict["slip_ref"] = self.slip_ref - InputVoltage_dict["U0_ref"] = self.U0_ref - InputVoltage_dict["Phi0_ref"] = self.Phi0_ref - InputVoltage_dict["Pem_av_ref"] = self.Pem_av_ref if self.PWM is None: InputVoltage_dict["PWM"] = None else: @@ -358,14 +271,6 @@ def _set_None(self): self.angle_rotor._set_None() self.rot_dir = None self.angle_rotor_initial = None - self.Tem_av_ref = None - self.Ud_ref = None - self.Uq_ref = None - self.felec = None - self.slip_ref = None - self.U0_ref = None - self.Phi0_ref = None - self.Pem_av_ref = None if self.PWM is not None: self.PWM._set_None() # Set to None the properties inherited from Input @@ -439,150 +344,6 @@ def _set_angle_rotor_initial(self, value): """, ) - def _get_Tem_av_ref(self): - """getter of Tem_av_ref""" - return self._Tem_av_ref - - def _set_Tem_av_ref(self, value): - """setter of Tem_av_ref""" - check_var("Tem_av_ref", value, "float") - self._Tem_av_ref = value - - Tem_av_ref = property( - fget=_get_Tem_av_ref, - fset=_set_Tem_av_ref, - doc=u"""Theorical Average Electromagnetic torque - - :Type: float - """, - ) - - def _get_Ud_ref(self): - """getter of Ud_ref""" - return self._Ud_ref - - def _set_Ud_ref(self, value): - """setter of Ud_ref""" - check_var("Ud_ref", value, "float") - self._Ud_ref = value - - Ud_ref = property( - fget=_get_Ud_ref, - fset=_set_Ud_ref, - doc=u"""d-axis current RMS magnitude (phase to neutral) - - :Type: float - """, - ) - - def _get_Uq_ref(self): - """getter of Uq_ref""" - return self._Uq_ref - - def _set_Uq_ref(self, value): - """setter of Uq_ref""" - check_var("Uq_ref", value, "float") - self._Uq_ref = value - - Uq_ref = property( - fget=_get_Uq_ref, - fset=_set_Uq_ref, - doc=u"""q-axis current RMS magnitude (phase to neutral) - - :Type: float - """, - ) - - def _get_felec(self): - """getter of felec""" - return self._felec - - def _set_felec(self, value): - """setter of felec""" - check_var("felec", value, "float") - self._felec = value - - felec = property( - fget=_get_felec, - fset=_set_felec, - doc=u"""electrical frequency - - :Type: float - """, - ) - - def _get_slip_ref(self): - """getter of slip_ref""" - return self._slip_ref - - def _set_slip_ref(self, value): - """setter of slip_ref""" - check_var("slip_ref", value, "float") - self._slip_ref = value - - slip_ref = property( - fget=_get_slip_ref, - fset=_set_slip_ref, - doc=u"""Rotor mechanical slip - - :Type: float - """, - ) - - def _get_U0_ref(self): - """getter of U0_ref""" - return self._U0_ref - - def _set_U0_ref(self, value): - """setter of U0_ref""" - check_var("U0_ref", value, "float") - self._U0_ref = value - - U0_ref = property( - fget=_get_U0_ref, - fset=_set_U0_ref, - doc=u"""stator voltage (phase to neutral) - - :Type: float - """, - ) - - def _get_Phi0_ref(self): - """getter of Phi0_ref""" - return self._Phi0_ref - - def _set_Phi0_ref(self, value): - """setter of Phi0_ref""" - check_var("Phi0_ref", value, "float") - self._Phi0_ref = value - - Phi0_ref = property( - fget=_get_Phi0_ref, - fset=_set_Phi0_ref, - doc=u"""stator voltage phase - - :Type: float - """, - ) - - def _get_Pem_av_ref(self): - """getter of Pem_av_ref""" - return self._Pem_av_ref - - def _set_Pem_av_ref(self, value): - """setter of Pem_av_ref""" - check_var("Pem_av_ref", value, "float") - self._Pem_av_ref = value - - Pem_av_ref = property( - fget=_get_Pem_av_ref, - fset=_set_Pem_av_ref, - doc=u"""Theorical Average Electromagnetic Power - - :Type: float - """, - ) - def _get_PWM(self): """getter of PWM""" return self._PWM diff --git a/pyleecan/Classes/OutElec.py b/pyleecan/Classes/OutElec.py index 9ce098960..3874c8541 100644 --- a/pyleecan/Classes/OutElec.py +++ b/pyleecan/Classes/OutElec.py @@ -135,6 +135,7 @@ def __init__( OP=None, Pem_av_ref=None, Tem_av_ref=None, + Is_harm=None, init_dict=None, init_str=None, ): @@ -179,6 +180,8 @@ def __init__( Pem_av_ref = init_dict["Pem_av_ref"] if "Tem_av_ref" in list(init_dict.keys()): Tem_av_ref = init_dict["Tem_av_ref"] + if "Is_harm" in list(init_dict.keys()): + Is_harm = init_dict["Is_harm"] # Set the properties (value check and convertion are done in setter) self.parent = None self.axes_dict = axes_dict @@ -194,6 +197,7 @@ def __init__( self.OP = OP self.Pem_av_ref = Pem_av_ref self.Tem_av_ref = Tem_av_ref + self.Is_harm = Is_harm # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -235,6 +239,7 @@ def __str__(self): OutElec_str += "OP = None" + linesep + linesep OutElec_str += "Pem_av_ref = " + str(self.Pem_av_ref) + linesep OutElec_str += "Tem_av_ref = " + str(self.Tem_av_ref) + linesep + OutElec_str += "Is_harm = " + str(self.Is_harm) + linesep + linesep return OutElec_str def __eq__(self, other): @@ -268,6 +273,8 @@ def __eq__(self, other): return False if other.Tem_av_ref != self.Tem_av_ref: return False + if other.Is_harm != self.Is_harm: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -345,6 +352,14 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".Pem_av_ref") if other._Tem_av_ref != self._Tem_av_ref: diff_list.append(name + ".Tem_av_ref") + if (other.Is_harm is None and self.Is_harm is not None) or ( + other.Is_harm is not None and self.Is_harm is None + ): + diff_list.append(name + ".Is_harm None mismatch") + elif self.Is_harm is not None: + diff_list.extend( + self.Is_harm.compare(other.Is_harm, name=name + ".Is_harm") + ) # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -368,6 +383,7 @@ def __sizeof__(self): S += getsizeof(self.OP) S += getsizeof(self.Pem_av_ref) S += getsizeof(self.Tem_av_ref) + S += getsizeof(self.Is_harm) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -461,6 +477,14 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): ) OutElec_dict["Pem_av_ref"] = self.Pem_av_ref OutElec_dict["Tem_av_ref"] = self.Tem_av_ref + if self.Is_harm is None: + OutElec_dict["Is_harm"] = None + else: + OutElec_dict["Is_harm"] = self.Is_harm.as_dict( + type_handle_ndarray=type_handle_ndarray, + keep_function=keep_function, + **kwargs + ) # The class name is added to the dict for deserialisation purpose OutElec_dict["__class__"] = "OutElec" return OutElec_dict @@ -483,6 +507,7 @@ def _set_None(self): self.OP._set_None() self.Pem_av_ref = None self.Tem_av_ref = None + self.Is_harm = None def _get_axes_dict(self): """getter of axes_dict""" @@ -795,3 +820,30 @@ def _set_Tem_av_ref(self, value): :Type: float """, ) + + def _get_Is_harm(self): + """getter of Is_harm""" + return self._Is_harm + + def _set_Is_harm(self, value): + """setter of Is_harm""" + if isinstance(value, str): # Load from file + value = load_init_dict(value)[1] + if isinstance(value, dict) and "__class__" in value: + class_obj = import_class( + "SciDataTool.Classes", value.get("__class__"), "Is_harm" + ) + value = class_obj(init_dict=value) + elif type(value) is int and value == -1: # Default constructor + value = DataND() + check_var("Is_harm", value, "DataND") + self._Is_harm = value + + Is_harm = property( + fget=_get_Is_harm, + fset=_set_Is_harm, + doc=u"""Harmonic stator current + + :Type: SciDataTool.Classes.DataND.DataND + """, + ) diff --git a/pyleecan/Generator/ClassesRef/Output/OutElec.csv b/pyleecan/Generator/ClassesRef/Output/OutElec.csv index 6e2e3da5e..b7211aceb 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutElec.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutElec.csv @@ -12,3 +12,4 @@ Us_harm,V,Harmonic stator voltage as a function of time (each column correspond OP,-,Operating Point,,OP,None,,,,,,,,, Pem_av_ref,W,Theorical Average Electromagnetic Power,1,float,None,,,,,,,,, Tem_av_ref,N.m,Theorical Average Electromagnetic torque,,float,None,,,,,,,,, +Is_harm,A,Harmonic stator current ,,SciDataTool.Classes.DataND.DataND,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/EEC_ANL.csv b/pyleecan/Generator/ClassesRef/Simulation/EEC_ANL.csv index c3d342ed3..876cb3100 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/EEC_ANL.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/EEC_ANL.csv @@ -1,4 +1,4 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille parameters,-,"Parameters of the EEC: computed if empty, or enforced",,dict,{},,,,Simulation,EEC,comp_parameters,VERSION,1,Electric module: Analytical Electrical Equivalent Circuit, freq0,Hz,Frequency,,float,None,,,,,,solve_EEC,,,, -drive,-,Drive,,Drive,None,,,,,,gen_drive,,,, +drive,-,Drive,,Drive,None,,,,,,comp_joule_losses,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/Input.csv b/pyleecan/Generator/ClassesRef/Simulation/Input.csv index 3b6111b0e..d8de92d21 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/Input.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/Input.csv @@ -4,4 +4,4 @@ angle,rad,Electrical position vector (no symmetry) to import,Na_tot,ImportMatrix Nt_tot,-,Time discretization,0,int,2048,1,,,,,comp_axis_angle,,, Nrev,-,Number of rotor revolution (to compute the final time),,float,None,0,,,,,comp_axis_phase,,, Na_tot,-,Angular discretization,,int,2048,1,,,,,,,, -N0,rpm,Rotor speed,1,float,None,,,,,,,,, +OP,-,Operating Point,,OP,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/InputCurrent.csv b/pyleecan/Generator/ClassesRef/Simulation/InputCurrent.csv index c2b644148..e28f2076f 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/InputCurrent.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/InputCurrent.csv @@ -1,5 +1,4 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description Is,A,Stator currents as a function of time (each column correspond to one phase) to import,"(Nt_tot, qs)",ImportMatrix,None,,,,Simulation,InputVoltage,gen_input,VERSION,1,Input to skip the electrical module and start with the magnetic one Ir,A,Rotor currents as a function of time (each column correspond to one phase) to import,"(Nt_tot, qs)",ImportMatrix,None,,,,,,set_Id_Iq,,, -Id_ref,A,d-axis current RMS magnitude,1,float,None,,,,,,set_OP_from_array,,, -Iq_ref,A,q-axis current RMS magnitude,1,float,None,,,,,,,,, +,,,,,,,,,,,set_OP_from_array,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv index 08542797e..a6d35882d 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv @@ -2,12 +2,4 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximu angle_rotor,rad,Rotor angular position as a function of time (if None computed according to Nr) to import,Nt_tot,Import,None,,,,Simulation,Input,gen_input,VERSION,1,Input to start the electrical module with voltage input rot_dir,-,"Rotation direction of the rotor 1 trigo, -1 clockwise",,float,None,-1,1,,,,set_OP_from_array,,, angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,,,, -Tem_av_ref,N.m,Theorical Average Electromagnetic torque,,float,None,,,,,,,,, -Ud_ref,Vrms,d-axis current RMS magnitude (phase to neutral),1,float,None,,,,,,,,, -Uq_ref,Vrms,q-axis current RMS magnitude (phase to neutral),1,float,None,,,,,,,,, -felec,Hz,electrical frequency,1,float,None,,,,,,,,, -slip_ref,,Rotor mechanical slip,,float,0,,,,,,,,, -U0_ref,Vrms,stator voltage (phase to neutral),,float,None,,,,,,,,, -Phi0_ref,rad,stator voltage phase,,float,None,,,,,,,,, -Pem_av_ref,W,Theorical Average Electromagnetic Power,1,float,None,,,,,,,,, PWM,,Object to generate PWM signal,,ImportGenPWM,None,,,,,,,,, diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py index 89279b2fc..eec9b5368 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py @@ -2,6 +2,9 @@ from SciDataTool import DataTime +from pyleecan.Classes.OPdq import OPdq +from pyleecan.Classes.OPslip import OPslip + from ....Functions.Electrical.coordinate_transformation import dqh2n from ....Functions.Load.import_class import import_class @@ -42,11 +45,14 @@ def comp_mmf_unit(self, Na=None, Nt=None, felec=1, rot_dir=-1, N0=1000): # Get stator winding number of phases qs = self.winding.qs - # Output = import_class("pyleecan.Classes", "Output") - # OutElec = import_class("pyleecan.Classes", "OutElec") - # Simu1 = import_class("pyleecan.Classes", "Simu1") + if machine.is_synchronous(): + OPclass = OPdq + else: + OPclass = OPslip InputCurrent = import_class("pyleecan.Classes", "InputCurrent") - input = InputCurrent(Na_tot=Na, Nt_tot=Nt, felec=felec, rot_dir=rot_dir, N0=N0) + input = InputCurrent( + Na_tot=Na, Nt_tot=Nt, OP=OPclass(N0=N0, felec=felec), rot_dir=rot_dir + ) axes_dict = input.comp_axes( axes_list=["time", "angle", "phase_S", "phase_R"], diff --git a/pyleecan/Methods/Output/OutElec/get_Us.py b/pyleecan/Methods/Output/OutElec/get_Us.py index 0aad2ac2c..b726d1328 100644 --- a/pyleecan/Methods/Output/OutElec/get_Us.py +++ b/pyleecan/Methods/Output/OutElec/get_Us.py @@ -8,13 +8,13 @@ def get_Us(self): """Return the stator voltage""" if self.Us is None: # Generate current according to Ud/Uq - Usdq = array([self.Ud_ref, self.Uq_ref]) + Usdqh = array([self.OP.get_Ud_Uq()["Ud"], self.OP.get_Ud_Uq()["Uq"], 0]) time = self.axes_dict["time"].get_values(is_oneperiod=True) qs = self.parent.simu.machine.stator.winding.qs - felec = self.felec + felec = self.OP.get_felec() # add stator current - Us = dqh2n(Usdq, 2 * pi * felec * time, n=qs) + Us = dqh2n(Usdqh, 2 * pi * felec * time, n=qs) Phase = Data1D( name="phase", unit="", diff --git a/pyleecan/Methods/Output/OutElec/store.py b/pyleecan/Methods/Output/OutElec/store.py index a8aff0463..907ae9a4f 100644 --- a/pyleecan/Methods/Output/OutElec/store.py +++ b/pyleecan/Methods/Output/OutElec/store.py @@ -6,7 +6,7 @@ from ....Functions.Winding.gen_phase_list import gen_name -def store(self, out_dict, out_dict_harm, axes_dict): +def store(self, out_dict, out_dict_harm): """Store the standard outputs of Electrical that are temporarily in out_dict as arrays into OutElec as Data object Parameters @@ -17,8 +17,6 @@ def store(self, out_dict, out_dict_harm, axes_dict): Dict containing all electrical quantities that have been calculated in EEC out_dict_harm : dict Dict containing harmonic quantities that have been calculated in EEC - axes_dict: {Data} - Dict of axes used for electrical calculation """ diff --git a/pyleecan/Methods/Simulation/EEC_ANL/comp_joule_losses.py b/pyleecan/Methods/Simulation/EEC_ANL/comp_joule_losses.py index d49b402e9..5090e21d7 100644 --- a/pyleecan/Methods/Simulation/EEC_ANL/comp_joule_losses.py +++ b/pyleecan/Methods/Simulation/EEC_ANL/comp_joule_losses.py @@ -20,3 +20,5 @@ def comp_joule_losses(self, out_dict, machine): Pj_losses = qs * R * (Id ** 2 + Iq ** 2) out_dict["Pj_losses"] = Pj_losses + + return out_dict diff --git a/pyleecan/Methods/Simulation/EEC_ANL/solve_EEC.py b/pyleecan/Methods/Simulation/EEC_ANL/solve_EEC.py index 6df998511..b11ca4fa5 100644 --- a/pyleecan/Methods/Simulation/EEC_ANL/solve_EEC.py +++ b/pyleecan/Methods/Simulation/EEC_ANL/solve_EEC.py @@ -27,8 +27,13 @@ def solve_EEC(self): out_dict = dict() - # Solve system - if "Ud" in PAR: # Voltage driven + if "Ud" in PAR and "Id" in PAR: + # No need to compute + out_dict["Id"] = PAR["Id"] + out_dict["Iq"] = PAR["Iq"] + out_dict["Ud"] = PAR["Ud"] + out_dict["Uq"] = PAR["Uq"] + elif "Ud" in PAR: # Voltage driven out_dict["Id"] = PAR["Ud"] / (1j * ws * PAR["Ld"]) out_dict["Iq"] = PAR["Uq"] / (1j * ws * PAR["Lq"]) out_dict["Ud"] = PAR["Ud"] diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/comp_joule_losses.py b/pyleecan/Methods/Simulation/EEC_PMSM/comp_joule_losses.py index d9f749929..2cf7e0f34 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/comp_joule_losses.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/comp_joule_losses.py @@ -22,3 +22,5 @@ def comp_joule_losses(self, out_dict, machine): Pj_losses = qs * R * (Id ** 2 + Iq ** 2) out_dict["Pj_losses"] = Pj_losses + + return out_dict diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py b/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py index afb53dc4e..cbbf33c7f 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py @@ -45,3 +45,5 @@ def comp_joule_losses(self, out_dict, machine): P_joule_r = 3 * Rr * mean(Ir_mag ** 2) / 2 out_dict["Pj_losses"] = P_joule_s + P_joule_r + + return out_dict diff --git a/pyleecan/Methods/Simulation/Electrical/comp_power.py b/pyleecan/Methods/Simulation/Electrical/comp_power.py index 6a23811c5..f85e5f715 100644 --- a/pyleecan/Methods/Simulation/Electrical/comp_power.py +++ b/pyleecan/Methods/Simulation/Electrical/comp_power.py @@ -20,3 +20,5 @@ def comp_power(self, out_dict, machine): Pem_av_ref = qs * (Ud * Id + Uq * Iq) out_dict["Pem_av_ref"] = Pem_av_ref + + return out_dict diff --git a/pyleecan/Methods/Simulation/Electrical/comp_torque.py b/pyleecan/Methods/Simulation/Electrical/comp_torque.py index 93fb014cd..75660afb1 100644 --- a/pyleecan/Methods/Simulation/Electrical/comp_torque.py +++ b/pyleecan/Methods/Simulation/Electrical/comp_torque.py @@ -21,3 +21,5 @@ def comp_torque(self, out_dict, N0): Tem_av_ref = (P - out_dict["Pj_losses"]) / omega out_dict["Tem_av_ref"] = Tem_av_ref + + return out_dict diff --git a/pyleecan/Methods/Simulation/Electrical/run.py b/pyleecan/Methods/Simulation/Electrical/run.py index 358da0820..5912e0d92 100644 --- a/pyleecan/Methods/Simulation/Electrical/run.py +++ b/pyleecan/Methods/Simulation/Electrical/run.py @@ -69,10 +69,19 @@ def run(self): # Solve for each harmonic in case of Us_harm out_dict_harm = dict() if output.elec.Us_harm is not None: - freqs = output.elec.Us_harm.get_axes("freqs")[0].get_values().tolist() - Is_harm = zeros((len(freqs), machine.winding.qs)) + result = output.elec.Us_harm.get_along("freqs", "phase") + Udqh = result[output.elec.Us_harm.symbol] + freqs = result["freqs"].tolist() + Is_harm = zeros((len(freqs), machine.stator.winding.qs), dtype=complex) + # Remove Id/Iq from eec parameters + del self.eec.parameters["Id"] + del self.eec.parameters["Iq"] for i, f in enumerate(freqs): + # Update eec paremeters self.eec.freq0 = f + self.eec.parameters["Ud"] = Udqh[i, 0] + self.eec.parameters["Uq"] = Udqh[i, 1] + # Solve eec out_dict_i = self.eec.solve_EEC() Is_harm[i, :] = array([out_dict_i["Id"], out_dict_i["Iq"], 0]) out_dict_harm["Is_harm"] = Is_harm diff --git a/pyleecan/Methods/Simulation/Input/comp_axis_time.py b/pyleecan/Methods/Simulation/Input/comp_axis_time.py index a67c89331..9d88fe10c 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axis_time.py +++ b/pyleecan/Methods/Simulation/Input/comp_axis_time.py @@ -39,7 +39,7 @@ def comp_axis_time(self, p, per_t, is_antiper_t, Time_in=None, output=None): self.rot_dir = -1 logger.debug("Enforcing input.rot_dir=-1") rot_dir = self.rot_dir - f_elec = self.felec + f_elec = self.OP.get_felec() # Setup normalizations for time and angle axes norm_time = { @@ -58,7 +58,7 @@ def comp_axis_time(self, p, per_t, is_antiper_t, Time_in=None, output=None): # Create time axis as a DataLinspace if self.Nrev is not None: # Set final time depending on rotor speed and number of revolutions - t_final = 60 / self.N0 * self.Nrev + t_final = 60 / self.OP.N0 * self.Nrev else: # Set final time to p times the number of electrical periods t_final = p / f_elec @@ -94,7 +94,9 @@ def comp_axis_time(self, p, per_t, is_antiper_t, Time_in=None, output=None): output.comp_angle_rotor(Time) else: # Add default normalization - Time.normalizations["angle_rotor"] = Norm_ref(ref=rot_dir * self.N0 * 360 / 60) + Time.normalizations["angle_rotor"] = Norm_ref( + ref=rot_dir * self.OP.N0 * 360 / 60 + ) logger.debug("Enforcing default angle_rotor normalization to time axis") return Time diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index b0174b38a..9d0787d36 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -39,12 +39,12 @@ def gen_input(self): # Load and check Is if qs > 0: if self.Is is None: - if self.Id_ref is None and self.Iq_ref is None: + if self.OP.Id_ref is None and self.OP.Iq_ref is None: raise InputError( - "InputCurrent.Is, InputCurrent.Id_ref, and InputCurrent.Iq_ref missing" + "InputCurrent.Is, InputCurrent.OP.Id_ref, and InputCurrent.OP.Iq_ref missing" ) else: - outelec.OP.set_Id_Iq(self.Id_ref, self.Iq_ref) + outelec.OP = self.OP outelec.Is = None else: Is = self.Is.get_data() diff --git a/pyleecan/Methods/Simulation/InputCurrent/set_Id_Iq.py b/pyleecan/Methods/Simulation/InputCurrent/set_Id_Iq.py index f18484a04..d6f28638a 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/set_Id_Iq.py +++ b/pyleecan/Methods/Simulation/InputCurrent/set_Id_Iq.py @@ -14,9 +14,9 @@ def set_Id_Iq(self, I0, Phi0): Current phase [rad] """ - self.Id_ref = I0 * cos(Phi0) - self.Iq_ref = I0 * sin(Phi0) - if abs(self.Id_ref) < 1e-10: - self.Id_ref = 0 - if abs(self.Iq_ref) < 1e-10: - self.Iq_ref = 0 + self.OP.Id_ref = I0 * cos(Phi0) + self.OP.Iq_ref = I0 * sin(Phi0) + if abs(self.OP.Id_ref) < 1e-10: + self.OP.Id_ref = 0 + if abs(self.OP.Iq_ref) < 1e-10: + self.OP.Iq_ref = 0 diff --git a/pyleecan/Methods/Simulation/InputCurrent/set_OP_from_array.py b/pyleecan/Methods/Simulation/InputCurrent/set_OP_from_array.py index ab198efdf..1728e1e98 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/set_OP_from_array.py +++ b/pyleecan/Methods/Simulation/InputCurrent/set_OP_from_array.py @@ -19,11 +19,11 @@ def set_OP_from_array(self, OP_matrix, type_OP_matrix=1, index=0): assert index < OP_matrix.shape[0] assert type_OP_matrix in [0, 1] - self.N0 = OP_matrix[index, 0] + self.OP.N0 = OP_matrix[index, 0] if type_OP_matrix == 1: - self.Id_ref = OP_matrix[index, 1] - self.Iq_ref = OP_matrix[index, 2] + self.OP.Id_ref = OP_matrix[index, 1] + self.OP.Iq_ref = OP_matrix[index, 2] else: self.set_Id_Iq(I0=OP_matrix[index, 1], Phi0=OP_matrix[index, 2]) if OP_matrix.shape[1] > 3: - self.Tem_av_ref = OP_matrix[index, 3] + self.OP.Tem_av_ref = OP_matrix[index, 3] diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 6ec4e5be2..7ea21be26 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -39,32 +39,13 @@ def gen_input(self): outelec = OutElec() output.elec = outelec outgeo = output.geo - if simu.machine.is_synchronous(): - output.elec.OP = OPdq( - N0=self.N0, - felec=self.felec, - Ud_ref=self.Ud_ref, - Uq_ref=self.Uq_ref, - Tem_av_ref=self.Tem_av_ref, - Pem_av_ref=self.Pem_av_ref, - ) - else: - output.elec.OP = OPslip( - N0=self.N0, - felec=self.felec, - slip_ref=self.slip_ref, - U0_ref=self.U0_ref, - UPhi0_ref=self.Phi0_ref, - Tem_av_ref=self.Tem_av_ref, - Pem_av_ref=self.Pem_av_ref, - ) - OP = output.elec.OP # Replace N0=0 by 0.1 rpm - if OP.N0 == 0: - OP.N0 = 0.1 + if self.OP.N0 == 0: + self.OP.N0 = 0.1 self.get_logger().debug("Updating N0 from 0 [rpm] to 0.1 [rpm] in gen_input") # Check that felec/N0 can be computed - OP.get_felec() + self.OP.get_felec() + output.elec.OP = self.OP if self.angle_rotor is not None: outelec.angle_rotor = self.angle_rotor.get_data() @@ -113,7 +94,7 @@ def gen_input(self): # Generate PWM signal if self.PWM is not None: # Fill generator with simu data - felec = OP.get_felec() + felec = self.OP.get_felec() rot_dir = output.get_rot_dir() qs = simu.machine.stator.winding.qs self.PWM.f = felec @@ -150,5 +131,7 @@ def gen_input(self): Udqh = n2dqh_DataTime(Uabc_data, is_dqh_rms=True) # fft Udqh_freq = Udqh.time_to_freq() + # Remove f=0 + Us_harm = Udqh_freq.get_data_along("freqs>" + str(self.OP.get_felec()), "phase") # Store - outelec.Us_harm = Udqh_freq + outelec.Us_harm = Us_harm diff --git a/pyleecan/Methods/Simulation/StructElmer/gen_case.py b/pyleecan/Methods/Simulation/StructElmer/gen_case.py index e0cf4ccf1..2482b5078 100644 --- a/pyleecan/Methods/Simulation/StructElmer/gen_case.py +++ b/pyleecan/Methods/Simulation/StructElmer/gen_case.py @@ -272,7 +272,7 @@ def gen_case(self, output, mesh_names): boundaries.append(bnd) # --- body force ----------------------------------------------------------------- # - omega = 2 * pi * self.parent.input.N0 / 60 + omega = 2 * pi * self.parent.input.OP.N0 / 60 bfs = [] ID_list = [ diff --git a/pyleecan/Methods/Simulation/VarLoadCurrent/get_input_list.py b/pyleecan/Methods/Simulation/VarLoadCurrent/get_input_list.py index bf543e078..9f3789d89 100644 --- a/pyleecan/Methods/Simulation/VarLoadCurrent/get_input_list.py +++ b/pyleecan/Methods/Simulation/VarLoadCurrent/get_input_list.py @@ -1,5 +1,7 @@ from ....Classes.Simulation import Simulation from ....Classes.InputCurrent import InputCurrent +from ....Classes.OPdq import OPdq +from ....Classes.OPslip import OPslip def get_input_list(self): @@ -25,8 +27,13 @@ def get_input_list(self): else: Nrev = ref_input.Nrev # Update OP according to OP_matrix + if self.parent.machine.is_synchronous(): + OPclass = OPdq + else: + OPclass = OPslip for ii in range(N_simu): - input_list[ii].N0 = self.OP_matrix[ii, 0] + input_list[ii].OP = OPclass() + input_list[ii].OP.N0 = self.OP_matrix[ii, 0] # Edit time vector input_list[ii].time = None input_list[ii].Nt_tot = Nt_tot @@ -36,11 +43,11 @@ def get_input_list(self): I0=self.OP_matrix[ii, 1], Phi0=self.OP_matrix[ii, 2] ) else: # Id/Iq - input_list[ii].Id_ref = self.OP_matrix[ii, 1] - input_list[ii].Iq_ref = self.OP_matrix[ii, 2] + input_list[ii].OP.Id_ref = self.OP_matrix[ii, 1] + input_list[ii].OP.Iq_ref = self.OP_matrix[ii, 2] if self.is_torque: - input_list[ii].Tem_av_ref = self.OP_matrix[ii, 3] + input_list[ii].OP.Tem_av_ref = self.OP_matrix[ii, 3] if self.is_power and self.OP_matrix.shape[1] > 4: - input_list[ii].Pem_av_ref = self.OP_matrix[ii, 4] + input_list[ii].OP.Pem_av_ref = self.OP_matrix[ii, 4] return input_list diff --git a/pyleecan/Methods/Simulation/VarSimu/run.py b/pyleecan/Methods/Simulation/VarSimu/run.py index 513fee415..d58ed685a 100644 --- a/pyleecan/Methods/Simulation/VarSimu/run.py +++ b/pyleecan/Methods/Simulation/VarSimu/run.py @@ -176,9 +176,9 @@ def log_step_simu(index, nb_simu, paramexplorer_list, logger, layer): for param_exp in paramexplorer_list: value = param_exp.get_value()[index] if isinstance(value, InputCurrent): - msg += "N0=" + format(value.N0, ".6g") + " [rpm]" - msg += ", Id=" + format(value.Id_ref, ".4g") + " [Arms]" - msg += ", Iq=" + format(value.Iq_ref, ".4g") + " [Arms], " + msg += "N0=" + format(value.OP.N0, ".6g") + " [rpm]" + msg += ", Id=" + format(value.OP.Id_ref, ".4g") + " [Arms]" + msg += ", Iq=" + format(value.OP.Iq_ref, ".4g") + " [Arms], " elif isinstance(value, (list, np.ndarray)): msg += param_exp.symbol msg += "=" From 70514ac7d5370d7cc1d749b80e47433eb2f8a2ce Mon Sep 17 00:00:00 2001 From: helene-t Date: Fri, 15 Oct 2021 14:01:39 +0200 Subject: [PATCH 122/167] [BC] add OP into InputCurrent --- .../Methods/Simulation/test_InCurrent_meth.py | 22 +++++++++---------- .../Electrical/test_EEC_ELUT_PMSM.py | 1 - 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Tests/Methods/Simulation/test_InCurrent_meth.py b/Tests/Methods/Simulation/test_InCurrent_meth.py index 886654295..4db50c985 100644 --- a/Tests/Methods/Simulation/test_InCurrent_meth.py +++ b/Tests/Methods/Simulation/test_InCurrent_meth.py @@ -75,13 +75,13 @@ # Wrong time test_obj = Simulation(machine=Toyota_Prius) -test_obj.input = InputCurrent(time=None) +test_obj.input = InputCurrent(time=None, OP=OPdq()) InputCurrent_Error_test.append( {"test_obj": test_obj, "exp": "ERROR: InputCurrent.time missing"} ) # Wrong time shape test_obj = Simulation(machine=Toyota_Prius) -test_obj.input = InputCurrent(time=time_wrong) +test_obj.input = InputCurrent(time=time_wrong, OP=OPdq()) InputCurrent_Error_test.append( { "test_obj": test_obj, @@ -90,7 +90,7 @@ ) # Wrong angle shape test_obj = Simulation(machine=Toyota_Prius) -test_obj.input = InputCurrent(time=time, angle=angle_wrong) +test_obj.input = InputCurrent(time=time, angle=angle_wrong, OP=OPdq()) InputCurrent_Error_test.append( { "test_obj": test_obj, @@ -99,7 +99,7 @@ ) # Missing Is test_obj = Simulation(machine=M1) -test_obj.input = InputCurrent(time=time, angle=angle, Is=None) +test_obj.input = InputCurrent(time=time, angle=angle, Is=None, OP=OPdq()) InputCurrent_Error_test.append( { "test_obj": test_obj, @@ -108,7 +108,7 @@ ) # Is wrong shape test_obj = Simulation(machine=M1) -test_obj.input = InputCurrent(time=time, angle=angle, Is=I_3) +test_obj.input = InputCurrent(time=time, angle=angle, Is=I_3, OP=OPdq()) InputCurrent_Error_test.append( { "test_obj": test_obj, @@ -116,7 +116,7 @@ } ) test_obj = Simulation(machine=M1) -test_obj.input = InputCurrent(time=time, angle=angle, Is=I_4) +test_obj.input = InputCurrent(time=time, angle=angle, Is=I_4, OP=OPdq()) InputCurrent_Error_test.append( { "test_obj": test_obj, @@ -125,12 +125,12 @@ ) # Wrong Ir test_obj = Simulation(machine=M2) -test_obj.input = InputCurrent(time=time, angle=angle, Ir=None) +test_obj.input = InputCurrent(time=time, angle=angle, Ir=None, OP=OPdq()) InputCurrent_Error_test.append( {"test_obj": test_obj, "exp": "ERROR: InputCurrent.Ir missing"} ) test_obj = Simulation(machine=M2) -test_obj.input = InputCurrent(time=time, angle=angle, Ir=I_3) +test_obj.input = InputCurrent(time=time, angle=angle, Ir=I_3, OP=OPdq()) InputCurrent_Error_test.append( { "test_obj": test_obj, @@ -138,7 +138,7 @@ } ) test_obj = Simulation(machine=M2) -test_obj.input = InputCurrent(time=time, angle=angle, Ir=I_4) +test_obj.input = InputCurrent(time=time, angle=angle, Ir=I_4, OP=OPdq()) InputCurrent_Error_test.append( { "test_obj": test_obj, @@ -326,8 +326,8 @@ def test_InputCurrent_DQ(self, test_dict): # test_dict = idq_test[0] # obj.test_InputCurrent_DQ(test_dict) - for test_dict in idq_test: - out = obj.test_InputCurrent_DQ(test_dict) + for test_dict in InputCurrent_Error_test: + out = obj.test_InputCurrent_Error_test(test_dict) print("Done") # out.plot_2D_Data( # "elec.Is", diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py index 4a7fa2f8d..75fb54ec1 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py @@ -30,7 +30,6 @@ @pytest.mark.EEC_PMSM @pytest.mark.IPMSM @pytest.mark.periodicity -@pytest.mark.skip(reason="Work in progress") def test_EEC_ELUT_PMSM(): """Validation of the PMSM Electrical Equivalent Circuit with the Prius machine""" From 96dc59f64ee1e0cfc431cdfd5bd05d402ba2450a Mon Sep 17 00:00:00 2001 From: helene-t Date: Fri, 15 Oct 2021 14:40:44 +0200 Subject: [PATCH 123/167] [BC] add N0 as parameter + use OP in input --- Tests/Plot/test_PostPlot.py | 4 +--- pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py | 4 ++-- pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Tests/Plot/test_PostPlot.py b/Tests/Plot/test_PostPlot.py index 279cd7fa1..c35324375 100644 --- a/Tests/Plot/test_PostPlot.py +++ b/Tests/Plot/test_PostPlot.py @@ -42,11 +42,9 @@ def test_PostPlot(): Iq_ref = (I0_rms * exp(1j * Phi0)).imag simu.input = InputCurrent( - Id_ref=Id_ref, - Iq_ref=Iq_ref, Na_tot=252 * 8, Nt_tot=20 * 8, - OP=OPdq(N0=1000), + OP=OPdq(N0=1000, Id_ref=Id_ref, Iq_ref=Iq_ref), ) # Definition of the magnetic simulation: with periodicity diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py index 165f48e67..013b0f785 100644 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py @@ -1,7 +1,7 @@ from ....Functions.Load.import_class import import_class -def comp_mmf_unit(self, Na, Nt, felec=1, rot_dir=-1): +def comp_mmf_unit(self, Na, Nt, felec=1, rot_dir=-1, N0=1000): """Compute the winding Unit magnetomotive force Parameters @@ -30,7 +30,7 @@ def comp_mmf_unit(self, Na, Nt, felec=1, rot_dir=-1): LamSlotWind = import_class("pyleecan.Classes", "LamSlotWind") MMF_U, WF = LamSlotWind.comp_mmf_unit( - self, Na=Na, Nt=Nt, felec=felec, rot_dir=rot_dir + self, Na=Na, Nt=Nt, felec=felec, rot_dir=rot_dir, N0=N0 ) return MMF_U, WF diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py index 4ceb55e28..99acf4e83 100644 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py @@ -1,7 +1,7 @@ from numpy import sign -def comp_rot_dir(self): +def comp_rot_dir(self, N0=1000, felec=1): """Compute the rotation direction of the fundamental magnetic field induced by the winding WARNING: rot_dir = -1 to have positive rotor rotating direction, i.e. rotor position moves towards positive angle @@ -19,7 +19,7 @@ def comp_rot_dir(self): # Compute unit mmf MMF, _ = self.comp_mmf_unit( - Nt=20 * p, Na=20 * p + Nt=20 * p, Na=20 * p, felec=felec, N0=N0 ) # 20 points per pole over time and space is enough to capture rotating direction of fundamental mmf # Extract fundamental from unit mmf From 0ea91f7b98672e77b3d4219fc298865702026ae3 Mon Sep 17 00:00:00 2001 From: helene-t Date: Fri, 15 Oct 2021 16:05:57 +0200 Subject: [PATCH 124/167] [CO] use periodicity to speed up --- Tests/Validation/Loss/test_FEMM_Loss.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Tests/Validation/Loss/test_FEMM_Loss.py b/Tests/Validation/Loss/test_FEMM_Loss.py index 2046f123b..2868b7d04 100644 --- a/Tests/Validation/Loss/test_FEMM_Loss.py +++ b/Tests/Validation/Loss/test_FEMM_Loss.py @@ -48,7 +48,7 @@ def test_FEMM_Loss(): Id_ref = 0 Iq_ref = 2 ** (1 / 2) - n_step = 180 + n_step = 176 Nrev = 1 / 2 # readability @@ -61,18 +61,23 @@ def test_FEMM_Loss(): simu.input = InputCurrent( OP=OPdq(N0=rotor_speed, Id_ref=Id_ref, Iq_ref=Iq_ref), Na_tot=2048, + Nt_tot=n_step, ) # time discretization [s] # TODO without explicit time def. there is an error - simu.input.time = ImportMatrixVal() - simu.input.time.value = linspace( - start=0, stop=60 / rotor_speed * Nrev, num=n_step, endpoint=False - ) # n_step timesteps + # simu.input.time = ImportMatrixVal() + # simu.input.time.value = linspace( + # start=0, stop=60 / rotor_speed * Nrev, num=n_step, endpoint=False + # ) # n_step timesteps # Definition of the magnetic simulation: with periodicity simu.mag = MagFEMM( - type_BH_stator=0, type_BH_rotor=0, is_periodicity_a=True, nb_worker=4 + type_BH_stator=0, + type_BH_rotor=0, + is_periodicity_a=True, + is_periodicity_t=True, + nb_worker=4, ) simu.mag.is_get_meshsolution = True # To get FEA mesh for latter post-procesing @@ -129,7 +134,7 @@ def test_FEMM_Loss(): # mshsol.plot_contour(label="LossDens", itime=7) # mshsol.plot_contour(label="LossDensSum", itime=0) - P_mech = 2 * pi * rotor_speed / 60 * out.mag.Tem_av + P_mech = out.mag.Pem_av # Tem is now negative... loss_stator_iron = loss.get_loss(part_label="Stator", index=0) loss_rotor_iron = loss.get_loss(part_label="Rotor", index=0) From 0c27d94329fb7a213dc0cfa2a4cfe7a8609107d3 Mon Sep 17 00:00:00 2001 From: helene-t Date: Fri, 15 Oct 2021 16:06:15 +0200 Subject: [PATCH 125/167] [CO] use periodicity --- pyleecan/Methods/Simulation/LossModelWinding/comp_loss.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyleecan/Methods/Simulation/LossModelWinding/comp_loss.py b/pyleecan/Methods/Simulation/LossModelWinding/comp_loss.py index 35739584a..208b5c5ba 100644 --- a/pyleecan/Methods/Simulation/LossModelWinding/comp_loss.py +++ b/pyleecan/Methods/Simulation/LossModelWinding/comp_loss.py @@ -34,7 +34,7 @@ def comp_loss(self, output, part_label): else: current = output.elec.get_Ir() - axes_names = [axis.name for axis in current.axes] + axes_names = [axis.name + "[smallestperiod]" for axis in current.axes] data_dict = current.get_along(*axes_names) data = DataTime( From 94ce5fd7e869892dd4ae4a5f9844342e386b27c2 Mon Sep 17 00:00:00 2001 From: helene-t Date: Mon, 18 Oct 2021 10:16:48 +0200 Subject: [PATCH 126/167] [BC] felec is now included in OP --- Tests/Validation/Magnetics/test_FEMM_torque.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tests/Validation/Magnetics/test_FEMM_torque.py b/Tests/Validation/Magnetics/test_FEMM_torque.py index 9f299e7d2..35a8b585e 100644 --- a/Tests/Validation/Magnetics/test_FEMM_torque.py +++ b/Tests/Validation/Magnetics/test_FEMM_torque.py @@ -99,11 +99,10 @@ def test_FEMM_torque(): simu.input = InputCurrent( Is=None, Ir=None, # No winding on the rotor - OP=OPdq(N0=N0), + OP=OPdq(N0=N0, felec=felec), Nt_tot=Nt_tot, Nrev=1 / 6, Na_tot=Na_tot, - felec=felec, ) # Select first OP as reference simu.input.set_OP_from_array(OP_matrix, type_OP_matrix=varload.type_OP_matrix) From 276839129abc4005ab79f0300ee5805a97f4d7ea Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 19 Oct 2021 10:25:48 +0200 Subject: [PATCH 127/167] [CO] store Us_PWM and get_Us_harm method --- .../Methods/Simulation/test_InVoltage_PWM.py | 25 ++++--- .../Electrical/test_EEC_ELUT_PMSM.py | 4 +- pyleecan/Classes/Class_Dict.json | 5 +- pyleecan/Classes/OutElec.py | 72 +++++++++++-------- .../Generator/ClassesRef/Output/OutElec.csv | 6 +- .../Methods/Import/ImportGenPWM/get_data.py | 9 ++- .../Methods/Output/OutElec/get_Us_harm.py | 15 ++++ pyleecan/Methods/Simulation/Electrical/run.py | 9 +-- .../Simulation/InputVoltage/gen_input.py | 32 +++------ 9 files changed, 101 insertions(+), 76 deletions(-) create mode 100644 pyleecan/Methods/Output/OutElec/get_Us_harm.py diff --git a/Tests/Methods/Simulation/test_InVoltage_PWM.py b/Tests/Methods/Simulation/test_InVoltage_PWM.py index 40c0937ad..040a0a7b0 100644 --- a/Tests/Methods/Simulation/test_InVoltage_PWM.py +++ b/Tests/Methods/Simulation/test_InVoltage_PWM.py @@ -16,8 +16,8 @@ def test_InVoltage_PWM(): fmax = 20000 fswi = 7000 - Vdc1 = 2 # Bus voltage - U0 = 0.5 # Phase voltage + Vdc1 = 800 # Bus voltage + U0 = 460 # Phase voltage Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) @@ -32,13 +32,20 @@ def test_InVoltage_PWM(): out = simu.run() - if is_show_fig: - out.elec.Us_harm.plot_2D_Data( - "freqs", - is_auto_ticks=False, - save_path=join(save_path, "test_InVoltage_PWM.png"), - **dict_2D - ) + out.elec.Us_PWM.plot_2D_Data( + "freqs", + is_auto_ticks=False, + save_path=join(save_path, "test_InVoltage_PWM.png"), + is_show_fig=is_show_fig, + **dict_2D + ) + out.elec.get_Us_harm().plot_2D_Data( + "freqs", + is_auto_ticks=False, + save_path=join(save_path, "test_InVoltage_PWM_dqh.png"), + is_show_fig=is_show_fig, + **dict_2D + ) return out diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py index 75fb54ec1..4d2b2a793 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py @@ -86,8 +86,8 @@ def test_EEC_ELUT_PMSM(): # Simu with EEC using ELUT fmax = 20000 fswi = 7000 - Vdc1 = 1000 # Bus voltage - U0 = 800 # Phase voltage + Vdc1 = 800 # Bus voltage + U0 = 460 # Phase voltage simu_EEC = Simu1(name="test_LUT_PMSM", machine=Toyota_Prius) # Definition of the input diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index f1e809e42..0f2a71994 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -8097,6 +8097,7 @@ "get_Is", "get_Nr", "get_Us", + "get_Us_harm", "store" ], "mother": "", @@ -8186,10 +8187,10 @@ "value": null }, { - "desc": "Harmonic stator voltage as a function of time (each column correspond to one phase)", + "desc": "PWM stator voltage as a function of time (each column correspond to one phase)", "max": "", "min": "", - "name": "Us_harm", + "name": "Us_PWM", "type": "SciDataTool.Classes.DataND.DataND", "unit": "V", "value": "None" diff --git a/pyleecan/Classes/OutElec.py b/pyleecan/Classes/OutElec.py index 3874c8541..cc4f48c5f 100644 --- a/pyleecan/Classes/OutElec.py +++ b/pyleecan/Classes/OutElec.py @@ -42,6 +42,11 @@ except ImportError as error: get_Us = error +try: + from ..Methods.Output.OutElec.get_Us_harm import get_Us_harm +except ImportError as error: + get_Us_harm = error + try: from ..Methods.Output.OutElec.store import store except ImportError as error: @@ -105,6 +110,15 @@ class OutElec(FrozenClass): ) else: get_Us = get_Us + # cf Methods.Output.OutElec.get_Us_harm + if isinstance(get_Us_harm, ImportError): + get_Us_harm = property( + fget=lambda x: raise_( + ImportError("Can't use OutElec method get_Us_harm: " + str(get_Us_harm)) + ) + ) + else: + get_Us_harm = get_Us_harm # cf Methods.Output.OutElec.store if isinstance(store, ImportError): store = property( @@ -131,7 +145,7 @@ def __init__( Pj_losses=None, Us=None, internal=None, - Us_harm=None, + Us_PWM=None, OP=None, Pem_av_ref=None, Tem_av_ref=None, @@ -172,8 +186,8 @@ def __init__( Us = init_dict["Us"] if "internal" in list(init_dict.keys()): internal = init_dict["internal"] - if "Us_harm" in list(init_dict.keys()): - Us_harm = init_dict["Us_harm"] + if "Us_PWM" in list(init_dict.keys()): + Us_PWM = init_dict["Us_PWM"] if "OP" in list(init_dict.keys()): OP = init_dict["OP"] if "Pem_av_ref" in list(init_dict.keys()): @@ -193,7 +207,7 @@ def __init__( self.Pj_losses = Pj_losses self.Us = Us self.internal = internal - self.Us_harm = Us_harm + self.Us_PWM = Us_PWM self.OP = OP self.Pem_av_ref = Pem_av_ref self.Tem_av_ref = Tem_av_ref @@ -231,7 +245,7 @@ def __str__(self): OutElec_str += "internal = " + tmp else: OutElec_str += "internal = None" + linesep + linesep - OutElec_str += "Us_harm = " + str(self.Us_harm) + linesep + linesep + OutElec_str += "Us_PWM = " + str(self.Us_PWM) + linesep + linesep if self.OP is not None: tmp = self.OP.__str__().replace(linesep, linesep + "\t").rstrip("\t") OutElec_str += "OP = " + tmp @@ -265,7 +279,7 @@ def __eq__(self, other): return False if other.internal != self.internal: return False - if other.Us_harm != self.Us_harm: + if other.Us_PWM != self.Us_PWM: return False if other.OP != self.OP: return False @@ -334,14 +348,12 @@ def compare(self, other, name="self", ignore_list=None): diff_list.extend( self.internal.compare(other.internal, name=name + ".internal") ) - if (other.Us_harm is None and self.Us_harm is not None) or ( - other.Us_harm is not None and self.Us_harm is None + if (other.Us_PWM is None and self.Us_PWM is not None) or ( + other.Us_PWM is not None and self.Us_PWM is None ): - diff_list.append(name + ".Us_harm None mismatch") - elif self.Us_harm is not None: - diff_list.extend( - self.Us_harm.compare(other.Us_harm, name=name + ".Us_harm") - ) + diff_list.append(name + ".Us_PWM None mismatch") + elif self.Us_PWM is not None: + diff_list.extend(self.Us_PWM.compare(other.Us_PWM, name=name + ".Us_PWM")) if (other.OP is None and self.OP is not None) or ( other.OP is not None and self.OP is None ): @@ -379,7 +391,7 @@ def __sizeof__(self): S += getsizeof(self.Pj_losses) S += getsizeof(self.Us) S += getsizeof(self.internal) - S += getsizeof(self.Us_harm) + S += getsizeof(self.Us_PWM) S += getsizeof(self.OP) S += getsizeof(self.Pem_av_ref) S += getsizeof(self.Tem_av_ref) @@ -459,10 +471,10 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): keep_function=keep_function, **kwargs ) - if self.Us_harm is None: - OutElec_dict["Us_harm"] = None + if self.Us_PWM is None: + OutElec_dict["Us_PWM"] = None else: - OutElec_dict["Us_harm"] = self.Us_harm.as_dict( + OutElec_dict["Us_PWM"] = self.Us_PWM.as_dict( type_handle_ndarray=type_handle_ndarray, keep_function=keep_function, **kwargs @@ -502,7 +514,7 @@ def _set_None(self): self.Us = None if self.internal is not None: self.internal._set_None() - self.Us_harm = None + self.Us_PWM = None if self.OP is not None: self.OP._set_None() self.Pem_av_ref = None @@ -730,28 +742,28 @@ def _set_internal(self, value): """, ) - def _get_Us_harm(self): - """getter of Us_harm""" - return self._Us_harm + def _get_Us_PWM(self): + """getter of Us_PWM""" + return self._Us_PWM - def _set_Us_harm(self, value): - """setter of Us_harm""" + def _set_Us_PWM(self, value): + """setter of Us_PWM""" if isinstance(value, str): # Load from file value = load_init_dict(value)[1] if isinstance(value, dict) and "__class__" in value: class_obj = import_class( - "SciDataTool.Classes", value.get("__class__"), "Us_harm" + "SciDataTool.Classes", value.get("__class__"), "Us_PWM" ) value = class_obj(init_dict=value) elif type(value) is int and value == -1: # Default constructor value = DataND() - check_var("Us_harm", value, "DataND") - self._Us_harm = value + check_var("Us_PWM", value, "DataND") + self._Us_PWM = value - Us_harm = property( - fget=_get_Us_harm, - fset=_set_Us_harm, - doc=u"""Harmonic stator voltage as a function of time (each column correspond to one phase) + Us_PWM = property( + fget=_get_Us_PWM, + fset=_set_Us_PWM, + doc=u"""PWM stator voltage as a function of time (each column correspond to one phase) :Type: SciDataTool.Classes.DataND.DataND """, diff --git a/pyleecan/Generator/ClassesRef/Output/OutElec.csv b/pyleecan/Generator/ClassesRef/Output/OutElec.csv index b7211aceb..2e95f68bb 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutElec.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutElec.csv @@ -4,11 +4,11 @@ Is,A,Stator currents DataTime object,"(qs, Nt_tot)",SciDataTool.Classes.DataND.D Ir,A,Rotor currents as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Is,,, angle_rotor,rad,Rotor angular position as a function of time (if None computed according to Nr),Nt_tot,ndarray,None,,,,,,get_Nr,,, angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,get_Us,,, -logger_name,-,Name of the logger to use,0,str,Pyleecan.Electrical,,,,,,store,,, -Pj_losses,W,Electrical Joule losses,,float,None,,,,,,,,, +logger_name,-,Name of the logger to use,0,str,Pyleecan.Electrical,,,,,,get_Us_harm,,, +Pj_losses,W,Electrical Joule losses,,float,None,,,,,,store,,, Us,V,Stator voltage as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,,,, internal,-,OutInternal object containg outputs related to a specific model,0,OutInternal,None,,,,,,,,, -Us_harm,V,Harmonic stator voltage as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,,,, +Us_PWM,V,PWM stator voltage as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,,,, OP,-,Operating Point,,OP,None,,,,,,,,, Pem_av_ref,W,Theorical Average Electromagnetic Power,1,float,None,,,,,,,,, Tem_av_ref,N.m,Theorical Average Electromagnetic torque,,float,None,,,,,,,,, diff --git a/pyleecan/Methods/Import/ImportGenPWM/get_data.py b/pyleecan/Methods/Import/ImportGenPWM/get_data.py index ff7451020..52d655ccf 100644 --- a/pyleecan/Methods/Import/ImportGenPWM/get_data.py +++ b/pyleecan/Methods/Import/ImportGenPWM/get_data.py @@ -52,8 +52,11 @@ def get_data(self): ) ref = np.zeros(np.size(v_pwm[0])).astype(np.float32) - PWM1 = np.where(v_pwm[0] < ref, -1, 1) # .astype(np.float32) - PWM2 = np.where(v_pwm[1] < ref, -1, 1) # .astype(np.float32) - PWM3 = np.where(v_pwm[2] < ref, -1, 1) # .astype(np.float32) + # PWM1 = np.where(v_pwm[0] < ref, -1, 1) # .astype(np.float32) + # PWM2 = np.where(v_pwm[1] < ref, -1, 1) # .astype(np.float32) + # PWM3 = np.where(v_pwm[2] < ref, -1, 1) # .astype(np.float32) + PWM1 = v_pwm[0] + PWM2 = v_pwm[1] + PWM3 = v_pwm[2] Vpwm = np.column_stack([PWM1, PWM2, PWM3]) return Vpwm, Vas, MI, carrier, Tpwmu diff --git a/pyleecan/Methods/Output/OutElec/get_Us_harm.py b/pyleecan/Methods/Output/OutElec/get_Us_harm.py new file mode 100644 index 000000000..bad8baacf --- /dev/null +++ b/pyleecan/Methods/Output/OutElec/get_Us_harm.py @@ -0,0 +1,15 @@ +from ....Functions.Electrical.coordinate_transformation import n2dqh_DataTime + + +def get_Us_harm(self): + """Return the harmonic stator voltage in dqh frame""" + if self.Us_PWM is None: + raise Exception("No PWM voltage was defined in the simulation") + else: + # Rotate to DQH frame + Udqh = n2dqh_DataTime(self.Us_PWM, is_dqh_rms=True) + # fft + Udqh_freq = Udqh.time_to_freq() + # Remove f=0 (/!\ for DC machines) + Us_harm = Udqh_freq.get_data_along("freqs>" + str(self.OP.get_felec()), "phase") + return Us_harm diff --git a/pyleecan/Methods/Simulation/Electrical/run.py b/pyleecan/Methods/Simulation/Electrical/run.py index 5912e0d92..0fc7e037d 100644 --- a/pyleecan/Methods/Simulation/Electrical/run.py +++ b/pyleecan/Methods/Simulation/Electrical/run.py @@ -66,11 +66,12 @@ def run(self): # Solve the electrical equivalent circuit out_dict = self.eec.solve_EEC() - # Solve for each harmonic in case of Us_harm + # Solve for each harmonic in case of Us_PWM out_dict_harm = dict() - if output.elec.Us_harm is not None: - result = output.elec.Us_harm.get_along("freqs", "phase") - Udqh = result[output.elec.Us_harm.symbol] + if output.elec.Us_PWM is not None: + Us_harm = output.elec.get_Us_harm() + result = Us_harm.get_along("freqs", "phase") + Udqh = result[Us_harm.symbol] freqs = result["freqs"].tolist() Is_harm = zeros((len(freqs), machine.stator.winding.qs), dtype=complex) # Remove Id/Iq from eec parameters diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 7ea21be26..40e370f64 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -102,36 +102,22 @@ def gen_input(self): self.PWM.fmax * 2.56 ) # Shanon based sampling frequency (with margin) self.PWM.duration = 1 / felec + self.PWM.typePWM = 7 # Generate PWM signal Uabc, modulation, _, carrier, time = self.PWM.get_data() # Create DataTime object - Time = DataLinspace( - name="time", - unit="s", - initial=0, - final=time[-1], - number=len(time), - include_endpoint=True, - normalizations={"angle_elec": Norm_ref(ref=rot_dir / (2 * pi * felec))}, + self.time = time + Time = self.comp_axis_time( + simu.machine.get_pole_pair_number(), + per_t=1, + is_antiper_t=False, + output=output, ) - Phase = Data1D( - name="phase", - unit="", - values=gen_name(qs), - is_components=True, - ) - Uabc_data = DataTime( + Phase = self.comp_axis_phase(simu.machine.stator) + outelec.Us_PWM = DataTime( name="Stator voltage", symbol="U_s", unit="V", axes=[Time, Phase], values=Uabc, ) - # Rotate to DQH frame - Udqh = n2dqh_DataTime(Uabc_data, is_dqh_rms=True) - # fft - Udqh_freq = Udqh.time_to_freq() - # Remove f=0 - Us_harm = Udqh_freq.get_data_along("freqs>" + str(self.OP.get_felec()), "phase") - # Store - outelec.Us_harm = Us_harm From 4fc5c96a105236f7308fea0ec6e563296be4f42d Mon Sep 17 00:00:00 2001 From: helene-t Date: Tue, 19 Oct 2021 11:36:57 +0200 Subject: [PATCH 128/167] [CO] option to get Us_harm in DQH frame --- .../Methods/Import/ImportGenPWM/get_data.py | 20 ++++++++------ .../Methods/Output/OutElec/get_Us_harm.py | 26 +++++++++++++++---- .../Simulation/InputVoltage/gen_input.py | 2 +- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/pyleecan/Methods/Import/ImportGenPWM/get_data.py b/pyleecan/Methods/Import/ImportGenPWM/get_data.py index 52d655ccf..fc63d65ce 100644 --- a/pyleecan/Methods/Import/ImportGenPWM/get_data.py +++ b/pyleecan/Methods/Import/ImportGenPWM/get_data.py @@ -10,13 +10,15 @@ from ....Functions.Electrical.comp_PWM import comp_volt_PWM_NUM -def get_data(self): +def get_data(self, is_norm=True): """Generate the PWM matrix Parameters ---------- self : ImportGenPWM An ImportGenPWM object + is_norm : bool + True to normalize signal Returns ------- @@ -51,12 +53,14 @@ def get_data(self): var_amp=self.var_amp, ) - ref = np.zeros(np.size(v_pwm[0])).astype(np.float32) - # PWM1 = np.where(v_pwm[0] < ref, -1, 1) # .astype(np.float32) - # PWM2 = np.where(v_pwm[1] < ref, -1, 1) # .astype(np.float32) - # PWM3 = np.where(v_pwm[2] < ref, -1, 1) # .astype(np.float32) - PWM1 = v_pwm[0] - PWM2 = v_pwm[1] - PWM3 = v_pwm[2] + if is_norm: + ref = np.zeros(np.size(v_pwm[0])).astype(np.float32) + PWM1 = np.where(v_pwm[0] < ref, -1, 1) # .astype(np.float32) + PWM2 = np.where(v_pwm[1] < ref, -1, 1) # .astype(np.float32) + PWM3 = np.where(v_pwm[2] < ref, -1, 1) # .astype(np.float32) + else: + PWM1 = v_pwm[0] + PWM2 = v_pwm[1] + PWM3 = v_pwm[2] Vpwm = np.column_stack([PWM1, PWM2, PWM3]) return Vpwm, Vas, MI, carrier, Tpwmu diff --git a/pyleecan/Methods/Output/OutElec/get_Us_harm.py b/pyleecan/Methods/Output/OutElec/get_Us_harm.py index bad8baacf..49511c062 100644 --- a/pyleecan/Methods/Output/OutElec/get_Us_harm.py +++ b/pyleecan/Methods/Output/OutElec/get_Us_harm.py @@ -1,15 +1,31 @@ from ....Functions.Electrical.coordinate_transformation import n2dqh_DataTime -def get_Us_harm(self): - """Return the harmonic stator voltage in dqh frame""" +def get_Us_harm(self, is_dqh=True): + """Return the harmonic stator voltage + + Parameters + ---------- + self : OutElec + an OutElec object + is_dqh : bool + True to rotate in DQH frame + + Returns + ------- + Us_harm + harmonic stator voltage + """ if self.Us_PWM is None: raise Exception("No PWM voltage was defined in the simulation") else: # Rotate to DQH frame - Udqh = n2dqh_DataTime(self.Us_PWM, is_dqh_rms=True) + if is_dqh: + U = n2dqh_DataTime(self.Us_PWM, is_dqh_rms=True) + else: + U = self.Us_PWM # fft - Udqh_freq = Udqh.time_to_freq() + Us_fft = U.time_to_freq() # Remove f=0 (/!\ for DC machines) - Us_harm = Udqh_freq.get_data_along("freqs>" + str(self.OP.get_felec()), "phase") + Us_harm = Us_fft.get_data_along("freqs>" + str(self.OP.get_felec()), "phase") return Us_harm diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 40e370f64..f02c67755 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -104,7 +104,7 @@ def gen_input(self): self.PWM.duration = 1 / felec self.PWM.typePWM = 7 # Generate PWM signal - Uabc, modulation, _, carrier, time = self.PWM.get_data() + Uabc, modulation, _, carrier, time = self.PWM.get_data(is_norm=False) # Create DataTime object self.time = time Time = self.comp_axis_time( From db889067c6b6e892c1d08d6d10dcdc365b8b78ca Mon Sep 17 00:00:00 2001 From: helene-t Date: Wed, 20 Oct 2021 14:46:11 +0200 Subject: [PATCH 129/167] [CO] add parameters to GenPWM + adjust number of points to have periodic carrier/modulant --- .../Methods/Simulation/test_InVoltage_PWM.py | 2 +- pyleecan/Classes/Class_Dict.json | 18 ++++++ pyleecan/Classes/ImportGenPWM.py | 60 +++++++++++++++++++ pyleecan/Functions/Electrical/comp_PWM.py | 30 ++++++---- .../ClassesRef/Import/ImportGenPWM.csv | 2 + .../Methods/Import/ImportGenPWM/get_data.py | 10 +++- .../Simulation/InputVoltage/gen_input.py | 27 +++++++-- 7 files changed, 129 insertions(+), 20 deletions(-) diff --git a/Tests/Methods/Simulation/test_InVoltage_PWM.py b/Tests/Methods/Simulation/test_InVoltage_PWM.py index 040a0a7b0..48c25f33e 100644 --- a/Tests/Methods/Simulation/test_InVoltage_PWM.py +++ b/Tests/Methods/Simulation/test_InVoltage_PWM.py @@ -9,7 +9,7 @@ from pyleecan.Functions.Plot import dict_2D from Tests import save_plot_path as save_path -is_show_fig = False +is_show_fig = True def test_InVoltage_PWM(): diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 0f2a71994..7c4dd3efd 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -3389,6 +3389,24 @@ "type": "int", "unit": "", "value": 3 + }, + { + "desc": "True if star coupling, False if triangle coupling", + "max": "", + "min": "", + "name": "is_star", + "type": "bool", + "unit": "", + "value": 1 + }, + { + "desc": "rotor rotation direction", + "max": "", + "min": "", + "name": "rot_dir", + "type": "int", + "unit": "", + "value": -1 } ] }, diff --git a/pyleecan/Classes/ImportGenPWM.py b/pyleecan/Classes/ImportGenPWM.py index f15e288cd..72a12ae13 100644 --- a/pyleecan/Classes/ImportGenPWM.py +++ b/pyleecan/Classes/ImportGenPWM.py @@ -62,6 +62,8 @@ def __init__( type_carrier=0, var_amp=20, qs=3, + is_star=True, + rot_dir=-1, is_transpose=False, init_dict=None, init_str=None, @@ -109,6 +111,10 @@ def __init__( var_amp = init_dict["var_amp"] if "qs" in list(init_dict.keys()): qs = init_dict["qs"] + if "is_star" in list(init_dict.keys()): + is_star = init_dict["is_star"] + if "rot_dir" in list(init_dict.keys()): + rot_dir = init_dict["rot_dir"] if "is_transpose" in list(init_dict.keys()): is_transpose = init_dict["is_transpose"] # Set the properties (value check and convertion are done in setter) @@ -126,6 +132,8 @@ def __init__( self.type_carrier = type_carrier self.var_amp = var_amp self.qs = qs + self.is_star = is_star + self.rot_dir = rot_dir # Call ImportMatrix init super(ImportGenPWM, self).__init__(is_transpose=is_transpose) # The class is frozen (in ImportMatrix init), for now it's impossible to @@ -151,6 +159,8 @@ def __str__(self): ImportGenPWM_str += "type_carrier = " + str(self.type_carrier) + linesep ImportGenPWM_str += "var_amp = " + str(self.var_amp) + linesep ImportGenPWM_str += "qs = " + str(self.qs) + linesep + ImportGenPWM_str += "is_star = " + str(self.is_star) + linesep + ImportGenPWM_str += "rot_dir = " + str(self.rot_dir) + linesep return ImportGenPWM_str def __eq__(self, other): @@ -190,6 +200,10 @@ def __eq__(self, other): return False if other.qs != self.qs: return False + if other.is_star != self.is_star: + return False + if other.rot_dir != self.rot_dir: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -231,6 +245,10 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".var_amp") if other._qs != self._qs: diff_list.append(name + ".qs") + if other._is_star != self._is_star: + diff_list.append(name + ".is_star") + if other._rot_dir != self._rot_dir: + diff_list.append(name + ".rot_dir") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -256,6 +274,8 @@ def __sizeof__(self): S += getsizeof(self.type_carrier) S += getsizeof(self.var_amp) S += getsizeof(self.qs) + S += getsizeof(self.is_star) + S += getsizeof(self.rot_dir) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -289,6 +309,8 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): ImportGenPWM_dict["type_carrier"] = self.type_carrier ImportGenPWM_dict["var_amp"] = self.var_amp ImportGenPWM_dict["qs"] = self.qs + ImportGenPWM_dict["is_star"] = self.is_star + ImportGenPWM_dict["rot_dir"] = self.rot_dir # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name ImportGenPWM_dict["__class__"] = "ImportGenPWM" @@ -311,6 +333,8 @@ def _set_None(self): self.type_carrier = None self.var_amp = None self.qs = None + self.is_star = None + self.rot_dir = None # Set to None the properties inherited from ImportMatrix super(ImportGenPWM, self)._set_None() @@ -570,3 +594,39 @@ def _set_qs(self, value): :Type: int """, ) + + def _get_is_star(self): + """getter of is_star""" + return self._is_star + + def _set_is_star(self, value): + """setter of is_star""" + check_var("is_star", value, "bool") + self._is_star = value + + is_star = property( + fget=_get_is_star, + fset=_set_is_star, + doc=u"""True if star coupling, False if triangle coupling + + :Type: bool + """, + ) + + def _get_rot_dir(self): + """getter of rot_dir""" + return self._rot_dir + + def _set_rot_dir(self, value): + """setter of rot_dir""" + check_var("rot_dir", value, "int") + self._rot_dir = value + + rot_dir = property( + fget=_get_rot_dir, + fset=_set_rot_dir, + doc=u"""rotor rotation direction + + :Type: int + """, + ) diff --git a/pyleecan/Functions/Electrical/comp_PWM.py b/pyleecan/Functions/Electrical/comp_PWM.py index 0b547fdec..db04d1229 100644 --- a/pyleecan/Functions/Electrical/comp_PWM.py +++ b/pyleecan/Functions/Electrical/comp_PWM.py @@ -20,6 +20,8 @@ def comp_volt_PWM_NUM( freq0_max=0, type_carrier=0, var_amp=0, + is_norm=True, + N_add=None, ): """ Generalized DPWM using numerical method according to @@ -204,15 +206,19 @@ def comp_volt_PWM_NUM( Phase = [0, 1, -1] + # Remove additional points + if N_add is not None: + Tpwmu2 = np.linspace(0, Tpwmu[-N_add], len(Tpwmu), endpoint=False) + if is_sin: - Vas = k * M_I * (Vdc1 / 2) * np.sin(ws * Tpwmu + Phase[0] * 2 * np.pi / 3) - Vbs = k * M_I * (Vdc1 / 2) * np.sin(ws * Tpwmu + Phase[1] * 2 * np.pi / 3) - Vcs = k * M_I * (Vdc1 / 2) * np.sin(ws * Tpwmu + Phase[2] * 2 * np.pi / 3) + Vas = k * M_I * (Vdc1 / 2) * np.sin(ws * Tpwmu2 + Phase[0] * 2 * np.pi / 3) + Vbs = k * M_I * (Vdc1 / 2) * np.sin(ws * Tpwmu2 + Phase[1] * 2 * np.pi / 3) + Vcs = k * M_I * (Vdc1 / 2) * np.sin(ws * Tpwmu2 + Phase[2] * 2 * np.pi / 3) else: - Vas = k * M_I * (Vdc1 / 2) * np.cos(ws * Tpwmu + Phase[0] * 2 * np.pi / 3) - Vbs = k * M_I * (Vdc1 / 2) * np.cos(ws * Tpwmu + Phase[1] * 2 * np.pi / 3) - Vcs = k * M_I * (Vdc1 / 2) * np.cos(ws * Tpwmu + Phase[2] * 2 * np.pi / 3) + Vas = k * M_I * (Vdc1 / 2) * np.cos(ws * Tpwmu2 + Phase[0] * 2 * np.pi / 3) + Vbs = k * M_I * (Vdc1 / 2) * np.cos(ws * Tpwmu2 + Phase[1] * 2 * np.pi / 3) + Vcs = k * M_I * (Vdc1 / 2) * np.cos(ws * Tpwmu2 + Phase[2] * 2 * np.pi / 3) V_min = np.amin( np.concatenate((Vas[:, None], Vbs[:, None], Vcs[:, None]), axis=1), axis=1 @@ -327,10 +333,14 @@ def comp_volt_PWM_NUM( if type_DPWM == 8: v_pwm = np.ones((qs, Npsim)) - - v_pwm[0] = np.where(Vas < carrier, -1, 1) - v_pwm[1] = np.where(Vbs < carrier, -1, 1) - v_pwm[2] = np.where(Vcs < carrier, -1, 1) + if is_norm: + v_pwm[0] = np.where(Vas < carrier, -1, 1) + v_pwm[1] = np.where(Vbs < carrier, -1, 1) + v_pwm[2] = np.where(Vcs < carrier, -1, 1) + else: + v_pwm[0] = np.where(Vas < carrier, -Vdc1 / 2, Vdc1 / 2) + v_pwm[1] = np.where(Vbs < carrier, -Vdc1 / 2, Vdc1 / 2) + v_pwm[2] = np.where(Vcs < carrier, -Vdc1 / 2, Vdc1 / 2) else: diff --git a/pyleecan/Generator/ClassesRef/Import/ImportGenPWM.csv b/pyleecan/Generator/ClassesRef/Import/ImportGenPWM.csv index 196bdba98..89bdb3a68 100644 --- a/pyleecan/Generator/ClassesRef/Import/ImportGenPWM.csv +++ b/pyleecan/Generator/ClassesRef/Import/ImportGenPWM.csv @@ -13,3 +13,5 @@ U0,V,reference voltage,0,float,1,,,,,,,,, type_carrier,,1: forward toothsaw carrier 2: backwards toothsaw carrier 3: toothsaw carrier else: symetrical toothsaw carrier,0,int,0,,,,,,,,, var_amp,%,percentage of variation of carrier amplitude,0,int,20,,,,,,,,, qs,,number of phase,0,int,3,,,,,,,,, +is_star,,"True if star coupling, False if triangle coupling",,bool,1,,,,,,,,, +rot_dir,,rotor rotation direction,,int,-1,,,,,,,,, diff --git a/pyleecan/Methods/Import/ImportGenPWM/get_data.py b/pyleecan/Methods/Import/ImportGenPWM/get_data.py index fc63d65ce..7a52b8c3b 100644 --- a/pyleecan/Methods/Import/ImportGenPWM/get_data.py +++ b/pyleecan/Methods/Import/ImportGenPWM/get_data.py @@ -10,7 +10,7 @@ from ....Functions.Electrical.comp_PWM import comp_volt_PWM_NUM -def get_data(self, is_norm=True): +def get_data(self, is_norm=True, N_add=None): """Generate the PWM matrix Parameters @@ -34,7 +34,9 @@ def get_data(self, is_norm=True): time vector """ N = int(self.fs * self.duration) # Number of points - Tpwmu = np.linspace(0, (N - 1) / self.fs, N, endpoint=True) # Time vector + if N_add is not None: + N += N_add + Tpwmu = np.linspace(0, N / self.fs, N, endpoint=False) # Time vector v_pwm, Vas, MI, carrier = comp_volt_PWM_NUM( Tpwmu=Tpwmu, freq0=self.f, @@ -47,10 +49,12 @@ def get_data(self, is_norm=True): Vdc1=self.Vdc1, U0=self.U0, type_carrier=self.type_carrier, - rot_dir=-1, + rot_dir=self.rot_dir, type_DPWM=self.typePWM, PF_angle=0, var_amp=self.var_amp, + is_norm=is_norm, + N_add=N_add, ) if is_norm: diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index f02c67755..38dc5d53b 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -1,5 +1,5 @@ from SciDataTool import Norm_ref -from numpy import ndarray, pi +from numpy import ndarray, arange, searchsorted, ceil from ....Classes.OutElec import OutElec from ....Classes.Simulation import Simulation @@ -98,15 +98,30 @@ def gen_input(self): rot_dir = output.get_rot_dir() qs = simu.machine.stator.winding.qs self.PWM.f = felec - self.PWM.fs = ( - self.PWM.fmax * 2.56 - ) # Shanon based sampling frequency (with margin) + self.PWM.qs = qs + self.PWM.rot_dir = rot_dir self.PWM.duration = 1 / felec - self.PWM.typePWM = 7 + self.PWM.typePWM = 8 + self.PWM.Vdc1 *= 2 # In comp_PWM, max is Vdc1/2 + # Compute sampling frequency (even multiple of fswi + close to 2*fmax) + mult = arange(1, 100) + ind = searchsorted(self.PWM.fswi * 2 * mult, 2 * self.PWM.fmax, side="right") + self.PWM.fs = 2 * mult[ind] * self.PWM.fswi # Generate PWM signal - Uabc, modulation, _, carrier, time = self.PWM.get_data(is_norm=False) + Uabc, modulant, _, carrier, time = self.PWM.get_data( + is_norm=False, N_add=mult[ind] + ) + # Account for coupling phase + if self.PWM.is_star: + Uabc[:, 0] = Uabc[:, 0] - Uabc.mean(axis=1) + Uabc[:, 1] = Uabc[:, 1] - Uabc.mean(axis=1) + Uabc[:, 2] = Uabc[:, 2] - Uabc.mean(axis=1) # Create DataTime object self.time = time + Time = Data1D(name="time", unit="s", symmetries={"period": 5}, values=time) + Carrier = DataTime(values=carrier, axes=[Time]) + Modulant = DataTime(values=modulant, axes=[Time]) + Carrier.plot_2D_Data("time", data_list=[Modulant]) Time = self.comp_axis_time( simu.machine.get_pole_pair_number(), per_t=1, From 59371bc86ea0375eb975e82b27a1caf1173ecb90 Mon Sep 17 00:00:00 2001 From: helene-t Date: Wed, 20 Oct 2021 16:44:15 +0200 Subject: [PATCH 130/167] [CC] remove plots --- Tests/Methods/Simulation/test_InVoltage_PWM.py | 2 +- pyleecan/Methods/Simulation/InputVoltage/gen_input.py | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Tests/Methods/Simulation/test_InVoltage_PWM.py b/Tests/Methods/Simulation/test_InVoltage_PWM.py index 48c25f33e..040a0a7b0 100644 --- a/Tests/Methods/Simulation/test_InVoltage_PWM.py +++ b/Tests/Methods/Simulation/test_InVoltage_PWM.py @@ -9,7 +9,7 @@ from pyleecan.Functions.Plot import dict_2D from Tests import save_plot_path as save_path -is_show_fig = True +is_show_fig = False def test_InVoltage_PWM(): diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 38dc5d53b..d610b700c 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -108,9 +108,7 @@ def gen_input(self): ind = searchsorted(self.PWM.fswi * 2 * mult, 2 * self.PWM.fmax, side="right") self.PWM.fs = 2 * mult[ind] * self.PWM.fswi # Generate PWM signal - Uabc, modulant, _, carrier, time = self.PWM.get_data( - is_norm=False, N_add=mult[ind] - ) + Uabc, _, _, _, time = self.PWM.get_data(is_norm=False, N_add=mult[ind]) # Account for coupling phase if self.PWM.is_star: Uabc[:, 0] = Uabc[:, 0] - Uabc.mean(axis=1) @@ -118,10 +116,6 @@ def gen_input(self): Uabc[:, 2] = Uabc[:, 2] - Uabc.mean(axis=1) # Create DataTime object self.time = time - Time = Data1D(name="time", unit="s", symmetries={"period": 5}, values=time) - Carrier = DataTime(values=carrier, axes=[Time]) - Modulant = DataTime(values=modulant, axes=[Time]) - Carrier.plot_2D_Data("time", data_list=[Modulant]) Time = self.comp_axis_time( simu.machine.get_pole_pair_number(), per_t=1, From 4c8f4c99c6194fe41059aa624f176964a939b23b Mon Sep 17 00:00:00 2001 From: helene-t Date: Wed, 20 Oct 2021 16:47:13 +0200 Subject: [PATCH 131/167] [BC] keep time axis if no additional point --- pyleecan/Functions/Electrical/comp_PWM.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyleecan/Functions/Electrical/comp_PWM.py b/pyleecan/Functions/Electrical/comp_PWM.py index db04d1229..21ec02c4e 100644 --- a/pyleecan/Functions/Electrical/comp_PWM.py +++ b/pyleecan/Functions/Electrical/comp_PWM.py @@ -209,6 +209,8 @@ def comp_volt_PWM_NUM( # Remove additional points if N_add is not None: Tpwmu2 = np.linspace(0, Tpwmu[-N_add], len(Tpwmu), endpoint=False) + else: + Tpwmu2 = Tpwmu if is_sin: Vas = k * M_I * (Vdc1 / 2) * np.sin(ws * Tpwmu2 + Phase[0] * 2 * np.pi / 3) From 85aff99cc524b9bfeedc7a43e1200e7bca8fb6b6 Mon Sep 17 00:00:00 2001 From: helene-t Date: Wed, 20 Oct 2021 17:16:50 +0200 Subject: [PATCH 132/167] [CO] move star coupling correction into ImportGenPWM --- pyleecan/Methods/Import/ImportGenPWM/get_data.py | 6 ++++++ pyleecan/Methods/Simulation/InputVoltage/gen_input.py | 5 ----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pyleecan/Methods/Import/ImportGenPWM/get_data.py b/pyleecan/Methods/Import/ImportGenPWM/get_data.py index 7a52b8c3b..8d8af96b1 100644 --- a/pyleecan/Methods/Import/ImportGenPWM/get_data.py +++ b/pyleecan/Methods/Import/ImportGenPWM/get_data.py @@ -67,4 +67,10 @@ def get_data(self, is_norm=True, N_add=None): PWM2 = v_pwm[1] PWM3 = v_pwm[2] Vpwm = np.column_stack([PWM1, PWM2, PWM3]) + if self.is_star: # star coupling + mean = Vpwm.mean(axis=1) + PWM1 = PWM1 - mean + PWM2 = PWM2 - mean + PWM3 = PWM3 - mean + Vpwm = np.column_stack([PWM1, PWM2, PWM3]) return Vpwm, Vas, MI, carrier, Tpwmu diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index d610b700c..2cffa5c9a 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -109,11 +109,6 @@ def gen_input(self): self.PWM.fs = 2 * mult[ind] * self.PWM.fswi # Generate PWM signal Uabc, _, _, _, time = self.PWM.get_data(is_norm=False, N_add=mult[ind]) - # Account for coupling phase - if self.PWM.is_star: - Uabc[:, 0] = Uabc[:, 0] - Uabc.mean(axis=1) - Uabc[:, 1] = Uabc[:, 1] - Uabc.mean(axis=1) - Uabc[:, 2] = Uabc[:, 2] - Uabc.mean(axis=1) # Create DataTime object self.time = time Time = self.comp_axis_time( From b9d201504fc9a14144f735a2c69af6f21e908097 Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 21 Oct 2021 10:58:54 +0200 Subject: [PATCH 133/167] [BC] typo in parameter names --- pyleecan/Methods/Simulation/OPslip/get_Id_Iq.py | 2 +- pyleecan/Methods/Simulation/OPslip/get_Ud_Uq.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyleecan/Methods/Simulation/OPslip/get_Id_Iq.py b/pyleecan/Methods/Simulation/OPslip/get_Id_Iq.py index bcbff207d..6c8bda231 100644 --- a/pyleecan/Methods/Simulation/OPslip/get_Id_Iq.py +++ b/pyleecan/Methods/Simulation/OPslip/get_Id_Iq.py @@ -15,5 +15,5 @@ def get_Id_Iq(self): Dict with key "Id", "Iq" """ - Z = self.I0 * exp(1j * self.IPhi0) + Z = self.I0_ref * exp(1j * self.IPhi0_ref) return {"Id": Z.real, "Iq": Z.imag} diff --git a/pyleecan/Methods/Simulation/OPslip/get_Ud_Uq.py b/pyleecan/Methods/Simulation/OPslip/get_Ud_Uq.py index ab68a04f1..87448a1cd 100644 --- a/pyleecan/Methods/Simulation/OPslip/get_Ud_Uq.py +++ b/pyleecan/Methods/Simulation/OPslip/get_Ud_Uq.py @@ -15,5 +15,5 @@ def get_Ud_Uq(self): Dict with key "Ud", "Uq" """ - Z = self.U0 * exp(1j * self.UPhi0) + Z = self.U0_ref * exp(1j * self.UPhi0_ref) return {"Ud": Z.real, "Uq": Z.imag} From d419ba6c637f02f22f20bc271497e9a1f3cf6592 Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 21 Oct 2021 11:25:47 +0200 Subject: [PATCH 134/167] [BC] adapt test to new OP object --- Tests/Functions/test_save_load.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tests/Functions/test_save_load.py b/Tests/Functions/test_save_load.py index ac1fac998..8690975c4 100644 --- a/Tests/Functions/test_save_load.py +++ b/Tests/Functions/test_save_load.py @@ -21,6 +21,7 @@ from pyleecan.Classes.SlotM11 import SlotM11 from pyleecan.Classes.SlotW10 import SlotW10 from pyleecan.Classes.Winding import Winding +from pyleecan.Classes.OPdq import OPdq from pyleecan.Functions.load import ( LoadSwitchError, LoadWrongDictClassError, @@ -129,7 +130,7 @@ def test_save_load_folder_path(): simu.input = InputCurrent( Is=Is, Ir=None, # No winding on the rotor - N0=N0, + OP=OPdq(N0=N0), angle_rotor=None, # Will be computed time=time, angle=angle, @@ -375,7 +376,7 @@ def test_save_load_simu(type_file): simu.input = InputCurrent( Is=Is, Ir=None, # No winding on the rotor - N0=N0, + OP=OPdq(N0=N0), angle_rotor=None, # Will be computed time=time, angle=angle, From 5b4a8b106aaede32af87f99e68dada5f80a2f004 Mon Sep 17 00:00:00 2001 From: helene-t Date: Thu, 21 Oct 2021 16:06:23 +0200 Subject: [PATCH 135/167] [CO] store Iabc(f) --- pyleecan/Methods/Output/OutElec/store.py | 14 +++++++++++--- pyleecan/Methods/Simulation/Electrical/run.py | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pyleecan/Methods/Output/OutElec/store.py b/pyleecan/Methods/Output/OutElec/store.py index 907ae9a4f..663f4931f 100644 --- a/pyleecan/Methods/Output/OutElec/store.py +++ b/pyleecan/Methods/Output/OutElec/store.py @@ -3,6 +3,8 @@ from SciDataTool import DataFreq +from pyleecan.Functions.Electrical.coordinate_transformation import dqh2n_DataTime + from ....Functions.Winding.gen_phase_list import gen_name @@ -40,11 +42,17 @@ def store(self, out_dict, out_dict_harm): if "Is_harm" in out_dict_harm: # Create Data object - axes_list = self.Us_harm.get_axes() - self.Is_harm = DataFreq( + Is_dqh = DataFreq( name="Harmonic stator current", unit="A", symbol="I_s^{harm}", - axes=axes_list, + axes=out_dict_harm["axes_list"], values=out_dict_harm["Is_harm"], ) + # ifft + Is_dqh_time = Is_dqh.freq_to_time() + qs = self.parent.simu.machine.stator.winding.qs + # back to ABC + Is_abc = dqh2n_DataTime(Is_dqh_time, qs, is_n_rms=True) + # fft + self.Is_harm = Is_abc.time_to_freq() diff --git a/pyleecan/Methods/Simulation/Electrical/run.py b/pyleecan/Methods/Simulation/Electrical/run.py index 0fc7e037d..1e879abe2 100644 --- a/pyleecan/Methods/Simulation/Electrical/run.py +++ b/pyleecan/Methods/Simulation/Electrical/run.py @@ -86,6 +86,7 @@ def run(self): out_dict_i = self.eec.solve_EEC() Is_harm[i, :] = array([out_dict_i["Id"], out_dict_i["Iq"], 0]) out_dict_harm["Is_harm"] = Is_harm + out_dict_harm["axes_list"] = Us_harm.get_axes() # Compute losses due to Joule effects out_dict = self.eec.comp_joule_losses(out_dict, machine) From 2d33b8e1700e0f58fccdc88aa9e8050f013d8cc5 Mon Sep 17 00:00:00 2001 From: helene-t Date: Fri, 22 Oct 2021 13:23:34 +0200 Subject: [PATCH 136/167] [BC] fix typo in axis name --- pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py index 187f2f323..141769464 100644 --- a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py @@ -27,9 +27,7 @@ def get_Phidqh_mean(self): is_dqh_rms=True, ) # mean over time axis - Phi_dqh_mean[i, :] = Phi_dqh.get_along("time=mean", "phases")[ - Phi_dqh.symbol - ] + Phi_dqh_mean[i, :] = Phi_dqh.get_along("time=mean", "phase")[Phi_dqh.symbol] # Store for next call self.Phi_dqh_mean = Phi_dqh_mean From ea47de74fa398628c3a496185c2bfacedac0ee29 Mon Sep 17 00:00:00 2001 From: helene-t Date: Fri, 22 Oct 2021 13:24:36 +0200 Subject: [PATCH 137/167] [CC] code cleaning --- pyleecan/Functions/Electrical/comp_PWM.py | 19 ++++++------------- .../Methods/Import/ImportGenPWM/get_data.py | 5 +---- .../Simulation/InputVoltage/gen_input.py | 4 ++-- .../Simulation/LUTdq/get_Phidqh_mag.py | 1 - 4 files changed, 9 insertions(+), 20 deletions(-) diff --git a/pyleecan/Functions/Electrical/comp_PWM.py b/pyleecan/Functions/Electrical/comp_PWM.py index 21ec02c4e..eb1985dc9 100644 --- a/pyleecan/Functions/Electrical/comp_PWM.py +++ b/pyleecan/Functions/Electrical/comp_PWM.py @@ -21,7 +21,6 @@ def comp_volt_PWM_NUM( type_carrier=0, var_amp=0, is_norm=True, - N_add=None, ): """ Generalized DPWM using numerical method according to @@ -206,21 +205,15 @@ def comp_volt_PWM_NUM( Phase = [0, 1, -1] - # Remove additional points - if N_add is not None: - Tpwmu2 = np.linspace(0, Tpwmu[-N_add], len(Tpwmu), endpoint=False) - else: - Tpwmu2 = Tpwmu - if is_sin: - Vas = k * M_I * (Vdc1 / 2) * np.sin(ws * Tpwmu2 + Phase[0] * 2 * np.pi / 3) - Vbs = k * M_I * (Vdc1 / 2) * np.sin(ws * Tpwmu2 + Phase[1] * 2 * np.pi / 3) - Vcs = k * M_I * (Vdc1 / 2) * np.sin(ws * Tpwmu2 + Phase[2] * 2 * np.pi / 3) + Vas = k * M_I * (Vdc1 / 2) * np.sin(ws * Tpwmu + Phase[0] * 2 * np.pi / 3) + Vbs = k * M_I * (Vdc1 / 2) * np.sin(ws * Tpwmu + Phase[1] * 2 * np.pi / 3) + Vcs = k * M_I * (Vdc1 / 2) * np.sin(ws * Tpwmu + Phase[2] * 2 * np.pi / 3) else: - Vas = k * M_I * (Vdc1 / 2) * np.cos(ws * Tpwmu2 + Phase[0] * 2 * np.pi / 3) - Vbs = k * M_I * (Vdc1 / 2) * np.cos(ws * Tpwmu2 + Phase[1] * 2 * np.pi / 3) - Vcs = k * M_I * (Vdc1 / 2) * np.cos(ws * Tpwmu2 + Phase[2] * 2 * np.pi / 3) + Vas = k * M_I * (Vdc1 / 2) * np.cos(ws * Tpwmu + Phase[0] * 2 * np.pi / 3) + Vbs = k * M_I * (Vdc1 / 2) * np.cos(ws * Tpwmu + Phase[1] * 2 * np.pi / 3) + Vcs = k * M_I * (Vdc1 / 2) * np.cos(ws * Tpwmu + Phase[2] * 2 * np.pi / 3) V_min = np.amin( np.concatenate((Vas[:, None], Vbs[:, None], Vcs[:, None]), axis=1), axis=1 diff --git a/pyleecan/Methods/Import/ImportGenPWM/get_data.py b/pyleecan/Methods/Import/ImportGenPWM/get_data.py index 8d8af96b1..0ae79b1aa 100644 --- a/pyleecan/Methods/Import/ImportGenPWM/get_data.py +++ b/pyleecan/Methods/Import/ImportGenPWM/get_data.py @@ -10,7 +10,7 @@ from ....Functions.Electrical.comp_PWM import comp_volt_PWM_NUM -def get_data(self, is_norm=True, N_add=None): +def get_data(self, is_norm=True): """Generate the PWM matrix Parameters @@ -34,8 +34,6 @@ def get_data(self, is_norm=True, N_add=None): time vector """ N = int(self.fs * self.duration) # Number of points - if N_add is not None: - N += N_add Tpwmu = np.linspace(0, N / self.fs, N, endpoint=False) # Time vector v_pwm, Vas, MI, carrier = comp_volt_PWM_NUM( Tpwmu=Tpwmu, @@ -54,7 +52,6 @@ def get_data(self, is_norm=True, N_add=None): PF_angle=0, var_amp=self.var_amp, is_norm=is_norm, - N_add=N_add, ) if is_norm: diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 2cffa5c9a..591d1c5c1 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -105,10 +105,10 @@ def gen_input(self): self.PWM.Vdc1 *= 2 # In comp_PWM, max is Vdc1/2 # Compute sampling frequency (even multiple of fswi + close to 2*fmax) mult = arange(1, 100) - ind = searchsorted(self.PWM.fswi * 2 * mult, 2 * self.PWM.fmax, side="right") + ind = searchsorted(2 * mult * self.PWM.fswi, 2 * self.PWM.fmax, side="right") self.PWM.fs = 2 * mult[ind] * self.PWM.fswi # Generate PWM signal - Uabc, _, _, _, time = self.PWM.get_data(is_norm=False, N_add=mult[ind]) + Uabc, _, _, _, time = self.PWM.get_data(is_norm=False) # Create DataTime object self.time = time Time = self.comp_axis_time( diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py index 115cddebf..faf215fec 100644 --- a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py @@ -23,7 +23,6 @@ def get_Phidqh_mag(self): else: raise Exception("Operating Point Id=Iq=0 is required to compute LUT") - Time = self.Phi_wind[ii].get_axes("time")[0] # dqh transform Phi_dqh_mag = n2dqh_DataTime( self.Phi_wind[ii], From 6710dd919b1f5063b6df46a55b8ad96c70f9b48f Mon Sep 17 00:00:00 2001 From: helene-t Date: Fri, 22 Oct 2021 13:25:01 +0200 Subject: [PATCH 138/167] [BC] add f=0Hz before ifft for consistent time vector --- pyleecan/Methods/Output/OutElec/store.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pyleecan/Methods/Output/OutElec/store.py b/pyleecan/Methods/Output/OutElec/store.py index 663f4931f..df10934ef 100644 --- a/pyleecan/Methods/Output/OutElec/store.py +++ b/pyleecan/Methods/Output/OutElec/store.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- -from numpy import mean, max as np_max, min as np_min +from numpy import insert from SciDataTool import DataFreq from pyleecan.Functions.Electrical.coordinate_transformation import dqh2n_DataTime -from ....Functions.Winding.gen_phase_list import gen_name - def store(self, out_dict, out_dict_harm): """Store the standard outputs of Electrical that are temporarily in out_dict as arrays into OutElec as Data object @@ -42,17 +40,20 @@ def store(self, out_dict, out_dict_harm): if "Is_harm" in out_dict_harm: # Create Data object + # Add f=0Hz + out_dict_harm["axes_list"][0].initial = 0 + out_dict_harm["axes_list"][0].number += 1 + values = insert(out_dict_harm["Is_harm"], 0, 0, axis=0) Is_dqh = DataFreq( name="Harmonic stator current", unit="A", symbol="I_s^{harm}", axes=out_dict_harm["axes_list"], - values=out_dict_harm["Is_harm"], + values=values, ) # ifft Is_dqh_time = Is_dqh.freq_to_time() qs = self.parent.simu.machine.stator.winding.qs # back to ABC Is_abc = dqh2n_DataTime(Is_dqh_time, qs, is_n_rms=True) - # fft self.Is_harm = Is_abc.time_to_freq() From 6364d6aa38860dfa7b323f12ae5027fefba2170f Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Fri, 22 Oct 2021 17:15:52 +0200 Subject: [PATCH 139/167] [CC] Add docstrings to current/voltage getters + clean get_US method --- pyleecan/Methods/Output/OutElec/get_I_fund.py | 10 ++++- pyleecan/Methods/Output/OutElec/get_I_harm.py | 7 ++- pyleecan/Methods/Output/OutElec/get_Is.py | 10 ++++- pyleecan/Methods/Output/OutElec/get_Us.py | 44 ++++++++++++------- .../Methods/Output/OutElec/get_Us_harm.py | 2 +- 5 files changed, 51 insertions(+), 22 deletions(-) diff --git a/pyleecan/Methods/Output/OutElec/get_I_fund.py b/pyleecan/Methods/Output/OutElec/get_I_fund.py index 3c6cb0d3f..e01f1923f 100644 --- a/pyleecan/Methods/Output/OutElec/get_I_fund.py +++ b/pyleecan/Methods/Output/OutElec/get_I_fund.py @@ -1,4 +1,4 @@ -from numpy import array, where, isclose, zeros +from numpy import where, isclose, zeros from SciDataTool import Data1D, DataTime, DataFreq @@ -6,13 +6,19 @@ def get_I_fund(self, Time=None): - """Return the stator current DataTime object + """Return the fundamental of stator currents in a DataND object Parameters ---------- self : OutElec an OutElec object + Time : Data + Time axis + Returns + ------- + I_fund: DataND + fundamental stator current """ if Time is None: Time = self.axes_dict["time"] diff --git a/pyleecan/Methods/Output/OutElec/get_I_harm.py b/pyleecan/Methods/Output/OutElec/get_I_harm.py index d742d3fcc..f5f854404 100644 --- a/pyleecan/Methods/Output/OutElec/get_I_harm.py +++ b/pyleecan/Methods/Output/OutElec/get_I_harm.py @@ -3,13 +3,18 @@ def get_I_harm(self): - """Return the stator current DataTime object + """Return the stator current harmonics in a DataFreq object Parameters ---------- self : OutElec an OutElec object + Returns + ------- + I_harm: DataND + stator current harmonics + """ # Generate current according to Id/Iq diff --git a/pyleecan/Methods/Output/OutElec/get_Is.py b/pyleecan/Methods/Output/OutElec/get_Is.py index deacab066..f3c70d4c8 100644 --- a/pyleecan/Methods/Output/OutElec/get_Is.py +++ b/pyleecan/Methods/Output/OutElec/get_Is.py @@ -1,11 +1,19 @@ def get_Is(self, Time=None, is_current_harm=False): - """Return the stator current DataTime object + """Return the stator current DataND object Parameters ---------- self : OutElec an OutElec object + Time : Data + Time axis + is_current_harm: bool + True to return current harmonics too + Returns + ------- + Is: DataND + fundamental stator current """ # Calculate stator currents if Is is not in OutElec if self.Is is None or not is_current_harm: diff --git a/pyleecan/Methods/Output/OutElec/get_Us.py b/pyleecan/Methods/Output/OutElec/get_Us.py index b726d1328..28c64b05f 100644 --- a/pyleecan/Methods/Output/OutElec/get_Us.py +++ b/pyleecan/Methods/Output/OutElec/get_Us.py @@ -1,31 +1,41 @@ +from numpy import array + +from SciDataTool import DataTime + from ....Functions.Electrical.coordinate_transformation import dqh2n -from numpy import pi, array, transpose -from SciDataTool import Data1D, DataTime -from ....Functions.Winding.gen_phase_list import gen_name def get_Us(self): - """Return the stator voltage""" + """Return the fundamental stator voltage as DataND object + + Parameters + ---------- + self : OutElec + an OutElec object + + Returns + ------- + Us: DataND + fundamental stator voltage + """ if self.Us is None: # Generate current according to Ud/Uq Usdqh = array([self.OP.get_Ud_Uq()["Ud"], self.OP.get_Ud_Uq()["Uq"], 0]) - time = self.axes_dict["time"].get_values(is_oneperiod=True) + + Time = self.axes_dict["time"] + + angle_elec = Time.get_values(is_smallestperiod=True, normalization="angle_elec") qs = self.parent.simu.machine.stator.winding.qs - felec = self.OP.get_felec() - - # add stator current - Us = dqh2n(Usdqh, 2 * pi * felec * time, n=qs) - Phase = Data1D( - name="phase", - unit="", - values=gen_name(qs), - is_components=True, - ) + stator_label = "phase_" + self.parent.simu.machine.stator.get_label() + + # Switch from dqh to abc referential + Us = dqh2n(Usdqh, angle_elec, n=qs) + self.Us = DataTime( name="Stator voltage", unit="V", symbol="Us", - axes=[Phase, self.axes_dict["time"].copy()], - values=transpose(Us), + axes=[Time.copy(), self.axes_dict[stator_label].copy()], + values=Us, ) return self.Us diff --git a/pyleecan/Methods/Output/OutElec/get_Us_harm.py b/pyleecan/Methods/Output/OutElec/get_Us_harm.py index 49511c062..a8a5c9b9d 100644 --- a/pyleecan/Methods/Output/OutElec/get_Us_harm.py +++ b/pyleecan/Methods/Output/OutElec/get_Us_harm.py @@ -13,7 +13,7 @@ def get_Us_harm(self, is_dqh=True): Returns ------- - Us_harm + Us_harm: DataND harmonic stator voltage """ if self.Us_PWM is None: From 5a88ea7393987caee819fea58c58fe4139b8278d Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Sun, 24 Oct 2021 23:13:45 +0200 Subject: [PATCH 140/167] [CO] Improve validation tests for EEC_PMSM [WiP] Add validation test with MTPA [CC] Merge get_Ld/get_Lq into get_Ldqh [CO] Enable scatter interpolation in interp_Phi_dqh --- .../Electrical/test_EEC_ELUT_PMSM.py | 280 +++++++++++++++--- Tests/Validation/Electrical/test_EEC_PMSM.py | 133 ++++++--- pyleecan/Classes/Class_Dict.json | 27 +- pyleecan/Classes/EEC_PMSM.py | 17 ++ pyleecan/Classes/FluxLinkFEMM.py | 30 ++ pyleecan/Classes/IndMagFEMM.py | 30 ++ pyleecan/Classes/LUTdq.py | 56 +--- .../Functions/Electrical/comp_fluxlinkage.py | 118 -------- pyleecan/Functions/Electrical/solve_FEMM.py | 79 ----- .../ClassesRef/Simulation/EEC_PMSM.csv | 1 + .../ClassesRef/Simulation/FluxLinkFEMM.csv | 1 + .../ClassesRef/Simulation/IndMagFEMM.csv | 1 + .../Generator/ClassesRef/Simulation/LUTdq.csv | 10 +- .../Machine/Conductor/comp_skin_effect.py | 22 +- pyleecan/Methods/Post/PostLUT/run.py | 14 +- .../Simulation/EEC_PMSM/comp_parameters.py | 57 ++-- .../EEC_PMSM/comp_torque_sync_rel.py | 33 +++ .../FluxLinkFEMM/comp_fluxlinkage.py | 49 +-- .../Simulation/IndMagFEMM/comp_inductance.py | 55 ++-- pyleecan/Methods/Simulation/LUTdq/get_Ld.py | 22 -- .../LUTdq/{get_Lq.py => get_Ldqh.py} | 12 +- pyleecan/Methods/Simulation/LUTdq/get_Lmd.py | 23 -- .../Methods/Simulation/LUTdq/get_Lmdqh.py | 42 +++ pyleecan/Methods/Simulation/LUTdq/get_Lmq.py | 23 -- .../Simulation/LUTdq/get_param_dict.py | 38 ++- .../Simulation/LUTdq/interp_Phi_dqh.py | 58 +++- 26 files changed, 716 insertions(+), 515 deletions(-) delete mode 100644 pyleecan/Functions/Electrical/comp_fluxlinkage.py delete mode 100644 pyleecan/Functions/Electrical/solve_FEMM.py create mode 100644 pyleecan/Methods/Simulation/EEC_PMSM/comp_torque_sync_rel.py delete mode 100644 pyleecan/Methods/Simulation/LUTdq/get_Ld.py rename pyleecan/Methods/Simulation/LUTdq/{get_Lq.py => get_Ldqh.py} (52%) delete mode 100644 pyleecan/Methods/Simulation/LUTdq/get_Lmd.py create mode 100644 pyleecan/Methods/Simulation/LUTdq/get_Lmdqh.py delete mode 100644 pyleecan/Methods/Simulation/LUTdq/get_Lmq.py diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py index 4d2b2a793..ab3a359e2 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py @@ -1,10 +1,12 @@ from os.path import join -from Tests import save_validation_path as save_path, TEST_DATA_DIR -from numpy import sqrt, pi -from multiprocessing import cpu_count +import numpy as np +from numpy.testing import assert_almost_equal import pytest + +from SciDataTool.Functions.Plot.plot_2D import plot_2D + from pyleecan.Classes.ImportGenPWM import ImportGenPWM from pyleecan.Classes.InputVoltage import InputVoltage from pyleecan.Classes.OPdq import OPdq @@ -13,15 +15,17 @@ from pyleecan.Classes.VarLoadCurrent import VarLoadCurrent from pyleecan.Classes.MagFEMM import MagFEMM from pyleecan.Classes.PostLUT import PostLUT -from pyleecan.Classes.DataKeeper import DataKeeper -from pyleecan.Classes.ImportMatrixXls import ImportMatrixXls from pyleecan.Classes.Electrical import Electrical from pyleecan.Classes.EEC_ANL import EEC_ANL +from pyleecan.Classes.EEC_PMSM import EEC_PMSM from pyleecan.Functions.load import load from pyleecan.Functions.Plot import dict_2D +from pyleecan.Functions.Electrical.coordinate_transformation import n2dqh_DataTime from pyleecan.definitions import DATA_DIR +from Tests import save_validation_path as save_path + is_show_fig = False @@ -30,13 +34,29 @@ @pytest.mark.EEC_PMSM @pytest.mark.IPMSM @pytest.mark.periodicity -def test_EEC_ELUT_PMSM(): +@pytest.fixture(scope="module") +def test_ELUT(): + """Fixture to calculate ELUT and use it in all tests""" + + _, ELUT = test_EEC_ELUT_PMSM() + + return ELUT + + +@pytest.mark.long_5s +@pytest.mark.MagFEMM +@pytest.mark.EEC_PMSM +@pytest.mark.IPMSM +@pytest.mark.periodicity +@pytest.mark.skip(reason="called by fixture and for direct call to test") +def test_EEC_ELUT_PMSM(n_Id=3, n_Iq=3): """Validation of the PMSM Electrical Equivalent Circuit with the Prius machine""" Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) # Generate ELUT - simu = Simu1(name="test_LUT_PMSM", machine=Toyota_Prius) + name = "test_EEC_ELUT_PMSM" + simu = Simu1(name=name, machine=Toyota_Prius) # Definition of the input simu.input = InputCurrent( @@ -45,14 +65,16 @@ def test_EEC_ELUT_PMSM(): OP=OPdq(N0=1000, Id_ref=0, Iq_ref=0), ) - # Load OP_matrix - OP_matrix = ( - ImportMatrixXls( - file_path=join(TEST_DATA_DIR, "OP_ELUT_PMSM.xlsx"), sheet="Feuil1" - ) - .get_data() - .astype(float) + # Build OP_matrix with a meshgrid of Id/Iq + Id_min, Id_max = 0, 100 + Iq_min, Iq_max = 0, 200 + Id, Iq = np.meshgrid( + np.linspace(Id_min, Id_max, n_Id), np.linspace(Iq_min, Iq_max, n_Iq) ) + OP_matrix = np.zeros((n_Id * n_Iq, 3)) + OP_matrix[:, 0] = simu.input.OP.N0 + OP_matrix[:, 1] = Id.ravel() + OP_matrix[:, 2] = Iq.ravel() # Set varspeed simulation simu.var_simu = VarLoadCurrent( @@ -65,24 +87,212 @@ def test_EEC_ELUT_PMSM(): # Define second simu for FEMM comparison simu.mag = MagFEMM(is_periodicity_a=True, is_periodicity_t=True, nb_worker=4) - # Stator Winding Flux along dq Datakeeper - Phi_wind_dk = DataKeeper( - name="Stator Winding Flux", - symbol="Phi_{wind}", - unit="Wb", - keeper="lambda out: out.mag.Phi_wind_stator", - ) - - # Store Datakeepers - simu.var_simu.datakeeper_list = [Phi_wind_dk] - # Postprocessing - simu.var_simu.postproc_list = [PostLUT()] + simu.var_simu.postproc_list = [PostLUT(is_save_LUT=True)] out = simu.run() ELUT = out.simu.var_simu.postproc_list[0].LUT + # Check flux linkage dqh values + Phi_dqh_mean = ELUT.get_Phidqh_mean() + Phi_dqh0 = n2dqh_DataTime( + ELUT.Phi_wind[0], + is_dqh_rms=True, + ) + Phi_dqh0_mean = Phi_dqh0.get_along("time=mean", "phase")[Phi_dqh0.symbol] + assert_almost_equal(Phi_dqh0_mean, Phi_dqh_mean[0, :], decimal=20) + assert_almost_equal(Phi_dqh0_mean[0], 0.141, decimal=3) + + # Plot 3-phase current function of time + ELUT.Phi_wind[0].plot_2D_Data( + "time", + "phase[]", + save_path=join(save_path, name + "_flux_linkage_abc.png"), + is_show_fig=is_show_fig, + **dict_2D + ) + Phi_dqh0.plot_2D_Data( + "time", + "phase[]", + save_path=join(save_path, name + "_flux_linkage_dqh.png"), + is_show_fig=is_show_fig, + **dict_2D + ) + + return out, ELUT + + +@pytest.mark.long_5s +@pytest.mark.MagFEMM +@pytest.mark.EEC_PMSM +@pytest.mark.IPMSM +@pytest.mark.periodicity +def test_EEC_ELUT_PMSM_MTPA(test_ELUT): + """Validation of the PMSM Electrical Equivalent Circuit with the Prius machine for MTPA calculation""" + + Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) + qs = Toyota_Prius.stator.winding.qs + p = Toyota_Prius.get_pole_pair_number() + + simu_MTPA = Simu1(name="test_EEC_ELUT_PMSM_MTPA", machine=Toyota_Prius) + + # Definition of the input + OP_ref = OPdq(N0=1000, Id_ref=50, Iq_ref=100) + simu_MTPA.input = InputCurrent( + Na_tot=1024, + Nt_tot=1024, + OP=OP_ref, + ) + + OP_matrix = test_ELUT.OP_matrix + # Get Id_min, Id_max, Iq_min, Iq_max from OP_matrix + Id_min = np.min(OP_matrix[:, 1]) + Id_max = np.max(OP_matrix[:, 1]) + Iq_min = np.min(OP_matrix[:, 2]) + Iq_max = np.max(OP_matrix[:, 2]) + + Id, Iq = np.meshgrid( + np.linspace(Id_min, Id_max, 50), np.linspace(Iq_min, Iq_max, 100) + ) + Id, Iq = Id.ravel(), Iq.ravel() + + elec_model = Electrical(eec=EEC_PMSM(), ELUT_enforced=test_ELUT) + # Interpolate stator winding flux in dqh frame for all Id/Iq + elec_model.eec.parameters = elec_model.ELUT_enforced.get_param_dict( + Id=Id, Iq=Iq, param_list=["Idqh", "Phidqh"] + ) + + # Maximum current [Arms] + I_max = 250 + Imax_interp = np.sqrt(Id ** 2 + Iq ** 2) + # Maximum voltage [Vrms] + U_max = 500 + # Speed vector + Nspeed = 50 + N0_min = 50 + N0_max = 8000 + N0_vect = np.linspace(N0_min, N0_max, Nspeed) + # Maximum current vector + Ntorque = 5 + if Ntorque == 1: + I_max_vect = np.array([I_max]) + else: + I_max_vect = np.linspace(0, I_max, Ntorque) + + # Init OP_matrix + OP_matrix_MTPA = np.zeros((Nspeed, Ntorque, 4)) + U_MTPA = np.zeros((Nspeed, Ntorque, 3)) + I_MTPA = np.zeros((Nspeed, Ntorque, 3)) + + for ii, N0 in enumerate(N0_vect): + + print("Speed " + str(ii + 1) + "/" + str(Nspeed)) + + # Update operating point + OP_ref.N0 = N0 + OP_ref.felec = None + + # Update stator resistance with skin effect + elec_model.eec.comp_parameters( + Toyota_Prius, + OP=OP_ref, + Tsta=elec_model.Tsta, + Trot=elec_model.Trot, + ) + + # Calculate voltage + out_dict = elec_model.eec.solve_EEC() + U_max_interp = np.sqrt(out_dict["Ud"] ** 2 + out_dict["Uq"] ** 2) + + # Compute torque + Tem_sync, Tem_rel = elec_model.eec.comp_torque_sync_rel(qs, p) + Tem_interp = Tem_sync + Tem_rel + + for kk, I_max0 in enumerate(I_max_vect): + + if I_max0 == 0: + # Finding indices of operating points satisfying Vmax voltage + k0 = np.logical_and(U_max_interp <= U_max, np.abs(Tem_interp) <= 1e-1) + j0 = np.logical_and(k0, np.abs(Iq) == 0) + + # Finding index of operating point giving lowest current + jmax = np.argmin(np.abs(Imax_interp[j0])) + + # print(Id_interp[j0][jmax]) + # print(Iq_interp[j0][jmax]) + + else: + # Finding indices of operating points satisfying Vmax and XImax(i) voltage and torque limitations + j0 = np.logical_and( + U_max_interp <= U_max, Imax_interp <= np.abs(I_max0) + ) + + if I_max0 > 0: + # Finding index of operating point giving maximum positive torque among feasible operating points + jmax = np.argmax(Tem_interp[j0]) + else: + # Finding index of operating point giving maximum negative torque among feasible operating points + jmax = np.argmin(Tem_interp[j0]) + + # Store values in MTPA + OP_matrix_MTPA[ii, kk, 0] = N0 + OP_matrix_MTPA[ii, kk, 1] = Id[j0][jmax] + OP_matrix_MTPA[ii, kk, 2] = Iq[j0][jmax] + OP_matrix_MTPA[ii, kk, 3] = Tem_interp[j0][jmax] + U_MTPA[ii, kk, 0] = out_dict["Ud"][j0][jmax] + U_MTPA[ii, kk, 1] = out_dict["Uq"][j0][jmax] + U_MTPA[ii, kk, 2] = U_max_interp[j0][jmax] + I_MTPA[ii, kk, 0] = OP_matrix_MTPA[ii, kk, 1] + I_MTPA[ii, kk, 1] = OP_matrix_MTPA[ii, kk, 2] + I_MTPA[ii, kk, 2] = Imax_interp[j0][jmax] + + i_load = 0 + plot_2D( + [OP_matrix_MTPA[:, i_load, 0]], + [I_MTPA[:, i_load, 0], I_MTPA[:, i_load, 1], I_MTPA[:, i_load, 2]], + xlabel="Speed [rpm]", + ylabel="Current [Arms]", + legend_list=["Id", "Iq", "Imax"], + ) + + i_load = 0 + plot_2D( + [OP_matrix_MTPA[:, i_load, 0]], + [U_MTPA[:, i_load, 0], U_MTPA[:, i_load, 1], U_MTPA[:, i_load, 2]], + xlabel="Speed [rpm]", + ylabel="Voltage [Vrms]", + legend_list=["Ud", "Uq", "Umax"], + ) + + if Ntorque > 1: + y_list = list() + legend_list = list() + for i_load in range(Ntorque): + y_list.append(OP_matrix_MTPA[:, i_load, 3]) + legend_list.append( + "Load level = " + str(int(round(100 * (i_load) / (Ntorque - 1)))) + " %" + ) + + plot_2D( + [OP_matrix_MTPA[:, i_load, 0]], + y_list, + xlabel="Speed [rpm]", + ylabel="Average torque [N.m]", + legend_list=legend_list, + ) + + +@pytest.mark.long_5s +@pytest.mark.MagFEMM +@pytest.mark.EEC_PMSM +@pytest.mark.IPMSM +@pytest.mark.periodicity +def test_EEC_ELUT_PMSM_PWM(test_ELUT): + """Validation of the PMSM Electrical Equivalent Circuit with the Prius machine including PWM""" + + Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) + # Simu with EEC using ELUT fmax = 20000 fswi = 7000 @@ -98,19 +308,11 @@ def test_EEC_ELUT_PMSM(): OP=OPdq(N0=1000, Id_ref=50, Iq_ref=100, Ud_ref=200, Uq_ref=300), ) - simu_EEC.elec = Electrical(eec=EEC_ANL(), ELUT_enforced=ELUT) + simu_EEC.elec = Electrical(eec=EEC_ANL(), ELUT_enforced=test_ELUT) out_EEC = simu_EEC.run() # Plot 3-phase current function of time - out.mag.Phi_wind_stator.plot_2D_Data( - "time", - "phase[]", - save_path=join(save_path, "EEC_FEMM_IPMSM_currents.png"), - is_show_fig=is_show_fig, - **dict_2D - ) - out_EEC.elec.Is_harm.plot_2D_Data( "freqs", save_path=join(save_path, "EEC_FEMM_IPMSM_Is_harm.png"), @@ -118,9 +320,13 @@ def test_EEC_ELUT_PMSM(): **dict_2D ) - return out, out_EEC + return out_EEC # To run it without pytest if __name__ == "__main__": - out, out_EEC = test_EEC_ELUT_PMSM() + # out0, ELUT = test_ELUT() + # ELUT.save("ELUT_PMSM.h5") + ELUT = load("ELUT_PMSM.h5") + test_EEC_ELUT_PMSM_MTPA(ELUT) + # test_EEC_ELUT_PMSM_PWM(ELUT) diff --git a/Tests/Validation/Electrical/test_EEC_PMSM.py b/Tests/Validation/Electrical/test_EEC_PMSM.py index 0e34c6e5e..729d3c138 100644 --- a/Tests/Validation/Electrical/test_EEC_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_PMSM.py @@ -1,12 +1,15 @@ from os.path import join -from Tests import save_validation_path as save_path -from numpy import sqrt, pi +from numpy import sqrt, pi, linspace, array, zeros +from numpy.testing import assert_almost_equal + from multiprocessing import cpu_count import pytest -from pyleecan.Classes.OPdq import OPdq +from SciDataTool.Functions.Plot.plot_2D import plot_2D + +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.Electrical import Electrical @@ -14,11 +17,15 @@ from pyleecan.Classes.FluxLinkFEMM import FluxLinkFEMM from pyleecan.Classes.IndMagFEMM import IndMagFEMM from pyleecan.Classes.MagFEMM import MagFEMM -from pyleecan.Classes.Output import Output +from pyleecan.Classes.VarLoadCurrent import VarLoadCurrent + from pyleecan.Functions.load import load from pyleecan.Functions.Plot import dict_2D + from pyleecan.definitions import DATA_DIR +from Tests import save_validation_path as save_path + @pytest.mark.long_5s @pytest.mark.MagFEMM @@ -27,7 +34,7 @@ @pytest.mark.periodicity @pytest.mark.SingleOP @pytest.mark.skip(reason="Work in progress") -def test_EEC_PMSM(): +def test_EEC_PMSM(nb_worker=int(0.5 * cpu_count())): """Validation of the PMSM Electrical Equivalent Circuit with the Prius machine Compute Torque from EEC results and compare with Yang et al, 2013 """ @@ -36,54 +43,110 @@ def test_EEC_PMSM(): simu = Simu1(name="test_EEC_PMSM", machine=Toyota_Prius) # Definition of the input - simu.input = InputCurrent(OP=OPdq(N0=2000), Nt_tot=10, Na_tot=2048) + simu.input = InputCurrent(OP=OPdq(N0=2000), Nt_tot=8 * 16, Na_tot=2048) simu.input.set_Id_Iq(I0=250 / sqrt(2), Phi0=60 * pi / 180) - # Define second simu for FEMM comparison - simu2 = simu.copy() - simu2.name = "test_EEC_PMSM_FEMM" + # Definition of the magnetic simulation + simu_mag = simu.copy() + simu_mag.mag = MagFEMM( + is_periodicity_a=True, is_periodicity_t=True, nb_worker=nb_worker + ) - # Definition of the electrical simulation (FEMM) + # Definition of the electrical simulation simu.elec = Electrical() simu.elec.eec = EEC_PMSM( - indmag=IndMagFEMM(is_periodicity_a=True, Nt_tot=16), - fluxlink=FluxLinkFEMM(is_periodicity_a=True, Nt_tot=16), + indmag=IndMagFEMM(is_periodicity_a=True, Nt_tot=8 * 16, nb_worker=nb_worker), + fluxlink=FluxLinkFEMM( + is_periodicity_a=True, Nt_tot=8 * 16, nb_worker=nb_worker + ), ) - simu.mag = None - simu.force = None - simu.struct = None + out = simu.run() + out_mag = simu_mag.run() - out = Output(simu=simu) - simu.run() - - # Definition of the magnetic simulation (FEMM) - simu2.mag = MagFEMM( - type_BH_stator=0, - type_BH_rotor=0, - is_periodicity_a=True, - nb_worker=cpu_count(), - ) - - out2 = Output(simu=simu2) - simu2.run() + # from Yang et al, 2013 + assert out.elec.Tem_av_ref == pytest.approx(81.69, rel=0.1) + assert out_mag.mag.Tem_av == pytest.approx(81.91, rel=0.1) # Plot 3-phase current function of time out.elec.get_Is().plot_2D_Data( "time", "phase[]", - save_path=join(save_path, "EEC_FEMM_IPMSM_currents.png"), - is_show_fig=False, + # save_path=join(save_path, "EEC_FEMM_IPMSM_currents.png"), + # is_show_fig=False, **dict_2D ) - # from Yang et al, 2013 - assert out.elec.Tem_av_ref == pytest.approx(81.69, rel=0.1) - assert out2.mag.Tem_av == pytest.approx(81.91, rel=0.1) + return out + + +def test_EEC_PMSM_sync_rel(nb_worker=int(0.5 * cpu_count())): - return out, out2 + Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) + simu = Simu1(name="test_EEC_PMSM", machine=Toyota_Prius) + + # Definition of the input + simu.input = InputCurrent(OP=OPdq(N0=2000), Nt_tot=8 * 16, Na_tot=2048) + simu.input.set_Id_Iq(I0=250 / sqrt(2), Phi0=60 * pi / 180) + + # Definition of the simulation (FEMM) + simu.elec = Electrical() + simu.elec.eec = EEC_PMSM( + indmag=IndMagFEMM(is_periodicity_a=True, Nt_tot=8 * 16, nb_worker=nb_worker), + fluxlink=FluxLinkFEMM( + is_periodicity_a=True, Nt_tot=8 * 16, nb_worker=nb_worker + ), + ) + + # Creating the Operating point matrix + Tem_av_ref = array([79, 125, 160, 192, 237, 281, 319, 343, 353, 332, 266, 164, 22]) + N_simu = Tem_av_ref.size + Phi0_ref = linspace(60 * pi / 180, 180 * pi / 180, N_simu) + OP_matrix = zeros((N_simu, 4)) + # Set N0 = 2000 [rpm] for all simulation + OP_matrix[:, 0] = 2000 + # Set I0 = 250/sqrt(2) [A] (RMS) for all simulations + OP_matrix[:, 1] = 250 / sqrt(2) + # Set Phi0 from 60° to 180° + OP_matrix[:, 2] = Phi0_ref + # Set reference torque from Yang et al, 2013 + OP_matrix[:, 3] = Tem_av_ref + + simu.var_simu = VarLoadCurrent( + is_torque=True, OP_matrix=OP_matrix, type_OP_matrix=0, is_keep_all_output=True + ) + + out = simu.run() + + plot_2D( + array([x * 180 / pi for x in out.xoutput_dict["Phi0"].result]), + [out.xoutput_dict["Tem_av_ref"].result, Tem_av_ref], + legend_list=["Pyleecan", "Yang et al, 2013"], + xlabel="Current angle [°]", + ylabel="Electrical torque [N.m]", + title="Electrical torque vs current angle", + **dict_2D + ) + + qs = Toyota_Prius.stator.winding.qs + p = Toyota_Prius.get_pole_pair_number() + Tem_sync = zeros(N_simu) + Tem_rel = zeros(N_simu) + phi_mag = out.output_list[0].elec.eec.fluxlink.comp_fluxlinkage(Toyota_Prius) + for ii, out_ii in enumerate(out.output_list): + out_ii.simu.elec.eec.parameters["phi"] = phi_mag + Tem_sync[ii], Tem_rel[ii] = out_ii.simu.elec.eec.comp_torque_sync_rel( + qs, p, Toyota_Prius + ) + + Tem2 = Tem_sync + Tem_rel + assert_almost_equal(out.xoutput_dict["Tem_av_ref"].result - Tem2, 0, decimal=13) + + return out # To run it without pytest if __name__ == "__main__": - out, out2 = test_EEC_PMSM() + # out, out2 = test_EEC_PMSM() + + test_EEC_PMSM_sync_rel() diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 7c4dd3efd..66dbffad2 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -1146,7 +1146,8 @@ "solve_EEC", "gen_drive", "comp_joule_losses", - "comp_BEMF_harmonics" + "comp_BEMF_harmonics", + "comp_torque_sync_rel" ], "mother": "EEC", "name": "EEC_PMSM", @@ -1724,6 +1725,15 @@ "type": "float", "unit": "", "value": 0.5 + }, + { + "desc": "To run FEMM in parallel (the parallelization is on the time loop)", + "max": "", + "min": "", + "name": "nb_worker", + "type": "int", + "unit": "", + "value": 1 } ] }, @@ -3981,6 +3991,15 @@ "type": "float", "unit": "", "value": 0.5 + }, + { + "desc": "To run FEMM in parallel (the parallelization is on the time loop)", + "max": "", + "min": "", + "name": "nb_worker", + "type": "int", + "unit": "", + "value": 1 } ] }, @@ -4405,11 +4424,9 @@ "is_internal": false, "methods": [ "get_param_dict", - "get_Lq", "get_bemf", - "get_Ld", - "get_Lmd", - "get_Lmq", + "get_Ldqh", + "get_Lmdqh", "import_from_data", "get_Phidqh_mean", "get_Phidqh_mag", diff --git a/pyleecan/Classes/EEC_PMSM.py b/pyleecan/Classes/EEC_PMSM.py index 1d34cb451..f9d7892fe 100644 --- a/pyleecan/Classes/EEC_PMSM.py +++ b/pyleecan/Classes/EEC_PMSM.py @@ -42,6 +42,11 @@ except ImportError as error: comp_BEMF_harmonics = error +try: + from ..Methods.Simulation.EEC_PMSM.comp_torque_sync_rel import comp_torque_sync_rel +except ImportError as error: + comp_torque_sync_rel = error + from ._check import InitUnKnowClassError from .IndMag import IndMag @@ -108,6 +113,18 @@ class EEC_PMSM(EEC): ) else: comp_BEMF_harmonics = comp_BEMF_harmonics + # cf Methods.Simulation.EEC_PMSM.comp_torque_sync_rel + if isinstance(comp_torque_sync_rel, ImportError): + comp_torque_sync_rel = property( + fget=lambda x: raise_( + ImportError( + "Can't use EEC_PMSM method comp_torque_sync_rel: " + + str(comp_torque_sync_rel) + ) + ) + ) + else: + comp_torque_sync_rel = comp_torque_sync_rel # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/FluxLinkFEMM.py b/pyleecan/Classes/FluxLinkFEMM.py index a59899263..b14dc6775 100644 --- a/pyleecan/Classes/FluxLinkFEMM.py +++ b/pyleecan/Classes/FluxLinkFEMM.py @@ -57,6 +57,7 @@ def __init__( is_periodicity_a=False, Nt_tot=5, Kgeo_fineness=0.5, + nb_worker=1, init_dict=None, init_str=None, ): @@ -87,6 +88,8 @@ def __init__( Nt_tot = init_dict["Nt_tot"] if "Kgeo_fineness" in list(init_dict.keys()): Kgeo_fineness = init_dict["Kgeo_fineness"] + if "nb_worker" in list(init_dict.keys()): + nb_worker = init_dict["nb_worker"] # Set the properties (value check and convertion are done in setter) self.FEMM_dict = FEMM_dict self.type_calc_leakage = type_calc_leakage @@ -94,6 +97,7 @@ def __init__( self.is_periodicity_a = is_periodicity_a self.Nt_tot = Nt_tot self.Kgeo_fineness = Kgeo_fineness + self.nb_worker = nb_worker # Call FluxLink init super(FluxLinkFEMM, self).__init__() # The class is frozen (in FluxLink init), for now it's impossible to @@ -113,6 +117,7 @@ def __str__(self): FluxLinkFEMM_str += "is_periodicity_a = " + str(self.is_periodicity_a) + linesep FluxLinkFEMM_str += "Nt_tot = " + str(self.Nt_tot) + linesep FluxLinkFEMM_str += "Kgeo_fineness = " + str(self.Kgeo_fineness) + linesep + FluxLinkFEMM_str += "nb_worker = " + str(self.nb_worker) + linesep return FluxLinkFEMM_str def __eq__(self, other): @@ -136,6 +141,8 @@ def __eq__(self, other): return False if other.Kgeo_fineness != self.Kgeo_fineness: return False + if other.nb_worker != self.nb_worker: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -161,6 +168,8 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".Nt_tot") if other._Kgeo_fineness != self._Kgeo_fineness: diff_list.append(name + ".Kgeo_fineness") + if other._nb_worker != self._nb_worker: + diff_list.append(name + ".nb_worker") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -180,6 +189,7 @@ def __sizeof__(self): S += getsizeof(self.is_periodicity_a) S += getsizeof(self.Nt_tot) S += getsizeof(self.Kgeo_fineness) + S += getsizeof(self.nb_worker) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -207,6 +217,7 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): FluxLinkFEMM_dict["is_periodicity_a"] = self.is_periodicity_a FluxLinkFEMM_dict["Nt_tot"] = self.Nt_tot FluxLinkFEMM_dict["Kgeo_fineness"] = self.Kgeo_fineness + FluxLinkFEMM_dict["nb_worker"] = self.nb_worker # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name FluxLinkFEMM_dict["__class__"] = "FluxLinkFEMM" @@ -221,6 +232,7 @@ def _set_None(self): self.is_periodicity_a = None self.Nt_tot = None self.Kgeo_fineness = None + self.nb_worker = None # Set to None the properties inherited from FluxLink super(FluxLinkFEMM, self)._set_None() @@ -335,3 +347,21 @@ def _set_Kgeo_fineness(self, value): :Type: float """, ) + + def _get_nb_worker(self): + """getter of nb_worker""" + return self._nb_worker + + def _set_nb_worker(self, value): + """setter of nb_worker""" + check_var("nb_worker", value, "int") + self._nb_worker = value + + nb_worker = property( + fget=_get_nb_worker, + fset=_set_nb_worker, + doc=u"""To run FEMM in parallel (the parallelization is on the time loop) + + :Type: int + """, + ) diff --git a/pyleecan/Classes/IndMagFEMM.py b/pyleecan/Classes/IndMagFEMM.py index b2707cc3e..976253309 100644 --- a/pyleecan/Classes/IndMagFEMM.py +++ b/pyleecan/Classes/IndMagFEMM.py @@ -57,6 +57,7 @@ def __init__( is_periodicity_a=False, Nt_tot=5, Kgeo_fineness=0.5, + nb_worker=1, init_dict=None, init_str=None, ): @@ -87,6 +88,8 @@ def __init__( Nt_tot = init_dict["Nt_tot"] if "Kgeo_fineness" in list(init_dict.keys()): Kgeo_fineness = init_dict["Kgeo_fineness"] + if "nb_worker" in list(init_dict.keys()): + nb_worker = init_dict["nb_worker"] # Set the properties (value check and convertion are done in setter) self.FEMM_dict = FEMM_dict self.type_calc_leakage = type_calc_leakage @@ -94,6 +97,7 @@ def __init__( self.is_periodicity_a = is_periodicity_a self.Nt_tot = Nt_tot self.Kgeo_fineness = Kgeo_fineness + self.nb_worker = nb_worker # Call IndMag init super(IndMagFEMM, self).__init__() # The class is frozen (in IndMag init), for now it's impossible to @@ -111,6 +115,7 @@ def __str__(self): IndMagFEMM_str += "is_periodicity_a = " + str(self.is_periodicity_a) + linesep IndMagFEMM_str += "Nt_tot = " + str(self.Nt_tot) + linesep IndMagFEMM_str += "Kgeo_fineness = " + str(self.Kgeo_fineness) + linesep + IndMagFEMM_str += "nb_worker = " + str(self.nb_worker) + linesep return IndMagFEMM_str def __eq__(self, other): @@ -134,6 +139,8 @@ def __eq__(self, other): return False if other.Kgeo_fineness != self.Kgeo_fineness: return False + if other.nb_worker != self.nb_worker: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -159,6 +166,8 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".Nt_tot") if other._Kgeo_fineness != self._Kgeo_fineness: diff_list.append(name + ".Kgeo_fineness") + if other._nb_worker != self._nb_worker: + diff_list.append(name + ".nb_worker") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -178,6 +187,7 @@ def __sizeof__(self): S += getsizeof(self.is_periodicity_a) S += getsizeof(self.Nt_tot) S += getsizeof(self.Kgeo_fineness) + S += getsizeof(self.nb_worker) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -205,6 +215,7 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): IndMagFEMM_dict["is_periodicity_a"] = self.is_periodicity_a IndMagFEMM_dict["Nt_tot"] = self.Nt_tot IndMagFEMM_dict["Kgeo_fineness"] = self.Kgeo_fineness + IndMagFEMM_dict["nb_worker"] = self.nb_worker # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name IndMagFEMM_dict["__class__"] = "IndMagFEMM" @@ -219,6 +230,7 @@ def _set_None(self): self.is_periodicity_a = None self.Nt_tot = None self.Kgeo_fineness = None + self.nb_worker = None # Set to None the properties inherited from IndMag super(IndMagFEMM, self)._set_None() @@ -333,3 +345,21 @@ def _set_Kgeo_fineness(self, value): :Type: float """, ) + + def _get_nb_worker(self): + """getter of nb_worker""" + return self._nb_worker + + def _set_nb_worker(self, value): + """setter of nb_worker""" + check_var("nb_worker", value, "int") + self._nb_worker = value + + nb_worker = property( + fget=_get_nb_worker, + fset=_set_nb_worker, + doc=u"""To run FEMM in parallel (the parallelization is on the time loop) + + :Type: int + """, + ) diff --git a/pyleecan/Classes/LUTdq.py b/pyleecan/Classes/LUTdq.py index 8e71f8295..e0a245700 100644 --- a/pyleecan/Classes/LUTdq.py +++ b/pyleecan/Classes/LUTdq.py @@ -22,30 +22,20 @@ except ImportError as error: get_param_dict = error -try: - from ..Methods.Simulation.LUTdq.get_Lq import get_Lq -except ImportError as error: - get_Lq = error - try: from ..Methods.Simulation.LUTdq.get_bemf import get_bemf except ImportError as error: get_bemf = error try: - from ..Methods.Simulation.LUTdq.get_Ld import get_Ld + from ..Methods.Simulation.LUTdq.get_Ldqh import get_Ldqh except ImportError as error: - get_Ld = error + get_Ldqh = error try: - from ..Methods.Simulation.LUTdq.get_Lmd import get_Lmd + from ..Methods.Simulation.LUTdq.get_Lmdqh import get_Lmdqh except ImportError as error: - get_Lmd = error - -try: - from ..Methods.Simulation.LUTdq.get_Lmq import get_Lmq -except ImportError as error: - get_Lmq = error + get_Lmdqh = error try: from ..Methods.Simulation.LUTdq.import_from_data import import_from_data @@ -111,15 +101,6 @@ class LUTdq(LUT): ) else: get_param_dict = get_param_dict - # cf Methods.Simulation.LUTdq.get_Lq - if isinstance(get_Lq, ImportError): - get_Lq = property( - fget=lambda x: raise_( - ImportError("Can't use LUTdq method get_Lq: " + str(get_Lq)) - ) - ) - else: - get_Lq = get_Lq # cf Methods.Simulation.LUTdq.get_bemf if isinstance(get_bemf, ImportError): get_bemf = property( @@ -129,33 +110,24 @@ class LUTdq(LUT): ) else: get_bemf = get_bemf - # cf Methods.Simulation.LUTdq.get_Ld - if isinstance(get_Ld, ImportError): - get_Ld = property( - fget=lambda x: raise_( - ImportError("Can't use LUTdq method get_Ld: " + str(get_Ld)) - ) - ) - else: - get_Ld = get_Ld - # cf Methods.Simulation.LUTdq.get_Lmd - if isinstance(get_Lmd, ImportError): - get_Lmd = property( + # cf Methods.Simulation.LUTdq.get_Ldqh + if isinstance(get_Ldqh, ImportError): + get_Ldqh = property( fget=lambda x: raise_( - ImportError("Can't use LUTdq method get_Lmd: " + str(get_Lmd)) + ImportError("Can't use LUTdq method get_Ldqh: " + str(get_Ldqh)) ) ) else: - get_Lmd = get_Lmd - # cf Methods.Simulation.LUTdq.get_Lmq - if isinstance(get_Lmq, ImportError): - get_Lmq = property( + get_Ldqh = get_Ldqh + # cf Methods.Simulation.LUTdq.get_Lmdqh + if isinstance(get_Lmdqh, ImportError): + get_Lmdqh = property( fget=lambda x: raise_( - ImportError("Can't use LUTdq method get_Lmq: " + str(get_Lmq)) + ImportError("Can't use LUTdq method get_Lmdqh: " + str(get_Lmdqh)) ) ) else: - get_Lmq = get_Lmq + get_Lmdqh = get_Lmdqh # cf Methods.Simulation.LUTdq.import_from_data if isinstance(import_from_data, ImportError): import_from_data = property( diff --git a/pyleecan/Functions/Electrical/comp_fluxlinkage.py b/pyleecan/Functions/Electrical/comp_fluxlinkage.py deleted file mode 100644 index 7527f2d28..000000000 --- a/pyleecan/Functions/Electrical/comp_fluxlinkage.py +++ /dev/null @@ -1,118 +0,0 @@ -import os -from os.path import join - -from ...Functions.FEMM.draw_FEMM import draw_FEMM -from ...Functions.Electrical.coordinate_transformation import n2dqh -from ...Classes._FEMMHandler import _FEMMHandler -from ...Classes.OutMagFEMM import OutMagFEMM -from numpy import linspace, pi, split -from SciDataTool.Classes.Data1D import Data1D - - -def comp_fluxlinkage(obj, output): - """Compute the flux linkage using FEMM and electrical output reference currents - - Parameters - ---------- - obj : FluxLinkFEMM or IndMagFEMM - a FluxLinkFEMM object or an IndMagFEMM object - output : Output - an Output object - - Return - ------ - fluxdq : ndarray - the calculated fluxlinkage - """ - # get some machine and simulation parameters - L1 = output.simu.machine.stator.L1 - qs = output.simu.machine.stator.winding.qs - zp = output.simu.machine.stator.get_pole_pair_number() - Nt_tot = obj.Nt_tot - rot_dir = output.get_rot_dir() - - # Get save path - str_EEC = "EEC" - - path_res = output.get_path_result() - - save_dir = join(path_res, "Femm") - if not os.path.exists(save_dir): - os.makedirs(save_dir) - - if output.simu.machine.name not in [None, ""]: - file_name = output.simu.machine.name + "_" + str_EEC + ".fem" - elif output.simu.name not in [None, ""]: - file_name = output.simu.name + "_" + str_EEC + ".fem" - else: # Default name - file_name = "FEMM_" + str_EEC + ".fem" - - path_save = join(save_dir, file_name) - - # Set the symmetry factor according to the machine - if obj.is_periodicity_a: - ( - sym, - is_antiper_a, - _, - _, - ) = obj.parent.parent.parent.parent.get_machine_periodicity() - if is_antiper_a: - sym = sym * 2 - else: - sym = 1 - is_antiper_a = False - - # store orignal elec and make a copy to do temp. modifications - elec = output.elec - output.elec = elec.copy() - - # Set rotor angle for the FEMM simulation - angle_offset_initial = output.get_angle_offset_initial() - angle_rotor = ( - linspace(0, -1 * rot_dir * 2 * pi / sym, Nt_tot, endpoint=False) - + angle_offset_initial - ) - - # modify some quantities - if output.elec.axes_dict is None: - output.elec.axes_dict = dict() - output.elec.axes_dict["time"] = Data1D( - name="time", - unit="s", - values=(angle_rotor - angle_rotor[0]) / (2 * pi * output.elec.OP.get_N0() / 60), - ) - output.elec.Is = None # to compute Is from Id_ref and Iq_ref (that are mean val.) - output.elec.Is = output.elec.get_Is() # TODO get_Is disregards initial rotor angle - - # Open FEMM - femm = _FEMMHandler() - if output.elec.internal is None: - output.elec.internal = OutMagFEMM() - output.elec.internal.handler_list.append(femm) - - # Setup the FEMM simulation - # Geometry building and assigning property in FEMM - FEMM_dict = draw_FEMM( - femm=femm, - output=output, - is_mmfr=1, - is_mmfs=1, - sym=sym, - is_antiper=is_antiper_a, - type_calc_leakage=obj.type_calc_leakage, - kgeo_fineness=obj.Kgeo_fineness, # TODO fix inconsistent lower/upper case - path_save=path_save, - ) - - # Solve for all time step and store all the results in output - Phi_wind = L1 * obj.solve_FEMM(femm, output, sym, FEMM_dict) - - # Close FEMM after simulation - femm.closefemm() - output.elec.internal.handler_list.remove(femm) - - # restore the original elec - output.elec = elec - - return fluxdq diff --git a/pyleecan/Functions/Electrical/solve_FEMM.py b/pyleecan/Functions/Electrical/solve_FEMM.py deleted file mode 100644 index b036424d9..000000000 --- a/pyleecan/Functions/Electrical/solve_FEMM.py +++ /dev/null @@ -1,79 +0,0 @@ -from numpy import zeros -from os.path import splitext -from ...Functions.FEMM.update_FEMM_simulation import update_FEMM_simulation -from ...Functions.FEMM.comp_FEMM_Phi_wind import comp_FEMM_Phi_wind - - -def solve_FEMM(obj, femm, output, sym, FEMM_dict): - - L1 = output.simu.machine.stator.comp_length() - L2 = output.simu.machine.rotor.comp_length() - Nt_tot = obj.Nt_tot # Number of time step - is_internal_rotor = output.simu.machine.rotor.is_internal - - if ( - hasattr(output.simu.machine.stator, "winding") - and output.simu.machine.stator.winding is not None - ): - qs = output.simu.machine.stator.winding.qs # Winding phase number - Npcp = output.simu.machine.stator.winding.Npcp - Phi_wind_stator = zeros((Nt_tot, qs)) - else: - Phi_wind_stator = None - - # Interpolate current on electric model time axis - # Get stator current from elec out - Is = output.elec.comp_I_mag( - time=output.elec.axes_dict["time"].values, is_stator=True - ) - - # Get rotor current from elec out - Ir = output.elec.comp_I_mag( - time=output.elec.axes_dict["time"].values, is_stator=False - ) - - # Get rotor angular position - angle_rotor = output.get_angle_rotor()[0:Nt_tot] - - # Create the mesh - femm.mi_createmesh() - - # Compute the data for each time step - for ii in range(Nt_tot): - # Update rotor position and currents - update_FEMM_simulation( - femm, - FEMM_dict["circuits"], - is_internal_rotor, - obj.is_sliding_band, - angle_rotor, - Is, - Ir, - ii, - ) - # try "previous solution" for speed up of FEMM calculation - if obj.is_sliding_band: - try: - base = FEMM_dict["path_save"] - ans_file = splitext(base)[0] + ".ans" - femm.mi_setprevious(ans_file, 0) - except Exception: - pass - - # Run the computation - femm.mi_analyze() - femm.mi_loadsolution() - - if Phi_wind_stator is not None: - # Phi_wind computation - Phi_wind_stator[ii, :] = comp_FEMM_Phi_wind( - femm, - qs, - Npcp, - is_stator=True, - L1=L1, - L2=L2, - sym=sym, - ) - - return Phi_wind_stator diff --git a/pyleecan/Generator/ClassesRef/Simulation/EEC_PMSM.csv b/pyleecan/Generator/ClassesRef/Simulation/EEC_PMSM.csv index 40fb7d696..73c73705d 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/EEC_PMSM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/EEC_PMSM.csv @@ -4,3 +4,4 @@ fluxlink,-,Flux Linkage,,FluxLink,None,,,,,,solve_EEC,,,, parameters,-,"Parameters of the EEC: computed if empty, or enforced",,dict,{},,,,,,gen_drive,,,, freq0,Hz,Frequency,,float,None,,,,,,comp_joule_losses,,,, drive,-,Drive,,Drive,None,,,,,,comp_BEMF_harmonics,,,, +,,,,,,,,,,,comp_torque_sync_rel,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/FluxLinkFEMM.csv b/pyleecan/Generator/ClassesRef/Simulation/FluxLinkFEMM.csv index 33c47bcdf..7df3e7297 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/FluxLinkFEMM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/FluxLinkFEMM.csv @@ -5,3 +5,4 @@ is_sliding_band,,0 to desactivate the sliding band,0,bool,1,,,,,,,,,, is_periodicity_a,,True to take into account the spatial periodicity of the machine,0,bool,0,,,,,,,,,, Nt_tot,-,Number of time steps for the FEMM simulation,0,int,5,,,,,,,,,, Kgeo_fineness,,"global coefficient to adjust geometry fineness in FEMM (0.5 : default , > 1 : finner , < 1 : less fine)",0,float,0.5,,,,,,,,,, +nb_worker,,To run FEMM in parallel (the parallelization is on the time loop),,int,1,,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/IndMagFEMM.csv b/pyleecan/Generator/ClassesRef/Simulation/IndMagFEMM.csv index 9007ad768..d739b037d 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/IndMagFEMM.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/IndMagFEMM.csv @@ -5,3 +5,4 @@ is_sliding_band,,0 to desactivate the sliding band,0,bool,1,,,,,,,,,, is_periodicity_a,,True to take into account the spatial periodicity of the machine,0,bool,0,,,,,,,,,, Nt_tot,-,Number of time steps for the FEMM simulation,0,int,5,,,,,,,,,, Kgeo_fineness,,"global coefficient to adjust geometry fineness in FEMM (0.5 : default , > 1 : finner , < 1 : less fine)",0,float,0.5,,,,,,,,,, +nb_worker,,To run FEMM in parallel (the parallelization is on the time loop),,int,1,,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/LUTdq.csv b/pyleecan/Generator/ClassesRef/Simulation/LUTdq.csv index c4cbbedca..e56557594 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/LUTdq.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/LUTdq.csv @@ -1,11 +1,9 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille Phi_dqh_mean,Wbrms,RMS stator winding flux table in dqh frame (including magnets and currents given by I_dqh),"(N_dq, 3)",ndarray,None,,,,Simulation,LUT,get_param_dict,VERSION,1,Look Up Table class for dq OP matrix, -Tmag_ref,degC,Magnet average temperature at which Phi_dqh is given,,float,20,,,,,,get_Lq,,,, -Phi_dqh_mag,Wbrms,RMS stator winding flux linkage spectrum in dqh frame including harmonics (only magnets),"(Nharm, 3)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_bemf,,,, -Phi_wind,Wb,Stator winding flux function of time and phases,"(Nt_tot, qs)",[SciDataTool.Classes.DataND.DataND],None,,,,,,get_Ld,,,, -Phi_dqh_interp,,Interpolant function of Phi_dqh,,scipy.interpolate.interpolate.RegularGridInterpolator,None,,,,,,get_Lmd,,,, -,,,,,,,,,,,get_Lmq,,,, -,,,,,,,,,,,import_from_data,,,, +Tmag_ref,degC,Magnet average temperature at which Phi_dqh is given,,float,20,,,,,,get_bemf,,,, +Phi_dqh_mag,Wbrms,RMS stator winding flux linkage spectrum in dqh frame including harmonics (only magnets),"(Nharm, 3)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Ldqh,,,, +Phi_wind,Wb,Stator winding flux function of time and phases,"(Nt_tot, qs)",[SciDataTool.Classes.DataND.DataND],None,,,,,,get_Lmdqh,,,, +Phi_dqh_interp,,Interpolant function of Phi_dqh,,scipy.interpolate.interpolate.RegularGridInterpolator,None,,,,,,import_from_data,,,, ,,,,,,,,,,,get_Phidqh_mean,,,, ,,,,,,,,,,,get_Phidqh_mag,,,, ,,,,,,,,,,,get_Phidqh_mag_mean,,,, diff --git a/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py b/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py index f75942f3b..ba12174e4 100644 --- a/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py +++ b/pyleecan/Methods/Machine/Conductor/comp_skin_effect.py @@ -1,20 +1,21 @@ -# -*- coding: utf-8 -*- -# from ....Classes.InputElec import InputElec -# from ....Classes.Slot import Slot -# from ....Classes.Conductor import Conductor -# from ....Classes.CondType11 import CondType11 -# from ....Classes.CondType12 import CondType12 +from numpy import pi, sqrt, sin -from numpy import ones, pi, sqrt, sin - -def comp_skin_effect(self, freq, T=20): +def comp_skin_effect(self, freq, T=20, type_skin_effect=1): """Compute the skin effect factor for the conductor Parameters ---------- self : Conductor an Conductor object + freq: float + electrical frequency [Hz] + T: float + Conductor temperature [degC] + type_skin_effect: int + Model type for skin effect calculation: + - 1: analytical model (default) + Returns ---------- Xkr_skinS : float @@ -29,8 +30,7 @@ def comp_skin_effect(self, freq, T=20): sigmar = 1 / rho mu0 = 4 * pi * 1e-7 ws = 2 * pi * freq - Slot = self.parent.parent.parent.stator.slot - type_skin_effect = self.parent.parent.parent.parent.elec.type_skin_effect + Slot = self.parent.parent.slot # nsw = len(ws) # initialization diff --git a/pyleecan/Methods/Post/PostLUT/run.py b/pyleecan/Methods/Post/PostLUT/run.py index 7fa569dd5..34ab1118d 100644 --- a/pyleecan/Methods/Post/PostLUT/run.py +++ b/pyleecan/Methods/Post/PostLUT/run.py @@ -40,9 +40,10 @@ def run(self, out): LUT.OP_matrix[:, ii] = array(out.simu.var_simu.OP_matrix[:, ii]) # Fill LUT variables - dk_list = list(out.keys()) - if "Phi_{wind}" in dk_list: + if "Phi_{wind}" in out.keys(): LUT.Phi_wind = out["Phi_{wind}"].result + elif out.output_list is not None: + LUT.Phi_wind = [o.mag.Phi_wind_stator for o in out.output_list] # Find Id=Iq=0 OP_list = LUT.OP_matrix[:, 1:3].tolist() @@ -50,15 +51,6 @@ def run(self, out): ii = OP_list.index([0, 0]) else: raise Exception("Operating Point Id=Iq=0 is required to compute LUT") - # # Compute back electromotive force - # out.output_list[ii].mag.comp_emf() - # BEMF = out.output_list[ii].mag.emf - # BEMF.name = "Stator Winding Back Electromotive Force" - # BEMF.symbol = "BEMF" - # LUT.bemf = BEMF - - # # Set if the interpolation must be made along a curve - # LUT.set_is_interp_along_curve() if self.is_save_LUT: # Save LUT object diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py index 3a729a037..af5168db9 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/comp_parameters.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- - - -from enum import unique +from numpy import isscalar def comp_parameters(self, machine, OP, Tsta=None, Trot=None): @@ -27,60 +24,72 @@ def comp_parameters(self, machine, OP, Tsta=None, Trot=None): PAR = self.parameters Cond = machine.stator.winding.conductor + I_dict = OP.get_Id_Iq() Id_ref, Iq_ref = I_dict["Id"], I_dict["Iq"] + U_dict = OP.get_Ud_Uq() Ud_ref, Uq_ref = U_dict["Ud"], U_dict["Uq"] - felec = OP.get_felec() + + # Update frequency with current OP and store frequency in EEC + self.freq0 = OP.get_felec() + felec = self.freq0 # compute skin_effect - Xkr_skinS, Xke_skinS = Cond.comp_skin_effect(T=20, freq=felec) + Xkr_skinS, Xke_skinS = Cond.comp_skin_effect(T=Tsta, freq=felec) - # Parameters to compute only once + # Stator resistance if "R20" not in PAR: R20 = machine.stator.comp_resistance_wind() PAR["R20"] = R20 * Xkr_skinS - if "phi" not in PAR: + + # Stator flux linkage + if "phi" not in PAR and Ud_ref is not None and Uq_ref is not None: PAR["phi"] = self.fluxlink.comp_fluxlinkage(machine) # Parameters which may vary for each simulation is_comp_ind = False # check for complete parameter set # (there may be some redundancy here but it seems simplier to implement) - if not all(k in PAR for k in ("Phid", "Phiq", "Ld", "Lq")): + if ( + Ud_ref is not None + and Uq_ref is not None + and not all(k in PAR for k in ("Phid", "Phiq", "Ld", "Lq")) + ): is_comp_ind = True # check for d- and q-current (change) - if "Id" not in PAR or PAR["Id"] != Id_ref: + if "Id" not in PAR or (isscalar(PAR["Id"]) and PAR["Id"] != Id_ref): PAR["Id"] = Id_ref is_comp_ind = True - if "Iq" not in PAR or PAR["Iq"] != Iq_ref: + if "Iq" not in PAR or (isscalar(PAR["Iq"]) and PAR["Iq"] != Iq_ref): PAR["Iq"] = Iq_ref is_comp_ind = True # check for d- and q-voltage (change) - if "Ud" not in PAR or PAR["Ud"] != Ud_ref: + if Ud_ref is not None and ( + "Ud" not in PAR or (isscalar(PAR["Ud"]) and PAR["Ud"] != Ud_ref) + ): PAR["Ud"] = Ud_ref - if "Uq" not in PAR or PAR["Uq"] != Uq_ref: + if Uq_ref is not None and ( + "Uq" not in PAR or (isscalar(PAR["Uq"]) and PAR["Uq"] != Uq_ref) + ): PAR["Uq"] = Uq_ref # compute inductance if necessary if is_comp_ind: - (phid, phiq) = self.indmag.comp_inductance( - machine=machine, Id_ref=Id_ref, Iq_ref=Iq_ref - ) - (phid, phiq) = tuple([z * Xke_skinS for z in (phid, phiq)]) - if PAR["Id"] != 0: - PAR["Ld"] = (phid - PAR["phi"]) / PAR["Id"] + (phid, phiq) = self.indmag.comp_inductance(machine=machine, OP_ref=OP) + PAR["Phid"] = phid * Xke_skinS + PAR["Phiq"] = phiq * Xke_skinS + + if PAR["Id"] != 0 and "phi" in PAR and Ud_ref is not None: + PAR["Ld"] = (PAR["Phid"] - PAR["phi"]) / PAR["Id"] else: PAR["Ld"] = None # to have the parameters complete though - if PAR["Iq"] != 0: - PAR["Lq"] = phiq / PAR["Iq"] + if PAR["Iq"] != 0 and Uq_ref is not None: + PAR["Lq"] = PAR["Phiq"] / PAR["Iq"] else: PAR["Lq"] = None # to have the parameters complete though - - PAR["Phid"] = phid - PAR["Phiq"] = phiq diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/comp_torque_sync_rel.py b/pyleecan/Methods/Simulation/EEC_PMSM/comp_torque_sync_rel.py new file mode 100644 index 000000000..8d9ff1c38 --- /dev/null +++ b/pyleecan/Methods/Simulation/EEC_PMSM/comp_torque_sync_rel.py @@ -0,0 +1,33 @@ +def comp_torque_sync_rel(self, qs, p, machine=None): + """Calculate synchronous and reluctant torque + + Parameters + ---------- + self : EEC_PMSM + an EEC_PMSM object + qs: int + Number of stator winding phase + p: int + Number of pole pairs + + Returns + ------ + Tem_sync : float or ndarray + Synchronous torque [N.m] + Tem_rel : float or ndarray + Reluctant torque [N.m] + """ + + Id = self.parameters["Id"] + Iq = self.parameters["Iq"] + Phid = self.parameters["Phid"] + Phiq = self.parameters["Phiq"] + if "phi" not in self.parameters: + self.parameters["phi"] = self.fluxlink.comp_fluxlinkage(machine) + phi_mag = self.parameters["phi"] + + Tem_sync = qs * p * phi_mag * Iq + + Tem_rel = qs * p * ((Phid - phi_mag) * Iq - Phiq * Id) + + return Tem_sync, Tem_rel diff --git a/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py b/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py index 99f2767cf..762e4650b 100644 --- a/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py +++ b/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py @@ -1,10 +1,10 @@ from os.path import join -from numpy import mean, split -from pyleecan.Classes.InputCurrent import InputCurrent -from pyleecan.Classes.MagFEMM import MagFEMM -from pyleecan.Classes.Simu1 import Simu1 -from pyleecan.Classes.Simulation import Simulation +from ....Classes.InputCurrent import InputCurrent +from ....Classes.OPdq import OPdq +from ....Classes.MagFEMM import MagFEMM +from ....Classes.Simu1 import Simu1 +from ....Classes.Simulation import Simulation from ....Functions.Electrical.coordinate_transformation import n2dqh_DataTime @@ -15,51 +15,52 @@ def comp_fluxlinkage(self, machine): ---------- self : FluxLinkFEMM a FluxLinkFEMM object - output : Output - an Output object - """ + machine : Machine + a Machine object - self.get_logger().info("INFO: Compute flux linkage with FEMM") + Returns + ---------- + Phi_d_mean: float + Flux linkage along d-axis + """ - # Remove skew - machine_fl = machine.copy() - machine_fl.rotor.skew = None - machine_fl.stator.skew = None + self.get_logger().info("Compute flux linkage with FEMM") # Get simulation name and result path if isinstance(machine.parent, Simulation) and machine.parent.name not in [None, ""]: - simu_name = machine.parent.name + "_FluxLinkage" + simu_name = machine.parent.name + "_FluxLinkFEMM" path_result = ( - join(machine.parent.path_result, "FluxLinkage") + join(machine.parent.path_result, "FluxLinkFEMM") if machine.parent.path_result not in [None, ""] else None ) elif machine.name not in [None, ""]: - simu_name = machine.name + "_FluxLinkage" + simu_name = machine.name + "_FluxLinkFEMM" path_result = None else: - simu_name = "FluxLinkage" + simu_name = "FluxLinkFEMM" path_result = None # Define simulation - simu_fl = Simu1( - elec=None, name=simu_name, path_result=path_result, machine=machine_fl - ) + simu_fl = Simu1(elec=None, name=simu_name, path_result=path_result, machine=machine) simu_fl.input = InputCurrent( - N0=2000, Id_ref=0, Iq_ref=0, Nt_tot=self.Nt_tot, Na_tot=2048 + OP=OPdq(N0=1000, Id_ref=0, Iq_ref=0), Nt_tot=self.Nt_tot, Na_tot=2048 ) simu_fl.mag = MagFEMM( is_periodicity_t=True, - is_periodicity_a=True, + is_periodicity_a=self.is_periodicity_a, is_sliding_band=self.is_sliding_band, Kgeo_fineness=self.Kgeo_fineness, type_calc_leakage=self.type_calc_leakage, + nb_worker=self.nb_worker, ) # Run Simulation out_fl = simu_fl.run() # Post-Process - Phidqh = n2dqh_DataTime(out_fl.mag.Phi_wind_stator) - Phi_d_mean = float(Phidqh.get_along("time=mean", "phase[0]")["Phi_{wind}"]) + stator_label = machine.stator.get_label() + Phidqh = n2dqh_DataTime(out_fl.mag.Phi_wind[stator_label]) + Phi_d_mean = float(Phidqh.get_along("time=mean", "phase[0]")[Phidqh.symbol]) + return Phi_d_mean diff --git a/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py b/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py index d1f9e1aff..f8be89e19 100644 --- a/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py +++ b/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py @@ -1,66 +1,67 @@ from os.path import join -from numpy import mean, split -from pyleecan.Classes.InputCurrent import InputCurrent -from pyleecan.Classes.MagFEMM import MagFEMM -from pyleecan.Classes.Simulation import Simulation -from pyleecan.Classes.Simu1 import Simu1 +from ....Classes.InputCurrent import InputCurrent +from ....Classes.MagFEMM import MagFEMM +from ....Classes.Simulation import Simulation +from ....Classes.Simu1 import Simu1 from ....Functions.Electrical.coordinate_transformation import n2dqh_DataTime -def comp_inductance(self, machine, Id_ref, Iq_ref): +def comp_inductance(self, machine, OP_ref): """Compute using FEMM the inductance (Current driven only) Parameters ---------- self : IndMagFEMM an IndMagFEMM object - output : Output - an Output object - """ + machine : Machine + a Machine object + OP_ref: OperatingPoint + an OP object - self.get_logger().info("INFO: Compute dq inductances with FEMM") + Returns + ---------- + Phi_d_mean: float + Flux linkage along d-axis + """ - # Remove skew - machine_fl = machine.copy() - machine_fl.rotor.skew = None - machine_fl.stator.skew = None + self.get_logger().info("Compute dq inductances with FEMM") # Get simulation name and result path if isinstance(machine.parent, Simulation) and machine.parent.name not in [None, ""]: - simu_name = machine.parent.name + "_FluxLinkage" + simu_name = machine.parent.name + "_IndMagFEMM" path_result = ( - join(machine.parent.path_result, "FluxLinkage") + join(machine.parent.path_result, "IndMagFEMM") if machine.parent.path_result not in [None, ""] else None ) elif machine.name not in [None, ""]: - simu_name = machine.name + "_FluxLinkage" + simu_name = machine.name + "_IndMagFEMM" path_result = None else: - simu_name = "FluxLinkage" + simu_name = "IndMagFEMM" path_result = None # Define simulation simu_ind = Simu1( - elec=None, name=simu_name, path_result=path_result, machine=machine_fl - ) - simu_ind.input = InputCurrent( - N0=2000, Id_ref=Id_ref, Iq_ref=Iq_ref, Nt_tot=self.Nt_tot, Na_tot=2048 + elec=None, name=simu_name, path_result=path_result, machine=machine ) + simu_ind.input = InputCurrent(OP=OP_ref, Nt_tot=self.Nt_tot, Na_tot=2048) simu_ind.mag = MagFEMM( is_periodicity_t=True, - is_periodicity_a=True, + is_periodicity_a=self.is_periodicity_a, is_sliding_band=self.is_sliding_band, Kgeo_fineness=self.Kgeo_fineness, type_calc_leakage=self.type_calc_leakage, + nb_worker=self.nb_worker, ) # Run Simulation out_ind = simu_ind.run() # Post-Process - Phidqh = n2dqh_DataTime(out_ind.mag.Phi_wind_stator) - Phi_d_mean = float(Phidqh.get_along("time=mean", "phase[0]")["Phi_{wind}"]) - Phi_q_mean = float(Phidqh.get_along("time=mean", "phase[1]")["Phi_{wind}"]) - return (Phi_d_mean, Phi_q_mean) + stator_label = machine.stator.get_label() + Phidqh = n2dqh_DataTime(out_ind.mag.Phi_wind[stator_label]) + Phi_dqh_mean = Phidqh.get_along("time=mean", "phase")[Phidqh.symbol] + + return (Phi_dqh_mean[0], Phi_dqh_mean[1]) diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Ld.py b/pyleecan/Methods/Simulation/LUTdq/get_Ld.py deleted file mode 100644 index 9108bcd22..000000000 --- a/pyleecan/Methods/Simulation/LUTdq/get_Ld.py +++ /dev/null @@ -1,22 +0,0 @@ -def get_Ld(self, Id, Iq, L_endwinding=0): - """Get the total d-axis inductance - Parameters - ---------- - self : LUTdq - a LUTdq object - Id : float - current Id - Iq : float - current Iq - L_endwinding : float - end winding inductance provided by user - - Returns - ---------- - Ld : ndarray - d-axis inductance - """ - - Lmd = self.get_Lmd(Id=Id, Iq=Iq) - - return Lmd + L_endwinding diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Lq.py b/pyleecan/Methods/Simulation/LUTdq/get_Ldqh.py similarity index 52% rename from pyleecan/Methods/Simulation/LUTdq/get_Lq.py rename to pyleecan/Methods/Simulation/LUTdq/get_Ldqh.py index 559996084..6f220c94e 100644 --- a/pyleecan/Methods/Simulation/LUTdq/get_Lq.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_Ldqh.py @@ -1,5 +1,5 @@ -def get_Lq(self, Id, Iq, L_endwinding=0): - """Get the total q-axis inductance +def get_Ldqh(self, Id, Iq, L_endwinding=0, Phi_dqh=None): + """Get the total dqh inductance Parameters ---------- self : LUTdq @@ -13,10 +13,10 @@ def get_Lq(self, Id, Iq, L_endwinding=0): Returns ---------- - Lq : ndarray - q-axis inductance + Ldqh : ndarray + ddqh inductance """ - Lmq = self.get_Lmq(Id=Id, Iq=Iq) + Lmdqh = self.get_Lmdqh(Id=Id, Iq=Iq, Phi_dqh=Phi_dqh) - return Lmq + L_endwinding + return Lmdqh + L_endwinding diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Lmd.py b/pyleecan/Methods/Simulation/LUTdq/get_Lmd.py deleted file mode 100644 index fc60e7a5d..000000000 --- a/pyleecan/Methods/Simulation/LUTdq/get_Lmd.py +++ /dev/null @@ -1,23 +0,0 @@ -def get_Lmd(self, Id, Iq): - """Get the magnets d-axis inductance - Parameters - ---------- - self : LUTdq - a LUTdq object - Id : float - current Id - Iq : float - current Iq - - Returns - ---------- - Lmd : ndarray - magnets d-axis inductance - """ - - Phi_d = self.interp_Phi_dqh(Id=Id, Iq=Iq)[0] - Phi_mag = self.get_Phidqh_mag_mean()[0] - - Lmd = (Phi_d - Phi_mag) / Id - - return Lmd diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Lmdqh.py b/pyleecan/Methods/Simulation/LUTdq/get_Lmdqh.py new file mode 100644 index 000000000..2246958ba --- /dev/null +++ b/pyleecan/Methods/Simulation/LUTdq/get_Lmdqh.py @@ -0,0 +1,42 @@ +import numpy as np + + +def get_Lmdqh(self, Id, Iq, Phi_dqh=None): + """Get the magnetizing dqh inductance + + Parameters + ---------- + self : LUTdq + a LUTdq object + Id : float + current Id + Iq : float + current Iq + + Returns + ---------- + Lmdqh : ndarray + magnetizing dqh inductance + """ + + if Phi_dqh is None: + # Get dqh flux function of current + Phi_dqh = self.interp_Phi_dqh(Id=Id, Iq=Iq) + + # Get dqh flux linkage (without currents, only due to PM) + Phi_dqh_mag = self.get_Phidqh_mag_mean()[:, None] + + # Init dqh current + if np.isscalar(Id) and np.isscalar(Iq): + n_OP = 1 + else: + n_OP = Id.size + I_dqh = np.zeros((3, n_OP)) + I_dqh[0] = Id + I_dqh[1] = Iq + I_dqh[2] = 1 + + # Divide flux by current to get inductance + Lmdqh = (Phi_dqh - Phi_dqh_mag) / I_dqh + + return Lmdqh diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Lmq.py b/pyleecan/Methods/Simulation/LUTdq/get_Lmq.py deleted file mode 100644 index 9bacbe9ae..000000000 --- a/pyleecan/Methods/Simulation/LUTdq/get_Lmq.py +++ /dev/null @@ -1,23 +0,0 @@ -def get_Lmq(self, Id, Iq): - """Get the magnets q-axis inductance - Parameters - ---------- - self : LUTdq - a LUTdq object - Id : float - current Id - Iq : float - current Iq - - Returns - ---------- - Lmq : ndarray - magnets q-axis inductance - """ - - Phi_q = self.interp_Phi_dqh(Id=Id, Iq=Iq)[1] - Phi_mag = self.get_Phidqh_mag_mean()[1] - - Lmq = (Phi_q - Phi_mag) / Iq - - return Lmq diff --git a/pyleecan/Methods/Simulation/LUTdq/get_param_dict.py b/pyleecan/Methods/Simulation/LUTdq/get_param_dict.py index e01dea143..02b8d1ca8 100644 --- a/pyleecan/Methods/Simulation/LUTdq/get_param_dict.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_param_dict.py @@ -1,7 +1,4 @@ -from numpy import interp - - -def get_param_dict(self, OP): +def get_param_dict(self, OP=None, param_list=None, Id=None, Iq=None): """Get the parameters dict for the ELUT of PMSM Parameters ---------- @@ -9,6 +6,10 @@ def get_param_dict(self, OP): a LUTdq object OP : OP an OP object + Id : float or ndarray + current Id + Iq : float or ndarray + current Iq Returns ---------- @@ -16,11 +17,30 @@ def get_param_dict(self, OP): a Dict object """ + if OP is not None: + Id, Iq = OP.get_Id_Iq()["Id"], OP.get_Id_Iq()["Iq"] + elif Id is None or Iq is None: + raise Exception("Cannot get parameters dict if OP is None and Id or Iq is None") + + if param_list is None: + param_list = ["Idqh", "Ldqh"] + param_dict = dict() - Id, Iq = OP.get_Id_Iq()["Id"], OP.get_Id_Iq()["Iq"] - param_dict["Ld"] = self.get_Ld(Id=Id, Iq=Iq) - param_dict["Lq"] = self.get_Lq(Id=Id, Iq=Iq) - param_dict["Id"] = Id - param_dict["Iq"] = Iq + Phi_dqh = None + + if "Idqh" in param_list: + param_dict["Id"] = Id + param_dict["Iq"] = Iq + + if "Phidqh" in param_list: + Phi_dqh = self.interp_Phi_dqh(Id=Id, Iq=Iq) + param_dict["Phid"] = Phi_dqh[0] + param_dict["Phiq"] = Phi_dqh[1] + param_dict["phi"] = self.get_Phidqh_mag_mean()[0] + + if "Ldqh" in param_list: + Ldqh = self.get_Ldqh(Id=Id, Iq=Iq, Phi_dqh=Phi_dqh)[:, 0] + param_dict["Ld"] = Ldqh[0] + param_dict["Lq"] = Ldqh[1] return param_dict diff --git a/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py b/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py index fb98832c8..c8e7bcd58 100644 --- a/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py +++ b/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py @@ -8,9 +8,9 @@ def interp_Phi_dqh(self, Id, Iq): ---------- self : LUTdq a LUTdq object - Id : float + Id : float or ndarray current Id - Iq : float + Iq : float or ndarray current Iq Returns @@ -21,14 +21,46 @@ def interp_Phi_dqh(self, Id, Iq): # Compute interpolant at first call if self.Phi_dqh_interp is None: - # Get unique Id, Iq, assuming regular grid - XId, XIq = np.unique(self.OP_matrix[:, 1]), np.unique(self.OP_matrix[:, 2]) - # Sort in ascending order - self.Phi_dqh_interp = scp_int.RegularGridInterpolator( - (XId, XIq), - self.get_Phidqh_mean()[:, 0:2].reshape((len(XId), len(XIq), 2)), - method="linear", - ) - - # Perform 2D interpolation - return self.Phi_dqh_interp((Id, Iq)) + # Calculate average value of dqh flux linkage + Phi_dqh_mean = self.get_Phidqh_mean() + + # Get unique Id, Iq sorted in ascending order + XId, jd = np.unique(self.OP_matrix[:, 1], return_inverse=True) + XIq, jq = np.unique(self.OP_matrix[:, 2], return_inverse=True) + nd, nq = XId.size, XIq.size + + # Perform 2D interpolation + if nd * nq == self.OP_matrix.shape[0]: + # sort flux linkage matrix and reshape to (nd, nq, 2) + Phi_dqh_mean_reg = np.zeros((nd, nq, 2)) + for ii, (m, n) in enumerate(zip(jd, jq)): + Phi_dqh_mean_reg[m, n, :] = Phi_dqh_mean[ii, 0:2] + # regular grid interpolation + self.Phi_dqh_interp = scp_int.RegularGridInterpolator( + (XId, XIq), + Phi_dqh_mean_reg, + method="linear", + ) + else: + # scattered interpolation + # not working since LinearNDInterpolator is not of same class as RegularGridInterpolator + self.Phi_dqh_interp = scp_int.LinearNDInterpolator( + (self.OP_matrix[:, 1], self.OP_matrix[:, 2]), Phi_dqh_mean[:, 0:2] + ) + + # Init dqh flux linkage + if np.isscalar(Id) and np.isscalar(Iq): + n_OP = 1 + else: + n_OP = Id.size + Phi_dqh = np.zeros((3, n_OP)) + + # Interpolate Phid and Phiq, Phih is enforced to 0 + Phi_dqh_interp = self.Phi_dqh_interp((Id, Iq)) + if Phi_dqh_interp.ndim == 1: + Phi_dqh_interp = Phi_dqh_interp[:, None] + else: + Phi_dqh_interp = Phi_dqh_interp.T + Phi_dqh[0:2, :] = Phi_dqh_interp + + return Phi_dqh From 27e331b9aa80b1181da62937b159e014378e8b45 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Mon, 25 Oct 2021 01:31:59 +0200 Subject: [PATCH 141/167] [BC] Add - in current phase shift + rotor now rotates with same sign as fundamental field --- Tests/Functions/test_coordinate_transformation.py | 2 +- .../Functions/Electrical/coordinate_transformation.py | 2 +- pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py | 4 +--- pyleecan/Methods/Output/Output/getter/get_rot_dir.py | 8 ++------ 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/Tests/Functions/test_coordinate_transformation.py b/Tests/Functions/test_coordinate_transformation.py index 18cab3736..288b164b4 100644 --- a/Tests/Functions/test_coordinate_transformation.py +++ b/Tests/Functions/test_coordinate_transformation.py @@ -60,7 +60,7 @@ def test_coordinate_transformation(param_dict): for phase in phase_list: In = zeros((Nt, qs)) for ii in range(qs): - In[:, ii] = cos(angle_elec + phase + 2 * ii * pi / qs) + In[:, ii] = cos(angle_elec + phase - 2 * ii * pi / qs) Idqh_rms = n2dqh(In, angle_elec, is_dqh_rms=True) Idqh_amp = n2dqh(In, angle_elec, is_dqh_rms=False) diff --git a/pyleecan/Functions/Electrical/coordinate_transformation.py b/pyleecan/Functions/Electrical/coordinate_transformation.py index ee5e96e13..dadd3d16b 100644 --- a/pyleecan/Functions/Electrical/coordinate_transformation.py +++ b/pyleecan/Functions/Electrical/coordinate_transformation.py @@ -333,7 +333,7 @@ def comp_Clarke_transform(n, is_inv=False): """ # Phasor depending on fundamental field rotation direction - phasor = np.linspace(0, 2 * np.pi * (n - 1) / n, n) + phasor = np.linspace(0, -2 * np.pi * (n - 1) / n, n) # Clarke transformation matrix if is_inv: diff --git a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py index 867ef0aa4..8ac593394 100644 --- a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py +++ b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py @@ -25,9 +25,7 @@ def comp_angle_rotor(self, Time): Nr = self.elec.get_Nr(Time=Time) # Get rotor rotating direction - # rotor rotating is the opposite of rot_dir which is fundamental field rotation direction - # so that rotor moves in positive angles - rot_dir = -self.get_rot_dir() + rot_dir = self.get_rot_dir() # Compute rotor initial angle (for synchronous machines, to align rotor d-axis and stator alpha-axis) A0 = self.get_angle_offset_initial() diff --git a/pyleecan/Methods/Output/Output/getter/get_rot_dir.py b/pyleecan/Methods/Output/Output/getter/get_rot_dir.py index 9586359eb..600473e70 100644 --- a/pyleecan/Methods/Output/Output/getter/get_rot_dir.py +++ b/pyleecan/Methods/Output/Output/getter/get_rot_dir.py @@ -1,9 +1,5 @@ -# -*- coding: utf-8 -*- - - def get_rot_dir(self): - """Return the rotation direction of the magnetic field fundamental - WARNING: rot_dir = -1 to have positive rotor rotating direction, i.e. rotor position moves towards positive angle + """Return the rotation direction of rotor and magnetic field fundamental Parameters ---------- @@ -13,7 +9,7 @@ def get_rot_dir(self): Returns ------- rot_dir: int - Rotation direction of magnetic field fundamental + Rotation direction of rotor and magnetic field fundamental """ From 85ce6756e70cd9d6ce5075c77d6af6ee8911efd0 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Mon, 25 Oct 2021 01:33:27 +0200 Subject: [PATCH 142/167] [CO] Add plot maps in tests + finish MTPA validation case --- .../Electrical/test_EEC_ELUT_PMSM.py | 147 ++++++++++++------ Tests/Validation/Electrical/test_EEC_PMSM.py | 38 +++-- .../Magnetics/test_FEMM_set_previous.py | 7 +- 3 files changed, 134 insertions(+), 58 deletions(-) diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py index ab3a359e2..2bd1231bc 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py @@ -6,6 +6,7 @@ import pytest from SciDataTool.Functions.Plot.plot_2D import plot_2D +from SciDataTool.Functions.Plot.plot_3D import plot_3D from pyleecan.Classes.ImportGenPWM import ImportGenPWM from pyleecan.Classes.InputVoltage import InputVoltage @@ -30,6 +31,7 @@ @pytest.mark.long_5s +@pytest.mark.long_1m @pytest.mark.MagFEMM @pytest.mark.EEC_PMSM @pytest.mark.IPMSM @@ -38,18 +40,19 @@ def test_ELUT(): """Fixture to calculate ELUT and use it in all tests""" - _, ELUT = test_EEC_ELUT_PMSM() + _, ELUT = test_EEC_ELUT_PMSM_calc() return ELUT @pytest.mark.long_5s +@pytest.mark.long_1m @pytest.mark.MagFEMM @pytest.mark.EEC_PMSM @pytest.mark.IPMSM @pytest.mark.periodicity @pytest.mark.skip(reason="called by fixture and for direct call to test") -def test_EEC_ELUT_PMSM(n_Id=3, n_Iq=3): +def test_EEC_ELUT_PMSM_calc(n_Id=5, n_Iq=5): """Validation of the PMSM Electrical Equivalent Circuit with the Prius machine""" Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) @@ -60,14 +63,14 @@ def test_EEC_ELUT_PMSM(n_Id=3, n_Iq=3): # Definition of the input simu.input = InputCurrent( - Nt_tot=8 * 10, + Nt_tot=8 * 12, Na_tot=8 * 200, OP=OPdq(N0=1000, Id_ref=0, Iq_ref=0), ) # Build OP_matrix with a meshgrid of Id/Iq - Id_min, Id_max = 0, 100 - Iq_min, Iq_max = 0, 200 + Id_min, Id_max = -100, 100 + Iq_min, Iq_max = -200, 200 Id, Iq = np.meshgrid( np.linspace(Id_min, Id_max, n_Id), np.linspace(Iq_min, Iq_max, n_Iq) ) @@ -96,28 +99,30 @@ def test_EEC_ELUT_PMSM(n_Id=3, n_Iq=3): # Check flux linkage dqh values Phi_dqh_mean = ELUT.get_Phidqh_mean() + OP_list = OP_matrix[:, 1:3].tolist() + ii = OP_list.index([0, 0]) Phi_dqh0 = n2dqh_DataTime( - ELUT.Phi_wind[0], + ELUT.Phi_wind[ii], is_dqh_rms=True, ) Phi_dqh0_mean = Phi_dqh0.get_along("time=mean", "phase")[Phi_dqh0.symbol] - assert_almost_equal(Phi_dqh0_mean, Phi_dqh_mean[0, :], decimal=20) + assert_almost_equal(Phi_dqh0_mean, Phi_dqh_mean[ii, :], decimal=20) assert_almost_equal(Phi_dqh0_mean[0], 0.141, decimal=3) # Plot 3-phase current function of time - ELUT.Phi_wind[0].plot_2D_Data( + ELUT.Phi_wind[ii].plot_2D_Data( "time", "phase[]", save_path=join(save_path, name + "_flux_linkage_abc.png"), is_show_fig=is_show_fig, - **dict_2D + **dict_2D, ) Phi_dqh0.plot_2D_Data( "time", "phase[]", save_path=join(save_path, name + "_flux_linkage_dqh.png"), is_show_fig=is_show_fig, - **dict_2D + **dict_2D, ) return out, ELUT @@ -128,14 +133,15 @@ def test_EEC_ELUT_PMSM(n_Id=3, n_Iq=3): @pytest.mark.EEC_PMSM @pytest.mark.IPMSM @pytest.mark.periodicity -def test_EEC_ELUT_PMSM_MTPA(test_ELUT): +def test_EEC_ELUT_PMSM_MTPA(test_ELUT, n_Id=51, n_Iq=101): """Validation of the PMSM Electrical Equivalent Circuit with the Prius machine for MTPA calculation""" Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) qs = Toyota_Prius.stator.winding.qs p = Toyota_Prius.get_pole_pair_number() - simu_MTPA = Simu1(name="test_EEC_ELUT_PMSM_MTPA", machine=Toyota_Prius) + name = "test_EEC_ELUT_PMSM_MTPA" + simu_MTPA = Simu1(name=name, machine=Toyota_Prius) # Definition of the input OP_ref = OPdq(N0=1000, Id_ref=50, Iq_ref=100) @@ -153,30 +159,81 @@ def test_EEC_ELUT_PMSM_MTPA(test_ELUT): Iq_max = np.max(OP_matrix[:, 2]) Id, Iq = np.meshgrid( - np.linspace(Id_min, Id_max, 50), np.linspace(Iq_min, Iq_max, 100) + np.linspace(Id_min, Id_max, n_Id), np.linspace(Iq_min, Iq_max, n_Iq) ) Id, Iq = Id.ravel(), Iq.ravel() elec_model = Electrical(eec=EEC_PMSM(), ELUT_enforced=test_ELUT) + # Interpolate stator winding flux in dqh frame for all Id/Iq elec_model.eec.parameters = elec_model.ELUT_enforced.get_param_dict( Id=Id, Iq=Iq, param_list=["Idqh", "Phidqh"] ) + # Compute torque + Tem_sync, Tem_rel = elec_model.eec.comp_torque_sync_rel(qs, p) + Tem_interp = Tem_sync + Tem_rel + + # Init plot map + dict_map = { + "Xdata": Id.reshape((n_Iq, n_Id))[0, :], + "Ydata": Iq.reshape((n_Iq, n_Id))[:, 0], + "xlabel": "d-axis current [Arms]", + "ylabel": "q-axis current [Arms]", + "type_plot": "pcolor", + "is_contour": True, + "is_show_fig": is_show_fig, + } + + # Plot torque map + plot_3D( + Zdata=Tem_interp.reshape((n_Iq, n_Id)).T, + zlabel="Average Torque [N.m]", + title="Torque map in dq plane", + save_path=join(save_path, name + "_torque_map.png"), + **dict_map, + ) + + # Plot Phi_d map + plot_3D( + Zdata=elec_model.eec.parameters["Phid"].reshape((n_Iq, n_Id)).T, + zlabel="$\Phi_d$ [Wb]", + title="Flux linkage map in dq plane (d-axis)", + save_path=join(save_path, name + "_phid_map.png"), + **dict_map, + ) + + # Plot Phi_q map + plot_3D( + Zdata=elec_model.eec.parameters["Phiq"].reshape((n_Iq, n_Id)).T, + zlabel="$\Phi_q$ [Wb]", + title="Flux linkage map in dq plane (q-axis)", + save_path=join(save_path, name + "_phiq_map.png"), + **dict_map, + ) + + # MTPA # Maximum current [Arms] - I_max = 250 + I_max = 250 / np.sqrt(2) Imax_interp = np.sqrt(Id ** 2 + Iq ** 2) # Maximum voltage [Vrms] - U_max = 500 + U_max = 300 # Speed vector Nspeed = 50 N0_min = 50 N0_max = 8000 N0_vect = np.linspace(N0_min, N0_max, Nspeed) - # Maximum current vector + # Maximum load vector Ntorque = 5 - if Ntorque == 1: + is_braking = False # True to include negative torque (braking) + if is_braking: + Ntorque = ( + 2 * Ntorque + 1 + ) # Take twice the number of torques + odd to include zero torque + if not is_braking and Ntorque == 1: I_max_vect = np.array([I_max]) + elif is_braking: + I_max_vect = np.linspace(-I_max, I_max, Ntorque) else: I_max_vect = np.linspace(0, I_max, Ntorque) @@ -205,16 +262,11 @@ def test_EEC_ELUT_PMSM_MTPA(test_ELUT): out_dict = elec_model.eec.solve_EEC() U_max_interp = np.sqrt(out_dict["Ud"] ** 2 + out_dict["Uq"] ** 2) - # Compute torque - Tem_sync, Tem_rel = elec_model.eec.comp_torque_sync_rel(qs, p) - Tem_interp = Tem_sync + Tem_rel - for kk, I_max0 in enumerate(I_max_vect): if I_max0 == 0: - # Finding indices of operating points satisfying Vmax voltage - k0 = np.logical_and(U_max_interp <= U_max, np.abs(Tem_interp) <= 1e-1) - j0 = np.logical_and(k0, np.abs(Iq) == 0) + # Finding indices of operating points satisfying Vmax voltage for Iq=0 (no torque production) + j0 = np.logical_and(U_max_interp <= U_max, np.abs(Iq) == 0) # Finding index of operating point giving lowest current jmax = np.argmin(np.abs(Imax_interp[j0])) @@ -247,25 +299,8 @@ def test_EEC_ELUT_PMSM_MTPA(test_ELUT): I_MTPA[ii, kk, 1] = OP_matrix_MTPA[ii, kk, 2] I_MTPA[ii, kk, 2] = Imax_interp[j0][jmax] - i_load = 0 - plot_2D( - [OP_matrix_MTPA[:, i_load, 0]], - [I_MTPA[:, i_load, 0], I_MTPA[:, i_load, 1], I_MTPA[:, i_load, 2]], - xlabel="Speed [rpm]", - ylabel="Current [Arms]", - legend_list=["Id", "Iq", "Imax"], - ) - - i_load = 0 - plot_2D( - [OP_matrix_MTPA[:, i_load, 0]], - [U_MTPA[:, i_load, 0], U_MTPA[:, i_load, 1], U_MTPA[:, i_load, 2]], - xlabel="Speed [rpm]", - ylabel="Voltage [Vrms]", - legend_list=["Ud", "Uq", "Umax"], - ) - if Ntorque > 1: + # Plot torque speed curve for each load level y_list = list() legend_list = list() for i_load in range(Ntorque): @@ -273,15 +308,37 @@ def test_EEC_ELUT_PMSM_MTPA(test_ELUT): legend_list.append( "Load level = " + str(int(round(100 * (i_load) / (Ntorque - 1)))) + " %" ) - plot_2D( [OP_matrix_MTPA[:, i_load, 0]], y_list, xlabel="Speed [rpm]", ylabel="Average torque [N.m]", legend_list=legend_list, + # save_path=join(save_path, name + "_MTPA_torque_speed.png"), + # is_show_fig=is_show_fig, ) + i_load = -1 + plot_2D( + [OP_matrix_MTPA[:, i_load, 0]], + [I_MTPA[:, i_load, 0], I_MTPA[:, i_load, 1], I_MTPA[:, i_load, 2]], + xlabel="Speed [rpm]", + ylabel="Current [Arms]", + legend_list=["Id", "Iq", "Imax"], + # save_path=join(save_path, name + "_current_MTPA_OP" + str(i_load) + ".png"), + # is_show_fig=is_show_fig, + ) + + plot_2D( + [OP_matrix_MTPA[:, i_load, 0]], + [U_MTPA[:, i_load, 0], U_MTPA[:, i_load, 1], U_MTPA[:, i_load, 2]], + xlabel="Speed [rpm]", + ylabel="Voltage [Vrms]", + legend_list=["Ud", "Uq", "Umax"], + # save_path=join(save_path, name + "_voltage_MTPA_OP" + str(i_load) + ".png"), + # is_show_fig=is_show_fig, + ) + @pytest.mark.long_5s @pytest.mark.MagFEMM @@ -317,7 +374,7 @@ def test_EEC_ELUT_PMSM_PWM(test_ELUT): "freqs", save_path=join(save_path, "EEC_FEMM_IPMSM_Is_harm.png"), is_show_fig=is_show_fig, - **dict_2D + **dict_2D, ) return out_EEC @@ -325,7 +382,7 @@ def test_EEC_ELUT_PMSM_PWM(test_ELUT): # To run it without pytest if __name__ == "__main__": - # out0, ELUT = test_ELUT() + # out0, ELUT = test_EEC_ELUT_PMSM_calc() # ELUT.save("ELUT_PMSM.h5") ELUT = load("ELUT_PMSM.h5") test_EEC_ELUT_PMSM_MTPA(ELUT) diff --git a/Tests/Validation/Electrical/test_EEC_PMSM.py b/Tests/Validation/Electrical/test_EEC_PMSM.py index 729d3c138..65efd9f5b 100644 --- a/Tests/Validation/Electrical/test_EEC_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_PMSM.py @@ -33,11 +33,8 @@ @pytest.mark.IPMSM @pytest.mark.periodicity @pytest.mark.SingleOP -@pytest.mark.skip(reason="Work in progress") def test_EEC_PMSM(nb_worker=int(0.5 * cpu_count())): - """Validation of the PMSM Electrical Equivalent Circuit with the Prius machine - Compute Torque from EEC results and compare with Yang et al, 2013 - """ + """Validation of the PMSM Electrical Equivalent Circuit by comparing torque with MagFEMM""" Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) simu = Simu1(name="test_EEC_PMSM", machine=Toyota_Prius) @@ -65,22 +62,31 @@ def test_EEC_PMSM(nb_worker=int(0.5 * cpu_count())): out_mag = simu_mag.run() # from Yang et al, 2013 - assert out.elec.Tem_av_ref == pytest.approx(81.69, rel=0.1) - assert out_mag.mag.Tem_av == pytest.approx(81.91, rel=0.1) + assert out.elec.Tem_av_ref == pytest.approx(82.7, rel=0.1) + assert out_mag.mag.Tem_av == pytest.approx(82.7, rel=0.1) # Plot 3-phase current function of time out.elec.get_Is().plot_2D_Data( "time", "phase[]", - # save_path=join(save_path, "EEC_FEMM_IPMSM_currents.png"), - # is_show_fig=False, + save_path=join(save_path, "EEC_FEMM_IPMSM_currents.png"), + is_show_fig=False, **dict_2D ) return out +@pytest.mark.long_5s +@pytest.mark.long_1m +@pytest.mark.MagFEMM +@pytest.mark.EEC_PMSM +@pytest.mark.IPMSM +@pytest.mark.periodicity def test_EEC_PMSM_sync_rel(nb_worker=int(0.5 * cpu_count())): + """Validation of the PMSM Electrical Equivalent Circuit with the Prius machine + Compute Torque from EEC results and compare with Yang et al, 2013 + """ Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) simu = Simu1(name="test_EEC_PMSM", machine=Toyota_Prius) @@ -125,6 +131,8 @@ def test_EEC_PMSM_sync_rel(nb_worker=int(0.5 * cpu_count())): xlabel="Current angle [°]", ylabel="Electrical torque [N.m]", title="Electrical torque vs current angle", + save_path=join(save_path, "test_EEC_PMSM_validation.png"), + is_show_fig=False, **dict_2D ) @@ -132,7 +140,7 @@ def test_EEC_PMSM_sync_rel(nb_worker=int(0.5 * cpu_count())): p = Toyota_Prius.get_pole_pair_number() Tem_sync = zeros(N_simu) Tem_rel = zeros(N_simu) - phi_mag = out.output_list[0].elec.eec.fluxlink.comp_fluxlinkage(Toyota_Prius) + phi_mag = out.output_list[0].simu.elec.eec.fluxlink.comp_fluxlinkage(Toyota_Prius) for ii, out_ii in enumerate(out.output_list): out_ii.simu.elec.eec.parameters["phi"] = phi_mag Tem_sync[ii], Tem_rel[ii] = out_ii.simu.elec.eec.comp_torque_sync_rel( @@ -142,6 +150,18 @@ def test_EEC_PMSM_sync_rel(nb_worker=int(0.5 * cpu_count())): Tem2 = Tem_sync + Tem_rel assert_almost_equal(out.xoutput_dict["Tem_av_ref"].result - Tem2, 0, decimal=13) + plot_2D( + array([x * 180 / pi for x in out.xoutput_dict["Phi0"].result]), + [out.xoutput_dict["Tem_av_ref"].result, Tem_sync, Tem_rel], + legend_list=["Overall", "Synchronous", "Reluctant"], + xlabel="Current angle [°]", + ylabel="Electrical torque [N.m]", + title="Electrical torque vs current angle", + save_path=join(save_path, "test_EEC_PMSM_sync_rel.png"), + is_show_fig=False, + **dict_2D + ) + return out diff --git a/Tests/Validation/Magnetics/test_FEMM_set_previous.py b/Tests/Validation/Magnetics/test_FEMM_set_previous.py index 9b9d42fa1..b47bcb207 100644 --- a/Tests/Validation/Magnetics/test_FEMM_set_previous.py +++ b/Tests/Validation/Magnetics/test_FEMM_set_previous.py @@ -32,10 +32,9 @@ def test_FEMM_set_previous(): simu = Simu1(name="test_FEMM_set_previous", machine=Toyota_Prius) # Definition of a sinusoidal current - simu.input = InputCurrent() - simu.input.OP = OPdq(Id_ref=-100, Iq_ref=200, N0=2000) - simu.input.Nt_tot = 32 * 4 # Number of time step - simu.input.Na_tot = 1024 # Spatial discretization + simu.input = InputCurrent( + OP=OPdq(Id_ref=-100, Iq_ref=200, N0=2000), Nt_tot=32 * 4, Na_tot=1024 + ) # Definition of the magnetic simulation with previous ans file simu.mag = MagFEMM( From 1ffe23ae0a3cdd13f37c5e53d6a364ec1b8ea4b3 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 27 Oct 2021 03:08:39 +0200 Subject: [PATCH 143/167] [WiP] Set rot_dir=-1 by default to get fundamental mmf at (fs,p) + introduce current_dir and mmf_dir along with rot_dir to clarify conventions [CO] Directly define angle_rotor normalization in comp_axis_time (no need of comp_angle_rotor which is in output instead of input) [CC] Rename comp_rot_dir to comp_mmf_dir [CC] Reuse LamSlotWind methods in LamSlotMultiWind methods --- Tests/Methods/Simulation/test_Electrical.py | 59 ---------- .../Methods/Simulation/test_InCurrent_meth.py | 2 +- Tests/Methods/Simulation/test_magelmer.py | 4 +- .../Simulation/test_rot_dir_dq_axis.py | 106 ++++++++++++++++++ Tests/Simulation/test_LSRPM_simulation.py | 2 +- .../Webinar/Webinar_Pyleecan_Basics.ipynb | 2 +- Tutorials/tuto_Simulation_FEMM.ipynb | 2 +- pyleecan/Classes/Class_Dict.json | 38 ++++++- pyleecan/Classes/InputCurrent.py | 6 +- pyleecan/Classes/InputFlux.py | 6 +- pyleecan/Classes/InputVoltage.py | 38 ++++++- pyleecan/Classes/LamSlotMultiWind.py | 16 +-- pyleecan/Classes/LamSlotWind.py | 14 +-- pyleecan/Classes/OutGeo.py | 66 ++++++++++- pyleecan/Classes/Output.py | 14 --- .../Dialog/DMachineSetup/SWinding/SWinding.py | 2 +- .../ClassesRef/Machine/LamSlotMultiWind.csv | 2 +- .../ClassesRef/Machine/LamSlotWind.csv | 2 +- .../Generator/ClassesRef/Output/OutGeo.csv | 4 +- .../Generator/ClassesRef/Output/Output.csv | 7 +- .../ClassesRef/Simulation/InputVoltage.csv | 3 +- .../LamSlotMultiWind/comp_angle_d_axis.py | 47 ++------ .../Machine/LamSlotMultiWind/comp_mmf_dir.py | 31 +++++ .../Machine/LamSlotMultiWind/comp_mmf_unit.py | 10 +- .../Machine/LamSlotMultiWind/comp_rot_dir.py | 41 ------- .../{comp_rot_dir.py => comp_mmf_dir.py} | 27 +++-- .../Machine/LamSlotWind/comp_mmf_unit.py | 8 +- .../Output/Output/getter/comp_angle_rotor.py | 12 +- .../Output/Output/getter/get_rot_dir.py | 33 ------ .../Methods/Simulation/Input/comp_axes.py | 2 +- .../Simulation/Input/comp_axis_time.py | 39 ++----- .../Simulation/InputVoltage/gen_input.py | 62 +++++----- pyleecan/Methods/Simulation/OPdq/get_N0.py | 12 +- pyleecan/Methods/Simulation/OPdq/get_felec.py | 13 ++- 34 files changed, 411 insertions(+), 321 deletions(-) delete mode 100644 Tests/Methods/Simulation/test_Electrical.py create mode 100644 Tests/Methods/Simulation/test_rot_dir_dq_axis.py create mode 100644 pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_dir.py delete mode 100644 pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py rename pyleecan/Methods/Machine/LamSlotWind/{comp_rot_dir.py => comp_mmf_dir.py} (54%) delete mode 100644 pyleecan/Methods/Output/Output/getter/get_rot_dir.py diff --git a/Tests/Methods/Simulation/test_Electrical.py b/Tests/Methods/Simulation/test_Electrical.py deleted file mode 100644 index 149fd2922..000000000 --- a/Tests/Methods/Simulation/test_Electrical.py +++ /dev/null @@ -1,59 +0,0 @@ -import pytest -from numpy import pi -from os.path import join -from pyleecan.Functions.load import load -from pyleecan.definitions import DATA_DIR - - -@pytest.fixture(scope="module") -def Toyota_Prius(): - Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) - return Toyota_Prius - - -@pytest.mark.IPMSM -class Test_Electrical(object): - def test_resistance(self, Toyota_Prius): - """Check that resistance computation is correct""" - result = Toyota_Prius.stator.comp_resistance_wind() - assert result == pytest.approx(0.035951, abs=0.00001) - - def test_DQ_axis_rotor(self, Toyota_Prius): - """Check that the DQ axis are correct for the rotor""" - d_axis = Toyota_Prius.rotor.comp_angle_d_axis() - assert d_axis == pytest.approx(pi / 8, abs=0.0001) - - q_axis = Toyota_Prius.rotor.comp_angle_q_axis() - assert q_axis == pytest.approx(2 * pi / 8, abs=0.0001) - - def test_DQ_axis_stator(self, Toyota_Prius): - """Check that the DQ axis are correct for the stator""" - d_axis = Toyota_Prius.stator.comp_angle_d_axis() - assert d_axis == pytest.approx(1.3076, abs=0.001) - - q_axis = Toyota_Prius.stator.comp_angle_q_axis() - assert q_axis == pytest.approx(1.3076 + pi / 8, abs=0.001) - - def test_comp_rot_dir(self, Toyota_Prius): - """Check that the computation of the rot dir is correct""" - rot_dir = Toyota_Prius.stator.comp_rot_dir() - assert rot_dir == -1, "rot_dir=" + str(rot_dir) + " instead of -1" - - def test_comp_rot_dir_reverse_wind(self, Toyota_Prius): - """Check that the computation of the rot dir is correct when reversing the winding""" - IPMSM_B = Toyota_Prius.copy() - IPMSM_B.stator.winding.is_reverse_wind = ( - not IPMSM_B.stator.winding.is_reverse_wind - ) - rot_dir = IPMSM_B.stator.comp_rot_dir() - assert rot_dir == 1 - - -if __name__ == "__main__": - a = Test_Electrical() - toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) - a.test_resistance(toyota_Prius) - a.test_DQ_axis_rotor(toyota_Prius) - a.test_DQ_axis_stator(toyota_Prius) - a.test_comp_rot_dir(toyota_Prius) - a.test_comp_rot_dir_reverse_wind(toyota_Prius) diff --git a/Tests/Methods/Simulation/test_InCurrent_meth.py b/Tests/Methods/Simulation/test_InCurrent_meth.py index 4db50c985..15beda9d0 100644 --- a/Tests/Methods/Simulation/test_InCurrent_meth.py +++ b/Tests/Methods/Simulation/test_InCurrent_meth.py @@ -229,7 +229,7 @@ def test_InputCurrent_DQ(self, test_dict): p = Toyota_Prius.stator.get_pole_pair_number() time_exp = linspace(0, 60 / N0, Nt_tot, endpoint=False) felec = p * N0 / 60 - rot_dir = Toyota_Prius.stator.comp_rot_dir() + rot_dir = Toyota_Prius.stator.comp_mmf_dir() Ia = ( A_rms * sqrt(2) diff --git a/Tests/Methods/Simulation/test_magelmer.py b/Tests/Methods/Simulation/test_magelmer.py index b71c7852d..2ab0e6a35 100644 --- a/Tests/Methods/Simulation/test_magelmer.py +++ b/Tests/Methods/Simulation/test_magelmer.py @@ -80,7 +80,7 @@ def test_ipm_Elmer(): simu.input.angle = linspace(0, 2 * pi, num=2048, endpoint=False) I0 = 250 felec = p * simu.input.OP.N0 / 60 - rot_dir = simu.machine.stator.comp_rot_dir() + rot_dir = simu.machine.stator.comp_mmf_dir() Phi0 = 140 * pi / 180 Ia = I0 * cos(2 * pi * felec * time + 0 * rot_dir * 2 * pi / 3 + Phi0) Ib = I0 * cos(2 * pi * felec * time + 1 * rot_dir * 2 * pi / 3 + Phi0) @@ -143,7 +143,7 @@ def test_spm_Elmer(): simu.input.angle = linspace(0, 2 * pi, num=2048, endpoint=False) I0 = 150 felec = p * simu.input.OP.N0 / 60 - rot_dir = simu.machine.stator.comp_rot_dir() + rot_dir = simu.machine.stator.comp_mmf_dir() Phi0 = 140 * pi / 180 Ia = I0 * cos(2 * pi * felec * time + 0 * rot_dir * 2 * pi / 3 + Phi0) Ib = I0 * cos(2 * pi * felec * time + 1 * rot_dir * 2 * pi / 3 + Phi0) diff --git a/Tests/Methods/Simulation/test_rot_dir_dq_axis.py b/Tests/Methods/Simulation/test_rot_dir_dq_axis.py new file mode 100644 index 000000000..c67c65b74 --- /dev/null +++ b/Tests/Methods/Simulation/test_rot_dir_dq_axis.py @@ -0,0 +1,106 @@ +import pytest +from numpy import pi +from os.path import join +from multiprocessing import cpu_count + +from pyleecan.Classes.MagFEMM import MagFEMM +from pyleecan.Classes.OPdq import OPdq +from pyleecan.Classes.Simu1 import Simu1 +from pyleecan.Classes.InputCurrent import InputCurrent + +from pyleecan.Functions.load import load +from pyleecan.definitions import DATA_DIR + + +param_list = [ + { + "name": "Toyota_Prius", + "resistance": 0.035951, + "stator_d_axis": 1.3076, + "rotor_d_axis": pi / 8, + "rot_dir": 1, + }, + { + "name": "PR0178_machine_new", + "resistance": 0.035951, + "stator_d_axis": 1.3076, + "rotor_d_axis": pi / 8, + "rot_dir": 1, + }, +] + + +@pytest.mark.parametrize("param_dict", param_list) +def test_rot_dir_dq_axis(param_dict, nb_worker=int(cpu_count() / 2)): + + machine = load(join(DATA_DIR, "Machine", param_dict["name"] + ".json")) + + p = machine.get_pole_pair_number() + + # # Check that resistance computation is correct + # result = machine.stator.comp_resistance_wind() + # assert result == pytest.approx(param_dict["resistance"], abs=0.00001) + + # # Check that the DQ axis are correct for the stator + # d_axis = machine.stator.comp_angle_d_axis() + # assert d_axis == pytest.approx(param_dict["stator_d_axis"], abs=0.001) + + # q_axis = machine.stator.comp_angle_q_axis() + # assert q_axis == pytest.approx(param_dict["stator_d_axis"] + pi / 2 / p, abs=0.001) + + # # Check that the DQ axis are correct for the rotor + # d_axis = machine.rotor.comp_angle_d_axis() + # assert d_axis == pytest.approx(param_dict["rotor_d_axis"], abs=0.0001) + # q_axis = machine.rotor.comp_angle_q_axis() + # assert q_axis == pytest.approx(param_dict["rotor_d_axis"] + pi / 2 / p, abs=0.0001) + + rot_dir = machine.stator.comp_mmf_dir(is_plot=True) + # assert rot_dir == param_dict["rot_dir"], ( + # "rot_dir=" + str(rot_dir) + " instead of " + str(param_dict["rot_dir"]) + # ) + + machine_B = machine.copy() + machine_B.stator.winding.is_reverse_wind = ( + not machine_B.stator.winding.is_reverse_wind + ) + rot_dir_inv = machine_B.stator.comp_mmf_dir() + assert rot_dir_inv == -rot_dir + + simu = Simu1(name="test_rot_dir_dq_axis_" + machine.name, machine=machine) + + # Compute time and space (anti-)periodicities from the machine + per_a, is_aper_a = machine.comp_periodicity_spatial() + per_t, is_aper_t, _, _ = machine.comp_periodicity_time() + + per_a = int(2 * per_a) if is_aper_a else per_a + per_t = int(2 * per_t) if is_aper_t else per_t + + simu.input = InputCurrent( + OP=OPdq(Id_ref=-50, Iq_ref=150, N0=1000), + # OP=OPdq(Id_ref=0, Iq_ref=0, N0=1000), + Nt_tot=10 * nb_worker * per_t, + Na_tot=200 * per_a, + ) + + # Definition of the magnetic simulation (only one OP in MLUT => direct calculation) + simu.mag = MagFEMM( + is_periodicity_a=True, + is_periodicity_t=True, + nb_worker=nb_worker, + ) + + out = simu.run() + + out.elec.get_Is().plot_2D_Data("time", "phase[]") + + out.mag.Tem.plot_2D_Data("time") + + out.mag.B.components["radial"].plot_3D_Data("time", "angle") + + out.mag.B.components["radial"].plot_3D_Data("freqs", "wavenumber") + + return out + + +if __name__ == "__main__": + out = test_rot_dir_dq_axis(param_list[1]) diff --git a/Tests/Simulation/test_LSRPM_simulation.py b/Tests/Simulation/test_LSRPM_simulation.py index 9591f42d9..a444b62dc 100644 --- a/Tests/Simulation/test_LSRPM_simulation.py +++ b/Tests/Simulation/test_LSRPM_simulation.py @@ -46,7 +46,7 @@ def test_LSRPM_simulation(): # Stator currents as a function of time, each column correspond to one phase [A] I0_rms = 6.85 felec = p * simu_femm.input.OP.N0 / 60 # [Hz] - rot_dir = simu_femm.machine.stator.comp_rot_dir() + rot_dir = simu_femm.machine.stator.comp_mmf_dir() Phi0 = 140 * pi / 180 # Maximum Torque Per Amp Ia = ( diff --git a/Tutorials/Webinar/Webinar_Pyleecan_Basics.ipynb b/Tutorials/Webinar/Webinar_Pyleecan_Basics.ipynb index 9b9983e21..6a18abbc2 100644 --- a/Tutorials/Webinar/Webinar_Pyleecan_Basics.ipynb +++ b/Tutorials/Webinar/Webinar_Pyleecan_Basics.ipynb @@ -615,7 +615,7 @@ "# Stator currents as a function of time, each column correspond to one phase [A]\n", "I0_rms = 250/sqrt(2) \n", "felec = p * simu_femm.input.N0 /60 # [Hz]\n", - "rot_dir = simu_femm.machine.stator.comp_rot_dir()\n", + "rot_dir = simu_femm.machine.stator.comp_mmf_dir()\n", "Phi0 = 140*pi/180 # Maximum Torque Per Amp\n", "\n", "Ia = (\n", diff --git a/Tutorials/tuto_Simulation_FEMM.ipynb b/Tutorials/tuto_Simulation_FEMM.ipynb index df7cb84ba..7235c129e 100644 --- a/Tutorials/tuto_Simulation_FEMM.ipynb +++ b/Tutorials/tuto_Simulation_FEMM.ipynb @@ -120,7 +120,7 @@ "# Stator currents as a function of time, each column correspond to one phase [A]\n", "I0_rms = 250/sqrt(2) \n", "felec = p * simu_femm.input.N0 /60 # [Hz]\n", - "rot_dir = simu_femm.machine.stator.comp_rot_dir()\n", + "rot_dir = simu_femm.machine.stator.comp_mmf_dir()\n", "Phi0 = 140*pi/180 # Maximum Torque Per Amp\n", "\n", "Ia = (\n", diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 66dbffad2..40632e841 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4284,9 +4284,9 @@ "max": "1", "min": "-1", "name": "rot_dir", - "type": "float", + "type": "int", "unit": "-", - "value": null + "value": -1 }, { "desc": "Initial angular position of the rotor at t=0", @@ -4305,6 +4305,15 @@ "type": "ImportGenPWM", "unit": "", "value": null + }, + { + "desc": "Rotation direction of the stator currents -1 trigo, 1 clockwise", + "max": "1", + "min": "-1", + "name": "current_dir", + "type": "int", + "unit": "-", + "value": 1 } ] }, @@ -4745,7 +4754,7 @@ "plot", "build_geometry", "get_pole_pair_number", - "comp_rot_dir", + "comp_mmf_dir", "plot_mmf_unit", "comp_mmf_unit", "comp_wind_function", @@ -4806,7 +4815,7 @@ "comp_resistance_wind", "comp_angle_d_axis", "comp_mmf_unit", - "comp_rot_dir", + "comp_mmf_dir", "comp_lengths_winding", "comp_number_phase_eq", "comp_periodicity_spatial", @@ -8422,7 +8431,7 @@ "value": null }, { - "desc": "rotation direction of the magnetic field fundamental !! WARNING: rot_dir = -1 to have positive rotor rotating direction, i.e. rotor position moves towards positive angle", + "desc": "rotation direction of rotor\u00c2\u00a0: rot_dir = -1 by default (counter clockwise rotation)", "max": "1", "min": "-1", "name": "rot_dir", @@ -8492,6 +8501,24 @@ "type": "bool", "unit": "-", "value": null + }, + { + "desc": "rotation direction of the stator magnetomotive force\u00c2\u00a0: mmf_dir=1 by default (counter clockwise rotation)", + "max": "1", + "min": "-1", + "name": "mmf_dir", + "type": "int", + "unit": "-", + "value": null + }, + { + "desc": "rotation direction of the stator currents: current_dir=1 by default (counter clockwise rotation)", + "max": "1", + "min": "-1", + "name": "current_dir", + "type": "int", + "unit": "-", + "value": null } ] }, @@ -9034,7 +9061,6 @@ "getter.get_BH_stator", "getter.get_path_result", "getter.get_machine_periodicity", - "getter.get_rot_dir", "getter.get_fund_harm", "getter.get_data_from_str", "print_memory" diff --git a/pyleecan/Classes/InputCurrent.py b/pyleecan/Classes/InputCurrent.py index fb76c2e68..0cb4b2128 100644 --- a/pyleecan/Classes/InputCurrent.py +++ b/pyleecan/Classes/InputCurrent.py @@ -94,9 +94,10 @@ def __init__( Is=None, Ir=None, angle_rotor=None, - rot_dir=None, + rot_dir=-1, angle_rotor_initial=0, PWM=None, + current_dir=1, time=None, angle=None, Nt_tot=2048, @@ -133,6 +134,8 @@ def __init__( angle_rotor_initial = init_dict["angle_rotor_initial"] if "PWM" in list(init_dict.keys()): PWM = init_dict["PWM"] + if "current_dir" in list(init_dict.keys()): + current_dir = init_dict["current_dir"] if "time" in list(init_dict.keys()): time = init_dict["time"] if "angle" in list(init_dict.keys()): @@ -154,6 +157,7 @@ def __init__( rot_dir=rot_dir, angle_rotor_initial=angle_rotor_initial, PWM=PWM, + current_dir=current_dir, time=time, angle=angle, Nt_tot=Nt_tot, diff --git a/pyleecan/Classes/InputFlux.py b/pyleecan/Classes/InputFlux.py index b1c7a167c..e30dad827 100644 --- a/pyleecan/Classes/InputFlux.py +++ b/pyleecan/Classes/InputFlux.py @@ -66,9 +66,10 @@ def __init__( Is=None, Ir=None, angle_rotor=None, - rot_dir=None, + rot_dir=-1, angle_rotor_initial=0, PWM=None, + current_dir=1, time=None, angle=None, Nt_tot=2048, @@ -121,6 +122,8 @@ def __init__( angle_rotor_initial = init_dict["angle_rotor_initial"] if "PWM" in list(init_dict.keys()): PWM = init_dict["PWM"] + if "current_dir" in list(init_dict.keys()): + current_dir = init_dict["current_dir"] if "time" in list(init_dict.keys()): time = init_dict["time"] if "angle" in list(init_dict.keys()): @@ -150,6 +153,7 @@ def __init__( rot_dir=rot_dir, angle_rotor_initial=angle_rotor_initial, PWM=PWM, + current_dir=current_dir, time=time, angle=angle, Nt_tot=Nt_tot, diff --git a/pyleecan/Classes/InputVoltage.py b/pyleecan/Classes/InputVoltage.py index 9f20ce7b0..ddd939bc9 100644 --- a/pyleecan/Classes/InputVoltage.py +++ b/pyleecan/Classes/InputVoltage.py @@ -76,9 +76,10 @@ class InputVoltage(Input): def __init__( self, angle_rotor=None, - rot_dir=None, + rot_dir=-1, angle_rotor_initial=0, PWM=None, + current_dir=1, time=None, angle=None, Nt_tot=2048, @@ -111,6 +112,8 @@ def __init__( angle_rotor_initial = init_dict["angle_rotor_initial"] if "PWM" in list(init_dict.keys()): PWM = init_dict["PWM"] + if "current_dir" in list(init_dict.keys()): + current_dir = init_dict["current_dir"] if "time" in list(init_dict.keys()): time = init_dict["time"] if "angle" in list(init_dict.keys()): @@ -128,6 +131,7 @@ def __init__( self.rot_dir = rot_dir self.angle_rotor_initial = angle_rotor_initial self.PWM = PWM + self.current_dir = current_dir # Call Input init super(InputVoltage, self).__init__( time=time, angle=angle, Nt_tot=Nt_tot, Nrev=Nrev, Na_tot=Na_tot, OP=OP @@ -157,6 +161,7 @@ def __str__(self): InputVoltage_str += "PWM = " + tmp else: InputVoltage_str += "PWM = None" + linesep + linesep + InputVoltage_str += "current_dir = " + str(self.current_dir) + linesep return InputVoltage_str def __eq__(self, other): @@ -176,6 +181,8 @@ def __eq__(self, other): return False if other.PWM != self.PWM: return False + if other.current_dir != self.current_dir: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -207,6 +214,8 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".PWM None mismatch") elif self.PWM is not None: diff_list.extend(self.PWM.compare(other.PWM, name=name + ".PWM")) + if other._current_dir != self._current_dir: + diff_list.append(name + ".current_dir") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -222,6 +231,7 @@ def __sizeof__(self): S += getsizeof(self.rot_dir) S += getsizeof(self.angle_rotor_initial) S += getsizeof(self.PWM) + S += getsizeof(self.current_dir) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -259,6 +269,7 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): keep_function=keep_function, **kwargs ) + InputVoltage_dict["current_dir"] = self.current_dir # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name InputVoltage_dict["__class__"] = "InputVoltage" @@ -273,6 +284,7 @@ def _set_None(self): self.angle_rotor_initial = None if self.PWM is not None: self.PWM._set_None() + self.current_dir = None # Set to None the properties inherited from Input super(InputVoltage, self)._set_None() @@ -312,7 +324,7 @@ def _get_rot_dir(self): def _set_rot_dir(self, value): """setter of rot_dir""" - check_var("rot_dir", value, "float", Vmin=-1, Vmax=1) + check_var("rot_dir", value, "int", Vmin=-1, Vmax=1) self._rot_dir = value rot_dir = property( @@ -320,7 +332,7 @@ def _set_rot_dir(self, value): fset=_set_rot_dir, doc=u"""Rotation direction of the rotor 1 trigo, -1 clockwise - :Type: float + :Type: int :min: -1 :max: 1 """, @@ -371,3 +383,23 @@ def _set_PWM(self, value): :Type: ImportGenPWM """, ) + + def _get_current_dir(self): + """getter of current_dir""" + return self._current_dir + + def _set_current_dir(self, value): + """setter of current_dir""" + check_var("current_dir", value, "int", Vmin=-1, Vmax=1) + self._current_dir = value + + current_dir = property( + fget=_get_current_dir, + fset=_set_current_dir, + doc=u"""Rotation direction of the stator currents -1 trigo, 1 clockwise + + :Type: int + :min: -1 + :max: 1 + """, + ) diff --git a/pyleecan/Classes/LamSlotMultiWind.py b/pyleecan/Classes/LamSlotMultiWind.py index 9b7f91675..c788b4b4f 100644 --- a/pyleecan/Classes/LamSlotMultiWind.py +++ b/pyleecan/Classes/LamSlotMultiWind.py @@ -35,9 +35,9 @@ get_pole_pair_number = error try: - from ..Methods.Machine.LamSlotMultiWind.comp_rot_dir import comp_rot_dir + from ..Methods.Machine.LamSlotMultiWind.comp_mmf_dir import comp_mmf_dir except ImportError as error: - comp_rot_dir = error + comp_mmf_dir = error try: from ..Methods.Machine.LamSlotMultiWind.plot_mmf_unit import plot_mmf_unit @@ -110,18 +110,18 @@ class LamSlotMultiWind(LamSlotMulti): ) else: get_pole_pair_number = get_pole_pair_number - # cf Methods.Machine.LamSlotMultiWind.comp_rot_dir - if isinstance(comp_rot_dir, ImportError): - comp_rot_dir = property( + # cf Methods.Machine.LamSlotMultiWind.comp_mmf_dir + if isinstance(comp_mmf_dir, ImportError): + comp_mmf_dir = property( fget=lambda x: raise_( ImportError( - "Can't use LamSlotMultiWind method comp_rot_dir: " - + str(comp_rot_dir) + "Can't use LamSlotMultiWind method comp_mmf_dir: " + + str(comp_mmf_dir) ) ) ) else: - comp_rot_dir = comp_rot_dir + comp_mmf_dir = comp_mmf_dir # cf Methods.Machine.LamSlotMultiWind.plot_mmf_unit if isinstance(plot_mmf_unit, ImportError): plot_mmf_unit = property( diff --git a/pyleecan/Classes/LamSlotWind.py b/pyleecan/Classes/LamSlotWind.py index 016b29283..75e208ec9 100644 --- a/pyleecan/Classes/LamSlotWind.py +++ b/pyleecan/Classes/LamSlotWind.py @@ -98,9 +98,9 @@ comp_mmf_unit = error try: - from ..Methods.Machine.LamSlotWind.comp_rot_dir import comp_rot_dir + from ..Methods.Machine.LamSlotWind.comp_mmf_dir import comp_mmf_dir except ImportError as error: - comp_rot_dir = error + comp_mmf_dir = error try: from ..Methods.Machine.LamSlotWind.comp_lengths_winding import comp_lengths_winding @@ -320,17 +320,17 @@ class LamSlotWind(LamSlot): ) else: comp_mmf_unit = comp_mmf_unit - # cf Methods.Machine.LamSlotWind.comp_rot_dir - if isinstance(comp_rot_dir, ImportError): - comp_rot_dir = property( + # cf Methods.Machine.LamSlotWind.comp_mmf_dir + if isinstance(comp_mmf_dir, ImportError): + comp_mmf_dir = property( fget=lambda x: raise_( ImportError( - "Can't use LamSlotWind method comp_rot_dir: " + str(comp_rot_dir) + "Can't use LamSlotWind method comp_mmf_dir: " + str(comp_mmf_dir) ) ) ) else: - comp_rot_dir = comp_rot_dir + comp_mmf_dir = comp_mmf_dir # cf Methods.Machine.LamSlotWind.comp_lengths_winding if isinstance(comp_lengths_winding, ImportError): comp_lengths_winding = property( diff --git a/pyleecan/Classes/OutGeo.py b/pyleecan/Classes/OutGeo.py index ab7c12360..d8a958a13 100644 --- a/pyleecan/Classes/OutGeo.py +++ b/pyleecan/Classes/OutGeo.py @@ -48,6 +48,8 @@ def __init__( axes_dict=None, per_t_R=None, is_antiper_t_R=None, + mmf_dir=None, + current_dir=None, init_dict=None, init_str=None, ): @@ -98,6 +100,10 @@ def __init__( per_t_R = init_dict["per_t_R"] if "is_antiper_t_R" in list(init_dict.keys()): is_antiper_t_R = init_dict["is_antiper_t_R"] + if "mmf_dir" in list(init_dict.keys()): + mmf_dir = init_dict["mmf_dir"] + if "current_dir" in list(init_dict.keys()): + current_dir = init_dict["current_dir"] # Set the properties (value check and convertion are done in setter) self.parent = None self.stator = stator @@ -116,6 +122,8 @@ def __init__( self.axes_dict = axes_dict self.per_t_R = per_t_R self.is_antiper_t_R = is_antiper_t_R + self.mmf_dir = mmf_dir + self.current_dir = current_dir # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -154,6 +162,8 @@ def __str__(self): OutGeo_str += "axes_dict = " + str(self.axes_dict) + linesep + linesep OutGeo_str += "per_t_R = " + str(self.per_t_R) + linesep OutGeo_str += "is_antiper_t_R = " + str(self.is_antiper_t_R) + linesep + OutGeo_str += "mmf_dir = " + str(self.mmf_dir) + linesep + OutGeo_str += "current_dir = " + str(self.current_dir) + linesep return OutGeo_str def __eq__(self, other): @@ -193,6 +203,10 @@ def __eq__(self, other): return False if other.is_antiper_t_R != self.is_antiper_t_R: return False + if other.mmf_dir != self.mmf_dir: + return False + if other.current_dir != self.current_dir: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -256,6 +270,10 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".per_t_R") if other._is_antiper_t_R != self._is_antiper_t_R: diff_list.append(name + ".is_antiper_t_R") + if other._mmf_dir != self._mmf_dir: + diff_list.append(name + ".mmf_dir") + if other._current_dir != self._current_dir: + diff_list.append(name + ".current_dir") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -282,6 +300,8 @@ def __sizeof__(self): S += getsizeof(value) + getsizeof(key) S += getsizeof(self.per_t_R) S += getsizeof(self.is_antiper_t_R) + S += getsizeof(self.mmf_dir) + S += getsizeof(self.current_dir) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -338,6 +358,8 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): OutGeo_dict["axes_dict"][key] = None OutGeo_dict["per_t_R"] = self.per_t_R OutGeo_dict["is_antiper_t_R"] = self.is_antiper_t_R + OutGeo_dict["mmf_dir"] = self.mmf_dir + OutGeo_dict["current_dir"] = self.current_dir # The class name is added to the dict for deserialisation purpose OutGeo_dict["__class__"] = "OutGeo" return OutGeo_dict @@ -363,6 +385,8 @@ def _set_None(self): self.axes_dict = None self.per_t_R = None self.is_antiper_t_R = None + self.mmf_dir = None + self.current_dir = None def _get_stator(self): """getter of stator""" @@ -544,7 +568,7 @@ def _set_rot_dir(self, value): rot_dir = property( fget=_get_rot_dir, fset=_set_rot_dir, - doc=u"""rotation direction of the magnetic field fundamental !! WARNING: rot_dir = -1 to have positive rotor rotating direction, i.e. rotor position moves towards positive angle + doc=u"""rotation direction of rotor : rot_dir = -1 by default (counter clockwise rotation) :Type: int :min: -1 @@ -690,3 +714,43 @@ def _set_is_antiper_t_R(self, value): :Type: bool """, ) + + def _get_mmf_dir(self): + """getter of mmf_dir""" + return self._mmf_dir + + def _set_mmf_dir(self, value): + """setter of mmf_dir""" + check_var("mmf_dir", value, "int", Vmin=-1, Vmax=1) + self._mmf_dir = value + + mmf_dir = property( + fget=_get_mmf_dir, + fset=_set_mmf_dir, + doc=u"""rotation direction of the stator magnetomotive force : mmf_dir=1 by default (counter clockwise rotation) + + :Type: int + :min: -1 + :max: 1 + """, + ) + + def _get_current_dir(self): + """getter of current_dir""" + return self._current_dir + + def _set_current_dir(self, value): + """setter of current_dir""" + check_var("current_dir", value, "int", Vmin=-1, Vmax=1) + self._current_dir = value + + current_dir = property( + fget=_get_current_dir, + fset=_set_current_dir, + doc=u"""rotation direction of the stator currents: current_dir=1 by default (counter clockwise rotation) + + :Type: int + :min: -1 + :max: 1 + """, + ) diff --git a/pyleecan/Classes/Output.py b/pyleecan/Classes/Output.py index 1b5f5310c..d68de1690 100644 --- a/pyleecan/Classes/Output.py +++ b/pyleecan/Classes/Output.py @@ -56,11 +56,6 @@ except ImportError as error: get_machine_periodicity = error -try: - from ..Methods.Output.Output.getter.get_rot_dir import get_rot_dir -except ImportError as error: - get_rot_dir = error - try: from ..Methods.Output.Output.getter.get_fund_harm import get_fund_harm except ImportError as error: @@ -173,15 +168,6 @@ class Output(FrozenClass): ) else: get_machine_periodicity = get_machine_periodicity - # cf Methods.Output.Output.getter.get_rot_dir - if isinstance(get_rot_dir, ImportError): - get_rot_dir = property( - fget=lambda x: raise_( - ImportError("Can't use Output method get_rot_dir: " + str(get_rot_dir)) - ) - ) - else: - get_rot_dir = get_rot_dir # cf Methods.Output.Output.getter.get_fund_harm if isinstance(get_fund_harm, ImportError): get_fund_harm = property( diff --git a/pyleecan/GUI/Dialog/DMachineSetup/SWinding/SWinding.py b/pyleecan/GUI/Dialog/DMachineSetup/SWinding/SWinding.py index 6a3302ebd..ed0ee3001 100644 --- a/pyleecan/GUI/Dialog/DMachineSetup/SWinding/SWinding.py +++ b/pyleecan/GUI/Dialog/DMachineSetup/SWinding/SWinding.py @@ -441,7 +441,7 @@ def comp_output(self): wind = self.obj.winding # For readability try: - rot_dir = self.obj.comp_rot_dir() + rot_dir = self.obj.comp_mmf_dir() if rot_dir == 1: rot_dir = "CCW" elif rot_dir == -1: diff --git a/pyleecan/Generator/ClassesRef/Machine/LamSlotMultiWind.csv b/pyleecan/Generator/ClassesRef/Machine/LamSlotMultiWind.csv index cae02a50a..a4764fc50 100644 --- a/pyleecan/Generator/ClassesRef/Machine/LamSlotMultiWind.csv +++ b/pyleecan/Generator/ClassesRef/Machine/LamSlotMultiWind.csv @@ -2,7 +2,7 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximu Ksfill,-,"Imposed Slot Fill factor (if None, will be computed according to the winding and the slot)",,float,None,0,1,,Machine,LamSlotMulti,plot,VERSION,1,Lamination with list of Slot filled with winding winding,,Lamination's Winding,,Winding,,,,,,,build_geometry,,, ,,,,,,,,,,,get_pole_pair_number,,, -,,,,,,,,,,,comp_rot_dir,,, +,,,,,,,,,,,comp_mmf_dir,,, ,,,,,,,,,,,plot_mmf_unit,,, ,,,,,,,,,,,comp_mmf_unit,,, ,,,,,,,,,,,comp_wind_function,,, diff --git a/pyleecan/Generator/ClassesRef/Machine/LamSlotWind.csv b/pyleecan/Generator/ClassesRef/Machine/LamSlotWind.csv index 40a0e192d..e8416e43c 100644 --- a/pyleecan/Generator/ClassesRef/Machine/LamSlotWind.csv +++ b/pyleecan/Generator/ClassesRef/Machine/LamSlotWind.csv @@ -15,7 +15,7 @@ winding,,Lamination's Winding,,Winding,,,,,,,check,,, ,,,,,,,,,,,comp_resistance_wind,,, ,,,,,,,,,,,comp_angle_d_axis,,, ,,,,,,,,,,,comp_mmf_unit,,, -,,,,,,,,,,,comp_rot_dir,,, +,,,,,,,,,,,comp_mmf_dir,,, ,,,,,,,,,,,comp_lengths_winding,,, ,,,,,,,,,,,comp_number_phase_eq,,, ,,,,,,,,,,,comp_periodicity_spatial,,, diff --git a/pyleecan/Generator/ClassesRef/Output/OutGeo.csv b/pyleecan/Generator/ClassesRef/Output/OutGeo.csv index 4f6a0c0f8..0c9cafaeb 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutGeo.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutGeo.csv @@ -7,7 +7,7 @@ Rgap_mec,m,radius of the center of the mecanical airgap,0,float,None,,,,,,,,, Lgap,m,Airgap active length,0,float,None,,,,,,,,, logger_name,-,Name of the logger to use,0,str,Pyleecan.OutGeo,,,,,,,,, angle_offset_initial,rad,Difference between the d axis angle of the stator and the rotor,0,float,None,,,,,,,,, -rot_dir,-,"rotation direction of the magnetic field fundamental !! WARNING: rot_dir = -1 to have positive rotor rotating direction, i.e. rotor position moves towards positive angle",0,int,None,-1,1,,,,,,, +rot_dir,-,rotation direction of rotor : rot_dir = -1 by default (counter clockwise rotation),0,int,None,-1,1,,,,,,, per_a,-,Number of spatial periodicities of the machine,0,int,None,,,,,,,,, is_antiper_a,-,True if an spatial anti-periodicity is possible after the periodicities,0,bool,None,,,,,,,,, per_t_S,-,Number of time periodicities of the machine in static referential,0,int,None,,,,,,,,, @@ -15,3 +15,5 @@ is_antiper_t_S,-,True if an time anti-periodicity is possible after the periodic axes_dict,,Dict containing axes data without periodicities used for plots and to have simulation full time/angle vectors,,{SciDataTool.Classes.DataND.Data},None,,,,,,,,, per_t_R,-,Number of time periodicities of the machine in rotating referential,0,int,None,,,,,,,,, is_antiper_t_R,-,True if an time anti-periodicity is possible after the periodicities in rotating referential,0,bool,None,,,,,,,,, +mmf_dir,-,rotation direction of the stator magnetomotive force : mmf_dir=1 by default (counter clockwise rotation),0,int,None,-1,1,,,,,,, +current_dir,-,rotation direction of the stator currents: current_dir=1 by default (counter clockwise rotation),0,int,None,-1,1,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Output/Output.csv b/pyleecan/Generator/ClassesRef/Output/Output.csv index 01e5ea128..fa80b6dff 100644 --- a/pyleecan/Generator/ClassesRef/Output/Output.csv +++ b/pyleecan/Generator/ClassesRef/Output/Output.csv @@ -6,7 +6,6 @@ elec,-,Electrical module output,0,OutElec,,,,,,,getter.get_BH_rotor,,, mag,-,Magnetic module output,0,OutMag,,,,,,,getter.get_BH_stator,,, struct,-,Structural module output,0,OutStruct,,,,,,,getter.get_path_result,,, post,-,Post-Processing settings,0,OutPost,,,,,,,getter.get_machine_periodicity,,, -logger_name,-,Name of the logger to use,0,str,Pyleecan.Output,,,,,,getter.get_rot_dir,,, -force,-,Force module output,0,OutForce,,,,,,,getter.get_fund_harm,,, -loss,-,Loss module output,0,OutLoss,,,,,,,getter.get_data_from_str,,, -,,,,,,,,,,,print_memory,,, +logger_name,-,Name of the logger to use,0,str,Pyleecan.Output,,,,,,getter.get_fund_harm,,, +force,-,Force module output,0,OutForce,,,,,,,getter.get_data_from_str,,, +loss,-,Loss module output,0,OutLoss,,,,,,,print_memory,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv index a6d35882d..8c69e573c 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv @@ -1,5 +1,6 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description angle_rotor,rad,Rotor angular position as a function of time (if None computed according to Nr) to import,Nt_tot,Import,None,,,,Simulation,Input,gen_input,VERSION,1,Input to start the electrical module with voltage input -rot_dir,-,"Rotation direction of the rotor 1 trigo, -1 clockwise",,float,None,-1,1,,,,set_OP_from_array,,, +rot_dir,-,"Rotation direction of the rotor 1 trigo, -1 clockwise",,int,-1,-1,1,,,,set_OP_from_array,,, angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,,,, PWM,,Object to generate PWM signal,,ImportGenPWM,None,,,,,,,,, +current_dir,-,"Rotation direction of the stator currents -1 trigo, 1 clockwise",,int,1,-1,1,,,,,,, diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_angle_d_axis.py b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_angle_d_axis.py index 9052c66ae..35dbd355e 100644 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_angle_d_axis.py +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_angle_d_axis.py @@ -1,14 +1,16 @@ -from numpy import argmax, cos, abs as np_abs, angle as np_angle +from ....Functions.Load.import_class import import_class -def comp_angle_d_axis(self): +def comp_angle_d_axis(self, is_plot=False): """Compute the angle between the X axis and the first d+ axis By convention a "Tooth" is centered on the X axis Parameters ---------- - self : LamSlotMultiWind - A LamSlotMultiWind object + self : LamSlotWind + A LamSlotWind object + is_plot : bool + True to plot d axis position regarding unit mmf Returns ------- @@ -16,40 +18,9 @@ def comp_angle_d_axis(self): angle between the X axis and the first d+ axis """ - if self.winding is None or self.winding.qs == 0 or self.winding.conductor is None: - return 0 + # Call method of LamSlotWind + LamSlotWind = import_class("pyleecan.Classes", "LamSlotWind") - p = self.get_pole_pair_number() - - MMF, _ = self.comp_mmf_unit(Nt=1, Na=400 * p) - - # Get angle values - results1 = MMF.get_along("angle[oneperiod]") - angle_stator = results1["angle"] - - # Get the unit mmf FFT and wavenumbers - results = MMF.get_along("wavenumber") - wavenumber = results["wavenumber"] - mmf_ft = results[MMF.symbol] - - # Find the fundamental harmonic of MMF - indr_fund = np_abs(wavenumber - p).argmin() - phimax = np_angle(mmf_ft[indr_fund]) - magmax = np_abs(mmf_ft[indr_fund]) - - # Reconstruct fundamental MMF wave - mmf_waveform = magmax * cos(p * angle_stator + phimax) - - # Get the first angle where mmf is max - d_angle = angle_stator[argmax(mmf_waveform)] - - # import matplotlib.pyplot as plt - # from numpy import squeeze - - # fig, ax = plt.subplots() - # ax.plot(angle_stator, squeeze(MMF.get_along("angle[oneperiod]")[MMF.symbol]), "k") - # ax.plot(angle_stator, mmf_waveform, "r") - # ax.plot([d_angle, d_angle], [-magmax, magmax], "--k") - # plt.show() + d_angle = LamSlotWind.comp_angle_d_axis(self, is_plot=is_plot) return d_angle diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_dir.py b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_dir.py new file mode 100644 index 000000000..10718eaa9 --- /dev/null +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_dir.py @@ -0,0 +1,31 @@ +from ....Functions.Load.import_class import import_class + + +def comp_mmf_dir(self, felec=1, current_dir=1, is_plot=False): + """Compute the rotation direction of the fundamental magnetomotive force induced by the winding + + Parameters + ---------- + self : LamSlotMultiWind + A LamSlotMultiWind object + felec : float + Stator current frequency to consider + current_dir: int + Stator current rotation direction +/-1 + is_plot: bool + True to plot fft2 of stator MMF + + Returns + ------- + mmf_dir : int + -1 or +1 + """ + + # Call method of LamSlotWind + LamSlotWind = import_class("pyleecan.Classes", "LamSlotWind") + + rot_dir = LamSlotWind.comp_mmf_dir( + self, felec=felec, current_dir=current_dir, is_plot=is_plot + ) + + return rot_dir diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py index 013b0f785..653d666f1 100644 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py @@ -1,7 +1,7 @@ from ....Functions.Load.import_class import import_class -def comp_mmf_unit(self, Na, Nt, felec=1, rot_dir=-1, N0=1000): +def comp_mmf_unit(self, Na, Nt, felec=1, current_dir=1): """Compute the winding Unit magnetomotive force Parameters @@ -12,10 +12,10 @@ def comp_mmf_unit(self, Na, Nt, felec=1, rot_dir=-1, N0=1000): Space discretization for offline computation (otherwise use out.elec.angle) Nt : int Time discretization for offline computation (otherwise use out.elec.time) - freq : float + felec : float Stator current frequency to consider - rot_dir : int - Rotation direction (+/- 1) + current_dir: int + Stator current rotation direction +/-1 Returns ------- @@ -30,7 +30,7 @@ def comp_mmf_unit(self, Na, Nt, felec=1, rot_dir=-1, N0=1000): LamSlotWind = import_class("pyleecan.Classes", "LamSlotWind") MMF_U, WF = LamSlotWind.comp_mmf_unit( - self, Na=Na, Nt=Nt, felec=felec, rot_dir=rot_dir, N0=N0 + self, Na=Na, Nt=Nt, felec=felec, current_dir=current_dir ) return MMF_U, WF diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py deleted file mode 100644 index 99acf4e83..000000000 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_rot_dir.py +++ /dev/null @@ -1,41 +0,0 @@ -from numpy import sign - - -def comp_rot_dir(self, N0=1000, felec=1): - """Compute the rotation direction of the fundamental magnetic field induced by the winding - WARNING: rot_dir = -1 to have positive rotor rotating direction, i.e. rotor position moves towards positive angle - - Parameters - ---------- - self : LamSlotMultiWind - A LamSlotMultiWind object - - Returns - ------- - rot_dir : int - -1 or +1 - """ - p = self.get_pole_pair_number() - - # Compute unit mmf - MMF, _ = self.comp_mmf_unit( - Nt=20 * p, Na=20 * p, felec=felec, N0=N0 - ) # 20 points per pole over time and space is enough to capture rotating direction of fundamental mmf - - # Extract fundamental from unit mmf - result_p = MMF.get_harmonics(1, "freqs>0", "wavenumber=" + str(p)) - result_n = MMF.get_harmonics(1, "freqs>0", "wavenumber=" + str(-p)) - - if result_p[MMF.symbol][0] > result_n[MMF.symbol][0]: - result = result_p - else: - result = result_n - - # Get frequency and wavenumber of fundamental - f = result["freqs"][0] - r = result["wavenumber"][0] - - # Rotating direction is the sign of the mechanical speed of the magnetic field fundamental, i.e frequency over wavenumber - rot_dir = int(sign(f / r)) - - return rot_dir diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_dir.py similarity index 54% rename from pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py rename to pyleecan/Methods/Machine/LamSlotWind/comp_mmf_dir.py index 490a61c41..3851c2b9c 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_rot_dir.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_dir.py @@ -1,25 +1,32 @@ from numpy import sign -def comp_rot_dir(self, N0=1000, felec=1): - """Compute the rotation direction of the fundamental magnetic field induced by the winding - WARNING: rot_dir = -1 to have positive rotor rotating direction, i.e. rotor position moves towards positive angle +def comp_mmf_dir(self, felec=1, current_dir=1, is_plot=False): + """Compute the rotation direction of the fundamental magnetomotive force induced by the winding Parameters ---------- self : LamSlotWind A LamSlotWind object + felec : float + Stator current frequency to consider + current_dir: int + Stator current rotation direction +/-1 + is_plot: bool + True to plot fft2 of stator MMF Returns ------- - rot_dir : int + mmf_dir : int -1 or +1 """ p = self.get_pole_pair_number() # Compute unit mmf # 20 points per pole over time and space is enough to capture rotating direction of fundamental mmf - MMF, _ = self.comp_mmf_unit(Nt=20 * p, Na=20 * p, felec=felec, N0=N0) + MMF, _ = self.comp_mmf_unit( + Nt=20 * p, Na=20 * p, felec=felec, current_dir=current_dir + ) # Extract fundamental from unit mmf result_p = MMF.get_harmonics(1, "freqs>0", "wavenumber=" + str(p)) @@ -34,7 +41,11 @@ def comp_rot_dir(self, N0=1000, felec=1): f = result["freqs"][0] r = result["wavenumber"][0] - # Rotating direction is the sign of the mechanical speed of the magnetic field fundamental, i.e frequency over wavenumber - rot_dir = int(sign(f / r)) + # Rotating direction is the sign of the mechanical speed of the magnetomotive force fundamental, + # i.e frequency over wavenumber + mmf_dir = int(sign(f / r)) - return rot_dir + if is_plot: + MMF.plot_3D_Data("freqs", "wavenumber") + + return mmf_dir diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py index eec9b5368..10a96dd59 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py @@ -9,7 +9,7 @@ from ....Functions.Load.import_class import import_class -def comp_mmf_unit(self, Na=None, Nt=None, felec=1, rot_dir=-1, N0=1000): +def comp_mmf_unit(self, Na=None, Nt=None, felec=1, current_dir=1): """Compute the winding unit magnetomotive force Parameters @@ -20,8 +20,10 @@ def comp_mmf_unit(self, Na=None, Nt=None, felec=1, rot_dir=-1, N0=1000): Space discretization for offline computation Nt : int Time discretization for offline computation - freq : float + felec : float Stator current frequency to consider + current_dir: int + Stator current rotation direction +/-1 Returns ------- @@ -51,7 +53,7 @@ def comp_mmf_unit(self, Na=None, Nt=None, felec=1, rot_dir=-1, N0=1000): OPclass = OPslip InputCurrent = import_class("pyleecan.Classes", "InputCurrent") input = InputCurrent( - Na_tot=Na, Nt_tot=Nt, OP=OPclass(N0=N0, felec=felec), rot_dir=rot_dir + Na_tot=Na, Nt_tot=Nt, OP=OPclass(felec=felec), current_dir=current_dir ) axes_dict = input.comp_axes( diff --git a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py index 8ac593394..76549f7ec 100644 --- a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py +++ b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py @@ -1,11 +1,10 @@ from numpy import pi, cumsum, roll, array, unique + from SciDataTool import Norm_vector, Norm_affine -def comp_angle_rotor(self, Time): - """ - Computes the angular position of the rotor as a function of time - and set the Output.elec.angle_rotor attribute if it is None +def comp_angle_rotor(self, Time, rot_dir=-1): + """Computes the angular position of the rotor as a function of time and set it as normalization Parameters ---------- @@ -13,6 +12,8 @@ def comp_angle_rotor(self, Time): an Output object Time : Data a time axis (SciDataTool Data object) + rot_dir: int + Rotor rotating direction (by default -1: anti-clockwise) Returns ------- @@ -24,9 +25,6 @@ def comp_angle_rotor(self, Time): # Compute according to the speed Nr = self.elec.get_Nr(Time=Time) - # Get rotor rotating direction - rot_dir = self.get_rot_dir() - # Compute rotor initial angle (for synchronous machines, to align rotor d-axis and stator alpha-axis) A0 = self.get_angle_offset_initial() diff --git a/pyleecan/Methods/Output/Output/getter/get_rot_dir.py b/pyleecan/Methods/Output/Output/getter/get_rot_dir.py deleted file mode 100644 index 600473e70..000000000 --- a/pyleecan/Methods/Output/Output/getter/get_rot_dir.py +++ /dev/null @@ -1,33 +0,0 @@ -def get_rot_dir(self): - """Return the rotation direction of rotor and magnetic field fundamental - - Parameters - ---------- - self : Output - an Output object - - Returns - ------- - rot_dir: int - Rotation direction of rotor and magnetic field fundamental - - """ - - # Already available => Return - if self.geo.rot_dir is not None: - return self.geo.rot_dir - # check for imposed rot_dir in Simulation - elif ( - self.simu is not None - and self.simu.input is not None - and hasattr(self.simu.input, "rot_dir") - and self.simu.input.rot_dir is not None - ): - rot_dir = self.simu.input.rot_dir - else: # Compute from stator winding - rot_dir = self.simu.machine.stator.comp_rot_dir( - N0=self.elec.OP.get_N0(), felec=self.elec.OP.get_felec() - ) - - self.geo.rot_dir = rot_dir - return rot_dir diff --git a/pyleecan/Methods/Simulation/Input/comp_axes.py b/pyleecan/Methods/Simulation/Input/comp_axes.py index a3d1eb56b..fe2d00555 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axes.py +++ b/pyleecan/Methods/Simulation/Input/comp_axes.py @@ -124,7 +124,7 @@ def comp_axes( Time_in = None # Calculate time axis - Time = self.comp_axis_time(p, per_t, is_antiper_t, Time_in, output) + Time = self.comp_axis_time(p, per_t, is_antiper_t, Time_in) # Store time axis in dict axes_dict["time"] = Time diff --git a/pyleecan/Methods/Simulation/Input/comp_axis_time.py b/pyleecan/Methods/Simulation/Input/comp_axis_time.py index 9d88fe10c..448d77d35 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axis_time.py +++ b/pyleecan/Methods/Simulation/Input/comp_axis_time.py @@ -1,9 +1,9 @@ from numpy import pi -from SciDataTool import Data1D, DataLinspace, Norm_ref +from SciDataTool import Data1D, DataLinspace, Norm_ref, Norm_affine -def comp_axis_time(self, p, per_t, is_antiper_t, Time_in=None, output=None): +def comp_axis_time(self, p, per_t, is_antiper_t, Time_in=None): """Compute time axis, with or without periodicities and including normalizations Parameters @@ -18,8 +18,6 @@ def comp_axis_time(self, p, per_t, is_antiper_t, Time_in=None, output=None): if the time axis is antiperiodic Time_in: Data Input time axis - output: Output - An output object to calculate angle_rotor normalization Returns ------- @@ -27,25 +25,18 @@ def comp_axis_time(self, p, per_t, is_antiper_t, Time_in=None, output=None): Requested Time axis """ - logger = self.get_logger() - - # Get magnetic field rotation direction - if output is not None: - rot_dir = output.get_rot_dir() - # Get electrical fundamental frequency - f_elec = output.elec.OP.get_felec() - else: - if self.rot_dir not in [-1, 1]: - self.rot_dir = -1 - logger.debug("Enforcing input.rot_dir=-1") - rot_dir = self.rot_dir - f_elec = self.OP.get_felec() + f_elec = self.OP.get_felec(p=p) + N0 = self.OP.get_N0(p=p) + A0 = self.angle_rotor_initial # Setup normalizations for time and angle axes norm_time = { "elec_order": Norm_ref(ref=f_elec), - "mech_order": Norm_ref(ref=f_elec / p), - "angle_elec": Norm_ref(ref=rot_dir / (2 * pi * f_elec)), + "mech_order": Norm_ref(ref=N0 / 60), + "angle_elec": Norm_ref(ref=self.current_dir / (2 * pi * f_elec)), + "angle_rotor": Norm_affine( + slope=self.rot_dir * N0 * 360 / 60, offset=A0 * 180 / pi + ), } if Time_in is not None: @@ -89,14 +80,4 @@ def comp_axis_time(self, p, per_t, is_antiper_t, Time_in=None, output=None): Time.symmetries = sym_t Time = Time.to_linspace() - if output is not None: - # Compute angle_rotor (added to time normalizations) - output.comp_angle_rotor(Time) - else: - # Add default normalization - Time.normalizations["angle_rotor"] = Norm_ref( - ref=rot_dir * self.OP.N0 * 360 / 60 - ) - logger.debug("Enforcing default angle_rotor normalization to time axis") - return Time diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 591d1c5c1..0d6527e19 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -35,6 +35,8 @@ def gen_input(self): else: raise InputError("Simulation object should be inside an Output object") + logger = self.get_logger() + # Create the correct Output object outelec = OutElec() output.elec = outelec @@ -42,40 +44,38 @@ def gen_input(self): # Replace N0=0 by 0.1 rpm if self.OP.N0 == 0: self.OP.N0 = 0.1 - self.get_logger().debug("Updating N0 from 0 [rpm] to 0.1 [rpm] in gen_input") + logger.debug("Updating N0 from 0 [rpm] to 0.1 [rpm] in gen_input") # Check that felec/N0 can be computed self.OP.get_felec() output.elec.OP = self.OP - if self.angle_rotor is not None: - outelec.angle_rotor = self.angle_rotor.get_data() - if ( - not isinstance(outelec.angle_rotor, ndarray) - or len(outelec.angle_rotor.shape) != 1 - or outelec.angle_rotor.size != self.Nt_tot - ): - # angle_rotor should be a vector of same length as time - raise InputError( - "InputVoltage.angle_rotor should be a vector of the same length as time, " - + str(outelec.angle_rotor.shape) - + " shape found, " - + str(self.Nt_tot) - + " expected" - ) - - if self.rot_dir is not None: - if self.rot_dir in [-1, 1]: - # Enforce user-defined rotation direction - outgeo.rot_dir = self.rot_dir - else: - # Enforce calculation of rotation direction - outgeo.rot_dir = None - - if self.angle_rotor_initial is None: - # Enforce default initial position - outelec.angle_rotor_initial = 0 - else: - outelec.angle_rotor_initial = self.angle_rotor_initial + # Set rotor rotation direction + if self.rot_dir not in [-1, 1]: + # Enforce rotor rotation direction to -1 + logger.info("Enforcing rotor rotating direction to -1: clockwise rotation") + self.rot_dir = -1 + outgeo.rot_dir = self.rot_dir + + # Set current rotation direction + if self.current_dir not in [-1, 1]: + # Enforce current rotation direction to 1 + logger.info("Enforcing current rotating direction to 1: clockwise rotation") + self.current_dir = 1 + outgeo.current_dir = self.current_dir + + # Check if stator magnetomotive force direction is consistent with rotor direction + mmf_dir = simu.machine.stator.comp_mmf_dir(felec=1, current_dir=self.current_dir) + if mmf_dir != -self.rot_dir: + logger.debug( + "Reverse current rotation direction so that stator mmf rotates in same direction as rotor" + ) + self.current_dir = -self.current_dir + + # Set rotor initial angular position + if self.angle_rotor_initial in [0, None]: + # Calculate initial position according to machine properties + self.angle_rotor_initial = simu.machine.comp_angle_offset_initial() + output.geo.angle_offset_initial = self.angle_rotor_initial # Calculate time, angle and phase axes and store them in OutGeo outgeo.axes_dict = self.comp_axes( @@ -95,7 +95,7 @@ def gen_input(self): if self.PWM is not None: # Fill generator with simu data felec = self.OP.get_felec() - rot_dir = output.get_rot_dir() + rot_dir = outgeo.rot_dir qs = simu.machine.stator.winding.qs self.PWM.f = felec self.PWM.qs = qs diff --git a/pyleecan/Methods/Simulation/OPdq/get_N0.py b/pyleecan/Methods/Simulation/OPdq/get_N0.py index 4536ff257..0fd056001 100644 --- a/pyleecan/Methods/Simulation/OPdq/get_N0.py +++ b/pyleecan/Methods/Simulation/OPdq/get_N0.py @@ -1,7 +1,7 @@ from ....Methods.Simulation.Input import InputError -def get_N0(self): +def get_N0(self, p=None): """Returns the Rotor speed Parameters @@ -22,10 +22,12 @@ def get_N0(self): if self.felec is None: raise InputError("OPdq object can't have felec and N0 both None") - machine = self.get_machine_from_parent() - if machine is None: - raise InputError("OPdq object can't find machine parent to compute N0") + if p is None: + machine = self.get_machine_from_parent() + if machine is None: + raise InputError("OPdq object can't find machine parent to compute N0") + p = machine.get_pole_pair_number() - p = machine.get_pole_pair_number() self.N0 = self.felec * 60 / p + return self.N0 diff --git a/pyleecan/Methods/Simulation/OPdq/get_felec.py b/pyleecan/Methods/Simulation/OPdq/get_felec.py index 12fd346d5..b4c43de4f 100644 --- a/pyleecan/Methods/Simulation/OPdq/get_felec.py +++ b/pyleecan/Methods/Simulation/OPdq/get_felec.py @@ -1,7 +1,7 @@ from ....Methods.Simulation.Input import InputError -def get_felec(self): +def get_felec(self, p=None): """Returns the electrical frequency Parameters @@ -22,10 +22,13 @@ def get_felec(self): if self.N0 is None: raise InputError("OPdq object can't have felec and N0 both None") - machine = self.get_machine_from_parent() - if machine is None: - raise InputError("OPdq object can't find machine parent to compute felec") + if p is None: + machine = self.get_machine_from_parent() + if machine is None: + raise InputError("OPdq object can't find machine parent to compute felec") + + p = machine.get_pole_pair_number() - p = machine.get_pole_pair_number() self.felec = self.N0 * p / 60 + return self.felec From df26e603d7d1e69ea9321156d47f44c3195038cc Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 27 Oct 2021 12:57:52 +0200 Subject: [PATCH 144/167] [CO] Better account for stator mmf rotating direction by introducing perm_phases in outelec in case mmf_dir is not consistent with rot_dir [CO] Improve validation test to check calculation of current/rotor direction for Toyota_Prius and Protean_InWheel (reverse winding pattern) --- .../test_coordinate_transformation.py | 25 ++-- Tests/Methods/Simulation/test_mmf_dir.py | 136 ++++++++++++++++++ .../Simulation/test_rot_dir_dq_axis.py | 106 -------------- pyleecan/Classes/Class_Dict.json | 22 +-- pyleecan/Classes/InputCurrent.py | 2 +- pyleecan/Classes/InputFlux.py | 2 +- pyleecan/Classes/InputVoltage.py | 4 +- pyleecan/Classes/OutElec.py | 55 +++++++ pyleecan/Classes/OutGeo.py | 32 ----- .../Electrical/coordinate_transformation.py | 18 ++- .../Generator/ClassesRef/Output/OutElec.csv | 1 + .../Generator/ClassesRef/Output/OutGeo.csv | 1 - .../ClassesRef/Simulation/InputVoltage.csv | 2 +- .../Machine/LamSlotMultiWind/comp_mmf_dir.py | 6 +- .../Machine/LamSlotWind/comp_mmf_dir.py | 8 +- .../Machine/LamSlotWind/comp_mmf_unit.py | 16 ++- pyleecan/Methods/Output/OutElec/get_I_fund.py | 6 +- pyleecan/Methods/Output/OutElec/get_Us.py | 3 +- .../Simulation/Input/comp_axis_time.py | 2 +- .../Simulation/InputVoltage/gen_input.py | 15 +- 20 files changed, 266 insertions(+), 196 deletions(-) create mode 100644 Tests/Methods/Simulation/test_mmf_dir.py delete mode 100644 Tests/Methods/Simulation/test_rot_dir_dq_axis.py diff --git a/Tests/Functions/test_coordinate_transformation.py b/Tests/Functions/test_coordinate_transformation.py index 288b164b4..4a0b920d6 100644 --- a/Tests/Functions/test_coordinate_transformation.py +++ b/Tests/Functions/test_coordinate_transformation.py @@ -3,7 +3,7 @@ from numpy import pi, array, linspace, zeros, cos, mean, sqrt, sin from numpy.testing import assert_array_almost_equal -from SciDataTool import DataTime, Data1D, Norm_ref +from SciDataTool import DataTime, Data1D, Norm_ref, Norm_affine from pyleecan.Functions.Electrical.coordinate_transformation import ( n2dqh, @@ -30,18 +30,21 @@ def test_coordinate_transformation(param_dict): Nt = 100 f_elec = 1 p = 1 + N0 = f_elec / p qs = param_dict["qs"] rot_dir = param_dict["rot_dir"] + current_dir = -rot_dir time = linspace(0, 1 / f_elec, Nt, endpoint=False) - angle_elec = rot_dir * 2 * pi * f_elec * time + angle_elec = current_dir * 2 * pi * f_elec * time # Time axis for plots norm_time = { "elec_order": Norm_ref(ref=f_elec), - "mech_order": Norm_ref(ref=f_elec / p), - "angle_elec": Norm_ref(ref=rot_dir / (2 * pi * f_elec)), + "mech_order": Norm_ref(ref=N0 / 60), + "angle_elec": Norm_ref(ref=current_dir / (2 * pi * f_elec)), + "angle_rotor": Norm_affine(slope=rot_dir * N0 * 360 / 60, offset=0), } Time = Data1D(name="time", unit="s", values=time, normalizations=norm_time) @@ -60,12 +63,16 @@ def test_coordinate_transformation(param_dict): for phase in phase_list: In = zeros((Nt, qs)) for ii in range(qs): - In[:, ii] = cos(angle_elec + phase - 2 * ii * pi / qs) + In[:, ii] = cos(angle_elec + phase + rot_dir * 2 * ii * pi / qs) - Idqh_rms = n2dqh(In, angle_elec, is_dqh_rms=True) - Idqh_amp = n2dqh(In, angle_elec, is_dqh_rms=False) - In_check_rms = dqh2n(Idqh_rms, angle_elec, n=qs, is_n_rms=False) - In_check_amp = dqh2n(Idqh_amp, angle_elec, n=qs, is_n_rms=True) + Idqh_rms = n2dqh(In, angle_elec, is_dqh_rms=True, is_clockwise=rot_dir < 0) + Idqh_amp = n2dqh(In, angle_elec, is_dqh_rms=False, is_clockwise=rot_dir < 0) + In_check_rms = dqh2n( + Idqh_rms, angle_elec, n=qs, is_n_rms=False, is_clockwise=rot_dir < 0 + ) + In_check_amp = dqh2n( + Idqh_amp, angle_elec, n=qs, is_n_rms=True, is_clockwise=rot_dir < 0 + ) assert_array_almost_equal( mean(Idqh_rms, axis=0), diff --git a/Tests/Methods/Simulation/test_mmf_dir.py b/Tests/Methods/Simulation/test_mmf_dir.py new file mode 100644 index 000000000..a1d8acf4e --- /dev/null +++ b/Tests/Methods/Simulation/test_mmf_dir.py @@ -0,0 +1,136 @@ +import pytest +from numpy import pi +from numpy.testing import assert_almost_equal +from os.path import join +from multiprocessing import cpu_count + +from pyleecan.Classes.MagFEMM import MagFEMM +from pyleecan.Classes.OPdq import OPdq +from pyleecan.Classes.Simu1 import Simu1 +from pyleecan.Classes.InputVoltage import InputVoltage +from pyleecan.Classes.InputCurrent import InputCurrent + +from pyleecan.Functions.load import load +from pyleecan.definitions import DATA_DIR + + +param_list = [ + { + "name": "Toyota_Prius", + "resistance": 0.035951, + "stator_d_axis": 1.3076, + "rotor_d_axis": pi / 8, + "mmf_dir": 1, + "Tem_av": 368.04, + }, + { + "name": "Protean_InWheel", + "resistance": 0.000733, + "stator_d_axis": 0.09817, + "rotor_d_axis": pi / 64, + "mmf_dir": -1, + "Tem_av": 806.31, + }, +] + +is_show_fig = True + + +@pytest.mark.parametrize("param_dict", param_list) +def test_mmf_dir(param_dict, nb_worker=int(cpu_count() / 2)): + + machine = load(join(DATA_DIR, "Machine", param_dict["name"] + ".json")) + + p = machine.get_pole_pair_number() + + current_dir_ref = InputVoltage().current_dir + + # Check that resistance computation is correct + resistance = machine.stator.comp_resistance_wind() + assert resistance == pytest.approx(param_dict["resistance"], abs=0.0001) + + # Check that the DQ axis are correct for the stator + d_axis = machine.stator.comp_angle_d_axis() + assert d_axis == pytest.approx(param_dict["stator_d_axis"], abs=0.001) + q_axis = machine.stator.comp_angle_q_axis() + assert q_axis == pytest.approx(param_dict["stator_d_axis"] + pi / 2 / p, abs=0.001) + + # Check that the DQ axis are correct for the rotor + d_axis = machine.rotor.comp_angle_d_axis() + assert d_axis == pytest.approx(param_dict["rotor_d_axis"], abs=0.0001) + q_axis = machine.rotor.comp_angle_q_axis() + assert q_axis == pytest.approx(param_dict["rotor_d_axis"] + pi / 2 / p, abs=0.0001) + + # Check mmf rotating direction with current rotating in default + mmf_dir = machine.stator.comp_mmf_dir(is_plot=False, current_dir=current_dir_ref) + assert mmf_dir == param_dict["mmf_dir"] + + # Check mmf rotating direction by reversing current rotating direction + mmf_dir_inv = machine.stator.comp_mmf_dir( + is_plot=False, current_dir=-current_dir_ref + ) + assert ( + mmf_dir_inv == -mmf_dir + ), "mmf_dir_inv should be opposite when reversing current rotating direction" + + # Check mmf rotating direction by reversing current rotating direction + machine_B = machine.copy() + machine_B.stator.winding.is_reverse_wind = ( + not machine_B.stator.winding.is_reverse_wind + ) + mmf_dir_rev_wind = machine_B.stator.comp_mmf_dir( + is_plot=False, current_dir=current_dir_ref + ) + assert ( + mmf_dir_rev_wind == -mmf_dir + ), "mmf_dir_inv should be opposite when reversing machine winding pattern" + + # Check torque sign with MagFEMM calculation + if param_dict["Tem_av"] is not None: + simu = Simu1(name="test_mmf_dir_" + machine.name, machine=machine) + + # Compute time and space (anti-)periodicities from the machine + per_a, is_aper_a = machine.comp_periodicity_spatial() + per_t, is_aper_t, _, _ = machine.comp_periodicity_time() + + per_a = int(2 * per_a) if is_aper_a else per_a + per_t = int(2 * per_t) if is_aper_t else per_t + + simu.input = InputCurrent( + OP=OPdq(Id_ref=-100, Iq_ref=200, N0=1000), # arbitrary current + Nt_tot=4 * nb_worker * per_t, + Na_tot=200 * per_a, + ) + + # Definition of the MagFEMM simulation + simu.mag = MagFEMM( + is_periodicity_a=True, + is_periodicity_t=True, + nb_worker=nb_worker, + Kmesh_fineness=0.5, # reduce mesh size to speed up calculation + ) + + # Run simulation + out = simu.run() + + assert out.mag.Tem_av == pytest.approx(param_dict["Tem_av"], abs=0.01) + + if is_show_fig: + + out.mag.Tem.plot_2D_Data("time") + + out.mag.B.components["radial"].plot_3D_Data("time", "angle") + + out.mag.B.components["radial"].plot_3D_Data("freqs", "wavenumber") + + out.elec.get_Is().plot_2D_Data("time", "phase[]") + + return out + + +if __name__ == "__main__": + + # for param_dict in param_list: + # out = test_mmf_dir(param_dict) + + out = test_mmf_dir(param_list[1]) diff --git a/Tests/Methods/Simulation/test_rot_dir_dq_axis.py b/Tests/Methods/Simulation/test_rot_dir_dq_axis.py deleted file mode 100644 index c67c65b74..000000000 --- a/Tests/Methods/Simulation/test_rot_dir_dq_axis.py +++ /dev/null @@ -1,106 +0,0 @@ -import pytest -from numpy import pi -from os.path import join -from multiprocessing import cpu_count - -from pyleecan.Classes.MagFEMM import MagFEMM -from pyleecan.Classes.OPdq import OPdq -from pyleecan.Classes.Simu1 import Simu1 -from pyleecan.Classes.InputCurrent import InputCurrent - -from pyleecan.Functions.load import load -from pyleecan.definitions import DATA_DIR - - -param_list = [ - { - "name": "Toyota_Prius", - "resistance": 0.035951, - "stator_d_axis": 1.3076, - "rotor_d_axis": pi / 8, - "rot_dir": 1, - }, - { - "name": "PR0178_machine_new", - "resistance": 0.035951, - "stator_d_axis": 1.3076, - "rotor_d_axis": pi / 8, - "rot_dir": 1, - }, -] - - -@pytest.mark.parametrize("param_dict", param_list) -def test_rot_dir_dq_axis(param_dict, nb_worker=int(cpu_count() / 2)): - - machine = load(join(DATA_DIR, "Machine", param_dict["name"] + ".json")) - - p = machine.get_pole_pair_number() - - # # Check that resistance computation is correct - # result = machine.stator.comp_resistance_wind() - # assert result == pytest.approx(param_dict["resistance"], abs=0.00001) - - # # Check that the DQ axis are correct for the stator - # d_axis = machine.stator.comp_angle_d_axis() - # assert d_axis == pytest.approx(param_dict["stator_d_axis"], abs=0.001) - - # q_axis = machine.stator.comp_angle_q_axis() - # assert q_axis == pytest.approx(param_dict["stator_d_axis"] + pi / 2 / p, abs=0.001) - - # # Check that the DQ axis are correct for the rotor - # d_axis = machine.rotor.comp_angle_d_axis() - # assert d_axis == pytest.approx(param_dict["rotor_d_axis"], abs=0.0001) - # q_axis = machine.rotor.comp_angle_q_axis() - # assert q_axis == pytest.approx(param_dict["rotor_d_axis"] + pi / 2 / p, abs=0.0001) - - rot_dir = machine.stator.comp_mmf_dir(is_plot=True) - # assert rot_dir == param_dict["rot_dir"], ( - # "rot_dir=" + str(rot_dir) + " instead of " + str(param_dict["rot_dir"]) - # ) - - machine_B = machine.copy() - machine_B.stator.winding.is_reverse_wind = ( - not machine_B.stator.winding.is_reverse_wind - ) - rot_dir_inv = machine_B.stator.comp_mmf_dir() - assert rot_dir_inv == -rot_dir - - simu = Simu1(name="test_rot_dir_dq_axis_" + machine.name, machine=machine) - - # Compute time and space (anti-)periodicities from the machine - per_a, is_aper_a = machine.comp_periodicity_spatial() - per_t, is_aper_t, _, _ = machine.comp_periodicity_time() - - per_a = int(2 * per_a) if is_aper_a else per_a - per_t = int(2 * per_t) if is_aper_t else per_t - - simu.input = InputCurrent( - OP=OPdq(Id_ref=-50, Iq_ref=150, N0=1000), - # OP=OPdq(Id_ref=0, Iq_ref=0, N0=1000), - Nt_tot=10 * nb_worker * per_t, - Na_tot=200 * per_a, - ) - - # Definition of the magnetic simulation (only one OP in MLUT => direct calculation) - simu.mag = MagFEMM( - is_periodicity_a=True, - is_periodicity_t=True, - nb_worker=nb_worker, - ) - - out = simu.run() - - out.elec.get_Is().plot_2D_Data("time", "phase[]") - - out.mag.Tem.plot_2D_Data("time") - - out.mag.B.components["radial"].plot_3D_Data("time", "angle") - - out.mag.B.components["radial"].plot_3D_Data("freqs", "wavenumber") - - return out - - -if __name__ == "__main__": - out = test_rot_dir_dq_axis(param_list[1]) diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 40632e841..020597525 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4307,13 +4307,13 @@ "value": null }, { - "desc": "Rotation direction of the stator currents -1 trigo, 1 clockwise", + "desc": "Rotation direction of the stator currents 1 trigo, -1 clockwise", "max": "1", "min": "-1", "name": "current_dir", "type": "int", "unit": "-", - "value": 1 + "value": -1 } ] }, @@ -8274,6 +8274,15 @@ "type": "SciDataTool.Classes.DataND.DataND", "unit": "A", "value": "None" + }, + { + "desc": "Phase permutation array to reverse rotating direction of stator mmf fundamental ", + "max": "", + "min": "", + "name": "perm_phases", + "type": "ndarray", + "unit": "", + "value": null } ] }, @@ -8502,15 +8511,6 @@ "unit": "-", "value": null }, - { - "desc": "rotation direction of the stator magnetomotive force\u00c2\u00a0: mmf_dir=1 by default (counter clockwise rotation)", - "max": "1", - "min": "-1", - "name": "mmf_dir", - "type": "int", - "unit": "-", - "value": null - }, { "desc": "rotation direction of the stator currents: current_dir=1 by default (counter clockwise rotation)", "max": "1", diff --git a/pyleecan/Classes/InputCurrent.py b/pyleecan/Classes/InputCurrent.py index 0cb4b2128..dbbd4ff8a 100644 --- a/pyleecan/Classes/InputCurrent.py +++ b/pyleecan/Classes/InputCurrent.py @@ -97,7 +97,7 @@ def __init__( rot_dir=-1, angle_rotor_initial=0, PWM=None, - current_dir=1, + current_dir=-1, time=None, angle=None, Nt_tot=2048, diff --git a/pyleecan/Classes/InputFlux.py b/pyleecan/Classes/InputFlux.py index e30dad827..0b53016cc 100644 --- a/pyleecan/Classes/InputFlux.py +++ b/pyleecan/Classes/InputFlux.py @@ -69,7 +69,7 @@ def __init__( rot_dir=-1, angle_rotor_initial=0, PWM=None, - current_dir=1, + current_dir=-1, time=None, angle=None, Nt_tot=2048, diff --git a/pyleecan/Classes/InputVoltage.py b/pyleecan/Classes/InputVoltage.py index ddd939bc9..85dbb2f70 100644 --- a/pyleecan/Classes/InputVoltage.py +++ b/pyleecan/Classes/InputVoltage.py @@ -79,7 +79,7 @@ def __init__( rot_dir=-1, angle_rotor_initial=0, PWM=None, - current_dir=1, + current_dir=-1, time=None, angle=None, Nt_tot=2048, @@ -396,7 +396,7 @@ def _set_current_dir(self, value): current_dir = property( fget=_get_current_dir, fset=_set_current_dir, - doc=u"""Rotation direction of the stator currents -1 trigo, 1 clockwise + doc=u"""Rotation direction of the stator currents 1 trigo, -1 clockwise :Type: int :min: -1 diff --git a/pyleecan/Classes/OutElec.py b/pyleecan/Classes/OutElec.py index cc4f48c5f..4cba707de 100644 --- a/pyleecan/Classes/OutElec.py +++ b/pyleecan/Classes/OutElec.py @@ -150,6 +150,7 @@ def __init__( Pem_av_ref=None, Tem_av_ref=None, Is_harm=None, + perm_phases=None, init_dict=None, init_str=None, ): @@ -196,6 +197,8 @@ def __init__( Tem_av_ref = init_dict["Tem_av_ref"] if "Is_harm" in list(init_dict.keys()): Is_harm = init_dict["Is_harm"] + if "perm_phases" in list(init_dict.keys()): + perm_phases = init_dict["perm_phases"] # Set the properties (value check and convertion are done in setter) self.parent = None self.axes_dict = axes_dict @@ -212,6 +215,7 @@ def __init__( self.Pem_av_ref = Pem_av_ref self.Tem_av_ref = Tem_av_ref self.Is_harm = Is_harm + self.perm_phases = perm_phases # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -254,6 +258,13 @@ def __str__(self): OutElec_str += "Pem_av_ref = " + str(self.Pem_av_ref) + linesep OutElec_str += "Tem_av_ref = " + str(self.Tem_av_ref) + linesep OutElec_str += "Is_harm = " + str(self.Is_harm) + linesep + linesep + OutElec_str += ( + "perm_phases = " + + linesep + + str(self.perm_phases).replace(linesep, linesep + "\t") + + linesep + + linesep + ) return OutElec_str def __eq__(self, other): @@ -289,6 +300,8 @@ def __eq__(self, other): return False if other.Is_harm != self.Is_harm: return False + if not array_equal(other.perm_phases, self.perm_phases): + return False return True def compare(self, other, name="self", ignore_list=None): @@ -372,6 +385,8 @@ def compare(self, other, name="self", ignore_list=None): diff_list.extend( self.Is_harm.compare(other.Is_harm, name=name + ".Is_harm") ) + if not array_equal(other.perm_phases, self.perm_phases): + diff_list.append(name + ".perm_phases") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -396,6 +411,7 @@ def __sizeof__(self): S += getsizeof(self.Pem_av_ref) S += getsizeof(self.Tem_av_ref) S += getsizeof(self.Is_harm) + S += getsizeof(self.perm_phases) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -497,6 +513,19 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): keep_function=keep_function, **kwargs ) + if self.perm_phases is None: + OutElec_dict["perm_phases"] = None + else: + if type_handle_ndarray == 0: + OutElec_dict["perm_phases"] = self.perm_phases.tolist() + elif type_handle_ndarray == 1: + OutElec_dict["perm_phases"] = self.perm_phases.copy() + elif type_handle_ndarray == 2: + OutElec_dict["perm_phases"] = self.perm_phases + else: + raise Exception( + "Unknown type_handle_ndarray: " + str(type_handle_ndarray) + ) # The class name is added to the dict for deserialisation purpose OutElec_dict["__class__"] = "OutElec" return OutElec_dict @@ -520,6 +549,7 @@ def _set_None(self): self.Pem_av_ref = None self.Tem_av_ref = None self.Is_harm = None + self.perm_phases = None def _get_axes_dict(self): """getter of axes_dict""" @@ -859,3 +889,28 @@ def _set_Is_harm(self, value): :Type: SciDataTool.Classes.DataND.DataND """, ) + + def _get_perm_phases(self): + """getter of perm_phases""" + return self._perm_phases + + def _set_perm_phases(self, value): + """setter of perm_phases""" + if type(value) is int and value == -1: + value = array([]) + elif type(value) is list: + try: + value = array(value) + except: + pass + check_var("perm_phases", value, "ndarray") + self._perm_phases = value + + perm_phases = property( + fget=_get_perm_phases, + fset=_set_perm_phases, + doc=u"""Phase permutation array to reverse rotating direction of stator mmf fundamental + + :Type: ndarray + """, + ) diff --git a/pyleecan/Classes/OutGeo.py b/pyleecan/Classes/OutGeo.py index d8a958a13..d57ff3afc 100644 --- a/pyleecan/Classes/OutGeo.py +++ b/pyleecan/Classes/OutGeo.py @@ -48,7 +48,6 @@ def __init__( axes_dict=None, per_t_R=None, is_antiper_t_R=None, - mmf_dir=None, current_dir=None, init_dict=None, init_str=None, @@ -100,8 +99,6 @@ def __init__( per_t_R = init_dict["per_t_R"] if "is_antiper_t_R" in list(init_dict.keys()): is_antiper_t_R = init_dict["is_antiper_t_R"] - if "mmf_dir" in list(init_dict.keys()): - mmf_dir = init_dict["mmf_dir"] if "current_dir" in list(init_dict.keys()): current_dir = init_dict["current_dir"] # Set the properties (value check and convertion are done in setter) @@ -122,7 +119,6 @@ def __init__( self.axes_dict = axes_dict self.per_t_R = per_t_R self.is_antiper_t_R = is_antiper_t_R - self.mmf_dir = mmf_dir self.current_dir = current_dir # The class is frozen, for now it's impossible to add new properties @@ -162,7 +158,6 @@ def __str__(self): OutGeo_str += "axes_dict = " + str(self.axes_dict) + linesep + linesep OutGeo_str += "per_t_R = " + str(self.per_t_R) + linesep OutGeo_str += "is_antiper_t_R = " + str(self.is_antiper_t_R) + linesep - OutGeo_str += "mmf_dir = " + str(self.mmf_dir) + linesep OutGeo_str += "current_dir = " + str(self.current_dir) + linesep return OutGeo_str @@ -203,8 +198,6 @@ def __eq__(self, other): return False if other.is_antiper_t_R != self.is_antiper_t_R: return False - if other.mmf_dir != self.mmf_dir: - return False if other.current_dir != self.current_dir: return False return True @@ -270,8 +263,6 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".per_t_R") if other._is_antiper_t_R != self._is_antiper_t_R: diff_list.append(name + ".is_antiper_t_R") - if other._mmf_dir != self._mmf_dir: - diff_list.append(name + ".mmf_dir") if other._current_dir != self._current_dir: diff_list.append(name + ".current_dir") # Filter ignore differences @@ -300,7 +291,6 @@ def __sizeof__(self): S += getsizeof(value) + getsizeof(key) S += getsizeof(self.per_t_R) S += getsizeof(self.is_antiper_t_R) - S += getsizeof(self.mmf_dir) S += getsizeof(self.current_dir) return S @@ -358,7 +348,6 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): OutGeo_dict["axes_dict"][key] = None OutGeo_dict["per_t_R"] = self.per_t_R OutGeo_dict["is_antiper_t_R"] = self.is_antiper_t_R - OutGeo_dict["mmf_dir"] = self.mmf_dir OutGeo_dict["current_dir"] = self.current_dir # The class name is added to the dict for deserialisation purpose OutGeo_dict["__class__"] = "OutGeo" @@ -385,7 +374,6 @@ def _set_None(self): self.axes_dict = None self.per_t_R = None self.is_antiper_t_R = None - self.mmf_dir = None self.current_dir = None def _get_stator(self): @@ -715,26 +703,6 @@ def _set_is_antiper_t_R(self, value): """, ) - def _get_mmf_dir(self): - """getter of mmf_dir""" - return self._mmf_dir - - def _set_mmf_dir(self, value): - """setter of mmf_dir""" - check_var("mmf_dir", value, "int", Vmin=-1, Vmax=1) - self._mmf_dir = value - - mmf_dir = property( - fget=_get_mmf_dir, - fset=_set_mmf_dir, - doc=u"""rotation direction of the stator magnetomotive force : mmf_dir=1 by default (counter clockwise rotation) - - :Type: int - :min: -1 - :max: 1 - """, - ) - def _get_current_dir(self): """getter of current_dir""" return self._current_dir diff --git a/pyleecan/Functions/Electrical/coordinate_transformation.py b/pyleecan/Functions/Electrical/coordinate_transformation.py index dadd3d16b..e7cf04217 100644 --- a/pyleecan/Functions/Electrical/coordinate_transformation.py +++ b/pyleecan/Functions/Electrical/coordinate_transformation.py @@ -3,6 +3,7 @@ from SciDataTool import Data1D, DataTime from ...Functions.Winding.gen_phase_list import gen_name +from ...Functions.Load.import_class import import_class def n2dqh_DataTime(data_n, is_dqh_rms=True): @@ -30,8 +31,8 @@ def n2dqh_DataTime(data_n, is_dqh_rms=True): raise Exception("DataTime object should contain time as first axis") if data_n.axes[1].name != "phase": raise Exception("DataTime object should contain phase as second axis") - - # TODO: add check on angle_elec normalization + if "angle_elec" not in data_n.axes[0].normalizations: + raise Exception("Time axis should contain angle_elec normalization") # Get values for one time period converted in electrical angle and for all phases result = data_n.get_along("time[oneperiod]->angle_elec", "phase") @@ -100,8 +101,8 @@ def dqh2n_DataTime(data_dqh, n, is_n_rms=False): raise Exception("DataTime object should contain time as first axis") if data_dqh.axes[1].name != "phase": raise Exception("DataTime object should contain phase as second axis") - - # TODO: add check on angle_elec normalization + if "angle_elec" not in data_dqh.axes[0].normalizations: + raise Exception("Time axis should contain angle_elec normalization") # Get values for one time period converted in electrical angle and for all phases result = data_dqh.get_along("time[oneperiod]->angle_elec", "phase") @@ -161,6 +162,7 @@ def n2dqh(Z_n, angle_elec, is_dqh_rms=True): Z_dqh : ndarray transformed matrix (N x 3) of dqh equivalent values """ + Z_dqh = abc2dqh(n2abc(Z_n), angle_elec) if is_dqh_rms: @@ -329,11 +331,15 @@ def comp_Clarke_transform(n, is_inv=False): ------- mat : ndarray Clarke transform matrix of size (n, 3) - """ + # Take phase rotation direction from default current rotation direction in InputVoltage + InputVoltage = import_class("pyleecan.Classes", "InputVoltage") + input = InputVoltage() + current_dir_ref = input.current_dir + # Phasor depending on fundamental field rotation direction - phasor = np.linspace(0, -2 * np.pi * (n - 1) / n, n) + phasor = np.linspace(0, current_dir_ref * 2 * np.pi * (n - 1) / n, n) # Clarke transformation matrix if is_inv: diff --git a/pyleecan/Generator/ClassesRef/Output/OutElec.csv b/pyleecan/Generator/ClassesRef/Output/OutElec.csv index 2e95f68bb..7cbbfa616 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutElec.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutElec.csv @@ -13,3 +13,4 @@ OP,-,Operating Point,,OP,None,,,,,,,,, Pem_av_ref,W,Theorical Average Electromagnetic Power,1,float,None,,,,,,,,, Tem_av_ref,N.m,Theorical Average Electromagnetic torque,,float,None,,,,,,,,, Is_harm,A,Harmonic stator current ,,SciDataTool.Classes.DataND.DataND,None,,,,,,,,, +perm_phases,,Phase permutation array to reverse rotating direction of stator mmf fundamental ,,ndarray,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Output/OutGeo.csv b/pyleecan/Generator/ClassesRef/Output/OutGeo.csv index 0c9cafaeb..d49758f14 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutGeo.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutGeo.csv @@ -15,5 +15,4 @@ is_antiper_t_S,-,True if an time anti-periodicity is possible after the periodic axes_dict,,Dict containing axes data without periodicities used for plots and to have simulation full time/angle vectors,,{SciDataTool.Classes.DataND.Data},None,,,,,,,,, per_t_R,-,Number of time periodicities of the machine in rotating referential,0,int,None,,,,,,,,, is_antiper_t_R,-,True if an time anti-periodicity is possible after the periodicities in rotating referential,0,bool,None,,,,,,,,, -mmf_dir,-,rotation direction of the stator magnetomotive force : mmf_dir=1 by default (counter clockwise rotation),0,int,None,-1,1,,,,,,, current_dir,-,rotation direction of the stator currents: current_dir=1 by default (counter clockwise rotation),0,int,None,-1,1,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv index 8c69e573c..cf4dd2bf4 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv @@ -3,4 +3,4 @@ angle_rotor,rad,Rotor angular position as a function of time (if None computed a rot_dir,-,"Rotation direction of the rotor 1 trigo, -1 clockwise",,int,-1,-1,1,,,,set_OP_from_array,,, angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,,,, PWM,,Object to generate PWM signal,,ImportGenPWM,None,,,,,,,,, -current_dir,-,"Rotation direction of the stator currents -1 trigo, 1 clockwise",,int,1,-1,1,,,,,,, +current_dir,-,"Rotation direction of the stator currents 1 trigo, -1 clockwise",,int,-1,-1,1,,,,,,, diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_dir.py b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_dir.py index 10718eaa9..d592660e0 100644 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_dir.py +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_dir.py @@ -1,7 +1,7 @@ from ....Functions.Load.import_class import import_class -def comp_mmf_dir(self, felec=1, current_dir=1, is_plot=False): +def comp_mmf_dir(self, current_dir, is_plot=False): """Compute the rotation direction of the fundamental magnetomotive force induced by the winding Parameters @@ -24,8 +24,6 @@ def comp_mmf_dir(self, felec=1, current_dir=1, is_plot=False): # Call method of LamSlotWind LamSlotWind = import_class("pyleecan.Classes", "LamSlotWind") - rot_dir = LamSlotWind.comp_mmf_dir( - self, felec=felec, current_dir=current_dir, is_plot=is_plot - ) + rot_dir = LamSlotWind.comp_mmf_dir(self, current_dir, is_plot) return rot_dir diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_dir.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_dir.py index 3851c2b9c..17cfbbff2 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_dir.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_dir.py @@ -1,15 +1,13 @@ from numpy import sign -def comp_mmf_dir(self, felec=1, current_dir=1, is_plot=False): +def comp_mmf_dir(self, current_dir=None, is_plot=False): """Compute the rotation direction of the fundamental magnetomotive force induced by the winding Parameters ---------- self : LamSlotWind A LamSlotWind object - felec : float - Stator current frequency to consider current_dir: int Stator current rotation direction +/-1 is_plot: bool @@ -24,9 +22,7 @@ def comp_mmf_dir(self, felec=1, current_dir=1, is_plot=False): # Compute unit mmf # 20 points per pole over time and space is enough to capture rotating direction of fundamental mmf - MMF, _ = self.comp_mmf_unit( - Nt=20 * p, Na=20 * p, felec=felec, current_dir=current_dir - ) + MMF, _ = self.comp_mmf_unit(Nt=20 * p, Na=20 * p, current_dir=current_dir) # Extract fundamental from unit mmf result_p = MMF.get_harmonics(1, "freqs>0", "wavenumber=" + str(p)) diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py index 10a96dd59..b906d0605 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py @@ -9,8 +9,8 @@ from ....Functions.Load.import_class import import_class -def comp_mmf_unit(self, Na=None, Nt=None, felec=1, current_dir=1): - """Compute the winding unit magnetomotive force +def comp_mmf_unit(self, Na=None, Nt=None, felec=1, current_dir=None): + """Compute the winding unit magnetomotive force for given inputs Parameters ---------- @@ -51,10 +51,14 @@ def comp_mmf_unit(self, Na=None, Nt=None, felec=1, current_dir=1): OPclass = OPdq else: OPclass = OPslip - InputCurrent = import_class("pyleecan.Classes", "InputCurrent") - input = InputCurrent( - Na_tot=Na, Nt_tot=Nt, OP=OPclass(felec=felec), current_dir=current_dir - ) + InputVoltage = import_class("pyleecan.Classes", "InputVoltage") + input = InputVoltage(Na_tot=Na, Nt_tot=Nt, OP=OPclass(felec=felec)) + if current_dir is not None: + if current_dir in [-1, 1]: + # Enforce input current_dir otherwise keep it as default + input.current_dir = current_dir + else: + raise Exception("Cannot enforce current_dir other than +1 or -1") axes_dict = input.comp_axes( axes_list=["time", "angle", "phase_S", "phase_R"], diff --git a/pyleecan/Methods/Output/OutElec/get_I_fund.py b/pyleecan/Methods/Output/OutElec/get_I_fund.py index e01f1923f..83ba30f51 100644 --- a/pyleecan/Methods/Output/OutElec/get_I_fund.py +++ b/pyleecan/Methods/Output/OutElec/get_I_fund.py @@ -26,8 +26,8 @@ def get_I_fund(self, Time=None): qs = self.parent.simu.machine.stator.winding.qs stator_label = "phase_" + self.parent.simu.machine.stator.get_label() felec = self.OP.get_felec() - Phase = self.axes_dict[stator_label] + perm_phases = self.perm_phases if self.Is is None: I_dict = self.OP.get_Id_Iq() @@ -48,7 +48,7 @@ def get_I_fund(self, Time=None): unit="A", symbol="I_s", axes=[Time, Phase], - values=Is, + values=Is[:, perm_phases], ) else: @@ -56,7 +56,7 @@ def get_I_fund(self, Time=None): Is_val = result[self.Is.symbol] freqs = result["freqs"] ifund = where(isclose(freqs, felec)) - Is_fund = Is_val[ifund, :] + Is_fund = Is_val[ifund, perm_phases] Freq = Data1D( name="freqs", diff --git a/pyleecan/Methods/Output/OutElec/get_Us.py b/pyleecan/Methods/Output/OutElec/get_Us.py index 28c64b05f..cf34b14f8 100644 --- a/pyleecan/Methods/Output/OutElec/get_Us.py +++ b/pyleecan/Methods/Output/OutElec/get_Us.py @@ -27,6 +27,7 @@ def get_Us(self): angle_elec = Time.get_values(is_smallestperiod=True, normalization="angle_elec") qs = self.parent.simu.machine.stator.winding.qs stator_label = "phase_" + self.parent.simu.machine.stator.get_label() + perm_phases = self.perm_phases # Switch from dqh to abc referential Us = dqh2n(Usdqh, angle_elec, n=qs) @@ -36,6 +37,6 @@ def get_Us(self): unit="V", symbol="Us", axes=[Time.copy(), self.axes_dict[stator_label].copy()], - values=Us, + values=Us[:, perm_phases], ) return self.Us diff --git a/pyleecan/Methods/Simulation/Input/comp_axis_time.py b/pyleecan/Methods/Simulation/Input/comp_axis_time.py index 448d77d35..43f89ebb8 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axis_time.py +++ b/pyleecan/Methods/Simulation/Input/comp_axis_time.py @@ -35,7 +35,7 @@ def comp_axis_time(self, p, per_t, is_antiper_t, Time_in=None): "mech_order": Norm_ref(ref=N0 / 60), "angle_elec": Norm_ref(ref=self.current_dir / (2 * pi * f_elec)), "angle_rotor": Norm_affine( - slope=self.rot_dir * N0 * 360 / 60, offset=A0 * 180 / pi + slope=self.rot_dir * N0 * 360 / 60, offset=-self.rot_dir * A0 * 180 / pi ), } diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 0d6527e19..3c6b34c2f 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -47,7 +47,7 @@ def gen_input(self): logger.debug("Updating N0 from 0 [rpm] to 0.1 [rpm] in gen_input") # Check that felec/N0 can be computed self.OP.get_felec() - output.elec.OP = self.OP + outelec.OP = self.OP # Set rotor rotation direction if self.rot_dir not in [-1, 1]: @@ -63,13 +63,18 @@ def gen_input(self): self.current_dir = 1 outgeo.current_dir = self.current_dir + # Init permutation array of stator currents + qs = simu.machine.stator.winding.qs + perm_phases = arange(qs) # Check if stator magnetomotive force direction is consistent with rotor direction - mmf_dir = simu.machine.stator.comp_mmf_dir(felec=1, current_dir=self.current_dir) + mmf_dir = simu.machine.stator.comp_mmf_dir(current_dir=self.current_dir) if mmf_dir != -self.rot_dir: - logger.debug( - "Reverse current rotation direction so that stator mmf rotates in same direction as rotor" + # Switch two last phases to reverse rotation direction + perm_phases[-1], perm_phases[-2] = perm_phases[-2], perm_phases[-1] + logger.info( + "Reverse the two last stator current phases to reverse rotation direction of stator mmf fundamental according to rotor direction" ) - self.current_dir = -self.current_dir + outelec.perm_phases = perm_phases # Set rotor initial angular position if self.angle_rotor_initial in [0, None]: From 39d0ae7389fcbcaa87a8f8696b36a554357304fc Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 27 Oct 2021 12:58:28 +0200 Subject: [PATCH 145/167] [BC] Revert last commit: mean is calculated directly instead of calling SciDataTool --- pyleecan/Methods/Output/OutMag/store.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pyleecan/Methods/Output/OutMag/store.py b/pyleecan/Methods/Output/OutMag/store.py index 3e44d3aee..8b30ab403 100644 --- a/pyleecan/Methods/Output/OutMag/store.py +++ b/pyleecan/Methods/Output/OutMag/store.py @@ -1,4 +1,4 @@ -from numpy import mean, max as np_max, min as np_min +from numpy import max as np_max, min as np_min, mean as np_mean from SciDataTool import DataTime, VectorField, Data1D @@ -78,16 +78,15 @@ def store(self, out_dict, axes_dict): self.Tem = self.Tem_slice.get_data_along("time[smallestperiod]", "z=integrate") # Calculate average torque in Nm - Tem_values = self.Tem.get_along("time[smallestperiod]")[self.Tem.symbol] - self.Tem_av = mean(Tem_values) - self.get_logger().debug("Average Torque: " + str(self.Tem_av) + " N.m") + self.Tem_av = np_mean(self.Tem.values) + # self.Tem_av = float(self.Tem.get_along("time=mean")[self.Tem.symbol]) - # Calculate peak to peak torque in absolute value Nm - self.Tem_rip_pp = abs(np_max(Tem_values) - np_min(Tem_values)) # [N.m] + # Calculate peak to peak torque in absolute value [N.m] + self.Tem_rip_pp = abs(np_max(self.Tem.values) - np_min(self.Tem.values)) - # Calculate torque ripple in percentage + # Calculate torque ripple in percentage [%] if self.Tem_av != 0: - self.Tem_rip_norm = self.Tem_rip_pp / self.Tem_av # [] + self.Tem_rip_norm = self.Tem_rip_pp / self.Tem_av else: self.Tem_rip_norm = None From 1861b906bbb3378721efa860c88dd3718722caef Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 27 Oct 2021 12:59:31 +0200 Subject: [PATCH 146/167] [CC] Minor updates on comp_angle_rotor and get_angle_rotor methods that are no longer used since angle_rotor is now a normalization of time axis --- .../Output/Output/getter/comp_angle_rotor.py | 8 ++++++-- .../Methods/Output/Output/getter/get_angle_rotor.py | 13 +++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py index 76549f7ec..c4129baea 100644 --- a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py +++ b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py @@ -3,8 +3,9 @@ from SciDataTool import Norm_vector, Norm_affine -def comp_angle_rotor(self, Time, rot_dir=-1): +def comp_angle_rotor(self, Time): """Computes the angular position of the rotor as a function of time and set it as normalization + (should not happen since angle_rotor is already added in Time normalizations in Input.comp_axis_time) Parameters ---------- @@ -13,7 +14,7 @@ def comp_angle_rotor(self, Time, rot_dir=-1): Time : Data a time axis (SciDataTool Data object) rot_dir: int - Rotor rotating direction (by default -1: anti-clockwise) + Rotor rotating direction (by default -1: clockwise) Returns ------- @@ -22,6 +23,9 @@ def comp_angle_rotor(self, Time, rot_dir=-1): """ + # Get rotor rotating direction + rot_dir = self.geo.rot_dir + # Compute according to the speed Nr = self.elec.get_Nr(Time=Time) diff --git a/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py b/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py index b62c566af..d95cfc4a9 100644 --- a/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py +++ b/pyleecan/Methods/Output/Output/getter/get_angle_rotor.py @@ -2,9 +2,8 @@ def get_angle_rotor(self, Time=None): - """ - Return the angular position of the rotor as a function of time - and set the Output.elec.angle_rotor attribute if it is None + """Return the angular position of the rotor as a function of time from Time normalizations + and or calculate angle_rotor and add it to Time normalization Parameters ---------- @@ -27,14 +26,16 @@ def get_angle_rotor(self, Time=None): elif self.mag.axes_dict is not None and "time" in self.mag.axes_dict: Time = self.mag.axes_dict["time"] else: - logger = self.get_logger() - logger.error("No time axis, cannot compute rotor angle") + raise Exception("No time axis given, cannot compute rotor angle") if "angle_rotor" in Time.normalizations: # angle rotor is stored in degrees as normalization of Time axis angle_rotor = Time.get_values(normalization="angle_rotor") * pi / 180 else: - # compute angle rotor from time axis + # Recalculate rotor angular position over time (should not happen) + self.get_logger().warning( + "angle_rotor not in time normalizations, recalculate rotor angular position over time and add it to normalizations" + ) angle_rotor = self.comp_angle_rotor(Time) return angle_rotor From 746dc40a9e6dc4bd9b276e78556191a2e7cbc240 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 27 Oct 2021 12:59:55 +0200 Subject: [PATCH 147/167] [CC] few code cleaning --- .../Output/getter/get_angle_offset_initial.py | 3 --- .../Methods/Simulation/MagFEMM/comp_flux_airgap.py | 13 ++++++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/pyleecan/Methods/Output/Output/getter/get_angle_offset_initial.py b/pyleecan/Methods/Output/Output/getter/get_angle_offset_initial.py index ebf8d2091..dca5fa7d7 100644 --- a/pyleecan/Methods/Output/Output/getter/get_angle_offset_initial.py +++ b/pyleecan/Methods/Output/Output/getter/get_angle_offset_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - def get_angle_offset_initial(self): """Return the difference between the d axis angle of the stator and the rotor diff --git a/pyleecan/Methods/Simulation/MagFEMM/comp_flux_airgap.py b/pyleecan/Methods/Simulation/MagFEMM/comp_flux_airgap.py index 1ab34a6c2..e16bde755 100644 --- a/pyleecan/Methods/Simulation/MagFEMM/comp_flux_airgap.py +++ b/pyleecan/Methods/Simulation/MagFEMM/comp_flux_airgap.py @@ -1,13 +1,15 @@ from numpy import zeros -from ....Functions.labels import STATOR_LAB -from ....Functions.FEMM.draw_FEMM import draw_FEMM +from SciDataTool import Data1D + from ....Classes._FEMMHandler import _FEMMHandler from ....Classes.OutMagFEMM import OutMagFEMM + +from ....Functions.labels import STATOR_LAB +from ....Functions.FEMM.draw_FEMM import draw_FEMM from ....Functions.MeshSolution.build_solution_data import build_solution_data from ....Functions.MeshSolution.build_meshsolution import build_meshsolution from ....Functions.MeshSolution.build_solution_vector import build_solution_vector -from SciDataTool import Data1D def comp_flux_airgap(self, output, axes_dict, Is=None, Ir=None): @@ -72,9 +74,6 @@ def comp_flux_airgap(self, output, axes_dict, Is=None, Ir=None): # Get rotor angular position angle_rotor = output.get_angle_rotor()[0:Nt] - # Interpolate current on magnetic model time axis - # Get stator current from elec out - # Setup the FEMM simulation # Geometry building and assigning property in FEMM # Instanciate a new FEMM @@ -167,7 +166,7 @@ def comp_flux_airgap(self, output, axes_dict, Is=None, Ir=None): if STATOR_LAB + "-0" in out_dict["Phi_wind"].keys(): out_dict["Phi_wind_stator"] = out_dict["Phi_wind"][STATOR_LAB + "-0"] - # Store mesh data & solution + # Store mesh data & solution if self.is_get_meshsolution and B_elem is not None: # Define axis From 6c330f91740d9bbde98fe5b82dd20f95c3096872 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 27 Oct 2021 13:00:22 +0200 Subject: [PATCH 148/167] [CO] Add p as argument of get_N0 and get_felec if machine is None --- pyleecan/Methods/Simulation/OPdq/get_N0.py | 2 ++ pyleecan/Methods/Simulation/OPdq/get_felec.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pyleecan/Methods/Simulation/OPdq/get_N0.py b/pyleecan/Methods/Simulation/OPdq/get_N0.py index 0fd056001..a41bd0a59 100644 --- a/pyleecan/Methods/Simulation/OPdq/get_N0.py +++ b/pyleecan/Methods/Simulation/OPdq/get_N0.py @@ -8,6 +8,8 @@ def get_N0(self, p=None): ---------- self : OPdq An OPdq object + p: int + pole pair number Returns ------- diff --git a/pyleecan/Methods/Simulation/OPdq/get_felec.py b/pyleecan/Methods/Simulation/OPdq/get_felec.py index b4c43de4f..766d2790d 100644 --- a/pyleecan/Methods/Simulation/OPdq/get_felec.py +++ b/pyleecan/Methods/Simulation/OPdq/get_felec.py @@ -8,6 +8,8 @@ def get_felec(self, p=None): ---------- self : OPdq An OPdq object + p: int + pole pair number Returns ------- From dcebc38741b41c892179ea225578ff23f0773c2f Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 27 Oct 2021 14:06:36 +0200 Subject: [PATCH 149/167] [CO] Improve test_dqh_transformation with different configurations other than pyleecan convention [NF] Add phase_dir as parameter of functions in dqh_transformation.py to enforce a different direction of phase distribution than given by current rotating direction [CC] Rename coordinate_transformation to dqh_transformation for more clarity --- .../test_coordinate_transformation.py | 110 ----------------- Tests/Functions/test_dqh_transformation.py | 112 ++++++++++++++++++ Tests/Methods/Simulation/test_mmf_dir.py | 16 ++- .../Electrical/test_EEC_ELUT_PMSM.py | 2 +- ...ransformation.py => dqh_transformation.py} | 59 ++++++--- .../Machine/LamSlotWind/comp_mmf_unit.py | 2 +- pyleecan/Methods/Output/OutElec/get_I_fund.py | 2 +- pyleecan/Methods/Output/OutElec/get_Us.py | 2 +- .../Methods/Output/OutElec/get_Us_harm.py | 2 +- pyleecan/Methods/Output/OutElec/store.py | 2 +- pyleecan/Methods/Output/OutMag/comp_emf.py | 5 - .../Methods/Simulation/EEC_LSRPM/gen_drive.py | 2 +- .../Methods/Simulation/EEC_PMSM/gen_drive.py | 2 +- .../Simulation/EEC_SCIM/comp_joule_losses.py | 2 +- .../Simulation/EEC_SCIM/comp_parameters.py | 2 +- .../Methods/Simulation/EEC_SCIM/gen_drive.py | 2 +- .../FluxLinkFEMM/comp_fluxlinkage.py | 2 +- .../Simulation/IndMagFEMM/comp_inductance.py | 2 +- .../Simulation/InputCurrent/gen_input.py | 2 +- .../Simulation/InputVoltage/gen_input.py | 2 +- .../Simulation/LUTdq/get_Phidqh_mag.py | 2 +- .../Simulation/LUTdq/get_Phidqh_mean.py | 2 +- 22 files changed, 181 insertions(+), 155 deletions(-) delete mode 100644 Tests/Functions/test_coordinate_transformation.py create mode 100644 Tests/Functions/test_dqh_transformation.py rename pyleecan/Functions/Electrical/{coordinate_transformation.py => dqh_transformation.py} (78%) diff --git a/Tests/Functions/test_coordinate_transformation.py b/Tests/Functions/test_coordinate_transformation.py deleted file mode 100644 index 4a0b920d6..000000000 --- a/Tests/Functions/test_coordinate_transformation.py +++ /dev/null @@ -1,110 +0,0 @@ -import pytest - -from numpy import pi, array, linspace, zeros, cos, mean, sqrt, sin -from numpy.testing import assert_array_almost_equal - -from SciDataTool import DataTime, Data1D, Norm_ref, Norm_affine - -from pyleecan.Functions.Electrical.coordinate_transformation import ( - n2dqh, - dqh2n, - n2dqh_DataTime, - dqh2n_DataTime, -) -from pyleecan.Functions.Winding.gen_phase_list import gen_name - -param_list = [ - {"qs": 3, "rot_dir": -1}, - {"qs": 3, "rot_dir": 1}, - {"qs": 6, "rot_dir": -1}, - {"qs": 11, "rot_dir": -1}, -] - -is_show_fig = False - - -@pytest.mark.parametrize("param_dict", param_list) -def test_coordinate_transformation(param_dict): - """Check that the coordinate transformations can return a correct output""" - - Nt = 100 - f_elec = 1 - p = 1 - N0 = f_elec / p - - qs = param_dict["qs"] - rot_dir = param_dict["rot_dir"] - current_dir = -rot_dir - - time = linspace(0, 1 / f_elec, Nt, endpoint=False) - angle_elec = current_dir * 2 * pi * f_elec * time - - # Time axis for plots - norm_time = { - "elec_order": Norm_ref(ref=f_elec), - "mech_order": Norm_ref(ref=N0 / 60), - "angle_elec": Norm_ref(ref=current_dir / (2 * pi * f_elec)), - "angle_rotor": Norm_affine(slope=rot_dir * N0 * 360 / 60, offset=0), - } - Time = Data1D(name="time", unit="s", values=time, normalizations=norm_time) - - angle_elec_bis = Time.get_values(is_smallestperiod=True, normalization="angle_elec") - assert_array_almost_equal(angle_elec, angle_elec_bis) - - # Phase axis for plots - Phase = Data1D( - name="phase", - unit="", - values=gen_name(qs), - is_components=True, - ) - - phase_list = array([0, 45, 90, 180, 270]) * pi / 180 - for phase in phase_list: - In = zeros((Nt, qs)) - for ii in range(qs): - In[:, ii] = cos(angle_elec + phase + rot_dir * 2 * ii * pi / qs) - - Idqh_rms = n2dqh(In, angle_elec, is_dqh_rms=True, is_clockwise=rot_dir < 0) - Idqh_amp = n2dqh(In, angle_elec, is_dqh_rms=False, is_clockwise=rot_dir < 0) - In_check_rms = dqh2n( - Idqh_rms, angle_elec, n=qs, is_n_rms=False, is_clockwise=rot_dir < 0 - ) - In_check_amp = dqh2n( - Idqh_amp, angle_elec, n=qs, is_n_rms=True, is_clockwise=rot_dir < 0 - ) - - assert_array_almost_equal( - mean(Idqh_rms, axis=0), - array([cos(phase) / sqrt(2), sin(phase) / sqrt(2), 0]), - ) - - assert_array_almost_equal(Idqh_rms * sqrt(2), Idqh_amp) - - assert_array_almost_equal(In, In_check_rms) - assert_array_almost_equal(In, In_check_amp) - - In_data = DataTime( - name="Stator current", - unit="A", - symbol="I_s", - axes=[Time, Phase], - values=In, - ) - - I_dqh_data = n2dqh_DataTime(In_data) - In_data_check = dqh2n_DataTime(I_dqh_data, n=qs) - - if is_show_fig: - In_data.plot_2D_Data("time", "phase[]") - I_dqh_data.plot_2D_Data("time", "phase[]") - - pass - - -if __name__ == "__main__": - - for param_dict in param_list: - test_coordinate_transformation(param_dict) - - # test_coordinate_transformation(param_list[2]) diff --git a/Tests/Functions/test_dqh_transformation.py b/Tests/Functions/test_dqh_transformation.py new file mode 100644 index 000000000..fe9640938 --- /dev/null +++ b/Tests/Functions/test_dqh_transformation.py @@ -0,0 +1,112 @@ +import pytest + +from numpy import pi, array, linspace, zeros, cos, mean, sqrt, sin +from numpy.testing import assert_array_almost_equal + +from SciDataTool import DataTime, Data1D, Norm_ref + +from pyleecan.Functions.Electrical.dqh_transformation import ( + n2dqh, + dqh2n, + n2dqh_DataTime, + dqh2n_DataTime, +) +from pyleecan.Functions.Winding.gen_phase_list import gen_name + +param_list = [ + {"qs": 3, "current_dir": -1, "phase_dir": -1}, # pyleecan convention, 3 phases + {"qs": 3, "current_dir": 1, "phase_dir": 1}, # other convention, 3 phases + {"qs": 3, "current_dir": 1, "phase_dir": -1}, # other convention, 3 phases + {"qs": 6, "current_dir": -1, "phase_dir": -1}, # pyleecan convention, 6 phases + {"qs": 11, "current_dir": -1, "phase_dir": -1}, # pyleecan convention, 1 phases +] + +is_show_fig = False + + +@pytest.mark.parametrize("param_dict", param_list) +def test_dqh_transformation(param_dict): + """Check that the dqh transformations can return a correct output""" + + Nt = 100 + f_elec = 1 # assume unit frequency + + # Get parameters for current test case + qs = param_dict["qs"] + current_dir = param_dict["current_dir"] # current direction + phase_dir = param_dict["phase_dir"] # phase direction + + # Define time and electrical angle arrays + time = linspace(0, 1 / f_elec, Nt, endpoint=False) + angle_elec = current_dir * 2 * pi * f_elec * time + + # Time axis for plots including angle_elec normalization used for DQH (cf Input.comp_axis_time()) + norm_time = {"angle_elec": Norm_ref(ref=current_dir / (2 * pi * f_elec))} + Time = Data1D(name="time", unit="s", values=time, normalizations=norm_time) + + # Check that values stored in Time data object agrees with initial values + angle_elec_bis = Time.get_values(is_smallestperiod=True, normalization="angle_elec") + assert_array_almost_equal(angle_elec, angle_elec_bis) + + # Phase axis for plots + Phase = Data1D( + name="phase", + unit="", + values=gen_name(qs), + is_components=True, + ) + + # Calculate dqh transform for several current angles + angle_curr_list = array([0, 45, 90, 180, 270]) * pi / 180 + for angle_curr in angle_curr_list: + In = zeros((Nt, qs)) + for ii in range(qs): + # current dir is included in angle_elec while phase_dir is related + # to the one enforced in Clarke transform + In[:, ii] = cos(angle_elec + angle_curr + phase_dir * 2 * ii * pi / qs) + + # check Idqh calculated with RMS convention (pyleecan convention) + Idqh_rms = n2dqh(In, angle_elec, is_dqh_rms=True, phase_dir=phase_dir) + assert_array_almost_equal( + mean(Idqh_rms, axis=0), + array([cos(angle_curr) / sqrt(2), sin(angle_curr) / sqrt(2), 0]), + ) + + # check Idqh calculated with peak convention (other convention) + Idqh_peak = n2dqh(In, angle_elec, is_dqh_rms=False, phase_dir=phase_dir) + assert_array_almost_equal(Idqh_rms * sqrt(2), Idqh_peak) + + # check In calculated from Idqh with rms convention (pyleecan convention) + In_rms = dqh2n(Idqh_rms, angle_elec, n=qs, is_n_rms=False, phase_dir=phase_dir) + assert_array_almost_equal(In, In_rms) + + # check In calculated from Idqh with peak convention (other convention) + In_peak = dqh2n(Idqh_peak, angle_elec, n=qs, is_n_rms=True, phase_dir=phase_dir) + assert_array_almost_equal(In, In_peak) + + # Test calculation dqh transform directly applied to DataND objects + In_data = DataTime( + name="Stator current", + unit="A", + symbol="I_s", + axes=[Time, Phase], + values=In, + ) + I_dqh_data = n2dqh_DataTime(In_data, phase_dir=phase_dir) + assert_array_almost_equal(I_dqh_data.values, Idqh_rms) + In_data1 = dqh2n_DataTime(I_dqh_data, n=qs, phase_dir=phase_dir) + assert_array_almost_equal(In_data1.values, In_rms) + + if is_show_fig: + In_data.plot_2D_Data("time", "phase[]") + I_dqh_data.plot_2D_Data("time", "phase[]") + + pass + + +if __name__ == "__main__": + + for param_dict in param_list: + test_dqh_transformation(param_dict) + + # test_dqh_transformation(param_list[2]) diff --git a/Tests/Methods/Simulation/test_mmf_dir.py b/Tests/Methods/Simulation/test_mmf_dir.py index a1d8acf4e..50f0055c8 100644 --- a/Tests/Methods/Simulation/test_mmf_dir.py +++ b/Tests/Methods/Simulation/test_mmf_dir.py @@ -1,7 +1,9 @@ import pytest + from numpy import pi -from numpy.testing import assert_almost_equal + from os.path import join + from multiprocessing import cpu_count from pyleecan.Classes.MagFEMM import MagFEMM @@ -11,6 +13,7 @@ from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Functions.load import load + from pyleecan.definitions import DATA_DIR @@ -35,9 +38,11 @@ is_show_fig = True - +@pytest.mark.long_5s +@pytest.mark.MagFEMM @pytest.mark.parametrize("param_dict", param_list) def test_mmf_dir(param_dict, nb_worker=int(cpu_count() / 2)): + """test calculation of stator mmf rotating direction according to winding pattern and rotor direction""" machine = load(join(DATA_DIR, "Machine", param_dict["name"] + ".json")) @@ -113,6 +118,7 @@ def test_mmf_dir(param_dict, nb_worker=int(cpu_count() / 2)): # Run simulation out = simu.run() + # Check torque value assert out.mag.Tem_av == pytest.approx(param_dict["Tem_av"], abs=0.01) if is_show_fig: @@ -130,7 +136,7 @@ def test_mmf_dir(param_dict, nb_worker=int(cpu_count() / 2)): if __name__ == "__main__": - # for param_dict in param_list: - # out = test_mmf_dir(param_dict) + for param_dict in param_list: + out = test_mmf_dir(param_dict) - out = test_mmf_dir(param_list[1]) + # out = test_mmf_dir(param_list[1]) diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py index 2bd1231bc..d93065637 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py @@ -22,7 +22,7 @@ from pyleecan.Functions.load import load from pyleecan.Functions.Plot import dict_2D -from pyleecan.Functions.Electrical.coordinate_transformation import n2dqh_DataTime +from pyleecan.Functions.Electrical.dqh_transformation import n2dqh_DataTime from pyleecan.definitions import DATA_DIR from Tests import save_validation_path as save_path diff --git a/pyleecan/Functions/Electrical/coordinate_transformation.py b/pyleecan/Functions/Electrical/dqh_transformation.py similarity index 78% rename from pyleecan/Functions/Electrical/coordinate_transformation.py rename to pyleecan/Functions/Electrical/dqh_transformation.py index e7cf04217..ade47c253 100644 --- a/pyleecan/Functions/Electrical/coordinate_transformation.py +++ b/pyleecan/Functions/Electrical/dqh_transformation.py @@ -6,7 +6,7 @@ from ...Functions.Load.import_class import import_class -def n2dqh_DataTime(data_n, is_dqh_rms=True): +def n2dqh_DataTime(data_n, is_dqh_rms=True, phase_dir=None): """n phases to dqh equivalent coordinate transformation of DataTime object Parameters @@ -15,6 +15,9 @@ def n2dqh_DataTime(data_n, is_dqh_rms=True): data object containing values over time and phase axes is_dqh_rms : boolean True to return dq currents in rms value (Pyleecan convention), False to return peak values + phase_dir: int + direction of phase distribution: +/-1 (-1 clockwise) to enforce + (it can be different from current direction given by slope of angle_elec) Returns ------- @@ -40,7 +43,7 @@ def n2dqh_DataTime(data_n, is_dqh_rms=True): angle_elec = result["time"] # Convert values to dqh frame - data_dqh_val = n2dqh(data_n_val, angle_elec, is_dqh_rms=is_dqh_rms) + data_dqh_val = n2dqh(data_n_val, angle_elec, is_dqh_rms, phase_dir) # Get time axis on one period per_t, is_aper_t = data_n.axes[0].get_periodicity() @@ -75,7 +78,7 @@ def n2dqh_DataTime(data_n, is_dqh_rms=True): return data_dqh -def dqh2n_DataTime(data_dqh, n, is_n_rms=False): +def dqh2n_DataTime(data_dqh, n, is_n_rms=False, phase_dir=None): """dqh to n phase coordinate transformation of DataTime object Parameters @@ -86,6 +89,9 @@ def dqh2n_DataTime(data_dqh, n, is_n_rms=False): number of phases is_n_rms : boolean True to return n currents in rms value, False to return peak values (Pyleecan convention) + phase_dir: int + direction of phase distribution: +/-1 (-1 clockwise) to enforce + (it can be different from current direction given by slope of angle_elec) Returns ------- @@ -110,7 +116,7 @@ def dqh2n_DataTime(data_dqh, n, is_n_rms=False): angle_elec = result["time"] # Convert values to dqh frame - data_n_val = dqh2n(data_dqh_val, angle_elec, n=n, is_n_rms=is_n_rms) + data_n_val = dqh2n(data_dqh_val, angle_elec, n, is_n_rms, phase_dir) # Get time axis on one period per_t, is_aper_t = data_dqh.axes[0].get_periodicity() @@ -145,7 +151,7 @@ def dqh2n_DataTime(data_dqh, n, is_n_rms=False): return data_n -def n2dqh(Z_n, angle_elec, is_dqh_rms=True): +def n2dqh(Z_n, angle_elec, is_dqh_rms=True, phase_dir=None): """n phases to dqh equivalent coordinate transformation Parameters @@ -156,6 +162,9 @@ def n2dqh(Z_n, angle_elec, is_dqh_rms=True): angle of the rotor coordinate system is_dqh_rms : boolean True to return dq currents in rms value (Pyleecan convention), False to return peak values + phase_dir: int + direction of phase distribution: +/-1 (-1 clockwise) to enforce + (it can be different from current direction given by slope of angle_elec) Returns ------- @@ -163,7 +172,7 @@ def n2dqh(Z_n, angle_elec, is_dqh_rms=True): transformed matrix (N x 3) of dqh equivalent values """ - Z_dqh = abc2dqh(n2abc(Z_n), angle_elec) + Z_dqh = abc2dqh(n2abc(Z_n, phase_dir), angle_elec) if is_dqh_rms: # Divide by sqrt(2) to go from (Id_peak, Iq_peak) to (Id_rms, Iq_rms) @@ -172,7 +181,7 @@ def n2dqh(Z_n, angle_elec, is_dqh_rms=True): return Z_dqh -def dqh2n(Z_dqh, angle_elec, n, is_n_rms=False): +def dqh2n(Z_dqh, angle_elec, n, is_n_rms=False, phase_dir=None): """dqh to n phase coordinate transformation Parameters @@ -185,6 +194,9 @@ def dqh2n(Z_dqh, angle_elec, n, is_n_rms=False): number of phases is_n_rms : boolean True to return n currents in rms value, False to return peak values (Pyleecan convention) + phase_dir: int + direction of phase distribution: +/-1 (-1 clockwise) to enforce + (it can be different from current direction given by slope of angle_elec) Returns ------- @@ -192,7 +204,7 @@ def dqh2n(Z_dqh, angle_elec, n, is_n_rms=False): transformed matrix (N x n) of n phase values """ - Z_n = abc2n(dqh2abc(Z_dqh, angle_elec), n) + Z_n = abc2n(dqh2abc(Z_dqh, angle_elec), n, phase_dir) if not is_n_rms: # Multiply by sqrt(2) to from (I_n_rms) to (I_n_peak) @@ -201,7 +213,7 @@ def dqh2n(Z_dqh, angle_elec, n, is_n_rms=False): return Z_n -def abc2n(Z_abc, n=3): +def abc2n(Z_abc, n=3, phase_dir=None): """3 phase equivalent to n phase coordinate transformation, i.e. Clarke transformation Parameters @@ -210,6 +222,9 @@ def abc2n(Z_abc, n=3): matrix (N x 3) of 3 phase equivalent values in alpha-beta-gamma frame n: int number of phases + phase_dir: int + direction of phase distribution: +/-1 (-1 clockwise) to enforce + (it can be different from current direction given by slope of angle_elec) Returns ------- @@ -219,20 +234,23 @@ def abc2n(Z_abc, n=3): """ # Inverse of Clarke transformation matrix - ab_2_n = comp_Clarke_transform(n, is_inv=True) + ab_2_n = comp_Clarke_transform(n, is_inv=True, phase_dir=phase_dir) Z_n = np.matmul(Z_abc, ab_2_n) return Z_n -def n2abc(Z_n): +def n2abc(Z_n, phase_dir=None): """n phase to 3 phases equivalent coordinate transformation, i.e. Clarke transformation Parameters ---------- Z_n : ndarray matrix (N x n) of n phase values + phase_dir: int + direction of phase distribution: +/-1 (-1 clockwise) to enforce + (it can be different from current direction given by slope of angle_elec) Returns ------- @@ -243,7 +261,7 @@ def n2abc(Z_n): n = Z_n.shape[1] - n_2_abc = comp_Clarke_transform(n, is_inv=False) + n_2_abc = comp_Clarke_transform(n, is_inv=False, phase_dir=phase_dir) Z_abc = np.matmul(Z_n, n_2_abc) @@ -317,7 +335,7 @@ def dqh2abc(Z_dqh, angle_elec): return Z_abc -def comp_Clarke_transform(n, is_inv=False): +def comp_Clarke_transform(n, is_inv=False, phase_dir=None): """Compute Clarke transformation for given number of phases and rotating direction of phases Parameters @@ -326,6 +344,9 @@ def comp_Clarke_transform(n, is_inv=False): number of phases is_inv: bool False to return Clarke transform, True to return inverse of Clarke transform + phase_dir: int + direction of phase distribution: +/-1 (-1 clockwise) to enforce + (it can be different from current direction given by slope of angle_elec) Returns ------- @@ -333,13 +354,15 @@ def comp_Clarke_transform(n, is_inv=False): Clarke transform matrix of size (n, 3) """ - # Take phase rotation direction from default current rotation direction in InputVoltage - InputVoltage = import_class("pyleecan.Classes", "InputVoltage") - input = InputVoltage() - current_dir_ref = input.current_dir + if phase_dir is None: + # Take phase rotation direction from default current rotation direction in InputVoltage + InputVoltage = import_class("pyleecan.Classes", "InputVoltage") + phase_dir = InputVoltage().current_dir + elif phase_dir not in [-1, 1]: + raise Exception("Cannot enforce phase_dir other than +1 or -1") # Phasor depending on fundamental field rotation direction - phasor = np.linspace(0, current_dir_ref * 2 * np.pi * (n - 1) / n, n) + phasor = np.linspace(0, phase_dir * 2 * np.pi * (n - 1) / n, n) # Clarke transformation matrix if is_inv: diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py index b906d0605..5cef6d5cb 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py @@ -5,7 +5,7 @@ from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.OPslip import OPslip -from ....Functions.Electrical.coordinate_transformation import dqh2n +from ....Functions.Electrical.dqh_transformation import dqh2n from ....Functions.Load.import_class import import_class diff --git a/pyleecan/Methods/Output/OutElec/get_I_fund.py b/pyleecan/Methods/Output/OutElec/get_I_fund.py index 83ba30f51..76f9d0027 100644 --- a/pyleecan/Methods/Output/OutElec/get_I_fund.py +++ b/pyleecan/Methods/Output/OutElec/get_I_fund.py @@ -2,7 +2,7 @@ from SciDataTool import Data1D, DataTime, DataFreq -from ....Functions.Electrical.coordinate_transformation import dqh2n +from ....Functions.Electrical.dqh_transformation import dqh2n def get_I_fund(self, Time=None): diff --git a/pyleecan/Methods/Output/OutElec/get_Us.py b/pyleecan/Methods/Output/OutElec/get_Us.py index cf34b14f8..d82dfd419 100644 --- a/pyleecan/Methods/Output/OutElec/get_Us.py +++ b/pyleecan/Methods/Output/OutElec/get_Us.py @@ -2,7 +2,7 @@ from SciDataTool import DataTime -from ....Functions.Electrical.coordinate_transformation import dqh2n +from ....Functions.Electrical.dqh_transformation import dqh2n def get_Us(self): diff --git a/pyleecan/Methods/Output/OutElec/get_Us_harm.py b/pyleecan/Methods/Output/OutElec/get_Us_harm.py index a8a5c9b9d..f75aa2480 100644 --- a/pyleecan/Methods/Output/OutElec/get_Us_harm.py +++ b/pyleecan/Methods/Output/OutElec/get_Us_harm.py @@ -1,4 +1,4 @@ -from ....Functions.Electrical.coordinate_transformation import n2dqh_DataTime +from ....Functions.Electrical.dqh_transformation import n2dqh_DataTime def get_Us_harm(self, is_dqh=True): diff --git a/pyleecan/Methods/Output/OutElec/store.py b/pyleecan/Methods/Output/OutElec/store.py index df10934ef..6ffa4a693 100644 --- a/pyleecan/Methods/Output/OutElec/store.py +++ b/pyleecan/Methods/Output/OutElec/store.py @@ -3,7 +3,7 @@ from SciDataTool import DataFreq -from pyleecan.Functions.Electrical.coordinate_transformation import dqh2n_DataTime +from pyleecan.Functions.Electrical.dqh_transformation import dqh2n_DataTime def store(self, out_dict, out_dict_harm): diff --git a/pyleecan/Methods/Output/OutMag/comp_emf.py b/pyleecan/Methods/Output/OutMag/comp_emf.py index 412ff8aa9..361fb3b5d 100644 --- a/pyleecan/Methods/Output/OutMag/comp_emf.py +++ b/pyleecan/Methods/Output/OutMag/comp_emf.py @@ -1,8 +1,3 @@ -from numpy import diff, zeros, newaxis, pi - -from ....Functions.Electrical.coordinate_transformation import n2dqh - - def comp_emf(self): """Compute the Electromotive force [V] diff --git a/pyleecan/Methods/Simulation/EEC_LSRPM/gen_drive.py b/pyleecan/Methods/Simulation/EEC_LSRPM/gen_drive.py index 087921e6c..2a8ffb1e0 100644 --- a/pyleecan/Methods/Simulation/EEC_LSRPM/gen_drive.py +++ b/pyleecan/Methods/Simulation/EEC_LSRPM/gen_drive.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from ....Functions.Electrical.coordinate_transformation import n2dqh +from ....Functions.Electrical.dqh_transformation import n2dqh from numpy import split, transpose, mean, pi import matplotlib.pyplot as plt diff --git a/pyleecan/Methods/Simulation/EEC_PMSM/gen_drive.py b/pyleecan/Methods/Simulation/EEC_PMSM/gen_drive.py index 56bdf55be..98b3f5d83 100644 --- a/pyleecan/Methods/Simulation/EEC_PMSM/gen_drive.py +++ b/pyleecan/Methods/Simulation/EEC_PMSM/gen_drive.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from ....Functions.Electrical.coordinate_transformation import n2dqh +from ....Functions.Electrical.dqh_transformation import n2dqh from numpy import split, transpose, mean, pi import matplotlib.pyplot as plt diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py b/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py index cbbf33c7f..bc1464a5d 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/comp_joule_losses.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from numpy import mean, zeros -from ....Functions.Electrical.coordinate_transformation import n2abc +from ....Functions.Electrical.dqh_transformation import n2abc def comp_joule_losses(self, out_dict, machine): diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py index a47c9668b..57ca911cd 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py @@ -1,7 +1,7 @@ from numpy import zeros, sqrt, pi, tile, isnan from multiprocessing import cpu_count -from ....Functions.Electrical.coordinate_transformation import n2abc, abc2n +from ....Functions.Electrical.dqh_transformation import n2abc, abc2n from ....Functions.labels import STATOR_LAB, ROTOR_LAB from ....Functions.load import import_class diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/gen_drive.py b/pyleecan/Methods/Simulation/EEC_SCIM/gen_drive.py index 77ce5ffda..eb6e13bf0 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/gen_drive.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/gen_drive.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from ....Functions.Electrical.coordinate_transformation import n2dqh +from ....Functions.Electrical.dqh_transformation import n2dqh from numpy import split, transpose, mean, pi import matplotlib.pyplot as plt diff --git a/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py b/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py index 762e4650b..e7e3dd789 100644 --- a/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py +++ b/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py @@ -5,7 +5,7 @@ from ....Classes.MagFEMM import MagFEMM from ....Classes.Simu1 import Simu1 from ....Classes.Simulation import Simulation -from ....Functions.Electrical.coordinate_transformation import n2dqh_DataTime +from ....Functions.Electrical.dqh_transformation import n2dqh_DataTime def comp_fluxlinkage(self, machine): diff --git a/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py b/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py index f8be89e19..d719b040d 100644 --- a/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py +++ b/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py @@ -4,7 +4,7 @@ from ....Classes.MagFEMM import MagFEMM from ....Classes.Simulation import Simulation from ....Classes.Simu1 import Simu1 -from ....Functions.Electrical.coordinate_transformation import n2dqh_DataTime +from ....Functions.Electrical.dqh_transformation import n2dqh_DataTime def comp_inductance(self, machine, OP_ref): diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index 9d0787d36..1211f8763 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -2,7 +2,7 @@ from ....Methods.Simulation.Input import InputError -from ....Functions.Electrical.coordinate_transformation import n2dqh +from ....Functions.Electrical.dqh_transformation import n2dqh from ....Classes.InputVoltage import InputVoltage from SciDataTool import Data1D, DataTime diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 3c6b34c2f..6a737100e 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -6,7 +6,7 @@ from ....Classes.OPdq import OPdq from ....Classes.OPslip import OPslip from ....Methods.Simulation.Input import InputError -from ....Functions.Electrical.coordinate_transformation import n2dqh, n2dqh_DataTime +from ....Functions.Electrical.dqh_transformation import n2dqh, n2dqh_DataTime from ....Functions.Winding.gen_phase_list import gen_name from SciDataTool import Data1D, DataLinspace, DataTime diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py index faf215fec..09baacda7 100644 --- a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py @@ -1,4 +1,4 @@ -from ....Functions.Electrical.coordinate_transformation import n2dqh_DataTime +from ....Functions.Electrical.dqh_transformation import n2dqh_DataTime def get_Phidqh_mag(self): diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py index 141769464..4390d375f 100644 --- a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py @@ -1,6 +1,6 @@ from numpy import zeros -from ....Functions.Electrical.coordinate_transformation import n2dqh_DataTime +from ....Functions.Electrical.dqh_transformation import n2dqh_DataTime def get_Phidqh_mean(self): From 6e436bd2cc3aa98a10485e2c44ca98c042df9146 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 27 Oct 2021 14:12:00 +0200 Subject: [PATCH 150/167] [CC] Rename angle_offset_initial to angle_rotor_initial --- .../Methods/Simulation/test_InCurrent_meth.py | 2 +- pyleecan/Classes/Class_Dict.json | 6 +-- pyleecan/Classes/Machine.py | 18 ++++---- pyleecan/Classes/OutGeo.py | 44 +++++++++---------- pyleecan/Classes/Output.py | 18 ++++---- .../SPreview/WMachineTable/WMachineTable.py | 2 +- .../Generator/ClassesRef/Machine/Machine.csv | 2 +- .../Generator/ClassesRef/Output/OutGeo.csv | 2 +- .../Generator/ClassesRef/Output/Output.csv | 2 +- .../Machine/comp_angle_offset_initial.py | 22 ---------- .../Machine/comp_angle_rotor_initial.py | 15 +++++++ .../Output/Output/getter/comp_angle_rotor.py | 2 +- ..._initial.py => get_angle_rotor_initial.py} | 12 ++--- .../Simulation/InputVoltage/gen_input.py | 4 +- .../Methods/Simulation/MagElmer/solve_FEA.py | 2 +- 15 files changed, 72 insertions(+), 81 deletions(-) delete mode 100644 pyleecan/Methods/Machine/Machine/comp_angle_offset_initial.py create mode 100644 pyleecan/Methods/Machine/Machine/comp_angle_rotor_initial.py rename pyleecan/Methods/Output/Output/getter/{get_angle_offset_initial.py => get_angle_rotor_initial.py} (52%) diff --git a/Tests/Methods/Simulation/test_InCurrent_meth.py b/Tests/Methods/Simulation/test_InCurrent_meth.py index 15beda9d0..2425eacad 100644 --- a/Tests/Methods/Simulation/test_InCurrent_meth.py +++ b/Tests/Methods/Simulation/test_InCurrent_meth.py @@ -248,7 +248,7 @@ def test_InputCurrent_DQ(self, test_dict): Is_exp = array([Ia, Ib, Ic]) # Compute expected rotor position - angle_rotor_initial = Toyota_Prius.comp_angle_offset_initial() + angle_rotor_initial = Toyota_Prius.comp_angle_rotor_initial() # rot_dir is the rotation direction of the fundamental magnetic field # Then rotor position is -1 * rot_dir angle_rotor_exp = ( diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 020597525..8cf46ce2a 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -5394,7 +5394,7 @@ "methods": [ "build_geometry", "check", - "comp_angle_offset_initial", + "comp_angle_rotor_initial", "comp_desc_dict", "comp_length_airgap_active", "comp_masses", @@ -8434,7 +8434,7 @@ "desc": "Difference between the d axis angle of the stator and the rotor", "max": "", "min": "", - "name": "angle_offset_initial", + "name": "angle_rotor_initial", "type": "float", "unit": "rad", "value": null @@ -9055,7 +9055,7 @@ "is_internal": false, "methods": [ "getter.comp_angle_rotor", - "getter.get_angle_offset_initial", + "getter.get_angle_rotor_initial", "getter.get_angle_rotor", "getter.get_BH_rotor", "getter.get_BH_stator", diff --git a/pyleecan/Classes/Machine.py b/pyleecan/Classes/Machine.py index 06d597b1c..109b9c0c9 100644 --- a/pyleecan/Classes/Machine.py +++ b/pyleecan/Classes/Machine.py @@ -28,11 +28,11 @@ check = error try: - from ..Methods.Machine.Machine.comp_angle_offset_initial import ( - comp_angle_offset_initial, + from ..Methods.Machine.Machine.comp_angle_rotor_initial import ( + comp_angle_rotor_initial, ) except ImportError as error: - comp_angle_offset_initial = error + comp_angle_rotor_initial = error try: from ..Methods.Machine.Machine.comp_desc_dict import comp_desc_dict @@ -165,18 +165,18 @@ class Machine(FrozenClass): ) else: check = check - # cf Methods.Machine.Machine.comp_angle_offset_initial - if isinstance(comp_angle_offset_initial, ImportError): - comp_angle_offset_initial = property( + # cf Methods.Machine.Machine.comp_angle_rotor_initial + if isinstance(comp_angle_rotor_initial, ImportError): + comp_angle_rotor_initial = property( fget=lambda x: raise_( ImportError( - "Can't use Machine method comp_angle_offset_initial: " - + str(comp_angle_offset_initial) + "Can't use Machine method comp_angle_rotor_initial: " + + str(comp_angle_rotor_initial) ) ) ) else: - comp_angle_offset_initial = comp_angle_offset_initial + comp_angle_rotor_initial = comp_angle_rotor_initial # cf Methods.Machine.Machine.comp_desc_dict if isinstance(comp_desc_dict, ImportError): comp_desc_dict = property( diff --git a/pyleecan/Classes/OutGeo.py b/pyleecan/Classes/OutGeo.py index d57ff3afc..e536ea881 100644 --- a/pyleecan/Classes/OutGeo.py +++ b/pyleecan/Classes/OutGeo.py @@ -39,7 +39,7 @@ def __init__( Rgap_mec=None, Lgap=None, logger_name="Pyleecan.OutGeo", - angle_offset_initial=None, + angle_rotor_initial=None, rot_dir=None, per_a=None, is_antiper_a=None, @@ -81,8 +81,8 @@ def __init__( Lgap = init_dict["Lgap"] if "logger_name" in list(init_dict.keys()): logger_name = init_dict["logger_name"] - if "angle_offset_initial" in list(init_dict.keys()): - angle_offset_initial = init_dict["angle_offset_initial"] + if "angle_rotor_initial" in list(init_dict.keys()): + angle_rotor_initial = init_dict["angle_rotor_initial"] if "rot_dir" in list(init_dict.keys()): rot_dir = init_dict["rot_dir"] if "per_a" in list(init_dict.keys()): @@ -110,7 +110,7 @@ def __init__( self.Rgap_mec = Rgap_mec self.Lgap = Lgap self.logger_name = logger_name - self.angle_offset_initial = angle_offset_initial + self.angle_rotor_initial = angle_rotor_initial self.rot_dir = rot_dir self.per_a = per_a self.is_antiper_a = is_antiper_a @@ -147,9 +147,7 @@ def __str__(self): OutGeo_str += "Rgap_mec = " + str(self.Rgap_mec) + linesep OutGeo_str += "Lgap = " + str(self.Lgap) + linesep OutGeo_str += 'logger_name = "' + str(self.logger_name) + '"' + linesep - OutGeo_str += ( - "angle_offset_initial = " + str(self.angle_offset_initial) + linesep - ) + OutGeo_str += "angle_rotor_initial = " + str(self.angle_rotor_initial) + linesep OutGeo_str += "rot_dir = " + str(self.rot_dir) + linesep OutGeo_str += "per_a = " + str(self.per_a) + linesep OutGeo_str += "is_antiper_a = " + str(self.is_antiper_a) + linesep @@ -180,7 +178,7 @@ def __eq__(self, other): return False if other.logger_name != self.logger_name: return False - if other.angle_offset_initial != self.angle_offset_initial: + if other.angle_rotor_initial != self.angle_rotor_initial: return False if other.rot_dir != self.rot_dir: return False @@ -232,8 +230,8 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".Lgap") if other._logger_name != self._logger_name: diff_list.append(name + ".logger_name") - if other._angle_offset_initial != self._angle_offset_initial: - diff_list.append(name + ".angle_offset_initial") + if other._angle_rotor_initial != self._angle_rotor_initial: + diff_list.append(name + ".angle_rotor_initial") if other._rot_dir != self._rot_dir: diff_list.append(name + ".rot_dir") if other._per_a != self._per_a: @@ -280,7 +278,7 @@ def __sizeof__(self): S += getsizeof(self.Rgap_mec) S += getsizeof(self.Lgap) S += getsizeof(self.logger_name) - S += getsizeof(self.angle_offset_initial) + S += getsizeof(self.angle_rotor_initial) S += getsizeof(self.rot_dir) S += getsizeof(self.per_a) S += getsizeof(self.is_antiper_a) @@ -327,7 +325,7 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): OutGeo_dict["Rgap_mec"] = self.Rgap_mec OutGeo_dict["Lgap"] = self.Lgap OutGeo_dict["logger_name"] = self.logger_name - OutGeo_dict["angle_offset_initial"] = self.angle_offset_initial + OutGeo_dict["angle_rotor_initial"] = self.angle_rotor_initial OutGeo_dict["rot_dir"] = self.rot_dir OutGeo_dict["per_a"] = self.per_a OutGeo_dict["is_antiper_a"] = self.is_antiper_a @@ -365,7 +363,7 @@ def _set_None(self): self.Rgap_mec = None self.Lgap = None self.logger_name = None - self.angle_offset_initial = None + self.angle_rotor_initial = None self.rot_dir = None self.per_a = None self.is_antiper_a = None @@ -526,18 +524,18 @@ def _set_logger_name(self, value): """, ) - def _get_angle_offset_initial(self): - """getter of angle_offset_initial""" - return self._angle_offset_initial + def _get_angle_rotor_initial(self): + """getter of angle_rotor_initial""" + return self._angle_rotor_initial - def _set_angle_offset_initial(self, value): - """setter of angle_offset_initial""" - check_var("angle_offset_initial", value, "float") - self._angle_offset_initial = value + def _set_angle_rotor_initial(self, value): + """setter of angle_rotor_initial""" + check_var("angle_rotor_initial", value, "float") + self._angle_rotor_initial = value - angle_offset_initial = property( - fget=_get_angle_offset_initial, - fset=_set_angle_offset_initial, + angle_rotor_initial = property( + fget=_get_angle_rotor_initial, + fset=_set_angle_rotor_initial, doc=u"""Difference between the d axis angle of the stator and the rotor :Type: float diff --git a/pyleecan/Classes/Output.py b/pyleecan/Classes/Output.py index d68de1690..faf702417 100644 --- a/pyleecan/Classes/Output.py +++ b/pyleecan/Classes/Output.py @@ -23,11 +23,11 @@ comp_angle_rotor = error try: - from ..Methods.Output.Output.getter.get_angle_offset_initial import ( - get_angle_offset_initial, + from ..Methods.Output.Output.getter.get_angle_rotor_initial import ( + get_angle_rotor_initial, ) except ImportError as error: - get_angle_offset_initial = error + get_angle_rotor_initial = error try: from ..Methods.Output.Output.getter.get_angle_rotor import get_angle_rotor @@ -100,18 +100,18 @@ class Output(FrozenClass): ) else: comp_angle_rotor = comp_angle_rotor - # cf Methods.Output.Output.getter.get_angle_offset_initial - if isinstance(get_angle_offset_initial, ImportError): - get_angle_offset_initial = property( + # cf Methods.Output.Output.getter.get_angle_rotor_initial + if isinstance(get_angle_rotor_initial, ImportError): + get_angle_rotor_initial = property( fget=lambda x: raise_( ImportError( - "Can't use Output method get_angle_offset_initial: " - + str(get_angle_offset_initial) + "Can't use Output method get_angle_rotor_initial: " + + str(get_angle_rotor_initial) ) ) ) else: - get_angle_offset_initial = get_angle_offset_initial + get_angle_rotor_initial = get_angle_rotor_initial # cf Methods.Output.Output.getter.get_angle_rotor if isinstance(get_angle_rotor, ImportError): get_angle_rotor = property( diff --git a/pyleecan/GUI/Dialog/DMachineSetup/SPreview/WMachineTable/WMachineTable.py b/pyleecan/GUI/Dialog/DMachineSetup/SPreview/WMachineTable/WMachineTable.py index 6b31fff29..f7f86ceb9 100644 --- a/pyleecan/GUI/Dialog/DMachineSetup/SPreview/WMachineTable/WMachineTable.py +++ b/pyleecan/GUI/Dialog/DMachineSetup/SPreview/WMachineTable/WMachineTable.py @@ -150,7 +150,7 @@ def draw_FEMM(self): is_antiperiod=False, ) Is = output.elec.comp_I_mag(time, is_stator=True) - alpha = output.get_angle_offset_initial() + alpha = output.get_angle_rotor_initial() try: # Draw the machine FEMM_dict = draw_FEMM( diff --git a/pyleecan/Generator/ClassesRef/Machine/Machine.csv b/pyleecan/Generator/ClassesRef/Machine/Machine.csv index ba76af78b..f7ad19c96 100644 --- a/pyleecan/Generator/ClassesRef/Machine/Machine.csv +++ b/pyleecan/Generator/ClassesRef/Machine/Machine.csv @@ -1,7 +1,7 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description frame,,Machine's Frame,,Frame,,,,,Machine,,build_geometry,VERSION,1,Abstract class for machines shaft,,Machine's Shaft,,Shaft,,,,,,,check,,, -name,,Name of the machine,,str,default_machine,,,,,,comp_angle_offset_initial,,, +name,,Name of the machine,,str,default_machine,,,,,,comp_angle_rotor_initial,,, desc,,Machine description,,str,,,,,,,comp_desc_dict,,, type_machine,,"Integer to store the machine type (for the GUI, should be replaced by a test of the object type)",,int,1,,,,,,comp_length_airgap_active,,, logger_name,-,Name of the logger to use,0,str,Pyleecan.Machine,,,,,,comp_masses,,, diff --git a/pyleecan/Generator/ClassesRef/Output/OutGeo.csv b/pyleecan/Generator/ClassesRef/Output/OutGeo.csv index d49758f14..db4be3816 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutGeo.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutGeo.csv @@ -6,7 +6,7 @@ Wgap_mag,m,the magnetic airgap width (distance beetween the two Laminations bore Rgap_mec,m,radius of the center of the mecanical airgap,0,float,None,,,,,,,,, Lgap,m,Airgap active length,0,float,None,,,,,,,,, logger_name,-,Name of the logger to use,0,str,Pyleecan.OutGeo,,,,,,,,, -angle_offset_initial,rad,Difference between the d axis angle of the stator and the rotor,0,float,None,,,,,,,,, +angle_rotor_initial,rad,Difference between the d axis angle of the stator and the rotor,0,float,None,,,,,,,,, rot_dir,-,rotation direction of rotor : rot_dir = -1 by default (counter clockwise rotation),0,int,None,-1,1,,,,,,, per_a,-,Number of spatial periodicities of the machine,0,int,None,,,,,,,,, is_antiper_a,-,True if an spatial anti-periodicity is possible after the periodicities,0,bool,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Output/Output.csv b/pyleecan/Generator/ClassesRef/Output/Output.csv index fa80b6dff..9c2defb4e 100644 --- a/pyleecan/Generator/ClassesRef/Output/Output.csv +++ b/pyleecan/Generator/ClassesRef/Output/Output.csv @@ -1,6 +1,6 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description simu,-,Simulation object that generated the Output,0,Simulation,,,,,Output,,getter.comp_angle_rotor,VERSION,1,Main Output object: gather all the outputs of all the modules -path_result,-,Path to the folder to same the results,0,str,,,,,,,getter.get_angle_offset_initial,,, +path_result,-,Path to the folder to same the results,0,str,,,,,,,getter.get_angle_rotor_initial,,, geo,-,Geometry output,0,OutGeo,,,,,,,getter.get_angle_rotor,,, elec,-,Electrical module output,0,OutElec,,,,,,,getter.get_BH_rotor,,, mag,-,Magnetic module output,0,OutMag,,,,,,,getter.get_BH_stator,,, diff --git a/pyleecan/Methods/Machine/Machine/comp_angle_offset_initial.py b/pyleecan/Methods/Machine/Machine/comp_angle_offset_initial.py deleted file mode 100644 index da8c71eb2..000000000 --- a/pyleecan/Methods/Machine/Machine/comp_angle_offset_initial.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- - - -def comp_angle_offset_initial(self): - """Compute initial angle between the d-axis of the rotor and stator - - Parameters - ---------- - self : Machine - A: Machine object - - Returns - ------- - angle_offset_initial: float - initial angle between the d-axis of the rotor and stator [rad] - - Raises - ------ - - - """ - return self.stator.comp_angle_d_axis() - self.rotor.comp_angle_d_axis() diff --git a/pyleecan/Methods/Machine/Machine/comp_angle_rotor_initial.py b/pyleecan/Methods/Machine/Machine/comp_angle_rotor_initial.py new file mode 100644 index 000000000..35fb2c73d --- /dev/null +++ b/pyleecan/Methods/Machine/Machine/comp_angle_rotor_initial.py @@ -0,0 +1,15 @@ +def comp_angle_rotor_initial(self): + """Compute rotor initial angle between rotor and stator d-axes + + Parameters + ---------- + self : Machine + A: Machine object + + Returns + ------- + angle_rotor_initial: float + rotor initial angle given by the difference of rotor and stator d-axes [rad] + """ + + return self.stator.comp_angle_d_axis() - self.rotor.comp_angle_d_axis() diff --git a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py index c4129baea..2a5eae609 100644 --- a/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py +++ b/pyleecan/Methods/Output/Output/getter/comp_angle_rotor.py @@ -30,7 +30,7 @@ def comp_angle_rotor(self, Time): Nr = self.elec.get_Nr(Time=Time) # Compute rotor initial angle (for synchronous machines, to align rotor d-axis and stator alpha-axis) - A0 = self.get_angle_offset_initial() + A0 = self.get_angle_rotor_initial() # Case where normalization is a constant if unique(Nr).size == 1: diff --git a/pyleecan/Methods/Output/Output/getter/get_angle_offset_initial.py b/pyleecan/Methods/Output/Output/getter/get_angle_rotor_initial.py similarity index 52% rename from pyleecan/Methods/Output/Output/getter/get_angle_offset_initial.py rename to pyleecan/Methods/Output/Output/getter/get_angle_rotor_initial.py index dca5fa7d7..5537d7280 100644 --- a/pyleecan/Methods/Output/Output/getter/get_angle_offset_initial.py +++ b/pyleecan/Methods/Output/Output/getter/get_angle_rotor_initial.py @@ -1,4 +1,4 @@ -def get_angle_offset_initial(self): +def get_angle_rotor_initial(self): """Return the difference between the d axis angle of the stator and the rotor Parameters @@ -8,14 +8,14 @@ def get_angle_offset_initial(self): Returns ------- - angle_offset_initial: float + angle_rotor_initial: float difference between the d axis angle of the stator and the rotor [rad] """ # Already available => Return - if self.geo.angle_offset_initial is not None: - return self.geo.angle_offset_initial + if self.geo.angle_rotor_initial is not None: + return self.geo.angle_rotor_initial else: # Compute - self.geo.angle_offset_initial = self.simu.machine.comp_angle_offset_initial() - return self.geo.angle_offset_initial + self.geo.angle_rotor_initial = self.simu.machine.comp_angle_rotor_initial() + return self.geo.angle_rotor_initial diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 6a737100e..a8a941ccd 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -79,8 +79,8 @@ def gen_input(self): # Set rotor initial angular position if self.angle_rotor_initial in [0, None]: # Calculate initial position according to machine properties - self.angle_rotor_initial = simu.machine.comp_angle_offset_initial() - output.geo.angle_offset_initial = self.angle_rotor_initial + self.angle_rotor_initial = simu.machine.comp_angle_rotor_initial() + output.geo.angle_rotor_initial = self.angle_rotor_initial # Calculate time, angle and phase axes and store them in OutGeo outgeo.axes_dict = self.comp_axes( diff --git a/pyleecan/Methods/Simulation/MagElmer/solve_FEA.py b/pyleecan/Methods/Simulation/MagElmer/solve_FEA.py index 605b1a5bc..cb07f4c86 100644 --- a/pyleecan/Methods/Simulation/MagElmer/solve_FEA.py +++ b/pyleecan/Methods/Simulation/MagElmer/solve_FEA.py @@ -353,7 +353,7 @@ def solve_FEA(self, output, sym, angle, time, angle_rotor, Is, Ir): degrees_step = 1 # Fixed for now current_angle = 0 - pp * degrees_step * skip_steps angle_shift = self.angle_rotor_shift - self.angle_stator_shift - rotor_init_pos = machine.comp_angle_offset_initial() + angle_shift + rotor_init_pos = machine.comp_angle_rotor_initial() + angle_shift rotor_d_axis = machine.rotor.comp_angle_d_axis() * 180.0 / pi Ncond = 1 # Fixed for Now Cp = 1 # Fixed for Now From 9e85625ccf77e97fe91fc6509728896b0cb8f5dc Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 27 Oct 2021 14:55:51 +0200 Subject: [PATCH 151/167] [CC] Remove angle_rotor from InputVoltage and OutElec classes since angle_rotor is entirely given by time and rotor speed, which can be already enforced --- Tests/Functions/test_save_load.py | 2 - .../Methods/Simulation/test_InCurrent_meth.py | 48 -------------- Tests/Methods/Simulation/test_magelmer.py | 1 - Tests/Plot/Schematics/test_plot_axis.py | 1 - Tests/Plot/test_ICEM_2020.py | 3 - Tests/Validation/Force/test_AGSF_SynRM.py | 38 +++++------ .../Magnetics/test_EM_BH_Model_001.py | 1 - .../Validation/Magnetics/test_FEMM_compare.py | 16 ++--- .../Magnetics/test_FEMM_meshsolution_plots.py | 2 - .../Magnetics/test_FEMM_parallelization.py | 1 - .../Multisimulation/test_multi_multi.py | 1 - .../Multisimulation/test_slot_scale.py | 1 - .../Optimization/test_Binh_and_Korn.py | 1 - Tests/Validation/Optimization/test_zdt3.py | 1 - Tutorials/tuto_MeshSolution.ipynb | 1 - pyleecan/Classes/Class_Dict.json | 18 ------ pyleecan/Classes/InputCurrent.py | 5 -- pyleecan/Classes/InputFlux.py | 5 -- pyleecan/Classes/InputVoltage.py | 63 ------------------- pyleecan/Classes/OutElec.py | 55 ---------------- .../Generator/ClassesRef/Output/OutElec.csv | 9 ++- .../ClassesRef/Simulation/InputVoltage.csv | 5 +- .../Simulation/EEC_SCIM/comp_parameters.py | 1 - 23 files changed, 28 insertions(+), 251 deletions(-) diff --git a/Tests/Functions/test_save_load.py b/Tests/Functions/test_save_load.py index 8690975c4..66a73a062 100644 --- a/Tests/Functions/test_save_load.py +++ b/Tests/Functions/test_save_load.py @@ -131,7 +131,6 @@ def test_save_load_folder_path(): Is=Is, Ir=None, # No winding on the rotor OP=OPdq(N0=N0), - angle_rotor=None, # Will be computed time=time, angle=angle, rot_dir=-1, @@ -377,7 +376,6 @@ def test_save_load_simu(type_file): Is=Is, Ir=None, # No winding on the rotor OP=OPdq(N0=N0), - angle_rotor=None, # Will be computed time=time, angle=angle, rot_dir=-1, diff --git a/Tests/Methods/Simulation/test_InCurrent_meth.py b/Tests/Methods/Simulation/test_InCurrent_meth.py index 2425eacad..2c707b3dc 100644 --- a/Tests/Methods/Simulation/test_InCurrent_meth.py +++ b/Tests/Methods/Simulation/test_InCurrent_meth.py @@ -145,52 +145,6 @@ "exp": "ERROR: InputCurrent.Ir must be a matrix with the shape (100, 2) (len(time), rotor phase number), (100,) returned", } ) -# Wrong N0, alpha_rotor -test_obj = Simulation(machine=M3) -test_obj.input = InputCurrent( - time=time, - angle=angle, - Is=I_1, - Ir=I_2, - angle_rotor=None, - OP=OPdq(N0=None), -) -InputCurrent_Error_test.append( - { - "test_obj": test_obj, - "exp": "ERROR: InputCurrent.angle_rotor and InputCurrent.N0 can't be None at the same time", - } -) -test_obj = Simulation(machine=M3) -test_obj.input = InputCurrent( - time=time, - angle=angle, - Is=I_1, - Ir=I_2, - angle_rotor=angle_rotor_wrong, - OP=OPdq(N0=None), -) -InputCurrent_Error_test.append( - { - "test_obj": test_obj, - "exp": "ERROR: InputCurrent.angle_rotor should be a vector of the same length as time, (10, 2) shape found, (100,) expected", - } -) -test_obj = Simulation(machine=M3) -test_obj.input = InputCurrent( - time=time, - angle=angle, - Is=I_1, - Ir=I_2, - angle_rotor=angle_rotor_wrong2, - OP=OPdq(N0=None), -) -InputCurrent_Error_test.append( - { - "test_obj": test_obj, - "exp": "ERROR: InputCurrent.angle_rotor should be a vector of the same length as time, (102,) shape found, (100,) expected", - } -) idq_test = list() idq_test.append({"Id": 2, "Iq": 0}) @@ -262,7 +216,6 @@ def test_InputCurrent_DQ(self, test_dict): Is=None, OP=OPdq(Id_ref=Id_ref, Iq_ref=Iq_ref, N0=N0), Ir=None, - angle_rotor=None, # Will be computed according to N0 and rot_dir rot_dir=None, ) @@ -288,7 +241,6 @@ def test_InputCurrent_DQ(self, test_dict): Is=Is_exp.transpose(), OP=OPdq(Id_ref=None, Iq_ref=None, N0=N0), Ir=None, - angle_rotor=None, # Will be computed according to N0 and rot_dir rot_dir=None, ) out = Output(simu=test_obj) diff --git a/Tests/Methods/Simulation/test_magelmer.py b/Tests/Methods/Simulation/test_magelmer.py index 2ab0e6a35..ff47b4273 100644 --- a/Tests/Methods/Simulation/test_magelmer.py +++ b/Tests/Methods/Simulation/test_magelmer.py @@ -61,7 +61,6 @@ def test_ipm_Elmer(): # Is=Is, # Ir=Ir, # zero current for the rotor # N0=N0, - # angle_rotor=None, # Will be computed # Nt_tot=Nt_tot, # Na_tot=Na_tot, # angle_rotor_initial=0.2244, diff --git a/Tests/Plot/Schematics/test_plot_axis.py b/Tests/Plot/Schematics/test_plot_axis.py index 799adfdf9..7a7c38494 100644 --- a/Tests/Plot/Schematics/test_plot_axis.py +++ b/Tests/Plot/Schematics/test_plot_axis.py @@ -266,7 +266,6 @@ def test_axis_LamWind(): Is=Is, Ir=Ir, # zero current for the rotor OP=OPdq(N0=N0), - angle_rotor=None, # Will be computed Nt_tot=Nt_tot, Na_tot=Na_tot, angle_rotor_initial=0, diff --git a/Tests/Plot/test_ICEM_2020.py b/Tests/Plot/test_ICEM_2020.py index 7706f1913..343615f9a 100644 --- a/Tests/Plot/test_ICEM_2020.py +++ b/Tests/Plot/test_ICEM_2020.py @@ -66,7 +66,6 @@ def test_FEMM_sym(): Is=Is, Ir=Ir, # zero current for the rotor OP=OPdq(N0=N0), - angle_rotor=None, # Will be computed Nt_tot=Nt_tot, Na_tot=Na_tot, angle_rotor_initial=0.2244, @@ -553,7 +552,6 @@ def test_ecc_FEMM(): Is=Is, Ir=None, # No winding on the rotor OP=OPdq(N0=N0), - angle_rotor=None, time=time, angle=angle, angle_rotor_initial=0, @@ -681,7 +679,6 @@ def test_Optimization_problem(): Is=Is, Ir=Ir, # zero current for the rotor OP=OPdq(N0=N0), - angle_rotor=None, # Will be computed Nt_tot=Nt_tot, Na_tot=Na_tot, angle_rotor_initial=0.39, diff --git a/Tests/Validation/Force/test_AGSF_SynRM.py b/Tests/Validation/Force/test_AGSF_SynRM.py index 763d86458..24816c770 100644 --- a/Tests/Validation/Force/test_AGSF_SynRM.py +++ b/Tests/Validation/Force/test_AGSF_SynRM.py @@ -1,28 +1,27 @@ from numpy import pi, zeros, linspace, cos from os.path import join -from Tests import save_validation_path as save_path -from pyleecan.Classes.OPdq import OPdq +import pytest -from pyleecan.Classes.Simu1 import Simu1 +import json -from pyleecan.Classes.InputCurrent import InputCurrent +from multiprocessing import cpu_count -from pyleecan.Classes.ImportGenVectLin import ImportGenVectLin +from pyleecan.Classes.OPdq import OPdq +from pyleecan.Classes.Simu1 import Simu1 +from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.ImportMatrixVal import ImportMatrixVal - from pyleecan.Classes.ForceMT import ForceMT - from pyleecan.Classes.MagFEMM import MagFEMM from pyleecan.Classes.Output import Output -import pytest -import json - from pyleecan.Functions.load import load from pyleecan.Functions.Plot import dict_2D, dict_3D + from pyleecan.definitions import DATA_DIR +from Tests import save_validation_path as save_path + @pytest.mark.long_5s @pytest.mark.long_1m @@ -31,7 +30,7 @@ @pytest.mark.SynRM @pytest.mark.periodicity @pytest.mark.SingleOP -def test_AGSF_SynRM(): +def test_AGSF_SynRM(nb_worker=int(cpu_count() / 2)): """Validation of a SynRM machine from Syr-e r29 open source software https://sourceforge.net/projects/syr-e/ Test compute air-gap surface force with Maxwell Tensor and load the results @@ -54,19 +53,17 @@ def test_AGSF_SynRM(): # Definition of the main simulation simu = Simu1(name="test_AGSF_SynRM", machine=SynRM_001) - time_obj = ImportMatrixVal(value=time) + Na_tot = 2016 - alpha_rotor = ImportGenVectLin(start=0, stop=2 * pi, num=Nt_tot, endpoint=False) simu.input = InputCurrent( - Is=None, + Is=ImportMatrixVal(value=Is), Ir=None, # No winding on the rotor OP=OPdq(N0=None, felec=freq0), - angle_rotor=alpha_rotor, - time=time_obj, + time=ImportMatrixVal(value=time), Na_tot=Na_tot, Nt_tot=Nt_tot, - angle_rotor_initial=0, + nb_worker=nb_worker, ) # Definition of the magnetic simulation (1/2 symmetry) @@ -79,11 +76,8 @@ def test_AGSF_SynRM(): # Definition of the magnetic simulation (no symmetry) simu.force = ForceMT(is_periodicity_a=True) - simu.struct = None - - simu.input.Is = ImportMatrixVal(value=Is) - out = Output(simu=simu) - simu.run() + # Run simulation + out = simu.run() # Test save with MeshSolution object in out out.save(save_path=save_path + "\Output.json") diff --git a/Tests/Validation/Magnetics/test_EM_BH_Model_001.py b/Tests/Validation/Magnetics/test_EM_BH_Model_001.py index 2238671b0..5e792047e 100644 --- a/Tests/Validation/Magnetics/test_EM_BH_Model_001.py +++ b/Tests/Validation/Magnetics/test_EM_BH_Model_001.py @@ -53,7 +53,6 @@ def test_EM_BH_Model_001_Toyota_Prius(): Is=Is, Ir=None, # No winding on the rotor OP=OPdq(N0=N0), - angle_rotor=None, # Will be computed Nt_tot=Nt_tot, Na_tot=Na_tot, angle_rotor_initial=0.86, diff --git a/Tests/Validation/Magnetics/test_FEMM_compare.py b/Tests/Validation/Magnetics/test_FEMM_compare.py index 39ff1fd54..5e99f2939 100644 --- a/Tests/Validation/Magnetics/test_FEMM_compare.py +++ b/Tests/Validation/Magnetics/test_FEMM_compare.py @@ -110,7 +110,6 @@ def test_FEMM_compare_Prius(): Is=Is, Ir=None, # No winding on the rotor OP=OPdq(N0=N0), - angle_rotor=None, # Will be computed Nt_tot=Nt_tot, Na_tot=Na_tot, angle_rotor_initial=0.86, @@ -179,7 +178,6 @@ def test_FEMM_compare_SCIM(): Is=Is, Ir=Ir, # zero current for the rotor OP=OPdq(N0=N0), - angle_rotor=None, # Will be computed time=time, Na_tot=Na_tot, angle_rotor_initial=0.2244, @@ -279,7 +277,7 @@ def test_FEMM_compare_SIPMSM(): Is=Is, Ir=None, # No winding on the rotor OP=OPdq(N0=N0), - angle_rotor=Ar, # Will be computed + # angle_rotor=Ar, # Will be computed time=time, Nt_tot=Nt_tot, Na_tot=Na_tot, @@ -370,7 +368,6 @@ def test_SPMSM_load(): Is=Is, Ir=None, # No winding on the rotor OP=OPdq(N0=N0), - angle_rotor=None, # Will be computed time=time, Na_tot=Na_tot, angle_rotor_initial=0.5216 + pi, @@ -461,7 +458,6 @@ def test_SPMSM_noload(): Is=Is, Ir=None, # No winding on the rotor OP=OPdq(N0=N0), - angle_rotor=None, time=time, angle=angle, angle_rotor_initial=0, @@ -511,9 +507,9 @@ def test_SPMSM_noload(): if __name__ == "__main__": - test_FEMM_compare_IPMSM_xxx() - test_FEMM_compare_Prius() - test_FEMM_compare_SCIM() + # test_FEMM_compare_IPMSM_xxx() + # test_FEMM_compare_Prius() + # test_FEMM_compare_SCIM() test_FEMM_compare_SIPMSM() - test_SPMSM_load() - test_SPMSM_noload() + # test_SPMSM_load() + # test_SPMSM_noload() diff --git a/Tests/Validation/Magnetics/test_FEMM_meshsolution_plots.py b/Tests/Validation/Magnetics/test_FEMM_meshsolution_plots.py index a1e0d6fa0..bf52baa1e 100644 --- a/Tests/Validation/Magnetics/test_FEMM_meshsolution_plots.py +++ b/Tests/Validation/Magnetics/test_FEMM_meshsolution_plots.py @@ -54,7 +54,6 @@ def test_FEMM_meshsolution_plots_SPMSM(): Is=Is, Ir=None, # No winding on the rotor OP=OPdq(N0=N0), - angle_rotor=None, # Will be computed time=time, Na_tot=Na_tot, angle_rotor_initial=0.5216 + pi, @@ -289,7 +288,6 @@ def test_FEMM_meshsolution_plots_Prius(): Is=Is, Ir=None, # No winding on the rotor OP=OPdq(N0=N0), - angle_rotor=None, # Will be computed Nt_tot=Nt_tot, Na_tot=Na_tot, angle_rotor_initial=0.86, diff --git a/Tests/Validation/Magnetics/test_FEMM_parallelization.py b/Tests/Validation/Magnetics/test_FEMM_parallelization.py index 93e854b27..d1f15accf 100644 --- a/Tests/Validation/Magnetics/test_FEMM_parallelization.py +++ b/Tests/Validation/Magnetics/test_FEMM_parallelization.py @@ -156,7 +156,6 @@ def test_FEMM_parallelization_meshsolution(): Is=Is, Ir=None, # No winding on the rotor OP=OPdq(N0=N0), - angle_rotor=None, # Will be computed time=time, angle=angle, angle_rotor_initial=0.5216 + pi, diff --git a/Tests/Validation/Multisimulation/test_multi_multi.py b/Tests/Validation/Multisimulation/test_multi_multi.py index 57c334b99..da8aac835 100644 --- a/Tests/Validation/Multisimulation/test_multi_multi.py +++ b/Tests/Validation/Multisimulation/test_multi_multi.py @@ -132,7 +132,6 @@ def test_multi_multi(): Is=None, Ir=None, # No winding on the rotor OP=OPdq(N0=N0_MTPA[0], Id_ref=Id_MTPA[0], Iq_ref=Iq_MTPA[0]), - angle_rotor=None, # Will be computed Nt_tot=Nt_tot, Na_tot=2048, ) diff --git a/Tests/Validation/Multisimulation/test_slot_scale.py b/Tests/Validation/Multisimulation/test_slot_scale.py index b64f8f3fb..5b1b5b0c9 100644 --- a/Tests/Validation/Multisimulation/test_slot_scale.py +++ b/Tests/Validation/Multisimulation/test_slot_scale.py @@ -55,7 +55,6 @@ def test_slot_scale(): Is=Is, Ir=None, # No winding on the rotor OP=OPdq(N0=2504), - angle_rotor=None, # Will be computed time=time, Na_tot=Na_tot, angle_rotor_initial=0.86, diff --git a/Tests/Validation/Optimization/test_Binh_and_Korn.py b/Tests/Validation/Optimization/test_Binh_and_Korn.py index 621326df5..a4d55357e 100644 --- a/Tests/Validation/Optimization/test_Binh_and_Korn.py +++ b/Tests/Validation/Optimization/test_Binh_and_Korn.py @@ -66,7 +66,6 @@ def test_Binh_and_Korn(): Is=Is, Ir=Ir, # zero current for the rotor OP=OPslip(N0=N0), - angle_rotor=None, # Will be computed time=time, Na_tot=Na_tot, angle_rotor_initial=0.5216 + np.pi, diff --git a/Tests/Validation/Optimization/test_zdt3.py b/Tests/Validation/Optimization/test_zdt3.py index f51e9839c..1917b9725 100644 --- a/Tests/Validation/Optimization/test_zdt3.py +++ b/Tests/Validation/Optimization/test_zdt3.py @@ -61,7 +61,6 @@ def test_zdt3(): Is=Is, Ir=Ir, # zero current for the rotor OP=OPslip(N0=N0), - angle_rotor=None, # Will be computed time=time, Na_tot=Na_tot, angle_rotor_initial=0.5216 + np.pi, diff --git a/Tutorials/tuto_MeshSolution.ipynb b/Tutorials/tuto_MeshSolution.ipynb index 429675dd4..66c1cca79 100644 --- a/Tutorials/tuto_MeshSolution.ipynb +++ b/Tutorials/tuto_MeshSolution.ipynb @@ -257,7 +257,6 @@ " Is=Is,\n", " Ir=None, # No winding on the rotor\n", " N0=N0,\n", - " angle_rotor=None, # Will be computed\n", " time=time,\n", " Na_tot=Na_tot,\n", " angle_rotor_initial=0.5216 + pi,\n", diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 8cf46ce2a..8bb6f7a15 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4270,15 +4270,6 @@ "package": "Simulation", "path": "pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv", "properties": [ - { - "desc": "Rotor angular position as a function of time (if None computed according to Nr) to import", - "max": "", - "min": "", - "name": "angle_rotor", - "type": "Import", - "unit": "rad", - "value": null - }, { "desc": "Rotation direction of the rotor 1 trigo, -1 clockwise", "max": "1", @@ -8176,15 +8167,6 @@ "unit": "A", "value": "None" }, - { - "desc": "Rotor angular position as a function of time (if None computed according to Nr)", - "max": "", - "min": "", - "name": "angle_rotor", - "type": "ndarray", - "unit": "rad", - "value": null - }, { "desc": "Initial angular position of the rotor at t=0", "max": "", diff --git a/pyleecan/Classes/InputCurrent.py b/pyleecan/Classes/InputCurrent.py index dbbd4ff8a..db9881116 100644 --- a/pyleecan/Classes/InputCurrent.py +++ b/pyleecan/Classes/InputCurrent.py @@ -38,7 +38,6 @@ from numpy import array, array_equal from ._check import InitUnKnowClassError from .ImportMatrix import ImportMatrix -from .Import import Import from .ImportGenPWM import ImportGenPWM from .OP import OP @@ -93,7 +92,6 @@ def __init__( self, Is=None, Ir=None, - angle_rotor=None, rot_dir=-1, angle_rotor_initial=0, PWM=None, @@ -126,8 +124,6 @@ def __init__( Is = init_dict["Is"] if "Ir" in list(init_dict.keys()): Ir = init_dict["Ir"] - if "angle_rotor" in list(init_dict.keys()): - angle_rotor = init_dict["angle_rotor"] if "rot_dir" in list(init_dict.keys()): rot_dir = init_dict["rot_dir"] if "angle_rotor_initial" in list(init_dict.keys()): @@ -153,7 +149,6 @@ def __init__( self.Ir = Ir # Call InputVoltage init super(InputCurrent, self).__init__( - angle_rotor=angle_rotor, rot_dir=rot_dir, angle_rotor_initial=angle_rotor_initial, PWM=PWM, diff --git a/pyleecan/Classes/InputFlux.py b/pyleecan/Classes/InputFlux.py index 0b53016cc..0890600e4 100644 --- a/pyleecan/Classes/InputFlux.py +++ b/pyleecan/Classes/InputFlux.py @@ -28,7 +28,6 @@ from numpy import array, array_equal from ._check import InitUnKnowClassError from .ImportMatrix import ImportMatrix -from .Import import Import from .ImportGenPWM import ImportGenPWM from .OP import OP @@ -65,7 +64,6 @@ def __init__( B_enforced=None, Is=None, Ir=None, - angle_rotor=None, rot_dir=-1, angle_rotor_initial=0, PWM=None, @@ -114,8 +112,6 @@ def __init__( Is = init_dict["Is"] if "Ir" in list(init_dict.keys()): Ir = init_dict["Ir"] - if "angle_rotor" in list(init_dict.keys()): - angle_rotor = init_dict["angle_rotor"] if "rot_dir" in list(init_dict.keys()): rot_dir = init_dict["rot_dir"] if "angle_rotor_initial" in list(init_dict.keys()): @@ -149,7 +145,6 @@ def __init__( super(InputFlux, self).__init__( Is=Is, Ir=Ir, - angle_rotor=angle_rotor, rot_dir=rot_dir, angle_rotor_initial=angle_rotor_initial, PWM=PWM, diff --git a/pyleecan/Classes/InputVoltage.py b/pyleecan/Classes/InputVoltage.py index 85dbb2f70..12f658061 100644 --- a/pyleecan/Classes/InputVoltage.py +++ b/pyleecan/Classes/InputVoltage.py @@ -32,7 +32,6 @@ from numpy import ndarray from numpy import array, array_equal from ._check import InitUnKnowClassError -from .Import import Import from .ImportGenPWM import ImportGenPWM from .ImportMatrix import ImportMatrix from .OP import OP @@ -75,7 +74,6 @@ class InputVoltage(Input): def __init__( self, - angle_rotor=None, rot_dir=-1, angle_rotor_initial=0, PWM=None, @@ -104,8 +102,6 @@ def __init__( if init_dict is not None: # Initialisation by dict assert type(init_dict) is dict # Overwrite default value with init_dict content - if "angle_rotor" in list(init_dict.keys()): - angle_rotor = init_dict["angle_rotor"] if "rot_dir" in list(init_dict.keys()): rot_dir = init_dict["rot_dir"] if "angle_rotor_initial" in list(init_dict.keys()): @@ -127,7 +123,6 @@ def __init__( if "OP" in list(init_dict.keys()): OP = init_dict["OP"] # Set the properties (value check and convertion are done in setter) - self.angle_rotor = angle_rotor self.rot_dir = rot_dir self.angle_rotor_initial = angle_rotor_initial self.PWM = PWM @@ -145,13 +140,6 @@ def __str__(self): InputVoltage_str = "" # Get the properties inherited from Input InputVoltage_str += super(InputVoltage, self).__str__() - if self.angle_rotor is not None: - tmp = ( - self.angle_rotor.__str__().replace(linesep, linesep + "\t").rstrip("\t") - ) - InputVoltage_str += "angle_rotor = " + tmp - else: - InputVoltage_str += "angle_rotor = None" + linesep + linesep InputVoltage_str += "rot_dir = " + str(self.rot_dir) + linesep InputVoltage_str += ( "angle_rotor_initial = " + str(self.angle_rotor_initial) + linesep @@ -173,8 +161,6 @@ def __eq__(self, other): # Check the properties inherited from Input if not super(InputVoltage, self).__eq__(other): return False - if other.angle_rotor != self.angle_rotor: - return False if other.rot_dir != self.rot_dir: return False if other.angle_rotor_initial != self.angle_rotor_initial: @@ -196,14 +182,6 @@ def compare(self, other, name="self", ignore_list=None): # Check the properties inherited from Input diff_list.extend(super(InputVoltage, self).compare(other, name=name)) - if (other.angle_rotor is None and self.angle_rotor is not None) or ( - other.angle_rotor is not None and self.angle_rotor is None - ): - diff_list.append(name + ".angle_rotor None mismatch") - elif self.angle_rotor is not None: - diff_list.extend( - self.angle_rotor.compare(other.angle_rotor, name=name + ".angle_rotor") - ) if other._rot_dir != self._rot_dir: diff_list.append(name + ".rot_dir") if other._angle_rotor_initial != self._angle_rotor_initial: @@ -227,7 +205,6 @@ def __sizeof__(self): # Get size of the properties inherited from Input S += super(InputVoltage, self).__sizeof__() - S += getsizeof(self.angle_rotor) S += getsizeof(self.rot_dir) S += getsizeof(self.angle_rotor_initial) S += getsizeof(self.PWM) @@ -251,14 +228,6 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): keep_function=keep_function, **kwargs ) - if self.angle_rotor is None: - InputVoltage_dict["angle_rotor"] = None - else: - InputVoltage_dict["angle_rotor"] = self.angle_rotor.as_dict( - type_handle_ndarray=type_handle_ndarray, - keep_function=keep_function, - **kwargs - ) InputVoltage_dict["rot_dir"] = self.rot_dir InputVoltage_dict["angle_rotor_initial"] = self.angle_rotor_initial if self.PWM is None: @@ -278,8 +247,6 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): def _set_None(self): """Set all the properties to None (except pyleecan object)""" - if self.angle_rotor is not None: - self.angle_rotor._set_None() self.rot_dir = None self.angle_rotor_initial = None if self.PWM is not None: @@ -288,36 +255,6 @@ def _set_None(self): # Set to None the properties inherited from Input super(InputVoltage, self)._set_None() - def _get_angle_rotor(self): - """getter of angle_rotor""" - return self._angle_rotor - - def _set_angle_rotor(self, value): - """setter of angle_rotor""" - if isinstance(value, str): # Load from file - value = load_init_dict(value)[1] - if isinstance(value, dict) and "__class__" in value: - class_obj = import_class( - "pyleecan.Classes", value.get("__class__"), "angle_rotor" - ) - value = class_obj(init_dict=value) - elif type(value) is int and value == -1: # Default constructor - value = Import() - check_var("angle_rotor", value, "Import") - self._angle_rotor = value - - if self._angle_rotor is not None: - self._angle_rotor.parent = self - - angle_rotor = property( - fget=_get_angle_rotor, - fset=_set_angle_rotor, - doc=u"""Rotor angular position as a function of time (if None computed according to Nr) to import - - :Type: Import - """, - ) - def _get_rot_dir(self): """getter of rot_dir""" return self._rot_dir diff --git a/pyleecan/Classes/OutElec.py b/pyleecan/Classes/OutElec.py index 4cba707de..1f89af681 100644 --- a/pyleecan/Classes/OutElec.py +++ b/pyleecan/Classes/OutElec.py @@ -139,7 +139,6 @@ def __init__( axes_dict=None, Is=None, Ir=None, - angle_rotor=None, angle_rotor_initial=0, logger_name="Pyleecan.Electrical", Pj_losses=None, @@ -175,8 +174,6 @@ def __init__( Is = init_dict["Is"] if "Ir" in list(init_dict.keys()): Ir = init_dict["Ir"] - if "angle_rotor" in list(init_dict.keys()): - angle_rotor = init_dict["angle_rotor"] if "angle_rotor_initial" in list(init_dict.keys()): angle_rotor_initial = init_dict["angle_rotor_initial"] if "logger_name" in list(init_dict.keys()): @@ -204,7 +201,6 @@ def __init__( self.axes_dict = axes_dict self.Is = Is self.Ir = Ir - self.angle_rotor = angle_rotor self.angle_rotor_initial = angle_rotor_initial self.logger_name = logger_name self.Pj_losses = Pj_losses @@ -231,13 +227,6 @@ def __str__(self): OutElec_str += "axes_dict = " + str(self.axes_dict) + linesep + linesep OutElec_str += "Is = " + str(self.Is) + linesep + linesep OutElec_str += "Ir = " + str(self.Ir) + linesep + linesep - OutElec_str += ( - "angle_rotor = " - + linesep - + str(self.angle_rotor).replace(linesep, linesep + "\t") - + linesep - + linesep - ) OutElec_str += ( "angle_rotor_initial = " + str(self.angle_rotor_initial) + linesep ) @@ -278,8 +267,6 @@ def __eq__(self, other): return False if other.Ir != self.Ir: return False - if not array_equal(other.angle_rotor, self.angle_rotor): - return False if other.angle_rotor_initial != self.angle_rotor_initial: return False if other.logger_name != self.logger_name: @@ -339,8 +326,6 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".Ir None mismatch") elif self.Ir is not None: diff_list.extend(self.Ir.compare(other.Ir, name=name + ".Ir")) - if not array_equal(other.angle_rotor, self.angle_rotor): - diff_list.append(name + ".angle_rotor") if other._angle_rotor_initial != self._angle_rotor_initial: diff_list.append(name + ".angle_rotor_initial") if other._logger_name != self._logger_name: @@ -400,7 +385,6 @@ def __sizeof__(self): S += getsizeof(value) + getsizeof(key) S += getsizeof(self.Is) S += getsizeof(self.Ir) - S += getsizeof(self.angle_rotor) S += getsizeof(self.angle_rotor_initial) S += getsizeof(self.logger_name) S += getsizeof(self.Pj_losses) @@ -455,19 +439,6 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): keep_function=keep_function, **kwargs ) - if self.angle_rotor is None: - OutElec_dict["angle_rotor"] = None - else: - if type_handle_ndarray == 0: - OutElec_dict["angle_rotor"] = self.angle_rotor.tolist() - elif type_handle_ndarray == 1: - OutElec_dict["angle_rotor"] = self.angle_rotor.copy() - elif type_handle_ndarray == 2: - OutElec_dict["angle_rotor"] = self.angle_rotor - else: - raise Exception( - "Unknown type_handle_ndarray: " + str(type_handle_ndarray) - ) OutElec_dict["angle_rotor_initial"] = self.angle_rotor_initial OutElec_dict["logger_name"] = self.logger_name OutElec_dict["Pj_losses"] = self.Pj_losses @@ -536,7 +507,6 @@ def _set_None(self): self.axes_dict = None self.Is = None self.Ir = None - self.angle_rotor = None self.angle_rotor_initial = None self.logger_name = None self.Pj_losses = None @@ -636,31 +606,6 @@ def _set_Ir(self, value): """, ) - def _get_angle_rotor(self): - """getter of angle_rotor""" - return self._angle_rotor - - def _set_angle_rotor(self, value): - """setter of angle_rotor""" - if type(value) is int and value == -1: - value = array([]) - elif type(value) is list: - try: - value = array(value) - except: - pass - check_var("angle_rotor", value, "ndarray") - self._angle_rotor = value - - angle_rotor = property( - fget=_get_angle_rotor, - fset=_set_angle_rotor, - doc=u"""Rotor angular position as a function of time (if None computed according to Nr) - - :Type: ndarray - """, - ) - def _get_angle_rotor_initial(self): """getter of angle_rotor_initial""" return self._angle_rotor_initial diff --git a/pyleecan/Generator/ClassesRef/Output/OutElec.csv b/pyleecan/Generator/ClassesRef/Output/OutElec.csv index 7cbbfa616..2de08dc40 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutElec.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutElec.csv @@ -2,11 +2,10 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximu axes_dict,,Dict containing axes data used for Electrical,,{SciDataTool.Classes.DataND.Data},None,,,,Output,,get_I_fund,VERSION,1,Gather the electric module outputs Is,A,Stator currents DataTime object,"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_I_harm,,, Ir,A,Rotor currents as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Is,,, -angle_rotor,rad,Rotor angular position as a function of time (if None computed according to Nr),Nt_tot,ndarray,None,,,,,,get_Nr,,, -angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,get_Us,,, -logger_name,-,Name of the logger to use,0,str,Pyleecan.Electrical,,,,,,get_Us_harm,,, -Pj_losses,W,Electrical Joule losses,,float,None,,,,,,store,,, -Us,V,Stator voltage as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,,,, +angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,get_Nr,,, +logger_name,-,Name of the logger to use,0,str,Pyleecan.Electrical,,,,,,get_Us,,, +Pj_losses,W,Electrical Joule losses,,float,None,,,,,,get_Us_harm,,, +Us,V,Stator voltage as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,store,,, internal,-,OutInternal object containg outputs related to a specific model,0,OutInternal,None,,,,,,,,, Us_PWM,V,PWM stator voltage as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,,,, OP,-,Operating Point,,OP,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv index cf4dd2bf4..6db475332 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv @@ -1,6 +1,5 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -angle_rotor,rad,Rotor angular position as a function of time (if None computed according to Nr) to import,Nt_tot,Import,None,,,,Simulation,Input,gen_input,VERSION,1,Input to start the electrical module with voltage input -rot_dir,-,"Rotation direction of the rotor 1 trigo, -1 clockwise",,int,-1,-1,1,,,,set_OP_from_array,,, -angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,,,, +rot_dir,-,"Rotation direction of the rotor 1 trigo, -1 clockwise",,int,-1,-1,1,,Simulation,Input,gen_input,VERSION,1,Input to start the electrical module with voltage input +angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,set_OP_from_array,,, PWM,,Object to generate PWM signal,,ImportGenPWM,None,,,,,,,,, current_dir,-,"Rotation direction of the stator currents 1 trigo, -1 clockwise",,int,-1,-1,1,,,,,,, diff --git a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py index 57ca911cd..8ecc30801 100644 --- a/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py +++ b/pyleecan/Methods/Simulation/EEC_SCIM/comp_parameters.py @@ -270,7 +270,6 @@ def _comp_Lm_FEA(self): Iq_ref=0, Ir=Ir, # zero current for the rotor N0=N0, - angle_rotor=None, # Will be computed time=time, felec=felec, ) From a6f97ca019071d9d17193c6732e13a571bfabb12 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 27 Oct 2021 17:56:15 +0200 Subject: [PATCH 152/167] [BC] Correct tests --- Tests/Methods/Machine/test_comp_periodicity.py | 8 +++++--- pyleecan/Classes/Class_Dict.json | 4 ++-- pyleecan/Classes/OutGeo.py | 4 ++-- pyleecan/Generator/ClassesRef/Output/OutGeo.csv | 4 ++-- .../LamSlotWind/comp_periodicity_spatial.py | 1 + .../Lamination/comp_periodicity_spatial.py | 17 ++++++++++------- 6 files changed, 22 insertions(+), 16 deletions(-) diff --git a/Tests/Methods/Machine/test_comp_periodicity.py b/Tests/Methods/Machine/test_comp_periodicity.py index b5f9e3686..6833a3e82 100644 --- a/Tests/Methods/Machine/test_comp_periodicity.py +++ b/Tests/Methods/Machine/test_comp_periodicity.py @@ -84,20 +84,20 @@ SC0 = SC.copy() SC0.name = SC0.name + "_0" SC0.rotor.axial_vent = [v9] -test_per_list.append({"machine": SC0, "exp": (1, False, 1, True)}) +test_per_list.append({"machine": SC0, "exp": (1, False, 1, False)}) # Rotor is 4, True => 1, True SP = load(join(DATA_DIR, "Machine", "SPMSM_001.json")) SP0 = SP.copy() SP0.name = SP0.name + "_0" SP0.rotor.axial_vent = [v2] -test_per_list.append({"machine": SP0, "exp": (2, False, 1, True)}) +test_per_list.append({"machine": SP0, "exp": (2, False, 2, False)}) # no antiper, remove sym SP1 = SP.copy() SP1.name = SP1.name + "_1" SP1.rotor.axial_vent = [v9] -test_per_list.append({"machine": SP1, "exp": (1, False, 1, True)}) +test_per_list.append({"machine": SP1, "exp": (1, False, 1, False)}) @pytest.mark.periodicity @@ -147,3 +147,5 @@ def test_comp_periodicity(test_dict): print(str(ii) + " :" + machine_name) per_tuple = test_comp_periodicity(test_dict) print("Done") + + # per_tuple = test_comp_periodicity(test_per_list[22]) diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 77f3c2575..0db4f04db 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -8424,7 +8424,7 @@ "value": null }, { - "desc": "rotation direction of rotor\u00c2\u00a0: rot_dir = -1 by default (counter clockwise rotation)", + "desc": "rotation direction of rotor(rot_dir = -1 by default, i.e. clockwise rotation)", "max": "1", "min": "-1", "name": "rot_dir", @@ -8496,7 +8496,7 @@ "value": null }, { - "desc": "rotation direction of the stator currents: current_dir=1 by default (counter clockwise rotation)", + "desc": "rotation direction of the stator currents(current_dir = -1 by default, i.e. clockwise rotation)", "max": "1", "min": "-1", "name": "current_dir", diff --git a/pyleecan/Classes/OutGeo.py b/pyleecan/Classes/OutGeo.py index e536ea881..125545b4a 100644 --- a/pyleecan/Classes/OutGeo.py +++ b/pyleecan/Classes/OutGeo.py @@ -554,7 +554,7 @@ def _set_rot_dir(self, value): rot_dir = property( fget=_get_rot_dir, fset=_set_rot_dir, - doc=u"""rotation direction of rotor : rot_dir = -1 by default (counter clockwise rotation) + doc=u"""rotation direction of rotor(rot_dir = -1 by default, i.e. clockwise rotation) :Type: int :min: -1 @@ -713,7 +713,7 @@ def _set_current_dir(self, value): current_dir = property( fget=_get_current_dir, fset=_set_current_dir, - doc=u"""rotation direction of the stator currents: current_dir=1 by default (counter clockwise rotation) + doc=u"""rotation direction of the stator currents(current_dir = -1 by default, i.e. clockwise rotation) :Type: int :min: -1 diff --git a/pyleecan/Generator/ClassesRef/Output/OutGeo.csv b/pyleecan/Generator/ClassesRef/Output/OutGeo.csv index db4be3816..bc43a2a3c 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutGeo.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutGeo.csv @@ -7,7 +7,7 @@ Rgap_mec,m,radius of the center of the mecanical airgap,0,float,None,,,,,,,,, Lgap,m,Airgap active length,0,float,None,,,,,,,,, logger_name,-,Name of the logger to use,0,str,Pyleecan.OutGeo,,,,,,,,, angle_rotor_initial,rad,Difference between the d axis angle of the stator and the rotor,0,float,None,,,,,,,,, -rot_dir,-,rotation direction of rotor : rot_dir = -1 by default (counter clockwise rotation),0,int,None,-1,1,,,,,,, +rot_dir,-,"rotation direction of rotor(rot_dir = -1 by default, i.e. clockwise rotation)",0,int,None,-1,1,,,,,,, per_a,-,Number of spatial periodicities of the machine,0,int,None,,,,,,,,, is_antiper_a,-,True if an spatial anti-periodicity is possible after the periodicities,0,bool,None,,,,,,,,, per_t_S,-,Number of time periodicities of the machine in static referential,0,int,None,,,,,,,,, @@ -15,4 +15,4 @@ is_antiper_t_S,-,True if an time anti-periodicity is possible after the periodic axes_dict,,Dict containing axes data without periodicities used for plots and to have simulation full time/angle vectors,,{SciDataTool.Classes.DataND.Data},None,,,,,,,,, per_t_R,-,Number of time periodicities of the machine in rotating referential,0,int,None,,,,,,,,, is_antiper_t_R,-,True if an time anti-periodicity is possible after the periodicities in rotating referential,0,bool,None,,,,,,,,, -current_dir,-,rotation direction of the stator currents: current_dir=1 by default (counter clockwise rotation),0,int,None,-1,1,,,,,,, +current_dir,-,"rotation direction of the stator currents(current_dir = -1 by default, i.e. clockwise rotation)",0,int,None,-1,1,,,,,,, diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_periodicity_spatial.py b/pyleecan/Methods/Machine/LamSlotWind/comp_periodicity_spatial.py index fa054b666..f962f19bb 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_periodicity_spatial.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_periodicity_spatial.py @@ -21,6 +21,7 @@ def comp_periodicity_spatial(self): per_a, is_antiper_a = 1, False if is_antiper_a: per_a = int(per_a / 2) + per_a, is_antiper_a = self.comp_periodicity_duct_spatial(per_a, is_antiper_a) return int(per_a), bool(is_antiper_a) diff --git a/pyleecan/Methods/Machine/Lamination/comp_periodicity_spatial.py b/pyleecan/Methods/Machine/Lamination/comp_periodicity_spatial.py index f4e3fcb76..b97a2724e 100644 --- a/pyleecan/Methods/Machine/Lamination/comp_periodicity_spatial.py +++ b/pyleecan/Methods/Machine/Lamination/comp_periodicity_spatial.py @@ -21,15 +21,18 @@ def comp_periodicity_spatial(self): Zs = self.get_Zs() p = self.get_pole_pair_number() - per = int(gcd(Zs, p)) + per_a = int(gcd(Zs, p)) - if per == 1: - is_aper = bool(Zs % 2 == 0) + if per_a == 1: + is_antiper_a = bool(Zs % 2 == 0) else: - is_aper = bool(Zs / p % 2 == 0) + is_antiper_a = bool(Zs / p % 2 == 0) + + # Account for duct periodicity + per_a, is_antiper_a = self.comp_periodicity_duct_spatial(per_a, is_antiper_a) else: - per = None - is_aper = False + per_a = None + is_antiper_a = False - return per, is_aper + return per_a, is_antiper_a From b42aa1ba89fd8c1b39f9e27e17ef785b5771022e Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 27 Oct 2021 18:01:39 +0200 Subject: [PATCH 153/167] [BC] Add p to OPslip getters --- Tests/Plot/test_ICEM_2020.py | 2 ++ pyleecan/Methods/Simulation/OPslip/get_N0.py | 15 ++++++++++----- pyleecan/Methods/Simulation/OPslip/get_felec.py | 15 ++++++++++----- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Tests/Plot/test_ICEM_2020.py b/Tests/Plot/test_ICEM_2020.py index 343615f9a..9ac0de7b5 100644 --- a/Tests/Plot/test_ICEM_2020.py +++ b/Tests/Plot/test_ICEM_2020.py @@ -44,6 +44,8 @@ """ +@pytest.mark.long_5s +@pytest.mark.long_1m @pytest.mark.MagFEMM @pytest.mark.SCIM @pytest.mark.periodicity diff --git a/pyleecan/Methods/Simulation/OPslip/get_N0.py b/pyleecan/Methods/Simulation/OPslip/get_N0.py index 4d4656570..15af27259 100644 --- a/pyleecan/Methods/Simulation/OPslip/get_N0.py +++ b/pyleecan/Methods/Simulation/OPslip/get_N0.py @@ -1,13 +1,15 @@ from ....Methods.Simulation.Input import InputError -def get_N0(self): +def get_N0(self, p=None): """Returns the Rotor speed Parameters ---------- self : OPslip An OPslip object + p: int + pole pair number Returns ------- @@ -22,10 +24,13 @@ def get_N0(self): if self.felec is None: raise InputError("OPslip object can't have felec and N0 both None") - machine = self.get_machine_from_parent() - if machine is None: - raise InputError("OPslip object can't find machine parent to compute N0") + if p is None: + machine = self.get_machine_from_parent() + if machine is None: + raise InputError("OPslip object can't find machine parent to compute N0") + + p = machine.get_pole_pair_number() - p = machine.get_pole_pair_number() self.N0 = 60 * (1 - self.slip_ref) * self.felec / p + return self.N0 diff --git a/pyleecan/Methods/Simulation/OPslip/get_felec.py b/pyleecan/Methods/Simulation/OPslip/get_felec.py index e797947e4..41d9c27ec 100644 --- a/pyleecan/Methods/Simulation/OPslip/get_felec.py +++ b/pyleecan/Methods/Simulation/OPslip/get_felec.py @@ -1,13 +1,15 @@ from ....Methods.Simulation.Input import InputError -def get_felec(self): +def get_felec(self, p=None): """Returns the electrical frequency Parameters ---------- self : OPslip An OPslip object + p: int + pole pair number Returns ------- @@ -22,10 +24,13 @@ def get_felec(self): if self.N0 is None: raise InputError("OPslip object can't have felec and N0 both None") - machine = self.get_machine_from_parent() - if machine is None: - raise InputError("OPslip object can't find machine parent to compute felec") + if p is None: + machine = self.get_machine_from_parent() + if machine is None: + raise InputError("OPslip object can't find machine parent to compute felec") + + p = machine.get_pole_pair_number() - p = machine.get_pole_pair_number() self.felec = self.N0 * p / (60 * (1 - self.slip_ref)) + return self.felec From bdaed3840537464bfd9c03e38088046e5667eafc Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 27 Oct 2021 18:16:17 +0200 Subject: [PATCH 154/167] [BC] Correct tests --- .../Methods/Simulation/test_InCurrent_meth.py | 39 +++++++++---------- .../Methods/Simulation/test_InVoltage_PWM.py | 3 ++ .../Simulation/InputVoltage/gen_input.py | 8 +--- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/Tests/Methods/Simulation/test_InCurrent_meth.py b/Tests/Methods/Simulation/test_InCurrent_meth.py index 2c707b3dc..d3007994b 100644 --- a/Tests/Methods/Simulation/test_InCurrent_meth.py +++ b/Tests/Methods/Simulation/test_InCurrent_meth.py @@ -1,25 +1,20 @@ -# -*- coding: utf-8 -*- - from os.path import join from numpy import ( array, linspace, - ones, pi, sqrt, cos, - transpose, zeros, abs as np_abs, angle as np_angle, ) from numpy.testing import assert_array_almost_equal import matplotlib.pyplot as plt -from pyleecan.Classes.ImportGenMatrixSin import ImportGenMatrixSin from pyleecan.Classes.ImportGenVectLin import ImportGenVectLin -from pyleecan.Classes.ImportGenVectSin import ImportGenVectSin from pyleecan.Classes.ImportMatrixVal import ImportMatrixVal +from pyleecan.Classes.InputVoltage import InputVoltage from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.LamSlotWind import LamSlotWind from pyleecan.Classes.MachineDFIM import MachineDFIM @@ -183,31 +178,36 @@ def test_InputCurrent_DQ(self, test_dict): p = Toyota_Prius.stator.get_pole_pair_number() time_exp = linspace(0, 60 / N0, Nt_tot, endpoint=False) felec = p * N0 / 60 - rot_dir = Toyota_Prius.stator.comp_mmf_dir() + current_dir = InputVoltage().current_dir + assert current_dir == -1 + rot_dir = InputVoltage().rot_dir + assert rot_dir == -1 + # mmf_dir is the rotation direction of the fundamental magnetic field + mmf_dir = Toyota_Prius.stator.comp_mmf_dir(current_dir=current_dir) + assert mmf_dir == 1 Ia = ( A_rms * sqrt(2) - * cos(rot_dir * 2 * pi * felec * time_exp + 0 * 2 * pi / qs + Phi0) + * cos(current_dir * (2 * pi * felec * time_exp + 0 * 2 * pi / qs) + Phi0) ) Ib = ( A_rms * sqrt(2) - * cos(rot_dir * 2 * pi * felec * time_exp + 1 * 2 * pi / qs + Phi0) + * cos(current_dir * (2 * pi * felec * time_exp + 1 * 2 * pi / qs) + Phi0) ) Ic = ( A_rms * sqrt(2) - * cos(rot_dir * 2 * pi * felec * time_exp + 2 * 2 * pi / qs + Phi0) + * cos(current_dir * (2 * pi * felec * time_exp + 2 * 2 * pi / qs) + Phi0) ) Is_exp = array([Ia, Ib, Ic]) # Compute expected rotor position angle_rotor_initial = Toyota_Prius.comp_angle_rotor_initial() - # rot_dir is the rotation direction of the fundamental magnetic field - # Then rotor position is -1 * rot_dir + + # Then rotor position is -1 * mmf_dir angle_rotor_exp = ( - linspace(0, -1 * rot_dir * 2 * pi, Nt_tot, endpoint=False) - + angle_rotor_initial + linspace(0, rot_dir * 2 * pi, Nt_tot, endpoint=False) + angle_rotor_initial ) test_obj.input = InputCurrent( @@ -216,7 +216,6 @@ def test_InputCurrent_DQ(self, test_dict): Is=None, OP=OPdq(Id_ref=Id_ref, Iq_ref=Iq_ref, N0=N0), Ir=None, - rot_dir=None, ) # Generate Is according to Id/Iq @@ -232,7 +231,7 @@ def test_InputCurrent_DQ(self, test_dict): assert_array_almost_equal(output.elec.get_Is().values, Is_exp.T) assert_array_almost_equal(output.get_angle_rotor(), angle_rotor_exp) assert_array_almost_equal(output.elec.OP.get_N0(), N0) - assert_array_almost_equal(output.geo.rot_dir, rot_dir) + assert_array_almost_equal(output.geo.rot_dir, -mmf_dir) # Check Id/Iq by enforcing Is test_obj.input = InputCurrent( @@ -241,7 +240,6 @@ def test_InputCurrent_DQ(self, test_dict): Is=Is_exp.transpose(), OP=OPdq(Id_ref=None, Iq_ref=None, N0=N0), Ir=None, - rot_dir=None, ) out = Output(simu=test_obj) test_obj.input.gen_input() @@ -277,9 +275,10 @@ def test_InputCurrent_DQ(self, test_dict): obj = Test_InCurrent_meth() # test_dict = idq_test[0] - # obj.test_InputCurrent_DQ(test_dict) - for test_dict in InputCurrent_Error_test: - out = obj.test_InputCurrent_Error_test(test_dict) + for test_dict in idq_test: + obj.test_InputCurrent_DQ(test_dict) + # for test_dict in InputCurrent_Error_test: + # out = obj.test_InputCurrent_Error_test(test_dict) print("Done") # out.plot_2D_Data( # "elec.Is", diff --git a/Tests/Methods/Simulation/test_InVoltage_PWM.py b/Tests/Methods/Simulation/test_InVoltage_PWM.py index 040a0a7b0..9d2ca3f35 100644 --- a/Tests/Methods/Simulation/test_InVoltage_PWM.py +++ b/Tests/Methods/Simulation/test_InVoltage_PWM.py @@ -13,6 +13,9 @@ def test_InVoltage_PWM(): + """Test voltage generation with PWM""" + + # TODO: add assert almost equal fmax = 20000 fswi = 7000 diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index a8a941ccd..e373306af 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -102,6 +102,7 @@ def gen_input(self): felec = self.OP.get_felec() rot_dir = outgeo.rot_dir qs = simu.machine.stator.winding.qs + p = simu.machine.get_pole_pair_number() self.PWM.f = felec self.PWM.qs = qs self.PWM.rot_dir = rot_dir @@ -116,12 +117,7 @@ def gen_input(self): Uabc, _, _, _, time = self.PWM.get_data(is_norm=False) # Create DataTime object self.time = time - Time = self.comp_axis_time( - simu.machine.get_pole_pair_number(), - per_t=1, - is_antiper_t=False, - output=output, - ) + Time = self.comp_axis_time(p, per_t=1, is_antiper_t=False) Phase = self.comp_axis_phase(simu.machine.stator) outelec.Us_PWM = DataTime( name="Stator voltage", From 6c9bf5b13ad15c873df20e37da2098fa9e079db9 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 27 Oct 2021 18:22:12 +0200 Subject: [PATCH 155/167] [BC] Correct winding test --- Tests/GUI/DMachineSetup/test_SWinding.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tests/GUI/DMachineSetup/test_SWinding.py b/Tests/GUI/DMachineSetup/test_SWinding.py index 3074dfa33..7ea15cfb9 100644 --- a/Tests/GUI/DMachineSetup/test_SWinding.py +++ b/Tests/GUI/DMachineSetup/test_SWinding.py @@ -71,7 +71,7 @@ def test_init(self): assert self.widget.is_reverse_layer.checkState() == Qt.Checked assert self.widget.is_change_layer.checkState() == Qt.Checked assert self.widget.is_permute_B_C.checkState() == Qt.Checked - assert self.widget.out_rot_dir.text() == "Rotation direction: CCW" + assert self.widget.out_rot_dir.text() == "Rotation direction: CW" self.test_obj = MachineSCIM() self.test_obj.stator = LamSlotWind() @@ -127,7 +127,7 @@ def test_generate(self): self.widget.b_generate.clicked.emit() assert self.widget.obj.winding.wind_mat.shape == (2, 1, 36, 3) - assert self.widget.out_rot_dir.text() == "Rotation direction: CW" + assert self.widget.out_rot_dir.text() == "Rotation direction: CCW" assert self.widget.out_ms.text() == "ms = Zs / (2*p*qs) = 2.0" assert self.widget.out_Nperw.text() == "Nperw: 6" assert self.widget.out_Ncspc.text() == "Ncspc: 6" @@ -214,4 +214,5 @@ def test_check(self): a.setup_class() a.setup_method() a.test_init() + a.test_generate() print("Done") From 0a9b5a8e546005c105e62ca493a6c0942a8acb43 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Wed, 27 Oct 2021 18:27:21 +0200 Subject: [PATCH 156/167] [CC] Remove -rot_dir in front of initial angle --- pyleecan/Methods/Simulation/Input/comp_axis_time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyleecan/Methods/Simulation/Input/comp_axis_time.py b/pyleecan/Methods/Simulation/Input/comp_axis_time.py index 43f89ebb8..448d77d35 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axis_time.py +++ b/pyleecan/Methods/Simulation/Input/comp_axis_time.py @@ -35,7 +35,7 @@ def comp_axis_time(self, p, per_t, is_antiper_t, Time_in=None): "mech_order": Norm_ref(ref=N0 / 60), "angle_elec": Norm_ref(ref=self.current_dir / (2 * pi * f_elec)), "angle_rotor": Norm_affine( - slope=self.rot_dir * N0 * 360 / 60, offset=-self.rot_dir * A0 * 180 / pi + slope=self.rot_dir * N0 * 360 / 60, offset=A0 * 180 / pi ), } From ab3fb97eb79a392317180667f4eb8b099d8e7985 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Thu, 28 Oct 2021 17:41:43 +0200 Subject: [PATCH 157/167] [BC] Store B in outmag only if Br/Bt/Bz are in out_dict --- pyleecan/Methods/Output/OutMag/store.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pyleecan/Methods/Output/OutMag/store.py b/pyleecan/Methods/Output/OutMag/store.py index 8b30ab403..6dd945966 100644 --- a/pyleecan/Methods/Output/OutMag/store.py +++ b/pyleecan/Methods/Output/OutMag/store.py @@ -29,11 +29,14 @@ def store(self, out_dict, axes_dict): # Store airgap flux as VectorField object # Axes for each airgap flux component axis_list = [Time, axes_dict["angle"], axes_dict["z"]] + # Create VectorField with empty components - self.B = VectorField( - name="Airgap flux density", - symbol="B", - ) + if "Br" in out_dict or "Bt" in out_dict or "Bz" in out_dict: + self.B = VectorField( + name="Airgap flux density", + symbol="B", + ) + # Radial flux component if "Br" in out_dict: self.B.components["radial"] = DataTime( From d607474b02bd5bb3c05c3412bddf0724f6a7359b Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Thu, 28 Oct 2021 17:56:38 +0200 Subject: [PATCH 158/167] [CC] Put conventions in init.py of Input methods --- Tests/Methods/Simulation/test_InCurrent_meth.py | 7 +++---- Tests/Methods/Simulation/test_mmf_dir.py | 5 +++-- pyleecan/Classes/Class_Dict.json | 4 ++-- pyleecan/Classes/InputCurrent.py | 4 ++-- pyleecan/Classes/InputFlux.py | 4 ++-- pyleecan/Classes/InputVoltage.py | 4 ++-- .../Functions/Electrical/dqh_transformation.py | 8 ++++---- .../ClassesRef/Simulation/InputVoltage.csv | 4 ++-- .../Methods/Machine/LamSlotWind/comp_mmf_unit.py | 4 +++- pyleecan/Methods/Simulation/Input/__init__.py | 5 ++++- .../Methods/Simulation/InputVoltage/gen_input.py | 16 ++++++---------- 11 files changed, 33 insertions(+), 32 deletions(-) diff --git a/Tests/Methods/Simulation/test_InCurrent_meth.py b/Tests/Methods/Simulation/test_InCurrent_meth.py index d3007994b..6883e55e5 100644 --- a/Tests/Methods/Simulation/test_InCurrent_meth.py +++ b/Tests/Methods/Simulation/test_InCurrent_meth.py @@ -14,7 +14,6 @@ import matplotlib.pyplot as plt from pyleecan.Classes.ImportGenVectLin import ImportGenVectLin from pyleecan.Classes.ImportMatrixVal import ImportMatrixVal -from pyleecan.Classes.InputVoltage import InputVoltage from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.LamSlotWind import LamSlotWind from pyleecan.Classes.MachineDFIM import MachineDFIM @@ -24,7 +23,7 @@ from pyleecan.definitions import DATA_DIR from pyleecan.Functions.load import load from pyleecan.Functions.Plot import dict_2D -from pyleecan.Methods.Simulation.Input import InputError +from pyleecan.Methods.Simulation.Input import CURRENT_DIR_REF, ROT_DIR_REF, InputError import pytest from Tests import save_plot_path as save_path @@ -178,9 +177,9 @@ def test_InputCurrent_DQ(self, test_dict): p = Toyota_Prius.stator.get_pole_pair_number() time_exp = linspace(0, 60 / N0, Nt_tot, endpoint=False) felec = p * N0 / 60 - current_dir = InputVoltage().current_dir + current_dir = CURRENT_DIR_REF assert current_dir == -1 - rot_dir = InputVoltage().rot_dir + rot_dir = ROT_DIR_REF assert rot_dir == -1 # mmf_dir is the rotation direction of the fundamental magnetic field mmf_dir = Toyota_Prius.stator.comp_mmf_dir(current_dir=current_dir) diff --git a/Tests/Methods/Simulation/test_mmf_dir.py b/Tests/Methods/Simulation/test_mmf_dir.py index 50f0055c8..8c54db477 100644 --- a/Tests/Methods/Simulation/test_mmf_dir.py +++ b/Tests/Methods/Simulation/test_mmf_dir.py @@ -9,10 +9,10 @@ from pyleecan.Classes.MagFEMM import MagFEMM from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 -from pyleecan.Classes.InputVoltage import InputVoltage from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Functions.load import load +from pyleecan.Methods.Simulation.Input import CURRENT_DIR_REF from pyleecan.definitions import DATA_DIR @@ -38,6 +38,7 @@ is_show_fig = True + @pytest.mark.long_5s @pytest.mark.MagFEMM @pytest.mark.parametrize("param_dict", param_list) @@ -48,7 +49,7 @@ def test_mmf_dir(param_dict, nb_worker=int(cpu_count() / 2)): p = machine.get_pole_pair_number() - current_dir_ref = InputVoltage().current_dir + current_dir_ref = CURRENT_DIR_REF # Check that resistance computation is correct resistance = machine.stator.comp_resistance_wind() diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 0db4f04db..370ea0ad4 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4277,7 +4277,7 @@ "name": "rot_dir", "type": "int", "unit": "-", - "value": -1 + "value": null }, { "desc": "Initial angular position of the rotor at t=0", @@ -4304,7 +4304,7 @@ "name": "current_dir", "type": "int", "unit": "-", - "value": -1 + "value": null } ] }, diff --git a/pyleecan/Classes/InputCurrent.py b/pyleecan/Classes/InputCurrent.py index db9881116..49739c971 100644 --- a/pyleecan/Classes/InputCurrent.py +++ b/pyleecan/Classes/InputCurrent.py @@ -92,10 +92,10 @@ def __init__( self, Is=None, Ir=None, - rot_dir=-1, + rot_dir=None, angle_rotor_initial=0, PWM=None, - current_dir=-1, + current_dir=None, time=None, angle=None, Nt_tot=2048, diff --git a/pyleecan/Classes/InputFlux.py b/pyleecan/Classes/InputFlux.py index 0890600e4..e3cbd7f86 100644 --- a/pyleecan/Classes/InputFlux.py +++ b/pyleecan/Classes/InputFlux.py @@ -64,10 +64,10 @@ def __init__( B_enforced=None, Is=None, Ir=None, - rot_dir=-1, + rot_dir=None, angle_rotor_initial=0, PWM=None, - current_dir=-1, + current_dir=None, time=None, angle=None, Nt_tot=2048, diff --git a/pyleecan/Classes/InputVoltage.py b/pyleecan/Classes/InputVoltage.py index 12f658061..b84037efe 100644 --- a/pyleecan/Classes/InputVoltage.py +++ b/pyleecan/Classes/InputVoltage.py @@ -74,10 +74,10 @@ class InputVoltage(Input): def __init__( self, - rot_dir=-1, + rot_dir=None, angle_rotor_initial=0, PWM=None, - current_dir=-1, + current_dir=None, time=None, angle=None, Nt_tot=2048, diff --git a/pyleecan/Functions/Electrical/dqh_transformation.py b/pyleecan/Functions/Electrical/dqh_transformation.py index ade47c253..20fc2b91f 100644 --- a/pyleecan/Functions/Electrical/dqh_transformation.py +++ b/pyleecan/Functions/Electrical/dqh_transformation.py @@ -3,7 +3,8 @@ from SciDataTool import Data1D, DataTime from ...Functions.Winding.gen_phase_list import gen_name -from ...Functions.Load.import_class import import_class + +from ...Methods.Simulation.Input import PHASE_DIR_REF def n2dqh_DataTime(data_n, is_dqh_rms=True, phase_dir=None): @@ -355,9 +356,8 @@ def comp_Clarke_transform(n, is_inv=False, phase_dir=None): """ if phase_dir is None: - # Take phase rotation direction from default current rotation direction in InputVoltage - InputVoltage = import_class("pyleecan.Classes", "InputVoltage") - phase_dir = InputVoltage().current_dir + # Take phase rotation direction from reference current rotation direction + phase_dir = PHASE_DIR_REF elif phase_dir not in [-1, 1]: raise Exception("Cannot enforce phase_dir other than +1 or -1") diff --git a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv index 6db475332..470d804aa 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv @@ -1,5 +1,5 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -rot_dir,-,"Rotation direction of the rotor 1 trigo, -1 clockwise",,int,-1,-1,1,,Simulation,Input,gen_input,VERSION,1,Input to start the electrical module with voltage input +rot_dir,-,"Rotation direction of the rotor 1 trigo, -1 clockwise",,int,None,-1,1,,Simulation,Input,gen_input,VERSION,1,Input to start the electrical module with voltage input angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,set_OP_from_array,,, PWM,,Object to generate PWM signal,,ImportGenPWM,None,,,,,,,,, -current_dir,-,"Rotation direction of the stator currents 1 trigo, -1 clockwise",,int,-1,-1,1,,,,,,, +current_dir,-,"Rotation direction of the stator currents 1 trigo, -1 clockwise",,int,None,-1,1,,,,,,, diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py index 5cef6d5cb..4bf42ba10 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py @@ -8,6 +8,8 @@ from ....Functions.Electrical.dqh_transformation import dqh2n from ....Functions.Load.import_class import import_class +from ....Methods.Simulation.Input import CURRENT_DIR_REF + def comp_mmf_unit(self, Na=None, Nt=None, felec=1, current_dir=None): """Compute the winding unit magnetomotive force for given inputs @@ -56,7 +58,7 @@ def comp_mmf_unit(self, Na=None, Nt=None, felec=1, current_dir=None): if current_dir is not None: if current_dir in [-1, 1]: # Enforce input current_dir otherwise keep it as default - input.current_dir = current_dir + input.current_dir = CURRENT_DIR_REF else: raise Exception("Cannot enforce current_dir other than +1 or -1") diff --git a/pyleecan/Methods/Simulation/Input/__init__.py b/pyleecan/Methods/Simulation/Input/__init__.py index a9c497380..7545b24c0 100644 --- a/pyleecan/Methods/Simulation/Input/__init__.py +++ b/pyleecan/Methods/Simulation/Input/__init__.py @@ -1,4 +1,7 @@ -# -*- coding: utf-8 -*- +# Init conventions for input generation +ROT_DIR_REF = -1 +CURRENT_DIR_REF = -1 +PHASE_DIR_REF = -1 class InputError(Exception): diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index e373306af..54421281c 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -1,15 +1,11 @@ -from SciDataTool import Norm_ref -from numpy import ndarray, arange, searchsorted, ceil +from numpy import arange, searchsorted + +from SciDataTool import DataTime from ....Classes.OutElec import OutElec from ....Classes.Simulation import Simulation -from ....Classes.OPdq import OPdq -from ....Classes.OPslip import OPslip -from ....Methods.Simulation.Input import InputError -from ....Functions.Electrical.dqh_transformation import n2dqh, n2dqh_DataTime -from ....Functions.Winding.gen_phase_list import gen_name -from SciDataTool import Data1D, DataLinspace, DataTime +from ....Methods.Simulation.Input import CURRENT_DIR_REF, ROT_DIR_REF, InputError def gen_input(self): @@ -53,14 +49,14 @@ def gen_input(self): if self.rot_dir not in [-1, 1]: # Enforce rotor rotation direction to -1 logger.info("Enforcing rotor rotating direction to -1: clockwise rotation") - self.rot_dir = -1 + self.rot_dir = ROT_DIR_REF outgeo.rot_dir = self.rot_dir # Set current rotation direction if self.current_dir not in [-1, 1]: # Enforce current rotation direction to 1 logger.info("Enforcing current rotating direction to 1: clockwise rotation") - self.current_dir = 1 + self.current_dir = CURRENT_DIR_REF outgeo.current_dir = self.current_dir # Init permutation array of stator currents From b24c7f5a0bae22715f09eda03334cc170b094158 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Fri, 29 Oct 2021 00:37:33 +0200 Subject: [PATCH 159/167] [CC] add phase_dir in comp_mmf_unit and comp_mmf_dir --- .../Machine/LamSlotMultiWind/comp_mmf_dir.py | 4 ++- .../Machine/LamSlotMultiWind/comp_mmf_unit.py | 6 ++-- .../Machine/LamSlotWind/comp_mmf_dir.py | 8 +++-- .../Machine/LamSlotWind/comp_mmf_unit.py | 32 +++++++++++++------ 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_dir.py b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_dir.py index d592660e0..548051bcd 100644 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_dir.py +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_dir.py @@ -1,7 +1,7 @@ from ....Functions.Load.import_class import import_class -def comp_mmf_dir(self, current_dir, is_plot=False): +def comp_mmf_dir(self, current_dir=None, phase_dir=None, is_plot=False): """Compute the rotation direction of the fundamental magnetomotive force induced by the winding Parameters @@ -12,6 +12,8 @@ def comp_mmf_dir(self, current_dir, is_plot=False): Stator current frequency to consider current_dir: int Stator current rotation direction +/-1 + phase_dir: int + Stator winding phasor rotation direction +/-1 is_plot: bool True to plot fft2 of stator MMF diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py index 653d666f1..354fd8ef6 100644 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_unit.py @@ -1,7 +1,7 @@ from ....Functions.Load.import_class import import_class -def comp_mmf_unit(self, Na, Nt, felec=1, current_dir=1): +def comp_mmf_unit(self, Na, Nt, felec=1, current_dir=None, phase_dir=None): """Compute the winding Unit magnetomotive force Parameters @@ -16,6 +16,8 @@ def comp_mmf_unit(self, Na, Nt, felec=1, current_dir=1): Stator current frequency to consider current_dir: int Stator current rotation direction +/-1 + phase_dir: int + Stator winding phasor rotation direction +/-1 Returns ------- @@ -30,7 +32,7 @@ def comp_mmf_unit(self, Na, Nt, felec=1, current_dir=1): LamSlotWind = import_class("pyleecan.Classes", "LamSlotWind") MMF_U, WF = LamSlotWind.comp_mmf_unit( - self, Na=Na, Nt=Nt, felec=felec, current_dir=current_dir + self, Na=Na, Nt=Nt, felec=felec, current_dir=current_dir, phase_dir=phase_dir ) return MMF_U, WF diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_dir.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_dir.py index 17cfbbff2..5e0229b92 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_dir.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_dir.py @@ -1,7 +1,7 @@ from numpy import sign -def comp_mmf_dir(self, current_dir=None, is_plot=False): +def comp_mmf_dir(self, current_dir=None, phase_dir=None, is_plot=False): """Compute the rotation direction of the fundamental magnetomotive force induced by the winding Parameters @@ -10,6 +10,8 @@ def comp_mmf_dir(self, current_dir=None, is_plot=False): A LamSlotWind object current_dir: int Stator current rotation direction +/-1 + phase_dir: int + Stator winding phasor rotation direction +/-1 is_plot: bool True to plot fft2 of stator MMF @@ -22,7 +24,9 @@ def comp_mmf_dir(self, current_dir=None, is_plot=False): # Compute unit mmf # 20 points per pole over time and space is enough to capture rotating direction of fundamental mmf - MMF, _ = self.comp_mmf_unit(Nt=20 * p, Na=20 * p, current_dir=current_dir) + MMF, _ = self.comp_mmf_unit( + Nt=20 * p, Na=20 * p, current_dir=current_dir, phase_dir=phase_dir + ) # Extract fundamental from unit mmf result_p = MMF.get_harmonics(1, "freqs>0", "wavenumber=" + str(p)) diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py index 4bf42ba10..0dc9b4ece 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py @@ -8,10 +8,10 @@ from ....Functions.Electrical.dqh_transformation import dqh2n from ....Functions.Load.import_class import import_class -from ....Methods.Simulation.Input import CURRENT_DIR_REF +from ....Methods.Simulation.Input import CURRENT_DIR_REF, PHASE_DIR_REF -def comp_mmf_unit(self, Na=None, Nt=None, felec=1, current_dir=None): +def comp_mmf_unit(self, Na=None, Nt=None, felec=1, current_dir=None, phase_dir=None): """Compute the winding unit magnetomotive force for given inputs Parameters @@ -26,6 +26,8 @@ def comp_mmf_unit(self, Na=None, Nt=None, felec=1, current_dir=None): Stator current frequency to consider current_dir: int Stator current rotation direction +/-1 + phase_dir: int + Stator winding phasor rotation direction +/-1 Returns ------- @@ -49,19 +51,29 @@ def comp_mmf_unit(self, Na=None, Nt=None, felec=1, current_dir=None): # Get stator winding number of phases qs = self.winding.qs - if machine.is_synchronous(): - OPclass = OPdq - else: - OPclass = OPslip - InputVoltage = import_class("pyleecan.Classes", "InputVoltage") - input = InputVoltage(Na_tot=Na, Nt_tot=Nt, OP=OPclass(felec=felec)) if current_dir is not None: if current_dir in [-1, 1]: # Enforce input current_dir otherwise keep it as default - input.current_dir = CURRENT_DIR_REF + current_dir = CURRENT_DIR_REF + else: + raise Exception("Cannot enforce current_dir other than +1 or -1") + + if phase_dir is not None: + if phase_dir in [-1, 1]: + # Enforce input phase_dir otherwise keep it as default + phase_dir = PHASE_DIR_REF else: raise Exception("Cannot enforce current_dir other than +1 or -1") + if machine.is_synchronous(): + OPclass = OPdq + else: + OPclass = OPslip + InputVoltage = import_class("pyleecan.Classes", "InputVoltage") + input = InputVoltage( + Na_tot=Na, Nt_tot=Nt, OP=OPclass(felec=felec), current_dir=current_dir + ) + axes_dict = input.comp_axes( axes_list=["time", "angle", "phase_S", "phase_R"], machine=machine, @@ -82,7 +94,7 @@ def comp_mmf_unit(self, Na=None, Nt=None, felec=1, current_dir=None): ) Idq = zeros((angle_elec.size, 3)) Idq[:, 0] = ones(angle_elec.size) - I = dqh2n(Idq, angle_elec, n=qs, is_n_rms=False) + I = dqh2n(Idq, angle_elec, n=qs, is_n_rms=False, phase_dir=phase_dir) # Compute unit mmf mmf_u = squeeze(dot(I, wf)) From da27ac81a456d9f33e440398e160e6a2fd7dadc2 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Fri, 29 Oct 2021 18:36:45 +0200 Subject: [PATCH 160/167] [CO] Reorganize current_dir and phase_dir in InputVoltage/OutElec [NF] Add method in dqh_transformation.py to calculate phase rotating direction from an n-phase signal --- .../Electrical/test_EEC_ELUT_PMSM.py | 32 ++++- Tests/Validation/Electrical/test_EEC_PMSM.py | 11 +- .../Magnetics/test_FEMM_periodicity.py | 1 - pyleecan/Classes/Class_Dict.json | 55 ++++++--- pyleecan/Classes/InputCurrent.py | 4 + pyleecan/Classes/InputFlux.py | 4 + pyleecan/Classes/InputVoltage.py | 36 +++++- pyleecan/Classes/LUT.py | 55 ++++++++- pyleecan/Classes/LUTdq.py | 7 +- pyleecan/Classes/LUTslip.py | 7 +- pyleecan/Classes/OutElec.py | 112 ++++++++++-------- pyleecan/Classes/OutGeo.py | 34 +----- .../Electrical/dqh_transformation.py | 112 +++++++++++++----- .../Dialog/DMachineSetup/SWinding/SWinding.py | 16 +-- .../Generator/ClassesRef/Output/OutElec.csv | 3 +- .../Generator/ClassesRef/Output/OutGeo.csv | 3 +- .../ClassesRef/Simulation/InputVoltage.csv | 5 +- .../Generator/ClassesRef/Simulation/LUT.csv | 3 +- .../Machine/LamSlotMultiWind/comp_mmf_dir.py | 2 +- .../Machine/LamSlotWind/comp_mmf_unit.py | 28 ++--- pyleecan/Methods/Output/OutElec/get_I_fund.py | 8 +- pyleecan/Methods/Output/OutElec/get_Us.py | 6 +- .../Methods/Output/OutElec/get_Us_harm.py | 2 +- pyleecan/Methods/Output/OutElec/store.py | 2 +- pyleecan/Methods/Post/PostLUT/run.py | 12 +- .../FluxLinkFEMM/comp_fluxlinkage.py | 4 +- .../Simulation/IndMagFEMM/comp_inductance.py | 4 +- .../Simulation/InputVoltage/gen_input.py | 41 ++++--- .../Methods/Simulation/LUT/get_phase_dir.py | 22 ++++ .../Simulation/LUTdq/get_Phidqh_mag.py | 1 + .../Simulation/LUTdq/get_Phidqh_mean.py | 1 + 31 files changed, 419 insertions(+), 214 deletions(-) create mode 100644 pyleecan/Methods/Simulation/LUT/get_phase_dir.py diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py index d93065637..75d890a60 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py @@ -22,7 +22,10 @@ from pyleecan.Functions.load import load from pyleecan.Functions.Plot import dict_2D -from pyleecan.Functions.Electrical.dqh_transformation import n2dqh_DataTime +from pyleecan.Functions.Electrical.dqh_transformation import ( + get_phase_dir_DataTime, + n2dqh_DataTime, +) from pyleecan.definitions import DATA_DIR from Tests import save_validation_path as save_path @@ -97,6 +100,9 @@ def test_EEC_ELUT_PMSM_calc(n_Id=5, n_Iq=5): ELUT = out.simu.var_simu.postproc_list[0].LUT + # Check phase_dir calculation + assert ELUT.get_phase_dir() == get_phase_dir_DataTime(ELUT.Phi_wind[0]) + # Check flux linkage dqh values Phi_dqh_mean = ELUT.get_Phidqh_mean() OP_list = OP_matrix[:, 1:3].tolist() @@ -104,6 +110,7 @@ def test_EEC_ELUT_PMSM_calc(n_Id=5, n_Iq=5): Phi_dqh0 = n2dqh_DataTime( ELUT.Phi_wind[ii], is_dqh_rms=True, + phase_dir=ELUT.get_phase_dir(), ) Phi_dqh0_mean = Phi_dqh0.get_along("time=mean", "phase")[Phi_dqh0.symbol] assert_almost_equal(Phi_dqh0_mean, Phi_dqh_mean[ii, :], decimal=20) @@ -185,7 +192,7 @@ def test_EEC_ELUT_PMSM_MTPA(test_ELUT, n_Id=51, n_Iq=101): "is_show_fig": is_show_fig, } - # Plot torque map + # Plot torque maps plot_3D( Zdata=Tem_interp.reshape((n_Iq, n_Id)).T, zlabel="Average Torque [N.m]", @@ -193,6 +200,20 @@ def test_EEC_ELUT_PMSM_MTPA(test_ELUT, n_Id=51, n_Iq=101): save_path=join(save_path, name + "_torque_map.png"), **dict_map, ) + plot_3D( + Zdata=Tem_sync.reshape((n_Iq, n_Id)).T, + zlabel="Synchrnous Torque [N.m]", + title="Torque map in dq plane", + save_path=join(save_path, name + "_torque_sync_map.png"), + **dict_map, + ) + plot_3D( + Zdata=Tem_rel.reshape((n_Iq, n_Id)).T, + zlabel="Reluctant Torque [N.m]", + title="Torque map in dq plane", + save_path=join(save_path, name + "_torque_rel_map.png"), + **dict_map, + ) # Plot Phi_d map plot_3D( @@ -217,7 +238,7 @@ def test_EEC_ELUT_PMSM_MTPA(test_ELUT, n_Id=51, n_Iq=101): I_max = 250 / np.sqrt(2) Imax_interp = np.sqrt(Id ** 2 + Iq ** 2) # Maximum voltage [Vrms] - U_max = 300 + U_max = 200 # Speed vector Nspeed = 50 N0_min = 50 @@ -271,9 +292,6 @@ def test_EEC_ELUT_PMSM_MTPA(test_ELUT, n_Id=51, n_Iq=101): # Finding index of operating point giving lowest current jmax = np.argmin(np.abs(Imax_interp[j0])) - # print(Id_interp[j0][jmax]) - # print(Iq_interp[j0][jmax]) - else: # Finding indices of operating points satisfying Vmax and XImax(i) voltage and torque limitations j0 = np.logical_and( @@ -339,6 +357,8 @@ def test_EEC_ELUT_PMSM_MTPA(test_ELUT, n_Id=51, n_Iq=101): # is_show_fig=is_show_fig, ) + pass + @pytest.mark.long_5s @pytest.mark.MagFEMM diff --git a/Tests/Validation/Electrical/test_EEC_PMSM.py b/Tests/Validation/Electrical/test_EEC_PMSM.py index fd9d2b038..686701c65 100644 --- a/Tests/Validation/Electrical/test_EEC_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_PMSM.py @@ -113,7 +113,7 @@ def test_EEC_PMSM_sync_rel(nb_worker=int(0.5 * cpu_count())): OP_matrix[:, 0] = 2000 # Set I0 = 250/sqrt(2) [A] (RMS) for all simulations OP_matrix[:, 1] = 250 / sqrt(2) - # Set Phi0 from 60° to 180° + # Set Phi0 from 60� to 180� OP_matrix[:, 2] = Phi0_ref # Set reference torque from Yang et al, 2013 OP_matrix[:, 3] = Tem_av_ref @@ -128,7 +128,7 @@ def test_EEC_PMSM_sync_rel(nb_worker=int(0.5 * cpu_count())): array([x * 180 / pi for x in out.xoutput_dict["Phi0"].result]), [out.xoutput_dict["Tem_av_ref"].result, Tem_av_ref], legend_list=["Pyleecan", "Yang et al, 2013"], - xlabel="Current angle [°]", + xlabel="Current angle [�]", ylabel="Electrical torque [N.m]", title="Electrical torque vs current angle", save_path=join(save_path, "test_EEC_PMSM_validation.png"), @@ -154,7 +154,7 @@ def test_EEC_PMSM_sync_rel(nb_worker=int(0.5 * cpu_count())): array([x * 180 / pi for x in out.xoutput_dict["Phi0"].result]), [out.xoutput_dict["Tem_av_ref"].result, Tem_sync, Tem_rel], legend_list=["Overall", "Synchronous", "Reluctant"], - xlabel="Current angle [°]", + xlabel="Current angle [�]", ylabel="Electrical torque [N.m]", title="Electrical torque vs current angle", save_path=join(save_path, "test_EEC_PMSM_sync_rel.png"), @@ -167,7 +167,6 @@ def test_EEC_PMSM_sync_rel(nb_worker=int(0.5 * cpu_count())): # To run it without pytest if __name__ == "__main__": - out, out2 = test_EEC_PMSM() - - test_EEC_PMSM_sync_rel() + out = test_EEC_PMSM() + out = test_EEC_PMSM_sync_rel() print("Done") diff --git a/Tests/Validation/Magnetics/test_FEMM_periodicity.py b/Tests/Validation/Magnetics/test_FEMM_periodicity.py index 210e9a5aa..bfa5ac027 100644 --- a/Tests/Validation/Magnetics/test_FEMM_periodicity.py +++ b/Tests/Validation/Magnetics/test_FEMM_periodicity.py @@ -621,7 +621,6 @@ def test_Ring_Magnet(): # To run it without pytest if __name__ == "__main__": - # out, out2 = test_FEMM_periodicity_angle() # out3, out4 = test_FEMM_periodicity_time() # out5, out6 = test_FEMM_periodicity_time_no_periodicity_a() diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 9d54648f0..0af4b39ba 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4271,7 +4271,7 @@ "path": "pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv", "properties": [ { - "desc": "Rotation direction of the rotor 1 trigo, -1 clockwise", + "desc": "Rotation direction of the rotor\u00c2\u00a0: rot_dir*N0, default value given by ROT_DIR_REF", "max": "1", "min": "-1", "name": "rot_dir", @@ -4298,7 +4298,16 @@ "value": null }, { - "desc": "Rotation direction of the stator currents 1 trigo, -1 clockwise", + "desc": "Rotation direction of the stator phases\u00c2\u00a0: phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF", + "max": "1", + "min": "-1", + "name": "phase_dir", + "type": "int", + "unit": "", + "value": null + }, + { + "desc": "Rotation direction of the stator currents\u00c2\u00a0: current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF", "max": "1", "min": "-1", "name": "current_dir", @@ -4367,7 +4376,8 @@ "desc": "Abstract class for Look Up Table (LUT)", "is_internal": false, "methods": [ - "get_param_dict" + "get_param_dict", + "get_phase_dir" ], "mother": "", "name": "LUT", @@ -4409,6 +4419,15 @@ "type": "ndarray", "unit": "", "value": null + }, + { + "desc": "Rotation direction of the stator phases\u00c2\u00a0: phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF", + "max": "1", + "min": "-1", + "name": "phase_dir", + "type": "int", + "unit": "", + "value": null } ] }, @@ -8269,13 +8288,22 @@ "value": "None" }, { - "desc": "Phase permutation array to reverse rotating direction of stator mmf fundamental ", - "max": "", - "min": "", - "name": "perm_phases", - "type": "ndarray", + "desc": "Rotation direction of the stator phases\u00c2\u00a0: phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF", + "max": "1", + "min": "-1", + "name": "phase_dir", + "type": "int", "unit": "", "value": null + }, + { + "desc": "Rotation direction of the stator currents\u00c2\u00a0: current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF", + "max": "1", + "min": "-1", + "name": "current_dir", + "type": "int", + "unit": "-", + "value": null } ] }, @@ -8433,7 +8461,7 @@ "value": null }, { - "desc": "rotation direction of rotor(rot_dir = -1 by default, i.e. clockwise rotation)", + "desc": "Rotation direction of the rotor\u00c2\u00a0: rot_dir*N0, default value given by ROT_DIR_REF", "max": "1", "min": "-1", "name": "rot_dir", @@ -8503,15 +8531,6 @@ "type": "bool", "unit": "-", "value": null - }, - { - "desc": "rotation direction of the stator currents(current_dir = -1 by default, i.e. clockwise rotation)", - "max": "1", - "min": "-1", - "name": "current_dir", - "type": "int", - "unit": "-", - "value": null } ] }, diff --git a/pyleecan/Classes/InputCurrent.py b/pyleecan/Classes/InputCurrent.py index 49739c971..4dd115030 100644 --- a/pyleecan/Classes/InputCurrent.py +++ b/pyleecan/Classes/InputCurrent.py @@ -95,6 +95,7 @@ def __init__( rot_dir=None, angle_rotor_initial=0, PWM=None, + phase_dir=None, current_dir=None, time=None, angle=None, @@ -130,6 +131,8 @@ def __init__( angle_rotor_initial = init_dict["angle_rotor_initial"] if "PWM" in list(init_dict.keys()): PWM = init_dict["PWM"] + if "phase_dir" in list(init_dict.keys()): + phase_dir = init_dict["phase_dir"] if "current_dir" in list(init_dict.keys()): current_dir = init_dict["current_dir"] if "time" in list(init_dict.keys()): @@ -152,6 +155,7 @@ def __init__( rot_dir=rot_dir, angle_rotor_initial=angle_rotor_initial, PWM=PWM, + phase_dir=phase_dir, current_dir=current_dir, time=time, angle=angle, diff --git a/pyleecan/Classes/InputFlux.py b/pyleecan/Classes/InputFlux.py index e3cbd7f86..93753419a 100644 --- a/pyleecan/Classes/InputFlux.py +++ b/pyleecan/Classes/InputFlux.py @@ -67,6 +67,7 @@ def __init__( rot_dir=None, angle_rotor_initial=0, PWM=None, + phase_dir=None, current_dir=None, time=None, angle=None, @@ -118,6 +119,8 @@ def __init__( angle_rotor_initial = init_dict["angle_rotor_initial"] if "PWM" in list(init_dict.keys()): PWM = init_dict["PWM"] + if "phase_dir" in list(init_dict.keys()): + phase_dir = init_dict["phase_dir"] if "current_dir" in list(init_dict.keys()): current_dir = init_dict["current_dir"] if "time" in list(init_dict.keys()): @@ -148,6 +151,7 @@ def __init__( rot_dir=rot_dir, angle_rotor_initial=angle_rotor_initial, PWM=PWM, + phase_dir=phase_dir, current_dir=current_dir, time=time, angle=angle, diff --git a/pyleecan/Classes/InputVoltage.py b/pyleecan/Classes/InputVoltage.py index b84037efe..5c3fa823c 100644 --- a/pyleecan/Classes/InputVoltage.py +++ b/pyleecan/Classes/InputVoltage.py @@ -77,6 +77,7 @@ def __init__( rot_dir=None, angle_rotor_initial=0, PWM=None, + phase_dir=None, current_dir=None, time=None, angle=None, @@ -108,6 +109,8 @@ def __init__( angle_rotor_initial = init_dict["angle_rotor_initial"] if "PWM" in list(init_dict.keys()): PWM = init_dict["PWM"] + if "phase_dir" in list(init_dict.keys()): + phase_dir = init_dict["phase_dir"] if "current_dir" in list(init_dict.keys()): current_dir = init_dict["current_dir"] if "time" in list(init_dict.keys()): @@ -126,6 +129,7 @@ def __init__( self.rot_dir = rot_dir self.angle_rotor_initial = angle_rotor_initial self.PWM = PWM + self.phase_dir = phase_dir self.current_dir = current_dir # Call Input init super(InputVoltage, self).__init__( @@ -149,6 +153,7 @@ def __str__(self): InputVoltage_str += "PWM = " + tmp else: InputVoltage_str += "PWM = None" + linesep + linesep + InputVoltage_str += "phase_dir = " + str(self.phase_dir) + linesep InputVoltage_str += "current_dir = " + str(self.current_dir) + linesep return InputVoltage_str @@ -167,6 +172,8 @@ def __eq__(self, other): return False if other.PWM != self.PWM: return False + if other.phase_dir != self.phase_dir: + return False if other.current_dir != self.current_dir: return False return True @@ -192,6 +199,8 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".PWM None mismatch") elif self.PWM is not None: diff_list.extend(self.PWM.compare(other.PWM, name=name + ".PWM")) + if other._phase_dir != self._phase_dir: + diff_list.append(name + ".phase_dir") if other._current_dir != self._current_dir: diff_list.append(name + ".current_dir") # Filter ignore differences @@ -208,6 +217,7 @@ def __sizeof__(self): S += getsizeof(self.rot_dir) S += getsizeof(self.angle_rotor_initial) S += getsizeof(self.PWM) + S += getsizeof(self.phase_dir) S += getsizeof(self.current_dir) return S @@ -238,6 +248,7 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): keep_function=keep_function, **kwargs ) + InputVoltage_dict["phase_dir"] = self.phase_dir InputVoltage_dict["current_dir"] = self.current_dir # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name @@ -251,6 +262,7 @@ def _set_None(self): self.angle_rotor_initial = None if self.PWM is not None: self.PWM._set_None() + self.phase_dir = None self.current_dir = None # Set to None the properties inherited from Input super(InputVoltage, self)._set_None() @@ -267,7 +279,7 @@ def _set_rot_dir(self, value): rot_dir = property( fget=_get_rot_dir, fset=_set_rot_dir, - doc=u"""Rotation direction of the rotor 1 trigo, -1 clockwise + doc=u"""Rotation direction of the rotor : rot_dir*N0, default value given by ROT_DIR_REF :Type: int :min: -1 @@ -321,6 +333,26 @@ def _set_PWM(self, value): """, ) + def _get_phase_dir(self): + """getter of phase_dir""" + return self._phase_dir + + def _set_phase_dir(self, value): + """setter of phase_dir""" + check_var("phase_dir", value, "int", Vmin=-1, Vmax=1) + self._phase_dir = value + + phase_dir = property( + fget=_get_phase_dir, + fset=_set_phase_dir, + doc=u"""Rotation direction of the stator phases : phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF + + :Type: int + :min: -1 + :max: 1 + """, + ) + def _get_current_dir(self): """getter of current_dir""" return self._current_dir @@ -333,7 +365,7 @@ def _set_current_dir(self, value): current_dir = property( fget=_get_current_dir, fset=_set_current_dir, - doc=u"""Rotation direction of the stator currents 1 trigo, -1 clockwise + doc=u"""Rotation direction of the stator currents : current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF :Type: int :min: -1 diff --git a/pyleecan/Classes/LUT.py b/pyleecan/Classes/LUT.py index e058442dd..914768260 100644 --- a/pyleecan/Classes/LUT.py +++ b/pyleecan/Classes/LUT.py @@ -22,6 +22,11 @@ except ImportError as error: get_param_dict = error +try: + from ..Methods.Simulation.LUT.get_phase_dir import get_phase_dir +except ImportError as error: + get_phase_dir = error + from numpy import array, array_equal from ._check import InitUnKnowClassError @@ -32,6 +37,7 @@ class LUT(FrozenClass): VERSION = 1 + # Check ImportError to remove unnecessary dependencies in unused method # cf Methods.Simulation.LUT.get_param_dict if isinstance(get_param_dict, ImportError): get_param_dict = property( @@ -43,6 +49,15 @@ class LUT(FrozenClass): ) else: get_param_dict = get_param_dict + # cf Methods.Simulation.LUT.get_phase_dir + if isinstance(get_phase_dir, ImportError): + get_phase_dir = property( + fget=lambda x: raise_( + ImportError("Can't use LUT method get_phase_dir: " + str(get_phase_dir)) + ) + ) + else: + get_phase_dir = get_phase_dir # save and copy methods are available in all object save = save copy = copy @@ -50,7 +65,14 @@ class LUT(FrozenClass): get_logger = get_logger def __init__( - self, R1=None, L1=None, T1_ref=20, OP_matrix=None, init_dict=None, init_str=None + self, + R1=None, + L1=None, + T1_ref=20, + OP_matrix=None, + phase_dir=None, + init_dict=None, + init_str=None, ): """Constructor of the class. Can be use in three ways : - __init__ (arg1 = 1, arg3 = 5) every parameters have name and default values @@ -75,12 +97,15 @@ def __init__( T1_ref = init_dict["T1_ref"] if "OP_matrix" in list(init_dict.keys()): OP_matrix = init_dict["OP_matrix"] + if "phase_dir" in list(init_dict.keys()): + phase_dir = init_dict["phase_dir"] # Set the properties (value check and convertion are done in setter) self.parent = None self.R1 = R1 self.L1 = L1 self.T1_ref = T1_ref self.OP_matrix = OP_matrix + self.phase_dir = phase_dir # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -103,6 +128,7 @@ def __str__(self): + linesep + linesep ) + LUT_str += "phase_dir = " + str(self.phase_dir) + linesep return LUT_str def __eq__(self, other): @@ -118,6 +144,8 @@ def __eq__(self, other): return False if not array_equal(other.OP_matrix, self.OP_matrix): return False + if other.phase_dir != self.phase_dir: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -136,6 +164,8 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".T1_ref") if not array_equal(other.OP_matrix, self.OP_matrix): diff_list.append(name + ".OP_matrix") + if other._phase_dir != self._phase_dir: + diff_list.append(name + ".phase_dir") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -148,6 +178,7 @@ def __sizeof__(self): S += getsizeof(self.L1) S += getsizeof(self.T1_ref) S += getsizeof(self.OP_matrix) + S += getsizeof(self.phase_dir) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -178,6 +209,7 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): raise Exception( "Unknown type_handle_ndarray: " + str(type_handle_ndarray) ) + LUT_dict["phase_dir"] = self.phase_dir # The class name is added to the dict for deserialisation purpose LUT_dict["__class__"] = "LUT" return LUT_dict @@ -189,6 +221,7 @@ def _set_None(self): self.L1 = None self.T1_ref = None self.OP_matrix = None + self.phase_dir = None def _get_R1(self): """getter of R1""" @@ -268,3 +301,23 @@ def _set_OP_matrix(self, value): :Type: ndarray """, ) + + def _get_phase_dir(self): + """getter of phase_dir""" + return self._phase_dir + + def _set_phase_dir(self, value): + """setter of phase_dir""" + check_var("phase_dir", value, "int", Vmin=-1, Vmax=1) + self._phase_dir = value + + phase_dir = property( + fget=_get_phase_dir, + fset=_set_phase_dir, + doc=u"""Rotation direction of the stator phases : phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF + + :Type: int + :min: -1 + :max: 1 + """, + ) diff --git a/pyleecan/Classes/LUTdq.py b/pyleecan/Classes/LUTdq.py index e0a245700..4a0d36c74 100644 --- a/pyleecan/Classes/LUTdq.py +++ b/pyleecan/Classes/LUTdq.py @@ -224,6 +224,7 @@ def __init__( L1=None, T1_ref=20, OP_matrix=None, + phase_dir=None, init_dict=None, init_str=None, ): @@ -260,6 +261,8 @@ def __init__( T1_ref = init_dict["T1_ref"] if "OP_matrix" in list(init_dict.keys()): OP_matrix = init_dict["OP_matrix"] + if "phase_dir" in list(init_dict.keys()): + phase_dir = init_dict["phase_dir"] # Set the properties (value check and convertion are done in setter) self.Phi_dqh_mean = Phi_dqh_mean self.Tmag_ref = Tmag_ref @@ -267,7 +270,9 @@ def __init__( self.Phi_wind = Phi_wind self.Phi_dqh_interp = Phi_dqh_interp # Call LUT init - super(LUTdq, self).__init__(R1=R1, L1=L1, T1_ref=T1_ref, OP_matrix=OP_matrix) + super(LUTdq, self).__init__( + R1=R1, L1=L1, T1_ref=T1_ref, OP_matrix=OP_matrix, phase_dir=phase_dir + ) # The class is frozen (in LUT init), for now it's impossible to # add new properties diff --git a/pyleecan/Classes/LUTslip.py b/pyleecan/Classes/LUTslip.py index 03634f15c..ba45f530c 100644 --- a/pyleecan/Classes/LUTslip.py +++ b/pyleecan/Classes/LUTslip.py @@ -109,6 +109,7 @@ def __init__( L1=None, T1_ref=20, OP_matrix=None, + phase_dir=None, init_dict=None, init_str=None, ): @@ -145,6 +146,8 @@ def __init__( T1_ref = init_dict["T1_ref"] if "OP_matrix" in list(init_dict.keys()): OP_matrix = init_dict["OP_matrix"] + if "phase_dir" in list(init_dict.keys()): + phase_dir = init_dict["phase_dir"] # Set the properties (value check and convertion are done in setter) self.Phi_m = Phi_m self.I_m = I_m @@ -152,7 +155,9 @@ def __init__( self.R2 = R2 self.L2 = L2 # Call LUT init - super(LUTslip, self).__init__(R1=R1, L1=L1, T1_ref=T1_ref, OP_matrix=OP_matrix) + super(LUTslip, self).__init__( + R1=R1, L1=L1, T1_ref=T1_ref, OP_matrix=OP_matrix, phase_dir=phase_dir + ) # The class is frozen (in LUT init), for now it's impossible to # add new properties diff --git a/pyleecan/Classes/OutElec.py b/pyleecan/Classes/OutElec.py index 1f89af681..5b1f472ce 100644 --- a/pyleecan/Classes/OutElec.py +++ b/pyleecan/Classes/OutElec.py @@ -7,7 +7,7 @@ from os import linesep from sys import getsizeof from logging import getLogger -from ._check import set_array, check_var, raise_ +from ._check import check_var, raise_ from ..Functions.get_logger import get_logger from ..Functions.save import save from ..Functions.copy import copy @@ -53,7 +53,6 @@ store = error -from numpy import array, array_equal from ._check import InitUnKnowClassError from .OutInternal import OutInternal from .OP import OP @@ -149,7 +148,8 @@ def __init__( Pem_av_ref=None, Tem_av_ref=None, Is_harm=None, - perm_phases=None, + phase_dir=None, + current_dir=None, init_dict=None, init_str=None, ): @@ -194,8 +194,10 @@ def __init__( Tem_av_ref = init_dict["Tem_av_ref"] if "Is_harm" in list(init_dict.keys()): Is_harm = init_dict["Is_harm"] - if "perm_phases" in list(init_dict.keys()): - perm_phases = init_dict["perm_phases"] + if "phase_dir" in list(init_dict.keys()): + phase_dir = init_dict["phase_dir"] + if "current_dir" in list(init_dict.keys()): + current_dir = init_dict["current_dir"] # Set the properties (value check and convertion are done in setter) self.parent = None self.axes_dict = axes_dict @@ -211,7 +213,8 @@ def __init__( self.Pem_av_ref = Pem_av_ref self.Tem_av_ref = Tem_av_ref self.Is_harm = Is_harm - self.perm_phases = perm_phases + self.phase_dir = phase_dir + self.current_dir = current_dir # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -247,13 +250,8 @@ def __str__(self): OutElec_str += "Pem_av_ref = " + str(self.Pem_av_ref) + linesep OutElec_str += "Tem_av_ref = " + str(self.Tem_av_ref) + linesep OutElec_str += "Is_harm = " + str(self.Is_harm) + linesep + linesep - OutElec_str += ( - "perm_phases = " - + linesep - + str(self.perm_phases).replace(linesep, linesep + "\t") - + linesep - + linesep - ) + OutElec_str += "phase_dir = " + str(self.phase_dir) + linesep + OutElec_str += "current_dir = " + str(self.current_dir) + linesep return OutElec_str def __eq__(self, other): @@ -287,7 +285,9 @@ def __eq__(self, other): return False if other.Is_harm != self.Is_harm: return False - if not array_equal(other.perm_phases, self.perm_phases): + if other.phase_dir != self.phase_dir: + return False + if other.current_dir != self.current_dir: return False return True @@ -370,8 +370,10 @@ def compare(self, other, name="self", ignore_list=None): diff_list.extend( self.Is_harm.compare(other.Is_harm, name=name + ".Is_harm") ) - if not array_equal(other.perm_phases, self.perm_phases): - diff_list.append(name + ".perm_phases") + if other._phase_dir != self._phase_dir: + diff_list.append(name + ".phase_dir") + if other._current_dir != self._current_dir: + diff_list.append(name + ".current_dir") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -395,7 +397,8 @@ def __sizeof__(self): S += getsizeof(self.Pem_av_ref) S += getsizeof(self.Tem_av_ref) S += getsizeof(self.Is_harm) - S += getsizeof(self.perm_phases) + S += getsizeof(self.phase_dir) + S += getsizeof(self.current_dir) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -484,19 +487,8 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): keep_function=keep_function, **kwargs ) - if self.perm_phases is None: - OutElec_dict["perm_phases"] = None - else: - if type_handle_ndarray == 0: - OutElec_dict["perm_phases"] = self.perm_phases.tolist() - elif type_handle_ndarray == 1: - OutElec_dict["perm_phases"] = self.perm_phases.copy() - elif type_handle_ndarray == 2: - OutElec_dict["perm_phases"] = self.perm_phases - else: - raise Exception( - "Unknown type_handle_ndarray: " + str(type_handle_ndarray) - ) + OutElec_dict["phase_dir"] = self.phase_dir + OutElec_dict["current_dir"] = self.current_dir # The class name is added to the dict for deserialisation purpose OutElec_dict["__class__"] = "OutElec" return OutElec_dict @@ -519,7 +511,8 @@ def _set_None(self): self.Pem_av_ref = None self.Tem_av_ref = None self.Is_harm = None - self.perm_phases = None + self.phase_dir = None + self.current_dir = None def _get_axes_dict(self): """getter of axes_dict""" @@ -835,27 +828,42 @@ def _set_Is_harm(self, value): """, ) - def _get_perm_phases(self): - """getter of perm_phases""" - return self._perm_phases + def _get_phase_dir(self): + """getter of phase_dir""" + return self._phase_dir - def _set_perm_phases(self, value): - """setter of perm_phases""" - if type(value) is int and value == -1: - value = array([]) - elif type(value) is list: - try: - value = array(value) - except: - pass - check_var("perm_phases", value, "ndarray") - self._perm_phases = value - - perm_phases = property( - fget=_get_perm_phases, - fset=_set_perm_phases, - doc=u"""Phase permutation array to reverse rotating direction of stator mmf fundamental - - :Type: ndarray + def _set_phase_dir(self, value): + """setter of phase_dir""" + check_var("phase_dir", value, "int", Vmin=-1, Vmax=1) + self._phase_dir = value + + phase_dir = property( + fget=_get_phase_dir, + fset=_set_phase_dir, + doc=u"""Rotation direction of the stator phases : phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF + + :Type: int + :min: -1 + :max: 1 + """, + ) + + def _get_current_dir(self): + """getter of current_dir""" + return self._current_dir + + def _set_current_dir(self, value): + """setter of current_dir""" + check_var("current_dir", value, "int", Vmin=-1, Vmax=1) + self._current_dir = value + + current_dir = property( + fget=_get_current_dir, + fset=_set_current_dir, + doc=u"""Rotation direction of the stator currents : current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF + + :Type: int + :min: -1 + :max: 1 """, ) diff --git a/pyleecan/Classes/OutGeo.py b/pyleecan/Classes/OutGeo.py index 125545b4a..65da2529b 100644 --- a/pyleecan/Classes/OutGeo.py +++ b/pyleecan/Classes/OutGeo.py @@ -48,7 +48,6 @@ def __init__( axes_dict=None, per_t_R=None, is_antiper_t_R=None, - current_dir=None, init_dict=None, init_str=None, ): @@ -99,8 +98,6 @@ def __init__( per_t_R = init_dict["per_t_R"] if "is_antiper_t_R" in list(init_dict.keys()): is_antiper_t_R = init_dict["is_antiper_t_R"] - if "current_dir" in list(init_dict.keys()): - current_dir = init_dict["current_dir"] # Set the properties (value check and convertion are done in setter) self.parent = None self.stator = stator @@ -119,7 +116,6 @@ def __init__( self.axes_dict = axes_dict self.per_t_R = per_t_R self.is_antiper_t_R = is_antiper_t_R - self.current_dir = current_dir # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -156,7 +152,6 @@ def __str__(self): OutGeo_str += "axes_dict = " + str(self.axes_dict) + linesep + linesep OutGeo_str += "per_t_R = " + str(self.per_t_R) + linesep OutGeo_str += "is_antiper_t_R = " + str(self.is_antiper_t_R) + linesep - OutGeo_str += "current_dir = " + str(self.current_dir) + linesep return OutGeo_str def __eq__(self, other): @@ -196,8 +191,6 @@ def __eq__(self, other): return False if other.is_antiper_t_R != self.is_antiper_t_R: return False - if other.current_dir != self.current_dir: - return False return True def compare(self, other, name="self", ignore_list=None): @@ -261,8 +254,6 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".per_t_R") if other._is_antiper_t_R != self._is_antiper_t_R: diff_list.append(name + ".is_antiper_t_R") - if other._current_dir != self._current_dir: - diff_list.append(name + ".current_dir") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -289,7 +280,6 @@ def __sizeof__(self): S += getsizeof(value) + getsizeof(key) S += getsizeof(self.per_t_R) S += getsizeof(self.is_antiper_t_R) - S += getsizeof(self.current_dir) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -346,7 +336,6 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): OutGeo_dict["axes_dict"][key] = None OutGeo_dict["per_t_R"] = self.per_t_R OutGeo_dict["is_antiper_t_R"] = self.is_antiper_t_R - OutGeo_dict["current_dir"] = self.current_dir # The class name is added to the dict for deserialisation purpose OutGeo_dict["__class__"] = "OutGeo" return OutGeo_dict @@ -372,7 +361,6 @@ def _set_None(self): self.axes_dict = None self.per_t_R = None self.is_antiper_t_R = None - self.current_dir = None def _get_stator(self): """getter of stator""" @@ -554,7 +542,7 @@ def _set_rot_dir(self, value): rot_dir = property( fget=_get_rot_dir, fset=_set_rot_dir, - doc=u"""rotation direction of rotor(rot_dir = -1 by default, i.e. clockwise rotation) + doc=u"""Rotation direction of the rotor : rot_dir*N0, default value given by ROT_DIR_REF :Type: int :min: -1 @@ -700,23 +688,3 @@ def _set_is_antiper_t_R(self, value): :Type: bool """, ) - - def _get_current_dir(self): - """getter of current_dir""" - return self._current_dir - - def _set_current_dir(self, value): - """setter of current_dir""" - check_var("current_dir", value, "int", Vmin=-1, Vmax=1) - self._current_dir = value - - current_dir = property( - fget=_get_current_dir, - fset=_set_current_dir, - doc=u"""rotation direction of the stator currents(current_dir = -1 by default, i.e. clockwise rotation) - - :Type: int - :min: -1 - :max: 1 - """, - ) diff --git a/pyleecan/Functions/Electrical/dqh_transformation.py b/pyleecan/Functions/Electrical/dqh_transformation.py index 20fc2b91f..0c6a23040 100644 --- a/pyleecan/Functions/Electrical/dqh_transformation.py +++ b/pyleecan/Functions/Electrical/dqh_transformation.py @@ -4,8 +4,6 @@ from ...Functions.Winding.gen_phase_list import gen_name -from ...Methods.Simulation.Input import PHASE_DIR_REF - def n2dqh_DataTime(data_n, is_dqh_rms=True, phase_dir=None): """n phases to dqh equivalent coordinate transformation of DataTime object @@ -18,7 +16,6 @@ def n2dqh_DataTime(data_n, is_dqh_rms=True, phase_dir=None): True to return dq currents in rms value (Pyleecan convention), False to return peak values phase_dir: int direction of phase distribution: +/-1 (-1 clockwise) to enforce - (it can be different from current direction given by slope of angle_elec) Returns ------- @@ -26,15 +23,9 @@ def n2dqh_DataTime(data_n, is_dqh_rms=True, phase_dir=None): data object transformed in dqh frame """ + # Check if input data object is compliant with dqh transformation + check_DataTime(data_n) - if not isinstance(data_n, DataTime): - raise Exception("Input object should be a DataTime object") - if len(data_n.axes) != 2: - raise Exception("DataTime object should contain two axes: time and phase") - if data_n.axes[0].name != "time": - raise Exception("DataTime object should contain time as first axis") - if data_n.axes[1].name != "phase": - raise Exception("DataTime object should contain phase as second axis") if "angle_elec" not in data_n.axes[0].normalizations: raise Exception("Time axis should contain angle_elec normalization") @@ -92,7 +83,6 @@ def dqh2n_DataTime(data_dqh, n, is_n_rms=False, phase_dir=None): True to return n currents in rms value, False to return peak values (Pyleecan convention) phase_dir: int direction of phase distribution: +/-1 (-1 clockwise) to enforce - (it can be different from current direction given by slope of angle_elec) Returns ------- @@ -100,14 +90,9 @@ def dqh2n_DataTime(data_dqh, n, is_n_rms=False, phase_dir=None): data object containing values over time and phase axes """ - if not isinstance(data_dqh, DataTime): - raise Exception("Input object should be a DataTime object") - if len(data_dqh.axes) != 2: - raise Exception("DataTime object should contain two axes: time and phase") - if data_dqh.axes[0].name != "time": - raise Exception("DataTime object should contain time as first axis") - if data_dqh.axes[1].name != "phase": - raise Exception("DataTime object should contain phase as second axis") + # Check if input data object is compliant with dqh transformation + check_DataTime(data_dqh) + if "angle_elec" not in data_dqh.axes[0].normalizations: raise Exception("Time axis should contain angle_elec normalization") @@ -165,7 +150,6 @@ def n2dqh(Z_n, angle_elec, is_dqh_rms=True, phase_dir=None): True to return dq currents in rms value (Pyleecan convention), False to return peak values phase_dir: int direction of phase distribution: +/-1 (-1 clockwise) to enforce - (it can be different from current direction given by slope of angle_elec) Returns ------- @@ -197,7 +181,6 @@ def dqh2n(Z_dqh, angle_elec, n, is_n_rms=False, phase_dir=None): True to return n currents in rms value, False to return peak values (Pyleecan convention) phase_dir: int direction of phase distribution: +/-1 (-1 clockwise) to enforce - (it can be different from current direction given by slope of angle_elec) Returns ------- @@ -225,7 +208,6 @@ def abc2n(Z_abc, n=3, phase_dir=None): number of phases phase_dir: int direction of phase distribution: +/-1 (-1 clockwise) to enforce - (it can be different from current direction given by slope of angle_elec) Returns ------- @@ -251,7 +233,6 @@ def n2abc(Z_n, phase_dir=None): matrix (N x n) of n phase values phase_dir: int direction of phase distribution: +/-1 (-1 clockwise) to enforce - (it can be different from current direction given by slope of angle_elec) Returns ------- @@ -347,7 +328,7 @@ def comp_Clarke_transform(n, is_inv=False, phase_dir=None): False to return Clarke transform, True to return inverse of Clarke transform phase_dir: int direction of phase distribution: +/-1 (-1 clockwise) to enforce - (it can be different from current direction given by slope of angle_elec) + Returns ------- @@ -355,10 +336,7 @@ def comp_Clarke_transform(n, is_inv=False, phase_dir=None): Clarke transform matrix of size (n, 3) """ - if phase_dir is None: - # Take phase rotation direction from reference current rotation direction - phase_dir = PHASE_DIR_REF - elif phase_dir not in [-1, 1]: + if phase_dir not in [-1, 1]: raise Exception("Cannot enforce phase_dir other than +1 or -1") # Phasor depending on fundamental field rotation direction @@ -371,3 +349,79 @@ def comp_Clarke_transform(n, is_inv=False, phase_dir=None): mat = 2 / n * np.column_stack((np.cos(phasor), -np.sin(phasor), np.ones(n) / 2)) return mat + + +def get_phase_dir_DataTime(data_n): + """Get the phase rotating direction of input n-phase DataTime object + + Parameters + ---------- + data_n : DataTime + data object containing values over time and phase axes + + Returns + ------- + phase_dir : int + rotating direction of phases +/-1 + """ + + # Check if input data object is compliant with dqh transformation + check_DataTime(data_n) + + # Extract values from DataTime + Z_n = data_n.get_along("time[oneperiod]", "phase")[data_n.symbol] + + # Get phase direction + phase_dir = get_phase_dir(Z_n) + + return phase_dir + + +def get_phase_dir(Z_n): + """Get the phase rotating direction of input n-phase quantity + + Parameters + ---------- + Z_n : ndarray + matrix (N x n) of n phase values + + Returns + ---------- + phase_dir : int + rotating direction of phases +/-1 + """ + + # Get number of time steps and phase + N, n = Z_n.shape + + # Shift first phase of +/- Nt/qs + Z_n_p = np.roll(Z_n[:, 0], shift=int(N / n)) + Z_n_n = np.roll(Z_n[:, 0], shift=-int(N / n)) + + # Find which shifted phase is closer to second phase + is_trigo = np.sum(np.abs(Z_n_p - Z_n[:, 1])) > np.sum(np.abs(Z_n_n - Z_n[:, 1])) + + # phase_dir=+/-1 + phase_dir = int((-1) ** int(is_trigo)) + + return phase_dir + + +def check_DataTime(data): + """Check if input data object is compliant with dqh transformation + + Parameters + ---------- + data : Data + data object to check + + """ + + if not isinstance(data, DataTime): + raise Exception("Input object should be a DataTime object") + if len(data.axes) != 2: + raise Exception("DataTime object should contain two axes: time and phase") + if data.axes[0].name != "time": + raise Exception("DataTime object should contain time as first axis") + if data.axes[1].name != "phase": + raise Exception("DataTime object should contain phase as second axis") \ No newline at end of file diff --git a/pyleecan/GUI/Dialog/DMachineSetup/SWinding/SWinding.py b/pyleecan/GUI/Dialog/DMachineSetup/SWinding/SWinding.py index ed0ee3001..46536afd7 100644 --- a/pyleecan/GUI/Dialog/DMachineSetup/SWinding/SWinding.py +++ b/pyleecan/GUI/Dialog/DMachineSetup/SWinding/SWinding.py @@ -441,16 +441,16 @@ def comp_output(self): wind = self.obj.winding # For readability try: - rot_dir = self.obj.comp_mmf_dir() - if rot_dir == 1: - rot_dir = "CCW" - elif rot_dir == -1: - rot_dir = "CW" + mmf_dir = self.obj.comp_mmf_dir() + if mmf_dir == 1: + mmf_dir = "CCW" + elif mmf_dir == -1: + mmf_dir = "CW" else: - rot_dir = "?" + mmf_dir = "?" except Exception: # Unable to compution the connection matrix - rot_dir = "?" - self.out_rot_dir.setText(self.tr("Rotation direction: ") + rot_dir) + mmf_dir = "?" + self.out_mmf_dir.setText(self.tr("Rotation direction: ") + mmf_dir) try: ms = str(self.obj.slot.Zs / (wind.p * wind.qs * 2.0)) diff --git a/pyleecan/Generator/ClassesRef/Output/OutElec.csv b/pyleecan/Generator/ClassesRef/Output/OutElec.csv index 2de08dc40..1639825c0 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutElec.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutElec.csv @@ -12,4 +12,5 @@ OP,-,Operating Point,,OP,None,,,,,,,,, Pem_av_ref,W,Theorical Average Electromagnetic Power,1,float,None,,,,,,,,, Tem_av_ref,N.m,Theorical Average Electromagnetic torque,,float,None,,,,,,,,, Is_harm,A,Harmonic stator current ,,SciDataTool.Classes.DataND.DataND,None,,,,,,,,, -perm_phases,,Phase permutation array to reverse rotating direction of stator mmf fundamental ,,ndarray,None,,,,,,,,, +phase_dir,,"Rotation direction of the stator phases : phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF",0,int,None,-1,1,,,,,,, +current_dir,-,"Rotation direction of the stator currents : current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF",0,int,None,-1,1,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Output/OutGeo.csv b/pyleecan/Generator/ClassesRef/Output/OutGeo.csv index bc43a2a3c..e4b9b9fe8 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutGeo.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutGeo.csv @@ -7,7 +7,7 @@ Rgap_mec,m,radius of the center of the mecanical airgap,0,float,None,,,,,,,,, Lgap,m,Airgap active length,0,float,None,,,,,,,,, logger_name,-,Name of the logger to use,0,str,Pyleecan.OutGeo,,,,,,,,, angle_rotor_initial,rad,Difference between the d axis angle of the stator and the rotor,0,float,None,,,,,,,,, -rot_dir,-,"rotation direction of rotor(rot_dir = -1 by default, i.e. clockwise rotation)",0,int,None,-1,1,,,,,,, +rot_dir,-,"Rotation direction of the rotor : rot_dir*N0, default value given by ROT_DIR_REF",,int,None,-1,1,,,,,,, per_a,-,Number of spatial periodicities of the machine,0,int,None,,,,,,,,, is_antiper_a,-,True if an spatial anti-periodicity is possible after the periodicities,0,bool,None,,,,,,,,, per_t_S,-,Number of time periodicities of the machine in static referential,0,int,None,,,,,,,,, @@ -15,4 +15,3 @@ is_antiper_t_S,-,True if an time anti-periodicity is possible after the periodic axes_dict,,Dict containing axes data without periodicities used for plots and to have simulation full time/angle vectors,,{SciDataTool.Classes.DataND.Data},None,,,,,,,,, per_t_R,-,Number of time periodicities of the machine in rotating referential,0,int,None,,,,,,,,, is_antiper_t_R,-,True if an time anti-periodicity is possible after the periodicities in rotating referential,0,bool,None,,,,,,,,, -current_dir,-,"rotation direction of the stator currents(current_dir = -1 by default, i.e. clockwise rotation)",0,int,None,-1,1,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv index 470d804aa..862e0c796 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv @@ -1,5 +1,6 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -rot_dir,-,"Rotation direction of the rotor 1 trigo, -1 clockwise",,int,None,-1,1,,Simulation,Input,gen_input,VERSION,1,Input to start the electrical module with voltage input +rot_dir,-,"Rotation direction of the rotor : rot_dir*N0, default value given by ROT_DIR_REF",,int,None,-1,1,,Simulation,Input,gen_input,VERSION,1,Input to start the electrical module with voltage input angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,set_OP_from_array,,, PWM,,Object to generate PWM signal,,ImportGenPWM,None,,,,,,,,, -current_dir,-,"Rotation direction of the stator currents 1 trigo, -1 clockwise",,int,None,-1,1,,,,,,, +phase_dir,,"Rotation direction of the stator phases : phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF",0,int,None,-1,1,,,,,,, +current_dir,-,"Rotation direction of the stator currents : current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF",0,int,None,-1,1,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/LUT.csv b/pyleecan/Generator/ClassesRef/Simulation/LUT.csv index 3c0180f1a..eac8970c2 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/LUT.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/LUT.csv @@ -1,5 +1,6 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constante Name,Constante Value,Description classe,Classe fille R1,Ohm,DC phase winding resistance at T1_ref per phase ,0,float,None,,,,Simulation,,get_param_dict,VERSION,1,Abstract class for Look Up Table (LUT), -L1,H,Phase winding leakage inductance ,0,float,None,,,,,,,,,, +L1,H,Phase winding leakage inductance ,0,float,None,,,,,,get_phase_dir,,,, T1_ref,degC,"Stator winding average temperature associated to R1, L1 parameters",0,float,20,,,,,,,,,, OP_matrix,,"Array of operating point values of size (N,5) with N the number of operating points (Speed, Id, Iq, Torque, Power). OP values are set to nan if they are not given.",,ndarray,None,,,,,,,,,, +phase_dir,,"Rotation direction of the stator phases : phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF",0,int,None,-1,1,,,,,,,, diff --git a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_dir.py b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_dir.py index 548051bcd..14d005673 100644 --- a/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_dir.py +++ b/pyleecan/Methods/Machine/LamSlotMultiWind/comp_mmf_dir.py @@ -26,6 +26,6 @@ def comp_mmf_dir(self, current_dir=None, phase_dir=None, is_plot=False): # Call method of LamSlotWind LamSlotWind = import_class("pyleecan.Classes", "LamSlotWind") - rot_dir = LamSlotWind.comp_mmf_dir(self, current_dir, is_plot) + rot_dir = LamSlotWind.comp_mmf_dir(self, current_dir, phase_dir, is_plot) return rot_dir diff --git a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py index 0dc9b4ece..c12350028 100644 --- a/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py +++ b/pyleecan/Methods/Machine/LamSlotWind/comp_mmf_unit.py @@ -8,7 +8,7 @@ from ....Functions.Electrical.dqh_transformation import dqh2n from ....Functions.Load.import_class import import_class -from ....Methods.Simulation.Input import CURRENT_DIR_REF, PHASE_DIR_REF +from ....Methods.Simulation.Input import CURRENT_DIR_REF, PHASE_DIR_REF, ROT_DIR_REF def comp_mmf_unit(self, Na=None, Nt=None, felec=1, current_dir=None, phase_dir=None): @@ -51,19 +51,15 @@ def comp_mmf_unit(self, Na=None, Nt=None, felec=1, current_dir=None, phase_dir=N # Get stator winding number of phases qs = self.winding.qs - if current_dir is not None: - if current_dir in [-1, 1]: - # Enforce input current_dir otherwise keep it as default - current_dir = CURRENT_DIR_REF - else: - raise Exception("Cannot enforce current_dir other than +1 or -1") + if current_dir is None: + current_dir = CURRENT_DIR_REF + elif current_dir not in [-1, 1]: + raise Exception("Cannot enforce current_dir other than +1 or -1") - if phase_dir is not None: - if phase_dir in [-1, 1]: - # Enforce input phase_dir otherwise keep it as default - phase_dir = PHASE_DIR_REF - else: - raise Exception("Cannot enforce current_dir other than +1 or -1") + if phase_dir is None: + phase_dir = PHASE_DIR_REF + elif phase_dir not in [-1, 1]: + raise Exception("Cannot enforce phase_dir other than +1 or -1") if machine.is_synchronous(): OPclass = OPdq @@ -71,7 +67,11 @@ def comp_mmf_unit(self, Na=None, Nt=None, felec=1, current_dir=None, phase_dir=N OPclass = OPslip InputVoltage = import_class("pyleecan.Classes", "InputVoltage") input = InputVoltage( - Na_tot=Na, Nt_tot=Nt, OP=OPclass(felec=felec), current_dir=current_dir + Na_tot=Na, + Nt_tot=Nt, + OP=OPclass(felec=felec), + current_dir=current_dir, + rot_dir=ROT_DIR_REF, # rotor rotating dir has not impact on unit mmf ) axes_dict = input.comp_axes( diff --git a/pyleecan/Methods/Output/OutElec/get_I_fund.py b/pyleecan/Methods/Output/OutElec/get_I_fund.py index 76f9d0027..d4956dfbe 100644 --- a/pyleecan/Methods/Output/OutElec/get_I_fund.py +++ b/pyleecan/Methods/Output/OutElec/get_I_fund.py @@ -27,7 +27,7 @@ def get_I_fund(self, Time=None): stator_label = "phase_" + self.parent.simu.machine.stator.get_label() felec = self.OP.get_felec() Phase = self.axes_dict[stator_label] - perm_phases = self.perm_phases + phase_dir = self.phase_dir if self.Is is None: I_dict = self.OP.get_Id_Iq() @@ -39,7 +39,7 @@ def get_I_fund(self, Time=None): Is_dqh[:, 1] = Iq # Get stator current function of time - Is = dqh2n(Is_dqh, angle_elec, n=qs, is_n_rms=False) + Is = dqh2n(Is_dqh, angle_elec, n=qs, is_n_rms=False, phase_dir=phase_dir) else: Is = zeros((angle_elec.size, qs)) @@ -48,7 +48,7 @@ def get_I_fund(self, Time=None): unit="A", symbol="I_s", axes=[Time, Phase], - values=Is[:, perm_phases], + values=Is, ) else: @@ -56,7 +56,7 @@ def get_I_fund(self, Time=None): Is_val = result[self.Is.symbol] freqs = result["freqs"] ifund = where(isclose(freqs, felec)) - Is_fund = Is_val[ifund, perm_phases] + Is_fund = Is_val[ifund] Freq = Data1D( name="freqs", diff --git a/pyleecan/Methods/Output/OutElec/get_Us.py b/pyleecan/Methods/Output/OutElec/get_Us.py index d82dfd419..2621f1278 100644 --- a/pyleecan/Methods/Output/OutElec/get_Us.py +++ b/pyleecan/Methods/Output/OutElec/get_Us.py @@ -27,16 +27,16 @@ def get_Us(self): angle_elec = Time.get_values(is_smallestperiod=True, normalization="angle_elec") qs = self.parent.simu.machine.stator.winding.qs stator_label = "phase_" + self.parent.simu.machine.stator.get_label() - perm_phases = self.perm_phases + phase_dir = self.phase_dir # Switch from dqh to abc referential - Us = dqh2n(Usdqh, angle_elec, n=qs) + Us = dqh2n(Usdqh, angle_elec, n=qs, is_n_rms=False, phase_dir=phase_dir) self.Us = DataTime( name="Stator voltage", unit="V", symbol="Us", axes=[Time.copy(), self.axes_dict[stator_label].copy()], - values=Us[:, perm_phases], + values=Us, ) return self.Us diff --git a/pyleecan/Methods/Output/OutElec/get_Us_harm.py b/pyleecan/Methods/Output/OutElec/get_Us_harm.py index f75aa2480..333fd86fc 100644 --- a/pyleecan/Methods/Output/OutElec/get_Us_harm.py +++ b/pyleecan/Methods/Output/OutElec/get_Us_harm.py @@ -21,7 +21,7 @@ def get_Us_harm(self, is_dqh=True): else: # Rotate to DQH frame if is_dqh: - U = n2dqh_DataTime(self.Us_PWM, is_dqh_rms=True) + U = n2dqh_DataTime(self.Us_PWM, is_dqh_rms=True, phase_dir=self.phase_dir) else: U = self.Us_PWM # fft diff --git a/pyleecan/Methods/Output/OutElec/store.py b/pyleecan/Methods/Output/OutElec/store.py index 6ffa4a693..e07195a14 100644 --- a/pyleecan/Methods/Output/OutElec/store.py +++ b/pyleecan/Methods/Output/OutElec/store.py @@ -55,5 +55,5 @@ def store(self, out_dict, out_dict_harm): Is_dqh_time = Is_dqh.freq_to_time() qs = self.parent.simu.machine.stator.winding.qs # back to ABC - Is_abc = dqh2n_DataTime(Is_dqh_time, qs, is_n_rms=True) + Is_abc = dqh2n_DataTime(Is_dqh_time, qs, is_n_rms=True, phase=self.phase_dir) self.Is_harm = Is_abc.time_to_freq() diff --git a/pyleecan/Methods/Post/PostLUT/run.py b/pyleecan/Methods/Post/PostLUT/run.py index 34ab1118d..c7c331860 100644 --- a/pyleecan/Methods/Post/PostLUT/run.py +++ b/pyleecan/Methods/Post/PostLUT/run.py @@ -19,7 +19,7 @@ def run(self, out): if out.simu.machine.is_synchronous(): # Init LUT object - LUT = LUTdq() + LUT = LUTdq(phase_dir=out.elec.phase_dir) XOutput = import_class("pyleecan.Classes", "XOutput") @@ -43,14 +43,8 @@ def run(self, out): if "Phi_{wind}" in out.keys(): LUT.Phi_wind = out["Phi_{wind}"].result elif out.output_list is not None: - LUT.Phi_wind = [o.mag.Phi_wind_stator for o in out.output_list] - - # Find Id=Iq=0 - OP_list = LUT.OP_matrix[:, 1:3].tolist() - if [0, 0] in OP_list: - ii = OP_list.index([0, 0]) - else: - raise Exception("Operating Point Id=Iq=0 is required to compute LUT") + stator_label = out.simu.machine.stator.get_label() + LUT.Phi_wind = [o.mag.Phi_wind[stator_label] for o in out.output_list] if self.is_save_LUT: # Save LUT object diff --git a/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py b/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py index e7e3dd789..dde6d92db 100644 --- a/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py +++ b/pyleecan/Methods/Simulation/FluxLinkFEMM/comp_fluxlinkage.py @@ -60,7 +60,9 @@ def comp_fluxlinkage(self, machine): # Post-Process stator_label = machine.stator.get_label() - Phidqh = n2dqh_DataTime(out_fl.mag.Phi_wind[stator_label]) + Phidqh = n2dqh_DataTime( + out_fl.mag.Phi_wind[stator_label], phase_dir=out_fl.elec.phase_dir + ) Phi_d_mean = float(Phidqh.get_along("time=mean", "phase[0]")[Phidqh.symbol]) return Phi_d_mean diff --git a/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py b/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py index d719b040d..8809761f6 100644 --- a/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py +++ b/pyleecan/Methods/Simulation/IndMagFEMM/comp_inductance.py @@ -61,7 +61,9 @@ def comp_inductance(self, machine, OP_ref): # Post-Process stator_label = machine.stator.get_label() - Phidqh = n2dqh_DataTime(out_ind.mag.Phi_wind[stator_label]) + Phidqh = n2dqh_DataTime( + out_ind.mag.Phi_wind[stator_label], phase_dir=out_ind.elec.phase_dir + ) Phi_dqh_mean = Phidqh.get_along("time=mean", "phase")[Phidqh.symbol] return (Phi_dqh_mean[0], Phi_dqh_mean[1]) diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 54421281c..980738b25 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -5,7 +5,12 @@ from ....Classes.OutElec import OutElec from ....Classes.Simulation import Simulation -from ....Methods.Simulation.Input import CURRENT_DIR_REF, ROT_DIR_REF, InputError +from ....Methods.Simulation.Input import ( + CURRENT_DIR_REF, + ROT_DIR_REF, + InputError, + PHASE_DIR_REF, +) def gen_input(self): @@ -46,31 +51,37 @@ def gen_input(self): outelec.OP = self.OP # Set rotor rotation direction - if self.rot_dir not in [-1, 1]: - # Enforce rotor rotation direction to -1 - logger.info("Enforcing rotor rotating direction to -1: clockwise rotation") + if self.rot_dir is None: self.rot_dir = ROT_DIR_REF + elif self.rot_dir not in [-1, 1]: + raise Exception("Cannot enforce rot_dir other than +1 or -1") outgeo.rot_dir = self.rot_dir # Set current rotation direction - if self.current_dir not in [-1, 1]: - # Enforce current rotation direction to 1 - logger.info("Enforcing current rotating direction to 1: clockwise rotation") + if self.current_dir is None: self.current_dir = CURRENT_DIR_REF - outgeo.current_dir = self.current_dir + elif self.current_dir not in [-1, 1]: + raise Exception("Cannot enforce current_dir other than +1 or -1") + outelec.current_dir = self.current_dir + + # Set phase rotation direction + if self.phase_dir is None: + self.phase_dir = PHASE_DIR_REF + elif self.phase_dir not in [-1, 1]: + raise Exception("Cannot enforce phase_dir other than +1 or -1") + outelec.phase_dir = self.phase_dir - # Init permutation array of stator currents - qs = simu.machine.stator.winding.qs - perm_phases = arange(qs) # Check if stator magnetomotive force direction is consistent with rotor direction - mmf_dir = simu.machine.stator.comp_mmf_dir(current_dir=self.current_dir) + mmf_dir = simu.machine.stator.comp_mmf_dir( + current_dir=self.current_dir, phase_dir=self.phase_dir + ) if mmf_dir != -self.rot_dir: - # Switch two last phases to reverse rotation direction - perm_phases[-1], perm_phases[-2] = perm_phases[-2], perm_phases[-1] + # Switch phase direction to reverse rotation direction + self.phase_dir = -self.phase_dir logger.info( "Reverse the two last stator current phases to reverse rotation direction of stator mmf fundamental according to rotor direction" ) - outelec.perm_phases = perm_phases + outelec.phase_dir = self.phase_dir # Set rotor initial angular position if self.angle_rotor_initial in [0, None]: diff --git a/pyleecan/Methods/Simulation/LUT/get_phase_dir.py b/pyleecan/Methods/Simulation/LUT/get_phase_dir.py new file mode 100644 index 000000000..5b96585e0 --- /dev/null +++ b/pyleecan/Methods/Simulation/LUT/get_phase_dir.py @@ -0,0 +1,22 @@ +from ....Functions.Electrical.dqh_transformation import get_phase_dir_DataTime + + +def get_phase_dir(self): + """Get the phase rotating direction of stator flux stored in LUT + + Parameters + ---------- + self : LUT + a LUT object + + Returns + ---------- + phase_dir : int + rotating direction of phases +/-1 + """ + + if self.phase_dir not in [-1, 1]: + # recalculate phase_dir from Phi_wind + self.phase_dir = get_phase_dir_DataTime(self.Phi_wind[0]) + + return self.phase_dir diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py index 09baacda7..4916e7d21 100644 --- a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mag.py @@ -27,6 +27,7 @@ def get_Phidqh_mag(self): Phi_dqh_mag = n2dqh_DataTime( self.Phi_wind[ii], is_dqh_rms=True, + phase_dir=self.get_phase_dir(), ) # Store for next call diff --git a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py index 4390d375f..58a764fef 100644 --- a/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py +++ b/pyleecan/Methods/Simulation/LUTdq/get_Phidqh_mean.py @@ -25,6 +25,7 @@ def get_Phidqh_mean(self): Phi_dqh = n2dqh_DataTime( Phi_wind, is_dqh_rms=True, + phase_dir=self.get_phase_dir(), ) # mean over time axis Phi_dqh_mean[i, :] = Phi_dqh.get_along("time=mean", "phase")[Phi_dqh.symbol] From bf796089333f6bede67b5f6b981690518df2b15f Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Sat, 30 Oct 2021 11:29:49 +0200 Subject: [PATCH 161/167] [BC] Correct tests due to introduction of current_dir and rot_dir --- Tests/Classes/test_classes.py | 3 +- Tests/Functions/test_save_load.py | 10 +-- Tests/GUI/DMachineSetup/test_DMachineSetup.py | 8 +-- Tests/GUI/DMachineSetup/test_SWinding.py | 9 +++ Tests/Methods/Geometry/test_Arc1_meth.py | 2 +- Tests/Plot/test_ICEM_2020.py | 1 + .../Electrical/test_EEC_ELUT_PMSM.py | 28 ++++++--- Tests/Validation/Force/test_AGSF_SynRM.py | 2 +- .../Magnetics/test_FEMM_periodicity.py | 56 ++++++++--------- pyleecan/Classes/Class_Dict.json | 12 ++-- pyleecan/Classes/InputVoltage.py | 6 +- pyleecan/Classes/OutElec.py | 4 +- pyleecan/Classes/OutGeo.py | 2 +- .../Electrical/dqh_transformation.py | 62 +++++++++++-------- .../Dialog/DMachineSetup/SWinding/SWinding.py | 2 +- .../Generator/ClassesRef/Output/OutElec.csv | 4 +- .../Generator/ClassesRef/Output/OutGeo.csv | 2 +- .../ClassesRef/Simulation/InputVoltage.csv | 6 +- pyleecan/Methods/Output/OutElec/store.py | 4 +- .../Simulation/InputCurrent/gen_input.py | 25 +++++--- 20 files changed, 146 insertions(+), 102 deletions(-) diff --git a/Tests/Classes/test_classes.py b/Tests/Classes/test_classes.py index 312edc8e1..9e20ea5b8 100644 --- a/Tests/Classes/test_classes.py +++ b/Tests/Classes/test_classes.py @@ -426,4 +426,5 @@ def test_class_copy(class_dict): if __name__ == "__main__": - test_class_as_dict(class_list[144]) + # test_class_as_dict(class_list[116]) + test_class_prop_doc(class_list[116]) diff --git a/Tests/Functions/test_save_load.py b/Tests/Functions/test_save_load.py index 66a73a062..04824c345 100644 --- a/Tests/Functions/test_save_load.py +++ b/Tests/Functions/test_save_load.py @@ -406,9 +406,9 @@ def test_save_load_simu(type_file): if __name__ == "__main__": - test_save_load_folder_path() - test_save_load_json_compressed() + test_save_load_simu("json") + test_save_load_simu("h5") + test_save_load_simu("pkl") + # test_save_load_folder_path() + # test_save_load_json_compressed() print("Done") - # test_save_load_simu("json") - # test_save_load_simu("h5") - # test_save_load_simu("pkl") diff --git a/Tests/GUI/DMachineSetup/test_DMachineSetup.py b/Tests/GUI/DMachineSetup/test_DMachineSetup.py index 159828c6e..6d2de738c 100644 --- a/Tests/GUI/DMachineSetup/test_DMachineSetup.py +++ b/Tests/GUI/DMachineSetup/test_DMachineSetup.py @@ -177,8 +177,8 @@ def save_function(widget, file_name): a = TestDMachineSetup() a.setup_class() a.setup_method() - for ii, test_dict in enumerate(load_test): - print(ii) - a.test_load(test_dict) - a.test_load(load_test[6]) + # for ii, test_dict in enumerate(load_test): + # print(ii) + # a.test_load(test_dict) + a.test_load(load_test[0]) print("Done") diff --git a/Tests/GUI/DMachineSetup/test_SWinding.py b/Tests/GUI/DMachineSetup/test_SWinding.py index 7ea15cfb9..29f619908 100644 --- a/Tests/GUI/DMachineSetup/test_SWinding.py +++ b/Tests/GUI/DMachineSetup/test_SWinding.py @@ -214,5 +214,14 @@ def test_check(self): a.setup_class() a.setup_method() a.test_init() + a.test_set_wind_type() a.test_generate() + a.test_export_import() + a.test_set_is_reverse() + a.test_set_is_reverse_layer() + a.test_set_is_permute_B_C() + a.test_set_is_change_layer() + a.test_set_Nslot() + a.test_set_Npcp() + a.test_check() print("Done") diff --git a/Tests/Methods/Geometry/test_Arc1_meth.py b/Tests/Methods/Geometry/test_Arc1_meth.py index bb4408f76..b54893c19 100644 --- a/Tests/Methods/Geometry/test_Arc1_meth.py +++ b/Tests/Methods/Geometry/test_Arc1_meth.py @@ -890,4 +890,4 @@ def test_translate_error(self): for ii, test_dict in enumerate(split_half_test): print(ii) a.test_split_half(test_dict) - print("Done") \ No newline at end of file + print("Done") diff --git a/Tests/Plot/test_ICEM_2020.py b/Tests/Plot/test_ICEM_2020.py index 9ac0de7b5..aa9284940 100644 --- a/Tests/Plot/test_ICEM_2020.py +++ b/Tests/Plot/test_ICEM_2020.py @@ -842,4 +842,5 @@ def test_Optimization_problem(): if __name__ == "__main__": test_FEMM_sym() + test_ecc_FEMM() test_WindingUD_layer() diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py index 75d890a60..8c66353c4 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py @@ -72,7 +72,7 @@ def test_EEC_ELUT_PMSM_calc(n_Id=5, n_Iq=5): ) # Build OP_matrix with a meshgrid of Id/Iq - Id_min, Id_max = -100, 100 + Id_min, Id_max = -200, 200 Iq_min, Iq_max = -200, 200 Id, Iq = np.meshgrid( np.linspace(Id_min, Id_max, n_Id), np.linspace(Iq_min, Iq_max, n_Iq) @@ -169,6 +169,7 @@ def test_EEC_ELUT_PMSM_MTPA(test_ELUT, n_Id=51, n_Iq=101): np.linspace(Id_min, Id_max, n_Id), np.linspace(Iq_min, Iq_max, n_Iq) ) Id, Iq = Id.ravel(), Iq.ravel() + Imax_interp = np.sqrt(Id ** 2 + Iq ** 2) elec_model = Electrical(eec=EEC_PMSM(), ELUT_enforced=test_ELUT) @@ -197,9 +198,23 @@ def test_EEC_ELUT_PMSM_MTPA(test_ELUT, n_Id=51, n_Iq=101): Zdata=Tem_interp.reshape((n_Iq, n_Id)).T, zlabel="Average Torque [N.m]", title="Torque map in dq plane", - save_path=join(save_path, name + "_torque_map.png"), + # save_path=join(save_path, name + "_torque_map.png"), **dict_map, ) + # plt.contour( + # dict_map["Xdata"], + # dict_map["Ydata"], + # Imax_interp.reshape((n_Iq, n_Id)), + # colors="red", + # linewidths=0.8, + # ) + # plt.contour( + # dict_map["Xdata"], + # dict_map["Ydata"], + # U_max_interp.reshape((n_Iq, n_Id)), + # colors="blue", + # linewidths=0.8, + # ) plot_3D( Zdata=Tem_sync.reshape((n_Iq, n_Id)).T, zlabel="Synchrnous Torque [N.m]", @@ -236,16 +251,15 @@ def test_EEC_ELUT_PMSM_MTPA(test_ELUT, n_Id=51, n_Iq=101): # MTPA # Maximum current [Arms] I_max = 250 / np.sqrt(2) - Imax_interp = np.sqrt(Id ** 2 + Iq ** 2) # Maximum voltage [Vrms] - U_max = 200 + U_max = 500 # Speed vector Nspeed = 50 N0_min = 50 N0_max = 8000 N0_vect = np.linspace(N0_min, N0_max, Nspeed) # Maximum load vector - Ntorque = 5 + Ntorque = 1 is_braking = False # True to include negative torque (braking) if is_braking: Ntorque = ( @@ -405,5 +419,5 @@ def test_EEC_ELUT_PMSM_PWM(test_ELUT): # out0, ELUT = test_EEC_ELUT_PMSM_calc() # ELUT.save("ELUT_PMSM.h5") ELUT = load("ELUT_PMSM.h5") - test_EEC_ELUT_PMSM_MTPA(ELUT) - # test_EEC_ELUT_PMSM_PWM(ELUT) + # test_EEC_ELUT_PMSM_MTPA(ELUT) + test_EEC_ELUT_PMSM_PWM(ELUT) diff --git a/Tests/Validation/Force/test_AGSF_SynRM.py b/Tests/Validation/Force/test_AGSF_SynRM.py index 24816c770..36f6019ad 100644 --- a/Tests/Validation/Force/test_AGSF_SynRM.py +++ b/Tests/Validation/Force/test_AGSF_SynRM.py @@ -63,7 +63,6 @@ def test_AGSF_SynRM(nb_worker=int(cpu_count() / 2)): time=ImportMatrixVal(value=time), Na_tot=Na_tot, Nt_tot=Nt_tot, - nb_worker=nb_worker, ) # Definition of the magnetic simulation (1/2 symmetry) @@ -71,6 +70,7 @@ def test_AGSF_SynRM(nb_worker=int(cpu_count() / 2)): type_BH_stator=0, type_BH_rotor=0, is_periodicity_a=True, + nb_worker=nb_worker, ) # Definition of the magnetic simulation (no symmetry) diff --git a/Tests/Validation/Magnetics/test_FEMM_periodicity.py b/Tests/Validation/Magnetics/test_FEMM_periodicity.py index bfa5ac027..6b0f2ceee 100644 --- a/Tests/Validation/Magnetics/test_FEMM_periodicity.py +++ b/Tests/Validation/Magnetics/test_FEMM_periodicity.py @@ -6,10 +6,9 @@ from numpy import exp, sqrt, pi, max as np_max from numpy.testing import assert_array_almost_equal -from pyleecan.Classes.OPdq import OPdq +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.Simu1 import Simu1 -import matplotlib.pyplot as plt from pyleecan.Classes.InputCurrent import InputCurrent from pyleecan.Classes.VentilationCirc import VentilationCirc from pyleecan.Classes.VentilationPolar import VentilationPolar @@ -19,10 +18,10 @@ from pyleecan.Classes.NotchEvenDist import NotchEvenDist from pyleecan.Classes.MagFEMM import MagFEMM from pyleecan.Classes.ForceMT import ForceMT -from pyleecan.Classes.Output import Output from pyleecan.Functions.load import load from pyleecan.Functions.Plot import dict_2D + from pyleecan.definitions import DATA_DIR @@ -74,11 +73,9 @@ def test_FEMM_periodicity_time_no_periodicity_a(): simu2.mag.is_periodicity_t = False # Run simulations - out = Output(simu=simu) - simu.run() + out = simu.run() - out2 = Output(simu=simu2) - simu2.run() + out2 = simu2.run() # Plot the result out.mag.B.plot_2D_Data( @@ -222,11 +219,9 @@ def test_FEMM_periodicity_time(): simu2.mag.is_periodicity_t = False # Run simulations - out = Output(simu=simu) - simu.run() + out = simu.run() - out2 = Output(simu=simu2) - simu2.run() + out2 = simu2.run() # Plot the result out.mag.B.plot_2D_Data( @@ -437,14 +432,12 @@ def test_FEMM_periodicity_angle(): simu2.force = ForceMT() # Run simulations - out = Output(simu=simu) - simu.run() - assert np_max(out.mag.B.components["radial"].values) == pytest.approx(3.95, rel=0.1) + out = simu.run() + assert np_max(out.mag.B.components["radial"].values) == pytest.approx(4.82, rel=0.1) - out2 = Output(simu=simu2) - simu2.run() + out2 = simu2.run() assert np_max(out2.mag.B.components["radial"].values) == pytest.approx( - 3.95, rel=0.1 + 4.82, rel=0.1 ) # Plot the result @@ -453,8 +446,8 @@ def test_FEMM_periodicity_angle(): "angle[0]{°}", data_list=[out2.mag.B], legend_list=["Periodic", "Full"], - save_path=join(save_path, simu.name + "_B_time.png"), - is_show_fig=False, + # save_path=join(save_path, simu.name + "_B_time.png"), + # is_show_fig=False, **dict_2D ) @@ -463,8 +456,8 @@ def test_FEMM_periodicity_angle(): "time[1]", data_list=[out2.mag.B], legend_list=["Periodic", "Full"], - save_path=join(save_path, simu.name + "_B_space.png"), - is_show_fig=False, + # save_path=join(save_path, simu.name + "_B_space.png"), + # is_show_fig=False, **dict_2D ) @@ -524,6 +517,13 @@ def test_FEMM_periodicity_angle(): return out, out2 +@pytest.mark.long_5s +@pytest.mark.long_1m +@pytest.mark.MagFEMM +@pytest.mark.SPMSM +@pytest.mark.periodicity +@pytest.mark.SingleOP +@pytest.mark.ForceMT def test_Ring_Magnet(): """Check that a machine with Ring magnet can be simulated with sym""" machine = load(join(DATA_DIR, "Machine", "SPMSM_001.json")) @@ -538,11 +538,9 @@ def test_Ring_Magnet(): Iq_ref = (I0_rms * exp(1j * Phi0)).imag simu.input = InputCurrent( - Id_ref=Id_ref, - Iq_ref=Iq_ref, + OP=OPdq(Id_ref=Id_ref, Iq_ref=Iq_ref, N0=1000), Na_tot=252 * 9, Nt_tot=8 * 3, - N0=1000, ) # Definition of the magnetic simulation: with periodicity @@ -563,11 +561,9 @@ def test_Ring_Magnet(): simu2.mag.is_periodicity_a = True # Run simulations - out = Output(simu=simu) - simu.run() + out = simu.run() - out2 = Output(simu=simu2) - simu2.run() + out2 = simu2.run() # Plot the result out.mag.B.plot_2D_Data( @@ -621,8 +617,8 @@ def test_Ring_Magnet(): # To run it without pytest if __name__ == "__main__": - # out, out2 = test_FEMM_periodicity_angle() + out, out2 = test_FEMM_periodicity_angle() # out3, out4 = test_FEMM_periodicity_time() # out5, out6 = test_FEMM_periodicity_time_no_periodicity_a() - test_Ring_Magnet() + # test_Ring_Magnet() print("Done") diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 0af4b39ba..6970b74cd 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4271,7 +4271,7 @@ "path": "pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv", "properties": [ { - "desc": "Rotation direction of the rotor\u00c2\u00a0: rot_dir*N0, default value given by ROT_DIR_REF", + "desc": "Rotation direction of the rotor (rot_dir*N0, default value given by ROT_DIR_REF)", "max": "1", "min": "-1", "name": "rot_dir", @@ -4298,7 +4298,7 @@ "value": null }, { - "desc": "Rotation direction of the stator phases\u00c2\u00a0: phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF", + "desc": "Rotation direction of the stator phase (phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF)", "max": "1", "min": "-1", "name": "phase_dir", @@ -4307,7 +4307,7 @@ "value": null }, { - "desc": "Rotation direction of the stator currents\u00c2\u00a0: current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF", + "desc": "Rotation direction of the stator currents (current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF)", "max": "1", "min": "-1", "name": "current_dir", @@ -8288,7 +8288,7 @@ "value": "None" }, { - "desc": "Rotation direction of the stator phases\u00c2\u00a0: phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF", + "desc": "Rotation direction of the stator phases (phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF)", "max": "1", "min": "-1", "name": "phase_dir", @@ -8297,7 +8297,7 @@ "value": null }, { - "desc": "Rotation direction of the stator currents\u00c2\u00a0: current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF", + "desc": "Rotation direction of the stator currents (current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF)", "max": "1", "min": "-1", "name": "current_dir", @@ -8461,7 +8461,7 @@ "value": null }, { - "desc": "Rotation direction of the rotor\u00c2\u00a0: rot_dir*N0, default value given by ROT_DIR_REF", + "desc": "Rotation direction of the rotor (rot_dir*N0, default value given by ROT_DIR_REF)", "max": "1", "min": "-1", "name": "rot_dir", diff --git a/pyleecan/Classes/InputVoltage.py b/pyleecan/Classes/InputVoltage.py index 5c3fa823c..06f3f0d28 100644 --- a/pyleecan/Classes/InputVoltage.py +++ b/pyleecan/Classes/InputVoltage.py @@ -279,7 +279,7 @@ def _set_rot_dir(self, value): rot_dir = property( fget=_get_rot_dir, fset=_set_rot_dir, - doc=u"""Rotation direction of the rotor : rot_dir*N0, default value given by ROT_DIR_REF + doc=u"""Rotation direction of the rotor (rot_dir*N0, default value given by ROT_DIR_REF) :Type: int :min: -1 @@ -345,7 +345,7 @@ def _set_phase_dir(self, value): phase_dir = property( fget=_get_phase_dir, fset=_set_phase_dir, - doc=u"""Rotation direction of the stator phases : phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF + doc=u"""Rotation direction of the stator phase (phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF) :Type: int :min: -1 @@ -365,7 +365,7 @@ def _set_current_dir(self, value): current_dir = property( fget=_get_current_dir, fset=_set_current_dir, - doc=u"""Rotation direction of the stator currents : current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF + doc=u"""Rotation direction of the stator currents (current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF) :Type: int :min: -1 diff --git a/pyleecan/Classes/OutElec.py b/pyleecan/Classes/OutElec.py index 5b1f472ce..2a2fe4d28 100644 --- a/pyleecan/Classes/OutElec.py +++ b/pyleecan/Classes/OutElec.py @@ -840,7 +840,7 @@ def _set_phase_dir(self, value): phase_dir = property( fget=_get_phase_dir, fset=_set_phase_dir, - doc=u"""Rotation direction of the stator phases : phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF + doc=u"""Rotation direction of the stator phases (phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF) :Type: int :min: -1 @@ -860,7 +860,7 @@ def _set_current_dir(self, value): current_dir = property( fget=_get_current_dir, fset=_set_current_dir, - doc=u"""Rotation direction of the stator currents : current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF + doc=u"""Rotation direction of the stator currents (current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF) :Type: int :min: -1 diff --git a/pyleecan/Classes/OutGeo.py b/pyleecan/Classes/OutGeo.py index 65da2529b..7da0105ba 100644 --- a/pyleecan/Classes/OutGeo.py +++ b/pyleecan/Classes/OutGeo.py @@ -542,7 +542,7 @@ def _set_rot_dir(self, value): rot_dir = property( fget=_get_rot_dir, fset=_set_rot_dir, - doc=u"""Rotation direction of the rotor : rot_dir*N0, default value given by ROT_DIR_REF + doc=u"""Rotation direction of the rotor (rot_dir*N0, default value given by ROT_DIR_REF) :Type: int :min: -1 diff --git a/pyleecan/Functions/Electrical/dqh_transformation.py b/pyleecan/Functions/Electrical/dqh_transformation.py index 0c6a23040..73ce5822a 100644 --- a/pyleecan/Functions/Electrical/dqh_transformation.py +++ b/pyleecan/Functions/Electrical/dqh_transformation.py @@ -5,6 +5,26 @@ from ...Functions.Winding.gen_phase_list import gen_name +def check_DataTime(data): + """Check if input data object is compliant with dqh transformation + + Parameters + ---------- + data : Data + data object to check + + """ + + if not isinstance(data, DataTime): + raise Exception("Input object should be a DataTime object") + if len(data.axes) != 2: + raise Exception("DataTime object should contain two axes: time and phase") + if data.axes[0].name != "time": + raise Exception("DataTime object should contain time as first axis") + if data.axes[1].name != "phase": + raise Exception("DataTime object should contain phase as second axis") + + def n2dqh_DataTime(data_n, is_dqh_rms=True, phase_dir=None): """n phases to dqh equivalent coordinate transformation of DataTime object @@ -23,8 +43,14 @@ def n2dqh_DataTime(data_n, is_dqh_rms=True, phase_dir=None): data object transformed in dqh frame """ - # Check if input data object is compliant with dqh transformation - check_DataTime(data_n) + + if phase_dir is None: + # Check if input data object is compliant with dqh transformation + # and get phase_dir from data object + phase_dir = get_phase_dir_DataTime(data_n) + else: + # Only check if input data object is compliant with dqh transformation + check_DataTime(data_n) if "angle_elec" not in data_n.axes[0].normalizations: raise Exception("Time axis should contain angle_elec normalization") @@ -157,6 +183,10 @@ def n2dqh(Z_n, angle_elec, is_dqh_rms=True, phase_dir=None): transformed matrix (N x 3) of dqh equivalent values """ + if phase_dir is None: + # Get phase_dir from Z_n + phase_dir = get_phase_dir(Z_n) + Z_dqh = abc2dqh(n2abc(Z_n, phase_dir), angle_elec) if is_dqh_rms: @@ -395,33 +425,13 @@ def get_phase_dir(Z_n): N, n = Z_n.shape # Shift first phase of +/- Nt/qs - Z_n_p = np.roll(Z_n[:, 0], shift=int(N / n)) - Z_n_n = np.roll(Z_n[:, 0], shift=-int(N / n)) + Z_n_p = np.roll(Z_n[:, 0], shift=int(N / n)) # clockwise shift + Z_n_n = np.roll(Z_n[:, 0], shift=-int(N / n)) # trigonomic shift - # Find which shifted phase is closer to second phase - is_trigo = np.sum(np.abs(Z_n_p - Z_n[:, 1])) > np.sum(np.abs(Z_n_n - Z_n[:, 1])) + # Input Z_n rotates in trigo direction if first phase with negative shift is closer to second phase + is_trigo = np.mean(np.abs(Z_n_p - Z_n[:, 1])) > np.mean(np.abs(Z_n_n - Z_n[:, 1])) # phase_dir=+/-1 phase_dir = int((-1) ** int(is_trigo)) return phase_dir - - -def check_DataTime(data): - """Check if input data object is compliant with dqh transformation - - Parameters - ---------- - data : Data - data object to check - - """ - - if not isinstance(data, DataTime): - raise Exception("Input object should be a DataTime object") - if len(data.axes) != 2: - raise Exception("DataTime object should contain two axes: time and phase") - if data.axes[0].name != "time": - raise Exception("DataTime object should contain time as first axis") - if data.axes[1].name != "phase": - raise Exception("DataTime object should contain phase as second axis") \ No newline at end of file diff --git a/pyleecan/GUI/Dialog/DMachineSetup/SWinding/SWinding.py b/pyleecan/GUI/Dialog/DMachineSetup/SWinding/SWinding.py index 46536afd7..e1af32f8d 100644 --- a/pyleecan/GUI/Dialog/DMachineSetup/SWinding/SWinding.py +++ b/pyleecan/GUI/Dialog/DMachineSetup/SWinding/SWinding.py @@ -450,7 +450,7 @@ def comp_output(self): mmf_dir = "?" except Exception: # Unable to compution the connection matrix mmf_dir = "?" - self.out_mmf_dir.setText(self.tr("Rotation direction: ") + mmf_dir) + self.out_rot_dir.setText(self.tr("Rotation direction: ") + mmf_dir) try: ms = str(self.obj.slot.Zs / (wind.p * wind.qs * 2.0)) diff --git a/pyleecan/Generator/ClassesRef/Output/OutElec.csv b/pyleecan/Generator/ClassesRef/Output/OutElec.csv index 1639825c0..d0bcb1b34 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutElec.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutElec.csv @@ -12,5 +12,5 @@ OP,-,Operating Point,,OP,None,,,,,,,,, Pem_av_ref,W,Theorical Average Electromagnetic Power,1,float,None,,,,,,,,, Tem_av_ref,N.m,Theorical Average Electromagnetic torque,,float,None,,,,,,,,, Is_harm,A,Harmonic stator current ,,SciDataTool.Classes.DataND.DataND,None,,,,,,,,, -phase_dir,,"Rotation direction of the stator phases : phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF",0,int,None,-1,1,,,,,,, -current_dir,-,"Rotation direction of the stator currents : current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF",0,int,None,-1,1,,,,,,, +phase_dir,,"Rotation direction of the stator phases (phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF)",0,int,None,-1,1,,,,,,, +current_dir,-,"Rotation direction of the stator currents (current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF)",0,int,None,-1,1,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Output/OutGeo.csv b/pyleecan/Generator/ClassesRef/Output/OutGeo.csv index e4b9b9fe8..4d5e8512f 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutGeo.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutGeo.csv @@ -7,7 +7,7 @@ Rgap_mec,m,radius of the center of the mecanical airgap,0,float,None,,,,,,,,, Lgap,m,Airgap active length,0,float,None,,,,,,,,, logger_name,-,Name of the logger to use,0,str,Pyleecan.OutGeo,,,,,,,,, angle_rotor_initial,rad,Difference between the d axis angle of the stator and the rotor,0,float,None,,,,,,,,, -rot_dir,-,"Rotation direction of the rotor : rot_dir*N0, default value given by ROT_DIR_REF",,int,None,-1,1,,,,,,, +rot_dir,-,"Rotation direction of the rotor (rot_dir*N0, default value given by ROT_DIR_REF)",,int,None,-1,1,,,,,,, per_a,-,Number of spatial periodicities of the machine,0,int,None,,,,,,,,, is_antiper_a,-,True if an spatial anti-periodicity is possible after the periodicities,0,bool,None,,,,,,,,, per_t_S,-,Number of time periodicities of the machine in static referential,0,int,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv index 862e0c796..07f8c71ac 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv @@ -1,6 +1,6 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -rot_dir,-,"Rotation direction of the rotor : rot_dir*N0, default value given by ROT_DIR_REF",,int,None,-1,1,,Simulation,Input,gen_input,VERSION,1,Input to start the electrical module with voltage input +rot_dir,-,"Rotation direction of the rotor (rot_dir*N0, default value given by ROT_DIR_REF)",,int,None,-1,1,,Simulation,Input,gen_input,VERSION,1,Input to start the electrical module with voltage input angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,set_OP_from_array,,, PWM,,Object to generate PWM signal,,ImportGenPWM,None,,,,,,,,, -phase_dir,,"Rotation direction of the stator phases : phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF",0,int,None,-1,1,,,,,,, -current_dir,-,"Rotation direction of the stator currents : current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF",0,int,None,-1,1,,,,,,, +phase_dir,,"Rotation direction of the stator phase (phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF)",0,int,None,-1,1,,,,,,, +current_dir,-,"Rotation direction of the stator currents (current_dir*2*pi*felec*time, default value given by CURRENT_DIR_REF)",0,int,None,-1,1,,,,,,, diff --git a/pyleecan/Methods/Output/OutElec/store.py b/pyleecan/Methods/Output/OutElec/store.py index e07195a14..858afdbe2 100644 --- a/pyleecan/Methods/Output/OutElec/store.py +++ b/pyleecan/Methods/Output/OutElec/store.py @@ -55,5 +55,7 @@ def store(self, out_dict, out_dict_harm): Is_dqh_time = Is_dqh.freq_to_time() qs = self.parent.simu.machine.stator.winding.qs # back to ABC - Is_abc = dqh2n_DataTime(Is_dqh_time, qs, is_n_rms=True, phase=self.phase_dir) + Is_abc = dqh2n_DataTime( + Is_dqh_time, qs, is_n_rms=True, phase_dir=self.phase_dir + ) self.Is_harm = Is_abc.time_to_freq() diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index 1211f8763..06205584e 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -1,11 +1,12 @@ -from numpy import ndarray, mean, zeros +from numpy import mean as np_mean, zeros -from ....Methods.Simulation.Input import InputError +from SciDataTool import DataTime -from ....Functions.Electrical.dqh_transformation import n2dqh from ....Classes.InputVoltage import InputVoltage -from SciDataTool import Data1D, DataTime +from ....Functions.Electrical.dqh_transformation import n2dqh, get_phase_dir + +from ....Methods.Simulation.Input import InputError def gen_input(self): @@ -48,7 +49,16 @@ def gen_input(self): outelec.Is = None else: Is = self.Is.get_data() - if not isinstance(Is, ndarray) or Is.shape != (self.Nt_tot, qs): + # Get phase_dir from Is + phase_dir = get_phase_dir(Is) + if phase_dir != outelec.phase_dir: + self.get_logger().warning( + "Enforcing outelec.phase_dir=" + + str(phase_dir) + + " to comply with input current" + ) + outelec.phase_dir = phase_dir + if Is.shape != (self.Nt_tot, qs): raise InputError( "InputCurrent.Is must be a matrix with the shape " + str((self.Nt_tot, qs)) @@ -70,8 +80,9 @@ def gen_input(self): outelec.Is.values, Time.get_values(is_oneperiod=False, normalization="angle_elec"), is_dqh_rms=True, + phase_dir=outelec.phase_dir, ) - outelec.OP.set_Id_Iq(mean(Idq[:, 0]), mean(Idq[:, 1])) + outelec.OP.set_Id_Iq(np_mean(Idq[:, 0]), np_mean(Idq[:, 1])) # Load and check Ir is needed if qr > 0: @@ -79,7 +90,7 @@ def gen_input(self): Ir = zeros((self.Nt_tot, qr)) else: Ir = self.Ir.get_data() - if not isinstance(Ir, ndarray) or Ir.shape != (self.Nt_tot, qr): + if Ir.shape != (self.Nt_tot, qr): raise InputError( "InputCurrent.Ir must be a matrix with the shape " + str((self.Nt_tot, qr)) From 42f5d80b2e30073775b82352969a81d3856e70c0 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Sat, 30 Oct 2021 11:48:51 +0200 Subject: [PATCH 162/167] [CC] Reorganize/clean some tests --- Tests/Methods/Import/test_ImportGenPWM.py | 1 - Tests/Methods/Simulation/test_mmf_dir.py | 2 +- Tests/Validation/Electrical/test_EEC_PMSM.py | 6 +- Tests/Validation/Force/test_AGSF_spectrum.py | 78 ++++++++++++-------- 4 files changed, 53 insertions(+), 34 deletions(-) diff --git a/Tests/Methods/Import/test_ImportGenPWM.py b/Tests/Methods/Import/test_ImportGenPWM.py index cd1bf975a..cc3030a6d 100644 --- a/Tests/Methods/Import/test_ImportGenPWM.py +++ b/Tests/Methods/Import/test_ImportGenPWM.py @@ -60,7 +60,6 @@ def testSPWM(): @pytest.mark.long_5s -@pytest.mark.long_1m def testDPWM(): """Check""" for ii in range(9): diff --git a/Tests/Methods/Simulation/test_mmf_dir.py b/Tests/Methods/Simulation/test_mmf_dir.py index 8c54db477..b873cb5f7 100644 --- a/Tests/Methods/Simulation/test_mmf_dir.py +++ b/Tests/Methods/Simulation/test_mmf_dir.py @@ -36,7 +36,7 @@ }, ] -is_show_fig = True +is_show_fig = False @pytest.mark.long_5s diff --git a/Tests/Validation/Electrical/test_EEC_PMSM.py b/Tests/Validation/Electrical/test_EEC_PMSM.py index 686701c65..ffc9799f2 100644 --- a/Tests/Validation/Electrical/test_EEC_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_PMSM.py @@ -113,7 +113,7 @@ def test_EEC_PMSM_sync_rel(nb_worker=int(0.5 * cpu_count())): OP_matrix[:, 0] = 2000 # Set I0 = 250/sqrt(2) [A] (RMS) for all simulations OP_matrix[:, 1] = 250 / sqrt(2) - # Set Phi0 from 60� to 180� + # Set Phi0 from 60 to 180 OP_matrix[:, 2] = Phi0_ref # Set reference torque from Yang et al, 2013 OP_matrix[:, 3] = Tem_av_ref @@ -128,7 +128,7 @@ def test_EEC_PMSM_sync_rel(nb_worker=int(0.5 * cpu_count())): array([x * 180 / pi for x in out.xoutput_dict["Phi0"].result]), [out.xoutput_dict["Tem_av_ref"].result, Tem_av_ref], legend_list=["Pyleecan", "Yang et al, 2013"], - xlabel="Current angle [�]", + xlabel="Current angle [deg]", ylabel="Electrical torque [N.m]", title="Electrical torque vs current angle", save_path=join(save_path, "test_EEC_PMSM_validation.png"), @@ -154,7 +154,7 @@ def test_EEC_PMSM_sync_rel(nb_worker=int(0.5 * cpu_count())): array([x * 180 / pi for x in out.xoutput_dict["Phi0"].result]), [out.xoutput_dict["Tem_av_ref"].result, Tem_sync, Tem_rel], legend_list=["Overall", "Synchronous", "Reluctant"], - xlabel="Current angle [�]", + xlabel="Current angle [deg]", ylabel="Electrical torque [N.m]", title="Electrical torque vs current angle", save_path=join(save_path, "test_EEC_PMSM_sync_rel.png"), diff --git a/Tests/Validation/Force/test_AGSF_spectrum.py b/Tests/Validation/Force/test_AGSF_spectrum.py index 08129a8e5..5c020192f 100644 --- a/Tests/Validation/Force/test_AGSF_spectrum.py +++ b/Tests/Validation/Force/test_AGSF_spectrum.py @@ -25,31 +25,6 @@ DELTA = 1e-6 -# Load machine -Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) -# Prepare simulation -simu = Simu1(name="test_AGSF_spectrum", machine=Toyota_Prius) - -simu.input = InputCurrent( - OP=OPdq(Id_ref=0, Iq_ref=0, N0=1200), - Ir=None, - Na_tot=2 ** 6, - Nt_tot=4 * 2 ** 4, -) - -simu.elec = None - -simu.mag = MagFEMM( - type_BH_stator=1, - type_BH_rotor=1, - is_periodicity_a=False, - is_periodicity_t=False, -) -simu.force = ForceMT( - is_periodicity_a=False, - is_periodicity_t=False, -) - @pytest.mark.MagFEMM @pytest.mark.ForceMT @@ -62,7 +37,30 @@ def test_IPMSM_AGSF_spectrum_no_sym(): """Validation of the AGSF spectrum calculation for IPMSM machine""" - simu.name = "test_IPMSM_AGSF_spectrum_no_sym" + # Load machine + Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) + # Prepare simulation + simu = Simu1(name="test_IPMSM_AGSF_spectrum_no_sym", machine=Toyota_Prius) + + simu.input = InputCurrent( + OP=OPdq(Id_ref=0, Iq_ref=0, N0=1200), + Ir=None, + Na_tot=2 ** 6, + Nt_tot=4 * 2 ** 4, + ) + + simu.elec = None + + simu.mag = MagFEMM( + type_BH_stator=1, + type_BH_rotor=1, + is_periodicity_a=False, + is_periodicity_t=False, + ) + simu.force = ForceMT( + is_periodicity_a=False, + is_periodicity_t=False, + ) # Run simulation out = simu.run() @@ -131,10 +129,32 @@ def test_IPMSM_AGSF_spectrum_no_sym(): @pytest.mark.SingleOP def test_IPMSM_AGSF_spectrum_sym(): """Validation of the AGSF spectrum calculation for IPMSM machine with symmetries""" - # Test 2 : With sym - simu2 = simu.copy() - simu2.name = "test_IPMSM_AGSF_spectrum_sym" + + # Load machine + Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) + # Prepare simulation + simu2 = Simu1(name="test_IPMSM_AGSF_spectrum_sym", machine=Toyota_Prius) + + simu2.input = InputCurrent( + OP=OPdq(Id_ref=0, Iq_ref=0, N0=1200), + Ir=None, + Na_tot=2 ** 6, + Nt_tot=4 * 2 ** 4, + ) + + simu2.elec = None + + simu2.mag = MagFEMM( + type_BH_stator=1, + type_BH_rotor=1, + is_periodicity_a=False, + is_periodicity_t=False, + ) + simu2.force = ForceMT( + is_periodicity_a=False, + is_periodicity_t=False, + ) simu2.mag.is_periodicity_a = True simu2.mag.is_periodicity_t = True From b2a01bafdbd1683c8e3264eb4a67efa73db5ffbc Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Sat, 30 Oct 2021 15:58:44 +0200 Subject: [PATCH 163/167] [BC] correct test --- Tests/Classes/test_classes.py | 2 +- Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py | 14 +++++++------- pyleecan/Classes/Class_Dict.json | 2 +- pyleecan/Classes/LUT.py | 2 +- pyleecan/Generator/ClassesRef/Simulation/LUT.csv | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Tests/Classes/test_classes.py b/Tests/Classes/test_classes.py index 9e20ea5b8..ec137031a 100644 --- a/Tests/Classes/test_classes.py +++ b/Tests/Classes/test_classes.py @@ -427,4 +427,4 @@ def test_class_copy(class_dict): if __name__ == "__main__": # test_class_as_dict(class_list[116]) - test_class_prop_doc(class_list[116]) + test_class_prop_doc(class_list[160]) diff --git a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py index 8c66353c4..586cb6bdd 100644 --- a/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py +++ b/Tests/Validation/Electrical/test_EEC_ELUT_PMSM.py @@ -198,7 +198,7 @@ def test_EEC_ELUT_PMSM_MTPA(test_ELUT, n_Id=51, n_Iq=101): Zdata=Tem_interp.reshape((n_Iq, n_Id)).T, zlabel="Average Torque [N.m]", title="Torque map in dq plane", - # save_path=join(save_path, name + "_torque_map.png"), + save_path=join(save_path, name + "_torque_map.png"), **dict_map, ) # plt.contour( @@ -346,8 +346,8 @@ def test_EEC_ELUT_PMSM_MTPA(test_ELUT, n_Id=51, n_Iq=101): xlabel="Speed [rpm]", ylabel="Average torque [N.m]", legend_list=legend_list, - # save_path=join(save_path, name + "_MTPA_torque_speed.png"), - # is_show_fig=is_show_fig, + save_path=join(save_path, name + "_MTPA_torque_speed.png"), + is_show_fig=is_show_fig, ) i_load = -1 @@ -357,8 +357,8 @@ def test_EEC_ELUT_PMSM_MTPA(test_ELUT, n_Id=51, n_Iq=101): xlabel="Speed [rpm]", ylabel="Current [Arms]", legend_list=["Id", "Iq", "Imax"], - # save_path=join(save_path, name + "_current_MTPA_OP" + str(i_load) + ".png"), - # is_show_fig=is_show_fig, + save_path=join(save_path, name + "_current_MTPA_OP" + str(i_load) + ".png"), + is_show_fig=is_show_fig, ) plot_2D( @@ -367,8 +367,8 @@ def test_EEC_ELUT_PMSM_MTPA(test_ELUT, n_Id=51, n_Iq=101): xlabel="Speed [rpm]", ylabel="Voltage [Vrms]", legend_list=["Ud", "Uq", "Umax"], - # save_path=join(save_path, name + "_voltage_MTPA_OP" + str(i_load) + ".png"), - # is_show_fig=is_show_fig, + save_path=join(save_path, name + "_voltage_MTPA_OP" + str(i_load) + ".png"), + is_show_fig=is_show_fig, ) pass diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 6970b74cd..05d0dc912 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4421,7 +4421,7 @@ "value": null }, { - "desc": "Rotation direction of the stator phases\u00c2\u00a0: phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF", + "desc": "Rotation direction of the stator phases (phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF)", "max": "1", "min": "-1", "name": "phase_dir", diff --git a/pyleecan/Classes/LUT.py b/pyleecan/Classes/LUT.py index 914768260..e1c763c3f 100644 --- a/pyleecan/Classes/LUT.py +++ b/pyleecan/Classes/LUT.py @@ -314,7 +314,7 @@ def _set_phase_dir(self, value): phase_dir = property( fget=_get_phase_dir, fset=_set_phase_dir, - doc=u"""Rotation direction of the stator phases : phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF + doc=u"""Rotation direction of the stator phases (phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF) :Type: int :min: -1 diff --git a/pyleecan/Generator/ClassesRef/Simulation/LUT.csv b/pyleecan/Generator/ClassesRef/Simulation/LUT.csv index eac8970c2..e0637f483 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/LUT.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/LUT.csv @@ -3,4 +3,4 @@ R1,Ohm,DC phase winding resistance at T1_ref per phase ,0,float,None,,,,Simulati L1,H,Phase winding leakage inductance ,0,float,None,,,,,,get_phase_dir,,,, T1_ref,degC,"Stator winding average temperature associated to R1, L1 parameters",0,float,20,,,,,,,,,, OP_matrix,,"Array of operating point values of size (N,5) with N the number of operating points (Speed, Id, Iq, Torque, Power). OP values are set to nan if they are not given.",,ndarray,None,,,,,,,,,, -phase_dir,,"Rotation direction of the stator phases : phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF",0,int,None,-1,1,,,,,,,, +phase_dir,,"Rotation direction of the stator phases (phase_dir*(n-1)*pi/qs, default value given by PHASE_DIR_REF)",0,int,None,-1,1,,,,,,,, From 726e25635355b43d918f305486f65b2d69591fc6 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Tue, 2 Nov 2021 12:12:43 +0100 Subject: [PATCH 164/167] [CC] improve test of model periodicity in models --- pyleecan/Methods/Simulation/Force/comp_axes.py | 6 ++---- pyleecan/Methods/Simulation/Magnetics/comp_axes.py | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/pyleecan/Methods/Simulation/Force/comp_axes.py b/pyleecan/Methods/Simulation/Force/comp_axes.py index 1e0175dbe..8b28e0753 100644 --- a/pyleecan/Methods/Simulation/Force/comp_axes.py +++ b/pyleecan/Methods/Simulation/Force/comp_axes.py @@ -39,8 +39,7 @@ def comp_axes(self, output): is_periodicity_t0 = per_t0 > 1 or is_antiper_t0 if self.is_periodicity_t is None: self.is_periodicity_t = is_periodicity_t0 - is_periodic_machine_t = outgeo.per_t_S > 1 or outgeo.is_antiper_t_S - if is_periodicity_t0 != self.is_periodicity_t and is_periodic_machine_t: + if not is_periodicity_t0 and self.is_periodicity_t: # Remove time periodicity in Force model self.is_periodicity_t = False Nt_tot = axes_dict["time"].get_length(is_oneperiod=False) @@ -63,8 +62,7 @@ def comp_axes(self, output): is_periodicity_a0 = per_a0 > 1 or is_antiper_a0 if self.is_periodicity_a is None: self.is_periodicity_a = is_periodicity_a0 - is_periodic_machine_a = outgeo.per_a > 1 or outgeo.is_antiper_a - if is_periodicity_a0 != self.is_periodicity_a and is_periodic_machine_a: + if not is_periodicity_a0 and self.is_periodicity_a: # Remove time periodicity in Magnetic model self.is_periodicity_a = False Na_tot = axes_dict["angle"].get_length(is_oneperiod=False) diff --git a/pyleecan/Methods/Simulation/Magnetics/comp_axes.py b/pyleecan/Methods/Simulation/Magnetics/comp_axes.py index 49a9c83cc..6e45390da 100644 --- a/pyleecan/Methods/Simulation/Magnetics/comp_axes.py +++ b/pyleecan/Methods/Simulation/Magnetics/comp_axes.py @@ -32,8 +32,7 @@ def comp_axes(self, output): # Check Time periodicities regarding Magnetics model input per_t0, is_antiper_t0 = axes_dict["time"].get_periodicity() is_periodicity_t0 = per_t0 > 1 or is_antiper_t0 - is_periodic_machine_t = outgeo.per_t_S > 1 or outgeo.is_antiper_t_S - if is_periodicity_t0 != self.is_periodicity_t and is_periodic_machine_t: + if not is_periodicity_t0 and self.is_periodicity_t: # Remove time periodicity in Magnetic model self.is_periodicity_t = False Nt_tot = axes_dict["time"].get_length(is_oneperiod=False) @@ -48,8 +47,7 @@ def comp_axes(self, output): # Check Angle periodicities regarding Magnetics model input per_a0, is_antiper_a0 = axes_dict["angle"].get_periodicity() is_periodicity_a0 = per_a0 > 1 or is_antiper_a0 - is_periodic_machine_a = outgeo.per_a > 1 or outgeo.is_antiper_a - if is_periodicity_a0 != self.is_periodicity_a and is_periodic_machine_a: + if not is_periodicity_a0 and self.is_periodicity_a: # Remove time periodicity in Magnetic model self.is_periodicity_a = False Na_tot = axes_dict["angle"].get_length(is_oneperiod=False) From 3d5f9822bafbee64599a3897a78f9c1af2a55e09 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Tue, 2 Nov 2021 16:19:44 +0100 Subject: [PATCH 165/167] [CC] Before merging EEC into master --- pyleecan/Classes/Class_Dict.json | 9 ------ pyleecan/Classes/OutElec.py | 32 ------------------- .../Generator/ClassesRef/Output/OutElec.csv | 9 +++--- .../Simulation/LUTdq/interp_Phi_dqh.py | 2 +- 4 files changed, 5 insertions(+), 47 deletions(-) diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 05d0dc912..810e7217c 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -8197,15 +8197,6 @@ "unit": "A", "value": "None" }, - { - "desc": "Initial angular position of the rotor at t=0", - "max": "", - "min": "", - "name": "angle_rotor_initial", - "type": "float", - "unit": "", - "value": 0 - }, { "desc": "Name of the logger to use", "max": "", diff --git a/pyleecan/Classes/OutElec.py b/pyleecan/Classes/OutElec.py index 2a2fe4d28..264ab6ea1 100644 --- a/pyleecan/Classes/OutElec.py +++ b/pyleecan/Classes/OutElec.py @@ -138,7 +138,6 @@ def __init__( axes_dict=None, Is=None, Ir=None, - angle_rotor_initial=0, logger_name="Pyleecan.Electrical", Pj_losses=None, Us=None, @@ -174,8 +173,6 @@ def __init__( Is = init_dict["Is"] if "Ir" in list(init_dict.keys()): Ir = init_dict["Ir"] - if "angle_rotor_initial" in list(init_dict.keys()): - angle_rotor_initial = init_dict["angle_rotor_initial"] if "logger_name" in list(init_dict.keys()): logger_name = init_dict["logger_name"] if "Pj_losses" in list(init_dict.keys()): @@ -203,7 +200,6 @@ def __init__( self.axes_dict = axes_dict self.Is = Is self.Ir = Ir - self.angle_rotor_initial = angle_rotor_initial self.logger_name = logger_name self.Pj_losses = Pj_losses self.Us = Us @@ -230,9 +226,6 @@ def __str__(self): OutElec_str += "axes_dict = " + str(self.axes_dict) + linesep + linesep OutElec_str += "Is = " + str(self.Is) + linesep + linesep OutElec_str += "Ir = " + str(self.Ir) + linesep + linesep - OutElec_str += ( - "angle_rotor_initial = " + str(self.angle_rotor_initial) + linesep - ) OutElec_str += 'logger_name = "' + str(self.logger_name) + '"' + linesep OutElec_str += "Pj_losses = " + str(self.Pj_losses) + linesep OutElec_str += "Us = " + str(self.Us) + linesep + linesep @@ -265,8 +258,6 @@ def __eq__(self, other): return False if other.Ir != self.Ir: return False - if other.angle_rotor_initial != self.angle_rotor_initial: - return False if other.logger_name != self.logger_name: return False if other.Pj_losses != self.Pj_losses: @@ -326,8 +317,6 @@ def compare(self, other, name="self", ignore_list=None): diff_list.append(name + ".Ir None mismatch") elif self.Ir is not None: diff_list.extend(self.Ir.compare(other.Ir, name=name + ".Ir")) - if other._angle_rotor_initial != self._angle_rotor_initial: - diff_list.append(name + ".angle_rotor_initial") if other._logger_name != self._logger_name: diff_list.append(name + ".logger_name") if other._Pj_losses != self._Pj_losses: @@ -387,7 +376,6 @@ def __sizeof__(self): S += getsizeof(value) + getsizeof(key) S += getsizeof(self.Is) S += getsizeof(self.Ir) - S += getsizeof(self.angle_rotor_initial) S += getsizeof(self.logger_name) S += getsizeof(self.Pj_losses) S += getsizeof(self.Us) @@ -442,7 +430,6 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): keep_function=keep_function, **kwargs ) - OutElec_dict["angle_rotor_initial"] = self.angle_rotor_initial OutElec_dict["logger_name"] = self.logger_name OutElec_dict["Pj_losses"] = self.Pj_losses if self.Us is None: @@ -499,7 +486,6 @@ def _set_None(self): self.axes_dict = None self.Is = None self.Ir = None - self.angle_rotor_initial = None self.logger_name = None self.Pj_losses = None self.Us = None @@ -599,24 +585,6 @@ def _set_Ir(self, value): """, ) - def _get_angle_rotor_initial(self): - """getter of angle_rotor_initial""" - return self._angle_rotor_initial - - def _set_angle_rotor_initial(self, value): - """setter of angle_rotor_initial""" - check_var("angle_rotor_initial", value, "float") - self._angle_rotor_initial = value - - angle_rotor_initial = property( - fget=_get_angle_rotor_initial, - fset=_set_angle_rotor_initial, - doc=u"""Initial angular position of the rotor at t=0 - - :Type: float - """, - ) - def _get_logger_name(self): """getter of logger_name""" return self._logger_name diff --git a/pyleecan/Generator/ClassesRef/Output/OutElec.csv b/pyleecan/Generator/ClassesRef/Output/OutElec.csv index d0bcb1b34..20c4b2f72 100644 --- a/pyleecan/Generator/ClassesRef/Output/OutElec.csv +++ b/pyleecan/Generator/ClassesRef/Output/OutElec.csv @@ -2,11 +2,10 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximu axes_dict,,Dict containing axes data used for Electrical,,{SciDataTool.Classes.DataND.Data},None,,,,Output,,get_I_fund,VERSION,1,Gather the electric module outputs Is,A,Stator currents DataTime object,"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_I_harm,,, Ir,A,Rotor currents as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Is,,, -angle_rotor_initial,,Initial angular position of the rotor at t=0,,float,0,,,,,,get_Nr,,, -logger_name,-,Name of the logger to use,0,str,Pyleecan.Electrical,,,,,,get_Us,,, -Pj_losses,W,Electrical Joule losses,,float,None,,,,,,get_Us_harm,,, -Us,V,Stator voltage as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,store,,, -internal,-,OutInternal object containg outputs related to a specific model,0,OutInternal,None,,,,,,,,, +logger_name,-,Name of the logger to use,0,str,Pyleecan.Electrical,,,,,,get_Nr,,, +Pj_losses,W,Electrical Joule losses,,float,None,,,,,,get_Us,,, +Us,V,Stator voltage as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,get_Us_harm,,, +internal,-,OutInternal object containg outputs related to a specific model,0,OutInternal,None,,,,,,store,,, Us_PWM,V,PWM stator voltage as a function of time (each column correspond to one phase),"(qs, Nt_tot)",SciDataTool.Classes.DataND.DataND,None,,,,,,,,, OP,-,Operating Point,,OP,None,,,,,,,,, Pem_av_ref,W,Theorical Average Electromagnetic Power,1,float,None,,,,,,,,, diff --git a/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py b/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py index c8e7bcd58..2ac6f83d4 100644 --- a/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py +++ b/pyleecan/Methods/Simulation/LUTdq/interp_Phi_dqh.py @@ -1,5 +1,5 @@ -import scipy.interpolate as scp_int import numpy as np +import scipy.interpolate as scp_int def interp_Phi_dqh(self, Id, Iq): From b20ecb3b21c4669416520b9a49a87ec2af8b6d06 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Tue, 2 Nov 2021 16:53:44 +0100 Subject: [PATCH 166/167] [BC] correct test with new OPdq object --- Tests/Methods/Mesh/test_plot_contour.py | 7 ++-- Tests/Plot/Magnetics/test_plot_contour.py | 39 +++++++++++++---------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/Tests/Methods/Mesh/test_plot_contour.py b/Tests/Methods/Mesh/test_plot_contour.py index 1ebcb3d4d..b88fd4c64 100644 --- a/Tests/Methods/Mesh/test_plot_contour.py +++ b/Tests/Methods/Mesh/test_plot_contour.py @@ -1,14 +1,13 @@ -# -*- coding: utf-8 -*- import pytest + +import numpy as np + from pyleecan.Classes.MeshMat import MeshMat from pyleecan.Classes.NodeMat import NodeMat from pyleecan.Classes.CellMat import CellMat from pyleecan.Classes.MeshSolution import MeshSolution from pyleecan.Classes.SolutionMat import SolutionMat -import numpy as np -from os.path import join - from Tests import save_plot_path as save_path diff --git a/Tests/Plot/Magnetics/test_plot_contour.py b/Tests/Plot/Magnetics/test_plot_contour.py index e8c8f7810..af91fd574 100644 --- a/Tests/Plot/Magnetics/test_plot_contour.py +++ b/Tests/Plot/Magnetics/test_plot_contour.py @@ -1,21 +1,21 @@ -# -*- coding: utf-8 -*- from os import cpu_count +from os.path import join + import pytest + +import numpy as np + from pyleecan.Classes.InputCurrent import InputCurrent +from pyleecan.Classes.OPdq import OPdq from pyleecan.Classes.MagFEMM import MagFEMM -from pyleecan.Classes.MeshMat import MeshMat -from pyleecan.Classes.NodeMat import NodeMat -from pyleecan.Classes.CellMat import CellMat -from pyleecan.Classes.MeshSolution import MeshSolution from pyleecan.Classes.Simu1 import Simu1 -from pyleecan.Classes.SolutionMat import SolutionMat from pyleecan.Functions.load import load from pyleecan.definitions import DATA_DIR -import numpy as np -from os.path import join + from Tests import save_plot_path as save_path + @pytest.mark.long_5s @pytest.mark.long_1m @pytest.mark.MagFEMM @@ -35,11 +35,9 @@ def test_SPMSM015_plot_contour_B_FEMM(): Iq_ref = (I0_rms * np.exp(1j * Phi0)).imag simu.input = InputCurrent( - Id_ref=Id_ref, - Iq_ref=Iq_ref, + OP=OPdq(Id_ref=Id_ref, Iq_ref=Iq_ref, N0=1000), Na_tot=252 * 9, Nt_tot=4 * 9, - N0=1000, ) # Definition of the magnetic simulation: with periodicity @@ -55,7 +53,9 @@ def test_SPMSM015_plot_contour_B_FEMM(): out = simu.run() - out.mag.meshsolution.plot_contour(is_show_fig=False, save_path=join(save_path, "plot_mesh.png")) + out.mag.meshsolution.plot_contour( + is_show_fig=False, save_path=join(save_path, "plot_mesh.png") + ) out.mag.meshsolution.plot_contour( group_names="stator core", is_show_fig=False, @@ -70,6 +70,7 @@ def test_SPMSM015_plot_contour_B_FEMM(): pass + def test_Benchmark_plot_contour_B_FEMM(): """Validation of the implementaiton of periodic angle axis in Magnetic (MagFEMM) and Force (ForceMT) modules""" @@ -77,11 +78,9 @@ def test_Benchmark_plot_contour_B_FEMM(): simu = Simu1(name="test_FEMM_compare_Toyota_Prius", machine=Benchmark) simu.input = InputCurrent( - Id_ref=0, - Iq_ref=0, + OP=OPdq(Id_ref=0, Iq_ref=0, N0=2504), Na_tot=2048, Nt_tot=50, - N0=2504, ) # Definition of the magnetic simulation: with periodicity @@ -98,16 +97,22 @@ def test_Benchmark_plot_contour_B_FEMM(): out.plot_B_mesh(save_path=join(save_path, "plot_B_mesh.png")) - out.plot_B_mesh(group_names="stator core", is_animated=True, is_show_fig=False, save_path=join(save_path, "plot_B_mesh.gif"),) + out.plot_B_mesh( + group_names="stator core", + is_animated=True, + is_show_fig=False, + save_path=join(save_path, "plot_B_mesh.gif"), + ) out.mag.meshsolution.plot_contour( - group_names=["rotor magnets","rotor core"], + group_names=["rotor magnets", "rotor core"], is_show_fig=False, save_path=join(save_path, "plot_mesh_stator.png"), ) pass + if __name__ == "__main__": test_Benchmark_plot_contour_B_FEMM() From c608619753ed554621b82bdc2f704d97ce66662b Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Tue, 2 Nov 2021 17:41:31 +0100 Subject: [PATCH 167/167] [CC] comment figures in test --- Tests/Validation/Magnetics/test_FEMM_periodicity.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/Validation/Magnetics/test_FEMM_periodicity.py b/Tests/Validation/Magnetics/test_FEMM_periodicity.py index 6b0f2ceee..ad9d0421b 100644 --- a/Tests/Validation/Magnetics/test_FEMM_periodicity.py +++ b/Tests/Validation/Magnetics/test_FEMM_periodicity.py @@ -446,8 +446,8 @@ def test_FEMM_periodicity_angle(): "angle[0]{°}", data_list=[out2.mag.B], legend_list=["Periodic", "Full"], - # save_path=join(save_path, simu.name + "_B_time.png"), - # is_show_fig=False, + save_path=join(save_path, simu.name + "_B_time.png"), + is_show_fig=False, **dict_2D ) @@ -456,8 +456,8 @@ def test_FEMM_periodicity_angle(): "time[1]", data_list=[out2.mag.B], legend_list=["Periodic", "Full"], - # save_path=join(save_path, simu.name + "_B_space.png"), - # is_show_fig=False, + save_path=join(save_path, simu.name + "_B_space.png"), + is_show_fig=False, **dict_2D )