##########################################################################
# #
# Copyright (C) 2015-2017 Carsten Fortmann-Grote #
# Contact: Carsten Fortmann-Grote <carsten.grote@xfel.eu> #
# #
# This file is part of simex_platform. #
# simex_platform is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# simex_platform is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
##########################################################################
from argparse import ArgumentParser
import h5py
import math
import numpy
from scipy import constants
# Get some constants.
c = constants.speed_of_light
eps0 = constants.epsilon_0
e = constants.e
from SimEx.Utilities import OpenPMDTools as opmd
[docs]def convertToOPMD(input_file):
""" Take native wpg output and rewrite in openPMD conformant way.
@param input_file: The hdf5 file to be converted.
@type: string
@example: input_file = "prop_out.h5"
"""
# Check input file.
if not h5py.is_hdf5(input_file):
raise IOError("Not a valid hdf5 file: %s. " % (input_file))
# Open in and out files.
with h5py.File( input_file, 'r') as h5:
with h5py.File(input_file.replace(".h5", ".opmd.h5"), 'w') as opmd_h5:
# Get number of time slices in wpg output, assuming horizontal and vertical polarizations have same dimensions, which is always true for wpg output.
data_shape = h5['data/arrEhor'].value.shape
# Branch off if this is a non-time dependent calculation in frequency domain.
if data_shape[2] == 1 and h5['params/wDomain'].value == "frequency":
# Time independent calculation in frequency domain.
_convert_from_frequency_representation(h5, opmd_h5, data_shape)
return
number_of_x_meshpoints = data_shape[0]
number_of_y_meshpoints = data_shape[1]
number_of_time_steps = data_shape[2]
time_max = h5['params/Mesh/sliceMax'].value #s
time_min = h5['params/Mesh/sliceMin'].value #s
time_step = abs(time_max - time_min) / number_of_time_steps #s
photon_energy = h5['params/photonEnergy'].value # eV
photon_energy = photon_energy * e # Convert to J
# Copy misc and params from original wpg output.
opmd_h5.create_group('history/parent')
try:
h5.copy('/params', opmd_h5['history/parent'])
h5.copy('/misc', opmd_h5['history/parent'])
h5.copy('/history', opmd_h5['history/parent'])
# Some keys may not exist, e.g. if the input file comes from a non-simex wpg run.
except KeyError:
pass
except:
raise
sum_x = 0.0
sum_y = 0.0
for it in range(number_of_time_steps):
# Write opmd
# Setup the root attributes for iteration 0
opmd.setup_root_attr( opmd_h5 )
full_meshes_path = opmd.get_basePath(opmd_h5, it) + opmd_h5.attrs["meshesPath"]
# Setup basepath.
time=time_min+it*time_step
opmd.setup_base_path( opmd_h5, iteration=it, time=time, time_step=time_step)
opmd_h5.create_group(full_meshes_path)
meshes = opmd_h5[full_meshes_path]
# Path to the E field, within the h5 file.
full_e_path_name = b"E"
meshes.create_group(full_e_path_name)
E = meshes[full_e_path_name]
# Create the dataset (2d cartesian grid)
E.create_dataset(b"x", (number_of_x_meshpoints, number_of_y_meshpoints), dtype=numpy.complex64, compression='gzip')
E.create_dataset(b"y", (number_of_x_meshpoints, number_of_y_meshpoints), dtype=numpy.complex64, compression='gzip')
# Write the common metadata for the group
E.attrs["geometry"] = numpy.string_("cartesian")
# Get grid geometry.
nx = h5['params/Mesh/nx'].value
xMax = h5['params/Mesh/xMax'].value
xMin = h5['params/Mesh/xMin'].value
dx = (xMax - xMin) / nx
ny = h5['params/Mesh/ny'].value
yMax = h5['params/Mesh/yMax'].value
yMin = h5['params/Mesh/yMin'].value
dy = (yMax - yMin) / ny
E.attrs["gridSpacing"] = numpy.array( [dx,dy], dtype=numpy.float64)
E.attrs["gridGlobalOffset"] = numpy.array([h5['params/xCentre'].value, h5['params/yCentre'].value], dtype=numpy.float64)
E.attrs["gridUnitSI"] = numpy.float64(1.0)
E.attrs["dataOrder"] = numpy.string_("C")
E.attrs["axisLabels"] = numpy.array([b"x",b"y"])
E.attrs["unitDimension"] = \
numpy.array([1.0, 1.0, -3.0, -1.0, 0.0, 0.0, 0.0 ], dtype=numpy.float64)
# L M T I theta N J
# E is in volts per meters: V / m = kg * m / (A * s^3)
# -> L * M * T^-3 * I^-1
# Add time information
E.attrs["timeOffset"] = 0. # Time offset with respect to basePath's time
# Write attribute that is specific to each dataset:
# - Staggered position within a cell
E["x"].attrs["position"] = numpy.array([0.0, 0.5], dtype=numpy.float32)
E["y"].attrs["position"] = numpy.array([0.5, 0.0], dtype=numpy.float32)
# - Conversion factor to SI units
# WPG writes E fields in units of sqrt(W/mm^2), i.e. it writes E*sqrt(c * eps0 / 2).
# Unit analysis:
# [E] = V/m
# [eps0] = As/Vm
# [c] = m/s
# ==> [E^2 * eps0 * c] = V**2/m**2 * As/Vm * m/s = V*A/m**2 = W/m**2 = [Intensity]
# Converting to SI units by dividing by sqrt(c*eps0/2)*1e3, 1e3 for conversion from mm to m.
c = 2.998e8 # m/s
eps0 = 8.854e-12 # As/Vm
E["x"].attrs["unitSI"] = numpy.float64(1.0 / math.sqrt(0.5 * c * eps0) / 1.0e3 )
E["y"].attrs["unitSI"] = numpy.float64(1.0 / math.sqrt(0.5 * c * eps0) / 1.0e3 )
# Copy the fields.
Ex = h5['data/arrEhor'][:,:,it,0] + 1j * h5['data/arrEhor'][:,:,it,1]
Ey = h5['data/arrEver'][:,:,it,0] + 1j * h5['data/arrEver'][:,:,it,1]
E["x"][:,:] = Ex
E["y"][:,:] = Ey
# Get area element.
dA = dx*dy
### Number of photon fields.
# Path to the number of photons.
full_nph_path_name = b"Nph"
meshes.create_group(full_nph_path_name)
Nph = meshes[full_nph_path_name]
# Create the dataset (2d cartesian grid)
Nph.create_dataset(b"x", (number_of_x_meshpoints, number_of_y_meshpoints), dtype=numpy.float32, compression='gzip')
Nph.create_dataset(b"y", (number_of_x_meshpoints, number_of_y_meshpoints), dtype=numpy.float32, compression='gzip')
# Write the common metadata for the group
Nph.attrs["geometry"] = numpy.string_("cartesian")
Nph.attrs["gridSpacing"] = numpy.array( [dx,dy], dtype=numpy.float64)
Nph.attrs["gridGlobalOffset"] = numpy.array([h5['params/xCentre'].value, h5['params/yCentre'].value], dtype=numpy.float64)
Nph.attrs["gridUnitSI"] = numpy.float64(1.0)
Nph.attrs["dataOrder"] = numpy.string_("C")
Nph.attrs["axisLabels"] = numpy.array([b"x",b"y"])
Nph.attrs["unitDimension"] = \
numpy.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], dtype=numpy.float64)
# Add time information
Nph.attrs["timeOffset"] = 0. # Time offset with respect to basePath's time
# Nph - Staggered position within a cell
Nph["x"].attrs["position"] = numpy.array([0.0, 0.5], dtype=numpy.float32)
Nph["y"].attrs["position"] = numpy.array([0.5, 0.0], dtype=numpy.float32)
Nph["x"].attrs["unitSI"] = numpy.float64(1.0 )
Nph["y"].attrs["unitSI"] = numpy.float64(1.0 )
# Calculate number of photons via intensity and photon energy.
# Since fields are stored as sqrt(W/mm^2), have to convert to W/m^2 (factor 1e6 below).
number_of_photons_x = numpy.round(abs(Ex)**2 * dA * time_step *1.0e6 / photon_energy)
number_of_photons_y = numpy.round(abs(Ey)**2 * dA * time_step *1.0e6 / photon_energy)
sum_x += number_of_photons_x.sum(axis=-1).sum(axis=-1)
sum_y += number_of_photons_y.sum(axis=-1).sum(axis=-1)
Nph["x"][:,:] = number_of_photons_x
Nph["y"][:,:] = number_of_photons_y
### Phases.
# Path to phases
full_phases_path_name = b"phases"
meshes.create_group(full_phases_path_name)
phases = meshes[full_phases_path_name]
# Create the dataset (2d cartesian grid)
phases.create_dataset(b"x", (number_of_x_meshpoints, number_of_y_meshpoints), dtype=numpy.float32, compression='gzip')
phases.create_dataset(b"y", (number_of_x_meshpoints, number_of_y_meshpoints), dtype=numpy.float32, compression='gzip')
# Write the common metadata for the group
phases.attrs["geometry"] = numpy.string_("cartesian")
phases.attrs["gridSpacing"] = numpy.array( [dx,dy], dtype=numpy.float64)
phases.attrs["gridGlobalOffset"] = numpy.array([h5['params/xCentre'].value, h5['params/yCentre'].value], dtype=numpy.float64)
phases.attrs["gridUnitSI"] = numpy.float64(1.0)
phases.attrs["dataOrder"] = numpy.string_("C")
phases.attrs["axisLabels"] = numpy.array([b"x",b"y"])
phases.attrs["unitDimension"] = numpy.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], dtype=numpy.float64)
phases["x"].attrs["unitSI"] = numpy.float64(1.0 )
phases["y"].attrs["unitSI"] = numpy.float64(1.0 )
# Add time information
phases.attrs["timeOffset"] = 0. # Time offset with respect to basePath's time
# phases positions. - Staggered position within a cell
phases["x"].attrs["position"] = numpy.array([0.0, 0.5], dtype=numpy.float32)
phases["y"].attrs["position"] = numpy.array([0.5, 0.0], dtype=numpy.float32)
phases["x"][:,:] = numpy.angle(Ex)
phases["y"][:,:] = numpy.angle(Ey)
print("Found %e and %e photons for horizontal and vertical polarization, respectively." % (sum_x, sum_y))
def _convert_from_frequency_representation(h5, opmd_h5, data_shape, pulse_energy=1.0e-3, pulse_duration=23.0e-15):
""" Converter for non-time dependent wavefronts in frequency representation.
Requires knowledge of pulse energy and pulse duration to allow photon number calculation.
"""
number_of_x_meshpoints = data_shape[0]
number_of_y_meshpoints = data_shape[1]
photon_energy = h5['params/photonEnergy'].value # eV
photon_energy = photon_energy * e # Convert to J
# Copy misc and params from original wpg output.
opmd_h5.create_group('history/parent')
try:
h5.copy('/params', opmd_h5['history/parent'])
h5.copy('/misc', opmd_h5['history/parent'])
h5.copy('/history', opmd_h5['history/parent'])
# Some keys may not exist, e.g. if the input file comes from a non-simex wpg run.
except KeyError:
pass
except:
raise
sum_x = 0.0
sum_y = 0.0
# Write opmd
# Setup the root attributes.
it = 0
opmd.setup_root_attr( opmd_h5 )
full_meshes_path = opmd.get_basePath(opmd_h5, it) + opmd_h5.attrs["meshesPath"]
# Setup basepath.
time = 0.0
time_step = pulse_duration
opmd.setup_base_path( opmd_h5, iteration=it, time=time, time_step=time_step)
opmd_h5.create_group(full_meshes_path)
meshes = opmd_h5[full_meshes_path]
## Path to the E field, within the h5 file.
#full_e_path_name = b"E"
#meshes.create_group(full_e_path_name)
#E = meshes[full_e_path_name]
## Create the dataset (2d cartesian grid)
#E.create_dataset(b"x", (number_of_x_meshpoints, number_of_y_meshpoints), dtype=numpy.complex64, compression='gzip')
#E.create_dataset(b"y", (number_of_x_meshpoints, number_of_y_meshpoints), dtype=numpy.complex64, compression='gzip')
## Write the common metadata for the group
#E.attrs["geometry"] = numpy.string_("cartesian")
## Get grid geometry.
nx = h5['params/Mesh/nx'].value
xMax = h5['params/Mesh/xMax'].value
xMin = h5['params/Mesh/xMin'].value
dx = (xMax - xMin) / nx
ny = h5['params/Mesh/ny'].value
yMax = h5['params/Mesh/yMax'].value
yMin = h5['params/Mesh/yMin'].value
dy = (yMax - yMin) / ny
#E.attrs["gridSpacing"] = numpy.array( [dx,dy], dtype=numpy.float64)
#E.attrs["gridGlobalOffset"] = numpy.array([h5['params/xCentre'].value, h5['params/yCentre'].value], dtype=numpy.float64)
#E.attrs["gridUnitSI"] = numpy.float64(1.0)
#E.attrs["dataOrder"] = numpy.string_("C")
#E.attrs["axisLabels"] = numpy.array([b"x",b"y"])
#E.attrs["unitDimension"] = \
#numpy.array([1.0, 1.0, -3.0, -1.0, 0.0, 0.0, 0.0 ], dtype=numpy.float64)
## L M T I theta N J
## E is in volts per meters: V / m = kg * m / (A * s^3)
## -> L * M * T^-3 * I^-1
## Add time information
#E.attrs["timeOffset"] = 0. # Time offset with respect to basePath's time
## Write attribute that is specific to each dataset:
## - Staggered position within a cell
#E["x"].attrs["position"] = numpy.array([0.0, 0.5], dtype=numpy.float32)
#E["y"].attrs["position"] = numpy.array([0.5, 0.0], dtype=numpy.float32)
## - Conversion factor to SI units
## WPG writes E fields in units of sqrt(W/mm^2), i.e. it writes E*sqrt(c * eps0 / 2).
## Unit analysis:
## [E] = V/m
## [eps0] = As/Vm
## [c] = m/s
## ==> [E^2 * eps0 * c] = V**2/m**2 * As/Vm * m/s = V*A/m**2 = W/m**2 = [Intensity]
## Converting to SI units by dividing by sqrt(c*eps0/2)*1e3, 1e3 for conversion from mm to m.
#c = 2.998e8 # m/s
#eps0 = 8.854e-12 # As/Vm
#E["x"].attrs["unitSI"] = numpy.float64(1.0 / math.sqrt(0.5 * c * eps0) / 1.0e3 )
#E["y"].attrs["unitSI"] = numpy.float64(1.0 / math.sqrt(0.5 * c * eps0) / 1.0e3 )
# Get E fields.
Ex = h5['data/arrEhor'][:,:,it,0] + 1j * h5['data/arrEhor'][:,:,it,1]
Ey = h5['data/arrEver'][:,:,it,0] + 1j * h5['data/arrEver'][:,:,it,1]
#E["x"][:,:] = Ex
#E["y"][:,:] = Ey
### Number of photon fields.
# Path to the number of photons.
full_nph_path_name = b"Nph"
meshes.create_group(full_nph_path_name)
Nph = meshes[full_nph_path_name]
# Create the dataset (2d cartesian grid)
Nph.create_dataset(b"x", (number_of_x_meshpoints, number_of_y_meshpoints), dtype=numpy.float32, compression='gzip')
Nph.create_dataset(b"y", (number_of_x_meshpoints, number_of_y_meshpoints), dtype=numpy.float32, compression='gzip')
# Write the common metadata for the group
Nph.attrs["geometry"] = numpy.string_("cartesian")
Nph.attrs["gridSpacing"] = numpy.array( [dx,dy], dtype=numpy.float64)
Nph.attrs["gridGlobalOffset"] = numpy.array([h5['params/xCentre'].value, h5['params/yCentre'].value], dtype=numpy.float64)
Nph.attrs["gridUnitSI"] = numpy.float64(1.0)
Nph.attrs["dataOrder"] = numpy.string_("C")
Nph.attrs["axisLabels"] = numpy.array([b"x",b"y"])
Nph.attrs["unitDimension"] = \
numpy.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], dtype=numpy.float64)
# Add time information
Nph.attrs["timeOffset"] = 0. # Time offset with respect to basePath's time
# Nph - Staggered position within a cell
Nph["x"].attrs["position"] = numpy.array([0.0, 0.5], dtype=numpy.float32)
Nph["y"].attrs["position"] = numpy.array([0.5, 0.0], dtype=numpy.float32)
Nph["x"].attrs["unitSI"] = numpy.float64(1.0 )
Nph["y"].attrs["unitSI"] = numpy.float64(1.0 )
# Calculate number of photons via intensity and photon energy.
# Since fields are stored as sqrt(W/mm^2), have to convert to W/m^2 (factor 1e6 below).
number_of_photons_x = numpy.round(abs(Ex)**2)
number_of_photons_y = numpy.round(abs(Ey)**2)
sum_x = number_of_photons_x.sum(axis=-1).sum(axis=-1)
sum_y = number_of_photons_y.sum(axis=-1).sum(axis=-1)
# Conversion from Nph/s/0.1%bandwidth/mm to Nph. Sum * photon_energy must be equal to pulse energy.
# Normalization factor.
c_factor = pulse_energy / photon_energy
# Normalize
number_of_photons_x *= c_factor
number_of_photons_y *= c_factor
# Normalize to sum over all pixels (if != 0 ).
if sum_x != 0.0:
number_of_photons_x /= sum_x
if sum_y != 0.0:
number_of_photons_y /= sum_y
# Write to h5 dataset.
Nph["x"][:,:] = number_of_photons_x
Nph["y"][:,:] = number_of_photons_y
### Phases.
# Path to phases
full_phases_path_name = b"phases"
meshes.create_group(full_phases_path_name)
phases = meshes[full_phases_path_name]
# Create the dataset (2d cartesian grid)
phases.create_dataset(b"x", (number_of_x_meshpoints, number_of_y_meshpoints), dtype=numpy.float32, compression='gzip')
phases.create_dataset(b"y", (number_of_x_meshpoints, number_of_y_meshpoints), dtype=numpy.float32, compression='gzip')
# Write the common metadata for the group
phases.attrs["geometry"] = numpy.string_("cartesian")
phases.attrs["gridSpacing"] = numpy.array( [dx,dy], dtype=numpy.float64)
phases.attrs["gridGlobalOffset"] = numpy.array([h5['params/xCentre'].value, h5['params/yCentre'].value], dtype=numpy.float64)
phases.attrs["gridUnitSI"] = numpy.float64(1.0)
phases.attrs["dataOrder"] = numpy.string_("C")
phases.attrs["axisLabels"] = numpy.array([b"x",b"y"])
phases.attrs["unitDimension"] = numpy.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], dtype=numpy.float64)
phases["x"].attrs["unitSI"] = numpy.float64(1.0 )
phases["y"].attrs["unitSI"] = numpy.float64(1.0 )
# Add time information
phases.attrs["timeOffset"] = 0. # Time offset with respect to basePath's time
# phases positions. - Staggered position within a cell
phases["x"].attrs["position"] = numpy.array([0.0, 0.5], dtype=numpy.float32)
phases["y"].attrs["position"] = numpy.array([0.5, 0.0], dtype=numpy.float32)
phases["x"][:,:] = numpy.angle(Ex)
phases["y"][:,:] = numpy.angle(Ey)
print("Found %e and %e photons for horizontal and vertical polarization, respectively." % (sum_x, sum_y))
opmd_h5.close()
h5.close()
if __name__ == "__main__":
# Parse arguments.
parser = ArgumentParser(description="Convert wpg output to openPMD conform hdf5.")
parser.add_argument("input_file", metavar="input_file",
help="name of the file to convert.")
args = parser.parse_args()
# Call the converter routine.
convertToOPMD(args.input_file)