diff --git a/conda-requirements b/conda-requirements index 039f827092e..03b5e396cb9 100644 --- a/conda-requirements +++ b/conda-requirements @@ -6,6 +6,7 @@ h5py==2.5 matplotlib==1.4.3 astropy==1.0.5 PyYAML==3.11 +jsonschema==2.5.1 numexpr==2.4.4 Cython==0.21 networkx==1.10 diff --git a/docs/configuration/configuration.rst b/docs/configuration/configuration.rst index 90b210a37e3..9aba121c805 100644 --- a/docs/configuration/configuration.rst +++ b/docs/configuration/configuration.rst @@ -3,11 +3,4 @@ Configuration File ****************** .. note:: - The following block shows the contents of - ''tardis/data/tardis_config_definition.yml'' and thus gives an account of all - the configuration keys TARDIS recognises, whether it considers these entries - mandatory and which default values the validator will use if they are not - provided in the YAML file. - -.. literalinclude:: ../../tardis/data/tardis_config_definition.yml - :language: yaml + This page is Work in Progress. diff --git a/setup.py b/setup.py index 8219902f0f9..4fbe7fabd27 100755 --- a/setup.py +++ b/setup.py @@ -90,6 +90,7 @@ # Add the project-global data package_info['package_data'].setdefault(PACKAGENAME, []) package_info['package_data'][PACKAGENAME].append('data/*') +package_info['package_data'][PACKAGENAME].append('io/schemas/*') # Define entry points for command-line scripts entry_points = {} diff --git a/tardis/data/tardis_config_definition.yml b/tardis/data/tardis_config_definition.yml deleted file mode 100644 index 2ca4b2b2e3e..00000000000 --- a/tardis/data/tardis_config_definition.yml +++ /dev/null @@ -1,446 +0,0 @@ -tardis_config_version: - property_type: string - default: None - mandatory: True - help: Version of the configuration file - - -supernova: - luminosity_requested: - property_type: quantity - mandatory: True - default: 1 solLum - help: requested output luminosity for simulation - - time_explosion: - property_type: quantity - mandatory: True - default: None - help: time since explosion - - distance: - property_type: quantity - mandatory: False - default: None - help: distance to supernova - - luminosity_wavelength_start: - property_type: quantity - mandatory: False - default: 0 angstrom - help: start of the integral needed for getting the luminosity right - - - luminosity_wavelength_end: - property_type: quantity - mandatory: False - default: inf angstrom - help: start of the integral needed for getting the luminosity right - -atom_data: - property_type: string - mandatory: True - help: path or filename to the Atomic Data HDF5 file - -plasma: - initial_t_inner: - property_type: quantity - mandatory: False - default: -1 K - help: > - initial temperature of the inner boundary black body. If set to -1 K - will result in automatic calculation of boundary - - - initial_t_rad: - property_type: quantity - mandatory: False - default: -1 K - help: > - initial radiative temperature in all cells. If set to -1 K will result - in automtatic calculation of the initial temperatures - - disable_electron_scattering: - property_type: bool - mandatory: False - default: False - help: > - disable electron scattering process in montecarlo part - non-physical only - for tests - - ionization: - property_type: string - mandatory: True - default: None - allowed_value: nebular lte - help: ionization treatment mode - - - excitation: - property_type: string - mandatory: True - default: None - allowed_value: lte dilute-lte - help: excitation treatment mode - - radiative_rates_type: - property_type: string - mandatory: True - default: None - allowed_value: dilute-blackbody detailed blackbody - help: radiative rates treatment mode - - line_interaction_type: - property_type: string - mandatory: True - default: None - allowed_value: scatter downbranch macroatom - help: line interaction mode - - w_epsilon: - property_type: float - mandatory: False - default: 1e-10 - help: w to use when j_blues get numerically 0. - avoids numerical complications - - delta_treatment: - property_type: float - mandatory: False - default: None - help: > - In the saha calculation set delta equals to the number given in - this configuration item. if set to None (default), normal delta - treatment (as described in Mazzali & Lucy 1993) will be applied - - nlte: - species: - property_type: list - mandatory: False - default: [] - help: > - Species that are requested to be NLTE treated in the format - ['Si 2', 'Ca 1', etc.] - - coronal_approximation: - property_type: bool - default: False - mandatory: False - help: set all jblues=0.0 - - classical_nebular: - property_type: bool - default: False - mandatory: False - help: sets all beta_sobolevs to 1 - - helium_treatment: - property_type: string - mandatory: False - default: none - allowed_value: none recomb-nlte numerical-nlte - help: none to treat He as the other elements. recomb-nlte to treat with NLTE approximation. - - heating_rate_data_file: - property_type: string - mandatory: False - default: none - help: Path to file containing heating rate/light curve data. - -model: - structure: - property_type : container-property - type: - property_type: container-declaration - containers: ['file', 'specific'] - _file: ['filename','filetype'] - +file: ['v_inner_boundary','v_outer_boundary'] - _specific: ['velocity', 'density'] - - - filename: - property_type: string - default: None - mandatory: True - help: file name (with path) to structure model - - filetype: - property_type: string - default: None - allowed_value: ['simple_ascii', 'artis'] - mandatory: True - help: file type - - v_inner_boundary: - property_type: quantity - default: 0 km/s - mandatory: False - help: location of the inner boundary chosen from the model - - v_outer_boundary: - property_type: quantity - default: inf km/s - mandatory: False - help: location of the inner boundary chosen from the model - - - velocity: - property_type: quantity_range_sampled - default: None - mandatory: True - help: description of the boundaries of the shells - - density: - property_type: container-property - type: - property_type: container-declaration - containers: ['branch85_w7','exponential','power_law', 'uniform'] - _uniform: ['value'] - _branch85_w7: [] - +branch85_w7: ['w7_time_0', 'w7_rho_0', 'w7_v_0'] - _power_law: ['time_0', 'rho_0', 'v_0', 'exponent'] - _exponential: ['time_0', 'rho_0', 'v_0', 'exponent'] - - w7_time_0: - property_type: quantity - default: 0.000231481 day - mandatory: False - help: This needs no change - DO NOT TOUCH - - w7_rho_0: - property_type: quantity - default: 3e29 g/cm^3 - mandatory: False - help: This needs no change - DO NOT TOUCH - - w7_v_0: - property_type: quantity - default: 1 km/s - mandatory: False - help: This needs no change - DO NOT TOUCH - - time_0: - property_type: quantity - default: None - mandatory: True - help: Time at which the pure model densities are right - - rho_0: - property_type: quantity - default: None - mandatory: True - help: density at time_0 - - v_0: - property_type: quantity - default: None - mandatory: True - help: at what velocity the density rho_0 applies - - exponent: - property_type: float - default: None - mandatory: True - help: exponent for exponential density profile - - value: - property_type: quantity - default: None - mandatory: True - help: value for uniform density - - abundances: - property_type: container-property - type: - property_type: container-declaration - containers: ['file','uniform'] - _uniform: [] - _file: ['filetype','filename'] - - filename: - property_type: string - default: None - mandatory: True - help: filename - - filetype: - property_type: string - default: None - mandatory: False - help: type of abundance file to read in - - - - - - - -montecarlo: - nthreads: - property_type: int - default: 1 - mandatory: False - help: The number of OpenMP threads. - - seed: - property_type: int - default: 23111963 - mandatory: False - help: Seed for the random number generator - - no_of_packets: - property_type: int - default: None - mandatory: True - help: Seed for the random number generator - - iterations: - property_type: int - default: None - mandatory: True - help: Number of maximum iterations - - black_body_sampling: - property_type: quantity_range_sampled - default: [50 angstrom, 200000 angstrom, 1000000] - mandatory: False - help: Sampling of the black-body for energy packet creation (giving maximum and minimum packet frequency) - - last_no_of_packets: - property_type: int - default: -1 - mandatory: False - help: > - This can set the number of packets for the last run. - If set negative it will remain the same as all other runs. - - no_of_virtual_packets: - property_type: int - default: 0 - mandatory: False - help: Setting the number of virtual packets for the last iteration. - - virtual_spectrum_range: - property_type: quantity_range_sampled - default: [50 angstrom, 250000 angstrom, 1000000] - mandatory: False - help: Limits of virtual packet spectrum (giving maximum and minimum packet frequency) - - - enable_reflective_inner_boundary: - property_type: bool - default: False - mandatory: False - help: > - experimental feature to enable a reflective boundary. - - inner_boundary_albedo: - property_type: float - default: 0.0 - mandatory: False - help: albedo of the reflective boundary - - convergence_strategy: - property_type : container-property - type: - property_type: container-declaration - containers: ['damped', 'specific'] - _damped: [] - +damped: ['damping_constant', 't_inner', 't_rad', 'w', - 'lock_t_inner_cycles', 't_inner_update_exponent',] - _specific: ['threshold', 'fraction', 'hold_iterations'] - +specific: ['t_inner', 't_rad', 'w', 'lock_t_inner_cycles', - 'damping_constant', 't_inner_update_exponent'] - - t_inner_update_exponent: - property_type: float - default: -0.5 - mandatory: False - help: L=4*pi*r**2*T^y - - lock_t_inner_cycles: - property_type: int - mandatory: False - default: 1 - help: > - The number of cycles to lock the update of the inner boundary temperature. - This process helps with convergence. The default is to switch it off (1 cycle) - hold_iterations: - property_type: int - default: 3 - mandatory: True - help: > - the number of iterations that the convergence criteria need to be - fulfilled before TARDIS accepts the simulation as converged - - fraction: - property_type: float - default: 0.8 - mandatory: True - help: > - the fraction of shells that have to converge to the given - convergence threshold. For example, 0.8 means that 80% of shells - have to converge to the threshold that convergence is established - - damping_constant: - property_type: float - mandatory: False - default: 0.5 - help: damping constant - - threshold: - property_type: float - mandatory: True - help: > - specifies the threshold that is taken as convergence - (i.e. 0.05 means that the value does not change more than 5%) - - - t_inner: - damping_constant: - property_type: float - mandatory: False - default: 0.5 - help: damping constant - - threshold: - property_type: float - mandatory: False - help: > - specifies the threshold that is taken as convergence - (i.e. 0.05 means that the value does not change more than 5%) - - t_rad: - damping_constant: - property_type: float - mandatory: False - default: 0.5 - help: damping constant - - threshold: - property_type: float - mandatory: True - help: > - specifies the threshold that is taken as convergence - (i.e. 0.05 means that the value does not change more than 5%) - - - w: - damping_constant: - property_type: float - mandatory: False - default: 0.5 - help: damping constant - - threshold: - property_type: float - mandatory: True - help: > - specifies the threshold that is taken as convergence - (i.e. 0.05 means that the value does not change more than 5%) - -spectrum: - property_type: quantity_range_sampled - default: None - mandatory: True - help: Final spectrum sampling - diff --git a/tardis/io/config_reader.py b/tardis/io/config_reader.py index fdcdeb6451b..6fc603d5e9f 100644 --- a/tardis/io/config_reader.py +++ b/tardis/io/config_reader.py @@ -11,7 +11,7 @@ import tardis from tardis.io.model_reader import ( read_density_file, calculate_density_after_time, read_abundances_file) -from tardis.io.config_validator import ConfigurationValidator +from tardis.io import config_validator from tardis.io.util import YAMLLoader, yaml_load_file from tardis import atomic from tardis.util import (species_string_to_tuple, parse_quantity, @@ -25,8 +25,6 @@ data_dir = os.path.abspath(os.path.join(tardis.__path__[0], 'data')) -default_config_definition_file = os.path.join(data_dir, - 'tardis_config_definition.yml') #File parsers for different file formats: @@ -486,7 +484,10 @@ def parse_spectrum_list2dict(spectrum_list): """ Parse the spectrum list [start, stop, num] to a list """ - + if 'start' in spectrum_list and 'stop' in spectrum_list \ + and 'num' in spectrum_list: + spectrum_list = [spectrum_list['start'], spectrum_list['stop'], + spectrum_list['num']] if spectrum_list[0].unit.physical_type != 'length' and \ spectrum_list[1].unit.physical_type != 'length': raise ValueError('start and end of spectrum need to be a length') @@ -601,7 +602,7 @@ def from_yaml(cls, fname): return cls.from_config_dict(yaml_dict) @classmethod - def from_config_dict(cls, config_dict, config_definition_file=None): + def from_config_dict(cls, config_dict): """ Validating a config file. @@ -621,13 +622,7 @@ def from_config_dict(cls, config_dict, config_definition_file=None): """ - if config_definition_file is None: - config_definition_file = default_config_definition_file - - config_definition = yaml_load_file(config_definition_file) - - return cls(ConfigurationValidator(config_definition, - config_dict).get_config()) + return cls(config_validator.validate_dict(config_dict)) def __init__(self, value=None): if value is None: @@ -753,7 +748,7 @@ def from_yaml(cls, fname, *args, **kwargs): @classmethod def from_config_dict(cls, config_dict, atom_data=None, test_parser=False, - config_definition_file=None, validate=True, + validate=True, config_dirname=''): """ Validating and subsequently parsing a config file. @@ -773,10 +768,6 @@ def from_config_dict(cls, config_dict, atom_data=None, test_parser=False, switch on to ignore a working atom_data, mainly useful for testing this reader - config_definition_file: ~str - path to config definition file, if `None` will be set to the default - in the `data` directory that ships with TARDIS - validate: ~bool Turn validation on or off. @@ -788,13 +779,8 @@ def from_config_dict(cls, config_dict, atom_data=None, test_parser=False, """ - if config_definition_file is None: - config_definition_file = default_config_definition_file - - config_definition = yaml_load_file(config_definition_file) if validate: - validated_config_dict = ConfigurationValidator(config_definition, - config_dict).get_config() + validated_config_dict = config_validator.validate_dict(config_dict) else: validated_config_dict = config_dict @@ -850,7 +836,9 @@ def from_config_dict(cls, config_dict, atom_data=None, test_parser=False, structure_section = model_section['structure'] if structure_section['type'] == 'specific': - start, stop, num = model_section['structure']['velocity'] + velocity = model_section['structure']['velocity'] + start, stop, num = velocity['start'], velocity['stop'], \ + velocity['num'] num += 1 velocities = quantity_linspace(start, stop, num) @@ -1007,43 +995,34 @@ def from_config_dict(cls, config_dict, atom_data=None, test_parser=False, ##### Monte Carlo Section montecarlo_section = validated_config_dict['montecarlo'] + montecarlo_section['no_of_packets'] = \ + int(montecarlo_section['no_of_packets']) + montecarlo_section['last_no_of_packets'] = \ + int(montecarlo_section['last_no_of_packets']) if montecarlo_section['last_no_of_packets'] < 0: montecarlo_section['last_no_of_packets'] = \ montecarlo_section['no_of_packets'] - default_convergence_section = {'type': 'damped', - 'lock_t_inner_cycles': 1, - 't_inner_update_exponent': -0.5, - 'damping_constant': 0.5} - - - - if montecarlo_section['convergence_strategy'] is None: - logger.warning('No convergence criteria selected - ' - 'just damping by 0.5 for w, t_rad and t_inner') - montecarlo_section['convergence_strategy'] = ( - parse_convergence_section(default_convergence_section)) - else: - montecarlo_section['convergence_strategy'] = ( + montecarlo_section['convergence_strategy'] = ( parse_convergence_section( montecarlo_section['convergence_strategy'])) black_body_section = montecarlo_section['black_body_sampling'] montecarlo_section['black_body_sampling'] = {} montecarlo_section['black_body_sampling']['start'] = \ - black_body_section[0] + black_body_section['start'] montecarlo_section['black_body_sampling']['end'] = \ - black_body_section[1] + black_body_section['stop'] montecarlo_section['black_body_sampling']['samples'] = \ - black_body_section[2] + black_body_section['num'] virtual_spectrum_section = montecarlo_section['virtual_spectrum_range'] montecarlo_section['virtual_spectrum_range'] = {} montecarlo_section['virtual_spectrum_range']['start'] = \ - virtual_spectrum_section[0] + virtual_spectrum_section['start'] montecarlo_section['virtual_spectrum_range']['end'] = \ - virtual_spectrum_section[1] + virtual_spectrum_section['stop'] montecarlo_section['virtual_spectrum_range']['samples'] = \ - virtual_spectrum_section[2] + virtual_spectrum_section['num'] ###### END of convergence section reading diff --git a/tardis/io/config_validator.py b/tardis/io/config_validator.py index e852bf2ff55..754a9bd1280 100644 --- a/tardis/io/config_validator.py +++ b/tardis/io/config_validator.py @@ -1,1436 +1,81 @@ -# coding=utf-8 - -import re -import logging -import pprint -import ast - -import numpy as np -from astropy import units - -try: - from astropy.units.core import UnitsException -except ImportError: - from astropy.units.core import UnitsError as UnitsException - -from astropy import constants - +import os import yaml +from copy import deepcopy +from jsonschema import Draft4Validator, RefResolver, validators +from astropy.units.quantity import Quantity +from tardis.io.util import YAMLLoader -from tardis.atomic import atomic_symbols_data - - -logger = logging.getLogger(__name__) - - -class Error(Exception): - """Base class for exceptions in the config parser.""" - pass - - -class ConfigTypeError(Error, ValueError): - """ - Exception raised if the type of the configured value mismatches the type - specified in the default configuration. - """ - - def __init__(self, value, expected_type, _help): - self.value = value - self.expected_type = expected_type - self.help = _help - - def __str__(self): - return "Expected type %s but found %s.\nHelp:%s " % \ - (repr(self.expected_type), repr(type(self.value)), help) - - -class ConfigError(Error): - """ - Exception raised if something is wrong in the configuration. - """ - - def __init__(self, path): - self.path = path - - def __str__(self): - return "Error in the configuration at %s " % ("->".join(self.path)) - - -class ConfigValueError(ConfigError, ValueError): - """ - Exception raised if the given value does not match the allowed constraints. - """ - - default_msg = "Given value (%s) not allowed in constraint (%s). [%s]" +base_dir = os.path.abspath(os.path.dirname(__file__)) +schema_dir = os.path.join(base_dir, 'schemas') +config_schema_file = os.path.join(schema_dir, 'base.yml') - def __init__(self, config_value, allowed_constraint, path, msg=None): - self.config_value = config_value - self.allowed_constraint = allowed_constraint - self.path = path - if msg is None: - self.msg = self.default_msg - else: - self.msg = msg - def __str__(self): - return self.msg % (str(self.config_value), str(self.allowed_constraint), self.path) - - -class DefaultConfigError(ConfigError): - """ - Exception raised if something is wrong in the default configuration. - """ - - def __str__(self): - return "Error in the default configuration at %s " % \ - ("->".join(self.path)) - - -class PropertyType(object): +def extend_with_default(validator_class): """ - Base class for all property types containing all the basic methods. - """ - - def __init__(self): - self._default = None - self._allowed_value = None - self._allowed_type = None - self._help = None - self._mandatory = False - self._lower = None - self._upper = None - pass - - @property - def default(self): - """ - Geter for the default config value. - Returns - ------- - default - default config value. - """ - return self._default - - @default.setter - def default(self, value): - """ - Sets the default value if the type is ok. - Parameters - ---------- - value - default value - """ - self._default = self.to_type(value) - - @property - def allowed_value(self): - """ - Returns the allowed value - Returns - ------- - allowed_value - allowed value - """ - return self._allowed_value - - @allowed_value.setter - def allowed_value(self, value): - """ - Sets the allowed values - Parameters - ---------- - value - allowed values - """ - if isinstance(value, basestring): - self._allowed_value = set(self.__list_dtype(value.split())) - elif isinstance(value, list) or isinstance(value, set): - self._allowed_value = set(self.__list_dtype(value)) - elif isinstance(value, float) or isinstance(value, int): - self._allowed_type = {value} - else: - raise ValueError("Can not set allowed value.") - - @property - def allowed_type(self): - """ - Returns the allowed type - Returns - ------- - allowed_type - allowed type - - """ - return self._allowed_value - - @allowed_type.setter - def allowed_type(self, value): - self._allowed_type = value - if '_parse_allowed_type' in (set(dir(self.__class__)) - set(dir(PropertyType))) and value is not None: - self._lower, self._upper = self._parse_allowed_type(value) - - @property - def help(self): - return self._help - - @help.setter - def help(self, value): - self._help = value - - @property - def mandatory(self): - return self._mandatory - - @mandatory.setter - def mandatory(self, value): - self._mandatory = value - - def check_type(self, value): - return True - - def to_type(self, value): - return value - - @staticmethod - def __list_dtype(mixed_list): - try: - tmp = [str(a) for a in mixed_list] - except ValueError: - try: - tmp = [float(a) for a in mixed_list] - except ValueError: - try: - tmp = [int(a) for a in mixed_list] - except: - raise ValueError("Forbidden type in allowed_type") - return tmp - - def _check_allowed_value(self, _value): - """ - Returns True if the value is allowed or no allowed value is given. - """ - if self._allowed_value is not None: - atype = type(iter(self.allowed_value).next()) - value = atype(_value) - if value in self.allowed_value: - return True - else: - return False - else: - return True - - def __repr__(self): - if hasattr(self, "_allowed_type"): - return str("Type %s; Allowed type: %s" % (self.__class__.__name__, self._allowed_type)) - else: - return str("Type %s; " % self.__class__.__name__) - - -class PropertyTypeContainer(PropertyType): - def check_type(self): - pass - - -class PropertyTypeBool(PropertyType): - def check_type(self, value): - try: - foo = bool(value) - return True - except ValueError: - return False - - def to_type(self, value): - return bool(value) - - -class PropertyTypeInt(PropertyType): - def check_type(self, value): - try: - int(value) - if float.is_integer(float(value)): - if self._check_allowed_value(value) and self._check_allowed_type(value): - return True - else: - return False - except ValueError: - return False - - def to_type(self, value): - return int(value) - #ToDo: use this if allowed type is specified - - @staticmethod - def _parse_allowed_type(allowed_type): - string = allowed_type.strip() - upper = None - lower = None - if string.find("<") or string.find(">"): - #like x < a - match = re.compile('[<][\s]*[0-9.+^*eE]*$').findall(string) - if match: - value = re.compile('[0-9.+^*eE]+').findall(string)[0] - upper = float(value) - #like a > x" - match = re.compile('^[\s0-9.+^*eE]*[\s]*[<]$').findall(string) - if match: - value = re.compile('[0-9.+^*eE]+').findall(string)[0] - upper = float(value) - #like x > a - match = re.compile('[>][\s]*[0-9.+^*eE]*$').findall(string) - if match: - value = re.compile('[0-9.+^*eE]+').findall(string)[0] - lower = float(value) - #like a < x - match = re.compile('^[\s0-9.+^*eE]*[\s]*[<]$').findall(string) - if match: - value = re.compile('[0-9.+^*eE]+').findall(string)[0] - lower = float(value) - return lower, upper - - @staticmethod - def __check_type(value, lower_lim, upper_lim): - upper, lower = True, True - if upper_lim is not None: - upper = value < upper_lim - if lower_lim is not None: - lower = value > lower_lim - return upper and lower - - def _check_allowed_type(self, value): - if self._allowed_type is not None: - if self.__check_type(value, self._lower, self._upper): - return True - else: - return False - else: - return True - - def _is_valid(self, value): - if not self.check_type(value): - return False - if self.allowed_value is not None: - return False - if not self.__check_type(value, self._lower, self._upper): - return False - return True - - -class PropertyTypeFloat(PropertyTypeInt): - def check_type(self, value): - try: - float(value) - if self._check_allowed_value(value) and self._check_allowed_type(value): - return True - except ValueError: - return False - - def to_type(self, value): - return float(value) - - -class PropertyTypeQuantity(PropertyType): - def check_type(self, value): - if hasattr(value, 'unit') and hasattr(value, 'value'): - if self._default is not None: - try: - self._default.to(value.unit) - except ValueError: - return False - return True - try: - quantity_split = value.strip().split() - quantity_value = quantity_split[0] - quantity_unit = ' '.join(quantity_split[1:]) - if quantity_unit.strip() == 'log_lsun': - quantity_unit = 'erg/s' - try: - float(quantity_value) - units.Unit(quantity_unit) - except ValueError: - return False - - if self._default is not None: - #d_quantity_split = self._default.strip().split() - self._default.to(quantity_unit) - float(quantity_value) - units.Unit(quantity_unit) - return True - except (ValueError, AttributeError): - return False - - def to_type(self, value): - if hasattr(value, 'unit') and hasattr(value, 'value'): - return value - elif hasattr(value, 'split'): - quantity_split = value.strip().split() - quantity_value = quantity_split[0] - quantity_unit = ' '.join(quantity_split[1:]) - if quantity_unit.strip() == 'log_lsun': - quantity_value = 10 ** (float(quantity_value) + - np.log10(constants.L_sun.cgs.value)) - quantity_unit = 'erg/s' - - return float(quantity_value) * units.Unit(quantity_unit) - else: - raise ConfigError - - - -class PropertyTypeQuantityRange(PropertyTypeQuantity): - @staticmethod - def _to_units(los): - if len(los) > 2: - loq = [(lambda x: (units.Quantity(float(x[0]), x[1])))(x.split()) - if not isinstance(x, units.Quantity) else x - for x in los[:-1]] - else: - loq = [(lambda x: (units.Quantity(float(x[0]), x[1])))(x.split()) - if not isinstance(x, units.Quantity) else x - for x in los] - try: - _ = reduce((lambda a, b: a.to(b.unit)), loq) - loq = [a.to(loq[0].unit) for a in loq] - return loq - except UnitsException as e: - msg = "Incompatible units in %s" % str(los) + str(e) - raise ValueError(msg) - - def check_type(self, value): - if isinstance(value, dict): - if (reduce((lambda a, b: a and b in value.keys()), [True, 'start', 'end'])) \ - or (reduce((lambda a, b: a and b in value.keys()), [True, 'start', 'stop'])): # for legacy support - los = [value['start'], value['end']] - loq = self._to_units(los) - if abs(loq[0].value - loq[1].value) > 0: - return True - elif isinstance(value, list): - if len(value) == 2: - loq = self._to_units(value) - if abs(loq[0].value - loq[1].value) > 0: - return True - else: - return False - elif isinstance(value, basestring): - try: - clist = ast.literal_eval(value) - if len(clist) == 2: - loq = self._to_units(value) - if abs(loq[0].value - loq[1].value) > 0: - return True - else: - return False - else: - return False - except SyntaxError: - clist = value.split() - if len(clist) == 2: - loq = self._to_units(value) - if abs(loq[0].value - loq[1].value) > 0: - return True - else: - return False - else: - return False - except ValueError: - return False - return False - - def to_type(self, value): - if isinstance(value, list): - return self._to_units(value[:2]) - elif isinstance(value, dict): - los = [value['start'], value['end']] - return self._to_units(los) - - -class PropertyTypeQuantityRangeSampled(PropertyTypeQuantityRange): - def check_type(self, value): - if isinstance(value, dict): - if reduce((lambda a, b: a and b in value), [True, 'start', 'stop', 'num']): - los = [value['start'], value['stop']] - loq = self._to_units(los) - if abs(loq[0].value - loq[1].value) > 0: - return True - elif isinstance(value, list): - if len(value) == 3: - loq = self._to_units(value) - if abs(loq[0].value - loq[1].value) > 0: - return True - return False - - def to_type(self, value): - if isinstance(value, list): - _tmp = self._to_units(value[:2]) - _tmp.append(value[2]) - return _tmp - elif isinstance(value, dict): - los = [value['start'], value['stop']] - _tmp = self._to_units(los) - _tmp.append(value['num']) - return _tmp - - -class PropertyTypeString(PropertyType): - def check_type(self, value): - try: - str(value) - if self._check_allowed_value(value): - return True - except ValueError: - return False - - def to_type(self, value): - return str(value) - - -class PropertyTypeStringList(PropertyTypeString): - def check_type(self, value): - try: - str(value) - except ValueError: - return False - if value in self.allowed_value: - return True - else: - return False - - def to_type(self, value): - return str(value) - - pass - - -class PropertyTypeList(PropertyType): - def check_type(self, value): - if isinstance(value, list): - return True - elif isinstance(value, basestring): - try: - ast.literal_eval(value) - return True - except SyntaxError: - try: - value.split() - return True - except AttributeError: - return False - return False - - def to_type(self, value): - if isinstance(value, list): - return value - elif isinstance(value, basestring): - try: - return ast.literal_eval(value) - except SyntaxError: - return value.split() - else: - return [] - - -class PropertyTypeRange(PropertyType): - def check_type(self, value): - if isinstance(value, dict): - if reduce((lambda a, b: a in value), [True, 'start', 'stop']): - if abs(value['start'] - value['stop']) > 0: - return True - elif isinstance(value, list): - if len(value) == 2: - if abs(value[0] - value[1]) > 0: - return True - elif isinstance(value, basestring): - try: - clist = ast.literal_eval(value) - if abs(clist[0] - clist[1]) > 0: - return True - except SyntaxError: - clist = value.split() - if abs(clist[0] - clist[1]) > 0: - return True - return False - - def to_type(self, value): - if isinstance(value, list): - return value - elif isinstance(value, dict): - return [value['start'], value['stop']] - elif isinstance(value, basestring): - try: - return ast.literal_eval(value) - except SyntaxError: - return value.split() - - -class PropertyTypeRangeSampled(PropertyTypeRange): - def check_type(self, value): - if isinstance(value, dict): - if reduce((lambda a, b: a in value), - [True, 'start', 'stop', 'num']): - if abs(value['start'] - value['stop']) > 0: - return True - elif isinstance(value, list): - if len(value) == 3: - if abs(value[0] - value[1]) > 0: - return True - elif isinstance(value, basestring): - try: - clist = ast.literal_eval(value) - if abs(clist[0] - clist[1]) > 0: - return True - except SyntaxError: - clist = value.split() - if abs(clist[0] - clist[1]) > 0: - return True - return False - - def to_type(self, value): - if isinstance(value, list): - return value - elif isinstance(value, dict): - return [value['start'], value['stop'], value['num']] - elif isinstance(value, basestring): - try: - return ast.literal_eval(value) - except SyntaxError: - return value.split() - - -class PropertyTypeAbundances(PropertyType): - elements = dict([(x, y.lower()) for (x, y) in atomic_symbols_data]) - - def check_type(self, _value): - if isinstance(_value, dict): - value = dict((k.lower(), v) for k, v in _value.items()) - if set(value).issubset(set(self.elements.values())): - return True - else: - return False - else: - return False - - def to_type(self, _value): - if isinstance(_value, dict): - value = dict((k.lower(), v) for k, v in _value.items()) - abundances = dict.fromkeys(self.elements.copy(), 0.0) - for k in value: - abundances[k] = value[k] - abundances = {k: v for k, v in abundances.items() if not (v == 0.)} - return abundances - else: - raise ConfigError - - -class PropertyTypeLegacyAbundances(PropertyType): - elements = dict([(x, y.lower()) for (x, y) in atomic_symbols_data]) - types = ['uniform'] - - def check_type(self, _value): - value = dict((k.lower(), v) for k, v in _value.items()) - if 'type' in value: - if value['type'] in self.types: - tmp = value.copy() - tmp.pop('type', None) - if set(tmp).issubset(set(self.elements.values())): - return True - else: - return False - return False - - def to_type(self, _value): - if isinstance(_value, dict): - value = dict((k.lower(), v) for k, v in _value.items()) - abundances = dict.fromkeys(self.elements.copy(), 0.0) - for k in value: - abundances[k] = value[k] - abundances['type'] = value['type'] - return abundances - else: - raise ConfigError - - -class DefaultParser(object): - """ - Creates a new property object for the given config level + Extend a `jsonschema.IValidator` to also set default + values on properties. By default jsonschema ignores + default values. Parameters ---------- - default_dict: dict - default configuration + validator_class: + The `jsonschema.IValidator` class to extend + Returns + ------- + The extended `jsonschema.IValidator` """ - - __check = {} - __convert = {} - __list_of_leaf_types = [] - __types = {} - - def __init__(self, default_dict, item_path=None): - - self.__item_path = item_path - #create property type dict - self.__types['arbitrary'] = PropertyType - - self.__types['int'] = PropertyTypeInt - self.__register_leaf('int') - - self.__types['float'] = PropertyTypeFloat - self.__register_leaf('float') - - self.__types['quantity'] = PropertyTypeQuantity - self.__register_leaf('quantity') - - self.__types['quantity_range'] = PropertyTypeQuantityRange - self.__register_leaf('quantity_range') - - self.__types['quantity_range_sampled'] = PropertyTypeQuantityRangeSampled - self.__register_leaf('quantity_range_sampled') - - self.__types['string'] = PropertyTypeString - self.__register_leaf('string') - - self.__types['range'] = PropertyTypeRange - self.__register_leaf('range') - - self.__types['range_sampled'] = PropertyTypeRangeSampled - self.__register_leaf('range_sampled') - - self.__types['list'] = PropertyTypeList - self.__register_leaf('list') - - self.__types['container-declaration'] = PropertyTypeContainer - self.__register_leaf('container-declaration') - - self.__types['container-property'] = PropertyTypeContainer - self.__register_leaf('container-property') - - self.__types['abundance_set'] = PropertyTypeAbundances - self.__register_leaf('abundance_set') - - self.__types['legacy-abundances'] = PropertyTypeLegacyAbundances - self.__register_leaf('legacy-abundances') - - self.__types['bool'] = PropertyTypeBool - self.__register_leaf('bool') - - self.__mandatory = False - - self.__default_value = None - - self.__allowed_value = None - self.__allowed_type = None - self.__config_value = None - self.__path = None - - self.__container_dic = None - - self.__default_dict = default_dict - - if not 'property_type' in default_dict: - self.__property_type = 'arbitrary' - else: - self.__property_type = default_dict['property_type'] - if not self.__property_type in self.__types.keys(): - raise ValueError - self.__type = self.__types[self.__property_type]() - - if 'allowed_value' in default_dict: - self.__type.allowed_value = default_dict['allowed_value'] - - if 'allowed_type' in default_dict: - self.__type.allowed_type = default_dict['allowed_type'] - - if 'default' in default_dict: - if default_dict['default'] is not None and not default_dict['default'] in ['None', '']: - self.__type.default = default_dict['default'] - - if 'mandatory' in default_dict: - self.__type.mandatory = default_dict['mandatory'] - - self.is_leaf = self.__is_leaf(self.__property_type) - - def get_default(self): - """Returns the default value of this property, if specified. - - Returns - ------- - default value - """ - return self.__type.default - - def set_default(self, value): - """Set a new default value. - Parameters - ---------- - value: - new default value - """ - if value is not None: - if self.__type.check_type(value): - self.__type.default = value - else: - raise ConfigValueError(value, self.__type.allowed_value, self.get_path_in_dict(), - msg='Default value (%s) violates property constraint (%s). [%s]') - else: - self.__type.default = None - - @property - def is_mandatory(self): - """Returns True if this property is a mandatory. - Returns - ------- - bool - True if this property is a mandatory. - """ - return self.__type.mandatory - - @property - def has_default(self): - """Returns True if this property has a default value - Returns - ------- - bool - True if a default value was given. - """ - try: - if self.__type.default is not None: - return True - else: - return False - except NameError: - pass - - def set_path_in_dic(self, path): - """Set the path to this property in the config - Parameters - ---------- - path: list of str - Path in config dictionary. - """ - self.__path = path - - def get_path_in_dict(self): - """Returns the path of this property in the config - Returns - ------- - path: list of str - Path in config dictionary. - """ - return self.__path - - def set_config_value(self, value): - """Set a new config value. - Parameters - ---------- - value - New config value. - """ - self.__config_value = value - - def get_value(self): - """ - Returns the configuration value from the configuration. - If the value specified in the configuration is invalid - the default value is returned - Returns - ------- - value - ConfigurationValidator value. - """ - if self.__config_value is not None: - if self.__type.check_type(self.__config_value): - return self.__type.to_type(self.__config_value) - else: - raise ConfigValueError(self.__config_value, self.__type.allowed_value, self.get_path_in_dict()) - else: - if self.has_default: - logger.debug("Value <%s> specified in the configuration violates a constraint " - "given in the default configuration. Expected type: %s. Using the default value." % ( - str(self.__config_value), str(self.__property_type))) - return self.__type.default - else: - if self.is_mandatory: - raise ValueError('Value is mandatory, but no value was given in default configuration. [%s]' % str( - self.get_path_in_dict())) - else: - logger.debug("Value is not mandatory and is not specified in the configuration. [%s]" % ( - str(self.get_path_in_dict()))) - return None - - def is_container(self): - """ - Returns True if this property is of type container. - """ - return self.__is_container() - - def get_container_dic(self): - """ - If this property is a container it returns the corresponding - container dictionary - Returns - ------- - container dictionary: dict - Container dictionary - """ - if self.__is_container(): - return self.__container_dic - - @classmethod - def update_container_dic(cls, container_dic, current_entry_name): - if reduce(lambda a, b: a or b, - [(i in container_dic) for i in ['and', 'or']], True): - if 'or' in container_dic: - if current_entry_name in container_dic['or']: - container_dic['or'] = [] - return container_dic - if 'and' in container_dic: - if current_entry_name in container_dic['and']: - current_entry_name['and'].remove(current_entry_name) - return container_dic - - def __register_leaf(self, type_name): - if not type_name in self.__list_of_leaf_types: - self.__list_of_leaf_types.append(type_name) - - def __is_leaf(self, type_name): - return type_name in self.__list_of_leaf_types - - def __is_container(self): - if self.__property_type == 'container-property': - try: - self.__container_dic = self.__default_dict['type']['containers'] - return True - except KeyError: - return False - else: - return False - - # __check['container-property'] = __is_container - - def __is_container_declaration(self, value): - pass - - -class Container(DefaultParser): - def __init__(self, container_default_dict, container_dict, container_path=None): - """Creates a new container object. - Parameters - ---------- - container_default_dict: dict - Dictionary containing the default properties of the container. - container_dict: dict - Dictionary containing the configuration of the container. - """ - - self.__container_path = container_path - self.__type = None - self.__allowed_value = None - self.__allowed_type = None - self.__config_value = None - self.__path = None - self.__paper_abundances = False - self.__has_additional_items = False - - self.__property_type = 'container-property' - - self.__default_container = {} - self.__config_container = {} - - #check if it is a valid default container - if not 'type' in container_default_dict: - raise ValueError('No type specified in the default configuration. [%s]' % self.__container_path) - - #set allowed containers - try: - self.__allowed_container = container_default_dict['type']['containers'] - except KeyError: - raise ValueError('No container names specified in the default configuration. [%s]' % self.__container_path) - - #check if the specified container is given in the config - if container_dict is None: - logger.debug('%s specified in the default configuration but not present in the configuration. [%s]' - % (container_path[-1], container_path)) - self.__container_ob = None - self.__conf = None - return - #check if the specified container in the config is allowed - try: - if not container_dict['type'] in self.__allowed_container: - - raise ValueError( - 'Wrong container type in the configuration! The container is %s but only %s' - ' are allowed containers. [%s]' % (container_dict['type'], self.__allowed_container, - self.__container_path)) - else: - type_dict = container_dict['type'] - self.__type = container_dict['type'] - except KeyError: - raise ValueError('No type specified in the configuration. [%s]' % self.__container_path) - - #get selected container from conf - try: - self.__selected_container = container_dict['type'] - except KeyError: - self.__selected_container = None - raise ValueError('No container type specified in config') - - ####This is for the uniform abundances section in the paper. - if self.__type == 'uniform' and self.__container_path[-1] == 'abundances': - logger.debug('Using the legacy support for the uniform abundances section in the paper.') - self.__paper_abundances = True - cabundances_section = PropertyTypeAbundances() - tmp_container_dict = dict(container_dict) - tmp_container_dict.pop('type', None) - cabundances_section.check_type(tmp_container_dict) - tmp = cabundances_section.to_type(tmp_container_dict) - self.__default_container, self.__config_container = tmp, tmp - #### - else: - #look for necessary items - entry_name = '_' + self.__selected_container - try: - necessary_items = container_default_dict['type'][entry_name] - except KeyError: - raise ValueError('Container insufficient specified in the default configuration. The declaration of' - ' necessary items is missing. Add %s: ["your items"] to %s' % ( - necessary_items, self.__container_path)) - - #look for additional items - entry_name = '+' + self.__selected_container - self.__has_additional_items = False - try: - additional_items = container_default_dict['type'][entry_name] - self.__has_additional_items = True - except KeyError: - self.__has_additional_items = False - pass - - if not self.__paper_abundances: - for nitem in necessary_items: - if not nitem in container_dict: - raise ValueError('Entry %s is missing in configuration. [%s]' % (str(nitem), self.__container_path)) - else: - self.__default_container[nitem], self.__config_container[nitem] = self.parse_container_items( - container_default_dict[nitem], - container_dict[nitem], nitem, self.__container_path + [nitem]) - if self.__has_additional_items: - for aitem in additional_items: - try: - if aitem in container_dict: - self.__default_container[aitem], self.__config_container[aitem] = \ - self.parse_container_items(container_default_dict[aitem], - container_dict[aitem], aitem, - self.__container_path + [aitem]) - else: - self.__default_container[aitem], self.__config_container[aitem] = \ - self.parse_container_items(container_default_dict[aitem], - None, aitem, - self.__container_path + [aitem]) - - except KeyError: - pass - - #go through all items and create an conf object thereby check the conf - self.__container_ob = self.__default_container - if isinstance(self.__config_container, dict): - self.__conf = self.__config_container - else: - self.__conf = {"No Name": self.__config_container} - - def parse_container_items(self, top_default, top_config, item, full_path): - """Recursive parser for the container default dictionary and the container configuration - dictionary. - - Parameters - ---------- - top_default: dict - container default dictionary of the upper recursion level - top_config: dict - container configuration dictionary of the upper recursion level - level_name: str - name(key) of the of the upper recursion level - path: list of str - path in the nested container dictionary from the main level to the current level - Returns - ------- - If the current recursion level is not a leaf, the function returns a dictionary with itself for - each branch. If the current recursion level is a leaf the configured value and a configuration object is - returned - """ - - def reduce_list(a, b): - """ - removes items from list a which are in b. (o.B.d.A trivial) - Parameters - ---------- - a: list - minuend - b: list - subtrahend - Returns - ------- - a: list - difference - """ - for k in b: - a.remove(k) - return a - - def get_value_by_path(_dict, path): - """ - Value from a nested dictionary specified by its path. - Parameters - ---------- - dict: dict - nested source dictionary - path: list of str - path (composed of keys) in the dictionary - Returns - ------- - dict: str - value corresponding to the given path - """ - if _dict is None: - return None - else: - for key in path: - _dict = _dict[key] - return _dict - - path = reduce_list(list(full_path), self.__container_path + [item]) - tmp_conf_ob = {} - tmp_conf_val = {} - if isinstance(top_default, dict): - default_property = DefaultParser(top_default) - if default_property.is_container(): - container_conf = get_value_by_path(top_config, path) - ccontainer = Container(top_default, container_conf, container_path=full_path) - return ccontainer.get_container_ob(), ccontainer.get_container_conf() - elif not default_property.is_leaf: - for k, v in top_default.items(): - if top_config is None: - tmp_conf_ob[k], tmp_conf_val[k] = None, None - else: - self.__container_path = list(full_path) - tmp_conf_ob[k], tmp_conf_val[k] = self.parse_container_items(v, top_config[k], k, - full_path + [k]) - - return tmp_conf_ob, tmp_conf_val - else: - default_property.set_path_in_dic(path) - try: - conf_value = get_value_by_path(top_config, path) - except KeyError: - conf_value = None - - if conf_value is not None: - default_property.set_config_value(conf_value) - - return default_property, default_property.get_value() - - def get_container_ob(self): - """ - Return the container configuration object - Returns - ------- - self.__container_ob: DefaultParser - container configuration object - """ - return self.__container_ob - - def get_container_conf(self): - """ - Return the configuration - Returns - ------- - self.__container_ob: dict - container configuration - """ - if self.__conf is not None: - self.__conf['type'] = self.__type - return self.__conf - else: - return self.__conf - - -class ConfigurationValidator(object): - """ - An configuration validator parses an input dictionary with a default schema. - - Creates the configuration object. - Parameters - ---------- - configuration_definition: dict - Default configuration dictionary - input_configuration: dict - Configuration dictionary - """ - - def __init__(self, configuration_definition, input_configuration): - self.__conf_o = None - self.__conf_v = None - self.mandatories = {} - self.fulfilled = {} - self.__create_default_conf(configuration_definition) - self.__parse_config(configuration_definition, input_configuration) - self.__help_string = '' - self.__default_config = None - - @classmethod - def from_yaml(cls, fname_config, fname_default): - with open(fname_config) as f: - conff = f.read() - conf = yaml.safe_load(conff) - with open(fname_default) as f: - defaf = f.read() - defa = yaml.safe_load(defaf) - return cls(defa, conf) - - @staticmethod - def __mandatory_key(path): - """Return the key string for dictionary of mandatory entries - Parameters - ---------- - path: list of str - path (composed of keys) in the dictionary - Returns - ------- - mandatory_key: str - corresponding key - """ - return ':'.join(path) - - def register_mandatory(self, name, path): - """Register a mandatory entry - Parameters - ---------- - name: str - name of the mandatory entry to be registered - path: list of str - path (composed of keys) in the dictionary - """ - self.mandatories[self.__mandatory_key(path)] = name - - def deregister_mandatory(self, name, path): - """Register a deregistered mandatory entry - Parameters - ---------- - name: str - name of the mandatory entry to be deregistered - path: list of str - path (composed of keys) in the dictionary - """ - self.fulfilled[self.__mandatory_key(path)] = name - - def is_mandatory_fulfilled(self): - """ - Check if all mandatory entries are deregistered. - Returns - ------- - mandatory: bool - True if all mandatory entries are deregistered, otherwise False - """ - if len(set(self.mandatories.keys()) - set(self.fulfilled.keys())) <= 0: - return True - else: - return False - - def __parse_config(self, default_configuration, configuration): - """Parser for the default dictionary and the configuration dictionary. - Parameters - ------ - default_configuration: dict - Default configuration dictionary - configuration: dict - Configuration dictionary - """ - - def find_item(_dict, key): - """ - Returns the value for a specific key in a nested dictionary - note:: Deprecated, use get_property_by_path() - Parameters - ---------- - _dict: dict - nested dictionary - key: str - key in the nested dictionary - Returns - ------- - item: object - value corresponding to the specific key. - """ - if key in _dict: - return _dict[key] - for k, v in _dict.items(): - if isinstance(v, _dict): - item = find_item(v, key) - if item is not None: - return item - - def get_property_by_path(d, path): - """ Returns the value for a specific path(chain of keys) in a nested dictionary - Parameters - ---------- - dict: dict - nested dictionary - path: list of str - chain of keys as list - Returns - ------- - item: object - value in the nested dictionary at the specific path. - """ - if len(path) <= 0: - return d - else: - try: - v = d - for k in path: - v = v[k] - return v - except KeyError: - return None - - def recursive_parser(top_default, configuration, path): - """ - Recursive parser for the default dictionary. - Parameters - ---------- - top_default: dict - container default dictionary of the upper recursion level - configuration: dict - configuration dictionary - path: list of str - path in the nested container dictionary from the main level to the current level - Returns - ------- - If the current recursion level is not a leaf, the function returns a dictionary with itself for - each branch. If the current recursion level is a leaf, the configuration value and object are - returned - """ - tmp_conf_ob = {} - tmp_conf_val = {} - if isinstance(top_default, dict): - default_property = DefaultParser(top_default, item_path=path) - if default_property.is_mandatory: - self.register_mandatory(self, path) - self.deregister_mandatory(self, path) - - if default_property.is_container(): - container_conf = get_property_by_path(configuration, path) - #try: - ccontainer = Container(top_default, container_conf, container_path=path) - return ccontainer.get_container_ob(), ccontainer.get_container_conf() - #ToDo: remove general except!!! - #except: - # logger.warning('Container specified in default_configuration, but not used in the current\ - #configuration file. [%s]' % str(path)) - # return None, None - - elif not default_property.is_leaf: - no_default = self.__check_items_in_conf(get_property_by_path(configuration, path), top_default) - if len(no_default) > 0: - logger.debug('The items %s from the configuration are not specified in the default ' - 'configuration' % str(no_default)) - for k, v in top_default.items(): - tmp_conf_ob[k], tmp_conf_val[k] = recursive_parser(v, configuration, path + [k]) - return tmp_conf_ob, tmp_conf_val - - else: - default_property.set_path_in_dic(path) - try: - conf_value = get_property_by_path(configuration, path) - except KeyError: - conf_value = None - - if conf_value is not None: - default_property.set_config_value(conf_value) - return default_property, default_property.get_value() - - self.__conf_o, self.__conf_v = recursive_parser(default_configuration, configuration, []) - - @staticmethod - def __check_items_in_conf(config_dict, default_dict): - if isinstance(config_dict, dict) and len(config_dict) > 0: - return list(set(config_dict.keys()) - set(default_dict.keys())) - else: - return list(default_dict.keys()) - - def __create_default_conf(self, default_conf): - """Returns the default configuration values as dictionary. - Parameters - ---------- - default_conf: dict - default configuration dictionary - Returns - ------- - default configuration values - """ - - def recursive_default_parser(top_default, path): - """Recursive parser for the default dictionary. - Parameters - ---------- - top_default: dict - container default dictionary of the upper recursion level - path: list of str - path in the nested container dictionary from the main level to the current level - Returns - ------- - If the current recursion level is not a leaf, the function returns a dictionary with itself for - each branch. If the current recursion level is a leaf, the default configuration value is - returned - """ - tmp_default = {} - if isinstance(top_default, dict): - default_property = DefaultParser(top_default) - if not default_property.is_container(): - if not default_property.is_leaf: - for k, v in top_default.items(): - tmp_default[k] = recursive_default_parser(v, path + [k]) - return tmp_default - else: - default_property.set_path_in_dic(path) - if default_property.has_default: - return default_property.get_default() - else: - return None - - self.__default_config = recursive_default_parser(default_conf, []) - - def get_config(self): - """Returns the parsed configuration as dictionary. - Returns - ------- - configuration: dict - configuration values as dictionary - """ - return self.__conf_v - - def get_default_config(self): - """Returns the default configuration values as dictionary - Returns - ------- - default_configuration: dict - default configuration values as dictionary - """ - return self.__default_config - - def get_config_object(self): - """Returns the default configuration objects as dictionary - Returns - ------- - default_configuration: objects - default configuration objects as dictionary - """ - return self.__conf_o - - def get_help(self): - return pprint.pformat(self.get_default_config()) - - def __repr__(self): - return str(pprint.pformat(self.get_config())) - + validate_properties = validator_class.VALIDATORS['properties'] + + def set_defaults(validator, properties, instance, schema): + # This validator also checks if default values + # are of the correct type and properly sets default + # values on schemas that use the oneOf keyword + if not list( + validate_properties(validator, properties, instance, schema)): + for property, subschema in properties.iteritems(): + if 'default' in subschema: + instance.setdefault(property, subschema['default']) + + for error in validate_properties( + validator, properties, instance, schema, + ): + yield error + + return validators.extend( + validator_class, {'properties': set_defaults}, + ) + + +DefaultDraft4Validator = extend_with_default(Draft4Validator) + + +def _yaml_handler(path): + if not path.startswith('file://'): + raise Exception('Not a file URL: {}'.format(path)) + with open(path[len('file://'):]) as f: + return yaml.load(f, Loader=YAMLLoader) + + +def validate_dict(config_dict, schemapath=config_schema_file, + validator=DefaultDraft4Validator): + with open(schemapath) as f: + schema = yaml.load(f, Loader=YAMLLoader) + schemaurl = "file://" + schemapath + handlers = {'file': _yaml_handler} + resolver = RefResolver(schemaurl, schema, handlers=handlers) + validated_dict = deepcopy(config_dict) + validator(schema=schema, + types={'quantity': (Quantity,)}, + resolver=resolver + ).validate(validated_dict) + return validated_dict + + +def validate_yaml(configpath, schemapath=config_schema_file, + validator=DefaultDraft4Validator): + with open(configpath) as f: + config = yaml.load(f, Loader=YAMLLoader) + return validate_dict(config, schemapath, validator) diff --git a/tardis/io/schemas/base.yml b/tardis/io/schemas/base.yml new file mode 100644 index 00000000000..e61750b1994 --- /dev/null +++ b/tardis/io/schemas/base.yml @@ -0,0 +1,34 @@ +$schema: http://json-schema.org/draft-04/schema# +type: object +additionalProperties: false +properties: + tardis_config_version: + type: string + description: Version of the configuration file + supernova: + $ref: supernova.yml + atom_data: + type: string + description: path or filename to the Atomic Data HDF5 file + plasma: + $ref: plasma.yml + model: + $ref: model.yml + montecarlo: + $ref: montecarlo.yml + spectrum: + type: object + properties: + start: + type: quantity + stop: + type: quantity + num: + type: number + multipleOf: 1.0 + description: Final spectrum sampling +required: +- tardis_config_version +- atom_data +- spectrum + diff --git a/tardis/io/schemas/model.yml b/tardis/io/schemas/model.yml new file mode 100644 index 00000000000..a256d8ebdb7 --- /dev/null +++ b/tardis/io/schemas/model.yml @@ -0,0 +1,173 @@ +type: object +additionalProperties: false +properties: + structure: + oneOf: + - $ref: '#/definitions/structure/file' + - $ref: '#/definitions/structure/specific' + abundances: + oneOf: + - $ref: '#/definitions/abundances/file' + - $ref: '#/definitions/abundances/uniform' + +definitions: + density: + branch85_w7: + type: object + additionalProperties: false + properties: + type: + enum: + - branch85_w7 + w7_time_0: + type: quantity + default: 0.000231481 day + description: This needs no change - DO NOT TOUCH + w7_rho_0: + type: quantity + default: 3e29 g/cm^3 + description: This needs no change - DO NOT TOUCH + w7_v_0: + type: quantity + default: 1 km/s + description: This needs no change - DO NOT TOUCH + exponential: + type: object + additionalProperties: false + properties: + type: + enum: + - exponential + time_0: + type: quantity + description: Time at which the pure model densities are right + rho_0: + type: quantity + description: density at time_0 + v_0: + type: quantity + description: at what velocity the density rho_0 applies + exponent: + type: number + description: exponent for exponential density profile + required: + - time_0 + - rho_0 + - v_0 + - exponent + power_law: + type: object + additionalProperties: false + properties: + type: + enum: + - power_law + time_0: + type: quantity + description: Time at which the pure model densities are right + rho_0: + type: quantity + description: density at time_0 + v_0: + type: quantity + description: at what velocity the density rho_0 applies + exponent: + type: number + description: exponent for exponential density profile + required: + - time_0 + - rho_0 + - v_0 + - exponent + uniform: + type: object + additionalProperties: false + properties: + type: + enum: + - uniform + value: + type: quantity + description: value for uniform density + required: + - value + structure: + file: + type: object + additionalProperties: false + properties: + type: + enum: + - file + filename: + type: string + description: file name (with path) to structure model + filetype: + type: string + enum: + - simple_ascii + - artis + description: file type + v_inner_boundary: + type: quantity + default: 0 km/s + description: location of the inner boundary chosen from the model + v_outer_boundary: + type: quantity + default: inf km/s + description: location of the inner boundary chosen from the model + required: + - filename + - filetype + specific: + type: object + additionalProperties: false + properties: + type: + enum: + - specific + velocity: + type: object + properties: + start: + type: quantity + stop: + type: quantity + num: + type: number + multipleOf: 1.0 + description: description of the boundaries of the shells + density: + oneOf: + - $ref: '#/definitions/density/branch85_w7' + - $ref: '#/definitions/density/exponential' + - $ref: '#/definitions/density/power_law' + - $ref: '#/definitions/density/uniform' + required: + - velocity + - density + abundances: + file: + type: object + additionalProperties: false + properties: + type: + enum: + - file + filetype: + type: string + description: type of abundance file to read in + filename: + type: string + description: filename + required: + - filetype + - filename + uniform: + type: object + additionalProperties: true + properties: + type: + enum: + - uniform + diff --git a/tardis/io/schemas/montecarlo.yml b/tardis/io/schemas/montecarlo.yml new file mode 100644 index 00000000000..f7424c3e3e7 --- /dev/null +++ b/tardis/io/schemas/montecarlo.yml @@ -0,0 +1,228 @@ +type: object +additionalProperties: false +properties: + nthreads: + type: number + multipleOf: 1.0 + default: 1 + description: The number of OpenMP threads. + seed: + type: number + multipleOf: 1.0 + default: 23111963 + description: Seed for the random number generator + no_of_packets: + type: number + multipleOf: 1.0 + description: Seed for the random number generator + iterations: + type: number + multipleOf: 1.0 + description: Number of maximum iterations + black_body_sampling: + type: object + default: {} + properties: + start: + type: quantity + default: 50 angstrom + stop: + type: quantity + default: 200000 angstrom + num: + type: number + multipleOf: 1.0 + default: 1000000 + description: Sampling of the black-body for energy packet creation (giving maximum + and minimum packet frequency) + last_no_of_packets: + type: number + multipleOf: 1.0 + default: -1 + description: This can set the number of packets for the last run. If set negative + it will remain the same as all other runs. + no_of_virtual_packets: + type: number + multipleOf: 1.0 + default: 0 + description: Setting the number of virtual packets for the last iteration. + virtual_spectrum_range: + type: object + default: {} + properties: + start: + type: quantity + default: 50 angstrom + stop: + type: quantity + default: 250000 angstrom + num: + type: number + multipleOf: 1.0 + default: 1000000 + description: Limits of virtual packet spectrum (giving maximum and minimum packet + frequency) + enable_reflective_inner_boundary: + type: boolean + default: false + description: experimental feature to enable a reflective boundary. + inner_boundary_albedo: + type: number + default: 0.0 + description: albedo of the reflective boundary + convergence_strategy: + oneOf: + - $ref: '#/definitions/convergence_strategy/damped' + - $ref: '#/definitions/convergence_strategy/specific' + default: + type: 'damped' +required: +- no_of_packets +- iterations + +definitions: + convergence_strategy: + damped: + type: object + additionalProperties: false + properties: + type: + enum: + - damped + damping_constant: + type: number + default: 0.5 + description: damping constant + t_inner: + type: object + additionalProperties: false + properties: + damping_constant: + type: number + default: 0.5 + description: damping constant + threshold: + type: number + description: specifies the threshold that is taken as convergence (i.e. + 0.05 means that the value does not change more than 5%) + t_rad: + type: object + additionalProperties: false + properties: + damping_constant: + type: number + default: 0.5 + description: damping constant + threshold: + type: number + description: specifies the threshold that is taken as convergence (i.e. + 0.05 means that the value does not change more than 5%) + required: + - threshold + w: + type: object + additionalProperties: false + properties: + damping_constant: + type: number + default: 0.5 + description: damping constant + threshold: + type: number + description: specifies the threshold that is taken as convergence (i.e. + 0.05 means that the value does not change more than 5%) + required: + - threshold + lock_t_inner_cycles: + type: number + multipleOf: 1.0 + default: 1 + description: The number of cycles to lock the update of the inner boundary + temperature. This process helps with convergence. The default is to switch + it off (1 cycle) + t_inner_update_exponent: + type: number + default: -0.5 + description: L=4*pi*r**2*T^y + specific: + type: object + additionalProperties: false + properties: + type: + enum: + - specific + threshold: + type: number + description: specifies the threshold that is taken as convergence (i.e. + 0.05 means that the value does not change more than 5%) + fraction: + type: number + default: 0.8 + description: the fraction of shells that have to converge to the given convergence + threshold. For example, 0.8 means that 80% of shells have to converge + to the threshold that convergence is established + hold_iterations: + type: number + multipleOf: 1.0 + default: 3 + description: the number of iterations that the convergence criteria need + to be fulfilled before TARDIS accepts the simulation as converged + t_inner: + type: object + additionalProperties: false + properties: + damping_constant: + type: number + default: 0.5 + description: damping constant + threshold: + type: number + description: specifies the threshold that is taken as convergence (i.e. + 0.05 means that the value does not change more than 5%) + t_rad: + type: object + additionalProperties: false + properties: + damping_constant: + type: number + default: 0.5 + description: damping constant + threshold: + type: number + description: specifies the threshold that is taken as convergence (i.e. + 0.05 means that the value does not change more than 5%) + required: + - threshold + w: + type: object + additionalProperties: false + properties: + damping_constant: + type: number + default: 0.5 + description: damping constant + threshold: + type: number + description: specifies the threshold that is taken as convergence (i.e. + 0.05 means that the value does not change more than 5%) + required: + - threshold + lock_t_inner_cycles: + type: number + multipleOf: 1.0 + default: 1 + description: The number of cycles to lock the update of the inner boundary + temperature. This process helps with convergence. The default is to switch + it off (1 cycle) + damping_constant: + type: number + default: 0.5 + description: damping constant + t_inner_update_exponent: + type: number + default: -0.5 + description: L=4*pi*r**2*T^y + required: + - threshold + - fraction + - hold_iterations diff --git a/tardis/io/schemas/plasma.yml b/tardis/io/schemas/plasma.yml new file mode 100644 index 00000000000..ac78f07d4bc --- /dev/null +++ b/tardis/io/schemas/plasma.yml @@ -0,0 +1,89 @@ +type: object +additionalProperties: false +properties: + initial_t_inner: + type: quantity + default: -1 K + description: initial temperature of the inner boundary black body. If set to -1 + K will result in automatic calculation of boundary + initial_t_rad: + type: quantity + default: -1 K + description: initial radiative temperature in all cells. If set to -1 K will result + in automtatic calculation of the initial temperatures + disable_electron_scattering: + type: boolean + default: false + description: disable electron scattering process in montecarlo part - non-physical + only for tests + ionization: + type: string + enum: + - nebular + - lte + description: ionization treatment mode + excitation: + type: string + enum: + - lte + - dilute-lte + description: excitation treatment mode + radiative_rates_type: + type: string + enum: + - dilute-blackbody + - detailed + - blackbody + description: radiative rates treatment mode + line_interaction_type: + type: string + enum: + - scatter + - downbranch + - macroatom + description: line interaction mode + w_epsilon: + type: number + default: 1e-10 + description: w to use when j_blues get numerically 0. - avoids numerical complications + delta_treatment: + type: number + description: In the saha calculation set delta equals to the number given in this + configuration item. if set to None (default), normal delta treatment (as described + in Mazzali & Lucy 1993) will be applied + nlte: + type: object + default: {} + additionalProperties: false + properties: + species: + type: array + default: [] + description: Species that are requested to be NLTE treated in the format ['Si + 2', 'Ca 1', etc.] + coronal_approximation: + type: boolean + default: false + description: set all jblues=0.0 + classical_nebular: + type: boolean + default: false + description: sets all beta_sobolevs to 1 + helium_treatment: + type: string + default: none + enum: + - none + - recomb-nlte + - numerical-nlte + description: none to treat He as the other elements. recomb-nlte to treat with + NLTE approximation. + heating_rate_data_file: + type: string + default: none + description: Path to file containing heating rate/light curve data. +required: +- ionization +- excitation +- radiative_rates_type +- line_interaction_type diff --git a/tardis/io/schemas/supernova.yml b/tardis/io/schemas/supernova.yml new file mode 100644 index 00000000000..f6b929e9106 --- /dev/null +++ b/tardis/io/schemas/supernova.yml @@ -0,0 +1,24 @@ +type: object +additionalProperties: false +properties: + luminosity_requested: + type: quantity + default: 1 solLum + description: requested output luminosity for simulation + time_explosion: + type: quantity + description: time since explosion + distance: + type: quantity + description: distance to supernova + luminosity_wavelength_start: + type: quantity + default: 0 angstrom + description: start of the integral needed for getting the luminosity right + luminosity_wavelength_end: + type: quantity + default: inf angstrom + description: start of the integral needed for getting the luminosity right +required: +- luminosity_requested +- time_explosion diff --git a/tardis/io/tests/test_config_reader.py b/tardis/io/tests/test_config_reader.py index 6f04b3f19ff..073f8b39a68 100644 --- a/tardis/io/tests/test_config_reader.py +++ b/tardis/io/tests/test_config_reader.py @@ -367,15 +367,5 @@ def test_absolute_relative_config_paths(monkeypatch, cwd): config_abs = config_reader.Configuration.from_yaml(os.path.abspath(data_path(filename)), test_parser=True) -def test_custom_yaml_loader(): - filename = 'tardis_configv1_density_exponential_test.yml' - default_conf = config_reader.Configuration.from_yaml(data_path(filename), test_parser=True, - loader=yaml.Loader) - custom_conf = config_reader.Configuration.from_yaml(data_path(filename), test_parser=True, - loader=YAMLLoader) - assert len(default_conf) == len(custom_conf) - assert set(default_conf.keys()) == set(custom_conf.keys()) - assert util.check_equality(default_conf, custom_conf) - #write tests for inner and outer boundary indices diff --git a/tardis/io/tests/test_config_validator.py b/tardis/io/tests/test_config_validator.py index 0508e651c1b..ee96b004089 100644 --- a/tardis/io/tests/test_config_validator.py +++ b/tardis/io/tests/test_config_validator.py @@ -1,322 +1,2 @@ -import os -from glob import glob - -from astropy import units as u -import pytest - -from tardis.io.config_validator import DefaultParser, ConfigurationValidator, ConfigValueError - - -existing_configs = glob(os.path.join('docs', 'examples', '*.yml')) -existing_configs += glob(os.path.join('tardis', 'io', 'tests', 'data', '*.yml')) -config_definition = os.path.join('tardis', 'data', 'tardis_config_definition.yml') - -test_config_definition = os.path.join('tardis', 'io', 'tests', 'data', 'conf_def.yml') -test_config = os.path.join('tardis', 'io', 'tests', 'data', 'conf_tes.yml') -existing_configs.remove(test_config_definition) -existing_configs.remove(test_config) - - -@pytest.mark.parametrize("config_filename", existing_configs) -def test_configread(config_filename): - config = ConfigurationValidator.from_yaml(config_filename, config_definition) - - -def test_configread_test_config(): - config = ConfigurationValidator.from_yaml(test_config, test_config_definition) - - -def default_parser_helper(test_dic, default, wdefault, value, wvalue, container, mandatory, return_default=None, - return_value=None, value_as_string=True): - test_ob = DefaultParser(test_dic) - - if return_value is None: - return_value = value - - if return_default is None: - return_default = default - - if not default == None: - dhelper = True - else: - dhelper = False - - assert test_ob.has_default == dhelper - assert test_ob.get_default() == return_default - assert test_ob.is_leaf - assert test_ob.is_container() == container - assert test_ob.is_mandatory == mandatory - - #set good default - test_ob.set_default(default) - - assert test_ob.get_default() == return_default - #set bad default - if wdefault is not None: - with pytest.raises(ValueError): - test_ob.set_default(wdefault) - - assert test_ob.get_value() == return_default - - #set good value - test_ob.set_config_value(value) - - assert test_ob.get_value() == return_value - - #set bad value - if wvalue is not None: - with pytest.raises(ConfigValueError): - test_ob.set_config_value(wvalue) - test_ob.get_value() - - return 0 - - -def test_default_parser_float(): - example_dic = {'default': 99.99, - 'help': 'float value for testing', - 'mandatory': True, - 'property_type': 'float'} - default = 99.99 - wdefault = "xx" - value = 11.12 - wvalue = "yy" - container = False - mandatory = True - ex = default_parser_helper(example_dic, default, wdefault, value, wvalue, container, mandatory) - - -def test_default_parser_integer(): - example_dic = {'default': 99, - 'help': 'integer value for testing', - 'mandatory': True, - 'property_type': 'int'} - - default = 99 - wdefault = 9.15 - value = 11 - wvalue = 9.22 - container = False - mandatory = True - - ex = default_parser_helper(example_dic, default, wdefault, value, wvalue, container, mandatory) - - -def test_default_parser_quantity_log_lsun(): - example_dic = {'default': '10 erg/s', - 'help': 'quantity for testing', - 'mandatory': True, - 'property_type': 'quantity'} - - default = "10 erg/s" - return_default = 10 * u.erg / u.s - wdefault = "kl" - value = "9 log_lsun" - return_value = 3.8459999999999732e+42 * u.erg / u.s - wvalue = "yy" - container = False - mandatory = True - - ex = default_parser_helper(example_dic, default, wdefault, value, wvalue, container, mandatory, - return_default=return_default, return_value=return_value) - - - -def test_default_parser_quantity(): - example_dic = {'default': '99.99 cm', - 'help': 'quantity for testing', - 'mandatory': True, - 'property_type': 'quantity'} - - default = "99.99 cm" - return_default = 99.99 * u.cm - wdefault = "kl" - value = "11.12 m" - return_value = 11.12 * u.m - wvalue = "yy" - container = False - mandatory = True - - ex = default_parser_helper(example_dic, default, wdefault, value, wvalue, container, mandatory, - return_default=return_default, return_value=return_value) - - -def test_default_parser_quantity_range(): - example_dic = {'default': ['1 cm', '5 cm'], - 'help': 'quantity for testing', - 'mandatory': True, - 'property_type': 'quantity_range'} - - default = ['1.0 cm', '5 cm'] - return_default = [1.0 * u.cm, 5 * u.cm] - wdefault = "kl" - value = ['10 m', '50 cm'] - return_value = [10 * u.m, 50 * u.cm] - wvalue = "yy" - container = False - mandatory = True - - ex = default_parser_helper(example_dic, default, wdefault, value, wvalue, container, mandatory, - return_default=return_default, return_value=return_value) - - -def test_default_parser_quantity_range_old(): - example_dic = {'default': {'start': '1 cm', 'end': '5 cm'}, - 'help': 'quantity for testing', - 'mandatory': True, - 'property_type': 'quantity_range'} - - default = ['1.0 cm', '5 cm'] - return_default = [1.0 * u.cm, 5 * u.cm] - wdefault = "kl" - value = ['10 m', '50 cm'] - return_value = [10 * u.m, 50 * u.cm] - wvalue = "yy" - container = False - mandatory = True - - ex = default_parser_helper(example_dic, default, wdefault, value, wvalue, container, mandatory, - return_default=return_default, return_value=return_value) - - -def test_default_parser_quantity_range_sampeled(): - example_dic = {'default': ['1 cm', '5 cm', 10], - 'help': 'quantity for testing', - 'mandatory': True, - 'property_type': 'quantity_range_sampled'} - - default = ['1.0 cm', '5 cm', 10] - return_default = [1.0 * u.cm, 5 * u.cm, 10] - wdefault = "kl" - value = ['10 m', '50 cm', 10] - return_value = [10 * u.m, 50 * u.cm, 10] - wvalue = "yy" - container = False - mandatory = True - - ex = default_parser_helper(example_dic, default, wdefault, value, wvalue, container, mandatory, - return_default=return_default, return_value=return_value) - - -def test_default_parser_quantity_range_sampeled_old(): - example_dic = {'default': {'start': '1 cm', 'stop': '5 cm', 'num': 10}, - 'help': 'quantity for testing', - 'mandatory': True, - 'property_type': 'quantity_range_sampled'} - - default = ['1.0 cm', '5 cm', 10] - return_default = [1.0 * u.cm, 5 * u.cm, 10] - wdefault = "kl" - value = ['10 m', '50 cm', 10] - return_value = [10 * u.m, 50 * u.cm, 10] - wvalue = "yy" - container = False - mandatory = True - - ex = default_parser_helper(example_dic, default, wdefault, value, wvalue, container, mandatory, - return_default=return_default, return_value=return_value) - - -def test_default_parser_range(): - example_dic = {'default': [0, 10], - 'help': 'range for testing', - 'mandatory': False, - 'property_type': 'range'} - - default = [0, 10] - wdefault = 1 - value = [7, 8] - wvalue = 2 - container = False - mandatory = False - - ex = default_parser_helper(example_dic, default, wdefault, value, wvalue, container, mandatory) - - -def test_default_parser_range_old(): - example_dic = {'default': {'start': 0, 'stop': 10}, - 'help': 'range for testing', - 'mandatory': False, - 'property_type': 'range'} - - default = [0, 10] - wdefault = 1 - value = [7, 8] - wvalue = 2 - container = False - mandatory = False - - ex = default_parser_helper(example_dic, default, wdefault, value, wvalue, container, mandatory) - - -def test_default_parser_range_sampled(): - example_dic = {'default': [0, 10, 1], - 'help': 'range for testing', - 'mandatory': False, - 'property_type': 'range_sampled'} - - default = [0, 10, 1] - wdefault = [1, 3] - value = [1, 5, 1] - wvalue = [1, 1] - container = False - mandatory = False - - ex = default_parser_helper(example_dic, default, wdefault, value, wvalue, container, mandatory) - - -def test_default_parser_range_sampled(): - example_dic = {'default': {'start': 0, 'stop': 10, 'num': 1}, - 'help': 'range for testing', - 'mandatory': False, - 'property_type': 'range_sampled'} - - default = [0, 10, 1] - wdefault = [1, 3] - value = [1, 5, 1] - wvalue = [1, 1] - container = False - mandatory = False - - ex = default_parser_helper(example_dic, default, wdefault, value, wvalue, container, mandatory) - - -def test_default_parser_string(): - example_dic = {'default': 'DEFAULT', - 'help': 'string for testing', - 'mandatory': True, - 'property_type': 'string'} - - default = "DEFAULT" - wdefault = None - value = "blub" - wvalue = None - container = False - mandatory = True - - ex = default_parser_helper(example_dic, default, wdefault, value, wvalue, container, mandatory) - - -def test_property_type_abundances(): - example_dic = {'default': {'He': 0.4, 'Mg': 0.1, 'Pb': 0.5}, - 'help': 'quantity for testing', - 'mandatory': True, - 'property_type': 'abundance_set'} - - default = {'He': 0.4, 'Mg': 0.1, 'Pb': 0.5} - return_default = {'he': 0.4, 'mg': 0.1, 'pb': 0.5} - wdefault = "kl" - value = {'He': 0.4, 'Mg': 0.6} - return_value = {'he': 0.4, 'mg': 0.6} - wvalue = "yy" - container = False - mandatory = True - - ex = default_parser_helper(example_dic, default, wdefault, value, wvalue, container, mandatory, - return_default=return_default, return_value=return_value) - - - - - - +# TODO: Write tests for the new validator +pass diff --git a/tardis/io/tests/test_configuration_namespace.py b/tardis/io/tests/test_configuration_namespace.py index 19428dc2cc8..6101eb5cf19 100644 --- a/tardis/io/tests/test_configuration_namespace.py +++ b/tardis/io/tests/test_configuration_namespace.py @@ -1,5 +1,4 @@ from tardis.io.config_reader import ConfigurationNameSpace -from tardis.io.config_validator import ConfigurationValidator from astropy import units as u import os diff --git a/tardis/model.py b/tardis/model.py index 09027452e71..ef003fe35ac 100644 --- a/tardis/model.py +++ b/tardis/model.py @@ -107,7 +107,7 @@ def __init__(self, tardis_config): tardis_config.number_densities, tardis_config.atom_data, tardis_config.supernova.time_explosion.to('s').value, nlte_config=tardis_config.plasma.nlte, - delta_treatment=tardis_config.plasma.delta_treatment, + delta_treatment=tardis_config.plasma.get('delta_treatment', None), ionization_mode=tardis_config.plasma.ionization, excitation_mode=tardis_config.plasma.excitation, line_interaction_type=tardis_config.plasma.line_interaction_type, @@ -117,12 +117,13 @@ def __init__(self, tardis_config): v_inner=tardis_config.structure.v_inner, v_outer=tardis_config.structure.v_outer) + distance = tardis_config.supernova.get('distance', None) self.spectrum = TARDISSpectrum( - tardis_config.spectrum.frequency, tardis_config.supernova.distance) + tardis_config.spectrum.frequency, distance) self.spectrum_virtual = TARDISSpectrum( - tardis_config.spectrum.frequency, tardis_config.supernova.distance) + tardis_config.spectrum.frequency, distance) self.spectrum_reabsorbed = TARDISSpectrum( - tardis_config.spectrum.frequency, tardis_config.supernova.distance) + tardis_config.spectrum.frequency, distance) self.calculate_j_blues(init_detailed_j_blues=True) self.update_plasmas(initialize_nlte=True) diff --git a/tardis_ci/tardis_ci_env27.yml b/tardis_ci/tardis_ci_env27.yml index 5a5a6358f79..32210ff077b 100644 --- a/tardis_ci/tardis_ci_env27.yml +++ b/tardis_ci/tardis_ci_env27.yml @@ -9,6 +9,7 @@ dependencies: - matplotlib=1.4.3 - astropy=1.0.5 - PyYAML=3.11 +- jsonschema=2.5.1 - numexpr=2.4.4 - Cython=0.21 - networkx=1.10 diff --git a/tardis_ci/tardis_rtd_env27.yml b/tardis_ci/tardis_rtd_env27.yml index b68354aece8..dc483e6b230 100644 --- a/tardis_ci/tardis_rtd_env27.yml +++ b/tardis_ci/tardis_rtd_env27.yml @@ -9,6 +9,7 @@ dependencies: - matplotlib=1.4.3 - astropy=1.0.5 - PyYAML=3.11 +- jsonschema=2.5.1 - numexpr=2.4.4 - Cython=0.21 - networkx=1.10