Exemple #1
0
    def get_tube_speed(self, which_values=None):
        """
        Calculates the tube speed for a cylinder, given by
        :math:`c_t = \\frac{c_s c_A}{\\sqrt{c_s^2 + c_A^2}}`.

        Parameters
        ----------
        which_values : str
            Can be one of the following:
                - None : returns the tube speed as a function of the grid.
                - "average": returns the average tube speed over the grid.
                - "minimum": returns the minimum tube speed over the grid.
                - "maximum": returns the maximum tube speed over the grid.

        Returns
        -------
        ct = float, numpy.ndarray
            Array with the tube speed at every grid point,
            or a float corresponding to the value of `which_values` if provided.
            Returns `None` if the geometry is not cylindrical.
        """
        if not self.geometry == "cylindrical":
            pylboLogger.warning(
                "geometry is not cylindrical, unable to calculate tube speed"
            )
            return None
        else:
            cA = self.get_alfven_speed()
            cs = self.get_sound_speed()
            ct = cs * cA / np.sqrt(cs ** 2 + cA ** 2)
            return self._get_values(ct, which_values)
Exemple #2
0
    def get_magnetic_reynolds_nb(self, which_values=None):
        """
        Calculates the magnetic Reynolds number, defined as
        :math:`R_m = \\frac{ac_A}{\\eta}` where the slabsize is given by
        :math:`a = x_{end} - x_{start}`.

        Parameters
        ----------
        which_values : str
            Can be one of the following:
                - None : returns the magnetic Reynolds number as a function of the grid.
                - "average": returns the average magnetic Reynolds number over the grid.
                - "minimum": returns the minimum magnetic Reynolds number over the grid.
                - "maximum": returns the maximum magnetic Reynolds number over the grid

        Returns
        -------
        Rm : float, numpy.ndarray(dtype=float, ndim=1)
            Array with the magnetic Reynolds number at every grid point,
            or a float corresponding to the value of `which_values` if provided.
            Returns `None` if the resistivity is zero somewhere on the domain.
        """
        cA = self.get_alfven_speed()
        a = self.x_end - self.x_start
        eta = self.equilibria["eta"]
        if (eta == 0).any():
            pylboLogger.warning(
                "resistivity is zero somewhere on the domain, unable to "
                "calculate the magnetic Reynolds number"
            )
            return None
        else:
            Rm = a * cA / eta
            return self._get_values(Rm, which_values)
Exemple #3
0
def _validate_nb_cpus(cpus):
    """
    Validates the number of cpus passed to the multiprocessing pool.
    Defaults to the maximum available number if exceeded.

    Parameters
    ----------
    cpus : int
        The number of cpus to use.

    Returns
    -------
    cpus : int
        The number of cpus to use, limited to the maximum number available.

    """
    cpus_available = multiprocessing.cpu_count()
    if cpus > cpus_available:
        pylboLogger.warning(
            f"Requested more than the available number of cpus ({cpus}). "
            f"Setting nb_cpus to maximum available ({cpus_available}).")
        cpus = cpus_available
    return cpus
