Source code for mechanics_dsl.utils.config

"""
Configuration management for MechanicsDSL
"""

import logging
from typing import Any, Dict, Tuple

from .logging import logger

# ============================================================================
# PHYSICAL CONSTANTS
# ============================================================================

# Standard gravity (m/s²)
STANDARD_GRAVITY = 9.80665
DEFAULT_GRAVITY = 9.81

# Speed of light (m/s)
SPEED_OF_LIGHT = 299792458.0

# Planck constant (J·s)
PLANCK_CONSTANT = 6.62607015e-34
HBAR = 1.054571817e-34

# ============================================================================
# NUMERICAL CONSTANTS
# ============================================================================

# Default tolerances for numerical integration
DEFAULT_RTOL = 1e-6
DEFAULT_ATOL = 1e-8

# Energy conservation tolerance (relative)
ENERGY_TOLERANCE = 0.01

# Default number of simulation points
DEFAULT_NUM_POINTS = 1000

# Default simulation time step hints
DEFAULT_MAX_STEP_FRACTION = 0.01  # Max step as fraction of time span
DEFAULT_STIFFNESS_TEST_DURATION = 0.01

# Simplification timeout (seconds)
SIMPLIFICATION_TIMEOUT = 5.0

# Maximum parser errors before aborting
MAX_PARSER_ERRORS = 10

# Maximum number of iterations for solvers
MAX_SOLVER_ITERATIONS = 1000

# Infinity approximation for numerical comparisons
NUMERICAL_INFINITY = 1e10

# Near-zero threshold for singularity detection
SINGULARITY_THRESHOLD = 1e-12

# ============================================================================
# VISUALIZATION CONSTANTS
# ============================================================================

# Animation settings
DEFAULT_TRAIL_LENGTH = 150
DEFAULT_FPS = 30
ANIMATION_INTERVAL_MS = 33  # ~30 FPS
DEFAULT_DPI = 100
MIN_DPI = 10
MAX_DPI = 1000

# Trail appearance
TRAIL_ALPHA = 0.4

# Default color scheme (professional palette)
PRIMARY_COLOR = "#E63946"  # Red-orange
SECONDARY_COLOR = "#457B9D"  # Steel blue
TERTIARY_COLOR = "#F1FAEE"  # Off-white
ACCENT_COLOR = "#A8DADC"  # Light cyan
WARNING_COLOR = "#FFB703"  # Amber
SUCCESS_COLOR = "#2A9D8F"  # Teal

# Marker sizes
DEFAULT_MARKER_SIZE = 10
LARGE_MARKER_SIZE = 100

# ============================================================================
# FILE VALIDATION CONSTANTS
# ============================================================================

# Maximum file path length
MAX_PATH_LENGTH = 4096

# Maximum file size for loading (bytes) - 100MB
MAX_FILE_SIZE = 100 * 1024 * 1024

# ============================================================================
# CACHE CONSTANTS
# ============================================================================

# Default LRU cache settings
DEFAULT_CACHE_SIZE = 128
DEFAULT_CACHE_MEMORY_MB = 100.0

# ============================================================================
# LOGGING CONSTANTS
# ============================================================================

# Log level mappings
LOG_LEVELS = {
    "DEBUG": logging.DEBUG,
    "INFO": logging.INFO,
    "WARNING": logging.WARNING,
    "ERROR": logging.ERROR,
    "CRITICAL": logging.CRITICAL,
}


