Source code for fuzzy_dl_owl2.fuzzydl.util.config_reader

from __future__ import annotations

import configparser
import math
import os

from dotenv import dotenv_values, find_dotenv, load_dotenv

from fuzzy_dl_owl2.fuzzydl.util import constants


[docs] class ConfigReader: """ A centralized configuration manager for a reasoning engine, defining default parameters that control precision, optimization levels, blocking strategies, and the selection of the Mixed-Integer Linear Programming (MILP) solver. It allows users to customize the reasoner's behavior by loading settings from a configuration file (INI) or, when that file is missing or unspecified, from a ``.env`` file located in the current working directory. Specific values can be overridden via command-line arguments. When parameters are loaded, the manager automatically adjusts internal precision calculations and updates global constants within the application to match the capabilities of the selected solver provider. :param ANYWHERE_DOUBLE_BLOCKING: Determines whether the anywhere double blocking optimization is applied. :type ANYWHERE_DOUBLE_BLOCKING: bool :param ANYWHERE_SIMPLE_BLOCKING: Determines whether anywhere simple blocking is applied during reasoning. :type ANYWHERE_SIMPLE_BLOCKING: bool :param DEBUG_PRINT: Flag to enable or disable the printing of debug messages to the console. :type DEBUG_PRINT: bool :param EPSILON: Precision threshold defining the minimum degree of satisfaction required for a concept to be considered satisfied by an individual. :type EPSILON: float :param MAX_INDIVIDUALS: Defines the maximum number of new individuals that can be generated during reasoning. A negative value disables this limit, allowing unlimited creation. :type MAX_INDIVIDUALS: int :param NUMBER_DIGITS: Number of digits of precision, computed from the epsilon value to define the decimal places required for the reasoner's operations. :type NUMBER_DIGITS: int :param OPTIMIZATIONS: Level of optimizations applied. A value of 0 disables optimizations, while a positive value enables them. Default is 1. :type OPTIMIZATIONS: int :param RULE_ACYCLIC_TBOXES: Enables the rule acyclic TBox optimization. :type RULE_ACYCLIC_TBOXES: bool :param OWL_ANNOTATION_LABEL: The XML annotation label used to identify fuzzy logic constructs when creating or parsing Fuzzy OWL 2 ontologies. :type OWL_ANNOTATION_LABEL: str :param MILP_PROVIDER: Specifies the Mixed-Integer Linear Programming (MILP) solver backend used by the reasoner for optimization tasks, influencing internal numerical limits based on the selected provider. :type MILP_PROVIDER: constants.MILPProvider """ # Anywhere pairwise blocking applied. false disables anywhere double blocking; true enables anywher edouble blocking. ANYWHERE_DOUBLE_BLOCKING: bool = True # Anywhere simple blocking applied. false disables anywhere simple blocking; true enables anywhere simple blocking. ANYWHERE_SIMPLE_BLOCKING: bool = True # Debugging mode DEBUG_PRINT: bool = False # Precision of the reasoner EPSILON: float = 0.001 # Maximum number of new individuals that will be created MAX_INDIVIDUALS: int = -1 # Number of digits of precision NUMBER_DIGITS: int = 2 # Level of the optimizations applied. 0 disables optimizations; a positive value enables optimizations. OPTIMIZATIONS: int = 1 # Rule acyclic TBox optimization applied RULE_ACYCLIC_TBOXES: bool = True # XML OWL 2 annotation label used to create and parse Fuzzy OWL 2 ontologies OWL_ANNOTATION_LABEL: str = "fuzzyLabel" # MILP Solver provider used by the reasoner MILP_PROVIDER: constants.MILPProvider = constants.MILPProvider.GUROBI
[docs] @staticmethod def _read_ini(config_file: str) -> dict[str, str]: """ Reads an INI-style configuration file and returns the key/value pairs of its ``DEFAULT`` section as a plain dictionary. Parsing is delegated to :class:`configparser.ConfigParser`; if the file cannot be found or read, a ``FileNotFoundError`` is raised rather than silently returning an empty mapping. :param config_file: Path to the INI configuration file to read. :type config_file: str :raises FileNotFoundError: if the configuration file does not exist or cannot be read. :return: The ``DEFAULT`` section entries as a ``{key: value}`` mapping. :rtype: dict[str, str] """ config = configparser.ConfigParser() read_files = config.read(config_file) if not read_files: raise FileNotFoundError(f"Config file not found: {config_file}") return {k: v for k, v in config["DEFAULT"].items()}
[docs] @staticmethod def _read_env() -> dict[str, str] | None: """ Loads the default ``.env`` discovered by python-dotenv. python-dotenv's default ``find_dotenv()`` walks up from the *caller's file* (``config_reader.py``), not from the current working directory, so a ``.env`` placed in the user's working directory was previously invisible. Pass ``usecwd=True`` so discovery starts at ``os.getcwd()`` and walks up from there. :return: The file-only key/value mapping, or ``None`` when no ``.env`` file is discovered. :rtype: dict[str, str] | None """ dotenv_path = find_dotenv(usecwd=True) if not dotenv_path or not load_dotenv(dotenv_path, override=True): return None return {k: v for k, v in dotenv_values(dotenv_path).items() if v is not None}
[docs] @staticmethod def load_parameters(config_file: str | None, **kwargs: list[str]) -> None: """ Loads configuration settings and applies overrides from the provided argument list. When ``config_file`` is ``None`` or points to a non-existent path, the loader falls back to a ``.env`` file located in the current working directory (``./.env``). The ``.env`` file uses the standard ``KEY=VALUE`` format; keys are matched against the INI configuration keys in a case- and underscore-insensitive way, so either ``DEBUG_PRINT=True`` or ``debugPrint=True`` is accepted. Overrides are supplied as keyword arguments and applied on top of the values read from the INI/``.env`` source before case- and underscore-insensitive normalisation. :param config_file: Filesystem path to the INI configuration file. If ``None`` or missing, ``./.env`` is used as a fallback. :type config_file: str | None :param kwargs: A dictionary of key-value pairs to override configuration settings. :type kwargs: dict[str, typing.Any] :raises FileNotFoundError: if ``config_file`` is ``None`` or missing and no ``.env`` file can be discovered by python-dotenv. """ if config_file is not None and os.path.isfile(config_file): settings = ConfigReader._read_ini(config_file) else: settings = ConfigReader._read_env() if settings is None: raise FileNotFoundError( f"No configuration source found: config_file={config_file!r}, " "no .env discovered by python-dotenv" ) # kwargs are optional key/value override pairs. for key, value in kwargs.items(): settings[key] = value # Match keys case- and underscore-insensitively so INI keys # (``milpProvider``) and ``.env`` keys (``MILP_PROVIDER``) both resolve. settings = {k.replace("_", "").lower(): v for k, v in settings.items()} # Apply settings with appropriate type conversions and defaults. debug_print = settings.get("debugprint", ConfigReader.DEBUG_PRINT) ConfigReader.DEBUG_PRINT = ( debug_print if isinstance(debug_print, bool) else str(debug_print).strip().lower() in ("1", "true", "yes", "on") ) ConfigReader.EPSILON = float(settings.get("epsilon", ConfigReader.EPSILON)) ConfigReader.MAX_INDIVIDUALS = int( settings.get("maxindividuals", ConfigReader.MAX_INDIVIDUALS) ) ConfigReader.OWL_ANNOTATION_LABEL = settings.get( "owlannotationlabel", ConfigReader.OWL_ANNOTATION_LABEL ) ConfigReader.MILP_PROVIDER = constants.MILPProvider( str( settings.get("milpprovider", ConfigReader.MILP_PROVIDER.name) ).lower() ) # Compute the number of digits of precision based on the epsilon value and set global constants based on the selected MILP provider. ConfigReader.NUMBER_DIGITS = int( round(abs(math.log10(ConfigReader.EPSILON) - 1.0)) ) # Set global constants based on the selected MILP provider, adjusting MAXVAL accordingly to ensure compatibility with the solver's capabilities. if ConfigReader.MILP_PROVIDER in ( constants.MILPProvider.MIP, constants.MILPProvider.PULP, ): constants.MAXVAL = (1 << 31) - 1 elif ConfigReader.MILP_PROVIDER in ( constants.MILPProvider.PULP_GLPK, constants.MILPProvider.PULP_CPLEX, constants.MILPProvider.PULP_HIGHS, ): constants.MAXVAL = (1 << 28) - 1 constants.MAXVAL2 = constants.MAXVAL * 2 if ConfigReader.DEBUG_PRINT: print(f"Debugging mode = {ConfigReader.DEBUG_PRINT}")