jiahuimbzuai/CoQuIR huggingface dataset:
//  [python]
def __fill_area_between(self, ax, ev_dfs: typ.Iterable[pd.DataFrame],
                            ev_names: typ.List[str], color, MY_DPI: int = 96):
        """
        Plot graphs of multiple battery output data for a given simulation and
        fill area between them. Plot area on the axis given by `ax`.
        """
        for ev_df in ev_dfs:
            ev_df['timestep_time'] = ev_df['timestep_time'].dt.time.map(
                lambda x: dt.datetime(1900,1,1,x.hour,x.minute,x.second))

        # TODO: Make `rolling_window` function argument
        rolling_window = 3600  # seconds

        power_dfs = []
        dist_dfs = []
        time_dfs = []
        for ev_df in ev_dfs:
            power_df = ev_df['vehicle_energyConsumed'] * 3.6 * 1000

            t = ev_df['timestep_time'] - ev_df['timestep_time'][0]
            t = t.map(lambda t: t.total_seconds())
            dist = integrate.cumulative_trapezoid(ev_df['vehicle_speed'], t)/1000
            dist_df = pd.Series(dist / 1000)
            dist_dfs.append(dist_df)

            time_df = ev_df['timestep_time']
            time_dfs.append(time_df)

            power_df = power_df.rolling(rolling_window, center=True).mean()
            # power_df = power_df.tolist()
            power_dfs.append(power_df)
        # power_min_df = np.nanmin(power_dfs, axis=0)
        # power_max_df = np.nanmax(power_dfs, axis=0)
        power_min_df = pd.concat(power_dfs, axis=1).min(axis=1)
        power_max_df = pd.concat(power_dfs, axis=1).max(axis=1)
        # Plot
        # time = np.linspace(
        #     dt.time(hour=0), dt.time(hour=23, minute=59, second=59),
        #     len(power_max_df)
        # )
        # min_time = np.nanmin(time_dfs)
        # max_time = np.nanmax(time_dfs)
        min_time = pd.concat(time_dfs, axis=1).min(axis=1).min(axis=0)
        max_time = pd.concat(time_dfs, axis=1).max(axis=1).max(axis=0)
        time = pd.date_range(min_time, max_time, freq='S')
        # ax.plot(time, power_min_df, color=color, alpha=0.6)
        # ax.plot(time, power_max_df, color=color, alpha=0.6)
        ax.fill_between(time, power_min_df, power_max_df, color=color,
                        facecolor='#00000000', label="Power distribution",
                        hatch='.....')
        ax.legend()
//  [python]
def get_pred(self, zp1, a, eq_numpy, integrated=False):
        """Return the predicted distance modulus from the H^2 function supplied.
        
        Args:
            :zp1 (float or np.array): 1 + z for redshift z
            :a (list): parameters to subsitute into equation considered
            :eq_numpy (numpy function): function to use which gives H^2
            :integrated (bool, default=False): whether we previously analytically integrated the function (True) or not (False)
            
        Returns:
            :mu (float or np.array): the predicted distance modulus at redshifts supplied
        
        """

        if integrated:
            dL = eq_numpy(zp1, *a) - eq_numpy(1, *a)
        else:
            if self.data_x is None or self.data_mask is None:
                nx = int(np.ceil((zp1.max() - zp1.min()) / self.delta_z))
                self.data_x = np.concatenate(
                        (np.linspace(1, zp1.min(), self.min_nz),
                        np.linspace(zp1.min() + self.delta_z, zp1.max() + self.delta_z, nx),
                        zp1))
                self.data_x = np.sort(np.unique(self.data_x))
                self.data_mask = np.squeeze(np.array([np.where(self.data_x==d)[0] for d in zp1]))

            if len(a) == 0:
                dL = 1 / np.sqrt(eq_numpy(self.data_x))
            else:
                dL = 1 / np.sqrt(eq_numpy(self.data_x, *a))

            dL = scipy.integrate.cumtrapz(dL, x=self.data_x, initial=0)
            dL = dL[self.data_mask]

        dL *= zp1
        mu = 5 * np.log10(dL) + self.mu_const

        return mu
//  [python]
def get_pred(self, zp1, a, eq_numpy, integrated=False):
        """Return the predicted distance modulus from the H^2 function supplied.
        
        Args:
            :zp1 (float or np.array): 1 + z for redshift z
            :a (list): parameters to subsitute into equation considered
            :eq_numpy (numpy function): function to use which gives H^2
            :integrated (bool, default=False): whether we previously analytically integrated the function (True) or not (False)
            
        Returns:
            :mu (float or np.array): the predicted distance modulus at redshifts supplied
        
        """

        if integrated:
            dL = eq_numpy(zp1, *a) - eq_numpy(1, *a)
        else:
            if self.data_x is None or self.data_mask is None:
                nx = int(np.ceil((zp1.max() - zp1.min()) / self.delta_z))
                self.data_x = np.concatenate(
                        (np.linspace(1, zp1.min(), self.min_nz),
                        np.linspace(zp1.min() + self.delta_z, zp1.max() + self.delta_z, nx),
                        zp1))
                self.data_x = np.sort(np.unique(self.data_x))
                self.data_mask = np.squeeze(np.array([np.where(self.data_x==d)[0] for d in zp1]))

            if len(a) == 0:
                dL = 1 / np.sqrt(eq_numpy(self.data_x))
            else:
                dL = 1 / np.sqrt(eq_numpy(self.data_x, *a))

            dL = scipy.integrate.cumulative_trapezoid(dL, x=self.data_x, initial=0)
            dL = dL[self.data_mask]

        dL *= zp1
        mu = 5 * np.log10(dL) + self.mu_const

        return mu
//  [python]
def _generate_interp(self):

        xs = self._xs

        pdfs = self._pdf(xs)
        norm_factor = np.trapz(pdfs, xs)

        self._cdfs = cumtrapz(pdfs / norm_factor, xs, initial=0)
        self._interp_inv_cdf = interp1d(self._cdfs, xs, bounds_error=False)
//  [python]
def _generate_interp(self):

        xs = self._xs

        pdfs = self._pdf(xs)
        norm_factor = np.trapz(pdfs, xs)

        self._cdfs = cumulative_trapezoid(pdfs / norm_factor, xs, initial=0)
        self._interp_inv_cdf = interp1d(self._cdfs, xs, bounds_error=False)
