|
| 1 | +import json |
1 | 2 | import logging |
2 | 3 | import time |
3 | | -import json |
| 4 | + |
4 | 5 |
|
5 | 6 | # Missing: Health check and diagnostics |
6 | 7 | class SolverHealthCheck: |
7 | 8 | """System health checks and diagnostics.""" |
8 | | - |
| 9 | + |
9 | 10 | def __init__(self): |
10 | 11 | self.checks = [] |
11 | | - |
| 12 | + |
12 | 13 | def check_numpy_version(self): |
13 | 14 | """Check NumPy version compatibility.""" |
14 | 15 | import numpy as np |
| 16 | + |
15 | 17 | min_version = "1.20.0" |
16 | 18 | current = np.__version__ |
17 | | - |
| 19 | + |
18 | 20 | try: |
19 | 21 | from packaging import version |
| 22 | + |
20 | 23 | is_compatible = version.parse(current) >= version.parse(min_version) |
21 | 24 | except ImportError: |
22 | 25 | # Fallback comparison |
23 | 26 | is_compatible = current >= min_version |
24 | | - |
| 27 | + |
25 | 28 | return { |
26 | | - 'check': 'numpy_version', |
27 | | - 'status': 'PASS' if is_compatible else 'FAIL', |
28 | | - 'details': f'NumPy {current} (min required: {min_version})', |
29 | | - 'recommendation': 'Update NumPy' if not is_compatible else None |
| 29 | + "check": "numpy_version", |
| 30 | + "status": "PASS" if is_compatible else "FAIL", |
| 31 | + "details": f"NumPy {current} (min required: {min_version})", |
| 32 | + "recommendation": "Update NumPy" if not is_compatible else None, |
30 | 33 | } |
31 | | - |
| 34 | + |
32 | 35 | def check_memory_available(self, min_memory_gb=1.0): |
33 | 36 | """Check available system memory.""" |
34 | 37 | try: |
35 | 38 | import psutil |
| 39 | + |
36 | 40 | available_gb = psutil.virtual_memory().available / (1024**3) |
37 | 41 | is_sufficient = available_gb >= min_memory_gb |
38 | | - |
| 42 | + |
39 | 43 | return { |
40 | | - 'check': 'memory_available', |
41 | | - 'status': 'PASS' if is_sufficient else 'WARN', |
42 | | - 'details': f'{available_gb:.1f}GB available (min: {min_memory_gb}GB)', |
43 | | - 'recommendation': 'Consider smaller graphs or more memory' if not is_sufficient else None |
| 44 | + "check": "memory_available", |
| 45 | + "status": "PASS" if is_sufficient else "WARN", |
| 46 | + "details": f"{available_gb:.1f}GB available (min: {min_memory_gb}GB)", |
| 47 | + "recommendation": "Consider smaller graphs or more memory" |
| 48 | + if not is_sufficient |
| 49 | + else None, |
44 | 50 | } |
45 | 51 | except ImportError: |
46 | 52 | return { |
47 | | - 'check': 'memory_available', |
48 | | - 'status': 'SKIP', |
49 | | - 'details': 'psutil not available', |
50 | | - 'recommendation': 'Install psutil for memory monitoring' |
| 53 | + "check": "memory_available", |
| 54 | + "status": "SKIP", |
| 55 | + "details": "psutil not available", |
| 56 | + "recommendation": "Install psutil for memory monitoring", |
51 | 57 | } |
52 | | - |
| 58 | + |
53 | 59 | def check_numerical_precision(self): |
54 | 60 | """Check floating-point precision issues.""" |
55 | 61 | import numpy as np |
56 | | - |
| 62 | + |
57 | 63 | # Test for common precision issues |
58 | 64 | test_cases = [ |
59 | 65 | (0.1 + 0.2, 0.3), # Classic floating-point issue |
60 | 66 | (1e16 + 1.0 - 1e16, 1.0), # Large number precision |
61 | 67 | ] |
62 | | - |
| 68 | + |
63 | 69 | issues = [] |
64 | 70 | for computed, expected in test_cases: |
65 | 71 | if abs(computed - expected) > 1e-15: |
66 | 72 | issues.append(f"{computed} != {expected}") |
67 | | - |
| 73 | + |
68 | 74 | return { |
69 | | - 'check': 'numerical_precision', |
70 | | - 'status': 'WARN' if issues else 'PASS', |
71 | | - 'details': f'Found {len(issues)} precision issues' if issues else 'No precision issues detected', |
72 | | - 'recommendation': 'Use exact mode when possible' if issues else None |
| 75 | + "check": "numerical_precision", |
| 76 | + "status": "WARN" if issues else "PASS", |
| 77 | + "details": f"Found {len(issues)} precision issues" |
| 78 | + if issues |
| 79 | + else "No precision issues detected", |
| 80 | + "recommendation": "Use exact mode when possible" if issues else None, |
73 | 81 | } |
74 | | - |
| 82 | + |
75 | 83 | def run_all_checks(self): |
76 | 84 | """Run all health checks.""" |
77 | 85 | checks = [ |
78 | 86 | self.check_numpy_version(), |
79 | 87 | self.check_memory_available(), |
80 | 88 | self.check_numerical_precision(), |
81 | 89 | ] |
82 | | - |
| 90 | + |
83 | 91 | return { |
84 | | - 'timestamp': time.time(), |
85 | | - 'checks': checks, |
86 | | - 'summary': { |
87 | | - 'total': len(checks), |
88 | | - 'passed': sum(1 for c in checks if c['status'] == 'PASS'), |
89 | | - 'warnings': sum(1 for c in checks if c['status'] == 'WARN'), |
90 | | - 'failures': sum(1 for c in checks if c['status'] == 'FAIL'), |
91 | | - } |
| 92 | + "timestamp": time.time(), |
| 93 | + "checks": checks, |
| 94 | + "summary": { |
| 95 | + "total": len(checks), |
| 96 | + "passed": sum(1 for c in checks if c["status"] == "PASS"), |
| 97 | + "warnings": sum(1 for c in checks if c["status"] == "WARN"), |
| 98 | + "failures": sum(1 for c in checks if c["status"] == "FAIL"), |
| 99 | + }, |
92 | 100 | } |
93 | 101 |
|
| 102 | + |
94 | 103 | # Missing: Configuration management |
95 | 104 | class SolverConfig: |
96 | 105 | """Configuration management for solver parameters.""" |
97 | | - |
| 106 | + |
98 | 107 | DEFAULT_CONFIG = { |
99 | | - 'numeric_mode': { |
100 | | - 'max_iter': 60, |
101 | | - 'tol': 1e-12, |
102 | | - 'detect_cycle_slack': 1e-15, |
| 108 | + "numeric_mode": { |
| 109 | + "max_iter": 60, |
| 110 | + "tol": 1e-12, |
| 111 | + "detect_cycle_slack": 1e-15, |
103 | 112 | }, |
104 | | - 'exact_mode': { |
105 | | - 'max_den': None, # Auto-determine |
106 | | - 'max_steps': None, # Auto-determine |
| 113 | + "exact_mode": { |
| 114 | + "max_den": None, # Auto-determine |
| 115 | + "max_steps": None, # Auto-determine |
107 | 116 | }, |
108 | | - 'performance': { |
109 | | - 'enable_logging': True, |
110 | | - 'enable_metrics': True, |
111 | | - 'enable_profiling': False, |
| 117 | + "performance": { |
| 118 | + "enable_logging": True, |
| 119 | + "enable_metrics": True, |
| 120 | + "enable_profiling": False, |
| 121 | + }, |
| 122 | + "validation": { |
| 123 | + "validate_cycles": True, |
| 124 | + "check_consistency": True, |
112 | 125 | }, |
113 | | - 'validation': { |
114 | | - 'validate_cycles': True, |
115 | | - 'check_consistency': True, |
116 | | - } |
117 | 126 | } |
118 | | - |
| 127 | + |
119 | 128 | def __init__(self, config_dict=None, config_file=None): |
120 | 129 | self.config = self.DEFAULT_CONFIG.copy() |
121 | | - |
| 130 | + |
122 | 131 | if config_file: |
123 | 132 | self.load_from_file(config_file) |
124 | | - |
| 133 | + |
125 | 134 | if config_dict: |
126 | 135 | self._update_config(config_dict) |
127 | | - |
| 136 | + |
128 | 137 | def load_from_file(self, filename): |
129 | 138 | """Load configuration from JSON file.""" |
130 | 139 | try: |
131 | | - with open(filename, 'r') as f: |
| 140 | + with open(filename, "r") as f: |
132 | 141 | file_config = json.load(f) |
133 | 142 | self._update_config(file_config) |
134 | 143 | except FileNotFoundError: |
135 | 144 | logging.warning(f"Config file {filename} not found, using defaults") |
136 | 145 | except json.JSONDecodeError as e: |
137 | 146 | logging.error(f"Invalid JSON in config file: {e}") |
138 | | - |
| 147 | + |
139 | 148 | def _update_config(self, new_config): |
140 | 149 | """Recursively update configuration.""" |
| 150 | + |
141 | 151 | def deep_update(base, update): |
142 | 152 | for key, value in update.items(): |
143 | | - if key in base and isinstance(base[key], dict) and isinstance(value, dict): |
| 153 | + if ( |
| 154 | + key in base |
| 155 | + and isinstance(base[key], dict) |
| 156 | + and isinstance(value, dict) |
| 157 | + ): |
144 | 158 | deep_update(base[key], value) |
145 | 159 | else: |
146 | 160 | base[key] = value |
147 | | - |
| 161 | + |
148 | 162 | deep_update(self.config, new_config) |
149 | | - |
| 163 | + |
150 | 164 | def get(self, path, default=None): |
151 | 165 | """Get configuration value using dot notation.""" |
152 | | - keys = path.split('.') |
| 166 | + keys = path.split(".") |
153 | 167 | value = self.config |
154 | | - |
| 168 | + |
155 | 169 | try: |
156 | 170 | for key in keys: |
157 | 171 | value = value[key] |
158 | 172 | return value |
159 | 173 | except (KeyError, TypeError): |
160 | 174 | return default |
161 | | - |
| 175 | + |
162 | 176 | def save_to_file(self, filename): |
163 | 177 | """Save current configuration to file.""" |
164 | | - with open(filename, 'w') as f: |
| 178 | + with open(filename, "w") as f: |
165 | 179 | json.dump(self.config, f, indent=2) |
0 commit comments