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)
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)
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
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