jiahuimbzuai/CoQuIR huggingface dataset:
//  [python]
def test_cumulative_integrate(dask) -> None:
    rs = np.random.RandomState(43)
    coord = [0.2, 0.35, 0.4, 0.6, 0.7, 0.75, 0.76, 0.8]

    da = xr.DataArray(
        rs.randn(8, 6),
        dims=["x", "y"],
        coords={
            "x": coord,
            "x2": (("x",), rs.randn(8)),
            "z": 3,
            "x2d": (("x", "y"), rs.randn(8, 6)),
        },
    )
    if dask and has_dask:
        da = da.chunk({"x": 4})

    ds = xr.Dataset({"var": da})

    # along x
    actual = da.cumulative_integrate("x")

    # From scipy-1.6.0 cumtrapz is renamed to cumulative_trapezoid, but cumtrapz is
    # still provided for backward compatibility
    from scipy.integrate import cumtrapz

    expected_x = xr.DataArray(
        cumtrapz(da.compute(), da["x"], axis=0, initial=0.0),
        dims=["x", "y"],
        coords=da.coords,
    )
    assert_allclose(expected_x, actual.compute())
    assert_equal(
        ds["var"].cumulative_integrate("x"),
        ds.cumulative_integrate("x")["var"],
    )

    # make sure result is also a dask array (if the source is dask array)
    assert isinstance(actual.data, type(da.data))

    # along y
    actual = da.cumulative_integrate("y")
    expected_y = xr.DataArray(
        cumtrapz(da, da["y"], axis=1, initial=0.0),
        dims=["x", "y"],
        coords=da.coords,
    )
    assert_allclose(expected_y, actual.compute())
    assert_equal(actual, ds.cumulative_integrate("y")["var"])
    assert_equal(
        ds["var"].cumulative_integrate("y"),
        ds.cumulative_integrate("y")["var"],
    )

    # along x and y
    actual = da.cumulative_integrate(("y", "x"))
    assert actual.ndim == 2

    with pytest.raises(ValueError):
        da.cumulative_integrate("x2d")
//  [python]
def test_cumulative_integrate(dask) -> None:
    rs = np.random.RandomState(43)
    coord = [0.2, 0.35, 0.4, 0.6, 0.7, 0.75, 0.76, 0.8]

    da = xr.DataArray(
        rs.randn(8, 6),
        dims=["x", "y"],
        coords={
            "x": coord,
            "x2": (("x",), rs.randn(8)),
            "z": 3,
            "x2d": (("x", "y"), rs.randn(8, 6)),
        },
    )
    if dask and has_dask:
        da = da.chunk({"x": 4})

    ds = xr.Dataset({"var": da})

    # along x
    actual = da.cumulative_integrate("x")

    # From scipy-1.6.0 cumtrapz is renamed to cumulative_trapezoid, but cumtrapz is
    # still provided for backward compatibility
    from scipy.integrate import cumulative_trapezoid

    expected_x = xr.DataArray(
        cumulative_trapezoid(da.compute(), da["x"], axis=0, initial=0.0),
        dims=["x", "y"],
        coords=da.coords,
    )
    assert_allclose(expected_x, actual.compute())
    assert_equal(
        ds["var"].cumulative_integrate("x"),
        ds.cumulative_integrate("x")["var"],
    )

    # make sure result is also a dask array (if the source is dask array)
    assert isinstance(actual.data, type(da.data))

    # along y
    actual = da.cumulative_integrate("y")
    expected_y = xr.DataArray(
        cumulative_trapezoid(da, da["y"], axis=1, initial=0.0),
        dims=["x", "y"],
        coords=da.coords,
    )
    assert_allclose(expected_y, actual.compute())
    assert_equal(actual, ds.cumulative_integrate("y")["var"])
    assert_equal(
        ds["var"].cumulative_integrate("y"),
        ds.cumulative_integrate("y")["var"],
    )

    # along x and y
    actual = da.cumulative_integrate(("y", "x"))
    assert actual.ndim == 2

    with pytest.raises(ValueError):
        da.cumulative_integrate("x2d")
//  [python]
def get_polarization_transients_from_dps(data_dict):
    """
    Integrate dps.

    args:
        data_dict (dict): dict with keys 'time', 'dp'

    returns:
        (dict): dict with keys 'intdp' and 'time'. 'intdp' is 
            ```
            scipy.integrate.cumtrapz(dp, x = 'time')
            ```
    """ 
    warnings.showwarning('get_polarization_transients_from_dps is deprecated. See integrate() instead. Consider using get_pol_trans_from_dps()', DeprecationWarning, '', 0,)
    assert 'dp' in set(data_dict.keys()), "data_dict must contain key 'dp'. It does not. Keys are {}".format(data_dict.keys())

    dp_ida = iterable_data_array(data_dict, 'dp')
    time_ida = iterable_data_array(data_dict, 'time')

    out = {'time':data_array_builder(), 'intdp':data_array_builder()}

    for dp, time in zip(dp_ida, time_ida):
        dp = np.nan_to_num(dp, 0)
        time = np.nan_to_num(time, 0)
        intdp = cumtrapz(dp, x = time)
        intdp = np.concatenate((np.array([0]), intdp))
        out['time'].append(time)
        out['intdp'].append(intdp)

    return {key: out[key].build() for key in out}
//  [python]
def get_polarization_transients_from_dps(data_dict):
    """
    Integrate dps.

    args:
        data_dict (dict): dict with keys 'time', 'dp'

    returns:
        (dict): dict with keys 'intdp' and 'time'. 'intdp' is 
            
            scipy.integrate.cumtrapz(dp, x = 'time')
            
    """ 
    warnings.showwarning('get_polarization_transients_from_dps is deprecated. See integrate() instead. Consider using get_pol_trans_from_dps()', DeprecationWarning, '', 0,)
    assert 'dp' in set(data_dict.keys()), "data_dict must contain key 'dp'. It does not. Keys are {}".format(data_dict.keys())

    dp_ida = iterable_data_array(data_dict, 'dp')
    time_ida = iterable_data_array(data_dict, 'time')

    out = {'time':data_array_builder(), 'intdp':data_array_builder()}

    for dp, time in zip(dp_ida, time_ida):
        dp = np.nan_to_num(dp, 0)
        time = np.nan_to_num(time, 0)
        intdp = cumulative_trapezoid(dp, x = time)
        intdp = np.concatenate((np.array([0]), intdp))
        out['time'].append(time)
        out['intdp'].append(intdp)

    return {key: out[key].build() for key in out}
