Beam-beam simulations at the interaction point
This section contains example scripts for simulations of beam-beam crossing at the interaction point using WarpX and Guinea-Pig. As an arbitrary choice, we selected the \(C^3\) collider parameters at 125 GeV.
Overview
This simulates the collision between a beam of electrons and a beam of positrons traveling at 125 GeV. As usual, \(x, y, z\) are the horizontal, vertical, and longitudinal coordinates, respectively. The beams are initialized such that their centroids are \(4 \sigma_z\) away from interaction point.
Beamstrahlung and coherent pair production (though not actually important for these beam parameters) are activated. Note that incoherent processes are not enabled in either WarpX and Guinea-Pig. We will add them in future versions of this example.
WarpX
Resources
Some useful WarpX’s links if you get stuck or want to know more:
Build
WarpX is open-source and available on GitHub. You can install WarpX in several different ways (for example from source, using Conda, Spack), and on all the major operative systems (Linux, Windows, MacOS). You can compile it on CPU or on GPU, depending on your machine. Find the instructions here.
Run
Download or copy the input file below.
See WarpX inputs
########################
##### MY CONSTANTS #####
########################
my_constants.mc2 = m_e*clight*clight
my_constants.nano = 1.0e-9
my_constants.micro = 1.e-6
my_constants.GeV = q_e*1.e9
my_constants.milli = 1.e-3
# BEAMS
my_constants.beam_energy = 125*GeV
my_constants.beam_gamma = beam_energy/mc2
my_constants.beam_npart = 6.24e9
my_constants.nmacropart = 1e5
my_constants.beam_charge = q_e * beam_npart
my_constants.sigmaz = 100.*micro
my_constants.beam_uth = 0.3/100*beam_gamma
my_constants.mux = 0.*sigmax
my_constants.muy = 0.*sigmay
my_constants.muz = 4*sigmaz
my_constants.emitx = 900*nano
my_constants.emity = 20*nano
my_constants.dux = emitx / sigmax
my_constants.duy = emity / sigmay
my_constants.betax = 12*milli
my_constants.betay = 0.12*milli
my_constants.sigmax = sqrt( emitx * betax / beam_gamma )
my_constants.sigmay = sqrt( emity * betay / beam_gamma )
# BOX
my_constants.Lx = 20*sigmax
my_constants.Ly = 20*sigmay
my_constants.Lz = 16*sigmaz
my_constants.nx = 256
my_constants.ny = 256
my_constants.nz = 64
my_constants.dx = Lx/nx
my_constants.dy = Ly/ny
my_constants.dz = Lz/nz
# TIME
my_constants.T = 0.5*Lz/clight
my_constants.dt = T / nz
my_constants.nt = floor(T/dt)
##############################
##### GENERAL PARAMETERS #####
##############################
stop_time = T
amr.n_cell = nx ny nz
amr.max_grid_size = nz
amr.blocking_factor = 2
amr.max_level = 0
geometry.dims = 3
geometry.prob_lo = -0.5*Lx -0.5*Ly -0.5*Lz
geometry.prob_hi = 0.5*Lx 0.5*Ly 0.5*Lz
####################
##### NUMERICS #####
####################
warpx.do_electrostatic = relativistic
warpx.const_dt = dt
warpx.grid_type = collocated
algo.particle_shape = 3
algo.particle_pusher = vay
warpx.poisson_solver = fft
###############################
##### BOUNDARY CONDITIONS #####
###############################
boundary.field_lo = open open open
boundary.field_hi = open open open
boundary.particle_lo = Absorbing Absorbing Absorbing
boundary.particle_hi = Absorbing Absorbing Absorbing
#####################
##### PARTICLES #####
#####################
particles.species_names = beam1 beam2 pho1 pho2 ele_nlbw1 pos_nlbw1 ele_nlbw2 pos_nlbw2
particles.photon_species = pho1 pho2
beam1.species_type = electron
beam1.injection_style = gaussian_beam
beam1.x_rms = sigmax
beam1.y_rms = sigmay
beam1.z_rms = sigmaz
beam1.x_m = - mux
beam1.y_m = - muy
beam1.z_m = - muz
beam1.npart = nmacropart
beam1.q_tot = -beam_charge
beam1.z_cut = 4
beam1.focal_distance = muz
beam1.momentum_distribution_type = gaussian
beam1.uz_m = beam_gamma
beam1.uy_m = 0.0
beam1.ux_m = 0.0
beam1.ux_th = dux
beam1.uy_th = duy
beam1.uz_th = beam_uth
beam1.initialize_self_fields = 1
beam1.do_qed_quantum_sync = 1
beam1.qed_quantum_sync_phot_product_species = pho1
beam1.do_classical_radiation_reaction = 0
beam2.species_type = positron
beam2.injection_style = gaussian_beam
beam2.x_rms = sigmax
beam2.y_rms = sigmay
beam2.z_rms = sigmaz
beam2.x_m = mux
beam2.y_m = muy
beam2.z_m = muz
beam2.npart = nmacropart
beam2.q_tot = beam_charge
beam2.z_cut = 4
beam2.focal_distance = muz
beam2.momentum_distribution_type = gaussian
beam2.uz_m = -beam_gamma
beam2.uy_m = 0.0
beam2.ux_m = 0.0
beam2.ux_th = dux
beam2.uy_th = duy
beam2.uz_th = beam_uth
beam2.initialize_self_fields = 1
beam2.do_qed_quantum_sync = 1
beam2.qed_quantum_sync_phot_product_species = pho2
beam2.do_classical_radiation_reaction = 0
pho1.species_type = photon
pho1.injection_style = none
pho1.do_qed_breit_wheeler = 1
pho1.qed_breit_wheeler_ele_product_species = ele_nlbw1
pho1.qed_breit_wheeler_pos_product_species = pos_nlbw1
pho2.species_type = photon
pho2.injection_style = none
pho2.do_qed_breit_wheeler = 1
pho2.qed_breit_wheeler_ele_product_species = ele_nlbw2
pho2.qed_breit_wheeler_pos_product_species = pos_nlbw2
ele_nlbw1.species_type = electron
ele_nlbw1.injection_style = none
ele_nlbw1.do_qed_quantum_sync = 1
ele_nlbw1.qed_quantum_sync_phot_product_species = pho1
ele_nlbw1.do_classical_radiation_reaction = 0
pos_nlbw1.species_type = positron
pos_nlbw1.injection_style = none
pos_nlbw1.do_qed_quantum_sync = 1
pos_nlbw1.qed_quantum_sync_phot_product_species = pho1
pos_nlbw1.do_classical_radiation_reaction = 0
ele_nlbw2.species_type = electron
ele_nlbw2.injection_style = none
ele_nlbw2.do_qed_quantum_sync = 1
ele_nlbw2.qed_quantum_sync_phot_product_species = pho2
ele_nlbw2.do_classical_radiation_reaction = 0
pos_nlbw2.species_type = positron
pos_nlbw2.injection_style = none
pos_nlbw2.do_qed_quantum_sync = 1
pos_nlbw2.qed_quantum_sync_phot_product_species = pho2
pos_nlbw2.do_classical_radiation_reaction = 0
###############
##### QED #####
###############
qed_qs.photon_creation_energy_threshold = 0.
qed_qs.lookup_table_mode = builtin
qed_qs.chi_min = 1.e-7
qed_bw.lookup_table_mode = builtin
qed_bw.chi_min = 1.e-2
warpx.do_qed_schwinger = 0.
#######################
##### DIAGNOSTICS #####
#######################
# FULL
diagnostics.diags_names = bound trajs
bound.dump_last_timestep = 1
bound.diag_type = BoundaryScraping
bound.format = openpmd
bound.openpmd_backend = bp
bound.intervals = 1
beam1.save_particles_at_xlo = 1
beam1.save_particles_at_ylo = 1
beam1.save_particles_at_zlo = 1
beam1.save_particles_at_xhi = 1
beam1.save_particles_at_yhi = 1
beam1.save_particles_at_zhi = 1
beam2.save_particles_at_xlo = 1
beam2.save_particles_at_ylo = 1
beam2.save_particles_at_zlo = 1
beam2.save_particles_at_xhi = 1
beam2.save_particles_at_yhi = 1
beam2.save_particles_at_zhi = 1
ele_nlbw1.save_particles_at_xlo = 1
ele_nlbw1.save_particles_at_ylo = 1
ele_nlbw1.save_particles_at_zlo = 1
ele_nlbw1.save_particles_at_xhi = 1
ele_nlbw1.save_particles_at_yhi = 1
ele_nlbw1.save_particles_at_zhi = 1
ele_nlbw2.save_particles_at_xlo = 1
ele_nlbw2.save_particles_at_ylo = 1
ele_nlbw2.save_particles_at_zlo = 1
ele_nlbw2.save_particles_at_xhi = 1
ele_nlbw2.save_particles_at_yhi = 1
ele_nlbw2.save_particles_at_zhi = 1
pos_nlbw1.save_particles_at_xlo = 1
pos_nlbw1.save_particles_at_ylo = 1
pos_nlbw1.save_particles_at_zlo = 1
pos_nlbw1.save_particles_at_xhi = 1
pos_nlbw1.save_particles_at_yhi = 1
pos_nlbw1.save_particles_at_zhi = 1
pos_nlbw2.save_particles_at_xlo = 1
pos_nlbw2.save_particles_at_ylo = 1
pos_nlbw2.save_particles_at_zlo = 1
pos_nlbw2.save_particles_at_xhi = 1
pos_nlbw2.save_particles_at_yhi = 1
pos_nlbw2.save_particles_at_zhi = 1
pho1.save_particles_at_xlo = 1
pho1.save_particles_at_ylo = 1
pho1.save_particles_at_zlo = 1
pho1.save_particles_at_xhi = 1
pho1.save_particles_at_yhi = 1
pho1.save_particles_at_zhi = 1
pho2.save_particles_at_xlo = 1
pho2.save_particles_at_ylo = 1
pho2.save_particles_at_zlo = 1
pho2.save_particles_at_xhi = 1
pho2.save_particles_at_yhi = 1
pho2.save_particles_at_zhi = 1
trajs.intervals = 32
trajs.diag_type = Full
trajs.species = beam1 beam2 pho1 pho2 ele_nlbw1 pos_nlbw1 ele_nlbw2 pos_nlbw2
trajs.fields_to_plot = none
trajs.format = openpmd
trajs.openpmd_backend = h5
trajs.dump_last_timestep = 1
# REDUCED
warpx.reduced_diags_names = ParticleEnergy RhoMaximum ParticleNumber CollRel_beam1_beam2 DiffLumi_beam1_beam2 DiffLumi_beam1_ele2 DiffLumi_beam1_pos2 DiffLumi_beam1_pho2 DiffLumi_ele1_beam2 DiffLumi_ele1_ele2 DiffLumi_ele1_pos2 DiffLumi_ele1_pho2 DiffLumi_pos1_beam2 DiffLumi_pos1_ele2 DiffLumi_pos1_pos2 DiffLumi_pos1_pho2 DiffLumi_pho1_beam2 DiffLumi_pho1_ele2 DiffLumi_pho1_pos2 DiffLumi_pho1_pho2
ParticleEnergy.type = ParticleEnergy
ParticleEnergy.intervals = 1
RhoMaximum.type = RhoMaximum
RhoMaximum.intervals = 1
ParticleNumber.type = ParticleNumber
ParticleNumber.intervals = 1
CollRel_beam1_beam2.type = ColliderRelevant
CollRel_beam1_beam2.intervals = 1
CollRel_beam1_beam2.species = beam1 beam2
DiffLumi_beam1_beam2.type = DifferentialLuminosity
DiffLumi_beam1_ele2.type = DifferentialLuminosity
DiffLumi_beam1_pos2.type = DifferentialLuminosity
DiffLumi_beam1_pho2.type = DifferentialLuminosity
DiffLumi_ele1_beam2.type = DifferentialLuminosity
DiffLumi_ele1_ele2.type = DifferentialLuminosity
DiffLumi_ele1_pos2.type = DifferentialLuminosity
DiffLumi_ele1_pho2.type = DifferentialLuminosity
DiffLumi_pos1_beam2.type = DifferentialLuminosity
DiffLumi_pos1_ele2.type = DifferentialLuminosity
DiffLumi_pos1_pos2.type = DifferentialLuminosity
DiffLumi_pos1_pho2.type = DifferentialLuminosity
DiffLumi_pho1_beam2.type = DifferentialLuminosity
DiffLumi_pho1_ele2.type = DifferentialLuminosity
DiffLumi_pho1_pos2.type = DifferentialLuminosity
DiffLumi_pho1_pho2.type = DifferentialLuminosity
DiffLumi_beam1_beam2.bin_max = 2.1*beam_energy/q_e
DiffLumi_beam1_ele2.bin_max = 2.1*beam_energy/q_e
DiffLumi_beam1_pos2.bin_max = 2.1*beam_energy/q_e
DiffLumi_beam1_pho2.bin_max = 2.1*beam_energy/q_e
DiffLumi_ele1_beam2.bin_max = 2.1*beam_energy/q_e
DiffLumi_ele1_ele2.bin_max = 2.1*beam_energy/q_e
DiffLumi_ele1_pos2.bin_max = 2.1*beam_energy/q_e
DiffLumi_ele1_pho2.bin_max = 2.1*beam_energy/q_e
DiffLumi_pos1_beam2.bin_max = 2.1*beam_energy/q_e
DiffLumi_pos1_ele2.bin_max = 2.1*beam_energy/q_e
DiffLumi_pos1_pos2.bin_max = 2.1*beam_energy/q_e
DiffLumi_pos1_pho2.bin_max = 2.1*beam_energy/q_e
DiffLumi_pho1_beam2.bin_max = 2.1*beam_energy/q_e
DiffLumi_pho1_ele2.bin_max = 2.1*beam_energy/q_e
DiffLumi_pho1_pos2.bin_max = 2.1*beam_energy/q_e
DiffLumi_pho1_pho2.bin_max = 2.1*beam_energy/q_e
DiffLumi_beam1_beam2.bin_min = 0.
DiffLumi_beam1_ele2.bin_min = 0.
DiffLumi_beam1_pos2.bin_min = 0.
DiffLumi_beam1_pho2.bin_min = 0.
DiffLumi_ele1_beam2.bin_min = 0.
DiffLumi_ele1_ele2.bin_min = 0.
DiffLumi_ele1_pos2.bin_min = 0.
DiffLumi_ele1_pho2.bin_min = 0.
DiffLumi_pos1_beam2.bin_min = 0.
DiffLumi_pos1_ele2.bin_min = 0.
DiffLumi_pos1_pos2.bin_min = 0.
DiffLumi_pos1_pho2.bin_min = 0.
DiffLumi_pho1_beam2.bin_min = 0.
DiffLumi_pho1_ele2.bin_min = 0.
DiffLumi_pho1_pos2.bin_min = 0.
DiffLumi_pho1_pho2.bin_min = 0.
DiffLumi_beam1_beam2.species = beam1 beam2
DiffLumi_beam1_ele2.species = beam1 ele_nlbw2
DiffLumi_beam1_pos2.species = beam1 pos_nlbw2
DiffLumi_beam1_pho2.species = beam1 pho2
DiffLumi_ele1_beam2.species = ele_nlbw1 beam2
DiffLumi_ele1_ele2.species= ele_nlbw1 ele_nlbw2
DiffLumi_ele1_pos2.species= ele_nlbw1 pos_nlbw2
DiffLumi_ele1_pho2.species= ele_nlbw1 pho2
DiffLumi_pos1_beam2.species = pos_nlbw1 beam2
DiffLumi_pos1_ele2.species= pos_nlbw1 ele_nlbw2
DiffLumi_pos1_pos2.species= pos_nlbw1 pos_nlbw2
DiffLumi_pos1_pho2.species= pos_nlbw1 pho2
DiffLumi_pho1_beam2.species = pho1 beam2
DiffLumi_pho1_ele2.species= pho1 ele_nlbw2
DiffLumi_pho1_pos2.species= pho1 pos_nlbw2
DiffLumi_pho1_pho2.species= pho1 pho2
DiffLumi_beam1_beam2.bin_number = 256
DiffLumi_beam1_ele2.bin_number = 256
DiffLumi_beam1_pos2.bin_number = 256
DiffLumi_beam1_pho2.bin_number = 256
DiffLumi_ele1_beam2.bin_number = 256
DiffLumi_ele1_ele2.bin_number = 256
DiffLumi_ele1_pos2.bin_number = 256
DiffLumi_ele1_pho2.bin_number = 256
DiffLumi_pos1_beam2.bin_number = 256
DiffLumi_pos1_ele2.bin_number = 256
DiffLumi_pos1_pos2.bin_number = 256
DiffLumi_pos1_pho2.bin_number = 256
DiffLumi_pho1_beam2.bin_number = 256
DiffLumi_pho1_ele2.bin_number = 256
DiffLumi_pho1_pos2.bin_number = 256
DiffLumi_pho1_pho2.bin_number = 256
DiffLumi_beam1_beam2.intervals = nt
DiffLumi_beam1_ele2.intervals = nt
DiffLumi_beam1_pos2.intervals = nt
DiffLumi_beam1_pho2.intervals = nt
DiffLumi_ele1_beam2.intervals = nt
DiffLumi_ele1_ele2.intervals = nt
DiffLumi_ele1_pos2.intervals = nt
DiffLumi_ele1_pho2.intervals = nt
DiffLumi_pos1_beam2.intervals = nt
DiffLumi_pos1_ele2.intervals = nt
DiffLumi_pos1_pos2.intervals = nt
DiffLumi_pos1_pho2.intervals = nt
DiffLumi_pho1_beam2.intervals = nt
DiffLumi_pho1_ele2.intervals = nt
DiffLumi_pho1_pos2.intervals = nt
DiffLumi_pho1_pho2.intervals = nt
On your local machine, you can run it with:
mpirun -np <n_ranks> ./<warpx_executable> <input_file>
The simulation takes about ~500 seconds on two cores on an ordinary not-too-old laptop.
Guinea-Pig
Build
First, install FFTW3. Then follow the instructions here.
Run
You can download or copy the input file below
See Guinea-Pig inputs
$ACCELERATOR:: C3_250_PS1
{
energy=125.0;
particles=0.624;
charge_sign=-1;
beta_x=12.0;
beta_y=0.12;
emitt_x=0.9;
emitt_y=0.02;
sigma_z=100.0;
scale_step=1.0;
espread=0.003;
which_espread=3;
dist_z=0;
f_rep=1;
n_b=1;
offset_y=0.;
offset_x=0;
waist_y=0;
}
$PARAMETERS:: params
{
n_z=33;
n_t=1;
n_m=100000;
cut_z=4*sigma_z.1;
n_x=256;
n_y=256;
cut_x=20*sigma_x.1;
cut_y=20*sigma_y.1;
pair_q2=1;
beam_size=1;
grids=7;
store_beam=1;
do_pairs=0;
track_pairs=0;
store_pairs=0;
do_photons=1;
store_photons=1;
do_hadrons=0;
do_jets=0;
do_coherent=1;
electron_ratio=1;
photon_ratio=1;
do_eloss=1;
do_espread=1;
rndm_seed=978360;
rndm_load=0;
rndm_save=1;
do_lumi=7;
automatic_grid_sizing=0;
bmt_precession=0;
pair_ecut=0.;
do_dump=1;
}
To run, do:
./<guineapig_executable> C3_250_PS1 params acc.out
Visualization and postprocessing
You can download this Jupyter notebbok
that shows how to extract and make a video of the main particle data.
Soon we will add the luminosities.
Jupyter Notebook
Visualizing the beams with Guinea-Pig and WarpX
import os, sys
import numpy as np
import matplotlib.pyplot as plt
from openpmd_viewer import OpenPMDTimeSeries
from scipy.constants import c, micro, nano, pi
Collider parameters
sigmaz = 100*micro
sigmax = 210*nano
sigmay = 3.1*nano
npart = 6.24e9
n0 = npart / (sigmax * sigmay * sigmaz * (2.*pi)**(3./2.))
Simulation parameters
# time
n_iterations = 256
iterations = range(n_iterations)
# box
nx = 512
ny = 512
nz = 512
Lx = 20*sigmax
Ly = 20*sigmay
Lz = 16*sigmaz
gridx = np.linspace(-0.5*Lx, 0.5*Lx, nx+1)
gridy = np.linspace(-0.5*Ly, 0.5*Ly, ny+1)
gridz = np.linspace(-0.5*Lz, 0.5*Lz, nz+1)
dx = gridx[1]-gridx[0]
dy = gridy[1]-gridy[0]
dz = gridz[1]-gridz[0]
# particles
nmacropart = 1e5
w0 = npart / nmacropart # weight
Function to plot a single Guine-Pig step
def one_step_gp(n, gp_dir):
'''
inputs
n: current timestep
gp_dir: simulation folder
outputs
H_zx, H_zy: density of the beams integrated along y and x, resp
'''
global w0, dx, dy, dz, gridx, gridy, gridz
# get beams' data, columns are:
# Particle Energy [GeV] | x [um] | y [um] | z [um] | x' [urad] | y' [urad]
data1 = np.loadtxt(os.path.join(gp_dir, 'b1.%d' % n))
data2 = np.loadtxt(os.path.join(gp_dir, 'b2.%d' % n))
# get the number of macroparticles in the beams
N1 = np.shape(data1)[0]
N2 = np.shape(data2)[0]
# stack the data together and convert to SI
x_data = np.hstack((data1[:,1], data2[:,1]))*micro
y_data = np.hstack((data1[:,2], data2[:,2]))*micro
z_data = np.hstack((data1[:,3], data2[:,3]))*micro
weights = np.ones(N1+N2) * w0
weights[-N2:] = - w0 # assign negative weights to the second beam
H_zx, bx, bz = np.histogram2d(x_data, z_data, bins=(gridx, gridz), weights=weights)
H_zy, by, bz = np.histogram2d(y_data, z_data, bins=(gridy, gridz), weights=weights)
print(np.max(np.abs(H_zx)), np.max(np.abs(H_zy)))
return H_zx/(dz*dx), H_zy/(dz*dy)
Function to plot a single WarpX step
def one_step_wx(n, wx_series):
'''
inputs
n: current timestep
wx_dir: simulation folder
outputs
H_zx: density of the beams projected on the plane (z,x), integrated along y
H_zy: density of the beams projected on the plane (z,y), integrated along x
'''
global dx, dy, dz, gridx, gridy, gridz
x1,y1,z1,w1 = wx_series.get_particle(['x','y','z','w'], species='beam1', iteration=n)
x2,y2,z2,w2 = wx_series.get_particle(['x','y','z','w'], species='beam2', iteration=n)
w1 = -w1
X = np.hstack((x1,x2))
Y = np.hstack((y1,y2))
Z = np.hstack((z1,z2))
W = np.hstack((w1,w2))
H_zx, bx, bz = np.histogram2d(X, Z, bins=(gridx, gridz), weights=W)
H_zy, by, bz = np.histogram2d(Y, Z, bins=(gridy, gridz), weights=W)
print(np.max(np.abs(H_zx)), np.max(np.abs(H_zy)))
return H_zx/(dz*dx), H_zy/(dz*dy)
Generate a video of the colliding beams
!mkdir -p "plots"
gp_dir = "gp"
wx_dir = "wx"
path=os.path.join(wx_dir, 'diags/trajs')
series = OpenPMDTimeSeries(path)
v0 = n0 * np.sqrt(sigmax * sigmay) * 0.05
extent_zx = [gridz[0]/micro, gridz[-1]/micro, gridx[0]/nano, gridx[-1]/nano]
extent_zy = [gridz[0]/micro, gridz[-1]/micro, gridy[0]/nano, gridy[-1]/nano]
plt.rcParams.update({'font.size': 16})
# loop through the timesteps
for n in iterations:
# prepare canvas
fig, ax = plt.subplots(ncols=2, nrows=2, figsize=(16,12), dpi=300, sharex='col', sharey='row')
H_zx, H_zy = one_step_gp(n, gp_dir)
im=ax[0][0].imshow(H_zx, extent=extent_zx, cmap='seismic', origin='lower', interpolation='nearest', vmin=-v0, vmax=v0)
im=ax[1][0].imshow(H_zy, extent=extent_zy, cmap='seismic', origin='lower', interpolation='nearest', vmin=-v0, vmax=v0)
ax[0][0].set_title("Guinea-Pig")
H_zx, H_zy = one_step_wx(n, series)
im=ax[0][1].imshow(H_zx, extent=extent_zx, cmap='seismic', origin='lower', interpolation='nearest', vmin=-v0, vmax=v0)
im=ax[1][1].imshow(H_zy, extent=extent_zy, cmap='seismic', origin='lower', interpolation='nearest', vmin=-v0, vmax=v0)
ax[0][1].set_title("WarpX")
fig.subplots_adjust(right=0.85)
cbar_ax = fig.add_axes([0.88, 0.085, 0.015, 0.87])
fig.colorbar(im, cax=cbar_ax, label='density [arb. units]')
for a in ax.reshape(-1):
a.set_aspect('auto')
ax[1][0].set_xlabel(r'z [$\mu$m]')
ax[1][1].set_xlabel(r'z [$\mu$m]')
ax[0][0].set_ylabel(r'x [nm]')
ax[1][0].set_ylabel(r'y [nm]')
plt.savefig(f"plots/img_{n:04d}",dpi=300, bbox_inches='tight')
plt.close("all")
! ffmpeg -framerate 30 -i 'plots/img_%04d.png' -y out.mp4
from IPython.display import Video, display
display(Video("out.mp4", embed=True))