def comp_3oct_spec(self): """ Method to compute third-octave spectrum according to ISO""" freqs = Data1D(name='freqs', unit='Hertz') if self.is_stationary == True: third_spec, freqs.values = oct3spec(self.signal.values, self.fs) np.squeeze(third_spec) self.third_spec = DataFreq(symbol="3oct", axes=[freqs], values=third_spec, name="Third-octave spectrum", unit="dB ref 2e-05") elif self.is_stationary == False: time = Data1D(name='time', unit='s') third_spec, freqs.values, time.values = calc_third_octave_levels( self.signal.values, self.fs) np.squeeze(third_spec) self.third_spec = DataFreq(symbol="3oct", axes=[freqs, time], values=third_spec, name="Third-octave spectrum", unit="dB ref 2e-05")
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 time_to_freq(self): """Performs the Fourier Transform and stores the resulting field in a DataFreq object. Parameters ---------- self : DataTime a DataTime object Returns ------- a DataFreq object """ axes_str = [axis.name for axis in self.axes] axes_str = [ "freqs" if axis_name == "time" else axis_name for axis_name in axes_str ] axes_str = [ "wavenumber" if axis_name == "angle" 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 DataFreq( name=self.name, unit=self.unit, symbol=self.symbol, axes=Axes, values=values, )
def solve_PWM(self, output, freq_max=None, is_dqh_freq=False): """Get stator current harmonics due to PWM harmonics TODO: validation with transient FEA simulation Parameters ---------- self : EEC_PMSM an EEC_PMSM object output: Output An Output object freq_max: float Maximum frequency is_dqh_freq: bool True to consider frequencies in dqh frame Returns ------ Is_PWM : DataFreq Stator current harmonics as DataFreq """ self.get_logger().info("Calculating PWM current harmonics") # Get stator winding phase number qs = output.simu.machine.stator.winding.qs # Get PWM frequencies in abc frame freqs_n = output.elec.Us.axes_df[0].values # Get stator voltage harmonics in dqh frame Us_PWM = output.elec.get_Us(is_dqh=True, is_harm_only=True, is_freq=True) result_dqh = Us_PWM.get_along("freqs<" + str(freq_max), "phase") Udqh_val = result_dqh[Us_PWM.symbol] freqs_dqh = result_dqh["freqs"] # # Plot Us_n and U_dqh # output.elec.Us.plot_2D_Data("freqs=[0,10000]", "phase[0]") # Us_PWM.plot_2D_Data("freqs=[0,5000]", "phase[0]") # Filter Udqh_val zeros values Udqh_norm = np.linalg.norm(Udqh_val, axis=-1) Iamp = Udqh_norm > 1e-6 * Udqh_norm.max() freqs_dqh = freqs_dqh[Iamp] Udqh_val = Udqh_val[Iamp, :] # Get parameters from eec par = self.parameters # Init current harmonics matrix Idqh_val = np.zeros((freqs_dqh.size, qs), dtype=complex) if is_dqh_freq: # Take dqh frequency values fn_dqh = freqs_dqh else: # Look for frequency value in n frame for each frequency in dqh frame fn_dqh = np.zeros(freqs_dqh.size) fn_pos = freqs_dqh + par["felec"] fn_neg = freqs_dqh - par["felec"] for ii, (fpos, fneg) in enumerate(zip(fn_pos, fn_neg)): fn_ii = None jj = 0 while fn_ii is None and jj < freqs_n.size: if (np.abs(fpos - freqs_n[jj]) < 1e-4 or np.abs(fneg - freqs_n[jj]) < 1e-4): fn_ii = freqs_n[jj] elif (np.abs(fpos + freqs_n[jj]) < 1e-4 or np.abs(fneg + freqs_n[jj]) < 1e-4): fn_ii = -freqs_n[jj] else: jj += 1 if fn_ii is None: raise Exception("Cannot map dqh frequency back to n frequency") else: fn_dqh[ii] = fn_ii # Calculate impedances we = 0 * 2 * np.pi * par["felec"] # in static frame wh = 2 * np.pi * fn_dqh a = par["R1"] + 1j * wh * par["Ld"] b = -we * par["Lq"] c = we * par["Ld"] d = par["R1"] + 1j * wh * par["Lq"] det = a * d - c * b # Calculate current harmonics # Calculate Id Idqh_val[:, 0] = (d * Udqh_val[:, 0] - b * Udqh_val[:, 1]) / det # Calculate Iq Idqh_val[:, 1] = (-c * Udqh_val[:, 0] + a * Udqh_val[:, 1]) / det # if np.any(np.abs(Idqh_val[:, 0]) > 20) or np.any(np.abs(Idqh_val[:, 1]) > 20): # print("problem") # # Check # Ud = a * Idqh_val[If, 0] + b * Idqh_val[If, 1] # Uq = c * Idqh_val[If, 0] + d * Idqh_val[If, 1] # check_d = Ud - Udqh_val[If, 0] # check_q = Uq - Udqh_val[If, 1] # Create frequency axis Freqs_PWM = Us_PWM.axes[0] norm_freq = dict() if Freqs_PWM.normalizations is not None and len( Freqs_PWM.normalizations) > 0: for key, val in Freqs_PWM.normalizations.items(): norm_freq[key] = val.copy() Freqs = Data1D( name=Freqs_PWM.name, symbol=Freqs_PWM.symbol, unit=Freqs_PWM.unit, values=freqs_dqh, normalizations=norm_freq, ) # Create DataFreq in DQH Frame Is_PWM_dqh = DataFreq( name="Stator current", unit="A", symbol="I_s", axes=[Freqs, Us_PWM.axes[1].copy()], values=Idqh_val, ) # # Plot PWM current in dqh frame over frequency # Is_PWM_dqh.plot_2D_Data("freqs=[0,20000]", "phase[]") # Convert I_dqh spectrum back to stator frame Is_PWM_n = dqh2n_DataFreq( Is_PWM_dqh, n=qs, phase_dir=output.elec.phase_dir, current_dir=output.elec.current_dir, felec=output.elec.OP.felec, is_n_rms=False, ) # Reduce current to original voltage frequencies Is_PWM_n = Is_PWM_n.get_data_along("freqs=" + str(freqs_n.tolist()), "phase") return Is_PWM_n
def dqh2abc(Z_dqh, freqs, current_dir, felec): """dqh to alpha-beta-gamma coordinate transformation Parameters ---------- Z_dqh : ndarray matrix (N x 3) of dqh - reference frame values freqs : ndarray frequency array in dqh frame [Hz] current_dir: int direction of current waveform: +/-1 (-1 clockwise) to enforce felec: float fundamental electrical frequency [Hz] Returns ------- Z_abc : ndarray transformed array freqs_abc : ndarray frequency array in static frame [Hz] """ # Create Frequency axes Freqs = Data1D(name="freqs", unit="Hz", values=freqs) Freqs0 = Data1D(name="freqs", unit="Hz", values=np.array([felec])) # Build DataFreq for Zd and Zq df_d = DataFreq(axes=[Freqs], values=Z_dqh[:, 0]) df_q = DataFreq(axes=[Freqs], values=Z_dqh[:, 1]) # Build DataFreq for cos(angle_elec) / sin(angle_elec) df_cos = DataFreq(axes=[Freqs0], values=np.array([1])) df_sin = DataFreq(axes=[Freqs0], values=np.array([-current_dir * 1j])) df_sin_neg = DataFreq(axes=[Freqs0], values=np.array([current_dir * 1j])) # Calculate Za with convolution (Za = Zd*cos - Zq*sin) df_a = df_d.conv(df_cos) df_a = df_a.sum(df_q.conv(df_sin_neg)) # Calculate Zb with convolution (Zb = Zd*sin + Zq*cos) df_b = df_d.conv(df_sin) df_b = df_b.sum(df_q.conv(df_cos)) # Merge frequencies freqs_abc, Ifab, Ifc = union1d_tol(df_a.axes[0].values, freqs, return_indices=True, tol=1e-4, is_abs_tol=False) # Rebuild abc values Z_abc = np.zeros((freqs_abc.size, 3), dtype=complex) Z_abc[Ifab, 0] = df_a.values Z_abc[Ifab, 1] = df_b.values Z_abc[Ifc, 2] = Z_dqh[:, 2] # c axis same as homopolar axis # Only return non zero values Z_abc_norm = np.linalg.norm(Z_abc, axis=1) I0 = Z_abc_norm > 1e-10 return Z_abc[I0, :], freqs_abc[I0]
def dqh2n_DataFreq(data_dqh, n, current_dir, felec, is_n_rms=False, phase_dir=None): """dqh to n phase coordinate transformation of DataFreq object Parameters ---------- data_dqh : DataFreq data object containing values over time in dqh frame n: int number of phases current_dir: int direction of current waveform: +/-1 (-1 clockwise) to enforce felec: float fundamental electrical frequency [Hz] 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 : DataFreq data object containing values over time and phase axes """ # Check if input data object is compliant with dqh transformation _check_data(data_dqh, is_freq=True) # Get values for one time period converted in electrical angle and for all phases result_dqh = data_dqh.get_along("freqs", "phase", is_squeeze=False) # Convert values to dqh frame Z_abc, freqs_abc = dqh2n( Z_dqh=result_dqh[data_dqh.symbol], freqs=result_dqh["freqs"], phase_dir=phase_dir, current_dir=current_dir, felec=felec, n=n, is_n_rms=is_n_rms, ) # Create frequency axis norm_freq = dict() ax_freq = data_dqh.axes[0] if ax_freq.normalizations is not None and len(ax_freq.normalizations) > 0: for key, val in ax_freq.normalizations.items(): norm_freq[key] = val.copy() Freqs = Data1D(name="freqs", unit="Hz", values=freqs_abc, normalizations=norm_freq) # 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 DataFreq object in dqh frame data_n = DataFreq( name=data_dqh.name.replace(" in DQH frame", ""), unit=data_dqh.unit, symbol=data_dqh.symbol, values=Z_abc, axes=[Freqs, Phase], normalizations=normalizations, is_real=data_dqh.is_real, ) return data_n
def abc2dqh(Z_abc, freqs, current_dir, felec): """alpha-beta-gamma to dqh coordinate transformation Parameters ---------- Z_abc : ndarray matrix (N x 3) of phases values in abc static frame freqs : ndarray frequency array in static frame [Hz] current_dir: int direction of current waveform: +/-1 (-1 clockwise) to enforce felec: float fundamental electrical frequency [Hz] Returns ------- Z_dqh : ndarray transformed matrix (N x 3) of dqh equivalent values freqs_dqh : ndarray frequency array in dqh frame [Hz] """ # Create Frequency axes Freqs = Data1D(name="freqs", unit="Hz", values=freqs) Freqs0 = Data1D(name="freqs", unit="Hz", values=np.array([felec])) # Build DataFreq for Za and Zb df_a = DataFreq(axes=[Freqs], values=Z_abc[:, 0]) df_b = DataFreq(axes=[Freqs], values=Z_abc[:, 1]) # Build DataFreq for cos(angle_elec) / sin(angle_elec) df_cos = DataFreq(axes=[Freqs0], values=np.array([1])) df_sin = DataFreq(axes=[Freqs0], values=np.array([-current_dir * 1j])) df_sin_neg = DataFreq(axes=[Freqs0], values=np.array([current_dir * 1j])) # Calculate Zd in spectrum domain (Zd = Za*cos + Zb*sin) df_d = df_a.conv(df_cos) df_d = df_d.sum(df_b.conv(df_sin)) # Calculate Zq in spectrum domain (Zq = -Za*sin + Zb*cos) df_q = df_a.conv(df_sin_neg) df_q = df_q.sum(df_b.conv(df_cos)) # Merge frequencies freqs_dqh, Ifdq, Ifh = union1d_tol(df_d.axes[0].values, freqs, return_indices=True) # Rebuild dqh values Z_dqh = np.zeros((freqs_dqh.size, 3), dtype=complex) Z_dqh[Ifdq, 0] = df_d.values Z_dqh[Ifdq, 1] = df_q.values Z_dqh[Ifh, 2] = Z_abc[:, 2] # Homopolar axis same as c axis # Only return non zero values Z_dqh_norm = np.linalg.norm(Z_dqh, axis=1) I0 = Z_dqh_norm > 1e-10 return Z_dqh[I0, :], freqs_dqh[I0]
def n2dqh_DataFreq(data_n, current_dir, felec, is_dqh_rms=True, phase_dir=None): """n phases to dqh equivalent coordinate transformation of DataFreq/DataTime object Parameters ---------- data_n : DataFreq/DataTime data object containing values over freqs 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 current_dir: int direction of current waveform: +/-1 (-1 clockwise) to enforce felec: float fundamental electrical frequency [Hz] Returns ------- data_dqh : DataFreq 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_DataFreq(data_n) else: # Only check if input data object is compliant with dqh transformation _check_data(data_n, is_freq=True) # Get values for one time period converted in electrical angle and for all phases result_n = data_n.get_along("freqs", "phase", is_squeeze=False) # Convert values to dqh frame Z_dqh, freqs_dqh = n2dqh( Z_n=result_n[data_n.symbol], freqs=result_n["freqs"], phase_dir=phase_dir, current_dir=current_dir, felec=felec, is_dqh_rms=is_dqh_rms, ) # Create frequency axis norm_freq = dict() ax_freq = data_n.axes[0] if ax_freq.normalizations is not None and len(ax_freq.normalizations) > 0: for key, val in ax_freq.normalizations.items(): norm_freq[key] = val.copy() Freqs = Data1D(name="freqs", unit="Hz", values=freqs_dqh, normalizations=norm_freq) # 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 DataFreq object in dqh frame data_dqh = DataFreq( name=data_n.name + " in DQH frame", unit=data_n.unit, symbol=data_n.symbol, values=Z_dqh, axes=[Freqs, axis_dq], normalizations=normalizations, is_real=data_n.is_real, ) return data_dqh
def comp_AGSF_transfer(self, output, rnoise=None): """Method to compute Air-Gap Surface Force transfer from middle air-gap radius to stator bore radius. From publication: PILE, Raphaël, LE BESNERAIS, Jean, PARENT, Guillaume, et al. Analytical study of air-gap surface force–application to electrical machines. Open Physics, 2020, vol. 18, no 1, p. 658-673. https://www.degruyter.com/view/journals/phys/18/1/article-p658.xml Parameters ---------- self: Force a Force object output : Output an Output object """ Rag = output.force.Rag Rsbo = output.simu.machine.stator.Rint AGSF = output.force.AGSF arg_list = ["freqs", "wavenumber"] result_freq = AGSF.get_rphiz_along(*arg_list) Prad_wr = result_freq["radial"] Ptan_wr = result_freq["tangential"] wavenumber = result_freq["wavenumber"] freqs = result_freq["freqs"] Nf = len(freqs) Ratio = Rag / Rsbo # Transfer coefficients Eq. (46) Sn = (Ratio** 2) * (power(Ratio, wavenumber) + power(Ratio, -wavenumber)) / 2 Cn = (Ratio** 2) * (power(Ratio, wavenumber) - power(Ratio, -wavenumber)) / 2 # Noise filtering (useful with magnetic FEA) if rnoise is not None: Inoise = where(abs(wavenumber) > rnoise)[0] Sn[Inoise] = 1 Cn[Inoise] = 0 XSn = repeat(Sn[..., newaxis], Nf, axis=1).transpose() XCn = repeat(Cn[..., newaxis], Nf, axis=1).transpose() # Transfer law Eq. (45) Prad_wr_TR = multiply(XSn, Prad_wr) + 1j * multiply(XCn, Ptan_wr) Ptan_wr_TR = multiply(XSn, Ptan_wr) - 1j * multiply(XCn, Prad_wr) Datafreqs = Data1D(name="freqs", values=freqs) Datawavenumbers = Data1D(name="wavenumber", values=wavenumber) axes_list = [Datafreqs, Datawavenumbers] AGSF_TR = VectorField( name="Air gap Surface Force", symbol="AGSF", ) AGSF_TR.components["radial"] = DataFreq( name="Radial AGSF", unit="N/m²", symbol="AGSF_r", axes=axes_list, values=Prad_wr_TR, ) AGSF_TR.components["tangential"] = DataFreq( name="Tangential AGSF", unit="N/m²", symbol="AGSF_t", axes=axes_list, values=Ptan_wr_TR, ) # Replace original AGSF output.force.AGSF = AGSF_TR output.force.Rag = Rsbo
def comp_loss_density(self, meshsolution): """ Compute the losses density (per kg) according to the following model equation: Loss = C0*f*B^C1 + C2*(f*B)^C3 + C4*(f*B)^C4 Parameters ---------- self : LossModelBertotti a LossModelBertotti object field : DataND a DataND object that contains the flux density values Returns ------- loss_density: DataND a DataND object of the normalized losses """ # get the parameters Coeff = list( [self.k_hy, self.alpha_hy, self.k_ed, self.alpha_ed, self.k_ex, self.alpha_ex] ) F_REF = self.F_REF B_REF = self.B_REF # filter needed mesh group sol = meshsolution.get_solution(label="B") # TODO Calculate principle axes and transform for exponentials other than 2 # TODO maybe use rad. and tan. comp. as intermediate solution # loop over field components HY, ED, EX = None, None, None for component in sol.field.components.values(): axes_names = ["freqs" if x.name == "time" else x.name for x in component.axes] # TODO add filter function to limit max. order of harmonics mag_dict = component.get_magnitude_along(*axes_names) symbol = component.symbol # TODO better data check (axis size, ...) freqs = mag_dict["freqs"] # freqs[freqs<0] = 0 # to only regard positive freqs k = 1 # 1 / sqrt(2) f_norm = abs(freqs[:, newaxis] / F_REF) B_norm = k * mag_dict[symbol] / B_REF # factor 1 / sqrt(2) to account for SciDataTool FFT of double sided spectrum # TODO is this factor also true for powers other than 2 ? HY_ = Coeff[0] * f_norm * B_norm ** Coeff[1] ED_ = Coeff[2] * (f_norm * B_norm) ** Coeff[3] EX_ = Coeff[4] * (f_norm * B_norm) ** Coeff[5] HY = HY_ if HY is None else HY + HY_ ED = ED_ if ED is None else ED + ED_ EX = EX_ if EX is None else EX + EX_ loss = HY + ED + EX # setup loss density data Freq = Data1D(name="freqs", unit="", values=freqs) axes = [Freq if x.name == "time" else x for x in component.axes] loss_density = DataFreq( name="Loss Density", unit="W/kg", symbol="LossDens", axes=axes, values=loss ) # setup loss density components data Comps = Data1D( name="Components", unit="W/kg", values=["Hysteresis", "Eddy", "Excess"], is_components=True, ) axes = [axis.copy() for axis in axes] axes.append(Comps) comps_data = stack([HY, ED, EX], axis=len(axes) - 1) loss_density_comps = DataFreq( name="Loss Density Components", unit="W/kg", symbol="LossDensComps", axes=axes, values=comps_data, ) return loss_density, loss_density_comps