""":module WavePropagator: Module that holds the WavePropagator class. """
##########################################################################
# #
# Copyright (C) 2015-2018 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/>. #
# #
##########################################################################
import os
import subprocess
import shlex
from SimEx.Calculators.AbstractPhotonPropagator import AbstractPhotonPropagator
from SimEx.Parameters.WavePropagatorParameters import WavePropagatorParameters
from SimEx.Utilities import EntityChecks
from SimEx.Utilities import IOUtilities
from SimEx.Utilities import ParallelUtilities
from SimEx.Utilities import wpg_to_opmd
from prop import propagate_s2e
[docs]class WavePropagator(AbstractPhotonPropagator):
"""
:class WavePropagator: Represents coherent wavefront propagation using the WPG wrapper for SWR.
"""
def __init__(self, parameters=None, input_path=None, output_path=None):
"""
:param parameters: Parameters for the photon propagation.
:type parameters: WavePropagatorParameters instance.
:param input_path: Location of input data for photon propagation.
:type input_path: str, default 'FELsource/'
:param output_path: Location of propagation output data.
:type output_path: str, default 'prop/'
"""
# Handle default parameters if None.
parameters = EntityChecks.checkAndSetInstance(
WavePropagatorParameters, parameters, WavePropagatorParameters())
# Initialize base class.
super(WavePropagator, self).__init__(parameters, input_path,
output_path)
[docs] def computeNTasks(self):
resources = ParallelUtilities.getParallelResourceInfo()
nnodes = resources['NNodes']
ncores = resources['NCores']
cpusPerTask = self.parameters.cpus_per_task
if cpusPerTask == "MAX":
np = nnodes
ncores = 0
else:
np = max(1, int(ncores / int(cpusPerTask)))
ncores = int(cpusPerTask)
return (np, ncores)
[docs] def backengine(self):
""" Starts WPG simulations in parallel in a subprocess """
fname = IOUtilities.getTmpFileName()
self.dumpToFile(fname)
forcedMPIcommand = self.parameters.forced_mpi_command
if forcedMPIcommand == "":
(np, ncores) = self.computeNTasks()
mpicommand = ParallelUtilities.prepareMPICommandArguments(
np, ncores)
else:
mpicommand = forcedMPIcommand
if 'SIMEX_VERBOSE' in os.environ:
if 'MPI' in os.environ['SIMEX_VERBOSE']:
print(("WavePropagator backengine mpicommand: " + mpicommand))
if 'PYTHON' in os.environ['SIMEX_VERBOSE']:
import platform
print("Running python %s." % (platform.python_version()))
mpicommand += " python " + __file__ + " " + fname
args = shlex.split(mpicommand)
proc = subprocess.Popen(args, universal_newlines=True)
proc.wait()
os.remove(fname)
return proc.returncode
def _run(self):
""" This method drives the WPG backengine.
:return: 0 if WPG returns successfully, 1 if not.
"""
# import should be here, not in header as it calls MPI_Init when
# imported. We want MPI to be initialized at this stage only.
from mpi4py import MPI
# MPI info
comm = MPI.COMM_WORLD
thisProcess = comm.rank
numProcesses = comm.size
# Check if input path is a directory.
if os.path.isdir(self.input_path):
input_files = [os.path.join(self.input_path, input_file) for \
input_file in os.listdir(self.input_path)]
input_files.sort(
) # Assuming the filenames have some kind of ordering scheme.
else:
if thisProcess == 0: # other MPI processes (if any) have nothing to do
propagate_s2e.propagate(self.input_path, self.output_path,
self.parameters.beamline.get_beamline)
return 0
# If we have more than one input file, we should also have more than
# one output file, i.e. output_path should be a directory.
if os.path.isfile(self.output_path):
raise IOError(
"The given output path is a file but a directory is needed. Cowardly refusing to overwrite."
)
# Check if output dir exists, create if not.
if not os.path.isdir(self.output_path):
os.mkdir(self.output_path)
# Loop over all input files and generate one run per source file.
for i, input_file in enumerate(input_files):
# TODO: Transmit number of cpus.
# process file on a corresponding process (round-robin)
if i % numProcesses == thisProcess:
output_file = os.path.join(self.output_path,
'prop_out_%07d.h5' % (i))
propagate_s2e.propagate(input_file, output_file,
self.parameters.beamline.get_beamline)
# Rewrite in openpmd conformant way.
# wpg_to_opmd.convertToOPMD( output_file )
return 0
@property
def data(self):
""" Query for the field data. """
return self.__data
def _readH5(self):
""" """
""" Private method for reading the hdf5 input and extracting the parameters and data relevant to initialize the object. """
pass # Nothing to be done since IO happens in backengine.
[docs] def saveH5(self):
""" """
"""
:param output_path: Path to propagation output.
:type output_path: string
"""
pass # No action required since output is written in backengine.
if __name__ == '__main__':
WavePropagator.runFromCLI()