Skip to main content

scirs2_core/integration/
config.rs

1//! Unified Configuration System for SciRS2 Ecosystem
2//!
3//! This module provides a comprehensive configuration system for managing
4//! ecosystem-wide settings across all SciRS2 modules.
5//!
6//! # Features
7//!
8//! - **Hierarchical Configuration**: Module-specific configs with inheritance
9//! - **Type-Safe Settings**: Strongly typed configuration values
10//! - **Runtime Flexibility**: Dynamic configuration changes with validation
11//! - **Cross-Module Consistency**: Unified settings propagation
12
13use std::collections::HashMap;
14use std::sync::{Arc, RwLock};
15
16use crate::error::{CoreError, CoreResult, ErrorContext, ErrorLocation};
17
18/// Precision level for numerical computations
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
20pub enum Precision {
21    /// Half precision (16-bit floating point)
22    Half,
23    /// Single precision (32-bit floating point)
24    Single,
25    /// Double precision (64-bit floating point)
26    #[default]
27    Double,
28    /// Extended precision (80-bit or higher)
29    Extended,
30    /// Automatic selection based on operation
31    Auto,
32}
33
34impl Precision {
35    /// Get the number of bits for this precision level
36    #[must_use]
37    pub const fn bits(&self) -> usize {
38        match self {
39            Precision::Half => 16,
40            Precision::Single => 32,
41            Precision::Double => 64,
42            Precision::Extended => 80,
43            Precision::Auto => 64, // Default to double
44        }
45    }
46
47    /// Get the epsilon value for this precision
48    #[must_use]
49    pub fn epsilon(&self) -> f64 {
50        match self {
51            Precision::Half => 9.77e-4,      // ~2^-10
52            Precision::Single => 1.19e-7,    // ~2^-23
53            Precision::Double => 2.22e-16,   // ~2^-52
54            Precision::Extended => 1.08e-19, // ~2^-63
55            Precision::Auto => f64::EPSILON,
56        }
57    }
58}
59
60/// Logging level for diagnostics
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
62pub enum LogLevel {
63    /// No logging
64    Off,
65    /// Critical errors only
66    Error,
67    /// Warnings and errors
68    Warn,
69    /// Informational messages
70    #[default]
71    Info,
72    /// Detailed debugging information
73    Debug,
74    /// Very detailed tracing information
75    Trace,
76}
77
78impl LogLevel {
79    /// Check if this level should log at the given level
80    #[must_use]
81    pub const fn should_log(&self, level: LogLevel) -> bool {
82        (*self as u8) >= (level as u8)
83    }
84}
85
86/// Precision-related configuration
87#[derive(Debug, Clone, PartialEq)]
88pub struct PrecisionConfig {
89    /// Default precision level
90    pub default_precision: Precision,
91    /// Epsilon for floating-point comparisons
92    pub epsilon: f64,
93    /// Enable strict precision mode
94    pub strict_mode: bool,
95    /// Allow precision loss in conversions
96    pub allow_precision_loss: bool,
97    /// Maximum relative error allowed
98    pub max_relative_error: f64,
99}
100
101impl Default for PrecisionConfig {
102    fn default() -> Self {
103        Self {
104            default_precision: Precision::Double,
105            epsilon: 1e-10,
106            strict_mode: false,
107            allow_precision_loss: true,
108            max_relative_error: 1e-6,
109        }
110    }
111}
112
113/// Parallel processing configuration
114#[derive(Debug, Clone, PartialEq)]
115pub struct ParallelConfig {
116    /// Enable parallel processing
117    pub enabled: bool,
118    /// Number of threads to use (None = auto-detect)
119    pub num_threads: Option<usize>,
120    /// Minimum chunk size for parallel operations
121    pub min_chunk_size: usize,
122    /// Maximum number of parallel tasks
123    pub max_tasks: usize,
124    /// Enable work stealing
125    pub work_stealing: bool,
126    /// Thread priority (0-100)
127    pub thread_priority: u8,
128}
129
130impl Default for ParallelConfig {
131    fn default() -> Self {
132        Self {
133            enabled: true,
134            num_threads: None,
135            min_chunk_size: 1024,
136            max_tasks: 256,
137            work_stealing: true,
138            thread_priority: 50,
139        }
140    }
141}
142
143impl ParallelConfig {
144    /// Get the effective number of threads
145    #[must_use]
146    pub fn effective_threads(&self) -> usize {
147        self.num_threads.unwrap_or_else(|| {
148            std::thread::available_parallelism()
149                .map(|n| n.get())
150                .unwrap_or(4)
151        })
152    }
153}
154
155/// Memory management configuration
156#[derive(Debug, Clone, PartialEq)]
157pub struct MemoryConfig {
158    /// Maximum memory limit in bytes (None = unlimited)
159    pub max_memory: Option<usize>,
160    /// Enable memory pooling
161    pub enable_pooling: bool,
162    /// Pool block size
163    pub pool_block_size: usize,
164    /// Enable memory-mapped files for large data
165    pub enable_mmap: bool,
166    /// Threshold for using mmap (in bytes)
167    pub mmap_threshold: usize,
168    /// Enable memory usage tracking
169    pub track_usage: bool,
170    /// Cache size limit
171    pub cache_size: usize,
172}
173
174impl Default for MemoryConfig {
175    fn default() -> Self {
176        Self {
177            max_memory: None,
178            enable_pooling: true,
179            pool_block_size: 4096,
180            enable_mmap: true,
181            mmap_threshold: 64 * 1024 * 1024, // 64 MB
182            track_usage: false,
183            cache_size: 256 * 1024 * 1024, // 256 MB
184        }
185    }
186}
187
188/// Numeric computation configuration
189#[derive(Debug, Clone, PartialEq)]
190pub struct NumericConfig {
191    /// Maximum iterations for iterative algorithms
192    pub max_iterations: usize,
193    /// Default tolerance for convergence
194    pub tolerance: f64,
195    /// Enable overflow checking
196    pub check_overflow: bool,
197    /// Enable NaN/Inf detection
198    pub check_special_values: bool,
199    /// Default seed for random operations (None = random)
200    pub random_seed: Option<u64>,
201    /// Enable deterministic mode
202    pub deterministic: bool,
203}
204
205impl Default for NumericConfig {
206    fn default() -> Self {
207        Self {
208            max_iterations: 1000,
209            tolerance: 1e-8,
210            check_overflow: true,
211            check_special_values: true,
212            random_seed: None,
213            deterministic: false,
214        }
215    }
216}
217
218/// Diagnostics and debugging configuration
219#[derive(Debug, Clone, PartialEq)]
220pub struct DiagnosticsConfig {
221    /// Logging level
222    pub log_level: LogLevel,
223    /// Enable profiling
224    pub enable_profiling: bool,
225    /// Enable tracing
226    pub enable_tracing: bool,
227    /// Collect performance metrics
228    pub collect_metrics: bool,
229    /// Enable debug assertions
230    pub debug_assertions: bool,
231    /// Output format for diagnostics
232    pub output_format: DiagnosticsFormat,
233}
234
235impl Default for DiagnosticsConfig {
236    fn default() -> Self {
237        Self {
238            log_level: LogLevel::Info,
239            enable_profiling: false,
240            enable_tracing: false,
241            collect_metrics: false,
242            debug_assertions: cfg!(debug_assertions),
243            output_format: DiagnosticsFormat::Text,
244        }
245    }
246}
247
248/// Output format for diagnostics
249#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
250pub enum DiagnosticsFormat {
251    /// Plain text output
252    #[default]
253    Text,
254    /// JSON format
255    Json,
256    /// CSV format
257    Csv,
258    /// Binary format
259    Binary,
260}
261
262/// Module-specific configuration container
263#[derive(Debug, Clone, Default)]
264pub struct ModuleConfig {
265    /// Module name
266    pub name: String,
267    /// Custom key-value settings
268    pub settings: HashMap<String, ConfigValue>,
269    /// Whether this module is enabled
270    pub enabled: bool,
271}
272
273impl ModuleConfig {
274    /// Create a new module configuration
275    #[must_use]
276    pub fn new(name: impl Into<String>) -> Self {
277        Self {
278            name: name.into(),
279            settings: HashMap::new(),
280            enabled: true,
281        }
282    }
283
284    /// Set a configuration value
285    pub fn set(&mut self, key: impl Into<String>, value: impl Into<ConfigValue>) -> &mut Self {
286        self.settings.insert(key.into(), value.into());
287        self
288    }
289
290    /// Get a configuration value
291    #[must_use]
292    pub fn get(&self, key: &str) -> Option<&ConfigValue> {
293        self.settings.get(key)
294    }
295
296    /// Get a typed configuration value
297    pub fn get_typed<T: TryFrom<ConfigValue, Error = CoreError>>(
298        &self,
299        key: &str,
300    ) -> CoreResult<T> {
301        match self.settings.get(key) {
302            Some(value) => T::try_from(value.clone()),
303            None => Err(CoreError::ConfigError(
304                ErrorContext::new(format!(
305                    "Key '{key}' not found in module '{name}'",
306                    name = self.name
307                ))
308                .with_location(ErrorLocation::new(file!(), line!())),
309            )),
310        }
311    }
312
313    /// Enable the module
314    pub fn enable(&mut self) -> &mut Self {
315        self.enabled = true;
316        self
317    }
318
319    /// Disable the module
320    pub fn disable(&mut self) -> &mut Self {
321        self.enabled = false;
322        self
323    }
324}
325
326/// Configuration value type
327#[derive(Debug, Clone, PartialEq)]
328pub enum ConfigValue {
329    /// Boolean value
330    Bool(bool),
331    /// Integer value
332    Int(i64),
333    /// Unsigned integer value
334    UInt(u64),
335    /// Floating-point value
336    Float(f64),
337    /// String value
338    String(String),
339    /// List of values
340    List(Vec<ConfigValue>),
341    /// Map of values
342    Map(HashMap<String, ConfigValue>),
343}
344
345impl From<bool> for ConfigValue {
346    fn from(v: bool) -> Self {
347        ConfigValue::Bool(v)
348    }
349}
350
351impl From<i64> for ConfigValue {
352    fn from(v: i64) -> Self {
353        ConfigValue::Int(v)
354    }
355}
356
357impl From<i32> for ConfigValue {
358    fn from(v: i32) -> Self {
359        ConfigValue::Int(v as i64)
360    }
361}
362
363impl From<u64> for ConfigValue {
364    fn from(v: u64) -> Self {
365        ConfigValue::UInt(v)
366    }
367}
368
369impl From<usize> for ConfigValue {
370    fn from(v: usize) -> Self {
371        ConfigValue::UInt(v as u64)
372    }
373}
374
375impl From<f64> for ConfigValue {
376    fn from(v: f64) -> Self {
377        ConfigValue::Float(v)
378    }
379}
380
381impl From<f32> for ConfigValue {
382    fn from(v: f32) -> Self {
383        ConfigValue::Float(v as f64)
384    }
385}
386
387impl From<String> for ConfigValue {
388    fn from(v: String) -> Self {
389        ConfigValue::String(v)
390    }
391}
392
393impl From<&str> for ConfigValue {
394    fn from(v: &str) -> Self {
395        ConfigValue::String(v.to_string())
396    }
397}
398
399impl TryFrom<ConfigValue> for bool {
400    type Error = CoreError;
401
402    fn try_from(value: ConfigValue) -> Result<Self, Self::Error> {
403        match value {
404            ConfigValue::Bool(v) => Ok(v),
405            _ => Err(CoreError::ConfigError(
406                ErrorContext::new(format!("Expected bool, got {:?}", value))
407                    .with_location(ErrorLocation::new(file!(), line!())),
408            )),
409        }
410    }
411}
412
413impl TryFrom<ConfigValue> for i64 {
414    type Error = CoreError;
415
416    fn try_from(value: ConfigValue) -> Result<Self, Self::Error> {
417        match value {
418            ConfigValue::Int(v) => Ok(v),
419            ConfigValue::UInt(v) if v <= i64::MAX as u64 => Ok(v as i64),
420            _ => Err(CoreError::ConfigError(
421                ErrorContext::new(format!("Expected int, got {:?}", value))
422                    .with_location(ErrorLocation::new(file!(), line!())),
423            )),
424        }
425    }
426}
427
428impl TryFrom<ConfigValue> for u64 {
429    type Error = CoreError;
430
431    fn try_from(value: ConfigValue) -> Result<Self, Self::Error> {
432        match value {
433            ConfigValue::UInt(v) => Ok(v),
434            ConfigValue::Int(v) if v >= 0 => Ok(v as u64),
435            _ => Err(CoreError::ConfigError(
436                ErrorContext::new(format!("Expected uint, got {:?}", value))
437                    .with_location(ErrorLocation::new(file!(), line!())),
438            )),
439        }
440    }
441}
442
443impl TryFrom<ConfigValue> for f64 {
444    type Error = CoreError;
445
446    fn try_from(value: ConfigValue) -> Result<Self, Self::Error> {
447        match value {
448            ConfigValue::Float(v) => Ok(v),
449            ConfigValue::Int(v) => Ok(v as f64),
450            ConfigValue::UInt(v) => Ok(v as f64),
451            _ => Err(CoreError::ConfigError(
452                ErrorContext::new(format!("Expected float, got {:?}", value))
453                    .with_location(ErrorLocation::new(file!(), line!())),
454            )),
455        }
456    }
457}
458
459impl TryFrom<ConfigValue> for String {
460    type Error = CoreError;
461
462    fn try_from(value: ConfigValue) -> Result<Self, Self::Error> {
463        match value {
464            ConfigValue::String(v) => Ok(v),
465            _ => Err(CoreError::ConfigError(
466                ErrorContext::new(format!("Expected string, got {:?}", value))
467                    .with_location(ErrorLocation::new(file!(), line!())),
468            )),
469        }
470    }
471}
472
473/// Ecosystem-wide configuration
474#[derive(Debug, Clone)]
475pub struct EcosystemConfig {
476    /// Precision settings
477    pub precision: PrecisionConfig,
478    /// Parallel processing settings
479    pub parallel: ParallelConfig,
480    /// Memory management settings
481    pub memory: MemoryConfig,
482    /// Numeric computation settings
483    pub numeric: NumericConfig,
484    /// Diagnostics settings
485    pub diagnostics: DiagnosticsConfig,
486    /// Module-specific configurations
487    pub modules: HashMap<String, ModuleConfig>,
488    /// Global custom settings
489    pub custom: HashMap<String, ConfigValue>,
490    /// Configuration version
491    pub version: u32,
492}
493
494impl Default for EcosystemConfig {
495    fn default() -> Self {
496        Self {
497            precision: PrecisionConfig::default(),
498            parallel: ParallelConfig::default(),
499            memory: MemoryConfig::default(),
500            numeric: NumericConfig::default(),
501            diagnostics: DiagnosticsConfig::default(),
502            modules: HashMap::new(),
503            custom: HashMap::new(),
504            version: 1,
505        }
506    }
507}
508
509impl EcosystemConfig {
510    /// Create a new default configuration
511    #[must_use]
512    pub fn new() -> Self {
513        Self::default()
514    }
515
516    /// Create a builder for configuration
517    #[must_use]
518    pub fn builder() -> EcosystemConfigBuilder {
519        EcosystemConfigBuilder::new()
520    }
521
522    /// Get module configuration
523    #[must_use]
524    pub fn module(&self, name: &str) -> Option<&ModuleConfig> {
525        self.modules.get(name)
526    }
527
528    /// Get mutable module configuration
529    pub fn module_mut(&mut self, name: &str) -> Option<&mut ModuleConfig> {
530        self.modules.get_mut(name)
531    }
532
533    /// Register a module configuration
534    pub fn register_module(&mut self, config: ModuleConfig) -> &mut Self {
535        self.modules.insert(config.name.clone(), config);
536        self
537    }
538
539    /// Set a custom configuration value
540    pub fn set_custom(
541        &mut self,
542        key: impl Into<String>,
543        value: impl Into<ConfigValue>,
544    ) -> &mut Self {
545        self.custom.insert(key.into(), value.into());
546        self
547    }
548
549    /// Get a custom configuration value
550    #[must_use]
551    pub fn get_custom(&self, key: &str) -> Option<&ConfigValue> {
552        self.custom.get(key)
553    }
554
555    /// Validate the configuration
556    pub fn validate(&self) -> CoreResult<()> {
557        // Validate precision settings
558        if self.precision.epsilon <= 0.0 {
559            return Err(CoreError::ConfigError(
560                ErrorContext::new("Epsilon must be positive")
561                    .with_location(ErrorLocation::new(file!(), line!())),
562            ));
563        }
564
565        if self.precision.max_relative_error <= 0.0 {
566            return Err(CoreError::ConfigError(
567                ErrorContext::new("Max relative error must be positive")
568                    .with_location(ErrorLocation::new(file!(), line!())),
569            ));
570        }
571
572        // Validate parallel settings
573        if self.parallel.min_chunk_size == 0 {
574            return Err(CoreError::ConfigError(
575                ErrorContext::new("Minimum chunk size must be positive")
576                    .with_location(ErrorLocation::new(file!(), line!())),
577            ));
578        }
579
580        // Validate numeric settings
581        if self.numeric.max_iterations == 0 {
582            return Err(CoreError::ConfigError(
583                ErrorContext::new("Maximum iterations must be positive")
584                    .with_location(ErrorLocation::new(file!(), line!())),
585            ));
586        }
587
588        if self.numeric.tolerance <= 0.0 {
589            return Err(CoreError::ConfigError(
590                ErrorContext::new("Tolerance must be positive")
591                    .with_location(ErrorLocation::new(file!(), line!())),
592            ));
593        }
594
595        Ok(())
596    }
597
598    /// Merge with another configuration (other takes precedence)
599    pub fn merge(&mut self, other: &EcosystemConfig) {
600        // Merge modules
601        for (name, config) in &other.modules {
602            if let Some(existing) = self.modules.get_mut(name) {
603                existing.settings.extend(config.settings.clone());
604                existing.enabled = config.enabled;
605            } else {
606                self.modules.insert(name.clone(), config.clone());
607            }
608        }
609
610        // Merge custom settings
611        self.custom.extend(other.custom.clone());
612    }
613}
614
615/// Builder for EcosystemConfig
616#[derive(Debug, Default)]
617pub struct EcosystemConfigBuilder {
618    config: EcosystemConfig,
619}
620
621impl EcosystemConfigBuilder {
622    /// Create a new builder
623    #[must_use]
624    pub fn new() -> Self {
625        Self::default()
626    }
627
628    /// Set precision level
629    #[must_use]
630    pub fn with_precision(mut self, precision: Precision) -> Self {
631        self.config.precision.default_precision = precision;
632        self.config.precision.epsilon = precision.epsilon();
633        self
634    }
635
636    /// Set epsilon for comparisons
637    #[must_use]
638    pub fn with_epsilon(mut self, epsilon: f64) -> Self {
639        self.config.precision.epsilon = epsilon;
640        self
641    }
642
643    /// Enable/disable parallel processing
644    #[must_use]
645    pub fn with_parallel(mut self, enabled: bool) -> Self {
646        self.config.parallel.enabled = enabled;
647        self
648    }
649
650    /// Set number of threads
651    #[must_use]
652    pub fn with_num_threads(mut self, threads: usize) -> Self {
653        self.config.parallel.num_threads = Some(threads);
654        self
655    }
656
657    /// Set maximum memory limit
658    #[must_use]
659    pub fn with_max_memory(mut self, bytes: usize) -> Self {
660        self.config.memory.max_memory = Some(bytes);
661        self
662    }
663
664    /// Enable/disable memory pooling
665    #[must_use]
666    pub fn with_memory_pooling(mut self, enabled: bool) -> Self {
667        self.config.memory.enable_pooling = enabled;
668        self
669    }
670
671    /// Set maximum iterations
672    #[must_use]
673    pub fn with_max_iterations(mut self, max: usize) -> Self {
674        self.config.numeric.max_iterations = max;
675        self
676    }
677
678    /// Set tolerance
679    #[must_use]
680    pub fn with_tolerance(mut self, tolerance: f64) -> Self {
681        self.config.numeric.tolerance = tolerance;
682        self
683    }
684
685    /// Set random seed for deterministic behavior
686    #[must_use]
687    pub fn with_random_seed(mut self, seed: u64) -> Self {
688        self.config.numeric.random_seed = Some(seed);
689        self.config.numeric.deterministic = true;
690        self
691    }
692
693    /// Enable deterministic mode
694    #[must_use]
695    pub fn with_deterministic(mut self, deterministic: bool) -> Self {
696        self.config.numeric.deterministic = deterministic;
697        self
698    }
699
700    /// Set log level
701    #[must_use]
702    pub fn with_log_level(mut self, level: LogLevel) -> Self {
703        self.config.diagnostics.log_level = level;
704        self
705    }
706
707    /// Enable/disable profiling
708    #[must_use]
709    pub fn with_profiling(mut self, enabled: bool) -> Self {
710        self.config.diagnostics.enable_profiling = enabled;
711        self
712    }
713
714    /// Enable/disable tracing
715    #[must_use]
716    pub fn with_tracing(mut self, enabled: bool) -> Self {
717        self.config.diagnostics.enable_tracing = enabled;
718        self
719    }
720
721    /// Add a module configuration
722    #[must_use]
723    pub fn with_module(mut self, config: ModuleConfig) -> Self {
724        self.config.modules.insert(config.name.clone(), config);
725        self
726    }
727
728    /// Add a custom setting
729    #[must_use]
730    pub fn with_custom(mut self, key: impl Into<String>, value: impl Into<ConfigValue>) -> Self {
731        self.config.custom.insert(key.into(), value.into());
732        self
733    }
734
735    /// Build the configuration
736    ///
737    /// # Errors
738    ///
739    /// Returns error if validation fails
740    pub fn build(self) -> CoreResult<EcosystemConfig> {
741        self.config.validate()?;
742        Ok(self.config)
743    }
744
745    /// Build without validation
746    #[must_use]
747    pub fn build_unchecked(self) -> EcosystemConfig {
748        self.config
749    }
750}
751
752/// Global ecosystem configuration singleton
753static GLOBAL_ECOSYSTEM_CONFIG: std::sync::LazyLock<RwLock<Arc<EcosystemConfig>>> =
754    std::sync::LazyLock::new(|| RwLock::new(Arc::new(EcosystemConfig::default())));
755
756/// Get the global ecosystem configuration
757#[must_use]
758pub fn global_config() -> Arc<EcosystemConfig> {
759    GLOBAL_ECOSYSTEM_CONFIG
760        .read()
761        .map(|guard| Arc::clone(&*guard))
762        .unwrap_or_else(|_| Arc::new(EcosystemConfig::default()))
763}
764
765/// Set the global ecosystem configuration
766pub fn set_global_config(config: EcosystemConfig) -> CoreResult<()> {
767    config.validate()?;
768    let mut guard = GLOBAL_ECOSYSTEM_CONFIG.write().map_err(|e| {
769        CoreError::ConfigError(
770            ErrorContext::new(format!("Failed to acquire write lock: {e}"))
771                .with_location(ErrorLocation::new(file!(), line!())),
772        )
773    })?;
774    *guard = Arc::new(config);
775    Ok(())
776}
777
778/// Update the global configuration with a function
779pub fn update_global_config<F>(f: F) -> CoreResult<()>
780where
781    F: FnOnce(&mut EcosystemConfig),
782{
783    let mut guard = GLOBAL_ECOSYSTEM_CONFIG.write().map_err(|e| {
784        CoreError::ConfigError(
785            ErrorContext::new(format!("Failed to acquire write lock: {e}"))
786                .with_location(ErrorLocation::new(file!(), line!())),
787        )
788    })?;
789
790    let mut config = (**guard).clone();
791    f(&mut config);
792    config.validate()?;
793    *guard = Arc::new(config);
794    Ok(())
795}
796
797#[cfg(test)]
798mod tests {
799    use super::*;
800
801    #[test]
802    fn test_precision_levels() {
803        assert_eq!(Precision::Half.bits(), 16);
804        assert_eq!(Precision::Single.bits(), 32);
805        assert_eq!(Precision::Double.bits(), 64);
806        assert_eq!(Precision::Extended.bits(), 80);
807    }
808
809    #[test]
810    fn test_log_level_ordering() {
811        assert!(LogLevel::Error < LogLevel::Warn);
812        assert!(LogLevel::Warn < LogLevel::Info);
813        assert!(LogLevel::Info < LogLevel::Debug);
814        assert!(LogLevel::Debug < LogLevel::Trace);
815    }
816
817    #[test]
818    fn test_config_builder() {
819        let config = EcosystemConfig::builder()
820            .with_precision(Precision::Single)
821            .with_parallel(true)
822            .with_num_threads(4)
823            .with_max_iterations(500)
824            .with_tolerance(1e-6)
825            .build()
826            .expect("Config should be valid");
827
828        assert_eq!(config.precision.default_precision, Precision::Single);
829        assert!(config.parallel.enabled);
830        assert_eq!(config.parallel.num_threads, Some(4));
831        assert_eq!(config.numeric.max_iterations, 500);
832        assert_eq!(config.numeric.tolerance, 1e-6);
833    }
834
835    #[test]
836    fn test_module_config() {
837        let mut module = ModuleConfig::new("test_module");
838        module
839            .set("key1", true)
840            .set("key2", 42i64)
841            .set("key3", "value");
842
843        assert!(module.get_typed::<bool>("key1").expect("Should work"));
844        assert_eq!(module.get_typed::<i64>("key2").expect("Should work"), 42);
845        assert_eq!(
846            module.get_typed::<String>("key3").expect("Should work"),
847            "value"
848        );
849    }
850
851    #[test]
852    fn test_config_validation() {
853        let config = EcosystemConfig::builder()
854            .with_epsilon(-1.0)
855            .build_unchecked();
856
857        assert!(config.validate().is_err());
858    }
859
860    #[test]
861    fn test_config_value_conversions() {
862        let bool_val = ConfigValue::from(true);
863        assert!(bool::try_from(bool_val).expect("Should work"));
864
865        let int_val = ConfigValue::from(42i64);
866        assert_eq!(i64::try_from(int_val).expect("Should work"), 42);
867
868        let float_val = ConfigValue::from(3.5f64);
869        let f = f64::try_from(float_val).expect("Should work");
870        assert!((f - 3.5).abs() < 1e-10);
871    }
872
873    #[test]
874    fn test_parallel_config() {
875        let config = ParallelConfig {
876            num_threads: Some(8),
877            ..Default::default()
878        };
879        assert_eq!(config.effective_threads(), 8);
880
881        let auto_config = ParallelConfig::default();
882        assert!(auto_config.effective_threads() >= 1);
883    }
884
885    #[test]
886    fn test_ecosystem_config_merge() {
887        let mut config1 = EcosystemConfig::new();
888        config1.set_custom("key1", "value1");
889        config1.register_module(ModuleConfig::new("module1"));
890
891        let mut config2 = EcosystemConfig::new();
892        config2.set_custom("key2", "value2");
893        config2.register_module(ModuleConfig::new("module2"));
894
895        config1.merge(&config2);
896
897        assert!(config1.get_custom("key1").is_some());
898        assert!(config1.get_custom("key2").is_some());
899        assert!(config1.module("module1").is_some());
900        assert!(config1.module("module2").is_some());
901    }
902}