From 1ab79165b6836e88dc5e74ee39eeaf1cad453531 Mon Sep 17 00:00:00 2001 From: EmileDvs <58598539+EmileDvs@users.noreply.github.com> Date: Thu, 2 Dec 2021 14:42:05 +0100 Subject: [PATCH] [NF] Rework VarLoadCurrent/Voltage, InputVoltage/Current and OP/OPdq/OPslip classes --- pyleecan/Classes/Class_Dict.json | 87 ++-- pyleecan/Classes/Input.py | 30 ++ pyleecan/Classes/InputCurrent.py | 4 + pyleecan/Classes/InputFlux.py | 4 + pyleecan/Classes/InputForce.py | 11 +- pyleecan/Classes/InputVoltage.py | 27 +- pyleecan/Classes/OPdq.py | 26 +- pyleecan/Classes/OPslip.py | 14 + pyleecan/Classes/VarLoad.py | 150 ++++++- pyleecan/Classes/VarLoadCurrent.py | 155 +------ pyleecan/Classes/VarLoadVoltage.py | 251 +++++++++++ pyleecan/Classes/import_all.py | 1 + pyleecan/Functions/Electrical/comp_PWM.py | 414 ------------------ pyleecan/Functions/load_switch.py | 1 + .../Generator/ClassesRef/Simulation/Input.csv | 1 + .../ClassesRef/Simulation/InputVoltage.csv | 2 +- .../Generator/ClassesRef/Simulation/OPdq.csv | 2 +- .../ClassesRef/Simulation/OPslip.csv | 1 + .../ClassesRef/Simulation/VarLoad.csv | 6 +- .../ClassesRef/Simulation/VarLoadCurrent.csv | 7 +- .../ClassesRef/Simulation/VarLoadVoltage.csv | 4 + .../Methods/Simulation/Input/comp_axes.py | 3 +- .../Simulation/Input/comp_axis_angle.py | 10 +- .../Simulation/Input/comp_axis_phase.py | 40 +- .../Simulation/Input/comp_axis_time.py | 14 +- .../Simulation/InputCurrent/gen_input.py | 76 +++- .../InputCurrent/set_OP_from_array.py | 21 +- .../Simulation/InputVoltage/gen_input.py | 56 ++- .../InputVoltage/set_OP_from_array.py | 25 +- .../Simulation/InputVoltage/set_Ud_Uq.py | 22 + pyleecan/Methods/Simulation/OPdq/set_Ud_Uq.py | 15 + .../Methods/Simulation/OPslip/set_Ud_Uq.py | 23 + .../Simulation/VarLoadCurrent/check_param.py | 2 - .../Simulation/VarLoadVoltage/__init__.py | 0 .../generate_simulation_list.py | 74 ++++ .../VarLoadVoltage/get_elec_datakeeper.py | 69 +++ .../VarLoadVoltage/get_input_list.py | 44 ++ 37 files changed, 995 insertions(+), 697 deletions(-) create mode 100644 pyleecan/Classes/VarLoadVoltage.py delete mode 100644 pyleecan/Functions/Electrical/comp_PWM.py create mode 100644 pyleecan/Generator/ClassesRef/Simulation/VarLoadVoltage.csv create mode 100644 pyleecan/Methods/Simulation/InputVoltage/set_Ud_Uq.py create mode 100644 pyleecan/Methods/Simulation/OPdq/set_Ud_Uq.py create mode 100644 pyleecan/Methods/Simulation/OPslip/set_Ud_Uq.py delete mode 100644 pyleecan/Methods/Simulation/VarLoadCurrent/check_param.py create mode 100644 pyleecan/Methods/Simulation/VarLoadVoltage/__init__.py create mode 100644 pyleecan/Methods/Simulation/VarLoadVoltage/generate_simulation_list.py create mode 100644 pyleecan/Methods/Simulation/VarLoadVoltage/get_elec_datakeeper.py create mode 100644 pyleecan/Methods/Simulation/VarLoadVoltage/get_input_list.py diff --git a/pyleecan/Classes/Class_Dict.json b/pyleecan/Classes/Class_Dict.json index 6728d2e4e..115dfe23c 100644 --- a/pyleecan/Classes/Class_Dict.json +++ b/pyleecan/Classes/Class_Dict.json @@ -4045,6 +4045,15 @@ "type": "OP", "unit": "-", "value": null + }, + { + "desc": "To enforce final time", + "max": "", + "min": "", + "name": "t_final", + "type": "float", + "unit": "", + "value": null } ] }, @@ -4226,7 +4235,8 @@ "is_internal": false, "methods": [ "gen_input", - "set_OP_from_array" + "set_OP_from_array", + "set_Ud_Uq" ], "mother": "Input", "name": "InputVoltage", @@ -7608,8 +7618,8 @@ "get_slip", "get_Ud_Uq", "set_Id_Iq", - "set_I0_Phi0", - "get_I0_Phi0" + "get_I0_Phi0", + "set_Ud_Uq" ], "mother": "OP", "name": "OPdq", @@ -7672,7 +7682,8 @@ "set_Id_Iq", "get_I0_Phi0", "get_slip", - "set_I0_Phi0" + "set_I0_Phi0", + "set_Ud_Uq" ], "mother": "OP", "name": "OPslip", @@ -13583,7 +13594,8 @@ } ], "daughters": [ - "VarLoadCurrent" + "VarLoadCurrent", + "VarLoadVoltage" ], "desc": "Abstract class to generate multi-simulation by changing the operating point", "is_internal": false, @@ -13594,28 +13606,6 @@ "name": "VarLoad", "package": "Simulation", "path": "pyleecan/Generator/ClassesRef/Simulation/VarLoad.csv", - "properties": [] - }, - "VarLoadCurrent": { - "constants": [ - { - "name": "VERSION", - "value": "1" - } - ], - "daughters": [], - "desc": "Generate a multisimulation with InputCurrent at variable operating point", - "is_internal": false, - "methods": [ - "get_input_list", - "generate_simulation_list", - "check_param", - "get_elec_datakeeper" - ], - "mother": "VarLoad", - "name": "VarLoadCurrent", - "package": "Simulation", - "path": "pyleecan/Generator/ClassesRef/Simulation/VarLoadCurrent.csv", "properties": [ { "desc": "Operating point matrix (N0,I0,Phi0,T,P) or (N0,Id,Iq,T,P) ", @@ -13655,6 +13645,48 @@ } ] }, + "VarLoadCurrent": { + "constants": [ + { + "name": "VERSION", + "value": "1" + } + ], + "daughters": [], + "desc": "Generate a multisimulation with InputCurrent at variable operating point", + "is_internal": false, + "methods": [ + "get_input_list", + "generate_simulation_list", + "get_elec_datakeeper" + ], + "mother": "VarLoad", + "name": "VarLoadCurrent", + "package": "Simulation", + "path": "pyleecan/Generator/ClassesRef/Simulation/VarLoadCurrent.csv", + "properties": [] + }, + "VarLoadVoltage": { + "constants": [ + { + "name": "VERSION", + "value": "1" + } + ], + "daughters": [], + "desc": "Generate a multisimulation with InputVoltage at variable operating point", + "is_internal": false, + "methods": [ + "generate_simulation_list", + "get_elec_datakeeper", + "get_input_list" + ], + "mother": "VarLoad", + "name": "VarLoadVoltage", + "package": "Simulation", + "path": "pyleecan/Generator/ClassesRef/Simulation/VarLoadVoltage.csv", + "properties": [] + }, "VarParam": { "constants": [ { @@ -13700,6 +13732,7 @@ "daughters": [ "VarLoad", "VarLoadCurrent", + "VarLoadVoltage", "VarParam" ], "desc": "Abstract class for the multi-simulation", diff --git a/pyleecan/Classes/Input.py b/pyleecan/Classes/Input.py index 481a474e6..cb7fa0eb4 100644 --- a/pyleecan/Classes/Input.py +++ b/pyleecan/Classes/Input.py @@ -108,6 +108,7 @@ def __init__( Nrev=None, Na_tot=2048, OP=None, + t_final=None, init_dict=None, init_str=None, ): @@ -138,6 +139,8 @@ def __init__( Na_tot = init_dict["Na_tot"] if "OP" in list(init_dict.keys()): OP = init_dict["OP"] + if "t_final" in list(init_dict.keys()): + t_final = init_dict["t_final"] # Set the properties (value check and convertion are done in setter) self.parent = None self.time = time @@ -146,6 +149,7 @@ def __init__( self.Nrev = Nrev self.Na_tot = Na_tot self.OP = OP + self.t_final = t_final # The class is frozen, for now it's impossible to add new properties self._freeze() @@ -176,6 +180,7 @@ def __str__(self): Input_str += "OP = " + tmp else: Input_str += "OP = None" + linesep + linesep + Input_str += "t_final = " + str(self.t_final) + linesep return Input_str def __eq__(self, other): @@ -195,6 +200,8 @@ def __eq__(self, other): return False if other.OP != self.OP: return False + if other.t_final != self.t_final: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -229,6 +236,8 @@ 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._t_final != self._t_final: + diff_list.append(name + ".t_final") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -243,6 +252,7 @@ def __sizeof__(self): S += getsizeof(self.Nrev) S += getsizeof(self.Na_tot) S += getsizeof(self.OP) + S += getsizeof(self.t_final) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -284,6 +294,7 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): keep_function=keep_function, **kwargs ) + Input_dict["t_final"] = self.t_final # The class name is added to the dict for deserialisation purpose Input_dict["__class__"] = "Input" return Input_dict @@ -300,6 +311,7 @@ def _set_None(self): self.Na_tot = None if self.OP is not None: self.OP._set_None() + self.t_final = None def _get_time(self): """getter of time""" @@ -451,3 +463,21 @@ def _set_OP(self, value): :Type: OP """, ) + + def _get_t_final(self): + """getter of t_final""" + return self._t_final + + def _set_t_final(self, value): + """setter of t_final""" + check_var("t_final", value, "float") + self._t_final = value + + t_final = property( + fget=_get_t_final, + fset=_set_t_final, + doc=u"""To enforce final time + + :Type: float + """, + ) diff --git a/pyleecan/Classes/InputCurrent.py b/pyleecan/Classes/InputCurrent.py index 4dd115030..333ad683f 100644 --- a/pyleecan/Classes/InputCurrent.py +++ b/pyleecan/Classes/InputCurrent.py @@ -103,6 +103,7 @@ def __init__( Nrev=None, Na_tot=2048, OP=None, + t_final=None, init_dict=None, init_str=None, ): @@ -147,6 +148,8 @@ def __init__( Na_tot = init_dict["Na_tot"] if "OP" in list(init_dict.keys()): OP = init_dict["OP"] + if "t_final" in list(init_dict.keys()): + t_final = init_dict["t_final"] # Set the properties (value check and convertion are done in setter) self.Is = Is self.Ir = Ir @@ -163,6 +166,7 @@ def __init__( Nrev=Nrev, Na_tot=Na_tot, OP=OP, + t_final=t_final, ) # The class is frozen (in InputVoltage init), for now it's impossible to # add new properties diff --git a/pyleecan/Classes/InputFlux.py b/pyleecan/Classes/InputFlux.py index 93753419a..3d6ab2564 100644 --- a/pyleecan/Classes/InputFlux.py +++ b/pyleecan/Classes/InputFlux.py @@ -75,6 +75,7 @@ def __init__( Nrev=None, Na_tot=2048, OP=None, + t_final=None, init_dict=None, init_str=None, ): @@ -135,6 +136,8 @@ def __init__( Na_tot = init_dict["Na_tot"] if "OP" in list(init_dict.keys()): OP = init_dict["OP"] + if "t_final" in list(init_dict.keys()): + t_final = init_dict["t_final"] # Set the properties (value check and convertion are done in setter) self.per_a = per_a self.per_t = per_t @@ -159,6 +162,7 @@ def __init__( Nrev=Nrev, Na_tot=Na_tot, OP=OP, + t_final=t_final, ) # 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 81d944de3..f4ab94664 100644 --- a/pyleecan/Classes/InputForce.py +++ b/pyleecan/Classes/InputForce.py @@ -61,6 +61,7 @@ def __init__( Nrev=None, Na_tot=2048, OP=None, + t_final=None, init_dict=None, init_str=None, ): @@ -93,11 +94,19 @@ def __init__( Na_tot = init_dict["Na_tot"] if "OP" in list(init_dict.keys()): OP = init_dict["OP"] + if "t_final" in list(init_dict.keys()): + t_final = init_dict["t_final"] # 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, OP=OP + time=time, + angle=angle, + Nt_tot=Nt_tot, + Nrev=Nrev, + Na_tot=Na_tot, + OP=OP, + t_final=t_final, ) # 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 06f3f0d28..6798ff7a3 100644 --- a/pyleecan/Classes/InputVoltage.py +++ b/pyleecan/Classes/InputVoltage.py @@ -27,6 +27,11 @@ except ImportError as error: set_OP_from_array = error +try: + from ..Methods.Simulation.InputVoltage.set_Ud_Uq import set_Ud_Uq +except ImportError as error: + set_Ud_Uq = error + from ..Classes.ImportMatrixVal import ImportMatrixVal from numpy import ndarray @@ -66,6 +71,17 @@ class InputVoltage(Input): ) else: set_OP_from_array = set_OP_from_array + # cf Methods.Simulation.InputVoltage.set_Ud_Uq + if isinstance(set_Ud_Uq, ImportError): + set_Ud_Uq = property( + fget=lambda x: raise_( + ImportError( + "Can't use InputVoltage method set_Ud_Uq: " + str(set_Ud_Uq) + ) + ) + ) + else: + set_Ud_Uq = set_Ud_Uq # save and copy methods are available in all object save = save copy = copy @@ -85,6 +101,7 @@ def __init__( Nrev=None, Na_tot=2048, OP=None, + t_final=None, init_dict=None, init_str=None, ): @@ -125,6 +142,8 @@ def __init__( Na_tot = init_dict["Na_tot"] if "OP" in list(init_dict.keys()): OP = init_dict["OP"] + if "t_final" in list(init_dict.keys()): + t_final = init_dict["t_final"] # Set the properties (value check and convertion are done in setter) self.rot_dir = rot_dir self.angle_rotor_initial = angle_rotor_initial @@ -133,7 +152,13 @@ def __init__( 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 + time=time, + angle=angle, + Nt_tot=Nt_tot, + Nrev=Nrev, + Na_tot=Na_tot, + OP=OP, + t_final=t_final, ) # The class is frozen (in Input init), for now it's impossible to # add new properties diff --git a/pyleecan/Classes/OPdq.py b/pyleecan/Classes/OPdq.py index 6d0dadbab..1acec6102 100644 --- a/pyleecan/Classes/OPdq.py +++ b/pyleecan/Classes/OPdq.py @@ -48,14 +48,14 @@ set_Id_Iq = error try: - from ..Methods.Simulation.OPdq.set_I0_Phi0 import set_I0_Phi0 + from ..Methods.Simulation.OPdq.get_I0_Phi0 import get_I0_Phi0 except ImportError as error: - set_I0_Phi0 = error + get_I0_Phi0 = error try: - from ..Methods.Simulation.OPdq.get_I0_Phi0 import get_I0_Phi0 + from ..Methods.Simulation.OPdq.set_Ud_Uq import set_Ud_Uq except ImportError as error: - get_I0_Phi0 = error + set_Ud_Uq = error from ._check import InitUnKnowClassError @@ -121,15 +121,6 @@ class OPdq(OP): ) else: set_Id_Iq = set_Id_Iq - # cf Methods.Simulation.OPdq.set_I0_Phi0 - if isinstance(set_I0_Phi0, ImportError): - set_I0_Phi0 = property( - fget=lambda x: raise_( - ImportError("Can't use OPdq method set_I0_Phi0: " + str(set_I0_Phi0)) - ) - ) - else: - set_I0_Phi0 = set_I0_Phi0 # cf Methods.Simulation.OPdq.get_I0_Phi0 if isinstance(get_I0_Phi0, ImportError): get_I0_Phi0 = property( @@ -139,6 +130,15 @@ class OPdq(OP): ) else: get_I0_Phi0 = get_I0_Phi0 + # cf Methods.Simulation.OPdq.set_Ud_Uq + if isinstance(set_Ud_Uq, ImportError): + set_Ud_Uq = property( + fget=lambda x: raise_( + ImportError("Can't use OPdq method set_Ud_Uq: " + str(set_Ud_Uq)) + ) + ) + else: + set_Ud_Uq = set_Ud_Uq # 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 f0c2091be..a89b01b33 100644 --- a/pyleecan/Classes/OPslip.py +++ b/pyleecan/Classes/OPslip.py @@ -57,6 +57,11 @@ except ImportError as error: set_I0_Phi0 = error +try: + from ..Methods.Simulation.OPslip.set_Ud_Uq import set_Ud_Uq +except ImportError as error: + set_Ud_Uq = error + from ._check import InitUnKnowClassError @@ -139,6 +144,15 @@ class OPslip(OP): ) else: set_I0_Phi0 = set_I0_Phi0 + # cf Methods.Simulation.OPslip.set_Ud_Uq + if isinstance(set_Ud_Uq, ImportError): + set_Ud_Uq = property( + fget=lambda x: raise_( + ImportError("Can't use OPslip method set_Ud_Uq: " + str(set_Ud_Uq)) + ) + ) + else: + set_Ud_Uq = set_Ud_Uq # save and copy methods are available in all object save = save copy = copy diff --git a/pyleecan/Classes/VarLoad.py b/pyleecan/Classes/VarLoad.py index a953357a1..ff3bfec4a 100644 --- a/pyleecan/Classes/VarLoad.py +++ b/pyleecan/Classes/VarLoad.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_ref_simu_index = error +from numpy import array, array_equal from ._check import InitUnKnowClassError from .DataKeeper import DataKeeper from .VarSimu import VarSimu @@ -55,6 +56,10 @@ class VarLoad(VarSimu): def __init__( self, + OP_matrix=None, + type_OP_matrix=0, + is_torque=False, + is_power=False, name="", desc="", datakeeper_list=-1, @@ -84,6 +89,14 @@ 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 "OP_matrix" in list(init_dict.keys()): + OP_matrix = init_dict["OP_matrix"] + if "type_OP_matrix" in list(init_dict.keys()): + type_OP_matrix = init_dict["type_OP_matrix"] + if "is_torque" in list(init_dict.keys()): + is_torque = init_dict["is_torque"] + if "is_power" in list(init_dict.keys()): + is_power = init_dict["is_power"] if "name" in list(init_dict.keys()): name = init_dict["name"] if "desc" in list(init_dict.keys()): @@ -107,6 +120,10 @@ def __init__( if "post_keeper_postproc_list" in list(init_dict.keys()): post_keeper_postproc_list = init_dict["post_keeper_postproc_list"] # Set the properties (value check and convertion are done in setter) + self.OP_matrix = OP_matrix + self.type_OP_matrix = type_OP_matrix + self.is_torque = is_torque + self.is_power = is_power # Call VarSimu init super(VarLoad, self).__init__( name=name, @@ -130,6 +147,16 @@ def __str__(self): VarLoad_str = "" # Get the properties inherited from VarSimu VarLoad_str += super(VarLoad, self).__str__() + VarLoad_str += ( + "OP_matrix = " + + linesep + + str(self.OP_matrix).replace(linesep, linesep + "\t") + + linesep + + linesep + ) + VarLoad_str += "type_OP_matrix = " + str(self.type_OP_matrix) + linesep + VarLoad_str += "is_torque = " + str(self.is_torque) + linesep + VarLoad_str += "is_power = " + str(self.is_power) + linesep return VarLoad_str def __eq__(self, other): @@ -141,6 +168,14 @@ def __eq__(self, other): # Check the properties inherited from VarSimu if not super(VarLoad, self).__eq__(other): return False + if not array_equal(other.OP_matrix, self.OP_matrix): + return False + if other.type_OP_matrix != self.type_OP_matrix: + return False + if other.is_torque != self.is_torque: + return False + if other.is_power != self.is_power: + return False return True def compare(self, other, name="self", ignore_list=None): @@ -154,6 +189,14 @@ def compare(self, other, name="self", ignore_list=None): # Check the properties inherited from VarSimu diff_list.extend(super(VarLoad, self).compare(other, name=name)) + if not array_equal(other.OP_matrix, self.OP_matrix): + diff_list.append(name + ".OP_matrix") + if other._type_OP_matrix != self._type_OP_matrix: + diff_list.append(name + ".type_OP_matrix") + if other._is_torque != self._is_torque: + diff_list.append(name + ".is_torque") + if other._is_power != self._is_power: + diff_list.append(name + ".is_power") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -165,6 +208,10 @@ def __sizeof__(self): # Get size of the properties inherited from VarSimu S += super(VarLoad, self).__sizeof__() + S += getsizeof(self.OP_matrix) + S += getsizeof(self.type_OP_matrix) + S += getsizeof(self.is_torque) + S += getsizeof(self.is_power) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -184,6 +231,22 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): keep_function=keep_function, **kwargs ) + if self.OP_matrix is None: + VarLoad_dict["OP_matrix"] = None + else: + if type_handle_ndarray == 0: + VarLoad_dict["OP_matrix"] = self.OP_matrix.tolist() + elif type_handle_ndarray == 1: + VarLoad_dict["OP_matrix"] = self.OP_matrix.copy() + elif type_handle_ndarray == 2: + VarLoad_dict["OP_matrix"] = self.OP_matrix + else: + raise Exception( + "Unknown type_handle_ndarray: " + str(type_handle_ndarray) + ) + VarLoad_dict["type_OP_matrix"] = self.type_OP_matrix + VarLoad_dict["is_torque"] = self.is_torque + VarLoad_dict["is_power"] = self.is_power # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name VarLoad_dict["__class__"] = "VarLoad" @@ -192,5 +255,90 @@ 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.OP_matrix = None + self.type_OP_matrix = None + self.is_torque = None + self.is_power = None # Set to None the properties inherited from VarSimu super(VarLoad, self)._set_None() + + 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"""Operating point matrix (N0,I0,Phi0,T,P) or (N0,Id,Iq,T,P) + + :Type: ndarray + """, + ) + + def _get_type_OP_matrix(self): + """getter of type_OP_matrix""" + return self._type_OP_matrix + + def _set_type_OP_matrix(self, value): + """setter of type_OP_matrix""" + check_var("type_OP_matrix", value, "int", Vmin=0, Vmax=1) + self._type_OP_matrix = value + + type_OP_matrix = property( + fget=_get_type_OP_matrix, + fset=_set_type_OP_matrix, + doc=u"""Select which kind of OP_matrix is used 0: (N0,I0,Phi0,T,P), 1:(N0,Id,Iq,T,P) + + :Type: int + :min: 0 + :max: 1 + """, + ) + + def _get_is_torque(self): + """getter of is_torque""" + return self._is_torque + + def _set_is_torque(self, value): + """setter of is_torque""" + check_var("is_torque", value, "bool") + self._is_torque = value + + is_torque = property( + fget=_get_is_torque, + fset=_set_is_torque, + doc=u"""True if the Torque is defined in OP_matrix + + :Type: bool + """, + ) + + def _get_is_power(self): + """getter of is_power""" + return self._is_power + + def _set_is_power(self, value): + """setter of is_power""" + check_var("is_power", value, "bool") + self._is_power = value + + is_power = property( + fget=_get_is_power, + fset=_set_is_power, + doc=u"""True if the Power is defined in OP_matrix + + :Type: bool + """, + ) diff --git a/pyleecan/Classes/VarLoadCurrent.py b/pyleecan/Classes/VarLoadCurrent.py index d51f550cc..c78d586e1 100644 --- a/pyleecan/Classes/VarLoadCurrent.py +++ b/pyleecan/Classes/VarLoadCurrent.py @@ -29,11 +29,6 @@ except ImportError as error: generate_simulation_list = error -try: - from ..Methods.Simulation.VarLoadCurrent.check_param import check_param -except ImportError as error: - check_param = error - try: from ..Methods.Simulation.VarLoadCurrent.get_elec_datakeeper import ( get_elec_datakeeper, @@ -79,17 +74,6 @@ class VarLoadCurrent(VarLoad): ) else: generate_simulation_list = generate_simulation_list - # cf Methods.Simulation.VarLoadCurrent.check_param - if isinstance(check_param, ImportError): - check_param = property( - fget=lambda x: raise_( - ImportError( - "Can't use VarLoadCurrent method check_param: " + str(check_param) - ) - ) - ) - else: - check_param = check_param # cf Methods.Simulation.VarLoadCurrent.get_elec_datakeeper if isinstance(get_elec_datakeeper, ImportError): get_elec_datakeeper = property( @@ -174,12 +158,12 @@ def __init__( if "post_keeper_postproc_list" in list(init_dict.keys()): post_keeper_postproc_list = init_dict["post_keeper_postproc_list"] # Set the properties (value check and convertion are done in setter) - self.OP_matrix = OP_matrix - self.type_OP_matrix = type_OP_matrix - self.is_torque = is_torque - self.is_power = is_power # Call VarLoad init super(VarLoadCurrent, self).__init__( + OP_matrix=OP_matrix, + type_OP_matrix=type_OP_matrix, + is_torque=is_torque, + is_power=is_power, name=name, desc=desc, datakeeper_list=datakeeper_list, @@ -201,16 +185,6 @@ def __str__(self): VarLoadCurrent_str = "" # Get the properties inherited from VarLoad VarLoadCurrent_str += super(VarLoadCurrent, self).__str__() - VarLoadCurrent_str += ( - "OP_matrix = " - + linesep - + str(self.OP_matrix).replace(linesep, linesep + "\t") - + linesep - + linesep - ) - VarLoadCurrent_str += "type_OP_matrix = " + str(self.type_OP_matrix) + linesep - VarLoadCurrent_str += "is_torque = " + str(self.is_torque) + linesep - VarLoadCurrent_str += "is_power = " + str(self.is_power) + linesep return VarLoadCurrent_str def __eq__(self, other): @@ -222,14 +196,6 @@ def __eq__(self, other): # Check the properties inherited from VarLoad if not super(VarLoadCurrent, self).__eq__(other): return False - if not array_equal(other.OP_matrix, self.OP_matrix): - return False - if other.type_OP_matrix != self.type_OP_matrix: - return False - if other.is_torque != self.is_torque: - return False - if other.is_power != self.is_power: - return False return True def compare(self, other, name="self", ignore_list=None): @@ -243,14 +209,6 @@ def compare(self, other, name="self", ignore_list=None): # Check the properties inherited from VarLoad diff_list.extend(super(VarLoadCurrent, self).compare(other, name=name)) - if not array_equal(other.OP_matrix, self.OP_matrix): - diff_list.append(name + ".OP_matrix") - if other._type_OP_matrix != self._type_OP_matrix: - diff_list.append(name + ".type_OP_matrix") - if other._is_torque != self._is_torque: - diff_list.append(name + ".is_torque") - if other._is_power != self._is_power: - diff_list.append(name + ".is_power") # Filter ignore differences diff_list = list(filter(lambda x: x not in ignore_list, diff_list)) return diff_list @@ -262,10 +220,6 @@ def __sizeof__(self): # Get size of the properties inherited from VarLoad S += super(VarLoadCurrent, self).__sizeof__() - S += getsizeof(self.OP_matrix) - S += getsizeof(self.type_OP_matrix) - S += getsizeof(self.is_torque) - S += getsizeof(self.is_power) return S def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): @@ -285,22 +239,6 @@ def as_dict(self, type_handle_ndarray=0, keep_function=False, **kwargs): keep_function=keep_function, **kwargs ) - if self.OP_matrix is None: - VarLoadCurrent_dict["OP_matrix"] = None - else: - if type_handle_ndarray == 0: - VarLoadCurrent_dict["OP_matrix"] = self.OP_matrix.tolist() - elif type_handle_ndarray == 1: - VarLoadCurrent_dict["OP_matrix"] = self.OP_matrix.copy() - elif type_handle_ndarray == 2: - VarLoadCurrent_dict["OP_matrix"] = self.OP_matrix - else: - raise Exception( - "Unknown type_handle_ndarray: " + str(type_handle_ndarray) - ) - VarLoadCurrent_dict["type_OP_matrix"] = self.type_OP_matrix - VarLoadCurrent_dict["is_torque"] = self.is_torque - VarLoadCurrent_dict["is_power"] = self.is_power # The class name is added to the dict for deserialisation purpose # Overwrite the mother class name VarLoadCurrent_dict["__class__"] = "VarLoadCurrent" @@ -309,90 +247,5 @@ 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.OP_matrix = None - self.type_OP_matrix = None - self.is_torque = None - self.is_power = None # Set to None the properties inherited from VarLoad super(VarLoadCurrent, self)._set_None() - - 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"""Operating point matrix (N0,I0,Phi0,T,P) or (N0,Id,Iq,T,P) - - :Type: ndarray - """, - ) - - def _get_type_OP_matrix(self): - """getter of type_OP_matrix""" - return self._type_OP_matrix - - def _set_type_OP_matrix(self, value): - """setter of type_OP_matrix""" - check_var("type_OP_matrix", value, "int", Vmin=0, Vmax=1) - self._type_OP_matrix = value - - type_OP_matrix = property( - fget=_get_type_OP_matrix, - fset=_set_type_OP_matrix, - doc=u"""Select which kind of OP_matrix is used 0: (N0,I0,Phi0,T,P), 1:(N0,Id,Iq,T,P) - - :Type: int - :min: 0 - :max: 1 - """, - ) - - def _get_is_torque(self): - """getter of is_torque""" - return self._is_torque - - def _set_is_torque(self, value): - """setter of is_torque""" - check_var("is_torque", value, "bool") - self._is_torque = value - - is_torque = property( - fget=_get_is_torque, - fset=_set_is_torque, - doc=u"""True if the Torque is defined in OP_matrix - - :Type: bool - """, - ) - - def _get_is_power(self): - """getter of is_power""" - return self._is_power - - def _set_is_power(self, value): - """setter of is_power""" - check_var("is_power", value, "bool") - self._is_power = value - - is_power = property( - fget=_get_is_power, - fset=_set_is_power, - doc=u"""True if the Power is defined in OP_matrix - - :Type: bool - """, - ) diff --git a/pyleecan/Classes/VarLoadVoltage.py b/pyleecan/Classes/VarLoadVoltage.py new file mode 100644 index 000000000..efbdcba23 --- /dev/null +++ b/pyleecan/Classes/VarLoadVoltage.py @@ -0,0 +1,251 @@ +# -*- coding: utf-8 -*- +# File generated according to Generator/ClassesRef/Simulation/VarLoadVoltage.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/VarLoadVoltage +""" + +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 .VarLoad import VarLoad + +# Import all class method +# Try/catch to remove unnecessary dependencies in unused method +try: + from ..Methods.Simulation.VarLoadVoltage.generate_simulation_list import ( + generate_simulation_list, + ) +except ImportError as error: + generate_simulation_list = error + +try: + from ..Methods.Simulation.VarLoadVoltage.get_elec_datakeeper import ( + get_elec_datakeeper, + ) +except ImportError as error: + get_elec_datakeeper = error + +try: + from ..Methods.Simulation.VarLoadVoltage.get_input_list import get_input_list +except ImportError as error: + get_input_list = error + + +from numpy import array, array_equal +from ._check import InitUnKnowClassError +from .DataKeeper import DataKeeper +from .VarSimu import VarSimu +from .Post import Post + + +class VarLoadVoltage(VarLoad): + """Generate a multisimulation with InputVoltage at variable operating point""" + + VERSION = 1 + + # Check ImportError to remove unnecessary dependencies in unused method + # cf Methods.Simulation.VarLoadVoltage.generate_simulation_list + if isinstance(generate_simulation_list, ImportError): + generate_simulation_list = property( + fget=lambda x: raise_( + ImportError( + "Can't use VarLoadVoltage method generate_simulation_list: " + + str(generate_simulation_list) + ) + ) + ) + else: + generate_simulation_list = generate_simulation_list + # cf Methods.Simulation.VarLoadVoltage.get_elec_datakeeper + if isinstance(get_elec_datakeeper, ImportError): + get_elec_datakeeper = property( + fget=lambda x: raise_( + ImportError( + "Can't use VarLoadVoltage method get_elec_datakeeper: " + + str(get_elec_datakeeper) + ) + ) + ) + else: + get_elec_datakeeper = get_elec_datakeeper + # cf Methods.Simulation.VarLoadVoltage.get_input_list + if isinstance(get_input_list, ImportError): + get_input_list = property( + fget=lambda x: raise_( + ImportError( + "Can't use VarLoadVoltage method get_input_list: " + + str(get_input_list) + ) + ) + ) + else: + get_input_list = get_input_list + # 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, + OP_matrix=None, + type_OP_matrix=0, + is_torque=False, + is_power=False, + name="", + desc="", + datakeeper_list=-1, + is_keep_all_output=False, + stop_if_error=False, + var_simu=None, + nb_simu=0, + is_reuse_femm_file=True, + postproc_list=-1, + pre_keeper_postproc_list=None, + post_keeper_postproc_list=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 "OP_matrix" in list(init_dict.keys()): + OP_matrix = init_dict["OP_matrix"] + if "type_OP_matrix" in list(init_dict.keys()): + type_OP_matrix = init_dict["type_OP_matrix"] + if "is_torque" in list(init_dict.keys()): + is_torque = init_dict["is_torque"] + if "is_power" in list(init_dict.keys()): + is_power = init_dict["is_power"] + if "name" in list(init_dict.keys()): + name = init_dict["name"] + if "desc" in list(init_dict.keys()): + desc = init_dict["desc"] + if "datakeeper_list" in list(init_dict.keys()): + datakeeper_list = init_dict["datakeeper_list"] + if "is_keep_all_output" in list(init_dict.keys()): + is_keep_all_output = init_dict["is_keep_all_output"] + if "stop_if_error" in list(init_dict.keys()): + stop_if_error = init_dict["stop_if_error"] + if "var_simu" in list(init_dict.keys()): + var_simu = init_dict["var_simu"] + if "nb_simu" in list(init_dict.keys()): + nb_simu = init_dict["nb_simu"] + if "is_reuse_femm_file" in list(init_dict.keys()): + is_reuse_femm_file = init_dict["is_reuse_femm_file"] + if "postproc_list" in list(init_dict.keys()): + postproc_list = init_dict["postproc_list"] + if "pre_keeper_postproc_list" in list(init_dict.keys()): + pre_keeper_postproc_list = init_dict["pre_keeper_postproc_list"] + if "post_keeper_postproc_list" in list(init_dict.keys()): + post_keeper_postproc_list = init_dict["post_keeper_postproc_list"] + # Set the properties (value check and convertion are done in setter) + # Call VarLoad init + super(VarLoadVoltage, self).__init__( + OP_matrix=OP_matrix, + type_OP_matrix=type_OP_matrix, + is_torque=is_torque, + is_power=is_power, + name=name, + desc=desc, + datakeeper_list=datakeeper_list, + is_keep_all_output=is_keep_all_output, + stop_if_error=stop_if_error, + var_simu=var_simu, + nb_simu=nb_simu, + is_reuse_femm_file=is_reuse_femm_file, + postproc_list=postproc_list, + pre_keeper_postproc_list=pre_keeper_postproc_list, + post_keeper_postproc_list=post_keeper_postproc_list, + ) + # The class is frozen (in VarLoad init), for now it's impossible to + # add new properties + + def __str__(self): + """Convert this object in a readeable string (for print)""" + + VarLoadVoltage_str = "" + # Get the properties inherited from VarLoad + VarLoadVoltage_str += super(VarLoadVoltage, self).__str__() + return VarLoadVoltage_str + + def __eq__(self, other): + """Compare two objects (skip parent)""" + + if type(other) != type(self): + return False + + # Check the properties inherited from VarLoad + if not super(VarLoadVoltage, self).__eq__(other): + 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 VarLoad + diff_list.extend(super(VarLoadVoltage, self).compare(other, name=name)) + # 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 VarLoad + S += super(VarLoadVoltage, self).__sizeof__() + 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 VarLoad + VarLoadVoltage_dict = super(VarLoadVoltage, self).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 + VarLoadVoltage_dict["__class__"] = "VarLoadVoltage" + return VarLoadVoltage_dict + + def _set_None(self): + """Set all the properties to None (except pyleecan object)""" + + # Set to None the properties inherited from VarLoad + super(VarLoadVoltage, self)._set_None() diff --git a/pyleecan/Classes/import_all.py b/pyleecan/Classes/import_all.py index ec819f9ca..9f57a8c19 100644 --- a/pyleecan/Classes/import_all.py +++ b/pyleecan/Classes/import_all.py @@ -224,6 +224,7 @@ from ..Classes.Unit import Unit from ..Classes.VarLoad import VarLoad from ..Classes.VarLoadCurrent import VarLoadCurrent +from ..Classes.VarLoadVoltage import VarLoadVoltage from ..Classes.VarParam import VarParam from ..Classes.VarSimu import VarSimu from ..Classes.VentilationCirc import VentilationCirc diff --git a/pyleecan/Functions/Electrical/comp_PWM.py b/pyleecan/Functions/Electrical/comp_PWM.py deleted file mode 100644 index eb1985dc9..000000000 --- a/pyleecan/Functions/Electrical/comp_PWM.py +++ /dev/null @@ -1,414 +0,0 @@ -import numpy as np -from numpy.core.numeric import ones -from scipy import signal, integrate - - -def comp_volt_PWM_NUM( - Tpwmu, - freq0, - fmode, - fswi, - qs, - Vdc1, - U0, - rot_dir, - type_DPWM: int, - fswimode=0, - PF_angle=0, - is_sin=True, - fswi_max=0, - freq0_max=0, - type_carrier=0, - var_amp=0, - is_norm=True, -): - """ - Generalized DPWM using numerical method according to - 'Impact of Modulation Schemes on DC-Link Capacitor of VSI in HEV Applications' - - 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 [VDC] - U0: float - Phase Voltage [Vrms] - rot_dir: int - rotation direction - type_DPWM : int - type of modulation waveform - 0: GDPWM - 1: DPWMMIN - 2: DPWMMAX - 3: DPWM0 - 4: DPWM1 - 5: DPWM2 - 6: DPWM3 - 7: SVPWM - 8: SPWM - 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, 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) - carrier = np.ones(len(Tpwmu)) - if fmode == 0: # Fixed speed: - ws = 2 * np.pi * freq0 - elif fmode == 1: # Variable speed: - if type_DPWM == 8: - freq0_array = (freq0_max - freq0) / Tpwmu[-1] * Tpwmu + freq0 * np.ones( - Npsim - ) - ws = np.pi * freq0_array - else: - print("ERROR:only SPWM supports the variable fundamental frequency") - else: - pass - - if fswimode == 0: # Fixed fswi: - if type_DPWM == 8: - - carrier = Vdc1 / 2 * comp_carrier(Tpwmu, fswi, type_carrier) - else: - Th = 1 / fswi - elif fswimode == 1: # Variable fswi (ramp): - if type_DPWM == 8: - wswiT = ( - np.pi * (fswi_max - fswi) / Tpwmu[-1] * Tpwmu ** 2 - + 2 * np.pi * fswi * Tpwmu - ) - carrier = Vdc1 / 2 * signal.sawtooth(wswiT, 0.5) - 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) # Nombre de points - 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) < t1: - fswi = np.concatenate((fswi, fswi[-1] * np.ones(t1 - len(fswi)))) - else: - fswi = fswi[:t1] - - 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)) - carrier = signal.resample(carrier, len(Tpwmu)) - elif fswimode == 4: # Random amplitude carrier wave - if type_DPWM == 8: - 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 - ) / 100 + np.ones(int(num_slice)) - - amp_base = np.ones(len(Tpwmu)) - S_delta = 1 - - delta_t = S_delta / fswi * np.ones(int(num_slice)) - - delta_point = np.round(delta_t / Tpwmu[-1], 4) * len(Tpwmu) - delta_point = np.array(delta_point).astype(int) - amp = np.concatenate( - [ - amp_base[0 : delta_point[ii]] * delta_amp[ii] - for ii in range(len(delta_amp)) - ] - ) - if len(amp) < len(Tpwmu): - amp = np.concatenate((amp, amp[-1] * np.ones(len(Tpwmu) - len(amp)))) - else: - amp = amp[: len(Tpwmu)] - carrier = carrier * amp - else: - print("ERROR:only SPWM supports the variable switching frequency") - - else: - pass - - M_I = 2 * np.sqrt(2) * U0 / Vdc1 # [0,1] - - k = 1 # 2/sqrt(3)#2/sqrt(3) factor to have higher fundamental compared to SPWM - if rot_dir == -1: - - Phase = [0, -1, 1] - else: - - Phase = [0, 1, -1] - - 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) - - 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) - - V_min = np.amin( - np.concatenate((Vas[:, None], Vbs[:, None], Vcs[:, None]), axis=1), axis=1 - ) - V_max = np.amax( - np.concatenate((Vas[:, None], Vbs[:, None], Vcs[:, None]), axis=1), axis=1 - ) - - alpha_rad = 0 - - if type_DPWM == 0: # GDPWM - if PF_angle >= -np.pi / 6 and PF_angle <= np.pi / 6: - alpha_rad = PF_angle - elif PF_angle > np.pi / 6 and PF_angle <= 5 * np.pi / 12: - alpha_rad = np.pi / 6 - elif PF_angle >= -5 * np.pi / 12 and PF_angle < -np.pi / 6: - alpha_rad = -np.pi / 6 - elif PF_angle > 5 * np.pi / 12 and PF_angle <= np.pi / 2: - alpha_rad = np.pi / 3 - elif PF_angle >= -np.pi / 2 and PF_angle < -5 * np.pi / 12: - alpha_rad = -np.pi / 3 - - elif type_DPWM == 3: # elif type_waveform==63 #DPWM0 - alpha_rad = -30 * np.pi / 180 - elif type_DPWM == 4: # elif type_waveform==64 #DPWM1 - alpha_rad = 0 - elif type_DPWM == 5: # elif type_waveform==65 #DPWM2 - alpha_rad = 30 * np.pi / 180 - - if is_sin: - Vas_g = ( - k - * M_I - * (Vdc1 / 2) - * np.sin(ws * Tpwmu + Phase[0] * 2 * np.pi / 3 - alpha_rad) - ) - Vbs_g = ( - k - * M_I - * (Vdc1 / 2) - * np.sin(ws * Tpwmu + Phase[1] * 2 * np.pi / 3 - alpha_rad) - ) - Vcs_g = ( - k - * M_I - * (Vdc1 / 2) - * np.sin(ws * Tpwmu + Phase[2] * 2 * np.pi / 3 - alpha_rad) - ) - else: - Vas_g = ( - k - * M_I - * (Vdc1 / 2) - * np.cos(ws * Tpwmu + Phase[0] * 2 * np.pi / 3 - alpha_rad) - ) - Vbs_g = ( - k - * M_I - * (Vdc1 / 2) - * np.cos(ws * Tpwmu + Phase[1] * 2 * np.pi / 3 - alpha_rad) - ) - Vcs_g = ( - k - * M_I - * (Vdc1 / 2) - * np.cos(ws * Tpwmu + Phase[2] * 2 * np.pi / 3 - alpha_rad) - ) - - V_offset = np.zeros(Npsim) - - min_abc = np.squeeze( - np.amin( - np.concatenate((Vas_g[:, None], Vbs_g[:, None], Vcs_g[:, None]), axis=1), - axis=1, - ) - ) - max_abc = np.squeeze( - np.amax( - np.concatenate((Vas_g[:, None], Vbs_g[:, None], Vcs_g[:, None]), axis=1), - axis=1, - ) - ) - i1 = min_abc + max_abc > 0 - i2 = min_abc + max_abc < 0 - V_offset[i1] = Vdc1 / 2 - V_max[i1] - V_offset[i2] = -Vdc1 / 2 - V_min[i2] - - # type_DPWM {0, 1, 2, 3, 4, 5, 6, 7} # {GDPWM, DPWMMIN, DPWMMAX, DPWM0, DPWM1, DPWM2, DPWM3, SVPWM) - if type_DPWM == 1: # type_waveform==61 #DPWMMIN - V_offset = -V_min - Vdc1 / 2 - elif type_DPWM == 2: # elif type_waveform==62 #DPWMMAX - V_offset = -V_max + Vdc1 / 2 - elif type_DPWM == 6: # elif type_waveform==66 #DPWM3 - min_abc = np.amin( - np.concatenate((Vas[:, None], Vbs[:, None], Vcs[:, None]), axis=1), axis=1 - ) - max_abc = np.amax( - np.concatenate((Vas[:, None], Vbs[:, None], Vcs[:, None]), axis=1), axis=1 - ) - i1 = min_abc + max_abc < 0 - i2 = min_abc + max_abc > 0 - V_offset[i1] = Vdc1 / 2 - V_max[i1] - V_offset[i2] = -Vdc1 / 2 - V_min[i2] - elif type_DPWM == 7: # elif type_waveform==67 #SVPWM - V_offset = -1 / 2 * (V_max + V_min) - elif type_DPWM == 8: - V_offset = 0 * (V_max + V_min) - - Van = Vas + V_offset - Vbn = Vbs + V_offset - Vcn = Vcs + V_offset - - if type_DPWM == 8: - v_pwm = np.ones((qs, Npsim)) - 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: - - T1 = Th / 4 - Th / (2 * Vdc1) * Van - T2 = Th / 4 - Th / (2 * Vdc1) * Vbn - T3 = Th / 4 - Th / (2 * Vdc1) * Vcn - n = np.floor(Tpwmu / Th).astype(int) - v_pwm = Vdc1 / 2 * np.ones((qs, Npsim)) - v_pwm[0, Tpwmu < (T1 + n * Th)] = -Vdc1 / 2 - v_pwm[0, Tpwmu > ((n + 1) * Th - T1)] = -Vdc1 / 2 - v_pwm[1, Tpwmu < (T2 + n * Th)] = -Vdc1 / 2 - v_pwm[1, Tpwmu > ((n + 1) * Th - T2)] = -Vdc1 / 2 - 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, carrier - - -def comp_carrier(time, fswi, type_carrier): - """Function to compute the carrier - - Parameters - ---------- - time : array - Time vector - fswi : array - Switching frequency - type_carrier : int - 1: forward toothsaw carrier - 2: backwards toothsaw carrier - 3: toothsaw carrier - else: Symmetrical toothsaw carrier - - Returns - ------- - Y: ndarray - carrier - - """ - T = 1 / fswi - time = time % T - - if type_carrier == 1: # forward toothsaw carrier - Y = ( - 20 - * ( - np.where(time <= 0.5 * T, time, 0) * time - + np.where(time > 0.5 * T, time, 0) * (time - T) - ) - / (0.5 * T) - ) - elif type_carrier == 2: # backwards toothsaw carrier - Y = ( - 20 - * -( - np.where(time <= 0.5 * T, time, 0) * time - + np.where(time > 0.5 * T, time, 0) * (time - T) - ) - / (0.5 * T) - ) - elif type_carrier == 3: # toothsaw carrier - t1 = (1 + type_carrier) * T / 4 - t2 = T - t1 - Y = ( - np.where(time <= t1, 1, 0) * time / t1 - + np.where(time > t1, 1, 0) - * np.where(time < t2, 1, 0) - * (-time + 0.5 * T) - / (-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) - - return Y diff --git a/pyleecan/Functions/load_switch.py b/pyleecan/Functions/load_switch.py index 4bdfe569f..24e077638 100644 --- a/pyleecan/Functions/load_switch.py +++ b/pyleecan/Functions/load_switch.py @@ -226,6 +226,7 @@ "Unit": Unit, "VarLoad": VarLoad, "VarLoadCurrent": VarLoadCurrent, + "VarLoadVoltage": VarLoadVoltage, "VarParam": VarParam, "VarSimu": VarSimu, "VentilationCirc": VentilationCirc, diff --git a/pyleecan/Generator/ClassesRef/Simulation/Input.csv b/pyleecan/Generator/ClassesRef/Simulation/Input.csv index d8de92d21..2c686385d 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/Input.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/Input.csv @@ -5,3 +5,4 @@ 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,,,,,,,, OP,-,Operating Point,,OP,None,,,,,,,,, +t_final,,To enforce final time,,float,None,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv b/pyleecan/Generator/ClassesRef/Simulation/InputVoltage.csv index 07f8c71ac..eee1e1484 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 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,,,,,,,,, +PWM,,Object to generate PWM signal,,ImportGenPWM,None,,,,,,set_Ud_Uq,,, 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/Generator/ClassesRef/Simulation/OPdq.csv b/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv index 716c1c8d4..24844d8cf 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/OPdq.csv @@ -5,5 +5,5 @@ 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_slip,,, ,,,,,,,,,,,get_Ud_Uq,,, ,,,,,,,,,,,set_Id_Iq,,, -,,,,,,,,,,,set_I0_Phi0,,, ,,,,,,,,,,,get_I0_Phi0,,, +,,,,,,,,,,,set_Ud_Uq,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv b/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv index a1f1e7898..341aaad79 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/OPslip.csv @@ -7,3 +7,4 @@ UPhi0_ref,rad,Voltage phase,,float,None,,,,,,set_Id_Iq,,, ,,,,,,,,,,,get_I0_Phi0,,, ,,,,,,,,,,,get_slip,,, ,,,,,,,,,,,set_I0_Phi0,,, +,,,,,,,,,,,set_Ud_Uq,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/VarLoad.csv b/pyleecan/Generator/ClassesRef/Simulation/VarLoad.csv index d78a77ae6..8c2b1bac6 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/VarLoad.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/VarLoad.csv @@ -1,3 +1,5 @@ Variable name,Unit,Description (EN),Size,Type,Default value,Minimum value,Maximum value,,Package,Inherit,Methods,Constant Name,Constant Value,Class description -,,,,,,,,,Simulation,VarSimu,get_ref_simu_index,VERSION,1,Abstract class to generate multi-simulation by changing the operating point -,,,,,,,,,,,,NAME,"""Variable Load""", +OP_matrix,-,"Operating point matrix (N0,I0,Phi0,T,P) or (N0,Id,Iq,T,P) ","(Nsimu, 5)",ndarray,None,,,,Simulation,VarSimu,get_ref_simu_index,VERSION,1,Abstract class to generate multi-simulation by changing the operating point +type_OP_matrix,-,"Select which kind of OP_matrix is used 0: (N0,I0,Phi0,T,P), 1:(N0,Id,Iq,T,P) ",,int,0,0,1,,,,,NAME,"""Variable Load""", +is_torque,-,True if the Torque is defined in OP_matrix,,bool,False,,,,,,,,, +is_power,,True if the Power is defined in OP_matrix,,bool,False,,,,,,,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/VarLoadCurrent.csv b/pyleecan/Generator/ClassesRef/Simulation/VarLoadCurrent.csv index f8e4db93c..56a01d7d3 100644 --- a/pyleecan/Generator/ClassesRef/Simulation/VarLoadCurrent.csv +++ b/pyleecan/Generator/ClassesRef/Simulation/VarLoadCurrent.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 -OP_matrix,-,"Operating point matrix (N0,I0,Phi0,T,P) or (N0,Id,Iq,T,P) ","(Nsimu, 5)",ndarray,None,,,,Simulation,VarLoad,get_input_list,VERSION,1,Generate a multisimulation with InputCurrent at variable operating point -type_OP_matrix,-,"Select which kind of OP_matrix is used 0: (N0,I0,Phi0,T,P), 1:(N0,Id,Iq,T,P) ",,int,0,0,1,,,,generate_simulation_list,,, -is_torque,-,True if the Torque is defined in OP_matrix,,bool,False,,,,,,check_param,,, -is_power,,True if the Power is defined in OP_matrix,,bool,False,,,,,,get_elec_datakeeper,,, +,,,,,,,,,Simulation,VarLoad,get_input_list,VERSION,1,Generate a multisimulation with InputCurrent at variable operating point +,,,,,,,,,,,generate_simulation_list,,, +,,,,,,,,,,,get_elec_datakeeper,,, diff --git a/pyleecan/Generator/ClassesRef/Simulation/VarLoadVoltage.csv b/pyleecan/Generator/ClassesRef/Simulation/VarLoadVoltage.csv new file mode 100644 index 000000000..3971c6cc6 --- /dev/null +++ b/pyleecan/Generator/ClassesRef/Simulation/VarLoadVoltage.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 +,,,,,,,,,Simulation,VarLoad,generate_simulation_list,VERSION,1,Generate a multisimulation with InputVoltage at variable operating point +,,,,,,,,,,,get_elec_datakeeper,,, +,,,,,,,,,,,get_input_list,,, diff --git a/pyleecan/Methods/Simulation/Input/comp_axes.py b/pyleecan/Methods/Simulation/Input/comp_axes.py index fe2d00555..04c7e7984 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axes.py +++ b/pyleecan/Methods/Simulation/Input/comp_axes.py @@ -173,8 +173,7 @@ def comp_axes( 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) + Phase = self.comp_axis_phase(machine.rotor, per_a, is_antiper_a, Phase_in) if Phase is not None: # Store phase axis in dict diff --git a/pyleecan/Methods/Simulation/Input/comp_axis_angle.py b/pyleecan/Methods/Simulation/Input/comp_axis_angle.py index 8d527d7d8..80e330cf1 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axis_angle.py +++ b/pyleecan/Methods/Simulation/Input/comp_axis_angle.py @@ -3,7 +3,7 @@ from SciDataTool import Data1D, DataLinspace, Norm_ref -def comp_axis_angle(self, p, Rag, per_a, is_antiper_a, Angle_in=None): +def comp_axis_angle(self, p, Rag, per_a=None, is_antiper_a=None, Angle_in=None): """Compute angle axis with or without periodicities and including normalizations Parameters @@ -30,9 +30,15 @@ def comp_axis_angle(self, p, Rag, per_a, is_antiper_a, Angle_in=None): norm_angle = {"space_order": Norm_ref(ref=p), "distance": Norm_ref(ref=1 / Rag)} + # Compute angle axis based on input one if Angle_in is not None: - # Compute Angle axis based on the one stored in OutElec + if per_a is None or is_antiper_a is None: + # Get periodicity from input Angle axis + per_a, is_antiper_a = Angle_in.get_periodicity() + per_a = int(per_a / 2) if is_antiper_a else per_a + # Get Angle axis on requested periodicities Angle = Angle_in.get_axis_periodic(Nper=per_a, is_aper=is_antiper_a) + Angle.normalizations = norm_angle # Create angle axis elif self.angle is None: diff --git a/pyleecan/Methods/Simulation/Input/comp_axis_phase.py b/pyleecan/Methods/Simulation/Input/comp_axis_phase.py index 194ee756f..57f20d7ba 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axis_phase.py +++ b/pyleecan/Methods/Simulation/Input/comp_axis_phase.py @@ -1,7 +1,9 @@ -from SciDataTool import Data1D +from numpy import pi +from SciDataTool import Data1D, DataLinspace, Norm_indices -def comp_axis_phase(self, lamination, per_a=None, Phase_in=None): + +def comp_axis_phase(self, lamination, per_a=None, is_apera=None, Phase_in=None): """Compute phase axes for given lamination Parameters @@ -31,12 +33,32 @@ def comp_axis_phase(self, lamination, per_a=None, Phase_in=None): if len(name_phase) > 0: - # Creating the data object - Phase = Data1D( - name="phase", - unit="", - values=name_phase, - is_components=True, - ) + if per_a is not None and is_apera is not None: + sym_dict = dict() + if is_apera: + per_a *= 2 + sym_dict["antiperiod"] = per_a + elif per_a > 1: + sym_dict["period"] = per_a + + # Creating the data object + Phase = DataLinspace( + name="phase", + unit="rad", + initial=0, + final=2 * pi / per_a, + number=int(len(name_phase) / per_a), + include_endpoint=False, + symmetries=sym_dict, + normalizations={"bar_id": Norm_indices()}, + ) + else: + # Creating the data object + Phase = Data1D( + name="phase", + unit="rad", + 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 index 448d77d35..d5cd45994 100644 --- a/pyleecan/Methods/Simulation/Input/comp_axis_time.py +++ b/pyleecan/Methods/Simulation/Input/comp_axis_time.py @@ -3,7 +3,7 @@ from SciDataTool import Data1D, DataLinspace, Norm_ref, Norm_affine -def comp_axis_time(self, p, per_t, is_antiper_t, Time_in=None): +def comp_axis_time(self, p, per_t=None, is_antiper_t=None, Time_in=None): """Compute time axis, with or without periodicities and including normalizations Parameters @@ -39,15 +39,23 @@ def comp_axis_time(self, p, per_t, is_antiper_t, Time_in=None): ), } + # Compute Time axis based on input one if Time_in is not None: - # Compute Time axis based on the one stored in OutElec + if per_t is None or is_antiper_t is None: + # Get periodicity from input Time axis + per_t, is_antiper_t = Time_in.get_periodicity() + per_t = int(per_t / 2) if is_antiper_t else per_t + # Get axis on given periodicities 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.t_final is not None: + # Enforce final time + t_final = self.t_final + elif self.Nrev is not None: # Set final time depending on rotor speed and number of revolutions t_final = 60 / self.OP.N0 * self.Nrev else: diff --git a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py index 06205584e..c99ba9f31 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/gen_input.py +++ b/pyleecan/Methods/Simulation/InputCurrent/gen_input.py @@ -1,10 +1,10 @@ -from numpy import mean as np_mean, zeros +from numpy import zeros from SciDataTool import DataTime from ....Classes.InputVoltage import InputVoltage -from ....Functions.Electrical.dqh_transformation import n2dqh, get_phase_dir +from ....Functions.Electrical.dqh_transformation import get_phase_dir, n2dqh_DataTime from ....Methods.Simulation.Input import InputError @@ -40,7 +40,7 @@ def gen_input(self): # Load and check Is if qs > 0: if self.Is is None: - if self.OP.Id_ref is None and self.OP.Iq_ref is None: + if self.OP.get_Id_Iq()["Id"] is None and self.OP.get_Id_Iq()["Iq"] is None: raise InputError( "InputCurrent.Is, InputCurrent.OP.Id_ref, and InputCurrent.OP.Iq_ref missing" ) @@ -48,9 +48,9 @@ def gen_input(self): outelec.OP = self.OP outelec.Is = None else: - Is = self.Is.get_data() + Is_val = self.Is.get_data() # Get phase_dir from Is - phase_dir = get_phase_dir(Is) + phase_dir = get_phase_dir(Is_val) if phase_dir != outelec.phase_dir: self.get_logger().warning( "Enforcing outelec.phase_dir=" @@ -58,51 +58,79 @@ def gen_input(self): + " to comply with input current" ) outelec.phase_dir = phase_dir - if Is.shape != (self.Nt_tot, qs): + if Is_val.shape != (self.Nt_tot, qs): raise InputError( "InputCurrent.Is must be a matrix with the shape " + str((self.Nt_tot, qs)) + " (len(time), stator phase number), " - + str(Is.shape) + + str(Is_val.shape) + " returned" ) # Creating the data object - stator_label = "phase_" + simu.machine.stator.get_label() - outelec.Is = DataTime( + Phase_S = outelec.axes_dict["phase_" + simu.machine.stator.get_label()] + Is = DataTime( name="Stator current", unit="A", symbol="I_s", - axes=[Time, outelec.axes_dict[stator_label]], - values=Is, + axes=[Time, Phase_S], + values=Is_val, ) # Compute corresponding Id/Iq reference - Idq = n2dqh( - outelec.Is.values, - Time.get_values(is_oneperiod=False, normalization="angle_elec"), + Idq = n2dqh_DataTime( + Is, is_dqh_rms=True, phase_dir=outelec.phase_dir, ) - outelec.OP.set_Id_Iq(np_mean(Idq[:, 0]), np_mean(Idq[:, 1])) + Idq_mean = Idq.get_along("time=mean", "phase")[Is.symbol] + # Store currents in OutElec + outelec.OP.set_Id_Iq(Idq_mean[0], Idq_mean[1]) + outelec.Is = Is + + if self.Is_harm is not None: + # Enforce current harmonics + # TODO: merge Is_harm and Is_fund + outelec.Is_harm = self.Is_harm.get_data() # Load and check Ir is needed if qr > 0: + Nt_tot = Time.get_length(is_smallestperiod=True) if self.Ir is None: - Ir = zeros((self.Nt_tot, qr)) + Ir_val = zeros((Nt_tot, qr)) else: - Ir = self.Ir.get_data() - if Ir.shape != (self.Nt_tot, qr): + Ir_val = self.Ir.get_data() + if Ir_val.shape != (Nt_tot, qr): raise InputError( "InputCurrent.Ir must be a matrix with the shape " - + str((self.Nt_tot, qr)) + + str((Nt_tot, qr)) + " (len(time), rotor phase number), " - + str(Ir.shape) + + str(Ir_val.shape) + " returned" ) - - outelec.Ir = DataTime( + Phase_R = outelec.axes_dict["phase_" + simu.machine.rotor.get_label()] + Ir = DataTime( name="Rotor current", unit="A", symbol="Ir", - axes=[Time, outelec.axes_dict["phase_" + simu.machine.rotor.get_label()]], - values=Ir, + axes=[Time, Phase_R], + values=Ir_val, ) + outelec.Ir = Ir + + if outelec.PWM is not None: + Udq_dict = outelec.OP.get_Ud_Uq() + Ud_ref, Uq_ref = Udq_dict["Ud"], Udq_dict["Uq"] + # Check PWM phase voltage consistency in current driven mode + if outelec.PWM.U0 is not None or Ud_ref is not None or Uq_ref is not None: + if outelec.PWM.U0 is not None: + self.get_logger().warning( + "Neglecting U0 given as input of PWM object since voltage will be calculated" + ) + if Ud_ref is not None or Uq_ref is not None: + self.get_logger().warning( + "Neglecting Ud_ref/Uq_ref given as input since voltage will be calculated" + ) + # Set all voltages to None since they will be calculated by Electrical model + outelec.PWM.U0 = None + outelec.PWM.Phi0 = None + outelec.OP.Ud_ref = None + outelec.OP.Uq_ref = None diff --git a/pyleecan/Methods/Simulation/InputCurrent/set_OP_from_array.py b/pyleecan/Methods/Simulation/InputCurrent/set_OP_from_array.py index 1728e1e98..0c9ac12ba 100644 --- a/pyleecan/Methods/Simulation/InputCurrent/set_OP_from_array.py +++ b/pyleecan/Methods/Simulation/InputCurrent/set_OP_from_array.py @@ -1,3 +1,8 @@ +from numpy import isnan + +from ....Classes.OPdq import OPdq + + def set_OP_from_array(self, OP_matrix, type_OP_matrix=1, index=0): """Extract the Operating Point from an OP_matrix @@ -19,11 +24,23 @@ 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.OP.N0 = OP_matrix[index, 0] if type_OP_matrix == 1: + if self.OP is None: + self.OP = OPdq() self.OP.Id_ref = OP_matrix[index, 1] self.OP.Iq_ref = OP_matrix[index, 2] else: + if self.OP is None: + self.OP = OPdq() self.set_Id_Iq(I0=OP_matrix[index, 1], Phi0=OP_matrix[index, 2]) + self.OP.N0 = OP_matrix[index, 0] if OP_matrix.shape[1] > 3: - self.OP.Tem_av_ref = OP_matrix[index, 3] + if isnan(OP_matrix[index, 3]): + self.OP.Tem_av_ref = None + else: + self.OP.Tem_av_ref = OP_matrix[index, 3] + if OP_matrix.shape[1] > 4: + if isnan(OP_matrix[index, 4]): + self.OP.Pem_av_ref = None + else: + self.OP.Pem_av_ref = OP_matrix[index, 4] diff --git a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py index 980738b25..1f9b826aa 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/gen_input.py +++ b/pyleecan/Methods/Simulation/InputVoltage/gen_input.py @@ -1,6 +1,4 @@ -from numpy import arange, searchsorted - -from SciDataTool import DataTime +from numpy import arange, searchsorted, angle from ....Classes.OutElec import OutElec from ....Classes.Simulation import Simulation @@ -42,6 +40,7 @@ def gen_input(self): outelec = OutElec() output.elec = outelec outgeo = output.geo + # Replace N0=0 by 0.1 rpm if self.OP.N0 == 0: self.OP.N0 = 0.1 @@ -100,36 +99,35 @@ def gen_input(self): outelec.axes_dict = self.comp_axes( axes_list=["time", "phase_S", "phase_R"], axes_dict_in=outgeo.axes_dict, - is_periodicity_t=False, + is_periodicity_t=True, ) # Generate PWM signal if self.PWM is not None: + outelec.PWM = self.PWM.copy() + Udq_dict = outelec.OP.get_Ud_Uq() + Ud_ref, Uq_ref = Udq_dict["Ud"], Udq_dict["Uq"] + # Check PWM phase voltage consistency in voltage driven mode + if Ud_ref is not None or Uq_ref is not None: + U0c = Ud_ref + 1j * Uq_ref + if ( + self.PWM.U0 is not None + and outelec.PWM.Phi0 is not None + and (outelec.PWM.U0 != abs(U0c) or outelec.PWM.Phi0 != angle(U0c)) + ): + logger.warning("Enforcing PWM.U0 = Ud_ref + jUq_ref") + outelec.PWM.U0 = abs(U0c) + outelec.PWM.Phi0 = angle(U0c) # Fill generator with simu data 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 - self.PWM.duration = 1 / felec - 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(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) - # Create DataTime object - self.time = time - 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", - symbol="U_s", - unit="V", - axes=[Time, Phase], - values=Uabc, - ) + outelec.PWM.f = felec + outelec.PWM.qs = qs + outelec.PWM.phase_dir = outelec.phase_dir + outelec.PWM.current_dir = outelec.current_dir + # Enforce Sine PWM + outelec.PWM.typePWM = 8 + # Take sampling frequency as 2*fmax to catch fmax components + outelec.PWM.fs = 2 * outelec.PWM.fmax + # Set PWM duration as 2*p times the electrical period (to increase frequency resolution) + outelec.PWM.duration = 2 * simu.machine.get_pole_pair_number() / felec diff --git a/pyleecan/Methods/Simulation/InputVoltage/set_OP_from_array.py b/pyleecan/Methods/Simulation/InputVoltage/set_OP_from_array.py index ab198efdf..cd4d2b0d1 100644 --- a/pyleecan/Methods/Simulation/InputVoltage/set_OP_from_array.py +++ b/pyleecan/Methods/Simulation/InputVoltage/set_OP_from_array.py @@ -1,14 +1,17 @@ +from ....Classes.OPdq import OPdq + + 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 + self : InputVoltage + An InputVoltage 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) + Select which kind of OP_matrix is used 0: (N0,U0,Phi0,T,P), 1:(N0,Ud,Uq,T,P) index : int To select the line of the OP_matrix to use (default=0) """ @@ -19,11 +22,17 @@ 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] if type_OP_matrix == 1: - self.Id_ref = OP_matrix[index, 1] - self.Iq_ref = OP_matrix[index, 2] + if self.OP is None: + self.OP = OPdq() + self.OP.Ud_ref = OP_matrix[index, 1] + self.OP.Uq_ref = OP_matrix[index, 2] else: - self.set_Id_Iq(I0=OP_matrix[index, 1], Phi0=OP_matrix[index, 2]) + if self.OP is None: + self.OP = OPdq() + self.set_Ud_Uq(U0=OP_matrix[index, 1], Phi0=OP_matrix[index, 2]) + self.OP.N0 = OP_matrix[index, 0] if OP_matrix.shape[1] > 3: - self.Tem_av_ref = OP_matrix[index, 3] + self.OP.Tem_av_ref = OP_matrix[index, 3] + if OP_matrix.shape[1] > 4: + self.OP.Pem_av_ref = OP_matrix[index, 4] diff --git a/pyleecan/Methods/Simulation/InputVoltage/set_Ud_Uq.py b/pyleecan/Methods/Simulation/InputVoltage/set_Ud_Uq.py new file mode 100644 index 000000000..038bf9ef1 --- /dev/null +++ b/pyleecan/Methods/Simulation/InputVoltage/set_Ud_Uq.py @@ -0,0 +1,22 @@ +from numpy import cos, sin + + +def set_Ud_Uq(self, U0, Phi0): + """Set Ud_ref and Uq_ref according to U0, Phi0 + + Parameters + ---------- + self : InputVoltage + An InputVoltage object + U0 : float + Voltage amplitude [Arms] + Phi0 : float + Voltage phase [rad] + """ + + self.OP.Ud_ref = U0 * cos(Phi0) + self.OP.Uq_ref = U0 * sin(Phi0) + if abs(self.OP.Ud_ref) < 1e-10: + self.OP.Ud_ref = 0 + if abs(self.OP.Uq_ref) < 1e-10: + self.OP.Uq_ref = 0 diff --git a/pyleecan/Methods/Simulation/OPdq/set_Ud_Uq.py b/pyleecan/Methods/Simulation/OPdq/set_Ud_Uq.py new file mode 100644 index 000000000..ddd241f50 --- /dev/null +++ b/pyleecan/Methods/Simulation/OPdq/set_Ud_Uq.py @@ -0,0 +1,15 @@ +def set_Ud_Uq(self, Ud, Uq): + """Set the value for Ud and Uq + + Parameters + ---------- + self : OPdq + An OPdq object + Ud : float + Ud value to set [Vrms] + Uq : float + Uq value to set [Vrms] + """ + + self.Ud_ref = Ud + self.Uq_ref = Uq diff --git a/pyleecan/Methods/Simulation/OPslip/set_Ud_Uq.py b/pyleecan/Methods/Simulation/OPslip/set_Ud_Uq.py new file mode 100644 index 000000000..213cba2a1 --- /dev/null +++ b/pyleecan/Methods/Simulation/OPslip/set_Ud_Uq.py @@ -0,0 +1,23 @@ +from numpy import angle + + +def set_Ud_Uq(self, Ud, Uq): + """Set the value for Ud and Uq + + Parameters + ---------- + self : OPdq + An OPdq object + Ud : float + Ud value to set [Arms] + Uq : float + Uq value to set [Arms] + """ + + if Ud == 0 and Uq == 0: + self.I0_ref = 0 + self.IPhi0_ref = 0 + else: + Z = Ud + 1j * Uq + self.I0_ref = abs(Z) + self.IPhi0_ref = angle(Z) diff --git a/pyleecan/Methods/Simulation/VarLoadCurrent/check_param.py b/pyleecan/Methods/Simulation/VarLoadCurrent/check_param.py deleted file mode 100644 index c03317540..000000000 --- a/pyleecan/Methods/Simulation/VarLoadCurrent/check_param.py +++ /dev/null @@ -1,2 +0,0 @@ -def check_param(self): - super(type(self), self).check_param() diff --git a/pyleecan/Methods/Simulation/VarLoadVoltage/__init__.py b/pyleecan/Methods/Simulation/VarLoadVoltage/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pyleecan/Methods/Simulation/VarLoadVoltage/generate_simulation_list.py b/pyleecan/Methods/Simulation/VarLoadVoltage/generate_simulation_list.py new file mode 100644 index 000000000..22cc8ebb2 --- /dev/null +++ b/pyleecan/Methods/Simulation/VarLoadVoltage/generate_simulation_list.py @@ -0,0 +1,74 @@ +from ....Classes.ParamExplorerSet import ParamExplorerSet +from ....Classes.PostCleanVS import PostCleanVS + +from ....Methods.Simulation.Simulation import reuse_list + + +def generate_simulation_list(self, ref_simu=None): + """Generate all the simulation for the multi-simulation + + Parameters + ---------- + self : VarLoadCurrent + A VarLoadCurrent object + ref_simu : Simulation + Reference simulation to copy / update + + Returns + ------- + multisim_dict : dict + dictionary containing the simulation and paramexplorer list + """ + + # Update is_reuse for all parameters in REUSE_LIST if needed + for attr in reuse_list: + if getattr(self, attr) is None: + # If is_reuse is not forced by user, set it to True + setattr(self, attr, True) + + # Don't reuse eccentricity model if slice model is not reused + if self.is_reuse_eccentricity is True and self.is_reuse_slice is False: + self.get_logger().info( + "Reset self.is_reuse_eccentricity to False if self.is_reuse_slice is False" + ) + self.is_reuse_eccentricity = self.is_reuse_slice + + # Get InputCurrent list + list_input = self.get_input_list() + + multisim_dict = { + "paramexplorer_list": [], # Setter's values + "simulation_list": [], + } + + # Create Simulations 1 per load + for input_obj in list_input: + # Generate the simulation + new_simu = ref_simu.copy(keep_function=True) + + # Edit simulation + new_simu.input = input_obj + # Add simulation to the list + multisim_dict["simulation_list"].append(new_simu) + + # Automatically clean what is not releavant for VS post + if self.is_clean: + if self.post_keeper_postproc_list is None: + self.post_keeper_postproc_list = list() + self.post_keeper_postproc_list.append(PostCleanVS()) + + # Create ParamExplorerSet + # This version uses a single ParamExplorerSet to define the simulation + # Other parameters can be stored in a dedicated ParamExplorerSet if needed + multisim_dict["paramexplorer_list"].append( + ParamExplorerSet( + name="InputCurrent", + symbol="In", + unit="-", + setter="simu.input", + getter="simu.input", + value=list_input, + ) + ) + + return multisim_dict diff --git a/pyleecan/Methods/Simulation/VarLoadVoltage/get_elec_datakeeper.py b/pyleecan/Methods/Simulation/VarLoadVoltage/get_elec_datakeeper.py new file mode 100644 index 000000000..e844565b0 --- /dev/null +++ b/pyleecan/Methods/Simulation/VarLoadVoltage/get_elec_datakeeper.py @@ -0,0 +1,69 @@ +from numpy import abs as np_abs, angle, pi + +from ....Classes.DataKeeper import DataKeeper +from ....Classes.VarLoad import VarLoad + + +def get_elec_datakeeper(self, symbol_list, is_multi=False): + """ + Generate DataKeepers to store by default results from electric module + + Parameters + ---------- + self: VarLoadCurrent + A VarLoadCurrent object + symbol_list : list + List of the existing datakeeper (to avoid duplicate) + is_multi : bool + True for multi-simulation of multi-simulation + + Returns + ------- + dk_list: list + list of DataKeeper + """ + dk_list = VarLoad.get_elec_datakeeper(self, symbol_list) + if self.type_OP_matrix == 0: # I0 and Phi0 + if not is_multi and "I0" not in symbol_list: + # Save I0 + dk_list.append( + DataKeeper( + name="I0", + symbol="I0", + unit="A", + keeper="lambda output: np.abs(output.elec.Id_ref + 1j * output.elec.Iq_ref)", + ) + ) + # Save Phi0 + if not is_multi and "Phi0" not in symbol_list: + dk_list.append( + DataKeeper( + name="Phi0", + symbol="Phi0", + unit="", + keeper="lambda output: np.angle(output.elec.Id_ref + 1j * output.elec.Iq_ref) % (2 * np.pi)", + ) + ) + # Keep torque + if not is_multi and self.OP_matrix.shape[1] > 3 and "Tem_av_ref" not in symbol_list: + dk_list.append( + DataKeeper( + name="Reference Average Torque", + symbol="Tem_av_ref", + unit="N.m", + keeper="lambda output: output.elec.Tem_av_ref", + ) + ) + + # TODO Save power + # if self.is_power: + # datakeeper_list.append( + # DataKeeper( + # name="Power", + # symbol="Tem", + # unit="N.m", + # keeper="lambda output: output.simu.mag.power", + # ), + # ) + + return dk_list diff --git a/pyleecan/Methods/Simulation/VarLoadVoltage/get_input_list.py b/pyleecan/Methods/Simulation/VarLoadVoltage/get_input_list.py new file mode 100644 index 000000000..d831fe706 --- /dev/null +++ b/pyleecan/Methods/Simulation/VarLoadVoltage/get_input_list.py @@ -0,0 +1,44 @@ +from ....Classes.InputVoltage import InputVoltage +from ....Classes.Simulation import Simulation + + +def get_input_list(self): + """Return a list of InputVoltage to set the Operating point""" + + # Check that the object has the correct type + assert isinstance(self.parent, Simulation) + ref_simu = self.parent + assert isinstance(ref_simu.input, InputVoltage) + + OP_matrix = self.get_OP_matrix() + N_simu = OP_matrix.shape[0] + + # Generate initial input_list + ref_input = ref_simu.input.copy() + input_list = [ref_input.copy() for ii in range(N_simu)] + + # Set default time vector (enforce definition Nt_tot, Nrev) + if ref_input.Nt_tot is None: + Nt_tot = len(ref_input.time.get_data()) + else: + Nt_tot = ref_input.Nt_tot + if ref_input.Nrev is None: + Nrev = 1 + else: + Nrev = ref_input.Nrev + # Update OP according to OP_matrix + for ii in range(N_simu): + input_list[ii].OP.N0 = OP_matrix[ii, 0] + # Edit time vector + input_list[ii].time = None + input_list[ii].Nt_tot = Nt_tot + input_list[ii].Nrev = Nrev + if self.type_OP_matrix == 0: # U0, Phi0 + input_list[ii].set_Ud_Uq(U0=OP_matrix[ii, 1], Phi0=OP_matrix[ii, 2]) + else: # Ud/Uq + input_list[ii].OP.Ud_ref = OP_matrix[ii, 1] + input_list[ii].OP.Uq_ref = OP_matrix[ii, 2] + if self.OP_matrix.shape[1] > 3: + input_list[ii].OP.Tem_av_ref = OP_matrix[ii, 3] + + return input_list