class TestOptionsExpander(unittest.TestCase): def setUp(self): unittest.TestCase.setUp(self) self.ops = Options("op1") self.ops.beam.energy_eV = [5e3, 10e3, 15e3] self.expander = OptionsExpander() def tearDown(self): unittest.TestCase.tearDown(self) def testexpand(self): opss = self.expander.expand(self.ops) self.assertEqual(3, len(opss)) names = list(map(attrgetter('name'), opss)) self.assertIn('op1+energy_eV=5000.0', names) self.assertIn('op1+energy_eV=10000.0', names) self.assertIn('op1+energy_eV=15000.0', names) def testis_expandable(self): self.assertTrue(self.expander.is_expandable(self.ops)) self.ops.beam.energy_eV = [5e3] self.assertFalse(self.expander.is_expandable(self.ops))
def setUp(self): unittest.TestCase.setUp(self) self.ops = Options("op1") self.ops.beam.energy_eV = [5e3, 10e3, 15e3] self.expander = OptionsExpander()
def __init__(self): self._expander = OptionsExpander() self._beam_exporters = {} self._geometry_exporters = {} self._detector_exporters = {} self._limit_exporters = {} self._model_exporters = {}
def __init__(self): self._expander = OptionsExpander()
class Converter(object): """ Base class of all converters. Derived class shall modify the following class variables to specify the allowable classes for each of these parameters: * :attr:`MATERIALS`: list of allowable material classes * :attr:`BEAM`: list of allowable beam classes * :attr:`GEOMETRIES`: list of allowable geometry classes * :attr:`DETECTORS`: list of allowable detector classes * :attr:`LIMITS`: list of allowable limit classes * :attr:`MODELS`: dictionary of allowable models (key: model type, value: list of allowable models) * :attr:`DEFAULT_MODELS`: dictionary of default models (key: model type, value: default model) .. note:: The keys for :attr:`MODELS` and :attr:`DEFAULT_MODELS` have to be the same. """ PARTICLES = [] MATERIALS = [] BEAMS = [] GEOMETRIES = [] DETECTORS = [] LIMITS = [] MODELS = {} DEFAULT_MODELS = {} def __init__(self): self._expander = OptionsExpander() def _warn(self, *messages): message = ' '.join(messages) warnings.warn(message, ConversionWarning) def convert(self, options): """ Performs two tasks: 1. Expand the options to create single options that the Monte Carlo program can run 2. Converts the beam, geometry, detectors and limits to be compatible with the Monte Carlo program. This method may raise :exc:`ConversionException` if some parameters cannot be converted. If a parameter is modified or removed during the conversion, a :class:`ConversionWarning` is issued. If no conversion is required, the parameter (and its object) are left intact. This method returns a list of options, corresponding to the expanded options. :arg options: options """ list_options = [] for options in self._expand(options): if not self._convert_beam(options): continue if not self._convert_geometry(options): continue if not self._convert_detectors(options): continue if not self._convert_limits(options): continue if not self._convert_models(options): continue list_options.append(options) if not list_options: self._warn("No options could be converted") return list_options def _expand(self, options): return self._expander.expand(options) def _convert_beam(self, options): clasz = options.beam.__class__ if clasz not in self.BEAMS: self._warn("Cannot convert beam (%s)." % clasz.__name__, "This options definition was removed.") return False if options.beam.particle not in self.PARTICLES: self._warn("Particle (%s) not supported" % options.beam.particle, "This options definition was removed.") return False return True def _convert_geometry(self, options): # Check class clasz = options.geometry.__class__ if clasz not in self.GEOMETRIES: self._warn("Cannot convert geometry (%s)." % clasz.__name__, "This options definition was removed.") return False # Check material for material in options.geometry.get_materials(): if not isinstance(material, tuple(self.MATERIALS)): self._warn("Invalid material (%s) in geometry (%s)" % \ (material.name, clasz.__name__), "This options definition was removed.") return False return True def _convert_detectors(self, options): for key in list(options.detectors.keys()): detector = options.detectors[key] clasz = detector.__class__ if clasz not in self.DETECTORS: options.detectors.pop(key) self._warn("Detector '%s' of type '%s' cannot be converted." % \ (key, clasz.__name__), "It was removed") if not options.detectors: self._warn("No detectors in options.", "This options definition was removed.") return False return True def _convert_limits(self, options): for limit in list(options.limits): clasz = limit.__class__ if clasz not in self.LIMITS: options.limits.discard(limit) self._warn("Limit of type '%s' cannot be converted." % clasz.__name__, "It was removed") if not options.limits: self._warn("No limits in options.", "This options definition was removed.") return False return True def _convert_models(self, options): for model_type, default_model in self.DEFAULT_MODELS.items(): models = list(options.models.iterclass(model_type)) # Add default model if model type is missing if not models: options.models.add(default_model) self._warn("Set default model (%s) for model type '%s'" % \ (default_model, model_type)) # Check if model is allowable else: availables = set(self.MODELS[model_type]) classes = set(filter(inspect.isclass, availables)) for model in models: if model not in availables and \ not isinstance(model, tuple(classes)): options.models.discard(model) # not required options.models.add(default_model) self._warn("Model (%s) is not allowable." % model, "It is replaced by the default model (%s)." % default_model) # Remove extra model types for model in list(options.models): if model.type not in self.DEFAULT_MODELS: options.models.discard(model) self._warn("Unknown model type (%s) for this converter." % model.type, "Model (%s) is removed." % model) return True
class Exporter(object, metaclass=ABCMeta): """ Base class for all exporters. """ def __init__(self): self._expander = OptionsExpander() self._beam_exporters = {} self._geometry_exporters = {} self._detector_exporters = {} self._limit_exporters = {} self._model_exporters = {} def export(self, options, dirpath, *args, **kwargs): """ Exports options to a file inside the specified output directory. Returns the filepath of the exported options. :arg options: options to export The options must only contained a single value for each parameter. :arg dirpath: full path to output directory """ if self._expander.is_expandable(options): raise ValueError("Only options with singular value can be exported") return self._export(options, dirpath, *args, **kwargs) @abstractmethod def _export(self, options, dirpath, *args, **kwargs): """ Performs the actual export. """ raise NotImplementedError def _run_exporters(self, options, *args, **kwargs): """ Internal command to call the register export functions. """ self._export_beam(options, *args, **kwargs) self._export_geometry(options, *args, **kwargs) self._export_detectors(options, *args, **kwargs) self._export_limits(options, *args, **kwargs) self._export_models(options, *args, **kwargs) def _export_beam(self, options, *args, **kwargs): """ Exports the beam. If a exporter function is defined, it calls this function with the following arguments: * options object * beam object * optional arguments and keyword-arguments """ clasz = options.beam.__class__ method = self._beam_exporters.get(clasz) if not method: raise ExporterException("Could not export beam '%s'" % clasz.__name__) method(options, options.beam, *args, **kwargs) def _export_geometry(self, options, *args, **kwargs): """ Exports the geometry. If a exporter function is defined, it calls this function with the following arguments: * options object * geometry object * optional arguments and keyword-arguments """ clasz = options.geometry.__class__ method = self._geometry_exporters.get(clasz) if method: method(options, options.geometry, *args, **kwargs) else: raise ExporterException("Could not export geometry '%s'" % clasz.__name__) def _export_detectors(self, options, *args, **kwargs): """ Exports the detectors. If a exporter function is defined, it calls this function with the following arguments for each detector: * options object * name/key of detector * detector object * optional arguments and keyword-arguments """ for name, detector in options.detectors.items(): clasz = detector.__class__ method = self._detector_exporters.get(clasz) if not method: raise ExporterException("Could not export detector '%s' (%s)" % \ (name, clasz.__name__)) method(options, name, detector, *args, **kwargs) def _export_limits(self, options, *args, **kwargs): """ Exports the limit. If a exporter function is defined, it calls this function with the following arguments for each limit: * options object * limit object * optional arguments and keyword-arguments """ for limit in options.limits: clasz = limit.__class__ method = self._limit_exporters.get(clasz) if not method: raise ExporterException("Could not export limit '%s'" % \ clasz.__name__) method(options, limit, *args, **kwargs) def _export_models(self, options, *args, **kwargs): """ Exports the models. If a exporter function is defined, it calls this function with the following arguments for each model: * options object * model object * optional arguments and keyword-arguments """ for model in options.models: type_ = model.type method = self._model_exporters.get(type_) if not method: raise ExporterException("Could not export model of type '%s'" % \ type_.name) method(options, model, *args, **kwargs) def _export_dummy(self, options, *args, **kwargs): pass