//  [python]
def _h(self, c=None, m=None) -> [float, np.ndarray]:
        """
        The integral of f(x)*x^2 out to c

        .. note:: This function should be replaced with an analytic solution if
                  possible in derived classes.

        Parameters
        ----------
        c : float or array_like, optional
            The halo_concentration(s) of the halo(s). Used ONLY if m is not specified.
        m : float or array_like, optional
            The mass of the halo. Determines the halo_concentration if provided.
        """
        if c is None and m is None:
            raise ValueError("Either c or m must be provided.")
        if m is not None:
            c = self.cm_relation(m)

        x, dx = np.linspace(1e-6, np.max(c), 2000, retstep=True)
        integrand = self._f(x) * x ** 2

        integ = intg.cumtrapz(integrand, dx=dx, initial=0)

        if not hasattr(c, "__len__"):
            return integ[-1]
        else:
            sp = spline(x, integ, k=3)
            return sp(c)
//  [python]
def _h(self, c=None, m=None) -> [float, np.ndarray]:
        """
        The integral of f(x)*x^2 out to c

        .. note:: This function should be replaced with an analytic solution if
                  possible in derived classes.

        Parameters
        ----------
        c : float or array_like, optional
            The halo_concentration(s) of the halo(s). Used ONLY if m is not specified.
        m : float or array_like, optional
            The mass of the halo. Determines the halo_concentration if provided.
        """
        if c is None and m is None:
            raise ValueError("Either c or m must be provided.")
        if m is not None:
            c = self.cm_relation(m)

        x, dx = np.linspace(1e-6, np.max(c), 2000, retstep=True)
        integrand = self._f(x) * x ** 2

        integ = integrate.cumulative_trapezoid(integrand, dx=dx, initial=0)

        if not hasattr(c, "__len__"):
            return integ[-1]
        else:
            sp = spline(x, integ, k=3)
            return sp(c)
//  [python]
def phi(self, radius, vlum):
        """Computes potential. phi = integrate vlum^2/r/c^2"""
        
        x = radius
        y = np.square(vlum) / (x*c*c)
        x = np.concatenate([np.array([0]), x])
        y = np.concatenate([np.array([0]), y])
        #May want to switch to Simpson's rule: scipy.integrate.simps
        return scipy.integrate.cumtrapz(y,x)
//  [python]
def phi(self, radius, vlum):
        """Computes potential. phi = integrate vlum^2/r/c^2"""
        
        x = radius
        y = np.square(vlum) / (x*c*c)
        x = np.concatenate([np.array([0]), x])
        y = np.concatenate([np.array([0]), y])
        #May want to switch to Simpson's rule: scipy.integrate.simps
        return scipy.integrate.cumulative_trapezoid(y,x)
//  [python]
cumsum_JxJx = scipy.integrate.cumtrapz(JxJx,initial=0)*scale; #np.insert(cumsum_JxJx, 0, 0)
cumsum_JyJy = scipy.integrate.cumtrapz(JyJy,initial=0)*scale
//  [python]
cumsum_JxJx = scipy.integrate.cumulative_trapezoid(JxJx,initial=0)*scale; #np.insert(cumsum_JxJx, 0, 0)
cumsum_JyJy = scipy.integrate.cumulative_trapezoid(JyJy,initial=0)*scale
//  [python]
def ana_likelihood(self, rv, leff, fakedata, errors, model, prior, mylikelihood=LogLikelihood, covariance_model_funct=None, otherp=None, nbins=None):
        ll = mylikelihood(xvals=leff, yvals=fakedata, errors=errors, model = model, flatprior=prior,
                                    covariance_model_funct=covariance_model_funct, nbins=nbins)

        like = np.zeros_like(rv)
        for i in range(len(rv)):
            like[i] = np.exp(ll([rv[i]]))
            maxL = rv[like == np.max(like)]
            cumint = scipy.integrate.cumtrapz(like, x=rv)
            cumint = cumint / np.max(cumint)
            onesigma = np.interp(0.68, cumint, rv[1:])
        if otherp:
            other = np.interp(otherp, cumint, rv[1:])
            return like, cumint, onesigma, other, maxL
        else:
            return like, cumint, onesigma, maxL
//  [python]
def ana_likelihood(self, rv, leff, fakedata, errors, model, prior, mylikelihood=LogLikelihood, covariance_model_funct=None, otherp=None, nbins=None):
        ll = mylikelihood(xvals=leff, yvals=fakedata, errors=errors, model = model, flatprior=prior,
                                    covariance_model_funct=covariance_model_funct, nbins=nbins)

        like = np.zeros_like(rv)
        for i in range(len(rv)):
            like[i] = np.exp(ll([rv[i]]))
            maxL = rv[like == np.max(like)]
            cumint = scipy.integrate.cumulative_trapezoid(like, x=rv)
            cumint = cumint / np.max(cumint)
            onesigma = np.interp(0.68, cumint, rv[1:])
        if otherp:
            other = np.interp(otherp, cumint, rv[1:])
            return like, cumint, onesigma, other, maxL
        else:
            return like, cumint, onesigma, maxL
//  [python]
def chi_inc(aLTe, Qe_MWm2, Te_keV, a_m, ne_20, aLTe_base, order=2):
    Te_J = Te_keV * 1e3 * e_J
    ne = ne_20 * 1e20

    Chi_inc_norm, Chi_inc_norm_x, x_grid, y_grid, fit_params = MATHtools.deriv_fit(
        aLTe, Qe_MWm2, aLTe_base, order=order
    )

    # Chi incremental at the base location
    Chi_inc = Chi_inc_norm * (1.0e6 / (Te_J * ne / a_m))  # m^2/s
    # Chi incremental array
    Chi_inc_x = Chi_inc_norm_x * (1.0e6 / (Te_J * ne / a_m))  # m^2/s

    gradTe = -x_grid * (Te_J / a_m)
    gradTe_base = -aLTe_base * (Te_J / a_m)
    Qe_base = Qe_MWm2[np.argmin(np.abs(aLTe - aLTe_base))]
    Chi_eff = -Qe_base / gradTe_base * (1.0e6 / ne)

    # Calculate power balance chi
    Chi_pb_x = (1 / gradTe) * scipy.integrate.cumtrapz(Chi_inc_x, gradTe, initial=0)
    Chi_pb = np.interp(aLTe_base, x_grid, Chi_pb_x)

    QeCond = -ne * Chi_pb * gradTe_base  # W/m^2
    QeConv = Qe_base * 1e6 - QeCond  # W/m^2
    Vpinch = QeConv / (Te_J * ne)  # m/s

    return Chi_inc, x_grid, y_grid, Chi_pb, Vpinch, Chi_eff