//  [python]
def get_RAD(M_pc):
    x = M_pc/12
    coefficients= np.array([-0.0025163,0.00106611, 0.12671325, 0.01288683])

    polynomial_function = np.poly1d(coefficients)
    x_pdf = np.linspace(0, 7.36, 200)  # extended range for PDF
    fitted_y_pdf = polynomial_function(x_pdf)
    pdf_normalized = fitted_y_pdf / np.trapz(fitted_y_pdf, x_pdf)


    # Compute the cumulative probability distribution function (CDF)
    cdf = cumtrapz(pdf_normalized, x_pdf, initial=0)
    cdf = np.where(cdf > 1.0, 1.0, cdf)
    # If x is less than the minimum x value in x_pdf, return 0 (assuming CDF is 0 at negative infinity)
    if x < x_pdf[0]:
        return 0

    # If x is greater than the maximum x value in x_pdf, return 1 (CDF is 1 at positive infinity)
    if x > x_pdf[-1]:
        return 1

    # Find the index in x_pdf that is closest to x
    index = np.searchsorted(x_pdf, x, side="left")

    # Return the corresponding CDF value
    return cdf[index]
//  [python]
def get_RAD(M_pc):
    x = M_pc/12
    coefficients= np.array([-0.0025163,0.00106611, 0.12671325, 0.01288683])

    polynomial_function = np.poly1d(coefficients)
    x_pdf = np.linspace(0, 7.36, 200)  # extended range for PDF
    fitted_y_pdf = polynomial_function(x_pdf)
    pdf_normalized = fitted_y_pdf / np.trapz(fitted_y_pdf, x_pdf)


    # Compute the cumulative probability distribution function (CDF)
    cdf = cumulative_trapezoid(pdf_normalized, x_pdf, initial=0)
    cdf = np.where(cdf > 1.0, 1.0, cdf)
    # If x is less than the minimum x value in x_pdf, return 0 (assuming CDF is 0 at negative infinity)
    if x < x_pdf[0]:
        return 0

    # If x is greater than the maximum x value in x_pdf, return 1 (CDF is 1 at positive infinity)
    if x > x_pdf[-1]:
        return 1

    # Find the index in x_pdf that is closest to x
    index = np.searchsorted(x_pdf, x, side="left")

    # Return the corresponding CDF value
    return cdf[index]
//  [python]
def Hm0(self, fmin=0, fmax=np.inf):
        """
        Integrate Hm0 (Hs) from wave spectra: 
        >> S = swan.Spec2()
        >> self.from_jonswap(1,5,-90,10)
        >> S.Hm0()
        
        Optionallly parse a frequency range with kwargs fmin, fmax.
        This methods interpolated the cumulative m0 integral, 
        so is independent from the frequency discretrization.
        E.g. for swell wave height (SWAN default 0.1 Hz): 
        >> S.Hm0(fmax = 0.1)        
        """

    
        if self.energy_units[0:9] == 'm2/Hz/deg': # deg, degr, degree
            # np.abs: descending directions lead to negs
            
            spec1 = np.abs(np.trapz(self.energy,self.direction)) # implement directional range?
            
            if fmin==0 and fmax==np.inf:
            
                m0 = np.trapz(spec1,self.f, axis=-1) # int along last dimension
                
            else: # frequency range

                Efcum = scipy.integrate.cumtrapz(spec1,self.f,axis=-1,initial=0)
                
                m0 = np.ones(list(Efcum.shape)[0:2]) # preallocate
                for it,a in enumerate(m0):
                    for ix,b in enumerate(a):
                        m0[it,ix] = np.interp(fmax,self.f,Efcum[it,ix,:]) - \
                                    np.interp(fmin,self.f,Efcum[it,ix,:])               
            
            
        else:
            m0 = None
            print('unknown units:"',self.energy_units,'"')
        
        return 4*np.sqrt(m0)
//  [python]
def Hm0(self, fmin=0, fmax=np.inf):
        """
        Integrate Hm0 (Hs) from wave spectra: 
        >> S = swan.Spec2()
        >> self.from_jonswap(1,5,-90,10)
        >> S.Hm0()
        
        Optionallly parse a frequency range with kwargs fmin, fmax.
        This methods interpolated the cumulative m0 integral, 
        so is independent from the frequency discretrization.
        E.g. for swell wave height (SWAN default 0.1 Hz): 
        >> S.Hm0(fmax = 0.1)        
        """

    
        if self.energy_units[0:9] == 'm2/Hz/deg': # deg, degr, degree
            # np.abs: descending directions lead to negs
            
            spec1 = np.abs(np.trapz(self.energy,self.direction)) # implement directional range?
            
            if fmin==0 and fmax==np.inf:
            
                m0 = np.trapz(spec1,self.f, axis=-1) # int along last dimension
                
            else: # frequency range

                Efcum = scipy.integrate.cumulative_trapezoid(spec1,self.f,axis=-1,initial=0)
                
                m0 = np.ones(list(Efcum.shape)[0:2]) # preallocate
                for it,a in enumerate(m0):
                    for ix,b in enumerate(a):
                        m0[it,ix] = np.interp(fmax,self.f,Efcum[it,ix,:]) - \
                                    np.interp(fmin,self.f,Efcum[it,ix,:])               
            
            
        else:
            m0 = None
            print('unknown units:"',self.energy_units,'"')
        
        return 4*np.sqrt(m0)
//  [python]
def Hm0(self, fmin=0, fmax=np.inf):
        """
        Integrate Hm0 (Hs) from wave spectra: 
        >> S = swan.Spec1()
        >> self.from_jonswap(1,5,-90,10)
        >> S.Hm0()
        
        Optionallly parse a frequency range with kwargs fmin, fmax.
        This methods interpolated the cumulative m0 integral, 
        so is independent from the frequency discretrization.
        E.g. for swell wave height (SWAN default 0.1 Hz): 
        >> S.Hm0(fmax = 0.1)
        """
        
    
        if self.energy_units == 'm2/Hz':
        
            if fmin==0 and fmax==np.inf:
            
                m0 = np.trapz(self.energy,self.f, axis=-1) # int along last dimension
                
            else: # frequency range
            
                Efcum = scipy.integrate.cumtrapz(self.energy,self.f,axis=-1,initial=0)
                
                m0 = np.ones(list(Efcum.shape)[0:2]) # preallocate
                for it,a in enumerate(m0):
                    for ix,b in enumerate(a):
                        m0[it,ix] = np.interp(fmax,self.f,Efcum[it,ix,:]) - \
                                    np.interp(fmin,self.f,Efcum[it,ix,:])                
                
        else:
            m0 = None
            print('unknown units',self.energy_units)
        
        return 4*np.sqrt(m0)
