aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Docs/source/usage/parameters.rst10
-rwxr-xr-xExamples/Tests/plasma_lens/analysis.py102
-rw-r--r--Examples/Tests/plasma_lens/inputs_3d50
-rw-r--r--Regression/Checksum/benchmarks_json/Plasma_lens.json23
-rw-r--r--Regression/WarpX-tests.ini18
-rw-r--r--Source/Particles/Gather/GetExternalFields.H51
-rw-r--r--Source/Particles/Gather/GetExternalFields.cpp15
-rw-r--r--Source/Particles/MultiParticleContainer.H8
-rw-r--r--Source/Particles/MultiParticleContainer.cpp25
9 files changed, 300 insertions, 2 deletions
diff --git a/Docs/source/usage/parameters.rst b/Docs/source/usage/parameters.rst
index 99b9e259d..13e6f5319 100644
--- a/Docs/source/usage/parameters.rst
+++ b/Docs/source/usage/parameters.rst
@@ -1134,8 +1134,16 @@ Laser initialization
using ``my_constants``. For a two-dimensional simulation, similar to the B-field,
it is assumed that the first and second dimensions are `x` and `z`, respectively,
and the value of the `Ey` component is set to zero.
- The current implementation of the parser for B-field on particles
+ The current implementation of the parser for E-field on particles
is applied in cartesian co-ordinates as a function of (x,y,z) even for RZ.
+ To apply a series of plasma lenses, use the option ``repeated_plasma_lens``. This
+ option requires the following parameters,
+ ``repeated_plasma_lens_period``, the period length of the repeat, a single float number,
+ ``repeated_plasma_lens_starts``, the start of each lens relative to the period, an array of floats,
+ ``repeated_plasma_lens_lengths``, the length of each lens, an array of floats,
+ ``repeated_plasma_lens_strengths``, the focusing strength of each lens, an array of floats.
+ The applied field is uniform longitudinally (along z) with a hard edge,
+ where residence corrections are used for more accurate field calculation.
* ``particles.E_external_particle`` & ``particles.B_external_particle`` (list of `float`) optional (default `0. 0. 0.`)
Two separate parameters which add an externally applied uniform E-field or
diff --git a/Examples/Tests/plasma_lens/analysis.py b/Examples/Tests/plasma_lens/analysis.py
new file mode 100755
index 000000000..b348a3053
--- /dev/null
+++ b/Examples/Tests/plasma_lens/analysis.py
@@ -0,0 +1,102 @@
+#! /usr/bin/env python
+
+# Copyright 2021 David Grote
+#
+# This file is part of WarpX.
+#
+# License: BSD-3-Clause-LBNL
+
+
+"""
+This script tests the plasma lens.
+The input file sets up a series of plasma lens and propagates a particle through them.
+The final position is compared to the analytic solution.
+The motion is slow enough that relativistic effects are ignored.
+"""
+
+import sys
+import os
+import yt
+import numpy as np
+from scipy.constants import e, m_e, c
+yt.funcs.mylog.setLevel(0)
+sys.path.insert(1, '../../../../warpx/Regression/Checksum/')
+import checksumAPI
+
+filename = sys.argv[1]
+ds = yt.load( filename )
+ad = ds.all_data()
+
+# Get final position of the particles
+# The sorting by id is to get them in the correct order
+# There are two particles, one moves in x, the other in y.
+r_id = ad['electrons', 'particle_id'].v
+
+xx_sim = ad['electrons', 'particle_position_x'].v[np.argsort(r_id)][0]
+yy_sim = ad['electrons', 'particle_position_y'].v[np.argsort(r_id)][1]
+zz_sim0 = ad['electrons', 'particle_position_z'].v[np.argsort(r_id)][0]
+zz_sim1 = ad['electrons', 'particle_position_z'].v[np.argsort(r_id)][1]
+
+ux_sim = ad['electrons', 'particle_momentum_x'].v[np.argsort(r_id)][0]/m_e
+uy_sim = ad['electrons', 'particle_momentum_y'].v[np.argsort(r_id)][1]/m_e
+
+
+def applylens(x0, vx0, vz0, lens_length, lens_strength):
+ """Given the initial position at the start of the lens,
+ calculate the end position at the exit.
+ This assumes harmonic motion."""
+ w = np.sqrt(e/m_e*lens_strength)
+ A = np.sqrt(x0**2 + (vx0/w)**2)
+ phi = np.arctan2(-vx0, w*x0)
+ t = lens_length/vz0
+ x1 = A*np.cos(w*t + phi)
+ vx1 = -w*A*np.sin(w*t + phi)
+ return x1, vx1
+
+plasma_lens_period = float(ds.parameters.get('particles.repeated_plasma_lens_period'))
+plasma_lens_starts = [float(x) for x in ds.parameters.get('particles.repeated_plasma_lens_starts').split()]
+plasma_lens_lengths = [float(x) for x in ds.parameters.get('particles.repeated_plasma_lens_lengths').split()]
+plasma_lens_strengths = [eval(x) for x in ds.parameters.get('particles.repeated_plasma_lens_strengths').split()]
+
+clight = c
+
+x0 = float(ds.parameters.get('electrons.multiple_particles_pos_x').split()[0])
+y0 = float(ds.parameters.get('electrons.multiple_particles_pos_y').split()[1])
+z0 = float(ds.parameters.get('electrons.multiple_particles_pos_z').split()[0])
+ux0 = float(ds.parameters.get('electrons.multiple_particles_vel_x').split()[0])*c
+uy0 = float(ds.parameters.get('electrons.multiple_particles_vel_y').split()[1])*c
+uz0 = eval(ds.parameters.get('electrons.multiple_particles_vel_z').split()[0])*c
+
+tt = 0.
+xx = x0
+yy = y0
+zz = z0
+ux = ux0
+uy = uy0
+uz = uz0
+
+for i in range(len(plasma_lens_starts)):
+ z_lens = i*plasma_lens_period + plasma_lens_starts[i]
+ dt = (z_lens - zz)/uz
+ tt = tt + dt
+ xx = xx + dt*ux
+ yy = yy + dt*uy
+ xx, ux = applylens(xx, ux, uz, plasma_lens_lengths[i], plasma_lens_strengths[i])
+ yy, uy = applylens(yy, uy, uz, plasma_lens_lengths[i], plasma_lens_strengths[i])
+ dt = plasma_lens_lengths[i]/uz
+ tt = tt + dt
+ zz = z_lens + plasma_lens_lengths[i]
+
+dt0 = (zz_sim0 - zz)/uz
+dt1 = (zz_sim1 - zz)/uz
+xx = xx + dt0*ux
+yy = yy + dt1*uy
+
+assert abs(xx - xx_sim) < 0.011, Exception('error in x particle position')
+assert abs(yy - yy_sim) < 0.011, Exception('error in y particle position')
+assert abs(ux - ux_sim) < 70., Exception('error in x particle velocity')
+assert abs(uy - uy_sim) < 70., Exception('error in y particle velocity')
+
+test_name = os.path.split(os.getcwd())[1]
+checksumAPI.evaluate_checksum(test_name, filename)
+
diff --git a/Examples/Tests/plasma_lens/inputs_3d b/Examples/Tests/plasma_lens/inputs_3d
new file mode 100644
index 000000000..fa19685a2
--- /dev/null
+++ b/Examples/Tests/plasma_lens/inputs_3d
@@ -0,0 +1,50 @@
+# Maximum number of time steps
+max_step = 97
+
+# number of grid points
+amr.n_cell = 16 16 16
+
+amr.max_level = 0
+
+# Geometry
+geometry.coord_sys = 0 # 0: Cartesian
+geometry.prob_lo = -1.0 -1.0 0.0 # physical domain
+geometry.prob_hi = 1.0 1.0 2.0
+
+boundary.field_lo = pec pec pec
+boundary.field_hi = pec pec pec
+boundary.particle_lo = absorbing absorbing absorbing
+boundary.particle_hi = absorbing absorbing absorbing
+
+warpx.do_pml = 0
+warpx.const_dt = 1.e-6
+warpx.do_electrostatic = labframe
+
+# Algorithms
+algo.particle_shape = 1
+
+# particles
+particles.species_names = electrons
+
+electrons.charge = -q_e
+electrons.mass = m_e
+electrons.injection_style = "MultipleParticles"
+electrons.multiple_particles_pos_x = 0.5 0.
+electrons.multiple_particles_pos_y = 0. 0.4
+electrons.multiple_particles_pos_z = 0.05 0.05
+electrons.multiple_particles_vel_x = 0. 0.
+electrons.multiple_particles_vel_y = 0. 0.
+electrons.multiple_particles_vel_z = 0.02e6/clight 0.02e6/clight
+electrons.multiple_particles_weight = 1. 1.
+
+particles.E_ext_particle_init_style = repeated_plasma_lens
+particles.repeated_plasma_lens_period = 0.5
+particles.repeated_plasma_lens_starts = 0.1 0.11 0.12 0.13
+particles.repeated_plasma_lens_lengths = 0.1 0.11 0.12 0.13
+particles.repeated_plasma_lens_strengths = 0.07 0.06 0.06 0.03
+
+# Diagnostics
+diagnostics.diags_names = diag1
+diag1.intervals = 97
+diag1.diag_type = Full
+diag1.electrons.variables = ux uy uz
diff --git a/Regression/Checksum/benchmarks_json/Plasma_lens.json b/Regression/Checksum/benchmarks_json/Plasma_lens.json
new file mode 100644
index 000000000..69ad58618
--- /dev/null
+++ b/Regression/Checksum/benchmarks_json/Plasma_lens.json
@@ -0,0 +1,23 @@
+{
+ "electrons": {
+ "particle_cpu": 1.0,
+ "particle_id": 2.0,
+ "particle_momentum_x": 2.2518779586696112e-26,
+ "particle_momentum_y": 1.8015029214643703e-26,
+ "particle_momentum_z": 3.6437524496743613e-26,
+ "particle_position_x": 0.33581536370442583,
+ "particle_position_y": 0.2686524426674559,
+ "particle_position_z": 3.979988490885961
+ },
+ "lev=0": {
+ "Bx": 0.0,
+ "By": 0.0,
+ "Bz": 0.0,
+ "Ex": 8.557217761445071e-07,
+ "Ey": 9.086796476605627e-07,
+ "Ez": 1.2874219492176113e-06,
+ "jx": 0.0,
+ "jy": 0.0,
+ "jz": 0.0
+ }
+}
diff --git a/Regression/WarpX-tests.ini b/Regression/WarpX-tests.ini
index 5ab0c3c0f..f79c7eb8e 100644
--- a/Regression/WarpX-tests.ini
+++ b/Regression/WarpX-tests.ini
@@ -2188,6 +2188,24 @@ particleTypes = electrons
analysisRoutine = Examples/Tests/pass_mpi_communicator/analysis.py
tolerance = 1.e-14
+[Plasma_lens]
+buildDir = .
+inputFile = Examples/Tests/plasma_lens/inputs_3d
+runtime_params =
+dim = 3
+addToCompileString =
+restartTest = 0
+useMPI = 1
+numprocs = 2
+useOMP = 1
+numthreads = 2
+compileTest = 0
+doVis = 0
+compareParticles = 1
+particleTypes = electrons
+analysisRoutine = Examples/Tests/plasma_lens/analysis.py
+tolerance = 1.e-14
+
[background_mcc]
buildDir = .
inputFile = Examples/Physics_applications/capacitive_discharge/inputs_2d
diff --git a/Source/Particles/Gather/GetExternalFields.H b/Source/Particles/Gather/GetExternalFields.H
index 254d335b9..468b8d078 100644
--- a/Source/Particles/Gather/GetExternalFields.H
+++ b/Source/Particles/Gather/GetExternalFields.H
@@ -12,7 +12,7 @@
#include <AMReX_Parser.H>
#include <AMReX_REAL.H>
-enum ExternalFieldInitType { Constant, Parser };
+enum ExternalFieldInitType { Constant, Parser, RepeatedPlasmaLens };
/** \brief Base class for functors that assign external
* field values (E or B) to particles.
@@ -29,12 +29,23 @@ struct GetExternalField
GetParticlePosition m_get_position;
amrex::Real m_time;
+ amrex::Real m_repeated_plasma_lens_period;
+ const amrex::Real* AMREX_RESTRICT m_repeated_plasma_lens_starts = nullptr;
+ const amrex::Real* AMREX_RESTRICT m_repeated_plasma_lens_lengths = nullptr;
+ const amrex::Real* AMREX_RESTRICT m_repeated_plasma_lens_strengths = nullptr;
+ int m_n_lenses;
+ amrex::Real m_dt;
+ const amrex::ParticleReal* AMREX_RESTRICT m_ux = nullptr;
+ const amrex::ParticleReal* AMREX_RESTRICT m_uy = nullptr;
+ const amrex::ParticleReal* AMREX_RESTRICT m_uz = nullptr;
+
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
void operator () (long i,
amrex::ParticleReal& field_x,
amrex::ParticleReal& field_y,
amrex::ParticleReal& field_z) const noexcept
{
+ using namespace amrex::literals;
if (m_type == Constant)
{
field_x += m_field_value[0];
@@ -49,6 +60,44 @@ struct GetExternalField
field_y += m_yfield_partparser(x, y, z, m_time);
field_z += m_zfield_partparser(x, y, z, m_time);
}
+ else if (m_type == RepeatedPlasmaLens)
+ {
+ amrex::ParticleReal x, y, z;
+ m_get_position(i, x, y, z);
+
+ amrex::ParticleReal const uxp = m_ux[i];
+ amrex::ParticleReal const uyp = m_uy[i];
+ amrex::ParticleReal const uzp = m_uz[i];
+ constexpr amrex::Real inv_c2 = 1._rt/(PhysConst::c*PhysConst::c);
+ const amrex::Real inv_gamma = 1._rt/std::sqrt(1._rt + (uxp*uxp + uyp*uyp + uzp*uzp)*inv_c2);
+ const amrex::ParticleReal vzp = uzp*inv_gamma;
+
+ // This assumes that vzp > 0.
+ amrex::ParticleReal const zl = z;
+ amrex::ParticleReal const zr = z + vzp*m_dt;
+
+ // This assumes that zl > 0.
+ int i_lens = static_cast<int>(std::floor(zl/m_repeated_plasma_lens_period));
+ i_lens = i_lens % m_n_lenses;
+ amrex::Real const lens_start = m_repeated_plasma_lens_starts[i_lens] + i_lens*m_repeated_plasma_lens_period;
+ amrex::Real const lens_end = lens_start + m_repeated_plasma_lens_lengths[i_lens];
+
+ // Calculate the residence correction
+ // frac will be 1 if the step is completely inside the lens, between 0 and 1
+ // when entering or leaving the lens, and otherwise 0.
+ amrex::Real fl = 0.;
+ if (zl >= lens_start && zl < lens_end) fl = 1.;
+ amrex::Real fr = 0.;
+ if (zr >= lens_start && zr < lens_end) fr = 1.;
+ amrex::Real frac = fl;
+ amrex::Real dzi = 1./(vzp*m_dt);
+ if (fl > fr) frac = (lens_end - zl)*dzi;
+ if (fr > fl) frac = (zr - lens_start)*dzi;
+
+ field_x += x*frac*m_repeated_plasma_lens_strengths[i_lens];
+ field_y += y*frac*m_repeated_plasma_lens_strengths[i_lens];
+
+ }
else
{
amrex::Abort("ExternalFieldInitType not known!!! \n");
diff --git a/Source/Particles/Gather/GetExternalFields.cpp b/Source/Particles/Gather/GetExternalFields.cpp
index 0808bd6c1..03c4dc2b8 100644
--- a/Source/Particles/Gather/GetExternalFields.cpp
+++ b/Source/Particles/Gather/GetExternalFields.cpp
@@ -29,6 +29,21 @@ GetExternalEField::GetExternalEField (const WarpXParIter& a_pti, int a_offset) n
m_yfield_partparser = mypc.m_Ey_particle_parser->compile<4>();
m_zfield_partparser = mypc.m_Ez_particle_parser->compile<4>();
}
+ else if (mypc.m_E_ext_particle_s=="repeated_plasma_lens")
+ {
+ m_type = RepeatedPlasmaLens;
+ m_dt = warpx.getdt(a_pti.GetLevel());
+ m_get_position = GetParticlePosition(a_pti, a_offset);
+ auto& attribs = a_pti.GetAttribs();
+ m_ux = attribs[PIdx::ux].dataPtr() + a_offset;
+ m_uy = attribs[PIdx::uy].dataPtr() + a_offset;
+ m_uz = attribs[PIdx::uz].dataPtr() + a_offset;
+ m_repeated_plasma_lens_period = mypc.m_repeated_plasma_lens_period;
+ m_n_lenses = static_cast<int>(mypc.h_repeated_plasma_lens_starts.size());
+ m_repeated_plasma_lens_starts = mypc.d_repeated_plasma_lens_starts.data();
+ m_repeated_plasma_lens_lengths = mypc.d_repeated_plasma_lens_lengths.data();
+ m_repeated_plasma_lens_strengths = mypc.d_repeated_plasma_lens_strengths.data();
+ }
}
GetExternalBField::GetExternalBField (const WarpXParIter& a_pti, int a_offset) noexcept
diff --git a/Source/Particles/MultiParticleContainer.H b/Source/Particles/MultiParticleContainer.H
index 5ee416add..b59aa4371 100644
--- a/Source/Particles/MultiParticleContainer.H
+++ b/Source/Particles/MultiParticleContainer.H
@@ -296,6 +296,14 @@ public:
std::unique_ptr<amrex::Parser> m_Ey_particle_parser;
std::unique_ptr<amrex::Parser> m_Ez_particle_parser;
+ amrex::Real m_repeated_plasma_lens_period;
+ amrex::Vector<amrex::Real> h_repeated_plasma_lens_starts;
+ amrex::Vector<amrex::Real> h_repeated_plasma_lens_lengths;
+ amrex::Vector<amrex::Real> h_repeated_plasma_lens_strengths;
+ amrex::Gpu::DeviceVector<amrex::Real> d_repeated_plasma_lens_starts;
+ amrex::Gpu::DeviceVector<amrex::Real> d_repeated_plasma_lens_lengths;
+ amrex::Gpu::DeviceVector<amrex::Real> d_repeated_plasma_lens_strengths;
+
#ifdef WARPX_QED
/**
* \brief Performs QED events (Breit-Wheeler process and photon emission)
diff --git a/Source/Particles/MultiParticleContainer.cpp b/Source/Particles/MultiParticleContainer.cpp
index 5e59d6eaa..b0a1d211f 100644
--- a/Source/Particles/MultiParticleContainer.cpp
+++ b/Source/Particles/MultiParticleContainer.cpp
@@ -223,6 +223,31 @@ MultiParticleContainer::ReadParameters ()
}
+ // if the input string for E_ext_particle_s is
+ // "repeated_plasma_lens" then the plasma lens properties
+ // must be provided in the input file.
+ if (m_E_ext_particle_s == "repeated_plasma_lens") {
+ queryWithParser(pp_particles, "repeated_plasma_lens_period", m_repeated_plasma_lens_period);
+ getArrWithParser(pp_particles, "repeated_plasma_lens_starts", h_repeated_plasma_lens_starts);
+ getArrWithParser(pp_particles, "repeated_plasma_lens_lengths", h_repeated_plasma_lens_lengths);
+ getArrWithParser(pp_particles, "repeated_plasma_lens_strengths", h_repeated_plasma_lens_strengths);
+
+ int n_lenses = static_cast<int>(h_repeated_plasma_lens_starts.size());
+ d_repeated_plasma_lens_starts.resize(n_lenses);
+ d_repeated_plasma_lens_lengths.resize(n_lenses);
+ d_repeated_plasma_lens_strengths.resize(n_lenses);
+ amrex::Gpu::copyAsync(amrex::Gpu::hostToDevice,
+ h_repeated_plasma_lens_starts.begin(), h_repeated_plasma_lens_starts.end(),
+ d_repeated_plasma_lens_starts.begin());
+ amrex::Gpu::copyAsync(amrex::Gpu::hostToDevice,
+ h_repeated_plasma_lens_lengths.begin(), h_repeated_plasma_lens_lengths.end(),
+ d_repeated_plasma_lens_lengths.begin());
+ amrex::Gpu::copyAsync(amrex::Gpu::hostToDevice,
+ h_repeated_plasma_lens_strengths.begin(), h_repeated_plasma_lens_strengths.end(),
+ d_repeated_plasma_lens_strengths.begin());
+ amrex::Gpu::synchronize();
+ }
+
// particle species
pp_particles.queryarr("species_names", species_names);