//  [python]
def chi_inc(aLTe, Qe_MWm2, Te_keV, a_m, ne_20, aLTe_base, order=2):
    Te_J = Te_keV * 1e3 * e_J
    ne = ne_20 * 1e20

    Chi_inc_norm, Chi_inc_norm_x, x_grid, y_grid, fit_params = MATHtools.deriv_fit(
        aLTe, Qe_MWm2, aLTe_base, order=order
    )

    # Chi incremental at the base location
    Chi_inc = Chi_inc_norm * (1.0e6 / (Te_J * ne / a_m))  # m^2/s
    # Chi incremental array
    Chi_inc_x = Chi_inc_norm_x * (1.0e6 / (Te_J * ne / a_m))  # m^2/s

    gradTe = -x_grid * (Te_J / a_m)
    gradTe_base = -aLTe_base * (Te_J / a_m)
    Qe_base = Qe_MWm2[np.argmin(np.abs(aLTe - aLTe_base))]
    Chi_eff = -Qe_base / gradTe_base * (1.0e6 / ne)

    # Calculate power balance chi
    Chi_pb_x = (1 / gradTe) * scipy.integrate.cumulative_trapezoid(Chi_inc_x, gradTe, initial=0)
    Chi_pb = np.interp(aLTe_base, x_grid, Chi_pb_x)

    QeCond = -ne * Chi_pb * gradTe_base  # W/m^2
    QeConv = Qe_base * 1e6 - QeCond  # W/m^2
    Vpinch = QeConv / (Te_J * ne)  # m/s

    return Chi_inc, x_grid, y_grid, Chi_pb, Vpinch, Chi_eff
//  [python]
def PhotonsPerBaryon(self, Emin, Emax, raw=True, return_all_t=False):
        """
        Compute the number of photons emitted per unit stellar baryon.

        ..note:: This integrand over the provided band, and cumulatively over time.

        Parameters
        ----------
        Emin : int, float
            Minimum rest-frame photon energy to consider [eV].
        Emax : int, float
            Maximum rest-frame photon energy to consider [eV].

        Returns
        -------
        An array with the same dimensions as ``self.times``, representing the
        cumulative number of photons emitted per stellar baryon of star formation
        as a function of time.

        """

        #assert self.pf['pop_ssp'], "Probably shouldn't do this for continuous SF."
        photons_per_s_per_msun = self.IntegratedEmission(Emin, Emax, raw=raw,
            energy_units=False)

        # Current units:
        # if pop_ssp:
        #     photons / sec / Msun
        # else:
        #     photons / sec / (Msun / yr)

        # Integrate (cumulatively) over time
        if self.pf['source_ssp']:
            photons_per_b_t = photons_per_s_per_msun / self.cosm.b_per_msun
            if return_all_t:
                return cumtrapz(photons_per_b_t, x=self.times*s_per_myr,
                    initial=0.0)
            else:
                return np.trapz(photons_per_b_t, x=self.times*s_per_myr)
        # Take steady-state result
        else:
            photons_per_b_t = photons_per_s_per_msun * s_per_yr \
                / self.cosm.b_per_msun

            # Return last element: steady state result
            return photons_per_b_t[-1]
//  [python]
def PhotonsPerBaryon(self, Emin, Emax, raw=True, return_all_t=False):
        """
        Compute the number of photons emitted per unit stellar baryon.

        ..note:: This integrand over the provided band, and cumulatively over time.

        Parameters
        ----------
        Emin : int, float
            Minimum rest-frame photon energy to consider [eV].
        Emax : int, float
            Maximum rest-frame photon energy to consider [eV].

        Returns
        -------
        An array with the same dimensions as ``self.times``, representing the
        cumulative number of photons emitted per stellar baryon of star formation
        as a function of time.

        """

        #assert self.pf['pop_ssp'], "Probably shouldn't do this for continuous SF."
        photons_per_s_per_msun = self.IntegratedEmission(Emin, Emax, raw=raw,
            energy_units=False)

        # Current units:
        # if pop_ssp:
        #     photons / sec / Msun
        # else:
        #     photons / sec / (Msun / yr)

        # Integrate (cumulatively) over time
        if self.pf['source_ssp']:
            photons_per_b_t = photons_per_s_per_msun / self.cosm.b_per_msun
            if return_all_t:
                return cumulative_trapezoid(photons_per_b_t, x=self.times*s_per_myr,
                    initial=0.0)
            else:
                return np.trapz(photons_per_b_t, x=self.times*s_per_myr)
        # Take steady-state result
        else:
            photons_per_b_t = photons_per_s_per_msun * s_per_yr \
                / self.cosm.b_per_msun

            # Return last element: steady state result
            return photons_per_b_t[-1]
//  [python]
def integrate(x, deriv):
    return np.append([0], scipy.integrate.cumtrapz(deriv, x=x))
//  [python]
def integrate(x, deriv):
    return np.append([0], scipy.integrate.cumulative_trapezoid(deriv, x=x))
