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))
logger = <Logger easyvvuq.params_specification (DEBUG)>
class EasyVVUQValidator(cerberus.validator.Validator):
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 to None.
  • ignore_none_values: See ~cerberus.Validator.ignore_none_values. Defaults to False.
  • allow_unknown: See ~cerberus.Validator.allow_unknown. Defaults to False.
  • require_all: See ~cerberus.Validator.require_all. Defaults to False.
  • purge_unknown: See ~cerberus.Validator.purge_unknown. Defaults to to False.
  • purge_readonly: Removes all fields that are defined as readonly in 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.
EasyVVUQValidator(*args, **kwargs)
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)

validation_rules = {'allof': {'type': 'list', 'logical': 'allof'}, 'allow_unknown': {'oneof': [{'type': 'boolean'}, {'type': ['dict', 'string'], 'check_with': 'bulk_schema'}]}, 'allowed': {'type': 'container'}, 'anyof': {'type': 'list', 'logical': 'anyof'}, 'check_with': {'oneof': [{'type': 'callable'}, {'type': 'list', 'schema': {'oneof': [{'type': 'callable'}, {'type': 'string', 'allowed': ()}]}}, {'type': 'string', 'allowed': ()}]}, 'contains': {'empty': False}, 'dependencies': {'type': ('dict', 'hashable', 'list'), 'check_with': 'dependencies'}, 'empty': {'type': 'boolean'}, 'excludes': {'type': ('hashable', 'list'), 'schema': {'type': 'hashable'}}, 'forbidden': {'type': 'list'}, 'items': {'type': 'list', 'check_with': 'items'}, 'keysrules': {'type': ['dict', 'string'], 'check_with': 'bulk_schema', 'forbidden': ['rename', 'rename_handler']}, 'max': {'nullable': False}, 'maxlength': {'type': 'integer'}, 'meta': {}, 'min': {'nullable': False}, 'minlength': {'type': 'integer'}, 'noneof': {'type': 'list', 'logical': 'noneof'}, 'nullable': {'type': 'boolean'}, 'oneof': {'type': 'list', 'logical': 'oneof'}, 'readonly': {'type': 'boolean'}, 'regex': {'type': 'string'}, 'require_all': {'type': 'boolean'}, 'required': {'type': 'boolean'}, 'schema': {'type': ['dict', 'string'], 'anyof': [{'check_with': 'schema'}, {'check_with': 'bulk_schema'}]}, 'type': {'type': ['string', 'list'], 'check_with': 'type'}, 'valuesrules': {'type': ['dict', 'string'], 'check_with': 'bulk_schema', 'forbidden': ['rename', 'rename_handler']}}
checkers = ()
coercers = ()
default_setters = ()
normalization_rules = {'coerce': {'oneof': [{'type': 'callable'}, {'type': 'list', 'schema': {'oneof': [{'type': 'callable'}, {'type': 'string', 'allowed': ()}]}}, {'type': 'string', 'allowed': ()}]}, 'default': {'nullable': True}, 'default_setter': {'oneof': [{'type': 'callable'}, {'type': 'string', 'allowed': ()}]}, 'purge_unknown': {'type': 'boolean'}, 'rename': {'type': 'hashable'}, 'rename_handler': {'oneof': [{'type': 'callable'}, {'type': 'list', 'schema': {'oneof': [{'type': 'callable'}, {'type': 'string', 'allowed': ()}]}}, {'type': 'string', 'allowed': ()}]}}
rules = {'allof': {'type': 'list', 'logical': 'allof'}, 'allow_unknown': {'oneof': [{'type': 'boolean'}, {'type': ['dict', 'string'], 'check_with': 'bulk_schema'}]}, 'allowed': {'type': 'container'}, 'anyof': {'type': 'list', 'logical': 'anyof'}, 'check_with': {'oneof': [{'type': 'callable'}, {'type': 'list', 'schema': {'oneof': [{'type': 'callable'}, {'type': 'string', 'allowed': ()}]}}, {'type': 'string', 'allowed': ()}]}, 'contains': {'empty': False}, 'dependencies': {'type': ('dict', 'hashable', 'list'), 'check_with': 'dependencies'}, 'empty': {'type': 'boolean'}, 'excludes': {'type': ('hashable', 'list'), 'schema': {'type': 'hashable'}}, 'forbidden': {'type': 'list'}, 'items': {'type': 'list', 'check_with': 'items'}, 'keysrules': {'type': ['dict', 'string'], 'check_with': 'bulk_schema', 'forbidden': ['rename', 'rename_handler']}, 'max': {'nullable': False}, 'maxlength': {'type': 'integer'}, 'meta': {}, 'min': {'nullable': False}, 'minlength': {'type': 'integer'}, 'noneof': {'type': 'list', 'logical': 'noneof'}, 'nullable': {'type': 'boolean'}, 'oneof': {'type': 'list', 'logical': 'oneof'}, 'readonly': {'type': 'boolean'}, 'regex': {'type': 'string'}, 'require_all': {'type': 'boolean'}, 'required': {'type': 'boolean'}, 'schema': {'type': ['dict', 'string'], 'anyof': [{'check_with': 'schema'}, {'check_with': 'bulk_schema'}]}, 'type': {'type': ['string', 'list'], 'check_with': 'type'}, 'valuesrules': {'type': ['dict', 'string'], 'check_with': 'bulk_schema', 'forbidden': ['rename', 'rename_handler']}, 'coerce': {'oneof': [{'type': 'callable'}, {'type': 'list', 'schema': {'oneof': [{'type': 'callable'}, {'type': 'string', 'allowed': ()}]}}, {'type': 'string', 'allowed': ()}]}, 'default': {'nullable': True}, 'default_setter': {'oneof': [{'type': 'callable'}, {'type': 'string', 'allowed': ()}]}, 'purge_unknown': {'type': 'boolean'}, 'rename': {'type': 'hashable'}, 'rename_handler': {'oneof': [{'type': 'callable'}, {'type': 'list', 'schema': {'oneof': [{'type': 'callable'}, {'type': 'string', 'allowed': ()}]}}, {'type': 'string', 'allowed': ()}]}}
class ParamsSpecification:
 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))
ParamsSpecification(params, appname=None)
 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)
params_dict
appname
cerberus_validator
def process_run(self, new_run, verify=True):
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
def serialize(self):
143    def serialize(self):
144        return json.dumps(self.params_dict)
@staticmethod
def deserialize(serialized_params):
146    @staticmethod
147    def deserialize(serialized_params):
148        return ParamsSpecification(json.loads(serialized_params))