Source code for SimExLite.PhotonBeamData

# Copyright (C) 2021 Juncheng E
# Contact: Juncheng E <juncheng.e@xfel.eu>
# This file is part of SimEx-Lite which is released under GNU General Public License v3.
# See file LICENSE or go to <http://www.gnu.org/licenses> for full license details.
"""Photon Beam data APIs"""

import numpy as np
from numpy import ndarray
from libpyvinyl import BaseData
from . import setValue


[docs]def hcDivide(val): """h is the Plank constant and c is the speed of light. keV <-> Angstrom.""" return 12.398 / val
[docs]class BeamBase(BaseData): """The simplest description of a pulse of the beam. :param pulse_energy: The energy of the X-ray pulse in Joule. :type pulse_energy: float, optional :param photons_per_pulse: The number of photons of this pulse. :type photons_per_pulse: int, optional :param wavelength: The wavelength of this pulse in Angstrom. :type wavelength: float, optional :param photon_energy: The photon energy of this pulse, in eV. :type photon_energy: float, optional :param focus_area: The focus area of this pulse, in m**2. :type focus_area: float, optional """ def __init__(self, pulse_energy=None, wavelength=None, focus_area=None): super().__init__("special data", {}) # Default photon energy unit self._attrs = {} if wavelength is not None: self.set_wavelength(wavelength) if pulse_energy: self.set_pulse_energy(pulse_energy) if focus_area: self.set_focus_area(focus_area) def set_focus_area(self, val, unit='m**2'): focus_area = setValue(val, unit) self._attrs['focus_area'] = focus_area.to('m**2') def set_pulse_energy(self, val, unit='joule'): pulse_energy = setValue(val, unit) self._attrs['pulse_energy'] = pulse_energy.to('joule') def set_wavelength(self, val, unit='angstrom'): wavelength = setValue(val, unit) self._attrs['wavelength'] = wavelength.to('angstrom') def get_focus_area(self, unit='m**2'): return self._attrs['focus_area'].to(unit) def get_pulse_energy(self, unit='joule'): return self._attrs['pulse_energy'].to(unit) @property def pulse_energy(self): """The pulse_energy property.""" return self.get_pulse_energy().magnitude @pulse_energy.setter def pulse_energy(self, value): self.set_pulse_energy(value) def get_wavelength(self, unit='angstrom'): return self._attrs['wavelength'].to(unit) # This is to be compatile with the attribute access way. @property def wavelength(self): """The wavelength property.""" return self.get_wavelength().magnitude @wavelength.setter def wavelength(self, value): self.set_wavelength(value) def get_photons_per_pulse(self): return self._attrs['pulse_energy'].to( 'joule') / self.get_photon_energy().to('joule') def get_photon_energy(self, unit='eV'): photon_energy = setValue( hcDivide(self._attrs['wavelength'].to('angstrom').magnitude), 'keV') return photon_energy.to(unit) def get_fluence(self, unit='joule/cm**2'): fluence = self.get_pulse_energy() / self.get_focus_area() return fluence.to(unit) def get_flux(self, unit='1/um**2'): flux = self.get_photons_per_pulse() / self.get_focus_area() return flux.to(unit) @property def attrs(self): my_attrs = {} for key in self._attrs.keys(): try: my_attrs[key] = self._attrs[key].magnitude except AttributeError: my_attrs[key] = self._attrs[key] return my_attrs def showAttrs(self): # for key, value in self._attrs.items(): # try: # print('{} = {:.3~P}'.format(key, value)) # except ValueError: # print('{} = {}'.format(key, value)) print(self.__repr__) def supported_formats(self): format_dict = {} # Add the supported format classes when creating a concrete class. # See the example at `tests/BaseDataTest.py` # self._add_ioformat(format_dict, FormatClass) return format_dict def __repr__(self): """ Returns string with all the parameters """ string = "Beam parameters:\n" for key, value in self._attrs.items(): try: string += '{} = {:.3~P}\n'.format(key, value) except ValueError: string += '{} = {}\n'.format(key, value) return string
[docs]class SimpleBeam(BeamBase): """The simplest description of a pulse of the beam. :param pulse_energy: The energy of the X-ray pulse in Joule. :type pulse_energy: float, optional :param photons_per_pulse: The number of photons of this pulse. :type photons_per_pulse: int, optional :param wavelength: The wavelength(s) of this pulse in Angstrom. Monchromatic beam: a scalar wavelength. Polychromatic beam (shot-to-shot-invariant): an array of length **m** of wavelengths. Polychromatic beam (shot-to-shot-variant): an array of **np** by **m** of wavelengths. :type wavelength: float or ndarray, optional :param wavelength_weights: The relative weights of the corresponding wavelengths. Monchromatic beam: None Polychromatic beam (shot-to-shot-invariant): an array of length **m** of wavelengths. Polychromatic beam (shot-to-shot-variant): an array of **np** by **m** of wavelengths. :type wavelength_weights: ndarray, optional :param photon_energy: The photon energy(s) of this pulse, in eV. Monchromatic beam: a scalar. Polychromatic beam (shot-to-shot-invariant): an array of length **m** of photon energies. Polychromatic beam (shot-to-shot-variant): an array of **np** by **m** of photon energies. :type photon_energy: float of ndarray, optional :param photon_energy_weights: The relative weights of the corresponding photon energies. Monchromatic beam: None Polychromatic beam (shot-to-shot-invariant): an array of length **m** of photon energies. Polychromatic beam (shot-to-shot-variant): an array of **np** by **m** of photon energies. :type photon_energy_weights: ndarray, optional :param beam_size: Two-element array/list of FWHM (if Gaussian or Airy function) or diameters (if top hat) or widths (if rectangular) of the beam in the order x, y. The unit is meter. :type beam_size: array-like, optional :param profile: The beam profile. It can be one of: gaussian|airy|top-hat|rectangular. :type profile: str, optional :param focus_area: The focus area of this pulse, in m**2. It will be overridden by beam_size if both are provided. :type focus_area: float, optional """ def __init__(self, pulse_energy=None, photons_per_pulse=None, wavelength=None, wavelength_weights=None, photon_energy=None, photon_energy_weights=None, beam_size: ndarray = None, profile: str = 'gaussian', focus_area=None): super().__init__(pulse_energy, wavelength, focus_area) if wavelength is not None and photon_energy is not None: raise ValueError( 'wavelength and photon_energy can not be set at the same time.' ) if pulse_energy and photons_per_pulse: raise ValueError( 'pulse_energy and photons_per_pulse can not be set at the same time.' ) if wavelength_weights is not None and photon_energy_weights is not None: raise ValueError( 'wavelength_weights and photon_energy_weights can not be set at the same time.' ) if wavelength_weights is not None: self.set_wavelength_weights(wavelength_weights) if photon_energy is not None: self.set_photon_energy(photon_energy) if photon_energy_weights is not None: self.set_photon_energy_weights(photon_energy_weights) if photons_per_pulse: self.set_photons_per_pulse(photons_per_pulse) if profile: self.set_profile(profile) if beam_size: self.set_beam_size(beam_size) def set_wavelength_weights(self, val): self._attrs['wavelength_weights'] = setValue(val, '') def get_wavelength_weights(self): return self._attrs['wavelength_weights'] def set_photon_energy(self, val, unit='eV'): photon_energy = setValue(val, unit) # Convert to wavelength self._attrs['wavelength'] = setValue( hcDivide(photon_energy.to('keV').magnitude), 'angstrom') def set_photon_energy_weights(self, val): # Convert to wavelength self._attrs['wavelength_weights'] = setValue(val, '') def get_photon_energy_weights(self): # Convert to wavelength return self.get_wavelength_weights() def set_photons_per_pulse(self, val): pulse_energy = val * self.get_photon_energy() self._attrs['pulse_energy'] = setValue(pulse_energy, 'joule') def set_profile(self, val): if not isinstance(val, str): raise TypeError("profile should be in str type.") if val in ['gaussian', 'airy', 'top-hat', 'rectangular']: self._attrs['profile'] = val else: raise ValueError( "profile should be one of: gaussian|airy|top-hat|rectangular") def get_profile(self): return self._attrs['profile'] def set_beam_size(self, val, unit='m'): if not isinstance(val, (list, np.array)): raise TypeError( "beamsize should be a list or numpy array of [x, y]") if len(val) != 2: raise TypeError( "beamsize should be a list or numpy array of [x, y]") beam_size = setValue(val, unit) self._attrs['beam_size'] = beam_size.to('m') profile = self.get_profile() # This will override focus_area if profile in ['gaussian', 'airy', 'top-hat']: focus = beam_size[0] * beam_size[1] * np.pi / 4 elif profile == 'rectangular': focus = beam_size[0] * beam_size[1] self._attrs['focus_area'] = focus.to('m**2') def get_beam_size(self, unit='m'): return self._attrs['beam_size'].to(unit)
if __name__ == "__main__": pass