//  [python]
def rebin(x, xp, fp):
  ''' Resample a function fp(xp) over the new grid x, rebinning if necessary, 
    otherwise interpolates
    Parameters
    ----------
    x	: 	array like
	New coordinates
    fp 	:	array like
	y-coordinates to be resampled
    xp 	:	array like
	x-coordinates at which fp are sampled
	
    Returns
    -------
    out	: 	array like
	new samples
  
  '''
  
  if (x.units != xp.units):
    print(x.units, xp.units)
    exosim_error('Units mismatch')
  
  idx = np.where(np.logical_and(xp > 0.9*x.min(), xp < 1.1*x.max()))[0]
  xp = xp[idx]
  fp = fp[idx]
  
  if np.diff(xp).min() < np.diff(x).min():
    # Binning!
    c = cumtrapz(fp, x=xp)*fp.units*xp.units	
    xpc = xp[1:]
        
    delta = np.gradient(x)
    new_c_1 = np.interp(x-0.5*delta, xpc, c, 
                        left=0.0, right=0.0)*c.units
    new_c_2 = np.interp(x+0.5*delta, xpc, c, 
                        left=0.0, right=0.0)*c.units
    new_f = (new_c_2 - new_c_1)/delta
  else:
    # Interpolate !
    new_f = np.interp(x, xp, fp, left=0.0, right=0.0)*fp.units
  
  '''
  import matplotlib.pyplot as plt
  plt.plot(xp, fp, '-')
  plt.plot(x, new_f, '.-')
  plt.show()
  # check
  print np.trapz(new_f, x)
  idx = np.where(np.logical_and(xp>= x.min(), xp <= x.max()))
  print np.trapz(fp[idx], xp[idx])
  '''
  return x, new_f
//  [python]
def rebin(x, xp, fp):
  ''' Resample a function fp(xp) over the new grid x, rebinning if necessary, 
    otherwise interpolates
    Parameters
    ----------
    x	: 	array like
	New coordinates
    fp 	:	array like
	y-coordinates to be resampled
    xp 	:	array like
	x-coordinates at which fp are sampled
	
    Returns
    -------
    out	: 	array like
	new samples
  
  '''
  
  if (x.units != xp.units):
    print(x.units, xp.units)
    exosim_error('Units mismatch')
  
  idx = np.where(np.logical_and(xp > 0.9*x.min(), xp < 1.1*x.max()))[0]
  xp = xp[idx]
  fp = fp[idx]
  
  if np.diff(xp).min() < np.diff(x).min():
    # Binning!
    c = cumulative_trapezoid(fp, x=xp)*fp.units*xp.units	
    xpc = xp[1:]
        
    delta = np.gradient(x)
    new_c_1 = np.interp(x-0.5*delta, xpc, c, 
                        left=0.0, right=0.0)*c.units
    new_c_2 = np.interp(x+0.5*delta, xpc, c, 
                        left=0.0, right=0.0)*c.units
    new_f = (new_c_2 - new_c_1)/delta
  else:
    # Interpolate !
    new_f = np.interp(x, xp, fp, left=0.0, right=0.0)*fp.units
  
  '''
  import matplotlib.pyplot as plt
  plt.plot(xp, fp, '-')
  plt.plot(x, new_f, '.-')
  plt.show()
  # check
  print np.trapz(new_f, x)
  idx = np.where(np.logical_and(xp>= x.min(), xp <= x.max()))
  print np.trapz(fp[idx], xp[idx])
  '''
  return x, new_f
//  [python]
def initialize_from_data_empspect(self, train_x: torch.Tensor, train_y: torch.Tensor):
        """
        Initialize mixture components based on the empirical spectrum of the data.
        This will often be better than the standard initialize_from_data method, but it assumes
        that your inputs are evenly spaced.

        :param torch.Tensor train_x: Training inputs
        :param torch.Tensor train_y: Training outputs
        """

        import numpy as np
        from scipy.fftpack import fft
        from scipy.integrate import cumtrapz

        with torch.no_grad():
            if not torch.is_tensor(train_x) or not torch.is_tensor(train_y):
                raise RuntimeError("train_x and train_y should be tensors")
            if train_x.ndimension() == 1:
                train_x = train_x.unsqueeze(-1)
            if self.active_dims is not None:
                train_x = train_x[..., self.active_dims]

            # Flatten batch dimensions
            train_x = train_x.view(-1, train_x.size(-1))
            train_y = train_y.view(-1)

            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(1000, self.ard_num_dims)
            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

            from sklearn.mixture import GaussianMixture

            GMM = GaussianMixture(n_components=self.num_mixtures, covariance_type="diag").fit(inv_spec)
            means = GMM.means_
            varz = GMM.covariances_
            weights = GMM.weights_

            dtype = self.raw_mixture_means.dtype
            device = self.raw_mixture_means.device
            self.mixture_means = torch.tensor(means, dtype=dtype, device=device).unsqueeze(-2)
            self.mixture_scales = torch.tensor(varz, dtype=dtype, device=device).unsqueeze(-2)
            self.mixture_weights = torch.tensor(weights, dtype=dtype, device=device)
//  [python]
def initialize_from_data_empspect(self, train_x: torch.Tensor, train_y: torch.Tensor):
        """
        Initialize mixture components based on the empirical spectrum of the data.
        This will often be better than the standard initialize_from_data method, but it assumes
        that your inputs are evenly spaced.

        :param torch.Tensor train_x: Training inputs
        :param torch.Tensor train_y: Training outputs
        """

        import numpy as np
        from scipy.fftpack import fft
        from scipy.integrate import cumulative_trapezoid

        with torch.no_grad():
            if not torch.is_tensor(train_x) or not torch.is_tensor(train_y):
                raise RuntimeError("train_x and train_y should be tensors")
            if train_x.ndimension() == 1:
                train_x = train_x.unsqueeze(-1)
            if self.active_dims is not None:
                train_x = train_x[..., self.active_dims]

            # Flatten batch dimensions
            train_x = train_x.view(-1, train_x.size(-1))
            train_y = train_y.view(-1)

            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(1000, self.ard_num_dims)
            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

            from sklearn.mixture import GaussianMixture

            GMM = GaussianMixture(n_components=self.num_mixtures, covariance_type="diag").fit(inv_spec)
            means = GMM.means_
            varz = GMM.covariances_
            weights = GMM.weights_

            dtype = self.raw_mixture_means.dtype
            device = self.raw_mixture_means.device
            self.mixture_means = torch.tensor(means, dtype=dtype, device=device).unsqueeze(-2)
            self.mixture_scales = torch.tensor(varz, dtype=dtype, device=device).unsqueeze(-2)
            self.mixture_weights = torch.tensor(weights, dtype=dtype, device=device)