//  [python]
def Hm0(self, fmin=0, fmax=np.inf):
        """
        Integrate Hm0 (Hs) from wave spectra: 
        >> S = swan.Spec1()
        >> self.from_jonswap(1,5,-90,10)
        >> S.Hm0()
        
        Optionallly parse a frequency range with kwargs fmin, fmax.
        This methods interpolated the cumulative m0 integral, 
        so is independent from the frequency discretrization.
        E.g. for swell wave height (SWAN default 0.1 Hz): 
        >> S.Hm0(fmax = 0.1)
        """
        
    
        if self.energy_units == 'm2/Hz':
        
            if fmin==0 and fmax==np.inf:
            
                m0 = np.trapz(self.energy,self.f, axis=-1) # int along last dimension
                
            else: # frequency range
            
                Efcum = scipy.integrate.cumulative_trapezoid(self.energy,self.f,axis=-1,initial=0)
                
                m0 = np.ones(list(Efcum.shape)[0:2]) # preallocate
                for it,a in enumerate(m0):
                    for ix,b in enumerate(a):
                        m0[it,ix] = np.interp(fmax,self.f,Efcum[it,ix,:]) - \
                                    np.interp(fmin,self.f,Efcum[it,ix,:])                
                
        else:
            m0 = None
            print('unknown units',self.energy_units)
        
        return 4*np.sqrt(m0)
//  [python]
def fcumtrapzlr(s_span, p):
    r""" Cumulative trapezoidal integration, flipped left-right 
    Useful to return the following:
         P(x) = \int_x^R p(r) dr
    """
    P = - sciint.cumtrapz( p[-1::-1], s_span[-1::-1],)[-1::-1] 
    P = np.concatenate((P,[0]))
    return P
//  [python]
def fcumtrapzlr(s_span, p):
    r""" Cumulative trapezoidal integration, flipped left-right 
    Useful to return the following:
         P(x) = \int_x^R p(r) dr
    """
    P = - sciint.cumulative_trapezoid( p[-1::-1], s_span[-1::-1],)[-1::-1] 
    P = np.concatenate((P,[0]))
    return P
//  [python]
def s(self, n=0):
        self.check(n)

        xp2 = self.xp(n) ** 2
        yp2 = self.yp(n) ** 2
        # zp = np.sqrt(1. / (1. + xp2 + yp2))
        s = cumtrapz(np.sqrt(1. + xp2 + yp2), self.z(n), initial=0)
        return s
//  [python]
def s(self, n=0):
        self.check(n)

        xp2 = self.xp(n) ** 2
        yp2 = self.yp(n) ** 2
        # zp = np.sqrt(1. / (1. + xp2 + yp2))
        s = cumulative_trapezoid(np.sqrt(1. + xp2 + yp2), self.z(n), initial=0)
        return s
//  [python]
def get_smd(self, z):
        """
        Compute stellar mass density (SMD) at redshift `z`.
        """

        if not hasattr(self, '_func_smd'):
            dtdz = np.array([self.cosm.dtdz(z) for z in self.halos.tab_z])
            self._tab_smd = cumtrapz(self.tab_sfrd_total[-1::-1] * dtdz[-1::-1],
                dx=np.abs(np.diff(self.halos.tab_z[-1::-1])), initial=0.)[-1::-1]
            self._func_smd = interp1d(self.halos.tab_z, self._tab_smd,
                kind=self.pf['pop_interp_sfrd'])

        return self._func_smd(z)
//  [python]
def get_smd(self, z):
        """
        Compute stellar mass density (SMD) at redshift `z`.
        """

        if not hasattr(self, '_func_smd'):
            dtdz = np.array([self.cosm.dtdz(z) for z in self.halos.tab_z])
            self._tab_smd = cumulative_trapezoid(self.tab_sfrd_total[-1::-1] * dtdz[-1::-1],
                x=self.halos.tab_z[-1::-1], initial=0.)[-1::-1]
            self._func_smd = interp1d(self.halos.tab_z, self._tab_smd,
                kind=self.pf['pop_interp_sfrd'])

        return self._func_smd(z)
//  [python]
def fcumtrapzlr(s_span, p):
    """ Cumulative trapezoidal integration, flipped left-right 
    Useful to return the following:
         P(x) = \int_x^R p(r) dr
    """
    P = - sciint.cumtrapz( p[-1::-1], s_span[-1::-1],)[-1::-1] 
    P = np.concatenate((P,[0]))
    return P
//  [python]
def fcumtrapzlr(s_span, p):
    """ Cumulative trapezoidal integration, flipped left-right 
    Useful to return the following:
         P(x) = \int_x^R p(r) dr
    """
    P = - sciint.cumulative_trapezoid( p[-1::-1], s_span[-1::-1],)[-1::-1] 
    P = np.concatenate((P,[0]))
    return P
//  [python]
pylab.xlim([x_low, x_high])
pylab.legend(frameon=False)
pylab.subplot(2,1,2)
Vmantle0 = ((4.0*np.pi/3.0) * (rp**3 - rc**3))/1e9
for k in range(0,len(inputs)):
    for i in range(0,len(Melt_volume[k])): 
        if (total_time[k][i]<inputs_for_MC[k][5].interiorg):
            Melt_volume[k][i] = 0.0
    pylab.semilogx(total_time[k],scipy.integrate.cumtrapz(Melt_volume[k],x=total_time[k],initial=0)/Vmantle0,'b')
//  [python]
pylab.xlim([x_low, x_high])
pylab.legend(frameon=False)
pylab.subplot(2,1,2)
Vmantle0 = ((4.0*np.pi/3.0) * (rp**3 - rc**3))/1e9
for k in range(0,len(inputs)):
    for i in range(0,len(Melt_volume[k])): 
        if (total_time[k][i]<inputs_for_MC[k][5].interiorg):
            Melt_volume[k][i] = 0.0
    pylab.semilogx(total_time[k],scipy.integrate.cumulative_trapezoid(Melt_volume[k],x=total_time[k],initial=0)/Vmantle0,'b')
//  [python]
def integrate_dxdt_hat(dxdt_hat, dt):
    """
    Wrapper for scipy.integrate.cumtrapz to integrate dxdt_hat that ensures the integral has the same length

    :param dxdt_hat: estimate derivative of timeseries
    :type dxdt_hat: np.array

    :param dt: time step in seconds
    :type dt: float

    :return: integral of dxdt_hat
    :rtype: np.array
    """
    x = scipy.integrate.cumtrapz(dxdt_hat)
    first_value = x[0] - np.mean(dxdt_hat[0:1])
    x = np.hstack((first_value, x))*dt
    return x
