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 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 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 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 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 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 get_solution(self, indice=None): """Return a copy of the solution with the option to only include specified indice. Parameters ---------- self : SolutionData a SolutionData object indice : list list of indice, if list is empty or None all indice are included Returns ------- solution: SolutionMat solution """ logger = self.get_logger() # create copy to directly manipulate data solution = self.copy() if indice: axes = solution.field.axes axes_names = solution.get_axes_list()[0] ax_idx = axes_names.index("indice") org_indice = axes[ax_idx].get_values() if set(indice) - set(org_indice): logger.warning( "At least one input indice is not part of the solution. " + "Respective indice will be skipped.") # skip indice that are not part of the solution new_indice = [ii for ii in indice if ii in org_indice] # create requested axes list to get field values (see SciDataTool slicing ref.) args = [name for name in axes_names] args[ax_idx] += "=axis_data" # get the field values field_dict = solution.field.get_along(*args, axis_data={"indice": new_indice}) # set new indice axis axes[ax_idx] = Data1D( values=new_indice, is_components=axes[ax_idx].is_components, symmetries=axes[ax_idx].symmetries, symbol=axes[ax_idx].symbol, name=axes[ax_idx].name, unit=axes[ax_idx].unit, normalizations=axes[ax_idx].normalizations, ) # set new field data solution.field.values = field_dict[self.field.symbol] return solution
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_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 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 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 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 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 get_data_along(self, *args, unit="SI", is_norm=False, axis_data=[]): """Returns the sliced or interpolated version of the data, using conversions and symmetries if needed. Parameters ---------- self: Data a Data object *args: list of strings List of axes requested by the user, their units and values (optional) unit: str Unit requested by the user ("SI" by default) is_norm: bool Boolean indicating if the field must be normalized (False by default) axis_data: list list of ndarray corresponding to user-input data Returns ------- a DataND object """ # Dynamic import to avoid loop module = __import__("SciDataTool.Classes.DataND", fromlist=["DataND"]) DataND = getattr(module, "DataND") results = self.get_along(*args) values = results.pop(self.symbol) del results["axes_dict_other"] del results["axes_list"] Axes = [] for axis_name in results.keys(): if len(results[axis_name]) > 1: for axis in self.axes: if axis.name == axis_name: name = axis.name is_components = axis.is_components axis_values = results[axis_name] unit = axis.unit elif axis_name in axes_dict: if axes_dict[axis_name][0] == axis.name: name = axis_name is_components = axis.is_components axis_values = results[axis_name] unit = axes_dict[axis_name][2] elif axis_name in rev_axes_dict: if rev_axes_dict[axis_name][0] == axis.name: name = axis_name is_components = axis.is_components axis_values = results[axis_name] unit = rev_axes_dict[axis_name][2] Axes.append( Data1D( name=name, unit=unit, values=axis_values, is_components=is_components, ) ) return DataND( name=self.name, unit=self.unit, symbol=self.symbol, axes=Axes, values=values, is_real=self.is_real, )
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_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 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 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 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_flux_airgap(self, output, axes_dict, Is=None, Ir=None): """Build and solve FEMM model to calculate and store magnetic quantities Parameters ---------- self : MagFEMM a MagFEMM object output : Output an Output object axes_dict: {Data} Dict of axes used for magnetic calculation Returns ------- out_dict: dict Dict containing the following quantities: Br : ndarray Airgap radial flux density (Nt,Na) [T] Bt : ndarray Airgap tangential flux density (Nt,Na) [T] Tem : ndarray Electromagnetic torque over time (Nt,) [Nm] Phi_wind_stator : ndarray Stator winding flux (qs,Nt) [Wb] Phi_wind : dict Dict of winding fluxlinkage with respect to Machine.get_lam_list_label (qs,Nt) [Wb] meshsolution: MeshSolution MeshSolution object containing magnetic quantities B, H, mu for each time step """ # Init output out_dict = dict() if output.mag.internal is None: output.mag.internal = OutMagFEMM() # Get time and angular axes Angle = axes_dict["angle"] Time = axes_dict["time"] # Set the angular symmetry factor according to the machine and check if it is anti-periodic sym, is_antiper_a = Angle.get_periodicity() # Import angular vector from Data object angle = Angle.get_values( is_oneperiod=self.is_periodicity_a, is_antiperiod=is_antiper_a and self.is_periodicity_a, ) Na = angle.size # Check if the time axis is anti-periodic _, is_antiper_t = Time.get_periodicity() # Number of time steps time = Time.get_values( is_oneperiod=self.is_periodicity_t, is_antiperiod=is_antiper_t and self.is_periodicity_t, ) Nt = time.size # Get rotor angular position angle_rotor = output.get_angle_rotor()[0:Nt] # Setup the FEMM simulation # Geometry building and assigning property in FEMM # Instanciate a new FEMM femm = _FEMMHandler() output.mag.internal.handler_list.append(femm) if self.import_file is None: self.get_logger().debug("Drawing machine in FEMM...") FEMM_dict = draw_FEMM( femm, output, is_mmfr=self.is_mmfr, is_mmfs=self.is_mmfs, sym=sym, is_antiper=is_antiper_a, type_calc_leakage=self.type_calc_leakage, is_remove_ventS=self.is_remove_ventS, is_remove_ventR=self.is_remove_ventR, is_remove_slotS=self.is_remove_slotS, is_remove_slotR=self.is_remove_slotR, type_BH_stator=self.type_BH_stator, type_BH_rotor=self.type_BH_rotor, kgeo_fineness=self.Kgeo_fineness, kmesh_fineness=self.Kmesh_fineness, user_FEMM_dict=self.FEMM_dict_enforced, path_save=self.get_path_save_fem(output), is_sliding_band=self.is_sliding_band, transform_list=self.transform_list, rotor_dxf=self.rotor_dxf, stator_dxf=self.stator_dxf, ) else: self.get_logger().debug("Reusing the FEMM file: " + self.import_file) FEMM_dict = self.FEMM_dict_enforced # Init flux arrays in out_dict out_dict["Br"] = zeros((Nt, Na)) out_dict["Bt"] = zeros((Nt, Na)) # Init torque array in out_dict out_dict["Tem"] = zeros((Nt)) # Init lamination winding flux list of arrays in out_dict machine = output.simu.machine out_dict["Phi_wind"] = {} for label, lam in zip(machine.get_lam_list_label(), machine.get_lam_list()): if hasattr(lam, "winding") and lam.winding is not None: qs = lam.winding.qs # Winding phase number out_dict["Phi_wind"][label] = zeros((Nt, qs)) # delete 'Phi_wind' if empty if not out_dict["Phi_wind"]: out_dict.pop("Phi_wind") # Solve for all time step and store all the results in out_dict if self.nb_worker > 1: # A Femm handler will be created for each worker femm.closefemm() output.mag.internal.handler_list.remove(femm) # With parallelization B_elem, H_elem, mu_elem, A_node, meshFEMM, groups = self.solve_FEMM_parallel( femm, output, out_dict, FEMM_dict=FEMM_dict, sym=sym, Nt=Nt, angle=angle, Is=Is, Ir=Ir, angle_rotor=angle_rotor, ) else: # Without parallelization B_elem, H_elem, mu_elem, A_node, meshFEMM, groups = self.solve_FEMM( femm, output, out_dict, FEMM_dict=FEMM_dict, sym=sym, Nt=Nt, angle=angle, Is=Is, Ir=Ir, angle_rotor=angle_rotor, is_close_femm=self.is_close_femm, filename=self.import_file, ) # Store FEMM_dict in out_dict if FEMM file is not imported if self.import_file is None: output.mag.internal.FEMM_dict = FEMM_dict # Store stator winding flux if STATOR_LAB + "-0" in out_dict["Phi_wind"].keys(): out_dict["Phi_wind_stator"] = out_dict["Phi_wind"][STATOR_LAB + "-0"] # Store mesh data & solution if self.is_get_meshsolution and B_elem is not None: # Define axis Time = Time.copy() indices_cell = meshFEMM[0].cell["triangle"].indice Indices_Cell = Data1D(name="indice", values=indices_cell, is_components=True) axis_list = [Time, Indices_Cell] B_sol = build_solution_vector( field=B_elem, axis_list=axis_list, name="Magnetic Flux Density", symbol="B", unit="T", ) H_sol = build_solution_vector( field=H_elem, axis_list=axis_list, name="Magnetic Field", symbol="H", unit="A/m", ) mu_sol = build_solution_data( field=mu_elem, axis_list=axis_list, name="Magnetic Permeability", symbol="\mu", unit="H/m", ) indices_nodes = meshFEMM[0].node.indice Indices_Nodes = Data1D(name="indice", values=indices_nodes, is_components=True) axis_list_node = [Time, Indices_Nodes] A_sol = build_solution_data( field=A_node, axis_list=axis_list_node, name="Magnetic Potential Vector", symbol="A_z", unit="T.m", ) A_sol.type_cell = "node" list_solution = [B_sol, H_sol, mu_sol, A_sol] out_dict["meshsolution"] = build_meshsolution( list_solution=list_solution, label="FEMM 2D Magnetostatic", list_mesh=meshFEMM, group=groups, ) return out_dict
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 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 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 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 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 """ # Dynamic import to avoid loop module = __import__("SciDataTool.Classes.DataTime", fromlist=["DataTime"]) DataTime = getattr(module, "DataTime") module = __import__("SciDataTool.Classes.DataPattern", fromlist=["DataPattern"]) DataPattern = getattr(module, "DataPattern") axes_str = [] for i, axis in enumerate(self.axes): if axis.is_components: axis_str = axis.name + str(list(range(len(axis.values)))) elif axis.name == "freqs": axis_str = "time[smallestperiod]" elif axis.name == "wavenumber": axis_str = "angle[smallestperiod]" elif isinstance(axis, DataPattern): axis_str = axis.name + "[pattern]" else: axis_str = axis.name + "[smallestperiod]" axes_str.append(axis_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 self.axes: if axis.name == "freqs": axis_new = Data1D( name="time", is_components=False, values=results["time"], unit="s", symmetries=axis.symmetries.copy(), normalizations=axis.normalizations.copy(), ) elif axis.name == "wavenumber": axis_new = Data1D( name="angle", is_components=False, values=results["angle"], unit="rad", symmetries=axis.symmetries.copy(), normalizations=axis.normalizations.copy(), ) else: axis_new = axis.copy() Axes.append(axis_new) return DataTime( name=self.name, unit=self.unit, symbol=self.symbol, axes=Axes, values=values, is_real=self.is_real, normalizations=self.normalizations.copy(), )
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