//  [python]
def measure_eps(self, energy_dist, e_inj, prtl_type):
        injloc=self.gamma.searchsorted(e_inj)

        if injloc >= len(energy_dist)-1:
            return 0.0

        else:
            '''
            HERE is a trapezoidal integration in logspace not using anymore
            because it goes bad when energy_dist is zero. I'm leaving it in, commented out in case it is
            valuable later.
            # do a cummulative
            LogY = np.log(energy_dist[e_injloc-1:])
            # Make all values where LogY ==-inf == -1E4 effectively set them to zero
            LogY[np.isinf(LogY)] = -1E4
            LogX = np.log(self.gamma[e_injloc-1:])
            dLogY = np.diff(LogY)
            dLogX = np.diff(LogX)
            dF = np.exp(LogY[:-1]+LogX[:-1])
            dF *= dLogX*(np.exp(dLogX+dLogY)-1)
            dF *= (dLogX+dLogY)**(-1)
            endSumF = np.cumsum(dF[::-1])[::-1]
            '''
            # Do a reverse cummulative trapz integration over the energy_dist
            endSum = cumtrapz(energy_dist[::-1],self.gamma[::-1])[::-1]
            endSum *= -1

            logF = np.nan

            if injloc ==0:
                logF = np.log(endSum[0])
            else:
                # The answer we want will be between the [injloc-1] and [injloc] place in
                # endSumF We do linear interpolation in logspace to find the answer
                logY_left = np.log(endSum[injloc-1])
                logY_right = np.log(endSum[injloc])
                logX_left = np.log(self.gamma[injloc-1])
                logX_right = np.log(self.gamma[injloc])
                # do the linear interpolation in logspace
                logF = np.log(e_inj)-logX_left
                logF *= (logX_right-logX_left)**(-1)
                logF *= logY_right-logY_left
                logF += logY_left

            # Now calculate the eps
            if prtl_type == 'electron':
                psum = np.trapz(self.pdist,self.gamma)
                return np.exp(logF)/psum * self.FigWrap.LoadKey('me')[0]/self.FigWrap.LoadKey('mi')[0]
            else:
                return np.exp(logF)/endSum[0]
//  [python]
def measure_eps(self, energy_dist, e_inj, prtl_type):
        injloc=self.gamma.searchsorted(e_inj)

        if injloc >= len(energy_dist)-1:
            return 0.0

        else:
            '''
            HERE is a trapezoidal integration in logspace not using anymore
            because it goes bad when energy_dist is zero. I'm leaving it in, commented out in case it is
            valuable later.
            # do a cummulative
            LogY = np.log(energy_dist[e_injloc-1:])
            # Make all values where LogY ==-inf == -1E4 effectively set them to zero
            LogY[np.isinf(LogY)] = -1E4
            LogX = np.log(self.gamma[e_injloc-1:])
            dLogY = np.diff(LogY)
            dLogX = np.diff(LogX)
            dF = np.exp(LogY[:-1]+LogX[:-1])
            dF *= dLogX*(np.exp(dLogX+dLogY)-1)
            dF *= (dLogX+dLogY)**(-1)
            endSumF = np.cumsum(dF[::-1])[::-1]
            '''
            # Do a reverse cummulative trapz integration over the energy_dist
            endSum = cumulative_trapezoid(energy_dist[::-1],self.gamma[::-1])[::-1]
            endSum *= -1

            logF = np.nan

            if injloc ==0:
                logF = np.log(endSum[0])
            else:
                # The answer we want will be between the [injloc-1] and [injloc] place in
                # endSumF We do linear interpolation in logspace to find the answer
                logY_left = np.log(endSum[injloc-1])
                logY_right = np.log(endSum[injloc])
                logX_left = np.log(self.gamma[injloc-1])
                logX_right = np.log(self.gamma[injloc])
                # do the linear interpolation in logspace
                logF = np.log(e_inj)-logX_left
                logF *= (logX_right-logX_left)**(-1)
                logF *= logY_right-logY_left
                logF += logY_left

            # Now calculate the eps
            if prtl_type == 'electron':
                psum = np.trapz(self.pdist,self.gamma)
                return np.exp(logF)/psum * self.FigWrap.LoadKey('me')[0]/self.FigWrap.LoadKey('mi')[0]
            else:
                return np.exp(logF)/endSum[0]
//  [python]
def integrated(self, dx=None):
        """Return a series that is the integral computed with method of
        the rectangles.

        The spacing ``dx`` can be optionally provided. If provided, it will be
        used (increasing performance), otherwise it will be computed internally.

        :param dx: Delta x in the independent variable. If None it will be
                   computed internally.
        :type dx: float or None
        :returns:  New series with the cumulative integral.
        :rtype:    :py:class:`~.BaseSeries` or derived class

        """
        if self.is_masked():
            raise RuntimeError(
                "Integration with masked data is not supported."
            )

        # We pass self.x only if dx was not provided
        passing_x = self.x if dx is None else None
        return type(self)(
            self.x,
            integrate.cumtrapz(self.y, x=passing_x, dx=dx, initial=0),
            True,
        )
//  [python]
def integrated(self, dx=None):
        """Return a series that is the integral computed with method of
        the rectangles.

        The spacing ``dx`` can be optionally provided. If provided, it will be
        used (increasing performance), otherwise it will be computed internally.

        :param dx: Delta x in the independent variable. If None it will be
                   computed internally.
        :type dx: float or None
        :returns:  New series with the cumulative integral.
        :rtype:    :py:class:`~.BaseSeries` or derived class

        """
        if self.is_masked():
            raise RuntimeError(
                "Integration with masked data is not supported."
            )

        # We pass self.x only if dx was not provided
        passing_x = self.x if dx is None else None
        return type(self)(
            self.x,
            integrate.cumulative_trapezoid(self.y, x=passing_x, dx=dx, initial=0),
            True,
        )
//  [python]
def DSAR(data, samp_rate, datas, freqs_names, freqs, Nm, N):
    # compute dsar
    data = scipy.integrate.cumtrapz(data, dx=1./100, initial=0) # vel to disp
    data -= np.mean(data) # detrend('mean')
    j = freqs_names.index('mf')
    mfd = obspy.signal.filter.bandpass(data, freqs[j][0], freqs[j][1], samp_rate)
    mfd = abs(mfd[:Nm])
    mfd = mfd.reshape(-1,N).mean(axis=-1)
    j = freqs_names.index('hf')
    hfd = obspy.signal.filter.bandpass(data, freqs[j][0], freqs[j][1], samp_rate)
    hfd = abs(hfd[:Nm])
    hfd = hfd.reshape(-1,N).mean(axis=-1)
    dsar = mfd/hfd
    datas.append(dsar)
    return(datas, dsar)
