qnty 0.1.3__tar.gz → 0.1.4__tar.gz
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-0.1.3 → qnty-0.1.4}/PKG-INFO +1 -1
- {qnty-0.1.3 → qnty-0.1.4}/pyproject.toml +1 -1
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/equations/equation.py +29 -3
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/expressions/nodes.py +21 -13
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/problems/problem.py +4 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/quantities/base_qnty.py +38 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/quantities/field_converters.py +5 -2
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/quantities/field_qnty.py +25 -5
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/quantities/field_vars.py +3527 -1606
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/quantities/field_vars.pyi +107 -107
- qnty-0.1.4/src/qnty/utils/unit_suggestions.py +198 -0
- {qnty-0.1.3 → qnty-0.1.4}/README.md +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/__init__.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/constants/__init__.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/constants/numerical.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/constants/solvers.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/constants/tests.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/dimensions/__init__.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/dimensions/base.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/dimensions/field_dims.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/dimensions/field_dims.pyi +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/dimensions/signature.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/equations/__init__.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/equations/system.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/expressions/__init__.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/expressions/formatter.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/expressions/functions.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/expressions/types.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/extensions/__init__.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/extensions/integration/__init__.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/extensions/plotting/__init__.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/extensions/reporting/__init__.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/problems/__init__.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/problems/composition.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/problems/rules.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/problems/solving.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/problems/validation.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/quantities/__init__.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/quantities/field_setter.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/quantities/field_setter.pyi +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/solving/__init__.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/solving/manager.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/solving/order.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/solving/solvers/__init__.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/solving/solvers/base.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/solving/solvers/iterative.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/solving/solvers/simultaneous.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/units/__init__.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/units/field_units.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/units/field_units.pyi +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/units/prefixes.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/units/registry.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/utils/__init__.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/utils/caching/__init__.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/utils/caching/manager.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/utils/error_handling/__init__.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/utils/error_handling/context.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/utils/error_handling/exceptions.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/utils/error_handling/handlers.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/utils/logging.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/utils/protocols.py +0 -0
- {qnty-0.1.3 → qnty-0.1.4}/src/qnty/utils/scope_discovery.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: qnty
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.4
|
4
4
|
Summary: High-performance unit system library for Python with dimensional safety and fast unit conversions
|
5
5
|
License: Apache-2.0
|
6
6
|
Keywords: units,dimensional analysis,engineering,physics,quantities,measurements
|
@@ -98,12 +98,38 @@ class Equation:
|
|
98
98
|
if var_obj is None:
|
99
99
|
raise ValueError(f"Variable '{target_var}' not found in variable_values")
|
100
100
|
|
101
|
-
# Convert result to
|
101
|
+
# Convert result to preferred unit or original unit if available
|
102
|
+
target_unit_constant = None
|
103
|
+
|
104
|
+
# First priority: existing quantity unit
|
102
105
|
if var_obj.quantity is not None and var_obj.quantity.unit is not None:
|
106
|
+
target_unit_constant = var_obj.quantity.unit
|
107
|
+
# Second priority: preferred unit from constructor
|
108
|
+
elif hasattr(var_obj, 'preferred_unit') and var_obj.preferred_unit is not None:
|
109
|
+
# Look up unit constant from string name
|
110
|
+
preferred_unit_name = var_obj.preferred_unit
|
103
111
|
try:
|
104
|
-
|
112
|
+
# Get the dimension-specific units class
|
113
|
+
class_name = var_obj.__class__.__name__
|
114
|
+
units_class_name = f'{class_name}Units'
|
115
|
+
|
116
|
+
# Import field_units dynamically to avoid circular imports
|
117
|
+
from ..units import field_units
|
118
|
+
units_class = getattr(field_units, units_class_name, None)
|
119
|
+
if units_class and hasattr(units_class, preferred_unit_name):
|
120
|
+
target_unit_constant = getattr(units_class, preferred_unit_name)
|
121
|
+
_logger.debug(f"Found preferred unit constant: {preferred_unit_name}")
|
122
|
+
else:
|
123
|
+
_logger.debug(f"Could not find unit constant for {preferred_unit_name} in {units_class_name}")
|
124
|
+
except (ImportError, AttributeError) as e:
|
125
|
+
_logger.debug(f"Failed to lookup preferred unit {preferred_unit_name}: {e}")
|
126
|
+
|
127
|
+
if target_unit_constant is not None:
|
128
|
+
try:
|
129
|
+
result_qty = result_qty.to(target_unit_constant)
|
130
|
+
_logger.debug(f"Converted {target_var} result to preferred unit: {target_unit_constant.symbol}")
|
105
131
|
except (ValueError, TypeError, AttributeError) as e:
|
106
|
-
_logger.debug(f"Unit conversion failed for {target_var}: {e}. Using calculated unit.")
|
132
|
+
_logger.debug(f"Unit conversion failed for {target_var} to {target_unit_constant}: {e}. Using calculated unit.")
|
107
133
|
|
108
134
|
# Update the variable and return it
|
109
135
|
var_obj.quantity = result_qty
|
@@ -304,32 +304,37 @@ class BinaryOperation(Expression):
|
|
304
304
|
right_value = right_val.value
|
305
305
|
|
306
306
|
# ENHANCED FAST PATHS: Check most common optimizations first
|
307
|
+
# BUT ONLY when they don't affect dimensional analysis!
|
308
|
+
|
307
309
|
# Identity optimizations (1.0 multiplication) - most frequent case
|
308
|
-
|
310
|
+
# Only safe when the 1.0 value is dimensionless
|
311
|
+
if right_value == 1.0 and right_val._dimension_sig == 1:
|
309
312
|
return left_val
|
310
|
-
elif left_value == 1.0:
|
313
|
+
elif left_value == 1.0 and left_val._dimension_sig == 1:
|
311
314
|
return right_val
|
312
315
|
|
313
316
|
# Zero optimizations - second most common
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
return
|
317
|
+
# Zero multiplication always gives zero, but we need proper units via full multiplication
|
318
|
+
elif right_value == 0.0 or left_value == 0.0:
|
319
|
+
# Use full multiplication to get correct dimensional result for zero
|
320
|
+
return left_val * right_val
|
318
321
|
|
319
322
|
# Additional fast paths for common values
|
320
|
-
|
323
|
+
# Only safe when the scalar value is dimensionless
|
324
|
+
elif right_value == -1.0 and right_val._dimension_sig == 1:
|
321
325
|
return Quantity(-left_value, left_val.unit)
|
322
|
-
elif left_value == -1.0:
|
326
|
+
elif left_value == -1.0 and left_val._dimension_sig == 1:
|
323
327
|
return Quantity(-right_value, right_val.unit)
|
324
328
|
|
325
329
|
# ADDITIONAL COMMON CASES: Powers of 2 and 0.5 (very common in engineering)
|
326
|
-
|
330
|
+
# Only safe when the scalar value is dimensionless
|
331
|
+
elif right_value == 2.0 and right_val._dimension_sig == 1:
|
327
332
|
return Quantity(left_value * 2.0, left_val.unit)
|
328
|
-
elif left_value == 2.0:
|
333
|
+
elif left_value == 2.0 and left_val._dimension_sig == 1:
|
329
334
|
return Quantity(right_value * 2.0, right_val.unit)
|
330
|
-
elif right_value == 0.5:
|
335
|
+
elif right_value == 0.5 and right_val._dimension_sig == 1:
|
331
336
|
return Quantity(left_value * 0.5, left_val.unit)
|
332
|
-
elif left_value == 0.5:
|
337
|
+
elif left_value == 0.5 and left_val._dimension_sig == 1:
|
333
338
|
return Quantity(right_value * 0.5, right_val.unit)
|
334
339
|
|
335
340
|
# OPTIMIZED REGULAR CASE: Use the enhanced multiplication from base_qnty
|
@@ -432,7 +437,10 @@ class BinaryOperation(Expression):
|
|
432
437
|
|
433
438
|
# Perform comparison using optimized dispatch
|
434
439
|
result = self._perform_comparison(left_val.value, right_val.value)
|
435
|
-
|
440
|
+
|
441
|
+
# Import BooleanQuantity locally to avoid circular imports
|
442
|
+
from ..quantities.base_qnty import BooleanQuantity
|
443
|
+
return BooleanQuantity(result)
|
436
444
|
|
437
445
|
def _normalize_comparison_units(self, left_val: "Quantity", right_val: "Quantity") -> tuple["Quantity", "Quantity"]:
|
438
446
|
"""Normalize units for comparison operations."""
|
@@ -372,6 +372,10 @@ class Problem(ValidationMixin):
|
|
372
372
|
cloned.quantity = variable.quantity # Keep reference to same quantity - units must not be copied
|
373
373
|
cloned.is_known = variable.is_known
|
374
374
|
|
375
|
+
# Copy unit preference if it exists
|
376
|
+
if hasattr(variable, '_preferred_unit'):
|
377
|
+
cloned._preferred_unit = variable._preferred_unit
|
378
|
+
|
375
379
|
# Ensure the cloned variable has fresh validation checks
|
376
380
|
if hasattr(variable, "validation_checks") and hasattr(cloned, "validation_checks"):
|
377
381
|
cloned.validation_checks = []
|
@@ -673,5 +673,43 @@ class Quantity:
|
|
673
673
|
return UnitConversions.to(self, target_unit)
|
674
674
|
|
675
675
|
|
676
|
+
class BooleanQuantity(Quantity):
|
677
|
+
"""A quantity that represents a boolean value but maintains Quantity compatibility.
|
678
|
+
|
679
|
+
This class is used for comparison operations that need to return boolean results
|
680
|
+
while maintaining the Expression interface requirement of returning Quantity objects.
|
681
|
+
"""
|
682
|
+
|
683
|
+
__slots__ = ("_boolean_value",)
|
684
|
+
|
685
|
+
def __init__(self, boolean_value: bool):
|
686
|
+
"""Initialize with a boolean value."""
|
687
|
+
# Store the actual boolean value
|
688
|
+
self._boolean_value = boolean_value
|
689
|
+
|
690
|
+
# Initialize parent with numeric representation
|
691
|
+
super().__init__(
|
692
|
+
1.0 if boolean_value else 0.0,
|
693
|
+
DimensionlessUnits.dimensionless
|
694
|
+
)
|
695
|
+
|
696
|
+
def __str__(self) -> str:
|
697
|
+
"""Display as True/False instead of 1.0/0.0."""
|
698
|
+
return str(self._boolean_value)
|
699
|
+
|
700
|
+
def __repr__(self) -> str:
|
701
|
+
"""Display as BooleanQuantity(True/False)."""
|
702
|
+
return f"BooleanQuantity({self._boolean_value})"
|
703
|
+
|
704
|
+
def __bool__(self) -> bool:
|
705
|
+
"""Return the actual boolean value."""
|
706
|
+
return self._boolean_value
|
707
|
+
|
708
|
+
@property
|
709
|
+
def boolean_value(self) -> bool:
|
710
|
+
"""Access the boolean value directly."""
|
711
|
+
return self._boolean_value
|
712
|
+
|
713
|
+
|
676
714
|
# Initialize cache manager at module load
|
677
715
|
_cache_manager.initialize_common_operations()
|
@@ -13,8 +13,8 @@ from typing import TYPE_CHECKING, Final
|
|
13
13
|
if TYPE_CHECKING:
|
14
14
|
from ..quantities.field_qnty import FieldQnty
|
15
15
|
|
16
|
-
from ..units import field_units
|
17
16
|
from .base_qnty import Quantity
|
17
|
+
from ..units import field_units
|
18
18
|
|
19
19
|
# ===== BASE CONVERTER CLASSES =====
|
20
20
|
|
@@ -33,7 +33,10 @@ class UnitConverter:
|
|
33
33
|
units_class = getattr(field_units, units_class_name, None)
|
34
34
|
if units_class and hasattr(units_class, unit_name):
|
35
35
|
return getattr(units_class, unit_name)
|
36
|
-
|
36
|
+
# Raise error with suggestions
|
37
|
+
from ..utils.unit_suggestions import create_unit_validation_error
|
38
|
+
var_type = getattr(self.variable, '__class__', type(self.variable)).__name__
|
39
|
+
raise create_unit_validation_error(unit_name, var_type)
|
37
40
|
|
38
41
|
def _convert_quantity(self, unit_constant, modify_original: bool = False):
|
39
42
|
"""Convert quantity to specified unit."""
|
@@ -43,6 +43,7 @@ class QuantityManagementMixin:
|
|
43
43
|
self._is_known: bool = False
|
44
44
|
self._name: str = ""
|
45
45
|
self._symbol: str | None = None
|
46
|
+
self._preferred_unit: str | None = None
|
46
47
|
|
47
48
|
@property
|
48
49
|
def quantity(self) -> Quantity | None:
|
@@ -90,6 +91,15 @@ class QuantityManagementMixin:
|
|
90
91
|
|
91
92
|
return self
|
92
93
|
|
94
|
+
def _set_preferred_unit(self, unit: str) -> None:
|
95
|
+
"""Set the preferred unit for solver results."""
|
96
|
+
self._preferred_unit = unit
|
97
|
+
|
98
|
+
@property
|
99
|
+
def preferred_unit(self) -> str | None:
|
100
|
+
"""Get the preferred unit for this variable."""
|
101
|
+
return self._preferred_unit
|
102
|
+
|
93
103
|
|
94
104
|
class FlexibleConstructorMixin:
|
95
105
|
"""Handles flexible variable initialization patterns maintaining backward compatibility."""
|
@@ -180,10 +190,9 @@ class FlexibleConstructorMixin:
|
|
180
190
|
return result
|
181
191
|
|
182
192
|
# Fallback to direct quantity creation
|
183
|
-
from ..units import DimensionlessUnits
|
184
193
|
from .base_qnty import Quantity
|
185
194
|
|
186
|
-
# Try to find the unit in the registry
|
195
|
+
# Try to find the unit in the registry
|
187
196
|
try:
|
188
197
|
from ..units.registry import registry
|
189
198
|
|
@@ -194,8 +203,10 @@ class FlexibleConstructorMixin:
|
|
194
203
|
except Exception:
|
195
204
|
pass
|
196
205
|
|
197
|
-
#
|
198
|
-
|
206
|
+
# If unit is not found, raise error with suggestions instead of falling back to dimensionless
|
207
|
+
from ..utils.unit_suggestions import create_unit_validation_error
|
208
|
+
var_type = getattr(self, '__class__', type(self)).__name__
|
209
|
+
raise create_unit_validation_error(str(unit), var_type)
|
199
210
|
|
200
211
|
def _find_unit_property(self, setter: TypeSafeSetter, unit: str) -> str | None:
|
201
212
|
"""Find unit property with simple lookup."""
|
@@ -609,6 +620,12 @@ class ExpressionMixin:
|
|
609
620
|
|
610
621
|
def __gt__(self, other) -> Expression:
|
611
622
|
return self.gt(other)
|
623
|
+
|
624
|
+
def __eq__(self, other) -> Expression: # type: ignore[override]
|
625
|
+
return self.eq(other)
|
626
|
+
|
627
|
+
def __ne__(self, other) -> Expression: # type: ignore[override]
|
628
|
+
return self.ne(other)
|
612
629
|
|
613
630
|
def eq(self, other) -> Expression:
|
614
631
|
"""Create equality comparison expression."""
|
@@ -827,7 +844,10 @@ class UnitConverter:
|
|
827
844
|
except Exception:
|
828
845
|
pass
|
829
846
|
|
830
|
-
|
847
|
+
# Raise error with suggestions
|
848
|
+
from ..utils.unit_suggestions import create_unit_validation_error
|
849
|
+
var_type = getattr(self.variable, '__class__', type(self.variable)).__name__
|
850
|
+
raise create_unit_validation_error(unit_str, var_type)
|
831
851
|
|
832
852
|
def _convert_quantity(self, to_unit_constant, modify_original: bool = True):
|
833
853
|
"""Convert the variable's quantity to the specified unit."""
|