Exemplo n.º 1
0
    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")
Exemplo n.º 2
0
    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")
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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,
        )
Exemplo n.º 5
0
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
Exemplo n.º 6
0
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]
Exemplo n.º 7
0
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
Exemplo n.º 8
0
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]
Exemplo n.º 9
0
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
Exemplo n.º 10
0
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
Exemplo n.º 11
0
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