qnty 0.0.9__py3-none-any.whl → 0.1.1__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.
Files changed (92) hide show
  1. qnty/__init__.py +2 -3
  2. qnty/constants/__init__.py +10 -0
  3. qnty/constants/numerical.py +18 -0
  4. qnty/constants/solvers.py +6 -0
  5. qnty/constants/tests.py +6 -0
  6. qnty/dimensions/__init__.py +23 -0
  7. qnty/dimensions/base.py +97 -0
  8. qnty/dimensions/field_dims.py +126 -0
  9. qnty/dimensions/field_dims.pyi +128 -0
  10. qnty/dimensions/signature.py +111 -0
  11. qnty/equations/__init__.py +1 -1
  12. qnty/equations/equation.py +118 -155
  13. qnty/equations/system.py +68 -65
  14. qnty/expressions/__init__.py +25 -46
  15. qnty/expressions/formatter.py +188 -0
  16. qnty/expressions/functions.py +46 -68
  17. qnty/expressions/nodes.py +540 -384
  18. qnty/expressions/types.py +70 -0
  19. qnty/problems/__init__.py +145 -0
  20. qnty/problems/composition.py +1101 -0
  21. qnty/problems/problem.py +737 -0
  22. qnty/problems/rules.py +145 -0
  23. qnty/problems/solving.py +1216 -0
  24. qnty/problems/validation.py +127 -0
  25. qnty/quantities/__init__.py +28 -5
  26. qnty/quantities/base_qnty.py +677 -0
  27. qnty/quantities/field_converters.py +24004 -0
  28. qnty/quantities/field_qnty.py +1012 -0
  29. qnty/{generated/setters.py → quantities/field_setter.py} +3071 -2961
  30. qnty/{generated/quantities.py → quantities/field_vars.py} +829 -444
  31. qnty/{generated/quantities.pyi → quantities/field_vars.pyi} +1289 -1290
  32. qnty/solving/manager.py +50 -44
  33. qnty/solving/order.py +181 -133
  34. qnty/solving/solvers/__init__.py +2 -9
  35. qnty/solving/solvers/base.py +27 -37
  36. qnty/solving/solvers/iterative.py +115 -135
  37. qnty/solving/solvers/simultaneous.py +93 -165
  38. qnty/units/__init__.py +1 -0
  39. qnty/{generated/units.py → units/field_units.py} +1700 -991
  40. qnty/units/field_units.pyi +2461 -0
  41. qnty/units/prefixes.py +58 -105
  42. qnty/units/registry.py +76 -89
  43. qnty/utils/__init__.py +16 -0
  44. qnty/utils/caching/__init__.py +23 -0
  45. qnty/utils/caching/manager.py +401 -0
  46. qnty/utils/error_handling/__init__.py +66 -0
  47. qnty/utils/error_handling/context.py +39 -0
  48. qnty/utils/error_handling/exceptions.py +96 -0
  49. qnty/utils/error_handling/handlers.py +171 -0
  50. qnty/utils/logging.py +4 -4
  51. qnty/utils/protocols.py +164 -0
  52. qnty/utils/scope_discovery.py +420 -0
  53. {qnty-0.0.9.dist-info → qnty-0.1.1.dist-info}/METADATA +1 -1
  54. qnty-0.1.1.dist-info/RECORD +60 -0
  55. qnty/_backup/problem_original.py +0 -1251
  56. qnty/_backup/quantity.py +0 -63
  57. qnty/codegen/cli.py +0 -125
  58. qnty/codegen/generators/data/unit_data.json +0 -8807
  59. qnty/codegen/generators/data_processor.py +0 -345
  60. qnty/codegen/generators/dimensions_gen.py +0 -434
  61. qnty/codegen/generators/doc_generator.py +0 -141
  62. qnty/codegen/generators/out/dimension_mapping.json +0 -974
  63. qnty/codegen/generators/out/dimension_metadata.json +0 -123
  64. qnty/codegen/generators/out/units_metadata.json +0 -223
  65. qnty/codegen/generators/quantities_gen.py +0 -159
  66. qnty/codegen/generators/setters_gen.py +0 -178
  67. qnty/codegen/generators/stubs_gen.py +0 -167
  68. qnty/codegen/generators/units_gen.py +0 -295
  69. qnty/expressions/cache.py +0 -94
  70. qnty/generated/dimensions.py +0 -514
  71. qnty/problem/__init__.py +0 -91
  72. qnty/problem/base.py +0 -142
  73. qnty/problem/composition.py +0 -385
  74. qnty/problem/composition_mixin.py +0 -382
  75. qnty/problem/equations.py +0 -413
  76. qnty/problem/metaclass.py +0 -302
  77. qnty/problem/reconstruction.py +0 -1016
  78. qnty/problem/solving.py +0 -180
  79. qnty/problem/validation.py +0 -64
  80. qnty/problem/variables.py +0 -239
  81. qnty/quantities/expression_quantity.py +0 -314
  82. qnty/quantities/quantity.py +0 -428
  83. qnty/quantities/typed_quantity.py +0 -215
  84. qnty/validation/__init__.py +0 -0
  85. qnty/validation/registry.py +0 -0
  86. qnty/validation/rules.py +0 -167
  87. qnty-0.0.9.dist-info/RECORD +0 -63
  88. /qnty/{codegen → extensions}/__init__.py +0 -0
  89. /qnty/{codegen/generators → extensions/integration}/__init__.py +0 -0
  90. /qnty/{codegen/generators/utils → extensions/plotting}/__init__.py +0 -0
  91. /qnty/{generated → extensions/reporting}/__init__.py +0 -0
  92. {qnty-0.0.9.dist-info → qnty-0.1.1.dist-info}/WHEEL +0 -0
