Source code for xopto.mcbase.mcoptions

# -*- 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 Tuple, List

from .mcobject import McObject, RawOption, RawOptions

[docs]def make_defines(options: List[RawOption], indent: str=None) -> str: ''' Create defines for the given list of options. These defines should be included at the beginning of the OpenCL source file. Parameters ---------- options: List[RawOption] List of options defined as [(option_name, option_value), ...]. indent: str Indent to use when creating define directives. Returns ------- defs: str Options as defines, one per line. ''' if indent is None: indent = '' d = [] names = [] if options: for name, value in options: d.append(McOption.make_define(name, value)) return '\n'.join(d)
[docs]def resolve_cl_options(*args: Tuple[RawOption] or List[RawOption]) \ -> List[RawOption]: ''' Resolve collections of OpenCL options passed as input arguments. Check for duplicate options with different values and raise ValueError if the same option is defined with different values. Parameters ---------- args: Tuple[RawOption] Each positional argument must be a list of options defined as [(name, value), ...]. Returns ------- options: List[RawOption] List of resolved options defined as [(name, value), ...]. ''' options = [] for item in args: options.extend(item) options = list(set(options)) names = [] for name, value in options: if name in names: value_ = options[names.index(name)][1] raise ValueError( 'The same option cannot be defned with different values!' 'Found multiple definitions of option {} with values ' '{} and {}!'.format(name, value_, value)) names.append(name) return options
[docs]def cl_value(value: str or bool or int or float) -> str: ''' OpenCL representation of the option value that can be set by a define. Parameters ---------- value: str or bool or int or float Python representation of the option value. Returns ------- cl_value: str Representation of the option value that can be used in a standard C/OpenCL define. ''' if isinstance(value, bool): value = {False:'FALSE', True:'TRUE'}.get(value) elif isinstance(value, int): value = '{:d}'.format(value) elif isinstance(value, float): value = '{:.16g}'.format(value) elif isinstance(value, str): pass elif value is None: value = '' else: raise TypeError('Option must be of str, bool, int or float type!') return value
[docs]class McOption(McObject): ''' Base Class of Monte Carlo kernel options that are set up through defines. '''
[docs] @staticmethod def cl_value(value: str or bool or int or float) -> str: ''' OpenCL representation of the option value that can be set by a define. Parameters ---------- value: str or bool or int or float Python representation of the option value. Returns ------- cl_value: str Representation of the option value that can be used in a standard C/OpenCL define. ''' if isinstance(value, bool): value = {False:'FALSE', True:'TRUE'}.get(value) elif isinstance(value, int): value = '{:d}'.format(value) elif isinstance(value, float): value = 'FP_LITERAL({:.16g})'.format(value) elif isinstance(value, str): pass elif value is None: value = '' else: raise TypeError('Option must be of str, bool, int or float type!') return value
[docs] @classmethod def make_define(cls, name, value) -> str: ''' Create and return a standard C/OpenCL define. Returns ------- define: str A standard C/OpenCL define representing the option. ''' if value is None: return '#define {}'.format(name) else: return '#define {} {}'.format(name, cls.cl_value(value))
def __init__(self, name:str, value: str or bool or int or float): ''' Initializes a kernel option. Parameters ---------- name: str Option name value: str, bool, int, float Option value. ''' self.cl_options = [(name, value)] def _get_name(self) -> str: return self.cl_options[0][0] name = property(_get_name, None, None, 'Option name.') def _get_value(self) -> str or bool or int or float: return self.cl_options[0][1] value = property(_get_value, None, None, 'Option value.') def __repr__(self): return "{}('{}', {})".format(self.__class__.__name__, self.cl_options[0][0], self.cl_options[0][1]) def __str__(self): return self.__repr__()
[docs]class McBoolOption(McOption): ''' Base Class of Monte Carlo kernel options that have a boolean type. ''' def __init__(self, name: str, value: bool): ''' Initializes a boolean kernel option. Parameters ---------- name: str Property name value: bool Boolean value of the kernel option. ''' super().__init__(name, bool(value))
[docs] def get_define(self): return '#define {}'.format(self.name, self.value)
[docs]class McIntOption(McOption): ''' Base Class of Monte Carlo kernel options that have an integer type. ''' def __init__(self, name: str, value: int): ''' Initializes an integer kernel option. Parameters ---------- name: str Property name value: int Integer value of the kernel option. ''' super().__init__(name, int(value))
[docs]class McFloatOption(McOption): def __init__(self, name: str, value: float): ''' Initializes a floating-point kernel option. Parameters ---------- name: str Property name value: float Floating-point value of the kernel option. ''' super().__init__(name, float(value))
[docs]class McTypeOption(McOption): ''' Base Class of Monte Carlo kernel options that have a label type (no quotes in the define). ''' def __init__(self, name: str, value: str): ''' Initializes a label (no quotes in the define) kernel option. Parameters ---------- name: str Property name value: str String value of the kernel option. ''' super().__init__(name, str(value))
[docs]class McMethod(McIntOption): ''' Selects the Monte Carlo simulation method: - Albedo Weight (0) - Albedo Rejection (1) - Microscopic Beer Lambert (2) A detailed description of the available methods can be found in: A. Sassaroli and F. Martelli Equivalence of four Monte Carlo methods for photon migration in turbid media J. Opt. Soc. Am. A / Vol. 29, No. 10 / October 2012 Note ---- Default method is Albedo Weight. The Albedo Rejection is fast but produces noisy results. The Microscopic Beer-Lambert is useful for fluence simulations with voxel size that is smaller than the mean free path. ''' albedo_weight = McIntOption('MC_METHOD', 0) aw = albedo_weight albedo_rejection = McIntOption('MC_METHOD', 1) ar = albedo_rejection microscopic_beer_lambert = McIntOption('MC_METHOD', 2) mbl = microscopic_beer_lambert default = albedo_weight def __init__(self, value: int=0): ''' Initializes the Monte Carlo simulation method option. Parameters ---------- value: int Allowed values are: - 0 for Albedo Weight - 1 for Albedo Rejection or, - 2 for Microscopic Beer-Lambert. ''' if value not in (0, 1, 2): raise ValueError('Allowed values are 0, 1 or 2!') super().__init__('MC_METHOD', value) def __repr__(self): return '{}({})'.format(self.__class__.__name__, self.cl_options[0][1])
[docs]class McUseFluenceCache(McBoolOption): ''' Turn on or off the use of OpenCL fluence cache. Default is off. Note ---- Fluence cache will improve performance for large deposition voxels, where there is a high likelihood that consecutive weight depositions will go into the same accumulator. ''' on = McBoolOption('MC_USE_FLUENCE_CACHE', True) off = McBoolOption('MC_USE_FLUENCE_CACHE', False) default = off def __init__(self, value: bool=False): ''' Initializes the fluence cache option. Parameters ---------- value: bool Use True to enable fluence cache. ''' super().__init__('MC_USE_FLUENCE_CACHE', value) def __repr__(self): return '{}({})'.format(self.__class__.__name__, self.cl_options[0][1])
[docs]class McUseHalfMath(McBoolOption): ''' Turn on or off the use of OpenCL half-precision math. Default is off. Note ---- Half-precision math gives performance benefit at a significantly reduced precision. Use this option with care. ''' on = McBoolOption('MC_USE_HALF_MATH', True) off = McBoolOption('MC_USE_HALF_MATH', False) default = off def __init__(self, value: bool=False): ''' Initializes the half-precision math kernel option. Parameters ---------- value: bool Use True to enable the half-precision math or False to disable half-precision math. Note ---- The half-precision math and native math :py:class:`~xopto.mcbase.mcoptions.McUseNativeMath` options are mutually excluding. Ony one of the two options can be enabled at any given time. If both options are enabled, the native math option takes precedence. ''' super().__init__('MC_USE_HALF_MATH', value) def __repr__(self): return '{}({})'.format(self.__class__.__name__, self.cl_options[0][1])
[docs]class McUseNativeMath(McBoolOption): ''' Turn on or off the use of OpenCL native math. Default is off. Note ---- Native math usually gives some performance benefit, but might not be fully compliant with precisions defined by the IEEE standards. ''' on = McBoolOption('MC_USE_NATIVE_MATH', True) off = McBoolOption('MC_USE_NATIVE_MATH', False) default = off def __init__(self, value: bool=False): ''' Initializes the native math kernel option. Parameters ---------- value: bool Use True to enable native math or False to disable native math. ''' super().__init__('MC_USE_NATIVE_MATH', value) def __repr__(self): return '{}({})'.format(self.__class__.__name__, self.cl_options[0][1])
[docs]class McIntLutMemory(McTypeOption): ''' OpenCL memory type used to hold the floating-point lookup table data. Use one of :py:attr:`__constant` or :py:attr:`__global`. Default is :py:attr:`__global`. Note ---- Selecting "__global" memory will likely lead to a significant performance degradation, in particular on older GPUs. The amount of available "__constant" memory on GPUs is typically limited to about 64k. ''' #class global(McObject): # cl_options = [('MC_INT_LUT_ARRAY_MEMORY', '__global')] __constant = McTypeOption('MC_INT_LUT_ARRAY_MEMORY', 'constant') __global = McTypeOption('MC_INT_LUT_ARRAY_MEMORY', 'global') default = __constant def __init__(self, value: str='global'): ''' Initializes the integer type lookup table memory type kernel option. Parameters ---------- value: str Use "global" to move integer type lookup table data to the __global OpenCL memory or use "constant" to move the lookup table data to the __constant OpenCL memory. ''' value = {'global': '__global', '__global':'__global', 'constant':'__constant', '__constant':'__constant'}.get(value) if value is None: raise ValueError( 'Lookup table memory type must be one of ' '"constant" or "global", but got "{}"!'.format(value) ) super().__init__('MC_INT_LUT_ARRAY_MEMORY', value) def __repr__(self): return '{}({})'.format(self.__class__.__name__, self.cl_options[0][1])
[docs]class McFloatLutMemory(McTypeOption): ''' OpenCL memory type used to hold the floating-point lookup table data. Use one of "constant_mem" or "global_mem". Default is "global_mem". Note ---- Selecting "global_mem" memory will likely lead to a significant performance degradation, in particular on older GPUs. The amount of available "constant_mem" memory on GPUs is typically limited to about 64k. ''' #class global(McObject): # cl_options = [('MC_FP_LUT_ARRAY_MEMORY', '__global')] constant_mem = McTypeOption('MC_FP_LUT_ARRAY_MEMORY', '__constant') global_mem = McTypeOption('MC_FP_LUT_ARRAY_MEMORY', '__global') default = constant_mem def __init__(self, value: str='global'): ''' Initializes the floating-point lookup table memory type kernel option. Parameters ---------- value: str Use "global_mem" to move integer lookup table data to the __global OpenCL memory or use "constant_mem" to move the lookup table data to the __constant OpenCL memory. ''' value = {'global': '__global', '__global':'__global', 'constant':'__constant', '__constant':'__constant'}.get(value) if value is None: raise ValueError( 'Lookup table memory type must be one of ' '"constant" or "global", but got "{}"!'.format(value) ) super().__init__('MC_FP_LUT_ARRAY_MEMORY', value) def __repr__(self): return '{}({})'.format(self.__class__.__name__, self.cl_options[0][1])
[docs]class McDebugMode(McBoolOption): ''' Switch the kernel debug mode on or off. Use only for kernel development. Default is off. ''' on = McBoolOption('MC_ENABLE_DEBUG', True) off = McBoolOption('MC_ENABLE_DEBUG', False) default = off def __init__(self, value: bool): ''' Initializes the debug mode kernel option. Parameters ---------- value: bool Use True to enable the debug mode or False to disable the debug mode. ''' super().__init__('MC_ENABLE_DEBUG', value) def __repr__(self): return '{}({})'.format(self.__class__.__name__, self.cl_options[0][1])
[docs]class McUseEnhancedRng(McBoolOption): ''' Run the kernel with the enhanced version of the random number generator. Default is off. ''' on = McBoolOption('MC_USE_ENHANCED_RNG', True) off = McBoolOption('MC_USE_ENHANCED_RNG', False) default = off def __init__(self, value: bool): ''' Initializes the enhanced random number generator mode kernel option. Note ---- The enhanced version of random number generator is required when performing simulations in large scattering volumes, were the number of scattering events is expected to be on the order of millions. Depending on the OpenCL device, there might be a small performance cost of about 15%, which is the reason for disabling this option by default. Parameters ---------- value: bool Use True to enable the enhanced random number generator mode or False to disable the enhanced random number generator mode. ''' super().__init__('MC_USE_ENHANCED_RNG', value) def __repr__(self): return '{}({})'.format(self.__class__.__name__, self.cl_options[0][1])
[docs]class McUseSoft64Atomics(McBoolOption): ''' Force software implementation of 64-bit atomic operations. ''' on = McBoolOption('MC_USE_SOFT_64_ATOMICS', True) off = McBoolOption('MC_USE_SOFT_64_ATOMICS', False) default = off def __init__(self, value: bool): ''' Initializes the software-based 64-bit integer atomics usage option. Parameters ---------- value: bool Use True to force software-implemented 64-bit integer atomics. ''' super().__init__('MC_USE_SOFT_64_ATOMICS', value) def __repr__(self): return '{}({})'.format(self.__class__.__name__, self.cl_options[0][1])
[docs]class McUseLottery(McBoolOption): ''' Switch on or off photon packet termination by lottery. Default is on. ''' on = McBoolOption('MC_USE_LOTTERY', True) off = McBoolOption('MC_USE_LOTTERY', False) default = on def __init__(self, value: bool): ''' Initializes the lottery kernel option. Parameters ---------- value: bool Use True to enable the lottery or False to disable the lottery. ''' super().__init__('MC_USE_LOTTERY', value) def __repr__(self): return '{}({})'.format(self.__class__.__name__, self.cl_options[0][1])
[docs]class McMinimumPacketWeight(McFloatOption): ''' Minimum packet weight allowed before starting packet termination or lottery. Default value is 1e-4. ''' default = McFloatOption('MC_PACKET_WEIGHT_MIN', 1e-4) def __init__(self, value: float=1e-4): if 0.0 >= value or value > 1.0: raise ValueError( 'Minimum photon packet weight must be > 0.0 and <= 1.0, ' 'but got {}!'.format(value) ) super().__init__('MC_PACKET_WEIGHT_MIN', value) def __repr__(self): return '{}({})'.format(self.__class__.__name__, self.cl_options[0][1])
[docs]class McPacketLotteryChance(McFloatOption): ''' Terminate photon packet by lottery if the value of a uniform random number from [0, 1] exceeds this value. Default value is 0.1. ''' default = McFloatOption('MC_PACKET_LOTTERY_CHANCE', 0.1) def __init__(self, value: float=0.1): ''' Initializes the lottery chance kernel option. Parameters ---------- value: float Lottery survival fraction from 0.0 to 1.0. ''' if 0.0 > value or value > 1.0: raise ValueError( 'Lottery chance must be a value from 0.0 to 1.0, ' 'but got {}!'.format(value) ) super().__init__('MC_PACKET_LOTTERY_CHANCE', value) def __repr__(self): return '{}({})'.format(self.__class__.__name__, self.cl_options[0][1])
[docs]class McUsePackedStructures(McBoolOption): ''' Turn on/off the use of tightly packed structures. Default value is off. Note ---- Note that packed structures can lead to significant performance degradation of the Monte Carlo kernel. This option is the last resort if the fields of the OpenCL and host structures cannot be properly aligned. When declaring OPenCL or host structures always start with the largest data type and move towards smaller data types. Use data types that are of size no less than 4 bytes. ''' on = McBoolOption('MC_USE_PACKED_STRUCTURES', True) off = McBoolOption('MC_USE_PACKED_STRUCTURES', False) default = off def __init__(self, value: bool): ''' Initializes the packed structures kernel option. Parameters ---------- value: bool Use True to enable packed structures or False to disable packed structures. ''' super().__init__('MC_USE_PACKED_STRUCTURES', value) def __repr__(self): return '{}({})'.format(self.__class__.__name__, self.cl_options[0][1])
[docs]class McUseEvents(McBoolOption): ''' Turn on/off tracking of packet events. Default value is off. ''' on = McBoolOption('MC_USE_EVENTS', True) off = McBoolOption('MC_USE_EVENTS', False) default = off def __init__(self, value: bool): ''' Initializes the kernel option for tracking packet events. Parameters ---------- value: bool Use True to enable tracking of packet events or False to disable tracking of packet events. ''' super().__init__('MC_USE_PACKED_STRUCTURES', value) def __repr__(self): return '{}({})'.format(self.__class__.__name__, self.cl_options[0][1])
Options = List[McOption]