Source code for hexrd.instrument.physics_package

from abc import abstractmethod
from dataclasses import dataclass, fields
from functools import partial

import numpy as np
from hexrd.material.utils import calculate_linear_absorption_length


# Below are the possible layers
[docs]@dataclass class PhysicsPackageLayer: name: str = '' material: str = '' density: float = 0 thickness: float = 0 formula: str | None = None # chemical formula
[docs]@dataclass class PinholeLayer(PhysicsPackageLayer): diameter: float = 0
[docs]@dataclass class HEDMSampleLayer(PhysicsPackageLayer): geometry: str = ''
[docs]class AbstractPhysicsPackage: """abstract class for the physics package. there will be two separate physics package class types -- one for HED samples and the other for HEDM samples. Parameters ---------- The parameters are set up so that layer attributes can be accessed (via both setters and getters) with `<layer_name>_<attribute>`. For example, for sample thickness, you may specify `sample_thickness`. Notes ----- [1] Rygg et al., X-ray diffraction at the National Ignition Facility, Rev. Sci. Instrum. 91, 043902 (2020) [2] M. Stoeckl, A. A. Solodov Readout models for BaFBr0.85I0.15:Eu image plates Rev. Sci. Instrum. 89, 063101 (2018 """ # Abstract methods that must be redefined in derived classes @property @abstractmethod def type(self): pass # If you want to add more layers, you just need to: # 1. Add the layer name to this list # 2. If the layer requires a special class (like `PinholeLayer`), # specify that in `SPECIAL_LAYERS` LAYER_TYPES = [] SPECIAL_LAYERS = {} def __init__(self, **kwargs): # The physics packages are set up so that you can access layer # attributes via `<layer>_<attr>`. For example, for the sample # thickness, you can do `self.sample_thickness`. self._setup_layers() for key, value in kwargs.items(): setattr(self, key, value) def _setup_layers(self): super().__setattr__('_layers', {}) for name in self.LAYER_TYPES: self._layers[name] = self.make_layer(name) def __getattr__(self, key): if not key.startswith('_') and key.count('_') == 1: name, attr = key.split('_') if name in self._layers: return getattr(self._layers[name], attr) return super().__getattr__(key) def __setattr__(self, key, value): if key.count('_') == 1: name, attr = key.split('_') if name in self._layers: setattr(self._layers[name], attr, value) # Default behavior is standard super().__setattr__(key, value) @property def attributes_to_serialize(self): result = [] for name, layer in self._layers.items(): result += [f'{name}_{x.name}' for x in fields(layer)] return result
[docs] def make_layer(self, name: str, **kwargs) -> PhysicsPackageLayer: cls = self.SPECIAL_LAYERS.get(name, PhysicsPackageLayer) return cls(name, **kwargs)
[docs] def serialize(self): return {a: getattr(self, a) for a in self.attributes_to_serialize}
[docs] def deserialize(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value)
[docs]class HEDPhysicsPackage(AbstractPhysicsPackage): # If you want to add more layers, you just need to: # 1. Add the layer name to this list # 2. If the layer requires a special class (like `PinholeLayer`), # specify that in `SPECIAL_LAYERS` # These layer types should be in order, for proper computation of # layer standoff. LAYER_TYPES = [ 'ablator', 'heatshield', 'pusher', 'sample', 'reflective', 'window', 'pinhole', ] SPECIAL_LAYERS = { 'pinhole': PinholeLayer, } @property def type(self): return 'HED' @property def pinhole_radius(self): return 0.5 * self.pinhole_diameter @pinhole_radius.setter def pinhole_radius(self, value): self.pinhole_diameter = 2.0 * value
[docs] def absorption_length(self, energy, layer): if isinstance(energy, float): energy_inp = np.array([energy]) elif isinstance(energy, list): energy_inp = np.array(energy) elif isinstance(energy, np.ndarray): energy_inp = energy density = getattr(self, f'{layer}_density') formula = getattr(self, f'{layer}_formula') if not formula: # Assume the material name is the formula formula = getattr(self, f'{layer}_material') layer = layer.lower() args = ( density, formula, energy_inp ) abs_length = calculate_linear_absorption_length(*args) if abs_length.shape[0] == 1: return abs_length[0] else: return abs_length
[docs] def layer_standoff(self, layer: str) -> float: # Compute layer standoff from the pinhole idx = self.LAYER_TYPES.index(layer) result = 0. for i in range(idx + 1, len(self.LAYER_TYPES) - 1): name = self.LAYER_TYPES[i] result += self._layers[name].thickness return result
[docs] def layer_thickness(self, layer: str) -> float: return self._layers[layer].thickness
def __getattr__(self, key: str): if key.endswith('_absorption_length'): # Make a function to get the absorption length of the layer # For example, you can get the sample absorption length like # this: `package.sample_absorption_length(energy)` name = key.split('_', 1)[0] f = partial(self.absorption_length, layer=name) return f return super().__getattr__(key)
[docs]class HEDMPhysicsPackage(AbstractPhysicsPackage): # If you want to add more layers, you just need to: # 1. Add the layer name to this list # 2. If the layer requires a special class (like `PinholeLayer`), # specify that in `SPECIAL_LAYERS` LAYER_TYPES = [ 'sample', ] SPECIAL_LAYERS = { 'sample': HEDMSampleLayer, } @property def type(self): return 'HEDM' @property def sample_diameter(self): if self.sample_geometry == 'cylinder': return self.sample_thickness raise Exception( 'sample geometry does not have diameter associated with it.' )