//  [python]
def integrate_dxdt_hat(dxdt_hat, dt):
    """
    Wrapper for scipy.integrate.cumtrapz to integrate dxdt_hat that ensures the integral has the same length

    :param dxdt_hat: estimate derivative of timeseries
    :type dxdt_hat: np.array

    :param dt: time step in seconds
    :type dt: float

    :return: integral of dxdt_hat
    :rtype: np.array
    """
    x = scipy.integrate.cumulative_trapezoid(dxdt_hat)
    first_value = x[0] - np.mean(dxdt_hat[0:1])
    x = np.hstack((first_value, x))*dt
    return x
//  [python]
def _get_photon_density(self, z, Emin, Emax):
        """
        Compute the photon luminosity density in some bandpass at some redshift.

        Returns
        -------
        Luminosity density in units of photons / s / (comoving cm)**3.
        """

        if not hasattr(self, '_rho_N'):
            self._rho_N = {}

        # If we've already figured it out, just return
        if (Emin, Emax) in self._rho_N:
            return self._rho_N[(Emin, Emax)](z)

        tab = np.ones_like(self.halos.tab_z)

        # For all halos
        N_per_Msun = self.get_photons_per_Msun(Emin=Emin, Emax=Emax)

        if abs(Emin - E_LL) < 0.1:
            fesc = self.fesc(z=z, Mh=self.halos.tab_M)
        elif (abs(Emin - E_LyA) < 0.1 and abs(Emax - E_LL) < 0.1):
            fesc = self.fesc_LW(z=z, Mh=self.halos.tab_M)
        else:
            raise NotImplementedError('help!')

        ok = ~self._tab_sfr_mask
        for i, z in enumerate(self.halos.tab_z):
            integrand = self.tab_sfr[i] * self.halos.tab_dndlnm[i] \
                * self.tab_focc[i] * N_per_Msun * fesc * ok[i]

            tot = np.trapz(integrand, x=np.log(self.halos.tab_M))
            cumtot = cumtrapz(integrand, x=np.log(self.halos.tab_M),
                initial=0.0)

            tab[i] = tot - \
                np.interp(np.log(self._tab_Mmin[i]), np.log(self.halos.tab_M), cumtot)

        tab *= 1. / s_per_yr / cm_per_mpc**3

        self._rho_N[(Emin, Emax)] = interp1d(self.halos.tab_z, tab,
            kind=self.pf['pop_interp_sfrd'])

        return self._rho_N[(Emin, Emax)](z)
//  [python]
def _get_photon_density(self, z, Emin, Emax):
        """
        Compute the photon luminosity density in some bandpass at some redshift.

        Returns
        -------
        Luminosity density in units of photons / s / (comoving cm)**3.
        """

        if not hasattr(self, '_rho_N'):
            self._rho_N = {}

        # If we've already figured it out, just return
        if (Emin, Emax) in self._rho_N:
            return self._rho_N[(Emin, Emax)](z)

        tab = np.ones_like(self.halos.tab_z)

        # For all halos
        N_per_Msun = self.get_photons_per_Msun(Emin=Emin, Emax=Emax)

        if abs(Emin - E_LL) < 0.1:
            fesc = self.fesc(z=z, Mh=self.halos.tab_M)
        elif (abs(Emin - E_LyA) < 0.1 and abs(Emax - E_LL) < 0.1):
            fesc = self.fesc_LW(z=z, Mh=self.halos.tab_M)
        else:
            raise NotImplementedError('help!')

        ok = ~self._tab_sfr_mask
        for i, z in enumerate(self.halos.tab_z):
            integrand = self.tab_sfr[i] * self.halos.tab_dndlnm[i] \
                * self.tab_focc[i] * N_per_Msun * fesc * ok[i]

            tot = np.trapz(integrand, x=np.log(self.halos.tab_M))
            cumtot = cumulative_trapezoid(integrand, x=np.log(self.halos.tab_M),
                initial=0.0)

            tab[i] = tot - \
                np.interp(np.log(self._tab_Mmin[i]), np.log(self.halos.tab_M), cumtot)

        tab *= 1. / s_per_yr / cm_per_mpc**3

        self._rho_N[(Emin, Emax)] = interp1d(self.halos.tab_z, tab,
            kind=self.pf['pop_interp_sfrd'])

        return self._rho_N[(Emin, Emax)](z)
//  [python]
def TropD_Calculate_StreamFunction(
    V: np.ndarray, lat: np.ndarray, lev: np.ndarray
) -> np.ndarray:
    """
    Calculates the meridional mass streamfunction by integrating meridional wind from top
    of the atmosphere to surface

    Parameters
    ----------
    V : numpy.ndarray (dim1, ..., dimN-2, lat, lev)
        N-dimensional array of zonal-mean meridional wind with final 2 axes corresponding to latitude and vertical level, respectively
    lat : numpy.ndarray
        equally-spaced latitude array
    lev : numpy.ndarray
        vertical level array in hPa

    Returns
    -------
    numpy.ndarray (same shape as V)
        the meridional mass overturning streamfunction (psi)
    """

    if V.shape[-2:] != (lat.size, lev.size):
        raise ValueError(
            f"final dimensions on V {V.shape[-2:]} and grid"
            f" coordinates don't match ({lat.size},{lev.size})"
        )

    # mask any subsurface or missing data
    V = np.where(np.isfinite(V), V, 0)
    cos_lat = np.cos(lat * np.pi / 180.0)[:, None]
    # cumtrapz: if F(x) = int f(x) dx, return F(x) - F(x[0])
    psi = (
        cumtrapz(V, lev * 100.0, axis=-1, initial=0.0)
        * (EARTH_RADIUS / GRAV * 2.0 * np.pi)
        * cos_lat
    )

    return psi
//  [python]
def TropD_Calculate_StreamFunction(
    V: np.ndarray, lat: np.ndarray, lev: np.ndarray
) -> np.ndarray:
    """
    Calculates the meridional mass streamfunction by integrating meridional wind from top
    of the atmosphere to surface

    Parameters
    ----------
    V : numpy.ndarray (dim1, ..., dimN-2, lat, lev)
        N-dimensional array of zonal-mean meridional wind with final 2 axes corresponding to latitude and vertical level, respectively
    lat : numpy.ndarray
        equally-spaced latitude array
    lev : np.ndarray
        vertical level array in hPa

    Returns
    -------
    numpy.ndarray (same shape as V)
        the meridional mass overturning streamfunction (psi)
    """

    if V.shape[-2:] != (lat.size, lev.size):
        raise ValueError(
            f"final dimensions on V {V.shape[-2:]} and grid"
            f" coordinates don't match ({lat.size},{lev.size})"
        )

    # mask any subsurface or missing data
    V = np.where(np.isfinite(V), V, 0)
    cos_lat = np.cos(lat * np.pi / 180.0)[:, None]
    # cumtrapz: if F(x) = int f(x) dx, return F(x) - F(x[0])
    psi = (
        cumulative_trapezoid(V, lev * 100.0, axis=-1, initial=0.0)
        * (EARTH_RADIUS / GRAV * 2.0 * np.pi)
        * cos_lat
    )

    return psi