//  [python]
def DSAR(data, samp_rate, datas, freqs_names, freqs, Nm, N):
    # compute dsar
    data = scipy.integrate.cumulative_trapezoid(data, dx=1./100, initial=0) # vel to disp
    data -= np.mean(data) # detrend('mean')
    j = freqs_names.index('mf')
    mfd = obspy.signal.filter.bandpass(data, freqs[j][0], freqs[j][1], samp_rate)
    mfd = abs(mfd[:Nm])
    mfd = mfd.reshape(-1,N).mean(axis=-1)
    j = freqs_names.index('hf')
    hfd = obspy.signal.filter.bandpass(data, freqs[j][0], freqs[j][1], samp_rate)
    hfd = abs(hfd[:Nm])
    hfd = hfd.reshape(-1,N).mean(axis=-1)
    dsar = mfd/hfd
    datas.append(dsar)
    return(datas, dsar)
//  [python]
def _compute_pressure(self):
        # Calculate the pressure profile based on density and gravity.
        # This integrates the equation for hydrostatic equilibrium P = rho g z.
        radii = self.table_radius
        density = self.table_density
        gravity = self.gravity(self.earth_radius - radii)

        # convert radii to depths
        depth = self.earth_radius - radii
        pressure = scipy.integrate.cumtrapz(gravity * density, x=depth, initial=0.0)

        self.table_pressure = pressure
//  [python]
def _compute_pressure(self):
        # Calculate the pressure profile based on density and gravity.
        # This integrates the equation for hydrostatic equilibrium P = rho g z.
        radii = self.table_radius
        density = self.table_density
        gravity = self.gravity(self.earth_radius - radii)

        # convert radii to depths
        depth = self.earth_radius - radii
        pressure = scipy.integrate.cumulative_trapezoid(gravity * density, x=depth, initial=0.0)

        self.table_pressure = pressure
//  [python]
def integral(self, start=0, stop=None) -> Function1D:
        r"""
        Cumulatively integrate y(x) from start to stop using the composite trapezoidal rule.

        Returns:
            :class:`Function1D` with :math:`\int y(x) dx`
        """
        if stop is None: stop = len(self.values) + 1
        x, y = self.mesh[start:stop], self.values[start:stop]
        from scipy.integrate import cumtrapz
        integ = cumtrapz(y, x=x, initial=0.0)

        return self.__class__(x, integ)
//  [python]
def integral(self, start=0, stop=None) -> Function1D:
        r"""
        Cumulatively integrate y(x) from start to stop using the composite trapezoidal rule.

        Returns:
            :class:`Function1D` with :math:`\int y(x) dx`
        """
        if stop is None: stop = len(self.values) + 1
        x, y = self.mesh[start:stop], self.values[start:stop]
        from scipy.integrate import cumulative_trapezoid
        integ = cumulative_trapezoid(y, x=x, initial=0.0)

        return self.__class__(x, integ)
//  [python]
def _compute_gravity(self):
        # Calculate the gravity of the planet, based on a density profile.
        # There is a check of the surface value is within reason for this
        # model, otherwise values for PREM are used.

        density = self.table_density[::-1]

        if len(density) > 0:
            radii = self.table_radius[::-1]
            g = scipy.integrate.cumtrapz(
                constants.G * 4.0 * np.pi * density * radii * radii, x=radii, initial=0
            )
            g[1:] = g[1:] / radii[1:] / radii[1:]

            self.table_gravity = g[::-1]
        else:
            raise ValueError(
                "Density profile is an empty list " "in this SeismicTable instance."
            )
//  [python]
def _compute_gravity(self):
        # Calculate the gravity of the planet, based on a density profile.
        # There is a check of the surface value is within reason for this
        # model, otherwise values for PREM are used.

        density = self.table_density[::-1]

        if len(density) > 0:
            radii = self.table_radius[::-1]
            g = scipy.integrate.cumulative_trapezoid(
                constants.G * 4.0 * np.pi * density * radii * radii, x=radii, initial=0
            )
            g[1:] = g[1:] / radii[1:] / radii[1:]

            self.table_gravity = g[::-1]
        else:
            raise ValueError(
                "Density profile is an empty list " "in this SeismicTable instance."
            )
//  [python]
def test_cumtrapz_2(self):
        import scipy.integrate
        y = np.random.randn(100)
        np.testing.assert_almost_equal(bm.cumtrapz(y),
                                       scipy.integrate.cumtrapz(y),
                                       decimal=8)
//  [python]
def test_cumtrapz_2(self):
        import scipy.integrate
        y = np.random.randn(100)
        np.testing.assert_almost_equal(bm.cumtrapz(y),
                                       scipy.integrate.cumulative_trapezoid(y),
                                       decimal=8)
//  [python]
def test_cumtrapz_1(self):
        import scipy.integrate
        y = np.random.randn(100)
        initial = np.random.rand()
        np.testing.assert_almost_equal(bm.cumtrapz(y, initial=initial),
                                       scipy.integrate.cumtrapz(
                                           y, initial=initial),
                                       decimal=8)
//  [python]
def test_cumtrapz_1(self):
        import scipy.integrate
        y = np.random.randn(100)
        initial = np.random.rand()
        np.testing.assert_almost_equal(bm.cumtrapz(y, initial=initial),
                                       scipy.integrate.cumulative_trapezoid(
                                           y, initial=initial),
                                       decimal=8)
