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
qnty/quantities/quantity.py
DELETED
@@ -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)
|
qnty/validation/__init__.py
DELETED
File without changes
|
qnty/validation/registry.py
DELETED
File without changes
|