Exemple #4
0
def calculate_continua(ds):
    """
    Calculates the different continua for a given dataset.

    Parameters
    ----------
    ds : ~pylbo.data_containers.LegolasDataSet
        The Legolas dataset.

    Returns
    -------
    continua : dict
        Dictonary containing the various continua as numpy arrays.
    """
    rho = ds.equilibria["rho0"]
    B02 = ds.equilibria["B02"]
    B03 = ds.equilibria["B03"]
    B0 = np.sqrt(B02 ** 2 + B03 ** 2)
    v02 = ds.equilibria["v02"]
    v03 = ds.equilibria["v03"]
    T = ds.equilibria["T0"]
    p = rho * T

    k2 = ds.parameters["k2"]
    k3 = ds.parameters["k3"]
    gamma = ds.gamma

    # Alfven and slow continuum (squared)
    alfven2 = (1 / rho) * ((k2 * B02 / ds.scale_factor) + k3 * B03) ** 2
    slow2 = (gamma * p / (gamma * p + B0 ** 2)) * alfven2
    # doppler shift equals dot product of k and v
    doppler = k2 * v02 / ds.scale_factor + k3 * v03
    slow_min = -np.sqrt(slow2)
    slow_plus = np.sqrt(slow2)
    slow_is_zero = np.all(np.isclose(slow2, 0))

    # Thermal continuum calculation
    # wave vector parallel to magnetic field, uses vector projection and scale factor
    kpara = (k2 * B02 / ds.scale_factor + k3 * B03) / B0
    cs2 = gamma * p / rho  # sound speed
    vA2 = B0 ** 2 / rho  # Alfvén speed
    ci2 = p / rho  # isothermal sound speed
    dLdT = ds.equilibria["dLdT"]
    dLdrho = ds.equilibria["dLdrho"]
    kappa_para = ds.equilibria["kappa_para"]
    kappa_perp = ds.equilibria["kappa_perp"]
    # if there is no conduction/cooling, there is no thermal continuum.
    if (
        all(dLdT == 0)
        and all(dLdrho == 0)
        and all(kappa_para == 0)
        and all(kappa_perp == 0)
    ):
        thermal = np.zeros_like(ds.grid_gauss)
    # if temperature is zero (no pressure), set to zero and return
    elif (T == 0).all():
        thermal = np.zeros_like(ds.grid_gauss)
    # if slow continuum vanishes, thermal continuum is analytical
    elif slow_is_zero:
        thermal = 1j * (gamma - 1) * (rho * dLdrho - dLdT * (ci2 + vA2)) / (cs2 + vA2)
    else:
        sigma_A2 = kpara ** 2 * vA2  # Alfvén frequency
        sigma_c2 = cs2 * sigma_A2 / (vA2 + cs2)  # cusp frequency
        sigma_i2 = ci2 * sigma_A2 / (vA2 + ci2)  # isothermal cusp frequency

        # thermal and slow continuum are coupled in a third degree polynomial in omega,
        # coeffi means the coefficient corresponding to the term omega^i
        coeff3 = rho * (cs2 + vA2) * 1j / (gamma - 1)
        coeff2 = (
            -(kappa_para * kpara ** 2 + rho * dLdT) * (ci2 + vA2) + rho ** 2 * dLdrho
        )
        coeff1 = -rho * (cs2 + vA2) * sigma_c2 * 1j / (gamma - 1)
        coeff0 = (kappa_para * kpara ** 2 + rho * dLdT) * (
            ci2 + vA2
        ) * sigma_i2 - rho ** 2 * dLdrho * sigma_A2
        # we have to solve this equation "gauss_gridpts" times.
        # the thermal continuum corresponds to the (only) purely imaginary solution,
        # modified slow continuum are other two solutions
        thermal = np.empty(shape=len(ds.grid_gauss), dtype=complex)
        slow_min = np.empty_like(thermal)
        slow_plus = np.empty_like(thermal)
        for idx, _ in enumerate(ds.grid_gauss):
            solutions = np.roots([coeff3[idx], coeff2[idx], coeff1[idx], coeff0[idx]])
            # create mask for purely imaginary solutions
            mask = np.isclose(abs(solutions.real), 0)
            imag_sol = solutions[mask]
            # extract slow continuum solutions
            s_neg, s_pos = np.sort_complex(solutions[np.invert(mask)])
            slow_min[idx] = s_neg
            slow_plus[idx] = s_pos
            # process thermal continuum solution
            if (imag_sol == 0).all():
                # if all solutions are zero (no thermal continuum), then append zero
                w = 0
            else:
                # else there should be ONE value that is nonzero,
                # this is the thermal continuum value.
                # Filter out non-zero solution and try to unpack.
                # If there is more than one non-zero
                # value unpacking fails, this is a sanity check so we raise an error.
                try:
                    (w,) = imag_sol[imag_sol != 0]  # this is an array, so unpack with ,
                except ValueError:
                    pylboLogger.warning(
                        f"Something went wrong, more than one solution for the thermal "
                        f"continuum was found (and there should be only one). "
                        f"Setting value to zero. "
                        f"Solutions found: {imag_sol[imag_sol != 0]}."
                    )
                    w = 0
            thermal[idx] = w

    # get doppler-shifted continua and return
    continua = {
        CONTINUA_NAMES[0]: doppler + slow_min,  # minus is accounted for in slow_min
        CONTINUA_NAMES[1]: doppler + slow_plus,
        CONTINUA_NAMES[2]: doppler - np.sqrt(alfven2),
        CONTINUA_NAMES[3]: doppler + np.sqrt(alfven2),
        CONTINUA_NAMES[4]: thermal,
        CONTINUA_NAMES[5]: doppler,
    }
    return continua