//  [python]
def make_funcs(variables):
    '''Simplifies some functions through currying the time axis'''

    def integrate(y):
        '''numeric integration of ys'''
        return scipy.integrate.cumtrapz(y, x=variables["t"])

    def differentiate(y, sigma=5):
        '''numeric differentiation of xs.
        fudges it so that the length of output is the same as the input'''

        time = variables["t"]
        y = scipy.ndimage.gaussian_filter1d(y, sigma=sigma)
        diff = (y[1:]-y[:-1])/(time[1:]-time[:-1])
        return np.concatenate(([diff[0]], diff))

    return dict(integrate=integrate, differentiate=differentiate, hdg=hdg)
//  [python]
def make_funcs(variables):
    '''Simplifies some functions through currying the time axis'''

    def integrate(y):
        '''numeric integration of ys'''
        return scipy.integrate.cumulative_trapezoid(y, x=variables["t"])

    def differentiate(y, sigma=5):
        '''numeric differentiation of xs.
        fudges it so that the length of output is the same as the input'''

        time = variables["t"]
        y = scipy.ndimage.gaussian_filter1d(y, sigma=sigma)
        diff = (y[1:]-y[:-1])/(time[1:]-time[:-1])
        return np.concatenate(([diff[0]], diff))

    return dict(integrate=integrate, differentiate=differentiate, hdg=hdg)
//  [python]
def compute_mean_rt_by_quantiles(fit, time, quantiles):
    '''Compute the mean rt for each quantile. '''

    # Cumulative density function
    cum = cumtrapz(fit, time)
    # Normalize if necessary
    fit /= cum[-1]
    cum /= cum[-1]

    i0 = 0

    rts = []

    for quantile in quantiles:
        # Find the index for this quantile
        i = np.searchsorted(cum, quantile) - 1

        # Normalization
        mass = cum[i] - cum[i0]

        # Mean reaction time
        rt = np.trapz(fit[i0:i] * time[i0:i], time[i0:i]) / mass

        rts.append(rt)

        # Update the index
        i0 = i

    return rts
//  [python]
def compute_mean_rt_by_quantiles(fit, time, quantiles):
    '''Compute the mean rt for each quantile. '''

    # Cumulative density function
    cum = cumulative_trapezoid(fit, time)
    # Normalize if necessary
    fit /= cum[-1]
    cum /= cum[-1]

    i0 = 0

    rts = []

    for quantile in quantiles:
        # Find the index for this quantile
        i = np.searchsorted(cum, quantile) - 1

        # Normalization
        mass = cum[i] - cum[i0]

        # Mean reaction time
        rt = np.trapz(fit[i0:i] * time[i0:i], time[i0:i]) / mass

        rts.append(rt)

        # Update the index
        i0 = i

    return rts
//  [python]
def compute_predicted_error_quantiles(fits, quantiles=None):
    '''Compute the quantiles. '''

    if quantiles is None:
        quantiles = [0.25, 0.50, 0.75, 1.0]

    # Get the unique trial types, which are coded as the second index
    # The first index is always the action
    trial_types = set([field[1] for field in fits.__class__.fields])

    # Compute the cumulative distribution
    delta_er = {'quantiles': quantiles}

    for trial in trial_types:
        # Compute the cumulative distribution
        cums = cumtrapz(fits.__dict__[COMP, trial] +
                fits.__dict__[INCOMP, trial], fits.time)
        total = cums[-1]
        cums /= total

        cums_comp = cumtrapz(fits.__dict__[COMP, trial], fits.time)
        cums_comp /= total

        delta_er[trial] = {'x': [], 'y': []}
        i0 = 0
        for quantile in quantiles:
            # Find the index for this quantile
            i = np.searchsorted(cums, quantile) - 1

            # Compute the total mass for this quantile
            total_mass = (cums[i] - cums[i0])

            # Compute the fraction corresponding to comps
            comp_mass = (cums_comp[i] - cums_comp[i0])

            x = (fits.time[i] + fits.time[i0])/2.0
            delta_er[trial]['x'].append(x)
            delta_er[trial]['y'].append(comp_mass/total_mass)

            i0 = i

    return delta_er
//  [python]
def compute_predicted_error_quantiles(fits, quantiles=None):
    '''Compute the quantiles. '''

    if quantiles is None:
        quantiles = [0.25, 0.50, 0.75, 1.0]

    # Get the unique trial types, which are coded as the second index
    # The first index is always the action
    trial_types = set([field[1] for field in fits.__class__.fields])

    # Compute the cumulative distribution
    delta_er = {'quantiles': quantiles}

    for trial in trial_types:
        # Compute the cumulative distribution
        cums = cumulative_trapezoid(fits.__dict__[COMP, trial] +
                fits.__dict__[INCOMP, trial], fits.time)
        total = cums[-1]
        cums /= total

        cums_comp = cumulative_trapezoid(fits.__dict__[COMP, trial], fits.time)
        cums_comp /= total

        delta_er[trial] = {'x': [], 'y': []}
        i0 = 0
        for quantile in quantiles:
            # Find the index for this quantile
            i = np.searchsorted(cums, quantile) - 1

            # Compute the total mass for this quantile
            total_mass = (cums[i] - cums[i0])

            # Compute the fraction corresponding to comps
            comp_mass = (cums_comp[i] - cums_comp[i0])

            x = (fits.time[i] + fits.time[i0])/2.0
            delta_er[trial]['x'].append(x)
            delta_er[trial]['y'].append(comp_mass/total_mass)

            i0 = i

    return delta_er
