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,677 @@
|
|
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 TypeVar
|
12
|
+
|
13
|
+
from ..constants import FLOAT_EQUALITY_TOLERANCE
|
14
|
+
from ..dimensions.field_dims import (
|
15
|
+
AREA,
|
16
|
+
DIMENSIONLESS,
|
17
|
+
ENERGY_PER_UNIT_AREA,
|
18
|
+
FORCE,
|
19
|
+
LENGTH,
|
20
|
+
PRESSURE,
|
21
|
+
SURFACE_TENSION,
|
22
|
+
VOLUME,
|
23
|
+
DimensionSignature,
|
24
|
+
)
|
25
|
+
from ..dimensions.field_dims import (
|
26
|
+
ENERGY_HEAT_WORK as ENERGY,
|
27
|
+
)
|
28
|
+
from ..units.field_units import AreaUnits, DimensionlessUnits, LengthUnits, PressureUnits, VolumeUnits
|
29
|
+
from ..units.registry import UnitConstant, UnitDefinition, registry
|
30
|
+
|
31
|
+
# TypeVar for generic dimensional types
|
32
|
+
DimensionType = TypeVar("DimensionType", bound="Quantity")
|
33
|
+
|
34
|
+
|
35
|
+
# Performance optimization data structures
|
36
|
+
class ResultTemplate:
|
37
|
+
"""Pre-computed result template to bypass Quantity creation overhead."""
|
38
|
+
|
39
|
+
__slots__ = ("unit", "combined_si_factor")
|
40
|
+
|
41
|
+
def __init__(self, unit: UnitConstant, combined_si_factor: float):
|
42
|
+
self.unit = unit
|
43
|
+
self.combined_si_factor = combined_si_factor
|
44
|
+
|
45
|
+
|
46
|
+
class ObjectPool:
|
47
|
+
"""Object pool for common quantity types to reduce allocations."""
|
48
|
+
|
49
|
+
__slots__ = ("_area_pool", "_volume_pool", "_force_pool", "_energy_pool", "_pool_index", "_initialized")
|
50
|
+
|
51
|
+
def __init__(self):
|
52
|
+
# Initialize empty pools - delay creation until first use
|
53
|
+
self._area_pool: list[Quantity] = []
|
54
|
+
self._volume_pool: list[Quantity] = []
|
55
|
+
self._force_pool: list[Quantity] = []
|
56
|
+
self._energy_pool: list[Quantity] = []
|
57
|
+
self._pool_index = 0
|
58
|
+
self._initialized = False
|
59
|
+
|
60
|
+
def _initialize_pools_lazy(self):
|
61
|
+
"""Lazily create pooled objects to avoid circular imports."""
|
62
|
+
if self._initialized:
|
63
|
+
return
|
64
|
+
|
65
|
+
area_unit = AreaUnits.square_millimeter
|
66
|
+
volume_unit = VolumeUnits.cubic_millimeter
|
67
|
+
force_unit = UnitConstant(UnitDefinition("newton", "N", FORCE, 1.0))
|
68
|
+
energy_unit = UnitConstant(UnitDefinition("joule", "J", ENERGY, 1.0))
|
69
|
+
|
70
|
+
# Pre-allocate 10 of each common type
|
71
|
+
for _ in range(10):
|
72
|
+
self._area_pool.append(Quantity(0.0, area_unit))
|
73
|
+
self._volume_pool.append(Quantity(0.0, volume_unit))
|
74
|
+
self._force_pool.append(Quantity(0.0, force_unit))
|
75
|
+
self._energy_pool.append(Quantity(0.0, energy_unit))
|
76
|
+
|
77
|
+
self._initialized = True
|
78
|
+
|
79
|
+
def get_area_quantity(self, value: float, unit: UnitConstant) -> Quantity:
|
80
|
+
"""Get pooled area quantity or create new if pool empty."""
|
81
|
+
self._initialize_pools_lazy()
|
82
|
+
if self._area_pool:
|
83
|
+
qty = self._area_pool.pop()
|
84
|
+
qty.value = value
|
85
|
+
qty.unit = unit
|
86
|
+
qty._si_factor = unit.si_factor
|
87
|
+
qty._dimension_sig = unit.dimension._signature
|
88
|
+
return qty
|
89
|
+
return Quantity(value, unit)
|
90
|
+
|
91
|
+
def get_volume_quantity(self, value: float, unit: UnitConstant) -> Quantity:
|
92
|
+
"""Get pooled volume quantity or create new if pool empty."""
|
93
|
+
self._initialize_pools_lazy()
|
94
|
+
if self._volume_pool:
|
95
|
+
qty = self._volume_pool.pop()
|
96
|
+
qty.value = value
|
97
|
+
qty.unit = unit
|
98
|
+
qty._si_factor = unit.si_factor
|
99
|
+
qty._dimension_sig = unit.dimension._signature
|
100
|
+
return qty
|
101
|
+
return Quantity(value, unit)
|
102
|
+
|
103
|
+
def get_force_quantity(self, value: float, unit: UnitConstant) -> Quantity:
|
104
|
+
"""Get pooled force quantity or create new if pool empty."""
|
105
|
+
self._initialize_pools_lazy()
|
106
|
+
if self._force_pool:
|
107
|
+
qty = self._force_pool.pop()
|
108
|
+
qty.value = value
|
109
|
+
qty.unit = unit
|
110
|
+
qty._si_factor = unit.si_factor
|
111
|
+
qty._dimension_sig = unit.dimension._signature
|
112
|
+
return qty
|
113
|
+
return Quantity(value, unit)
|
114
|
+
|
115
|
+
|
116
|
+
# Global state management and caching system
|
117
|
+
class CacheManager:
|
118
|
+
"""Centralized cache management for better encapsulation and control."""
|
119
|
+
|
120
|
+
def __init__(self):
|
121
|
+
self.small_integer_cache: dict[tuple[int, str], Quantity] = {}
|
122
|
+
self.multiplication_cache: dict[tuple[int | float, int | float], UnitConstant] = {}
|
123
|
+
# NEW: Result templates with pre-computed SI factors
|
124
|
+
self.multiplication_templates: dict[tuple[int | float, int | float], ResultTemplate] = {}
|
125
|
+
self.division_cache: dict[tuple[int | float, int | float], UnitConstant] = {}
|
126
|
+
self.max_cache_size = 100
|
127
|
+
# Object pool for common types
|
128
|
+
self.object_pool = ObjectPool()
|
129
|
+
|
130
|
+
def get_cached_quantity(self, value: int | float, unit: UnitConstant) -> Quantity | None:
|
131
|
+
"""Get cached quantity for small integers."""
|
132
|
+
if isinstance(value, int | float) and -10 <= value <= 10 and value == int(value):
|
133
|
+
cache_key = (int(value), unit.name)
|
134
|
+
return self.small_integer_cache.get(cache_key)
|
135
|
+
return None
|
136
|
+
|
137
|
+
def cache_quantity(self, value: int | float, unit: UnitConstant, quantity: Quantity) -> None:
|
138
|
+
"""Cache quantity if under size limit."""
|
139
|
+
if len(self.small_integer_cache) < self.max_cache_size:
|
140
|
+
cache_key = (int(value), unit.name)
|
141
|
+
self.small_integer_cache[cache_key] = quantity
|
142
|
+
|
143
|
+
def get_multiplication_result(self, left_sig: int | float, right_sig: int | float) -> UnitConstant | None:
|
144
|
+
"""Get cached multiplication result."""
|
145
|
+
return self.multiplication_cache.get((left_sig, right_sig))
|
146
|
+
|
147
|
+
def cache_multiplication_result(self, left_sig: int | float, right_sig: int | float, result_unit: UnitConstant) -> None:
|
148
|
+
"""Cache multiplication result if under size limit."""
|
149
|
+
if len(self.multiplication_cache) < self.max_cache_size:
|
150
|
+
self.multiplication_cache[(left_sig, right_sig)] = result_unit
|
151
|
+
|
152
|
+
def get_division_result(self, left_sig: int | float, right_sig: int | float) -> UnitConstant | None:
|
153
|
+
"""Get cached division result."""
|
154
|
+
return self.division_cache.get((left_sig, right_sig))
|
155
|
+
|
156
|
+
def cache_division_result(self, left_sig: int | float, right_sig: int | float, result_unit: UnitConstant) -> None:
|
157
|
+
"""Cache division result if under size limit."""
|
158
|
+
if len(self.division_cache) < self.max_cache_size:
|
159
|
+
self.division_cache[(left_sig, right_sig)] = result_unit
|
160
|
+
|
161
|
+
def initialize_common_operations(self) -> None:
|
162
|
+
"""Initialize common dimensional operations cache."""
|
163
|
+
if not registry._dimension_cache:
|
164
|
+
registry._dimension_cache = {
|
165
|
+
DIMENSIONLESS._signature: DimensionlessUnits.dimensionless,
|
166
|
+
LENGTH._signature: LengthUnits.millimeter,
|
167
|
+
PRESSURE._signature: PressureUnits.Pa,
|
168
|
+
AREA._signature: AreaUnits.square_millimeter,
|
169
|
+
VOLUME._signature: VolumeUnits.cubic_millimeter,
|
170
|
+
FORCE._signature: UnitConstant(UnitDefinition("newton", "N", FORCE, 1.0)),
|
171
|
+
ENERGY._signature: UnitConstant(UnitDefinition("joule", "J", ENERGY, 1.0)),
|
172
|
+
SURFACE_TENSION._signature: UnitConstant(UnitDefinition("newton_per_meter", "N/m", SURFACE_TENSION, 1.0)),
|
173
|
+
ENERGY_PER_UNIT_AREA._signature: UnitConstant(UnitDefinition("joule_per_square_meter", "J/m²", ENERGY_PER_UNIT_AREA, 1.0)),
|
174
|
+
}
|
175
|
+
|
176
|
+
# Pre-populate common engineering combinations with extensive coverage
|
177
|
+
force_unit = UnitConstant(UnitDefinition("newton", "N", FORCE, 1.0))
|
178
|
+
energy_unit = UnitConstant(UnitDefinition("joule", "J", ENERGY, 1.0))
|
179
|
+
# _surface_tension_unit = UnitConstant(UnitDefinition("newton_per_meter", "N/m", SURFACE_TENSION, 1.0)) # Unused
|
180
|
+
energy_per_area_unit = UnitConstant(UnitDefinition("joule_per_square_meter", "J/m²", ENERGY_PER_UNIT_AREA, 1.0))
|
181
|
+
|
182
|
+
# NEW APPROACH: Create result templates with pre-computed combined SI factors
|
183
|
+
# This eliminates the need for SI factor division during multiplication
|
184
|
+
|
185
|
+
# Helper function to create template with pre-computed factors
|
186
|
+
def create_template(unit: UnitConstant) -> ResultTemplate:
|
187
|
+
"""Create result template with pre-computed combined SI factor.
|
188
|
+
|
189
|
+
For typical case where left and right operands use the cached base units,
|
190
|
+
the combined factor is just 1.0 / result_unit.si_factor since the SI
|
191
|
+
conversion will be handled by the quantity's own _si_factor attributes.
|
192
|
+
"""
|
193
|
+
# The template assumes SI values will be multiplied in, so we just need
|
194
|
+
# the reciprocal of the result unit's SI factor for final conversion
|
195
|
+
combined_factor = 1.0 / unit.si_factor
|
196
|
+
return ResultTemplate(unit, combined_factor)
|
197
|
+
|
198
|
+
# Get common unit SI factors for template pre-computation (unused in current implementation)
|
199
|
+
# _mm_si = LengthUnits.millimeter.si_factor # 0.001
|
200
|
+
# _pa_si = PressureUnits.Pa.si_factor # 1.0
|
201
|
+
# _mm2_si = AreaUnits.square_millimeter.si_factor # 1e-6
|
202
|
+
# _mm3_si = VolumeUnits.cubic_millimeter.si_factor # 1e-9
|
203
|
+
|
204
|
+
# ULTRA-OPTIMIZED TEMPLATES: Most common engineering operations
|
205
|
+
self.multiplication_templates.update(
|
206
|
+
{
|
207
|
+
# TOP 5 ENGINEERING COMBINATIONS (hardcoded for maximum speed)
|
208
|
+
# 1. Length × Length = Area (most common geometric operation)
|
209
|
+
(LENGTH._signature, LENGTH._signature): create_template(AreaUnits.square_millimeter),
|
210
|
+
# 2. Pressure × Area = Force (ASME pressure vessel calculations)
|
211
|
+
(PRESSURE._signature, AREA._signature): create_template(force_unit),
|
212
|
+
(AREA._signature, PRESSURE._signature): create_template(force_unit),
|
213
|
+
# 3. Length × Area = Volume (geometric calculations)
|
214
|
+
(LENGTH._signature, AREA._signature): create_template(VolumeUnits.cubic_millimeter),
|
215
|
+
(AREA._signature, LENGTH._signature): create_template(VolumeUnits.cubic_millimeter),
|
216
|
+
# 4. Force × Length = Energy (work calculations)
|
217
|
+
(FORCE._signature, LENGTH._signature): create_template(energy_unit),
|
218
|
+
(LENGTH._signature, FORCE._signature): create_template(energy_unit),
|
219
|
+
# 5. Dimensionless scaling (extremely common - factors of safety, ratios)
|
220
|
+
(PRESSURE._signature, DIMENSIONLESS._signature): create_template(PressureUnits.Pa),
|
221
|
+
(DIMENSIONLESS._signature, PRESSURE._signature): create_template(PressureUnits.Pa),
|
222
|
+
(LENGTH._signature, DIMENSIONLESS._signature): create_template(LengthUnits.millimeter),
|
223
|
+
(DIMENSIONLESS._signature, LENGTH._signature): create_template(LengthUnits.millimeter),
|
224
|
+
(AREA._signature, DIMENSIONLESS._signature): create_template(AreaUnits.square_millimeter),
|
225
|
+
(DIMENSIONLESS._signature, AREA._signature): create_template(AreaUnits.square_millimeter),
|
226
|
+
(VOLUME._signature, DIMENSIONLESS._signature): create_template(VolumeUnits.cubic_millimeter),
|
227
|
+
(DIMENSIONLESS._signature, VOLUME._signature): create_template(VolumeUnits.cubic_millimeter),
|
228
|
+
(FORCE._signature, DIMENSIONLESS._signature): create_template(force_unit),
|
229
|
+
(DIMENSIONLESS._signature, FORCE._signature): create_template(force_unit),
|
230
|
+
(ENERGY._signature, DIMENSIONLESS._signature): create_template(energy_unit),
|
231
|
+
(DIMENSIONLESS._signature, ENERGY._signature): create_template(energy_unit),
|
232
|
+
(DIMENSIONLESS._signature, DIMENSIONLESS._signature): create_template(DimensionlessUnits.dimensionless),
|
233
|
+
}
|
234
|
+
)
|
235
|
+
|
236
|
+
# Keep legacy multiplication cache for compatibility with less common operations
|
237
|
+
multiplication_patterns = {
|
238
|
+
# Less common but still cached operations
|
239
|
+
(AREA._signature, AREA._signature): UnitConstant(UnitDefinition("m4", "m⁴", AREA * AREA, 1e-12)), # mm⁴
|
240
|
+
(FORCE._signature, FORCE._signature): UnitConstant(UnitDefinition("N2", "N²", FORCE * FORCE, 1.0)),
|
241
|
+
(PRESSURE._signature, LENGTH._signature): force_unit,
|
242
|
+
(LENGTH._signature, PRESSURE._signature): force_unit,
|
243
|
+
(PRESSURE._signature, PRESSURE._signature): UnitConstant(UnitDefinition("Pa2", "Pa²", PRESSURE * PRESSURE, 1.0)),
|
244
|
+
(PRESSURE._signature, VOLUME._signature): energy_unit, # PV work
|
245
|
+
(VOLUME._signature, PRESSURE._signature): energy_unit,
|
246
|
+
(FORCE._signature, AREA._signature): energy_unit, # Force × Area = Energy
|
247
|
+
(AREA._signature, FORCE._signature): energy_unit,
|
248
|
+
(ENERGY._signature, LENGTH._signature): force_unit, # Energy/Length = Force
|
249
|
+
(LENGTH._signature, ENERGY._signature): force_unit,
|
250
|
+
(ENERGY._signature, AREA._signature): energy_per_area_unit,
|
251
|
+
(AREA._signature, ENERGY._signature): energy_per_area_unit,
|
252
|
+
(SURFACE_TENSION._signature, LENGTH._signature): force_unit,
|
253
|
+
(LENGTH._signature, SURFACE_TENSION._signature): force_unit,
|
254
|
+
}
|
255
|
+
|
256
|
+
self.multiplication_cache.update(multiplication_patterns)
|
257
|
+
|
258
|
+
self.division_cache.update(
|
259
|
+
{
|
260
|
+
# Basic geometric divisions
|
261
|
+
(AREA._signature, LENGTH._signature): LengthUnits.millimeter,
|
262
|
+
(VOLUME._signature, AREA._signature): LengthUnits.millimeter,
|
263
|
+
(VOLUME._signature, LENGTH._signature): AreaUnits.square_millimeter,
|
264
|
+
# Force and pressure divisions
|
265
|
+
(FORCE._signature, AREA._signature): PressureUnits.Pa,
|
266
|
+
(ENERGY._signature, FORCE._signature): LengthUnits.meter,
|
267
|
+
(ENERGY._signature, LENGTH._signature): force_unit,
|
268
|
+
# ASME-specific: Force ÷ Length = Pressure (common in final result)
|
269
|
+
(FORCE._signature, LENGTH._signature): PressureUnits.Pa,
|
270
|
+
# Dimensionless divisions (maintain original units)
|
271
|
+
(PRESSURE._signature, DIMENSIONLESS._signature): PressureUnits.Pa,
|
272
|
+
(LENGTH._signature, DIMENSIONLESS._signature): LengthUnits.millimeter,
|
273
|
+
(FORCE._signature, DIMENSIONLESS._signature): force_unit,
|
274
|
+
# Combined unit divisions (P×D ÷ P = D pattern in ASME)
|
275
|
+
(force_unit.dimension._signature, PRESSURE._signature): LengthUnits.millimeter,
|
276
|
+
}
|
277
|
+
)
|
278
|
+
|
279
|
+
|
280
|
+
# Pre-cached units for ultra-fast paths (avoid repeated creation/import overhead)
|
281
|
+
_CACHED_AREA_UNIT = AreaUnits.square_millimeter
|
282
|
+
_CACHED_VOLUME_UNIT = VolumeUnits.cubic_millimeter
|
283
|
+
_CACHED_FORCE_UNIT = UnitConstant(UnitDefinition("newton", "N", FORCE, 1.0))
|
284
|
+
# _CACHED_ENERGY_UNIT = UnitConstant(UnitDefinition("joule", "J", ENERGY, 1.0)) # Unused
|
285
|
+
|
286
|
+
# Pre-computed reciprocals for division (eliminate division operations)
|
287
|
+
_AREA_SI_RECIPROCAL = 1.0 / _CACHED_AREA_UNIT.si_factor # 1.0 / 1e-6 = 1e6
|
288
|
+
_VOLUME_SI_RECIPROCAL = 1.0 / _CACHED_VOLUME_UNIT.si_factor # 1.0 / 1e-9 = 1e9
|
289
|
+
# _FORCE_SI_RECIPROCAL = 1.0 / _CACHED_FORCE_UNIT.si_factor # 1.0 / 1.0 = 1.0 # Unused
|
290
|
+
|
291
|
+
# Global cache manager instance
|
292
|
+
_cache_manager = CacheManager()
|
293
|
+
|
294
|
+
|
295
|
+
# Error message constants for better maintainability
|
296
|
+
class ErrorMessages:
|
297
|
+
"""Centralized error message templates."""
|
298
|
+
|
299
|
+
INCOMPATIBLE_ADD = "Cannot add {} and {}"
|
300
|
+
INCOMPATIBLE_SUBTRACT = "Cannot subtract {} from {}"
|
301
|
+
INCOMPATIBLE_DIMENSION = "Unit {} incompatible with expected dimension"
|
302
|
+
UNIT_NOT_FOUND = "Unit '{}' not found for {}. Available units: {}"
|
303
|
+
UNKNOWN_FUNCTION = "Unknown function: {}"
|
304
|
+
INCOMPATIBLE_COMPARISON = "Cannot compare incompatible dimensions"
|
305
|
+
|
306
|
+
|
307
|
+
# Component classes for separation of concerns
|
308
|
+
class ArithmeticOperations:
|
309
|
+
"""Handles arithmetic operations for quantities."""
|
310
|
+
|
311
|
+
@staticmethod
|
312
|
+
def add(quantity, other):
|
313
|
+
"""Add two quantities with dimensional checking."""
|
314
|
+
if quantity._dimension_sig != other._dimension_sig:
|
315
|
+
raise ValueError(ErrorMessages.INCOMPATIBLE_ADD.format(quantity.unit.name, other.unit.name))
|
316
|
+
|
317
|
+
# Fast path for same units
|
318
|
+
if quantity.unit.name == other.unit.name:
|
319
|
+
return Quantity(quantity.value + other.value, quantity.unit)
|
320
|
+
|
321
|
+
# Convert using cached SI factors
|
322
|
+
converted_value = other.value * other._si_factor / quantity._si_factor
|
323
|
+
return Quantity(quantity.value + converted_value, quantity.unit)
|
324
|
+
|
325
|
+
@staticmethod
|
326
|
+
def subtract(quantity, other):
|
327
|
+
"""Subtract two quantities with dimensional checking."""
|
328
|
+
if quantity._dimension_sig != other._dimension_sig:
|
329
|
+
raise ValueError(ErrorMessages.INCOMPATIBLE_SUBTRACT.format(other.unit.name, quantity.unit.name))
|
330
|
+
|
331
|
+
# Fast path for same units
|
332
|
+
if quantity.unit.name == other.unit.name:
|
333
|
+
return Quantity(quantity.value - other.value, quantity.unit)
|
334
|
+
|
335
|
+
# Convert using cached SI factors
|
336
|
+
converted_value = other.value * other._si_factor / quantity._si_factor
|
337
|
+
return Quantity(quantity.value - converted_value, quantity.unit)
|
338
|
+
|
339
|
+
@staticmethod
|
340
|
+
def multiply(quantity, other):
|
341
|
+
"""Ultra-aggressive multiplication optimizations targeting <0.400μs performance."""
|
342
|
+
# FASTEST PATH: Use type() check for numbers (faster than isinstance)
|
343
|
+
other_type = type(other)
|
344
|
+
if other_type in (int, float):
|
345
|
+
return Quantity(quantity.value * other, quantity.unit)
|
346
|
+
|
347
|
+
# DUCK TYPING: Extract quantity without hasattr overhead
|
348
|
+
other_qty = getattr(other, "quantity", None)
|
349
|
+
if other_qty is not None:
|
350
|
+
other = other_qty
|
351
|
+
|
352
|
+
# ULTRA-FAST ATTRIBUTE ACCESS: Direct access with getattr fallback for robustness
|
353
|
+
q_val = quantity.value
|
354
|
+
q_dim = quantity._dimension_sig
|
355
|
+
o_val = getattr(other, "value", None)
|
356
|
+
o_dim = getattr(other, "_dimension_sig", None)
|
357
|
+
|
358
|
+
# Quick validation
|
359
|
+
if o_val is None or o_dim is None:
|
360
|
+
raise TypeError(f"Expected Quantity-like object, got {type(other)}")
|
361
|
+
|
362
|
+
# SPECIAL VALUE FAST PATHS: Most performance-critical optimizations first
|
363
|
+
if o_val == 1.0 and o_dim == 1: # Multiply by 1.0 dimensionless
|
364
|
+
return Quantity(q_val * other._si_factor, quantity.unit)
|
365
|
+
if q_val == 1.0 and q_dim == 1: # 1.0 dimensionless * something
|
366
|
+
return Quantity(o_val * quantity._si_factor, other.unit)
|
367
|
+
if q_val == 0.0 or o_val == 0.0: # Zero multiplication
|
368
|
+
return Quantity(0.0, quantity.unit if q_val == 0.0 else other.unit)
|
369
|
+
|
370
|
+
# DIMENSIONLESS FAST PATHS: Treat as scaling to avoid template lookup
|
371
|
+
if o_dim == 1: # Right dimensionless
|
372
|
+
return Quantity(q_val * o_val * other._si_factor, quantity.unit)
|
373
|
+
if q_dim == 1: # Left dimensionless
|
374
|
+
return Quantity(q_val * quantity._si_factor * o_val, other.unit)
|
375
|
+
|
376
|
+
# ULTRA-OPTIMIZED HARDCODED PATHS: Use multiplication instead of division
|
377
|
+
# Length × Length = Area (most common geometric operation)
|
378
|
+
if q_dim == LENGTH._signature and o_dim == LENGTH._signature:
|
379
|
+
# Eliminate division: (q_val * q_si) * (o_val * o_si) * (1/result_si)
|
380
|
+
result_val = q_val * quantity._si_factor * o_val * other._si_factor * _AREA_SI_RECIPROCAL
|
381
|
+
return Quantity(result_val, _CACHED_AREA_UNIT)
|
382
|
+
|
383
|
+
# Pressure × Area = Force (ASME pressure vessel calculations)
|
384
|
+
elif (q_dim == PRESSURE._signature and o_dim == AREA._signature) or (q_dim == AREA._signature and o_dim == PRESSURE._signature):
|
385
|
+
# Force SI factor is 1.0, so no reciprocal multiplication needed
|
386
|
+
result_val = q_val * quantity._si_factor * o_val * other._si_factor
|
387
|
+
return Quantity(result_val, _CACHED_FORCE_UNIT)
|
388
|
+
|
389
|
+
# Length × Area = Volume (geometric calculations)
|
390
|
+
elif (q_dim == LENGTH._signature and o_dim == AREA._signature) or (q_dim == AREA._signature and o_dim == LENGTH._signature):
|
391
|
+
result_val = q_val * quantity._si_factor * o_val * other._si_factor * _VOLUME_SI_RECIPROCAL
|
392
|
+
return Quantity(result_val, _CACHED_VOLUME_UNIT)
|
393
|
+
|
394
|
+
# TEMPLATE LOOKUP for remaining optimized cases
|
395
|
+
cache_key = (q_dim, o_dim)
|
396
|
+
template = _cache_manager.multiplication_templates.get(cache_key)
|
397
|
+
if template is not None:
|
398
|
+
# Single combined operation
|
399
|
+
result_value = q_val * o_val * (quantity._si_factor * other._si_factor * template.combined_si_factor)
|
400
|
+
# Skip object pooling overhead - just create directly
|
401
|
+
return Quantity(result_value, template.unit)
|
402
|
+
|
403
|
+
# LEGACY CACHE LOOKUP: For operations not in optimized templates
|
404
|
+
cached_unit = _cache_manager.multiplication_cache.get(cache_key)
|
405
|
+
if cached_unit is not None:
|
406
|
+
# INLINE SI CALCULATION: Single combined expression
|
407
|
+
result_si_value = (q_val * quantity._si_factor) * (o_val * other._si_factor)
|
408
|
+
return Quantity(result_si_value / cached_unit.si_factor, cached_unit)
|
409
|
+
|
410
|
+
# FALLBACK PATH: General case for uncached operations
|
411
|
+
result_dimension_sig = q_dim * o_dim
|
412
|
+
result_si_value = (q_val * quantity._si_factor) * (o_val * other._si_factor)
|
413
|
+
|
414
|
+
# Fast unit resolution
|
415
|
+
result_unit = UnitResolution.find_result_unit_fast(quantity, result_dimension_sig)
|
416
|
+
result_value = result_si_value / result_unit.si_factor
|
417
|
+
|
418
|
+
# Direct cache assignment for future use
|
419
|
+
if len(_cache_manager.multiplication_cache) < _cache_manager.max_cache_size:
|
420
|
+
_cache_manager.multiplication_cache[cache_key] = result_unit
|
421
|
+
|
422
|
+
return Quantity(result_value, result_unit)
|
423
|
+
|
424
|
+
@staticmethod
|
425
|
+
def divide(quantity, other):
|
426
|
+
"""Divide quantity by another quantity or scalar."""
|
427
|
+
# Fast path for numeric types - use type() for speed
|
428
|
+
if isinstance(other, int | float):
|
429
|
+
return Quantity(quantity.value / other, quantity.unit)
|
430
|
+
|
431
|
+
# Handle UnifiedVariable objects by using their quantity
|
432
|
+
if hasattr(other, "quantity") and getattr(other, "quantity", None) is not None:
|
433
|
+
other = other.quantity # type: ignore
|
434
|
+
|
435
|
+
# Type narrowing: at this point other should be Quantity
|
436
|
+
if not isinstance(other, Quantity):
|
437
|
+
raise TypeError(f"Expected Quantity, got {type(other)}")
|
438
|
+
|
439
|
+
# OPTIMIZATION: Fast path for dimensionless divisor (signature = 1)
|
440
|
+
# Treat dimensionless quantities more like scalars to reduce overhead
|
441
|
+
if other._dimension_sig == 1: # DIMENSIONLESS divisor
|
442
|
+
# Division by dimensionless: just scale the value, keep original unit
|
443
|
+
divisor = other.value * other._si_factor
|
444
|
+
return Quantity(quantity.value / divisor, quantity.unit)
|
445
|
+
|
446
|
+
# Check division cache first
|
447
|
+
cached_unit = _cache_manager.get_division_result(quantity._dimension_sig, other._dimension_sig)
|
448
|
+
if cached_unit:
|
449
|
+
result_si_value = (quantity.value * quantity._si_factor) / (other.value * other._si_factor)
|
450
|
+
return Quantity(result_si_value / cached_unit.si_factor, cached_unit)
|
451
|
+
|
452
|
+
# Calculate result dimension and value
|
453
|
+
result_dimension_sig = quantity._dimension_sig / other._dimension_sig
|
454
|
+
result_si_value = (quantity.value * quantity._si_factor) / (other.value * other._si_factor)
|
455
|
+
|
456
|
+
# Find appropriate result unit
|
457
|
+
result_unit = UnitResolution.find_result_unit_fast(quantity, result_dimension_sig)
|
458
|
+
result_value = result_si_value / result_unit.si_factor
|
459
|
+
|
460
|
+
# Cache the result for future use
|
461
|
+
_cache_manager.cache_division_result(quantity._dimension_sig, other._dimension_sig, result_unit)
|
462
|
+
return Quantity(result_value, result_unit)
|
463
|
+
|
464
|
+
|
465
|
+
class ComparisonOperations:
|
466
|
+
"""Handles comparison operations for quantities."""
|
467
|
+
|
468
|
+
@staticmethod
|
469
|
+
def less_than(quantity, other):
|
470
|
+
"""Compare if this quantity is less than another."""
|
471
|
+
# Optimized comparison with bulk operations
|
472
|
+
if quantity._dimension_sig != other._dimension_sig:
|
473
|
+
raise ValueError(ErrorMessages.INCOMPATIBLE_COMPARISON)
|
474
|
+
|
475
|
+
# Fast path for same units (use name comparison for speed)
|
476
|
+
if quantity.unit.name == other.unit.name:
|
477
|
+
return quantity.value < other.value
|
478
|
+
|
479
|
+
# Convert using cached SI factors (bulk assignment)
|
480
|
+
self_si, other_si = quantity._si_factor, other._si_factor
|
481
|
+
return quantity.value < (other.value * other_si / self_si)
|
482
|
+
|
483
|
+
@staticmethod
|
484
|
+
def equals(quantity, other):
|
485
|
+
"""Check equality between quantities."""
|
486
|
+
if not isinstance(other, Quantity):
|
487
|
+
return False
|
488
|
+
if quantity._dimension_sig != other._dimension_sig:
|
489
|
+
return False
|
490
|
+
|
491
|
+
# Fast path for same units (use name comparison)
|
492
|
+
if quantity.unit.name == other.unit.name:
|
493
|
+
return abs(quantity.value - other.value) < FLOAT_EQUALITY_TOLERANCE
|
494
|
+
|
495
|
+
# Convert using cached SI factors (bulk assignment)
|
496
|
+
self_si, other_si = quantity._si_factor, other._si_factor
|
497
|
+
return abs(quantity.value - (other.value * other_si / self_si)) < FLOAT_EQUALITY_TOLERANCE
|
498
|
+
|
499
|
+
|
500
|
+
class UnitConversions:
|
501
|
+
"""Handles unit conversion operations."""
|
502
|
+
|
503
|
+
@staticmethod
|
504
|
+
def to(quantity, target_unit):
|
505
|
+
"""Convert quantity to target unit."""
|
506
|
+
# Ultra-fast same unit check using name comparison
|
507
|
+
if quantity.unit.name == target_unit.name:
|
508
|
+
return Quantity(quantity.value, target_unit)
|
509
|
+
|
510
|
+
# Direct SI factor conversion - avoid registry lookup
|
511
|
+
converted_value = quantity.value * quantity._si_factor / target_unit.si_factor
|
512
|
+
return Quantity(converted_value, target_unit)
|
513
|
+
|
514
|
+
|
515
|
+
class QuantityFormatting:
|
516
|
+
"""Handles string formatting for quantities."""
|
517
|
+
|
518
|
+
@staticmethod
|
519
|
+
def to_string(quantity):
|
520
|
+
"""String representation of the quantity."""
|
521
|
+
# Optimized string representation (caching removed for simplicity)
|
522
|
+
return f"{quantity.value} {quantity.unit.symbol}"
|
523
|
+
|
524
|
+
@staticmethod
|
525
|
+
def to_repr(quantity):
|
526
|
+
"""Detailed representation of the quantity."""
|
527
|
+
return f"Quantity({quantity.value}, {quantity.unit.name})"
|
528
|
+
|
529
|
+
|
530
|
+
class UnitResolution:
|
531
|
+
"""Handles unit resolution for dimensional operations."""
|
532
|
+
|
533
|
+
@staticmethod
|
534
|
+
def find_result_unit_fast(_quantity, result_dimension_sig: int | float) -> UnitConstant:
|
535
|
+
"""Ultra-fast unit finding using pre-cached dimension signatures."""
|
536
|
+
# CRITICAL OPTIMIZATION: Direct dictionary access (O(1)) for common dimensions
|
537
|
+
dimension_cache = registry._dimension_cache
|
538
|
+
if result_dimension_sig in dimension_cache:
|
539
|
+
return dimension_cache[result_dimension_sig]
|
540
|
+
|
541
|
+
# OPTIMIZED PATH: For rare combined dimensions, create SI base unit efficiently
|
542
|
+
# Create new dimension signature from the computed value
|
543
|
+
result_dimension = DimensionSignature(result_dimension_sig) # type: ignore[misc]
|
544
|
+
|
545
|
+
# FAST CREATION: Minimize string operations for performance
|
546
|
+
sig_hash = abs(hash(result_dimension_sig)) % 10000
|
547
|
+
si_name = f"si_derived_{sig_hash}"
|
548
|
+
si_symbol = f"SI_{sig_hash % 1000}"
|
549
|
+
|
550
|
+
# STREAMLINED CREATION: Avoid unnecessary method calls
|
551
|
+
temp_unit = UnitDefinition(name=si_name, symbol=si_symbol, dimension=result_dimension, si_factor=1.0)
|
552
|
+
result_unit = UnitConstant(temp_unit)
|
553
|
+
|
554
|
+
# DIRECT CACHE ASSIGNMENT: Bypass cache size checks for dimension cache
|
555
|
+
dimension_cache[result_dimension_sig] = result_unit
|
556
|
+
return result_unit
|
557
|
+
|
558
|
+
@staticmethod
|
559
|
+
def create_si_unit_name(_quantity, dimension: DimensionSignature) -> str:
|
560
|
+
"""Create descriptive SI unit name based on dimensional analysis."""
|
561
|
+
# For now, return a generic SI unit name. In the future, this could be enhanced
|
562
|
+
# to parse the dimension signature and create descriptive names like "newton_per_meter"
|
563
|
+
return f"si_derived_unit_{abs(hash(dimension._signature)) % 10000}"
|
564
|
+
|
565
|
+
@staticmethod
|
566
|
+
def create_si_unit_symbol(_quantity, dimension: DimensionSignature) -> str:
|
567
|
+
"""Create SI unit symbol based on dimensional analysis."""
|
568
|
+
# For complex units, return descriptive symbol based on common engineering units
|
569
|
+
# Use dimension signature for unique symbol generation
|
570
|
+
return f"SI_{abs(hash(dimension._signature)) % 1000}"
|
571
|
+
|
572
|
+
@staticmethod
|
573
|
+
def find_result_unit(quantity, result_dimension: DimensionSignature) -> UnitConstant:
|
574
|
+
"""Legacy method - kept for compatibility."""
|
575
|
+
return UnitResolution.find_result_unit_fast(quantity, result_dimension._signature)
|
576
|
+
|
577
|
+
|
578
|
+
class TypeSafeSetter:
|
579
|
+
"""Basic type-safe setter that accepts compatible units."""
|
580
|
+
|
581
|
+
def __init__(self, variable, value: float):
|
582
|
+
self.variable = variable
|
583
|
+
self.value = value
|
584
|
+
|
585
|
+
def with_unit(self, unit: UnitConstant):
|
586
|
+
"""Set with type-safe unit constant."""
|
587
|
+
if not self.variable.expected_dimension.is_compatible(unit.dimension):
|
588
|
+
raise TypeError(ErrorMessages.INCOMPATIBLE_DIMENSION.format(unit.name))
|
589
|
+
|
590
|
+
self.variable.quantity = Quantity(self.value, unit)
|
591
|
+
return self.variable
|
592
|
+
|
593
|
+
|
594
|
+
class Quantity:
|
595
|
+
"""High-performance quantity optimized for engineering calculations."""
|
596
|
+
|
597
|
+
__slots__ = ("value", "unit", "dimension", "_si_factor", "_dimension_sig")
|
598
|
+
|
599
|
+
def __init__(self, value: float, unit: UnitConstant):
|
600
|
+
# Optimized initialization with cached values for performance
|
601
|
+
self.value = value if isinstance(value, float) else float(value)
|
602
|
+
self.unit = unit
|
603
|
+
self.dimension = unit.dimension
|
604
|
+
# Cache commonly used values to avoid repeated attribute access
|
605
|
+
self._si_factor = unit.si_factor
|
606
|
+
self._dimension_sig = unit.dimension._signature
|
607
|
+
|
608
|
+
@classmethod
|
609
|
+
def get_cached(cls, value: int | float, unit: UnitConstant):
|
610
|
+
"""Get cached instance for small integers, otherwise create new."""
|
611
|
+
# Check cache for small integers
|
612
|
+
cached = _cache_manager.get_cached_quantity(value, unit)
|
613
|
+
if cached:
|
614
|
+
return cached
|
615
|
+
|
616
|
+
# Create new quantity
|
617
|
+
obj = cls(value, unit)
|
618
|
+
|
619
|
+
# Cache if it qualifies
|
620
|
+
_cache_manager.cache_quantity(value, unit, obj)
|
621
|
+
return obj
|
622
|
+
|
623
|
+
def __str__(self) -> str:
|
624
|
+
return QuantityFormatting.to_string(self)
|
625
|
+
|
626
|
+
def __repr__(self) -> str:
|
627
|
+
return QuantityFormatting.to_repr(self)
|
628
|
+
|
629
|
+
# Ultra-fast arithmetic with dimensional checking
|
630
|
+
def __add__(self, other: Quantity) -> Quantity:
|
631
|
+
return ArithmeticOperations.add(self, other)
|
632
|
+
|
633
|
+
def __sub__(self, other: Quantity) -> Quantity:
|
634
|
+
return ArithmeticOperations.subtract(self, other)
|
635
|
+
|
636
|
+
def __mul__(self, other: Quantity | float | int) -> Quantity:
|
637
|
+
return ArithmeticOperations.multiply(self, other)
|
638
|
+
|
639
|
+
def __rmul__(self, other: float | int) -> Quantity:
|
640
|
+
"""Reverse multiplication for cases like 2 * quantity."""
|
641
|
+
if isinstance(other, int | float):
|
642
|
+
return Quantity(other * self.value, self.unit)
|
643
|
+
return NotImplemented
|
644
|
+
|
645
|
+
def __truediv__(self, other: Quantity | float | int) -> Quantity:
|
646
|
+
return ArithmeticOperations.divide(self, other)
|
647
|
+
|
648
|
+
def _find_result_unit_fast(self, result_dimension_sig: int | float) -> UnitConstant:
|
649
|
+
"""Delegate to unit resolution component."""
|
650
|
+
return UnitResolution.find_result_unit_fast(self, result_dimension_sig)
|
651
|
+
|
652
|
+
def _create_si_unit_name(self, dimension: DimensionSignature) -> str:
|
653
|
+
"""Delegate to unit resolution component."""
|
654
|
+
return UnitResolution.create_si_unit_name(self, dimension)
|
655
|
+
|
656
|
+
def _create_si_unit_symbol(self, dimension: DimensionSignature) -> str:
|
657
|
+
"""Delegate to unit resolution component."""
|
658
|
+
return UnitResolution.create_si_unit_symbol(self, dimension)
|
659
|
+
|
660
|
+
def _find_result_unit(self, result_dimension: DimensionSignature) -> UnitConstant:
|
661
|
+
"""Legacy method - kept for compatibility."""
|
662
|
+
return UnitResolution.find_result_unit(self, result_dimension)
|
663
|
+
|
664
|
+
# Ultra-fast comparisons
|
665
|
+
def __lt__(self, other: Quantity) -> bool:
|
666
|
+
return ComparisonOperations.less_than(self, other)
|
667
|
+
|
668
|
+
def __eq__(self, other) -> bool:
|
669
|
+
return ComparisonOperations.equals(self, other)
|
670
|
+
|
671
|
+
def to(self, target_unit: UnitConstant) -> Quantity:
|
672
|
+
"""Ultra-fast unit conversion with optimized same-unit check."""
|
673
|
+
return UnitConversions.to(self, target_unit)
|
674
|
+
|
675
|
+
|
676
|
+
# Initialize cache manager at module load
|
677
|
+
_cache_manager.initialize_common_operations()
|