A Two-stream Instability
Last updated on 2026-04-06 | Edit this page
Estimated time: 30 minutes
Overview
Questions
How do two counter-streaming electron beams become unstable and generate electrostatic waves?
Objectives
Set up, run, and visualize a 1D two-stream instability simulation with WarpX. Understand how the choice of physical parameters affects the instability growth.
Introduction
The two-stream instability is one of the most fundamental kinetic instabilities in plasma physics. When two populations of charged particles (here, electrons) stream through each other with a relative drift velocity, small perturbations in the charge density can grow exponentially. The free energy stored in the relative drift is converted into electrostatic wave energy until the beams thermalize and the instability saturates.
This example uses a 1D periodic box with two counter-streaming electron populations, making it an ideal starting point for studying kinetic plasma instabilities with a PIC code.
Setup
Make sure to download the input file.
Whenever you need to prepare an input file, this is where you want to go.
OUTPUT
####################
### MY CONSTANTS ###
####################
# PLASMAS
my_constants.n0 = ... # [m^-3]
my_constants.T0 = ...*q_e # [J]
my_constants.v_te = sqrt(T0 / m_e) # [m/s]
my_constants.beta0 = ... # [-]
my_constants.omega_pe = sqrt(n0*q_e**2/(m_e*epsilon0)) # [1/s]
# BOX
my_constants.Lx = ...*clight/omega_pe
my_constants.nx = ...
my_constants.dx = Lx/nx
# TIME
my_constants.cfl = ...
my_constants.T = .../omega_pe
my_constants.dt = cfl * dx / clight
my_constants.nt = floor(T/dt)
##########################
### GENERAL PARAMETERS ###
##########################
stop_time = T
amr.n_cell = nx nx nx
amr.max_level = 0
geometry.dims = 1
geometry.prob_lo = -0.5*Lx -0.5*Lx -0.5*Lx
geometry.prob_hi = 0.5*Lx 0.5*Lx 0.5*Lx
##########################
### BOUNDARY CONDITION ###
##########################
boundary.field_lo = periodic periodic periodic
boundary.field_hi = periodic periodic periodic
boundary.particle_lo = periodic periodic periodic
boundary.particle_hi = periodic periodic periodic
################
### NUMERICS ###
################
warpx.cfl = cfl
algo.maxwell_solver = yee
algo.particle_shape = 3
algo.particle_pusher = boris
warpx.use_filter = 1
#################
### PARTICLES ###
#################
particles.species_names = ele1 ele2
ele1.species_type = electron
ele1.injection_style = NRandomPerCell
ele1.num_particles_per_cell = 200
ele1.profile = constant
ele1.density = n0
ele1.momentum_distribution_type = maxwell_boltzmann
ele1.theta_distribution_type = constant
ele1.theta = T0 / (m_e * clight**2)
ele1.beta_distribution_type = parser
ele1.beta_function(x,y,z) = beta0
ele1.bulk_vel_dir = +z
ele2.species_type = electron
ele2.injection_style = NRandomPerCell
ele2.num_particles_per_cell = 200
ele2.profile = constant
ele2.density = n0
ele2.momentum_distribution_type = maxwell_boltzmann
ele2.theta_distribution_type = constant
ele2.theta = T0 / (m_e * clight**2)
ele2.beta_distribution_type = constant
ele2.beta = beta0
ele2.bulk_vel_dir = -z
###################
### DIAGNOSTICS ###
###################
# FULL
diagnostics.diags_names = particles
particles.intervals = floor(nt/200)
particles.diag_type = Full
particles.species = ele1 ele2
particles.fields_to_plot = none
particles.format = openpmd
particles.openpmd_backend = bp
particles.dump_last_timestep = 1
particles.ele1.variables = w z uz
particles.ele2.variables = w z uz
# REDUCED
warpx.reduced_diags_names = FieldEnergy FieldMaximum
FieldEnergy.type = FieldEnergy
FieldEnergy.intervals = 1
FieldMaximum.type = FieldMaximum
FieldMaximum.intervals = 1
Choosing the parameters
You will notice that some parameters in the input file are set to
... (ellipsis). These are left for you to
choose! Picking sensible physical and numerical parameters is an
important part of setting up a simulation. Here is some guidance.
Choose the physical parameters
The physical parameters you need to set are:
| Parameter | Symbol | Description |
|---|---|---|
n0 |
\(n_0\) | Electron density of each beam \([\mathrm{m^{-3}}]\) |
T0 |
\(T_0\) | Thermal temperature of each beam \([\mathrm{eV}]\) |
beta0 |
\(\beta_0 = v_d/c\) | Drift velocity of each beam normalized to \(c\) |
Think about the following:
- A typical laboratory plasma density is in the range \(10^{18}\)–\(10^{24}\) m\(^{-3}\).
- The temperature should be low enough that the thermal spread does not wash out the instability: the drift velocity should be larger than the thermal velocity, i.e. \(v_d \gg v_{\mathrm{th}}\).
- The drift velocity \(\beta_0\) controls how fast the instability grows. Non-relativistic drifts (\(\beta_0 \ll 1\)) are a good starting point.
Choose the numerical parameters
The numerical parameters you need to set are:
| Parameter | Description |
|---|---|
Lx |
Box length, expressed in units of the skin depth \(c/\omega_{pe}\) |
nx |
Number of grid cells |
cfl |
CFL number (must be \(< 1\)) |
T |
Total simulation time, expressed in units of \(\omega_{pe}^{-1}\) |
Think about the following:
- The box must be large enough to fit several wavelengths of the most unstable mode. A box of \(\sim 10\)–\(50\) skin depths is a reasonable choice.
- The grid must resolve the skin depth, i.e. \(\Delta x \lesssim c/\omega_{pe}\). So
nxshould be at least comparable toLx(in skin depth units). - The CFL should be \(\lesssim 1\) for stability of the Yee solver.
- The simulation must run long enough for the instability to develop and saturate: \(\sim 50\)–\(200\,\omega_{pe}^{-1}\) is a typical range.
Notable details
Two electron species (
ele1,ele2) are initialized with equal densityn0and temperatureT0, but with opposite drift velocities \(\pm \beta_0 c\) along the \(z\)-axis.There is no ion background in this input; the simulation relies on the periodic boundary conditions and the two equal-density beams to maintain charge neutrality on average.
The diagnostics write out particle data (
z,uz) for both species, which lets you construct the phase space \((z, u_z)\) at each snapshot.Reduced diagnostics (
FieldEnergy,FieldMaximum) record the total field energy and the maximum field value at every timestep, so you can track the exponential growth of the instability without post-processing the full data.
Run
Create a new folder and copy the input file there, after filling in your chosen parameters. This is a 1D simulation, so it should run very quickly even in serial.
You should see a standard output flashing out a lot of info.
At the end, you should find in your folder:
- a subfolder called
diags: here is where the code stored the diagnostics - a file called
warpx_used_inputs: this is a summary of the inputs that were used to run the simulation - files
FieldEnergy.txtandFieldMaximum.txtwith the reduced diagnostics
If that’s the case, yey! 💯
If the run went wrong, you may find a Backtrace.0.0 file
which can be useful for debugging purposes. Let me know if the code
fails in any way!
Visualize
With Python 🐍
Now that we have the results, we can analyze them using Python.
We will use the openPMD-viewer library
to grab the data that the simulation produced in openPMD format. Here you can find a
few tutorials on how to use the viewer. If you feel nerdy and/or you
need to deal with the data in parallel workflows, you can use the openPMD-api.
Here are some things you can visualize:
Phase space \((z, u_z)\): plot the particle positions and momenta at several time snapshots to see how the beams evolve from smooth streams into characteristic “cat-eye” vortices.
Field energy vs time: load
FieldEnergy.txtand plot the total electrostatic energy as a function of time on a semi-log scale. You should see an exponential growth phase followed by saturation.Growth rate: from the slope of \(\ln(E_{\mathrm{field}})\) during the linear phase, extract the growth rate and compare it to the theoretical prediction \(\gamma \sim \omega_{pe}\).
With a Jupyter notebook 📓
The notebook includes a phase-space plot \((z, u_z)\) and the evolution of the electric and magnetic field energy on a semi-log scale.
You can download the notebook and try it yourself. Remember to either run the notebook from the simulation directory or change the corresponding path in the notebook.
Questions for analysis
How does the growth rate of the field energy depend on the drift velocity \(\beta_0\)? Try running with two different values and compare.
What happens to the phase space after saturation? Can you identify particle trapping in the electrostatic potential wells?
If you increase the temperature (decrease the ratio \(v_d / v_{\mathrm{th}}\)), does the instability still develop? At what point is it suppressed?
How does the number of particles per cell affect the noise level and the onset of the instability?
These are open-ended questions meant to guide your exploration. The key insight is that the instability requires \(v_d > v_{\mathrm{th}}\); when thermal effects dominate, Landau damping stabilizes the modes.
Gallery
💡 The two-stream instability converts the kinetic energy of counter-streaming beams into electrostatic wave energy.
🔬 The instability growth rate depends on the ratio of drift velocity to thermal velocity: \(v_d / v_{\mathrm{th}} \gg 1\) is needed for the instability to develop.
📊 Reduced diagnostics (FieldEnergy,
FieldMaximum) let you track the instability growth in real
time without post-processing full datasets.
🔍 The documentation is the first place to look for answers, otherwise check out our issues and discussions and ask there.
📷 To analyze and visualize the simulation results in openPMD format, you can use the openPMD-viewer library for Python.