//  [python]
def cumulative_integrate(data, dim="f2", regions=None):
    """Cumulative integration

    Args:
        data (DNPData): Data object
        dim (str): Dimension to perform cumulative integration
        regions (None, list): List of tuples to specify range of integration [(min, max), ...]

    Returns:
        data: cumulative sum of data

    Examples:
        Example showing cumulative integration of lorentzian function

        >>> import numpy as np
        >>> from matplotlib.pylab import *
        >>> import dnplab as dnp
        >>> x = np.r_[-10:10:1000j]
        >>> y = dnp.math.lineshape.lorentzian(x,0,1)
        >>> data = dnp.DNPData(y, ['f2'], [x])
        >>> data_int = dnp.cumulative_integrate(data)
        >>> figure()
        >>> dnp.plot(data)
        >>> dnp.plot(data_int)
        >>> show()


    """

    out = data.copy()

    if regions == None:
        index = out.index(dim)
        out.values = cumtrapz(out.values, out.coords[dim], axis=index, initial=0)

        proc_attr_name = "cumlative_integrate"
        proc_parameters = {
            "dim": dim,
            "regions": regions,
        }
        out.add_proc_attrs(proc_attr_name, proc_parameters)
        return out

    else:
        data_list = []
        for region in regions:
            proc_attr_name = "cumlative_integrate"
            proc_parameters = {
                "dim": dim,
                "regions": regions,
            }
            out.add_proc_attrs(proc_attr_name, proc_parameters)
            data_list.append(cumulative_integrate(out[dim, region], dim))

        return data_list
//  [python]
def cumulative_integrate(data, dim="f2", regions=None):
    """Cumulative integration

    Args:
        data (DNPData): Data object
        dim (str): Dimension to perform cumulative integration
        regions (None, list): List of tuples to specify range of integration [(min, max), ...]

    Returns:
        data: cumulative sum of data

    Examples:
        Example showing cumulative integration of lorentzian function

        >>> import numpy as np
        >>> from matplotlib.pylab import *
        >>> import dnplab as dnp
        >>> x = np.r_[-10:10:1000j]
        >>> y = dnp.math.lineshape.lorentzian(x,0,1)
        >>> data = dnp.DNPData(y, ['f2'], [x])
        >>> data_int = dnp.cumulative_integrate(data)
        >>> figure()
        >>> dnp.plot(data)
        >>> dnp.plot(data_int)
        >>> show()


    """

    out = data.copy()

    if regions == None:
        index = out.index(dim)
        out.values = cumulative_trapezoid(out.values, out.coords[dim], axis=index, initial=0)

        proc_attr_name = "cumlative_integrate"
        proc_parameters = {
            "dim": dim,
            "regions": regions,
        }
        out.add_proc_attrs(proc_attr_name, proc_parameters)
        return out

    else:
        data_list = []
        for region in regions:
            proc_attr_name = "cumlative_integrate"
            proc_parameters = {
                "dim": dim,
                "regions": regions,
            }
            out.add_proc_attrs(proc_attr_name, proc_parameters)
            data_list.append(cumulative_integrate(out[dim, region], dim))

        return data_list
//  [python]
def pressure(self, r):
        """
        Evaluate pressure (in GPa) at radius r (in km)
        """
        # NB: this is done numerically, because otherise
        # I need to work out how to express g as a polynomial,
        # multiply this by rho, then do the integral inwards.
        # all a bit of a faff!
        if np.ndim(r) == 0:
            # 10 km grid spacing
            rs = np.arange(r, self.r_earth, 1.0)
            g = self.gravity(rs)
            rho = self.density(rs)
            ps = spint.cumtrapz((-g*rho)[::-1], rs[::-1]*1000.0, initial=0)
            pressure = ps[-1]/1E9
        else:
            # Assume I have been fed something I can integrate
            g = self.gravity(r)
            rho = self.density(r)
            pressure = spint.cumtrapz((-g*rho)[::-1],
                                      r[::-1]*1000.0, initial=0)
            pressure = pressure[::-1]/1E9
        return pressure
//  [python]
def pressure(self, r):
        """
        Evaluate pressure (in GPa) at radius r (in km)
        """
        # NB: this is done numerically, because otherise
        # I need to work out how to express g as a polynomial,
        # multiply this by rho, then do the integral inwards.
        # all a bit of a faff!
        if np.ndim(r) == 0:
            # 10 km grid spacing
            rs = np.arange(r, self.r_earth, 1.0)
            g = self.gravity(rs)
            rho = self.density(rs)
            ps = spint.cumulative_trapezoid((-g*rho)[::-1], rs[::-1]*1000.0, initial=0)
            pressure = ps[-1]/1E9
        else:
            # Assume I have been fed something I can integrate
            g = self.gravity(r)
            rho = self.density(r)
            pressure = spint.cumulative_trapezoid((-g*rho)[::-1],
                                      r[::-1]*1000.0, initial=0)
            pressure = pressure[::-1]/1E9
        return pressure
//  [python]
def integration(self, value):

        integration_order = value.get("order", None)
        if integration_order is None:
            print('Error: keyword "order" not found.')
            sys.exit("Abnormal termination.")

        print(
            'Signal process: "integration" applied '
            + str(integration_order)
            + " time(s) with"
        )

        default_ics = np.zeros(integration_order)
        ics = value.get("initial_conditions", default_ics)

        print(f"initial condition(s) as {ics}")

        if len(ics) != integration_order:
            print("Error: length of initial condition(s) array")
            print("must be equal to the order of integration.")
            print(f"Specified order = {integration_order}")
            print(f"Length of intial condition(s) array = {len(ics)}")
            sys.exit("Abnormal termination.")

        # https://docs.scipy.org/doc/numpy/reference/generated/numpy.trapz.html
        # https://docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.integrate.cumtrapz.html
        for k in range(integration_order):
            # inty = np.trapz(self._data[:, 1], self._data[:, 0]) + ics[k]
            # inty = cumtrapz(self._data[:, 1], self._data[:, 0], initial=ics[k])
            inty = cumtrapz(self._data[:, 1], self._data[:, 0], initial=0) + ics[k]
            self._data[:, 1] = inty  # overwrite
            print(f"  Integral {k+1} completed.")

        serialize = value.get("serialize", 0)  # default is not to serialize
        if serialize:
            folder = value.get("folder", ".")  # default to current folder
            file_output = value.get("file", None)

            if file_output is None:
                print('Error: keyword "file" not found.')
                sys.exit("Abnormal termination.")
            else:
                self.serialize(folder, file_output)
