"""
Module containing the three basic classes: Parameters, Particles, Species.
"""
from copy import deepcopy
from numpy import array, cross, float64, int64, ndarray, pi, rint, sqrt, tanh, zeros
from scipy.constants import physical_constants
from scipy.linalg import norm
from .plasma import Species
from .utilities.exceptions import ParticlesError
[docs]class Parameters:
"""
Class containing all the constants and physical constants of the simulation.
Parameters
----------
dic : dict, optional
Dictionary to be copied.
Attributes
----------
a_ws : float
Wigner-Seitz radius. Calculated from the ``total_num_density`` .
equilibration_steps : int
Total number of equilibration timesteps.
eq_dump_step : int
Equilibration dump interval.
magnetization_steps : int
Total number of magnetization timesteps.
mag_dump_step : int
Magnetization dump interval.
production_steps : int
Total number of production timesteps.
prod_dump_step : int
Production dump interval.
box_volume : float
Volume of simulation box.
pbox_volume : float
Volume of initial particle box.
dimensions : int
Number of non-zero dimensions. Default = 3.
fourpie0: float
Electrostatic constant :math:`4\\pi \\epsilon_0`.
num_species : int
Number of species.
kB : float
Boltzmann constant obtained from ``scipy.constants``.
hbar : float
Reduced Planck's constant.
hbar2 : float
Square of reduced Planck's constant.
a0 : float
Bohr Radius.
c0 : float
Speed of light.
qe : float
Elementary charge.
me : float
Electron mass.
eps0 : float
Vacuum electrical permittivity.
eV2K : float
Conversion factor from eV to Kelvin obtained from ``scipy.constants``.
J2erg : float
Conversion factor from Joules to erg. Needed for cgs units.
QFactor : float
Charge Factor defined as :math:`\mathcal Q = \sum_{i}^{N} q_{i}^2` .
Lx : float
Box length in the :math:`x` direction.
Ly : float
Box length in the :math:`y` direction.
Lz : float
Box length in the :math:`z` direction.
e1 : float
Unit vector in the :math:`x` direction.
e2 : float
Unit vector in the :math:`y` direction.
e3 : float
Unit vector in the :math:`z` direction.
LPx : float
Initial particle box length in the :math:`x` direction.
LPy : float
Initial particle box length in the :math:`y` direction.
LPz : float
Initial particle box length in the :math:`z` direction.
ep1 : float
Unit vector of the initial particle box in the :math:`x` direction.
ep2 : float
Unit vector of the initial particle box in the :math:`y` direction.
ep3 : float
Unit vector of the initial particle box in the :math:`z` direction.
input_file : str
YAML Input file with all the simulation's parameters.
T_desired : float
Target temperature for the equilibration phase.
species_num : numpy.ndarray
Number of particles of each species. Shape = (``num_species``)
species_concentrations : numpy.ndarray
Concentration of each species. Shape = (``num_species``)
species_temperatures : numpy.ndarray
Initial temperature of each species. Shape = (``num_species``)
species_masses : numpy.ndarray
Mass of each species. Shape = (``num_species``)
species_charges : numpy.ndarray
Charge of each species. Shape = (``num_species``)
species_names : list
Name of each species. Len = (``num_species``)
species_plasma_frequencies : numpy.ndarray
Plasma Frequency of each species. Shape = (``num_species``)
species_num_dens : numpy.ndarray
Number density of each species. Shape = (``num_species``)
total_ion_temperature : float
Total initial ion temperature calculated as `` = species_concentration @ species_temperatures``.
total_net_charge : float
Total charge in the system.
total_num_density : float
Total number density. Calculated from the sum of :attr:`Species.number_density`.
total_num_ptcls : int
Total number of particles. Calculated from the sum of :attr:`Species.num`.
measure : bool
Flag for production phase.
verbose : bool
Flag for screen output.
simulations_dir : str
Name of directory where to store simulations.
job_dir : str
Directory name of the current job/run
production_dir : str
Directory name where to store simulation's files of the production phase. Default = 'Production'.
equilibration_dir : str
Directory name where to store simulation's file of the equilibration phase. Default = 'Equilibration'.
preprocessing_dir : str
Directory name where to store preprocessing files. Default = "PreProcessing".
postprocessing_dir : str
Directory name where to store postprocessing files. Default = "PostProcessing".
prod_dump_dir : str
Directory name where to store production phase's simulation's checkpoints. Default = 'dumps'.
eq_dump_dir : str
Directory name where to store equilibration phase's simulation's checkpoints. Default = 'dumps'.
job_id : str
Appendix of all simulation's files.
log_file : str
Filename of the simulation's log.
np_per_side : numpy.ndarray
Number of particles per simulation's box side.
The product of its components should be equal to ``total_num_ptcls``.
pre_run : bool
Flag for preprocessing phase.
"""
[docs] def __init__(self, dic: dict = None) -> None:
self.particles_input_file = None
self.load_perturb = 0.0
self.initial_lattice_config = "simple_cubic"
self.load_rejection_radius = None
self.load_halton_bases = None
self.load_method = None
self.potential_type = None
self.units = None
self.electron_magnetic_energy = None
self.input_file = None
# Sim box geometry
self.Lx = 0.0
self.Ly = 0.0
self.Lz = 0.0
self.LPx = 0.0
self.LPy = 0.0
self.LPz = 0.0
self.e1 = None
self.e2 = None
self.e3 = None
self.ep1 = None
self.ep2 = None
self.ep3 = None
self.box_lengths = array([0.0, 0.0, 0.0])
self.pbox_lengths = array([0.0, 0.0, 0.0])
self.box_volume = 0.0
self.pbox_volume = 0.0
self.dimensions = 3
# Physical Constants and conversion units
self.J2erg = 1.0e7 # erg/J
self.eps0 = physical_constants["vacuum electric permittivity"][0]
self.fourpie0 = 4.0 * pi * self.eps0
self.mp = physical_constants["proton mass"][0]
self.me = physical_constants["electron mass"][0]
self.qe = physical_constants["elementary charge"][0]
self.hbar = physical_constants["reduced Planck constant"][0]
self.hbar2 = self.hbar**2
self.c0 = physical_constants["speed of light in vacuum"][0]
self.eV2K = physical_constants["electron volt-kelvin relationship"][0]
self.eV2J = physical_constants["electron volt-joule relationship"][0]
self.a0 = physical_constants["Bohr radius"][0]
self.kB = physical_constants["Boltzmann constant"][0]
self.kB_eV = physical_constants["Boltzmann constant in eV/K"][0]
self.a_ws = 0.0
# Phases
self.equilibration_phase = True
self.electrostatic_equilibration = True
self.magnetization_phase = False
self.production_phase = True
# Timing
self.equilibration_steps = 0
self.production_steps = 0
self.magnetization_steps = 0
self.eq_dump_step = 1
self.prod_dump_step = 1
self.mag_dump_step = 1
# Control
self.job_id = None
self.job_dir = None
self.log_file = None
self.measure = False
self.magnetized = False
self.plot_style = None
self.pre_run = False
self.simulations_dir = "Simulations"
self.production_dir = "Production"
self.magnetization_dir = "Magnetization"
self.equilibration_dir = "Equilibration"
self.preprocessing_dir = "PreProcessing"
self.postprocessing_dir = "PostProcessing"
self.prod_dump_dir = "dumps"
self.eq_dump_dir = "dumps"
self.mag_dump_dir = "dumps"
self.verbose = True
self.restart_step = None
self.np_per_side = None
self.num_species = 1
self.magnetic_field = None
self.species_lj_sigmas = None
self.species_names = None
self.species_num = None
self.species_num_dens = None
self.species_concentrations = None
self.species_temperatures = None
self.species_temperatures_eV = None
self.species_masses = None
self.species_charges = None
self.species_plasma_frequencies = None
self.species_cyclotron_frequencies = None
self.species_couplings = None
self.coupling_constant = 0.0
self.total_num_density = 0.0
self.total_num_ptcls = 0
self.total_plasma_frequency = 0.0
self.total_debye_length = 0.0
self.total_mass_density = 0.0
self.total_ion_temperature = 0.0
self.T_desired = 0.0
self.total_net_charge = 0.0
self.QFactor = 0.0
self.average_charge = None
self.average_mass = None
self.hydrodynamic_frequency = None
if dic:
self.from_dict(dic)
def __repr__(self):
sortedDict = dict(sorted(self.__dict__.items(), key=lambda x: x[0].lower()))
disp = "Parameters( \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)(dic=self.__dict__)
return _copy
def __deepcopy__(self, memodict={}):
"""
Make a deepcopy of the object.
Parameters
----------
memodict: dict
Dictionary of id's to copies
Returns
-------
_copy: :class:`sarkas.core.Parameters`
A new Parameters class.
"""
id_self = id(self) # memorization avoids unnecessary recursion
_copy = memodict.get(id_self)
if _copy is None:
_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 calc_coupling_constant(self, species: list):
"""
Calculate the coupling constant of each species and the total coupling constant. For more information see
the theory pages.
Parameters
----------
species: list
List of ``sarkas.plasma.Species`` objects.
"""
z_avg = (self.species_charges.transpose()) @ self.species_concentrations
for i, sp in enumerate(species):
const = self.fourpie0 * self.kB
sp.calc_coupling(self.a_ws, z_avg, const)
self.species_couplings[i] = sp.coupling
self.coupling_constant += sp.concentration * sp.coupling
[docs] def calc_electron_properties(self, species: list):
"""Check whether the electrons are a dynamical species or not."""
# Check for electrons as dynamical species
if "e" not in self.species_names:
electrons = {
"name": "electron_background",
"number_density": (
self.species_charges.transpose() @ self.species_concentrations * self.total_num_density / self.qe
),
}
if hasattr(self, "electron_temperature_eV"):
electrons["temperature_eV"] = self.electron_temperature_eV
electrons["temperature"] = self.eV2K * self.electron_temperature_eV
elif hasattr(self, "electron_temperature"):
electrons["temperature"] = self.electron_temperature
electrons["temperature_eV"] = self.electron_temperature / self.eV2K
else:
electrons["temperature"] = self.total_ion_temperature
electrons["temperature_eV"] = self.total_ion_temperature / self.eV2K
electrons["mass"] = self.me
electrons["Z"] = -1.0
electrons["charge"] = electrons["Z"] * self.qe
electrons["spin_degeneracy"] = 2.0
electrons["num"] = (self.species_num.T @ self.species_charges / self.qe).astype(int64)
e_species = Species(electrons)
e_species.copy_params(self)
e_species.calc_ws_radius()
e_species.calc_plasma_frequency()
e_species.calc_debye_length()
e_species.calc_landau_length()
if self.magnetized:
b_mag = norm(self.magnetic_field) # magnitude of B
if self.units == "cgs":
b_mag /= self.c0
e_species.calc_cyclotron_frequency(magnetic_field_strength=b_mag)
# Electron should be the last species if not dynamical
species.append(e_species)
else:
# Electron should be the first species if dynamical
e_species = species[0]
e_species.calc_debroglie_wavelength()
e_species.calc_quantum_attributes(spin_statistics="fermi-dirac")
# Electron WS radius
e_species.a_ws = (3.0 / (4.0 * pi * e_species.number_density)) ** (1.0 / 3.0)
# Brueckner parameters
e_species.rs = e_species.a_ws / self.a0
# Other electron parameters
e_species.degeneracy_parameter = self.kB * e_species.temperature / e_species.Fermi_energy
e_species.relativistic_parameter = self.hbar * e_species.Fermi_wavenumber / (self.me * self.c0)
# Eq. 1 in Murillo Phys Rev E 81 036403 (2010)
e_species.coupling = e_species.charge**2 / (
self.fourpie0 * e_species.Fermi_energy * e_species.a_ws * sqrt(1.0 + e_species.degeneracy_parameter**2)
)
# Warm Dense Matter Parameter, Eq.3 in Murillo Phys Rev E 81 036403 (2010)
e_species.wdm_parameter = 2.0 / (e_species.degeneracy_parameter + 1.0 / e_species.degeneracy_parameter)
e_species.wdm_parameter *= 2.0 / (e_species.coupling + 1.0 / e_species.coupling)
if self.magnetized:
# Inverse temperature for convenience
beta_e = 1.0 / (self.kB * e_species.temperature)
e_species.magnetic_energy = self.hbar * e_species.cyclotron_frequency
tan_arg = 0.5 * self.hbar * e_species.cyclotron_frequency * beta_e
# Perpendicular correction
e_species.horing_perp_correction = (e_species.plasma_frequency / e_species.cyclotron_frequency) ** 2
e_species.horing_perp_correction *= 1.0 - tan_arg / tanh(tan_arg)
e_species.horing_perp_correction += 1
# Parallel correction
e_species.horing_par_correction = 1 - (self.hbar * beta_e * e_species.plasma_frequency) ** 2 / 12.0
# Quantum Anisotropy Parameter
e_species.horing_delta = e_species.horing_perp_correction - 1
e_species.horing_delta += (self.hbar * beta_e * e_species.cyclotron_frequency) ** 2 / 12.0
e_species.horing_delta /= e_species.horing_par_correction
[docs] def calc_parameters(self, species: list):
"""
Assign the parsed parameters.
Parameters
----------
species : list
List of :class:`sarkas.plasma.Species` .
"""
self.set_species_attributes(species)
self.create_species_arrays(species)
if self.magnetized:
self.magnetic_field = array(self.magnetic_field, dtype=float)
self.calc_magnetic_parameters(species)
self.sim_box_setup()
[docs] def calc_magnetic_parameters(self, species: list):
"""
Calculate cyclotron frequency in case of a magnetized simulation.
Parameters
----------
species: list,
List of :class:`sarkas.plasma.Species`.
"""
self.species_cyclotron_frequencies = zeros(self.num_species)
for i, sp in enumerate(species):
b_mag = norm(self.magnetic_field)
if self.units == "cgs":
b_mag /= self.c0
sp.calc_cyclotron_frequency(magnetic_field_strength=b_mag)
sp.beta_c = sp.cyclotron_frequency / sp.plasma_frequency
self.species_cyclotron_frequencies[i] = sp.cyclotron_frequency
[docs] def check_units(self) -> None:
"""Adjust default physical constants for cgs unit system and check for LJ potential."""
# Physical constants
if self.units == "cgs":
self.kB *= self.J2erg
self.c0 *= 1e2 # cm/s
self.mp *= 1e3
# Coulomb to statCoulomb conversion factor. See https://en.wikipedia.org/wiki/Statcoulomb
C2statC = 1.0e-01 * self.c0
self.hbar = self.J2erg * self.hbar
self.hbar2 = self.hbar**2
self.qe *= C2statC
self.me *= 1.0e3
self.eps0 = 1.0
self.fourpie0 = 1.0
self.a0 *= 1e2
if self.potential_type == "lj":
self.fourpie0 = 1.0
self.species_lj_sigmas = zeros(self.num_species)
[docs] def create_species_arrays(self, species: list):
"""
Get species information into arrays for the postprocessing part.
Parameters
----------
species : list
List of :class:`sarkas.plasma.Species` .
"""
self.num_species = len(species)
# Initialize the arrays containing species attributes. This is needed for postprocessing
self.species_names = []
self.species_num = zeros(self.num_species, dtype=int64)
self.species_num_dens = zeros(self.num_species)
self.species_concentrations = zeros(self.num_species)
self.species_temperatures = zeros(self.num_species)
self.species_temperatures_eV = zeros(self.num_species)
self.species_masses = zeros(self.num_species)
self.species_charges = zeros(self.num_species)
self.species_plasma_frequencies = zeros(self.num_species)
self.species_couplings = zeros(self.num_species)
if self.potential_type == "lj":
self.species_lj_sigmas = zeros(self.num_species)
# Initialization of attributes
self.total_num_ptcls = 0
self.total_num_density = 0.0
wp_tot_sq = 0.0
lambda_D = 0.0
for i, sp in enumerate(species):
self.total_num_ptcls += sp.num
self.total_num_density += sp.number_density
self.species_concentrations[i] = sp.concentration
self.species_names.append(sp.name)
self.species_num[i] = sp.num
self.species_masses[i] = sp.mass
self.species_num_dens[i] = sp.number_density
self.species_temperatures_eV[i] = sp.temperature_eV
self.species_temperatures[i] = sp.temperature
self.species_charges[i] = sp.charge
self.species_plasma_frequencies[i] = sp.plasma_frequency
self.QFactor += sp.QFactor / self.fourpie0
wp_tot_sq += sp.plasma_frequency**2
lambda_D += sp.debye_length**2
if self.potential_type == "lj":
self.species_lj_sigmas[i] = sp.sigma
self.total_mass_density = self.species_masses.transpose() @ self.species_num_dens
# Calculate total quantities
self.total_net_charge = (self.species_charges.transpose()) @ self.species_num
self.total_plasma_frequency = sqrt(wp_tot_sq)
self.total_debye_length = sqrt(lambda_D)
# Transform the list of species names into an array
self.species_names = array(self.species_names)
self.total_ion_temperature = (self.species_concentrations.transpose()) @ self.species_temperatures
# Redundancy!!!
self.T_desired = self.total_ion_temperature
self.average_charge = (self.species_charges.transpose()) @ self.species_concentrations
self.average_mass = (self.species_masses.transpose()) @ self.species_concentrations
# Hydrodynamic Frequency
self.hydrodynamic_frequency = sqrt(
4.0 * pi * self.average_charge**2 * self.total_num_density / (self.fourpie0 * self.average_mass)
)
[docs] def from_dict(self, input_dict: dict) -> None:
"""
Update attributes from input dictionary.
Parameters
----------
input_dict: dict
Dictionary to be copied.
"""
self.__dict__.update(input_dict)
[docs] def pretty_print(self):
"""
Print simulation parameters in a user-friendly way.
"""
print("\nSIMULATION AND INITIAL PARTICLE BOX:")
if hasattr(self, "rand_seed"):
print("Random Seed = ", self.rand_seed)
print(f"Units: {self.units}")
print(f"No. of non-zero box dimensions = {self.dimensions}")
print(f"Wigner-Seitz radius = {self.a_ws:.6e} ", end="")
print("[cm]" if self.units == "cgs" else "[m]")
box_a = self.box_lengths / self.a_ws
print(f"Box side along x axis = {box_a[0]:.6e} a_ws = {self.box_lengths[0]:.6e} ", end="")
print("[cm]" if self.units == "cgs" else "[m]")
print(f"Box side along y axis = {box_a[1]:.6e} a_ws = {self.box_lengths[1]:.6e} ", end="")
print("[cm]" if self.units == "cgs" else "[m]")
print(f"Box side along z axis = {box_a[2]:.6e} a_ws = {self.box_lengths[2]:.6e} ", end="")
print("[cm]" if self.units == "cgs" else "[m]")
print(f"Box Volume = {self.box_volume:.6e} ", end="")
if self.dimensions == 3:
print("[cm^3]" if self.units == "cgs" else "[m^3]")
else:
print("[cm^2]" if self.units == "cgs" else "[m^2]")
pbox_a = self.pbox_lengths / self.a_ws
print(f"Initial particle box side along x axis = {pbox_a[0]:.6e} a_ws = {self.pbox_lengths[0]:.6e} ", end="")
print("[cm]" if self.units == "cgs" else "[m]")
print(f"Initial particle box side along y axis = {pbox_a[1]:.6e} a_ws = {self.pbox_lengths[1]:.6e} ", end="")
print("[cm]" if self.units == "cgs" else "[m]")
print(f"Initial particle box side along z axis = {pbox_a[2]:.6e} a_ws = {self.pbox_lengths[2]:.6e} ", end="")
print("[cm]" if self.units == "cgs" else "[m]")
print(f"Initial particle box Volume = {self.pbox_volume:.6e} ", end="")
if self.dimensions == 3:
print("[cm^3]" if self.units == "cgs" else "[m^3]")
else:
print("[cm^2]" if self.units == "cgs" else "[m^2]")
print(f"Boundary conditions: {self.boundary_conditions}")
if self.magnetized:
print("\nMAGNETIC FIELD:")
print(f"Magnetic Field = {self.magnetic_field}", end="")
print("[Tesla]" if self.units == "mks" else "[Gauss]")
print(f"Magnetic Field Magnitude = {norm(self.magnetic_field):.4e} ", end="")
print("[Tesla]" if self.units == "mks" else "[Gauss]")
print(f"Magnetic Field Unit Vector = {self.magnetic_field / norm(self.magnetic_field)}")
restart = self.load_method
restart_step = self.restart_step if self.restart_step else 0
wp_dt = self.total_plasma_frequency * self.dt
# Print Time steps information
phase_dict = {
"eq": ["equilibration", "equilibration_steps", "eq_dump_step"],
"ma": ["magnetization", "magnetization_steps", "mag_dump_step"],
"pr": ["production", "production_steps", "prod_dump_step"],
}
# Check for restart simulations
if restart[-7:] == "restart":
phase_ls = phase_dict[restart[:2]]
phase = phase_ls[0]
steps = self.__dict__[phase_ls[1]]
dump_step = self.__dict__[phase_ls[2]]
print(f"Restart step: {restart_step}")
print(f"Total {phase} steps = {steps}")
print(f"Total {phase} time = {steps * self.dt:.4e} [s] ~ {int(steps * wp_dt)} w_p T_prod ")
print(f"snapshot interval step = {dump_step}")
print(f"snapshot interval time = {dump_step * self.dt:.4e} [s] = {dump_step * wp_dt:.4f} w_p T_snap")
print(f"Total number of snapshots = {int(steps / dump_step)}")
else:
for (key, phase_ls) in phase_dict.items():
phase = phase_ls[0]
steps = self.__dict__[phase_ls[1]]
dump_step = self.__dict__[phase_ls[2]]
if key == "mg" and not self.magnetized:
continue
else:
print(f"\n{phase.capitalize()}: \nNo. of {phase} steps = {steps}")
print(f"Total {phase} time = {steps * self.dt:.4e} [s] ~ {int(steps * wp_dt)} w_p T_eq")
print(f"snapshot interval step = {dump_step}")
print(f"snapshot interval time = {dump_step * self.dt:.4e} [s] = {dump_step * wp_dt:.4f} w_p T_snap")
print(f"Total number of snapshots = {int(steps / dump_step)}")
[docs] def set_species_attributes(self, species: list):
"""
Set species attributes that have not been defined in the input file.
Parameters
----------
species: list,
List of :class:`sarkas.plasma.Species`.
"""
# Loop over species and assign missing attributes
# Collect species properties in single arrays
tot_num_ptcls = 0
for i, sp in enumerate(species):
tot_num_ptcls += sp.num
# Calculate the mass of the species from the atomic weight if given
if sp.atomic_weight:
# Choose between atomic mass constant or proton mass
# u = const.physical_constants["atomic mass constant"][0]
sp.mass = self.mp * sp.atomic_weight
else:
sp.atomic_weight = sp.mass / self.mp
# Calculate the mass of the species from the mass density if given
if sp.mass_density:
Av = physical_constants["Avogadro constant"][0]
sp.number_density = sp.mass_density * Av / sp.atomic_weight
if not hasattr(sp, "number_density"):
raise AttributeError(f"\nSpecies {sp.name} number density not defined")
# Calculate the temperature in K if eV has been provided and vice versa
if sp.temperature_eV:
sp.temperature = self.eV2K * sp.temperature_eV
else:
# Convert to eV and save
sp.temperature_eV = sp.temperature / self.eV2K
# Calculate the species charge based on the inputs
if sp.charge:
sp.Z = sp.charge / self.qe
elif sp.Z:
sp.charge = sp.Z * self.qe
elif sp.epsilon:
# Lennard-Jones potentials don't have charge but have the equivalent epsilon.
sp.charge = sqrt(sp.epsilon)
sp.Z = 1.0
else:
sp.charge = 0.0
sp.Z = 0.0
if sp.mass_density is None:
sp.mass_density = sp.mass * sp.number_density
# Q^2 factor see eq.(2.10) in Ballenegger et al. J Chem Phys 128 034109 (2008).
sp.QFactor = sp.num * sp.charge**2 # In case of LJ this is zero
sp.copy_params(self)
sp.calc_ws_radius()
sp.calc_plasma_frequency()
sp.calc_debye_length()
sp.calc_landau_length()
# Calculate species concentrations
for i, sp in enumerate(species):
sp.concentration = float(sp.num / tot_num_ptcls)
[docs] def setup(self, species) -> None:
"""
Setup simulations' parameters.
Parameters
----------
species : list
List of :class:`sarkas.plasma.Species` objects.
"""
self.check_units()
self.calc_parameters(species)
self.calc_coupling_constant(species)
self.calc_electron_properties(species)
[docs] def sim_box_setup(self):
"""Calculate initial particle's and simulation's box parameters."""
# Simulation Box Parameters
# Wigner-Seitz radius calculated from the total number density
# Calculate initial particle's and simulation's box parameters
if self.np_per_side:
if not isinstance(self.np_per_side, ndarray):
self.np_per_side = array(self.np_per_side, dtype=int64)
if rint(self.np_per_side.prod()) != self.total_num_ptcls:
raise ParticlesError("Number of particles per dimension does not match total number of particles.")
if self.dimensions != 3:
new_array = zeros(3, dtype=int64)
for d in range(self.dimensions):
new_array[d] = self.np_per_side[d]
del self.np_per_side
self.np_per_side = new_array.copy()
self.pbox_lengths = self.np_per_side / self.total_num_density ** (1.0 / self.dimensions)
else:
self.pbox_lengths = zeros(3, dtype=float64)
self.np_per_side = zeros(3, dtype=int64)
for d in range(self.dimensions):
self.pbox_lengths[d] = (self.total_num_ptcls / self.total_num_density) ** (1.0 / self.dimensions)
self.np_per_side[d] = rint(self.total_num_ptcls ** (1.0 / self.dimensions))
self.LPx, self.LPy, self.LPz = self.pbox_lengths.ravel()
# The following if are needed if you define Lx, Ly, Lz in the input file
if self.Lx == 0.0:
self.box_lengths[0] = self.pbox_lengths[0]
self.Lx = self.box_lengths[0]
else:
self.box_lengths[0] = self.Lx
if self.Ly == 0.0:
self.box_lengths[1] = self.pbox_lengths[1]
self.Ly = self.box_lengths[1]
else:
self.box_lengths[1] = self.Ly
if self.Lz == 0.0:
self.box_lengths[2] = self.pbox_lengths[2]
self.Lz = self.box_lengths[2]
else:
self.box_lengths[2] = self.Lz
# Dev Note: The following are useful for future geometries.
# Dev Note: Do we really need it?
self.e1 = array([self.Lx, 0.0, 0.0])
self.e2 = array([0.0, self.Ly, 0.0])
self.e3 = array([0.0, 0.0, self.Lz])
self.ep1 = array([self.LPx, 0.0, 0.0])
self.ep2 = array([0.0, self.LPy, 0.0])
self.ep3 = array([0.0, 0.0, self.LPz])
if self.dimensions == 3:
self.a_ws = (3.0 / (4.0 * pi * self.total_num_density)) ** (1.0 / 3.0)
self.box_volume = abs(cross(self.e1, self.e2).dot(self.e3))
self.pbox_volume = abs(cross(self.ep1, self.ep2).dot(self.ep3))
elif self.dimensions == 2:
self.a_ws = 1.0 / sqrt(pi * self.total_num_density)
self.box_volume = abs(cross(self.e1, self.e2)[-1])
self.pbox_volume = abs(cross(self.ep1, self.ep2)[-1])
else:
self.a_ws = 2.0 / self.total_num_density
self.box_volume = self.Lx
self.pbox_volume = self.LPx