qnty 0.0.1__py3-none-any.whl → 0.0.3__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 +175 -0
- qnty/dimension.py +113 -14
- qnty/equation.py +216 -0
- qnty/expression.py +480 -0
- qnty/prefixes.py +229 -0
- qnty/unit.py +76 -8
- qnty/unit_types/__init__.py +0 -0
- qnty/unit_types/base.py +47 -0
- qnty/units.py +8078 -33
- qnty/variable.py +69 -29
- qnty/variable_types/__init__.py +0 -0
- qnty/variable_types/base.py +58 -0
- qnty/variable_types/expression_variable.py +68 -0
- qnty/variable_types/typed_variable.py +92 -0
- qnty/variables.py +2291 -19
- qnty/variables.pyi +6097 -0
- {qnty-0.0.1.dist-info → qnty-0.0.3.dist-info}/METADATA +109 -22
- qnty-0.0.3.dist-info/RECORD +19 -0
- qnty/setters.py +0 -89
- qnty-0.0.1.dist-info/RECORD +0 -10
- {qnty-0.0.1.dist-info → qnty-0.0.3.dist-info}/WHEEL +0 -0
qnty/variable.py
CHANGED
@@ -2,21 +2,37 @@
|
|
2
2
|
High-Performance Quantity and Variables
|
3
3
|
========================================
|
4
4
|
|
5
|
-
FastQuantity class and type-safe variables optimized for engineering calculations
|
5
|
+
FastQuantity class and type-safe variables optimized for engineering calculations
|
6
6
|
with dimensional safety.
|
7
7
|
"""
|
8
8
|
|
9
|
-
from
|
10
|
-
|
9
|
+
from __future__ import annotations
|
10
|
+
|
11
|
+
from typing import Generic, Self, TypeVar
|
12
|
+
|
13
|
+
from .dimension import AREA, DIMENSIONLESS, FORCE, LENGTH, PRESSURE, VOLUME, DimensionSignature
|
14
|
+
from .dimension import ENERGY_HEAT_WORK as ENERGY
|
11
15
|
from .unit import UnitConstant, UnitDefinition, registry
|
12
16
|
from .units import DimensionlessUnits, LengthUnits, PressureUnits
|
13
17
|
|
14
|
-
|
15
|
-
|
18
|
+
# TypeVar for generic dimensional types
|
19
|
+
DimensionType = TypeVar('DimensionType', bound='FastQuantity')
|
16
20
|
|
17
21
|
|
18
|
-
|
19
|
-
|
22
|
+
class TypeSafeSetter:
|
23
|
+
"""Basic type-safe setter that accepts compatible units."""
|
24
|
+
|
25
|
+
def __init__(self, variable: TypeSafeVariable, value: float):
|
26
|
+
self.variable = variable
|
27
|
+
self.value = value
|
28
|
+
|
29
|
+
def with_unit(self, unit: UnitConstant) -> TypeSafeVariable:
|
30
|
+
"""Set with type-safe unit constant."""
|
31
|
+
if not self.variable.expected_dimension.is_compatible(unit.dimension):
|
32
|
+
raise TypeError(f"Unit {unit.name} incompatible with expected dimension")
|
33
|
+
|
34
|
+
self.variable.quantity = FastQuantity(self.value, unit)
|
35
|
+
return self.variable
|
20
36
|
|
21
37
|
|
22
38
|
class FastQuantity:
|
@@ -39,7 +55,7 @@ class FastQuantity:
|
|
39
55
|
return f"FastQuantity({self.value}, {self.unit.name})"
|
40
56
|
|
41
57
|
# Ultra-fast arithmetic with dimensional checking
|
42
|
-
def __add__(self, other:
|
58
|
+
def __add__(self, other: FastQuantity) -> FastQuantity:
|
43
59
|
# Fast dimension compatibility check using cached signatures
|
44
60
|
if self._dimension_sig != other._dimension_sig:
|
45
61
|
raise ValueError(f"Cannot add {self.unit.name} and {other.unit.name}")
|
@@ -52,7 +68,7 @@ class FastQuantity:
|
|
52
68
|
other_value = other.value * other._si_factor / self._si_factor
|
53
69
|
return FastQuantity(self.value + other_value, self.unit)
|
54
70
|
|
55
|
-
def __sub__(self, other:
|
71
|
+
def __sub__(self, other: FastQuantity) -> FastQuantity:
|
56
72
|
# Fast dimension compatibility check using cached signatures
|
57
73
|
if self._dimension_sig != other._dimension_sig:
|
58
74
|
raise ValueError(f"Cannot subtract {other.unit.name} from {self.unit.name}")
|
@@ -65,8 +81,8 @@ class FastQuantity:
|
|
65
81
|
other_value = other.value * other._si_factor / self._si_factor
|
66
82
|
return FastQuantity(self.value - other_value, self.unit)
|
67
83
|
|
68
|
-
def __mul__(self, other:
|
69
|
-
if isinstance(other,
|
84
|
+
def __mul__(self, other: FastQuantity | float | int) -> FastQuantity:
|
85
|
+
if isinstance(other, int | float):
|
70
86
|
return FastQuantity(self.value * other, self.unit)
|
71
87
|
|
72
88
|
# Fast dimensional analysis using cached signatures
|
@@ -83,14 +99,14 @@ class FastQuantity:
|
|
83
99
|
|
84
100
|
return FastQuantity(result_value, result_unit)
|
85
101
|
|
86
|
-
def __rmul__(self, other:
|
102
|
+
def __rmul__(self, other: float | int) -> FastQuantity:
|
87
103
|
"""Reverse multiplication for cases like 2 * quantity."""
|
88
|
-
if isinstance(other,
|
104
|
+
if isinstance(other, int | float):
|
89
105
|
return FastQuantity(other * self.value, self.unit)
|
90
106
|
return NotImplemented
|
91
107
|
|
92
|
-
def __truediv__(self, other:
|
93
|
-
if isinstance(other,
|
108
|
+
def __truediv__(self, other: FastQuantity | float | int) -> FastQuantity:
|
109
|
+
if isinstance(other, int | float):
|
94
110
|
return FastQuantity(self.value / other, self.unit)
|
95
111
|
|
96
112
|
# Fast dimensional analysis using cached signatures
|
@@ -107,8 +123,8 @@ class FastQuantity:
|
|
107
123
|
|
108
124
|
return FastQuantity(result_value, result_unit)
|
109
125
|
|
110
|
-
def _find_result_unit_fast(self, result_dimension_sig: int,
|
111
|
-
left_qty:
|
126
|
+
def _find_result_unit_fast(self, result_dimension_sig: int,
|
127
|
+
left_qty: FastQuantity, right_qty: FastQuantity) -> UnitConstant:
|
112
128
|
"""Ultra-fast unit finding using cached dimension signatures."""
|
113
129
|
|
114
130
|
# Initialize dimension cache if empty
|
@@ -130,7 +146,7 @@ class FastQuantity:
|
|
130
146
|
# For rare combined dimensions, create temporary unit
|
131
147
|
temp_unit = UnitDefinition(
|
132
148
|
name=f"combined_{result_dimension_sig}",
|
133
|
-
symbol="combined",
|
149
|
+
symbol="combined",
|
134
150
|
dimension=DimensionSignature(result_dimension_sig),
|
135
151
|
si_factor=1.0
|
136
152
|
)
|
@@ -140,13 +156,13 @@ class FastQuantity:
|
|
140
156
|
registry._dimension_cache[result_dimension_sig] = result_unit
|
141
157
|
return result_unit
|
142
158
|
|
143
|
-
def _find_result_unit(self, result_dimension: DimensionSignature,
|
144
|
-
left_qty:
|
159
|
+
def _find_result_unit(self, result_dimension: DimensionSignature,
|
160
|
+
left_qty: FastQuantity, right_qty: FastQuantity) -> UnitConstant:
|
145
161
|
"""Legacy method - kept for compatibility."""
|
146
162
|
return self._find_result_unit_fast(result_dimension._signature, left_qty, right_qty)
|
147
163
|
|
148
164
|
# Ultra-fast comparisons
|
149
|
-
def __lt__(self, other:
|
165
|
+
def __lt__(self, other: FastQuantity) -> bool:
|
150
166
|
if self._dimension_sig != other._dimension_sig:
|
151
167
|
raise ValueError("Cannot compare incompatible dimensions")
|
152
168
|
|
@@ -172,7 +188,7 @@ class FastQuantity:
|
|
172
188
|
other_value = other.value * other._si_factor / self._si_factor
|
173
189
|
return abs(self.value - other_value) < 1e-10
|
174
190
|
|
175
|
-
def to(self, target_unit: UnitConstant) ->
|
191
|
+
def to(self, target_unit: UnitConstant) -> FastQuantity:
|
176
192
|
"""Ultra-fast unit conversion."""
|
177
193
|
if self.unit == target_unit:
|
178
194
|
return FastQuantity(self.value, target_unit)
|
@@ -183,16 +199,40 @@ class FastQuantity:
|
|
183
199
|
|
184
200
|
|
185
201
|
class TypeSafeVariable(Generic[DimensionType]):
|
186
|
-
"""
|
202
|
+
"""
|
203
|
+
Base class for type-safe variables with dimensional checking.
|
187
204
|
|
188
|
-
|
205
|
+
This is a simple data container without dependencies on expressions or equations.
|
206
|
+
Mathematical operations are added by subclasses or mixins.
|
207
|
+
"""
|
208
|
+
|
209
|
+
# Class attribute defining which setter to use - subclasses can override
|
210
|
+
_setter_class = TypeSafeSetter
|
211
|
+
|
212
|
+
def __init__(self, name: str, expected_dimension, is_known: bool = True):
|
189
213
|
self.name = name
|
214
|
+
self.symbol: str | None = None # Will be set by EngineeringProblem to attribute name
|
190
215
|
self.expected_dimension = expected_dimension
|
191
|
-
self.quantity:
|
216
|
+
self.quantity: FastQuantity | None = None
|
217
|
+
self.is_known = is_known
|
218
|
+
|
219
|
+
def set(self, value: float):
|
220
|
+
"""Create a setter for this variable using the class-specific setter type."""
|
221
|
+
return self._setter_class(self, value)
|
192
222
|
|
193
|
-
|
194
|
-
|
195
|
-
|
223
|
+
@property
|
224
|
+
def unknown(self) -> Self:
|
225
|
+
"""Mark this variable as unknown using fluent API."""
|
226
|
+
self.is_known = False
|
227
|
+
return self
|
228
|
+
|
229
|
+
@property
|
230
|
+
def known(self) -> Self:
|
231
|
+
"""Mark this variable as known using fluent API."""
|
232
|
+
self.is_known = True
|
233
|
+
return self
|
196
234
|
|
197
235
|
def __str__(self):
|
198
|
-
return f"{self.name}: {self.quantity}" if self.quantity else f"{self.name}: unset"
|
236
|
+
return f"{self.name}: {self.quantity}" if self.quantity else f"{self.name}: unset"
|
237
|
+
|
238
|
+
|
File without changes
|
@@ -0,0 +1,58 @@
|
|
1
|
+
"""
|
2
|
+
Base Variable Module Definition
|
3
|
+
===============================
|
4
|
+
|
5
|
+
Provides abstract base class for variable modules and registration functionality.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from abc import ABC, abstractmethod
|
9
|
+
from typing import Any
|
10
|
+
|
11
|
+
|
12
|
+
class VariableModule(ABC):
|
13
|
+
"""Abstract base class for variable modules."""
|
14
|
+
|
15
|
+
@abstractmethod
|
16
|
+
def get_variable_class(self) -> type[Any]:
|
17
|
+
"""Return the variable class for this module."""
|
18
|
+
pass
|
19
|
+
|
20
|
+
@abstractmethod
|
21
|
+
def get_setter_class(self) -> type[Any]:
|
22
|
+
"""Return the setter class for this module."""
|
23
|
+
pass
|
24
|
+
|
25
|
+
@abstractmethod
|
26
|
+
def get_expected_dimension(self) -> Any:
|
27
|
+
"""Return the expected dimension for this variable type."""
|
28
|
+
pass
|
29
|
+
|
30
|
+
def register_to_registry(self, variable_registry):
|
31
|
+
"""Register this variable module to the given registry."""
|
32
|
+
variable_registry.register_module(
|
33
|
+
self.get_expected_dimension(),
|
34
|
+
self.get_variable_class(),
|
35
|
+
self.get_setter_class()
|
36
|
+
)
|
37
|
+
|
38
|
+
|
39
|
+
class VariableRegistry:
|
40
|
+
"""Registry for variable modules."""
|
41
|
+
|
42
|
+
def __init__(self):
|
43
|
+
self._modules = {}
|
44
|
+
|
45
|
+
def register_module(self, dimension, variable_class, setter_class):
|
46
|
+
"""Register a variable module."""
|
47
|
+
self._modules[dimension] = {
|
48
|
+
'variable_class': variable_class,
|
49
|
+
'setter_class': setter_class
|
50
|
+
}
|
51
|
+
|
52
|
+
def get_variable_class(self, dimension):
|
53
|
+
"""Get variable class for a dimension."""
|
54
|
+
return self._modules.get(dimension, {}).get('variable_class')
|
55
|
+
|
56
|
+
def get_setter_class(self, dimension):
|
57
|
+
"""Get setter class for a dimension."""
|
58
|
+
return self._modules.get(dimension, {}).get('setter_class')
|
@@ -0,0 +1,68 @@
|
|
1
|
+
"""
|
2
|
+
Expression Variable Base Class
|
3
|
+
==============================
|
4
|
+
|
5
|
+
Base class that extends TypeSafeVariable with mathematical expression
|
6
|
+
and equation capabilities.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from __future__ import annotations
|
10
|
+
|
11
|
+
from ..equation import Equation
|
12
|
+
from ..expression import Expression, wrap_operand
|
13
|
+
from ..variable import FastQuantity, TypeSafeVariable
|
14
|
+
|
15
|
+
|
16
|
+
class ExpressionVariable(TypeSafeVariable):
|
17
|
+
"""
|
18
|
+
TypeSafeVariable extended with expression and equation capabilities.
|
19
|
+
|
20
|
+
This adds mathematical operations that create expressions and equations,
|
21
|
+
keeping the base TypeSafeVariable free of these dependencies.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def equals(self, expression: Expression | TypeSafeVariable | FastQuantity | int | float):
|
25
|
+
"""Create an equation: self = expression."""
|
26
|
+
# Wrap the expression in proper Expression type
|
27
|
+
rhs_expr = wrap_operand(expression)
|
28
|
+
return Equation(f"{self.name}_eq", self, rhs_expr)
|
29
|
+
|
30
|
+
def __add__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
31
|
+
"""Add this variable to another operand, returning an Expression."""
|
32
|
+
return wrap_operand(self) + wrap_operand(other)
|
33
|
+
|
34
|
+
def __radd__(self, other: FastQuantity | int | float) -> Expression:
|
35
|
+
"""Reverse add for this variable."""
|
36
|
+
return wrap_operand(other) + wrap_operand(self)
|
37
|
+
|
38
|
+
def __sub__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
39
|
+
"""Subtract another operand from this variable, returning an Expression."""
|
40
|
+
return wrap_operand(self) - wrap_operand(other)
|
41
|
+
|
42
|
+
def __rsub__(self, other: FastQuantity | int | float) -> Expression:
|
43
|
+
"""Reverse subtract for this variable."""
|
44
|
+
return wrap_operand(other) - wrap_operand(self)
|
45
|
+
|
46
|
+
def __mul__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
47
|
+
"""Multiply this variable by another operand, returning an Expression."""
|
48
|
+
return wrap_operand(self) * wrap_operand(other)
|
49
|
+
|
50
|
+
def __rmul__(self, other: FastQuantity | int | float) -> Expression:
|
51
|
+
"""Reverse multiply for this variable."""
|
52
|
+
return wrap_operand(other) * wrap_operand(self)
|
53
|
+
|
54
|
+
def __truediv__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
55
|
+
"""Divide this variable by another operand, returning an Expression."""
|
56
|
+
return wrap_operand(self) / wrap_operand(other)
|
57
|
+
|
58
|
+
def __rtruediv__(self, other: FastQuantity | int | float) -> Expression:
|
59
|
+
"""Reverse divide for this variable."""
|
60
|
+
return wrap_operand(other) / wrap_operand(self)
|
61
|
+
|
62
|
+
def __pow__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
63
|
+
"""Raise this variable to a power, returning an Expression."""
|
64
|
+
return wrap_operand(self) ** wrap_operand(other)
|
65
|
+
|
66
|
+
def __rpow__(self, other: FastQuantity | int | float) -> Expression:
|
67
|
+
"""Reverse power for this variable."""
|
68
|
+
return wrap_operand(other) ** wrap_operand(self)
|
@@ -0,0 +1,92 @@
|
|
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 ..dimension import DimensionSignature
|
10
|
+
from ..variable import TypeSafeSetter
|
11
|
+
from .expression_variable import ExpressionVariable
|
12
|
+
|
13
|
+
|
14
|
+
class TypedVariable(ExpressionVariable):
|
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
|
+
def __init__(self, *args, is_known: bool = True):
|
29
|
+
"""
|
30
|
+
Flexible constructor supporting multiple syntaxes.
|
31
|
+
|
32
|
+
Single argument: TypedVariable("name")
|
33
|
+
Three arguments: TypedVariable(value, "unit", "name")
|
34
|
+
Two arguments (Dimensionless only): TypedVariable(value, "name")
|
35
|
+
"""
|
36
|
+
if self._setter_class is None or self._expected_dimension is None:
|
37
|
+
raise NotImplementedError("Subclass must define _setter_class and _expected_dimension")
|
38
|
+
|
39
|
+
# Handle different argument patterns
|
40
|
+
if len(args) == 1:
|
41
|
+
# Original syntax: Variable("name")
|
42
|
+
super().__init__(args[0], self._expected_dimension, is_known=is_known)
|
43
|
+
|
44
|
+
elif len(args) == 2 and self.__class__.__name__ == 'Dimensionless':
|
45
|
+
# Special case for Dimensionless: (value, "name")
|
46
|
+
value, name = args
|
47
|
+
super().__init__(name, self._expected_dimension, is_known=is_known)
|
48
|
+
setter = self._setter_class(self, value)
|
49
|
+
# For DimensionlessSetter, use the dimensionless property
|
50
|
+
# Type ignore since we know DimensionlessSetter has this property
|
51
|
+
getattr(setter, 'dimensionless', None) # type: ignore
|
52
|
+
|
53
|
+
elif len(args) == 3:
|
54
|
+
# New syntax: Variable(value, "unit", "name")
|
55
|
+
# But Dimensionless doesn't support this pattern
|
56
|
+
if self.__class__.__name__ == 'Dimensionless':
|
57
|
+
raise ValueError(f"{self.__class__.__name__} expects either 1 argument (name) or 2 arguments (value, name), got {len(args)}")
|
58
|
+
|
59
|
+
value, unit, name = args
|
60
|
+
super().__init__(name, self._expected_dimension, is_known=is_known)
|
61
|
+
|
62
|
+
# Auto-set the value with the specified unit
|
63
|
+
setter = self._setter_class(self, value)
|
64
|
+
|
65
|
+
# Handle special unit aliases
|
66
|
+
if unit == "in": # Handle Python reserved word
|
67
|
+
unit = "inchs" # Match the actual property name
|
68
|
+
elif unit == "inches": # Handle common plural form
|
69
|
+
unit = "inchs" # Match the actual property name
|
70
|
+
|
71
|
+
# Try to find the unit property on the setter
|
72
|
+
if hasattr(setter, unit):
|
73
|
+
getattr(setter, unit)
|
74
|
+
elif hasattr(setter, unit + 's'): # Handle singular/plural
|
75
|
+
getattr(setter, unit + 's')
|
76
|
+
elif self._default_unit_property and hasattr(setter, self._default_unit_property):
|
77
|
+
# Fall back to default unit
|
78
|
+
getattr(setter, self._default_unit_property)
|
79
|
+
else:
|
80
|
+
# Last resort - try to find any valid unit property
|
81
|
+
# This helps with forward compatibility
|
82
|
+
unit_properties = [attr for attr in dir(setter)
|
83
|
+
if not attr.startswith('_') and attr != 'value' and attr != 'variable']
|
84
|
+
if unit_properties:
|
85
|
+
getattr(setter, unit_properties[0])
|
86
|
+
|
87
|
+
else:
|
88
|
+
# More specific error messages matching test expectations
|
89
|
+
if self.__class__.__name__ == 'Dimensionless':
|
90
|
+
raise ValueError(f"{self.__class__.__name__} expects either 1 argument (name) or 2 arguments (value, name), got {len(args)}")
|
91
|
+
else:
|
92
|
+
raise ValueError(f"{self.__class__.__name__} expects either 1 argument (name) or 3 arguments (value, unit, name), got {len(args)}")
|