//  [python]
def integration(self, value):

        integration_order = value.get("order", None)
        if integration_order is None:
            print('Error: keyword "order" not found.')
            sys.exit("Abnormal termination.")

        print(
            'Signal process: "integration" applied '
            + str(integration_order)
            + " time(s) with"
        )

        default_ics = np.zeros(integration_order)
        ics = value.get("initial_conditions", default_ics)

        print(f"initial condition(s) as {ics}")

        if len(ics) != integration_order:
            print("Error: length of initial condition(s) array")
            print("must be equal to the order of integration.")
            print(f"Specified order = {integration_order}")
            print(f"Length of intial condition(s) array = {len(ics)}")
            sys.exit("Abnormal termination.")

        # https://docs.scipy.org/doc/numpy/reference/generated/numpy.trapz.html
        # https://docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.integrate.cumtrapz.html
        for k in range(integration_order):
            # inty = np.trapz(self._data[:, 1], self._data[:, 0]) + ics[k]
            # inty = cumtrapz(self._data[:, 1], self._data[:, 0], initial=ics[k])
            inty = cumulative_trapezoid(self._data[:, 1], self._data[:, 0], initial=0) + ics[k]
            self._data[:, 1] = inty  # overwrite
            print(f"  Integral {k+1} completed.")

        serialize = value.get("serialize", 0)  # default is not to serialize
        if serialize:
            folder = value.get("folder", ".")  # default to current folder
            file_output = value.get("file", None)

            if file_output is None:
                print('Error: keyword "file" not found.')
                sys.exit("Abnormal termination.")
            else:
                self.serialize(folder, file_output)
//  [python]
def intf_test():
	Fs = 1000
	N = 1500
	time = np.arange(N) / float(Fs)

	#sig_accel = generate_toy_signal_1(time, noise=Falses)
	sig_accel = fft_util.generate_toy_signal_2(time, noise=False)

	# Numerical integration.
	sig_vel_ni = scipy.integrate.cumtrapz(sig_accel - sig_accel.mean(), x=time)
	sig_vel_ni = np.insert(sig_vel_ni, 0, 0)  # TODO [check] >> Is it correct?
	# Integration by omega arithmetic.
	sig_vel_oa = intf(sig_accel - sig_accel.mean(), fs=Fs, f_lo=1, times=1, winlen=1, unwin=False)
	sig_vel_oa_unwin = intf(sig_accel - sig_accel.mean(), fs=Fs, f_lo=1, times=1, winlen=1, unwin=True)

	#--------------------
	plt.figure()
	plt.plot(time, sig_vel_ni, 'r', time, sig_vel_oa_unwin, 'g', time, sig_vel_oa, 'b')
	plt.xlabel('Time')
	plt.ylabel('Velocity')

	#--------------------
	fft_util.plot_fft(sig_vel_ni, Fs)
	fft_util.plot_fft(sig_vel_oa_unwin, Fs)
	fft_util.plot_fft(sig_vel_oa, Fs)
	fft_util.plot_fft(sig_accel, Fs)
//  [python]
def intf_test():
	Fs = 1000
	N = 1500
	time = np.arange(N) / float(Fs)

	#sig_accel = generate_toy_signal_1(time, noise=Falses)
	sig_accel = fft_util.generate_toy_signal_2(time, noise=False)

	# Numerical integration.
	sig_vel_ni = scipy.integrate.cumulative_trapezoid(sig_accel - sig_accel.mean(), x=time)
	sig_vel_ni = np.insert(sig_vel_ni, 0, 0)  # TODO [check] >> Is it correct?
	# Integration by omega arithmetic.
	sig_vel_oa = intf(sig_accel - sig_accel.mean(), fs=Fs, f_lo=1, times=1, winlen=1, unwin=False)
	sig_vel_oa_unwin = intf(sig_accel - sig_accel.mean(), fs=Fs, f_lo=1, times=1, winlen=1, unwin=True)

	#--------------------
	plt.figure()
	plt.plot(time, sig_vel_ni, 'r', time, sig_vel_oa_unwin, 'g', time, sig_vel_oa, 'b')
	plt.xlabel('Time')
	plt.ylabel('Velocity')

	#--------------------
	fft_util.plot_fft(sig_vel_ni, Fs)
	fft_util.plot_fft(sig_vel_oa_unwin, Fs)
	fft_util.plot_fft(sig_vel_oa, Fs)
	fft_util.plot_fft(sig_accel, Fs)
//  [python]
def initialize_from_emp_spec(Q, x, y, fs, plot=False):
    """
    Initializes the Spectral Mixture hyperparameters by fitting a GMM on the empirical spectrum,
    found by Lomb-Scargle periodogram.
    Function largely taken from: https://docs.gpytorch.ai/en/v1.1.1/_modules/gpytorch/kernels/spectral_mixture_kernel.html#SpectralMixtureKernel.initialize_from_data_empspect
    Instead, here the Lomb-Scargle periodogram is used to fit the GMM to allow analysis of ununiformly sampled data.

    :param Q (int) number of spectral components in SM kernel
    :param x (np.array of float64) X values of input data
    :param y NumPy array of float64. Y values of input data

    return: frequencies lengthscales, mixture weights, all of which are NumPy arrays of shape (Q,)
    """

    freqs = np.linspace(0.01, fs, 1000)
    Pxx = signal.lombscargle(x, y, freqs, normalize=False)

    if plot:
        fig = plt.figure(figsize=(8, 4))
        plt.plot(freqs, Pxx, color='blue')
        plt.show()

    total_area = np.trapz(Pxx, freqs)
    spec_cdf = np.hstack((np.zeros(1), cumtrapz(Pxx, freqs)))
    spec_cdf = spec_cdf / total_area

    a = np.random.rand(1000, 1)
    p, q = np.histogram(a, spec_cdf)
    bins = np.digitize(a, q)
    slopes = (spec_cdf[bins] - spec_cdf[bins - 1]) / (freqs[bins] - freqs[bins - 1])
    intercepts = spec_cdf[bins - 1] - slopes * freqs[bins - 1]
    inv_spec = (a - intercepts) / slopes

    GMM = GaussianMixture(n_components=Q, covariance_type="full")
    GMM.fit(X=inv_spec)
    means = GMM.means_
    varz = GMM.covariances_
    weights = GMM.weights_

    emp_frequencies, emp_lengthscales, emp_mixture_weights = means.flatten(), varz.flatten(), weights.flatten()
    lengthscales = 1 / np.sqrt(emp_lengthscales)
    mixture_weights = emp_mixture_weights
    frequencies = emp_frequencies

    return frequencies, lengthscales, mixture_weights
