Source code for infomeasure.utils.config

"""Utility module for configuration settings."""

import logging

# Get the logger for this module with NullHandler
logging.getLogger("infomeasure").addHandler(logging.NullHandler())
logging.basicConfig(
    format="%(asctime)s | %(levelname)8s | %(filename)s:%(lineno)d | %(message)s",
    level=logging.INFO,
)
# set standard logger to 'infomeasure' logger
logger = logging.getLogger("infomeasure")

# Define a dictionary to map base values to their corresponding unit names
BASE_UNIT_MAP = {
    2: {
        "identifiers": ["bits", "bit", "shannons", "shannon"],
        "name": "bit/shannon",
        "description": "This base is best suited for binary information, "
        "which is fundamental in digital systems.",
    },
    "e": {
        "identifiers": ["nats", "nat", "nit", "nepit"],
        "name": "nat/nit/nepit",
        "description": "This base is best suited for "
        "continuous probability distributions.",
    },
    10: {
        "identifiers": ["hartleys", "hartley", "bans", "ban", "dits", "dit"],
        "name": "hartley/ban/dit",
        "description": "This base is best suited for decimal information, "
        "which is commonly used in human-centric measurements.",
    },
}


[docs] class Config: """Configuration settings for the package. This class provides configuration settings for the package. The settings are stored as class attributes and can be accessed and modified using the class methods. Default settings: - ``base``: "e" (nats) - ``statistical_test_method``: "permutation_test" - ``statistical_test_n_tests``: 200 Attributes ---------- _settings : dict A dictionary containing the configuration settings. """ __default_settings = { "base": { "value": "e", # 2: bits/shannon, e: nats, 10: hartleys/bans/dits "types": int | float, "explicitly_allowed": ["e"], }, "statistical_test_method": { "value": "permutation_test", "types": None, "explicitly_allowed": ["permutation_test", "bootstrap"], }, "statistical_test_n_tests": { "value": 200, "types": int, "explicitly_allowed": None, }, } _settings = {key: value["value"] for key, value in __default_settings.items()}
[docs] @classmethod def get(cls, key: str): """Get the value of a configuration setting. Parameters ---------- key : str The key of the configuration setting. Returns ------- Any The value of the configuration setting. """ return cls._settings[key]
[docs] @classmethod def set(cls, key: str, value): """Set the value of a configuration setting. Parameters ---------- key : str The key of the configuration setting. value : Any The value to set the configuration setting to. Raises ------ KeyError If the key is not recognised. TypeError If the value is not of the correct type. """ if key not in cls._settings: raise KeyError(f"Unknown configuration setting: {key}") if ( cls.__default_settings[key]["types"] is None or not isinstance(value, cls.__default_settings[key]["types"]) ) and ( "explicitly_allowed" not in cls.__default_settings[key] or value not in cls.__default_settings[key]["explicitly_allowed"] ): raise TypeError( f"Invalid value '{value}' ({type(value)}) for setting '{key}'. " f"Expected type: {cls.__default_settings[key]['types']}" + ( f" or one of {cls.__default_settings[key]['explicitly_allowed']}" if "explicitly_allowed" in cls.__default_settings[key] else "" ) ) cls._settings[key] = value
[docs] @classmethod def reset(cls): """Reset the configuration settings to the default values.""" cls._settings = { key: value["value"] for key, value in cls.__default_settings.items() }
[docs] @classmethod def set_logarithmic_unit(cls, unit: str): """Set the base for the logarithmic unit. The base determines the logarithmic unit used for entropy calculations: - 'bits' or 'shannons' (base 2) - 'nats' (base e) - 'hartleys', 'bans', or 'dits' (base 10) Alternatively, you can set the base directly using the 'base' key, via :meth:`set`. Parameters ---------- unit : str The logarithmic unit to set. Use 'bit(s)' or 'shannon(s)' for base 2, 'nat(s)' for base e, and 'hartley(s)', 'ban(s)', or 'dit(s)' for base 10. Raises ------ ValueError If the unit is not recognised. """ unit = unit.lower() for base, units in BASE_UNIT_MAP.items(): if unit in units["identifiers"]: cls.set("base", base) return raise ValueError(f"Unknown logarithmic unit: {unit}")
[docs] @classmethod def get_logarithmic_unit(cls) -> str: """Get the logarithmic unit for entropy calculations. Returns ------- str The logarithmic unit. """ for base, units in BASE_UNIT_MAP.items(): if cls.get("base") == base: return units["name"] return f"unknown (base {cls.get('base')})"
[docs] @classmethod def get_logarithmic_unit_description(cls) -> str: """Get the description of the logarithmic unit for entropy calculations. Returns ------- str The description of the logarithmic unit. Raises ------ ValueError If there is no description for the logarithmic unit. """ for base, units in BASE_UNIT_MAP.items(): if cls.get("base") == base: return units["description"] raise ValueError(f"No description for logarithmic unit: base {cls.get('base')}")
[docs] @staticmethod def set_log_level(level: int | str) -> None: """Set the logging level for the package. Parameters ---------- level : int | str The logging level. See the :mod:`logging` module for more information. Raises ------ ValueError If the level is not a valid logging level. """ # get logging representation of level level = level if isinstance(level, int) else getattr(logging, level.upper()) logger.setLevel(level)
logger.debug( "Using %s (base %s) for entropy calculations.\n%s\n" r"Use 'infomeasure.Config.set_logarithmic_unit(unit)' to change the unit or " r"'infomeasure.Config.set(base)' to set the base directly.", Config.get_logarithmic_unit(), Config.get("base"), Config.get_logarithmic_unit_description(), )