def comp_force(self, output): """Compute the air-gap surface force based on Maxwell Tensor (MT). Parameters ---------- self : ForceMT A ForceMT object output : Output an Output object (to update) """ mu_0 = 4 * np.pi * 1e-7 angle = output.force.angle Na_tot = output.force.Na_tot time = output.force.time Nt_tot = output.force.Nt_tot # Load magnetic flux Br = output.mag.Br.values Bt = output.mag.Bt.values # Compute AGSF with MT formula Prad = (Br * Br - Bt * Bt) / (2 * mu_0) Ptan = Br * Bt / mu_0 # Store the results Time = DataLinspace( name="time", unit="s", symmetries={}, initial=time[0], final=time[-1], number=Nt_tot, ) Angle = DataLinspace( name="angle", unit="rad", symmetries={}, initial=angle[0], final=angle[-1], number=Na_tot, ) output.force.Prad = DataTime( name="Airgap radial surface force", unit="T", symbol="P_r", axes=[Time, Angle], values=Prad, ) output.force.Ptan = DataTime( name="Airgap radial surface force", unit="T", symbol="P_t", axes=[Time, Angle], values=Ptan, )
def plot_mmf_unit(self, Na=2048, fig=None): """Plot the winding unit mmf as a function of space Parameters ---------- self : LamSlotWind an LamSlotWind object Na : int Space discretization fig : Matplotlib.figure.Figure existing figure to use if None create a new one """ # Create an empty Output object to use the generic plot methods module = __import__("pyleecan.Classes.Output", fromlist=["Output"]) Output = getattr(module, "Output") out = Output() # Compute the winding function and mmf wf = self.comp_wind_function(Na=Na) qs = self.winding.qs mmf_u = self.comp_mmf_unit(Na=Na) # Create a Data object Phase = Data1D( name="phase", unit="", values=gen_name(qs, is_add_phase=True), is_components=True, ) Angle = DataLinspace( name="angle", unit="rad", symmetries={}, initial=0, final=2 * pi, number=Na, include_endpoint=False, ) out.mag.Br = DataTime(name="WF", unit="p.u.", symbol="Magnitude", axes=[Phase, Angle], values=wf) color_list = config_dict["PLOT"]["COLOR_DICT"]["PHASE_COLORS"][:qs + 1] out.plot_A_space( "mag.Br", is_fft=True, index_list=[0, 1, 2], data_list=[mmf_u], fig=fig, color_list=color_list, )
def get_wave(self): """Return the wave generated by the Drive Parameters ---------- self : DriveWave A DriveWave object Returns ------- wave : DataTime Voltage / current waveform (Nt, qs) """ wave = self.wave.get_data() Phase = Data1D( name="phase", unit="", values=gen_name(wave.shape[0], is_add_phase=True), is_components=True, ) # Ouput.Simulation.Electrical.EEC.Drive if (check_parent(self, 4) and self.parent.parent.parent.parent.elec.time is not None and len(self.parent.parent.parent.parent.elec.time) > 1): Time = Data1D(name="time", unit="s", values=self.parent.parent.parent.parent.elec.time) else: Nt = wave.shape[1] Time = DataLinspace( name="time", unit="s", symmetries={}, initial=0, final=Nt, number=Nt, include_endpoint=False, ) if self.is_current: return DataTime(name="Current", unit="A", symbol="I", axes=[Phase, Time], values=wave) else: return DataTime(name="Voltage", unit="V", symbol="U", axes=[Phase, Time], values=wave)
def import_signal(self, is_stationary, file, calib=1, mat_signal='', mat_fs=''): """ Method to load the signal from a .wav .mat or .uff file Parameters ---------- is_stationary : boolean TRUE if the signal is stationary, FALSE if it is time-varying file : string string path to the signal file calib : float calibration factor for the signal to be in [pa] mat_signal : string in case of a .mat file, name of the signal variable mat_fs : string in case of a .mat file, name of the sampling frequency variable Outputs ------- signal : numpy.array time signal values fs : integer sampling frequency """ self.is_stationary = is_stationary values, self.fs = load(self.is_stationary, file, calib, mat_signal, mat_fs) self.signal = Data1D(values=values, name="Audio signal", unit='Pa') self.time_axis = DataLinspace( initial=0, final=len(self.signal.values) / self.fs, step=1 / self.fs, )
def comp_axes(self, machine, N0=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 Parameters ---------- self : Input an Input object machine : Machine a Machine object N0 : float rotating speed [rpm] Returns ------- Time : DataLinspace Time axis including (anti)-periodicity and accounting for rotating speed and number of revolutions Angle : DataLinspace Angle axis including (anti)-periodicity """ if self.time is None and N0 is None: raise InputError("ERROR: time and N0 can't be both 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, "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} # Create time axis if self.time is None: # 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, ) 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) # 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, ) 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) return Time, Angle
def __init__(self, file, is_stationary=False, calib=1, mat_signal="", mat_fs=""): """Constructor of the class. Loads the signal from a .wav .mat or .uff file Parameters ---------- self : Audio object Object from the Audio class file : string string path to the signal file is_stationary : boolean TRUE if the signal is stationary, FALSE if it is time-varying calib : float calibration factor for the signal to be in [pa] mat_signal : string in case of a .mat file, name of the signal variable mat_fs : string in case of a .mat file, name of the sampling frequency variable """ # Import audio signal values, fs = load( is_stationary, file, calib=calib, mat_signal=mat_signal, mat_fs=mat_fs, ) # Create Data object for time axis time_axis = DataLinspace( name="time", unit="s", initial=0, final=(len(values) - 1) / fs, number=len(values), include_endpoint=True, ) # Create audio signal Data object and populate the object self.fs = fs self.is_stationary = is_stationary self.signal = DataTime( name="Audio signal", symbol="x", unit="Pa", normalizations={"ref": 2e-5}, axes=[time_axis], values=values, ) # Init physical metrics attributes self.third_spec = None self.level = None self.welch = None # Init physiological metrics attributes self.loudness_zwicker = None self.loudness_zwicker_specific = None self.sharpness = dict() self.roughness = dict() self.tonality = dict()
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 ---------- self : Input an Input object 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 """ 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=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), } # Compute Time axis based on input one if Time_in is not None: 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.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: # Set final time to p times the number of electrical periods 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 elif per_t > 1: sym_t["period"] = per_t Time.symmetries = sym_t Time = Time.to_linspace() return Time
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 ---------- 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) } # Compute angle axis based on input one if Angle_in is not None: 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: # 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 elif per_a > 1: sym_a["period"] = per_a Angle.symmetries = sym_a Angle = Angle.to_linspace() return Angle
def comp_mmf_unit(self, Na=2048, Nt=50): """Compute the winding Unit magnetomotive force Parameters ---------- self : LamSlotWind an LamSlotWind object Na : int Space discretization for offline computation (otherwise use out.elec.angle) Nt : int Time discretization for offline computation (otherwise use out.elec.time) Returns ------- mmf_unit : SciDataTool.Classes.DataND.DataND Unit magnetomotive force (Na) """ # Check if the lamination is within an output object is_out = check_parent(self, 3) # Check if the result is already available if is_out and self.parent.parent.parent.elec.mmf_unit is not None: return self.parent.parent.parent.elec.mmf_unit # Define the space dicretization if (is_out and self.parent.parent.parent.elec.angle is not None and self.parent.parent.parent.elec.angle.size > 0): # Use Electrical module discretization angle = self.parent.parent.parent.elec.angle Na = angle.size else: angle = linspace(0, 2 * pi, Na, endpoint=False) # Define the time dicretization if (is_out and self.parent.parent.parent.elec.time is not None and self.parent.parent.parent.elec.time.size > 1): time = self.parent.parent.parent.elec.time Nt = time.size else: time = linspace(0, 1 / 50, Nt, endpoint=False) # freq = 50Hz # Compute the winding function and mmf wf = self.comp_wind_function(angle=angle) qs = self.winding.qs # Compute unit mmf Idq = zeros((Nt, 2)) Idq[:, 0] = ones(Nt) I = dq2n(Idq, 0, n=qs) mmf_u = squeeze(dot(I, wf)) # Create a Data object Time = Data1D(name="time", unit="s", values=time) Angle = DataLinspace( name="angle", unit="rad", symmetries={}, initial=0, final=2 * pi, number=Na, include_endpoint=False, ) MMF = DataTime( name="Unit MMF", unit="p.u.", symbol="Magnitude", axes=[Time, Angle], values=mmf_u, ) if is_out: # Store the result if the Output is available self.parent.parent.parent.elec.mmf_unit = MMF return MMF
def comp_axes(self, machine, N0=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 Parameters ---------- self : Input an Input object machine : Machine a Machine object N0 : float rotating speed [rpm] Returns ------- Time : DataLinspace Time axis including (anti)-periodicity and accounting for rotating speed and number of revolutions Angle : DataLinspace Angle axis including (anti)-periodicity """ # Time axis if self.time is None: if N0 is None: raise InputError("ERROR: time and N0 can't be both None") Time = DataLinspace( name="time", unit="s", initial=0, final=60 / N0 * self.Nrev, number=self.Nt_tot, include_endpoint=False, ) else: # Load and check time time = self.time.get_data() self.Nt_tot = len(time) Time = Data1D(name="time", unit="s", values=time) # Angle axis if self.angle is None: # Create angle vector as a linspace Angle = DataLinspace( name="angle", unit="rad", initial=0, final=2 * pi, number=self.Na_tot, include_endpoint=False, ) else: # Load angle data angle = self.angle.get_data() self.Na_tot = len(angle) Angle = Data1D(name="angle", unit="rad", values=angle) return Time, Angle
def draw_FEMM(self): """Draw the Machine in FEMM""" save_file_path = self.get_save_path(ext=".fem", file_type="FEMM (*.fem)") # Avoid bug due to user closing the popup witout selecting a file if save_file_path is [None, ""]: return femm = _FEMMHandler() output = Output(simu=Simu1(machine=self.machine)) # Periodicity sym, is_antiper, _, _ = self.machine.comp_periodicity() if is_antiper: sym *= 2 # Set Current (constant J in a layer) S_slot = self.machine.stator.slot.comp_surface_active() (Nrad, Ntan) = self.machine.stator.winding.get_dim_wind() Ntcoil = self.machine.stator.winding.Ntcoil Sphase = S_slot / (Nrad * Ntan) J = 5e6 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", initial=0, final=60, number=20, include_endpoint=False, ) time = output.elec.Time.get_values( is_oneperiod=False, is_antiperiod=False, ) Is = output.elec.comp_I_mag(time, is_stator=True) alpha = output.get_angle_rotor_initial() try: # Draw the machine FEMM_dict = draw_FEMM( femm, output, is_mmfr=True, is_mmfs=True, sym=sym, is_antiper=is_antiper, type_calc_leakage=0, path_save=None, is_sliding_band=True, ) # Set the current update_FEMM_simulation( femm=femm, circuits=FEMM_dict["circuits"], is_sliding_band=True, is_internal_rotor=self.machine.rotor.is_internal, angle_rotor=[alpha], Is=Is, Ir=None, ii=0, ) femm.mi_saveas(save_file_path) # Save except Exception as e: err_msg = ("Error while drawing machine " + self.machine.name + " in FEMM:\n" + str(e)) getLogger(GUI_LOG_NAME).error(err_msg) QMessageBox().critical( self, self.tr("Error"), self.tr(err_msg), ) femm.closefemm()
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
def solve_FEMM(self, femm, output, sym): # Loading parameters for readibility angle = output.mag.angle L1 = output.simu.machine.stator.comp_length() Nt_tot = output.mag.Nt_tot # Number of time step Na_tot = output.mag.Na_tot # Number of angular step save_path = self.get_path_save(output) FEMM_dict = output.mag.FEMM_dict 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 Npcpp = output.simu.machine.stator.winding.Npcpp Phi_wind_stator = zeros((Nt_tot, qs)) else: Phi_wind_stator = None # Create the mesh femm.mi_createmesh() # Initialize results matrix Br = zeros((Nt_tot, Na_tot)) Bt = zeros((Nt_tot, Na_tot)) Tem = zeros((Nt_tot)) Rag = output.simu.machine.comp_Rgap_mec() # Compute the data for each time step for ii in range(Nt_tot): self.get_logger().debug("Solving step " + str(ii + 1) + " / " + str(Nt_tot)) # Update rotor position and currents update_FEMM_simulation( femm=femm, output=output, materials=FEMM_dict["materials"], circuits=FEMM_dict["circuits"], is_mmfs=self.is_mmfs, is_mmfr=self.is_mmfr, j_t0=ii, is_sliding_band=self.is_sliding_band, ) # try "previous solution" for speed up of FEMM calculation if self.is_sliding_band: try: base = basename(self.get_path_save_fem(output)) ans_file = splitext(base)[0] + ".ans" femm.mi_setprevious(ans_file, 0) except: pass # Run the computation femm.mi_analyze() femm.mi_loadsolution() # Get the flux result if self.is_sliding_band: for jj in range(Na_tot): Br[ii, jj], Bt[ii, jj] = femm.mo_getgapb("bc_ag2", angle[jj] * 180 / pi) else: for jj in range(Na_tot): B = femm.mo_getb(Rag * np.cos(angle[jj]), Rag * np.sin(angle[jj])) Br[ii, jj] = B[0] * np.cos(angle[jj]) + B[1] * np.sin(angle[jj]) Bt[ii, jj] = -B[0] * np.sin(angle[jj]) + B[1] * np.cos(angle[jj]) # Compute the torque Tem[ii] = comp_FEMM_torque(femm, FEMM_dict, sym=sym) if (hasattr(output.simu.machine.stator, "winding") and output.simu.machine.stator.winding is not None): # Phi_wind computation Phi_wind_stator[ii, :] = comp_FEMM_Phi_wind( femm, qs, Npcpp, is_stator=True, Lfemm=FEMM_dict["Lfemm"], L1=L1, sym=sym, ) # Load mesh data & solution if (self.is_sliding_band or Nt_tot == 1) and (self.is_get_mesh or self.is_save_FEA): tmpmeshFEMM, tmpB, tmpH, tmpmu, tmpgroups = self.get_meshsolution( femm, save_path, ii) if ii == 0: meshFEMM = [tmpmeshFEMM] groups = [tmpgroups] B_elem = np.zeros( [Nt_tot, meshFEMM[ii].cell["triangle"].nb_cell, 3]) H_elem = np.zeros( [Nt_tot, meshFEMM[ii].cell["triangle"].nb_cell, 3]) mu_elem = np.zeros( [Nt_tot, meshFEMM[ii].cell["triangle"].nb_cell]) B_elem[ii, :, 0:2] = tmpB H_elem[ii, :, 0:2] = tmpH mu_elem[ii, :] = tmpmu # Shift to take into account stator position roll_id = int(self.angle_stator * Na_tot / (2 * pi)) Br = roll(Br, roll_id, axis=1) Bt = roll(Bt, roll_id, axis=1) # Store the results Time = DataLinspace( name="time", unit="s", symmetries={}, initial=output.mag.time[0], final=output.mag.time[-1], number=Nt_tot, include_endpoint=True, ) Angle = DataLinspace( name="angle", unit="rad", symmetries={}, initial=angle[0], final=angle[-1], number=Na_tot, include_endpoint=True, ) Br_data = DataTime( name="Airgap radial flux density", unit="T", symbol="B_r", axes=[Time, Angle], values=Br, ) Bt_data = DataTime( name="Airgap tangential flux density", unit="T", symbol="B_t", axes=[Time, Angle], values=Bt, ) output.mag.B = VectorField( name="Airgap flux density", symbol="B", components={ "radial": Br_data, "tangential": Bt_data }, ) output.mag.Tem = DataTime( name="Electromagnetic torque", unit="Nm", symbol="T_{em}", axes=[Time], values=Tem, ) output.mag.Tem_av = mean(Tem) self.get_logger().debug("Average Torque: " + str(output.mag.Tem_av) + " N.m") output.mag.Tem_rip_pp = abs(np_max(Tem) - np_min(Tem)) # [N.m] if output.mag.Tem_av != 0: output.mag.Tem_rip_norm = output.mag.Tem_rip_pp / output.mag.Tem_av # [] else: output.mag.Tem_rip_norm = None output.mag.Phi_wind_stator = Phi_wind_stator output.mag.FEMM_dict = FEMM_dict if self.is_get_mesh: output.mag.meshsolution = self.build_meshsolution( Nt_tot, meshFEMM, Time, B_elem, H_elem, mu_elem, groups) if self.is_save_FEA: save_path_fea = join(save_path, "MeshSolutionFEMM.h5") output.mag.meshsolution.save(save_path_fea) if (hasattr(output.simu.machine.stator, "winding") and output.simu.machine.stator.winding is not None): # Electromotive forces computation (update output) self.comp_emf() else: output.mag.emf = None if self.is_close_femm: femm.closefemm()
def comp_axis_phase(self, lamination, per_a=None, is_apera=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: 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()}, is_overlay=True, # filter={"Phase": []}, ) else: # Creating the data object Phase = Data1D( name="phase", unit="rad", values=name_phase, is_components=True, is_overlay=True, filter={"Phase": []}, ) return Phase
def comp_mmf_unit(self, Na=None, Nt=None, freq=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) Nt : int Time discretization for offline computation (otherwise use out.elec.time) freq : float Stator current frequency to consider Returns ------- mmf_unit : SciDataTool.Classes.DataND.DataND Unit magnetomotive force (Na) """ # Check if the lamination is within an output object is_out = check_parent(self, 3) # Get stator winding number of phases qs = self.winding.qs # Get spatial symmetry per_a, _, _, _ = self.comp_periodicity() # Check if the result is already available and that requested size is the same as stored data if (is_out and self.parent.parent.parent.elec.mmf_unit is not None and Nt is not None and Na is not None): if self.parent.parent.parent.elec.mmf_unit.values.shape == (Nt, Na): return self.parent.parent.parent.elec.mmf_unit # Define the space dicretization if Na is None and is_out and self.parent.parent.parent.elec.angle is not None: # Use Electrical module discretization angle = self.parent.parent.parent.elec.angle Na = angle.size else: angle = linspace(0, 2 * pi / per_a, Na, endpoint=False) # Define the time dicretization if Nt is None and is_out and self.parent.parent.parent.elec.time is not None: # Use Electrical module discretization time = self.parent.parent.parent.elec.time freq = self.parent.parent.parent.elec.felec Nt = time.size else: time = linspace(0, 1 / freq, Nt, endpoint=False) # Compute the winding function and mmf 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[:, 0] = ones(Nt) I = dq2n(Idq, 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 = DataLinspace( name="angle", unit="rad", symmetries={"angle": { "period": per_a }}, initial=0, final=2 * pi / per_a, number=Na, include_endpoint=False, ) MMF = DataTime( name="Unit MMF", unit="p.u.", symbol="Magnitude", axes=[Time, Angle], values=mmf_u, symmetries={"angle": { "period": per_a }}, ) if is_out: # Store the result if the Output is available self.parent.parent.parent.elec.mmf_unit = MMF return MMF
def comp_BEMF_harmonics(Phi_A, Phi_B, Phi_C, delta, time): """ Compute the back electromotive force harmonics from magnet fluxes (PMSM) 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
def solve_FEMM(self, output, sym, FEMM_dict): # Loading parameters for readibilitys angle = output.mag.angle L1 = output.simu.machine.stator.comp_length() Nt_tot = output.mag.Nt_tot # Number of time step Na_tot = output.mag.Na_tot # Number of angular step save_path = self.get_path_save(output) 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 Npcpp = output.simu.machine.stator.winding.Npcpp Phi_wind_stator = zeros((Nt_tot, qs)) else: Phi_wind_stator = None # Create the mesh femm.mi_createmesh() # Initialize results matrix Br = zeros((Nt_tot, Na_tot)) Bt = zeros((Nt_tot, Na_tot)) Tem = zeros((Nt_tot, 1)) lam_int = output.simu.machine.get_lamination(True) lam_ext = output.simu.machine.get_lamination(False) Rgap_mec_int = lam_int.comp_radius_mec() Rgap_mec_ext = lam_ext.comp_radius_mec() if self.is_get_mesh or self.is_save_FEA: meshFEMM = [Mesh() for ii in range(Nt_tot)] solutionFEMM = [Solution() for ii in range(Nt_tot)] else: meshFEMM = [Mesh()] solutionFEMM = [Solution()] # Compute the data for each time step for ii in range(Nt_tot): # Update rotor position and currents update_FEMM_simulation( output=output, materials=FEMM_dict["materials"], circuits=FEMM_dict["circuits"], is_mmfs=self.is_mmfs, is_mmfr=self.is_mmfr, j_t0=ii, is_sliding_band=self.is_sliding_band, ) # try "previous solution" for speed up of FEMM calculation if self.is_sliding_band: try: base = basename(self.get_path_save_fem(output)) ans_file = splitext(base)[0] + ".ans" femm.mi_setprevious(ans_file, 0) except: pass # Run the computation femm.mi_analyze() femm.mi_loadsolution() # Get the flux result if self.is_sliding_band: for jj in range(Na_tot): Br[ii, jj], Bt[ii, jj] = femm.mo_getgapb("bc_ag2", angle[jj] * 180 / pi) else: Rag = (Rgap_mec_ext + Rgap_mec_int) / 2 for jj in range(Na_tot): B = femm.mo_getb(Rag * np.cos(angle[jj]), Rag * np.sin(angle[jj])) Br[ii, jj] = B[0] * np.cos(angle[jj]) + B[1] * np.sin(angle[jj]) Bt[ii, jj] = -B[0] * np.sin(angle[jj]) + B[1] * np.cos(angle[jj]) # Compute the torque Tem[ii] = comp_FEMM_torque(FEMM_dict, sym=sym) if (hasattr(output.simu.machine.stator, "winding") and output.simu.machine.stator.winding is not None): # Phi_wind computation Phi_wind_stator[ii, :] = comp_FEMM_Phi_wind( qs, Npcpp, is_stator=True, Lfemm=FEMM_dict["Lfemm"], L1=L1, sym=sym) # Load mesh data & solution if self.is_get_mesh or self.is_save_FEA: meshFEMM[ii], solutionFEMM[ii] = self.get_meshsolution( self.is_get_mesh, self.is_save_FEA, save_path, ii) # Shift to take into account stator position roll_id = int(self.angle_stator * Na_tot / (2 * pi)) Br = roll(Br, roll_id, axis=1) Bt = roll(Bt, roll_id, axis=1) # Store the results Time = DataLinspace( name="time", unit="s", symmetries={}, initial=output.mag.time[0], final=output.mag.time[-1], number=Nt_tot, ) Angle = DataLinspace( name="angle", unit="rad", symmetries={}, initial=angle[0], final=angle[-1], number=Na_tot, ) output.mag.Br = DataTime( name="Airgap radial flux density", unit="T", symbol="B_r", axes=[Time, Angle], values=Br, ) output.mag.Bt = DataTime( name="Airgap tangential flux density", unit="T", symbol="B_t", axes=[Time, Angle], values=Bt, ) output.mag.Tem = Tem output.mag.Tem_av = mean(Tem) if output.mag.Tem_av != 0: output.mag.Tem_rip = abs( (np_max(Tem) - np_min(Tem)) / output.mag.Tem_av) output.mag.Phi_wind_stator = Phi_wind_stator output.mag.FEMM_dict = FEMM_dict if self.is_get_mesh: cond = (not self.is_sliding_band) or (Nt_tot == 1) output.mag.meshsolution = MeshSolution( name="FEMM_magnetic_mesh", mesh=meshFEMM, solution=solutionFEMM, is_same_mesh=cond, ) if self.is_save_FEA: save_path_fea = join(save_path, "MeshSolutionFEMM.json") output.mag.meshsolution.save(save_path_fea) if (hasattr(output.simu.machine.stator, "winding") and output.simu.machine.stator.winding is not None): # Electromotive forces computation (update output) self.comp_emf() else: output.mag.emf = None
def gen_input(self): """Generate the input for the structural module (magnetic output) Parameters ---------- self : InFlux An InFlux object """ output = OutMag() # Load and check time if self.time is None: raise InputError("ERROR: InFlux.time missing") output.time = self.time.get_data() if not isinstance(output.time, ndarray) or len(output.time.shape) != 1: # time should be a vector raise InputError("ERROR: InFlux.time should be a vector, " + str(output.time.shape) + " shape found") Nt_tot = len(output.time) # Load and check angle if self.angle is None: raise InputError("ERROR: InFlux.angle missing") output.angle = self.angle.get_data() if not isinstance(output.angle, ndarray) or len(output.angle.shape) != 1: # angle should be a vector raise InputError("ERROR: InFlux.angle should be a vector, " + str(output.angle.shape) + " shape found") Na_tot = len(output.angle) if self.Br is None: raise InputError("ERROR: InFlux.Br missing") Br = self.Br.get_data() Time = DataLinspace( name="time", unit="s", symmetries={}, initial=output.time[0], final=output.time[-1], number=Nt_tot, ) Angle = DataLinspace( name="angle", unit="rad", symmetries={}, initial=output.angle[0], final=output.angle[-1], number=Na_tot, ) output.Br = DataTime( name="Airgap radial flux density", unit="T", symbol="B_r", axes=[Time, Angle], values=Br, ) if not isinstance(output.Br, DataND) or Br.shape != (Nt_tot, Na_tot): raise InputError("ERROR: InFlux.Br must be a matrix with the shape " + str((Nt_tot, Na_tot)) + " (len(time), stator phase number), " + str(Br.shape) + " returned") if self.Bt is not None: Bt = self.Bt.get_data() output.Bt = DataTime( name="Airgap tangential flux density", unit="T", symbol="B_t", axes=[Time, Angle], values=Bt, ) if not isinstance(output.Bt, DataND) or Bt.shape != (Nt_tot, Na_tot): raise InputError( "ERROR: InFlux.Bt must be a matrix with the shape " + str((Nt_tot, Na_tot)) + " (len(time), rotor phase number), " + str(Bt.shape) + " returned") else: output.Bt = None 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.mag = output