Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add calculation of EM energy #431

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions openpmd_viewer/addons/pic/lpa_diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,56 @@ def get_laser_envelope( self, t=None, iteration=None, pol=None,
# Return the result
return( envelope, info )


def get_electromagnetic_energy( self, t=None, iteration=None ):
"""
Compute the total electromagnetic energy inside the box, i.e.

.. math::

\\mathcal{E}_{tot} = \\int dV\,\\left[ \\frac{\\epsilon_0}{2}\\boldsymbol{E}^2 + \\frac{1}{2\mu_0}\\boldsymbol{B}^2 \\right]

For simulations of high-intensity lasers propagating in underdense plasmas,
this is approximately equal to the laser energy (since the plasma fields are usually low)

Parameters:
-----------
t : float (in seconds), optional
Time at which to obtain the data (if this does not correspond to
an existing iteration, the closest existing iteration will be used)
Either `t` or `iteration` should be given by the user.

iteration : int
The iteration at which to obtain the data
Either `t` or `iteration` should be given by the user.

Returns:
--------
A float with the total electromagnetic energy inside the box (in Joules)
"""
# Check that the required data is available
if 'E' not in self.avail_fields or 'B' not in self.avail_fields:
raise ValueError('The fields E and B are required to compute the electromagnetic energy.')
# Check that the fields have either the thetaMode or 3dcartesian geometry
if (self.fields_metadata['E']['geometry'] not in ['thetaMode', '3dcartesian']) or \
(self.fields_metadata['B']['geometry'] not in ['thetaMode', '3dcartesian']):
raise ValueError('The electromagnetic energy can only be computed for 3D Cartesian and cylindrical simulations.')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this should work for 2D and 1D, though depending on how info.dy and info.dz are defined.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is a bit tricky. info.dy does not exist in 2D and 1D in openPMD-viewer.
Technically, we could multiply only by info.dx (instead of info.dx and info.dy) and get a result in J/m (instead of J), but I wanted to avoid this, so that the user does not get confused.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For comparison, this is what is done in the WarpX reduced field energy diagnostic. The volume is calculated with the assumption that the extra dimensions have a length of 1. This would be easy to implement here so might as well for completeness so you wouldn't have to come back and submit another change if this is requested in the future.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, sounds good. Will do.


# For RZ: use `theta=None` to get the full 3D field
Ex, info = self.get_field('E', 'x', t=t, iteration=iteration, theta=None )
Ey, info = self.get_field('E', 'y', t=t, iteration=iteration, theta=None )
Ez, info = self.get_field('E', 'z', t=t, iteration=iteration, theta=None )
Bx, info = self.get_field('B', 'x', t=t, iteration=iteration, theta=None )
By, info = self.get_field('B', 'y', t=t, iteration=iteration, theta=None )
Bz, info = self.get_field('B', 'z', t=t, iteration=iteration, theta=None )

# Compute the energy
energy_density = const.epsilon_0/2.*(Ex**2 + Ey**2 + Ez**2) + 1./(2*const.mu_0)*(Bx**2 + By**2 + Bz**2)
volume = info.dx*info.dy*info.dz # Cell volume
E = energy_density.sum() * volume
return E


def get_main_frequency( self, t=None, iteration=None, pol=None, m='all',
method='max'):
"""
Expand Down
57 changes: 57 additions & 0 deletions tests/test_laser_energy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""
This test file is part of the openPMD-viewer.

It checks that the function `get_electromagnetic_energy` works correctly

Usage:
This file is meant to be run from the root directory of openPMD-viewer,
by any of the following commands
$ python tests/test_laser_energy.py
$ py.test
$ python -m pytest tests

Copyright 2024, openPMD-viewer contributors
Authors: Remi Lehe
License: 3-Clause-BSD-LBNL
"""
from openpmd_viewer.addons import LpaDiagnostics
from scipy.constants import c
import numpy as np

# Download required datasets
import os
def download_if_absent( dataset_name ):
"Function that downloads and decompress a chosen dataset"
if os.path.exists( dataset_name ) is False:
import wget, tarfile
tar_name = "%s.tar.gz" %dataset_name
url = "https://github.com/openPMD/openPMD-example-datasets/raw/draft/%s" %tar_name
wget.download(url, tar_name)
with tarfile.open( tar_name ) as tar_file:
tar_file.extractall()
os.remove( tar_name )
download_if_absent( 'example-thetaMode' )

def test_laser_energy():
"""
Check that the function `get_electromagnetic_energy` gives the same result
as the engineering formula for a Gaussian pulse

E = 2.7e-5*(a0*w0/lambd)**2*(tau[fs])
"""
ts = LpaDiagnostics('./example-thetaMode/hdf5')
iteration = 300

# Evaluate the laser energy using the engineering formula
a0 = ts.get_a0(iteration=iteration, pol='y')
w0 = ts.get_laser_waist(iteration=iteration, pol='y')
tau_fs = ts.get_ctau(iteration=iteration, pol='y') / c * 1e15
omega = ts.get_main_frequency(iteration=iteration, pol='y')
lambd = 2*np.pi*c/omega
E_eng = 2.7e-5*(a0*w0/lambd)**2*tau_fs

# Evaluate the laser energy using the function
E_func = ts.get_electromagnetic_energy(iteration=iteration)

# Compare the two results
assert np.isclose(E_eng, E_func, rtol=0.04)
Loading