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 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 * pi * 1e-7 # Load magnetic flux Brphiz = output.mag.B.get_rphiz_along("time", "angle") Br = Brphiz["radial"] Bt = Brphiz["tangential"] Bz = Brphiz["axial"] # Compute AGSF with MT formula Prad = (Br * Br - Bt * Bt - Bz * Bz) / (2 * mu_0) Ptan = Br * Bt / mu_0 Pz = Br * Bz / mu_0 # Store the results components = {} if not np_all((Prad == 0)): Prad_data = DataTime( name="Airgap radial surface force", unit="N/m2", symbol="P_r", axes=list(output.mag.B.components.values())[0].axes, values=Prad, ) components["radial"] = Prad_data if not np_all((Ptan == 0)): Ptan_data = DataTime( name="Airgap tangential surface force", unit="N/m2", symbol="P_t", axes=list(output.mag.B.components.values())[0].axes, values=Ptan, ) components["tangential"] = Ptan_data if not np_all((Pz == 0)): Pz_data = DataTime( name="Airgap axial surface force", unit="N/m2", symbol="P_z", axes=list(output.mag.B.components.values())[0].axes, values=Pz, ) components["axial"] = Pz_data output.force.P = VectorField(name="Magnetic airgap surface force", symbol="P", components=components)
def build_solution_vector(field, axis_list, name="", symbol="", unit="", is_real=True): """Build a SolutionVector object Parameters ---------- field : ndarray a vector vield axis_list : list a list of SciDataTool axis Returns ------- solution: SolutionVector a SolutionVector object """ components = {} x_data = DataTime( name=name, unit=unit, symbol=symbol + "x", axes=axis_list, values=field[..., 0], is_real=is_real, ) components["comp_x"] = x_data y_data = DataTime( name=name, unit=unit, symbol=symbol + "y", axes=axis_list, values=field[..., 1], is_real=is_real, ) components["comp_y"] = y_data if field.shape[-1] == 3 and not np_all((field[..., 2] == 0)): z_data = DataTime( name=name, unit=unit, symbol=symbol + "z", axes=axis_list, values=field[..., 2], is_real=is_real, ) components["comp_z"] = z_data vectorfield = VectorField(name=name, symbol=symbol, components=components) solution = SolutionVector(field=vectorfield, label=symbol) return solution
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 freq_to_time(self): """Performs the inverse Fourier Transform and stores the resulting field in a DataTime object. Parameters ---------- self : DataFreq a DataFreq object Returns ------- a DataTime object """ axes_str = [axis.name for axis in self.axes] axes_str = ["time" if axis_name == "freqs" else axis_name for axis_name in axes_str] axes_str = ["angle" if axis_name == "wavenumber" else axis_name for axis_name in axes_str] if axes_str == [axis.name for axis in self.axes]: raise AxisError( "ERROR: No available axis is compatible with fft (should be time or angle)" ) else: results = self.get_along(*axes_str) values = results.pop(self.symbol) Axes = [] for axis in results.keys(): Axes.append(Data1D(name=axis, values=results[axis])) return DataTime( name=self.name, unit=self.unit, symbol=self.symbol, axes=Axes, values=values, )
def build_solution_data(field, axis_list, name="", symbol="", unit="", is_real=True): """Build the MeshSolution objets from FEMM outputs. Parameters ---------- field : ndarray a data field axis_list : list a list of SciDataTool axis Returns ------- solution: SolutionData a SolutionData object """ data = DataTime( name=name, unit=unit, symbol=symbol, axes=axis_list, values=field, is_real=is_real, ) return SolutionData(field=data, label=symbol)
def solve_EEC(self, output): """Compute the parameters dict for the equivalent electrical circuit cf "Advanced Electrical Drives, analysis, modeling, control" Rik de doncker, Duco W.J. Pulle, Andre Veltman, Springer edition <--- ---> -----R-----wsLqIq---- -----R-----wsLdId---- | | | | | | | BEMF | | | | ---------Id---------- ---------Iq---------- ---> ---> Ud Uq Parameters ---------- self : EEC_PMSM an EEC_PMSM object output : Output an Output object """ qs = output.simu.machine.stator.winding.qs freq0 = self.freq0 ws = 2 * pi * freq0 rot_dir = output.get_rot_dir() time = output.elec.time # Prepare linear system XR = array([ [self.parameters["R20"], -ws * self.parameters["Lq"]], [ws * self.parameters["Ld"], self.parameters["R20"]], ]) XE = array([0, self.parameters["BEMF"]]) XU = array([self.parameters["Ud"], self.parameters["Uq"]]) Idq = solve(XR, XU - XE) # dq to abc transform Is = dq2n(Idq, -rot_dir * 2 * pi * freq0 * time, n=qs) # Store currents into a Data object Time = Data1D(name="time", unit="s", values=time) phases_names = gen_name(qs, is_add_phase=True) Phases = Data1D(name="phases", unit="dimless", values=phases_names, is_components=True) output.elec.Currents = DataTime( name="Stator currents", unit="A", symbol="I_s", axes=[Phases, Time], values=transpose(Is), ) output.elec.Is = Is output.elec.Ir = None
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 compute_loudness(self, field_type='free'): """ Method to compute the loudness according to Zwicker's method Parameter ---------- field-type: string 'free' by default or 'diffuse' """ if self.third_spec == None: self.comp_3oct_spec() if self.is_stationary == True: N, N_specific = loudness_zwicker_stationary( self.third_spec.values, self.third_spec.axes[0].values, field_type) elif self.is_stationary == False: N, N_specific = loudness_zwicker_time(self.third_spec.values, field_type) barks = Data1D(name='Frequency Bark scale', unit='Bark', values=np.linspace(0.1, 24, int(24 / 0.1))) if self.is_stationary == True: self.loudness_zwicker = Data1D(values=[N], name="Loudness", unit="Sones") self.loudness_zwicker_specific = DataFreq(symbol="N'", axes=[barks], values=N_specific, name="Specific loudness", unit="Sones") elif self.is_stationary == False: time = Data1D(symbol="T", name="Time axis", unit="s", values=np.linspace(0, len(self.signal.values) / self.fs, num=N.size)) self.loudness_zwicker = DataTime(symbol="N", axes=[time], values=N, name="Loudness", unit="Sones") self.loudness_zwicker_specific = DataFreq(symbol="N'", axes=[barks, time], values=N_specific, name="Specific loudness", unit="Sones")
def get_data(self): """Generate Data objects Parameters ---------- self : ImportData An ImportData object Returns ------- Data: DataND The generated Data object """ axes_list = [] is_freq = False for axis in self.axes: if axis.name == "freqs" or axis.name == "wavenumber": is_freq = True axes_list.append( Data1D( values=axis.field.get_data(), name=axis.name, unit=axis.unit, symmetries=axis.symmetries, )) if is_freq: Data = DataFreq( axes=axes_list, values=self.field.get_data(), name=self.name, symbol=self.symbol, unit=self.unit, normalizations=self.normalizations, symmetries=self.symmetries, ) else: Data = DataTime( axes=axes_list, values=self.field.get_data(), name=self.name, symbol=self.symbol, unit=self.unit, normalizations=self.normalizations, symmetries=self.symmetries, ) return Data
def plot_mmf_unit(self, 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 """ # Compute the winding function and mmf wf = self.comp_wind_function() qs = self.winding.qs mmf_u = self.comp_mmf_unit(Nt=1, Na=wf.shape[1]) if len(mmf_u.values.shape) == 1: mmf_u.values = mmf_u.values[ None, :] # TODO: correct bug in SciDataTool # Create a Data object Phase = Data1D( name="phase", unit="", values=gen_name(qs, is_add_phase=True), is_components=True, ) Angle = mmf_u.axes[1] WF = DataTime( name="WF", unit="p.u.", symbol="Magnitude", axes=[Phase, Angle], values=wf, symmetries=mmf_u.symmetries, ) color_list = config_dict["PLOT"]["COLOR_DICT"]["PHASE_COLORS"][:qs + 1] plot_A_space( WF, is_fft=True, index_list=[0, 1, 2], data_list=[mmf_u], fig=fig, color_list=color_list, )
def compute_roughness(self, overlap=0): """ Method to compute roughness according to the Daniel and Weber implementation Parameter --------- overlap: float overlapping coefficient for the time windows of 200ms """ roughness = comp_roughness(self.signal.values, self.fs, overlap) time = Data1D(name='Time', unit='s', values=roughness['time']) self.roughness = DataTime(symbol="R", axes=[time], values=roughness['values'], name="Roughness", unit="Asper")
def comp_loss(self, output, part_label): """Compute the Losses""" # get logger logger = self.get_logger() # check inpurt if not "Stator" in part_label and not "Rotor" in part_label: logger.warning(f"LossModelWinding.comp_loss(): 'part_label'" + f" {part_label} not implemented yet.") return None, None # get the simulation and the lamination simu = output.simu lam = simu.machine.get_lam_by_label(part_label) # check that lamination has a winding if hasattr(lam, "winding") and lam.winding is not None: R = lam.comp_resistance_wind(T=self.temperature) if lam.is_stator: current = output.elec.get_Is() else: current = output.elec.get_Ir() axes_names = [axis.name for axis in current.axes] data_dict = current.get_along(*axes_names) data = DataTime( name=self.name, unit="W", symbol="Loss", axes=current.axes, values=R * data_dict[current.symbol]**2, ) return data, None else: logger.warning( "LossModelWinding.comp_loss(): Lamination has no winding.") return None, None
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 solve_FEMM(self, femm, output, sym): # Get time and angular axes Angle_comp, Time_comp = self.get_axes(output) _, Time_comp_Tem = self.get_axes(output, is_remove_apert=True) # Check if the angular axis is anti-periodic _, is_antiper_a = Angle_comp.get_periodicity() # Import angular vector from Data object angle = Angle_comp.get_values( is_oneperiod=self.is_periodicity_a, is_antiperiod=is_antiper_a and self.is_periodicity_a, ) # Number of angular steps Na_comp = angle.size # Check if the angular axis is anti-periodic _, is_antiper_t = Time_comp.get_periodicity() # Number of time steps Nt_comp = Time_comp.get_length( is_oneperiod=True, is_antiperiod=is_antiper_t and self.is_periodicity_t, ) # Loading parameters for readibility L1 = output.simu.machine.stator.comp_length() 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_comp, qs)) else: Phi_wind_stator = None # Create the mesh femm.mi_createmesh() # Initialize results matrix Br = zeros((Nt_comp, Na_comp)) Bt = zeros((Nt_comp, Na_comp)) Tem = zeros((Nt_comp)) Rag = output.simu.machine.comp_Rgap_mec() # Compute the data for each time step for ii in range(Nt_comp): self.get_logger().debug("Solving step " + str(ii + 1) + " / " + str(Nt_comp)) # 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_comp): Br[ii, jj], Bt[ii, jj] = femm.mo_getgapb("bc_ag2", angle[jj] * 180 / pi) else: for jj in range(Na_comp): 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_comp == 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_comp, meshFEMM[ii].cell["triangle"].nb_cell, 3]) H_elem = np.zeros( [Nt_comp, meshFEMM[ii].cell["triangle"].nb_cell, 3]) mu_elem = np.zeros( [Nt_comp, 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_comp / (2 * pi)) Br = roll(Br, roll_id, axis=1) Bt = roll(Bt, roll_id, axis=1) # Store the results sym_dict = dict() # Define the periodicity if self.is_periodicity_t: sym_dict.update(Time_comp.symmetries) if self.is_periodicity_a: sym_dict.update(Angle_comp.symmetries) sym_dict_Tem = dict() if self.is_periodicity_t: sym_dict_Tem.update(Time_comp_Tem.symmetries) Br_data = DataTime( name="Airgap radial flux density", unit="T", symbol="B_r", axes=[Time_comp, Angle_comp], symmetries=sym_dict, values=Br, ) Bt_data = DataTime( name="Airgap tangential flux density", unit="T", symbol="B_t", axes=[Time_comp, Angle_comp], symmetries=sym_dict, 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_comp_Tem], symmetries=sym_dict_Tem, 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 if (hasattr(output.simu.machine.stator, "winding") and output.simu.machine.stator.winding is not None): Phase = Data1D( name="phase", unit="", values=gen_name(qs, is_add_phase=True), is_components=True, ) output.mag.Phi_wind_stator = DataTime( name="Stator Winding Flux", unit="Wb", symbol="Phi_{wind}", axes=[Time_comp, Phase], symmetries=sym_dict, values=Phi_wind_stator, ) output.mag.FEMM_dict = FEMM_dict if self.is_get_mesh: output.mag.meshsolution = self.build_meshsolution( Nt_comp, meshFEMM, Time_comp, 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_loss(self, output, part_label): """Compute the Losses""" # get logger logger = self.get_logger() # check inpurt if not "Stator" in part_label and not "Rotor" in part_label: logger.warning(f"LossModelBertotti.comp_loss(): 'part_label'" + f" {part_label} not implemented yet.") return None, None # get the simulation and the lamination simu = output.simu lam = simu.machine.get_lam_by_label(part_label) # get length, material and speed L1 = lam.L1 mat_type = lam.mat_type rho = mat_type.struct.rho N0 = output.elec.N0 group_name = part_label.lower() + " " + self.group # TODO unifiy FEA names # setup meshsolution and solution list meshsolution = output.mag.meshsolution.get_group(group_name) # compute needed model parameter from material data success = self.comp_coeff_Bertotti(mat_type) if not success: logger.warning( "LossModelBertotti: Unable to estimate model coefficents.") if success: # compute loss density LossDens, LossDensComps = self.comp_loss_density(meshsolution) # compute sum over frequencies axes_list = [axis.name for axis in LossDens.axes] freqs_idx = axes_list.index("freqs") loss_dens_freq_sum = LossDens.get_along(*axes_list)["LossDens"].sum( axis=freqs_idx) time = Data1D(name="time", unit="", values=array([0, 1])) # time = Data1D(name="time", unit="", values=array([0]), ) # TODO squeeze issue axes = [ axis for axis in LossDens.axes if axis.name not in ["time", "freqs"] ] # TODO utilize periodicity or use DataFreqs to reduce memory usage LossDensSum = DataTime( name="Losses sum", unit="W/kg", symbol="LossDensSum", axes=[time, *axes], values=tile(loss_dens_freq_sum, (2, 1)), # values=loss_freqs_sum[newaxis,:], # TODO squeeze issue ) # Set the symmetry factor according to the machine if simu.mag.is_periodicity_a: sym, is_antiper_a, _, _ = output.get_machine_periodicity() sym *= is_antiper_a + 1 else: sym = 1 # compute the sum of the losses area = meshsolution.get_mesh().get_cell_area() N0_list = self.N0 if self.N0 else [N0] k_freq = [n / N0 for n in N0_list] Time = output.elec.Time Speed = Data1D(name="speed", unit="rpm", symbol="N0", values=N0_list) loss_sum = _comp_loss_sum(self, LossDensComps, area, k_freq)[newaxis, :] loss_sum = (loss_sum * ones( (Time.get_length(), 1))[:, newaxis]) # TODO use periodicity loss_sum *= L1 * rho * sym data = DataTime(name=self.name, unit="W", symbol="Loss", axes=[Time, Speed], values=loss_sum) if self.get_meshsolution: solution = [] meshsolution.solution = solution solution.append(SolutionData(field=LossDens, label="LossDens")) solution.append( SolutionData(field=LossDensComps, label="LossDensComps")) solution.append( SolutionData(field=LossDensSum, label="LossDensSum")) return data, meshsolution else: return data, None else: return None, None
def dqh2n_DataTime(data_dqh, n, is_n_rms=False, phase_dir=None): """dqh to n phase coordinate transformation of DataTime object Parameters ---------- data_dqh : DataTime data object containing values over time in dqh frame n: int number of phases is_n_rms : boolean True to return n currents in rms value, False to return peak values (Pyleecan convention) phase_dir: int direction of phase distribution: +/-1 (-1 clockwise) to enforce Returns ------- data_n : DataTime data object containing values over time and phase axes """ # Check if input data object is compliant with dqh transformation _check_data(data_dqh) if "angle_elec" not in data_dqh.axes[0].normalizations: raise Exception("Time axis should contain angle_elec normalization") # Get values for one time period converted in electrical angle and for all phases angle_elec = data_dqh.axes[0].get_values(normalization="angle_elec", is_oneperiod=True) data_dqh_val = data_dqh.get_along("time[oneperiod]", "phase", is_squeeze=False)[data_dqh.symbol] # Convert values to dqh frame data_n_val = dqh2n(data_dqh_val, angle_elec, n, is_n_rms, phase_dir) # Get time axis on one period per_t, is_aper_t = data_dqh.axes[0].get_periodicity() per_t = int(per_t / 2) if is_aper_t else per_t Time = data_dqh.axes[0].get_axis_periodic(per_t, is_aper=False) # Create DQH axis Phase = Data1D( name="phase", unit="", values=gen_name(n), is_components=True, ) # Get normalizations normalizations = dict() if data_dqh.normalizations is not None and len( data_dqh.normalizations) > 0: for key, val in data_dqh.normalizations.items(): normalizations[key] = val.copy() # Create DataTime object in dqh frame data_n = DataTime( name=data_dqh.name.replace(" in DQH frame", ""), unit=data_dqh.unit, symbol=data_dqh.symbol, values=data_n_val, axes=[Time, Phase], normalizations=normalizations, is_real=data_dqh.is_real, ) return data_n
def build_meshsolution(self): """Get the mesh and solution data from an Elmer VTU results file Parameters ---------- self : ElmerResultsVTU a ElmerResultsVTU object Returns ------- success: bool Information if meshsolution could be created """ # create meshsolution meshsol = MeshSolution(label=self.label) # get the mesh save_path, fn = split(self.file_path) file_name, file_ext = splitext(fn) if file_ext != ".vtu": raise ElmerResultsVTUError( "ElmerResultsVTU: Results file must be of type VTU.") meshvtk = MeshVTK(path=save_path, name=file_name, format="vtu") # TODO maybe convert to MeshMat before meshsol.mesh = [meshvtk] # get the solution data on the mesh meshsolvtu = read(self.file_path) pt_data = meshsolvtu.point_data # point_data is of type dict # setup axes indices = arange(meshsolvtu.points.shape[0]) Indices = Data1D(name="indice", values=indices, is_components=True) # store only data from store dict if available comp_ext = ["x", "y", "z"] sol_list = [] # list of solutions for key, value in pt_data.items(): # check if value should be stored if key in self.store_dict.keys(): siz = value.shape[1] # only regard max. 3 components if siz > 3: logger.warning( f'ElmerResultsVTU.build_meshsolution(): size of data "{key}" > 3' + " - " + "Data will be truncated.") siz = 3 components = [] comp_name = [] # loop though components for i in range(siz): # setup name, symbol and component name extension if siz == 1: ext = "" else: ext = comp_ext[i] # setup data object data = DataTime( name=self.store_dict[key]["name"] + " " + ext, unit=self.store_dict[key]["unit"], symbol=self.store_dict[key]["symbol"] + ext, axes=[Indices], values=value[:, i], normalizations={ "ref": Norm_ref(ref=self.store_dict[key]["norm"]) }, ) components.append(data) comp_name.append("comp_" + ext) # setup solution depending on number of field components if siz == 1: field = components[0] sol_list.append( SolutionData( field=field, type_cell="point", label=self.store_dict[key]["symbol"], )) else: comps = {} for i in range(siz): comps[comp_name[i]] = components[i] field = VectorField( name=self.store_dict[key]["name"], symbol=self.store_dict[key]["symbol"], components=comps, ) sol_list.append( SolutionVector( field=field, type_cell="point", label=self.store_dict[key]["symbol"], )) meshsol.solution = sol_list return meshsol
def build_meshsolution(self, Nt_tot, meshFEMM, Time, B, H, mu, groups): """Build the MeshSolution objets from FEMM outputs. Parameters ---------- self : MagFEMM a MagFEMM object is_get_mesh : bool 1 to load the mesh and solution into the simulation is_save_FEA : bool 1 to save the mesh and solution into a .json file j_t0 : int Targeted time step Returns ------- meshsol: MeshSolution a MeshSolution object with FEMM outputs at every time step """ sollist = list() cond = self.is_sliding_band or Nt_tot == 1 if cond: indices_cell = meshFEMM[0].cell["triangle"].indice Direction = Data1D(name="direction", values=["x", "y", "z"], is_components=True) Indices_Cell = Data1D(name="indice", values=indices_cell, is_components=True) Nodirection = Data1D(name="direction", values=["scalar"], is_components=False) # Store the results for B components = {} Bx_data = DataTime( name="Magnetic Flux Density Bx", unit="T", symbol="Bx", axes=[Time, Indices_Cell], values=B[:, :, 0], ) components["x"] = Bx_data By_data = DataTime( name="Magnetic Flux Density By", unit="T", symbol="By", axes=[Time, Indices_Cell], values=B[:, :, 1], ) components["y"] = By_data if not np.all((B[:, :, 2] == 0)): Bz_data = DataTime( name="Magnetic Flux Density Bz", unit="T", symbol="Bz", axes=[Time, Indices_Cell], values=B[:, :, 2], ) components["z"] = Bz_data solB = VectorField(name="Magnetic Flux Density", symbol="B", components=components) # Store the results for H componentsH = {} Hx_data = DataTime( name="Magnetic Field Hx", unit="A/m", symbol="Hx", axes=[Time, Indices_Cell], values=H[:, :, 0], ) componentsH["x"] = Hx_data Hy_data = DataTime( name="Magnetic Field Hy", unit="A/m", symbol="Hy", axes=[Time, Indices_Cell], values=H[:, :, 1], ) componentsH["y"] = Hy_data if not np.all((H[:, :, 2] == 0)): Hz_data = DataTime( name="Magnetic Field Hz", unit="A/m", symbol="Hz", axes=[Time, Indices_Cell], values=H[:, :, 2], ) componentsH["z"] = Hz_data solH = VectorField(name="Magnetic Field", symbol="H", components=componentsH) solmu = DataTime( name="Magnetic Permeability", unit="H/m", symbol="\mu", axes=[Time, Indices_Cell], values=mu, ) sollist.append( SolutionVector(field=solB, type_cell="triangle", label="B")) # Face solution sollist.append( SolutionVector(field=solH, type_cell="triangle", label="H")) sollist.append( SolutionData(field=solmu, type_cell="triangle", label="\mu")) meshsol = MeshSolution( label="FEMM_magnetotatic", mesh=meshFEMM, solution=sollist, is_same_mesh=cond, dimension=2, ) meshsol.group = groups[0] return meshsol
def gen_drive(self, output): """Generate the drive for the equivalent electrical circuit (only PWM drive for now) Parameters ---------- self : Electrical an Electrical object output : Output an Output object """ self.get_logger().info("Calculating PWM voltage") p = output.simu.machine.get_pole_pair_number() # Get PWM object PWM = output.elec.PWM if PWM.U0 in [0, None]: raise Exception("Cannot calculate PWM voltage if PWM.U0 is None or 0") # Get operating point OP = output.elec.OP if PWM.is_star: # Calculate modulation index to account for quick variations M_I = PWM.get_modulation_index() # Number of points depends on modulation index PWM.fs /= max([M_I, 0.05]) # Number of points depends on modulation index Nt_tot = int(PWM.fs * PWM.duration) # Get time axis input_pwm = type(output.simu.input)( OP=OP, Nt_tot=Nt_tot, t_final=PWM.duration, current_dir=output.elec.current_dir, rot_dir=output.geo.rot_dir, ) Time_PWM = input_pwm.comp_axis_time(p, per_t=1, is_antiper_t=False) # Generate PWM signal Uabc = PWM.get_data(is_norm=False, Time=Time_PWM)[0] # Get phase axis stator_label = output.simu.machine.stator.get_label() Phase = output.elec.axes_dict["phase_" + stator_label] # Create DataTime object Us_dt = DataTime( name="Stator voltage", symbol="U_s", unit="V", axes=[Time_PWM, Phase], values=Uabc, ) # Get DataFreq object and frequency axis by taking FFT Us_df = Us_dt.get_data_along("freqs<" + str(self.freq_max), "phase") Freqs = Us_df.axes[0] freqs = Freqs.get_values() # Filter frequencies with low amplitude and create new frequency axis Un_norm = np.linalg.norm(Us_df.values, axis=-1) Iamp_n = Un_norm > 1e-2 * Un_norm.max() Us_df.axes[0] = Data1D( name=Freqs.name, unit=Freqs.unit, symbol=Freqs.symbol, values=freqs[Iamp_n], normalizations=Freqs.normalizations, is_components=False, symmetries=Freqs.symmetries, ) Us_df.values = Us_df.values[Iamp_n, :] # Store voltage spectrum in OutElec output.elec.Us = Us_dt.to_datadual(datafreq=Us_df) # Store Time_PWM axis in axes_dict output.elec.axes_dict["time"] = Time_PWM
def comp_mmf_unit(self, Na=None, Nt=None, felec=1, current_dir=None, phase_dir=None): """Compute the winding unit magnetomotive force for given inputs Parameters ---------- self : LamSlotWind an LamSlotWind object Na : int Space discretization for offline computation Nt : int Time discretization for offline computation felec : float Stator current frequency to consider current_dir: int Stator current rotation direction +/-1 phase_dir: int Stator winding phasor rotation direction +/-1 Returns ------- MMF_U : DataTime Unit magnetomotive force (Na,Nt) WF : DataTime Winding functions (qs,Na) """ # Check that parent machine is not None if self.parent is None: raise Exception("Cannot calculate mmf unit if parent machine is None") else: machine = self.parent # Compute the winding function and mmf if self.winding is None: raise Exception("Cannot calculate mmf unit if winding is None") else: # Get stator winding number of phases qs = self.winding.qs if current_dir is None: current_dir = CURRENT_DIR_REF elif current_dir not in [-1, 1]: raise Exception("Cannot enforce current_dir other than +1 or -1") if phase_dir is None: phase_dir = PHASE_DIR_REF elif phase_dir not in [-1, 1]: raise Exception("Cannot enforce phase_dir other than +1 or -1") if machine.is_synchronous(): OPclass = OPdq else: OPclass = OPslip InputVoltage = import_class("pyleecan.Classes", "InputVoltage") input = InputVoltage( Na_tot=Na, Nt_tot=Nt, OP=OPclass(felec=felec), current_dir=current_dir, rot_dir=ROT_DIR_REF, # rotor rotating dir has not impact on unit mmf ) axes_dict = input.comp_axes( axes_list=["time", "angle", "phase_S", "phase_R"], machine=machine, is_periodicity_t=True, is_periodicity_a=True, is_antiper_t=False, is_antiper_a=False, ) # Compute winding function angle = axes_dict["angle"].get_values(is_oneperiod=True) per_a, _ = axes_dict["angle"].get_periodicity() wf = self.comp_wind_function(angle=angle, per_a=per_a) # Compute unit current function of time applying constant Id=1 Arms, Iq=Ih=0 angle_elec = axes_dict["time"].get_values(is_oneperiod=True, normalization="angle_elec") Idq = zeros((angle_elec.size, 3)) Idq[:, 0] = ones(angle_elec.size) I = dqh2n(Idq, angle_elec, n=qs, is_n_rms=False, phase_dir=phase_dir) # Compute unit mmf mmf_u = squeeze(dot(I, wf)) # Create a Data object MMF_U = DataTime( name="Overall MMF", unit="A", symbol="MMF", axes=[axes_dict["time"], axes_dict["angle"]], values=mmf_u, ) WF = DataTime( name="Phase MMF", unit="A", symbol="MMF", axes=[axes_dict["angle"], axes_dict["phase_" + self.get_label()]], values=wf.T, ) return MMF_U, WF
def solve_EEC(self, output): """Compute the parameters dict for the equivalent electrical circuit TODO find ref. to cite cf "Title" Autor, Publisher ---> ----> -----Rs------XsIs---- --- -----Rr'----Xr'Ir'---- | | | | | Rfe Xm Rr'*(s-1)/s | | | | ---------Is---------- --- ---------Ir------------ ---> Us Parameters ---------- self : EEC_SCIM an EEC_SCIM object output : Output an Output object """ Rs = self.parameters["Rs"] Rr = self.parameters["Rr_norm"] Rfe = self.parameters["Rfe"] Ls = self.parameters["Ls"] Lr = self.parameters["Lr_norm"] Lm = self.parameters["Lm"] norm = self.parameters["norm"] slip = self.parameters["slip"] felec = output.elec.felec ws = 2 * pi * felec Xs = ws * Ls Xm = ws * Lm Xr = ws * Lr Rr_s = Rr / slip if slip != 0 else 1e16 # TODO modify system instead # Prepare linear system # Solve system if "Ud" in self.parameters: Us = self.parameters["Ud"] + 1j * self.parameters["Uq"] # input vector b = array([real(Us), imag(Us), 0, 0, 0, 0, 0, 0, 0, 0]) # system matrix (unknowns order: Um, Is, Im, Ir', Ife each real and imagine parts) # TODO simplify system for less unknows (only calculate them afterwards, e.g. Um, Im, Ife) # fmt: off A = array( [ # sum of (real and imagine) voltages equals the input voltage Us [ 1, 0, Rs, -Xs, 0, 0, 0, 0, 0, 0, ], [ 0, 1, Xs, Rs, 0, 0, 0, 0, 0, 0, ], # sum of (real and imagine) currents are zeros [ 0, 0, -1, 0, 1, 0, 1, 0, 1, 0, ], [ 0, 0, 0, -1, 0, 1, 0, 1, 0, 1, ], # j*Xm*Im = Um [-1, 0, 0, 0, 0, -Xm, 0, 0, 0, 0, ], [ 0, -1, 0, 0, Xm, 0, 0, 0, 0, 0, ], # (Rr'/s + j*Xr')*Ir' = Um [-1, 0, 0, 0, 0, 0, Rr_s, -Xr, 0, 0, ], [ 0, -1, 0, 0, 0, 0, Xr, Rr_s, 0, 0, ], # Rfe*Ife = Um [-1, 0, 0, 0, 0, 0, 0, 0, Rfe, 0, ], [ 0, -1, 0, 0, 0, 0, 0, 0, 0, Rfe, ], ] ) # fmt: on # delete last row and column if Rfe is None if Rfe is None: A = A[:-2, :-2] b = b[:-2] # print(b) # print(A) X = solve(A.astype(float), b.astype(float)) Ir_norm = array([X[6], X[7]]) # TODO use logger for output of some quantities output.elec.Id_ref = X[2] # use Id_ref / Iq_ref for now output.elec.Iq_ref = X[3] else: pass # TODO # Compute stator currents output.elec.Is = None output.elec.Is = output.elec.get_Is() # Compute stator voltage output.elec.Us = None output.elec.Us = output.elec.get_Us() # Compute rotor currents time = output.elec.Time.get_values(is_oneperiod=True) Nt = time.size qsr = output.simu.machine.rotor.winding.qs sym = output.simu.machine.comp_periodicity()[0] Ir_ = tile(Ir_norm, (Nt, 1)) * norm w_slip = ws * slip # Get rotation direction rot_dir = output.get_rot_dir() # compute actual rotor bar currents # TODO fix: initial rotor pos. is disregarded for now Ir = dq2n(Ir_, w_slip * time, n=qsr // sym, rot_dir=rot_dir, is_n_rms=False) Ir = tile(Ir, (1, sym)) Phase = Data1D( name="phase", unit="", values=gen_name(qsr), is_components=True, ) output.elec.Ir = DataTime( name="Rotor current", unit="A", symbol="Ir", axes=[Phase, output.elec.Time.copy()], values=Ir.T, )
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 n2dqh_DataTime(data_n, is_dqh_rms=True, phase_dir=None): """n phases to dqh equivalent coordinate transformation of DataTime object Parameters ---------- data_n : DataTime data object containing values over time and phase axes is_dqh_rms : boolean True to return dq currents in rms value (Pyleecan convention), False to return peak values phase_dir: int direction of phase distribution: +/-1 (-1 clockwise) to enforce Returns ------- data_dqh : DataTime data object transformed in dqh frame """ if phase_dir is None: # Check if input data object is compliant with dqh transformation # and get phase_dir from data object phase_dir = get_phase_dir_DataTime(data_n) else: # Only check if input data object is compliant with dqh transformation _check_data(data_n) if "angle_elec" not in data_n.axes[0].normalizations: raise Exception("Time axis should contain angle_elec normalization") # Get values for one time period converted in electrical angle and for all phases angle_elec = data_n.axes[0].get_values(normalization="angle_elec", is_oneperiod=True) data_n_val = data_n.get_along("time[oneperiod]", "phase", is_squeeze=False)[data_n.symbol] # Convert values to dqh frame data_dqh_val = n2dqh(data_n_val, angle_elec, is_dqh_rms, phase_dir) # Get time axis on one period per_t, is_aper_t = data_n.axes[0].get_periodicity() per_t = int(per_t / 2) if is_aper_t else per_t Time = data_n.axes[0].get_axis_periodic(per_t, is_aper=False) # Create DQH axis axis_dq = Data1D( name="phase", unit="", values=["direct", "quadrature", "homopolar"], is_components=True, ) # Get normalizations normalizations = dict() if data_n.normalizations is not None and len(data_n.normalizations) > 0: for key, val in data_n.normalizations.items(): normalizations[key] = val.copy() # Create DataTime object in dqh frame data_dqh = DataTime( name=data_n.name + " in DQH frame", unit=data_n.unit, symbol=data_n.symbol, values=data_dqh_val, axes=[Time, axis_dq], normalizations=normalizations, is_real=data_n.is_real, ) return data_dqh
def get_meshsolution(self, output): """Build the MeshSolution objects from the FEA outputs. Parameters ---------- self : MagElmer a MagElmer object output: Output An Output object Returns ------- meshsol: MeshSolution a MeshSolution object with Elmer outputs at every time step """ project_name = self.get_path_save_fea(output) elmermesh_folder = project_name meshsol = MeshSolution(label="Elmer MagnetoDynamics") if not self.is_get_mesh or not self.is_save_FEA: self.get_logger().info( "MagElmer: MeshSolution is not stored by request.") return False meshvtk = MeshVTK(path=elmermesh_folder, name="step_t0002", format="vtu") meshsol.mesh = [meshvtk] result_filename = join(elmermesh_folder, "step_t0002.vtu") meshsolvtu = read(result_filename) #pt_data = meshsolvtu.point_data cell_data = meshsolvtu.cell_data #indices = arange(meshsolvtu.points.shape[0]) indices = arange(meshsolvtu.cells[0].data.shape[0] + meshsolvtu.cells[1].data.shape[0]) Indices = Data1D(name="indice", values=indices, is_components=True) # store_dict = { # "magnetic vector potential": { # "name": "Magnetic Vector Potential A", # "unit": "Wb", # "symbol": "A", # "norm": 1, # }, # "magnetic flux density": { # "name": "Magnetic Flux Density B", # "unit": "T", # "symbol": "B", # "norm": 1, # }, # "magnetic field strength": { # "name": "Magnetic Field H", # "unit": "A/m", # "symbol": "H", # "norm": 1, # }, # "current density": { # "name": "Current Density J", # "unit": "A/mm2", # "symbol": "J", # "norm": 1, # } # } store_dict = { "magnetic flux density e": { "name": "Magnetic Flux Density B", "unit": "T", "symbol": "B", "norm": 1, }, "magnetic vector potential e": { "name": "Magnetic Vector Potential A", "unit": "Wb", "symbol": "A", "norm": 1, }, "magnetic field strength e": { "name": "Magnetic Field H", "unit": "A/m", "symbol": "H", "norm": 1, }, "current density e": { "name": "Current Density J", "unit": "A/mm2", "symbol": "J", "norm": 1, }, } comp_ext = ["x", "y", "z"] sol_list = [] #for key, value in pt_data.items(): for key, value in cell_data.items(): if key in store_dict.keys(): #siz = value.shape[1] siz = value[0].shape[1] if siz > 3: print("Some Message") siz = 3 components = [] comp_name = [] values = np_append(value[0], value[1], axis=0) for i in range(siz): if siz == 1: ext = "" else: ext = comp_ext[i] data = DataTime( name=store_dict[key]["name"] + ext, unit=store_dict[key]["unit"], symbol=store_dict[key]["symbol"] + ext, axes=[Indices], #values=value[:, i], values=values[:, i], normalizations={"ref": store_dict[key]["norm"]}, ) components.append(data) comp_name.append("comp_" + ext) if siz == 1: field = components[0] sol_list.append( SolutionData( field=field, #type_cell="point", type_cell="triangle", label=store_dict[key]["symbol"], )) else: comps = {} for i in range(siz): comps[comp_name[i]] = components[i] field = VectorField(name=store_dict[key]["name"], symbol=store_dict[key]["symbol"], components=comps) sol_list.append( SolutionVector( field=field, #type_cell="point", type_cell="triangle", label=store_dict[key]["symbol"], )) meshsol.solution = sol_list output.mag.meshsolution = meshsol return True
def gen_input(self): """Generate the input for the magnetic module (electrical output) Parameters ---------- self : InputCurrent An InputCurrent object """ # Get the simulation if isinstance(self.parent, Simulation): simu = self.parent elif isinstance(self.parent.parent, Simulation): simu = self.parent.parent else: raise InputError( "ERROR: InputCurrent object should be inside a Simulation object") # Create the correct Output object output = OutElec() # Set discretization Time, Angle = self.comp_axes(simu.machine, self.N0) output.time = Time output.angle = Angle # Number of winding phases for stator/rotor qs = len(simu.machine.stator.get_name_phase()) qr = len(simu.machine.rotor.get_name_phase()) output.N0 = self.N0 output.felec = self.comp_felec() # TODO introduce set_felec(slip) # Load and check Is if qs > 0: if self.Is is None: if self.Id_ref is None and self.Iq_ref is None: raise InputError( "ERROR: InputCurrent.Is, InputCurrent.Id_ref, and InputCurrent.Iq_ref missing" ) else: output.Id_ref = self.Id_ref output.Iq_ref = self.Iq_ref output.Is = None else: Is = self.Is.get_data() if not isinstance(Is, ndarray) or Is.shape != (self.Nt_tot, qs): raise InputError( "ERROR: InputCurrent.Is must be a matrix with the shape " + str((self.Nt_tot, qs)) + " (len(time), stator phase number), " + str(Is.shape) + " returned") # Creating the data object Phase = Data1D( name="phase", unit="", values=gen_name(qs, is_add_phase=True), is_components=True, ) output.Is = DataTime( name="Stator current", unit="A", symbol="Is", axes=[Phase, Time], values=transpose(Is), ) # Compute corresponding Id/Iq reference Idq = n2dq( transpose(output.Is.values), 2 * pi * output.felec * output.time.get_values(is_oneperiod=False), is_dq_rms=True, ) output.Id_ref = mean(Idq[:, 0]) output.Iq_ref = mean( Idq[:, 1]) # TODO use of mean has to be documented # Load and check Ir is needed if qr > 0: if self.Ir is None: raise InputError("ERROR: InputCurrent.Ir missing") else: Ir = self.Ir.get_data() if not isinstance(Ir, ndarray) or Ir.shape != (self.Nt_tot, qr): raise InputError( "ERROR: InputCurrent.Ir must be a matrix with the shape " + str((self.Nt_tot, qr)) + " (len(time), rotor phase number), " + str(Ir.shape) + " returned") # Creating the data object Phase = Data1D( name="phase", unit="", values=gen_name(qr, is_add_phase=True), is_components=True, ) output.Ir = DataTime( name="Rotor current", unit="A", symbol="Ir", axes=[Phase, Time], values=transpose(Ir), ) # Load and check alpha_rotor and N0 if self.angle_rotor is None and self.N0 is None: raise InputError( "ERROR: InputCurrent.angle_rotor and InputCurrent.N0 can't be None at the same time" ) if self.angle_rotor is not None: output.angle_rotor = self.angle_rotor.get_data() if (not isinstance(output.angle_rotor, ndarray) or len(output.angle_rotor.shape) != 1 or output.angle_rotor.size != self.Nt_tot): # angle_rotor should be a vector of same length as time raise InputError( "ERROR: InputCurrent.angle_rotor should be a vector of the same length as time, " + str(output.angle_rotor.shape) + " shape found, " + str(self.Nt_tot) + " expected") if self.rot_dir is None or self.rot_dir not in [-1, 1]: # Enforce default rotation direction simu.parent.geo.rot_dir = None else: simu.parent.geo.rot_dir = self.rot_dir if self.angle_rotor_initial is None: # Enforce default initial position output.angle_rotor_initial = 0 else: output.angle_rotor_initial = self.angle_rotor_initial if self.Tem_av_ref is not None: output.Tem_av_ref = self.Tem_av_ref if simu.parent is None: raise InputError( "ERROR: The Simulation object must be in an Output object to run") # Save the Output in the correct place simu.parent.elec = output
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_U : SciDataTool.Classes.DataND.DataND Unit magnetomotive force (Na,Nt) WF : SciDataTool.Classes.DataND.DataND Winding functions (qs,Na) """ # Get stator winding number of phases qs = self.winding.qs # Get spatial symmetry per_a, _, _, _ = self.comp_periodicity() # Define the space dicretization angle = linspace(0, 2 * pi / per_a, Na, endpoint=False) # Define the time dicretization time = linspace(0, 1 / freq, Nt, endpoint=False) # Compute the winding function and mmf if self.winding is None or self.winding.conductor is None: wf = zeros((qs, Na)) else: wf = self.comp_wind_function(angle=angle, per_a=per_a) # Compute unit current function of time applying constant Id=1 Arms, Iq=0 Idq = zeros((Nt, 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 = Data1D( name="angle", unit="rad", symmetries={"period": per_a}, values=angle, normalizations={"space_order": self.get_pole_pair_number()}, ) Phase = Data1D( name="phase", unit="", values=gen_name(qs), is_components=True, ) MMF_U = DataTime( name="Unit MMF", unit="A", symbol="Magnitude", axes=[Time, Angle], values=mmf_u, ) WF = DataTime( name="Winding Functions", unit="A", symbol="Magnitude", axes=[Phase, Angle], values=wf, ) return MMF_U, WF
def comp_force(self, output, axes_dict): """Compute the air-gap surface force based on Maxwell Tensor (MT). Parameters ---------- self : ForceMT A ForceMT object output : Output an Output object (to update) axes_dict: {Data} Dict of axes used for force calculation """ # Get time and angular axes Angle = axes_dict["Angle"] Time = axes_dict["Time"] # Import angular vector from Angle Data object _, is_antiper_a = Angle.get_periodicity() angle = Angle.get_values( is_oneperiod=self.is_periodicity_a, is_antiperiod=is_antiper_a and self.is_periodicity_a, ) # Import time vector from Time Data object _, is_antiper_t = Time.get_periodicity() time = Time.get_values( is_oneperiod=self.is_periodicity_t, is_antiperiod=is_antiper_t and self.is_periodicity_t, ) # Load magnetic flux Brphiz = output.mag.B.get_rphiz_along( "time=axis_data", "angle=axis_data", axis_data={ "time": time, "angle": angle }, ) Br = Brphiz["radial"] Bt = Brphiz["tangential"] Bz = Brphiz["axial"] # Magnetic void permeability mu_0 = 4 * pi * 1e-7 # Compute AGSF with MT formula Prad = (Br * Br - Bt * Bt - Bz * Bz) / (2 * mu_0) Ptan = Br * Bt / mu_0 Pz = Br * Bz / mu_0 # Store Maxwell Stress tensor P in VectorField # Build axes list axes_list = list() for axe in output.mag.B.get_axes(): if axe.name == Angle.name: axes_list.append(Angle) elif axe.name == Time.name: axes_list.append(Time) else: axes_list.append(axe) # Build components list components = {} if not np_all((Prad == 0)): Prad_data = DataTime( name="Airgap radial surface force", unit="N/m2", symbol="P_r", axes=axes_list, values=Prad, ) components["radial"] = Prad_data if not np_all((Ptan == 0)): Ptan_data = DataTime( name="Airgap tangential surface force", unit="N/m2", symbol="P_t", axes=axes_list, values=Ptan, ) components["tangential"] = Ptan_data if not np_all((Pz == 0)): Pz_data = DataTime( name="Airgap axial surface force", unit="N/m2", symbol="P_z", axes=axes_list, values=Pz, ) components["axial"] = Pz_data # Store components in VectorField output.force.P = VectorField(name="Magnetic airgap surface force", symbol="P", components=components)
def comp_force_nodal(self, output, axes_dict): """Run the nodal forces calculation based on a tensor. from publications: Parameters ---------- self : ForceTensor A ForceTensor object output : Output an Output object (to update) """ dim = 2 Time = axes_dict["Time"] Nt_tot = Time.get_length() # Number of time step meshsolution_mag = output.mag.meshsolution # Comes from FEMM simulation # Select the target group (stator, rotor ...) meshsolution_group = meshsolution_mag.get_group(self.group) # TODO before: Check if is_same_mesh is True mesh = meshsolution_group.get_mesh() # New meshsolution object for output, that could be different from the one inputed meshsolution = MeshSolution(mesh=[mesh.copy()], is_same_mesh=True, dimension=dim) # Load magnetic flux B and H and mu objects B_sol = meshsolution_group.get_solution(label="B") H_sol = meshsolution_group.get_solution(label="H") mu_sol = meshsolution_group.get_solution(label="\mu") # Import time vector from Time Data object if self.is_periodicity_t is not None: is_periodicity_t = self.is_periodicity_t is_periodicity_t, is_antiper_t = Time.get_periodicity() time = Time.get_values( is_oneperiod=is_periodicity_t, is_antiperiod=is_antiper_t and is_periodicity_t, ) # Load magnetic flux B and H of size (Nt_tot, nb_elem, dim) and mu (Nt_tot, nb_elem) resultB = B_sol.field.get_xyz_along( "indice", "time=axis_data", axis_data={"time": time}, ) indice = resultB["indice"] # Store elements indices Bx = resultB["comp_x"] By = resultB["comp_y"] B = np.stack((Bx, By), axis=2) resultH = H_sol.field.get_xyz_along( "indice", "time=axis_data", axis_data={"time": time}, ) Hx = resultH["comp_x"] Hy = resultH["comp_y"] H = np.stack((Hx, Hy), axis=2) resultmu = mu_sol.field.get_along( "indice", "time=axis_data", axis_data={"time": time}, ) mu = resultmu["\\mu"] # Move time axis at the end for clarity purpose B = np.moveaxis(B, 0, -1) H = np.moveaxis(H, 0, -1) mu = np.moveaxis(mu, 0, -1) # Loop on elements and nodes for nodal forces f, connect = self.element_loop(mesh, B, H, mu, indice, dim, Nt_tot) indices_nodes = np.sort(np.unique(connect)) Indices_Point = Data1D(name="indice", values=indices_nodes, is_components=True) # Time axis goes back to first axis f = np.moveaxis(f, -1, 0) components = {} fx_data = DataTime( name="Nodal force (x)", unit="N", symbol="Fx", axes=[Time, Indices_Point], values=f[..., 0], ) components["comp_x"] = fx_data fy_data = DataTime( name="Nodal force (y)", unit="N", symbol="Fy", axes=[Time, Indices_Point], values=f[..., 1], ) components["comp_y"] = fy_data vec_force = VectorField(name="Nodal forces", symbol="F", components=components) solforce = SolutionVector(field=vec_force, type_cell="node", label="F") meshsolution.solution.append(solforce) out_dict = dict() out_dict["meshsolution"] = meshsolution return out_dict
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