//  [python]
def moment_area(self, *event):
        if self.has_run == 1:
            pass
        else:
            self.run()

        ml = self.momentl
        mc = self.momentc
        mr = self.momentr

        xsl = self.xsl
        xsc = self.xsc - xsl[-1]
        xsr = self.xsr - self.xsc[-1]

        al = sci_int.cumtrapz(ml, xsl, initial = 0)
        ac = sci_int.cumtrapz(mc, xsc, initial = 0)
        ar = sci_int.cumtrapz(mr, xsr, initial = 0)

        m_xl = []
        m_xc = []
        m_xr = []

        for i in range(0,len(xsl)):
            m_xl.append(ml[i]*xsl[i])
            m_xc.append(mc[i]*xsc[i])
            m_xr.append(mr[i]*xsr[i])

        m_xl_a = sci_int.cumtrapz(m_xl, xsl, initial = 0)
        m_xc_a = sci_int.cumtrapz(m_xc, xsc, initial = 0)
        m_xr_a = sci_int.cumtrapz(m_xr, xsr, initial = 0)

        xl_l = (1/al[-1])*m_xl_a[-1]
        xl_c = (1/ac[-1])*m_xc_a[-1]
        xl_r = (1/ar[-1])*m_xr_a[-1]

        xr_l = xsl[-1] - xl_l
        xr_c = xsc[-1] - xl_c
        xr_r = xsr[-1] - xl_r

        res_string = 'Left:\nA = {0:.4f}   Xleft = {1:.4f} m    Xright = {2:.4f} m\n'.format(al[-1],xl_l,xr_l)
        res_string = res_string+'Center:\nA = {0:.4f}   Xleft = {1:.4f} m    Xright = {2:.4f} m\n'.format(ac[-1],xl_c,xr_c)
        res_string = res_string+'Right:\nA = {0:.4f}   Xleft = {1:.4f} m    Xright = {2:.4f} m\n'.format(ar[-1],xl_r,xr_r)

        self.moment_area_label.configure(text=res_string)
//  [python]
def moment_area(self, *event):
        if self.has_run == 1:
            pass
        else:
            self.run()

        ml = self.momentl
        mc = self.momentc
        mr = self.momentr

        xsl = self.xsl
        xsc = self.xsc - xsl[-1]
        xsr = self.xsr - self.xsc[-1]

        al = sci_int.cumulative_trapezoid(ml, xsl, initial = 0)
        ac = sci_int.cumulative_trapezoid(mc, xsc, initial = 0)
        ar = sci_int.cumulative_trapezoid(mr, xsr, initial = 0)

        m_xl = []
        m_xc = []
        m_xr = []

        for i in range(0,len(xsl)):
            m_xl.append(ml[i]*xsl[i])
            m_xc.append(mc[i]*xsc[i])
            m_xr.append(mr[i]*xsr[i])

        m_xl_a = sci_int.cumulative_trapezoid(m_xl, xsl, initial = 0)
        m_xc_a = sci_int.cumulative_trapezoid(m_xc, xsc, initial = 0)
        m_xr_a = sci_int.cumulative_trapezoid(m_xr, xsr, initial = 0)

        xl_l = (1/al[-1])*m_xl_a[-1]
        xl_c = (1/ac[-1])*m_xc_a[-1]
        xl_r = (1/ar[-1])*m_xr_a[-1]

        xr_l = xsl[-1] - xl_l
        xr_c = xsc[-1] - xl_c
        xr_r = xsr[-1] - xl_r

        res_string = 'Left:\nA = {0:.4f}   Xleft = {1:.4f} m    Xright = {2:.4f} m\n'.format(al[-1],xl_l,xr_l)
        res_string = res_string+'Center:\nA = {0:.4f}   Xleft = {1:.4f} m    Xright = {2:.4f} m\n'.format(ac[-1],xl_c,xr_c)
        res_string = res_string+'Right:\nA = {0:.4f}   Xleft = {1:.4f} m    Xright = {2:.4f} m\n'.format(ar[-1],xl_r,xr_r)

        self.moment_area_label.configure(text=res_string)
//  [python]
def test_cumtrapz_3(self):
        import scipy.integrate
        y = np.random.randn(100)
        dx = np.random.rand()
        np.testing.assert_almost_equal(bm.cumtrapz(y, dx=dx),
                                       scipy.integrate.cumtrapz(y, dx=dx),
                                       decimal=8)
//  [python]
def test_cumtrapz_3(self):
        import scipy.integrate
        y = np.random.randn(100)
        dx = np.random.rand()
        np.testing.assert_almost_equal(bm.cumtrapz(y, dx=dx),
                                       scipy.integrate.cumulative_trapezoid(y, dx=dx),
                                       decimal=8)
//  [python]
def test_cumtrapz_4(self):
        import scipy.integrate
        y = np.random.randn(100)
        dx = np.random.rand()
        initial = np.random.rand()
        np.testing.assert_almost_equal(bm.cumtrapz(y, initial=initial, dx=dx),
                                       scipy.integrate.cumtrapz(
                                           y, initial=initial, dx=dx),
                                       decimal=8)
//  [python]
def test_cumtrapz_4(self):
        import scipy.integrate
        y = np.random.randn(100)
        dx = np.random.rand()
        initial = np.random.rand()
        np.testing.assert_almost_equal(bm.cumtrapz(y, initial=initial, dx=dx),
                                       scipy.integrate.cumulative_trapezoid(
                                           y, initial=initial, dx=dx),
                                       decimal=8)
//  [python]
def give_r_sigr(r, like):
    maxL = r[like == like.max()]
    
    cumint = scipy.integrate.cumtrapz(like, x=r)
    cumint = cumint / np.max(cumint)
    onesigma = np.interp(0.68, cumint, r[1:])
    if len(maxL) != 1:
        maxL = maxL[0]
    
    return maxL, onesigma
//  [python]
def give_r_sigr(r, like):
    maxL = r[like == like.max()]
    
    cumint = scipy.integrate.cumulative_trapezoid(like, x=r)
    cumint = cumint / np.max(cumint)
    onesigma = np.interp(0.68, cumint, r[1:])
    if len(maxL) != 1:
        maxL = maxL[0]
    
    return maxL, onesigma
//  [python]
def discretize_distribution(tau, gamma, singularity_info=None):
    """
    Discretize a distribution into a discrete mass function
    :param ndarray tau: Tau array
    :param ndarray gamma: Array of distribution values
    :param list singularity_info: List of 2-tuples indicating mass (R) and location (tau) of singularities in the
    distribution which are not captured by gamma
    :return:
    """
    # Get cumulative integral
    cum_mass = cumtrapz(gamma, x=np.log(tau), initial=0)

    # Add singularity mass
    if singularity_info is not None:
        for si in singularity_info:
            r_s, tau_s = si
            cum_mass[tau >= tau_s] += r_s

    inc_mass = np.diff(cum_mass)

    # Pad with zero to maintain array size
    inc_mass = np.concatenate(([0], inc_mass))

    return inc_mass
