#!/usr/bin/env python3 # Copyright 2022 David Grote # # This file is part of WarpX. # # License: BSD-3-Clause-LBNL # This script tests the ion stopping model. # It uses the same stopping power formula that # is used in the C++ to check the resulting # particle energies. import os import re import sys import numpy as np from scipy.constants import e, epsilon_0, k, m_e, m_p import yt sys.path.insert(1, '../../../../warpx/Regression/Checksum/') import checksumAPI # Define constants using the WarpX names for the evals below q_e = e kb = k # Tolerance on the error in the final energy (in eV) tolerance = 1.e-7 last_filename = sys.argv[1] # Remove trailing '/' from file name, if necessary last_filename.rstrip('/') # Find last iteration in file name, such as 'test_name_plt000001' (last_it = '000001') last_it = re.search('\d+$', last_filename).group() # Find output prefix in file name, such as 'test_name_plt000001' (prefix = 'test_name_plt') prefix = last_filename[:-len(last_it)] def stopping_from_electrons(ne, Te, Zb, ion_mass): """Calculate the coefficient in equation 14.13 from "Introduction to Plasma Physics", Goldston and Rutherford. ne: electron density Te: electron temperature (eV) Zb: ion charge state ion_mass: (kg) """ vthe = np.sqrt(3.*Te*e/m_e) wpe = np.sqrt(ne*e**2/(epsilon_0*m_e)) lambdadb = vthe/wpe loglambda = np.log((12.*np.pi/Zb)*(ne*lambdadb**3)) # Goldston's equation 14.13 dEdt = - np.sqrt(2.)*ne*Zb**2*e**4*np.sqrt(m_e)*loglambda/(6.*np.pi**1.5*epsilon_0**2*ion_mass*(Te*e)**1.5) return dEdt def stopping_from_ions(dt, ni, Ti, mi, Zi, Zb, ion_mass, ion_energy): """ ni: background ion density Ti: background ion temperature (eV) mi: background ion mass Zi: background ion charge state Zb: ion charge state ion_mass: (kg) ion_energy: (eV) """ vthi = np.sqrt(3.*Ti*e/mi) wpi = np.sqrt(ni*e**2/(epsilon_0*mi)) lambdadb = vthi/wpi loglambda = np.log((12.*np.pi/Zb)*(ni*lambdadb**3)) alpha = np.sqrt(2.)*ni*Zi**2*Zb**2*e**4*np.sqrt(ion_mass)*loglambda/(8.*np.pi*epsilon_0**2*mi) f1 = np.clip((ion_energy*e)**(3./2.) - 3./2.*alpha*dt, 0., None) ion_energy = (f1)**(2./3.)/e return ion_energy # Fetch background parameters and inital particle data ds0 = yt.load(f'{prefix}{len(last_it)*"0"}') ad0 = ds0.all_data() Zb = 1. # Ion charge state ne = float(ds0.parameters['stopping_on_electrons_constant.background_density']) Te = eval(ds0.parameters['stopping_on_electrons_constant.background_temperature'])*kb/e ion_mass12 = m_p mi = eval(ds0.parameters['stopping_on_ions_constant.background_mass']) Zi = float(ds0.parameters['stopping_on_ions_constant.background_charge_state']) ni = float(ds0.parameters['stopping_on_ions_constant.background_density']) Ti = eval(ds0.parameters['stopping_on_ions_constant.background_temperature'])*kb/e ion_mass34 = 4.*m_p # For ions1, the background parameters are constants vx = ad0[('ions1', 'particle_momentum_x')].to_ndarray()/ion_mass12 vy = ad0[('ions1', 'particle_momentum_y')].to_ndarray()/ion_mass12 vz = ad0[('ions1', 'particle_momentum_z')].to_ndarray()/ion_mass12 EE1 = 0.5*ion_mass12*(vx**2 + vy**2 + vz**2)/e # For ions2, the background parameters are parsed xx = ad0[('ions2', 'particle_position_x')].to_ndarray()/ion_mass12 yy = ad0[('ions2', 'particle_position_y')].to_ndarray()/ion_mass12 ne2 = np.where(xx > 0., 1.e20, 1.e21) Te2 = np.where(yy > 0., 1., 2.) vx = ad0[('ions2', 'particle_momentum_x')].to_ndarray()/ion_mass12 vy = ad0[('ions2', 'particle_momentum_y')].to_ndarray()/ion_mass12 vz = ad0[('ions2', 'particle_momentum_z')].to_ndarray()/ion_mass12 EE2 = 0.5*ion_mass12*(vx**2 + vy**2 + vz**2)/e # For ions3, the background parameters are constants vx = ad0[('ions3', 'particle_momentum_x')].to_ndarray()/ion_mass34 vy = ad0[('ions3', 'particle_momentum_y')].to_ndarray()/ion_mass34 vz = ad0[('ions3', 'particle_momentum_z')].to_ndarray()/ion_mass34 EE3 = 0.5*ion_mass34*(vx**2 + vy**2 + vz**2)/e # For ions4, the background parameters are parsed xx = ad0[('ions4', 'particle_position_x')].to_ndarray()/ion_mass34 yy = ad0[('ions4', 'particle_position_y')].to_ndarray()/ion_mass34 ni4 = np.where(xx > 0., 1.e20, 1.e21) Ti4 = np.where(yy > 0., 0.05, 0.10) vx = ad0[('ions4', 'particle_momentum_x')].to_ndarray()/ion_mass34 vy = ad0[('ions4', 'particle_momentum_y')].to_ndarray()/ion_mass34 vz = ad0[('ions4', 'particle_momentum_z')].to_ndarray()/ion_mass34 EE4 = 0.5*ion_mass34*(vx**2 + vy**2 + vz**2)/e ds = yt.load(last_filename) ad = ds.all_data() dt = ds.current_time.to_value()/int(last_it) # Step through the same number of time steps a_EE1 = EE1 a_EE2 = EE2 a_EE3 = EE3 a_EE4 = EE4 for it in range(int(last_it)): dEdt1 = stopping_from_electrons(ne, Te, Zb, ion_mass12) a_EE1 *= np.exp(dEdt1*dt) dEdt2 = stopping_from_electrons(ne2, Te2, Zb, ion_mass12) a_EE2 *= np.exp(dEdt2*dt) a_EE3 = stopping_from_ions(dt, ni, Ti, mi, Zi, Zb, ion_mass34, a_EE3) a_EE4 = stopping_from_ions(dt, ni4, Ti4, mi, Zi, Zb, ion_mass34, a_EE4) # Fetch the final particle data vx = ad[('ions1', 'particle_momentum_x')].to_ndarray()/ion_mass12 vy = ad[('ions1', 'particle_momentum_y')].to_ndarray()/ion_mass12 vz = ad[('ions1', 'particle_momentum_z')].to_ndarray()/ion_mass12 EE1 = 0.5*ion_mass12*(vx**2 + vy**2 + vz**2)/e vx = ad[('ions2', 'particle_momentum_x')].to_ndarray()/ion_mass12 vy = ad[('ions2', 'particle_momentum_y')].to_ndarray()/ion_mass12 vz = ad[('ions2', 'particle_momentum_z')].to_ndarray()/ion_mass12 EE2 = 0.5*ion_mass12*(vx**2 + vy**2 + vz**2)/e vx = ad[('ions3', 'particle_momentum_x')].to_ndarray()/ion_mass34 vy = ad[('ions3', 'particle_momentum_y')].to_ndarray()/ion_mass34 vz = ad[('ions3', 'particle_momentum_z')].to_ndarray()/ion_mass34 EE3 = 0.5*ion_mass34*(vx**2 + vy**2 + vz**2)/e vx = ad[('ions4', 'particle_momentum_x')].to_ndarray()/ion_mass34 vy = ad[('ions4', 'particle_momentum_y')].to_ndarray()/ion_mass34 vz = ad[('ions4', 'particle_momentum_z')].to_ndarray()/ion_mass34 EE4 = 0.5*ion_mass34*(vx**2 + vy**2 + vz**2)/e error1 = np.abs(EE1 - a_EE1) error2 = np.abs(EE2 - a_EE2) error3 = np.abs(EE3 - a_EE3) error4 = np.abs(EE4 - a_EE4) print('stopping on electrons error with constant = ', error1) print('stopping on electrons error with parsed = ', error2) print('stopping on ions error with constant = ', error3) print('stopping on ions error with parsed = ', error4) print('tolerance = ', tolerance) assert np.all(error1 < tolerance) assert np.all(error2 < tolerance) assert np.all(error3 < tolerance) assert np.all(error4 < tolerance) test_name = os.path.split(os.getcwd())[1] checksumAPI.evaluate_checksum(test_name, last_filename)