qnty 0.0.9__py3-none-any.whl → 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qnty/__init__.py +2 -3
- qnty/constants/__init__.py +10 -0
- qnty/constants/numerical.py +18 -0
- qnty/constants/solvers.py +6 -0
- qnty/constants/tests.py +6 -0
- qnty/dimensions/__init__.py +23 -0
- qnty/dimensions/base.py +97 -0
- qnty/dimensions/field_dims.py +126 -0
- qnty/dimensions/field_dims.pyi +128 -0
- qnty/dimensions/signature.py +111 -0
- qnty/equations/__init__.py +1 -1
- qnty/equations/equation.py +118 -155
- qnty/equations/system.py +68 -65
- qnty/expressions/__init__.py +25 -46
- qnty/expressions/formatter.py +188 -0
- qnty/expressions/functions.py +46 -68
- qnty/expressions/nodes.py +539 -384
- qnty/expressions/types.py +70 -0
- qnty/problems/__init__.py +145 -0
- qnty/problems/composition.py +1031 -0
- qnty/problems/problem.py +695 -0
- qnty/problems/rules.py +145 -0
- qnty/problems/solving.py +1216 -0
- qnty/problems/validation.py +127 -0
- qnty/quantities/__init__.py +28 -5
- qnty/quantities/base_qnty.py +677 -0
- qnty/quantities/field_converters.py +24004 -0
- qnty/quantities/field_qnty.py +1012 -0
- qnty/{generated/setters.py → quantities/field_setter.py} +3071 -2961
- qnty/{generated/quantities.py → quantities/field_vars.py} +754 -432
- qnty/{generated/quantities.pyi → quantities/field_vars.pyi} +1289 -1290
- qnty/solving/manager.py +50 -44
- qnty/solving/order.py +181 -133
- qnty/solving/solvers/__init__.py +2 -9
- qnty/solving/solvers/base.py +27 -37
- qnty/solving/solvers/iterative.py +115 -135
- qnty/solving/solvers/simultaneous.py +93 -165
- qnty/units/__init__.py +1 -0
- qnty/{generated/units.py → units/field_units.py} +1700 -991
- qnty/units/field_units.pyi +2461 -0
- qnty/units/prefixes.py +58 -105
- qnty/units/registry.py +76 -89
- qnty/utils/__init__.py +16 -0
- qnty/utils/caching/__init__.py +23 -0
- qnty/utils/caching/manager.py +401 -0
- qnty/utils/error_handling/__init__.py +66 -0
- qnty/utils/error_handling/context.py +39 -0
- qnty/utils/error_handling/exceptions.py +96 -0
- qnty/utils/error_handling/handlers.py +171 -0
- qnty/utils/logging.py +4 -4
- qnty/utils/protocols.py +164 -0
- qnty/utils/scope_discovery.py +420 -0
- {qnty-0.0.9.dist-info → qnty-0.1.0.dist-info}/METADATA +1 -1
- qnty-0.1.0.dist-info/RECORD +60 -0
- qnty/_backup/problem_original.py +0 -1251
- qnty/_backup/quantity.py +0 -63
- qnty/codegen/cli.py +0 -125
- qnty/codegen/generators/data/unit_data.json +0 -8807
- qnty/codegen/generators/data_processor.py +0 -345
- qnty/codegen/generators/dimensions_gen.py +0 -434
- qnty/codegen/generators/doc_generator.py +0 -141
- qnty/codegen/generators/out/dimension_mapping.json +0 -974
- qnty/codegen/generators/out/dimension_metadata.json +0 -123
- qnty/codegen/generators/out/units_metadata.json +0 -223
- qnty/codegen/generators/quantities_gen.py +0 -159
- qnty/codegen/generators/setters_gen.py +0 -178
- qnty/codegen/generators/stubs_gen.py +0 -167
- qnty/codegen/generators/units_gen.py +0 -295
- qnty/expressions/cache.py +0 -94
- qnty/generated/dimensions.py +0 -514
- qnty/problem/__init__.py +0 -91
- qnty/problem/base.py +0 -142
- qnty/problem/composition.py +0 -385
- qnty/problem/composition_mixin.py +0 -382
- qnty/problem/equations.py +0 -413
- qnty/problem/metaclass.py +0 -302
- qnty/problem/reconstruction.py +0 -1016
- qnty/problem/solving.py +0 -180
- qnty/problem/validation.py +0 -64
- qnty/problem/variables.py +0 -239
- qnty/quantities/expression_quantity.py +0 -314
- qnty/quantities/quantity.py +0 -428
- qnty/quantities/typed_quantity.py +0 -215
- qnty/validation/__init__.py +0 -0
- qnty/validation/registry.py +0 -0
- qnty/validation/rules.py +0 -167
- qnty-0.0.9.dist-info/RECORD +0 -63
- /qnty/{codegen → extensions}/__init__.py +0 -0
- /qnty/{codegen/generators → extensions/integration}/__init__.py +0 -0
- /qnty/{codegen/generators/utils → extensions/plotting}/__init__.py +0 -0
- /qnty/{generated → extensions/reporting}/__init__.py +0 -0
- {qnty-0.0.9.dist-info → qnty-0.1.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,401 @@
|
|
1
|
+
"""
|
2
|
+
Unified Cache Management System
|
3
|
+
===============================
|
4
|
+
|
5
|
+
Centralized caching infrastructure for all qnty operations, providing
|
6
|
+
consistent cache policies, monitoring, and memory management.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from __future__ import annotations
|
10
|
+
|
11
|
+
from typing import Any, TypeVar
|
12
|
+
from weakref import WeakValueDictionary
|
13
|
+
|
14
|
+
# Type variables for cache keys and values
|
15
|
+
K = TypeVar("K")
|
16
|
+
V = TypeVar("V")
|
17
|
+
|
18
|
+
|
19
|
+
class CacheStats:
|
20
|
+
"""Statistics tracking for cache performance analysis."""
|
21
|
+
|
22
|
+
def __init__(self, name: str):
|
23
|
+
self.name = name
|
24
|
+
self.hits = 0
|
25
|
+
self.misses = 0
|
26
|
+
self.evictions = 0
|
27
|
+
|
28
|
+
def hit(self):
|
29
|
+
"""Record a cache hit."""
|
30
|
+
self.hits += 1
|
31
|
+
|
32
|
+
def miss(self):
|
33
|
+
"""Record a cache miss."""
|
34
|
+
self.misses += 1
|
35
|
+
|
36
|
+
def evict(self):
|
37
|
+
"""Record a cache eviction."""
|
38
|
+
self.evictions += 1
|
39
|
+
|
40
|
+
@property
|
41
|
+
def hit_rate(self) -> float:
|
42
|
+
"""Calculate cache hit rate."""
|
43
|
+
total = self.hits + self.misses
|
44
|
+
return self.hits / total if total > 0 else 0.0
|
45
|
+
|
46
|
+
def __str__(self) -> str:
|
47
|
+
return f"{self.name}: {self.hits}/{self.hits + self.misses} hits ({self.hit_rate:.1%}), {self.evictions} evictions"
|
48
|
+
|
49
|
+
|
50
|
+
class UnifiedCacheManager:
|
51
|
+
"""
|
52
|
+
Centralized cache manager for all qnty operations.
|
53
|
+
|
54
|
+
Provides consistent caching policies, memory management, and performance
|
55
|
+
monitoring across all library components.
|
56
|
+
"""
|
57
|
+
|
58
|
+
# Cache size limits (tuned based on typical usage patterns)
|
59
|
+
UNIT_PROPERTY_CACHE_SIZE = 200
|
60
|
+
AVAILABLE_UNITS_CACHE_SIZE = 50
|
61
|
+
EXPRESSION_CACHE_SIZE = 300
|
62
|
+
TYPE_CHECK_CACHE_SIZE = 100
|
63
|
+
DIMENSIONLESS_CACHE_SIZE = 50
|
64
|
+
DIMENSION_SIGNATURE_CACHE_SIZE = 500
|
65
|
+
VALIDATION_CACHE_SIZE = 150
|
66
|
+
SMALL_INTEGER_CACHE_SIZE = 100
|
67
|
+
MULTIPLICATION_CACHE_SIZE = 100
|
68
|
+
DIVISION_CACHE_SIZE = 100
|
69
|
+
|
70
|
+
def __init__(self):
|
71
|
+
# Core caches with different policies
|
72
|
+
self._unit_property_cache: dict[tuple[type, str], str | None] = {}
|
73
|
+
self._available_units_cache: dict[type, list[str]] = {}
|
74
|
+
self._expression_result_cache: dict[str, Any] = {}
|
75
|
+
self._type_check_cache: dict[type, bool] = {}
|
76
|
+
self._dimensionless_cache: dict[float, Any] = {}
|
77
|
+
self._validation_cache: dict[tuple[type, str], bool] = {}
|
78
|
+
|
79
|
+
# Quantity-specific caches
|
80
|
+
self._small_integer_cache: dict[tuple[int, str], Any] = {}
|
81
|
+
self._multiplication_cache: dict[tuple[Any, Any], Any] = {}
|
82
|
+
self._division_cache: dict[tuple[Any, Any], Any] = {}
|
83
|
+
self._dimension_cache: dict[Any, Any] = {}
|
84
|
+
|
85
|
+
# Weak reference caches for object-based keys
|
86
|
+
self._dimension_signature_cache: WeakValueDictionary = WeakValueDictionary()
|
87
|
+
|
88
|
+
# Statistics tracking
|
89
|
+
self._stats = {
|
90
|
+
"unit_property": CacheStats("Unit Property"),
|
91
|
+
"available_units": CacheStats("Available Units"),
|
92
|
+
"expression_result": CacheStats("Expression Result"),
|
93
|
+
"type_check": CacheStats("Type Check"),
|
94
|
+
"dimensionless": CacheStats("Dimensionless"),
|
95
|
+
"validation": CacheStats("Validation"),
|
96
|
+
"small_integer": CacheStats("Small Integer"),
|
97
|
+
"multiplication": CacheStats("Multiplication"),
|
98
|
+
"division": CacheStats("Division"),
|
99
|
+
"dimension": CacheStats("Dimension"),
|
100
|
+
}
|
101
|
+
|
102
|
+
# Unit Property Cache Operations
|
103
|
+
def get_unit_property(self, setter_class: type, unit: str) -> str | None:
|
104
|
+
"""Get cached unit property mapping."""
|
105
|
+
key = (setter_class, unit)
|
106
|
+
if key in self._unit_property_cache:
|
107
|
+
self._stats["unit_property"].hit()
|
108
|
+
return self._unit_property_cache[key]
|
109
|
+
|
110
|
+
self._stats["unit_property"].miss()
|
111
|
+
return None
|
112
|
+
|
113
|
+
def cache_unit_property(self, setter_class: type, unit: str, property_name: str | None) -> None:
|
114
|
+
"""Cache unit property mapping."""
|
115
|
+
key = (setter_class, unit)
|
116
|
+
self._unit_property_cache[key] = property_name
|
117
|
+
|
118
|
+
# Enforce cache size limit
|
119
|
+
if len(self._unit_property_cache) > self.UNIT_PROPERTY_CACHE_SIZE:
|
120
|
+
self._evict_oldest("unit_property", self._unit_property_cache, self.UNIT_PROPERTY_CACHE_SIZE // 4)
|
121
|
+
|
122
|
+
# Available Units Cache Operations
|
123
|
+
def get_available_units(self, setter_class: type) -> list[str] | None:
|
124
|
+
"""Get cached available units for a setter class."""
|
125
|
+
if setter_class in self._available_units_cache:
|
126
|
+
self._stats["available_units"].hit()
|
127
|
+
return self._available_units_cache[setter_class]
|
128
|
+
|
129
|
+
self._stats["available_units"].miss()
|
130
|
+
return None
|
131
|
+
|
132
|
+
def cache_available_units(self, setter_class: type, units: list[str]) -> None:
|
133
|
+
"""Cache available units for a setter class."""
|
134
|
+
self._available_units_cache[setter_class] = units
|
135
|
+
|
136
|
+
if len(self._available_units_cache) > self.AVAILABLE_UNITS_CACHE_SIZE:
|
137
|
+
self._evict_oldest("available_units", self._available_units_cache, self.AVAILABLE_UNITS_CACHE_SIZE // 4)
|
138
|
+
|
139
|
+
# Type Check Cache Operations
|
140
|
+
def get_type_check(self, obj_type: type) -> bool | None:
|
141
|
+
"""Get cached type check result."""
|
142
|
+
if obj_type in self._type_check_cache:
|
143
|
+
self._stats["type_check"].hit()
|
144
|
+
return self._type_check_cache[obj_type]
|
145
|
+
|
146
|
+
self._stats["type_check"].miss()
|
147
|
+
return None
|
148
|
+
|
149
|
+
def cache_type_check(self, obj_type: type, result: bool) -> None:
|
150
|
+
"""Cache type check result."""
|
151
|
+
self._type_check_cache[obj_type] = result
|
152
|
+
|
153
|
+
if len(self._type_check_cache) > self.TYPE_CHECK_CACHE_SIZE:
|
154
|
+
self._evict_oldest("type_check", self._type_check_cache, self.TYPE_CHECK_CACHE_SIZE // 4)
|
155
|
+
|
156
|
+
# Dimensionless Quantity Cache Operations
|
157
|
+
def get_dimensionless_quantity(self, value: float) -> Any | None:
|
158
|
+
"""Get cached dimensionless quantity."""
|
159
|
+
if value in self._dimensionless_cache:
|
160
|
+
self._stats["dimensionless"].hit()
|
161
|
+
return self._dimensionless_cache[value]
|
162
|
+
|
163
|
+
self._stats["dimensionless"].miss()
|
164
|
+
return None
|
165
|
+
|
166
|
+
def cache_dimensionless_quantity(self, value: float, quantity: Any) -> None:
|
167
|
+
"""Cache dimensionless quantity for common values."""
|
168
|
+
# Only cache small integer/simple values to prevent memory bloat
|
169
|
+
if -10 <= value <= 10 and (value == int(value) or value in [0.5, 0.25, 0.75]):
|
170
|
+
self._dimensionless_cache[value] = quantity
|
171
|
+
|
172
|
+
if len(self._dimensionless_cache) > self.DIMENSIONLESS_CACHE_SIZE:
|
173
|
+
self._evict_oldest("dimensionless", self._dimensionless_cache, self.DIMENSIONLESS_CACHE_SIZE // 4)
|
174
|
+
|
175
|
+
# Validation Cache Operations
|
176
|
+
def get_validation_result(self, setter_class: type, unit: str) -> bool | None:
|
177
|
+
"""Get cached validation result."""
|
178
|
+
key = (setter_class, unit)
|
179
|
+
if key in self._validation_cache:
|
180
|
+
self._stats["validation"].hit()
|
181
|
+
return self._validation_cache[key]
|
182
|
+
|
183
|
+
self._stats["validation"].miss()
|
184
|
+
return None
|
185
|
+
|
186
|
+
def cache_validation_result(self, setter_class: type, unit: str, is_valid: bool) -> None:
|
187
|
+
"""Cache validation result."""
|
188
|
+
key = (setter_class, unit)
|
189
|
+
self._validation_cache[key] = is_valid
|
190
|
+
|
191
|
+
if len(self._validation_cache) > self.VALIDATION_CACHE_SIZE:
|
192
|
+
self._evict_oldest("validation", self._validation_cache, self.VALIDATION_CACHE_SIZE // 4)
|
193
|
+
|
194
|
+
# Expression Result Cache Operations (for future use)
|
195
|
+
def get_expression_result(self, expression_key: str) -> Any | None:
|
196
|
+
"""Get cached expression evaluation result."""
|
197
|
+
if expression_key in self._expression_result_cache:
|
198
|
+
self._stats["expression_result"].hit()
|
199
|
+
return self._expression_result_cache[expression_key]
|
200
|
+
|
201
|
+
self._stats["expression_result"].miss()
|
202
|
+
return None
|
203
|
+
|
204
|
+
def cache_expression_result(self, expression_key: str, result: Any) -> None:
|
205
|
+
"""Cache expression evaluation result."""
|
206
|
+
self._expression_result_cache[expression_key] = result
|
207
|
+
|
208
|
+
if len(self._expression_result_cache) > self.EXPRESSION_CACHE_SIZE:
|
209
|
+
self._evict_oldest("expression_result", self._expression_result_cache, self.EXPRESSION_CACHE_SIZE // 4)
|
210
|
+
|
211
|
+
# Quantity-specific Cache Operations
|
212
|
+
def get_cached_quantity(self, value: int | float, unit_name: str) -> Any | None:
|
213
|
+
"""Get cached quantity for small integers."""
|
214
|
+
if isinstance(value, int | float) and -10 <= value <= 10 and value == int(value):
|
215
|
+
key = (int(value), unit_name)
|
216
|
+
if key in self._small_integer_cache:
|
217
|
+
self._stats["small_integer"].hit()
|
218
|
+
return self._small_integer_cache[key]
|
219
|
+
|
220
|
+
self._stats["small_integer"].miss()
|
221
|
+
return None
|
222
|
+
|
223
|
+
def cache_quantity(self, value: int | float, unit_name: str, quantity: Any) -> None:
|
224
|
+
"""Cache quantity if it's a small integer."""
|
225
|
+
if isinstance(value, int | float) and -10 <= value <= 10 and value == int(value):
|
226
|
+
key = (int(value), unit_name)
|
227
|
+
self._small_integer_cache[key] = quantity
|
228
|
+
|
229
|
+
if len(self._small_integer_cache) > self.SMALL_INTEGER_CACHE_SIZE:
|
230
|
+
self._evict_oldest("small_integer", self._small_integer_cache, self.SMALL_INTEGER_CACHE_SIZE // 4)
|
231
|
+
|
232
|
+
def get_multiplication_result(self, left_sig: Any, right_sig: Any) -> Any | None:
|
233
|
+
"""Get cached multiplication result."""
|
234
|
+
key = (left_sig, right_sig)
|
235
|
+
if key in self._multiplication_cache:
|
236
|
+
self._stats["multiplication"].hit()
|
237
|
+
return self._multiplication_cache[key]
|
238
|
+
|
239
|
+
self._stats["multiplication"].miss()
|
240
|
+
return None
|
241
|
+
|
242
|
+
def cache_multiplication_result(self, left_sig: Any, right_sig: Any, result_unit: Any) -> None:
|
243
|
+
"""Cache multiplication result."""
|
244
|
+
key = (left_sig, right_sig)
|
245
|
+
self._multiplication_cache[key] = result_unit
|
246
|
+
|
247
|
+
if len(self._multiplication_cache) > self.MULTIPLICATION_CACHE_SIZE:
|
248
|
+
self._evict_oldest("multiplication", self._multiplication_cache, self.MULTIPLICATION_CACHE_SIZE // 4)
|
249
|
+
|
250
|
+
def get_division_result(self, left_sig: Any, right_sig: Any) -> Any | None:
|
251
|
+
"""Get cached division result."""
|
252
|
+
key = (left_sig, right_sig)
|
253
|
+
if key in self._division_cache:
|
254
|
+
self._stats["division"].hit()
|
255
|
+
return self._division_cache[key]
|
256
|
+
|
257
|
+
self._stats["division"].miss()
|
258
|
+
return None
|
259
|
+
|
260
|
+
def cache_division_result(self, left_sig: Any, right_sig: Any, result_unit: Any) -> None:
|
261
|
+
"""Cache division result."""
|
262
|
+
key = (left_sig, right_sig)
|
263
|
+
self._division_cache[key] = result_unit
|
264
|
+
|
265
|
+
if len(self._division_cache) > self.DIVISION_CACHE_SIZE:
|
266
|
+
self._evict_oldest("division", self._division_cache, self.DIVISION_CACHE_SIZE // 4)
|
267
|
+
|
268
|
+
def get_dimension_unit(self, dimension_sig: Any) -> Any | None:
|
269
|
+
"""Get cached unit for dimension signature."""
|
270
|
+
if dimension_sig in self._dimension_cache:
|
271
|
+
self._stats["dimension"].hit()
|
272
|
+
return self._dimension_cache[dimension_sig]
|
273
|
+
|
274
|
+
self._stats["dimension"].miss()
|
275
|
+
return None
|
276
|
+
|
277
|
+
def cache_dimension_unit(self, dimension_sig: Any, unit: Any) -> None:
|
278
|
+
"""Cache unit for dimension signature."""
|
279
|
+
self._dimension_cache[dimension_sig] = unit
|
280
|
+
|
281
|
+
def initialize_dimension_cache(self, initial_mappings: dict[Any, Any]) -> None:
|
282
|
+
"""Initialize dimension cache with common mappings."""
|
283
|
+
self._dimension_cache.update(initial_mappings)
|
284
|
+
|
285
|
+
def initialize_operation_caches(self, multiplication_mappings: dict[tuple[Any, Any], Any], division_mappings: dict[tuple[Any, Any], Any]) -> None:
|
286
|
+
"""Initialize multiplication and division caches with common operations."""
|
287
|
+
self._multiplication_cache.update(multiplication_mappings)
|
288
|
+
self._division_cache.update(division_mappings)
|
289
|
+
|
290
|
+
# Cache Management Operations
|
291
|
+
def _evict_oldest(self, cache_name: str, cache_dict: dict, evict_count: int) -> None:
|
292
|
+
"""Evict oldest entries from cache."""
|
293
|
+
# Simple FIFO eviction - remove first N items
|
294
|
+
keys_to_remove = list(cache_dict.keys())[:evict_count]
|
295
|
+
for key in keys_to_remove:
|
296
|
+
del cache_dict[key]
|
297
|
+
self._stats[cache_name].evict()
|
298
|
+
|
299
|
+
def clear_cache(self, cache_name: str | None = None) -> None:
|
300
|
+
"""Clear specific cache or all caches."""
|
301
|
+
if cache_name is None:
|
302
|
+
# Clear all caches
|
303
|
+
self._unit_property_cache.clear()
|
304
|
+
self._available_units_cache.clear()
|
305
|
+
self._expression_result_cache.clear()
|
306
|
+
self._type_check_cache.clear()
|
307
|
+
self._dimensionless_cache.clear()
|
308
|
+
self._validation_cache.clear()
|
309
|
+
self._dimension_signature_cache.clear()
|
310
|
+
self._small_integer_cache.clear()
|
311
|
+
self._multiplication_cache.clear()
|
312
|
+
self._division_cache.clear()
|
313
|
+
self._dimension_cache.clear()
|
314
|
+
else:
|
315
|
+
# Clear specific cache
|
316
|
+
cache_map = {
|
317
|
+
"unit_property": self._unit_property_cache,
|
318
|
+
"available_units": self._available_units_cache,
|
319
|
+
"expression_result": self._expression_result_cache,
|
320
|
+
"type_check": self._type_check_cache,
|
321
|
+
"dimensionless": self._dimensionless_cache,
|
322
|
+
"validation": self._validation_cache,
|
323
|
+
"small_integer": self._small_integer_cache,
|
324
|
+
"multiplication": self._multiplication_cache,
|
325
|
+
"division": self._division_cache,
|
326
|
+
"dimension": self._dimension_cache,
|
327
|
+
}
|
328
|
+
if cache_name in cache_map:
|
329
|
+
cache_map[cache_name].clear()
|
330
|
+
|
331
|
+
def get_cache_stats(self) -> dict[str, CacheStats]:
|
332
|
+
"""Get performance statistics for all caches."""
|
333
|
+
return self._stats.copy()
|
334
|
+
|
335
|
+
def get_cache_sizes(self) -> dict[str, int]:
|
336
|
+
"""Get current size of all caches."""
|
337
|
+
return {
|
338
|
+
"unit_property": len(self._unit_property_cache),
|
339
|
+
"available_units": len(self._available_units_cache),
|
340
|
+
"expression_result": len(self._expression_result_cache),
|
341
|
+
"type_check": len(self._type_check_cache),
|
342
|
+
"dimensionless": len(self._dimensionless_cache),
|
343
|
+
"validation": len(self._validation_cache),
|
344
|
+
"dimension_signature": len(self._dimension_signature_cache),
|
345
|
+
"small_integer": len(self._small_integer_cache),
|
346
|
+
"multiplication": len(self._multiplication_cache),
|
347
|
+
"division": len(self._division_cache),
|
348
|
+
"dimension": len(self._dimension_cache),
|
349
|
+
}
|
350
|
+
|
351
|
+
def get_memory_usage_estimate(self) -> int:
|
352
|
+
"""Estimate memory usage in bytes (rough approximation)."""
|
353
|
+
# Rough estimates based on typical key/value sizes
|
354
|
+
estimates = {
|
355
|
+
"unit_property": len(self._unit_property_cache) * 200, # tuple keys + string values
|
356
|
+
"available_units": len(self._available_units_cache) * 500, # type keys + list values
|
357
|
+
"expression_result": len(self._expression_result_cache) * 300, # string keys + object values
|
358
|
+
"type_check": len(self._type_check_cache) * 100, # type keys + bool values
|
359
|
+
"dimensionless": len(self._dimensionless_cache) * 150, # float keys + quantity values
|
360
|
+
"validation": len(self._validation_cache) * 150, # tuple keys + bool values
|
361
|
+
"small_integer": len(self._small_integer_cache) * 200, # tuple keys + quantity values
|
362
|
+
"multiplication": len(self._multiplication_cache) * 250, # tuple keys + unit values
|
363
|
+
"division": len(self._division_cache) * 250, # tuple keys + unit values
|
364
|
+
"dimension": len(self._dimension_cache) * 200, # signature keys + unit values
|
365
|
+
}
|
366
|
+
return sum(estimates.values())
|
367
|
+
|
368
|
+
|
369
|
+
# Global cache manager instance
|
370
|
+
_cache_manager: UnifiedCacheManager | None = None
|
371
|
+
|
372
|
+
|
373
|
+
def get_cache_manager() -> UnifiedCacheManager:
|
374
|
+
"""Get the global cache manager instance."""
|
375
|
+
global _cache_manager
|
376
|
+
if _cache_manager is None:
|
377
|
+
_cache_manager = UnifiedCacheManager()
|
378
|
+
return _cache_manager
|
379
|
+
|
380
|
+
|
381
|
+
# Convenience functions for common operations
|
382
|
+
def clear_all_caches() -> None:
|
383
|
+
"""Clear all caches - useful for testing and memory management."""
|
384
|
+
get_cache_manager().clear_cache()
|
385
|
+
|
386
|
+
|
387
|
+
def get_cache_statistics() -> dict[str, str]:
|
388
|
+
"""Get formatted cache statistics."""
|
389
|
+
stats = get_cache_manager().get_cache_stats()
|
390
|
+
return {name: str(stat) for name, stat in stats.items()}
|
391
|
+
|
392
|
+
|
393
|
+
def get_memory_usage() -> str:
|
394
|
+
"""Get formatted memory usage estimate."""
|
395
|
+
bytes_used = get_cache_manager().get_memory_usage_estimate()
|
396
|
+
if bytes_used < 1024:
|
397
|
+
return f"{bytes_used} bytes"
|
398
|
+
elif bytes_used < 1024 * 1024:
|
399
|
+
return f"{bytes_used / 1024:.1f} KB"
|
400
|
+
else:
|
401
|
+
return f"{bytes_used / (1024 * 1024):.1f} MB"
|
@@ -0,0 +1,66 @@
|
|
1
|
+
"""
|
2
|
+
Error handling infrastructure for the Qnty library.
|
3
|
+
|
4
|
+
This package provides comprehensive error handling capabilities including
|
5
|
+
custom exceptions, error handlers, and context management.
|
6
|
+
|
7
|
+
Public API:
|
8
|
+
- Exception classes: QntyError, DimensionalError, UnitConversionError, etc.
|
9
|
+
- Error handling: ErrorHandler, ErrorHandlerMixin
|
10
|
+
- Context management: ErrorContext
|
11
|
+
- Convenience functions: require_variable, ensure_not_zero, safe_evaluate
|
12
|
+
"""
|
13
|
+
|
14
|
+
# Import all exception classes
|
15
|
+
# Import context management
|
16
|
+
from .context import (
|
17
|
+
ErrorContext,
|
18
|
+
create_context,
|
19
|
+
get_dimension_string,
|
20
|
+
)
|
21
|
+
from .exceptions import (
|
22
|
+
ERROR_MESSAGES,
|
23
|
+
DimensionalError,
|
24
|
+
DivisionByZeroError,
|
25
|
+
EquationSolvingError,
|
26
|
+
ExpressionEvaluationError,
|
27
|
+
QntyError,
|
28
|
+
UnitConversionError,
|
29
|
+
VariableNotFoundError,
|
30
|
+
)
|
31
|
+
|
32
|
+
# Import handlers and utilities
|
33
|
+
from .handlers import (
|
34
|
+
ErrorHandler,
|
35
|
+
ErrorHandlerMixin,
|
36
|
+
ensure_not_zero,
|
37
|
+
get_error_handler,
|
38
|
+
require_variable,
|
39
|
+
safe_evaluate,
|
40
|
+
set_error_handler,
|
41
|
+
)
|
42
|
+
|
43
|
+
# Define public API
|
44
|
+
__all__ = [
|
45
|
+
# Exception classes
|
46
|
+
"QntyError",
|
47
|
+
"DimensionalError",
|
48
|
+
"UnitConversionError",
|
49
|
+
"VariableNotFoundError",
|
50
|
+
"EquationSolvingError",
|
51
|
+
"ExpressionEvaluationError",
|
52
|
+
"DivisionByZeroError",
|
53
|
+
"ERROR_MESSAGES",
|
54
|
+
# Context management
|
55
|
+
"ErrorContext",
|
56
|
+
"get_dimension_string",
|
57
|
+
"create_context",
|
58
|
+
# Handlers and utilities
|
59
|
+
"ErrorHandler",
|
60
|
+
"ErrorHandlerMixin",
|
61
|
+
"get_error_handler",
|
62
|
+
"set_error_handler",
|
63
|
+
"require_variable",
|
64
|
+
"ensure_not_zero",
|
65
|
+
"safe_evaluate",
|
66
|
+
]
|
@@ -0,0 +1,39 @@
|
|
1
|
+
"""
|
2
|
+
Error context and utility functions for error handling.
|
3
|
+
|
4
|
+
This module provides context management and utility functions to support
|
5
|
+
consistent error reporting throughout the library.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from dataclasses import dataclass
|
9
|
+
from typing import Any
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class ErrorContext:
|
14
|
+
"""Context information for errors to aid in debugging."""
|
15
|
+
|
16
|
+
module: str
|
17
|
+
function: str
|
18
|
+
operation: str
|
19
|
+
variables: dict[str, Any] | None = None
|
20
|
+
additional_info: dict[str, Any] | None = None
|
21
|
+
|
22
|
+
def to_dict(self) -> dict[str, Any]:
|
23
|
+
"""Convert to dictionary for error logging."""
|
24
|
+
return {"module": self.module, "function": self.function, "operation": self.operation, "variables": self.variables, "additional_info": self.additional_info}
|
25
|
+
|
26
|
+
|
27
|
+
def get_dimension_string(obj: Any) -> str:
|
28
|
+
"""Get dimension string representation for error messages."""
|
29
|
+
if hasattr(obj, "_dimension_sig"):
|
30
|
+
return str(obj._dimension_sig)
|
31
|
+
elif hasattr(obj, "unit") and hasattr(obj.unit, "dimension"):
|
32
|
+
return str(obj.unit.dimension)
|
33
|
+
else:
|
34
|
+
return f"{type(obj).__name__}"
|
35
|
+
|
36
|
+
|
37
|
+
def create_context(module: str, function: str, operation: str, **kwargs) -> ErrorContext:
|
38
|
+
"""Create error context for consistent error reporting."""
|
39
|
+
return ErrorContext(module=module, function=function, operation=operation, additional_info=kwargs)
|
@@ -0,0 +1,96 @@
|
|
1
|
+
"""
|
2
|
+
Exception classes for the Qnty library.
|
3
|
+
|
4
|
+
This module defines all custom exception types used throughout the library
|
5
|
+
for consistent error handling and reporting.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Any
|
9
|
+
|
10
|
+
|
11
|
+
# Custom Exception Hierarchy
|
12
|
+
class QntyError(Exception):
|
13
|
+
"""Base exception for all qnty library errors."""
|
14
|
+
|
15
|
+
def __init__(self, message: str, context: dict[str, Any] | None = None):
|
16
|
+
super().__init__(message)
|
17
|
+
self.context = context or {}
|
18
|
+
self.message = message
|
19
|
+
|
20
|
+
|
21
|
+
class DimensionalError(QntyError):
|
22
|
+
"""Raised when operations involve incompatible dimensions."""
|
23
|
+
|
24
|
+
def __init__(self, operation: str, left_dim: str, right_dim: str, context: dict | None = None):
|
25
|
+
message = f"Incompatible dimensions for {operation}: {left_dim} and {right_dim}"
|
26
|
+
super().__init__(message, context)
|
27
|
+
self.operation = operation
|
28
|
+
self.left_dimension = left_dim
|
29
|
+
self.right_dimension = right_dim
|
30
|
+
|
31
|
+
|
32
|
+
class UnitConversionError(QntyError):
|
33
|
+
"""Raised when unit conversions fail."""
|
34
|
+
|
35
|
+
def __init__(self, from_unit: str, to_unit: str, reason: str = "", context: dict | None = None):
|
36
|
+
message = f"Cannot convert from '{from_unit}' to '{to_unit}'"
|
37
|
+
if reason:
|
38
|
+
message += f": {reason}"
|
39
|
+
super().__init__(message, context)
|
40
|
+
self.from_unit = from_unit
|
41
|
+
self.to_unit = to_unit
|
42
|
+
|
43
|
+
|
44
|
+
class VariableNotFoundError(QntyError):
|
45
|
+
"""Raised when a required variable is not found."""
|
46
|
+
|
47
|
+
def __init__(self, variable_name: str, available_vars: list[str] | None = None, context: dict | None = None):
|
48
|
+
message = f"Variable '{variable_name}' not found"
|
49
|
+
if available_vars:
|
50
|
+
message += f". Available variables: {', '.join(available_vars)}"
|
51
|
+
super().__init__(message, context)
|
52
|
+
self.variable_name = variable_name
|
53
|
+
self.available_vars = available_vars or []
|
54
|
+
|
55
|
+
|
56
|
+
class EquationSolvingError(QntyError):
|
57
|
+
"""Raised when equation solving fails."""
|
58
|
+
|
59
|
+
def __init__(self, equation_name: str, target_var: str, reason: str = "", context: dict | None = None):
|
60
|
+
message = f"Cannot solve equation '{equation_name}' for variable '{target_var}'"
|
61
|
+
if reason:
|
62
|
+
message += f": {reason}"
|
63
|
+
super().__init__(message, context)
|
64
|
+
self.equation_name = equation_name
|
65
|
+
self.target_var = target_var
|
66
|
+
|
67
|
+
|
68
|
+
class ExpressionEvaluationError(QntyError):
|
69
|
+
"""Raised when expression evaluation fails."""
|
70
|
+
|
71
|
+
def __init__(self, expression: str, reason: str = "", context: dict | None = None):
|
72
|
+
message = f"Cannot evaluate expression '{expression}'"
|
73
|
+
if reason:
|
74
|
+
message += f": {reason}"
|
75
|
+
super().__init__(message, context)
|
76
|
+
self.expression = expression
|
77
|
+
|
78
|
+
|
79
|
+
class DivisionByZeroError(QntyError):
|
80
|
+
"""Raised for division by zero operations."""
|
81
|
+
|
82
|
+
def __init__(self, dividend: str, context: dict | None = None):
|
83
|
+
message = f"Division by zero: {dividend} / 0"
|
84
|
+
super().__init__(message, context)
|
85
|
+
self.dividend = dividend
|
86
|
+
|
87
|
+
|
88
|
+
# Error message templates for consistency
|
89
|
+
ERROR_MESSAGES = {
|
90
|
+
"incompatible_dimensions": "Incompatible dimensions: {left} and {right} for operation {operation}",
|
91
|
+
"variable_not_found": "Variable '{variable}' not found. Available: {available}",
|
92
|
+
"unit_conversion_failed": "Cannot convert from '{from_unit}' to '{to_unit}': {reason}",
|
93
|
+
"equation_unsolvable": "Cannot solve equation '{equation}' for variable '{variable}': {reason}",
|
94
|
+
"division_by_zero": "Division by zero in expression: {expression}",
|
95
|
+
"invalid_operation": "Invalid operation '{operation}' on {type}: {reason}",
|
96
|
+
}
|