//  [python]
def discretize_distribution(tau, gamma, singularity_info=None):
    """
    Discretize a distribution into a discrete mass function
    :param ndarray tau: Tau array
    :param ndarray gamma: Array of distribution values
    :param list singularity_info: List of 2-tuples indicating mass (R) and location (tau) of singularities in the
    distribution which are not captured by gamma
    :return:
    """
    # Get cumulative integral
    cum_mass = cumulative_trapezoid(gamma, x=np.log(tau), initial=0)

    # Add singularity mass
    if singularity_info is not None:
        for si in singularity_info:
            r_s, tau_s = si
            cum_mass[tau >= tau_s] += r_s

    inc_mass = np.diff(cum_mass)

    # Pad with zero to maintain array size
    inc_mass = np.concatenate(([0], inc_mass))

    return inc_mass
//  [python]
def fit_bounded_keelin_3(a, grids, pdfs):

    import scipy.integrate as igt
    
    cdfs = igt.cumtrapz(pdfs, grids, initial = 0)
    
    if cdfs.ndim == 1:
        cdfs  = cdfs[np.newaxis, :]
        grids = grids[np.newaxis, :]
    

    cdfs =  cdfs / ((cdfs[:, -1])[:, np.newaxis])

    ret = np.zeros((cdfs.shape[0], 5))
    
    for i, quant in enumerate([0.001, a, 0.5, 1-a, 0.999]):
        
        for row in np.arange(cdfs.shape[0]):
            
            ret[row, i] = grids[row, np.searchsorted(cdfs[row], quant)]
            
    return ret
//  [python]
def fit_bounded_keelin_3(a, grids, pdfs):

    import scipy.integrate as igt
    
    cdfs = igt.cumulative_trapezoid(pdfs, grids, initial = 0)
    
    if cdfs.ndim == 1:
        cdfs  = cdfs[np.newaxis, :]
        grids = grids[np.newaxis, :]
    

    cdfs =  cdfs / ((cdfs[:, -1])[:, np.newaxis])

    ret = np.zeros((cdfs.shape[0], 5))
    
    for i, quant in enumerate([0.001, a, 0.5, 1-a, 0.999]):
        
        for row in np.arange(cdfs.shape[0]):
            
            ret[row, i] = grids[row, np.searchsorted(cdfs[row], quant)]
            
    return ret
//  [python]
def integrate(y, x): # int_0^r 
    return np.hstack((0., cumtrapz(y, x)))
//  [python]
def integrate(y, x): # int_0^r 
    return np.hstack((0., scipy.integrate.cumulative_trapezoid(y, x)))
//  [python]
def GetFragility(SaCollapse):
    import numpy as np

    Mean = np.mean(SaCollapse)
    STD = np.std(SaCollapse)
    COV = STD/Mean

    Mu = np.log(Mean**2/(STD**2+Mean**2)**.5)
    Sigma = (np.log(1+COV**2))**.5

    Median = np.exp(Mu)

    Beta = np.std(np.log(SaCollapse))

    Sas = np.arange(0.01,max(SaCollapse),0.01)

    def Prob(x):
        return 1.0/x/(2.0*np.pi)**.5/Sigma*np.exp(-1.0*(np.log(x)-Mu)**2/2/Sigma**2)

    PDF = []
    for i in range(len(Sas)):
        PDF.append(Prob(Sas[i]))

    import scipy.integrate

    CDF = scipy.integrate.cumtrapz(PDF, Sas, initial=0)

    return Sas, CDF, Median, Beta
//  [python]
def GetFragility(SaCollapse):
    import numpy as np

    Mean = np.mean(SaCollapse)
    STD = np.std(SaCollapse)
    COV = STD/Mean

    Mu = np.log(Mean**2/(STD**2+Mean**2)**.5)
    Sigma = (np.log(1+COV**2))**.5

    Median = np.exp(Mu)

    Beta = np.std(np.log(SaCollapse))

    Sas = np.arange(0.01,max(SaCollapse),0.01)

    def Prob(x):
        return 1.0/x/(2.0*np.pi)**.5/Sigma*np.exp(-1.0*(np.log(x)-Mu)**2/2/Sigma**2)

    PDF = []
    for i in range(len(Sas)):
        PDF.append(Prob(Sas[i]))

    import scipy.integrate

    CDF = scipy.integrate.cumulative_trapezoid(PDF, Sas, initial=0)

    return Sas, CDF, Median, Beta
//  [python]
def _one_component_kilonova_rosswog_heatingrate(time, mej, vej, electron_fraction, **kwargs):
    tdays = time/day_to_s
    # set up kilonova physics
    av, bv, dv = interpolated_barnes_and_kasen_thermalisation_efficiency(mej, vej)
    # thermalisation from Barnes+16
    e_th = 0.36 * (np.exp(-av * tdays) + np.log1p(2.0 * bv * tdays ** dv) / (2.0 * bv * tdays ** dv))
    temperature_floor = kwargs.get('temperature_floor', 4000)  # kelvin
    beta = 13.7

    v0 = vej * speed_of_light
    m0 = mej * solar_mass
    kappa = kappa_from_electron_fraction(electron_fraction)
    tdiff = np.sqrt(2.0 * kappa * (m0) / (beta * v0 * speed_of_light))

    lum_in = _calc_new_heating_rate(time, mej, electron_fraction, vej, **kwargs)
    integrand = lum_in * e_th * (time / tdiff) * np.exp(time ** 2 / tdiff ** 2)

    bolometric_luminosity = np.zeros(len(time))
    bolometric_luminosity[1:] = cumtrapz(integrand, time)
    bolometric_luminosity[0] = bolometric_luminosity[1]
    bolometric_luminosity = bolometric_luminosity * np.exp(-time ** 2 / tdiff ** 2) / tdiff

    temperature = (bolometric_luminosity / (4.0 * np.pi * sigma_sb * v0 ** 2 * time ** 2)) ** 0.25
    r_photosphere = (bolometric_luminosity / (4.0 * np.pi * sigma_sb * temperature_floor ** 4)) ** 0.5

    # check temperature floor conditions
    mask = temperature <= temperature_floor
    temperature[mask] = temperature_floor
    mask = np.logical_not(mask)
    r_photosphere[mask] = v0 * time[mask]
    return bolometric_luminosity, temperature, r_photosphere