@@ -1,428 +0,0 @@
1
- """
2
- High-Performance Quantity and Variables
3
- ========================================
4
-
5
- FastQuantity class and type-safe variables optimized for engineering calculations
6
- with dimensional safety.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- from typing import TYPE_CHECKING, Generic, Self, TypeVar
12
-
13
- from ..generated.dimensions import (
14
- AREA,
15
- DIMENSIONLESS,
16
- ENERGY_PER_UNIT_AREA,
17
- FORCE,
18
- LENGTH,
19
- PRESSURE,
20
- SURFACE_TENSION,
21
- VOLUME,
22
- DimensionSignature,
23
- )
24
- from ..generated.dimensions import (
25
- ENERGY_HEAT_WORK as ENERGY,
26
- )
27
- from ..generated.units import AreaUnits, DimensionlessUnits, LengthUnits, PressureUnits, VolumeUnits
28
- from ..units.registry import UnitConstant, UnitDefinition, registry
29
-
30
- if TYPE_CHECKING:
31
- from ..problem.variables import VariablesMixin
32
-
33
- # TypeVar for generic dimensional types
34
- DimensionType = TypeVar("DimensionType", bound="Quantity")
35
-
36
-
37
- # Removed object pool system - benchmarking showed it was counter-productive
38
- # Simple cache for small integer values only (most common case)
39
- _SMALL_INTEGER_CACHE: dict[tuple[int, str], Quantity] = {}
40
-
41
- # Pre-computed error message templates to eliminate f-string overhead
42
- ERROR_TEMPLATES = {
43
- "incompatible_add": "Cannot add {} and {}",
44
- "incompatible_subtract": "Cannot subtract {} from {}",
45
- "incompatible_dimension": "Unit {} incompatible with expected dimension",
46
- "unit_not_found": "Unit '{}' not found for {}. Available units: {}",
47
- "unknown_function": "Unknown function: {}",
48
- "incompatible_comparison": "Cannot compare incompatible dimensions",
49
- }
50
-
51
- # Cache for multiplication/division results - keyed by dimension signature pairs
52
- _MULTIPLICATION_CACHE = {}
53
- _DIVISION_CACHE = {}
54
-
55
-
56
- # Pre-initialize common dimension cache for performance
57
- def _initialize_dimension_cache():
58
- """Initialize dimension cache at module load to avoid runtime checks."""
59
- if not registry._dimension_cache:
60
- registry._dimension_cache = {
61
- DIMENSIONLESS._signature: DimensionlessUnits.dimensionless,
62
- LENGTH._signature: LengthUnits.millimeter,
63
- PRESSURE._signature: PressureUnits.Pa,
64
- AREA._signature: AreaUnits.square_millimeter,
65
- VOLUME._signature: VolumeUnits.cubic_millimeter, # mm³
66
- FORCE._signature: UnitConstant(UnitDefinition("newton", "N", FORCE, 1.0)),
67
- ENERGY._signature: UnitConstant(UnitDefinition("joule", "J", ENERGY, 1.0)),
68
- SURFACE_TENSION._signature: UnitConstant(UnitDefinition("newton_per_meter", "N/m", SURFACE_TENSION, 1.0)),
69
- ENERGY_PER_UNIT_AREA._signature: UnitConstant(UnitDefinition("joule_per_square_meter", "J/m²", ENERGY_PER_UNIT_AREA, 1.0)),
70
- }
71
-
72
- # Pre-populate multiplication cache with comprehensive engineering combinations
73
- global _MULTIPLICATION_CACHE, _DIVISION_CACHE
74
-
75
- # Create basic unit constants for common results
76
- force_unit = UnitConstant(UnitDefinition("newton", "N", FORCE, 1.0))
77
- energy_unit = UnitConstant(UnitDefinition("joule", "J", ENERGY, 1.0))
78
-
79
- _MULTIPLICATION_CACHE = {
80
- # Basic geometric combinations
81
- (LENGTH._signature, LENGTH._signature): AreaUnits.square_millimeter,
82
- (LENGTH._signature, AREA._signature): VolumeUnits.cubic_millimeter,
83
- (AREA._signature, LENGTH._signature): VolumeUnits.cubic_millimeter,
84
- # Force and pressure combinations
85
- (PRESSURE._signature, AREA._signature): force_unit,
86
- (AREA._signature, PRESSURE._signature): force_unit,
87
- # Energy combinations
88
- (FORCE._signature, LENGTH._signature): energy_unit,
89
- (LENGTH._signature, FORCE._signature): energy_unit,
90
- }
91
-
92
- _DIVISION_CACHE = {
93
- # Basic geometric divisions
94
- (AREA._signature, LENGTH._signature): LengthUnits.millimeter,
95
- (VOLUME._signature, AREA._signature): LengthUnits.millimeter,
96
- (VOLUME._signature, LENGTH._signature): AreaUnits.square_millimeter,
97
- # Force and pressure divisions
98
- (FORCE._signature, AREA._signature): PressureUnits.Pa,
99
- # Energy divisions
100
- (ENERGY._signature, FORCE._signature): LengthUnits.meter,
101
- (ENERGY._signature, LENGTH._signature): force_unit,
102
- }
103
-
104
-
105
- class TypeSafeSetter:
106
- """Basic type-safe setter that accepts compatible units."""
107
-
108
- def __init__(self, variable: TypeSafeVariable, value: float):
109
- self.variable = variable
110
- self.value = value
111
-
112
- def with_unit(self, unit: UnitConstant) -> TypeSafeVariable:
113
- """Set with type-safe unit constant."""
114
- if not self.variable.expected_dimension.is_compatible(unit.dimension):
115
- raise TypeError(ERROR_TEMPLATES["incompatible_dimension"].format(unit.name))
116
-
117
- self.variable.quantity = Quantity(self.value, unit)
118
- return self.variable
119
-
120
-
121
- class Quantity:
122
- """High-performance quantity optimized for engineering calculations."""
123
-
124
- __slots__ = ("value", "unit", "dimension", "_si_factor", "_dimension_sig")
125
-
126
- def __init__(self, value: float, unit: UnitConstant):
127
- # Optimized initialization with cached values for performance
128
- self.value = value if isinstance(value, float) else float(value)
129
- self.unit = unit
130
- self.dimension = unit.dimension
131
- # Cache commonly used values to avoid repeated attribute access
132
- self._si_factor = unit.si_factor
133
- self._dimension_sig = unit.dimension._signature
134
-
135
- @classmethod
136
- def get_cached(cls, value: int | float, unit: UnitConstant):
137
- """Get cached instance for small integers, otherwise create new."""
138
- # Only cache small integers (most common case in engineering)
139
- if isinstance(value, int | float) and -10 <= value <= 10 and value == int(value):
140
- cache_key = (int(value), unit.name)
141
- if cache_key in _SMALL_INTEGER_CACHE:
142
- return _SMALL_INTEGER_CACHE[cache_key]
143
-
144
- # Create and cache if under limit
145
- if len(_SMALL_INTEGER_CACHE) < 100: # Small cache limit
146
- obj = cls(float(value), unit)
147
- _SMALL_INTEGER_CACHE[cache_key] = obj
148
- return obj
149
-
150
- # Regular creation for everything else
151
- return cls(value, unit)
152
-
153
- def __str__(self) -> str:
154
- # Optimized string representation (caching removed for simplicity)
155
- return f"{self.value} {self.unit.symbol}"
156
-
157
- def __repr__(self) -> str:
158
- return f"FastQuantity({self.value}, {self.unit.name})"
159
-
160
- # Ultra-fast arithmetic with dimensional checking
161
- def __add__(self, other: Quantity) -> Quantity:
162
- # Optimized addition with early exit and bulk operations
163
- self_sig, other_sig = self._dimension_sig, other._dimension_sig
164
- if self_sig != other_sig:
165
- raise ValueError(ERROR_TEMPLATES["incompatible_add"].format(self.unit.name, other.unit.name))
166
-
167
- # Ultra-fast path: compare unit names directly
168
- if self.unit.name == other.unit.name:
169
- return Quantity(self.value + other.value, self.unit)
170
-
171
- # Convert using cached SI factors (avoid repeated attribute access)
172
- self_si, other_si = self._si_factor, other._si_factor
173
- other_value = other.value * other_si / self_si
174
- return Quantity(self.value + other_value, self.unit)
175
-
176
- def __sub__(self, other: Quantity) -> Quantity:
177
- # Optimized subtraction with early exit and bulk operations
178
- self_sig, other_sig = self._dimension_sig, other._dimension_sig
179
- if self_sig != other_sig:
180
- raise ValueError(ERROR_TEMPLATES["incompatible_subtract"].format(other.unit.name, self.unit.name))
181
-
182
- # Ultra-fast path: compare unit names directly
183
- if self.unit.name == other.unit.name:
184
- return Quantity(self.value - other.value, self.unit)
185
-
186
- # Convert using cached SI factors (avoid repeated attribute access)
187
- self_si, other_si = self._si_factor, other._si_factor
188
- other_value = other.value * other_si / self_si
189
- return Quantity(self.value - other_value, self.unit)
190
-
191
- def __mul__(self, other: Quantity | float | int | TypeSafeVariable) -> Quantity:
192
- # Fast path for numeric types - use type() for speed
193
- if isinstance(other, int | float):
194
- return Quantity(self.value * other, self.unit)
195
-
196
- # Handle TypeSafeVariable objects by using their quantity
197
- # Check for quantity attribute without importing (duck typing)
198
- if hasattr(other, "quantity") and getattr(other, "quantity", None) is not None:
199
- other = other.quantity # type: ignore
200
-
201
- # Type narrowing: at this point other should be FastQuantity
202
- if not isinstance(other, Quantity):
203
- raise TypeError(f"Expected FastQuantity, got {type(other)}")
204
-
205
- # Check multiplication cache first
206
- cache_key = (self._dimension_sig, other._dimension_sig)
207
- if cache_key in _MULTIPLICATION_CACHE:
208
- result_unit = _MULTIPLICATION_CACHE[cache_key]
209
- # Fast computation with cached unit
210
- result_si_value = (self.value * self._si_factor) * (other.value * other._si_factor)
211
- return Quantity(result_si_value / result_unit.si_factor, result_unit)
212
-
213
- # Fast dimensional analysis using cached signatures
214
- result_dimension_sig = self._dimension_sig * other._dimension_sig
215
-
216
- # Use cached SI factors for conversion (bulk operations)
217
- self_si, other_si = self._si_factor, other._si_factor
218
- result_si_value = (self.value * self_si) * (other.value * other_si)
219
-
220
- # Fast path for common dimension combinations
221
- result_unit = self._find_result_unit_fast(result_dimension_sig)
222
- result_value = result_si_value / result_unit.si_factor
223
-
224
- # Cache the result for future use (limit cache size)
225
- if len(_MULTIPLICATION_CACHE) < 100:
226
- _MULTIPLICATION_CACHE[cache_key] = result_unit
227
-
228
- return Quantity(result_value, result_unit)
229
-
230
- def __rmul__(self, other: float | int) -> Quantity:
231
- """Reverse multiplication for cases like 2 * quantity."""
232
- if isinstance(other, int | float):
233
- return Quantity(other * self.value, self.unit)
234
- return NotImplemented
235
-
236
- def __truediv__(self, other: Quantity | float | int | TypeSafeVariable) -> Quantity:
237
- # Fast path for numeric types - use type() for speed
238
- if isinstance(other, int | float):
239
- return Quantity(self.value / other, self.unit)
240
-
241
- # Handle TypeSafeVariable objects by using their quantity
242
- if hasattr(other, "quantity") and getattr(other, "quantity", None) is not None:
243
- other = other.quantity # type: ignore
244
-
245
- # Type narrowing: at this point other should be FastQuantity
246
- if not isinstance(other, Quantity):
247
- raise TypeError(f"Expected FastQuantity, got {type(other)}")
248
-
249
- # Check division cache first
250
- cache_key = (self._dimension_sig, other._dimension_sig)
251
- if cache_key in _DIVISION_CACHE:
252
- result_unit = _DIVISION_CACHE[cache_key]
253
- # Fast computation with cached unit
254
- result_si_value = (self.value * self._si_factor) / (other.value * other._si_factor)
255
- return Quantity(result_si_value / result_unit.si_factor, result_unit)
256
-
257
- # Fast dimensional analysis using cached signatures
258
- result_dimension_sig = self._dimension_sig / other._dimension_sig
259
-
260
- # Use cached SI factors for conversion (bulk operations)
261
- self_si, other_si = self._si_factor, other._si_factor
262
- result_si_value = (self.value * self_si) / (other.value * other_si)
263
-
264
- # Fast path for common dimension combinations
265
- result_unit = self._find_result_unit_fast(result_dimension_sig)
266
- result_value = result_si_value / result_unit.si_factor
267
-
268
- # Cache the result for future use (limit cache size)
269
- if len(_DIVISION_CACHE) < 100:
270
- _DIVISION_CACHE[cache_key] = result_unit
271
-
272
- return Quantity(result_value, result_unit)
273
-
274
- def _find_result_unit_fast(self, result_dimension_sig: int | float) -> UnitConstant:
275
- """Ultra-fast unit finding using pre-cached dimension signatures."""
276
-
277
- # O(1) lookup for common dimensions (cache initialized at module load)
278
- if result_dimension_sig in registry._dimension_cache:
279
- return registry._dimension_cache[result_dimension_sig]
280
-
281
- # For rare combined dimensions, create SI base unit with descriptive name
282
- result_dimension = DimensionSignature(result_dimension_sig)
283
-
284
- # Create descriptive name based on dimensional analysis
285
- si_name = self._create_si_unit_name(result_dimension)
286
- si_symbol = self._create_si_unit_symbol(result_dimension)
287
-
288
- temp_unit = UnitDefinition(name=si_name, symbol=si_symbol, dimension=result_dimension, si_factor=1.0)
289
- result_unit = UnitConstant(temp_unit)
290
-
291
- # Cache for future use
292
- registry._dimension_cache[result_dimension_sig] = result_unit
293
- return result_unit
294
-
295
- def _create_si_unit_name(self, dimension: DimensionSignature) -> str:
296
- """Create descriptive SI unit name based on dimensional analysis."""
297
- # For now, return a generic SI unit name. In the future, this could be enhanced
298
- # to parse the dimension signature and create descriptive names like "newton_per_meter"
299
- return f"si_derived_unit_{abs(hash(dimension._signature)) % 10000}"
300
-
301
- def _create_si_unit_symbol(self, dimension: DimensionSignature) -> str:
302
- """Create SI unit symbol based on dimensional analysis."""
303
- # For complex units, return descriptive symbol based on common engineering units
304
- # Use dimension signature for unique symbol generation
305
- return f"SI_{abs(hash(dimension._signature)) % 1000}"
306
-
307
- def _find_result_unit(self, result_dimension: DimensionSignature) -> UnitConstant:
308
- """Legacy method - kept for compatibility."""
309
- return self._find_result_unit_fast(result_dimension._signature)
310
-
311
- # Ultra-fast comparisons
312
- def __lt__(self, other: Quantity) -> bool:
313
- # Optimized comparison with bulk operations
314
- if self._dimension_sig != other._dimension_sig:
315
- raise ValueError(ERROR_TEMPLATES["incompatible_comparison"])
316
-
317
- # Fast path for same units (use name comparison for speed)
318
- if self.unit.name == other.unit.name:
319
- return self.value < other.value
320
-
321
- # Convert using cached SI factors (bulk assignment)
322
- self_si, other_si = self._si_factor, other._si_factor
323
- return self.value < (other.value * other_si / self_si)
324
-
325
- def __eq__(self, other) -> bool:
326
- if not isinstance(other, Quantity):
327
- return False
328
- if self._dimension_sig != other._dimension_sig:
329
- return False
330
-
331
- # Fast path for same units (use name comparison)
332
- if self.unit.name == other.unit.name:
333
- return abs(self.value - other.value) < 1e-10
334
-
335
- # Convert using cached SI factors (bulk assignment)
336
- self_si, other_si = self._si_factor, other._si_factor
337
- return abs(self.value - (other.value * other_si / self_si)) < 1e-10
338
-
339
- def to(self, target_unit: UnitConstant) -> Quantity:
340
- """Ultra-fast unit conversion with optimized same-unit check."""
341
- # Ultra-fast same unit check using name comparison
342
- if self.unit.name == target_unit.name:
343
- return Quantity(self.value, target_unit)
344
-
345
- # Direct SI factor conversion - avoid registry lookup
346
- converted_value = self.value * self._si_factor / target_unit.si_factor
347
- return Quantity(converted_value, target_unit)
348
-
349
-
350
- class TypeSafeVariable(Generic[DimensionType]):
351
- """
352
- Base class for type-safe variables with dimensional checking.
353
-
354
- This is a simple data container without dependencies on expressions or equations.
355
- Mathematical operations are added by subclasses or mixins.
356
- """
357
-
358
- # Class attribute defining which setter to use - subclasses can override
359
- _setter_class = TypeSafeSetter
360
-
361
- def __init__(self, name: str, expected_dimension, is_known: bool = True):
362
- self.name = name
363
- self.symbol: str | None = None # Will be set by EngineeringProblem to attribute name
364
- self.expected_dimension = expected_dimension
365
- self.quantity: Quantity | None = None
366
- self.is_known = is_known
367
- self._parent_problem: VariablesMixin | None = None # Set by EngineeringProblem when added
368
- self.validation_checks: list = [] # List for validation checks
369
-
370
- def set(self, value: float):
371
- """Create a setter for this variable using the class-specific setter type."""
372
- return self._setter_class(self, value)
373
-
374
- @property
375
- def unknown(self) -> Self:
376
- """Mark this variable as unknown using fluent API."""
377
- self.is_known = False
378
- return self
379
-
380
- @property
381
- def known(self) -> Self:
382
- """Mark this variable as known using fluent API."""
383
- self.is_known = True
384
- return self
385
-
386
- def update(self, value=None, unit=None, quantity=None, is_known=None):
387
- """Update variable properties flexibly."""
388
- if quantity is not None:
389
- self.quantity = quantity
390
- elif value is not None:
391
- # Create setter and call the appropriate unit property
392
- setter = self.set(value)
393
- if unit is not None:
394
- # Try to find the unit property on the setter
395
- if hasattr(setter, unit):
396
- getattr(setter, unit)
397
- elif hasattr(setter, unit + "s"): # Handle singular/plural
398
- getattr(setter, unit + "s")
399
- elif unit.endswith("s") and hasattr(setter, unit[:-1]): # Handle plural to singular
400
- getattr(setter, unit[:-1])
401
- else:
402
- raise ValueError(f"Unit '{unit}' not found for {self.__class__.__name__}")
403
- else:
404
- # If no unit specified, we can't automatically choose a unit
405
- # The caller should specify either a unit or a quantity
406
- raise ValueError("Must specify either 'unit' with 'value' or provide 'quantity' directly")
407
- if is_known is not None:
408
- self.is_known = is_known
409
- return self # For method chaining
410
-
411
- def mark_known(self, quantity=None):
412
- """Mark variable as known, optionally updating its value."""
413
- self.is_known = True
414
- if quantity is not None:
415
- self.quantity = quantity
416
- return self # For method chaining
417
-
418
- def mark_unknown(self):
419
- """Mark variable as unknown."""
420
- self.is_known = False
421
- return self # For method chaining
422
-
423
- def __str__(self):
424
- return f"{self.name}: {self.quantity}" if self.quantity else f"{self.name}: unset"
425
-
426
-
427
- # Initialize dimension cache at module load for performance
428
- _initialize_dimension_cache()
@@ -1,215 +0,0 @@
1
- """
2
- Typed Variable Base Class
3
- =========================
4
-
5
- Base class that provides common constructor logic for all typed variables,
6
- handling both the original syntax and the new value/unit/name syntax.
7
- """
8
-
9
- from ..generated.dimensions import DimensionSignature
10
- from .expression_quantity import ExpressionQuantity
11
- from .quantity import TypeSafeSetter
12
-
13
-
14
- class TypedQuantity(ExpressionQuantity):
15
- """
16
- Base class for typed variables with common constructor logic.
17
-
18
- Subclasses need to define:
19
- - _setter_class: The setter class to use
20
- - _expected_dimension: The expected dimension
21
- - _default_unit_property: The default unit property name for fallback
22
- """
23
-
24
- _setter_class: type[TypeSafeSetter] | None = None
25
- _expected_dimension: DimensionSignature | None = None
26
- _default_unit_property: str | None = None
27
-
28
- # Cache for unit property lookups
29
- _unit_property_cache: dict[tuple[type, str], str | None] = {}
30
- # Cache available units per setter class for error messages
31
- _available_units_cache: dict[type, list[str]] = {}
32
- # Pre-computed unit mappings for common units (override in subclasses for better performance)
33
- _unit_mappings: dict[str, str] = {}
34
- # Fast-path validation cache to bypass complex checks
35
- _fast_validation_cache: dict[tuple[type, str], bool] = {}
36
-
37
- @classmethod
38
- def _find_unit_property(cls, setter: TypeSafeSetter, unit: str) -> str | None:
39
- """Find unit property with optimized lookup and caching."""
40
- if cls._setter_class is None:
41
- return None
42
-
43
- # Ultra-fast path: Check pre-computed mappings first
44
- if unit in cls._unit_mappings:
45
- return cls._unit_mappings[unit]
46
-
47
- cache_key = (cls._setter_class, unit)
48
-
49
- # Fast path: Check cache second
50
- if cache_key in cls._unit_property_cache:
51
- return cls._unit_property_cache[cache_key]
52
-
53
- # Medium path: Check if we've validated this unit before
54
- if cache_key in cls._fast_validation_cache:
55
- if cls._fast_validation_cache[cache_key]:
56
- # We know this unit exists, try direct lookup first
57
- if hasattr(setter, unit):
58
- cls._unit_property_cache[cache_key] = unit
59
- return unit
60
-
61
- # Slow path: Try all variants and cache both property and validation result
62
- for unit_variant in [unit, unit + 's', unit[:-1] if unit.endswith('s') else None]:
63
- if unit_variant and hasattr(setter, unit_variant):
64
- cls._unit_property_cache[cache_key] = unit_variant
65
- cls._fast_validation_cache[cache_key] = True
66
- return unit_variant
67
-
68
- # Cache miss - remember that this unit doesn't exist
69
- cls._unit_property_cache[cache_key] = None
70
- cls._fast_validation_cache[cache_key] = False
71
- return None
72
-
73
- @classmethod
74
- def _get_available_units_error(cls, unit: str) -> str:
75
- """Generate helpful error message with available units."""
76
- if cls._setter_class is None:
77
- return f"Unit '{unit}' not found for {cls.__name__}. Setter class not defined."
78
-
79
- if cls._setter_class not in cls._available_units_cache:
80
- # Create dummy setter to get available units
81
- dummy_var = object.__new__(cls)
82
- dummy_var._setter_class = cls._setter_class
83
- dummy_var._expected_dimension = cls._expected_dimension
84
- dummy_setter = cls._setter_class(dummy_var, 0.0)
85
-
86
- # Cache available units
87
- unit_properties = [attr for attr in dir(dummy_setter)
88
- if not attr.startswith('_') and attr not in ('value', 'variable')]
89
- cls._available_units_cache[cls._setter_class] = sorted(unit_properties)
90
-
91
- available_units = cls._available_units_cache[cls._setter_class]
92
- display_units = ', '.join(available_units[:10])
93
- if len(available_units) > 10:
94
- display_units += f' ... and {len(available_units) - 10} more'
95
-
96
- return f"Unit '{unit}' not found for {cls.__name__}. Available units: {display_units}"
97
-
98
- def __init__(self, *args, is_known: bool = True, _bypass_validation: bool = False):
99
- """
100
- Flexible constructor supporting multiple syntaxes.
101
-
102
- Single argument: TypedVariable("name")
103
- Three arguments: TypedVariable(value, "unit", "name")
104
- Two arguments (Dimensionless only): TypedVariable(value, "name")
105
- """
106
- if not _bypass_validation and (self._setter_class is None or self._expected_dimension is None):
107
- raise NotImplementedError("Subclass must define _setter_class and _expected_dimension")
108
-
109
- is_dimensionless = self.__class__.__name__ == 'Dimensionless'
110
-
111
- # Handle different argument patterns
112
- if len(args) == 1:
113
- # Original syntax: Variable("name")
114
- super().__init__(args[0], self._expected_dimension, is_known=is_known)
115
-
116
- elif len(args) == 2 and is_dimensionless:
117
- # Special case for Dimensionless: (value, "name")
118
- value, name = args
119
- super().__init__(name, self._expected_dimension, is_known=is_known)
120
- if self._setter_class is not None:
121
- setter = self._setter_class(self, value)
122
- getattr(setter, 'dimensionless', None) # type: ignore
123
-
124
- elif len(args) == 3:
125
- # New syntax: Variable(value, "unit", "name")
126
- if is_dimensionless:
127
- raise ValueError(f"{self.__class__.__name__} expects either 1 argument (name) or 2 arguments (value, name), got {len(args)}")
128
-
129
- value, unit, name = args
130
- super().__init__(name, self._expected_dimension, is_known=is_known)
131
-
132
- # Fast path for pre-computed units
133
- if not _bypass_validation and unit in self._unit_mappings and self._setter_class is not None:
134
- setter = self._setter_class(self, value)
135
- getattr(setter, self._unit_mappings[unit])
136
- else:
137
- # Standard path with unit lookup
138
- if self._setter_class is not None:
139
- setter = self._setter_class(self, value)
140
- unit_prop = self._find_unit_property(setter, unit)
141
-
142
- if unit_prop:
143
- getattr(setter, unit_prop)
144
- elif not _bypass_validation:
145
- raise ValueError(self._get_available_units_error(unit))
146
-
147
- else:
148
- # Error messages
149
- if is_dimensionless:
150
- raise ValueError(f"{self.__class__.__name__} expects either 1 argument (name) or 2 arguments (value, name), got {len(args)}")
151
- else:
152
- raise ValueError(f"{self.__class__.__name__} expects either 1 argument (name) or 3 arguments (value, unit, name), got {len(args)}")
153
-
154
- @classmethod
155
- def from_value(cls, value: float, unit: str, name: str, is_known: bool = True):
156
- """
157
- Optimized factory method for creating variables with known values.
158
- Bypasses complex constructor logic for better performance.
159
- """
160
- instance = cls.__new__(cls)
161
- # Direct initialization without checks
162
- ExpressionQuantity.__init__(instance, name, instance._expected_dimension, is_known=is_known)
163
-
164
- # Fast path for setting value using shared lookup logic
165
- if instance._setter_class is None:
166
- raise NotImplementedError("Subclass must define _setter_class")
167
-
168
- setter = instance._setter_class(instance, value)
169
- unit_prop = cls._find_unit_property(setter, unit)
170
-
171
- if unit_prop:
172
- getattr(setter, unit_prop)
173
- else:
174
- raise ValueError(cls._get_available_units_error(unit))
175
-
176
- return instance
177
-
178
- @classmethod
179
- def create_unknown(cls, name: str):
180
- """Fast factory method for creating unknown variables."""
181
- return cls(name, is_known=False)
182
-
183
- @classmethod
184
- def create_bulk(cls, variable_specs: list[tuple[float, str, str]]) -> list:
185
- """Bulk creation method for multiple variables with same type."""
186
- if cls._setter_class is None:
187
- raise NotImplementedError("Subclass must define _setter_class")
188
-
189
- variables = []
190
- for value, unit, name in variable_specs:
191
- # Use fast path creation if possible
192
- if unit in cls._unit_mappings:
193
- var = cls._create_with_fast_unit_lookup(value, unit, name)
194
- else:
195
- var = cls.from_value(value, unit, name)
196
- variables.append(var)
197
-
198
- return variables
199
-
200
- @classmethod
201
- def _create_with_fast_unit_lookup(cls, value: float, unit: str, name: str, is_known: bool = True):
202
- """Ultra-fast constructor bypassing complex lookup when unit mappings are pre-computed."""
203
- if unit in cls._unit_mappings and cls._setter_class is not None:
204
- # Ultra-fast path: direct property access
205
- instance = cls.__new__(cls)
206
- ExpressionQuantity.__init__(instance, name, cls._expected_dimension, is_known=is_known)
207
-
208
- setter = cls._setter_class(instance, value)
209
- unit_prop = cls._unit_mappings[unit]
210
- getattr(setter, unit_prop)
211
-
212
- return instance
213
- else:
214
- # Fall back to standard constructor
215
- return cls(value, unit, name, is_known=is_known)
File without changes
File without changes