"""
Module containing the basic class for handling the plasma's components.
"""
from copy import deepcopy
from numpy import array, ndarray, pi, sqrt, zeros
from .utilities.fdints import fdm1h, invfd1h
[docs]class Species:
"""
Class used to store all the information of a single species.
Attributes
----------
name : str
Species' name.
number_density : float
Species number density in appropriate units.
num : int
Number of particles of Species.
mass : float
Species' mass.
charge : float
Species' charge.
Z : float
Species charge number or ionization degree.
ai : float
Species Wigner - Seitz radius calculate from electron density.
Used in the calculation of the effective coupling constant.
ai_dens : float
Species Wigner - Seitz radius calculate from the species density.
coupling : float
Species coupling constant
plasma_frequency : float
Species' plasma frequency.
debye_length : float
Species' Debye Length.
cyclotron_frequency : float
Species' cyclotron frequency.
initial_velocity : numpy.ndarray
Initial velocity in x,y,z directions.
temperature : float
Initial temperature of the species.
temperature_eV : float
Initial temperature of the species in eV.
initial_velocity_distribution : str
Type of distribution. Default = 'boltzmann'.
initial_spatial_distribution : str
Type of distribution. Default = 'uniform'.
atomic_weight : float
(Optional) Species mass in atomic units.
concentration : float
Species' concentration.
mass_density : float
(Optional) Species' mass density.
"""
[docs] def __init__(self, input_dict: dict = None):
"""
Parameters
----------
input_dict: dict, optional
Dictionary with species information.
"""
self.ai = None
self.ai_dens = None
self.atomic_weight = None
self.charge = None
self.Z = None
self.coupling = None
self.concentration = None
self.debye_length = None
self.initial_spatial_distribution = "random_no_reject"
self.initial_velocity_distribution = "boltzmann"
self.initial_velocity = zeros(3)
self.mass = None
self.mass_density = None
self.name = None
self.num = None
self.number_density = None
self.cyclotron_frequency = None
self.plasma_frequency = None
self.temperature = None
self.temperature_eV = None
if input_dict:
self.from_dict(input_dict)
def __repr__(self):
sortedDict = dict(sorted(self.__dict__.items(), key=lambda x: x[0].lower()))
disp = "Species( \n"
for key, value in sortedDict.items():
disp += "\t{} : {}\n".format(key, value)
disp += ")"
return disp
def __copy__(self):
"""Make a shallow copy of the object using copy by creating a new instance of the object and copying its __dict__."""
# Create a new object
_copy = type(self)()
# copy the dictionary
_copy.from_dict(input_dict=self.__dict__)
return _copy
def __deepcopy__(self, memodict: dict = {}):
"""Make a deepcopy of the object.
Parameters
----------
memodict: dict
Dictionary of id's to copies
Returns
-------
_copy: :class:`sarkas.plasma.Species`
A new Species class.
"""
id_self = id(self) # memorization avoids unnecessary recursion
_copy = memodict.get(id_self)
if _copy is None:
# Make a shallow copy of all attributes
_copy = type(self)()
# Make a deepcopy of the mutable arrays using numpy copy function
for k, v in self.__dict__.items():
_copy.__dict__[k] = deepcopy(v, memodict)
return _copy
[docs] def from_dict(self, input_dict: dict):
"""
Update attributes from input dictionary using the ``__dict__.update()`` method.
Parameters
----------
input_dict: dict
Dictionary to be copied.
"""
self.__dict__.update(input_dict)
if not isinstance(self.initial_velocity, ndarray):
self.initial_velocity = array(self.initial_velocity)
[docs] def copy_params(self, params):
"""
Copy physical constants from parameters class.
Parameters
----------
params: :class:`sarkas.core.Parameters`
Parameters class.
"""
self.kB = params.kB
self.eV2K = params.eV2K
self.eV2J = params.eV2J
self.hbar = params.hbar
self.c0 = params.c0
self.units_dict = params.units_dict
self.dimensions = params.dimensions
# Charged systems: Electrostatic constant :math:`4 \\pi \\epsilon_0` [mks]
# Neutral systems: :math:`1/n\\sigma^2`
if hasattr(self, "sigma"):
self.fourpie0 = 4.0 * pi * self.number_density * self.sigma**2
else:
self.fourpie0 = params.fourpie0
[docs] def calc_plasma_frequency(self):
"""Calculate the plasma frequency."""
if self.dimensions == 3:
self.plasma_frequency = sqrt(4.0 * pi * self.charge**2 * self.number_density / (self.mass * self.fourpie0))
else:
self.plasma_frequency = sqrt(
2.0 * pi * self.charge**2 * self.number_density / (self.ai_dens * self.mass * self.fourpie0)
)
[docs] def calc_debye_length(self):
"""Calculate the Debye Length."""
self.debye_length = sqrt(
(self.temperature * self.kB * self.fourpie0) / (4.0 * pi * self.charge**2 * self.number_density)
)
[docs] def calc_debroglie_wavelength(self):
"""Calculate the de Broglie wavelength."""
self.deBroglie_wavelength = sqrt(2.0 * pi * self.hbar**2 / (self.kB * self.temperature * self.mass))
[docs] def calc_cyclotron_frequency(self, magnetic_field_strength: float):
"""
Calculate the cyclotron frequency. See `Wikipedia link <https://en.wikipedia.org/wiki/Lorentz_force>`_.
Parameters
----------
magnetic_field_strength : float
Magnetic field strength.
"""
self.cyclotron_frequency = abs(self.charge) * magnetic_field_strength / self.mass
[docs] def calc_landau_length(self):
"""Calculate the Landau Length."""
# Landau length 4pi e^2 beta. The division by fourpie0 is needed for MKS units
self.landau_length = 4.0 * pi * self.charge**2 / (self.temperature * self.fourpie0 * self.kB)
[docs] def calc_coupling(self, a_ws: float, z_avg: float, const: float):
"""
Calculate the coupling constant between particles.
Parameters
----------
a_ws : float
Total Wigner-Seitz radius.
z_avg : float
Average charge of the system.
const : float
Electrostatic * Thermal constants.
"""
self.ai = (self.charge / z_avg) ** (1.0 / 3.0) * a_ws if z_avg > 0 else self.ai_dens
self.coupling = self.charge**2 / (self.ai * const * self.temperature)
[docs] def calc_ws_radius(self):
if self.dimensions == 3:
self.ai_dens = (3.0 / (4.0 * pi * self.number_density)) ** (1.0 / 3.0)
else:
self.ai_dens = 1.0 / sqrt(pi * self.number_density)
[docs] def calc_quantum_attributes(self, spin_statistics: str = "fermi-dirac"):
"""
Calculate the following quantum parameters:
Dimensionless chemical potential, Fermi wavenumber, Fermi energy, Thomas-Fermi wavenumber.
This function work only for electrons. Other species might give nonsensical values.
Parameters
----------
spin_statistics: str
Spin statistic. Default and only option "fermi-dirac".
Raises
------
`ValueError`:
If not Fermi-Dirac statistics.
"""
if spin_statistics.lower() != "fermi-dirac":
raise ValueError(f"{spin_statistics} spin_statistic not implemented yet.")
self.spin_degeneracy = 2.0
lambda3 = self.deBroglie_wavelength**3
# chemical potential mu/(kB T), obtained by inverting the density equation.
self.dimensionless_chemical_potential = invfd1h(lambda3 * sqrt(pi) * self.number_density / 4.0)
# Thomas-Fermi length obtained from compressibility. See eq.(10) in Ref. [3]_
lambda_TF_sq = lambda3 / self.landau_length
lambda_TF_sq /= self.spin_degeneracy / sqrt(pi) * fdm1h(self.dimensionless_chemical_potential)
self.ThomasFermi_wavelength = sqrt(lambda_TF_sq)
# Fermi wave number
self.Fermi_wavenumber = (3.0 * pi**2 * self.number_density) ** (1.0 / 3.0)
# Fermi energy
self.Fermi_energy = self.hbar**2 * self.Fermi_wavenumber**2 / (2.0 * self.mass)
[docs] def pretty_print(self, potential_type: str = None, units: str = "mks"):
"""Print Species information in a user-friendly way.
Parameters
----------
potential_type: str
Interaction potential. If 'LJ' it will print out the epsilon and sigma attributes.
units: str
Unit system used in the simulation. Default = 'mks'.
"""
# Pre compute the units to be printed
if units == "cgs":
density_units = "[N/cc]" if self.dimensions == 3 else "[N/cm^2]"
weight_units = "[g]"
mass_dens_units = "[g/cc]" if self.dimensions == 3 else "[g/cm^2]"
charge_units = "[esu]"
energy_units = "[erg]"
length_units = "[cm]"
inv_length_units = "[1/cm]"
else:
density_units = "[N/m^3]" if self.dimensions == 3 else "[N/m^2]"
weight_units = "[kg]"
mass_dens_units = "[kg/cc]" if self.dimensions == 3 else "[kg/m^2]"
charge_units = "[C]"
energy_units = "[J]"
length_units = "[m]"
inv_length_units = "[1/m]"
# Assemble the entire message to be printed
if self.name == "electron_background":
kf_xf = self.mass * self.c0**2 * (sqrt(1.0 + self.relativistic_parameter**2) - 1.0)
mu_EF = self.dimensionless_chemical_potential * self.kB * self.temperature / self.Fermi_energy
if self.cyclotron_frequency:
b_ef = self.magnetic_energy / self.Fermi_energy
b_t = self.magnetic_energy / (self.kB * self.temperature)
elec_mag_msg = (
f"Electron cyclotron frequency: w_c = {self.cyclotron_frequency:.6e}\n"
f"Lowest Landau energy level: h w_c/2 = {0.5 * self.magnetic_energy:.6e}\n"
f"Electron magnetic energy gap: h w_c = {self.magnetic_energy:.6e} = {b_ef:.4e} E_F = {b_t:.4e} k_B T_e\n"
)
else:
elec_mag_msg = ""
msg = (
f"ELECTRON BACKGROUND PROPERTIES:\n"
f"Number density: n_e = {self.number_density:.6e} {density_units}\n"
f"Wigner-Seitz radius: a_e = {self.a_ws:.6e} {length_units}\n"
f"Temperature: T_e = {self.temperature:.6e} [K] = {self.temperature_eV:.6e} [eV]\n"
f"de Broglie wavelength: lambda_deB = {self.deBroglie_wavelength:.6e} {length_units}\n"
f"Thomas-Fermi length: lambda_TF = {self.ThomasFermi_wavelength:.6e}{length_units}\n"
f"Fermi wave number: k_F = {self.Fermi_wavenumber:.6e} {inv_length_units}\n"
f"Fermi Energy: E_F = {self.Fermi_energy / self.kB / self.eV2K:.6e} [eV]\n"
f"Relativistic parameter: x_F = {self.relativistic_parameter:.6e} --> E_F = {(kf_xf / self.kB / self.eV2K):.6e} [eV]\n"
f"Degeneracy parameter: Theta = {self.degeneracy_parameter:.6e}\n"
f"Coupling: r_s = {self.rs:.6f}, Gamma_e = {self.coupling:.6f}\n"
f"Warm Dense Matter parameter: W = {self.wdm_parameter:.4e}\n"
f"Chemical potential: mu = {self.dimensionless_chemical_potential:.4e} k_B T_e = {mu_EF:.4e} E_F\n"
)
msg += elec_mag_msg
else:
if potential_type == "lj":
pot_msg = (
f"\tEpsilon = {self.epsilon:.6e} {energy_units}\n"
f"\tSigma = {self.sigma:.6e} {length_units}\n"
f"\tEquivalent Plasma frequency = {self.plasma_frequency: .6e} {self.units_dict['frequency']}\n"
)
else:
pot_msg = (
f"\tDebye length = {self.debye_length: .6e} {length_units}\n"
f"\tPlasma frequency = {self.plasma_frequency: .6e} {self.units_dict['frequency']}\n"
)
if self.cyclotron_frequency:
mag_msg = (
f"\tCyclotron Frequency = {self.cyclotron_frequency:.6e} {self.units_dict['frequency']}\n"
f"\tbeta_c = {self.cyclotron_frequency / self.plasma_frequency:.4e}"
)
else:
mag_msg = ""
msg = (
f"\tName: {self.name}\n"
f"\tNo. of particles = {self.num}\n"
f"\tNumber density = {self.number_density:.6e} {density_units}\n"
f"\tAtomic weight = {self.atomic_weight:.6e} [a.u.]\n"
f"\tMass = {self.mass:.6e} {weight_units}\n"
f"\tMass density = {self.mass_density:.6e} {mass_dens_units}\n"
f"\tCharge number/ionization degree = {self.Z:.4f}\n"
f"\tCharge = {self.charge:.6e} {charge_units}\n"
f"\tTemperature = {self.temperature:.6e} [K] = {self.temperature_eV:.6e} [eV]\n"
)
msg = msg + pot_msg + mag_msg
print(msg)