easyvvuq.params_specification
Data structures to ensure consistency during serialization for databases.
1"""Data structures to ensure consistency during serialization for databases. 2 3""" 4import logging 5import cerberus 6import json 7import numpy 8from .utils.discrete_validation import is_integer_valued, convert_to_integer 9 10__copyright__ = """ 11 12 Copyright 2018 Robin A. Richardson, David W. Wright 13 14 This file is part of EasyVVUQ 15 16 EasyVVUQ is free software: you can redistribute it and/or modify 17 it under the terms of the Lesser GNU General Public License as published by 18 the Free Software Foundation, either version 3 of the License, or 19 (at your option) any later version. 20 21 EasyVVUQ is distributed in the hope that it will be useful, 22 but WITHOUT ANY WARRANTY; without even the implied warranty of 23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 Lesser GNU General Public License for more details. 25 26 You should have received a copy of the Lesser GNU General Public License 27 along with this program. If not, see <https://www.gnu.org/licenses/>. 28 29""" 30__license__ = "LGPL" 31 32logger = logging.getLogger(__name__) 33 34 35class EasyVVUQValidator(cerberus.Validator): 36 def __init__(self, *args, **kwargs): 37 super(EasyVVUQValidator, self).__init__(*args, **kwargs) 38 39 # Add numpy.int64 as an acceptable 'integer' type 40 integer_type = cerberus.TypeDefinition('integer', (int, numpy.int64), ()) 41 cerberus.Validator.types_mapping['integer'] = integer_type 42 43 # Add 'fixture' type (for now, it's expected just to be a string) 44 fixture_type = cerberus.TypeDefinition('fixture', (str), ()) 45 cerberus.Validator.types_mapping['fixture'] = fixture_type 46 47 def _validate_type_integer(self, value): 48 """ 49 Enhanced integer validation that handles discrete distributions. 50 51 This method allows float values that represent integers (e.g., 2.0) 52 to pass validation for integer parameters. This is necessary because 53 chaospy returns float arrays for discrete distributions when mixed 54 with continuous distributions. 55 """ 56 # First check if it's already an integer type 57 if isinstance(value, (int, numpy.int64)): 58 return True 59 60 # Check if it's a float that represents an integer 61 if is_integer_valued(value): 62 return True 63 64 # Fall back to standard validation 65 return super()._validate_type_integer(value) 66 67 68class ParamsSpecification: 69 70 def __init__(self, params, appname=None): 71 72 if not isinstance(params, dict): 73 msg = "params must be of type 'dict'" 74 logger.error(msg) 75 raise Exception(msg) 76 77 if not params: 78 msg = ("params must not be empty. At least one parameter " 79 "should be specified.") 80 logger.error(msg) 81 raise Exception(msg) 82 83 # Check each param has a dict as a value, and that dict has a "default" defined 84 for param_key, param_def in params.items(): 85 if not isinstance(param_def, dict): 86 msg = f"Entry for param '{param_key}' must be a dictionary" 87 logger.error(msg) 88 raise Exception(msg) 89 if "default" not in param_def: 90 msg = ( 91 f"Entry for param '{param_key}' must be a dictionary" 92 f"defining a 'default' value for this parameter." 93 ) 94 logger.error(msg) 95 raise Exception(msg) 96 97 self.params_dict = params 98 self.appname = appname 99 100 # Create a validator for the schema defined by params_dict 101 self.cerberus_validator = EasyVVUQValidator(self.params_dict) 102 103 def process_run(self, new_run, verify=True): 104 # If necessary parameter names are missing, fill them in from the 105 # default values in params_info 106 for param in self.params_dict.keys(): 107 if param not in new_run.keys(): 108 default_val = self.params_dict[param]["default"] 109 new_run[param] = default_val 110 111 # Convert float values to integers for integer parameters when they represent integers 112 # This handles the case where chaospy returns float arrays for discrete distributions 113 # Do this BEFORE validation to avoid type errors 114 for param_name, value in new_run.items(): 115 if param_name in self.params_dict: 116 param_def = self.params_dict[param_name] 117 if param_def.get('type') == 'integer' and is_integer_valued(value): 118 new_run[param_name] = convert_to_integer(value) 119 120 # Optionally verify that all params are known for this app, that the types are 121 # correct, params are within specified ranges etc. Uses cerberus for this. 122 if verify: 123 if not self.cerberus_validator.validate(new_run): 124 errors = self.cerberus_validator.errors 125 msg = ( 126 f"Error when verifying the following new run:\n" 127 f"{new_run}\n" 128 f"Identified errors were:\n" 129 f"{errors}\n") 130 131 errors_list = [error[0] for error in self.cerberus_validator.errors.values()] 132 if 'unknown field' in errors_list: 133 msg += ( 134 f"The allowed parameter names for this app are:\n" 135 f"{list(self.params_dict.keys())}") 136 137 logger.error(msg) 138 raise RuntimeError(msg) 139 140 return new_run 141 142 def serialize(self): 143 return json.dumps(self.params_dict) 144 145 @staticmethod 146 def deserialize(serialized_params): 147 return ParamsSpecification(json.loads(serialized_params))
36class EasyVVUQValidator(cerberus.Validator): 37 def __init__(self, *args, **kwargs): 38 super(EasyVVUQValidator, self).__init__(*args, **kwargs) 39 40 # Add numpy.int64 as an acceptable 'integer' type 41 integer_type = cerberus.TypeDefinition('integer', (int, numpy.int64), ()) 42 cerberus.Validator.types_mapping['integer'] = integer_type 43 44 # Add 'fixture' type (for now, it's expected just to be a string) 45 fixture_type = cerberus.TypeDefinition('fixture', (str), ()) 46 cerberus.Validator.types_mapping['fixture'] = fixture_type 47 48 def _validate_type_integer(self, value): 49 """ 50 Enhanced integer validation that handles discrete distributions. 51 52 This method allows float values that represent integers (e.g., 2.0) 53 to pass validation for integer parameters. This is necessary because 54 chaospy returns float arrays for discrete distributions when mixed 55 with continuous distributions. 56 """ 57 # First check if it's already an integer type 58 if isinstance(value, (int, numpy.int64)): 59 return True 60 61 # Check if it's a float that represents an integer 62 if is_integer_valued(value): 63 return True 64 65 # Fall back to standard validation 66 return super()._validate_type_integer(value)
Validator class. Normalizes and/or validates any mapping against a
validation-schema which is provided as an argument at class instantiation
or upon calling the ~cerberus.Validator.validate(),
~cerberus.Validator.validated() or
~cerberus.Validator.normalized() method. An instance itself is
callable and executes a validation.
All instantiation parameters are optional.
There are the introspective properties types, validators,
coercers, default_setters, rules,
normalization_rules and validation_rules.
The attributes reflecting the available rules are assembled considering
constraints that are defined in the docstrings of rules' methods and is
effectively used as validation schema for schema.
Parameters
- schema: See
~cerberus.Validator.schema. Defaults toNone. - ignore_none_values: See
~cerberus.Validator.ignore_none_values. Defaults toFalse. - allow_unknown: See
~cerberus.Validator.allow_unknown. Defaults toFalse. - require_all: See
~cerberus.Validator.require_all. Defaults toFalse. - purge_unknown: See
~cerberus.Validator.purge_unknown. Defaults to toFalse. - purge_readonly: Removes all fields that are defined as
readonlyin the normalization phase. - error_handler: The error handler that formats the result of
~cerberus.Validator.errors. When given as two-value tuple with an error-handler class and a dictionary, the latter is passed to the initialization of the error handler. Default:~cerberus.errors.BasicErrorHandler.
37 def __init__(self, *args, **kwargs): 38 super(EasyVVUQValidator, self).__init__(*args, **kwargs) 39 40 # Add numpy.int64 as an acceptable 'integer' type 41 integer_type = cerberus.TypeDefinition('integer', (int, numpy.int64), ()) 42 cerberus.Validator.types_mapping['integer'] = integer_type 43 44 # Add 'fixture' type (for now, it's expected just to be a string) 45 fixture_type = cerberus.TypeDefinition('fixture', (str), ()) 46 cerberus.Validator.types_mapping['fixture'] = fixture_type
The arguments will be treated as with this signature:
__init__(self, schema=None, ignore_none_values=False, allow_unknown=False, require_all=False, purge_unknown=False, purge_readonly=False, error_handler=errors.BasicErrorHandler)
69class ParamsSpecification: 70 71 def __init__(self, params, appname=None): 72 73 if not isinstance(params, dict): 74 msg = "params must be of type 'dict'" 75 logger.error(msg) 76 raise Exception(msg) 77 78 if not params: 79 msg = ("params must not be empty. At least one parameter " 80 "should be specified.") 81 logger.error(msg) 82 raise Exception(msg) 83 84 # Check each param has a dict as a value, and that dict has a "default" defined 85 for param_key, param_def in params.items(): 86 if not isinstance(param_def, dict): 87 msg = f"Entry for param '{param_key}' must be a dictionary" 88 logger.error(msg) 89 raise Exception(msg) 90 if "default" not in param_def: 91 msg = ( 92 f"Entry for param '{param_key}' must be a dictionary" 93 f"defining a 'default' value for this parameter." 94 ) 95 logger.error(msg) 96 raise Exception(msg) 97 98 self.params_dict = params 99 self.appname = appname 100 101 # Create a validator for the schema defined by params_dict 102 self.cerberus_validator = EasyVVUQValidator(self.params_dict) 103 104 def process_run(self, new_run, verify=True): 105 # If necessary parameter names are missing, fill them in from the 106 # default values in params_info 107 for param in self.params_dict.keys(): 108 if param not in new_run.keys(): 109 default_val = self.params_dict[param]["default"] 110 new_run[param] = default_val 111 112 # Convert float values to integers for integer parameters when they represent integers 113 # This handles the case where chaospy returns float arrays for discrete distributions 114 # Do this BEFORE validation to avoid type errors 115 for param_name, value in new_run.items(): 116 if param_name in self.params_dict: 117 param_def = self.params_dict[param_name] 118 if param_def.get('type') == 'integer' and is_integer_valued(value): 119 new_run[param_name] = convert_to_integer(value) 120 121 # Optionally verify that all params are known for this app, that the types are 122 # correct, params are within specified ranges etc. Uses cerberus for this. 123 if verify: 124 if not self.cerberus_validator.validate(new_run): 125 errors = self.cerberus_validator.errors 126 msg = ( 127 f"Error when verifying the following new run:\n" 128 f"{new_run}\n" 129 f"Identified errors were:\n" 130 f"{errors}\n") 131 132 errors_list = [error[0] for error in self.cerberus_validator.errors.values()] 133 if 'unknown field' in errors_list: 134 msg += ( 135 f"The allowed parameter names for this app are:\n" 136 f"{list(self.params_dict.keys())}") 137 138 logger.error(msg) 139 raise RuntimeError(msg) 140 141 return new_run 142 143 def serialize(self): 144 return json.dumps(self.params_dict) 145 146 @staticmethod 147 def deserialize(serialized_params): 148 return ParamsSpecification(json.loads(serialized_params))
71 def __init__(self, params, appname=None): 72 73 if not isinstance(params, dict): 74 msg = "params must be of type 'dict'" 75 logger.error(msg) 76 raise Exception(msg) 77 78 if not params: 79 msg = ("params must not be empty. At least one parameter " 80 "should be specified.") 81 logger.error(msg) 82 raise Exception(msg) 83 84 # Check each param has a dict as a value, and that dict has a "default" defined 85 for param_key, param_def in params.items(): 86 if not isinstance(param_def, dict): 87 msg = f"Entry for param '{param_key}' must be a dictionary" 88 logger.error(msg) 89 raise Exception(msg) 90 if "default" not in param_def: 91 msg = ( 92 f"Entry for param '{param_key}' must be a dictionary" 93 f"defining a 'default' value for this parameter." 94 ) 95 logger.error(msg) 96 raise Exception(msg) 97 98 self.params_dict = params 99 self.appname = appname 100 101 # Create a validator for the schema defined by params_dict 102 self.cerberus_validator = EasyVVUQValidator(self.params_dict)
104 def process_run(self, new_run, verify=True): 105 # If necessary parameter names are missing, fill them in from the 106 # default values in params_info 107 for param in self.params_dict.keys(): 108 if param not in new_run.keys(): 109 default_val = self.params_dict[param]["default"] 110 new_run[param] = default_val 111 112 # Convert float values to integers for integer parameters when they represent integers 113 # This handles the case where chaospy returns float arrays for discrete distributions 114 # Do this BEFORE validation to avoid type errors 115 for param_name, value in new_run.items(): 116 if param_name in self.params_dict: 117 param_def = self.params_dict[param_name] 118 if param_def.get('type') == 'integer' and is_integer_valued(value): 119 new_run[param_name] = convert_to_integer(value) 120 121 # Optionally verify that all params are known for this app, that the types are 122 # correct, params are within specified ranges etc. Uses cerberus for this. 123 if verify: 124 if not self.cerberus_validator.validate(new_run): 125 errors = self.cerberus_validator.errors 126 msg = ( 127 f"Error when verifying the following new run:\n" 128 f"{new_run}\n" 129 f"Identified errors were:\n" 130 f"{errors}\n") 131 132 errors_list = [error[0] for error in self.cerberus_validator.errors.values()] 133 if 'unknown field' in errors_list: 134 msg += ( 135 f"The allowed parameter names for this app are:\n" 136 f"{list(self.params_dict.keys())}") 137 138 logger.error(msg) 139 raise RuntimeError(msg) 140 141 return new_run