# -*- coding: utf-8 -*-
################################ Begin license #################################
# Copyright (C) Laboratory of Imaging technologies,
# Faculty of Electrical Engineering,
# University of Ljubljana.
#
# This file is part of PyXOpto.
#
# PyXOpto 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.
#
# PyXOpto 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 PyXOpto. If not, see <https://www.gnu.org/licenses/>.
################################# End license ##################################
from typing import List, Dict
import numpy as np
from xopto.mcvox import cltypes
from xopto.mcvox import mcobject
from xopto.mcvox import mctypes
from xopto.mcvox import mcoptions
NONE = 'none'
TOP = 'top'
BOTTOM = 'bottom'
SPECULAR = 'specular'
[docs]class DetectorBase(mcobject.McObject):
'''
Base class of all detectors.
'''
def __init__(self, location: NONE or TOP or BOTTOM or SPECULAR):
'''
Detector at the sample surface.
Parameters
----------
location: TOP or BOTTOM or SPECULAR
Location of the detector.
'''
super().__init__()
if location not in (NONE, TOP, BOTTOM, SPECULAR):
raise ValueError(
'Detector location must be "{}", "{}", "{}" or "{}"!'.format(
NONE, TOP, BOTTOM, SPECULAR)
)
self._location = location
def _get_location(self) -> str:
return self._location
def _set_location(self, location: TOP or BOTTOM or SPECULAR):
if location not in (TOP, BOTTOM, SPECULAR):
raise ValueError(
'Surface layout location must be "{}", "{}", "{}"!'.format(
TOP, BOTTOM, SPECULAR)
)
if location != self._location and self._location != NONE:
raise RuntimeError('Detector location cannot be changed!')
self._location = str(location)
location = property(_get_location, _set_location, None,
'Location of the detector.')
[docs]class DetectorTop(DetectorBase):
'''
Base class of detectors at the top sample surface.
'''
def __init__(self, *args, **kwargs):
'''
Create a detector at the top sample surface.
'''
super().__init__(TOP)
[docs]class DetectorBottom(DetectorBase):
'''
Base class of the detectors at the bottom sample surface.
'''
def __init__(self, *args, **kwargs):
'''
Create a detector at the bottom sample surface.
'''
super().__init__(BOTTOM)
[docs]class DetectorSpecular(DetectorBase):
'''
Base class of the detectors for specular reflections.
'''
def __init__(self, *args, **kwargs):
'''
Create a detector for specular reflections.
'''
super().__init__(SPECULAR)
[docs]class DetectorAny(DetectorBase):
'''
Base class of the detectors at the top or bottom sample surface or
for specular reflections.
'''
def __init__(self, *args, **kwargs):
'''
Create a detector for any location.
'''
super().__init__(NONE)
[docs]class DetectorDefault(DetectorAny):
'''
Default / dummy detector for the top or bottom sample surface or for
specular reflections.
'''
[docs] def cl_type(self, mc: mcobject.McObject) -> cltypes.Structure:
'''
Structure that is passed to the Monte carlo simulator kernel.
Parameters
----------
mc: McObject
A Monte Carlo simulator instance.
Returns
-------
struct: cltypes.Structure
A structure type that represents the detector in
the Monte Carlo kernel.
Fields
------
dummy: mc_int_t
Dummy field of the dummy detector.
'''
T = mc.types
class ClDetectorDefault(cltypes.Structure):
_fields_ = [('dummy', T.mc_int_t)]
return ClDetectorDefault
[docs] @staticmethod
def cl_options(mc: mcobject.McObject) -> str:
return ''
[docs] def cl_declaration(self, mc: mcobject.McObject) -> str:
'''
Structure that defines the default detector in the Monte Carlo simulator.
'''
Loc = self.location.capitalize()
return 'struct Mc{}Detector{{mc_int_t dummy;}};'.format(Loc)
[docs] def cl_implementation(self, mc: mcobject.McObject) -> str:
'''
OpenCL implementation of the default detector
'''
loc = self.location
Loc = loc.capitalize()
return '\n'.join((
'void dbg_print_{}_detector(__mc_detector_mem const Mc{}Detector *detector){{'.format(loc, Loc),
' dbg_print("Mc{}Detector - default detector:");'.format(Loc),
'};',
))
[docs] def cl_pack(self, mc: mcobject.McObject, target=None) -> cltypes.Structure:
'''
Fills the structure (target) with the data required by the
Monte Carlo simulator.
See the :py:meth:`DetectorDefault.cl_type` method for a detailed list
of fields.
Parameters
----------
mc: mcobject.McObject
Monte Carlo simulator instance.
target: cltypes.Structure
Ctypes structure that is filled with the source data.
Returns
-------
target: cltypes.Structure
Filled structure received as an input argument or a new
instance if the input argument target is None.
'''
if target is None:
target_type = self.cl_type(mc)
target = target_type()
target.dummy = 0
return target
[docs] def todict(self) -> dict:
'''
Export object to dict.
'''
return {'type': self.__class__.__name__}
[docs] @classmethod
def fromdict(cls, data: dict) -> 'DetectorDefault':
'''
Create an instance of :py:class:`DetectorDefault` from a
dictionary.
Parameters
----------
data: dict
Dictionary created by the :py:meth:`DetectorDefault.todict` method.
'''
layout_type = data.pop('type')
if layout_type != cls.__name__:
raise TypeError(
'Expected a "{}" type bot got "{}"!'.format(
cls.__name__, layout_type))
return cls(**data)
[docs]class Detector(DetectorAny):
def __init__(self, raw_data: np.ndarray, nphotons: int):
'''
Detector base class.
Parameters
----------
raw_data: np.ndarray
Raw data accumulator.
nphotons: int
Number of photon packets used in the simulations.
'''
super().__init__()
self._nphotons = int(nphotons)
self._raw_data = raw_data
def _get_raw(self) -> np.ndarray:
return self._raw_data
raw = property(_get_raw, None, None,
'Raw accumulator data array.')
def _get_nphotons(self) -> int:
return self._nphotons
nphotons = property(_get_nphotons, None, None,
'The number of photon packets that produced '\
'the raw data accumulator content.')
def _get_shape(self) -> tuple:
return self._raw_data.shape
shape = property(_get_shape, None, None,
'Shape of the raw data accumulator array.')
def _get_raw_total(self) -> float:
return self._raw_data.sum()
total = property(_get_raw_total, None, None,
'Total weight of the accumulated photon packets.')
[docs] def set_raw_data(self, data: np.ndarray, nphotons: int):
self._raw_data[:] = np.asarray(data, dtype=self._raw_data.dtype)
self._nphotons = int(nphotons)
[docs] def update_data(self, mc: mcobject.McObject,
accumulators: List[np.ndarray], nphotons: int, **kwargs):
'''
Update the content of raw data accumulators with simulation results.
Parameters
----------
mc: mcobject.McObject
Simulator instance that produced the data.
accumulators: List[np.ndarray]
List of allocated accumulators (this implementation uses only one).
nphotons: int
The number of photon packets that produced the raw data
accumulator content.
kwargs: dict
Additional keyword arguments not used by this implementation.
'''
new_data = np.reshape(accumulators[0], self.shape)
if self._raw_data is not None:
self._raw_data += new_data*(1.0/mc.types.mc_accu_k)
self._nphotons += int(nphotons)
else:
self._raw_data[:] = new_data*(1.0/mc.types.mc_accu_k)
self._nphotons = int(nphotons)
[docs]class Detectors(mcobject.McObject):
[docs] def cl_type(self, mc: mcobject.McObject) -> cltypes.Structure:
'''
Structure that is passed to the Monte carlo simulator kernel.
Parameters
----------
mc: McObject
A Monte Carlo simulator instance.
Returns
-------
struct: cltypes.Structure
A structure type that represents the detectors in
the Monte Carlo kernel.
Fields
------
top: cltypes.Structure
Detector at the top sample surface.
bottom: cltypes.Structure
Detector at the top sample surface.
specular: cltypes.Structure
Detector of specular reflections.
'''
T = mc.types
class ClDetectors(cltypes.Structure):
_fields_ = [
('top', self._top.fetch_cl_type(mc)),
('bottom', self._bottom.fetch_cl_type(mc)),
('specular', self._specular.fetch_cl_type(mc))
]
return ClDetectors
[docs] def cl_options(self, mc: mcobject.McObject) -> str:
'''
Returns the OpenCL options of the detectors.
If the detectors for the top or bottom sample surface or the detector
of specular reflection are specified, this function activates
(not the dummy default :py:class:`SurfaceLayoutDefault`),
the corresponding OpenCL options that enable the corresponding surface
detector, i.e. MC_USE_TOP_DETECTOR for the detector at the top sample
surface, MC_USE_BOTTOM_DETECTOR for the detector at the bottom sample
surface and MC_USE_SPECULAR_DETECTOR for detector of specular
reflections.
'''
options = []
use_detectors = False
if type(self._top) != DetectorDefault:
options.append(('MC_USE_TOP_DETECTOR', True))
options.extend(self._top.fetch_cl_options(mc))
use_detectors = True
if type(self._bottom) != DetectorDefault:
options.append(('MC_USE_BOTTOM_DETECTOR', True))
options.extend(self._bottom.fetch_cl_options(mc))
use_detectors = True
if type(self._specular) != DetectorDefault:
options.append(('MC_USE_SPECULAR_DETECTOR', True))
options.extend(self._specular.fetch_cl_options(mc))
use_detectors = True
if use_detectors:
options.append(('MC_USE_DETECTORS', True))
return options
[docs] def cl_declaration(self, mc: mcobject.McObject) -> str:
'''
Declarations of detectors in OpenCL.
'''
return '\n'.join((
self._top.fetch_cl_declaration(mc),
'typedef struct McTopDetector McTopDetector;',
'',
self._bottom.fetch_cl_declaration(mc),
'typedef struct McBottomDetector McBottomDetector;',
'',
self._specular.fetch_cl_declaration(mc),
'typedef struct McSpecularDetector McSpecularDetector;',
'',
'struct MC_STRUCT_ATTRIBUTES McDetectors{',
' McTopDetector top;',
' McBottomDetector bottom;',
' McSpecularDetector specular;',
'};',
'',
'inline void mcsim_top_detector_deposit(',
' McSim *mcsim, mc_point3f_t const *pos, mc_point3f_t const *dir,',
' mc_fp_t weight);',
'inline void mcsim_bottom_detector_deposit(',
' McSim *mcsim, mc_point3f_t const *pos, mc_point3f_t const *dir,',
' mc_fp_t weight);',
'inline void mcsim_specular_detector_deposit(',
' McSim *mcsim, mc_point3f_t const *pos, mc_point3f_t const *dir,',
' mc_fp_t weight);',
'',
'#define mcsim_top_detector(psim) (&mcsim_detectors(psim)->top)',
'#define mcsim_bottom_detector(psim) (&mcsim_detectors(psim)->bottom)',
'#define mcsim_specular_detector(psim) (&mcsim_detectors(psim)->specular)',
))
[docs] def cl_implementation(self, mc: mcobject.McObject) -> str:
'''
Implementation of the detectors.
'''
return '\n'.join((
self._top.fetch_cl_implementation(mc),
'',
self._bottom.fetch_cl_implementation(mc),
'',
self._specular.fetch_cl_implementation(mc),
'',
'void dbg_print_detectors(__mc_detector_mem const McDetectors *detectors){',
' dbg_print("McDetectors");',
' dbg_print_top_detector(&detectors->top);',
' dbg_print_bottom_detector(&detectors->bottom);',
' dbg_print_specular_detector(&detectors->specular);',
'};',
))
def __init__(self, top: Detector or 'Detectors' = None,
bottom: Detector = None, specular: Detector = None):
'''
Create an instance of simulator detectors.
Parameters
----------
top: DetectorBase or Detectors
Detector at the top sample surface, None, or an existing Detectors
instance. If an existing Detectors instance, a new copy will
be made.
bottom: DetectorBase
Detector at the bottom sample surface or None
specular: DetectorBase
Detector of specular reflections or None.
'''
super().__init__()
if isinstance(top, Detectors):
detectors = top
top = detectors.top
top = type(top)(top)
bottom = detectors.bottom
bottom = type(bottom)(bottom)
specular = detectors.specular
specular = type(specular)(specular)
else:
if top is None:
top = DetectorDefault()
if bottom is None:
bottom = DetectorDefault()
if specular is None:
specular = DetectorDefault()
top.location = TOP
bottom.location = BOTTOM
specular.location = SPECULAR
self._top = top
self._bottom = bottom
self._specular = specular
[docs] def update_data(self, mc: mcobject.McObject, detector: Detector or str,
data: Dict[np.dtype, List[np.ndarray]],
nphotons: int = 0):
'''
Update the detector data with simulation results.
Parameters
----------
mc: mcobject.McObject
Simulator instance that produced the results.
detector: Detector or str
Detector or location to update.
data: Dict[np.dtype: List[np.ndarray]]
A dict of list of numpy data buffers downloaded from the kernel.
nphotons: int
The number of photon packets that produced the results.
'''
if isinstance(detector, Detector):
location = detector.location
else:
location = detector
if location not in (TOP, BOTTOM, SPECULAR):
raise ValueError(
'Detector location must be one of "{}", "{}" or "{}" '
'but got "{}"!'.format(
TOP, BOTTOM, SPECULAR, location
)
)
accumulators = data.get(np.dtype(mc.types.np_accu))
float_buffers = data.get(np.dtype(mc.types.np_float))
integer_buffers = data.get(np.dtype(mc.types.np_int))
getattr(self, location).update_data(
mc, accumulators=accumulators, float_buffers=float_buffers,
integer_buffers=integer_buffers, nphotons=nphotons)
def _get_top(self) -> Detector:
return self._top
top = property(_get_top, None, None,
'Detector at the top sample surface.')
def _get_bottom(self) -> Detector:
return self._bottom
bottom = property(_get_bottom, None, None,
'Detector at the bottom sample surface.')
def _get_specular(self) -> Detector:
return self._specular
specular = property(_get_specular, None, None,
'Detector of specular reflections.')
def __iter__(self):
return iter([self._top, self._bottom, self._specular])
[docs] def cl_pack(self, mc: mcobject.McObject, target: cltypes.Structure = None) \
-> cltypes.Structure:
'''
Fills the structure (target) with the data required by the
Monte Carlo simulator.
See the :py:meth:`Detectors.cl_type` method for a detailed list of
fields.
Parameters
----------
mc: mcobject.McObject
Monte Carlo simulator instance.
target: cltypes.Structure
Ctypes structure that is filled with the source data.
Returns
-------
target: cltypes.Structure
Filled structure received as an input argument or a new
instance if the input argument target is None.
'''
if target is None:
target_type = self.cl_type(mc)
target = target_type()
target.top = self._top.cl_pack(mc, target.top)
target.bottom = self._bottom.cl_pack(mc, target.bottom)
target.specular = self._specular.cl_pack(mc, target.specular)
return target
[docs] def types(self) -> tuple:
'''
Returns a tuple of detector types assigned to this instance.
'''
return type(self._top), type(self._bottom), type(self._specular)
[docs] def todict(self) -> dict:
'''
Export object to dict.
'''
return {
'type': self.__class__.__name__,
'top': self._top.todict(),
'bottom': self._bottom.todict(),
'specular': self._specular.todict()
}
def __str__(self):
return 'Detectors(top={}, bottom={}, specular={})'.format(
self._top, self._bottom, self._specular)
def __repr__(self):
return '{} #{}'.format(self.__str__(), id(self))