//  [python]
def initialize_from_emp_spec(Q, x, y, fs, plot=False):
    """
    Initializes the Spectral Mixture hyperparameters by fitting a GMM on the empirical spectrum,
    found by Lomb-Scargle periodogram.
    Function largely taken from: https://docs.gpytorch.ai/en/v1.1.1/_modules/gpytorch/kernels/spectral_mixture_kernel.html#SpectralMixtureKernel.initialize_from_data_empspect
    Instead, here the Lomb-Scargle periodogram is used to fit the GMM to allow analysis of ununiformly sampled data.

    :param Q (int) number of spectral components in SM kernel
    :param x (np.array of float64) X values of input data
    :param y NumPy array of float64. Y values of input data

    return: frequencies lengthscales, mixture weights, all of which are NumPy arrays of shape (Q,)
    """

    freqs = np.linspace(0.01, fs, 1000)
    Pxx = signal.lombscargle(x, y, freqs, normalize=False)

    if plot:
        fig = plt.figure(figsize=(8, 4))
        plt.plot(freqs, Pxx, color='blue')
        plt.show()

    total_area = np.trapz(Pxx, freqs)
    spec_cdf = np.hstack((np.zeros(1), cumulative_trapezoid(Pxx, freqs)))
    spec_cdf = spec_cdf / total_area

    a = np.random.rand(1000, 1)
    p, q = np.histogram(a, spec_cdf)
    bins = np.digitize(a, q)
    slopes = (spec_cdf[bins] - spec_cdf[bins - 1]) / (freqs[bins] - freqs[bins - 1])
    intercepts = spec_cdf[bins - 1] - slopes * freqs[bins - 1]
    inv_spec = (a - intercepts) / slopes

    GMM = GaussianMixture(n_components=Q, covariance_type="full")
    GMM.fit(X=inv_spec)
    means = GMM.means_
    varz = GMM.covariances_
    weights = GMM.weights_

    emp_frequencies, emp_lengthscales, emp_mixture_weights = means.flatten(), varz.flatten(), weights.flatten()
    lengthscales = 1 / np.sqrt(emp_lengthscales)
    mixture_weights = emp_mixture_weights
    frequencies = emp_frequencies

    return frequencies, lengthscales, mixture_weights
//  [python]
def score_ld_derivative(alpha, xs, ys, **kw):
    if not 'maxit' in kw.keys():
        kw['maxit'] = 1000
    if not 'linalg_solver_maxit' in kw.keys():
        kw['linalg_solver_maxit'] = 10000
    if not 'verbose' in kw.keys():
        kw['verbose'] = False
    if not 'solver' in kw.keys():
        kw['solver'] = 'lgmres'
    if not 'tol' in kw.keys():
        kw['tol'] = 1e-12
    if not 'atol' in kw.keys():
        kw['atol'] = 1e-4
    if not 'rtol' in kw.keys():
        kw['rtol'] = 1e-6
    ld_deriv = ld_derivative(ys, xs, alpha=alpha, **kw)
    integrated_ld = integrate.cumtrapz(ld_deriv, x=xs, initial=ys[0])
    return np.abs(estimate_noise_variance(xs, ys) - mse(integrated_ld, ys))
//  [python]
def score_ld_derivative(alpha, xs, ys, **kw):
    if not 'maxit' in kw.keys():
        kw['maxit'] = 1000
    if not 'linalg_solver_maxit' in kw.keys():
        kw['linalg_solver_maxit'] = 10000
    if not 'verbose' in kw.keys():
        kw['verbose'] = False
    if not 'solver' in kw.keys():
        kw['solver'] = 'lgmres'
    if not 'tol' in kw.keys():
        kw['tol'] = 1e-12
    if not 'atol' in kw.keys():
        kw['atol'] = 1e-4
    if not 'rtol' in kw.keys():
        kw['rtol'] = 1e-6
    ld_deriv = ld_derivative(ys, xs, alpha=alpha, **kw)
    integrated_ld = integrate.cumulative_trapezoid(ld_deriv, x=xs, initial=ys[0])
    return np.abs(estimate_noise_variance(xs, ys) - mse(integrated_ld, ys))
//  [python]
def initialize_from_data(self, train_x, train_y):
        """
        Initialize the point masses for this kernel from the empirical spectrum of the data. To do this, we estimate
        the empirical spectrum's CDF and then simply sample from it. This is analogous to how the SM kernel's mixture
        is initialized, but we skip the last step of fitting a GMM to the samples and just use the samples directly.
        """
        import numpy as np
        from scipy.fftpack import fft
        from scipy.integrate import cumtrapz

        N = train_x.size(-2)
        emp_spect = np.abs(fft(train_y.cpu().detach().numpy())) ** 2 / N
        M = math.floor(N / 2)

        freq1 = np.arange(M + 1)
        freq2 = np.arange(-M + 1, 0)
        freq = np.hstack((freq1, freq2)) / N
        freq = freq[: M + 1]
        emp_spect = emp_spect[: M + 1]

        total_area = np.trapz(emp_spect, freq)
        spec_cdf = np.hstack((np.zeros(1), cumtrapz(emp_spect, freq)))
        spec_cdf = spec_cdf / total_area

        a = np.random.rand(self.raw_Z.size(-2), 1)
        p, q = np.histogram(a, spec_cdf)
        bins = np.digitize(a, q)
        slopes = (spec_cdf[bins] - spec_cdf[bins - 1]) / (freq[bins] - freq[bins - 1])
        intercepts = spec_cdf[bins - 1] - slopes * freq[bins - 1]
        inv_spec = (a - intercepts) / slopes

        self.Z = inv_spec
//  [python]
def initialize_from_data(self, train_x, train_y):
        """
        Initialize the point masses for this kernel from the empirical spectrum of the data. To do this, we estimate
        the empirical spectrum's CDF and then simply sample from it. This is analogous to how the SM kernel's mixture
        is initialized, but we skip the last step of fitting a GMM to the samples and just use the samples directly.
        """
        import numpy as np
        from scipy.fftpack import fft
        from scipy.integrate import cumulative_trapezoid

        N = train_x.size(-2)
        emp_spect = np.abs(fft(train_y.cpu().detach().numpy())) ** 2 / N
        M = math.floor(N / 2)

        freq1 = np.arange(M + 1)
        freq2 = np.arange(-M + 1, 0)
        freq = np.hstack((freq1, freq2)) / N
        freq = freq[: M + 1]
        emp_spect = emp_spect[: M + 1]

        total_area = np.trapz(emp_spect, freq)
        spec_cdf = np.hstack((np.zeros(1), cumulative_trapezoid(emp_spect, freq)))
        spec_cdf = spec_cdf / total_area

        a = np.random.rand(self.raw_Z.size(-2), 1)
        p, q = np.histogram(a, spec_cdf)
        bins = np.digitize(a, q)
        slopes = (spec_cdf[bins] - spec_cdf[bins - 1]) / (freq[bins] - freq[bins - 1])
        intercepts = spec_cdf[bins - 1] - slopes * freq[bins - 1]
        inv_spec = (a - intercepts) / slopes

        self.Z = inv_spec