[docs] class Config: """ Global configuration for MechanicsDSL with validation. All configuration values are validated on assignment to ensure they are within reasonable bounds and of correct types. """ def __init__(self) -> None: """Initialize configuration with default values.""" self._enable_profiling: bool = False self._enable_debug_logging: bool = False self._simplification_timeout: float = SIMPLIFICATION_TIMEOUT self._max_parser_errors: int = MAX_PARSER_ERRORS self._default_rtol: float = DEFAULT_RTOL self._default_atol: float = DEFAULT_ATOL self._trail_length: int = DEFAULT_TRAIL_LENGTH self._animation_fps: int = DEFAULT_FPS self._save_intermediate_results: bool = False self._cache_symbolic_results: bool = True # v6.0 Advanced features self._enable_performance_monitoring: bool = True self._cache_max_size: int = 256 self._cache_max_memory_mb: float = 200.0 self._enable_adaptive_solver: bool = True self._enable_parallel_processing: bool = False self._max_workers: int = 4 self._enable_memory_monitoring: bool = True self._gc_threshold: Tuple[int, int, int] = (700, 10, 10) self._enable_type_checking: bool = True self._error_recovery_enabled: bool = True self._max_retry_attempts: int = 3 @property def enable_profiling(self) -> bool: """Whether to enable performance profiling.""" return self._enable_profiling @enable_profiling.setter def enable_profiling(self, value: bool) -> None: if not isinstance(value, bool): raise TypeError(f"enable_profiling must be bool, got {type(value).__name__}") self._enable_profiling = value @property def enable_debug_logging(self) -> bool: """Whether to enable debug-level logging.""" return self._enable_debug_logging @enable_debug_logging.setter def enable_debug_logging(self, value: bool) -> None: if not isinstance(value, bool): raise TypeError(f"enable_debug_logging must be bool, got {type(value).__name__}") self._enable_debug_logging = value @property def simplification_timeout(self) -> float: """Timeout for symbolic simplification operations in seconds.""" return self._simplification_timeout @simplification_timeout.setter def simplification_timeout(self, value: float) -> None: if not isinstance(value, (int, float)): raise TypeError(f"simplification_timeout must be numeric, got {type(value).__name__}") if value < 0: raise ValueError(f"simplification_timeout must be non-negative, got {value}") if value > 3600: raise ValueError(f"simplification_timeout too large (>{3600}s), got {value}") self._simplification_timeout = float(value) @property def max_parser_errors(self) -> int: """Maximum parser errors before giving up.""" return self._max_parser_errors @max_parser_errors.setter def max_parser_errors(self, value: int) -> None: if not isinstance(value, int): raise TypeError(f"max_parser_errors must be int, got {type(value).__name__}") if value < 1: raise ValueError(f"max_parser_errors must be at least 1, got {value}") if value > 1000: raise ValueError(f"max_parser_errors too large (>{1000}), got {value}") self._max_parser_errors = value @property def default_rtol(self) -> float: """Default relative tolerance for numerical integration.""" return self._default_rtol @default_rtol.setter def default_rtol(self, value: float) -> None: if not isinstance(value, (int, float)): raise TypeError(f"default_rtol must be numeric, got {type(value).__name__}") if value <= 0 or value >= 1: raise ValueError(f"default_rtol must be in (0, 1), got {value}") self._default_rtol = float(value) @property def default_atol(self) -> float: """Default absolute tolerance for numerical integration.""" return self._default_atol @default_atol.setter def default_atol(self, value: float) -> None: if not isinstance(value, (int, float)): raise TypeError(f"default_atol must be numeric, got {type(value).__name__}") if value <= 0: raise ValueError(f"default_atol must be positive, got {value}") self._default_atol = float(value) @property def trail_length(self) -> int: """Maximum length of animation trails.""" return self._trail_length @trail_length.setter def trail_length(self, value: int) -> None: if not isinstance(value, int): raise TypeError(f"trail_length must be int, got {type(value).__name__}") if value < 0: raise ValueError(f"trail_length must be non-negative, got {value}") if value > 100000: raise ValueError(f"trail_length too large (>{100000}), got {value}") self._trail_length = value @property def animation_fps(self) -> int: """Animation frames per second.""" return self._animation_fps @animation_fps.setter def animation_fps(self, value: int) -> None: if not isinstance(value, int): raise TypeError(f"animation_fps must be int, got {type(value).__name__}") if value < 1: raise ValueError(f"animation_fps must be at least 1, got {value}") if value > 120: raise ValueError(f"animation_fps too large (>{120}), got {value}") self._animation_fps = value @property def save_intermediate_results(self) -> bool: """Whether to save intermediate computation results.""" return self._save_intermediate_results @save_intermediate_results.setter def save_intermediate_results(self, value: bool) -> None: if not isinstance(value, bool): raise TypeError(f"save_intermediate_results must be bool, got {type(value).__name__}") self._save_intermediate_results = value @property def cache_symbolic_results(self) -> bool: """Whether to cache symbolic computation results.""" return self._cache_symbolic_results @cache_symbolic_results.setter def cache_symbolic_results(self, value: bool) -> None: if not isinstance(value, bool): raise TypeError(f"cache_symbolic_results must be bool, got {type(value).__name__}") self._cache_symbolic_results = value @property def enable_performance_monitoring(self) -> bool: """Whether to enable performance monitoring.""" return self._enable_performance_monitoring @enable_performance_monitoring.setter def enable_performance_monitoring(self, value: bool) -> None: if not isinstance(value, bool): raise TypeError( f"enable_performance_monitoring must be bool, got {type(value).__name__}" ) self._enable_performance_monitoring = value @property def enable_memory_monitoring(self) -> bool: """Whether to enable additional memory monitoring.""" return self._enable_memory_monitoring @enable_memory_monitoring.setter def enable_memory_monitoring(self, value: bool) -> None: if not isinstance(value, bool): raise TypeError(f"enable_memory_monitoring must be bool, got {type(value).__name__}") self._enable_memory_monitoring = value @property def cache_max_size(self) -> int: """Maximum cache size.""" return self._cache_max_size @cache_max_size.setter def cache_max_size(self, value: int) -> None: if not isinstance(value, int): raise TypeError(f"cache_max_size must be int, got {type(value).__name__}") if value < 1: raise ValueError(f"cache_max_size must be at least 1, got {value}") if value > 10000: raise ValueError(f"cache_max_size too large (>{10000}), got {value}") self._cache_max_size = value @property def cache_max_memory_mb(self) -> float: """Maximum cache memory in MB.""" return self._cache_max_memory_mb @cache_max_memory_mb.setter def cache_max_memory_mb(self, value: float) -> None: if not isinstance(value, (int, float)): raise TypeError(f"cache_max_memory_mb must be numeric, got {type(value).__name__}") if value <= 0: raise ValueError(f"cache_max_memory_mb must be positive, got {value}") if value > 10000: raise ValueError(f"cache_max_memory_mb too large (>{10000} MB), got {value}") self._cache_max_memory_mb = float(value) @property def enable_adaptive_solver(self) -> bool: """Whether to enable adaptive solver selection.""" return self._enable_adaptive_solver @enable_adaptive_solver.setter def enable_adaptive_solver(self, value: bool) -> None: if not isinstance(value, bool): raise TypeError(f"enable_adaptive_solver must be bool, got {type(value).__name__}") self._enable_adaptive_solver = value @property def error_recovery_enabled(self) -> bool: """Whether error recovery is enabled.""" return self._error_recovery_enabled @error_recovery_enabled.setter def error_recovery_enabled(self, value: bool) -> None: if not isinstance(value, bool): raise TypeError(f"error_recovery_enabled must be bool, got {type(value).__name__}") self._error_recovery_enabled = value
[docs] def to_dict(self) -> Dict[str, Any]: """ Convert configuration to dictionary. Returns: Dictionary containing all configuration values """ return { # Core settings "enable_profiling": self._enable_profiling, "enable_debug_logging": self._enable_debug_logging, "simplification_timeout": self._simplification_timeout, "max_parser_errors": self._max_parser_errors, "default_rtol": self._default_rtol, "default_atol": self._default_atol, "trail_length": self._trail_length, "animation_fps": self._animation_fps, "save_intermediate_results": self._save_intermediate_results, "cache_symbolic_results": self._cache_symbolic_results, # v6.0 Advanced features "enable_performance_monitoring": self._enable_performance_monitoring, "cache_max_size": self._cache_max_size, "cache_max_memory_mb": self._cache_max_memory_mb, "enable_adaptive_solver": self._enable_adaptive_solver, "enable_parallel_processing": self._enable_parallel_processing, "max_workers": self._max_workers, "enable_memory_monitoring": self._enable_memory_monitoring, "gc_threshold": self._gc_threshold, "enable_type_checking": self._enable_type_checking, "error_recovery_enabled": self._error_recovery_enabled, "max_retry_attempts": self._max_retry_attempts, }
[docs] def from_dict(self, data: Dict[str, Any]) -> None: """ Load configuration from dictionary with validation. Uses property setters to ensure all values are validated. Args: data: Dictionary containing configuration values Raises: TypeError: If data is not a dictionary ValueError: If any value is invalid """ if not isinstance(data, dict): raise TypeError(f"data must be dict, got {type(data).__name__}") # Map property names (some internal names differ from property names) property_mapping = { "gc_threshold": "_gc_threshold", "max_workers": "_max_workers", "enable_parallel_processing": "_enable_parallel_processing", "enable_type_checking": "_enable_type_checking", "max_retry_attempts": "_max_retry_attempts", } for key, value in data.items(): # Try to use property setter first (provides validation) if hasattr(self.__class__, key) and isinstance( getattr(self.__class__, key, None), property ): try: setattr(self, key, value) except (TypeError, ValueError) as e: logger.warning(f"Invalid value for config key '{key}': {e}") # Fallback to direct attribute setting for internal-only fields elif key in property_mapping: internal_name = property_mapping[key] setattr(self, internal_name, value) elif key.startswith("_") and hasattr(self, key): setattr(self, key, value) else: logger.warning(f"Unknown configuration key: {key}")
# Global config instance config = Config()